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

Update genesis validator lifecycle #778

Merged
merged 6 commits into from
Mar 15, 2024
Merged

Conversation

cryptoAtwill
Copy link
Contributor

@cryptoAtwill cryptoAtwill commented Mar 8, 2024

Fixing genesis validators not updated when unstake and leave are called.

Currently in unstake, when a validator unstakes all the collateral previuosly staked, then the validator address will not be removed from genesisValidators, see line. The same happens to leave.

Note that this PR will fail the CI only when #784 is merged into this branch.

@cryptoAtwill cryptoAtwill requested review from raulk and aakoshh March 8, 2024 15:19
Comment on lines -17 to +19
event SubnetBootstrapped(Validator[]);
event SubnetBootstrapped(address[]);
Copy link
Contributor

Choose a reason for hiding this comment

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

Why does this have to change?

Copy link
Contributor

Choose a reason for hiding this comment

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

I mean the subscriber would be interested in the actual powers, wouldn't it?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

They can call genesisValidators to query their information. Getter is free and omitting the addresses should be cheaper.

Copy link
Contributor

Choose a reason for hiding this comment

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

I'm not a contract designer, so I am possibly completely off the mark on this: I thought events should ideally be self-contained so you learn what you have to without the need for further queries, and if you do need a query, then I'm not sure why it should return even the list of addresses, why not just a signal to flag up the block where the query can be made?

Copy link
Contributor

Choose a reason for hiding this comment

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

This event payload seems somewhat arbitrary. I wouldn't dwell too much on it right now, we'll need to change it going forward anyway to serve practical observability purposes. There are many ways to skin a cat, but subjective best practices:

  • Keep events as small as they can be.
  • Indexed fields: only publish identification data that subscribers will be interested to filter by through equality match (as that's the only predicate supported in Eth JSON-RPCs), e.g. publish addresses, IDs, kinds, categories, classes, etc. but do not publish balances, amounts, etc. as indexed fields.
  • In the payload, only publish data that refers to the transaction itself, which would otherwise NOT be available in state (e.g. deltas, actioning address, etc.) Public state data can be fetched from the contract directly, so it's superfluous to publish here. (Draw parallels with event streaming principles)

Comment on lines 111 to +112
s.bootstrapped = true;
emit SubnetBootstrapped(s.genesisValidators);
s.genesis.bootstrap(s.validatorSet);
Copy link
Contributor

Choose a reason for hiding this comment

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

Should bootstrapped be moved into this new SubnetGenesis struct?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yeah, actually I think a lot of the parameters can be moved to SubnetGenesis struct so that we have better encapsulation, now it's scattered around. If this struct is ok, then I will create more PRs to migrate the fields one by one.

Comment on lines +82 to +84
// for genesis validators, totalCollateral == confirmedCollateral
collateral: info.totalCollateral,
federatedPower: info.federatedPower,
Copy link
Contributor

Choose a reason for hiding this comment

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

Can these be 0? I can't see where a validator is removed from SubnetGenesis, but I assume that they can leave before genesis and remove their collateral?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yeah, when they unstake or leave with zero collateral, they are removed from genesis validator.

@@ -611,15 +586,15 @@ library LibValidatorTracking {
ParentValidatorsTracker storage self,
uint16 activeValidatorsLimit,
GenesisValidator[] memory validators
) internal returns (Validator[] memory membership) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Where was membership assigned before? 🤔

++i;
}
}
if (!alreadyValidator) {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

The issue here is that if there are subsequent changes to the validator, like stake, the weight is not updated.

Copy link
Contributor

Choose a reason for hiding this comment

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

Good catch!
Also 🤯

Copy link
Contributor

Choose a reason for hiding this comment

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

Do we have a authoritative state diagram showing the lifecycle stages of a subnet, all transition edges, and all possible actions within each stage?

// unstake everything else
saDiamond.manager().unstake(validator1Stake / 2);
require(saDiamond.getter().genesisValidators().length == 0, "should not be validator");
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Great! I couldn't write any of this, but it helps to understand the flow 🙏

Copy link
Contributor

@aakoshh aakoshh left a comment

Choose a reason for hiding this comment

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

This looks good to me, apart from my general comments about federated_power itself.

Comment on lines +37 to +39
address[] memory validators = self.validators.values();

for (uint256 i = 0; i < validators.length; ) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we harden this by asserting that validatorInfo contains exactly the validators we expect (and hold in storage)?

++i;
}
}
if (!alreadyValidator) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we have a authoritative state diagram showing the lifecycle stages of a subnet, all transition edges, and all possible actions within each stage?

Comment on lines -17 to +19
event SubnetBootstrapped(Validator[]);
event SubnetBootstrapped(address[]);
Copy link
Contributor

Choose a reason for hiding this comment

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

This event payload seems somewhat arbitrary. I wouldn't dwell too much on it right now, we'll need to change it going forward anyway to serve practical observability purposes. There are many ways to skin a cat, but subjective best practices:

  • Keep events as small as they can be.
  • Indexed fields: only publish identification data that subscribers will be interested to filter by through equality match (as that's the only predicate supported in Eth JSON-RPCs), e.g. publish addresses, IDs, kinds, categories, classes, etc. but do not publish balances, amounts, etc. as indexed fields.
  • In the payload, only publish data that refers to the transaction itself, which would otherwise NOT be available in state (e.g. deltas, actioning address, etc.) Public state data can be fetched from the contract directly, so it's superfluous to publish here. (Draw parallels with event streaming principles)

Comment on lines +164 to +165
/// TODO: migrating all the genesis related fields to this struct so that one can handle
/// them all in a library.
Copy link
Contributor

Choose a reason for hiding this comment

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

Does it make sense to do this now, to minimise the migration logic we'll need to write to upgrade early adopters?

@@ -153,6 +154,17 @@ struct GenesisValidator {
bytes metadata;
}

/// @notice Maintains the genesis information related to a particular subnet
struct SubnetGenesis {
Copy link
Contributor

Choose a reason for hiding this comment

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

Nice work -- this type makes a ton of sense.

@@ -90,6 +90,79 @@ contract SubnetActorDiamondTest is Test, IntegrationTestBase {
);
}

/// @notice Testing the genesis validator's collateral
function testSubnetActorDiamond_GenesisValidators() public {
Copy link
Contributor

Choose a reason for hiding this comment

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

Great work writing these tests!

@raulk
Copy link
Contributor

raulk commented Mar 15, 2024

Going to merge this because it's way easier to reason about the complete changeset all at once in the base branch.

@raulk raulk merged commit d29b922 into init-staking Mar 15, 2024
14 of 22 checks passed
@raulk raulk deleted the init-staking-genesis branch March 15, 2024 20:39
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.

3 participants