Merge pull request #6243 from sellout/extract-memo
Factor out memo parsing from asyncrpcoperation_sendmany
This commit is contained in:
commit
26df6834ca
|
@ -323,12 +323,14 @@ BITCOIN_CORE_H = \
|
||||||
wallet/asyncrpcoperation_shieldcoinbase.h \
|
wallet/asyncrpcoperation_shieldcoinbase.h \
|
||||||
wallet/crypter.h \
|
wallet/crypter.h \
|
||||||
wallet/db.h \
|
wallet/db.h \
|
||||||
|
wallet/memo.h \
|
||||||
wallet/orchard.h \
|
wallet/orchard.h \
|
||||||
wallet/paymentdisclosure.h \
|
wallet/paymentdisclosure.h \
|
||||||
wallet/paymentdisclosuredb.h \
|
wallet/paymentdisclosuredb.h \
|
||||||
wallet/rpcwallet.h \
|
wallet/rpcwallet.h \
|
||||||
wallet/wallet.h \
|
wallet/wallet.h \
|
||||||
wallet/walletdb.h \
|
wallet/walletdb.h \
|
||||||
|
wallet/wallet_tx_builder.h \
|
||||||
warnings.h \
|
warnings.h \
|
||||||
zmq/zmqabstractnotifier.h \
|
zmq/zmqabstractnotifier.h \
|
||||||
zmq/zmqconfig.h\
|
zmq/zmqconfig.h\
|
||||||
|
|
|
@ -254,8 +254,8 @@ TransactionBuilder::TransactionBuilder(
|
||||||
const Consensus::Params& consensusParams,
|
const Consensus::Params& consensusParams,
|
||||||
int nHeight,
|
int nHeight,
|
||||||
std::optional<uint256> orchardAnchor,
|
std::optional<uint256> orchardAnchor,
|
||||||
CKeyStore* keystore,
|
const CKeyStore* keystore,
|
||||||
CCoinsViewCache* coinsView,
|
const CCoinsViewCache* coinsView,
|
||||||
CCriticalSection* cs_coinsView) :
|
CCriticalSection* cs_coinsView) :
|
||||||
consensusParams(consensusParams),
|
consensusParams(consensusParams),
|
||||||
nHeight(nHeight),
|
nHeight(nHeight),
|
||||||
|
|
|
@ -290,8 +290,8 @@ public:
|
||||||
const Consensus::Params& consensusParams,
|
const Consensus::Params& consensusParams,
|
||||||
int nHeight,
|
int nHeight,
|
||||||
std::optional<uint256> orchardAnchor,
|
std::optional<uint256> orchardAnchor,
|
||||||
CKeyStore* keyStore = nullptr,
|
const CKeyStore* keyStore = nullptr,
|
||||||
CCoinsViewCache* coinsView = nullptr,
|
const CCoinsViewCache* coinsView = nullptr,
|
||||||
CCriticalSection* cs_coinsView = nullptr);
|
CCriticalSection* cs_coinsView = nullptr);
|
||||||
|
|
||||||
// TransactionBuilder should never be copied
|
// TransactionBuilder should never be copied
|
||||||
|
|
|
@ -30,6 +30,7 @@
|
||||||
#include "zcash/IncrementalMerkleTree.hpp"
|
#include "zcash/IncrementalMerkleTree.hpp"
|
||||||
#include "miner.h"
|
#include "miner.h"
|
||||||
#include "wallet/paymentdisclosuredb.h"
|
#include "wallet/paymentdisclosuredb.h"
|
||||||
|
#include "wallet/wallet_tx_builder.h"
|
||||||
|
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
@ -46,7 +47,7 @@ using namespace libzcash;
|
||||||
AsyncRPCOperation_sendmany::AsyncRPCOperation_sendmany(
|
AsyncRPCOperation_sendmany::AsyncRPCOperation_sendmany(
|
||||||
TransactionBuilder builder,
|
TransactionBuilder builder,
|
||||||
ZTXOSelector ztxoSelector,
|
ZTXOSelector ztxoSelector,
|
||||||
std::vector<SendManyRecipient> recipients,
|
std::vector<ResolvedPayment> recipients,
|
||||||
int minDepth,
|
int minDepth,
|
||||||
unsigned int anchorDepth,
|
unsigned int anchorDepth,
|
||||||
TransactionStrategy strategy,
|
TransactionStrategy strategy,
|
||||||
|
@ -64,7 +65,7 @@ AsyncRPCOperation_sendmany::AsyncRPCOperation_sendmany(
|
||||||
sendFromAccount_ = pwalletMain->FindAccountForSelector(ztxoSelector_).value_or(ZCASH_LEGACY_ACCOUNT);
|
sendFromAccount_ = pwalletMain->FindAccountForSelector(ztxoSelector_).value_or(ZCASH_LEGACY_ACCOUNT);
|
||||||
|
|
||||||
// Determine the target totals and recipient pools
|
// Determine the target totals and recipient pools
|
||||||
for (const SendManyRecipient& recipient : recipients_) {
|
for (const ResolvedPayment& recipient : recipients_) {
|
||||||
std::visit(match {
|
std::visit(match {
|
||||||
[&](const CKeyID& addr) {
|
[&](const CKeyID& addr) {
|
||||||
transparentRecipients_ += 1;
|
transparentRecipients_ += 1;
|
||||||
|
@ -559,15 +560,14 @@ uint256 AsyncRPCOperation_sendmany::main_impl() {
|
||||||
builder_.AddTransparentOutput(scriptId, r.amount);
|
builder_.AddTransparentOutput(scriptId, r.amount);
|
||||||
},
|
},
|
||||||
[&](const libzcash::SaplingPaymentAddress& addr) {
|
[&](const libzcash::SaplingPaymentAddress& addr) {
|
||||||
auto value = r.amount;
|
builder_.AddSaplingOutput(
|
||||||
auto memo = get_memo_from_hex_string(r.memo.value_or(""));
|
ovks.second, addr, r.amount,
|
||||||
|
r.memo.has_value() ? r.memo.value().ToBytes() : Memo::NoMemo().ToBytes());
|
||||||
builder_.AddSaplingOutput(ovks.second, addr, value, memo);
|
|
||||||
},
|
},
|
||||||
[&](const libzcash::OrchardRawAddress& addr) {
|
[&](const libzcash::OrchardRawAddress& addr) {
|
||||||
auto value = r.amount;
|
builder_.AddOrchardOutput(
|
||||||
auto memo = r.memo.has_value() ? std::optional(get_memo_from_hex_string(r.memo.value())) : std::nullopt;
|
ovks.second, addr, r.amount,
|
||||||
builder_.AddOrchardOutput(ovks.second, addr, value, memo);
|
r.memo.has_value() ? std::optional(r.memo.value().ToBytes()) : std::nullopt);
|
||||||
}
|
}
|
||||||
}, r.address);
|
}, r.address);
|
||||||
}
|
}
|
||||||
|
@ -770,32 +770,6 @@ CAmount AsyncRPCOperation_sendmany::DefaultDustThreshold() {
|
||||||
return txout.GetDustThreshold(minRelayTxFee);
|
return txout.GetDustThreshold(minRelayTxFee);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::array<unsigned char, ZC_MEMO_SIZE> AsyncRPCOperation_sendmany::get_memo_from_hex_string(std::string s) {
|
|
||||||
// initialize to default memo (no_memo), see section 5.5 of the protocol spec
|
|
||||||
std::array<unsigned char, ZC_MEMO_SIZE> memo = {{0xF6}};
|
|
||||||
|
|
||||||
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 bytes is too big, maximum allowed is %d bytes", 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];
|
|
||||||
}
|
|
||||||
return memo;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Override getStatus() to append the operation's input parameters to the default status object.
|
* Override getStatus() to append the operation's input parameters to the default status object.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -13,6 +13,7 @@
|
||||||
#include "zcash/Address.hpp"
|
#include "zcash/Address.hpp"
|
||||||
#include "wallet.h"
|
#include "wallet.h"
|
||||||
#include "wallet/paymentdisclosure.h"
|
#include "wallet/paymentdisclosure.h"
|
||||||
|
#include "wallet/wallet_tx_builder.h"
|
||||||
|
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
|
@ -25,15 +26,6 @@
|
||||||
|
|
||||||
using namespace libzcash;
|
using namespace libzcash;
|
||||||
|
|
||||||
class SendManyRecipient : public RecipientMapping {
|
|
||||||
public:
|
|
||||||
CAmount amount;
|
|
||||||
std::optional<std::string> memo;
|
|
||||||
|
|
||||||
SendManyRecipient(std::optional<libzcash::UnifiedAddress> ua_, libzcash::RecipientAddress address_, CAmount amount_, std::optional<std::string> memo_) :
|
|
||||||
RecipientMapping(ua_, address_), amount(amount_), memo(memo_) {}
|
|
||||||
};
|
|
||||||
|
|
||||||
class TxOutputAmounts {
|
class TxOutputAmounts {
|
||||||
public:
|
public:
|
||||||
CAmount t_outputs_total{0};
|
CAmount t_outputs_total{0};
|
||||||
|
@ -46,7 +38,7 @@ public:
|
||||||
AsyncRPCOperation_sendmany(
|
AsyncRPCOperation_sendmany(
|
||||||
TransactionBuilder builder,
|
TransactionBuilder builder,
|
||||||
ZTXOSelector ztxoSelector,
|
ZTXOSelector ztxoSelector,
|
||||||
std::vector<SendManyRecipient> recipients,
|
std::vector<ResolvedPayment> recipients,
|
||||||
int minDepth,
|
int minDepth,
|
||||||
unsigned int anchorDepth,
|
unsigned int anchorDepth,
|
||||||
TransactionStrategy strategy,
|
TransactionStrategy strategy,
|
||||||
|
@ -71,7 +63,7 @@ private:
|
||||||
|
|
||||||
TransactionBuilder builder_;
|
TransactionBuilder builder_;
|
||||||
ZTXOSelector ztxoSelector_;
|
ZTXOSelector ztxoSelector_;
|
||||||
std::vector<SendManyRecipient> recipients_;
|
std::vector<ResolvedPayment> recipients_;
|
||||||
int mindepth_{1};
|
int mindepth_{1};
|
||||||
unsigned int anchordepth_{nAnchorConfirmations};
|
unsigned int anchordepth_{nAnchorConfirmations};
|
||||||
CAmount fee_;
|
CAmount fee_;
|
||||||
|
@ -93,8 +85,6 @@ private:
|
||||||
|
|
||||||
static CAmount DefaultDustThreshold();
|
static CAmount DefaultDustThreshold();
|
||||||
|
|
||||||
static std::array<unsigned char, ZC_MEMO_SIZE> get_memo_from_hex_string(std::string s);
|
|
||||||
|
|
||||||
uint256 main_impl();
|
uint256 main_impl();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -105,10 +95,6 @@ public:
|
||||||
|
|
||||||
TEST_FRIEND_AsyncRPCOperation_sendmany(std::shared_ptr<AsyncRPCOperation_sendmany> ptr) : delegate(ptr) {}
|
TEST_FRIEND_AsyncRPCOperation_sendmany(std::shared_ptr<AsyncRPCOperation_sendmany> ptr) : delegate(ptr) {}
|
||||||
|
|
||||||
std::array<unsigned char, ZC_MEMO_SIZE> get_memo_from_hex_string(std::string s) {
|
|
||||||
return delegate->get_memo_from_hex_string(s);
|
|
||||||
}
|
|
||||||
|
|
||||||
uint256 main_impl() {
|
uint256 main_impl() {
|
||||||
return delegate->main_impl();
|
return delegate->main_impl();
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
#include "wallet/asyncrpcoperation_mergetoaddress.h"
|
#include "wallet/asyncrpcoperation_mergetoaddress.h"
|
||||||
#include "wallet/asyncrpcoperation_shieldcoinbase.h"
|
#include "wallet/asyncrpcoperation_shieldcoinbase.h"
|
||||||
#include "wallet/asyncrpcoperation_sendmany.h"
|
#include "wallet/asyncrpcoperation_sendmany.h"
|
||||||
|
#include "wallet/memo.h"
|
||||||
#include "zcash/JoinSplit.hpp"
|
#include "zcash/JoinSplit.hpp"
|
||||||
|
|
||||||
#include <librustzcash.h>
|
#include <librustzcash.h>
|
||||||
|
@ -287,7 +288,7 @@ TEST(WalletRPCTests, RPCZsendmanyTaddrToSapling)
|
||||||
mtx = CreateNewContextualCMutableTransaction(consensusParams, nextBlockHeight, false);
|
mtx = CreateNewContextualCMutableTransaction(consensusParams, nextBlockHeight, false);
|
||||||
|
|
||||||
auto selector = pwalletMain->ZTXOSelectorForAddress(taddr, true, false).value();
|
auto selector = pwalletMain->ZTXOSelectorForAddress(taddr, true, false).value();
|
||||||
std::vector<SendManyRecipient> recipients = { SendManyRecipient(std::nullopt, pa, 1*COIN, "ABCD") };
|
std::vector<ResolvedPayment> recipients = { ResolvedPayment(std::nullopt, pa, 1*COIN, Memo::FromHexOrThrow("ABCD")) };
|
||||||
TransactionStrategy strategy(PrivacyPolicy::AllowRevealedSenders);
|
TransactionStrategy strategy(PrivacyPolicy::AllowRevealedSenders);
|
||||||
std::shared_ptr<AsyncRPCOperation> operation(new AsyncRPCOperation_sendmany(std::move(builder), selector, recipients, 0, 0, strategy));
|
std::shared_ptr<AsyncRPCOperation> operation(new AsyncRPCOperation_sendmany(std::move(builder), selector, recipients, 0, 0, strategy));
|
||||||
std::shared_ptr<AsyncRPCOperation_sendmany> ptr = std::dynamic_pointer_cast<AsyncRPCOperation_sendmany> (operation);
|
std::shared_ptr<AsyncRPCOperation_sendmany> ptr = std::dynamic_pointer_cast<AsyncRPCOperation_sendmany> (operation);
|
||||||
|
|
|
@ -0,0 +1,87 @@
|
||||||
|
// 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_WALLET_MEMO_H
|
||||||
|
#define ZCASH_WALLET_MEMO_H
|
||||||
|
|
||||||
|
#include "util/strencodings.h"
|
||||||
|
#include "zcash/Zcash.h"
|
||||||
|
#include "tinyformat.h"
|
||||||
|
|
||||||
|
|
||||||
|
enum class MemoError {
|
||||||
|
HexDecodeError,
|
||||||
|
MemoTooLong
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef std::array<unsigned char, ZC_MEMO_SIZE> MemoBytes;
|
||||||
|
|
||||||
|
class Memo {
|
||||||
|
private:
|
||||||
|
MemoBytes value;
|
||||||
|
public:
|
||||||
|
// initialize to default memo (no_memo), see section 5.5 of the protocol spec
|
||||||
|
Memo(): value({{0xF6}}) { }
|
||||||
|
Memo(MemoBytes value): value(value) {}
|
||||||
|
|
||||||
|
static Memo NoMemo() {
|
||||||
|
return Memo();
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::variant<MemoError, Memo> FromHex(const std::string& memoHex) {
|
||||||
|
Memo result;
|
||||||
|
std::vector<unsigned char> rawMemo = ParseHex(memoHex.c_str());
|
||||||
|
|
||||||
|
// If ParseHex comes across a non-hex char, it will stop but still return results so far.
|
||||||
|
size_t slen = memoHex.length();
|
||||||
|
if (slen != rawMemo.size() * 2) {
|
||||||
|
return MemoError::HexDecodeError;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rawMemo.size() > ZC_MEMO_SIZE) {
|
||||||
|
return MemoError::MemoTooLong;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto rest = std::copy(rawMemo.begin(), rawMemo.end(), result.value.begin());
|
||||||
|
std::fill(rest, result.value.end(), 0);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Memo FromHexOrThrow(const std::string& memoHex) {
|
||||||
|
return std::visit(match {
|
||||||
|
[&](Memo memo) {
|
||||||
|
return memo;
|
||||||
|
},
|
||||||
|
[&](MemoError err) {
|
||||||
|
switch (err) {
|
||||||
|
case MemoError::HexDecodeError:
|
||||||
|
throw std::runtime_error(
|
||||||
|
"Invalid parameter, expected memo data in hexadecimal format.");
|
||||||
|
case MemoError::MemoTooLong:
|
||||||
|
throw std::runtime_error(strprintf(
|
||||||
|
"Invalid parameter, memo is longer than the maximum allowed %d characters.",
|
||||||
|
ZC_MEMO_SIZE));
|
||||||
|
default:
|
||||||
|
assert(false);
|
||||||
|
}
|
||||||
|
// unreachable, but the compiler can't tell
|
||||||
|
return Memo::NoMemo();
|
||||||
|
}
|
||||||
|
}, Memo::FromHex(memoHex));
|
||||||
|
}
|
||||||
|
|
||||||
|
// This copies, because if it returns a reference to the underlying value,
|
||||||
|
// using an idiom like `Memo::FromHexOrThrow("abcd").ToBytes()` is unsafe
|
||||||
|
// and can result in pointing to memory that has been deallocated.
|
||||||
|
MemoBytes ToBytes() const {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string ToHex() const {
|
||||||
|
return HexStr(value);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
|
@ -41,6 +41,7 @@
|
||||||
#include "wallet/asyncrpcoperation_saplingmigration.h"
|
#include "wallet/asyncrpcoperation_saplingmigration.h"
|
||||||
#include "wallet/asyncrpcoperation_sendmany.h"
|
#include "wallet/asyncrpcoperation_sendmany.h"
|
||||||
#include "wallet/asyncrpcoperation_shieldcoinbase.h"
|
#include "wallet/asyncrpcoperation_shieldcoinbase.h"
|
||||||
|
#include "wallet/wallet_tx_builder.h"
|
||||||
|
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
|
@ -4988,7 +4989,7 @@ UniValue z_getoperationstatus_IMPL(const UniValue& params, bool fRemoveFinishedO
|
||||||
|
|
||||||
size_t EstimateTxSize(
|
size_t EstimateTxSize(
|
||||||
const ZTXOSelector& ztxoSelector,
|
const ZTXOSelector& ztxoSelector,
|
||||||
const std::vector<SendManyRecipient>& recipients,
|
const std::vector<ResolvedPayment>& recipients,
|
||||||
int nextBlockHeight) {
|
int nextBlockHeight) {
|
||||||
CMutableTransaction mtx;
|
CMutableTransaction mtx;
|
||||||
mtx.fOverwintered = true;
|
mtx.fOverwintered = true;
|
||||||
|
@ -5002,7 +5003,7 @@ size_t EstimateTxSize(
|
||||||
size_t txsize = 0;
|
size_t txsize = 0;
|
||||||
size_t taddrRecipientCount = 0;
|
size_t taddrRecipientCount = 0;
|
||||||
size_t orchardRecipientCount = 0;
|
size_t orchardRecipientCount = 0;
|
||||||
for (const SendManyRecipient& recipient : recipients) {
|
for (const ResolvedPayment& recipient : recipients) {
|
||||||
std::visit(match {
|
std::visit(match {
|
||||||
[&](const CKeyID&) {
|
[&](const CKeyID&) {
|
||||||
taddrRecipientCount += 1;
|
taddrRecipientCount += 1;
|
||||||
|
@ -5207,7 +5208,7 @@ UniValue z_sendmany(const UniValue& params, bool fHelp)
|
||||||
}
|
}
|
||||||
|
|
||||||
std::set<RecipientAddress> recipientAddrs;
|
std::set<RecipientAddress> recipientAddrs;
|
||||||
std::vector<SendManyRecipient> recipients;
|
std::vector<ResolvedPayment> recipients;
|
||||||
CAmount nTotalOut = 0;
|
CAmount nTotalOut = 0;
|
||||||
size_t nOrchardOutputs = 0;
|
size_t nOrchardOutputs = 0;
|
||||||
for (const UniValue& o : outputs.getValues()) {
|
for (const UniValue& o : outputs.getValues()) {
|
||||||
|
@ -5249,18 +5250,34 @@ UniValue z_sendmany(const UniValue& params, bool fHelp)
|
||||||
}
|
}
|
||||||
|
|
||||||
UniValue memoValue = find_value(o, "memo");
|
UniValue memoValue = find_value(o, "memo");
|
||||||
std::optional<std::string> memo;
|
std::optional<Memo> memo;
|
||||||
if (!memoValue.isNull()) {
|
if (!memoValue.isNull()) {
|
||||||
memo = memoValue.get_str();
|
auto memoHex = memoValue.get_str();
|
||||||
if (!std::visit(libzcash::IsShieldedRecipient(), addr.value())) {
|
if (!std::visit(libzcash::IsShieldedRecipient(), addr.value())) {
|
||||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, memos cannot be sent to transparent addresses.");
|
throw JSONRPCError(
|
||||||
} else if (!IsHex(memo.value())) {
|
RPC_INVALID_PARAMETER,
|
||||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, expected memo data in hexadecimal format.");
|
"Invalid parameter, memos cannot be sent to transparent addresses.");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (memo.value().length() > ZC_MEMO_SIZE*2) {
|
std::visit(match {
|
||||||
throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Invalid parameter, size of memo is larger than maximum allowed %d", ZC_MEMO_SIZE ));
|
[&](MemoError err) {
|
||||||
}
|
switch (err) {
|
||||||
|
case MemoError::HexDecodeError:
|
||||||
|
throw JSONRPCError(
|
||||||
|
RPC_INVALID_PARAMETER,
|
||||||
|
"Invalid parameter, expected memo data in hexadecimal format.");
|
||||||
|
case MemoError::MemoTooLong:
|
||||||
|
throw JSONRPCError(
|
||||||
|
RPC_INVALID_PARAMETER,
|
||||||
|
strprintf("Invalid parameter, size of memo is larger than maximum allowed %d", ZC_MEMO_SIZE ));
|
||||||
|
default:
|
||||||
|
assert(false);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[&](Memo result) {
|
||||||
|
memo = result;
|
||||||
|
}
|
||||||
|
}, Memo::FromHex(memoHex));
|
||||||
}
|
}
|
||||||
|
|
||||||
UniValue av = find_value(o, "amount");
|
UniValue av = find_value(o, "amount");
|
||||||
|
@ -5292,7 +5309,7 @@ UniValue z_sendmany(const UniValue& params, bool fHelp)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
recipients.push_back(SendManyRecipient(ua, addr.value(), nAmount, memo));
|
recipients.push_back(ResolvedPayment(ua, addr.value(), nAmount, memo));
|
||||||
nTotalOut += nAmount;
|
nTotalOut += nAmount;
|
||||||
}
|
}
|
||||||
if (recipients.empty()) {
|
if (recipients.empty()) {
|
||||||
|
|
|
@ -20,6 +20,7 @@
|
||||||
#include "wallet/asyncrpcoperation_mergetoaddress.h"
|
#include "wallet/asyncrpcoperation_mergetoaddress.h"
|
||||||
#include "wallet/asyncrpcoperation_sendmany.h"
|
#include "wallet/asyncrpcoperation_sendmany.h"
|
||||||
#include "wallet/asyncrpcoperation_shieldcoinbase.h"
|
#include "wallet/asyncrpcoperation_shieldcoinbase.h"
|
||||||
|
#include "wallet/memo.h"
|
||||||
|
|
||||||
#include "init.h"
|
#include "init.h"
|
||||||
#include "util/test.h"
|
#include "util/test.h"
|
||||||
|
@ -1237,7 +1238,7 @@ BOOST_AUTO_TEST_CASE(rpc_z_sendmany_internals)
|
||||||
{
|
{
|
||||||
auto selector = pwalletMain->ZTXOSelectorForAddress(taddr1, true, false).value();
|
auto selector = pwalletMain->ZTXOSelectorForAddress(taddr1, true, false).value();
|
||||||
TransactionBuilder builder(consensusParams, nHeight + 1, std::nullopt, pwalletMain);
|
TransactionBuilder builder(consensusParams, nHeight + 1, std::nullopt, pwalletMain);
|
||||||
std::vector<SendManyRecipient> recipients = { SendManyRecipient(std::nullopt, zaddr1, 100*COIN, "DEADBEEF") };
|
std::vector<ResolvedPayment> recipients = { ResolvedPayment(std::nullopt, zaddr1, 100*COIN, Memo::FromHexOrThrow("DEADBEEF")) };
|
||||||
TransactionStrategy strategy;
|
TransactionStrategy strategy;
|
||||||
std::shared_ptr<AsyncRPCOperation> operation(new AsyncRPCOperation_sendmany(std::move(builder), selector, recipients, 1, 1, strategy));
|
std::shared_ptr<AsyncRPCOperation> operation(new AsyncRPCOperation_sendmany(std::move(builder), selector, recipients, 1, 1, strategy));
|
||||||
operation->main();
|
operation->main();
|
||||||
|
@ -1250,7 +1251,7 @@ BOOST_AUTO_TEST_CASE(rpc_z_sendmany_internals)
|
||||||
{
|
{
|
||||||
auto selector = pwalletMain->ZTXOSelectorForAddress(zaddr1, true, false).value();
|
auto selector = pwalletMain->ZTXOSelectorForAddress(zaddr1, true, false).value();
|
||||||
TransactionBuilder builder(consensusParams, nHeight + 1, std::nullopt, pwalletMain);
|
TransactionBuilder builder(consensusParams, nHeight + 1, std::nullopt, pwalletMain);
|
||||||
std::vector<SendManyRecipient> recipients = { SendManyRecipient(std::nullopt, taddr1, 100*COIN, "DEADBEEF") };
|
std::vector<ResolvedPayment> recipients = { ResolvedPayment(std::nullopt, taddr1, 100*COIN, Memo::FromHexOrThrow("DEADBEEF")) };
|
||||||
TransactionStrategy strategy(PrivacyPolicy::AllowRevealedRecipients);
|
TransactionStrategy strategy(PrivacyPolicy::AllowRevealedRecipients);
|
||||||
std::shared_ptr<AsyncRPCOperation> operation(new AsyncRPCOperation_sendmany(std::move(builder), selector, recipients, 1, 1, strategy));
|
std::shared_ptr<AsyncRPCOperation> operation(new AsyncRPCOperation_sendmany(std::move(builder), selector, recipients, 1, 1, strategy));
|
||||||
operation->main();
|
operation->main();
|
||||||
|
@ -1258,59 +1259,36 @@ BOOST_AUTO_TEST_CASE(rpc_z_sendmany_internals)
|
||||||
std::string msg = operation->getErrorMessage();
|
std::string msg = operation->getErrorMessage();
|
||||||
BOOST_CHECK(msg.find("Insufficient funds: have 0.00") != string::npos);
|
BOOST_CHECK(msg.find("Insufficient funds: have 0.00") != string::npos);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// get_memo_from_hex_string())
|
BOOST_AUTO_TEST_CASE(memo_hex_parsing) {
|
||||||
{
|
std::string memo = "DEADBEEF";
|
||||||
auto selector = pwalletMain->ZTXOSelectorForAddress(zaddr1, true, false).value();
|
MemoBytes memoBytes = Memo::FromHexOrThrow(memo).ToBytes();
|
||||||
TransactionBuilder builder(consensusParams, nHeight + 1, std::nullopt, pwalletMain);
|
BOOST_CHECK_EQUAL(memoBytes[0], 0xDE);
|
||||||
std::vector<SendManyRecipient> recipients = { SendManyRecipient(std::nullopt, zaddr1, 100*COIN, "DEADBEEF") };
|
BOOST_CHECK_EQUAL(memoBytes[1], 0xAD);
|
||||||
TransactionStrategy strategy;
|
BOOST_CHECK_EQUAL(memoBytes[2], 0xBE);
|
||||||
std::shared_ptr<AsyncRPCOperation> operation(new AsyncRPCOperation_sendmany(std::move(builder), selector, recipients, 1, 1, strategy));
|
BOOST_CHECK_EQUAL(memoBytes[3], 0xEF);
|
||||||
std::shared_ptr<AsyncRPCOperation_sendmany> ptr = std::dynamic_pointer_cast<AsyncRPCOperation_sendmany> (operation);
|
for (int i=4; i<ZC_MEMO_SIZE; i++) {
|
||||||
TEST_FRIEND_AsyncRPCOperation_sendmany proxy(ptr);
|
BOOST_CHECK_EQUAL(memoBytes[i], 0x00); // zero padding
|
||||||
|
|
||||||
std::string memo = "DEADBEEF";
|
|
||||||
std::array<unsigned char, ZC_MEMO_SIZE> array = proxy.get_memo_from_hex_string(memo);
|
|
||||||
BOOST_CHECK_EQUAL(array[0], 0xDE);
|
|
||||||
BOOST_CHECK_EQUAL(array[1], 0xAD);
|
|
||||||
BOOST_CHECK_EQUAL(array[2], 0xBE);
|
|
||||||
BOOST_CHECK_EQUAL(array[3], 0xEF);
|
|
||||||
for (int i=4; i<ZC_MEMO_SIZE; i++) {
|
|
||||||
BOOST_CHECK_EQUAL(array[i], 0x00); // zero padding
|
|
||||||
}
|
|
||||||
|
|
||||||
// memo is longer than allowed
|
|
||||||
std::vector<char> v (2 * (ZC_MEMO_SIZE+1));
|
|
||||||
std::fill(v.begin(),v.end(), 'A');
|
|
||||||
std::string bigmemo(v.begin(), v.end());
|
|
||||||
|
|
||||||
try {
|
|
||||||
proxy.get_memo_from_hex_string(bigmemo);
|
|
||||||
} catch (const UniValue& objError) {
|
|
||||||
BOOST_CHECK( find_error(objError, "too big"));
|
|
||||||
}
|
|
||||||
|
|
||||||
// invalid hexadecimal string
|
|
||||||
std::fill(v.begin(),v.end(), '@'); // not a hex character
|
|
||||||
std::string badmemo(v.begin(), v.end());
|
|
||||||
|
|
||||||
try {
|
|
||||||
proxy.get_memo_from_hex_string(badmemo);
|
|
||||||
} catch (const UniValue& objError) {
|
|
||||||
BOOST_CHECK( find_error(objError, "hexadecimal format"));
|
|
||||||
}
|
|
||||||
|
|
||||||
// odd length hexadecimal string
|
|
||||||
std::fill(v.begin(),v.end(), 'A');
|
|
||||||
v.resize(v.size() - 1);
|
|
||||||
assert(v.size() %2 == 1); // odd length
|
|
||||||
std::string oddmemo(v.begin(), v.end());
|
|
||||||
try {
|
|
||||||
proxy.get_memo_from_hex_string(oddmemo);
|
|
||||||
} catch (const UniValue& objError) {
|
|
||||||
BOOST_CHECK( find_error(objError, "hexadecimal format"));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// memo is longer than allowed
|
||||||
|
std::vector<char> v (2 * (ZC_MEMO_SIZE+1));
|
||||||
|
std::fill(v.begin(),v.end(), 'A');
|
||||||
|
std::string bigmemo(v.begin(), v.end());
|
||||||
|
BOOST_CHECK(std::get<MemoError>(Memo::FromHex(bigmemo)) == MemoError::MemoTooLong);
|
||||||
|
|
||||||
|
// invalid hexadecimal string
|
||||||
|
std::fill(v.begin(),v.end(), '@'); // not a hex character
|
||||||
|
std::string badmemo(v.begin(), v.end());
|
||||||
|
BOOST_CHECK(std::get<MemoError>(Memo::FromHex(badmemo)) == MemoError::HexDecodeError);
|
||||||
|
|
||||||
|
// odd length hexadecimal string
|
||||||
|
std::fill(v.begin(),v.end(), 'A');
|
||||||
|
v.resize(v.size() - 1);
|
||||||
|
assert(v.size() %2 == 1); // odd length
|
||||||
|
std::string oddmemo(v.begin(), v.end());
|
||||||
|
BOOST_CHECK(std::get<MemoError>(Memo::FromHex(oddmemo)) == MemoError::HexDecodeError);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
|
@ -7851,7 +7851,7 @@ bool ZTXOSelector::SelectsOrchard() const {
|
||||||
bool SpendableInputs::LimitToAmount(
|
bool SpendableInputs::LimitToAmount(
|
||||||
const CAmount amountRequired,
|
const CAmount amountRequired,
|
||||||
const CAmount dustThreshold,
|
const CAmount dustThreshold,
|
||||||
std::set<OutputPool> recipientPools)
|
const std::set<OutputPool>& recipientPools)
|
||||||
{
|
{
|
||||||
assert(amountRequired >= 0 && dustThreshold > 0);
|
assert(amountRequired >= 0 && dustThreshold > 0);
|
||||||
// Calling this method twice is a programming error.
|
// Calling this method twice is a programming error.
|
||||||
|
|
|
@ -906,24 +906,48 @@ public:
|
||||||
* This method must only be called once.
|
* This method must only be called once.
|
||||||
*/
|
*/
|
||||||
bool LimitToAmount(
|
bool LimitToAmount(
|
||||||
CAmount amount,
|
const CAmount amount,
|
||||||
CAmount dustThreshold,
|
const CAmount dustThreshold,
|
||||||
std::set<libzcash::OutputPool> recipientPools);
|
const std::set<libzcash::OutputPool>& recipientPools);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Compute the total ZEC amount of spendable inputs.
|
* Compute the total ZEC amount of spendable inputs.
|
||||||
*/
|
*/
|
||||||
CAmount Total() const {
|
CAmount Total() const {
|
||||||
|
CAmount result = 0;
|
||||||
|
result += GetTransparentBalance();
|
||||||
|
result += GetSproutBalance();
|
||||||
|
result += GetSaplingBalance();
|
||||||
|
result += GetOrchardBalance();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
CAmount GetTransparentBalance() const {
|
||||||
CAmount result = 0;
|
CAmount result = 0;
|
||||||
for (const auto& t : utxos) {
|
for (const auto& t : utxos) {
|
||||||
result += t.Value();
|
result += t.Value();
|
||||||
}
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
CAmount GetSproutBalance() const {
|
||||||
|
CAmount result = 0;
|
||||||
for (const auto& t : sproutNoteEntries) {
|
for (const auto& t : sproutNoteEntries) {
|
||||||
result += t.note.value();
|
result += t.note.value();
|
||||||
}
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
CAmount GetSaplingBalance() const {
|
||||||
|
CAmount result = 0;
|
||||||
for (const auto& t : saplingNoteEntries) {
|
for (const auto& t : saplingNoteEntries) {
|
||||||
result += t.note.value();
|
result += t.note.value();
|
||||||
}
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
CAmount GetOrchardBalance() const {
|
||||||
|
CAmount result = 0;
|
||||||
for (const auto& t : orchardNoteMetadata) {
|
for (const auto& t : orchardNoteMetadata) {
|
||||||
result += t.GetNoteValue();
|
result += t.GetNoteValue();
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,28 @@
|
||||||
|
// 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_WALLET_WALLET_TX_BUILDER_H
|
||||||
|
#define ZCASH_WALLET_WALLET_TX_BUILDER_H
|
||||||
|
|
||||||
|
#include "wallet/memo.h"
|
||||||
|
|
||||||
|
using namespace libzcash;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A payment that has been resolved to send to a specific
|
||||||
|
* recipient address in a single pool.
|
||||||
|
*/
|
||||||
|
class ResolvedPayment : public RecipientMapping {
|
||||||
|
public:
|
||||||
|
CAmount amount;
|
||||||
|
std::optional<Memo> memo;
|
||||||
|
|
||||||
|
ResolvedPayment(
|
||||||
|
std::optional<libzcash::UnifiedAddress> ua,
|
||||||
|
libzcash::RecipientAddress address,
|
||||||
|
CAmount amount,
|
||||||
|
std::optional<Memo> memo) :
|
||||||
|
RecipientMapping(ua, address), amount(amount), memo(memo) {}
|
||||||
|
};
|
||||||
|
#endif
|
Loading…
Reference in New Issue