diff --git a/explorer-mapper/src/block.rs b/explorer-mapper/src/block.rs index 5c654367b..2f9b4ccdc 100644 --- a/explorer-mapper/src/block.rs +++ b/explorer-mapper/src/block.rs @@ -1,9 +1,19 @@ use anyhow::Result; -use everscale_types::models::{Block as BCBlock, BlockId, BlockInfo as BCBlockInfo, PrevBlockRef}; -use explorer_models::{Block, BlockInfo, ExtBlkRef, GlobalVersion, HashInsert, ProcessingContext}; +use everscale_types::models::{ + Block as BCBlock, BlockExtra, BlockId, BlockInfo as BCBlockInfo, PrevBlockRef, +}; +use explorer_models::schema::blocks::block_info; +use explorer_models::schema::sql_types::State; +use explorer_models::{ + to_json_value, AccountUpdate, BlkPrevInfo, Block, BlockInfo, ExtBlkRef, GlobalVersion, + HashInsert, JsonValue, ProcessingContext, ValueFlow, +}; + +use crate::transaction::process_transaction; pub fn fill_block(ctx: &mut ProcessingContext, block: &BCBlock, block_id: BlockId) -> Result<()> { let block_info = block.load_info()?; + let extra = block.load_extra()?; let prev_info = match block_info.load_prev_ref()? { PrevBlockRef::Single(rf) => (rf, None), PrevBlockRef::AfterMerge { left, right } => (left, Some(right)), @@ -30,21 +40,48 @@ pub fn fill_block(ctx: &mut ProcessingContext, block: &BCBlock, block_id: BlockI prev2: prev_info.1.map(|rf| HashInsert { hash: rf.root_hash.0, }), - prev2_seqno: prev_info.1.map(|rf| rf.seqno), + prev2_seqno: prev_info.1.as_ref().map(|rf| rf.seqno), prev_key_block: block_info.prev_key_block_seqno, - block_info: "".to_string(), - value_flow: "".to_string(), + block_info: map_block_info(&block_info)?, + value_flow: map_value_flow(block)?, account_blocks: "".to_string(), shards_info: None, additional_info: None, }; } -fn fill_block_info( - ctx: &mut ProcessingContext, - block: &BCBlock, - block_info: &BCBlockInfo, -) -> Result<()> { +fn map_block_info(block_info: &BCBlockInfo) -> Result { + let master_ref = match block_info.load_master_ref()? { + Some(rf) => Some(ExtBlkRef { + end_lt: rf.end_lt, + seq_no: rf.seqno, + root_hash: rf.root_hash.0.into(), + }), + None => None, + }; + let prev_ref = block_info.load_prev_ref()?; + let prev_ref = match prev_ref { + PrevBlockRef::Single(s) => BlkPrevInfo::Block { + prev: ExtBlkRef { + end_lt: s.end_lt, + seq_no: s.seqno, + root_hash: s.root_hash.0.into(), + }, + }, + PrevBlockRef::AfterMerge { left, right } => BlkPrevInfo::Blocks { + prev1: ExtBlkRef { + end_lt: left.end_lt, + seq_no: left.seqno, + root_hash: left.root_hash.0.into(), + }, + prev2: ExtBlkRef { + end_lt: right.end_lt, + seq_no: right.seqno, + root_hash: right.root_hash.0.into(), + }, + }, + }; + let block_info = BlockInfo { version: block_info.version, after_merge: block_info.after_merge, @@ -67,12 +104,54 @@ fn fill_block_info( capabilities: block_info.gen_software.capabilities.into_inner(), }), - master_ref: Some(ExtBlkRef { - end_lt: block_info.master_ref, - seq_no: 0, - root_hash: HashInsert {}, - }), - prev_ref: (), - prev_vert_ref: None, + master_ref, + prev_ref, + prev_vert_ref: None, // doesn't exist in real world + }; + + Ok(to_json_value(&block_info)) +} + +fn map_value_flow(block: &BCBlock) -> Result { + let value_flow = block.load_value_flow()?; + let value_flow = ValueFlow { + from_prev_blk: value_flow.from_prev_block.tokens.into_inner() as u64, + to_next_blk: value_flow.to_next_block.tokens.into_inner() as u64, + imported: value_flow.imported.tokens.into_inner() as u64, + exported: value_flow.exported.tokens.into_inner() as u64, + fees_collected: value_flow.fees_collected.tokens.into_inner() as u64, + fees_imported: value_flow.fees_imported.tokens.into_inner() as u64, + recovered: value_flow.recovered.tokens.into_inner() as u64, + created: value_flow.created.tokens.into_inner() as u64, + minted: value_flow.minted.tokens.into_inner() as u64, }; + + Ok(to_json_value(&value_flow)) +} + +fn map_account_blocks(block: &BlockExtra, block_id: BlockId) -> Result { + let account_blocks = block.account_blocks.load()?; + + let mut res = Vec::new(); + for account in account_blocks.values() { + let (cc, block) = account?; + let transactions = block.transactions; + + let mut update = AccountUpdate { + address: block.account.0.into(), + wc: block_id.shard.workchain() as i8, + last_transaction_time: 0, + last_transaction_lt: 0, + creator: None, + state: State::NonExist, + deleted: false, + }; + for tx in transactions.values() { + let tx = tx?; + let tx = tx.1.load()?; + process_transaction(&mut update, &block_id, &tx)?; + } + } + + todo!() } diff --git a/explorer-mapper/src/lib.rs b/explorer-mapper/src/lib.rs index ea81df5e8..c3f3e99f3 100644 --- a/explorer-mapper/src/lib.rs +++ b/explorer-mapper/src/lib.rs @@ -1,9 +1,11 @@ -use everscale_types::models::Block; +use everscale_types::models::{Block, BlockId}; use explorer_models::ProcessingContext; mod block; +mod message; +mod transaction; -pub fn fill_processing_context(block: Block) -> ProcessingContext { +pub fn fill_processing_context(block: Block, block_id: BlockId) -> ProcessingContext { let mut context = ProcessingContext::default(); context.blocks.push(block); context diff --git a/explorer-mapper/src/message.rs b/explorer-mapper/src/message.rs new file mode 100644 index 000000000..da9452177 --- /dev/null +++ b/explorer-mapper/src/message.rs @@ -0,0 +1,79 @@ +use anyhow::{Context, Result}; +use everscale_types::cell::CellBuilder; +use everscale_types::models::{IntAddr, Message, MsgInfo}; +use explorer_models::schema::sql_types::MessageType; +use explorer_models::{Hash, MessageInfo, ProcessingContext}; + +#[allow(clippy::too_many_arguments)] +pub fn process_message( + ctx: &mut ProcessingContext, + block_hash: Hash, + transaction_hash: Hash, + is_out: bool, + index_in_transaction: u16, + message: &Message<'_>, + account: Hash, + lt: u64, + wc: i8, + transaction_time: u32, +) -> Result<()> { + let msg_cell = CellBuilder::build_from(message)?; + + let (src, dst, msg_ty) = match message.info { + MsgInfo::Int(i) => (map_addr(&i.src)?, map_addr(&i.dst)?, MessageType::Internal), + MsgInfo::ExtIn(m) => ((None, 0), map_addr(&m.dst)?, MessageType::ExternalIn), + MsgInfo::ExtOut(m) => (map_addr(&m.src)?, (None, 0), MessageType::ExternalOut), + }; + + let info = MessageInfo::from(msg.header()); + + ctx.messages.push(Message { + message_hash, + src_workchain, + src_address, + dst_workchain, + dst_address, + + message_type, + + message_value: info.value, + ihr_fee: info.ihr_fee, + fwd_fee: info.fwd_fee, + import_fee: info.import_fee, + + created_lt: info.created_lt, + created_at: info.created_at, + + bounced: info.bounced, + bounce: info.bounce, + }); + + ctx.transaction_messages.push(TransactionMessage { + transaction_hash, + index_in_transaction, + is_out, + transaction_lt: lt, + transaction_account_workchain: wc, + transaction_account_address: account, + block_hash, + dst_workchain, + dst_address, + src_workchain, + src_address, + message_hash, + message_type, + transaction_time, + message_value: info.value, + bounced: info.bounced, + bounce: info.bounce, + }); + + Ok(()) +} + +fn map_addr(addr: &IntAddr) -> Result<(Option, i8)> { + match addr { + IntAddr::Std(add) => Ok((Some(add.address.0.into()), add.workchain)), + _ => anyhow::bail!("Unsupported address type"), + } +} diff --git a/explorer-mapper/src/transaction.rs b/explorer-mapper/src/transaction.rs new file mode 100644 index 000000000..190e78e0f --- /dev/null +++ b/explorer-mapper/src/transaction.rs @@ -0,0 +1,196 @@ +use anyhow::Context; +use everscale_types::boc::{Boc, BocRepr}; +use everscale_types::cell::CellBuilder; +use everscale_types::models::{ + BlockId, ComputePhase, Message, MsgInfo, OrdinaryTxInfo, Transaction, TxInfo, +}; +use explorer_models::schema::sql_types::TransactionType; +use explorer_models::{ + AccountUpdate, ProcessingContext, RawTransaction, Transaction as DbTransaction, +}; + +use crate::message::process_message; + +pub fn process_transaction( + ctx: &mut ProcessingContext, + account_update: &mut AccountUpdate, + block_id: &BlockId, + tx: &Transaction, +) -> Result<()> { + let tx_cell = CellBuilder::build_from(tx)?; + let hash = *tx_cell.repr_hash(); + + let description = tx.load_info()?; + + let mut balance_change = 0; + + let in_message = tx.load_in_msg()?; + if let Some(in_msg) = in_message { + if let MsgInfo::Int(info) = in_msg.info { + balance_change += info.value.tokens.into_inner() as i128; + } + } + + for out_msg in tx.out_msgs.values() { + let out_msg = out_msg?; + let msg: Message<'_> = out_msg.parse()?; + if let MsgInfo::Int(info) = msg.info { + balance_change -= info.value.tokens.into_inner() as i128; + } + } + + let (tx_type, aborted, exit_code, result_code) = match &description { + TxInfo::Ordinary(tx) => { + let exit_code = match &tx.compute_phase { + ComputePhase::Skipped(_) => None, + ComputePhase::Executed(res) => Some(res.exit_code), + }; + let result_code = tx.action_phase.as_ref().map(|phase| phase.result_code); + ( + TransactionType::Ordinary, + tx.aborted, + exit_code, + result_code, + ) + } + TxInfo::TickTock(tt) => { + let exit_code = match &tt.compute_phase { + ComputePhase::Skipped(_) => None, + ComputePhase::Executed(res) => Some(res.exit_code), + }; + let result_code = tt.action_phase.as_ref().map(|phase| phase.result_code); + ( + TransactionType::TickTock, + tt.aborted, + exit_code, + result_code, + ) + } + }; + + let raw_transaction = RawTransaction { + wc: block_id.shard.workchain() as i8, + account_id: tx.account.0.into(), + lt: tx.lt, + data: Boc::encode(&tx_cell), + }; + + ctx.raw_transactions.push(raw_transaction); + ctx.transactions.push(DbTransaction { + workchain: block_id.shard.workchain() as i8, + account_id: tx.account.0.into(), + lt: tx.lt, + time: tx.now, + hash: hash.0.into(), + block_shard: block_id.shard.prefix(), + block_seqno: block_id.seqno, + block_hash: block_id.root_hash.0.into(), + tx_type, + + aborted, + balance_change: balance_change.try_into().unwrap_or({ + if balance_change < 0 { + i64::MIN + } else { + i64::MAX + } + }), + exit_code, + result_code, + }); + + let account = tx.account.0.into(); + let wc = block_id.shard.workchain() as i8; + + if let Some(ref in_msg) = in_message { + process_message( + ctx, + block_id.root_hash.0.into(), + hash.0.into(), + false, + 0, + in_msg, + account, + tx.lt, + wc, + tx.now, + ) + .context("Failed to process transaction in_msg")?; + } + + let mut i = 0; + tx.out_msgs.iterate_slices(|item| { + if let Some(item) = item.reference_opt(0) { + process_message( + ctx, + block_id.root_hash.as_slice().into(), + hash.as_slice().into(), + true, + i, + item, + account, + tx.lt, + wc, + tx.now, + ) + .context("Failed to process transaction out_msg")?; + i += 1; + } + Ok(true) + })?; + + account_update.last_transaction_time = tx.now; + account_update.last_transaction_lt = tx.lt; + account_update.state = match (&tx.orig_status, &tx.end_status) { + ( + ton_block::AccountStatus::AccStateNonexist, + ton_block::AccountStatus::AccStateNonexist, + ) if account_update.state != State::Deleted => State::NonExist, + (_, ton_block::AccountStatus::AccStateNonexist) => State::Deleted, + (_, ton_block::AccountStatus::AccStateUninit) => State::Uninit, + (_, ton_block::AccountStatus::AccStateActive) => State::Active, + (_, ton_block::AccountStatus::AccStateFrozen) => State::Frozen, + }; + + if tx.orig_status == AccountStatus::AccStateNonexist + && tx.end_status != AccountStatus::AccStateNonexist + && account_update.creator.is_none() + { + let creator = tx + .in_msg + .as_ref() + .map(|msg| msg.read_struct().context("Failed to read in_msg creator")) + .transpose()? + .and_then(|msg| msg.src()); + + if let Some(address) = creator { + account_update.creator = Some(CreatorInfo { + created_at: tx.now, + creator_address: address.address().get_bytestring(0).try_into()?, + creator_wc: address.workchain_id() as i8, + }); + } + } + + Ok(()) +} + +pub fn compute_total_transaction_fees( + transaction: &Transaction, + description: &OrdinaryTxInfo, +) -> u128 { + let mut total_fees = transaction.total_fees.tokens.into_inner(); + if let Some(phase) = &description.action_phase { + total_fees += phase + .total_fwd_fees + .as_ref() + .map(|tokens| tokens.into_inner()) + .unwrap_or_default(); + total_fees -= phase + .total_action_fees + .as_ref() + .map(|tokens| tokens.into_inner()) + .unwrap_or_default(); + }; + total_fees +} diff --git a/explorer-models/src/block.rs b/explorer-models/src/block.rs index 6e129b560..0945d4261 100644 --- a/explorer-models/src/block.rs +++ b/explorer-models/src/block.rs @@ -105,9 +105,19 @@ impl NumBinds for KeyBlockConfig { } #[cfg(not(feature = "csv"))] -type JsonValue = serde_json::Value; +pub type JsonValue = serde_json::Value; #[cfg(feature = "csv")] -type JsonValue = String; +pub type JsonValue = String; + +#[cfg(feature = "csv")] +pub fn to_json_value(value: &T) -> JsonValue { + serde_json::to_string(value).unwrap() +} + +#[cfg(not(feature = "csv"))] +pub fn to_json_value(value: &T) -> JsonValue { + serde_json::to_value(value).unwrap() +} #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")]