Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ The format is loosely based on [Keep a Changelog](https://keepachangelog.com/en/
is normally done for misbehaving peers) and the node won't try connecting to it again.\
Also, the peer will be sent an appropriate `WillDisconnect` message prior to disconnection.

- Wallet CLI and RPC: the commands `account-utxos` and `standalone-multisig-utxos` and their RPC
counterparts now return correct decimal amounts for tokens with non-default number of decimals.

## [1.2.0] - 2025-10-27

### Changed
Expand Down
68 changes: 57 additions & 11 deletions chainstate/src/rpc/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,29 +18,39 @@
mod types;

use std::{
collections::BTreeMap,
convert::Infallible,
io::{Read, Write},
num::NonZeroUsize,
sync::Arc,
};

use self::types::{block::RpcBlock, event::RpcEvent};
use crate::{Block, BlockSource, ChainInfo, GenBlock};
use chainstate_types::BlockIndex;
use common::{
address::{dehexify::to_dehexified_json, Address},
chain::{
output_values_holder::collect_token_v1_ids_from_output_values_holder,
tokens::{RPCTokenInfo, TokenId},
ChainConfig, DelegationId, Destination, OrderId, PoolId, RpcOrderInfo, TxOutput,
},
primitives::{Amount, BlockHeight, Id},
};
use rpc::{subscription, RpcResult};
use serialization::hex_encoded::HexEncoded;

use crate::{
chainstate_interface::ChainstateInterface, Block, BlockSource, ChainInfo, ChainstateError,
GenBlock,
};

use self::types::{block::RpcBlock, event::RpcEvent};

pub use types::{
input::RpcUtxoOutpoint,
output::{RpcOutputValueIn, RpcOutputValueOut, RpcTxOutput},
signed_transaction::RpcSignedTransaction,
token_decimals_provider::{TokenDecimals, TokenDecimalsProvider},
RpcTypeError,
};

#[rpc::describe]
Expand Down Expand Up @@ -231,15 +241,33 @@ impl ChainstateRpcServer for super::ChainstateHandle {
.await,
)?;

let rpc_blk: Option<RpcBlock> = both
.map(|(block, block_index)| {
rpc::handle_result(RpcBlock::new(&chain_config, block, block_index))
})
.transpose()?;

let result = rpc_blk.map(|rpc_blk| to_dehexified_json(&chain_config, rpc_blk)).transpose();

rpc::handle_result(result)
if let Some((block, block_index)) = both {
let token_ids = collect_token_v1_ids_from_output_values_holder(&block);
let mut token_decimals = BTreeMap::new();

// TODO replace this loop with a single ChainstateInterface function call obtaining
// all infos at once (when the function is implemented).
for token_id in token_ids {
let token_info: RPCTokenInfo = rpc::handle_result(
self.call(move |this| get_existing_token_info_for_rpc(this, token_id)).await,
)?;
token_decimals.insert(
token_id,
TokenDecimals(token_info.token_number_of_decimals()),
);
}

let rpc_block: RpcBlock = rpc::handle_result(RpcBlock::new(
&chain_config,
&token_decimals,
block,
block_index,
))?;
let json = rpc::handle_result(to_dehexified_json(&chain_config, rpc_block))?;
Ok(Some(json))
} else {
Ok(None)
}
}

async fn get_mainchain_blocks(
Expand Down Expand Up @@ -469,6 +497,24 @@ where
o.map_err(Into::into)
}

fn get_existing_token_info_for_rpc(
chainstate: &(impl ChainstateInterface + ?Sized),
token_id: TokenId,
) -> Result<RPCTokenInfo, LocalRpcError> {
chainstate
.get_token_info_for_rpc(token_id)?
.ok_or(LocalRpcError::MissingTokenInfo(token_id))
}

