Commit 4a598b6f authored by Pavle Batuta's avatar Pavle Batuta Committed by GitHub

Add deploy command to bee (#1314)

parent d1920d8f
......@@ -100,6 +100,10 @@ func newCommand(opts ...option) (c *command, err error) {
return nil, err
}
if err := c.initDeployCmd(); err != nil {
return nil, err
}
c.initVersionCmd()
if err := c.initConfigurateOptionsCmd(); err != nil {
......
// Copyright 2021 The Swarm Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package cmd
import (
"fmt"
"io/ioutil"
"strings"
"github.com/ethersphere/bee/pkg/logging"
"github.com/ethersphere/bee/pkg/node"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
)
func (c *command) initDeployCmd() error {
cmd := &cobra.Command{
Use: "deploy",
Short: "Deploy and fund the chequebook contract",
RunE: func(cmd *cobra.Command, args []string) (err error) {
if (len(args)) > 0 {
return cmd.Help()
}
var logger logging.Logger
switch v := strings.ToLower(c.config.GetString(optionNameVerbosity)); v {
case "0", "silent":
logger = logging.New(ioutil.Discard, 0)
case "1", "error":
logger = logging.New(cmd.OutOrStdout(), logrus.ErrorLevel)
case "2", "warn":
logger = logging.New(cmd.OutOrStdout(), logrus.WarnLevel)
case "3", "info":
logger = logging.New(cmd.OutOrStdout(), logrus.InfoLevel)
case "4", "debug":
logger = logging.New(cmd.OutOrStdout(), logrus.DebugLevel)
case "5", "trace":
logger = logging.New(cmd.OutOrStdout(), logrus.TraceLevel)
default:
return fmt.Errorf("unknown verbosity level %q", v)
}
dataDir := c.config.GetString(optionNameDataDir)
factoryAddress := c.config.GetString(optionNameSwapFactoryAddress)
swapInitialDeposit := c.config.GetString(optionNameSwapInitialDeposit)
swapEndpoint := c.config.GetString(optionNameSwapEndpoint)
stateStore, err := node.InitStateStore(logger, dataDir)
if err != nil {
return
}
defer stateStore.Close()
signerConfig, err := c.configureSigner(cmd, logger)
if err != nil {
return
}
signer := signerConfig.signer
ctx := cmd.Context()
swapBackend, overlayEthAddress, chainID, transactionService, err := node.InitChain(
ctx,
logger,
stateStore,
swapEndpoint,
signer,
)
if err != nil {
return
}
defer swapBackend.Close()
chequebookFactory, err := node.InitChequebookFactory(
logger,
swapBackend,
chainID,
transactionService,
factoryAddress,
)
if err != nil {
return
}
_, err = node.InitChequebookService(
ctx,
logger,
stateStore,
signer,
chainID,
swapBackend,
overlayEthAddress,
transactionService,
chequebookFactory,
swapInitialDeposit,
)
return
},
PreRunE: func(cmd *cobra.Command, args []string) error {
return c.config.BindPFlags(cmd.Flags())
},
}
c.setAllFlags(cmd)
c.root.AddCommand(cmd)
return nil
}
// Copyright 2021 The Swarm Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package node
import (
"context"
"errors"
"fmt"
"math/big"
"time"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/ethersphere/bee/pkg/crypto"
"github.com/ethersphere/bee/pkg/logging"
"github.com/ethersphere/bee/pkg/p2p/libp2p"
"github.com/ethersphere/bee/pkg/settlement/swap"
"github.com/ethersphere/bee/pkg/settlement/swap/chequebook"
"github.com/ethersphere/bee/pkg/settlement/swap/swapprotocol"
"github.com/ethersphere/bee/pkg/settlement/swap/transaction"
"github.com/ethersphere/bee/pkg/storage"
"gopkg.in/src-d/go-log.v1"
)
const (
maxDelay = 1 * time.Minute
)
// InitChain will initialize the Ethereum backend at the given endpoint and
// set up the Transacton Service to interact with it using the provided signer.
func InitChain(
ctx context.Context,
logger logging.Logger,
stateStore storage.StateStorer,
endpoint string,
signer crypto.Signer,
) (*ethclient.Client, common.Address, int64, transaction.Service, error) {
backend, err := ethclient.Dial(endpoint)
if err != nil {
return nil, common.Address{}, 0, nil, fmt.Errorf("dial eth client: %w", err)
}
chainID, err := backend.ChainID(ctx)
if err != nil {
logger.Infof("could not connect to backend at %v. In a swap-enabled network a working blockchain node (for goerli network in production) is required. Check your node or specify another node using --swap-endpoint.", endpoint)
return nil, common.Address{}, 0, nil, fmt.Errorf("get chain id: %w", err)
}
transactionService, err := transaction.NewService(logger, backend, signer, stateStore, chainID)
if err != nil {
return nil, common.Address{}, 0, nil, fmt.Errorf("new transaction service: %w", err)
}
overlayEthAddress, err := signer.EthereumAddress()
if err != nil {
return nil, common.Address{}, 0, nil, fmt.Errorf("eth address: %w", err)
}
// Sync the with the given Ethereum backend:
isSynced, err := transaction.IsSynced(ctx, backend, maxDelay)
if err != nil {
return nil, common.Address{}, 0, nil, fmt.Errorf("is synced: %w", err)
}
if !isSynced {
log.Infof("waiting to sync with the Ethereum backend")
err := transaction.WaitSynced(ctx, backend, maxDelay)
if err != nil {
return nil, common.Address{}, 0, nil, fmt.Errorf("waiting backend sync: %w", err)
}
}
return backend, overlayEthAddress, chainID.Int64(), transactionService, nil
}
// InitChequebookFactory will initialize the chequebook factory with the given
// chain backend.
func InitChequebookFactory(
logger logging.Logger,
backend *ethclient.Client,
chainID int64,
transactionService transaction.Service,
factoryAddress string,
) (chequebook.Factory, error) {
var addr common.Address
if factoryAddress == "" {
var found bool
addr, found = chequebook.DiscoverFactoryAddress(chainID)
if !found {
return nil, errors.New("no known factory address for this network")
}
log.Infof("using default factory address for chain id %d: %x", chainID, addr)
} else if !common.IsHexAddress(factoryAddress) {
return nil, errors.New("malformed factory address")
} else {
addr = common.HexToAddress(factoryAddress)
log.Infof("using custom factory address: %x", factoryAddress)
}
chequebookFactory, err := chequebook.NewFactory(
backend,
transactionService,
addr,
chequebook.NewSimpleSwapFactoryBindingFunc,
)
if err != nil {
return nil, fmt.Errorf("new factory: %w", err)
}
return chequebookFactory, nil
}
// InitChequebookService will initialize the chequebook service with the given
// chequebook factory and chain backend.
func InitChequebookService(
ctx context.Context,
logger logging.Logger,
stateStore storage.StateStorer,
signer crypto.Signer,
chainID int64,
backend *ethclient.Client,
overlayEthAddress common.Address,
transactionService transaction.Service,
chequebookFactory chequebook.Factory,
initialDeposit string,
) (chequebook.Service, error) {
chequeSigner := chequebook.NewChequeSigner(signer, chainID)
deposit, ok := new(big.Int).SetString(initialDeposit, 10)
if !ok {
return nil, fmt.Errorf("initial swap deposit \"%s\" cannot be parsed", initialDeposit)
}
chequebookService, err := chequebook.Init(
ctx,
chequebookFactory,
stateStore,
logger,
deposit,
transactionService,
backend,
chainID,
overlayEthAddress,
chequeSigner,
chequebook.NewSimpleSwapBindings,
chequebook.NewERC20Bindings,
)
if err != nil {
return nil, fmt.Errorf("chequebook init: %w", err)
}
return chequebookService, nil
}
func initChequeStoreCashout(
stateStore storage.StateStorer,
swapBackend transaction.Backend,
chequebookFactory chequebook.Factory,
chainID int64,
overlayEthAddress common.Address,
transactionService transaction.Service,
) (chequebook.ChequeStore, chequebook.CashoutService, error) {
chequeStore := chequebook.NewChequeStore(
stateStore,
swapBackend,
chequebookFactory,
chainID,
overlayEthAddress,
chequebook.NewSimpleSwapBindings,
chequebook.RecoverCheque,
)
cashout, err := chequebook.NewCashoutService(
stateStore,
chequebook.NewSimpleSwapBindings,
swapBackend,
transactionService,
chequeStore,
)
if err != nil {
return nil, nil, err
}
return chequeStore, cashout, nil
}
// InitSwap will initialize and register the swap service.
func InitSwap(
p2ps *libp2p.Service,
logger logging.Logger,
stateStore storage.StateStorer,
networkID uint64,
overlayEthAddress common.Address,
chequebookService chequebook.Service,
chequeStore chequebook.ChequeStore,
cashoutService chequebook.CashoutService,
) (*swap.Service, error) {
swapProtocol := swapprotocol.New(p2ps, logger, overlayEthAddress)
swapAddressBook := swap.NewAddressbook(stateStore)
swapService := swap.New(
swapProtocol,
logger,
stateStore,
chequebookService,
chequeStore,
swapAddressBook,
networkID,
cashoutService,
p2ps,
)
swapProtocol.SetSwap(swapService)
err := p2ps.AddProtocol(swapProtocol.Protocol())
if err != nil {
return nil, err
}
return swapService, nil
}
// Copyright 2020 The Swarm Authors. All rights reserved.
// Copyright 2021 The Swarm Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
......@@ -10,7 +10,6 @@ package node
import (
"context"
"crypto/ecdsa"
"errors"
"fmt"
"io"
"log"
......@@ -50,10 +49,7 @@ import (
"github.com/ethersphere/bee/pkg/settlement/pseudosettle"
"github.com/ethersphere/bee/pkg/settlement/swap"
"github.com/ethersphere/bee/pkg/settlement/swap/chequebook"
"github.com/ethersphere/bee/pkg/settlement/swap/swapprotocol"
"github.com/ethersphere/bee/pkg/settlement/swap/transaction"
"github.com/ethersphere/bee/pkg/statestore/leveldb"
mockinmem "github.com/ethersphere/bee/pkg/statestore/mock"
"github.com/ethersphere/bee/pkg/storage"
"github.com/ethersphere/bee/pkg/swarm"
"github.com/ethersphere/bee/pkg/tags"
......@@ -81,6 +77,7 @@ type Bee struct {
pullerCloser io.Closer
pullSyncCloser io.Closer
pssCloser io.Closer
ethClientCloser func()
recoveryHandleCleanup func()
}
......@@ -131,103 +128,75 @@ func NewBee(addr string, swarmAddress swarm.Address, publicKey ecdsa.PublicKey,
tracerCloser: tracerCloser,
}
var stateStore storage.StateStorer
if o.DataDir == "" {
stateStore = mockinmem.NewStateStore()
logger.Warning("using in-mem state store. no node state will be persisted")
} else {
stateStore, err = leveldb.NewStateStore(filepath.Join(o.DataDir, "statestore"), logger)
stateStore, err := InitStateStore(logger, o.DataDir)
if err != nil {
return nil, fmt.Errorf("statestore: %w", err)
}
return nil, err
}
b.stateStoreCloser = stateStore
addressbook := addressbook.New(stateStore)
var swapBackend *ethclient.Client
var overlayEthAddress common.Address
var chainID int64
var transactionService transaction.Service
var chequebookFactory chequebook.Factory
var chequebookService chequebook.Service
var chequeStore chequebook.ChequeStore
var cashoutService chequebook.CashoutService
var overlayEthAddress common.Address
if o.SwapEnable {
swapBackend, err := ethclient.Dial(o.SwapEndpoint)
if err != nil {
return nil, err
}
chainID, err := swapBackend.ChainID(p2pCtx)
if err != nil {
logger.Infof("could not connect to backend at %v. In a swap-enabled network a working blockchain node (for goerli network in production) is required. Check your node or specify another node using --swap-endpoint.", o.SwapEndpoint)
return nil, fmt.Errorf("could not get chain id from ethereum backend: %w", err)
}
transactionService, err := transaction.NewService(logger, swapBackend, signer, stateStore, chainID)
if err != nil {
return nil, err
}
overlayEthAddress, err = signer.EthereumAddress()
if o.SwapEnable {
swapBackend, overlayEthAddress, chainID, transactionService, err = InitChain(
p2pCtx,
logger,
stateStore,
o.SwapEndpoint,
signer,
)
if err != nil {
return nil, err
}
b.ethClientCloser = swapBackend.Close
var factoryAddress common.Address
if o.SwapFactoryAddress == "" {
var found bool
factoryAddress, found = chequebook.DiscoverFactoryAddress(chainID.Int64())
if !found {
return nil, errors.New("no known factory address for this network")
}
logger.Infof("using default factory address for chain id %d: %x", chainID, factoryAddress)
} else if !common.IsHexAddress(o.SwapFactoryAddress) {
return nil, errors.New("malformed factory address")
} else {
factoryAddress = common.HexToAddress(o.SwapFactoryAddress)
logger.Infof("using custom factory address: %x", factoryAddress)
}
chequebookFactory, err := chequebook.NewFactory(swapBackend, transactionService, factoryAddress, chequebook.NewSimpleSwapFactoryBindingFunc)
chequebookFactory, err = InitChequebookFactory(
logger,
swapBackend,
chainID,
transactionService,
o.SwapFactoryAddress,
)
if err != nil {
return nil, err
}
chequeSigner := chequebook.NewChequeSigner(signer, chainID.Int64())
maxDelay := 1 * time.Minute
synced, err := transaction.IsSynced(p2pCtx, swapBackend, maxDelay)
if err != nil {
return nil, err
}
if !synced {
logger.Infof("waiting for ethereum backend to be synced.")
err = transaction.WaitSynced(p2pCtx, swapBackend, maxDelay)
if err != nil {
return nil, fmt.Errorf("could not wait for ethereum backend to sync: %w", err)
}
if err = chequebookFactory.VerifyBytecode(p2pCtx); err != nil {
return nil, fmt.Errorf("factory fail: %w", err)
}
swapInitialDeposit, ok := new(big.Int).SetString(o.SwapInitialDeposit, 10)
if !ok {
return nil, fmt.Errorf("invalid initial deposit: %s", swapInitialDeposit)
}
// initialize chequebook logic
chequebookService, err = chequebook.Init(p2pCtx,
chequebookFactory,
stateStore,
chequebookService, err = InitChequebookService(
p2pCtx,
logger,
swapInitialDeposit,
transactionService,
stateStore,
signer,
chainID,
swapBackend,
chainID.Int64(),
overlayEthAddress,
chequeSigner,
chequebook.NewSimpleSwapBindings,
chequebook.NewERC20Bindings)
transactionService,
chequebookFactory,
o.SwapInitialDeposit,
)
if err != nil {
return nil, err
}
chequeStore = chequebook.NewChequeStore(stateStore, swapBackend, chequebookFactory, chainID.Int64(), overlayEthAddress, chequebook.NewSimpleSwapBindings, chequebook.RecoverCheque)
cashoutService, err = chequebook.NewCashoutService(stateStore, chequebook.NewSimpleSwapBindings, swapBackend, transactionService, chequeStore)
chequeStore, cashoutService, err = initChequeStoreCashout(
stateStore,
swapBackend,
chequebookFactory,
chainID,
overlayEthAddress,
transactionService,
)
if err != nil {
return nil, err
}
......@@ -294,12 +263,18 @@ func NewBee(addr string, swarmAddress swarm.Address, publicKey ecdsa.PublicKey,
var swapService *swap.Service
if o.SwapEnable {
swapProtocol := swapprotocol.New(p2ps, logger, overlayEthAddress)
swapAddressBook := swap.NewAddressbook(stateStore)
swapService = swap.New(swapProtocol, logger, stateStore, chequebookService, chequeStore, swapAddressBook, networkID, cashoutService, p2ps)
swapProtocol.SetSwap(swapService)
if err = p2ps.AddProtocol(swapProtocol.Protocol()); err != nil {
return nil, fmt.Errorf("swap protocol: %w", err)
swapService, err = InitSwap(
p2ps,
logger,
stateStore,
networkID,
overlayEthAddress,
chequebookService,
chequeStore,
cashoutService,
)
if err != nil {
return nil, err
}
settlement = swapService
} else {
......@@ -582,6 +557,10 @@ func (b *Bee) Shutdown(ctx context.Context) error {
errs.add(fmt.Errorf("p2p server: %w", err))
}
if c := b.ethClientCloser; c != nil {
c()
}
if err := b.tracerCloser.Close(); err != nil {
errs.add(fmt.Errorf("tracer: %w", err))
}
......
// Copyright 2021 The Swarm Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package node
import (
"path/filepath"
"github.com/ethersphere/bee/pkg/logging"
"github.com/ethersphere/bee/pkg/statestore/leveldb"
"github.com/ethersphere/bee/pkg/statestore/mock"
"github.com/ethersphere/bee/pkg/storage"
)
// InitStateStore will initialze the stateStore with the given path to the
// data directory. When given an empty directory path, the function will instead
// initialize an in-memory state store that will not be persisted.
func InitStateStore(log logging.Logger, dataDir string) (ret storage.StateStorer, err error) {
if dataDir == "" {
ret = mock.NewStateStore()
log.Warning("using in-mem state store, no node state will be persisted")
return ret, nil
}
return leveldb.NewStateStore(filepath.Join(dataDir, "statestore"), log)
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment