From 69672eac75e71530d8124efb6a66cd4590f67745 Mon Sep 17 00:00:00 2001 From: liyukun Date: Fri, 8 Dec 2023 21:34:02 +0800 Subject: [PATCH] feat: adjust tx proof calculation --- .github/workflows/ibc-test.yaml | 4 +- crates/relayer/src/chain/ckb/communication.rs | 2 + .../relayer/src/chain/ckb/mock_rpc_client.rs | 4 + crates/relayer/src/chain/ckb/rpc_client.rs | 4 + crates/relayer/src/chain/ckb4ibc.rs | 30 +---- crates/relayer/src/chain/ckb4ibc/utils.rs | 123 ++++++++++++------ tools/ibc-test/src/rpc_client.rs | 11 ++ 7 files changed, 111 insertions(+), 67 deletions(-) diff --git a/.github/workflows/ibc-test.yaml b/.github/workflows/ibc-test.yaml index 8c5846f5..2d66af23 100644 --- a/.github/workflows/ibc-test.yaml +++ b/.github/workflows/ibc-test.yaml @@ -28,9 +28,7 @@ jobs: timeout-minutes: 75 env: SRC_DIR: ${{ github.workspace }}/ibc-test-src - # https://github.com/axonweb3/axon/commits/forcerelay-dev - # commit: 7ae97ae5ffd3429746315acea77a742f6bdb6f0c - AXON_COMMIT: forcerelay-dev + AXON_COMMIT: dd35f500fce0ec3adf28b59fea8c431a4a4087c8 IBC_CONTRACT_COMMIT: c5417573ec15c8aaab048caa1ec5f3bd50c2170e strategy: fail-fast: false diff --git a/crates/relayer/src/chain/ckb/communication.rs b/crates/relayer/src/chain/ckb/communication.rs index bd84a7b1..dfd432b2 100644 --- a/crates/relayer/src/chain/ckb/communication.rs +++ b/crates/relayer/src/chain/ckb/communication.rs @@ -20,6 +20,8 @@ pub trait CkbReader { fn get_tip_header(&self) -> Response; + fn get_header(&self, hash: &H256) -> Response>; + fn get_transaction(&self, hash: &H256) -> Response>; fn get_live_cell(&self, out_point: &OutPoint, with_data: bool) -> Response; diff --git a/crates/relayer/src/chain/ckb/mock_rpc_client.rs b/crates/relayer/src/chain/ckb/mock_rpc_client.rs index 0202323d..d2581017 100644 --- a/crates/relayer/src/chain/ckb/mock_rpc_client.rs +++ b/crates/relayer/src/chain/ckb/mock_rpc_client.rs @@ -105,6 +105,10 @@ impl CkbReader for RpcClient { Box::pin(async { Ok(resp) }) } + fn get_header(&self, _hash: &H256) -> Rpc> { + todo!() + } + fn get_transaction(&self, hash: &H256) -> Rpc> { let transaction = ResponseFormat::::json(Default::default()); let resp = TransactionWithStatusResponse { diff --git a/crates/relayer/src/chain/ckb/rpc_client.rs b/crates/relayer/src/chain/ckb/rpc_client.rs index cec9d96a..7abb0e53 100644 --- a/crates/relayer/src/chain/ckb/rpc_client.rs +++ b/crates/relayer/src/chain/ckb/rpc_client.rs @@ -98,6 +98,10 @@ impl CkbReader for RpcClient { jsonrpc!("get_tip_header", Target::CKB, self, HeaderView).boxed() } + fn get_header(&self, hash: &H256) -> Rpc> { + jsonrpc!("get_header", Target::CKB, self, Option, hash).boxed() + } + fn get_transaction(&self, hash: &H256) -> Rpc> { jsonrpc!( "get_transaction", diff --git a/crates/relayer/src/chain/ckb4ibc.rs b/crates/relayer/src/chain/ckb4ibc.rs index 7b69a62a..bb4c40cb 100644 --- a/crates/relayer/src/chain/ckb4ibc.rs +++ b/crates/relayer/src/chain/ckb4ibc.rs @@ -82,7 +82,7 @@ use self::monitor::{Ckb4IbcEventMonitor, WriteAckMonitorCmd}; use self::utils::{ fetch_transaction_by_hash, generate_ibc_packet_event, generate_tx_proof_from_block, get_channel_search_key, get_encoded_object, get_ibc_merkle_proof, get_packet_search_key, - get_prefix_search_key, get_search_key_with_sudt, transaction_to_event, + get_prefix_search_key, get_search_key_with_sudt, parse_transaction, transaction_to_event, }; use super::ckb::rpc_client::RpcClient; @@ -306,12 +306,7 @@ impl Ckb4IbcChain { .ok_or(Error::query("ckb transaction unready".to_string()))? .transaction .unwrap(); - let tx = match tx_resp.inner { - ckb_jsonrpc_types::Either::Left(tx) => tx, - ckb_jsonrpc_types::Either::Right(json_bytes) => { - serde_json::from_slice(json_bytes.as_bytes()).unwrap() - } - }; + let tx = parse_transaction(tx_resp); let channel_end = extract_channel_end_from_tx(&tx)?; let input = CellInput::new_builder() .previous_output(cell.out_point.clone().into()) @@ -373,12 +368,7 @@ impl Ckb4IbcChain { .expect("empty transaction response") .transaction .expect("empty transaction view"); - let tx = match tx.inner { - ckb_jsonrpc_types::Either::Left(tx) => tx, - ckb_jsonrpc_types::Either::Right(bytes) => { - serde_json::from_slice::(bytes.as_bytes()).unwrap() - } - }; + let tx = parse_transaction(tx); let (connections, ibc_connection) = extract_connections_from_tx(&tx, &prefix)?; cache.insert( client_type, @@ -1067,12 +1057,7 @@ impl ChainEndpoint for Ckb4IbcChain { None }) .flat_map(|tx| { - let tx = match tx.transaction.unwrap().inner { - ckb_jsonrpc_types::Either::Left(tx) => tx, - ckb_jsonrpc_types::Either::Right(bytes) => { - serde_json::from_slice::(bytes.as_bytes()).unwrap() - } - }; + let tx = parse_transaction(tx.transaction.unwrap()); extract_channel_end_from_tx(&tx) }) .map(|(channel, _)| channel) @@ -1348,12 +1333,7 @@ impl ChainEndpoint for Ckb4IbcChain { let Some(tx) = tx.transaction else { return Ok(vec![]); }; - let tx = match tx.inner { - ckb_jsonrpc_types::Either::Left(tx) => tx, - ckb_jsonrpc_types::Either::Right(json_bytes) => { - serde_json::from_slice(json_bytes.as_bytes()).unwrap() - } - }; + let tx = parse_transaction(tx); let event = transaction_to_event(&tx, &prefix)?; vec![IbcEventWithHeight { event, diff --git a/crates/relayer/src/chain/ckb4ibc/utils.rs b/crates/relayer/src/chain/ckb4ibc/utils.rs index 05b7f7a3..38c31ff6 100644 --- a/crates/relayer/src/chain/ckb4ibc/utils.rs +++ b/crates/relayer/src/chain/ckb4ibc/utils.rs @@ -11,7 +11,9 @@ use ckb_ics_axon::consts::CHANNEL_ID_PREFIX; use ckb_ics_axon::handler::IbcPacket; use ckb_ics_axon::message::MsgType; use ckb_ics_axon::{ChannelArgs, PacketArgs}; -use ckb_jsonrpc_types::{TransactionAndWitnessProof, TransactionView}; +use ckb_jsonrpc_types::{ + MerkleProof as JsonMerkleProof, ResponseFormat, TransactionAndWitnessProof, TransactionView, +}; use ckb_sdk::constants::TYPE_ID_CODE_HASH; use ckb_sdk::rpc::ckb_indexer::ScriptSearchMode; use ckb_sdk::rpc::ckb_light_client::{ScriptType, SearchKey}; @@ -20,7 +22,7 @@ use ckb_sdk::NetworkType; use ckb_types::core::ScriptHashType; use ckb_types::packed::{Byte32, Bytes, BytesOpt, OutPoint, Script, Transaction}; use ckb_types::prelude::{Builder, Entity, Pack, Unpack}; -use ckb_types::utilities::merkle_root; +use ckb_types::utilities::{merkle_root, MerkleProof}; use ckb_types::{h256, H256}; use ethers::abi::AbiEncode; use ethers::contract::{EthAbiCodec, EthAbiType}; @@ -526,6 +528,15 @@ pub fn get_ibc_merkle_proof(height: Height, encoded: Vec) -> Result) -> TransactionView { + match tx.inner { + ckb_jsonrpc_types::Either::Left(tx) => tx, + ckb_jsonrpc_types::Either::Right(bytes) => { + serde_json::from_slice::(bytes.as_bytes()).unwrap() + } + } +} + #[derive(EthAbiCodec, EthAbiType)] struct AxonObjectProof { pub ckb_transaction: Vec, @@ -537,11 +548,11 @@ pub async fn generate_tx_proof_from_block( rpc_client: &impl CkbReader, tx_hash: &H256, ) -> Result, Error> { - let block_hash = rpc_client + let result = rpc_client .get_transaction(tx_hash) .await? - .map(|v| v.tx_status.block_hash); - let Some(Some(block_hash)) = block_hash else { + .map(|v| (v.tx_status.block_hash, v.transaction)); + let Some((Some(block_hash), Some(transaction))) = result else { return Err(Error::other_error(format!( "cannot find block_hash from tx {}", hex::encode(tx_hash) @@ -549,49 +560,69 @@ pub async fn generate_tx_proof_from_block( }; // collect transaction hashes from block - let mut transaction: Option = None; - let block = rpc_client.get_block(&block_hash).await?; - let tx_hashes = block - .transactions - .iter() - .map(|tx| { - if &tx.hash == tx_hash { - transaction = Some(tx.inner.clone().into()); - } - tx.hash.clone() - }) - .collect_vec(); - let witness_hashes = block - .transactions - .into_iter() - .map(|tx| Transaction::from(tx.inner).calc_witness_hash().unpack()) - .collect_vec(); - - let Some(transaction) = transaction else { - return Ok(None); - }; + // let mut transaction: Option = None; + // let block = rpc_client.get_block(&block_hash).await?; + // let tx_hashes = block + // .transactions + // .iter() + // .map(|tx| { + // if &tx.hash == tx_hash { + // transaction = Some(tx.inner.clone().into()); + // } + // tx.hash.clone() + // }) + // .collect_vec(); + // let witness_hashes = block + // .transactions + // .into_iter() + // .map(|tx| Transaction::from(tx.inner).calc_witness_hash().unpack()) + // .collect_vec(); + + // let Some(transaction) = transaction else { + // return Ok(None); + // }; + + let header = rpc_client + .get_header(&block_hash) + .await? + .expect("invalid block_hash"); // generate transaction proof let TransactionAndWitnessProof { block_hash, - transactions_proof: _, - witnesses_proof: proof, + transactions_proof, + witnesses_proof, } = rpc_client - .get_transaction_and_witness_proof(tx_hashes.clone(), block.header.hash) + .get_transaction_and_witness_proof(vec![tx_hash.clone()], block_hash) .await?; - let raw_transaction_root = merkle_root(&tx_hashes.iter().map(Pack::pack).collect_vec()); - let witnesses_root = merkle_root(&witness_hashes.iter().map(Pack::pack).collect_vec()); + let transaction = Transaction::from(parse_transaction(transaction).inner); + let transaction_hash = transaction.calc_tx_hash(); + let witness_hash = transaction.calc_witness_hash(); + + let raw_transactions_root = jsonrpc_merkle_root(&transactions_proof, vec![transaction_hash])?; + let witnesses_root = jsonrpc_merkle_root(&witnesses_proof, vec![witness_hash.clone()])?; + + let transactions_root = merkle_root(&[raw_transactions_root.pack(), witnesses_root.pack()]); + if transactions_root.unpack() != header.inner.transactions_root { + return Err(Error::other_error( + "unexpected transactions_root".to_owned(), + )); + } let proof_payload = VerifyProofPayload { verify_type: 1, // to verify witness - transactions_root: block.header.inner.transactions_root.into(), - witnesses_root: witnesses_root.unpack().into(), - raw_transactions_root: raw_transaction_root.unpack().into(), + transactions_root: header.inner.transactions_root.into(), + witnesses_root, + raw_transactions_root, proof: Proof { - indices: proof.indices.into_iter().map(Into::into).collect(), - lemmas: proof.lemmas.into_iter().map(Into::into).collect(), - leaves: witness_hashes.into_iter().map(Into::into).collect(), + indices: witnesses_proof + .indices + .into_iter() + .map(Into::into) + .collect_vec(), + lemmas: witnesses_proof.lemmas.into_iter().map(Into::into).collect(), + leaves: vec![witness_hash.unpack().into()], }, }; @@ -599,13 +630,27 @@ pub async fn generate_tx_proof_from_block( .map_err(|err| Error::other_error(format!("proof payload verify failed: {err}")))?; let object_proof = AxonObjectProof { - ckb_transaction: transaction.as_slice().to_owned(), + ckb_transaction: transaction.as_slice().into(), block_hash: block_hash.into(), proof_payload, }; // assemble ibc-compatible proof - let block_number = Height::from_noncosmos_height(block.header.inner.number.into()); + let block_number = Height::from_noncosmos_height(header.inner.number.into()); let proofs = get_ibc_merkle_proof(block_number, object_proof.encode())?; Ok(Some(proofs)) } + +fn jsonrpc_merkle_root( + merkle_proof: &JsonMerkleProof, + leaves: Vec, +) -> Result<[u8; 32], Error> { + let proof = merkle_proof.clone(); + MerkleProof::new( + proof.indices.into_iter().map(Into::into).collect(), + proof.lemmas.into_iter().map(|v| v.pack()).collect(), + ) + .root(&leaves) + .map(|v| v.unpack().into()) + .ok_or(Error::other_error("invalid merkle proof".to_owned())) +} diff --git a/tools/ibc-test/src/rpc_client.rs b/tools/ibc-test/src/rpc_client.rs index 534db0ec..de0adaed 100644 --- a/tools/ibc-test/src/rpc_client.rs +++ b/tools/ibc-test/src/rpc_client.rs @@ -98,6 +98,17 @@ impl CkbReader for RpcClient { jsonrpc!("get_tip_header", Target::CKB, self, HeaderView).boxed() } + fn get_header(&self, hash: &H256) -> Rpc> { + jsonrpc!( + "get_tip_header", + Target::CKB, + self, + Option, + hash + ) + .boxed() + } + fn get_transaction(&self, hash: &H256) -> Rpc> { jsonrpc!( "get_transaction",