Allow account reuse for message posting (#1077)

* Implement message posting with account reuse

Change-Id: I195f493f6816048f5f8f76e1f0f6e561fa0fe692

* Use different magic for unreliable messages

* guardiand: Ignore solana instructions with empty data

Co-authored-by: Csongor Kiss <ckiss@jumptrading.com>
This commit is contained in:
Hendrik Hofstadt 2022-06-27 10:57:25 +02:00 committed by GitHub
parent c3e7b33388
commit f1dba4adfe
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 724 additions and 205 deletions

View File

@ -87,8 +87,9 @@ func (c ConsistencyLevel) Commitment() (rpc.CommitmentType, error) {
}
const (
postMessageInstructionNumAccounts = 9
postMessageInstructionID = 0x01
postMessageInstructionNumAccounts = 9
postMessageInstructionID = 0x01
postMessageUnreliableInstructionID = 0x08
)
// PostMessageData represents the user-supplied, untrusted instruction data
@ -387,7 +388,11 @@ func (s *SolanaWatcher) processInstruction(ctx context.Context, logger *zap.Logg
return false, nil
}
if inst.Data[0] != postMessageInstructionID {
if len(inst.Data) == 0 {
return false, nil
}
if inst.Data[0] != postMessageInstructionID && inst.Data[0] != postMessageUnreliableInstructionID {
return false, nil
}

View File

@ -7,11 +7,13 @@ pub mod posted_vaa;
pub mod sequence;
pub mod signature_set;
pub use self::bridge::*;
pub use self::claim::*;
pub use self::fee_collector::*;
pub use self::guardian_set::*;
pub use self::posted_message::*;
pub use self::posted_vaa::*;
pub use self::sequence::*;
pub use self::signature_set::*;
pub use self::{
bridge::*,
claim::*,
fee_collector::*,
guardian_set::*,
posted_message::*,
posted_vaa::*,
sequence::*,
signature_set::*,
};

View File

@ -14,7 +14,11 @@ use solitaire::{
Owned,
};
use std::{
io::Write,
io::{
Error,
ErrorKind::InvalidData,
Write,
},
ops::{
Deref,
DerefMut,
@ -23,11 +27,20 @@ use std::{
pub type PostedMessage<'a, const State: AccountState> = Data<'a, PostedMessageData, { State }>;
// This is using the same payload as the PostedVAA for backwards compatibility.
// This will be deprecated in a future release.
#[repr(transparent)]
#[derive(Default)]
pub struct PostedMessageData(pub MessageData);
pub struct PostedMessageData {
pub message: MessageData,
}
pub type PostedMessageUnreliable<'a, const State: AccountState> =
Data<'a, PostedMessageUnreliableData, { State }>;
#[repr(transparent)]
#[derive(Default)]
pub struct PostedMessageUnreliableData {
pub message: MessageData,
}
#[derive(Debug, Default, BorshSerialize, BorshDeserialize, Clone, Serialize, Deserialize)]
pub struct MessageData {
@ -62,19 +75,36 @@ pub struct MessageData {
pub payload: Vec<u8>,
}
// PostedMessageData impls
impl BorshSerialize for PostedMessageData {
fn serialize<W: Write>(&self, writer: &mut W) -> std::io::Result<()> {
writer.write_all(b"msg")?;
BorshSerialize::serialize(&self.0, writer)
BorshSerialize::serialize(&self.message, writer)
}
}
impl BorshDeserialize for PostedMessageData {
fn deserialize(buf: &mut &[u8]) -> std::io::Result<Self> {
if buf.len() < 3 {
return Err(Error::new(InvalidData, "Not enough bytes"))
}
let expected = b"msg";
let magic: &[u8] = &buf[0..3];
if magic != expected {
return Err(Error::new(
InvalidData,
format!(
"Magic mismatch. Expected {:?} but got {:?}",
expected, magic
),
));
};
*buf = &buf[3..];
Ok(PostedMessageData(
<MessageData as BorshDeserialize>::deserialize(buf)?,
))
Ok(PostedMessageData {
message: <MessageData as BorshDeserialize>::deserialize(buf)?,
})
}
}
@ -82,19 +112,21 @@ impl Deref for PostedMessageData {
type Target = MessageData;
fn deref(&self) -> &Self::Target {
unsafe { std::mem::transmute(&self.0) }
&self.message
}
}
impl DerefMut for PostedMessageData {
fn deref_mut(&mut self) -> &mut Self::Target {
unsafe { std::mem::transmute(&mut self.0) }
&mut self.message
}
}
impl Clone for PostedMessageData {
fn clone(&self) -> Self {
PostedMessageData(self.0.clone())
PostedMessageData {
message: self.message.clone(),
}
}
}
@ -112,3 +144,73 @@ impl Owned for PostedMessageData {
AccountOwner::Other(Pubkey::from_str(env!("BRIDGE_ADDRESS")).unwrap())
}
}
// PostedMessageUnreliableData impls
impl BorshSerialize for PostedMessageUnreliableData {
fn serialize<W: Write>(&self, writer: &mut W) -> std::io::Result<()> {
writer.write_all(b"msu")?;
BorshSerialize::serialize(&self.message, writer)
}
}
impl BorshDeserialize for PostedMessageUnreliableData {
fn deserialize(buf: &mut &[u8]) -> std::io::Result<Self> {
if buf.len() < 3 {
return Err(Error::new(InvalidData, "Not enough bytes"))
}
let expected = b"msu";
let magic: &[u8] = &buf[0..3];
if magic != expected {
return Err(Error::new(
InvalidData,
format!(
"Magic mismatch. Expected {:?} but got {:?}",
expected, magic
),
));
};
*buf = &buf[3..];
Ok(PostedMessageUnreliableData {
message: <MessageData as BorshDeserialize>::deserialize(buf)?,
})
}
}
impl Deref for PostedMessageUnreliableData {
type Target = MessageData;
fn deref(&self) -> &Self::Target {
&self.message
}
}
impl DerefMut for PostedMessageUnreliableData {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.message
}
}
impl Clone for PostedMessageUnreliableData {
fn clone(&self) -> Self {
PostedMessageUnreliableData {
message: self.message.clone(),
}
}
}
#[cfg(not(feature = "cpi"))]
impl Owned for PostedMessageUnreliableData {
fn owner(&self) -> AccountOwner {
AccountOwner::This
}
}
#[cfg(feature = "cpi")]
impl Owned for PostedMessageUnreliableData {
fn owner(&self) -> AccountOwner {
use std::str::FromStr;
AccountOwner::Other(Pubkey::from_str(env!("BRIDGE_ADDRESS")).unwrap())
}
}

View File

@ -11,7 +11,11 @@ use solitaire::{
Owned,
};
use std::{
io::Write,
io::{
Error,
ErrorKind::InvalidData,
Write,
},
ops::{
Deref,
DerefMut,
@ -26,27 +30,39 @@ pub struct PostedVAADerivationData {
impl<'a, const State: AccountState> Seeded<&PostedVAADerivationData> for PostedVAA<'a, { State }> {
fn seeds(data: &PostedVAADerivationData) -> Vec<Vec<u8>> {
vec!["PostedVAA".as_bytes().to_vec(), data.payload_hash.to_vec()]
vec![b"PostedVAA".to_vec(), data.payload_hash.to_vec()]
}
}
#[repr(transparent)]
#[derive(Default)]
pub struct PostedVAAData(pub MessageData);
pub struct PostedVAAData {
pub message: MessageData,
}
impl BorshSerialize for PostedVAAData {
fn serialize<W: Write>(&self, writer: &mut W) -> std::io::Result<()> {
writer.write_all(b"vaa")?;
BorshSerialize::serialize(&self.0, writer)
BorshSerialize::serialize(&self.message, writer)
}
}
impl BorshDeserialize for PostedVAAData {
fn deserialize(buf: &mut &[u8]) -> std::io::Result<Self> {
if buf.len() < 3 {
return Err(Error::new(InvalidData, "Not enough bytes"));
}
// We accept "vaa", "msg", or "msu" because it's convenient to read all of these as PostedVAAData
let expected: [&[u8]; 3] = [b"vaa", b"msg", b"msu"];
let magic: &[u8] = &buf[0..3];
if !expected.contains(&magic) {
return Err(Error::new(InvalidData, "Magic mismatch."));
};
*buf = &buf[3..];
Ok(PostedVAAData(
<MessageData as BorshDeserialize>::deserialize(buf)?,
))
Ok(PostedVAAData {
message: <MessageData as BorshDeserialize>::deserialize(buf)?,
})
}
}
@ -54,19 +70,21 @@ impl Deref for PostedVAAData {
type Target = MessageData;
fn deref(&self) -> &Self::Target {
unsafe { std::mem::transmute(&self.0) }
&self.message
}
}
impl DerefMut for PostedVAAData {
fn deref_mut(&mut self) -> &mut Self::Target {
unsafe { std::mem::transmute(&mut self.0) }
&mut self.message
}
}
impl Clone for PostedVAAData {
fn clone(&self) -> Self {
PostedVAAData(self.0.clone())
PostedVAAData {
message: self.message.clone(),
}
}
}
#[cfg(not(feature = "cpi"))]
@ -79,8 +97,8 @@ impl Owned for PostedVAAData {
#[cfg(feature = "cpi")]
impl Owned for PostedVAAData {
fn owner(&self) -> AccountOwner {
use std::str::FromStr;
use solana_program::pubkey::Pubkey;
use std::str::FromStr;
AccountOwner::Other(Pubkey::from_str(env!("BRIDGE_ADDRESS")).unwrap())
}
}

View File

@ -3,19 +3,24 @@ use crate::{
Bridge,
FeeCollector,
PostedMessage,
PostedMessageUnreliable,
Sequence,
SequenceDerivationData,
},
error::Error::{
EmitterChanged,
InsufficientFees,
InvalidPayloadLength,
MathOverflow,
},
types::ConsistencyLevel,
IsSigned::*,
MessageData,
CHAIN_ID_SOLANA,
};
use solana_program::{
msg,
pubkey::Pubkey,
sysvar::clock::Clock,
};
use solitaire::{
@ -27,14 +32,6 @@ use solitaire::{
pub type UninitializedMessage<'b> = PostedMessage<'b, { AccountState::Uninitialized }>;
impl<'a> From<&PostMessage<'a>> for SequenceDerivationData<'a> {
fn from(accs: &PostMessage<'a>) -> Self {
SequenceDerivationData {
emitter_key: accs.emitter.key,
}
}
}
#[derive(FromAccounts)]
pub struct PostMessage<'b> {
/// Bridge config needed for fee calculation.
@ -58,6 +55,29 @@ pub struct PostMessage<'b> {
pub clock: Sysvar<'b, Clock>,
}
#[derive(FromAccounts)]
pub struct PostMessageUnreliable<'b> {
/// Bridge config needed for fee calculation.
pub bridge: Mut<Bridge<'b, { AccountState::Initialized }>>,
/// Account to store the posted message
pub message: Signer<Mut<PostedMessageUnreliable<'b, { AccountState::MaybeInitialized }>>>,
/// Emitter of the VAA
pub emitter: Signer<MaybeMut<Info<'b>>>,
/// Tracker for the emitter sequence
pub sequence: Mut<Sequence<'b>>,
/// Payer for account creation
pub payer: Mut<Signer<Info<'b>>>,
/// Account to collect tx fee
pub fee_collector: Mut<FeeCollector<'b>>,
pub clock: Sysvar<'b, Clock>,
}
#[derive(BorshDeserialize, BorshSerialize)]
pub struct PostMessageData {
/// Unique nonce for this message
@ -75,55 +95,18 @@ pub fn post_message(
accs: &mut PostMessage,
data: PostMessageData,
) -> Result<()> {
trace!("Message Address: {}", accs.message.info().key);
trace!("Emitter Address: {}", accs.emitter.info().key);
trace!("Nonce: {}", data.nonce);
accs.sequence
.verify_derivation(ctx.program_id, &(&*accs).into())?;
let fee = accs.bridge.config.fee;
// Fee handling, checking previously known balance allows us to not care who is the payer of
// this submission.
if accs
.fee_collector
.lamports()
.checked_sub(accs.bridge.last_lamports)
.ok_or(MathOverflow)?
< fee
{
trace!(
"Expected fee not found: fee, last_lamports, collector: {} {} {}",
fee,
accs.bridge.last_lamports,
accs.fee_collector.lamports(),
);
return Err(InsufficientFees.into());
}
accs.bridge.last_lamports = accs.fee_collector.lamports();
// Init sequence tracker if it does not exist yet.
if !accs.sequence.is_initialized() {
trace!("Initializing Sequence account to 0.");
accs.sequence
.create(&(&*accs).into(), ctx, accs.payer.key, Exempt)?;
}
// DO NOT REMOVE - CRITICAL OUTPUT
msg!("Sequence: {}", accs.sequence.sequence);
// Initialize transfer
trace!("Setting Message Details");
accs.message.submission_time = accs.clock.unix_timestamp as u32;
accs.message.emitter_chain = CHAIN_ID_SOLANA;
accs.message.emitter_address = accs.emitter.key.to_bytes();
accs.message.nonce = data.nonce;
accs.message.payload = data.payload;
accs.message.sequence = accs.sequence.sequence;
accs.message.consistency_level = match data.consistency_level {
ConsistencyLevel::Confirmed => 1,
ConsistencyLevel::Finalized => 32,
};
post_message_internal(
ctx,
&mut accs.bridge,
accs.message.info().key,
&mut accs.message,
&mut accs.emitter,
&mut accs.sequence,
&mut accs.payer,
&mut accs.fee_collector,
&mut accs.clock,
data,
)?;
// Create message account
let size = accs.message.size();
@ -137,9 +120,126 @@ pub fn post_message(
NotSigned,
)?;
// Bump sequence number
trace!("New Sequence: {}", accs.sequence.sequence + 1);
accs.sequence.sequence += 1;
Ok(())
}
/// Post a message while reusing the message account. This saves the rent that would be required for
/// allocating a new message account. When an account is reused and the guardians don't pick up the
/// message due to network instability or a bug there is NO way to recover the message if it has
/// been overwritten. This makes this instruction useful for use-cases that require high number of
/// messages to be published but don't require 100% delivery guarantee.
/// DO NOT USE THIS FOR USE-CASES THAT MOVE VALUE; MESSAGES MAY NOT BE DELIVERED
pub fn post_message_unreliable(
ctx: &ExecutionContext,
accs: &mut PostMessageUnreliable,
data: PostMessageData,
) -> Result<()> {
// Accounts can't be resized so the payload sizes need to match
if accs.message.is_initialized() && accs.message.payload.len() != data.payload.len() {
return Err(InvalidPayloadLength.into());
}
// The emitter must be identical
if accs.message.is_initialized() && accs.emitter.key.to_bytes() != accs.message.emitter_address
{
return Err(EmitterChanged.into());
}
post_message_internal(
ctx,
&mut accs.bridge,
accs.message.info().key,
&mut accs.message,
&mut accs.emitter,
&mut accs.sequence,
&mut accs.payer,
&mut accs.fee_collector,
&mut accs.clock,
data,
)?;
if !accs.message.is_initialized() {
// Create message account
let size = accs.message.size();
create_account(
ctx,
accs.message.info(),
accs.payer.key,
Exempt,
size,
ctx.program_id,
NotSigned,
)?;
}
Ok(())
}
#[allow(unused_variables)] // message_key is used in `trace!`
fn post_message_internal<'b>(
ctx: &ExecutionContext,
bridge: &mut Mut<Bridge<'b, { AccountState::Initialized }>>,
message_key: &Pubkey,
message: &mut MessageData,
emitter: &mut Signer<MaybeMut<Info<'b>>>,
sequence: &mut Mut<Sequence<'b>>,
payer: &mut Mut<Signer<Info<'b>>>,
fee_collector: &mut Mut<FeeCollector<'b>>,
clock: &mut Sysvar<'b, Clock>,
data: PostMessageData,
) -> Result<()> {
trace!("Message Address: {}", message_key);
trace!("Emitter Address: {}", emitter.info().key);
trace!("Nonce: {}", data.nonce);
let sequence_derivation = SequenceDerivationData {
emitter_key: emitter.key,
};
sequence.verify_derivation(ctx.program_id, &sequence_derivation)?;
let fee = bridge.config.fee;
// Fee handling, checking previously known balance allows us to not care who is the payer of
// this submission.
if fee_collector
.lamports()
.checked_sub(bridge.last_lamports)
.ok_or(MathOverflow)?
< fee
{
trace!(
"Expected fee not found: fee, last_lamports, collector: {} {} {}",
fee,
bridge.last_lamports,
fee_collector.lamports(),
);
return Err(InsufficientFees.into());
}
bridge.last_lamports = fee_collector.lamports();
// Init sequence tracker if it does not exist yet.
if !sequence.is_initialized() {
trace!("Initializing Sequence account to 0.");
sequence.create(&sequence_derivation, ctx, payer.key, Exempt)?;
}
// DO NOT REMOVE - CRITICAL OUTPUT
msg!("Sequence: {}", sequence.sequence);
// Initialize transfer
trace!("Setting Message Details");
message.submission_time = clock.unix_timestamp as u32;
message.emitter_chain = CHAIN_ID_SOLANA;
message.emitter_address = emitter.key.to_bytes();
message.nonce = data.nonce;
message.payload = data.payload;
message.sequence = sequence.sequence;
message.consistency_level = match data.consistency_level {
ConsistencyLevel::Confirmed => 1,
ConsistencyLevel::Finalized => 32,
};
// Bump sequence number
trace!("New Sequence: {}", sequence.sequence + 1);
sequence.sequence += 1;
Ok(())
}

View File

@ -24,6 +24,8 @@ pub enum Error {
TooManyGuardians,
VAAAlreadyExecuted,
VAAInvalid,
InvalidPayloadLength,
EmitterChanged,
}
/// Errors thrown by the program will bubble up to the solitaire wrapper, which needs a way to

View File

@ -104,7 +104,6 @@ pub fn post_message(
Ok(Instruction {
program_id,
accounts: vec![
AccountMeta::new(bridge, false),
AccountMeta::new(message, true),
@ -116,7 +115,6 @@ pub fn post_message(
AccountMeta::new_readonly(sysvar::rent::id(), false),
AccountMeta::new_readonly(solana_program::system_program::id(), false),
],
data: (
crate::instruction::Instruction::PostMessage,
PostMessageData {
@ -129,6 +127,49 @@ pub fn post_message(
})
}
pub fn post_message_unreliable(
program_id: Pubkey,
payer: Pubkey,
emitter: Pubkey,
message: Pubkey,
nonce: u32,
payload: Vec<u8>,
commitment: ConsistencyLevel,
) -> solitaire::Result<Instruction> {
let bridge = Bridge::<'_, { AccountState::Uninitialized }>::key(None, &program_id);
let fee_collector = FeeCollector::<'_>::key(None, &program_id);
let sequence = Sequence::<'_>::key(
&SequenceDerivationData {
emitter_key: &emitter,
},
&program_id,
);
Ok(Instruction {
program_id,
accounts: vec![
AccountMeta::new(bridge, false),
AccountMeta::new(message, true),
AccountMeta::new_readonly(emitter, true),
AccountMeta::new(sequence, false),
AccountMeta::new(payer, true),
AccountMeta::new(fee_collector, false),
AccountMeta::new_readonly(sysvar::clock::id(), false),
AccountMeta::new_readonly(sysvar::rent::id(), false),
AccountMeta::new_readonly(solana_program::system_program::id(), false),
],
data: (
crate::instruction::Instruction::PostMessageUnreliable,
PostMessageData {
nonce,
payload,
consistency_level: commitment,
},
)
.try_to_vec()?,
})
}
pub fn verify_signatures(
program_id: Pubkey,
payer: Pubkey,

View File

@ -1,4 +1,3 @@
#![feature(adt_const_params)]
#![allow(non_upper_case_globals)]
#![allow(incomplete_features)]
@ -20,32 +19,37 @@ extern crate wasm_bindgen;
pub mod wasm;
pub mod accounts;
pub use accounts::{
BridgeConfig,
BridgeData,
Claim,
ClaimData,
ClaimDerivationData,
ClaimDerivationData,
FeeCollector,
GuardianSet,
GuardianSetData,
GuardianSetDerivationData,
MessageData,
PostedMessage,
PostedMessageData,
MessageData,
PostedMessageUnreliable,
PostedMessageUnreliableData,
PostedVAA,
PostedVAAData,
Sequence,
SequenceTracker,
SequenceDerivationData,
SequenceTracker,
SignatureSet,
SignatureSetData,
};
pub mod api;
pub use api::{
initialize,
post_message,
post_message_unreliable,
post_vaa,
set_fees,
transfer_fees,
@ -56,6 +60,7 @@ pub use api::{
InitializeData,
PostMessage,
PostMessageData,
PostMessageUnreliable,
PostVAA,
PostVAAData,
SetFees,
@ -93,4 +98,5 @@ solitaire! {
UpgradeContract => upgrade_contract,
UpgradeGuardianSet => upgrade_guardian_set,
VerifySignatures => verify_signatures,
PostMessageUnreliable => post_message_unreliable,
}

View File

@ -33,6 +33,7 @@ use crate::{
instructions::{
hash_vaa,
post_message,
post_message_unreliable,
post_vaa,
set_fees,
transfer_fees,
@ -82,6 +83,34 @@ pub fn post_message_ix(
return JsValue::from_serde(&ix).unwrap();
}
#[wasm_bindgen]
pub fn post_message_unreliable_ix(
program_id: String,
payer: String,
emitter: String,
message: String,
nonce: u32,
msg: Vec<u8>,
consistency: String,
) -> JsValue {
let consistency_level = match consistency.as_str() {
"CONFIRMED" => ConsistencyLevel::Confirmed,
"FINALIZED" => ConsistencyLevel::Finalized,
_ => panic!("invalid consistency level"),
};
let ix = post_message_unreliable(
Pubkey::from_str(program_id.as_str()).unwrap(),
Pubkey::from_str(payer.as_str()).unwrap(),
Pubkey::from_str(emitter.as_str()).unwrap(),
Pubkey::from_str(message.as_str()).unwrap(),
nonce,
msg,
consistency_level,
)
.unwrap();
return JsValue::from_serde(&ix).unwrap();
}
#[wasm_bindgen]
pub fn post_vaa_ix(
program_id: String,
@ -363,7 +392,7 @@ pub fn claim_address(program_id: String, vaa: Vec<u8>) -> Vec<u8> {
#[wasm_bindgen]
pub fn parse_posted_message(data: Vec<u8>) -> JsValue {
JsValue::from_serde(&PostedVAAData::try_from_slice(data.as_slice()).unwrap().0).unwrap()
JsValue::from_serde(&PostedVAAData::try_from_slice(data.as_slice()).unwrap().message).unwrap()
}
#[wasm_bindgen]

View File

@ -116,12 +116,7 @@ mod helpers {
/// Fetch account balance
pub async fn get_account_balance(client: &mut BanksClient, account: Pubkey) -> u64 {
client
.get_account(account)
.await
.unwrap()
.unwrap()
.lamports
client.get_account(account).await.unwrap().unwrap().lamports
}
/// Generate `count` secp256k1 private keys, along with their ethereum-styled public key
@ -236,6 +231,8 @@ mod helpers {
program: &Pubkey,
payer: &Keypair,
emitter: &Keypair,
// when None, then a new keypair is generated
message: Option<&Keypair>,
nonce: u32,
data: Vec<u8>,
fee: u64,
@ -243,7 +240,12 @@ mod helpers {
// Transfer money into the fee collector as it needs a balance/must exist.
let fee_collector = FeeCollector::<'_>::key(None, program);
let message = Keypair::new();
let new_message_pair = &Keypair::new();
let message: &Keypair = match message {
Some(keypair) => keypair,
None => new_message_pair
};
// Capture the resulting message, later functions will need this.
let instruction = instructions::post_message(
@ -272,6 +274,44 @@ mod helpers {
Ok(message.pubkey())
}
pub async fn post_message_unreliable(
client: &mut BanksClient,
program: &Pubkey,
payer: &Keypair,
emitter: &Keypair,
message: &Keypair,
nonce: u32,
data: Vec<u8>,
fee: u64,
) -> Result<(), TransportError> {
// Transfer money into the fee collector as it needs a balance/must exist.
let fee_collector = FeeCollector::<'_>::key(None, program);
// Capture the resulting message, later functions will need this.
let instruction = instructions::post_message_unreliable(
*program,
payer.pubkey(),
emitter.pubkey(),
message.pubkey(),
nonce,
data,
ConsistencyLevel::Confirmed,
)
.unwrap();
execute(
client,
payer,
&[payer, emitter, &message],
&[
system_instruction::transfer(&payer.pubkey(), &fee_collector, fee),
instruction,
],
CommitmentLevel::Processed,
)
.await
}
pub async fn verify_signatures(
client: &mut BanksClient,
program: &Pubkey,

View File

@ -145,6 +145,7 @@ async fn bridge_messages() {
program,
payer,
&emitter,
None,
nonce,
message.clone(),
10_000,
@ -153,15 +154,15 @@ async fn bridge_messages() {
.unwrap();
let posted_message: PostedVAAData = common::get_account_data(client, message_key).await;
assert_eq!(posted_message.0.vaa_version, 0);
assert_eq!(posted_message.0.consistency_level, 1);
assert_eq!(posted_message.0.nonce, nonce);
assert_eq!(posted_message.0.sequence, sequence);
assert_eq!(posted_message.0.emitter_chain, 1);
assert_eq!(&posted_message.0.emitter_address, emitter.pubkey().as_ref());
assert_eq!(posted_message.0.payload, message);
assert_eq!(posted_message.message.vaa_version, 0);
assert_eq!(posted_message.message.consistency_level, 1);
assert_eq!(posted_message.message.nonce, nonce);
assert_eq!(posted_message.message.sequence, sequence);
assert_eq!(posted_message.message.emitter_chain, 1);
assert_eq!(&posted_message.message.emitter_address, emitter.pubkey().as_ref());
assert_eq!(posted_message.message.payload, message);
assert_eq!(
posted_message.0.emitter_address,
posted_message.message.emitter_address,
emitter.pubkey().to_bytes()
);
@ -192,20 +193,20 @@ async fn bridge_messages() {
let signatures: SignatureSetData = common::get_account_data(client, signature_set).await;
// Verify on chain Message
assert_eq!(posted_message.0.vaa_version, 0);
assert_eq!(posted_message.message.vaa_version, 0);
assert_eq!(
posted_message.0.consistency_level,
posted_message.message.consistency_level,
ConsistencyLevel::Confirmed as u8
);
assert_eq!(posted_message.0.vaa_time, vaa_time);
assert_eq!(posted_message.0.vaa_signature_account, signature_set);
assert_eq!(posted_message.0.nonce, nonce);
assert_eq!(posted_message.0.sequence, sequence);
assert_eq!(posted_message.0.emitter_chain, 1);
assert_eq!(&posted_message.0.emitter_address, emitter.pubkey().as_ref());
assert_eq!(posted_message.0.payload, message);
assert_eq!(posted_message.message.vaa_time, vaa_time);
assert_eq!(posted_message.message.vaa_signature_account, signature_set);
assert_eq!(posted_message.message.nonce, nonce);
assert_eq!(posted_message.message.sequence, sequence);
assert_eq!(posted_message.message.emitter_chain, 1);
assert_eq!(&posted_message.message.emitter_address, emitter.pubkey().as_ref());
assert_eq!(posted_message.message.payload, message);
assert_eq!(
posted_message.0.emitter_address,
posted_message.message.emitter_address,
emitter.pubkey().to_bytes()
);
@ -229,6 +230,7 @@ async fn bridge_messages() {
program,
payer,
&emitter,
None,
nonce,
message.clone(),
10_000,
@ -237,15 +239,15 @@ async fn bridge_messages() {
.unwrap();
let posted_message: PostedVAAData = common::get_account_data(client, message_key).await;
assert_eq!(posted_message.0.vaa_version, 0);
assert_eq!(posted_message.0.consistency_level, 1);
assert_eq!(posted_message.0.nonce, nonce);
assert_eq!(posted_message.0.sequence, sequence);
assert_eq!(posted_message.0.emitter_chain, 1);
assert_eq!(&posted_message.0.emitter_address, emitter.pubkey().as_ref());
assert_eq!(posted_message.0.payload, message);
assert_eq!(posted_message.message.vaa_version, 0);
assert_eq!(posted_message.message.consistency_level, 1);
assert_eq!(posted_message.message.nonce, nonce);
assert_eq!(posted_message.message.sequence, sequence);
assert_eq!(posted_message.message.emitter_chain, 1);
assert_eq!(&posted_message.message.emitter_address, emitter.pubkey().as_ref());
assert_eq!(posted_message.message.payload, message);
assert_eq!(
posted_message.0.emitter_address,
posted_message.message.emitter_address,
emitter.pubkey().to_bytes()
);
@ -274,20 +276,20 @@ async fn bridge_messages() {
let signatures: SignatureSetData = common::get_account_data(client, signature_set).await;
// Verify on chain Message
assert_eq!(posted_message.0.vaa_version, 0);
assert_eq!(posted_message.message.vaa_version, 0);
assert_eq!(
posted_message.0.consistency_level,
posted_message.message.consistency_level,
ConsistencyLevel::Confirmed as u8
);
assert_eq!(posted_message.0.vaa_time, vaa_time);
assert_eq!(posted_message.0.vaa_signature_account, signature_set);
assert_eq!(posted_message.0.nonce, nonce);
assert_eq!(posted_message.0.sequence, sequence);
assert_eq!(posted_message.0.emitter_chain, 1);
assert_eq!(&posted_message.0.emitter_address, emitter.pubkey().as_ref());
assert_eq!(posted_message.0.payload, message);
assert_eq!(posted_message.message.vaa_time, vaa_time);
assert_eq!(posted_message.message.vaa_signature_account, signature_set);
assert_eq!(posted_message.message.nonce, nonce);
assert_eq!(posted_message.message.sequence, sequence);
assert_eq!(posted_message.message.emitter_chain, 1);
assert_eq!(&posted_message.message.emitter_address, emitter.pubkey().as_ref());
assert_eq!(posted_message.message.payload, message);
assert_eq!(
posted_message.0.emitter_address,
posted_message.message.emitter_address,
emitter.pubkey().to_bytes()
);
@ -300,6 +302,166 @@ async fn bridge_messages() {
}
}
// Make sure that posting messages with account reuse works and only accepts messages with the same
// length.
#[tokio::test]
async fn test_bridge_messages_unreliable() {
let (ref mut context, ref mut client, ref payer, ref program) = initialize().await;
// Data/Nonce used for emitting a message we want to prove exists. Run this twice to make sure
// that duplicate data does not clash.
let emitter = Keypair::new();
let message_key = Keypair::new();
for _ in 0..2 {
let nonce = rand::thread_rng().gen();
let message: [u8; 32] = rand::thread_rng().gen();
let sequence = context.seq.next(emitter.pubkey().to_bytes());
// Post the message, publishing the data for guardian consumption.
common::post_message_unreliable(
client,
program,
payer,
&emitter,
&message_key,
nonce,
message.to_vec(),
10_000,
)
.await
.unwrap();
// Verify on chain Message
let posted_message: PostedVAAData =
common::get_account_data(client, message_key.pubkey()).await;
assert_eq!(posted_message.message.vaa_version, 0);
assert_eq!(posted_message.message.nonce, nonce);
assert_eq!(posted_message.message.sequence, sequence);
assert_eq!(posted_message.message.emitter_chain, 1);
assert_eq!(posted_message.message.payload, message);
assert_eq!(
posted_message.message.emitter_address,
emitter.pubkey().to_bytes()
);
// Emulate Guardian behaviour, verifying the data and publishing signatures/VAA.
let (vaa, body, body_hash) =
common::generate_vaa(&emitter, message.to_vec(), nonce, sequence, 0, 1);
let signature_set =
common::verify_signatures(client, program, payer, body, &context.secret, 0)
.await
.unwrap();
common::post_vaa(client, program, payer, signature_set, vaa)
.await
.unwrap();
let message_key = PostedVAA::<'_, { AccountState::MaybeInitialized }>::key(
&PostedVAADerivationData {
payload_hash: body.to_vec(),
},
program,
);
common::sync(client, payer).await;
// Fetch chain accounts to verify state.
let posted_message: PostedVAAData = common::get_account_data(client, message_key).await;
let signatures: SignatureSetData = common::get_account_data(client, signature_set).await;
// Verify on chain vaa
assert_eq!(posted_message.message.vaa_version, 0);
assert_eq!(posted_message.message.vaa_signature_account, signature_set);
assert_eq!(posted_message.message.nonce, nonce);
assert_eq!(posted_message.message.sequence, sequence);
assert_eq!(posted_message.message.emitter_chain, 1);
assert_eq!(posted_message.message.payload, message);
assert_eq!(
posted_message.message.emitter_address,
emitter.pubkey().to_bytes()
);
// Verify on chain Signatures
assert_eq!(signatures.hash, body);
assert_eq!(signatures.guardian_set_index, 0);
for (signature, secret_key) in signatures.signatures.iter().zip(context.secret.iter()) {
assert_eq!(*signature, true);
}
}
// Make sure that posting a message with a different length fails (<len)
let nonce = rand::thread_rng().gen();
let message: [u8; 16] = rand::thread_rng().gen();
assert!(common::post_message_unreliable(
client,
program,
payer,
&emitter,
&message_key,
nonce,
message.to_vec(),
10_000,
)
.await
.is_err());
// Make sure that posting a message with a different length fails (>len)
let nonce = rand::thread_rng().gen();
let message: [u8; 128] = [0u8; 128];
assert!(common::post_message_unreliable(
client,
program,
payer,
&emitter,
&message_key,
nonce,
message.to_vec(),
10_000,
)
.await
.is_err());
}
#[tokio::test]
async fn test_bridge_messages_unreliable_do_not_override_reliable() {
let (ref mut _context, ref mut client, ref payer, ref program) = initialize().await;
let emitter = Keypair::new();
let message_key = Keypair::new();
let nonce = rand::thread_rng().gen();
let message: [u8; 32] = rand::thread_rng().gen();
// Post the message using the reliable method
common::post_message(
client,
program,
payer,
&emitter,
Some(&message_key),
nonce,
message.to_vec(),
10_000,
)
.await
.unwrap();
// Make sure that posting an unreliable message to the same message account fails
assert!(common::post_message_unreliable(
client,
program,
payer,
&emitter,
&message_key,
nonce,
message.to_vec(),
10_000,
)
.await
.is_err());
}
// Make sure that solitaire can claim accounts that already hold lamports so the protocol can't be
// DoSd by someone funding derived accounts making CreateAccount fail.
#[tokio::test]
@ -363,13 +525,13 @@ async fn test_bridge_message_prefunded_account() {
// Verify on chain Message
let posted_message: PostedVAAData = common::get_account_data(client, message.pubkey()).await;
assert_eq!(posted_message.0.vaa_version, 0);
assert_eq!(posted_message.0.nonce, nonce);
assert_eq!(posted_message.0.sequence, sequence);
assert_eq!(posted_message.0.emitter_chain, 1);
assert_eq!(posted_message.0.payload, payload);
assert_eq!(posted_message.message.vaa_version, 0);
assert_eq!(posted_message.message.nonce, nonce);
assert_eq!(posted_message.message.sequence, sequence);
assert_eq!(posted_message.message.emitter_chain, 1);
assert_eq!(posted_message.message.payload, payload);
assert_eq!(
posted_message.0.emitter_address,
posted_message.message.emitter_address,
emitter.pubkey().to_bytes()
);
}
@ -448,6 +610,7 @@ async fn guardian_set_change() {
program,
payer,
&emitter,
None,
nonce,
message.clone(),
10_000,
@ -456,15 +619,15 @@ async fn guardian_set_change() {
.unwrap();
let posted_message: PostedVAAData = common::get_account_data(client, message_key).await;
assert_eq!(posted_message.0.vaa_version, 0);
assert_eq!(posted_message.0.consistency_level, 1);
assert_eq!(posted_message.0.nonce, nonce);
assert_eq!(posted_message.0.sequence, sequence);
assert_eq!(posted_message.0.emitter_chain, 1);
assert_eq!(&posted_message.0.emitter_address, emitter.pubkey().as_ref());
assert_eq!(posted_message.0.payload, message);
assert_eq!(posted_message.message.vaa_version, 0);
assert_eq!(posted_message.message.consistency_level, 1);
assert_eq!(posted_message.message.nonce, nonce);
assert_eq!(posted_message.message.sequence, sequence);
assert_eq!(posted_message.message.emitter_chain, 1);
assert_eq!(&posted_message.message.emitter_address, emitter.pubkey().as_ref());
assert_eq!(posted_message.message.payload, message);
assert_eq!(
posted_message.0.emitter_address,
posted_message.message.emitter_address,
emitter.pubkey().to_bytes()
);
@ -510,20 +673,20 @@ async fn guardian_set_change() {
let guardian_set: GuardianSetData = common::get_account_data(client, guardian_set_key).await;
// Verify on chain Message
assert_eq!(posted_message.0.vaa_version, 0);
assert_eq!(posted_message.message.vaa_version, 0);
assert_eq!(
posted_message.0.consistency_level,
posted_message.message.consistency_level,
ConsistencyLevel::Confirmed as u8
);
assert_eq!(posted_message.0.vaa_time, vaa_time);
assert_eq!(posted_message.0.vaa_signature_account, signature_set);
assert_eq!(posted_message.0.nonce, nonce);
assert_eq!(posted_message.0.sequence, sequence);
assert_eq!(posted_message.0.emitter_chain, 1);
assert_eq!(&posted_message.0.emitter_address, emitter.pubkey().as_ref());
assert_eq!(posted_message.0.payload, message);
assert_eq!(posted_message.message.vaa_time, vaa_time);
assert_eq!(posted_message.message.vaa_signature_account, signature_set);
assert_eq!(posted_message.message.nonce, nonce);
assert_eq!(posted_message.message.sequence, sequence);
assert_eq!(posted_message.message.emitter_chain, 1);
assert_eq!(&posted_message.message.emitter_address, emitter.pubkey().as_ref());
assert_eq!(posted_message.message.payload, message);
assert_eq!(
posted_message.0.emitter_address,
posted_message.message.emitter_address,
emitter.pubkey().to_bytes()
);
@ -544,6 +707,7 @@ async fn guardian_set_change() {
program,
payer,
&emitter,
None,
nonce,
message.clone(),
10_000,
@ -577,19 +741,19 @@ async fn guardian_set_change() {
let signatures: SignatureSetData = common::get_account_data(client, signature_set).await;
// Verify on chain Message
assert_eq!(posted_message.0.vaa_version, 0);
assert_eq!(posted_message.message.vaa_version, 0);
assert_eq!(
posted_message.0.consistency_level,
posted_message.message.consistency_level,
ConsistencyLevel::Confirmed as u8
);
assert_eq!(posted_message.0.vaa_time, vaa_time);
assert_eq!(posted_message.0.vaa_signature_account, signature_set);
assert_eq!(posted_message.0.nonce, nonce);
assert_eq!(posted_message.0.sequence, sequence);
assert_eq!(posted_message.0.emitter_chain, 1);
assert_eq!(posted_message.0.payload, message);
assert_eq!(posted_message.message.vaa_time, vaa_time);
assert_eq!(posted_message.message.vaa_signature_account, signature_set);
assert_eq!(posted_message.message.nonce, nonce);
assert_eq!(posted_message.message.sequence, sequence);
assert_eq!(posted_message.message.emitter_chain, 1);
assert_eq!(posted_message.message.payload, message);
assert_eq!(
posted_message.0.emitter_address,
posted_message.message.emitter_address,
emitter.pubkey().to_bytes()
);
@ -626,6 +790,7 @@ async fn guardian_set_change_fails() {
program,
payer,
&emitter,
None,
nonce,
message.clone(),
10_000,
@ -666,6 +831,7 @@ async fn set_fees() {
program,
payer,
&emitter,
None,
nonce,
message.clone(),
10_000,
@ -705,7 +871,7 @@ async fn set_fees() {
let nonce = rand::thread_rng().gen();
let message = [0u8; 32].to_vec();
assert!(
common::post_message(client, program, payer, &emitter, nonce, message.clone(), 50)
common::post_message(client, program, payer, &emitter, None, nonce, message.clone(), 50)
.await
.is_err()
);
@ -726,6 +892,7 @@ async fn set_fees() {
program,
payer,
&emitter,
None,
nonce,
message.clone(),
100,
@ -761,20 +928,20 @@ async fn set_fees() {
let signatures: SignatureSetData = common::get_account_data(client, signature_set).await;
// Verify on chain Message
assert_eq!(posted_message.0.vaa_version, 0);
assert_eq!(posted_message.message.vaa_version, 0);
assert_eq!(
posted_message.0.consistency_level,
posted_message.message.consistency_level,
ConsistencyLevel::Confirmed as u8
);
assert_eq!(posted_message.0.vaa_time, vaa_time);
assert_eq!(posted_message.0.vaa_signature_account, signature_set);
assert_eq!(posted_message.0.nonce, nonce);
assert_eq!(posted_message.0.sequence, sequence);
assert_eq!(posted_message.0.emitter_chain, 1);
assert_eq!(&posted_message.0.emitter_address, emitter.pubkey().as_ref());
assert_eq!(posted_message.0.payload, message);
assert_eq!(posted_message.message.vaa_time, vaa_time);
assert_eq!(posted_message.message.vaa_signature_account, signature_set);
assert_eq!(posted_message.message.nonce, nonce);
assert_eq!(posted_message.message.sequence, sequence);
assert_eq!(posted_message.message.emitter_chain, 1);
assert_eq!(&posted_message.message.emitter_address, emitter.pubkey().as_ref());
assert_eq!(posted_message.message.payload, message);
assert_eq!(
posted_message.0.emitter_address,
posted_message.message.emitter_address,
emitter.pubkey().to_bytes()
);
@ -808,6 +975,7 @@ async fn set_fees_fails() {
program,
payer,
&emitter,
None,
nonce,
message.clone(),
10_000,
@ -856,6 +1024,7 @@ async fn free_fees() {
program,
payer,
&emitter,
None,
nonce,
message.clone(),
10_000,
@ -896,7 +1065,7 @@ async fn free_fees() {
let nonce = rand::thread_rng().gen();
let message = [0u8; 32].to_vec();
let _message_key =
common::post_message(client, program, payer, &emitter, nonce, message.clone(), 0)
common::post_message(client, program, payer, &emitter, None, nonce, message.clone(), 0)
.await
.unwrap();
@ -928,20 +1097,20 @@ async fn free_fees() {
let signatures: SignatureSetData = common::get_account_data(client, signature_set).await;
// Verify on chain Message
assert_eq!(posted_message.0.vaa_version, 0);
assert_eq!(posted_message.message.vaa_version, 0);
assert_eq!(
posted_message.0.consistency_level,
posted_message.message.consistency_level,
ConsistencyLevel::Confirmed as u8
);
assert_eq!(posted_message.0.vaa_time, vaa_time);
assert_eq!(posted_message.0.vaa_signature_account, signature_set);
assert_eq!(posted_message.0.nonce, nonce);
assert_eq!(posted_message.0.sequence, sequence);
assert_eq!(posted_message.0.emitter_chain, 1);
assert_eq!(&posted_message.0.emitter_address, emitter.pubkey().as_ref());
assert_eq!(posted_message.0.payload, message);
assert_eq!(posted_message.message.vaa_time, vaa_time);
assert_eq!(posted_message.message.vaa_signature_account, signature_set);
assert_eq!(posted_message.message.nonce, nonce);
assert_eq!(posted_message.message.sequence, sequence);
assert_eq!(posted_message.message.emitter_chain, 1);
assert_eq!(&posted_message.message.emitter_address, emitter.pubkey().as_ref());
assert_eq!(posted_message.message.payload, message);
assert_eq!(
posted_message.0.emitter_address,
posted_message.message.emitter_address,
emitter.pubkey().to_bytes()
);
@ -977,6 +1146,7 @@ async fn transfer_fees() {
program,
payer,
&emitter,
None,
nonce,
message.clone(),
10_000,
@ -1038,6 +1208,7 @@ async fn transfer_fees_fails() {
program,
payer,
&emitter,
None,
nonce,
message.clone(),
10_000,
@ -1097,6 +1268,7 @@ async fn transfer_too_much() {
program,
payer,
&emitter,
None,
nonce,
message.clone(),
10_000,
@ -1167,14 +1339,14 @@ async fn foreign_bridge_messages() {
let posted_message: PostedVAAData = common::get_account_data(client, message_key).await;
let signatures: SignatureSetData = common::get_account_data(client, signature_set).await;
assert_eq!(posted_message.0.vaa_version, 0);
assert_eq!(posted_message.0.vaa_signature_account, signature_set);
assert_eq!(posted_message.0.nonce, nonce);
assert_eq!(posted_message.0.sequence, sequence);
assert_eq!(posted_message.0.emitter_chain, 2);
assert_eq!(posted_message.0.payload, message);
assert_eq!(posted_message.message.vaa_version, 0);
assert_eq!(posted_message.message.vaa_signature_account, signature_set);
assert_eq!(posted_message.message.nonce, nonce);
assert_eq!(posted_message.message.sequence, sequence);
assert_eq!(posted_message.message.emitter_chain, 2);
assert_eq!(posted_message.message.payload, message);
assert_eq!(
posted_message.0.emitter_address,
posted_message.message.emitter_address,
emitter.pubkey().to_bytes()
);
@ -1214,6 +1386,7 @@ async fn transfer_total_fails() {
program,
payer,
&emitter,
None,
nonce,
message.clone(),
10_000,
@ -1278,6 +1451,7 @@ async fn upgrade_contract() {
program,
payer,
&emitter,
None,
nonce,
message.clone(),
10_000,