Skip to content

Commit f7f9900

Browse files
authored
Merge pull request #1999 from mintlayer/chainstate_tokens_info_as_single_call
New chainstate RPC call to obtain multiple token infos at once
2 parents b91d6f9 + fa7349a commit f7f9900

File tree

32 files changed

+2039
-866
lines changed

32 files changed

+2039
-866
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ The format is loosely based on [Keep a Changelog](https://keepachangelog.com/en/
1111

1212
## [Unreleased]
1313

14+
### Added
15+
- Node RPC: new method added - `chainstate_tokens_info`.
16+
1417
### Changed
1518
- Wallet RPC:
1619
`wallet_info`: the structure of the returned field `extra_info` was changed.

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

chainstate/src/detail/chainstateref/mod.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -361,6 +361,8 @@ impl<'a, S: BlockchainStorageRead, V: TransactionVerificationStrategy> Chainstat
361361
self.last_common_ancestor(block_index, &best_block_index)
362362
}
363363

364+
// Note: this function will return Some for NFTs only, because the data is only written
365+
// for NFTs. This is decided by `token_issuance_cache::has_tokens_issuance_to_cache`.
364366
#[log_error]
365367
pub fn get_token_aux_data(
366368
&self,
@@ -369,8 +371,9 @@ impl<'a, S: BlockchainStorageRead, V: TransactionVerificationStrategy> Chainstat
369371
self.db_tx.get_token_aux_data(token_id).map_err(PropertyQueryError::from)
370372
}
371373

