Skip to content

Commit

Permalink
feat(node): integration rewards, review comments. (#1205)
Browse files Browse the repository at this point in the history
Co-authored-by: cryptoAtwill <[email protected]>
Co-authored-by: cryptoAtwill <[email protected]>
  • Loading branch information
3 people authored Nov 25, 2024
1 parent 86538d5 commit 69e04a9
Show file tree
Hide file tree
Showing 57 changed files with 1,147 additions and 936 deletions.
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion contracts/binding/Cargo.toml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

92 changes: 50 additions & 42 deletions contracts/contracts/activities/Activity.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,55 +3,63 @@ pragma solidity ^0.8.23;

import {SubnetID} from "../structs/Subnet.sol";

event ActivityReportCreated(uint64 checkpointHeight, ActivityReport report);
// Event to be emitted within the subnet when a new activity summary has been recorded.
event ActivityRollupRecorded(uint64 checkpointHeight, FullActivityRollup rollup);

/// The full validator activities report
struct ActivityReport {
ValidatorActivityReport[] validators;
// Carries a set of reports summarising various aspects of the activity that took place in the subnet between the
// previous checkpoint and the checkpoint this summary is committed into. If this is the first checkpoint, the summary
// contains information about the subnet's activity since genesis.
// In the future we'll be having more kinds of activity reports here.
struct FullActivityRollup {
/// A report of consensus-level activity that took place in the subnet between the previous checkpoint
/// and the checkpoint this summary is committed into.
/// @dev If there is a configuration change applied at this checkpoint, this carries information
/// about the _old_ validator set.
Consensus.FullSummary consensus;
}

struct ValidatorActivityReport {
/// @dev The validator whose activity we're reporting about.
address validator;
/// @dev The number of blocks committed by each validator in the position they appear in the validators array.
/// If there is a configuration change applied at this checkpoint, this carries information about the _old_ validator set.
uint64 blocksCommitted;
/// @dev Other metadata
bytes metadata;
// Compressed representation of the activity summary that can be embedded in checkpoints to propagate up the hierarchy.
struct CompressedActivityRollup {
Consensus.CompressedSummary consensus;
}

/// The summary for the child subnet activities that should be submitted to the parent subnet
/// together with a bottom up checkpoint
struct ActivitySummary {
/// The total number of distintive validators that have mined
uint64 totalActiveValidators;
/// The activity commitment for validators
bytes32 commitment;
/// Namespace for consensus-level activity summaries.
library Consensus {
type MerkleHash is bytes32;

// TODO: add relayed rewarder commitment
}
// Aggregated stats for consensus-level activity.
struct AggregatedStats {
/// The total number of unique validators that have mined within this period.
uint64 totalActiveValidators;
/// The total number of blocks committed by all validators during this period.
uint64 totalNumBlocksCommitted;
}

/// The summary for a single validator
struct ValidatorSummary {
/// @dev The child subnet checkpoint height associated with this summary
uint64 checkpointHeight;
/// @dev The validator whose activity we're reporting about.
address validator;
/// @dev The number of blocks committed by each validator in the position they appear in the validators array.
/// If there is a configuration change applied at this checkpoint, this carries information about the _old_ validator set.
uint64 blocksCommitted;
/// @dev Other metadata
bytes metadata;
}
// The full activity summary for consensus-level activity.
struct FullSummary {
AggregatedStats stats;
/// The breakdown of activity per validator.
ValidatorData[] data;
}

/// The proof required for validators to claim rewards
struct ValidatorClaimProof {
ValidatorSummary summary;
bytes32[] proof;
}
// The compresed representation of the activity summary for consensus-level activity suitable for embedding in a checkpoint.
struct CompressedSummary {
AggregatedStats stats;
/// The commitment for the validator details, so that we don't have to transmit them in full.
MerkleHash dataRootCommitment;
}

struct ValidatorData {
/// @dev The validator whose activity we're reporting about, identified by the Ethereum address corresponding
/// to its secp256k1 pubkey.
address validator;
/// @dev The number of blocks committed by this validator during the summarised period.
uint64 blocksCommitted;
}

/// The proofs to batch claim validator rewards in a specific subnet
struct BatchClaimProofs {
SubnetID subnetId;
ValidatorClaimProof[] proofs;
/// The payload for validators to claim rewards
struct ValidatorClaim {
ValidatorData data;
MerkleHash[] proof;
}
}
4 changes: 2 additions & 2 deletions contracts/contracts/activities/IValidatorRewarder.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
pragma solidity ^0.8.23;

import {SubnetID} from "../structs/Subnet.sol";
import {ValidatorSummary} from "./Activity.sol";
import {Consensus} from "./Activity.sol";

/// @title ValidatorRewarder interface.
///
Expand All @@ -14,7 +14,7 @@ interface IValidatorRewarder {
/// @notice Called by the subnet manager contract to instruct the rewarder to process the subnet summary and
/// disburse any relevant rewards.
/// @dev This method should revert if the summary is invalid; this will cause the
function disburseRewards(SubnetID calldata id, ValidatorSummary calldata summary) external;
function disburseRewards(SubnetID calldata id, Consensus.ValidatorData calldata detail) external;
}

/// @title Validator reward setup interface
Expand Down
21 changes: 10 additions & 11 deletions contracts/contracts/activities/LibActivityMerkleVerifier.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,24 @@ pragma solidity ^0.8.23;

import {SubnetID} from "../structs/Subnet.sol";
import {InvalidProof} from "../errors/IPCErrors.sol";
import {ValidatorSummary} from "./Activity.sol";
import {Consensus} from "./Activity.sol";
import {MerkleProof} from "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol";

/// Verifies the proof to the commitment in subnet activity summary
library LibActivityMerkleVerifier {
function ensureValidProof(
bytes32 commitment,
ValidatorSummary calldata summary,
bytes32[] calldata proof
Consensus.ValidatorData calldata detail,
Consensus.MerkleHash[] calldata proof
) internal pure {
// Constructing leaf: https://github.com/OpenZeppelin/merkle-tree#leaf-hash
bytes32 leaf = keccak256(
bytes.concat(
keccak256(
abi.encode(summary.validator, summary.blocksCommitted, summary.metadata)
)
)
);
bool valid = MerkleProof.verify({proof: proof, root: commitment, leaf: leaf});
bytes32 leaf = keccak256(bytes.concat(keccak256(abi.encode(detail.validator, detail.blocksCommitted))));
// converting proof to bytes32[]
bytes32[] memory proofBytes = new bytes32[](proof.length);
for (uint256 i = 0; i < proof.length; i++) {
proofBytes[i] = Consensus.MerkleHash.unwrap(proof[i]);
}
bool valid = MerkleProof.verify({proof: proofBytes, root: commitment, leaf: leaf});
if (!valid) {
revert InvalidProof();
}
Expand Down
89 changes: 43 additions & 46 deletions contracts/contracts/activities/ValidatorRewardFacet.sol
Original file line number Diff line number Diff line change
@@ -1,27 +1,32 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity ^0.8.23;

import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
import {EnumerableMap} from "@openzeppelin/contracts/utils/structs/EnumerableMap.sol";
import {Consensus} from "./Activity.sol";

import {EnumerableMap} from "@openzeppelin/contracts/utils/structs/EnumerableMap.sol";
import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
import {IValidatorRewarder, IValidatorRewardSetup} from "./IValidatorRewarder.sol";
import {LibActivityMerkleVerifier} from "./LibActivityMerkleVerifier.sol";
import {LibDiamond} from "../lib/LibDiamond.sol";
import {NotValidator, SubnetNoTargetCommitment, CommitmentAlreadyInitialized, ValidatorAlreadyClaimed, NotGateway, NotOwner} from "../errors/IPCErrors.sol";
import {Pausable} from "../lib/LibPausable.sol";
import {ReentrancyGuard} from "../lib/LibReentrancyGuard.sol";
import {NotValidator, SubnetNoTargetCommitment, CommitmentAlreadyInitialized, ValidatorAlreadyClaimed, NotGateway, NotOwner} from "../errors/IPCErrors.sol";
import {ValidatorSummary, BatchClaimProofs} from "./Activity.sol";
import {IValidatorRewarder, IValidatorRewardSetup} from "./IValidatorRewarder.sol";
import {SubnetIDHelper} from "../lib/SubnetIDHelper.sol";
import {SubnetID} from "../structs/Subnet.sol";
import {LibActivityMerkleVerifier} from "./LibActivityMerkleVerifier.sol";
import {LibDiamond} from "../lib/LibDiamond.sol";

/// The validator reward facet for the parent subnet, i.e. for validators in the child subnet
/// to claim their reward in the parent subnet, which should be the current subnet this facet
/// is deployed.
contract ValidatorRewardFacet is ReentrancyGuard, Pausable {
function batchClaim(BatchClaimProofs[] calldata payload) external nonReentrant whenNotPaused {
uint256 len = payload.length;
function batchSubnetClaim(
SubnetID calldata subnet,
uint64[] calldata checkpointHeights,
Consensus.ValidatorClaim[] calldata claims
) external nonReentrant whenNotPaused {
require(checkpointHeights.length == claims.length, "length mismatch");
uint256 len = claims.length;
for (uint256 i = 0; i < len; ) {
_batchClaimInSubnet(payload[i]);
_claim(subnet, checkpointHeights[i], claims[i].data, claims[i].proof);
unchecked {
i++;
}
Expand All @@ -30,51 +35,41 @@ contract ValidatorRewardFacet is ReentrancyGuard, Pausable {

/// Validators claim their reward for doing work in the child subnet
function claim(
SubnetID calldata subnetId,
ValidatorSummary calldata summary,
bytes32[] calldata proof
SubnetID calldata subnet,
uint64 checkpointHeight,
Consensus.ValidatorData calldata data,
Consensus.MerkleHash[] calldata proof
) external nonReentrant whenNotPaused {
ValidatorRewardStorage storage s = LibValidatorReward.facetStorage();
_claim(s, subnetId, summary, proof);
_claim(subnet, checkpointHeight, data, proof);
}

// ======== Internal functions ===========

function handleRelay() internal pure {
// no opt for now
// no-op for now
return;
}

function _batchClaimInSubnet(BatchClaimProofs calldata payload) internal {
uint256 len = payload.proofs.length;
ValidatorRewardStorage storage s = LibValidatorReward.facetStorage();

for (uint256 i = 0; i < len; ) {
_claim(s, payload.subnetId, payload.proofs[i].summary, payload.proofs[i].proof);
unchecked {
i++;
}
}
}

function _claim(
ValidatorRewardStorage storage s,
SubnetID calldata subnetId,
ValidatorSummary calldata summary,
bytes32[] calldata proof
uint64 checkpointHeight,
Consensus.ValidatorData calldata detail,
Consensus.MerkleHash[] calldata proof
) internal {
ValidatorRewardStorage storage s = LibValidatorReward.facetStorage();

// note: No need to check if the subnet is active. If the subnet is not active, the checkpointHeight
// note: will never exist.

if (msg.sender != summary.validator) {
if (msg.sender != detail.validator) {
revert NotValidator(msg.sender);
}

if (s.validatorRewarder == address(0)) {
return handleRelay();
}

LibValidatorReward.handleDistribution(s, subnetId, summary, proof);
LibValidatorReward.handleDistribution(subnetId, checkpointHeight, detail, proof);
}
}

Expand Down Expand Up @@ -102,7 +97,7 @@ struct ValidatorRewardStorage {
}

/// The payload for list commitments query
struct ListCommimentDetail {
struct ListCommitmentDetail {
/// The child subnet checkpoint height
uint64 checkpointHeight;
/// The actual commiment of the activities
Expand All @@ -121,7 +116,7 @@ library LibValidatorReward {
function initNewDistribution(
SubnetID calldata subnetId,
uint64 checkpointHeight,
bytes32 commitment,
Consensus.MerkleHash commitment,
uint64 totalActiveValidators
) internal {
ValidatorRewardStorage storage ds = facetStorage();
Expand All @@ -132,24 +127,24 @@ library LibValidatorReward {
revert CommitmentAlreadyInitialized();
}

ds.commitments[subnetKey].set(bytes32(uint256(checkpointHeight)), commitment);
ds.commitments[subnetKey].set(bytes32(uint256(checkpointHeight)), Consensus.MerkleHash.unwrap(commitment));
ds.distributions[subnetKey][checkpointHeight].totalValidators = totalActiveValidators;
}

function listCommitments(
SubnetID calldata subnetId
) internal view returns (ListCommimentDetail[] memory listDetails) {
) internal view returns (ListCommitmentDetail[] memory listDetails) {
ValidatorRewardStorage storage ds = facetStorage();

bytes32 subnetKey = subnetId.toHash();

uint256 size = ds.commitments[subnetKey].length();
listDetails = new ListCommimentDetail[](size);
listDetails = new ListCommitmentDetail[](size);

for (uint256 i = 0; i < size; ) {
(bytes32 heightBytes32, bytes32 commitment) = ds.commitments[subnetKey].at(i);

listDetails[i] = ListCommimentDetail({
listDetails[i] = ListCommitmentDetail({
checkpointHeight: uint64(uint256(heightBytes32)),
commitment: commitment
});
Expand Down Expand Up @@ -178,18 +173,20 @@ library LibValidatorReward {
}

function handleDistribution(
ValidatorRewardStorage storage s,
SubnetID calldata subnetId,
ValidatorSummary calldata summary,
bytes32[] calldata proof
uint64 checkpointHeight,
Consensus.ValidatorData calldata detail,
Consensus.MerkleHash[] calldata proof
) internal {
ValidatorRewardStorage storage s = LibValidatorReward.facetStorage();

bytes32 subnetKey = subnetId.toHash();

bytes32 commitment = ensureValidCommitment(s, subnetKey, summary.checkpointHeight);
LibActivityMerkleVerifier.ensureValidProof(commitment, summary, proof);
bytes32 commitment = ensureValidCommitment(s, subnetKey, checkpointHeight);
LibActivityMerkleVerifier.ensureValidProof(commitment, detail, proof);

validatorTryClaim(s, subnetKey, summary.checkpointHeight, summary.validator);
IValidatorRewarder(s.validatorRewarder).disburseRewards(subnetId, summary);
validatorTryClaim(s, subnetKey, checkpointHeight, detail.validator);
IValidatorRewarder(s.validatorRewarder).disburseRewards(subnetId, detail);
}

function ensureValidCommitment(
Expand Down
Loading

0 comments on commit 69e04a9

Please sign in to comment.