Skip to content

Commit f5a5e43

Browse files
authored
store: validate freezer hash lookups (#5222)
### What problem does this PR solve? When freezer storage is enabled, ChainStore may use the requested hash only to find a header/transaction index, then fetch the frozen canonical block by height without checking the hash. If same-height side-chain data remains in RocksDB, a lookup by side-chain block or transaction hash can incorrectly return data from the canonical frozen block at that height. This violates the hash-addressed lookup contract and may make RPC or internal chain logic operate on the wrong block/transaction. ### What is changed and how it works? Freezer stores canonical blocks by height, but ChainStore lookup APIs are hash-addressed. When a requested hash points at a frozen height, verify the frozen block or transaction still matches the requested hash before returning it. If the freezer data does not match, fall back to the RocksDB keyed path instead of aliasing a side-chain hash to canonical frozen data. ### Related changes - PR to update `owner/repo`: - Need to cherry-pick to the release branch ### Check List <!--REMOVE the items that are not applicable--> Tests <!-- At least one of them must be included. --> - Unit test - Integration test - Manual test (add detailed scripts or steps below) - No code Side effects - Performance regression - Breaking backward compatibility
1 parent cbc69bf commit f5a5e43

2 files changed

Lines changed: 111 additions & 6 deletions

File tree

store/src/store.rs

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -46,10 +46,11 @@ pub trait ChainStore: Send + Sync + Sized {
4646
&& header.number() < freezer.number()
4747
{
4848
let raw_block = freezer.retrieve(header.number()).expect("block frozen")?;
49-
let raw_block = packed::BlockReader::from_compatible_slice(&raw_block)
50-
.expect("checked data")
51-
.to_entity();
52-
return Some(raw_block.into_view());
49+
let raw_block_reader =
50+
packed::BlockReader::from_compatible_slice(&raw_block).expect("checked data");
51+
if raw_block_reader.calc_header_hash().as_slice() == h.as_slice() {
52+
return Some(raw_block_reader.to_entity().into_view());
53+
}
5354
}
5455
let body = self.get_block_body(h);
5556
let uncles = self
@@ -326,8 +327,12 @@ pub trait ChainStore: Send + Sync + Sized {
326327
.expect("block frozen")?;
327328
let raw_block_reader =
328329
packed::BlockReader::from_compatible_slice(&raw_block).expect("checked data");
329-
let tx_reader = raw_block_reader.transactions().get(tx_info.index)?;
330-
return Some((tx_reader.to_entity().into_view(), tx_info));
330+
if raw_block_reader.calc_header_hash().as_slice() == tx_info.block_hash.as_slice()
331+
&& let Some(tx_reader) = raw_block_reader.transactions().get(tx_info.index)
332+
&& tx_reader.calc_tx_hash().as_slice() == hash.as_slice()
333+
{
334+
return Some((tx_reader.to_entity().into_view(), tx_info));
335+
}
331336
}
332337
self.get(COLUMN_BLOCK_BODY, tx_info.key().as_slice())
333338
.map(|slice| {

store/src/tests/db.rs

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,3 +158,103 @@ fn freeze_blockv1_with_extension() {
158158
let block = store.get_block(&block_hash).expect("get_block");
159159
assert_eq!(store.get_block(&block_hash), Some(block));
160160
}
161+
162+
#[test]
163+
fn freezer_get_block_keeps_hash_lookup_contract_for_same_height_side_block() {
164+
let tmp_dir = TempDir::new().unwrap();
165+
let db = RocksDB::open_in(&tmp_dir, COLUMNS);
166+
let tmp_dir2 = TempDir::new().unwrap();
167+
let freezer = Freezer::open_in(&tmp_dir2).expect("tmp freezer");
168+
let store = ChainDB::new_with_freezer(db, freezer.clone(), Default::default());
169+
170+
let frozen_block = packed::Block::new_builder()
171+
.header(
172+
packed::Header::new_builder()
173+
.raw(packed::RawHeader::new_builder().number(1u64).build())
174+
.nonce(1u128)
175+
.build(),
176+
)
177+
.build()
178+
.into_view();
179+
let side_block = packed::Block::new_builder()
180+
.header(
181+
packed::Header::new_builder()
182+
.raw(packed::RawHeader::new_builder().number(1u64).build())
183+
.nonce(2u128)
184+
.build(),
185+
)
186+
.build()
187+
.into_view();
188+
let side_hash = side_block.hash();
189+
assert_ne!(frozen_block.hash(), side_hash);
190+
191+
let txn = store.begin_transaction();
192+
txn.insert_block(&frozen_block).unwrap();
193+
txn.insert_block(&side_block).unwrap();
194+
txn.commit().unwrap();
195+
196+
freezer
197+
.freeze(2, |_number| Some(frozen_block.clone()))
198+
.expect("freeze");
199+
200+
assert_eq!(store.get_block(&side_hash), Some(side_block));
201+
}
202+
203+
#[test]
204+
fn freezer_get_transaction_keeps_hash_lookup_contract_for_same_height_side_tx() {
205+
let tmp_dir = TempDir::new().unwrap();
206+
let db = RocksDB::open_in(&tmp_dir, COLUMNS);
207+
let tmp_dir2 = TempDir::new().unwrap();
208+
let freezer = Freezer::open_in(&tmp_dir2).expect("tmp freezer");
209+
let store = ChainDB::new_with_freezer(db, freezer.clone(), Default::default());
210+
211+
let frozen_tx = packed::Transaction::new_builder()
212+
.raw(packed::RawTransaction::new_builder().version(1u32).build())
213+
.build()
214+
.into_view();
215+
let side_tx = packed::Transaction::new_builder()
216+
.raw(packed::RawTransaction::new_builder().version(2u32).build())
217+
.build()
218+
.into_view();
219+
let side_tx_hash = side_tx.hash();
220+
assert_ne!(frozen_tx.hash(), side_tx_hash);
221+
222+
let frozen_block = packed::Block::new_builder()
223+
.header(
224+
packed::Header::new_builder()
225+
.raw(packed::RawHeader::new_builder().number(1u64).build())
226+
.nonce(1u128)
227+
.build(),
228+
)
229+
.transactions(vec![frozen_tx.data()])
230+
.build();
231+
let frozen_block = frozen_block.into_view();
232+
let side_block = packed::Block::new_builder()
233+
.header(
234+
packed::Header::new_builder()
235+
.raw(packed::RawHeader::new_builder().number(1u64).build())
236+
.nonce(2u128)
237+
.build(),
238+
)
239+
.transactions(vec![side_tx.data()])
240+
.build();
241+
let side_block = side_block.into_view();
242+
let side_block_hash = side_block.hash();
243+
assert_ne!(frozen_block.hash(), side_block_hash);
244+
245+
let txn = store.begin_transaction();
246+
txn.insert_block(&frozen_block).unwrap();
247+
txn.insert_block(&side_block).unwrap();
248+
txn.attach_block(&side_block).unwrap();
249+
txn.commit().unwrap();
250+
251+
freezer
252+
.freeze(2, |_number| Some(frozen_block.clone()))
253+
.expect("freeze");
254+
255+
let (tx, tx_info) = store
256+
.get_transaction_with_info(&side_tx_hash)
257+
.expect("get side transaction");
258+
assert_eq!(tx, side_tx);
259+
assert_eq!(tx_info.block_hash, side_block_hash);
260+
}

0 commit comments

Comments
 (0)