374+
// Note: same as get_token_aux_data, this only works for NFTs, for the same reason.
372375
#[log_error]
373-
pub fn get_token_id(
376+
pub fn get_token_id_from_issuance_tx(
374377
&self,
375378
tx_id: &Id<Transaction>,
376379
) -> Result<Option<TokenId>, PropertyQueryError> {

chainstate/src/detail/error_classification.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -449,9 +449,8 @@ impl BlockProcessingErrorClassification for PropertyQueryError {
449449
| PropertyQueryError::GenesisHeaderRequested
450450
| PropertyQueryError::InvalidStartingBlockHeightForMainchainBlocks(_)
451451
| PropertyQueryError::InvalidBlockHeightRange { .. }
452-
| PropertyQueryError::UnsupportedTokenV0InOrder(_) => {
453-
BlockProcessingErrorClass::General
454-
}
452+
| PropertyQueryError::UnsupportedTokenV0InOrder(_)
453+
| PropertyQueryError::TokenInfoMissing(_) => BlockProcessingErrorClass::General,
455454
// Note: these errors are strange - sometimes they don't look like General, judging
456455
// by the code that uses them. But other times some of them seem to just wrap storage
457456
// errors.

chainstate/src/detail/query.rs

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
// See the License for the specific language governing permissions and
1414
// limitations under the License.
1515

16-
use std::num::NonZeroUsize;
16+
use std::{collections::BTreeSet, num::NonZeroUsize};
1717

1818
use chainstate_storage::BlockchainStorageRead;
1919
use chainstate_types::{BlockIndex, GenBlockIndex, Locator, PropertyQueryError};
@@ -367,6 +367,19 @@ impl<'a, S: BlockchainStorageRead, V: TransactionVerificationStrategy> Chainstat
367367
}
368368
}
369369

370+
pub fn get_tokens_info_for_rpc(
371+
&self,
372+
token_ids: &BTreeSet<TokenId>,
373+
) -> Result<Vec<RPCTokenInfo>, PropertyQueryError> {
374+
token_ids
375+
.iter()
376+
.map(|id| -> Result<_, PropertyQueryError> {
377+
self.get_token_info_for_rpc(*id)?
378+
.ok_or(PropertyQueryError::TokenInfoMissing(*id))
379+
})
380+
.collect::<Result<_, _>>()
381+
}
382+
370383
pub fn get_token_aux_data(
371384
&self,
372385
token_id: &TokenId,
@@ -378,7 +391,7 @@ impl<'a, S: BlockchainStorageRead, V: TransactionVerificationStrategy> Chainstat
378391
&self,
379392
tx_id: &Id<Transaction>,
380393
) -> Result<Option<TokenId>, PropertyQueryError> {
381-
self.chainstate_ref.get_token_id(tx_id)
394+
self.chainstate_ref.get_token_id_from_issuance_tx(tx_id)
382395
}
383396

384397
pub fn get_mainchain_blocks_list(&self) -> Result<Vec<Id<Block>>, PropertyQueryError> {

chainstate/src/interface/chainstate_interface.rs

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,11 @@
1313
// See the License for the specific language governing permissions and
1414
// limitations under the License.
1515

16-
use std::{collections::BTreeMap, num::NonZeroUsize, sync::Arc};
16+
use std::{
17+
collections::{BTreeMap, BTreeSet},
18+
num::NonZeroUsize,
19+
sync::Arc,
20+
};
1721

1822
use crate::{
1923
detail::BlockSource, ChainInfo, ChainstateConfig, ChainstateError, ChainstateEvent,
@@ -206,14 +210,24 @@ pub trait ChainstateInterface: Send + Sync {
206210
&self,
207211
token_id: TokenId,
208212
) -> Result<Option<RPCTokenInfo>, ChainstateError>;
213+
214+
/// Return infos for the specified token ids.
215+
fn get_tokens_info_for_rpc(
216+
&self,
217+
token_ids: &BTreeSet<TokenId>,
218+
) -> Result<Vec<RPCTokenInfo>, ChainstateError>;
219+
220+
/// Return token's auxiliary data; available for NFTs only.
209221
fn get_token_aux_data(
210222
&self,
211223
token_id: TokenId,
212224
) -> Result<Option<TokenAuxiliaryData>, ChainstateError>;
225+
/// Obtain token id given the id of the issuing tx; available for NFTs only.
213226
fn get_token_id_from_issuance_tx(
214227
&self,
215228
tx_id: &Id<Transaction>,
216229
) -> Result<Option<TokenId>, ChainstateError>;
230+
/// Obtain token data given its id; available for fungible tokens only.
217231
fn get_token_data(
218232
&self,
219233
id: &TokenId,

chainstate/src/interface/chainstate_interface_impl.rs

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,11 @@
1313
// See the License for the specific language governing permissions and
1414
// limitations under the License.
1515

16-
use std::{collections::BTreeMap, num::NonZeroUsize, sync::Arc};
16+
use std::{
17+
collections::{BTreeMap, BTreeSet},
18+
num::NonZeroUsize,
19+
sync::Arc,
20+
};
1721

1822
use crate::{
1923
detail::{
@@ -523,6 +527,18 @@ where
523527
.map_err(ChainstateError::FailedToReadProperty)
524528
}
525529

530+
#[tracing::instrument(skip(self))]
531+
fn get_tokens_info_for_rpc(
532+
&self,
533+
token_ids: &BTreeSet<TokenId>,
534+
) -> Result<Vec<RPCTokenInfo>, ChainstateError> {
535+
self.chainstate
536+
.query()
537+
.map_err(ChainstateError::from)?
538+
.get_tokens_info_for_rpc(token_ids)
539+
.map_err(ChainstateError::FailedToReadProperty)
540+
}
541+
526542
#[tracing::instrument(skip_all, fields(token_id = %token_id))]
527543
fn get_token_aux_data(
528544
&self,

chainstate/src/interface/chainstate_interface_impl_delegation.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
// limitations under the License.
1515

1616
use std::{
17-
collections::BTreeMap,
17+
collections::{BTreeMap, BTreeSet},
1818
num::NonZeroUsize,
1919
ops::{Deref, DerefMut},
2020
sync::Arc,
@@ -276,6 +276,13 @@ where
276276
self.deref().get_token_info_for_rpc(token_id)
277277
}
278278

279+
fn get_tokens_info_for_rpc(
280+
&self,
281+
token_ids: &BTreeSet<TokenId>,
282+
) -> Result<Vec<RPCTokenInfo>, ChainstateError> {
283+
self.deref().get_tokens_info_for_rpc(token_ids)
284+
}
285+
279286
fn get_token_aux_data(
280287
&self,
281288
token_id: TokenId,

chainstate/src/rpc/mod.rs

Lines changed: 46 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -172,10 +172,14 @@ trait ChainstateRpc {
172172
delegation_address: String,
173173
) -> RpcResult<Option<Amount>>;
174174

175-
/// Get token information, given a token id, in address form.
175+
/// Get token information, given a token id in address form.
176176
#[method(name = "token_info")]
177177
async fn token_info(&self, token_id: String) -> RpcResult<Option<RPCTokenInfo>>;
178178

179+
/// Get tokens information, given multiple token ids in address form.
180+
#[method(name = "tokens_info")]
181+
async fn tokens_info(&self, token_ids: Vec<String>) -> RpcResult<Vec<RPCTokenInfo>>;
182+
179183
/// Get order information, given an order id, in address form.
180184
#[method(name = "order_info")]
181185
async fn order_info(&self, order_id: String) -> RpcResult<Option<RpcOrderInfo>>;
@@ -243,19 +247,22 @@ impl ChainstateRpcServer for super::ChainstateHandle {
243247

244248
if let Some((block, block_index)) = both {
245249
let token_ids = collect_token_v1_ids_from_output_values_holder(&block);
246-
let mut token_decimals = BTreeMap::new();
247-
248-
// TODO replace this loop with a single ChainstateInterface function call obtaining
249-
// all infos at once (when the function is implemented).
250-
for token_id in token_ids {
251-
let token_info: RPCTokenInfo = rpc::handle_result(
252-
self.call(move |this| get_existing_token_info_for_rpc(this, token_id)).await,
253-
)?;
254-
token_decimals.insert(
255-
token_id,
256-
TokenDecimals(token_info.token_number_of_decimals()),
257-
);
258-
}
250+
let token_decimals: BTreeMap<TokenId, TokenDecimals> = rpc::handle_result(
251+
self.call(move |this| -> Result<_, ChainstateError> {
252+
let infos = this.get_tokens_info_for_rpc(&token_ids)?;
253+
let decimals = infos
254+
.iter()
255+
.map(|info| {
256+
(
257+
info.token_id(),
258+
TokenDecimals(info.token_number_of_decimals()),
259+
)
260+
})
261+
.collect::<BTreeMap<_, _>>();
262+
Ok(decimals)
263+
})
264+
.await,
265+
)?;
259266

260267
let rpc_block: RpcBlock = rpc::handle_result(RpcBlock::new(
261268
&chain_config,
@@ -438,6 +445,27 @@ impl ChainstateRpcServer for super::ChainstateHandle {
438445
)
439446
}
440447

448+
async fn tokens_info(&self, token_ids: Vec<String>) -> RpcResult<Vec<RPCTokenInfo>> {
449+
rpc::handle_result(
450+
self.call(move |this| -> Result<_, DynamizedError> {
451+
let chain_config = this.get_chain_config();
452+
453+
let token_ids = token_ids
454+
.into_iter()
455+
.map(|token_id| -> Result<_, DynamizedError> {
456+
Ok(
457+
dynamize_err(Address::<TokenId>::from_string(chain_config, token_id))?
458+
.into_object(),
459+
)
460+
})
461+
.collect::<Result<_, _>>()?;
462+
463+
dynamize_err(this.get_tokens_info_for_rpc(&token_ids))
464+
})
465+
.await,
466+
)
467+
}
468+
441469
async fn order_info(&self, order_id: String) -> RpcResult<Option<RpcOrderInfo>> {
442470
rpc::handle_result(
443471
self.call(move |this| {
@@ -488,33 +516,15 @@ impl ChainstateRpcServer for super::ChainstateHandle {
488516
}
489517
}
490518

491-
fn dynamize_err<T, E: std::error::Error + Send + Sync>(
492-
o: Result<T, E>,
493-
) -> Result<T, Box<dyn std::error::Error + Send + Sync>>
519+
type DynamizedError = Box<dyn std::error::Error + Send + Sync>;
520+
521+
fn dynamize_err<T, E: std::error::Error + Send + Sync>(o: Result<T, E>) -> Result<T, DynamizedError>
494522
where
495-
Box<dyn std::error::Error + Send + Sync>: From<E>,
523+
DynamizedError: From<E>,
496524
{
497525
o.map_err(Into::into)
498526
}
499527

500-
fn get_existing_token_info_for_rpc(
501-
chainstate: &(impl ChainstateInterface + ?Sized),
502-
token_id: TokenId,
503-
) -> Result<RPCTokenInfo, LocalRpcError> {
504-
chainstate
505-
.get_token_info_for_rpc(token_id)?
506-
.ok_or(LocalRpcError::MissingTokenInfo(token_id))
507-
}
508-
509-
#[derive(thiserror::Error, Debug, PartialEq, Eq)]
510-
enum LocalRpcError {
511-
#[error("Token info missing for token {0:x}")]
512-
MissingTokenInfo(TokenId),
513-
514-
#[error(transparent)]
515-
ChainstateError(#[from] ChainstateError),
516-
}
517-
518528
#[cfg(test)]
519529
mod test {
520530
use super::*;

chainstate/test-framework/src/helpers.rs

Lines changed: 60 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ pub fn issue_and_mint_random_token_from_best_block(
6464
TokenIssuance::V1(issuance)
6565
};
6666

67-
let (token_id, _, utxo_with_change) =
67+
let (token_id, _, _, utxo_with_change) =
6868
issue_token_from_block(rng, tf, best_block_id, utxo_to_pay_fee, issuance);
6969

7070
let best_block_id = tf.best_block_id();
@@ -91,7 +91,12 @@ pub fn issue_token_from_block(
9191
parent_block_id: Id<GenBlock>,
9292
utxo_to_pay_fee: UtxoOutPoint,
9393
issuance: TokenIssuance,
94-
) -> (TokenId, Id<Block>, UtxoOutPoint) {
94+
) -> (
95+
TokenId,
96+
/*issuance_block_id*/ Id<Block>,
97+
/*issuance_tx*/ Transaction,
98+
/*change_outpoint*/ UtxoOutPoint,
99+
) {
95100
let token_issuance_fee = tf.chainstate.get_chain_config().fungible_token_issuance_fee();
96101

97102
let fee_utxo_coins =
@@ -118,13 +123,64 @@ pub fn issue_token_from_block(
118123
let tx_id = tx.transaction().get_id();
119124
let block = tf
120125
.make_block_builder()
121-
.add_transaction(tx)
126+
.add_transaction(tx.clone())
122127
.with_parent(parent_block_id)
123128
.build(rng);
124129
let block_id = block.get_id();
125130
tf.process_block(block, BlockSource::Local).unwrap();
126131

127-
(token_id, block_id, UtxoOutPoint::new(tx_id.into(), 0))
132+
(
133+
token_id,
134+
block_id,
135+
tx.take_transaction(),
136+
UtxoOutPoint::new(tx_id.into(), 0),
137+
)
138+
}
139+
140+
pub fn make_token_issuance(
141+
rng: &mut impl Rng,
142+
supply: TokenTotalSupply,
143+
freezable: IsTokenFreezable,
144+
) -> TokenIssuance {
145+
TokenIssuance::V1(TokenIssuanceV1 {
146+
token_ticker: random_ascii_alphanumeric_string(rng, 1..5).as_bytes().to_vec(),
147+
number_of_decimals: rng.gen_range(1..18),
148+
metadata_uri: random_ascii_alphanumeric_string(rng, 1..1024).as_bytes().to_vec(),
149+
total_supply: supply,
150+
authority: Destination::AnyoneCanSpend,
151+
is_freezable: freezable,
152+
})
153+
}
154+
155+
pub fn issue_token_from_genesis(
156+
rng: &mut (impl Rng + CryptoRng),
157+
tf: &mut TestFramework,
158+
supply: TokenTotalSupply,
159+
freezable: IsTokenFreezable,
160+
) -> (
161+
TokenId,
162+
/*issuance_block_id*/ Id<Block>,
163+
/*issuance_tx*/ Transaction,
164+
TokenIssuance,
165+
/*change_outpoint*/ UtxoOutPoint,
166+
) {
167+
let utxo_input_outpoint = UtxoOutPoint::new(tf.best_block_id().into(), 0);
168+
let issuance = make_token_issuance(rng, supply, freezable);
169+
let (token_id, issuance_block_id, issuance_tx, change_outpoint) = issue_token_from_block(
170+
rng,
171+
tf,
172+
tf.genesis().get_id().into(),
173+
utxo_input_outpoint,
174+
issuance.clone(),
175+
);
176+
177+
(
178+
token_id,
179+
issuance_block_id,
180+
issuance_tx,
181+
issuance,
182+
change_outpoint,
183+
)
128184
}
129185

130186
pub fn mint_tokens_in_block(

0 commit comments

Comments
 (0)