concordium_rust_sdk/v2/
mod.rs

1//! This module exposes [Client] which is a wrapper around the
2//! generated gRPC rust client, providing a more ergonomic interface than the
3//! generated client. See [Client] for documentation of how to use.
4use crate::{
5    endpoints,
6    id::{self, types::AccountCredentialMessage},
7    types::{
8        self, block_certificates,
9        hashes::{self, BlockHash, TransactionHash, TransactionSignHash},
10        queries::ConsensusDetailedStatus,
11        smart_contracts::{
12            ContractContext, InstanceInfo, InvokeContractResult, ModuleReference, WasmModule,
13        },
14        transactions::{self, InitContractPayload, UpdateContractPayload, UpdateInstruction},
15        AbsoluteBlockHeight, AccountInfo, AccountPending, BlockItemSummary,
16        CredentialRegistrationID, Energy, Memo, Nonce, RegisteredData, SpecialTransactionOutcome,
17        TransactionStatus, UpdateSequenceNumber,
18    },
19};
20use anyhow::Context;
21use concordium_base::{
22    base::{
23        AccountIndex, BlockHeight, ChainParameterVersion0, ChainParameterVersion1,
24        CredentialsPerBlockLimit, ElectionDifficulty, Epoch, ExchangeRate, GenesisIndex,
25        MintDistributionV0, MintDistributionV1,
26    },
27    common::{
28        self,
29        types::{TransactionSignature, TransactionTime},
30    },
31    contracts_common::{
32        AccountAddress, AccountAddressParseError, Amount, ContractAddress, Duration,
33        OwnedContractName, OwnedParameter, OwnedReceiveName, ReceiveName,
34    },
35    hashes::HashFromStrError,
36    transactions::{BlockItem, EncodedPayload, PayloadLike},
37    updates::{
38        AuthorizationsV0, CooldownParameters, FinalizationCommitteeParameters, GASRewards,
39        GASRewardsV1, PoolParameters, TimeParameters, TimeoutParameters,
40        TransactionFeeDistribution, ValidatorScoreParameters,
41    },
42};
43pub use endpoints::{QueryError, QueryResult, RPCError, RPCResult};
44use futures::{Stream, StreamExt, TryStreamExt};
45pub use http::uri::Scheme;
46use num::{BigUint, ToPrimitive};
47use std::{collections::HashMap, num::ParseIntError, str::FromStr};
48use tonic::IntoRequest;
49pub use tonic::{
50    transport::{Endpoint, Error},
51    Code, Status,
52};
53
54use self::dry_run::WithRemainingQuota;
55
56mod conversions;
57pub mod dry_run;
58#[path = "generated/concordium.v2.rs"]
59#[allow(
60    clippy::large_enum_variant,
61    clippy::enum_variant_names,
62    clippy::derive_partial_eq_without_eq
63)]
64mod generated;
65pub mod proto_schema_version;
66
67/// A client for gRPC API v2 of the Concordium node. Can be used to control the
68/// node, send transactions and query information about the node and the state
69/// of the chain.
70///
71/// # Connecting to a Concordium node
72///
73/// Creates a new client connection to a Concordium node.
74/// Make sure to have access to the gRPC API v2 endpoint of a running node.
75///
76/// ```no_run
77/// # tokio_test::block_on(async {
78/// use concordium_rust_sdk::v2::{Client, Endpoint};
79/// use std::str::FromStr;
80///
81/// // Assumes the node is running locally and gRPC API v2 can be accessed on port 20001.
82/// let node_endpoint = Endpoint::from_str("http://localhost:20001")?;
83/// let mut client = Client::new(node_endpoint).await?;
84///
85/// // Verify the connection to the node by printing node information.
86/// let node_info = client.get_node_info().await?;
87/// println!("{:#?}", node_info);
88/// # Ok::<(), anyhow::Error>(())
89/// # });
90/// ```
91///
92/// # Concurrent use of the client
93///
94/// All endpoints take a `&mut self` as an argument which means that a single
95/// instance cannot be used concurrently. However instead of putting the Client
96/// behind a Mutex, the intended way to use it is to clone it. Cloning is very
97/// cheap and will reuse the underlying connection.
98#[derive(Clone, Debug)]
99pub struct Client {
100    client: generated::queries_client::QueriesClient<tonic::transport::Channel>,
101}
102
103/// A query response with the addition of the block hash used by the query.
104/// The block hash used for querying might be unknown when providing the block
105/// as [BlockIdentifier::Best] or [BlockIdentifier::LastFinal].
106#[derive(Clone, Copy, Debug)]
107pub struct QueryResponse<A> {
108    /// Block hash for which the query applies.
109    pub block_hash: BlockHash,
110    /// The result of the query.
111    pub response:   A,
112}
113
114impl<A> AsRef<A> for QueryResponse<A> {
115    fn as_ref(&self) -> &A { &self.response }
116}
117
118/// A block identifier used in queries.
119#[derive(Copy, Clone, Debug, derive_more::From, PartialEq, Eq)]
120pub enum BlockIdentifier {
121    /// Query in the context of the best block.
122    Best,
123    /// Query in the context of the last finalized block at the time of the
124    /// query.
125    LastFinal,
126    /// Query in the context of a specific block hash.
127    Given(BlockHash),
128    /// Query for a block at absolute height. If a unique
129    /// block can not be identified at that height the query will return
130    /// `NotFound`.
131    AbsoluteHeight(AbsoluteBlockHeight),
132    /// Query for a block at a height relative to genesis index. If a unique
133    /// block can not be identified at that height the query will return
134    /// `NotFound`.
135    RelativeHeight(RelativeBlockHeight),
136}
137
138#[derive(Debug, thiserror::Error)]
139pub enum BlockIdentifierFromStrError {
140    #[error("The input is not recognized.")]
141    InvalidFormat,
142    #[error("The input is not a valid hash: {0}.")]
143    InvalidHash(#[from] HashFromStrError),
144    #[error("The input is not a valid unsigned integer: {0}.")]
145    InvalidInteger(#[from] ParseIntError),
146}
147
148/// Display implementation to match the [`FromStr`] implementation defined just
149/// below.
150impl std::fmt::Display for BlockIdentifier {
151    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
152        match self {
153            BlockIdentifier::Best => "best".fmt(f),
154            BlockIdentifier::LastFinal => "lastfinal".fmt(f),
155            BlockIdentifier::Given(bh) => bh.fmt(f),
156            BlockIdentifier::AbsoluteHeight(h) => write!(f, "@{h}"),
157            BlockIdentifier::RelativeHeight(rh) => {
158                write!(
159                    f,
160                    "@{}/{}{}",
161                    rh.height,
162                    rh.genesis_index,
163                    if rh.restrict { "!" } else { "" }
164                )
165            }
166        }
167    }
168}
169
170/// Parse a string as a [`BlockIdentifier`]. The format is one of the following
171///
172/// - the string `best` for [`Best`](BlockIdentifier::Best)
173/// - the string `lastFinal` or `lastfinal` for
174///   [`LastFinal`](BlockIdentifier::LastFinal)
175/// - a valid block hash for [`Given`](BlockIdentifier::Given)
176/// - a string starting with `@` followed by an integer and nothing else for
177///   [`AbsoluteHeight`](BlockIdentifier::AbsoluteHeight)
178/// - a string in the format `@123/3` optionally followed by `!` where `123` is
179///   the block height and `3` is the genesis index for
180///   [`RelativeHeight`](BlockIdentifier::RelativeHeight). If `!` is present
181///   then `restrict` is set to `true`.
182impl std::str::FromStr for BlockIdentifier {
183    type Err = BlockIdentifierFromStrError;
184
185    fn from_str(s: &str) -> Result<Self, Self::Err> {
186        match s {
187            "best" => Ok(Self::Best),
188            "lastFinal" => Ok(Self::LastFinal),
189            "lastfinal" => Ok(Self::LastFinal),
190            _ => {
191                if let Some(rest) = s.strip_prefix('@') {
192                    if let Some((height_str, gen_idx_str)) = rest.split_once('/') {
193                        let height = BlockHeight::from_str(height_str)?;
194                        if let Some(gen_idx) = gen_idx_str.strip_suffix('!') {
195                            let genesis_index = GenesisIndex::from_str(gen_idx)?;
196                            Ok(Self::RelativeHeight(RelativeBlockHeight {
197                                genesis_index,
198                                height,
199                                restrict: true,
200                            }))
201                        } else {
202                            let genesis_index = GenesisIndex::from_str(gen_idx_str)?;
203                            Ok(Self::RelativeHeight(RelativeBlockHeight {
204                                genesis_index,
205                                height,
206                                restrict: false,
207                            }))
208                        }
209                    } else {
210                        let h = AbsoluteBlockHeight::from_str(rest)?;
211                        Ok(Self::AbsoluteHeight(h))
212                    }
213                } else {
214                    let h = BlockHash::from_str(s)?;
215                    Ok(Self::Given(h))
216                }
217            }
218        }
219    }
220}
221
222/// Block height relative to an explicit genesis index.
223#[derive(Copy, Clone, Debug, PartialEq, Eq)]
224pub struct RelativeBlockHeight {
225    /// Genesis index to start from.
226    pub genesis_index: types::GenesisIndex,
227    /// Height starting from the genesis block at the genesis index.
228    pub height:        types::BlockHeight,
229    /// Whether to return results only from the specified genesis index
230    /// (`true`), or allow results from more recent genesis indices
231    /// as well (`false`).
232    pub restrict:      bool,
233}
234
235/// An account identifier used in queries.
236#[derive(Copy, Clone, Debug, derive_more::From, derive_more::Display)]
237pub enum AccountIdentifier {
238    /// Identify an account by an address.
239    #[display(fmt = "{_0}")]
240    Address(AccountAddress),
241    /// Identify an account by the credential registration id.
242    #[display(fmt = "{_0}")]
243    CredId(CredentialRegistrationID),
244    /// Identify an account by its account index.
245    #[display(fmt = "{_0}")]
246    Index(crate::types::AccountIndex),
247}
248
249impl FromStr for AccountIdentifier {
250    type Err = AccountAddressParseError;
251
252    fn from_str(s: &str) -> Result<Self, Self::Err> {
253        if let Ok(ai) = s.parse::<crate::types::AccountIndex>() {
254            return Ok(Self::Index(ai));
255        }
256        if let Ok(cid) = s.parse::<CredentialRegistrationID>() {
257            return Ok(Self::CredId(cid));
258        }
259        s.parse().map(Self::Address)
260    }
261}
262
263/// Identifier for an [`Epoch`] relative to the specified genesis index.
264#[derive(Debug, Copy, Clone)]
265pub struct SpecifiedEpoch {
266    /// Genesis index to query in.
267    pub genesis_index: types::GenesisIndex,
268    /// The epoch of the genesis to query.
269    pub epoch:         types::Epoch,
270}
271
272/// An identifier of an epoch used in queries.
273#[derive(Copy, Clone, Debug, derive_more::From)]
274pub enum EpochIdentifier {
275    /// A specified epoch to query.
276    Specified(SpecifiedEpoch),
277    /// Query the epoch of the block.
278    Block(BlockIdentifier),
279}
280
281/// Errors that may occur as a result of
282/// parsing a [`EpochIdentifier`] from a string via
283/// [from_str(&str)][std::str::FromStr].
284#[derive(Debug, thiserror::Error)]
285pub enum EpochIdentifierFromStrError {
286    #[error("The input is not recognized.")]
287    InvalidFormat,
288    #[error("The genesis index is not a valid unsigned integer")]
289    InvalidGenesis,
290    #[error("The epoch index is not a valid unsigned integer")]
291    InvalidEpoch,
292    #[error("The input is not a valid block identifier: {0}.")]
293    InvalidBlockIdentifier(#[from] BlockIdentifierFromStrError),
294}
295
296/// Parse a string as an [`EpochIdentifier`]. The format is one of the
297/// following:
298///
299/// - a string starting with `%` followed by two integers separated by `,` for
300///   [`Specified`](EpochIdentifier::Specified). First component is treated as
301///   the genesis index and the second component as the epoch.
302/// - a string starting with `@` followed by a [`BlockIdentifier`] for
303///   [`Block`](EpochIdentifier::Block).
304impl std::str::FromStr for EpochIdentifier {
305    type Err = EpochIdentifierFromStrError;
306
307    fn from_str(s: &str) -> Result<Self, Self::Err> {
308        if let Some(rest) = s.strip_prefix('%') {
309            if let Some((gen_idx_str, epoch_str)) = rest.split_once(',') {
310                let genesis_index = GenesisIndex::from_str(gen_idx_str)
311                    .map_err(|_| EpochIdentifierFromStrError::InvalidGenesis)?;
312                let epoch = Epoch::from_str(epoch_str)
313                    .map_err(|_| EpochIdentifierFromStrError::InvalidEpoch)?;
314                Ok(Self::Specified(SpecifiedEpoch {
315                    genesis_index,
316                    epoch,
317                }))
318            } else {
319                Err(EpochIdentifierFromStrError::InvalidFormat)
320            }
321        } else {
322            Ok(Self::Block(BlockIdentifier::from_str(s)?))
323        }
324    }
325}
326
327impl IntoRequest<generated::EpochRequest> for &EpochIdentifier {
328    fn into_request(self) -> tonic::Request<generated::EpochRequest> {
329        tonic::Request::new((*self).into())
330    }
331}
332
333impl From<EpochIdentifier> for generated::EpochRequest {
334    fn from(ei: EpochIdentifier) -> Self {
335        match ei {
336            EpochIdentifier::Specified(SpecifiedEpoch {
337                genesis_index,
338                epoch,
339            }) => generated::EpochRequest {
340                epoch_request_input: Some(
341                    generated::epoch_request::EpochRequestInput::RelativeEpoch(
342                        generated::epoch_request::RelativeEpoch {
343                            genesis_index: Some(generated::GenesisIndex {
344                                value: genesis_index.height,
345                            }),
346                            epoch:         Some(generated::Epoch { value: epoch.epoch }),
347                        },
348                    ),
349                ),
350            },
351            EpochIdentifier::Block(bi) => generated::EpochRequest {
352                epoch_request_input: Some(generated::epoch_request::EpochRequestInput::BlockHash(
353                    (&bi).into(),
354                )),
355            },
356        }
357    }
358}
359
360/// Information of a finalized block.
361#[derive(Copy, Clone, Debug)]
362pub struct FinalizedBlockInfo {
363    /// The block hash for the finalized block.
364    pub block_hash: BlockHash,
365    /// The absolute block height for the finalized block.
366    pub height:     AbsoluteBlockHeight,
367}
368
369#[derive(Debug, Clone)]
370/// Values of chain parameters that can be updated via chain updates.
371/// This applies to protocol version 1-3.
372pub struct ChainParametersV0 {
373    /// Election difficulty for consensus lottery.
374    pub election_difficulty: ElectionDifficulty,
375    /// Euro per energy exchange rate.
376    pub euro_per_energy: ExchangeRate,
377    /// Micro ccd per euro exchange rate.
378    pub micro_ccd_per_euro: ExchangeRate,
379    /// Extra number of epochs before reduction in stake, or baker
380    /// deregistration is completed.
381    pub baker_cooldown_epochs: Epoch,
382    /// The limit for the number of account creations in a block.
383    pub account_creation_limit: CredentialsPerBlockLimit,
384    /// Parameters related to the distribution of newly minted CCD.
385    pub mint_distribution: MintDistributionV0,
386    /// Parameters related to the distribution of transaction fees.
387    pub transaction_fee_distribution: TransactionFeeDistribution,
388    /// Parameters related to the distribution of the GAS account.
389    pub gas_rewards: GASRewards,
390    /// Address of the foundation account.
391    pub foundation_account: AccountAddress,
392    /// Minimum threshold for becoming a baker.
393    pub minimum_threshold_for_baking: Amount,
394    /// Keys allowed to do updates.
395    pub keys: types::UpdateKeysCollection<ChainParameterVersion0>,
396}
397
398#[derive(Debug, Clone)]
399/// Values of chain parameters that can be updated via chain updates.
400/// This applies to protocol version 4 and 5.
401pub struct ChainParametersV1 {
402    /// Election difficulty for consensus lottery.
403    pub election_difficulty: ElectionDifficulty,
404    /// Euro per energy exchange rate.
405    pub euro_per_energy: ExchangeRate,
406    /// Micro ccd per euro exchange rate.
407    pub micro_ccd_per_euro: ExchangeRate,
408    pub cooldown_parameters: CooldownParameters,
409    pub time_parameters: TimeParameters,
410    /// The limit for the number of account creations in a block.
411    pub account_creation_limit: CredentialsPerBlockLimit,
412    /// Parameters related to the distribution of newly minted CCD.
413    pub mint_distribution: MintDistributionV1,
414    /// Parameters related to the distribution of transaction fees.
415    pub transaction_fee_distribution: TransactionFeeDistribution,
416    /// Parameters related to the distribution of the GAS account.
417    pub gas_rewards: GASRewards,
418    /// Address of the foundation account.
419    pub foundation_account: AccountAddress,
420    /// Parameters for baker pools.
421    pub pool_parameters: PoolParameters,
422    /// Keys allowed to do updates.
423    pub keys: types::UpdateKeysCollection<ChainParameterVersion1>,
424}
425
426#[derive(Debug, Clone)]
427/// Values of chain parameters that can be updated via chain updates.
428/// This applies to protocol version 6 and 7.
429pub struct ChainParametersV2 {
430    /// Consensus protocol version 2 timeout parameters.
431    pub timeout_parameters: TimeoutParameters,
432    /// Minimum time interval between blocks.
433    pub min_block_time: Duration,
434    /// Maximum energy allowed per block.
435    pub block_energy_limit: Energy,
436    /// Euro per energy exchange rate.
437    pub euro_per_energy: ExchangeRate,
438    /// Micro ccd per euro exchange rate.
439    pub micro_ccd_per_euro: ExchangeRate,
440    /// Parameters related to cooldowns when staking.
441    pub cooldown_parameters: CooldownParameters,
442    /// Parameters related mint rate and reward period.
443    pub time_parameters: TimeParameters,
444    /// The limit for the number of account creations in a block.
445    pub account_creation_limit: CredentialsPerBlockLimit,
446    /// Parameters related to the distribution of newly minted CCD.
447    pub mint_distribution: MintDistributionV1,
448    /// Parameters related to the distribution of transaction fees.
449    pub transaction_fee_distribution: TransactionFeeDistribution,
450    /// Parameters related to the distribution from the GAS account.
451    pub gas_rewards: GASRewardsV1,
452    /// Address of the foundation account.
453    pub foundation_account: AccountAddress,
454    /// Parameters for baker pools.
455    pub pool_parameters: PoolParameters,
456    /// The finalization committee parameters.
457    pub finalization_committee_parameters: FinalizationCommitteeParameters,
458    /// Keys allowed to do updates.
459    pub keys: types::UpdateKeysCollection<ChainParameterVersion1>,
460}
461
462#[derive(Debug, Clone)]
463/// Values of chain parameters that can be updated via chain updates.
464/// This applies to protocol version 8 and up.
465pub struct ChainParametersV3 {
466    /// Consensus protocol version 2 timeout parameters.
467    pub timeout_parameters: TimeoutParameters,
468    /// Minimum time interval between blocks.
469    pub min_block_time: Duration,
470    /// Maximum energy allowed per block.
471    pub block_energy_limit: Energy,
472    /// Euro per energy exchange rate.
473    pub euro_per_energy: ExchangeRate,
474    /// Micro ccd per euro exchange rate.
475    pub micro_ccd_per_euro: ExchangeRate,
476    /// Parameters related to cooldowns when staking.
477    pub cooldown_parameters: CooldownParameters,
478    /// Parameters related mint rate and reward period.
479    pub time_parameters: TimeParameters,
480    /// The limit for the number of account creations in a block.
481    pub account_creation_limit: CredentialsPerBlockLimit,
482    /// Parameters related to the distribution of newly minted CCD.
483    pub mint_distribution: MintDistributionV1,
484    /// Parameters related to the distribution of transaction fees.
485    pub transaction_fee_distribution: TransactionFeeDistribution,
486    /// Parameters related to the distribution from the GAS account.
487    pub gas_rewards: GASRewardsV1,
488    /// Address of the foundation account.
489    pub foundation_account: AccountAddress,
490    /// Parameters for baker pools.
491    pub pool_parameters: PoolParameters,
492    /// The finalization committee parameters.
493    pub finalization_committee_parameters: FinalizationCommitteeParameters,
494    /// Validator score parameters.
495    pub validator_score_parameters: ValidatorScoreParameters,
496    /// Keys allowed to do updates.
497    pub keys: types::UpdateKeysCollection<ChainParameterVersion1>,
498}
499
500/// Chain parameters. See [`ChainParametersV0`] and [`ChainParametersV1`] for
501/// details. `V0` parameters apply to protocol version `1..=3`, and `V1`
502/// parameters apply to protocol versions `4` and up.
503#[derive(Debug, Clone)]
504pub enum ChainParameters {
505    V0(ChainParametersV0),
506    V1(ChainParametersV1),
507    V2(ChainParametersV2),
508    V3(ChainParametersV3),
509}
510
511impl ChainParameters {
512    /// Get the keys for parameter updates that are common to all versions.
513    pub fn common_update_keys(&self) -> &AuthorizationsV0 {
514        match self {
515            Self::V0(data) => &data.keys.level_2_keys,
516            Self::V1(data) => &data.keys.level_2_keys.v0,
517            Self::V2(data) => &data.keys.level_2_keys.v0,
518            Self::V3(data) => &data.keys.level_2_keys.v0,
519        }
520    }
521}
522
523impl ChainParameters {
524    /// Compute the exchange rate between `microCCD` and `NRG`.
525    pub fn micro_ccd_per_energy(&self) -> num::rational::Ratio<u128> {
526        let (num, denom) = match self {
527            ChainParameters::V0(v0) => {
528                let x = v0.micro_ccd_per_euro;
529                let y = v0.euro_per_energy;
530                (
531                    u128::from(x.numerator()) * u128::from(y.numerator()),
532                    u128::from(x.denominator()) * u128::from(y.denominator()),
533                )
534            }
535            ChainParameters::V1(v1) => {
536                let x = v1.micro_ccd_per_euro;
537                let y = v1.euro_per_energy;
538                (
539                    u128::from(x.numerator()) * u128::from(y.numerator()),
540                    u128::from(x.denominator()) * u128::from(y.denominator()),
541                )
542            }
543            ChainParameters::V2(v2) => {
544                let x = v2.micro_ccd_per_euro;
545                let y = v2.euro_per_energy;
546                (
547                    u128::from(x.numerator()) * u128::from(y.numerator()),
548                    u128::from(x.denominator()) * u128::from(y.denominator()),
549                )
550            }
551            ChainParameters::V3(v3) => {
552                let x = v3.micro_ccd_per_euro;
553                let y = v3.euro_per_energy;
554                (
555                    u128::from(x.numerator()) * u128::from(y.numerator()),
556                    u128::from(x.denominator()) * u128::from(y.denominator()),
557                )
558            }
559        };
560        num::rational::Ratio::new(num, denom)
561    }
562
563    /// Get the CCD cost of the given amount of energy for the current chain
564    /// parameters.
565    pub fn ccd_cost(&self, nrg: Energy) -> Amount {
566        let ratio = self.micro_ccd_per_energy();
567        let numer = BigUint::from(*ratio.numer()) * nrg.energy;
568        let denomer = BigUint::from(*ratio.denom());
569        let cost = num::rational::Ratio::new(numer, denomer);
570        let i = cost.ceil().to_integer();
571        // The next line should be a no-op when the values are coming from the chain
572        // since the amount should always fit a u64. With a 3_000_000 maximum
573        // NRG limit the exchange rate would have to be more than 6148914691236
574        // microCCD per NRG (6148914 CCD per NRG) for this to occur. But even in that
575        // case this behaviour here matches the node behaviour.
576        let micro = i % u64::MAX;
577        Amount::from_micro_ccd(micro.to_u64().expect("Value is known to be under u64::MAX"))
578    }
579
580    /// The foundation account that gets the foundation tax.
581    pub fn foundation_account(&self) -> AccountAddress {
582        match self {
583            ChainParameters::V0(v0) => v0.foundation_account,
584            ChainParameters::V1(v1) => v1.foundation_account,
585            ChainParameters::V2(v2) => v2.foundation_account,
586            ChainParameters::V3(v3) => v3.foundation_account,
587        }
588    }
589}
590
591impl From<&BlockIdentifier> for generated::BlockHashInput {
592    fn from(bi: &BlockIdentifier) -> Self {
593        let block_hash_input = match bi {
594            BlockIdentifier::Best => {
595                generated::block_hash_input::BlockHashInput::Best(Default::default())
596            }
597            BlockIdentifier::LastFinal => {
598                generated::block_hash_input::BlockHashInput::LastFinal(Default::default())
599            }
600            BlockIdentifier::Given(h) => {
601                generated::block_hash_input::BlockHashInput::Given(generated::BlockHash {
602                    value: h.as_ref().to_vec(),
603                })
604            }
605            &BlockIdentifier::AbsoluteHeight(h) => {
606                generated::block_hash_input::BlockHashInput::AbsoluteHeight(h.into())
607            }
608            &BlockIdentifier::RelativeHeight(h) => {
609                generated::block_hash_input::BlockHashInput::RelativeHeight(h.into())
610            }
611        };
612        generated::BlockHashInput {
613            block_hash_input: Some(block_hash_input),
614        }
615    }
616}
617
618impl IntoRequest<generated::BlockHashInput> for &BlockIdentifier {
619    fn into_request(self) -> tonic::Request<generated::BlockHashInput> {
620        tonic::Request::new(self.into())
621    }
622}
623
624impl From<&AccountAddress> for generated::AccountAddress {
625    fn from(addr: &AccountAddress) -> Self {
626        generated::AccountAddress {
627            value: concordium_base::common::to_bytes(addr),
628        }
629    }
630}
631
632impl From<AccountAddress> for generated::AccountAddress {
633    fn from(addr: AccountAddress) -> Self {
634        generated::AccountAddress {
635            value: common::to_bytes(&addr),
636        }
637    }
638}
639
640impl From<&super::types::Address> for generated::Address {
641    fn from(addr: &super::types::Address) -> Self {
642        let ty = match addr {
643            super::types::Address::Account(account) => {
644                generated::address::Type::Account(account.into())
645            }
646            super::types::Address::Contract(contract) => {
647                generated::address::Type::Contract(contract.into())
648            }
649        };
650        generated::Address { r#type: Some(ty) }
651    }
652}
653
654impl From<&Memo> for generated::Memo {
655    fn from(v: &Memo) -> Self {
656        Self {
657            value: v.as_ref().clone(),
658        }
659    }
660}
661
662impl<'a> From<ReceiveName<'a>> for generated::ReceiveName {
663    fn from(a: ReceiveName<'a>) -> Self {
664        generated::ReceiveName {
665            value: a.get_chain_name().to_string(),
666        }
667    }
668}
669
670impl From<&RegisteredData> for generated::RegisteredData {
671    fn from(v: &RegisteredData) -> Self {
672        Self {
673            value: v.as_ref().clone(),
674        }
675    }
676}
677impl From<&[u8]> for generated::Parameter {
678    fn from(a: &[u8]) -> Self { generated::Parameter { value: a.to_vec() } }
679}
680
681impl From<&TransactionHash> for generated::TransactionHash {
682    fn from(th: &TransactionHash) -> Self { generated::TransactionHash { value: th.to_vec() } }
683}
684
685impl From<&AccountIdentifier> for generated::AccountIdentifierInput {
686    fn from(ai: &AccountIdentifier) -> Self {
687        let account_identifier_input = match ai {
688            AccountIdentifier::Address(addr) => {
689                generated::account_identifier_input::AccountIdentifierInput::Address(addr.into())
690            }
691            AccountIdentifier::CredId(credid) => {
692                let credid = generated::CredentialRegistrationId {
693                    value: concordium_base::common::to_bytes(credid),
694                };
695                generated::account_identifier_input::AccountIdentifierInput::CredId(credid)
696            }
697            AccountIdentifier::Index(index) => {
698                generated::account_identifier_input::AccountIdentifierInput::AccountIndex(
699                    (*index).into(),
700                )
701            }
702        };
703        generated::AccountIdentifierInput {
704            account_identifier_input: Some(account_identifier_input),
705        }
706    }
707}
708
709impl From<&ModuleReference> for generated::ModuleRef {
710    fn from(mr: &ModuleReference) -> Self { Self { value: mr.to_vec() } }
711}
712
713impl From<ModuleReference> for generated::ModuleRef {
714    fn from(mr: ModuleReference) -> Self { Self { value: mr.to_vec() } }
715}
716
717impl From<&WasmModule> for generated::VersionedModuleSource {
718    fn from(v: &WasmModule) -> Self {
719        Self {
720            module: Some(match v.version {
721                types::smart_contracts::WasmVersion::V0 => {
722                    generated::versioned_module_source::Module::V0(
723                        generated::versioned_module_source::ModuleSourceV0 {
724                            value: v.source.as_ref().clone(),
725                        },
726                    )
727                }
728                types::smart_contracts::WasmVersion::V1 => {
729                    generated::versioned_module_source::Module::V1(
730                        generated::versioned_module_source::ModuleSourceV1 {
731                            value: v.source.as_ref().clone(),
732                        },
733                    )
734                }
735            }),
736        }
737    }
738}
739
740impl From<&OwnedContractName> for generated::InitName {
741    fn from(v: &OwnedContractName) -> Self {
742        Self {
743            value: v.as_contract_name().get_chain_name().to_string(),
744        }
745    }
746}
747
748impl From<&OwnedReceiveName> for generated::ReceiveName {
749    fn from(v: &OwnedReceiveName) -> Self {
750        Self {
751            value: v.as_receive_name().get_chain_name().to_string(),
752        }
753    }
754}
755
756impl From<&OwnedParameter> for generated::Parameter {
757    fn from(v: &OwnedParameter) -> Self {
758        Self {
759            value: v.as_ref().to_vec(),
760        }
761    }
762}
763
764impl From<&InitContractPayload> for generated::InitContractPayload {
765    fn from(v: &InitContractPayload) -> Self {
766        Self {
767            amount:     Some(v.amount.into()),
768            module_ref: Some(v.mod_ref.into()),
769            init_name:  Some((&v.init_name).into()),
770            parameter:  Some((&v.param).into()),
771        }
772    }
773}
774
775impl From<&UpdateContractPayload> for generated::UpdateContractPayload {
776    fn from(v: &UpdateContractPayload) -> Self {
777        Self {
778            amount:       Some(v.amount.into()),
779            address:      Some(v.address.into()),
780            receive_name: Some((&v.receive_name).into()),
781            parameter:    Some((&v.message).into()),
782        }
783    }
784}
785
786impl From<&ContractAddress> for generated::ContractAddress {
787    fn from(ca: &ContractAddress) -> Self {
788        Self {
789            index:    ca.index,
790            subindex: ca.subindex,
791        }
792    }
793}
794
795impl From<Nonce> for generated::SequenceNumber {
796    fn from(v: Nonce) -> Self { generated::SequenceNumber { value: v.nonce } }
797}
798
799impl From<UpdateSequenceNumber> for generated::UpdateSequenceNumber {
800    fn from(v: UpdateSequenceNumber) -> Self { generated::UpdateSequenceNumber { value: v.number } }
801}
802
803impl From<Energy> for generated::Energy {
804    fn from(v: Energy) -> Self { generated::Energy { value: v.energy } }
805}
806
807impl From<TransactionTime> for generated::TransactionTime {
808    fn from(v: TransactionTime) -> Self { generated::TransactionTime { value: v.seconds } }
809}
810
811impl From<&Amount> for generated::Amount {
812    fn from(v: &Amount) -> Self { Self { value: v.micro_ccd } }
813}
814
815impl From<Amount> for generated::Amount {
816    fn from(v: Amount) -> Self { Self { value: v.micro_ccd } }
817}
818
819impl
820    From<
821        &AccountCredentialMessage<
822            id::constants::IpPairing,
823            id::constants::ArCurve,
824            id::constants::AttributeKind,
825        >,
826    > for generated::CredentialDeployment
827{
828    fn from(
829        v: &AccountCredentialMessage<
830            id::constants::IpPairing,
831            id::constants::ArCurve,
832            id::constants::AttributeKind,
833        >,
834    ) -> Self {
835        Self {
836            message_expiry: Some(v.message_expiry.into()),
837            payload:        Some(generated::credential_deployment::Payload::RawPayload(
838                common::to_bytes(&v.credential),
839            )),
840        }
841    }
842}
843
844impl From<&UpdateInstruction> for generated::UpdateInstruction {
845    fn from(v: &UpdateInstruction) -> Self {
846        Self {
847            signatures: Some(generated::SignatureMap {
848                signatures: {
849                    let mut hm = HashMap::new();
850                    for (key_idx, sig) in v.signatures.signatures.iter() {
851                        hm.insert(key_idx.index.into(), generated::Signature {
852                            value: sig.sig.to_owned(),
853                        });
854                    }
855                    hm
856                },
857            }),
858            header:     Some(generated::UpdateInstructionHeader {
859                sequence_number: Some(v.header.seq_number.into()),
860                effective_time:  Some(v.header.effective_time.into()),
861                timeout:         Some(v.header.timeout.into()),
862            }),
863            payload:    Some(generated::UpdateInstructionPayload {
864                payload: Some(generated::update_instruction_payload::Payload::RawPayload(
865                    common::to_bytes(&v.payload),
866                )),
867            }),
868        }
869    }
870}
871
872impl IntoRequest<generated::AccountInfoRequest> for (&AccountIdentifier, &BlockIdentifier) {
873    fn into_request(self) -> tonic::Request<generated::AccountInfoRequest> {
874        let ai = generated::AccountInfoRequest {
875            block_hash:         Some(self.1.into()),
876            account_identifier: Some(self.0.into()),
877        };
878        tonic::Request::new(ai)
879    }
880}
881
882impl IntoRequest<generated::AncestorsRequest> for (&BlockIdentifier, u64) {
883    fn into_request(self) -> tonic::Request<generated::AncestorsRequest> {
884        let ar = generated::AncestorsRequest {
885            block_hash: Some(self.0.into()),
886            amount:     self.1,
887        };
888        tonic::Request::new(ar)
889    }
890}
891
892impl IntoRequest<generated::ModuleSourceRequest> for (&ModuleReference, &BlockIdentifier) {
893    fn into_request(self) -> tonic::Request<generated::ModuleSourceRequest> {
894        let r = generated::ModuleSourceRequest {
895            block_hash: Some(self.1.into()),
896            module_ref: Some(self.0.into()),
897        };
898        tonic::Request::new(r)
899    }
900}
901
902impl IntoRequest<generated::InstanceInfoRequest> for (ContractAddress, &BlockIdentifier) {
903    fn into_request(self) -> tonic::Request<generated::InstanceInfoRequest> {
904        let r = generated::InstanceInfoRequest {
905            block_hash: Some(self.1.into()),
906            address:    Some(self.0.into()),
907        };
908        tonic::Request::new(r)
909    }
910}
911
912impl<V: Into<Vec<u8>>> IntoRequest<generated::InstanceStateLookupRequest>
913    for (ContractAddress, &BlockIdentifier, V)
914{
915    fn into_request(self) -> tonic::Request<generated::InstanceStateLookupRequest> {
916        let r = generated::InstanceStateLookupRequest {
917            block_hash: Some(self.1.into()),
918            address:    Some(self.0.into()),
919            key:        self.2.into(),
920        };
921        tonic::Request::new(r)
922    }
923}
924
925impl IntoRequest<generated::TransactionHash> for &TransactionHash {
926    fn into_request(self) -> tonic::Request<generated::TransactionHash> {
927        tonic::Request::new(self.into())
928    }
929}
930
931impl IntoRequest<generated::AccountIdentifierInput> for &AccountIdentifier {
932    fn into_request(self) -> tonic::Request<generated::AccountIdentifierInput> {
933        tonic::Request::new(self.into())
934    }
935}
936
937impl IntoRequest<generated::AccountAddress> for &AccountAddress {
938    fn into_request(self) -> tonic::Request<generated::AccountAddress> {
939        tonic::Request::new(self.into())
940    }
941}
942
943impl From<transactions::TransactionHeader> for generated::AccountTransactionHeader {
944    fn from(v: transactions::TransactionHeader) -> Self { (&v).into() }
945}
946
947impl From<&transactions::TransactionHeader> for generated::AccountTransactionHeader {
948    fn from(v: &transactions::TransactionHeader) -> Self {
949        Self {
950            sender:          Some(generated::AccountAddress::from(v.sender)),
951            sequence_number: Some(v.nonce.into()),
952            energy_amount:   Some(v.energy_amount.into()),
953            expiry:          Some(v.expiry.into()),
954        }
955    }
956}
957
958impl From<TransactionSignature> for generated::AccountTransactionSignature {
959    fn from(v: TransactionSignature) -> Self { (&v).into() }
960}
961
962impl From<&TransactionSignature> for generated::AccountTransactionSignature {
963    fn from(v: &TransactionSignature) -> Self {
964        Self {
965            signatures: {
966                let mut cred_map: HashMap<u32, generated::AccountSignatureMap> = HashMap::new();
967                for (cred_idx, sig_map) in v.signatures.iter() {
968                    let mut acc_sig_map: HashMap<u32, generated::Signature> = HashMap::new();
969                    for (key_idx, sig) in sig_map.iter() {
970                        acc_sig_map.insert(key_idx.0.into(), generated::Signature {
971                            value: sig.sig.to_owned(),
972                        });
973                    }
974                    cred_map.insert(cred_idx.index.into(), generated::AccountSignatureMap {
975                        signatures: acc_sig_map,
976                    });
977                }
978                cred_map
979            },
980        }
981    }
982}
983
984impl IntoRequest<generated::PreAccountTransaction>
985    for (&transactions::TransactionHeader, &transactions::Payload)
986{
987    fn into_request(self) -> tonic::Request<generated::PreAccountTransaction> {
988        let request = generated::PreAccountTransaction {
989            header:  Some(self.0.into()),
990            payload: Some(generated::AccountTransactionPayload {
991                payload: Some(generated::account_transaction_payload::Payload::RawPayload(
992                    self.1.encode().into(),
993                )),
994            }),
995        };
996        tonic::Request::new(request)
997    }
998}
999
1000impl<P: PayloadLike> IntoRequest<generated::SendBlockItemRequest> for &transactions::BlockItem<P> {
1001    fn into_request(self) -> tonic::Request<generated::SendBlockItemRequest> {
1002        let request = match self {
1003            transactions::BlockItem::AccountTransaction(v) => {
1004                generated::SendBlockItemRequest {
1005                    block_item: Some(
1006                        generated::send_block_item_request::BlockItem::AccountTransaction(
1007                            generated::AccountTransaction {
1008                                signature: Some((&v.signature).into()),
1009                                header:    Some((&v.header).into()),
1010                                payload:   {
1011                                    let atp = generated::AccountTransactionPayload{
1012                                    payload: Some(generated::account_transaction_payload::Payload::RawPayload(v.payload.encode().into())),
1013                                };
1014                                    Some(atp)
1015                                },
1016                            },
1017                        ),
1018                    ),
1019                }
1020            }
1021            transactions::BlockItem::CredentialDeployment(v) => generated::SendBlockItemRequest {
1022                block_item: Some(
1023                    generated::send_block_item_request::BlockItem::CredentialDeployment(
1024                        v.as_ref().into(),
1025                    ),
1026                ),
1027            },
1028            transactions::BlockItem::UpdateInstruction(v) => generated::SendBlockItemRequest {
1029                block_item: Some(
1030                    generated::send_block_item_request::BlockItem::UpdateInstruction(v.into()),
1031                ),
1032            },
1033        };
1034        tonic::Request::new(request)
1035    }
1036}
1037
1038impl IntoRequest<generated::InvokeInstanceRequest> for (&BlockIdentifier, &ContractContext) {
1039    fn into_request(self) -> tonic::Request<generated::InvokeInstanceRequest> {
1040        let (block, context) = self;
1041        tonic::Request::new(generated::InvokeInstanceRequest {
1042            block_hash: Some(block.into()),
1043            invoker:    context.invoker.as_ref().map(|a| a.into()),
1044            instance:   Some((&context.contract).into()),
1045            amount:     Some(context.amount.into()),
1046            entrypoint: Some(context.method.as_receive_name().into()),
1047            parameter:  Some(context.parameter.as_ref().into()),
1048            energy:     context.energy.map(From::from),
1049        })
1050    }
1051}
1052
1053impl IntoRequest<generated::PoolInfoRequest> for (&BlockIdentifier, types::BakerId) {
1054    fn into_request(self) -> tonic::Request<generated::PoolInfoRequest> {
1055        let req = generated::PoolInfoRequest {
1056            block_hash: Some(self.0.into()),
1057            baker:      Some(self.1.into()),
1058        };
1059        tonic::Request::new(req)
1060    }
1061}
1062
1063impl IntoRequest<generated::BakerId> for types::BakerId {
1064    fn into_request(self) -> tonic::Request<generated::BakerId> {
1065        tonic::Request::new(generated::BakerId {
1066            value: self.id.index,
1067        })
1068    }
1069}
1070
1071impl IntoRequest<generated::BlocksAtHeightRequest> for &endpoints::BlocksAtHeightInput {
1072    fn into_request(self) -> tonic::Request<generated::BlocksAtHeightRequest> {
1073        tonic::Request::new(self.into())
1074    }
1075}
1076
1077impl IntoRequest<generated::GetPoolDelegatorsRequest> for (&BlockIdentifier, types::BakerId) {
1078    fn into_request(self) -> tonic::Request<generated::GetPoolDelegatorsRequest> {
1079        let req = generated::GetPoolDelegatorsRequest {
1080            block_hash: Some(self.0.into()),
1081            baker:      Some(self.1.into()),
1082        };
1083        tonic::Request::new(req)
1084    }
1085}
1086
1087impl TryFrom<crate::v2::generated::BannedPeer> for types::network::BannedPeer {
1088    type Error = anyhow::Error;
1089
1090    fn try_from(value: crate::v2::generated::BannedPeer) -> Result<Self, Self::Error> {
1091        Ok(types::network::BannedPeer(
1092            <std::net::IpAddr as std::str::FromStr>::from_str(&value.ip_address.require()?.value)?,
1093        ))
1094    }
1095}
1096
1097impl TryFrom<generated::IpSocketAddress> for std::net::SocketAddr {
1098    type Error = anyhow::Error;
1099
1100    fn try_from(value: generated::IpSocketAddress) -> Result<Self, Self::Error> {
1101        Ok(std::net::SocketAddr::new(
1102            <std::net::IpAddr as std::str::FromStr>::from_str(&value.ip.require()?.value)?,
1103            value.port.require()?.value as u16,
1104        ))
1105    }
1106}
1107
1108impl IntoRequest<crate::v2::generated::BannedPeer> for &types::network::BannedPeer {
1109    fn into_request(self) -> tonic::Request<crate::v2::generated::BannedPeer> {
1110        tonic::Request::new(crate::v2::generated::BannedPeer {
1111            ip_address: Some(crate::v2::generated::IpAddress {
1112                value: self.0.to_string(),
1113            }),
1114        })
1115    }
1116}
1117
1118impl From<generated::PeerId> for types::network::PeerId {
1119    fn from(value: generated::PeerId) -> Self { types::network::PeerId(value.value) }
1120}
1121
1122impl TryFrom<generated::PeersInfo> for types::network::PeersInfo {
1123    type Error = anyhow::Error;
1124
1125    fn try_from(peers_info: generated::PeersInfo) -> Result<Self, Self::Error> {
1126        // Get information of the peers that the node is connected to.
1127        // Note. If one peer contains malformed data then this function does not
1128        // return any information about the others.
1129        // This should only happen in cases where the sdk and node is not on the same
1130        // major version.
1131        let peers = peers_info
1132            .peers
1133            .into_iter()
1134            .map(|peer| {
1135                // Parse the catchup status of the peer.
1136                let peer_consensus_info = match peer.consensus_info.require()? {
1137                    generated::peers_info::peer::ConsensusInfo::Bootstrapper(_) => {
1138                        types::network::PeerConsensusInfo::Bootstrapper
1139                    }
1140                    generated::peers_info::peer::ConsensusInfo::NodeCatchupStatus(0) => {
1141                        types::network::PeerConsensusInfo::Node(
1142                            types::network::PeerCatchupStatus::UpToDate,
1143                        )
1144                    }
1145                    generated::peers_info::peer::ConsensusInfo::NodeCatchupStatus(1) => {
1146                        types::network::PeerConsensusInfo::Node(
1147                            types::network::PeerCatchupStatus::Pending,
1148                        )
1149                    }
1150                    generated::peers_info::peer::ConsensusInfo::NodeCatchupStatus(2) => {
1151                        types::network::PeerConsensusInfo::Node(
1152                            types::network::PeerCatchupStatus::CatchingUp,
1153                        )
1154                    }
1155                    _ => anyhow::bail!("Malformed catchup status from peer."),
1156                };
1157                // Parse the network statistics for the peer.
1158                let stats = peer.network_stats.require()?;
1159                let network_stats = types::network::NetworkStats {
1160                    packets_sent:     stats.packets_sent,
1161                    packets_received: stats.packets_received,
1162                    latency:          stats.latency,
1163                };
1164                Ok(types::network::Peer {
1165                    peer_id: peer.peer_id.require()?.into(),
1166                    consensus_info: peer_consensus_info,
1167                    network_stats,
1168                    addr: peer.socket_address.require()?.try_into()?,
1169                })
1170            })
1171            .collect::<anyhow::Result<Vec<types::network::Peer>>>()?;
1172        Ok(types::network::PeersInfo { peers })
1173    }
1174}
1175
1176impl TryFrom<generated::node_info::NetworkInfo> for types::NetworkInfo {
1177    type Error = anyhow::Error;
1178
1179    fn try_from(network_info: generated::node_info::NetworkInfo) -> Result<Self, Self::Error> {
1180        Ok(types::NetworkInfo {
1181            node_id:             network_info.node_id.require()?.value,
1182            peer_total_sent:     network_info.peer_total_sent,
1183            peer_total_received: network_info.peer_total_received,
1184            avg_bps_in:          network_info.avg_bps_in,
1185            avg_bps_out:         network_info.avg_bps_out,
1186        })
1187    }
1188}
1189
1190impl IntoRequest<crate::v2::generated::PeerToBan> for types::network::PeerToBan {
1191    fn into_request(self) -> tonic::Request<crate::v2::generated::PeerToBan> {
1192        tonic::Request::new(match self {
1193            types::network::PeerToBan::IpAddr(ip_addr) => crate::v2::generated::PeerToBan {
1194                ip_address: Some(crate::v2::generated::IpAddress {
1195                    value: ip_addr.to_string(),
1196                }),
1197            },
1198        })
1199    }
1200}
1201
1202impl TryFrom<generated::NodeInfo> for types::NodeInfo {
1203    type Error = anyhow::Error;
1204
1205    fn try_from(node_info: generated::NodeInfo) -> Result<Self, Self::Error> {
1206        let version = semver::Version::parse(&node_info.peer_version)?;
1207        let local_time = chrono::DateTime::<chrono::Utc>::from(std::time::UNIX_EPOCH)
1208            + chrono::TimeDelta::try_milliseconds(node_info.local_time.require()?.value as i64)
1209                .context("Node local time out of bounds!")?;
1210        let uptime = chrono::Duration::try_from(types::DurationSeconds::from(
1211            node_info.peer_uptime.require()?.value,
1212        ))?;
1213        let network_info = node_info.network_info.require()?.try_into()?;
1214        let details = match node_info.details.require()? {
1215            generated::node_info::Details::Bootstrapper(_) => types::NodeDetails::Bootstrapper,
1216            generated::node_info::Details::Node(status) => {
1217                let consensus_status = match status.consensus_status.require()? {
1218                    generated::node_info::node::ConsensusStatus::NotRunning(_) => {
1219                        types::NodeConsensusStatus::ConsensusNotRunning
1220                    }
1221                    generated::node_info::node::ConsensusStatus::Passive(_) => {
1222                        types::NodeConsensusStatus::ConsensusPassive
1223                    }
1224                    generated::node_info::node::ConsensusStatus::Active(baker) => {
1225                        let baker_id = baker.baker_id.require()?.into();
1226                        match baker.status.require()? {
1227                            generated::node_info::baker_consensus_info::Status::PassiveCommitteeInfo(0) => types::NodeConsensusStatus::NotInCommittee(baker_id),
1228                            generated::node_info::baker_consensus_info::Status::PassiveCommitteeInfo(1) => types::NodeConsensusStatus::AddedButNotActiveInCommittee(baker_id),
1229                            generated::node_info::baker_consensus_info::Status::PassiveCommitteeInfo(2) => types::NodeConsensusStatus::AddedButWrongKeys(baker_id),
1230                            generated::node_info::baker_consensus_info::Status::ActiveBakerCommitteeInfo(_) => types::NodeConsensusStatus::Baker(baker_id),
1231                            generated::node_info::baker_consensus_info::Status::ActiveFinalizerCommitteeInfo(_) => types::NodeConsensusStatus::Finalizer(baker_id),
1232                            _ => anyhow::bail!("Malformed baker status")
1233                        }
1234                    }
1235                };
1236                types::NodeDetails::Node(consensus_status)
1237            }
1238        };
1239        Ok(types::NodeInfo {
1240            version,
1241            local_time,
1242            uptime,
1243            network_info,
1244            details,
1245        })
1246    }
1247}
1248
1249/// A helper trait that is implemented by types that can be cheaply converted to
1250/// a [`BlockIdentifier`]. This is esentially [`Into<BlockIdentifier>`] but
1251/// orphan rules prevent using that exactly.
1252///
1253/// This trait makes it convenient to use block hashes as input to functions
1254/// that take a block identifier.
1255pub trait IntoBlockIdentifier {
1256    fn into_block_identifier(self) -> BlockIdentifier;
1257}
1258
1259impl IntoBlockIdentifier for BlockIdentifier {
1260    fn into_block_identifier(self) -> BlockIdentifier { self }
1261}
1262
1263impl<X: IntoBlockIdentifier + Copy> IntoBlockIdentifier for &X {
1264    fn into_block_identifier(self) -> BlockIdentifier { (*self).into_block_identifier() }
1265}
1266
1267impl IntoBlockIdentifier for BlockHash {
1268    fn into_block_identifier(self) -> BlockIdentifier { BlockIdentifier::Given(self) }
1269}
1270
1271impl IntoBlockIdentifier for AbsoluteBlockHeight {
1272    fn into_block_identifier(self) -> BlockIdentifier { BlockIdentifier::AbsoluteHeight(self) }
1273}
1274
1275impl IntoBlockIdentifier for RelativeBlockHeight {
1276    fn into_block_identifier(self) -> BlockIdentifier { BlockIdentifier::RelativeHeight(self) }
1277}
1278
1279impl Client {
1280    /// Construct a new client connection to a concordium node.
1281    ///
1282    /// # Example
1283    /// Creates a new client. Note the example assumes access to a local running
1284    /// node.
1285    ///
1286    /// ```no_run
1287    /// # tokio_test::block_on(async {
1288    /// use concordium_rust_sdk::{endpoints::Endpoint, v2::Client};
1289    /// use std::str::FromStr;
1290    ///
1291    /// let mut client = Client::new("http://localhost:20001").await?;
1292    ///
1293    /// # Ok::<(), anyhow::Error>(())
1294    /// # });
1295    /// ```
1296    pub async fn new<E>(endpoint: E) -> Result<Self, tonic::transport::Error>
1297    where
1298        E: TryInto<tonic::transport::Endpoint>,
1299        E::Error: Into<Box<dyn std::error::Error + Send + Sync + 'static>>, {
1300        let client = generated::queries_client::QueriesClient::connect(endpoint).await?;
1301        Ok(Self { client })
1302    }
1303
1304    /// Get the information for the given account in the given block. If either
1305    /// the block or the account do not exist [`QueryError::NotFound`] is
1306    /// returned.
1307    pub async fn get_account_info(
1308        &mut self,
1309        acc: &AccountIdentifier,
1310        bi: impl IntoBlockIdentifier,
1311    ) -> endpoints::QueryResult<QueryResponse<AccountInfo>> {
1312        let response = self
1313            .client
1314            .get_account_info((acc, &bi.into_block_identifier()))
1315            .await?;
1316        let block_hash = extract_metadata(&response)?;
1317        let response = AccountInfo::try_from(response.into_inner())?;
1318        Ok(QueryResponse {
1319            block_hash,
1320            response,
1321        })
1322    }
1323
1324    /// Get the next sequence number for the account, with information on how
1325    /// reliable the information is.
1326    pub async fn get_next_account_sequence_number(
1327        &mut self,
1328        account_address: &AccountAddress,
1329    ) -> endpoints::QueryResult<types::queries::AccountNonceResponse> {
1330        let response = self
1331            .client
1332            .get_next_account_sequence_number(account_address)
1333            .await?;
1334        let response = types::queries::AccountNonceResponse::try_from(response.into_inner())?;
1335        Ok(response)
1336    }
1337
1338    /// Get information about the current state of consensus. This is an
1339    /// overview of the node's current view of the chain.
1340    pub async fn get_consensus_info(
1341        &mut self,
1342    ) -> endpoints::QueryResult<types::queries::ConsensusInfo> {
1343        let response = self
1344            .client
1345            .get_consensus_info(generated::Empty::default())
1346            .await?;
1347        let response = types::queries::ConsensusInfo::try_from(response.into_inner())?;
1348        Ok(response)
1349    }
1350
1351    /// Get the currently used cryptographic parameters. If the block does
1352    /// not exist [`QueryError::NotFound`] is returned.
1353    pub async fn get_cryptographic_parameters(
1354        &mut self,
1355        bi: impl IntoBlockIdentifier,
1356    ) -> endpoints::QueryResult<QueryResponse<types::CryptographicParameters>> {
1357        let response = self
1358            .client
1359            .get_cryptographic_parameters(&bi.into_block_identifier())
1360            .await?;
1361        let block_hash = extract_metadata(&response)?;
1362        let response = types::CryptographicParameters::try_from(response.into_inner())?;
1363        Ok(QueryResponse {
1364            block_hash,
1365            response,
1366        })
1367    }
1368
1369    /// Get the list of accounts in the given block.
1370    /// The stream will end when all accounts that exist in the state at the end
1371    /// of the given block have been returned. If the block does not exist
1372    /// [`QueryError::NotFound`] is returned.
1373    pub async fn get_account_list(
1374        &mut self,
1375        bi: impl IntoBlockIdentifier,
1376    ) -> endpoints::QueryResult<
1377        QueryResponse<impl Stream<Item = Result<AccountAddress, tonic::Status>>>,
1378    > {
1379        let response = self
1380            .client
1381            .get_account_list(&bi.into_block_identifier())
1382            .await?;
1383        let block_hash = extract_metadata(&response)?;
1384        let stream = response.into_inner().map(|x| x.and_then(TryFrom::try_from));
1385        Ok(QueryResponse {
1386            block_hash,
1387            response: stream,
1388        })
1389    }
1390
1391    /// Get a list of all smart contract modules. The stream will end
1392    /// when all modules that exist in the state at the end of the given
1393    /// block have been returned.
1394    /// If the block does not exist [`QueryError::NotFound`] is returned.
1395    pub async fn get_module_list(
1396        &mut self,
1397        bi: impl IntoBlockIdentifier,
1398    ) -> endpoints::QueryResult<
1399        QueryResponse<impl Stream<Item = Result<ModuleReference, tonic::Status>>>,
1400    > {
1401        let response = self
1402            .client
1403            .get_module_list(&bi.into_block_identifier())
1404            .await?;
1405        let block_hash = extract_metadata(&response)?;
1406        let stream = response.into_inner().map(|x| x.and_then(TryFrom::try_from));
1407        Ok(QueryResponse {
1408            block_hash,
1409            response: stream,
1410        })
1411    }
1412
1413    /// Get the source of a smart contract module.
1414    /// If the block or module does not exist [`QueryError::NotFound`] is
1415    /// returned.
1416    pub async fn get_module_source(
1417        &mut self,
1418        module_ref: &ModuleReference,
1419        bi: impl IntoBlockIdentifier,
1420    ) -> endpoints::QueryResult<QueryResponse<types::smart_contracts::WasmModule>> {
1421        let response = self
1422            .client
1423            .get_module_source((module_ref, &bi.into_block_identifier()))
1424            .await?;
1425        let block_hash = extract_metadata(&response)?;
1426        let response = types::smart_contracts::WasmModule::try_from(response.into_inner())?;
1427        Ok(QueryResponse {
1428            block_hash,
1429            response,
1430        })
1431    }
1432
1433    /// Get the list of smart contract instances in a given block.
1434    /// The stream will end when all instances that exist in the state at the
1435    /// end of the given block have been returned. If the block does not
1436    /// exist [`QueryError::NotFound`] is returned.
1437    pub async fn get_instance_list(
1438        &mut self,
1439        bi: impl IntoBlockIdentifier,
1440    ) -> endpoints::QueryResult<
1441        QueryResponse<impl Stream<Item = Result<ContractAddress, tonic::Status>>>,
1442    > {
1443        let response = self
1444            .client
1445            .get_instance_list(&bi.into_block_identifier())
1446            .await?;
1447        let block_hash = extract_metadata(&response)?;
1448        let stream = response.into_inner().map(|x| x.map(From::from));
1449        Ok(QueryResponse {
1450            block_hash,
1451            response: stream,
1452        })
1453    }
1454
1455    /// Get information about a smart contract instance as it appears at the end
1456    /// of the given block. If the block or instance does not exist
1457    /// [`QueryError::NotFound`] is returned.
1458    pub async fn get_instance_info(
1459        &mut self,
1460        address: ContractAddress,
1461        bi: impl IntoBlockIdentifier,
1462    ) -> endpoints::QueryResult<QueryResponse<InstanceInfo>> {
1463        let response = self
1464            .client
1465            .get_instance_info((address, &bi.into_block_identifier()))
1466            .await?;
1467        let block_hash = extract_metadata(&response)?;
1468        let response = InstanceInfo::try_from(response.into_inner())?;
1469        Ok(QueryResponse {
1470            block_hash,
1471            response,
1472        })
1473    }
1474
1475    /// Get a stream of ancestors for the provided block.
1476    /// Starting with the provided block itself, moving backwards until no more
1477    /// ancestors or the requested number of ancestors have been returned.
1478    pub async fn get_ancestors(
1479        &mut self,
1480        bi: impl IntoBlockIdentifier,
1481        limit: u64,
1482    ) -> endpoints::QueryResult<QueryResponse<impl Stream<Item = Result<BlockHash, tonic::Status>>>>
1483    {
1484        let response = self
1485            .client
1486            .get_ancestors((&bi.into_block_identifier(), limit))
1487            .await?;
1488        let block_hash = extract_metadata(&response)?;
1489        let stream = response.into_inner().map(|x| x.and_then(TryFrom::try_from));
1490        Ok(QueryResponse {
1491            block_hash,
1492            response: stream,
1493        })
1494    }
1495
1496    /// Return a stream of blocks that are finalized from the time the query is
1497    /// made onward.
1498    /// This can be used to listen for newly finalized blocks.
1499    ///
1500    /// Note: There is no guarantee that blocks will not be skipped if the
1501    /// client is too slow in processing the stream, however blocks will
1502    /// always be sent by increasing block height.
1503    pub async fn get_finalized_blocks(
1504        &mut self,
1505    ) -> endpoints::QueryResult<impl Stream<Item = Result<FinalizedBlockInfo, tonic::Status>>> {
1506        let response = self
1507            .client
1508            .get_finalized_blocks(generated::Empty::default())
1509            .await?;
1510        let stream = response.into_inner().map(|x| match x {
1511            Ok(v) => {
1512                let block_hash = v.hash.require().and_then(TryFrom::try_from)?;
1513                let height = v.height.require()?.into();
1514                Ok(FinalizedBlockInfo { block_hash, height })
1515            }
1516            Err(x) => Err(x),
1517        });
1518        Ok(stream)
1519    }
1520
1521    /// Get the exact state of a specific contract instance, streamed as a list
1522    /// of key-value pairs. The list is streamed in lexicographic order of
1523    /// keys.
1524    /// If the block or instance does not exist [`QueryError::NotFound`] is
1525    /// returned.
1526    pub async fn get_instance_state(
1527        &mut self,
1528        ca: ContractAddress,
1529        bi: impl IntoBlockIdentifier,
1530    ) -> endpoints::QueryResult<
1531        QueryResponse<impl Stream<Item = Result<(Vec<u8>, Vec<u8>), tonic::Status>>>,
1532    > {
1533        let response = self
1534            .client
1535            .get_instance_state((ca, &bi.into_block_identifier()))
1536            .await?;
1537        let block_hash = extract_metadata(&response)?;
1538        let stream = response.into_inner().map(|x| match x {
1539            Ok(v) => {
1540                let key = v.key;
1541                let value = v.value;
1542                Ok((key, value))
1543            }
1544            Err(x) => Err(x),
1545        });
1546        Ok(QueryResponse {
1547            block_hash,
1548            response: stream,
1549        })
1550    }
1551
1552    /// Get the value at a specific key of a contract state. In contrast to
1553    /// [`get_instance_state`](Self::get_instance_state) this is more efficient,
1554    /// but requires the user to know the specific key to look for.
1555    /// If the block or instance does not exist [`QueryError::NotFound`] is
1556    /// returned.
1557    pub async fn instance_state_lookup(
1558        &mut self,
1559        ca: ContractAddress,
1560        key: impl Into<Vec<u8>>,
1561        bi: impl IntoBlockIdentifier,
1562    ) -> endpoints::QueryResult<QueryResponse<Vec<u8>>> {
1563        let response = self
1564            .client
1565            .instance_state_lookup((ca, &bi.into_block_identifier(), key))
1566            .await?;
1567        let block_hash = extract_metadata(&response)?;
1568        Ok(QueryResponse {
1569            block_hash,
1570            response: response.into_inner().value,
1571        })
1572    }
1573
1574    /// Get the status of and information about a specific block item
1575    /// (transaction). If the block item does not exist
1576    /// [`QueryError::NotFound`] is returned.
1577    pub async fn get_block_item_status(
1578        &mut self,
1579        th: &TransactionHash,
1580    ) -> endpoints::QueryResult<TransactionStatus> {
1581        let response = self.client.get_block_item_status(th).await?;
1582        let response = TransactionStatus::try_from(response.into_inner())?;
1583        Ok(response)
1584    }
1585
1586    /// Send a block item. A block item is either an `AccountTransaction`, which
1587    /// is a transaction signed and paid for by an account, a
1588    /// `CredentialDeployment`, which creates a new account, or
1589    /// `UpdateInstruction`, which is an instruction to change some
1590    /// parameters of the chain. Update instructions can only be sent by the
1591    /// governance committee.
1592    pub async fn send_block_item<P: PayloadLike>(
1593        &mut self,
1594        bi: &transactions::BlockItem<P>,
1595    ) -> endpoints::RPCResult<TransactionHash> {
1596        let response = self.client.send_block_item(bi).await?;
1597        let response = TransactionHash::try_from(response.into_inner())?;
1598        Ok(response)
1599    }
1600
1601    /// Send an account transaction. This is just a helper around
1602    /// [`send_block_item`](Self::send_block_item) block item for convenience.
1603    pub async fn send_account_transaction<P: PayloadLike>(
1604        &mut self,
1605        at: transactions::AccountTransaction<P>,
1606    ) -> endpoints::RPCResult<TransactionHash> {
1607        self.send_block_item(&at.into()).await
1608    }
1609
1610    /// Get the hash to be signed for an account transaction from the node. The
1611    /// hash returned can then be used for signing when constructing
1612    /// [`TransactionSignature`] as part of calling [`Client::send_block_item`].
1613    ///
1614    /// This is provided as a convenience to support cases where the right SDK
1615    /// is not available for interacting with the node.
1616    ///
1617    /// This SDK can compute the hash off-line and it is not recommended to use
1618    /// this endpoint, instead use [`compute_transaction_sign_hash`].
1619    ///
1620    /// [`compute_transaction_sign_hash`]:
1621    /// types::transactions::compute_transaction_sign_hash
1622    pub async fn get_account_transaction_sign_hash(
1623        &mut self,
1624        header: &transactions::TransactionHeader,
1625        payload: &transactions::Payload,
1626    ) -> endpoints::RPCResult<TransactionSignHash> {
1627        let response = self
1628            .client
1629            .get_account_transaction_sign_hash((header, payload))
1630            .await?;
1631        let response = TransactionSignHash::try_from(response.into_inner())?;
1632        Ok(response)
1633    }
1634
1635    /// Wait until the transaction is finalized. Returns
1636    /// [`NotFound`](QueryError::NotFound) in case the transaction is not
1637    /// known to the node. In case of success, the return value is a pair of the
1638    /// block hash of the block that contains the transactions, and its
1639    /// outcome in the block.
1640    ///
1641    /// Since this can take an indefinite amount of time in general, users of
1642    /// this function might wish to wrap it inside
1643    /// [`timeout`](tokio::time::timeout) handler and handle the resulting
1644    /// failure.
1645    pub async fn wait_until_finalized(
1646        &mut self,
1647        hash: &types::hashes::TransactionHash,
1648    ) -> endpoints::QueryResult<(types::hashes::BlockHash, types::BlockItemSummary)> {
1649        let hash = *hash;
1650        let process_response = |response| {
1651            if let types::TransactionStatus::Finalized(blocks) = response {
1652                let mut iter = blocks.into_iter();
1653                if let Some(rv) = iter.next() {
1654                    if iter.next().is_some() {
1655                        Err(tonic::Status::internal(
1656                            "Finalized transaction finalized into multiple blocks. This cannot \
1657                             happen.",
1658                        )
1659                        .into())
1660                    } else {
1661                        Ok::<_, QueryError>(Some(rv))
1662                    }
1663                } else {
1664                    Err(tonic::Status::internal(
1665                        "Finalized transaction finalized into no blocks. This cannot happen.",
1666                    )
1667                    .into())
1668                }
1669            } else {
1670                Ok(None)
1671            }
1672        };
1673
1674        match process_response(self.get_block_item_status(&hash).await?)? {
1675            Some(rv) => Ok(rv),
1676            None => {
1677                // if the first query did not succeed then start listening for finalized blocks.
1678                // and on each new block try to query the status.
1679                let mut blocks = self.get_finalized_blocks().await?;
1680                while blocks.next().await.transpose()?.is_some() {
1681                    if let Some(rv) = process_response(self.get_block_item_status(&hash).await?)? {
1682                        return Ok(rv);
1683                    }
1684                }
1685                Err(QueryError::NotFound)
1686            }
1687        }
1688    }
1689
1690    /// Run the smart contract instance entrypoint in a given context and in the
1691    /// state at the end of the given block and return the results.
1692    /// If the block does not exist [`QueryError::NotFound`] is returned.
1693    pub async fn invoke_instance(
1694        &mut self,
1695        bi: impl IntoBlockIdentifier,
1696        context: &ContractContext,
1697    ) -> endpoints::QueryResult<QueryResponse<InvokeContractResult>> {
1698        let response = self
1699            .client
1700            .invoke_instance((&bi.into_block_identifier(), context))
1701            .await?;
1702        let block_hash = extract_metadata(&response)?;
1703        let response = InvokeContractResult::try_from(response.into_inner())?;
1704        Ok(QueryResponse {
1705            block_hash,
1706            response,
1707        })
1708    }
1709
1710    /// Start a dry-run sequence that can be used to simulate a series of
1711    /// transactions and other operations on the node.
1712    ///
1713    /// Before invoking any other operations on the [`dry_run::DryRun`] object,
1714    /// the state must be loaded by calling
1715    /// [`dry_run::DryRun::load_block_state`].
1716    pub async fn begin_dry_run(&mut self) -> endpoints::QueryResult<dry_run::DryRun> {
1717        Ok(dry_run::DryRun::new(&mut self.client).await?)
1718    }
1719
1720    /// Start a dry-run sequence that can be used to simulate a series of
1721    /// transactions and other operations on the node, starting from the
1722    /// specified block.
1723    pub async fn dry_run(
1724        &mut self,
1725        bi: impl IntoBlockIdentifier,
1726    ) -> dry_run::DryRunResult<(dry_run::DryRun, dry_run::BlockStateLoaded)> {
1727        let mut runner = dry_run::DryRun::new(&mut self.client).await?;
1728        let load_result = runner.load_block_state(bi).await?;
1729        Ok(WithRemainingQuota {
1730            inner:           (runner, load_result.inner),
1731            quota_remaining: load_result.quota_remaining,
1732        })
1733    }
1734
1735    /// Get information, such as height, timings, and transaction counts for the
1736    /// given block. If the block does not exist [`QueryError::NotFound`] is
1737    /// returned.
1738    pub async fn get_block_info(
1739        &mut self,
1740        bi: impl IntoBlockIdentifier,
1741    ) -> endpoints::QueryResult<QueryResponse<types::queries::BlockInfo>> {
1742        let response = self
1743            .client
1744            .get_block_info(&bi.into_block_identifier())
1745            .await?;
1746        let block_hash = extract_metadata(&response)?;
1747        let response = types::queries::BlockInfo::try_from(response.into_inner())?;
1748        Ok(QueryResponse {
1749            block_hash,
1750            response,
1751        })
1752    }
1753
1754    /// Get information about whether a block identified by `bi` is a payday
1755    /// block or not. This will always return `false` for blocks produced prior
1756    /// to protocol version 4. If the block does not exits
1757    /// [`QueryError::NotFound`] is returned.
1758    pub async fn is_payday_block(
1759        &mut self,
1760        bi: impl IntoBlockIdentifier,
1761    ) -> endpoints::QueryResult<QueryResponse<bool>> {
1762        let mut special_events = self.get_block_special_events(bi).await?;
1763        let block_hash = special_events.block_hash;
1764
1765        while let Some(event) = special_events.response.next().await.transpose()? {
1766            let has_payday_event = matches!(
1767                event,
1768                SpecialTransactionOutcome::PaydayPoolReward { .. }
1769                    | SpecialTransactionOutcome::PaydayAccountReward { .. }
1770                    | SpecialTransactionOutcome::PaydayFoundationReward { .. }
1771            );
1772
1773            if has_payday_event {
1774                return Ok(QueryResponse {
1775                    block_hash,
1776                    response: true,
1777                });
1778            };
1779        }
1780
1781        Ok(QueryResponse {
1782            block_hash,
1783            response: false,
1784        })
1785    }
1786
1787    /// Get all the bakers at the end of the given block.
1788    /// If the block does not exist [`QueryError::NotFound`] is returned.
1789    pub async fn get_baker_list(
1790        &mut self,
1791        bi: impl IntoBlockIdentifier,
1792    ) -> endpoints::QueryResult<
1793        QueryResponse<impl Stream<Item = Result<types::BakerId, tonic::Status>>>,
1794    > {
1795        let response = self
1796            .client
1797            .get_baker_list(&bi.into_block_identifier())
1798            .await?;
1799        let block_hash = extract_metadata(&response)?;
1800        let stream = response.into_inner().map(|x| x.map(From::from));
1801        Ok(QueryResponse {
1802            block_hash,
1803            response: stream,
1804        })
1805    }
1806
1807    /// Get information about a given pool at the end of a given block.
1808    /// If the block does not exist or is prior to protocol version 4 then
1809    /// [`QueryError::NotFound`] is returned.
1810    pub async fn get_pool_info(
1811        &mut self,
1812        block_id: impl IntoBlockIdentifier,
1813        baker_id: types::BakerId,
1814    ) -> endpoints::QueryResult<QueryResponse<types::BakerPoolStatus>> {
1815        let response = self
1816            .client
1817            .get_pool_info((&block_id.into_block_identifier(), baker_id))
1818            .await?;
1819        let block_hash = extract_metadata(&response)?;
1820        let response = types::BakerPoolStatus::try_from(response.into_inner())?;
1821        Ok(QueryResponse {
1822            block_hash,
1823            response,
1824        })
1825    }
1826
1827    /// Get information about the passive delegators at the end of a given
1828    /// block.
1829    /// If the block does not exist or is prior to protocol version 4 then
1830    /// [`QueryError::NotFound`] is returned.
1831    pub async fn get_passive_delegation_info(
1832        &mut self,
1833        block_id: impl IntoBlockIdentifier,
1834    ) -> endpoints::QueryResult<QueryResponse<types::PassiveDelegationStatus>> {
1835        let response = self
1836            .client
1837            .get_passive_delegation_info(&block_id.into_block_identifier())
1838            .await?;
1839        let block_hash = extract_metadata(&response)?;
1840        let response = types::PassiveDelegationStatus::try_from(response.into_inner())?;
1841        Ok(QueryResponse {
1842            block_hash,
1843            response,
1844        })
1845    }
1846
1847    /// Get a list of live blocks at a given height.
1848    pub async fn get_blocks_at_height(
1849        &mut self,
1850        blocks_at_height_input: &endpoints::BlocksAtHeightInput,
1851    ) -> endpoints::QueryResult<Vec<BlockHash>> {
1852        let response = self
1853            .client
1854            .get_blocks_at_height(blocks_at_height_input)
1855            .await?;
1856        let blocks = response
1857            .into_inner()
1858            .blocks
1859            .into_iter()
1860            .map(TryFrom::try_from)
1861            .collect::<Result<_, tonic::Status>>()?;
1862        Ok(blocks)
1863    }
1864
1865    /// Get information about tokenomics at the end of a given block.
1866    /// If the block does not exist [`QueryError::NotFound`] is returned.
1867    pub async fn get_tokenomics_info(
1868        &mut self,
1869        block_id: impl IntoBlockIdentifier,
1870    ) -> endpoints::QueryResult<QueryResponse<types::RewardsOverview>> {
1871        let response = self
1872            .client
1873            .get_tokenomics_info(&block_id.into_block_identifier())
1874            .await?;
1875        let block_hash = extract_metadata(&response)?;
1876        let response = types::RewardsOverview::try_from(response.into_inner())?;
1877        Ok(QueryResponse {
1878            block_hash,
1879            response,
1880        })
1881    }
1882
1883    /// Get the registered delegators of a given pool at the end of a given
1884    /// block.
1885    /// If the block or baker ID does not exist [`QueryError::NotFound`] is
1886    /// returned, and if the block is baked prior to protocol version 4
1887    /// [`QueryError::RPCError`] with status [`Code::InvalidArgument`] is
1888    /// returned. The stream will end when all the delegators have been
1889    /// returned for the given block.
1890    ///
1891    /// In contrast to the [Client::get_pool_delegators_reward_period] which
1892    /// returns delegators that are fixed for the reward period of the
1893    /// block, this endpoint returns the list of delegators that are
1894    /// registered in the block. Any changes to delegators are immediately
1895    /// visible in this list.
1896    pub async fn get_pool_delegators(
1897        &mut self,
1898        bi: impl IntoBlockIdentifier,
1899        baker_id: types::BakerId,
1900    ) -> endpoints::QueryResult<
1901        QueryResponse<impl Stream<Item = Result<types::DelegatorInfo, tonic::Status>>>,
1902    > {
1903        let response = self
1904            .client
1905            .get_pool_delegators((&bi.into_block_identifier(), baker_id))
1906            .await?;
1907        let block_hash = extract_metadata(&response)?;
1908        let stream = response.into_inner().map(|result| match result {
1909            Ok(delegator) => delegator.try_into(),
1910            Err(err) => Err(err),
1911        });
1912        Ok(QueryResponse {
1913            block_hash,
1914            response: stream,
1915        })
1916    }
1917
1918    /// Get the fixed delegators of a given pool for the reward period of the
1919    /// given block.
1920    /// If the block or baker ID does not exist [`QueryError::NotFound`] is
1921    /// returned, and if the block is baked prior to protocol version 4
1922    /// [`QueryError::RPCError`] with status [`Code::InvalidArgument`] is
1923    /// returned. The stream will end when all the delegators have been
1924    /// returned.
1925    ///
1926    /// In contrast to the [Client::get_pool_delegators] which
1927    /// returns delegators registered for the given block, this endpoint
1928    /// returns the fixed delegators contributing stake in the reward period
1929    /// containing the given block.
1930    pub async fn get_pool_delegators_reward_period(
1931        &mut self,
1932        bi: impl IntoBlockIdentifier,
1933        baker_id: types::BakerId,
1934    ) -> endpoints::QueryResult<
1935        QueryResponse<impl Stream<Item = Result<types::DelegatorRewardPeriodInfo, tonic::Status>>>,
1936    > {
1937        let response = self
1938            .client
1939            .get_pool_delegators_reward_period((&bi.into_block_identifier(), baker_id))
1940            .await?;
1941        let block_hash = extract_metadata(&response)?;
1942        let stream = response.into_inner().map(|result| match result {
1943            Ok(delegator) => delegator.try_into(),
1944            Err(err) => Err(err),
1945        });
1946        Ok(QueryResponse {
1947            block_hash,
1948            response: stream,
1949        })
1950    }
1951
1952    /// Get the registered passive delegators at the end of a given block.
1953    /// If the block does not exist [`QueryError::NotFound`] is returned, and if
1954    /// the block is baked prior to protocol version 4 [`QueryError::
1955    /// RPCError`] with status [`Code::InvalidArgument`] is returned. The stream
1956    /// will end when all the delegators have been returned.
1957    ///
1958    /// In contrast to the [`Client::get_passive_delegators_reward_period`]
1959    /// which returns delegators that are fixed for the reward period of the
1960    /// block, this endpoint returns the list of delegators that are
1961    /// registered in the block. Any changes to delegators are immediately
1962    /// visible in this list.
1963    pub async fn get_passive_delegators(
1964        &mut self,
1965        bi: impl IntoBlockIdentifier,
1966    ) -> endpoints::QueryResult<
1967        QueryResponse<impl Stream<Item = Result<types::DelegatorInfo, tonic::Status>>>,
1968    > {
1969        let response = self
1970            .client
1971            .get_passive_delegators(&bi.into_block_identifier())
1972            .await?;
1973        let block_hash = extract_metadata(&response)?;
1974        let stream = response.into_inner().map(|result| match result {
1975            Ok(delegator) => delegator.try_into(),
1976            Err(err) => Err(err),
1977        });
1978        Ok(QueryResponse {
1979            block_hash,
1980            response: stream,
1981        })
1982    }
1983
1984    /// Get the fixed passive delegators for the reward period of the given
1985    /// block.
1986    /// If the block does not exist [`QueryError::NotFound`] is returned.
1987    /// If the block is baked prior to protocol version 4,
1988    /// [`QueryError::RPCError`] with status [`Code::InvalidArgument`] is
1989    /// returned. The stream will end when all the delegators have been
1990    /// returned.
1991    ///
1992    /// In contrast to the `GetPassiveDelegators` which returns delegators
1993    /// registered for the given block, this endpoint returns the fixed
1994    /// delegators contributing stake in the reward period containing the
1995    /// given block.
1996    pub async fn get_passive_delegators_reward_period(
1997        &mut self,
1998        bi: impl IntoBlockIdentifier,
1999    ) -> endpoints::QueryResult<
2000        QueryResponse<impl Stream<Item = Result<types::DelegatorRewardPeriodInfo, tonic::Status>>>,
2001    > {
2002        let response = self
2003            .client
2004            .get_passive_delegators_reward_period(&bi.into_block_identifier())
2005            .await?;
2006        let block_hash = extract_metadata(&response)?;
2007        let stream = response.into_inner().map(|result| match result {
2008            Ok(delegator) => delegator.try_into(),
2009            Err(err) => Err(err),
2010        });
2011        Ok(QueryResponse {
2012            block_hash,
2013            response: stream,
2014        })
2015    }
2016
2017    /// Get the current branches of blocks starting from and including the last
2018    /// finalized block.
2019    ///
2020    /// Branches are all live blocks that are successors of the last finalized
2021    /// block. In particular this means that blocks which do not have a
2022    /// parent are not included in this response.
2023    pub async fn get_branches(&mut self) -> endpoints::QueryResult<types::queries::Branch> {
2024        let response = self
2025            .client
2026            .get_branches(generated::Empty::default())
2027            .await?;
2028        let response = types::queries::Branch::try_from(response.into_inner())?;
2029        Ok(response)
2030    }
2031
2032    /// Get information related to the baker election for a particular block.
2033    /// If the block does not exist [`QueryError::NotFound`] is returned.
2034    pub async fn get_election_info(
2035        &mut self,
2036        bi: impl IntoBlockIdentifier,
2037    ) -> endpoints::QueryResult<QueryResponse<types::BirkParameters>> {
2038        let response = self
2039            .client
2040            .get_election_info(&bi.into_block_identifier())
2041            .await?;
2042        let block_hash = extract_metadata(&response)?;
2043        let response = types::BirkParameters::try_from(response.into_inner())?;
2044        Ok(QueryResponse {
2045            block_hash,
2046            response,
2047        })
2048    }
2049
2050    /// Get the identity providers registered as of the end of a given block.
2051    /// If the block does not exist [`QueryError::NotFound`] is returned.
2052    /// The stream will end when all the identity providers have been returned.
2053    pub async fn get_identity_providers(
2054        &mut self,
2055        bi: impl IntoBlockIdentifier,
2056    ) -> endpoints::QueryResult<
2057        QueryResponse<
2058            impl Stream<
2059                Item = Result<
2060                    crate::id::types::IpInfo<crate::id::constants::IpPairing>,
2061                    tonic::Status,
2062                >,
2063            >,
2064        >,
2065    > {
2066        let response = self
2067            .client
2068            .get_identity_providers(&bi.into_block_identifier())
2069            .await?;
2070        let block_hash = extract_metadata(&response)?;
2071        let stream = response.into_inner().map(|result| match result {
2072            Ok(ip_info) => ip_info.try_into(),
2073            Err(err) => Err(err),
2074        });
2075        Ok(QueryResponse {
2076            block_hash,
2077            response: stream,
2078        })
2079    }
2080
2081    /// Get the list of anonymity revokers in the given block.
2082    /// If the block does not exist [`QueryError::NotFound`] is returned.
2083    /// The stream will end when all the anonymity revokers have been returned.
2084    pub async fn get_anonymity_revokers(
2085        &mut self,
2086        bi: impl IntoBlockIdentifier,
2087    ) -> endpoints::QueryResult<
2088        QueryResponse<
2089            impl Stream<
2090                Item = Result<
2091                    crate::id::types::ArInfo<crate::id::constants::ArCurve>,
2092                    tonic::Status,
2093                >,
2094            >,
2095        >,
2096    > {
2097        let response = self
2098            .client
2099            .get_anonymity_revokers(&bi.into_block_identifier())
2100            .await?;
2101        let block_hash = extract_metadata(&response)?;
2102        let stream = response.into_inner().map(|result| match result {
2103            Ok(ar_info) => ar_info.try_into(),
2104            Err(err) => Err(err),
2105        });
2106        Ok(QueryResponse {
2107            block_hash,
2108            response: stream,
2109        })
2110    }
2111
2112    /// Get the list of transactions hashes for transactions that claim to be
2113    /// from the given account, but which are not yet finalized.
2114    /// They are either committed to a block or still pending.
2115    /// The stream will end when all the non-finalized transaction hashes have
2116    /// been returned. If the account does not exist an empty list will be
2117    /// returned.
2118    ///
2119    /// This endpoint is not expected to return a large amount of data in most
2120    /// cases, but in bad network condtions it might.
2121    pub async fn get_account_non_finalized_transactions(
2122        &mut self,
2123        account_address: &AccountAddress,
2124    ) -> endpoints::QueryResult<impl Stream<Item = Result<TransactionHash, tonic::Status>>> {
2125        let response = self
2126            .client
2127            .get_account_non_finalized_transactions(account_address)
2128            .await?;
2129        let stream = response.into_inner().map(|result| match result {
2130            Ok(transaction_hash) => transaction_hash.try_into(),
2131            Err(err) => Err(err),
2132        });
2133        Ok(stream)
2134    }
2135
2136    /// Get the block items included in a given block.
2137    /// If the block does not exist [`QueryError::NotFound`] is returned.
2138    /// The stream will end when all the block items in the given block have
2139    /// been returned.
2140    pub async fn get_block_items(
2141        &mut self,
2142        bi: impl IntoBlockIdentifier,
2143    ) -> endpoints::QueryResult<
2144        QueryResponse<impl Stream<Item = Result<BlockItem<EncodedPayload>, tonic::Status>>>,
2145    > {
2146        let response = self
2147            .client
2148            .get_block_items(&bi.into_block_identifier())
2149            .await?;
2150        let block_hash = extract_metadata(&response)?;
2151        let stream = response.into_inner().map(|result| match result {
2152            Ok(summary) => summary.try_into(),
2153            Err(err) => Err(err),
2154        });
2155        Ok(QueryResponse {
2156            block_hash,
2157            response: stream,
2158        })
2159    }
2160
2161    /// Get the specific **block item** if it is finalized.
2162    /// If the transaction does not exist in a finalized block
2163    /// [`QueryError::NotFound`] is returned.
2164    ///
2165    /// **Note that this is not an efficient method** since the node API does
2166    /// not allow for retrieving just the specific block item, but rather
2167    /// requires retrieving the full block. Use it for testing and debugging
2168    /// only.
2169    ///
2170    /// The return value is a triple of the [`BlockItem`], the hash of the block
2171    /// in which it is finalized, and the outcome in the form of
2172    /// [`BlockItemSummary`].
2173    pub async fn get_finalized_block_item(
2174        &mut self,
2175        th: TransactionHash,
2176    ) -> endpoints::QueryResult<(BlockItem<EncodedPayload>, BlockHash, BlockItemSummary)> {
2177        let status = self.get_block_item_status(&th).await?;
2178        let Some((bh, status)) = status.is_finalized() else {
2179            return Err(QueryError::NotFound);
2180        };
2181        let mut response = self.get_block_items(bh).await?.response;
2182        while let Some(tx) = response.try_next().await? {
2183            if tx.hash() == th {
2184                return Ok((tx, *bh, status.clone()));
2185            }
2186        }
2187        Err(endpoints::QueryError::NotFound)
2188    }
2189
2190    /// Shut down the node.
2191    /// Return a GRPC error if the shutdown failed.
2192    pub async fn shutdown(&mut self) -> endpoints::RPCResult<()> {
2193        self.client.shutdown(generated::Empty::default()).await?;
2194        Ok(())
2195    }
2196
2197    /// Suggest a peer to connect to the submitted peer details.
2198    /// This, if successful, adds the peer to the list of given addresses.
2199    /// Otherwise return a GRPC error.
2200    ///
2201    /// Note: The peer might not be connected to instantly, in that case
2202    /// the node will try to establish the connection in near future. This
2203    /// function returns a GRPC status 'Ok' in this case.
2204    pub async fn peer_connect(&mut self, addr: std::net::SocketAddr) -> endpoints::RPCResult<()> {
2205        let peer_connection = generated::IpSocketAddress {
2206            ip:   Some(generated::IpAddress {
2207                value: addr.ip().to_string(),
2208            }),
2209            port: Some(generated::Port {
2210                value: addr.port() as u32,
2211            }),
2212        };
2213        self.client.peer_connect(peer_connection).await?;
2214        Ok(())
2215    }
2216
2217    /// Disconnect from the peer and remove them from the given addresses list
2218    /// if they are on it. Return if the request was processed successfully.
2219    /// Otherwise return a GRPC error.
2220    pub async fn peer_disconnect(
2221        &mut self,
2222        addr: std::net::SocketAddr,
2223    ) -> endpoints::RPCResult<()> {
2224        let peer_connection = generated::IpSocketAddress {
2225            ip:   Some(generated::IpAddress {
2226                value: addr.ip().to_string(),
2227            }),
2228            port: Some(generated::Port {
2229                value: addr.port() as u32,
2230            }),
2231        };
2232        self.client.peer_disconnect(peer_connection).await?;
2233        Ok(())
2234    }
2235
2236    /// Get a vector of the banned peers.
2237    pub async fn get_banned_peers(
2238        &mut self,
2239    ) -> endpoints::RPCResult<Vec<super::types::network::BannedPeer>> {
2240        Ok(self
2241            .client
2242            .get_banned_peers(generated::Empty::default())
2243            .await?
2244            .into_inner()
2245            .peers
2246            .into_iter()
2247            .map(super::types::network::BannedPeer::try_from)
2248            .collect::<anyhow::Result<Vec<super::types::network::BannedPeer>>>()?)
2249    }
2250
2251    /// Ban a peer.
2252    /// When successful return `Ok(())`, and otherwise return an error
2253    /// describing the issue.
2254    pub async fn ban_peer(
2255        &mut self,
2256        peer_to_ban: super::types::network::PeerToBan,
2257    ) -> endpoints::RPCResult<()> {
2258        self.client.ban_peer(peer_to_ban).await?;
2259        Ok(())
2260    }
2261
2262    /// Unban a peer.
2263    /// When successful return `Ok(())`, and otherwise return an error
2264    /// describing the issue.
2265    pub async fn unban_peer(
2266        &mut self,
2267        banned_peer: &super::types::network::BannedPeer,
2268    ) -> endpoints::RPCResult<()> {
2269        self.client.unban_peer(banned_peer).await?;
2270        Ok(())
2271    }
2272
2273    /// Start a network dump if the feature is enabled on the node.
2274    /// This writes all the network packets into the given file.
2275    /// Return `Ok(())` if a network dump has been initiated, and an error
2276    /// otherwise.
2277    ///
2278    /// * file - The file to write to.
2279    /// * raw - Whether raw packets should be included in the dump or not.
2280    ///
2281    /// Note. If the feature 'network_dump' is not enabled on the node then this
2282    /// will return a 'Precondition failed' error.
2283    pub async fn dump_start(
2284        &mut self,
2285        file: &std::path::Path,
2286        raw: bool,
2287    ) -> endpoints::RPCResult<()> {
2288        let file_str = file.to_str().ok_or_else(|| {
2289            tonic::Status::invalid_argument(
2290                "The provided path cannot is not a valid UTF8 string, so cannot be used.",
2291            )
2292        })?;
2293
2294        self.client
2295            .dump_start(generated::DumpRequest {
2296                file: file_str.to_string(),
2297                raw,
2298            })
2299            .await?;
2300        Ok(())
2301    }
2302
2303    /// Stop an ongoing network dump.
2304    /// Return nothing if it was successfully stopped, and otherwise return an
2305    /// error.
2306    ///
2307    /// Note. If the feature 'network_dump' is not enabled on the node then this
2308    /// will return a 'Precondition failed' error.
2309    pub async fn dump_stop(&mut self) -> endpoints::RPCResult<()> {
2310        self.client.dump_stop(generated::Empty::default()).await?;
2311        Ok(())
2312    }
2313
2314    /// Get a list of the peers that the node is connected to and associated
2315    /// network related information for each peer.
2316    pub async fn get_peers_info(&mut self) -> endpoints::RPCResult<types::network::PeersInfo> {
2317        let response = self
2318            .client
2319            .get_peers_info(generated::Empty::default())
2320            .await?;
2321        let peers_info = types::network::PeersInfo::try_from(response.into_inner())?;
2322        Ok(peers_info)
2323    }
2324
2325    /// Retrieve information about the node.
2326    /// The response contains meta information about the node
2327    /// such as the version of the software, the local time of the node etc.
2328    ///
2329    /// The response also yields network related information such as the node
2330    /// ID, bytes sent/received etc.
2331    ///
2332    /// Finally depending on the type of the node (regular node or
2333    /// 'bootstrapper') the response also yields baking information if
2334    /// the node is configured with baker credentials.
2335    ///
2336    /// Bootstrappers do no reveal any consensus information as they do not run
2337    /// the consensus protocol.
2338    pub async fn get_node_info(&mut self) -> endpoints::RPCResult<types::NodeInfo> {
2339        let response = self
2340            .client
2341            .get_node_info(generated::Empty::default())
2342            .await?;
2343        let node_info = types::NodeInfo::try_from(response.into_inner())?;
2344        Ok(node_info)
2345    }
2346
2347    /// Get the projected earliest time a baker wins the opportunity to bake a
2348    /// block.
2349    /// If the baker is not a baker for the current reward period then then the
2350    /// timestamp returned is the projected time of the first block of the
2351    /// new reward period.
2352    /// Note that the endpoint is only available on a node running at least
2353    /// protocol version 6.
2354    pub async fn get_baker_earliest_win_time(
2355        &mut self,
2356        bid: types::BakerId,
2357    ) -> endpoints::RPCResult<chrono::DateTime<chrono::Utc>> {
2358        let ts = self.client.get_baker_earliest_win_time(bid).await?;
2359        let local_time = ts.into_inner().try_into()?;
2360        Ok(local_time)
2361    }
2362
2363    /// Get the transaction events in a given block. If the block does not exist
2364    /// [`QueryError::NotFound`] is returned. The stream will end when all the
2365    /// transaction events for a given block have been returned.
2366    pub async fn get_block_transaction_events(
2367        &mut self,
2368        bi: impl IntoBlockIdentifier,
2369    ) -> endpoints::QueryResult<
2370        QueryResponse<impl Stream<Item = Result<types::BlockItemSummary, tonic::Status>>>,
2371    > {
2372        let response = self
2373            .client
2374            .get_block_transaction_events(&bi.into_block_identifier())
2375            .await?;
2376        let block_hash = extract_metadata(&response)?;
2377        let stream = response.into_inner().map(|result| match result {
2378            Ok(summary) => summary.try_into(),
2379            Err(err) => Err(err),
2380        });
2381        Ok(QueryResponse {
2382            block_hash,
2383            response: stream,
2384        })
2385    }
2386
2387    /// Get a the special events in a given block. If the block does not exist
2388    /// [`QueryError::NotFound`] is returned. The stream will end when all the
2389    /// special events for a given block have been returned.
2390    ///
2391    /// These are events generated by the protocol, such as minting and reward
2392    /// payouts. They are not directly generated by any transaction.
2393    pub async fn get_block_special_events(
2394        &mut self,
2395        bi: impl IntoBlockIdentifier,
2396    ) -> endpoints::QueryResult<
2397        QueryResponse<impl Stream<Item = Result<types::SpecialTransactionOutcome, tonic::Status>>>,
2398    > {
2399        let response = self
2400            .client
2401            .get_block_special_events(&bi.into_block_identifier())
2402            .await?;
2403        let block_hash = extract_metadata(&response)?;
2404        let stream = response.into_inner().map(|result| match result {
2405            Ok(summary) => summary.try_into(),
2406            Err(err) => Err(err),
2407        });
2408        Ok(QueryResponse {
2409            block_hash,
2410            response: stream,
2411        })
2412    }
2413
2414    /// Get the pending updates to chain parameters at the end of a given block.
2415    /// If the block does not exist [`QueryError::NotFound`] is returned.
2416    /// The stream will end when all the pending updates for a given block have
2417    /// been returned.
2418    pub async fn get_block_pending_updates(
2419        &mut self,
2420        bi: impl IntoBlockIdentifier,
2421    ) -> endpoints::QueryResult<
2422        QueryResponse<impl Stream<Item = Result<types::queries::PendingUpdate, tonic::Status>>>,
2423    > {
2424        let response = self
2425            .client
2426            .get_block_pending_updates(&bi.into_block_identifier())
2427            .await?;
2428        let block_hash = extract_metadata(&response)?;
2429        let stream = response.into_inner().map(|result| match result {
2430            Ok(update) => update.try_into(),
2431            Err(err) => Err(err),
2432        });
2433        Ok(QueryResponse {
2434            block_hash,
2435            response: stream,
2436        })
2437    }
2438
2439    /// Get the winning bakers of an historical `Epoch`.
2440    /// Hence, when this function is invoked using [`EpochIdentifier::Block`]
2441    /// and the [`BlockIdentifier`] is either [`BlockIdentifier::Best`] or
2442    /// [`BlockIdentifier::LastFinal`], then [`tonic::Code::Unavailable`] is
2443    /// returned, as these identifiers are not historical by definition.
2444    ///
2445    /// The stream ends when there
2446    /// are no more rounds for the epoch specified. This only works for
2447    /// epochs in at least protocol version 6. Note that the endpoint is
2448    /// only available on a node running at least protocol version 6.
2449    pub async fn get_winning_bakers_epoch(
2450        &mut self,
2451        ei: impl Into<EpochIdentifier>,
2452    ) -> endpoints::QueryResult<impl Stream<Item = Result<types::WinningBaker, tonic::Status>>>
2453    {
2454        let response = self.client.get_winning_bakers_epoch(&ei.into()).await?;
2455        let stream = response.into_inner().map(|result| match result {
2456            Ok(wb) => wb.try_into(),
2457            Err(err) => Err(err),
2458        });
2459        Ok(stream)
2460    }
2461
2462    /// Get the first block of the epoch.
2463    pub async fn get_first_block_epoch(
2464        &mut self,
2465        ei: impl Into<EpochIdentifier>,
2466    ) -> endpoints::QueryResult<BlockHash> {
2467        let response = self.client.get_first_block_epoch(&ei.into()).await?;
2468        Ok(response.into_inner().try_into()?)
2469    }
2470
2471    /// Get the detailed status of the consensus. This is only available for
2472    /// consensus version 1. If the genesis index is not specified, the
2473    /// status for the current genesis index is returned.
2474    pub async fn get_consensus_detailed_status(
2475        &mut self,
2476        genesis_index: Option<GenesisIndex>,
2477    ) -> endpoints::RPCResult<ConsensusDetailedStatus> {
2478        let query = generated::ConsensusDetailedStatusQuery {
2479            genesis_index: genesis_index.map(Into::into),
2480        };
2481        let response = self.client.get_consensus_detailed_status(query).await?;
2482        Ok(response.into_inner().try_into()?)
2483    }
2484
2485    /// Get next available sequence numbers for updating chain parameters after
2486    /// a given block. If the block does not exist then [`QueryError::NotFound`]
2487    /// is returned.
2488    pub async fn get_next_update_sequence_numbers(
2489        &mut self,
2490        block_id: impl IntoBlockIdentifier,
2491    ) -> endpoints::QueryResult<QueryResponse<types::queries::NextUpdateSequenceNumbers>> {
2492        let response = self
2493            .client
2494            .get_next_update_sequence_numbers(&block_id.into_block_identifier())
2495            .await?;
2496        let block_hash = extract_metadata(&response)?;
2497        let response = types::queries::NextUpdateSequenceNumbers::try_from(response.into_inner())?;
2498        Ok(QueryResponse {
2499            block_hash,
2500            response,
2501        })
2502    }
2503
2504    /// Get all accounts that have scheduled releases, with the timestamp of the
2505    /// first pending scheduled release for that account. (Note, this only
2506    /// identifies accounts by index, and only indicates the first pending
2507    /// release for each account.)
2508    pub async fn get_scheduled_release_accounts(
2509        &mut self,
2510        block_id: impl IntoBlockIdentifier,
2511    ) -> endpoints::QueryResult<
2512        QueryResponse<impl Stream<Item = Result<AccountPending, tonic::Status>>>,
2513    > {
2514        let response = self
2515            .client
2516            .get_scheduled_release_accounts(&block_id.into_block_identifier())
2517            .await?;
2518        let block_hash = extract_metadata(&response)?;
2519        let stream = response.into_inner().map(|result| match result {
2520            Ok(pending) => pending.try_into(),
2521            Err(err) => Err(err),
2522        });
2523        Ok(QueryResponse {
2524            block_hash,
2525            response: stream,
2526        })
2527    }
2528
2529    /// Get all accounts that have stake in cooldown, with the timestamp of the
2530    /// first pending cooldown expiry for each account. (Note, this only
2531    /// identifies accounts by index, and only indicates the first pending
2532    /// cooldown for each account.) Prior to protocol version 7, the
2533    /// resulting stream will always be empty.
2534    pub async fn get_cooldown_accounts(
2535        &mut self,
2536        block_id: impl IntoBlockIdentifier,
2537    ) -> endpoints::QueryResult<
2538        QueryResponse<impl Stream<Item = Result<AccountPending, tonic::Status>>>,
2539    > {
2540        let response = self
2541            .client
2542            .get_cooldown_accounts(&block_id.into_block_identifier())
2543            .await?;
2544        let block_hash = extract_metadata(&response)?;
2545        let stream = response.into_inner().map(|result| match result {
2546            Ok(pending) => pending.try_into(),
2547            Err(err) => Err(err),
2548        });
2549        Ok(QueryResponse {
2550            block_hash,
2551            response: stream,
2552        })
2553    }
2554
2555    /// Get all accounts that have stake in pre-cooldown.
2556    /// (This only identifies accounts by index.)
2557    /// Prior to protocol version 7, the resulting stream will always be empty.
2558    pub async fn get_pre_cooldown_accounts(
2559        &mut self,
2560        block_id: impl IntoBlockIdentifier,
2561    ) -> endpoints::QueryResult<
2562        QueryResponse<impl Stream<Item = Result<AccountIndex, tonic::Status>>>,
2563    > {
2564        let response = self
2565            .client
2566            .get_pre_cooldown_accounts(&block_id.into_block_identifier())
2567            .await?;
2568        let block_hash = extract_metadata(&response)?;
2569        let stream = response.into_inner().map(|result| match result {
2570            Ok(account) => Ok(account.into()),
2571            Err(err) => Err(err),
2572        });
2573        Ok(QueryResponse {
2574            block_hash,
2575            response: stream,
2576        })
2577    }
2578
2579    /// Get all accounts that have stake in pre-pre-cooldown.
2580    /// (This only identifies accounts by index.)
2581    /// Prior to protocol version 7, the resulting stream will always be empty.
2582    pub async fn get_pre_pre_cooldown_accounts(
2583        &mut self,
2584        block_id: impl IntoBlockIdentifier,
2585    ) -> endpoints::QueryResult<
2586        QueryResponse<impl Stream<Item = Result<AccountIndex, tonic::Status>>>,
2587    > {
2588        let response = self
2589            .client
2590            .get_pre_pre_cooldown_accounts(&block_id.into_block_identifier())
2591            .await?;
2592        let block_hash = extract_metadata(&response)?;
2593        let stream = response.into_inner().map(|result| match result {
2594            Ok(account) => Ok(account.into()),
2595            Err(err) => Err(err),
2596        });
2597        Ok(QueryResponse {
2598            block_hash,
2599            response: stream,
2600        })
2601    }
2602
2603    /// Get the chain parameters in effect after a given block.
2604    /// If the block does not exist [`QueryError::NotFound`] is returned.
2605    pub async fn get_block_chain_parameters(
2606        &mut self,
2607        block_id: impl IntoBlockIdentifier,
2608    ) -> endpoints::QueryResult<QueryResponse<ChainParameters>> {
2609        let response = self
2610            .client
2611            .get_block_chain_parameters(&block_id.into_block_identifier())
2612            .await?;
2613        let block_hash = extract_metadata(&response)?;
2614        let response = ChainParameters::try_from(response.into_inner())?;
2615        Ok(QueryResponse {
2616            block_hash,
2617            response,
2618        })
2619    }
2620
2621    /// For a non-genesis block, this returns the
2622    /// [`QuorumCertificate`](block_certificates::QuorumCertificate), a
2623    /// [`TimeoutCertificate`](block_certificates::TimeoutCertificate) (if
2624    /// present)
2625    /// and [`EpochFinalizationEntry`](block_certificates::EpochFinalizationEntry) (if
2626    /// present).
2627    /// If the block being pointed to is *not* from protocol version 6 or
2628    /// above, then [`InvalidArgument`](`tonic::Code::InvalidArgument`)
2629    /// is returned.
2630    pub async fn get_block_certificates(
2631        &mut self,
2632        bi: impl IntoBlockIdentifier,
2633    ) -> endpoints::QueryResult<QueryResponse<block_certificates::BlockCertificates>> {
2634        let response = self
2635            .client
2636            .get_block_certificates(&bi.into_block_identifier())
2637            .await?;
2638        let block_hash = extract_metadata(&response)?;
2639        let response = block_certificates::BlockCertificates::try_from(response.into_inner())?;
2640        Ok(QueryResponse {
2641            block_hash,
2642            response,
2643        })
2644    }
2645
2646    /// Get the information about a finalization record in a block.
2647    /// A block can contain zero or one finalization record. If a record is
2648    /// contained then this query will return information about the finalization
2649    /// session that produced it, including the finalizers eligible for the
2650    /// session, their power, and whether they signed this particular record. If
2651    /// the block does not exist [`QueryError::NotFound`] is returned.
2652    pub async fn get_block_finalization_summary(
2653        &mut self,
2654        block_id: impl IntoBlockIdentifier,
2655    ) -> endpoints::QueryResult<QueryResponse<Option<types::FinalizationSummary>>> {
2656        let response = self
2657            .client
2658            .get_block_finalization_summary(&block_id.into_block_identifier())
2659            .await?;
2660        let block_hash = extract_metadata(&response)?;
2661        let response = response.into_inner().try_into()?;
2662        Ok(QueryResponse {
2663            block_hash,
2664            response,
2665        })
2666    }
2667
2668    /// Get a continous stream of finalized blocks starting from a given height.
2669    /// This function starts a background task (a `tokio` task) that listens for
2670    /// new finalized blocks. This task is killed when the
2671    /// [`FinalizedBlocksStream`] is dropped.
2672    pub async fn get_finalized_blocks_from(
2673        &mut self,
2674        start_height: AbsoluteBlockHeight,
2675    ) -> endpoints::QueryResult<FinalizedBlocksStream> {
2676        let mut fin_height = self.get_consensus_info().await?.last_finalized_block_height;
2677        let (sender, receiver) = tokio::sync::mpsc::channel(100);
2678        let mut client = self.clone();
2679        let handle = tokio::spawn(async move {
2680            let mut height = start_height;
2681            loop {
2682                if height > fin_height {
2683                    fin_height = client
2684                        .get_consensus_info()
2685                        .await?
2686                        .last_finalized_block_height;
2687                    if height > fin_height {
2688                        break;
2689                    }
2690                } else {
2691                    let mut bi = client.get_blocks_at_height(&height.into()).await?;
2692                    let block_hash = bi.pop().ok_or(endpoints::QueryError::NotFound)?;
2693                    let info = FinalizedBlockInfo { block_hash, height };
2694                    if sender.send(info).await.is_err() {
2695                        return Ok(());
2696                    }
2697                    height = height.next();
2698                }
2699            }
2700            let mut stream = client.get_finalized_blocks().await?;
2701            while let Some(fbi) = stream.next().await.transpose()? {
2702                // recover missed blocks.
2703                while height < fbi.height {
2704                    let mut bi = client.get_blocks_at_height(&height.into()).await?;
2705                    let block_hash = bi.pop().ok_or(endpoints::QueryError::NotFound)?;
2706                    let info = FinalizedBlockInfo { block_hash, height };
2707                    if sender.send(info).await.is_err() {
2708                        return Ok(());
2709                    }
2710                    height = height.next();
2711                }
2712                if sender.send(fbi).await.is_err() {
2713                    return Ok(());
2714                }
2715                height = height.next();
2716            }
2717            Ok(())
2718        });
2719        Ok(FinalizedBlocksStream { handle, receiver })
2720    }
2721
2722    /// Find a block in which the account was created, if it exists and is
2723    /// finalized. The return value is a triple of the absolute block height and
2724    /// the corresponding block hash, and the account information at the
2725    /// end of that block. The block is the first block in which the account
2726    /// appears.
2727    ///
2728    /// Note that this is not necessarily the initial state of the account
2729    /// since there can be transactions updating it in the same block that it is
2730    /// created.
2731    ///
2732    /// Optional bounds can be provided, and the search will only
2733    /// consider blocks in that range. If the lower bound is not
2734    /// provided it defaults to 0, if the upper bound is not provided it
2735    /// defaults to the last finalized block at the time of the call.
2736    ///
2737    /// If the account cannot be found [`QueryError::NotFound`] is returned.
2738    pub async fn find_account_creation(
2739        &mut self,
2740        range: impl std::ops::RangeBounds<AbsoluteBlockHeight>,
2741        addr: AccountAddress,
2742    ) -> QueryResult<(AbsoluteBlockHeight, BlockHash, AccountInfo)> {
2743        self.find_at_lowest_height(range, |mut client, height| async move {
2744            match client.get_account_info(&addr.into(), &height).await {
2745                Ok(ii) => Ok(Some((height, ii.block_hash, ii.response))),
2746                Err(e) if e.is_not_found() => Ok(None),
2747                Err(e) => Err(e),
2748            }
2749        })
2750        .await
2751    }
2752
2753    /// Find a block in which the instance was created, if it exists and is
2754    /// finalized. The return value is a triple of the absolute block height and
2755    /// the corresponding block hash, and the instance information at the
2756    /// end of that block. The block is the first block in which the instance
2757    /// appears.
2758    ///
2759    /// Note that this is not necessarily the initial state of the instance
2760    /// since there can be transactions updating the instance in the same block
2761    /// as the initialization transaction.
2762    ///
2763    /// Optional bounds can be provided, and the search will only
2764    /// consider blocks in that range. If the lower bound is not
2765    /// provided it defaults to 0, if the upper bound is not provided it
2766    /// defaults to the last finalized block at the time of the call.
2767    ///
2768    /// If the instance cannot be found [`QueryError::NotFound`] is returned.
2769    pub async fn find_instance_creation(
2770        &mut self,
2771        range: impl std::ops::RangeBounds<AbsoluteBlockHeight>,
2772        addr: ContractAddress,
2773    ) -> QueryResult<(AbsoluteBlockHeight, BlockHash, InstanceInfo)> {
2774        self.find_at_lowest_height(range, |mut client, height| async move {
2775            match client.get_instance_info(addr, &height).await {
2776                Ok(ii) => Ok(Some((height, ii.block_hash, ii.response))),
2777                Err(e) if e.is_not_found() => Ok(None),
2778                Err(e) => Err(e),
2779            }
2780        })
2781        .await
2782    }
2783
2784    /// Find the first (i.e., earliest) finalized block whose slot time is no
2785    /// earlier than the specified time. If a block is not found return
2786    /// [`QueryError::NotFound`].
2787    ///
2788    /// The search is limited to the bounds specified. If the lower bound is not
2789    /// provided it defaults to 0, if the upper bound is not provided it
2790    /// defaults to the last finalized block at the time of the call.
2791    pub async fn find_first_finalized_block_no_earlier_than(
2792        &mut self,
2793        range: impl std::ops::RangeBounds<AbsoluteBlockHeight>,
2794        time: chrono::DateTime<chrono::Utc>,
2795    ) -> QueryResult<types::queries::BlockInfo> {
2796        self.find_at_lowest_height(range, move |mut client, height| async move {
2797            let info = client.get_block_info(&height).await?.response;
2798            if info.block_slot_time >= time {
2799                Ok(Some(info))
2800            } else {
2801                Ok(None)
2802            }
2803        })
2804        .await
2805    }
2806
2807    /// Find a **finalized** block with lowest height that satisfies the given
2808    /// condition. If a block is not found return [`QueryError::NotFound`].
2809    ///
2810    /// The `test` method should return `Some` if the object is found in the
2811    /// block, and `None` otherwise. It can also signal errors which will
2812    /// terminate search immediately.
2813    ///
2814    /// The precondition for this method is that the `test` method is monotone,
2815    /// i.e., if block at height `h` satisfies the test then also a block at
2816    /// height `h+1` does.
2817    /// If this precondition does not hold then the return value from this
2818    /// method is unspecified.
2819    ///
2820    /// The search is limited to at most the given range, the upper bound is
2821    /// always at most the last finalized block at the time of the call. If the
2822    /// lower bound is not provided it defaults to 0, if the upper bound is
2823    /// not provided it defaults to the last finalized block at the time of
2824    /// the call.
2825    pub async fn find_at_lowest_height<A, F: futures::Future<Output = QueryResult<Option<A>>>>(
2826        &mut self,
2827        range: impl std::ops::RangeBounds<AbsoluteBlockHeight>,
2828        test: impl Fn(Self, AbsoluteBlockHeight) -> F,
2829    ) -> QueryResult<A> {
2830        let mut start = match range.start_bound() {
2831            std::ops::Bound::Included(s) => u64::from(*s),
2832            std::ops::Bound::Excluded(e) => u64::from(*e).saturating_add(1),
2833            std::ops::Bound::Unbounded => 0,
2834        };
2835        let mut end = {
2836            let ci = self.get_consensus_info().await?;
2837            let bound = |end: u64| std::cmp::min(end, ci.last_finalized_block_height.into());
2838            match range.end_bound() {
2839                std::ops::Bound::Included(e) => bound(u64::from(*e)),
2840                std::ops::Bound::Excluded(e) => {
2841                    bound(u64::from(*e).checked_sub(1).ok_or(QueryError::NotFound)?)
2842                }
2843                std::ops::Bound::Unbounded => u64::from(ci.last_finalized_block_height),
2844            }
2845        };
2846        if end < start {
2847            return Err(QueryError::NotFound);
2848        }
2849        let mut last_found = None;
2850        while start < end {
2851            let mid = start + (end - start) / 2;
2852            let ok = test(self.clone(), mid.into()).await?;
2853            if ok.is_some() {
2854                end = mid;
2855                last_found = ok;
2856            } else {
2857                start = mid + 1;
2858            }
2859        }
2860        last_found.ok_or(QueryError::NotFound)
2861    }
2862
2863    #[deprecated(note = "Use [`find_at_lowest_height`](./struct.Client.html#method.\
2864                         find_at_lowest_height) instead since it avoids an extra call.")]
2865    pub async fn find_earliest_finalized<A, F: futures::Future<Output = QueryResult<Option<A>>>>(
2866        &mut self,
2867        range: impl std::ops::RangeBounds<AbsoluteBlockHeight>,
2868        test: impl Fn(Self, AbsoluteBlockHeight, BlockHash) -> F,
2869    ) -> QueryResult<A> {
2870        let mut start = match range.start_bound() {
2871            std::ops::Bound::Included(s) => u64::from(*s),
2872            std::ops::Bound::Excluded(e) => u64::from(*e).saturating_add(1),
2873            std::ops::Bound::Unbounded => 0,
2874        };
2875        let mut end = {
2876            let ci = self.get_consensus_info().await?;
2877            let bound = |end: u64| std::cmp::min(end, ci.last_finalized_block_height.into());
2878            match range.end_bound() {
2879                std::ops::Bound::Included(e) => bound(u64::from(*e)),
2880                std::ops::Bound::Excluded(e) => {
2881                    bound(u64::from(*e).checked_sub(1).ok_or(QueryError::NotFound)?)
2882                }
2883                std::ops::Bound::Unbounded => u64::from(ci.last_finalized_block_height),
2884            }
2885        };
2886        if end < start {
2887            return Err(QueryError::NotFound);
2888        }
2889        let mut last_found = None;
2890        while start < end {
2891            let mid = start + (end - start) / 2;
2892            let bh = self
2893                .get_blocks_at_height(&AbsoluteBlockHeight::from(mid).into())
2894                .await?[0]; // using [0] is safe since we are only looking at finalized blocks.
2895            let ok = test(self.clone(), mid.into(), bh).await?;
2896            if ok.is_some() {
2897                end = mid;
2898                last_found = ok;
2899            } else {
2900                start = mid + 1;
2901            }
2902        }
2903        last_found.ok_or(QueryError::NotFound)
2904    }
2905
2906    /// Get all bakers in the reward period of a block.
2907    /// This endpoint is only supported for protocol version 4 and onwards.
2908    /// If the protocol does not support the endpoint then an
2909    /// [`IllegalArgument`](tonic::Code::InvalidArgument) is returned.
2910    pub async fn get_bakers_reward_period(
2911        &mut self,
2912        bi: impl IntoBlockIdentifier,
2913    ) -> endpoints::QueryResult<
2914        QueryResponse<impl Stream<Item = Result<types::BakerRewardPeriodInfo, tonic::Status>>>,
2915    > {
2916        let response = self
2917            .client
2918            .get_bakers_reward_period(&bi.into_block_identifier())
2919            .await?;
2920        let block_hash = extract_metadata(&response)?;
2921        let stream = response.into_inner().map(|result| match result {
2922            Ok(baker) => baker.try_into(),
2923            Err(err) => Err(err),
2924        });
2925        Ok(QueryResponse {
2926            block_hash,
2927            response: stream,
2928        })
2929    }
2930}
2931
2932/// A stream of finalized blocks. This contains a background task that polls
2933/// for new finalized blocks indefinitely. The task can be stopped by dropping
2934/// the object.
2935pub struct FinalizedBlocksStream {
2936    handle:   tokio::task::JoinHandle<endpoints::QueryResult<()>>,
2937    receiver: tokio::sync::mpsc::Receiver<FinalizedBlockInfo>,
2938}
2939
2940// Make sure to abort the background task so that those resources are cleaned up
2941// before we drop the handle.
2942impl Drop for FinalizedBlocksStream {
2943    fn drop(&mut self) { self.handle.abort(); }
2944}
2945
2946impl FinalizedBlocksStream {
2947    /// Retrieves the next finalized block from the stream. This function will
2948    /// block until a finalized block becomes available. To avoid waiting
2949    /// indefinitely, consider using [`FinalizedBlocksStream::next_timeout`]
2950    /// instead. If the channel is closed, the next element is `None`.
2951    pub async fn next(&mut self) -> Option<FinalizedBlockInfo> { self.receiver.recv().await }
2952
2953    /// Similar to [`FinalizedBlocksStream::next`], but with a maximum wait time
2954    /// defined by the specified duration between each finalized block.
2955    pub async fn next_timeout(
2956        &mut self,
2957        duration: std::time::Duration,
2958    ) -> Result<Option<FinalizedBlockInfo>, tokio::time::error::Elapsed> {
2959        tokio::time::timeout(duration, async move { self.next().await }).await
2960    }
2961
2962    /// Get the next chunk of blocks. If the finalized block poller has been
2963    /// disconnected this will return `Err(blocks)` where `blocks` are the
2964    /// finalized blocks that were retrieved before closure. In that case
2965    /// all further calls will return `Err(Vec::new())`.
2966    ///
2967    /// In case of success up to `max(1, n)` elements will be returned. This
2968    /// function will block so it always returns at least one element, and
2969    /// will retrieve up to `n` elements without blocking further once at least
2970    /// one element has been acquired.
2971    pub async fn next_chunk(
2972        &mut self,
2973        n: usize,
2974    ) -> Result<Vec<FinalizedBlockInfo>, Vec<FinalizedBlockInfo>> {
2975        let mut out = Vec::with_capacity(n);
2976        let first = self.receiver.recv().await;
2977        match first {
2978            Some(v) => out.push(v),
2979            None => {
2980                return Err(out);
2981            }
2982        }
2983        for _ in 1..n {
2984            match self.receiver.try_recv() {
2985                Ok(v) => {
2986                    out.push(v);
2987                }
2988                Err(tokio::sync::mpsc::error::TryRecvError::Empty) => {
2989                    break;
2990                }
2991                Err(tokio::sync::mpsc::error::TryRecvError::Disconnected) => return Err(out),
2992            }
2993        }
2994        Ok(out)
2995    }
2996
2997    /// Like [`next_chunk`](Self::next_chunk), but waits no more than the given
2998    /// duration for the block. The boolean signifies whether an error
2999    /// occurred (it is `true` if an error occurred) while getting blocks.
3000    /// If that is the case further calls will always yield an error.
3001    ///
3002    /// The first field of the response indicates if an error occurred. This
3003    /// will only happen if the stream of finalized blocks has unexpectedly
3004    /// dropped.
3005    pub async fn next_chunk_timeout(
3006        &mut self,
3007        n: usize,
3008        duration: std::time::Duration,
3009    ) -> Result<(bool, Vec<FinalizedBlockInfo>), tokio::time::error::Elapsed> {
3010        let mut out = Vec::with_capacity(n);
3011        let first = self.next_timeout(duration).await?;
3012        match first {
3013            Some(v) => out.push(v),
3014            None => return Ok((true, out)),
3015        }
3016        for _ in 1..n {
3017            match self.receiver.try_recv() {
3018                Ok(v) => {
3019                    out.push(v);
3020                }
3021                Err(tokio::sync::mpsc::error::TryRecvError::Empty) => {
3022                    break;
3023                }
3024                Err(tokio::sync::mpsc::error::TryRecvError::Disconnected) => {
3025                    return Ok((true, out))
3026                }
3027            }
3028        }
3029        Ok((false, out))
3030    }
3031}
3032
3033fn extract_metadata<T>(response: &tonic::Response<T>) -> endpoints::RPCResult<BlockHash> {
3034    match response.metadata().get("blockhash") {
3035        Some(bytes) => {
3036            let bytes = bytes.as_bytes();
3037            if bytes.len() == 64 {
3038                let mut hash = [0u8; 32];
3039                if hex::decode_to_slice(bytes, &mut hash).is_err() {
3040                    tonic::Status::unknown("Response does correctly encode the block hash.");
3041                }
3042                Ok(hash.into())
3043            } else {
3044                Err(endpoints::RPCError::CallError(tonic::Status::unknown(
3045                    "Response does not include the expected metadata.",
3046                )))
3047            }
3048        }
3049        None => Err(endpoints::RPCError::CallError(tonic::Status::unknown(
3050            "Response does not include the expected metadata.",
3051        ))),
3052    }
3053}
3054
3055/// A helper trait to make it simpler to require specific fields when parsing a
3056/// protobuf message by allowing us to use method calling syntax and
3057/// constructing responses that match the calling context, allowing us to use
3058/// the `?` syntax.
3059///
3060/// The main reason for needing this is that in proto3 all fields are optional,
3061/// so it is up to the application to validate inputs if they are required.
3062trait Require<E> {
3063    type A;
3064    fn require(self) -> Result<Self::A, E>;
3065}
3066
3067impl<A> Require<tonic::Status> for Option<A> {
3068    type A = A;
3069
3070    fn require(self) -> Result<Self::A, tonic::Status> {
3071        match self {
3072            Some(v) => Ok(v),
3073            None => Err(tonic::Status::invalid_argument("missing field in response")),
3074        }
3075    }
3076}
3077
3078#[cfg(test)]
3079mod tests {
3080    use super::*;
3081    #[test]
3082    /// Test the different cases when parsing BlockIdentifiers.
3083    fn block_ident_from_str() -> anyhow::Result<()> {
3084        let b1 = "best".parse::<BlockIdentifier>()?;
3085        assert_eq!(b1, BlockIdentifier::Best);
3086
3087        let b2 = "lastFinal".parse::<BlockIdentifier>()?;
3088        assert_eq!(b2, BlockIdentifier::LastFinal);
3089
3090        let b3 = "lastfinal".parse::<BlockIdentifier>()?;
3091        assert_eq!(b3, BlockIdentifier::LastFinal);
3092
3093        let b4 = "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
3094            .parse::<BlockIdentifier>()?;
3095        assert_eq!(
3096            b4,
3097            BlockIdentifier::Given(
3098                "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff".parse()?
3099            )
3100        );
3101
3102        let b5 = "@33".parse::<BlockIdentifier>()?;
3103        assert_eq!(b5, BlockIdentifier::AbsoluteHeight(33.into()));
3104
3105        let b6 = "@33/3".parse::<BlockIdentifier>()?;
3106        assert_eq!(
3107            b6,
3108            BlockIdentifier::RelativeHeight(RelativeBlockHeight {
3109                genesis_index: 3.into(),
3110                height:        33.into(),
3111                restrict:      false,
3112            })
3113        );
3114
3115        let b7 = "@33/3!".parse::<BlockIdentifier>()?;
3116        assert_eq!(
3117            b7,
3118            BlockIdentifier::RelativeHeight(RelativeBlockHeight {
3119                genesis_index: 3.into(),
3120                height:        33.into(),
3121                restrict:      true,
3122            })
3123        );
3124
3125        Ok(())
3126    }
3127}