PIVX Core  5.6.99
P2P Digital Currency
transaction_builder_tests.cpp
Go to the documentation of this file.
1 // Copyright (c) 2016-2020 The ZCash developers
2 // Copyright (c) 2020-2021 The PIVX Core developers
3 // Distributed under the MIT software license, see the accompanying
4 // file COPYING or https://www.opensource.org/licenses/mit-license.php.
5 
8 
9 #include "sapling/sapling.h"
12 
13 #include <univalue.h>
14 #include <boost/test/unit_test.hpp>
15 
16 BOOST_FIXTURE_TEST_SUITE(sapling_transaction_builder_tests, SaplingRegTestingSetup)
17 
18 BOOST_AUTO_TEST_CASE(TransparentToSapling)
19 {
20  auto consensusParams = Params().GetConsensus();
21 
22  CBasicKeyStore keystore;
23  CKey tsk = AddTestCKeyToKeyStore(keystore);
24  auto scriptPubKey = GetScriptForDestination(tsk.GetPubKey().GetID());
25 
26  auto sk_from = libzcash::SaplingSpendingKey::random();
27  auto fvk_from = sk_from.full_viewing_key();
28 
30  auto fvk = sk.full_viewing_key();
31  auto ivk = fvk.in_viewing_key();
32  diversifier_t d = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
33  auto pk = *ivk.address(d);
34 
35  // Create a shielding transaction from transparent to Sapling
36  // 0.5 t-PIV in, 0.4 shielded-PIV out, 0.1 t-PIV fee
37  auto builder = TransactionBuilder(consensusParams, &keystore);
38  builder.AddTransparentInput(COutPoint(uint256S("1234"), 0), scriptPubKey, 50000000);
39  builder.AddSaplingOutput(fvk_from.ovk, pk, 40000000, {});
40  builder.SetFee(10000000);
41  auto tx = builder.Build().GetTxOrThrow();
42 
43  BOOST_CHECK_EQUAL(tx.vin.size(), 1);
44  BOOST_CHECK_EQUAL(tx.vout.size(), 0);
45  BOOST_CHECK_EQUAL(tx.sapData->vShieldedSpend.size(), 0);
46  BOOST_CHECK_EQUAL(tx.sapData->vShieldedOutput.size(), 1);
47  BOOST_CHECK_EQUAL(tx.sapData->valueBalance, -40000000);
48 
49  CValidationState state;
52 }
53 
54 BOOST_AUTO_TEST_CASE(SaplingToSapling)
55 {
56  auto consensusParams = Params().GetConsensus();
57 
59  auto expsk = sk.expanded_spending_key();
60  auto fvk = sk.full_viewing_key();
61  auto pa = sk.default_address();
62 
63  // Create a Sapling-only transaction
64  // --- 0.4 shielded-PIV in, 0.299 shielded-PIV out, 0.1 shielded-PIV fee, 0.001 shielded-PIV change (added to fee)
65  auto testNote = GetTestSaplingNote(pa, 40000000);
66  auto builder = TransactionBuilder(consensusParams);
67  builder.AddSaplingSpend(expsk, testNote.note, testNote.tree.root(), testNote.tree.witness());
68  builder.SetFee(10000000);
69 
70  // Check that trying to add a different anchor fails
71  // TODO: the following check can be split out in to another test
72  BOOST_CHECK_THROW(builder.AddSaplingSpend(expsk, testNote.note, uint256(), testNote.tree.witness()), std::runtime_error);
73 
74  builder.AddSaplingOutput(fvk.ovk, pa, 29900000, {});
75  auto tx = builder.Build().GetTxOrThrow();
76 
77  BOOST_CHECK_EQUAL(tx.vin.size(), 0);
78  BOOST_CHECK_EQUAL(tx.vout.size(), 0);
79  BOOST_CHECK_EQUAL(tx.sapData->vShieldedSpend.size(), 1);
80  // since the change is below the dust threshold, it is added to the fee
81  BOOST_CHECK_EQUAL(tx.sapData->vShieldedOutput.size(), 1);
82  BOOST_CHECK_EQUAL(tx.sapData->valueBalance, 10100000);
83 
84  CValidationState state;
87 
88  // --- Now try with 1 shielded-PIV in, 0.5 shielded-PIV out, 0.1 shielded-PIV fee, 0.4 shielded-PIV change
89  auto testNote2 = GetTestSaplingNote(pa, 100000000);
90  auto builder2 = TransactionBuilder(consensusParams);
91  builder2.AddSaplingSpend(expsk, testNote2.note, testNote2.tree.root(), testNote2.tree.witness());
92  builder2.SetFee(10000000);
93  builder2.AddSaplingOutput(fvk.ovk, pa, 50000000, {});
94  auto tx2 = builder2.Build().GetTxOrThrow();
95  BOOST_CHECK_EQUAL(tx2.vin.size(), 0);
96  BOOST_CHECK_EQUAL(tx2.vout.size(), 0);
97  BOOST_CHECK_EQUAL(tx2.sapData->vShieldedSpend.size(), 1);
98  BOOST_CHECK_EQUAL(tx2.sapData->vShieldedOutput.size(), 2);
99  BOOST_CHECK_EQUAL(tx2.sapData->valueBalance, 10000000);
100  BOOST_CHECK(SaplingValidation::ContextualCheckTransaction(tx2, state, Params(), 3, true, false));
101  BOOST_CHECK_EQUAL(state.GetRejectReason(), "");
102 }
103 
104 BOOST_AUTO_TEST_CASE(ThrowsOnTransparentInputWithoutKeyStore)
105 {
106  auto builder = TransactionBuilder(Params().GetConsensus());
107  BOOST_CHECK_THROW(builder.AddTransparentInput(COutPoint(), CScript(), 1), std::runtime_error);
108 }
109 
110 BOOST_AUTO_TEST_CASE(RejectsInvalidTransparentOutput)
111 {
112  // Default CTxDestination type is an invalid address
113  CTxDestination taddr;
114  auto builder = TransactionBuilder(Params().GetConsensus());
115  BOOST_CHECK_THROW(builder.AddTransparentOutput(taddr, 50), std::runtime_error);
116 }
117 
118 BOOST_AUTO_TEST_CASE(RejectsInvalidTransparentChangeAddress)
119 {
120  // Default CTxDestination type is an invalid address
121  CTxDestination taddr;
122  auto builder = TransactionBuilder(Params().GetConsensus());
123  BOOST_CHECK_THROW(builder.SendChangeTo(taddr), std::runtime_error);
124 }
125 
126 BOOST_AUTO_TEST_CASE(FailsWithNegativeChange)
127 {
128  auto consensusParams = Params().GetConsensus();
129 
130  // Generate dummy Sapling address
132  auto expsk = sk.expanded_spending_key();
133  auto fvk = sk.full_viewing_key();
134  auto pa = sk.default_address();
135 
136  // Set up dummy transparent address
137  CBasicKeyStore keystore;
138  CKey tsk = AddTestCKeyToKeyStore(keystore);
139  auto tkeyid = tsk.GetPubKey().GetID();
140  auto scriptPubKey = GetScriptForDestination(tkeyid);
141  CTxDestination taddr = tkeyid;
142 
143  // Generate a 0.5 PIV note
144  auto testNote = GetTestSaplingNote(pa, 59990000);
145 
146  // Fail if there is only a Sapling output
147  // 0.5 shielded-PIV out, 0.1 t-PIV fee
148  auto builder = TransactionBuilder(consensusParams);
149  builder.AddSaplingOutput(fvk.ovk, pa, 50000000, {});
150  builder.SetFee(10000000);
151  BOOST_CHECK_EQUAL("Change cannot be negative", builder.Build().GetError());
152 
153  // Fail if there is only a transparent output
154  // 0.5 t-PIV out, 0.1 t-PIV fee
155  builder = TransactionBuilder(consensusParams, &keystore);
156  builder.AddTransparentOutput(taddr, 50000000);
157  builder.SetFee(10000000);
158  BOOST_CHECK_EQUAL("Change cannot be negative", builder.Build().GetError());
159 
160  // Fails if there is insufficient input
161  // 0.5 t-PIV out, 0.1 t-PIV fee, 0.59999 shielded-PIV in
162  builder.AddSaplingSpend(expsk, testNote.note, testNote.tree.root(), testNote.tree.witness());
163  BOOST_CHECK_EQUAL("Change cannot be negative", builder.Build().GetError());
164 
165  // Succeeds if there is sufficient input
166  builder.AddTransparentInput(COutPoint(), scriptPubKey, 10000);
167  BOOST_CHECK(builder.Build().IsTx());
168 }
169 
170 BOOST_AUTO_TEST_CASE(ChangeOutput)
171 {
172  auto consensusParams = Params().GetConsensus();
173 
174  // Generate dummy Sapling address
176  auto expsk = sk.expanded_spending_key();
177  auto pa = sk.default_address();
178 
179  auto testNote = GetTestSaplingNote(pa, 25000000);
180 
181  // Generate change Sapling address
183  auto fvkOut = sk2.full_viewing_key();
184  auto zChangeAddr = sk2.default_address();
185 
186  // Set up dummy transparent address
187  CBasicKeyStore keystore;
188  CKey tsk = AddTestCKeyToKeyStore(keystore);
189  auto tkeyid = tsk.GetPubKey().GetID();
190  auto scriptPubKey = GetScriptForDestination(tkeyid);
191  CTxDestination taddr = tkeyid;
192 
193  // No change address and no Sapling spends
194  {
195  auto builder = TransactionBuilder(consensusParams, &keystore);
196  builder.SetFee(10000000);
197  builder.AddTransparentInput(COutPoint(), scriptPubKey, 25000000);
198  BOOST_CHECK_EQUAL("Could not determine change address", builder.Build().GetError());
199  }
200 
201  // Change to the same address as the first Sapling spend
202  {
203  auto builder = TransactionBuilder(consensusParams, &keystore);
204  builder.SetFee(10000000);
205  builder.AddTransparentInput(COutPoint(), scriptPubKey, 25000000);
206  builder.AddSaplingSpend(expsk, testNote.note, testNote.tree.root(), testNote.tree.witness());
207  auto tx = builder.Build().GetTxOrThrow();
208 
209  BOOST_CHECK_EQUAL(tx.vin.size(), 1);
210  BOOST_CHECK_EQUAL(tx.vout.size(), 0);
211  BOOST_CHECK_EQUAL(tx.sapData->vShieldedSpend.size(), 1);
212  BOOST_CHECK_EQUAL(tx.sapData->vShieldedOutput.size(), 1);
213  BOOST_CHECK_EQUAL(tx.sapData->valueBalance, -15000000);
214  }
215 
216  // Change to a Sapling address
217  {
218  auto builder = TransactionBuilder(consensusParams, &keystore);
219  builder.SetFee(10000000);
220  builder.AddTransparentInput(COutPoint(), scriptPubKey, 25000000);
221  builder.SendChangeTo(zChangeAddr, fvkOut.ovk);
222  auto tx = builder.Build().GetTxOrThrow();
223 
224  BOOST_CHECK_EQUAL(tx.vin.size(), 1);
225  BOOST_CHECK_EQUAL(tx.vout.size(), 0);
226  BOOST_CHECK_EQUAL(tx.sapData->vShieldedSpend.size(), 0);
227  BOOST_CHECK_EQUAL(tx.sapData->vShieldedOutput.size(), 1);
228  BOOST_CHECK_EQUAL(tx.sapData->valueBalance, -15000000);
229  }
230 
231  // Change to a transparent address
232  {
233  auto builder = TransactionBuilder(consensusParams, &keystore);
234  builder.SetFee(10000000);
235  builder.AddTransparentInput(COutPoint(), scriptPubKey, 25000000);
236  builder.SendChangeTo(taddr);
237  auto tx = builder.Build().GetTxOrThrow();
238 
239  BOOST_CHECK_EQUAL(tx.vin.size(), 1);
240  BOOST_CHECK_EQUAL(tx.vout.size(), 1);
241  BOOST_CHECK_EQUAL(tx.sapData->vShieldedSpend.size(), 0);
242  BOOST_CHECK_EQUAL(tx.sapData->vShieldedOutput.size(), 0);
243  BOOST_CHECK_EQUAL(tx.sapData->valueBalance, 0);
244  BOOST_CHECK_EQUAL(tx.vout[0].nValue, 15000000);
245  }
246 }
247 
249 {
250  auto consensusParams = Params().GetConsensus();
251 
252  // Generate dummy Sapling address
254  auto expsk = sk.expanded_spending_key();
255  auto fvk = sk.full_viewing_key();
256  auto pa = sk.default_address();
257 
258  auto testNote = GetTestSaplingNote(pa, 5 * COIN);
259 
260  // Configured fee (auto with SaplingOperation)
261  {
262  auto builder = TransactionBuilder(consensusParams);
263  builder.AddSaplingSpend(expsk, testNote.note, testNote.tree.root(), testNote.tree.witness());
264  builder.AddSaplingOutput(fvk.ovk, pa, 3 * COIN, {});
265 
266  builder.SetFee(COIN);
267  auto tx = builder.Build().GetTxOrThrow();
268 
269  BOOST_CHECK_EQUAL(tx.vin.size(), 0);
270  BOOST_CHECK_EQUAL(tx.vout.size(), 0);
271  BOOST_CHECK_EQUAL(tx.sapData->vShieldedSpend.size(), 1);
272  BOOST_CHECK_EQUAL(tx.sapData->vShieldedOutput.size(), 2);
273  BOOST_CHECK_EQUAL(tx.sapData->valueBalance, COIN);
274  }
275 }
276 
277 BOOST_AUTO_TEST_CASE(CheckSaplingTxVersion)
278 {
279  auto consensusParams = Params().GetConsensus();
280 
282  auto expsk = sk.expanded_spending_key();
283  auto pk = sk.default_address();
284 
285  // Cannot add Sapling outputs to a non-Sapling transaction
286  auto builder = TransactionBuilder(consensusParams);
287  builder.SetFee(10000000);
288  try {
289  builder.AddSaplingOutput(uint256(), pk, 12345, {});
290  } catch (std::runtime_error const & err) {
291  BOOST_CHECK_EQUAL(err.what(), std::string("TransactionBuilder cannot add Sapling output to pre-Sapling transaction"));
292  } catch(...) {
293  BOOST_CHECK_MESSAGE(false, "Expected std::runtime_error");
294  }
295 
296  // Cannot add Sapling spends to a non-Sapling transaction
297  libzcash::SaplingNote note(pk, 50000);
298  SaplingMerkleTree tree;
299  try {
300  builder.AddSaplingSpend(expsk, note, uint256(), tree.witness());
301  } catch (std::runtime_error const & err) {
302  BOOST_CHECK_EQUAL(err.what(), std::string("TransactionBuilder cannot add Sapling spend to pre-Sapling transaction"));
303  } catch(...) {
304  BOOST_CHECK_MESSAGE(false, "Expected std::runtime_error");
305  }
306 }
307 
308 
const CChainParams & Params()
Return the currently selected parameters.
Basic key store, that keeps keys in an address->secret map.
Definition: keystore.h:99
const Consensus::Params & GetConsensus() const
Definition: chainparams.h:72
An encapsulated private key.
Definition: key.h:30
CPubKey GetPubKey() const
Compute the public key from a private key.
Definition: key.cpp:186
An outpoint - a combination of a transaction hash and an index n into its vout.
Definition: transaction.h:72
CKeyID GetID() const
Get the KeyID of this public key (hash of its serialization)
Definition: pubkey.h:167
Serialized script, used inside transaction inputs and outputs.
Definition: script.h:381
Capture information about block/transaction validation.
Definition: validation.h:24
std::string GetRejectReason() const
Definition: validation.h:94
IncrementalWitness< Depth, Hash > witness() const
static SaplingSpendingKey random()
Definition: address.cpp:64
256-bit opaque blob.
Definition: uint256.h:138
BOOST_AUTO_TEST_SUITE_END()
bool ContextualCheckTransaction(const CTransaction &tx, CValidationState &state, const CChainParams &chainparams, const int nHeight, const bool isMined, bool isInitBlockDownload)
Check a transaction contextually against a set of consensus rules valid at a given block height.
#define BOOST_CHECK_THROW(stmt, excMatch)
Definition: object.cpp:19
#define BOOST_FIXTURE_TEST_SUITE(a, b)
Definition: object.cpp:14
#define BOOST_CHECK_EQUAL(v1, v2)
Definition: object.cpp:18
#define BOOST_CHECK(expr)
Definition: object.cpp:17
std::array< unsigned char, ZC_DIVERSIFIER_SIZE > diversifier_t
Definition: sapling.h:38
CScript GetScriptForDestination(const CTxDestination &dest)
Generate a PIVX scriptPubKey for the given CTxDestination.
Definition: standard.cpp:278
boost::variant< CNoDestination, CKeyID, CScriptID, CExchangeKeyID > CTxDestination
A txout script template with a specific destination.
Definition: standard.h:72
Regtest setup with sapling always active.
BOOST_AUTO_TEST_CASE(TransparentToSapling)
uint256 uint256S(const char *str)
Definition: uint256.h:157
CKey AddTestCKeyToKeyStore(CBasicKeyStore &keyStore, bool genNewKey)
Definition: utiltest.cpp:30
TestSaplingNote GetTestSaplingNote(const libzcash::SaplingPaymentAddress &pa, CAmount value)
Generate a dummy SaplingNote and a SaplingMerkleTree with that note's commitment.
Definition: utiltest.cpp:41