Skip to content

Commit

Permalink
feat(rpc): add tonapi interface
Browse files Browse the repository at this point in the history
  • Loading branch information
serejkaaa512 committed Feb 4, 2025
1 parent e18ee50 commit 2214252
Show file tree
Hide file tree
Showing 9 changed files with 909 additions and 0 deletions.
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.

1 change: 1 addition & 0 deletions rpc/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ base64 = { workspace = true }
bytes = { workspace = true }
everscale-types = { workspace = true }
futures-util = { workspace = true }
hex = { workspace = true }
metrics = { workspace = true }
moka = { workspace = true }
parking_lot = { workspace = true }
Expand Down
2 changes: 2 additions & 0 deletions rpc/src/endpoint/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ use crate::state::RpcState;

mod jrpc;
mod proto;
mod tonapi;

pub struct RpcEndpoint {
listener: TcpListener,
Expand Down Expand Up @@ -45,6 +46,7 @@ impl RpcEndpoint {
.route("/", post(common_route))
.route("/rpc", post(common_route))
.route("/proto", post(common_route))
.nest("/v2", tonapi::router())
.layer(service)
.with_state(self.state);

Expand Down
61 changes: 61 additions & 0 deletions rpc/src/endpoint/tonapi/accounts.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
use axum::extract::{Path, State};
use axum::routing::get;
use axum::Json;
use everscale_types::models::StdAddr;

use super::error::Error;
use super::responses::{status_to_string, GetAccountResponse};
use crate::endpoint::tonapi::error::Result;
use crate::state::LoadedAccountState;
use crate::RpcState;

pub fn router() -> axum::Router<RpcState> {
axum::Router::new().route("/accounts/:account", get(get_account))
}

async fn get_account(
Path(address): Path<StdAddr>,
State(state): State<RpcState>,
) -> Result<Json<GetAccountResponse>> {
let item = match state.get_account_state(&address) {
Ok(item) => item,
Err(e) => return Err(Error::AnyhowText(e.to_string())),
};

match &item {
&LoadedAccountState::NotFound { .. } => Err(Error::NotFound("account not found")),
LoadedAccountState::Found { state, .. } => {
match state.load_account() {
Ok(Some(loaded)) => {
Ok(Json(GetAccountResponse {
address: address.to_string(),
balance: loaded.balance.tokens.into_inner() as u64,
extra_balance: None, // TODO: fill with correct extra balance
status: status_to_string(loaded.state.status()),
currencies_balance: Some(
loaded
.balance
.other
.as_dict()
.iter()
.filter_map(|res| res.ok())
.map(|(id, balance)| (id, balance.as_usize() as u64))
.collect(),
),
last_activity: loaded.last_trans_lt,
interfaces: None, // TODO: fill with correct interfaces,
name: None, // TODO: fill with correct name,
is_scam: None, // TODO: fill with correct is_scam,
icon: None, // TODO: fill with correct icon,
memo_required: None, // TODO: fill with correct memo_required,
get_methods: vec![], // TODO: fill with correct get_methods,
is_suspended: None, // TODO: fill with correct is_suspended,
is_wallet: true, // TODO: fill with correct is_wallet,
}))
}
Ok(None) => Err(Error::NotFound("account not found")),
Err(e) => Err(Error::AnyhowText(e.to_string())),
}
}
}
}
246 changes: 246 additions & 0 deletions rpc/src/endpoint/tonapi/blockchain.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,246 @@
use axum::extract::{Path, Query, State};
use axum::routing::{get, post};
use axum::Json;
use everscale_types::boc::Boc;
use everscale_types::models::{
AccountState, ComputePhase, MsgInfo, OwnedMessage, StateInit, StdAddr, Transaction, TxInfo,
};

use super::error::Error;
use super::requests::{ExecMethodArgs, Pagination, SendMessageRequest};
use super::responses::{
bounce_phase_to_string, status_to_string, AccountResponse, ExecGetMethodResponse,
LibraryResponse, TransactionAccountResponse, TransactionResponse, TransactionsResponse,
};
use crate::endpoint::tonapi::error::Result;
use crate::state::LoadedAccountState;
use crate::RpcState;

pub fn router() -> axum::Router<RpcState> {
axum::Router::new()
.route("/accounts/:account", get(get_blockchain_raw_account))
.route(
"/accounts/:account/transactions",
get(get_blockchain_account_transactions),
)
.route(
"/accounts/:account/methods/:method_name",
get(exec_get_method_for_blockchain_account),
)
.route("/message", post(send_blockchain_message))
}

async fn get_blockchain_raw_account(
Path(address): Path<StdAddr>,
State(state): State<RpcState>,
) -> Result<Json<AccountResponse>> {
let item = state.get_account_state(&address)?;

match &item {
&LoadedAccountState::NotFound { .. } => Err(Error::NotFound("account not found")),
LoadedAccountState::Found { state, .. } => {
let account = state.load_account()?;
match account {
Some(loaded) => {
let status = loaded.state.status();
let (code, data, libraries, frozen_hash) = match loaded.state {
AccountState::Active(StateInit {
code,
data,
libraries,
..
}) => (
code.map(Boc::encode_hex),
data.map(Boc::encode_hex),
Some(
libraries
.iter()
.filter_map(|res| res.ok())
.map(|(_, lib)| LibraryResponse {
public: lib.public,
root: Boc::encode_hex(lib.root),
})
.collect(),
),
None,
),
AccountState::Uninit => (None, None, None, None),
AccountState::Frozen(hash_bytes) => {
(None, None, None, Some(hash_bytes.to_string()))
}
};

Ok(Json(AccountResponse {
address: address.to_string(),
balance: loaded.balance.tokens.into_inner() as u64,
extra_balance: None, // TODO: fill with correct extra balance
status: status_to_string(status),
code,
data,
last_transaction_lt: loaded.last_trans_lt,
last_transaction_hash: Some(state.last_trans_hash.to_string()),
frozen_hash,
libraries,
storage: loaded.storage_stat.into(),
}))
}
None => Err(Error::NotFound("account not found")),
}
}
}
}

async fn get_blockchain_account_transactions(
Path(address): Path<StdAddr>,
Query(pagination): Query<Pagination>,
State(state): State<RpcState>,
) -> Result<Json<TransactionsResponse>> {
let limit = pagination.limit.unwrap_or(TransactionsResponse::MAX_LIMIT);

if limit == 0 {
return Ok(Json(TransactionsResponse {
transactions: vec![],
}));
}

if limit > TransactionsResponse::MAX_LIMIT {
return Err(Error::BadRequest("limit is too large"));
}

let list = state.get_transactions(&address, pagination.after_lt)?;
let transactions = list.map(|item| {
match Boc::decode(item) {
Ok(root) => {
let hash = *root.repr_hash();
let t = root.parse::<Transaction>()?;

let in_msg = if let Some(in_msg) = &t.in_msg {
let hash = *in_msg.repr_hash();
let in_msg = in_msg.parse::<OwnedMessage>()?;
Some((in_msg.info,Some(in_msg.body.0), hash).into())
} else {
None
};

let mut out_msgs = vec![];

for out_msg_cell in t.out_msgs.values() {
let out_msg_cell = out_msg_cell?;
let out_msg_hash = *out_msg_cell.repr_hash();
let out_msg_info = out_msg_cell.parse::<MsgInfo>()?;
out_msgs.push((out_msg_info, None, out_msg_hash).into());
}

let info = t.load_info().map_err(|e| Error::AnyhowText(e.to_string()))?;

let transaction_type;
let compute_phase;
let credit_phase;
let storage_phase;
let action_phase;
let aborted;
let bounce_phase;
let destroyed;
let mut success = true;

match info {
TxInfo::Ordinary(ordinary_tx_info) => {
transaction_type = "TransOrd".to_string();

compute_phase = match ordinary_tx_info.compute_phase {
ComputePhase::Skipped(skipped_compute_phase) => {
Some(skipped_compute_phase.into())
},
ComputePhase::Executed(executed_compute_phase) => {
success = executed_compute_phase.success;
Some(executed_compute_phase.into())
},
};
storage_phase = ordinary_tx_info.storage_phase.map(From::from);
credit_phase = ordinary_tx_info.credit_phase.map(From::from);
action_phase = ordinary_tx_info.action_phase.map(From::from);
aborted = ordinary_tx_info.aborted;
bounce_phase = ordinary_tx_info.bounce_phase.map(bounce_phase_to_string);
destroyed = ordinary_tx_info.destroyed;
},
TxInfo::TickTock(tick_tock_tx_info) => {
transaction_type = "TransTickTock".to_string();
compute_phase = match tick_tock_tx_info.compute_phase {
ComputePhase::Skipped(skipped_compute_phase) => {
Some(skipped_compute_phase.into())
},
ComputePhase::Executed(executed_compute_phase) => {
success = executed_compute_phase.success;
Some(executed_compute_phase.into())
},
};
storage_phase = Some(tick_tock_tx_info.storage_phase.into());
credit_phase = None;
action_phase = tick_tock_tx_info.action_phase.map(From::from);
aborted = tick_tock_tx_info.aborted;
bounce_phase = None;
destroyed = tick_tock_tx_info.destroyed;
},
}

let state_update = t.state_update.load()?;

Ok(TransactionResponse {
hash: hash.to_string(),
lt: t.lt,
account: TransactionAccountResponse {
address: address.to_string(),
name: None,
is_scam: false, // TODO: fill with correct is_scam
icon: None,
is_wallet: true,// TODO: fill with correct is_wallet
},
success,
utime: t.now,
orig_status: status_to_string(t.orig_status),
end_status: status_to_string(t.end_status),
total_fees: t.total_fees.tokens.into_inner() as u64,
end_balance: 0, // TODO: fill with correct end_balance
transaction_type,
state_update_old: state_update.old.to_string(),
state_update_new: state_update.new.to_string(),
in_msg,
out_msgs,
block: "(-1,4234234,8000000000000000)".to_string(), // TODO: fill with correct block
prev_trans_hash: Some(t.prev_trans_hash.to_string()),
prev_trans_lt: Some(t.prev_trans_lt),
compute_phase,
storage_phase,
credit_phase,
action_phase,
bounce_phase,
aborted,
destroyed,
raw: Boc::encode_hex( root),
})
},
Err(e) => Err(Error::AnyhowText(e.to_string())),
}
})
.take(limit as _)
.collect::<Result<Vec<TransactionResponse>, _>>()?;

Ok(Json(TransactionsResponse { transactions }))
}

async fn exec_get_method_for_blockchain_account(
Path((_address, _method_name)): Path<(StdAddr, String)>,
Query(_args): Query<ExecMethodArgs>,
State(_state): State<RpcState>,
) -> Result<Json<ExecGetMethodResponse>> {
Err(Error::BadRequest("Unimplemented"))
}

async fn send_blockchain_message(
State(state): State<RpcState>,
Json(input): Json<SendMessageRequest>,
) -> Result<Json<()>> {
let data = hex::decode(input.boc)?;
state.broadcast_external_message(&data).await;
Ok(Json(()))
}
Loading

0 comments on commit 2214252

Please sign in to comment.