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
|
go: 1.10.x
|
||||||
script:
|
script:
|
||||||
- unset -f cd # workaround for https://github.com/travis-ci/travis-ci/issues/8703
|
- unset -f cd # workaround for https://github.com/travis-ci/travis-ci/issues/8703
|
||||||
- brew update
|
|
||||||
- brew cask install osxfuse
|
|
||||||
- go run build/ci.go install
|
- go run build/ci.go install
|
||||||
- go run build/ci.go test -coverage $TEST_PACKAGES
|
- go run build/ci.go test -coverage $TEST_PACKAGES
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -106,7 +106,7 @@ type Wallet interface {
|
||||||
// or optionally with the aid of any location metadata from the embedded URL field.
|
// or optionally with the aid of any location metadata from the embedded URL field.
|
||||||
//
|
//
|
||||||
// If the wallet requires additional authentication to sign the request (e.g.
|
// If the wallet requires additional authentication to sign the request (e.g.
|
||||||
// a password to decrypt the account, or a PIN code o verify the transaction),
|
// a password to decrypt the account, or a PIN code to verify the transaction),
|
||||||
// an AuthNeededError instance will be returned, containing infos for the user
|
// an AuthNeededError instance will be returned, containing infos for the user
|
||||||
// about which fields or actions are needed. The user may retry by providing
|
// about which fields or actions are needed. The user may retry by providing
|
||||||
// the needed details via SignTxWithPassphrase, or by other means (e.g. unlock
|
// the needed details via SignTxWithPassphrase, or by other means (e.g. unlock
|
||||||
|
|
|
||||||
11
build/ci.go
11
build/ci.go
|
|
@ -644,17 +644,6 @@ func (meta debMetadata) ExeName(exe debExecutable) string {
|
||||||
return exe.Package()
|
return exe.Package()
|
||||||
}
|
}
|
||||||
|
|
||||||
// EthereumSwarmPackageName returns the name of the swarm package based on
|
|
||||||
// environment, e.g. "ethereum-swarm-unstable", or "ethereum-swarm".
|
|
||||||
// This is needed so that we make sure that "ethereum" package,
|
|
||||||
// depends on and installs "ethereum-swarm"
|
|
||||||
func (meta debMetadata) EthereumSwarmPackageName() string {
|
|
||||||
if isUnstableBuild(meta.Env) {
|
|
||||||
return debSwarm.Name + "-unstable"
|
|
||||||
}
|
|
||||||
return debSwarm.Name
|
|
||||||
}
|
|
||||||
|
|
||||||
// ExeConflicts returns the content of the Conflicts field
|
// ExeConflicts returns the content of the Conflicts field
|
||||||
// for executable packages.
|
// for executable packages.
|
||||||
func (meta debMetadata) ExeConflicts(exe debExecutable) string {
|
func (meta debMetadata) ExeConflicts(exe debExecutable) string {
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ Vcs-Browser: https://github.com/ethereum/go-ethereum
|
||||||
|
|
||||||
Package: {{.Name}}
|
Package: {{.Name}}
|
||||||
Architecture: any
|
Architecture: any
|
||||||
Depends: ${misc:Depends}, {{.EthereumSwarmPackageName}}, {{.ExeList}}
|
Depends: ${misc:Depends}, {{.ExeList}}
|
||||||
Description: Meta-package to install geth, swarm, and other tools
|
Description: Meta-package to install geth, swarm, and other tools
|
||||||
Meta-package to install geth, swarm and other tools
|
Meta-package to install geth, swarm and other tools
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -48,7 +48,6 @@ var (
|
||||||
ArgsUsage: "<genesisPath>",
|
ArgsUsage: "<genesisPath>",
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
utils.DataDirFlag,
|
utils.DataDirFlag,
|
||||||
utils.LightModeFlag,
|
|
||||||
},
|
},
|
||||||
Category: "BLOCKCHAIN COMMANDS",
|
Category: "BLOCKCHAIN COMMANDS",
|
||||||
Description: `
|
Description: `
|
||||||
|
|
@ -66,7 +65,7 @@ It expects the genesis file as argument.`,
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
utils.DataDirFlag,
|
utils.DataDirFlag,
|
||||||
utils.CacheFlag,
|
utils.CacheFlag,
|
||||||
utils.LightModeFlag,
|
utils.SyncModeFlag,
|
||||||
utils.GCModeFlag,
|
utils.GCModeFlag,
|
||||||
utils.CacheDatabaseFlag,
|
utils.CacheDatabaseFlag,
|
||||||
utils.CacheGCFlag,
|
utils.CacheGCFlag,
|
||||||
|
|
@ -87,7 +86,7 @@ processing will proceed even if an individual RLP-file import failure occurs.`,
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
utils.DataDirFlag,
|
utils.DataDirFlag,
|
||||||
utils.CacheFlag,
|
utils.CacheFlag,
|
||||||
utils.LightModeFlag,
|
utils.SyncModeFlag,
|
||||||
},
|
},
|
||||||
Category: "BLOCKCHAIN COMMANDS",
|
Category: "BLOCKCHAIN COMMANDS",
|
||||||
Description: `
|
Description: `
|
||||||
|
|
@ -105,7 +104,7 @@ be gzipped.`,
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
utils.DataDirFlag,
|
utils.DataDirFlag,
|
||||||
utils.CacheFlag,
|
utils.CacheFlag,
|
||||||
utils.LightModeFlag,
|
utils.SyncModeFlag,
|
||||||
},
|
},
|
||||||
Category: "BLOCKCHAIN COMMANDS",
|
Category: "BLOCKCHAIN COMMANDS",
|
||||||
Description: `
|
Description: `
|
||||||
|
|
@ -119,7 +118,7 @@ be gzipped.`,
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
utils.DataDirFlag,
|
utils.DataDirFlag,
|
||||||
utils.CacheFlag,
|
utils.CacheFlag,
|
||||||
utils.LightModeFlag,
|
utils.SyncModeFlag,
|
||||||
},
|
},
|
||||||
Category: "BLOCKCHAIN COMMANDS",
|
Category: "BLOCKCHAIN COMMANDS",
|
||||||
Description: `
|
Description: `
|
||||||
|
|
@ -149,7 +148,6 @@ The first argument must be the directory containing the blockchain to download f
|
||||||
ArgsUsage: " ",
|
ArgsUsage: " ",
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
utils.DataDirFlag,
|
utils.DataDirFlag,
|
||||||
utils.LightModeFlag,
|
|
||||||
},
|
},
|
||||||
Category: "BLOCKCHAIN COMMANDS",
|
Category: "BLOCKCHAIN COMMANDS",
|
||||||
Description: `
|
Description: `
|
||||||
|
|
@ -163,7 +161,7 @@ Remove blockchain and state databases`,
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
utils.DataDirFlag,
|
utils.DataDirFlag,
|
||||||
utils.CacheFlag,
|
utils.CacheFlag,
|
||||||
utils.LightModeFlag,
|
utils.SyncModeFlag,
|
||||||
},
|
},
|
||||||
Category: "BLOCKCHAIN COMMANDS",
|
Category: "BLOCKCHAIN COMMANDS",
|
||||||
Description: `
|
Description: `
|
||||||
|
|
|
||||||
|
|
@ -72,6 +72,7 @@ var (
|
||||||
utils.EthashDatasetDirFlag,
|
utils.EthashDatasetDirFlag,
|
||||||
utils.EthashDatasetsInMemoryFlag,
|
utils.EthashDatasetsInMemoryFlag,
|
||||||
utils.EthashDatasetsOnDiskFlag,
|
utils.EthashDatasetsOnDiskFlag,
|
||||||
|
utils.TxPoolLocalsFlag,
|
||||||
utils.TxPoolNoLocalsFlag,
|
utils.TxPoolNoLocalsFlag,
|
||||||
utils.TxPoolJournalFlag,
|
utils.TxPoolJournalFlag,
|
||||||
utils.TxPoolRejournalFlag,
|
utils.TxPoolRejournalFlag,
|
||||||
|
|
@ -82,8 +83,6 @@ var (
|
||||||
utils.TxPoolAccountQueueFlag,
|
utils.TxPoolAccountQueueFlag,
|
||||||
utils.TxPoolGlobalQueueFlag,
|
utils.TxPoolGlobalQueueFlag,
|
||||||
utils.TxPoolLifetimeFlag,
|
utils.TxPoolLifetimeFlag,
|
||||||
utils.FastSyncFlag,
|
|
||||||
utils.LightModeFlag,
|
|
||||||
utils.SyncModeFlag,
|
utils.SyncModeFlag,
|
||||||
utils.GCModeFlag,
|
utils.GCModeFlag,
|
||||||
utils.LightServFlag,
|
utils.LightServFlag,
|
||||||
|
|
@ -96,12 +95,19 @@ var (
|
||||||
utils.ListenPortFlag,
|
utils.ListenPortFlag,
|
||||||
utils.MaxPeersFlag,
|
utils.MaxPeersFlag,
|
||||||
utils.MaxPendingPeersFlag,
|
utils.MaxPendingPeersFlag,
|
||||||
utils.EtherbaseFlag,
|
|
||||||
utils.GasPriceFlag,
|
|
||||||
utils.MiningEnabledFlag,
|
utils.MiningEnabledFlag,
|
||||||
utils.MinerThreadsFlag,
|
utils.MinerThreadsFlag,
|
||||||
|
utils.MinerLegacyThreadsFlag,
|
||||||
utils.MinerNotifyFlag,
|
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.NATFlag,
|
||||||
utils.NoDiscoverFlag,
|
utils.NoDiscoverFlag,
|
||||||
utils.DiscoveryV5Flag,
|
utils.DiscoveryV5Flag,
|
||||||
|
|
@ -122,7 +128,6 @@ var (
|
||||||
utils.NoCompactionFlag,
|
utils.NoCompactionFlag,
|
||||||
utils.GpoBlocksFlag,
|
utils.GpoBlocksFlag,
|
||||||
utils.GpoPercentileFlag,
|
utils.GpoPercentileFlag,
|
||||||
utils.ExtraDataFlag,
|
|
||||||
configFileFlag,
|
configFileFlag,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -325,25 +330,25 @@ func startNode(ctx *cli.Context, stack *node.Node) {
|
||||||
// Start auxiliary services if enabled
|
// Start auxiliary services if enabled
|
||||||
if ctx.GlobalBool(utils.MiningEnabledFlag.Name) || ctx.GlobalBool(utils.DeveloperFlag.Name) {
|
if ctx.GlobalBool(utils.MiningEnabledFlag.Name) || ctx.GlobalBool(utils.DeveloperFlag.Name) {
|
||||||
// Mining only makes sense if a full Ethereum node is running
|
// Mining only makes sense if a full Ethereum node is running
|
||||||
if ctx.GlobalBool(utils.LightModeFlag.Name) || ctx.GlobalString(utils.SyncModeFlag.Name) == "light" {
|
if ctx.GlobalString(utils.SyncModeFlag.Name) == "light" {
|
||||||
utils.Fatalf("Light clients do not support mining")
|
utils.Fatalf("Light clients do not support mining")
|
||||||
}
|
}
|
||||||
var ethereum *eth.Ethereum
|
var ethereum *eth.Ethereum
|
||||||
if err := stack.Service(ðereum); err != nil {
|
if err := stack.Service(ðereum); err != nil {
|
||||||
utils.Fatalf("Ethereum service not running: %v", err)
|
utils.Fatalf("Ethereum service not running: %v", err)
|
||||||
}
|
}
|
||||||
// Use a reduced number of threads if requested
|
|
||||||
if threads := ctx.GlobalInt(utils.MinerThreadsFlag.Name); threads > 0 {
|
|
||||||
type threaded interface {
|
|
||||||
SetThreads(threads int)
|
|
||||||
}
|
|
||||||
if th, ok := ethereum.Engine().(threaded); ok {
|
|
||||||
th.SetThreads(threads)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Set the gas price to the limits from the CLI and start mining
|
// Set the gas price to the limits from the CLI and start mining
|
||||||
ethereum.TxPool().SetGasPrice(utils.GlobalBig(ctx, utils.GasPriceFlag.Name))
|
gasprice := utils.GlobalBig(ctx, utils.MinerLegacyGasPriceFlag.Name)
|
||||||
if err := ethereum.StartMining(true); err != nil {
|
if ctx.IsSet(utils.MinerGasPriceFlag.Name) {
|
||||||
|
gasprice = utils.GlobalBig(ctx, utils.MinerGasPriceFlag.Name)
|
||||||
|
}
|
||||||
|
ethereum.TxPool().SetGasPrice(gasprice)
|
||||||
|
|
||||||
|
threads := ctx.GlobalInt(utils.MinerLegacyThreadsFlag.Name)
|
||||||
|
if ctx.GlobalIsSet(utils.MinerThreadsFlag.Name) {
|
||||||
|
threads = ctx.GlobalInt(utils.MinerThreadsFlag.Name)
|
||||||
|
}
|
||||||
|
if err := ethereum.StartMining(threads); err != nil {
|
||||||
utils.Fatalf("Failed to start mining: %v", err)
|
utils.Fatalf("Failed to start mining: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -114,6 +114,7 @@ var AppHelpFlagGroups = []flagGroup{
|
||||||
{
|
{
|
||||||
Name: "TRANSACTION POOL",
|
Name: "TRANSACTION POOL",
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
|
utils.TxPoolLocalsFlag,
|
||||||
utils.TxPoolNoLocalsFlag,
|
utils.TxPoolNoLocalsFlag,
|
||||||
utils.TxPoolJournalFlag,
|
utils.TxPoolJournalFlag,
|
||||||
utils.TxPoolRejournalFlag,
|
utils.TxPoolRejournalFlag,
|
||||||
|
|
@ -186,10 +187,11 @@ var AppHelpFlagGroups = []flagGroup{
|
||||||
utils.MiningEnabledFlag,
|
utils.MiningEnabledFlag,
|
||||||
utils.MinerThreadsFlag,
|
utils.MinerThreadsFlag,
|
||||||
utils.MinerNotifyFlag,
|
utils.MinerNotifyFlag,
|
||||||
utils.EtherbaseFlag,
|
utils.MinerGasPriceFlag,
|
||||||
utils.TargetGasLimitFlag,
|
utils.MinerGasTargetFlag,
|
||||||
utils.GasPriceFlag,
|
utils.MinerEtherbaseFlag,
|
||||||
utils.ExtraDataFlag,
|
utils.MinerExtraDataFlag,
|
||||||
|
utils.MinerRecommitIntervalFlag,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -231,8 +233,11 @@ var AppHelpFlagGroups = []flagGroup{
|
||||||
{
|
{
|
||||||
Name: "DEPRECATED",
|
Name: "DEPRECATED",
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
utils.FastSyncFlag,
|
utils.MinerLegacyThreadsFlag,
|
||||||
utils.LightModeFlag,
|
utils.MinerLegacyGasTargetFlag,
|
||||||
|
utils.MinerLegacyGasPriceFlag,
|
||||||
|
utils.MinerLegacyEtherbaseFlag,
|
||||||
|
utils.MinerLegacyExtraDataFlag,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,7 @@ ADD genesis.json /genesis.json
|
||||||
RUN \
|
RUN \
|
||||||
echo 'geth --cache 512 init /genesis.json' > geth.sh && \{{if .Unlock}}
|
echo 'geth --cache 512 init /genesis.json' > geth.sh && \{{if .Unlock}}
|
||||||
echo 'mkdir -p /root/.ethereum/keystore/ && cp /signer.json /root/.ethereum/keystore/' >> geth.sh && \{{end}}
|
echo 'mkdir -p /root/.ethereum/keystore/ && cp /signer.json /root/.ethereum/keystore/' >> geth.sh && \{{end}}
|
||||||
echo $'exec geth --networkid {{.NetworkID}} --cache 512 --port {{.Port}} --maxpeers {{.Peers}} {{.LightFlag}} --ethstats \'{{.Ethstats}}\' {{if .Bootnodes}}--bootnodes {{.Bootnodes}}{{end}} {{if .Etherbase}}--etherbase {{.Etherbase}} --mine --minerthreads 1{{end}} {{if .Unlock}}--unlock 0 --password /signer.pass --mine{{end}} --targetgaslimit {{.GasTarget}} --gasprice {{.GasPrice}}' >> geth.sh
|
echo $'exec geth --networkid {{.NetworkID}} --cache 512 --port {{.Port}} --maxpeers {{.Peers}} {{.LightFlag}} --ethstats \'{{.Ethstats}}\' {{if .Bootnodes}}--bootnodes {{.Bootnodes}}{{end}} {{if .Etherbase}}--miner.etherbase {{.Etherbase}} --mine --miner.threads 1{{end}} {{if .Unlock}}--unlock 0 --password /signer.pass --mine{{end}} --miner.gastarget {{.GasTarget}} --miner.gasprice {{.GasPrice}}' >> geth.sh
|
||||||
|
|
||||||
ENTRYPOINT ["/bin/sh", "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
|
// dial establishes an SSH connection to a remote node using the current user and
|
||||||
// the user's configured private RSA key. If that fails, password authentication
|
// the user's configured private RSA key. If that fails, password authentication
|
||||||
// is fallen back to. The caller may override the login user via user@server:port.
|
// is fallen back to. server can be a string like user:identity@server:port.
|
||||||
func dial(server string, pubkey []byte) (*sshClient, error) {
|
func dial(server string, pubkey []byte) (*sshClient, error) {
|
||||||
// Figure out a label for the server and a logger
|
// Figure out username, identity, hostname and port
|
||||||
label := server
|
hostname := ""
|
||||||
if strings.Contains(label, ":") {
|
hostport := server
|
||||||
label = label[:strings.Index(label, ":")]
|
username := ""
|
||||||
}
|
identity := "id_rsa" // default
|
||||||
login := ""
|
|
||||||
if strings.Contains(server, "@") {
|
if strings.Contains(server, "@") {
|
||||||
login = label[:strings.Index(label, "@")]
|
prefix := server[:strings.Index(server, "@")]
|
||||||
label = label[strings.Index(label, "@")+1:]
|
if strings.Contains(prefix, ":") {
|
||||||
server = server[strings.Index(server, "@")+1:]
|
username = prefix[:strings.Index(prefix, ":")]
|
||||||
|
identity = prefix[strings.Index(prefix, ":")+1:]
|
||||||
|
} else {
|
||||||
|
username = prefix
|
||||||
}
|
}
|
||||||
logger := log.New("server", label)
|
hostport = server[strings.Index(server, "@")+1:]
|
||||||
|
}
|
||||||
|
if strings.Contains(hostport, ":") {
|
||||||
|
hostname = hostport[:strings.Index(hostport, ":")]
|
||||||
|
} else {
|
||||||
|
hostname = hostport
|
||||||
|
hostport += ":22"
|
||||||
|
}
|
||||||
|
logger := log.New("server", server)
|
||||||
logger.Debug("Attempting to establish SSH connection")
|
logger.Debug("Attempting to establish SSH connection")
|
||||||
|
|
||||||
user, err := user.Current()
|
user, err := user.Current()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if login == "" {
|
if username == "" {
|
||||||
login = user.Username
|
username = user.Username
|
||||||
}
|
}
|
||||||
// Configure the supported authentication methods (private key and password)
|
// Configure the supported authentication methods (private key and password)
|
||||||
var auths []ssh.AuthMethod
|
var auths []ssh.AuthMethod
|
||||||
|
|
||||||
path := filepath.Join(user.HomeDir, ".ssh", "id_rsa")
|
path := filepath.Join(user.HomeDir, ".ssh", identity)
|
||||||
if buf, err := ioutil.ReadFile(path); err != nil {
|
if buf, err := ioutil.ReadFile(path); err != nil {
|
||||||
log.Warn("No SSH key, falling back to passwords", "path", path, "err", err)
|
log.Warn("No SSH key, falling back to passwords", "path", path, "err", err)
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -94,14 +105,14 @@ func dial(server string, pubkey []byte) (*sshClient, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
auths = append(auths, ssh.PasswordCallback(func() (string, error) {
|
auths = append(auths, ssh.PasswordCallback(func() (string, error) {
|
||||||
fmt.Printf("What's the login password for %s at %s? (won't be echoed)\n> ", login, server)
|
fmt.Printf("What's the login password for %s at %s? (won't be echoed)\n> ", username, server)
|
||||||
blob, err := terminal.ReadPassword(int(os.Stdin.Fd()))
|
blob, err := terminal.ReadPassword(int(os.Stdin.Fd()))
|
||||||
|
|
||||||
fmt.Println()
|
fmt.Println()
|
||||||
return string(blob), err
|
return string(blob), err
|
||||||
}))
|
}))
|
||||||
// Resolve the IP address of the remote server
|
// Resolve the IP address of the remote server
|
||||||
addr, err := net.LookupHost(label)
|
addr, err := net.LookupHost(hostname)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -109,10 +120,7 @@ func dial(server string, pubkey []byte) (*sshClient, error) {
|
||||||
return nil, errors.New("no IPs associated with domain")
|
return nil, errors.New("no IPs associated with domain")
|
||||||
}
|
}
|
||||||
// Try to dial in to the remote server
|
// Try to dial in to the remote server
|
||||||
logger.Trace("Dialing remote SSH server", "user", login)
|
logger.Trace("Dialing remote SSH server", "user", username)
|
||||||
if !strings.Contains(server, ":") {
|
|
||||||
server += ":22"
|
|
||||||
}
|
|
||||||
keycheck := func(hostname string, remote net.Addr, key ssh.PublicKey) error {
|
keycheck := func(hostname string, remote net.Addr, key ssh.PublicKey) error {
|
||||||
// If no public key is known for SSH, ask the user to confirm
|
// If no public key is known for SSH, ask the user to confirm
|
||||||
if pubkey == nil {
|
if pubkey == nil {
|
||||||
|
|
@ -139,13 +147,13 @@ func dial(server string, pubkey []byte) (*sshClient, error) {
|
||||||
// We have a mismatch, forbid connecting
|
// We have a mismatch, forbid connecting
|
||||||
return errors.New("ssh key mismatch, readd the machine to update")
|
return errors.New("ssh key mismatch, readd the machine to update")
|
||||||
}
|
}
|
||||||
client, err := ssh.Dial("tcp", server, &ssh.ClientConfig{User: login, Auth: auths, HostKeyCallback: keycheck})
|
client, err := ssh.Dial("tcp", hostport, &ssh.ClientConfig{User: username, Auth: auths, HostKeyCallback: keycheck})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
// Connection established, return our utility wrapper
|
// Connection established, return our utility wrapper
|
||||||
c := &sshClient{
|
c := &sshClient{
|
||||||
server: label,
|
server: hostname,
|
||||||
address: addr[0],
|
address: addr[0],
|
||||||
pubkey: pubkey,
|
pubkey: pubkey,
|
||||||
client: client,
|
client: client,
|
||||||
|
|
|
||||||
|
|
@ -82,7 +82,6 @@ func (w *wizard) gatherStats(server string, pubkey []byte, client *sshClient) *s
|
||||||
logger.Info("Starting remote server health-check")
|
logger.Info("Starting remote server health-check")
|
||||||
|
|
||||||
stat := &serverStat{
|
stat := &serverStat{
|
||||||
address: client.address,
|
|
||||||
services: make(map[string]map[string]string),
|
services: make(map[string]map[string]string),
|
||||||
}
|
}
|
||||||
if client == nil {
|
if client == nil {
|
||||||
|
|
@ -94,6 +93,8 @@ func (w *wizard) gatherStats(server string, pubkey []byte, client *sshClient) *s
|
||||||
}
|
}
|
||||||
client = conn
|
client = conn
|
||||||
}
|
}
|
||||||
|
stat.address = client.address
|
||||||
|
|
||||||
// Client connected one way or another, run health-checks
|
// Client connected one way or another, run health-checks
|
||||||
logger.Debug("Checking for nginx availability")
|
logger.Debug("Checking for nginx availability")
|
||||||
if infos, err := checkNginx(client, w.network); err != nil {
|
if infos, err := checkNginx(client, w.network); err != nil {
|
||||||
|
|
@ -214,6 +215,9 @@ func (stats serverStats) render() {
|
||||||
if len(stat.address) > len(separator[1]) {
|
if len(stat.address) > len(separator[1]) {
|
||||||
separator[1] = strings.Repeat("-", len(stat.address))
|
separator[1] = strings.Repeat("-", len(stat.address))
|
||||||
}
|
}
|
||||||
|
if len(stat.failure) > len(separator[1]) {
|
||||||
|
separator[1] = strings.Repeat("-", len(stat.failure))
|
||||||
|
}
|
||||||
for service, configs := range stat.services {
|
for service, configs := range stat.services {
|
||||||
if len(service) > len(separator[2]) {
|
if len(service) > len(separator[2]) {
|
||||||
separator[2] = strings.Repeat("-", len(service))
|
separator[2] = strings.Repeat("-", len(service))
|
||||||
|
|
@ -250,8 +254,12 @@ func (stats serverStats) render() {
|
||||||
sort.Strings(services)
|
sort.Strings(services)
|
||||||
|
|
||||||
if len(services) == 0 {
|
if len(services) == 0 {
|
||||||
|
if stats[server].failure != "" {
|
||||||
|
table.Append([]string{server, stats[server].failure, "", "", ""})
|
||||||
|
} else {
|
||||||
table.Append([]string{server, stats[server].address, "", "", ""})
|
table.Append([]string{server, stats[server].address, "", "", ""})
|
||||||
}
|
}
|
||||||
|
}
|
||||||
for j, service := range services {
|
for j, service := range services {
|
||||||
// Add an empty line between all services
|
// Add an empty line between all services
|
||||||
if j > 0 {
|
if j > 0 {
|
||||||
|
|
|
||||||
|
|
@ -62,14 +62,14 @@ func (w *wizard) manageServers() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// makeServer reads a single line from stdin and interprets it as a hostname to
|
// makeServer reads a single line from stdin and interprets it as
|
||||||
// connect to. It tries to establish a new SSH session and also executing some
|
// username:identity@hostname to connect to. It tries to establish a
|
||||||
// baseline validations.
|
// new SSH session and also executing some baseline validations.
|
||||||
//
|
//
|
||||||
// If connection succeeds, the server is added to the wizards configs!
|
// If connection succeeds, the server is added to the wizards configs!
|
||||||
func (w *wizard) makeServer() string {
|
func (w *wizard) makeServer() string {
|
||||||
fmt.Println()
|
fmt.Println()
|
||||||
fmt.Println("Please enter remote server's address:")
|
fmt.Println("What is the remote server's address ([username[:identity]@]hostname[:port])?")
|
||||||
|
|
||||||
// Read and dial the server to ensure docker is present
|
// Read and dial the server to ensure docker is present
|
||||||
input := w.readString()
|
input := w.readString()
|
||||||
|
|
|
||||||
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_PATH = "SWARM_STORE_PATH"
|
||||||
SWARM_ENV_STORE_CAPACITY = "SWARM_STORE_CAPACITY"
|
SWARM_ENV_STORE_CAPACITY = "SWARM_STORE_CAPACITY"
|
||||||
SWARM_ENV_STORE_CACHE_CAPACITY = "SWARM_STORE_CACHE_CAPACITY"
|
SWARM_ENV_STORE_CACHE_CAPACITY = "SWARM_STORE_CACHE_CAPACITY"
|
||||||
|
SWARM_ACCESS_PASSWORD = "SWARM_ACCESS_PASSWORD"
|
||||||
GETH_ENV_DATADIR = "GETH_DATADIR"
|
GETH_ENV_DATADIR = "GETH_DATADIR"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -232,10 +233,6 @@ func cmdLineOverride(currentConfig *bzzapi.Config, ctx *cli.Context) *bzzapi.Con
|
||||||
currentConfig.Cors = cors
|
currentConfig.Cors = cors
|
||||||
}
|
}
|
||||||
|
|
||||||
if ctx.GlobalIsSet(utils.BootnodesFlag.Name) {
|
|
||||||
currentConfig.BootNodes = ctx.GlobalString(utils.BootnodesFlag.Name)
|
|
||||||
}
|
|
||||||
|
|
||||||
if storePath := ctx.GlobalString(SwarmStorePath.Name); storePath != "" {
|
if storePath := ctx.GlobalString(SwarmStorePath.Name); storePath != "" {
|
||||||
currentConfig.LocalStoreParams.ChunkDbPath = storePath
|
currentConfig.LocalStoreParams.ChunkDbPath = storePath
|
||||||
}
|
}
|
||||||
|
|
@ -333,10 +330,6 @@ func envVarsOverride(currentConfig *bzzapi.Config) (config *bzzapi.Config) {
|
||||||
currentConfig.Cors = cors
|
currentConfig.Cors = cors
|
||||||
}
|
}
|
||||||
|
|
||||||
if bootnodes := os.Getenv(SWARM_ENV_BOOTNODES); bootnodes != "" {
|
|
||||||
currentConfig.BootNodes = bootnodes
|
|
||||||
}
|
|
||||||
|
|
||||||
return currentConfig
|
return currentConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -68,18 +68,36 @@ func download(ctx *cli.Context) {
|
||||||
utils.Fatalf("could not parse uri argument: %v", err)
|
utils.Fatalf("could not parse uri argument: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dl := func(credentials string) error {
|
||||||
// assume behaviour according to --recursive switch
|
// assume behaviour according to --recursive switch
|
||||||
if isRecursive {
|
if isRecursive {
|
||||||
if err := client.DownloadDirectory(uri.Addr, uri.Path, dest); err != nil {
|
if err := client.DownloadDirectory(uri.Addr, uri.Path, dest, credentials); err != nil {
|
||||||
utils.Fatalf("encoutered an error while downloading directory: %v", err)
|
if err == swarm.ErrUnauthorized {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return fmt.Errorf("directory %s: %v", uri.Path, err)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// we are downloading a file
|
// we are downloading a file
|
||||||
log.Debug(fmt.Sprintf("downloading file/path from a manifest. hash: %s, path:%s", uri.Addr, uri.Path))
|
log.Debug("downloading file/path from a manifest", "uri.Addr", uri.Addr, "uri.Path", uri.Path)
|
||||||
|
|
||||||
err := client.DownloadFile(uri.Addr, uri.Path, dest)
|
err := client.DownloadFile(uri.Addr, uri.Path, dest, credentials)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.Fatalf("could not download %s from given address: %s. error: %v", uri.Path, uri.Addr, err)
|
if err == swarm.ErrUnauthorized {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
return fmt.Errorf("file %s from address: %s: %v", uri.Path, uri.Addr, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if passwords := makePasswordList(ctx); passwords != nil {
|
||||||
|
password := getPassPhrase(fmt.Sprintf("Downloading %s is restricted", uri), 0, passwords)
|
||||||
|
err = dl(password)
|
||||||
|
} else {
|
||||||
|
err = dl("")
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
utils.Fatalf("download: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,7 @@ func list(ctx *cli.Context) {
|
||||||
|
|
||||||
bzzapi := strings.TrimRight(ctx.GlobalString(SwarmApiFlag.Name), "/")
|
bzzapi := strings.TrimRight(ctx.GlobalString(SwarmApiFlag.Name), "/")
|
||||||
client := swarm.NewClient(bzzapi)
|
client := swarm.NewClient(bzzapi)
|
||||||
list, err := client.List(manifest, prefix)
|
list, err := client.List(manifest, prefix, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.Fatalf("Failed to generate file and directory list: %s", err)
|
utils.Fatalf("Failed to generate file and directory list: %s", err)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,6 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/internal/debug"
|
"github.com/ethereum/go-ethereum/internal/debug"
|
||||||
"github.com/ethereum/go-ethereum/log"
|
"github.com/ethereum/go-ethereum/log"
|
||||||
"github.com/ethereum/go-ethereum/node"
|
"github.com/ethereum/go-ethereum/node"
|
||||||
"github.com/ethereum/go-ethereum/p2p"
|
|
||||||
"github.com/ethereum/go-ethereum/p2p/discover"
|
"github.com/ethereum/go-ethereum/p2p/discover"
|
||||||
"github.com/ethereum/go-ethereum/swarm"
|
"github.com/ethereum/go-ethereum/swarm"
|
||||||
bzzapi "github.com/ethereum/go-ethereum/swarm/api"
|
bzzapi "github.com/ethereum/go-ethereum/swarm/api"
|
||||||
|
|
@ -68,13 +67,6 @@ OPTIONS:
|
||||||
|
|
||||||
var (
|
var (
|
||||||
gitCommit string // Git SHA1 commit hash of the release (set via linker flags)
|
gitCommit string // Git SHA1 commit hash of the release (set via linker flags)
|
||||||
testbetBootNodes = []string{
|
|
||||||
"enode://ec8ae764f7cb0417bdfb009b9d0f18ab3818a3a4e8e7c67dd5f18971a93510a2e6f43cd0b69a27e439a9629457ea804104f37c85e41eed057d3faabbf7744cdf@13.74.157.139:30429",
|
|
||||||
"enode://c2e1fceb3bf3be19dff71eec6cccf19f2dbf7567ee017d130240c670be8594bc9163353ca55dd8df7a4f161dd94b36d0615c17418b5a3cdcbb4e9d99dfa4de37@13.74.157.139:30430",
|
|
||||||
"enode://fe29b82319b734ce1ec68b84657d57145fee237387e63273989d354486731e59f78858e452ef800a020559da22dcca759536e6aa5517c53930d29ce0b1029286@13.74.157.139:30431",
|
|
||||||
"enode://1d7187e7bde45cf0bee489ce9852dd6d1a0d9aa67a33a6b8e6db8a4fbc6fcfa6f0f1a5419343671521b863b187d1c73bad3603bae66421d157ffef357669ddb8@13.74.157.139:30432",
|
|
||||||
"enode://0e4cba800f7b1ee73673afa6a4acead4018f0149d2e3216be3f133318fd165b324cd71b81fbe1e80deac8dbf56e57a49db7be67f8b9bc81bd2b7ee496434fb5d@13.74.157.139:30433",
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|
@ -155,6 +147,14 @@ var (
|
||||||
Name: "defaultpath",
|
Name: "defaultpath",
|
||||||
Usage: "path to file served for empty url path (none)",
|
Usage: "path to file served for empty url path (none)",
|
||||||
}
|
}
|
||||||
|
SwarmAccessGrantKeyFlag = cli.StringFlag{
|
||||||
|
Name: "grant-key",
|
||||||
|
Usage: "grants a given public key access to an ACT",
|
||||||
|
}
|
||||||
|
SwarmAccessGrantKeysFlag = cli.StringFlag{
|
||||||
|
Name: "grant-keys",
|
||||||
|
Usage: "grants a given list of public keys in the following file (separated by line breaks) access to an ACT",
|
||||||
|
}
|
||||||
SwarmUpFromStdinFlag = cli.BoolFlag{
|
SwarmUpFromStdinFlag = cli.BoolFlag{
|
||||||
Name: "stdin",
|
Name: "stdin",
|
||||||
Usage: "reads data to be uploaded from stdin",
|
Usage: "reads data to be uploaded from stdin",
|
||||||
|
|
@ -167,6 +167,15 @@ var (
|
||||||
Name: "encrypt",
|
Name: "encrypt",
|
||||||
Usage: "use encrypted upload",
|
Usage: "use encrypted upload",
|
||||||
}
|
}
|
||||||
|
SwarmAccessPasswordFlag = cli.StringFlag{
|
||||||
|
Name: "password",
|
||||||
|
Usage: "Password",
|
||||||
|
EnvVar: SWARM_ACCESS_PASSWORD,
|
||||||
|
}
|
||||||
|
SwarmDryRunFlag = cli.BoolFlag{
|
||||||
|
Name: "dry-run",
|
||||||
|
Usage: "dry-run",
|
||||||
|
}
|
||||||
CorsStringFlag = cli.StringFlag{
|
CorsStringFlag = cli.StringFlag{
|
||||||
Name: "corsdomain",
|
Name: "corsdomain",
|
||||||
Usage: "Domain on which to send Access-Control-Allow-Origin header (multiple domains can be supplied separated by a ',')",
|
Usage: "Domain on which to send Access-Control-Allow-Origin header (multiple domains can be supplied separated by a ',')",
|
||||||
|
|
@ -252,6 +261,61 @@ func init() {
|
||||||
Flags: []cli.Flag{SwarmEncryptedFlag},
|
Flags: []cli.Flag{SwarmEncryptedFlag},
|
||||||
Description: "uploads a file or directory to swarm using the HTTP API and prints the root hash",
|
Description: "uploads a file or directory to swarm using the HTTP API and prints the root hash",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
CustomHelpTemplate: helpTemplate,
|
||||||
|
Name: "access",
|
||||||
|
Usage: "encrypts a reference and embeds it into a root manifest",
|
||||||
|
ArgsUsage: "<ref>",
|
||||||
|
Description: "encrypts a reference and embeds it into a root manifest",
|
||||||
|
Subcommands: []cli.Command{
|
||||||
|
{
|
||||||
|
CustomHelpTemplate: helpTemplate,
|
||||||
|
Name: "new",
|
||||||
|
Usage: "encrypts a reference and embeds it into a root manifest",
|
||||||
|
ArgsUsage: "<ref>",
|
||||||
|
Description: "encrypts a reference and embeds it into a root access manifest and prints the resulting manifest",
|
||||||
|
Subcommands: []cli.Command{
|
||||||
|
{
|
||||||
|
Action: accessNewPass,
|
||||||
|
CustomHelpTemplate: helpTemplate,
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
utils.PasswordFileFlag,
|
||||||
|
SwarmDryRunFlag,
|
||||||
|
},
|
||||||
|
Name: "pass",
|
||||||
|
Usage: "encrypts a reference with a password and embeds it into a root manifest",
|
||||||
|
ArgsUsage: "<ref>",
|
||||||
|
Description: "encrypts a reference and embeds it into a root access manifest and prints the resulting manifest",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Action: accessNewPK,
|
||||||
|
CustomHelpTemplate: helpTemplate,
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
utils.PasswordFileFlag,
|
||||||
|
SwarmDryRunFlag,
|
||||||
|
SwarmAccessGrantKeyFlag,
|
||||||
|
},
|
||||||
|
Name: "pk",
|
||||||
|
Usage: "encrypts a reference with the node's private key and a given grantee's public key and embeds it into a root manifest",
|
||||||
|
ArgsUsage: "<ref>",
|
||||||
|
Description: "encrypts a reference and embeds it into a root access manifest and prints the resulting manifest",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Action: accessNewACT,
|
||||||
|
CustomHelpTemplate: helpTemplate,
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
SwarmAccessGrantKeysFlag,
|
||||||
|
SwarmDryRunFlag,
|
||||||
|
},
|
||||||
|
Name: "act",
|
||||||
|
Usage: "encrypts a reference with the node's private key and a given grantee's public key and embeds it into a root manifest",
|
||||||
|
ArgsUsage: "<ref>",
|
||||||
|
Description: "encrypts a reference and embeds it into a root access manifest and prints the resulting manifest",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
CustomHelpTemplate: helpTemplate,
|
CustomHelpTemplate: helpTemplate,
|
||||||
Name: "resource",
|
Name: "resource",
|
||||||
|
|
@ -306,14 +370,11 @@ func init() {
|
||||||
{
|
{
|
||||||
Action: download,
|
Action: download,
|
||||||
Name: "down",
|
Name: "down",
|
||||||
Flags: []cli.Flag{SwarmRecursiveFlag},
|
Flags: []cli.Flag{SwarmRecursiveFlag, SwarmAccessPasswordFlag},
|
||||||
Usage: "downloads a swarm manifest or a file inside a manifest",
|
Usage: "downloads a swarm manifest or a file inside a manifest",
|
||||||
ArgsUsage: " <uri> [<dir>]",
|
ArgsUsage: " <uri> [<dir>]",
|
||||||
Description: `
|
Description: `Downloads a swarm bzz uri to the given dir. When no dir is provided, working directory is assumed. --recursive flag is expected when downloading a manifest with multiple entries.`,
|
||||||
Downloads a swarm bzz uri to the given dir. When no dir is provided, working directory is assumed. --recursive flag is expected when downloading a manifest with multiple entries.
|
|
||||||
`,
|
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
Name: "manifest",
|
Name: "manifest",
|
||||||
CustomHelpTemplate: helpTemplate,
|
CustomHelpTemplate: helpTemplate,
|
||||||
|
|
@ -413,16 +474,14 @@ pv(1) tool to get a progress bar:
|
||||||
Name: "import",
|
Name: "import",
|
||||||
Usage: "import chunks from a tar archive into a local chunk database (use - to read from stdin)",
|
Usage: "import chunks from a tar archive into a local chunk database (use - to read from stdin)",
|
||||||
ArgsUsage: "<chunkdb> <file>",
|
ArgsUsage: "<chunkdb> <file>",
|
||||||
Description: `
|
Description: `Import chunks from a tar archive into a local chunk database (use - to read from stdin).
|
||||||
Import chunks from a tar archive into a local chunk database (use - to read from stdin).
|
|
||||||
|
|
||||||
swarm db import ~/.ethereum/swarm/bzz-KEY/chunks chunks.tar
|
swarm db import ~/.ethereum/swarm/bzz-KEY/chunks chunks.tar
|
||||||
|
|
||||||
The import may be quite large, consider piping the input through the Unix
|
The import may be quite large, consider piping the input through the Unix
|
||||||
pv(1) tool to get a progress bar:
|
pv(1) tool to get a progress bar:
|
||||||
|
|
||||||
pv chunks.tar | swarm db import ~/.ethereum/swarm/bzz-KEY/chunks -
|
pv chunks.tar | swarm db import ~/.ethereum/swarm/bzz-KEY/chunks -`,
|
||||||
`,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Action: dbClean,
|
Action: dbClean,
|
||||||
|
|
@ -535,6 +594,7 @@ func version(ctx *cli.Context) error {
|
||||||
func bzzd(ctx *cli.Context) error {
|
func bzzd(ctx *cli.Context) error {
|
||||||
//build a valid bzzapi.Config from all available sources:
|
//build a valid bzzapi.Config from all available sources:
|
||||||
//default config, file config, command line and env vars
|
//default config, file config, command line and env vars
|
||||||
|
|
||||||
bzzconfig, err := buildConfig(ctx)
|
bzzconfig, err := buildConfig(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.Fatalf("unable to configure swarm: %v", err)
|
utils.Fatalf("unable to configure swarm: %v", err)
|
||||||
|
|
@ -551,12 +611,16 @@ func bzzd(ctx *cli.Context) error {
|
||||||
if _, err := os.Stat(bzzconfig.Path); err == nil {
|
if _, err := os.Stat(bzzconfig.Path); err == nil {
|
||||||
cfg.DataDir = bzzconfig.Path
|
cfg.DataDir = bzzconfig.Path
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//optionally set the bootnodes before configuring the node
|
||||||
|
setSwarmBootstrapNodes(ctx, &cfg)
|
||||||
//setup the ethereum node
|
//setup the ethereum node
|
||||||
utils.SetNodeConfig(ctx, &cfg)
|
utils.SetNodeConfig(ctx, &cfg)
|
||||||
stack, err := node.New(&cfg)
|
stack, err := node.New(&cfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.Fatalf("can't create node: %v", err)
|
utils.Fatalf("can't create node: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
//a few steps need to be done after the config phase is completed,
|
//a few steps need to be done after the config phase is completed,
|
||||||
//due to overriding behavior
|
//due to overriding behavior
|
||||||
initSwarmNode(bzzconfig, stack, ctx)
|
initSwarmNode(bzzconfig, stack, ctx)
|
||||||
|
|
@ -574,16 +638,6 @@ func bzzd(ctx *cli.Context) error {
|
||||||
stack.Stop()
|
stack.Stop()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
// Add bootnodes as initial peers.
|
|
||||||
if bzzconfig.BootNodes != "" {
|
|
||||||
bootnodes := strings.Split(bzzconfig.BootNodes, ",")
|
|
||||||
injectBootnodes(stack.Server(), bootnodes)
|
|
||||||
} else {
|
|
||||||
if bzzconfig.NetworkID == 3 {
|
|
||||||
injectBootnodes(stack.Server(), testbetBootNodes)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
stack.Wait()
|
stack.Wait()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
@ -691,17 +745,6 @@ func getPassPhrase(prompt string, i int, passwords []string) string {
|
||||||
return password
|
return password
|
||||||
}
|
}
|
||||||
|
|
||||||
func injectBootnodes(srv *p2p.Server, nodes []string) {
|
|
||||||
for _, url := range nodes {
|
|
||||||
n, err := discover.ParseNode(url)
|
|
||||||
if err != nil {
|
|
||||||
log.Error("Invalid swarm bootnode", "err", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
srv.AddPeer(n)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// addDefaultHelpSubcommand scans through defined CLI commands and adds
|
// addDefaultHelpSubcommand scans through defined CLI commands and adds
|
||||||
// a basic help subcommand to each
|
// a basic help subcommand to each
|
||||||
// if a help command is already defined, it will take precedence over the default.
|
// if a help command is already defined, it will take precedence over the default.
|
||||||
|
|
@ -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 (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"crypto/ecdsa"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
@ -181,6 +183,7 @@ type testNode struct {
|
||||||
Enode string
|
Enode string
|
||||||
Dir string
|
Dir string
|
||||||
IpcPath string
|
IpcPath string
|
||||||
|
PrivateKey *ecdsa.PrivateKey
|
||||||
Client *rpc.Client
|
Client *rpc.Client
|
||||||
Cmd *cmdtest.TestCmd
|
Cmd *cmdtest.TestCmd
|
||||||
}
|
}
|
||||||
|
|
@ -289,7 +292,11 @@ func existingTestNode(t *testing.T, dir string, bzzaccount string) *testNode {
|
||||||
func newTestNode(t *testing.T, dir string) *testNode {
|
func newTestNode(t *testing.T, dir string) *testNode {
|
||||||
|
|
||||||
conf, account := getTestAccount(t, dir)
|
conf, account := getTestAccount(t, dir)
|
||||||
node := &testNode{Dir: dir}
|
ks := keystore.NewKeyStore(path.Join(dir, "keystore"), 1<<18, 1)
|
||||||
|
|
||||||
|
pk := decryptStoreAccount(ks, account.Address.Hex(), []string{testPassphrase})
|
||||||
|
|
||||||
|
node := &testNode{Dir: dir, PrivateKey: pk}
|
||||||
|
|
||||||
// assign ports
|
// assign ports
|
||||||
ports, err := getAvailableTCPPorts(2)
|
ports, err := getAvailableTCPPorts(2)
|
||||||
|
|
|
||||||
|
|
@ -157,14 +157,6 @@ var (
|
||||||
Usage: "Document Root for HTTPClient file scheme",
|
Usage: "Document Root for HTTPClient file scheme",
|
||||||
Value: DirectoryString{homeDir()},
|
Value: DirectoryString{homeDir()},
|
||||||
}
|
}
|
||||||
FastSyncFlag = cli.BoolFlag{
|
|
||||||
Name: "fast",
|
|
||||||
Usage: "Enable fast syncing through state downloads (replaced by --syncmode)",
|
|
||||||
}
|
|
||||||
LightModeFlag = cli.BoolFlag{
|
|
||||||
Name: "light",
|
|
||||||
Usage: "Enable light client mode (replaced by --syncmode)",
|
|
||||||
}
|
|
||||||
defaultSyncMode = eth.DefaultConfig.SyncMode
|
defaultSyncMode = eth.DefaultConfig.SyncMode
|
||||||
SyncModeFlag = TextMarshalerFlag{
|
SyncModeFlag = TextMarshalerFlag{
|
||||||
Name: "syncmode",
|
Name: "syncmode",
|
||||||
|
|
@ -241,6 +233,10 @@ var (
|
||||||
Value: eth.DefaultConfig.Ethash.DatasetsOnDisk,
|
Value: eth.DefaultConfig.Ethash.DatasetsOnDisk,
|
||||||
}
|
}
|
||||||
// Transaction pool settings
|
// Transaction pool settings
|
||||||
|
TxPoolLocalsFlag = cli.StringFlag{
|
||||||
|
Name: "txpool.locals",
|
||||||
|
Usage: "Comma separated accounts to treat as locals (no flush, priority inclusion)",
|
||||||
|
}
|
||||||
TxPoolNoLocalsFlag = cli.BoolFlag{
|
TxPoolNoLocalsFlag = cli.BoolFlag{
|
||||||
Name: "txpool.nolocals",
|
Name: "txpool.nolocals",
|
||||||
Usage: "Disables price exemptions for locally submitted transactions",
|
Usage: "Disables price exemptions for locally submitted transactions",
|
||||||
|
|
@ -321,29 +317,58 @@ var (
|
||||||
Usage: "Number of CPU threads to use for mining",
|
Usage: "Number of CPU threads to use for mining",
|
||||||
Value: 0,
|
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{
|
MinerNotifyFlag = cli.StringFlag{
|
||||||
Name: "miner.notify",
|
Name: "miner.notify",
|
||||||
Usage: "Comma separated HTTP URL list to notify of new work packages",
|
Usage: "Comma separated HTTP URL list to notify of new work packages",
|
||||||
}
|
}
|
||||||
TargetGasLimitFlag = cli.Uint64Flag{
|
MinerGasTargetFlag = cli.Uint64Flag{
|
||||||
Name: "targetgaslimit",
|
Name: "miner.gastarget",
|
||||||
Usage: "Target gas limit sets the artificial target gas floor for the blocks to mine",
|
Usage: "Target gas floor for mined blocks",
|
||||||
Value: params.GenesisGasLimit,
|
Value: params.GenesisGasLimit,
|
||||||
}
|
}
|
||||||
EtherbaseFlag = cli.StringFlag{
|
MinerLegacyGasTargetFlag = cli.Uint64Flag{
|
||||||
Name: "etherbase",
|
Name: "targetgaslimit",
|
||||||
Usage: "Public address for block mining rewards (default = first account created)",
|
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",
|
Value: "0",
|
||||||
}
|
}
|
||||||
GasPriceFlag = BigFlag{
|
MinerLegacyEtherbaseFlag = cli.StringFlag{
|
||||||
Name: "gasprice",
|
Name: "etherbase",
|
||||||
Usage: "Minimal gas price to accept for mining a transactions",
|
Usage: "Public address for block mining rewards (default = first account, deprecated, use --miner.etherbase)",
|
||||||
Value: eth.DefaultConfig.GasPrice,
|
Value: "0",
|
||||||
}
|
}
|
||||||
ExtraDataFlag = cli.StringFlag{
|
MinerExtraDataFlag = cli.StringFlag{
|
||||||
Name: "extradata",
|
Name: "miner.extradata",
|
||||||
Usage: "Block extra data set by the miner (default = client version)",
|
Usage: "Block extra data set by the miner (default = client version)",
|
||||||
}
|
}
|
||||||
|
MinerLegacyExtraDataFlag = cli.StringFlag{
|
||||||
|
Name: "extradata",
|
||||||
|
Usage: "Block extra data set by the miner (default = client version, deprecated, use --miner.extradata)",
|
||||||
|
}
|
||||||
|
MinerRecommitIntervalFlag = cli.DurationFlag{
|
||||||
|
Name: "miner.recommit",
|
||||||
|
Usage: "Time interval to recreate the block being mined.",
|
||||||
|
Value: eth.DefaultConfig.MinerRecommit,
|
||||||
|
}
|
||||||
// Account settings
|
// Account settings
|
||||||
UnlockedAccountFlag = cli.StringFlag{
|
UnlockedAccountFlag = cli.StringFlag{
|
||||||
Name: "unlock",
|
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
|
// setEtherbase retrieves the etherbase either from the directly specified
|
||||||
// command line flags or from the keystore if CLI indexed.
|
// command line flags or from the keystore if CLI indexed.
|
||||||
func setEtherbase(ctx *cli.Context, ks *keystore.KeyStore, cfg *eth.Config) {
|
func setEtherbase(ctx *cli.Context, ks *keystore.KeyStore, cfg *eth.Config) {
|
||||||
if ctx.GlobalIsSet(EtherbaseFlag.Name) {
|
// Extract the current etherbase, new flag overriding legacy one
|
||||||
account, err := MakeAddress(ks, ctx.GlobalString(EtherbaseFlag.Name))
|
var etherbase string
|
||||||
|
if ctx.GlobalIsSet(MinerLegacyEtherbaseFlag.Name) {
|
||||||
|
etherbase = ctx.GlobalString(MinerLegacyEtherbaseFlag.Name)
|
||||||
|
}
|
||||||
|
if ctx.GlobalIsSet(MinerEtherbaseFlag.Name) {
|
||||||
|
etherbase = ctx.GlobalString(MinerEtherbaseFlag.Name)
|
||||||
|
}
|
||||||
|
// Convert the etherbase into an address and configure it
|
||||||
|
if etherbase != "" {
|
||||||
|
account, err := MakeAddress(ks, etherbase)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
Fatalf("Option %q: %v", EtherbaseFlag.Name, err)
|
Fatalf("Invalid miner etherbase: %v", err)
|
||||||
}
|
}
|
||||||
cfg.Etherbase = account.Address
|
cfg.Etherbase = account.Address
|
||||||
}
|
}
|
||||||
|
|
@ -851,7 +885,7 @@ func SetP2PConfig(ctx *cli.Context, cfg *p2p.Config) {
|
||||||
setBootstrapNodes(ctx, cfg)
|
setBootstrapNodes(ctx, cfg)
|
||||||
setBootstrapNodesV5(ctx, cfg)
|
setBootstrapNodesV5(ctx, cfg)
|
||||||
|
|
||||||
lightClient := ctx.GlobalBool(LightModeFlag.Name) || ctx.GlobalString(SyncModeFlag.Name) == "light"
|
lightClient := ctx.GlobalString(SyncModeFlag.Name) == "light"
|
||||||
lightServer := ctx.GlobalInt(LightServFlag.Name) != 0
|
lightServer := ctx.GlobalInt(LightServFlag.Name) != 0
|
||||||
lightPeers := ctx.GlobalInt(LightPeersFlag.Name)
|
lightPeers := ctx.GlobalInt(LightPeersFlag.Name)
|
||||||
|
|
||||||
|
|
@ -951,6 +985,16 @@ func setGPO(ctx *cli.Context, cfg *gasprice.Config) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func setTxPool(ctx *cli.Context, cfg *core.TxPoolConfig) {
|
func setTxPool(ctx *cli.Context, cfg *core.TxPoolConfig) {
|
||||||
|
if ctx.GlobalIsSet(TxPoolLocalsFlag.Name) {
|
||||||
|
locals := strings.Split(ctx.GlobalString(TxPoolLocalsFlag.Name), ",")
|
||||||
|
for _, account := range locals {
|
||||||
|
if trimmed := strings.TrimSpace(account); !common.IsHexAddress(trimmed) {
|
||||||
|
Fatalf("Invalid account in --txpool.locals: %s", trimmed)
|
||||||
|
} else {
|
||||||
|
cfg.Locals = append(cfg.Locals, common.HexToAddress(account))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
if ctx.GlobalIsSet(TxPoolNoLocalsFlag.Name) {
|
if ctx.GlobalIsSet(TxPoolNoLocalsFlag.Name) {
|
||||||
cfg.NoLocals = ctx.GlobalBool(TxPoolNoLocalsFlag.Name)
|
cfg.NoLocals = ctx.GlobalBool(TxPoolNoLocalsFlag.Name)
|
||||||
}
|
}
|
||||||
|
|
@ -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) {
|
func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *eth.Config) {
|
||||||
// Avoid conflicting network flags
|
// Avoid conflicting network flags
|
||||||
checkExclusive(ctx, DeveloperFlag, TestnetFlag, RinkebyFlag)
|
checkExclusive(ctx, DeveloperFlag, TestnetFlag, RinkebyFlag)
|
||||||
checkExclusive(ctx, FastSyncFlag, LightModeFlag, SyncModeFlag)
|
|
||||||
checkExclusive(ctx, LightServFlag, LightModeFlag)
|
|
||||||
checkExclusive(ctx, LightServFlag, SyncModeFlag, "light")
|
checkExclusive(ctx, LightServFlag, SyncModeFlag, "light")
|
||||||
|
|
||||||
ks := stack.AccountManager().Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore)
|
ks := stack.AccountManager().Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore)
|
||||||
|
|
@ -1069,13 +1111,8 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *eth.Config) {
|
||||||
setTxPool(ctx, &cfg.TxPool)
|
setTxPool(ctx, &cfg.TxPool)
|
||||||
setEthash(ctx, cfg)
|
setEthash(ctx, cfg)
|
||||||
|
|
||||||
switch {
|
if ctx.GlobalIsSet(SyncModeFlag.Name) {
|
||||||
case ctx.GlobalIsSet(SyncModeFlag.Name):
|
|
||||||
cfg.SyncMode = *GlobalTextMarshaler(ctx, SyncModeFlag.Name).(*downloader.SyncMode)
|
cfg.SyncMode = *GlobalTextMarshaler(ctx, SyncModeFlag.Name).(*downloader.SyncMode)
|
||||||
case ctx.GlobalBool(FastSyncFlag.Name):
|
|
||||||
cfg.SyncMode = downloader.FastSync
|
|
||||||
case ctx.GlobalBool(LightModeFlag.Name):
|
|
||||||
cfg.SyncMode = downloader.LightSync
|
|
||||||
}
|
}
|
||||||
if ctx.GlobalIsSet(LightServFlag.Name) {
|
if ctx.GlobalIsSet(LightServFlag.Name) {
|
||||||
cfg.LightServ = ctx.GlobalInt(LightServFlag.Name)
|
cfg.LightServ = ctx.GlobalInt(LightServFlag.Name)
|
||||||
|
|
@ -1100,20 +1137,26 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *eth.Config) {
|
||||||
if ctx.GlobalIsSet(CacheFlag.Name) || ctx.GlobalIsSet(CacheGCFlag.Name) {
|
if ctx.GlobalIsSet(CacheFlag.Name) || ctx.GlobalIsSet(CacheGCFlag.Name) {
|
||||||
cfg.TrieCache = ctx.GlobalInt(CacheFlag.Name) * ctx.GlobalInt(CacheGCFlag.Name) / 100
|
cfg.TrieCache = ctx.GlobalInt(CacheFlag.Name) * ctx.GlobalInt(CacheGCFlag.Name) / 100
|
||||||
}
|
}
|
||||||
if ctx.GlobalIsSet(MinerThreadsFlag.Name) {
|
|
||||||
cfg.MinerThreads = ctx.GlobalInt(MinerThreadsFlag.Name)
|
|
||||||
}
|
|
||||||
if ctx.GlobalIsSet(MinerNotifyFlag.Name) {
|
if ctx.GlobalIsSet(MinerNotifyFlag.Name) {
|
||||||
cfg.MinerNotify = strings.Split(ctx.GlobalString(MinerNotifyFlag.Name), ",")
|
cfg.MinerNotify = strings.Split(ctx.GlobalString(MinerNotifyFlag.Name), ",")
|
||||||
}
|
}
|
||||||
if ctx.GlobalIsSet(DocRootFlag.Name) {
|
if ctx.GlobalIsSet(DocRootFlag.Name) {
|
||||||
cfg.DocRoot = ctx.GlobalString(DocRootFlag.Name)
|
cfg.DocRoot = ctx.GlobalString(DocRootFlag.Name)
|
||||||
}
|
}
|
||||||
if ctx.GlobalIsSet(ExtraDataFlag.Name) {
|
if ctx.GlobalIsSet(MinerLegacyExtraDataFlag.Name) {
|
||||||
cfg.ExtraData = []byte(ctx.GlobalString(ExtraDataFlag.Name))
|
cfg.MinerExtraData = []byte(ctx.GlobalString(MinerLegacyExtraDataFlag.Name))
|
||||||
}
|
}
|
||||||
if ctx.GlobalIsSet(GasPriceFlag.Name) {
|
if ctx.GlobalIsSet(MinerExtraDataFlag.Name) {
|
||||||
cfg.GasPrice = GlobalBig(ctx, GasPriceFlag.Name)
|
cfg.MinerExtraData = []byte(ctx.GlobalString(MinerExtraDataFlag.Name))
|
||||||
|
}
|
||||||
|
if ctx.GlobalIsSet(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) {
|
if ctx.GlobalIsSet(VMEnableDebugFlag.Name) {
|
||||||
// TODO(fjl): force-enable this in --dev mode
|
// 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)
|
log.Info("Using developer account", "address", developer.Address)
|
||||||
|
|
||||||
cfg.Genesis = core.DeveloperGenesisBlock(uint64(ctx.GlobalInt(DeveloperPeriodFlag.Name)), developer.Address)
|
cfg.Genesis = core.DeveloperGenesisBlock(uint64(ctx.GlobalInt(DeveloperPeriodFlag.Name)), developer.Address)
|
||||||
if !ctx.GlobalIsSet(GasPriceFlag.Name) {
|
if !ctx.GlobalIsSet(MinerGasPriceFlag.Name) && !ctx.GlobalIsSet(MinerLegacyGasPriceFlag.Name) {
|
||||||
cfg.GasPrice = big.NewInt(1)
|
cfg.MinerGasPrice = big.NewInt(1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// TODO(fjl): move trie cache generations into config
|
// TODO(fjl): move trie cache generations into config
|
||||||
|
|
@ -1230,7 +1273,10 @@ func RegisterEthStatsService(stack *node.Node, url string) {
|
||||||
// SetupNetwork configures the system for either the main net or some test network.
|
// SetupNetwork configures the system for either the main net or some test network.
|
||||||
func SetupNetwork(ctx *cli.Context) {
|
func SetupNetwork(ctx *cli.Context) {
|
||||||
// TODO(fjl): move target gas limit into config
|
// 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) {
|
func SetupMetrics(ctx *cli.Context) {
|
||||||
|
|
@ -1261,7 +1307,7 @@ func MakeChainDatabase(ctx *cli.Context, stack *node.Node) ethdb.Database {
|
||||||
handles = makeDatabaseHandles()
|
handles = makeDatabaseHandles()
|
||||||
)
|
)
|
||||||
name := "chaindata"
|
name := "chaindata"
|
||||||
if ctx.GlobalBool(LightModeFlag.Name) {
|
if ctx.GlobalString(SyncModeFlag.Name) == "light" {
|
||||||
name = "lightchaindata"
|
name = "lightchaindata"
|
||||||
}
|
}
|
||||||
chainDb, err := stack.OpenDatabase(name, cache, handles)
|
chainDb, err := stack.OpenDatabase(name, cache, handles)
|
||||||
|
|
|
||||||
|
|
@ -387,23 +387,24 @@ func (c *Clique) snapshot(chain consensus.ChainReader, number uint64, hash commo
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// If we're at block zero, make a snapshot
|
// If we're at an checkpoint block, make a snapshot if it's known
|
||||||
if number == 0 {
|
if number%c.config.Epoch == 0 {
|
||||||
genesis := chain.GetHeaderByNumber(0)
|
checkpoint := chain.GetHeaderByNumber(number)
|
||||||
if err := c.VerifyHeader(chain, genesis, false); err != nil {
|
if checkpoint != nil {
|
||||||
return nil, err
|
hash := checkpoint.Hash()
|
||||||
}
|
|
||||||
signers := make([]common.Address, (len(genesis.Extra)-extraVanity-extraSeal)/common.AddressLength)
|
signers := make([]common.Address, (len(checkpoint.Extra)-extraVanity-extraSeal)/common.AddressLength)
|
||||||
for i := 0; i < len(signers); i++ {
|
for i := 0; i < len(signers); i++ {
|
||||||
copy(signers[i][:], genesis.Extra[extraVanity+i*common.AddressLength:])
|
copy(signers[i][:], checkpoint.Extra[extraVanity+i*common.AddressLength:])
|
||||||
}
|
}
|
||||||
snap = newSnapshot(c.config, c.signatures, 0, genesis.Hash(), signers)
|
snap = newSnapshot(c.config, c.signatures, number, hash, signers)
|
||||||
if err := snap.store(c.db); err != nil {
|
if err := snap.store(c.db); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
log.Trace("Stored genesis voting snapshot to disk")
|
log.Info("Stored checkpoint snapshot to disk", "number", number, "hash", hash)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
}
|
||||||
// No snapshot for this header, gather the header and move backward
|
// No snapshot for this header, gather the header and move backward
|
||||||
var header *types.Header
|
var header *types.Header
|
||||||
if len(parents) > 0 {
|
if len(parents) > 0 {
|
||||||
|
|
@ -672,6 +673,11 @@ func CalcDifficulty(snap *Snapshot, signer common.Address) *big.Int {
|
||||||
return new(big.Int).Set(diffNoTurn)
|
return new(big.Int).Set(diffNoTurn)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SealHash returns the hash of a block prior to it being sealed.
|
||||||
|
func (c *Clique) SealHash(header *types.Header) common.Hash {
|
||||||
|
return sigHash(header)
|
||||||
|
}
|
||||||
|
|
||||||
// Close implements consensus.Engine. It's a noop for clique as there is are no background threads.
|
// Close implements consensus.Engine. It's a noop for clique as there is are no background threads.
|
||||||
func (c *Clique) Close() error {
|
func (c *Clique) Close() error {
|
||||||
return nil
|
return nil
|
||||||
|
|
|
||||||
|
|
@ -84,7 +84,7 @@ func (r *testerChainReader) GetHeaderByNumber(number uint64) *types.Header {
|
||||||
if number == 0 {
|
if number == 0 {
|
||||||
return rawdb.ReadHeader(r.db, rawdb.ReadCanonicalHash(r.db, 0), 0)
|
return rawdb.ReadHeader(r.db, rawdb.ReadCanonicalHash(r.db, 0), 0)
|
||||||
}
|
}
|
||||||
panic("not supported")
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tests that voting is evaluated correctly for various simple and complex scenarios.
|
// Tests that voting is evaluated correctly for various simple and complex scenarios.
|
||||||
|
|
|
||||||
|
|
@ -90,6 +90,9 @@ type Engine interface {
|
||||||
// seal place on top.
|
// seal place on top.
|
||||||
Seal(chain ChainReader, block *types.Block, stop <-chan struct{}) (*types.Block, error)
|
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
|
// CalcDifficulty is the difficulty adjustment algorithm. It returns the difficulty
|
||||||
// that a new block should have.
|
// that a new block should have.
|
||||||
CalcDifficulty(chain ChainReader, time uint64, parent *types.Header) *big.Int
|
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/consensus/misc"
|
||||||
"github.com/ethereum/go-ethereum/core/state"
|
"github.com/ethereum/go-ethereum/core/state"
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
|
"github.com/ethereum/go-ethereum/crypto/sha3"
|
||||||
"github.com/ethereum/go-ethereum/params"
|
"github.com/ethereum/go-ethereum/params"
|
||||||
|
"github.com/ethereum/go-ethereum/rlp"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Ethash proof-of-work protocol constants.
|
// Ethash proof-of-work protocol constants.
|
||||||
|
|
@ -461,6 +463,13 @@ func calcDifficultyFrontier(time uint64, parent *types.Header) *big.Int {
|
||||||
// VerifySeal implements consensus.Engine, checking whether the given block satisfies
|
// VerifySeal implements consensus.Engine, checking whether the given block satisfies
|
||||||
// the PoW difficulty requirements.
|
// the PoW difficulty requirements.
|
||||||
func (ethash *Ethash) VerifySeal(chain consensus.ChainReader, header *types.Header) error {
|
func (ethash *Ethash) VerifySeal(chain consensus.ChainReader, header *types.Header) error {
|
||||||
|
return ethash.verifySeal(chain, header, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// verifySeal checks whether a block satisfies the PoW difficulty requirements,
|
||||||
|
// either using the usual ethash cache for it, or alternatively using a full DAG
|
||||||
|
// to make remote mining fast.
|
||||||
|
func (ethash *Ethash) verifySeal(chain consensus.ChainReader, header *types.Header, fulldag bool) error {
|
||||||
// If we're running a fake PoW, accept any seal as valid
|
// If we're running a fake PoW, accept any seal as valid
|
||||||
if ethash.config.PowMode == ModeFake || ethash.config.PowMode == ModeFullFake {
|
if ethash.config.PowMode == ModeFake || ethash.config.PowMode == ModeFullFake {
|
||||||
time.Sleep(ethash.fakeDelay)
|
time.Sleep(ethash.fakeDelay)
|
||||||
|
|
@ -471,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 we're running a shared PoW, delegate verification to it
|
||||||
if ethash.shared != nil {
|
if ethash.shared != nil {
|
||||||
return ethash.shared.VerifySeal(chain, header)
|
return ethash.shared.verifySeal(chain, header, fulldag)
|
||||||
}
|
}
|
||||||
// Ensure that we have a valid difficulty for the block
|
// Ensure that we have a valid difficulty for the block
|
||||||
if header.Difficulty.Sign() <= 0 {
|
if header.Difficulty.Sign() <= 0 {
|
||||||
return errInvalidDifficulty
|
return errInvalidDifficulty
|
||||||
}
|
}
|
||||||
// Recompute the digest and PoW value and verify against the header
|
// Recompute the digest and PoW values
|
||||||
number := header.Number.Uint64()
|
number := header.Number.Uint64()
|
||||||
|
|
||||||
|
var (
|
||||||
|
digest []byte
|
||||||
|
result []byte
|
||||||
|
)
|
||||||
|
// If fast-but-heavy PoW verification was requested, use an ethash dataset
|
||||||
|
if fulldag {
|
||||||
|
dataset := ethash.dataset(number, true)
|
||||||
|
if dataset.generated() {
|
||||||
|
digest, result = hashimotoFull(dataset.dataset, ethash.SealHash(header).Bytes(), header.Nonce.Uint64())
|
||||||
|
|
||||||
|
// Datasets are unmapped in a finalizer. Ensure that the dataset stays alive
|
||||||
|
// until after the call to hashimotoFull so it's not unmapped while being used.
|
||||||
|
runtime.KeepAlive(dataset)
|
||||||
|
} else {
|
||||||
|
// Dataset not yet generated, don't hang, use a cache instead
|
||||||
|
fulldag = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// If slow-but-light PoW verification was requested (or DAG not yet ready), use an ethash cache
|
||||||
|
if !fulldag {
|
||||||
cache := ethash.cache(number)
|
cache := ethash.cache(number)
|
||||||
|
|
||||||
size := datasetSize(number)
|
size := datasetSize(number)
|
||||||
if ethash.config.PowMode == ModeTest {
|
if ethash.config.PowMode == ModeTest {
|
||||||
size = 32 * 1024
|
size = 32 * 1024
|
||||||
}
|
}
|
||||||
digest, result := hashimotoLight(size, cache.cache, header.HashNoNonce().Bytes(), header.Nonce.Uint64())
|
digest, result = hashimotoLight(size, cache.cache, ethash.SealHash(header).Bytes(), header.Nonce.Uint64())
|
||||||
// Caches are unmapped in a finalizer. Ensure that the cache stays live
|
|
||||||
|
// Caches are unmapped in a finalizer. Ensure that the cache stays alive
|
||||||
// until after the call to hashimotoLight so it's not unmapped while being used.
|
// until after the call to hashimotoLight so it's not unmapped while being used.
|
||||||
runtime.KeepAlive(cache)
|
runtime.KeepAlive(cache)
|
||||||
|
}
|
||||||
|
// Verify the calculated values against the ones provided in the header
|
||||||
if !bytes.Equal(header.MixDigest[:], digest) {
|
if !bytes.Equal(header.MixDigest[:], digest) {
|
||||||
return errInvalidMixDigest
|
return errInvalidMixDigest
|
||||||
}
|
}
|
||||||
|
|
@ -522,6 +554,29 @@ func (ethash *Ethash) Finalize(chain consensus.ChainReader, header *types.Header
|
||||||
return types.NewBlock(header, txs, uncles, receipts), nil
|
return types.NewBlock(header, txs, uncles, receipts), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SealHash returns the hash of a block prior to it being sealed.
|
||||||
|
func (ethash *Ethash) SealHash(header *types.Header) (hash common.Hash) {
|
||||||
|
hasher := sha3.NewKeccak256()
|
||||||
|
|
||||||
|
rlp.Encode(hasher, []interface{}{
|
||||||
|
header.ParentHash,
|
||||||
|
header.UncleHash,
|
||||||
|
header.Coinbase,
|
||||||
|
header.Root,
|
||||||
|
header.TxHash,
|
||||||
|
header.ReceiptHash,
|
||||||
|
header.Bloom,
|
||||||
|
header.Difficulty,
|
||||||
|
header.Number,
|
||||||
|
header.GasLimit,
|
||||||
|
header.GasUsed,
|
||||||
|
header.Time,
|
||||||
|
header.Extra,
|
||||||
|
})
|
||||||
|
hasher.Sum(hash[:0])
|
||||||
|
return hash
|
||||||
|
}
|
||||||
|
|
||||||
// Some weird constants to avoid constant memory allocs for them.
|
// Some weird constants to avoid constant memory allocs for them.
|
||||||
var (
|
var (
|
||||||
big8 = big.NewInt(8)
|
big8 = big.NewInt(8)
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,7 @@ import (
|
||||||
"runtime"
|
"runtime"
|
||||||
"strconv"
|
"strconv"
|
||||||
"sync"
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
|
|
@ -281,6 +282,7 @@ type dataset struct {
|
||||||
mmap mmap.MMap // Memory map itself to unmap before releasing
|
mmap mmap.MMap // Memory map itself to unmap before releasing
|
||||||
dataset []uint32 // The actual cache data content
|
dataset []uint32 // The actual cache data content
|
||||||
once sync.Once // Ensures the cache is generated only once
|
once sync.Once // Ensures the cache is generated only once
|
||||||
|
done uint32 // Atomic flag to determine generation status
|
||||||
}
|
}
|
||||||
|
|
||||||
// newDataset creates a new ethash mining dataset and returns it as a plain Go
|
// newDataset creates a new ethash mining dataset and returns it as a plain Go
|
||||||
|
|
@ -292,6 +294,9 @@ func newDataset(epoch uint64) interface{} {
|
||||||
// generate ensures that the dataset content is generated before use.
|
// generate ensures that the dataset content is generated before use.
|
||||||
func (d *dataset) generate(dir string, limit int, test bool) {
|
func (d *dataset) generate(dir string, limit int, test bool) {
|
||||||
d.once.Do(func() {
|
d.once.Do(func() {
|
||||||
|
// Mark the dataset generated after we're done. This is needed for remote
|
||||||
|
defer atomic.StoreUint32(&d.done, 1)
|
||||||
|
|
||||||
csize := cacheSize(d.epoch*epochLength + 1)
|
csize := cacheSize(d.epoch*epochLength + 1)
|
||||||
dsize := datasetSize(d.epoch*epochLength + 1)
|
dsize := datasetSize(d.epoch*epochLength + 1)
|
||||||
seed := seedHash(d.epoch*epochLength + 1)
|
seed := seedHash(d.epoch*epochLength + 1)
|
||||||
|
|
@ -306,6 +311,8 @@ func (d *dataset) generate(dir string, limit int, test bool) {
|
||||||
|
|
||||||
d.dataset = make([]uint32, dsize/4)
|
d.dataset = make([]uint32, dsize/4)
|
||||||
generateDataset(d.dataset, d.epoch, cache)
|
generateDataset(d.dataset, d.epoch, cache)
|
||||||
|
|
||||||
|
return
|
||||||
}
|
}
|
||||||
// Disk storage is needed, this will get fancy
|
// Disk storage is needed, this will get fancy
|
||||||
var endian string
|
var endian string
|
||||||
|
|
@ -348,6 +355,13 @@ func (d *dataset) generate(dir string, limit int, test bool) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// generated returns whether this particular dataset finished generating already
|
||||||
|
// or not (it may not have been started at all). This is useful for remote miners
|
||||||
|
// to default to verification caches instead of blocking on DAG generations.
|
||||||
|
func (d *dataset) generated() bool {
|
||||||
|
return atomic.LoadUint32(&d.done) == 1
|
||||||
|
}
|
||||||
|
|
||||||
// finalizer closes any file handlers and memory maps open.
|
// finalizer closes any file handlers and memory maps open.
|
||||||
func (d *dataset) finalizer() {
|
func (d *dataset) finalizer() {
|
||||||
if d.mmap != nil {
|
if d.mmap != nil {
|
||||||
|
|
@ -589,20 +603,34 @@ func (ethash *Ethash) cache(block uint64) *cache {
|
||||||
// dataset tries to retrieve a mining dataset for the specified block number
|
// dataset tries to retrieve a mining dataset for the specified block number
|
||||||
// by first checking against a list of in-memory datasets, then against DAGs
|
// by first checking against a list of in-memory datasets, then against DAGs
|
||||||
// stored on disk, and finally generating one if none can be found.
|
// stored on disk, and finally generating one if none can be found.
|
||||||
func (ethash *Ethash) dataset(block uint64) *dataset {
|
//
|
||||||
|
// If async is specified, not only the future but the current DAG is also
|
||||||
|
// generates on a background thread.
|
||||||
|
func (ethash *Ethash) dataset(block uint64, async bool) *dataset {
|
||||||
|
// Retrieve the requested ethash dataset
|
||||||
epoch := block / epochLength
|
epoch := block / epochLength
|
||||||
currentI, futureI := ethash.datasets.get(epoch)
|
currentI, futureI := ethash.datasets.get(epoch)
|
||||||
current := currentI.(*dataset)
|
current := currentI.(*dataset)
|
||||||
|
|
||||||
// Wait for generation finish.
|
// If async is specified, generate everything in a background thread
|
||||||
|
if async && !current.generated() {
|
||||||
|
go func() {
|
||||||
|
current.generate(ethash.config.DatasetDir, ethash.config.DatasetsOnDisk, ethash.config.PowMode == ModeTest)
|
||||||
|
|
||||||
|
if futureI != nil {
|
||||||
|
future := futureI.(*dataset)
|
||||||
|
future.generate(ethash.config.DatasetDir, ethash.config.DatasetsOnDisk, ethash.config.PowMode == ModeTest)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
} else {
|
||||||
|
// Either blocking generation was requested, or already done
|
||||||
current.generate(ethash.config.DatasetDir, ethash.config.DatasetsOnDisk, ethash.config.PowMode == ModeTest)
|
current.generate(ethash.config.DatasetDir, ethash.config.DatasetsOnDisk, ethash.config.PowMode == ModeTest)
|
||||||
|
|
||||||
// If we need a new future dataset, now's a good time to regenerate it.
|
|
||||||
if futureI != nil {
|
if futureI != nil {
|
||||||
future := futureI.(*dataset)
|
future := futureI.(*dataset)
|
||||||
go future.generate(ethash.config.DatasetDir, ethash.config.DatasetsOnDisk, ethash.config.PowMode == ModeTest)
|
go future.generate(ethash.config.DatasetDir, ethash.config.DatasetsOnDisk, ethash.config.PowMode == ModeTest)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return current
|
return current
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -94,6 +94,7 @@ func TestRemoteSealer(t *testing.T) {
|
||||||
}
|
}
|
||||||
header := &types.Header{Number: big.NewInt(1), Difficulty: big.NewInt(100)}
|
header := &types.Header{Number: big.NewInt(1), Difficulty: big.NewInt(100)}
|
||||||
block := types.NewBlockWithHeader(header)
|
block := types.NewBlockWithHeader(header)
|
||||||
|
sealhash := ethash.SealHash(header)
|
||||||
|
|
||||||
// Push new work.
|
// Push new work.
|
||||||
ethash.Seal(nil, block, nil)
|
ethash.Seal(nil, block, nil)
|
||||||
|
|
@ -102,27 +103,29 @@ func TestRemoteSealer(t *testing.T) {
|
||||||
work [3]string
|
work [3]string
|
||||||
err error
|
err error
|
||||||
)
|
)
|
||||||
if work, err = api.GetWork(); err != nil || work[0] != block.HashNoNonce().Hex() {
|
if work, err = api.GetWork(); err != nil || work[0] != sealhash.Hex() {
|
||||||
t.Error("expect to return a mining work has same hash")
|
t.Error("expect to return a mining work has same hash")
|
||||||
}
|
}
|
||||||
|
|
||||||
if res := api.SubmitWork(types.BlockNonce{}, block.HashNoNonce(), common.Hash{}); res {
|
if res := api.SubmitWork(types.BlockNonce{}, sealhash, common.Hash{}); res {
|
||||||
t.Error("expect to return false when submit a fake solution")
|
t.Error("expect to return false when submit a fake solution")
|
||||||
}
|
}
|
||||||
// Push new block with same block number to replace the original one.
|
// Push new block with same block number to replace the original one.
|
||||||
header = &types.Header{Number: big.NewInt(1), Difficulty: big.NewInt(1000)}
|
header = &types.Header{Number: big.NewInt(1), Difficulty: big.NewInt(1000)}
|
||||||
block = types.NewBlockWithHeader(header)
|
block = types.NewBlockWithHeader(header)
|
||||||
|
sealhash = ethash.SealHash(header)
|
||||||
ethash.Seal(nil, block, nil)
|
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")
|
t.Error("expect to return the latest pushed work")
|
||||||
}
|
}
|
||||||
// Push block with higher block number.
|
// Push block with higher block number.
|
||||||
newHead := &types.Header{Number: big.NewInt(2), Difficulty: big.NewInt(100)}
|
newHead := &types.Header{Number: big.NewInt(2), Difficulty: big.NewInt(100)}
|
||||||
newBlock := types.NewBlockWithHeader(newHead)
|
newBlock := types.NewBlockWithHeader(newHead)
|
||||||
|
newSealhash := ethash.SealHash(newHead)
|
||||||
ethash.Seal(nil, newBlock, nil)
|
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")
|
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
|
// Extract some data from the header
|
||||||
var (
|
var (
|
||||||
header = block.Header()
|
header = block.Header()
|
||||||
hash = header.HashNoNonce().Bytes()
|
hash = ethash.SealHash(header).Bytes()
|
||||||
target = new(big.Int).Div(two256, header.Difficulty)
|
target = new(big.Int).Div(two256, header.Difficulty)
|
||||||
number = header.Number.Uint64()
|
number = header.Number.Uint64()
|
||||||
dataset = ethash.dataset(number)
|
dataset = ethash.dataset(number, false)
|
||||||
)
|
)
|
||||||
// Start generating random nonces until we abort or find a good one
|
// Start generating random nonces until we abort or find a good one
|
||||||
var (
|
var (
|
||||||
|
|
@ -213,7 +213,7 @@ func (ethash *Ethash) remote(notify []string) {
|
||||||
// result[1], 32 bytes hex encoded seed hash used for DAG
|
// result[1], 32 bytes hex encoded seed hash used for DAG
|
||||||
// result[2], 32 bytes hex encoded boundary condition ("target"), 2^256/difficulty
|
// result[2], 32 bytes hex encoded boundary condition ("target"), 2^256/difficulty
|
||||||
makeWork := func(block *types.Block) {
|
makeWork := func(block *types.Block) {
|
||||||
hash := block.HashNoNonce()
|
hash := ethash.SealHash(block.Header())
|
||||||
|
|
||||||
currentWork[0] = hash.Hex()
|
currentWork[0] = hash.Hex()
|
||||||
currentWork[1] = common.BytesToHash(SeedHash(block.NumberU64())).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)
|
log.Info("Work submitted but none pending", "hash", hash)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify the correctness of submitted result.
|
// Verify the correctness of submitted result.
|
||||||
header := block.Header()
|
header := block.Header()
|
||||||
header.Nonce = nonce
|
header.Nonce = nonce
|
||||||
header.MixDigest = mixDigest
|
header.MixDigest = mixDigest
|
||||||
if err := ethash.VerifySeal(nil, header); err != nil {
|
|
||||||
log.Warn("Invalid proof-of-work submitted", "hash", hash, "err", err)
|
start := time.Now()
|
||||||
|
if 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
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make sure the result channel is created.
|
// Make sure the result channel is created.
|
||||||
if ethash.resultCh == nil {
|
if ethash.resultCh == nil {
|
||||||
log.Warn("Ethash result channel is empty, submitted mining result is rejected")
|
log.Warn("Ethash result channel is empty, submitted mining result is rejected")
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
log.Trace("Verified correct proof-of-work", "hash", hash, "elapsed", time.Since(start))
|
||||||
|
|
||||||
// Solutions seems to be valid, return to the miner and notify acceptance.
|
// Solutions seems to be valid, return to the miner and notify acceptance.
|
||||||
select {
|
select {
|
||||||
|
|
|
||||||
|
|
@ -51,7 +51,7 @@ func TestRemoteNotify(t *testing.T) {
|
||||||
ethash.Seal(nil, block, nil)
|
ethash.Seal(nil, block, nil)
|
||||||
select {
|
select {
|
||||||
case work := <-sink:
|
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)
|
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 {
|
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.
|
// issues in the notifications.
|
||||||
func TestRemoteMultiNotify(t *testing.T) {
|
func TestRemoteMultiNotify(t *testing.T) {
|
||||||
// Start a simple webserver to capture notifications
|
// Start a simple webserver to capture notifications
|
||||||
sink := make(chan [3]string, 1024)
|
sink := make(chan [3]string, 64)
|
||||||
|
|
||||||
server := &http.Server{
|
server := &http.Server{
|
||||||
Handler: http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
Handler: http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||||
|
|
|
||||||
|
|
@ -314,7 +314,7 @@ func (c *Console) Interactive() {
|
||||||
input = "" // Current user input
|
input = "" // Current user input
|
||||||
scheduler = make(chan string) // Channel to send the next prompt on and receive the input
|
scheduler = make(chan string) // Channel to send the next prompt on and receive the input
|
||||||
)
|
)
|
||||||
// Start a goroutine to listen for promt requests and send back inputs
|
// Start a goroutine to listen for prompt requests and send back inputs
|
||||||
go func() {
|
go func() {
|
||||||
for {
|
for {
|
||||||
// Read the next user input
|
// Read the next user input
|
||||||
|
|
|
||||||
|
|
@ -201,7 +201,7 @@ func TestInteractive(t *testing.T) {
|
||||||
|
|
||||||
go tester.console.Interactive()
|
go tester.console.Interactive()
|
||||||
|
|
||||||
// Wait for a promt and send a statement back
|
// Wait for a prompt and send a statement back
|
||||||
select {
|
select {
|
||||||
case <-tester.input.scheduler:
|
case <-tester.input.scheduler:
|
||||||
case <-time.After(time.Second):
|
case <-time.After(time.Second):
|
||||||
|
|
@ -212,7 +212,7 @@ func TestInteractive(t *testing.T) {
|
||||||
case <-time.After(time.Second):
|
case <-time.After(time.Second):
|
||||||
t.Fatalf("input feedback timeout")
|
t.Fatalf("input feedback timeout")
|
||||||
}
|
}
|
||||||
// Wait for the second promt and ensure first statement was evaluated
|
// Wait for the second prompt and ensure first statement was evaluated
|
||||||
select {
|
select {
|
||||||
case <-tester.input.scheduler:
|
case <-tester.input.scheduler:
|
||||||
case <-time.After(time.Second):
|
case <-time.After(time.Second):
|
||||||
|
|
@ -249,7 +249,7 @@ func TestExecute(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tests that the JavaScript objects returned by statement executions are properly
|
// Tests that the JavaScript objects returned by statement executions are properly
|
||||||
// pretty printed instead of just displaing "[object]".
|
// pretty printed instead of just displaying "[object]".
|
||||||
func TestPrettyPrint(t *testing.T) {
|
func TestPrettyPrint(t *testing.T) {
|
||||||
tester := newTester(t, nil)
|
tester := newTester(t, nil)
|
||||||
defer tester.Close(t)
|
defer tester.Close(t)
|
||||||
|
|
@ -300,7 +300,7 @@ func TestIndenting(t *testing.T) {
|
||||||
}{
|
}{
|
||||||
{`var a = 1;`, 0},
|
{`var a = 1;`, 0},
|
||||||
{`"some string"`, 0},
|
{`"some string"`, 0},
|
||||||
{`"some string with (parentesis`, 0},
|
{`"some string with (parenthesis`, 0},
|
||||||
{`"some string with newline
|
{`"some string with newline
|
||||||
("`, 0},
|
("`, 0},
|
||||||
{`function v(a,b) {}`, 0},
|
{`function v(a,b) {}`, 0},
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ import (
|
||||||
// Only this reader may be used for input because it keeps an internal buffer.
|
// Only this reader may be used for input because it keeps an internal buffer.
|
||||||
var Stdin = newTerminalPrompter()
|
var Stdin = newTerminalPrompter()
|
||||||
|
|
||||||
// UserPrompter defines the methods needed by the console to promt the user for
|
// UserPrompter defines the methods needed by the console to prompt the user for
|
||||||
// various types of inputs.
|
// various types of inputs.
|
||||||
type UserPrompter interface {
|
type UserPrompter interface {
|
||||||
// PromptInput displays the given prompt to the user and requests some textual
|
// PromptInput displays the given prompt to the user and requests some textual
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@
|
||||||
package core
|
package core
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"fmt"
|
"fmt"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
@ -37,11 +38,11 @@ import (
|
||||||
type ChainIndexerBackend interface {
|
type ChainIndexerBackend interface {
|
||||||
// Reset initiates the processing of a new chain segment, potentially terminating
|
// Reset initiates the processing of a new chain segment, potentially terminating
|
||||||
// any partially completed operations (in case of a reorg).
|
// any partially completed operations (in case of a reorg).
|
||||||
Reset(section uint64, prevHead common.Hash) error
|
Reset(ctx context.Context, section uint64, prevHead common.Hash) error
|
||||||
|
|
||||||
// Process crunches through the next header in the chain segment. The caller
|
// Process crunches through the next header in the chain segment. The caller
|
||||||
// will ensure a sequential order of headers.
|
// will ensure a sequential order of headers.
|
||||||
Process(header *types.Header)
|
Process(ctx context.Context, header *types.Header) error
|
||||||
|
|
||||||
// Commit finalizes the section metadata and stores it into the database.
|
// Commit finalizes the section metadata and stores it into the database.
|
||||||
Commit() error
|
Commit() error
|
||||||
|
|
@ -74,6 +75,8 @@ type ChainIndexer struct {
|
||||||
active uint32 // Flag whether the event loop was started
|
active uint32 // Flag whether the event loop was started
|
||||||
update chan struct{} // Notification channel that headers should be processed
|
update chan struct{} // Notification channel that headers should be processed
|
||||||
quit chan chan error // Quit channel to tear down running goroutines
|
quit chan chan error // Quit channel to tear down running goroutines
|
||||||
|
ctx context.Context
|
||||||
|
ctxCancel func()
|
||||||
|
|
||||||
sectionSize uint64 // Number of blocks in a single chain segment to process
|
sectionSize uint64 // Number of blocks in a single chain segment to process
|
||||||
confirmsReq uint64 // Number of confirmations before processing a completed segment
|
confirmsReq uint64 // Number of confirmations before processing a completed segment
|
||||||
|
|
@ -105,6 +108,8 @@ func NewChainIndexer(chainDb, indexDb ethdb.Database, backend ChainIndexerBacken
|
||||||
}
|
}
|
||||||
// Initialize database dependent fields and start the updater
|
// Initialize database dependent fields and start the updater
|
||||||
c.loadValidSections()
|
c.loadValidSections()
|
||||||
|
c.ctx, c.ctxCancel = context.WithCancel(context.Background())
|
||||||
|
|
||||||
go c.updateLoop()
|
go c.updateLoop()
|
||||||
|
|
||||||
return c
|
return c
|
||||||
|
|
@ -138,6 +143,8 @@ func (c *ChainIndexer) Start(chain ChainIndexerChain) {
|
||||||
func (c *ChainIndexer) Close() error {
|
func (c *ChainIndexer) Close() error {
|
||||||
var errs []error
|
var errs []error
|
||||||
|
|
||||||
|
c.ctxCancel()
|
||||||
|
|
||||||
// Tear down the primary update loop
|
// Tear down the primary update loop
|
||||||
errc := make(chan error)
|
errc := make(chan error)
|
||||||
c.quit <- errc
|
c.quit <- errc
|
||||||
|
|
@ -297,6 +304,12 @@ func (c *ChainIndexer) updateLoop() {
|
||||||
c.lock.Unlock()
|
c.lock.Unlock()
|
||||||
newHead, err := c.processSection(section, oldHead)
|
newHead, err := c.processSection(section, oldHead)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
select {
|
||||||
|
case <-c.ctx.Done():
|
||||||
|
<-c.quit <- nil
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
}
|
||||||
c.log.Error("Section processing failed", "error", err)
|
c.log.Error("Section processing failed", "error", err)
|
||||||
}
|
}
|
||||||
c.lock.Lock()
|
c.lock.Lock()
|
||||||
|
|
@ -344,7 +357,7 @@ func (c *ChainIndexer) processSection(section uint64, lastHead common.Hash) (com
|
||||||
|
|
||||||
// Reset and partial processing
|
// Reset and partial processing
|
||||||
|
|
||||||
if err := c.backend.Reset(section, lastHead); err != nil {
|
if err := c.backend.Reset(c.ctx, section, lastHead); err != nil {
|
||||||
c.setValidSections(0)
|
c.setValidSections(0)
|
||||||
return common.Hash{}, err
|
return common.Hash{}, err
|
||||||
}
|
}
|
||||||
|
|
@ -360,11 +373,12 @@ func (c *ChainIndexer) processSection(section uint64, lastHead common.Hash) (com
|
||||||
} else if header.ParentHash != lastHead {
|
} else if header.ParentHash != lastHead {
|
||||||
return common.Hash{}, fmt.Errorf("chain reorged during section processing")
|
return common.Hash{}, fmt.Errorf("chain reorged during section processing")
|
||||||
}
|
}
|
||||||
c.backend.Process(header)
|
if err := c.backend.Process(c.ctx, header); err != nil {
|
||||||
|
return common.Hash{}, err
|
||||||
|
}
|
||||||
lastHead = header.Hash()
|
lastHead = header.Hash()
|
||||||
}
|
}
|
||||||
if err := c.backend.Commit(); err != nil {
|
if err := c.backend.Commit(); err != nil {
|
||||||
c.log.Error("Section commit failed", "error", err)
|
|
||||||
return common.Hash{}, err
|
return common.Hash{}, err
|
||||||
}
|
}
|
||||||
return lastHead, nil
|
return lastHead, nil
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@
|
||||||
package core
|
package core
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/big"
|
"math/big"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
|
|
@ -210,13 +211,13 @@ func (b *testChainIndexBackend) reorg(headNum uint64) uint64 {
|
||||||
return b.stored * b.indexer.sectionSize
|
return b.stored * b.indexer.sectionSize
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *testChainIndexBackend) Reset(section uint64, prevHead common.Hash) error {
|
func (b *testChainIndexBackend) Reset(ctx context.Context, section uint64, prevHead common.Hash) error {
|
||||||
b.section = section
|
b.section = section
|
||||||
b.headerCnt = 0
|
b.headerCnt = 0
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *testChainIndexBackend) Process(header *types.Header) {
|
func (b *testChainIndexBackend) Process(ctx context.Context, header *types.Header) error {
|
||||||
b.headerCnt++
|
b.headerCnt++
|
||||||
if b.headerCnt > b.indexer.sectionSize {
|
if b.headerCnt > b.indexer.sectionSize {
|
||||||
b.t.Error("Processing too many headers")
|
b.t.Error("Processing too many headers")
|
||||||
|
|
@ -227,6 +228,7 @@ func (b *testChainIndexBackend) Process(header *types.Header) {
|
||||||
b.t.Fatal("Unexpected call to Process")
|
b.t.Fatal("Unexpected call to Process")
|
||||||
case b.processCh <- header.Number.Uint64():
|
case b.processCh <- header.Number.Uint64():
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *testChainIndexBackend) Commit() error {
|
func (b *testChainIndexBackend) Commit() error {
|
||||||
|
|
|
||||||
|
|
@ -29,9 +29,6 @@ type PendingLogsEvent struct {
|
||||||
Logs []*types.Log
|
Logs []*types.Log
|
||||||
}
|
}
|
||||||
|
|
||||||
// PendingStateEvent is posted pre mining and notifies of pending state changes.
|
|
||||||
type PendingStateEvent struct{}
|
|
||||||
|
|
||||||
// NewMinedBlockEvent is posted when a block has been imported.
|
// NewMinedBlockEvent is posted when a block has been imported.
|
||||||
type NewMinedBlockEvent struct{ Block *types.Block }
|
type NewMinedBlockEvent struct{ Block *types.Block }
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -489,10 +489,13 @@ func (self *StateDB) Copy() *StateDB {
|
||||||
state.stateObjectsDirty[addr] = struct{}{}
|
state.stateObjectsDirty[addr] = struct{}{}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for hash, logs := range self.logs {
|
for hash, logs := range self.logs {
|
||||||
state.logs[hash] = make([]*types.Log, len(logs))
|
cpy := make([]*types.Log, len(logs))
|
||||||
copy(state.logs[hash], logs)
|
for i, l := range logs {
|
||||||
|
cpy[i] = new(types.Log)
|
||||||
|
*cpy[i] = *l
|
||||||
|
}
|
||||||
|
state.logs[hash] = cpy
|
||||||
}
|
}
|
||||||
for hash, preimage := range self.preimages {
|
for hash, preimage := range self.preimages {
|
||||||
state.preimages[hash] = preimage
|
state.preimages[hash] = preimage
|
||||||
|
|
|
||||||
|
|
@ -123,6 +123,7 @@ type blockChain interface {
|
||||||
|
|
||||||
// TxPoolConfig are the configuration parameters of the transaction pool.
|
// TxPoolConfig are the configuration parameters of the transaction pool.
|
||||||
type TxPoolConfig struct {
|
type TxPoolConfig struct {
|
||||||
|
Locals []common.Address // Addresses that should be treated by default as local
|
||||||
NoLocals bool // Whether local transaction handling should be disabled
|
NoLocals bool // Whether local transaction handling should be disabled
|
||||||
Journal string // Journal of local transactions to survive node restarts
|
Journal string // Journal of local transactions to survive node restarts
|
||||||
Rejournal time.Duration // Time interval to regenerate the local transaction journal
|
Rejournal time.Duration // Time interval to regenerate the local transaction journal
|
||||||
|
|
@ -231,6 +232,10 @@ func NewTxPool(config TxPoolConfig, chainconfig *params.ChainConfig, chain block
|
||||||
gasPrice: new(big.Int).SetUint64(config.PriceLimit),
|
gasPrice: new(big.Int).SetUint64(config.PriceLimit),
|
||||||
}
|
}
|
||||||
pool.locals = newAccountSet(pool.signer)
|
pool.locals = newAccountSet(pool.signer)
|
||||||
|
for _, addr := range config.Locals {
|
||||||
|
log.Info("Setting new local account", "address", addr)
|
||||||
|
pool.locals.add(addr)
|
||||||
|
}
|
||||||
pool.priced = newTxPricedList(pool.all)
|
pool.priced = newTxPricedList(pool.all)
|
||||||
pool.reset(nil, chain.CurrentBlock().Header())
|
pool.reset(nil, chain.CurrentBlock().Header())
|
||||||
|
|
||||||
|
|
@ -534,6 +539,14 @@ func (pool *TxPool) Pending() (map[common.Address]types.Transactions, error) {
|
||||||
return pending, nil
|
return pending, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Locals retrieves the accounts currently considered local by the pool.
|
||||||
|
func (pool *TxPool) Locals() []common.Address {
|
||||||
|
pool.mu.Lock()
|
||||||
|
defer pool.mu.Unlock()
|
||||||
|
|
||||||
|
return pool.locals.flatten()
|
||||||
|
}
|
||||||
|
|
||||||
// local retrieves all currently known local transactions, groupped by origin
|
// local retrieves all currently known local transactions, groupped by origin
|
||||||
// account and sorted by nonce. The returned transaction set is a copy and can be
|
// account and sorted by nonce. The returned transaction set is a copy and can be
|
||||||
// freely modified by calling code.
|
// freely modified by calling code.
|
||||||
|
|
@ -665,8 +678,11 @@ func (pool *TxPool) add(tx *types.Transaction, local bool) (bool, error) {
|
||||||
}
|
}
|
||||||
// Mark local addresses and journal local transactions
|
// Mark local addresses and journal local transactions
|
||||||
if local {
|
if local {
|
||||||
|
if !pool.locals.contains(from) {
|
||||||
|
log.Info("Setting new local account", "address", from)
|
||||||
pool.locals.add(from)
|
pool.locals.add(from)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
pool.journalTx(from, tx)
|
pool.journalTx(from, tx)
|
||||||
|
|
||||||
log.Trace("Pooled new future transaction", "hash", hash, "from", from, "to", tx.To())
|
log.Trace("Pooled new future transaction", "hash", hash, "from", from, "to", tx.To())
|
||||||
|
|
@ -1138,6 +1154,7 @@ func (a addressesByHeartbeat) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||||
type accountSet struct {
|
type accountSet struct {
|
||||||
accounts map[common.Address]struct{}
|
accounts map[common.Address]struct{}
|
||||||
signer types.Signer
|
signer types.Signer
|
||||||
|
cache *[]common.Address
|
||||||
}
|
}
|
||||||
|
|
||||||
// newAccountSet creates a new address set with an associated signer for sender
|
// newAccountSet creates a new address set with an associated signer for sender
|
||||||
|
|
@ -1167,6 +1184,20 @@ func (as *accountSet) containsTx(tx *types.Transaction) bool {
|
||||||
// add inserts a new address into the set to track.
|
// add inserts a new address into the set to track.
|
||||||
func (as *accountSet) add(addr common.Address) {
|
func (as *accountSet) add(addr common.Address) {
|
||||||
as.accounts[addr] = struct{}{}
|
as.accounts[addr] = struct{}{}
|
||||||
|
as.cache = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// flatten returns the list of addresses within this set, also caching it for later
|
||||||
|
// reuse. The returned slice should not be changed!
|
||||||
|
func (as *accountSet) flatten() []common.Address {
|
||||||
|
if as.cache == nil {
|
||||||
|
accounts := make([]common.Address, 0, len(as.accounts))
|
||||||
|
for account := range as.accounts {
|
||||||
|
accounts = append(accounts, account)
|
||||||
|
}
|
||||||
|
as.cache = &accounts
|
||||||
|
}
|
||||||
|
return *as.cache
|
||||||
}
|
}
|
||||||
|
|
||||||
// txLookup is used internally by TxPool to track transactions while allowing lookup without
|
// txLookup is used internally by TxPool to track transactions while allowing lookup without
|
||||||
|
|
|
||||||
|
|
@ -102,25 +102,6 @@ func (h *Header) Hash() common.Hash {
|
||||||
return rlpHash(h)
|
return rlpHash(h)
|
||||||
}
|
}
|
||||||
|
|
||||||
// HashNoNonce returns the hash which is used as input for the proof-of-work search.
|
|
||||||
func (h *Header) HashNoNonce() common.Hash {
|
|
||||||
return rlpHash([]interface{}{
|
|
||||||
h.ParentHash,
|
|
||||||
h.UncleHash,
|
|
||||||
h.Coinbase,
|
|
||||||
h.Root,
|
|
||||||
h.TxHash,
|
|
||||||
h.ReceiptHash,
|
|
||||||
h.Bloom,
|
|
||||||
h.Difficulty,
|
|
||||||
h.Number,
|
|
||||||
h.GasLimit,
|
|
||||||
h.GasUsed,
|
|
||||||
h.Time,
|
|
||||||
h.Extra,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Size returns the approximate memory used by all internal contents. It is used
|
// Size returns the approximate memory used by all internal contents. It is used
|
||||||
// to approximate and limit the memory consumption of various caches.
|
// to approximate and limit the memory consumption of various caches.
|
||||||
func (h *Header) Size() common.StorageSize {
|
func (h *Header) Size() common.StorageSize {
|
||||||
|
|
@ -324,10 +305,6 @@ func (b *Block) Header() *Header { return CopyHeader(b.header) }
|
||||||
// Body returns the non-header content of the block.
|
// Body returns the non-header content of the block.
|
||||||
func (b *Block) Body() *Body { return &Body{b.transactions, b.uncles} }
|
func (b *Block) Body() *Body { return &Body{b.transactions, b.uncles} }
|
||||||
|
|
||||||
func (b *Block) HashNoNonce() common.Hash {
|
|
||||||
return b.header.HashNoNonce()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Size returns the true RLP encoded storage size of the block, either by encoding
|
// Size returns the true RLP encoded storage size of the block, either by encoding
|
||||||
// and returning it, or returning a previsouly cached value.
|
// and returning it, or returning a previsouly cached value.
|
||||||
func (b *Block) Size() common.StorageSize {
|
func (b *Block) Size() common.StorageSize {
|
||||||
|
|
|
||||||
|
|
@ -119,7 +119,7 @@ func isProtectedV(V *big.Int) bool {
|
||||||
v := V.Uint64()
|
v := V.Uint64()
|
||||||
return v != 27 && v != 28
|
return v != 27 && v != 28
|
||||||
}
|
}
|
||||||
// anything not 27 or 28 are considered unprotected
|
// anything not 27 or 28 is considered protected
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
28
crypto/bn256/LICENSE
Normal file
28
crypto/bn256/LICENSE
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
Copyright (c) 2012 The Go Authors. All rights reserved.
|
||||||
|
Copyright (c) 2018 Péter Szilágyi. All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are
|
||||||
|
met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
* Redistributions in binary form must reproduce the above
|
||||||
|
copyright notice, this list of conditions and the following disclaimer
|
||||||
|
in the documentation and/or other materials provided with the
|
||||||
|
distribution.
|
||||||
|
* Neither the name of Google Inc. nor the names of its
|
||||||
|
contributors may be used to endorse or promote products derived from
|
||||||
|
this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
@ -1,18 +1,6 @@
|
||||||
// Copyright 2018 The go-ethereum Authors
|
// Copyright 2018 Péter Szilágyi. All rights reserved.
|
||||||
// This file is part of the go-ethereum library.
|
// Use of this source code is governed by a BSD-style license that can be found
|
||||||
//
|
// in the LICENSE file.
|
||||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Lesser General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU Lesser General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Lesser General Public License
|
|
||||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
// +build amd64 arm64
|
// +build amd64 arm64
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,6 @@
|
||||||
// Copyright 2018 The go-ethereum Authors
|
// Copyright 2018 Péter Szilágyi. All rights reserved.
|
||||||
// This file is part of the go-ethereum library.
|
// Use of this source code is governed by a BSD-style license that can be found
|
||||||
//
|
// in the LICENSE file.
|
||||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Lesser General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU Lesser General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Lesser General Public License
|
|
||||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
// +build gofuzz
|
// +build gofuzz
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,6 @@
|
||||||
// Copyright 2018 The go-ethereum Authors
|
// Copyright 2018 Péter Szilágyi. All rights reserved.
|
||||||
// This file is part of the go-ethereum library.
|
// Use of this source code is governed by a BSD-style license that can be found
|
||||||
//
|
// in the LICENSE file.
|
||||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Lesser General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU Lesser General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Lesser General Public License
|
|
||||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
// +build !amd64,!arm64
|
// +build !amd64,!arm64
|
||||||
|
|
||||||
|
|
|
||||||
27
crypto/bn256/cloudflare/LICENSE
Normal file
27
crypto/bn256/cloudflare/LICENSE
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
Copyright (c) 2009 The Go Authors. All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are
|
||||||
|
met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
* Redistributions in binary form must reproduce the above
|
||||||
|
copyright notice, this list of conditions and the following disclaimer
|
||||||
|
in the documentation and/or other materials provided with the
|
||||||
|
distribution.
|
||||||
|
* Neither the name of Google Inc. nor the names of its
|
||||||
|
contributors may be used to endorse or promote products derived from
|
||||||
|
this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
@ -110,7 +110,7 @@ TEXT ·gfpMul(SB),0,$160-24
|
||||||
MOVQ b+16(FP), SI
|
MOVQ b+16(FP), SI
|
||||||
|
|
||||||
// Jump to a slightly different implementation if MULX isn't supported.
|
// Jump to a slightly different implementation if MULX isn't supported.
|
||||||
CMPB runtime·support_bmi2(SB), $0
|
CMPB ·hasBMI2(SB), $0
|
||||||
JE nobmi2Mul
|
JE nobmi2Mul
|
||||||
|
|
||||||
mulBMI2(0(DI),8(DI),16(DI),24(DI), 0(SI))
|
mulBMI2(0(DI),8(DI),16(DI),24(DI), 0(SI))
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,13 @@ package bn256
|
||||||
// This file contains forward declarations for the architecture-specific
|
// This file contains forward declarations for the architecture-specific
|
||||||
// assembly implementations of these functions, provided that they exist.
|
// assembly implementations of these functions, provided that they exist.
|
||||||
|
|
||||||
|
import (
|
||||||
|
"golang.org/x/sys/cpu"
|
||||||
|
)
|
||||||
|
|
||||||
|
//nolint:varcheck
|
||||||
|
var hasBMI2 = cpu.X86.HasBMI2
|
||||||
|
|
||||||
// go:noescape
|
// go:noescape
|
||||||
func gfpNeg(c, a *gfP)
|
func gfpNeg(c, a *gfP)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
// Package bn256 implements a particular bilinear group at the 128-bit security level.
|
// Package bn256 implements a particular bilinear group.
|
||||||
//
|
//
|
||||||
// Bilinear groups are the basis of many of the new cryptographic protocols
|
// Bilinear groups are the basis of many of the new cryptographic protocols
|
||||||
// that have been proposed over the past decade. They consist of a triplet of
|
// that have been proposed over the past decade. They consist of a triplet of
|
||||||
|
|
@ -14,6 +14,10 @@
|
||||||
// Barreto-Naehrig curve as described in
|
// Barreto-Naehrig curve as described in
|
||||||
// http://cryptojedi.org/papers/dclxvi-20100714.pdf. Its output is compatible
|
// http://cryptojedi.org/papers/dclxvi-20100714.pdf. Its output is compatible
|
||||||
// with the implementation described in that paper.
|
// with the implementation described in that paper.
|
||||||
|
//
|
||||||
|
// (This package previously claimed to operate at a 128-bit security level.
|
||||||
|
// However, recent improvements in attacks mean that is no longer true. See
|
||||||
|
// https://moderncrypto.org/mail-archive/curves/2016/000740.html.)
|
||||||
package bn256
|
package bn256
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|
@ -50,8 +54,8 @@ func RandomG1(r io.Reader) (*big.Int, *G1, error) {
|
||||||
return k, new(G1).ScalarBaseMult(k), nil
|
return k, new(G1).ScalarBaseMult(k), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *G1) String() string {
|
func (e *G1) String() string {
|
||||||
return "bn256.G1" + g.p.String()
|
return "bn256.G1" + e.p.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
// CurvePoints returns p's curve points in big integer
|
// CurvePoints returns p's curve points in big integer
|
||||||
|
|
@ -98,15 +102,19 @@ func (e *G1) Neg(a *G1) *G1 {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Marshal converts n to a byte slice.
|
// Marshal converts n to a byte slice.
|
||||||
func (n *G1) Marshal() []byte {
|
func (e *G1) Marshal() []byte {
|
||||||
n.p.MakeAffine(nil)
|
|
||||||
|
|
||||||
xBytes := new(big.Int).Mod(n.p.x, P).Bytes()
|
|
||||||
yBytes := new(big.Int).Mod(n.p.y, P).Bytes()
|
|
||||||
|
|
||||||
// Each value is a 256-bit number.
|
// Each value is a 256-bit number.
|
||||||
const numBytes = 256 / 8
|
const numBytes = 256 / 8
|
||||||
|
|
||||||
|
if e.p.IsInfinity() {
|
||||||
|
return make([]byte, numBytes*2)
|
||||||
|
}
|
||||||
|
|
||||||
|
e.p.MakeAffine(nil)
|
||||||
|
|
||||||
|
xBytes := new(big.Int).Mod(e.p.x, P).Bytes()
|
||||||
|
yBytes := new(big.Int).Mod(e.p.y, P).Bytes()
|
||||||
|
|
||||||
ret := make([]byte, numBytes*2)
|
ret := make([]byte, numBytes*2)
|
||||||
copy(ret[1*numBytes-len(xBytes):], xBytes)
|
copy(ret[1*numBytes-len(xBytes):], xBytes)
|
||||||
copy(ret[2*numBytes-len(yBytes):], yBytes)
|
copy(ret[2*numBytes-len(yBytes):], yBytes)
|
||||||
|
|
@ -175,8 +183,8 @@ func RandomG2(r io.Reader) (*big.Int, *G2, error) {
|
||||||
return k, new(G2).ScalarBaseMult(k), nil
|
return k, new(G2).ScalarBaseMult(k), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *G2) String() string {
|
func (e *G2) String() string {
|
||||||
return "bn256.G2" + g.p.String()
|
return "bn256.G2" + e.p.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
// CurvePoints returns the curve points of p which includes the real
|
// CurvePoints returns the curve points of p which includes the real
|
||||||
|
|
@ -216,6 +224,13 @@ func (e *G2) Add(a, b *G2) *G2 {
|
||||||
|
|
||||||
// Marshal converts n into a byte slice.
|
// Marshal converts n into a byte slice.
|
||||||
func (n *G2) Marshal() []byte {
|
func (n *G2) Marshal() []byte {
|
||||||
|
// Each value is a 256-bit number.
|
||||||
|
const numBytes = 256 / 8
|
||||||
|
|
||||||
|
if n.p.IsInfinity() {
|
||||||
|
return make([]byte, numBytes*4)
|
||||||
|
}
|
||||||
|
|
||||||
n.p.MakeAffine(nil)
|
n.p.MakeAffine(nil)
|
||||||
|
|
||||||
xxBytes := new(big.Int).Mod(n.p.x.x, P).Bytes()
|
xxBytes := new(big.Int).Mod(n.p.x.x, P).Bytes()
|
||||||
|
|
@ -223,9 +238,6 @@ func (n *G2) Marshal() []byte {
|
||||||
yxBytes := new(big.Int).Mod(n.p.y.x, P).Bytes()
|
yxBytes := new(big.Int).Mod(n.p.y.x, P).Bytes()
|
||||||
yyBytes := new(big.Int).Mod(n.p.y.y, P).Bytes()
|
yyBytes := new(big.Int).Mod(n.p.y.y, P).Bytes()
|
||||||
|
|
||||||
// Each value is a 256-bit number.
|
|
||||||
const numBytes = 256 / 8
|
|
||||||
|
|
||||||
ret := make([]byte, numBytes*4)
|
ret := make([]byte, numBytes*4)
|
||||||
copy(ret[1*numBytes-len(xxBytes):], xxBytes)
|
copy(ret[1*numBytes-len(xxBytes):], xxBytes)
|
||||||
copy(ret[2*numBytes-len(xyBytes):], xyBytes)
|
copy(ret[2*numBytes-len(xyBytes):], xyBytes)
|
||||||
|
|
|
||||||
|
|
@ -245,11 +245,19 @@ func (c *curvePoint) Mul(a *curvePoint, scalar *big.Int, pool *bnPool) *curvePoi
|
||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MakeAffine converts c to affine form and returns c. If c is ∞, then it sets
|
||||||
|
// c to 0 : 1 : 0.
|
||||||
func (c *curvePoint) MakeAffine(pool *bnPool) *curvePoint {
|
func (c *curvePoint) MakeAffine(pool *bnPool) *curvePoint {
|
||||||
if words := c.z.Bits(); len(words) == 1 && words[0] == 1 {
|
if words := c.z.Bits(); len(words) == 1 && words[0] == 1 {
|
||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
if c.IsInfinity() {
|
||||||
|
c.x.SetInt64(0)
|
||||||
|
c.y.SetInt64(1)
|
||||||
|
c.z.SetInt64(0)
|
||||||
|
c.t.SetInt64(0)
|
||||||
|
return c
|
||||||
|
}
|
||||||
zInv := pool.Get().ModInverse(c.z, P)
|
zInv := pool.Get().ModInverse(c.z, P)
|
||||||
t := pool.Get().Mul(c.y, zInv)
|
t := pool.Get().Mul(c.y, zInv)
|
||||||
t.Mod(t, P)
|
t.Mod(t, P)
|
||||||
|
|
|
||||||
|
|
@ -225,11 +225,19 @@ func (c *twistPoint) Mul(a *twistPoint, scalar *big.Int, pool *bnPool) *twistPoi
|
||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MakeAffine converts c to affine form and returns c. If c is ∞, then it sets
|
||||||
|
// c to 0 : 1 : 0.
|
||||||
func (c *twistPoint) MakeAffine(pool *bnPool) *twistPoint {
|
func (c *twistPoint) MakeAffine(pool *bnPool) *twistPoint {
|
||||||
if c.z.IsOne() {
|
if c.z.IsOne() {
|
||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
if c.IsInfinity() {
|
||||||
|
c.x.SetZero()
|
||||||
|
c.y.SetOne()
|
||||||
|
c.z.SetZero()
|
||||||
|
c.t.SetZero()
|
||||||
|
return c
|
||||||
|
}
|
||||||
zInv := newGFp2(pool).Invert(c.z, pool)
|
zInv := newGFp2(pool).Invert(c.z, pool)
|
||||||
t := newGFp2(pool).Mul(c.y, zInv, pool)
|
t := newGFp2(pool).Mul(c.y, zInv, pool)
|
||||||
zInv2 := newGFp2(pool).Square(zInv, pool)
|
zInv2 := newGFp2(pool).Square(zInv, pool)
|
||||||
|
|
|
||||||
53
eth/api.go
53
eth/api.go
|
|
@ -24,7 +24,9 @@ import (
|
||||||
"io"
|
"io"
|
||||||
"math/big"
|
"math/big"
|
||||||
"os"
|
"os"
|
||||||
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||||
|
|
@ -33,7 +35,6 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/core/state"
|
"github.com/ethereum/go-ethereum/core/state"
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/internal/ethapi"
|
"github.com/ethereum/go-ethereum/internal/ethapi"
|
||||||
"github.com/ethereum/go-ethereum/log"
|
|
||||||
"github.com/ethereum/go-ethereum/params"
|
"github.com/ethereum/go-ethereum/params"
|
||||||
"github.com/ethereum/go-ethereum/rlp"
|
"github.com/ethereum/go-ethereum/rlp"
|
||||||
"github.com/ethereum/go-ethereum/rpc"
|
"github.com/ethereum/go-ethereum/rpc"
|
||||||
|
|
@ -93,47 +94,22 @@ func NewPrivateMinerAPI(e *Ethereum) *PrivateMinerAPI {
|
||||||
return &PrivateMinerAPI{e: e}
|
return &PrivateMinerAPI{e: e}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start the miner with the given number of threads. If threads is nil the number
|
// Start starts the miner with the given number of threads. If threads is nil,
|
||||||
// of workers started is equal to the number of logical CPUs that are usable by
|
// the number of workers started is equal to the number of logical CPUs that are
|
||||||
// this process. If mining is already running, this method adjust the number of
|
// usable by this process. If mining is already running, this method adjust the
|
||||||
// threads allowed to use and updates the minimum price required by the transaction
|
// number of threads allowed to use and updates the minimum price required by the
|
||||||
// pool.
|
// transaction pool.
|
||||||
func (api *PrivateMinerAPI) Start(threads *int) error {
|
func (api *PrivateMinerAPI) Start(threads *int) error {
|
||||||
// Set the number of threads if the seal engine supports it
|
|
||||||
if threads == nil {
|
if threads == nil {
|
||||||
threads = new(int)
|
return api.e.StartMining(runtime.NumCPU())
|
||||||
} else if *threads == 0 {
|
|
||||||
*threads = -1 // Disable the miner from within
|
|
||||||
}
|
}
|
||||||
type threaded interface {
|
return api.e.StartMining(*threads)
|
||||||
SetThreads(threads int)
|
|
||||||
}
|
|
||||||
if th, ok := api.e.engine.(threaded); ok {
|
|
||||||
log.Info("Updated mining threads", "threads", *threads)
|
|
||||||
th.SetThreads(*threads)
|
|
||||||
}
|
|
||||||
// Start the miner and return
|
|
||||||
if !api.e.IsMining() {
|
|
||||||
// Propagate the initial price point to the transaction pool
|
|
||||||
api.e.lock.RLock()
|
|
||||||
price := api.e.gasPrice
|
|
||||||
api.e.lock.RUnlock()
|
|
||||||
api.e.txPool.SetGasPrice(price)
|
|
||||||
return api.e.StartMining(true)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stop the miner
|
// Stop terminates the miner, both at the consensus engine level as well as at
|
||||||
func (api *PrivateMinerAPI) Stop() bool {
|
// the block creation level.
|
||||||
type threaded interface {
|
func (api *PrivateMinerAPI) Stop() {
|
||||||
SetThreads(threads int)
|
|
||||||
}
|
|
||||||
if th, ok := api.e.engine.(threaded); ok {
|
|
||||||
th.SetThreads(-1)
|
|
||||||
}
|
|
||||||
api.e.StopMining()
|
api.e.StopMining()
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetExtra sets the extra data string that is included when this miner mines a block.
|
// SetExtra sets the extra data string that is included when this miner mines a block.
|
||||||
|
|
@ -160,6 +136,11 @@ func (api *PrivateMinerAPI) SetEtherbase(etherbase common.Address) bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetRecommitInterval updates the interval for miner sealing work recommitting.
|
||||||
|
func (api *PrivateMinerAPI) SetRecommitInterval(interval int) {
|
||||||
|
api.e.Miner().SetRecommitInterval(time.Duration(interval) * time.Millisecond)
|
||||||
|
}
|
||||||
|
|
||||||
// GetHashrate returns the current hashrate of the miner.
|
// GetHashrate returns the current hashrate of the miner.
|
||||||
func (api *PrivateMinerAPI) GetHashrate() uint64 {
|
func (api *PrivateMinerAPI) GetHashrate() uint64 {
|
||||||
return api.e.miner.HashRate()
|
return api.e.miner.HashRate()
|
||||||
|
|
|
||||||
|
|
@ -119,6 +119,9 @@ func (api *PrivateDebugAPI) TraceChain(ctx context.Context, start, end rpc.Block
|
||||||
if to == nil {
|
if to == nil {
|
||||||
return nil, fmt.Errorf("end block #%d not found", end)
|
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)
|
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
|
// New creates a new Ethereum object (including the
|
||||||
// initialisation of the common Ethereum object)
|
// initialisation of the common Ethereum object)
|
||||||
func New(ctx *node.ServiceContext, config *Config) (*Ethereum, error) {
|
func New(ctx *node.ServiceContext, config *Config) (*Ethereum, error) {
|
||||||
|
// Ensure configuration values are compatible and sane
|
||||||
if config.SyncMode == downloader.LightSync {
|
if config.SyncMode == downloader.LightSync {
|
||||||
return nil, errors.New("can't run eth.Ethereum in light sync mode, use les.LightEthereum")
|
return nil, errors.New("can't run eth.Ethereum in light sync mode, use les.LightEthereum")
|
||||||
}
|
}
|
||||||
if !config.SyncMode.IsValid() {
|
if !config.SyncMode.IsValid() {
|
||||||
return nil, fmt.Errorf("invalid sync mode %d", config.SyncMode)
|
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")
|
chainDb, err := CreateDB(ctx, config, "chaindata")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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),
|
engine: CreateConsensusEngine(ctx, chainConfig, &config.Ethash, config.MinerNotify, chainDb),
|
||||||
shutdownChan: make(chan bool),
|
shutdownChan: make(chan bool),
|
||||||
networkID: config.NetworkId,
|
networkID: config.NetworkId,
|
||||||
gasPrice: config.GasPrice,
|
gasPrice: config.MinerGasPrice,
|
||||||
etherbase: config.Etherbase,
|
etherbase: config.Etherbase,
|
||||||
bloomRequests: make(chan chan *bloombits.Retrieval),
|
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)
|
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 {
|
if !config.SkipBcVersionCheck {
|
||||||
bcVersion := rawdb.ReadDatabaseVersion(chainDb)
|
bcVersion := rawdb.ReadDatabaseVersion(chainDb)
|
||||||
if bcVersion != core.BlockChainVersion && bcVersion != 0 {
|
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)
|
rawdb.WriteDatabaseVersion(chainDb, core.BlockChainVersion)
|
||||||
}
|
}
|
||||||
|
|
@ -167,13 +173,13 @@ func New(ctx *node.ServiceContext, config *Config) (*Ethereum, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
eth.miner = miner.New(eth, eth.chainConfig, eth.EventMux(), eth.engine)
|
eth.miner = miner.New(eth, eth.chainConfig, eth.EventMux(), eth.engine, config.MinerRecommit)
|
||||||
eth.miner.SetExtra(makeExtraData(config.ExtraData))
|
eth.miner.SetExtra(makeExtraData(config.MinerExtraData))
|
||||||
|
|
||||||
eth.APIBackend = &EthAPIBackend{eth, nil}
|
eth.APIBackend = &EthAPIBackend{eth, nil}
|
||||||
gpoParams := config.GPO
|
gpoParams := config.GPO
|
||||||
if gpoParams.Default == nil {
|
if gpoParams.Default == nil {
|
||||||
gpoParams.Default = config.GasPrice
|
gpoParams.Default = config.MinerGasPrice
|
||||||
}
|
}
|
||||||
eth.APIBackend.gpo = gasprice.NewOracle(eth.APIBackend, gpoParams)
|
eth.APIBackend.gpo = gasprice.NewOracle(eth.APIBackend, gpoParams)
|
||||||
|
|
||||||
|
|
@ -333,7 +339,30 @@ func (s *Ethereum) SetEtherbase(etherbase common.Address) {
|
||||||
s.miner.SetEtherbase(etherbase)
|
s.miner.SetEtherbase(etherbase)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Ethereum) StartMining(local bool) error {
|
// 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 th, ok := s.engine.(threaded); ok {
|
||||||
|
log.Info("Updated mining threads", "threads", threads)
|
||||||
|
if threads == 0 {
|
||||||
|
threads = -1 // Disable the miner from within
|
||||||
|
}
|
||||||
|
th.SetThreads(threads)
|
||||||
|
}
|
||||||
|
// 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()
|
eb, err := s.Etherbase()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("Cannot start mining without etherbase", "err", err)
|
log.Error("Cannot start mining without etherbase", "err", err)
|
||||||
|
|
@ -347,18 +376,29 @@ func (s *Ethereum) StartMining(local bool) error {
|
||||||
}
|
}
|
||||||
clique.Authorize(eb, wallet.SignHash)
|
clique.Authorize(eb, wallet.SignHash)
|
||||||
}
|
}
|
||||||
if local {
|
// If mining is started, we can disable the transaction rejection mechanism
|
||||||
// If local (CPU) mining is started, we can disable the transaction rejection
|
// introduced to speed sync times.
|
||||||
// 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.
|
|
||||||
atomic.StoreUint32(&s.protocolManager.acceptTxs, 1)
|
atomic.StoreUint32(&s.protocolManager.acceptTxs, 1)
|
||||||
}
|
|
||||||
go s.miner.Start(eb)
|
go s.miner.Start(eb)
|
||||||
|
}
|
||||||
return nil
|
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) IsMining() bool { return s.miner.Mining() }
|
||||||
func (s *Ethereum) Miner() *miner.Miner { return s.miner }
|
func (s *Ethereum) Miner() *miner.Miner { return s.miner }
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@
|
||||||
package eth
|
package eth
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
|
@ -93,29 +94,27 @@ const (
|
||||||
// for the Ethereum header bloom filters, permitting blazing fast filtering.
|
// for the Ethereum header bloom filters, permitting blazing fast filtering.
|
||||||
type BloomIndexer struct {
|
type BloomIndexer struct {
|
||||||
size uint64 // section size to generate bloombits for
|
size uint64 // section size to generate bloombits for
|
||||||
|
|
||||||
db ethdb.Database // database instance to write index data and metadata into
|
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
|
gen *bloombits.Generator // generator to rotate the bloom bits crating the bloom index
|
||||||
|
|
||||||
section uint64 // Section is the section number being processed currently
|
section uint64 // Section is the section number being processed currently
|
||||||
head common.Hash // Head is the hash of the last header processed
|
head common.Hash // Head is the hash of the last header processed
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewBloomIndexer returns a chain indexer that generates bloom bits data for the
|
// NewBloomIndexer returns a chain indexer that generates bloom bits data for the
|
||||||
// canonical chain for fast logs filtering.
|
// 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{
|
backend := &BloomIndexer{
|
||||||
db: db,
|
db: db,
|
||||||
size: size,
|
size: size,
|
||||||
}
|
}
|
||||||
table := ethdb.NewTable(db, string(rawdb.BloomBitsIndexPrefix))
|
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
|
// Reset implements core.ChainIndexerBackend, starting a new bloombits index
|
||||||
// section.
|
// 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))
|
gen, err := bloombits.NewGenerator(uint(b.size))
|
||||||
b.gen, b.section, b.head = gen, section, common.Hash{}
|
b.gen, b.section, b.head = gen, section, common.Hash{}
|
||||||
return err
|
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
|
// Process implements core.ChainIndexerBackend, adding a new header's bloom into
|
||||||
// the index.
|
// 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.gen.AddBloom(uint(header.Number.Uint64()-b.section*b.size), header.Bloom)
|
||||||
b.head = header.Hash()
|
b.head = header.Hash()
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Commit implements core.ChainIndexerBackend, finalizing the bloom section and
|
// Commit implements core.ChainIndexerBackend, finalizing the bloom section and
|
||||||
// writing it out into the database.
|
// writing it out into the database.
|
||||||
func (b *BloomIndexer) Commit() error {
|
func (b *BloomIndexer) Commit() error {
|
||||||
batch := b.db.NewBatch()
|
batch := b.db.NewBatch()
|
||||||
|
|
||||||
for i := 0; i < types.BloomBitLength; i++ {
|
for i := 0; i < types.BloomBitLength; i++ {
|
||||||
bits, err := b.gen.Bitset(uint(i))
|
bits, err := b.gen.Bitset(uint(i))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
|
|
@ -48,7 +48,8 @@ var DefaultConfig = Config{
|
||||||
DatabaseCache: 768,
|
DatabaseCache: 768,
|
||||||
TrieCache: 256,
|
TrieCache: 256,
|
||||||
TrieTimeout: 60 * time.Minute,
|
TrieTimeout: 60 * time.Minute,
|
||||||
GasPrice: big.NewInt(18 * params.Shannon),
|
MinerGasPrice: big.NewInt(18 * params.Shannon),
|
||||||
|
MinerRecommit: 3 * time.Second,
|
||||||
|
|
||||||
TxPool: core.DefaultTxPoolConfig,
|
TxPool: core.DefaultTxPoolConfig,
|
||||||
GPO: gasprice.Config{
|
GPO: gasprice.Config{
|
||||||
|
|
@ -96,10 +97,10 @@ type Config struct {
|
||||||
|
|
||||||
// Mining-related options
|
// Mining-related options
|
||||||
Etherbase common.Address `toml:",omitempty"`
|
Etherbase common.Address `toml:",omitempty"`
|
||||||
MinerThreads int `toml:",omitempty"`
|
|
||||||
MinerNotify []string `toml:",omitempty"`
|
MinerNotify []string `toml:",omitempty"`
|
||||||
ExtraData []byte `toml:",omitempty"`
|
MinerExtraData []byte `toml:",omitempty"`
|
||||||
GasPrice *big.Int
|
MinerGasPrice *big.Int
|
||||||
|
MinerRecommit time.Duration
|
||||||
|
|
||||||
// Ethash options
|
// Ethash options
|
||||||
Ethash ethash.Config
|
Ethash ethash.Config
|
||||||
|
|
@ -118,5 +119,5 @@ type Config struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type configMarshaling struct {
|
type configMarshaling struct {
|
||||||
ExtraData hexutil.Bytes
|
MinerExtraData hexutil.Bytes
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ package eth
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"math/big"
|
"math/big"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||||
|
|
@ -15,20 +16,25 @@ import (
|
||||||
|
|
||||||
var _ = (*configMarshaling)(nil)
|
var _ = (*configMarshaling)(nil)
|
||||||
|
|
||||||
|
// MarshalTOML marshals as TOML.
|
||||||
func (c Config) MarshalTOML() (interface{}, error) {
|
func (c Config) MarshalTOML() (interface{}, error) {
|
||||||
type Config struct {
|
type Config struct {
|
||||||
Genesis *core.Genesis `toml:",omitempty"`
|
Genesis *core.Genesis `toml:",omitempty"`
|
||||||
NetworkId uint64
|
NetworkId uint64
|
||||||
SyncMode downloader.SyncMode
|
SyncMode downloader.SyncMode
|
||||||
|
NoPruning bool
|
||||||
LightServ int `toml:",omitempty"`
|
LightServ int `toml:",omitempty"`
|
||||||
LightPeers int `toml:",omitempty"`
|
LightPeers int `toml:",omitempty"`
|
||||||
SkipBcVersionCheck bool `toml:"-"`
|
SkipBcVersionCheck bool `toml:"-"`
|
||||||
DatabaseHandles int `toml:"-"`
|
DatabaseHandles int `toml:"-"`
|
||||||
DatabaseCache int
|
DatabaseCache int
|
||||||
|
TrieCache int
|
||||||
|
TrieTimeout time.Duration
|
||||||
Etherbase common.Address `toml:",omitempty"`
|
Etherbase common.Address `toml:",omitempty"`
|
||||||
MinerThreads int `toml:",omitempty"`
|
MinerNotify []string `toml:",omitempty"`
|
||||||
ExtraData hexutil.Bytes `toml:",omitempty"`
|
MinerExtraData hexutil.Bytes `toml:",omitempty"`
|
||||||
GasPrice *big.Int
|
MinerGasPrice *big.Int
|
||||||
|
MinerRecommit time.Duration
|
||||||
Ethash ethash.Config
|
Ethash ethash.Config
|
||||||
TxPool core.TxPoolConfig
|
TxPool core.TxPoolConfig
|
||||||
GPO gasprice.Config
|
GPO gasprice.Config
|
||||||
|
|
@ -39,15 +45,19 @@ func (c Config) MarshalTOML() (interface{}, error) {
|
||||||
enc.Genesis = c.Genesis
|
enc.Genesis = c.Genesis
|
||||||
enc.NetworkId = c.NetworkId
|
enc.NetworkId = c.NetworkId
|
||||||
enc.SyncMode = c.SyncMode
|
enc.SyncMode = c.SyncMode
|
||||||
|
enc.NoPruning = c.NoPruning
|
||||||
enc.LightServ = c.LightServ
|
enc.LightServ = c.LightServ
|
||||||
enc.LightPeers = c.LightPeers
|
enc.LightPeers = c.LightPeers
|
||||||
enc.SkipBcVersionCheck = c.SkipBcVersionCheck
|
enc.SkipBcVersionCheck = c.SkipBcVersionCheck
|
||||||
enc.DatabaseHandles = c.DatabaseHandles
|
enc.DatabaseHandles = c.DatabaseHandles
|
||||||
enc.DatabaseCache = c.DatabaseCache
|
enc.DatabaseCache = c.DatabaseCache
|
||||||
|
enc.TrieCache = c.TrieCache
|
||||||
|
enc.TrieTimeout = c.TrieTimeout
|
||||||
enc.Etherbase = c.Etherbase
|
enc.Etherbase = c.Etherbase
|
||||||
enc.MinerThreads = c.MinerThreads
|
enc.MinerNotify = c.MinerNotify
|
||||||
enc.ExtraData = c.ExtraData
|
enc.MinerExtraData = c.MinerExtraData
|
||||||
enc.GasPrice = c.GasPrice
|
enc.MinerGasPrice = c.MinerGasPrice
|
||||||
|
enc.MinerRecommit = c.MinerRecommit
|
||||||
enc.Ethash = c.Ethash
|
enc.Ethash = c.Ethash
|
||||||
enc.TxPool = c.TxPool
|
enc.TxPool = c.TxPool
|
||||||
enc.GPO = c.GPO
|
enc.GPO = c.GPO
|
||||||
|
|
@ -56,20 +66,26 @@ func (c Config) MarshalTOML() (interface{}, error) {
|
||||||
return &enc, nil
|
return &enc, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UnmarshalTOML unmarshals from TOML.
|
||||||
func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error {
|
func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error {
|
||||||
type Config struct {
|
type Config struct {
|
||||||
Genesis *core.Genesis `toml:",omitempty"`
|
Genesis *core.Genesis `toml:",omitempty"`
|
||||||
NetworkId *uint64
|
NetworkId *uint64
|
||||||
SyncMode *downloader.SyncMode
|
SyncMode *downloader.SyncMode
|
||||||
|
NoPruning *bool
|
||||||
LightServ *int `toml:",omitempty"`
|
LightServ *int `toml:",omitempty"`
|
||||||
LightPeers *int `toml:",omitempty"`
|
LightPeers *int `toml:",omitempty"`
|
||||||
SkipBcVersionCheck *bool `toml:"-"`
|
SkipBcVersionCheck *bool `toml:"-"`
|
||||||
DatabaseHandles *int `toml:"-"`
|
DatabaseHandles *int `toml:"-"`
|
||||||
DatabaseCache *int
|
DatabaseCache *int
|
||||||
|
TrieCache *int
|
||||||
|
TrieTimeout *time.Duration
|
||||||
Etherbase *common.Address `toml:",omitempty"`
|
Etherbase *common.Address `toml:",omitempty"`
|
||||||
MinerThreads *int `toml:",omitempty"`
|
MinerThreads *int `toml:",omitempty"`
|
||||||
ExtraData *hexutil.Bytes `toml:",omitempty"`
|
MinerNotify []string `toml:",omitempty"`
|
||||||
GasPrice *big.Int
|
MinerExtraData *hexutil.Bytes `toml:",omitempty"`
|
||||||
|
MinerGasPrice *big.Int
|
||||||
|
MinerRecommit *time.Duration
|
||||||
Ethash *ethash.Config
|
Ethash *ethash.Config
|
||||||
TxPool *core.TxPoolConfig
|
TxPool *core.TxPoolConfig
|
||||||
GPO *gasprice.Config
|
GPO *gasprice.Config
|
||||||
|
|
@ -89,6 +105,9 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error {
|
||||||
if dec.SyncMode != nil {
|
if dec.SyncMode != nil {
|
||||||
c.SyncMode = *dec.SyncMode
|
c.SyncMode = *dec.SyncMode
|
||||||
}
|
}
|
||||||
|
if dec.NoPruning != nil {
|
||||||
|
c.NoPruning = *dec.NoPruning
|
||||||
|
}
|
||||||
if dec.LightServ != nil {
|
if dec.LightServ != nil {
|
||||||
c.LightServ = *dec.LightServ
|
c.LightServ = *dec.LightServ
|
||||||
}
|
}
|
||||||
|
|
@ -104,17 +123,26 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error {
|
||||||
if dec.DatabaseCache != nil {
|
if dec.DatabaseCache != nil {
|
||||||
c.DatabaseCache = *dec.DatabaseCache
|
c.DatabaseCache = *dec.DatabaseCache
|
||||||
}
|
}
|
||||||
|
if dec.TrieCache != nil {
|
||||||
|
c.TrieCache = *dec.TrieCache
|
||||||
|
}
|
||||||
|
if dec.TrieTimeout != nil {
|
||||||
|
c.TrieTimeout = *dec.TrieTimeout
|
||||||
|
}
|
||||||
if dec.Etherbase != nil {
|
if dec.Etherbase != nil {
|
||||||
c.Etherbase = *dec.Etherbase
|
c.Etherbase = *dec.Etherbase
|
||||||
}
|
}
|
||||||
if dec.MinerThreads != nil {
|
if dec.MinerNotify != nil {
|
||||||
c.MinerThreads = *dec.MinerThreads
|
c.MinerNotify = dec.MinerNotify
|
||||||
}
|
}
|
||||||
if dec.ExtraData != nil {
|
if dec.MinerExtraData != nil {
|
||||||
c.ExtraData = *dec.ExtraData
|
c.MinerExtraData = *dec.MinerExtraData
|
||||||
}
|
}
|
||||||
if dec.GasPrice != nil {
|
if dec.MinerGasPrice != nil {
|
||||||
c.GasPrice = dec.GasPrice
|
c.MinerGasPrice = dec.MinerGasPrice
|
||||||
|
}
|
||||||
|
if dec.MinerRecommit != nil {
|
||||||
|
c.MinerRecommit = *dec.MinerRecommit
|
||||||
}
|
}
|
||||||
if dec.Ethash != nil {
|
if dec.Ethash != nil {
|
||||||
c.Ethash = *dec.Ethash
|
c.Ethash = *dec.Ethash
|
||||||
|
|
|
||||||
|
|
@ -519,6 +519,11 @@ web3._extend({
|
||||||
params: 1,
|
params: 1,
|
||||||
inputFormatter: [web3._extend.utils.fromDecimal]
|
inputFormatter: [web3._extend.utils.fromDecimal]
|
||||||
}),
|
}),
|
||||||
|
new web3._extend.Method({
|
||||||
|
name: 'setRecommitInterval',
|
||||||
|
call: 'miner_setRecommitInterval',
|
||||||
|
params: 1,
|
||||||
|
}),
|
||||||
new web3._extend.Method({
|
new web3._extend.Method({
|
||||||
name: 'getHashrate',
|
name: 'getHashrate',
|
||||||
call: 'miner_getHashrate'
|
call: 'miner_getHashrate'
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,6 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/eth/downloader"
|
"github.com/ethereum/go-ethereum/eth/downloader"
|
||||||
"github.com/ethereum/go-ethereum/eth/filters"
|
"github.com/ethereum/go-ethereum/eth/filters"
|
||||||
"github.com/ethereum/go-ethereum/eth/gasprice"
|
"github.com/ethereum/go-ethereum/eth/gasprice"
|
||||||
"github.com/ethereum/go-ethereum/ethdb"
|
|
||||||
"github.com/ethereum/go-ethereum/event"
|
"github.com/ethereum/go-ethereum/event"
|
||||||
"github.com/ethereum/go-ethereum/internal/ethapi"
|
"github.com/ethereum/go-ethereum/internal/ethapi"
|
||||||
"github.com/ethereum/go-ethereum/light"
|
"github.com/ethereum/go-ethereum/light"
|
||||||
|
|
@ -47,26 +46,24 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type LightEthereum struct {
|
type LightEthereum struct {
|
||||||
config *eth.Config
|
lesCommons
|
||||||
|
|
||||||
odr *LesOdr
|
odr *LesOdr
|
||||||
relay *LesTxRelay
|
relay *LesTxRelay
|
||||||
chainConfig *params.ChainConfig
|
chainConfig *params.ChainConfig
|
||||||
// Channel for shutting down the service
|
// Channel for shutting down the service
|
||||||
shutdownChan chan bool
|
shutdownChan chan bool
|
||||||
|
|
||||||
// Handlers
|
// Handlers
|
||||||
peers *peerSet
|
peers *peerSet
|
||||||
txPool *light.TxPool
|
txPool *light.TxPool
|
||||||
blockchain *light.LightChain
|
blockchain *light.LightChain
|
||||||
protocolManager *ProtocolManager
|
|
||||||
serverPool *serverPool
|
serverPool *serverPool
|
||||||
reqDist *requestDistributor
|
reqDist *requestDistributor
|
||||||
retriever *retrieveManager
|
retriever *retrieveManager
|
||||||
// DB interfaces
|
|
||||||
chainDb ethdb.Database // Block chain database
|
|
||||||
|
|
||||||
bloomRequests chan chan *bloombits.Retrieval // Channel receiving bloom data retrieval requests
|
bloomRequests chan chan *bloombits.Retrieval // Channel receiving bloom data retrieval requests
|
||||||
bloomIndexer, chtIndexer, bloomTrieIndexer *core.ChainIndexer
|
bloomIndexer *core.ChainIndexer
|
||||||
|
|
||||||
ApiBackend *LesApiBackend
|
ApiBackend *LesApiBackend
|
||||||
|
|
||||||
|
|
@ -95,9 +92,11 @@ func New(ctx *node.ServiceContext, config *eth.Config) (*LightEthereum, error) {
|
||||||
quitSync := make(chan struct{})
|
quitSync := make(chan struct{})
|
||||||
|
|
||||||
leth := &LightEthereum{
|
leth := &LightEthereum{
|
||||||
config: config,
|
lesCommons: lesCommons{
|
||||||
chainConfig: chainConfig,
|
|
||||||
chainDb: chainDb,
|
chainDb: chainDb,
|
||||||
|
config: config,
|
||||||
|
},
|
||||||
|
chainConfig: chainConfig,
|
||||||
eventMux: ctx.EventMux,
|
eventMux: ctx.EventMux,
|
||||||
peers: peers,
|
peers: peers,
|
||||||
reqDist: newRequestDistributor(peers, quitSync),
|
reqDist: newRequestDistributor(peers, quitSync),
|
||||||
|
|
@ -106,19 +105,28 @@ func New(ctx *node.ServiceContext, config *eth.Config) (*LightEthereum, error) {
|
||||||
shutdownChan: make(chan bool),
|
shutdownChan: make(chan bool),
|
||||||
networkId: config.NetworkId,
|
networkId: config.NetworkId,
|
||||||
bloomRequests: make(chan chan *bloombits.Retrieval),
|
bloomRequests: make(chan chan *bloombits.Retrieval),
|
||||||
bloomIndexer: eth.NewBloomIndexer(chainDb, light.BloomTrieFrequency),
|
bloomIndexer: eth.NewBloomIndexer(chainDb, light.BloomTrieFrequency, light.HelperTrieConfirmations),
|
||||||
chtIndexer: light.NewChtIndexer(chainDb, true),
|
|
||||||
bloomTrieIndexer: light.NewBloomTrieIndexer(chainDb, true),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
leth.relay = NewLesTxRelay(peers, leth.reqDist)
|
leth.relay = NewLesTxRelay(peers, leth.reqDist)
|
||||||
leth.serverPool = newServerPool(chainDb, quitSync, &leth.wg)
|
leth.serverPool = newServerPool(chainDb, quitSync, &leth.wg)
|
||||||
leth.retriever = newRetrieveManager(peers, leth.reqDist, leth.serverPool)
|
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 {
|
if leth.blockchain, err = light.NewLightChain(leth.odr, leth.chainConfig, leth.engine); err != nil {
|
||||||
return nil, err
|
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)
|
leth.bloomIndexer.Start(leth.blockchain)
|
||||||
|
|
||||||
// Rewind the chain in case of an incompatible config upgrade.
|
// Rewind the chain in case of an incompatible config upgrade.
|
||||||
if compat, ok := genesisErr.(*params.ConfigCompatError); ok {
|
if compat, ok := genesisErr.(*params.ConfigCompatError); ok {
|
||||||
log.Warn("Rewinding chain to upgrade configuration", "err", compat)
|
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)
|
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
|
return nil, err
|
||||||
}
|
}
|
||||||
leth.ApiBackend = &LesApiBackend{leth, nil}
|
leth.ApiBackend = &LesApiBackend{leth, nil}
|
||||||
gpoParams := config.GPO
|
gpoParams := config.GPO
|
||||||
if gpoParams.Default == nil {
|
if gpoParams.Default == nil {
|
||||||
gpoParams.Default = config.GasPrice
|
gpoParams.Default = config.MinerGasPrice
|
||||||
}
|
}
|
||||||
leth.ApiBackend.gpo = gasprice.NewOracle(leth.ApiBackend, gpoParams)
|
leth.ApiBackend.gpo = gasprice.NewOracle(leth.ApiBackend, gpoParams)
|
||||||
return leth, nil
|
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) BlockChain() *light.LightChain { return s.blockchain }
|
||||||
func (s *LightEthereum) TxPool() *light.TxPool { return s.txPool }
|
func (s *LightEthereum) TxPool() *light.TxPool { return s.txPool }
|
||||||
func (s *LightEthereum) Engine() consensus.Engine { return s.engine }
|
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) Downloader() *downloader.Downloader { return s.protocolManager.downloader }
|
||||||
func (s *LightEthereum) EventMux() *event.TypeMux { return s.eventMux }
|
func (s *LightEthereum) EventMux() *event.TypeMux { return s.eventMux }
|
||||||
|
|
||||||
// Protocols implements node.Service, returning all the currently configured
|
// Protocols implements node.Service, returning all the currently configured
|
||||||
// network protocols to start.
|
// network protocols to start.
|
||||||
func (s *LightEthereum) Protocols() []p2p.Protocol {
|
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
|
// 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.
|
// Ethereum protocol.
|
||||||
func (s *LightEthereum) Stop() error {
|
func (s *LightEthereum) Stop() error {
|
||||||
s.odr.Stop()
|
s.odr.Stop()
|
||||||
if s.bloomIndexer != nil {
|
|
||||||
s.bloomIndexer.Close()
|
s.bloomIndexer.Close()
|
||||||
}
|
|
||||||
if s.chtIndexer != nil {
|
|
||||||
s.chtIndexer.Close()
|
s.chtIndexer.Close()
|
||||||
}
|
|
||||||
if s.bloomTrieIndexer != nil {
|
|
||||||
s.bloomTrieIndexer.Close()
|
|
||||||
}
|
|
||||||
s.blockchain.Stop()
|
s.blockchain.Stop()
|
||||||
s.protocolManager.Stop()
|
s.protocolManager.Stop()
|
||||||
s.txPool.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 (
|
import (
|
||||||
"container/list"
|
"container/list"
|
||||||
"errors"
|
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"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
|
// requestDistributor implements a mechanism that distributes requests to
|
||||||
// suitable peers, obeying flow control rules and prioritizing them in creation
|
// suitable peers, obeying flow control rules and prioritizing them in creation
|
||||||
// order (even when a resend is necessary).
|
// order (even when a resend is necessary).
|
||||||
|
|
|
||||||
112
les/handler.go
112
les/handler.go
|
|
@ -20,7 +20,6 @@ package les
|
||||||
import (
|
import (
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/big"
|
"math/big"
|
||||||
"net"
|
"net"
|
||||||
|
|
@ -40,7 +39,6 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/light"
|
"github.com/ethereum/go-ethereum/light"
|
||||||
"github.com/ethereum/go-ethereum/log"
|
"github.com/ethereum/go-ethereum/log"
|
||||||
"github.com/ethereum/go-ethereum/p2p"
|
"github.com/ethereum/go-ethereum/p2p"
|
||||||
"github.com/ethereum/go-ethereum/p2p/discover"
|
|
||||||
"github.com/ethereum/go-ethereum/p2p/discv5"
|
"github.com/ethereum/go-ethereum/p2p/discv5"
|
||||||
"github.com/ethereum/go-ethereum/params"
|
"github.com/ethereum/go-ethereum/params"
|
||||||
"github.com/ethereum/go-ethereum/rlp"
|
"github.com/ethereum/go-ethereum/rlp"
|
||||||
|
|
@ -65,10 +63,6 @@ const (
|
||||||
disableClientRemovePeer = false
|
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 {
|
func errResp(code errCode, format string, v ...interface{}) error {
|
||||||
return fmt.Errorf("%v - %v", code, fmt.Sprintf(format, v...))
|
return fmt.Errorf("%v - %v", code, fmt.Sprintf(format, v...))
|
||||||
}
|
}
|
||||||
|
|
@ -115,8 +109,6 @@ type ProtocolManager struct {
|
||||||
peers *peerSet
|
peers *peerSet
|
||||||
maxPeers int
|
maxPeers int
|
||||||
|
|
||||||
SubProtocols []p2p.Protocol
|
|
||||||
|
|
||||||
eventMux *event.TypeMux
|
eventMux *event.TypeMux
|
||||||
|
|
||||||
// channels for fetcher, syncer, txsyncLoop
|
// 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
|
// NewProtocolManager returns a new ethereum sub protocol manager. The Ethereum sub protocol manages peers capable
|
||||||
// with the ethereum network.
|
// 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
|
// Create the protocol manager with the base fields
|
||||||
manager := &ProtocolManager{
|
manager := &ProtocolManager{
|
||||||
lightSync: lightSync,
|
lightSync: lightSync,
|
||||||
|
|
@ -155,54 +147,6 @@ func NewProtocolManager(chainConfig *params.ChainConfig, lightSync bool, protoco
|
||||||
manager.reqDist = odr.retriever.dist
|
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
|
removePeer := manager.removePeer
|
||||||
if disableClientRemovePeer {
|
if disableClientRemovePeer {
|
||||||
removePeer = func(id string) {}
|
removePeer = func(id string) {}
|
||||||
|
|
@ -262,6 +206,32 @@ func (pm *ProtocolManager) Stop() {
|
||||||
log.Info("Light Ethereum protocol stopped")
|
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 {
|
func (pm *ProtocolManager) newPeer(pv int, nv uint64, p *p2p.Peer, rw p2p.MsgReadWriter) *peer {
|
||||||
return newPeer(pv, nv, p, newMeteredMsgWriter(rw))
|
return newPeer(pv, nv, p, newMeteredMsgWriter(rw))
|
||||||
}
|
}
|
||||||
|
|
@ -1203,30 +1173,6 @@ func (pm *ProtocolManager) txStatus(hashes []common.Hash) []txStatus {
|
||||||
return stats
|
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
|
// downloaderPeerNotify implements peerSetNotify
|
||||||
type downloaderPeerNotify ProtocolManager
|
type downloaderPeerNotify ProtocolManager
|
||||||
|
|
||||||
|
|
@ -1258,7 +1204,7 @@ func (pc *peerConnection) RequestHeadersByHash(origin common.Hash, amount int, s
|
||||||
}
|
}
|
||||||
_, ok := <-pc.manager.reqDist.queue(rq)
|
_, ok := <-pc.manager.reqDist.queue(rq)
|
||||||
if !ok {
|
if !ok {
|
||||||
return ErrNoPeers
|
return light.ErrNoPeers
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
@ -1282,7 +1228,7 @@ func (pc *peerConnection) RequestHeadersByNumber(origin uint64, amount int, skip
|
||||||
}
|
}
|
||||||
_, ok := <-pc.manager.reqDist.queue(rq)
|
_, ok := <-pc.manager.reqDist.queue(rq)
|
||||||
if !ok {
|
if !ok {
|
||||||
return ErrNoPeers
|
return light.ErrNoPeers
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -156,12 +156,12 @@ func newTestProtocolManager(lightSync bool, blocks int, generator func(int, *cor
|
||||||
} else {
|
} else {
|
||||||
blockchain, _ := core.NewBlockChain(db, nil, gspec.Config, engine, vm.Config{})
|
blockchain, _ := core.NewBlockChain(db, nil, gspec.Config, engine, vm.Config{})
|
||||||
|
|
||||||
chtIndexer := light.NewChtIndexer(db, false)
|
chtIndexer := light.NewChtIndexer(db, false, nil)
|
||||||
chtIndexer.Start(blockchain)
|
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.AddChildIndexer(bbtIndexer)
|
||||||
bloomIndexer.Start(blockchain)
|
bloomIndexer.Start(blockchain)
|
||||||
|
|
||||||
|
|
@ -172,18 +172,12 @@ func newTestProtocolManager(lightSync bool, blocks int, generator func(int, *cor
|
||||||
chain = blockchain
|
chain = blockchain
|
||||||
}
|
}
|
||||||
|
|
||||||
var protocolVersions []uint
|
pm, err := NewProtocolManager(gspec.Config, lightSync, NetworkId, evmux, engine, peers, chain, nil, db, odr, nil, nil, make(chan struct{}), new(sync.WaitGroup))
|
||||||
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))
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if !lightSync {
|
if !lightSync {
|
||||||
srv := &LesServer{protocolManager: pm}
|
srv := &LesServer{lesCommons: lesCommons{protocolManager: pm}}
|
||||||
pm.server = srv
|
pm.server = srv
|
||||||
|
|
||||||
srv.defParams = &flowcontrol.ServerParams{
|
srv.defParams = &flowcontrol.ServerParams{
|
||||||
|
|
|
||||||
12
les/odr.go
12
les/odr.go
|
|
@ -33,12 +33,9 @@ type LesOdr struct {
|
||||||
stop chan 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{
|
return &LesOdr{
|
||||||
db: db,
|
db: db,
|
||||||
chtIndexer: chtIndexer,
|
|
||||||
bloomTrieIndexer: bloomTrieIndexer,
|
|
||||||
bloomIndexer: bloomIndexer,
|
|
||||||
retriever: retriever,
|
retriever: retriever,
|
||||||
stop: make(chan struct{}),
|
stop: make(chan struct{}),
|
||||||
}
|
}
|
||||||
|
|
@ -54,6 +51,13 @@ func (odr *LesOdr) Database() ethdb.Database {
|
||||||
return odr.db
|
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
|
// ChtIndexer returns the CHT chain indexer
|
||||||
func (odr *LesOdr) ChtIndexer() *core.ChainIndexer {
|
func (odr *LesOdr) ChtIndexer() *core.ChainIndexer {
|
||||||
return odr.chtIndexer
|
return odr.chtIndexer
|
||||||
|
|
|
||||||
|
|
@ -167,7 +167,8 @@ func testOdr(t *testing.T, protocol int, expFail uint64, fn odrTestFn) {
|
||||||
rm := newRetrieveManager(peers, dist, nil)
|
rm := newRetrieveManager(peers, dist, nil)
|
||||||
db := ethdb.NewMemDatabase()
|
db := ethdb.NewMemDatabase()
|
||||||
ldb := 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)
|
pm := newTestProtocolManagerMust(t, false, 4, testChainGen, nil, nil, db)
|
||||||
lpm := newTestProtocolManagerMust(t, true, 0, nil, peers, odr, ldb)
|
lpm := newTestProtocolManagerMust(t, true, 0, nil, peers, odr, ldb)
|
||||||
_, err1, lpeer, err2 := newTestPeerPair("peer", protocol, pm, lpm)
|
_, 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)
|
rm := newRetrieveManager(peers, dist, nil)
|
||||||
db := ethdb.NewMemDatabase()
|
db := ethdb.NewMemDatabase()
|
||||||
ldb := 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)
|
pm := newTestProtocolManagerMust(t, false, 4, testChainGen, nil, nil, db)
|
||||||
lpm := newTestProtocolManagerMust(t, true, 0, nil, peers, odr, ldb)
|
lpm := newTestProtocolManagerMust(t, true, 0, nil, peers, odr, ldb)
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common/mclock"
|
"github.com/ethereum/go-ethereum/common/mclock"
|
||||||
|
"github.com/ethereum/go-ethereum/light"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|
@ -207,7 +208,7 @@ func (r *sentReq) stateRequesting() reqStateFn {
|
||||||
return r.stateNoMorePeers
|
return r.stateNoMorePeers
|
||||||
}
|
}
|
||||||
// nothing to wait for, no more peers to ask, return with error
|
// 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
|
// no need to go to stopped state because waiting() already returned false
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -38,21 +38,19 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type LesServer struct {
|
type LesServer struct {
|
||||||
config *eth.Config
|
lesCommons
|
||||||
protocolManager *ProtocolManager
|
|
||||||
fcManager *flowcontrol.ClientManager // nil if our node is client only
|
fcManager *flowcontrol.ClientManager // nil if our node is client only
|
||||||
fcCostStats *requestCostStats
|
fcCostStats *requestCostStats
|
||||||
defParams *flowcontrol.ServerParams
|
defParams *flowcontrol.ServerParams
|
||||||
lesTopics []discv5.Topic
|
lesTopics []discv5.Topic
|
||||||
privateKey *ecdsa.PrivateKey
|
privateKey *ecdsa.PrivateKey
|
||||||
quitSync chan struct{}
|
quitSync chan struct{}
|
||||||
|
|
||||||
chtIndexer, bloomTrieIndexer *core.ChainIndexer
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewLesServer(eth *eth.Ethereum, config *eth.Config) (*LesServer, error) {
|
func NewLesServer(eth *eth.Ethereum, config *eth.Config) (*LesServer, error) {
|
||||||
quitSync := make(chan struct{})
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -63,13 +61,17 @@ func NewLesServer(eth *eth.Ethereum, config *eth.Config) (*LesServer, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
srv := &LesServer{
|
srv := &LesServer{
|
||||||
|
lesCommons: lesCommons{
|
||||||
config: config,
|
config: config,
|
||||||
|
chainDb: eth.ChainDb(),
|
||||||
|
chtIndexer: light.NewChtIndexer(eth.ChainDb(), false, nil),
|
||||||
|
bloomTrieIndexer: light.NewBloomTrieIndexer(eth.ChainDb(), false, nil),
|
||||||
protocolManager: pm,
|
protocolManager: pm,
|
||||||
|
},
|
||||||
quitSync: quitSync,
|
quitSync: quitSync,
|
||||||
lesTopics: lesTopics,
|
lesTopics: lesTopics,
|
||||||
chtIndexer: light.NewChtIndexer(eth.ChainDb(), false),
|
|
||||||
bloomTrieIndexer: light.NewBloomTrieIndexer(eth.ChainDb(), false),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
logger := log.New()
|
logger := log.New()
|
||||||
|
|
||||||
chtV1SectionCount, _, _ := srv.chtIndexer.Sections() // indexer still uses LES/1 4k section size for backwards server compatibility
|
chtV1SectionCount, _, _ := srv.chtIndexer.Sections() // indexer still uses LES/1 4k section size for backwards server compatibility
|
||||||
|
|
@ -104,7 +106,7 @@ func NewLesServer(eth *eth.Ethereum, config *eth.Config) (*LesServer, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *LesServer) Protocols() []p2p.Protocol {
|
func (s *LesServer) Protocols() []p2p.Protocol {
|
||||||
return s.protocolManager.SubProtocols
|
return s.makeProtocols(ServerProtocolVersions)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start starts the LES server
|
// 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
|
// 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 {
|
if self.odr.ChtIndexer() != nil {
|
||||||
StoreChtRoot(self.chainDb, cp.sectionIdx, cp.sectionHead, cp.chtRoot)
|
StoreChtRoot(self.chainDb, cp.SectionIdx, cp.SectionHead, cp.CHTRoot)
|
||||||
self.odr.ChtIndexer().AddKnownSectionHead(cp.sectionIdx, cp.sectionHead)
|
self.odr.ChtIndexer().AddKnownSectionHead(cp.SectionIdx, cp.SectionHead)
|
||||||
}
|
}
|
||||||
if self.odr.BloomTrieIndexer() != nil {
|
if self.odr.BloomTrieIndexer() != nil {
|
||||||
StoreBloomTrieRoot(self.chainDb, cp.sectionIdx, cp.sectionHead, cp.bloomTrieRoot)
|
StoreBloomTrieRoot(self.chainDb, cp.SectionIdx, cp.SectionHead, cp.BloomRoot)
|
||||||
self.odr.BloomTrieIndexer().AddKnownSectionHead(cp.sectionIdx, cp.sectionHead)
|
self.odr.BloomTrieIndexer().AddKnownSectionHead(cp.SectionIdx, cp.SectionHead)
|
||||||
}
|
}
|
||||||
if self.odr.BloomIndexer() != nil {
|
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 {
|
func (self *LightChain) getProcInterrupt() bool {
|
||||||
|
|
@ -464,23 +464,33 @@ func (self *LightChain) GetHeaderByNumberOdr(ctx context.Context, number uint64)
|
||||||
func (self *LightChain) Config() *params.ChainConfig { return self.hc.Config() }
|
func (self *LightChain) Config() *params.ChainConfig { return self.hc.Config() }
|
||||||
|
|
||||||
func (self *LightChain) SyncCht(ctx context.Context) bool {
|
func (self *LightChain) SyncCht(ctx context.Context) bool {
|
||||||
|
// If we don't have a CHT indexer, abort
|
||||||
if self.odr.ChtIndexer() == nil {
|
if self.odr.ChtIndexer() == nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
headNum := self.CurrentHeader().Number.Uint64()
|
// Ensure the remote CHT head is ahead of us
|
||||||
chtCount, _, _ := self.odr.ChtIndexer().Sections()
|
head := self.CurrentHeader().Number.Uint64()
|
||||||
if headNum+1 < chtCount*CHTFrequencyClient {
|
sections, _, _ := self.odr.ChtIndexer().Sections()
|
||||||
num := chtCount*CHTFrequencyClient - 1
|
|
||||||
header, err := GetHeaderByNumber(ctx, self.odr, num)
|
latest := sections*CHTFrequencyClient - 1
|
||||||
if header != nil && err == nil {
|
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()
|
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() {
|
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)
|
self.hc.SetCurrentHeader(header)
|
||||||
}
|
}
|
||||||
self.mu.Unlock()
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ package light
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"math/big"
|
"math/big"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
|
@ -33,6 +34,9 @@ import (
|
||||||
// service is not required.
|
// service is not required.
|
||||||
var NoOdr = context.Background()
|
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
|
// OdrBackend is an interface to a backend service that handles ODR retrievals type
|
||||||
type OdrBackend interface {
|
type OdrBackend interface {
|
||||||
Database() ethdb.Database
|
Database() ethdb.Database
|
||||||
|
|
|
||||||
|
|
@ -17,8 +17,10 @@
|
||||||
package light
|
package light
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"math/big"
|
"math/big"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
|
@ -47,37 +49,38 @@ const (
|
||||||
HelperTrieProcessConfirmations = 256 // number of confirmations before a HelperTrie is generated
|
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
|
// 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.
|
// and avoid downloading the entire header chain while still being able to securely access old headers/logs.
|
||||||
type trustedCheckpoint struct {
|
type TrustedCheckpoint struct {
|
||||||
name string
|
name string
|
||||||
sectionIdx uint64
|
SectionIdx uint64
|
||||||
sectionHead, chtRoot, bloomTrieRoot common.Hash
|
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
|
// trustedCheckpoints associates each known checkpoint with the genesis hash of the chain it belongs to
|
||||||
var trustedCheckpoints = map[common.Hash]trustedCheckpoint{
|
var trustedCheckpoints = map[common.Hash]TrustedCheckpoint{
|
||||||
params.MainnetGenesisHash: mainnetCheckpoint,
|
params.MainnetGenesisHash: {
|
||||||
params.TestnetGenesisHash: ropstenCheckpoint,
|
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 (
|
var (
|
||||||
|
|
@ -119,7 +122,8 @@ func StoreChtRoot(db ethdb.Database, sectionIdx uint64, sectionHead, root common
|
||||||
|
|
||||||
// ChtIndexerBackend implements core.ChainIndexerBackend
|
// ChtIndexerBackend implements core.ChainIndexerBackend
|
||||||
type ChtIndexerBackend struct {
|
type ChtIndexerBackend struct {
|
||||||
diskdb ethdb.Database
|
diskdb, trieTable ethdb.Database
|
||||||
|
odr OdrBackend
|
||||||
triedb *trie.Database
|
triedb *trie.Database
|
||||||
section, sectionSize uint64
|
section, sectionSize uint64
|
||||||
lastHash common.Hash
|
lastHash common.Hash
|
||||||
|
|
@ -127,7 +131,7 @@ type ChtIndexerBackend struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewBloomTrieIndexer creates a BloomTrie chain indexer
|
// 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
|
var sectionSize, confirmReq uint64
|
||||||
if clientMode {
|
if clientMode {
|
||||||
sectionSize = CHTFrequencyClient
|
sectionSize = CHTFrequencyClient
|
||||||
|
|
@ -137,28 +141,64 @@ func NewChtIndexer(db ethdb.Database, clientMode bool) *core.ChainIndexer {
|
||||||
confirmReq = HelperTrieProcessConfirmations
|
confirmReq = HelperTrieProcessConfirmations
|
||||||
}
|
}
|
||||||
idb := ethdb.NewTable(db, "chtIndex-")
|
idb := ethdb.NewTable(db, "chtIndex-")
|
||||||
|
trieTable := ethdb.NewTable(db, ChtTablePrefix)
|
||||||
backend := &ChtIndexerBackend{
|
backend := &ChtIndexerBackend{
|
||||||
diskdb: db,
|
diskdb: db,
|
||||||
triedb: trie.NewDatabase(ethdb.NewTable(db, ChtTablePrefix)),
|
odr: odr,
|
||||||
|
trieTable: trieTable,
|
||||||
|
triedb: trie.NewDatabase(trieTable),
|
||||||
sectionSize: sectionSize,
|
sectionSize: sectionSize,
|
||||||
}
|
}
|
||||||
return core.NewChainIndexer(db, idb, backend, sectionSize, confirmReq, time.Millisecond*100, "cht")
|
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
|
// 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
|
var root common.Hash
|
||||||
if section > 0 {
|
if section > 0 {
|
||||||
root = GetChtRoot(c.diskdb, section-1, lastSectionHead)
|
root = GetChtRoot(c.diskdb, section-1, lastSectionHead)
|
||||||
}
|
}
|
||||||
var err error
|
var err error
|
||||||
c.trie, err = trie.New(root, c.triedb)
|
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
|
c.section = section
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Process implements core.ChainIndexerBackend
|
// 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()
|
hash, num := header.Hash(), header.Number.Uint64()
|
||||||
c.lastHash = hash
|
c.lastHash = hash
|
||||||
|
|
||||||
|
|
@ -170,6 +210,7 @@ func (c *ChtIndexerBackend) Process(header *types.Header) {
|
||||||
binary.BigEndian.PutUint64(encNumber[:], num)
|
binary.BigEndian.PutUint64(encNumber[:], num)
|
||||||
data, _ := rlp.EncodeToBytes(ChtNode{hash, td})
|
data, _ := rlp.EncodeToBytes(ChtNode{hash, td})
|
||||||
c.trie.Update(encNumber[:], data)
|
c.trie.Update(encNumber[:], data)
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Commit implements core.ChainIndexerBackend
|
// Commit implements core.ChainIndexerBackend
|
||||||
|
|
@ -181,7 +222,7 @@ func (c *ChtIndexerBackend) Commit() error {
|
||||||
c.triedb.Commit(root, false)
|
c.triedb.Commit(root, false)
|
||||||
|
|
||||||
if ((c.section+1)*c.sectionSize)%CHTFrequencyClient == 0 {
|
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)
|
StoreChtRoot(c.diskdb, c.section, c.lastHash, root)
|
||||||
return nil
|
return nil
|
||||||
|
|
@ -190,7 +231,6 @@ func (c *ChtIndexerBackend) Commit() error {
|
||||||
const (
|
const (
|
||||||
BloomTrieFrequency = 32768
|
BloomTrieFrequency = 32768
|
||||||
ethBloomBitsSection = 4096
|
ethBloomBitsSection = 4096
|
||||||
ethBloomBitsConfirmations = 256
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|
@ -215,7 +255,8 @@ func StoreBloomTrieRoot(db ethdb.Database, sectionIdx uint64, sectionHead, root
|
||||||
|
|
||||||
// BloomTrieIndexerBackend implements core.ChainIndexerBackend
|
// BloomTrieIndexerBackend implements core.ChainIndexerBackend
|
||||||
type BloomTrieIndexerBackend struct {
|
type BloomTrieIndexerBackend struct {
|
||||||
diskdb ethdb.Database
|
diskdb, trieTable ethdb.Database
|
||||||
|
odr OdrBackend
|
||||||
triedb *trie.Database
|
triedb *trie.Database
|
||||||
section, parentSectionSize, bloomTrieRatio uint64
|
section, parentSectionSize, bloomTrieRatio uint64
|
||||||
trie *trie.Trie
|
trie *trie.Trie
|
||||||
|
|
@ -223,44 +264,98 @@ type BloomTrieIndexerBackend struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewBloomTrieIndexer creates a BloomTrie chain indexer
|
// 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{
|
backend := &BloomTrieIndexerBackend{
|
||||||
diskdb: db,
|
diskdb: db,
|
||||||
triedb: trie.NewDatabase(ethdb.NewTable(db, BloomTrieTablePrefix)),
|
odr: odr,
|
||||||
|
trieTable: trieTable,
|
||||||
|
triedb: trie.NewDatabase(trieTable),
|
||||||
}
|
}
|
||||||
idb := ethdb.NewTable(db, "bltIndex-")
|
idb := ethdb.NewTable(db, "bltIndex-")
|
||||||
|
|
||||||
var confirmReq uint64
|
|
||||||
if clientMode {
|
if clientMode {
|
||||||
backend.parentSectionSize = BloomTrieFrequency
|
backend.parentSectionSize = BloomTrieFrequency
|
||||||
confirmReq = HelperTrieConfirmations
|
|
||||||
} else {
|
} else {
|
||||||
backend.parentSectionSize = ethBloomBitsSection
|
backend.parentSectionSize = ethBloomBitsSection
|
||||||
confirmReq = HelperTrieProcessConfirmations
|
|
||||||
}
|
}
|
||||||
backend.bloomTrieRatio = BloomTrieFrequency / backend.parentSectionSize
|
backend.bloomTrieRatio = BloomTrieFrequency / backend.parentSectionSize
|
||||||
backend.sectionHeads = make([]common.Hash, backend.bloomTrieRatio)
|
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
|
// 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
|
var root common.Hash
|
||||||
if section > 0 {
|
if section > 0 {
|
||||||
root = GetBloomTrieRoot(b.diskdb, section-1, lastSectionHead)
|
root = GetBloomTrieRoot(b.diskdb, section-1, lastSectionHead)
|
||||||
}
|
}
|
||||||
var err error
|
var err error
|
||||||
b.trie, err = trie.New(root, b.triedb)
|
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
|
b.section = section
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Process implements core.ChainIndexerBackend
|
// 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
|
num := header.Number.Uint64() - b.section*BloomTrieFrequency
|
||||||
if (num+1)%b.parentSectionSize == 0 {
|
if (num+1)%b.parentSectionSize == 0 {
|
||||||
b.sectionHeads[num/b.parentSectionSize] = header.Hash()
|
b.sectionHeads[num/b.parentSectionSize] = header.Hash()
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Commit implements core.ChainIndexerBackend
|
// Commit implements core.ChainIndexerBackend
|
||||||
|
|
@ -300,7 +395,7 @@ func (b *BloomTrieIndexerBackend) Commit() error {
|
||||||
b.triedb.Commit(root, false)
|
b.triedb.Commit(root, false)
|
||||||
|
|
||||||
sectionHead := b.sectionHeads[b.bloomTrieRatio-1]
|
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)
|
StoreBloomTrieRoot(b.diskdb, b.section, sectionHead, root)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ package miner
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/consensus"
|
"github.com/ethereum/go-ethereum/consensus"
|
||||||
|
|
@ -51,13 +52,13 @@ type Miner struct {
|
||||||
shouldStart int32 // should start indicates whether we should start after sync
|
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{
|
miner := &Miner{
|
||||||
eth: eth,
|
eth: eth,
|
||||||
mux: mux,
|
mux: mux,
|
||||||
engine: engine,
|
engine: engine,
|
||||||
exitCh: make(chan struct{}),
|
exitCh: make(chan struct{}),
|
||||||
worker: newWorker(config, engine, eth, mux),
|
worker: newWorker(config, engine, eth, mux, recommit),
|
||||||
canStart: 1,
|
canStart: 1,
|
||||||
}
|
}
|
||||||
go miner.update()
|
go miner.update()
|
||||||
|
|
@ -144,6 +145,11 @@ func (self *Miner) SetExtra(extra []byte) error {
|
||||||
return nil
|
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.
|
// Pending returns the currently pending block and associated state.
|
||||||
func (self *Miner) Pending() (*types.Block, *state.StateDB) {
|
func (self *Miner) Pending() (*types.Block, *state.StateDB) {
|
||||||
return self.worker.pending()
|
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"
|
"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.
|
// 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 retrieves the canonical header associated with a block number.
|
||||||
GetHeaderByNumber(number uint64) *types.Header
|
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
|
// 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
|
// 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.
|
// has a high enough guarantee to not be reorged out of the canonical chain.
|
||||||
type unconfirmedBlocks struct {
|
type unconfirmedBlocks struct {
|
||||||
chain headerRetriever // Blockchain to verify canonical status through
|
chain chainRetriever // Blockchain to verify canonical status through
|
||||||
depth uint // Depth after which to discard previous blocks
|
depth uint // Depth after which to discard previous blocks
|
||||||
blocks *ring.Ring // Block infos to allow canonical chain cross checks
|
blocks *ring.Ring // Block infos to allow canonical chain cross checks
|
||||||
lock sync.RWMutex // Protects the fields from concurrent access
|
lock sync.RWMutex // Protects the fields from concurrent access
|
||||||
}
|
}
|
||||||
|
|
||||||
// newUnconfirmedBlocks returns new data structure to track currently unconfirmed blocks.
|
// 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{
|
return &unconfirmedBlocks{
|
||||||
chain: chain,
|
chain: chain,
|
||||||
depth: depth,
|
depth: depth,
|
||||||
|
|
@ -103,7 +106,23 @@ func (set *unconfirmedBlocks) Shift(height uint64) {
|
||||||
case header.Hash() == next.hash:
|
case header.Hash() == next.hash:
|
||||||
log.Info("🔗 block reached canonical chain", "number", next.index, "hash", next.hash)
|
log.Info("🔗 block reached canonical chain", "number", next.index, "hash", next.hash)
|
||||||
default:
|
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
|
// Drop the block out of the ring
|
||||||
if set.blocks.Value == set.blocks.Next().Value {
|
if set.blocks.Value == set.blocks.Next().Value {
|
||||||
|
|
|
||||||
|
|
@ -23,11 +23,14 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"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.
|
// 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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -36,7 +39,7 @@ func (r *noopHeaderRetriever) GetHeaderByNumber(number uint64) *types.Header {
|
||||||
func TestUnconfirmedInsertBounds(t *testing.T) {
|
func TestUnconfirmedInsertBounds(t *testing.T) {
|
||||||
limit := uint(10)
|
limit := uint(10)
|
||||||
|
|
||||||
pool := newUnconfirmedBlocks(new(noopHeaderRetriever), limit)
|
pool := newUnconfirmedBlocks(new(noopChainRetriever), limit)
|
||||||
for depth := uint64(0); depth < 2*uint64(limit); depth++ {
|
for depth := uint64(0); depth < 2*uint64(limit); depth++ {
|
||||||
// Insert multiple blocks for the same level just to stress it
|
// Insert multiple blocks for the same level just to stress it
|
||||||
for i := 0; i < int(depth); i++ {
|
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
|
// Create a pool with a few blocks on various depths
|
||||||
limit, start := uint(10), uint64(25)
|
limit, start := uint(10), uint64(25)
|
||||||
|
|
||||||
pool := newUnconfirmedBlocks(new(noopHeaderRetriever), limit)
|
pool := newUnconfirmedBlocks(new(noopChainRetriever), limit)
|
||||||
for depth := start; depth < start+uint64(limit); depth++ {
|
for depth := start; depth < start+uint64(limit); depth++ {
|
||||||
pool.Insert(depth, common.Hash([32]byte{byte(depth)}))
|
pool.Insert(depth, common.Hash([32]byte{byte(depth)}))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
631
miner/worker.go
631
miner/worker.go
|
|
@ -40,19 +40,45 @@ import (
|
||||||
const (
|
const (
|
||||||
// resultQueueSize is the size of channel listening to sealing result.
|
// resultQueueSize is the size of channel listening to sealing result.
|
||||||
resultQueueSize = 10
|
resultQueueSize = 10
|
||||||
|
|
||||||
// txChanSize is the size of channel listening to NewTxsEvent.
|
// txChanSize is the size of channel listening to NewTxsEvent.
|
||||||
// The number is referenced from the size of tx pool.
|
// The number is referenced from the size of tx pool.
|
||||||
txChanSize = 4096
|
txChanSize = 4096
|
||||||
|
|
||||||
// chainHeadChanSize is the size of channel listening to ChainHeadEvent.
|
// chainHeadChanSize is the size of channel listening to ChainHeadEvent.
|
||||||
chainHeadChanSize = 10
|
chainHeadChanSize = 10
|
||||||
|
|
||||||
// chainSideChanSize is the size of channel listening to ChainSideEvent.
|
// chainSideChanSize is the size of channel listening to ChainSideEvent.
|
||||||
chainSideChanSize = 10
|
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.
|
// environment is the worker's current environment and holds all of the current state information.
|
||||||
type Env struct {
|
type environment struct {
|
||||||
config *params.ChainConfig
|
|
||||||
signer types.Signer
|
signer types.Signer
|
||||||
|
|
||||||
state *state.StateDB // apply state changes here
|
state *state.StateDB // apply state changes here
|
||||||
|
|
@ -67,105 +93,6 @@ type Env struct {
|
||||||
receipts []*types.Receipt
|
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.
|
// task contains all information for consensus engine sealing and result submitting.
|
||||||
type task struct {
|
type task struct {
|
||||||
receipts []*types.Receipt
|
receipts []*types.Receipt
|
||||||
|
|
@ -174,6 +101,24 @@ type task struct {
|
||||||
createdAt time.Time
|
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
|
// worker is the main object which takes care of submitting new work to consensus engine
|
||||||
// and gathering the sealing result.
|
// and gathering the sealing result.
|
||||||
type worker struct {
|
type worker struct {
|
||||||
|
|
@ -192,12 +137,15 @@ type worker struct {
|
||||||
chainSideSub event.Subscription
|
chainSideSub event.Subscription
|
||||||
|
|
||||||
// Channels
|
// Channels
|
||||||
newWork chan struct{}
|
newWorkCh chan *newWorkReq
|
||||||
taskCh chan *task
|
taskCh chan *task
|
||||||
resultCh chan *task
|
resultCh chan *task
|
||||||
|
startCh chan struct{}
|
||||||
exitCh 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.
|
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.
|
unconfirmed *unconfirmedBlocks // A set of locally mined blocks pending canonicalness confirmations.
|
||||||
|
|
||||||
|
|
@ -205,19 +153,25 @@ type worker struct {
|
||||||
coinbase common.Address
|
coinbase common.Address
|
||||||
extra []byte
|
extra []byte
|
||||||
|
|
||||||
|
pendingMu sync.RWMutex
|
||||||
|
pendingTasks map[common.Hash]*task
|
||||||
|
|
||||||
snapshotMu sync.RWMutex // The lock used to protect the block snapshot and state snapshot
|
snapshotMu sync.RWMutex // The lock used to protect the block snapshot and state snapshot
|
||||||
snapshotBlock *types.Block
|
snapshotBlock *types.Block
|
||||||
snapshotState *state.StateDB
|
snapshotState *state.StateDB
|
||||||
|
|
||||||
// atomic status counters
|
// atomic status counters
|
||||||
running int32 // The indicator whether the consensus engine is running or not.
|
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
|
// Test hooks
|
||||||
newTaskHook func(*task) // Method to call upon receiving a new sealing task
|
newTaskHook func(*task) // Method to call upon receiving a new sealing task.
|
||||||
fullTaskInterval func() // Method to call before pushing the full 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{
|
worker := &worker{
|
||||||
config: config,
|
config: config,
|
||||||
engine: engine,
|
engine: engine,
|
||||||
|
|
@ -226,13 +180,17 @@ func newWorker(config *params.ChainConfig, engine consensus.Engine, eth Backend,
|
||||||
chain: eth.BlockChain(),
|
chain: eth.BlockChain(),
|
||||||
possibleUncles: make(map[common.Hash]*types.Block),
|
possibleUncles: make(map[common.Hash]*types.Block),
|
||||||
unconfirmed: newUnconfirmedBlocks(eth.BlockChain(), miningLogAtDepth),
|
unconfirmed: newUnconfirmedBlocks(eth.BlockChain(), miningLogAtDepth),
|
||||||
|
pendingTasks: make(map[common.Hash]*task),
|
||||||
txsCh: make(chan core.NewTxsEvent, txChanSize),
|
txsCh: make(chan core.NewTxsEvent, txChanSize),
|
||||||
chainHeadCh: make(chan core.ChainHeadEvent, chainHeadChanSize),
|
chainHeadCh: make(chan core.ChainHeadEvent, chainHeadChanSize),
|
||||||
chainSideCh: make(chan core.ChainSideEvent, chainSideChanSize),
|
chainSideCh: make(chan core.ChainSideEvent, chainSideChanSize),
|
||||||
newWork: make(chan struct{}, 1),
|
newWorkCh: make(chan *newWorkReq),
|
||||||
taskCh: make(chan *task),
|
taskCh: make(chan *task),
|
||||||
resultCh: make(chan *task, resultQueueSize),
|
resultCh: make(chan *task, resultQueueSize),
|
||||||
exitCh: make(chan struct{}),
|
exitCh: make(chan struct{}),
|
||||||
|
startCh: make(chan struct{}, 1),
|
||||||
|
resubmitIntervalCh: make(chan time.Duration),
|
||||||
|
resubmitAdjustCh: make(chan *intervalAdjust, resubmitAdjustChanSize),
|
||||||
}
|
}
|
||||||
// Subscribe NewTxsEvent for tx pool
|
// Subscribe NewTxsEvent for tx pool
|
||||||
worker.txsSub = eth.TxPool().SubscribeNewTxsEvent(worker.txsCh)
|
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.chainHeadSub = eth.BlockChain().SubscribeChainHeadEvent(worker.chainHeadCh)
|
||||||
worker.chainSideSub = eth.BlockChain().SubscribeChainSideEvent(worker.chainSideCh)
|
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.mainLoop()
|
||||||
|
go worker.newWorkLoop(recommit)
|
||||||
go worker.resultLoop()
|
go worker.resultLoop()
|
||||||
go worker.taskLoop()
|
go worker.taskLoop()
|
||||||
|
|
||||||
// Submit first work to initialize pending state.
|
// Submit first work to initialize pending state.
|
||||||
worker.newWork <- struct{}{}
|
worker.startCh <- struct{}{}
|
||||||
|
|
||||||
return worker
|
return worker
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -263,6 +229,11 @@ func (w *worker) setExtra(extra []byte) {
|
||||||
w.extra = extra
|
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.
|
// pending returns the pending state and corresponding block.
|
||||||
func (w *worker) pending() (*types.Block, *state.StateDB) {
|
func (w *worker) pending() (*types.Block, *state.StateDB) {
|
||||||
// return a snapshot to avoid contention on currentMu mutex
|
// 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.
|
// start sets the running status as 1 and triggers new work submitting.
|
||||||
func (w *worker) start() {
|
func (w *worker) start() {
|
||||||
atomic.StoreInt32(&w.running, 1)
|
atomic.StoreInt32(&w.running, 1)
|
||||||
w.newWork <- struct{}{}
|
w.startCh <- struct{}{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// stop sets the running status as 0.
|
// 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.
|
// mainLoop is a standalone goroutine to regenerate the sealing task based on the received event.
|
||||||
func (w *worker) mainLoop() {
|
func (w *worker) mainLoop() {
|
||||||
defer w.txsSub.Unsubscribe()
|
defer w.txsSub.Unsubscribe()
|
||||||
|
|
@ -320,17 +400,36 @@ func (w *worker) mainLoop() {
|
||||||
|
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-w.newWork:
|
case req := <-w.newWorkCh:
|
||||||
// Submit a work when the worker is created or started.
|
w.commitNewWork(req.interrupt, req.noempty)
|
||||||
w.commitNewWork()
|
|
||||||
|
|
||||||
case <-w.chainHeadCh:
|
|
||||||
// Resubmit a work for new cycle once worker receives chain head event.
|
|
||||||
w.commitNewWork()
|
|
||||||
|
|
||||||
case ev := <-w.chainSideCh:
|
case ev := <-w.chainSideCh:
|
||||||
|
if _, exist := w.possibleUncles[ev.Block.Hash()]; exist {
|
||||||
|
continue
|
||||||
|
}
|
||||||
// Add side block to possible uncle block set.
|
// Add side block to possible uncle block set.
|
||||||
w.possibleUncles[ev.Block.Hash()] = ev.Block
|
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:
|
case ev := <-w.txsCh:
|
||||||
// Apply transactions to the pending state if we're not mining.
|
// 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
|
// already included in the current mining block. These transactions will
|
||||||
// be automatically eliminated.
|
// be automatically eliminated.
|
||||||
if !w.isRunning() && w.current != nil {
|
if !w.isRunning() && w.current != nil {
|
||||||
w.mu.Lock()
|
w.mu.RLock()
|
||||||
coinbase := w.coinbase
|
coinbase := w.coinbase
|
||||||
w.mu.Unlock()
|
w.mu.RUnlock()
|
||||||
|
|
||||||
txs := make(map[common.Address]types.Transactions)
|
txs := make(map[common.Address]types.Transactions)
|
||||||
for _, tx := range ev.Txs {
|
for _, tx := range ev.Txs {
|
||||||
|
|
@ -349,14 +448,15 @@ func (w *worker) mainLoop() {
|
||||||
txs[acc] = append(txs[acc], tx)
|
txs[acc] = append(txs[acc], tx)
|
||||||
}
|
}
|
||||||
txset := types.NewTransactionsByPriceAndNonce(w.current.signer, txs)
|
txset := types.NewTransactionsByPriceAndNonce(w.current.signer, txs)
|
||||||
w.current.commitTransactions(w.mux, txset, w.chain, coinbase)
|
w.commitTransactions(txset, coinbase, nil)
|
||||||
w.updateSnapshot()
|
w.updateSnapshot()
|
||||||
} else {
|
} else {
|
||||||
// If we're mining, but nothing is being processed, wake on new transactions
|
// If we're mining, but nothing is being processed, wake on new transactions
|
||||||
if w.config.Clique != nil && w.config.Clique.Period == 0 {
|
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
|
// System stopped
|
||||||
case <-w.exitCh:
|
case <-w.exitCh:
|
||||||
|
|
@ -373,31 +473,47 @@ func (w *worker) mainLoop() {
|
||||||
|
|
||||||
// seal pushes a sealing task to consensus engine and submits the result.
|
// seal pushes a sealing task to consensus engine and submits the result.
|
||||||
func (w *worker) seal(t *task, stop <-chan struct{}) {
|
func (w *worker) seal(t *task, stop <-chan struct{}) {
|
||||||
var (
|
if w.skipSealHook != nil && w.skipSealHook(t) {
|
||||||
err error
|
return
|
||||||
res *task
|
}
|
||||||
)
|
// 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 t.block, err = w.engine.Seal(w.chain, t.block, stop); t.block != nil {
|
if block, err := w.engine.Seal(w.chain, t.block, stop); block != nil {
|
||||||
log.Info("Successfully sealed new block", "number", t.block.Number(), "hash", t.block.Hash(),
|
sealhash := w.engine.SealHash(block.Header())
|
||||||
"elapsed", common.PrettyDuration(time.Since(t.createdAt)))
|
w.pendingMu.RLock()
|
||||||
res = t
|
task, exist := w.pendingTasks[sealhash]
|
||||||
} else {
|
w.pendingMu.RUnlock()
|
||||||
if err != nil {
|
if !exist {
|
||||||
log.Warn("Block sealing failed", "err", err)
|
log.Error("Block found but no relative pending task", "number", block.Number(), "sealhash", sealhash, "hash", block.Hash())
|
||||||
}
|
return
|
||||||
res = nil
|
|
||||||
}
|
}
|
||||||
|
// 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 {
|
select {
|
||||||
case w.resultCh <- res:
|
case w.resultCh <- task:
|
||||||
case <-w.exitCh:
|
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
|
// taskLoop is a standalone goroutine to fetch sealing task from the generator and
|
||||||
// push them to consensus engine.
|
// push them to consensus engine.
|
||||||
func (w *worker) taskLoop() {
|
func (w *worker) taskLoop() {
|
||||||
var stopCh chan struct{}
|
var (
|
||||||
|
stopCh chan struct{}
|
||||||
|
prev common.Hash
|
||||||
|
)
|
||||||
|
|
||||||
// interrupt aborts the in-flight sealing task.
|
// interrupt aborts the in-flight sealing task.
|
||||||
interrupt := func() {
|
interrupt := func() {
|
||||||
|
|
@ -412,8 +528,14 @@ func (w *worker) taskLoop() {
|
||||||
if w.newTaskHook != nil {
|
if w.newTaskHook != nil {
|
||||||
w.newTaskHook(task)
|
w.newTaskHook(task)
|
||||||
}
|
}
|
||||||
|
// Reject duplicate sealing work due to resubmitting.
|
||||||
|
sealHash := w.engine.SealHash(task.block.Header())
|
||||||
|
if sealHash == prev {
|
||||||
|
continue
|
||||||
|
}
|
||||||
interrupt()
|
interrupt()
|
||||||
stopCh = make(chan struct{})
|
stopCh = make(chan struct{})
|
||||||
|
prev = sealHash
|
||||||
go w.seal(task, stopCh)
|
go w.seal(task, stopCh)
|
||||||
case <-w.exitCh:
|
case <-w.exitCh:
|
||||||
interrupt()
|
interrupt()
|
||||||
|
|
@ -428,11 +550,15 @@ func (w *worker) resultLoop() {
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case result := <-w.resultCh:
|
case result := <-w.resultCh:
|
||||||
|
// Short circuit when receiving empty result.
|
||||||
if result == nil {
|
if result == nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
// Short circuit when receiving duplicate result caused by resubmitting.
|
||||||
block := result.block
|
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
|
// Update the block hash in all logs since it is now available and not when the
|
||||||
// receipt/log of individual transactions were created.
|
// receipt/log of individual transactions were created.
|
||||||
for _, r := range result.receipts {
|
for _, r := range result.receipts {
|
||||||
|
|
@ -479,8 +605,7 @@ func (w *worker) makeCurrent(parent *types.Block, header *types.Header) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
env := &Env{
|
env := &environment{
|
||||||
config: w.config,
|
|
||||||
signer: types.NewEIP155Signer(w.config.ChainID),
|
signer: types.NewEIP155Signer(w.config.ChainID),
|
||||||
state: state,
|
state: state,
|
||||||
ancestors: mapset.NewSet(),
|
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.
|
// 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()
|
hash := uncle.Hash()
|
||||||
if env.uncles.Contains(hash) {
|
if env.uncles.Contains(hash) {
|
||||||
return fmt.Errorf("uncle not unique")
|
return fmt.Errorf("uncle not unique")
|
||||||
|
|
@ -537,7 +662,7 @@ func (w *worker) updateSnapshot() {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
uncles = append(uncles, uncle.Header())
|
uncles = append(uncles, uncle.Header())
|
||||||
return true
|
return false
|
||||||
})
|
})
|
||||||
|
|
||||||
w.snapshotBlock = types.NewBlock(
|
w.snapshotBlock = types.NewBlock(
|
||||||
|
|
@ -550,8 +675,135 @@ func (w *worker) updateSnapshot() {
|
||||||
w.snapshotState = w.current.state.Copy()
|
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.
|
// 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()
|
w.mu.RLock()
|
||||||
defer w.mu.RUnlock()
|
defer w.mu.RUnlock()
|
||||||
|
|
||||||
|
|
@ -637,29 +889,10 @@ func (w *worker) commitNewWork() {
|
||||||
delete(w.possibleUncles, hash)
|
delete(w.possibleUncles, hash)
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
if !noempty {
|
||||||
emptyBlock, fullBlock *types.Block
|
|
||||||
emptyState, fullState *state.StateDB
|
|
||||||
)
|
|
||||||
|
|
||||||
// Create an empty block based on temporary copied state for sealing in advance without waiting block
|
// Create an empty block based on temporary copied state for sealing in advance without waiting block
|
||||||
// execution finished.
|
// execution finished.
|
||||||
emptyState = env.state.Copy()
|
w.commit(uncles, nil, false, tstart)
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fill the block with all available pending transactions.
|
// Fill the block with all available pending transactions.
|
||||||
|
|
@ -673,34 +906,66 @@ func (w *worker) commitNewWork() {
|
||||||
w.updateSnapshot()
|
w.updateSnapshot()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
txs := types.NewTransactionsByPriceAndNonce(w.current.signer, pending)
|
// Split the pending transactions into locals and remotes
|
||||||
env.commitTransactions(w.mux, txs, w.chain, w.coinbase)
|
localTxs, remoteTxs := make(map[common.Address]types.Transactions), pending
|
||||||
|
for _, account := range w.eth.TxPool().Locals() {
|
||||||
// Create the full block to seal with the consensus engine
|
if txs := remoteTxs[account]; len(txs) > 0 {
|
||||||
fullState = env.state.Copy()
|
delete(remoteTxs, account)
|
||||||
if fullBlock, err = w.engine.Finalize(w.chain, header, fullState, env.txs, uncles, env.receipts); err != nil {
|
localTxs[account] = txs
|
||||||
log.Error("Failed to finalize block for sealing", "err", err)
|
}
|
||||||
|
}
|
||||||
|
if len(localTxs) > 0 {
|
||||||
|
txs := types.NewTransactionsByPriceAndNonce(w.current.signer, localTxs)
|
||||||
|
if w.commitTransactions(txs, w.coinbase, interrupt) {
|
||||||
return
|
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 len(remoteTxs) > 0 {
|
||||||
if w.isRunning() {
|
txs := types.NewTransactionsByPriceAndNonce(w.current.signer, remoteTxs)
|
||||||
if w.fullTaskInterval != nil {
|
if w.commitTransactions(txs, w.coinbase, interrupt) {
|
||||||
w.fullTaskInterval()
|
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 {
|
select {
|
||||||
case w.taskCh <- &task{receipts: cpy, state: fullState, block: fullBlock, createdAt: time.Now()}:
|
case w.taskCh <- &task{receipts: receipts, state: s, block: block, createdAt: time.Now()}:
|
||||||
w.unconfirmed.Shift(fullBlock.NumberU64() - 1)
|
w.unconfirmed.Shift(block.NumberU64() - 1)
|
||||||
log.Info("Commit new full mining work", "number", fullBlock.Number(), "txs", env.tcount, "uncles", len(uncles), "elapsed", common.PrettyDuration(time.Since(tstart)))
|
|
||||||
|
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:
|
case <-w.exitCh:
|
||||||
log.Info("Worker has exited")
|
log.Info("Worker has exited")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if update {
|
||||||
w.updateSnapshot()
|
w.updateSnapshot()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -59,7 +59,7 @@ func init() {
|
||||||
ethashChainConfig = params.TestChainConfig
|
ethashChainConfig = params.TestChainConfig
|
||||||
cliqueChainConfig = params.TestChainConfig
|
cliqueChainConfig = params.TestChainConfig
|
||||||
cliqueChainConfig.Clique = ¶ms.CliqueConfig{
|
cliqueChainConfig.Clique = ¶ms.CliqueConfig{
|
||||||
Period: 1,
|
Period: 10,
|
||||||
Epoch: 30000,
|
Epoch: 30000,
|
||||||
}
|
}
|
||||||
tx1, _ := types.SignTx(types.NewTransaction(0, acc1Addr, big.NewInt(1000), params.TxGas, nil, nil), types.HomesteadSigner{}, testBankKey)
|
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
|
txPool *core.TxPool
|
||||||
chain *core.BlockChain
|
chain *core.BlockChain
|
||||||
testTxFeed event.Feed
|
testTxFeed event.Feed
|
||||||
|
uncleBlock *types.Block
|
||||||
}
|
}
|
||||||
|
|
||||||
func newTestWorkerBackend(t *testing.T, chainConfig *params.ChainConfig, engine consensus.Engine) *testWorkerBackend {
|
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:
|
default:
|
||||||
t.Fatal("unexpect consensus engine type")
|
t.Fatal("unexpect consensus engine type")
|
||||||
}
|
}
|
||||||
gspec.MustCommit(db)
|
genesis := gspec.MustCommit(db)
|
||||||
|
|
||||||
chain, _ := core.NewBlockChain(db, nil, gspec.Config, engine, vm.Config{})
|
chain, _ := core.NewBlockChain(db, nil, gspec.Config, engine, vm.Config{})
|
||||||
txpool := core.NewTxPool(testTxPoolConfig, chainConfig, chain)
|
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{
|
return &testWorkerBackend{
|
||||||
db: db,
|
db: db,
|
||||||
chain: chain,
|
chain: chain,
|
||||||
txPool: txpool,
|
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) {
|
func newTestWorker(t *testing.T, chainConfig *params.ChainConfig, engine consensus.Engine) (*worker, *testWorkerBackend) {
|
||||||
backend := newTestWorkerBackend(t, chainConfig, engine)
|
backend := newTestWorkerBackend(t, chainConfig, engine)
|
||||||
backend.txPool.AddLocals(pendingTxs)
|
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)
|
w.setEtherbase(testBankAddress)
|
||||||
return w, backend
|
return w, backend
|
||||||
}
|
}
|
||||||
|
|
@ -188,7 +193,7 @@ func testEmptyWork(t *testing.T, chainConfig *params.ChainConfig, engine consens
|
||||||
taskCh <- struct{}{}
|
taskCh <- struct{}{}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
w.fullTaskInterval = func() {
|
w.fullTaskHook = func() {
|
||||||
time.Sleep(100 * time.Millisecond)
|
time.Sleep(100 * time.Millisecond)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -202,11 +207,233 @@ func testEmptyWork(t *testing.T, chainConfig *params.ChainConfig, engine consens
|
||||||
|
|
||||||
w.start()
|
w.start()
|
||||||
for i := 0; i < 2; i += 1 {
|
for i := 0; i < 2; i += 1 {
|
||||||
to := time.NewTimer(time.Second)
|
|
||||||
select {
|
select {
|
||||||
case <-taskCh:
|
case <-taskCh:
|
||||||
case <-to.C:
|
case <-time.NewTimer(time.Second).C:
|
||||||
t.Error("new task timeout")
|
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")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -183,10 +183,7 @@ func (b *Block) GetTime() int64 { return b.block.Time().Int64() }
|
||||||
func (b *Block) GetExtra() []byte { return b.block.Extra() }
|
func (b *Block) GetExtra() []byte { return b.block.Extra() }
|
||||||
func (b *Block) GetMixDigest() *Hash { return &Hash{b.block.MixDigest()} }
|
func (b *Block) GetMixDigest() *Hash { return &Hash{b.block.MixDigest()} }
|
||||||
func (b *Block) GetNonce() int64 { return int64(b.block.Nonce()) }
|
func (b *Block) GetNonce() int64 { return int64(b.block.Nonce()) }
|
||||||
|
|
||||||
func (b *Block) GetHash() *Hash { return &Hash{b.block.Hash()} }
|
func (b *Block) GetHash() *Hash { return &Hash{b.block.Hash()} }
|
||||||
func (b *Block) GetHashNoNonce() *Hash { return &Hash{b.block.HashNoNonce()} }
|
|
||||||
|
|
||||||
func (b *Block) GetHeader() *Header { return &Header{b.block.Header()} }
|
func (b *Block) GetHeader() *Header { return &Header{b.block.Header()} }
|
||||||
func (b *Block) GetUncles() *Headers { return &Headers{b.block.Uncles()} }
|
func (b *Block) GetUncles() *Headers { return &Headers{b.block.Uncles()} }
|
||||||
func (b *Block) GetTransactions() *Transactions { return &Transactions{b.block.Transactions()} }
|
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) {
|
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.
|
// entry, giving users the opportunity to force a resolve operation.
|
||||||
s.static[n.ID] = &dialTask{flags: staticDialedConn, dest: n}
|
s.static[n.ID] = &dialTask{flags: staticDialedConn, dest: n}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,7 @@ type Protocol struct {
|
||||||
// by the protocol.
|
// by the protocol.
|
||||||
Length uint64
|
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
|
// negotiated with a peer. It should read and write messages from
|
||||||
// rw. The Payload for each message must be fully consumed.
|
// rw. The Payload for each message must be fully consumed.
|
||||||
//
|
//
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,7 @@ import (
|
||||||
var (
|
var (
|
||||||
MainnetGenesisHash = common.HexToHash("0xd4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3")
|
MainnetGenesisHash = common.HexToHash("0xd4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3")
|
||||||
TestnetGenesisHash = common.HexToHash("0x41941023680923e0fe4d74a34bdac8141f2540e3ae90623718e47d66d1ca4a2d")
|
TestnetGenesisHash = common.HexToHash("0x41941023680923e0fe4d74a34bdac8141f2540e3ae90623718e47d66d1ca4a2d")
|
||||||
|
RinkebyGenesisHash = common.HexToHash("0x6341fd3daf94b748c72ced5a5b26028f2474f5f00d824504e4fa37a75767e177")
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ import (
|
||||||
const (
|
const (
|
||||||
VersionMajor = 1 // Major version component of the current release
|
VersionMajor = 1 // Major version component of the current release
|
||||||
VersionMinor = 8 // Minor version component of the current release
|
VersionMinor = 8 // Minor version component of the current release
|
||||||
VersionPatch = 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
|
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 (
|
import (
|
||||||
"archive/tar"
|
"archive/tar"
|
||||||
"context"
|
"context"
|
||||||
|
"crypto/ecdsa"
|
||||||
|
"encoding/hex"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"math/big"
|
"math/big"
|
||||||
|
|
@ -43,6 +46,10 @@ import (
|
||||||
opentracing "github.com/opentracing/opentracing-go"
|
opentracing "github.com/opentracing/opentracing-go"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrNotFound = errors.New("not found")
|
||||||
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
apiResolveCount = metrics.NewRegisteredCounter("api.resolve.count", nil)
|
apiResolveCount = metrics.NewRegisteredCounter("api.resolve.count", nil)
|
||||||
apiResolveFail = metrics.NewRegisteredCounter("api.resolve.fail", nil)
|
apiResolveFail = metrics.NewRegisteredCounter("api.resolve.fail", nil)
|
||||||
|
|
@ -227,14 +234,18 @@ type API struct {
|
||||||
resource *mru.Handler
|
resource *mru.Handler
|
||||||
fileStore *storage.FileStore
|
fileStore *storage.FileStore
|
||||||
dns Resolver
|
dns Resolver
|
||||||
|
Decryptor func(context.Context, string) DecryptFunc
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewAPI the api constructor initialises a new API instance.
|
// 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{
|
self = &API{
|
||||||
fileStore: fileStore,
|
fileStore: fileStore,
|
||||||
dns: dns,
|
dns: dns,
|
||||||
resource: resourceHandler,
|
resource: resourceHandler,
|
||||||
|
Decryptor: func(ctx context.Context, credentials string) DecryptFunc {
|
||||||
|
return self.doDecrypt(ctx, credentials, pk)
|
||||||
|
},
|
||||||
}
|
}
|
||||||
return
|
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.
|
// ErrResolve is returned when an URI cannot be resolved from ENS.
|
||||||
type ErrResolve error
|
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.
|
// 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)
|
apiResolveCount.Inc(1)
|
||||||
log.Trace("resolving", "uri", uri.Addr)
|
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
|
return key, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// if DNS is not configured, check if the address is a hash
|
addr, err := a.Resolve(ctx, uri.Addr)
|
||||||
if a.dns == nil {
|
if err != 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)
|
|
||||||
return nil, err
|
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
|
// 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
|
// Get uses iterative manifest retrieval and prefix matching
|
||||||
// to resolve basePath to content using FileStore retrieve
|
// to resolve basePath to content using FileStore retrieve
|
||||||
// it returns a section reader, mimeType, status, the key of the actual content and an error
|
// 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)
|
log.Debug("api.get", "key", manifestAddr, "path", path)
|
||||||
apiGetCount.Inc(1)
|
apiGetCount.Inc(1)
|
||||||
trie, err := loadManifest(ctx, a.fileStore, manifestAddr, nil)
|
trie, err := loadManifest(ctx, a.fileStore, manifestAddr, nil, decrypt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
apiGetNotFound.Inc(1)
|
apiGetNotFound.Inc(1)
|
||||||
status = http.StatusNotFound
|
status = http.StatusNotFound
|
||||||
|
|
@ -347,6 +396,16 @@ func (a *API) Get(ctx context.Context, manifestAddr storage.Address, path string
|
||||||
|
|
||||||
if entry != nil {
|
if entry != nil {
|
||||||
log.Debug("trie got entry", "key", manifestAddr, "path", path, "entry.Hash", entry.Hash)
|
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
|
// we need to do some extra work if this is a mutable resource manifest
|
||||||
if entry.ContentType == ResourceContentType {
|
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)
|
log.Trace("resource is multihash", "key", manifestAddr)
|
||||||
|
|
||||||
// get the manifest the multihash digest points to
|
// 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 {
|
if err != nil {
|
||||||
apiGetNotFound.Inc(1)
|
apiGetNotFound.Inc(1)
|
||||||
status = http.StatusNotFound
|
status = http.StatusNotFound
|
||||||
|
|
@ -451,7 +510,7 @@ func (a *API) Delete(ctx context.Context, addr string, path string) (storage.Add
|
||||||
apiDeleteFail.Inc(1)
|
apiDeleteFail.Inc(1)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
key, err := a.Resolve(ctx, uri)
|
key, err := a.ResolveURI(ctx, uri, EMPTY_CREDENTIALS)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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
|
// GetDirectoryTar fetches a requested directory as a tarstream
|
||||||
// it returns an io.Reader and an error. Do not forget to Close() the returned ReadCloser
|
// 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)
|
apiGetTarCount.Inc(1)
|
||||||
addr, err := a.Resolve(ctx, uri)
|
addr, err := a.Resolve(ctx, uri.Addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
walker, err := a.NewManifestWalker(ctx, addr, nil)
|
walker, err := a.NewManifestWalker(ctx, addr, decrypt, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
apiGetTarFail.Inc(1)
|
apiGetTarFail.Inc(1)
|
||||||
return nil, err
|
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
|
// GetManifestList lists the manifest entries for the specified address and prefix
|
||||||
// and returns it as a ManifestList
|
// 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)
|
apiManifestListCount.Inc(1)
|
||||||
walker, err := a.NewManifestWalker(ctx, addr, nil)
|
walker, err := a.NewManifestWalker(ctx, addr, decryptor, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
apiManifestListFail.Inc(1)
|
apiManifestListFail.Inc(1)
|
||||||
return ManifestList{}, err
|
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) {
|
func (a *API) Modify(ctx context.Context, addr storage.Address, path, contentHash, contentType string) (storage.Address, error) {
|
||||||
apiModifyCount.Inc(1)
|
apiModifyCount.Inc(1)
|
||||||
quitC := make(chan bool)
|
quitC := make(chan bool)
|
||||||
trie, err := loadManifest(ctx, a.fileStore, addr, quitC)
|
trie, err := loadManifest(ctx, a.fileStore, addr, quitC, NOOPDecrypt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
apiModifyFail.Inc(1)
|
apiModifyFail.Inc(1)
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
@ -663,7 +722,7 @@ func (a *API) AddFile(ctx context.Context, mhash, path, fname string, content []
|
||||||
apiAddFileFail.Inc(1)
|
apiAddFileFail.Inc(1)
|
||||||
return nil, "", err
|
return nil, "", err
|
||||||
}
|
}
|
||||||
mkey, err := a.Resolve(ctx, uri)
|
mkey, err := a.ResolveURI(ctx, uri, EMPTY_CREDENTIALS)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
apiAddFileFail.Inc(1)
|
apiAddFileFail.Inc(1)
|
||||||
return nil, "", err
|
return nil, "", err
|
||||||
|
|
@ -770,7 +829,7 @@ func (a *API) RemoveFile(ctx context.Context, mhash string, path string, fname s
|
||||||
apiRmFileFail.Inc(1)
|
apiRmFileFail.Inc(1)
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
mkey, err := a.Resolve(ctx, uri)
|
mkey, err := a.ResolveURI(ctx, uri, EMPTY_CREDENTIALS)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
apiRmFileFail.Inc(1)
|
apiRmFileFail.Inc(1)
|
||||||
return "", err
|
return "", err
|
||||||
|
|
@ -837,7 +896,7 @@ func (a *API) AppendFile(ctx context.Context, mhash, path, fname string, existin
|
||||||
apiAppendFileFail.Inc(1)
|
apiAppendFileFail.Inc(1)
|
||||||
return nil, "", err
|
return nil, "", err
|
||||||
}
|
}
|
||||||
mkey, err := a.Resolve(ctx, uri)
|
mkey, err := a.ResolveURI(ctx, uri, EMPTY_CREDENTIALS)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
apiAppendFileFail.Inc(1)
|
apiAppendFileFail.Inc(1)
|
||||||
return nil, "", err
|
return nil, "", err
|
||||||
|
|
@ -891,13 +950,13 @@ func (a *API) BuildDirectoryTree(ctx context.Context, mhash string, nameresolver
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
addr, err = a.Resolve(ctx, uri)
|
addr, err = a.Resolve(ctx, uri.Addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
quitC := make(chan bool)
|
quitC := make(chan bool)
|
||||||
rootTrie, err := loadManifest(ctx, a.fileStore, addr, quitC)
|
rootTrie, err := loadManifest(ctx, a.fileStore, addr, quitC, NOOPDecrypt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, fmt.Errorf("can't load manifest %v: %v", addr.String(), err)
|
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.
|
// 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) {
|
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 {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("cannot load resource manifest: %v", err)
|
return nil, fmt.Errorf("cannot load resource manifest: %v", err)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ package api
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
|
@ -28,10 +29,17 @@ import (
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/swarm/log"
|
"github.com/ethereum/go-ethereum/log"
|
||||||
|
"github.com/ethereum/go-ethereum/swarm/sctx"
|
||||||
"github.com/ethereum/go-ethereum/swarm/storage"
|
"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)) {
|
func testAPI(t *testing.T, f func(*API, bool)) {
|
||||||
datadir, err := ioutil.TempDir("", "bzz-test")
|
datadir, err := ioutil.TempDir("", "bzz-test")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -42,7 +50,7 @@ func testAPI(t *testing.T, f func(*API, bool)) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
api := NewAPI(fileStore, nil, nil)
|
api := NewAPI(fileStore, nil, nil, nil)
|
||||||
f(api, false)
|
f(api, false)
|
||||||
f(api, true)
|
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 {
|
func testGet(t *testing.T, api *API, bzzhash, path string) *testResponse {
|
||||||
addr := storage.Address(common.Hex2Bytes(bzzhash))
|
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 {
|
if err != nil {
|
||||||
t.Fatalf("unexpected error: %v", err)
|
t.Fatalf("unexpected error: %v", err)
|
||||||
}
|
}
|
||||||
|
|
@ -229,7 +237,7 @@ func TestAPIResolve(t *testing.T) {
|
||||||
if x.immutable {
|
if x.immutable {
|
||||||
uri.Scheme = "bzz-immutable"
|
uri.Scheme = "bzz-immutable"
|
||||||
}
|
}
|
||||||
res, err := api.Resolve(context.TODO(), uri)
|
res, err := api.ResolveURI(context.TODO(), uri, "")
|
||||||
if err == nil {
|
if err == nil {
|
||||||
if x.expectErr != nil {
|
if x.expectErr != nil {
|
||||||
t.Fatalf("expected error %q, got result %q", x.expectErr, res)
|
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)
|
DefaultClient = NewClient(DefaultGateway)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrUnauthorized = errors.New("unauthorized")
|
||||||
|
)
|
||||||
|
|
||||||
func NewClient(gateway string) *Client {
|
func NewClient(gateway string) *Client {
|
||||||
return &Client{
|
return &Client{
|
||||||
Gateway: gateway,
|
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
|
// DownloadDirectory downloads the files contained in a swarm manifest under
|
||||||
// the given path into a local directory (existing files will be overwritten)
|
// 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)
|
stat, err := os.Stat(destDir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
@ -201,13 +205,20 @@ func (c *Client) DownloadDirectory(hash, path, destDir string) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if credentials != "" {
|
||||||
|
req.SetBasicAuth("", credentials)
|
||||||
|
}
|
||||||
req.Header.Set("Accept", "application/x-tar")
|
req.Header.Set("Accept", "application/x-tar")
|
||||||
res, err := http.DefaultClient.Do(req)
|
res, err := http.DefaultClient.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer res.Body.Close()
|
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)
|
return fmt.Errorf("unexpected HTTP status: %s", res.Status)
|
||||||
}
|
}
|
||||||
tr := tar.NewReader(res.Body)
|
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
|
// DownloadFile downloads a single file into the destination directory
|
||||||
// if the manifest entry does not specify a file name - it will fallback
|
// if the manifest entry does not specify a file name - it will fallback
|
||||||
// to the hash of the file as a filename
|
// 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
|
hasDestinationFilename := false
|
||||||
if stat, err := os.Stat(dest); err == nil {
|
if stat, err := os.Stat(dest); err == nil {
|
||||||
hasDestinationFilename = !stat.IsDir()
|
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 {
|
if err != nil {
|
||||||
return fmt.Errorf("could not list manifest: %v", err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
switch len(manifestList.Entries) {
|
switch len(manifestList.Entries) {
|
||||||
|
|
@ -280,13 +291,19 @@ func (c *Client) DownloadFile(hash, path, dest string) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if credentials != "" {
|
||||||
|
req.SetBasicAuth("", credentials)
|
||||||
|
}
|
||||||
res, err := http.DefaultClient.Do(req)
|
res, err := http.DefaultClient.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer res.Body.Close()
|
defer res.Body.Close()
|
||||||
|
switch res.StatusCode {
|
||||||
if res.StatusCode != http.StatusOK {
|
case http.StatusOK:
|
||||||
|
case http.StatusUnauthorized:
|
||||||
|
return ErrUnauthorized
|
||||||
|
default:
|
||||||
return fmt.Errorf("unexpected HTTP status: expected 200 OK, got %d", res.StatusCode)
|
return fmt.Errorf("unexpected HTTP status: expected 200 OK, got %d", res.StatusCode)
|
||||||
}
|
}
|
||||||
filename := ""
|
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]
|
// - a prefix of "dir1/" would return [dir1/dir2/, dir1/file3.txt]
|
||||||
//
|
//
|
||||||
// where entries ending with "/" are common prefixes.
|
// where entries ending with "/" are common prefixes.
|
||||||
func (c *Client) List(hash, prefix string) (*api.ManifestList, error) {
|
func (c *Client) List(hash, prefix, credentials string) (*api.ManifestList, error) {
|
||||||
res, err := http.DefaultClient.Get(c.Gateway + "/bzz-list:/" + hash + "/" + prefix)
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
defer res.Body.Close()
|
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)
|
return nil, fmt.Errorf("unexpected HTTP status: %s", res.Status)
|
||||||
}
|
}
|
||||||
var list api.ManifestList
|
var list api.ManifestList
|
||||||
|
|
|
||||||
|
|
@ -228,7 +228,7 @@ func TestClientUploadDownloadDirectory(t *testing.T) {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
defer os.RemoveAll(tmp)
|
defer os.RemoveAll(tmp)
|
||||||
if err := client.DownloadDirectory(hash, "", tmp); err != nil {
|
if err := client.DownloadDirectory(hash, "", tmp, ""); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
for _, file := range testDirFiles {
|
for _, file := range testDirFiles {
|
||||||
|
|
@ -265,7 +265,7 @@ func testClientFileList(toEncrypt bool, t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
ls := func(prefix string) []string {
|
ls := func(prefix string) []string {
|
||||||
list, err := client.List(hash, prefix)
|
list, err := client.List(hash, prefix, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -68,7 +68,6 @@ type Config struct {
|
||||||
SwapAPI string
|
SwapAPI string
|
||||||
Cors string
|
Cors string
|
||||||
BzzAccount string
|
BzzAccount string
|
||||||
BootNodes string
|
|
||||||
privateKey *ecdsa.PrivateKey
|
privateKey *ecdsa.PrivateKey
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -93,7 +92,6 @@ func NewConfig() (c *Config) {
|
||||||
DeliverySkipCheck: false,
|
DeliverySkipCheck: false,
|
||||||
SyncUpdateDelay: 15 * time.Second,
|
SyncUpdateDelay: 15 * time.Second,
|
||||||
SwapAPI: "",
|
SwapAPI: "",
|
||||||
BootNodes: "",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
addr, err := fs.api.Resolve(context.TODO(), uri)
|
addr, err := fs.api.Resolve(context.TODO(), uri.Addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -202,7 +202,7 @@ func (fs *FileSystem) Download(bzzpath, localpath string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
quitC := make(chan bool)
|
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 {
|
if err != nil {
|
||||||
log.Warn(fmt.Sprintf("fs.Download: loadManifestTrie error: %v", err))
|
log.Warn(fmt.Sprintf("fs.Download: loadManifestTrie error: %v", err))
|
||||||
return err
|
return err
|
||||||
|
|
|
||||||
|
|
@ -64,7 +64,7 @@ func TestApiDirUpload0(t *testing.T) {
|
||||||
checkResponse(t, resp, exp)
|
checkResponse(t, resp, exp)
|
||||||
|
|
||||||
addr := storage.Address(common.Hex2Bytes(bzzhash))
|
addr := storage.Address(common.Hex2Bytes(bzzhash))
|
||||||
_, _, _, _, err = api.Get(context.TODO(), addr, "")
|
_, _, _, _, err = api.Get(context.TODO(), NOOPDecrypt, addr, "")
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Fatalf("expected error: %v", err)
|
t.Fatalf("expected error: %v", err)
|
||||||
}
|
}
|
||||||
|
|
@ -143,7 +143,7 @@ func TestApiDirUploadModify(t *testing.T) {
|
||||||
exp = expResponse(content, "text/css", 0)
|
exp = expResponse(content, "text/css", 0)
|
||||||
checkResponse(t, resp, exp)
|
checkResponse(t, resp, exp)
|
||||||
|
|
||||||
_, _, _, _, err = api.Get(context.TODO(), addr, "")
|
_, _, _, _, err = api.Get(context.TODO(), nil, addr, "")
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Errorf("expected error: %v", err)
|
t.Errorf("expected error: %v", err)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/metrics"
|
"github.com/ethereum/go-ethereum/metrics"
|
||||||
"github.com/ethereum/go-ethereum/swarm/api"
|
"github.com/ethereum/go-ethereum/swarm/api"
|
||||||
"github.com/ethereum/go-ethereum/swarm/log"
|
"github.com/ethereum/go-ethereum/swarm/log"
|
||||||
|
"github.com/ethereum/go-ethereum/swarm/sctx"
|
||||||
"github.com/ethereum/go-ethereum/swarm/spancontext"
|
"github.com/ethereum/go-ethereum/swarm/spancontext"
|
||||||
"github.com/pborman/uuid"
|
"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 {
|
func ParseURI(h http.Handler) http.Handler {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
uri, err := api.Parse(strings.TrimLeft(r.URL.Path, "/"))
|
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) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
defer func() {
|
defer func() {
|
||||||
if err := recover(); err != nil {
|
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)
|
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) {
|
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)
|
RespondTemplate(w, r, "error", msg, code)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,6 @@ import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
|
@ -97,6 +96,7 @@ func NewServer(api *api.API, corsString string) *Server {
|
||||||
defaultMiddlewares := []Adapter{
|
defaultMiddlewares := []Adapter{
|
||||||
RecoverPanic,
|
RecoverPanic,
|
||||||
SetRequestID,
|
SetRequestID,
|
||||||
|
SetRequestHost,
|
||||||
InitLoggingResponseWriter,
|
InitLoggingResponseWriter,
|
||||||
ParseURI,
|
ParseURI,
|
||||||
InstrumentOpenTracing,
|
InstrumentOpenTracing,
|
||||||
|
|
@ -169,6 +169,7 @@ func NewServer(api *api.API, corsString string) *Server {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) ListenAndServe(addr string) error {
|
func (s *Server) ListenAndServe(addr string) error {
|
||||||
|
s.listenAddr = addr
|
||||||
return http.ListenAndServe(addr, s)
|
return http.ListenAndServe(addr, s)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -179,15 +180,23 @@ func (s *Server) ListenAndServe(addr string) error {
|
||||||
type Server struct {
|
type Server struct {
|
||||||
http.Handler
|
http.Handler
|
||||||
api *api.API
|
api *api.API
|
||||||
|
listenAddr string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) HandleBzzGet(w http.ResponseWriter, r *http.Request) {
|
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" {
|
if r.Header.Get("Accept") == "application/x-tar" {
|
||||||
uri := GetURI(r.Context())
|
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 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)
|
RespondError(w, r, fmt.Sprintf("Had an error building the tarball: %v", err), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
defer reader.Close()
|
defer reader.Close()
|
||||||
|
|
||||||
|
|
@ -287,7 +296,7 @@ func (s *Server) HandlePostFiles(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
var addr storage.Address
|
var addr storage.Address
|
||||||
if uri.Addr != "" && uri.Addr != "encrypt" {
|
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 {
|
if err != nil {
|
||||||
postFilesFail.Inc(1)
|
postFilesFail.Inc(1)
|
||||||
RespondError(w, r, fmt.Sprintf("cannot resolve %s: %s", uri.Addr, err), http.StatusInternalServerError)
|
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.
|
// resolve the content key.
|
||||||
manifestAddr := uri.Address()
|
manifestAddr := uri.Address()
|
||||||
if manifestAddr == nil {
|
if manifestAddr == nil {
|
||||||
manifestAddr, err = s.api.Resolve(r.Context(), uri)
|
manifestAddr, err = s.api.Resolve(r.Context(), uri.Addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
getFail.Inc(1)
|
getFail.Inc(1)
|
||||||
RespondError(w, r, fmt.Sprintf("cannot resolve %s: %s", uri.Addr, err), http.StatusNotFound)
|
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())
|
uri := GetURI(r.Context())
|
||||||
log.Debug("handle.get", "ruid", ruid, "uri", uri)
|
log.Debug("handle.get", "ruid", ruid, "uri", uri)
|
||||||
getCount.Inc(1)
|
getCount.Inc(1)
|
||||||
|
_, pass, _ := r.BasicAuth()
|
||||||
|
|
||||||
var err error
|
addr, err := s.api.ResolveURI(r.Context(), uri, pass)
|
||||||
addr := uri.Address()
|
|
||||||
if addr == nil {
|
|
||||||
addr, err = s.api.Resolve(r.Context(), uri)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
getFail.Inc(1)
|
getFail.Inc(1)
|
||||||
RespondError(w, r, fmt.Sprintf("cannot resolve %s: %s", uri.Addr, err), http.StatusNotFound)
|
RespondError(w, r, fmt.Sprintf("cannot resolve %s: %s", uri.Addr, err), http.StatusNotFound)
|
||||||
return
|
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.
|
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)
|
log.Debug("handle.get: resolved", "ruid", ruid, "key", addr)
|
||||||
|
|
||||||
// if path is set, interpret <key> as a manifest and return the
|
// if path is set, interpret <key> as a manifest and return the
|
||||||
// raw entry at the given path
|
// 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)
|
etag := common.Bytes2Hex(addr)
|
||||||
noneMatchEtag := r.Header.Get("If-None-Match")
|
noneMatchEtag := r.Header.Get("If-None-Match")
|
||||||
w.Header().Set("ETag", fmt.Sprintf("%q", etag)) // set etag to manifest key or raw entry key.
|
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) {
|
func (s *Server) HandleGetList(w http.ResponseWriter, r *http.Request) {
|
||||||
ruid := GetRUID(r.Context())
|
ruid := GetRUID(r.Context())
|
||||||
uri := GetURI(r.Context())
|
uri := GetURI(r.Context())
|
||||||
|
_, credentials, _ := r.BasicAuth()
|
||||||
log.Debug("handle.get.list", "ruid", ruid, "uri", uri)
|
log.Debug("handle.get.list", "ruid", ruid, "uri", uri)
|
||||||
getListCount.Inc(1)
|
getListCount.Inc(1)
|
||||||
|
|
||||||
|
|
@ -790,7 +759,7 @@ func (s *Server) HandleGetList(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
addr, err := s.api.Resolve(r.Context(), uri)
|
addr, err := s.api.Resolve(r.Context(), uri.Addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
getListFail.Inc(1)
|
getListFail.Inc(1)
|
||||||
RespondError(w, r, fmt.Sprintf("cannot resolve %s: %s", uri.Addr, err), http.StatusNotFound)
|
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)
|
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 {
|
if err != nil {
|
||||||
getListFail.Inc(1)
|
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)
|
RespondError(w, r, err.Error(), http.StatusInternalServerError)
|
||||||
return
|
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) {
|
func (s *Server) HandleGetFile(w http.ResponseWriter, r *http.Request) {
|
||||||
ruid := GetRUID(r.Context())
|
ruid := GetRUID(r.Context())
|
||||||
uri := GetURI(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)
|
getFileCount.Inc(1)
|
||||||
|
|
||||||
// ensure the root path has a trailing slash so that relative URLs work
|
// ensure the root path has a trailing slash so that relative URLs work
|
||||||
|
|
@ -845,7 +820,7 @@ func (s *Server) HandleGetFile(w http.ResponseWriter, r *http.Request) {
|
||||||
manifestAddr := uri.Address()
|
manifestAddr := uri.Address()
|
||||||
|
|
||||||
if manifestAddr == nil {
|
if manifestAddr == nil {
|
||||||
manifestAddr, err = s.api.Resolve(r.Context(), uri)
|
manifestAddr, err = s.api.Resolve(r.Context(), uri.Addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
getFileFail.Inc(1)
|
getFileFail.Inc(1)
|
||||||
RespondError(w, r, fmt.Sprintf("cannot resolve %s: %s", uri.Addr, err), http.StatusNotFound)
|
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)
|
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)
|
etag := common.Bytes2Hex(contentKey)
|
||||||
noneMatchEtag := r.Header.Get("If-None-Match")
|
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 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 {
|
switch status {
|
||||||
case http.StatusNotFound:
|
case http.StatusNotFound:
|
||||||
getFileNotFound.Inc(1)
|
getFileNotFound.Inc(1)
|
||||||
|
|
@ -883,9 +865,14 @@ func (s *Server) HandleGetFile(w http.ResponseWriter, r *http.Request) {
|
||||||
//the request results in ambiguous files
|
//the request results in ambiguous files
|
||||||
//e.g. /read with readme.md and readinglist.txt available in manifest
|
//e.g. /read with readme.md and readinglist.txt available in manifest
|
||||||
if status == http.StatusMultipleChoices {
|
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 {
|
if err != nil {
|
||||||
getFileFail.Inc(1)
|
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)
|
RespondError(w, r, err.Error(), http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
@ -951,3 +938,7 @@ func (lrw *loggingResponseWriter) WriteHeader(code int) {
|
||||||
lrw.statusCode = code
|
lrw.statusCode = code
|
||||||
lrw.ResponseWriter.WriteHeader(code)
|
lrw.ResponseWriter.WriteHeader(code)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func isDecryptError(err error) bool {
|
||||||
|
return strings.Contains(err.Error(), api.ErrDecrypt.Error())
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -53,6 +53,7 @@ type ManifestEntry struct {
|
||||||
Size int64 `json:"size,omitempty"`
|
Size int64 `json:"size,omitempty"`
|
||||||
ModTime time.Time `json:"mod_time,omitempty"`
|
ModTime time.Time `json:"mod_time,omitempty"`
|
||||||
Status int `json:"status,omitempty"`
|
Status int `json:"status,omitempty"`
|
||||||
|
Access *AccessEntry `json:"access,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ManifestList represents the result of listing files in a manifest
|
// 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) {
|
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 {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("error loading manifest %s: %s", addr, err)
|
return nil, fmt.Errorf("error loading manifest %s: %s", addr, err)
|
||||||
}
|
}
|
||||||
|
|
@ -141,8 +142,8 @@ type ManifestWalker struct {
|
||||||
quitC chan bool
|
quitC chan bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *API) NewManifestWalker(ctx context.Context, addr storage.Address, quitC chan bool) (*ManifestWalker, error) {
|
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)
|
trie, err := loadManifest(ctx, a.fileStore, addr, quitC, decrypt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("error loading manifest %s: %s", addr, err)
|
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
|
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
|
ref storage.Address // if ref != nil, it is stored
|
||||||
encrypted bool
|
encrypted bool
|
||||||
|
decrypt DecryptFunc
|
||||||
}
|
}
|
||||||
|
|
||||||
func newManifestTrieEntry(entry *ManifestEntry, subtrie *manifestTrie) *manifestTrieEntry {
|
func newManifestTrieEntry(entry *ManifestEntry, subtrie *manifestTrie) *manifestTrieEntry {
|
||||||
|
|
@ -209,15 +211,15 @@ type manifestTrieEntry struct {
|
||||||
subtrie *manifestTrie
|
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)
|
log.Trace("manifest lookup", "key", hash)
|
||||||
// retrieve manifest via FileStore
|
// retrieve manifest via FileStore
|
||||||
manifestReader, isEncrypted := fileStore.Retrieve(ctx, hash)
|
manifestReader, isEncrypted := fileStore.Retrieve(ctx, hash)
|
||||||
log.Trace("reader retrieved", "key", 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
|
// TODO check size for oversized manifests
|
||||||
size, err := mr.Size(mr.Context(), quitC)
|
size, err := mr.Size(mr.Context(), quitC)
|
||||||
|
|
@ -258,26 +260,41 @@ func readManifest(mr storage.LazySectionReader, hash storage.Address, fileStore
|
||||||
trie = &manifestTrie{
|
trie = &manifestTrie{
|
||||||
fileStore: fileStore,
|
fileStore: fileStore,
|
||||||
encrypted: isEncrypted,
|
encrypted: isEncrypted,
|
||||||
|
decrypt: decrypt,
|
||||||
}
|
}
|
||||||
for _, entry := range man.Entries {
|
for _, entry := range man.Entries {
|
||||||
trie.addEntry(entry, quitC)
|
err = trie.addEntry(entry, quitC)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
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
|
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 {
|
if len(entry.Path) == 0 {
|
||||||
mt.entries[256] = entry
|
mt.entries[256] = entry
|
||||||
return
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
b := entry.Path[0]
|
b := entry.Path[0]
|
||||||
oldentry := mt.entries[b]
|
oldentry := mt.entries[b]
|
||||||
if (oldentry == nil) || (oldentry.Path == entry.Path && oldentry.ContentType != ManifestType) {
|
if (oldentry == nil) || (oldentry.Path == entry.Path && oldentry.ContentType != ManifestType) {
|
||||||
mt.entries[b] = entry
|
mt.entries[b] = entry
|
||||||
return
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
cpl := 0
|
cpl := 0
|
||||||
|
|
@ -287,12 +304,12 @@ func (mt *manifestTrie) addEntry(entry *manifestTrieEntry, quitC chan bool) {
|
||||||
|
|
||||||
if (oldentry.ContentType == ManifestType) && (cpl == len(oldentry.Path)) {
|
if (oldentry.ContentType == ManifestType) && (cpl == len(oldentry.Path)) {
|
||||||
if mt.loadSubTrie(oldentry, quitC) != nil {
|
if mt.loadSubTrie(oldentry, quitC) != nil {
|
||||||
return
|
return nil
|
||||||
}
|
}
|
||||||
entry.Path = entry.Path[cpl:]
|
entry.Path = entry.Path[cpl:]
|
||||||
oldentry.subtrie.addEntry(entry, quitC)
|
oldentry.subtrie.addEntry(entry, quitC)
|
||||||
oldentry.Hash = ""
|
oldentry.Hash = ""
|
||||||
return
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
commonPrefix := entry.Path[:cpl]
|
commonPrefix := entry.Path[:cpl]
|
||||||
|
|
@ -310,6 +327,7 @@ func (mt *manifestTrie) addEntry(entry *manifestTrieEntry, quitC chan bool) {
|
||||||
Path: commonPrefix,
|
Path: commonPrefix,
|
||||||
ContentType: ManifestType,
|
ContentType: ManifestType,
|
||||||
}, subtrie)
|
}, subtrie)
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (mt *manifestTrie) getCountLast() (cnt int, entry *manifestTrieEntry) {
|
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) {
|
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 {
|
if entry.subtrie == nil {
|
||||||
hash := common.Hex2Bytes(entry.Hash)
|
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
|
entry.Hash = "" // might not match, should be recalculated
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,7 @@ func testGetEntry(t *testing.T, path, match string, multiple bool, paths ...stri
|
||||||
quitC := make(chan bool)
|
quitC := make(chan bool)
|
||||||
fileStore := storage.NewFileStore(nil, storage.NewFileStoreParams())
|
fileStore := storage.NewFileStore(nil, storage.NewFileStoreParams())
|
||||||
ref := make([]byte, fileStore.HashSize())
|
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 {
|
if err != nil {
|
||||||
t.Errorf("unexpected error making manifest: %v", err)
|
t.Errorf("unexpected error making manifest: %v", err)
|
||||||
}
|
}
|
||||||
|
|
@ -101,7 +101,7 @@ func TestExactMatch(t *testing.T) {
|
||||||
mf := manifest("shouldBeExactMatch.css", "shouldBeExactMatch.css.map")
|
mf := manifest("shouldBeExactMatch.css", "shouldBeExactMatch.css.map")
|
||||||
fileStore := storage.NewFileStore(nil, storage.NewFileStoreParams())
|
fileStore := storage.NewFileStore(nil, storage.NewFileStoreParams())
|
||||||
ref := make([]byte, fileStore.HashSize())
|
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 {
|
if err != nil {
|
||||||
t.Errorf("unexpected error making manifest: %v", err)
|
t.Errorf("unexpected error making manifest: %v", err)
|
||||||
}
|
}
|
||||||
|
|
@ -134,7 +134,7 @@ func TestAddFileWithManifestPath(t *testing.T) {
|
||||||
}
|
}
|
||||||
fileStore := storage.NewFileStore(nil, storage.NewFileStoreParams())
|
fileStore := storage.NewFileStore(nil, storage.NewFileStoreParams())
|
||||||
ref := make([]byte, fileStore.HashSize())
|
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 {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
@ -161,7 +161,7 @@ func TestReadManifestOverSizeLimit(t *testing.T) {
|
||||||
reader := &storage.LazyTestSectionReader{
|
reader := &storage.LazyTestSectionReader{
|
||||||
SectionReader: io.NewSectionReader(bytes.NewReader(manifest), 0, int64(len(manifest))),
|
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 {
|
if err == nil {
|
||||||
t.Fatal("got no error from readManifest")
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
addr, err := s.api.Resolve(ctx, uri)
|
addr, err := s.api.Resolve(ctx, uri.Addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -93,7 +93,7 @@ func (s *Storage) Modify(ctx context.Context, rootHash, path, contentHash, conte
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
addr, err := s.api.Resolve(ctx, uri)
|
addr, err := s.api.Resolve(ctx, uri.Addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -53,6 +53,19 @@ type URI struct {
|
||||||
Path string
|
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
|
// Parse parses rawuri into a URI struct, where rawuri is expected to have one
|
||||||
// of the following formats:
|
// of the following formats:
|
||||||
//
|
//
|
||||||
|
|
|
||||||
|
|
@ -1650,7 +1650,7 @@ func TestFUSE(t *testing.T) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
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
|
//run a short suite of tests
|
||||||
//approx time: 28s
|
//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
|
// 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
|
// these are called on the p2p.Server which runs on the node
|
||||||
func (h *Hive) Start(server *p2p.Server) error {
|
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 state store is specified, load peers to prepopulate the overlay address book
|
||||||
if h.Store != nil {
|
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 {
|
if err := h.loadPeers(); err != nil {
|
||||||
log.Error(fmt.Sprintf("%08x hive encoutered an error trying to load peers", h.BaseAddr()[:4]))
|
log.Error(fmt.Sprintf("%08x hive encoutered an error trying to load peers", h.BaseAddr()[:4]))
|
||||||
return err
|
return err
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,7 @@ const (
|
||||||
// BzzSpec is the spec of the generic swarm handshake
|
// BzzSpec is the spec of the generic swarm handshake
|
||||||
var BzzSpec = &protocols.Spec{
|
var BzzSpec = &protocols.Spec{
|
||||||
Name: "bzz",
|
Name: "bzz",
|
||||||
Version: 5,
|
Version: 6,
|
||||||
MaxMsgSize: 10 * 1024 * 1024,
|
MaxMsgSize: 10 * 1024 * 1024,
|
||||||
Messages: []interface{}{
|
Messages: []interface{}{
|
||||||
HandshakeMsg{},
|
HandshakeMsg{},
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
TestProtocolVersion = 5
|
TestProtocolVersion = 6
|
||||||
TestProtocolNetworkID = 3
|
TestProtocolNetworkID = 3
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue