Skip to content

Commit

Permalink
Merge branch 'main' into chore/add-wormchain-ibc-receiver-rust-tests
Browse files Browse the repository at this point in the history
  • Loading branch information
kakucodes authored Oct 10, 2024
2 parents 0d72add + 4894961 commit 078742f
Show file tree
Hide file tree
Showing 10 changed files with 199 additions and 12 deletions.
4 changes: 2 additions & 2 deletions .github/CODEOWNERS
Validating CODEOWNERS rules …
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,14 @@
/lp_ui/ @evan-gray @kev1n-peters
/relayer/generic_relayer @nonergodic @gator-boi
/scripts/ @evan-gray @kcsongor
/sdk/ @bruce-riley @evan-gray @kev1n-peters @SEJeff
/sdk/ @bruce-riley @evan-gray @kev1n-peters @panoel @SEJeff
/sdk/js-proto-node/ @evan-gray @kev1n-peters
/sdk/js-proto-web/ @evan-gray @kev1n-peters
/sdk/js-query/ @evan-gray @kev1n-peters @bruce-riley
/sdk/js-wasm/ @evan-gray @kev1n-peters
/sdk/js/ @evan-gray @kev1n-peters @panoel
/sdk/rust/ @a5-pickle
/sdk/vaa/ @bruce-riley @SEJeff
/sdk/vaa/ @bruce-riley @evan-gray @panoel @SEJeff
/spydk/ @evan-gray
/testing/ @a5-pickle @evan-gray
/wormchain/contracts/tools/ @evan-gray @kev1n-peters @panoel
Expand Down
132 changes: 132 additions & 0 deletions docs/guardian.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
# Wormhole Guardian Node

The guardian node software is responsible for monitoring Wormhole connected chains, reporting on-chain observations,
aggregating those observations with those reported by other guardians until quorum is reached, and publishing signed VAAs.

For details on how to set up and run a guardian node see [operations.md](operations.md).

## Components

### Watchers

The watchers are responsible for monitoring the connected chains for Wormhole messages. They listen for messages published
by the Wormhole core contract and post them to the processor. They also handle requests to re-observe messages that may have
been missed. Additionally they monitor the latest block posted on chain, which is published in gossip heartbeat messages.

Each watcher listens to one chain, and there are a number of different watcher types:

1. EVM - the EVM watcher connects to one of the EVM chains using the `go-ethereum` library. It uses the EVM subscription
mechanism to listen for new logs from the Wormhole core contract as well as the latest blocks. Additionally, it polls for
new finalized and safe blocks (as supported on each chain).

2. Solana - the Solana watcher monitors a Solana runtime based chain, namely Solana and Pyth. It uses the `solana-go` library.
For each chain, there are two watchers, one listening for new confirmed slots, and one listening for new finalized slots.
For Pyth, it uses a web socket subscription to listen for messages. For Solana, it polls for slots.

3. Cosmwasm - the Cosmwasm watcher connects to the various Cosmwasm chains (one chain per watcher instance). It subscribes
for events from the Wormhole core contract and polls for new blocks.

4. Sui / Aptos / Algorand / Near - there are bespoke watchers for each of these chains that listen / poll for messages from
the Wormhole core contract and poll for new blocks.

5. IBC - there is a Cosmwasm based watcher that listens to the IBC relayer contract on Gateway and publishes messages. The
IBC watcher is different from the others in that a single watcher instance monitors _all_ IBC connected chains.

### P2P

The p2p package is responsible for listening to messages from and publishing messages to the libp2p based gossip network.
All of the guardians communicate over gossip. Most importantly, they publish observations they make and signed VAAs
for all observations that they observe reaching quorum. Additionally, they routinely publish heartbeats and various other
status events (such as from the governor).

The p2p package primarily posts events to and receives events from the processor package using golang channels.

The p2p package also maintains a separate pair of topics used for receiving and sending Wormhole Queries messages.
The guardian joins both of these topics, but it only subscribes to the request topic. This means a given guardian
does not see the Queries responses from other guardians. The guardian also applies libp2p filters so that it only
processes requests from a select set of peers.

### Processor

