cmd/swarm: make bzzaccount flag optional and add bzzkeyhex flag (#1531)

* cmd/swarm: don't require bzzaccount flag

* docker: remove setup script because bzzaccount is not required anymore

* cmd/swarm: manage account creation/selection when bzzflag is not defined

* cmd/swarm: no bzzaccount flag tests

* cmd/swarm: use random network ports on tests

* cmd/swarm: fix typo

* cmd/swarm: add --bzzkeyhex flag

* cmd/swarm: use different ipc paths for tests

* readme: update example on how to run swarm

* cmd/swarm: rename getAccount -> getOrCreateAccount

* cmd/swarm: remove unneeded comment

* cmd/swarm: use shorter ipc path for test
This commit is contained in:
Rafael Matias
2019-07-04 13:14:10 +02:00
committed by GitHub
parent fb73e6cb9b
commit d5f6ee4620
10 changed files with 266 additions and 94 deletions

View File

@ -10,5 +10,4 @@ FROM alpine:3.9
RUN apk --no-cache add ca-certificates && update-ca-certificates
COPY --from=builder /swarm/build/bin/swarm /usr/local/bin/
COPY --from=geth /usr/local/bin/geth /usr/local/bin/
COPY docker/run.sh /run.sh
ENTRYPOINT ["/run.sh"]
ENTRYPOINT ["/usr/local/bin/swarm"]

View File

@ -10,6 +10,5 @@ FROM alpine:3.9
RUN apk --no-cache add ca-certificates
COPY --from=builder /swarm/build/bin/* /usr/local/bin/
COPY --from=geth /usr/local/bin/geth /usr/local/bin/
COPY docker/run.sh /run.sh
COPY docker/run-smoke.sh /run-smoke.sh
ENTRYPOINT ["/run.sh"]
ENTRYPOINT ["/usr/local/bin/swarm"]

View File

@ -9,26 +9,26 @@ Swarm is a distributed storage platform and content distribution service, a nati
## Table of Contents <!-- omit in toc -->
- [Building the source](#building-the-source)
- [Running Swarm](#running-swarm)
- [Verifying that your local Swarm node is running](#verifying-that-your-local-swarm-node-is-running)
- [Ethereum Name Service resolution](#ethereum-name-service-resolution)
- [Documentation](#documentation)
- [Docker](#docker)
- [Docker tags](#docker-tags)
- [Environment variables](#environment-variables)
- [Swarm command line arguments](#swarm-command-line-arguments)
- [Developers Guide](#developers-guide)
- [Go Environment](#go-environment)
- [Vendored Dependencies](#vendored-dependencies)
- [Testing](#testing)
- [Profiling Swarm](#profiling-swarm)
- [Metrics and Instrumentation in Swarm](#metrics-and-instrumentation-in-swarm)
- [Visualizing metrics](#visualizing-metrics)
- [Public Gateways](#public-gateways)
- [Swarm Dapps](#swarm-dapps)
- [Contributing](#contributing)
- [License](#license)
- [Building the source](#Building-the-source)
- [Running Swarm](#Running-Swarm)
- [Verifying that your local Swarm node is running](#Verifying-that-your-local-Swarm-node-is-running)
- [Ethereum Name Service resolution](#Ethereum-Name-Service-resolution)
- [Documentation](#Documentation)
- [Docker](#Docker)
- [Docker tags](#Docker-tags)
- [Environment variables](#Environment-variables)
- [Swarm command line arguments](#Swarm-command-line-arguments)
- [Developers Guide](#Developers-Guide)
- [Go Environment](#Go-Environment)
- [Vendored Dependencies](#Vendored-Dependencies)
- [Testing](#Testing)
- [Profiling Swarm](#Profiling-Swarm)
- [Metrics and Instrumentation in Swarm](#Metrics-and-Instrumentation-in-Swarm)
- [Visualizing metrics](#Visualizing-metrics)
- [Public Gateways](#Public-Gateways)
- [Swarm Dapps](#Swarm-Dapps)
- [Contributing](#Contributing)
- [License](#License)
## Building the source
@ -53,15 +53,11 @@ $ go install github.com/ethersphere/swarm/cmd/swarm
## Running Swarm
Going through all the possible command line flags is out of scope here, but we've enumerated a few common parameter combos to get you up to speed quickly on how you can run your own Swarm node.
To run Swarm you need an Ethereum account. Download and install [Geth](https://geth.ethereum.org) if you don't have it on your system. You can create a new Ethereum account by running the following command:
```bash
$ geth account new
$ swarm
```
You will be prompted for a password:
If you don't have an account yet, then you will be prompted to create one and secure it with a password:
```
Your new account is locked with a password. Please give a password. Do not forget this password.
@ -69,18 +65,12 @@ Passphrase:
Repeat passphrase:
```
Once you have specified the password, the output will be the Ethereum address representing that account. For example:
```
Address: {2f1cd699b0bf461dcfbf0098ad8f5587b038f0f1}
```
Using this account, connect to Swarm with
If you have multiple accounts created, then you'll have to choose one of the accounts by using the `--bzzaccount` flag.
```bash
$ swarm --bzzaccount <your-account-here>
# in our example
# example
$ swarm --bzzaccount 2f1cd699b0bf461dcfbf0098ad8f5587b038f0f1
```

View File

@ -106,7 +106,7 @@ func accessNewPass(ctx *cli.Context) {
accessKey []byte
err error
ref = args[0]
password = getPassPhrase("", 0, makePasswordList(ctx))
password = getPassPhrase("", false, 0, makePasswordList(ctx))
dryRun = ctx.Bool(SwarmDryRunFlag.Name)
)
accessKey, ae, err = api.DoPassword(ctx, password, salt)

View File

@ -17,6 +17,7 @@
package main
import (
"crypto/ecdsa"
"errors"
"fmt"
"io"
@ -60,6 +61,7 @@ var (
const (
SwarmEnvChequebookAddr = "SWARM_CHEQUEBOOK_ADDR"
SwarmEnvAccount = "SWARM_ACCOUNT"
SwarmEnvBzzKeyHex = "SWARM_BZZ_KEY_HEX"
SwarmEnvListenAddr = "SWARM_LISTEN_ADDR"
SwarmEnvPort = "SWARM_PORT"
SwarmEnvNetworkID = "SWARM_NETWORK_ID"
@ -121,9 +123,9 @@ func buildConfig(ctx *cli.Context) (config *bzzapi.Config, err error) {
//finally, after the configuration build phase is finished, initialize
func initSwarmNode(config *bzzapi.Config, stack *node.Node, ctx *cli.Context, nodeconfig *node.Config) error {
//at this point, all vars should be set in the Config
//get the account for the provided swarm account
prvkey := getAccount(config.BzzAccount, ctx, stack)
var prvkey *ecdsa.PrivateKey
config.BzzAccount, prvkey = getOrCreateAccount(ctx, stack)
//set the resolved config path (geth --datadir)
config.Path = expandPath(stack.InstanceDir())
//finally, initialize the configuration

View File

@ -17,6 +17,7 @@
package main
import (
"encoding/hex"
"fmt"
"io"
"io/ioutil"
@ -28,6 +29,8 @@ import (
"github.com/docker/docker/pkg/reexec"
"github.com/ethereum/go-ethereum/cmd/utils"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/node"
"github.com/ethereum/go-ethereum/rpc"
"github.com/ethersphere/swarm"
"github.com/ethersphere/swarm/api"
@ -48,6 +51,7 @@ func TestConfigFailsSwapEnabledNoSwapApi(t *testing.T) {
flags := []string{
fmt.Sprintf("--%s", SwarmNetworkIdFlag.Name), "42",
fmt.Sprintf("--%s", SwarmPortFlag.Name), "54545",
fmt.Sprintf("--%s", utils.ListenPortFlag.Name), "0",
fmt.Sprintf("--%s", SwarmSwapEnabledFlag.Name),
}
@ -56,15 +60,161 @@ func TestConfigFailsSwapEnabledNoSwapApi(t *testing.T) {
swarm.ExpectExit()
}
func TestConfigFailsNoBzzAccount(t *testing.T) {
flags := []string{
fmt.Sprintf("--%s", SwarmNetworkIdFlag.Name), "42",
fmt.Sprintf("--%s", SwarmPortFlag.Name), "54545",
func TestBzzKeyFlag(t *testing.T) {
key, err := crypto.GenerateKey()
if err != nil {
t.Fatal("unable to generate key")
}
hexKey := hex.EncodeToString(crypto.FromECDSA(key))
bzzaccount := crypto.PubkeyToAddress(key.PublicKey).Hex()
dir, err := ioutil.TempDir("", "bzztest")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(dir)
conf := &node.Config{
DataDir: dir,
IPCPath: "testbzzkeyflag.ipc",
NoUSB: true,
}
swarm := runSwarm(t, flags...)
swarm.Expect("Fatal: " + SwarmErrNoBZZAccount + "\n")
swarm.ExpectExit()
node := &testNode{Dir: dir}
flags := []string{
fmt.Sprintf("--%s", SwarmNetworkIdFlag.Name), "42",
fmt.Sprintf("--%s", SwarmPortFlag.Name), "0",
fmt.Sprintf("--%s", utils.ListenPortFlag.Name), "0",
fmt.Sprintf("--%s", utils.DataDirFlag.Name), dir,
fmt.Sprintf("--%s", utils.IPCPathFlag.Name), conf.IPCPath,
fmt.Sprintf("--%s", SwarmBzzKeyHexFlag.Name), hexKey,
}
node.Cmd = runSwarm(t, flags...)
defer func() {
node.Shutdown()
}()
node.Cmd.InputLine(testPassphrase)
// wait for the node to start
for start := time.Now(); time.Since(start) < 10*time.Second; time.Sleep(50 * time.Millisecond) {
node.Client, err = rpc.Dial(conf.IPCEndpoint())
if err == nil {
break
}
}
if node.Client == nil {
t.Fatal(err)
}
// load info
var info swarm.Info
if err := node.Client.Call(&info, "bzz_info"); err != nil {
t.Fatal(err)
}
if info.BzzAccount != bzzaccount {
t.Fatalf("Expected account to be %s, got %s", bzzaccount, info.BzzAccount)
}
}
func TestEmptyBzzAccountFlagMultipleAccounts(t *testing.T) {
dir, err := ioutil.TempDir("", "bzztest")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(dir)
// Create two accounts on disk
getTestAccount(t, dir)
getTestAccount(t, dir)
node := &testNode{Dir: dir}
flags := []string{
fmt.Sprintf("--%s", SwarmNetworkIdFlag.Name), "42",
fmt.Sprintf("--%s", SwarmPortFlag.Name), "0",
fmt.Sprintf("--%s", utils.ListenPortFlag.Name), "0",
fmt.Sprintf("--%s", utils.DataDirFlag.Name), dir,
}
node.Cmd = runSwarm(t, flags...)
node.Cmd.ExpectRegexp(fmt.Sprintf("Please choose one of the accounts by running swarm with the --%s flag.", SwarmAccountFlag.Name))
}
func TestEmptyBzzAccountFlagSingleAccount(t *testing.T) {
dir, err := ioutil.TempDir("", "bzztest")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(dir)
conf, account := getTestAccount(t, dir)
node := &testNode{Dir: dir}
flags := []string{
fmt.Sprintf("--%s", SwarmNetworkIdFlag.Name), "42",
fmt.Sprintf("--%s", SwarmPortFlag.Name), "0",
fmt.Sprintf("--%s", utils.ListenPortFlag.Name), "0",
fmt.Sprintf("--%s", utils.DataDirFlag.Name), dir,
fmt.Sprintf("--%s", utils.IPCPathFlag.Name), conf.IPCPath,
}
node.Cmd = runSwarm(t, flags...)
defer func() {
node.Shutdown()
}()
node.Cmd.InputLine(testPassphrase)
// wait for the node to start
for start := time.Now(); time.Since(start) < 10*time.Second; time.Sleep(50 * time.Millisecond) {
node.Client, err = rpc.Dial(conf.IPCEndpoint())
if err == nil {
break
}
}
if node.Client == nil {
t.Fatal(err)
}
// load info
var info swarm.Info
if err := node.Client.Call(&info, "bzz_info"); err != nil {
t.Fatal(err)
}
if info.BzzAccount != account.Address.Hex() {
t.Fatalf("Expected account to be %s, got %s", account.Address.Hex(), info.BzzAccount)
}
}
func TestEmptyBzzAccountFlagNoAccountWrongPassword(t *testing.T) {
dir, err := ioutil.TempDir("", "bzztest")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(dir)
node := &testNode{Dir: dir}
flags := []string{
fmt.Sprintf("--%s", SwarmNetworkIdFlag.Name), "42",
fmt.Sprintf("--%s", SwarmPortFlag.Name), "0",
fmt.Sprintf("--%s", utils.ListenPortFlag.Name), "0",
fmt.Sprintf("--%s", utils.DataDirFlag.Name), dir,
}
node.Cmd = runSwarm(t, flags...)
// Set password
node.Cmd.InputLine("goodpassword")
// Confirm password
node.Cmd.InputLine("wrongpassword")
node.Cmd.ExpectRegexp("Passphrases do not match")
}
func TestConfigCmdLineOverrides(t *testing.T) {
@ -86,6 +236,7 @@ func TestConfigCmdLineOverrides(t *testing.T) {
flags := []string{
fmt.Sprintf("--%s", SwarmNetworkIdFlag.Name), "42",
fmt.Sprintf("--%s", SwarmPortFlag.Name), httpPort,
fmt.Sprintf("--%s", utils.ListenPortFlag.Name), "0",
fmt.Sprintf("--%s", SwarmSyncDisabledFlag.Name),
fmt.Sprintf("--%s", CorsStringFlag.Name), "*",
fmt.Sprintf("--%s", SwarmAccountFlag.Name), account.Address.String(),

View File

@ -101,7 +101,7 @@ func download(ctx *cli.Context) {
return nil
}
if passwords := makePasswordList(ctx); passwords != nil {
password := getPassPhrase(fmt.Sprintf("Downloading %s is restricted", uri), 0, passwords)
password := getPassPhrase(fmt.Sprintf("Downloading %s is restricted", uri), false, 0, passwords)
err = dl(password)
} else {
err = dl("")

View File

@ -30,6 +30,11 @@ var (
Usage: "Swarm account key file",
EnvVar: SwarmEnvAccount,
}
SwarmBzzKeyHexFlag = cli.StringFlag{
Name: "bzzkeyhex",
Usage: "BzzAccount key in hex (for testing)",
EnvVar: SwarmEnvBzzKeyHex,
}
SwarmListenAddrFlag = cli.StringFlag{
Name: "httpaddr",
Usage: "Swarm HTTP API listening interface",

View File

@ -184,6 +184,7 @@ func init() {
SwarmListenAddrFlag,
SwarmPortFlag,
SwarmAccountFlag,
SwarmBzzKeyHexFlag,
SwarmNetworkIdFlag,
ChequebookAddrFlag,
// upload flags
@ -351,21 +352,62 @@ func registerBzzService(bzzconfig *bzzapi.Config, stack *node.Node) {
}
}
func getAccount(bzzaccount string, ctx *cli.Context, stack *node.Node) *ecdsa.PrivateKey {
//an account is mandatory
if bzzaccount == "" {
utils.Fatalf(SwarmErrNoBZZAccount)
// getOrCreateAccount returns the address and associated private key for a bzzaccount
// If no account exists, it will create an account for you.
func getOrCreateAccount(ctx *cli.Context, stack *node.Node) (string, *ecdsa.PrivateKey) {
var bzzaddr string
// Check if a key was provided
if hexkey := ctx.GlobalString(SwarmBzzKeyHexFlag.Name); hexkey != "" {
key, err := crypto.HexToECDSA(hexkey)
if err != nil {
utils.Fatalf("failed using %s: %v", SwarmBzzKeyHexFlag.Name, err)
}
bzzaddr := crypto.PubkeyToAddress(key.PublicKey).Hex()
log.Info(fmt.Sprintf("Swarm account key loaded from %s", SwarmBzzKeyHexFlag.Name), "address", bzzaddr)
return bzzaddr, key
}
// Try to load the arg as a hex key file.
if key, err := crypto.LoadECDSA(bzzaccount); err == nil {
log.Info("Swarm account key loaded", "address", crypto.PubkeyToAddress(key.PublicKey))
return key
}
// Otherwise try getting it from the keystore.
am := stack.AccountManager()
ks := am.Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore)
return decryptStoreAccount(ks, bzzaccount, utils.MakePasswordList(ctx))
// Check if an address was provided
if bzzaddr = ctx.GlobalString(SwarmAccountFlag.Name); bzzaddr != "" {
// Try to load the arg as a hex key file.
if key, err := crypto.LoadECDSA(bzzaddr); err == nil {
bzzaddr := crypto.PubkeyToAddress(key.PublicKey).Hex()
log.Info("Swarm account key loaded", "address", bzzaddr)
return bzzaddr, key
}
return bzzaddr, decryptStoreAccount(ks, bzzaddr, utils.MakePasswordList(ctx))
}
// No address or key were provided
accounts := ks.Accounts()
switch l := len(accounts); l {
case 0:
// Create an account
log.Info("You don't have an account yet. Creating one...")
password := getPassPhrase("Your new account is locked with a password. Please give a password. Do not forget this password.", true, 0, utils.MakePasswordList(ctx))
account, err := ks.NewAccount(password)
if err != nil {
utils.Fatalf("failed creating an account: %v", err)
}
bzzaddr = account.Address.Hex()
case 1:
// Use existing account
bzzaddr = accounts[0].Address.Hex()
default:
// Inform user about multiple accounts
log.Info(fmt.Sprintf("Multiple (%d) accounts were found in your keystore.", l))
for _, a := range accounts {
log.Info(fmt.Sprintf("Account: %s", a.Address.Hex()))
}
utils.Fatalf(fmt.Sprintf("Please choose one of the accounts by running swarm with the --%s flag.", SwarmAccountFlag.Name))
}
return bzzaddr, decryptStoreAccount(ks, bzzaddr, utils.MakePasswordList(ctx))
}
// getPrivKey returns the private key of the specified bzzaccount
@ -387,7 +429,9 @@ func getPrivKey(ctx *cli.Context) *ecdsa.PrivateKey {
}
defer stack.Close()
return getAccount(bzzconfig.BzzAccount, ctx, stack)
var privkey *ecdsa.PrivateKey
bzzconfig.BzzAccount, privkey = getOrCreateAccount(ctx, stack)
return privkey
}
func decryptStoreAccount(ks *keystore.KeyStore, account string, passwords []string) *ecdsa.PrivateKey {
@ -412,7 +456,7 @@ func decryptStoreAccount(ks *keystore.KeyStore, account string, passwords []stri
utils.Fatalf("Can't load swarm account key: %v", err)
}
for i := 0; i < 3; i++ {
password := getPassPhrase(fmt.Sprintf("Unlocking swarm account %s [%d/3]", a.Address.Hex(), i+1), i, passwords)
password := getPassPhrase(fmt.Sprintf("Unlocking swarm account %s [%d/3]", a.Address.Hex(), i+1), false, i, passwords)
key, err := keystore.DecryptKey(keyjson, password)
if err == nil {
return key.PrivateKey
@ -422,18 +466,17 @@ func decryptStoreAccount(ks *keystore.KeyStore, account string, passwords []stri
return nil
}
// getPassPhrase retrieves the password associated with bzz account, either by fetching
// from a list of pre-loaded passwords, or by requesting it interactively from user.
func getPassPhrase(prompt string, i int, passwords []string) string {
// non-interactive
// getPassPhrase retrieves the password associated with a bzzaccount, either fetched
// from a list of preloaded passphrases, or requested interactively from the user.
func getPassPhrase(prompt string, confirmation bool, i int, passwords []string) string {
// If a list of passwords was supplied, retrieve from them
if len(passwords) > 0 {
if i < len(passwords) {
return passwords[i]
}
return passwords[len(passwords)-1]
}
// fallback to interactive mode
// Otherwise prompt the user for the password
if prompt != "" {
fmt.Println(prompt)
}
@ -441,6 +484,15 @@ func getPassPhrase(prompt string, i int, passwords []string) string {
if err != nil {
utils.Fatalf("Failed to read passphrase: %v", err)
}
if confirmation {
confirm, err := console.Stdin.PromptPassword("Repeat passphrase: ")
if err != nil {
utils.Fatalf("Failed to read passphrase confirmation: %v", err)
}
if password != confirm {
utils.Fatalf("Passphrases do not match")
}
}
return password
}

View File

@ -1,26 +0,0 @@
#!/bin/sh
set -o errexit
set -o pipefail
set -o nounset
PASSWORD=${PASSWORD:-}
DATADIR=${DATADIR:-/root/.ethereum/}
if [ "$PASSWORD" == "" ]; then echo "Password must be set, in order to use swarm non-interactively." && exit 1; fi
echo $PASSWORD > /password
KEYFILE=`find $DATADIR | grep UTC | head -n 1` || true
if [ ! -f "$KEYFILE" ]; then echo "No keyfile found. Generating..." && geth --datadir $DATADIR --password /password account new; fi
KEYFILE=`find $DATADIR | grep UTC | head -n 1` || true
if [ ! -f "$KEYFILE" ]; then echo "Could not find nor generate a BZZ keyfile." && exit 1; else echo "Found keyfile $KEYFILE"; fi
VERSION=`swarm version`
echo "Running Swarm:"
echo $VERSION
export BZZACCOUNT="`echo -n $KEYFILE | tail -c 40`" || true
if [ "$BZZACCOUNT" == "" ]; then echo "Could not parse BZZACCOUNT from keyfile." && exit 1; fi
exec swarm --bzzaccount=$BZZACCOUNT --password /password --datadir $DATADIR $@ 2>&1