Add Orchard recipient support to the transaction builder
Co-authored-by: Kris Nuttycombe <kris@nutty.land>
This commit is contained in:
parent
e568a190f3
commit
2da0856e6f
|
@ -3,7 +3,12 @@ replace-with = "vendored-sources"
|
|||
|
||||
[source."https://github.com/zcash/librustzcash.git"]
|
||||
git = "https://github.com/zcash/librustzcash.git"
|
||||
rev = "5622b060b1f57de7afc3d0b4e425b9b4b22482a0"
|
||||
rev = "3d935a94e75786a67c3ea4992d7c372af203086f"
|
||||
replace-with = "vendored-sources"
|
||||
|
||||
[source."https://github.com/zcash/orchard.git"]
|
||||
git = "https://github.com/zcash/orchard.git"
|
||||
rev = "c4cd541e6c9ca657fa33fdd686d70d81bf3e4e65"
|
||||
replace-with = "vendored-sources"
|
||||
|
||||
[source.vendored-sources]
|
||||
|
|
|
@ -501,7 +501,7 @@ checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d"
|
|||
[[package]]
|
||||
name = "equihash"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/zcash/librustzcash.git?rev=4f4a25252f0fc6b84c44316d810107bc7ea7f32a#4f4a25252f0fc6b84c44316d810107bc7ea7f32a"
|
||||
source = "git+https://github.com/zcash/librustzcash.git?rev=3d935a94e75786a67c3ea4992d7c372af203086f#3d935a94e75786a67c3ea4992d7c372af203086f"
|
||||
dependencies = [
|
||||
"blake2b_simd 1.0.0",
|
||||
"byteorder",
|
||||
|
@ -510,7 +510,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "f4jumble"
|
||||
version = "0.0.0"
|
||||
source = "git+https://github.com/zcash/librustzcash.git?rev=4f4a25252f0fc6b84c44316d810107bc7ea7f32a#4f4a25252f0fc6b84c44316d810107bc7ea7f32a"
|
||||
source = "git+https://github.com/zcash/librustzcash.git?rev=3d935a94e75786a67c3ea4992d7c372af203086f#3d935a94e75786a67c3ea4992d7c372af203086f"
|
||||
dependencies = [
|
||||
"blake2b_simd 1.0.0",
|
||||
]
|
||||
|
@ -1090,8 +1090,7 @@ checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
|
|||
[[package]]
|
||||
name = "orchard"
|
||||
version = "0.1.0-beta.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e31f03b6d0aee6d993cac35388b818e04f076ded0ab284979e4d7cd5a8b3c2be"
|
||||
source = "git+https://github.com/zcash/orchard.git?rev=c4cd541e6c9ca657fa33fdd686d70d81bf3e4e65#c4cd541e6c9ca657fa33fdd686d70d81bf3e4e65"
|
||||
dependencies = [
|
||||
"aes",
|
||||
"arrayvec 0.7.2",
|
||||
|
@ -1917,7 +1916,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "zcash_address"
|
||||
version = "0.0.0"
|
||||
source = "git+https://github.com/zcash/librustzcash.git?rev=4f4a25252f0fc6b84c44316d810107bc7ea7f32a#4f4a25252f0fc6b84c44316d810107bc7ea7f32a"
|
||||
source = "git+https://github.com/zcash/librustzcash.git?rev=3d935a94e75786a67c3ea4992d7c372af203086f#3d935a94e75786a67c3ea4992d7c372af203086f"
|
||||
dependencies = [
|
||||
"bech32",
|
||||
"bs58",
|
||||
|
@ -1928,7 +1927,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "zcash_encoding"
|
||||
version = "0.0.0"
|
||||
source = "git+https://github.com/zcash/librustzcash.git?rev=4f4a25252f0fc6b84c44316d810107bc7ea7f32a#4f4a25252f0fc6b84c44316d810107bc7ea7f32a"
|
||||
source = "git+https://github.com/zcash/librustzcash.git?rev=3d935a94e75786a67c3ea4992d7c372af203086f#3d935a94e75786a67c3ea4992d7c372af203086f"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"nonempty",
|
||||
|
@ -1937,7 +1936,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "zcash_history"
|
||||
version = "0.2.0"
|
||||
source = "git+https://github.com/zcash/librustzcash.git?rev=4f4a25252f0fc6b84c44316d810107bc7ea7f32a#4f4a25252f0fc6b84c44316d810107bc7ea7f32a"
|
||||
source = "git+https://github.com/zcash/librustzcash.git?rev=3d935a94e75786a67c3ea4992d7c372af203086f#3d935a94e75786a67c3ea4992d7c372af203086f"
|
||||
dependencies = [
|
||||
"bigint",
|
||||
"blake2b_simd 1.0.0",
|
||||
|
@ -1947,7 +1946,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "zcash_note_encryption"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/zcash/librustzcash.git?rev=4f4a25252f0fc6b84c44316d810107bc7ea7f32a#4f4a25252f0fc6b84c44316d810107bc7ea7f32a"
|
||||
source = "git+https://github.com/zcash/librustzcash.git?rev=3d935a94e75786a67c3ea4992d7c372af203086f#3d935a94e75786a67c3ea4992d7c372af203086f"
|
||||
dependencies = [
|
||||
"chacha20",
|
||||
"chacha20poly1305",
|
||||
|
@ -1958,7 +1957,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "zcash_primitives"
|
||||
version = "0.5.0"
|
||||
source = "git+https://github.com/zcash/librustzcash.git?rev=4f4a25252f0fc6b84c44316d810107bc7ea7f32a#4f4a25252f0fc6b84c44316d810107bc7ea7f32a"
|
||||
source = "git+https://github.com/zcash/librustzcash.git?rev=3d935a94e75786a67c3ea4992d7c372af203086f#3d935a94e75786a67c3ea4992d7c372af203086f"
|
||||
dependencies = [
|
||||
"aes",
|
||||
"bip0039",
|
||||
|
@ -1994,7 +1993,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "zcash_proofs"
|
||||
version = "0.5.0"
|
||||
source = "git+https://github.com/zcash/librustzcash.git?rev=4f4a25252f0fc6b84c44316d810107bc7ea7f32a#4f4a25252f0fc6b84c44316d810107bc7ea7f32a"
|
||||
source = "git+https://github.com/zcash/librustzcash.git?rev=3d935a94e75786a67c3ea4992d7c372af203086f#3d935a94e75786a67c3ea4992d7c372af203086f"
|
||||
dependencies = [
|
||||
"bellman",
|
||||
"blake2b_simd 1.0.0",
|
||||
|
|
11
Cargo.toml
11
Cargo.toml
|
@ -72,8 +72,9 @@ codegen-units = 1
|
|||
|
||||
[patch.crates-io]
|
||||
hdwallet = { git = "https://github.com/nuttycom/hdwallet", rev = "576683b9f2865f1118c309017ff36e01f84420c9" }
|
||||
zcash_address = { git = "https://github.com/zcash/librustzcash.git", rev = "4f4a25252f0fc6b84c44316d810107bc7ea7f32a" }
|
||||
zcash_history = { git = "https://github.com/zcash/librustzcash.git", rev = "4f4a25252f0fc6b84c44316d810107bc7ea7f32a" }
|
||||
zcash_note_encryption = { git = "https://github.com/zcash/librustzcash.git", rev = "4f4a25252f0fc6b84c44316d810107bc7ea7f32a" }
|
||||
zcash_primitives = { git = "https://github.com/zcash/librustzcash.git", rev = "4f4a25252f0fc6b84c44316d810107bc7ea7f32a" }
|
||||
zcash_proofs = { git = "https://github.com/zcash/librustzcash.git", rev = "4f4a25252f0fc6b84c44316d810107bc7ea7f32a" }
|
||||
orchard = { git = "https://github.com/zcash/orchard.git", rev = "c4cd541e6c9ca657fa33fdd686d70d81bf3e4e65" }
|
||||
zcash_address = { git = "https://github.com/zcash/librustzcash.git", rev = "3d935a94e75786a67c3ea4992d7c372af203086f" }
|
||||
zcash_history = { git = "https://github.com/zcash/librustzcash.git", rev = "3d935a94e75786a67c3ea4992d7c372af203086f" }
|
||||
zcash_note_encryption = { git = "https://github.com/zcash/librustzcash.git", rev = "3d935a94e75786a67c3ea4992d7c372af203086f" }
|
||||
zcash_primitives = { git = "https://github.com/zcash/librustzcash.git", rev = "3d935a94e75786a67c3ea4992d7c372af203086f" }
|
||||
zcash_proofs = { git = "https://github.com/zcash/librustzcash.git", rev = "3d935a94e75786a67c3ea4992d7c372af203086f" }
|
||||
|
|
|
@ -11,6 +11,8 @@
|
|||
#include "transaction_builder.h"
|
||||
#include "utiltest.h"
|
||||
|
||||
#include <optional>
|
||||
|
||||
TEST(RecursiveDynamicUsageTests, TestTransactionTransparent)
|
||||
{
|
||||
auto consensusParams = RegtestActivateSapling();
|
||||
|
@ -20,7 +22,7 @@ TEST(RecursiveDynamicUsageTests, TestTransactionTransparent)
|
|||
auto scriptPubKey = GetScriptForDestination(tsk.GetPubKey().GetID());
|
||||
CTxDestination taddr = tsk.GetPubKey().GetID();
|
||||
|
||||
auto builder = TransactionBuilder(consensusParams, 1, &keystore);
|
||||
auto builder = TransactionBuilder(consensusParams, 1, std::nullopt, &keystore);
|
||||
builder.AddTransparentInput(COutPoint(), scriptPubKey, 50000);
|
||||
builder.AddTransparentOutput(taddr, 40000);
|
||||
|
||||
|
@ -53,7 +55,7 @@ TEST(RecursiveDynamicUsageTests, TestTransactionSaplingToSapling)
|
|||
auto sk = libzcash::SaplingSpendingKey::random();
|
||||
auto testNote = GetTestSaplingNote(sk.default_address(), 50000);
|
||||
|
||||
auto builder = TransactionBuilder(consensusParams, 1);
|
||||
auto builder = TransactionBuilder(consensusParams, 1, std::nullopt);
|
||||
builder.AddSaplingSpend(sk.expanded_spending_key(), testNote.note, testNote.tree.root(), testNote.tree.witness());
|
||||
builder.AddSaplingOutput(sk.full_viewing_key().ovk, sk.default_address(), 5000, {});
|
||||
|
||||
|
@ -74,7 +76,7 @@ TEST(RecursiveDynamicUsageTests, TestTransactionTransparentToSapling)
|
|||
auto scriptPubKey = GetScriptForDestination(tsk.GetPubKey().GetID());
|
||||
auto sk = libzcash::SaplingSpendingKey::random();
|
||||
|
||||
auto builder = TransactionBuilder(consensusParams, 1, &keystore);
|
||||
auto builder = TransactionBuilder(consensusParams, 1, std::nullopt, &keystore);
|
||||
builder.AddTransparentInput(COutPoint(), scriptPubKey, 50000);
|
||||
builder.AddSaplingOutput(sk.full_viewing_key().ovk, sk.default_address(), 40000, {});
|
||||
|
||||
|
@ -96,7 +98,7 @@ TEST(RecursiveDynamicUsageTests, TestTransactionSaplingToTransparent)
|
|||
auto sk = libzcash::SaplingSpendingKey::random();
|
||||
auto testNote = GetTestSaplingNote(sk.default_address(), 50000);
|
||||
|
||||
auto builder = TransactionBuilder(consensusParams, 1, &keystore);
|
||||
auto builder = TransactionBuilder(consensusParams, 1, std::nullopt, &keystore);
|
||||
builder.AddSaplingSpend(sk.expanded_spending_key(), testNote.note, testNote.tree.root(), testNote.tree.witness());
|
||||
builder.AddTransparentOutput(taddr, 40000);
|
||||
|
||||
|
|
|
@ -119,7 +119,7 @@ TEST(MempoolLimitTests, WeightedTxInfoFromTx)
|
|||
|
||||
// Default fee
|
||||
{
|
||||
auto builder = TransactionBuilder(consensusParams, 1);
|
||||
auto builder = TransactionBuilder(consensusParams, 1, std::nullopt);
|
||||
builder.AddSaplingSpend(sk.expanded_spending_key(), testNote.note, testNote.tree.root(), testNote.tree.witness());
|
||||
builder.AddSaplingOutput(sk.full_viewing_key().ovk, sk.default_address(), 25000, {});
|
||||
|
||||
|
@ -130,7 +130,7 @@ TEST(MempoolLimitTests, WeightedTxInfoFromTx)
|
|||
|
||||
// Lower than standard fee
|
||||
{
|
||||
auto builder = TransactionBuilder(consensusParams, 1);
|
||||
auto builder = TransactionBuilder(consensusParams, 1, std::nullopt);
|
||||
builder.AddSaplingSpend(sk.expanded_spending_key(), testNote.note, testNote.tree.root(), testNote.tree.witness());
|
||||
builder.AddSaplingOutput(sk.full_viewing_key().ovk, sk.default_address(), 25000, {});
|
||||
static_assert(DEFAULT_FEE == 1000);
|
||||
|
@ -143,7 +143,7 @@ TEST(MempoolLimitTests, WeightedTxInfoFromTx)
|
|||
|
||||
// Larger Tx
|
||||
{
|
||||
auto builder = TransactionBuilder(consensusParams, 1);
|
||||
auto builder = TransactionBuilder(consensusParams, 1, std::nullopt);
|
||||
builder.AddSaplingSpend(sk.expanded_spending_key(), testNote.note, testNote.tree.root(), testNote.tree.witness());
|
||||
builder.AddSaplingOutput(sk.full_viewing_key().ovk, sk.default_address(), 5000, {});
|
||||
builder.AddSaplingOutput(sk.full_viewing_key().ovk, sk.default_address(), 5000, {});
|
||||
|
|
|
@ -9,6 +9,9 @@
|
|||
#include "transaction_builder.h"
|
||||
#include "utiltest.h"
|
||||
#include "zcash/Address.hpp"
|
||||
#include "zcash/address/mnemonic.h"
|
||||
|
||||
#include <optional>
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
@ -34,6 +37,10 @@ public:
|
|||
return false;
|
||||
}
|
||||
|
||||
bool GetOrchardAnchorAt(const uint256 &rt, OrchardMerkleFrontier &tree) const {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool GetNullifier(const uint256 &nf, ShieldedType type) const {
|
||||
return false;
|
||||
}
|
||||
|
@ -92,7 +99,7 @@ TEST(TransactionBuilder, TransparentToSapling)
|
|||
|
||||
// Create a shielding transaction from transparent to Sapling
|
||||
// 0.0005 t-ZEC in, 0.0004 z-ZEC out, default fee
|
||||
auto builder = TransactionBuilder(consensusParams, 1, &keystore);
|
||||
auto builder = TransactionBuilder(consensusParams, 1, std::nullopt, &keystore);
|
||||
builder.AddTransparentInput(COutPoint(uint256S("1234"), 0), scriptPubKey, 50000);
|
||||
builder.AddSaplingOutput(fvk_from.ovk, pk, 40000, {});
|
||||
auto tx = builder.Build().GetTxOrThrow();
|
||||
|
@ -124,7 +131,7 @@ TEST(TransactionBuilder, SaplingToSapling) {
|
|||
|
||||
// Create a Sapling-only transaction
|
||||
// 0.0004 z-ZEC in, 0.00025 z-ZEC out, default fee, 0.00005 z-ZEC change
|
||||
auto builder = TransactionBuilder(consensusParams, 2);
|
||||
auto builder = TransactionBuilder(consensusParams, 2, std::nullopt);
|
||||
builder.AddSaplingSpend(expsk, testNote.note, testNote.tree.root(), testNote.tree.witness());
|
||||
|
||||
// Check that trying to add a different anchor fails
|
||||
|
@ -165,7 +172,7 @@ TEST(TransactionBuilder, SaplingToSprout) {
|
|||
// - 0.0004 Sapling-ZEC in - 0.00025 Sprout-ZEC out
|
||||
// - 0.00005 Sapling-ZEC change
|
||||
// - default t-ZEC fee
|
||||
auto builder = TransactionBuilder(consensusParams, 2, nullptr);
|
||||
auto builder = TransactionBuilder(consensusParams, 2, std::nullopt, nullptr);
|
||||
builder.AddSaplingSpend(expsk, testNote.note, testNote.tree.root(), testNote.tree.witness());
|
||||
builder.AddSproutOutput(sproutAddr, 25000);
|
||||
auto tx = builder.Build().GetTxOrThrow();
|
||||
|
@ -217,7 +224,7 @@ TEST(TransactionBuilder, SproutToSproutAndSapling) {
|
|||
// - 0.00005 Sprout-ZEC change
|
||||
// - 0.00005 Sapling-ZEC out
|
||||
// - 0.00005 t-ZEC fee
|
||||
auto builder = TransactionBuilder(consensusParams, 2, nullptr, &view);
|
||||
auto builder = TransactionBuilder(consensusParams, 2, std::nullopt, nullptr, &view);
|
||||
builder.SetFee(5000);
|
||||
builder.AddSproutInput(sproutSk, sproutNote, sproutWitness);
|
||||
builder.AddSproutOutput(sproutAddr, 6000);
|
||||
|
@ -248,12 +255,60 @@ TEST(TransactionBuilder, SproutToSproutAndSapling) {
|
|||
RegtestDeactivateSapling();
|
||||
}
|
||||
|
||||
TEST(TransactionBuilder, TransparentToOrchard)
|
||||
{
|
||||
auto consensusParams = RegtestActivateNU5();
|
||||
|
||||
CBasicKeyStore keystore;
|
||||
CKey tsk = AddTestCKeyToKeyStore(keystore);
|
||||
auto scriptPubKey = GetScriptForDestination(tsk.GetPubKey().GetID());
|
||||
|
||||
auto coinType = Params().BIP44CoinType();
|
||||
auto seed = MnemonicSeed::Random(coinType);
|
||||
auto sk = libzcash::OrchardSpendingKey::ForAccount(seed, coinType, 0);
|
||||
auto fvk = sk.ToFullViewingKey();
|
||||
auto ivk = fvk.ToIncomingViewingKey();
|
||||
libzcash::diversifier_index_t j(0);
|
||||
auto recipient = ivk.Address(j);
|
||||
|
||||
TransactionBuilderCoinsViewDB fakeDB;
|
||||
auto orchardAnchor = fakeDB.GetBestAnchor(ShieldedType::ORCHARD);
|
||||
|
||||
// Create a shielding transaction from transparent to Orchard
|
||||
// 0.0005 t-ZEC in, 0.0004 z-ZEC out, default fee
|
||||
auto builder = TransactionBuilder(consensusParams, 1, orchardAnchor, &keystore);
|
||||
builder.AddTransparentInput(COutPoint(uint256S("1234"), 0), scriptPubKey, 50000);
|
||||
builder.AddOrchardOutput(std::nullopt, recipient, 40000);
|
||||
auto maybeTx = builder.Build();
|
||||
EXPECT_TRUE(maybeTx.IsTx());
|
||||
if (maybeTx.IsError()) {
|
||||
std::cerr << "Failed to build transaction: " << maybeTx.GetError() << std::endl;
|
||||
GTEST_FAIL();
|
||||
}
|
||||
auto tx = maybeTx.GetTxOrThrow();
|
||||
|
||||
EXPECT_EQ(tx.vin.size(), 1);
|
||||
EXPECT_EQ(tx.vout.size(), 0);
|
||||
EXPECT_EQ(tx.vJoinSplit.size(), 0);
|
||||
EXPECT_EQ(tx.vShieldedSpend.size(), 0);
|
||||
EXPECT_EQ(tx.vShieldedOutput.size(), 0);
|
||||
EXPECT_TRUE(tx.GetOrchardBundle().IsPresent());
|
||||
EXPECT_EQ(tx.GetOrchardBundle().GetValueBalance(), -40000);
|
||||
|
||||
CValidationState state;
|
||||
EXPECT_TRUE(ContextualCheckTransaction(tx, state, Params(), 2, true));
|
||||
EXPECT_EQ(state.GetRejectReason(), "");
|
||||
|
||||
// Revert to default
|
||||
RegtestDeactivateNU5();
|
||||
}
|
||||
|
||||
TEST(TransactionBuilder, ThrowsOnTransparentInputWithoutKeyStore)
|
||||
{
|
||||
SelectParams(CBaseChainParams::REGTEST);
|
||||
const Consensus::Params& consensusParams = Params().GetConsensus();
|
||||
|
||||
auto builder = TransactionBuilder(consensusParams, 1);
|
||||
auto builder = TransactionBuilder(consensusParams, 1, std::nullopt);
|
||||
ASSERT_THROW(builder.AddTransparentInput(COutPoint(), CScript(), 1), std::runtime_error);
|
||||
}
|
||||
|
||||
|
@ -264,7 +319,7 @@ TEST(TransactionBuilder, RejectsInvalidTransparentOutput)
|
|||
|
||||
// Default CTxDestination type is an invalid address
|
||||
CTxDestination taddr;
|
||||
auto builder = TransactionBuilder(consensusParams, 1);
|
||||
auto builder = TransactionBuilder(consensusParams, 1, std::nullopt);
|
||||
ASSERT_THROW(builder.AddTransparentOutput(taddr, 50), UniValue);
|
||||
}
|
||||
|
||||
|
@ -289,13 +344,13 @@ TEST(TransactionBuilder, FailsWithNegativeChange)
|
|||
|
||||
// Fail if there is only a Sapling output
|
||||
// 0.0005 z-ZEC out, default fee
|
||||
auto builder = TransactionBuilder(consensusParams, 1);
|
||||
auto builder = TransactionBuilder(consensusParams, 1, std::nullopt);
|
||||
builder.AddSaplingOutput(fvk.ovk, pa, 50000, {});
|
||||
EXPECT_EQ("Change cannot be negative", builder.Build().GetError());
|
||||
|
||||
// Fail if there is only a transparent output
|
||||
// 0.0005 t-ZEC out, default fee
|
||||
builder = TransactionBuilder(consensusParams, 1, &keystore);
|
||||
builder = TransactionBuilder(consensusParams, 1, std::nullopt, &keystore);
|
||||
builder.AddTransparentOutput(taddr, 50000);
|
||||
EXPECT_EQ("Change cannot be negative", builder.Build().GetError());
|
||||
|
||||
|
@ -336,14 +391,14 @@ TEST(TransactionBuilder, ChangeOutput)
|
|||
|
||||
// No change address and no Sapling spends
|
||||
{
|
||||
auto builder = TransactionBuilder(consensusParams, 1, &keystore);
|
||||
auto builder = TransactionBuilder(consensusParams, 1, std::nullopt, &keystore);
|
||||
builder.AddTransparentInput(COutPoint(), scriptPubKey, 25000);
|
||||
EXPECT_EQ("Could not determine change address", builder.Build().GetError());
|
||||
}
|
||||
|
||||
// Change to the same address as the first Sapling spend
|
||||
{
|
||||
auto builder = TransactionBuilder(consensusParams, 1, &keystore);
|
||||
auto builder = TransactionBuilder(consensusParams, 1, std::nullopt, &keystore);
|
||||
builder.AddTransparentInput(COutPoint(), scriptPubKey, 25000);
|
||||
builder.AddSaplingSpend(expsk, testNote.note, testNote.tree.root(), testNote.tree.witness());
|
||||
auto tx = builder.Build().GetTxOrThrow();
|
||||
|
@ -358,7 +413,7 @@ TEST(TransactionBuilder, ChangeOutput)
|
|||
|
||||
// Change to a Sapling address
|
||||
{
|
||||
auto builder = TransactionBuilder(consensusParams, 1, &keystore);
|
||||
auto builder = TransactionBuilder(consensusParams, 1, std::nullopt, &keystore);
|
||||
builder.AddTransparentInput(COutPoint(), scriptPubKey, 25000);
|
||||
builder.SendChangeTo(zChangeAddr, fvkOut.ovk);
|
||||
auto tx = builder.Build().GetTxOrThrow();
|
||||
|
@ -373,7 +428,7 @@ TEST(TransactionBuilder, ChangeOutput)
|
|||
|
||||
// Change to a transparent address
|
||||
{
|
||||
auto builder = TransactionBuilder(consensusParams, 1, &keystore);
|
||||
auto builder = TransactionBuilder(consensusParams, 1, std::nullopt, &keystore);
|
||||
builder.AddTransparentInput(COutPoint(), scriptPubKey, 25000);
|
||||
builder.SendChangeTo(tkeyid, {});
|
||||
auto tx = builder.Build().GetTxOrThrow();
|
||||
|
@ -405,7 +460,7 @@ TEST(TransactionBuilder, SetFee)
|
|||
|
||||
// Default fee
|
||||
{
|
||||
auto builder = TransactionBuilder(consensusParams, 1);
|
||||
auto builder = TransactionBuilder(consensusParams, 1, std::nullopt);
|
||||
builder.AddSaplingSpend(expsk, testNote.note, testNote.tree.root(), testNote.tree.witness());
|
||||
builder.AddSaplingOutput(fvk.ovk, pa, 25000, {});
|
||||
auto tx = builder.Build().GetTxOrThrow();
|
||||
|
@ -420,7 +475,7 @@ TEST(TransactionBuilder, SetFee)
|
|||
|
||||
// Configured fee
|
||||
{
|
||||
auto builder = TransactionBuilder(consensusParams, 1);
|
||||
auto builder = TransactionBuilder(consensusParams, 1, std::nullopt);
|
||||
builder.AddSaplingSpend(expsk, testNote.note, testNote.tree.root(), testNote.tree.witness());
|
||||
builder.AddSaplingOutput(fvk.ovk, pa, 25000, {});
|
||||
builder.SetFee(20000);
|
||||
|
@ -449,7 +504,7 @@ TEST(TransactionBuilder, CheckSaplingTxVersion)
|
|||
auto pk = sk.default_address();
|
||||
|
||||
// Cannot add Sapling outputs to a non-Sapling transaction
|
||||
auto builder = TransactionBuilder(consensusParams, 1);
|
||||
auto builder = TransactionBuilder(consensusParams, 1, std::nullopt);
|
||||
try {
|
||||
builder.AddSaplingOutput(uint256(), pk, 12345, {});
|
||||
} catch (std::runtime_error const & err) {
|
||||
|
|
|
@ -172,7 +172,7 @@ TEST(Validation, ContextualCheckInputsDetectsOldBranchId) {
|
|||
|
||||
// Create a transparent transaction that spends the coin, targeting
|
||||
// a height during the Overwinter epoch.
|
||||
auto builder = TransactionBuilder(consensusParams, 15, &keystore);
|
||||
auto builder = TransactionBuilder(consensusParams, 15, std::nullopt, &keystore);
|
||||
builder.AddTransparentInput(utxo, scriptPubKey, coinValue);
|
||||
builder.AddTransparentOutput(destination, 40000);
|
||||
auto tx = builder.Build().GetTxOrThrow();
|
||||
|
|
|
@ -11,9 +11,10 @@
|
|||
#include <rust/orchard.h>
|
||||
|
||||
class OrchardMerkleFrontier;
|
||||
namespace orchard { class UnauthorizedBundle; }
|
||||
|
||||
/**
|
||||
* The Orchard component of a transaction.
|
||||
* The Orchard component of an authorized transaction.
|
||||
*/
|
||||
class OrchardBundle
|
||||
{
|
||||
|
@ -22,7 +23,10 @@ private:
|
|||
/// Memory is allocated by Rust.
|
||||
std::unique_ptr<OrchardBundlePtr, decltype(&orchard_bundle_free)> inner;
|
||||
|
||||
OrchardBundle(OrchardBundlePtr* bundle) : inner(bundle, orchard_bundle_free) {}
|
||||
|
||||
friend class OrchardMerkleFrontier;
|
||||
friend class orchard::UnauthorizedBundle;
|
||||
public:
|
||||
OrchardBundle() : inner(nullptr, orchard_bundle_free) {}
|
||||
|
||||
|
|
|
@ -0,0 +1,80 @@
|
|||
// Copyright (c) 2022 The Zcash developers
|
||||
// Distributed under the MIT software license, see the accompanying
|
||||
// file COPYING or https://www.opensource.org/licenses/mit-license.php .
|
||||
|
||||
#ifndef ZCASH_RUST_INCLUDE_RUST_BUILDER_H
|
||||
#define ZCASH_RUST_INCLUDE_RUST_BUILDER_H
|
||||
|
||||
#include "rust/orchard.h"
|
||||
#include "rust/orchard/keys.h"
|
||||
#include "rust/transaction.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/// Pointer to Rust-allocated Orchard bundle builder.
|
||||
struct OrchardBuilderPtr;
|
||||
typedef struct OrchardBuilderPtr OrchardBuilderPtr;
|
||||
|
||||
/// Pointer to Rust-allocated Orchard bundle without proofs
|
||||
/// or authorizing data.
|
||||
struct OrchardUnauthorizedBundlePtr;
|
||||
typedef struct OrchardUnauthorizedBundlePtr OrchardUnauthorizedBundlePtr;
|
||||
|
||||
/// Construct a new Orchard transaction builder.
|
||||
OrchardBuilderPtr* orchard_builder_new(
|
||||
bool spends_enabled,
|
||||
bool outputs_enabled,
|
||||
const unsigned char* anchor);
|
||||
|
||||
/// Frees an Orchard builder returned from `orchard_builder_new`.
|
||||
void orchard_builder_free(OrchardBuilderPtr* ptr);
|
||||
|
||||
/// Adds an address which will receive funds in this bundle.
|
||||
///
|
||||
/// `ovk` is a pointer to the outgoing viewing key to make this recipient recoverable by,
|
||||
/// or `null` to make the recipient unrecoverable by the sender.
|
||||
bool orchard_builder_add_recipient(
|
||||
OrchardBuilderPtr* ptr,
|
||||
const unsigned char* ovk,
|
||||
const OrchardRawAddressPtr* recipient,
|
||||
uint64_t value,
|
||||
const unsigned char* memo);
|
||||
|
||||
/// Builds a bundle containing the given spent notes and recipients.
|
||||
///
|
||||
/// Returns `null` if an error occurs.
|
||||
///
|
||||
/// `builder` is always freed by this method.
|
||||
OrchardUnauthorizedBundlePtr* orchard_builder_build(OrchardBuilderPtr* builder);
|
||||
|
||||
/// Frees an Orchard bundle returned from `orchard_bundle_build`.
|
||||
void orchard_unauthorized_bundle_free(OrchardUnauthorizedBundlePtr* bundle);
|
||||
|
||||
/// Adds proofs and signatures to the bundle.
|
||||
///
|
||||
/// Returns `null` if an error occurs.
|
||||
///
|
||||
/// `bundle` is always freed by this method.
|
||||
OrchardBundlePtr* orchard_unauthorized_bundle_prove_and_sign(
|
||||
OrchardUnauthorizedBundlePtr* bundle,
|
||||
const unsigned char* sighash);
|
||||
|
||||
/// Calculates a ZIP 244 shielded signature digest for the given under-construction
|
||||
/// transaction.
|
||||
///
|
||||
/// Returns `false` if any of the parameters are invalid; in this case, `sighash_ret`
|
||||
/// will be unaltered.
|
||||
///
|
||||
/// `preTx` is always freed by this method.
|
||||
bool zcash_builder_zip244_shielded_signature_digest(
|
||||
PrecomputedTxParts* preTx,
|
||||
const OrchardUnauthorizedBundlePtr* bundle,
|
||||
unsigned char* sighash_ret);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // ZCASH_RUST_INCLUDE_RUST_BUILDER_H
|
|
@ -13,6 +13,7 @@
|
|||
extern "C" {
|
||||
#endif
|
||||
|
||||
/// Typesafe pointer to a Rust-allocated orchard::bundle::Bundle value
|
||||
struct OrchardBundlePtr;
|
||||
typedef struct OrchardBundlePtr OrchardBundlePtr;
|
||||
|
||||
|
|
|
@ -0,0 +1,169 @@
|
|||
use std::convert::TryInto;
|
||||
use std::ptr;
|
||||
|
||||
use orchard::{
|
||||
builder::{Builder, InProgress, Unauthorized, Unproven},
|
||||
bundle::{Authorized, Flags},
|
||||
keys::OutgoingViewingKey,
|
||||
value::NoteValue,
|
||||
Bundle,
|
||||
};
|
||||
use rand_core::OsRng;
|
||||
use tracing::error;
|
||||
use zcash_primitives::transaction::{
|
||||
components::{sapling, transparent, Amount},
|
||||
sighash::{SignableInput, SIGHASH_ALL},
|
||||
sighash_v5::v5_signature_hash,
|
||||
Authorization, TransactionData, TxVersion,
|
||||
};
|
||||
|
||||
use crate::{transaction_ffi::PrecomputedTxParts, ORCHARD_PK};
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn orchard_builder_new(
|
||||
spends_enabled: bool,
|
||||
outputs_enabled: bool,
|
||||
anchor: *const [u8; 32],
|
||||
) -> *mut Builder {
|
||||
let anchor = unsafe { anchor.as_ref() }.expect("Anchor pointer may not be null.");
|
||||
Box::into_raw(Box::new(Builder::new(
|
||||
Flags::from_parts(spends_enabled, outputs_enabled),
|
||||
orchard::Anchor::from_bytes(*anchor).unwrap(),
|
||||
)))
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn orchard_builder_add_recipient(
|
||||
builder: *mut Builder,
|
||||
ovk: *const [u8; 32],
|
||||
recipient: *const orchard::Address,
|
||||
value: u64,
|
||||
memo: *const [u8; 512],
|
||||
) -> bool {
|
||||
let builder = unsafe { builder.as_mut() }.expect("Builder may not be null.");
|
||||
let ovk = unsafe { ovk.as_ref() }
|
||||
.copied()
|
||||
.map(OutgoingViewingKey::from);
|
||||
let recipient = unsafe { recipient.as_ref() }.expect("Recipient may not be null.");
|
||||
let value = NoteValue::from_raw(value);
|
||||
let memo = unsafe { memo.as_ref() }.expect("Memo may not be null.");
|
||||
|
||||
match builder.add_recipient(ovk, *recipient, value, Some(*memo)) {
|
||||
Ok(()) => true,
|
||||
Err(e) => {
|
||||
error!("Failed to add Orchard recipient: {}", e);
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn orchard_builder_free(builder: *mut Builder) {
|
||||
if !builder.is_null() {
|
||||
drop(unsafe { Box::from_raw(builder) });
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn orchard_builder_build(
|
||||
builder: *mut Builder,
|
||||
) -> *mut Bundle<InProgress<Unproven, Unauthorized>, Amount> {
|
||||
if builder.is_null() {
|
||||
error!("Called with null builder");
|
||||
return ptr::null_mut();
|
||||
}
|
||||
let builder = unsafe { Box::from_raw(builder) };
|
||||
|
||||
match builder.build(OsRng) {
|
||||
Ok(bundle) => Box::into_raw(Box::new(bundle)),
|
||||
Err(e) => {
|
||||
error!("Failed to build Orchard bundle: {:?}", e);
|
||||
ptr::null_mut()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn orchard_unauthorized_bundle_free(
|
||||
bundle: *mut Bundle<InProgress<Unproven, Unauthorized>, Amount>,
|
||||
) {
|
||||
if !bundle.is_null() {
|
||||
drop(unsafe { Box::from_raw(bundle) });
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn orchard_unauthorized_bundle_prove_and_sign(
|
||||
bundle: *mut Bundle<InProgress<Unproven, Unauthorized>, Amount>,
|
||||
sighash: *const [u8; 32],
|
||||
) -> *mut Bundle<Authorized, Amount> {
|
||||
let bundle = unsafe { Box::from_raw(bundle) };
|
||||
let sighash = unsafe { sighash.as_ref() }.expect("sighash pointer may not be null.");
|
||||
let pk = unsafe { ORCHARD_PK.as_ref() }.unwrap();
|
||||
|
||||
let res = bundle
|
||||
.create_proof(pk)
|
||||
.and_then(|b| b.apply_signatures(OsRng, *sighash, &[]));
|
||||
|
||||
match res {
|
||||
Ok(signed) => Box::into_raw(Box::new(signed)),
|
||||
Err(e) => {
|
||||
error!(
|
||||
"An error occurred while authorizing the orchard bundle: {:?}",
|
||||
e
|
||||
);
|
||||
std::ptr::null_mut()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Calculates a ZIP 244 shielded signature digest for the given under-construction
|
||||
/// transaction.
|
||||
///
|
||||
/// Returns `false` if any of the parameters are invalid; in this case, `sighash_ret`
|
||||
/// will be unaltered.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn zcash_builder_zip244_shielded_signature_digest(
|
||||
precomputed_tx: *mut PrecomputedTxParts,
|
||||
bundle: *const Bundle<InProgress<Unproven, Unauthorized>, Amount>,
|
||||
sighash_ret: *mut [u8; 32],
|
||||
) -> bool {
|
||||
let precomputed_tx = if !precomputed_tx.is_null() {
|
||||
unsafe { Box::from_raw(precomputed_tx) }
|
||||
} else {
|
||||
error!("Invalid precomputed transaction");
|
||||
return false;
|
||||
};
|
||||
if matches!(
|
||||
precomputed_tx.tx.version(),
|
||||
TxVersion::Sprout(_) | TxVersion::Overwinter | TxVersion::Sapling,
|
||||
) {
|
||||
error!("Cannot calculate ZIP 244 digest for pre-v5 transaction");
|
||||
return false;
|
||||
}
|
||||
let bundle = unsafe { bundle.as_ref().unwrap() };
|
||||
|
||||
struct Signable {}
|
||||
impl Authorization for Signable {
|
||||
type TransparentAuth = transparent::Authorized;
|
||||
type SaplingAuth = sapling::Authorized;
|
||||
type OrchardAuth = InProgress<Unproven, Unauthorized>;
|
||||
}
|
||||
|
||||
let txdata: TransactionData<Signable> =
|
||||
precomputed_tx
|
||||
.tx
|
||||
.into_data()
|
||||
.map_bundles(|b| b, |b| b, |_| Some(bundle.clone()));
|
||||
|
||||
let sighash = v5_signature_hash(
|
||||
&txdata,
|
||||
SIGHASH_ALL,
|
||||
&SignableInput::Shielded,
|
||||
&precomputed_tx.txid_parts,
|
||||
);
|
||||
|
||||
// `v5_signature_hash` output is always 32 bytes.
|
||||
*unsafe { &mut *sighash_ret } = sighash.as_ref().try_into().unwrap();
|
||||
true
|
||||
}
|
|
@ -71,6 +71,7 @@ mod streams_ffi;
|
|||
mod tracing_ffi;
|
||||
|
||||
mod address_ffi;
|
||||
mod builder_ffi;
|
||||
mod history_ffi;
|
||||
mod incremental_merkle_tree_ffi;
|
||||
mod orchard_ffi;
|
||||
|
|
|
@ -56,8 +56,8 @@ pub extern "C" fn zcash_transaction_digests(
|
|||
}
|
||||
|
||||
pub struct PrecomputedTxParts {
|
||||
tx: Transaction,
|
||||
txid_parts: TxDigests<Hash>,
|
||||
pub(crate) tx: Transaction,
|
||||
pub(crate) txid_parts: TxDigests<Hash>,
|
||||
}
|
||||
|
||||
/// Precomputes the `TxDigest` struct for the given transaction.
|
||||
|
|
|
@ -15,6 +15,81 @@
|
|||
#include <librustzcash.h>
|
||||
#include <rust/ed25519.h>
|
||||
|
||||
uint256 ProduceZip244SignatureHash(
|
||||
const CTransaction& tx,
|
||||
const orchard::UnauthorizedBundle& orchardBundle)
|
||||
{
|
||||
uint256 dataToBeSigned;
|
||||
PrecomputedTransactionData local(tx);
|
||||
if (!zcash_builder_zip244_shielded_signature_digest(
|
||||
local.preTx.release(),
|
||||
orchardBundle.inner.get(),
|
||||
dataToBeSigned.begin()))
|
||||
{
|
||||
throw std::logic_error("ZIP 225 signature hash failed");
|
||||
}
|
||||
return dataToBeSigned;
|
||||
}
|
||||
|
||||
namespace orchard {
|
||||
|
||||
Builder::Builder(
|
||||
bool spendsEnabled,
|
||||
bool outputsEnabled,
|
||||
uint256 anchor) : inner(nullptr, orchard_builder_free)
|
||||
{
|
||||
inner.reset(orchard_builder_new(spendsEnabled, outputsEnabled, anchor.begin()));
|
||||
}
|
||||
|
||||
void Builder::AddOutput(
|
||||
const std::optional<uint256>& ovk,
|
||||
const libzcash::OrchardRawAddress& to,
|
||||
CAmount value,
|
||||
std::array<unsigned char, ZC_MEMO_SIZE> memo)
|
||||
{
|
||||
if (!inner) {
|
||||
throw std::logic_error("orchard::Builder has already been used");
|
||||
}
|
||||
|
||||
orchard_builder_add_recipient(
|
||||
inner.get(),
|
||||
ovk.has_value() ? ovk->begin() : nullptr,
|
||||
to.inner.get(),
|
||||
value,
|
||||
memo.data());
|
||||
}
|
||||
|
||||
std::optional<UnauthorizedBundle> Builder::Build() {
|
||||
if (!inner) {
|
||||
throw std::logic_error("orchard::Builder has already been used");
|
||||
}
|
||||
|
||||
auto bundle = orchard_builder_build(inner.release());
|
||||
if (bundle == nullptr) {
|
||||
return std::nullopt;
|
||||
} else {
|
||||
return UnauthorizedBundle(bundle);
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<OrchardBundle> UnauthorizedBundle::ProveAndSign(
|
||||
uint256 sighash)
|
||||
{
|
||||
if (!inner) {
|
||||
throw std::logic_error("orchard::UnauthorizedBundle has already been used");
|
||||
}
|
||||
|
||||
auto authorizedBundle = orchard_unauthorized_bundle_prove_and_sign(
|
||||
inner.release(), sighash.begin());
|
||||
if (authorizedBundle == nullptr) {
|
||||
return std::nullopt;
|
||||
} else {
|
||||
return OrchardBundle(authorizedBundle);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace orchard
|
||||
|
||||
SpendDescriptionInfo::SpendDescriptionInfo(
|
||||
libzcash::SaplingExpandedSpendingKey expsk,
|
||||
libzcash::SaplingNote note,
|
||||
|
@ -150,16 +225,19 @@ std::string TransactionBuilderResult::GetError() {
|
|||
TransactionBuilder::TransactionBuilder(
|
||||
const Consensus::Params& consensusParams,
|
||||
int nHeight,
|
||||
std::optional<uint256> orchardAnchor,
|
||||
CKeyStore* keystore,
|
||||
CCoinsViewCache* coinsView,
|
||||
CCriticalSection* cs_coinsView) :
|
||||
usingSprout(std::nullopt),
|
||||
consensusParams(consensusParams),
|
||||
nHeight(nHeight),
|
||||
keystore(keystore),
|
||||
coinsView(coinsView),
|
||||
cs_coinsView(cs_coinsView)
|
||||
{
|
||||
if (orchardAnchor.has_value()) {
|
||||
orchardBuilder = orchard::Builder(true, true, orchardAnchor.value());
|
||||
}
|
||||
mtx = CreateNewContextualCMutableTransaction(consensusParams, nHeight);
|
||||
}
|
||||
|
||||
|
@ -174,7 +252,6 @@ private:
|
|||
std::string msg;
|
||||
};
|
||||
|
||||
|
||||
void TransactionBuilder::SetExpiryHeight(uint32_t nExpiryHeight)
|
||||
{
|
||||
if (nExpiryHeight < nHeight || nExpiryHeight <= 0 || nExpiryHeight >= TX_EXPIRY_HEIGHT_THRESHOLD) {
|
||||
|
@ -183,6 +260,20 @@ void TransactionBuilder::SetExpiryHeight(uint32_t nExpiryHeight)
|
|||
mtx.nExpiryHeight = nExpiryHeight;
|
||||
}
|
||||
|
||||
void TransactionBuilder::AddOrchardOutput(
|
||||
const std::optional<uint256>& ovk,
|
||||
const libzcash::OrchardRawAddress& to,
|
||||
CAmount value,
|
||||
std::array<unsigned char, ZC_MEMO_SIZE> memo)
|
||||
{
|
||||
if (!orchardBuilder.has_value()) {
|
||||
throw std::runtime_error("TransactionBuilder cannot add Orchard output without Orchard anchor");
|
||||
}
|
||||
|
||||
orchardBuilder.value().AddOutput(ovk, to, value, memo);
|
||||
valueBalanceOrchard -= value;
|
||||
}
|
||||
|
||||
void TransactionBuilder::AddSaplingSpend(
|
||||
libzcash::SaplingExpandedSpendingKey expsk,
|
||||
libzcash::SaplingNote note,
|
||||
|
@ -314,7 +405,7 @@ TransactionBuilderResult TransactionBuilder::Build()
|
|||
//
|
||||
|
||||
// Valid change
|
||||
CAmount change = mtx.valueBalanceSapling - fee;
|
||||
CAmount change = mtx.valueBalanceSapling + valueBalanceOrchard - fee;
|
||||
for (auto jsInput : jsInputs) {
|
||||
change += jsInput.note.value();
|
||||
}
|
||||
|
@ -360,6 +451,20 @@ TransactionBuilderResult TransactionBuilder::Build()
|
|||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Orchard
|
||||
//
|
||||
|
||||
std::optional<orchard::UnauthorizedBundle> orchardBundle;
|
||||
if (orchardBuilder.has_value()) {
|
||||
auto bundle = orchardBuilder->Build();
|
||||
if (bundle.has_value()) {
|
||||
orchardBundle = std::move(bundle);
|
||||
} else {
|
||||
return TransactionBuilderResult("Failed to build Orchard bundle");
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Sapling spends and outputs
|
||||
//
|
||||
|
@ -449,14 +554,28 @@ TransactionBuilderResult TransactionBuilder::Build()
|
|||
|
||||
// Empty output script.
|
||||
uint256 dataToBeSigned;
|
||||
CScript scriptCode;
|
||||
try {
|
||||
dataToBeSigned = SignatureHash(scriptCode, mtx, NOT_AN_INPUT, SIGHASH_ALL, 0, consensusBranchId);
|
||||
if (orchardBundle.has_value()) {
|
||||
// Orchard is only usable with v5+ transactions.
|
||||
dataToBeSigned = ProduceZip244SignatureHash(mtx, orchardBundle.value());
|
||||
} else {
|
||||
CScript scriptCode;
|
||||
dataToBeSigned = SignatureHash(scriptCode, mtx, NOT_AN_INPUT, SIGHASH_ALL, 0, consensusBranchId);
|
||||
}
|
||||
} catch (std::logic_error ex) {
|
||||
librustzcash_sapling_proving_ctx_free(ctx);
|
||||
return TransactionBuilderResult("Could not construct signature hash: " + std::string(ex.what()));
|
||||
}
|
||||
|
||||
if (orchardBundle.has_value()) {
|
||||
auto authorizedBundle = orchardBundle.value().ProveAndSign(dataToBeSigned);
|
||||
if (authorizedBundle.has_value()) {
|
||||
mtx.orchardBundle = authorizedBundle.value();
|
||||
} else {
|
||||
return TransactionBuilderResult("Failed to create Orchard proof or signatures");
|
||||
}
|
||||
}
|
||||
|
||||
// Create Sapling spendAuth and binding signatures
|
||||
for (size_t i = 0; i < spends.size(); i++) {
|
||||
librustzcash_sapling_spend_sig(
|
||||
|
@ -513,15 +632,11 @@ TransactionBuilderResult TransactionBuilder::Build()
|
|||
|
||||
void TransactionBuilder::CheckOrSetUsingSprout()
|
||||
{
|
||||
if (usingSprout.has_value()) {
|
||||
if (!usingSprout.value()) {
|
||||
throw JSONRPCError(RPC_WALLET_ERROR, "Can't use Sprout with a v5 transaction.");
|
||||
}
|
||||
if (orchardBuilder.has_value()) {
|
||||
throw JSONRPCError(RPC_WALLET_ERROR, "Can't use Sprout with a v5 transaction.");
|
||||
} else {
|
||||
usingSprout = true;
|
||||
|
||||
// Switch if necessary to a Sprout-supporting transaction format.
|
||||
auto txVersionInfo = CurrentTxVersionInfo(consensusParams, nHeight, usingSprout.value());
|
||||
auto txVersionInfo = CurrentTxVersionInfo(consensusParams, nHeight, true);
|
||||
mtx.nVersionGroupId = txVersionInfo.nVersionGroupId;
|
||||
mtx.nVersion = txVersionInfo.nVersion;
|
||||
mtx.nConsensusBranchId = std::nullopt;
|
||||
|
|
|
@ -21,8 +21,107 @@
|
|||
|
||||
#include <optional>
|
||||
|
||||
#include <rust/builder.h>
|
||||
|
||||
#define NO_MEMO {{0xF6}}
|
||||
|
||||
namespace orchard { class UnauthorizedBundle; }
|
||||
|
||||
uint256 ProduceZip244SignatureHash(
|
||||
const CTransaction& tx,
|
||||
const orchard::UnauthorizedBundle& orchardBundle);
|
||||
|
||||
namespace orchard {
|
||||
|
||||
/// A builder that constructs an `UnauthorizedBundle` from a set of notes to be spent,
|
||||
/// and recipients to receive funds.
|
||||
class Builder {
|
||||
private:
|
||||
/// The Orchard builder. Memory is allocated by Rust. If this is `nullptr` then
|
||||
/// `Builder::Build` has been called, and all subsequent operations will throw an
|
||||
/// exception.
|
||||
std::unique_ptr<OrchardBuilderPtr, decltype(&orchard_builder_free)> inner;
|
||||
|
||||
Builder() : inner(nullptr, orchard_builder_free) { }
|
||||
|
||||
public:
|
||||
Builder(bool spendsEnabled, bool outputsEnabled, uint256 anchor);
|
||||
|
||||
// Builder should never be copied
|
||||
Builder(const Builder&) = delete;
|
||||
Builder& operator=(const Builder&) = delete;
|
||||
Builder(Builder&& builder) : inner(std::move(builder.inner)) {}
|
||||
Builder& operator=(Builder&& builder)
|
||||
{
|
||||
if (this != &builder) {
|
||||
inner = std::move(builder.inner);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
/// Adds an address which will receive funds in this bundle.
|
||||
void AddOutput(
|
||||
const std::optional<uint256>& ovk,
|
||||
const libzcash::OrchardRawAddress& to,
|
||||
CAmount value,
|
||||
std::array<unsigned char, ZC_MEMO_SIZE> memo = NO_MEMO);
|
||||
|
||||
/// Builds a bundle containing the given spent notes and recipients.
|
||||
///
|
||||
/// Returns `std::nullopt` if an error occurs.
|
||||
///
|
||||
/// Calling this method invalidates this object; in particular, if an error occurs
|
||||
/// this builder must be discarded and a new builder created. Subsequent usage of this
|
||||
/// object in any way will cause an exception. This emulates Rust's compile-time move
|
||||
/// semantics at runtime.
|
||||
std::optional<UnauthorizedBundle> Build();
|
||||
};
|
||||
|
||||
/// An unauthorized Orchard bundle, ready for its proof to be created and signatures
|
||||
/// applied.
|
||||
class UnauthorizedBundle {
|
||||
private:
|
||||
/// An optional Orchard bundle (with `nullptr` corresponding to `None`).
|
||||
/// Memory is allocated by Rust.
|
||||
std::unique_ptr<OrchardUnauthorizedBundlePtr, decltype(&orchard_unauthorized_bundle_free)> inner;
|
||||
|
||||
UnauthorizedBundle() : inner(nullptr, orchard_unauthorized_bundle_free) {}
|
||||
UnauthorizedBundle(OrchardUnauthorizedBundlePtr* bundle) : inner(bundle, orchard_unauthorized_bundle_free) {}
|
||||
friend class Builder;
|
||||
// The parentheses here are necessary to avoid the following compilation error:
|
||||
// error: C++ requires a type specifier for all declarations
|
||||
// friend uint256 ::ProduceZip244SignatureHash(
|
||||
// ~~~~~~ ^
|
||||
friend uint256 (::ProduceZip244SignatureHash(
|
||||
const CTransaction& tx,
|
||||
const UnauthorizedBundle& orchardBundle));
|
||||
|
||||
public:
|
||||
// UnauthorizedBundle should never be copied
|
||||
UnauthorizedBundle(const UnauthorizedBundle&) = delete;
|
||||
UnauthorizedBundle& operator=(const UnauthorizedBundle&) = delete;
|
||||
UnauthorizedBundle(UnauthorizedBundle&& bundle) : inner(std::move(bundle.inner)) {}
|
||||
UnauthorizedBundle& operator=(UnauthorizedBundle&& bundle)
|
||||
{
|
||||
if (this != &bundle) {
|
||||
inner = std::move(bundle.inner);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
/// Adds proofs and signatures to this bundle.
|
||||
///
|
||||
/// Returns `std::nullopt` if an error occurs.
|
||||
///
|
||||
/// Calling this method invalidates this object; in particular, if an error occurs
|
||||
/// this bundle must be discarded and a new bundle built. Subsequent usage of this
|
||||
/// object in any way will cause an exception. This emulates Rust's compile-time
|
||||
/// move semantics at runtime.
|
||||
std::optional<OrchardBundle> ProveAndSign(uint256 sighash);
|
||||
};
|
||||
|
||||
} // namespace orchard
|
||||
|
||||
struct SpendDescriptionInfo {
|
||||
libzcash::SaplingExpandedSpendingKey expsk;
|
||||
libzcash::SaplingNote note;
|
||||
|
@ -107,7 +206,6 @@ public:
|
|||
class TransactionBuilder
|
||||
{
|
||||
private:
|
||||
std::optional<bool> usingSprout;
|
||||
Consensus::Params consensusParams;
|
||||
int nHeight;
|
||||
const CKeyStore* keystore;
|
||||
|
@ -115,6 +213,8 @@ private:
|
|||
CCriticalSection* cs_coinsView;
|
||||
CMutableTransaction mtx;
|
||||
CAmount fee = 10000;
|
||||
std::optional<orchard::Builder> orchardBuilder;
|
||||
CAmount valueBalanceOrchard = 0;
|
||||
|
||||
std::vector<SpendDescriptionInfo> spends;
|
||||
std::vector<OutputDescriptionInfo> outputs;
|
||||
|
@ -131,14 +231,66 @@ public:
|
|||
TransactionBuilder(
|
||||
const Consensus::Params& consensusParams,
|
||||
int nHeight,
|
||||
std::optional<uint256> orchardAnchor,
|
||||
CKeyStore* keyStore = nullptr,
|
||||
CCoinsViewCache* coinsView = nullptr,
|
||||
CCriticalSection* cs_coinsView = nullptr);
|
||||
|
||||
// TransactionBuilder should never be copied
|
||||
TransactionBuilder(const TransactionBuilder&) = delete;
|
||||
TransactionBuilder& operator=(const TransactionBuilder&) = delete;
|
||||
TransactionBuilder(TransactionBuilder&& builder) :
|
||||
consensusParams(std::move(builder.consensusParams)),
|
||||
nHeight(std::move(builder.nHeight)),
|
||||
keystore(std::move(builder.keystore)),
|
||||
coinsView(std::move(builder.coinsView)),
|
||||
cs_coinsView(std::move(builder.cs_coinsView)),
|
||||
mtx(std::move(builder.mtx)),
|
||||
fee(std::move(builder.fee)),
|
||||
orchardBuilder(std::move(builder.orchardBuilder)),
|
||||
valueBalanceOrchard(std::move(builder.valueBalanceOrchard)),
|
||||
spends(std::move(builder.spends)),
|
||||
outputs(std::move(builder.outputs)),
|
||||
jsInputs(std::move(builder.jsInputs)),
|
||||
jsOutputs(std::move(builder.jsOutputs)),
|
||||
tIns(std::move(builder.tIns)),
|
||||
saplingChangeAddr(std::move(builder.saplingChangeAddr)),
|
||||
sproutChangeAddr(std::move(builder.sproutChangeAddr)),
|
||||
tChangeAddr(std::move(builder.tChangeAddr)) {}
|
||||
TransactionBuilder& operator=(TransactionBuilder&& builder)
|
||||
{
|
||||
if (this != &builder) {
|
||||
consensusParams = std::move(builder.consensusParams);
|
||||
nHeight = std::move(builder.nHeight);
|
||||
keystore = std::move(builder.keystore);
|
||||
coinsView = std::move(builder.coinsView);
|
||||
cs_coinsView = std::move(builder.cs_coinsView);
|
||||
mtx = std::move(builder.mtx);
|
||||
fee = std::move(builder.fee);
|
||||
orchardBuilder = std::move(builder.orchardBuilder);
|
||||
valueBalanceOrchard = std::move(builder.valueBalanceOrchard);
|
||||
spends = std::move(builder.spends);
|
||||
outputs = std::move(builder.outputs);
|
||||
jsInputs = std::move(builder.jsInputs);
|
||||
jsOutputs = std::move(builder.jsOutputs);
|
||||
tIns = std::move(builder.tIns);
|
||||
saplingChangeAddr = std::move(builder.saplingChangeAddr);
|
||||
sproutChangeAddr = std::move(builder.sproutChangeAddr);
|
||||
tChangeAddr = std::move(builder.tChangeAddr);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
void SetExpiryHeight(uint32_t nExpiryHeight);
|
||||
|
||||
void SetFee(CAmount fee);
|
||||
|
||||
void AddOrchardOutput(
|
||||
const std::optional<uint256>& ovk,
|
||||
const libzcash::OrchardRawAddress& to,
|
||||
CAmount value,
|
||||
std::array<unsigned char, ZC_MEMO_SIZE> memo = NO_MEMO);
|
||||
|
||||
// Throws if the anchor does not match the anchor used by
|
||||
// previously-added Sapling spends.
|
||||
void AddSaplingSpend(
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
#include "transaction_builder.h"
|
||||
|
||||
#include <array>
|
||||
#include <optional>
|
||||
|
||||
#include <rust/ed25519.h>
|
||||
|
||||
|
@ -348,7 +349,7 @@ CWalletTx GetValidSaplingReceive(const Consensus::Params& consensusParams,
|
|||
auto fvk = sk.expsk.full_viewing_key();
|
||||
auto pa = sk.ToXFVK().DefaultAddress();
|
||||
|
||||
auto builder = TransactionBuilder(consensusParams, 1, &keyStore);
|
||||
auto builder = TransactionBuilder(consensusParams, 1, std::nullopt, &keyStore);
|
||||
builder.SetFee(0);
|
||||
builder.AddTransparentInput(COutPoint(), scriptPubKey, value);
|
||||
builder.AddSaplingOutput(fvk.ovk, pa, value, {});
|
||||
|
|
|
@ -89,7 +89,7 @@ AsyncRPCOperation_mergetoaddress::AsyncRPCOperation_mergetoaddress(
|
|||
isUsingBuilder_ = false;
|
||||
if (builder) {
|
||||
isUsingBuilder_ = true;
|
||||
builder_ = builder.value();
|
||||
builder_ = std::move(builder.value());
|
||||
}
|
||||
|
||||
KeyIO keyIO(Params());
|
||||
|
|
|
@ -111,7 +111,7 @@ bool AsyncRPCOperation_saplingmigration::main_impl() {
|
|||
CCoinsViewCache coinsView(pcoinsTip);
|
||||
do {
|
||||
CAmount amountToSend = chooseAmount(availableFunds);
|
||||
auto builder = TransactionBuilder(consensusParams, targetHeight_, pwalletMain, &coinsView, &cs_main);
|
||||
auto builder = TransactionBuilder(consensusParams, targetHeight_, std::nullopt, pwalletMain, &coinsView, &cs_main);
|
||||
builder.SetExpiryHeight(targetHeight_ + MIGRATION_EXPIRY_DELTA);
|
||||
LogPrint("zrpcunsafe", "%s: Beginning creating transaction with Sapling output amount=%s\n", getId(), FormatMoney(amountToSend - DEFAULT_FEE));
|
||||
std::vector<SproutNoteEntry> fromNotes;
|
||||
|
|
|
@ -51,7 +51,7 @@ AsyncRPCOperation_sendmany::AsyncRPCOperation_sendmany(
|
|||
CAmount fee,
|
||||
bool allowRevealedAmounts,
|
||||
UniValue contextInfo) :
|
||||
builder_(builder), ztxoSelector_(ztxoSelector), recipients_(recipients),
|
||||
builder_(std::move(builder)), ztxoSelector_(ztxoSelector), recipients_(recipients),
|
||||
mindepth_(minDepth), fee_(fee), allowRevealedAmounts_(allowRevealedAmounts),
|
||||
contextinfo_(contextInfo)
|
||||
{
|
||||
|
|
|
@ -67,7 +67,7 @@ AsyncRPCOperation_shieldcoinbase::AsyncRPCOperation_shieldcoinbase(
|
|||
PaymentAddress toAddress,
|
||||
CAmount fee,
|
||||
UniValue contextInfo) :
|
||||
builder_(builder), tx_(contextualTx), inputs_(inputs), fee_(fee), contextinfo_(contextInfo)
|
||||
builder_(std::move(builder)), tx_(contextualTx), inputs_(inputs), fee_(fee), contextinfo_(contextInfo)
|
||||
{
|
||||
assert(contextualTx.nVersion >= 2); // transaction format version must support vJoinSplit
|
||||
|
||||
|
|
|
@ -402,7 +402,7 @@ TEST(WalletTests, SetSaplingNoteAddrsInCWalletTx) {
|
|||
ASSERT_TRUE(nf);
|
||||
uint256 nullifier = nf.value();
|
||||
|
||||
auto builder = TransactionBuilder(consensusParams, 1);
|
||||
auto builder = TransactionBuilder(consensusParams, 1, std::nullopt);
|
||||
builder.AddSaplingSpend(expsk, note, anchor, witness);
|
||||
builder.AddSaplingOutput(fvk.ovk, pk, 50000, {});
|
||||
builder.SetFee(0);
|
||||
|
@ -537,7 +537,7 @@ TEST(WalletTests, FindMySaplingNotes) {
|
|||
auto testNote = GetTestSaplingNote(pa, 50000);
|
||||
|
||||
// Generate transaction
|
||||
auto builder = TransactionBuilder(consensusParams, 1);
|
||||
auto builder = TransactionBuilder(consensusParams, 1, std::nullopt);
|
||||
builder.AddSaplingSpend(expsk, testNote.note, testNote.tree.root(), testNote.tree.witness());
|
||||
builder.AddSaplingOutput(extfvk.fvk.ovk, pa, 25000, {});
|
||||
auto tx = builder.Build().GetTxOrThrow();
|
||||
|
@ -682,7 +682,7 @@ TEST(WalletTests, GetConflictedSaplingNotes) {
|
|||
auto witness = saplingTree.witness();
|
||||
|
||||
// Generate tx to create output note B
|
||||
auto builder = TransactionBuilder(consensusParams, 1);
|
||||
auto builder = TransactionBuilder(consensusParams, 1, std::nullopt);
|
||||
builder.AddSaplingSpend(expsk, note, anchor, witness);
|
||||
builder.AddSaplingOutput(extfvk.fvk.ovk, pk, 35000, {});
|
||||
auto tx = builder.Build().GetTxOrThrow();
|
||||
|
@ -738,13 +738,13 @@ TEST(WalletTests, GetConflictedSaplingNotes) {
|
|||
anchor = saplingTree.root();
|
||||
|
||||
// Create transaction to spend note B
|
||||
auto builder2 = TransactionBuilder(consensusParams, 2);
|
||||
auto builder2 = TransactionBuilder(consensusParams, 2, std::nullopt);
|
||||
builder2.AddSaplingSpend(expsk, note2, anchor, spend_note_witness);
|
||||
builder2.AddSaplingOutput(extfvk.fvk.ovk, pk, 20000, {});
|
||||
auto tx2 = builder2.Build().GetTxOrThrow();
|
||||
|
||||
// Create conflicting transaction which also spends note B
|
||||
auto builder3 = TransactionBuilder(consensusParams, 2);
|
||||
auto builder3 = TransactionBuilder(consensusParams, 2, std::nullopt);
|
||||
builder3.AddSaplingSpend(expsk, note2, anchor, spend_note_witness);
|
||||
builder3.AddSaplingOutput(extfvk.fvk.ovk, pk, 19999, {});
|
||||
auto tx3 = builder3.Build().GetTxOrThrow();
|
||||
|
@ -833,7 +833,7 @@ TEST(WalletTests, SaplingNullifierIsSpent) {
|
|||
auto testNote = GetTestSaplingNote(pa, 50000);
|
||||
|
||||
// Generate transaction
|
||||
auto builder = TransactionBuilder(consensusParams, 1);
|
||||
auto builder = TransactionBuilder(consensusParams, 1, std::nullopt);
|
||||
builder.AddSaplingSpend(expsk, testNote.note, testNote.tree.root(), testNote.tree.witness());
|
||||
builder.AddSaplingOutput(extfvk.fvk.ovk, pa, 25000, {});
|
||||
auto tx = builder.Build().GetTxOrThrow();
|
||||
|
@ -918,7 +918,7 @@ TEST(WalletTests, NavigateFromSaplingNullifierToNote) {
|
|||
auto testNote = GetTestSaplingNote(pa, 50000);
|
||||
|
||||
// Generate transaction
|
||||
auto builder = TransactionBuilder(consensusParams, 1);
|
||||
auto builder = TransactionBuilder(consensusParams, 1, std::nullopt);
|
||||
builder.AddSaplingSpend(expsk, testNote.note, testNote.tree.root(), testNote.tree.witness());
|
||||
builder.AddSaplingOutput(extfvk.fvk.ovk, pa, 25000, {});
|
||||
auto tx = builder.Build().GetTxOrThrow();
|
||||
|
@ -1054,7 +1054,7 @@ TEST(WalletTests, SpentSaplingNoteIsFromMe) {
|
|||
auto witness = saplingTree.witness();
|
||||
|
||||
// Generate transaction, which sends funds to note B
|
||||
auto builder = TransactionBuilder(consensusParams, 1);
|
||||
auto builder = TransactionBuilder(consensusParams, 1, std::nullopt);
|
||||
builder.AddSaplingSpend(expsk, note, anchor, witness);
|
||||
builder.AddSaplingOutput(extfvk.fvk.ovk, pk, 25000, {});
|
||||
auto tx = builder.Build().GetTxOrThrow();
|
||||
|
@ -1126,7 +1126,7 @@ TEST(WalletTests, SpentSaplingNoteIsFromMe) {
|
|||
anchor = saplingTree.root();
|
||||
|
||||
// Create transaction to spend note B
|
||||
auto builder2 = TransactionBuilder(consensusParams, 2);
|
||||
auto builder2 = TransactionBuilder(consensusParams, 2, std::nullopt);
|
||||
builder2.AddSaplingSpend(expsk, note2, anchor, spend_note_witness);
|
||||
builder2.AddSaplingOutput(extfvk.fvk.ovk, pk, 12500, {});
|
||||
auto tx2 = builder2.Build().GetTxOrThrow();
|
||||
|
@ -1861,7 +1861,7 @@ TEST(WalletTests, UpdatedSaplingNoteData) {
|
|||
auto testNote = GetTestSaplingNote(pa, 50000);
|
||||
|
||||
// Generate transaction
|
||||
auto builder = TransactionBuilder(consensusParams, 1);
|
||||
auto builder = TransactionBuilder(consensusParams, 1, std::nullopt);
|
||||
builder.AddSaplingSpend(expsk, testNote.note, testNote.tree.root(), testNote.tree.witness());
|
||||
builder.AddSaplingOutput(extfvk.fvk.ovk, pa2, 25000, {});
|
||||
auto tx = builder.Build().GetTxOrThrow();
|
||||
|
@ -2004,7 +2004,7 @@ TEST(WalletTests, MarkAffectedSaplingTransactionsDirty) {
|
|||
|
||||
// Generate shielding tx from transparent to Sapling
|
||||
// 0.0005 t-ZEC in, 0.0004 z-ZEC out, default fee
|
||||
auto builder = TransactionBuilder(consensusParams, 1, &keystore);
|
||||
auto builder = TransactionBuilder(consensusParams, 1, std::nullopt, &keystore);
|
||||
builder.AddTransparentInput(COutPoint(), scriptPubKey, 50000);
|
||||
builder.AddSaplingOutput(extfvk.fvk.ovk, pk, 40000, {});
|
||||
auto tx1 = builder.Build().GetTxOrThrow();
|
||||
|
@ -2058,7 +2058,7 @@ TEST(WalletTests, MarkAffectedSaplingTransactionsDirty) {
|
|||
|
||||
// Create a Sapling-only transaction
|
||||
// 0.0004 z-ZEC in, 0.00025 z-ZEC out, default fee, 0.00005 z-ZEC change
|
||||
auto builder2 = TransactionBuilder(consensusParams, 2);
|
||||
auto builder2 = TransactionBuilder(consensusParams, 2, std::nullopt);
|
||||
builder2.AddSaplingSpend(expsk, note, anchor, witness);
|
||||
builder2.AddSaplingOutput(extfvk.fvk.ovk, pk, 25000, {});
|
||||
auto tx2 = builder2.Build().GetTxOrThrow();
|
||||
|
|
|
@ -4623,12 +4623,15 @@ UniValue z_sendmany(const UniValue& params, bool fHelp)
|
|||
o.pushKV("fee", std::stod(FormatMoney(nFee)));
|
||||
UniValue contextInfo = o;
|
||||
|
||||
TransactionBuilder builder(chainparams.GetConsensus(), nextBlockHeight, pwalletMain);
|
||||
// TODO: Allow Orchard recipients by specifying an Orchard anchor.
|
||||
auto orchardAnchor = std::nullopt;
|
||||
TransactionBuilder builder(chainparams.GetConsensus(), nextBlockHeight, orchardAnchor, pwalletMain);
|
||||
|
||||
// Create operation and add to global queue
|
||||
std::shared_ptr<AsyncRPCQueue> q = getAsyncRPCQueue();
|
||||
std::shared_ptr<AsyncRPCOperation> operation(
|
||||
new AsyncRPCOperation_sendmany(builder, ztxoSelector, recipients, nMinDepth, nFee, allowRevealedAmounts, contextInfo)
|
||||
new AsyncRPCOperation_sendmany(
|
||||
std::move(builder), ztxoSelector, recipients, nMinDepth, nFee, allowRevealedAmounts, contextInfo)
|
||||
);
|
||||
q->addOperation(operation);
|
||||
AsyncRPCOperationId operationId = operation->getId();
|
||||
|
@ -4972,8 +4975,10 @@ UniValue z_shieldcoinbase(const UniValue& params, bool fHelp)
|
|||
contextInfo.pushKV("fee", ValueFromAmount(nFee));
|
||||
|
||||
// Builder (used if Sapling addresses are involved)
|
||||
// TODO: Allow Orchard recipients by specifying an Orchard anchor.
|
||||
auto orchardAnchor = std::nullopt;
|
||||
TransactionBuilder builder = TransactionBuilder(
|
||||
Params().GetConsensus(), nextBlockHeight, pwalletMain);
|
||||
Params().GetConsensus(), nextBlockHeight, orchardAnchor, pwalletMain);
|
||||
|
||||
// Contextual transaction we will build on
|
||||
// (used if no Sapling addresses are involved)
|
||||
|
@ -4985,7 +4990,8 @@ UniValue z_shieldcoinbase(const UniValue& params, bool fHelp)
|
|||
|
||||
// Create operation and add to global queue
|
||||
std::shared_ptr<AsyncRPCQueue> q = getAsyncRPCQueue();
|
||||
std::shared_ptr<AsyncRPCOperation> operation( new AsyncRPCOperation_shieldcoinbase(builder, contextualTx, inputs, destaddress.value(), nFee, contextInfo) );
|
||||
std::shared_ptr<AsyncRPCOperation> operation( new AsyncRPCOperation_shieldcoinbase(
|
||||
std::move(builder), contextualTx, inputs, destaddress.value(), nFee, contextInfo) );
|
||||
q->addOperation(operation);
|
||||
AsyncRPCOperationId operationId = operation->getId();
|
||||
|
||||
|
@ -5439,12 +5445,15 @@ UniValue z_mergetoaddress(const UniValue& params, bool fHelp)
|
|||
// Builder (used if Sapling addresses are involved)
|
||||
std::optional<TransactionBuilder> builder;
|
||||
if (isToSaplingZaddr || saplingNoteInputs.size() > 0) {
|
||||
builder = TransactionBuilder(Params().GetConsensus(), nextBlockHeight, pwalletMain);
|
||||
// TODO: Allow Orchard recipients by specifying an Orchard anchor.
|
||||
auto orchardAnchor = std::nullopt;
|
||||
builder = TransactionBuilder(Params().GetConsensus(), nextBlockHeight, orchardAnchor, pwalletMain);
|
||||
}
|
||||
// Create operation and add to global queue
|
||||
std::shared_ptr<AsyncRPCQueue> q = getAsyncRPCQueue();
|
||||
std::shared_ptr<AsyncRPCOperation> operation(
|
||||
new AsyncRPCOperation_mergetoaddress(builder, contextualTx, utxoInputs, sproutNoteInputs, saplingNoteInputs, recipient, nFee, contextInfo) );
|
||||
new AsyncRPCOperation_mergetoaddress(
|
||||
std::move(builder), contextualTx, utxoInputs, sproutNoteInputs, saplingNoteInputs, recipient, nFee, contextInfo) );
|
||||
q->addOperation(operation);
|
||||
AsyncRPCOperationId operationId = operation->getId();
|
||||
|
||||
|
|
|
@ -1191,7 +1191,7 @@ BOOST_AUTO_TEST_CASE(rpc_z_sendmany_parameters)
|
|||
// Mutable tx containing contextual information we need to build tx
|
||||
UniValue retValue = CallRPC("getblockcount");
|
||||
int nHeight = retValue.get_int();
|
||||
TransactionBuilder builder(Params().GetConsensus(), nHeight + 1, pwalletMain);
|
||||
TransactionBuilder builder(Params().GetConsensus(), nHeight + 1, std::nullopt, pwalletMain);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(asyncrpcoperation_sign_send_raw_transaction) {
|
||||
|
@ -1234,9 +1234,9 @@ BOOST_AUTO_TEST_CASE(rpc_z_sendmany_internals)
|
|||
// there are no utxos to spend
|
||||
{
|
||||
auto selector = pwalletMain->ZTXOSelectorForAddress(taddr1, true).value();
|
||||
TransactionBuilder builder(consensusParams, nHeight + 1, pwalletMain);
|
||||
TransactionBuilder builder(consensusParams, nHeight + 1, std::nullopt, pwalletMain);
|
||||
std::vector<SendManyRecipient> recipients = { SendManyRecipient(zaddr1, 100*COIN, "DEADBEEF") };
|
||||
std::shared_ptr<AsyncRPCOperation> operation(new AsyncRPCOperation_sendmany(builder, selector, recipients, 1));
|
||||
std::shared_ptr<AsyncRPCOperation> operation(new AsyncRPCOperation_sendmany(std::move(builder), selector, recipients, 1));
|
||||
operation->main();
|
||||
BOOST_CHECK(operation->isFailed());
|
||||
std::string msg = operation->getErrorMessage();
|
||||
|
@ -1246,9 +1246,9 @@ BOOST_AUTO_TEST_CASE(rpc_z_sendmany_internals)
|
|||
// there are no unspent notes to spend
|
||||
{
|
||||
auto selector = pwalletMain->ZTXOSelectorForAddress(zaddr1, true).value();
|
||||
TransactionBuilder builder(consensusParams, nHeight + 1, pwalletMain);
|
||||
TransactionBuilder builder(consensusParams, nHeight + 1, std::nullopt, pwalletMain);
|
||||
std::vector<SendManyRecipient> recipients = { SendManyRecipient(taddr1, 100*COIN, "DEADBEEF") };
|
||||
std::shared_ptr<AsyncRPCOperation> operation(new AsyncRPCOperation_sendmany(builder, selector, recipients, 1));
|
||||
std::shared_ptr<AsyncRPCOperation> operation(new AsyncRPCOperation_sendmany(std::move(builder), selector, recipients, 1));
|
||||
operation->main();
|
||||
BOOST_CHECK(operation->isFailed());
|
||||
std::string msg = operation->getErrorMessage();
|
||||
|
@ -1258,9 +1258,9 @@ BOOST_AUTO_TEST_CASE(rpc_z_sendmany_internals)
|
|||
// get_memo_from_hex_string())
|
||||
{
|
||||
auto selector = pwalletMain->ZTXOSelectorForAddress(zaddr1, true).value();
|
||||
TransactionBuilder builder(consensusParams, nHeight + 1, pwalletMain);
|
||||
TransactionBuilder builder(consensusParams, nHeight + 1, std::nullopt, pwalletMain);
|
||||
std::vector<SendManyRecipient> recipients = { SendManyRecipient(zaddr1, 100*COIN, "DEADBEEF") };
|
||||
std::shared_ptr<AsyncRPCOperation> operation(new AsyncRPCOperation_sendmany(builder, selector, recipients, 1));
|
||||
std::shared_ptr<AsyncRPCOperation> operation(new AsyncRPCOperation_sendmany(std::move(builder), selector, recipients, 1));
|
||||
std::shared_ptr<AsyncRPCOperation_sendmany> ptr = std::dynamic_pointer_cast<AsyncRPCOperation_sendmany> (operation);
|
||||
TEST_FRIEND_AsyncRPCOperation_sendmany proxy(ptr);
|
||||
|
||||
|
@ -1355,12 +1355,12 @@ BOOST_AUTO_TEST_CASE(rpc_z_sendmany_taddr_to_sapling)
|
|||
pwalletMain->AddToWallet(wtx, true, NULL);
|
||||
|
||||
// Context that z_sendmany requires
|
||||
auto builder = TransactionBuilder(consensusParams, nextBlockHeight, pwalletMain);
|
||||
auto builder = TransactionBuilder(consensusParams, nextBlockHeight, std::nullopt, pwalletMain);
|
||||
mtx = CreateNewContextualCMutableTransaction(consensusParams, nextBlockHeight);
|
||||
|
||||
auto selector = pwalletMain->ZTXOSelectorForAddress(taddr, true).value();
|
||||
std::vector<SendManyRecipient> recipients = { SendManyRecipient(pa, 1*COIN, "ABCD") };
|
||||
std::shared_ptr<AsyncRPCOperation> operation(new AsyncRPCOperation_sendmany(builder, selector, recipients, 0));
|
||||
std::shared_ptr<AsyncRPCOperation> operation(new AsyncRPCOperation_sendmany(std::move(builder), selector, recipients, 0));
|
||||
std::shared_ptr<AsyncRPCOperation_sendmany> ptr = std::dynamic_pointer_cast<AsyncRPCOperation_sendmany> (operation);
|
||||
|
||||
// Enable test mode so tx is not sent
|
||||
|
|
|
@ -9,6 +9,8 @@
|
|||
#include "zcash/address/zip32.h"
|
||||
#include <rust/orchard/keys.h>
|
||||
|
||||
namespace orchard { class Builder; }
|
||||
|
||||
namespace libzcash {
|
||||
|
||||
class OrchardIncomingViewingKey;
|
||||
|
@ -23,6 +25,7 @@ private:
|
|||
OrchardRawAddress(OrchardRawAddressPtr* ptr) : inner(ptr, orchard_address_free) {}
|
||||
|
||||
friend class OrchardIncomingViewingKey;
|
||||
friend class ::orchard::Builder;
|
||||
public:
|
||||
OrchardRawAddress(OrchardRawAddress&& key) : inner(std::move(key.inner)) {}
|
||||
|
||||
|
|
Loading…
Reference in New Issue