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: subnet activity rollups and validator rewards (phase 1) #1181

Merged
merged 144 commits into from
Nov 27, 2024

Conversation

raulk
Copy link
Contributor

@raulk raulk commented Oct 22, 2024


Integrates these stacked PRs:


Subnet activity rollups (Phase 1)

This PR introduces the notion of subnet activity rollups that piggyback on bottom-up checkpoints. This is a mechanism to synthesise and bubble up information to the parent about some activity that occurred inside a subnet. The parent can choose to either relay this information up the hierarchy, or consume the incoming rollup for some effect (e.g. rewarding validators by minting a token, rebalancing the validator set based on performance metrics).

Consensus summary

Activity rollups contain subject-scoped summaries. These can be protocol-defined, or in user-defined (in the future). The protocol-defined summary we add is the consensus summary. This summary collects datapoints from the consensus layer that are relevant to export/surface to ancestors. Namely, the first attribute we care about is the number of blocks committed per validator. This will predictably be used by subnet creators to reward validators at the parent through, e.g. token inflation.

Compressed vs. full rollups and summaries

To avoid saturating checkpoints with activity data, we publish full rollups locally within the subnet as events only. In checkpoints, rollups travel in a compressed form, usually by creating commitments to data rather than embedding the data itself. This limits the footprint and spares space for xnet messages.

In the case of the consensus summary, checkpoints only carry short aggregated stats, and the root of a Merkle tree of the full data as a commitment.

Processing activity rollups and ValidatorRewarder

When an activity rollup arrives to the parent within a checkpoint, the SubnetActor uses the LibActivity library to process it. Currently, we extract the consensus summary and store it so that validators can come later and claim rewards by presenting a claim, which consists of the data leaf and a Merkle proof of inclusion in the root commitiment. The library tracks in state which validators have claimed per subnet and checkpoint height to prevent duplicate claims.

LibActivity is responsible for validating claims, but not for disbursing rewards. For this, it calls the configured user-defined ValidatorRewarder on its notifyClaimValid function, passing in the validated data leaf.

We provide an example ValidatorRewarderMap implementation that merely tallies the number of blocks committed per validator.

Activity actor and Merkle tree commitments

The new activity-tracker built-in actor acts as an accumulator of subnet activity. It is called at ABCI::end_block time to report the validator that committed the block. When a checkpoint is emitted, we pull and reset the accumulated data out of the actor, populate a Merkle tree with the entries, and we set its root hash in the consensus summary within the activity rollup.

We emit the full activity rollup as a local event in the subnet, for stakeholders (e.g. validators) to monitor and present any relevant payloads up in the hierarchy to execute the claim.

Secured by subnet consensus

Activity rollups present the same degree of security as all other vertical IPC interactions. This is because checkpoints are (a) published in the subnet during state transition and therefore secured by consensus, and (b) checkpoints are signed by a quorum of validators containing activity rollups are signed by a subnet quorum.

Claiming interfaces

The subnet actor is augmented with methods to perform single and batch claims. The validator (or another party acting on its behalf) must submit the checkpoint height, the relevant data leaf, and the merkle inclusion proof.

The subnet actor will verify the merkle inclusion proof for the pending checkpoint height, and call the ValidatorRewarder if it succeeds, marking the fragment as claimed in its state, so it cannot be double claimed.

A validator can claim rewards from multiple checkpoints, across multiple subnets rooted at that subnet actor, in a single call. Thus, it is rational for the validator to wait until enough rewards have been accumulated to offset and amortize the gas costs of the claim process.

Relayer enhancements

The relayer is enhanced with a new operating mode and task with the job of claiming rewards on behalf of a validator. Validators can use this new mode to watch a subnet for validator activity matching address, and to subsequently submit any relevant claims to the appropriate parent network (normally the root) to withdraw their corresponding validator rewards.

Unimplemented (phase 2)

Activity summary routing / relaying

At every level, subnets can decide whether they consume activity summaries from descendants, or they relay them up the chain. Usually, deeper subnets will want to relay these all the way up to the root, to provide a single entrypoint for validators operating in a subtree hierarchy to claim a canonical ERC20 token seated at the rootnet.

cryptoAtwill and others added 30 commits May 31, 2024 00:14
Co-authored-by: Eric Tu <[email protected]>
Co-authored-by: Mikers <[email protected]>
Co-authored-by: raulk <[email protected]>
Co-authored-by: Shashank Trivedi <[email protected]>
@raulk raulk changed the title feat: subnet activity summaries and validator rewards feat: subnet activity summaries and validator rewards (phase 1) Nov 15, 2024
Copy link
Contributor Author

@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.

Some initial comments.

Ok(())
}

pub fn purge_validator_block_committed(&mut self, rt: &impl Runtime) -> Result<(), ActorError> {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Any reason we don't just store an empty HAMT? It's cheaper than iterating over all entries and deleting each.

Comment on lines 72 to 76
let v = if let Some(v) = validators.get(validator)? {
*v + 1
} else {
1
};
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Since this returns an option, it's more idiomatic to just use unwrap_or to set the initial value.

use serde::{Deserialize, Serialize};

pub type BlockCommittedMap<BS> = Map2<BS, Address, BlockCommitted>;
pub type BlockCommitted = u64;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This type alias is kinda redundant IMO.

@raulk
Copy link
Contributor Author

raulk commented Nov 15, 2024

Full review developing here: #1205.

@raulk raulk marked this pull request as ready for review November 25, 2024 11:18
@raulk raulk requested a review from a team as a code owner November 25, 2024 11:18
Comment on lines 35 to 41
// TODO(rewards): enable the relayer to watch multiple subnets at once.

// TODO(rewards): add a new flag --process-summaries to activate processing summaries on all subnets.
// Enabling this mode makes the relayer watch for ActivitySummaryCommitted events, and stores the summaries in a database.
// It then tracks which summaries have been committed to the root (right now we only support submitting to the L1), to chase
// after those and present them via SubnetActor#submitSummary in order to trigger reward payout.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Let's open issues to add these TODOs to the backlog.

Copy link
Contributor

Choose a reason for hiding this comment

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

Added issue: #1215, remove the todo.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Stale/leaked from previous changes.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Stale/leaked from previous changes.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Stale/leaked from previous changes.

Comment on lines 90 to 94
// TODO(rewarder): step 1. call fvm ActivityTrackerActor::get_summary to generate the summary
// TODO(rewarder): step 2. update checkpoint.activities with that in step 1
// TODO: (if there is more time, should wrap param checkpoint with another data structure)
// TODO(rewarder): step 3. call fvm ActivityTrackerActor::purge_activities to purge the activities
// TODO(rewarder): step 4. emit validator details as event
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@cryptoAtwill is this still relevant?

Copy link
Contributor

Choose a reason for hiding this comment

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

This is for using fvm actor to derive commitment, we have pushed this to later phases, not relevant now, I can create an issue to track this

return compressed;
}

function newCompressedSummary(
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is only used above and once elsewhere. I don't think it's worth having it.

Copy link
Contributor

Choose a reason for hiding this comment

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

ok, I removed it.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This file has leaked. This is already covered in gas_market.

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 ID of this actor cannot be 99, as that conflicts with the burnt funds actor.

Comment on lines 66 to 75
impl<'a, DB: Blockstore + Clone + 'static> ActorActivityTracker<'a, DB> {
fn apply_implicit_message(&mut self, msg: FvmMessage) -> anyhow::Result<ApplyRet> {
let (apply_ret, _) = self.executor.execute_implicit(msg)?;
if let Some(err) = apply_ret.failure_info {
anyhow::bail!("failed to apply activity tracker messages: {}", err)
} else {
Ok(apply_ret)
}
}
}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is duplicated code, no? Do we actually need it? It seems it's only used once and kinda superfluous.

Copy link
Contributor

Choose a reason for hiding this comment

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

This method is used repeatedly in gas market and activity actor, I think it's worth making this a util function in FvmExecState, a function that executes the message and makes sure the execution is ok.

Comment on lines 22 to 38
impl MerkleProofGen {
pub fn pack_validator(v: &ValidatorData) -> Vec<String> {
vec![format!("{:?}", v.validator), v.blocks_committed.to_string()]
}

pub fn root(&self) -> Hash {
self.tree.root()
}

pub fn new(values: &[ValidatorData]) -> anyhow::Result<Self> {
let values = values.iter().map(Self::pack_validator).collect::<Vec<_>>();

let tree = StandardMerkleTree::of(&values, &VALIDATOR_SUMMARY_FIELDS)
.context("failed to construct Merkle tree")?;
Ok(MerkleProofGen { tree })
}
}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Code like this already exists in ipc/provider/.../evm/manager.rs. Looks like we can refactor into an Activity helper of some kind?

Copy link
Contributor

Choose a reason for hiding this comment

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

I created a MerkleGen util struct that can be used repeatedly, a simple wrapper.

@raulk raulk changed the title feat: subnet activity summaries and validator rewards (phase 1) feat: subnet activity rollups and validator rewards (phase 1) Nov 27, 2024
Copy link
Contributor Author

@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 is good enough to merge now. We'll land the docs + ERC20 minter ValidatorRewarder in separate PRs, before making a release.

@raulk raulk merged commit 4243ff9 into main Nov 27, 2024
11 checks passed
@raulk raulk deleted the integration/rewards branch November 27, 2024 16:19
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.

2 participants