diff --git a/crates/rooch-open-rpc-spec/schemas/openrpc.json b/crates/rooch-open-rpc-spec/schemas/openrpc.json index 7d7efbd388..3e632908f3 100644 --- a/crates/rooch-open-rpc-spec/schemas/openrpc.json +++ b/crates/rooch-open-rpc-spec/schemas/openrpc.json @@ -1237,6 +1237,12 @@ "owner": { "$ref": "#/components/schemas/rooch_types::address::RoochAddress" }, + "owner_bitcoin_address": { + "type": [ + "string", + "null" + ] + }, "size": { "type": "integer", "format": "uint64", @@ -1676,6 +1682,12 @@ "sender": { "type": "string" }, + "sender_bitcoin_address": { + "type": [ + "string", + "null" + ] + }, "sequence_number": { "type": "integer", "format": "uint64", @@ -1914,6 +1926,12 @@ "owner": { "$ref": "#/components/schemas/rooch_types::address::RoochAddress" }, + "owner_bitcoin_address": { + "type": [ + "string", + "null" + ] + }, "size": { "type": "integer", "format": "uint64", diff --git a/crates/rooch-rpc-api/src/jsonrpc_types/rooch_types.rs b/crates/rooch-rpc-api/src/jsonrpc_types/rooch_types.rs index c11caf4f16..40ce25152d 100644 --- a/crates/rooch-rpc-api/src/jsonrpc_types/rooch_types.rs +++ b/crates/rooch-rpc-api/src/jsonrpc_types/rooch_types.rs @@ -44,16 +44,21 @@ pub struct PageView { pub struct TransactionView { pub sequence_number: u64, pub sender: String, + pub sender_bitcoin_address: Option, pub action_type: MoveActionTypeView, pub action: MoveActionView, pub raw: BytesView, } -impl From for TransactionView { - fn from(transaction: RoochTransaction) -> Self { +impl TransactionView { + pub fn new_from_rooch_transaction( + transaction: RoochTransaction, + sender_bitcoin_address: Option, + ) -> Self { Self { sequence_number: transaction.sequence_number(), sender: transaction.sender().to_string(), + sender_bitcoin_address, action: transaction.action().clone().into(), action_type: transaction.action().clone().into(), raw: transaction.encode().into(), diff --git a/crates/rooch-rpc-api/src/jsonrpc_types/state_view.rs b/crates/rooch-rpc-api/src/jsonrpc_types/state_view.rs index 6f69ab092d..8106d4d8a4 100644 --- a/crates/rooch-rpc-api/src/jsonrpc_types/state_view.rs +++ b/crates/rooch-rpc-api/src/jsonrpc_types/state_view.rs @@ -355,6 +355,7 @@ impl From for StateSyncFilter { pub struct IndexerObjectStateView { pub object_id: ObjectID, pub owner: RoochAddressView, + pub owner_bitcoin_address: Option, pub flag: u8, /// bcs bytes of the Object. pub value: BytesView, @@ -373,12 +374,14 @@ impl IndexerObjectStateView { pub fn new_from_object_state( state: IndexerObjectState, value: Vec, + owner_bitcoin_address: Option, decoded_value: Option, display_fields: Option, ) -> IndexerObjectStateView { IndexerObjectStateView { object_id: state.object_id, owner: state.owner.into(), + owner_bitcoin_address, flag: state.flag, value: value.into(), decoded_value, @@ -437,6 +440,7 @@ impl ObjectStateFilterView { pub struct ObjectStateView { pub id: ObjectID, pub owner: RoochAddressView, + pub owner_bitcoin_address: Option, pub flag: u8, pub object_type: StructTagView, pub state_root: H256View, @@ -453,6 +457,7 @@ impl ObjectStateView { ObjectStateView { id: object.id, owner: object.owner.into(), + owner_bitcoin_address: None, flag: object.flag, object_type: object.value.type_.clone().into(), state_root: object.state_root.into(), @@ -473,6 +478,7 @@ impl ObjectStateView { ObjectStateView { id: object.id, owner: object.owner.into(), + owner_bitcoin_address: None, flag: object.flag, object_type: object.value.struct_tag.into(), state_root: object.state_root.into(), @@ -489,4 +495,9 @@ impl ObjectStateView { self.display_fields = display_fields; self } + + pub fn with_owner_bitcoin_address(mut self, owner_bitcoin_address: Option) -> Self { + self.owner_bitcoin_address = owner_bitcoin_address; + self + } } diff --git a/crates/rooch-rpc-api/src/jsonrpc_types/transaction_view.rs b/crates/rooch-rpc-api/src/jsonrpc_types/transaction_view.rs index 67356fea98..0f112a3279 100644 --- a/crates/rooch-rpc-api/src/jsonrpc_types/transaction_view.rs +++ b/crates/rooch-rpc-api/src/jsonrpc_types/transaction_view.rs @@ -35,11 +35,16 @@ pub enum LedgerTxDataView { L2Tx(TransactionView), } -impl From for LedgerTxDataView { - fn from(data: LedgerTxData) -> Self { +impl LedgerTxDataView { + pub fn new_from_ledger_txdata( + data: LedgerTxData, + sender_bitcoin_address: Option, + ) -> Self { match data { LedgerTxData::L1Block(block) => LedgerTxDataView::L1Block(block.into()), - LedgerTxData::L2Tx(tx) => LedgerTxDataView::L2Tx(tx.into()), + LedgerTxData::L2Tx(tx) => LedgerTxDataView::L2Tx( + TransactionView::new_from_rooch_transaction(tx, sender_bitcoin_address), + ), } } } @@ -50,10 +55,13 @@ pub struct LedgerTransactionView { pub sequence_info: TransactionSequenceInfoView, } -impl From for LedgerTransactionView { - fn from(tx: LedgerTransaction) -> Self { +impl LedgerTransactionView { + pub fn new_from_ledger_transaction( + tx: LedgerTransaction, + sender_bitcoin_address: Option, + ) -> Self { Self { - data: tx.data.into(), + data: LedgerTxDataView::new_from_ledger_txdata(tx.data, sender_bitcoin_address), sequence_info: tx.sequence_info.into(), } } @@ -65,10 +73,16 @@ pub struct TransactionWithInfoView { pub execution_info: TransactionExecutionInfoView, } -impl From for TransactionWithInfoView { - fn from(tx: TransactionWithInfo) -> Self { +impl TransactionWithInfoView { + pub fn new_from_transaction_with_info( + tx: TransactionWithInfo, + sender_bitcoin_address: Option, + ) -> Self { Self { - transaction: tx.transaction.into(), + transaction: LedgerTransactionView::new_from_ledger_transaction( + tx.transaction, + sender_bitcoin_address, + ), execution_info: tx.execution_info.into(), } } diff --git a/crates/rooch-rpc-server/src/server/rooch_server.rs b/crates/rooch-rpc-server/src/server/rooch_server.rs index 53a0800d27..e9b0fbd83b 100644 --- a/crates/rooch-rpc-server/src/server/rooch_server.rs +++ b/crates/rooch-rpc-server/src/server/rooch_server.rs @@ -44,7 +44,7 @@ use rooch_rpc_api::{ }; use rooch_types::indexer::event::IndexerEventID; use rooch_types::indexer::state::IndexerStateID; -use rooch_types::transaction::rooch::RoochTransaction; +use rooch_types::transaction::{RoochTransaction, TransactionWithInfo}; use std::cmp::min; use std::str::FromStr; use tracing::info; @@ -116,6 +116,39 @@ impl RoochServer { } Ok(display_field_views) } + + async fn transactions_to_view( + &self, + data: Vec, + ) -> Result> { + let rooch_addresses = data + .iter() + .filter_map(|tx| tx.transaction.sender()) + .collect::>(); + let address_mapping = self + .rpc_service + .get_bitcoin_addresses(rooch_addresses) + .await?; + let bitcoin_network = self.rpc_service.get_bitcoin_network().await?; + let data = data + .into_iter() + .map(|tx| { + let sender_bitcoin_address = match tx.transaction.sender() { + Some(rooch_address) => address_mapping + .get(&rooch_address) + .map(|addr| addr.clone().map(|a| a.format(bitcoin_network))), + None => None, + } + .flatten() + .transpose()?; + Ok(TransactionWithInfoView::new_from_transaction_with_info( + tx, + sender_bitcoin_address, + )) + }) + .collect::>>()?; + Ok(data) + } } #[async_trait] @@ -362,6 +395,31 @@ impl RoochAPIServer for RoochServer { }) .collect() }; + + // Get owner_bitcoin_address + let addresses = objects_view + .iter() + .filter_map(|o| o.as_ref().map(|s| s.owner.into())) + .collect::>(); + let btc_network = self.rpc_service.get_bitcoin_network().await?; + let address_mapping = self.rpc_service.get_bitcoin_addresses(addresses).await?; + + let objects_view = objects_view + .into_iter() + .map(|o| { + o.map(|s| { + let rooch_address = s.owner.into(); + let bitcoin_address = address_mapping + .get(&rooch_address) + .expect("should exist.") + .clone() + .map(|a| a.format(btc_network)) + .transpose()?; + Ok(s.with_owner_bitcoin_address(bitcoin_address)) + }) + .transpose() + }) + .collect::>>()?; Ok(objects_view) } @@ -427,13 +485,41 @@ impl RoochAPIServer for RoochServer { ) -> RpcResult>> { let tx_hashes: Vec = tx_hashes.iter().map(|m| (*m).into()).collect::>(); + let bitcoin_network = self.rpc_service.get_bitcoin_network().await?; let data = self .aggregate_service .get_transaction_with_info(tx_hashes) - .await? - .into_iter() - .map(|item| item.map(TransactionWithInfoView::from)) + .await?; + + let rooch_addresses = data + .iter() + .filter_map(|tx| tx.as_ref().and_then(|tx| tx.transaction.sender())) .collect::>(); + let address_mapping = self + .rpc_service + .get_bitcoin_addresses(rooch_addresses) + .await?; + + let data = data + .into_iter() + .map(|item| { + item.map(|tx| { + let sender_bitcoin_address = match tx.transaction.sender() { + Some(rooch_address) => address_mapping + .get(&rooch_address) + .map(|addr| addr.clone().map(|a| a.format(bitcoin_network))), + None => None, + } + .flatten() + .transpose()?; + Ok(TransactionWithInfoView::new_from_transaction_with_info( + tx, + sender_bitcoin_address, + )) + }) + .transpose() + }) + .collect::>>()?; Ok(data) } @@ -502,9 +588,10 @@ impl RoochAPIServer for RoochServer { .await? .into_iter() .flatten() - .map(TransactionWithInfoView::from) .collect::>(); + let data = self.transactions_to_view(data).await?; + Ok(TransactionWithInfoPageView { data, next_cursor, @@ -582,13 +669,13 @@ impl RoochAPIServer for RoochServer { let mut data = self .aggregate_service .build_transaction_with_infos(txs) - .await? - .into_iter() - .map(TransactionWithInfoView::from) - .collect::>(); + .await?; let has_next_page = data.len() > limit_of; data.truncate(limit_of); + + let data = self.transactions_to_view(data).await?; + let next_cursor = data .last() .cloned() @@ -703,22 +790,36 @@ impl RoochAPIServer for RoochServer { .collect::>() }; + let network = self.rpc_service.get_bitcoin_network().await?; + let rooch_addresses = states.iter().map(|s| s.owner).collect::>(); + let bitcoin_addresses = self + .rpc_service + .get_bitcoin_addresses(rooch_addresses) + .await? + .into_values() + .map(|btc_addr| btc_addr.map(|addr| addr.format(network)).transpose()) + .collect::>>>()?; + let mut data = annotated_states_with_display .into_iter() .zip(states) - .map(|((value, annotated_state, display_fields), state)| { - let decoded_value = if decode { - Some(AnnotatedMoveStructView::from(annotated_state.value)) - } else { - None - }; - IndexerObjectStateView::new_from_object_state( - state, - value, - decoded_value, - display_fields, - ) - }) + .zip(bitcoin_addresses) + .map( + |(((value, annotated_state, display_fields), state), bitcoin_address)| { + let decoded_value = if decode { + Some(AnnotatedMoveStructView::from(annotated_state.value)) + } else { + None + }; + IndexerObjectStateView::new_from_object_state( + state, + value, + bitcoin_address, + decoded_value, + display_fields, + ) + }, + ) .collect::>(); let has_next_page = data.len() > limit_of; diff --git a/crates/rooch-types/src/transaction/ledger_transaction.rs b/crates/rooch-types/src/transaction/ledger_transaction.rs index fc0ddf5155..cc676646a1 100644 --- a/crates/rooch-types/src/transaction/ledger_transaction.rs +++ b/crates/rooch-types/src/transaction/ledger_transaction.rs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 use super::{RoochTransaction, TransactionSequenceInfo}; -use crate::multichain_id::MultiChainID; +use crate::{address::RoochAddress, multichain_id::MultiChainID}; use anyhow::Result; use moveos_types::h256::H256; use serde::{Deserialize, Serialize}; @@ -47,6 +47,13 @@ impl LedgerTxData { LedgerTxData::L2Tx(tx) => tx.tx_hash(), } } + + pub fn sender(&self) -> Option { + match self { + LedgerTxData::L1Block(_) => None, + LedgerTxData::L2Tx(tx) => Some(tx.sender()), + } + } } /// The transaction which is recorded in the L2 DA ledger. @@ -91,6 +98,10 @@ impl LedgerTransaction { self.data.tx_hash() } + pub fn sender(&self) -> Option { + self.data.sender() + } + pub fn encode(&self) -> Vec { bcs::to_bytes(self).expect("encode transaction should success") }