Преглед на файлове

Guibescos/executor cli (#309)

* Add tests to CI

* Fix yaml format

* Update pythnet address

* Checkpoint

* Format

* Reorder args

* Update executor contract

* Checkpoint

* Deployment address and fix deser bug

* Cli cleanup

* Format

* Get wormhole sdk from git

* Format

* Fix some bugs

* Non-emtpy lib
guibescos преди 3 години
родител
ревизия
5214d185e8

+ 1 - 1
.pre-commit-config.yaml

@@ -12,7 +12,7 @@ repos:
       - id: cargo-fmt-executor
         name: Cargo format executor
         language: "rust"
-        entry: cargo +nightly fmt --manifest-path ./pythnet/remote-executor/Cargo.toml
+        entry: cargo +nightly fmt --manifest-path ./pythnet/remote-executor/Cargo.toml --all
         pass_filenames: false
       - id: cargo-clippy-executor
         name: Cargo clippy executor

+ 126 - 6
pythnet/remote-executor/Cargo.lock

@@ -200,6 +200,23 @@ dependencies = [
  "syn 1.0.100",
 ]
 
+[[package]]
+name = "anchor-client"
+version = "0.25.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ee0e630f9310a0134c92df4458890a0f9c5b662d69c305690af1c17f5cd0b3ba"
+dependencies = [
+ "anchor-lang",
+ "anyhow",
+ "regex",
+ "serde",
+ "solana-account-decoder",
+ "solana-client",
+ "solana-sdk",
+ "thiserror",
+ "url",
+]
+
 [[package]]
 name = "anchor-derive-accounts"
 version = "0.25.0"
@@ -717,12 +734,51 @@ dependencies = [
  "ansi_term",
  "atty",
  "bitflags",
- "strsim",
- "textwrap",
+ "strsim 0.8.0",
+ "textwrap 0.11.0",
  "unicode-width",
  "vec_map",
 ]
 
+[[package]]
+name = "clap"
+version = "3.2.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "86447ad904c7fb335a790c9d7fe3d0d971dc523b8ccd1561a520de9a85302750"
+dependencies = [
+ "atty",
+ "bitflags",
+ "clap_derive",
+ "clap_lex",
+ "indexmap",
+ "once_cell",
+ "strsim 0.10.0",
+ "termcolor",
+ "textwrap 0.15.1",
+]
+
+[[package]]
+name = "clap_derive"
+version = "3.2.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ea0c8bce528c4be4da13ea6fead8965e95b6073585a2f05204bd8f4119f82a65"
+dependencies = [
+ "heck 0.4.0",
+ "proc-macro-error",
+ "proc-macro2 1.0.43",
+ "quote 1.0.21",
+ "syn 1.0.100",
+]
+
+[[package]]
+name = "clap_lex"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5"
+dependencies = [
+ "os_str_bytes",
+]
+
 [[package]]
 name = "combine"
 version = "3.8.1"
@@ -996,6 +1052,15 @@ dependencies = [
  "walkdir",
 ]
 
+[[package]]
+name = "dirs"
+version = "4.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059"
+dependencies = [
+ "dirs-sys",
+]
+
 [[package]]
 name = "dirs-next"
 version = "2.0.0"
@@ -1006,6 +1071,17 @@ dependencies = [
  "dirs-sys-next",
 ]
 
+[[package]]
+name = "dirs-sys"
+version = "0.3.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6"
+dependencies = [
+ "libc",
+ "redox_users",
+ "winapi",
+]
+
 [[package]]
 name = "dirs-sys-next"
 version = "0.1.2"
@@ -2185,6 +2261,12 @@ dependencies = [
  "thiserror",
 ]
 
+[[package]]
+name = "os_str_bytes"
+version = "6.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9ff7415e9ae3fff1225851df9e0d9e4e5479f947619774677a63572e55e80eff"
+
 [[package]]
 name = "ouroboros"
 version = "0.14.2"
@@ -2721,6 +2803,23 @@ dependencies = [
  "wormhole-solana",
 ]
 
+[[package]]
+name = "remote-executor-cli"
+version = "0.1.0"
+dependencies = [
+ "anchor-client",
+ "anyhow",
+ "base64 0.13.0",
+ "clap 3.2.22",
+ "remote-executor",
+ "shellexpand",
+ "solana-client",
+ "solana-program",
+ "solana-sdk",
+ "wormhole-core",
+ "wormhole-solana",
+]
+
 [[package]]
 name = "remove_dir_all"
 version = "0.5.3"
@@ -3096,6 +3195,15 @@ dependencies = [
  "lazy_static",
 ]
 
+[[package]]
+name = "shellexpand"
+version = "2.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7ccc8076840c4da029af4f87e4e8daeb0fca6b87bbb02e10cb60b791450e11e4"
+dependencies = [
+ "dirs",
+]
+
 [[package]]
 name = "signal-hook-registry"
 version = "1.4.0"
@@ -3280,7 +3388,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "31c3b376c469a550be2f45890c81246362aed55bf06b5b4195002f1e84492f1c"
 dependencies = [
  "chrono",
- "clap",
+ "clap 2.34.0",
  "rpassword",
  "solana-perf",
  "solana-remote-wallet",
@@ -3319,7 +3427,7 @@ dependencies = [
  "bincode",
  "bs58 0.4.0",
  "bytes",
- "clap",
+ "clap 2.34.0",
  "crossbeam-channel",
  "enum_dispatch",
  "futures",
@@ -3396,7 +3504,7 @@ checksum = "e99dd10530cbd7f7c056a381e2df892dafec8a45abbf95b8e30d7871ff44809a"
 dependencies = [
  "bincode",
  "byteorder",
- "clap",
+ "clap 2.34.0",
  "crossbeam-channel",
  "log",
  "serde",
@@ -3488,7 +3596,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "e0a3c31c9f64d268110f59f162023dee05fb41f8d033d69fb980d6e80566f6b1"
 dependencies = [
  "bincode",
- "clap",
+ "clap 2.34.0",
  "crossbeam-channel",
  "log",
  "nix",
@@ -4085,6 +4193,12 @@ version = "0.8.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
 
+[[package]]
+name = "strsim"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
+
 [[package]]
 name = "strum"
 version = "0.24.1"
@@ -4241,6 +4355,12 @@ dependencies = [
  "unicode-width",
 ]
 
+[[package]]
+name = "textwrap"
+version = "0.15.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "949517c0cf1bf4ee812e2e07e08ab448e3ae0d23472aee8a06c985f0c8815b16"
+
 [[package]]
 name = "thiserror"
 version = "1.0.35"

+ 2 - 1
pythnet/remote-executor/Cargo.toml

@@ -1,4 +1,5 @@
 [workspace]
 members = [
-    "programs/*"
+    "programs/*",
+    "cli/"
 ]

+ 20 - 0
pythnet/remote-executor/cli/Cargo.toml

@@ -0,0 +1,20 @@
+[package]
+name = "remote-executor-cli"
+version = "0.1.0"
+edition = "2018"
+
+[lib]
+name = "remote_executor_cli"
+
+[dependencies]
+clap = {version ="3.2.22", features = ["derive"]}
+remote-executor = {path = "../programs/remote-executor/"}
+solana-program = "1.10.31"
+solana-client = "1.10.31"
+solana-sdk = "1.10.31"
+anchor-client = "0.25.0"
+shellexpand = "2.1.2"
+anyhow = "1.0.65"
+base64 = "0.13.0"
+wormhole-solana = { git = "https://github.com/guibescos/wormhole", branch = "reisen/sdk-solana"}
+wormhole-core = { git = "https://github.com/guibescos/wormhole", branch = "reisen/sdk-solana"}

+ 42 - 0
pythnet/remote-executor/cli/src/cli.rs

@@ -0,0 +1,42 @@
+//! CLI options
+use clap::{
+    Parser,
+    Subcommand,
+};
+use solana_sdk::commitment_config::CommitmentConfig;
+
+#[derive(Parser, Debug)]
+#[clap(
+    about = "A cli for the remote executor",
+    author = "Pyth Network Contributors"
+)]
+pub struct Cli {
+    #[clap(long, default_value = "confirmed")]
+    pub commitment: CommitmentConfig,
+    #[clap(subcommand)]
+    pub action: Action,
+}
+
+#[derive(Subcommand, Debug)]
+pub enum Action {
+    #[clap(about = "Post a VAA and execute it through the remote executor")]
+    PostAndExecute {
+        #[clap(short = 'v', long = "vaa")]
+        vaa: String,
+        #[clap(
+            long,
+            default_value = "~/.config/solana/id.json",
+            help = "Keypair file the funder of the transaction"
+        )]
+        keypair: String,
+    },
+    #[clap(about = "Send test VAA from solana")]
+    SendTestVAA {
+        #[clap(
+            long,
+            default_value = "~/.config/solana/id.json",
+            help = "Keypair file the funder of the transaction"
+        )]
+        keypair: String,
+    },
+}

