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

ACP 99 reference implementation #700

Open
wants to merge 24 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
229 changes: 229 additions & 0 deletions contracts/validator-manager/ACP99Manager.sol
richardpringle marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,229 @@
// (c) 2025, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.

// SPDX-License-Identifier: Ecosystem

pragma solidity 0.8.25;

/**
* @notice Description of the conversion data used to convert
* a subnet to an L1 on the P-Chain.
* This data is the pre-image of a hash that is authenticated by the P-Chain
* and verified by the Validator Manager.
*/
richardpringle marked this conversation as resolved.
Show resolved Hide resolved
struct ConversionData {
bytes32 subnetID;
bytes32 validatorManagerBlockchainID;
address validatorManagerAddress;
InitialValidator[] initialValidators;
}

/// @notice Specifies an initial validator, used in the conversion data.
struct InitialValidator {
bytes nodeID;
bytes blsPublicKey;
uint64 weight;
}

/// @notice L1 validator status
enum ValidatorStatus {
Unknown,
PendingAdded,
Active,
PendingRemoved,
Completed,
Invalidated
}

/**
* @notice Specifies the owner of a validator's remaining balance or disable owner on the P-Chain.
* P-Chain addresses are also 20-bytes, so we use the address type to represent them.
*/
struct PChainOwner {
uint32 threshold;
address[] addresses;
}

/**
* @notice Contains the active state of a Validator
* @param status The validator status
* @param nodeID The NodeID of the validator
* @param startingWeight The weight of the validator at the time of registration
* @param messageNonce The current weight update nonce
cam-schultz marked this conversation as resolved.
Show resolved Hide resolved
* @param weight The current weight of the validator
* @param startTime The start time of the validator
* @param endTime The end time of the validator
*/
struct Validator {
ValidatorStatus status;
bytes nodeID;
uint64 startingWeight;
uint64 sentNonce;
uint64 receivedNonce;
uint64 weight;
uint64 startTime;
uint64 endTime;
}

// solhint-disable ordering

