diff --git a/.github/workflows/beekeeper.yml b/.github/workflows/beekeeper.yml index 0cb7c41fe2f..b907093bad0 100644 --- a/.github/workflows/beekeeper.yml +++ b/.github/workflows/beekeeper.yml @@ -7,8 +7,8 @@ on: paths-ignore: - packaging/** - openapi/** - - '**/*.md' - - '.github/ISSUE_TEMPLATE/**' + - "**/*.md" + - ".github/ISSUE_TEMPLATE/**" branches: - "**" diff --git a/cmd/bee/cmd/cmd.go b/cmd/bee/cmd/cmd.go index eb09b03021a..6b22f74ddfd 100644 --- a/cmd/bee/cmd/cmd.go +++ b/cmd/bee/cmd/cmd.go @@ -50,6 +50,7 @@ const ( optionNamePaymentEarly = "payment-early-percent" optionNameResolverEndpoints = "resolver-options" optionNameBootnodeMode = "bootnode-mode" + optionNameBzzTokenAddress = "bzz-token-address" optionNameSwapFactoryAddress = "swap-factory-address" optionNameSwapInitialDeposit = "swap-initial-deposit" optionNameSwapEnable = "swap-enable" @@ -301,6 +302,7 @@ func (c *command) setAllFlags(cmd *cobra.Command) { cmd.Flags().Duration(optionNameBlockchainRpcIdleTimeout, 90*time.Second, "blockchain rpc idle connection timeout") cmd.Flags().Duration(optionNameBlockchainRpcKeepalive, 30*time.Second, "blockchain rpc TCP keepalive interval") cmd.Flags().String(optionNameSwapFactoryAddress, "", "swap factory addresses") + cmd.Flags().String(optionNameBzzTokenAddress, "", "bzz token contract address") cmd.Flags().String(optionNameSwapInitialDeposit, "0", "initial deposit if deploying a new chequebook") cmd.Flags().Bool(optionNameSwapEnable, false, "enable swap") cmd.Flags().Bool(optionNameChequebookEnable, true, "enable chequebook") diff --git a/cmd/bee/cmd/start.go b/cmd/bee/cmd/start.go index 0e13c75ccb3..a423c6bbcaf 100644 --- a/cmd/bee/cmd/start.go +++ b/cmd/bee/cmd/start.go @@ -21,6 +21,7 @@ import ( "syscall" "time" + "github.com/ethereum/go-ethereum/common" "github.com/ethersphere/bee/v2" "github.com/ethersphere/bee/v2/pkg/accesscontrol" "github.com/ethersphere/bee/v2/pkg/bmt" @@ -285,6 +286,14 @@ func buildBeeNode(ctx context.Context, c *command, cmd *cobra.Command, logger lo logger.Info("SIMD hashing enabled", "batch_width", keccak.BatchWidth(), "avx512", keccak.HasAVX512()) } + var bzzTokenAddress common.Address + if a := c.config.GetString(optionNameBzzTokenAddress); a != "" { + if !common.IsHexAddress(a) { + return nil, errors.New("malformed bzz token address") + } + bzzTokenAddress = common.HexToAddress(a) + } + b, err := node.NewBee(ctx, c.config.GetString(optionNameP2PAddr), signerConfig.publicKey, signerConfig.signer, networkID, logger, signerConfig.libp2pPrivateKey, signerConfig.pssPrivateKey, signerConfig.session, &node.Options{ Addr: c.config.GetString(optionNameP2PAddr), AllowPrivateCIDRs: c.config.GetBool(optionNameAllowPrivateCIDRs), @@ -297,6 +306,7 @@ func buildBeeNode(ctx context.Context, c *command, cmd *cobra.Command, logger lo BlockchainRpcTLSTimeout: c.config.GetDuration(configKeyBlockchainRpcTLSTimeout), BlockchainRpcIdleTimeout: c.config.GetDuration(configKeyBlockchainRpcIdleTimeout), BlockchainRpcKeepalive: c.config.GetDuration(configKeyBlockchainRpcKeepalive), + BzzTokenAddress: bzzTokenAddress, BlockProfile: c.config.GetBool(optionNamePProfBlock), BlockTime: networkConfig.blockTime, BlockSyncInterval: c.config.GetUint64(optionNameBlockSyncInterval), diff --git a/packaging/bee.yaml b/packaging/bee.yaml index 835ab8ba9ed..59bcad603af 100644 --- a/packaging/bee.yaml +++ b/packaging/bee.yaml @@ -19,6 +19,8 @@ # bootnode: ["/dnsaddr/mainnet.ethswarm.org"] ## cause the node to always accept incoming connections # bootnode-mode: false +## bzz token contract address +# bzz-token-address: "" ## cache capacity in chunks, multiply by 4096 to get approximate capacity in bytes # cache-capacity: "1000000" ## enable forwarded content caching diff --git a/packaging/docker/docker-compose.yml b/packaging/docker/docker-compose.yml index ba0a33cade4..16818215c7c 100644 --- a/packaging/docker/docker-compose.yml +++ b/packaging/docker/docker-compose.yml @@ -19,6 +19,7 @@ services: - BEE_BLOCKCHAIN_RPC_TLS_TIMEOUT - BEE_BOOTNODE - BEE_BOOTNODE_MODE + - BEE_BZZ_TOKEN_ADDRESS - BEE_CACHE_CAPACITY - BEE_CACHE_RETRIEVAL - BEE_CHEQUEBOOK_ENABLE diff --git a/packaging/docker/env b/packaging/docker/env index 5fbcb195345..f33a62c0f15 100644 --- a/packaging/docker/env +++ b/packaging/docker/env @@ -31,6 +31,8 @@ # BEE_BOOTNODE=[/dnsaddr/mainnet.ethswarm.org] ## cause the node to always accept incoming connections (default false) # BEE_BOOTNODE_MODE=false +## bzz token contract address +# BEE_BZZ_TOKEN_ADDRESS= ## cache capacity in chunks, multiply by 4096 to get approximate capacity in bytes (default 1000000) # BEE_CACHE_CAPACITY=1000000 ## enable forwarded content caching (default true) diff --git a/packaging/homebrew-amd64/bee.yaml b/packaging/homebrew-amd64/bee.yaml index fffe37234c7..01e33f4c529 100644 --- a/packaging/homebrew-amd64/bee.yaml +++ b/packaging/homebrew-amd64/bee.yaml @@ -19,6 +19,8 @@ # bootnode: ["/dnsaddr/mainnet.ethswarm.org"] ## cause the node to always accept incoming connections # bootnode-mode: false +## bzz token contract address +# bzz-token-address: "" ## cache capacity in chunks, multiply by 4096 to get approximate capacity in bytes # cache-capacity: "1000000" ## enable forwarded content caching diff --git a/packaging/homebrew-arm64/bee.yaml b/packaging/homebrew-arm64/bee.yaml index 3d908a690c7..33608073903 100644 --- a/packaging/homebrew-arm64/bee.yaml +++ b/packaging/homebrew-arm64/bee.yaml @@ -19,6 +19,8 @@ # bootnode: ["/dnsaddr/mainnet.ethswarm.org"] ## cause the node to always accept incoming connections # bootnode-mode: false +## bzz token contract address +# bzz-token-address: "" ## cache capacity in chunks, multiply by 4096 to get approximate capacity in bytes # cache-capacity: "1000000" ## enable forwarded content caching diff --git a/packaging/scoop/bee.yaml b/packaging/scoop/bee.yaml index 32fe5d1f970..95864803848 100644 --- a/packaging/scoop/bee.yaml +++ b/packaging/scoop/bee.yaml @@ -19,6 +19,8 @@ # bootnode: ["/dnsaddr/mainnet.ethswarm.org"] ## cause the node to always accept incoming connections # bootnode-mode: false +## bzz token contract address +# bzz-token-address: "" ## cache capacity in chunks, multiply by 4096 to get approximate capacity in bytes # cache-capacity: "1000000" ## enable forwarded content caching diff --git a/pkg/config/chain.go b/pkg/config/chain.go index 1bbadfa169f..0cf07026bf1 100644 --- a/pkg/config/chain.go +++ b/pkg/config/chain.go @@ -12,8 +12,6 @@ import ( "github.com/ethersphere/go-storage-incentives-abi/abi" ) -// TODO: consider adding BzzAddress (also as a cmd param) to the ChainConfig and remove the postagecontract.LookupERC20Address function. - type ChainConfig struct { // General. ChainID int64 @@ -28,6 +26,7 @@ type ChainConfig struct { RedistributionAddress common.Address SwapPriceOracleAddress common.Address // Swap swear and swindle (S3) Contracts CurrentFactoryAddress common.Address + TokenContractAddress common.Address // ABIs. StakingABI string @@ -65,6 +64,7 @@ var ( RedistributionAddress: common.HexToAddress(abi.TestnetRedistributionAddress), SwapPriceOracleAddress: common.HexToAddress("0x1814e9b3951Df0CB8e12b2bB99c5594514588936"), CurrentFactoryAddress: common.HexToAddress("0x0fF044F6bB4F684a5A149B46D7eC03ea659F98A1"), + TokenContractAddress: common.HexToAddress(abi.TestnetBzzTokenAddress), StakingABI: abi.TestnetStakingABI, PostageStampABI: abi.TestnetPostageStampABI, @@ -87,6 +87,7 @@ var ( RedistributionAddress: common.HexToAddress(abi.MainnetRedistributionAddress), SwapPriceOracleAddress: common.HexToAddress("0xA57A50a831B31c904A770edBCb706E03afCdbd94"), CurrentFactoryAddress: common.HexToAddress("0xc2d5a532cf69aa9a1378737d8ccdef884b6e7420"), + TokenContractAddress: common.HexToAddress(abi.MainnetBzzTokenAddress), StakingABI: abi.MainnetStakingABI, PostageStampABI: abi.MainnetPostageStampABI, diff --git a/pkg/config/chain_test.go b/pkg/config/chain_test.go index 241c908a097..5ecb9262102 100644 --- a/pkg/config/chain_test.go +++ b/pkg/config/chain_test.go @@ -13,8 +13,73 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/ethclient" + "github.com/ethersphere/go-storage-incentives-abi/abi" + + "github.com/ethersphere/bee/v2/pkg/config" ) +func TestChainConfigTokenContractAddress(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + cfg config.ChainConfig + want common.Address + }{ + { + name: "mainnet", + cfg: config.Mainnet, + want: common.HexToAddress(abi.MainnetBzzTokenAddress), + }, + { + name: "testnet", + cfg: config.Testnet, + want: common.HexToAddress(abi.TestnetBzzTokenAddress), + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + if (tc.want == common.Address{}) { + t.Fatal("expected a non-zero bzz token address") + } + if tc.cfg.TokenContractAddress != tc.want { + t.Fatalf("got token contract address %s, want %s", tc.cfg.TokenContractAddress, tc.want) + } + }) + } +} + +func TestGetByChainIDTokenContractAddress(t *testing.T) { + t.Parallel() + + t.Run("known chain", func(t *testing.T) { + t.Parallel() + + cfg, found := config.GetByChainID(config.Mainnet.ChainID) + if !found { + t.Fatal("expected mainnet to be a known chain") + } + if cfg.TokenContractAddress != common.HexToAddress(abi.MainnetBzzTokenAddress) { + t.Fatalf("got token contract address %s, want %s", cfg.TokenContractAddress, abi.MainnetBzzTokenAddress) + } + }) + + t.Run("unknown chain", func(t *testing.T) { + t.Parallel() + + cfg, found := config.GetByChainID(-1) + if found { + t.Fatal("expected unknown chain to be reported as not found") + } + if (cfg.TokenContractAddress != common.Address{}) { + t.Fatalf("expected zero token contract address for unknown chain, got %s", cfg.TokenContractAddress) + } + }) +} + // TestDeriveChequebookBytecodeHash derives the keccak256(eth_getCode) hash for // a known-good chequebook deployed by a factory. Run this manually after a // factory upgrade to get the new hash for AcceptedChequebookBytecodeHashes. diff --git a/pkg/node/node.go b/pkg/node/node.go index 0b3c6f12bd4..b07a85ccae8 100644 --- a/pkg/node/node.go +++ b/pkg/node/node.go @@ -139,6 +139,7 @@ type Options struct { BlockchainRpcTLSTimeout time.Duration BlockchainRpcIdleTimeout time.Duration BlockchainRpcKeepalive time.Duration + BzzTokenAddress common.Address BlockProfile bool BlockTime time.Duration BlockSyncInterval uint64 @@ -545,56 +546,54 @@ func NewBee( } } + chainCfg, knownChain := config.GetByChainID(chainID) + + bzzTokenAddress := chainCfg.TokenContractAddress + if o.BzzTokenAddress != (common.Address{}) { + bzzTokenAddress = o.BzzTokenAddress + } + if chainEnabled { - chequebookFactory, ferr := InitChequebookFactory(logger, chainBackend, chainID, transactionService, o.SwapFactoryAddress) - if o.SwapEnable && ferr != nil { - return nil, fmt.Errorf("init chequebook factory: %w", ferr) + if bzzTokenAddress == (common.Address{}) { + return nil, errors.New("no known bzz token address for this network; provide --bzz-token-address") } + logger.Info("using bzz token address", "address", bzzTokenAddress) + erc20Service = erc20.New(transactionService, bzzTokenAddress) + } - if ferr == nil { - erc20Address, err := chequebookFactory.ERC20Address(ctx) - if err != nil { - if o.SwapEnable { - return nil, fmt.Errorf("factory fail: %w", err) - } - logger.Warning("unable to resolve ERC20 token address; BZZ balance will be unavailable via /wallet", "error", err) - } else { - erc20Service = erc20.New(transactionService, erc20Address) - } - } else { - logger.Warning("unable to init chequebook factory; BZZ balance will be unavailable via /wallet", "error", ferr) + if o.SwapEnable { + chequebookFactory, err := InitChequebookFactory(logger, chainBackend, chainID, transactionService, o.SwapFactoryAddress) + if err != nil { + return nil, fmt.Errorf("init chequebook factory: %w", err) } - if o.SwapEnable { - if o.ChequebookEnable { - var err error - chequebookService, err = InitChequebookService( - ctx, - logger, - stateStore, - signer, - chainID, - chainBackend, - overlayEthAddress, - transactionService, - chequebookFactory, - o.SwapInitialDeposit, - erc20Service, - ) - if err != nil { - return nil, fmt.Errorf("init chequebook service: %w", err) - } - } - - chequeStore, cashoutService = initChequeStoreCashout( + if o.ChequebookEnable { + chequebookService, err = InitChequebookService( + ctx, + logger, stateStore, - chainBackend, - chequebookFactory, + signer, chainID, + chainBackend, overlayEthAddress, transactionService, + chequebookFactory, + o.SwapInitialDeposit, + erc20Service, ) + if err != nil { + return nil, fmt.Errorf("init chequebook service: %w", err) + } } + + chequeStore, cashoutService = initChequeStoreCashout( + stateStore, + chainBackend, + chequebookFactory, + chainID, + overlayEthAddress, + transactionService, + ) } lightNodes := lightnode.NewContainer(swarmAddress) @@ -673,8 +672,6 @@ func NewBee( registry = apiService.MetricsRegistry() } - chainCfg, found := config.GetByChainID(chainID) - var ( cbVerifier chequebook.Verifier cbRegistry *chequebook.Registry @@ -754,17 +751,12 @@ func NewBee( return nil, errors.New("postage contract start block option not provided") } postageSyncStart = o.PostageContractStartBlock - } else if !found { + } else if !knownChain { return nil, errors.New("no known postage stamp addresses for this network") } postageStampContractABI := abiutil.MustParseABI(chainCfg.PostageStampABI) - bzzTokenAddress, err := postagecontract.LookupERC20Address(ctx, transactionService, postageStampContractAddress, postageStampContractABI, chainEnabled) - if err != nil { - return nil, fmt.Errorf("lookup erc20 postage address: %w", err) - } - // Compute gas limit for contract transactions: when TrxDebugMode is enabled, // gas estimation is skipped and DefaultGasLimit is used for all contract calls. var contractGasLimit uint64 diff --git a/pkg/postage/postagecontract/contract.go b/pkg/postage/postagecontract/contract.go index 42317014b38..cb6096032d5 100644 --- a/pkg/postage/postagecontract/contract.go +++ b/pkg/postage/postagecontract/contract.go @@ -552,29 +552,3 @@ func (m *noOpPostageContract) MinimumValidityBlocks(context.Context) (uint64, er func (m *noOpPostageContract) ExpireBatches(context.Context) error { return ErrChainDisabled } - -func LookupERC20Address(ctx context.Context, transactionService transaction.Service, postageStampContractAddress common.Address, postageStampContractABI abi.ABI, chainEnabled bool) (common.Address, error) { - if !chainEnabled { - return common.Address{}, nil - } - - callData, err := postageStampContractABI.Pack("bzzToken") - if err != nil { - return common.Address{}, err - } - - request := &transaction.TxRequest{ - To: &postageStampContractAddress, - Data: callData, - GasPrice: nil, - GasLimit: 0, - Value: big.NewInt(0), - } - - data, err := transactionService.Call(ctx, request) - if err != nil { - return common.Address{}, err - } - - return common.BytesToAddress(data), nil -} diff --git a/pkg/postage/postagecontract/contract_test.go b/pkg/postage/postagecontract/contract_test.go index 92020efbe9a..6b2607d590d 100644 --- a/pkg/postage/postagecontract/contract_test.go +++ b/pkg/postage/postagecontract/contract_test.go @@ -948,30 +948,3 @@ func TestBatchExpirer(t *testing.T) { } }) } - -func TestLookupERC20Address(t *testing.T) { - postageStampContractAddress := common.HexToAddress("ffff") - erc20Address := common.HexToAddress("ffff") - - addr, err := postagecontract.LookupERC20Address( - context.Background(), - transactionMock.New( - transactionMock.WithCallFunc(func(ctx context.Context, request *transaction.TxRequest) (result []byte, err error) { - if *request.To != postageStampContractAddress { - return nil, fmt.Errorf("called wrong contract. wanted %v, got %v", postageStampContractAddress, request.To) - } - return common.BytesToHash(erc20Address.Bytes()).Bytes(), nil - }), - ), - postageStampContractAddress, - postageStampContractABI, - true, - ) - if err != nil { - t.Fatal(err) - } - - if addr != postageStampContractAddress { - t.Fatalf("got wrong erc20 address. wanted %v, got %v", erc20Address, addr) - } -}