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

ICM & L1 Tokenomics: Add ACP-77 Registering ValidatorManager Section #192

Open
wants to merge 1 commit into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all 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
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
---
title: Registering Validators
description: Learn how validators are registered on the Avalanche network post-etna.
updated: 2024-10-14
authors: [owenwahlgren]
icon: Terminal
---

> How does it all work? I mean *actually* work.

### Sending Warp Messages

On the EVM, a warp message is simply an event log with `messageID = sha256(messagePayload)` Nothing is ever actually “sent” anywhere, which trips people up when first learning how Warp Messaging works.

The log event emitted is from the Warp precompile address (`0x0200000000000000000000000000000000000005`) The topic of the message will be `0x56600c567728a800c0aa927500f831cb451df66a7af570eb4df4dfbf4674887d` which is the output of
```bash
cast keccak "SendWarpMessage(address,bytes32,bytes)"
```

To find a specific warp messageID, one must know the block number or block hash to retrieve the logs, or be monitoring all events, looking for and indexing messages.

When a block is [accepted](https://github.com/ava-labs/coreth/blob/7242ae2d235593d2bb14e2496b6ed102aab14c3d/precompile/contracts/warp/config.go#L130) by the network, a precompile hook is called, and the EVM saves the warp message to a local database. The validator node will then be willing to sign any messages in that database upon request, typically via an `AppRequest` message over the p2p network.

### Receiving Warp Messages

On the EVM, any smart contract can call the precompile

which returns the warp message and verifies the attached signature. But what is `index`? Where does it get the warp message from? The answer is, you! When you send a tx to any contract that eventually calls `getVerifiedWarpMessage`, you **must** pass in the signed warp message as a “predicate”. It’s called an AccessList, and typically this is an optional field that can be used by the EVM to pre-fetch storage slots that will be accessed, for efficiency and reducing gas costs. But Warp cleverly (ab)uses this feature of the EVM to verify the signature of the warp message, and then make the warp message available during the transactions lifetime, by calling `getVerifiedWarpMessage`. You can pass multiple warp messages which is where the `index` comes in. (All of this is abstracted away by Teleporter for EVM to EVM comms)

Details: [Warp README](https://github.com/ava-labs/coreth/blob/7242ae2d235593d2bb14e2496b6ed102aab14c3d/precompile/contracts/warp/README.md)

### Teleporter

[Teleporter](https://github.com/ava-labs/teleporter) is a set of smart contracts that implements a rich messaging protocol on top of the Warp Messaging primitive, allowing for verified message passing between EVM chains.

<Callout>
**Only (currently) supports EVM→EVM comms. Not used for EVM→P-Chain**
</Callout>

### Relayer

[Relayer](https://github.com/ava-labs/awm-relayer/blob/main/relayer/README.md) is an off-chain program that listens for warp messages on a source chain, and delivers them to a destination chain. Messages can choose to be relayed by anyone, or by only privileged relayers. Messages can also pay fees to incentivize relayers and cover gas costs.

## Registering ValidatorManager

ACP-77 introduces L1-only Validators, that stake no AVAX, and do not validate the X/C chains. They must connect to the Primary Network and track the P-Chain, and also validate their own chain, and pay a small continuous fee in AVAX to the network.

User submits EVM tx to smart contract [NativeTokenStakingManager.sol](https://github.com/ava-labs/teleporter/blob/acp-77-updates/contracts/validator-manager/NativeTokenStakingManager.sol)

```solidity
function initializeValidatorRegistration(
ValidatorRegistrationInput calldata registrationInput,
uint16 delegationFeeBips,
uint64 minStakeDuration) payable returns (bytes32 validationID)
```
Notably, this function will:

- Lock the native tokens in the contract
- `validationID`: bytes32 hash of an encoded `RegisterSubnetValidatorMessage`
- `messageID`: bytes32 hash of the warp message
- Store some data in the contract:


```solidity
$._pendingRegisterValidationMessages[validationID] = registerSubnetValidatorMessage;
$._registeredValidators[input.nodeID] = validationID;
bytes32 messageID = WARP_MESSENGER.sendWarpMessage(registerSubnetValidatorMessage);
$._validationPeriods[validationID] = Validator({
status: ValidatorStatus.PendingAdded,
nodeID: input.nodeID,
startingWeight: weight,
messageNonce: 0,
weight: weight,
startedAt: 0, // The validation period only starts once the registration is acknowledged.
endedAt: 0
});
```

- Emit `ValidationPeriodCreated(validationID, nodeID, messageID, weight, registrationExpiry)`
- The `sendWarpMessage` precompile will emit a log with `sender`, `messageID`, `message`

At this point, we have submitted a transaction on the blockchain (C-Chain or L1, wherever the staking contract is deployed). All that has happened is we have stored some state in our contract and emitted some events. No changes to validators has occurred.

<Callout>
Nothing else will happen UNLESS an off-chain actor continues the process.
</Callout>


In our case, someone must be running a relayer that is configured with our staking contract as the source and the P-Chain as the destination.

<Callout>
At the moment, the relayer does not support the P-Chain, so messages must be signed and sent manually. The relayer will be updated soon to support the P-Chain.
</Callout>


So, to sign and send the message ourselves, we can use the Signature Aggregator server in the relayer project. This will use the p2p layer of the Avalanche network to connect to validators and send `AppRequest` messages to request each validator to sign a message.
```bash
curl --location 'http://localhost:8080/aggregate-signatures' \
--json '{"message": "<hex encoded unsigned message bytes retrieved from the logs>"}'
```
This will give us an aggregated BLS signature from at least 67% of the stake weighted validator set. Now we need to `POST` this to the P-Chain via the `RegisterSubnetValidatorTx` tx.

The P-Chain, if it accepts the tx, will “send” a Warp Message `SubnetValidatorRegistration` which acknowledges the change. Note that nothing is actually “sent”, instead the validator node will be willing to sign the unsigned message upon request.

So the next step is to use the Signature Aggregator service again, and provide the unsigned `SubnetValidatorRegistration` warp message, which the service will then ask validators to sign.

<Callout>
If your Validator Manager contract is on your L1 (instead of C-Chain), as an optimization, you only need to ask 67% of the stake weighted validators of your L1, not the entire P-Chain validator set.
</Callout>


Once an aggregate BLS signature is created, we need to submit the signed message to the C-Chain (or L1) by calling a function on the `ValidatorManager` contract.

```solidity
function completeValidatorRegistration(uint32 messageIndex)
```

Remember, this involves calling the function via `eth_sendTransaction` and encoding the signed warp message into the `AccessList`. (If you encode an array of 1 warp message, then `messageIndex` would be 0)

The function will adjust the state in the contract, including these bits:
```solidity
delete $._pendingRegisterValidationMessages[validationID];
$._validationPeriods[validationID].status = ValidatorStatus.Active;
$._validationPeriods[validationID].startedAt = uint64(block.timestamp);
```
And that’s it!

<Callout>
This doc is a WIP and we will eventually cover all the flows and add some diagrams.
</Callout>
4 changes: 3 additions & 1 deletion content/course/interchain-messaging/meta.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@
"---Restricting-the-relayer---",
"...11-restricting-the-relayer",
"---Incentivizing a Relayer---",
"...12-incentivizing-a-relayer"
"...12-incentivizing-a-relayer",
"---P-Chain Messaging---",
"...13-registering-validator"
]
}
130 changes: 130 additions & 0 deletions content/course/l1-tokenomics/04-staking/04-register-validators.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
---
title: Registering PoS Validators
description: Learn how validators are registered on the Avalanche network post-etna.
updated: 2024-10-14
authors: [owenwahlgren]
icon: Terminal
---

> How does it all work? I mean *actually* work.

### Sending Warp Messages

On the EVM, a warp message is simply an event log with `messageID = sha256(messagePayload)` Nothing is ever actually “sent” anywhere, which trips people up when first learning how Warp Messaging works.

The log event emitted is from the Warp precompile address (`0x0200000000000000000000000000000000000005`) The topic of the message will be `0x56600c567728a800c0aa927500f831cb451df66a7af570eb4df4dfbf4674887d` which is the output of
```bash
cast keccak "SendWarpMessage(address,bytes32,bytes)"
```

To find a specific warp messageID, one must know the block number or block hash to retrieve the logs, or be monitoring all events, looking for and indexing messages.

When a block is [accepted](https://github.com/ava-labs/coreth/blob/7242ae2d235593d2bb14e2496b6ed102aab14c3d/precompile/contracts/warp/config.go#L130) by the network, a precompile hook is called, and the EVM saves the warp message to a local database. The validator node will then be willing to sign any messages in that database upon request, typically via an `AppRequest` message over the p2p network.

### Receiving Warp Messages

On the EVM, any smart contract can call the precompile

which returns the warp message and verifies the attached signature. But what is `index`? Where does it get the warp message from? The answer is, you! When you send a tx to any contract that eventually calls `getVerifiedWarpMessage`, you **must** pass in the signed warp message as a “predicate”. It’s called an AccessList, and typically this is an optional field that can be used by the EVM to pre-fetch storage slots that will be accessed, for efficiency and reducing gas costs. But Warp cleverly (ab)uses this feature of the EVM to verify the signature of the warp message, and then make the warp message available during the transactions lifetime, by calling `getVerifiedWarpMessage`. You can pass multiple warp messages which is where the `index` comes in. (All of this is abstracted away by Teleporter for EVM to EVM comms)

Details: [Warp README](https://github.com/ava-labs/coreth/blob/7242ae2d235593d2bb14e2496b6ed102aab14c3d/precompile/contracts/warp/README.md)

### Teleporter

[Teleporter](https://github.com/ava-labs/teleporter) is a set of smart contracts that implements a rich messaging protocol on top of the Warp Messaging primitive, allowing for verified message passing between EVM chains.

<Callout>
**Only (currently) supports EVM→EVM comms. Not used for EVM→P-Chain**
</Callout>

### Relayer

[Relayer](https://github.com/ava-labs/awm-relayer/blob/main/relayer/README.md) is an off-chain program that listens for warp messages on a source chain, and delivers them to a destination chain. Messages can choose to be relayed by anyone, or by only privileged relayers. Messages can also pay fees to incentivize relayers and cover gas costs.

## Registering ValidatorManager

ACP-77 introduces L1-only Validators, that stake no AVAX, and do not validate the X/C chains. They must connect to the Primary Network and track the P-Chain, and also validate their own chain, and pay a small continuous fee in AVAX to the network.

User submits EVM tx to smart contract [NativeTokenStakingManager.sol](https://github.com/ava-labs/teleporter/blob/acp-77-updates/contracts/validator-manager/NativeTokenStakingManager.sol)

```solidity
function initializeValidatorRegistration(
ValidatorRegistrationInput calldata registrationInput,
uint16 delegationFeeBips,
uint64 minStakeDuration) payable returns (bytes32 validationID)
```
Notably, this function will:

- Lock the native tokens in the contract
- `validationID`: bytes32 hash of an encoded `RegisterSubnetValidatorMessage`
- `messageID`: bytes32 hash of the warp message
- Store some data in the contract:


```solidity
$._pendingRegisterValidationMessages[validationID] = registerSubnetValidatorMessage;
$._registeredValidators[input.nodeID] = validationID;
bytes32 messageID = WARP_MESSENGER.sendWarpMessage(registerSubnetValidatorMessage);
$._validationPeriods[validationID] = Validator({
status: ValidatorStatus.PendingAdded,
nodeID: input.nodeID,
startingWeight: weight,
messageNonce: 0,
weight: weight,
startedAt: 0, // The validation period only starts once the registration is acknowledged.
endedAt: 0
});
```

- Emit `ValidationPeriodCreated(validationID, nodeID, messageID, weight, registrationExpiry)`
- The `sendWarpMessage` precompile will emit a log with `sender`, `messageID`, `message`

At this point, we have submitted a transaction on the blockchain (C-Chain or L1, wherever the staking contract is deployed). All that has happened is we have stored some state in our contract and emitted some events. No changes to validators has occurred.

<Callout>
Nothing else will happen UNLESS an off-chain actor continues the process.
</Callout>


In our case, someone must be running a relayer that is configured with our staking contract as the source and the P-Chain as the destination.

<Callout>
At the moment, the relayer does not support the P-Chain, so messages must be signed and sent manually. The relayer will be updated soon to support the P-Chain.
</Callout>


So, to sign and send the message ourselves, we can use the Signature Aggregator server in the relayer project. This will use the p2p layer of the Avalanche network to connect to validators and send `AppRequest` messages to request each validator to sign a message.
```bash
curl --location 'http://localhost:8080/aggregate-signatures' \
--json '{"message": "<hex encoded unsigned message bytes retrieved from the logs>"}'
```
This will give us an aggregated BLS signature from at least 67% of the stake weighted validator set. Now we need to `POST` this to the P-Chain via the `RegisterSubnetValidatorTx` tx.

The P-Chain, if it accepts the tx, will “send” a Warp Message `SubnetValidatorRegistration` which acknowledges the change. Note that nothing is actually “sent”, instead the validator node will be willing to sign the unsigned message upon request.

So the next step is to use the Signature Aggregator service again, and provide the unsigned `SubnetValidatorRegistration` warp message, which the service will then ask validators to sign.

<Callout>
If your Validator Manager contract is on your L1 (instead of C-Chain), as an optimization, you only need to ask 67% of the stake weighted validators of your L1, not the entire P-Chain validator set.
</Callout>


Once an aggregate BLS signature is created, we need to submit the signed message to the C-Chain (or L1) by calling a function on the `ValidatorManager` contract.

```solidity
function completeValidatorRegistration(uint32 messageIndex)
```

Remember, this involves calling the function via `eth_sendTransaction` and encoding the signed warp message into the `AccessList`. (If you encode an array of 1 warp message, then `messageIndex` would be 0)

The function will adjust the state in the contract, including these bits:
```solidity
delete $._pendingRegisterValidationMessages[validationID];
$._validationPeriods[validationID].status = ValidatorStatus.Active;
$._validationPeriods[validationID].startedAt = uint64(block.timestamp);
```
And that’s it!

<Callout>
This doc is a WIP and we will eventually cover all the flows and add some diagrams.
</Callout>
Loading