Skip to content

Commit

Permalink
Add Gateway API to query transaction history (#1310)
Browse files Browse the repository at this point in the history
* Add Gateway API to query transaction range

* Fix corner case
  • Loading branch information
lxfind authored Apr 11, 2022
1 parent 28c10a6 commit e615cda
Show file tree
Hide file tree
Showing 6 changed files with 122 additions and 6 deletions.
21 changes: 19 additions & 2 deletions sui/src/rest_gateway.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ use serde::Serialize;
use serde_json::json;

use sui_core::gateway_state::gateway_responses::TransactionResponse;
use sui_core::gateway_state::GatewayAPI;
use sui_types::base_types::{encode_bytes_hex, ObjectID, ObjectRef, SuiAddress};
use sui_core::gateway_state::{GatewayAPI, GatewayTxSeqNumber};
use sui_types::base_types::{encode_bytes_hex, ObjectID, ObjectRef, SuiAddress, TransactionDigest};
use sui_types::messages::{Transaction, TransactionData};
use sui_types::object::ObjectRead;

Expand Down Expand Up @@ -213,6 +213,23 @@ impl GatewayAPI for RestGatewayClient {
// TODO: Implement this.
Ok(0)
}

fn get_transactions_in_range(
&self,
start: GatewayTxSeqNumber,
end: GatewayTxSeqNumber,
) -> Result<Vec<(GatewayTxSeqNumber, TransactionDigest)>, anyhow::Error> {
// TODO: Implement this.
Ok(vec![])
}

fn get_recent_transactions(
&self,
count: u64,
) -> Result<Vec<(GatewayTxSeqNumber, TransactionDigest)>, anyhow::Error> {
// TODO: Implement this.
Ok(vec![])
}
}

impl RestGatewayClient {
Expand Down
16 changes: 15 additions & 1 deletion sui_core/src/authority/authority_store.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright (c) 2022, Mysten Labs, Inc.
// SPDX-License-Identifier: Apache-2.0
use super::*;
use crate::gateway_state::GatewayTxSeqNumber;

use rocksdb::Options;
use serde::{Deserialize, Serialize};
Expand Down Expand Up @@ -575,7 +576,7 @@ impl<const ALL_OBJ_VER: bool, S: Eq + Serialize + for<'de> Deserialize<'de>>
mutated_objects: HashMap<ObjectRef, Object>,
certificate: CertifiedTransaction,
effects: TransactionEffects,
sequence_number: TxSequenceNumber,
sequence_number: GatewayTxSeqNumber,
) -> SuiResult {
let transaction_digest = certificate.digest();
let mut temporary_store =
Expand Down Expand Up @@ -854,6 +855,19 @@ impl<const ALL_OBJ_VER: bool, S: Eq + Serialize + for<'de> Deserialize<'de>>
write_batch.write().map_err(SuiError::from)
}

pub fn transactions_in_seq_range(
&self,
start: GatewayTxSeqNumber,
end: GatewayTxSeqNumber,
) -> SuiResult<Vec<(GatewayTxSeqNumber, TransactionDigest)>> {
Ok(self
.executed_sequence
.iter()
.skip_to(&start)?
.take_while(|(seq, _tx)| *seq < end)
.collect())
}

/// Retrieves batches including transactions within a range.
///
/// This function returns all signed batches that enclose the requested transaction
Expand Down
66 changes: 66 additions & 0 deletions sui_core/src/gateway_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@ pub type AsyncResult<'a, T, E> = future::BoxFuture<'a, Result<T, E>>;

pub type GatewayClient = Box<dyn GatewayAPI + Sync + Send>;

pub type GatewayTxSeqNumber = u64;

const MAX_TX_RANGE_SIZE: u64 = 4096;

