Skip to content

Commit 3b06d22

Browse files
kariyclaude
andauthored
Add calculate_hash method to BroadcastedTxWithChainId (#349)
Implements a `calculate_hash` method for `BroadcastedTxWithChainId` that computes the transaction hash by converting the broadcasted transaction to its corresponding primitive type and using the existing hash calculation logic. The method properly handles query transactions and supports all transaction types (Invoke, Declare, DeployAccount). Co-authored-by: Claude <noreply@anthropic.com>
1 parent 153cafd commit 3b06d22

File tree

1 file changed

+132
-0
lines changed

1 file changed

+132
-0
lines changed

crates/rpc/rpc-types/src/broadcasted.rs

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -329,6 +329,68 @@ pub struct BroadcastedTxWithChainId {
329329
pub chain: ChainId,
330330
}
331331

332+
impl BroadcastedTxWithChainId {
333+
/// Compute the hash of the transaction.
334+
pub fn calculate_hash(&self) -> TxHash {
335+
let is_query = self.tx.is_query();
336+
match &self.tx {
337+
BroadcastedTx::Invoke(tx) => {
338+
let invoke_tx = InvokeTx::V3(InvokeTxV3 {
339+
chain_id: self.chain,
340+
tip: tx.tip.into(),
341+
nonce: tx.nonce,
342+
calldata: tx.calldata.clone(),
343+
signature: tx.signature.clone(),
344+
sender_address: tx.sender_address,
345+
paymaster_data: tx.paymaster_data.clone(),
346+
resource_bounds: tx.resource_bounds.clone(),
347+
account_deployment_data: tx.account_deployment_data.clone(),
348+
fee_data_availability_mode: tx.fee_data_availability_mode,
349+
nonce_data_availability_mode: tx.nonce_data_availability_mode,
350+
});
351+
invoke_tx.calculate_hash(is_query)
352+
}
353+
354+
BroadcastedTx::Declare(tx) => {
355+
let declare_tx = DeclareTx::V3(DeclareTxV3 {
356+
chain_id: self.chain,
357+
class_hash: tx.contract_class.hash().expect("failed to compute class hash"),
358+
tip: tx.tip.into(),
359+
nonce: tx.nonce,
360+
signature: tx.signature.clone(),
361+
paymaster_data: tx.paymaster_data.clone(),
362+
sender_address: tx.sender_address,
363+
resource_bounds: tx.resource_bounds.clone(),
364+
compiled_class_hash: tx.compiled_class_hash,
365+
account_deployment_data: tx.account_deployment_data.clone(),
366+
fee_data_availability_mode: tx.fee_data_availability_mode,
367+
nonce_data_availability_mode: tx.nonce_data_availability_mode,
368+
});
369+
declare_tx.calculate_hash(is_query)
370+
}
371+
372+
BroadcastedTx::DeployAccount(tx) => {
373+
let contract_address = tx.contract_address();
374+
let deploy_account_tx = DeployAccountTx::V3(DeployAccountTxV3 {
375+
chain_id: self.chain,
376+
tip: tx.tip.into(),
377+
nonce: tx.nonce,
378+
contract_address,
379+
signature: tx.signature.clone(),
380+
class_hash: tx.class_hash,
381+
paymaster_data: tx.paymaster_data.clone(),
382+
contract_address_salt: tx.contract_address_salt,
383+
constructor_calldata: tx.constructor_calldata.clone(),
384+
resource_bounds: tx.resource_bounds.clone(),
385+
fee_data_availability_mode: tx.fee_data_availability_mode,
386+
nonce_data_availability_mode: tx.nonce_data_availability_mode,
387+
});
388+
deploy_account_tx.calculate_hash(is_query)
389+
}
390+
}
391+
}
392+
}
393+
332394
/// A broadcasted transaction.
333395
#[derive(Debug, Clone, Serialize)]
334396
#[serde(untagged)]
@@ -1216,6 +1278,76 @@ mod tests {
12161278
similar_asserts::assert_eq!(actual, expected);
12171279
}
12181280

1281+
#[test]
1282+
fn broadcasted_tx_with_chain_id_calculate_hash() {
1283+
use katana_primitives::chain::ChainId;
1284+
1285+
// Test with an Invoke transaction
1286+
let invoke_json = json!({
1287+
"type": "INVOKE",
1288+
"version": "0x3",
1289+
"signature": [],
1290+
"tip": "0x0",
1291+
"paymaster_data": [],
1292+
"resource_bounds": {
1293+
"l1_gas": {
1294+
"max_amount": "0x100",
1295+
"max_price_per_unit": "0x200"
1296+
},
1297+
"l2_gas": {
1298+
"max_amount": "0x0",
1299+
"max_price_per_unit": "0x0"
1300+
}
1301+
},
1302+
"nonce_data_availability_mode": "L1",
1303+
"fee_data_availability_mode": "L1",
1304+
"sender_address": "0x123",
1305+
"calldata": ["0x1", "0x2"],
1306+
"nonce": "0x0",
1307+
"account_deployment_data": []
1308+
});
1309+
1310+
let invoke_tx: BroadcastedTx = serde_json::from_value(invoke_json).unwrap();
1311+
let tx_with_chain = BroadcastedTxWithChainId { tx: invoke_tx, chain: ChainId::SEPOLIA };
1312+
1313+
// The hash should be computed successfully
1314+
let hash = tx_with_chain.calculate_hash();
1315+
assert_ne!(hash, Felt::ZERO);
1316+
1317+
// Test with a DeployAccount transaction
1318+
let deploy_account_json = json!({
1319+
"type": "DEPLOY_ACCOUNT",
1320+
"version": "0x3",
1321+
"signature": ["0x1", "0x2", "0x3"],
1322+
"tip": "0x1",
1323+
"paymaster_data": [],
1324+
"resource_bounds": {
1325+
"l1_gas": {
1326+
"max_amount": "0x100",
1327+
"max_price_per_unit": "0x200"
1328+
},
1329+
"l2_gas": {
1330+
"max_amount": "0x123",
1331+
"max_price_per_unit": "0x1337"
1332+
}
1333+
},
1334+
"nonce_data_availability_mode": "L1",
1335+
"fee_data_availability_mode": "L1",
1336+
"nonce": "0x0",
1337+
"contract_address_salt": "0xabc",
1338+
"constructor_calldata": ["0x1", "0x2"],
1339+
"class_hash": "0xdef"
1340+
});
1341+
1342+
let deploy_account_tx: BroadcastedTx = serde_json::from_value(deploy_account_json).unwrap();
1343+
let tx_with_chain =
1344+
BroadcastedTxWithChainId { tx: deploy_account_tx, chain: ChainId::SEPOLIA };
1345+
1346+
// The hash should be computed successfully
1347+
let hash = tx_with_chain.calculate_hash();
1348+
assert_ne!(hash, Felt::ZERO);
1349+
}
1350+
12191351
// This will currently fail because the l2 gas on legacy resource bounds format are not
12201352
// preserved throughout the serialization process.
12211353
#[test]

0 commit comments

Comments
 (0)