Skip to content

Commit

Permalink
Merge pull request BTCPrivate#97 from jc23424/rpc-calls
Browse files Browse the repository at this point in the history
Merge convenience rpc calls
  • Loading branch information
ch4ot1c authored Mar 1, 2018
2 parents e96711d + b5b8ef7 commit 3137f04
Show file tree
Hide file tree
Showing 13 changed files with 1,192 additions and 90 deletions.
5 changes: 3 additions & 2 deletions doc/payment-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ RPC calls by category:
* Addresses : z_getnewaddress, z_listaddresses, z_validateaddress
* Keys : z_exportkey, z_importkey, z_exportwallet, z_importwallet
* Operation: z_getoperationresult, z_getoperationstatus, z_listoperationids
* Payment : z_listreceivedbyaddress, z_sendmany
* Payment : z_listreceivedbyaddress, z_sendmany, z_shieldcoinbase

RPC parameter conventions:

Expand Down Expand Up @@ -71,7 +71,8 @@ z_importwallet | filename | _Requires an unlocked wallet or an unencrypted walle
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_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.
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. When sending from a zaddr, minconf must be greater than zero.<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.
z_shieldcoinbase<br> | fromaddress toaddress [fee=0.0001] | _This is an Asynchronous RPC call_<br><br>Shield transparent coinbase funds by sending to a shielded z address. Utxos selected for shielding will be locked. If there is an error, they are unlocked. The RPC call `listlockunspent` can be used to return a list of locked utxos. The number of coinbase utxos selected for shielding is limited by both the -mempooltxinputlimit=xxx option and a consensus rule defining a maximum transaction size of 100000 bytes. <br><br>The from address is a taddr or "*" for all taddrs belonging to the wallet. The to address is a zaddr. The default fee is 0.0001.<br><br>Returns an object containing an operationid which can be used with z_getoperationstatus and z_getoperationresult, along with key-value pairs regarding how many utxos are being shielded in this trasaction and what remains to be shielded.

### Operations

