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

View File

@ -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<Result<LockupEvent, Status>>;
@ -256,8 +234,7 @@ fn pack_sig_verification_txs<'a>(
bridge: &Pubkey,
vaa: &VAA,
sender_keypair: &'a Keypair,
sign_keypair: &'a Keypair,
) -> Result<Vec<(Transaction, Vec<&'a Keypair>)>, Status> {
) -> Result<Vec<Transaction>, 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<Transaction> = 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::<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_u8(secp_ix_index);
secp_payload.write_u8(0);
secp_payload.write_u16::<LittleEndian>(message_offset 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;
}
@ -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::<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],
))
}
verify_txs.push(Transaction::new_with_payer(
&[secp_ix, verify_ix],
Some(&sender_keypair.pubkey()),
))
}
Ok(verify_txs)

View File

@ -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<Instruction, ProgramError> {
@ -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<Instruction, ProgramError> {
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),
];

View File

@ -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<SigInfo> = 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::<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
for s in sig_infos {
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
pub fn derive_bridge_id(program_id: &Pubkey) -> Result<Pubkey, Error> {
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<Pubkey, Error> {
Ok(Self::derive_key(
program_id,
&Self::derive_signature_seeds(bridge, hash, guardian_index),
)?
.0)
}
pub fn derive_key(
program_id: &Pubkey,
seeds: &Vec<Vec<u8>>,