Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock
Original file line number Diff line number Diff line change
Expand Up @@ -6034,6 +6034,7 @@ dependencies = [
"incrementalmerkletree",
"itertools 0.13.0",
"jubjub",
"k256",
"lazy_static",
"nonempty",
"num-integer",
Expand Down
2 changes: 2 additions & 0 deletions zebra-chain/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,8 @@ rand_chacha = { version = "0.3.1", optional = true }

zebra-test = { path = "../zebra-test/", version = "1.0.0-beta.41", optional = true }

k256 = "0.13.3"

[dev-dependencies]
# Benchmarks
criterion = { version = "0.5.1", features = ["html_reports"] }
Expand Down
2 changes: 1 addition & 1 deletion zebra-chain/src/orchard_zsa.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ pub(crate) mod arbitrary;
#[cfg(any(test, feature = "proptest-impl"))]
pub mod tests;

mod asset_state;
pub mod asset_state;
mod burn;
mod issuance;

Expand Down
44 changes: 39 additions & 5 deletions zebra-chain/src/orchard_zsa/asset_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@ use std::{
use orchard::issuance::IssueAction;
pub use orchard::note::AssetBase;

use crate::transaction::Transaction;
use crate::{serialization::ZcashSerialize, transaction::Transaction};

use super::BurnItem;

/// The circulating supply and whether that supply has been finalized.
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, serde::Serialize)]
pub struct AssetState {
/// Indicates whether the asset is finalized such that no more of it can be issued.
pub is_finalized: bool,
Expand All @@ -28,8 +28,7 @@ pub struct AssetState {
pub struct AssetStateChange {
/// Whether the asset should be finalized such that no more of it can be issued.
pub should_finalize: bool,
// FIXME: is this a correct comment?
/// Whether the asset should be finalized such that no more of it can be issued.
/// Whether the asset has been issued in this change.
pub includes_issuance: bool,
/// The change in supply from newly issued assets or burned assets, if any.
pub supply_change: SupplyChange,
Expand Down Expand Up @@ -211,7 +210,6 @@ impl AssetStateChange {
})
.unwrap_or_default()
.into_iter()
// FIXME: We use 0 as a value - is that correct?
.map(|asset_base| Self::new(asset_base, SupplyChange::Issuance(0), true));

supply_changes.chain(finalize_changes)
Expand Down Expand Up @@ -374,3 +372,39 @@ impl std::ops::Add for IssuedAssetsChange {
}
}
}

impl From<Arc<[IssuedAssetsChange]>> for IssuedAssetsChange {
fn from(change: Arc<[IssuedAssetsChange]>) -> Self {
change
.iter()
.cloned()
.reduce(|a, b| a + b)
.unwrap_or_default()
}
}

/// Used in snapshot test for `getassetstate` RPC method.
// TODO: Replace with `AssetBase::random()` or a known value.
pub trait RandomAssetBase {
/// Generates a ZSA random asset.
///
/// This is only used in tests.
fn random_serialized() -> String;
}

impl RandomAssetBase for AssetBase {
fn random_serialized() -> String {
let isk = orchard::keys::IssuanceAuthorizingKey::from_bytes(
k256::NonZeroScalar::random(&mut rand_core::OsRng)
.to_bytes()
.into(),
)
.unwrap();
let ik = orchard::keys::IssuanceValidatingKey::from(&isk);
let asset_descr = b"zsa_asset".to_vec();
AssetBase::derive(&ik, &asset_descr)
.zcash_serialize_to_vec()
.map(hex::encode)
.expect("random asset base should serialize")
}
}
11 changes: 3 additions & 8 deletions zebra-consensus/src/block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ use std::{
};

use chrono::Utc;
use futures::stream::FuturesOrdered;
use futures::stream::FuturesUnordered;
use futures_util::FutureExt;
use thiserror::Error;
use tower::{Service, ServiceExt};
Expand Down Expand Up @@ -226,7 +226,7 @@ where
tx::check::coinbase_outputs_are_decryptable(&coinbase_tx, &network, height)?;

// Send transactions to the transaction verifier to be checked
let mut async_checks = FuturesOrdered::new();
let mut async_checks = FuturesUnordered::new();

let known_utxos = Arc::new(transparent::new_ordered_outputs(
&block,
Expand All @@ -243,7 +243,7 @@ where
height,
time: block.header.time,
});
async_checks.push_back(rsp);
async_checks.push(rsp);
}
tracing::trace!(len = async_checks.len(), "built async tx checks");

Expand All @@ -252,7 +252,6 @@ where
// Sum up some block totals from the transaction responses.
let mut legacy_sigop_count = 0;
let mut block_miner_fees = Ok(Amount::zero());
let mut issued_assets_changes = Vec::new();