The processor takes messages observed from the watchers and observations gossipped by other guardians. It aggregates these
until an observation is seen by a quorum of guardians, including itself. It then publishes the signed VAA. The processor
stores signed VAAs in an on-disk badgerDB instance. The processor also interfaces with the governor and accountant packages
before publishing an observation.

### Governor

The governor is responsible for verifying that a given token bridge transfer will not exceed a daily limit for the given
chain and is not too large. For a detailed descriptions of the governor, see [governor.md](governor.md).

### Accountant

The accountant package is responsible for interfacing with the accountant contracts on Gateway to verify that a given
transfer will not exceed the available notional value for a chain. The accountant package interfaces with two contracts.
The accountant contract is used to monitor Token Bridge transfers and the NTT-accountant is used to monitor NTT transfers.

### Query Support

The query package is used to process Wormhole Queries (also known as CCQ, which stands for Cross Chain Queries) requests,
forwarding them to the appropriate watcher for processing, and posting the responses. It receives requests and publishes
responses over a separate set of P2P topics, via the p2p package.

Queries are currently supported on EVM and Solana.

### Admin Interface

The guardian supports a variety of admin commands to do things like request re-observation or sign a given payload using its key.

### Public RPC Endpoint

Certain guardians may be configured as a public RPC endpoint. If this feature is enabled, the guardian will listen for https requests
for things like the status of a given VAA.

For a list of the guardian public RPC endpoints see `PublicRPCEndpoints` in [sdk/mainnet_consts.go](../sdk/mainnet_consts.go)

## Observation Lifecycle

An observation transitions through the following steps:

1. The watcher observes a message published by the Wormhole core contract.

2. On certain chains that do not have instant finality, the watcher waits until the block in which the message was
published reaches the appropriate state, such as the block is finalized, safe, or the integrator requested instant
finality.
3. When the watcher determines the block containing the message has reached the designated finality, it posts it to the
processor via a golang channel.

4. The processor performs integrity checks on the message (via the governor and accountant). Either of these
components may cause the observation to be delayed (the governor up to twenty-four hours, the accountant until
a quorum of guardians report the observation).

5. Once the message clears the integrity checks, the processor signs the observation using the guardian key and
posts it to an internal golang channel for batch publishing.

6. The processor batches observations, waiting no more than a second before publishing a batch. The batches are posted
to the p2p package for publishing as a signed observation batch. The processor also allows for certain observations
(specifically Pyth messages) to be published without batching. These are published immediately with a batch size of one.

7. The p2p package posts the signed observation batches to gossip.

8. Other guardians receive the observation. Each guardian aggregates the observation until it reaches quorum and
it has made the observation itself.

9. Once an observation reaches quorum, the processor generates a VAA and posts it to the p2p package for publishing
as a signed VAA with quorum.

10. The p2p package posts the signed VAA to gossip.

## Reobservation Requests

The guardian supports requesting that a missed observation be reobserved. These requests can originate from three sources:

1. From the guardian operator via an admin command.

2. From another guardian via gossip message.

3. Generated internally from the processor or accountant packages.

An observation request contains the chain ID and the transaction ID of the missed observation. The request is forwarded
to the watcher, based on the chain ID. The watcher queries for the transaction using the transaction ID and then publishes
the reobserved message to the processor. Note that there is a throttling mechanism in place to avoid requesting reobservation
of the same transaction too frequently.
5 changes: 3 additions & 2 deletions ethereum/anvil_fork
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@ set -euo pipefail
# This script forks a chain using anvil with the mnemonic that is used in the
# testing environment.

if [ -z "$1" ]; then
CHAIN_NAME="${1:-}"

if [ -z "$CHAIN_NAME" ]; then
echo "Usage: $0 <chain name>" >&2
exit 1
fi

CHAIN_NAME="$1"

DOCKER_ARGS="-p 8545:8545" ./foundry anvil --host 0.0.0.0 --base-fee 0 --fork-url $(worm info rpc mainnet $CHAIN_NAME) --mnemonic "myth like bonus scare over problem client lizard pioneer submit female collect"
22 changes: 18 additions & 4 deletions ethereum/sh/upgrade.sh
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,28 @@

set -euo pipefail

network=$1
module=$2
chain=$3
network="${1:-}"
module="${2:-}"
chain="${3:-}"

