mirror of
https://github.com/hinto-janai/cuprate.git
synced 2024-12-22 03:29:30 +00:00
parent
ecf5d66a91
commit
85ebc679b2
16 changed files with 132 additions and 84 deletions
|
@ -1,4 +1,8 @@
|
|||
//! RPC request handler functions (binary endpoints).
|
||||
//!
|
||||
//! TODO:
|
||||
//! Some handlers have `todo!()`s for other Cuprate internals that must be completed, see:
|
||||
//! <https://github.com/Cuprate/cuprate/pull/355>
|
||||
|
||||
use std::num::NonZero;
|
||||
|
||||
|
|
|
@ -170,7 +170,7 @@ pub(super) async fn top_height(state: &mut CupratedRpcHandler) -> Result<(u64, [
|
|||
Ok((height, hash))
|
||||
}
|
||||
|
||||
/// TODO
|
||||
/// TODO: impl bootstrap
|
||||
pub const fn response_base(is_bootstrap: bool) -> ResponseBase {
|
||||
if is_bootstrap {
|
||||
ResponseBase::OK_UNTRUSTED
|
||||
|
@ -179,7 +179,7 @@ pub const fn response_base(is_bootstrap: bool) -> ResponseBase {
|
|||
}
|
||||
}
|
||||
|
||||
/// TODO
|
||||
/// TODO: impl bootstrap
|
||||
pub const fn access_response_base(is_bootstrap: bool) -> AccessResponseBase {
|
||||
if is_bootstrap {
|
||||
AccessResponseBase::OK_UNTRUSTED
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
//! RPC request handler functions (JSON-RPC).
|
||||
//!
|
||||
//! TODO:
|
||||
//! Many handlers have `todo!()`s for other Cuprate internals that must be completed, see:
|
||||
//! <https://github.com/Cuprate/cuprate/pull/308>
|
||||
//! Some handlers have `todo!()`s for other Cuprate internals that must be completed, see:
|
||||
//! <https://github.com/Cuprate/cuprate/pull/355>
|
||||
|
||||
use std::{
|
||||
net::{IpAddr, Ipv4Addr, SocketAddr, SocketAddrV4},
|
||||
|
@ -147,23 +147,26 @@ async fn get_block_template(
|
|||
return Err(anyhow!("Too big extra_nonce size"));
|
||||
}
|
||||
|
||||
// cryptonote::address_parse_info info;
|
||||
// TODO: this is hardcoded for the current address scheme + mainnet,
|
||||
// create/use a more well-defined wallet lib.
|
||||
let parse_wallet_address = || {
|
||||
if request.wallet_address.len() == 95 {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(())
|
||||
}
|
||||
};
|
||||
let is_correct_address_type = || !request.wallet_address.starts_with("4");
|
||||
|
||||
if request.wallet_address.is_empty()
|
||||
|| todo!(
|
||||
"!cryptonote::get_account_address_from_str(info, nettype(), request.wallet_address))"
|
||||
)
|
||||
{
|
||||
if parse_wallet_address().is_err() {
|
||||
return Err(anyhow!("Failed to parse wallet address"));
|
||||
}
|
||||
|
||||
if todo!("info.is_subaddress") {
|
||||
return Err(anyhow!("Mining to subaddress is not supported yet"));
|
||||
if is_correct_address_type() {
|
||||
return Err(anyhow!("Incorrect address type"));
|
||||
}
|
||||
|
||||
let blob_reserve = hex::decode(request.extra_nonce)?;
|
||||
let prev_block: [u8; 32] = request.prev_block.try_into()?;
|
||||
let extra_nonce = hex::decode(request.extra_nonce)?;
|
||||
let prev_block = request.prev_block.try_into().unwrap_or([0; 32]);
|
||||
|
||||
let BlockTemplate {
|
||||
block,
|
||||
|
@ -178,14 +181,13 @@ async fn get_block_template(
|
|||
&mut state.blockchain_manager,
|
||||
prev_block,
|
||||
request.wallet_address,
|
||||
extra_nonce,
|
||||
request.extra_nonce.0,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let blockhashing_blob = HexVec(block.serialize_pow_hash());
|
||||
let blocktemplate_blob = HexVec(block.serialize());
|
||||
let (difficulty, difficulty_top64) = split_u128_into_low_high_bits(difficulty);
|
||||
// let next_seed_hash = Hex(next_seed_hash);
|
||||
let next_seed_hash = HexVec::empty_if_zeroed(next_seed_hash);
|
||||
let prev_hash = Hex(block.header.previous);
|
||||
let seed_hash = Hex(seed_hash);
|
||||
|
@ -242,7 +244,7 @@ async fn submit_block(
|
|||
) -> Result<SubmitBlockResponse, Error> {
|
||||
// Parse hex into block.
|
||||
let [blob] = request.block_blob;
|
||||
let block = Block::read(&mut blob.0.as_slice())?;
|
||||
let block = Block::read(&mut blob.as_slice())?;
|
||||
let block_id = Hex(block.hash());
|
||||
|
||||
// Attempt to relay the block.
|
||||
|
@ -562,8 +564,8 @@ async fn get_info(
|
|||
address_book::peerlist_size::<ClearNet>(&mut DummyAddressBook).await?
|
||||
};
|
||||
|
||||
let wide_cumulative_difficulty = format!("{cumulative_difficulty:#x}");
|
||||
let wide_difficulty = format!("{:#x}", c.next_difficulty);
|
||||
let wide_cumulative_difficulty = cumulative_difficulty.hex_prefix();
|
||||
let wide_difficulty = c.next_difficulty.hex_prefix();
|
||||
|
||||
Ok(GetInfoResponse {
|
||||
base: helper::access_response_base(false),
|
||||
|
@ -799,8 +801,8 @@ async fn get_coinbase_tx_sum(
|
|||
.await?;
|
||||
|
||||
// Formats `u128` as hexadecimal strings.
|
||||
let wide_emission_amount = format!("{fee_amount:#x}");
|
||||
let wide_fee_amount = format!("{emission_amount:#x}");
|
||||
let wide_emission_amount = fee_amount.hex_prefix();
|
||||
let wide_fee_amount = emission_amount.hex_prefix();
|
||||
|
||||
Ok(GetCoinbaseTxSumResponse {
|
||||
base: helper::access_response_base(false),
|
||||
|
@ -979,7 +981,7 @@ async fn get_miner_data(
|
|||
let height = usize_to_u64(c.chain_height);
|
||||
let prev_id = Hex(c.top_hash);
|
||||
let seed_hash = Hex(c.top_hash);
|
||||
let difficulty = format!("{:#x}", c.next_difficulty);
|
||||
let difficulty = c.next_difficulty.hex_prefix();
|
||||
let median_weight = usize_to_u64(c.median_weight_for_block_reward);
|
||||
let already_generated_coins = c.already_generated_coins;
|
||||
let tx_backlog = txpool::backlog(&mut state.txpool_read)
|
||||
|
@ -1028,7 +1030,7 @@ async fn calc_pow(
|
|||
request: CalcPowRequest,
|
||||
) -> Result<CalcPowResponse, Error> {
|
||||
let hardfork = HardFork::from_version(request.major_version)?;
|
||||
let block = Block::read(&mut request.block_blob.0.as_slice())?;
|
||||
let block = Block::read(&mut request.block_blob.as_slice())?;
|
||||
let seed_hash = request.seed_hash.0;
|
||||
|
||||
// let block_weight = todo!();
|
||||
|
@ -1219,7 +1221,7 @@ fn add_aux_pow_inner(
|
|||
let merkle_root = tree_hash(aux_pow_raw.as_ref());
|
||||
let merkle_tree_depth = encode_mm_depth(len, nonce);
|
||||
|
||||
let block_template = Block::read(&mut request.blocktemplate_blob.0.as_slice())?;
|
||||
let block_template = Block::read(&mut request.blocktemplate_blob.as_slice())?;
|
||||
|
||||
fn remove_field_from_tx_extra() -> Result<(), ()> {
|
||||
todo!("https://github.com/monero-project/monero/blob/master/src/cryptonote_basic/cryptonote_format_utils.cpp#L767")
|
||||
|
|
|
@ -1,4 +1,8 @@
|
|||
//! RPC request handler functions (other JSON endpoints).
|
||||
//!
|
||||
//! TODO:
|
||||
//! Some handlers have `todo!()`s for other Cuprate internals that must be completed, see:
|
||||
//! <https://github.com/Cuprate/cuprate/pull/355>
|
||||
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
|
@ -6,6 +10,7 @@ use std::{
|
|||
};
|
||||
|
||||
use anyhow::{anyhow, Error};
|
||||
use monero_serai::transaction::{Input, Timelock, Transaction};
|
||||
|
||||
use cuprate_constants::rpc::{
|
||||
MAX_RESTRICTED_GLOBAL_FAKE_OUTS_COUNT, RESTRICTED_SPENT_KEY_IMAGES_COUNT,
|
||||
|
@ -40,7 +45,6 @@ use cuprate_types::{
|
|||
rpc::{KeyImageSpentStatus, PoolInfo, PoolTxInfo, PublicNode},
|
||||
TxInPool, TxRelayChecks,
|
||||
};
|
||||
use monero_serai::transaction::{Input, Timelock, Transaction};
|
||||
|
||||
use crate::{
|
||||
rpc::{
|
||||
|
@ -173,7 +177,7 @@ async fn get_transactions(
|
|||
txs_as_hex.push(as_hex.clone());
|
||||
|
||||
let as_json = if request.decode_as_json {
|
||||
let tx = Transaction::read(&mut as_hex.0.as_slice())?;
|
||||
let tx = Transaction::read(&mut as_hex.as_slice())?;
|
||||
let json_type = cuprate_types::json::tx::Transaction::from(tx);
|
||||
let json = serde_json::to_string(&json_type).unwrap();
|
||||
txs_as_json.push(json.clone());
|
||||
|
@ -351,7 +355,7 @@ async fn send_raw_transaction(
|
|||
tx_extra_too_big: false,
|
||||
};
|
||||
|
||||
let tx = Transaction::read(&mut request.tx_as_hex.0.as_slice())?;
|
||||
let tx = Transaction::read(&mut request.tx_as_hex.as_slice())?;
|
||||
|
||||
if request.do_sanity_checks {
|
||||
/// FIXME: these checks could be defined elsewhere.
|
||||
|
|
|
@ -1,4 +1,8 @@
|
|||
//! RPC handler functions that are shared between different endpoint/methods.
|
||||
//!
|
||||
//! TODO:
|
||||
//! Some handlers have `todo!()`s for other Cuprate internals that must be completed, see:
|
||||
//! <https://github.com/Cuprate/cuprate/pull/355>
|
||||
|
||||
use std::{
|
||||
collections::{HashMap, HashSet},
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
use std::task::{Context, Poll};
|
||||
|
||||
use anyhow::Error;
|
||||
use cuprate_types::BlockTemplate;
|
||||
use futures::future::BoxFuture;
|
||||
use monero_serai::block::Block;
|
||||
use tower::Service;
|
||||
|
@ -18,6 +17,7 @@ use cuprate_rpc_types::{
|
|||
other::{OtherRequest, OtherResponse},
|
||||
};
|
||||
use cuprate_txpool::service::TxpoolReadHandle;
|
||||
use cuprate_types::BlockTemplate;
|
||||
|
||||
use crate::rpc::handlers;
|
||||
|
||||
|
@ -41,7 +41,7 @@ pub enum BlockchainManagerRequest {
|
|||
Box<Block>,
|
||||
),
|
||||
|
||||
/// TODO
|
||||
/// Sync/flush the blockchain database to disk.
|
||||
Sync,
|
||||
|
||||
/// Is the blockchain in the middle of syncing?
|
||||
|
@ -89,14 +89,14 @@ pub enum BlockchainManagerRequest {
|
|||
/// Get the next [`PruningSeed`] needed for a pruned sync.
|
||||
NextNeededPruningSeed,
|
||||
|
||||
/// TODO
|
||||
/// Create a block template.
|
||||
CreateBlockTemplate {
|
||||
prev_block: [u8; 32],
|
||||
account_public_address: String,
|
||||
extra_nonce: Vec<u8>,
|
||||
},
|
||||
|
||||
/// TODO
|
||||
/// Safely shutdown `cuprated`.
|
||||
Stop,
|
||||
}
|
||||
|
||||
|
@ -140,8 +140,6 @@ pub enum BlockchainManagerResponse {
|
|||
height: usize,
|
||||
},
|
||||
|
||||
// /// Response to [`BlockchainManagerRequest::Spans`].
|
||||
// Spans(Vec<Span<Z::Addr>>),
|
||||
/// Response to [`BlockchainManagerRequest::NextNeededPruningSeed`].
|
||||
NextNeededPruningSeed(PruningSeed),
|
||||
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
use std::net::SocketAddrV4;
|
||||
|
||||
use anyhow::{anyhow, Error};
|
||||
use cuprate_types::rpc::Peer;
|
||||
use tower::ServiceExt;
|
||||
|
||||
use cuprate_helper::{cast::usize_to_u64, map::u32_from_ipv4};
|
||||
|
@ -13,6 +12,7 @@ use cuprate_p2p_core::{
|
|||
AddressBook, NetworkZone,
|
||||
};
|
||||
use cuprate_rpc_types::misc::ConnectionInfo;
|
||||
use cuprate_types::rpc::Peer;
|
||||
|
||||
// FIXME: use `anyhow::Error` over `tower::BoxError` in address book.
|
||||
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
//! Functions to send [`BlockchainManagerRequest`]s.
|
||||
|
||||
use anyhow::Error;
|
||||
use cuprate_types::BlockTemplate;
|
||||
use monero_serai::block::Block;
|
||||
use tower::{Service, ServiceExt};
|
||||
|
||||
|
@ -9,6 +8,7 @@ use cuprate_helper::cast::{u64_to_usize, usize_to_u64};
|
|||
use cuprate_p2p_core::{types::ConnectionId, NetworkZone};
|
||||
use cuprate_pruning::PruningSeed;
|
||||
use cuprate_rpc_types::misc::Span;
|
||||
use cuprate_types::BlockTemplate;
|
||||
|
||||
use crate::rpc::rpc_handler::{
|
||||
BlockchainManagerHandle, BlockchainManagerRequest, BlockchainManagerResponse,
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
//! Formatting.
|
||||
//! String formatting.
|
||||
|
||||
/// A type that can be represented in hexadecimal (with a `0x` prefix).
|
||||
pub trait HexPrefix {
|
||||
/// Turn `self` into a hexadecimal string prefixed with `0x`.
|
||||
fn hex_prefix(self) -> String;
|
||||
}
|
||||
|
||||
|
|
|
@ -111,15 +111,6 @@ impl From<HistogramEntry> for crate::misc::HistogramEntry {
|
|||
}
|
||||
}
|
||||
|
||||
// impl From<HardforkEntry> for crate::misc::HardforkEntry {
|
||||
// fn from(x: HardforkEntry) -> Self {
|
||||
// Self {
|
||||
// height: x.height,
|
||||
// hf_version: x.hf_version,
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
impl From<ChainInfo> for crate::misc::ChainInfo {
|
||||
fn from(x: ChainInfo) -> Self {
|
||||
Self {
|
||||
|
@ -150,18 +141,6 @@ impl From<Span<SocketAddr>> for crate::misc::Span {
|
|||
}
|
||||
}
|
||||
|
||||
// impl From<OutputDistributionData> for crate::misc::OutputDistributionData {
|
||||
// fn from(x: OutputDistributionData) -> Self {
|
||||
// todo!();
|
||||
|
||||
// // Self {
|
||||
// // distribution: Vec<u64>,
|
||||
// // start_height: u64,
|
||||
// // base: u64,
|
||||
// // }
|
||||
// }
|
||||
// }
|
||||
|
||||
impl From<TxInfo> for crate::misc::TxInfo {
|
||||
fn from(x: TxInfo) -> Self {
|
||||
Self {
|
||||
|
|
|
@ -76,11 +76,11 @@ define_request_and_response! {
|
|||
// 1. As an expression
|
||||
// 2. As a string literal
|
||||
//
|
||||
// For example: `extra_nonce: String /* = default_string(), "default_string" */,`
|
||||
// For example: `extra_nonce: HexVec /* = default::<HexVec>(), "default" */,`
|
||||
//
|
||||
// This is a HACK since `serde`'s default attribute only takes in
|
||||
// string literals and macros (stringify) within attributes do not work.
|
||||
extra_nonce: String = default::<String>(), "default",
|
||||
extra_nonce: HexVec = default::<HexVec>(), "default",
|
||||
prev_block: HexVec = default::<HexVec>(), "default",
|
||||
|
||||
// Another optional expression:
|
||||
|
@ -958,7 +958,7 @@ mod test {
|
|||
fn get_block_template_request() {
|
||||
test_json_request(json::GET_BLOCK_TEMPLATE_REQUEST, GetBlockTemplateRequest {
|
||||
reserve_size: 60,
|
||||
extra_nonce: String::default(),
|
||||
extra_nonce: HexVec::default(),
|
||||
prev_block: HexVec::default(),
|
||||
wallet_address: "44GBHzv6ZyQdJkjqZje6KLZ3xSyN1hBSFAnLP6EAqJtCRVzMzZmeXTC2AHKDS9aEDTRKmo6a6o9r9j86pYfhCWDkKjbtcns".into(),
|
||||
});
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
//! TODO
|
||||
//! [`RequestedInfo`]
|
||||
|
||||
//---------------------------------------------------------------------------------------------------- Use
|
||||
#[cfg(feature = "serde")]
|
||||
|
|
|
@ -19,6 +19,10 @@ use crate::{
|
|||
|
||||
//---------------------------------------------------------------------------------------------------- TxpoolReadRequest
|
||||
/// The transaction pool [`tower::Service`] read request type.
|
||||
///
|
||||
/// ### `include_sensitive_txs`
|
||||
/// This field exists in many requests.
|
||||
/// If this is [`true`], the request will include private (local) transactions in the response.
|
||||
#[derive(Clone)]
|
||||
pub enum TxpoolReadRequest {
|
||||
/// Get the blob (raw bytes) of a transaction with the given hash.
|
||||
|
@ -39,42 +43,38 @@ pub enum TxpoolReadRequest {
|
|||
Backlog,
|
||||
|
||||
/// Get the number of transactions in the pool.
|
||||
Size {
|
||||
/// If this is [`true`], the size returned will
|
||||
/// include private transactions in the pool.
|
||||
include_sensitive_txs: bool,
|
||||
},
|
||||
Size { include_sensitive_txs: bool },
|
||||
|
||||
/// Get general information on the txpool.
|
||||
PoolInfo {
|
||||
/// If this is [`true`], the size returned will
|
||||
/// include private transactions in the pool.
|
||||
include_sensitive_txs: bool,
|
||||
/// TODO
|
||||
/// The maximum amount of transactions to retrieve.
|
||||
max_tx_count: usize,
|
||||
/// TODO
|
||||
/// Fetch transactions that start from this time.
|
||||
///
|
||||
/// [`None`] means all transactions.
|
||||
start_time: Option<NonZero<usize>>,
|
||||
},
|
||||
|
||||
/// TODO
|
||||
/// Get transactions by their hashes.
|
||||
TxsByHash {
|
||||
tx_hashes: Vec<[u8; 32]>,
|
||||
include_sensitive_txs: bool,
|
||||
},
|
||||
|
||||
/// TODO
|
||||
/// Check if certain key images exist in the txpool.
|
||||
KeyImagesSpent {
|
||||
key_images: Vec<[u8; 32]>,
|
||||
include_sensitive_txs: bool,
|
||||
},
|
||||
|
||||
/// TODO
|
||||
/// Get txpool info.
|
||||
Pool { include_sensitive_txs: bool },
|
||||
|
||||
/// TODO
|
||||
/// Get txpool stats.
|
||||
PoolStats { include_sensitive_txs: bool },
|
||||
|
||||
/// TODO
|
||||
/// Get the hashes of all transaction in the pool.
|
||||
AllHashes { include_sensitive_txs: bool },
|
||||
}
|
||||
|
||||
|
|
|
@ -3,6 +3,11 @@
|
|||
//! This module provides transparent wrapper types for
|
||||
//! arrays that (de)serialize from hexadecimal input/output.
|
||||
|
||||
use std::{
|
||||
borrow::Borrow,
|
||||
ops::{Deref, DerefMut},
|
||||
};
|
||||
|
||||
use hex::{FromHex, FromHexError};
|
||||
use serde::{Deserialize, Deserializer, Serialize};
|
||||
|
||||
|
@ -74,6 +79,31 @@ impl<const N: usize> Default for Hex<N> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> Deref for Hex<N> {
|
||||
type Target = [u8; N];
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> DerefMut for Hex<N> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> Borrow<[u8; N]> for Hex<N> {
|
||||
fn borrow(&self) -> &[u8; N] {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> AsRef<[u8; N]> for Hex<N> {
|
||||
fn as_ref(&self) -> &[u8; N] {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<const N: usize> From<Hex<N>> for [u8; N] {
|
||||
fn from(hex: Hex<N>) -> Self {
|
||||
hex.0
|
||||
|
|
|
@ -3,6 +3,11 @@
|
|||
//! This module provides transparent wrapper types for
|
||||
//! arrays that (de)serialize from hexadecimal input/output.
|
||||
|
||||
use std::{
|
||||
borrow::Borrow,
|
||||
ops::{Deref, DerefMut},
|
||||
};
|
||||
|
||||
use hex::FromHexError;
|
||||
use serde::{Deserialize, Deserializer, Serialize};
|
||||
|
||||
|
@ -42,11 +47,6 @@ impl HexVec {
|
|||
Self(Vec::new())
|
||||
}
|
||||
|
||||
/// [`Vec::is_empty`].
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.0.is_empty()
|
||||
}
|
||||
|
||||
/// Returns an empty [`Self`] if `array` is all `0`s.
|
||||
///
|
||||
/// ```rust
|
||||
|
@ -73,6 +73,31 @@ impl<'de> Deserialize<'de> for HexVec {
|
|||
}
|
||||
}
|
||||
|
||||
impl Deref for HexVec {
|
||||
type Target = Vec<u8>;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl DerefMut for HexVec {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl Borrow<Vec<u8>> for HexVec {
|
||||
fn borrow(&self) -> &Vec<u8> {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<Vec<u8>> for HexVec {
|
||||
fn as_ref(&self) -> &Vec<u8> {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<HexVec> for Vec<u8> {
|
||||
fn from(hex: HexVec) -> Self {
|
||||
hex.0
|
||||
|
|
|
@ -175,13 +175,13 @@ pub enum BlockchainReadRequest {
|
|||
/// Get the amount of alternative chains that exist.
|
||||
AltChainCount,
|
||||
|
||||
/// TODO
|
||||
/// Get transaction blobs by their hashes.
|
||||
Transactions { tx_hashes: HashSet<[u8; 32]> },
|
||||
|
||||
/// TODO
|
||||
/// Get the total amount of RCT outputs in the blockchain.
|
||||
TotalRctOutputs,
|
||||
|
||||
/// TODO
|
||||
/// Get the output indexes of a transaction.
|
||||
TxOutputIndexes { tx_hash: [u8; 32] },
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue