Auto merge of #1907 - bitcartel:1903_z_sendmany_fee_parameter, r=ebfull
Closes #1903. Add fee parameter to z_sendmany.
This commit is contained in:
commit
fa53daca4e
|
@ -71,7 +71,7 @@ z_importwallet | filename | _Requires an unlocked wallet or an unencrypted walle
|
||||||
Command | Parameters | Description
|
Command | Parameters | Description
|
||||||
--- | --- | ---
|
--- | --- | ---
|
||||||
z_listreceivedbyaddress<br> | zaddr [minconf=1] | Return a list of amounts received by a zaddr belonging to the node’s wallet.<br><br>Optionally set the minimum number of confirmations which a received amount must have in order to be included in the result. Use 0 to count unconfirmed transactions.<br><br>Output:<br>[{<br>“txid”: “4a0f…”,<br>“amount”: 0.54,<br>“memo”:”F0FF…”,}, {...}, {...}<br>]
|
z_listreceivedbyaddress<br> | zaddr [minconf=1] | Return a list of amounts received by a zaddr belonging to the node’s wallet.<br><br>Optionally set the minimum number of confirmations which a received amount must have in order to be included in the result. Use 0 to count unconfirmed transactions.<br><br>Output:<br>[{<br>“txid”: “4a0f…”,<br>“amount”: 0.54,<br>“memo”:”F0FF…”,}, {...}, {...}<br>]
|
||||||
z_sendmany<br> | fromaddress amounts [minconf=1] | _This is an Asynchronous RPC call_<br><br>Send funds from an address to multiple outputs. The address can be either a taddr or a zaddr.<br><br>Amounts is a list containing key/value pairs corresponding to the addresses and amount to pay. Each output address can be in taddr or zaddr format.<br><br>When sending to a zaddr, you also have the option of attaching a memo in hexadecimal format.<br><br>**NOTE:**When sending coinbase funds to a zaddr, the node's wallet does not allow any change. Put another way, spending a partial amount of a coinbase utxo is not allowed. This is not a consensus rule but a local wallet rule due to the current implementation of z_sendmany. In future, this rule may be removed.<br><br>Example of Outputs parameter:<br>[{“address”:”t123…”, “amount”:0.005},<br>,{“address”:”z010…”,”amount”:0.03, “memo”:”f508af…”}]<br><br>Optionally set the minimum number of confirmations which a private or transparent transaction must have in order to be used as an input.<br><br>The transaction fee will be determined by the node’s wallet. Any transparent change will be sent to a new transparent address. Any private change will be sent back to the zaddr being used as the source of funds.<br><br>Returns an operationid. You use the operationid value with z_getoperationstatus and z_getoperationresult to obtain the result of sending funds, which if successful, will be a txid.
|
z_sendmany<br> | fromaddress amounts [minconf=1] [fee=0.0001] | _This is an Asynchronous RPC call_<br><br>Send funds from an address to multiple outputs. The address can be either a taddr or a zaddr.<br><br>Amounts is a list containing key/value pairs corresponding to the addresses and amount to pay. Each output address can be in taddr or zaddr format.<br><br>When sending to a zaddr, you also have the option of attaching a memo in hexadecimal format.<br><br>**NOTE:**When sending coinbase funds to a zaddr, the node's wallet does not allow any change. Put another way, spending a partial amount of a coinbase utxo is not allowed. This is not a consensus rule but a local wallet rule due to the current implementation of z_sendmany. In future, this rule may be removed.<br><br>Example of Outputs parameter:<br>[{“address”:”t123…”, “amount”:0.005},<br>,{“address”:”z010…”,”amount”:0.03, “memo”:”f508af…”}]<br><br>Optionally set the minimum number of confirmations which a private or transparent transaction must have in order to be used as an input.<br><br>Optionally set a transaction fee, which by default is 0.0001 ZEC.<br><br>Any transparent change will be sent to a new transparent address. Any private change will be sent back to the zaddr being used as the source of funds.<br><br>Returns an operationid. You use the operationid value with z_getoperationstatus and z_getoperationresult to obtain the result of sending funds, which if successful, will be a txid.
|
||||||
|
|
||||||
### Operations
|
### Operations
|
||||||
|
|
||||||
|
|
|
@ -190,6 +190,27 @@ class WalletProtectCoinbaseTest (BitcoinTestFramework):
|
||||||
node2balance = amount_per_recipient * num_t_recipients
|
node2balance = amount_per_recipient * num_t_recipients
|
||||||
assert_equal(self.nodes[2].getbalance(), node2balance)
|
assert_equal(self.nodes[2].getbalance(), node2balance)
|
||||||
|
|
||||||
|
# Send will fail because fee is negative
|
||||||
|
try:
|
||||||
|
self.nodes[0].z_sendmany(myzaddr, recipients, 1, -1)
|
||||||
|
except JSONRPCException,e:
|
||||||
|
errorString = e.error['message']
|
||||||
|
assert_equal("Invalid amount" in errorString, True)
|
||||||
|
|
||||||
|
# Send will fail because fee is larger than MAX_MONEY
|
||||||
|
try:
|
||||||
|
self.nodes[0].z_sendmany(myzaddr, recipients, 1, Decimal('21000000.00000001'))
|
||||||
|
except JSONRPCException,e:
|
||||||
|
errorString = e.error['message']
|
||||||
|
assert_equal("Invalid amount" in errorString, True)
|
||||||
|
|
||||||
|
# Send will fail because fee is larger than sum of outputs
|
||||||
|
try:
|
||||||
|
self.nodes[0].z_sendmany(myzaddr, recipients, 1, (amount_per_recipient * num_t_recipients) + Decimal('0.00000001'))
|
||||||
|
except JSONRPCException,e:
|
||||||
|
errorString = e.error['message']
|
||||||
|
assert_equal("is greater than the sum of outputs" in errorString, True)
|
||||||
|
|
||||||
# Send will succeed because the balance of non-coinbase utxos is 10.0
|
# Send will succeed because the balance of non-coinbase utxos is 10.0
|
||||||
try:
|
try:
|
||||||
self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(), 9)
|
self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(), 9)
|
||||||
|
@ -208,10 +229,14 @@ class WalletProtectCoinbaseTest (BitcoinTestFramework):
|
||||||
recipients = []
|
recipients = []
|
||||||
num_recipients = 3
|
num_recipients = 3
|
||||||
amount_per_recipient = Decimal('0.002')
|
amount_per_recipient = Decimal('0.002')
|
||||||
|
minconf = 1
|
||||||
|
send_amount = num_recipients * amount_per_recipient
|
||||||
|
custom_fee = Decimal('0.00012345')
|
||||||
|
zbalance = self.nodes[0].z_getbalance(myzaddr)
|
||||||
for i in xrange(0,num_recipients):
|
for i in xrange(0,num_recipients):
|
||||||
newzaddr = self.nodes[2].z_getnewaddress()
|
newzaddr = self.nodes[2].z_getnewaddress()
|
||||||
recipients.append({"address":newzaddr, "amount":amount_per_recipient})
|
recipients.append({"address":newzaddr, "amount":amount_per_recipient})
|
||||||
myopid = self.nodes[0].z_sendmany(myzaddr, recipients)
|
myopid = self.nodes[0].z_sendmany(myzaddr, recipients, minconf, custom_fee)
|
||||||
self.wait_and_assert_operationid_status(myopid)
|
self.wait_and_assert_operationid_status(myopid)
|
||||||
self.sync_all()
|
self.sync_all()
|
||||||
self.nodes[1].generate(1)
|
self.nodes[1].generate(1)
|
||||||
|
@ -219,7 +244,9 @@ class WalletProtectCoinbaseTest (BitcoinTestFramework):
|
||||||
|
|
||||||
# check balances
|
# check balances
|
||||||
resp = self.nodes[2].z_gettotalbalance()
|
resp = self.nodes[2].z_gettotalbalance()
|
||||||
assert_equal(Decimal(resp["private"]), num_recipients * amount_per_recipient)
|
assert_equal(Decimal(resp["private"]), send_amount)
|
||||||
|
resp = self.nodes[0].z_getbalance(myzaddr)
|
||||||
|
assert_equal(Decimal(resp), zbalance - custom_fee - send_amount)
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
WalletProtectCoinbaseTest().main()
|
WalletProtectCoinbaseTest().main()
|
||||||
|
|
|
@ -103,6 +103,7 @@ static const CRPCConvertParam vRPCConvertParams[] =
|
||||||
{ "z_gettotalbalance", 0},
|
{ "z_gettotalbalance", 0},
|
||||||
{ "z_sendmany", 1},
|
{ "z_sendmany", 1},
|
||||||
{ "z_sendmany", 2},
|
{ "z_sendmany", 2},
|
||||||
|
{ "z_sendmany", 3},
|
||||||
{ "z_getoperationstatus", 0},
|
{ "z_getoperationstatus", 0},
|
||||||
{ "z_getoperationresult", 0},
|
{ "z_getoperationresult", 0},
|
||||||
{ "z_importkey", 1 }
|
{ "z_importkey", 1 }
|
||||||
|
|
|
@ -820,7 +820,7 @@ BOOST_AUTO_TEST_CASE(rpc_z_sendmany_parameters)
|
||||||
|
|
||||||
BOOST_CHECK_THROW(CallRPC("z_sendmany"), runtime_error);
|
BOOST_CHECK_THROW(CallRPC("z_sendmany"), runtime_error);
|
||||||
BOOST_CHECK_THROW(CallRPC("z_sendmany toofewargs"), runtime_error);
|
BOOST_CHECK_THROW(CallRPC("z_sendmany toofewargs"), runtime_error);
|
||||||
BOOST_CHECK_THROW(CallRPC("z_sendmany too many args here"), runtime_error);
|
BOOST_CHECK_THROW(CallRPC("z_sendmany just too many args here"), runtime_error);
|
||||||
|
|
||||||
// bad from address
|
// bad from address
|
||||||
BOOST_CHECK_THROW(CallRPC("z_sendmany "
|
BOOST_CHECK_THROW(CallRPC("z_sendmany "
|
||||||
|
@ -841,6 +841,27 @@ BOOST_AUTO_TEST_CASE(rpc_z_sendmany_parameters)
|
||||||
" {\"address\":\"tmQP9L3s31cLsghVYf2Jb5MhKj1jRBPoeQn\", \"amount\":12.0} ]"
|
" {\"address\":\"tmQP9L3s31cLsghVYf2Jb5MhKj1jRBPoeQn\", \"amount\":12.0} ]"
|
||||||
), runtime_error);
|
), runtime_error);
|
||||||
|
|
||||||
|
// invalid fee amount, cannot be negative
|
||||||
|
BOOST_CHECK_THROW(CallRPC("z_sendmany "
|
||||||
|
"tmRr6yJonqGK23UVhrKuyvTpF8qxQQjKigJ "
|
||||||
|
"[{\"address\":\"tmQP9L3s31cLsghVYf2Jb5MhKj1jRBPoeQn\", \"amount\":50.0}] "
|
||||||
|
"1 -0.0001"
|
||||||
|
), runtime_error);
|
||||||
|
|
||||||
|
// invalid fee amount, bigger than MAX_MONEY
|
||||||
|
BOOST_CHECK_THROW(CallRPC("z_sendmany "
|
||||||
|
"tmRr6yJonqGK23UVhrKuyvTpF8qxQQjKigJ "
|
||||||
|
"[{\"address\":\"tmQP9L3s31cLsghVYf2Jb5MhKj1jRBPoeQn\", \"amount\":50.0}] "
|
||||||
|
"1 21000001"
|
||||||
|
), runtime_error);
|
||||||
|
|
||||||
|
// fee amount is bigger than sum of outputs
|
||||||
|
BOOST_CHECK_THROW(CallRPC("z_sendmany "
|
||||||
|
"tmRr6yJonqGK23UVhrKuyvTpF8qxQQjKigJ "
|
||||||
|
"[{\"address\":\"tmQP9L3s31cLsghVYf2Jb5MhKj1jRBPoeQn\", \"amount\":50.0}] "
|
||||||
|
"1 50.00000001"
|
||||||
|
), runtime_error);
|
||||||
|
|
||||||
// memo bigger than allowed length of ZC_MEMO_SIZE
|
// memo bigger than allowed length of ZC_MEMO_SIZE
|
||||||
std::vector<char> v (2 * (ZC_MEMO_SIZE+1)); // x2 for hexadecimal string format
|
std::vector<char> v (2 * (ZC_MEMO_SIZE+1)); // x2 for hexadecimal string format
|
||||||
std::fill(v.begin(),v.end(), 'A');
|
std::fill(v.begin(),v.end(), 'A');
|
||||||
|
|
|
@ -50,9 +50,12 @@ AsyncRPCOperation_sendmany::AsyncRPCOperation_sendmany(
|
||||||
std::string fromAddress,
|
std::string fromAddress,
|
||||||
std::vector<SendManyRecipient> tOutputs,
|
std::vector<SendManyRecipient> tOutputs,
|
||||||
std::vector<SendManyRecipient> zOutputs,
|
std::vector<SendManyRecipient> zOutputs,
|
||||||
int minDepth) :
|
int minDepth,
|
||||||
fromaddress_(fromAddress), t_outputs_(tOutputs), z_outputs_(zOutputs), mindepth_(minDepth)
|
CAmount fee) :
|
||||||
|
fromaddress_(fromAddress), t_outputs_(tOutputs), z_outputs_(zOutputs), mindepth_(minDepth), fee_(fee)
|
||||||
{
|
{
|
||||||
|
assert(fee_ > 0);
|
||||||
|
|
||||||
if (minDepth < 0) {
|
if (minDepth < 0) {
|
||||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Minconf cannot be negative");
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Minconf cannot be negative");
|
||||||
}
|
}
|
||||||
|
@ -147,8 +150,8 @@ bool AsyncRPCOperation_sendmany::main_impl() {
|
||||||
bool isSingleZaddrOutput = (t_outputs_.size()==0 && z_outputs_.size()==1);
|
bool isSingleZaddrOutput = (t_outputs_.size()==0 && z_outputs_.size()==1);
|
||||||
bool isMultipleZaddrOutput = (t_outputs_.size()==0 && z_outputs_.size()>=1);
|
bool isMultipleZaddrOutput = (t_outputs_.size()==0 && z_outputs_.size()>=1);
|
||||||
bool isPureTaddrOnlyTx = (isfromtaddr_ && z_outputs_.size() == 0);
|
bool isPureTaddrOnlyTx = (isfromtaddr_ && z_outputs_.size() == 0);
|
||||||
CAmount minersFee = ASYNC_RPC_OPERATION_DEFAULT_MINERS_FEE;
|
CAmount minersFee = fee_;
|
||||||
|
|
||||||
// When spending coinbase utxos, you can only specify a single zaddr as the change must go somewhere
|
// When spending coinbase utxos, you can only specify a single zaddr as the change must go somewhere
|
||||||
// and if there are multiple zaddrs, we don't know where to send it.
|
// and if there are multiple zaddrs, we don't know where to send it.
|
||||||
if (isfromtaddr_) {
|
if (isfromtaddr_) {
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
|
|
||||||
#include <tuple>
|
#include <tuple>
|
||||||
|
|
||||||
// TODO: Compute fee based on a heuristic, e.g. (num tx output * dust threshold) + joinsplit bytes * ?
|
// Default transaction fee if caller does not specify one.
|
||||||
#define ASYNC_RPC_OPERATION_DEFAULT_MINERS_FEE 10000
|
#define ASYNC_RPC_OPERATION_DEFAULT_MINERS_FEE 10000
|
||||||
|
|
||||||
using namespace libzcash;
|
using namespace libzcash;
|
||||||
|
@ -43,7 +43,7 @@ struct AsyncJoinSplitInfo
|
||||||
|
|
||||||
class AsyncRPCOperation_sendmany : public AsyncRPCOperation {
|
class AsyncRPCOperation_sendmany : public AsyncRPCOperation {
|
||||||
public:
|
public:
|
||||||
AsyncRPCOperation_sendmany(std::string fromAddress, std::vector<SendManyRecipient> tOutputs, std::vector<SendManyRecipient> zOutputs, int minDepth);
|
AsyncRPCOperation_sendmany(std::string fromAddress, std::vector<SendManyRecipient> tOutputs, std::vector<SendManyRecipient> zOutputs, int minDepth, CAmount fee = ASYNC_RPC_OPERATION_DEFAULT_MINERS_FEE);
|
||||||
virtual ~AsyncRPCOperation_sendmany();
|
virtual ~AsyncRPCOperation_sendmany();
|
||||||
|
|
||||||
// We don't want to be copied or moved around
|
// We don't want to be copied or moved around
|
||||||
|
@ -59,6 +59,7 @@ public:
|
||||||
private:
|
private:
|
||||||
friend class TEST_FRIEND_AsyncRPCOperation_sendmany; // class for unit testing
|
friend class TEST_FRIEND_AsyncRPCOperation_sendmany; // class for unit testing
|
||||||
|
|
||||||
|
CAmount fee_;
|
||||||
int mindepth_;
|
int mindepth_;
|
||||||
std::string fromaddress_;
|
std::string fromaddress_;
|
||||||
bool isfromtaddr_;
|
bool isfromtaddr_;
|
||||||
|
|
|
@ -3182,9 +3182,9 @@ Value z_sendmany(const Array& params, bool fHelp)
|
||||||
if (!EnsureWalletIsAvailable(fHelp))
|
if (!EnsureWalletIsAvailable(fHelp))
|
||||||
return Value::null;
|
return Value::null;
|
||||||
|
|
||||||
if (fHelp || params.size() < 2 || params.size() > 3)
|
if (fHelp || params.size() < 2 || params.size() > 4)
|
||||||
throw runtime_error(
|
throw runtime_error(
|
||||||
"z_sendmany \"fromaddress\" [{\"address\":... ,\"amount\":...},...] ( minconf )\n"
|
"z_sendmany \"fromaddress\" [{\"address\":... ,\"amount\":...},...] ( minconf ) ( fee )\n"
|
||||||
"\nSend multiple times. Amounts are double-precision floating point numbers."
|
"\nSend multiple times. Amounts are double-precision floating point numbers."
|
||||||
"\nChange from a taddr flows to a new taddr address, while change from zaddr returns to itself."
|
"\nChange from a taddr flows to a new taddr address, while change from zaddr returns to itself."
|
||||||
"\nWhen sending coinbase UTXOs to a zaddr, change is not allowed. The entire value of the UTXO(s) must be consumed."
|
"\nWhen sending coinbase UTXOs to a zaddr, change is not allowed. The entire value of the UTXO(s) must be consumed."
|
||||||
|
@ -3199,6 +3199,8 @@ Value z_sendmany(const Array& params, bool fHelp)
|
||||||
" \"memo\":memo (string, optional) If the address is a zaddr, raw data represented in hexadecimal string format\n"
|
" \"memo\":memo (string, optional) If the address is a zaddr, raw data represented in hexadecimal string format\n"
|
||||||
" }, ... ]\n"
|
" }, ... ]\n"
|
||||||
"3. minconf (numeric, optional, default=1) Only use funds confirmed at least this many times.\n"
|
"3. minconf (numeric, optional, default=1) Only use funds confirmed at least this many times.\n"
|
||||||
|
"4. fee (numeric, optional, default="
|
||||||
|
+ strprintf("%s", FormatMoney(ASYNC_RPC_OPERATION_DEFAULT_MINERS_FEE)) + ") The fee amount to attach to this transaction.\n"
|
||||||
"\nResult:\n"
|
"\nResult:\n"
|
||||||
"\"operationid\" (string) An operationid to pass to z_getoperationstatus to get the result of the operation.\n"
|
"\"operationid\" (string) An operationid to pass to z_getoperationstatus to get the result of the operation.\n"
|
||||||
);
|
);
|
||||||
|
@ -3239,6 +3241,7 @@ Value z_sendmany(const Array& params, bool fHelp)
|
||||||
// Recipients
|
// Recipients
|
||||||
std::vector<SendManyRecipient> taddrRecipients;
|
std::vector<SendManyRecipient> taddrRecipients;
|
||||||
std::vector<SendManyRecipient> zaddrRecipients;
|
std::vector<SendManyRecipient> zaddrRecipients;
|
||||||
|
CAmount nTotalOut = 0;
|
||||||
|
|
||||||
BOOST_FOREACH(Value& output, outputs)
|
BOOST_FOREACH(Value& output, outputs)
|
||||||
{
|
{
|
||||||
|
@ -3294,6 +3297,8 @@ Value z_sendmany(const Array& params, bool fHelp)
|
||||||
} else {
|
} else {
|
||||||
taddrRecipients.push_back( SendManyRecipient(address, nAmount, memo) );
|
taddrRecipients.push_back( SendManyRecipient(address, nAmount, memo) );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
nTotalOut += nAmount;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check the number of zaddr outputs does not exceed the limit.
|
// Check the number of zaddr outputs does not exceed the limit.
|
||||||
|
@ -3329,9 +3334,19 @@ Value z_sendmany(const Array& params, bool fHelp)
|
||||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Minimum number of confirmations cannot be less than 0");
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Minimum number of confirmations cannot be less than 0");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Fee in Zatoshis, not currency format)
|
||||||
|
CAmount nFee = ASYNC_RPC_OPERATION_DEFAULT_MINERS_FEE;
|
||||||
|
if (params.size() > 3) {
|
||||||
|
nFee = AmountFromValue( params[3] );
|
||||||
|
// Check that the user specified fee is sane.
|
||||||
|
if (nFee > nTotalOut) {
|
||||||
|
throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Fee %s is greater than the sum of outputs %s", FormatMoney(nFee), FormatMoney(nTotalOut)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Create operation and add to global queue
|
// Create operation and add to global queue
|
||||||
std::shared_ptr<AsyncRPCQueue> q = getAsyncRPCQueue();
|
std::shared_ptr<AsyncRPCQueue> q = getAsyncRPCQueue();
|
||||||
std::shared_ptr<AsyncRPCOperation> operation( new AsyncRPCOperation_sendmany(fromaddress, taddrRecipients, zaddrRecipients, nMinDepth) );
|
std::shared_ptr<AsyncRPCOperation> operation( new AsyncRPCOperation_sendmany(fromaddress, taddrRecipients, zaddrRecipients, nMinDepth, nFee) );
|
||||||
q->addOperation(operation);
|
q->addOperation(operation);
|
||||||
AsyncRPCOperationId operationId = operation->getId();
|
AsyncRPCOperationId operationId = operation->getId();
|
||||||
return operationId;
|
return operationId;
|
||||||
|
|
Loading…
Reference in New Issue