-
Notifications
You must be signed in to change notification settings - Fork 45
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
Conversation
event SubnetBootstrapped(Validator[]); | ||
event SubnetBootstrapped(address[]); |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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)
s.bootstrapped = true; | ||
emit SubnetBootstrapped(s.genesisValidators); | ||
s.genesis.bootstrap(s.validatorSet); |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
// for genesis validators, totalCollateral == confirmedCollateral | ||
collateral: info.totalCollateral, | ||
federatedPower: info.federatedPower, |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch!
Also 🤯
There was a problem hiding this comment.
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"); | ||
} |
There was a problem hiding this comment.
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 🙏
There was a problem hiding this 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.
address[] memory validators = self.validators.values(); | ||
|
||
for (uint256 i = 0; i < validators.length; ) { |
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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?
event SubnetBootstrapped(Validator[]); | ||
event SubnetBootstrapped(address[]); |
There was a problem hiding this comment.
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)
/// TODO: migrating all the genesis related fields to this struct so that one can handle | ||
/// them all in a library. |
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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!
Going to merge this because it's way easier to reason about the complete changeset all at once in the base branch. |
Fixing genesis validators not updated when
unstake
andleave
are called.Currently in
unstake
, when a validator unstakes all the collateral previuosly staked, then the validator address will not be removed fromgenesisValidators
, see line. The same happens toleave
.Note that this PR will fail the CI only when #784 is merged into this branch.