Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add utxo inputs to transaction endpoint and block height #1528

Merged
merged 3 commits into from
Feb 7, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
2 changes: 1 addition & 1 deletion api-server/api-server-common/src/storage/impls/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.

pub const CURRENT_STORAGE_VERSION: u32 = 2;
pub const CURRENT_STORAGE_VERSION: u32 = 3;

pub mod in_memory;
pub mod postgres;
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": None::<BlockHeight>,
"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
Loading