+ 1 - 0
pythnet/remote-executor/cli/src/lib.rs

@@ -0,0 +1 @@
+mod cli;

+ 271 - 0
pythnet/remote-executor/cli/src/main.rs

@@ -0,0 +1,271 @@
+#![deny(warnings)]
+pub mod cli;
+
+use std::str::FromStr;
+
+use anchor_client::anchor_lang::{
+    AccountDeserialize,
+    AnchorDeserialize,
+    AnchorSerialize,
+    InstructionData,
+    Owner,
+    ToAccountMetas,
+};
+use clap::Parser;
+use cli::{
+    Action,
+    Cli,
+};
+
+use anyhow::Result;
+use remote_executor::{
+    accounts::ExecutePostedVaa,
+    EXECUTOR_KEY_SEED,
+    ID,
+};
+use solana_client::rpc_client::RpcClient;
+use solana_sdk::{
+    instruction::{
+        AccountMeta,
+        Instruction,
+    },
+    pubkey::Pubkey,
+    signature::{
+        read_keypair_file,
+        Keypair,
+    },
+    signer::Signer,
+    system_instruction,
+    system_instruction::transfer,
+    transaction::Transaction,
+};
+use wormhole_solana::{
+    instructions::{
+        post_message,
+        post_vaa,
+        verify_signatures_txs,
+        PostVAAData,
+    },
+    Account,
+    Config,
+    FeeCollector,
+    GuardianSet,
+    VAA as PostedVAA,
+};
+
+use remote_executor::state::{
+    governance_payload::{
+        ExecutorPayload,
+        GovernanceHeader,
+    },
+    posted_vaa::AnchorVaa,
+};
+use wormhole::VAA;
+
+fn main() -> Result<()> {
+    let cli = Cli::parse();
+
+    match cli.action {
+        Action::PostAndExecute { vaa, keypair } => {
+            let payer =
+                read_keypair_file(&*shellexpand::tilde(&keypair)).expect("Keypair not found");
+            let rpc_client =
+                RpcClient::new_with_commitment("https://pythnet.rpcpool.com/", cli.commitment);
+
+            let vaa_bytes: Vec<u8> = base64::decode(vaa)?;
+            let wormhole = AnchorVaa::owner();
+
+            let wormhole_config = Config::key(&wormhole, ());
+            let wormhole_config_data =
+                Config::try_from_slice(&rpc_client.get_account_data(&wormhole_config)?)?;
+
+            let guardian_set = GuardianSet::key(&wormhole, wormhole_config_data.guardian_set_index);
+            let guardian_set_data =
+                GuardianSet::try_from_slice(&rpc_client.get_account_data(&guardian_set)?)?;
+
+            let signature_set_keypair = Keypair::new();
+
+            let vaa = VAA::from_bytes(vaa_bytes.clone())?;
+
+            // RENT HACK STARTS HERE
+            let signature_set_size = 4 + 19 + 32 + 4;
+            let posted_vaa_size = 3 + 1 + 1 + 4 + 32 + 4 + 4 + 8 + 2 + 32 + 4 + vaa.payload.len();
+            let posted_vaa_key = PostedVAA::key(&wormhole, vaa.digest().unwrap().hash);
+
+            process_transaction(
+                &rpc_client,
+                vec![
+                    transfer(
+                        &payer.pubkey(),
+                        &signature_set_keypair.pubkey(),
+                        rpc_client.get_minimum_balance_for_rent_exemption(signature_set_size)?,
+                    ),
+                    transfer(
+                        &payer.pubkey(),
+                        &posted_vaa_key,
+                        rpc_client.get_minimum_balance_for_rent_exemption(posted_vaa_size)?,
+                    ),
+                ],
+                &vec![&payer],
+            )?;
+
+            // RENT HACK ENDS HERE
+
+            // First verify VAA
+            let verify_txs = verify_signatures_txs(
+                vaa_bytes.as_slice(),
+                guardian_set_data,
+                wormhole,
+                payer.pubkey(),
+                wormhole_config_data.guardian_set_index,
+                signature_set_keypair.pubkey(),
+            )?;
+
+            for tx in verify_txs {
+                process_transaction(&rpc_client, tx, &vec![&payer, &signature_set_keypair])?;
+            }
+
+            // Post VAA
+            let post_vaa_data = PostVAAData {
+                version: vaa.version,
+                guardian_set_index: vaa.guardian_set_index,
+                timestamp: vaa.timestamp,
+                nonce: vaa.nonce,
+                emitter_chain: vaa.emitter_chain.into(),
+                emitter_address: vaa.emitter_address,
+                sequence: vaa.sequence,
+                consistency_level: vaa.consistency_level,
+                payload: vaa.payload,
+            };
+
+            process_transaction(
+                &rpc_client,
+                vec![post_vaa(
+                    wormhole,
+                    payer.pubkey(),
+                    signature_set_keypair.pubkey(),
+                    post_vaa_data,
+                )?],
+                &vec![&payer],
+            )?;
+
+            // Now execute
+            process_transaction(
+                &rpc_client,
+                vec![get_execute_instruction(
+                    &rpc_client,
+                    &posted_vaa_key,
+                    &payer.pubkey(),
+                )?],
+                &vec![&payer],
+            )?;
+
+            Ok(())
+        }
+
+        Action::SendTestVAA { keypair } => {
+            let payer =
+                read_keypair_file(&*shellexpand::tilde(&keypair)).expect("Keypair not found");
+            let rpc_client = RpcClient::new_with_commitment(
+                "https://api.mainnet-beta.solana.com",
+                cli.commitment,
+            );
+
+            let message_keypair = Keypair::new();
+            let wormhole = Pubkey::from_str("worm2ZoG2kUd4vFXhvjh93UUH596ayRfgQ2MgjNMTth")?;
+
+            let fee_collector = FeeCollector::key(&wormhole, ());
+            let wormhole_config = Config::key(&wormhole, ());
+
+            let wormhole_config_data =
+                Config::try_from_slice(&rpc_client.get_account_data(&wormhole_config)?)?;
+
+            let payload = ExecutorPayload {
+                header: GovernanceHeader::executor_governance_header(),
+                instructions: vec![],
+            }
+            .try_to_vec()?;
+
+            let transfer_instruction = system_instruction::transfer(
+                &payer.pubkey(),
+                &fee_collector,
+                wormhole_config_data.params.fee,
+            );
+            let post_vaa_instruction = post_message(
+                wormhole,
+                payer.pubkey(),
+                payer.pubkey(),
+                message_keypair.pubkey(),
+                0,
+                payload.as_slice(),
+                0,
+            )?;
+
+            process_transaction(
+                &rpc_client,
+                vec![transfer_instruction, post_vaa_instruction],
+                &vec![&payer, &message_keypair],
+            )
+        }
+    }
+}
+
+pub fn process_transaction(
+    rpc_client: &RpcClient,
+    instructions: Vec<Instruction>,
+    signers: &Vec<&Keypair>,
+) -> Result<()> {
+    let mut transaction =
+        Transaction::new_with_payer(instructions.as_slice(), Some(&signers[0].pubkey()));
+    transaction.sign(signers, rpc_client.get_latest_blockhash()?);
+    let transaction_signature =
+        rpc_client.send_and_confirm_transaction_with_spinner(&transaction)?;
+    println!("Transaction successful : {:?}", transaction_signature);
+    Ok(())
+}
+
+pub fn get_execute_instruction(
+    rpc_client: &RpcClient,
+    posted_vaa_key: &Pubkey,
+    payer_pubkey: &Pubkey,
+) -> Result<Instruction> {
+    let anchor_vaa =
+        AnchorVaa::try_deserialize(&mut rpc_client.get_account_data(posted_vaa_key)?.as_slice())?;
+    let emitter = Pubkey::new(&anchor_vaa.emitter_address);
+
+    // First accounts from the anchor context
+    let mut account_metas = ExecutePostedVaa::populate(&ID, payer_pubkey, &emitter, posted_vaa_key)
+        .to_account_metas(None);
+
+    // Look at the payload
+    let executor_payload: ExecutorPayload =
+        AnchorDeserialize::try_from_slice(anchor_vaa.payload.as_slice()).unwrap();
+
+    // We need to add `executor_key` to the list of accounts
+    let executor_key = Pubkey::find_program_address(
+        &[EXECUTOR_KEY_SEED.as_bytes(), &anchor_vaa.emitter_address],
+        &ID,
+    )
+    .0;
+
+    account_metas.push(AccountMeta {
+        pubkey: executor_key,
+        is_signer: false,
+        is_writable: true,
+    });
+
+    // Add the rest of `remaining_accounts` from the payload
+    for instruction in executor_payload.instructions {
+        for account_meta in Instruction::from(&instruction).accounts {
+            if account_meta.pubkey != executor_key {
+                account_metas.push(account_meta.clone());
+            }
+        }
+    }
+
+    Ok(Instruction {
+        program_id: ID,
+        accounts: account_metas,
+        data: remote_executor::instruction::ExecutePostedVaa.data(),
+    })
+}