-
Notifications
You must be signed in to change notification settings - Fork 11
refactor: store TransactionContext in TransactionRecord
#582
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: v0.42-dev
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,28 +3,72 @@ | |
| use dashcore::hashes::Hash; | ||
| use key_wallet::transaction_checking::{BlockInfo, TransactionContext}; | ||
| use key_wallet::{Network, Wallet}; | ||
| use std::os::raw::{c_char, c_uint}; | ||
| use std::os::raw::c_char; | ||
| use std::sync::Arc; | ||
|
|
||
| /// Convert FFI block parameters to a `BlockInfo`. | ||
| /// | ||
| /// # Safety | ||
| /// FFI-compatible block metadata (height, hash, timestamp). | ||
| #[repr(C)] | ||
| #[derive(Debug, Clone, Copy)] | ||
| pub struct FFIBlockInfo { | ||
| /// Block height | ||
| pub height: u32, | ||
| /// Block hash (32 bytes) | ||
| pub block_hash: [u8; 32], | ||
| /// Unix timestamp | ||
| pub timestamp: u32, | ||
| } | ||
|
|
||
| impl FFIBlockInfo { | ||
| /// All-zeros placeholder used for unconfirmed contexts. | ||
| pub fn empty() -> Self { | ||
| Self { | ||
| height: 0, | ||
| block_hash: [0u8; 32], | ||
| timestamp: 0, | ||
| } | ||
| } | ||
|
|
||
| /// Convert to native `BlockInfo`. | ||
| pub fn to_block_info(&self) -> BlockInfo { | ||
| let block_hash = dashcore::BlockHash::from_byte_array(self.block_hash); | ||
| BlockInfo::new(self.height, block_hash, self.timestamp) | ||
| } | ||
|
Comment on lines
+31
to
+35
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Make confirmed This only rejects the exact empty sentinel. A caller that leaves just one field zeroed still gets a confirmed 🔧 Suggested direction impl FFIBlockInfo {
- /// Convert to native `BlockInfo`.
- pub fn to_block_info(&self) -> BlockInfo {
- let block_hash = dashcore::BlockHash::from_byte_array(self.block_hash);
- BlockInfo::new(self.height, block_hash, self.timestamp)
+ fn to_confirmed_block_info(&self) -> Option<BlockInfo> {
+ if self.block_hash == [0u8; 32] || self.timestamp == 0 {
+ return None;
+ }
+
+ let block_hash = dashcore::BlockHash::from_byte_array(self.block_hash);
+ Some(BlockInfo::new(self.height, block_hash, self.timestamp))
}
}
@@
FFITransactionContext::Mempool => Some(TransactionContext::Mempool),
FFITransactionContext::InstantSend => Some(TransactionContext::InstantSend),
- FFITransactionContext::InBlock => {
- if block_info.block_hash == [0u8; 32] && block_info.timestamp == 0 {
- return None;
- }
- Some(TransactionContext::InBlock(block_info.to_block_info()))
- }
- FFITransactionContext::InChainLockedBlock => {
- if block_info.block_hash == [0u8; 32] && block_info.timestamp == 0 {
- return None;
- }
- Some(TransactionContext::InChainLockedBlock(block_info.to_block_info()))
- }
+ FFITransactionContext::InBlock => block_info
+ .to_confirmed_block_info()
+ .map(TransactionContext::InBlock),
+ FFITransactionContext::InChainLockedBlock => block_info
+ .to_confirmed_block_info()
+ .map(TransactionContext::InChainLockedBlock),
}
}As per coding guidelines, Also applies to: 52-71 🤖 Prompt for AI Agents |
||
| } | ||
|
|
||
| impl From<BlockInfo> for FFIBlockInfo { | ||
| fn from(info: BlockInfo) -> Self { | ||
| Self { | ||
| height: info.height(), | ||
| block_hash: info.block_hash().to_byte_array(), | ||
| timestamp: info.timestamp(), | ||
| } | ||
| } | ||
| } | ||
|
|
||
| /// Convert an `FFIBlockInfo` and context type to a native `TransactionContext`. | ||
| /// | ||
| /// If `block_hash` is non-null it must point to 32 readable bytes. | ||
| pub(crate) unsafe fn block_info_from_ffi( | ||
| height: u32, | ||
| block_hash: *const u8, | ||
| timestamp: u64, | ||
| ) -> BlockInfo { | ||
| let block_hash = if !block_hash.is_null() { | ||
| let hash_bytes = std::slice::from_raw_parts(block_hash, 32); | ||
| let mut arr = [0u8; 32]; | ||
| arr.copy_from_slice(hash_bytes); | ||
| dashcore::BlockHash::from_byte_array(arr) | ||
| } else { | ||
| dashcore::BlockHash::all_zeros() | ||
| }; | ||
| BlockInfo::new(height, block_hash, timestamp as u32) | ||
| /// Returns `None` when block info is all-zeros for confirmed contexts (`InBlock`, | ||
| /// `InChainLockedBlock`), indicating invalid input from the FFI caller. | ||
| pub(crate) fn transaction_context_from_ffi( | ||
| context_type: FFITransactionContext, | ||
| block_info: &FFIBlockInfo, | ||
| ) -> Option<TransactionContext> { | ||
| match context_type { | ||
| FFITransactionContext::Mempool => Some(TransactionContext::Mempool), | ||
| FFITransactionContext::InstantSend => Some(TransactionContext::InstantSend), | ||
| FFITransactionContext::InBlock => { | ||
| if block_info.block_hash == [0u8; 32] && block_info.timestamp == 0 { | ||
| return None; | ||
| } | ||
| Some(TransactionContext::InBlock(block_info.to_block_info())) | ||
| } | ||
| FFITransactionContext::InChainLockedBlock => { | ||
| if block_info.block_hash == [0u8; 32] && block_info.timestamp == 0 { | ||
| return None; | ||
| } | ||
| Some(TransactionContext::InChainLockedBlock(block_info.to_block_info())) | ||
| } | ||
| } | ||
|
Comment on lines
+48
to
+71
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add regression tests for the new confirmed-context rejection. This fix now guards an FFI bug, but the local test module still doesn’t cover As per coding guidelines, Also applies to: 839-841 🤖 Prompt for AI Agents |
||
| } | ||
|
|
||
| /// FFI Network type (single network) | ||
|
|
@@ -760,62 +804,53 @@ impl From<TransactionContext> for FFITransactionContext { | |
| pub struct FFITransactionContextDetails { | ||
| /// The context type | ||
| pub context_type: FFITransactionContext, | ||
| /// Block height (0 for mempool) | ||
| pub height: c_uint, | ||
| /// Block hash (32 bytes, null for mempool or if unknown) | ||
| pub block_hash: *const u8, | ||
| /// Timestamp (0 if unknown) | ||
| pub timestamp: c_uint, | ||
| /// Block info (zeroed for mempool/instant-send contexts) | ||
| pub block_info: FFIBlockInfo, | ||
| } | ||
|
|
||
| impl FFITransactionContextDetails { | ||
| /// Create a mempool context | ||
| pub fn mempool() -> Self { | ||
| FFITransactionContextDetails { | ||
| Self { | ||
| context_type: FFITransactionContext::Mempool, | ||
| height: 0, | ||
| block_hash: std::ptr::null(), | ||
| timestamp: 0, | ||
| block_info: FFIBlockInfo::empty(), | ||
| } | ||
| } | ||
|
|
||
| /// Create an in-block context | ||
| pub fn in_block(height: c_uint, block_hash: *const u8, timestamp: c_uint) -> Self { | ||
| FFITransactionContextDetails { | ||
| pub fn in_block(block_info: FFIBlockInfo) -> Self { | ||
| Self { | ||
| context_type: FFITransactionContext::InBlock, | ||
| height, | ||
| block_hash, | ||
| timestamp, | ||
| block_info, | ||
| } | ||
| } | ||
|
|
||
| /// Create a chain-locked block context | ||
| pub fn in_chain_locked_block(height: c_uint, block_hash: *const u8, timestamp: c_uint) -> Self { | ||
| FFITransactionContextDetails { | ||
| pub fn in_chain_locked_block(block_info: FFIBlockInfo) -> Self { | ||
| Self { | ||
| context_type: FFITransactionContext::InChainLockedBlock, | ||
| height, | ||
| block_hash, | ||
| timestamp, | ||
| block_info, | ||
| } | ||
| } | ||
|
|
||
| /// Convert to the native TransactionContext | ||
| pub fn to_transaction_context(&self) -> TransactionContext { | ||
| match self.context_type { | ||
| FFITransactionContext::Mempool => TransactionContext::Mempool, | ||
| FFITransactionContext::InBlock => { | ||
| let info = unsafe { | ||
| block_info_from_ffi(self.height, self.block_hash, self.timestamp as u64) | ||
| }; | ||
| TransactionContext::InBlock(info) | ||
| } | ||
| FFITransactionContext::InChainLockedBlock => { | ||
| let info = unsafe { | ||
| block_info_from_ffi(self.height, self.block_hash, self.timestamp as u64) | ||
| }; | ||
| TransactionContext::InChainLockedBlock(info) | ||
| } | ||
| FFITransactionContext::InstantSend => TransactionContext::InstantSend, | ||
| /// Convert to the native `TransactionContext`. | ||
| /// | ||
| /// Returns `None` when block info is all-zeros for confirmed contexts. | ||
| pub fn to_transaction_context(&self) -> Option<TransactionContext> { | ||
| transaction_context_from_ffi(self.context_type, &self.block_info) | ||
| } | ||
| } | ||
|
|
||
| impl From<TransactionContext> for FFITransactionContextDetails { | ||
| fn from(ctx: TransactionContext) -> Self { | ||
| let context_type = FFITransactionContext::from(ctx); | ||
| let block_info = ctx | ||
| .block_info() | ||
| .map(|info| FFIBlockInfo::from(*info)) | ||
| .unwrap_or_else(FFIBlockInfo::empty); | ||
| Self { | ||
| context_type, | ||
| block_info, | ||
| } | ||
| } | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.