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(contracts): validator gating #1127

Merged
merged 16 commits into from
Sep 18, 2024
Merged

feat(contracts): validator gating #1127

merged 16 commits into from
Sep 18, 2024

Conversation

cryptoAtwill
Copy link
Contributor

@cryptoAtwill cryptoAtwill commented Aug 29, 2024

This is a simple implementation of validator gating as outlined in #1121.

The changes are:

  • Added an interface to the ValidatorGater interface.
  • Initial a simple implementation of validator gating contract that only allows exact power change.
  • Update federated and collateral based power update with gating
  • Unit tests + integration test cases
  • Fixed a minor selector update error

@cryptoAtwill cryptoAtwill requested a review from a team as a code owner August 29, 2024 09:04
@cryptoAtwill cryptoAtwill marked this pull request as draft August 29, 2024 09:04
@cryptoAtwill cryptoAtwill changed the title feat(contract): validator gating feat(contracts): validator gating Aug 29, 2024
Copy link
Contributor

@raulk raulk 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 directionally good to me! TODO:

  • Solidity integration tests.
  • IPC subnet creation / genesis tooling update.
  • Docs.

Comment on lines 31 to 33
if (msg.sender != caller) {
revert NotAuthorized(msg.sender);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

We should be having a helper method to verify that a given caller matches the subnet actor given a subnet ID.

Copy link
Contributor

Choose a reason for hiding this comment

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

Would we need to store the caller if we had that method?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

good call, because the subnet actor is the only one that should be calling it.

Copy link
Contributor

Choose a reason for hiding this comment

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

I think this contract should be in an examples directory.


/// Only owner can approve the validator join request
function approve(address validator, uint256 prevPower, uint256 newPower) external onlyOwner {
allowed[genKey(validator, prevPower, newPower)] = true;
Copy link
Contributor

Choose a reason for hiding this comment

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

I think it would be generally more useful if in this standard implementation, you approve validators to hold a power between a min and a max, instead of explicitly approving every delta.

Note that in this implementation, the allowed map can only grow infinitely. With my proposal, it would be a static configuration. If you agree, make sure you handle deletions (e.g. when min and max is 0 for a validator).

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, I think the current one might be too strict.

contracts/contracts/lib/LibSubnetActor.sol Outdated Show resolved Hide resolved
contracts/contracts/lib/LibSubnetActor.sol Outdated Show resolved Hide resolved
@@ -38,6 +41,39 @@ library LibSubnetActor {
return;
}

/// @notice Performs validator gating, i.e. checks if the validator power update is actually allowed.
function validatorGating(address validator, uint256 powerDelta, bool isIncrease) internal {
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 absolutely need these two variants?

Copy link
Contributor Author

@cryptoAtwill cryptoAtwill Aug 29, 2024

Choose a reason for hiding this comment

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

I have updated slightly to accommodate different usages.

@cryptoAtwill cryptoAtwill marked this pull request as ready for review August 30, 2024 09:02
@@ -67,6 +69,11 @@ contract SubnetActorManagerFacet is SubnetActorModifiers, ReentrancyGuard, Pausa
payable(msg.sender).sendValue(amount);
}

function setValidatorGater(address gater) external notKilled {
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 gater needs to be updated this way as compared to in a constructor because the gater needs the subnet address, but the subnet is not created at the constructor. Alternatively, one can also create the gater first then set the subnet address in gater. But I figured this is less intrusive to downstream ipc rust code and in the redesign, we can update accordingly.

Copy link
Contributor

Choose a reason for hiding this comment

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

I think the second approach you proposed leads to better security and encapsulation.

  1. This approach leaves open an opportunity window between subnet construction and setValidatorGater where any validator could join, as the subnet is effectively unguarded.
  2. Not all gater implementations will require whitelisting subnets, e.g. you could have a general purpose / utility gater that verifies if a validator is a Filecoin SP, an NFT holder, or something else. And any subnet who wants to reuse that logic could choose to rely on it.

So let's set the validator gater in the creation parameters, and let the gater manage the subnet whitelisting if it needs to.

Copy link
Contributor

Choose a reason for hiding this comment

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

@cryptoAtwill Do we still needs this? Since the IPC CLI only supports the constructor right? Also point no. 1 from Raul is very legit. I think we should just have users to have setSubnet method on their own gater implementations if they need to.

Copy link
Contributor

@raulk raulk left a comment

Choose a reason for hiding this comment

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

Just a few comments. Other than that, it looks fine! We're missing the ipc-cli subnet create update, right?

@@ -79,6 +79,7 @@ error MethodNotAllowed(string reason);
error InvalidFederationPayload();
error DuplicatedGenesisValidator();
error NotEnoughGenesisValidators();
error PowerChangeRequestNotApproved();
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
error PowerChangeRequestNotApproved();
error ValidatorPowerChangeDenied();

@@ -67,6 +69,11 @@ contract SubnetActorManagerFacet is SubnetActorModifiers, ReentrancyGuard, Pausa
payable(msg.sender).sendValue(amount);
}

function setValidatorGater(address gater) external notKilled {
Copy link
Contributor

Choose a reason for hiding this comment

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

I think the second approach you proposed leads to better security and encapsulation.

  1. This approach leaves open an opportunity window between subnet construction and setValidatorGater where any validator could join, as the subnet is effectively unguarded.
  2. Not all gater implementations will require whitelisting subnets, e.g. you could have a general purpose / utility gater that verifies if a validator is a Filecoin SP, an NFT holder, or something else. And any subnet who wants to reuse that logic could choose to rely on it.

So let's set the validator gater in the creation parameters, and let the gater manage the subnet whitelisting if it needs to.

Comment on lines +179 to +180
uint256 collateral = LibStaking.totalValidatorCollateral(msg.sender);
LibSubnetActor.gateValidatorPowerDelta(msg.sender, collateral, collateral + msg.value);
Copy link
Contributor

Choose a reason for hiding this comment

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

The problem that makes this impractical to use is that weights are relative. So I'm thinking it might be useful to pass the projected sum of total weights to the gater. That way the gater could reject takeover attempts (by having the old power, new power, and total network power). Granted, the gater could call back the subnet actor to get the current membership table and calculate the total, I think so? What do you think?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think the idea works, but just from gas concern + responsitbility, I'm not sure if SubnetActor should enforce this.

I can achieve the same check with my own Gater implementation because I know the old and new value. In my custom gater implementation, I can hardcode a total power limit. Then with each validator update, I can calculate the new total power with oldTotalPower = oldTotalPower + validatorNewPower - validatorOldPower. Then I can do the comparison with the hardcoded limit.

I feel (I could be wrong) the current interface is general enough for first iteration, adding total network power might incur a gas cost to users who dont need it. And I can achieve that with my custom implementation.

Copy link
Contributor

Choose a reason for hiding this comment

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

Yeah here I think I stand with Will. Since the users can have any arbitrary logic in the gater, I would leave it to them. They can quite easily store the current total powers if they want to.

Comment on lines -70 to -88
'src/GatewayDiamond.sol',
'src/SubnetActorDiamond.sol',
'src/SubnetRegistryDiamond.sol',
'src/OwnershipFacet.sol',
'src/diamond/DiamondCutFacet.sol',
'src/diamond/DiamondLoupeFacet.sol',
'src/gateway/GatewayGetterFacet.sol',
'src/gateway/GatewayManagerFacet.sol',
'src/gateway/GatewayMessengerFacet.sol',
'src/gateway/router/CheckpointingFacet.sol',
'src/gateway/router/TopDownFinalityFacet.sol',
'src/gateway/router/XnetMessagingFacet.sol',
'src/subnet/SubnetActorGetterFacet.sol',
'src/subnet/SubnetActorManagerFacet.sol',
'src/subnet/SubnetActorPauseFacet.sol',
'src/subnet/SubnetActorRewardFacet.sol',
'src/subnet/SubnetActorCheckpointingFacet.sol',
'src/subnetregistry/RegisterSubnetFacet.sol',
'src/subnetregistry/SubnetGetterFacet.sol',
Copy link
Contributor

Choose a reason for hiding this comment

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

Thank you!

Comment on lines +70 to +88
'contracts/GatewayDiamond.sol',
'contracts/SubnetActorDiamond.sol',
'contracts/SubnetRegistryDiamond.sol',
'contracts/OwnershipFacet.sol',
'contracts/diamond/DiamondCutFacet.sol',
'contracts/diamond/DiamondLoupeFacet.sol',
'contracts/gateway/GatewayGetterFacet.sol',
'contracts/gateway/GatewayManagerFacet.sol',
'contracts/gateway/GatewayMessengerFacet.sol',
'contracts/gateway/router/CheckpointingFacet.sol',
'contracts/gateway/router/TopDownFinalityFacet.sol',
'contracts/gateway/router/XnetMessagingFacet.sol',
'contracts/subnet/SubnetActorGetterFacet.sol',
'contracts/subnet/SubnetActorManagerFacet.sol',
'contracts/subnet/SubnetActorPauseFacet.sol',
'contracts/subnet/SubnetActorRewardFacet.sol',
'contracts/subnet/SubnetActorCheckpointingFacet.sol',
'contracts/subnetregistry/RegisterSubnetFacet.sol',
'contracts/subnetregistry/SubnetGetterFacet.sol',
Copy link
Contributor

Choose a reason for hiding this comment

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

Thanks for catching this!

Copy link
Contributor

@raulk raulk left a comment

Choose a reason for hiding this comment

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

Just a few comments. Other than that, it looks fine! We're missing the ipc-cli subnet create update, right?

@raulk raulk linked an issue Sep 2, 2024 that may be closed by this pull request
@@ -67,6 +69,11 @@ contract SubnetActorManagerFacet is SubnetActorModifiers, ReentrancyGuard, Pausa
payable(msg.sender).sendValue(amount);
}

function setValidatorGater(address gater) external notKilled {
Copy link
Contributor

Choose a reason for hiding this comment

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

@cryptoAtwill Do we still needs this? Since the IPC CLI only supports the constructor right? Also point no. 1 from Raul is very legit. I think we should just have users to have setSubnet method on their own gater implementations if they need to.

subnet = id;
}

function isAllow(address validator, uint256 power) public view returns (bool) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit

Suggested change
function isAllow(address validator, uint256 power) public view returns (bool) {
function isAllowed(address validator, uint256 power) public view returns (bool) {

Comment on lines +179 to +180
uint256 collateral = LibStaking.totalValidatorCollateral(msg.sender);
LibSubnetActor.gateValidatorPowerDelta(msg.sender, collateral, collateral + msg.value);
Copy link
Contributor

Choose a reason for hiding this comment

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

Yeah here I think I stand with Will. Since the users can have any arbitrary logic in the gater, I would leave it to them. They can quite easily store the current total powers if they want to.

@karlem karlem merged commit 8403e47 into main Sep 18, 2024
14 checks passed
@karlem karlem deleted the validator-gating branch September 18, 2024 20:33
@raulk raulk restored the validator-gating branch September 19, 2024 15:36
@raulk raulk deleted the validator-gating branch September 19, 2024 16:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Status: Done
Development

Successfully merging this pull request may close these issues.

[Staking] Validator gating
3 participants