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

Fix issue 34 #37

Merged
merged 9 commits into from
Feb 15, 2023
Merged

Fix issue 34 #37

merged 9 commits into from
Feb 15, 2023

Conversation

angbrav
Copy link
Collaborator

@angbrav angbrav commented Feb 9, 2023

This PR fixes issue #34 in the pseudocode model.

The current model disregards a corner case that should be taken care of in the implementation:

  • Assume a user delegates 10 tokens to a validator at epoch e1
  • Assume the user unbonds twice 5 tokens from the same validator in the same epoch in two different transactions e2
  • When executing the first undelegate tx, the model creates the record UnbondRecord{amount: 5, start: e1 } and updates the bond to 5 tokens
  • When executing the second undelegate tx, the model creates the same record UnbondRecord{amount: 5, start: e1 } and removes the bond.
  • The problem is that we keep unbond records in a set and when we try to add the second record, since it is a duplicate, it will be discarded.

It is an easy fix I'd say: use a bag instead of a set to allow duplicates, or check if the set includes the record and act upon (remove it, create a new one with double the amount, and add it). For now, I've left a comment on the model; I didn't want to "pollute" it. If we decide not to deal with it in the mode, I'll open an issue and refer to it in the model.

@@ -272,14 +277,23 @@ func unbond(validator_address, delegator_address, unbond_amount)
remain = 0
Copy link
Collaborator

Choose a reason for hiding this comment

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

Should remain = 0 come after the loop over the slashes and validators[validator_address].total_unbonds[cur_epoch+pipeline_length] = {UnbondRecord{amount: remain, start: start}}?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Thanks @brentstone! You are completely right. Fixed.

amount uint
start Epoch
}

type Validator struct {
Copy link
Collaborator

Choose a reason for hiding this comment

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

total_unbonds should really be total_unbonded, correct?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Do you mean the field in the Validator's struct? If I am not wrong, we use total_unbonds throughout the spec and total_unbonded at the end-of-epoch function when folding total_unbonds. I agree we could find better names. Was that your concern, or I misunderstood it?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Ok thanks for clarifying that these are intended to be different.

I'll elaborate a bit. First, yes, the field total_unbonds inside the Validator struct should be a map<Epoch, UnbondRecord> judging by the unbond code. It also seems that the Validator struct should explicitly have a total_unbonded field that is a map<Epoch, uint>, right? I think I was confused by the lack of a declaration of total_unbonded anywhere outside the end_of_epoch() code.

I'm still somewhat confused by what the purpose of Validator::total_unbonds is. From the pseudocode, it seems only to be used in func unbond, where we set values inside of it, but I don't see any other place where its data is read and then used for something. It is not in end_of_epoch, but perhaps there is another bug. From the following code from end_of_epoch:

      var total_unbonded = 0
      //find the total unbonded from the slash epoch up to the current epoch first
      //a..b notation determines an integer range: all integers between a and b inclusive
      forall (epoch in slash.epoch+1..cur_epoch) do
        forall (unbond in validators[validator_address].total_unbonded[epoch] s.t. unbond.start <= slash.epoch)
          total_unbonded += unbond.amount

it looks like perhaps forall (unbond in validators[validator_address].total_unbonded[epoch] should be forall (unbond in validators[validator_address].total_unbonds[epoch]. Does this seem to be the intention?

Copy link
Collaborator

Choose a reason for hiding this comment

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

actually I think I'm realizing that total_unbonds should be a map<Epoch, Set<UnbondRecord>>, where Set<UnbondRecord> could also be a Vec or similar

Copy link
Collaborator Author

@angbrav angbrav Feb 15, 2023

Choose a reason for hiding this comment

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

Yes, the code in end_of_epoch has a typo and validator's total_unbonds should be map<Epoch, Set<UnbondRecord>>. Both are now fixed, thanks for spotting them! It is difficult to produce typo-free pseudocode for non-executable specs...

@brentstone
Copy link
Collaborator

Going through the pseudocode again as I work on anoma/namada#892, apologies if some of my questions are technically out of scope for this PR but I figure here is a good place to put them anyway...

Looks like in withdrawing we are using a struct that looks like

type SlashedAmount struct {
  epoch Epoch
  amount uint
}

If I understand this correctly, we should add it to the other type declarations at the top of PoS-model.md

@brentstone
Copy link
Collaborator

What is the meaning of computed_amounts = computed_amounts \ {slashed_amount} in func withdraw?

@angbrav
Copy link
Collaborator Author

angbrav commented Feb 13, 2023

Going through the pseudocode again as I work on anoma/namada#892, apologies if some of my questions are technically out of scope for this PR but I figure here is a good place to put them anyway...

Sure, no problem.

Looks like in withdrawing we are using a struct that looks like

type SlashedAmount struct {
  epoch Epoch
  amount uint
}

If I understand this correctly, we should add it to the other type declarations at the top of PoS-model.md

Yes, done.

What is the meaning of computed_amounts = computed_amounts \ {slashed_amount} in func withdraw?

Set difference: X\Y is the set of elements that belong to X but not to Y. In this case it removes slashed_amount from the set computed_amounts.

forall (slash in slashes[validator_address] s.t. start <= slash.epoch)
amount_after_slashing -= amount*slash.rate
validators[validator_address].total_unbonds[cur_epoch+pipeline_length] += unbond_amount
remain -= amount
update_total_deltas(validator_address, pipeline_length, -1*amount_after_slashing)
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think that we should be tracking something like total_slashed_amount rather than amount_after_slashing. Looks like we want to supply update_total_deltas with the deltas change, rather than the new value post-slashing.

So instead of amount_after_slashing -= amount*slash.rate, want total_slashed_amount += amount*slash.rate.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Sorry, realized I am wrong in the above comment.

@angbrav angbrav merged commit 38e699c into trunk Feb 15, 2023
@angbrav angbrav deleted the manuel/23q1-fix-issue34 branch February 17, 2023 15:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants