From c0b7862be0fcaa9a6f41de6a6b651be9a0524b2d Mon Sep 17 00:00:00 2001 From: owenwahlgren Date: Tue, 5 Nov 2024 14:13:15 -0500 Subject: [PATCH] validator manager docs --- app/(home)/page.client.tsx | 12 +- app/layout.config.tsx | 4 +- components/mermaid.tsx | 19 +++ content/docs/evm-l1s/meta.json | 8 +- .../validator-manager/add-validator.mdx | 19 +++ .../evm-l1s/validator-manager/contract.mdx | 109 ++++++++++++++++++ .../custom-validator-manager.mdx | 93 +++++++++++++++ .../validator-manager/remove-validator.mdx | 42 +++++++ .../evm-l1s/validator-manager/upgrade.mdx | 8 ++ mdx-components.tsx | 6 + package.json | 1 + 11 files changed, 315 insertions(+), 6 deletions(-) create mode 100644 components/mermaid.tsx create mode 100644 content/docs/evm-l1s/validator-manager/add-validator.mdx create mode 100644 content/docs/evm-l1s/validator-manager/contract.mdx create mode 100644 content/docs/evm-l1s/validator-manager/custom-validator-manager.mdx create mode 100644 content/docs/evm-l1s/validator-manager/remove-validator.mdx create mode 100644 content/docs/evm-l1s/validator-manager/upgrade.mdx diff --git a/app/(home)/page.client.tsx b/app/(home)/page.client.tsx index 18a6dc9051f..21b144373e2 100644 --- a/app/(home)/page.client.tsx +++ b/app/(home)/page.client.tsx @@ -5,7 +5,7 @@ import React, { Fragment, type ReactElement, } from 'react'; -import { IndentDecrease, Layers, MailIcon, MonitorCheck, Settings, SproutIcon, SquareGanttChart, TerminalIcon, Webhook, HomeIcon, BadgeDollarSign, CpuIcon, Files, Folder, Globe, Link } from 'lucide-react'; +import { IndentDecrease, Layers, MailIcon, MonitorCheck, Settings, SproutIcon, SquareGanttChart, TerminalIcon, Webhook, HomeIcon, BadgeDollarSign, CpuIcon, Files, Folder, Globe, Link, FolderCode } from 'lucide-react'; import { RootToggle } from 'fumadocs-ui/components/layout/root-toggle'; export function DeployBlockchainAnimation(): React.ReactElement { @@ -123,13 +123,19 @@ export function HamburgerMenu(): React.ReactElement { url: '/dapps', }, { - title: 'Avalanche L1s', + title: 'Avalanche CLI', description: 'Build Your L1 Blockchain', icon: , url: '/avalanche-l1s', }, { - title: 'Virtual Machines', + title: 'EVM L1s', + description: 'Customize the Ethereum VM', + icon: , + url: '/evm-l1s', + }, + { + title: 'Custom Virtual Machines', description: 'Customize Your Execution Layer', icon: , url: '/virtual-machines', diff --git a/app/layout.config.tsx b/app/layout.config.tsx index 7eb18996bb8..6f3dc244927 100644 --- a/app/layout.config.tsx +++ b/app/layout.config.tsx @@ -2,7 +2,7 @@ import { type BaseLayoutProps, type DocsLayoutProps } from 'fumadocs-ui/layout'; import { Title, HomeTitle } from '@/app/layout.client'; import { docsPageTree } from '@/utils/docs-loader'; import { RootToggle } from 'fumadocs-ui/components/layout/root-toggle'; -import { MailIcon, SproutIcon, SquareGanttChart, IndentDecrease, Layers, MonitorCheck, Settings, Webhook } from 'lucide-react'; +import { MailIcon, SproutIcon, SquareGanttChart, IndentDecrease, Layers, MonitorCheck, Settings, Webhook, Server, FolderCode } from 'lucide-react'; // home page configuration (HomeTitle includes hamburger menu) export const homebaseOptions: BaseLayoutProps = { @@ -80,7 +80,7 @@ export const docsOptions: DocsLayoutProps = { { title: 'EVM L1s', description: 'Customize the Ethereum VM', - icon: , + icon: , url: '/evm-l1s', }, { diff --git a/components/mermaid.tsx b/components/mermaid.tsx new file mode 100644 index 00000000000..b52498d9178 --- /dev/null +++ b/components/mermaid.tsx @@ -0,0 +1,19 @@ +"use client"; +import React, { useEffect } from "react"; +import mermaid from "mermaid"; + +mermaid.initialize({ + startOnLoad: true, +}); + +type MermaidProps = { + readonly chart: string; +}; + +const Mermaid = ({ chart }: MermaidProps): JSX.Element => { + useEffect(() => mermaid.contentLoaded(), []); + + return
{chart}
; +}; + +export default Mermaid; \ No newline at end of file diff --git a/content/docs/evm-l1s/meta.json b/content/docs/evm-l1s/meta.json index e3e7a9bc8da..1f259580aa0 100644 --- a/content/docs/evm-l1s/meta.json +++ b/content/docs/evm-l1s/meta.json @@ -17,6 +17,12 @@ "custom-precompiles/defining-your-precompile", "custom-precompiles/writing-test-cases", "custom-precompiles/executing-test-cases", - "custom-precompiles/deploying-precompile" + "custom-precompiles/deploying-precompile", + "---Validator Manager---", + "validator-manager/contract", + "validator-manager/add-validator", + "validator-manager/remove-validator", + "validator-manager/upgrade", + "validator-manager/custom-validator-manager" ] } \ No newline at end of file diff --git a/content/docs/evm-l1s/validator-manager/add-validator.mdx b/content/docs/evm-l1s/validator-manager/add-validator.mdx new file mode 100644 index 00000000000..5465163b566 --- /dev/null +++ b/content/docs/evm-l1s/validator-manager/add-validator.mdx @@ -0,0 +1,19 @@ +--- +title: Add Validator +description: Learn how to add validators to your Avalanche L1 blockchain. +--- + + +### Register a Validator + +Validator registration is initiated with a call to `initializeValidatorRegistration`. The sender of this transaction is registered as the validator owner. Churn limitations are checked - only a certain (configurable) percentage of the total weight is allowed to be added or removed in a (configurable) period of time. The `ValidatorManager` then constructs a `RegisterL1ValidatorMessage` Warp message to be sent to the P-Chain. Each validator registration request includes all of the information needed to identify the validator and its stake weight, as well as an `expiry` timestamp before which the `RegisterL1ValidatorMessage` must be delivered to the P-Chain. If the validator is not registered on the P-Chain before the `expiry`, then the validator may be removed from the contract state by calling `completeEndValidation`. + +The `RegisterL1ValidatorMessage` is delivered to the P-Chain as the Warp message payload of a `RegisterL1ValidatorTx`. Please see the transaction specification for validity requirements. The P-Chain then signs a `L1ValidatorRegistrationMessage` Warp message indicating that the specified validator was successfully registered on the P-Chain. + +The `L1ValidatorRegistrationMessage` is delivered to the `ValidatorManager` via a call to `completeValidatorRegistration`. For PoS Validator Managers, staking rewards begin accruing at this time. + +### (PoS only) Register a Delegator + +`PoSValidatorManager` supports delegation to an actively staked validator as a way for users to earn staking rewards without having to validate the chain. Delegators pay a configurable percentage fee on any earned staking rewards to the host validator. A delegator may be registered by calling `initializeDelegatorRegistration` and providing an amount to stake. The delegator will be registered as long as churn restrictions are not violated. The delegator is reflected on the P-Chain by adjusting the validator's registered weight via a `SetL1ValidatorWeightTx`. The weight change acknowledgement is delivered to the `PoSValidatorManager` via an `L1ValidatorWeightMessage`, which is provided by calling `completeDelegatorRegistration`. + +The P-Chain is only willing to sign an `L1ValidatorWeightMessage` for an active validator. Once a validator exit has been initiated (via a call to `initializeEndValidation`), the `PoSValidatorManager` must assume that the validator has been deactivated on the P-Chain, and will therefore not sign any further weight updates. Therefore, it is invalid to initiate adding or removing a delegator when the validator is in this state, though it may be valid to complete an already initiated delegator action, depending on the order of delivery to the P-Chain. If the delegator weight change was submitted (and a Warp signature on the acknowledgement retrieved) before the validator was removed, then the delegator action may be completed. Otherwise, the acknowledgement of the validation end must first be delivered before completing the delegator action. diff --git a/content/docs/evm-l1s/validator-manager/contract.mdx b/content/docs/evm-l1s/validator-manager/contract.mdx new file mode 100644 index 00000000000..51301323b06 --- /dev/null +++ b/content/docs/evm-l1s/validator-manager/contract.mdx @@ -0,0 +1,109 @@ +--- +title: Validator Manager Contract +description: Documentation for the Validator Manager used to manage Avalanche L1 validators, as defined in ACP-77. +--- +import { Steps, Step } from 'fumadocs-ui/components/steps'; + +> ValidatorManager +class PoSValidatorManager { + initializeEndValidation() + completeDelegatorRegistration() + initializeEndDelegation() + completeEndDelegation() +} +<> PoSValidatorManager +class ERC20TokenStakingManager { + initializeValidatorRegistration() + initializeDelegatorRegistration() +} +class NativeTokenStakingManager { + initializeValidatorRegistration() payable + initializeDelegatorRegistration() payable +} +class PoAValidatorManager { + initializeValidatorRegistration() + initializeEndValidation() +} + +ValidatorManager <|-- PoSValidatorManager +ValidatorManager <|-- PoAValidatorManager +PoSValidatorManager <|-- ERC20TokenStakingManager +PoSValidatorManager <|-- NativeTokenStakingManager +`} /> + +## Deploying + +Three concrete `ValidatorManager` contracts are provided - `PoAValidatorManager`, `NativeTokenStakingManager`, and `ERC20TokenStakingManager`. `NativeTokenStakingManager` and `ERC20TokenStakingManager` implement `PoSValidatorManager`, which itself implements `ValidatorManager`. These are implemented as [upgradeable](https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable/blob/main/contracts/proxy/utils/Initializable.sol#L56) contracts. There are numerous [guides](https://blog.chain.link/upgradable-smart-contracts/) for deploying upgradeable smart contracts, but the general steps are as follows: + + + +Deploy the implementation contract + + +Deploy the proxy contract + + +Call the implementation contract's `initialize` function + +- Each flavor of `ValidatorManager` requires different settings. For example, `ValidatorManagerSettings` specifies the churn parameters, while `PoSValidatorManagerSettings` specifies the staking and rewards parameters. + + + +Initialize the validator set by calling `initializeValidatorSet` + +- When a Subnet is first created on the P-Chain, it must be explicitly converted to an L1 via [`ConvertSubnetToL1Tx`](https://github.com/avalanche-foundation/ACPs/tree/main/ACPs/77-reinventing-subnets#convertsubnettol1tx). The resulting `SubnetToL1ConversionMessage` Warp [message](https://github.com/avalanche-foundation/ACPs/tree/main/ACPs/77-reinventing-subnets#subnettol1conversionmessage) is provided in the call to `initializeValidatorSet` to specify the starting validator set in the `ValidatorManager`. Regardless of the implementation, these initial validators are treated as PoA and are not eligible for staking rewards. + + + + +### PoAValidatorManager + +Proof-of-Authority validator management is provided via `PoAValidatorManager`, which restricts modification of the validator set to a specified owner address. After deploying `PoAValidatorManager.sol` and a proxy, the `initialize` function takes the owner address, in addition to standard `ValidatorManagerSettings`. + +### PoSValidatorManager + +Proof-of-Stake validator management is provided by the abstract contract `PoSValidatorManager`, which has two concrete implementations: `NativeTokenStakingManager` and `ERC20TokenStakingManager`. In addition to basic validator management provided in `ValidatorManager`, `PoSValidatorManager` supports uptime-based validation rewards, as well as delegation to a chosen validator. This [state transition diagram](./StateTransition.md) illustrates the relationship between validators and delegators. + +The `weightToValueFactor` fields of the `PoSValidatorManagerSettings` passed to `PoSValidatorManager`'s `initialize` function sets the factor used to convert between the weight that the validator is registered with on the P-Chain, and the value transferred to the contract as stake. This involves integer division, which may result in loss of precision. When selecting `weightToValueFactor`, it's important to make the following considerations: + +1. If `weightToValueFactor` is near the denomination of the asset, then staking amounts on the order of 1 unit of the asset may cause the converted weight to round down to 0. This may impose a larger-than-expected minimum stake amount. + - Ex: If USDC (denomination of 6) is used as the staking token and `weightToValueFactor` is 1e9, then any amount less than 1,000 USDC will round down to 0 and therefore be invalid. +2. Staked amounts up to `weightToValueFactor - 1` may be lost in the contract as dust, as the validator's registered weight is used to calculate the original staked amount. + - Ex: `value=1001` and `weightToValueFactor=1e3`. The resulting weight will be `1`. Converting the weight back to a value results in `value=1000`. +3. The validator's weight is represented on the P-Chain as a `uint64`. `PoSValidatorManager` restricts values such that the calculated weight does not exceed the maximum value for that type. + +### NativeTokenStakingManager + +`NativeTokenStakingManager` allows permissionless addition and removal of validators that post the L1's native token as stake. Staking rewards are minted via the Native Minter Precompile, which is configured with a set of addresses with minting privileges. As such, the address that `NativeTokenStakingManager` is deployed to must be added as an admin to the precompile. This can be done by either calling the precompile's `setAdmin` method from an admin address, or setting the address in the Native Minter precompile settings in the chain's genesis (`config.contractNativeMinterConfig.adminAddresses`). There are a couple of methods to get this address: one is to calculate the resulting deployed address based on the deployer's address and account nonce: `keccak256(rlp.encode(address, nonce))`. The second method involves manually placing the `NativeTokenStakingManager` bytecode at a particular address in the genesis, then setting that address as an admin. + +```json +{ + "config" : { + ... + "contractNativeMinterConfig": { + "blockTimestamp": 0, + "adminAddresses": [ + "0xffffffffffffffffffffffffffffffffffffffff" + ] + } + }, + "alloc": { + "0xffffffffffffffffffffffffffffffffffffffff": { + "balance": "0x0", + "code": "", + "nonce": 1 + } + } +} +``` + +## ERC20TokenStakingManager + +`ERC20TokenStakingManager` allows permissionless addition and removal of validators that post an ERC20 token as stake. The ERC20 is specified in the call to `initialize`, and must implement `IERC20Mintable`. Care should be taken to enforce that only authorized users are able to mint the ERC20 staking token. diff --git a/content/docs/evm-l1s/validator-manager/custom-validator-manager.mdx b/content/docs/evm-l1s/validator-manager/custom-validator-manager.mdx new file mode 100644 index 00000000000..1744ab60108 --- /dev/null +++ b/content/docs/evm-l1s/validator-manager/custom-validator-manager.mdx @@ -0,0 +1,93 @@ +--- +title: Customize Validator Manager +description: Learn how to implement a custom Validator Manager on your Avalanche L1 blockchain. +--- + +The Validator Manager contracts provide a framework for managing validators on an Avalanche L1 blockchain, as defined in [ACP-77](https://github.com/avalanche-foundation/ACPs/tree/main/ACPs/77-reinventing-subnets). `ValidatorManager.sol` is the top-level abstract contract that provides basic functionality. Developers can build upon it to implement custom logic for validator management tailored to their specific requirements. + +## Building a Custom Validator Manager + +To implement custom validator management logic, you can create a new contract that inherits from `ValidatorManager` or one of its derived contracts (`PoSValidatorManager`, `PoAValidatorManager`, etc.). By extending these contracts, you can override existing functions or add new ones to introduce your custom logic. + +**Inherit from the Base Contract** + + Decide which base contract suits your needs. If you require Proof-of-Stake functionality, consider inheriting from `PoSValidatorManager`. For Proof-of-Authority, `PoAValidatorManager` might be appropriate. If you need basic functionality, you can inherit directly from `ValidatorManager`. + + ```solidity + pragma solidity ^0.8.0; + + import "./ValidatorManager.sol"; + + contract CustomValidatorManager is ValidatorManager { + // Your custom logic here + } + ``` + +### Override Functions + +Override existing functions to modify their behavior. Ensure that you adhere to the function signatures and access modifiers. + +```solidity +function initializeValidatorRegistration() public override { + // Custom implementation +} +``` +### Add Custom Functions + +Introduce new functions that implement the custom logic required for your blockchain. + +```solidity +function customValidatorLogic(address validator) public { + // Implement custom logic +} +``` +### Modify Access Control + +Adjust access control as needed using modifiers like onlyOwner or by implementing your own access control mechanisms. + +```solidity +modifier onlyValidator() { + require(isValidator(msg.sender), "Not a validator"); + _; +} +``` + +### Integrate with the P-Chain + +Ensure that your custom contract correctly constructs and handles Warp messages for interaction with the P-Chain, following the specifications in ACP-77. + +### Testing + +Thoroughly test your custom Validator Manager contract to ensure it behaves as expected and adheres to the required protocols. + +Example: Custom Reward Logic +Suppose you want to implement a custom reward distribution mechanism. You can create a new contract that inherits from PoSValidatorManager and override the reward calculation functions. + +```solidity + +pragma solidity ^0.8.0; + +import "./PoSValidatorManager.sol"; + +contract CustomRewardValidatorManager is PoSValidatorManager { + function calculateValidatorReward(address validator) internal view override returns (uint256) { + // Implement custom reward calculation logic + return super.calculateValidatorReward(validator) * 2; // Example: double the reward + } + + function calculateDelegatorReward(address delegator) internal view override returns (uint256) { + // Implement custom delegator reward calculation logic + return super.calculateDelegatorReward(delegator) / 2; // Example: halve the reward + } +} +``` + +### Considerations +**Security Audits**: Custom contracts should be audited to ensure security and correctness. + +**Compliance with ACP-77**: Ensure your custom logic complies with the specifications of ACP-77 to maintain compatibility with Avalanche's protocols. + +**Upgradeable Contracts**: If you plan to upgrade your contract in the future, follow best practices for upgradeable contracts. + +### Conclusion +Building on top of `ValidatorManager.sol` allows you to customize validator management to fit the specific needs of your Avalanche L1 blockchain. By extending and modifying the base contracts, you can implement custom staking mechanisms, reward distribution, and access control tailored to your application. \ No newline at end of file diff --git a/content/docs/evm-l1s/validator-manager/remove-validator.mdx b/content/docs/evm-l1s/validator-manager/remove-validator.mdx new file mode 100644 index 00000000000..792460759ca --- /dev/null +++ b/content/docs/evm-l1s/validator-manager/remove-validator.mdx @@ -0,0 +1,42 @@ +--- +title: Remove Validator +description: Learn how to remove validators from your Avalanche L1 blockchain. +--- + +### Remove a Validator + +Validator exit is initiated with a call to `initializeEndValidation` on the `ValidatorManager`. Only the validator owner may initiate exit. For `PoSValidatorManagers` a `ValidationUptimeMessage` Warp message may optionally be provided in order to calculate the staking rewards; otherwise the latest received uptime will be used (see [(PoS only) Submit an Uptime Proof](#pos-only-submit-an-uptime-proof)). This proof may be requested directly from the L1 validators, which will provide it in a `ValidationUptimeMessage` Warp message. If the uptime is not sufficient to earn validation rewards, the call to `initializeEndValidation` will fail. `forceInitializeEndValidation` acts the same as `initializeEndValidation`, but bypasses the uptime-based rewards check. Once `initializeEndValidation` or `forceInitializeEndValidation` is called, staking rewards cease accruing for `PoSValidatorManagers`. + +The `ValidatorManager` constructs an `L1ValidatorWeightMessage` Warp message with the weight set to `0`. This is delivered to the P-Chain as the payload of a `SetL1ValidatorWeightTx`. The P-Chain acknowledges the validator exit by signing an `L1ValidatorRegistrationMessage` with `valid=0`, which is delivered to the `ValidatorManager` by calling `completeEndValidation`. The validation is removed from the contract's state, and for `PoSValidatorManagers`, staking rewards are disbursed and stake is returned. + +#### Disable a Validator Directly on the P-Chain + +ACP-77 also provides a method to disable a validator without interacting with the L1 directly. The P-Chain transaction `DisableL1ValidatorTx` disables the validator on the P-Chain. The disabled validator's weight will still count towards the L1's total weight. + +Disabled L1 validators can re-activate at any time by increasing their balance with an `IncreaseBalanceTx`. Anyone can call `IncreaseBalanceTx` for any validator on the P-Chain. A disabled validator can only be completely and permanently removed from the validator set by a call to `initializeEndValidation`. + + +### (PoS only) Remove a Delegator + +Delegators removal may be initiated by calling `initializeEndDelegation`, as long as churn restrictions are not violated. Similar to `initializeEndValidation`, an uptime proof may be provided to be used to determine delegator rewards eligibility. If no proof is provided, the latest known uptime will be used (see [(PoS only) Submit an Uptime Proof](#pos-only-submit-an-uptime-proof)). The validator's weight is updated on the P-Chain by the same mechanism used to register a delegator. The `L1ValidatorWeightMessage` from the P-Chain is delivered to the `PoSValidatorManager` in the call to `completeEndDelegation`. + +Either the delegator owner or the validator owner may initiate removing a delegator. This is to prevent the validator from being unable to remove itself due to churn limitations if it has too high a proportion of the Subnet's total weight due to delegator additions. The validator owner may only remove Delegators after the minimum stake duration has elapsed. + + +### (PoS only) Collect Staking Rewards + +#### Submit an Uptime Proof + +The rewards calculator is a function of uptime seconds since the validator's start time. In addition to doing so in the calls to `initializeEndValidation` and `initializeEndDelegation` as described above, uptime proofs may also be supplied by calling `submitUptimeProof`. Unlike `initializeEndValidation` and `initializeEndDelegation`, `submitUptimeProof` may be called by anyone, decreasing the likelihood of a validation or delegation not being able to claim rewards that it deserved based on its actual uptime. + +#### Validation Rewards + +Validation rewards are distributed in the call to `completeEndValidation`. + +#### Delegation Rewards + +Delegation rewards are distributed in the call to `completeEndDelegation`. + +#### Delegation Fees + +Delegation fees owed to validators are _not_ distributed when the validation ends as to bound the amount of gas consumed in the call to `completeEndValidation`. Instead, `claimDelegationFees` may be called after the validation is completed. diff --git a/content/docs/evm-l1s/validator-manager/upgrade.mdx b/content/docs/evm-l1s/validator-manager/upgrade.mdx new file mode 100644 index 00000000000..95f948d6648 --- /dev/null +++ b/content/docs/evm-l1s/validator-manager/upgrade.mdx @@ -0,0 +1,8 @@ +--- +title: Upgrade Validator Manager +description: Learn how to upgrade the Validator Manager on your Avalanche L1 blockchain from PoA to PoS. +--- + +## Convert PoA to PoS + +A `PoAValidatorManager` can later be converted to a `PoSValidatorManager` by upgrading the implementation contract pointed to by the proxy. After performing the upgrade, the `PoSValidatorManager` contract should be initialized by calling `initialize` as described above. The validator set contained in the `PoAValidatorManager` will be tracked by the `PoSValidatorManager` after the upgrade, but these validators will neither be eligible to stake and earn staking rewards, nor support delegation. diff --git a/mdx-components.tsx b/mdx-components.tsx index 4d9f7357116..86e04e21996 100644 --- a/mdx-components.tsx +++ b/mdx-components.tsx @@ -17,6 +17,11 @@ import YouTube from "@/components/youtube"; import Gallery from "@/components/gallery"; import { cn } from "./utils/cn"; import { BadgeCheck } from "lucide-react"; +import dynamic from "next/dynamic"; + +const Mermaid = dynamic(() => import("@/components/mermaid"), { + ssr: false, +}); export function useMDXComponents(components: MDXComponents): MDXComponents { return { @@ -42,6 +47,7 @@ export function useMDXComponents(components: MDXComponents): MDXComponents { Accordions, YouTube, Gallery, + Mermaid, InstallTabs: ({ items, children, diff --git a/package.json b/package.json index cb3f263e638..8df7966124a 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,7 @@ "geist": "^1.3.1", "katex": "^0.16.11", "lucide-react": "^0.435.0", + "mermaid": "^11.4.0", "next": "^14.2.4", "posthog-js": "^1.166.1", "react": "^18.3.1",