#[derive(thiserror::Error, Debug, PartialEq, Eq)]
enum LocalRpcError {
#[error("Token info missing for token {0:x}")]
MissingTokenInfo(TokenId),

#[error(transparent)]
ChainstateError(#[from] ChainstateError),
}

#[cfg(test)]
mod test {
use super::*;
Expand Down
10 changes: 6 additions & 4 deletions chainstate/src/rpc/types/account.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
// limitations under the License.

use common::{
address::{AddressError, RpcAddress},
address::RpcAddress,
chain::{
tokens::{IsTokenUnfreezable, TokenId},
AccountCommand, AccountSpending, ChainConfig, DelegationId, Destination,
Expand All @@ -24,6 +24,8 @@ use common::{
};
use rpc::types::RpcHexString;

use super::RpcTypeError;

#[derive(Debug, Clone, serde::Serialize, rpc_description::HasValueHint)]
#[serde(tag = "type", content = "content")]
pub enum RpcAccountSpending {
Expand All @@ -37,7 +39,7 @@ impl RpcAccountSpending {
pub fn new(
chain_config: &ChainConfig,
spending: AccountSpending,
) -> Result<Self, AddressError> {
) -> Result<Self, RpcTypeError> {
let result = match spending {
AccountSpending::DelegationBalance(id, amount) => {
RpcAccountSpending::DelegationBalance {
Expand Down Expand Up @@ -89,7 +91,7 @@ pub enum RpcAccountCommand {
}

impl RpcAccountCommand {
pub fn new(chain_config: &ChainConfig, command: &AccountCommand) -> Result<Self, AddressError> {
pub fn new(chain_config: &ChainConfig, command: &AccountCommand) -> Result<Self, RpcTypeError> {
let result = match command {
AccountCommand::MintTokens(id, amount) => RpcAccountCommand::MintTokens {
token_id: RpcAddress::new(chain_config, *id)?,
Expand Down Expand Up @@ -155,7 +157,7 @@ impl RpcOrderAccountCommand {
pub fn new(
chain_config: &ChainConfig,
command: &OrderAccountCommand,
) -> Result<Self, AddressError> {
) -> Result<Self, RpcTypeError> {
let result = match command {
OrderAccountCommand::ConcludeOrder(order_id) => RpcOrderAccountCommand::Conclude {
order_id: RpcAddress::new(chain_config, *order_id)?,
Expand Down
20 changes: 7 additions & 13 deletions chainstate/src/rpc/types/block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,25 +15,17 @@

use chainstate_types::BlockIndex;
use common::{
address::AddressError,
chain::{block::timestamp::BlockTimestamp, Block, ChainConfig, GenBlock},
primitives::{BlockHeight, Id, Idable},
};
use serialization::hex_encoded::HexEncoded;

use super::{
block_reward::RpcBlockReward, consensus_data::RpcConsensusData,
signed_transaction::RpcSignedTransaction,
signed_transaction::RpcSignedTransaction, token_decimals_provider::TokenDecimalsProvider,
RpcTypeError,
};

#[derive(thiserror::Error, Debug)]
pub enum RpcTypeSerializationError {
#[error("Address error: {0}")]
Address(#[from] AddressError),
#[error("FromHex error: {0}")]
FromHex(#[from] hex::FromHexError),
}

#[derive(Debug, Clone, serde::Serialize)]
pub struct RpcBlock {
id: Id<Block>,
Expand All @@ -53,15 +45,17 @@ pub struct RpcBlock {
impl RpcBlock {
pub fn new(
chain_config: &ChainConfig,
token_decimals_provider: &impl TokenDecimalsProvider,
block: Block,
block_index: BlockIndex,
) -> Result<Self, RpcTypeSerializationError> {
) -> Result<Self, RpcTypeError> {
let rpc_consensus_data = RpcConsensusData::new(chain_config, block.consensus_data())?;
let rpc_block_reward = RpcBlockReward::new(chain_config, block.block_reward())?;
let rpc_block_reward =
RpcBlockReward::new(chain_config, token_decimals_provider, block.block_reward())?;
let rpc_transactions = block
.transactions()
.iter()
.map(|tx| RpcSignedTransaction::new(chain_config, tx.clone()))
.map(|tx| RpcSignedTransaction::new(chain_config, token_decimals_provider, tx.clone()))
.collect::<Result<Vec<_>, _>>()?;

let rpc_block = Self {
Expand Down
17 changes: 10 additions & 7 deletions chainstate/src/rpc/types/block_reward.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,9 @@
// See the License for the specific language governing permissions and
// limitations under the License.

use common::{
address::AddressError,
chain::{block::BlockReward, ChainConfig},
};
use common::chain::{block::BlockReward, ChainConfig};

use super::output::RpcTxOutput;
use super::{output::RpcTxOutput, token_decimals_provider::TokenDecimalsProvider, RpcTypeError};

#[derive(Debug, Clone, serde::Serialize)]
pub struct RpcBlockReward {
Expand All @@ -27,11 +24,17 @@ pub struct RpcBlockReward {
}

impl RpcBlockReward {
pub fn new(chain_config: &ChainConfig, reward: &BlockReward) -> Result<Self, AddressError> {
// Note: in a real blockchain BlockReward will never reference tokens. But it's still
// possible to manually construct it this way.
pub fn new(
chain_config: &ChainConfig,
token_decimals_provider: &impl TokenDecimalsProvider,
reward: &BlockReward,
) -> Result<Self, RpcTypeError> {
let rpc_outputs = reward
.outputs()
.iter()
.map(|output| RpcTxOutput::new(chain_config, output.clone()))
.map(|output| RpcTxOutput::new(chain_config, token_decimals_provider, output.clone()))
.collect::<Result<Vec<_>, _>>()?;

let rpc_tx = Self {
Expand Down
22 changes: 9 additions & 13 deletions chainstate/src/rpc/types/consensus_data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,13 @@
// See the License for the specific language governing permissions and
// limitations under the License.

use std::str::FromStr;

use common::{
address::RpcAddress,
chain::{block::ConsensusData, ChainConfig, PoolId},
};
use rpc::types::RpcHexString;

use super::{block::RpcTypeSerializationError, input::RpcTxInput};
use super::{input::RpcTxInput, RpcTypeError};

#[derive(Debug, Clone, serde::Serialize)]
#[serde(tag = "type", content = "content")]
Expand All @@ -35,7 +33,7 @@ impl RpcConsensusData {
pub fn new(
chain_config: &ChainConfig,
consensus_data: &ConsensusData,
) -> Result<Self, RpcTypeSerializationError> {
) -> Result<Self, RpcTypeError> {
let rpc_consensus_data = match consensus_data {
ConsensusData::None => RpcConsensusData::None,
ConsensusData::PoW(_) => RpcConsensusData::PoW,
Expand All @@ -47,16 +45,14 @@ impl RpcConsensusData {
.collect::<Result<Vec<_>, _>>()?;

let compact_target =
RpcHexString::from_str(format!("{:x}", pos_data.compact_target().0).as_str())?;
RpcHexString::from_bytes(pos_data.compact_target().0.to_be_bytes().to_vec());

let target = RpcHexString::from_str(
format!(
"{:x}",
TryInto::<common::Uint256>::try_into(pos_data.compact_target())
.expect("valid target")
)
.as_str(),
)?;
let target = RpcHexString::from_bytes(
TryInto::<common::Uint256>::try_into(pos_data.compact_target())
.expect("valid target")
.to_be_bytes()
.to_vec(),
);

RpcConsensusData::PoS {
pos_data: RpcPoSData {
Expand Down
8 changes: 5 additions & 3 deletions chainstate/src/rpc/types/input.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,14 @@
// limitations under the License.

use common::{
address::AddressError,
chain::{ChainConfig, GenBlock, OutPointSourceId, Transaction, TxInput, UtxoOutPoint},
primitives::Id,
};

use super::account::{RpcAccountCommand, RpcAccountSpending, RpcOrderAccountCommand};
use super::{
account::{RpcAccountCommand, RpcAccountSpending, RpcOrderAccountCommand},
RpcTypeError,
};

#[derive(Debug, Clone, serde::Serialize, rpc_description::HasValueHint)]
#[serde(tag = "type", content = "content")]
Expand All @@ -42,7 +44,7 @@ pub enum RpcTxInput {
}

impl RpcTxInput {
pub fn new(chain_config: &ChainConfig, input: &TxInput) -> Result<Self, AddressError> {
pub fn new(chain_config: &ChainConfig, input: &TxInput) -> Result<Self, RpcTypeError> {
let result = match input {
TxInput::Utxo(outpoint) => match outpoint.source_id() {
OutPointSourceId::Transaction(id) => RpcTxInput::Utxo {
Expand Down
12 changes: 12 additions & 0 deletions chainstate/src/rpc/types/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,15 @@ pub mod input;
pub mod output;
pub mod signed_transaction;
pub mod token;
pub mod token_decimals_provider;

use common::{address::AddressError, chain::tokens::TokenId};

#[derive(thiserror::Error, Debug)]
pub enum RpcTypeError {
#[error("Address error: {0}")]
Address(#[from] AddressError),

#[error("Token decimals unavailable for token {0:x}")]
TokenDecimalsUnavailable(TokenId),
}
Loading