solana: create sig verify account in instruction

This allows multiple guardians to submit the signatures in parallel without causing costs with all transactions because conflicting txs won't be mined.
This commit is contained in:
Hendrik Hofstadt 2020-10-03 21:19:29 +02:00
parent 25533f0264
commit ddd2c901bd
5 changed files with 105 additions and 86 deletions

View File

@ -31,9 +31,11 @@ Checks secp checks (in the previous instruction) and stores results.
| Index | Name | Type | signer | writeable | empty | derived | | Index | Name | Type | signer | writeable | empty | derived |
| ----- | ------ | ------------ | ------ | --------- | ----- | ------- | | ----- | ------ | ------------ | ------ | --------- | ----- | ------- |
| 0 | bridge_p | BridgeProgram | | | | | | 0 | bridge_p | BridgeProgram | | | | |
| 1 | instructions | Sysvar | | | | ✅ | | 1 | sys | SystemProgram | | | | |
| 2 | sig_status | SignatureState | | ✅ | | | | 2 | instructions | Sysvar | | | | ✅ |
| 3 | guardian_set | GuardianSet | | | | ✅ | | 3 | sig_status | SignatureState | | ✅ | | |
| 4 | guardian_set | GuardianSet | | | | ✅ |
| 5 | payer | Account | ✅ | | | |
#### TransferOut #### TransferOut

View File

