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:
parent
0a1f31a952
commit
5214d185e8
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
[workspace]
|
||||
members = [
|
||||
"programs/*"
|
||||
"programs/*",
|
||||
"cli/"
|
||||
]
|
||||
|
|
|
@ -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"}
|
|
@ -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,
|
||||
},
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
mod cli;
|
|
@ -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(),
|
||||
})
|
||||
}
|
Loading…
Reference in New Issue