-
Notifications
You must be signed in to change notification settings - Fork 2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Have z_mergetoaddress use WalletTxBuilder #6569
Have z_mergetoaddress use WalletTxBuilder #6569
Conversation
6f5146f
to
eea5084
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Incomplete review, one blocking change to flush.
eea5084
to
a2679ae
Compare
ff0fdca
to
16815e0
Compare
b60bb03
to
186b40a
Compare
186b40a
to
218169e
Compare
218169e
to
ae799f0
Compare
Previously, `WalletTxBuilder` expected a vector of payments with known amounts. This meant that operations like `z_shieldcoinbase` and `z_mergetoaddress` needed to pre-calculate the amount to be sent to the target address. With ZIP 317 fees, this became harder to do. Now `WalletTxBuilder` accepts either a vector of payments or a single address (and optional memo) for the net amount to be paid to. This also converts `z_shieldcoinbase` to use this approach. `z_mergetoaddress` conversion is blocked by zcash#6569.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A few minor changes are needed. I am not using the Request changes
feature because I don't want to block the PR since I'm out starting at 4am tomorrow.
self.nodes[0].z_mergetoaddress, | ||
["ANY_TADDR", "ANY_SPROUT"], self.nodes[1].z_getnewaddress('sprout')) | ||
print("[taddr, Sprout] -> Sprout z_mergetoaddress tx rejected at Canopy activation on node 0") | ||
|
||
# Create z_mergetoaddress Sprout -> Sprout transaction on node 0. Should pass |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is no longer a condition we can exercise.
self.sync_all() | ||
|
||
# Create z_mergetoaddress Sprout -> Sprout transaction on node 1. Should pass | ||
merge_tx_2 = self.nodes[1].z_mergetoaddress(["ANY_SPROUT"], n2_sprout_addr) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also not a condition we can exercise.
strategy.AllowLinkingAccountAddresses()); | ||
} else if (allInputs.saplingNoteEntries.size() > 0) { | ||
ztxoSelector = pwalletMain->ZTXOSelectorForAddress( | ||
allInputs.saplingNoteEntries[0].address, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The addresses here aren't actually being used; it might be worth adding some detail to the comment above.
ae799f0
to
705b17b
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
flushing a comment
705b17b
to
bab7d9c
Compare
# Merging will fail because fee is larger than `-maxtxfee` | ||
assert_mergetoaddress_exception( | ||
"Insufficient funds, have 50.00, which is less than miners fee 999.00", | ||
"Fee (999.00 ZEC) is greater than the maximum fee allowed by this instance (0.10 ZEC). Run zcashd with `-maxtxfee` to adjust this limit.", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This now tests a different case. I think the original one is still testable, but I was having trouble putting together the conditions that lead to it.
There are also a couple other tests that now test the wrong thing and still need to be updated. (E.g., things failing because they try to send to Sprout, rather than because of the condition they intend to test.)
src/wallet/wallet_tx_builder.h
Outdated
@@ -403,11 +403,11 @@ class WalletTxBuilder { | |||
ResolveInputsAndPayments( | |||
CWallet& wallet, | |||
const ZTXOSelector& selector, | |||
SpendableInputs& spendable, | |||
SpendableInputs spendable, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We specifically need a copy here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think so. Basically, there are four paths through PrepareTransaction
– fixed fee vs conventional fee and vector<Payment>
vs NetAmountRecipient
. ResolveInputsAndPayments
is only used in the fixed fee & vector<Payment>
case, and it’s the only place we modify the provided SpendableInputs
. Edit: it’s also used in the conventional & vector<Payment>
case, but that one makes its own copy later, which is another reason to change it like I mention at the end of this comment.
Making this copy means that we can use const SpendableInputs&
in the rest of the code, which avoids incorrectly signaling that PrepareTransaction
meaningfully modifies the SpendableInputs&
, when the correct value is actually gotten from the TransactionEffects
result.
Although we could instead make this const SpendableInputs&
and explicitly copy it in the body of the method, before calling LimitToAmount
, which is what does the modification. Actually, I like that better, as it preserves the const
across more calls, so I’ll make that change.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, I made this change as a separate commit, as I’m not 100% sold on it.
Co-authored-by: Kris Nuttycombe <kris@nutty.land> Co-authored-by: Jack Grigg <jack@z.cash>
- add ZIP 317 support - address review comments - restructure `AsyncRPCOperation_mergetoaddress` (removing the just-added `prepare`)
This change takes `const SpendableInputs&` instead of copying it. It does an explicit copy later in more limited scope, avoiding an extra copy in the conventional fee side. However, this means both the reference and mutable copy are in scope at the same time, making it easy to use the wrong one by mistake.
bab7d9c
to
ac218b6
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Reviewed ac218b6.
This removes a btest case (can’t mix Sprout and Sapling inputs in z_mergetoaddress) that is no longer testable outside the RPC interface (and there is an rpc-test for it already). It also adds a new failure case in `WalletTxBuilder::PrepareTransaction` – we now check that the fee is in `MoneyRange`. This is generally protected by `AmountFromValue` in RPC calls, but we have tests that check it at this level, and better safe than sorry.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
utACK 3bf966e with a sigh about C++ variadics.
"The provided fee, %s, is invalid. Fees must be non-negative, and no greater " | ||
"than the total amount of ZEC that will ever be available.", | ||
DisplayMoney(err.fixedFee), | ||
DisplayMoney(MAX_MONEY))); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This second argument is unused? And strprintf
(tinyformat) doesn't complain? I guess it only complains if there are too few arguments...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also this message never appears in tests.
RPC_INVALID_PARAMETER, | ||
strprintf( | ||
"The provided fee, %s, is invalid. Fees must be non-negative, and no greater " | ||
"than the total amount of ZEC that will ever be available.", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"than the total amount of ZEC that will ever be available.", | |
"than the total amount of ZEC that will ever be available (%s).", |
mtx.joinSplitSig); | ||
|
||
// Sanity check | ||
if (!ed25519::verify( | ||
mtx.joinSplitPubKey, | ||
mtx.joinSplitSig, | ||
{dataToBeSigned.begin(), 32})) | ||
{ | ||
throw std::runtime_error("ed25519_verify failed"); | ||
} | ||
|
||
CTransaction rawTx(mtx); | ||
tx_ = rawTx; | ||
|
||
CDataStream ss(SER_NETWORK, PROTOCOL_VERSION); | ||
ss << rawTx; | ||
|
||
std::string encryptedNote1; | ||
std::string encryptedNote2; | ||
{ | ||
CDataStream ss2(SER_NETWORK, PROTOCOL_VERSION); | ||
ss2 << ((unsigned char)0x00); | ||
ss2 << jsdesc.ephemeralKey; | ||
ss2 << jsdesc.ciphertexts[0]; | ||
ss2 << ZCJoinSplit::h_sig(jsdesc.randomSeed, jsdesc.nullifiers, joinSplitPubKey_); | ||
|
||
encryptedNote1 = HexStr(ss2.begin(), ss2.end()); | ||
} | ||
{ | ||
CDataStream ss2(SER_NETWORK, PROTOCOL_VERSION); | ||
ss2 << ((unsigned char)0x01); | ||
ss2 << jsdesc.ephemeralKey; | ||
ss2 << jsdesc.ciphertexts[1]; | ||
ss2 << ZCJoinSplit::h_sig(jsdesc.randomSeed, jsdesc.nullifiers, joinSplitPubKey_); | ||
|
||
encryptedNote2 = HexStr(ss2.begin(), ss2.end()); | ||
} | ||
|
||
UniValue arrInputMap(UniValue::VARR); | ||
UniValue arrOutputMap(UniValue::VARR); | ||
for (size_t i = 0; i < ZC_NUM_JS_INPUTS; i++) { | ||
arrInputMap.push_back(static_cast<uint64_t>(inputMap[i])); | ||
} | ||
for (size_t i = 0; i < ZC_NUM_JS_OUTPUTS; i++) { | ||
arrOutputMap.push_back(static_cast<uint64_t>(outputMap[i])); | ||
} | ||
|
||
KeyIO keyIO(Params()); | ||
|
||
// !!! Payment disclosure START | ||
size_t js_index = tx_.vJoinSplit.size() - 1; | ||
uint256 placeholder; | ||
for (int i = 0; i < ZC_NUM_JS_OUTPUTS; i++) { | ||
uint8_t mapped_index = outputMap[i]; | ||
// placeholder for txid will be filled in later when tx has been finalized and signed. | ||
PaymentDisclosureKey pdKey = {placeholder, js_index, mapped_index}; | ||
JSOutput output = outputs[mapped_index]; | ||
libzcash::SproutPaymentAddress zaddr = output.addr; // randomized output | ||
PaymentDisclosureInfo pdInfo = {PAYMENT_DISCLOSURE_VERSION_EXPERIMENTAL, esk, joinSplitPrivKey_, zaddr}; | ||
paymentDisclosureData_.push_back(PaymentDisclosureKeyInfo(pdKey, pdInfo)); | ||
|
||
LogPrint("paymentdisclosure", "%s: Payment Disclosure: js=%d, n=%d, zaddr=%s\n", getId(), js_index, int(mapped_index), keyIO.EncodePaymentAddress(zaddr)); | ||
} | ||
// !!! Payment disclosure END | ||
|
||
UniValue obj(UniValue::VOBJ); | ||
obj.pushKV("encryptednote1", encryptedNote1); | ||
obj.pushKV("encryptednote2", encryptedNote2); | ||
obj.pushKV("rawtxn", HexStr(ss.begin(), ss.end())); | ||
obj.pushKV("inputmap", arrInputMap); | ||
obj.pushKV("outputmap", arrOutputMap); | ||
return obj; | ||
} | ||
|
||
std::array<unsigned char, ZC_MEMO_SIZE> AsyncRPCOperation_mergetoaddress::get_memo_from_hex_string(std::string s) | ||
{ | ||
std::array<unsigned char, ZC_MEMO_SIZE> memo = {{0x00}}; | ||
|
||
std::vector<unsigned char> rawMemo = ParseHex(s.c_str()); | ||
|
||
// If ParseHex comes across a non-hex char, it will stop but still return results so far. | ||
size_t slen = s.length(); | ||
if (slen % 2 != 0 || (slen > 0 && rawMemo.size() != slen / 2)) { | ||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Memo must be in hexadecimal format"); | ||
} | ||
|
||
if (rawMemo.size() > ZC_MEMO_SIZE) { | ||
throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Memo size of %d is too big, maximum allowed is %d", rawMemo.size(), ZC_MEMO_SIZE)); | ||
} | ||
|
||
// copy vector into boost array | ||
int lenMemo = rawMemo.size(); | ||
for (int i = 0; i < ZC_MEMO_SIZE && i < lenMemo; i++) { | ||
memo[i] = rawMemo[i]; | ||
LogPrint("zrpcunsafe", "%s: spending %s to send %s with fee %s\n", | ||
id, | ||
FormatMoney(payments.Total()), | ||
FormatMoney(spendable.Total()), | ||
FormatMoney(effects.GetFee())); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider:
bool fullyTransparent =
spendable.sproutNoteEntries.empty()
&& spendable.saplingNoteEntries.empty()
&& spendable.orchardNoteMetadata.empty()
&& !sendsToShielded;
LogPrint(fullyTransparent ? "zrpc" : "zrpcunsafe",
"%s: spending %s to send %s with fee %s\n",
id,
FormatMoney(payments.Total()),
FormatMoney(spendable.Total()),
FormatMoney(effects.GetFee()));
} | ||
WalletTxBuilder builder(Params(), minRelayTxFee); | ||
auto selector = CWallet::LegacyTransparentZTXOSelector( | ||
true, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
true, | |
true, // only select inputs we can spend |
@@ -5409,10 +5430,10 @@ UniValue z_mergetoaddress(const UniValue& params, bool fHelp) | |||
|
|||
// Keep track of addresses to spot duplicates | |||
std::set<std::string> setAddress; | |||
std::set<ReceiverType> receiverTypes; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove receiverTypes
since it isn't used.
@@ -5576,9 +5574,7 @@ UniValue z_mergetoaddress(const UniValue& params, bool fHelp) | |||
|
|||
unsigned int max_tx_size = saplingActive ? MAX_TX_SIZE_AFTER_SAPLING : MAX_TX_SIZE_BEFORE_SAPLING; | |||
size_t estimatedTxSize = 200; // tx overhead + wiggle room |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
size_t estimatedTxSize = 200; // tx overhead + wiggle room | |
size_t estimatedTxSize = MIN_TX_COST; // tx overhead + wiggle room |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No. We still don't support UAs in z_mergetoaddress
, and the size of the Sprout or Sapling shielded output is accounted for right after this.
if (isToSaplingZaddr) { | ||
estimatedTxSize += OUTPUTDESCRIPTION_SIZE; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if (isToSaplingZaddr) { | |
estimatedTxSize += OUTPUTDESCRIPTION_SIZE; | |
} |
RPC_INVALID_PARAMETER, "Cannot create shielded transactions before Sapling has activated"); | ||
} | ||
// The privacy policy is determined early so as to be able to use it | ||
// for selector construction. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Explain the LegacyCompat case.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
utACK with suggestions.
This also supports ZIP 317.
Fixes #2979 and fixes #5685.