diff --git a/action/protocol/rewarding/admin.go b/action/protocol/rewarding/admin.go index 3e335d532c..a8ca7b99d6 100644 --- a/action/protocol/rewarding/admin.go +++ b/action/protocol/rewarding/admin.go @@ -220,7 +220,7 @@ func (p *Protocol) NumDelegatesForFoundationBonus(_ context.Context, sm protocol if err := p.state(sm, adminKey, &a); err != nil { return 0, err } - return a.numDelegatesForEpochReward, nil + return a.numDelegatesForFoundationBonus, nil } // ProductivityThreshold returns the productivity threshold diff --git a/action/protocol/rewarding/protocol_test.go b/action/protocol/rewarding/protocol_test.go index 24d9bcb63f..d4e2ce45bc 100644 --- a/action/protocol/rewarding/protocol_test.go +++ b/action/protocol/rewarding/protocol_test.go @@ -11,25 +11,21 @@ import ( "math/big" "testing" - "github.com/iotexproject/iotex-core/address" - - "github.com/iotexproject/iotex-core/action" - - "github.com/iotexproject/iotex-core/test/identityset" - - "github.com/iotexproject/iotex-core/blockchain/genesis" - "github.com/golang/mock/gomock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/iotexproject/iotex-core/action" "github.com/iotexproject/iotex-core/action/protocol" accountutil "github.com/iotexproject/iotex-core/action/protocol/account/util" "github.com/iotexproject/iotex-core/action/protocol/rolldpos" + "github.com/iotexproject/iotex-core/address" + "github.com/iotexproject/iotex-core/blockchain/genesis" "github.com/iotexproject/iotex-core/config" "github.com/iotexproject/iotex-core/pkg/unit" "github.com/iotexproject/iotex-core/state" "github.com/iotexproject/iotex-core/state/factory" + "github.com/iotexproject/iotex-core/test/identityset" "github.com/iotexproject/iotex-core/test/mock/mock_chainmanager" "github.com/iotexproject/iotex-core/test/testaddress" ) @@ -154,6 +150,18 @@ func testProtocol(t *testing.T, test func(*testing.T, context.Context, factory.F epochReward, err := p.EpochReward(ctx, ws) require.NoError(t, err) assert.Equal(t, big.NewInt(100), epochReward) + fb, err := p.FoundationBonus(ctx, ws) + require.NoError(t, err) + assert.Equal(t, big.NewInt(5), fb) + ndffb, err := p.NumDelegatesForFoundationBonus(ctx, ws) + require.NoError(t, err) + assert.Equal(t, uint64(5), ndffb) + fble, err := p.FoundationBonusLastEpoch(ctx, ws) + require.NoError(t, err) + assert.Equal(t, uint64(365), fble) + pt, err := p.ProductivityThreshold(ctx, ws) + require.NoError(t, err) + assert.Equal(t, uint64(50), pt) totalBalance, err := p.TotalBalance(ctx, ws) require.NoError(t, err) diff --git a/action/protocol/rewarding/reward.go b/action/protocol/rewarding/reward.go index bf5f7a2b64..aab9b12dd5 100644 --- a/action/protocol/rewarding/reward.go +++ b/action/protocol/rewarding/reward.go @@ -18,6 +18,7 @@ import ( "github.com/iotexproject/iotex-core/action/protocol/rewarding/rewardingpb" "github.com/iotexproject/iotex-core/address" "github.com/iotexproject/iotex-core/pkg/enc" + "github.com/iotexproject/iotex-core/pkg/log" "github.com/iotexproject/iotex-core/state" ) @@ -69,13 +70,7 @@ func (p *Protocol) GrantBlockReward( if err := p.assertNoRewardYet(sm, blockRewardHistoryKeyPrefix, raCtx.BlockHeight); err != nil { return err } - a := admin{} - if err := p.state(sm, adminKey, &a); err != nil { - return err - } - if err := p.updateAvailableBalance(sm, a.blockReward); err != nil { - return err - } + // Get the reward address for the block producer epochNum := p.rp.GetEpochNum(raCtx.BlockHeight) candidates, err := p.cm.CandidatesByHeight(p.rp.GetEpochHeight(epochNum)) @@ -90,10 +85,20 @@ func (p *Protocol) GrantBlockReward( break } } + // If reward address doesn't exist, do nothing if rewardAddrStr == "" { - return errors.Errorf("Producer %s doesn't have a reward address", producerAddrStr) + log.S().Warnf("Producer %s doesn't have a reward address", producerAddrStr) + return nil } rewardAddr, err := address.FromString(rewardAddrStr) + + a := admin{} + if err := p.state(sm, adminKey, &a); err != nil { + return err + } + if err := p.updateAvailableBalance(sm, a.blockReward); err != nil { + return err + } if err != nil { return err } @@ -120,9 +125,6 @@ func (p *Protocol) GrantEpochReward( if err := p.state(sm, adminKey, &a); err != nil { return err } - if err := p.updateAvailableBalance(sm, a.epochReward); err != nil { - return err - } // We need to consistently use the votes on of first block height in this epoch candidates, err := p.cm.CandidatesByHeight(p.rp.GetEpochHeight(epochNum)) if err != nil { @@ -139,10 +141,16 @@ func (p *Protocol) GrantEpochReward( if err != nil { return err } + actualTotalReward := big.NewInt(0) for i := range addrs { + // If reward address doesn't exist, do nothing + if addrs[i] == nil { + continue + } if err := p.grantToAccount(sm, addrs[i], amounts[i]); err != nil { return err } + actualTotalReward = big.NewInt(0).Add(actualTotalReward, amounts[i]) } // Reward additional bootstrap bonus @@ -152,6 +160,11 @@ func (p *Protocol) GrantEpochReward( l = a.numDelegatesForFoundationBonus } for i := uint64(0); i < l; i++ { + // If reward address doesn't exist, do nothing + if candidates[i].RewardAddress == "" { + log.S().Warnf("Candidate %s doesn't have a reward address", candidates[i].Address) + continue + } rewardAddr, err := address.FromString(candidates[i].RewardAddress) if err != nil { return err @@ -159,8 +172,14 @@ func (p *Protocol) GrantEpochReward( if err := p.grantToAccount(sm, rewardAddr, a.foundationBonus); err != nil { return err } + actualTotalReward = big.NewInt(0).Add(actualTotalReward, a.foundationBonus) } } + + // Update actual reward + if err := p.updateAvailableBalance(sm, actualTotalReward); err != nil { + return err + } return p.updateRewardHistory(sm, epochRewardHistoryKeyPrefix, epochNum) } @@ -310,9 +329,15 @@ func (p *Protocol) splitEpochReward( totalWeight := big.NewInt(0) rewardAddrs := make([]address.Address, 0) for _, candidate := range candidates { - rewardAddr, err := address.FromString(candidate.RewardAddress) - if err != nil { - return nil, nil, err + var rewardAddr address.Address + var err error + if candidate.RewardAddress != "" { + rewardAddr, err = address.FromString(candidate.RewardAddress) + if err != nil { + return nil, nil, err + } + } else { + log.S().Warnf("Candidate %s doesn't have a reward address", candidate.Address) } rewardAddrs = append(rewardAddrs, rewardAddr) totalWeight = big.NewInt(0).Add(totalWeight, candidate.Votes) diff --git a/action/protocol/rewarding/reward_test.go b/action/protocol/rewarding/reward_test.go index 419b1f3dd1..efcc3ea2e6 100644 --- a/action/protocol/rewarding/reward_test.go +++ b/action/protocol/rewarding/reward_test.go @@ -11,14 +11,21 @@ import ( "math/big" "testing" + "github.com/golang/mock/gomock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/iotexproject/iotex-core/action/protocol" accountutil "github.com/iotexproject/iotex-core/action/protocol/account/util" + "github.com/iotexproject/iotex-core/action/protocol/rolldpos" + "github.com/iotexproject/iotex-core/blockchain/genesis" + "github.com/iotexproject/iotex-core/config" "github.com/iotexproject/iotex-core/pkg/hash" + "github.com/iotexproject/iotex-core/pkg/unit" + "github.com/iotexproject/iotex-core/state" "github.com/iotexproject/iotex-core/state/factory" "github.com/iotexproject/iotex-core/test/identityset" + "github.com/iotexproject/iotex-core/test/mock/mock_chainmanager" "github.com/iotexproject/iotex-core/test/testaddress" ) @@ -84,7 +91,7 @@ func TestProtocol_GrantEpochReward(t *testing.T) { require.NoError(t, err) availableBalance, err := p.AvailableBalance(ctx, ws) require.NoError(t, err) - assert.Equal(t, big.NewInt(100), availableBalance) + assert.Equal(t, big.NewInt(95), availableBalance) // Operator shouldn't get reward unclaimedBalance, err := p.UnclaimedBalance(ctx, ws, testaddress.Addrinfo["producer"]) require.NoError(t, err) @@ -233,3 +240,123 @@ func TestProtocol_ClaimReward(t *testing.T) { require.Error(t, p.Claim(claimCtx, ws, big.NewInt(1))) }, false) } + +func TestProtocol_NoRewardAddr(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + cfg := config.Default + stateDB, err := factory.NewStateDB(cfg, factory.InMemStateDBOption()) + require.NoError(t, err) + require.NoError(t, stateDB.Start(context.Background())) + defer func() { + require.NoError(t, stateDB.Stop(context.Background())) + }() + + chain := mock_chainmanager.NewMockChainManager(ctrl) + chain.EXPECT().CandidatesByHeight(gomock.Any()).Return([]*state.Candidate{ + { + Address: identityset.Address(0).String(), + Votes: unit.ConvertIotxToRau(1000000), + RewardAddress: "", + }, + { + Address: identityset.Address(1).String(), + Votes: unit.ConvertIotxToRau(1000000), + RewardAddress: identityset.Address(1).String(), + }, + }, nil).AnyTimes() + chain.EXPECT().ProductivityByEpoch(gomock.Any()).Return( + uint64(19), + map[string]uint64{ + identityset.Address(0).String(): 9, + identityset.Address(1).String(): 10, + }, + nil, + ).AnyTimes() + p := NewProtocol(chain, rolldpos.NewProtocol( + genesis.Default.NumCandidateDelegates, + genesis.Default.NumDelegates, + genesis.Default.NumSubEpochs, + )) + + // Initialize the protocol + ctx := protocol.WithRunActionsCtx( + context.Background(), + protocol.RunActionsCtx{ + BlockHeight: 0, + }, + ) + ws, err := stateDB.NewWorkingSet() + require.NoError(t, err) + require.NoError( + t, + p.Initialize( + ctx, + ws, + big.NewInt(0), + big.NewInt(10), + big.NewInt(100), + 10, + nil, + big.NewInt(5), + 5, + 365, + 50, + )) + require.NoError(t, stateDB.Commit(ws)) + + // Create a test account with 1000 token + ws, err = stateDB.NewWorkingSet() + require.NoError(t, err) + _, err = accountutil.LoadOrCreateAccount(ws, identityset.Address(0).String(), big.NewInt(1000)) + require.NoError(t, err) + require.NoError(t, stateDB.Commit(ws)) + + ctx = protocol.WithRunActionsCtx( + context.Background(), + protocol.RunActionsCtx{ + Producer: identityset.Address(0), + Caller: identityset.Address(0), + BlockHeight: genesis.Default.NumDelegates * genesis.Default.NumSubEpochs, + }, + ) + ws, err = stateDB.NewWorkingSet() + require.NoError(t, err) + require.NoError(t, p.Deposit(ctx, ws, big.NewInt(200))) + require.NoError(t, stateDB.Commit(ws)) + + // Grant block reward + ws, err = stateDB.NewWorkingSet() + require.NoError(t, err) + require.NoError(t, p.GrantBlockReward(ctx, ws)) + require.NoError(t, stateDB.Commit(ws)) + + ws, err = stateDB.NewWorkingSet() + require.NoError(t, err) + availableBalance, err := p.AvailableBalance(ctx, ws) + require.NoError(t, err) + assert.Equal(t, big.NewInt(200), availableBalance) + unclaimedBalance, err := p.UnclaimedBalance(ctx, ws, identityset.Address(0)) + require.NoError(t, err) + assert.Equal(t, big.NewInt(0), unclaimedBalance) + + // Grant epoch reward + ws, err = stateDB.NewWorkingSet() + require.NoError(t, err) + require.NoError(t, p.GrantEpochReward(ctx, ws)) + require.NoError(t, stateDB.Commit(ws)) + + ws, err = stateDB.NewWorkingSet() + require.NoError(t, err) + availableBalance, err = p.AvailableBalance(ctx, ws) + require.NoError(t, err) + assert.Equal(t, big.NewInt(145), availableBalance) + unclaimedBalance, err = p.UnclaimedBalance(ctx, ws, identityset.Address(0)) + require.NoError(t, err) + assert.Equal(t, big.NewInt(0), unclaimedBalance) + // It doesn't affect others to get reward + unclaimedBalance, err = p.UnclaimedBalance(ctx, ws, identityset.Address(1)) + require.NoError(t, err) + assert.Equal(t, big.NewInt(55), unclaimedBalance) +}