TransactionBuilder: Add support for transparent inputs and outputs
This commit is contained in:
parent
3fd0a269e1
commit
3466b4677e
|
@ -1,13 +1,17 @@
|
|||
#include "chainparams.h"
|
||||
#include "consensus/params.h"
|
||||
#include "consensus/validation.h"
|
||||
#include "key_io.h"
|
||||
#include "main.h"
|
||||
#include "pubkey.h"
|
||||
#include "transaction_builder.h"
|
||||
#include "zcash/Address.hpp"
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
static const std::string tSecretRegtest = "cND2ZvtabDbJ1gucx9GWH6XT9kgTAqfb6cotPt5Q5CyxVDhid2EN";
|
||||
|
||||
TEST(TransactionBuilder, Invoke)
|
||||
{
|
||||
SelectParams(CBaseChainParams::REGTEST);
|
||||
|
@ -15,6 +19,11 @@ TEST(TransactionBuilder, Invoke)
|
|||
UpdateNetworkUpgradeParameters(Consensus::UPGRADE_SAPLING, Consensus::NetworkUpgrade::ALWAYS_ACTIVE);
|
||||
auto consensusParams = Params().GetConsensus();
|
||||
|
||||
CBasicKeyStore keystore;
|
||||
CKey tsk = DecodeSecret(tSecretRegtest);
|
||||
keystore.AddKey(tsk);
|
||||
auto scriptPubKey = GetScriptForDestination(tsk.GetPubKey().GetID());
|
||||
|
||||
auto sk_from = libzcash::SaplingSpendingKey::random();
|
||||
auto fvk_from = sk_from.full_viewing_key();
|
||||
|
||||
|
@ -26,19 +35,20 @@ TEST(TransactionBuilder, Invoke)
|
|||
auto pk = *ivk.address(d);
|
||||
|
||||
// Create a shielding transaction from transparent to Sapling
|
||||
// TODO: Add transparent inputs :P
|
||||
auto builder1 = TransactionBuilder(consensusParams, 1);
|
||||
builder1.AddSaplingOutput(fvk_from, pk, 50, {});
|
||||
// 0.0005 t-ZEC in, 0.0004 z-ZEC out, 0.0001 t-ZEC fee
|
||||
auto builder1 = TransactionBuilder(consensusParams, 1, &keystore);
|
||||
builder1.AddTransparentInput(COutPoint(), scriptPubKey, 50000);
|
||||
builder1.AddSaplingOutput(fvk_from, pk, 40000, {});
|
||||
auto maybe_tx1 = builder1.Build();
|
||||
ASSERT_EQ(static_cast<bool>(maybe_tx1), true);
|
||||
auto tx1 = maybe_tx1.get();
|
||||
|
||||
EXPECT_EQ(tx1.vin.size(), 0);
|
||||
EXPECT_EQ(tx1.vin.size(), 1);
|
||||
EXPECT_EQ(tx1.vout.size(), 0);
|
||||
EXPECT_EQ(tx1.vjoinsplit.size(), 0);
|
||||
EXPECT_EQ(tx1.vShieldedSpend.size(), 0);
|
||||
EXPECT_EQ(tx1.vShieldedOutput.size(), 1);
|
||||
EXPECT_EQ(tx1.valueBalance, -50);
|
||||
EXPECT_EQ(tx1.valueBalance, -40000);
|
||||
|
||||
CValidationState state;
|
||||
EXPECT_TRUE(ContextualCheckTransaction(tx1, state, 2, 0));
|
||||
|
@ -57,12 +67,13 @@ TEST(TransactionBuilder, Invoke)
|
|||
auto witness = tree.witness();
|
||||
|
||||
// Create a Sapling-only transaction
|
||||
// 0.0004 z-ZEC in, 0.00025 z-ZEC out, 0.0001 t-ZEC fee, 0.00005 ZEC change
|
||||
auto builder2 = TransactionBuilder(consensusParams, 2);
|
||||
ASSERT_TRUE(builder2.AddSaplingSpend(xsk, note, anchor, witness));
|
||||
// Check that trying to add a different anchor fails
|
||||
ASSERT_FALSE(builder2.AddSaplingSpend(xsk, note, uint256(), witness));
|
||||
|
||||
builder2.AddSaplingOutput(fvk, pk, 25, {});
|
||||
builder2.AddSaplingOutput(fvk, pk, 25000, {});
|
||||
auto maybe_tx2 = builder2.Build();
|
||||
ASSERT_EQ(static_cast<bool>(maybe_tx2), true);
|
||||
auto tx2 = maybe_tx2.get();
|
||||
|
@ -72,7 +83,7 @@ TEST(TransactionBuilder, Invoke)
|
|||
EXPECT_EQ(tx2.vjoinsplit.size(), 0);
|
||||
EXPECT_EQ(tx2.vShieldedSpend.size(), 1);
|
||||
EXPECT_EQ(tx2.vShieldedOutput.size(), 1);
|
||||
EXPECT_EQ(tx2.valueBalance, 25);
|
||||
EXPECT_EQ(tx2.valueBalance, 15000);
|
||||
|
||||
EXPECT_TRUE(ContextualCheckTransaction(tx2, state, 3, 0));
|
||||
EXPECT_EQ(state.GetRejectReason(), "");
|
||||
|
@ -81,3 +92,76 @@ TEST(TransactionBuilder, Invoke)
|
|||
UpdateNetworkUpgradeParameters(Consensus::UPGRADE_SAPLING, Consensus::NetworkUpgrade::NO_ACTIVATION_HEIGHT);
|
||||
UpdateNetworkUpgradeParameters(Consensus::UPGRADE_OVERWINTER, Consensus::NetworkUpgrade::NO_ACTIVATION_HEIGHT);
|
||||
}
|
||||
|
||||
TEST(TransactionBuilder, ThrowsOnTransparentInputWithoutKeyStore)
|
||||
{
|
||||
auto consensusParams = Params().GetConsensus();
|
||||
|
||||
auto builder = TransactionBuilder(consensusParams, 1);
|
||||
ASSERT_THROW(builder.AddTransparentInput(COutPoint(), CScript(), 1), std::runtime_error);
|
||||
}
|
||||
|
||||
TEST(TransactionBuilder, RejectsInvalidTransparentOutput)
|
||||
{
|
||||
auto consensusParams = Params().GetConsensus();
|
||||
|
||||
// Default CTxDestination type is an invalid address
|
||||
CTxDestination taddr;
|
||||
auto builder = TransactionBuilder(consensusParams, 1);
|
||||
EXPECT_FALSE(builder.AddTransparentOutput(taddr, 50));
|
||||
}
|
||||
|
||||
TEST(TransactionBuilder, FailsWithNegativeChange)
|
||||
{
|
||||
SelectParams(CBaseChainParams::REGTEST);
|
||||
UpdateNetworkUpgradeParameters(Consensus::UPGRADE_OVERWINTER, Consensus::NetworkUpgrade::ALWAYS_ACTIVE);
|
||||
UpdateNetworkUpgradeParameters(Consensus::UPGRADE_SAPLING, Consensus::NetworkUpgrade::ALWAYS_ACTIVE);
|
||||
auto consensusParams = Params().GetConsensus();
|
||||
|
||||
// Generate dummy Sapling address
|
||||
auto sk = libzcash::SaplingSpendingKey::random();
|
||||
auto xsk = sk.expanded_spending_key();
|
||||
auto fvk = sk.full_viewing_key();
|
||||
auto pk = sk.default_address();
|
||||
|
||||
// Set up dummy transparent address
|
||||
CBasicKeyStore keystore;
|
||||
CKey tsk = DecodeSecret(tSecretRegtest);
|
||||
keystore.AddKey(tsk);
|
||||
auto tkeyid = tsk.GetPubKey().GetID();
|
||||
auto scriptPubKey = GetScriptForDestination(tkeyid);
|
||||
CTxDestination taddr = tkeyid;
|
||||
|
||||
// Generate dummy Sapling note
|
||||
libzcash::SaplingNote note(pk, 59999);
|
||||
auto cm = note.cm().value();
|
||||
ZCSaplingIncrementalMerkleTree tree;
|
||||
tree.append(cm);
|
||||
auto anchor = tree.root();
|
||||
auto witness = tree.witness();
|
||||
|
||||
// Fail if there is only a Sapling output
|
||||
// 0.0005 z-ZEC out, 0.0001 t-ZEC fee
|
||||
auto builder = TransactionBuilder(consensusParams, 1);
|
||||
builder.AddSaplingOutput(fvk, pk, 50000, {});
|
||||
EXPECT_FALSE(static_cast<bool>(builder.Build()));
|
||||
|
||||
// Fail if there is only a transparent output
|
||||
// 0.0005 t-ZEC out, 0.0001 t-ZEC fee
|
||||
builder = TransactionBuilder(consensusParams, 1, &keystore);
|
||||
EXPECT_TRUE(builder.AddTransparentOutput(taddr, 50000));
|
||||
EXPECT_FALSE(static_cast<bool>(builder.Build()));
|
||||
|
||||
// Fails if there is insufficient input
|
||||
// 0.0005 t-ZEC out, 0.0001 t-ZEC fee, 0.00059999 z-ZEC in
|
||||
EXPECT_TRUE(builder.AddSaplingSpend(xsk, note, anchor, witness));
|
||||
EXPECT_FALSE(static_cast<bool>(builder.Build()));
|
||||
|
||||
// Succeeds if there is sufficient input
|
||||
builder.AddTransparentInput(COutPoint(), scriptPubKey, 1);
|
||||
EXPECT_TRUE(static_cast<bool>(builder.Build()));
|
||||
|
||||
// Revert to default
|
||||
UpdateNetworkUpgradeParameters(Consensus::UPGRADE_SAPLING, Consensus::NetworkUpgrade::NO_ACTIVATION_HEIGHT);
|
||||
UpdateNetworkUpgradeParameters(Consensus::UPGRADE_OVERWINTER, Consensus::NetworkUpgrade::NO_ACTIVATION_HEIGHT);
|
||||
}
|
||||
|
|
|
@ -5,7 +5,8 @@
|
|||
#include "transaction_builder.h"
|
||||
|
||||
#include "main.h"
|
||||
#include "script/script.h"
|
||||
#include "pubkey.h"
|
||||
#include "script/sign.h"
|
||||
|
||||
#include <boost/variant.hpp>
|
||||
#include <librustzcash.h>
|
||||
|
@ -21,7 +22,8 @@ SpendDescriptionInfo::SpendDescriptionInfo(
|
|||
|
||||
TransactionBuilder::TransactionBuilder(
|
||||
const Consensus::Params& consensusParams,
|
||||
int nHeight) : consensusParams(consensusParams), nHeight(nHeight)
|
||||
int nHeight,
|
||||
CKeyStore* keystore) : consensusParams(consensusParams), nHeight(nHeight), keystore(keystore)
|
||||
{
|
||||
mtx = CreateNewContextualCMutableTransaction(consensusParams, nHeight);
|
||||
}
|
||||
|
@ -55,8 +57,55 @@ void TransactionBuilder::AddSaplingOutput(
|
|||
mtx.valueBalance -= value;
|
||||
}
|
||||
|
||||
void TransactionBuilder::AddTransparentInput(COutPoint utxo, CScript scriptPubKey, CAmount value)
|
||||
{
|
||||
if (keystore == nullptr) {
|
||||
throw std::runtime_error("Cannot add transparent inputs to a TransactionBuilder without a keystore");
|
||||
}
|
||||
|
||||
mtx.vin.emplace_back(utxo);
|
||||
tIns.emplace_back(scriptPubKey, value);
|
||||
}
|
||||
|
||||
bool TransactionBuilder::AddTransparentOutput(CTxDestination& to, CAmount value)
|
||||
{
|
||||
if (!IsValidDestination(to)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
CScript scriptPubKey = GetScriptForDestination(to);
|
||||
CTxOut out(value, scriptPubKey);
|
||||
mtx.vout.push_back(out);
|
||||
return true;
|
||||
}
|
||||
|
||||
boost::optional<CTransaction> TransactionBuilder::Build()
|
||||
{
|
||||
// Fixed fee
|
||||
const CAmount fee = 10000;
|
||||
|
||||
//
|
||||
// Consistency checks
|
||||
//
|
||||
|
||||
// Valid change
|
||||
CAmount change = mtx.valueBalance - fee;
|
||||
for (auto tIn : tIns) {
|
||||
change += tIn.value;
|
||||
}
|
||||
for (auto tOut : mtx.vout) {
|
||||
change -= tOut.nValue;
|
||||
}
|
||||
if (change < 0) {
|
||||
return boost::none;
|
||||
}
|
||||
|
||||
// TODO: Create change output (currently, the change is added to the fee)
|
||||
|
||||
//
|
||||
// Sapling spends and outputs
|
||||
//
|
||||
|
||||
auto ctx = librustzcash_sapling_proving_ctx_init();
|
||||
|
||||
// Create Sapling SpendDescriptions
|
||||
|
@ -141,7 +190,10 @@ boost::optional<CTransaction> TransactionBuilder::Build()
|
|||
mtx.vShieldedOutput.push_back(odesc);
|
||||
}
|
||||
|
||||
// Calculate SignatureHash
|
||||
//
|
||||
// Signatures
|
||||
//
|
||||
|
||||
auto consensusBranchId = CurrentEpochBranchId(nHeight, consensusParams);
|
||||
|
||||
// Empty output script.
|
||||
|
@ -169,5 +221,23 @@ boost::optional<CTransaction> TransactionBuilder::Build()
|
|||
mtx.bindingSig.data());
|
||||
|
||||
librustzcash_sapling_proving_ctx_free(ctx);
|
||||
|
||||
// Transparent signatures
|
||||
CTransaction txNewConst(mtx);
|
||||
for (int nIn = 0; nIn < mtx.vin.size(); nIn++) {
|
||||
auto tIn = tIns[nIn];
|
||||
SignatureData sigdata;
|
||||
bool signSuccess = ProduceSignature(
|
||||
TransactionSignatureCreator(
|
||||
keystore, &txNewConst, nIn, tIn.value, SIGHASH_ALL),
|
||||
tIn.scriptPubKey, sigdata, consensusBranchId);
|
||||
|
||||
if (!signSuccess) {
|
||||
return boost::none;
|
||||
} else {
|
||||
UpdateTransaction(mtx, nIn, sigdata);
|
||||
}
|
||||
}
|
||||
|
||||
return CTransaction(mtx);
|
||||
}
|
||||
|
|
|
@ -6,7 +6,10 @@
|
|||
#define TRANSACTION_BUILDER_H
|
||||
|
||||
#include "consensus/params.h"
|
||||
#include "keystore.h"
|
||||
#include "primitives/transaction.h"
|
||||
#include "script/script.h"
|
||||
#include "script/standard.h"
|
||||
#include "uint256.h"
|
||||
#include "zcash/Address.hpp"
|
||||
#include "zcash/IncrementalMerkleTree.hpp"
|
||||
|
@ -40,18 +43,29 @@ struct OutputDescriptionInfo {
|
|||
std::array<unsigned char, ZC_MEMO_SIZE> memo) : ovk(ovk), note(note), memo(memo) {}
|
||||
};
|
||||
|
||||
struct TransparentInputInfo {
|
||||
CScript scriptPubKey;
|
||||
CAmount value;
|
||||
|
||||
TransparentInputInfo(
|
||||
CScript scriptPubKey,
|
||||
CAmount value) : scriptPubKey(scriptPubKey), value(value) {}
|
||||
};
|
||||
|
||||
class TransactionBuilder
|
||||
{
|
||||
private:
|
||||
Consensus::Params consensusParams;
|
||||
int nHeight;
|
||||
const CKeyStore* keystore;
|
||||
CMutableTransaction mtx;
|
||||
|
||||
std::vector<SpendDescriptionInfo> spends;
|
||||
std::vector<OutputDescriptionInfo> outputs;
|
||||
std::vector<TransparentInputInfo> tIns;
|
||||
|
||||
public:
|
||||
TransactionBuilder(const Consensus::Params& consensusParams, int nHeight);
|
||||
TransactionBuilder(const Consensus::Params& consensusParams, int nHeight, CKeyStore* keyStore = nullptr);
|
||||
|
||||
// Returns false if the anchor does not match the anchor used by
|
||||
// previously-added Sapling spends.
|
||||
|
@ -67,6 +81,11 @@ public:
|
|||
CAmount value,
|
||||
std::array<unsigned char, ZC_MEMO_SIZE> memo);
|
||||
|
||||
// Assumes that the value correctly corresponds to the provided UTXO.
|
||||
void AddTransparentInput(COutPoint utxo, CScript scriptPubKey, CAmount value);
|
||||
|
||||
bool AddTransparentOutput(CTxDestination& to, CAmount value);
|
||||
|
||||
boost::optional<CTransaction> Build();
|
||||
};
|
||||
|
||||
|
|
Loading…
Reference in New Issue