diff --git a/cli/src/config.rs b/cli/src/config.rs index 056adbbd564..d47b5ae1ff7 100644 --- a/cli/src/config.rs +++ b/cli/src/config.rs @@ -17,7 +17,7 @@ use serde::{Deserialize, Serialize}; use super::torii::config::ToriiConfiguration; /// Configuration parameters container. -#[derive(Clone, Deserialize, Serialize, Debug, Configurable)] +#[derive(Debug, Clone, Deserialize, Serialize, Configurable)] #[serde(default)] #[serde(rename_all = "UPPERCASE")] #[config(env_prefix = "IROHA_")] @@ -63,22 +63,16 @@ pub struct Configuration { } impl Default for Configuration { - #[allow(clippy::expect_used)] fn default() -> Self { - let public_key = "ed01201c61faf8fe94e253b93114240394f79a607b7fa55f9e5a41ebec74b88055768b" - .parse() - .expect("Public key not in mulithash format"); - let private_key = PrivateKey::from_hex( - Algorithm::Ed25519, - "282ed9f3cf92811c3818dbc4ae594ed59dc1a2f78e4241e31924e101d6b1fb831c61faf8fe94e253b93114240394f79a607b7fa55f9e5a41ebec74b88055768b" - ).expect("Private key not hex encoded"); + let sumeragi_configuration = SumeragiConfiguration::default(); + let (public_key, private_key) = sumeragi_configuration.key_pair.clone().into(); Self { public_key, private_key, disable_panic_terminal_colors: bool::default(), kura: KuraConfiguration::default(), - sumeragi: SumeragiConfiguration::default(), + sumeragi: sumeragi_configuration, torii: ToriiConfiguration::default(), block_sync: BlockSyncConfiguration::default(), queue: QueueConfiguration::default(), @@ -93,7 +87,7 @@ impl Default for Configuration { } /// Network Configuration parameters container. -#[derive(Clone, Copy, Deserialize, Serialize, Debug, Configurable, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize, Configurable)] #[serde(default)] #[serde(rename_all = "UPPERCASE")] #[config(env_prefix = "IROHA_NETWORK_")] diff --git a/cli/src/lib.rs b/cli/src/lib.rs index a2cce114664..dc85199ec9a 100644 --- a/cli/src/lib.rs +++ b/cli/src/lib.rs @@ -165,7 +165,7 @@ where WorldStateView::from_configuration( config.wsv, W::with( - domains(&config).wrap_err("Failed to get initial domains")?, + domains(&config), config.sumeragi.trusted_peers.peers.clone(), ), ) @@ -307,12 +307,7 @@ where /// /// # Errors /// - Genesis account public key not specified. -fn domains(configuration: &config::Configuration) -> Result> { - let key = configuration - .genesis - .account_public_key - .clone() - .ok_or_else(|| eyre!("Genesis account public key is not specified."))?; - - Ok([Domain::from(GenesisDomain::new(key))].into_iter()) +fn domains(configuration: &config::Configuration) -> impl Iterator { + let key = configuration.genesis.account_public_key.clone(); + [Domain::from(GenesisDomain::new(key))].into_iter() } diff --git a/cli/src/samples.rs b/cli/src/samples.rs index 09f8ecfe715..a1d6bda4384 100644 --- a/cli/src/samples.rs +++ b/cli/src/samples.rs @@ -97,7 +97,7 @@ pub fn get_config(trusted_peers: HashSet, key_pair: Option) -> ..QueueConfiguration::default() }, genesis: GenesisConfiguration { - account_public_key: Some(public_key), + account_public_key: public_key, account_private_key: Some(private_key), ..GenesisConfiguration::default() }, diff --git a/cli/src/torii/tests.rs b/cli/src/torii/tests.rs index 582bc600a6a..74b186fbb58 100644 --- a/cli/src/torii/tests.rs +++ b/cli/src/torii/tests.rs @@ -693,11 +693,7 @@ async fn blocks_stream() { fn domains( configuration: &crate::config::Configuration, ) -> eyre::Result> { - let key = configuration - .genesis - .account_public_key - .clone() - .ok_or_else(|| eyre!("Genesis account public key is not specified."))?; + let key = configuration.genesis.account_public_key.clone(); Ok([Domain::from(GenesisDomain::new(key))].into_iter()) } @@ -709,7 +705,7 @@ fn hash_should_be_the_same() { Some(key_pair.clone()), ); config.genesis.account_private_key = Some(key_pair.private_key().clone()); - config.genesis.account_public_key = Some(key_pair.public_key().clone()); + config.genesis.account_public_key = key_pair.public_key().clone(); let tx = Transaction::new( AccountId::new( diff --git a/client/src/config.rs b/client/src/config.rs index 1ffe1698ea2..ac2794ba5ed 100644 --- a/client/src/config.rs +++ b/client/src/config.rs @@ -97,20 +97,13 @@ pub struct Configuration { } impl Default for Configuration { - #[allow(clippy::expect_used)] fn default() -> Self { - let public_key = "ed01207233bfc89dcbd68c19fde6ce6158225298ec1131b6a130d1aeb454c1ab5183c0" - .parse() - .expect("Public key not in mulithash format"); - let private_key = PrivateKey::from_hex( - Algorithm::Ed25519, - "9ac47abf59b356e0bd7dcbbbb4dec080e302156a48ca907e47cb6aea1d32719e7233bfc89dcbd68c19fde6ce6158225298ec1131b6a130d1aeb454c1ab5183c0" - ).expect("Private key not hex encoded"); + let (public_key, private_key) = Self::placeholder_keypair().into(); Self { public_key, private_key, - account_id: AccountId::from_str("").expect("Empty strings are valid"), + account_id: Self::placeholder_account(), basic_auth: None, torii_api_url: small::SmallStr::from_str(uri::DEFAULT_API_URL), torii_telemetry_url: small::SmallStr::from_str(DEFAULT_TORII_TELEMETRY_URL), @@ -127,6 +120,26 @@ impl Default for Configuration { } impl Configuration { + /// Key-pair used by default for demo purposes + #[allow(clippy::expect_used)] + fn placeholder_keypair() -> KeyPair { + let public_key = "ed01207233bfc89dcbd68c19fde6ce6158225298ec1131b6a130d1aeb454c1ab5183c0" + .parse() + .expect("Public key not in mulithash format"); + let private_key = PrivateKey::from_hex( + Algorithm::Ed25519, + "9ac47abf59b356e0bd7dcbbbb4dec080e302156a48ca907e47cb6aea1d32719e7233bfc89dcbd68c19fde6ce6158225298ec1131b6a130d1aeb454c1ab5183c0" + ).expect("Private key not hex encoded"); + + KeyPair::new(public_key, private_key) + } + + /// Account ID used by default for demo purposes + #[allow(clippy::expect_used)] + fn placeholder_account() -> ::Id { + AccountId::from_str("alice@wonderland").expect("Account ID not valid") + } + /// This method will build `Configuration` from a json *pretty* formatted file (without `:` in /// key names). /// diff --git a/core/src/genesis.rs b/core/src/genesis.rs index 526c262dd3d..3cc8650ce5c 100644 --- a/core/src/genesis.rs +++ b/core/src/genesis.rs @@ -185,10 +185,7 @@ impl GenesisNetworkTrait for GenesisNetwork { .iter() .map(|raw_transaction| { let genesis_key_pair = KeyPair::new( - genesis_config - .account_public_key - .clone() - .ok_or_else(|| eyre!("Genesis account public key is empty."))?, + genesis_config.account_public_key.clone(), genesis_config .account_private_key .clone() @@ -310,22 +307,22 @@ impl GenesisTransaction { /// Module with genesis configuration logic. pub mod config { use iroha_config::derive::Configurable; - use iroha_crypto::{PrivateKey, PublicKey}; + use iroha_crypto::{KeyPair, PrivateKey, PublicKey}; use serde::{Deserialize, Serialize}; const DEFAULT_WAIT_FOR_PEERS_RETRY_COUNT: u64 = 100; const DEFAULT_WAIT_FOR_PEERS_RETRY_PERIOD_MS: u64 = 500; const DEFAULT_GENESIS_SUBMISSION_DELAY_MS: u64 = 1000; - #[derive(Clone, Deserialize, Serialize, Debug, Configurable, PartialEq, Eq)] + #[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize, Configurable)] + #[serde(default)] #[serde(rename_all = "UPPERCASE")] #[config(env_prefix = "IROHA_GENESIS_")] /// Configuration of the genesis block and the process of its submission. pub struct GenesisConfiguration { /// The genesis account public key, should be supplied to all peers. - /// The type is `Option` just because it might be loaded from environment variables and not from `config.json`. #[config(serde_as_str)] - pub account_public_key: Option, + pub account_public_key: PublicKey, /// Genesis account private key, only needed on the peer that submits the genesis block. pub account_private_key: Option, /// Number of attempts to connect to peers, while waiting for them to submit genesis. @@ -340,11 +337,14 @@ pub mod config { pub genesis_submission_delay_ms: u64, } + #[allow(clippy::expect_used)] impl Default for GenesisConfiguration { fn default() -> Self { + let (public_key, private_key) = Self::placeholder_keypair().into(); + Self { - account_public_key: None, - account_private_key: None, + account_public_key: public_key, + account_private_key: Some(private_key), wait_for_peers_retry_count: DEFAULT_WAIT_FOR_PEERS_RETRY_COUNT, wait_for_peers_retry_period_ms: DEFAULT_WAIT_FOR_PEERS_RETRY_PERIOD_MS, genesis_submission_delay_ms: DEFAULT_GENESIS_SUBMISSION_DELAY_MS, @@ -352,6 +352,23 @@ pub mod config { } } + impl GenesisConfiguration { + /// Key-pair used by default for demo purposes + #[allow(clippy::expect_used)] + fn placeholder_keypair() -> KeyPair { + let public_key = + "ed01204cffd0ee429b1bdd36b3910ec570852b8bb63f18750341772fb46bc856c5caaf" + .parse() + .expect("Public key not in mulithash format"); + let private_key = PrivateKey::from_hex( + iroha_crypto::Algorithm::Ed25519, + "d748e18ce60cb30dea3e73c9019b7af45a8d465e3d71bcc9a5ef99a008205e534cffd0ee429b1bdd36b3910ec570852b8bb63f18750341772fb46bc856c5caaf" + ).expect("Private key not hex encoded"); + + KeyPair::new(public_key, private_key) + } + } + const fn default_wait_for_peers_retry_count() -> u64 { DEFAULT_WAIT_FOR_PEERS_RETRY_COUNT } @@ -467,7 +484,7 @@ mod tests { true, RawGenesisBlock::default(), &GenesisConfiguration { - account_public_key: Some(public_key), + account_public_key: public_key, account_private_key: Some(private_key), ..GenesisConfiguration::default() }, diff --git a/core/src/sumeragi/config.rs b/core/src/sumeragi/config.rs index 51f7aacd8d9..34bb8d58d07 100644 --- a/core/src/sumeragi/config.rs +++ b/core/src/sumeragi/config.rs @@ -52,26 +52,11 @@ pub struct SumeragiConfiguration { } impl Default for SumeragiConfiguration { - #[allow(clippy::expect_used)] fn default() -> Self { - let public_key: PublicKey = - "ed01201c61faf8fe94e253b93114240394f79a607b7fa55f9e5a41ebec74b88055768b" - .parse() - .expect("Public key not in mulithash format"); - let private_key = PrivateKey::from_hex( - Algorithm::Ed25519, - "282ed9f3cf92811c3818dbc4ae594ed59dc1a2f78e4241e31924e101d6b1fb831c61faf8fe94e253b93114240394f79a607b7fa55f9e5a41ebec74b88055768b" - ).expect("Private key not hex encoded"); - - let peer_id = PeerId { - address: "127.0.0.1".to_owned(), - public_key: public_key.clone(), - }; - Self { - key_pair: KeyPair::new(public_key, private_key), - trusted_peers: TrustedPeers::default(), - peer_id, + key_pair: Self::placeholder_keypair(), + peer_id: Self::placeholder_peer_id(), + trusted_peers: Self::placeholder_trusted_peers(), block_time_ms: DEFAULT_BLOCK_TIME_MS, commit_time_ms: DEFAULT_COMMIT_TIME_MS, tx_receipt_time_ms: DEFAULT_TX_RECEIPT_TIME_MS, @@ -88,6 +73,35 @@ impl Default for SumeragiConfiguration { } impl SumeragiConfiguration { + /// Key-pair used by default for demo purposes + #[allow(clippy::expect_used)] + fn placeholder_keypair() -> KeyPair { + let public_key = "ed01201c61faf8fe94e253b93114240394f79a607b7fa55f9e5a41ebec74b88055768b" + .parse() + .expect("Public key not in mulithash format"); + let private_key = PrivateKey::from_hex( + Algorithm::Ed25519, + "282ed9f3cf92811c3818dbc4ae594ed59dc1a2f78e4241e31924e101d6b1fb831c61faf8fe94e253b93114240394f79a607b7fa55f9e5a41ebec74b88055768b" + ).expect("Private key not hex encoded"); + + KeyPair::new(public_key, private_key) + } + + fn placeholder_peer_id() -> PeerId { + let (public_key, _) = Self::placeholder_keypair().into(); + + PeerId { + address: "127.0.0.1:1337".to_owned(), + public_key, + } + } + + fn placeholder_trusted_peers() -> TrustedPeers { + let mut peers = HashSet::new(); + peers.insert(Self::placeholder_peer_id()); + TrustedPeers { peers } + } + /// Set `trusted_peers` configuration parameter. Will overwrite /// existing `trusted_peers` but does not check for duplication. #[inline] diff --git a/crypto/src/merkle.rs b/crypto/src/merkle.rs index d45241140dc..41bd03e1a5f 100644 --- a/crypto/src/merkle.rs +++ b/crypto/src/merkle.rs @@ -106,7 +106,7 @@ impl MerkleTree { } /// Return the `Hash` of the root node. - pub fn root_hash(&self) -> HashOf { + pub const fn root_hash(&self) -> HashOf { self.root_node.hash().transmute() } @@ -165,7 +165,7 @@ impl Node { } /// Return the `Hash` of the root node. - pub fn hash(&self) -> HashOf { + pub const fn hash(&self) -> HashOf { match self { Node::Subtree(Subtree { hash, .. }) => *hash, Node::Leaf(Leaf { hash }) => (*hash).transmute(), diff --git a/crypto/src/signature.rs b/crypto/src/signature.rs index 53d6aaa256e..a1908203ff1 100644 --- a/crypto/src/signature.rs +++ b/crypto/src/signature.rs @@ -55,7 +55,7 @@ pub struct Signature { public_key: PublicKey, /// Actual signature payload is placed here. #[getset(skip)] - signature: Payload, + payload: Payload, } impl Signature { @@ -83,7 +83,7 @@ impl Signature { Ok(Self { public_key, - signature, + payload: signature, }) } @@ -106,14 +106,12 @@ impl Signature { let public_key = UrsaPublicKey(self.public_key.payload.clone()); match algorithm { - Algorithm::Ed25519 => { - Ed25519Sha512::new().verify(payload, &self.signature, &public_key) - } + Algorithm::Ed25519 => Ed25519Sha512::new().verify(payload, &self.payload, &public_key), Algorithm::Secp256k1 => { - EcdsaSecp256k1Sha256::new().verify(payload, &self.signature, &public_key) + EcdsaSecp256k1Sha256::new().verify(payload, &self.payload, &public_key) } - Algorithm::BlsSmall => BlsSmall::new().verify(payload, &self.signature, &public_key), - Algorithm::BlsNormal => BlsNormal::new().verify(payload, &self.signature, &public_key), + Algorithm::BlsSmall => BlsSmall::new().verify(payload, &self.payload, &public_key), + Algorithm::BlsNormal => BlsNormal::new().verify(payload, &self.payload, &public_key), }?; Ok(()) @@ -124,7 +122,7 @@ impl fmt::Debug for Signature { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct(core::any::type_name::()) .field("public_key", &self.public_key) - .field("signature", &hex::encode_upper(self.signature.as_slice())) + .field("signature", &hex::encode_upper(self.payload.as_slice())) .finish() } } @@ -133,7 +131,7 @@ impl From for (PublicKey, Payload) { fn from( Signature { public_key, - signature, + payload: signature, }: Signature, ) -> Self { (public_key, signature) diff --git a/data_model/src/query.rs b/data_model/src/query.rs index d9c74ed6293..1e9ab9830f9 100644 --- a/data_model/src/query.rs +++ b/data_model/src/query.rs @@ -204,7 +204,7 @@ pub mod role { Serialize, IntoSchema, )] - pub struct FindAllRoles {} + pub struct FindAllRoles; impl Query for FindAllRoles { type Output = Vec; @@ -309,7 +309,7 @@ pub mod account { Serialize, IntoSchema, )] - pub struct FindAllAccounts {} + pub struct FindAllAccounts; impl Query for FindAllAccounts { type Output = Vec; @@ -415,7 +415,7 @@ pub mod account { impl FindAllAccounts { /// Construct [`FindAllAccounts`]. pub const fn new() -> Self { - FindAllAccounts {} + FindAllAccounts } } @@ -494,7 +494,7 @@ pub mod asset { Serialize, IntoSchema, )] - pub struct FindAllAssets {} + pub struct FindAllAssets; impl Query for FindAllAssets { type Output = Vec; @@ -517,7 +517,7 @@ pub mod asset { Serialize, IntoSchema, )] - pub struct FindAllAssetsDefinitions {} + pub struct FindAllAssetsDefinitions; impl Query for FindAllAssetsDefinitions { type Output = Vec; @@ -748,14 +748,14 @@ pub mod asset { impl FindAllAssets { /// Construct [`FindAllAssets`]. pub const fn new() -> Self { - FindAllAssets {} + FindAllAssets } } impl FindAllAssetsDefinitions { /// Construct [`FindAllAssetsDefinitions`]. pub const fn new() -> Self { - FindAllAssetsDefinitions {} + FindAllAssetsDefinitions } } @@ -874,7 +874,7 @@ pub mod domain { Serialize, IntoSchema, )] - pub struct FindAllDomains {} + pub struct FindAllDomains; impl Query for FindAllDomains { type Output = Vec; @@ -906,7 +906,7 @@ pub mod domain { impl FindAllDomains { /// Construct [`FindAllDomains`]. pub const fn new() -> Self { - FindAllDomains {} + FindAllDomains } } @@ -991,7 +991,7 @@ pub mod peer { Serialize, IntoSchema, )] - pub struct FindAllPeers {} + pub struct FindAllPeers; impl Query for FindAllPeers { type Output = Vec; @@ -1013,7 +1013,7 @@ pub mod peer { Serialize, IntoSchema, )] - pub struct FindAllParameters {} + pub struct FindAllParameters; impl Query for FindAllParameters { type Output = Vec; @@ -1022,14 +1022,14 @@ pub mod peer { impl FindAllPeers { ///Construct [`FindAllPeers`]. pub const fn new() -> Self { - FindAllPeers {} + FindAllPeers } } impl FindAllParameters { /// Construct [`FindAllParameters`]. pub const fn new() -> Self { - FindAllParameters {} + FindAllParameters } } /// The prelude re-exports most commonly used traits, structs and macros from this crate. diff --git a/docs/source/references/config.md b/docs/source/references/config.md index 093048dae64..54f4b6319af 100644 --- a/docs/source/references/config.md +++ b/docs/source/references/config.md @@ -22,11 +22,16 @@ The following is the default configuration used by Iroha. }, "SUMERAGI": { "PEER_ID": { - "address": "127.0.0.1", + "address": "127.0.0.1:1337", "public_key": "ed01201c61faf8fe94e253b93114240394f79a607b7fa55f9e5a41ebec74b88055768b" }, "BLOCK_TIME_MS": 1000, - "TRUSTED_PEERS": [], + "TRUSTED_PEERS": [ + { + "address": "127.0.0.1:1337", + "public_key": "ed01201c61faf8fe94e253b93114240394f79a607b7fa55f9e5a41ebec74b88055768b" + } + ], "COMMIT_TIME_MS": 2000, "TX_RECEIPT_TIME_MS": 500, "N_TOPOLOGY_SHIFTS_BEFORE_RESHUFFLE": 1, @@ -64,8 +69,11 @@ The following is the default configuration used by Iroha. "TERMINAL_COLORS": true }, "GENESIS": { - "ACCOUNT_PUBLIC_KEY": null, - "ACCOUNT_PRIVATE_KEY": null, + "ACCOUNT_PUBLIC_KEY": "ed01204cffd0ee429b1bdd36b3910ec570852b8bb63f18750341772fb46bc856c5caaf", + "ACCOUNT_PRIVATE_KEY": { + "digest_function": "ed25519", + "payload": "d748e18ce60cb30dea3e73c9019b7af45a8d465e3d71bcc9a5ef99a008205e534cffd0ee429b1bdd36b3910ec570852b8bb63f18750341772fb46bc856c5caaf" + }, "WAIT_FOR_PEERS_RETRY_COUNT": 100, "WAIT_FOR_PEERS_RETRY_PERIOD_MS": 500, "GENESIS_SUBMISSION_DELAY_MS": 1000 @@ -167,8 +175,11 @@ Has type `GenesisConfiguration`. Can be configured via environment variable `IRO ```json { - "ACCOUNT_PRIVATE_KEY": null, - "ACCOUNT_PUBLIC_KEY": null, + "ACCOUNT_PRIVATE_KEY": { + "digest_function": "ed25519", + "payload": "d748e18ce60cb30dea3e73c9019b7af45a8d465e3d71bcc9a5ef99a008205e534cffd0ee429b1bdd36b3910ec570852b8bb63f18750341772fb46bc856c5caaf" + }, + "ACCOUNT_PUBLIC_KEY": "ed01204cffd0ee429b1bdd36b3910ec570852b8bb63f18750341772fb46bc856c5caaf", "GENESIS_SUBMISSION_DELAY_MS": 1000, "WAIT_FOR_PEERS_RETRY_COUNT": 100, "WAIT_FOR_PEERS_RETRY_PERIOD_MS": 500 @@ -182,17 +193,20 @@ Genesis account private key, only needed on the peer that submits the genesis bl Has type `Option`. Can be configured via environment variable `IROHA_GENESIS_ACCOUNT_PRIVATE_KEY` ```json -null +{ + "digest_function": "ed25519", + "payload": "d748e18ce60cb30dea3e73c9019b7af45a8d465e3d71bcc9a5ef99a008205e534cffd0ee429b1bdd36b3910ec570852b8bb63f18750341772fb46bc856c5caaf" +} ``` ### `genesis.account_public_key` The genesis account public key, should be supplied to all peers. -Has type `Option`. Can be configured via environment variable `IROHA_GENESIS_ACCOUNT_PUBLIC_KEY` +Has type `PublicKey`. Can be configured via environment variable `IROHA_GENESIS_ACCOUNT_PUBLIC_KEY` ```json -null +"ed01204cffd0ee429b1bdd36b3910ec570852b8bb63f18750341772fb46bc856c5caaf" ``` ### `genesis.genesis_submission_delay_ms` @@ -461,14 +475,19 @@ Has type `SumeragiConfiguration`. Can be configured via environment variable `IR "MAILBOX": 100, "N_TOPOLOGY_SHIFTS_BEFORE_RESHUFFLE": 1, "PEER_ID": { - "address": "127.0.0.1", + "address": "127.0.0.1:1337", "public_key": "ed01201c61faf8fe94e253b93114240394f79a607b7fa55f9e5a41ebec74b88055768b" }, "TRANSACTION_LIMITS": { "max_instruction_number": 4096, "max_wasm_size_bytes": 4194304 }, - "TRUSTED_PEERS": [], + "TRUSTED_PEERS": [ + { + "address": "127.0.0.1:1337", + "public_key": "ed01201c61faf8fe94e253b93114240394f79a607b7fa55f9e5a41ebec74b88055768b" + } + ], "TX_RECEIPT_TIME_MS": 500 } ``` @@ -557,7 +576,7 @@ Has type `PeerId`. Can be configured via environment variable `SUMERAGI_PEER_ID` ```json { - "address": "127.0.0.1", + "address": "127.0.0.1:1337", "public_key": "ed01201c61faf8fe94e253b93114240394f79a607b7fa55f9e5a41ebec74b88055768b" } ``` @@ -582,7 +601,12 @@ Optional list of predefined trusted peers. Has type `TrustedPeers`. Can be configured via environment variable `SUMERAGI_TRUSTED_PEERS` ```json -[] +[ + { + "address": "127.0.0.1:1337", + "public_key": "ed01201c61faf8fe94e253b93114240394f79a607b7fa55f9e5a41ebec74b88055768b" + } +] ``` ### `sumeragi.tx_receipt_time_ms`