/*
* @title ACP99Manager
* @notice The ACP99Manager interface represents the functionality for sovereign L1
* validator management, as specified in ACP-77
*/
abstract contract ACP99Manager {
/// @notice Emitted when an initial validator is registered.
event RegisteredInitialValidator(
bytes32 indexed validationID, bytes nodeID, uint64 weight
);
/// @notice Emitted when a validator registration to the L1 is initiated.
event InitiatedValidatorRegistration(
bytes32 indexed validationID,
bytes nodeID,
bytes32 registrationMessageID,
uint64 registrationExpiry,
uint64 weight
);
/// @notice Emitted when a validator registration to the L1 is completed.
event CompletedValidatorRegistration(
bytes32 indexed validationID, bytes nodeID, uint64 weight
);
/// @notice Emitted when removal of an L1 validator is initiated.
event InitiatedValidatorRemoval(
bytes32 indexed validationID,
bytes32 validatorWeightMessageID,
uint64 weight,
uint64 endTime
);
/// @notice Emitted when removal of an L1 validator is completed.
event CompletedValidatorRemoval(
bytes32 indexed validationID
);
/// @notice Emitted when a validator weight update is initiated.
event InitiatedValidatorWeightUpdate(
bytes32 indexed validationID,
uint64 nonce,
bytes32 weightUpdateMessageID,
uint64 weight
);
/// @notice Emitted when a validator weight update is completed.
event CompletedValidatorWeightUpdate(
bytes32 indexed validationID, uint64 nonce, uint64 weight
);

/// @notice Returns the SubnetID of the L1 tied to this manager
function subnetID() virtual public view returns (bytes32 subnetID);
Fixed Show fixed Hide fixed

/// @notice Returns the validator details for a given validation ID.
function getValidator(
bytes32 validationID
) virtual public view returns (Validator memory validator);

/// @notice Returns the total weight of the current L1 validator set.
function l1TotalWeight() virtual public view returns (uint64 weight);

/**
* @notice Verifies and sets the initial validator set for the chain by consuming a
* SubnetToL1ConversionMessage from the P-Chain.
*
* Emits a {RegisteredInitialValidator} event for each initial validator in {conversionData}.
*
* @param conversionData The Subnet conversion message data used to recompute and verify against the ConversionID.
* @param messsageIndex The index that contains the SubnetToL1ConversionMessage ICM message containing the
* ConversionID to be verified against the provided {conversionData}.
*/
function initializeValidatorSet(
ConversionData calldata conversionData,
uint32 messsageIndex
) virtual public;

/**
* @notice Initiates validator registration by issuing a RegisterL1ValidatorMessage. The validator should
* not be considered active until completeValidatorRegistration is called.
*
* Emits an {InitiatedValidatorRegistration} event on success.
*
* @param nodeID The ID of the node to add to the L1.
* @param blsPublicKey The BLS public key of the validator.
* @param registrationExpiry The time after which this message is invalid.
* @param remainingBalanceOwner The remaining balance owner of the validator.
* @param disableOwner The disable owner of the validator.
* @param weight The weight of the node on the L1.
* @return validationID The ID of the registered validator.
*/
function _initiateValidatorRegistration(
bytes memory nodeID,
bytes memory blsPublicKey,
uint64 registrationExpiry,
PChainOwner memory remainingBalanceOwner,
PChainOwner memory disableOwner,
uint64 weight
) virtual internal returns (bytes32 validationID);

/**
* @notice Completes the validator registration process by returning an acknowledgement of the registration of a
* validationID from the P-Chain. The validator should not be considered active until this method is successfully called.
*
* Emits a {CompletedValidatorRegistration} event on success.
*
* @param messageIndex The index of the L1ValidatorRegistrationMessage to be received providing the acknowledgement.
* @return validationID The ID of the registered validator.
*/
function completeValidatorRegistration(
uint32 messageIndex
) virtual public returns (bytes32 validationID);

/**
* @notice Initiates validator removal by issuing a L1ValidatorWeightMessage with the weight set to zero.
* The validator should be considered inactive as soon as this function is called.
*
* Emits an {InitiatedValidatorRemoval} on success.
*
* @param validationID The ID of the validator to remove.
*/
function _initiateValidatorRemoval(
bytes32 validationID
) virtual internal;

/**
* @notice Completes validator removal by consuming an RegisterL1ValidatorMessage from the P-Chain acknowledging
* that the validator has been removed.
*
* Emits a {CompletedValidatorRemoval} on success.
*
* @param messageIndex The index of the RegisterL1ValidatorMessage.
*/
function completeValidatorRemoval(
uint32 messageIndex
) virtual public returns (bytes32 validationID);

/**
* @notice Initiates validator weight update by issuing a L1ValidatorWeightMessage with a nonzero weight.
* The validator weight change should not have any effect until completeValidatorWeightUpdate is successfully called.
*
* Emits an {InitiatedValidatorWeightUpdate} event on success.
*
* @param validationID The ID of the validator to modify.
* @param weight The new weight of the validator.
* @return nonce The validator nonce associated with the weight change.
* @return messageID The ID of the L1ValidatorWeightMessage used to update the validator's weight.
*/
function _initiateValidatorWeightUpdate(
bytes32 validationID,
uint64 weight
) virtual internal returns (uint64 nonce, bytes32 messageID);

/**
* @notice Completes the validator weight update process by consuming a L1ValidatorWeightMessage from the P-Chain
* acknowledging the weight update. The validator weight change should not have any effect until this method is successfully called.
*
* Emits a {CompletedValidatorWeightUpdate} event on success.
*
* @param messageIndex The index of the L1ValidatorWeightMessage message to be received providing the acknowledgement.
* @return validationID The ID of the validator.
*/
function completeValidatorWeightUpdate(
uint32 messageIndex
) virtual public returns (bytes32 validationID);
}
2 changes: 1 addition & 1 deletion contracts/validator-manager/ExampleRewardCalculator.sol
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ contract ExampleRewardCalculator is IRewardCalculator {
uint64 stakingEndTime,
uint64 uptimeSeconds
) external view returns (uint256) {
// Equivalent to uptimeSeconds/(validator.endedAt - validator.startedAt) < UPTIME_REWARDS_THRESHOLD_PERCENTAGE/100
// Equivalent to uptimeSeconds/(validator.endTime - validator.startTime) < UPTIME_REWARDS_THRESHOLD_PERCENTAGE/100
// Rearranged to prevent integer division truncation.
if (
uptimeSeconds * 100
Expand Down
16 changes: 12 additions & 4 deletions contracts/validator-manager/PoAValidatorManager.sol
richardpringle marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -55,21 +55,29 @@ contract PoAValidatorManager is IPoAValidatorManager, ValidatorManager, OwnableU
ValidatorRegistrationInput calldata registrationInput,
uint64 weight
) external onlyOwner returns (bytes32 validationID) {
return _initializeValidatorRegistration(registrationInput, weight);
return _initiateValidatorRegistration({
nodeID: registrationInput.nodeID,
blsPublicKey: registrationInput.blsPublicKey,
registrationExpiry: registrationInput.registrationExpiry,
remainingBalanceOwner: registrationInput.remainingBalanceOwner,
disableOwner: registrationInput.disableOwner,
weight: weight
});
}

// solhint-enable ordering
/**
* @notice See {IPoAValidatorManager-initializeEndValidation}.
*/
function initializeEndValidation(bytes32 validationID) external override onlyOwner {
_initializeEndValidation(validationID);
_initiateValidatorRemoval(validationID);
}

/**
* @notice See {IValidatorManager-completeEndValidation}.
*/
function completeEndValidation(uint32 messageIndex) external {
_completeEndValidation(messageIndex);
function completeValidatorRemoval(uint32 messageIndex) virtual override public returns (bytes32) {
(bytes32 validationID,) = _completeEndValidation(messageIndex);
return validationID;
}
}
Loading
Loading