Skip to content

Commit

Permalink
Add utxo inputs to transaction endpoint and block height
Browse files Browse the repository at this point in the history
  • Loading branch information
OBorce committed Feb 2, 2024
1 parent f6eb079 commit d419c9d
Show file tree
Hide file tree
Showing 16 changed files with 149 additions and 43 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

24 changes: 20 additions & 4 deletions api-server/api-server-common/src/storage/impls/in_memory/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
pub mod transactional;

use crate::storage::storage_api::{
block_aux_data::BlockAuxData, ApiServerStorageError, Delegation, FungibleTokenData,
block_aux_data::BlockAuxData, ApiServerStorageError, BlockInfo, Delegation, FungibleTokenData,
PoolBlockStats, TransactionInfo, Utxo,
};
use common::{
Expand Down Expand Up @@ -108,13 +108,18 @@ impl ApiServerInMemoryStorage {
}))
}

fn get_block(&self, block_id: Id<Block>) -> Result<Option<Block>, ApiServerStorageError> {
fn get_block(&self, block_id: Id<Block>) -> Result<Option<BlockInfo>, ApiServerStorageError> {
let block_result = self.block_table.get(&block_id);
let block = match block_result {
Some(blk) => blk,
None => return Ok(None),
};
Ok(Some(block.clone()))
let height = self.block_aux_data_table.get(&block_id).map(|data| data.block_height());

Ok(Some(BlockInfo {
block: block.clone(),
height,
}))
}

#[allow(clippy::type_complexity)]
Expand Down Expand Up @@ -503,6 +508,10 @@ impl ApiServerInMemoryStorage {
block: &Block,
) -> Result<(), ApiServerStorageError> {
self.block_table.insert(block_id, block.clone());
self.block_aux_data_table.insert(
block_id,
BlockAuxData::new(block_id, block_height, block.timestamp()),
);
self.main_chain_blocks_table.insert(block_height, block_id);
self.best_block = (block_height, block_id.into());
Ok(())
Expand Down Expand Up @@ -545,7 +554,14 @@ impl ApiServerInMemoryStorage {
&mut self,
block_height: BlockHeight,
) -> Result<(), ApiServerStorageError> {
self.main_chain_blocks_table.retain(|k, _| k <= &block_height);
self.main_chain_blocks_table.retain(|k, id| {
if k <= &block_height {
true
} else {
self.block_aux_data_table.remove(id);
false
}
});
Ok(())
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ use common::{
use pos_accounting::PoolData;

use crate::storage::storage_api::{
block_aux_data::BlockAuxData, ApiServerStorageError, ApiServerStorageRead, Delegation,
FungibleTokenData, PoolBlockStats, TransactionInfo, Utxo,
block_aux_data::BlockAuxData, ApiServerStorageError, ApiServerStorageRead, BlockInfo,
Delegation, FungibleTokenData, PoolBlockStats, TransactionInfo, Utxo,
};

use super::ApiServerInMemoryStorageTransactionalRo;
Expand All @@ -53,7 +53,10 @@ impl<'t> ApiServerStorageRead for ApiServerInMemoryStorageTransactionalRo<'t> {
self.transaction.get_address_transactions(address)
}

async fn get_block(&self, block_id: Id<Block>) -> Result<Option<Block>, ApiServerStorageError> {
async fn get_block(
&self,
block_id: Id<Block>,
) -> Result<Option<BlockInfo>, ApiServerStorageError> {
self.transaction.get_block(block_id)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ use pos_accounting::PoolData;

use crate::storage::storage_api::{
block_aux_data::BlockAuxData, ApiServerStorageError, ApiServerStorageRead,
ApiServerStorageWrite, Delegation, FungibleTokenData, PoolBlockStats, TransactionInfo, Utxo,
ApiServerStorageWrite, BlockInfo, Delegation, FungibleTokenData, PoolBlockStats,
TransactionInfo, Utxo,
};

use super::ApiServerInMemoryStorageTransactionalRw;
Expand Down Expand Up @@ -233,7 +234,10 @@ impl<'t> ApiServerStorageRead for ApiServerInMemoryStorageTransactionalRw<'t> {
self.transaction.get_best_block()
}

async fn get_block(&self, block_id: Id<Block>) -> Result<Option<Block>, ApiServerStorageError> {
async fn get_block(
&self,
block_id: Id<Block>,
) -> Result<Option<BlockInfo>, ApiServerStorageError> {
self.transaction.get_block(block_id)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@ use tokio_postgres::NoTls;
use crate::storage::{
impls::CURRENT_STORAGE_VERSION,
storage_api::{
block_aux_data::BlockAuxData, ApiServerStorageError, Delegation, FungibleTokenData,
PoolBlockStats, TransactionInfo, Utxo,
block_aux_data::BlockAuxData, ApiServerStorageError, BlockInfo, Delegation,
FungibleTokenData, PoolBlockStats, TransactionInfo, Utxo,
},
};

Expand Down Expand Up @@ -592,22 +592,24 @@ impl<'a, 'b> QueryFromConnection<'a, 'b> {
pub async fn get_block(
&mut self,
block_id: Id<Block>,
) -> Result<Option<Block>, ApiServerStorageError> {
) -> Result<Option<BlockInfo>, ApiServerStorageError> {
let row = self
.tx
.query_opt(
"SELECT block_data FROM ml_blocks WHERE block_id = $1;",
"SELECT block_data, block_height FROM ml_blocks WHERE block_id = $1;",
&[&block_id.encode()],
)
.await
.map_err(|e| ApiServerStorageError::LowLevelStorageError(e.to_string()))?;

let data = match row {
let row = match row {
Some(d) => d,
None => return Ok(None),
};

let data: Vec<u8> = data.get(0);
let data: Vec<u8> = row.get(0);
let height: Option<i64> = row.get(1);
let height = height.map(|h| BlockHeight::new(h as u64));

let block = Block::decode_all(&mut data.as_slice()).map_err(|e| {
ApiServerStorageError::DeserializationError(format!(
Expand All @@ -616,7 +618,7 @@ impl<'a, 'b> QueryFromConnection<'a, 'b> {
))
})?;

Ok(Some(block))
Ok(Some(BlockInfo { block, height }))
}

pub async fn get_block_range_from_time_range(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ use tokio_postgres::NoTls;

use crate::storage::storage_api::{
block_aux_data::BlockAuxData, ApiServerStorage, ApiServerStorageError, ApiServerTransactionRo,
ApiServerTransactionRw, TransactionInfo, Transactional,
ApiServerTransactionRw, BlockInfo, TransactionInfo, Transactional,
};

use super::{queries::QueryFromConnection, TransactionalApiServerPostgresStorage};
Expand Down Expand Up @@ -110,7 +110,7 @@ impl<'a> ApiServerPostgresTransactionalRo<'a> {
pub async fn get_block(
&mut self,
block_id: Id<Block>,
) -> Result<Option<Block>, ApiServerStorageError> {
) -> Result<Option<BlockInfo>, ApiServerStorageError> {
let mut conn = QueryFromConnection::new(self.connection.as_ref().expect(CONN_ERR));
let res = conn.get_block(block_id).await?;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ use common::{
use crate::storage::{
impls::postgres::queries::QueryFromConnection,
storage_api::{
block_aux_data::BlockAuxData, ApiServerStorageError, ApiServerStorageRead, Delegation,
FungibleTokenData, PoolBlockStats, TransactionInfo, Utxo,
block_aux_data::BlockAuxData, ApiServerStorageError, ApiServerStorageRead, BlockInfo,
Delegation, FungibleTokenData, PoolBlockStats, TransactionInfo, Utxo,
},
};
use std::collections::BTreeMap;
Expand Down Expand Up @@ -83,7 +83,7 @@ impl<'a> ApiServerStorageRead for ApiServerPostgresTransactionalRo<'a> {
async fn get_block(
&self,
block_id: Id<common::chain::Block>,
) -> Result<Option<common::chain::Block>, ApiServerStorageError> {
) -> Result<Option<BlockInfo>, ApiServerStorageError> {
let mut conn = QueryFromConnection::new(self.connection.as_ref().expect(CONN_ERR));
let res = conn.get_block(block_id).await?;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ use crate::storage::{
impls::postgres::queries::QueryFromConnection,
storage_api::{
block_aux_data::BlockAuxData, ApiServerStorageError, ApiServerStorageRead,
ApiServerStorageWrite, Delegation, FungibleTokenData, PoolBlockStats, TransactionInfo,
Utxo,
ApiServerStorageWrite, BlockInfo, Delegation, FungibleTokenData, PoolBlockStats,
TransactionInfo, Utxo,
},
};

Expand Down Expand Up @@ -309,7 +309,10 @@ impl<'a> ApiServerStorageRead for ApiServerPostgresTransactionalRw<'a> {
Ok(res)
}

async fn get_block(&self, block_id: Id<Block>) -> Result<Option<Block>, ApiServerStorageError> {
async fn get_block(
&self,
block_id: Id<Block>,
) -> Result<Option<BlockInfo>, ApiServerStorageError> {
let mut conn = QueryFromConnection::new(self.connection.as_ref().expect(CONN_ERR));
let res = conn.get_block(block_id).await?;

Expand Down
12 changes: 11 additions & 1 deletion api-server/api-server-common/src/storage/storage_api/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -193,12 +193,19 @@ impl FungibleTokenData {
pub struct TransactionInfo {
pub tx: SignedTransaction,
pub fee: Amount,
pub input_utxos: Vec<Option<TxOutput>>,
}

pub struct PoolBlockStats {
pub block_count: u64,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct BlockInfo {
pub block: Block,
pub height: Option<BlockHeight>,
}

#[async_trait::async_trait]
pub trait ApiServerStorageRead: Sync {
async fn is_initialized(&self) -> Result<bool, ApiServerStorageError>;
Expand All @@ -218,7 +225,10 @@ pub trait ApiServerStorageRead: Sync {

async fn get_best_block(&self) -> Result<(BlockHeight, Id<GenBlock>), ApiServerStorageError>;

async fn get_block(&self, block_id: Id<Block>) -> Result<Option<Block>, ApiServerStorageError>;
async fn get_block(
&self,
block_id: Id<Block>,
) -> Result<Option<BlockInfo>, ApiServerStorageError>;

async fn get_block_aux_data(
&self,
Expand Down
1 change: 1 addition & 0 deletions api-server/scanner-lib/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ tx-verifier = { path = "../../chainstate/tx-verifier" }
constraints-value-accumulator = { path = "../../chainstate/constraints-value-accumulator" }
mempool = { path = "../../mempool" }

futures = { workspace = true, default-features = false }
async-trait.workspace = true
thiserror.workspace = true
tokio = { workspace = true, features = ["full"] }
Expand Down
18 changes: 18 additions & 0 deletions api-server/scanner-lib/src/blockchain_state/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ use common::{
},
primitives::{id::WithId, Amount, BlockHeight, CoinOrTokenId, Fee, Id, Idable},
};
use futures::{stream::FuturesUnordered, TryStreamExt};
use pos_accounting::{make_delegation_id, PoSAccountingView, PoolData};
use std::{
collections::{BTreeMap, BTreeSet},
Expand Down Expand Up @@ -400,9 +401,14 @@ async fn calculate_fees<T: ApiServerStorageWrite>(
let fee = tx_fees(chain_config, block_height, tx, db_tx, &new_outputs).await?;
total_fees = total_fees.combine(fee.clone()).expect("no overflow");

let input_tasks: FuturesUnordered<_> =
tx.inputs().iter().map(|input| fetch_utxo(input, db_tx)).collect();
let input_utxos: Vec<Option<TxOutput>> = input_tasks.try_collect().await?;

let tx_info = TransactionInfo {
tx: tx.clone(),
fee: fee.map_into_block_fees(chain_config, block_height).expect("no overflow").0,
input_utxos,
};

db_tx
Expand All @@ -414,6 +420,18 @@ async fn calculate_fees<T: ApiServerStorageWrite>(
Ok(total_fees.map_into_block_fees(chain_config, block_height).expect("no overflow"))
}

async fn fetch_utxo<T: ApiServerStorageRead>(
input: &TxInput,
db_tx: &T,
) -> Result<Option<TxOutput>, ApiServerStorageError> {
match input {
TxInput::Utxo(outpoint) => {
Ok(db_tx.get_utxo(outpoint.clone()).await?.map(|utxo| utxo.into_output()))
}
TxInput::Account(_) | TxInput::AccountCommand(_, _) => Ok(None),
}
}

async fn tx_fees<T: ApiServerStorageWrite>(
chain_config: &ChainConfig,
block_height: BlockHeight,
Expand Down
2 changes: 2 additions & 0 deletions api-server/stack-test-suite/tests/v1/block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ async fn ok(#[case] seed: Seed) {
.unwrap();

let old_expected_block = json!({
"height": block_height,
"header": block_header_to_json(&block),
"body": {
"reward": block.block_reward()
Expand Down Expand Up @@ -192,6 +193,7 @@ async fn ok(#[case] seed: Seed) {
.unwrap();

let new_expected_block = json!({
"height": block_height,
"header": block_header_to_json(&block),
"body": {
"reward": block.block_reward()
Expand Down
17 changes: 15 additions & 2 deletions api-server/stack-test-suite/tests/v1/transaction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ async fn ok(#[case] seed: Seed) {
let task = tokio::spawn(async move {
let web_server_state = {
let mut rng = make_seedable_rng(seed);
let block_height = rng.gen_range(1..50);
let block_height = rng.gen_range(2..50);
let n_blocks = rng.gen_range(block_height..100);

let chain_config = create_unit_test_config();
Expand All @@ -78,19 +78,32 @@ async fn ok(#[case] seed: Seed) {
// Need the "- 1" to account for the genesis block not in the vec
let block_id = chainstate_block_ids[block_height - 1];
let block = tf.block(tf.to_chain_block_id(&block_id));
let prev_block =
tf.block(tf.to_chain_block_id(&chainstate_block_ids[block_height - 2]));
let prev_tx = &prev_block.transactions()[0];

let transaction_index = rng.gen_range(0..block.transactions().len());
let transaction = block.transactions()[transaction_index].transaction();
let transaction_id = transaction.get_id();

let utxos = transaction.inputs().iter().map(|inp| match inp {
TxInput::Utxo(outpoint) => {
Some(prev_tx.outputs()[outpoint.output_index() as usize].clone())
}
TxInput::Account(_) | TxInput::AccountCommand(_, _) => None,
});

let expected_transaction = json!({
"block_id": block_id.to_hash().encode_hex::<String>(),
"timestamp": block.timestamp().to_string(),
"confirmations": BlockHeight::new((n_blocks - block_height) as u64).to_string(),
"version_byte": transaction.version_byte(),
"is_replaceable": transaction.is_replaceable(),
"flags": transaction.flags(),
"inputs": transaction.inputs(),
"inputs": transaction.inputs().iter().zip(utxos).map(|(inp, utxo)| json!({
"input": inp,
"utxo": utxo.as_ref().map(|txo| txoutput_to_json(txo, &chain_config)),
})).collect::<Vec<_>>(),
"outputs": transaction.outputs()
.iter()
.map(|out| txoutput_to_json(out, &chain_config))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ async fn get_block_failed(#[case] seed: Seed) {
let tx_info = TransactionInfo {
tx: signed_transaction,
fee: Amount::from_atoms(rng.gen_range(0..100)),
input_utxos: vec![],
};

db_tx.set_transaction(transaction_id, Some(block_id), &tx_info).await.unwrap();
Expand Down Expand Up @@ -237,6 +238,7 @@ async fn transaction_not_part_of_block(#[case] seed: Seed) {
let tx_info = TransactionInfo {
tx: signed_transaction,
fee: Amount::from_atoms(rng.gen_range(0..100)),
input_utxos: vec![],
};
db_tx.set_transaction(transaction_id, None, &tx_info).await.unwrap();
db_tx.commit().await.unwrap();
Expand Down
Loading

0 comments on commit d419c9d

Please sign in to comment.