-
Notifications
You must be signed in to change notification settings - Fork 34
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
Conversation
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 directionally good to me! TODO:
- Solidity integration tests.
- IPC subnet creation / genesis tooling update.
- Docs.
if (msg.sender != caller) { | ||
revert NotAuthorized(msg.sender); | ||
} |
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.
We should be having a helper method to verify that a given caller matches the subnet actor given a subnet ID.
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.
Would we need to store the caller if we had that method?
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 call, because the subnet actor is the only one that should be calling 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.
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; |
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 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).
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, I think the current one might be too strict.
@@ -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 { |
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 absolutely need these two variants?
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 have updated slightly to accommodate different usages.
Co-authored-by: raulk <raul@protocol.ai>
Co-authored-by: raulk <raul@protocol.ai>
@@ -67,6 +69,11 @@ contract SubnetActorManagerFacet is SubnetActorModifiers, ReentrancyGuard, Pausa | |||
payable(msg.sender).sendValue(amount); | |||
} | |||
|
|||
function setValidatorGater(address gater) external notKilled { |
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 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.
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 think the second approach you proposed leads to better security and encapsulation.
- This approach leaves open an opportunity window between subnet construction and
setValidatorGater
where any validator could join, as the subnet is effectively unguarded. - 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.
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.
@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.
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.
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(); |
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.
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 { |
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 think the second approach you proposed leads to better security and encapsulation.
- This approach leaves open an opportunity window between subnet construction and
setValidatorGater
where any validator could join, as the subnet is effectively unguarded. - 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.
uint256 collateral = LibStaking.totalValidatorCollateral(msg.sender); | ||
LibSubnetActor.gateValidatorPowerDelta(msg.sender, collateral, collateral + msg.value); |
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 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?
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 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.
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 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.
'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', |
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.
Thank you!
'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', |
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.
Thanks for catching this!
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.
Just a few comments. Other than that, it looks fine! We're missing the ipc-cli subnet create
update, right?
@@ -67,6 +69,11 @@ contract SubnetActorManagerFacet is SubnetActorModifiers, ReentrancyGuard, Pausa | |||
payable(msg.sender).sendValue(amount); | |||
} | |||
|
|||
function setValidatorGater(address gater) external notKilled { |
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.
@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) { |
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.
Nit
function isAllow(address validator, uint256 power) public view returns (bool) { | |
function isAllowed(address validator, uint256 power) public view returns (bool) { |
uint256 collateral = LibStaking.totalValidatorCollateral(msg.sender); | ||
LibSubnetActor.gateValidatorPowerDelta(msg.sender, collateral, collateral + msg.value); |
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 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.
This is a simple implementation of validator gating as outlined in #1121.
The changes are:
ValidatorGater
interface.