Skip to content

Commit 6382f54

Browse files
author
Stanisław Drozd
authored
Wormhole message reuse via post_message_unreliable (#261)
* pyth2wormhole: Implement reusable message PDAs This changeset converts attest() to use a new PDA for reusable/unreliable wormhole message account. Each PDA is tied to a given attest() payer with an index that lets them rotate a number of message accounts. Keeping the appropriate timing of the reuses is up to the message owner, who should rotate a number of PDAs. * pyth2wormhole-client: Add a message acc rotation impl * p2w attest(): Integrate with bumped wormhole and fix call issues * p2w-client: Format code, fix test_attest, refactor message index * Dockerfile.client, Dockerfile.p2w-attest: Improve caching The main improvement comes from running cargo-install from within a workspace which lets us cache target/ * p2w-client: Make reusable messages configurable in yaml * p2w on-chain: refactor to unreliable-only, adjust msg balance, nits * p2w-client: P2WMessageIndex -> P2WMessageQueue, queue tests * p2w-client: Add a hard limit to the message account queue
1 parent a2a0f6e commit 6382f54

File tree

13 files changed

+400
-118
lines changed

13 files changed

+400
-118
lines changed

Dockerfile.client

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ RUN apt-get update && apt-get install -yq libudev-dev ncat
99
RUN curl -fsSL https://deb.nodesource.com/setup_16.x | bash - && apt-get install -y nodejs
1010

1111
COPY solana /usr/src/solana
12-
WORKDIR /usr/src/solana
12+
WORKDIR /usr/src/solana/pyth2wormhole
1313

1414
RUN --mount=type=cache,target=/root/.cache \
1515
cargo install --version =2.0.12 --locked spl-token-cli
@@ -23,6 +23,7 @@ ENV BRIDGE_ADDRESS="Bridge1p5gheXUvJ6jGWGeCsgPKgnE3YgdGKRVCMY9o"
2323

2424
RUN --mount=type=cache,target=/root/.cache \
2525
--mount=type=cache,target=/usr/local/cargo/registry,id=cargo_registry \
26+
--mount=type=cache,target=target,id=cargo_registry \
2627
set -xe && \
2728
cargo install bridge_client --git https://github.com/certusone/wormhole --tag $WORMHOLE_TAG --locked --root /usr/local && \
2829
cargo install token_bridge_client --git https://github.com/certusone/wormhole --tag $WORMHOLE_TAG --locked --root /usr/local

solana/pyth2wormhole/client/src/attestation_cfg.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@ use solana_program::pubkey::Pubkey;
1515
/// Pyth2wormhole config specific to attestation requests
1616
#[derive(Debug, Deserialize, Serialize, PartialEq)]
1717
pub struct AttestationConfig {
18+
#[serde(default = "default_min_msg_reuse_interval_ms")]
19+
pub min_msg_reuse_interval_ms: u64,
20+
#[serde(default = "default_max_msg_accounts")]
21+
pub max_msg_accounts: u64,
1822
pub symbol_groups: Vec<SymbolGroup>,
1923
}
2024

@@ -26,6 +30,14 @@ pub struct SymbolGroup {
2630
pub symbols: Vec<P2WSymbol>,
2731
}
2832

33+
pub const fn default_max_msg_accounts() -> u64 {
34+
1_000_000
35+
}
36+
37+
pub const fn default_min_msg_reuse_interval_ms() -> u64 {
38+
10_000 // 10s
39+
}
40+
2941
pub const fn default_min_interval_secs() -> u64 {
3042
60
3143
}
@@ -149,6 +161,8 @@ mod tests {
149161
};
150162

151163
let cfg = AttestationConfig {
164+
min_msg_reuse_interval_ms: 1000,
165+
max_msg_accounts: 100_000,
152166
symbol_groups: vec![fastbois, slowbois],
153167
};
154168

solana/pyth2wormhole/client/src/cli.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,9 @@ pub enum Action {
116116
#[clap(long = "is-active")]
117117
is_active: Option<bool>,
118118
},
119-
#[clap(about = "Migrate existing pyth2wormhole program settings to a newer format version. Client version must match the deployed contract.")]
119+
#[clap(
120+
about = "Migrate existing pyth2wormhole program settings to a newer format version. Client version must match the deployed contract."
121+
)]
120122
Migrate {
121123
/// owner keypair path
122124
#[clap(

solana/pyth2wormhole/client/src/lib.rs

Lines changed: 51 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
pub mod attestation_cfg;
22
pub mod batch_state;
3+
pub mod message;
34
pub mod util;
45

56
use borsh::{
@@ -20,7 +21,13 @@ use solana_program::{
2021
rent,
2122
},
2223
};
23-
use solana_sdk::{transaction::Transaction, signer::{Signer, keypair::Keypair}};
24+
use solana_sdk::{
25+
signer::{
26+
keypair::Keypair,
27+
Signer,
28+
},
29+
transaction::Transaction,
30+
};
2431
use solitaire::{
2532
processors::seeded::Seeded,
2633
AccountState,
@@ -41,7 +48,14 @@ use p2w_sdk::P2WEmitter;
4148

4249
use pyth2wormhole::{
4350
attest::P2W_MAX_BATCH_SIZE,
44-
config::{OldP2WConfigAccount, P2WConfigAccount},
51+
config::{
52+
OldP2WConfigAccount,
53+
P2WConfigAccount,
54+
},
55+
message::{
56+
P2WMessage,
57+
P2WMessageDrvData,
58+
},
4559
AttestData,
4660
};
4761

@@ -58,6 +72,8 @@ pub use util::{
5872
RLMutexGuard,
5973
};
6074

75+
pub use message::P2WMessageQueue;
76+
6177
/// Future-friendly version of solitaire::ErrBox
6278
pub type ErrBoxSend = Box<dyn std::error::Error + Send + Sync>;
6379

@@ -67,26 +83,22 @@ pub fn gen_init_tx(
6783
config: Pyth2WormholeConfig,
6884
latest_blockhash: Hash,
6985
) -> Result<Transaction, ErrBox> {
70-
7186
let payer_pubkey = payer.pubkey();
7287
let acc_metas = vec![
7388
// new_config
74-
AccountMeta::new(P2WConfigAccount::<{AccountState::Uninitialized}>::key(None, &p2w_addr), false),
89+
AccountMeta::new(
90+
P2WConfigAccount::<{ AccountState::Uninitialized }>::key(None, &p2w_addr),
91+
false,
92+
),
7593
// payer
7694
AccountMeta::new(payer.pubkey(), true),
7795
// system_program
7896
AccountMeta::new(system_program::id(), false),
79-
];
97+
];
8098

8199
let ix_data = (pyth2wormhole::instruction::Instruction::Initialize, config);
82100

83-
let ix = Instruction::new_with_bytes(
84-
p2w_addr,
85-
ix_data
86-
.try_to_vec()?
87-
.as_slice(),
88-
acc_metas,
89-
);
101+
let ix = Instruction::new_with_bytes(p2w_addr, ix_data.try_to_vec()?.as_slice(), acc_metas);
90102

91103
let signers = vec![&payer];
92104

@@ -110,25 +122,22 @@ pub fn gen_set_config_tx(
110122

111123
let acc_metas = vec![
112124
// config
113-
AccountMeta::new(P2WConfigAccount::<{AccountState::Initialized}>::key(None, &p2w_addr), false),
125+
AccountMeta::new(
126+
P2WConfigAccount::<{ AccountState::Initialized }>::key(None, &p2w_addr),
127+
false,
128+
),
114129
// current_owner
115130
AccountMeta::new(owner.pubkey(), true),
116131
// payer
117132
AccountMeta::new(payer.pubkey(), true),
118-
];
133+
];
119134

120135
let ix_data = (
121136
pyth2wormhole::instruction::Instruction::SetConfig,
122137
new_config,
123138
);
124139

125-
let ix = Instruction::new_with_bytes(
126-
p2w_addr,
127-
ix_data
128-
.try_to_vec()?
129-
.as_slice(),
130-
acc_metas,
131-
);
140+
let ix = Instruction::new_with_bytes(p2w_addr, ix_data.try_to_vec()?.as_slice(), acc_metas);
132141

133142
let signers = vec![&owner, &payer];
134143
let tx_signed = Transaction::new_signed_with_payer::<Vec<&Keypair>>(
@@ -146,12 +155,14 @@ pub fn gen_migrate_tx(
146155
owner: Keypair,
147156
latest_blockhash: Hash,
148157
) -> Result<Transaction, ErrBox> {
149-
150158
let payer_pubkey = payer.pubkey();
151159

152160
let acc_metas = vec![
153161
// new_config
154-
AccountMeta::new(P2WConfigAccount::<{AccountState::Uninitialized}>::key(None, &p2w_addr), false),
162+
AccountMeta::new(
163+
P2WConfigAccount::<{ AccountState::Uninitialized }>::key(None, &p2w_addr),
164+
false,
165+
),
155166
// old_config
156167
AccountMeta::new(OldP2WConfigAccount::key(None, &p2w_addr), false),
157168
// owner
@@ -160,20 +171,11 @@ pub fn gen_migrate_tx(
160171
AccountMeta::new(payer.pubkey(), true),
161172
// system_program
162173
AccountMeta::new(system_program::id(), false),
163-
];
174+
];
164175

165-
let ix_data = (
166-
pyth2wormhole::instruction::Instruction::Migrate,
167-
(),
168-
);
176+
let ix_data = (pyth2wormhole::instruction::Instruction::Migrate, ());
169177

170-
let ix = Instruction::new_with_bytes(
171-
p2w_addr,
172-
ix_data
173-
.try_to_vec()?
174-
.as_slice(),
175-
acc_metas,
176-
);
178+
let ix = Instruction::new_with_bytes(p2w_addr, ix_data.try_to_vec()?.as_slice(), acc_metas);
177179

178180
let signers = vec![&owner, &payer];
179181

@@ -209,8 +211,8 @@ pub fn gen_attest_tx(
209211
p2w_addr: Pubkey,
210212
p2w_config: &Pyth2WormholeConfig, // Must be fresh, not retrieved inside to keep side effects away
211213
payer: &Keypair,
214+
wh_msg_id: u64,
212215
symbols: &[P2WSymbol],
213-
wh_msg: &Keypair,
214216
latest_blockhash: Hash,
215217
) -> Result<Transaction, ErrBoxSend> {
216218
let emitter_addr = P2WEmitter::key(None, &p2w_addr);
@@ -279,7 +281,16 @@ pub fn gen_attest_tx(
279281
false,
280282
),
281283
// wh_message
282-
AccountMeta::new(wh_msg.pubkey(), true),
284+
AccountMeta::new(
285+
P2WMessage::key(
286+
&P2WMessageDrvData {
287+
id: wh_msg_id,
288+
message_owner: payer.pubkey(),
289+
},
290+
&p2w_addr,
291+
),
292+
false,
293+
),
283294
// wh_emitter
284295
AccountMeta::new_readonly(emitter_addr, false),
285296
// wh_sequence
@@ -295,21 +306,16 @@ pub fn gen_attest_tx(
295306
pyth2wormhole::instruction::Instruction::Attest,
296307
AttestData {
297308
consistency_level: ConsistencyLevel::Confirmed,
309+
message_account_id: wh_msg_id,
298310
},
299311
);
300312

301-
let ix = Instruction::new_with_bytes(
302-
p2w_addr,
303-
ix_data
304-
.try_to_vec()?
305-
.as_slice(),
306-
acc_metas,
307-
);
313+
let ix = Instruction::new_with_bytes(p2w_addr, ix_data.try_to_vec()?.as_slice(), acc_metas);
308314

309315
let tx_signed = Transaction::new_signed_with_payer::<Vec<&Keypair>>(
310316
&[ix],
311317
Some(&payer.pubkey()),
312-
&vec![&payer, &wh_msg],
318+
&vec![&payer],
313319
latest_blockhash,
314320
);
315321
Ok(tx_signed)

solana/pyth2wormhole/client/src/main.rs

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,10 @@ use solitaire::{
4545
ErrBox,
4646
};
4747
use tokio::{
48-
sync::Semaphore,
48+
sync::{
49+
Mutex,
50+
Semaphore,
51+
},
4952
task::JoinHandle,
5053
};
5154

@@ -136,16 +139,16 @@ async fn main() -> Result<(), ErrBox> {
136139
get_config_account(&rpc_client, &p2w_addr).await?
137140
);
138141
}
139-
Action::Migrate {
140-
ref owner,
141-
} => {
142+
Action::Migrate { ref owner } => {
142143
let tx = gen_migrate_tx(
143144
payer,
144145
p2w_addr,
145146
read_keypair_file(&*shellexpand::tilde(&owner))?,
146147
latest_blockhash,
147148
)?;
148-
rpc_client.send_and_confirm_transaction_with_spinner(&tx).await?;
149+
rpc_client
150+
.send_and_confirm_transaction_with_spinner(&tx)
151+
.await?;
149152
println!(
150153
"Applied conifg:\n{:?}",
151154
get_config_account(&rpc_client, &p2w_addr).await?
@@ -252,6 +255,8 @@ async fn handle_attest(
252255
rpc_interval,
253256
));
254257

258+
let message_q_mtx = Arc::new(Mutex::new(P2WMessageQueue::new(Duration::from_millis(attestation_cfg.min_msg_reuse_interval_ms), attestation_cfg.max_msg_accounts as usize)));
259+
255260
// Create attestation scheduling routines; see attestation_sched_job() for details
256261
let mut attestation_sched_futs = batches.into_iter().map(|(batch_no, batch)| {
257262
attestation_sched_job(
@@ -265,6 +270,7 @@ async fn handle_attest(
265270
p2w_addr,
266271
config.clone(),
267272
Keypair::from_bytes(&payer.to_bytes()).unwrap(),
273+
message_q_mtx.clone(),
268274
)
269275
});
270276

@@ -337,6 +343,7 @@ async fn attestation_sched_job(
337343
p2w_addr: Pubkey,
338344
config: Pyth2WormholeConfig,
339345
payer: Keypair,
346+
message_q_mtx: Arc<Mutex<P2WMessageQueue>>,
340347
) -> Result<(), ErrBoxSend> {
341348
let mut retries_left = n_retries;
342349
// Enforces the max batch job count
@@ -357,6 +364,7 @@ async fn attestation_sched_job(
357364
Keypair::from_bytes(&payer.to_bytes()).unwrap(), // Keypair has no clone
358365
batch.symbols.to_vec(),
359366
sema.clone(),
367+
message_q_mtx.clone(),
360368
);
361369

362370
if daemon {
@@ -456,6 +464,7 @@ async fn attestation_job(
456464
payer: Keypair,
457465
symbols: Vec<P2WSymbol>,
458466
max_jobs_sema: Arc<Semaphore>,
467+
message_q_mtx: Arc<Mutex<P2WMessageQueue>>,
459468
) -> Result<(), ErrBoxSend> {
460469
// Will be dropped after attestation is complete
461470
let _permit = max_jobs_sema.acquire().await?;
@@ -470,17 +479,18 @@ async fn attestation_job(
470479
.map_err(|e| -> ErrBoxSend { e.into() })
471480
.await?;
472481

482+
let wh_msg_id = message_q_mtx.lock().await.get_account()?.id;
483+
473484
let tx_res: Result<_, ErrBoxSend> = gen_attest_tx(
474485
p2w_addr,
475486
&config,
476487
&payer,
488+
wh_msg_id,
477489
symbols.as_slice(),
478-
&Keypair::new(),
479490
latest_blockhash,
480491
);
481-
let tx = tx_res?;
482492
let sig = rpc
483-
.send_and_confirm_transaction(&tx)
493+
.send_and_confirm_transaction(&tx_res?)
484494
.map_err(|e| -> ErrBoxSend { e.into() })
485495
.await?;
486496
let tx_data = rpc

0 commit comments

Comments
 (0)