Skip to content

Commit

Permalink
feat(rpc): add toncenter interface
Browse files Browse the repository at this point in the history
  • Loading branch information
serejkaaa512 committed Feb 19, 2025
1 parent b1c589c commit 5dbe6c0
Show file tree
Hide file tree
Showing 9 changed files with 697 additions and 5 deletions.
File renamed without changes.
4 changes: 4 additions & 0 deletions rpc/src/endpoint/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,12 @@ pub use self::jrpc::JrpcEndpointCache;
pub use self::proto::ProtoEndpointCache;
use crate::state::RpcState;

mod error;
mod jrpc;
mod proto;
mod tonapi;
mod toncenter;
mod utils;

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

Expand Down
3 changes: 1 addition & 2 deletions rpc/src/endpoint/tonapi/accounts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,8 @@ 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::endpoint::error::{Error, Result};
use crate::state::LoadedAccountState;
use crate::RpcState;

Expand Down
3 changes: 1 addition & 2 deletions rpc/src/endpoint/tonapi/blockchain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,14 @@ use everscale_types::models::{
use num_bigint::BigInt;
use tycho_vm::{GasParams, NaN, OwnedCellSlice, RcStackValue, SmcInfoBase, VmState};

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,
TvmStackRecord,
};
use super::utils::crc_16;
use crate::endpoint::tonapi::error::Result;
use crate::endpoint::error::{Error, Result};
use crate::state::LoadedAccountState;
use crate::RpcState;

Expand Down
1 change: 0 additions & 1 deletion rpc/src/endpoint/tonapi/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ use crate::RpcState;

mod accounts;
mod blockchain;
mod error;
mod requests;
mod responses;
mod utils;
Expand Down
325 changes: 325 additions & 0 deletions rpc/src/endpoint/toncenter/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,325 @@
use std::str::FromStr;
use std::vec;

use axum::extract::{Path, Query, State};
use axum::routing::{get, post};
use axum::Json;
use everscale_types::boc::Boc;
use everscale_types::cell::CellBuilder;
use everscale_types::models::{
AccountState, IntAddr, MsgInfo, OwnedMessage, StateInit, StdAddr, Transaction,
};
use num_bigint::BigInt;
use requests::{AddressInformationQuery, ExecMethodArgs, GetTransactionsQuery, SendMessageRequest};
use responses::{
status_to_string, AddressInformation, AddressResponse, BlockId, TonCenterResponse,
TransactionId, TransactionResponse, TransactionsResponse, TvmStackRecord,
};
use tycho_vm::{GasParams, NaN, OwnedCellSlice, RcStackValue, SmcInfoBase, VmState};

use super::utils::crc_16;
use crate::endpoint::error::{Error, Result};
use crate::state::LoadedAccountState;
use crate::RpcState;

mod requests;
mod responses;

pub fn router() -> axum::Router<RpcState> {
axum::Router::new()
.route("/getAddressInformation", get(get_address_information))
.route("/getTransactions", get(get_blockchain_account_transactions))
.route("/runGetMethod", get(exec_get_method_for_blockchain_account))
.route("/sendBoc", post(send_blockchain_message))
}

async fn get_address_information(
Query(address): Query<AddressInformationQuery>,
State(state): State<RpcState>,
) -> Result<Json<TonCenterResponse<AddressInformation>>> {
let Ok(item) = state.get_account_state(&address.address) else {
return Ok(Json(TonCenterResponse {
ok: false,
result: None,
error: Some("account not found".to_string()),
code: None,
}));
};

match &item {
&LoadedAccountState::NotFound { .. } => Err(Error::NotFound("account not found")),
LoadedAccountState::Found { state, .. } => {
let Ok(account) = state.load_account() else {
return Ok(Json(TonCenterResponse {
ok: false,
result: None,
error: Some("account not found".to_string()),
code: None,
}));
};
match account {
Some(loaded) => {
let status = loaded.state.status();
let (code, data, frozen_hash) = match loaded.state {
AccountState::Active(StateInit { code, data, .. }) => {
(code.map(Boc::encode_hex), data.map(Boc::encode_hex), None)
}
AccountState::Uninit => (None, None, None),
AccountState::Frozen(hash_bytes) => {
(None, None, Some(hash_bytes.to_string()))
}
};

Ok(Json(TonCenterResponse {
ok: true,
result: Some(AddressInformation {
type_field: "raw.fullAccountState".to_string(),
balance: loaded.balance.tokens.into_inner() as u64,
code,
data,
last_transaction_id: TransactionId {
type_field: "internal.transactionId".to_string(),
lt: loaded.last_trans_lt.to_string(),
hash: Some(state.last_trans_hash.to_string()),
},
block_id: BlockId {
type_field: "ton.blockIdExt".to_string(),
workchain: 0, // TODO: fix workchain
shard: "-9223372036854775808".to_string(), /* TODO: fix block_id shard */
seqno: 44906584, // TODO: fix block_id seqno
root_hash: "tgbt6ZqC1bYk4m9yMYQDHLUeNrmWRNtb5r/mhHoB9RA="
.to_string(), // TODO: fix block_id root_hash
file_hash: "4vkWnBWGIHiDy1Z7W2m+zcd/1HvZkGB1lCthVs07woM="
.to_string(), // TODO: fix block_id file_hash
},
frozen_hash,
sync_utime: 0, // TODO: fix sync utime
extra: "1739453311.547493:11:0.8618085632029536".to_string(), /* TODO: fix extra */
state: status_to_string(status),
}),
error: None,
code: None,
}))
}
None => Ok(Json(TonCenterResponse {
ok: false,
result: None,
error: Some("account not found".to_string()),
code: None,
})),
}
}
}
}

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

