github.com/ethersphere/bee/v2@v2.2.0/pkg/node/chain.go (about)

     1  // Copyright 2021 The Swarm Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package node
     6  
     7  import (
     8  	"context"
     9  	"encoding/hex"
    10  	"errors"
    11  	"fmt"
    12  	"math/big"
    13  	"strings"
    14  	"time"
    15  
    16  	"github.com/ethereum/go-ethereum"
    17  	"github.com/ethereum/go-ethereum/common"
    18  	"github.com/ethereum/go-ethereum/core/types"
    19  	"github.com/ethereum/go-ethereum/ethclient"
    20  	"github.com/ethereum/go-ethereum/rpc"
    21  	"github.com/ethersphere/bee/v2/pkg/config"
    22  	"github.com/ethersphere/bee/v2/pkg/crypto"
    23  	"github.com/ethersphere/bee/v2/pkg/log"
    24  	"github.com/ethersphere/bee/v2/pkg/p2p/libp2p"
    25  	"github.com/ethersphere/bee/v2/pkg/postage/postagecontract"
    26  	"github.com/ethersphere/bee/v2/pkg/sctx"
    27  	"github.com/ethersphere/bee/v2/pkg/settlement"
    28  	"github.com/ethersphere/bee/v2/pkg/settlement/swap"
    29  	"github.com/ethersphere/bee/v2/pkg/settlement/swap/chequebook"
    30  	"github.com/ethersphere/bee/v2/pkg/settlement/swap/erc20"
    31  	"github.com/ethersphere/bee/v2/pkg/settlement/swap/priceoracle"
    32  	"github.com/ethersphere/bee/v2/pkg/settlement/swap/swapprotocol"
    33  	"github.com/ethersphere/bee/v2/pkg/storage"
    34  	"github.com/ethersphere/bee/v2/pkg/transaction"
    35  	"github.com/ethersphere/bee/v2/pkg/transaction/wrapped"
    36  	"github.com/ethersphere/go-sw3-abi/sw3abi"
    37  	"github.com/prometheus/client_golang/prometheus"
    38  )
    39  
    40  const (
    41  	maxDelay                = 1 * time.Minute
    42  	cancellationDepth       = 12
    43  	additionalConfirmations = 2
    44  )
    45  
    46  // InitChain will initialize the Ethereum backend at the given endpoint and
    47  // set up the Transaction Service to interact with it using the provided signer.
    48  func InitChain(
    49  	ctx context.Context,
    50  	logger log.Logger,
    51  	stateStore storage.StateStorer,
    52  	endpoint string,
    53  	oChainID int64,
    54  	signer crypto.Signer,
    55  	pollingInterval time.Duration,
    56  	chainEnabled bool,
    57  ) (transaction.Backend, common.Address, int64, transaction.Monitor, transaction.Service, error) {
    58  	var backend transaction.Backend = &noOpChainBackend{
    59  		chainID: oChainID,
    60  	}
    61  
    62  	if chainEnabled {
    63  		// connect to the real one
    64  		rpcClient, err := rpc.DialContext(ctx, endpoint)
    65  		if err != nil {
    66  			return nil, common.Address{}, 0, nil, nil, fmt.Errorf("dial blockchain client: %w", err)
    67  		}
    68  
    69  		var versionString string
    70  		err = rpcClient.CallContext(ctx, &versionString, "web3_clientVersion")
    71  		if err != nil {
    72  			logger.Info("could not connect to backend; in a swap-enabled network a working blockchain node (for xdai network in production, sepolia in testnet) is required; check your node or specify another node using --swap-endpoint.", "backend_endpoint", endpoint)
    73  			return nil, common.Address{}, 0, nil, nil, fmt.Errorf("blockchain client get version: %w", err)
    74  		}
    75  
    76  		logger.Info("connected to blockchain backend", "version", versionString)
    77  
    78  		backend = wrapped.NewBackend(ethclient.NewClient(rpcClient))
    79  	}
    80  
    81  	chainID, err := backend.ChainID(ctx)
    82  	if err != nil {
    83  		return nil, common.Address{}, 0, nil, nil, fmt.Errorf("get chain id: %w", err)
    84  	}
    85  
    86  	overlayEthAddress, err := signer.EthereumAddress()
    87  	if err != nil {
    88  		return nil, common.Address{}, 0, nil, nil, fmt.Errorf("blockchain address: %w", err)
    89  	}
    90  
    91  	transactionMonitor := transaction.NewMonitor(logger, backend, overlayEthAddress, pollingInterval, cancellationDepth)
    92  
    93  	transactionService, err := transaction.NewService(logger, overlayEthAddress, backend, signer, stateStore, chainID, transactionMonitor)
    94  	if err != nil {
    95  		return nil, common.Address{}, 0, nil, nil, fmt.Errorf("new transaction service: %w", err)
    96  	}
    97  
    98  	return backend, overlayEthAddress, chainID.Int64(), transactionMonitor, transactionService, nil
    99  }
   100  
   101  // InitChequebookFactory will initialize the chequebook factory with the given
   102  // chain backend.
   103  func InitChequebookFactory(logger log.Logger, backend transaction.Backend, chainID int64, transactionService transaction.Service, factoryAddress string) (chequebook.Factory, error) {
   104  	var currentFactory common.Address
   105  	chainCfg, found := config.GetByChainID(chainID)
   106  
   107  	foundFactory := chainCfg.CurrentFactoryAddress
   108  	if factoryAddress == "" {
   109  		if !found {
   110  			return nil, fmt.Errorf("no known factory address for this network (chain id: %d)", chainID)
   111  		}
   112  		currentFactory = foundFactory
   113  		logger.Info("using default factory address", "chain_id", chainID, "factory_address", currentFactory)
   114  	} else if !common.IsHexAddress(factoryAddress) {
   115  		return nil, errors.New("malformed factory address")
   116  	} else {
   117  		currentFactory = common.HexToAddress(factoryAddress)
   118  		logger.Info("using custom factory address", "factory_address", currentFactory)
   119  	}
   120  
   121  	return chequebook.NewFactory(backend, transactionService, currentFactory), nil
   122  }
   123  
   124  // InitChequebookService will initialize the chequebook service with the given
   125  // chequebook factory and chain backend.
   126  func InitChequebookService(
   127  	ctx context.Context,
   128  	logger log.Logger,
   129  	stateStore storage.StateStorer,
   130  	signer crypto.Signer,
   131  	chainID int64,
   132  	backend transaction.Backend,
   133  	overlayEthAddress common.Address,
   134  	transactionService transaction.Service,
   135  	chequebookFactory chequebook.Factory,
   136  	initialDeposit string,
   137  	deployGasPrice string,
   138  	erc20Service erc20.Service,
   139  ) (chequebook.Service, error) {
   140  	chequeSigner := chequebook.NewChequeSigner(signer, chainID)
   141  
   142  	deposit, ok := new(big.Int).SetString(initialDeposit, 10)
   143  	if !ok {
   144  		return nil, fmt.Errorf("initial swap deposit \"%s\" cannot be parsed", initialDeposit)
   145  	}
   146  
   147  	if deployGasPrice != "" {
   148  		gasPrice, ok := new(big.Int).SetString(deployGasPrice, 10)
   149  		if !ok {
   150  			return nil, fmt.Errorf("deploy gas price \"%s\" cannot be parsed", deployGasPrice)
   151  		}
   152  		ctx = sctx.SetGasPrice(ctx, gasPrice)
   153  	}
   154  
   155  	chequebookService, err := chequebook.Init(
   156  		ctx,
   157  		chequebookFactory,
   158  		stateStore,
   159  		logger,
   160  		deposit,
   161  		transactionService,
   162  		backend,
   163  		chainID,
   164  		overlayEthAddress,
   165  		chequeSigner,
   166  		erc20Service,
   167  	)
   168  	if err != nil {
   169  		return nil, fmt.Errorf("chequebook init: %w", err)
   170  	}
   171  
   172  	return chequebookService, nil
   173  }
   174  
   175  func initChequeStoreCashout(
   176  	stateStore storage.StateStorer,
   177  	swapBackend transaction.Backend,
   178  	chequebookFactory chequebook.Factory,
   179  	chainID int64,
   180  	overlayEthAddress common.Address,
   181  	transactionService transaction.Service,
   182  ) (chequebook.ChequeStore, chequebook.CashoutService) {
   183  	chequeStore := chequebook.NewChequeStore(
   184  		stateStore,
   185  		chequebookFactory,
   186  		chainID,
   187  		overlayEthAddress,
   188  		transactionService,
   189  		chequebook.RecoverCheque,
   190  	)
   191  
   192  	cashout := chequebook.NewCashoutService(
   193  		stateStore,
   194  		swapBackend,
   195  		transactionService,
   196  		chequeStore,
   197  	)
   198  
   199  	return chequeStore, cashout
   200  }
   201  
   202  // InitSwap will initialize and register the swap service.
   203  func InitSwap(
   204  	p2ps *libp2p.Service,
   205  	logger log.Logger,
   206  	stateStore storage.StateStorer,
   207  	networkID uint64,
   208  	overlayEthAddress common.Address,
   209  	chequebookService chequebook.Service,
   210  	chequeStore chequebook.ChequeStore,
   211  	cashoutService chequebook.CashoutService,
   212  	accounting settlement.Accounting,
   213  	priceOracleAddress string,
   214  	chainID int64,
   215  	transactionService transaction.Service,
   216  ) (*swap.Service, priceoracle.Service, error) {
   217  
   218  	var currentPriceOracleAddress common.Address
   219  	if priceOracleAddress == "" {
   220  		chainCfg, found := config.GetByChainID(chainID)
   221  		currentPriceOracleAddress = chainCfg.SwapPriceOracleAddress
   222  		if !found {
   223  			return nil, nil, errors.New("no known price oracle address for this network")
   224  		}
   225  	} else {
   226  		currentPriceOracleAddress = common.HexToAddress(priceOracleAddress)
   227  	}
   228  
   229  	priceOracle := priceoracle.New(logger, currentPriceOracleAddress, transactionService, 300)
   230  	priceOracle.Start()
   231  	swapProtocol := swapprotocol.New(p2ps, logger, overlayEthAddress, priceOracle)
   232  	swapAddressBook := swap.NewAddressbook(stateStore)
   233  
   234  	cashoutAddress := overlayEthAddress
   235  	if chequebookService != nil {
   236  		cashoutAddress = chequebookService.Address()
   237  	}
   238  
   239  	swapService := swap.New(
   240  		swapProtocol,
   241  		logger,
   242  		stateStore,
   243  		chequebookService,
   244  		chequeStore,
   245  		swapAddressBook,
   246  		networkID,
   247  		cashoutService,
   248  		accounting,
   249  		cashoutAddress,
   250  	)
   251  
   252  	swapProtocol.SetSwap(swapService)
   253  
   254  	err := p2ps.AddProtocol(swapProtocol.Protocol())
   255  	if err != nil {
   256  		return nil, nil, err
   257  	}
   258  
   259  	return swapService, priceOracle, nil
   260  }
   261  
   262  func GetTxHash(stateStore storage.StateStorer, logger log.Logger, trxString string) ([]byte, error) {
   263  
   264  	if trxString != "" {
   265  		txHashTrimmed := strings.TrimPrefix(trxString, "0x")
   266  		if len(txHashTrimmed) != 64 {
   267  			return nil, errors.New("invalid length")
   268  		}
   269  		txHash, err := hex.DecodeString(txHashTrimmed)
   270  		if err != nil {
   271  			return nil, err
   272  		}
   273  		logger.Info("using the provided transaction hash", "tx_hash", txHashTrimmed)
   274  		return txHash, nil
   275  	}
   276  
   277  	var txHash common.Hash
   278  	key := chequebook.ChequebookDeploymentKey
   279  	if err := stateStore.Get(key, &txHash); err != nil {
   280  		if errors.Is(err, storage.ErrNotFound) {
   281  			return nil, errors.New("chequebook deployment transaction hash not found, please specify the transaction hash manually")
   282  		}
   283  		return nil, err
   284  	}
   285  
   286  	logger.Info("using the chequebook transaction hash", "tx_hash", txHash)
   287  	return txHash.Bytes(), nil
   288  }
   289  
   290  func GetTxNextBlock(ctx context.Context, logger log.Logger, backend transaction.Backend, monitor transaction.Monitor, duration time.Duration, trx []byte, blockHash string) ([]byte, error) {
   291  
   292  	if blockHash != "" {
   293  		blockHashTrimmed := strings.TrimPrefix(blockHash, "0x")
   294  		if len(blockHashTrimmed) != 64 {
   295  			return nil, errors.New("invalid length")
   296  		}
   297  		blockHash, err := hex.DecodeString(blockHashTrimmed)
   298  		if err != nil {
   299  			return nil, err
   300  		}
   301  		logger.Info("using the provided block hash", "block_hash", hex.EncodeToString(blockHash))
   302  		return blockHash, nil
   303  	}
   304  
   305  	block, err := transaction.WaitBlockAfterTransaction(ctx, backend, duration, common.BytesToHash(trx), additionalConfirmations)
   306  	if err != nil {
   307  		return nil, err
   308  	}
   309  
   310  	hash := block.Hash()
   311  	hashBytes := hash.Bytes()
   312  
   313  	logger.Info("using the next block hash from the blockchain", "block_hash", hex.EncodeToString(hashBytes))
   314  
   315  	return hashBytes, nil
   316  }
   317  
   318  // noOpChequebookService is a noOp implementation for chequebook.Service interface.
   319  type noOpChequebookService struct{}
   320  
   321  func (m *noOpChequebookService) Deposit(context.Context, *big.Int) (hash common.Hash, err error) {
   322  	return hash, postagecontract.ErrChainDisabled
   323  }
   324  func (m *noOpChequebookService) Withdraw(context.Context, *big.Int) (hash common.Hash, err error) {
   325  	return hash, postagecontract.ErrChainDisabled
   326  }
   327  func (m *noOpChequebookService) WaitForDeposit(context.Context, common.Hash) error {
   328  	return postagecontract.ErrChainDisabled
   329  }
   330  func (m *noOpChequebookService) Balance(context.Context) (*big.Int, error) {
   331  	return nil, postagecontract.ErrChainDisabled
   332  }
   333  func (m *noOpChequebookService) AvailableBalance(context.Context) (*big.Int, error) {
   334  	return nil, postagecontract.ErrChainDisabled
   335  }
   336  func (m *noOpChequebookService) Address() common.Address {
   337  	return common.Address{}
   338  }
   339  func (m *noOpChequebookService) Issue(context.Context, common.Address, *big.Int, chequebook.SendChequeFunc) (*big.Int, error) {
   340  	return nil, postagecontract.ErrChainDisabled
   341  }
   342  func (m *noOpChequebookService) LastCheque(common.Address) (*chequebook.SignedCheque, error) {
   343  	return nil, postagecontract.ErrChainDisabled
   344  }
   345  func (m *noOpChequebookService) LastCheques() (map[common.Address]*chequebook.SignedCheque, error) {
   346  	return nil, postagecontract.ErrChainDisabled
   347  }
   348  
   349  // noOpChainBackend is a noOp implementation for transaction.Backend interface.
   350  type noOpChainBackend struct {
   351  	chainID int64
   352  }
   353  
   354  func (m noOpChainBackend) Metrics() []prometheus.Collector {
   355  	return nil
   356  }
   357  
   358  func (m noOpChainBackend) CodeAt(context.Context, common.Address, *big.Int) ([]byte, error) {
   359  	return common.FromHex(sw3abi.SimpleSwapFactoryDeployedBinv0_6_5), nil
   360  }
   361  func (m noOpChainBackend) CallContract(context.Context, ethereum.CallMsg, *big.Int) ([]byte, error) {
   362  	return nil, errors.New("disabled chain backend")
   363  }
   364  func (m noOpChainBackend) HeaderByNumber(context.Context, *big.Int) (*types.Header, error) {
   365  	h := new(types.Header)
   366  	h.Time = uint64(time.Now().Unix())
   367  	return h, nil
   368  }
   369  func (m noOpChainBackend) PendingNonceAt(context.Context, common.Address) (uint64, error) {
   370  	panic("chain no op: PendingNonceAt")
   371  }
   372  func (m noOpChainBackend) SuggestGasPrice(context.Context) (*big.Int, error) {
   373  	panic("chain no op: SuggestGasPrice")
   374  }
   375  func (m noOpChainBackend) SuggestGasTipCap(context.Context) (*big.Int, error) {
   376  	panic("chain no op: SuggestGasPrice")
   377  }
   378  func (m noOpChainBackend) EstimateGas(context.Context, ethereum.CallMsg) (uint64, error) {
   379  	panic("chain no op: EstimateGas")
   380  }
   381  func (m noOpChainBackend) SendTransaction(context.Context, *types.Transaction) error {
   382  	panic("chain no op: SendTransaction")
   383  }
   384  func (m noOpChainBackend) TransactionReceipt(context.Context, common.Hash) (*types.Receipt, error) {
   385  	r := new(types.Receipt)
   386  	r.BlockNumber = big.NewInt(1)
   387  	return r, nil
   388  }
   389  func (m noOpChainBackend) TransactionByHash(context.Context, common.Hash) (tx *types.Transaction, isPending bool, err error) {
   390  	panic("chain no op: TransactionByHash")
   391  }
   392  func (m noOpChainBackend) BlockNumber(context.Context) (uint64, error) {
   393  	return 4, nil
   394  }
   395  func (m noOpChainBackend) BalanceAt(context.Context, common.Address, *big.Int) (*big.Int, error) {
   396  	return nil, postagecontract.ErrChainDisabled
   397  }
   398  func (m noOpChainBackend) NonceAt(context.Context, common.Address, *big.Int) (uint64, error) {
   399  	panic("chain no op: NonceAt")
   400  }
   401  func (m noOpChainBackend) FilterLogs(context.Context, ethereum.FilterQuery) ([]types.Log, error) {
   402  	panic("chain no op: FilterLogs")
   403  }
   404  func (m noOpChainBackend) ChainID(context.Context) (*big.Int, error) {
   405  	return big.NewInt(m.chainID), nil
   406  }
   407  func (m noOpChainBackend) Close() {}