Skip to content

Commit 788c984

Browse files
committed
Remove wrong chunk size recovery
1 parent c9ab347 commit 788c984

File tree

11 files changed

+177
-137
lines changed

11 files changed

+177
-137
lines changed

chains/solana/contracts/programs/ccip-offramp/src/instructions/interfaces.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ pub trait Execute {
6262
report_length: u32,
6363
chunk: Vec<u8>,
6464
chunk_index: u8,
65+
num_chunks: u8,
6566
) -> Result<()>;
6667
}
6768

chains/solana/contracts/programs/ccip-offramp/src/instructions/v1/buffering.rs

Lines changed: 90 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -3,26 +3,25 @@ use anchor_lang::prelude::*;
33
use crate::{messages::ExecutionReportSingleChain, CcipOfframpError, ExecutionReportBuffer};
44
pub trait Buffering {
55
fn is_initialized(&self) -> bool;
6-
fn filled_chunks(&self) -> u32;
6+
fn filled_chunks(&self) -> u8;
77
fn is_complete(&self) -> bool;
88
fn bytes(&self) -> Result<&[u8]>;
9-
fn initialize(&mut self, report_length: u32, chunk_length: u32) -> Result<()>;
10-
fn add_chunk(&mut self, report_length: u32, chunk: &[u8], chunk_index: u8) -> Result<()>;
11-
fn recover_wrong_size(
9+
fn add_chunk(
1210
&mut self,
1311
report_length: u32,
14-
new_chunk: &[u8],
12+
chunk: &[u8],
1513
chunk_index: u8,
14+
num_chunks: u8,
1615
) -> Result<()>;
1716
}
1817

1918
impl Buffering for ExecutionReportBuffer {
2019
fn is_initialized(&self) -> bool {
21-
self.total_chunks > 0
20+
!self.data.is_empty()
2221
}
2322

24-
fn filled_chunks(&self) -> u32 {
25-
self.chunk_bitmap.count_ones()
23+
fn filled_chunks(&self) -> u8 {
24+
self.chunk_bitmap.count_ones() as u8
2625
}
2726

2827
fn is_complete(&self) -> bool {
@@ -38,35 +37,29 @@ impl Buffering for ExecutionReportBuffer {
3837
Ok(&self.data)
3938
}
4039

41-
fn initialize(&mut self, report_length: u32, chunk_length: u32) -> Result<()> {
40+
fn add_chunk(
41+
&mut self,
42+
report_length: u32,
43+
chunk: &[u8],
44+
chunk_index: u8,
45+
num_chunks: u8,
46+
) -> Result<()> {
4247
require!(
43-
!self.is_initialized(),
44-
CcipOfframpError::ExecutionReportBufferAlreadyInitialized
48+
num_chunks > 0 && chunk_index < num_chunks,
49+
CcipOfframpError::ExecutionReportBufferInvalidChunkIndex
4550
);
4651
require!(
47-
chunk_length > 0 && report_length >= chunk_length,
48-
CcipOfframpError::ExecutionReportBufferInvalidLength
49-
);
50-
self.data.resize(report_length as usize, 0);
51-
self.total_chunks = (report_length + chunk_length - 1) / chunk_length;
52-
require_gt!(
53-
64,
54-
self.total_chunks,
55-
CcipOfframpError::ExecutionReportBufferChunkSizeTooSmall
52+
!chunk.is_empty() && chunk.len() <= report_length as usize,
53+
CcipOfframpError::ExecutionReportBufferInvalidChunkSize
5654
);
57-
self.chunk_length = chunk_length;
58-
self.report_length = report_length;
59-
Ok(())
60-
}
6155

62-
fn add_chunk(&mut self, report_length: u32, chunk: &[u8], chunk_index: u8) -> Result<()> {
6356
if !self.is_initialized() {
64-
self.initialize(report_length, chunk.len() as u32)?;
57+
self.initialize(report_length, chunk.len() as u32, num_chunks, chunk_index)?;
6558
}
6659

6760
require_eq!(
68-
self.report_length,
69-
report_length,
61+
self.data.len(),
62+
report_length as usize,
7063
CcipOfframpError::ExecutionReportBufferInvalidLength
7164
);
7265

@@ -76,13 +69,6 @@ impl Buffering for ExecutionReportBuffer {
7669
CcipOfframpError::ExecutionReportBufferAlreadyContainsChunk
7770
);
7871

79-
if chunk.len() as u32 > self.chunk_length {
80-
// We hit the special case where the first received chunk was the last one
81-
// in the buffer (terminator), which may be smaller than all others. It's okay,
82-
// we can recover in place.
83-
return self.recover_wrong_size(report_length, chunk, chunk_index);
84-
}
85-
8672
require_gte!(
8773
self.chunk_length,
8874
chunk.len() as u32,
@@ -92,15 +78,15 @@ impl Buffering for ExecutionReportBuffer {
9278
if chunk.len() < self.chunk_length as usize {
9379
// Only the terminator (last chunk) can be smaller than the others.
9480
require_eq!(
95-
chunk_index as u32,
81+
chunk_index,
9682
self.total_chunks - 1,
9783
CcipOfframpError::ExecutionReportBufferInvalidChunkSize
9884
);
9985
}
10086

10187
require_gt!(
10288
self.total_chunks,
103-
chunk_index as u32,
89+
chunk_index,
10490
CcipOfframpError::ExecutionReportBufferInvalidChunkIndex
10591
);
10692

@@ -111,37 +97,48 @@ impl Buffering for ExecutionReportBuffer {
11197

11298
Ok(())
11399
}
100+
}
114101

115-
fn recover_wrong_size(
102+
impl ExecutionReportBuffer {
103+
fn initialize(
116104
&mut self,
117105
report_length: u32,
118-
new_chunk: &[u8],
106+
chunk_length: u32,
107+
total_chunks: u8,
119108
chunk_index: u8,
120109
) -> Result<()> {
121-
// Only makes sense to recover if we got the first chunk wrong (because it was the buffer
122-
// terminator). Any size mismatch beyond that means the user is sending the chunks incorrectly.
123-
require_eq!(
124-
self.filled_chunks(),
125-
1,
110+
require!(
111+
!self.is_initialized(),
112+
CcipOfframpError::ExecutionReportBufferAlreadyInitialized
113+
);
114+
require!(
115+
report_length > 0 && chunk_length <= report_length && chunk_length > 0,
116+
CcipOfframpError::ExecutionReportBufferInvalidLength
117+
);
118+
require!(
119+
total_chunks < 64 && total_chunks > 0,
126120
CcipOfframpError::ExecutionReportBufferInvalidChunkSize
127121
);
128122

129-
// We extract what we now know is the terminator
130-
let terminator_index = self.chunk_bitmap.trailing_zeros() as u8;
131-
let mut terminator = vec![0u8; self.chunk_length as usize];
132-
let start = terminator_index as usize * self.chunk_length as usize;
133-
let end = start + terminator.len();
134-
terminator.copy_from_slice(&self.data[start..end]);
135-
136-
// We reset the buffer metadata. It's okay to leave the old data in, as it will be clobbered.
137-
self.chunk_bitmap = 0;
138-
self.total_chunks = 0;
139-
self.chunk_length = 0;
140-
141-
// We reinsert the new chunk and terminator, which will be accepted as it's smaller. From now
142-
// on, we won't accept bigger chunks than this again.
143-
self.add_chunk(report_length, new_chunk, chunk_index)?;
144-
self.add_chunk(report_length, &terminator, terminator_index)?;
123+
// If we're initializing with the last chunk, it could be smaller
124+
// than the rest, so we calculate the chunk size based on the expected
125+
// size of all the others.
126+
let is_last = chunk_index == total_chunks - 1;
127+
let global_chunk_length = if is_last && total_chunks > 1 {
128+
(report_length - chunk_length) / (total_chunks as u32 - 1)
129+
} else {
130+
chunk_length
131+
};
132+
133+
require_eq!(
134+
total_chunks as u32,
135+
div_ceil(report_length, global_chunk_length),
136+
CcipOfframpError::ExecutionReportBufferInvalidLength,
137+
);
138+
self.chunk_length = global_chunk_length;
139+
self.total_chunks = total_chunks;
140+
self.data.resize(report_length as usize, 0);
141+
145142
Ok(())
146143
}
147144
}
@@ -166,6 +163,12 @@ pub fn deserialize_from_buffer_account(
166163
))
167164
}
168165

166+
fn div_ceil<T: Into<u32>>(a: T, b: T) -> u32 {
167+
let (a, b) = (a.into(), b.into());
168+
assert!(a + b - 1 > 0);
169+
(a + b - 1) / b
170+
}
171+
169172
#[cfg(test)]
170173
mod tests {
171174
use super::*;
@@ -176,7 +179,6 @@ mod tests {
176179
chunk_bitmap: 0,
177180
total_chunks: 0,
178181
chunk_length: 0,
179-
report_length: 0,
180182
version: 0,
181183
}
182184
}
@@ -186,19 +188,27 @@ mod tests {
186188
let mut buffer = empty_buffer();
187189

188190
assert_eq!(
189-
buffer.initialize(10, 100).unwrap_err(),
191+
buffer.initialize(10, 100, 1, 0).unwrap_err(),
190192
CcipOfframpError::ExecutionReportBufferInvalidLength.into()
191193
);
192194

195+
let mut buffer = empty_buffer();
193196
assert_eq!(
194-
buffer.initialize(0, 1).unwrap_err(),
197+
buffer.initialize(0, 1, 1, 0).unwrap_err(),
195198
CcipOfframpError::ExecutionReportBufferInvalidLength.into()
196199
);
197200

201+
let mut buffer = empty_buffer();
198202
// Too small buffer sizes aren't OK as they'd break the bitmap
199203
assert_eq!(
200-
buffer.initialize(100000, 1).unwrap_err(),
201-
CcipOfframpError::ExecutionReportBufferChunkSizeTooSmall.into()
204+
buffer.initialize(100000, 1, 1, 0).unwrap_err(),
205+
CcipOfframpError::ExecutionReportBufferInvalidLength.into()
206+
);
207+
208+
let mut buffer = empty_buffer();
209+
assert_eq!(
210+
buffer.initialize(100, 10, 255, 0).unwrap_err(),
211+
CcipOfframpError::ExecutionReportBufferInvalidChunkSize.into()
202212
);
203213
}
204214

@@ -218,11 +228,13 @@ mod tests {
218228
message.len() as u32,
219229
&message[i * chunk_size..(i + 1) * chunk_size],
220230
i as u8,
231+
3,
221232
)
222233
.unwrap();
223234
assert_eq!(buffer.filled_chunks() as usize, i + 1);
224235
}
225236

237+
dbg!(&buffer);
226238
assert!(buffer.is_complete());
227239
assert_eq!(buffer.bytes().unwrap(), message);
228240
}
@@ -243,6 +255,7 @@ mod tests {
243255
message.len() as u32,
244256
&message[i * chunk_size..message.len().min((i + 1) * chunk_size)],
245257
i as u8,
258+
4,
246259
)
247260
.unwrap();
248261
assert_eq!(buffer.filled_chunks() as usize, i + 1);
@@ -270,6 +283,7 @@ mod tests {
270283
message.len() as u32,
271284
&message[i * chunk_size..message.len().min((i + 1) * chunk_size)],
272285
i as u8,
286+
4,
273287
)
274288
.unwrap();
275289
assert_eq!(buffer.filled_chunks() as usize, 4 - i);
@@ -284,31 +298,31 @@ mod tests {
284298
#[test]
285299
fn rejects_invalid_chunks() {
286300
let mut buffer = empty_buffer();
287-
buffer.add_chunk(22, b"AAAAA", 0).unwrap();
301+
buffer.add_chunk(22, b"AAAAA", 0, 5).unwrap();
288302
// This is OK as it is the terminator.
289-
buffer.add_chunk(22, b"BB", 4).unwrap();
303+
buffer.add_chunk(22, b"BB", 4, 5).unwrap();
290304
// This is not OK.
291-
buffer.add_chunk(22, b"CCCCCCCCCCC", 2).unwrap_err();
305+
buffer.add_chunk(22, b"CCCCCCCCCCC", 2, 5).unwrap_err();
292306

293307
let mut buffer = empty_buffer();
294-
buffer.add_chunk(100, b"small", 0).unwrap();
308+
buffer.add_chunk(100, b"small", 0, 20).unwrap();
295309
// Invalid due to inconsistent total report size.
296-
buffer.add_chunk(50, b"small", 1).unwrap_err();
310+
buffer.add_chunk(50, b"small", 1, 20).unwrap_err();
297311

298312
let mut buffer = empty_buffer();
299-
buffer.add_chunk(100, b"small", 0).unwrap();
313+
buffer.add_chunk(100, b"small", 0, 20).unwrap();
300314
// Invalid due to repeated index.
301-
buffer.add_chunk(100, b"small", 0).unwrap_err();
315+
buffer.add_chunk(100, b"small", 0, 20).unwrap_err();
302316

303317
let mut buffer = empty_buffer();
304318
buffer
305-
.add_chunk(10, b"Much bigger than ten characters", 0)
319+
.add_chunk(10, b"Much bigger than ten characters", 0, 1)
306320
.unwrap_err();
307321

308322
let mut buffer = empty_buffer();
309-
buffer.add_chunk(29, b"Medium sized", 1).unwrap();
323+
buffer.add_chunk(29, b"Medium sized", 1, 3).unwrap();
310324
// Cannot be smaller: only the terminator can (must come at the end.)
311-
buffer.add_chunk(29, b"small", 0).unwrap_err();
312-
buffer.add_chunk(29, b"small", 2).unwrap();
325+
buffer.add_chunk(29, b"small", 0, 3).unwrap_err();
326+
buffer.add_chunk(29, b"small", 2, 3).unwrap();
313327
}
314328
}

chains/solana/contracts/programs/ccip-offramp/src/instructions/v1/execute.rs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -114,10 +114,14 @@ impl Execute for Impl {
114114
report_length: u32,
115115
chunk: Vec<u8>,
116116
chunk_index: u8,
117+
num_chunks: u8,
117118
) -> Result<()> {
118-
ctx.accounts
119-
.execution_report_buffer
120-
.add_chunk(report_length, &chunk, chunk_index)
119+
ctx.accounts.execution_report_buffer.add_chunk(
120+
report_length,
121+
&chunk,
122+
chunk_index,
123+
num_chunks,
124+
)
121125
}
122126
}
123127

chains/solana/contracts/programs/ccip-offramp/src/lib.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -554,12 +554,14 @@ pub mod ccip_offramp {
554554
/// * `chunk` - The specific chunk to add to the buffer. Chunk must have a consistent size, except
555555
/// the last one in the buffer, which may be smaller.
556556
/// * `chunk_index` - The index of this chunk.
557+
/// * `num_chunks` - The total number of chunks in the report.
557558
pub fn buffer_execution_report<'info>(
558559
ctx: Context<'_, '_, 'info, 'info, BufferExecutionReportContext<'info>>,
559560
buffer_id: Vec<u8>,
560561
report_length: u32,
561562
chunk: Vec<u8>,
562563
chunk_index: u8,
564+
num_chunks: u8,
563565
) -> Result<()> {
564566
// Execution report buffering doesn't need to be done differently per lane, so we
565567
// use the default code version here.
@@ -578,6 +580,7 @@ pub mod ccip_offramp {
578580
report_length,
579581
chunk,
580582
chunk_index,
583+
num_chunks,
581584
)
582585
}
583586

@@ -742,9 +745,9 @@ pub enum CcipOfframpError {
742745
ExecutionReportBufferInvalidLength,
743746
#[msg("Chunk lies outside the execution report buffer")]
744747
ExecutionReportBufferInvalidChunkIndex,
745-
#[msg("Chunk size is too small.")]
748+
#[msg("Chunk size is too small")]
746749
ExecutionReportBufferChunkSizeTooSmall,
747-
#[msg("Invalid chunk size. Remember that the last chunk should be right-padded with zeros.")]
750+
#[msg("Invalid chunk size")]
748751
ExecutionReportBufferInvalidChunkSize,
749752
#[msg("Execution report buffer is not complete: chunks are missing")]
750753
ExecutionReportBufferIncomplete,

chains/solana/contracts/programs/ccip-offramp/src/state.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -105,9 +105,8 @@ pub struct OnRampAddress {
105105
pub struct ExecutionReportBuffer {
106106
pub version: u8,
107107
pub chunk_bitmap: u64,
108-
pub total_chunks: u32,
108+
pub total_chunks: u8,
109109
pub chunk_length: u32,
110-
pub report_length: u32,
111110
#[max_len(0)]
112111
pub data: Vec<u8>,
113112
}

0 commit comments

Comments
 (0)