diff --git a/pkg/node/node.go b/pkg/node/node.go index b0c64e21592..066c893fef5 100644 --- a/pkg/node/node.go +++ b/pkg/node/node.go @@ -273,6 +273,7 @@ func NewBee( } } + var changedOverlay bool if targetNeighborhood != "" { neighborhood, err := swarm.ParseBitStrAddress(targetNeighborhood) if err != nil { @@ -311,6 +312,7 @@ func NewBee( if err != nil { return nil, fmt.Errorf("statestore: save new overlay: %w", err) } + changedOverlay = true } } @@ -966,6 +968,21 @@ func NewBee( stakingContract := staking.New(overlayEthAddress, stakingContractAddress, abiutil.MustParseABI(chainCfg.StakingABI), bzzTokenAddress, transactionService, common.BytesToHash(nonce)) + if chainEnabled && changedOverlay { + stake, err := stakingContract.GetStake(ctx) + if err != nil { + return nil, errors.New("getting stake balance") + } + if stake.Cmp(big.NewInt(0)) > 0 { + logger.Debug("changing overlay address in staking contract") + tx, err := stakingContract.ChangeStakeOverlay(ctx, common.BytesToHash(nonce)) + if err != nil { + return nil, fmt.Errorf("cannot change staking overlay address: %v", err.Error()) + } + logger.Info("overlay address changed in staking contract", "transaction", tx) + } + } + var ( pullerService *puller.Puller agent *storageincentives.Agent diff --git a/pkg/storageincentives/staking/contract.go b/pkg/storageincentives/staking/contract.go index 823f8a0c050..ca533ade35d 100644 --- a/pkg/storageincentives/staking/contract.go +++ b/pkg/storageincentives/staking/contract.go @@ -37,6 +37,7 @@ var ( type Contract interface { DepositStake(ctx context.Context, stakedAmount *big.Int) (common.Hash, error) + ChangeStakeOverlay(ctx context.Context, nonce common.Hash) (common.Hash, error) GetStake(ctx context.Context) (*big.Int, error) WithdrawAllStake(ctx context.Context) (common.Hash, error) RedistributionStatuser @@ -223,6 +224,17 @@ func (c *contract) DepositStake(ctx context.Context, stakedAmount *big.Int) (com return receipt.TxHash, nil } +// ChangeStakeOverlay only changes the overlay address used in the redistribution game. +func (c *contract) ChangeStakeOverlay(ctx context.Context, nonce common.Hash) (common.Hash, error) { + c.overlayNonce = nonce + receipt, err := c.sendDepositStakeTransaction(ctx, new(big.Int), c.overlayNonce) + if err != nil { + return common.Hash{}, err + } + + return receipt.TxHash, nil +} + func (c *contract) GetStake(ctx context.Context) (*big.Int, error) { stakedAmount, err := c.getStake(ctx) if err != nil { diff --git a/pkg/storageincentives/staking/contract_test.go b/pkg/storageincentives/staking/contract_test.go index 6315e137060..b4a5ad841a6 100644 --- a/pkg/storageincentives/staking/contract_test.go +++ b/pkg/storageincentives/staking/contract_test.go @@ -10,6 +10,7 @@ import ( "errors" "fmt" "math/big" + "strings" "testing" "github.com/ethereum/go-ethereum/common" @@ -485,6 +486,201 @@ func TestDepositStake(t *testing.T) { }) } +func TestChangeStakeOverlay(t *testing.T) { + t.Parallel() + + ctx := context.Background() + owner := common.HexToAddress("abcd") + stakingContractAddress := common.HexToAddress("ffff") + bzzTokenAddress := common.HexToAddress("eeee") + nonce := common.BytesToHash(make([]byte, 32)) + txHashOverlayChanged := common.HexToHash("c3a7") + stakedAmount := big.NewInt(0) + txHashApprove := common.HexToHash("abb0") + + t.Run("ok", func(t *testing.T) { + t.Parallel() + + expectedCallData, err := stakingContractABI.Pack("manageStake", nonce, stakedAmount) + if err != nil { + t.Fatal(err) + } + + contract := staking.New( + owner, + stakingContractAddress, + stakingContractABI, + bzzTokenAddress, + transactionMock.New( + transactionMock.WithSendFunc(func(ctx context.Context, request *transaction.TxRequest, boost int) (txHash common.Hash, err error) { + if *request.To == stakingContractAddress { + if !bytes.Equal(expectedCallData[:80], request.Data[:80]) { + return common.Hash{}, fmt.Errorf("got wrong call data. wanted %x, got %x", expectedCallData, request.Data) + } + return txHashOverlayChanged, nil + } + return common.Hash{}, errors.New("sent to wrong contract") + }), + transactionMock.WithWaitForReceiptFunc(func(ctx context.Context, txHash common.Hash) (receipt *types.Receipt, err error) { + if txHash == txHashOverlayChanged { + return &types.Receipt{ + Status: 1, + }, nil + } + return nil, errors.New("unknown tx hash") + }), + ), + nonce, + ) + + _, err = contract.ChangeStakeOverlay(ctx, nonce) + if err != nil { + t.Fatal(err) + } + }) + + t.Run("send tx failed", func(t *testing.T) { + t.Parallel() + + contract := staking.New( + owner, + stakingContractAddress, + stakingContractABI, + bzzTokenAddress, + transactionMock.New( + transactionMock.WithSendFunc(func(ctx context.Context, request *transaction.TxRequest, boost int) (txHash common.Hash, err error) { + if *request.To == stakingContractAddress { + return common.Hash{}, errors.New("send transaction failed") + } + return common.Hash{}, errors.New("sent to wrong contract") + }), + ), + nonce, + ) + + _, err := contract.ChangeStakeOverlay(ctx, nonce) + if err == nil || !strings.Contains(err.Error(), "send transaction failed") { + t.Fatal("expected different error") + } + }) + + t.Run("invalid call data", func(t *testing.T) { + t.Parallel() + + expectedCallData, err := stakingContractABI.Pack("manageStake", nonce, stakedAmount) + if err != nil { + t.Fatal(err) + } + + contract := staking.New( + owner, + stakingContractAddress, + stakingContractABI, + bzzTokenAddress, + transactionMock.New( + transactionMock.WithSendFunc(func(ctx context.Context, request *transaction.TxRequest, boost int) (txHash common.Hash, err error) { + if *request.To == stakingContractAddress { + if !bytes.Equal(expectedCallData[:80], request.Data[:80]) { + return common.Hash{}, fmt.Errorf("got wrong call data. wanted %x, got %x", expectedCallData, request.Data) + } + return txHashApprove, nil + } + return common.Hash{}, errors.New("sent to wrong contract") + }), + ), + nonce, + ) + + newNonce := make([]byte, 32) + copy(newNonce, nonce[:]) + newNonce[0]++ + _, err = contract.ChangeStakeOverlay(ctx, common.BytesToHash(newNonce)) + if err == nil || !strings.Contains(err.Error(), "got wrong call data. wanted") { + t.Fatal("expected different error") + } + }) + + t.Run("transaction reverted", func(t *testing.T) { + t.Parallel() + + expectedCallData, err := stakingContractABI.Pack("manageStake", nonce, stakedAmount) + if err != nil { + t.Fatal(err) + } + + contract := staking.New( + owner, + stakingContractAddress, + stakingContractABI, + bzzTokenAddress, + transactionMock.New( + transactionMock.WithSendFunc(func(ctx context.Context, request *transaction.TxRequest, boost int) (txHash common.Hash, err error) { + if *request.To == stakingContractAddress { + if !bytes.Equal(expectedCallData[:80], request.Data[:80]) { + return common.Hash{}, fmt.Errorf("got wrong call data. wanted %x, got %x", expectedCallData, request.Data) + } + return txHashOverlayChanged, nil + } + return txHashOverlayChanged, errors.New("sent to wrong contract") + }), + transactionMock.WithWaitForReceiptFunc(func(ctx context.Context, txHash common.Hash) (receipt *types.Receipt, err error) { + if txHash == txHashOverlayChanged { + return &types.Receipt{ + Status: 0, + }, nil + } + return nil, errors.New("unknown tx hash") + }), + ), + nonce, + ) + + _, err = contract.ChangeStakeOverlay(ctx, nonce) + if !errors.Is(err, transaction.ErrTransactionReverted) { + t.Fatalf("expeted %v, got %v", transaction.ErrTransactionReverted, err) + } + }) + + t.Run("transaction error", func(t *testing.T) { + t.Parallel() + + expectedCallData, err := stakingContractABI.Pack("manageStake", nonce, stakedAmount) + if err != nil { + t.Fatal(err) + } + + contract := staking.New( + owner, + stakingContractAddress, + stakingContractABI, + bzzTokenAddress, + transactionMock.New( + transactionMock.WithSendFunc(func(ctx context.Context, request *transaction.TxRequest, boost int) (txHash common.Hash, err error) { + if *request.To == stakingContractAddress { + if !bytes.Equal(expectedCallData[:80], request.Data[:80]) { + return common.Hash{}, fmt.Errorf("got wrong call data. wanted %x, got %x", expectedCallData, request.Data) + } + return txHashOverlayChanged, nil + } + return common.Hash{}, errors.New("sent to wrong contract") + }), + transactionMock.WithWaitForReceiptFunc(func(ctx context.Context, txHash common.Hash) (receipt *types.Receipt, err error) { + if txHash == txHashOverlayChanged { + return nil, fmt.Errorf("unknown error") + } + return nil, errors.New("unknown tx hash") + }), + ), + nonce, + ) + + _, err = contract.ChangeStakeOverlay(ctx, nonce) + if err == nil || !strings.Contains(err.Error(), "unknown error") { + t.Fatal("expected different error") + } + }) +} + func TestGetStake(t *testing.T) { t.Parallel() diff --git a/pkg/storageincentives/staking/mock/contract.go b/pkg/storageincentives/staking/mock/contract.go index e21e51964f9..18f06e02eef 100644 --- a/pkg/storageincentives/staking/mock/contract.go +++ b/pkg/storageincentives/staking/mock/contract.go @@ -23,6 +23,10 @@ func (s *stakingContractMock) DepositStake(ctx context.Context, stakedAmount *bi return s.depositStake(ctx, stakedAmount) } +func (s *stakingContractMock) ChangeStakeOverlay(_ context.Context, h common.Hash) (common.Hash, error) { + return h, nil +} + func (s *stakingContractMock) GetStake(ctx context.Context) (*big.Int, error) { return s.getStake(ctx) }