mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-08-19 10:22:23 +00:00
Merge branch 'master' of github.com:ethereum/go-ethereum into whisper/drop_light_clients
This commit is contained in:
commit
23811249d7
132 changed files with 4651 additions and 1216 deletions
|
|
@ -30,8 +30,6 @@ matrix:
|
|||
go: 1.10.x
|
||||
script:
|
||||
- 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 test -coverage $TEST_PACKAGES
|
||||
|
||||
|
|
|
|||
|
|
@ -106,7 +106,7 @@ type Wallet interface {
|
|||
// 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.
|
||||
// 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
|
||||
// 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
|
||||
|
|
|
|||
11
build/ci.go
11
build/ci.go
|
|
@ -644,17 +644,6 @@ func (meta debMetadata) ExeName(exe debExecutable) string {
|
|||
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
|
||||
// for executable packages.
|
||||
func (meta debMetadata) ExeConflicts(exe debExecutable) string {
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ Vcs-Browser: https://github.com/ethereum/go-ethereum
|
|||
|
||||
Package: {{.Name}}
|
||||
Architecture: any
|
||||
Depends: ${misc:Depends}, {{.EthereumSwarmPackageName}}, {{.ExeList}}
|
||||
Depends: ${misc:Depends}, {{.ExeList}}
|
||||
Description: Meta-package to install geth, swarm, and other tools
|
||||
Meta-package to install geth, swarm and other tools
|
||||
|
||||
|
|
|
|||
|
|
@ -48,7 +48,6 @@ var (
|
|||
ArgsUsage: "<genesisPath>",
|
||||
Flags: []cli.Flag{
|
||||
utils.DataDirFlag,
|
||||
utils.LightModeFlag,
|
||||
},
|
||||
Category: "BLOCKCHAIN COMMANDS",
|
||||
Description: `
|
||||
|
|
@ -66,7 +65,7 @@ It expects the genesis file as argument.`,
|
|||
Flags: []cli.Flag{
|
||||
utils.DataDirFlag,
|
||||
utils.CacheFlag,
|
||||
utils.LightModeFlag,
|
||||
utils.SyncModeFlag,
|
||||
utils.GCModeFlag,
|
||||
utils.CacheDatabaseFlag,
|
||||
utils.CacheGCFlag,
|
||||
|
|
@ -87,7 +86,7 @@ processing will proceed even if an individual RLP-file import failure occurs.`,
|
|||
Flags: []cli.Flag{
|
||||
utils.DataDirFlag,
|
||||
utils.CacheFlag,
|
||||
utils.LightModeFlag,
|
||||
utils.SyncModeFlag,
|
||||
},
|
||||
Category: "BLOCKCHAIN COMMANDS",
|
||||
Description: `
|
||||
|
|
@ -105,7 +104,7 @@ be gzipped.`,
|
|||
Flags: []cli.Flag{
|
||||
utils.DataDirFlag,
|
||||
utils.CacheFlag,
|
||||
utils.LightModeFlag,
|
||||
utils.SyncModeFlag,
|
||||
},
|
||||
Category: "BLOCKCHAIN COMMANDS",
|
||||
Description: `
|
||||
|
|
@ -119,7 +118,7 @@ be gzipped.`,
|
|||
Flags: []cli.Flag{
|
||||
utils.DataDirFlag,
|
||||
utils.CacheFlag,
|
||||
utils.LightModeFlag,
|
||||
utils.SyncModeFlag,
|
||||
},
|
||||
Category: "BLOCKCHAIN COMMANDS",
|
||||
Description: `
|
||||
|
|
@ -149,7 +148,6 @@ The first argument must be the directory containing the blockchain to download f
|
|||
ArgsUsage: " ",
|
||||
Flags: []cli.Flag{
|
||||
utils.DataDirFlag,
|
||||
utils.LightModeFlag,
|
||||
},
|
||||
Category: "BLOCKCHAIN COMMANDS",
|
||||
Description: `
|
||||
|
|
@ -163,7 +161,7 @@ Remove blockchain and state databases`,
|
|||
Flags: []cli.Flag{
|
||||
utils.DataDirFlag,
|
||||
utils.CacheFlag,
|
||||
utils.LightModeFlag,
|
||||
utils.SyncModeFlag,
|
||||
},
|
||||
Category: "BLOCKCHAIN COMMANDS",
|
||||
Description: `
|
||||
|
|
|
|||
|
|
@ -72,6 +72,7 @@ var (
|
|||
utils.EthashDatasetDirFlag,
|
||||
utils.EthashDatasetsInMemoryFlag,
|
||||
utils.EthashDatasetsOnDiskFlag,
|
||||
utils.TxPoolLocalsFlag,
|
||||
utils.TxPoolNoLocalsFlag,
|
||||
utils.TxPoolJournalFlag,
|
||||
utils.TxPoolRejournalFlag,
|
||||
|
|
@ -82,8 +83,6 @@ var (
|
|||
utils.TxPoolAccountQueueFlag,
|
||||
utils.TxPoolGlobalQueueFlag,
|
||||
utils.TxPoolLifetimeFlag,
|
||||
utils.FastSyncFlag,
|
||||
utils.LightModeFlag,
|
||||
utils.SyncModeFlag,
|
||||
utils.GCModeFlag,
|
||||
utils.LightServFlag,
|
||||
|
|
@ -96,12 +95,19 @@ var (
|
|||
utils.ListenPortFlag,
|
||||
utils.MaxPeersFlag,
|
||||
utils.MaxPendingPeersFlag,
|
||||
utils.EtherbaseFlag,
|
||||
utils.GasPriceFlag,
|
||||
utils.MiningEnabledFlag,
|
||||
utils.MinerThreadsFlag,
|
||||
utils.MinerLegacyThreadsFlag,
|
||||
utils.MinerNotifyFlag,
|
||||
utils.TargetGasLimitFlag,
|
||||
utils.MinerGasTargetFlag,
|
||||
utils.MinerLegacyGasTargetFlag,
|
||||
utils.MinerGasPriceFlag,
|
||||
utils.MinerLegacyGasPriceFlag,
|
||||
utils.MinerEtherbaseFlag,
|
||||
utils.MinerLegacyEtherbaseFlag,
|
||||
utils.MinerExtraDataFlag,
|
||||
utils.MinerLegacyExtraDataFlag,
|
||||
utils.MinerRecommitIntervalFlag,
|
||||
utils.NATFlag,
|
||||
utils.NoDiscoverFlag,
|
||||
utils.DiscoveryV5Flag,
|
||||
|
|
@ -122,7 +128,6 @@ var (
|
|||
utils.NoCompactionFlag,
|
||||
utils.GpoBlocksFlag,
|
||||
utils.GpoPercentileFlag,
|
||||
utils.ExtraDataFlag,
|
||||
configFileFlag,
|
||||
}
|
||||
|
||||
|
|
@ -325,25 +330,25 @@ func startNode(ctx *cli.Context, stack *node.Node) {
|
|||
// Start auxiliary services if enabled
|
||||
if ctx.GlobalBool(utils.MiningEnabledFlag.Name) || ctx.GlobalBool(utils.DeveloperFlag.Name) {
|
||||
// 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")
|
||||
}
|
||||
var ethereum *eth.Ethereum
|
||||
if err := stack.Service(ðereum); err != nil {
|
||||
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
|
||||
ethereum.TxPool().SetGasPrice(utils.GlobalBig(ctx, utils.GasPriceFlag.Name))
|
||||
if err := ethereum.StartMining(true); err != nil {
|
||||
gasprice := utils.GlobalBig(ctx, utils.MinerLegacyGasPriceFlag.Name)
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -114,6 +114,7 @@ var AppHelpFlagGroups = []flagGroup{
|
|||
{
|
||||
Name: "TRANSACTION POOL",
|
||||
Flags: []cli.Flag{
|
||||
utils.TxPoolLocalsFlag,
|
||||
utils.TxPoolNoLocalsFlag,
|
||||
utils.TxPoolJournalFlag,
|
||||
utils.TxPoolRejournalFlag,
|
||||
|
|
@ -186,10 +187,11 @@ var AppHelpFlagGroups = []flagGroup{
|
|||
utils.MiningEnabledFlag,
|
||||
utils.MinerThreadsFlag,
|
||||
utils.MinerNotifyFlag,
|
||||
utils.EtherbaseFlag,
|
||||
utils.TargetGasLimitFlag,
|
||||
utils.GasPriceFlag,
|
||||
utils.ExtraDataFlag,
|
||||
utils.MinerGasPriceFlag,
|
||||
utils.MinerGasTargetFlag,
|
||||
utils.MinerEtherbaseFlag,
|
||||
utils.MinerExtraDataFlag,
|
||||
utils.MinerRecommitIntervalFlag,
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
@ -231,8 +233,11 @@ var AppHelpFlagGroups = []flagGroup{
|
|||
{
|
||||
Name: "DEPRECATED",
|
||||
Flags: []cli.Flag{
|
||||
utils.FastSyncFlag,
|
||||
utils.LightModeFlag,
|
||||
utils.MinerLegacyThreadsFlag,
|
||||
utils.MinerLegacyGasTargetFlag,
|
||||
utils.MinerLegacyGasPriceFlag,
|
||||
utils.MinerLegacyEtherbaseFlag,
|
||||
utils.MinerLegacyExtraDataFlag,
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ ADD genesis.json /genesis.json
|
|||
RUN \
|
||||
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 $'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.gasprice {{.GasPrice}}' >> geth.sh
|
||||
|
||||
ENTRYPOINT ["/bin/sh", "geth.sh"]
|
||||
`
|
||||
|
|
|
|||
|
|
@ -45,33 +45,44 @@ type sshClient struct {
|
|||
|
||||
// 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
|
||||
// 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) {
|
||||
// Figure out a label for the server and a logger
|
||||
label := server
|
||||
if strings.Contains(label, ":") {
|
||||
label = label[:strings.Index(label, ":")]
|
||||
}
|
||||
login := ""
|
||||
// Figure out username, identity, hostname and port
|
||||
hostname := ""
|
||||
hostport := server
|
||||
username := ""
|
||||
identity := "id_rsa" // default
|
||||
|
||||
if strings.Contains(server, "@") {
|
||||
login = label[:strings.Index(label, "@")]
|
||||
label = label[strings.Index(label, "@")+1:]
|
||||
server = server[strings.Index(server, "@")+1:]
|
||||
prefix := server[:strings.Index(server, "@")]
|
||||
if strings.Contains(prefix, ":") {
|
||||
username = prefix[:strings.Index(prefix, ":")]
|
||||
identity = prefix[strings.Index(prefix, ":")+1:]
|
||||
} else {
|
||||
username = prefix
|
||||
}
|
||||
hostport = server[strings.Index(server, "@")+1:]
|
||||
}
|
||||
logger := log.New("server", label)
|
||||
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")
|
||||
|
||||
user, err := user.Current()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if login == "" {
|
||||
login = user.Username
|
||||
if username == "" {
|
||||
username = user.Username
|
||||
}
|
||||
// Configure the supported authentication methods (private key and password)
|
||||
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 {
|
||||
log.Warn("No SSH key, falling back to passwords", "path", path, "err", err)
|
||||
} else {
|
||||
|
|
@ -94,14 +105,14 @@ func dial(server string, pubkey []byte) (*sshClient, 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()))
|
||||
|
||||
fmt.Println()
|
||||
return string(blob), err
|
||||
}))
|
||||
// Resolve the IP address of the remote server
|
||||
addr, err := net.LookupHost(label)
|
||||
addr, err := net.LookupHost(hostname)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -109,10 +120,7 @@ func dial(server string, pubkey []byte) (*sshClient, error) {
|
|||
return nil, errors.New("no IPs associated with domain")
|
||||
}
|
||||
// Try to dial in to the remote server
|
||||
logger.Trace("Dialing remote SSH server", "user", login)
|
||||
if !strings.Contains(server, ":") {
|
||||
server += ":22"
|
||||
}
|
||||
logger.Trace("Dialing remote SSH server", "user", username)
|
||||
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 pubkey == nil {
|
||||
|
|
@ -139,13 +147,13 @@ func dial(server string, pubkey []byte) (*sshClient, error) {
|
|||
// We have a mismatch, forbid connecting
|
||||
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 {
|
||||
return nil, err
|
||||
}
|
||||
// Connection established, return our utility wrapper
|
||||
c := &sshClient{
|
||||
server: label,
|
||||
server: hostname,
|
||||
address: addr[0],
|
||||
pubkey: pubkey,
|
||||
client: client,
|
||||
|
|
|
|||
|
|
@ -82,7 +82,6 @@ func (w *wizard) gatherStats(server string, pubkey []byte, client *sshClient) *s
|
|||
logger.Info("Starting remote server health-check")
|
||||
|
||||
stat := &serverStat{
|
||||
address: client.address,
|
||||
services: make(map[string]map[string]string),
|
||||
}
|
||||
if client == nil {
|
||||
|
|
@ -94,6 +93,8 @@ func (w *wizard) gatherStats(server string, pubkey []byte, client *sshClient) *s
|
|||
}
|
||||
client = conn
|
||||
}
|
||||
stat.address = client.address
|
||||
|
||||
// Client connected one way or another, run health-checks
|
||||
logger.Debug("Checking for nginx availability")
|
||||
if infos, err := checkNginx(client, w.network); err != nil {
|
||||
|
|
@ -214,6 +215,9 @@ func (stats serverStats) render() {
|
|||
if len(stat.address) > len(separator[1]) {
|
||||
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 {
|
||||
if len(service) > len(separator[2]) {
|
||||
separator[2] = strings.Repeat("-", len(service))
|
||||
|
|
@ -250,7 +254,11 @@ func (stats serverStats) render() {
|
|||
sort.Strings(services)
|
||||
|
||||
if len(services) == 0 {
|
||||
table.Append([]string{server, stats[server].address, "", "", ""})
|
||||
if stats[server].failure != "" {
|
||||
table.Append([]string{server, stats[server].failure, "", "", ""})
|
||||
} else {
|
||||
table.Append([]string{server, stats[server].address, "", "", ""})
|
||||
}
|
||||
}
|
||||
for j, service := range services {
|
||||
// Add an empty line between all services
|
||||
|
|
|
|||
|
|
@ -62,14 +62,14 @@ func (w *wizard) manageServers() {
|
|||
}
|
||||
}
|
||||
|
||||
// makeServer reads a single line from stdin and interprets it as a hostname to
|
||||
// connect to. It tries to establish a new SSH session and also executing some
|
||||
// baseline validations.
|
||||
// makeServer reads a single line from stdin and interprets it as
|
||||
// username:identity@hostname to connect to. It tries to establish a
|
||||
// new SSH session and also executing some baseline validations.
|
||||
//
|
||||
// If connection succeeds, the server is added to the wizards configs!
|
||||
func (w *wizard) makeServer() string {
|
||||
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
|
||||
input := w.readString()
|
||||
|
|
|
|||
219
cmd/swarm/access.go
Normal file
219
cmd/swarm/access.go
Normal file
|
|
@ -0,0 +1,219 @@
|
|||
// Copyright 2018 The go-ethereum Authors
|
||||
// This file is part of go-ethereum.
|
||||
//
|
||||
// go-ethereum is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// go-ethereum is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||
package main
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
|
||||
"github.com/ethereum/go-ethereum/cmd/utils"
|
||||
"github.com/ethereum/go-ethereum/swarm/api"
|
||||
"github.com/ethereum/go-ethereum/swarm/api/client"
|
||||
"gopkg.in/urfave/cli.v1"
|
||||
)
|
||||
|
||||
var salt = make([]byte, 32)
|
||||
|
||||
func init() {
|
||||
if _, err := io.ReadFull(rand.Reader, salt); err != nil {
|
||||
panic("reading from crypto/rand failed: " + err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func accessNewPass(ctx *cli.Context) {
|
||||
args := ctx.Args()
|
||||
if len(args) != 1 {
|
||||
utils.Fatalf("Expected 1 argument - the ref")
|
||||
}
|
||||
|
||||
var (
|
||||
ae *api.AccessEntry
|
||||
accessKey []byte
|
||||
err error
|
||||
ref = args[0]
|
||||
password = getPassPhrase("", 0, makePasswordList(ctx))
|
||||
dryRun = ctx.Bool(SwarmDryRunFlag.Name)
|
||||
)
|
||||
accessKey, ae, err = api.DoPasswordNew(ctx, password, salt)
|
||||
if err != nil {
|
||||
utils.Fatalf("error getting session key: %v", err)
|
||||
}
|
||||
m, err := api.GenerateAccessControlManifest(ctx, ref, accessKey, ae)
|
||||
if dryRun {
|
||||
err = printManifests(m, nil)
|
||||
if err != nil {
|
||||
utils.Fatalf("had an error printing the manifests: %v", err)
|
||||
}
|
||||
} else {
|
||||
utils.Fatalf("uploading manifests")
|
||||
err = uploadManifests(ctx, m, nil)
|
||||
if err != nil {
|
||||
utils.Fatalf("had an error uploading the manifests: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func accessNewPK(ctx *cli.Context) {
|
||||
args := ctx.Args()
|
||||
if len(args) != 1 {
|
||||
utils.Fatalf("Expected 1 argument - the ref")
|
||||
}
|
||||
|
||||
var (
|
||||
ae *api.AccessEntry
|
||||
sessionKey []byte
|
||||
err error
|
||||
ref = args[0]
|
||||
privateKey = getPrivKey(ctx)
|
||||
granteePublicKey = ctx.String(SwarmAccessGrantKeyFlag.Name)
|
||||
dryRun = ctx.Bool(SwarmDryRunFlag.Name)
|
||||
)
|
||||
sessionKey, ae, err = api.DoPKNew(ctx, privateKey, granteePublicKey, salt)
|
||||
if err != nil {
|
||||
utils.Fatalf("error getting session key: %v", err)
|
||||
}
|
||||
m, err := api.GenerateAccessControlManifest(ctx, ref, sessionKey, ae)
|
||||
if dryRun {
|
||||
err = printManifests(m, nil)
|
||||
if err != nil {
|
||||
utils.Fatalf("had an error printing the manifests: %v", err)
|
||||
}
|
||||
} else {
|
||||
err = uploadManifests(ctx, m, nil)
|
||||
if err != nil {
|
||||
utils.Fatalf("had an error uploading the manifests: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func accessNewACT(ctx *cli.Context) {
|
||||
args := ctx.Args()
|
||||
if len(args) != 1 {
|
||||
utils.Fatalf("Expected 1 argument - the ref")
|
||||
}
|
||||
|
||||
var (
|
||||
ae *api.AccessEntry
|
||||
actManifest *api.Manifest
|
||||
accessKey []byte
|
||||
err error
|
||||
ref = args[0]
|
||||
grantees = []string{}
|
||||
actFilename = ctx.String(SwarmAccessGrantKeysFlag.Name)
|
||||
privateKey = getPrivKey(ctx)
|
||||
dryRun = ctx.Bool(SwarmDryRunFlag.Name)
|
||||
)
|
||||
|
||||
bytes, err := ioutil.ReadFile(actFilename)
|
||||
if err != nil {
|
||||
utils.Fatalf("had an error reading the grantee public key list")
|
||||
}
|
||||
grantees = strings.Split(string(bytes), "\n")
|
||||
accessKey, ae, actManifest, err = api.DoACTNew(ctx, privateKey, salt, grantees)
|
||||
if err != nil {
|
||||
utils.Fatalf("error generating ACT manifest: %v", err)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
utils.Fatalf("error getting session key: %v", err)
|
||||
}
|
||||
m, err := api.GenerateAccessControlManifest(ctx, ref, accessKey, ae)
|
||||
if err != nil {
|
||||
utils.Fatalf("error generating root access manifest: %v", err)
|
||||
}
|
||||
|
||||
if dryRun {
|
||||
err = printManifests(m, actManifest)
|
||||
if err != nil {
|
||||
utils.Fatalf("had an error printing the manifests: %v", err)
|
||||
}
|
||||
} else {
|
||||
err = uploadManifests(ctx, m, actManifest)
|
||||
if err != nil {
|
||||
utils.Fatalf("had an error uploading the manifests: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func printManifests(rootAccessManifest, actManifest *api.Manifest) error {
|
||||
js, err := json.Marshal(rootAccessManifest)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println(string(js))
|
||||
|
||||
if actManifest != nil {
|
||||
js, err := json.Marshal(actManifest)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println(string(js))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func uploadManifests(ctx *cli.Context, rootAccessManifest, actManifest *api.Manifest) error {
|
||||
bzzapi := strings.TrimRight(ctx.GlobalString(SwarmApiFlag.Name), "/")
|
||||
client := client.NewClient(bzzapi)
|
||||
|
||||
var (
|
||||
key string
|
||||
err error
|
||||
)
|
||||
if actManifest != nil {
|
||||
key, err = client.UploadManifest(actManifest, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rootAccessManifest.Entries[0].Access.Act = key
|
||||
}
|
||||
key, err = client.UploadManifest(rootAccessManifest, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println(key)
|
||||
return nil
|
||||
}
|
||||
|
||||
// makePasswordList reads password lines from the file specified by the global --password flag
|
||||
// and also by the same subcommand --password flag.
|
||||
// This function ia a fork of utils.MakePasswordList to lookup cli context for subcommand.
|
||||
// Function ctx.SetGlobal is not setting the global flag value that can be accessed
|
||||
// by ctx.GlobalString using the current version of cli package.
|
||||
func makePasswordList(ctx *cli.Context) []string {
|
||||
path := ctx.GlobalString(utils.PasswordFileFlag.Name)
|
||||
if path == "" {
|
||||
path = ctx.String(utils.PasswordFileFlag.Name)
|
||||
if path == "" {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
text, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
utils.Fatalf("Failed to read password file: %v", err)
|
||||
}
|
||||
lines := strings.Split(string(text), "\n")
|
||||
// Sanitise DOS line endings.
|
||||
for i := range lines {
|
||||
lines[i] = strings.TrimRight(lines[i], "\r")
|
||||
}
|
||||
return lines
|
||||
}
|
||||
581
cmd/swarm/access_test.go
Normal file
581
cmd/swarm/access_test.go
Normal file
|
|
@ -0,0 +1,581 @@
|
|||
// 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"
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
gorand "math/rand"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/crypto/sha3"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/swarm/api"
|
||||
swarm "github.com/ethereum/go-ethereum/swarm/api/client"
|
||||
)
|
||||
|
||||
// TestAccessPassword tests for the correct creation of an ACT manifest protected by a password.
|
||||
// The test creates bogus content, uploads it encrypted, then creates the wrapping manifest with the Access entry
|
||||
// The parties participating - node (publisher), uploads to second node then disappears. Content which was uploaded
|
||||
// is then fetched through 2nd node. since the tested code is not key-aware - we can just
|
||||
// fetch from the 2nd node using HTTP BasicAuth
|
||||
func TestAccessPassword(t *testing.T) {
|
||||
cluster := newTestCluster(t, 1)
|
||||
defer cluster.Shutdown()
|
||||
proxyNode := cluster.Nodes[0]
|
||||
|
||||
// create a tmp file
|
||||
tmp, err := ioutil.TempDir("", "swarm-test")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(tmp)
|
||||
|
||||
// write data to file
|
||||
data := "notsorandomdata"
|
||||
dataFilename := filepath.Join(tmp, "data.txt")
|
||||
|
||||
err = ioutil.WriteFile(dataFilename, []byte(data), 0666)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
hashRegexp := `[a-f\d]{128}`
|
||||
|
||||
// upload the file with 'swarm up' and expect a hash
|
||||
up := runSwarm(t,
|
||||
"--bzzapi",
|
||||
proxyNode.URL, //it doesn't matter through which node we upload content
|
||||
"up",
|
||||
"--encrypt",
|
||||
dataFilename)
|
||||
_, matches := up.ExpectRegexp(hashRegexp)
|
||||
up.ExpectExit()
|
||||
|
||||
if len(matches) < 1 {
|
||||
t.Fatal("no matches found")
|
||||
}
|
||||
|
||||
ref := matches[0]
|
||||
|
||||
password := "smth"
|
||||
passwordFilename := filepath.Join(tmp, "password.txt")
|
||||
|
||||
err = ioutil.WriteFile(passwordFilename, []byte(password), 0666)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
up = runSwarm(t,
|
||||
"access",
|
||||
"new",
|
||||
"pass",
|
||||
"--dry-run",
|
||||
"--password",
|
||||
passwordFilename,
|
||||
ref,
|
||||
)
|
||||
|
||||
_, matches = up.ExpectRegexp(".+")
|
||||
up.ExpectExit()
|
||||
|
||||
if len(matches) == 0 {
|
||||
t.Fatalf("stdout not matched")
|
||||
}
|
||||
|
||||
var m api.Manifest
|
||||
|
||||
err = json.Unmarshal([]byte(matches[0]), &m)
|
||||
if err != nil {
|
||||
t.Fatalf("unmarshal manifest: %v", err)
|
||||
}
|
||||
|
||||
if len(m.Entries) != 1 {
|
||||
t.Fatalf("expected one manifest entry, got %v", len(m.Entries))
|
||||
}
|
||||
|
||||
e := m.Entries[0]
|
||||
|
||||
ct := "application/bzz-manifest+json"
|
||||
if e.ContentType != ct {
|
||||
t.Errorf("expected %q content type, got %q", ct, e.ContentType)
|
||||
}
|
||||
|
||||
if e.Access == nil {
|
||||
t.Fatal("manifest access is nil")
|
||||
}
|
||||
|
||||
a := e.Access
|
||||
|
||||
if a.Type != "pass" {
|
||||
t.Errorf(`got access type %q, expected "pass"`, a.Type)
|
||||
}
|
||||
if len(a.Salt) < 32 {
|
||||
t.Errorf(`got salt with length %v, expected not less the 32 bytes`, len(a.Salt))
|
||||
}
|
||||
if a.KdfParams == nil {
|
||||
t.Fatal("manifest access kdf params is nil")
|
||||
}
|
||||
|
||||
client := swarm.NewClient(cluster.Nodes[0].URL)
|
||||
|
||||
hash, err := client.UploadManifest(&m, false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
httpClient := &http.Client{}
|
||||
|
||||
url := cluster.Nodes[0].URL + "/" + "bzz:/" + hash
|
||||
response, err := httpClient.Get(url)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if response.StatusCode != http.StatusUnauthorized {
|
||||
t.Fatal("should be a 401")
|
||||
}
|
||||
authHeader := response.Header.Get("WWW-Authenticate")
|
||||
if authHeader == "" {
|
||||
t.Fatal("should be something here")
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(http.MethodGet, url, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
req.SetBasicAuth("", password)
|
||||
|
||||
response, err = http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer response.Body.Close()
|
||||
|
||||
if response.StatusCode != http.StatusOK {
|
||||
t.Errorf("expected status %v, got %v", http.StatusOK, response.StatusCode)
|
||||
}
|
||||
d, err := ioutil.ReadAll(response.Body)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if string(d) != data {
|
||||
t.Errorf("expected decrypted data %q, got %q", data, string(d))
|
||||
}
|
||||
|
||||
wrongPasswordFilename := filepath.Join(tmp, "password-wrong.txt")
|
||||
|
||||
err = ioutil.WriteFile(wrongPasswordFilename, []byte("just wr0ng"), 0666)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
//download file with 'swarm down' with wrong password
|
||||
up = runSwarm(t,
|
||||
"--bzzapi",
|
||||
proxyNode.URL,
|
||||
"down",
|
||||
"bzz:/"+hash,
|
||||
tmp,
|
||||
"--password",
|
||||
wrongPasswordFilename)
|
||||
|
||||
_, matches = up.ExpectRegexp("unauthorized")
|
||||
if len(matches) != 1 && matches[0] != "unauthorized" {
|
||||
t.Fatal(`"unauthorized" not found in output"`)
|
||||
}
|
||||
up.ExpectExit()
|
||||
}
|
||||
|
||||
// TestAccessPK tests for the correct creation of an ACT manifest between two parties (publisher and grantee).
|
||||
// The test creates bogus content, uploads it encrypted, then creates the wrapping manifest with the Access entry
|
||||
// The parties participating - node (publisher), uploads to second node (which is also the grantee) then disappears.
|
||||
// Content which was uploaded is then fetched through the grantee's http proxy. Since the tested code is private-key aware,
|
||||
// the test will fail if the proxy's given private key is not granted on the ACT.
|
||||
func TestAccessPK(t *testing.T) {
|
||||
// Setup Swarm and upload a test file to it
|
||||
cluster := newTestCluster(t, 1)
|
||||
defer cluster.Shutdown()
|
||||
|
||||
// create a tmp file
|
||||
tmp, err := ioutil.TempFile("", "swarm-test")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer tmp.Close()
|
||||
defer os.Remove(tmp.Name())
|
||||
|
||||
// write data to file
|
||||
data := "notsorandomdata"
|
||||
_, err = io.WriteString(tmp, data)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
hashRegexp := `[a-f\d]{128}`
|
||||
|
||||
// upload the file with 'swarm up' and expect a hash
|
||||
up := runSwarm(t,
|
||||
"--bzzapi",
|
||||
cluster.Nodes[0].URL,
|
||||
"up",
|
||||
"--encrypt",
|
||||
tmp.Name())
|
||||
_, matches := up.ExpectRegexp(hashRegexp)
|
||||
up.ExpectExit()
|
||||
|
||||
if len(matches) < 1 {
|
||||
t.Fatal("no matches found")
|
||||
}
|
||||
|
||||
ref := matches[0]
|
||||
|
||||
pk := cluster.Nodes[0].PrivateKey
|
||||
granteePubKey := crypto.CompressPubkey(&pk.PublicKey)
|
||||
|
||||
publisherDir, err := ioutil.TempDir("", "swarm-account-dir-temp")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
passFile, err := ioutil.TempFile("", "swarm-test")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer passFile.Close()
|
||||
defer os.Remove(passFile.Name())
|
||||
_, err = io.WriteString(passFile, testPassphrase)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, publisherAccount := getTestAccount(t, publisherDir)
|
||||
up = runSwarm(t,
|
||||
"--bzzaccount",
|
||||
publisherAccount.Address.String(),
|
||||
"--password",
|
||||
passFile.Name(),
|
||||
"--datadir",
|
||||
publisherDir,
|
||||
"--bzzapi",
|
||||
cluster.Nodes[0].URL,
|
||||
"access",
|
||||
"new",
|
||||
"pk",
|
||||
"--dry-run",
|
||||
"--grant-key",
|
||||
hex.EncodeToString(granteePubKey),
|
||||
ref,
|
||||
)
|
||||
|
||||
_, matches = up.ExpectRegexp(".+")
|
||||
up.ExpectExit()
|
||||
|
||||
if len(matches) == 0 {
|
||||
t.Fatalf("stdout not matched")
|
||||
}
|
||||
|
||||
var m api.Manifest
|
||||
|
||||
err = json.Unmarshal([]byte(matches[0]), &m)
|
||||
if err != nil {
|
||||
t.Fatalf("unmarshal manifest: %v", err)
|
||||
}
|
||||
|
||||
if len(m.Entries) != 1 {
|
||||
t.Fatalf("expected one manifest entry, got %v", len(m.Entries))
|
||||
}
|
||||
|
||||
e := m.Entries[0]
|
||||
|
||||
ct := "application/bzz-manifest+json"
|
||||
if e.ContentType != ct {
|
||||
t.Errorf("expected %q content type, got %q", ct, e.ContentType)
|
||||
}
|
||||
|
||||
if e.Access == nil {
|
||||
t.Fatal("manifest access is nil")
|
||||
}
|
||||
|
||||
a := e.Access
|
||||
|
||||
if a.Type != "pk" {
|
||||
t.Errorf(`got access type %q, expected "pk"`, a.Type)
|
||||
}
|
||||
if len(a.Salt) < 32 {
|
||||
t.Errorf(`got salt with length %v, expected not less the 32 bytes`, len(a.Salt))
|
||||
}
|
||||
if a.KdfParams != nil {
|
||||
t.Fatal("manifest access kdf params should be nil")
|
||||
}
|
||||
|
||||
client := swarm.NewClient(cluster.Nodes[0].URL)
|
||||
|
||||
hash, err := client.UploadManifest(&m, false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
httpClient := &http.Client{}
|
||||
|
||||
url := cluster.Nodes[0].URL + "/" + "bzz:/" + hash
|
||||
response, err := httpClient.Get(url)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if response.StatusCode != http.StatusOK {
|
||||
t.Fatal("should be a 200")
|
||||
}
|
||||
d, err := ioutil.ReadAll(response.Body)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if string(d) != data {
|
||||
t.Errorf("expected decrypted data %q, got %q", data, string(d))
|
||||
}
|
||||
}
|
||||
|
||||
// TestAccessACT tests the e2e creation, uploading and downloading of an ACT type access control
|
||||
// the test fires up a 3 node cluster, then randomly picks 2 nodes which will be acting as grantees to the data
|
||||
// set. the third node should fail decoding the reference as it will not be granted access. the publisher uploads through
|
||||
// one of the nodes then disappears.
|
||||
func TestAccessACT(t *testing.T) {
|
||||
// Setup Swarm and upload a test file to it
|
||||
cluster := newTestCluster(t, 3)
|
||||
defer cluster.Shutdown()
|
||||
|
||||
var uploadThroughNode = cluster.Nodes[0]
|
||||
client := swarm.NewClient(uploadThroughNode.URL)
|
||||
|
||||
r1 := gorand.New(gorand.NewSource(time.Now().UnixNano()))
|
||||
nodeToSkip := r1.Intn(3) // a number between 0 and 2 (node indices in `cluster`)
|
||||
// create a tmp file
|
||||
tmp, err := ioutil.TempFile("", "swarm-test")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer tmp.Close()
|
||||
defer os.Remove(tmp.Name())
|
||||
|
||||
// write data to file
|
||||
data := "notsorandomdata"
|
||||
_, err = io.WriteString(tmp, data)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
hashRegexp := `[a-f\d]{128}`
|
||||
|
||||
// upload the file with 'swarm up' and expect a hash
|
||||
up := runSwarm(t,
|
||||
"--bzzapi",
|
||||
cluster.Nodes[0].URL,
|
||||
"up",
|
||||
"--encrypt",
|
||||
tmp.Name())
|
||||
_, matches := up.ExpectRegexp(hashRegexp)
|
||||
up.ExpectExit()
|
||||
|
||||
if len(matches) < 1 {
|
||||
t.Fatal("no matches found")
|
||||
}
|
||||
|
||||
ref := matches[0]
|
||||
grantees := []string{}
|
||||
for i, v := range cluster.Nodes {
|
||||
if i == nodeToSkip {
|
||||
continue
|
||||
}
|
||||
pk := v.PrivateKey
|
||||
granteePubKey := crypto.CompressPubkey(&pk.PublicKey)
|
||||
grantees = append(grantees, hex.EncodeToString(granteePubKey))
|
||||
}
|
||||
|
||||
granteesPubkeyListFile, err := ioutil.TempFile("", "grantees-pubkey-list.csv")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = granteesPubkeyListFile.WriteString(strings.Join(grantees, "\n"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
defer granteesPubkeyListFile.Close()
|
||||
defer os.Remove(granteesPubkeyListFile.Name())
|
||||
|
||||
publisherDir, err := ioutil.TempDir("", "swarm-account-dir-temp")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
passFile, err := ioutil.TempFile("", "swarm-test")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer passFile.Close()
|
||||
defer os.Remove(passFile.Name())
|
||||
_, err = io.WriteString(passFile, testPassphrase)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, publisherAccount := getTestAccount(t, publisherDir)
|
||||
up = runSwarm(t,
|
||||
"--bzzaccount",
|
||||
publisherAccount.Address.String(),
|
||||
"--password",
|
||||
passFile.Name(),
|
||||
"--datadir",
|
||||
publisherDir,
|
||||
"--bzzapi",
|
||||
cluster.Nodes[0].URL,
|
||||
"access",
|
||||
"new",
|
||||
"act",
|
||||
"--grant-keys",
|
||||
granteesPubkeyListFile.Name(),
|
||||
ref,
|
||||
)
|
||||
|
||||
_, matches = up.ExpectRegexp(`[a-f\d]{64}`)
|
||||
up.ExpectExit()
|
||||
|
||||
if len(matches) == 0 {
|
||||
t.Fatalf("stdout not matched")
|
||||
}
|
||||
hash := matches[0]
|
||||
m, _, err := client.DownloadManifest(hash)
|
||||
if err != nil {
|
||||
t.Fatalf("unmarshal manifest: %v", err)
|
||||
}
|
||||
|
||||
if len(m.Entries) != 1 {
|
||||
t.Fatalf("expected one manifest entry, got %v", len(m.Entries))
|
||||
}
|
||||
|
||||
e := m.Entries[0]
|
||||
|
||||
ct := "application/bzz-manifest+json"
|
||||
if e.ContentType != ct {
|
||||
t.Errorf("expected %q content type, got %q", ct, e.ContentType)
|
||||
}
|
||||
|
||||
if e.Access == nil {
|
||||
t.Fatal("manifest access is nil")
|
||||
}
|
||||
|
||||
a := e.Access
|
||||
|
||||
if a.Type != "act" {
|
||||
t.Fatalf(`got access type %q, expected "act"`, a.Type)
|
||||
}
|
||||
if len(a.Salt) < 32 {
|
||||
t.Fatalf(`got salt with length %v, expected not less the 32 bytes`, len(a.Salt))
|
||||
}
|
||||
if a.KdfParams != nil {
|
||||
t.Fatal("manifest access kdf params should be nil")
|
||||
}
|
||||
|
||||
httpClient := &http.Client{}
|
||||
|
||||
// all nodes except the skipped node should be able to decrypt the content
|
||||
for i, node := range cluster.Nodes {
|
||||
log.Debug("trying to fetch from node", "node index", i)
|
||||
|
||||
url := node.URL + "/" + "bzz:/" + hash
|
||||
response, err := httpClient.Get(url)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
log.Debug("got response from node", "response code", response.StatusCode)
|
||||
|
||||
if i == nodeToSkip {
|
||||
log.Debug("reached node to skip", "status code", response.StatusCode)
|
||||
|
||||
if response.StatusCode != http.StatusUnauthorized {
|
||||
t.Fatalf("should be a 401")
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
if response.StatusCode != http.StatusOK {
|
||||
t.Fatal("should be a 200")
|
||||
}
|
||||
d, err := ioutil.ReadAll(response.Body)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if string(d) != data {
|
||||
t.Errorf("expected decrypted data %q, got %q", data, string(d))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestKeypairSanity is a sanity test for the crypto scheme for ACT. it asserts the correct shared secret according to
|
||||
// the specs at https://github.com/ethersphere/swarm-docs/blob/eb857afda906c6e7bb90d37f3f334ccce5eef230/act.md
|
||||
func TestKeypairSanity(t *testing.T) {
|
||||
salt := make([]byte, 32)
|
||||
if _, err := io.ReadFull(rand.Reader, salt); err != nil {
|
||||
t.Fatalf("reading from crypto/rand failed: %v", err.Error())
|
||||
}
|
||||
sharedSecret := "a85586744a1ddd56a7ed9f33fa24f40dd745b3a941be296a0d60e329dbdb896d"
|
||||
|
||||
for i, v := range []struct {
|
||||
publisherPriv string
|
||||
granteePub string
|
||||
}{
|
||||
{
|
||||
publisherPriv: "ec5541555f3bc6376788425e9d1a62f55a82901683fd7062c5eddcc373a73459",
|
||||
granteePub: "0226f213613e843a413ad35b40f193910d26eb35f00154afcde9ded57479a6224a",
|
||||
},
|
||||
{
|
||||
publisherPriv: "70c7a73011aa56584a0009ab874794ee7e5652fd0c6911cd02f8b6267dd82d2d",
|
||||
granteePub: "02e6f8d5e28faaa899744972bb847b6eb805a160494690c9ee7197ae9f619181db",
|
||||
},
|
||||
} {
|
||||
b, _ := hex.DecodeString(v.granteePub)
|
||||
granteePub, _ := crypto.DecompressPubkey(b)
|
||||
publisherPrivate, _ := crypto.HexToECDSA(v.publisherPriv)
|
||||
|
||||
ssKey, err := api.NewSessionKeyPK(publisherPrivate, granteePub, salt)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
hasher := sha3.NewKeccak256()
|
||||
hasher.Write(salt)
|
||||
shared, err := hex.DecodeString(sharedSecret)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
hasher.Write(shared)
|
||||
sum := hasher.Sum(nil)
|
||||
|
||||
if !bytes.Equal(ssKey, sum) {
|
||||
t.Fatalf("%d: got a session key mismatch", i)
|
||||
}
|
||||
}
|
||||
}
|
||||
77
cmd/swarm/bootnodes.go
Normal file
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_CAPACITY = "SWARM_STORE_CAPACITY"
|
||||
SWARM_ENV_STORE_CACHE_CAPACITY = "SWARM_STORE_CACHE_CAPACITY"
|
||||
SWARM_ACCESS_PASSWORD = "SWARM_ACCESS_PASSWORD"
|
||||
GETH_ENV_DATADIR = "GETH_DATADIR"
|
||||
)
|
||||
|
||||
|
|
@ -232,10 +233,6 @@ func cmdLineOverride(currentConfig *bzzapi.Config, ctx *cli.Context) *bzzapi.Con
|
|||
currentConfig.Cors = cors
|
||||
}
|
||||
|
||||
if ctx.GlobalIsSet(utils.BootnodesFlag.Name) {
|
||||
currentConfig.BootNodes = ctx.GlobalString(utils.BootnodesFlag.Name)
|
||||
}
|
||||
|
||||
if storePath := ctx.GlobalString(SwarmStorePath.Name); storePath != "" {
|
||||
currentConfig.LocalStoreParams.ChunkDbPath = storePath
|
||||
}
|
||||
|
|
@ -333,10 +330,6 @@ func envVarsOverride(currentConfig *bzzapi.Config) (config *bzzapi.Config) {
|
|||
currentConfig.Cors = cors
|
||||
}
|
||||
|
||||
if bootnodes := os.Getenv(SWARM_ENV_BOOTNODES); bootnodes != "" {
|
||||
currentConfig.BootNodes = bootnodes
|
||||
}
|
||||
|
||||
return currentConfig
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -68,18 +68,36 @@ func download(ctx *cli.Context) {
|
|||
utils.Fatalf("could not parse uri argument: %v", err)
|
||||
}
|
||||
|
||||
// assume behaviour according to --recursive switch
|
||||
if isRecursive {
|
||||
if err := client.DownloadDirectory(uri.Addr, uri.Path, dest); err != nil {
|
||||
utils.Fatalf("encoutered an error while downloading directory: %v", err)
|
||||
}
|
||||
} else {
|
||||
// we are downloading a file
|
||||
log.Debug(fmt.Sprintf("downloading file/path from a manifest. hash: %s, path:%s", uri.Addr, uri.Path))
|
||||
dl := func(credentials string) error {
|
||||
// assume behaviour according to --recursive switch
|
||||
if isRecursive {
|
||||
if err := client.DownloadDirectory(uri.Addr, uri.Path, dest, credentials); err != nil {
|
||||
if err == swarm.ErrUnauthorized {
|
||||
return err
|
||||
}
|
||||
return fmt.Errorf("directory %s: %v", uri.Path, err)
|
||||
}
|
||||
} else {
|
||||
// we are downloading a file
|
||||
log.Debug("downloading file/path from a manifest", "uri.Addr", uri.Addr, "uri.Path", uri.Path)
|
||||
|
||||
err := client.DownloadFile(uri.Addr, uri.Path, dest)
|
||||
if err != nil {
|
||||
utils.Fatalf("could not download %s from given address: %s. error: %v", uri.Path, uri.Addr, err)
|
||||
err := client.DownloadFile(uri.Addr, uri.Path, dest, credentials)
|
||||
if err != nil {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ func list(ctx *cli.Context) {
|
|||
|
||||
bzzapi := strings.TrimRight(ctx.GlobalString(SwarmApiFlag.Name), "/")
|
||||
client := swarm.NewClient(bzzapi)
|
||||
list, err := client.List(manifest, prefix)
|
||||
list, err := client.List(manifest, prefix, "")
|
||||
if err != nil {
|
||||
utils.Fatalf("Failed to generate file and directory list: %s", err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,7 +37,6 @@ import (
|
|||
"github.com/ethereum/go-ethereum/internal/debug"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/node"
|
||||
"github.com/ethereum/go-ethereum/p2p"
|
||||
"github.com/ethereum/go-ethereum/p2p/discover"
|
||||
"github.com/ethereum/go-ethereum/swarm"
|
||||
bzzapi "github.com/ethereum/go-ethereum/swarm/api"
|
||||
|
|
@ -67,14 +66,7 @@ OPTIONS:
|
|||
`
|
||||
|
||||
var (
|
||||
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",
|
||||
}
|
||||
gitCommit string // Git SHA1 commit hash of the release (set via linker flags)
|
||||
)
|
||||
|
||||
var (
|
||||
|
|
@ -155,6 +147,14 @@ var (
|
|||
Name: "defaultpath",
|
||||
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{
|
||||
Name: "stdin",
|
||||
Usage: "reads data to be uploaded from stdin",
|
||||
|
|
@ -167,6 +167,15 @@ var (
|
|||
Name: "encrypt",
|
||||
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{
|
||||
Name: "corsdomain",
|
||||
Usage: "Domain on which to send Access-Control-Allow-Origin header (multiple domains can be supplied separated by a ',')",
|
||||
|
|
@ -252,6 +261,61 @@ func init() {
|
|||
Flags: []cli.Flag{SwarmEncryptedFlag},
|
||||
Description: "uploads a file or directory to swarm using the HTTP API and prints the root hash",
|
||||
},
|
||||
{
|
||||
CustomHelpTemplate: helpTemplate,
|
||||
Name: "access",
|
||||
Usage: "encrypts a reference and embeds it into a root manifest",
|
||||
ArgsUsage: "<ref>",
|
||||
Description: "encrypts a reference and embeds it into a root manifest",
|
||||
Subcommands: []cli.Command{
|
||||
{
|
||||
CustomHelpTemplate: helpTemplate,
|
||||
Name: "new",
|
||||
Usage: "encrypts a reference and embeds it into a root manifest",
|
||||
ArgsUsage: "<ref>",
|
||||
Description: "encrypts a reference and embeds it into a root access manifest and prints the resulting manifest",
|
||||
Subcommands: []cli.Command{
|
||||
{
|
||||
Action: accessNewPass,
|
||||
CustomHelpTemplate: helpTemplate,
|
||||
Flags: []cli.Flag{
|
||||
utils.PasswordFileFlag,
|
||||
SwarmDryRunFlag,
|
||||
},
|
||||
Name: "pass",
|
||||
Usage: "encrypts a reference with a password and embeds it into a root manifest",
|
||||
ArgsUsage: "<ref>",
|
||||
Description: "encrypts a reference and embeds it into a root access manifest and prints the resulting manifest",
|
||||
},
|
||||
{
|
||||
Action: accessNewPK,
|
||||
CustomHelpTemplate: helpTemplate,
|
||||
Flags: []cli.Flag{
|
||||
utils.PasswordFileFlag,
|
||||
SwarmDryRunFlag,
|
||||
SwarmAccessGrantKeyFlag,
|
||||
},
|
||||
Name: "pk",
|
||||
Usage: "encrypts a reference with the node's private key and a given grantee's public key and embeds it into a root manifest",
|
||||
ArgsUsage: "<ref>",
|
||||
Description: "encrypts a reference and embeds it into a root access manifest and prints the resulting manifest",
|
||||
},
|
||||
{
|
||||
Action: accessNewACT,
|
||||
CustomHelpTemplate: helpTemplate,
|
||||
Flags: []cli.Flag{
|
||||
SwarmAccessGrantKeysFlag,
|
||||
SwarmDryRunFlag,
|
||||
},
|
||||
Name: "act",
|
||||
Usage: "encrypts a reference with the node's private key and a given grantee's public key and embeds it into a root manifest",
|
||||
ArgsUsage: "<ref>",
|
||||
Description: "encrypts a reference and embeds it into a root access manifest and prints the resulting manifest",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
CustomHelpTemplate: helpTemplate,
|
||||
Name: "resource",
|
||||
|
|
@ -304,16 +368,13 @@ func init() {
|
|||
Description: "Prints the swarm hash of file or directory",
|
||||
},
|
||||
{
|
||||
Action: download,
|
||||
Name: "down",
|
||||
Flags: []cli.Flag{SwarmRecursiveFlag},
|
||||
Usage: "downloads a swarm manifest or a file inside a manifest",
|
||||
ArgsUsage: " <uri> [<dir>]",
|
||||
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.
|
||||
`,
|
||||
Action: download,
|
||||
Name: "down",
|
||||
Flags: []cli.Flag{SwarmRecursiveFlag, SwarmAccessPasswordFlag},
|
||||
Usage: "downloads a swarm manifest or a file inside a manifest",
|
||||
ArgsUsage: " <uri> [<dir>]",
|
||||
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.`,
|
||||
},
|
||||
|
||||
{
|
||||
Name: "manifest",
|
||||
CustomHelpTemplate: helpTemplate,
|
||||
|
|
@ -413,16 +474,14 @@ pv(1) tool to get a progress bar:
|
|||
Name: "import",
|
||||
Usage: "import chunks from a tar archive into a local chunk database (use - to read from stdin)",
|
||||
ArgsUsage: "<chunkdb> <file>",
|
||||
Description: `
|
||||
Import chunks from a tar archive into a local chunk database (use - to read from stdin).
|
||||
Description: `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
|
||||
|
||||
The import may be quite large, consider piping the input through the Unix
|
||||
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,
|
||||
|
|
@ -535,6 +594,7 @@ func version(ctx *cli.Context) error {
|
|||
func bzzd(ctx *cli.Context) error {
|
||||
//build a valid bzzapi.Config from all available sources:
|
||||
//default config, file config, command line and env vars
|
||||
|
||||
bzzconfig, err := buildConfig(ctx)
|
||||
if err != nil {
|
||||
utils.Fatalf("unable to configure swarm: %v", err)
|
||||
|
|
@ -551,12 +611,16 @@ func bzzd(ctx *cli.Context) error {
|
|||
if _, err := os.Stat(bzzconfig.Path); err == nil {
|
||||
cfg.DataDir = bzzconfig.Path
|
||||
}
|
||||
|
||||
//optionally set the bootnodes before configuring the node
|
||||
setSwarmBootstrapNodes(ctx, &cfg)
|
||||
//setup the ethereum node
|
||||
utils.SetNodeConfig(ctx, &cfg)
|
||||
stack, err := node.New(&cfg)
|
||||
if err != nil {
|
||||
utils.Fatalf("can't create node: %v", err)
|
||||
}
|
||||
|
||||
//a few steps need to be done after the config phase is completed,
|
||||
//due to overriding behavior
|
||||
initSwarmNode(bzzconfig, stack, ctx)
|
||||
|
|
@ -574,16 +638,6 @@ func bzzd(ctx *cli.Context) error {
|
|||
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()
|
||||
return nil
|
||||
}
|
||||
|
|
@ -691,17 +745,6 @@ func getPassPhrase(prompt string, i int, passwords []string) string {
|
|||
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
|
||||
// a basic help subcommand to each
|
||||
// if a help command is already defined, it will take precedence over the default.
|
||||
|
|
@ -714,3 +757,20 @@ func addDefaultHelpSubcommands(commands []cli.Command) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
func setSwarmBootstrapNodes(ctx *cli.Context, cfg *node.Config) {
|
||||
if ctx.GlobalIsSet(utils.BootnodesFlag.Name) || ctx.GlobalIsSet(utils.BootnodesV4Flag.Name) {
|
||||
return
|
||||
}
|
||||
|
||||
cfg.P2P.BootstrapNodes = []*discover.Node{}
|
||||
|
||||
for _, url := range SwarmBootnodes {
|
||||
node, err := discover.ParseNode(url)
|
||||
if err != nil {
|
||||
log.Error("Bootstrap URL invalid", "enode", url, "err", err)
|
||||
}
|
||||
cfg.P2P.BootstrapNodes = append(cfg.P2P.BootstrapNodes, node)
|
||||
}
|
||||
log.Debug("added default swarm bootnodes", "length", len(cfg.P2P.BootstrapNodes))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,10 +18,12 @@ package main
|
|||
|
||||
import (
|
||||
"context"
|
||||
"crypto/ecdsa"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"sync"
|
||||
|
|
@ -175,14 +177,15 @@ func (c *testCluster) Cleanup() {
|
|||
}
|
||||
|
||||
type testNode struct {
|
||||
Name string
|
||||
Addr string
|
||||
URL string
|
||||
Enode string
|
||||
Dir string
|
||||
IpcPath string
|
||||
Client *rpc.Client
|
||||
Cmd *cmdtest.TestCmd
|
||||
Name string
|
||||
Addr string
|
||||
URL string
|
||||
Enode string
|
||||
Dir string
|
||||
IpcPath string
|
||||
PrivateKey *ecdsa.PrivateKey
|
||||
Client *rpc.Client
|
||||
Cmd *cmdtest.TestCmd
|
||||
}
|
||||
|
||||
const testPassphrase = "swarm-test-passphrase"
|
||||
|
|
@ -289,7 +292,11 @@ func existingTestNode(t *testing.T, dir string, bzzaccount string) *testNode {
|
|||
func newTestNode(t *testing.T, dir string) *testNode {
|
||||
|
||||
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
|
||||
ports, err := getAvailableTCPPorts(2)
|
||||
|
|
|
|||
|
|
@ -157,14 +157,6 @@ var (
|
|||
Usage: "Document Root for HTTPClient file scheme",
|
||||
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
|
||||
SyncModeFlag = TextMarshalerFlag{
|
||||
Name: "syncmode",
|
||||
|
|
@ -241,6 +233,10 @@ var (
|
|||
Value: eth.DefaultConfig.Ethash.DatasetsOnDisk,
|
||||
}
|
||||
// Transaction pool settings
|
||||
TxPoolLocalsFlag = cli.StringFlag{
|
||||
Name: "txpool.locals",
|
||||
Usage: "Comma separated accounts to treat as locals (no flush, priority inclusion)",
|
||||
}
|
||||
TxPoolNoLocalsFlag = cli.BoolFlag{
|
||||
Name: "txpool.nolocals",
|
||||
Usage: "Disables price exemptions for locally submitted transactions",
|
||||
|
|
@ -321,29 +317,58 @@ var (
|
|||
Usage: "Number of CPU threads to use for mining",
|
||||
Value: 0,
|
||||
}
|
||||
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",
|
||||
}
|
||||
TargetGasLimitFlag = cli.Uint64Flag{
|
||||
Name: "targetgaslimit",
|
||||
Usage: "Target gas limit sets the artificial target gas floor for the blocks to mine",
|
||||
MinerGasTargetFlag = cli.Uint64Flag{
|
||||
Name: "miner.gastarget",
|
||||
Usage: "Target gas floor for mined blocks",
|
||||
Value: params.GenesisGasLimit,
|
||||
}
|
||||
EtherbaseFlag = cli.StringFlag{
|
||||
Name: "etherbase",
|
||||
Usage: "Public address for block mining rewards (default = first account created)",
|
||||
MinerLegacyGasTargetFlag = cli.Uint64Flag{
|
||||
Name: "targetgaslimit",
|
||||
Usage: "Target gas floor for mined blocks (deprecated, use --miner.gastarget)",
|
||||
Value: params.GenesisGasLimit,
|
||||
}
|
||||
MinerGasPriceFlag = BigFlag{
|
||||
Name: "miner.gasprice",
|
||||
Usage: "Minimal gas price for mining a transactions",
|
||||
Value: eth.DefaultConfig.MinerGasPrice,
|
||||
}
|
||||
MinerLegacyGasPriceFlag = BigFlag{
|
||||
Name: "gasprice",
|
||||
Usage: "Minimal gas price for mining a transactions (deprecated, use --miner.gasprice)",
|
||||
Value: eth.DefaultConfig.MinerGasPrice,
|
||||
}
|
||||
MinerEtherbaseFlag = cli.StringFlag{
|
||||
Name: "miner.etherbase",
|
||||
Usage: "Public address for block mining rewards (default = first account)",
|
||||
Value: "0",
|
||||
}
|
||||
GasPriceFlag = BigFlag{
|
||||
Name: "gasprice",
|
||||
Usage: "Minimal gas price to accept for mining a transactions",
|
||||
Value: eth.DefaultConfig.GasPrice,
|
||||
MinerLegacyEtherbaseFlag = cli.StringFlag{
|
||||
Name: "etherbase",
|
||||
Usage: "Public address for block mining rewards (default = first account, deprecated, use --miner.etherbase)",
|
||||
Value: "0",
|
||||
}
|
||||
ExtraDataFlag = cli.StringFlag{
|
||||
Name: "extradata",
|
||||
MinerExtraDataFlag = cli.StringFlag{
|
||||
Name: "miner.extradata",
|
||||
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,
|
||||
}
|
||||
// Account settings
|
||||
UnlockedAccountFlag = cli.StringFlag{
|
||||
Name: "unlock",
|
||||
|
|
@ -817,10 +842,19 @@ func MakeAddress(ks *keystore.KeyStore, account string) (accounts.Account, error
|
|||
// setEtherbase retrieves the etherbase either from the directly specified
|
||||
// command line flags or from the keystore if CLI indexed.
|
||||
func setEtherbase(ctx *cli.Context, ks *keystore.KeyStore, cfg *eth.Config) {
|
||||
if ctx.GlobalIsSet(EtherbaseFlag.Name) {
|
||||
account, err := MakeAddress(ks, ctx.GlobalString(EtherbaseFlag.Name))
|
||||
// Extract the current etherbase, new flag overriding legacy one
|
||||
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 {
|
||||
Fatalf("Option %q: %v", EtherbaseFlag.Name, err)
|
||||
Fatalf("Invalid miner etherbase: %v", err)
|
||||
}
|
||||
cfg.Etherbase = account.Address
|
||||
}
|
||||
|
|
@ -851,7 +885,7 @@ func SetP2PConfig(ctx *cli.Context, cfg *p2p.Config) {
|
|||
setBootstrapNodes(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
|
||||
lightPeers := ctx.GlobalInt(LightPeersFlag.Name)
|
||||
|
||||
|
|
@ -951,6 +985,16 @@ func setGPO(ctx *cli.Context, cfg *gasprice.Config) {
|
|||
}
|
||||
|
||||
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) {
|
||||
cfg.NoLocals = ctx.GlobalBool(TxPoolNoLocalsFlag.Name)
|
||||
}
|
||||
|
|
@ -1059,8 +1103,6 @@ func SetShhConfig(ctx *cli.Context, stack *node.Node, cfg *whisper.Config) {
|
|||
func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *eth.Config) {
|
||||
// Avoid conflicting network flags
|
||||
checkExclusive(ctx, DeveloperFlag, TestnetFlag, RinkebyFlag)
|
||||
checkExclusive(ctx, FastSyncFlag, LightModeFlag, SyncModeFlag)
|
||||
checkExclusive(ctx, LightServFlag, LightModeFlag)
|
||||
checkExclusive(ctx, LightServFlag, SyncModeFlag, "light")
|
||||
|
||||
ks := stack.AccountManager().Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore)
|
||||
|
|
@ -1069,13 +1111,8 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *eth.Config) {
|
|||
setTxPool(ctx, &cfg.TxPool)
|
||||
setEthash(ctx, cfg)
|
||||
|
||||
switch {
|
||||
case ctx.GlobalIsSet(SyncModeFlag.Name):
|
||||
if ctx.GlobalIsSet(SyncModeFlag.Name) {
|
||||
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) {
|
||||
cfg.LightServ = ctx.GlobalInt(LightServFlag.Name)
|
||||
|
|
@ -1100,20 +1137,26 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *eth.Config) {
|
|||
if ctx.GlobalIsSet(CacheFlag.Name) || ctx.GlobalIsSet(CacheGCFlag.Name) {
|
||||
cfg.TrieCache = ctx.GlobalInt(CacheFlag.Name) * ctx.GlobalInt(CacheGCFlag.Name) / 100
|
||||
}
|
||||
if ctx.GlobalIsSet(MinerThreadsFlag.Name) {
|
||||
cfg.MinerThreads = ctx.GlobalInt(MinerThreadsFlag.Name)
|
||||
}
|
||||
if ctx.GlobalIsSet(MinerNotifyFlag.Name) {
|
||||
cfg.MinerNotify = strings.Split(ctx.GlobalString(MinerNotifyFlag.Name), ",")
|
||||
}
|
||||
if ctx.GlobalIsSet(DocRootFlag.Name) {
|
||||
cfg.DocRoot = ctx.GlobalString(DocRootFlag.Name)
|
||||
}
|
||||
if ctx.GlobalIsSet(ExtraDataFlag.Name) {
|
||||
cfg.ExtraData = []byte(ctx.GlobalString(ExtraDataFlag.Name))
|
||||
if ctx.GlobalIsSet(MinerLegacyExtraDataFlag.Name) {
|
||||
cfg.MinerExtraData = []byte(ctx.GlobalString(MinerLegacyExtraDataFlag.Name))
|
||||
}
|
||||
if ctx.GlobalIsSet(GasPriceFlag.Name) {
|
||||
cfg.GasPrice = GlobalBig(ctx, GasPriceFlag.Name)
|
||||
if ctx.GlobalIsSet(MinerExtraDataFlag.Name) {
|
||||
cfg.MinerExtraData = []byte(ctx.GlobalString(MinerExtraDataFlag.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(VMEnableDebugFlag.Name) {
|
||||
// TODO(fjl): force-enable this in --dev mode
|
||||
|
|
@ -1155,8 +1198,8 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *eth.Config) {
|
|||
log.Info("Using developer account", "address", developer.Address)
|
||||
|
||||
cfg.Genesis = core.DeveloperGenesisBlock(uint64(ctx.GlobalInt(DeveloperPeriodFlag.Name)), developer.Address)
|
||||
if !ctx.GlobalIsSet(GasPriceFlag.Name) {
|
||||
cfg.GasPrice = big.NewInt(1)
|
||||
if !ctx.GlobalIsSet(MinerGasPriceFlag.Name) && !ctx.GlobalIsSet(MinerLegacyGasPriceFlag.Name) {
|
||||
cfg.MinerGasPrice = big.NewInt(1)
|
||||
}
|
||||
}
|
||||
// TODO(fjl): move trie cache generations into config
|
||||
|
|
@ -1230,7 +1273,10 @@ 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)
|
||||
params.TargetGasLimit = ctx.GlobalUint64(MinerLegacyGasTargetFlag.Name)
|
||||
if ctx.GlobalIsSet(MinerGasTargetFlag.Name) {
|
||||
params.TargetGasLimit = ctx.GlobalUint64(MinerGasTargetFlag.Name)
|
||||
}
|
||||
}
|
||||
|
||||
func SetupMetrics(ctx *cli.Context) {
|
||||
|
|
@ -1261,7 +1307,7 @@ func MakeChainDatabase(ctx *cli.Context, stack *node.Node) ethdb.Database {
|
|||
handles = makeDatabaseHandles()
|
||||
)
|
||||
name := "chaindata"
|
||||
if ctx.GlobalBool(LightModeFlag.Name) {
|
||||
if ctx.GlobalString(SyncModeFlag.Name) == "light" {
|
||||
name = "lightchaindata"
|
||||
}
|
||||
chainDb, err := stack.OpenDatabase(name, cache, handles)
|
||||
|
|
|
|||
|
|
@ -387,22 +387,23 @@ func (c *Clique) snapshot(chain consensus.ChainReader, number uint64, hash commo
|
|||
break
|
||||
}
|
||||
}
|
||||
// If we're at block zero, make a snapshot
|
||||
if number == 0 {
|
||||
genesis := chain.GetHeaderByNumber(0)
|
||||
if err := c.VerifyHeader(chain, genesis, false); err != nil {
|
||||
return nil, err
|
||||
// If we're at an checkpoint block, make a snapshot if it's known
|
||||
if number%c.config.Epoch == 0 {
|
||||
checkpoint := chain.GetHeaderByNumber(number)
|
||||
if checkpoint != nil {
|
||||
hash := checkpoint.Hash()
|
||||
|
||||
signers := make([]common.Address, (len(checkpoint.Extra)-extraVanity-extraSeal)/common.AddressLength)
|
||||
for i := 0; i < len(signers); i++ {
|
||||
copy(signers[i][:], checkpoint.Extra[extraVanity+i*common.AddressLength:])
|
||||
}
|
||||
snap = newSnapshot(c.config, c.signatures, number, hash, signers)
|
||||
if err := snap.store(c.db); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
log.Info("Stored checkpoint snapshot to disk", "number", number, "hash", hash)
|
||||
break
|
||||
}
|
||||
signers := make([]common.Address, (len(genesis.Extra)-extraVanity-extraSeal)/common.AddressLength)
|
||||
for i := 0; i < len(signers); i++ {
|
||||
copy(signers[i][:], genesis.Extra[extraVanity+i*common.AddressLength:])
|
||||
}
|
||||
snap = newSnapshot(c.config, c.signatures, 0, genesis.Hash(), signers)
|
||||
if err := snap.store(c.db); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
log.Trace("Stored genesis voting snapshot to disk")
|
||||
break
|
||||
}
|
||||
// No snapshot for this header, gather the header and move backward
|
||||
var header *types.Header
|
||||
|
|
@ -672,6 +673,11 @@ func CalcDifficulty(snap *Snapshot, signer common.Address) *big.Int {
|
|||
return new(big.Int).Set(diffNoTurn)
|
||||
}
|
||||
|
||||
// SealHash returns the hash of a block prior to it being sealed.
|
||||
func (c *Clique) SealHash(header *types.Header) common.Hash {
|
||||
return sigHash(header)
|
||||
}
|
||||
|
||||
// Close implements consensus.Engine. It's a noop for clique as there is are no background threads.
|
||||
func (c *Clique) Close() error {
|
||||
return nil
|
||||
|
|
|
|||
|
|
@ -84,7 +84,7 @@ func (r *testerChainReader) GetHeaderByNumber(number uint64) *types.Header {
|
|||
if number == 0 {
|
||||
return rawdb.ReadHeader(r.db, rawdb.ReadCanonicalHash(r.db, 0), 0)
|
||||
}
|
||||
panic("not supported")
|
||||
return nil
|
||||
}
|
||||
|
||||
// Tests that voting is evaluated correctly for various simple and complex scenarios.
|
||||
|
|
|
|||
|
|
@ -90,6 +90,9 @@ type Engine interface {
|
|||
// seal place on top.
|
||||
Seal(chain ChainReader, block *types.Block, stop <-chan struct{}) (*types.Block, 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
|
||||
// that a new block should have.
|
||||
CalcDifficulty(chain ChainReader, time uint64, parent *types.Header) *big.Int
|
||||
|
|
|
|||
|
|
@ -31,7 +31,9 @@ import (
|
|||
"github.com/ethereum/go-ethereum/consensus/misc"
|
||||
"github.com/ethereum/go-ethereum/core/state"
|
||||
"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/rlp"
|
||||
)
|
||||
|
||||
// Ethash proof-of-work protocol constants.
|
||||
|
|
@ -461,6 +463,13 @@ func calcDifficultyFrontier(time uint64, parent *types.Header) *big.Int {
|
|||
// VerifySeal implements consensus.Engine, checking whether the given block satisfies
|
||||
// the PoW difficulty requirements.
|
||||
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 ethash.config.PowMode == ModeFake || ethash.config.PowMode == ModeFullFake {
|
||||
time.Sleep(ethash.fakeDelay)
|
||||
|
|
@ -471,25 +480,48 @@ func (ethash *Ethash) VerifySeal(chain consensus.ChainReader, header *types.Head
|
|||
}
|
||||
// If we're running a shared PoW, delegate verification to it
|
||||
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
|
||||
if header.Difficulty.Sign() <= 0 {
|
||||
return errInvalidDifficulty
|
||||
}
|
||||
// Recompute the digest and PoW value and verify against the header
|
||||
// Recompute the digest and PoW values
|
||||
number := header.Number.Uint64()
|
||||
|
||||
cache := ethash.cache(number)
|
||||
size := datasetSize(number)
|
||||
if ethash.config.PowMode == ModeTest {
|
||||
size = 32 * 1024
|
||||
}
|
||||
digest, result := hashimotoLight(size, cache.cache, header.HashNoNonce().Bytes(), header.Nonce.Uint64())
|
||||
// Caches are unmapped in a finalizer. Ensure that the cache stays live
|
||||
// until after the call to hashimotoLight so it's not unmapped while being used.
|
||||
runtime.KeepAlive(cache)
|
||||
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)
|
||||
|
||||
size := datasetSize(number)
|
||||
if ethash.config.PowMode == ModeTest {
|
||||
size = 32 * 1024
|
||||
}
|
||||
digest, result = hashimotoLight(size, cache.cache, ethash.SealHash(header).Bytes(), header.Nonce.Uint64())
|
||||
|
||||
// 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.
|
||||
runtime.KeepAlive(cache)
|
||||
}
|
||||
// Verify the calculated values against the ones provided in the header
|
||||
if !bytes.Equal(header.MixDigest[:], digest) {
|
||||
return errInvalidMixDigest
|
||||
}
|
||||
|
|
@ -522,6 +554,29 @@ func (ethash *Ethash) Finalize(chain consensus.ChainReader, header *types.Header
|
|||
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.
|
||||
var (
|
||||
big8 = big.NewInt(8)
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ import (
|
|||
"runtime"
|
||||
"strconv"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
|
|
@ -281,6 +282,7 @@ type dataset struct {
|
|||
mmap mmap.MMap // Memory map itself to unmap before releasing
|
||||
dataset []uint32 // The actual cache data content
|
||||
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
|
||||
|
|
@ -292,6 +294,9 @@ func newDataset(epoch uint64) interface{} {
|
|||
// generate ensures that the dataset content is generated before use.
|
||||
func (d *dataset) generate(dir string, limit int, test bool) {
|
||||
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)
|
||||
dsize := datasetSize(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)
|
||||
generateDataset(d.dataset, d.epoch, cache)
|
||||
|
||||
return
|
||||
}
|
||||
// Disk storage is needed, this will get fancy
|
||||
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.
|
||||
func (d *dataset) finalizer() {
|
||||
if d.mmap != nil {
|
||||
|
|
@ -589,20 +603,34 @@ func (ethash *Ethash) cache(block uint64) *cache {
|
|||
// 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
|
||||
// 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
|
||||
currentI, futureI := ethash.datasets.get(epoch)
|
||||
current := currentI.(*dataset)
|
||||
|
||||
// Wait for generation finish.
|
||||
current.generate(ethash.config.DatasetDir, ethash.config.DatasetsOnDisk, ethash.config.PowMode == ModeTest)
|
||||
// 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 we need a new future dataset, now's a good time to regenerate it.
|
||||
if futureI != nil {
|
||||
future := futureI.(*dataset)
|
||||
go future.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)
|
||||
|
||||
if futureI != nil {
|
||||
future := futureI.(*dataset)
|
||||
go future.generate(ethash.config.DatasetDir, ethash.config.DatasetsOnDisk, ethash.config.PowMode == ModeTest)
|
||||
}
|
||||
}
|
||||
|
||||
return current
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -94,6 +94,7 @@ func TestRemoteSealer(t *testing.T) {
|
|||
}
|
||||
header := &types.Header{Number: big.NewInt(1), Difficulty: big.NewInt(100)}
|
||||
block := types.NewBlockWithHeader(header)
|
||||
sealhash := ethash.SealHash(header)
|
||||
|
||||
// Push new work.
|
||||
ethash.Seal(nil, block, nil)
|
||||
|
|
@ -102,27 +103,29 @@ func TestRemoteSealer(t *testing.T) {
|
|||
work [3]string
|
||||
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")
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
||||
// Push new block with same block number to replace the original one.
|
||||
header = &types.Header{Number: big.NewInt(1), Difficulty: big.NewInt(1000)}
|
||||
block = types.NewBlockWithHeader(header)
|
||||
sealhash = ethash.SealHash(header)
|
||||
ethash.Seal(nil, block, 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")
|
||||
}
|
||||
// Push block with higher block number.
|
||||
newHead := &types.Header{Number: big.NewInt(2), Difficulty: big.NewInt(100)}
|
||||
newBlock := types.NewBlockWithHeader(newHead)
|
||||
newSealhash := ethash.SealHash(newHead)
|
||||
ethash.Seal(nil, newBlock, nil)
|
||||
|
||||
if res := api.SubmitWork(types.BlockNonce{}, block.HashNoNonce(), common.Hash{}); res {
|
||||
if res := api.SubmitWork(types.BlockNonce{}, newSealhash, common.Hash{}); res {
|
||||
t.Error("expect to return false when submit a stale solution")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -111,10 +111,10 @@ func (ethash *Ethash) mine(block *types.Block, id int, seed uint64, abort chan s
|
|||
// Extract some data from the header
|
||||
var (
|
||||
header = block.Header()
|
||||
hash = header.HashNoNonce().Bytes()
|
||||
hash = ethash.SealHash(header).Bytes()
|
||||
target = new(big.Int).Div(two256, header.Difficulty)
|
||||
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
|
||||
var (
|
||||
|
|
@ -213,7 +213,7 @@ func (ethash *Ethash) remote(notify []string) {
|
|||
// result[1], 32 bytes hex encoded seed hash used for DAG
|
||||
// result[2], 32 bytes hex encoded boundary condition ("target"), 2^256/difficulty
|
||||
makeWork := func(block *types.Block) {
|
||||
hash := block.HashNoNonce()
|
||||
hash := ethash.SealHash(block.Header())
|
||||
|
||||
currentWork[0] = hash.Hex()
|
||||
currentWork[1] = common.BytesToHash(SeedHash(block.NumberU64())).Hex()
|
||||
|
|
@ -233,21 +233,22 @@ func (ethash *Ethash) remote(notify []string) {
|
|||
log.Info("Work submitted but none pending", "hash", hash)
|
||||
return false
|
||||
}
|
||||
|
||||
// Verify the correctness of submitted result.
|
||||
header := block.Header()
|
||||
header.Nonce = nonce
|
||||
header.MixDigest = mixDigest
|
||||
if err := ethash.VerifySeal(nil, header); err != nil {
|
||||
log.Warn("Invalid proof-of-work submitted", "hash", hash, "err", err)
|
||||
|
||||
start := time.Now()
|
||||
if err := ethash.verifySeal(nil, header, true); err != nil {
|
||||
log.Warn("Invalid proof-of-work submitted", "hash", hash, "elapsed", time.Since(start), "err", err)
|
||||
return false
|
||||
}
|
||||
|
||||
// Make sure the result channel is created.
|
||||
if ethash.resultCh == nil {
|
||||
log.Warn("Ethash result channel is empty, submitted mining result is rejected")
|
||||
return false
|
||||
}
|
||||
log.Trace("Verified correct proof-of-work", "hash", hash, "elapsed", time.Since(start))
|
||||
|
||||
// Solutions seems to be valid, return to the miner and notify acceptance.
|
||||
select {
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ func TestRemoteNotify(t *testing.T) {
|
|||
ethash.Seal(nil, block, nil)
|
||||
select {
|
||||
case work := <-sink:
|
||||
if want := header.HashNoNonce().Hex(); work[0] != want {
|
||||
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 {
|
||||
|
|
@ -70,7 +70,7 @@ func TestRemoteNotify(t *testing.T) {
|
|||
// issues in the notifications.
|
||||
func TestRemoteMultiNotify(t *testing.T) {
|
||||
// Start a simple webserver to capture notifications
|
||||
sink := make(chan [3]string, 1024)
|
||||
sink := make(chan [3]string, 64)
|
||||
|
||||
server := &http.Server{
|
||||
Handler: http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
|
|
|
|||
|
|
@ -314,7 +314,7 @@ func (c *Console) Interactive() {
|
|||
input = "" // Current user 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() {
|
||||
for {
|
||||
// Read the next user input
|
||||
|
|
|
|||
|
|
@ -201,7 +201,7 @@ func TestInteractive(t *testing.T) {
|
|||
|
||||
go tester.console.Interactive()
|
||||
|
||||
// Wait for a promt and send a statement back
|
||||
// Wait for a prompt and send a statement back
|
||||
select {
|
||||
case <-tester.input.scheduler:
|
||||
case <-time.After(time.Second):
|
||||
|
|
@ -212,7 +212,7 @@ func TestInteractive(t *testing.T) {
|
|||
case <-time.After(time.Second):
|
||||
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 {
|
||||
case <-tester.input.scheduler:
|
||||
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
|
||||
// pretty printed instead of just displaing "[object]".
|
||||
// pretty printed instead of just displaying "[object]".
|
||||
func TestPrettyPrint(t *testing.T) {
|
||||
tester := newTester(t, nil)
|
||||
defer tester.Close(t)
|
||||
|
|
@ -300,7 +300,7 @@ func TestIndenting(t *testing.T) {
|
|||
}{
|
||||
{`var a = 1;`, 0},
|
||||
{`"some string"`, 0},
|
||||
{`"some string with (parentesis`, 0},
|
||||
{`"some string with (parenthesis`, 0},
|
||||
{`"some string with newline
|
||||
("`, 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.
|
||||
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.
|
||||
type UserPrompter interface {
|
||||
// PromptInput displays the given prompt to the user and requests some textual
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
package core
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"sync"
|
||||
|
|
@ -37,11 +38,11 @@ import (
|
|||
type ChainIndexerBackend interface {
|
||||
// Reset initiates the processing of a new chain segment, potentially terminating
|
||||
// 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
|
||||
// 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() error
|
||||
|
|
@ -71,9 +72,11 @@ type ChainIndexer struct {
|
|||
backend ChainIndexerBackend // Background processor generating the index data content
|
||||
children []*ChainIndexer // Child indexers to cascade chain updates to
|
||||
|
||||
active uint32 // Flag whether the event loop was started
|
||||
update chan struct{} // Notification channel that headers should be processed
|
||||
quit chan chan error // Quit channel to tear down running goroutines
|
||||
active uint32 // Flag whether the event loop was started
|
||||
update chan struct{} // Notification channel that headers should be processed
|
||||
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
|
||||
confirmsReq uint64 // Number of confirmations before processing a completed segment
|
||||
|
|
@ -105,6 +108,8 @@ func NewChainIndexer(chainDb, indexDb ethdb.Database, backend ChainIndexerBacken
|
|||
}
|
||||
// Initialize database dependent fields and start the updater
|
||||
c.loadValidSections()
|
||||
c.ctx, c.ctxCancel = context.WithCancel(context.Background())
|
||||
|
||||
go c.updateLoop()
|
||||
|
||||
return c
|
||||
|
|
@ -138,6 +143,8 @@ func (c *ChainIndexer) Start(chain ChainIndexerChain) {
|
|||
func (c *ChainIndexer) Close() error {
|
||||
var errs []error
|
||||
|
||||
c.ctxCancel()
|
||||
|
||||
// Tear down the primary update loop
|
||||
errc := make(chan error)
|
||||
c.quit <- errc
|
||||
|
|
@ -297,6 +304,12 @@ func (c *ChainIndexer) updateLoop() {
|
|||
c.lock.Unlock()
|
||||
newHead, err := c.processSection(section, oldHead)
|
||||
if err != nil {
|
||||
select {
|
||||
case <-c.ctx.Done():
|
||||
<-c.quit <- nil
|
||||
return
|
||||
default:
|
||||
}
|
||||
c.log.Error("Section processing failed", "error", err)
|
||||
}
|
||||
c.lock.Lock()
|
||||
|
|
@ -344,7 +357,7 @@ func (c *ChainIndexer) processSection(section uint64, lastHead common.Hash) (com
|
|||
|
||||
// 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)
|
||||
return common.Hash{}, err
|
||||
}
|
||||
|
|
@ -360,11 +373,12 @@ func (c *ChainIndexer) processSection(section uint64, lastHead common.Hash) (com
|
|||
} else if header.ParentHash != lastHead {
|
||||
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()
|
||||
}
|
||||
if err := c.backend.Commit(); err != nil {
|
||||
c.log.Error("Section commit failed", "error", err)
|
||||
return common.Hash{}, err
|
||||
}
|
||||
return lastHead, nil
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
package core
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"math/rand"
|
||||
|
|
@ -210,13 +211,13 @@ func (b *testChainIndexBackend) reorg(headNum uint64) uint64 {
|
|||
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.headerCnt = 0
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *testChainIndexBackend) Process(header *types.Header) {
|
||||
func (b *testChainIndexBackend) Process(ctx context.Context, header *types.Header) error {
|
||||
b.headerCnt++
|
||||
if b.headerCnt > b.indexer.sectionSize {
|
||||
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")
|
||||
case b.processCh <- header.Number.Uint64():
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *testChainIndexBackend) Commit() error {
|
||||
|
|
|
|||
|
|
@ -29,9 +29,6 @@ type PendingLogsEvent struct {
|
|||
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.
|
||||
type NewMinedBlockEvent struct{ Block *types.Block }
|
||||
|
||||
|
|
|
|||
|
|
@ -489,10 +489,13 @@ func (self *StateDB) Copy() *StateDB {
|
|||
state.stateObjectsDirty[addr] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
for hash, logs := range self.logs {
|
||||
state.logs[hash] = make([]*types.Log, len(logs))
|
||||
copy(state.logs[hash], logs)
|
||||
cpy := make([]*types.Log, len(logs))
|
||||
for i, l := range logs {
|
||||
cpy[i] = new(types.Log)
|
||||
*cpy[i] = *l
|
||||
}
|
||||
state.logs[hash] = cpy
|
||||
}
|
||||
for hash, preimage := range self.preimages {
|
||||
state.preimages[hash] = preimage
|
||||
|
|
|
|||
|
|
@ -123,9 +123,10 @@ type blockChain interface {
|
|||
|
||||
// TxPoolConfig are the configuration parameters of the transaction pool.
|
||||
type TxPoolConfig struct {
|
||||
NoLocals bool // Whether local transaction handling should be disabled
|
||||
Journal string // Journal of local transactions to survive node restarts
|
||||
Rejournal time.Duration // Time interval to regenerate the local transaction journal
|
||||
Locals []common.Address // Addresses that should be treated by default as local
|
||||
NoLocals bool // Whether local transaction handling should be disabled
|
||||
Journal string // Journal of local transactions to survive node restarts
|
||||
Rejournal time.Duration // Time interval to regenerate the local transaction journal
|
||||
|
||||
PriceLimit uint64 // Minimum gas price to enforce for acceptance into the pool
|
||||
PriceBump uint64 // Minimum price bump percentage to replace an already existing transaction (nonce)
|
||||
|
|
@ -231,6 +232,10 @@ func NewTxPool(config TxPoolConfig, chainconfig *params.ChainConfig, chain block
|
|||
gasPrice: new(big.Int).SetUint64(config.PriceLimit),
|
||||
}
|
||||
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.reset(nil, chain.CurrentBlock().Header())
|
||||
|
||||
|
|
@ -534,6 +539,14 @@ func (pool *TxPool) Pending() (map[common.Address]types.Transactions, error) {
|
|||
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
|
||||
// account and sorted by nonce. The returned transaction set is a copy and can be
|
||||
// freely modified by calling code.
|
||||
|
|
@ -665,7 +678,10 @@ func (pool *TxPool) add(tx *types.Transaction, local bool) (bool, error) {
|
|||
}
|
||||
// Mark local addresses and journal local transactions
|
||||
if local {
|
||||
pool.locals.add(from)
|
||||
if !pool.locals.contains(from) {
|
||||
log.Info("Setting new local account", "address", from)
|
||||
pool.locals.add(from)
|
||||
}
|
||||
}
|
||||
pool.journalTx(from, tx)
|
||||
|
||||
|
|
@ -1138,6 +1154,7 @@ func (a addressesByHeartbeat) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
|||
type accountSet struct {
|
||||
accounts map[common.Address]struct{}
|
||||
signer types.Signer
|
||||
cache *[]common.Address
|
||||
}
|
||||
|
||||
// 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.
|
||||
func (as *accountSet) add(addr common.Address) {
|
||||
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
|
||||
|
|
|
|||
|
|
@ -102,25 +102,6 @@ func (h *Header) Hash() common.Hash {
|
|||
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
|
||||
// to approximate and limit the memory consumption of various caches.
|
||||
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.
|
||||
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
|
||||
// and returning it, or returning a previsouly cached value.
|
||||
func (b *Block) Size() common.StorageSize {
|
||||
|
|
|
|||
|
|
@ -119,7 +119,7 @@ func isProtectedV(V *big.Int) bool {
|
|||
v := V.Uint64()
|
||||
return v != 27 && v != 28
|
||||
}
|
||||
// anything not 27 or 28 are considered unprotected
|
||||
// anything not 27 or 28 is considered protected
|
||||
return true
|
||||
}
|
||||
|
||||
|
|
|
|||
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
|
||||
// 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/>.
|
||||
// Copyright 2018 Péter Szilágyi. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be found
|
||||
// in the LICENSE file.
|
||||
|
||||
// +build amd64 arm64
|
||||
|
||||
|
|
|
|||
|
|
@ -1,18 +1,6 @@
|
|||
// 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/>.
|
||||
// Copyright 2018 Péter Szilágyi. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be found
|
||||
// in the LICENSE file.
|
||||
|
||||
// +build gofuzz
|
||||
|
||||
|
|
|
|||
|
|
@ -1,18 +1,6 @@
|
|||
// 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/>.
|
||||
// Copyright 2018 Péter Szilágyi. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be found
|
||||
// in the LICENSE file.
|
||||
|
||||
// +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
|
||||
|
||||
// Jump to a slightly different implementation if MULX isn't supported.
|
||||
CMPB runtime·support_bmi2(SB), $0
|
||||
CMPB ·hasBMI2(SB), $0
|
||||
JE nobmi2Mul
|
||||
|
||||
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
|
||||
// assembly implementations of these functions, provided that they exist.
|
||||
|
||||
import (
|
||||
"golang.org/x/sys/cpu"
|
||||
)
|
||||
|
||||
//nolint:varcheck
|
||||
var hasBMI2 = cpu.X86.HasBMI2
|
||||
|
||||
// go:noescape
|
||||
func gfpNeg(c, a *gfP)
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
// Use of this source code is governed by a BSD-style
|
||||
// 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
|
||||
// that have been proposed over the past decade. They consist of a triplet of
|
||||
|
|
@ -14,6 +14,10 @@
|
|||
// Barreto-Naehrig curve as described in
|
||||
// http://cryptojedi.org/papers/dclxvi-20100714.pdf. Its output is compatible
|
||||
// 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
|
||||
|
||||
import (
|
||||
|
|
@ -50,8 +54,8 @@ func RandomG1(r io.Reader) (*big.Int, *G1, error) {
|
|||
return k, new(G1).ScalarBaseMult(k), nil
|
||||
}
|
||||
|
||||
func (g *G1) String() string {
|
||||
return "bn256.G1" + g.p.String()
|
||||
func (e *G1) String() string {
|
||||
return "bn256.G1" + e.p.String()
|
||||
}
|
||||
|
||||
// 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.
|
||||
func (n *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()
|
||||
|
||||
func (e *G1) Marshal() []byte {
|
||||
// Each value is a 256-bit number.
|
||||
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)
|
||||
copy(ret[1*numBytes-len(xBytes):], xBytes)
|
||||
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
|
||||
}
|
||||
|
||||
func (g *G2) String() string {
|
||||
return "bn256.G2" + g.p.String()
|
||||
func (e *G2) String() string {
|
||||
return "bn256.G2" + e.p.String()
|
||||
}
|
||||
|
||||
// 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.
|
||||
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)
|
||||
|
||||
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()
|
||||
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)
|
||||
copy(ret[1*numBytes-len(xxBytes):], xxBytes)
|
||||
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
|
||||
}
|
||||
|
||||
// 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 {
|
||||
if words := c.z.Bits(); len(words) == 1 && words[0] == 1 {
|
||||
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)
|
||||
t := pool.Get().Mul(c.y, zInv)
|
||||
t.Mod(t, P)
|
||||
|
|
|
|||
|
|
@ -225,11 +225,19 @@ func (c *twistPoint) Mul(a *twistPoint, scalar *big.Int, pool *bnPool) *twistPoi
|
|||
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 {
|
||||
if c.z.IsOne() {
|
||||
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)
|
||||
t := newGFp2(pool).Mul(c.y, zInv, pool)
|
||||
zInv2 := newGFp2(pool).Square(zInv, pool)
|
||||
|
|
|
|||
53
eth/api.go
53
eth/api.go
|
|
@ -24,7 +24,9 @@ import (
|
|||
"io"
|
||||
"math/big"
|
||||
"os"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"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/types"
|
||||
"github.com/ethereum/go-ethereum/internal/ethapi"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
|
|
@ -93,47 +94,22 @@ func NewPrivateMinerAPI(e *Ethereum) *PrivateMinerAPI {
|
|||
return &PrivateMinerAPI{e: e}
|
||||
}
|
||||
|
||||
// Start the miner with the given number of threads. If threads is nil the number
|
||||
// of workers started is equal to the number of logical CPUs that are usable by
|
||||
// this process. If mining is already running, this method adjust the number of
|
||||
// threads allowed to use and updates the minimum price required by the transaction
|
||||
// pool.
|
||||
// Start starts the miner with the given number of threads. If threads is nil,
|
||||
// the number of workers started is equal to the number of logical CPUs that are
|
||||
// usable by this process. If mining is already running, this method adjust the
|
||||
// number of threads allowed to use and updates the minimum price required by the
|
||||
// transaction pool.
|
||||
func (api *PrivateMinerAPI) Start(threads *int) error {
|
||||
// Set the number of threads if the seal engine supports it
|
||||
if threads == nil {
|
||||
threads = new(int)
|
||||
} else if *threads == 0 {
|
||||
*threads = -1 // Disable the miner from within
|
||||
return api.e.StartMining(runtime.NumCPU())
|
||||
}
|
||||
type threaded interface {
|
||||
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
|
||||
return api.e.StartMining(*threads)
|
||||
}
|
||||
|
||||
// Stop the miner
|
||||
func (api *PrivateMinerAPI) Stop() bool {
|
||||
type threaded interface {
|
||||
SetThreads(threads int)
|
||||
}
|
||||
if th, ok := api.e.engine.(threaded); ok {
|
||||
th.SetThreads(-1)
|
||||
}
|
||||
// Stop terminates the miner, both at the consensus engine level as well as at
|
||||
// the block creation level.
|
||||
func (api *PrivateMinerAPI) Stop() {
|
||||
api.e.StopMining()
|
||||
return true
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// 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.
|
||||
func (api *PrivateMinerAPI) GetHashrate() uint64 {
|
||||
return api.e.miner.HashRate()
|
||||
|
|
|
|||
|
|
@ -119,6 +119,9 @@ func (api *PrivateDebugAPI) TraceChain(ctx context.Context, start, end rpc.Block
|
|||
if to == nil {
|
||||
return nil, fmt.Errorf("end block #%d not found", end)
|
||||
}
|
||||
if from.Number().Cmp(to.Number()) >= 0 {
|
||||
return nil, fmt.Errorf("end block (#%d) needs to come after start block (#%d)", end, start)
|
||||
}
|
||||
return api.traceChain(ctx, from, to, config)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -102,12 +102,18 @@ func (s *Ethereum) AddLesServer(ls LesServer) {
|
|||
// New creates a new Ethereum object (including the
|
||||
// initialisation of the common Ethereum object)
|
||||
func New(ctx *node.ServiceContext, config *Config) (*Ethereum, error) {
|
||||
// Ensure configuration values are compatible and sane
|
||||
if config.SyncMode == downloader.LightSync {
|
||||
return nil, errors.New("can't run eth.Ethereum in light sync mode, use les.LightEthereum")
|
||||
}
|
||||
if !config.SyncMode.IsValid() {
|
||||
return nil, fmt.Errorf("invalid sync mode %d", config.SyncMode)
|
||||
}
|
||||
if config.MinerGasPrice == nil || config.MinerGasPrice.Cmp(common.Big0) <= 0 {
|
||||
log.Warn("Sanitizing invalid miner gas price", "provided", config.MinerGasPrice, "updated", DefaultConfig.MinerGasPrice)
|
||||
config.MinerGasPrice = new(big.Int).Set(DefaultConfig.MinerGasPrice)
|
||||
}
|
||||
// Assemble the Ethereum object
|
||||
chainDb, err := CreateDB(ctx, config, "chaindata")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
@ -127,10 +133,10 @@ func New(ctx *node.ServiceContext, config *Config) (*Ethereum, error) {
|
|||
engine: CreateConsensusEngine(ctx, chainConfig, &config.Ethash, config.MinerNotify, chainDb),
|
||||
shutdownChan: make(chan bool),
|
||||
networkID: config.NetworkId,
|
||||
gasPrice: config.GasPrice,
|
||||
gasPrice: config.MinerGasPrice,
|
||||
etherbase: config.Etherbase,
|
||||
bloomRequests: make(chan chan *bloombits.Retrieval),
|
||||
bloomIndexer: NewBloomIndexer(chainDb, params.BloomBitsBlocks),
|
||||
bloomIndexer: NewBloomIndexer(chainDb, params.BloomBitsBlocks, bloomConfirms),
|
||||
}
|
||||
|
||||
log.Info("Initialising Ethereum protocol", "versions", ProtocolVersions, "network", config.NetworkId)
|
||||
|
|
@ -138,7 +144,7 @@ func New(ctx *node.ServiceContext, config *Config) (*Ethereum, error) {
|
|||
if !config.SkipBcVersionCheck {
|
||||
bcVersion := rawdb.ReadDatabaseVersion(chainDb)
|
||||
if bcVersion != core.BlockChainVersion && bcVersion != 0 {
|
||||
return nil, fmt.Errorf("Blockchain DB version mismatch (%d / %d). Run geth upgradedb.\n", bcVersion, core.BlockChainVersion)
|
||||
return nil, fmt.Errorf("Blockchain DB version mismatch (%d / %d).\n", bcVersion, core.BlockChainVersion)
|
||||
}
|
||||
rawdb.WriteDatabaseVersion(chainDb, core.BlockChainVersion)
|
||||
}
|
||||
|
|
@ -167,13 +173,13 @@ func New(ctx *node.ServiceContext, config *Config) (*Ethereum, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
eth.miner = miner.New(eth, eth.chainConfig, eth.EventMux(), eth.engine)
|
||||
eth.miner.SetExtra(makeExtraData(config.ExtraData))
|
||||
eth.miner = miner.New(eth, eth.chainConfig, eth.EventMux(), eth.engine, config.MinerRecommit)
|
||||
eth.miner.SetExtra(makeExtraData(config.MinerExtraData))
|
||||
|
||||
eth.APIBackend = &EthAPIBackend{eth, nil}
|
||||
gpoParams := config.GPO
|
||||
if gpoParams.Default == nil {
|
||||
gpoParams.Default = config.GasPrice
|
||||
gpoParams.Default = config.MinerGasPrice
|
||||
}
|
||||
eth.APIBackend.gpo = gasprice.NewOracle(eth.APIBackend, gpoParams)
|
||||
|
||||
|
|
@ -333,32 +339,66 @@ func (s *Ethereum) SetEtherbase(etherbase common.Address) {
|
|||
s.miner.SetEtherbase(etherbase)
|
||||
}
|
||||
|
||||
func (s *Ethereum) StartMining(local bool) error {
|
||||
eb, err := s.Etherbase()
|
||||
if err != nil {
|
||||
log.Error("Cannot start mining without etherbase", "err", err)
|
||||
return fmt.Errorf("etherbase missing: %v", err)
|
||||
// StartMining starts the miner with the given number of CPU threads. If mining
|
||||
// is already running, this method adjust the number of threads allowed to use
|
||||
// and updates the minimum price required by the transaction pool.
|
||||
func (s *Ethereum) StartMining(threads int) error {
|
||||
// Update the thread count within the consensus engine
|
||||
type threaded interface {
|
||||
SetThreads(threads int)
|
||||
}
|
||||
if clique, ok := s.engine.(*clique.Clique); ok {
|
||||
wallet, err := s.accountManager.Find(accounts.Account{Address: eb})
|
||||
if wallet == nil || err != nil {
|
||||
log.Error("Etherbase account unavailable locally", "err", err)
|
||||
return fmt.Errorf("signer missing: %v", err)
|
||||
if th, ok := s.engine.(threaded); ok {
|
||||
log.Info("Updated mining threads", "threads", threads)
|
||||
if threads == 0 {
|
||||
threads = -1 // Disable the miner from within
|
||||
}
|
||||
clique.Authorize(eb, wallet.SignHash)
|
||||
th.SetThreads(threads)
|
||||
}
|
||||
if local {
|
||||
// If local (CPU) mining is started, we can disable the transaction rejection
|
||||
// mechanism introduced to speed sync times. CPU mining on mainnet is ludicrous
|
||||
// so none will ever hit this path, whereas marking sync done on CPU mining
|
||||
// will ensure that private networks work in single miner mode too.
|
||||
// If the miner was not running, initialize it
|
||||
if !s.IsMining() {
|
||||
// Propagate the initial price point to the transaction pool
|
||||
s.lock.RLock()
|
||||
price := s.gasPrice
|
||||
s.lock.RUnlock()
|
||||
s.txPool.SetGasPrice(price)
|
||||
|
||||
// Configure the local mining addess
|
||||
eb, err := s.Etherbase()
|
||||
if err != nil {
|
||||
log.Error("Cannot start mining without etherbase", "err", err)
|
||||
return fmt.Errorf("etherbase missing: %v", err)
|
||||
}
|
||||
if clique, ok := s.engine.(*clique.Clique); ok {
|
||||
wallet, err := s.accountManager.Find(accounts.Account{Address: eb})
|
||||
if wallet == nil || err != nil {
|
||||
log.Error("Etherbase account unavailable locally", "err", err)
|
||||
return fmt.Errorf("signer missing: %v", err)
|
||||
}
|
||||
clique.Authorize(eb, wallet.SignHash)
|
||||
}
|
||||
// If mining is started, we can disable the transaction rejection mechanism
|
||||
// introduced to speed sync times.
|
||||
atomic.StoreUint32(&s.protocolManager.acceptTxs, 1)
|
||||
|
||||
go s.miner.Start(eb)
|
||||
}
|
||||
go s.miner.Start(eb)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Ethereum) StopMining() { s.miner.Stop() }
|
||||
// StopMining terminates the miner, both at the consensus engine level as well as
|
||||
// at the block creation level.
|
||||
func (s *Ethereum) StopMining() {
|
||||
// Update the thread count within the consensus engine
|
||||
type threaded interface {
|
||||
SetThreads(threads int)
|
||||
}
|
||||
if th, ok := s.engine.(threaded); ok {
|
||||
th.SetThreads(-1)
|
||||
}
|
||||
// Stop the block creating itself
|
||||
s.miner.Stop()
|
||||
}
|
||||
|
||||
func (s *Ethereum) IsMining() bool { return s.miner.Mining() }
|
||||
func (s *Ethereum) Miner() *miner.Miner { return s.miner }
|
||||
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
package eth
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
|
|
@ -92,30 +93,28 @@ const (
|
|||
// BloomIndexer implements a core.ChainIndexer, building up a rotated bloom bits index
|
||||
// for the Ethereum header bloom filters, permitting blazing fast filtering.
|
||||
type BloomIndexer struct {
|
||||
size uint64 // section size to generate bloombits for
|
||||
|
||||
db ethdb.Database // database instance to write index data and metadata into
|
||||
gen *bloombits.Generator // generator to rotate the bloom bits crating the bloom index
|
||||
|
||||
section uint64 // Section is the section number being processed currently
|
||||
head common.Hash // Head is the hash of the last header processed
|
||||
size uint64 // section size to generate bloombits for
|
||||
db ethdb.Database // database instance to write index data and metadata into
|
||||
gen *bloombits.Generator // generator to rotate the bloom bits crating the bloom index
|
||||
section uint64 // Section is the section number being processed currently
|
||||
head common.Hash // Head is the hash of the last header processed
|
||||
}
|
||||
|
||||
// NewBloomIndexer returns a chain indexer that generates bloom bits data for the
|
||||
// canonical chain for fast logs filtering.
|
||||
func NewBloomIndexer(db ethdb.Database, size uint64) *core.ChainIndexer {
|
||||
func NewBloomIndexer(db ethdb.Database, size, confReq uint64) *core.ChainIndexer {
|
||||
backend := &BloomIndexer{
|
||||
db: db,
|
||||
size: size,
|
||||
}
|
||||
table := ethdb.NewTable(db, string(rawdb.BloomBitsIndexPrefix))
|
||||
|
||||
return core.NewChainIndexer(db, table, backend, size, bloomConfirms, bloomThrottling, "bloombits")
|
||||
return core.NewChainIndexer(db, table, backend, size, confReq, bloomThrottling, "bloombits")
|
||||
}
|
||||
|
||||
// Reset implements core.ChainIndexerBackend, starting a new bloombits index
|
||||
// section.
|
||||
func (b *BloomIndexer) Reset(section uint64, lastSectionHead common.Hash) error {
|
||||
func (b *BloomIndexer) Reset(ctx context.Context, section uint64, lastSectionHead common.Hash) error {
|
||||
gen, err := bloombits.NewGenerator(uint(b.size))
|
||||
b.gen, b.section, b.head = gen, section, common.Hash{}
|
||||
return err
|
||||
|
|
@ -123,16 +122,16 @@ func (b *BloomIndexer) Reset(section uint64, lastSectionHead common.Hash) error
|
|||
|
||||
// Process implements core.ChainIndexerBackend, adding a new header's bloom into
|
||||
// the index.
|
||||
func (b *BloomIndexer) Process(header *types.Header) {
|
||||
func (b *BloomIndexer) Process(ctx context.Context, header *types.Header) error {
|
||||
b.gen.AddBloom(uint(header.Number.Uint64()-b.section*b.size), header.Bloom)
|
||||
b.head = header.Hash()
|
||||
return nil
|
||||
}
|
||||
|
||||
// Commit implements core.ChainIndexerBackend, finalizing the bloom section and
|
||||
// writing it out into the database.
|
||||
func (b *BloomIndexer) Commit() error {
|
||||
batch := b.db.NewBatch()
|
||||
|
||||
for i := 0; i < types.BloomBitLength; i++ {
|
||||
bits, err := b.gen.Bitset(uint(i))
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -48,7 +48,8 @@ var DefaultConfig = Config{
|
|||
DatabaseCache: 768,
|
||||
TrieCache: 256,
|
||||
TrieTimeout: 60 * time.Minute,
|
||||
GasPrice: big.NewInt(18 * params.Shannon),
|
||||
MinerGasPrice: big.NewInt(18 * params.Shannon),
|
||||
MinerRecommit: 3 * time.Second,
|
||||
|
||||
TxPool: core.DefaultTxPoolConfig,
|
||||
GPO: gasprice.Config{
|
||||
|
|
@ -95,11 +96,11 @@ type Config struct {
|
|||
TrieTimeout time.Duration
|
||||
|
||||
// Mining-related options
|
||||
Etherbase common.Address `toml:",omitempty"`
|
||||
MinerThreads int `toml:",omitempty"`
|
||||
MinerNotify []string `toml:",omitempty"`
|
||||
ExtraData []byte `toml:",omitempty"`
|
||||
GasPrice *big.Int
|
||||
Etherbase common.Address `toml:",omitempty"`
|
||||
MinerNotify []string `toml:",omitempty"`
|
||||
MinerExtraData []byte `toml:",omitempty"`
|
||||
MinerGasPrice *big.Int
|
||||
MinerRecommit time.Duration
|
||||
|
||||
// Ethash options
|
||||
Ethash ethash.Config
|
||||
|
|
@ -118,5 +119,5 @@ type Config struct {
|
|||
}
|
||||
|
||||
type configMarshaling struct {
|
||||
ExtraData hexutil.Bytes
|
||||
MinerExtraData hexutil.Bytes
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ package eth
|
|||
|
||||
import (
|
||||
"math/big"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
|
|
@ -15,20 +16,25 @@ import (
|
|||
|
||||
var _ = (*configMarshaling)(nil)
|
||||
|
||||
// MarshalTOML marshals as TOML.
|
||||
func (c Config) MarshalTOML() (interface{}, error) {
|
||||
type Config struct {
|
||||
Genesis *core.Genesis `toml:",omitempty"`
|
||||
NetworkId uint64
|
||||
SyncMode downloader.SyncMode
|
||||
NoPruning bool
|
||||
LightServ int `toml:",omitempty"`
|
||||
LightPeers int `toml:",omitempty"`
|
||||
SkipBcVersionCheck bool `toml:"-"`
|
||||
DatabaseHandles int `toml:"-"`
|
||||
DatabaseCache int
|
||||
TrieCache int
|
||||
TrieTimeout time.Duration
|
||||
Etherbase common.Address `toml:",omitempty"`
|
||||
MinerThreads int `toml:",omitempty"`
|
||||
ExtraData hexutil.Bytes `toml:",omitempty"`
|
||||
GasPrice *big.Int
|
||||
MinerNotify []string `toml:",omitempty"`
|
||||
MinerExtraData hexutil.Bytes `toml:",omitempty"`
|
||||
MinerGasPrice *big.Int
|
||||
MinerRecommit time.Duration
|
||||
Ethash ethash.Config
|
||||
TxPool core.TxPoolConfig
|
||||
GPO gasprice.Config
|
||||
|
|
@ -39,15 +45,19 @@ func (c Config) MarshalTOML() (interface{}, error) {
|
|||
enc.Genesis = c.Genesis
|
||||
enc.NetworkId = c.NetworkId
|
||||
enc.SyncMode = c.SyncMode
|
||||
enc.NoPruning = c.NoPruning
|
||||
enc.LightServ = c.LightServ
|
||||
enc.LightPeers = c.LightPeers
|
||||
enc.SkipBcVersionCheck = c.SkipBcVersionCheck
|
||||
enc.DatabaseHandles = c.DatabaseHandles
|
||||
enc.DatabaseCache = c.DatabaseCache
|
||||
enc.TrieCache = c.TrieCache
|
||||
enc.TrieTimeout = c.TrieTimeout
|
||||
enc.Etherbase = c.Etherbase
|
||||
enc.MinerThreads = c.MinerThreads
|
||||
enc.ExtraData = c.ExtraData
|
||||
enc.GasPrice = c.GasPrice
|
||||
enc.MinerNotify = c.MinerNotify
|
||||
enc.MinerExtraData = c.MinerExtraData
|
||||
enc.MinerGasPrice = c.MinerGasPrice
|
||||
enc.MinerRecommit = c.MinerRecommit
|
||||
enc.Ethash = c.Ethash
|
||||
enc.TxPool = c.TxPool
|
||||
enc.GPO = c.GPO
|
||||
|
|
@ -56,20 +66,26 @@ func (c Config) MarshalTOML() (interface{}, error) {
|
|||
return &enc, nil
|
||||
}
|
||||
|
||||
// UnmarshalTOML unmarshals from TOML.
|
||||
func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error {
|
||||
type Config struct {
|
||||
Genesis *core.Genesis `toml:",omitempty"`
|
||||
NetworkId *uint64
|
||||
SyncMode *downloader.SyncMode
|
||||
NoPruning *bool
|
||||
LightServ *int `toml:",omitempty"`
|
||||
LightPeers *int `toml:",omitempty"`
|
||||
SkipBcVersionCheck *bool `toml:"-"`
|
||||
DatabaseHandles *int `toml:"-"`
|
||||
DatabaseCache *int
|
||||
TrieCache *int
|
||||
TrieTimeout *time.Duration
|
||||
Etherbase *common.Address `toml:",omitempty"`
|
||||
MinerThreads *int `toml:",omitempty"`
|
||||
ExtraData *hexutil.Bytes `toml:",omitempty"`
|
||||
GasPrice *big.Int
|
||||
MinerNotify []string `toml:",omitempty"`
|
||||
MinerExtraData *hexutil.Bytes `toml:",omitempty"`
|
||||
MinerGasPrice *big.Int
|
||||
MinerRecommit *time.Duration
|
||||
Ethash *ethash.Config
|
||||
TxPool *core.TxPoolConfig
|
||||
GPO *gasprice.Config
|
||||
|
|
@ -89,6 +105,9 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error {
|
|||
if dec.SyncMode != nil {
|
||||
c.SyncMode = *dec.SyncMode
|
||||
}
|
||||
if dec.NoPruning != nil {
|
||||
c.NoPruning = *dec.NoPruning
|
||||
}
|
||||
if dec.LightServ != nil {
|
||||
c.LightServ = *dec.LightServ
|
||||
}
|
||||
|
|
@ -104,17 +123,26 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error {
|
|||
if dec.DatabaseCache != nil {
|
||||
c.DatabaseCache = *dec.DatabaseCache
|
||||
}
|
||||
if dec.TrieCache != nil {
|
||||
c.TrieCache = *dec.TrieCache
|
||||
}
|
||||
if dec.TrieTimeout != nil {
|
||||
c.TrieTimeout = *dec.TrieTimeout
|
||||
}
|
||||
if dec.Etherbase != nil {
|
||||
c.Etherbase = *dec.Etherbase
|
||||
}
|
||||
if dec.MinerThreads != nil {
|
||||
c.MinerThreads = *dec.MinerThreads
|
||||
if dec.MinerNotify != nil {
|
||||
c.MinerNotify = dec.MinerNotify
|
||||
}
|
||||
if dec.ExtraData != nil {
|
||||
c.ExtraData = *dec.ExtraData
|
||||
if dec.MinerExtraData != nil {
|
||||
c.MinerExtraData = *dec.MinerExtraData
|
||||
}
|
||||
if dec.GasPrice != nil {
|
||||
c.GasPrice = dec.GasPrice
|
||||
if dec.MinerGasPrice != nil {
|
||||
c.MinerGasPrice = dec.MinerGasPrice
|
||||
}
|
||||
if dec.MinerRecommit != nil {
|
||||
c.MinerRecommit = *dec.MinerRecommit
|
||||
}
|
||||
if dec.Ethash != nil {
|
||||
c.Ethash = *dec.Ethash
|
||||
|
|
|
|||
|
|
@ -519,6 +519,11 @@ web3._extend({
|
|||
params: 1,
|
||||
inputFormatter: [web3._extend.utils.fromDecimal]
|
||||
}),
|
||||
new web3._extend.Method({
|
||||
name: 'setRecommitInterval',
|
||||
call: 'miner_setRecommitInterval',
|
||||
params: 1,
|
||||
}),
|
||||
new web3._extend.Method({
|
||||
name: 'getHashrate',
|
||||
call: 'miner_getHashrate'
|
||||
|
|
|
|||
|
|
@ -34,7 +34,6 @@ import (
|
|||
"github.com/ethereum/go-ethereum/eth/downloader"
|
||||
"github.com/ethereum/go-ethereum/eth/filters"
|
||||
"github.com/ethereum/go-ethereum/eth/gasprice"
|
||||
"github.com/ethereum/go-ethereum/ethdb"
|
||||
"github.com/ethereum/go-ethereum/event"
|
||||
"github.com/ethereum/go-ethereum/internal/ethapi"
|
||||
"github.com/ethereum/go-ethereum/light"
|
||||
|
|
@ -47,26 +46,24 @@ import (
|
|||
)
|
||||
|
||||
type LightEthereum struct {
|
||||
config *eth.Config
|
||||
lesCommons
|
||||
|
||||
odr *LesOdr
|
||||
relay *LesTxRelay
|
||||
chainConfig *params.ChainConfig
|
||||
// Channel for shutting down the service
|
||||
shutdownChan chan bool
|
||||
// Handlers
|
||||
peers *peerSet
|
||||
txPool *light.TxPool
|
||||
blockchain *light.LightChain
|
||||
protocolManager *ProtocolManager
|
||||
serverPool *serverPool
|
||||
reqDist *requestDistributor
|
||||
retriever *retrieveManager
|
||||
// DB interfaces
|
||||
chainDb ethdb.Database // Block chain database
|
||||
|
||||
bloomRequests chan chan *bloombits.Retrieval // Channel receiving bloom data retrieval requests
|
||||
bloomIndexer, chtIndexer, bloomTrieIndexer *core.ChainIndexer
|
||||
// Handlers
|
||||
peers *peerSet
|
||||
txPool *light.TxPool
|
||||
blockchain *light.LightChain
|
||||
serverPool *serverPool
|
||||
reqDist *requestDistributor
|
||||
retriever *retrieveManager
|
||||
|
||||
bloomRequests chan chan *bloombits.Retrieval // Channel receiving bloom data retrieval requests
|
||||
bloomIndexer *core.ChainIndexer
|
||||
|
||||
ApiBackend *LesApiBackend
|
||||
|
||||
|
|
@ -95,30 +92,41 @@ func New(ctx *node.ServiceContext, config *eth.Config) (*LightEthereum, error) {
|
|||
quitSync := make(chan struct{})
|
||||
|
||||
leth := &LightEthereum{
|
||||
config: config,
|
||||
chainConfig: chainConfig,
|
||||
chainDb: chainDb,
|
||||
eventMux: ctx.EventMux,
|
||||
peers: peers,
|
||||
reqDist: newRequestDistributor(peers, quitSync),
|
||||
accountManager: ctx.AccountManager,
|
||||
engine: eth.CreateConsensusEngine(ctx, chainConfig, &config.Ethash, nil, chainDb),
|
||||
shutdownChan: make(chan bool),
|
||||
networkId: config.NetworkId,
|
||||
bloomRequests: make(chan chan *bloombits.Retrieval),
|
||||
bloomIndexer: eth.NewBloomIndexer(chainDb, light.BloomTrieFrequency),
|
||||
chtIndexer: light.NewChtIndexer(chainDb, true),
|
||||
bloomTrieIndexer: light.NewBloomTrieIndexer(chainDb, true),
|
||||
lesCommons: lesCommons{
|
||||
chainDb: chainDb,
|
||||
config: config,
|
||||
},
|
||||
chainConfig: chainConfig,
|
||||
eventMux: ctx.EventMux,
|
||||
peers: peers,
|
||||
reqDist: newRequestDistributor(peers, quitSync),
|
||||
accountManager: ctx.AccountManager,
|
||||
engine: eth.CreateConsensusEngine(ctx, chainConfig, &config.Ethash, nil, chainDb),
|
||||
shutdownChan: make(chan bool),
|
||||
networkId: config.NetworkId,
|
||||
bloomRequests: make(chan chan *bloombits.Retrieval),
|
||||
bloomIndexer: eth.NewBloomIndexer(chainDb, light.BloomTrieFrequency, light.HelperTrieConfirmations),
|
||||
}
|
||||
|
||||
leth.relay = NewLesTxRelay(peers, leth.reqDist)
|
||||
leth.serverPool = newServerPool(chainDb, quitSync, &leth.wg)
|
||||
leth.retriever = newRetrieveManager(peers, leth.reqDist, leth.serverPool)
|
||||
leth.odr = NewLesOdr(chainDb, leth.chtIndexer, leth.bloomTrieIndexer, leth.bloomIndexer, leth.retriever)
|
||||
|
||||
leth.odr = NewLesOdr(chainDb, leth.retriever)
|
||||
leth.chtIndexer = light.NewChtIndexer(chainDb, true, leth.odr)
|
||||
leth.bloomTrieIndexer = light.NewBloomTrieIndexer(chainDb, true, leth.odr)
|
||||
leth.odr.SetIndexers(leth.chtIndexer, leth.bloomTrieIndexer, leth.bloomIndexer)
|
||||
|
||||
// Note: NewLightChain adds the trusted checkpoint so it needs an ODR with
|
||||
// indexers already set but not started yet
|
||||
if leth.blockchain, err = light.NewLightChain(leth.odr, leth.chainConfig, leth.engine); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Note: AddChildIndexer starts the update process for the child
|
||||
leth.bloomIndexer.AddChildIndexer(leth.bloomTrieIndexer)
|
||||
leth.chtIndexer.Start(leth.blockchain)
|
||||
leth.bloomIndexer.Start(leth.blockchain)
|
||||
|
||||
// Rewind the chain in case of an incompatible config upgrade.
|
||||
if compat, ok := genesisErr.(*params.ConfigCompatError); ok {
|
||||
log.Warn("Rewinding chain to upgrade configuration", "err", compat)
|
||||
|
|
@ -127,13 +135,13 @@ func New(ctx *node.ServiceContext, config *eth.Config) (*LightEthereum, error) {
|
|||
}
|
||||
|
||||
leth.txPool = light.NewTxPool(leth.chainConfig, leth.blockchain, leth.relay)
|
||||
if leth.protocolManager, err = NewProtocolManager(leth.chainConfig, true, ClientProtocolVersions, config.NetworkId, leth.eventMux, leth.engine, leth.peers, leth.blockchain, nil, chainDb, leth.odr, leth.relay, leth.serverPool, quitSync, &leth.wg); err != nil {
|
||||
if leth.protocolManager, err = NewProtocolManager(leth.chainConfig, true, config.NetworkId, leth.eventMux, leth.engine, leth.peers, leth.blockchain, nil, chainDb, leth.odr, leth.relay, leth.serverPool, quitSync, &leth.wg); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
leth.ApiBackend = &LesApiBackend{leth, nil}
|
||||
gpoParams := config.GPO
|
||||
if gpoParams.Default == nil {
|
||||
gpoParams.Default = config.GasPrice
|
||||
gpoParams.Default = config.MinerGasPrice
|
||||
}
|
||||
leth.ApiBackend.gpo = gasprice.NewOracle(leth.ApiBackend, gpoParams)
|
||||
return leth, nil
|
||||
|
|
@ -209,14 +217,14 @@ func (s *LightEthereum) ResetWithGenesisBlock(gb *types.Block) {
|
|||
func (s *LightEthereum) BlockChain() *light.LightChain { return s.blockchain }
|
||||
func (s *LightEthereum) TxPool() *light.TxPool { return s.txPool }
|
||||
func (s *LightEthereum) Engine() consensus.Engine { return s.engine }
|
||||
func (s *LightEthereum) LesVersion() int { return int(s.protocolManager.SubProtocols[0].Version) }
|
||||
func (s *LightEthereum) LesVersion() int { return int(ClientProtocolVersions[0]) }
|
||||
func (s *LightEthereum) Downloader() *downloader.Downloader { return s.protocolManager.downloader }
|
||||
func (s *LightEthereum) EventMux() *event.TypeMux { return s.eventMux }
|
||||
|
||||
// Protocols implements node.Service, returning all the currently configured
|
||||
// network protocols to start.
|
||||
func (s *LightEthereum) Protocols() []p2p.Protocol {
|
||||
return s.protocolManager.SubProtocols
|
||||
return s.makeProtocols(ClientProtocolVersions)
|
||||
}
|
||||
|
||||
// Start implements node.Service, starting all internal goroutines needed by the
|
||||
|
|
@ -236,15 +244,8 @@ func (s *LightEthereum) Start(srvr *p2p.Server) error {
|
|||
// Ethereum protocol.
|
||||
func (s *LightEthereum) Stop() error {
|
||||
s.odr.Stop()
|
||||
if s.bloomIndexer != nil {
|
||||
s.bloomIndexer.Close()
|
||||
}
|
||||
if s.chtIndexer != nil {
|
||||
s.chtIndexer.Close()
|
||||
}
|
||||
if s.bloomTrieIndexer != nil {
|
||||
s.bloomTrieIndexer.Close()
|
||||
}
|
||||
s.bloomIndexer.Close()
|
||||
s.chtIndexer.Close()
|
||||
s.blockchain.Stop()
|
||||
s.protocolManager.Stop()
|
||||
s.txPool.Stop()
|
||||
|
|
|
|||
118
les/commons.go
Normal file
118
les/commons.go
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
// Copyright 2018 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package les
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/big"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core"
|
||||
"github.com/ethereum/go-ethereum/eth"
|
||||
"github.com/ethereum/go-ethereum/ethdb"
|
||||
"github.com/ethereum/go-ethereum/light"
|
||||
"github.com/ethereum/go-ethereum/p2p"
|
||||
"github.com/ethereum/go-ethereum/p2p/discover"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
)
|
||||
|
||||
// lesCommons contains fields needed by both server and client.
|
||||
type lesCommons struct {
|
||||
config *eth.Config
|
||||
chainDb ethdb.Database
|
||||
protocolManager *ProtocolManager
|
||||
chtIndexer, bloomTrieIndexer *core.ChainIndexer
|
||||
}
|
||||
|
||||
// NodeInfo represents a short summary of the Ethereum sub-protocol metadata
|
||||
// known about the host peer.
|
||||
type NodeInfo struct {
|
||||
Network uint64 `json:"network"` // Ethereum network ID (1=Frontier, 2=Morden, Ropsten=3, Rinkeby=4)
|
||||
Difficulty *big.Int `json:"difficulty"` // Total difficulty of the host's blockchain
|
||||
Genesis common.Hash `json:"genesis"` // SHA3 hash of the host's genesis block
|
||||
Config *params.ChainConfig `json:"config"` // Chain configuration for the fork rules
|
||||
Head common.Hash `json:"head"` // SHA3 hash of the host's best owned block
|
||||
CHT light.TrustedCheckpoint `json:"cht"` // Trused CHT checkpoint for fast catchup
|
||||
}
|
||||
|
||||
// makeProtocols creates protocol descriptors for the given LES versions.
|
||||
func (c *lesCommons) makeProtocols(versions []uint) []p2p.Protocol {
|
||||
protos := make([]p2p.Protocol, len(versions))
|
||||
for i, version := range versions {
|
||||
version := version
|
||||
protos[i] = p2p.Protocol{
|
||||
Name: "les",
|
||||
Version: version,
|
||||
Length: ProtocolLengths[version],
|
||||
NodeInfo: c.nodeInfo,
|
||||
Run: func(p *p2p.Peer, rw p2p.MsgReadWriter) error {
|
||||
return c.protocolManager.runPeer(version, p, rw)
|
||||
},
|
||||
PeerInfo: func(id discover.NodeID) interface{} {
|
||||
if p := c.protocolManager.peers.Peer(fmt.Sprintf("%x", id[:8])); p != nil {
|
||||
return p.Info()
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
}
|
||||
return protos
|
||||
}
|
||||
|
||||
// nodeInfo retrieves some protocol metadata about the running host node.
|
||||
func (c *lesCommons) nodeInfo() interface{} {
|
||||
var cht light.TrustedCheckpoint
|
||||
sections, _, _ := c.chtIndexer.Sections()
|
||||
sections2, _, _ := c.bloomTrieIndexer.Sections()
|
||||
|
||||
if !c.protocolManager.lightSync {
|
||||
// convert to client section size if running in server mode
|
||||
sections /= light.CHTFrequencyClient / light.CHTFrequencyServer
|
||||
}
|
||||
|
||||
if sections2 < sections {
|
||||
sections = sections2
|
||||
}
|
||||
if sections > 0 {
|
||||
sectionIndex := sections - 1
|
||||
sectionHead := c.bloomTrieIndexer.SectionHead(sectionIndex)
|
||||
var chtRoot common.Hash
|
||||
if c.protocolManager.lightSync {
|
||||
chtRoot = light.GetChtRoot(c.chainDb, sectionIndex, sectionHead)
|
||||
} else {
|
||||
chtRoot = light.GetChtV2Root(c.chainDb, sectionIndex, sectionHead)
|
||||
}
|
||||
cht = light.TrustedCheckpoint{
|
||||
SectionIdx: sectionIndex,
|
||||
SectionHead: sectionHead,
|
||||
CHTRoot: chtRoot,
|
||||
BloomRoot: light.GetBloomTrieRoot(c.chainDb, sectionIndex, sectionHead),
|
||||
}
|
||||
}
|
||||
|
||||
chain := c.protocolManager.blockchain
|
||||
head := chain.CurrentHeader()
|
||||
hash := head.Hash()
|
||||
return &NodeInfo{
|
||||
Network: c.config.NetworkId,
|
||||
Difficulty: chain.GetTd(hash, head.Number.Uint64()),
|
||||
Genesis: chain.Genesis().Hash(),
|
||||
Config: chain.Config(),
|
||||
Head: chain.CurrentHeader().Hash(),
|
||||
CHT: cht,
|
||||
}
|
||||
}
|
||||
|
|
@ -20,14 +20,10 @@ package les
|
|||
|
||||
import (
|
||||
"container/list"
|
||||
"errors"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// ErrNoPeers is returned if no peers capable of serving a queued request are available
|
||||
var ErrNoPeers = errors.New("no suitable peers available")
|
||||
|
||||
// requestDistributor implements a mechanism that distributes requests to
|
||||
// suitable peers, obeying flow control rules and prioritizing them in creation
|
||||
// order (even when a resend is necessary).
|
||||
|
|
|
|||
112
les/handler.go
112
les/handler.go
|
|
@ -20,7 +20,6 @@ package les
|
|||
import (
|
||||
"encoding/binary"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"net"
|
||||
|
|
@ -40,7 +39,6 @@ import (
|
|||
"github.com/ethereum/go-ethereum/light"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/p2p"
|
||||
"github.com/ethereum/go-ethereum/p2p/discover"
|
||||
"github.com/ethereum/go-ethereum/p2p/discv5"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
|
|
@ -65,10 +63,6 @@ const (
|
|||
disableClientRemovePeer = false
|
||||
)
|
||||
|
||||
// errIncompatibleConfig is returned if the requested protocols and configs are
|
||||
// not compatible (low protocol version restrictions and high requirements).
|
||||
var errIncompatibleConfig = errors.New("incompatible configuration")
|
||||
|
||||
func errResp(code errCode, format string, v ...interface{}) error {
|
||||
return fmt.Errorf("%v - %v", code, fmt.Sprintf(format, v...))
|
||||
}
|
||||
|
|
@ -115,8 +109,6 @@ type ProtocolManager struct {
|
|||
peers *peerSet
|
||||
maxPeers int
|
||||
|
||||
SubProtocols []p2p.Protocol
|
||||
|
||||
eventMux *event.TypeMux
|
||||
|
||||
// channels for fetcher, syncer, txsyncLoop
|
||||
|
|
@ -131,7 +123,7 @@ type ProtocolManager struct {
|
|||
|
||||
// NewProtocolManager returns a new ethereum sub protocol manager. The Ethereum sub protocol manages peers capable
|
||||
// with the ethereum network.
|
||||
func NewProtocolManager(chainConfig *params.ChainConfig, lightSync bool, protocolVersions []uint, networkId uint64, mux *event.TypeMux, engine consensus.Engine, peers *peerSet, blockchain BlockChain, txpool txPool, chainDb ethdb.Database, odr *LesOdr, txrelay *LesTxRelay, serverPool *serverPool, quitSync chan struct{}, wg *sync.WaitGroup) (*ProtocolManager, error) {
|
||||
func NewProtocolManager(chainConfig *params.ChainConfig, lightSync bool, networkId uint64, mux *event.TypeMux, engine consensus.Engine, peers *peerSet, blockchain BlockChain, txpool txPool, chainDb ethdb.Database, odr *LesOdr, txrelay *LesTxRelay, serverPool *serverPool, quitSync chan struct{}, wg *sync.WaitGroup) (*ProtocolManager, error) {
|
||||
// Create the protocol manager with the base fields
|
||||
manager := &ProtocolManager{
|
||||
lightSync: lightSync,
|
||||
|
|
@ -155,54 +147,6 @@ func NewProtocolManager(chainConfig *params.ChainConfig, lightSync bool, protoco
|
|||
manager.reqDist = odr.retriever.dist
|
||||
}
|
||||
|
||||
// Initiate a sub-protocol for every implemented version we can handle
|
||||
manager.SubProtocols = make([]p2p.Protocol, 0, len(protocolVersions))
|
||||
for _, version := range protocolVersions {
|
||||
// Compatible, initialize the sub-protocol
|
||||
version := version // Closure for the run
|
||||
manager.SubProtocols = append(manager.SubProtocols, p2p.Protocol{
|
||||
Name: "les",
|
||||
Version: version,
|
||||
Length: ProtocolLengths[version],
|
||||
Run: func(p *p2p.Peer, rw p2p.MsgReadWriter) error {
|
||||
var entry *poolEntry
|
||||
peer := manager.newPeer(int(version), networkId, p, rw)
|
||||
if manager.serverPool != nil {
|
||||
addr := p.RemoteAddr().(*net.TCPAddr)
|
||||
entry = manager.serverPool.connect(peer, addr.IP, uint16(addr.Port))
|
||||
}
|
||||
peer.poolEntry = entry
|
||||
select {
|
||||
case manager.newPeerCh <- peer:
|
||||
manager.wg.Add(1)
|
||||
defer manager.wg.Done()
|
||||
err := manager.handle(peer)
|
||||
if entry != nil {
|
||||
manager.serverPool.disconnect(entry)
|
||||
}
|
||||
return err
|
||||
case <-manager.quitSync:
|
||||
if entry != nil {
|
||||
manager.serverPool.disconnect(entry)
|
||||
}
|
||||
return p2p.DiscQuitting
|
||||
}
|
||||
},
|
||||
NodeInfo: func() interface{} {
|
||||
return manager.NodeInfo()
|
||||
},
|
||||
PeerInfo: func(id discover.NodeID) interface{} {
|
||||
if p := manager.peers.Peer(fmt.Sprintf("%x", id[:8])); p != nil {
|
||||
return p.Info()
|
||||
}
|
||||
return nil
|
||||
},
|
||||
})
|
||||
}
|
||||
if len(manager.SubProtocols) == 0 {
|
||||
return nil, errIncompatibleConfig
|
||||
}
|
||||
|
||||
removePeer := manager.removePeer
|
||||
if disableClientRemovePeer {
|
||||
removePeer = func(id string) {}
|
||||
|
|
@ -262,6 +206,32 @@ func (pm *ProtocolManager) Stop() {
|
|||
log.Info("Light Ethereum protocol stopped")
|
||||
}
|
||||
|
||||
// runPeer is the p2p protocol run function for the given version.
|
||||
func (pm *ProtocolManager) runPeer(version uint, p *p2p.Peer, rw p2p.MsgReadWriter) error {
|
||||
var entry *poolEntry
|
||||
peer := pm.newPeer(int(version), pm.networkId, p, rw)
|
||||
if pm.serverPool != nil {
|
||||
addr := p.RemoteAddr().(*net.TCPAddr)
|
||||
entry = pm.serverPool.connect(peer, addr.IP, uint16(addr.Port))
|
||||
}
|
||||
peer.poolEntry = entry
|
||||
select {
|
||||
case pm.newPeerCh <- peer:
|
||||
pm.wg.Add(1)
|
||||
defer pm.wg.Done()
|
||||
err := pm.handle(peer)
|
||||
if entry != nil {
|
||||
pm.serverPool.disconnect(entry)
|
||||
}
|
||||
return err
|
||||
case <-pm.quitSync:
|
||||
if entry != nil {
|
||||
pm.serverPool.disconnect(entry)
|
||||
}
|
||||
return p2p.DiscQuitting
|
||||
}
|
||||
}
|
||||
|
||||
func (pm *ProtocolManager) newPeer(pv int, nv uint64, p *p2p.Peer, rw p2p.MsgReadWriter) *peer {
|
||||
return newPeer(pv, nv, p, newMeteredMsgWriter(rw))
|
||||
}
|
||||
|
|
@ -1203,30 +1173,6 @@ func (pm *ProtocolManager) txStatus(hashes []common.Hash) []txStatus {
|
|||
return stats
|
||||
}
|
||||
|
||||
// NodeInfo represents a short summary of the Ethereum sub-protocol metadata
|
||||
// known about the host peer.
|
||||
type NodeInfo struct {
|
||||
Network uint64 `json:"network"` // Ethereum network ID (1=Frontier, 2=Morden, Ropsten=3, Rinkeby=4)
|
||||
Difficulty *big.Int `json:"difficulty"` // Total difficulty of the host's blockchain
|
||||
Genesis common.Hash `json:"genesis"` // SHA3 hash of the host's genesis block
|
||||
Config *params.ChainConfig `json:"config"` // Chain configuration for the fork rules
|
||||
Head common.Hash `json:"head"` // SHA3 hash of the host's best owned block
|
||||
}
|
||||
|
||||
// NodeInfo retrieves some protocol metadata about the running host node.
|
||||
func (self *ProtocolManager) NodeInfo() *NodeInfo {
|
||||
head := self.blockchain.CurrentHeader()
|
||||
hash := head.Hash()
|
||||
|
||||
return &NodeInfo{
|
||||
Network: self.networkId,
|
||||
Difficulty: self.blockchain.GetTd(hash, head.Number.Uint64()),
|
||||
Genesis: self.blockchain.Genesis().Hash(),
|
||||
Config: self.blockchain.Config(),
|
||||
Head: hash,
|
||||
}
|
||||
}
|
||||
|
||||
// downloaderPeerNotify implements peerSetNotify
|
||||
type downloaderPeerNotify ProtocolManager
|
||||
|
||||
|
|
@ -1258,7 +1204,7 @@ func (pc *peerConnection) RequestHeadersByHash(origin common.Hash, amount int, s
|
|||
}
|
||||
_, ok := <-pc.manager.reqDist.queue(rq)
|
||||
if !ok {
|
||||
return ErrNoPeers
|
||||
return light.ErrNoPeers
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
@ -1282,7 +1228,7 @@ func (pc *peerConnection) RequestHeadersByNumber(origin uint64, amount int, skip
|
|||
}
|
||||
_, ok := <-pc.manager.reqDist.queue(rq)
|
||||
if !ok {
|
||||
return ErrNoPeers
|
||||
return light.ErrNoPeers
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -156,12 +156,12 @@ func newTestProtocolManager(lightSync bool, blocks int, generator func(int, *cor
|
|||
} else {
|
||||
blockchain, _ := core.NewBlockChain(db, nil, gspec.Config, engine, vm.Config{})
|
||||
|
||||
chtIndexer := light.NewChtIndexer(db, false)
|
||||
chtIndexer := light.NewChtIndexer(db, false, nil)
|
||||
chtIndexer.Start(blockchain)
|
||||
|
||||
bbtIndexer := light.NewBloomTrieIndexer(db, false)
|
||||
bbtIndexer := light.NewBloomTrieIndexer(db, false, nil)
|
||||
|
||||
bloomIndexer := eth.NewBloomIndexer(db, params.BloomBitsBlocks)
|
||||
bloomIndexer := eth.NewBloomIndexer(db, params.BloomBitsBlocks, light.HelperTrieProcessConfirmations)
|
||||
bloomIndexer.AddChildIndexer(bbtIndexer)
|
||||
bloomIndexer.Start(blockchain)
|
||||
|
||||
|
|
@ -172,18 +172,12 @@ func newTestProtocolManager(lightSync bool, blocks int, generator func(int, *cor
|
|||
chain = blockchain
|
||||
}
|
||||
|
||||
var protocolVersions []uint
|
||||
if lightSync {
|
||||
protocolVersions = ClientProtocolVersions
|
||||
} else {
|
||||
protocolVersions = ServerProtocolVersions
|
||||
}
|
||||
pm, err := NewProtocolManager(gspec.Config, lightSync, protocolVersions, NetworkId, evmux, engine, peers, chain, nil, db, odr, nil, nil, make(chan struct{}), new(sync.WaitGroup))
|
||||
pm, err := NewProtocolManager(gspec.Config, lightSync, NetworkId, evmux, engine, peers, chain, nil, db, odr, nil, nil, make(chan struct{}), new(sync.WaitGroup))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !lightSync {
|
||||
srv := &LesServer{protocolManager: pm}
|
||||
srv := &LesServer{lesCommons: lesCommons{protocolManager: pm}}
|
||||
pm.server = srv
|
||||
|
||||
srv.defParams = &flowcontrol.ServerParams{
|
||||
|
|
|
|||
18
les/odr.go
18
les/odr.go
|
|
@ -33,14 +33,11 @@ type LesOdr struct {
|
|||
stop chan struct{}
|
||||
}
|
||||
|
||||
func NewLesOdr(db ethdb.Database, chtIndexer, bloomTrieIndexer, bloomIndexer *core.ChainIndexer, retriever *retrieveManager) *LesOdr {
|
||||
func NewLesOdr(db ethdb.Database, retriever *retrieveManager) *LesOdr {
|
||||
return &LesOdr{
|
||||
db: db,
|
||||
chtIndexer: chtIndexer,
|
||||
bloomTrieIndexer: bloomTrieIndexer,
|
||||
bloomIndexer: bloomIndexer,
|
||||
retriever: retriever,
|
||||
stop: make(chan struct{}),
|
||||
db: db,
|
||||
retriever: retriever,
|
||||
stop: make(chan struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -54,6 +51,13 @@ func (odr *LesOdr) Database() ethdb.Database {
|
|||
return odr.db
|
||||
}
|
||||
|
||||
// SetIndexers adds the necessary chain indexers to the ODR backend
|
||||
func (odr *LesOdr) SetIndexers(chtIndexer, bloomTrieIndexer, bloomIndexer *core.ChainIndexer) {
|
||||
odr.chtIndexer = chtIndexer
|
||||
odr.bloomTrieIndexer = bloomTrieIndexer
|
||||
odr.bloomIndexer = bloomIndexer
|
||||
}
|
||||
|
||||
// ChtIndexer returns the CHT chain indexer
|
||||
func (odr *LesOdr) ChtIndexer() *core.ChainIndexer {
|
||||
return odr.chtIndexer
|
||||
|
|
|
|||
|
|
@ -167,7 +167,8 @@ func testOdr(t *testing.T, protocol int, expFail uint64, fn odrTestFn) {
|
|||
rm := newRetrieveManager(peers, dist, nil)
|
||||
db := ethdb.NewMemDatabase()
|
||||
ldb := ethdb.NewMemDatabase()
|
||||
odr := NewLesOdr(ldb, light.NewChtIndexer(db, true), light.NewBloomTrieIndexer(db, true), eth.NewBloomIndexer(db, light.BloomTrieFrequency), rm)
|
||||
odr := NewLesOdr(ldb, rm)
|
||||
odr.SetIndexers(light.NewChtIndexer(db, true, nil), light.NewBloomTrieIndexer(db, true, nil), eth.NewBloomIndexer(db, light.BloomTrieFrequency, light.HelperTrieConfirmations))
|
||||
pm := newTestProtocolManagerMust(t, false, 4, testChainGen, nil, nil, db)
|
||||
lpm := newTestProtocolManagerMust(t, true, 0, nil, peers, odr, ldb)
|
||||
_, err1, lpeer, err2 := newTestPeerPair("peer", protocol, pm, lpm)
|
||||
|
|
|
|||
|
|
@ -89,7 +89,8 @@ func testAccess(t *testing.T, protocol int, fn accessTestFn) {
|
|||
rm := newRetrieveManager(peers, dist, nil)
|
||||
db := ethdb.NewMemDatabase()
|
||||
ldb := ethdb.NewMemDatabase()
|
||||
odr := NewLesOdr(ldb, light.NewChtIndexer(db, true), light.NewBloomTrieIndexer(db, true), eth.NewBloomIndexer(db, light.BloomTrieFrequency), rm)
|
||||
odr := NewLesOdr(ldb, rm)
|
||||
odr.SetIndexers(light.NewChtIndexer(db, true, nil), light.NewBloomTrieIndexer(db, true, nil), eth.NewBloomIndexer(db, light.BloomTrieFrequency, light.HelperTrieConfirmations))
|
||||
|
||||
pm := newTestProtocolManagerMust(t, false, 4, testChainGen, nil, nil, db)
|
||||
lpm := newTestProtocolManagerMust(t, true, 0, nil, peers, odr, ldb)
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common/mclock"
|
||||
"github.com/ethereum/go-ethereum/light"
|
||||
)
|
||||
|
||||
var (
|
||||
|
|
@ -207,7 +208,7 @@ func (r *sentReq) stateRequesting() reqStateFn {
|
|||
return r.stateNoMorePeers
|
||||
}
|
||||
// nothing to wait for, no more peers to ask, return with error
|
||||
r.stop(ErrNoPeers)
|
||||
r.stop(light.ErrNoPeers)
|
||||
// no need to go to stopped state because waiting() already returned false
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,21 +38,19 @@ import (
|
|||
)
|
||||
|
||||
type LesServer struct {
|
||||
config *eth.Config
|
||||
protocolManager *ProtocolManager
|
||||
fcManager *flowcontrol.ClientManager // nil if our node is client only
|
||||
fcCostStats *requestCostStats
|
||||
defParams *flowcontrol.ServerParams
|
||||
lesTopics []discv5.Topic
|
||||
privateKey *ecdsa.PrivateKey
|
||||
quitSync chan struct{}
|
||||
lesCommons
|
||||
|
||||
chtIndexer, bloomTrieIndexer *core.ChainIndexer
|
||||
fcManager *flowcontrol.ClientManager // nil if our node is client only
|
||||
fcCostStats *requestCostStats
|
||||
defParams *flowcontrol.ServerParams
|
||||
lesTopics []discv5.Topic
|
||||
privateKey *ecdsa.PrivateKey
|
||||
quitSync chan struct{}
|
||||
}
|
||||
|
||||
func NewLesServer(eth *eth.Ethereum, config *eth.Config) (*LesServer, error) {
|
||||
quitSync := make(chan struct{})
|
||||
pm, err := NewProtocolManager(eth.BlockChain().Config(), false, ServerProtocolVersions, config.NetworkId, eth.EventMux(), eth.Engine(), newPeerSet(), eth.BlockChain(), eth.TxPool(), eth.ChainDb(), nil, nil, nil, quitSync, new(sync.WaitGroup))
|
||||
pm, err := NewProtocolManager(eth.BlockChain().Config(), false, config.NetworkId, eth.EventMux(), eth.Engine(), newPeerSet(), eth.BlockChain(), eth.TxPool(), eth.ChainDb(), nil, nil, nil, quitSync, new(sync.WaitGroup))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -63,13 +61,17 @@ func NewLesServer(eth *eth.Ethereum, config *eth.Config) (*LesServer, error) {
|
|||
}
|
||||
|
||||
srv := &LesServer{
|
||||
config: config,
|
||||
protocolManager: pm,
|
||||
quitSync: quitSync,
|
||||
lesTopics: lesTopics,
|
||||
chtIndexer: light.NewChtIndexer(eth.ChainDb(), false),
|
||||
bloomTrieIndexer: light.NewBloomTrieIndexer(eth.ChainDb(), false),
|
||||
lesCommons: lesCommons{
|
||||
config: config,
|
||||
chainDb: eth.ChainDb(),
|
||||
chtIndexer: light.NewChtIndexer(eth.ChainDb(), false, nil),
|
||||
bloomTrieIndexer: light.NewBloomTrieIndexer(eth.ChainDb(), false, nil),
|
||||
protocolManager: pm,
|
||||
},
|
||||
quitSync: quitSync,
|
||||
lesTopics: lesTopics,
|
||||
}
|
||||
|
||||
logger := log.New()
|
||||
|
||||
chtV1SectionCount, _, _ := srv.chtIndexer.Sections() // indexer still uses LES/1 4k section size for backwards server compatibility
|
||||
|
|
@ -104,7 +106,7 @@ func NewLesServer(eth *eth.Ethereum, config *eth.Config) (*LesServer, error) {
|
|||
}
|
||||
|
||||
func (s *LesServer) Protocols() []p2p.Protocol {
|
||||
return s.protocolManager.SubProtocols
|
||||
return s.makeProtocols(ServerProtocolVersions)
|
||||
}
|
||||
|
||||
// Start starts the LES server
|
||||
|
|
|
|||
|
|
@ -116,19 +116,19 @@ func NewLightChain(odr OdrBackend, config *params.ChainConfig, engine consensus.
|
|||
}
|
||||
|
||||
// addTrustedCheckpoint adds a trusted checkpoint to the blockchain
|
||||
func (self *LightChain) addTrustedCheckpoint(cp trustedCheckpoint) {
|
||||
func (self *LightChain) addTrustedCheckpoint(cp TrustedCheckpoint) {
|
||||
if self.odr.ChtIndexer() != nil {
|
||||
StoreChtRoot(self.chainDb, cp.sectionIdx, cp.sectionHead, cp.chtRoot)
|
||||
self.odr.ChtIndexer().AddKnownSectionHead(cp.sectionIdx, cp.sectionHead)
|
||||
StoreChtRoot(self.chainDb, cp.SectionIdx, cp.SectionHead, cp.CHTRoot)
|
||||
self.odr.ChtIndexer().AddKnownSectionHead(cp.SectionIdx, cp.SectionHead)
|
||||
}
|
||||
if self.odr.BloomTrieIndexer() != nil {
|
||||
StoreBloomTrieRoot(self.chainDb, cp.sectionIdx, cp.sectionHead, cp.bloomTrieRoot)
|
||||
self.odr.BloomTrieIndexer().AddKnownSectionHead(cp.sectionIdx, cp.sectionHead)
|
||||
StoreBloomTrieRoot(self.chainDb, cp.SectionIdx, cp.SectionHead, cp.BloomRoot)
|
||||
self.odr.BloomTrieIndexer().AddKnownSectionHead(cp.SectionIdx, cp.SectionHead)
|
||||
}
|
||||
if self.odr.BloomIndexer() != nil {
|
||||
self.odr.BloomIndexer().AddKnownSectionHead(cp.sectionIdx, cp.sectionHead)
|
||||
self.odr.BloomIndexer().AddKnownSectionHead(cp.SectionIdx, cp.SectionHead)
|
||||
}
|
||||
log.Info("Added trusted checkpoint", "chain", cp.name, "block", (cp.sectionIdx+1)*CHTFrequencyClient-1, "hash", cp.sectionHead)
|
||||
log.Info("Added trusted checkpoint", "chain", cp.name, "block", (cp.SectionIdx+1)*CHTFrequencyClient-1, "hash", cp.SectionHead)
|
||||
}
|
||||
|
||||
func (self *LightChain) getProcInterrupt() bool {
|
||||
|
|
@ -464,22 +464,32 @@ func (self *LightChain) GetHeaderByNumberOdr(ctx context.Context, number uint64)
|
|||
func (self *LightChain) Config() *params.ChainConfig { return self.hc.Config() }
|
||||
|
||||
func (self *LightChain) SyncCht(ctx context.Context) bool {
|
||||
// If we don't have a CHT indexer, abort
|
||||
if self.odr.ChtIndexer() == nil {
|
||||
return false
|
||||
}
|
||||
headNum := self.CurrentHeader().Number.Uint64()
|
||||
chtCount, _, _ := self.odr.ChtIndexer().Sections()
|
||||
if headNum+1 < chtCount*CHTFrequencyClient {
|
||||
num := chtCount*CHTFrequencyClient - 1
|
||||
header, err := GetHeaderByNumber(ctx, self.odr, num)
|
||||
if header != nil && err == nil {
|
||||
self.mu.Lock()
|
||||
if self.hc.CurrentHeader().Number.Uint64() < header.Number.Uint64() {
|
||||
self.hc.SetCurrentHeader(header)
|
||||
}
|
||||
self.mu.Unlock()
|
||||
return true
|
||||
// Ensure the remote CHT head is ahead of us
|
||||
head := self.CurrentHeader().Number.Uint64()
|
||||
sections, _, _ := self.odr.ChtIndexer().Sections()
|
||||
|
||||
latest := sections*CHTFrequencyClient - 1
|
||||
if clique := self.hc.Config().Clique; clique != nil {
|
||||
latest -= latest % clique.Epoch // epoch snapshot for clique
|
||||
}
|
||||
if head >= latest {
|
||||
return false
|
||||
}
|
||||
// Retrieve the latest useful header and update to it
|
||||
if header, err := GetHeaderByNumber(ctx, self.odr, latest); header != nil && err == nil {
|
||||
self.mu.Lock()
|
||||
defer self.mu.Unlock()
|
||||
|
||||
// Ensure the chain didn't move past the latest block while retrieving it
|
||||
if self.hc.CurrentHeader().Number.Uint64() < header.Number.Uint64() {
|
||||
log.Info("Updated latest header based on CHT", "number", header.Number, "hash", header.Hash())
|
||||
self.hc.SetCurrentHeader(header)
|
||||
}
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ package light
|
|||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"math/big"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
|
|
@ -33,6 +34,9 @@ import (
|
|||
// service is not required.
|
||||
var NoOdr = context.Background()
|
||||
|
||||
// ErrNoPeers is returned if no peers capable of serving a queued request are available
|
||||
var ErrNoPeers = errors.New("no suitable peers available")
|
||||
|
||||
// OdrBackend is an interface to a backend service that handles ODR retrievals type
|
||||
type OdrBackend interface {
|
||||
Database() ethdb.Database
|
||||
|
|
|
|||
|
|
@ -17,8 +17,10 @@
|
|||
package light
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"time"
|
||||
|
||||
|
|
@ -47,37 +49,38 @@ const (
|
|||
HelperTrieProcessConfirmations = 256 // number of confirmations before a HelperTrie is generated
|
||||
)
|
||||
|
||||
// trustedCheckpoint represents a set of post-processed trie roots (CHT and BloomTrie) associated with
|
||||
// TrustedCheckpoint represents a set of post-processed trie roots (CHT and BloomTrie) associated with
|
||||
// the appropriate section index and head hash. It is used to start light syncing from this checkpoint
|
||||
// and avoid downloading the entire header chain while still being able to securely access old headers/logs.
|
||||
type trustedCheckpoint struct {
|
||||
name string
|
||||
sectionIdx uint64
|
||||
sectionHead, chtRoot, bloomTrieRoot common.Hash
|
||||
type TrustedCheckpoint struct {
|
||||
name string
|
||||
SectionIdx uint64
|
||||
SectionHead, CHTRoot, BloomRoot common.Hash
|
||||
}
|
||||
|
||||
var (
|
||||
mainnetCheckpoint = trustedCheckpoint{
|
||||
name: "mainnet",
|
||||
sectionIdx: 179,
|
||||
sectionHead: common.HexToHash("ae778e455492db1183e566fa0c67f954d256fdd08618f6d5a393b0e24576d0ea"),
|
||||
chtRoot: common.HexToHash("646b338f9ca74d936225338916be53710ec84020b89946004a8605f04c817f16"),
|
||||
bloomTrieRoot: common.HexToHash("d0f978f5dbc86e5bf931d8dd5b2ecbebbda6dc78f8896af6a27b46a3ced0ac25"),
|
||||
}
|
||||
|
||||
ropstenCheckpoint = trustedCheckpoint{
|
||||
name: "ropsten",
|
||||
sectionIdx: 107,
|
||||
sectionHead: common.HexToHash("e1988f95399debf45b873e065e5cd61b416ef2e2e5deec5a6f87c3127086e1ce"),
|
||||
chtRoot: common.HexToHash("15cba18e4de0ab1e95e202625199ba30147aec8b0b70384b66ebea31ba6a18e0"),
|
||||
bloomTrieRoot: common.HexToHash("e00fa6389b2e597d9df52172cd8e936879eed0fca4fa59db99e2c8ed682562f2"),
|
||||
}
|
||||
)
|
||||
|
||||
// trustedCheckpoints associates each known checkpoint with the genesis hash of the chain it belongs to
|
||||
var trustedCheckpoints = map[common.Hash]trustedCheckpoint{
|
||||
params.MainnetGenesisHash: mainnetCheckpoint,
|
||||
params.TestnetGenesisHash: ropstenCheckpoint,
|
||||
var trustedCheckpoints = map[common.Hash]TrustedCheckpoint{
|
||||
params.MainnetGenesisHash: {
|
||||
name: "mainnet",
|
||||
SectionIdx: 187,
|
||||
SectionHead: common.HexToHash("e6baa034efa31562d71ff23676512dec6562c1ad0301e08843b907e81958c696"),
|
||||
CHTRoot: common.HexToHash("28001955219719cf06de1b08648969139d123a9835fc760547a1e4dabdabc15a"),
|
||||
BloomRoot: common.HexToHash("395ca2373fc662720ac6b58b3bbe71f68aa0f38b63b2d3553dd32ff3c51eebc4"),
|
||||
},
|
||||
params.TestnetGenesisHash: {
|
||||
name: "ropsten",
|
||||
SectionIdx: 117,
|
||||
SectionHead: common.HexToHash("9529b38631ae30783f56cbe4c3b9f07575b770ecba4f6e20a274b1e2f40fede1"),
|
||||
CHTRoot: common.HexToHash("6f48e9f101f1fac98e7d74fbbcc4fda138358271ffd974d40d2506f0308bb363"),
|
||||
BloomRoot: common.HexToHash("8242342e66e942c0cd893484e6736b9862ceb88b43ca344bb06a8285ac1b6d64"),
|
||||
},
|
||||
params.RinkebyGenesisHash: {
|
||||
name: "rinkeby",
|
||||
SectionIdx: 85,
|
||||
SectionHead: common.HexToHash("92cfa67afc4ad8ab0dcbc6fa49efd14b5b19402442e7317e6bc879d85f89d64d"),
|
||||
CHTRoot: common.HexToHash("2802ec92cd7a54a75bca96afdc666ae7b99e5d96cf8192dcfb09588812f51564"),
|
||||
BloomRoot: common.HexToHash("ebefeb31a9a42866d8cf2d2477704b4c3d7c20d0e4e9b5aaa77f396e016a1263"),
|
||||
},
|
||||
}
|
||||
|
||||
var (
|
||||
|
|
@ -119,7 +122,8 @@ func StoreChtRoot(db ethdb.Database, sectionIdx uint64, sectionHead, root common
|
|||
|
||||
// ChtIndexerBackend implements core.ChainIndexerBackend
|
||||
type ChtIndexerBackend struct {
|
||||
diskdb ethdb.Database
|
||||
diskdb, trieTable ethdb.Database
|
||||
odr OdrBackend
|
||||
triedb *trie.Database
|
||||
section, sectionSize uint64
|
||||
lastHash common.Hash
|
||||
|
|
@ -127,7 +131,7 @@ type ChtIndexerBackend struct {
|
|||
}
|
||||
|
||||
// NewBloomTrieIndexer creates a BloomTrie chain indexer
|
||||
func NewChtIndexer(db ethdb.Database, clientMode bool) *core.ChainIndexer {
|
||||
func NewChtIndexer(db ethdb.Database, clientMode bool, odr OdrBackend) *core.ChainIndexer {
|
||||
var sectionSize, confirmReq uint64
|
||||
if clientMode {
|
||||
sectionSize = CHTFrequencyClient
|
||||
|
|
@ -137,28 +141,64 @@ func NewChtIndexer(db ethdb.Database, clientMode bool) *core.ChainIndexer {
|
|||
confirmReq = HelperTrieProcessConfirmations
|
||||
}
|
||||
idb := ethdb.NewTable(db, "chtIndex-")
|
||||
trieTable := ethdb.NewTable(db, ChtTablePrefix)
|
||||
backend := &ChtIndexerBackend{
|
||||
diskdb: db,
|
||||
triedb: trie.NewDatabase(ethdb.NewTable(db, ChtTablePrefix)),
|
||||
odr: odr,
|
||||
trieTable: trieTable,
|
||||
triedb: trie.NewDatabase(trieTable),
|
||||
sectionSize: sectionSize,
|
||||
}
|
||||
return core.NewChainIndexer(db, idb, backend, sectionSize, confirmReq, time.Millisecond*100, "cht")
|
||||
}
|
||||
|
||||
// fetchMissingNodes tries to retrieve the last entry of the latest trusted CHT from the
|
||||
// ODR backend in order to be able to add new entries and calculate subsequent root hashes
|
||||
func (c *ChtIndexerBackend) fetchMissingNodes(ctx context.Context, section uint64, root common.Hash) error {
|
||||
batch := c.trieTable.NewBatch()
|
||||
r := &ChtRequest{ChtRoot: root, ChtNum: section - 1, BlockNum: section*c.sectionSize - 1}
|
||||
for {
|
||||
err := c.odr.Retrieve(ctx, r)
|
||||
switch err {
|
||||
case nil:
|
||||
r.Proof.Store(batch)
|
||||
return batch.Write()
|
||||
case ErrNoPeers:
|
||||
// if there are no peers to serve, retry later
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
case <-time.After(time.Second * 10):
|
||||
// stay in the loop and try again
|
||||
}
|
||||
default:
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Reset implements core.ChainIndexerBackend
|
||||
func (c *ChtIndexerBackend) Reset(section uint64, lastSectionHead common.Hash) error {
|
||||
func (c *ChtIndexerBackend) Reset(ctx context.Context, section uint64, lastSectionHead common.Hash) error {
|
||||
var root common.Hash
|
||||
if section > 0 {
|
||||
root = GetChtRoot(c.diskdb, section-1, lastSectionHead)
|
||||
}
|
||||
var err error
|
||||
c.trie, err = trie.New(root, c.triedb)
|
||||
|
||||
if err != nil && c.odr != nil {
|
||||
err = c.fetchMissingNodes(ctx, section, root)
|
||||
if err == nil {
|
||||
c.trie, err = trie.New(root, c.triedb)
|
||||
}
|
||||
}
|
||||
|
||||
c.section = section
|
||||
return err
|
||||
}
|
||||
|
||||
// Process implements core.ChainIndexerBackend
|
||||
func (c *ChtIndexerBackend) Process(header *types.Header) {
|
||||
func (c *ChtIndexerBackend) Process(ctx context.Context, header *types.Header) error {
|
||||
hash, num := header.Hash(), header.Number.Uint64()
|
||||
c.lastHash = hash
|
||||
|
||||
|
|
@ -170,6 +210,7 @@ func (c *ChtIndexerBackend) Process(header *types.Header) {
|
|||
binary.BigEndian.PutUint64(encNumber[:], num)
|
||||
data, _ := rlp.EncodeToBytes(ChtNode{hash, td})
|
||||
c.trie.Update(encNumber[:], data)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Commit implements core.ChainIndexerBackend
|
||||
|
|
@ -181,16 +222,15 @@ func (c *ChtIndexerBackend) Commit() error {
|
|||
c.triedb.Commit(root, false)
|
||||
|
||||
if ((c.section+1)*c.sectionSize)%CHTFrequencyClient == 0 {
|
||||
log.Info("Storing CHT", "section", c.section*c.sectionSize/CHTFrequencyClient, "head", c.lastHash, "root", root)
|
||||
log.Info("Storing CHT", "section", c.section*c.sectionSize/CHTFrequencyClient, "head", fmt.Sprintf("%064x", c.lastHash), "root", fmt.Sprintf("%064x", root))
|
||||
}
|
||||
StoreChtRoot(c.diskdb, c.section, c.lastHash, root)
|
||||
return nil
|
||||
}
|
||||
|
||||
const (
|
||||
BloomTrieFrequency = 32768
|
||||
ethBloomBitsSection = 4096
|
||||
ethBloomBitsConfirmations = 256
|
||||
BloomTrieFrequency = 32768
|
||||
ethBloomBitsSection = 4096
|
||||
)
|
||||
|
||||
var (
|
||||
|
|
@ -215,7 +255,8 @@ func StoreBloomTrieRoot(db ethdb.Database, sectionIdx uint64, sectionHead, root
|
|||
|
||||
// BloomTrieIndexerBackend implements core.ChainIndexerBackend
|
||||
type BloomTrieIndexerBackend struct {
|
||||
diskdb ethdb.Database
|
||||
diskdb, trieTable ethdb.Database
|
||||
odr OdrBackend
|
||||
triedb *trie.Database
|
||||
section, parentSectionSize, bloomTrieRatio uint64
|
||||
trie *trie.Trie
|
||||
|
|
@ -223,44 +264,98 @@ type BloomTrieIndexerBackend struct {
|
|||
}
|
||||
|
||||
// NewBloomTrieIndexer creates a BloomTrie chain indexer
|
||||
func NewBloomTrieIndexer(db ethdb.Database, clientMode bool) *core.ChainIndexer {
|
||||
func NewBloomTrieIndexer(db ethdb.Database, clientMode bool, odr OdrBackend) *core.ChainIndexer {
|
||||
trieTable := ethdb.NewTable(db, BloomTrieTablePrefix)
|
||||
backend := &BloomTrieIndexerBackend{
|
||||
diskdb: db,
|
||||
triedb: trie.NewDatabase(ethdb.NewTable(db, BloomTrieTablePrefix)),
|
||||
diskdb: db,
|
||||
odr: odr,
|
||||
trieTable: trieTable,
|
||||
triedb: trie.NewDatabase(trieTable),
|
||||
}
|
||||
idb := ethdb.NewTable(db, "bltIndex-")
|
||||
|
||||
var confirmReq uint64
|
||||
if clientMode {
|
||||
backend.parentSectionSize = BloomTrieFrequency
|
||||
confirmReq = HelperTrieConfirmations
|
||||
} else {
|
||||
backend.parentSectionSize = ethBloomBitsSection
|
||||
confirmReq = HelperTrieProcessConfirmations
|
||||
}
|
||||
backend.bloomTrieRatio = BloomTrieFrequency / backend.parentSectionSize
|
||||
backend.sectionHeads = make([]common.Hash, backend.bloomTrieRatio)
|
||||
return core.NewChainIndexer(db, idb, backend, BloomTrieFrequency, confirmReq-ethBloomBitsConfirmations, time.Millisecond*100, "bloomtrie")
|
||||
return core.NewChainIndexer(db, idb, backend, BloomTrieFrequency, 0, time.Millisecond*100, "bloomtrie")
|
||||
}
|
||||
|
||||
// fetchMissingNodes tries to retrieve the last entries of the latest trusted bloom trie from the
|
||||
// ODR backend in order to be able to add new entries and calculate subsequent root hashes
|
||||
func (b *BloomTrieIndexerBackend) fetchMissingNodes(ctx context.Context, section uint64, root common.Hash) error {
|
||||
indexCh := make(chan uint, types.BloomBitLength)
|
||||
type res struct {
|
||||
nodes *NodeSet
|
||||
err error
|
||||
}
|
||||
resCh := make(chan res, types.BloomBitLength)
|
||||
for i := 0; i < 20; i++ {
|
||||
go func() {
|
||||
for bitIndex := range indexCh {
|
||||
r := &BloomRequest{BloomTrieRoot: root, BloomTrieNum: section - 1, BitIdx: bitIndex, SectionIdxList: []uint64{section - 1}}
|
||||
for {
|
||||
if err := b.odr.Retrieve(ctx, r); err == ErrNoPeers {
|
||||
// if there are no peers to serve, retry later
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
resCh <- res{nil, ctx.Err()}
|
||||
return
|
||||
case <-time.After(time.Second * 10):
|
||||
// stay in the loop and try again
|
||||
}
|
||||
} else {
|
||||
resCh <- res{r.Proofs, err}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
for i := uint(0); i < types.BloomBitLength; i++ {
|
||||
indexCh <- i
|
||||
}
|
||||
close(indexCh)
|
||||
batch := b.trieTable.NewBatch()
|
||||
for i := uint(0); i < types.BloomBitLength; i++ {
|
||||
res := <-resCh
|
||||
if res.err != nil {
|
||||
return res.err
|
||||
}
|
||||
res.nodes.Store(batch)
|
||||
}
|
||||
return batch.Write()
|
||||
}
|
||||
|
||||
// Reset implements core.ChainIndexerBackend
|
||||
func (b *BloomTrieIndexerBackend) Reset(section uint64, lastSectionHead common.Hash) error {
|
||||
func (b *BloomTrieIndexerBackend) Reset(ctx context.Context, section uint64, lastSectionHead common.Hash) error {
|
||||
var root common.Hash
|
||||
if section > 0 {
|
||||
root = GetBloomTrieRoot(b.diskdb, section-1, lastSectionHead)
|
||||
}
|
||||
var err error
|
||||
b.trie, err = trie.New(root, b.triedb)
|
||||
if err != nil && b.odr != nil {
|
||||
err = b.fetchMissingNodes(ctx, section, root)
|
||||
if err == nil {
|
||||
b.trie, err = trie.New(root, b.triedb)
|
||||
}
|
||||
}
|
||||
b.section = section
|
||||
return err
|
||||
}
|
||||
|
||||
// Process implements core.ChainIndexerBackend
|
||||
func (b *BloomTrieIndexerBackend) Process(header *types.Header) {
|
||||
func (b *BloomTrieIndexerBackend) Process(ctx context.Context, header *types.Header) error {
|
||||
num := header.Number.Uint64() - b.section*BloomTrieFrequency
|
||||
if (num+1)%b.parentSectionSize == 0 {
|
||||
b.sectionHeads[num/b.parentSectionSize] = header.Hash()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Commit implements core.ChainIndexerBackend
|
||||
|
|
@ -300,7 +395,7 @@ func (b *BloomTrieIndexerBackend) Commit() error {
|
|||
b.triedb.Commit(root, false)
|
||||
|
||||
sectionHead := b.sectionHeads[b.bloomTrieRatio-1]
|
||||
log.Info("Storing bloom trie", "section", b.section, "head", sectionHead, "root", root, "compression", float64(compSize)/float64(decompSize))
|
||||
log.Info("Storing bloom trie", "section", b.section, "head", fmt.Sprintf("%064x", sectionHead), "root", fmt.Sprintf("%064x", root), "compression", float64(compSize)/float64(decompSize))
|
||||
StoreBloomTrieRoot(b.diskdb, b.section, sectionHead, root)
|
||||
|
||||
return nil
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ package miner
|
|||
import (
|
||||
"fmt"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/consensus"
|
||||
|
|
@ -51,13 +52,13 @@ type Miner struct {
|
|||
shouldStart int32 // should start indicates whether we should start after sync
|
||||
}
|
||||
|
||||
func New(eth Backend, config *params.ChainConfig, mux *event.TypeMux, engine consensus.Engine) *Miner {
|
||||
func New(eth Backend, config *params.ChainConfig, mux *event.TypeMux, engine consensus.Engine, recommit time.Duration) *Miner {
|
||||
miner := &Miner{
|
||||
eth: eth,
|
||||
mux: mux,
|
||||
engine: engine,
|
||||
exitCh: make(chan struct{}),
|
||||
worker: newWorker(config, engine, eth, mux),
|
||||
worker: newWorker(config, engine, eth, mux, recommit),
|
||||
canStart: 1,
|
||||
}
|
||||
go miner.update()
|
||||
|
|
@ -144,6 +145,11 @@ func (self *Miner) SetExtra(extra []byte) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// SetRecommitInterval sets the interval for sealing work resubmitting.
|
||||
func (self *Miner) SetRecommitInterval(interval time.Duration) {
|
||||
self.worker.setRecommitInterval(interval)
|
||||
}
|
||||
|
||||
// Pending returns the currently pending block and associated state.
|
||||
func (self *Miner) Pending() (*types.Block, *state.StateDB) {
|
||||
return self.worker.pending()
|
||||
|
|
|
|||
217
miner/stress_clique.go
Normal file
217
miner/stress_clique.go
Normal file
|
|
@ -0,0 +1,217 @@
|
|||
// 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/>.
|
||||
|
||||
// +build none
|
||||
|
||||
// This file contains a miner stress test based on the Clique consensus engine.
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/ecdsa"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"math/big"
|
||||
"math/rand"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/accounts/keystore"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/fdlimit"
|
||||
"github.com/ethereum/go-ethereum/core"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/eth"
|
||||
"github.com/ethereum/go-ethereum/eth/downloader"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/node"
|
||||
"github.com/ethereum/go-ethereum/p2p"
|
||||
"github.com/ethereum/go-ethereum/p2p/discover"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
)
|
||||
|
||||
func main() {
|
||||
log.Root().SetHandler(log.LvlFilterHandler(log.LvlInfo, log.StreamHandler(os.Stderr, log.TerminalFormat(true))))
|
||||
fdlimit.Raise(2048)
|
||||
|
||||
// Generate a batch of accounts to seal and fund with
|
||||
faucets := make([]*ecdsa.PrivateKey, 128)
|
||||
for i := 0; i < len(faucets); i++ {
|
||||
faucets[i], _ = crypto.GenerateKey()
|
||||
}
|
||||
sealers := make([]*ecdsa.PrivateKey, 4)
|
||||
for i := 0; i < len(sealers); i++ {
|
||||
sealers[i], _ = crypto.GenerateKey()
|
||||
}
|
||||
// Create a Clique network based off of the Rinkeby config
|
||||
genesis := makeGenesis(faucets, sealers)
|
||||
|
||||
var (
|
||||
nodes []*node.Node
|
||||
enodes []string
|
||||
)
|
||||
for _, sealer := range sealers {
|
||||
// Start the node and wait until it's up
|
||||
node, err := makeSealer(genesis, enodes)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer node.Stop()
|
||||
|
||||
for node.Server().NodeInfo().Ports.Listener == 0 {
|
||||
time.Sleep(250 * time.Millisecond)
|
||||
}
|
||||
// Connect the node to al the previous ones
|
||||
for _, enode := range enodes {
|
||||
enode, err := discover.ParseNode(enode)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
node.Server().AddPeer(enode)
|
||||
}
|
||||
// Start tracking the node and it's enode url
|
||||
nodes = append(nodes, node)
|
||||
|
||||
enode := fmt.Sprintf("enode://%s@127.0.0.1:%d", node.Server().NodeInfo().ID, node.Server().NodeInfo().Ports.Listener)
|
||||
enodes = append(enodes, enode)
|
||||
|
||||
// Inject the signer key and start sealing with it
|
||||
store := node.AccountManager().Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore)
|
||||
signer, err := store.ImportECDSA(sealer, "")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err := store.Unlock(signer, ""); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
// Iterate over all the nodes and start signing with them
|
||||
time.Sleep(3 * time.Second)
|
||||
|
||||
for _, node := range nodes {
|
||||
var ethereum *eth.Ethereum
|
||||
if err := node.Service(ðereum); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err := ethereum.StartMining(1); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
time.Sleep(3 * time.Second)
|
||||
|
||||
// Start injecting transactions from the faucet like crazy
|
||||
nonces := make([]uint64, len(faucets))
|
||||
for {
|
||||
index := rand.Intn(len(faucets))
|
||||
|
||||
// Fetch the accessor for the relevant signer
|
||||
var ethereum *eth.Ethereum
|
||||
if err := nodes[index%len(nodes)].Service(ðereum); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
// Create a self transaction and inject into the pool
|
||||
tx, err := types.SignTx(types.NewTransaction(nonces[index], crypto.PubkeyToAddress(faucets[index].PublicKey), new(big.Int), 21000, big.NewInt(100000000000), nil), types.HomesteadSigner{}, faucets[index])
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err := ethereum.TxPool().AddLocal(tx); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
nonces[index]++
|
||||
|
||||
// Wait if we're too saturated
|
||||
if pend, _ := ethereum.TxPool().Stats(); pend > 2048 {
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// makeGenesis creates a custom Clique genesis block based on some pre-defined
|
||||
// signer and faucet accounts.
|
||||
func makeGenesis(faucets []*ecdsa.PrivateKey, sealers []*ecdsa.PrivateKey) *core.Genesis {
|
||||
// Create a Clique network based off of the Rinkeby config
|
||||
genesis := core.DefaultRinkebyGenesisBlock()
|
||||
genesis.GasLimit = 25000000
|
||||
|
||||
genesis.Config.ChainID = big.NewInt(18)
|
||||
genesis.Config.Clique.Period = 1
|
||||
genesis.Config.EIP150Hash = common.Hash{}
|
||||
|
||||
genesis.Alloc = core.GenesisAlloc{}
|
||||
for _, faucet := range faucets {
|
||||
genesis.Alloc[crypto.PubkeyToAddress(faucet.PublicKey)] = core.GenesisAccount{
|
||||
Balance: new(big.Int).Exp(big.NewInt(2), big.NewInt(128), nil),
|
||||
}
|
||||
}
|
||||
// Sort the signers and embed into the extra-data section
|
||||
signers := make([]common.Address, len(sealers))
|
||||
for i, sealer := range sealers {
|
||||
signers[i] = crypto.PubkeyToAddress(sealer.PublicKey)
|
||||
}
|
||||
for i := 0; i < len(signers); i++ {
|
||||
for j := i + 1; j < len(signers); j++ {
|
||||
if bytes.Compare(signers[i][:], signers[j][:]) > 0 {
|
||||
signers[i], signers[j] = signers[j], signers[i]
|
||||
}
|
||||
}
|
||||
}
|
||||
genesis.ExtraData = make([]byte, 32+len(signers)*common.AddressLength+65)
|
||||
for i, signer := range signers {
|
||||
copy(genesis.ExtraData[32+i*common.AddressLength:], signer[:])
|
||||
}
|
||||
// Return the genesis block for initialization
|
||||
return genesis
|
||||
}
|
||||
|
||||
func makeSealer(genesis *core.Genesis, nodes []string) (*node.Node, error) {
|
||||
// Define the basic configurations for the Ethereum node
|
||||
datadir, _ := ioutil.TempDir("", "")
|
||||
|
||||
config := &node.Config{
|
||||
Name: "geth",
|
||||
Version: params.Version,
|
||||
DataDir: datadir,
|
||||
P2P: p2p.Config{
|
||||
ListenAddr: "0.0.0.0:0",
|
||||
NoDiscovery: true,
|
||||
MaxPeers: 25,
|
||||
},
|
||||
NoUSB: true,
|
||||
}
|
||||
// Start the node and configure a full Ethereum node on it
|
||||
stack, err := node.New(config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := stack.Register(func(ctx *node.ServiceContext) (node.Service, error) {
|
||||
return eth.New(ctx, ð.Config{
|
||||
Genesis: genesis,
|
||||
NetworkId: genesis.Config.ChainID.Uint64(),
|
||||
SyncMode: downloader.FullSync,
|
||||
DatabaseCache: 256,
|
||||
DatabaseHandles: 256,
|
||||
TxPool: core.DefaultTxPoolConfig,
|
||||
GPO: eth.DefaultConfig.GPO,
|
||||
MinerGasPrice: big.NewInt(1),
|
||||
MinerRecommit: time.Second,
|
||||
})
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Start the node and return if successful
|
||||
return stack, stack.Start()
|
||||
}
|
||||
197
miner/stress_ethash.go
Normal file
197
miner/stress_ethash.go
Normal file
|
|
@ -0,0 +1,197 @@
|
|||
// 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/>.
|
||||
|
||||
// +build none
|
||||
|
||||
// This file contains a miner stress test based on the Ethash consensus engine.
|
||||
package main
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"math/big"
|
||||
"math/rand"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/accounts/keystore"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/fdlimit"
|
||||
"github.com/ethereum/go-ethereum/consensus/ethash"
|
||||
"github.com/ethereum/go-ethereum/core"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/eth"
|
||||
"github.com/ethereum/go-ethereum/eth/downloader"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/node"
|
||||
"github.com/ethereum/go-ethereum/p2p"
|
||||
"github.com/ethereum/go-ethereum/p2p/discover"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
)
|
||||
|
||||
func main() {
|
||||
log.Root().SetHandler(log.LvlFilterHandler(log.LvlInfo, log.StreamHandler(os.Stderr, log.TerminalFormat(true))))
|
||||
fdlimit.Raise(2048)
|
||||
|
||||
// Generate a batch of accounts to seal and fund with
|
||||
faucets := make([]*ecdsa.PrivateKey, 128)
|
||||
for i := 0; i < len(faucets); i++ {
|
||||
faucets[i], _ = crypto.GenerateKey()
|
||||
}
|
||||
// Pre-generate the ethash mining DAG so we don't race
|
||||
ethash.MakeDataset(1, filepath.Join(os.Getenv("HOME"), ".ethash"))
|
||||
|
||||
// Create an Ethash network based off of the Ropsten config
|
||||
genesis := makeGenesis(faucets)
|
||||
|
||||
var (
|
||||
nodes []*node.Node
|
||||
enodes []string
|
||||
)
|
||||
for i := 0; i < 4; i++ {
|
||||
// Start the node and wait until it's up
|
||||
node, err := makeMiner(genesis, enodes)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer node.Stop()
|
||||
|
||||
for node.Server().NodeInfo().Ports.Listener == 0 {
|
||||
time.Sleep(250 * time.Millisecond)
|
||||
}
|
||||
// Connect the node to al the previous ones
|
||||
for _, enode := range enodes {
|
||||
enode, err := discover.ParseNode(enode)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
node.Server().AddPeer(enode)
|
||||
}
|
||||
// Start tracking the node and it's enode url
|
||||
nodes = append(nodes, node)
|
||||
|
||||
enode := fmt.Sprintf("enode://%s@127.0.0.1:%d", node.Server().NodeInfo().ID, node.Server().NodeInfo().Ports.Listener)
|
||||
enodes = append(enodes, enode)
|
||||
|
||||
// Inject the signer key and start sealing with it
|
||||
store := node.AccountManager().Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore)
|
||||
if _, err := store.NewAccount(""); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
// Iterate over all the nodes and start signing with them
|
||||
time.Sleep(3 * time.Second)
|
||||
|
||||
for _, node := range nodes {
|
||||
var ethereum *eth.Ethereum
|
||||
if err := node.Service(ðereum); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err := ethereum.StartMining(1); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
time.Sleep(3 * time.Second)
|
||||
|
||||
// Start injecting transactions from the faucets like crazy
|
||||
nonces := make([]uint64, len(faucets))
|
||||
for {
|
||||
index := rand.Intn(len(faucets))
|
||||
|
||||
// Fetch the accessor for the relevant signer
|
||||
var ethereum *eth.Ethereum
|
||||
if err := nodes[index%len(nodes)].Service(ðereum); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
// Create a self transaction and inject into the pool
|
||||
tx, err := types.SignTx(types.NewTransaction(nonces[index], crypto.PubkeyToAddress(faucets[index].PublicKey), new(big.Int), 21000, big.NewInt(100000000000+rand.Int63n(65536)), nil), types.HomesteadSigner{}, faucets[index])
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err := ethereum.TxPool().AddLocal(tx); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
nonces[index]++
|
||||
|
||||
// Wait if we're too saturated
|
||||
if pend, _ := ethereum.TxPool().Stats(); pend > 2048 {
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// makeGenesis creates a custom Ethash genesis block based on some pre-defined
|
||||
// faucet accounts.
|
||||
func makeGenesis(faucets []*ecdsa.PrivateKey) *core.Genesis {
|
||||
genesis := core.DefaultTestnetGenesisBlock()
|
||||
genesis.Difficulty = params.MinimumDifficulty
|
||||
genesis.GasLimit = 25000000
|
||||
|
||||
genesis.Config.ChainID = big.NewInt(18)
|
||||
genesis.Config.EIP150Hash = common.Hash{}
|
||||
|
||||
genesis.Alloc = core.GenesisAlloc{}
|
||||
for _, faucet := range faucets {
|
||||
genesis.Alloc[crypto.PubkeyToAddress(faucet.PublicKey)] = core.GenesisAccount{
|
||||
Balance: new(big.Int).Exp(big.NewInt(2), big.NewInt(128), nil),
|
||||
}
|
||||
}
|
||||
return genesis
|
||||
}
|
||||
|
||||
func makeMiner(genesis *core.Genesis, nodes []string) (*node.Node, error) {
|
||||
// Define the basic configurations for the Ethereum node
|
||||
datadir, _ := ioutil.TempDir("", "")
|
||||
|
||||
config := &node.Config{
|
||||
Name: "geth",
|
||||
Version: params.Version,
|
||||
DataDir: datadir,
|
||||
P2P: p2p.Config{
|
||||
ListenAddr: "0.0.0.0:0",
|
||||
NoDiscovery: true,
|
||||
MaxPeers: 25,
|
||||
},
|
||||
NoUSB: true,
|
||||
UseLightweightKDF: true,
|
||||
}
|
||||
// Start the node and configure a full Ethereum node on it
|
||||
stack, err := node.New(config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := stack.Register(func(ctx *node.ServiceContext) (node.Service, error) {
|
||||
return eth.New(ctx, ð.Config{
|
||||
Genesis: genesis,
|
||||
NetworkId: genesis.Config.ChainID.Uint64(),
|
||||
SyncMode: downloader.FullSync,
|
||||
DatabaseCache: 256,
|
||||
DatabaseHandles: 256,
|
||||
TxPool: core.DefaultTxPoolConfig,
|
||||
GPO: eth.DefaultConfig.GPO,
|
||||
Ethash: eth.DefaultConfig.Ethash,
|
||||
MinerGasPrice: big.NewInt(1),
|
||||
MinerRecommit: time.Second,
|
||||
})
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Start the node and return if successful
|
||||
return stack, stack.Start()
|
||||
}
|
||||
|
|
@ -25,11 +25,14 @@ import (
|
|||
"github.com/ethereum/go-ethereum/log"
|
||||
)
|
||||
|
||||
// headerRetriever is used by the unconfirmed block set to verify whether a previously
|
||||
// chainRetriever is used by the unconfirmed block set to verify whether a previously
|
||||
// mined block is part of the canonical chain or not.
|
||||
type headerRetriever interface {
|
||||
type chainRetriever interface {
|
||||
// GetHeaderByNumber retrieves the canonical header associated with a block number.
|
||||
GetHeaderByNumber(number uint64) *types.Header
|
||||
|
||||
// GetBlockByNumber retrieves the canonical block associated with a block number.
|
||||
GetBlockByNumber(number uint64) *types.Block
|
||||
}
|
||||
|
||||
// unconfirmedBlock is a small collection of metadata about a locally mined block
|
||||
|
|
@ -44,14 +47,14 @@ type unconfirmedBlock struct {
|
|||
// used by the miner to provide logs to the user when a previously mined block
|
||||
// has a high enough guarantee to not be reorged out of the canonical chain.
|
||||
type unconfirmedBlocks struct {
|
||||
chain headerRetriever // Blockchain to verify canonical status through
|
||||
depth uint // Depth after which to discard previous blocks
|
||||
blocks *ring.Ring // Block infos to allow canonical chain cross checks
|
||||
lock sync.RWMutex // Protects the fields from concurrent access
|
||||
chain chainRetriever // Blockchain to verify canonical status through
|
||||
depth uint // Depth after which to discard previous blocks
|
||||
blocks *ring.Ring // Block infos to allow canonical chain cross checks
|
||||
lock sync.RWMutex // Protects the fields from concurrent access
|
||||
}
|
||||
|
||||
// newUnconfirmedBlocks returns new data structure to track currently unconfirmed blocks.
|
||||
func newUnconfirmedBlocks(chain headerRetriever, depth uint) *unconfirmedBlocks {
|
||||
func newUnconfirmedBlocks(chain chainRetriever, depth uint) *unconfirmedBlocks {
|
||||
return &unconfirmedBlocks{
|
||||
chain: chain,
|
||||
depth: depth,
|
||||
|
|
@ -103,7 +106,23 @@ func (set *unconfirmedBlocks) Shift(height uint64) {
|
|||
case header.Hash() == next.hash:
|
||||
log.Info("🔗 block reached canonical chain", "number", next.index, "hash", next.hash)
|
||||
default:
|
||||
log.Info("⑂ block became a side fork", "number", next.index, "hash", next.hash)
|
||||
// Block is not canonical, check whether we have an uncle or a lost block
|
||||
included := false
|
||||
for number := next.index; !included && number < next.index+uint64(set.depth) && number <= height; number++ {
|
||||
if block := set.chain.GetBlockByNumber(number); block != nil {
|
||||
for _, uncle := range block.Uncles() {
|
||||
if uncle.Hash() == next.hash {
|
||||
included = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if included {
|
||||
log.Info("⑂ block became an uncle", "number", next.index, "hash", next.hash)
|
||||
} else {
|
||||
log.Info("😱 block lost", "number", next.index, "hash", next.hash)
|
||||
}
|
||||
}
|
||||
// Drop the block out of the ring
|
||||
if set.blocks.Value == set.blocks.Next().Value {
|
||||
|
|
|
|||
|
|
@ -23,11 +23,14 @@ import (
|
|||
"github.com/ethereum/go-ethereum/core/types"
|
||||
)
|
||||
|
||||
// noopHeaderRetriever is an implementation of headerRetriever that always
|
||||
// noopChainRetriever is an implementation of headerRetriever that always
|
||||
// returns nil for any requested headers.
|
||||
type noopHeaderRetriever struct{}
|
||||
type noopChainRetriever struct{}
|
||||
|
||||
func (r *noopHeaderRetriever) GetHeaderByNumber(number uint64) *types.Header {
|
||||
func (r *noopChainRetriever) GetHeaderByNumber(number uint64) *types.Header {
|
||||
return nil
|
||||
}
|
||||
func (r *noopChainRetriever) GetBlockByNumber(number uint64) *types.Block {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
@ -36,7 +39,7 @@ func (r *noopHeaderRetriever) GetHeaderByNumber(number uint64) *types.Header {
|
|||
func TestUnconfirmedInsertBounds(t *testing.T) {
|
||||
limit := uint(10)
|
||||
|
||||
pool := newUnconfirmedBlocks(new(noopHeaderRetriever), limit)
|
||||
pool := newUnconfirmedBlocks(new(noopChainRetriever), limit)
|
||||
for depth := uint64(0); depth < 2*uint64(limit); depth++ {
|
||||
// Insert multiple blocks for the same level just to stress it
|
||||
for i := 0; i < int(depth); i++ {
|
||||
|
|
@ -58,7 +61,7 @@ func TestUnconfirmedShifts(t *testing.T) {
|
|||
// Create a pool with a few blocks on various depths
|
||||
limit, start := uint(10), uint64(25)
|
||||
|
||||
pool := newUnconfirmedBlocks(new(noopHeaderRetriever), limit)
|
||||
pool := newUnconfirmedBlocks(new(noopChainRetriever), limit)
|
||||
for depth := start; depth < start+uint64(limit); depth++ {
|
||||
pool.Insert(depth, common.Hash([32]byte{byte(depth)}))
|
||||
}
|
||||
|
|
|
|||
681
miner/worker.go
681
miner/worker.go
|
|
@ -40,19 +40,45 @@ import (
|
|||
const (
|
||||
// resultQueueSize is the size of channel listening to sealing result.
|
||||
resultQueueSize = 10
|
||||
|
||||
// txChanSize is the size of channel listening to NewTxsEvent.
|
||||
// The number is referenced from the size of tx pool.
|
||||
txChanSize = 4096
|
||||
|
||||
// chainHeadChanSize is the size of channel listening to ChainHeadEvent.
|
||||
chainHeadChanSize = 10
|
||||
|
||||
// chainSideChanSize is the size of channel listening to ChainSideEvent.
|
||||
chainSideChanSize = 10
|
||||
miningLogAtDepth = 5
|
||||
|
||||
// resubmitAdjustChanSize is the size of resubmitting interval adjustment channel.
|
||||
resubmitAdjustChanSize = 10
|
||||
|
||||
// miningLogAtDepth is the number of confirmations before logging successful mining.
|
||||
miningLogAtDepth = 7
|
||||
|
||||
// minRecommitInterval is the minimal time interval to recreate the mining block with
|
||||
// any newly arrived transactions.
|
||||
minRecommitInterval = 1 * time.Second
|
||||
|
||||
// maxRecommitInterval is the maximum time interval to recreate the mining block with
|
||||
// any newly arrived transactions.
|
||||
maxRecommitInterval = 15 * time.Second
|
||||
|
||||
// intervalAdjustRatio is the impact a single interval adjustment has on sealing work
|
||||
// resubmitting interval.
|
||||
intervalAdjustRatio = 0.1
|
||||
|
||||
// intervalAdjustBias is applied during the new resubmit interval calculation in favor of
|
||||
// increasing upper limit or decreasing lower limit so that the limit can be reachable.
|
||||
intervalAdjustBias = 200 * 1000.0 * 1000.0
|
||||
|
||||
// staleThreshold is the maximum distance of the acceptable stale block.
|
||||
staleThreshold = 7
|
||||
)
|
||||
|
||||
// Env is the worker's current environment and holds all of the current state information.
|
||||
type Env struct {
|
||||
config *params.ChainConfig
|
||||
// environment is the worker's current environment and holds all of the current state information.
|
||||
type environment struct {
|
||||
signer types.Signer
|
||||
|
||||
state *state.StateDB // apply state changes here
|
||||
|
|
@ -67,105 +93,6 @@ type Env struct {
|
|||
receipts []*types.Receipt
|
||||
}
|
||||
|
||||
func (env *Env) commitTransaction(tx *types.Transaction, bc *core.BlockChain, coinbase common.Address, gp *core.GasPool) (error, []*types.Log) {
|
||||
snap := env.state.Snapshot()
|
||||
|
||||
receipt, _, err := core.ApplyTransaction(env.config, bc, &coinbase, gp, env.state, env.header, tx, &env.header.GasUsed, vm.Config{})
|
||||
if err != nil {
|
||||
env.state.RevertToSnapshot(snap)
|
||||
return err, nil
|
||||
}
|
||||
env.txs = append(env.txs, tx)
|
||||
env.receipts = append(env.receipts, receipt)
|
||||
|
||||
return nil, receipt.Logs
|
||||
}
|
||||
|
||||
func (env *Env) commitTransactions(mux *event.TypeMux, txs *types.TransactionsByPriceAndNonce, bc *core.BlockChain, coinbase common.Address) {
|
||||
if env.gasPool == nil {
|
||||
env.gasPool = new(core.GasPool).AddGas(env.header.GasLimit)
|
||||
}
|
||||
|
||||
var coalescedLogs []*types.Log
|
||||
|
||||
for {
|
||||
// If we don't have enough gas for any further transactions then we're done
|
||||
if env.gasPool.Gas() < params.TxGas {
|
||||
log.Trace("Not enough gas for further transactions", "have", env.gasPool, "want", params.TxGas)
|
||||
break
|
||||
}
|
||||
// Retrieve the next transaction and abort if all done
|
||||
tx := txs.Peek()
|
||||
if tx == nil {
|
||||
break
|
||||
}
|
||||
// Error may be ignored here. The error has already been checked
|
||||
// during transaction acceptance is the transaction pool.
|
||||
//
|
||||
// We use the eip155 signer regardless of the current hf.
|
||||
from, _ := types.Sender(env.signer, tx)
|
||||
// Check whether the tx is replay protected. If we're not in the EIP155 hf
|
||||
// phase, start ignoring the sender until we do.
|
||||
if tx.Protected() && !env.config.IsEIP155(env.header.Number) {
|
||||
log.Trace("Ignoring reply protected transaction", "hash", tx.Hash(), "eip155", env.config.EIP155Block)
|
||||
|
||||
txs.Pop()
|
||||
continue
|
||||
}
|
||||
// Start executing the transaction
|
||||
env.state.Prepare(tx.Hash(), common.Hash{}, env.tcount)
|
||||
|
||||
err, logs := env.commitTransaction(tx, bc, coinbase, env.gasPool)
|
||||
switch err {
|
||||
case core.ErrGasLimitReached:
|
||||
// Pop the current out-of-gas transaction without shifting in the next from the account
|
||||
log.Trace("Gas limit exceeded for current block", "sender", from)
|
||||
txs.Pop()
|
||||
|
||||
case core.ErrNonceTooLow:
|
||||
// New head notification data race between the transaction pool and miner, shift
|
||||
log.Trace("Skipping transaction with low nonce", "sender", from, "nonce", tx.Nonce())
|
||||
txs.Shift()
|
||||
|
||||
case core.ErrNonceTooHigh:
|
||||
// Reorg notification data race between the transaction pool and miner, skip account =
|
||||
log.Trace("Skipping account with hight nonce", "sender", from, "nonce", tx.Nonce())
|
||||
txs.Pop()
|
||||
|
||||
case nil:
|
||||
// Everything ok, collect the logs and shift in the next transaction from the same account
|
||||
coalescedLogs = append(coalescedLogs, logs...)
|
||||
env.tcount++
|
||||
txs.Shift()
|
||||
|
||||
default:
|
||||
// Strange error, discard the transaction and get the next in line (note, the
|
||||
// nonce-too-high clause will prevent us from executing in vain).
|
||||
log.Debug("Transaction failed, account skipped", "hash", tx.Hash(), "err", err)
|
||||
txs.Shift()
|
||||
}
|
||||
}
|
||||
|
||||
if len(coalescedLogs) > 0 || env.tcount > 0 {
|
||||
// make a copy, the state caches the logs and these logs get "upgraded" from pending to mined
|
||||
// logs by filling in the block hash when the block was mined by the local miner. This can
|
||||
// cause a race condition if a log was "upgraded" before the PendingLogsEvent is processed.
|
||||
cpy := make([]*types.Log, len(coalescedLogs))
|
||||
for i, l := range coalescedLogs {
|
||||
cpy[i] = new(types.Log)
|
||||
*cpy[i] = *l
|
||||
}
|
||||
go func(logs []*types.Log, tcount int) {
|
||||
if len(logs) > 0 {
|
||||
mux.Post(core.PendingLogsEvent{Logs: logs})
|
||||
}
|
||||
if tcount > 0 {
|
||||
mux.Post(core.PendingStateEvent{})
|
||||
}
|
||||
}(cpy, env.tcount)
|
||||
}
|
||||
}
|
||||
|
||||
// task contains all information for consensus engine sealing and result submitting.
|
||||
type task struct {
|
||||
receipts []*types.Receipt
|
||||
|
|
@ -174,6 +101,24 @@ type task struct {
|
|||
createdAt time.Time
|
||||
}
|
||||
|
||||
const (
|
||||
commitInterruptNone int32 = iota
|
||||
commitInterruptNewHead
|
||||
commitInterruptResubmit
|
||||
)
|
||||
|
||||
// newWorkReq represents a request for new sealing work submitting with relative interrupt notifier.
|
||||
type newWorkReq struct {
|
||||
interrupt *int32
|
||||
noempty bool
|
||||
}
|
||||
|
||||
// intervalAdjust represents a resubmitting interval adjustment.
|
||||
type intervalAdjust struct {
|
||||
ratio float64
|
||||
inc bool
|
||||
}
|
||||
|
||||
// worker is the main object which takes care of submitting new work to consensus engine
|
||||
// and gathering the sealing result.
|
||||
type worker struct {
|
||||
|
|
@ -192,12 +137,15 @@ type worker struct {
|
|||
chainSideSub event.Subscription
|
||||
|
||||
// Channels
|
||||
newWork chan struct{}
|
||||
taskCh chan *task
|
||||
resultCh chan *task
|
||||
exitCh chan struct{}
|
||||
newWorkCh chan *newWorkReq
|
||||
taskCh chan *task
|
||||
resultCh chan *task
|
||||
startCh chan struct{}
|
||||
exitCh chan struct{}
|
||||
resubmitIntervalCh chan time.Duration
|
||||
resubmitAdjustCh chan *intervalAdjust
|
||||
|
||||
current *Env // An environment for current running cycle.
|
||||
current *environment // An environment for current running cycle.
|
||||
possibleUncles map[common.Hash]*types.Block // A set of side blocks as the possible uncle blocks.
|
||||
unconfirmed *unconfirmedBlocks // A set of locally mined blocks pending canonicalness confirmations.
|
||||
|
||||
|
|
@ -205,34 +153,44 @@ type worker struct {
|
|||
coinbase common.Address
|
||||
extra []byte
|
||||
|
||||
pendingMu sync.RWMutex
|
||||
pendingTasks map[common.Hash]*task
|
||||
|
||||
snapshotMu sync.RWMutex // The lock used to protect the block snapshot and state snapshot
|
||||
snapshotBlock *types.Block
|
||||
snapshotState *state.StateDB
|
||||
|
||||
// atomic status counters
|
||||
running int32 // The indicator whether the consensus engine is running or not.
|
||||
newTxs int32 // New arrival transaction count since last sealing work submitting.
|
||||
|
||||
// Test hooks
|
||||
newTaskHook func(*task) // Method to call upon receiving a new sealing task
|
||||
fullTaskInterval func() // Method to call before pushing the full sealing task
|
||||
newTaskHook func(*task) // Method to call upon receiving a new sealing task.
|
||||
skipSealHook func(*task) bool // Method to decide whether skipping the sealing.
|
||||
fullTaskHook func() // Method to call before pushing the full sealing task.
|
||||
resubmitHook func(time.Duration, time.Duration) // Method to call upon updating resubmitting interval.
|
||||
}
|
||||
|
||||
func newWorker(config *params.ChainConfig, engine consensus.Engine, eth Backend, mux *event.TypeMux) *worker {
|
||||
func newWorker(config *params.ChainConfig, engine consensus.Engine, eth Backend, mux *event.TypeMux, recommit time.Duration) *worker {
|
||||
worker := &worker{
|
||||
config: config,
|
||||
engine: engine,
|
||||
eth: eth,
|
||||
mux: mux,
|
||||
chain: eth.BlockChain(),
|
||||
possibleUncles: make(map[common.Hash]*types.Block),
|
||||
unconfirmed: newUnconfirmedBlocks(eth.BlockChain(), miningLogAtDepth),
|
||||
txsCh: make(chan core.NewTxsEvent, txChanSize),
|
||||
chainHeadCh: make(chan core.ChainHeadEvent, chainHeadChanSize),
|
||||
chainSideCh: make(chan core.ChainSideEvent, chainSideChanSize),
|
||||
newWork: make(chan struct{}, 1),
|
||||
taskCh: make(chan *task),
|
||||
resultCh: make(chan *task, resultQueueSize),
|
||||
exitCh: make(chan struct{}),
|
||||
config: config,
|
||||
engine: engine,
|
||||
eth: eth,
|
||||
mux: mux,
|
||||
chain: eth.BlockChain(),
|
||||
possibleUncles: make(map[common.Hash]*types.Block),
|
||||
unconfirmed: newUnconfirmedBlocks(eth.BlockChain(), miningLogAtDepth),
|
||||
pendingTasks: make(map[common.Hash]*task),
|
||||
txsCh: make(chan core.NewTxsEvent, txChanSize),
|
||||
chainHeadCh: make(chan core.ChainHeadEvent, chainHeadChanSize),
|
||||
chainSideCh: make(chan core.ChainSideEvent, chainSideChanSize),
|
||||
newWorkCh: make(chan *newWorkReq),
|
||||
taskCh: make(chan *task),
|
||||
resultCh: make(chan *task, resultQueueSize),
|
||||
exitCh: make(chan struct{}),
|
||||
startCh: make(chan struct{}, 1),
|
||||
resubmitIntervalCh: make(chan time.Duration),
|
||||
resubmitAdjustCh: make(chan *intervalAdjust, resubmitAdjustChanSize),
|
||||
}
|
||||
// Subscribe NewTxsEvent for tx pool
|
||||
worker.txsSub = eth.TxPool().SubscribeNewTxsEvent(worker.txsCh)
|
||||
|
|
@ -240,12 +198,20 @@ func newWorker(config *params.ChainConfig, engine consensus.Engine, eth Backend,
|
|||
worker.chainHeadSub = eth.BlockChain().SubscribeChainHeadEvent(worker.chainHeadCh)
|
||||
worker.chainSideSub = eth.BlockChain().SubscribeChainSideEvent(worker.chainSideCh)
|
||||
|
||||
// Sanitize recommit interval if the user-specified one is too short.
|
||||
if recommit < minRecommitInterval {
|
||||
log.Warn("Sanitizing miner recommit interval", "provided", recommit, "updated", minRecommitInterval)
|
||||
recommit = minRecommitInterval
|
||||
}
|
||||
|
||||
go worker.mainLoop()
|
||||
go worker.newWorkLoop(recommit)
|
||||
go worker.resultLoop()
|
||||
go worker.taskLoop()
|
||||
|
||||
// Submit first work to initialize pending state.
|
||||
worker.newWork <- struct{}{}
|
||||
worker.startCh <- struct{}{}
|
||||
|
||||
return worker
|
||||
}
|
||||
|
||||
|
|
@ -263,6 +229,11 @@ func (w *worker) setExtra(extra []byte) {
|
|||
w.extra = extra
|
||||
}
|
||||
|
||||
// setRecommitInterval updates the interval for miner sealing work recommitting.
|
||||
func (w *worker) setRecommitInterval(interval time.Duration) {
|
||||
w.resubmitIntervalCh <- interval
|
||||
}
|
||||
|
||||
// pending returns the pending state and corresponding block.
|
||||
func (w *worker) pending() (*types.Block, *state.StateDB) {
|
||||
// return a snapshot to avoid contention on currentMu mutex
|
||||
|
|
@ -285,7 +256,7 @@ func (w *worker) pendingBlock() *types.Block {
|
|||
// start sets the running status as 1 and triggers new work submitting.
|
||||
func (w *worker) start() {
|
||||
atomic.StoreInt32(&w.running, 1)
|
||||
w.newWork <- struct{}{}
|
||||
w.startCh <- struct{}{}
|
||||
}
|
||||
|
||||
// stop sets the running status as 0.
|
||||
|
|
@ -312,6 +283,115 @@ func (w *worker) close() {
|
|||
}
|
||||
}
|
||||
|
||||
// newWorkLoop is a standalone goroutine to submit new mining work upon received events.
|
||||
func (w *worker) newWorkLoop(recommit time.Duration) {
|
||||
var (
|
||||
interrupt *int32
|
||||
minRecommit = recommit // minimal resubmit interval specified by user.
|
||||
)
|
||||
|
||||
timer := time.NewTimer(0)
|
||||
<-timer.C // discard the initial tick
|
||||
|
||||
// commit aborts in-flight transaction execution with given signal and resubmits a new one.
|
||||
commit := func(noempty bool, s int32) {
|
||||
if interrupt != nil {
|
||||
atomic.StoreInt32(interrupt, s)
|
||||
}
|
||||
interrupt = new(int32)
|
||||
w.newWorkCh <- &newWorkReq{interrupt: interrupt, noempty: noempty}
|
||||
timer.Reset(recommit)
|
||||
atomic.StoreInt32(&w.newTxs, 0)
|
||||
}
|
||||
// recalcRecommit recalculates the resubmitting interval upon feedback.
|
||||
recalcRecommit := func(target float64, inc bool) {
|
||||
var (
|
||||
prev = float64(recommit.Nanoseconds())
|
||||
next float64
|
||||
)
|
||||
if inc {
|
||||
next = prev*(1-intervalAdjustRatio) + intervalAdjustRatio*(target+intervalAdjustBias)
|
||||
// Recap if interval is larger than the maximum time interval
|
||||
if next > float64(maxRecommitInterval.Nanoseconds()) {
|
||||
next = float64(maxRecommitInterval.Nanoseconds())
|
||||
}
|
||||
} else {
|
||||
next = prev*(1-intervalAdjustRatio) + intervalAdjustRatio*(target-intervalAdjustBias)
|
||||
// Recap if interval is less than the user specified minimum
|
||||
if next < float64(minRecommit.Nanoseconds()) {
|
||||
next = float64(minRecommit.Nanoseconds())
|
||||
}
|
||||
}
|
||||
recommit = time.Duration(int64(next))
|
||||
}
|
||||
// clearPending cleans the stale pending tasks.
|
||||
clearPending := func(number uint64) {
|
||||
w.pendingMu.Lock()
|
||||
for h, t := range w.pendingTasks {
|
||||
if t.block.NumberU64()+staleThreshold <= number {
|
||||
delete(w.pendingTasks, h)
|
||||
}
|
||||
}
|
||||
w.pendingMu.Unlock()
|
||||
}
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-w.startCh:
|
||||
clearPending(w.chain.CurrentBlock().NumberU64())
|
||||
commit(false, commitInterruptNewHead)
|
||||
|
||||
case head := <-w.chainHeadCh:
|
||||
clearPending(head.Block.NumberU64())
|
||||
commit(false, commitInterruptNewHead)
|
||||
|
||||
case <-timer.C:
|
||||
// If mining is running resubmit a new work cycle periodically to pull in
|
||||
// higher priced transactions. Disable this overhead for pending blocks.
|
||||
if w.isRunning() && (w.config.Clique == nil || w.config.Clique.Period > 0) {
|
||||
// Short circuit if no new transaction arrives.
|
||||
if atomic.LoadInt32(&w.newTxs) == 0 {
|
||||
timer.Reset(recommit)
|
||||
continue
|
||||
}
|
||||
commit(true, commitInterruptResubmit)
|
||||
}
|
||||
|
||||
case interval := <-w.resubmitIntervalCh:
|
||||
// Adjust resubmit interval explicitly by user.
|
||||
if interval < minRecommitInterval {
|
||||
log.Warn("Sanitizing miner recommit interval", "provided", interval, "updated", minRecommitInterval)
|
||||
interval = minRecommitInterval
|
||||
}
|
||||
log.Info("Miner recommit interval update", "from", minRecommit, "to", interval)
|
||||
minRecommit, recommit = interval, interval
|
||||
|
||||
if w.resubmitHook != nil {
|
||||
w.resubmitHook(minRecommit, recommit)
|
||||
}
|
||||
|
||||
case adjust := <-w.resubmitAdjustCh:
|
||||
// Adjust resubmit interval by feedback.
|
||||
if adjust.inc {
|
||||
before := recommit
|
||||
recalcRecommit(float64(recommit.Nanoseconds())/adjust.ratio, true)
|
||||
log.Trace("Increase miner recommit interval", "from", before, "to", recommit)
|
||||
} else {
|
||||
before := recommit
|
||||
recalcRecommit(float64(minRecommit.Nanoseconds()), false)
|
||||
log.Trace("Decrease miner recommit interval", "from", before, "to", recommit)
|
||||
}
|
||||
|
||||
if w.resubmitHook != nil {
|
||||
w.resubmitHook(minRecommit, recommit)
|
||||
}
|
||||
|
||||
case <-w.exitCh:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// mainLoop is a standalone goroutine to regenerate the sealing task based on the received event.
|
||||
func (w *worker) mainLoop() {
|
||||
defer w.txsSub.Unsubscribe()
|
||||
|
|
@ -320,17 +400,36 @@ func (w *worker) mainLoop() {
|
|||
|
||||
for {
|
||||
select {
|
||||
case <-w.newWork:
|
||||
// Submit a work when the worker is created or started.
|
||||
w.commitNewWork()
|
||||
|
||||
case <-w.chainHeadCh:
|
||||
// Resubmit a work for new cycle once worker receives chain head event.
|
||||
w.commitNewWork()
|
||||
case req := <-w.newWorkCh:
|
||||
w.commitNewWork(req.interrupt, req.noempty)
|
||||
|
||||
case ev := <-w.chainSideCh:
|
||||
if _, exist := w.possibleUncles[ev.Block.Hash()]; exist {
|
||||
continue
|
||||
}
|
||||
// Add side block to possible uncle block set.
|
||||
w.possibleUncles[ev.Block.Hash()] = ev.Block
|
||||
// If our mining block contains less than 2 uncle blocks,
|
||||
// add the new uncle block if valid and regenerate a mining block.
|
||||
if w.isRunning() && w.current != nil && w.current.uncles.Cardinality() < 2 {
|
||||
start := time.Now()
|
||||
if err := w.commitUncle(w.current, ev.Block.Header()); err == nil {
|
||||
var uncles []*types.Header
|
||||
w.current.uncles.Each(func(item interface{}) bool {
|
||||
hash, ok := item.(common.Hash)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
uncle, exist := w.possibleUncles[hash]
|
||||
if !exist {
|
||||
return false
|
||||
}
|
||||
uncles = append(uncles, uncle.Header())
|
||||
return false
|
||||
})
|
||||
w.commit(uncles, nil, true, start)
|
||||
}
|
||||
}
|
||||
|
||||
case ev := <-w.txsCh:
|
||||
// Apply transactions to the pending state if we're not mining.
|
||||
|
|
@ -339,9 +438,9 @@ func (w *worker) mainLoop() {
|
|||
// already included in the current mining block. These transactions will
|
||||
// be automatically eliminated.
|
||||
if !w.isRunning() && w.current != nil {
|
||||
w.mu.Lock()
|
||||
w.mu.RLock()
|
||||
coinbase := w.coinbase
|
||||
w.mu.Unlock()
|
||||
w.mu.RUnlock()
|
||||
|
||||
txs := make(map[common.Address]types.Transactions)
|
||||
for _, tx := range ev.Txs {
|
||||
|
|
@ -349,14 +448,15 @@ func (w *worker) mainLoop() {
|
|||
txs[acc] = append(txs[acc], tx)
|
||||
}
|
||||
txset := types.NewTransactionsByPriceAndNonce(w.current.signer, txs)
|
||||
w.current.commitTransactions(w.mux, txset, w.chain, coinbase)
|
||||
w.commitTransactions(txset, coinbase, nil)
|
||||
w.updateSnapshot()
|
||||
} else {
|
||||
// If we're mining, but nothing is being processed, wake on new transactions
|
||||
if w.config.Clique != nil && w.config.Clique.Period == 0 {
|
||||
w.commitNewWork()
|
||||
w.commitNewWork(nil, false)
|
||||
}
|
||||
}
|
||||
atomic.AddInt32(&w.newTxs, int32(len(ev.Txs)))
|
||||
|
||||
// System stopped
|
||||
case <-w.exitCh:
|
||||
|
|
@ -373,31 +473,47 @@ func (w *worker) mainLoop() {
|
|||
|
||||
// seal pushes a sealing task to consensus engine and submits the result.
|
||||
func (w *worker) seal(t *task, stop <-chan struct{}) {
|
||||
var (
|
||||
err error
|
||||
res *task
|
||||
)
|
||||
|
||||
if t.block, err = w.engine.Seal(w.chain, t.block, stop); t.block != nil {
|
||||
log.Info("Successfully sealed new block", "number", t.block.Number(), "hash", t.block.Hash(),
|
||||
"elapsed", common.PrettyDuration(time.Since(t.createdAt)))
|
||||
res = t
|
||||
} else {
|
||||
if err != nil {
|
||||
log.Warn("Block sealing failed", "err", err)
|
||||
}
|
||||
res = nil
|
||||
if w.skipSealHook != nil && w.skipSealHook(t) {
|
||||
return
|
||||
}
|
||||
select {
|
||||
case w.resultCh <- res:
|
||||
case <-w.exitCh:
|
||||
// The reason for caching task first is:
|
||||
// A previous sealing action will be canceled by subsequent actions,
|
||||
// however, remote miner may submit a result based on the cancelled task.
|
||||
// So we should only submit the pending state corresponding to the seal result.
|
||||
// TODO(rjl493456442) Replace the seal-wait logic structure
|
||||
w.pendingMu.Lock()
|
||||
w.pendingTasks[w.engine.SealHash(t.block.Header())] = t
|
||||
w.pendingMu.Unlock()
|
||||
|
||||
if block, err := w.engine.Seal(w.chain, t.block, stop); block != nil {
|
||||
sealhash := w.engine.SealHash(block.Header())
|
||||
w.pendingMu.RLock()
|
||||
task, exist := w.pendingTasks[sealhash]
|
||||
w.pendingMu.RUnlock()
|
||||
if !exist {
|
||||
log.Error("Block found but no relative pending task", "number", block.Number(), "sealhash", sealhash, "hash", block.Hash())
|
||||
return
|
||||
}
|
||||
// Assemble sealing result
|
||||
task.block = block
|
||||
log.Info("Successfully sealed new block", "number", block.Number(), "sealhash", sealhash, "hash", block.Hash(),
|
||||
"elapsed", common.PrettyDuration(time.Since(task.createdAt)))
|
||||
select {
|
||||
case w.resultCh <- task:
|
||||
case <-w.exitCh:
|
||||
}
|
||||
} else if err != nil {
|
||||
log.Warn("Block sealing failed", "err", err)
|
||||
}
|
||||
}
|
||||
|
||||
// taskLoop is a standalone goroutine to fetch sealing task from the generator and
|
||||
// push them to consensus engine.
|
||||
func (w *worker) taskLoop() {
|
||||
var stopCh chan struct{}
|
||||
var (
|
||||
stopCh chan struct{}
|
||||
prev common.Hash
|
||||
)
|
||||
|
||||
// interrupt aborts the in-flight sealing task.
|
||||
interrupt := func() {
|
||||
|
|
@ -412,8 +528,14 @@ func (w *worker) taskLoop() {
|
|||
if w.newTaskHook != nil {
|
||||
w.newTaskHook(task)
|
||||
}
|
||||
// Reject duplicate sealing work due to resubmitting.
|
||||
sealHash := w.engine.SealHash(task.block.Header())
|
||||
if sealHash == prev {
|
||||
continue
|
||||
}
|
||||
interrupt()
|
||||
stopCh = make(chan struct{})
|
||||
prev = sealHash
|
||||
go w.seal(task, stopCh)
|
||||
case <-w.exitCh:
|
||||
interrupt()
|
||||
|
|
@ -428,11 +550,15 @@ func (w *worker) resultLoop() {
|
|||
for {
|
||||
select {
|
||||
case result := <-w.resultCh:
|
||||
// Short circuit when receiving empty result.
|
||||
if result == nil {
|
||||
continue
|
||||
}
|
||||
// Short circuit when receiving duplicate result caused by resubmitting.
|
||||
block := result.block
|
||||
|
||||
if w.chain.HasBlock(block.Hash(), block.NumberU64()) {
|
||||
continue
|
||||
}
|
||||
// Update the block hash in all logs since it is now available and not when the
|
||||
// receipt/log of individual transactions were created.
|
||||
for _, r := range result.receipts {
|
||||
|
|
@ -479,8 +605,7 @@ func (w *worker) makeCurrent(parent *types.Block, header *types.Header) error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
env := &Env{
|
||||
config: w.config,
|
||||
env := &environment{
|
||||
signer: types.NewEIP155Signer(w.config.ChainID),
|
||||
state: state,
|
||||
ancestors: mapset.NewSet(),
|
||||
|
|
@ -505,7 +630,7 @@ func (w *worker) makeCurrent(parent *types.Block, header *types.Header) error {
|
|||
}
|
||||
|
||||
// commitUncle adds the given block to uncle block set, returns error if failed to add.
|
||||
func (w *worker) commitUncle(env *Env, uncle *types.Header) error {
|
||||
func (w *worker) commitUncle(env *environment, uncle *types.Header) error {
|
||||
hash := uncle.Hash()
|
||||
if env.uncles.Contains(hash) {
|
||||
return fmt.Errorf("uncle not unique")
|
||||
|
|
@ -537,7 +662,7 @@ func (w *worker) updateSnapshot() {
|
|||
return false
|
||||
}
|
||||
uncles = append(uncles, uncle.Header())
|
||||
return true
|
||||
return false
|
||||
})
|
||||
|
||||
w.snapshotBlock = types.NewBlock(
|
||||
|
|
@ -550,8 +675,135 @@ func (w *worker) updateSnapshot() {
|
|||
w.snapshotState = w.current.state.Copy()
|
||||
}
|
||||
|
||||
func (w *worker) commitTransaction(tx *types.Transaction, coinbase common.Address) ([]*types.Log, error) {
|
||||
snap := w.current.state.Snapshot()
|
||||
|
||||
receipt, _, err := core.ApplyTransaction(w.config, w.chain, &coinbase, w.current.gasPool, w.current.state, w.current.header, tx, &w.current.header.GasUsed, vm.Config{})
|
||||
if err != nil {
|
||||
w.current.state.RevertToSnapshot(snap)
|
||||
return nil, err
|
||||
}
|
||||
w.current.txs = append(w.current.txs, tx)
|
||||
w.current.receipts = append(w.current.receipts, receipt)
|
||||
|
||||
return receipt.Logs, nil
|
||||
}
|
||||
|
||||
func (w *worker) commitTransactions(txs *types.TransactionsByPriceAndNonce, coinbase common.Address, interrupt *int32) bool {
|
||||
// Short circuit if current is nil
|
||||
if w.current == nil {
|
||||
return true
|
||||
}
|
||||
|
||||
if w.current.gasPool == nil {
|
||||
w.current.gasPool = new(core.GasPool).AddGas(w.current.header.GasLimit)
|
||||
}
|
||||
|
||||
var coalescedLogs []*types.Log
|
||||
|
||||
for {
|
||||
// In the following three cases, we will interrupt the execution of the transaction.
|
||||
// (1) new head block event arrival, the interrupt signal is 1
|
||||
// (2) worker start or restart, the interrupt signal is 1
|
||||
// (3) worker recreate the mining block with any newly arrived transactions, the interrupt signal is 2.
|
||||
// For the first two cases, the semi-finished work will be discarded.
|
||||
// For the third case, the semi-finished work will be submitted to the consensus engine.
|
||||
if interrupt != nil && atomic.LoadInt32(interrupt) != commitInterruptNone {
|
||||
// Notify resubmit loop to increase resubmitting interval due to too frequent commits.
|
||||
if atomic.LoadInt32(interrupt) == commitInterruptResubmit {
|
||||
ratio := float64(w.current.header.GasLimit-w.current.gasPool.Gas()) / float64(w.current.header.GasLimit)
|
||||
if ratio < 0.1 {
|
||||
ratio = 0.1
|
||||
}
|
||||
w.resubmitAdjustCh <- &intervalAdjust{
|
||||
ratio: ratio,
|
||||
inc: true,
|
||||
}
|
||||
}
|
||||
return atomic.LoadInt32(interrupt) == commitInterruptNewHead
|
||||
}
|
||||
// If we don't have enough gas for any further transactions then we're done
|
||||
if w.current.gasPool.Gas() < params.TxGas {
|
||||
log.Trace("Not enough gas for further transactions", "have", w.current.gasPool, "want", params.TxGas)
|
||||
break
|
||||
}
|
||||
// Retrieve the next transaction and abort if all done
|
||||
tx := txs.Peek()
|
||||
if tx == nil {
|
||||
break
|
||||
}
|
||||
// Error may be ignored here. The error has already been checked
|
||||
// during transaction acceptance is the transaction pool.
|
||||
//
|
||||
// We use the eip155 signer regardless of the current hf.
|
||||
from, _ := types.Sender(w.current.signer, tx)
|
||||
// Check whether the tx is replay protected. If we're not in the EIP155 hf
|
||||
// phase, start ignoring the sender until we do.
|
||||
if tx.Protected() && !w.config.IsEIP155(w.current.header.Number) {
|
||||
log.Trace("Ignoring reply protected transaction", "hash", tx.Hash(), "eip155", w.config.EIP155Block)
|
||||
|
||||
txs.Pop()
|
||||
continue
|
||||
}
|
||||
// Start executing the transaction
|
||||
w.current.state.Prepare(tx.Hash(), common.Hash{}, w.current.tcount)
|
||||
|
||||
logs, err := w.commitTransaction(tx, coinbase)
|
||||
switch err {
|
||||
case core.ErrGasLimitReached:
|
||||
// Pop the current out-of-gas transaction without shifting in the next from the account
|
||||
log.Trace("Gas limit exceeded for current block", "sender", from)
|
||||
txs.Pop()
|
||||
|
||||
case core.ErrNonceTooLow:
|
||||
// New head notification data race between the transaction pool and miner, shift
|
||||
log.Trace("Skipping transaction with low nonce", "sender", from, "nonce", tx.Nonce())
|
||||
txs.Shift()
|
||||
|
||||
case core.ErrNonceTooHigh:
|
||||
// Reorg notification data race between the transaction pool and miner, skip account =
|
||||
log.Trace("Skipping account with hight nonce", "sender", from, "nonce", tx.Nonce())
|
||||
txs.Pop()
|
||||
|
||||
case nil:
|
||||
// Everything ok, collect the logs and shift in the next transaction from the same account
|
||||
coalescedLogs = append(coalescedLogs, logs...)
|
||||
w.current.tcount++
|
||||
txs.Shift()
|
||||
|
||||
default:
|
||||
// Strange error, discard the transaction and get the next in line (note, the
|
||||
// nonce-too-high clause will prevent us from executing in vain).
|
||||
log.Debug("Transaction failed, account skipped", "hash", tx.Hash(), "err", err)
|
||||
txs.Shift()
|
||||
}
|
||||
}
|
||||
|
||||
if !w.isRunning() && len(coalescedLogs) > 0 {
|
||||
// We don't push the pendingLogsEvent while we are mining. The reason is that
|
||||
// when we are mining, the worker will regenerate a mining block every 3 seconds.
|
||||
// In order to avoid pushing the repeated pendingLog, we disable the pending log pushing.
|
||||
|
||||
// make a copy, the state caches the logs and these logs get "upgraded" from pending to mined
|
||||
// logs by filling in the block hash when the block was mined by the local miner. This can
|
||||
// cause a race condition if a log was "upgraded" before the PendingLogsEvent is processed.
|
||||
cpy := make([]*types.Log, len(coalescedLogs))
|
||||
for i, l := range coalescedLogs {
|
||||
cpy[i] = new(types.Log)
|
||||
*cpy[i] = *l
|
||||
}
|
||||
go w.mux.Post(core.PendingLogsEvent{Logs: cpy})
|
||||
}
|
||||
// Notify resubmit loop to decrease resubmitting interval if current interval is larger
|
||||
// than the user-specified one.
|
||||
if interrupt != nil {
|
||||
w.resubmitAdjustCh <- &intervalAdjust{inc: false}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// commitNewWork generates several new sealing tasks based on the parent block.
|
||||
func (w *worker) commitNewWork() {
|
||||
func (w *worker) commitNewWork(interrupt *int32, noempty bool) {
|
||||
w.mu.RLock()
|
||||
defer w.mu.RUnlock()
|
||||
|
||||
|
|
@ -637,29 +889,10 @@ func (w *worker) commitNewWork() {
|
|||
delete(w.possibleUncles, hash)
|
||||
}
|
||||
|
||||
var (
|
||||
emptyBlock, fullBlock *types.Block
|
||||
emptyState, fullState *state.StateDB
|
||||
)
|
||||
|
||||
// Create an empty block based on temporary copied state for sealing in advance without waiting block
|
||||
// execution finished.
|
||||
emptyState = env.state.Copy()
|
||||
if emptyBlock, err = w.engine.Finalize(w.chain, header, emptyState, nil, uncles, nil); err != nil {
|
||||
log.Error("Failed to finalize block for temporary sealing", "err", err)
|
||||
} else {
|
||||
// Push empty work in advance without applying pending transaction.
|
||||
// The reason is transactions execution can cost a lot and sealer need to
|
||||
// take advantage of this part time.
|
||||
if w.isRunning() {
|
||||
select {
|
||||
case w.taskCh <- &task{receipts: nil, state: emptyState, block: emptyBlock, createdAt: time.Now()}:
|
||||
log.Info("Commit new empty mining work", "number", emptyBlock.Number(), "uncles", len(uncles))
|
||||
case <-w.exitCh:
|
||||
log.Info("Worker has exited")
|
||||
return
|
||||
}
|
||||
}
|
||||
if !noempty {
|
||||
// Create an empty block based on temporary copied state for sealing in advance without waiting block
|
||||
// execution finished.
|
||||
w.commit(uncles, nil, false, tstart)
|
||||
}
|
||||
|
||||
// Fill the block with all available pending transactions.
|
||||
|
|
@ -673,34 +906,66 @@ func (w *worker) commitNewWork() {
|
|||
w.updateSnapshot()
|
||||
return
|
||||
}
|
||||
txs := types.NewTransactionsByPriceAndNonce(w.current.signer, pending)
|
||||
env.commitTransactions(w.mux, txs, w.chain, w.coinbase)
|
||||
|
||||
// Create the full block to seal with the consensus engine
|
||||
fullState = env.state.Copy()
|
||||
if fullBlock, err = w.engine.Finalize(w.chain, header, fullState, env.txs, uncles, env.receipts); err != nil {
|
||||
log.Error("Failed to finalize block for sealing", "err", err)
|
||||
return
|
||||
}
|
||||
// Deep copy receipts here to avoid interaction between different tasks.
|
||||
cpy := make([]*types.Receipt, len(env.receipts))
|
||||
for i, l := range env.receipts {
|
||||
cpy[i] = new(types.Receipt)
|
||||
*cpy[i] = *l
|
||||
}
|
||||
// We only care about logging if we're actually mining.
|
||||
if w.isRunning() {
|
||||
if w.fullTaskInterval != nil {
|
||||
w.fullTaskInterval()
|
||||
// Split the pending transactions into locals and remotes
|
||||
localTxs, remoteTxs := make(map[common.Address]types.Transactions), pending
|
||||
for _, account := range w.eth.TxPool().Locals() {
|
||||
if txs := remoteTxs[account]; len(txs) > 0 {
|
||||
delete(remoteTxs, account)
|
||||
localTxs[account] = txs
|
||||
}
|
||||
}
|
||||
if len(localTxs) > 0 {
|
||||
txs := types.NewTransactionsByPriceAndNonce(w.current.signer, localTxs)
|
||||
if w.commitTransactions(txs, w.coinbase, interrupt) {
|
||||
return
|
||||
}
|
||||
}
|
||||
if len(remoteTxs) > 0 {
|
||||
txs := types.NewTransactionsByPriceAndNonce(w.current.signer, remoteTxs)
|
||||
if w.commitTransactions(txs, w.coinbase, interrupt) {
|
||||
return
|
||||
}
|
||||
}
|
||||
w.commit(uncles, w.fullTaskHook, true, tstart)
|
||||
}
|
||||
|
||||
// commit runs any post-transaction state modifications, assembles the final block
|
||||
// and commits new work if consensus engine is running.
|
||||
func (w *worker) commit(uncles []*types.Header, interval func(), update bool, start time.Time) error {
|
||||
// Deep copy receipts here to avoid interaction between different tasks.
|
||||
receipts := make([]*types.Receipt, len(w.current.receipts))
|
||||
for i, l := range w.current.receipts {
|
||||
receipts[i] = new(types.Receipt)
|
||||
*receipts[i] = *l
|
||||
}
|
||||
s := w.current.state.Copy()
|
||||
block, err := w.engine.Finalize(w.chain, w.current.header, s, w.current.txs, uncles, w.current.receipts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if w.isRunning() {
|
||||
if interval != nil {
|
||||
interval()
|
||||
}
|
||||
select {
|
||||
case w.taskCh <- &task{receipts: cpy, state: fullState, block: fullBlock, createdAt: time.Now()}:
|
||||
w.unconfirmed.Shift(fullBlock.NumberU64() - 1)
|
||||
log.Info("Commit new full mining work", "number", fullBlock.Number(), "txs", env.tcount, "uncles", len(uncles), "elapsed", common.PrettyDuration(time.Since(tstart)))
|
||||
case w.taskCh <- &task{receipts: receipts, state: s, block: block, createdAt: time.Now()}:
|
||||
w.unconfirmed.Shift(block.NumberU64() - 1)
|
||||
|
||||
feesWei := new(big.Int)
|
||||
for i, tx := range block.Transactions() {
|
||||
feesWei.Add(feesWei, new(big.Int).Mul(new(big.Int).SetUint64(receipts[i].GasUsed), tx.GasPrice()))
|
||||
}
|
||||
feesEth := new(big.Float).Quo(new(big.Float).SetInt(feesWei), new(big.Float).SetInt(big.NewInt(params.Ether)))
|
||||
|
||||
log.Info("Commit new mining work", "number", block.Number(), "sealhash", w.engine.SealHash(block.Header()),
|
||||
"uncles", len(uncles), "txs", w.current.tcount, "gas", block.GasUsed(), "fees", feesEth, "elapsed", common.PrettyDuration(time.Since(start)))
|
||||
|
||||
case <-w.exitCh:
|
||||
log.Info("Worker has exited")
|
||||
}
|
||||
}
|
||||
w.updateSnapshot()
|
||||
if update {
|
||||
w.updateSnapshot()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ func init() {
|
|||
ethashChainConfig = params.TestChainConfig
|
||||
cliqueChainConfig = params.TestChainConfig
|
||||
cliqueChainConfig.Clique = ¶ms.CliqueConfig{
|
||||
Period: 1,
|
||||
Period: 10,
|
||||
Epoch: 30000,
|
||||
}
|
||||
tx1, _ := types.SignTx(types.NewTransaction(0, acc1Addr, big.NewInt(1000), params.TxGas, nil, nil), types.HomesteadSigner{}, testBankKey)
|
||||
|
|
@ -74,6 +74,7 @@ type testWorkerBackend struct {
|
|||
txPool *core.TxPool
|
||||
chain *core.BlockChain
|
||||
testTxFeed event.Feed
|
||||
uncleBlock *types.Block
|
||||
}
|
||||
|
||||
func newTestWorkerBackend(t *testing.T, chainConfig *params.ChainConfig, engine consensus.Engine) *testWorkerBackend {
|
||||
|
|
@ -93,15 +94,19 @@ func newTestWorkerBackend(t *testing.T, chainConfig *params.ChainConfig, engine
|
|||
default:
|
||||
t.Fatal("unexpect consensus engine type")
|
||||
}
|
||||
gspec.MustCommit(db)
|
||||
genesis := gspec.MustCommit(db)
|
||||
|
||||
chain, _ := core.NewBlockChain(db, nil, gspec.Config, engine, vm.Config{})
|
||||
txpool := core.NewTxPool(testTxPoolConfig, chainConfig, chain)
|
||||
blocks, _ := core.GenerateChain(chainConfig, genesis, engine, db, 1, func(i int, gen *core.BlockGen) {
|
||||
gen.SetCoinbase(acc1Addr)
|
||||
})
|
||||
|
||||
return &testWorkerBackend{
|
||||
db: db,
|
||||
chain: chain,
|
||||
txPool: txpool,
|
||||
db: db,
|
||||
chain: chain,
|
||||
txPool: txpool,
|
||||
uncleBlock: blocks[0],
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -114,7 +119,7 @@ func (b *testWorkerBackend) PostChainEvents(events []interface{}) {
|
|||
func newTestWorker(t *testing.T, chainConfig *params.ChainConfig, engine consensus.Engine) (*worker, *testWorkerBackend) {
|
||||
backend := newTestWorkerBackend(t, chainConfig, engine)
|
||||
backend.txPool.AddLocals(pendingTxs)
|
||||
w := newWorker(chainConfig, engine, backend, new(event.TypeMux))
|
||||
w := newWorker(chainConfig, engine, backend, new(event.TypeMux), time.Second)
|
||||
w.setEtherbase(testBankAddress)
|
||||
return w, backend
|
||||
}
|
||||
|
|
@ -188,7 +193,7 @@ func testEmptyWork(t *testing.T, chainConfig *params.ChainConfig, engine consens
|
|||
taskCh <- struct{}{}
|
||||
}
|
||||
}
|
||||
w.fullTaskInterval = func() {
|
||||
w.fullTaskHook = func() {
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
}
|
||||
|
||||
|
|
@ -202,11 +207,233 @@ func testEmptyWork(t *testing.T, chainConfig *params.ChainConfig, engine consens
|
|||
|
||||
w.start()
|
||||
for i := 0; i < 2; i += 1 {
|
||||
to := time.NewTimer(time.Second)
|
||||
select {
|
||||
case <-taskCh:
|
||||
case <-to.C:
|
||||
case <-time.NewTimer(time.Second).C:
|
||||
t.Error("new task timeout")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestStreamUncleBlock(t *testing.T) {
|
||||
ethash := ethash.NewFaker()
|
||||
defer ethash.Close()
|
||||
|
||||
w, b := newTestWorker(t, ethashChainConfig, ethash)
|
||||
defer w.close()
|
||||
|
||||
var taskCh = make(chan struct{})
|
||||
|
||||
taskIndex := 0
|
||||
w.newTaskHook = func(task *task) {
|
||||
if task.block.NumberU64() == 1 {
|
||||
if taskIndex == 2 {
|
||||
has := task.block.Header().UncleHash
|
||||
want := types.CalcUncleHash([]*types.Header{b.uncleBlock.Header()})
|
||||
if has != want {
|
||||
t.Errorf("uncle hash mismatch, has %s, want %s", has.Hex(), want.Hex())
|
||||
}
|
||||
}
|
||||
taskCh <- struct{}{}
|
||||
taskIndex += 1
|
||||
}
|
||||
}
|
||||
w.skipSealHook = func(task *task) bool {
|
||||
return true
|
||||
}
|
||||
w.fullTaskHook = func() {
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
}
|
||||
|
||||
// Ensure worker has finished initialization
|
||||
for {
|
||||
b := w.pendingBlock()
|
||||
if b != nil && b.NumberU64() == 1 {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
w.start()
|
||||
// Ignore the first two works
|
||||
for i := 0; i < 2; i += 1 {
|
||||
select {
|
||||
case <-taskCh:
|
||||
case <-time.NewTimer(time.Second).C:
|
||||
t.Error("new task timeout")
|
||||
}
|
||||
}
|
||||
b.PostChainEvents([]interface{}{core.ChainSideEvent{Block: b.uncleBlock}})
|
||||
|
||||
select {
|
||||
case <-taskCh:
|
||||
case <-time.NewTimer(time.Second).C:
|
||||
t.Error("new task timeout")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRegenerateMiningBlockEthash(t *testing.T) {
|
||||
testRegenerateMiningBlock(t, ethashChainConfig, ethash.NewFaker())
|
||||
}
|
||||
|
||||
func TestRegenerateMiningBlockClique(t *testing.T) {
|
||||
testRegenerateMiningBlock(t, cliqueChainConfig, clique.New(cliqueChainConfig.Clique, ethdb.NewMemDatabase()))
|
||||
}
|
||||
|
||||
func testRegenerateMiningBlock(t *testing.T, chainConfig *params.ChainConfig, engine consensus.Engine) {
|
||||
defer engine.Close()
|
||||
|
||||
w, b := newTestWorker(t, chainConfig, engine)
|
||||
defer w.close()
|
||||
|
||||
var taskCh = make(chan struct{})
|
||||
|
||||
taskIndex := 0
|
||||
w.newTaskHook = func(task *task) {
|
||||
if task.block.NumberU64() == 1 {
|
||||
if taskIndex == 2 {
|
||||
receiptLen, balance := 2, big.NewInt(2000)
|
||||
if len(task.receipts) != receiptLen {
|
||||
t.Errorf("receipt number mismatch has %d, want %d", len(task.receipts), receiptLen)
|
||||
}
|
||||
if task.state.GetBalance(acc1Addr).Cmp(balance) != 0 {
|
||||
t.Errorf("account balance mismatch has %d, want %d", task.state.GetBalance(acc1Addr), balance)
|
||||
}
|
||||
}
|
||||
taskCh <- struct{}{}
|
||||
taskIndex += 1
|
||||
}
|
||||
}
|
||||
w.skipSealHook = func(task *task) bool {
|
||||
return true
|
||||
}
|
||||
w.fullTaskHook = func() {
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
}
|
||||
// Ensure worker has finished initialization
|
||||
for {
|
||||
b := w.pendingBlock()
|
||||
if b != nil && b.NumberU64() == 1 {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
w.start()
|
||||
// Ignore the first two works
|
||||
for i := 0; i < 2; i += 1 {
|
||||
select {
|
||||
case <-taskCh:
|
||||
case <-time.NewTimer(time.Second).C:
|
||||
t.Error("new task timeout")
|
||||
}
|
||||
}
|
||||
b.txPool.AddLocals(newTxs)
|
||||
time.Sleep(time.Second)
|
||||
|
||||
select {
|
||||
case <-taskCh:
|
||||
case <-time.NewTimer(time.Second).C:
|
||||
t.Error("new task timeout")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAdjustIntervalEthash(t *testing.T) {
|
||||
testAdjustInterval(t, ethashChainConfig, ethash.NewFaker())
|
||||
}
|
||||
|
||||
func TestAdjustIntervalClique(t *testing.T) {
|
||||
testAdjustInterval(t, cliqueChainConfig, clique.New(cliqueChainConfig.Clique, ethdb.NewMemDatabase()))
|
||||
}
|
||||
|
||||
func testAdjustInterval(t *testing.T, chainConfig *params.ChainConfig, engine consensus.Engine) {
|
||||
defer engine.Close()
|
||||
|
||||
w, _ := newTestWorker(t, chainConfig, engine)
|
||||
defer w.close()
|
||||
|
||||
w.skipSealHook = func(task *task) bool {
|
||||
return true
|
||||
}
|
||||
w.fullTaskHook = func() {
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
}
|
||||
var (
|
||||
progress = make(chan struct{}, 10)
|
||||
result = make([]float64, 0, 10)
|
||||
index = 0
|
||||
start = false
|
||||
)
|
||||
w.resubmitHook = func(minInterval time.Duration, recommitInterval time.Duration) {
|
||||
// Short circuit if interval checking hasn't started.
|
||||
if !start {
|
||||
return
|
||||
}
|
||||
var wantMinInterval, wantRecommitInterval time.Duration
|
||||
|
||||
switch index {
|
||||
case 0:
|
||||
wantMinInterval, wantRecommitInterval = 3*time.Second, 3*time.Second
|
||||
case 1:
|
||||
origin := float64(3 * time.Second.Nanoseconds())
|
||||
estimate := origin*(1-intervalAdjustRatio) + intervalAdjustRatio*(origin/0.8+intervalAdjustBias)
|
||||
wantMinInterval, wantRecommitInterval = 3*time.Second, time.Duration(int(estimate))*time.Nanosecond
|
||||
case 2:
|
||||
estimate := result[index-1]
|
||||
min := float64(3 * time.Second.Nanoseconds())
|
||||
estimate = estimate*(1-intervalAdjustRatio) + intervalAdjustRatio*(min-intervalAdjustBias)
|
||||
wantMinInterval, wantRecommitInterval = 3*time.Second, time.Duration(int(estimate))*time.Nanosecond
|
||||
case 3:
|
||||
wantMinInterval, wantRecommitInterval = time.Second, time.Second
|
||||
}
|
||||
|
||||
// Check interval
|
||||
if minInterval != wantMinInterval {
|
||||
t.Errorf("resubmit min interval mismatch want %s has %s", wantMinInterval, minInterval)
|
||||
}
|
||||
if recommitInterval != wantRecommitInterval {
|
||||
t.Errorf("resubmit interval mismatch want %s has %s", wantRecommitInterval, recommitInterval)
|
||||
}
|
||||
result = append(result, float64(recommitInterval.Nanoseconds()))
|
||||
index += 1
|
||||
progress <- struct{}{}
|
||||
}
|
||||
// Ensure worker has finished initialization
|
||||
for {
|
||||
b := w.pendingBlock()
|
||||
if b != nil && b.NumberU64() == 1 {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
w.start()
|
||||
|
||||
time.Sleep(time.Second)
|
||||
|
||||
start = true
|
||||
w.setRecommitInterval(3 * time.Second)
|
||||
select {
|
||||
case <-progress:
|
||||
case <-time.NewTimer(time.Second).C:
|
||||
t.Error("interval reset timeout")
|
||||
}
|
||||
|
||||
w.resubmitAdjustCh <- &intervalAdjust{inc: true, ratio: 0.8}
|
||||
select {
|
||||
case <-progress:
|
||||
case <-time.NewTimer(time.Second).C:
|
||||
t.Error("interval reset timeout")
|
||||
}
|
||||
|
||||
w.resubmitAdjustCh <- &intervalAdjust{inc: false}
|
||||
select {
|
||||
case <-progress:
|
||||
case <-time.NewTimer(time.Second).C:
|
||||
t.Error("interval reset timeout")
|
||||
}
|
||||
|
||||
w.setRecommitInterval(500 * time.Millisecond)
|
||||
select {
|
||||
case <-progress:
|
||||
case <-time.NewTimer(time.Second).C:
|
||||
t.Error("interval reset timeout")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -168,25 +168,22 @@ func (b *Block) EncodeJSON() (string, error) {
|
|||
return string(data), err
|
||||
}
|
||||
|
||||
func (b *Block) GetParentHash() *Hash { return &Hash{b.block.ParentHash()} }
|
||||
func (b *Block) GetUncleHash() *Hash { return &Hash{b.block.UncleHash()} }
|
||||
func (b *Block) GetCoinbase() *Address { return &Address{b.block.Coinbase()} }
|
||||
func (b *Block) GetRoot() *Hash { return &Hash{b.block.Root()} }
|
||||
func (b *Block) GetTxHash() *Hash { return &Hash{b.block.TxHash()} }
|
||||
func (b *Block) GetReceiptHash() *Hash { return &Hash{b.block.ReceiptHash()} }
|
||||
func (b *Block) GetBloom() *Bloom { return &Bloom{b.block.Bloom()} }
|
||||
func (b *Block) GetDifficulty() *BigInt { return &BigInt{b.block.Difficulty()} }
|
||||
func (b *Block) GetNumber() int64 { return b.block.Number().Int64() }
|
||||
func (b *Block) GetGasLimit() int64 { return int64(b.block.GasLimit()) }
|
||||
func (b *Block) GetGasUsed() int64 { return int64(b.block.GasUsed()) }
|
||||
func (b *Block) GetTime() int64 { return b.block.Time().Int64() }
|
||||
func (b *Block) GetExtra() []byte { return b.block.Extra() }
|
||||
func (b *Block) GetMixDigest() *Hash { return &Hash{b.block.MixDigest()} }
|
||||
func (b *Block) GetNonce() int64 { return int64(b.block.Nonce()) }
|
||||
|
||||
func (b *Block) GetHash() *Hash { return &Hash{b.block.Hash()} }
|
||||
func (b *Block) GetHashNoNonce() *Hash { return &Hash{b.block.HashNoNonce()} }
|
||||
|
||||
func (b *Block) GetParentHash() *Hash { return &Hash{b.block.ParentHash()} }
|
||||
func (b *Block) GetUncleHash() *Hash { return &Hash{b.block.UncleHash()} }
|
||||
func (b *Block) GetCoinbase() *Address { return &Address{b.block.Coinbase()} }
|
||||
func (b *Block) GetRoot() *Hash { return &Hash{b.block.Root()} }
|
||||
func (b *Block) GetTxHash() *Hash { return &Hash{b.block.TxHash()} }
|
||||
func (b *Block) GetReceiptHash() *Hash { return &Hash{b.block.ReceiptHash()} }
|
||||
func (b *Block) GetBloom() *Bloom { return &Bloom{b.block.Bloom()} }
|
||||
func (b *Block) GetDifficulty() *BigInt { return &BigInt{b.block.Difficulty()} }
|
||||
func (b *Block) GetNumber() int64 { return b.block.Number().Int64() }
|
||||
func (b *Block) GetGasLimit() int64 { return int64(b.block.GasLimit()) }
|
||||
func (b *Block) GetGasUsed() int64 { return int64(b.block.GasUsed()) }
|
||||
func (b *Block) GetTime() int64 { return b.block.Time().Int64() }
|
||||
func (b *Block) GetExtra() []byte { return b.block.Extra() }
|
||||
func (b *Block) GetMixDigest() *Hash { return &Hash{b.block.MixDigest()} }
|
||||
func (b *Block) GetNonce() int64 { return int64(b.block.Nonce()) }
|
||||
func (b *Block) GetHash() *Hash { return &Hash{b.block.Hash()} }
|
||||
func (b *Block) GetHeader() *Header { return &Header{b.block.Header()} }
|
||||
func (b *Block) GetUncles() *Headers { return &Headers{b.block.Uncles()} }
|
||||
func (b *Block) GetTransactions() *Transactions { return &Transactions{b.block.Transactions()} }
|
||||
|
|
|
|||
|
|
@ -146,7 +146,7 @@ func newDialState(static []*discover.Node, bootnodes []*discover.Node, ntab disc
|
|||
}
|
||||
|
||||
func (s *dialstate) addStatic(n *discover.Node) {
|
||||
// This overwites the task instead of updating an existing
|
||||
// This overwrites the task instead of updating an existing
|
||||
// entry, giving users the opportunity to force a resolve operation.
|
||||
s.static[n.ID] = &dialTask{flags: staticDialedConn, dest: n}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ type Protocol struct {
|
|||
// by the protocol.
|
||||
Length uint64
|
||||
|
||||
// Run is called in a new groutine when the protocol has been
|
||||
// Run is called in a new goroutine when the protocol has been
|
||||
// negotiated with a peer. It should read and write messages from
|
||||
// rw. The Payload for each message must be fully consumed.
|
||||
//
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ import (
|
|||
var (
|
||||
MainnetGenesisHash = common.HexToHash("0xd4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3")
|
||||
TestnetGenesisHash = common.HexToHash("0x41941023680923e0fe4d74a34bdac8141f2540e3ae90623718e47d66d1ca4a2d")
|
||||
RinkebyGenesisHash = common.HexToHash("0x6341fd3daf94b748c72ced5a5b26028f2474f5f00d824504e4fa37a75767e177")
|
||||
)
|
||||
|
||||
var (
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ import (
|
|||
const (
|
||||
VersionMajor = 1 // Major version component of the current release
|
||||
VersionMinor = 8 // Minor version component of the current release
|
||||
VersionPatch = 14 // Patch version component of the current release
|
||||
VersionPatch = 15 // Patch version component of the current release
|
||||
VersionMeta = "unstable" // Version metadata to append to the version string
|
||||
)
|
||||
|
||||
|
|
|
|||
468
swarm/api/act.go
Normal file
468
swarm/api/act.go
Normal file
|
|
@ -0,0 +1,468 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/ecdsa"
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"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/swarm/log"
|
||||
"github.com/ethereum/go-ethereum/swarm/sctx"
|
||||
"github.com/ethereum/go-ethereum/swarm/storage"
|
||||
"golang.org/x/crypto/scrypt"
|
||||
cli "gopkg.in/urfave/cli.v1"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrDecrypt = errors.New("cant decrypt - forbidden")
|
||||
ErrUnknownAccessType = errors.New("unknown access type (or not implemented)")
|
||||
ErrDecryptDomainForbidden = errors.New("decryption request domain forbidden - can only decrypt on localhost")
|
||||
AllowedDecryptDomains = []string{
|
||||
"localhost",
|
||||
"127.0.0.1",
|
||||
}
|
||||
)
|
||||
|
||||
const EMPTY_CREDENTIALS = ""
|
||||
|
||||
type AccessEntry struct {
|
||||
Type AccessType
|
||||
Publisher string
|
||||
Salt []byte
|
||||
Act string
|
||||
KdfParams *KdfParams
|
||||
}
|
||||
|
||||
type DecryptFunc func(*ManifestEntry) error
|
||||
|
||||
func (a *AccessEntry) MarshalJSON() (out []byte, err error) {
|
||||
|
||||
return json.Marshal(struct {
|
||||
Type AccessType `json:"type,omitempty"`
|
||||
Publisher string `json:"publisher,omitempty"`
|
||||
Salt string `json:"salt,omitempty"`
|
||||
Act string `json:"act,omitempty"`
|
||||
KdfParams *KdfParams `json:"kdf_params,omitempty"`
|
||||
}{
|
||||
Type: a.Type,
|
||||
Publisher: a.Publisher,
|
||||
Salt: hex.EncodeToString(a.Salt),
|
||||
Act: a.Act,
|
||||
KdfParams: a.KdfParams,
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
func (a *AccessEntry) UnmarshalJSON(value []byte) error {
|
||||
v := struct {
|
||||
Type AccessType `json:"type,omitempty"`
|
||||
Publisher string `json:"publisher,omitempty"`
|
||||
Salt string `json:"salt,omitempty"`
|
||||
Act string `json:"act,omitempty"`
|
||||
KdfParams *KdfParams `json:"kdf_params,omitempty"`
|
||||
}{}
|
||||
|
||||
err := json.Unmarshal(value, &v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
a.Act = v.Act
|
||||
a.KdfParams = v.KdfParams
|
||||
a.Publisher = v.Publisher
|
||||
a.Salt, err = hex.DecodeString(v.Salt)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(a.Salt) != 32 {
|
||||
return errors.New("salt should be 32 bytes long")
|
||||
}
|
||||
a.Type = v.Type
|
||||
return nil
|
||||
}
|
||||
|
||||
type KdfParams struct {
|
||||
N int `json:"n"`
|
||||
P int `json:"p"`
|
||||
R int `json:"r"`
|
||||
}
|
||||
|
||||
type AccessType string
|
||||
|
||||
const AccessTypePass = AccessType("pass")
|
||||
const AccessTypePK = AccessType("pk")
|
||||
const AccessTypeACT = AccessType("act")
|
||||
|
||||
func NewAccessEntryPassword(salt []byte, kdfParams *KdfParams) (*AccessEntry, error) {
|
||||
if len(salt) != 32 {
|
||||
return nil, fmt.Errorf("salt should be 32 bytes long")
|
||||
}
|
||||
return &AccessEntry{
|
||||
Type: AccessTypePass,
|
||||
Salt: salt,
|
||||
KdfParams: kdfParams,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func NewAccessEntryPK(publisher string, salt []byte) (*AccessEntry, error) {
|
||||
if len(publisher) != 66 {
|
||||
return nil, fmt.Errorf("publisher should be 66 characters long, got %d", len(publisher))
|
||||
}
|
||||
if len(salt) != 32 {
|
||||
return nil, fmt.Errorf("salt should be 32 bytes long")
|
||||
}
|
||||
return &AccessEntry{
|
||||
Type: AccessTypePK,
|
||||
Publisher: publisher,
|
||||
Salt: salt,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func NewAccessEntryACT(publisher string, salt []byte, act string) (*AccessEntry, error) {
|
||||
if len(salt) != 32 {
|
||||
return nil, fmt.Errorf("salt should be 32 bytes long")
|
||||
}
|
||||
if len(publisher) != 66 {
|
||||
return nil, fmt.Errorf("publisher should be 66 characters long")
|
||||
}
|
||||
|
||||
return &AccessEntry{
|
||||
Type: AccessTypeACT,
|
||||
Publisher: publisher,
|
||||
Salt: salt,
|
||||
Act: act,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func NOOPDecrypt(*ManifestEntry) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
var DefaultKdfParams = NewKdfParams(262144, 1, 8)
|
||||
|
||||
func NewKdfParams(n, p, r int) *KdfParams {
|
||||
|
||||
return &KdfParams{
|
||||
N: n,
|
||||
P: p,
|
||||
R: r,
|
||||
}
|
||||
}
|
||||
|
||||
// NewSessionKeyPassword creates a session key based on a shared secret (password) and the given salt
|
||||
// and kdf parameters in the access entry
|
||||
func NewSessionKeyPassword(password string, accessEntry *AccessEntry) ([]byte, error) {
|
||||
if accessEntry.Type != AccessTypePass {
|
||||
return nil, errors.New("incorrect access entry type")
|
||||
}
|
||||
return scrypt.Key(
|
||||
[]byte(password),
|
||||
accessEntry.Salt,
|
||||
accessEntry.KdfParams.N,
|
||||
accessEntry.KdfParams.R,
|
||||
accessEntry.KdfParams.P,
|
||||
32,
|
||||
)
|
||||
}
|
||||
|
||||
// NewSessionKeyPK creates a new ACT Session Key using an ECDH shared secret for the given key pair and the given salt value
|
||||
func NewSessionKeyPK(private *ecdsa.PrivateKey, public *ecdsa.PublicKey, salt []byte) ([]byte, error) {
|
||||
granteePubEcies := ecies.ImportECDSAPublic(public)
|
||||
privateKey := ecies.ImportECDSA(private)
|
||||
|
||||
bytes, err := privateKey.GenerateShared(granteePubEcies, 16, 16)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
bytes = append(salt, bytes...)
|
||||
sessionKey := crypto.Keccak256(bytes)
|
||||
return sessionKey, nil
|
||||
}
|
||||
|
||||
func (a *API) NodeSessionKey(privateKey *ecdsa.PrivateKey, publicKey *ecdsa.PublicKey, salt []byte) ([]byte, error) {
|
||||
return NewSessionKeyPK(privateKey, publicKey, salt)
|
||||
}
|
||||
func (a *API) doDecrypt(ctx context.Context, credentials string, pk *ecdsa.PrivateKey) DecryptFunc {
|
||||
return func(m *ManifestEntry) error {
|
||||
if m.Access == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
allowed := false
|
||||
requestDomain := sctx.GetHost(ctx)
|
||||
for _, v := range AllowedDecryptDomains {
|
||||
if strings.Contains(requestDomain, v) {
|
||||
allowed = true
|
||||
}
|
||||
}
|
||||
|
||||
if !allowed {
|
||||
return ErrDecryptDomainForbidden
|
||||
}
|
||||
|
||||
switch m.Access.Type {
|
||||
case "pass":
|
||||
if credentials != "" {
|
||||
key, err := NewSessionKeyPassword(credentials, m.Access)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ref, err := hex.DecodeString(m.Hash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
enc := NewRefEncryption(len(ref) - 8)
|
||||
decodedRef, err := enc.Decrypt(ref, key)
|
||||
if err != nil {
|
||||
return ErrDecrypt
|
||||
}
|
||||
|
||||
m.Hash = hex.EncodeToString(decodedRef)
|
||||
m.Access = nil
|
||||
return nil
|
||||
}
|
||||
return ErrDecrypt
|
||||
case "pk":
|
||||
publisherBytes, err := hex.DecodeString(m.Access.Publisher)
|
||||
if err != nil {
|
||||
return ErrDecrypt
|
||||
}
|
||||
publisher, err := crypto.DecompressPubkey(publisherBytes)
|
||||
if err != nil {
|
||||
return ErrDecrypt
|
||||
}
|
||||
key, err := a.NodeSessionKey(pk, publisher, m.Access.Salt)
|
||||
if err != nil {
|
||||
return ErrDecrypt
|
||||
}
|
||||
ref, err := hex.DecodeString(m.Hash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
enc := NewRefEncryption(len(ref) - 8)
|
||||
decodedRef, err := enc.Decrypt(ref, key)
|
||||
if err != nil {
|
||||
return ErrDecrypt
|
||||
}
|
||||
|
||||
m.Hash = hex.EncodeToString(decodedRef)
|
||||
m.Access = nil
|
||||
return nil
|
||||
case "act":
|
||||
publisherBytes, err := hex.DecodeString(m.Access.Publisher)
|
||||
if err != nil {
|
||||
return ErrDecrypt
|
||||
}
|
||||
publisher, err := crypto.DecompressPubkey(publisherBytes)
|
||||
if err != nil {
|
||||
return ErrDecrypt
|
||||
}
|
||||
|
||||
sessionKey, err := a.NodeSessionKey(pk, publisher, m.Access.Salt)
|
||||
if err != nil {
|
||||
return ErrDecrypt
|
||||
}
|
||||
|
||||
hasher := sha3.NewKeccak256()
|
||||
hasher.Write(append(sessionKey, 0))
|
||||
lookupKey := hasher.Sum(nil)
|
||||
|
||||
hasher.Reset()
|
||||
|
||||
hasher.Write(append(sessionKey, 1))
|
||||
accessKeyDecryptionKey := hasher.Sum(nil)
|
||||
|
||||
lk := hex.EncodeToString(lookupKey)
|
||||
list, err := a.GetManifestList(ctx, NOOPDecrypt, storage.Address(common.Hex2Bytes(m.Access.Act)), lk)
|
||||
|
||||
found := ""
|
||||
for _, v := range list.Entries {
|
||||
if v.Path == lk {
|
||||
found = v.Hash
|
||||
}
|
||||
}
|
||||
|
||||
if found == "" {
|
||||
return ErrDecrypt
|
||||
}
|
||||
|
||||
v, err := hex.DecodeString(found)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
enc := NewRefEncryption(len(v) - 8)
|
||||
decodedRef, err := enc.Decrypt(v, accessKeyDecryptionKey)
|
||||
if err != nil {
|
||||
return ErrDecrypt
|
||||
}
|
||||
|
||||
ref, err := hex.DecodeString(m.Hash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
enc = NewRefEncryption(len(ref) - 8)
|
||||
decodedMainRef, err := enc.Decrypt(ref, decodedRef)
|
||||
if err != nil {
|
||||
return ErrDecrypt
|
||||
}
|
||||
m.Hash = hex.EncodeToString(decodedMainRef)
|
||||
m.Access = nil
|
||||
return nil
|
||||
}
|
||||
return ErrUnknownAccessType
|
||||
}
|
||||
}
|
||||
|
||||
func GenerateAccessControlManifest(ctx *cli.Context, ref string, accessKey []byte, ae *AccessEntry) (*Manifest, error) {
|
||||
refBytes, err := hex.DecodeString(ref)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// encrypt ref with accessKey
|
||||
enc := NewRefEncryption(len(refBytes))
|
||||
encrypted, err := enc.Encrypt(refBytes, accessKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
m := &Manifest{
|
||||
Entries: []ManifestEntry{
|
||||
{
|
||||
Hash: hex.EncodeToString(encrypted),
|
||||
ContentType: ManifestType,
|
||||
ModTime: time.Now(),
|
||||
Access: ae,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func DoPKNew(ctx *cli.Context, privateKey *ecdsa.PrivateKey, granteePublicKey string, salt []byte) (sessionKey []byte, ae *AccessEntry, err error) {
|
||||
if granteePublicKey == "" {
|
||||
return nil, nil, errors.New("need a grantee Public Key")
|
||||
}
|
||||
b, err := hex.DecodeString(granteePublicKey)
|
||||
if err != nil {
|
||||
log.Error("error decoding grantee public key", "err", err)
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
granteePub, err := crypto.DecompressPubkey(b)
|
||||
if err != nil {
|
||||
log.Error("error decompressing grantee public key", "err", err)
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
sessionKey, err = NewSessionKeyPK(privateKey, granteePub, salt)
|
||||
if err != nil {
|
||||
log.Error("error getting session key", "err", err)
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
ae, err = NewAccessEntryPK(hex.EncodeToString(crypto.CompressPubkey(&privateKey.PublicKey)), salt)
|
||||
if err != nil {
|
||||
log.Error("error generating access entry", "err", err)
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return sessionKey, ae, nil
|
||||
}
|
||||
|
||||
func DoACTNew(ctx *cli.Context, privateKey *ecdsa.PrivateKey, salt []byte, grantees []string) (accessKey []byte, ae *AccessEntry, actManifest *Manifest, err error) {
|
||||
if len(grantees) == 0 {
|
||||
return nil, nil, nil, errors.New("did not get any grantee public keys")
|
||||
}
|
||||
|
||||
publisherPub := hex.EncodeToString(crypto.CompressPubkey(&privateKey.PublicKey))
|
||||
grantees = append(grantees, publisherPub)
|
||||
|
||||
accessKey = make([]byte, 32)
|
||||
if _, err := io.ReadFull(rand.Reader, salt); err != nil {
|
||||
panic("reading from crypto/rand failed: " + err.Error())
|
||||
}
|
||||
if _, err := io.ReadFull(rand.Reader, accessKey); err != nil {
|
||||
panic("reading from crypto/rand failed: " + err.Error())
|
||||
}
|
||||
|
||||
lookupPathEncryptedAccessKeyMap := make(map[string]string)
|
||||
i := 0
|
||||
for _, v := range grantees {
|
||||
i++
|
||||
if v == "" {
|
||||
return nil, nil, nil, errors.New("need a grantee Public Key")
|
||||
}
|
||||
b, err := hex.DecodeString(v)
|
||||
if err != nil {
|
||||
log.Error("error decoding grantee public key", "err", err)
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
granteePub, err := crypto.DecompressPubkey(b)
|
||||
if err != nil {
|
||||
log.Error("error decompressing grantee public key", "err", err)
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
sessionKey, err := NewSessionKeyPK(privateKey, granteePub, salt)
|
||||
|
||||
hasher := sha3.NewKeccak256()
|
||||
hasher.Write(append(sessionKey, 0))
|
||||
lookupKey := hasher.Sum(nil)
|
||||
|
||||
hasher.Reset()
|
||||
hasher.Write(append(sessionKey, 1))
|
||||
|
||||
accessKeyEncryptionKey := hasher.Sum(nil)
|
||||
|
||||
enc := NewRefEncryption(len(accessKey))
|
||||
encryptedAccessKey, err := enc.Encrypt(accessKey, accessKeyEncryptionKey)
|
||||
|
||||
lookupPathEncryptedAccessKeyMap[hex.EncodeToString(lookupKey)] = hex.EncodeToString(encryptedAccessKey)
|
||||
}
|
||||
|
||||
m := &Manifest{
|
||||
Entries: []ManifestEntry{},
|
||||
}
|
||||
|
||||
for k, v := range lookupPathEncryptedAccessKeyMap {
|
||||
m.Entries = append(m.Entries, ManifestEntry{
|
||||
Path: k,
|
||||
Hash: v,
|
||||
ContentType: "text/plain",
|
||||
})
|
||||
}
|
||||
|
||||
ae, err = NewAccessEntryACT(hex.EncodeToString(crypto.CompressPubkey(&privateKey.PublicKey)), salt, "")
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
return accessKey, ae, m, nil
|
||||
}
|
||||
|
||||
func DoPasswordNew(ctx *cli.Context, password string, salt []byte) (sessionKey []byte, ae *AccessEntry, err error) {
|
||||
ae, err = NewAccessEntryPassword(salt, DefaultKdfParams)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
sessionKey, err = NewSessionKeyPassword(password, ae)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return sessionKey, ae, nil
|
||||
}
|
||||
135
swarm/api/api.go
135
swarm/api/api.go
|
|
@ -19,6 +19,9 @@ package api
|
|||
import (
|
||||
"archive/tar"
|
||||
"context"
|
||||
"crypto/ecdsa"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"math/big"
|
||||
|
|
@ -43,6 +46,10 @@ import (
|
|||
opentracing "github.com/opentracing/opentracing-go"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrNotFound = errors.New("not found")
|
||||
)
|
||||
|
||||
var (
|
||||
apiResolveCount = metrics.NewRegisteredCounter("api.resolve.count", nil)
|
||||
apiResolveFail = metrics.NewRegisteredCounter("api.resolve.fail", nil)
|
||||
|
|
@ -227,14 +234,18 @@ type API struct {
|
|||
resource *mru.Handler
|
||||
fileStore *storage.FileStore
|
||||
dns Resolver
|
||||
Decryptor func(context.Context, string) DecryptFunc
|
||||
}
|
||||
|
||||
// NewAPI the api constructor initialises a new API instance.
|
||||
func NewAPI(fileStore *storage.FileStore, dns Resolver, resourceHandler *mru.Handler) (self *API) {
|
||||
func NewAPI(fileStore *storage.FileStore, dns Resolver, resourceHandler *mru.Handler, pk *ecdsa.PrivateKey) (self *API) {
|
||||
self = &API{
|
||||
fileStore: fileStore,
|
||||
dns: dns,
|
||||
resource: resourceHandler,
|
||||
Decryptor: func(ctx context.Context, credentials string) DecryptFunc {
|
||||
return self.doDecrypt(ctx, credentials, pk)
|
||||
},
|
||||
}
|
||||
return
|
||||
}
|
||||
|
|
@ -260,8 +271,30 @@ func (a *API) Store(ctx context.Context, data io.Reader, size int64, toEncrypt b
|
|||
// ErrResolve is returned when an URI cannot be resolved from ENS.
|
||||
type ErrResolve error
|
||||
|
||||
// Resolve a name into a content-addressed hash
|
||||
// where address could be an ENS name, or a content addressed hash
|
||||
func (a *API) Resolve(ctx context.Context, address string) (storage.Address, error) {
|
||||
// if DNS is not configured, return an error
|
||||
if a.dns == nil {
|
||||
if hashMatcher.MatchString(address) {
|
||||
return common.Hex2Bytes(address), nil
|
||||
}
|
||||
apiResolveFail.Inc(1)
|
||||
return nil, fmt.Errorf("no DNS to resolve name: %q", address)
|
||||
}
|
||||
// try and resolve the address
|
||||
resolved, err := a.dns.Resolve(address)
|
||||
if err != nil {
|
||||
if hashMatcher.MatchString(address) {
|
||||
return common.Hex2Bytes(address), nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return resolved[:], nil
|
||||
}
|
||||
|
||||
// Resolve resolves a URI to an Address using the MultiResolver.
|
||||
func (a *API) Resolve(ctx context.Context, uri *URI) (storage.Address, error) {
|
||||
func (a *API) ResolveURI(ctx context.Context, uri *URI, credentials string) (storage.Address, error) {
|
||||
apiResolveCount.Inc(1)
|
||||
log.Trace("resolving", "uri", uri.Addr)
|
||||
|
||||
|
|
@ -280,28 +313,44 @@ func (a *API) Resolve(ctx context.Context, uri *URI) (storage.Address, error) {
|
|||
return key, nil
|
||||
}
|
||||
|
||||
// if DNS is not configured, check if the address is a hash
|
||||
if a.dns == nil {
|
||||
key := uri.Address()
|
||||
if key == nil {
|
||||
apiResolveFail.Inc(1)
|
||||
return nil, fmt.Errorf("no DNS to resolve name: %q", uri.Addr)
|
||||
}
|
||||
return key, nil
|
||||
}
|
||||
|
||||
// try and resolve the address
|
||||
resolved, err := a.dns.Resolve(uri.Addr)
|
||||
if err == nil {
|
||||
return resolved[:], nil
|
||||
}
|
||||
|
||||
key := uri.Address()
|
||||
if key == nil {
|
||||
apiResolveFail.Inc(1)
|
||||
addr, err := a.Resolve(ctx, uri.Addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return key, nil
|
||||
|
||||
if uri.Path == "" {
|
||||
return addr, nil
|
||||
}
|
||||
walker, err := a.NewManifestWalker(ctx, addr, a.Decryptor(ctx, credentials), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var entry *ManifestEntry
|
||||
walker.Walk(func(e *ManifestEntry) error {
|
||||
// if the entry matches the path, set entry and stop
|
||||
// the walk
|
||||
if e.Path == uri.Path {
|
||||
entry = e
|
||||
// return an error to cancel the walk
|
||||
return errors.New("found")
|
||||
}
|
||||
// ignore non-manifest files
|
||||
if e.ContentType != ManifestType {
|
||||
return nil
|
||||
}
|
||||
// if the manifest's path is a prefix of the
|
||||
// requested path, recurse into it by returning
|
||||
// nil and continuing the walk
|
||||
if strings.HasPrefix(uri.Path, e.Path) {
|
||||
return nil
|
||||
}
|
||||
return ErrSkipManifest
|
||||
})
|
||||
if entry == nil {
|
||||
return nil, errors.New("not found")
|
||||
}
|
||||
addr = storage.Address(common.Hex2Bytes(entry.Hash))
|
||||
return addr, nil
|
||||
}
|
||||
|
||||
// Put provides singleton manifest creation on top of FileStore store
|
||||
|
|
@ -332,10 +381,10 @@ func (a *API) Put(ctx context.Context, content string, contentType string, toEnc
|
|||
// Get uses iterative manifest retrieval and prefix matching
|
||||
// to resolve basePath to content using FileStore retrieve
|
||||
// it returns a section reader, mimeType, status, the key of the actual content and an error
|
||||
func (a *API) Get(ctx context.Context, manifestAddr storage.Address, path string) (reader storage.LazySectionReader, mimeType string, status int, contentAddr storage.Address, err error) {
|
||||
func (a *API) Get(ctx context.Context, decrypt DecryptFunc, manifestAddr storage.Address, path string) (reader storage.LazySectionReader, mimeType string, status int, contentAddr storage.Address, err error) {
|
||||
log.Debug("api.get", "key", manifestAddr, "path", path)
|
||||
apiGetCount.Inc(1)
|
||||
trie, err := loadManifest(ctx, a.fileStore, manifestAddr, nil)
|
||||
trie, err := loadManifest(ctx, a.fileStore, manifestAddr, nil, decrypt)
|
||||
if err != nil {
|
||||
apiGetNotFound.Inc(1)
|
||||
status = http.StatusNotFound
|
||||
|
|
@ -347,6 +396,16 @@ func (a *API) Get(ctx context.Context, manifestAddr storage.Address, path string
|
|||
|
||||
if entry != nil {
|
||||
log.Debug("trie got entry", "key", manifestAddr, "path", path, "entry.Hash", entry.Hash)
|
||||
|
||||
if entry.ContentType == ManifestType {
|
||||
log.Debug("entry is manifest", "key", manifestAddr, "new key", entry.Hash)
|
||||
adr, err := hex.DecodeString(entry.Hash)
|
||||
if err != nil {
|
||||
return nil, "", 0, nil, err
|
||||
}
|
||||
return a.Get(ctx, decrypt, adr, entry.Path)
|
||||
}
|
||||
|
||||
// we need to do some extra work if this is a mutable resource manifest
|
||||
if entry.ContentType == ResourceContentType {
|
||||
|
||||
|
|
@ -398,7 +457,7 @@ func (a *API) Get(ctx context.Context, manifestAddr storage.Address, path string
|
|||
log.Trace("resource is multihash", "key", manifestAddr)
|
||||
|
||||
// get the manifest the multihash digest points to
|
||||
trie, err := loadManifest(ctx, a.fileStore, manifestAddr, nil)
|
||||
trie, err := loadManifest(ctx, a.fileStore, manifestAddr, nil, decrypt)
|
||||
if err != nil {
|
||||
apiGetNotFound.Inc(1)
|
||||
status = http.StatusNotFound
|
||||
|
|
@ -451,7 +510,7 @@ func (a *API) Delete(ctx context.Context, addr string, path string) (storage.Add
|
|||
apiDeleteFail.Inc(1)
|
||||
return nil, err
|
||||
}
|
||||
key, err := a.Resolve(ctx, uri)
|
||||
key, err := a.ResolveURI(ctx, uri, EMPTY_CREDENTIALS)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
@ -470,13 +529,13 @@ func (a *API) Delete(ctx context.Context, addr string, path string) (storage.Add
|
|||
|
||||
// GetDirectoryTar fetches a requested directory as a tarstream
|
||||
// it returns an io.Reader and an error. Do not forget to Close() the returned ReadCloser
|
||||
func (a *API) GetDirectoryTar(ctx context.Context, uri *URI) (io.ReadCloser, error) {
|
||||
func (a *API) GetDirectoryTar(ctx context.Context, decrypt DecryptFunc, uri *URI) (io.ReadCloser, error) {
|
||||
apiGetTarCount.Inc(1)
|
||||
addr, err := a.Resolve(ctx, uri)
|
||||
addr, err := a.Resolve(ctx, uri.Addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
walker, err := a.NewManifestWalker(ctx, addr, nil)
|
||||
walker, err := a.NewManifestWalker(ctx, addr, decrypt, nil)
|
||||
if err != nil {
|
||||
apiGetTarFail.Inc(1)
|
||||
return nil, err
|
||||
|
|
@ -542,9 +601,9 @@ func (a *API) GetDirectoryTar(ctx context.Context, uri *URI) (io.ReadCloser, err
|
|||
|
||||
// GetManifestList lists the manifest entries for the specified address and prefix
|
||||
// and returns it as a ManifestList
|
||||
func (a *API) GetManifestList(ctx context.Context, addr storage.Address, prefix string) (list ManifestList, err error) {
|
||||
func (a *API) GetManifestList(ctx context.Context, decryptor DecryptFunc, addr storage.Address, prefix string) (list ManifestList, err error) {
|
||||
apiManifestListCount.Inc(1)
|
||||
walker, err := a.NewManifestWalker(ctx, addr, nil)
|
||||
walker, err := a.NewManifestWalker(ctx, addr, decryptor, nil)
|
||||
if err != nil {
|
||||
apiManifestListFail.Inc(1)
|
||||
return ManifestList{}, err
|
||||
|
|
@ -631,7 +690,7 @@ func (a *API) UpdateManifest(ctx context.Context, addr storage.Address, update f
|
|||
func (a *API) Modify(ctx context.Context, addr storage.Address, path, contentHash, contentType string) (storage.Address, error) {
|
||||
apiModifyCount.Inc(1)
|
||||
quitC := make(chan bool)
|
||||
trie, err := loadManifest(ctx, a.fileStore, addr, quitC)
|
||||
trie, err := loadManifest(ctx, a.fileStore, addr, quitC, NOOPDecrypt)
|
||||
if err != nil {
|
||||
apiModifyFail.Inc(1)
|
||||
return nil, err
|
||||
|
|
@ -663,7 +722,7 @@ func (a *API) AddFile(ctx context.Context, mhash, path, fname string, content []
|
|||
apiAddFileFail.Inc(1)
|
||||
return nil, "", err
|
||||
}
|
||||
mkey, err := a.Resolve(ctx, uri)
|
||||
mkey, err := a.ResolveURI(ctx, uri, EMPTY_CREDENTIALS)
|
||||
if err != nil {
|
||||
apiAddFileFail.Inc(1)
|
||||
return nil, "", err
|
||||
|
|
@ -770,7 +829,7 @@ func (a *API) RemoveFile(ctx context.Context, mhash string, path string, fname s
|
|||
apiRmFileFail.Inc(1)
|
||||
return "", err
|
||||
}
|
||||
mkey, err := a.Resolve(ctx, uri)
|
||||
mkey, err := a.ResolveURI(ctx, uri, EMPTY_CREDENTIALS)
|
||||
if err != nil {
|
||||
apiRmFileFail.Inc(1)
|
||||
return "", err
|
||||
|
|
@ -837,7 +896,7 @@ func (a *API) AppendFile(ctx context.Context, mhash, path, fname string, existin
|
|||
apiAppendFileFail.Inc(1)
|
||||
return nil, "", err
|
||||
}
|
||||
mkey, err := a.Resolve(ctx, uri)
|
||||
mkey, err := a.ResolveURI(ctx, uri, EMPTY_CREDENTIALS)
|
||||
if err != nil {
|
||||
apiAppendFileFail.Inc(1)
|
||||
return nil, "", err
|
||||
|
|
@ -891,13 +950,13 @@ func (a *API) BuildDirectoryTree(ctx context.Context, mhash string, nameresolver
|
|||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
addr, err = a.Resolve(ctx, uri)
|
||||
addr, err = a.Resolve(ctx, uri.Addr)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
quitC := make(chan bool)
|
||||
rootTrie, err := loadManifest(ctx, a.fileStore, addr, quitC)
|
||||
rootTrie, err := loadManifest(ctx, a.fileStore, addr, quitC, NOOPDecrypt)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("can't load manifest %v: %v", addr.String(), err)
|
||||
}
|
||||
|
|
@ -955,7 +1014,7 @@ func (a *API) ResourceHashSize() int {
|
|||
|
||||
// ResolveResourceManifest retrieves the Mutable Resource manifest for the given address, and returns the address of the metadata chunk.
|
||||
func (a *API) ResolveResourceManifest(ctx context.Context, addr storage.Address) (storage.Address, error) {
|
||||
trie, err := loadManifest(ctx, a.fileStore, addr, nil)
|
||||
trie, err := loadManifest(ctx, a.fileStore, addr, nil, NOOPDecrypt)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot load resource manifest: %v", err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ package api
|
|||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
|
|
@ -28,10 +29,17 @@ import (
|
|||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/swarm/log"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/swarm/sctx"
|
||||
"github.com/ethereum/go-ethereum/swarm/storage"
|
||||
)
|
||||
|
||||
func init() {
|
||||
loglevel := flag.Int("loglevel", 2, "loglevel")
|
||||
flag.Parse()
|
||||
log.Root().SetHandler(log.CallerFileHandler(log.LvlFilterHandler(log.Lvl(*loglevel), log.StreamHandler(os.Stderr, log.TerminalFormat(true)))))
|
||||
}
|
||||
|
||||
func testAPI(t *testing.T, f func(*API, bool)) {
|
||||
datadir, err := ioutil.TempDir("", "bzz-test")
|
||||
if err != nil {
|
||||
|
|
@ -42,7 +50,7 @@ func testAPI(t *testing.T, f func(*API, bool)) {
|
|||
if err != nil {
|
||||
return
|
||||
}
|
||||
api := NewAPI(fileStore, nil, nil)
|
||||
api := NewAPI(fileStore, nil, nil, nil)
|
||||
f(api, false)
|
||||
f(api, true)
|
||||
}
|
||||
|
|
@ -85,7 +93,7 @@ func expResponse(content string, mimeType string, status int) *Response {
|
|||
|
||||
func testGet(t *testing.T, api *API, bzzhash, path string) *testResponse {
|
||||
addr := storage.Address(common.Hex2Bytes(bzzhash))
|
||||
reader, mimeType, status, _, err := api.Get(context.TODO(), addr, path)
|
||||
reader, mimeType, status, _, err := api.Get(context.TODO(), NOOPDecrypt, addr, path)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
|
@ -229,7 +237,7 @@ func TestAPIResolve(t *testing.T) {
|
|||
if x.immutable {
|
||||
uri.Scheme = "bzz-immutable"
|
||||
}
|
||||
res, err := api.Resolve(context.TODO(), uri)
|
||||
res, err := api.ResolveURI(context.TODO(), uri, "")
|
||||
if err == nil {
|
||||
if x.expectErr != nil {
|
||||
t.Fatalf("expected error %q, got result %q", x.expectErr, res)
|
||||
|
|
@ -373,3 +381,55 @@ func TestMultiResolver(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecryptOriginForbidden(t *testing.T) {
|
||||
ctx := context.TODO()
|
||||
ctx = sctx.SetHost(ctx, "swarm-gateways.net")
|
||||
|
||||
me := &ManifestEntry{
|
||||
Access: &AccessEntry{Type: AccessTypePass},
|
||||
}
|
||||
|
||||
api := NewAPI(nil, nil, nil, nil)
|
||||
|
||||
f := api.Decryptor(ctx, "")
|
||||
err := f(me)
|
||||
if err != ErrDecryptDomainForbidden {
|
||||
t.Fatalf("should fail with ErrDecryptDomainForbidden, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecryptOrigin(t *testing.T) {
|
||||
for _, v := range []struct {
|
||||
host string
|
||||
expectError error
|
||||
}{
|
||||
{
|
||||
host: "localhost",
|
||||
expectError: ErrDecrypt,
|
||||
},
|
||||
{
|
||||
host: "127.0.0.1",
|
||||
expectError: ErrDecrypt,
|
||||
},
|
||||
{
|
||||
host: "swarm-gateways.net",
|
||||
expectError: ErrDecryptDomainForbidden,
|
||||
},
|
||||
} {
|
||||
ctx := context.TODO()
|
||||
ctx = sctx.SetHost(ctx, v.host)
|
||||
|
||||
me := &ManifestEntry{
|
||||
Access: &AccessEntry{Type: AccessTypePass},
|
||||
}
|
||||
|
||||
api := NewAPI(nil, nil, nil, nil)
|
||||
|
||||
f := api.Decryptor(ctx, "")
|
||||
err := f(me)
|
||||
if err != v.expectError {
|
||||
t.Fatalf("should fail with %v, got %v", v.expectError, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -43,6 +43,10 @@ var (
|
|||
DefaultClient = NewClient(DefaultGateway)
|
||||
)
|
||||
|
||||
var (
|
||||
ErrUnauthorized = errors.New("unauthorized")
|
||||
)
|
||||
|
||||
func NewClient(gateway string) *Client {
|
||||
return &Client{
|
||||
Gateway: gateway,
|
||||
|
|
@ -188,7 +192,7 @@ func (c *Client) UploadDirectory(dir, defaultPath, manifest string, toEncrypt bo
|
|||
|
||||
// DownloadDirectory downloads the files contained in a swarm manifest under
|
||||
// the given path into a local directory (existing files will be overwritten)
|
||||
func (c *Client) DownloadDirectory(hash, path, destDir string) error {
|
||||
func (c *Client) DownloadDirectory(hash, path, destDir, credentials string) error {
|
||||
stat, err := os.Stat(destDir)
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
@ -201,13 +205,20 @@ func (c *Client) DownloadDirectory(hash, path, destDir string) error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if credentials != "" {
|
||||
req.SetBasicAuth("", credentials)
|
||||
}
|
||||
req.Header.Set("Accept", "application/x-tar")
|
||||
res, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
if res.StatusCode != http.StatusOK {
|
||||
switch res.StatusCode {
|
||||
case http.StatusOK:
|
||||
case http.StatusUnauthorized:
|
||||
return ErrUnauthorized
|
||||
default:
|
||||
return fmt.Errorf("unexpected HTTP status: %s", res.Status)
|
||||
}
|
||||
tr := tar.NewReader(res.Body)
|
||||
|
|
@ -248,7 +259,7 @@ func (c *Client) DownloadDirectory(hash, path, destDir string) error {
|
|||
// DownloadFile downloads a single file into the destination directory
|
||||
// if the manifest entry does not specify a file name - it will fallback
|
||||
// to the hash of the file as a filename
|
||||
func (c *Client) DownloadFile(hash, path, dest string) error {
|
||||
func (c *Client) DownloadFile(hash, path, dest, credentials string) error {
|
||||
hasDestinationFilename := false
|
||||
if stat, err := os.Stat(dest); err == nil {
|
||||
hasDestinationFilename = !stat.IsDir()
|
||||
|
|
@ -261,9 +272,9 @@ func (c *Client) DownloadFile(hash, path, dest string) error {
|
|||
}
|
||||
}
|
||||
|
||||
manifestList, err := c.List(hash, path)
|
||||
manifestList, err := c.List(hash, path, credentials)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not list manifest: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
switch len(manifestList.Entries) {
|
||||
|
|
@ -280,13 +291,19 @@ func (c *Client) DownloadFile(hash, path, dest string) error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if credentials != "" {
|
||||
req.SetBasicAuth("", credentials)
|
||||
}
|
||||
res, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
|
||||
if res.StatusCode != http.StatusOK {
|
||||
switch res.StatusCode {
|
||||
case http.StatusOK:
|
||||
case http.StatusUnauthorized:
|
||||
return ErrUnauthorized
|
||||
default:
|
||||
return fmt.Errorf("unexpected HTTP status: expected 200 OK, got %d", res.StatusCode)
|
||||
}
|
||||
filename := ""
|
||||
|
|
@ -367,13 +384,24 @@ func (c *Client) DownloadManifest(hash string) (*api.Manifest, bool, error) {
|
|||
// - a prefix of "dir1/" would return [dir1/dir2/, dir1/file3.txt]
|
||||
//
|
||||
// where entries ending with "/" are common prefixes.
|
||||
func (c *Client) List(hash, prefix string) (*api.ManifestList, error) {
|
||||
res, err := http.DefaultClient.Get(c.Gateway + "/bzz-list:/" + hash + "/" + prefix)
|
||||
func (c *Client) List(hash, prefix, credentials string) (*api.ManifestList, error) {
|
||||
req, err := http.NewRequest(http.MethodGet, c.Gateway+"/bzz-list:/"+hash+"/"+prefix, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if credentials != "" {
|
||||
req.SetBasicAuth("", credentials)
|
||||
}
|
||||
res, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
if res.StatusCode != http.StatusOK {
|
||||
switch res.StatusCode {
|
||||
case http.StatusOK:
|
||||
case http.StatusUnauthorized:
|
||||
return nil, ErrUnauthorized
|
||||
default:
|
||||
return nil, fmt.Errorf("unexpected HTTP status: %s", res.Status)
|
||||
}
|
||||
var list api.ManifestList
|
||||
|
|
|
|||
|
|
@ -228,7 +228,7 @@ func TestClientUploadDownloadDirectory(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(tmp)
|
||||
if err := client.DownloadDirectory(hash, "", tmp); err != nil {
|
||||
if err := client.DownloadDirectory(hash, "", tmp, ""); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for _, file := range testDirFiles {
|
||||
|
|
@ -265,7 +265,7 @@ func testClientFileList(toEncrypt bool, t *testing.T) {
|
|||
}
|
||||
|
||||
ls := func(prefix string) []string {
|
||||
list, err := client.List(hash, prefix)
|
||||
list, err := client.List(hash, prefix, "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -68,7 +68,6 @@ type Config struct {
|
|||
SwapAPI string
|
||||
Cors string
|
||||
BzzAccount string
|
||||
BootNodes string
|
||||
privateKey *ecdsa.PrivateKey
|
||||
}
|
||||
|
||||
|
|
@ -93,7 +92,6 @@ func NewConfig() (c *Config) {
|
|||
DeliverySkipCheck: false,
|
||||
SyncUpdateDelay: 15 * time.Second,
|
||||
SwapAPI: "",
|
||||
BootNodes: "",
|
||||
}
|
||||
|
||||
return
|
||||
|
|
|
|||
76
swarm/api/encrypt.go
Normal file
76
swarm/api/encrypt.go
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
// Copyright 2016 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package api
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
|
||||
"github.com/ethereum/go-ethereum/crypto/sha3"
|
||||
"github.com/ethereum/go-ethereum/swarm/storage/encryption"
|
||||
)
|
||||
|
||||
type RefEncryption struct {
|
||||
spanEncryption encryption.Encryption
|
||||
dataEncryption encryption.Encryption
|
||||
span []byte
|
||||
}
|
||||
|
||||
func NewRefEncryption(refSize int) *RefEncryption {
|
||||
span := make([]byte, 8)
|
||||
binary.LittleEndian.PutUint64(span, uint64(refSize))
|
||||
return &RefEncryption{
|
||||
spanEncryption: encryption.New(0, uint32(refSize/32), sha3.NewKeccak256),
|
||||
dataEncryption: encryption.New(refSize, 0, sha3.NewKeccak256),
|
||||
span: span,
|
||||
}
|
||||
}
|
||||
|
||||
func (re *RefEncryption) Encrypt(ref []byte, key []byte) ([]byte, error) {
|
||||
encryptedSpan, err := re.spanEncryption.Encrypt(re.span, key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
encryptedData, err := re.dataEncryption.Encrypt(ref, key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
encryptedRef := make([]byte, len(ref)+8)
|
||||
copy(encryptedRef[:8], encryptedSpan)
|
||||
copy(encryptedRef[8:], encryptedData)
|
||||
|
||||
return encryptedRef, nil
|
||||
}
|
||||
|
||||
func (re *RefEncryption) Decrypt(ref []byte, key []byte) ([]byte, error) {
|
||||
decryptedSpan, err := re.spanEncryption.Decrypt(ref[:8], key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
size := binary.LittleEndian.Uint64(decryptedSpan)
|
||||
if size != uint64(len(ref)-8) {
|
||||
return nil, errors.New("invalid span in encrypted reference")
|
||||
}
|
||||
|
||||
decryptedRef, err := re.dataEncryption.Decrypt(ref[8:], key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return decryptedRef, nil
|
||||
}
|
||||
|
|
@ -191,7 +191,7 @@ func (fs *FileSystem) Download(bzzpath, localpath string) error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
addr, err := fs.api.Resolve(context.TODO(), uri)
|
||||
addr, err := fs.api.Resolve(context.TODO(), uri.Addr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -202,7 +202,7 @@ func (fs *FileSystem) Download(bzzpath, localpath string) error {
|
|||
}
|
||||
|
||||
quitC := make(chan bool)
|
||||
trie, err := loadManifest(context.TODO(), fs.api.fileStore, addr, quitC)
|
||||
trie, err := loadManifest(context.TODO(), fs.api.fileStore, addr, quitC, NOOPDecrypt)
|
||||
if err != nil {
|
||||
log.Warn(fmt.Sprintf("fs.Download: loadManifestTrie error: %v", err))
|
||||
return err
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ func TestApiDirUpload0(t *testing.T) {
|
|||
checkResponse(t, resp, exp)
|
||||
|
||||
addr := storage.Address(common.Hex2Bytes(bzzhash))
|
||||
_, _, _, _, err = api.Get(context.TODO(), addr, "")
|
||||
_, _, _, _, err = api.Get(context.TODO(), NOOPDecrypt, addr, "")
|
||||
if err == nil {
|
||||
t.Fatalf("expected error: %v", err)
|
||||
}
|
||||
|
|
@ -143,7 +143,7 @@ func TestApiDirUploadModify(t *testing.T) {
|
|||
exp = expResponse(content, "text/css", 0)
|
||||
checkResponse(t, resp, exp)
|
||||
|
||||
_, _, _, _, err = api.Get(context.TODO(), addr, "")
|
||||
_, _, _, _, err = api.Get(context.TODO(), nil, addr, "")
|
||||
if err == nil {
|
||||
t.Errorf("expected error: %v", err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import (
|
|||
"github.com/ethereum/go-ethereum/metrics"
|
||||
"github.com/ethereum/go-ethereum/swarm/api"
|
||||
"github.com/ethereum/go-ethereum/swarm/log"
|
||||
"github.com/ethereum/go-ethereum/swarm/sctx"
|
||||
"github.com/ethereum/go-ethereum/swarm/spancontext"
|
||||
"github.com/pborman/uuid"
|
||||
)
|
||||
|
|
@ -35,6 +36,15 @@ func SetRequestID(h http.Handler) http.Handler {
|
|||
})
|
||||
}
|
||||
|
||||
func SetRequestHost(h http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
r = r.WithContext(sctx.SetHost(r.Context(), r.Host))
|
||||
log.Info("setting request host", "ruid", GetRUID(r.Context()), "host", sctx.GetHost(r.Context()))
|
||||
|
||||
h.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
|
||||
func ParseURI(h http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
uri, err := api.Parse(strings.TrimLeft(r.URL.Path, "/"))
|
||||
|
|
@ -87,7 +97,7 @@ func RecoverPanic(h http.Handler) http.Handler {
|
|||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
log.Error("panic recovery!", "stack trace", debug.Stack(), "url", r.URL.String(), "headers", r.Header)
|
||||
log.Error("panic recovery!", "stack trace", string(debug.Stack()), "url", r.URL.String(), "headers", r.Header)
|
||||
}
|
||||
}()
|
||||
h.ServeHTTP(w, r)
|
||||
|
|
|
|||
|
|
@ -79,7 +79,7 @@ func RespondTemplate(w http.ResponseWriter, r *http.Request, templateName, msg s
|
|||
}
|
||||
|
||||
func RespondError(w http.ResponseWriter, r *http.Request, msg string, code int) {
|
||||
log.Debug("RespondError", "ruid", GetRUID(r.Context()), "uri", GetURI(r.Context()))
|
||||
log.Debug("RespondError", "ruid", GetRUID(r.Context()), "uri", GetURI(r.Context()), "code", code)
|
||||
RespondTemplate(w, r, "error", msg, code)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -23,7 +23,6 @@ import (
|
|||
"bufio"
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
|
|
@ -97,6 +96,7 @@ func NewServer(api *api.API, corsString string) *Server {
|
|||
defaultMiddlewares := []Adapter{
|
||||
RecoverPanic,
|
||||
SetRequestID,
|
||||
SetRequestHost,
|
||||
InitLoggingResponseWriter,
|
||||
ParseURI,
|
||||
InstrumentOpenTracing,
|
||||
|
|
@ -169,6 +169,7 @@ func NewServer(api *api.API, corsString string) *Server {
|
|||
}
|
||||
|
||||
func (s *Server) ListenAndServe(addr string) error {
|
||||
s.listenAddr = addr
|
||||
return http.ListenAndServe(addr, s)
|
||||
}
|
||||
|
||||
|
|
@ -178,16 +179,24 @@ func (s *Server) ListenAndServe(addr string) error {
|
|||
// https://github.com/atom/electron/blob/master/docs/api/protocol.md
|
||||
type Server struct {
|
||||
http.Handler
|
||||
api *api.API
|
||||
api *api.API
|
||||
listenAddr string
|
||||
}
|
||||
|
||||
func (s *Server) HandleBzzGet(w http.ResponseWriter, r *http.Request) {
|
||||
log.Debug("handleBzzGet", "ruid", GetRUID(r.Context()))
|
||||
log.Debug("handleBzzGet", "ruid", GetRUID(r.Context()), "uri", r.RequestURI)
|
||||
if r.Header.Get("Accept") == "application/x-tar" {
|
||||
uri := GetURI(r.Context())
|
||||
reader, err := s.api.GetDirectoryTar(r.Context(), uri)
|
||||
_, credentials, _ := r.BasicAuth()
|
||||
reader, err := s.api.GetDirectoryTar(r.Context(), s.api.Decryptor(r.Context(), credentials), uri)
|
||||
if err != nil {
|
||||
if isDecryptError(err) {
|
||||
w.Header().Set("WWW-Authenticate", fmt.Sprintf("Basic realm=%q", uri.Address().String()))
|
||||
RespondError(w, r, err.Error(), http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
RespondError(w, r, fmt.Sprintf("Had an error building the tarball: %v", err), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
defer reader.Close()
|
||||
|
||||
|
|
@ -287,7 +296,7 @@ func (s *Server) HandlePostFiles(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
var addr storage.Address
|
||||
if uri.Addr != "" && uri.Addr != "encrypt" {
|
||||
addr, err = s.api.Resolve(r.Context(), uri)
|
||||
addr, err = s.api.Resolve(r.Context(), uri.Addr)
|
||||
if err != nil {
|
||||
postFilesFail.Inc(1)
|
||||
RespondError(w, r, fmt.Sprintf("cannot resolve %s: %s", uri.Addr, err), http.StatusInternalServerError)
|
||||
|
|
@ -563,7 +572,7 @@ func (s *Server) HandleGetResource(w http.ResponseWriter, r *http.Request) {
|
|||
// resolve the content key.
|
||||
manifestAddr := uri.Address()
|
||||
if manifestAddr == nil {
|
||||
manifestAddr, err = s.api.Resolve(r.Context(), uri)
|
||||
manifestAddr, err = s.api.Resolve(r.Context(), uri.Addr)
|
||||
if err != nil {
|
||||
getFail.Inc(1)
|
||||
RespondError(w, r, fmt.Sprintf("cannot resolve %s: %s", uri.Addr, err), http.StatusNotFound)
|
||||
|
|
@ -682,62 +691,21 @@ func (s *Server) HandleGet(w http.ResponseWriter, r *http.Request) {
|
|||
uri := GetURI(r.Context())
|
||||
log.Debug("handle.get", "ruid", ruid, "uri", uri)
|
||||
getCount.Inc(1)
|
||||
_, pass, _ := r.BasicAuth()
|
||||
|
||||
var err error
|
||||
addr := uri.Address()
|
||||
if addr == nil {
|
||||
addr, err = s.api.Resolve(r.Context(), uri)
|
||||
if err != nil {
|
||||
getFail.Inc(1)
|
||||
RespondError(w, r, fmt.Sprintf("cannot resolve %s: %s", uri.Addr, err), http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
w.Header().Set("Cache-Control", "max-age=2147483648, immutable") // url was of type bzz://<hex key>/path, so we are sure it is immutable.
|
||||
addr, err := s.api.ResolveURI(r.Context(), uri, pass)
|
||||
if err != nil {
|
||||
getFail.Inc(1)
|
||||
RespondError(w, r, fmt.Sprintf("cannot resolve %s: %s", uri.Addr, err), http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
w.Header().Set("Cache-Control", "max-age=2147483648, immutable") // url was of type bzz://<hex key>/path, so we are sure it is immutable.
|
||||
|
||||
log.Debug("handle.get: resolved", "ruid", ruid, "key", addr)
|
||||
|
||||
// if path is set, interpret <key> as a manifest and return the
|
||||
// raw entry at the given path
|
||||
if uri.Path != "" {
|
||||
walker, err := s.api.NewManifestWalker(r.Context(), addr, nil)
|
||||
if err != nil {
|
||||
getFail.Inc(1)
|
||||
RespondError(w, r, fmt.Sprintf("%s is not a manifest", addr), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
var entry *api.ManifestEntry
|
||||
walker.Walk(func(e *api.ManifestEntry) error {
|
||||
// if the entry matches the path, set entry and stop
|
||||
// the walk
|
||||
if e.Path == uri.Path {
|
||||
entry = e
|
||||
// return an error to cancel the walk
|
||||
return errors.New("found")
|
||||
}
|
||||
|
||||
// ignore non-manifest files
|
||||
if e.ContentType != api.ManifestType {
|
||||
return nil
|
||||
}
|
||||
|
||||
// if the manifest's path is a prefix of the
|
||||
// requested path, recurse into it by returning
|
||||
// nil and continuing the walk
|
||||
if strings.HasPrefix(uri.Path, e.Path) {
|
||||
return nil
|
||||
}
|
||||
|
||||
return api.ErrSkipManifest
|
||||
})
|
||||
if entry == nil {
|
||||
getFail.Inc(1)
|
||||
RespondError(w, r, fmt.Sprintf("manifest entry could not be loaded"), http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
addr = storage.Address(common.Hex2Bytes(entry.Hash))
|
||||
}
|
||||
etag := common.Bytes2Hex(addr)
|
||||
noneMatchEtag := r.Header.Get("If-None-Match")
|
||||
w.Header().Set("ETag", fmt.Sprintf("%q", etag)) // set etag to manifest key or raw entry key.
|
||||
|
|
@ -781,6 +749,7 @@ func (s *Server) HandleGet(w http.ResponseWriter, r *http.Request) {
|
|||
func (s *Server) HandleGetList(w http.ResponseWriter, r *http.Request) {
|
||||
ruid := GetRUID(r.Context())
|
||||
uri := GetURI(r.Context())
|
||||
_, credentials, _ := r.BasicAuth()
|
||||
log.Debug("handle.get.list", "ruid", ruid, "uri", uri)
|
||||
getListCount.Inc(1)
|
||||
|
||||
|
|
@ -790,7 +759,7 @@ func (s *Server) HandleGetList(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
addr, err := s.api.Resolve(r.Context(), uri)
|
||||
addr, err := s.api.Resolve(r.Context(), uri.Addr)
|
||||
if err != nil {
|
||||
getListFail.Inc(1)
|
||||
RespondError(w, r, fmt.Sprintf("cannot resolve %s: %s", uri.Addr, err), http.StatusNotFound)
|
||||
|
|
@ -798,9 +767,14 @@ func (s *Server) HandleGetList(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
log.Debug("handle.get.list: resolved", "ruid", ruid, "key", addr)
|
||||
|
||||
list, err := s.api.GetManifestList(r.Context(), addr, uri.Path)
|
||||
list, err := s.api.GetManifestList(r.Context(), s.api.Decryptor(r.Context(), credentials), addr, uri.Path)
|
||||
if err != nil {
|
||||
getListFail.Inc(1)
|
||||
if isDecryptError(err) {
|
||||
w.Header().Set("WWW-Authenticate", fmt.Sprintf("Basic realm=%q", addr.String()))
|
||||
RespondError(w, r, err.Error(), http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
RespondError(w, r, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
|
@ -833,7 +807,8 @@ func (s *Server) HandleGetList(w http.ResponseWriter, r *http.Request) {
|
|||
func (s *Server) HandleGetFile(w http.ResponseWriter, r *http.Request) {
|
||||
ruid := GetRUID(r.Context())
|
||||
uri := GetURI(r.Context())
|
||||
log.Debug("handle.get.file", "ruid", ruid)
|
||||
_, credentials, _ := r.BasicAuth()
|
||||
log.Debug("handle.get.file", "ruid", ruid, "uri", r.RequestURI)
|
||||
getFileCount.Inc(1)
|
||||
|
||||
// ensure the root path has a trailing slash so that relative URLs work
|
||||
|
|
@ -845,7 +820,7 @@ func (s *Server) HandleGetFile(w http.ResponseWriter, r *http.Request) {
|
|||
manifestAddr := uri.Address()
|
||||
|
||||
if manifestAddr == nil {
|
||||
manifestAddr, err = s.api.Resolve(r.Context(), uri)
|
||||
manifestAddr, err = s.api.Resolve(r.Context(), uri.Addr)
|
||||
if err != nil {
|
||||
getFileFail.Inc(1)
|
||||
RespondError(w, r, fmt.Sprintf("cannot resolve %s: %s", uri.Addr, err), http.StatusNotFound)
|
||||
|
|
@ -856,7 +831,8 @@ func (s *Server) HandleGetFile(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
|
||||
log.Debug("handle.get.file: resolved", "ruid", ruid, "key", manifestAddr)
|
||||
reader, contentType, status, contentKey, err := s.api.Get(r.Context(), manifestAddr, uri.Path)
|
||||
|
||||
reader, contentType, status, contentKey, err := s.api.Get(r.Context(), s.api.Decryptor(r.Context(), credentials), manifestAddr, uri.Path)
|
||||
|
||||
etag := common.Bytes2Hex(contentKey)
|
||||
noneMatchEtag := r.Header.Get("If-None-Match")
|
||||
|
|
@ -869,6 +845,12 @@ func (s *Server) HandleGetFile(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
|
||||
if err != nil {
|
||||
if isDecryptError(err) {
|
||||
w.Header().Set("WWW-Authenticate", fmt.Sprintf("Basic realm=%q", manifestAddr))
|
||||
RespondError(w, r, err.Error(), http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
switch status {
|
||||
case http.StatusNotFound:
|
||||
getFileNotFound.Inc(1)
|
||||
|
|
@ -883,9 +865,14 @@ func (s *Server) HandleGetFile(w http.ResponseWriter, r *http.Request) {
|
|||
//the request results in ambiguous files
|
||||
//e.g. /read with readme.md and readinglist.txt available in manifest
|
||||
if status == http.StatusMultipleChoices {
|
||||
list, err := s.api.GetManifestList(r.Context(), manifestAddr, uri.Path)
|
||||
list, err := s.api.GetManifestList(r.Context(), s.api.Decryptor(r.Context(), credentials), manifestAddr, uri.Path)
|
||||
if err != nil {
|
||||
getFileFail.Inc(1)
|
||||
if isDecryptError(err) {
|
||||
w.Header().Set("WWW-Authenticate", fmt.Sprintf("Basic realm=%q", manifestAddr))
|
||||
RespondError(w, r, err.Error(), http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
RespondError(w, r, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
|
@ -951,3 +938,7 @@ func (lrw *loggingResponseWriter) WriteHeader(code int) {
|
|||
lrw.statusCode = code
|
||||
lrw.ResponseWriter.WriteHeader(code)
|
||||
}
|
||||
|
||||
func isDecryptError(err error) bool {
|
||||
return strings.Contains(err.Error(), api.ErrDecrypt.Error())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -46,13 +46,14 @@ type Manifest struct {
|
|||
|
||||
// ManifestEntry represents an entry in a swarm manifest
|
||||
type ManifestEntry struct {
|
||||
Hash string `json:"hash,omitempty"`
|
||||
Path string `json:"path,omitempty"`
|
||||
ContentType string `json:"contentType,omitempty"`
|
||||
Mode int64 `json:"mode,omitempty"`
|
||||
Size int64 `json:"size,omitempty"`
|
||||
ModTime time.Time `json:"mod_time,omitempty"`
|
||||
Status int `json:"status,omitempty"`
|
||||
Hash string `json:"hash,omitempty"`
|
||||
Path string `json:"path,omitempty"`
|
||||
ContentType string `json:"contentType,omitempty"`
|
||||
Mode int64 `json:"mode,omitempty"`
|
||||
Size int64 `json:"size,omitempty"`
|
||||
ModTime time.Time `json:"mod_time,omitempty"`
|
||||
Status int `json:"status,omitempty"`
|
||||
Access *AccessEntry `json:"access,omitempty"`
|
||||
}
|
||||
|
||||
// ManifestList represents the result of listing files in a manifest
|
||||
|
|
@ -98,7 +99,7 @@ type ManifestWriter struct {
|
|||
}
|
||||
|
||||
func (a *API) NewManifestWriter(ctx context.Context, addr storage.Address, quitC chan bool) (*ManifestWriter, error) {
|
||||
trie, err := loadManifest(ctx, a.fileStore, addr, quitC)
|
||||
trie, err := loadManifest(ctx, a.fileStore, addr, quitC, NOOPDecrypt)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error loading manifest %s: %s", addr, err)
|
||||
}
|
||||
|
|
@ -141,8 +142,8 @@ type ManifestWalker struct {
|
|||
quitC chan bool
|
||||
}
|
||||
|
||||
func (a *API) NewManifestWalker(ctx context.Context, addr storage.Address, quitC chan bool) (*ManifestWalker, error) {
|
||||
trie, err := loadManifest(ctx, a.fileStore, addr, quitC)
|
||||
func (a *API) NewManifestWalker(ctx context.Context, addr storage.Address, decrypt DecryptFunc, quitC chan bool) (*ManifestWalker, error) {
|
||||
trie, err := loadManifest(ctx, a.fileStore, addr, quitC, decrypt)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error loading manifest %s: %s", addr, err)
|
||||
}
|
||||
|
|
@ -194,6 +195,7 @@ type manifestTrie struct {
|
|||
entries [257]*manifestTrieEntry // indexed by first character of basePath, entries[256] is the empty basePath entry
|
||||
ref storage.Address // if ref != nil, it is stored
|
||||
encrypted bool
|
||||
decrypt DecryptFunc
|
||||
}
|
||||
|
||||
func newManifestTrieEntry(entry *ManifestEntry, subtrie *manifestTrie) *manifestTrieEntry {
|
||||
|
|
@ -209,15 +211,15 @@ type manifestTrieEntry struct {
|
|||
subtrie *manifestTrie
|
||||
}
|
||||
|
||||
func loadManifest(ctx context.Context, fileStore *storage.FileStore, hash storage.Address, quitC chan bool) (trie *manifestTrie, err error) { // non-recursive, subtrees are downloaded on-demand
|
||||
func loadManifest(ctx context.Context, fileStore *storage.FileStore, hash storage.Address, quitC chan bool, decrypt DecryptFunc) (trie *manifestTrie, err error) { // non-recursive, subtrees are downloaded on-demand
|
||||
log.Trace("manifest lookup", "key", hash)
|
||||
// retrieve manifest via FileStore
|
||||
manifestReader, isEncrypted := fileStore.Retrieve(ctx, hash)
|
||||
log.Trace("reader retrieved", "key", hash)
|
||||
return readManifest(manifestReader, hash, fileStore, isEncrypted, quitC)
|
||||
return readManifest(manifestReader, hash, fileStore, isEncrypted, quitC, decrypt)
|
||||
}
|
||||
|
||||
func readManifest(mr storage.LazySectionReader, hash storage.Address, fileStore *storage.FileStore, isEncrypted bool, quitC chan bool) (trie *manifestTrie, err error) { // non-recursive, subtrees are downloaded on-demand
|
||||
func readManifest(mr storage.LazySectionReader, hash storage.Address, fileStore *storage.FileStore, isEncrypted bool, quitC chan bool, decrypt DecryptFunc) (trie *manifestTrie, err error) { // non-recursive, subtrees are downloaded on-demand
|
||||
|
||||
// TODO check size for oversized manifests
|
||||
size, err := mr.Size(mr.Context(), quitC)
|
||||
|
|
@ -258,26 +260,41 @@ func readManifest(mr storage.LazySectionReader, hash storage.Address, fileStore
|
|||
trie = &manifestTrie{
|
||||
fileStore: fileStore,
|
||||
encrypted: isEncrypted,
|
||||
decrypt: decrypt,
|
||||
}
|
||||
for _, entry := range man.Entries {
|
||||
trie.addEntry(entry, quitC)
|
||||
err = trie.addEntry(entry, quitC)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (mt *manifestTrie) addEntry(entry *manifestTrieEntry, quitC chan bool) {
|
||||
func (mt *manifestTrie) addEntry(entry *manifestTrieEntry, quitC chan bool) error {
|
||||
mt.ref = nil // trie modified, hash needs to be re-calculated on demand
|
||||
|
||||
if entry.ManifestEntry.Access != nil {
|
||||
if mt.decrypt == nil {
|
||||
return errors.New("dont have decryptor")
|
||||
}
|
||||
|
||||
err := mt.decrypt(&entry.ManifestEntry)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if len(entry.Path) == 0 {
|
||||
mt.entries[256] = entry
|
||||
return
|
||||
return nil
|
||||
}
|
||||
|
||||
b := entry.Path[0]
|
||||
oldentry := mt.entries[b]
|
||||
if (oldentry == nil) || (oldentry.Path == entry.Path && oldentry.ContentType != ManifestType) {
|
||||
mt.entries[b] = entry
|
||||
return
|
||||
return nil
|
||||
}
|
||||
|
||||
cpl := 0
|
||||
|
|
@ -287,12 +304,12 @@ func (mt *manifestTrie) addEntry(entry *manifestTrieEntry, quitC chan bool) {
|
|||
|
||||
if (oldentry.ContentType == ManifestType) && (cpl == len(oldentry.Path)) {
|
||||
if mt.loadSubTrie(oldentry, quitC) != nil {
|
||||
return
|
||||
return nil
|
||||
}
|
||||
entry.Path = entry.Path[cpl:]
|
||||
oldentry.subtrie.addEntry(entry, quitC)
|
||||
oldentry.Hash = ""
|
||||
return
|
||||
return nil
|
||||
}
|
||||
|
||||
commonPrefix := entry.Path[:cpl]
|
||||
|
|
@ -310,6 +327,7 @@ func (mt *manifestTrie) addEntry(entry *manifestTrieEntry, quitC chan bool) {
|
|||
Path: commonPrefix,
|
||||
ContentType: ManifestType,
|
||||
}, subtrie)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (mt *manifestTrie) getCountLast() (cnt int, entry *manifestTrieEntry) {
|
||||
|
|
@ -398,9 +416,20 @@ func (mt *manifestTrie) recalcAndStore() error {
|
|||
}
|
||||
|
||||
func (mt *manifestTrie) loadSubTrie(entry *manifestTrieEntry, quitC chan bool) (err error) {
|
||||
if entry.ManifestEntry.Access != nil {
|
||||
if mt.decrypt == nil {
|
||||
return errors.New("dont have decryptor")
|
||||
}
|
||||
|
||||
err := mt.decrypt(&entry.ManifestEntry)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if entry.subtrie == nil {
|
||||
hash := common.Hex2Bytes(entry.Hash)
|
||||
entry.subtrie, err = loadManifest(context.TODO(), mt.fileStore, hash, quitC)
|
||||
entry.subtrie, err = loadManifest(context.TODO(), mt.fileStore, hash, quitC, mt.decrypt)
|
||||
entry.Hash = "" // might not match, should be recalculated
|
||||
}
|
||||
return
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ func testGetEntry(t *testing.T, path, match string, multiple bool, paths ...stri
|
|||
quitC := make(chan bool)
|
||||
fileStore := storage.NewFileStore(nil, storage.NewFileStoreParams())
|
||||
ref := make([]byte, fileStore.HashSize())
|
||||
trie, err := readManifest(manifest(paths...), ref, fileStore, false, quitC)
|
||||
trie, err := readManifest(manifest(paths...), ref, fileStore, false, quitC, NOOPDecrypt)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error making manifest: %v", err)
|
||||
}
|
||||
|
|
@ -101,7 +101,7 @@ func TestExactMatch(t *testing.T) {
|
|||
mf := manifest("shouldBeExactMatch.css", "shouldBeExactMatch.css.map")
|
||||
fileStore := storage.NewFileStore(nil, storage.NewFileStoreParams())
|
||||
ref := make([]byte, fileStore.HashSize())
|
||||
trie, err := readManifest(mf, ref, fileStore, false, quitC)
|
||||
trie, err := readManifest(mf, ref, fileStore, false, quitC, nil)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error making manifest: %v", err)
|
||||
}
|
||||
|
|
@ -134,7 +134,7 @@ func TestAddFileWithManifestPath(t *testing.T) {
|
|||
}
|
||||
fileStore := storage.NewFileStore(nil, storage.NewFileStoreParams())
|
||||
ref := make([]byte, fileStore.HashSize())
|
||||
trie, err := readManifest(reader, ref, fileStore, false, nil)
|
||||
trie, err := readManifest(reader, ref, fileStore, false, nil, NOOPDecrypt)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
|
@ -161,7 +161,7 @@ func TestReadManifestOverSizeLimit(t *testing.T) {
|
|||
reader := &storage.LazyTestSectionReader{
|
||||
SectionReader: io.NewSectionReader(bytes.NewReader(manifest), 0, int64(len(manifest))),
|
||||
}
|
||||
_, err := readManifest(reader, storage.Address{}, nil, false, nil)
|
||||
_, err := readManifest(reader, storage.Address{}, nil, false, nil, NOOPDecrypt)
|
||||
if err == nil {
|
||||
t.Fatal("got no error from readManifest")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -63,11 +63,11 @@ func (s *Storage) Get(ctx context.Context, bzzpath string) (*Response, error) {
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
addr, err := s.api.Resolve(ctx, uri)
|
||||
addr, err := s.api.Resolve(ctx, uri.Addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
reader, mimeType, status, _, err := s.api.Get(ctx, addr, uri.Path)
|
||||
reader, mimeType, status, _, err := s.api.Get(ctx, nil, addr, uri.Path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -93,7 +93,7 @@ func (s *Storage) Modify(ctx context.Context, rootHash, path, contentHash, conte
|
|||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
addr, err := s.api.Resolve(ctx, uri)
|
||||
addr, err := s.api.Resolve(ctx, uri.Addr)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -53,6 +53,19 @@ type URI struct {
|
|||
Path string
|
||||
}
|
||||
|
||||
func (u *URI) MarshalJSON() (out []byte, err error) {
|
||||
return []byte(`"` + u.String() + `"`), nil
|
||||
}
|
||||
|
||||
func (u *URI) UnmarshalJSON(value []byte) error {
|
||||
uri, err := Parse(string(value))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*u = *uri
|
||||
return nil
|
||||
}
|
||||
|
||||
// Parse parses rawuri into a URI struct, where rawuri is expected to have one
|
||||
// of the following formats:
|
||||
//
|
||||
|
|
|
|||
|
|
@ -1650,7 +1650,7 @@ func TestFUSE(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ta := &testAPI{api: api.NewAPI(fileStore, nil, nil)}
|
||||
ta := &testAPI{api: api.NewAPI(fileStore, nil, nil, nil)}
|
||||
|
||||
//run a short suite of tests
|
||||
//approx time: 28s
|
||||
|
|
|
|||
|
|
@ -102,10 +102,10 @@ func NewHive(params *HiveParams, overlay Overlay, store state.Store) *Hive {
|
|||
// server is used to connect to a peer based on its NodeID or enode URL
|
||||
// these are called on the p2p.Server which runs on the node
|
||||
func (h *Hive) Start(server *p2p.Server) error {
|
||||
log.Info(fmt.Sprintf("%08x hive starting", h.BaseAddr()[:4]))
|
||||
log.Info("Starting hive", "baseaddr", fmt.Sprintf("%x", h.BaseAddr()[:4]))
|
||||
// if state store is specified, load peers to prepopulate the overlay address book
|
||||
if h.Store != nil {
|
||||
log.Info("detected an existing store. trying to load peers")
|
||||
log.Info("Detected an existing store. trying to load peers")
|
||||
if err := h.loadPeers(); err != nil {
|
||||
log.Error(fmt.Sprintf("%08x hive encoutered an error trying to load peers", h.BaseAddr()[:4]))
|
||||
return err
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ const (
|
|||
// BzzSpec is the spec of the generic swarm handshake
|
||||
var BzzSpec = &protocols.Spec{
|
||||
Name: "bzz",
|
||||
Version: 5,
|
||||
Version: 6,
|
||||
MaxMsgSize: 10 * 1024 * 1024,
|
||||
Messages: []interface{}{
|
||||
HandshakeMsg{},
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ import (
|
|||
)
|
||||
|
||||
const (
|
||||
TestProtocolVersion = 5
|
||||
TestProtocolVersion = 6
|
||||
TestProtocolNetworkID = 3
|
||||
)
|
||||
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue