Skip to content

Commit 0ac7712

Browse files
authored
[accumulator-updater 5/x] Single PDA (#760)
* feat(accumulator-updater): write accumulator messages into one account * chore(accumulator-updater): clean up commented out code * chore(accumulator-updater): remove unused feature for cargo * chore(accumulator-updater): minor comment fix * feat(accumulator-updater): update implementation of AccumulatorInput header Removed InputIndex in header, updated to use end_offsets & include header_len * feat(accumulator-updater): add MessageHeader to all PriceMessages and include in serialization * fix(accumulator-updater): fix AccumulatorInput size & unit tests * feat(accumulator-updater): update put_all to write as much data as possible put_all will write up to the limit of the accumulator_input.data.len(). this ix must not fail even when the data being written exceeds the size of the account. in this situation, we will write as many *COMPLETE* messages as possible * docs(accumulator-updater): update put_all ix documentation * chore(accumulator-updater): addressed PR feedback fixed comments, added test, renamed accumulatorInput.data to messages * fix(accumulator-updater): fix ts test
1 parent c951ff6 commit 0ac7712

File tree

10 files changed

+490
-250
lines changed

10 files changed

+490
-250
lines changed

accumulator_updater/programs/accumulator_updater/Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,6 @@ cpi = ["no-entrypoint"]
1616
default = []
1717

1818
[dependencies]
19-
anchor-lang = "0.27.0"
19+
anchor-lang = { version = "0.27.0" }
2020
# needed for the new #[account(zero_copy)] in anchor 0.27.0
2121
bytemuck = { version = "1.4.0", features = ["derive", "min_const_generics"]}

accumulator_updater/programs/accumulator_updater/src/instructions/put_all.rs

+49-75
Original file line numberDiff line numberDiff line change
@@ -12,99 +12,73 @@ use {
1212
},
1313
};
1414

15-
#[derive(AnchorSerialize, AnchorDeserialize)]
16-
pub struct InputSchemaAndData {
17-
pub schema: u8,
18-
pub data: Vec<u8>,
19-
}
2015

16+
pub const ACCUMULATOR: &[u8; 11] = b"accumulator";
2117

2218
pub fn put_all<'info>(
2319
ctx: Context<'_, '_, '_, 'info, PutAll<'info>>,
2420
base_account_key: Pubkey,
25-
values: Vec<InputSchemaAndData>,
21+
messages: Vec<Vec<u8>>,
2622
) -> Result<()> {
2723
let cpi_caller = ctx.accounts.whitelist_verifier.is_allowed()?;
28-
let account_infos = ctx.remaining_accounts;
29-
require_eq!(account_infos.len(), values.len());
24+
let accumulator_input_ai = ctx
25+
.remaining_accounts
26+
.first()
27+
.ok_or(AccumulatorUpdaterError::AccumulatorInputNotProvided)?;
3028

31-
let rent = Rent::get()?;
32-
let (mut initialized, mut updated) = (vec![], vec![]);
29+
let loader;
3330

34-
for (
35-
ai,
36-
InputSchemaAndData {
37-
schema: account_schema,
38-
data: account_data,
39-
},
40-
) in account_infos.iter().zip(values)
4131
{
42-
let bump = if is_uninitialized_account(ai) {
43-
let seeds = &[
32+
let accumulator_input = &mut (if is_uninitialized_account(accumulator_input_ai) {
33+
let (pda, bump) = Pubkey::find_program_address(
34+
&[
35+
cpi_caller.as_ref(),
36+
ACCUMULATOR.as_ref(),
37+
base_account_key.as_ref(),
38+
],
39+
&crate::ID,
40+
);
41+
require_keys_eq!(accumulator_input_ai.key(), pda);
42+
let signer_seeds = &[
4443
cpi_caller.as_ref(),
45-
b"accumulator".as_ref(),
44+
ACCUMULATOR.as_ref(),
4645
base_account_key.as_ref(),
47-
&account_schema.to_le_bytes(),
46+
&[bump],
4847
];
49-
let (pda, bump) = Pubkey::find_program_address(seeds, &crate::ID);
50-
require_keys_eq!(ai.key(), pda);
51-
52-
53-
//TODO: Update this with serialization logic
54-
// 8 for anchor discriminator
55-
let accumulator_size = 8 + AccumulatorInput::size(&account_data);
5648
PutAll::create_account(
57-
ai,
58-
accumulator_size,
49+
accumulator_input_ai,
50+
8 + AccumulatorInput::INIT_SPACE,
5951
&ctx.accounts.payer,
60-
&[
61-
cpi_caller.as_ref(),
62-
b"accumulator".as_ref(),
63-
base_account_key.as_ref(),
64-
&account_schema.to_le_bytes(),
65-
&[bump],
66-
],
67-
&rent,
52+
signer_seeds,
6853
&ctx.accounts.system_program,
6954
)?;
70-
initialized.push(ai.key());
71-
72-
bump
73-
} else {
74-
let accumulator_input = <AccumulatorInput as AccountDeserialize>::try_deserialize(
75-
&mut &**ai.try_borrow_mut_data()?,
55+
loader = AccountLoader::<AccumulatorInput>::try_from_unchecked(
56+
&crate::ID,
57+
accumulator_input_ai,
7658
)?;
77-
{
78-
// TODO: allow re-sizing?
79-
require_gte!(
80-
accumulator_input.data.len(),
81-
account_data.len(),
82-
AccumulatorUpdaterError::CurrentDataLengthExceeded
83-
);
84-
85-
accumulator_input.validate(
86-
ai.key(),
87-
cpi_caller,
88-
base_account_key,
89-
account_schema,
90-
)?;
91-
}
92-
59+
let mut accumulator_input = loader.load_init()?;
60+
accumulator_input.header = AccumulatorHeader::new(bump);
61+
accumulator_input
62+
} else {
63+
loader = AccountLoader::<AccumulatorInput>::try_from(accumulator_input_ai)?;
64+
let mut accumulator_input = loader.load_mut()?;
65+
accumulator_input.header.set_version();
66+
accumulator_input
67+
});
68+
// note: redundant for uninitialized code path but safer to check here.
69+
// compute budget cost should be minimal
70+
accumulator_input.validate(accumulator_input_ai.key(), cpi_caller, base_account_key)?;
71+
72+
73+
let (num_msgs, num_bytes) = accumulator_input.put_all(&messages);
74+
if num_msgs != messages.len() {
75+
msg!("unable to fit all messages in accumulator input account. Wrote {}/{} messages and {} bytes", num_msgs, messages.len(), num_bytes);
76+
}
77+
}
9378

94-
updated.push(ai.key());
95-
accumulator_input.header.bump
96-
};
9779

98-
let accumulator_input =
99-
AccumulatorInput::new(AccumulatorHeader::new(bump, account_schema), account_data);
100-
accumulator_input.persist(ai)?;
101-
}
80+
loader.exit(&crate::ID)?;
10281

103-
msg!(
104-
"[emit-updates]: initialized: {:?}, updated: {:?}",
105-
initialized,
106-
updated
107-
);
10882
Ok(())
10983
}
11084

@@ -113,12 +87,13 @@ pub fn is_uninitialized_account(ai: &AccountInfo) -> bool {
11387
}
11488

11589
#[derive(Accounts)]
90+
#[instruction( base_account_key: Pubkey)]
11691
pub struct PutAll<'info> {
11792
#[account(mut)]
11893
pub payer: Signer<'info>,
11994
pub whitelist_verifier: WhitelistVerifier<'info>,
12095
pub system_program: Program<'info, System>,
121-
// remaining_accounts: - [AccumulatorInput PDAs]
96+
// remaining_accounts: - [AccumulatorInput PDA]
12297
}
12398

12499
impl<'info> PutAll<'info> {
@@ -127,10 +102,9 @@ impl<'info> PutAll<'info> {
127102
space: usize,
128103
payer: &AccountInfo<'a>,
129104
seeds: &[&[u8]],
130-
rent: &Rent,
131105
system_program: &AccountInfo<'a>,
132106
) -> Result<()> {
133-
let lamports = rent.minimum_balance(space);
107+
let lamports = Rent::get()?.minimum_balance(space);
134108

135109
system_program::create_account(
136110
CpiContext::new_with_signer(

accumulator_updater/programs/accumulator_updater/src/lib.rs

+24-10
Original file line numberDiff line numberDiff line change
@@ -52,24 +52,36 @@ pub mod accumulator_updater {
5252
}
5353

5454

55-
/// Create or update inputs for the Accumulator. Each input is written
56-
/// into a separate PDA account derived with
57-
/// seeds = [cpi_caller, b"accumulator", base_account_key, schema]
55+
/// Insert messages/inputs for the Accumulator. All inputs derived from the
56+
/// `base_account_key` will go into the same PDA. The PDA is derived with
57+
/// seeds = [cpi_caller, b"accumulator", base_account_key]
5858
///
59-
/// The caller of this instruction must pass those PDAs
60-
/// while calling this function in the same order as elements.
6159
///
6260
///
6361
/// * `base_account_key` - Pubkey of the original account the
64-
/// AccumulatorInput(s) are derived from
65-
/// * `values` - Vec of (schema, account_data) in same respective
66-
/// order `ctx.remaining_accounts`
62+
/// AccumulatorInput is derived from
63+
/// * `messages` - Vec of vec of bytes, each representing a message
64+
/// to be hashed and accumulated
65+
///
66+
/// This ix will write as many of the messages up to the length
67+
/// of the `accumulator_input.data`.
68+
/// If `accumulator_input.data.len() < messages.map(|x| x.len()).sum()`
69+
/// then the remaining messages will be ignored.
70+
///
71+
/// The current implementation assumes that each invocation of this
72+
/// ix is independent of any previous invocations. It will overwrite
73+
/// any existing contents.
74+
///
75+
/// TODO:
76+
/// - try handling re-allocation of the accumulator_input space
77+
/// - handle updates ("paging/batches of messages")
78+
///
6779
pub fn put_all<'info>(
6880
ctx: Context<'_, '_, '_, 'info, PutAll<'info>>,
6981
base_account_key: Pubkey,
70-
values: Vec<InputSchemaAndData>,
82+
messages: Vec<Vec<u8>>,
7183
) -> Result<()> {
72-
instructions::put_all(ctx, base_account_key, values)
84+
instructions::put_all(ctx, base_account_key, messages)
7385
}
7486
}
7587

@@ -128,4 +140,6 @@ pub enum AccumulatorUpdaterError {
128140
CurrentDataLengthExceeded,
129141
#[msg("Accumulator Input not writable")]
130142
AccumulatorInputNotWritable,
143+
#[msg("Accumulator Input not provided")]
144+
AccumulatorInputNotProvided,
131145
}

accumulator_updater/programs/accumulator_updater/src/macros.rs

-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ macro_rules! accumulator_input_seeds {
55
$cpi_caller_pid.as_ref(),
66
b"accumulator".as_ref(),
77
$base_account.as_ref(),
8-
&$accumulator_input.header.account_schema.to_le_bytes(),
98
&[$accumulator_input.header.bump],
109
]
1110
};

0 commit comments

Comments
 (0)