if [ -z "$network" ] || [ -z "$module" ] || [ -z "$chain" ]; then
echo "Usage: MNEMONIC=... $0 <network> <module> <chain name>" >&2
exit 1
fi

if [ -z "${MNEMONIC:-}" ]; then
echo "MNEMONIC unset"
exit 1
fi

secret=$MNEMONIC
guardian_secret=""

if [ "$network" = testnet ]; then
if [ -z "${GUARDIAN_MNEMONIC:-}" ]; then
echo "GUARDIAN_MNEMONIC unset"
exit 1
fi
guardian_secret=$GUARDIAN_MNEMONIC
fi

Expand Down Expand Up @@ -114,4 +128,4 @@ if [ "$network" = testnet ]; then
worm submit $(worm generate upgrade -c "$chain" -a "$new_implementation" -m "$module" -g "$guardian_secret") -n "$network"
else
echo "../scripts/contract-upgrade-governance.sh -c $chain -m $verify_module -a $new_implementation"
fi
fi
2 changes: 1 addition & 1 deletion ethereum/sh/upgrade_all_testnet.sh
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ set -uo pipefail
network=testnet

for module in ${MODULES[@]}; do
for chain in ${chains[@]}; do
for chain in ${CHAINS[@]}; do
echo "Upgrading ${chain} ${module} ********************************************************************"
./sh/upgrade.sh "$network" "$module" "$chain"
done
Expand Down
39 changes: 39 additions & 0 deletions node/hack/governor/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,29 @@ import { arrayify, zeroPad } from "ethers/lib/utils";
const MinNotional = 0;
// Price change tolerance in %. Fallback to 30%
const PriceDeltaTolerance = process.env.PRICE_TOLERANCE ? Math.min(100, Math.max(0, parseInt(process.env.PRICE_TOLERANCE))) : 30;
// The percentage by which the price deviates from $1 to be considered depegged
const usdDepegPercentage = process.env.DEPEG_PERCENTAGE ? Math.min(100, Math.max(0, parseInt(process.env.DEPEG_PERCENTAGE))) : 10;
const usdPeggedStablecoins = [
"USD", // Matches with USDT, USDC, BUSD, etc.
"PAX", // Pax Dollar
"DAI", // Dai
"RSV", // Reserve
"VAI", // Vai
"FRAX", // Frax
"FEI", // Fei
];
const expectedUSDDepeggs = [
"2-00000000000000000000000045804880de22913dafe09f4980848ece6ecbaf78-PAXG", // This is PaxGold and not pegged to $1
"2-000000000000000000000000d13cfd3133239a3c73a9e535a5c4dadee36b395c-VAI", // This is Vaiot, not the VAI stablecoin
"5-000000000000000000000000ee327f889d5947c1dc1934bb208a1e792f953e96-frxETH", // Frax ETH
"23-0000000000000000000000009d2f299715d94d8a7e6f5eaa8e654e8c74a988a7-FXS", // Frax Share
"2-0000000000000000000000003432b6a60d23ca0dfca7761b7ab56459d9c964d0-FXS", // Frax Share
"23-00000000000000000000000051318b7d00db7acc4026c88c3952b66278b6a67f-PLS", // Plutus DAO
"3-0100000000000000000000000000000000000000000000000000000075757364-UST", // Terra USD
"2-000000000000000000000000dfdb7f72c1f195c5951a234e8db9806eb0635346-NFD", // Feisty Doge NFT
"2-00000000000000000000000000c5ca160a968f47e7272a0cfcda36428f386cb6-USDEBT", // US Debt Meme coin
"4-00000000000000000000000011a38e06699b238d6d9a0c7a01f3ac63a07ad318-USDFI", // USDFI is a protocol, not a stablecoin
]

