Skip to content
Open
6 changes: 3 additions & 3 deletions .github/workflows/beekeeper.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ on:
paths-ignore:
- packaging/**
- openapi/**
- '**/*.md'
- '.github/ISSUE_TEMPLATE/**'
- "**/*.md"
- ".github/ISSUE_TEMPLATE/**"
branches:
- "**"

Expand All @@ -19,7 +19,7 @@ env:
SETUP_CONTRACT_IMAGE: "ethersphere/bee-localchain"
SETUP_CONTRACT_IMAGE_TAG: "0.9.4"
BEELOCAL_BRANCH: "main"
BEEKEEPER_BRANCH: "master"
BEEKEEPER_BRANCH: "feat/bzz-token-address"
BEEKEEPER_METRICS_ENABLED: false
REACHABILITY_OVERRIDE_PUBLIC: true
BATCHFACTOR_OVERRIDE_PUBLIC: 2
Expand Down
2 changes: 2 additions & 0 deletions cmd/bee/cmd/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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")
Expand Down
10 changes: 10 additions & 0 deletions cmd/bee/cmd/start.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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),
Expand All @@ -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),
Expand Down
2 changes: 2 additions & 0 deletions packaging/bee.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions packaging/docker/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions packaging/docker/env
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 2 additions & 0 deletions packaging/homebrew-amd64/bee.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions packaging/homebrew-arm64/bee.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions packaging/scoop/bee.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 3 additions & 2 deletions pkg/config/chain.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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,
Expand All @@ -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,
Expand Down
65 changes: 65 additions & 0 deletions pkg/config/chain_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
85 changes: 38 additions & 47 deletions pkg/node/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -545,56 +546,53 @@ 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")
}
erc20Service = erc20.New(transactionService, bzzTokenAddress)
}
Comment on lines 556 to +561

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Full bzz token address verification can be moved here, not to have it partially before calling constructor and inside of it. But o.BzzTokenAddress need to be changed to string type. And it can be done only if chainEnabled. No need to error out if disabled.

	if chainEnabled {
		if o.BzzTokenAddress != "" {
			if !common.IsHexAddress(o.BzzTokenAddress) {
				return nil, errors.New("malformed bzz token address")
			}
			bzzTokenAddress = common.HexToAddress(o.BzzTokenAddress)
		}
		if bzzTokenAddress == (common.Address{}) {
			return nil, errors.New("no known bzz token address for this network; provide --bzz-token-address")
		}
		erc20Service = erc20.New(transactionService, bzzTokenAddress)
	}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the previous review round Elad asked specifically for the opposite — sanitize the address in cmd and pass a typed common.Address to NewBee rather than a raw string (done in 599f33b). That aligns it with how the other contract-address flags are validated and keeps the constructor free of string parsing. I'd prefer no to flip it back unless we settle on one convention — @acud, any objection to consolidating validation in node.go as @gacevicljubisa suggests, or keep it cmd-side?

@gacevicljubisa gacevicljubisa Jun 8, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have missed that comment. Most of verification is done inside the constructor, and my idea was to have it in one place and all can be inside this one if statement when chain is enabled. In any case no need to return error if chain is disabled.


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)
Expand Down Expand Up @@ -673,8 +671,6 @@ func NewBee(
registry = apiService.MetricsRegistry()
}

chainCfg, found := config.GetByChainID(chainID)

var (
cbVerifier chequebook.Verifier
cbRegistry *chequebook.Registry
Expand Down Expand Up @@ -754,17 +750,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
Expand Down
26 changes: 0 additions & 26 deletions pkg/postage/postagecontract/contract.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Loading
Loading