Closed angbrav closed 11 months ago
(ref some discussions in https://github.com/anoma/namada/issues/34)
We need to make sure not to iterate over delegations or redelegations (DoS vector). In this case I think we can treat all redelegations from X to Y in epoch e the same (so e.g. when slashing we can just store the total amount from X to Y in e and slash based on this, we don't have to iterate over the redelegations).
(ref some discussions in anoma/namada#34)
We need to make sure not to iterate over delegations or redelegations (DoS vector). In this case I think we can treat all redelegations from X to Y in epoch e the same (so e.g. when slashing we can just store the total amount from X to Y in e and slash based on this, we don't have to iterate over the redelegations).
Thanks, that's a very important point! I think we unwittingly introduced iteration in https://github.com/informalsystems/partnership-heliax/pull/38 that's being carried over to here too, where in end_of_epoch
we started iterating over validator.set_unbonds
.
For the redelegations themselves, we suggested grouping these by a triple of src validator, dest validator and redelegation epoch, and then iteration over the groups is acceptable.
I think we could solve the iteration over the set_unbonds
the same way - i.e. group unbonds by a pair of bond start epoch and unbond epoch.
I tried to check for these properties:
I haven't found any issues with the logic besides the comments, most of which are minor.
As per https://github.com/informalsystems/partnership-heliax/pull/45#discussion_r1174870619 I think for 2., 6. and 7. we're ignoring the start epochs of the src bond deltas, which I think is ok as we never skip a slash on something that should be slashed, only potentially slashing a re-delegation that might not need to be slashed if we were to track the start epochs of re-delegations in detail from the source deltas. I think this is applied consistently across the different paths (unbond/withdraw/end of epoch).
I tried to check for these properties:
- bonding, unbonding, withdrawal and slashing from a validator that has no outgoing or incoming re-delegations is unaffected by the changes
- when a re-delegation src validator is slashed before re-delegation epoch + pipeline and the src bond was in slashable epoch range, the slash for the re-delegated amount is subtracted for the slash applied on the src validator and applied on the dest validator instead
- when a re-delegation src validator is slashed at or after re-delegation epoch + pipeline, the dest validator is unaffected
- when a re-delegation dest validator is slashed before re-delegation epoch + pipeline, the re-delegated amount is unaffected
- when a re-delegation dest validator is slashed at or after re-delegation epoch + pipeline, the re-delegated amount is slashed
- when a re-delegation is unbonded after a slash on the src validator that's applicable to the re-delegation is discovered, the unbond gets slashed and the final amount that can be withdrawn is not slashed again
- when a re-delegation is unbonded before a slash on the src validator that's applicable to the re-delegation is discovered, the unbond isn't slashed and the final amount that can be withdrawn gets slashed
- when a re-delegation is unbonded and a slash on the dest validator that's applicable to the re-delegation is discovered, the unbond isn't slashed and the final amount that can be withdrawn gets slashed (like a regular bond unbond)
I haven't found any issues with the logic besides the comments, most of which are minor.
As per #45 (comment) I think for 2., 6. and 7. we're ignoring the start epochs of the src bond deltas, which I think is ok as we never skip a slash on something that should be slashed, only potentially slashing a re-delegation that might not need to be slashed if we were to track the start epochs of re-delegations in detail from the source deltas. I think this is applied consistently across the different paths (unbond/withdraw/end of epoch).
Thanks for the detailed analysis @tzemanovic . I am transforming the spec into a Quint executable spec, so we will be able to test those scenarios much more easily. Regarding your last comment, see my response.
@tzemanovic @cwgoes I've gotten rid of the expensive iterations, in case you want to take a look!
@angbrav should we merge this?
We could, but it is not in sync with the Quint spec. Do we want to spend some time correcting it?
Closed as agreed to stop maintaining pseudocode specs in favor of Quint specs.
This PR adds redelegation to the pseudocode model and English text to describe the main logic and some definitions.
Note that the text below may be a bit outdatet.
Definitions
tx_redelegate
tx is issued and ends when the redelegated tokens stop contributing to the source validator and start contributing to the destination validator.unbonding_length
epochs.Desired Properties
The solution aim to guarantee the following desired properties:
pipeline_length
epochs. This means that if a delegator redelegates X tokens at epoche
, then these tokens stop contributing to the source validator stake and start contributing to the destination validator stake ate+pipeline_length
.Data Structures
The solution introduces the following data structures to track redelegations:
Redelegation
is a record with two fields: the source validator and the amount of tokens redelegated.redelegated_bonds in (Addr X Addr X Epoch) → Redelegation
: A map from delegator to a map from redelegation's destination validator to a map from the epoch at which the redelegation ends to a Redelegation record. This sounds more complex than it is. This is accessed by simplyredelegated_bonds[delegator][dest_validator][epoch]
. It is the counterparty of thebonds
data structure.redelegated_unbonds[][][] in (Addr X Addr X (Epoch, Epoch)) → Redelegation
: Similar toredelegated_bonds
but the key of the last map is a tuple to track not only the end of the redelegation but also the when the tokens are unbonded at the destination validator. It is the counterparty of theunbonds
data structure.redelegations map<Addr, map<Addr, IncomingRedelegation>>
. This is useful to prevent redelegations.IncomingRedelegation
is a record that stores the amount redelegated and the epoch at which the redelegation started.How it roughly works
redelegated_bonds
andredelegated_unbonds
aim at solving this issue. Let's take the following example:del
and two validatorsval1
andval2
.del
redelegates X tokens fromval1
toval2
in epoche
. Then the redelegation ends ate + pipeline_length
. Thenbonds[del][val2].deltas[e+pipeline_length]=X
, assuming that there were no bonds before.del
delegatesY
tokens toval2
in epoche
as well. Thenbonds[del][val2].deltas[e+pipeline_length]=X+Y
.del
want to unbond at an epoche' >= e+pipeline_length
and the bonds inbonds[del][val2].deltas[e+pipeline_length]
are going to be unbonded. We need to do two things:e'
thatval2
committed in any epoch>= e+ pipeline_length
to the whole amount (X+Y).redelegated_bonds
. Thus:bonds[del][val2].deltas[e+pipeline_length]=X+Y
redelegated_bonds[del][val2][e+pipeline_length]=Y