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
This commit is contained in:
guibescos 2022-09-28 10:55:11 -05:00 committed by GitHub
parent 0a1f31a952
commit 5214d185e8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 463 additions and 8 deletions

View File

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

View File

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

View File

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

View File

@ -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"}

View File

@ -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,
},
}

View File

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

View File

@ -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(),
})
}