const axios = require("axios");
const fs = require("fs");
Expand Down Expand Up @@ -54,6 +77,7 @@ if (fs.existsSync(IncludeFileName)) {
var existingTokenPrices = {};
var existingTokenKeys: string[] = [];
var newTokenKeys = {};
var depeggedUSDStablecoins = [];

fs.readFile("../../pkg/governor/generated_mainnet_tokens.go", "utf8", function(_, doc) {
var matches = doc.matchAll(/{chain: (?<chain>[0-9]+).+addr: "(?<addr>[0-9a-fA-F]+)".*symbol: "(?<symbol>.*)", coin.*price: (?<price>.*)}.*\n/g);
Expand Down Expand Up @@ -174,6 +198,18 @@ axios
}
}

// This token looks like a USD stablecoin
if (usdPeggedStablecoins.findIndex(element => data.Symbol.toLowerCase().includes(element.toLowerCase()) || data.CoinGeckoId.toLowerCase().includes(element.toLowerCase())) != -1 ) {
// The token price has deviated significantly from $1
if (data.TokenPrice > 1 * ((100 + usdDepegPercentage) / 100) || data.TokenPrice < 1 * ((100 - usdDepegPercentage) / 100)) {
var uniqueIdentifier = chain + "-" + wormholeAddr + "-" + data.Symbol;
// Skip tokens that are not expected to be pegged to $1
if (!expectedUSDDepeggs.includes(uniqueIdentifier)) {
depeggedUSDStablecoins.push(uniqueIdentifier + " = " + data.TokenPrice);
}
}
}

// This is a new token
if (existingTokenPrices[chain] == undefined || existingTokenPrices[chain][wormholeAddr] == undefined) {
addedTokens.push(chain + "-" + wormholeAddr + "-" + data.Symbol);
Expand Down Expand Up @@ -268,6 +304,9 @@ axios
changedContent += "\n\nTokens with invalid symbols = " + failedInputValidationTokens.length + ":\n<WH_chain_id>-<WH_token_addr>-<token_symbol>\n\n";
changedContent += JSON.stringify(failedInputValidationTokens, null, 1);

changedContent += "\n\nPotentially depegged USD stablecoins (>" + usdDepegPercentage + "%) = " + depeggedUSDStablecoins.length + ":\n<WH_chain_id>-<WH_token_addr>-<token_symbol> = <token_price>\n\n";
changedContent += JSON.stringify(depeggedUSDStablecoins, null, 1);

changedContent += "\n\nTokens with significant price drops (>" + PriceDeltaTolerance + "%) = " + significantPriceChanges.length + ":\n\n"
changedContent += JSON.stringify(significantPriceChanges, null, 1);
changedContent += "\n```";
Expand Down
2 changes: 1 addition & 1 deletion node/pkg/p2p/gossip_cutover.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import (
)

// The format of this time is very picky. Please use the exact format specified by cutOverFmtStr!
const mainnetCutOverTimeStr = ""
const mainnetCutOverTimeStr = "2024-10-29T09:00:00-0500"
const testnetCutOverTimeStr = "2024-09-24T09:00:00-0500"
const devnetCutOverTimeStr = "2024-08-01T00:00:00-0500"
const cutOverFmtStr = "2006-01-02T15:04:05-0700"
Expand Down
2 changes: 1 addition & 1 deletion node/pkg/processor/cutover.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import (
)

// The format of this time is very picky. Please use the exact format specified by cutOverFmtStr!
const mainnetCutOverTimeStr = ""
const mainnetCutOverTimeStr = "2024-10-29T09:00:00-0500"
const testnetCutOverTimeStr = "2024-09-24T09:00:00-0500"
const devnetCutOverTimeStr = "2024-08-01T00:00:00-0500"
const cutOverFmtStr = "2006-01-02T15:04:05-0700"
Expand Down
1 change: 1 addition & 0 deletions sdk/mainnet_consts.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ var PublicRPCEndpoints = []string{
"https://wormhole-v2-mainnet-api.mcf.rocks",
"https://wormhole-v2-mainnet-api.chainlayer.network",
"https://wormhole-v2-mainnet-api.staking.fund",
"https://guardian.mainnet.xlabs.xyz",
}

type (
Expand Down
2 changes: 1 addition & 1 deletion whitepapers/0013_ccq.md
Original file line number Diff line number Diff line change
Expand Up @@ -301,7 +301,7 @@ Currently the supported query types on EVM are `eth_call`, `eth_call_by_timestam

#### Solana Queries

Currently the only supported query type on Solana is `sol_account`.
Currently the supported query types on Solana are `sol_account` and `sol_pda`.

1. sol_account (query type 4) - this query is used to read data for one or more accounts on Solana.

Expand Down

0 comments on commit 078742f

Please sign in to comment.