Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: tally bundle proposal on timeout #178

Merged
merged 9 commits into from
May 3, 2024
2 changes: 1 addition & 1 deletion x/bundles/keeper/logic_bundles.go
Original file line number Diff line number Diff line change
Expand Up @@ -513,7 +513,7 @@ func (k Keeper) GetVoteDistribution(ctx sdk.Context, poolId uint64) (voteDistrib
}

// tallyBundleProposal evaluates the votes of a bundle proposal and determines the outcome
func (k msgServer) tallyBundleProposal(ctx sdk.Context, bundleProposal types.BundleProposal, poolId uint64) (types.TallyResult, error) {
func (k Keeper) tallyBundleProposal(ctx sdk.Context, bundleProposal types.BundleProposal, poolId uint64) (types.TallyResult, error) {
// Increase points of stakers who did not vote at all + slash + remove if necessary.
// The protocol requires everybody to stay always active.
k.handleNonVoters(ctx, poolId)
Expand Down
55 changes: 46 additions & 9 deletions x/bundles/keeper/logic_end_block_handle_upload_timeout.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,16 +69,53 @@ func (k Keeper) HandleUploadTimeout(goCtx context.Context) {

// We now know that the pool is active and the upload timeout has been reached.

// Now we increase the points of the valaccount
// (if he is still participating in the pool) and select a new one.
if k.stakerKeeper.DoesValaccountExist(ctx, pool.Id, bundleProposal.NextUploader) {
k.addPoint(ctx, pool.Id, bundleProposal.NextUploader)
}
timedoutUploader := bundleProposal.NextUploader

// Update bundle proposal and choose next uploader
bundleProposal.NextUploader = k.chooseNextUploader(ctx, pool.Id)
bundleProposal.UpdatedAt = uint64(ctx.BlockTime().Unix())
// Check if we have a bundle proposal to validate.
if bundleProposal.StorageId != "" {
// Previous round contains a bundle which needs to be validated now.
result, err := k.tallyBundleProposal(ctx, bundleProposal, pool.Id)
if err != nil {
// If we have an error here we have some kind of inconsistency in the state.
// TODO: panic here?
shifty11 marked this conversation as resolved.
Show resolved Hide resolved
continue
}

switch result.Status {
case types.TallyResultValid:
// Get next uploader from stakers who voted `valid`
nextUploader := k.chooseNextUploaderFromList(ctx, pool.Id, bundleProposal.VotersValid)

// Finalize bundle by adding it to the store
k.finalizeCurrentBundleProposal(ctx, pool.Id, result.VoteDistribution, result.FundersPayout, result.InflationPayout, result.BundleReward, nextUploader)

// Register empty bundle with next uploader
bundleProposal = types.BundleProposal{
PoolId: pool.Id,
NextUploader: nextUploader,
UpdatedAt: uint64(ctx.BlockTime().Unix()),
}
k.SetBundleProposal(ctx, bundleProposal)
default:
// In every other case the bundle is dropped.

k.SetBundleProposal(ctx, bundleProposal)
// Get next uploader from all pool stakers
nextUploader := k.chooseNextUploader(ctx, pool.Id)

// Drop current bundle and set next uploader
k.dropCurrentBundleProposal(ctx, pool.Id, result.VoteDistribution, nextUploader)
}
} else {
// Update bundle proposal and choose next uploader
bundleProposal.NextUploader = k.chooseNextUploader(ctx, pool.Id)
bundleProposal.UpdatedAt = uint64(ctx.BlockTime().Unix())
k.SetBundleProposal(ctx, bundleProposal)
}

// Now we increase the points of the valaccount
// (if he is still participating in the pool)
if k.stakerKeeper.DoesValaccountExist(ctx, pool.Id, timedoutUploader) {
k.addPoint(ctx, pool.Id, timedoutUploader)
}
}
}
211 changes: 196 additions & 15 deletions x/bundles/keeper/logic_end_block_handle_upload_timeout_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,9 @@ TEST CASES - logic_end_block_handle_upload_timeout.go
* Staker is next uploader of genesis bundle and upload timeout does pass together with upload interval
* Staker is next uploader of bundle proposal and upload interval does not pass
* Staker is next uploader of bundle proposal and upload timeout does not pass
* Staker is next uploader of bundle proposal and upload timeout passes
* Staker is next uploader of bundle proposal and upload timeout passes with the previous bundle being valid
* Staker is next uploader of bundle proposal and upload timeout passes with the previous bundle not reaching quorum
* Staker is next uploader of bundle proposal and upload timeout passes with the previous bundle being invalid
* Staker with already max points is next uploader of bundle proposal and upload timeout passes
* A bundle proposal with no quorum does not reach the upload interval
* A bundle proposal with no quorum does reach the upload interval
Expand Down Expand Up @@ -508,7 +510,7 @@ var _ = Describe("logic_end_block_handle_upload_timeout.go", Ordered, func() {
Expect(s.App().DelegationKeeper.GetDelegationAmount(s.Ctx(), i.STAKER_1)).To(Equal(100 * i.KYVE))
})

It("Staker is next uploader of bundle proposal and upload timeout passes", func() {
It("Staker is next uploader of bundle proposal and upload timeout passes with the previous bundle being valid", func() {
// ARRANGE
s.RunTxBundlesSuccess(&bundletypes.MsgClaimUploaderRole{
Creator: i.VALADDRESS_0_A,
Expand Down Expand Up @@ -548,22 +550,183 @@ var _ = Describe("logic_end_block_handle_upload_timeout.go", Ordered, func() {
// ASSERT
bundleProposal, _ := s.App().BundlesKeeper.GetBundleProposal(s.Ctx(), 0)
Expect(bundleProposal.NextUploader).To(Equal(i.STAKER_0))
Expect(bundleProposal.StorageId).To(Equal("y62A3tfbSNcNYDGoL-eXwzyV-Zc9Q0OVtDvR1biJmNI"))
Expect(bundleProposal.StorageId).To(Equal(""))

// check if next uploader got not removed from pool
// check that previous bundle got finalized
finalizedBundle, _ := s.App().BundlesKeeper.GetFinalizedBundle(s.Ctx(), 0, 0)
Expect(finalizedBundle.Uploader).To(Equal(i.STAKER_0))
Expect(finalizedBundle.StorageId).To(Equal("y62A3tfbSNcNYDGoL-eXwzyV-Zc9Q0OVtDvR1biJmNI"))

// check that next uploader didn't get removed from pool
poolStakers := s.App().StakersKeeper.GetAllStakerAddressesOfPool(s.Ctx(), 0)
Expect(poolStakers).To(HaveLen(2))

// check if next uploader received a point
// check that next uploader received a point for not uploading
valaccount, _ := s.App().StakersKeeper.GetValaccount(s.Ctx(), 0, i.STAKER_1)
Expect(valaccount.Points).To(Equal(uint64(1)))

// check that staker 0 has no points
valaccount, _ = s.App().StakersKeeper.GetValaccount(s.Ctx(), 0, i.STAKER_0)
Expect(valaccount.Points).To(Equal(uint64(0)))

// check that uploader didn't get slashed
_, found := s.App().StakersKeeper.GetStaker(s.Ctx(), i.STAKER_1)
Expect(found).To(BeTrue())

Expect(s.App().DelegationKeeper.GetDelegationOfPool(s.Ctx(), 0)).To(Equal(200 * i.KYVE))

// check if next uploader not got slashed
// check that next uploader didn't get slashed
expectedBalance := 100 * i.KYVE
Expect(expectedBalance).To(Equal(s.App().DelegationKeeper.GetDelegationAmountOfDelegator(s.Ctx(), i.STAKER_1, i.STAKER_1)))
})

It("Staker is next uploader of bundle proposal and upload timeout passes with the previous bundle not reaching quorum", func() {
// ARRANGE
s.RunTxBundlesSuccess(&bundletypes.MsgClaimUploaderRole{
Creator: i.VALADDRESS_0_A,
Staker: i.STAKER_0,
PoolId: 0,
})

s.CommitAfterSeconds(60)

s.RunTxBundlesSuccess(&bundletypes.MsgSubmitBundleProposal{
Creator: i.VALADDRESS_0_A,
Staker: i.STAKER_0,
PoolId: 0,
StorageId: "y62A3tfbSNcNYDGoL-eXwzyV-Zc9Q0OVtDvR1biJmNI",
DataSize: 100,
DataHash: "test_hash",
FromIndex: 0,
BundleSize: 100,
FromKey: "0",
ToKey: "99",
BundleSummary: "test_value",
})

// ACT
s.CommitAfterSeconds(s.App().BundlesKeeper.GetUploadTimeout(s.Ctx()))
s.CommitAfterSeconds(60)
s.CommitAfterSeconds(1)

// ASSERT
bundleProposal, _ := s.App().BundlesKeeper.GetBundleProposal(s.Ctx(), 0)
Expect(bundleProposal.NextUploader).To(Equal(i.STAKER_0))
Expect(bundleProposal.StorageId).To(Equal(""))

// check that bundle didn't get finalized
_, found := s.App().BundlesKeeper.GetFinalizedBundle(s.Ctx(), 0, 0)
Expect(found).To(BeFalse())

// check that next uploader got not removed from pool
poolStakers := s.App().StakersKeeper.GetAllStakerAddressesOfPool(s.Ctx(), 0)
Expect(poolStakers).To(HaveLen(2))

// check that staker 1 received a point for not voting
valaccount, _ := s.App().StakersKeeper.GetValaccount(s.Ctx(), 0, i.STAKER_1)
Expect(valaccount.Points).To(Equal(uint64(1)))

// check that next uploader didn't receive a point
valaccount, _ = s.App().StakersKeeper.GetValaccount(s.Ctx(), 0, i.STAKER_0)
Expect(valaccount.Points).To(Equal(uint64(0)))

_, found = s.App().StakersKeeper.GetStaker(s.Ctx(), i.STAKER_1)
Expect(found).To(BeTrue())

Expect(s.App().DelegationKeeper.GetDelegationOfPool(s.Ctx(), 0)).To(Equal(200 * i.KYVE))

// check that next uploader didn't get slashed
expectedBalance := 100 * i.KYVE
Expect(expectedBalance).To(Equal(s.App().DelegationKeeper.GetDelegationAmountOfDelegator(s.Ctx(), i.STAKER_1, i.STAKER_1)))
})

It("Staker is next uploader of bundle proposal and upload timeout passes with the previous bundle being invalid", func() {
shifty11 marked this conversation as resolved.
Show resolved Hide resolved
// ARRANGE
s.RunTxStakersSuccess(&stakertypes.MsgCreateStaker{
Creator: i.STAKER_2,
Amount: 100 * i.KYVE,
})

s.RunTxStakersSuccess(&stakertypes.MsgJoinPool{
Creator: i.STAKER_2,
PoolId: 0,
Valaddress: i.VALADDRESS_2_A,
})

s.RunTxBundlesSuccess(&bundletypes.MsgClaimUploaderRole{
Creator: i.VALADDRESS_0_A,
Staker: i.STAKER_0,
PoolId: 0,
})

s.CommitAfterSeconds(60)

s.RunTxBundlesSuccess(&bundletypes.MsgSubmitBundleProposal{
Creator: i.VALADDRESS_0_A,
Staker: i.STAKER_0,
PoolId: 0,
StorageId: "y62A3tfbSNcNYDGoL-eXwzyV-Zc9Q0OVtDvR1biJmNI",
DataSize: 100,
DataHash: "test_hash",
FromIndex: 0,
BundleSize: 100,
FromKey: "0",
ToKey: "99",
BundleSummary: "test_value",
})

s.RunTxBundlesSuccess(&bundletypes.MsgVoteBundleProposal{
Creator: i.VALADDRESS_1_A,
Staker: i.STAKER_1,
PoolId: 0,
StorageId: "y62A3tfbSNcNYDGoL-eXwzyV-Zc9Q0OVtDvR1biJmNI",
Vote: bundletypes.VOTE_TYPE_INVALID,
})

s.RunTxBundlesSuccess(&bundletypes.MsgVoteBundleProposal{
Creator: i.VALADDRESS_2_A,
Staker: i.STAKER_2,
PoolId: 0,
StorageId: "y62A3tfbSNcNYDGoL-eXwzyV-Zc9Q0OVtDvR1biJmNI",
Vote: bundletypes.VOTE_TYPE_INVALID,
})

// ACT
s.CommitAfterSeconds(s.App().BundlesKeeper.GetUploadTimeout(s.Ctx()))
s.CommitAfterSeconds(60)
s.CommitAfterSeconds(1)

// ASSERT
bundleProposal, _ := s.App().BundlesKeeper.GetBundleProposal(s.Ctx(), 0)
Expect(bundleProposal.NextUploader).To(Equal(i.STAKER_2))
Expect(bundleProposal.StorageId).To(Equal(""))

// check that bundle didn't get finalized
_, found := s.App().BundlesKeeper.GetFinalizedBundle(s.Ctx(), 0, 0)
Expect(found).To(BeFalse())

// check that next uploader didn't get removed from pool
poolStakers := s.App().StakersKeeper.GetAllStakerAddressesOfPool(s.Ctx(), 0)
Expect(poolStakers).To(HaveLen(2))

// check that next uploader received a point
valaccount, _ := s.App().StakersKeeper.GetValaccount(s.Ctx(), 0, i.STAKER_1)
Expect(valaccount.Points).To(Equal(uint64(1)))

// check that staker 0 has no points
valaccount, _ = s.App().StakersKeeper.GetValaccount(s.Ctx(), 0, i.STAKER_0)
shifty11 marked this conversation as resolved.
Show resolved Hide resolved
Expect(valaccount.Points).To(Equal(uint64(0)))

// check that staker 2 has a no points
valaccount, _ = s.App().StakersKeeper.GetValaccount(s.Ctx(), 0, i.STAKER_2)
Expect(valaccount.Points).To(Equal(uint64(0)))

_, found = s.App().StakersKeeper.GetStaker(s.Ctx(), i.STAKER_1)
Expect(found).To(BeTrue())

Expect(s.App().DelegationKeeper.GetDelegationOfPool(s.Ctx(), 0)).To(Equal(200 * i.KYVE))

// check that next uploader didn't get slashed
expectedBalance := 100 * i.KYVE
Expect(expectedBalance).To(Equal(s.App().DelegationKeeper.GetDelegationAmountOfDelegator(s.Ctx(), i.STAKER_1, i.STAKER_1)))
})
Expand Down Expand Up @@ -661,7 +824,12 @@ var _ = Describe("logic_end_block_handle_upload_timeout.go", Ordered, func() {
// ASSERT
bundleProposal, _ = s.App().BundlesKeeper.GetBundleProposal(s.Ctx(), 0)
Expect(bundleProposal.NextUploader).To(Equal(i.STAKER_0))
Expect(bundleProposal.StorageId).To(Equal("P9edn0bjEfMU_lecFDIPLvGO2v2ltpFNUMWp5kgPddg"))
Expect(bundleProposal.StorageId).To(Equal(""))

// check if previous bundle got finalized
finalizedBundle, _ := s.App().BundlesKeeper.GetFinalizedBundle(s.Ctx(), 0, 0)
Expect(finalizedBundle.Uploader).To(Equal(i.STAKER_0))
Expect(finalizedBundle.StorageId).To(Equal("y62A3tfbSNcNYDGoL-eXwzyV-Zc9Q0OVtDvR1biJmNI"))

// check if next uploader got not removed from pool
shifty11 marked this conversation as resolved.
Show resolved Hide resolved
poolStakers := s.App().StakersKeeper.GetAllStakerAddressesOfPool(s.Ctx(), 0)
shifty11 marked this conversation as resolved.
Show resolved Hide resolved
Expand Down Expand Up @@ -910,7 +1078,12 @@ var _ = Describe("logic_end_block_handle_upload_timeout.go", Ordered, func() {
// ASSERT
bundleProposal, _ := s.App().BundlesKeeper.GetBundleProposal(s.Ctx(), 0)
Expect(bundleProposal.NextUploader).To(Equal(i.STAKER_0))
Expect(bundleProposal.StorageId).To(Equal("y62A3tfbSNcNYDGoL-eXwzyV-Zc9Q0OVtDvR1biJmNI"))
Expect(bundleProposal.StorageId).To(Equal(""))

// check if previous bundle got finalized
finalizedBundle, _ := s.App().BundlesKeeper.GetFinalizedBundle(s.Ctx(), 0, 0)
Expect(finalizedBundle.Uploader).To(Equal(i.STAKER_0))
Expect(finalizedBundle.StorageId).To(Equal("y62A3tfbSNcNYDGoL-eXwzyV-Zc9Q0OVtDvR1biJmNI"))

// check if next uploader got removed from pool
poolStakers := s.App().StakersKeeper.GetAllStakerAddressesOfPool(s.Ctx(), 0)
Expand Down Expand Up @@ -992,19 +1165,23 @@ var _ = Describe("logic_end_block_handle_upload_timeout.go", Ordered, func() {
// ASSERT
bundleProposal, _ := s.App().BundlesKeeper.GetBundleProposal(s.Ctx(), 0)
Expect(bundleProposal.NextUploader).To(Equal(i.STAKER_2))
Expect(bundleProposal.StorageId).To(Equal("y62A3tfbSNcNYDGoL-eXwzyV-Zc9Q0OVtDvR1biJmNI"))
Expect(bundleProposal.StorageId).To(Equal(""))

// check if next uploader got removed from pool
// check that bundle didn't get finalized
_, found := s.App().BundlesKeeper.GetFinalizedBundle(s.Ctx(), 0, 0)
Expect(found).To(BeFalse())

// check that next uploader got removed from pool
poolStakers := s.App().StakersKeeper.GetAllStakerAddressesOfPool(s.Ctx(), 0)
Expect(poolStakers).To(HaveLen(2))

_, found := s.App().StakersKeeper.GetStaker(s.Ctx(), i.STAKER_0)
_, found = s.App().StakersKeeper.GetStaker(s.Ctx(), i.STAKER_0)
Expect(found).To(BeTrue())

Expect(s.App().DelegationKeeper.GetDelegationOfPool(s.Ctx(), 0)).To(Equal(200 * i.KYVE))

// check if next uploader not got slashed
expectedBalance := 100 * i.KYVE
// check that next uploader got slashed for voting invalid
expectedBalance := 80 * i.KYVE

Expect(expectedBalance).To(Equal(s.App().DelegationKeeper.GetDelegationAmountOfDelegator(s.Ctx(), i.STAKER_0, i.STAKER_0)))
})
Expand Down Expand Up @@ -1140,7 +1317,11 @@ var _ = Describe("logic_end_block_handle_upload_timeout.go", Ordered, func() {
// ASSERT
bundleProposal, _ = s.App().BundlesKeeper.GetBundleProposal(s.Ctx(), 1)
Expect(bundleProposal.NextUploader).To(Equal(i.STAKER_0))
Expect(bundleProposal.StorageId).To(Equal("P9edn0bjEfMU_lecFDIPLvGO2v2ltpFNUMWp5kgPddg"))
Expect(bundleProposal.StorageId).To(Equal(""))

// check that bundle didn't get finalized
_, found := s.App().BundlesKeeper.GetFinalizedBundle(s.Ctx(), 0, 0)
Expect(found).To(BeFalse())

// check if next uploader got not removed from pool
poolStakers := s.App().StakersKeeper.GetAllStakerAddressesOfPool(s.Ctx(), 1)
Expand All @@ -1150,7 +1331,7 @@ var _ = Describe("logic_end_block_handle_upload_timeout.go", Ordered, func() {
_, valaccountFound := s.App().StakersKeeper.GetValaccount(s.Ctx(), 1, i.STAKER_2)
Expect(valaccountFound).To(BeFalse())

_, found := s.App().StakersKeeper.GetStaker(s.Ctx(), i.STAKER_2)
_, found = s.App().StakersKeeper.GetStaker(s.Ctx(), i.STAKER_2)
Expect(found).To(BeTrue())

Expect(s.App().DelegationKeeper.GetDelegationOfPool(s.Ctx(), 1)).To(Equal(200 * i.KYVE))
Expand Down
Loading