pub struct GatewayState<A> {
authorities: AuthorityAggregator<A>,
store: Arc<GatewayStore>,
Expand Down Expand Up @@ -179,6 +183,20 @@ pub trait GatewayAPI {

/// Get the total number of transactions ever happened in history.
fn get_total_transaction_number(&self) -> Result<u64, anyhow::Error>;

/// Return the list of transactions with sequence number in range [`start`, end).
/// `start` is included, `end` is excluded.
fn get_transactions_in_range(
&self,
start: GatewayTxSeqNumber,
end: GatewayTxSeqNumber,
) -> Result<Vec<(GatewayTxSeqNumber, TransactionDigest)>, anyhow::Error>;

/// Return the most recent `count` transactions.
fn get_recent_transactions(
&self,
count: u64,
) -> Result<Vec<(GatewayTxSeqNumber, TransactionDigest)>, anyhow::Error>;
}

impl<A> GatewayState<A>
Expand Down Expand Up @@ -725,4 +743,52 @@ where
fn get_total_transaction_number(&self) -> Result<u64, anyhow::Error> {
Ok(self.store.next_sequence_number()?)
}

fn get_transactions_in_range(
&self,
start: GatewayTxSeqNumber,
end: GatewayTxSeqNumber,
) -> Result<Vec<(GatewayTxSeqNumber, TransactionDigest)>, anyhow::Error> {
fp_ensure!(
start <= end,
SuiError::GatewayInvalidTxRangeQuery {
error: format!(
"start must not exceed end, (start={}, end={}) given",
start, end
),
}
.into()
);
fp_ensure!(
end - start <= MAX_TX_RANGE_SIZE,
SuiError::GatewayInvalidTxRangeQuery {
error: format!(
"Number of transactions queried must not exceed {}, {} queried",
MAX_TX_RANGE_SIZE,
end - start
),
}
.into()
);
Ok(self.store.transactions_in_seq_range(start, end)?)
}

fn get_recent_transactions(
&self,
count: u64,
) -> Result<Vec<(GatewayTxSeqNumber, TransactionDigest)>, anyhow::Error> {
fp_ensure!(
count <= MAX_TX_RANGE_SIZE,
SuiError::GatewayInvalidTxRangeQuery {
error: format!(
"Number of transactions queried must not exceed {}, {} queried",
MAX_TX_RANGE_SIZE, count
),
}
.into()
);
let end = self.get_total_transaction_number()?;
let start = if end >= count { end - count } else { 0 };
self.get_transactions_in_range(start, end)
}
}
19 changes: 16 additions & 3 deletions sui_core/src/unit_tests/gateway_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2103,7 +2103,7 @@ async fn test_coin_merge() {
}

#[tokio::test]
async fn test_get_total_transaction_number() -> Result<(), anyhow::Error> {
async fn test_recent_transactions() -> Result<(), anyhow::Error> {
let (mut client, authority_clients) = make_address_manager(4).await;
let (addr1, key1) = get_key_pair();
let (addr2, _) = get_key_pair();
Expand All @@ -2122,20 +2122,33 @@ async fn test_get_total_transaction_number() -> Result<(), anyhow::Error> {
)
.await;

assert_eq!(client.get_total_transaction_number()?, 0);
let mut cnt = 0;
let mut digests = vec![];
for obj_id in [object_id1, object_id2, object_id3] {
assert_eq!(client.get_total_transaction_number()?, cnt);
let data = client
.transfer_coin(addr1, obj_id, gas_object, 50000, addr2)
.await
.unwrap();
let signature = key1.sign(&data.to_bytes());
client
let response = client
.execute_transaction(Transaction::new(data, signature))
.await?;
digests.push((cnt, *response.to_effect_response()?.0.digest()));
cnt += 1;
assert_eq!(client.get_total_transaction_number()?, cnt);
}
// start must <= end.
assert!(client.get_transactions_in_range(2, 1).is_err());
assert!(client.get_transactions_in_range(1, 1).unwrap().is_empty());
// Extends max range allowed.
assert!(client.get_transactions_in_range(1, 100000).is_err());
let txs = client.get_recent_transactions(10)?;
assert_eq!(txs.len(), 3);
assert_eq!(txs, digests);
let txs = client.get_transactions_in_range(0, 10)?;
assert_eq!(txs.len(), 3);
assert_eq!(txs, digests);

Ok(())
}
Expand Down
4 changes: 4 additions & 0 deletions sui_core/tests/staged/sui.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -755,6 +755,10 @@ SuiError:
STRUCT:
- error: STR
92:
GatewayInvalidTxRangeQuery:
STRUCT:
- error: STR
93:
OnlyOneConsensusClientPermitted: UNIT
TransactionData:
STRUCT:
Expand Down
2 changes: 2 additions & 0 deletions sui_types/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,8 @@ pub enum SuiError {
},
#[error("Inconsistent results observed in the Gateway. This should not happen and typically means there is a bug in the Sui implementation. Details: {error:?}")]
InconsistentGatewayResult { error: String },
#[error("Invalid transactiojn range query to the gateway: {:?}", error)]
GatewayInvalidTxRangeQuery { error: String },

// Errors related to the authority-consensus interface.
#[error("Authority state can be modified by a single consensus client at the time")]
Expand Down

1 comment on commit e615cda

@github-actions
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bench results

�[0m�[0m�[1m�[32m Finished�[0m release [optimized] target(s) in 0.35s
�[0m�[0m�[1m�[32m Running�[0m target/release/microbench
�[2m2022-04-11T00:46:14.197935Z�[0m �[32m INFO�[0m �[2mmicrobench�[0m�[2m:�[0m Starting benchmark: TransactionsAndCerts
�[2m2022-04-11T00:46:14.197996Z�[0m �[32m INFO�[0m �[2mmicrobench�[0m�[2m:�[0m Preparing accounts.
�[2m2022-04-11T00:46:14.198384Z�[0m �[32m INFO�[0m �[2mmicrobench�[0m�[2m:�[0m Init Authority.
�[2m2022-04-11T00:46:14.198464Z�[0m �[32m INFO�[0m �[2mmicrobench�[0m�[2m:�[0m Open database on path: "/tmp/DB_FBF86DE2EB9F942E697A6CC7E45C9B10635072F7"
�[2m2022-04-11T00:46:14.483671Z�[0m �[32m INFO�[0m �[2mmicrobench�[0m�[2m:�[0m Generate empty store with Genesis.
�[2m2022-04-11T00:46:15.175002Z�[0m �[32m INFO�[0m �[2mmicrobench�[0m�[2m:�[0m Preparing transactions.
�[2m2022-04-11T00:46:15.530700Z�[0m �[32m INFO�[0m �[2msui_network::transport�[0m�[2m:�[0m Listening to TCP traffic on 127.0.0.1:9555
�[2m2022-04-11T00:46:15.530824Z�[0m �[32m INFO�[0m �[2mmicrobench�[0m�[2m:�[0m Sending requests.
�[2m2022-04-11T00:46:15.530840Z�[0m �[32m INFO�[0m �[2mmicrobench�[0m�[2m:�[0m Number of TCP connections: 2
�[2m2022-04-11T00:46:15.532260Z�[0m �[32m INFO�[0m �[2mmicrobench�[0m�[2m:�[0m Start batch listener at sequence: 0.
�[2m2022-04-11T00:46:15.533279Z�[0m �[32m INFO�[0m �[2mmicrobench�[0m�[2m:�[0m Client received batch up to sequence 0
�[2m2022-04-11T00:46:17.811384Z�[0m �[32m INFO�[0m �[2mmicrobench�[0m�[2m:�[0m Received 100 responses.
�[2m2022-04-11T00:46:17.880646Z�[0m �[33m WARN�[0m �[2mmicrobench�[0m�[2m:�[0m Completed benchmark for TransactionsAndCerts
Total time: 2280531us, items: 100000, tx/sec: 43849.43681975821

Please sign in to comment.