fix: complete cli intefaces

This commit is contained in:
Oleksandr Pravdyvyi 2025-08-21 15:58:31 +03:00
parent 0c7456d7a0
commit 914cbfb9dc
No known key found for this signature in database
GPG Key ID: 9F8955C63C443871
10 changed files with 223 additions and 30 deletions

View File

@ -43,6 +43,11 @@ pub struct GetTransactionByHashRequest {
pub hash: String,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct GetAccountsNoncesRequest {
pub addresses: Vec<String>,
}
parse_request!(HelloRequest);
parse_request!(RegisterAccountRequest);
parse_request!(SendTxRequest);
@ -52,6 +57,7 @@ parse_request!(GetLastBlockRequest);
parse_request!(GetInitialTestnetAccountsRequest);
parse_request!(GetAccountBalanceRequest);
parse_request!(GetTransactionByHashRequest);
parse_request!(GetAccountsNoncesRequest);
#[derive(Serialize, Deserialize, Debug)]
pub struct HelloResponse {
@ -66,6 +72,7 @@ pub struct RegisterAccountResponse {
#[derive(Serialize, Deserialize, Debug)]
pub struct SendTxResponse {
pub status: String,
pub tx_hash: String,
}
#[derive(Serialize, Deserialize, Debug)]
@ -88,6 +95,11 @@ pub struct GetAccountBalanceResponse {
pub balance: u128,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct GetAccountsNoncesResponse {
pub nonces: Vec<u128>,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct GetTransactionByHashResponse {
pub transaction: Option<String>,

View File

@ -12,7 +12,7 @@ pub struct SendTxRequest {
#[derive(Serialize, Deserialize, Debug)]
pub struct SendTxResponse {
pub status: String,
pub additional_data: Option<String>,
pub tx_hash: String,
}
//General

View File

@ -7,7 +7,10 @@ use json::{SendTxRequest, SendTxResponse, SequencerRpcRequest, SequencerRpcRespo
use reqwest::Client;
use serde_json::Value;
use crate::rpc_primitives::requests::{GetTransactionByHashRequest, GetTransactionByHashResponse};
use crate::rpc_primitives::requests::{
GetAccountsNoncesRequest, GetAccountsNoncesResponse, GetTransactionByHashRequest,
GetTransactionByHashResponse,
};
use crate::sequencer_client::json::AccountInitialData;
use crate::{SequencerClientError, SequencerRpcError};
@ -87,6 +90,42 @@ impl SequencerClient {
Ok(resp_deser)
}
///Get accounts nonces for `addresses`. `addresses` must be a list of valid hex-strings for 32 bytes.
pub async fn get_accounts_nonces(
&self,
addresses: Vec<String>,
) -> Result<GetAccountsNoncesResponse, SequencerClientError> {
let block_req = GetAccountsNoncesRequest { addresses };
let req = serde_json::to_value(block_req)?;
let resp = self
.call_method_with_payload("get_accounts_nonces", req)
.await?;
let resp_deser = serde_json::from_value(resp)?;
Ok(resp_deser)
}
///Get transaction details for `hash`.
pub async fn get_transaction_by_hash(
&self,
hash: String,
) -> Result<GetTransactionByHashResponse, SequencerClientError> {
let block_req = GetTransactionByHashRequest { hash };
let req = serde_json::to_value(block_req)?;
let resp = self
.call_method_with_payload("get_transaction_by_hash", req)
.await?;
let resp_deser = serde_json::from_value(resp)?;
Ok(resp_deser)
}
///Send transaction to sequencer
pub async fn send_tx(
&self,
@ -138,22 +177,4 @@ impl SequencerClient {
Ok(resp_deser)
}
///Get tx data for `tx_hash` from sequencer
pub async fn get_transaction_by_hash(
&self,
tx_hash: String,
) -> Result<GetTransactionByHashResponse, SequencerClientError> {
let block_req = GetTransactionByHashRequest { hash: tx_hash };
let req = serde_json::to_value(block_req)?;
let resp = self
.call_method_with_payload("get_transaction_by_hash", req)
.await?;
let resp_deser = serde_json::from_value(resp)?;
Ok(resp_deser)
}
}

View File

@ -116,6 +116,12 @@ impl NSSAUserData {
.and_modify(|(_, acc)| acc.balance = new_balance);
}
pub fn increment_account_nonce(&mut self, address: nssa::Address) {
self.accounts
.entry(address)
.and_modify(|(_, acc)| acc.nonce += 1);
}
//ToDo: Part of a private keys update
// pub fn make_tag(&self) -> Tag {
// self.address.value()[0]

View File

@ -12,7 +12,8 @@ use common::{
message::{Message, Request},
parser::RpcRequest,
requests::{
GetAccountBalanceRequest, GetAccountBalanceResponse, GetInitialTestnetAccountsRequest,
GetAccountBalanceRequest, GetAccountBalanceResponse, GetAccountsNoncesRequest,
GetAccountsNoncesResponse, GetInitialTestnetAccountsRequest,
GetTransactionByHashRequest, GetTransactionByHashResponse,
},
},
@ -33,10 +34,11 @@ 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 GET_ACCOUNTS_NONCES: &str = "get_accounts_nonces";
pub const HELLO_FROM_SEQUENCER: &str = "HELLO_FROM_SEQUENCER";
pub const SUCCESS: &str = "Success";
pub const TRANSACTION_SUBMITTED: &str = "Transaction submitted";
pub const GET_INITIAL_TESTNET_ACCOUNTS: &str = "get_initial_testnet_accounts";
@ -72,6 +74,7 @@ impl JsonHandler {
let send_tx_req = SendTxRequest::parse(Some(request.params))?;
let tx = nssa::PublicTransaction::from_bytes(&send_tx_req.transaction)
.map_err(|e| RpcError::serialization_error(&e.to_string()))?;
let tx_hash = hex::encode(tx.hash());
{
let mut state = self.sequencer_state.lock().await;
@ -80,7 +83,8 @@ impl JsonHandler {
}
let helperstruct = SendTxResponse {
status: SUCCESS.to_string(),
status: TRANSACTION_SUBMITTED.to_string(),
tx_hash,
};
respond(helperstruct)
@ -171,6 +175,38 @@ impl JsonHandler {
respond(helperstruct)
}
/// Returns the nonces of the accounts at the given addresses.
/// Each address must be a valid hex string of the correct length.
async fn process_get_accounts_nonces(&self, request: Request) -> Result<Value, RpcErr> {
let get_account_nonces_req = GetAccountsNoncesRequest::parse(Some(request.params))?;
let mut addresses = vec![];
for address_raw in get_account_nonces_req.addresses {
let address_bytes = hex::decode(address_raw)
.map_err(|_| RpcError::invalid_params("invalid hex".to_string()))?;
let address = nssa::Address::new(
address_bytes
.try_into()
.map_err(|_| RpcError::invalid_params("invalid length".to_string()))?,
);
addresses.push(address);
}
let nonces = {
let state = self.sequencer_state.lock().await;
addresses
.into_iter()
.map(|addr| state.store.state.get_account_by_address(&addr).nonce)
.collect()
};
let helperstruct = GetAccountsNoncesResponse { nonces };
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> {
@ -205,6 +241,7 @@ impl JsonHandler {
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_ACCOUNTS_NONCES => self.process_get_accounts_nonces(request).await,
GET_TRANSACTION_BY_HASH => self.process_get_transaction_by_hash(request).await,
_ => Err(RpcErr(RpcError::method_not_found(request.method))),
}

View File

@ -23,6 +23,7 @@ hex.workspace = true
actix-rt.workspace = true
clap.workspace = true
nssa-core = { path = "../nssa/core" }
base64.workspace = true
[dependencies.key_protocol]
path = "../key_protocol"

View File

@ -78,7 +78,10 @@ mod tests {
home,
override_rust_log: None,
sequencer_addr: "http://127.0.0.1".to_string(),
seq_poll_timeout_secs: 1,
seq_poll_timeout_millis: 12000,
seq_poll_max_blocks: 5,
seq_poll_max_retries: 10,
seq_poll_retry_delay_millis: 500,
initial_accounts: create_initial_accounts(),
}
}

View File

@ -34,8 +34,14 @@ pub struct WalletConfig {
pub override_rust_log: Option<String>,
///Sequencer URL
pub sequencer_addr: String,
///Sequencer polling duration for new blocks in seconds
pub seq_poll_timeout_secs: u64,
///Sequencer polling duration for new blocks in milliseconds
pub seq_poll_timeout_millis: u64,
///Sequencer polling max number of blocks
pub seq_poll_max_blocks: usize,
///Sequencer polling max number error retries
pub seq_poll_max_retries: u64,
///Sequencer polling error retry delay in milliseconds
pub seq_poll_retry_delay_millis: u64,
///Initial accounts for wallet
pub initial_accounts: Vec<InitialAccountData>,
}

View File

@ -1,5 +1,6 @@
use std::{fs::File, io::Write, path::PathBuf, sync::Arc};
use base64::Engine;
use common::{
sequencer_client::{json::SendTxResponse, SequencerClient},
ExecutionFailureKind,
@ -13,9 +14,12 @@ use nssa::Address;
use clap::{Parser, Subcommand};
use crate::helperfunctions::{
fetch_config, fetch_persistent_accounts, get_home, produce_account_addr_from_hex,
produce_data_for_storage,
use crate::{
helperfunctions::{
fetch_config, fetch_persistent_accounts, get_home, produce_account_addr_from_hex,
produce_data_for_storage,
},
poller::TxPoller,
};
pub const HOME_DIR_ENV_VAR: &str = "NSSA_WALLET_HOME_DIR";
@ -24,15 +28,24 @@ pub const BLOCK_GEN_DELAY_SECS: u64 = 20;
pub mod chain_storage;
pub mod config;
pub mod helperfunctions;
pub mod poller;
pub struct WalletCore {
pub storage: WalletChainStore,
pub poller: TxPoller,
pub sequencer_client: Arc<SequencerClient>,
}
impl WalletCore {
pub async fn start_from_config_update_chain(config: WalletConfig) -> Result<Self> {
let client = Arc::new(SequencerClient::new(config.sequencer_addr.clone())?);
let tx_poller = TxPoller {
polling_delay_millis: config.seq_poll_timeout_millis,
polling_max_blocks_to_query: config.seq_poll_max_blocks,
polling_max_error_attempts: config.seq_poll_max_retries,
polling_error_delay_millis: config.seq_poll_retry_delay_millis,
client: client.clone(),
};
let mut storage = WalletChainStore::new(config)?;
@ -48,6 +61,7 @@ impl WalletCore {
Ok(Self {
storage,
poller: tx_poller,
sequencer_client: client.clone(),
})
}
@ -110,6 +124,38 @@ impl WalletCore {
Err(ExecutionFailureKind::AmountMismatchError)
}
}
pub async fn poll_public_native_token_transfer(
&self,
hash: String,
) -> Result<nssa::PublicTransaction> {
let transaction_encoded = self.poller.poll_tx(hash).await?;
let tx_base64_decode =
base64::engine::general_purpose::STANDARD.decode(transaction_encoded)?;
let pub_tx = nssa::PublicTransaction::from_bytes(&tx_base64_decode)?;
Ok(pub_tx)
}
pub fn execute_native_token_transfer(
&mut self,
from: Address,
to: Address,
balance_to_move: u128,
) {
self.storage.user_data.increment_account_nonce(from);
self.storage.user_data.increment_account_nonce(to);
let from_bal = self.storage.user_data.get_account_balance(&from);
let to_bal = self.storage.user_data.get_account_balance(&to);
self.storage
.user_data
.update_account_balance(from, from_bal - balance_to_move);
self.storage
.user_data
.update_account_balance(to, to_bal + balance_to_move);
}
}
///Represents CLI command for a wallet
@ -169,7 +215,13 @@ pub async fn execute_subcommand(command: Command) -> Result<()> {
info!("Results of tx send is {res:#?}");
//ToDo: Insert transaction polling logic here
let transfer_tx = wallet_core
.poll_public_native_token_transfer(res.tx_hash)
.await?;
info!("Transaction data is {transfer_tx:#?}");
wallet_core.execute_native_token_transfer(from, to, amount);
}
Command::RegisterAccount {} => {
let addr = wallet_core.create_new_account();

55
wallet/src/poller.rs Normal file
View File

@ -0,0 +1,55 @@
use std::sync::Arc;
use anyhow::Result;
use common::sequencer_client::SequencerClient;
use log::{info, warn};
#[derive(Clone)]
pub struct TxPoller {
pub polling_max_blocks_to_query: usize,
pub polling_max_error_attempts: u64,
pub polling_error_delay_millis: u64,
pub polling_delay_millis: u64,
pub client: Arc<SequencerClient>,
}
impl TxPoller {
pub async fn poll_tx(&self, tx_hash: String) -> Result<String> {
let max_blocks_to_query = self.polling_max_blocks_to_query;
info!("Starting poll for transaction {tx_hash:#?}");
for poll_id in 1..max_blocks_to_query {
info!("Poll {poll_id}");
let mut try_error_counter = 0;
let tx_obj = loop {
let tx_obj = self
.client
.get_transaction_by_hash(tx_hash.clone())
.await
.inspect_err(|err| {
warn!("Failed to get transaction by hash {tx_hash:#?} with error: {err:#?}")
});
if let Ok(tx_obj) = tx_obj {
break tx_obj;
} else {
try_error_counter += 1;
}
if try_error_counter > self.polling_max_error_attempts {
anyhow::bail!("Number of retries exceeded");
}
};
if tx_obj.transaction.is_some() {
return Ok(tx_obj.transaction.unwrap());
}
tokio::time::sleep(std::time::Duration::from_millis(self.polling_delay_millis)).await;
}
anyhow::bail!("Transaction not found in preconfigured amount of blocks");
}
}