if limit == 0 {
return Ok(Json(TonCenterResponse {
ok: true,
result: Some(vec![]),
error: None,
code: None,
}));
}

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

let list = state.get_transactions(&address, pagination.lt)?;
let transactions = list
.map(|item| {
let root = Boc::decode(item).map_err(|e| anyhow::format_err!(e.to_string()))?;
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());
}

Ok(TransactionResponse {
type_field: "raw.transaction".to_string(),
address: AddressResponse {
type_field: "accountAddress".to_string(),
account_address: pagination.address.to_string(),
},
data: Boc::encode_hex(root),
transaction_id: TransactionId {
type_field: "internal.transactionId".to_string(),
lt: t.lt.to_string(),
hash: Some(hash.to_string()),
},
fee: (t.total_fees.tokens.into_inner() as u64).to_string(), // CHECK
storage_fee: 0.to_string(), // TODO: fill with correct storage_fee
other_fee: 0.to_string(), // TODO: fill with correct other_fee
utime: t.now,
in_msg,
out_msgs,
}) as Result<TransactionResponse, Error>
})
.take(limit as _)
.collect::<Result<Vec<TransactionResponse>, _>>()?;

Ok(Json(TonCenterResponse {
ok: true,
result: Some(transactions),
error: None,
code: None,
}))
}

async fn exec_get_method_for_blockchain_account(
Query(args): Query<ExecMethodArgs>,
State(state): State<RpcState>,
) -> Result<Json<TonCenterResponse<String>>> {
let item = state.get_account_state(&args.address)?;

match &item {
&LoadedAccountState::NotFound { .. } => Ok(Json(TonCenterResponse {
ok: false,
result: None,
error: Some("account not found".to_string()),
code: None,
})),
LoadedAccountState::Found { state, .. } => {
let account = state.load_account()?;
match account {
Some(loaded) => {
match loaded.state {
AccountState::Active(StateInit { code, data, .. }) => {
let smc_info = SmcInfoBase::new()
// .with_now(1733142533) // TODO: check if needed?
// .with_block_lt(50899537000013) // TODO: check if needed?
// .with_tx_lt(50899537000013) // TODO: check if needed?
.with_account_balance(loaded.balance)
.with_account_addr(IntAddr::Std(args.address.clone()))
.require_ton_v4();

let crc = crc_16(args.method.as_bytes());
let method_id = crc as u32 | 0x10000;

let mut stack = vec![RcStackValue::new_dyn_value(
OwnedCellSlice::from(CellBuilder::build_from(&args.address)?),
)];

let mut stack_response = vec![TvmStackRecord::Slice {
slice: args.address.to_string(),
}];

for args_stack in args.stack {
for arg in args_stack {
// TODO: check args
if arg == "NaN" {
stack.push(RcStackValue::new_dyn_value(NaN));
stack_response.push(TvmStackRecord::Nan);
continue;
}

if arg == "Null" {
stack.push(RcStackValue::new_dyn_value(()));
stack_response.push(TvmStackRecord::Null);
continue;
}

if let Ok(v) = BigInt::from_str(&arg) {
stack.push(RcStackValue::new_dyn_value(v));
stack_response.push(TvmStackRecord::Num { num: arg });
continue;
}

if let Ok(v) = hex::decode(&arg) {
if let Some(v) = BigInt::parse_bytes(&v, 16) {
stack.push(RcStackValue::new_dyn_value(v));
stack_response.push(TvmStackRecord::Num { num: arg });
continue;
}
}

if let Ok(cell) = Boc::decode_base64(&arg) {
stack.push(RcStackValue::new_dyn_value(
OwnedCellSlice::from(cell),
));
stack_response.push(TvmStackRecord::Cell { cell: arg });
continue;
}
if let Ok(cell) = Boc::decode(&arg) {
stack.push(RcStackValue::new_dyn_value(
OwnedCellSlice::from(cell),
));
stack_response.push(TvmStackRecord::Slice { slice: arg });
continue;
}
if let Ok(address) = IntAddr::from_str(&arg) {
stack.push(RcStackValue::new_dyn_value(
OwnedCellSlice::from(CellBuilder::build_from(
&address,
)?),
));
stack_response.push(TvmStackRecord::Slice { slice: arg });
continue;
}
}
}

stack.push(RcStackValue::new_dyn_value(BigInt::from(method_id)));
stack_response.push(TvmStackRecord::Num {
num: method_id.to_string(),
});

let mut vm_state = VmState::builder()
.with_smc_info(smc_info)
.with_stack(stack)
.with_code(code.unwrap_or_default())
.with_data(data.unwrap_or_default())
.with_gas(GasParams::getter())
.build();

let exit_code = vm_state.run();
let success = exit_code == 0;

Ok(Json(TonCenterResponse {
ok: success,
result: None, // TODO: fill with correct result
error: None,
code: None,
}))
}
_ => Err(Error::BadRequest("account has wrong state")),
}
}
None => Err(Error::NotFound("account not found")),
}
}
}
}

async fn send_blockchain_message(
State(state): State<RpcState>,
Json(input): Json<SendMessageRequest>,
) -> Result<Json<()>> {
let data =
hex::decode(input.boc).map_err(|_e| Error::BadRequest("can not parse boc from hex"))?;
state.broadcast_external_message(&data).await;
Ok(Json(()))
}
Loading

0 comments on commit 5dbe6c0

Please sign in to comment.