@ -1,26 +1,17 @@
use std::env; use std::{env, io::Write, mem::size_of, str::FromStr};
use std::{io::Write, mem::size_of};
use std::str::FromStr;
use byteorder::{BigEndian, LittleEndian, ReadBytesExt, WriteBytesExt}; use byteorder::{BigEndian, LittleEndian, ReadBytesExt, WriteBytesExt};
use solana_client::{ use solana_client::{
client_error::ClientError, rpc_client::RpcClient, rpc_config::RpcSendTransactionConfig, client_error::ClientError, rpc_client::RpcClient, rpc_config::RpcSendTransactionConfig,
}; };
use solana_sdk::commitment_config::{CommitmentConfig, CommitmentLevel};
use solana_sdk::instruction::Instruction;
use solana_sdk::{ use solana_sdk::{
commitment_config::{CommitmentConfig, CommitmentLevel},
instruction::Instruction,
pubkey::Pubkey, pubkey::Pubkey,
signature::{read_keypair_file, write_keypair_file, Keypair, Signature, Signer}, signature::{read_keypair_file, write_keypair_file, Keypair, Signature, Signer},
system_instruction::create_account,
transaction::Transaction, transaction::Transaction,
}; };
use tokio::sync::mpsc; use tokio::sync::mpsc;
use tonic::{transport::Server, Code, Request, Response, Status}; use tonic::{transport::Server, Code, Request, Response, Status};
use service::{ use service::{
@ -31,7 +22,7 @@ use service::{
}; };
use spl_bridge::{ use spl_bridge::{
instruction::{post_vaa, verify_signatures, VerifySigPayload, CHAIN_ID_SOLANA}, instruction::{post_vaa, verify_signatures, VerifySigPayload, CHAIN_ID_SOLANA},
state::{Bridge, GuardianSet, SignatureState, TransferOutProposal}, state::{Bridge, GuardianSet, TransferOutProposal},
vaa::VAA, vaa::VAA,
}; };
@ -74,8 +65,6 @@ impl Agent for AgentImpl {
std::thread::spawn(move || { std::thread::spawn(move || {
let rpc = RpcClient::new(rpc_url); let rpc = RpcClient::new(rpc_url);
let sig_key = solana_sdk::signature::Keypair::new();
let mut vaa = match VAA::deserialize(&request.get_ref().vaa) { let mut vaa = match VAA::deserialize(&request.get_ref().vaa) {
Ok(v) => v, Ok(v) => v,
Err(e) => { 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 // Strip signatures
vaa.signatures = Vec::new(); vaa.signatures = Vec::new();
let ix = match post_vaa( let ix = match post_vaa(&bridge, &key.pubkey(), vaa.serialize().unwrap()) {
&bridge,
&key.pubkey(),
&sig_key.pubkey(),
vaa.serialize().unwrap(),
) {
Ok(v) => v, Ok(v) => v,
Err(e) => { Err(e) => {
return Err(Status::new( 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 { for mut tx in verify_txs {
match sign_and_send(&rpc, &mut tx, signers) { match sign_and_send(&rpc, &mut tx, vec![&key]) {
Ok(_) => (), Ok(_) => (),
Err(e) => { Err(e) => {
return Err(Status::new( 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]) { match sign_and_send(&rpc, &mut transaction2, vec![&key]) {
Ok(s) => Ok(Response::new(SubmitVaaResponse { Ok(s) => Ok(Response::new(SubmitVaaResponse {
signature: s.to_string(), signature: s.to_string(),
@ -129,12 +113,6 @@ impl Agent for AgentImpl {
}) })
.join() .join()
.unwrap() .unwrap()
//check_fee_payer_balance(
// config,
// minimum_balance_for_rent_exemption
// + fee_calculator.calculate_fee(&transaction.message()),
//)?;
} }
type WatchLockupsStream = mpsc::Receiver<Result<LockupEvent, Status>>; type WatchLockupsStream = mpsc::Receiver<Result<LockupEvent, Status>>;
@ -256,8 +234,7 @@ fn pack_sig_verification_txs<'a>(
bridge: &Pubkey, bridge: &Pubkey,
vaa: &VAA, vaa: &VAA,
sender_keypair: &'a Keypair, sender_keypair: &'a Keypair,
sign_keypair: &'a Keypair, ) -> Result<Vec<Transaction>, Status> {
) -> Result<Vec<(Transaction, Vec<&'a Keypair>)>, Status> {
// Load guardian set // Load guardian set
let bridge_key = Bridge::derive_bridge_id(bridge).unwrap(); let bridge_key = Bridge::derive_bridge_id(bridge).unwrap();
let guardian_key = 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<Transaction> = Vec::new();
for (tx_index, chunk) in signature_items.chunks(6).enumerate() { for (tx_index, chunk) in signature_items.chunks(6).enumerate() {
let mut secp_payload = Vec::new(); let mut secp_payload = Vec::new();
let mut signature_status = [-1i8; 20]; let mut signature_status = [-1i8; 20];
@ -322,16 +302,15 @@ fn pack_sig_verification_txs<'a>(
// 1 number of signatures // 1 number of signatures
secp_payload.write_u8(chunk.len() as u8); 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) // Secp signature info description (11 bytes * n)
for (i, s) in chunk.iter().enumerate() { for (i, s) in chunk.iter().enumerate() {
secp_payload.write_u16::<LittleEndian>((data_offset + 85 * i) as u16); secp_payload.write_u16::<LittleEndian>((data_offset + 85 * i) as u16);
secp_payload.write_u8(secp_ix_index); secp_payload.write_u8(0);
secp_payload.write_u16::<LittleEndian>((data_offset + 85 * i + 65) as u16); secp_payload.write_u16::<LittleEndian>((data_offset + 85 * i + 65) as u16);
secp_payload.write_u8(secp_ix_index); secp_payload.write_u8(0);
secp_payload.write_u16::<LittleEndian>(message_offset as u16); secp_payload.write_u16::<LittleEndian>(message_offset as u16);
secp_payload.write_u16::<LittleEndian>(vaa_body.len() as u16); secp_payload.write_u16::<LittleEndian>(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; signature_status[s.index as usize] = i as i8;
} }
@ -353,10 +332,13 @@ fn pack_sig_verification_txs<'a>(
let payload = VerifySigPayload { let payload = VerifySigPayload {
signers: signature_status, signers: signature_status,
hash: vaa_hash, hash: vaa_hash,
initial_creation: tx_index == 0,
}; };
let verify_ix = match verify_signatures( let verify_ix = match verify_signatures(
&bridge, &bridge,
&sign_keypair.pubkey(), &signature_acc,
&sender_keypair.pubkey(),
vaa.guardian_set_index, vaa.guardian_set_index,
&payload, &payload,
) { ) {
@ -369,32 +351,10 @@ fn pack_sig_verification_txs<'a>(
} }
}; };
if tx_index == 0 { verify_txs.push(Transaction::new_with_payer(
// Instruction for creating the signature status account &[secp_ix, verify_ix],
let min_sig_rent = rpc Some(&sender_keypair.pubkey()),
.get_minimum_balance_for_rent_exemption(size_of::<SignatureState>()) ))
.unwrap();
let create_ix = create_account(
&sender_keypair.pubkey(),
&sign_keypair.pubkey(),
min_sig_rent,
size_of::<SignatureState>() 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],
))
}
} }
Ok(verify_txs) Ok(verify_txs)

View File

@ -81,6 +81,8 @@ pub struct VerifySigPayload {
pub hash: [u8; 32], pub hash: [u8; 32],
/// instruction indices of signers (-1 for missing) /// instruction indices of signers (-1 for missing)
pub signers: [i8; MAX_LEN_GUARDIAN_KEYS], 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. /// Instructions supported by the SwapInfo program.
@ -336,6 +338,7 @@ pub fn transfer_out(
pub fn verify_signatures( pub fn verify_signatures(
program_id: &Pubkey, program_id: &Pubkey,
signature_acc: &Pubkey, signature_acc: &Pubkey,
payer: &Pubkey,
guardian_set_id: u32, guardian_set_id: u32,
p: &VerifySigPayload, p: &VerifySigPayload,
) -> Result<Instruction, ProgramError> { ) -> Result<Instruction, ProgramError> {
@ -347,9 +350,11 @@ pub fn verify_signatures(
let accounts = vec![ let accounts = vec![
AccountMeta::new_readonly(*program_id, false), 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_readonly(solana_sdk::sysvar::instructions::id(), false),
AccountMeta::new(*signature_acc, false), AccountMeta::new(*signature_acc, false),
AccountMeta::new_readonly(guardian_set_key, false), AccountMeta::new_readonly(guardian_set_key, false),
AccountMeta::new(*payer, true),
]; ];
Ok(Instruction { Ok(Instruction {
@ -364,7 +369,6 @@ pub fn verify_signatures(
pub fn post_vaa( pub fn post_vaa(
program_id: &Pubkey, program_id: &Pubkey,
payer: &Pubkey, payer: &Pubkey,
signature_key: &Pubkey,
v: VAAData, v: VAAData,
) -> Result<Instruction, ProgramError> { ) -> Result<Instruction, ProgramError> {
let mut data = v.clone(); 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)?; 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 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![ let mut accounts = vec![
AccountMeta::new_readonly(*program_id, false), AccountMeta::new_readonly(*program_id, false),
AccountMeta::new_readonly(solana_sdk::system_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(bridge_key, false),
AccountMeta::new(guardian_set_key, false), AccountMeta::new(guardian_set_key, false),
AccountMeta::new(claim_key, false), AccountMeta::new(claim_key, false),
AccountMeta::new(*signature_key, false), AccountMeta::new(signature_acc, false),
AccountMeta::new(*payer, true), AccountMeta::new(*payer, true),
]; ];

View File

@ -189,28 +189,14 @@ impl Bridge {
) -> ProgramResult { ) -> ProgramResult {
let account_info_iter = &mut accounts.iter(); let account_info_iter = &mut accounts.iter();
next_account_info(account_info_iter)?; // Bridge program 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 instruction_accounts = next_account_info(account_info_iter)?;
let sig_info = 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 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 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<SigInfo> = payload let sig_infos: Vec<SigInfo> = payload
.signers .signers
.iter() .iter()
@ -310,6 +296,38 @@ impl Bridge {
return Err(ProgramError::InvalidArgument); 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::<SignatureState>(
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 // Check addresses
for s in sig_infos { for s in sig_infos {
if s.signer_index > guardian_set.len_keys { if s.signer_index > guardian_set.len_keys {

View File

@ -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<u8>> {
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 /// Calculates a derived address for this program
pub fn derive_bridge_id(program_id: &Pubkey) -> Result<Pubkey, Error> { pub fn derive_bridge_id(program_id: &Pubkey) -> Result<Pubkey, Error> {
Ok(Self::derive_key(program_id, &Self::derive_bridge_seeds())?.0) Ok(Self::derive_key(program_id, &Self::derive_bridge_seeds())?.0)
@ -434,6 +448,20 @@ impl Bridge {
.0) .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<Pubkey, Error> {
Ok(Self::derive_key(
program_id,
&Self::derive_signature_seeds(bridge, hash, guardian_index),
)?
.0)
}
pub fn derive_key( pub fn derive_key(
program_id: &Pubkey, program_id: &Pubkey,
seeds: &Vec<Vec<u8>>, seeds: &Vec<Vec<u8>>,