Expand Down
1 change: 1 addition & 0 deletions qa/pull-tester/rpc-tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ testScripts=(
'prioritisetransaction.py'
'wallet_treestate.py'
'wallet_protectcoinbase.py'
'wallet_shieldcoinbase.py'
'wallet.py'
'wallet_nullifiers.py'
'wallet_1941.py'
Expand Down
12 changes: 12 additions & 0 deletions qa/rpc-tests/wallet.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,18 @@ def run_test (self):
node0utxos = self.nodes[0].listunspent(1)
assert_equal(len(node0utxos), 3)

# Check 'generated' field of listunspent
# Node 0: has one coinbase utxo and two regular utxos
assert_equal(sum(int(uxto["generated"] is True) for uxto in node0utxos), 1)
# Node 1: has 101 coinbase utxos and no regular utxos
node1utxos = self.nodes[1].listunspent(1)
assert_equal(len(node1utxos), 101)
assert_equal(sum(int(uxto["generated"] is True) for uxto in node1utxos), 101)
# Node 2: has no coinbase utxos and two regular utxos
node2utxos = self.nodes[2].listunspent(1)
assert_equal(len(node2utxos), 2)
assert_equal(sum(int(uxto["generated"] is True) for uxto in node2utxos), 0)

# create both transactions
txns_to_send = []
for utxo in node0utxos:
Expand Down
190 changes: 190 additions & 0 deletions qa/rpc-tests/wallet_shieldcoinbase.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
#!/usr/bin/env python2
# Copyright (c) 2017 The Zcash developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.

from test_framework.test_framework import BitcoinTestFramework
from test_framework.authproxy import JSONRPCException
from test_framework.util import assert_equal, initialize_chain_clean, \
start_node, connect_nodes_bi, sync_blocks

import sys
import time
from decimal import Decimal

class WalletShieldCoinbaseTest (BitcoinTestFramework):

def setup_chain(self):
print("Initializing test directory "+self.options.tmpdir)
initialize_chain_clean(self.options.tmpdir, 4)

def setup_network(self, split=False):
args = ['-regtestprotectcoinbase', '-debug=zrpcunsafe']
self.nodes = []
self.nodes.append(start_node(0, self.options.tmpdir, args))
self.nodes.append(start_node(1, self.options.tmpdir, args))
args2 = ['-regtestprotectcoinbase', '-debug=zrpcunsafe', "-mempooltxinputlimit=7"]
self.nodes.append(start_node(2, self.options.tmpdir, args2))
connect_nodes_bi(self.nodes,0,1)
connect_nodes_bi(self.nodes,1,2)
connect_nodes_bi(self.nodes,0,2)
self.is_network_split=False
self.sync_all()

# Returns txid if operation was a success or None
def wait_and_assert_operationid_status(self, nodeid, myopid, in_status='success', in_errormsg=None):
print('waiting for async operation {}'.format(myopid))
opids = []
opids.append(myopid)
timeout = 300
status = None
errormsg = None
txid = None
for x in xrange(1, timeout):
results = self.nodes[nodeid].z_getoperationresult(opids)
if len(results)==0:
time.sleep(1)
else:
status = results[0]["status"]
if status == "failed":
errormsg = results[0]['error']['message']
elif status == "success":
txid = results[0]['result']['txid']
break
print('...returned status: {}'.format(status))
assert_equal(in_status, status)
if errormsg is not None:
assert(in_errormsg is not None)
assert_equal(in_errormsg in errormsg, True)
print('...returned error: {}'.format(errormsg))
return txid

def run_test (self):
print "Mining blocks..."

self.nodes[0].generate(1)
do_not_shield_taddr = self.nodes[0].getnewaddress()

self.nodes[0].generate(4)
walletinfo = self.nodes[0].getwalletinfo()
assert_equal(walletinfo['immature_balance'], 50)
assert_equal(walletinfo['balance'], 0)
self.sync_all()
self.nodes[2].generate(1)
self.nodes[2].getnewaddress()
self.nodes[2].generate(1)
self.nodes[2].getnewaddress()
self.nodes[2].generate(1)
self.sync_all()
self.nodes[1].generate(101)
self.sync_all()
assert_equal(self.nodes[0].getbalance(), 50)
assert_equal(self.nodes[1].getbalance(), 10)
assert_equal(self.nodes[2].getbalance(), 30)

# Prepare to send taddr->zaddr
mytaddr = self.nodes[0].getnewaddress()
myzaddr = self.nodes[0].z_getnewaddress()

# Shielding will fail when trying to spend from watch-only address
self.nodes[2].importaddress(mytaddr)
try:
self.nodes[2].z_shieldcoinbase(mytaddr, myzaddr)
except JSONRPCException,e:
errorString = e.error['message']
assert_equal("Could not find any coinbase funds to shield" in errorString, True)

# Shielding will fail because fee is negative
try:
self.nodes[0].z_shieldcoinbase("*", myzaddr, -1)
except JSONRPCException,e:
errorString = e.error['message']
assert_equal("Amount out of range" in errorString, True)

# Shielding will fail because fee is larger than MAX_MONEY
try:
self.nodes[0].z_shieldcoinbase("*", myzaddr, Decimal('21000000.00000001'))
except JSONRPCException,e:
errorString = e.error['message']
assert_equal("Amount out of range" in errorString, True)

# Shielding will fail because fee is larger than sum of utxos
try:
self.nodes[0].z_shieldcoinbase("*", myzaddr, 999)
except JSONRPCException,e:
errorString = e.error['message']
assert_equal("Insufficient coinbase funds" in errorString, True)

# Shield coinbase utxos from node 0 of value 40, standard fee of 0.00010000
result = self.nodes[0].z_shieldcoinbase(mytaddr, myzaddr)
mytxid = self.wait_and_assert_operationid_status(0, result['opid'])
self.sync_all()
self.nodes[1].generate(1)
self.sync_all()

# Confirm balances and that do_not_shield_taddr containing funds of 10 was left alone
assert_equal(self.nodes[0].getbalance(), 10)
assert_equal(self.nodes[0].z_getbalance(do_not_shield_taddr), Decimal('10.0'))
assert_equal(self.nodes[0].z_getbalance(myzaddr), Decimal('39.99990000'))
assert_equal(self.nodes[1].getbalance(), 20)
assert_equal(self.nodes[2].getbalance(), 30)

# Shield coinbase utxos from any node 2 taddr, and set fee to 0
result = self.nodes[2].z_shieldcoinbase("*", myzaddr, 0)
mytxid = self.wait_and_assert_operationid_status(2, result['opid'])
self.sync_all()
self.nodes[1].generate(1)
self.sync_all()

assert_equal(self.nodes[0].getbalance(), 10)
assert_equal(self.nodes[0].z_getbalance(myzaddr), Decimal('69.99990000'))
assert_equal(self.nodes[1].getbalance(), 30)
assert_equal(self.nodes[2].getbalance(), 0)

# Generate 800 coinbase utxos on node 0, and 20 coinbase utxos on node 2
self.nodes[0].generate(800)
self.sync_all()
self.nodes[2].generate(20)
self.sync_all()
self.nodes[1].generate(100)
self.sync_all()
mytaddr = self.nodes[0].getnewaddress()

# Shielding the 800 utxos will occur over two transactions, since max tx size is 100,000 bytes.
# We don't verify shieldingValue as utxos are not selected in any specific order, so value can change on each test run.
result = self.nodes[0].z_shieldcoinbase(mytaddr, myzaddr, 0)
assert_equal(result["shieldingUTXOs"], Decimal('662'))
assert_equal(result["remainingUTXOs"], Decimal('138'))
remainingValue = result["remainingValue"]
opid1 = result['opid']

# Verify that utxos are locked (not available for selection) by queuing up another shielding operation
result = self.nodes[0].z_shieldcoinbase(mytaddr, myzaddr)
assert_equal(result["shieldingValue"], Decimal(remainingValue))
assert_equal(result["shieldingUTXOs"], Decimal('138'))
assert_equal(result["remainingValue"], Decimal('0'))
assert_equal(result["remainingUTXOs"], Decimal('0'))
opid2 = result['opid']

# wait for both aysnc operations to complete
self.wait_and_assert_operationid_status(0, opid1)
self.wait_and_assert_operationid_status(0, opid2)

# sync_all() invokes sync_mempool() but node 2's mempool limit will cause tx1 and tx2 to be rejected.
# So instead, we sync on blocks, and after a new block is generated, all nodes will have an empty mempool.
sync_blocks(self.nodes)
self.nodes[1].generate(1)
self.sync_all()

# Verify maximum number of utxos which node 2 can shield is limited by option -mempooltxinputlimit
mytaddr = self.nodes[2].getnewaddress()
result = self.nodes[2].z_shieldcoinbase(mytaddr, myzaddr, 0)
assert_equal(result["shieldingUTXOs"], Decimal('7'))
assert_equal(result["remainingUTXOs"], Decimal('13'))
mytxid = self.wait_and_assert_operationid_status(2, result['opid'])
self.sync_all()
self.nodes[1].generate(1)
self.sync_all()

if __name__ == '__main__':
WalletShieldCoinbaseTest().main()
2 changes: 2 additions & 0 deletions src/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,7 @@ BITCOIN_CORE_H = \
version.h \
versionbits.h \
wallet/asyncrpcoperation_sendmany.h \
wallet/asyncrpcoperation_shieldcoinbase.h \
wallet/crypter.h \
wallet/db.h \
wallet/wallet.h \
Expand Down Expand Up @@ -273,6 +274,7 @@ libbitcoin_wallet_a_SOURCES = \
zcbenchmarks.cpp \
zcbenchmarks.h \
wallet/asyncrpcoperation_sendmany.cpp \
wallet/asyncrpcoperation_shieldcoinbase.cpp \
wallet/crypter.cpp \
wallet/db.cpp \
wallet/rpcdump.cpp \
Expand Down
1 change: 1 addition & 0 deletions src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
#include "validationinterface.h"
#include "versionbits.h"
#include "wallet/asyncrpcoperation_sendmany.h"
#include "wallet/asyncrpcoperation_shieldcoinbase.h"

#include <sstream>

Expand Down
3 changes: 2 additions & 1 deletion src/rpcclient.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -103,12 +103,13 @@ static const CRPCConvertParam vRPCConvertParams[] =
{ "zcbenchmark", 1 },
{ "zcbenchmark", 2 },
{ "getblocksubsidy", 0},
{ "z_listreceivedbyaddress", 1},
{ "z_listreceivedbyaddress", 1},
{ "z_getbalance", 1},
{ "z_gettotalbalance", 0},
{ "z_sendmany", 1},
{ "z_sendmany", 2},
{ "z_sendmany", 3},
{ "z_shieldcoinbase", 2},
{ "z_getoperationstatus", 0},
{ "z_getoperationresult", 0},
{ "z_importkey", 2 },
Expand Down
1 change: 1 addition & 0 deletions src/rpcserver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -386,6 +386,7 @@ static const CRPCCommand vRPCCommands[] =
{ "wallet", "z_getbalance", &z_getbalance, false },
{ "wallet", "z_gettotalbalance", &z_gettotalbalance, false },
{ "wallet", "z_sendmany", &z_sendmany, false },
{ "wallet", "z_shieldcoinbase", &z_shieldcoinbase, false },
{ "wallet", "z_getoperationstatus", &z_getoperationstatus, true },
{ "wallet", "z_getoperationresult", &z_getoperationresult, true },
{ "wallet", "z_listoperationids", &z_listoperationids, true },
Expand Down
1 change: 1 addition & 0 deletions src/rpcserver.h
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,7 @@ extern UniValue z_listreceivedbyaddress(const UniValue& params, bool fHelp); //
extern UniValue z_getbalance(const UniValue& params, bool fHelp); // in rpcwallet.cpp
extern UniValue z_gettotalbalance(const UniValue& params, bool fHelp); // in rpcwallet.cpp
extern UniValue z_sendmany(const UniValue& params, bool fHelp); // in rpcwallet.cpp
extern UniValue z_shieldcoinbase(const UniValue& params, bool fHelp); // in rpcwallet.cpp
extern UniValue z_getoperationstatus(const UniValue& params, bool fHelp); // in rpcwallet.cpp
extern UniValue z_getoperationresult(const UniValue& params, bool fHelp); // in rpcwallet.cpp
extern UniValue z_listoperationids(const UniValue& params, bool fHelp); // in rpcwallet.cpp
Expand Down
Loading

0 comments on commit 3137f04

Please sign in to comment.