diff --git a/docs/solana_program.md b/docs/solana_program.md index 6900379fb..2897cc758 100644 --- a/docs/solana_program.md +++ b/docs/solana_program.md @@ -31,9 +31,11 @@ Checks secp checks (in the previous instruction) and stores results. | Index | Name | Type | signer | writeable | empty | derived | | ----- | ------ | ------------ | ------ | --------- | ----- | ------- | | 0 | bridge_p | BridgeProgram | | | ️ | | -| 1 | instructions | Sysvar | | | ️ | ✅ | -| 2 | sig_status | SignatureState | | ✅ | ️ | | -| 3 | guardian_set | GuardianSet | | | ️ | ✅ | +| 1 | sys | SystemProgram | | | ️ | | +| 2 | instructions | Sysvar | | | ️ | ✅ | +| 3 | sig_status | SignatureState | | ✅ | ️ | | +| 4 | guardian_set | GuardianSet | | | ️ | ✅ | +| 5 | payer | Account | ✅ | | | | #### TransferOut diff --git a/solana/agent/src/main.rs b/solana/agent/src/main.rs index 47ea5d3a9..65a0ad22c 100644 --- a/solana/agent/src/main.rs +++ b/solana/agent/src/main.rs @@ -1,26 +1,17 @@ -use std::env; - -use std::{io::Write, mem::size_of}; - -use std::str::FromStr; +use std::{env, io::Write, mem::size_of, str::FromStr}; use byteorder::{BigEndian, LittleEndian, ReadBytesExt, WriteBytesExt}; use solana_client::{ client_error::ClientError, rpc_client::RpcClient, rpc_config::RpcSendTransactionConfig, }; -use solana_sdk::commitment_config::{CommitmentConfig, CommitmentLevel}; - -use solana_sdk::instruction::Instruction; - use solana_sdk::{ + commitment_config::{CommitmentConfig, CommitmentLevel}, + instruction::Instruction, pubkey::Pubkey, signature::{read_keypair_file, write_keypair_file, Keypair, Signature, Signer}, - system_instruction::create_account, transaction::Transaction, }; - use tokio::sync::mpsc; - use tonic::{transport::Server, Code, Request, Response, Status}; use service::{ @@ -31,7 +22,7 @@ use service::{ }; use spl_bridge::{ instruction::{post_vaa, verify_signatures, VerifySigPayload, CHAIN_ID_SOLANA}, - state::{Bridge, GuardianSet, SignatureState, TransferOutProposal}, + state::{Bridge, GuardianSet, TransferOutProposal}, vaa::VAA, }; @@ -74,8 +65,6 @@ impl Agent for AgentImpl { std::thread::spawn(move || { let rpc = RpcClient::new(rpc_url); - let sig_key = solana_sdk::signature::Keypair::new(); - let mut vaa = match VAA::deserialize(&request.get_ref().vaa) { Ok(v) => v, Err(e) => { @@ -85,16 +74,11 @@ impl Agent for AgentImpl { )); } }; - let verify_txs = pack_sig_verification_txs(&rpc, &bridge, &vaa, &key, &sig_key)?; + let verify_txs = pack_sig_verification_txs(&rpc, &bridge, &vaa, &key)?; // Strip signatures vaa.signatures = Vec::new(); - let ix = match post_vaa( - &bridge, - &key.pubkey(), - &sig_key.pubkey(), - vaa.serialize().unwrap(), - ) { + let ix = match post_vaa(&bridge, &key.pubkey(), vaa.serialize().unwrap()) { Ok(v) => v, Err(e) => { return Err(Status::new( @@ -103,10 +87,9 @@ impl Agent for AgentImpl { )); } }; - let mut transaction2 = Transaction::new_with_payer(&[ix], Some(&key.pubkey())); - for (mut tx, signers) in verify_txs { - match sign_and_send(&rpc, &mut tx, signers) { + for mut tx in verify_txs { + match sign_and_send(&rpc, &mut tx, vec![&key]) { Ok(_) => (), Err(e) => { return Err(Status::new( @@ -117,6 +100,7 @@ impl Agent for AgentImpl { }; } + let mut transaction2 = Transaction::new_with_payer(&[ix], Some(&key.pubkey())); match sign_and_send(&rpc, &mut transaction2, vec![&key]) { Ok(s) => Ok(Response::new(SubmitVaaResponse { signature: s.to_string(), @@ -129,12 +113,6 @@ impl Agent for AgentImpl { }) .join() .unwrap() - - //check_fee_payer_balance( - // config, - // minimum_balance_for_rent_exemption - // + fee_calculator.calculate_fee(&transaction.message()), - //)?; } type WatchLockupsStream = mpsc::Receiver>; @@ -256,8 +234,7 @@ fn pack_sig_verification_txs<'a>( bridge: &Pubkey, vaa: &VAA, sender_keypair: &'a Keypair, - sign_keypair: &'a Keypair, -) -> Result)>, Status> { +) -> Result, Status> { // Load guardian set let bridge_key = Bridge::derive_bridge_id(bridge).unwrap(); let guardian_key = @@ -311,7 +288,10 @@ fn pack_sig_verification_txs<'a>( } }; - let mut verify_txs: Vec<(Transaction, Vec<&Keypair>)> = Vec::new(); + let signature_acc = + Bridge::derive_signature_id(&bridge, &bridge_key, &vaa_hash, guardian_set.index).unwrap(); + + let mut verify_txs: Vec = Vec::new(); for (tx_index, chunk) in signature_items.chunks(6).enumerate() { let mut secp_payload = Vec::new(); let mut signature_status = [-1i8; 20]; @@ -322,16 +302,15 @@ fn pack_sig_verification_txs<'a>( // 1 number of signatures secp_payload.write_u8(chunk.len() as u8); - let secp_ix_index = if tx_index == 0 { 1u8 } else { 0u8 }; // Secp signature info description (11 bytes * n) for (i, s) in chunk.iter().enumerate() { secp_payload.write_u16::((data_offset + 85 * i) as u16); - secp_payload.write_u8(secp_ix_index); + secp_payload.write_u8(0); secp_payload.write_u16::((data_offset + 85 * i + 65) as u16); - secp_payload.write_u8(secp_ix_index); + secp_payload.write_u8(0); secp_payload.write_u16::(message_offset as u16); secp_payload.write_u16::(vaa_body.len() as u16); - secp_payload.write_u8(secp_ix_index); + secp_payload.write_u8(0); signature_status[s.index as usize] = i as i8; } @@ -353,10 +332,13 @@ fn pack_sig_verification_txs<'a>( let payload = VerifySigPayload { signers: signature_status, hash: vaa_hash, + initial_creation: tx_index == 0, }; + let verify_ix = match verify_signatures( &bridge, - &sign_keypair.pubkey(), + &signature_acc, + &sender_keypair.pubkey(), vaa.guardian_set_index, &payload, ) { @@ -369,32 +351,10 @@ fn pack_sig_verification_txs<'a>( } }; - if tx_index == 0 { - // Instruction for creating the signature status account - let min_sig_rent = rpc - .get_minimum_balance_for_rent_exemption(size_of::()) - .unwrap(); - let create_ix = create_account( - &sender_keypair.pubkey(), - &sign_keypair.pubkey(), - min_sig_rent, - size_of::() as u64, - bridge, - ); - - verify_txs.push(( - Transaction::new_with_payer( - &[create_ix, secp_ix, verify_ix], - Some(&sender_keypair.pubkey()), - ), - vec![sender_keypair, sign_keypair], - )) - } else { - verify_txs.push(( - Transaction::new_with_payer(&[secp_ix, verify_ix], Some(&sender_keypair.pubkey())), - vec![sender_keypair], - )) - } + verify_txs.push(Transaction::new_with_payer( + &[secp_ix, verify_ix], + Some(&sender_keypair.pubkey()), + )) } Ok(verify_txs) diff --git a/solana/bridge/src/instruction.rs b/solana/bridge/src/instruction.rs index 875cf9891..3c9b78a94 100644 --- a/solana/bridge/src/instruction.rs +++ b/solana/bridge/src/instruction.rs @@ -81,6 +81,8 @@ pub struct VerifySigPayload { pub hash: [u8; 32], /// instruction indices of signers (-1 for missing) pub signers: [i8; MAX_LEN_GUARDIAN_KEYS], + /// indicates whether this verification should only succeed if the sig account does not exist + pub initial_creation: bool, } /// Instructions supported by the SwapInfo program. @@ -336,6 +338,7 @@ pub fn transfer_out( pub fn verify_signatures( program_id: &Pubkey, signature_acc: &Pubkey, + payer: &Pubkey, guardian_set_id: u32, p: &VerifySigPayload, ) -> Result { @@ -347,9 +350,11 @@ pub fn verify_signatures( let accounts = vec![ AccountMeta::new_readonly(*program_id, false), + AccountMeta::new_readonly(solana_sdk::system_program::id(), false), AccountMeta::new_readonly(solana_sdk::sysvar::instructions::id(), false), AccountMeta::new(*signature_acc, false), AccountMeta::new_readonly(guardian_set_key, false), + AccountMeta::new(*payer, true), ]; Ok(Instruction { @@ -364,7 +369,6 @@ pub fn verify_signatures( pub fn post_vaa( program_id: &Pubkey, payer: &Pubkey, - signature_key: &Pubkey, v: VAAData, ) -> Result { let mut data = v.clone(); @@ -378,6 +382,13 @@ pub fn post_vaa( Bridge::derive_guardian_set_id(program_id, &bridge_key, vaa.guardian_set_index)?; let claim_key = Bridge::derive_claim_id(program_id, &bridge_key, vaa.signature_body()?)?; + let signature_acc = Bridge::derive_signature_id( + program_id, + &bridge_key, + &vaa.body_hash()?, + vaa.guardian_set_index, + )?; + let mut accounts = vec![ AccountMeta::new_readonly(*program_id, false), AccountMeta::new_readonly(solana_sdk::system_program::id(), false), @@ -386,7 +397,7 @@ pub fn post_vaa( AccountMeta::new(bridge_key, false), AccountMeta::new(guardian_set_key, false), AccountMeta::new(claim_key, false), - AccountMeta::new(*signature_key, false), + AccountMeta::new(signature_acc, false), AccountMeta::new(*payer, true), ]; diff --git a/solana/bridge/src/processor.rs b/solana/bridge/src/processor.rs index 7bbb14a62..8cfffca12 100644 --- a/solana/bridge/src/processor.rs +++ b/solana/bridge/src/processor.rs @@ -189,28 +189,14 @@ impl Bridge { ) -> ProgramResult { let account_info_iter = &mut accounts.iter(); next_account_info(account_info_iter)?; // Bridge program + next_account_info(account_info_iter)?; // System program let instruction_accounts = next_account_info(account_info_iter)?; let sig_info = next_account_info(account_info_iter)?; let guardian_set_info = next_account_info(account_info_iter)?; + let payer_info = next_account_info(account_info_iter)?; let guardian_set: GuardianSet = Self::guardian_set_deserialize(guardian_set_info)?; - let mut sig_state_data = sig_info.data.borrow_mut(); - let mut sig_state: &mut SignatureState = Self::unpack_unchecked(&mut sig_state_data)?; - - if sig_state.is_initialized { - if sig_state.guardian_set_index != guardian_set.index { - return Err(Error::GuardianSetMismatch.into()); - } - if sig_state.hash != payload.hash { - return Err(ProgramError::InvalidArgument); - } - } else { - sig_state.is_initialized = true; - sig_state.guardian_set_index = guardian_set.index; - sig_state.hash = payload.hash; - } - let sig_infos: Vec = payload .signers .iter() @@ -310,6 +296,38 @@ impl Bridge { return Err(ProgramError::InvalidArgument); } + if sig_info.data_is_empty() { + let bridge_key = Bridge::derive_bridge_id(program_id)?; + let sig_seeds = + Bridge::derive_signature_seeds(&bridge_key, &msg_hash, guardian_set.index); + Bridge::check_and_create_account::( + program_id, + accounts, + sig_info.key, + payer_info.key, + program_id, + &sig_seeds, + )?; + } else if payload.initial_creation { + return Err(Error::AlreadyExists.into()); + } + + let mut sig_state_data = sig_info.data.borrow_mut(); + let mut sig_state: &mut SignatureState = Self::unpack_unchecked(&mut sig_state_data)?; + + if sig_state.is_initialized { + if sig_state.guardian_set_index != guardian_set.index { + return Err(Error::GuardianSetMismatch.into()); + } + if sig_state.hash != payload.hash { + return Err(ProgramError::InvalidArgument); + } + } else { + sig_state.is_initialized = true; + sig_state.guardian_set_index = guardian_set.index; + sig_state.hash = payload.hash; + } + // Check addresses for s in sig_infos { if s.signer_index > guardian_set.len_keys { diff --git a/solana/bridge/src/state.rs b/solana/bridge/src/state.rs index 3674f2dff..a56ad976e 100644 --- a/solana/bridge/src/state.rs +++ b/solana/bridge/src/state.rs @@ -349,6 +349,20 @@ impl Bridge { ] } + /// Calculates derived seeds for a signature account + pub fn derive_signature_seeds<'a>( + bridge: &Pubkey, + hash: &[u8; 32], + guardian_index: u32, + ) -> Vec> { + vec![ + "sig".as_bytes().to_vec(), + bridge.to_bytes().to_vec(), + hash.to_vec(), + guardian_index.to_le_bytes().to_vec(), + ] + } + /// Calculates a derived address for this program pub fn derive_bridge_id(program_id: &Pubkey) -> Result { Ok(Self::derive_key(program_id, &Self::derive_bridge_seeds())?.0) @@ -434,6 +448,20 @@ impl Bridge { .0) } + /// Calculates derived address for a signature account + pub fn derive_signature_id<'a>( + program_id: &Pubkey, + bridge: &Pubkey, + hash: &[u8; 32], + guardian_index: u32, + ) -> Result { + Ok(Self::derive_key( + program_id, + &Self::derive_signature_seeds(bridge, hash, guardian_index), + )? + .0) + } + pub fn derive_key( program_id: &Pubkey, seeds: &Vec>,