diff --git a/.gitignore b/.gitignore index 38c08dd1cb..1cd885951e 100644 --- a/.gitignore +++ b/.gitignore @@ -26,3 +26,5 @@ book # vscode **/.vscode/ + +**/.env diff --git a/Cargo.lock b/Cargo.lock index eb3f84922f..409cd66082 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -360,9 +360,10 @@ dependencies = [ [[package]] name = "bee-inx" -version = "1.0.0-beta.6" +version = "1.0.0-rc.1" dependencies = [ "bee-block", + "dotenvy", "futures", "inx", "packable", @@ -1322,6 +1323,15 @@ dependencies = [ "winapi", ] +[[package]] +name = "dotenvy" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed9155c8f4dc55c7470ae9da3f63c6785245093b3f6aeb0f5bf2e968efbba314" +dependencies = [ + "dirs", +] + [[package]] name = "dtoa" version = "1.0.3" diff --git a/bee-inx/CHANGELOG.md b/bee-inx/CHANGELOG.md index 0048f1ce23..7d8ce33de9 100644 --- a/bee-inx/CHANGELOG.md +++ b/bee-inx/CHANGELOG.md @@ -19,6 +19,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Security --> +## 1.0.0-rc.1 - 2022-09-29 + +### Added + +- Missing INX bindings +- Examples +- Documentation + ## 1.0.0-beta.6 - 2022-09-26 ### Added diff --git a/bee-inx/Cargo.toml b/bee-inx/Cargo.toml index 2eb527a03b..b7bf94c775 100644 --- a/bee-inx/Cargo.toml +++ b/bee-inx/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "bee-inx" -version = "1.0.0-beta.6" +version = "1.0.0-rc.1" authors = [ "IOTA Stiftung" ] edition = "2021" description = "Rust bindings for IOTA node extensions (INX)" @@ -20,5 +20,6 @@ packable = { version = "0.6.2", default-features = false } [dev-dependencies] bee-block = { version = "1.0.0", path = "../bee-block", default-features = false, features = [ "inx", "rand" ] } +dotenvy = { version = "0.15.5", default-features = false } tokio = { version = "1.20.1", default-features = false, features = [ "macros", "rt-multi-thread" ] } diff --git a/bee-inx/examples/block.rs b/bee-inx/examples/block.rs new file mode 100644 index 0000000000..416f9c9a8d --- /dev/null +++ b/bee-inx/examples/block.rs @@ -0,0 +1,84 @@ +// Copyright 2022 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use bee_block::BlockId; +use bee_inx::{client::Inx, Error}; +use futures::StreamExt; + +#[tokio::main] +async fn main() -> Result<(), Error> { + dotenvy::dotenv().ok(); + let inx_connect_url = std::env::var("INX_CONNECT_URL").unwrap_or_else(|_| "http://localhost:9029".to_string()); + let block_stream = std::env::var("BLOCK_STREAM").unwrap_or_else(|_| "blocks".to_string()); + let read_block = std::env::var("READ_BLOCK") + .unwrap_or_else(|_| "false".to_string()) + .parse::() + .unwrap(); + let read_block_metadata = std::env::var("READ_BLOCK_METADATA") + .unwrap_or_else(|_| "false".to_string()) + .parse::() + .unwrap(); + + let mut inx = Inx::connect(&inx_connect_url).await?; + println!("Connected via INX to node at {inx_connect_url}"); + + match block_stream.as_str() { + "blocks" => { + let mut block_stream = inx.listen_to_blocks().await?; + println!("Streaming blocks ... "); + + while let Some(block) = block_stream.next().await { + let block = block?; + println!("{}", block.block_id); + + fetch_block_and_metadata(&mut inx, block.block_id, read_block, read_block_metadata).await?; + } + } + "solid_blocks" => { + let mut block_stream = inx.listen_to_solid_blocks().await?; + println!("Streaming solid blocks ... "); + + while let Some(block_metadata) = block_stream.next().await { + let block_metadata = block_metadata?; + println!("{}", block_metadata.block_id); + + fetch_block_and_metadata(&mut inx, block_metadata.block_id, read_block, read_block_metadata).await?; + } + } + "referenced_blocks" => { + let mut block_stream = inx.listen_to_referenced_blocks().await?; + println!("Streaming referenced blocks ... "); + + while let Some(block_metadata) = block_stream.next().await { + let block_metadata = block_metadata?; + println!("{}", block_metadata.block_id); + + fetch_block_and_metadata(&mut inx, block_metadata.block_id, read_block, read_block_metadata).await?; + } + } + _ => { + panic!("unknown block stream variant: '{block_stream}'"); + } + } + + Ok(()) +} + +async fn fetch_block_and_metadata( + inx: &mut Inx, + block_id: BlockId, + read_block: bool, + read_block_metadata: bool, +) -> Result<(), Error> { + if read_block { + let raw_block = inx.read_block(block_id).await?; + println!("{:?}", raw_block.inner_unverified()); + } + + if read_block_metadata { + let block_metadata = inx.read_block_metadata(block_id).await?; + println!("{:?}", block_metadata); + } + + Ok(()) +} diff --git a/bee-inx/examples/milestone.rs b/bee-inx/examples/milestone.rs index 7bdbe17fa7..f4365fc3c1 100644 --- a/bee-inx/examples/milestone.rs +++ b/bee-inx/examples/milestone.rs @@ -1,33 +1,96 @@ // Copyright 2022 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -use bee_inx::{client, Error}; +use bee_block::payload::milestone::MilestoneIndex; +use bee_inx::{client::Inx, Error}; use futures::StreamExt; -const INX_ADDRESS: &str = "http://localhost:9029"; - #[tokio::main] async fn main() -> Result<(), Error> { - let mut inx = client::Inx::connect(INX_ADDRESS.into()).await?; - let mut milestone_stream = inx.listen_to_confirmed_milestones((..).into()).await?; + dotenvy::dotenv().ok(); + let inx_connect_url = std::env::var("INX_CONNECT_URL").unwrap_or_else(|_| "http://localhost:9029".to_string()); + let milestone_stream = std::env::var("MILESTONE_STREAM").unwrap_or_else(|_| "confirmed_milestones".to_string()); + let read_cone = std::env::var("READ_CONE") + .unwrap_or_else(|_| "false".to_string()) + .parse::() + .unwrap(); + let read_cone_metadata = std::env::var("READ_CONE_METADATA") + .unwrap_or_else(|_| "false".to_string()) + .parse::() + .unwrap(); + + let mut inx = Inx::connect(&inx_connect_url).await?; + println!("Connected via INX to node at {inx_connect_url}"); + + match milestone_stream.as_str() { + "confirmed_milestones" => { + let mut milestone_stream = inx.listen_to_confirmed_milestones((..).into()).await?; + println!("Streaming confirmed milestones and protocol parameters... "); + + while let Some(milestone_and_params) = milestone_stream.next().await { + let milestone_and_params = milestone_and_params?; + println!( + "{:?}{:?}", + milestone_and_params.milestone.milestone_info, milestone_and_params.current_protocol_parameters + ); + + let milestone_index = milestone_and_params.milestone.milestone_info.milestone_index; + fetch_cone_and_metadata(&mut inx, milestone_index, read_cone, read_cone_metadata).await?; + } + } + "latest_milestones" => { + println!("Streaming latest milestones... "); - // Listen to the milestones from the node. - while let Some(milestone_and_params) = milestone_stream.next().await { - let milestone_index = milestone_and_params?.milestone.milestone_info.milestone_index; - println!("Fetch cone of milestone {milestone_index}"); + let mut milestone_stream = inx.listen_to_latest_milestones().await?; + + while let Some(milestone) = milestone_stream.next().await { + let milestone = milestone?; + println!("{:?}", milestone.milestone_info); + + let milestone_index = milestone.milestone_info.milestone_index; + fetch_cone_and_metadata(&mut inx, milestone_index, read_cone, read_cone_metadata).await?; + } + } + _ => { + panic!("unknown milestone stream variant: '{milestone_stream}'"); + } + } + + Ok(()) +} + +async fn fetch_cone_and_metadata( + inx: &mut Inx, + milestone_index: MilestoneIndex, + read_cone: bool, + read_cone_metadata: bool, +) -> Result<(), Error> { + if read_cone { + println!("Fetching cone for {milestone_index}..."); - // Listen to blocks in the past cone of a milestone. let mut cone_stream = inx.read_milestone_cone(milestone_index.0.into()).await?; + let mut count = 0usize; + + while let Some(Ok(block_metadata)) = cone_stream.next().await { + println!("\t{}", block_metadata.metadata.block_id); + count += 1; + } + + println!("Fetched {count} blocks in total."); + } + + if read_cone_metadata { + println!("Fetching cone metadata for {milestone_index}..."); - // Keep track of the number of blocks. + let mut cone_stream = inx.read_milestone_cone_metadata(milestone_index.0.into()).await?; let mut count = 0usize; while let Some(Ok(block_metadata)) = cone_stream.next().await { - println!("Received block with id `{}`", block_metadata.metadata.block_id); + println!("\t{}", block_metadata.block_id); count += 1; } - println!("Milestone `{:?}` contained {count} blocks", milestone_index); + println!("Fetched {count} blocks in total."); } Ok(()) diff --git a/bee-inx/examples/node.rs b/bee-inx/examples/node.rs new file mode 100644 index 0000000000..b6781c4836 --- /dev/null +++ b/bee-inx/examples/node.rs @@ -0,0 +1,44 @@ +// Copyright 2022 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use bee_inx::{client, milestone::requests::MilestoneRequest, node::requests::NodeStatusRequest, Error}; +use futures::StreamExt; + +#[tokio::main] +async fn main() -> Result<(), Error> { + dotenvy::dotenv().ok(); + let inx_connect_url = std::env::var("INX_CONNECT_URL").unwrap_or_else(|_| "http://localhost:9029".to_string()); + + let mut inx = client::Inx::connect(&inx_connect_url).await?; + println!("Connected via INX to node at {inx_connect_url}"); + + let node_status = inx.read_node_status().await?; + println!("{:?}", node_status); + + let node_configuration = inx.read_node_configuration().await?; + println!("{:?}", node_configuration); + + let protocol_parameters = inx + .read_protocol_parameters(MilestoneRequest::MilestoneIndex(node_status.ledger_index)) + .await?; + println!("{:?}", protocol_parameters); + + const COOLDOWN_MS: u32 = 5000; + let mut node_status_stream = inx + .listen_to_node_status(NodeStatusRequest { + cooldown_in_milliseconds: COOLDOWN_MS, + }) + .await?; + println!("Streaming current node status ... "); + + while let Some(node_status) = node_status_stream.next().await { + let node_status = node_status?; + + println!( + "healthy: {} | synced: {} | ledger_index: {}", + node_status.is_healthy, node_status.is_synced, node_status.ledger_index + ); + } + + Ok(()) +} diff --git a/bee-inx/examples/utxo.rs b/bee-inx/examples/utxo.rs new file mode 100644 index 0000000000..3f2c4529ef --- /dev/null +++ b/bee-inx/examples/utxo.rs @@ -0,0 +1,31 @@ +// Copyright 2022 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use bee_inx::{client::Inx, Error, MilestoneRangeRequest}; +use futures::StreamExt; + +#[tokio::main] +async fn main() -> Result<(), Error> { + dotenvy::dotenv().ok(); + let inx_connect_url = std::env::var("INX_CONNECT_URL").unwrap_or_else(|_| "http://localhost:9029".to_string()); + + let mut inx = Inx::connect(&inx_connect_url).await?; + println!("Connected via INX to node at {inx_connect_url}"); + + let mut unspent_outputs = inx.read_unspent_outputs().await?; + + let mut count = 0; + while let Some(_unspent_output) = unspent_outputs.next().await { + count += 1; + } + println!("Read {count} unspent outputs."); + + let mut ledger_update_feed = inx.listen_to_ledger_updates(MilestoneRangeRequest::from(..)).await?; + + while let Some(ledger_update) = ledger_update_feed.next().await { + let ledger_update = ledger_update?; + println!("{:?}", ledger_update); + } + + Ok(()) +} diff --git a/bee-inx/src/block.rs b/bee-inx/src/block.rs deleted file mode 100644 index 53c92828c0..0000000000 --- a/bee-inx/src/block.rs +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright 2022 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -use bee_block as bee; -use inx::proto; - -use crate::{maybe_missing, Raw}; - -/// The [`Block`] type. -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct Block { - /// The [`BlockId`](bee::BlockId) of the block. - pub block_id: bee::BlockId, - /// The complete [`Block`](bee::Block) as raw bytes. - pub block: Raw, -} - -/// The [`BlockWithMetadata`] type. -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct BlockWithMetadata { - /// The [`Metadata`](crate::BlockMetadata) of the block. - pub metadata: crate::BlockMetadata, - /// The complete [`Block`](bee::Block) as raw bytes. - pub block: Raw, -} - -impl TryFrom for BlockWithMetadata { - type Error = bee::InxError; - - fn try_from(value: proto::BlockWithMetadata) -> Result { - Ok(BlockWithMetadata { - metadata: maybe_missing!(value.metadata).try_into()?, - block: maybe_missing!(value.block).data.into(), - }) - } -} - -impl From for proto::BlockWithMetadata { - fn from(value: BlockWithMetadata) -> Self { - Self { - metadata: Some(value.metadata.into()), - block: Some(value.block.into()), - } - } -} - -impl TryFrom for Block { - type Error = bee::InxError; - - fn try_from(value: proto::Block) -> Result { - Ok(Block { - block_id: maybe_missing!(value.block_id).try_into()?, - block: maybe_missing!(value.block).data.into(), - }) - } -} - -impl From for proto::Block { - fn from(value: Block) -> Self { - Self { - block_id: Some(value.block_id.into()), - block: Some(value.block.into()), - } - } -} diff --git a/bee-inx/src/block/mod.rs b/bee-inx/src/block/mod.rs new file mode 100644 index 0000000000..454738cf64 --- /dev/null +++ b/bee-inx/src/block/mod.rs @@ -0,0 +1,81 @@ +// Copyright 2022 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +/// A module that provides block related INX responses. +pub mod responses; + +use futures::stream::{Stream, StreamExt}; + +pub use self::responses::*; +use crate::{ + bee, + client::{try_from_inx_type, Inx}, + error::Error, + inx, + raw::Raw, +}; + +impl Inx { + /// Listens to all blocks. + pub async fn listen_to_blocks(&mut self) -> Result>, Error> { + Ok(self + .client + .listen_to_blocks(inx::NoParams {}) + .await? + .into_inner() + .map(try_from_inx_type)) + } + + /// Listens to solid blocks. + pub async fn listen_to_solid_blocks(&mut self) -> Result>, Error> { + Ok(self + .client + .listen_to_solid_blocks(inx::NoParams {}) + .await? + .into_inner() + .map(try_from_inx_type)) + } + + /// Listens to referenced blocks. + pub async fn listen_to_referenced_blocks( + &mut self, + ) -> Result>, Error> { + Ok(self + .client + .listen_to_referenced_blocks(inx::NoParams {}) + .await? + .into_inner() + .map(try_from_inx_type)) + } + + /// Requests the block with the given block id. + pub async fn read_block(&mut self, block_id: bee::BlockId) -> Result, Error> { + Ok(self + .client + .read_block(inx::BlockId::from(block_id)) + .await? + .into_inner() + .data + .into()) + } + + /// Requests the metadata of the block with the given block id. + pub async fn read_block_metadata(&mut self, block_id: bee::BlockId) -> Result { + Ok(self + .client + .read_block_metadata(inx::BlockId::from(block_id)) + .await? + .into_inner() + .try_into()?) + } + + /// Submits a block and returns its corresponding block id. + pub async fn submit_block(&mut self, raw_block: Raw) -> Result { + Ok(self + .client + .submit_block(inx::RawBlock { data: raw_block.data() }) + .await? + .into_inner() + .try_into()?) + } +} diff --git a/bee-inx/src/metadata.rs b/bee-inx/src/block/responses.rs similarity index 54% rename from bee-inx/src/metadata.rs rename to bee-inx/src/block/responses.rs index 923dc2a078..3f96bdf9e0 100644 --- a/bee-inx/src/metadata.rs +++ b/bee-inx/src/block/responses.rs @@ -1,17 +1,64 @@ // Copyright 2022 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -use bee_block as bee; -use inx::proto; +use crate::{bee, inx, raw::Raw, return_err_if_none}; -use crate::maybe_missing; +/// The [`Block`] type. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Block { + /// The [`BlockId`](bee::BlockId) of the block. + pub block_id: bee::BlockId, + /// The complete [`Block`](bee::Block) as raw bytes. + pub block: Raw, +} -#[allow(missing_docs)] +impl TryFrom for Block { + type Error = bee::InxError; + + fn try_from(value: inx::Block) -> Result { + Ok(Block { + block_id: return_err_if_none!(value.block_id).try_into()?, + block: return_err_if_none!(value.block).data.into(), + }) + } +} + +impl From for inx::Block { + fn from(value: Block) -> Self { + Self { + block_id: Some(value.block_id.into()), + block: Some(value.block.into()), + } + } +} + +/// The [`BlockWithMetadata`] type. #[derive(Clone, Debug, PartialEq, Eq)] -pub enum LedgerInclusionState { - NoTransaction, - Included, - Conflicting, +pub struct BlockWithMetadata { + /// The [`Metadata`](crate::BlockMetadata) of the block. + pub metadata: crate::BlockMetadata, + /// The complete [`Block`](bee::Block) as raw bytes. + pub block: Raw, +} + +impl TryFrom for BlockWithMetadata { + type Error = bee::InxError; + + fn try_from(value: inx::BlockWithMetadata) -> Result { + Ok(BlockWithMetadata { + metadata: return_err_if_none!(value.metadata).try_into()?, + block: return_err_if_none!(value.block).data.into(), + }) + } +} + +impl From for inx::BlockWithMetadata { + fn from(value: BlockWithMetadata) -> Self { + Self { + metadata: Some(value.metadata.into()), + block: Some(value.block.into()), + } + } } /// The metadata for a block with a given [`BlockId`](bee::BlockId). @@ -34,15 +81,15 @@ pub struct BlockMetadata { /// Indicates if a block is part of the ledger state or not. pub ledger_inclusion_state: LedgerInclusionState, /// Indicates if a conflict occurred, and if so holds information about the reason for the conflict. - pub conflict_reason: bee::semantic::ConflictReason, + pub conflict_reason: bee::ConflictReason, /// The whiteflag index of this block inside the milestone. pub white_flag_index: u32, } -impl TryFrom for BlockMetadata { +impl TryFrom for BlockMetadata { type Error = bee::InxError; - fn try_from(value: proto::BlockMetadata) -> Result { + fn try_from(value: inx::BlockMetadata) -> Result { let ledger_inclusion_state = value.ledger_inclusion_state().into(); let conflict_reason = value.conflict_reason().into(); @@ -53,7 +100,7 @@ impl TryFrom for BlockMetadata { .collect::, _>>()?; Ok(BlockMetadata { - block_id: maybe_missing!(value.block_id).try_into()?, + block_id: return_err_if_none!(value.block_id).try_into()?, parents: parents.into_boxed_slice(), is_solid: value.solid, should_promote: value.should_promote, @@ -67,9 +114,27 @@ impl TryFrom for BlockMetadata { } } -impl From for LedgerInclusionState { - fn from(value: proto::block_metadata::LedgerInclusionState) -> Self { - use proto::block_metadata::LedgerInclusionState::*; +impl From for inx::BlockMetadata { + fn from(value: BlockMetadata) -> Self { + Self { + block_id: Some(value.block_id.into()), + parents: value.parents.into_vec().into_iter().map(Into::into).collect(), + solid: value.is_solid, + should_promote: value.should_promote, + should_reattach: value.should_reattach, + referenced_by_milestone_index: value.referenced_by_milestone_index, + milestone_index: value.milestone_index, + ledger_inclusion_state: inx::block_metadata::LedgerInclusionState::from(value.ledger_inclusion_state) + .into(), + conflict_reason: inx::block_metadata::ConflictReason::from(value.conflict_reason).into(), + white_flag_index: value.white_flag_index, + } + } +} + +impl From for LedgerInclusionState { + fn from(value: inx::LedgerInclusionState) -> Self { + use crate::inx::LedgerInclusionState::*; match value { NoTransaction => LedgerInclusionState::NoTransaction, Included => LedgerInclusionState::Included, @@ -78,7 +143,7 @@ impl From for LedgerInclusionState } } -impl From for proto::block_metadata::LedgerInclusionState { +impl From for inx::LedgerInclusionState { fn from(value: LedgerInclusionState) -> Self { match value { LedgerInclusionState::NoTransaction => Self::NoTransaction, @@ -88,20 +153,11 @@ impl From for proto::block_metadata::LedgerInclusionState } } -impl From for proto::BlockMetadata { - fn from(value: BlockMetadata) -> Self { - Self { - block_id: Some(value.block_id.into()), - parents: value.parents.into_vec().into_iter().map(Into::into).collect(), - solid: value.is_solid, - should_promote: value.should_promote, - should_reattach: value.should_reattach, - referenced_by_milestone_index: value.referenced_by_milestone_index, - milestone_index: value.milestone_index, - ledger_inclusion_state: proto::block_metadata::LedgerInclusionState::from(value.ledger_inclusion_state) - .into(), - conflict_reason: proto::block_metadata::ConflictReason::from(value.conflict_reason).into(), - white_flag_index: value.white_flag_index, - } - } +/// Whether a block contains a transaction that is either included or conflicting, or contains no transaction at all. +#[allow(missing_docs)] +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum LedgerInclusionState { + NoTransaction, + Included, + Conflicting, } diff --git a/bee-inx/src/client.rs b/bee-inx/src/client.rs index 97595be2fa..2ab2782f6c 100644 --- a/bee-inx/src/client.rs +++ b/bee-inx/src/client.rs @@ -1,110 +1,37 @@ // Copyright 2022 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -use futures::stream::{Stream, StreamExt}; -use inx::{proto, proto::inx_client::InxClient, tonic}; +use ::inx::{proto::inx_client::InxClient, tonic}; -use crate::{Error, Milestone, MilestoneRangeRequest, MilestoneRequest, NodeConfiguration, NodeStatus}; +use crate::error::Error; /// An INX client connection. #[derive(Clone, Debug)] pub struct Inx { - inx: InxClient, -} - -fn unpack_proto_msg(msg: Result) -> Result -where - Bee: TryFrom, -{ - let inner = msg.map_err(Error::StatusCode)?; - Bee::try_from(inner).map_err(Error::InxError) + pub(crate) client: InxClient, } impl Inx { /// Connect to the INX interface of a node. - pub async fn connect(address: String) -> Result { + pub async fn connect(address: impl ToString) -> Result { Ok(Self { - inx: InxClient::connect(address).await?, + client: InxClient::connect(address.to_string()).await?, }) } +} - /// Listens to confirmed milestones in the range of - pub async fn listen_to_confirmed_milestones( - &mut self, - request: MilestoneRangeRequest, - ) -> Result>, Error> { - Ok(self - .inx - .listen_to_confirmed_milestones(proto::MilestoneRangeRequest::from(request)) - .await? - .into_inner() - .map(unpack_proto_msg)) - } - - pub async fn listen_to_ledger_updates( - &mut self, - request: MilestoneRangeRequest, - ) -> Result>, Error> { - Ok(self - .inx - .listen_to_ledger_updates(proto::MilestoneRangeRequest::from(request)) - .await? - .into_inner() - .map(unpack_proto_msg)) - } - - pub async fn read_node_status(&mut self) -> Result { - NodeStatus::try_from(self.inx.read_node_status(proto::NoParams {}).await?.into_inner()).map_err(Error::InxError) - } - - pub async fn read_node_configuration(&mut self) -> Result { - NodeConfiguration::try_from(self.inx.read_node_configuration(proto::NoParams {}).await?.into_inner()) - .map_err(Error::InxError) - } - - pub async fn read_unspent_outputs( - &mut self, - ) -> Result>, Error> { - Ok(self - .inx - .read_unspent_outputs(proto::NoParams {}) - .await? - .into_inner() - .map(unpack_proto_msg)) - } - - pub async fn read_protocol_parameters( - &mut self, - request: MilestoneRequest, - ) -> Result { - Ok(self - .inx - .read_protocol_parameters(proto::MilestoneRequest::from(request)) - .await? - .into_inner() - .into()) - } - - /// Reads the past cone of a milestone specified by a [`MilestoneRequest`]. - pub async fn read_milestone_cone( - &mut self, - request: MilestoneRequest, - ) -> Result>, Error> { - Ok(self - .inx - .read_milestone_cone(proto::MilestoneRequest::from(request)) - .await? - .into_inner() - .map(unpack_proto_msg)) - } +pub(crate) fn try_from_inx_type(msg: Result) -> Result +where + BeeType: TryFrom, +{ + let inner = msg.map_err(Error::StatusCode)?; + BeeType::try_from(inner).map_err(Error::InxError) +} - pub async fn read_milestone(&mut self, request: MilestoneRequest) -> Result { - Milestone::try_from( - self.inx - .read_milestone(proto::MilestoneRequest::from(request)) - .await? - .into_inner(), - ) - .map_err(Error::InxError) - } +pub(crate) fn from_inx_type(msg: Result) -> Result +where + BeeType: From, +{ + let inner = msg.map_err(Error::StatusCode)?; + Ok(BeeType::from(inner)) } diff --git a/bee-inx/src/error.rs b/bee-inx/src/error.rs index 152ef62e5f..1094a9530e 100644 --- a/bee-inx/src/error.rs +++ b/bee-inx/src/error.rs @@ -4,6 +4,7 @@ use inx::tonic; use thiserror::Error; +#[allow(missing_docs)] #[derive(Debug, Error)] pub enum Error { #[error(transparent)] diff --git a/bee-inx/src/ledger.rs b/bee-inx/src/ledger.rs deleted file mode 100644 index 8f1e26cd65..0000000000 --- a/bee-inx/src/ledger.rs +++ /dev/null @@ -1,228 +0,0 @@ -// Copyright 2022 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -use bee_block as bee; -use inx::proto; - -use crate::{maybe_missing, Raw}; - -/// Represents a new output in the ledger. -#[allow(missing_docs)] -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct LedgerOutput { - pub output_id: bee::output::OutputId, - pub block_id: bee::BlockId, - pub milestone_index_booked: bee::payload::milestone::MilestoneIndex, - pub milestone_timestamp_booked: u32, - pub output: Raw, -} - -/// Represents a spent output in the ledger. -#[allow(missing_docs)] -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct LedgerSpent { - pub output: LedgerOutput, - pub transaction_id_spent: bee::payload::transaction::TransactionId, - pub milestone_index_spent: bee::payload::milestone::MilestoneIndex, - pub milestone_timestamp_spent: u32, -} - -#[allow(missing_docs)] -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct UnspentOutput { - pub ledger_index: bee::payload::milestone::MilestoneIndex, - pub output: LedgerOutput, -} - -#[allow(missing_docs)] -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct Marker { - pub milestone_index: bee::payload::milestone::MilestoneIndex, - pub consumed_count: usize, - pub created_count: usize, -} - -#[allow(missing_docs)] -#[derive(Clone, Debug, PartialEq, Eq)] -pub enum LedgerUpdate { - Consumed(LedgerSpent), - Created(LedgerOutput), - Begin(Marker), - End(Marker), -} - -impl LedgerUpdate { - /// If present, returns the contained `LedgerSpent` while consuming `self`. - pub fn consumed(self) -> Option { - match self { - Self::Consumed(ledger_spent) => Some(ledger_spent), - _ => None, - } - } - - /// If present, returns the contained `LedgerOutput` while consuming `self`. - pub fn created(self) -> Option { - match self { - Self::Created(ledger_output) => Some(ledger_output), - _ => None, - } - } - - /// If present, returns the `Marker` that denotes the beginning of a milestone while consuming `self`. - pub fn begin(self) -> Option { - match self { - Self::Begin(marker) => Some(marker), - _ => None, - } - } - - /// If present, returns the `Marker` that denotes the end if present while consuming `self`. - pub fn end(self) -> Option { - match self { - Self::End(marker) => Some(marker), - _ => None, - } - } -} - -impl From for Marker { - fn from(value: proto::ledger_update::Marker) -> Self { - Self { - milestone_index: value.milestone_index.into(), - consumed_count: value.consumed_count as usize, - created_count: value.created_count as usize, - } - } -} - -impl From for LedgerUpdate { - fn from(value: proto::ledger_update::Marker) -> Self { - use proto::ledger_update::marker::MarkerType as proto; - match value.marker_type() { - proto::Begin => Self::Begin(value.into()), - proto::End => Self::End(value.into()), - } - } -} - -impl TryFrom for proto::ledger_update::Marker { - type Error = bee::InxError; - - fn try_from(value: LedgerUpdate) -> Result { - use proto::ledger_update::marker::MarkerType; - let marker_type = match &value { - LedgerUpdate::Begin(_) => MarkerType::Begin, - LedgerUpdate::End(_) => MarkerType::End, - _ => { - return Err(Self::Error::MissingField("marker_type")); - } - }; - if let LedgerUpdate::Begin(marker) | LedgerUpdate::End(marker) = value { - Ok(Self { - milestone_index: marker.milestone_index.0, - marker_type: marker_type.into(), - consumed_count: marker.consumed_count as _, - created_count: marker.created_count as _, - }) - } else { - unreachable!() - } - } -} - -impl TryFrom for LedgerUpdate { - type Error = bee::InxError; - - fn try_from(value: proto::LedgerUpdate) -> Result { - use proto::ledger_update::Op as proto; - Ok(match maybe_missing!(value.op) { - proto::BatchMarker(marker) => marker.into(), - proto::Consumed(consumed) => LedgerUpdate::Consumed(consumed.try_into()?), - proto::Created(created) => LedgerUpdate::Created(created.try_into()?), - }) - } -} - -impl From for proto::LedgerUpdate { - fn from(value: LedgerUpdate) -> Self { - use proto::ledger_update::Op; - Self { - op: match value { - LedgerUpdate::Consumed(consumed) => Op::Consumed(consumed.into()), - LedgerUpdate::Created(created) => Op::Created(created.into()), - marker => Op::BatchMarker(marker.try_into().unwrap()), - } - .into(), - } - } -} - -impl TryFrom for LedgerOutput { - type Error = bee::InxError; - - fn try_from(value: proto::LedgerOutput) -> Result { - Ok(Self { - output_id: maybe_missing!(value.output_id).try_into()?, - block_id: maybe_missing!(value.block_id).try_into()?, - milestone_index_booked: value.milestone_index_booked.into(), - milestone_timestamp_booked: value.milestone_timestamp_booked, - output: maybe_missing!(value.output).into(), - }) - } -} - -impl From for proto::LedgerOutput { - fn from(value: LedgerOutput) -> Self { - Self { - output_id: Some(value.output_id.into()), - block_id: Some(value.block_id.into()), - milestone_index_booked: value.milestone_index_booked.0, - milestone_timestamp_booked: value.milestone_timestamp_booked, - output: Some(value.output.into()), - } - } -} - -impl TryFrom for LedgerSpent { - type Error = bee::InxError; - - fn try_from(value: proto::LedgerSpent) -> Result { - Ok(Self { - output: maybe_missing!(value.output).try_into()?, - transaction_id_spent: maybe_missing!(value.transaction_id_spent).try_into()?, - milestone_index_spent: value.milestone_index_spent.into(), - milestone_timestamp_spent: value.milestone_timestamp_spent, - }) - } -} - -impl From for proto::LedgerSpent { - fn from(value: LedgerSpent) -> Self { - Self { - output: Some(value.output.into()), - transaction_id_spent: Some(value.transaction_id_spent.into()), - milestone_index_spent: value.milestone_index_spent.0, - milestone_timestamp_spent: value.milestone_timestamp_spent, - } - } -} - -impl TryFrom for UnspentOutput { - type Error = bee::InxError; - - fn try_from(value: proto::UnspentOutput) -> Result { - Ok(Self { - ledger_index: value.ledger_index.into(), - output: maybe_missing!(value.output).try_into()?, - }) - } -} - -impl From for proto::UnspentOutput { - fn from(value: UnspentOutput) -> Self { - Self { - ledger_index: value.ledger_index.0, - output: Some(value.output.into()), - } - } -} diff --git a/bee-inx/src/lib.rs b/bee-inx/src/lib.rs index 416ed7ce77..a3c9f40f38 100644 --- a/bee-inx/src/lib.rs +++ b/bee-inx/src/lib.rs @@ -1,29 +1,54 @@ // Copyright 2022 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -mod block; -pub mod client; +//! Bee compatible INX types and INX node request bindings. + +#![deny(missing_docs)] + mod error; -mod ledger; -mod metadata; -mod milestone; -mod node; -mod protocol_parameters; -mod raw; -mod request; -mod treasury; - -pub use self::{ - block::*, error::Error, ledger::*, metadata::*, milestone::*, node::*, protocol_parameters::*, raw::*, request::*, - treasury::*, -}; - -pub mod proto { - pub use inx::proto::*; + +/// A module that provides block related requests.. +pub mod block; +/// A module that provides the INX client. +pub mod client; +/// A module that provides milestone related requests. +pub mod milestone; +/// A module that provides node related requests. +pub mod node; +/// A module that provides the [`Raw`] struct. +pub mod raw; +/// A module that provides UTXO ledger related requests. +pub mod utxo; + +pub use self::{block::*, error::Error, milestone::*, node::*, raw::*, utxo::*}; + +pub(crate) mod inx { + pub use ::inx::proto::{ + block_metadata::*, + ledger_update::{marker::*, *}, + *, + }; +} + +pub(crate) mod bee { + pub use bee_block::{ + output::{Output, OutputId}, + payload::{ + milestone::{MilestoneId, MilestoneIndex, MilestoneOption}, + transaction::TransactionId, + Payload, + }, + protocol::ProtocolParameters, + semantic::ConflictReason, + Block, BlockId, InxError, + }; + #[cfg(test)] + pub use bee_block::{protocol::protocol_parameters, rand::output::rand_output}; } +#[allow(missing_docs)] #[macro_export] -macro_rules! maybe_missing { +macro_rules! return_err_if_none { ($object:ident.$field:ident) => { $object.$field.ok_or(Self::Error::MissingField(stringify!($field)))? }; @@ -34,8 +59,8 @@ mod test { use super::*; #[test] - fn macro_missing_field() { - let proto = proto::TreasuryOutput { + fn test_return_err_if_none() { + let proto = inx::TreasuryOutput { milestone_id: None, amount: 42, }; diff --git a/bee-inx/src/milestone/info.rs b/bee-inx/src/milestone/info.rs deleted file mode 100644 index 46edc527dc..0000000000 --- a/bee-inx/src/milestone/info.rs +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright 2022 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -use bee_block as bee; -use inx::proto; - -/// The [`MilestoneInfo`] type. -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct MilestoneInfo { - /// The [`MilestoneId`](bee::payload::milestone::MilestoneId) of the milestone. - pub milestone_id: Option, - /// The milestone index. - pub milestone_index: bee::payload::milestone::MilestoneIndex, - /// The timestamp of the milestone. - pub milestone_timestamp: u32, -} - -impl TryFrom for MilestoneInfo { - type Error = bee::InxError; - - fn try_from(value: proto::MilestoneInfo) -> Result { - Ok(MilestoneInfo { - milestone_id: value.milestone_id.map(TryInto::try_into).transpose()?, - milestone_index: value.milestone_index.into(), - milestone_timestamp: value.milestone_timestamp, - }) - } -} - -impl From for proto::MilestoneInfo { - fn from(value: MilestoneInfo) -> Self { - Self { - milestone_id: value.milestone_id.map(Into::into), - milestone_index: value.milestone_index.0, - milestone_timestamp: value.milestone_timestamp, - } - } -} diff --git a/bee-inx/src/milestone/mod.rs b/bee-inx/src/milestone/mod.rs index b878bcb46f..59693fe24d 100644 --- a/bee-inx/src/milestone/mod.rs +++ b/bee-inx/src/milestone/mod.rs @@ -1,68 +1,88 @@ // Copyright 2022 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -use bee_block as bee; +/// A module that provides milestone related INX requests. +pub mod requests; +/// A module that provides milestone related INX responses. +pub mod responses; -mod info; +use futures::stream::{Stream, StreamExt}; -use inx::proto; +pub use self::{requests::*, responses::*}; +use crate::{ + block::responses::{BlockMetadata, BlockWithMetadata}, + client::{try_from_inx_type, Inx}, + error::Error, + inx, +}; -pub use self::info::MilestoneInfo; -use crate::{maybe_missing, ProtocolParameters, Raw}; - -/// The [`Milestone`] type. -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct Milestone { - /// Information about the milestone. - pub milestone_info: MilestoneInfo, - /// The raw bytes of the milestone. Note that this is not a [`bee::payload::milestone::MilestonePayload`], but - /// rather a [`bee::payload::Payload`] and still needs to be unpacked. - pub milestone: Raw, -} - -impl TryFrom for Milestone { - type Error = bee::InxError; - - fn try_from(value: proto::Milestone) -> Result { - Ok(Self { - milestone_info: maybe_missing!(value.milestone_info).try_into()?, - milestone: maybe_missing!(value.milestone).data.into(), - }) +impl Inx { + /// Requests a particular milestone. + pub async fn read_milestone(&mut self, request: MilestoneRequest) -> Result { + Ok(self + .client + .read_milestone(inx::MilestoneRequest::from(request)) + .await? + .into_inner() + .try_into()?) } -} -impl From for proto::Milestone { - fn from(value: Milestone) -> Self { - Self { - milestone_info: Some(value.milestone_info.into()), - milestone: Some(value.milestone.into()), - } + /// Listens to latest milestones. + pub async fn listen_to_latest_milestones(&mut self) -> Result>, Error> { + Ok(self + .client + .listen_to_latest_milestones(inx::NoParams {}) + .await? + .into_inner() + .map(try_from_inx_type)) } -} -/// The [`Milestone`] type. -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct MilestoneAndProtocolParameters { - pub milestone: Milestone, - pub current_protocol_parameters: ProtocolParameters, -} + /// Listens to confirmed milestones in a certain range. + pub async fn listen_to_confirmed_milestones( + &mut self, + request: MilestoneRangeRequest, + ) -> Result>, Error> { + Ok(self + .client + .listen_to_confirmed_milestones(inx::MilestoneRangeRequest::from(request)) + .await? + .into_inner() + .map(try_from_inx_type)) + } -impl TryFrom for MilestoneAndProtocolParameters { - type Error = bee::InxError; + /// Requests "white flag" data for a milestone. + pub async fn compute_white_flag(&mut self, request: WhiteFlagRequest) -> Result { + Ok(self + .client + .compute_white_flag(inx::WhiteFlagRequest::from(request)) + .await? + .into_inner() + .into()) + } - fn try_from(value: proto::MilestoneAndProtocolParameters) -> Result { - Ok(Self { - milestone: maybe_missing!(value.milestone).try_into()?, - current_protocol_parameters: maybe_missing!(value.current_protocol_parameters).into(), - }) + /// Reads the past cone of a milestone specified by a [`MilestoneRequest`]. + pub async fn read_milestone_cone( + &mut self, + request: MilestoneRequest, + ) -> Result>, Error> { + Ok(self + .client + .read_milestone_cone(inx::MilestoneRequest::from(request)) + .await? + .into_inner() + .map(try_from_inx_type)) } -} -impl From for proto::MilestoneAndProtocolParameters { - fn from(value: MilestoneAndProtocolParameters) -> Self { - Self { - milestone: Some(value.milestone.into()), - current_protocol_parameters: Some(value.current_protocol_parameters.into()), - } + /// Reads the past cone metadata of a milestone specified by a [`MilestoneRequest`]. + pub async fn read_milestone_cone_metadata( + &mut self, + request: MilestoneRequest, + ) -> Result>, Error> { + Ok(self + .client + .read_milestone_cone_metadata(inx::MilestoneRequest::from(request)) + .await? + .into_inner() + .map(try_from_inx_type)) } } diff --git a/bee-inx/src/milestone/requests.rs b/bee-inx/src/milestone/requests.rs new file mode 100644 index 0000000000..1009093311 --- /dev/null +++ b/bee-inx/src/milestone/requests.rs @@ -0,0 +1,142 @@ +// Copyright 2022 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use std::ops::{Bound, RangeBounds}; + +use crate::{bee, error, inx}; + +/// Allows to request a milestone by either its index or its id. +#[allow(missing_docs)] +pub enum MilestoneRequest { + MilestoneIndex(bee::MilestoneIndex), + MilestoneId(bee::MilestoneId), +} + +impl From for inx::MilestoneRequest { + fn from(value: MilestoneRequest) -> Self { + match value { + MilestoneRequest::MilestoneIndex(bee::MilestoneIndex(milestone_index)) => Self { + milestone_index, + milestone_id: None, + }, + MilestoneRequest::MilestoneId(milestone_id) => Self { + milestone_index: 0, + milestone_id: Some(milestone_id.into()), + }, + } + } +} + +impl> From for MilestoneRequest { + fn from(value: T) -> Self { + Self::MilestoneIndex(bee::MilestoneIndex(value.into())) + } +} + +/// A request for a range of milestones by [`bee::MilestoneIndex`]. +#[derive(Clone, Debug, PartialEq)] +pub struct MilestoneRangeRequest(inx::MilestoneRangeRequest); + +impl From for MilestoneRangeRequest +where + T: RangeBounds, +{ + fn from(value: T) -> MilestoneRangeRequest { + MilestoneRangeRequest(to_milestone_range_request(value)) + } +} + +impl From for inx::MilestoneRangeRequest { + fn from(value: MilestoneRangeRequest) -> Self { + value.0 + } +} + +fn to_milestone_range_request(range: T) -> inx::MilestoneRangeRequest +where + T: RangeBounds, + I: Into + Copy, +{ + let start_milestone_index = match range.start_bound() { + Bound::Included(&idx) => idx.into(), + Bound::Excluded(&idx) => idx.into() + 1, + Bound::Unbounded => 0, + }; + let end_milestone_index = match range.end_bound() { + Bound::Included(&idx) => idx.into(), + Bound::Excluded(&idx) => idx.into() - 1, + Bound::Unbounded => 0, + }; + inx::MilestoneRangeRequest { + start_milestone_index, + end_milestone_index, + } +} + +/// Allows to request "white flag" data for a particular milestone. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct WhiteFlagRequest { + milestone_index: bee::MilestoneIndex, + milestone_timestamp: u32, + parents: Box<[bee::BlockId]>, + previous_milestone_id: Option, +} + +impl TryFrom for WhiteFlagRequest { + type Error = error::Error; + + fn try_from(value: inx::WhiteFlagRequest) -> Result { + let parents = value + .parents + .into_iter() + .map(TryInto::try_into) + .collect::, _>>()?; + + Ok(Self { + milestone_index: value.milestone_index.into(), + milestone_timestamp: value.milestone_timestamp, + parents: parents.into_boxed_slice(), + previous_milestone_id: value.previous_milestone_id.map(TryInto::try_into).transpose()?, + }) + } +} + +impl From for inx::WhiteFlagRequest { + fn from(value: WhiteFlagRequest) -> Self { + Self { + milestone_index: value.milestone_index.0, + milestone_timestamp: value.milestone_timestamp, + parents: value.parents.into_vec().into_iter().map(Into::into).collect(), + previous_milestone_id: value.previous_milestone_id.map(Into::into), + } + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn exclusive() { + let range = MilestoneRangeRequest::from(17..43); + assert_eq!( + range, + MilestoneRangeRequest(inx::MilestoneRangeRequest { + start_milestone_index: 17, + end_milestone_index: 42 + }) + ); + } + + #[test] + fn inclusive() { + let range = MilestoneRangeRequest::from(17..=42); + assert_eq!( + range, + MilestoneRangeRequest(inx::MilestoneRangeRequest { + start_milestone_index: 17, + end_milestone_index: 42 + }) + ); + } +} diff --git a/bee-inx/src/milestone/responses.rs b/bee-inx/src/milestone/responses.rs new file mode 100644 index 0000000000..ccda4f95c0 --- /dev/null +++ b/bee-inx/src/milestone/responses.rs @@ -0,0 +1,123 @@ +// Copyright 2022 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use bee_block as bee; + +use crate::{inx, return_err_if_none, ProtocolParameters, Raw}; + +/// The [`Milestone`] type. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Milestone { + /// Information about the milestone. + pub milestone_info: MilestoneInfo, + /// The raw bytes of the milestone. Note that this is not a [`bee::payload::milestone::MilestonePayload`], but + /// rather a [`bee::payload::Payload`] and still needs to be unpacked. + pub milestone: Raw, +} + +impl TryFrom for Milestone { + type Error = bee::InxError; + + fn try_from(value: inx::Milestone) -> Result { + Ok(Self { + milestone_info: return_err_if_none!(value.milestone_info).try_into()?, + milestone: return_err_if_none!(value.milestone).data.into(), + }) + } +} + +impl From for inx::Milestone { + fn from(value: Milestone) -> Self { + Self { + milestone_info: Some(value.milestone_info.into()), + milestone: Some(value.milestone.into()), + } + } +} + +/// The [`Milestone`] type. +#[allow(missing_docs)] +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct MilestoneAndProtocolParameters { + pub milestone: Milestone, + pub current_protocol_parameters: ProtocolParameters, +} + +impl TryFrom for MilestoneAndProtocolParameters { + type Error = bee::InxError; + + fn try_from(value: inx::MilestoneAndProtocolParameters) -> Result { + Ok(Self { + milestone: return_err_if_none!(value.milestone).try_into()?, + current_protocol_parameters: return_err_if_none!(value.current_protocol_parameters).into(), + }) + } +} + +impl From for inx::MilestoneAndProtocolParameters { + fn from(value: MilestoneAndProtocolParameters) -> Self { + Self { + milestone: Some(value.milestone.into()), + current_protocol_parameters: Some(value.current_protocol_parameters.into()), + } + } +} + +/// The [`MilestoneInfo`] type. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct MilestoneInfo { + /// The [`MilestoneId`](bee::payload::milestone::MilestoneId) of the milestone. + pub milestone_id: Option, + /// The milestone index. + pub milestone_index: bee::payload::milestone::MilestoneIndex, + /// The timestamp of the milestone. + pub milestone_timestamp: u32, +} + +impl TryFrom for MilestoneInfo { + type Error = bee::InxError; + + fn try_from(value: inx::MilestoneInfo) -> Result { + Ok(MilestoneInfo { + milestone_id: value.milestone_id.map(TryInto::try_into).transpose()?, + milestone_index: value.milestone_index.into(), + milestone_timestamp: value.milestone_timestamp, + }) + } +} + +impl From for inx::MilestoneInfo { + fn from(value: MilestoneInfo) -> Self { + Self { + milestone_id: value.milestone_id.map(Into::into), + milestone_index: value.milestone_index.0, + milestone_timestamp: value.milestone_timestamp, + } + } +} + +/// The response of a corresponding "white flag" request. +#[allow(missing_docs)] +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct WhiteFlagResponse { + milestone_inclusion_merkle_root: Vec, + milestone_applied_merkle_root: Vec, +} + +impl From for WhiteFlagResponse { + fn from(value: inx::WhiteFlagResponse) -> Self { + Self { + milestone_inclusion_merkle_root: value.milestone_inclusion_merkle_root, + milestone_applied_merkle_root: value.milestone_applied_merkle_root, + } + } +} + +impl From for inx::WhiteFlagResponse { + fn from(value: WhiteFlagResponse) -> Self { + Self { + milestone_inclusion_merkle_root: value.milestone_inclusion_merkle_root, + milestone_applied_merkle_root: value.milestone_applied_merkle_root, + } + } +} diff --git a/bee-inx/src/node/config.rs b/bee-inx/src/node/config.rs deleted file mode 100644 index d57eca1644..0000000000 --- a/bee-inx/src/node/config.rs +++ /dev/null @@ -1,114 +0,0 @@ -// Copyright 2022 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -use bee_block as bee; -use inx::proto; - -use crate::maybe_missing; - -/// The [`BaseToken`] type. -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct BaseToken { - pub name: String, - pub ticker_symbol: String, - pub unit: String, - pub subunit: String, - pub decimals: u32, - pub use_metric_prefix: bool, -} - -impl From for BaseToken { - fn from(value: proto::BaseToken) -> Self { - Self { - name: value.name, - ticker_symbol: value.ticker_symbol, - unit: value.unit, - subunit: value.subunit, - decimals: value.decimals, - use_metric_prefix: value.use_metric_prefix, - } - } -} - -impl From for proto::BaseToken { - fn from(value: BaseToken) -> Self { - Self { - name: value.name, - ticker_symbol: value.ticker_symbol, - unit: value.unit, - subunit: value.subunit, - decimals: value.decimals, - use_metric_prefix: value.use_metric_prefix, - } - } -} - -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct MilestoneKeyRange { - pub public_key: Box<[u8]>, - pub start_index: bee::payload::milestone::MilestoneIndex, - pub end_index: bee::payload::milestone::MilestoneIndex, -} - -impl From for MilestoneKeyRange { - fn from(value: proto::MilestoneKeyRange) -> Self { - Self { - public_key: value.public_key.into_boxed_slice(), - start_index: value.start_index.into(), - end_index: value.end_index.into(), - } - } -} - -impl From for proto::MilestoneKeyRange { - fn from(value: MilestoneKeyRange) -> Self { - Self { - public_key: value.public_key.into_vec(), - start_index: value.start_index.0, - end_index: value.end_index.0, - } - } -} - -/// The [`NodeConfiguration`] type. -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct NodeConfiguration { - pub milestone_public_key_count: u32, - pub milestone_key_ranges: Box<[MilestoneKeyRange]>, - pub base_token: BaseToken, - pub supported_protocol_versions: Box<[u8]>, -} - -impl TryFrom for NodeConfiguration { - type Error = bee::InxError; - - fn try_from(value: proto::NodeConfiguration) -> Result { - Ok(NodeConfiguration { - milestone_public_key_count: value.milestone_public_key_count, - milestone_key_ranges: value.milestone_key_ranges.into_iter().map(Into::into).collect(), - base_token: maybe_missing!(value.base_token).into(), - supported_protocol_versions: value.supported_protocol_versions.into_iter().map(|v| v as u8).collect(), - }) - } -} - -impl From for proto::NodeConfiguration { - fn from(value: NodeConfiguration) -> Self { - Self { - milestone_public_key_count: value.milestone_public_key_count, - milestone_key_ranges: value - .milestone_key_ranges - .into_vec() - .into_iter() - .map(Into::into) - .collect(), - base_token: Some(value.base_token.into()), - supported_protocol_versions: value - .supported_protocol_versions - .into_vec() - .into_iter() - .map(|v| v as _) - .collect(), - } - } -} diff --git a/bee-inx/src/node/mod.rs b/bee-inx/src/node/mod.rs index 2dbe947ce5..00255b9c84 100644 --- a/bee-inx/src/node/mod.rs +++ b/bee-inx/src/node/mod.rs @@ -1,7 +1,59 @@ // Copyright 2022 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -mod config; -mod status; +/// A module that provides node related INX requests. +pub mod requests; +/// A module that provides node related INX responses. +pub mod responses; -pub use self::{config::NodeConfiguration, status::NodeStatus}; +use futures::stream::{Stream, StreamExt}; + +pub use self::{requests::*, responses::*}; +use crate::{ + client::{try_from_inx_type, Inx}, + error::Error, + inx, + milestone::requests::MilestoneRequest, +}; + +impl Inx { + /// Requests the status of the connected node. + pub async fn read_node_status(&mut self) -> Result { + NodeStatus::try_from(self.client.read_node_status(inx::NoParams {}).await?.into_inner()) + .map_err(Error::InxError) + } + + /// Listens to node status updates. + pub async fn listen_to_node_status( + &mut self, + request: NodeStatusRequest, + ) -> Result>, Error> { + Ok(self + .client + .listen_to_node_status(inx::NodeStatusRequest::from(request)) + .await? + .into_inner() + .map(try_from_inx_type)) + } + + /// Requests the configuration of the connected node. + pub async fn read_node_configuration(&mut self) -> Result { + NodeConfiguration::try_from( + self.client + .read_node_configuration(inx::NoParams {}) + .await? + .into_inner(), + ) + .map_err(Error::InxError) + } + + /// Requests the protocol parameters of the connected node. + pub async fn read_protocol_parameters(&mut self, request: MilestoneRequest) -> Result { + Ok(self + .client + .read_protocol_parameters(inx::MilestoneRequest::from(request)) + .await? + .into_inner() + .into()) + } +} diff --git a/bee-inx/src/node/requests.rs b/bee-inx/src/node/requests.rs new file mode 100644 index 0000000000..6731e4bbf5 --- /dev/null +++ b/bee-inx/src/node/requests.rs @@ -0,0 +1,27 @@ +// Copyright 2022 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use crate::inx; + +/// A request for the node status. +#[allow(missing_docs)] +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct NodeStatusRequest { + pub cooldown_in_milliseconds: u32, +} + +impl From for NodeStatusRequest { + fn from(value: inx::NodeStatusRequest) -> Self { + Self { + cooldown_in_milliseconds: value.cooldown_in_milliseconds, + } + } +} + +impl From for inx::NodeStatusRequest { + fn from(value: NodeStatusRequest) -> Self { + Self { + cooldown_in_milliseconds: value.cooldown_in_milliseconds, + } + } +} diff --git a/bee-inx/src/node/responses.rs b/bee-inx/src/node/responses.rs new file mode 100644 index 0000000000..86e21dcd01 --- /dev/null +++ b/bee-inx/src/node/responses.rs @@ -0,0 +1,202 @@ +// Copyright 2022 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use crate::{bee, inx, milestone::responses::Milestone, raw::Raw, return_err_if_none}; + +/// Represents the [`NodeStatus`] response. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct NodeStatus { + /// Signals if the node is healthy. + pub is_healthy: bool, + /// Signals if the node is synced. + pub is_synced: bool, + /// Signals if the node is almost synced (within a configured range). + pub is_almost_synced: bool, + /// The latest milestone seen by the node. + pub latest_milestone: Milestone, + /// The last confirmed milestone. + pub confirmed_milestone: Milestone, + /// The current protocol parameters. + pub current_protocol_parameters: ProtocolParameters, + /// The tangle pruning index of the node. + pub tangle_pruning_index: bee::MilestoneIndex, + /// The milestones pruning index of the node. + pub milestones_pruning_index: bee::MilestoneIndex, + /// The ledger pruning index of the node. + pub ledger_pruning_index: bee::MilestoneIndex, + /// The ledger index of the node. + pub ledger_index: bee::MilestoneIndex, +} + +impl TryFrom for NodeStatus { + type Error = bee::InxError; + + fn try_from(value: inx::NodeStatus) -> Result { + Ok(NodeStatus { + is_healthy: value.is_healthy, + is_synced: value.is_synced, + is_almost_synced: value.is_almost_synced, + latest_milestone: return_err_if_none!(value.latest_milestone).try_into()?, + confirmed_milestone: return_err_if_none!(value.confirmed_milestone).try_into()?, + current_protocol_parameters: return_err_if_none!(value.current_protocol_parameters).into(), + tangle_pruning_index: value.tangle_pruning_index.into(), + milestones_pruning_index: value.milestones_pruning_index.into(), + ledger_pruning_index: value.ledger_pruning_index.into(), + ledger_index: value.ledger_index.into(), + }) + } +} + +impl From for inx::NodeStatus { + fn from(value: NodeStatus) -> Self { + Self { + is_healthy: value.is_healthy, + is_synced: value.is_synced, + is_almost_synced: value.is_almost_synced, + latest_milestone: Some(value.latest_milestone.into()), + confirmed_milestone: Some(value.confirmed_milestone.into()), + current_protocol_parameters: Some(value.current_protocol_parameters.into()), + tangle_pruning_index: value.tangle_pruning_index.0, + milestones_pruning_index: value.milestones_pruning_index.0, + ledger_pruning_index: value.ledger_pruning_index.0, + ledger_index: value.ledger_index.0, + } + } +} + +/// Represents the [`NodeConfiguration`] response. +#[allow(missing_docs)] +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct NodeConfiguration { + pub milestone_public_key_count: u32, + pub milestone_key_ranges: Box<[MilestoneKeyRange]>, + pub base_token: BaseToken, + pub supported_protocol_versions: Box<[u8]>, +} + +impl TryFrom for NodeConfiguration { + type Error = bee::InxError; + + fn try_from(value: inx::NodeConfiguration) -> Result { + Ok(NodeConfiguration { + milestone_public_key_count: value.milestone_public_key_count, + milestone_key_ranges: value.milestone_key_ranges.into_iter().map(Into::into).collect(), + base_token: return_err_if_none!(value.base_token).into(), + supported_protocol_versions: value.supported_protocol_versions.into_iter().map(|v| v as u8).collect(), + }) + } +} + +impl From for inx::NodeConfiguration { + fn from(value: NodeConfiguration) -> Self { + Self { + milestone_public_key_count: value.milestone_public_key_count, + milestone_key_ranges: value + .milestone_key_ranges + .into_vec() + .into_iter() + .map(Into::into) + .collect(), + base_token: Some(value.base_token.into()), + supported_protocol_versions: value + .supported_protocol_versions + .into_vec() + .into_iter() + .map(|v| v as _) + .collect(), + } + } +} + +/// The [`BaseToken`] type. +#[allow(missing_docs)] +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct BaseToken { + pub name: String, + pub ticker_symbol: String, + pub unit: String, + pub subunit: String, + pub decimals: u32, + pub use_metric_prefix: bool, +} + +impl From for BaseToken { + fn from(value: inx::BaseToken) -> Self { + Self { + name: value.name, + ticker_symbol: value.ticker_symbol, + unit: value.unit, + subunit: value.subunit, + decimals: value.decimals, + use_metric_prefix: value.use_metric_prefix, + } + } +} + +impl From for inx::BaseToken { + fn from(value: BaseToken) -> Self { + Self { + name: value.name, + ticker_symbol: value.ticker_symbol, + unit: value.unit, + subunit: value.subunit, + decimals: value.decimals, + use_metric_prefix: value.use_metric_prefix, + } + } +} + +/// Represents a milestone key range. +#[allow(missing_docs)] +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct MilestoneKeyRange { + pub public_key: Box<[u8]>, + pub start_index: bee::MilestoneIndex, + pub end_index: bee::MilestoneIndex, +} + +impl From for MilestoneKeyRange { + fn from(value: inx::MilestoneKeyRange) -> Self { + Self { + public_key: value.public_key.into_boxed_slice(), + start_index: value.start_index.into(), + end_index: value.end_index.into(), + } + } +} + +impl From for inx::MilestoneKeyRange { + fn from(value: MilestoneKeyRange) -> Self { + Self { + public_key: value.public_key.into_vec(), + start_index: value.start_index.0, + end_index: value.end_index.0, + } + } +} + +/// Represents a protocol parameters response. +#[allow(missing_docs)] +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct ProtocolParameters { + pub protocol_version: u8, + pub params: Raw, +} + +impl From for ProtocolParameters { + fn from(value: inx::RawProtocolParameters) -> Self { + Self { + protocol_version: value.protocol_version as u8, + params: value.params.into(), + } + } +} + +impl From for inx::RawProtocolParameters { + fn from(value: ProtocolParameters) -> Self { + Self { + protocol_version: value.protocol_version as u32, + params: value.params.data(), + } + } +} diff --git a/bee-inx/src/node/status.rs b/bee-inx/src/node/status.rs deleted file mode 100644 index b92c2dedb0..0000000000 --- a/bee-inx/src/node/status.rs +++ /dev/null @@ -1,68 +0,0 @@ -// Copyright 2022 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -use bee_block as bee; -use inx::proto; - -use crate::{maybe_missing, Milestone, ProtocolParameters}; - -/// The [`NodeStatus`] type. -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct NodeStatus { - /// Signals if the node is healthy. - pub is_healthy: bool, - /// Signals if the node is synced. - pub is_synced: bool, - /// Signals if the node is almost synced (within a configured range). - pub is_almost_synced: bool, - /// The latest milestone seen by the node. - pub latest_milestone: Milestone, - /// The last confirmed milestone. - pub confirmed_milestone: Milestone, - /// The current protocol parameters. - pub current_protocol_parameters: ProtocolParameters, - /// The tangle pruning index of the node. - pub tangle_pruning_index: bee::payload::milestone::MilestoneIndex, - /// The milestones pruning index of the node. - pub milestones_pruning_index: bee::payload::milestone::MilestoneIndex, - /// The ledger pruning index of the node. - pub ledger_pruning_index: bee::payload::milestone::MilestoneIndex, - /// The ledger index of the node. - pub ledger_index: bee::payload::milestone::MilestoneIndex, -} - -impl TryFrom for NodeStatus { - type Error = bee::InxError; - - fn try_from(value: proto::NodeStatus) -> Result { - Ok(NodeStatus { - is_healthy: value.is_healthy, - is_synced: value.is_synced, - is_almost_synced: value.is_almost_synced, - latest_milestone: maybe_missing!(value.latest_milestone).try_into()?, - confirmed_milestone: maybe_missing!(value.confirmed_milestone).try_into()?, - current_protocol_parameters: maybe_missing!(value.current_protocol_parameters).into(), - tangle_pruning_index: value.tangle_pruning_index.into(), - milestones_pruning_index: value.milestones_pruning_index.into(), - ledger_pruning_index: value.ledger_pruning_index.into(), - ledger_index: value.ledger_index.into(), - }) - } -} - -impl From for proto::NodeStatus { - fn from(value: NodeStatus) -> Self { - Self { - is_healthy: value.is_healthy, - is_synced: value.is_synced, - is_almost_synced: value.is_almost_synced, - latest_milestone: Some(value.latest_milestone.into()), - confirmed_milestone: Some(value.confirmed_milestone.into()), - current_protocol_parameters: Some(value.current_protocol_parameters.into()), - tangle_pruning_index: value.tangle_pruning_index.0, - milestones_pruning_index: value.milestones_pruning_index.0, - ledger_pruning_index: value.ledger_pruning_index.0, - ledger_index: value.ledger_index.0, - } - } -} diff --git a/bee-inx/src/protocol_parameters.rs b/bee-inx/src/protocol_parameters.rs deleted file mode 100644 index c5d5ebf30e..0000000000 --- a/bee-inx/src/protocol_parameters.rs +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright 2022 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -use bee_block as bee; -use inx::proto; - -use crate::Raw; - -#[allow(missing_docs)] -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct ProtocolParameters { - pub protocol_version: u8, - pub params: Raw, -} - -impl From for ProtocolParameters { - fn from(value: proto::RawProtocolParameters) -> Self { - Self { - protocol_version: value.protocol_version as u8, - params: value.params.into(), - } - } -} - -impl From for proto::RawProtocolParameters { - fn from(value: ProtocolParameters) -> Self { - Self { - protocol_version: value.protocol_version as u32, - params: value.params.data(), - } - } -} diff --git a/bee-inx/src/raw.rs b/bee-inx/src/raw.rs index 94f79fbbf7..db29cf7a15 100644 --- a/bee-inx/src/raw.rs +++ b/bee-inx/src/raw.rs @@ -3,11 +3,9 @@ use std::marker::PhantomData; -use bee_block as bee; -use inx::proto; use packable::{Packable, PackableExt}; -use crate::Error; +use crate::{bee, inx, Error}; /// Represents a type as raw bytes. #[derive(Debug, Clone, PartialEq, Eq)] @@ -17,17 +15,20 @@ pub struct Raw { } impl Raw { + /// Returns the inner byte data as-is. #[must_use] pub fn data(self) -> Vec { self.data } + /// Deserializes the inner byte data into `T`. pub fn inner(self, visitor: &T::UnpackVisitor) -> Result { let unpacked = T::unpack_verified(self.data, visitor) .map_err(|e| bee_block::InxError::InvalidRawBytes(format!("{:?}", e)))?; Ok(unpacked) } + /// Deserializes the raw byte data into `T` without verification. pub fn inner_unverified(self) -> Result { let unpacked = T::unpack_unverified(self.data).map_err(|e| bee_block::InxError::InvalidRawBytes(format!("{:?}", e)))?; @@ -44,67 +45,79 @@ impl From> for Raw { } } -impl From for Raw { - fn from(value: proto::RawOutput) -> Self { +impl From for Raw { + fn from(value: inx::RawOutput) -> Self { value.data.into() } } -impl From> for proto::RawOutput { - fn from(value: Raw) -> Self { +impl From> for inx::RawOutput { + fn from(value: Raw) -> Self { Self { data: value.data } } } -impl From for Raw { - fn from(value: proto::RawBlock) -> Self { +impl From for Raw { + fn from(value: inx::RawBlock) -> Self { value.data.into() } } -impl From> for proto::RawBlock { +impl From> for inx::RawBlock { fn from(value: Raw) -> Self { Self { data: value.data } } } -impl From for Raw { - fn from(value: proto::RawMilestone) -> Self { +impl From for Raw { + fn from(value: inx::RawMilestone) -> Self { value.data.into() } } -impl From> for proto::RawMilestone { - fn from(value: Raw) -> Self { +impl From> for inx::RawMilestone { + fn from(value: Raw) -> Self { + Self { data: value.data } + } +} + +impl From for Raw { + fn from(value: inx::RawReceipt) -> Self { + value.data.into() + } +} + +impl From> for inx::RawReceipt { + fn from(value: Raw) -> Self { Self { data: value.data } } } #[cfg(test)] mod test { - use bee::{payload::Payload, rand::output::rand_output}; + use bee::{rand_output, Payload}; use super::*; use crate::ProtocolParameters; #[test] fn raw_output() { - let protocol_parameters = bee::protocol::protocol_parameters(); + let protocol_parameters = bee::protocol_parameters(); let output = rand_output(protocol_parameters.token_supply()); - let proto = proto::RawOutput { + let proto = inx::RawOutput { data: output.pack_to_vec(), }; - let raw: Raw = proto.into(); + let raw: Raw = proto.into(); assert_eq!(output, raw.clone().inner_unverified().unwrap()); assert_eq!(output, raw.inner(&protocol_parameters).unwrap()); } #[test] fn raw_protocol_parameters() { - let protocol_parameters = bee::protocol::protocol_parameters(); - let proto = proto::RawProtocolParameters::from(protocol_parameters.clone()); + let protocol_parameters = bee::protocol_parameters(); + let proto = inx::RawProtocolParameters::from(protocol_parameters.clone()); let pp: ProtocolParameters = proto.into(); assert_eq!(protocol_parameters, pp.params.inner(&()).unwrap()); diff --git a/bee-inx/src/request.rs b/bee-inx/src/request.rs deleted file mode 100644 index 71a7f0899e..0000000000 --- a/bee-inx/src/request.rs +++ /dev/null @@ -1,102 +0,0 @@ -// Copyright 2022 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -use std::ops::{Bound, RangeBounds}; - -use bee_block::payload::milestone::{MilestoneId, MilestoneIndex}; -use inx::proto; - -pub enum MilestoneRequest { - MilestoneIndex(MilestoneIndex), - MilestoneId(MilestoneId), -} - -impl From for proto::MilestoneRequest { - fn from(value: MilestoneRequest) -> Self { - match value { - MilestoneRequest::MilestoneIndex(MilestoneIndex(milestone_index)) => Self { - milestone_index, - milestone_id: None, - }, - MilestoneRequest::MilestoneId(milestone_id) => Self { - milestone_index: 0, - milestone_id: Some(milestone_id.into()), - }, - } - } -} - -impl> From for MilestoneRequest { - fn from(value: T) -> Self { - Self::MilestoneIndex(MilestoneIndex(value.into())) - } -} - -fn to_milestone_range_request(range: T) -> proto::MilestoneRangeRequest -where - T: RangeBounds, - I: Into + Copy, -{ - let start_milestone_index = match range.start_bound() { - Bound::Included(&idx) => idx.into(), - Bound::Excluded(&idx) => idx.into() + 1, - Bound::Unbounded => 0, - }; - let end_milestone_index = match range.end_bound() { - Bound::Included(&idx) => idx.into(), - Bound::Excluded(&idx) => idx.into() - 1, - Bound::Unbounded => 0, - }; - proto::MilestoneRangeRequest { - start_milestone_index, - end_milestone_index, - } -} - -/// A request for a range of milestones by [`MilestoneIndex`]. -#[derive(Clone, Debug, PartialEq)] -pub struct MilestoneRangeRequest(proto::MilestoneRangeRequest); - -impl From for MilestoneRangeRequest -where - T: RangeBounds, -{ - fn from(value: T) -> MilestoneRangeRequest { - MilestoneRangeRequest(to_milestone_range_request(value)) - } -} - -impl From for proto::MilestoneRangeRequest { - fn from(value: MilestoneRangeRequest) -> Self { - value.0 - } -} - -#[cfg(test)] -mod test { - use super::*; - - #[test] - fn exclusive() { - let range = MilestoneRangeRequest::from(17..43); - assert_eq!( - range, - MilestoneRangeRequest(proto::MilestoneRangeRequest { - start_milestone_index: 17, - end_milestone_index: 42 - }) - ); - } - - #[test] - fn inclusive() { - let range = MilestoneRangeRequest::from(17..=42); - assert_eq!( - range, - MilestoneRangeRequest(proto::MilestoneRangeRequest { - start_milestone_index: 17, - end_milestone_index: 42 - }) - ); - } -} diff --git a/bee-inx/src/treasury.rs b/bee-inx/src/treasury.rs deleted file mode 100644 index d6cbbff88e..0000000000 --- a/bee-inx/src/treasury.rs +++ /dev/null @@ -1,66 +0,0 @@ -// Copyright 2022 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -use bee_block as bee; -use inx::proto; - -use crate::maybe_missing; - -/// Represents a treasury output. -#[allow(missing_docs)] -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct TreasuryOutput { - pub milestone_id: bee::payload::milestone::MilestoneId, - pub amount: u64, -} - -impl TryFrom for TreasuryOutput { - type Error = bee::InxError; - - fn try_from(value: proto::TreasuryOutput) -> Result { - Ok(TreasuryOutput { - milestone_id: maybe_missing!(value.milestone_id).try_into()?, - amount: value.amount, - }) - } -} - -impl From for proto::TreasuryOutput { - fn from(value: TreasuryOutput) -> Self { - Self { - milestone_id: Some(value.milestone_id.into()), - amount: value.amount, - } - } -} - -/// Represents an update to the treasury. -#[allow(missing_docs)] -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct TreasuryUpdate { - pub milestone_index: u32, - pub created: TreasuryOutput, - pub consumed: TreasuryOutput, -} - -impl TryFrom for TreasuryUpdate { - type Error = bee::InxError; - - fn try_from(value: proto::TreasuryUpdate) -> Result { - Ok(Self { - milestone_index: value.milestone_index, - created: maybe_missing!(value.created).try_into()?, - consumed: maybe_missing!(value.consumed).try_into()?, - }) - } -} - -impl From for proto::TreasuryUpdate { - fn from(value: TreasuryUpdate) -> Self { - Self { - milestone_index: value.milestone_index, - created: Some(value.created.into()), - consumed: Some(value.consumed.into()), - } - } -} diff --git a/bee-inx/src/utxo/mod.rs b/bee-inx/src/utxo/mod.rs new file mode 100644 index 0000000000..c147c621b4 --- /dev/null +++ b/bee-inx/src/utxo/mod.rs @@ -0,0 +1,79 @@ +// Copyright 2022 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +/// A module that provides utxo related INX responses. +pub mod responses; + +use futures::{Stream, StreamExt}; + +pub use self::responses::*; +use crate::{ + bee, + client::{from_inx_type, try_from_inx_type, Inx}, + error::Error, + inx, + milestone::requests::MilestoneRangeRequest, + raw::Raw, +}; + +impl Inx { + /// Requests all unspent outputs. + pub async fn read_unspent_outputs( + &mut self, + ) -> Result>, Error> { + Ok(self + .client + .read_unspent_outputs(inx::NoParams {}) + .await? + .into_inner() + .map(try_from_inx_type)) + } + + /// Creates a feed of ledger updates. + pub async fn listen_to_ledger_updates( + &mut self, + request: MilestoneRangeRequest, + ) -> Result>, Error> { + Ok(self + .client + .listen_to_ledger_updates(inx::MilestoneRangeRequest::from(request)) + .await? + .into_inner() + .map(try_from_inx_type)) + } + + /// Creates a feed of treasury updates. + pub async fn listen_to_treasury_updates( + &mut self, + request: MilestoneRangeRequest, + ) -> Result>, Error> { + Ok(self + .client + .listen_to_treasury_updates(inx::MilestoneRangeRequest::from(request)) + .await? + .into_inner() + .map(try_from_inx_type)) + } + + /// Requests an output by its output id. + pub async fn read_output(&mut self, output_id: bee::OutputId) -> Result { + Ok(self + .client + .read_output(inx::OutputId::from(output_id)) + .await? + .into_inner() + .try_into()?) + } + + /// Creates a feed of migration receipts. + pub async fn listen_to_migration_receipts( + &mut self, + ) -> Result, Error>>, Error> { + Ok(self + .client + .listen_to_migration_receipts(inx::NoParams {}) + .await? + .into_inner() + .map(from_inx_type)) + } +} diff --git a/bee-inx/src/utxo/responses.rs b/bee-inx/src/utxo/responses.rs new file mode 100644 index 0000000000..17b244f6f7 --- /dev/null +++ b/bee-inx/src/utxo/responses.rs @@ -0,0 +1,334 @@ +// Copyright 2022 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use crate::{bee, inx, return_err_if_none, Raw}; + +/// Represents unspent output response. +#[allow(missing_docs)] +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct LedgerOutput { + pub output_id: bee::OutputId, + pub block_id: bee::BlockId, + pub milestone_index_booked: bee::MilestoneIndex, + pub milestone_timestamp_booked: u32, + pub output: Raw, +} + +impl TryFrom for LedgerOutput { + type Error = bee::InxError; + + fn try_from(value: inx::LedgerOutput) -> Result { + Ok(Self { + output_id: return_err_if_none!(value.output_id).try_into()?, + block_id: return_err_if_none!(value.block_id).try_into()?, + milestone_index_booked: value.milestone_index_booked.into(), + milestone_timestamp_booked: value.milestone_timestamp_booked, + output: return_err_if_none!(value.output).into(), + }) + } +} + +impl From for inx::LedgerOutput { + fn from(value: LedgerOutput) -> Self { + Self { + output_id: Some(value.output_id.into()), + block_id: Some(value.block_id.into()), + milestone_index_booked: value.milestone_index_booked.0, + milestone_timestamp_booked: value.milestone_timestamp_booked, + output: Some(value.output.into()), + } + } +} + +/// A spent output response. +#[allow(missing_docs)] +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct LedgerSpent { + pub output: LedgerOutput, + pub transaction_id_spent: bee::TransactionId, + pub milestone_index_spent: bee::MilestoneIndex, + pub milestone_timestamp_spent: u32, +} + +impl TryFrom for LedgerSpent { + type Error = bee::InxError; + + fn try_from(value: inx::LedgerSpent) -> Result { + Ok(Self { + output: return_err_if_none!(value.output).try_into()?, + transaction_id_spent: return_err_if_none!(value.transaction_id_spent).try_into()?, + milestone_index_spent: value.milestone_index_spent.into(), + milestone_timestamp_spent: value.milestone_timestamp_spent, + }) + } +} + +impl From for inx::LedgerSpent { + fn from(value: LedgerSpent) -> Self { + Self { + output: Some(value.output.into()), + transaction_id_spent: Some(value.transaction_id_spent.into()), + milestone_index_spent: value.milestone_index_spent.0, + milestone_timestamp_spent: value.milestone_timestamp_spent, + } + } +} + +/// An unspent output response. +#[allow(missing_docs)] +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct UnspentOutput { + pub ledger_index: bee::MilestoneIndex, + pub output: LedgerOutput, +} + +impl TryFrom for UnspentOutput { + type Error = bee::InxError; + + fn try_from(value: inx::UnspentOutput) -> Result { + Ok(Self { + ledger_index: value.ledger_index.into(), + output: return_err_if_none!(value.output).try_into()?, + }) + } +} + +impl From for inx::UnspentOutput { + fn from(value: UnspentOutput) -> Self { + Self { + ledger_index: value.ledger_index.0, + output: Some(value.output.into()), + } + } +} + +/// A ledger update response. +#[allow(missing_docs)] +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum LedgerUpdate { + Consumed(LedgerSpent), + Created(LedgerOutput), + Begin(Marker), + End(Marker), +} + +impl LedgerUpdate { + /// If present, returns the contained `LedgerSpent` while consuming `self`. + pub fn consumed(self) -> Option { + match self { + Self::Consumed(ledger_spent) => Some(ledger_spent), + _ => None, + } + } + + /// If present, returns the contained `LedgerOutput` while consuming `self`. + pub fn created(self) -> Option { + match self { + Self::Created(ledger_output) => Some(ledger_output), + _ => None, + } + } + + /// If present, returns the `Marker` that denotes the beginning of a milestone while consuming `self`. + pub fn begin(self) -> Option { + match self { + Self::Begin(marker) => Some(marker), + _ => None, + } + } + + /// If present, returns the `Marker` that denotes the end if present while consuming `self`. + pub fn end(self) -> Option { + match self { + Self::End(marker) => Some(marker), + _ => None, + } + } +} + +impl TryFrom for LedgerUpdate { + type Error = bee::InxError; + + fn try_from(value: inx::LedgerUpdate) -> Result { + use crate::inx::Op; + Ok(match return_err_if_none!(value.op) { + Op::BatchMarker(marker) => marker.into(), + Op::Consumed(consumed) => LedgerUpdate::Consumed(consumed.try_into()?), + Op::Created(created) => LedgerUpdate::Created(created.try_into()?), + }) + } +} + +impl From for inx::LedgerUpdate { + fn from(value: LedgerUpdate) -> Self { + use crate::inx::Op; + Self { + op: match value { + LedgerUpdate::Consumed(consumed) => Op::Consumed(consumed.into()), + LedgerUpdate::Created(created) => Op::Created(created.into()), + marker => Op::BatchMarker(marker.try_into().unwrap()), + } + .into(), + } + } +} + +/// Represents a ledger update batch marker. +#[allow(missing_docs)] +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Marker { + pub milestone_index: bee::MilestoneIndex, + pub consumed_count: usize, + pub created_count: usize, +} + +impl From for Marker { + fn from(value: inx::ledger_update::Marker) -> Self { + Self { + milestone_index: value.milestone_index.into(), + consumed_count: value.consumed_count as usize, + created_count: value.created_count as usize, + } + } +} + +impl From for LedgerUpdate { + fn from(value: inx::Marker) -> Self { + use crate::inx::MarkerType; + match value.marker_type() { + MarkerType::Begin => Self::Begin(value.into()), + MarkerType::End => Self::End(value.into()), + } + } +} + +impl TryFrom for inx::ledger_update::Marker { + type Error = bee::InxError; + + fn try_from(value: LedgerUpdate) -> Result { + use crate::inx::MarkerType; + let marker_type = match &value { + LedgerUpdate::Begin(_) => MarkerType::Begin, + LedgerUpdate::End(_) => MarkerType::End, + _ => { + return Err(Self::Error::MissingField("marker_type")); + } + }; + if let LedgerUpdate::Begin(marker) | LedgerUpdate::End(marker) = value { + Ok(Self { + milestone_index: marker.milestone_index.0, + marker_type: marker_type.into(), + consumed_count: marker.consumed_count as _, + created_count: marker.created_count as _, + }) + } else { + unreachable!() + } + } +} + +/// Represents a treasury output. +#[allow(missing_docs)] +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct TreasuryOutput { + pub milestone_id: bee::MilestoneId, + pub amount: u64, +} + +impl TryFrom for TreasuryOutput { + type Error = bee::InxError; + + fn try_from(value: inx::TreasuryOutput) -> Result { + Ok(TreasuryOutput { + milestone_id: return_err_if_none!(value.milestone_id).try_into()?, + amount: value.amount, + }) + } +} + +impl From for inx::TreasuryOutput { + fn from(value: TreasuryOutput) -> Self { + Self { + milestone_id: Some(value.milestone_id.into()), + amount: value.amount, + } + } +} + +/// Represents an update to the treasury. +#[allow(missing_docs)] +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct TreasuryUpdate { + pub milestone_index: u32, + pub created: TreasuryOutput, + pub consumed: TreasuryOutput, +} + +impl TryFrom for TreasuryUpdate { + type Error = bee::InxError; + + fn try_from(value: inx::TreasuryUpdate) -> Result { + Ok(Self { + milestone_index: value.milestone_index, + created: return_err_if_none!(value.created).try_into()?, + consumed: return_err_if_none!(value.consumed).try_into()?, + }) + } +} + +impl From for inx::TreasuryUpdate { + fn from(value: TreasuryUpdate) -> Self { + Self { + milestone_index: value.milestone_index, + created: Some(value.created.into()), + consumed: Some(value.consumed.into()), + } + } +} + +/// Represents an output response. +#[allow(missing_docs)] +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct OutputResponse { + pub ledger_index: bee::MilestoneIndex, + pub payload: Option, +} + +#[allow(missing_docs)] +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum OutputResponsePayload { + LedgerOutput(LedgerOutput), + LedgerSpent(LedgerSpent), +} + +impl TryFrom for OutputResponse { + type Error = bee::InxError; + + fn try_from(value: inx::OutputResponse) -> Result { + use crate::inx::output_response::Payload::*; + Ok(Self { + ledger_index: value.ledger_index.into(), + payload: if let Some(payload) = value.payload { + Some(match payload { + Output(ledger_output) => OutputResponsePayload::LedgerOutput(ledger_output.try_into()?), + Spent(ledger_spent) => OutputResponsePayload::LedgerSpent(ledger_spent.try_into()?), + }) + } else { + None + }, + }) + } +} + +impl From for inx::OutputResponse { + fn from(value: OutputResponse) -> Self { + use OutputResponsePayload::*; + Self { + ledger_index: value.ledger_index.0, + payload: value.payload.map(|payload| match payload { + LedgerOutput(ledger_output) => inx::output_response::Payload::Output(ledger_output.into()), + LedgerSpent(ledger_spent) => inx::output_response::Payload::Spent(ledger_spent.into()), + }), + } + } +}