mirror of
https://github.com/logos-blockchain/lssa.git
synced 2026-01-02 13:23:10 +00:00
722 lines
23 KiB
Rust
722 lines
23 KiB
Rust
use accounts::account_core::AccountForSerialization;
|
|
use actix_web::Error as HttpError;
|
|
use serde_json::Value;
|
|
|
|
use common::rpc_primitives::{
|
|
errors::RpcError,
|
|
message::{Message, Request},
|
|
parser::RpcRequest,
|
|
requests::GetInitialTestnetAccountsRequest,
|
|
requests::{GetAccountBalanceRequest, GetAccountBalanceResponse},
|
|
};
|
|
|
|
use common::rpc_primitives::requests::{
|
|
GetBlockDataRequest, GetBlockDataResponse, GetGenesisIdRequest, GetGenesisIdResponse,
|
|
GetLastBlockRequest, GetLastBlockResponse, HelloRequest, HelloResponse, RegisterAccountRequest,
|
|
RegisterAccountResponse, SendTxRequest, SendTxResponse,
|
|
};
|
|
|
|
use super::{respond, types::err_rpc::RpcErr, JsonHandler};
|
|
|
|
pub const HELLO: &str = "hello";
|
|
pub const REGISTER_ACCOUNT: &str = "register_account";
|
|
pub const SEND_TX: &str = "send_tx";
|
|
pub const GET_BLOCK: &str = "get_block";
|
|
pub const GET_GENESIS: &str = "get_genesis";
|
|
pub const GET_LAST_BLOCK: &str = "get_last_block";
|
|
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 SUCCESS: &str = "Success";
|
|
|
|
pub const GET_INITIAL_TESTNET_ACCOUNTS: &str = "get_initial_testnet_accounts";
|
|
|
|
impl JsonHandler {
|
|
pub async fn process(&self, message: Message) -> Result<Message, HttpError> {
|
|
let id = message.id();
|
|
if let Message::Request(request) = message {
|
|
let message_inner = self
|
|
.process_request_internal(request)
|
|
.await
|
|
.map_err(|e| e.0);
|
|
Ok(Message::response(id, message_inner))
|
|
} else {
|
|
Ok(Message::error(RpcError::parse_error(
|
|
"JSON RPC Request format was expected".to_owned(),
|
|
)))
|
|
}
|
|
}
|
|
|
|
#[allow(clippy::unused_async)]
|
|
///Example of request processing
|
|
async fn process_temp_hello(&self, request: Request) -> Result<Value, RpcErr> {
|
|
let _hello_request = HelloRequest::parse(Some(request.params))?;
|
|
|
|
let helperstruct = HelloResponse {
|
|
greeting: HELLO_FROM_SEQUENCER.to_string(),
|
|
};
|
|
|
|
respond(helperstruct)
|
|
}
|
|
|
|
async fn process_register_account_request(&self, request: Request) -> Result<Value, RpcErr> {
|
|
let acc_req = RegisterAccountRequest::parse(Some(request.params))?;
|
|
|
|
{
|
|
let mut acc_store = self.sequencer_state.lock().await;
|
|
|
|
acc_store.register_account(acc_req.address);
|
|
}
|
|
|
|
let helperstruct = RegisterAccountResponse {
|
|
status: SUCCESS.to_string(),
|
|
};
|
|
|
|
respond(helperstruct)
|
|
}
|
|
|
|
async fn process_send_tx(&self, request: Request) -> Result<Value, RpcErr> {
|
|
let send_tx_req = SendTxRequest::parse(Some(request.params))?;
|
|
|
|
{
|
|
let mut state = self.sequencer_state.lock().await;
|
|
|
|
state.push_tx_into_mempool_pre_check(send_tx_req.transaction, send_tx_req.tx_roots)?;
|
|
}
|
|
|
|
let helperstruct = SendTxResponse {
|
|
status: SUCCESS.to_string(),
|
|
};
|
|
|
|
respond(helperstruct)
|
|
}
|
|
|
|
async fn process_get_block_data(&self, request: Request) -> Result<Value, RpcErr> {
|
|
let get_block_req = GetBlockDataRequest::parse(Some(request.params))?;
|
|
|
|
let block = {
|
|
let state = self.sequencer_state.lock().await;
|
|
|
|
state
|
|
.store
|
|
.block_store
|
|
.get_block_at_id(get_block_req.block_id)?
|
|
};
|
|
|
|
let helperstruct = GetBlockDataResponse { block };
|
|
|
|
respond(helperstruct)
|
|
}
|
|
|
|
async fn process_get_genesis(&self, request: Request) -> Result<Value, RpcErr> {
|
|
let _get_genesis_req = GetGenesisIdRequest::parse(Some(request.params))?;
|
|
|
|
let genesis_id = {
|
|
let state = self.sequencer_state.lock().await;
|
|
|
|
state.store.block_store.genesis_id
|
|
};
|
|
|
|
let helperstruct = GetGenesisIdResponse { genesis_id };
|
|
|
|
respond(helperstruct)
|
|
}
|
|
|
|
async fn process_get_last_block(&self, request: Request) -> Result<Value, RpcErr> {
|
|
let _get_last_block_req = GetLastBlockRequest::parse(Some(request.params))?;
|
|
|
|
let last_block = {
|
|
let state = self.sequencer_state.lock().await;
|
|
|
|
state.chain_height
|
|
};
|
|
|
|
let helperstruct = GetLastBlockResponse { last_block };
|
|
|
|
respond(helperstruct)
|
|
}
|
|
|
|
/// Returns the initial accounts for testnet
|
|
/// ToDo: Useful only for testnet and needs to be removed later
|
|
async fn get_initial_testnet_accounts(&self, request: Request) -> Result<Value, RpcErr> {
|
|
let _get_initial_testnet_accounts_request =
|
|
GetInitialTestnetAccountsRequest::parse(Some(request.params))?;
|
|
|
|
let accounts_for_serialization: Vec<AccountForSerialization> = {
|
|
let state = self.sequencer_state.lock().await;
|
|
|
|
state.sequencer_config.initial_accounts.clone()
|
|
};
|
|
|
|
respond(accounts_for_serialization)
|
|
}
|
|
|
|
/// Returns the balance of the account at the given address.
|
|
/// The address must be a valid hex string of the correct length.
|
|
async fn process_get_account_balance(&self, request: Request) -> Result<Value, RpcErr> {
|
|
let get_account_req = GetAccountBalanceRequest::parse(Some(request.params))?;
|
|
let address_bytes = hex::decode(get_account_req.address)
|
|
.map_err(|_| RpcError::invalid_params("invalid hex".to_string()))?;
|
|
let address = address_bytes
|
|
.try_into()
|
|
.map_err(|_| RpcError::invalid_params("invalid length".to_string()))?;
|
|
|
|
let balance = {
|
|
let state = self.sequencer_state.lock().await;
|
|
state.store.acc_store.get_account_balance(&address)
|
|
};
|
|
|
|
let helperstruct = GetAccountBalanceResponse { balance };
|
|
|
|
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> {
|
|
match request.method.as_ref() {
|
|
HELLO => self.process_temp_hello(request).await,
|
|
REGISTER_ACCOUNT => self.process_register_account_request(request).await,
|
|
SEND_TX => self.process_send_tx(request).await,
|
|
GET_BLOCK => self.process_get_block_data(request).await,
|
|
GET_GENESIS => self.process_get_genesis(request).await,
|
|
GET_LAST_BLOCK => self.process_get_last_block(request).await,
|
|
GET_INITIAL_TESTNET_ACCOUNTS => self.get_initial_testnet_accounts(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))),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use std::sync::Arc;
|
|
|
|
use crate::{rpc_handler, JsonHandler};
|
|
use accounts::account_core::Account;
|
|
use common::rpc_primitives::RpcPollingConfig;
|
|
use sequencer_core::{config::SequencerConfig, SequencerCore};
|
|
use serde_json::Value;
|
|
use tempfile::tempdir;
|
|
use tokio::sync::Mutex;
|
|
|
|
fn sequencer_config_for_tests() -> SequencerConfig {
|
|
let tempdir = tempdir().unwrap();
|
|
let home = tempdir.path().to_path_buf();
|
|
let initial_acc1 = serde_json::from_str(r#"{
|
|
"address": [
|
|
244,
|
|
55,
|
|
238,
|
|
205,
|
|
74,
|
|
115,
|
|
179,
|
|
192,
|
|
65,
|
|
186,
|
|
166,
|
|
169,
|
|
221,
|
|
45,
|
|
6,
|
|
57,
|
|
200,
|
|
65,
|
|
195,
|
|
70,
|
|
118,
|
|
252,
|
|
206,
|
|
100,
|
|
215,
|
|
250,
|
|
72,
|
|
230,
|
|
19,
|
|
71,
|
|
217,
|
|
249
|
|
],
|
|
"balance": 100,
|
|
"key_holder": {
|
|
"address": [
|
|
244,
|
|
55,
|
|
238,
|
|
205,
|
|
74,
|
|
115,
|
|
179,
|
|
192,
|
|
65,
|
|
186,
|
|
166,
|
|
169,
|
|
221,
|
|
45,
|
|
6,
|
|
57,
|
|
200,
|
|
65,
|
|
195,
|
|
70,
|
|
118,
|
|
252,
|
|
206,
|
|
100,
|
|
215,
|
|
250,
|
|
72,
|
|
230,
|
|
19,
|
|
71,
|
|
217,
|
|
249
|
|
],
|
|
"nullifer_public_key": "03A340BECA9FAAB444CED0140681D72EA1318B5C611704FEE017DA9836B17DB718",
|
|
"pub_account_signing_key": [
|
|
244,
|
|
88,
|
|
134,
|
|
61,
|
|
35,
|
|
209,
|
|
229,
|
|
101,
|
|
85,
|
|
35,
|
|
140,
|
|
140,
|
|
192,
|
|
226,
|
|
83,
|
|
83,
|
|
190,
|
|
189,
|
|
110,
|
|
8,
|
|
89,
|
|
127,
|
|
147,
|
|
142,
|
|
157,
|
|
204,
|
|
51,
|
|
109,
|
|
189,
|
|
92,
|
|
144,
|
|
68
|
|
],
|
|
"top_secret_key_holder": {
|
|
"secret_spending_key": "7BC46784DB1BC67825D8F029436846712BFDF9B5D79EA3AB11D39A52B9B229D4"
|
|
},
|
|
"utxo_secret_key_holder": {
|
|
"nullifier_secret_key": "BB54A8D3C9C51B82C431082D1845A74677B0EF829A11B517E1D9885DE3139506",
|
|
"viewing_secret_key": "AD923E92F6A5683E30140CEAB2702AFB665330C1EE4EFA70FAF29767B6B52BAF"
|
|
},
|
|
"viewing_public_key": "0361220C5D277E7A1709340FD31A52600C1432B9C45B9BCF88A43581D58824A8B6"
|
|
},
|
|
"utxos": {}
|
|
}"#).unwrap();
|
|
|
|
let initial_acc2 = serde_json::from_str(r#"{
|
|
"address": [
|
|
72,
|
|
169,
|
|
70,
|
|
237,
|
|
1,
|
|
96,
|
|
35,
|
|
157,
|
|
25,
|
|
15,
|
|
83,
|
|
18,
|
|
52,
|
|
206,
|
|
202,
|
|
63,
|
|
48,
|
|
59,
|
|
173,
|
|
76,
|
|
78,
|
|
7,
|
|
254,
|
|
229,
|
|
28,
|
|
45,
|
|
194,
|
|
79,
|
|
6,
|
|
89,
|
|
58,
|
|
85
|
|
],
|
|
"balance": 200,
|
|
"key_holder": {
|
|
"address": [
|
|
72,
|
|
169,
|
|
70,
|
|
237,
|
|
1,
|
|
96,
|
|
35,
|
|
157,
|
|
25,
|
|
15,
|
|
83,
|
|
18,
|
|
52,
|
|
206,
|
|
202,
|
|
63,
|
|
48,
|
|
59,
|
|
173,
|
|
76,
|
|
78,
|
|
7,
|
|
254,
|
|
229,
|
|
28,
|
|
45,
|
|
194,
|
|
79,
|
|
6,
|
|
89,
|
|
58,
|
|
85
|
|
],
|
|
"nullifer_public_key": "02172F50274DE67C4087C344F5D58E11DF761D90285B095060E0994FAA6BCDE271",
|
|
"pub_account_signing_key": [
|
|
136,
|
|
105,
|
|
9,
|
|
53,
|
|
180,
|
|
145,
|
|
64,
|
|
5,
|
|
235,
|
|
174,
|
|
62,
|
|
211,
|
|
206,
|
|
116,
|
|
185,
|
|
24,
|
|
214,
|
|
62,
|
|
244,
|
|
64,
|
|
224,
|
|
59,
|
|
120,
|
|
150,
|
|
30,
|
|
249,
|
|
160,
|
|
46,
|
|
189,
|
|
254,
|
|
47,
|
|
244
|
|
],
|
|
"top_secret_key_holder": {
|
|
"secret_spending_key": "80A186737C8D38B4288A03F0F589957D9C040D79C19F3E0CC4BA80F8494E5179"
|
|
},
|
|
"utxo_secret_key_holder": {
|
|
"nullifier_secret_key": "746928E63F0984F6F4818933493CE9C067562D9CB932FDC06D82C86CDF6D7122",
|
|
"viewing_secret_key": "89176CF4BC9E673807643FD52110EF99D4894335AFB10D881AC0B5041FE1FCB7"
|
|
},
|
|
"viewing_public_key": "026072A8F83FEC3472E30CDD4767683F30B91661D25B1040AD9A5FC2E01D659F99"
|
|
},
|
|
"utxos": {}
|
|
}"#).unwrap();
|
|
|
|
let initial_accounts = vec![initial_acc1, initial_acc2];
|
|
|
|
SequencerConfig {
|
|
home,
|
|
override_rust_log: Some("info".to_string()),
|
|
genesis_id: 1,
|
|
is_genesis_random: false,
|
|
max_num_tx_in_block: 10,
|
|
block_create_timeout_millis: 1000,
|
|
port: 8080,
|
|
initial_accounts,
|
|
}
|
|
}
|
|
|
|
fn json_handler_for_tests() -> (JsonHandler, Vec<Account>) {
|
|
let config = sequencer_config_for_tests();
|
|
|
|
let sequencer_core = SequencerCore::start_from_config(config);
|
|
let initial_accounts = sequencer_core
|
|
.sequencer_config
|
|
.initial_accounts
|
|
.iter()
|
|
.map(|acc_ser| acc_ser.clone().into())
|
|
.collect();
|
|
|
|
let sequencer_core = Arc::new(Mutex::new(sequencer_core));
|
|
|
|
(
|
|
JsonHandler {
|
|
polling_config: RpcPollingConfig::default(),
|
|
sequencer_state: sequencer_core,
|
|
},
|
|
initial_accounts,
|
|
)
|
|
}
|
|
|
|
async fn call_rpc_handler_with_json(handler: JsonHandler, request_json: Value) -> Value {
|
|
use actix_web::{test, web, App};
|
|
|
|
let app = test::init_service(
|
|
App::new()
|
|
.app_data(web::Data::new(handler))
|
|
.route("/", web::post().to(rpc_handler)),
|
|
)
|
|
.await;
|
|
|
|
let req = test::TestRequest::post()
|
|
.uri("/")
|
|
.set_json(request_json)
|
|
.to_request();
|
|
|
|
let resp = test::call_service(&app, req).await;
|
|
let body = test::read_body(resp).await;
|
|
|
|
serde_json::from_slice(&body).unwrap()
|
|
}
|
|
|
|
#[actix_web::test]
|
|
async fn test_get_account_balance_for_non_existent_account() {
|
|
let (json_handler, _) = json_handler_for_tests();
|
|
let request = serde_json::json!({
|
|
"jsonrpc": "2.0",
|
|
"method": "get_account_balance",
|
|
"params": { "address": "efac".repeat(16) },
|
|
"id": 1
|
|
});
|
|
let expected_response = serde_json::json!({
|
|
"id": 1,
|
|
"jsonrpc": "2.0",
|
|
"result": {
|
|
"balance": 0
|
|
}
|
|
});
|
|
|
|
let response = call_rpc_handler_with_json(json_handler, request).await;
|
|
|
|
assert_eq!(response, expected_response);
|
|
}
|
|
|
|
#[actix_web::test]
|
|
async fn test_get_account_balance_for_invalid_hex() {
|
|
let (json_handler, _) = json_handler_for_tests();
|
|
let request = serde_json::json!({
|
|
"jsonrpc": "2.0",
|
|
"method": "get_account_balance",
|
|
"params": { "address": "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_account_balance_for_invalid_length() {
|
|
let (json_handler, _) = json_handler_for_tests();
|
|
let request = serde_json::json!({
|
|
"jsonrpc": "2.0",
|
|
"method": "get_account_balance",
|
|
"params": { "address": "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_account_balance_for_existing_account() {
|
|
let (json_handler, initial_accounts) = json_handler_for_tests();
|
|
|
|
let acc1_addr = hex::encode(initial_accounts[0].address);
|
|
|
|
let request = serde_json::json!({
|
|
"jsonrpc": "2.0",
|
|
"method": "get_account_balance",
|
|
"params": { "address": acc1_addr },
|
|
"id": 1
|
|
});
|
|
let expected_response = serde_json::json!({
|
|
"id": 1,
|
|
"jsonrpc": "2.0",
|
|
"result": {
|
|
"balance": 100
|
|
}
|
|
});
|
|
|
|
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_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);
|
|
}
|
|
}
|