use futures::StreamExt;
while let Some(result) = async_checks.next().await {
Expand All @@ -261,7 +260,6 @@ where
tx_id: _,
miner_fee,
legacy_sigop_count: tx_legacy_sigop_count,
issued_assets_change,
} = result
.map_err(Into::into)
.map_err(VerifyBlockError::Transaction)?
Expand All @@ -276,8 +274,6 @@ where
if let Some(miner_fee) = miner_fee {
block_miner_fees += miner_fee;
}

issued_assets_changes.push(issued_assets_change);
}

// Check the summed block totals
Expand Down Expand Up @@ -327,7 +323,6 @@ where
new_outputs,
transaction_hashes,
deferred_balance: Some(expected_deferred_amount),
issued_assets_changes: issued_assets_changes.into(),
};

// Return early for proposal requests when getblocktemplate-rpcs feature is enabled
Expand Down
5 changes: 2 additions & 3 deletions zebra-consensus/src/checkpoint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ use crate::{
Progress::{self, *},
TargetHeight::{self, *},
},
error::{BlockError, SubsidyError, TransactionError},
error::{BlockError, SubsidyError},
funding_stream_values, BoxError, ParameterCheckpoint as _,
};

Expand Down Expand Up @@ -619,8 +619,7 @@ where
};

// don't do precalculation until the block passes basic difficulty checks
let block = CheckpointVerifiedBlock::new(block, Some(hash), expected_deferred_amount)
.ok_or_else(|| VerifyBlockError::from(TransactionError::InvalidAssetIssuanceOrBurn))?;
let block = CheckpointVerifiedBlock::new(block, Some(hash), expected_deferred_amount);

