mirror of
https://github.com/logos-blockchain/logos-execution-zone.git
synced 2026-03-23 18:53:13 +00:00
Merge branch 'main' into Pravdyvy/state-transition-token-transfer
This commit is contained in:
commit
3de86d975e
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -4378,6 +4378,7 @@ dependencies = [
|
|||||||
"serde_json",
|
"serde_json",
|
||||||
"storage",
|
"storage",
|
||||||
"tiny-keccak",
|
"tiny-keccak",
|
||||||
|
"tempfile",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|||||||
@ -42,6 +42,11 @@ pub struct GetAccountBalanceRequest {
|
|||||||
pub address: String,
|
pub address: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
|
pub struct GetTransactionByHashRequest {
|
||||||
|
pub hash: String,
|
||||||
|
}
|
||||||
|
|
||||||
parse_request!(HelloRequest);
|
parse_request!(HelloRequest);
|
||||||
parse_request!(RegisterAccountRequest);
|
parse_request!(RegisterAccountRequest);
|
||||||
parse_request!(SendTxRequest);
|
parse_request!(SendTxRequest);
|
||||||
@ -50,6 +55,7 @@ parse_request!(GetGenesisIdRequest);
|
|||||||
parse_request!(GetLastBlockRequest);
|
parse_request!(GetLastBlockRequest);
|
||||||
parse_request!(GetInitialTestnetAccountsRequest);
|
parse_request!(GetInitialTestnetAccountsRequest);
|
||||||
parse_request!(GetAccountBalanceRequest);
|
parse_request!(GetAccountBalanceRequest);
|
||||||
|
parse_request!(GetTransactionByHashRequest);
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
pub struct HelloResponse {
|
pub struct HelloResponse {
|
||||||
@ -85,3 +91,8 @@ pub struct GetLastBlockResponse {
|
|||||||
pub struct GetAccountBalanceResponse {
|
pub struct GetAccountBalanceResponse {
|
||||||
pub balance: u64,
|
pub balance: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
|
pub struct GetTransactionByHashResponse {
|
||||||
|
pub transaction: Option<Transaction>,
|
||||||
|
}
|
||||||
|
|||||||
@ -14,6 +14,7 @@ rand.workspace = true
|
|||||||
elliptic-curve.workspace = true
|
elliptic-curve.workspace = true
|
||||||
k256.workspace = true
|
k256.workspace = true
|
||||||
tiny-keccak.workspace = true
|
tiny-keccak.workspace = true
|
||||||
|
tempfile.workspace = true
|
||||||
|
|
||||||
[dependencies.storage]
|
[dependencies.storage]
|
||||||
path = "../storage"
|
path = "../storage"
|
||||||
|
|||||||
@ -1,11 +1,13 @@
|
|||||||
use std::path::Path;
|
use std::{collections::HashMap, path::Path};
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use common::block::Block;
|
use common::{block::Block, merkle_tree_public::TreeHashType, transaction::Transaction};
|
||||||
use storage::RocksDBIO;
|
use storage::RocksDBIO;
|
||||||
|
|
||||||
pub struct SequecerBlockStore {
|
pub struct SequecerBlockStore {
|
||||||
dbio: RocksDBIO,
|
dbio: RocksDBIO,
|
||||||
|
// TODO: Consider adding the hashmap to the database for faster recovery.
|
||||||
|
tx_hash_to_block_map: HashMap<TreeHashType, u64>,
|
||||||
pub genesis_id: u64,
|
pub genesis_id: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -15,11 +17,21 @@ impl SequecerBlockStore {
|
|||||||
///
|
///
|
||||||
/// ATTENTION: Will overwrite genesis block.
|
/// ATTENTION: Will overwrite genesis block.
|
||||||
pub fn open_db_with_genesis(location: &Path, genesis_block: Option<Block>) -> Result<Self> {
|
pub fn open_db_with_genesis(location: &Path, genesis_block: Option<Block>) -> Result<Self> {
|
||||||
|
let tx_hash_to_block_map = if let Some(block) = &genesis_block {
|
||||||
|
block_to_transactions_map(block)
|
||||||
|
} else {
|
||||||
|
HashMap::new()
|
||||||
|
};
|
||||||
|
|
||||||
let dbio = RocksDBIO::new(location, genesis_block)?;
|
let dbio = RocksDBIO::new(location, genesis_block)?;
|
||||||
|
|
||||||
let genesis_id = dbio.get_meta_first_block_in_db()?;
|
let genesis_id = dbio.get_meta_first_block_in_db()?;
|
||||||
|
|
||||||
Ok(Self { dbio, genesis_id })
|
Ok(Self {
|
||||||
|
dbio,
|
||||||
|
genesis_id,
|
||||||
|
tx_hash_to_block_map,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
///Reopening existing database
|
///Reopening existing database
|
||||||
@ -31,7 +43,96 @@ impl SequecerBlockStore {
|
|||||||
Ok(self.dbio.get_block(id)?)
|
Ok(self.dbio.get_block(id)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn put_block_at_id(&self, block: Block) -> Result<()> {
|
pub fn put_block_at_id(&mut self, block: Block) -> Result<()> {
|
||||||
Ok(self.dbio.put_block(block, false)?)
|
let new_transactions_map = block_to_transactions_map(&block);
|
||||||
|
self.dbio.put_block(block, false)?;
|
||||||
|
self.tx_hash_to_block_map.extend(new_transactions_map);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the transaction corresponding to the given hash, if it exists in the blockchain.
|
||||||
|
pub fn get_transaction_by_hash(&self, hash: TreeHashType) -> Option<Transaction> {
|
||||||
|
let block_id = self.tx_hash_to_block_map.get(&hash);
|
||||||
|
let block = block_id.map(|&id| self.get_block_at_id(id));
|
||||||
|
if let Some(Ok(block)) = block {
|
||||||
|
for transaction in block.transactions.into_iter() {
|
||||||
|
if transaction.body().hash() == hash {
|
||||||
|
return Some(transaction);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn block_to_transactions_map(block: &Block) -> HashMap<TreeHashType, u64> {
|
||||||
|
block
|
||||||
|
.transactions
|
||||||
|
.iter()
|
||||||
|
.map(|transaction| (transaction.body().hash(), block.block_id))
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use common::transaction::{SignaturePrivateKey, TransactionBody};
|
||||||
|
use tempfile::tempdir;
|
||||||
|
|
||||||
|
fn create_dummy_block_with_transaction(block_id: u64) -> (Block, Transaction) {
|
||||||
|
let body = TransactionBody {
|
||||||
|
tx_kind: common::transaction::TxKind::Public,
|
||||||
|
execution_input: Default::default(),
|
||||||
|
execution_output: Default::default(),
|
||||||
|
utxo_commitments_spent_hashes: Default::default(),
|
||||||
|
utxo_commitments_created_hashes: Default::default(),
|
||||||
|
nullifier_created_hashes: Default::default(),
|
||||||
|
execution_proof_private: Default::default(),
|
||||||
|
encoded_data: Default::default(),
|
||||||
|
ephemeral_pub_key: Default::default(),
|
||||||
|
commitment: Default::default(),
|
||||||
|
tweak: Default::default(),
|
||||||
|
secret_r: Default::default(),
|
||||||
|
sc_addr: Default::default(),
|
||||||
|
state_changes: Default::default(),
|
||||||
|
};
|
||||||
|
let tx = Transaction::new(body, SignaturePrivateKey::from_slice(&[1; 32]).unwrap());
|
||||||
|
(
|
||||||
|
Block {
|
||||||
|
block_id,
|
||||||
|
prev_block_id: block_id - 1,
|
||||||
|
prev_block_hash: [0; 32],
|
||||||
|
hash: [1; 32],
|
||||||
|
transactions: vec![tx.clone()],
|
||||||
|
data: vec![],
|
||||||
|
},
|
||||||
|
tx,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_get_transaction_by_hash() {
|
||||||
|
let temp_dir = tempdir().unwrap();
|
||||||
|
let path = temp_dir.path();
|
||||||
|
let genesis_block = Block {
|
||||||
|
block_id: 0,
|
||||||
|
prev_block_id: 0,
|
||||||
|
prev_block_hash: [0; 32],
|
||||||
|
hash: [1; 32],
|
||||||
|
transactions: vec![],
|
||||||
|
data: vec![],
|
||||||
|
};
|
||||||
|
// Start an empty node store
|
||||||
|
let mut node_store =
|
||||||
|
SequecerBlockStore::open_db_with_genesis(path, Some(genesis_block)).unwrap();
|
||||||
|
let (block, tx) = create_dummy_block_with_transaction(1);
|
||||||
|
// Try retrieve a tx that's not in the chain yet.
|
||||||
|
let retrieved_tx = node_store.get_transaction_by_hash(tx.body().hash());
|
||||||
|
assert_eq!(None, retrieved_tx);
|
||||||
|
// Add the block with the transaction
|
||||||
|
node_store.put_block_at_id(block).unwrap();
|
||||||
|
// Try again
|
||||||
|
let retrieved_tx = node_store.get_transaction_by_hash(tx.body().hash());
|
||||||
|
assert_eq!(Some(tx), retrieved_tx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -25,6 +25,7 @@ pub const GET_BLOCK: &str = "get_block";
|
|||||||
pub const GET_GENESIS: &str = "get_genesis";
|
pub const GET_GENESIS: &str = "get_genesis";
|
||||||
pub const GET_LAST_BLOCK: &str = "get_last_block";
|
pub const GET_LAST_BLOCK: &str = "get_last_block";
|
||||||
pub const GET_ACCOUNT_BALANCE: &str = "get_account_balance";
|
pub const GET_ACCOUNT_BALANCE: &str = "get_account_balance";
|
||||||
|
pub const GET_TRANSACTION_BY_HASH: &str = "get_transaction_by_hash";
|
||||||
|
|
||||||
pub const HELLO_FROM_SEQUENCER: &str = "HELLO_FROM_SEQUENCER";
|
pub const HELLO_FROM_SEQUENCER: &str = "HELLO_FROM_SEQUENCER";
|
||||||
|
|
||||||
@ -161,6 +162,7 @@ impl JsonHandler {
|
|||||||
let address = address_bytes
|
let address = address_bytes
|
||||||
.try_into()
|
.try_into()
|
||||||
.map_err(|_| RpcError::invalid_params("invalid length".to_string()))?;
|
.map_err(|_| RpcError::invalid_params("invalid length".to_string()))?;
|
||||||
|
|
||||||
let balance = {
|
let balance = {
|
||||||
let state = self.sequencer_state.lock().await;
|
let state = self.sequencer_state.lock().await;
|
||||||
state.store.acc_store.get_account_balance(&address)
|
state.store.acc_store.get_account_balance(&address)
|
||||||
@ -171,6 +173,24 @@ impl JsonHandler {
|
|||||||
respond(helperstruct)
|
respond(helperstruct)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the transaction corresponding to the given hash, if it exists in the blockchain.
|
||||||
|
/// The hash must be a valid hex string of the correct length.
|
||||||
|
async fn process_get_transaction_by_hash(&self, request: Request) -> Result<Value, RpcErr> {
|
||||||
|
let get_transaction_req = GetTransactionByHashRequest::parse(Some(request.params))?;
|
||||||
|
let bytes: Vec<u8> = hex::decode(get_transaction_req.hash)
|
||||||
|
.map_err(|_| RpcError::invalid_params("invalid hex".to_string()))?;
|
||||||
|
let hash: TreeHashType = bytes
|
||||||
|
.try_into()
|
||||||
|
.map_err(|_| RpcError::invalid_params("invalid length".to_string()))?;
|
||||||
|
|
||||||
|
let transaction = {
|
||||||
|
let state = self.sequencer_state.lock().await;
|
||||||
|
state.store.block_store.get_transaction_by_hash(hash)
|
||||||
|
};
|
||||||
|
let helperstruct = GetTransactionByHashResponse { transaction };
|
||||||
|
respond(helperstruct)
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn process_request_internal(&self, request: Request) -> Result<Value, RpcErr> {
|
pub async fn process_request_internal(&self, request: Request) -> Result<Value, RpcErr> {
|
||||||
match request.method.as_ref() {
|
match request.method.as_ref() {
|
||||||
HELLO => self.process_temp_hello(request).await,
|
HELLO => self.process_temp_hello(request).await,
|
||||||
@ -181,6 +201,7 @@ impl JsonHandler {
|
|||||||
GET_LAST_BLOCK => self.process_get_last_block(request).await,
|
GET_LAST_BLOCK => self.process_get_last_block(request).await,
|
||||||
GET_INITIAL_TESTNET_ACCOUNTS => self.get_initial_testnet_accounts(request).await,
|
GET_INITIAL_TESTNET_ACCOUNTS => self.get_initial_testnet_accounts(request).await,
|
||||||
GET_ACCOUNT_BALANCE => self.process_get_account_balance(request).await,
|
GET_ACCOUNT_BALANCE => self.process_get_account_balance(request).await,
|
||||||
|
GET_TRANSACTION_BY_HASH => self.process_get_transaction_by_hash(request).await,
|
||||||
_ => Err(RpcErr(RpcError::method_not_found(request.method))),
|
_ => Err(RpcErr(RpcError::method_not_found(request.method))),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -586,4 +607,115 @@ mod tests {
|
|||||||
|
|
||||||
assert_eq!(response, expected_response);
|
assert_eq!(response, expected_response);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[actix_web::test]
|
||||||
|
async fn test_get_transaction_by_hash_for_non_existent_hash() {
|
||||||
|
let json_handler = json_handler_for_tests();
|
||||||
|
let request = serde_json::json!({
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"method": "get_transaction_by_hash",
|
||||||
|
"params": { "hash": "cafe".repeat(16) },
|
||||||
|
"id": 1
|
||||||
|
});
|
||||||
|
let expected_response = serde_json::json!({
|
||||||
|
"id": 1,
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"result": {
|
||||||
|
"transaction": null
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let response = call_rpc_handler_with_json(json_handler, request).await;
|
||||||
|
|
||||||
|
assert_eq!(response, expected_response);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix_web::test]
|
||||||
|
async fn test_get_transaction_by_hash_for_invalid_hex() {
|
||||||
|
let json_handler = json_handler_for_tests();
|
||||||
|
let request = serde_json::json!({
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"method": "get_transaction_by_hash",
|
||||||
|
"params": { "hash": "not_a_valid_hex" },
|
||||||
|
"id": 1
|
||||||
|
});
|
||||||
|
let expected_response = serde_json::json!({
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"id": 1,
|
||||||
|
"error": {
|
||||||
|
"code": -32602,
|
||||||
|
"message": "Invalid params",
|
||||||
|
"data": "invalid hex"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let response = call_rpc_handler_with_json(json_handler, request).await;
|
||||||
|
|
||||||
|
assert_eq!(response, expected_response);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix_web::test]
|
||||||
|
async fn test_get_transaction_by_hash_for_invalid_length() {
|
||||||
|
let json_handler = json_handler_for_tests();
|
||||||
|
let request = serde_json::json!({
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"method": "get_transaction_by_hash",
|
||||||
|
"params": { "hash": "cafecafe" },
|
||||||
|
"id": 1
|
||||||
|
});
|
||||||
|
let expected_response = serde_json::json!({
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"id": 1,
|
||||||
|
"error": {
|
||||||
|
"code": -32602,
|
||||||
|
"message": "Invalid params",
|
||||||
|
"data": "invalid length"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let response = call_rpc_handler_with_json(json_handler, request).await;
|
||||||
|
|
||||||
|
assert_eq!(response, expected_response);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix_web::test]
|
||||||
|
async fn test_get_transaction_by_hash_for_existing_transaction() {
|
||||||
|
let json_handler = json_handler_for_tests();
|
||||||
|
let request = serde_json::json!({
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"method": "get_transaction_by_hash",
|
||||||
|
"params": { "hash": "ca8e38269c0137d27cbe7c55d240a834b46e86e236578b9a1a3a25b3dabc5709" },
|
||||||
|
"id": 1
|
||||||
|
});
|
||||||
|
let expected_response = serde_json::json!({
|
||||||
|
"id": 1,
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"result": {
|
||||||
|
"transaction": {
|
||||||
|
"body": {
|
||||||
|
"commitment": [],
|
||||||
|
"encoded_data": [],
|
||||||
|
"ephemeral_pub_key": [],
|
||||||
|
"execution_input": [],
|
||||||
|
"execution_output": [],
|
||||||
|
"execution_proof_private": "",
|
||||||
|
"nullifier_created_hashes": [],
|
||||||
|
"sc_addr": "",
|
||||||
|
"secret_r": vec![0; 32],
|
||||||
|
"state_changes": [null, 0],
|
||||||
|
"tweak": "0".repeat(64),
|
||||||
|
"tx_kind": "Public",
|
||||||
|
"utxo_commitments_created_hashes": [],
|
||||||
|
"utxo_commitments_spent_hashes": []
|
||||||
|
},
|
||||||
|
"public_key": "3056301006072A8648CE3D020106052B8104000A034200041B84C5567B126440995D3ED5AABA0565D71E1834604819FF9C17F5E9D5DD078F70BEAF8F588B541507FED6A642C5AB42DFDF8120A7F639DE5122D47A69A8E8D1",
|
||||||
|
"signature": "28CB6CA744864340A3441CB48D5700690F90130DE0760EE5C640F85F4285C5FD2BD7D0E270EC2AC82E4124999E63659AA9C33CF378F959EDF4E50F2626EA3B99"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let response = call_rpc_handler_with_json(json_handler, request).await;
|
||||||
|
|
||||||
|
assert_eq!(response, expected_response);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user