diff --git a/lib/ain-ocean/src/api/response.rs b/lib/ain-ocean/src/api/response.rs index 4b9c244450..83cbcd35c2 100644 --- a/lib/ain-ocean/src/api/response.rs +++ b/lib/ain-ocean/src/api/response.rs @@ -128,7 +128,9 @@ mod tests { fn should_next_with_value() { let items: Vec = vec![Item::new("0", "a"), Item::new("1", "b")]; - let next = ApiPagedResponse::next(items, Some("b")).page.next; + let next = ApiPagedResponse::next(items, Some("b".to_string())) + .page + .next; assert_eq!(next, Some("b".into())); } diff --git a/lib/ain-ocean/src/api/transactions.rs b/lib/ain-ocean/src/api/transactions.rs index 9bb495fa30..ef2784ea89 100644 --- a/lib/ain-ocean/src/api/transactions.rs +++ b/lib/ain-ocean/src/api/transactions.rs @@ -1,24 +1,85 @@ -use std::sync::Arc; - -use axum::{extract::Path, routing::get, Router}; -use defichain_rpc::{Client, RpcApi}; +use axum::{ + extract::{Path, Query}, + routing::get, + Json, Router, +}; +use bitcoin::Txid; use serde::Deserialize; +use crate::{ + api_paged_response::ApiPagedResponse, + api_query::PaginationQuery, + model::{Transaction, TransactionVin, TransactionVout}, + repository::RepositoryOps, + Result, SERVICES, +}; + #[derive(Deserialize)] struct TransactionId { - id: String, + id: Txid, } -async fn get_transaction(Path(TransactionId { id }): Path) -> String { - format!("Details of transaction with id {}", id) +async fn get_transaction( + Path(TransactionId { id }): Path, +) -> Result>> { + format!("Details of transaction with id {}", id); + let transactions = SERVICES.transaction.by_id.get(&id)?; + Ok(Json(transactions)) } -async fn get_vins(Path(TransactionId { id }): Path) -> String { - format!("Vins for transaction with id {}", id) +async fn get_vins( + Query(query): Query, +) -> Result>> { + let transaction_list = SERVICES + .transaction + .vin_by_id + .list(None)? + .take(query.size) + .map(|item| { + let (txid, id) = item?; + let b = SERVICES + .transaction + .vin_by_id + .get(&txid)? + .ok_or("Missing block index")?; + + Ok(b) + }) + .collect::>>()?; + + Ok(Json(ApiPagedResponse::of( + transaction_list, + query.size, + |transaction_list| transaction_list.id.clone(), + ))) } -async fn get_vouts(Path(TransactionId { id }): Path) -> String { - format!("Vouts for transaction with id {}", id) +//get list of vout transaction, by passing id which contains txhash + vout_idx +async fn get_vouts( + Query(query): Query, +) -> Result>> { + let transaction_list = SERVICES + .transaction + .vout_by_id + .list(None)? + .take(query.size) + .map(|item| { + let (txid, id) = item?; + let b = SERVICES + .transaction + .vout_by_id + .get(&txid)? + .ok_or("Missing block index")?; + + Ok(b) + }) + .collect::>>()?; + + Ok(Json(ApiPagedResponse::of( + transaction_list, + query.size, + |transaction_list| transaction_list.id.clone(), + ))) } pub fn router(state: Arc) -> Router { diff --git a/lib/ain-ocean/src/indexer/mod.rs b/lib/ain-ocean/src/indexer/mod.rs index 6b336671ce..b3b93b56f6 100644 --- a/lib/ain-ocean/src/indexer/mod.rs +++ b/lib/ain-ocean/src/indexer/mod.rs @@ -2,6 +2,7 @@ mod auction; mod masternode; mod oracle; mod pool; +pub mod transaction; pub mod tx_result; use defichain_rpc::RpcApi; diff --git a/lib/ain-ocean/src/indexer/transaction.rs b/lib/ain-ocean/src/indexer/transaction.rs new file mode 100644 index 0000000000..95301caf7b --- /dev/null +++ b/lib/ain-ocean/src/indexer/transaction.rs @@ -0,0 +1,94 @@ +use bitcoin::{blockdata::locktime::absolute::LockTime, Txid}; +use dftx_rs::{Block, Transaction}; +use log::debug; + +use super::BlockContext; +use crate::{ + indexer::Result, + model::{ + Transaction as TrasnactionMapper, TransactionVin, TransactionVinScript, TransactionVinVout, + TransactionVinVoutScript, TransactionVout, TransactionVoutScript, + }, + repository::RepositoryOps, + SERVICES, +}; + +pub fn index_transactions(ctx: &BlockContext, tx: Transaction) -> Result<()> { + debug!("[CreateTransaction] Indexing..."); + let tx_id = tx.txid(); + + let lock_time_as_i32 = match tx.lock_time { + LockTime::Blocks(value) => value.to_consensus_u32(), + LockTime::Seconds(value) => value.to_consensus_u32(), + }; + let total_vout_value: u64 = tx.output.iter().map(|output| output.value.to_sat()).sum(); + let weight = tx.weight(); + let weight_i32 = weight.to_vbytes_ceil() as i32; + + let trx = TrasnactionMapper { + id: tx_id, + order: 0, + block: ctx.clone(), + txid: tx_id.to_string(), + hash: ctx.hash.to_string(), + version: tx.version.0, + size: tx.total_size() as i32, + v_size: tx.vsize() as i32, + weight: weight_i32, + total_vout_value: total_vout_value.to_string(), + lock_time: lock_time_as_i32 as i32, + vin_count: tx.input.len() as i32, + vout_count: tx.output.len() as i32, + }; + // Index transaction + SERVICES.transaction.by_id.put(&tx_id, &trx)?; + // Indexing transaction vin + for (vin_idx, vin) in tx.input.iter().enumerate() { + let trx_vin = TransactionVin { + id: format!("{}-{}", tx_id, vin_idx), + txid: tx_id.to_string(), + coinbase: vin.script_sig.to_string(), + vout: TransactionVinVout { + id: format!("{}-{}-vout", tx_id, vin_idx), + txid: tx_id.to_string(), + n: vin.previous_output.vout as i32, + value: "0".to_string(), + token_id: 0, + script: TransactionVinVoutScript { + hex: vin.script_sig.to_string(), + }, + }, + script: TransactionVinScript { + hex: vin.script_sig.to_string(), + }, + tx_in_witness: vec![], + sequence: vin.sequence.to_string(), + }; + + SERVICES.transaction.vin_by_id.put(&tx_id, &trx_vin)?; + } + // Index transaction vout + for (vout_idx, vout) in tx.output.iter().enumerate() { + let trx_vout = TransactionVout { + id: format!("{}-{}", tx_id, vout_idx), + txid: tx_id.to_string(), + n: vout_idx as i32, + value: vout.value.to_string(), + token_id: 0, + script: TransactionVoutScript { + hex: vout.script_pubkey.to_string(), + r#type: "pubkey".to_string(), + }, + }; + SERVICES.transaction.vout_by_id.put(&tx_id, &trx_vout)?; + // .put(&format!("{}-{}", tx_id, vout_idx), &trx_vout)?; //need + } + + Ok(()) +} + +pub fn invalidate_transaction(ctx: &BlockContext, tx: Txid, idx: usize) -> Result<()> { + debug!("[CreateMasternode] Invalidating..."); + SERVICES.transaction.by_id.delete(&tx)?; + Ok(()) +} diff --git a/lib/ain-ocean/src/lib.rs b/lib/ain-ocean/src/lib.rs index 3bfd5c0aa0..f0852aef32 100644 --- a/lib/ain-ocean/src/lib.rs +++ b/lib/ain-ocean/src/lib.rs @@ -6,11 +6,17 @@ use std::{path::PathBuf, sync::Arc}; pub use api::ocean_router; use error::OceanError; -pub use indexer::{index_block, invalidate_block, tx_result, BlockV2Info}; +pub use indexer::{ + index_block, invalidate_block, + transaction::{index_transactions, invalidate_transaction}, + tx_result, BlockV2Info, +}; +use model::TransactionVin; use repository::{ AuctionHistoryByHeightRepository, AuctionHistoryRepository, BlockByHeightRepository, BlockRepository, MasternodeByHeightRepository, MasternodeRepository, MasternodeStatsRepository, - PoolSwapRepository, RawBlockRepository, TxResultRepository, + PoolSwapRepository, RawBlockRepository, TransactionRepository, TransactionVinRepository, + TransactionVoutRepository, TxResultRepository, }; pub mod api; mod model; @@ -50,6 +56,12 @@ pub struct PoolService { by_id: PoolSwapRepository, } +pub struct TransactionService { + by_id: TransactionRepository, + vin_by_id: TransactionVinRepository, + vout_by_id: TransactionVoutRepository, +} + pub struct Services { masternode: MasternodeService, block: BlockService, @@ -57,6 +69,7 @@ pub struct Services { result: TxResultRepository, pool: PoolService, client: Arc, + transaction: TransactionService, } impl Services { @@ -89,6 +102,11 @@ impl Services { pool: PoolService { by_id: PoolSwapRepository::new(Arc::clone(&store)), }, + transaction: TransactionService { + by_id: TransactionRepository::new(Arc::clone(&store)), + vin_by_id: TransactionVinRepository::new(Arc::clone(&store)), + vout_by_id: TransactionVoutRepository::new(Arc::clone(&store)), + }, client, }) } diff --git a/lib/ain-ocean/src/model/transaction.rs b/lib/ain-ocean/src/model/transaction.rs index f1573415d2..f4ba120703 100644 --- a/lib/ain-ocean/src/model/transaction.rs +++ b/lib/ain-ocean/src/model/transaction.rs @@ -1,3 +1,4 @@ +use bitcoin::Txid; use serde::{Deserialize, Serialize}; use super::BlockContext; @@ -5,7 +6,7 @@ use super::BlockContext; #[derive(Serialize, Deserialize, Debug)] #[serde(rename_all = "camelCase")] pub struct Transaction { - pub id: String, + pub id: Txid, pub order: i32, pub block: BlockContext, pub txid: String, diff --git a/lib/ain-ocean/src/repository/transaction.rs b/lib/ain-ocean/src/repository/transaction.rs index 8b13789179..a8f271694b 100644 --- a/lib/ain-ocean/src/repository/transaction.rs +++ b/lib/ain-ocean/src/repository/transaction.rs @@ -1 +1,27 @@ +use std::sync::Arc; +use ain_db::LedgerColumn; +use ain_macros::Repository; +use bitcoin::{BlockHash, Txid}; + +use super::RepositoryOps; +use crate::{ + model::Transaction, + storage::{columns, ocean_store::OceanStore}, + Result, +}; +#[derive(Repository)] +#[repository(K = "Txid", V = "Transaction")] +pub struct TransactionRepository { + pub store: Arc, + col: LedgerColumn, +} + +impl TransactionRepository { + pub fn new(store: Arc) -> Self { + Self { + col: store.column(), + store, + } + } +} diff --git a/lib/ain-ocean/src/repository/transaction_vin.rs b/lib/ain-ocean/src/repository/transaction_vin.rs index 8b13789179..7d2c2ff375 100644 --- a/lib/ain-ocean/src/repository/transaction_vin.rs +++ b/lib/ain-ocean/src/repository/transaction_vin.rs @@ -1 +1,28 @@ +use std::sync::Arc; +use ain_db::LedgerColumn; +use ain_macros::Repository; +use bitcoin::Txid; + +use super::RepositoryOps; +use crate::{ + model::TransactionVin, + storage::{columns, ocean_store::OceanStore}, + Result, +}; + +#[derive(Repository)] +#[repository(K = "Txid", V = "TransactionVin")] +pub struct TransactionVinRepository { + pub store: Arc, + col: LedgerColumn, +} + +impl TransactionVinRepository { + pub fn new(store: Arc) -> Self { + Self { + col: store.column(), + store, + } + } +} diff --git a/lib/ain-ocean/src/repository/transaction_vout.rs b/lib/ain-ocean/src/repository/transaction_vout.rs index 8b13789179..1bbd8cc6a2 100644 --- a/lib/ain-ocean/src/repository/transaction_vout.rs +++ b/lib/ain-ocean/src/repository/transaction_vout.rs @@ -1 +1,28 @@ +use std::sync::Arc; +use ain_db::LedgerColumn; +use ain_macros::Repository; +use bitcoin::Txid; + +use super::RepositoryOps; +use crate::{ + model::TransactionVout, + storage::{columns, ocean_store::OceanStore}, + Result, +}; + +#[derive(Repository)] +#[repository(K = "Txid", V = "TransactionVout")] +pub struct TransactionVoutRepository { + pub store: Arc, + col: LedgerColumn, +} + +impl TransactionVoutRepository { + pub fn new(store: Arc) -> Self { + Self { + col: store.column(), + store, + } + } +} diff --git a/lib/ain-ocean/src/storage/columns/transaction.rs b/lib/ain-ocean/src/storage/columns/transaction.rs index 8c2052f21a..d3fefc4558 100644 --- a/lib/ain-ocean/src/storage/columns/transaction.rs +++ b/lib/ain-ocean/src/storage/columns/transaction.rs @@ -1,5 +1,7 @@ use ain_db::{Column, ColumnName, TypedColumn}; +use bitcoin::{BlockHash, Txid}; +use crate::model; #[derive(Debug)] pub struct Transaction; @@ -8,9 +10,23 @@ impl ColumnName for Transaction { } impl Column for Transaction { - type Index = String; + type Index = Txid; } impl TypedColumn for Transaction { - type Type = String; + type Type = model::Transaction; +} + +pub struct TransactionByBlockHash; + +impl ColumnName for TransactionByBlockHash { + const NAME: &'static str = "transaction_by_block_hash"; +} + +impl Column for TransactionByBlockHash { + type Index = BlockHash; +} + +impl TypedColumn for TransactionByBlockHash { + type Type = model::Transaction; } diff --git a/lib/ain-ocean/src/storage/columns/transaction_vin.rs b/lib/ain-ocean/src/storage/columns/transaction_vin.rs index a8149b1e5b..bbe2fa4b51 100644 --- a/lib/ain-ocean/src/storage/columns/transaction_vin.rs +++ b/lib/ain-ocean/src/storage/columns/transaction_vin.rs @@ -1,4 +1,7 @@ use ain_db::{Column, ColumnName, TypedColumn}; +use bitcoin::Txid; + +use crate::model; #[derive(Debug)] pub struct TransactionVin; @@ -8,9 +11,9 @@ impl ColumnName for TransactionVin { } impl Column for TransactionVin { - type Index = String; + type Index = Txid; } impl TypedColumn for TransactionVin { - type Type = String; + type Type = model::TransactionVin; } diff --git a/lib/ain-ocean/src/storage/columns/transaction_vout.rs b/lib/ain-ocean/src/storage/columns/transaction_vout.rs index 7de7b41c18..3af1bd48da 100644 --- a/lib/ain-ocean/src/storage/columns/transaction_vout.rs +++ b/lib/ain-ocean/src/storage/columns/transaction_vout.rs @@ -1,4 +1,7 @@ use ain_db::{Column, ColumnName, TypedColumn}; +use bitcoin::Txid; + +use crate::model; #[derive(Debug)] pub struct TransactionVout; @@ -7,9 +10,9 @@ impl ColumnName for TransactionVout { } impl Column for TransactionVout { - type Index = String; + type Index = Txid; } impl TypedColumn for TransactionVout { - type Type = String; + type Type = model::TransactionVout; }