crate::block::check::merkle_root_validity(
&self.network,
Expand Down
3 changes: 0 additions & 3 deletions zebra-consensus/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -239,9 +239,6 @@ pub enum TransactionError {
#[error("failed to verify ZIP-317 transaction rules, transaction was not inserted to mempool")]
#[cfg_attr(any(test, feature = "proptest-impl"), proptest(skip))]
Zip317(#[from] zebra_chain::transaction::zip317::Error),

#[error("failed to validate asset issuance and/or burns")]
InvalidAssetIssuanceOrBurn,
}

impl From<ValidateContextError> for TransactionError {
Expand Down
6 changes: 0 additions & 6 deletions zebra-consensus/src/transaction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ use tracing::Instrument;
use zebra_chain::{
amount::{Amount, NonNegative},
block, orchard,
orchard_zsa::IssuedAssetsChange,
parameters::{Network, NetworkUpgrade},
primitives::Groth16Proof,
sapling,
Expand Down Expand Up @@ -144,10 +143,6 @@ pub enum Response {
/// The number of legacy signature operations in this transaction's
/// transparent inputs and outputs.
legacy_sigop_count: u64,

/// The changes to the issued assets map that should be applied for
/// this transaction.
issued_assets_change: IssuedAssetsChange,
},

/// A response to a mempool transaction verification request.
Expand Down Expand Up @@ -485,7 +480,6 @@ where
tx_id,
miner_fee,
legacy_sigop_count,
issued_assets_change: IssuedAssetsChange::from_transaction(&tx).ok_or(TransactionError::InvalidAssetIssuanceOrBurn)?,
},
Request::Mempool { transaction, .. } => {
let transaction = VerifiedUnminedTx::new(
Expand Down
43 changes: 42 additions & 1 deletion zebra-rpc/src/methods.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ use zebra_chain::{
block::{self, Height, SerializedBlock},
chain_tip::{ChainTip, NetworkChainTipHeightEstimator},
parameters::{ConsensusBranchId, Network, NetworkUpgrade},
serialization::ZcashDeserialize,
serialization::{ZcashDeserialize, ZcashDeserializeInto},
subtree::NoteCommitmentSubtreeIndex,
transaction::{self, SerializedTransaction, Transaction, UnminedTx},
transparent::{self, Address},
Expand Down Expand Up @@ -302,6 +302,17 @@ pub trait Rpc {
address_strings: AddressStrings,
) -> BoxFuture<Result<Vec<GetAddressUtxos>>>;

/// Returns the asset state of the provided asset base at the best chain tip or finalized chain tip.
///
/// method: post
/// tags: blockchain
#[rpc(name = "getassetstate")]
fn get_asset_state(
&self,
asset_base: String,
include_non_finalized: Option<bool>,
) -> BoxFuture<Result<zebra_chain::orchard_zsa::AssetState>>;

/// Stop the running zebrad process.
///
/// # Notes
Expand Down Expand Up @@ -1358,6 +1369,36 @@ where
.boxed()
}

fn get_asset_state(
&self,
asset_base: String,
include_non_finalized: Option<bool>,
) -> BoxFuture<Result<zebra_chain::orchard_zsa::AssetState>> {
let state = self.state.clone();
let include_non_finalized = include_non_finalized.unwrap_or(true);

async move {
let asset_base = hex::decode(asset_base)
.map_server_error()?
.zcash_deserialize_into()
.map_server_error()?;

let request = zebra_state::ReadRequest::AssetState {
asset_base,
include_non_finalized,
};

let zebra_state::ReadResponse::AssetState(asset_state) =
state.oneshot(request).await.map_server_error()?
else {
unreachable!("unexpected response from state service");
};

asset_state.ok_or_server_error("asset base not found")
}
.boxed()
}

fn stop(&self) -> Result<String> {
#[cfg(not(target_os = "windows"))]
if self.network.is_regtest() {
Expand Down
36 changes: 36 additions & 0 deletions zebra-rpc/src/methods/tests/snapshot.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ use zebra_chain::{
block::Block,
chain_tip::mock::MockChainTip,
orchard,
orchard_zsa::{asset_state::RandomAssetBase, AssetBase, AssetState},
parameters::{
subsidy::POST_NU6_FUNDING_STREAMS_TESTNET,
testnet::{self, ConfiguredActivationHeights, Parameters},
Expand Down Expand Up @@ -536,6 +537,41 @@ async fn test_mocked_rpc_response_data_for_network(network: &Network) {
settings.bind(|| {
insta::assert_json_snapshot!(format!("z_get_subtrees_by_index_for_orchard"), subtrees)
});

// Test the response format from `getassetstate`.

// Prepare the state response and make the RPC request.
let rsp = state
.expect_request_that(|req| matches!(req, ReadRequest::AssetState { .. }))
.map(|responder| responder.respond(ReadResponse::AssetState(None)));
let req = rpc.get_asset_state(AssetBase::random_serialized(), None);

// Get the RPC error response.
let (asset_state_rsp, ..) = tokio::join!(req, rsp);
let asset_state = asset_state_rsp.expect_err("The RPC response should be an error");

// Check the error response.
settings
.bind(|| insta::assert_json_snapshot!(format!("get_asset_state_not_found"), asset_state));

// Prepare the state response and make the RPC request.
let rsp = state
.expect_request_that(|req| matches!(req, ReadRequest::AssetState { .. }))
.map(|responder| {
responder.respond(ReadResponse::AssetState(Some(AssetState {
is_finalized: true,
total_supply: 1000,
})))
});
let req = rpc.get_asset_state(AssetBase::random_serialized(), None);

// Get the RPC response.
let (asset_state_rsp, ..) = tokio::join!(req, rsp);
let asset_state =
asset_state_rsp.expect("The RPC response should contain a `AssetState` struct.");

// Check the response.
settings.bind(|| insta::assert_json_snapshot!(format!("get_asset_state"), asset_state));
}

/// Snapshot `getinfo` response, using `cargo insta` and JSON serialization.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
source: zebra-rpc/src/methods/tests/snapshot.rs
expression: asset_state
---
{
"is_finalized": true,
"total_supply": 1000
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
source: zebra-rpc/src/methods/tests/snapshot.rs
expression: asset_state
---
{
"is_finalized": true,
"total_supply": 1000
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
source: zebra-rpc/src/methods/tests/snapshot.rs
expression: asset_state
---
{
"code": 0,
"message": "asset base not found"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
source: zebra-rpc/src/methods/tests/snapshot.rs
expression: asset_state
---
{
"code": 0,
"message": "asset base not found"
}
5 changes: 0 additions & 5 deletions zebra-state/src/arbitrary.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ use std::sync::Arc;
use zebra_chain::{
amount::Amount,
block::{self, Block},
orchard_zsa::IssuedAssetsChange,
transaction::Transaction,
transparent,
value_balance::ValueBalance,
Expand All @@ -31,8 +30,6 @@ impl Prepare for Arc<Block> {
let transaction_hashes: Arc<[_]> = block.transactions.iter().map(|tx| tx.hash()).collect();
let new_outputs =
transparent::new_ordered_outputs_with_height(&block, height, &transaction_hashes);
let issued_assets_changes = IssuedAssetsChange::from_transactions(&block.transactions)
.expect("prepared blocks should be semantically valid");

SemanticallyVerifiedBlock {
block,
Expand All @@ -41,7 +38,6 @@ impl Prepare for Arc<Block> {
new_outputs,
transaction_hashes,
deferred_balance: None,
issued_assets_changes,
}
}
}
Expand Down Expand Up @@ -120,7 +116,6 @@ impl ContextuallyVerifiedBlock {
new_outputs,
transaction_hashes,
deferred_balance: _,
issued_assets_changes: _,
} = block.into();

Self {
Expand Down
Loading