|
|
@@ -1,18 +1,7 @@
|
|
|
-pub mod attestation_cfg;
|
|
|
pub mod cli;
|
|
|
|
|
|
-use std::{
|
|
|
- fs::File,
|
|
|
- path::{
|
|
|
- Path,
|
|
|
- PathBuf,
|
|
|
- },
|
|
|
-};
|
|
|
+use std::fs::File;
|
|
|
|
|
|
-use borsh::{
|
|
|
- BorshDeserialize,
|
|
|
- BorshSerialize,
|
|
|
-};
|
|
|
use clap::Clap;
|
|
|
use log::{
|
|
|
debug,
|
|
|
@@ -22,68 +11,26 @@ use log::{
|
|
|
LevelFilter,
|
|
|
};
|
|
|
use solana_client::rpc_client::RpcClient;
|
|
|
-use solana_program::{
|
|
|
- hash::Hash,
|
|
|
- instruction::{
|
|
|
- AccountMeta,
|
|
|
- Instruction,
|
|
|
- },
|
|
|
- pubkey::Pubkey,
|
|
|
- system_program,
|
|
|
- sysvar::{
|
|
|
- clock,
|
|
|
- rent,
|
|
|
- },
|
|
|
-};
|
|
|
+use solana_program::pubkey::Pubkey;
|
|
|
use solana_sdk::{
|
|
|
commitment_config::CommitmentConfig,
|
|
|
signature::read_keypair_file,
|
|
|
- transaction::Transaction,
|
|
|
};
|
|
|
use solana_transaction_status::UiTransactionEncoding;
|
|
|
use solitaire::{
|
|
|
processors::seeded::Seeded,
|
|
|
- AccountState,
|
|
|
- Derive,
|
|
|
- Info,
|
|
|
-};
|
|
|
-use solitaire_client::{
|
|
|
- AccEntry,
|
|
|
- Keypair,
|
|
|
- SolSigner,
|
|
|
- ToInstruction,
|
|
|
+ ErrBox,
|
|
|
};
|
|
|
+use solitaire_client::Keypair;
|
|
|
|
|
|
use cli::{
|
|
|
Action,
|
|
|
Cli,
|
|
|
};
|
|
|
|
|
|
-use bridge::{
|
|
|
- accounts::{
|
|
|
- Bridge,
|
|
|
- FeeCollector,
|
|
|
- Sequence,
|
|
|
- SequenceDerivationData,
|
|
|
- },
|
|
|
- types::ConsistencyLevel,
|
|
|
- CHAIN_ID_SOLANA,
|
|
|
-};
|
|
|
-
|
|
|
-use pyth2wormhole::{
|
|
|
- attest::P2W_MAX_BATCH_SIZE,
|
|
|
- config::P2WConfigAccount,
|
|
|
- initialize::InitializeAccounts,
|
|
|
- set_config::SetConfigAccounts,
|
|
|
- AttestData,
|
|
|
- Pyth2WormholeConfig,
|
|
|
-};
|
|
|
-
|
|
|
use p2w_sdk::P2WEmitter;
|
|
|
|
|
|
-use crate::attestation_cfg::AttestationConfig;
|
|
|
-
|
|
|
-pub type ErrBox = Box<dyn std::error::Error>;
|
|
|
+use pyth2wormhole_client::*;
|
|
|
|
|
|
pub const SEQNO_PREFIX: &'static str = "Program log: Sequence: ";
|
|
|
|
|
|
@@ -104,7 +51,7 @@ fn main() -> Result<(), ErrBox> {
|
|
|
pyth_owner_addr,
|
|
|
wh_prog,
|
|
|
} => {
|
|
|
- let tx = handle_init(
|
|
|
+ let tx = gen_init_tx(
|
|
|
payer,
|
|
|
p2w_addr,
|
|
|
owner_addr,
|
|
|
@@ -114,13 +61,16 @@ fn main() -> Result<(), ErrBox> {
|
|
|
)?;
|
|
|
rpc_client.send_and_confirm_transaction_with_spinner(&tx)?;
|
|
|
}
|
|
|
+ Action::GetConfig => {
|
|
|
+ println!("{:?}", get_config_account(&rpc_client, &p2w_addr)?);
|
|
|
+ }
|
|
|
Action::SetConfig {
|
|
|
ref owner,
|
|
|
new_owner_addr,
|
|
|
new_wh_prog,
|
|
|
new_pyth_owner_addr,
|
|
|
} => {
|
|
|
- let tx = handle_set_config(
|
|
|
+ let tx = gen_set_config_tx(
|
|
|
payer,
|
|
|
p2w_addr,
|
|
|
read_keypair_file(&*shellexpand::tilde(&owner))?,
|
|
|
@@ -133,118 +83,32 @@ fn main() -> Result<(), ErrBox> {
|
|
|
}
|
|
|
Action::Attest {
|
|
|
ref attestation_cfg,
|
|
|
+ n_retries,
|
|
|
} => {
|
|
|
// Load the attestation config yaml
|
|
|
let attestation_cfg: AttestationConfig =
|
|
|
serde_yaml::from_reader(File::open(attestation_cfg)?)?;
|
|
|
|
|
|
- handle_attest(&rpc_client, payer, p2w_addr, &attestation_cfg)?;
|
|
|
+ handle_attest(&rpc_client, payer, p2w_addr, &attestation_cfg, n_retries)?;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
Ok(())
|
|
|
}
|
|
|
|
|
|
-fn handle_init(
|
|
|
- payer: Keypair,
|
|
|
- p2w_addr: Pubkey,
|
|
|
- new_owner_addr: Pubkey,
|
|
|
- wh_prog: Pubkey,
|
|
|
- pyth_owner_addr: Pubkey,
|
|
|
- latest_blockhash: Hash,
|
|
|
-) -> Result<Transaction, ErrBox> {
|
|
|
- use AccEntry::*;
|
|
|
-
|
|
|
- let payer_pubkey = payer.pubkey();
|
|
|
-
|
|
|
- let accs = InitializeAccounts {
|
|
|
- payer: Signer(payer),
|
|
|
- new_config: Derived(p2w_addr),
|
|
|
- };
|
|
|
-
|
|
|
- let config = Pyth2WormholeConfig {
|
|
|
- max_batch_size: P2W_MAX_BATCH_SIZE,
|
|
|
- owner: new_owner_addr,
|
|
|
- wh_prog: wh_prog,
|
|
|
- pyth_owner: pyth_owner_addr,
|
|
|
- };
|
|
|
- let ix_data = (pyth2wormhole::instruction::Instruction::Initialize, config);
|
|
|
-
|
|
|
- let (ix, signers) = accs.to_ix(p2w_addr, ix_data.try_to_vec()?.as_slice())?;
|
|
|
-
|
|
|
- let tx_signed = Transaction::new_signed_with_payer::<Vec<&Keypair>>(
|
|
|
- &[ix],
|
|
|
- Some(&payer_pubkey),
|
|
|
- signers.iter().collect::<Vec<_>>().as_ref(),
|
|
|
- latest_blockhash,
|
|
|
- );
|
|
|
- Ok(tx_signed)
|
|
|
-}
|
|
|
-
|
|
|
-fn handle_set_config(
|
|
|
- payer: Keypair,
|
|
|
- p2w_addr: Pubkey,
|
|
|
- owner: Keypair,
|
|
|
- new_owner_addr: Pubkey,
|
|
|
- new_wh_prog: Pubkey,
|
|
|
- new_pyth_owner_addr: Pubkey,
|
|
|
- latest_blockhash: Hash,
|
|
|
-) -> Result<Transaction, ErrBox> {
|
|
|
- use AccEntry::*;
|
|
|
-
|
|
|
- let payer_pubkey = payer.pubkey();
|
|
|
-
|
|
|
- let accs = SetConfigAccounts {
|
|
|
- payer: Signer(payer),
|
|
|
- current_owner: Signer(owner),
|
|
|
- config: Derived(p2w_addr),
|
|
|
- };
|
|
|
-
|
|
|
- let config = Pyth2WormholeConfig {
|
|
|
- max_batch_size: P2W_MAX_BATCH_SIZE,
|
|
|
- owner: new_owner_addr,
|
|
|
- wh_prog: new_wh_prog,
|
|
|
- pyth_owner: new_pyth_owner_addr,
|
|
|
- };
|
|
|
- let ix_data = (pyth2wormhole::instruction::Instruction::SetConfig, config);
|
|
|
-
|
|
|
- let (ix, signers) = accs.to_ix(p2w_addr, ix_data.try_to_vec()?.as_slice())?;
|
|
|
-
|
|
|
- let tx_signed = Transaction::new_signed_with_payer::<Vec<&Keypair>>(
|
|
|
- &[ix],
|
|
|
- Some(&payer_pubkey),
|
|
|
- signers.iter().collect::<Vec<_>>().as_ref(),
|
|
|
- latest_blockhash,
|
|
|
- );
|
|
|
- Ok(tx_signed)
|
|
|
-}
|
|
|
-
|
|
|
fn handle_attest(
|
|
|
rpc_client: &RpcClient, // Needed for reading Pyth account data
|
|
|
payer: Keypair,
|
|
|
p2w_addr: Pubkey,
|
|
|
attestation_cfg: &AttestationConfig,
|
|
|
+ n_retries: usize,
|
|
|
) -> Result<(), ErrBox> {
|
|
|
// Derive seeded accounts
|
|
|
let emitter_addr = P2WEmitter::key(None, &p2w_addr);
|
|
|
|
|
|
info!("Using emitter addr {}", emitter_addr);
|
|
|
|
|
|
- let p2w_config_addr = P2WConfigAccount::<{ AccountState::Initialized }>::key(None, &p2w_addr);
|
|
|
-
|
|
|
- let config = Pyth2WormholeConfig::try_from_slice(
|
|
|
- rpc_client.get_account_data(&p2w_config_addr)?.as_slice(),
|
|
|
- )?;
|
|
|
-
|
|
|
- let seq_addr = Sequence::key(
|
|
|
- &SequenceDerivationData {
|
|
|
- emitter_key: &emitter_addr,
|
|
|
- },
|
|
|
- &config.wh_prog,
|
|
|
- );
|
|
|
-
|
|
|
- // Read the current max batch size from the contract's settings
|
|
|
- let max_batch_size = config.max_batch_size;
|
|
|
+ let config = get_config_account(rpc_client, &p2w_addr)?;
|
|
|
|
|
|
let batch_count = {
|
|
|
let whole_batches = attestation_cfg.symbols.len() / config.max_batch_size as usize;
|
|
|
@@ -262,156 +126,132 @@ fn handle_attest(
|
|
|
info!(
|
|
|
"{} symbols read, max batch size {}, dividing into {} batches",
|
|
|
attestation_cfg.symbols.len(),
|
|
|
- max_batch_size,
|
|
|
+ config.max_batch_size,
|
|
|
batch_count
|
|
|
);
|
|
|
|
|
|
let mut errors = Vec::new();
|
|
|
|
|
|
- for (idx, symbols) in attestation_cfg
|
|
|
+ // Reused for failed batch retries
|
|
|
+ let mut batches: Vec<_> = attestation_cfg
|
|
|
.symbols
|
|
|
.as_slice()
|
|
|
- .chunks(max_batch_size as usize)
|
|
|
+ .chunks(config.max_batch_size as usize)
|
|
|
.enumerate()
|
|
|
- {
|
|
|
- let batch_no = idx + 1;
|
|
|
- let sym_msg_keypair = Keypair::new();
|
|
|
- info!(
|
|
|
- "Batch {}/{} contents: {:?}",
|
|
|
- batch_no,
|
|
|
- batch_count,
|
|
|
- symbols
|
|
|
- .iter()
|
|
|
- .map(|s| s
|
|
|
- .name
|
|
|
- .clone()
|
|
|
- .unwrap_or(format!("unnamed product {:?}", s.product_addr)))
|
|
|
- .collect::<Vec<_>>()
|
|
|
- );
|
|
|
-
|
|
|
- let mut sym_metas_vec: Vec<_> = symbols
|
|
|
- .iter()
|
|
|
- .map(|s| {
|
|
|
- vec![
|
|
|
- AccountMeta::new_readonly(s.product_addr, false),
|
|
|
- AccountMeta::new_readonly(s.price_addr, false),
|
|
|
- ]
|
|
|
- })
|
|
|
- .flatten()
|
|
|
- .collect();
|
|
|
-
|
|
|
- // Align to max batch size with null accounts
|
|
|
- let mut blank_accounts =
|
|
|
- vec![
|
|
|
- AccountMeta::new_readonly(Pubkey::new_from_array([0u8; 32]), false);
|
|
|
- 2 * (max_batch_size as usize - symbols.len())
|
|
|
- ];
|
|
|
- sym_metas_vec.append(&mut blank_accounts);
|
|
|
-
|
|
|
- // Arrange Attest accounts
|
|
|
- let mut acc_metas = vec![
|
|
|
- // payer
|
|
|
- AccountMeta::new(payer.pubkey(), true),
|
|
|
- // system_program
|
|
|
- AccountMeta::new_readonly(system_program::id(), false),
|
|
|
- // config
|
|
|
- AccountMeta::new_readonly(p2w_config_addr, false),
|
|
|
- ];
|
|
|
-
|
|
|
- // Insert max_batch_size metas
|
|
|
- acc_metas.append(&mut sym_metas_vec);
|
|
|
-
|
|
|
- // Continue with other pyth2wormhole accounts
|
|
|
- let mut acc_metas_remainder = vec![
|
|
|
- // clock
|
|
|
- AccountMeta::new_readonly(clock::id(), false),
|
|
|
- // wh_prog
|
|
|
- AccountMeta::new_readonly(config.wh_prog, false),
|
|
|
- // wh_bridge
|
|
|
- AccountMeta::new(
|
|
|
- Bridge::<{ AccountState::Initialized }>::key(None, &config.wh_prog),
|
|
|
- false,
|
|
|
- ),
|
|
|
- // wh_message
|
|
|
- AccountMeta::new(sym_msg_keypair.pubkey(), true),
|
|
|
- // wh_emitter
|
|
|
- AccountMeta::new_readonly(emitter_addr, false),
|
|
|
- // wh_sequence
|
|
|
- AccountMeta::new(seq_addr, false),
|
|
|
- // wh_fee_collector
|
|
|
- AccountMeta::new(FeeCollector::<'_>::key(None, &config.wh_prog), false),
|
|
|
- AccountMeta::new_readonly(rent::id(), false),
|
|
|
- ];
|
|
|
-
|
|
|
- acc_metas.append(&mut acc_metas_remainder);
|
|
|
-
|
|
|
- let ix_data = (
|
|
|
- pyth2wormhole::instruction::Instruction::Attest,
|
|
|
- AttestData {
|
|
|
- consistency_level: ConsistencyLevel::Finalized,
|
|
|
- },
|
|
|
- );
|
|
|
-
|
|
|
- let ix = Instruction::new_with_bytes(p2w_addr, ix_data.try_to_vec()?.as_slice(), acc_metas);
|
|
|
-
|
|
|
- // Execute the transaction, obtain the resulting sequence
|
|
|
- // number. The and_then() calls enforce error handling
|
|
|
- // location near loop end.
|
|
|
- let res = rpc_client
|
|
|
- .get_latest_blockhash()
|
|
|
- .and_then(|latest_blockhash| {
|
|
|
- let tx_signed = Transaction::new_signed_with_payer::<Vec<&Keypair>>(
|
|
|
- &[ix],
|
|
|
- Some(&payer.pubkey()),
|
|
|
- &vec![&payer, &sym_msg_keypair],
|
|
|
- latest_blockhash,
|
|
|
- );
|
|
|
- rpc_client.send_and_confirm_transaction_with_spinner(&tx_signed)
|
|
|
- })
|
|
|
- .and_then(|sig| rpc_client.get_transaction(&sig, UiTransactionEncoding::Json))
|
|
|
- .map_err(|e| -> ErrBox { e.into() })
|
|
|
- .and_then(|this_tx| {
|
|
|
- this_tx
|
|
|
- .transaction
|
|
|
- .meta
|
|
|
- .and_then(|meta| meta.log_messages)
|
|
|
- .and_then(|logs| {
|
|
|
- let mut seqno = None;
|
|
|
- for log in logs {
|
|
|
- if log.starts_with(SEQNO_PREFIX) {
|
|
|
- seqno = Some(log.replace(SEQNO_PREFIX, ""));
|
|
|
- break;
|
|
|
+ .map(|(idx, symbols)| (idx + 1, symbols, 1))
|
|
|
+ .collect();
|
|
|
+
|
|
|
+ let mut batches4retry = Vec::new();
|
|
|
+
|
|
|
+ // If no batches are scheduled for retry, the vector eventually drains
|
|
|
+ while !batches.is_empty() {
|
|
|
+ for (batch_no, symbols, attempt_no) in batches {
|
|
|
+ info!(
|
|
|
+ "Batch {}/{} contents: {:?}",
|
|
|
+ batch_no,
|
|
|
+ batch_count,
|
|
|
+ symbols
|
|
|
+ .iter()
|
|
|
+ .map(|s| s
|
|
|
+ .name
|
|
|
+ .clone()
|
|
|
+ .unwrap_or(format!("unnamed product {:?}", s.product_addr)))
|
|
|
+ .collect::<Vec<_>>()
|
|
|
+ );
|
|
|
+
|
|
|
+ // Execute the transaction, obtain the resulting sequence
|
|
|
+ // number. The and_then() calls enforce permissible error
|
|
|
+ // handling location near loop end.
|
|
|
+ let res = rpc_client
|
|
|
+ .get_latest_blockhash()
|
|
|
+ .map_err(|e| -> ErrBox { e.into() })
|
|
|
+ .and_then(|latest_blockhash| {
|
|
|
+ let tx_signed = gen_attest_tx(
|
|
|
+ p2w_addr,
|
|
|
+ &config,
|
|
|
+ &payer,
|
|
|
+ symbols,
|
|
|
+ &Keypair::new(),
|
|
|
+ latest_blockhash,
|
|
|
+ )?;
|
|
|
+
|
|
|
+ rpc_client
|
|
|
+ .send_and_confirm_transaction_with_spinner(&tx_signed)
|
|
|
+ .map_err(|e| -> ErrBox { e.into() })
|
|
|
+ })
|
|
|
+ .and_then(|sig| {
|
|
|
+ rpc_client
|
|
|
+ .get_transaction(&sig, UiTransactionEncoding::Json)
|
|
|
+ .map_err(|e| -> ErrBox { e.into() })
|
|
|
+ })
|
|
|
+ .and_then(|this_tx| {
|
|
|
+ this_tx
|
|
|
+ .transaction
|
|
|
+ .meta
|
|
|
+ .and_then(|meta| meta.log_messages)
|
|
|
+ .and_then(|logs| {
|
|
|
+ let mut seqno = None;
|
|
|
+ for log in logs {
|
|
|
+ if log.starts_with(SEQNO_PREFIX) {
|
|
|
+ seqno = Some(log.replace(SEQNO_PREFIX, ""));
|
|
|
+ break;
|
|
|
+ }
|
|
|
}
|
|
|
- }
|
|
|
- seqno
|
|
|
- })
|
|
|
- .ok_or_else(|| format!("No seqno in program logs").into())
|
|
|
- });
|
|
|
-
|
|
|
- // Individual batch errors mustn't prevent other batches from being sent.
|
|
|
- match res {
|
|
|
- Ok(seqno) => {
|
|
|
- println!("Sequence number: {}", seqno);
|
|
|
- info!("Batch {}/{}: OK, seqno {}", batch_no, batch_count, seqno);
|
|
|
- }
|
|
|
- Err(e) => {
|
|
|
- let msg = format!(
|
|
|
- "Batch {}/{} tx error: {}",
|
|
|
- batch_no,
|
|
|
- batch_count,
|
|
|
- e.to_string()
|
|
|
- );
|
|
|
- error!("{}", &msg);
|
|
|
-
|
|
|
- errors.push(msg)
|
|
|
+ seqno
|
|
|
+ })
|
|
|
+ .ok_or_else(|| format!("No seqno in program logs").into())
|
|
|
+ });
|
|
|
+
|
|
|
+ // Individual batch errors mustn't prevent other batches from being sent.
|
|
|
+ match res {
|
|
|
+ Ok(seqno) => {
|
|
|
+ println!("Sequence number: {}", seqno);
|
|
|
+ info!("Batch {}/{}: OK, seqno {}", batch_no, batch_count, seqno);
|
|
|
+ }
|
|
|
+ Err(e) => {
|
|
|
+ let msg = format!(
|
|
|
+ "Batch {}/{} tx error (attempt {} of {}): {}",
|
|
|
+ batch_no,
|
|
|
+ batch_count,
|
|
|
+ attempt_no,
|
|
|
+ n_retries + 1,
|
|
|
+ e.to_string()
|
|
|
+ );
|
|
|
+ warn!("{}", &msg);
|
|
|
+
|
|
|
+ if attempt_no < n_retries + 1 {
|
|
|
+ // Schedule this batch for a retry if we have retries left
|
|
|
+ batches4retry.push((batch_no, symbols, attempt_no + 1));
|
|
|
+ } else {
|
|
|
+ // This batch failed all attempts, note the error but do not schedule for retry
|
|
|
+ error!(
|
|
|
+ "Batch {}/{}: All {} attempts failed",
|
|
|
+ batch_no,
|
|
|
+ batch_count,
|
|
|
+ n_retries + 1
|
|
|
+ );
|
|
|
+ errors.push(msg);
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+ // Batches scheduled for retry become the list of batches for
|
|
|
+ // next attempt round, clear retry vec for future failed attempts.
|
|
|
+ batches = batches4retry;
|
|
|
+ batches4retry = Vec::new();
|
|
|
}
|
|
|
|
|
|
- if errors.len() > 0 {
|
|
|
+ if !errors.is_empty() {
|
|
|
let err_list = errors.join("\n");
|
|
|
|
|
|
- Err(format!("{} of {} batches failed:\n{}", errors.len(), batch_count, err_list).into())
|
|
|
+ Err(format!(
|
|
|
+ "{} of {} batches failed:\n{}",
|
|
|
+ errors.len(),
|
|
|
+ batch_count,
|
|
|
+ err_list
|
|
|
+ )
|
|
|
+ .into())
|
|
|
} else {
|
|
|
Ok(())
|
|
|
}
|