Merge pull request #1462 from nuttycom/1434-store_decrypted_tx
zcash_client_sqlite: Store received UTXOs in `store_decrypted_tx`
This commit is contained in:
commit
4f95bd22d4
|
@ -26,7 +26,8 @@ funds to those addresses. See [ZIP 320](https://zips.z.cash/zip-0320) for detail
|
||||||
### Added
|
### Added
|
||||||
- `zcash_client_backend::data_api`:
|
- `zcash_client_backend::data_api`:
|
||||||
- `chain::BlockCache` trait, behind the `sync` feature flag.
|
- `chain::BlockCache` trait, behind the `sync` feature flag.
|
||||||
- `WalletRead::get_spendable_transparent_outputs`.
|
- `WalletRead::get_spendable_transparent_outputs`
|
||||||
|
- `DecryptedTransaction::mined_height`
|
||||||
- `zcash_client_backend::fees`:
|
- `zcash_client_backend::fees`:
|
||||||
- `EphemeralBalance`
|
- `EphemeralBalance`
|
||||||
- `ChangeValue::shielded, is_ephemeral`
|
- `ChangeValue::shielded, is_ephemeral`
|
||||||
|
@ -39,7 +40,9 @@ funds to those addresses. See [ZIP 320](https://zips.z.cash/zip-0320) for detail
|
||||||
- `testing` module
|
- `testing` module
|
||||||
- `zcash_client_backend::sync` module, behind the `sync` feature flag.
|
- `zcash_client_backend::sync` module, behind the `sync` feature flag.
|
||||||
- `zcash_client_backend::tor` module, behind the `tor` feature flag.
|
- `zcash_client_backend::tor` module, behind the `tor` feature flag.
|
||||||
- `zcash_client_backend::wallet::Recipient::map_ephemeral_transparent_outpoint`
|
- `zcash_client_backend::wallet`:
|
||||||
|
- `Recipient::map_ephemeral_transparent_outpoint`
|
||||||
|
- `WalletTransparentOutput::mined_height`
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
- MSRV is now 1.70.0.
|
- MSRV is now 1.70.0.
|
||||||
|
@ -67,6 +70,7 @@ funds to those addresses. See [ZIP 320](https://zips.z.cash/zip-0320) for detail
|
||||||
- `error::Error` has new `Address` and (when the "transparent-inputs" feature
|
- `error::Error` has new `Address` and (when the "transparent-inputs" feature
|
||||||
is enabled) `PaysEphemeralTransparentAddress` variants.
|
is enabled) `PaysEphemeralTransparentAddress` variants.
|
||||||
- `wallet::input_selection::InputSelectorError` has a new `Address` variant.
|
- `wallet::input_selection::InputSelectorError` has a new `Address` variant.
|
||||||
|
- `DecryptedTransaction::new` takes an additional `mined_height` argument.
|
||||||
- `zcash_client_backend::data_api::fees`
|
- `zcash_client_backend::data_api::fees`
|
||||||
- When the "transparent-inputs" feature is enabled, `ChangeValue` can also
|
- When the "transparent-inputs" feature is enabled, `ChangeValue` can also
|
||||||
represent an ephemeral transparent output in a proposal. Accordingly, the
|
represent an ephemeral transparent output in a proposal. Accordingly, the
|
||||||
|
@ -94,6 +98,9 @@ funds to those addresses. See [ZIP 320](https://zips.z.cash/zip-0320) for detail
|
||||||
tracking the original address to which value was sent. There is also a new
|
tracking the original address to which value was sent. There is also a new
|
||||||
`EphemeralTransparent` variant, and an additional generic parameter for the
|
`EphemeralTransparent` variant, and an additional generic parameter for the
|
||||||
type of metadata associated with an ephemeral transparent outpoint.
|
type of metadata associated with an ephemeral transparent outpoint.
|
||||||
|
- `zcash_client_backend::wallet::WalletTransparentOutput::from_parts`
|
||||||
|
now takes its height argument as `Option<BlockHeight>` rather than
|
||||||
|
`BlockHeight`.
|
||||||
|
|
||||||
### Removed
|
### Removed
|
||||||
- `zcash_client_backend::data_api`:
|
- `zcash_client_backend::data_api`:
|
||||||
|
@ -102,6 +109,8 @@ funds to those addresses. See [ZIP 320](https://zips.z.cash/zip-0320) for detail
|
||||||
`WalletRead::get_spendable_transparent_outputs` instead.
|
`WalletRead::get_spendable_transparent_outputs` instead.
|
||||||
- `zcash_client_backend::fees::ChangeValue::new`. Use `ChangeValue::shielded`
|
- `zcash_client_backend::fees::ChangeValue::new`. Use `ChangeValue::shielded`
|
||||||
or `ChangeValue::ephemeral_transparent` instead.
|
or `ChangeValue::ephemeral_transparent` instead.
|
||||||
|
- `zcash_client_backend::wallet::WalletTransparentOutput::height`
|
||||||
|
(use `WalletTransparentOutput::mined_height` instead).
|
||||||
|
|
||||||
## [0.12.1] - 2024-03-27
|
## [0.12.1] - 2024-03-27
|
||||||
|
|
||||||
|
|
|
@ -1263,6 +1263,7 @@ impl<A> ScannedBlock<A> {
|
||||||
/// The purpose of this struct is to permit atomic updates of the
|
/// The purpose of this struct is to permit atomic updates of the
|
||||||
/// wallet database when transactions are successfully decrypted.
|
/// wallet database when transactions are successfully decrypted.
|
||||||
pub struct DecryptedTransaction<'a, AccountId> {
|
pub struct DecryptedTransaction<'a, AccountId> {
|
||||||
|
mined_height: Option<BlockHeight>,
|
||||||
tx: &'a Transaction,
|
tx: &'a Transaction,
|
||||||
sapling_outputs: Vec<DecryptedOutput<sapling::Note, AccountId>>,
|
sapling_outputs: Vec<DecryptedOutput<sapling::Note, AccountId>>,
|
||||||
#[cfg(feature = "orchard")]
|
#[cfg(feature = "orchard")]
|
||||||
|
@ -1272,6 +1273,7 @@ pub struct DecryptedTransaction<'a, AccountId> {
|
||||||
impl<'a, AccountId> DecryptedTransaction<'a, AccountId> {
|
impl<'a, AccountId> DecryptedTransaction<'a, AccountId> {
|
||||||
/// Constructs a new [`DecryptedTransaction`] from its constituent parts.
|
/// Constructs a new [`DecryptedTransaction`] from its constituent parts.
|
||||||
pub fn new(
|
pub fn new(
|
||||||
|
mined_height: Option<BlockHeight>,
|
||||||
tx: &'a Transaction,
|
tx: &'a Transaction,
|
||||||
sapling_outputs: Vec<DecryptedOutput<sapling::Note, AccountId>>,
|
sapling_outputs: Vec<DecryptedOutput<sapling::Note, AccountId>>,
|
||||||
#[cfg(feature = "orchard")] orchard_outputs: Vec<
|
#[cfg(feature = "orchard")] orchard_outputs: Vec<
|
||||||
|
@ -1279,6 +1281,7 @@ impl<'a, AccountId> DecryptedTransaction<'a, AccountId> {
|
||||||
>,
|
>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
mined_height,
|
||||||
tx,
|
tx,
|
||||||
sapling_outputs,
|
sapling_outputs,
|
||||||
#[cfg(feature = "orchard")]
|
#[cfg(feature = "orchard")]
|
||||||
|
@ -1286,6 +1289,10 @@ impl<'a, AccountId> DecryptedTransaction<'a, AccountId> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the height at which the transaction was mined, if known.
|
||||||
|
pub fn mined_height(&self) -> Option<BlockHeight> {
|
||||||
|
self.mined_height
|
||||||
|
}
|
||||||
/// Returns the raw transaction data.
|
/// Returns the raw transaction data.
|
||||||
pub fn tx(&self) -> &Transaction {
|
pub fn tx(&self) -> &Transaction {
|
||||||
self.tx
|
self.tx
|
||||||
|
|
|
@ -215,6 +215,7 @@ pub fn decrypt_transaction<'a, P: consensus::Parameters, AccountId: Copy>(
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
DecryptedTransaction::new(
|
DecryptedTransaction::new(
|
||||||
|
Some(height),
|
||||||
tx,
|
tx,
|
||||||
sapling_outputs,
|
sapling_outputs,
|
||||||
#[cfg(feature = "orchard")]
|
#[cfg(feature = "orchard")]
|
||||||
|
|
|
@ -249,7 +249,7 @@ impl<AccountId> WalletTx<AccountId> {
|
||||||
pub struct WalletTransparentOutput {
|
pub struct WalletTransparentOutput {
|
||||||
outpoint: OutPoint,
|
outpoint: OutPoint,
|
||||||
txout: TxOut,
|
txout: TxOut,
|
||||||
height: BlockHeight,
|
mined_height: Option<BlockHeight>,
|
||||||
recipient_address: TransparentAddress,
|
recipient_address: TransparentAddress,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -257,14 +257,14 @@ impl WalletTransparentOutput {
|
||||||
pub fn from_parts(
|
pub fn from_parts(
|
||||||
outpoint: OutPoint,
|
outpoint: OutPoint,
|
||||||
txout: TxOut,
|
txout: TxOut,
|
||||||
height: BlockHeight,
|
mined_height: Option<BlockHeight>,
|
||||||
) -> Option<WalletTransparentOutput> {
|
) -> Option<WalletTransparentOutput> {
|
||||||
txout
|
txout
|
||||||
.recipient_address()
|
.recipient_address()
|
||||||
.map(|recipient_address| WalletTransparentOutput {
|
.map(|recipient_address| WalletTransparentOutput {
|
||||||
outpoint,
|
outpoint,
|
||||||
txout,
|
txout,
|
||||||
height,
|
mined_height,
|
||||||
recipient_address,
|
recipient_address,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -277,8 +277,8 @@ impl WalletTransparentOutput {
|
||||||
&self.txout
|
&self.txout
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn height(&self) -> BlockHeight {
|
pub fn mined_height(&self) -> Option<BlockHeight> {
|
||||||
self.height
|
self.mined_height
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn recipient_address(&self) -> &TransparentAddress {
|
pub fn recipient_address(&self) -> &TransparentAddress {
|
||||||
|
|
|
@ -124,6 +124,9 @@ use wallet::{
|
||||||
SubtreeScanProgress,
|
SubtreeScanProgress,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#[cfg(feature = "transparent-inputs")]
|
||||||
|
use wallet::transparent::{find_account_for_transparent_address, put_transparent_output};
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod testing;
|
mod testing;
|
||||||
|
|
||||||
|
@ -290,7 +293,7 @@ impl<C: Borrow<rusqlite::Connection>, P: consensus::Parameters> InputSource for
|
||||||
&self,
|
&self,
|
||||||
outpoint: &OutPoint,
|
outpoint: &OutPoint,
|
||||||
) -> Result<Option<WalletTransparentOutput>, Self::Error> {
|
) -> Result<Option<WalletTransparentOutput>, Self::Error> {
|
||||||
wallet::transparent::get_unspent_transparent_output(self.conn.borrow(), outpoint)
|
wallet::transparent::get_wallet_transparent_output(self.conn.borrow(), outpoint, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "transparent-inputs")]
|
#[cfg(feature = "transparent-inputs")]
|
||||||
|
@ -1376,6 +1379,24 @@ impl<P: consensus::Parameters> WalletWrite for WalletDb<rusqlite::Connection, P>
|
||||||
#[cfg(feature = "transparent-inputs")]
|
#[cfg(feature = "transparent-inputs")]
|
||||||
wallet::transparent::ephemeral::mark_ephemeral_address_as_seen(wdb, &address, tx_ref)?;
|
wallet::transparent::ephemeral::mark_ephemeral_address_as_seen(wdb, &address, tx_ref)?;
|
||||||
|
|
||||||
|
// If the output belongs to the wallet, add it to `transparent_received_outputs`.
|
||||||
|
#[cfg(feature = "transparent-inputs")]
|
||||||
|
if let Some(account_id) = find_account_for_transparent_address(
|
||||||
|
wdb.conn.0,
|
||||||
|
&wdb.params,
|
||||||
|
&address
|
||||||
|
)? {
|
||||||
|
put_transparent_output(
|
||||||
|
wdb.conn.0,
|
||||||
|
&wdb.params,
|
||||||
|
&OutPoint::new(d_tx.tx().txid().into(), u32::try_from(output_index).unwrap()),
|
||||||
|
txout,
|
||||||
|
d_tx.mined_height(),
|
||||||
|
&address,
|
||||||
|
account_id
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
|
||||||
// If a transaction we observe contains spends from our wallet, we will
|
// If a transaction we observe contains spends from our wallet, we will
|
||||||
// store its transparent outputs in the same way they would be stored by
|
// store its transparent outputs in the same way they would be stored by
|
||||||
// create_spend_to_address.
|
// create_spend_to_address.
|
||||||
|
|
|
@ -315,7 +315,9 @@ pub(crate) fn send_multi_step_proposed_transfer<T: ShieldedPoolTester>() {
|
||||||
};
|
};
|
||||||
use zcash_protocol::value::ZatBalance;
|
use zcash_protocol::value::ZatBalance;
|
||||||
|
|
||||||
use crate::wallet::{sapling::tests::test_prover, GAP_LIMIT};
|
use crate::wallet::{
|
||||||
|
sapling::tests::test_prover, transparent::get_wallet_transparent_output, GAP_LIMIT,
|
||||||
|
};
|
||||||
|
|
||||||
let mut st = TestBuilder::new()
|
let mut st = TestBuilder::new()
|
||||||
.with_block_cache()
|
.with_block_cache()
|
||||||
|
@ -553,8 +555,9 @@ pub(crate) fn send_multi_step_proposed_transfer<T: ShieldedPoolTester>() {
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
let (colliding_addr, _) = &known_addrs[10];
|
let (colliding_addr, _) = &known_addrs[10];
|
||||||
|
let utxo_value = (value - zip317::MINIMUM_FEE).unwrap();
|
||||||
assert_matches!(
|
assert_matches!(
|
||||||
builder.add_transparent_output(colliding_addr, (value - zip317::MINIMUM_FEE).unwrap()),
|
builder.add_transparent_output(colliding_addr, utxo_value),
|
||||||
Ok(_)
|
Ok(_)
|
||||||
);
|
);
|
||||||
let sk = account
|
let sk = account
|
||||||
|
@ -577,7 +580,9 @@ pub(crate) fn send_multi_step_proposed_transfer<T: ShieldedPoolTester>() {
|
||||||
&zip317::FeeRule::standard(),
|
&zip317::FeeRule::standard(),
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
let txid = build_result.transaction().txid();
|
||||||
let decrypted_tx = DecryptedTransaction::<AccountId>::new(
|
let decrypted_tx = DecryptedTransaction::<AccountId>::new(
|
||||||
|
None,
|
||||||
build_result.transaction(),
|
build_result.transaction(),
|
||||||
vec![],
|
vec![],
|
||||||
#[cfg(feature = "orchard")]
|
#[cfg(feature = "orchard")]
|
||||||
|
@ -585,6 +590,13 @@ pub(crate) fn send_multi_step_proposed_transfer<T: ShieldedPoolTester>() {
|
||||||
);
|
);
|
||||||
st.wallet_mut().store_decrypted_tx(decrypted_tx).unwrap();
|
st.wallet_mut().store_decrypted_tx(decrypted_tx).unwrap();
|
||||||
|
|
||||||
|
// We call get_wallet_transparent_output with `allow_unspendable = true` to verify
|
||||||
|
// storage because the decrypted transaction has not yet been mined.
|
||||||
|
let utxo =
|
||||||
|
get_wallet_transparent_output(&st.db_data.conn, &OutPoint::new(txid.into(), 0), true)
|
||||||
|
.unwrap();
|
||||||
|
assert_matches!(utxo, Some(v) if v.value() == utxo_value);
|
||||||
|
|
||||||
// That should have advanced the start of the gap to index 11.
|
// That should have advanced the start of the gap to index 11.
|
||||||
let new_known_addrs = st
|
let new_known_addrs = st
|
||||||
.wallet()
|
.wallet()
|
||||||
|
@ -1532,7 +1544,7 @@ pub(crate) fn shield_transparent<T: ShieldedPoolTester>() {
|
||||||
value: NonNegativeAmount::const_from_u64(10000),
|
value: NonNegativeAmount::const_from_u64(10000),
|
||||||
script_pubkey: taddr.script(),
|
script_pubkey: taddr.script(),
|
||||||
},
|
},
|
||||||
h,
|
Some(h),
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
|
|
@ -169,7 +169,7 @@ fn to_unspent_transparent_output(row: &Row) -> Result<WalletTransparentOutput, S
|
||||||
let value = NonNegativeAmount::from_nonnegative_i64(raw_value).map_err(|_| {
|
let value = NonNegativeAmount::from_nonnegative_i64(raw_value).map_err(|_| {
|
||||||
SqliteClientError::CorruptedData(format!("Invalid UTXO value: {}", raw_value))
|
SqliteClientError::CorruptedData(format!("Invalid UTXO value: {}", raw_value))
|
||||||
})?;
|
})?;
|
||||||
let height: u32 = row.get("received_height")?;
|
let height: Option<u32> = row.get("received_height")?;
|
||||||
|
|
||||||
let outpoint = OutPoint::new(txid_bytes, index);
|
let outpoint = OutPoint::new(txid_bytes, index);
|
||||||
WalletTransparentOutput::from_parts(
|
WalletTransparentOutput::from_parts(
|
||||||
|
@ -178,7 +178,7 @@ fn to_unspent_transparent_output(row: &Row) -> Result<WalletTransparentOutput, S
|
||||||
value,
|
value,
|
||||||
script_pubkey,
|
script_pubkey,
|
||||||
},
|
},
|
||||||
BlockHeight::from(height),
|
height.map(BlockHeight::from),
|
||||||
)
|
)
|
||||||
.ok_or_else(|| {
|
.ok_or_else(|| {
|
||||||
SqliteClientError::CorruptedData(
|
SqliteClientError::CorruptedData(
|
||||||
|
@ -188,16 +188,18 @@ fn to_unspent_transparent_output(row: &Row) -> Result<WalletTransparentOutput, S
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Select an output to fund a new transaction that is targeting at least `chain_tip_height + 1`.
|
/// Select an output to fund a new transaction that is targeting at least `chain_tip_height + 1`.
|
||||||
pub(crate) fn get_unspent_transparent_output(
|
pub(crate) fn get_wallet_transparent_output(
|
||||||
conn: &rusqlite::Connection,
|
conn: &rusqlite::Connection,
|
||||||
outpoint: &OutPoint,
|
outpoint: &OutPoint,
|
||||||
|
allow_unspendable: bool,
|
||||||
) -> Result<Option<WalletTransparentOutput>, SqliteClientError> {
|
) -> Result<Option<WalletTransparentOutput>, SqliteClientError> {
|
||||||
let chain_tip_height = chain_tip_height(conn)?;
|
let chain_tip_height = chain_tip_height(conn)?;
|
||||||
|
|
||||||
// This could, in very rare circumstances, return as unspent outputs that are actually not
|
// This could return as unspent outputs that are actually not spendable, if they are the
|
||||||
// spendable, if they are the outputs of deshielding transactions where the spend anchors have
|
// outputs of deshielding transactions where the spend anchors have been invalidated by a
|
||||||
// been invalidated by a rewind. There isn't a way to detect this circumstance at present, but
|
// rewind or spent in a transaction that has not been observed by this wallet. There isn't a
|
||||||
// it should be vanishingly rare as the vast majority of rewinds are of a single block.
|
// way to detect the circumstance related to anchor invalidation at present, but it should be
|
||||||
|
// vanishingly rare as the vast majority of rewinds are of a single block.
|
||||||
let mut stmt_select_utxo = conn.prepare_cached(
|
let mut stmt_select_utxo = conn.prepare_cached(
|
||||||
"SELECT t.txid, u.output_index, u.script,
|
"SELECT t.txid, u.output_index, u.script,
|
||||||
u.value_zat, t.mined_height AS received_height
|
u.value_zat, t.mined_height AS received_height
|
||||||
|
@ -207,6 +209,9 @@ pub(crate) fn get_unspent_transparent_output(
|
||||||
AND u.output_index = :output_index
|
AND u.output_index = :output_index
|
||||||
-- the transaction that created the output is mined or is definitely unexpired
|
-- the transaction that created the output is mined or is definitely unexpired
|
||||||
AND (
|
AND (
|
||||||
|
:allow_unspendable
|
||||||
|
OR (
|
||||||
|
(
|
||||||
t.mined_height IS NOT NULL -- tx is mined
|
t.mined_height IS NOT NULL -- tx is mined
|
||||||
-- TODO: uncomment the following two lines in order to enable zero-conf spends
|
-- TODO: uncomment the following two lines in order to enable zero-conf spends
|
||||||
-- OR t.expiry_height = 0 -- tx will not expire
|
-- OR t.expiry_height = 0 -- tx will not expire
|
||||||
|
@ -220,7 +225,10 @@ pub(crate) fn get_unspent_transparent_output(
|
||||||
WHERE tx.mined_height IS NOT NULL -- the spending tx is mined
|
WHERE tx.mined_height IS NOT NULL -- the spending tx is mined
|
||||||
OR tx.expiry_height = 0 -- the spending tx will not expire
|
OR tx.expiry_height = 0 -- the spending tx will not expire
|
||||||
OR tx.expiry_height >= :mempool_height -- the spending tx has not yet expired
|
OR tx.expiry_height >= :mempool_height -- the spending tx has not yet expired
|
||||||
)",
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
",
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let result: Result<Option<WalletTransparentOutput>, SqliteClientError> = stmt_select_utxo
|
let result: Result<Option<WalletTransparentOutput>, SqliteClientError> = stmt_select_utxo
|
||||||
|
@ -229,6 +237,7 @@ pub(crate) fn get_unspent_transparent_output(
|
||||||
":txid": outpoint.hash(),
|
":txid": outpoint.hash(),
|
||||||
":output_index": outpoint.n(),
|
":output_index": outpoint.n(),
|
||||||
":mempool_height": chain_tip_height.map(|h| u32::from(h) + 1),
|
":mempool_height": chain_tip_height.map(|h| u32::from(h) + 1),
|
||||||
|
":allow_unspendable": allow_unspendable
|
||||||
],
|
],
|
||||||
to_unspent_transparent_output,
|
to_unspent_transparent_output,
|
||||||
)?
|
)?
|
||||||
|
@ -456,7 +465,7 @@ pub(crate) fn put_received_transparent_utxo<P: consensus::Parameters>(
|
||||||
params,
|
params,
|
||||||
output.outpoint(),
|
output.outpoint(),
|
||||||
output.txout(),
|
output.txout(),
|
||||||
Some(output.height()),
|
output.mined_height(),
|
||||||
address,
|
address,
|
||||||
receiving_account,
|
receiving_account,
|
||||||
)
|
)
|
||||||
|
@ -695,7 +704,8 @@ mod tests {
|
||||||
|
|
||||||
// Pretend the output's transaction was mined at `height_1`.
|
// Pretend the output's transaction was mined at `height_1`.
|
||||||
let utxo =
|
let utxo =
|
||||||
WalletTransparentOutput::from_parts(outpoint.clone(), txout.clone(), height_1).unwrap();
|
WalletTransparentOutput::from_parts(outpoint.clone(), txout.clone(), Some(height_1))
|
||||||
|
.unwrap();
|
||||||
let res0 = st.wallet_mut().put_received_transparent_utxo(&utxo);
|
let res0 = st.wallet_mut().put_received_transparent_utxo(&utxo);
|
||||||
assert_matches!(res0, Ok(_));
|
assert_matches!(res0, Ok(_));
|
||||||
|
|
||||||
|
@ -706,18 +716,18 @@ mod tests {
|
||||||
height_1,
|
height_1,
|
||||||
0
|
0
|
||||||
).as_deref(),
|
).as_deref(),
|
||||||
Ok([ret]) if (ret.outpoint(), ret.txout(), ret.height()) == (utxo.outpoint(), utxo.txout(), height_1)
|
Ok([ret]) if (ret.outpoint(), ret.txout(), ret.mined_height()) == (utxo.outpoint(), utxo.txout(), Some(height_1))
|
||||||
);
|
);
|
||||||
assert_matches!(
|
assert_matches!(
|
||||||
st.wallet().get_unspent_transparent_output(utxo.outpoint()),
|
st.wallet().get_unspent_transparent_output(utxo.outpoint()),
|
||||||
Ok(Some(ret)) if (ret.outpoint(), ret.txout(), ret.height()) == (utxo.outpoint(), utxo.txout(), height_1)
|
Ok(Some(ret)) if (ret.outpoint(), ret.txout(), ret.mined_height()) == (utxo.outpoint(), utxo.txout(), Some(height_1))
|
||||||
);
|
);
|
||||||
|
|
||||||
// Change the mined height of the UTXO and upsert; we should get back
|
// Change the mined height of the UTXO and upsert; we should get back
|
||||||
// the same `UtxoId`.
|
// the same `UtxoId`.
|
||||||
let height_2 = birthday + 34567;
|
let height_2 = birthday + 34567;
|
||||||
st.wallet_mut().update_chain_tip(height_2).unwrap();
|
st.wallet_mut().update_chain_tip(height_2).unwrap();
|
||||||
let utxo2 = WalletTransparentOutput::from_parts(outpoint, txout, height_2).unwrap();
|
let utxo2 = WalletTransparentOutput::from_parts(outpoint, txout, Some(height_2)).unwrap();
|
||||||
let res1 = st.wallet_mut().put_received_transparent_utxo(&utxo2);
|
let res1 = st.wallet_mut().put_received_transparent_utxo(&utxo2);
|
||||||
assert_matches!(res1, Ok(id) if id == res0.unwrap());
|
assert_matches!(res1, Ok(id) if id == res0.unwrap());
|
||||||
|
|
||||||
|
@ -732,7 +742,7 @@ mod tests {
|
||||||
// We can still look up the specific output, and it has the expected height.
|
// We can still look up the specific output, and it has the expected height.
|
||||||
assert_matches!(
|
assert_matches!(
|
||||||
st.wallet().get_unspent_transparent_output(utxo2.outpoint()),
|
st.wallet().get_unspent_transparent_output(utxo2.outpoint()),
|
||||||
Ok(Some(ret)) if (ret.outpoint(), ret.txout(), ret.height()) == (utxo2.outpoint(), utxo2.txout(), height_2)
|
Ok(Some(ret)) if (ret.outpoint(), ret.txout(), ret.mined_height()) == (utxo2.outpoint(), utxo2.txout(), Some(height_2))
|
||||||
);
|
);
|
||||||
|
|
||||||
// If we include `height_2` then the output is returned.
|
// If we include `height_2` then the output is returned.
|
||||||
|
@ -740,7 +750,7 @@ mod tests {
|
||||||
st.wallet()
|
st.wallet()
|
||||||
.get_spendable_transparent_outputs(taddr, height_2, 0)
|
.get_spendable_transparent_outputs(taddr, height_2, 0)
|
||||||
.as_deref(),
|
.as_deref(),
|
||||||
Ok([ret]) if (ret.outpoint(), ret.txout(), ret.height()) == (utxo.outpoint(), utxo.txout(), height_2)
|
Ok([ret]) if (ret.outpoint(), ret.txout(), ret.mined_height()) == (utxo.outpoint(), utxo.txout(), Some(height_2))
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_matches!(
|
assert_matches!(
|
||||||
|
@ -842,7 +852,8 @@ mod tests {
|
||||||
|
|
||||||
// Pretend the output was received in the chain tip.
|
// Pretend the output was received in the chain tip.
|
||||||
let height = st.wallet().chain_height().unwrap().unwrap();
|
let height = st.wallet().chain_height().unwrap().unwrap();
|
||||||
let utxo = WalletTransparentOutput::from_parts(OutPoint::fake(), txout, height).unwrap();
|
let utxo =
|
||||||
|
WalletTransparentOutput::from_parts(OutPoint::fake(), txout, Some(height)).unwrap();
|
||||||
st.wallet_mut()
|
st.wallet_mut()
|
||||||
.put_received_transparent_utxo(&utxo)
|
.put_received_transparent_utxo(&utxo)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
//! Support for legacy transparent addresses and scripts.
|
//! Support for legacy transparent addresses and scripts.
|
||||||
|
|
||||||
use byteorder::{ReadBytesExt, WriteBytesExt};
|
use byteorder::{ReadBytesExt, WriteBytesExt};
|
||||||
|
use zcash_address::TryFromRawAddress;
|
||||||
|
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::io::{self, Read, Write};
|
use std::io::{self, Read, Write};
|
||||||
|
@ -407,6 +408,22 @@ impl TransparentAddress {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl TryFromRawAddress for TransparentAddress {
|
||||||
|
type Error = ();
|
||||||
|
|
||||||
|
fn try_from_raw_transparent_p2pkh(
|
||||||
|
data: [u8; 20],
|
||||||
|
) -> Result<Self, zcash_address::ConversionError<Self::Error>> {
|
||||||
|
Ok(TransparentAddress::PublicKeyHash(data))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn try_from_raw_transparent_p2sh(
|
||||||
|
data: [u8; 20],
|
||||||
|
) -> Result<Self, zcash_address::ConversionError<Self::Error>> {
|
||||||
|
Ok(TransparentAddress::ScriptHash(data))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(any(test, feature = "test-dependencies"))]
|
#[cfg(any(test, feature = "test-dependencies"))]
|
||||||
pub mod testing {
|
pub mod testing {
|
||||||
use proptest::prelude::{any, prop_compose};
|
use proptest::prelude::{any, prop_compose};
|
||||||
|
|
Loading…
Reference in New Issue