diff --git a/Makefile.am b/Makefile.am index 6b2a0d33b91..714acc6f647 100755 --- a/Makefile.am +++ b/Makefile.am @@ -14,6 +14,7 @@ endif BITCOIND_BIN=$(top_builddir)/src/zcashd$(EXEEXT) BITCOIN_CLI_BIN=$(top_builddir)/src/zcash-cli$(EXEEXT) +WALLET_UTILITY_BIN=$(top_builddir)/src/wallet-utility$(EXEEXT) BITCOIN_WIN_INSTALLER=$(PACKAGE)-$(PACKAGE_VERSION)-win$(WINDOWS_BITS)-setup$(EXEEXT) ##OSX_APP=Bitcoin-Qt.app @@ -64,6 +65,7 @@ $(BITCOIN_WIN_INSTALLER): all-recursive $(MKDIR_P) $(top_builddir)/release STRIPPROG="$(STRIP)" $(INSTALL_STRIP_PROGRAM) $(BITCOIND_BIN) $(top_builddir)/release STRIPPROG="$(STRIP)" $(INSTALL_STRIP_PROGRAM) $(BITCOIN_CLI_BIN) $(top_builddir)/release + STRIPPROG="$(STRIP)" $(INSTALL_STRIP_PROGRAM) $(WALLET_UTILITY_BIN) $(top_builddir)/release @test -f $(MAKENSIS) && $(MAKENSIS) -V2 $(top_builddir)/share/setup.nsi || \ echo error: could not build $@ @echo built $@ @@ -144,6 +146,9 @@ $(BITCOIND_BIN): FORCE $(BITCOIN_CLI_BIN): FORCE $(MAKE) -C src $(@F) +$(WALLET_UTILITY_BIN): FORCE + $(MAKE) -C src $(@F) + if USE_LCOV baseline.info: diff --git a/configure.ac b/configure.ac index 7ce256781a7..6667d73c866 100755 --- a/configure.ac +++ b/configure.ac @@ -199,7 +199,7 @@ CPPFLAGS="$CPPFLAGS -DHAVE_BUILD_INFO -D__STDC_FORMAT_MACROS" AC_ARG_WITH([utils], [AS_HELP_STRING([--with-utils], - [build zcash-cli zcash-tx (default=yes)])], + [build zcash-cli zcash-tx wallet-utility (default=yes)])], [build_bitcoin_utils=$withval], [build_bitcoin_utils=yes]) @@ -812,7 +812,7 @@ AC_MSG_CHECKING([whether to build bitcoind]) AM_CONDITIONAL([BUILD_BITCOIND], [test x$build_bitcoind = xyes]) AC_MSG_RESULT($build_bitcoind) -AC_MSG_CHECKING([whether to build utils (zcash-cli zcash-tx)]) +AC_MSG_CHECKING([whether to build utils (zcash-cli zcash-tx wallet-utility)]) AM_CONDITIONAL([BUILD_BITCOIN_UTILS], [test x$build_bitcoin_utils = xyes]) AC_MSG_RESULT($build_bitcoin_utils) diff --git a/doc/payment-api.md b/doc/payment-api.md index 5dc0eb90734..a26ce6b2709 100755 --- a/doc/payment-api.md +++ b/doc/payment-api.md @@ -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: @@ -71,7 +71,8 @@ z_importwallet | filename | _Requires an unlocked wallet or an unencrypted walle Command | Parameters | Description --- | --- | --- z_listreceivedbyaddress
| zaddr [minconf=1] | Return a list of amounts received by a zaddr belonging to the node’s wallet.

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.

Output:
[{
“txid”: “4a0f…”,
“amount”: 0.54,
“memo”:”F0FF…”,}, {...}, {...}
] -z_sendmany
| fromaddress amounts [minconf=1] [fee=0.0001] | _This is an Asynchronous RPC call_

Send funds from an address to multiple outputs. The address can be either a taddr or a zaddr.

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.

When sending to a zaddr, you also have the option of attaching a memo in hexadecimal format.

**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.

Example of Outputs parameter:
[{“address”:”t123…”, “amount”:0.005},
,{“address”:”z010…”,”amount”:0.03, “memo”:”f508af…”}]

Optionally set the minimum number of confirmations which a private or transparent transaction must have in order to be used as an input.

Optionally set a transaction fee, which by default is 0.0001 ZEC.

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.

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
| fromaddress amounts [minconf=1] [fee=0.0001] | _This is an Asynchronous RPC call_

Send funds from an address to multiple outputs. The address can be either a taddr or a zaddr.

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.

When sending to a zaddr, you also have the option of attaching a memo in hexadecimal format.

**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.

Example of Outputs parameter:
[{“address”:”t123…”, “amount”:0.005},
,{“address”:”z010…”,”amount”:0.03, “memo”:”f508af…”}]

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.

Optionally set a transaction fee, which by default is 0.0001 ZEC.

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.

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
| fromaddress toaddress [fee=0.0001] | _This is an Asynchronous RPC call_

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.

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.

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 diff --git a/qa/pull-tester/rpc-tests.sh b/qa/pull-tester/rpc-tests.sh index bd3357642f2..659de2f7b12 100755 --- a/qa/pull-tester/rpc-tests.sh +++ b/qa/pull-tester/rpc-tests.sh @@ -14,6 +14,7 @@ testScripts=( 'prioritisetransaction.py' 'wallet_treestate.py' 'wallet_protectcoinbase.py' + 'wallet_shieldcoinbase.py' 'wallet.py' 'wallet_nullifiers.py' 'wallet_1941.py' @@ -36,6 +37,9 @@ testScripts=( 'walletbackup.py' 'nodehandling.py' 'reindex.py' + 'addressindex.py' + 'timestampindex.py' + 'spentindex.py' 'decodescript.py' 'disablewallet.py' 'zcjoinsplit.py' diff --git a/qa/rpc-tests/addressindex.py b/qa/rpc-tests/addressindex.py new file mode 100644 index 00000000000..11fa7ffdbf4 --- /dev/null +++ b/qa/rpc-tests/addressindex.py @@ -0,0 +1,349 @@ +#!/usr/bin/env python2 +# Copyright (c) 2014-2015 The Bitcoin Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. + +# +# Test addressindex generation and fetching +# + +import time +from test_framework.test_framework import BitcoinTestFramework +from test_framework.util import * +from test_framework.script import * +from test_framework.mininode import * +import binascii + +class AddressIndexTest(BitcoinTestFramework): + + def setup_chain(self): + print("Initializing test directory "+self.options.tmpdir) + initialize_chain_clean(self.options.tmpdir, 4) + + def setup_network(self): + self.nodes = [] + # Nodes 0/1 are "wallet" nodes + self.nodes.append(start_node(0, self.options.tmpdir, ["-debug", "-relaypriority=0"])) + self.nodes.append(start_node(1, self.options.tmpdir, ["-debug", "-addressindex"])) + # Nodes 2/3 are used for testing + self.nodes.append(start_node(2, self.options.tmpdir, ["-debug", "-addressindex", "-relaypriority=0"])) + self.nodes.append(start_node(3, self.options.tmpdir, ["-debug", "-addressindex"])) + connect_nodes(self.nodes[0], 1) + connect_nodes(self.nodes[0], 2) + connect_nodes(self.nodes[0], 3) + + self.is_network_split = False + self.sync_all() + + def run_test(self): + print "Mining blocks..." + self.nodes[0].generate(105) + self.sync_all() + + chain_height = self.nodes[1].getblockcount() + assert_equal(chain_height, 105) + assert_equal(self.nodes[1].getbalance(), 0) + assert_equal(self.nodes[2].getbalance(), 0) + + # Check that balances are correct + balance0 = self.nodes[1].getaddressbalance("2N2JD6wb56AfK4tfmM6PwdVmoYk2dCKf4Br") + assert_equal(balance0["balance"], 0) + + # Check p2pkh and p2sh address indexes + print "Testing p2pkh and p2sh address index..." + + txid0 = self.nodes[0].sendtoaddress("mo9ncXisMeAoXwqcV5EWuyncbmCcQN4rVs", 10) + self.nodes[0].generate(1) + + txidb0 = self.nodes[0].sendtoaddress("2N2JD6wb56AfK4tfmM6PwdVmoYk2dCKf4Br", 10) + self.nodes[0].generate(1) + + txid1 = self.nodes[0].sendtoaddress("mo9ncXisMeAoXwqcV5EWuyncbmCcQN4rVs", 15) + self.nodes[0].generate(1) + + txidb1 = self.nodes[0].sendtoaddress("2N2JD6wb56AfK4tfmM6PwdVmoYk2dCKf4Br", 15) + self.nodes[0].generate(1) + + txid2 = self.nodes[0].sendtoaddress("mo9ncXisMeAoXwqcV5EWuyncbmCcQN4rVs", 20) + self.nodes[0].generate(1) + + txidb2 = self.nodes[0].sendtoaddress("2N2JD6wb56AfK4tfmM6PwdVmoYk2dCKf4Br", 20) + self.nodes[0].generate(1) + + self.sync_all() + + txids = self.nodes[1].getaddresstxids("mo9ncXisMeAoXwqcV5EWuyncbmCcQN4rVs") + assert_equal(len(txids), 3) + assert_equal(txids[0], txid0) + assert_equal(txids[1], txid1) + assert_equal(txids[2], txid2) + + txidsb = self.nodes[1].getaddresstxids("2N2JD6wb56AfK4tfmM6PwdVmoYk2dCKf4Br") + assert_equal(len(txidsb), 3) + assert_equal(txidsb[0], txidb0) + assert_equal(txidsb[1], txidb1) + assert_equal(txidsb[2], txidb2) + + # Check that limiting by height works + print "Testing querying txids by range of block heights.." + height_txids = self.nodes[1].getaddresstxids({ + "addresses": ["2N2JD6wb56AfK4tfmM6PwdVmoYk2dCKf4Br"], + "start": 105, + "end": 110 + }) + assert_equal(len(height_txids), 2) + assert_equal(height_txids[0], txidb0) + assert_equal(height_txids[1], txidb1) + + # Check that multiple addresses works + multitxids = self.nodes[1].getaddresstxids({"addresses": ["2N2JD6wb56AfK4tfmM6PwdVmoYk2dCKf4Br", "mo9ncXisMeAoXwqcV5EWuyncbmCcQN4rVs"]}) + assert_equal(len(multitxids), 6) + assert_equal(multitxids[0], txid0) + assert_equal(multitxids[1], txidb0) + assert_equal(multitxids[2], txid1) + assert_equal(multitxids[3], txidb1) + assert_equal(multitxids[4], txid2) + assert_equal(multitxids[5], txidb2) + + # Check that balances are correct + balance0 = self.nodes[1].getaddressbalance("2N2JD6wb56AfK4tfmM6PwdVmoYk2dCKf4Br") + assert_equal(balance0["balance"], 45 * 100000000) + + # Check that outputs with the same address will only return one txid + print "Testing for txid uniqueness..." + addressHash = "6349a418fc4578d10a372b54b45c280cc8c4382f".decode("hex") + scriptPubKey = CScript([OP_HASH160, addressHash, OP_EQUAL]) + unspent = self.nodes[0].listunspent() + tx = CTransaction() + tx.vin = [CTxIn(COutPoint(int(unspent[0]["txid"], 16), unspent[0]["vout"]))] + tx.vout = [CTxOut(10, scriptPubKey), CTxOut(11, scriptPubKey)] + tx.rehash() + + signed_tx = self.nodes[0].signrawtransaction(binascii.hexlify(tx.serialize()).decode("utf-8")) + sent_txid = self.nodes[0].sendrawtransaction(signed_tx["hex"], True) + + self.nodes[0].generate(1) + self.sync_all() + + txidsmany = self.nodes[1].getaddresstxids("2N2JD6wb56AfK4tfmM6PwdVmoYk2dCKf4Br") + assert_equal(len(txidsmany), 4) + assert_equal(txidsmany[3], sent_txid) + + # Check that balances are correct + print "Testing balances..." + balance0 = self.nodes[1].getaddressbalance("2N2JD6wb56AfK4tfmM6PwdVmoYk2dCKf4Br") + assert_equal(balance0["balance"], 45 * 100000000 + 21) + + # Check that balances are correct after spending + print "Testing balances after spending..." + privkey2 = "cSdkPxkAjA4HDr5VHgsebAPDEh9Gyub4HK8UJr2DFGGqKKy4K5sG" + address2 = "mgY65WSfEmsyYaYPQaXhmXMeBhwp4EcsQW" + addressHash2 = "0b2f0a0c31bfe0406b0ccc1381fdbe311946dadc".decode("hex") + scriptPubKey2 = CScript([OP_DUP, OP_HASH160, addressHash2, OP_EQUALVERIFY, OP_CHECKSIG]) + self.nodes[0].importprivkey(privkey2) + + unspent = self.nodes[0].listunspent() + tx = CTransaction() + tx.vin = [CTxIn(COutPoint(int(unspent[0]["txid"], 16), unspent[0]["vout"]))] + amount = unspent[0]["amount"] * 100000000 + tx.vout = [CTxOut(amount, scriptPubKey2)] + tx.rehash() + signed_tx = self.nodes[0].signrawtransaction(binascii.hexlify(tx.serialize()).decode("utf-8")) + spending_txid = self.nodes[0].sendrawtransaction(signed_tx["hex"], True) + self.nodes[0].generate(1) + self.sync_all() + balance1 = self.nodes[1].getaddressbalance(address2) + assert_equal(balance1["balance"], amount) + + tx = CTransaction() + tx.vin = [CTxIn(COutPoint(int(spending_txid, 16), 0))] + send_amount = 1 * 100000000 + 12840 + change_amount = amount - send_amount - 10000 + tx.vout = [CTxOut(change_amount, scriptPubKey2), CTxOut(send_amount, scriptPubKey)] + tx.rehash() + + signed_tx = self.nodes[0].signrawtransaction(binascii.hexlify(tx.serialize()).decode("utf-8")) + sent_txid = self.nodes[0].sendrawtransaction(signed_tx["hex"], True) + self.nodes[0].generate(1) + self.sync_all() + + balance2 = self.nodes[1].getaddressbalance(address2) + assert_equal(balance2["balance"], change_amount) + + # Check that deltas are returned correctly + deltas = self.nodes[1].getaddressdeltas({"addresses": [address2], "start": 1, "end": 200}) + balance3 = 0 + for delta in deltas: + balance3 += delta["satoshis"] + assert_equal(balance3, change_amount) + assert_equal(deltas[0]["address"], address2) + assert_equal(deltas[0]["blockindex"], 1) + + # Check that entire range will be queried + deltasAll = self.nodes[1].getaddressdeltas({"addresses": [address2]}) + assert_equal(len(deltasAll), len(deltas)) + + # Check that deltas can be returned from range of block heights + deltas = self.nodes[1].getaddressdeltas({"addresses": [address2], "start": 113, "end": 113}) + assert_equal(len(deltas), 1) + + # Check that unspent outputs can be queried + print "Testing utxos..." + utxos = self.nodes[1].getaddressutxos({"addresses": [address2]}) + assert_equal(len(utxos), 1) + assert_equal(utxos[0]["satoshis"], change_amount) + + # Check that indexes will be updated with a reorg + print "Testing reorg..." + + best_hash = self.nodes[0].getbestblockhash() + self.nodes[0].invalidateblock(best_hash) + self.nodes[1].invalidateblock(best_hash) + self.nodes[2].invalidateblock(best_hash) + self.nodes[3].invalidateblock(best_hash) + self.sync_all() + + balance4 = self.nodes[1].getaddressbalance(address2) + assert_equal(balance4, balance1) + + utxos2 = self.nodes[1].getaddressutxos({"addresses": [address2]}) + assert_equal(len(utxos2), 1) + assert_equal(utxos2[0]["satoshis"], amount) + + # Check sorting of utxos + self.nodes[2].generate(150) + + txidsort1 = self.nodes[2].sendtoaddress(address2, 50) + self.nodes[2].generate(1) + txidsort2 = self.nodes[2].sendtoaddress(address2, 50) + self.nodes[2].generate(1) + self.sync_all() + + utxos3 = self.nodes[1].getaddressutxos({"addresses": [address2]}) + assert_equal(len(utxos3), 3) + assert_equal(utxos3[0]["height"], 114) + assert_equal(utxos3[1]["height"], 264) + assert_equal(utxos3[2]["height"], 265) + + # Check mempool indexing + print "Testing mempool indexing..." + + privKey3 = "cVfUn53hAbRrDEuMexyfgDpZPhF7KqXpS8UZevsyTDaugB7HZ3CD" + address3 = "mw4ynwhS7MmrQ27hr82kgqu7zryNDK26JB" + addressHash3 = "aa9872b5bbcdb511d89e0e11aa27da73fd2c3f50".decode("hex") + scriptPubKey3 = CScript([OP_DUP, OP_HASH160, addressHash3, OP_EQUALVERIFY, OP_CHECKSIG]) + address4 = "2N8oFVB2vThAKury4vnLquW2zVjsYjjAkYQ" + scriptPubKey4 = CScript([OP_HASH160, addressHash3, OP_EQUAL]) + unspent = self.nodes[2].listunspent() + + tx = CTransaction() + tx.vin = [CTxIn(COutPoint(int(unspent[0]["txid"], 16), unspent[0]["vout"]))] + amount = unspent[0]["amount"] * 100000000 + tx.vout = [CTxOut(amount, scriptPubKey3)] + tx.rehash() + signed_tx = self.nodes[2].signrawtransaction(binascii.hexlify(tx.serialize()).decode("utf-8")) + memtxid1 = self.nodes[2].sendrawtransaction(signed_tx["hex"], True) + time.sleep(2) + + tx2 = CTransaction() + tx2.vin = [CTxIn(COutPoint(int(unspent[1]["txid"], 16), unspent[1]["vout"]))] + amount = unspent[1]["amount"] * 100000000 + tx2.vout = [ + CTxOut(amount / 4, scriptPubKey3), + CTxOut(amount / 4, scriptPubKey3), + CTxOut(amount / 4, scriptPubKey4), + CTxOut(amount / 4, scriptPubKey4) + ] + tx2.rehash() + signed_tx2 = self.nodes[2].signrawtransaction(binascii.hexlify(tx2.serialize()).decode("utf-8")) + memtxid2 = self.nodes[2].sendrawtransaction(signed_tx2["hex"], True) + time.sleep(2) + + mempool = self.nodes[2].getaddressmempool({"addresses": [address3]}) + assert_equal(len(mempool), 3) + assert_equal(mempool[0]["txid"], memtxid1) + assert_equal(mempool[0]["address"], address3) + assert_equal(mempool[0]["index"], 0) + assert_equal(mempool[1]["txid"], memtxid2) + assert_equal(mempool[1]["index"], 0) + assert_equal(mempool[2]["txid"], memtxid2) + assert_equal(mempool[2]["index"], 1) + + self.nodes[2].generate(1); + self.sync_all(); + mempool2 = self.nodes[2].getaddressmempool({"addresses": [address3]}) + assert_equal(len(mempool2), 0) + + tx = CTransaction() + tx.vin = [ + CTxIn(COutPoint(int(memtxid2, 16), 0)), + CTxIn(COutPoint(int(memtxid2, 16), 1)) + ] + tx.vout = [CTxOut(amount / 2 - 10000, scriptPubKey2)] + tx.rehash() + self.nodes[2].importprivkey(privKey3) + signed_tx3 = self.nodes[2].signrawtransaction(binascii.hexlify(tx.serialize()).decode("utf-8")) + memtxid3 = self.nodes[2].sendrawtransaction(signed_tx3["hex"], True) + time.sleep(2) + + mempool3 = self.nodes[2].getaddressmempool({"addresses": [address3]}) + assert_equal(len(mempool3), 2) + assert_equal(mempool3[0]["prevtxid"], memtxid2) + assert_equal(mempool3[0]["prevout"], 0) + assert_equal(mempool3[1]["prevtxid"], memtxid2) + assert_equal(mempool3[1]["prevout"], 1) + + # sending and receiving to the same address + privkey1 = "cQY2s58LhzUCmEXN8jtAp1Etnijx78YRZ466w4ikX1V4UpTpbsf8" + address1 = "myAUWSHnwsQrhuMWv4Br6QsCnpB41vFwHn" + address1hash = "c192bff751af8efec15135d42bfeedf91a6f3e34".decode("hex") + address1script = CScript([OP_DUP, OP_HASH160, address1hash, OP_EQUALVERIFY, OP_CHECKSIG]) + + self.nodes[0].sendtoaddress(address1, 10) + self.nodes[0].generate(1) + self.sync_all() + + utxos = self.nodes[1].getaddressutxos({"addresses": [address1]}) + assert_equal(len(utxos), 1) + + tx = CTransaction() + tx.vin = [ + CTxIn(COutPoint(int(utxos[0]["txid"], 16), utxos[0]["outputIndex"])) + ] + amount = utxos[0]["satoshis"] - 1000 + tx.vout = [CTxOut(amount, address1script)] + tx.rehash() + self.nodes[0].importprivkey(privkey1) + signed_tx = self.nodes[0].signrawtransaction(binascii.hexlify(tx.serialize()).decode("utf-8")) + mem_txid = self.nodes[0].sendrawtransaction(signed_tx["hex"], True) + + self.sync_all() + mempool_deltas = self.nodes[2].getaddressmempool({"addresses": [address1]}) + assert_equal(len(mempool_deltas), 2) + + # Include chaininfo in results + print "Testing results with chain info..." + + deltas_with_info = self.nodes[1].getaddressdeltas({ + "addresses": [address2], + "start": 1, + "end": 200, + "chainInfo": True + }) + start_block_hash = self.nodes[1].getblockhash(1); + end_block_hash = self.nodes[1].getblockhash(200); + assert_equal(deltas_with_info["start"]["height"], 1) + assert_equal(deltas_with_info["start"]["hash"], start_block_hash) + assert_equal(deltas_with_info["end"]["height"], 200) + assert_equal(deltas_with_info["end"]["hash"], end_block_hash) + + utxos_with_info = self.nodes[1].getaddressutxos({"addresses": [address2], "chainInfo": True}) + expected_tip_block_hash = self.nodes[1].getblockhash(267); + assert_equal(utxos_with_info["height"], 267) + assert_equal(utxos_with_info["hash"], expected_tip_block_hash) + + print "Passed\n" + + +if __name__ == '__main__': + AddressIndexTest().main() \ No newline at end of file diff --git a/qa/rpc-tests/spentindex.py b/qa/rpc-tests/spentindex.py new file mode 100644 index 00000000000..ebe2b8cc228 --- /dev/null +++ b/qa/rpc-tests/spentindex.py @@ -0,0 +1,139 @@ +#!/usr/bin/env python2 +# Copyright (c) 2014-2015 The Bitcoin Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. + +# +# Test addressindex generation and fetching +# + +import time +from test_framework.test_framework import BitcoinTestFramework +from test_framework.util import * +from test_framework.script import * +from test_framework.mininode import * +import binascii + +class SpentIndexTest(BitcoinTestFramework): + + def setup_chain(self): + print("Initializing test directory "+self.options.tmpdir) + initialize_chain_clean(self.options.tmpdir, 4) + + def setup_network(self): + self.nodes = [] + # Nodes 0/1 are "wallet" nodes + self.nodes.append(start_node(0, self.options.tmpdir, ["-debug"])) + self.nodes.append(start_node(1, self.options.tmpdir, ["-debug", "-spentindex"])) + # Nodes 2/3 are used for testing + self.nodes.append(start_node(2, self.options.tmpdir, ["-debug", "-spentindex"])) + self.nodes.append(start_node(3, self.options.tmpdir, ["-debug", "-spentindex", "-txindex"])) + connect_nodes(self.nodes[0], 1) + connect_nodes(self.nodes[0], 2) + connect_nodes(self.nodes[0], 3) + + self.is_network_split = False + self.sync_all() + + def run_test(self): + print "Mining blocks..." + self.nodes[0].generate(105) + self.sync_all() + + chain_height = self.nodes[1].getblockcount() + assert_equal(chain_height, 105) + + # Check that + print "Testing spent index..." + + privkey = "cSdkPxkAjA4HDr5VHgsebAPDEh9Gyub4HK8UJr2DFGGqKKy4K5sG" + address = "mgY65WSfEmsyYaYPQaXhmXMeBhwp4EcsQW" + addressHash = "0b2f0a0c31bfe0406b0ccc1381fdbe311946dadc".decode("hex") + scriptPubKey = CScript([OP_DUP, OP_HASH160, addressHash, OP_EQUALVERIFY, OP_CHECKSIG]) + unspent = self.nodes[0].listunspent() + tx = CTransaction() + amount = unspent[0]["amount"] * 100000000 + tx.vin = [CTxIn(COutPoint(int(unspent[0]["txid"], 16), unspent[0]["vout"]))] + tx.vout = [CTxOut(amount, scriptPubKey)] + tx.rehash() + + signed_tx = self.nodes[0].signrawtransaction(binascii.hexlify(tx.serialize()).decode("utf-8")) + txid = self.nodes[0].sendrawtransaction(signed_tx["hex"], True) + self.nodes[0].generate(1) + self.sync_all() + + print "Testing getspentinfo method..." + + # Check that the spentinfo works standalone + info = self.nodes[1].getspentinfo({"txid": unspent[0]["txid"], "index": unspent[0]["vout"]}) + assert_equal(info["txid"], txid) + assert_equal(info["index"], 0) + assert_equal(info["height"], 106) + + print "Testing getrawtransaction method..." + + # Check that verbose raw transaction includes spent info + txVerbose = self.nodes[3].getrawtransaction(unspent[0]["txid"], 1) + assert_equal(txVerbose["vout"][unspent[0]["vout"]]["spentTxId"], txid) + assert_equal(txVerbose["vout"][unspent[0]["vout"]]["spentIndex"], 0) + assert_equal(txVerbose["vout"][unspent[0]["vout"]]["spentHeight"], 106) + + # Check that verbose raw transaction includes input values + txVerbose2 = self.nodes[3].getrawtransaction(txid, 1) + assert_equal(txVerbose2["vin"][0]["value"], Decimal(unspent[0]["amount"])) + assert_equal(txVerbose2["vin"][0]["valueSat"], amount) + + # Check that verbose raw transaction includes address values and input values + privkey2 = "cSdkPxkAjA4HDr5VHgsebAPDEh9Gyub4HK8UJr2DFGGqKKy4K5sG" + address2 = "mgY65WSfEmsyYaYPQaXhmXMeBhwp4EcsQW" + addressHash2 = "0b2f0a0c31bfe0406b0ccc1381fdbe311946dadc".decode("hex") + scriptPubKey2 = CScript([OP_DUP, OP_HASH160, addressHash2, OP_EQUALVERIFY, OP_CHECKSIG]) + tx2 = CTransaction() + tx2.vin = [CTxIn(COutPoint(int(txid, 16), 0))] + tx2.vout = [CTxOut(amount, scriptPubKey2)] + tx.rehash() + self.nodes[0].importprivkey(privkey) + signed_tx2 = self.nodes[0].signrawtransaction(binascii.hexlify(tx2.serialize()).decode("utf-8")) + txid2 = self.nodes[0].sendrawtransaction(signed_tx2["hex"], True) + + # Check the mempool index + self.sync_all() + txVerbose3 = self.nodes[1].getrawtransaction(txid2, 1) + assert_equal(txVerbose3["vin"][0]["address"], address2) + assert_equal(txVerbose3["vin"][0]["value"], Decimal(unspent[0]["amount"])) + assert_equal(txVerbose3["vin"][0]["valueSat"], amount) + + # Check the database index + block_hash = self.nodes[0].generate(1) + self.sync_all() + + txVerbose4 = self.nodes[3].getrawtransaction(txid2, 1) + assert_equal(txVerbose4["vin"][0]["address"], address2) + assert_equal(txVerbose4["vin"][0]["value"], Decimal(unspent[0]["amount"])) + assert_equal(txVerbose4["vin"][0]["valueSat"], amount) + + + # Check block deltas + print "Testing getblockdeltas..." + + block = self.nodes[3].getblockdeltas(block_hash[0]) + assert_equal(len(block["deltas"]), 2) + assert_equal(block["deltas"][0]["index"], 0) + assert_equal(len(block["deltas"][0]["inputs"]), 0) + assert_equal(len(block["deltas"][0]["outputs"]), 0) + assert_equal(block["deltas"][1]["index"], 1) + assert_equal(block["deltas"][1]["txid"], txid2) + assert_equal(block["deltas"][1]["inputs"][0]["index"], 0) + assert_equal(block["deltas"][1]["inputs"][0]["address"], "mgY65WSfEmsyYaYPQaXhmXMeBhwp4EcsQW") + assert_equal(block["deltas"][1]["inputs"][0]["satoshis"], amount * -1) + assert_equal(block["deltas"][1]["inputs"][0]["prevtxid"], txid) + assert_equal(block["deltas"][1]["inputs"][0]["prevout"], 0) + assert_equal(block["deltas"][1]["outputs"][0]["index"], 0) + assert_equal(block["deltas"][1]["outputs"][0]["address"], "mgY65WSfEmsyYaYPQaXhmXMeBhwp4EcsQW") + assert_equal(block["deltas"][1]["outputs"][0]["satoshis"], amount) + + print "Passed\n" + + +if __name__ == '__main__': + SpentIndexTest().main() \ No newline at end of file diff --git a/qa/rpc-tests/timestampindex.py b/qa/rpc-tests/timestampindex.py new file mode 100644 index 00000000000..23244159fee --- /dev/null +++ b/qa/rpc-tests/timestampindex.py @@ -0,0 +1,61 @@ +#!/usr/bin/env python2 +# Copyright (c) 2014-2015 The Bitcoin Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. + +# +# Test timestampindex generation and fetching +# + +import time + +from test_framework.test_framework import BitcoinTestFramework +from test_framework.util import * + + +class TimestampIndexTest(BitcoinTestFramework): + + def setup_chain(self): + print("Initializing test directory "+self.options.tmpdir) + initialize_chain_clean(self.options.tmpdir, 4) + + def setup_network(self): + self.nodes = [] + # Nodes 0/1 are "wallet" nodes + self.nodes.append(start_node(0, self.options.tmpdir, ["-debug"])) + self.nodes.append(start_node(1, self.options.tmpdir, ["-debug", "-timestampindex"])) + # Nodes 2/3 are used for testing + self.nodes.append(start_node(2, self.options.tmpdir, ["-debug"])) + self.nodes.append(start_node(3, self.options.tmpdir, ["-debug", "-timestampindex"])) + connect_nodes(self.nodes[0], 1) + connect_nodes(self.nodes[0], 2) + connect_nodes(self.nodes[0], 3) + + self.is_network_split = False + self.sync_all() + + def run_test(self): + print "Mining 25 blocks..." + blockhashes = self.nodes[0].generate(25) + time.sleep(3) + print "Mining 25 blocks..." + blockhashes.extend(self.nodes[0].generate(25)) + time.sleep(3) + print "Mining 25 blocks..." + blockhashes.extend(self.nodes[0].generate(25)) + self.sync_all() + low = self.nodes[1].getblock(blockhashes[0])["time"] + high = low + 76 + + print "Checking timestamp index..." + hashes = self.nodes[1].getblockhashes(high, low) + + assert_equal(len(hashes), len(blockhashes)) + + assert_equal(hashes, blockhashes) + + print "Passed\n" + + +if __name__ == '__main__': + TimestampIndexTest().main() \ No newline at end of file diff --git a/qa/rpc-tests/txindex.py b/qa/rpc-tests/txindex.py new file mode 100644 index 00000000000..e59b6d7d93d --- /dev/null +++ b/qa/rpc-tests/txindex.py @@ -0,0 +1,73 @@ +#!/usr/bin/env python2 +# Copyright (c) 2014-2015 The Bitcoin Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. + +# +# Test txindex generation and fetching +# + +import time +from test_framework.test_framework import BitcoinTestFramework +from test_framework.util import * +from test_framework.script import * +from test_framework.mininode import * +import binascii + +class TxIndexTest(BitcoinTestFramework): + + def setup_chain(self): + print("Initializing test directory "+self.options.tmpdir) + initialize_chain_clean(self.options.tmpdir, 4) + + def setup_network(self): + self.nodes = [] + # Nodes 0/1 are "wallet" nodes + self.nodes.append(start_node(0, self.options.tmpdir, ["-debug"])) + self.nodes.append(start_node(1, self.options.tmpdir, ["-debug", "-txindex"])) + # Nodes 2/3 are used for testing + self.nodes.append(start_node(2, self.options.tmpdir, ["-debug", "-txindex"])) + self.nodes.append(start_node(3, self.options.tmpdir, ["-debug", "-txindex"])) + connect_nodes(self.nodes[0], 1) + connect_nodes(self.nodes[0], 2) + connect_nodes(self.nodes[0], 3) + + self.is_network_split = False + self.sync_all() + + def run_test(self): + print "Mining blocks..." + self.nodes[0].generate(105) + self.sync_all() + + chain_height = self.nodes[1].getblockcount() + assert_equal(chain_height, 105) + + print "Testing transaction index..." + + privkey = "cSdkPxkAjA4HDr5VHgsebAPDEh9Gyub4HK8UJr2DFGGqKKy4K5sG" + address = "mgY65WSfEmsyYaYPQaXhmXMeBhwp4EcsQW" + addressHash = "0b2f0a0c31bfe0406b0ccc1381fdbe311946dadc".decode("hex") + scriptPubKey = CScript([OP_DUP, OP_HASH160, addressHash, OP_EQUALVERIFY, OP_CHECKSIG]) + unspent = self.nodes[0].listunspent() + tx = CTransaction() + amount = unspent[0]["amount"] * 100000000 + tx.vin = [CTxIn(COutPoint(int(unspent[0]["txid"], 16), unspent[0]["vout"]))] + tx.vout = [CTxOut(amount, scriptPubKey)] + tx.rehash() + + signed_tx = self.nodes[0].signrawtransaction(binascii.hexlify(tx.serialize()).decode("utf-8")) + txid = self.nodes[0].sendrawtransaction(signed_tx["hex"], True) + self.nodes[0].generate(1) + self.sync_all() + + # Check verbose raw transaction results + verbose = self.nodes[3].getrawtransaction(unspent[0]["txid"], 1) + assert_equal(verbose["vout"][0]["valueSat"], 5000000000); + assert_equal(verbose["vout"][0]["value"], 50); + + print "Passed\n" + + +if __name__ == '__main__': + TxIndexTest().main() \ No newline at end of file diff --git a/qa/rpc-tests/wallet.py b/qa/rpc-tests/wallet.py index ff827892a22..555a3e90281 100755 --- a/qa/rpc-tests/wallet.py +++ b/qa/rpc-tests/wallet.py @@ -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: diff --git a/qa/rpc-tests/wallet_shieldcoinbase.py b/qa/rpc-tests/wallet_shieldcoinbase.py new file mode 100644 index 00000000000..7d2d63e629a --- /dev/null +++ b/qa/rpc-tests/wallet_shieldcoinbase.py @@ -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() diff --git a/src/Makefile.am b/src/Makefile.am index 1f67047986e..af17fbf53b7 100755 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -185,6 +185,7 @@ BITCOIN_CORE_H = \ validationinterface.h \ version.h \ wallet/asyncrpcoperation_sendmany.h \ + wallet/asyncrpcoperation_shieldcoinbase.h \ wallet/crypter.h \ wallet/db.h \ wallet/wallet.h \ @@ -271,6 +272,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 \ diff --git a/src/Makefile.test.include b/src/Makefile.test.include index d18f2964609..7e8eeaae5d4 100755 --- a/src/Makefile.test.include +++ b/src/Makefile.test.include @@ -15,7 +15,9 @@ EXTRA_DIST += \ test/data/tx394b54bb.hex \ test/data/txcreate1.hex \ test/data/txcreate2.hex \ - test/data/txcreatesign.hex + test/data/txcreatesign.hex \ + test/wallet-utility.py \ + test/data/wallet.dat JSON_TEST_FILES = \ test/data/script_valid.json \ @@ -75,6 +77,7 @@ BITCOIN_TESTS =\ test/sanity_tests.cpp \ test/scheduler_tests.cpp \ test/script_P2SH_tests.cpp \ + test/script_P2PKH_tests.cpp \ test/script_tests.cpp \ test/scriptnum_tests.cpp \ test/serialize_tests.cpp \ @@ -136,6 +139,11 @@ bitcoin_test_clean : FORCE check-local: @echo "Running test/bitcoin-util-test.py..." $(AM_V_at)srcdir=$(srcdir) PYTHONPATH=$(builddir)/test $(srcdir)/test/bitcoin-util-test.py + +if ENABLE_WALLET + @echo "Running test/wallet-utility.py..." + $(AM_V_at)srcdir=$(srcdir) PYTHONPATH=$(builddir)/test $(srcdir)/test/wallet-utility.py +endif $(AM_V_at)$(MAKE) $(AM_MAKEFLAGS) -C secp256k1 check $(AM_V_at)$(MAKE) $(AM_MAKEFLAGS) -C univalue check diff --git a/src/base58.h b/src/base58.h index 599fde24a0c..0fad3db0f9c 100755 --- a/src/base58.h +++ b/src/base58.h @@ -141,8 +141,7 @@ class CBitcoinAddress : public CBase58Data { CTxDestination Get() const; bool GetKeyID(CKeyID &keyID) const; - bool GetIndexKey(uint160& hashBytes, int& type) const; - + bool GetIndexKey(uint160& hashBytes, int& type) const; bool IsScript() const; }; diff --git a/src/init.cpp b/src/init.cpp index cb98452c931..bab4380098c 100755 --- a/src/init.cpp +++ b/src/init.cpp @@ -401,7 +401,7 @@ std::string HelpMessage(HelpMessageMode mode) #endif strUsage += HelpMessageOpt("-txindex", strprintf(_("Maintain a full transaction index, used by the getrawtransaction rpc call (default: %u)"), 0)); - strUsage += HelpMessageOpt("-addressindex", strprintf(_("Maintain a full address index, used to query for the balance, txids and unspent outputs for addresses (default: %u)"), DEFAULT_ADDRESSINDEX)); + strUsage += HelpMessageOpt("-addressindex", strprintf(_("Maintain a full address index, used to query for the balance, txids and unspent outputs for addresses (default: %u)"), DEFAULT_ADDRESSINDEX)); strUsage += HelpMessageOpt("-timestampindex", strprintf(_("Maintain a timestamp index for block hashes, used to query blocks hashes by a range of timestamps (default: %u)"), DEFAULT_TIMESTAMPINDEX)); strUsage += HelpMessageOpt("-spentindex", strprintf(_("Maintain a full spent index, used to query the spending txid and input index for an outpoint (default: %u)"), DEFAULT_SPENTINDEX)); @@ -1364,7 +1364,7 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler) } } - // block tree db settings + // block tree db settings int dbMaxOpenFiles = GetArg("-dbmaxopenfiles", DEFAULT_DB_MAX_OPEN_FILES); bool dbCompression = GetBoolArg("-dbcompression", DEFAULT_DB_COMPRESSION); @@ -1378,14 +1378,14 @@ bool AppInit2(boost::thread_group& threadGroup, CScheduler& scheduler) nTotalCache = std::max(nTotalCache, nMinDbCache << 20); // total cache cannot be less than nMinDbCache nTotalCache = std::min(nTotalCache, nMaxDbCache << 20); // total cache cannot be greated than nMaxDbcache int64_t nBlockTreeDBCache = nTotalCache / 8; - if (GetBoolArg("-addressindex", DEFAULT_ADDRESSINDEX) || GetBoolArg("-spentindex", DEFAULT_SPENTINDEX)) { - // enable 3/4 of the cache if addressindex and/or spentindex is enabled - nBlockTreeDBCache = nTotalCache * 3 / 4; - } else { - if (nBlockTreeDBCache > (1 << 21) && !GetBoolArg("-txindex", false)) { - nBlockTreeDBCache = (1 << 21); // block tree db cache shouldn't be larger than 2 MiB - } - } + if (GetBoolArg("-addressindex", DEFAULT_ADDRESSINDEX) || GetBoolArg("-spentindex", DEFAULT_SPENTINDEX)) { + // enable 3/4 of the cache if addressindex and/or spentindex is enabled + nBlockTreeDBCache = nTotalCache * 3 / 4; + } else { + if (nBlockTreeDBCache > (1 << 21) && !GetBoolArg("-txindex", false)) { + nBlockTreeDBCache = (1 << 21); // block tree db cache shouldn't be larger than 2 MiB + } + } nTotalCache -= nBlockTreeDBCache; int64_t nCoinDBCache = std::min(nTotalCache / 2, (nTotalCache / 4) + (1 << 23)); // use 25%-50% of the remainder for disk cache nTotalCache -= nCoinDBCache; diff --git a/src/main.cpp b/src/main.cpp index 320954a0219..a96fdf315f6 100755 --- a/src/main.cpp +++ b/src/main.cpp @@ -28,6 +28,7 @@ #include "utilmoneystr.h" #include "validationinterface.h" #include "wallet/asyncrpcoperation_sendmany.h" +#include "wallet/asyncrpcoperation_shieldcoinbase.h" #include @@ -61,11 +62,11 @@ bool fExperimentalMode = false; bool fImporting = false; bool fReindex = false; bool fTxIndex = false; -bool fHavePruned = false; -bool fSpentIndex = false; bool fAddressIndex = false; -bool fPruneMode = false; bool fTimestampIndex = false; +bool fSpentIndex = false; +bool fHavePruned = false; +bool fPruneMode = false; bool fIsBareMultisigStd = true; bool fCheckBlockIndex = false; bool fCheckpointsEnabled = true; @@ -1258,8 +1259,8 @@ bool AcceptToMemoryPool(CTxMemPool& pool, CValidationState &state, const CTransa // Store transaction in memory pool.addUnchecked(hash, entry, !IsInitialBlockDownload()); - - // Add memory address index + + // Add memory address index if (fAddressIndex) { pool.addAddressIndex(entry, view); } @@ -1943,8 +1944,8 @@ bool DisconnectBlock(CBlock& block, CValidationState& state, CBlockIndex* pindex if (blockUndo.vtxundo.size() + 1 != block.vtx.size()) return error("DisconnectBlock(): block and undo data inconsistent"); - - std::vector > addressIndex; + + std::vector > addressIndex; std::vector > addressUnspentIndex; std::vector > spentIndex; @@ -1952,37 +1953,37 @@ bool DisconnectBlock(CBlock& block, CValidationState& state, CBlockIndex* pindex for (int i = block.vtx.size() - 1; i >= 0; i--) { const CTransaction &tx = block.vtx[i]; uint256 hash = tx.GetHash(); - - if (fAddressIndex) { - - for (unsigned int k = tx.vout.size(); k-- > 0;) { - const CTxOut &out = tx.vout[k]; - - if (out.scriptPubKey.IsPayToScriptHash()) { - vector hashBytes(out.scriptPubKey.begin()+2, out.scriptPubKey.begin()+22); - - // undo receiving activity - addressIndex.push_back(make_pair(CAddressIndexKey(2, uint160(hashBytes), pindex->nHeight, i, hash, k, false), out.nValue)); - - // undo unspent index - addressUnspentIndex.push_back(make_pair(CAddressUnspentKey(2, uint160(hashBytes), hash, k), CAddressUnspentValue())); - - } else if (out.scriptPubKey.IsPayToPublicKeyHash()) { - vector hashBytes(out.scriptPubKey.begin()+3, out.scriptPubKey.begin()+23); - - // undo receiving activity - addressIndex.push_back(make_pair(CAddressIndexKey(1, uint160(hashBytes), pindex->nHeight, i, hash, k, false), out.nValue)); - - // undo unspent index - addressUnspentIndex.push_back(make_pair(CAddressUnspentKey(1, uint160(hashBytes), hash, k), CAddressUnspentValue())); - - } else { - continue; - } - - } - - } + + if (fAddressIndex) { + + for (unsigned int k = tx.vout.size(); k-- > 0;) { + const CTxOut &out = tx.vout[k]; + + if (out.scriptPubKey.IsPayToScriptHash()) { + vector hashBytes(out.scriptPubKey.begin()+2, out.scriptPubKey.begin()+22); + + // undo receiving activity + addressIndex.push_back(make_pair(CAddressIndexKey(2, uint160(hashBytes), pindex->nHeight, i, hash, k, false), out.nValue)); + + // undo unspent index + addressUnspentIndex.push_back(make_pair(CAddressUnspentKey(2, uint160(hashBytes), hash, k), CAddressUnspentValue())); + + } else if (out.scriptPubKey.IsPayToPublicKeyHash()) { + vector hashBytes(out.scriptPubKey.begin()+3, out.scriptPubKey.begin()+23); + + // undo receiving activity + addressIndex.push_back(make_pair(CAddressIndexKey(1, uint160(hashBytes), pindex->nHeight, i, hash, k, false), out.nValue)); + + // undo unspent index + addressUnspentIndex.push_back(make_pair(CAddressUnspentKey(1, uint160(hashBytes), hash, k), CAddressUnspentValue())); + + } else { + continue; + } + + } + + } // Check that all outputs are available and match the outputs in the block itself // exactly. @@ -2020,39 +2021,39 @@ bool DisconnectBlock(CBlock& block, CValidationState& state, CBlockIndex* pindex const CTxInUndo &undo = txundo.vprevout[j]; if (!ApplyTxInUndo(undo, view, out)) fClean = false; - - const CTxIn input = tx.vin[j]; - - if (fSpentIndex) { - // undo and delete the spent index - spentIndex.push_back(make_pair(CSpentIndexKey(input.prevout.hash, input.prevout.n), CSpentIndexValue())); - } - - if (fAddressIndex) { - const CTxOut &prevout = view.GetOutputFor(tx.vin[j]); - if (prevout.scriptPubKey.IsPayToScriptHash()) { - vector hashBytes(prevout.scriptPubKey.begin()+2, prevout.scriptPubKey.begin()+22); - - // undo spending activity - addressIndex.push_back(make_pair(CAddressIndexKey(2, uint160(hashBytes), pindex->nHeight, i, hash, j, true), prevout.nValue * -1)); - - // restore unspent index - addressUnspentIndex.push_back(make_pair(CAddressUnspentKey(2, uint160(hashBytes), input.prevout.hash, input.prevout.n), CAddressUnspentValue(prevout.nValue, prevout.scriptPubKey, undo.nHeight))); - - - } else if (prevout.scriptPubKey.IsPayToPublicKeyHash()) { - vector hashBytes(prevout.scriptPubKey.begin()+3, prevout.scriptPubKey.begin()+23); - - // undo spending activity - addressIndex.push_back(make_pair(CAddressIndexKey(1, uint160(hashBytes), pindex->nHeight, i, hash, j, true), prevout.nValue * -1)); - - // restore unspent index - addressUnspentIndex.push_back(make_pair(CAddressUnspentKey(1, uint160(hashBytes), input.prevout.hash, input.prevout.n), CAddressUnspentValue(prevout.nValue, prevout.scriptPubKey, undo.nHeight))); - - } else { - continue; - } - } + + const CTxIn input = tx.vin[j]; + + if (fSpentIndex) { + // undo and delete the spent index + spentIndex.push_back(make_pair(CSpentIndexKey(input.prevout.hash, input.prevout.n), CSpentIndexValue())); + } + + if (fAddressIndex) { + const CTxOut &prevout = view.GetOutputFor(tx.vin[j]); + if (prevout.scriptPubKey.IsPayToScriptHash()) { + vector hashBytes(prevout.scriptPubKey.begin()+2, prevout.scriptPubKey.begin()+22); + + // undo spending activity + addressIndex.push_back(make_pair(CAddressIndexKey(2, uint160(hashBytes), pindex->nHeight, i, hash, j, true), prevout.nValue * -1)); + + // restore unspent index + addressUnspentIndex.push_back(make_pair(CAddressUnspentKey(2, uint160(hashBytes), input.prevout.hash, input.prevout.n), CAddressUnspentValue(prevout.nValue, prevout.scriptPubKey, undo.nHeight))); + + + } else if (prevout.scriptPubKey.IsPayToPublicKeyHash()) { + vector hashBytes(prevout.scriptPubKey.begin()+3, prevout.scriptPubKey.begin()+23); + + // undo spending activity + addressIndex.push_back(make_pair(CAddressIndexKey(1, uint160(hashBytes), pindex->nHeight, i, hash, j, true), prevout.nValue * -1)); + + // restore unspent index + addressUnspentIndex.push_back(make_pair(CAddressUnspentKey(1, uint160(hashBytes), input.prevout.hash, input.prevout.n), CAddressUnspentValue(prevout.nValue, prevout.scriptPubKey, undo.nHeight))); + + } else { + continue; + } + } } } } @@ -2067,14 +2068,15 @@ bool DisconnectBlock(CBlock& block, CValidationState& state, CBlockIndex* pindex *pfClean = fClean; return true; } -if (fAddressIndex) { - if (!pblocktree->EraseAddressIndex(addressIndex)) { - return AbortNode(state, "Failed to delete address index"); - } - if (!pblocktree->UpdateAddressUnspentIndex(addressUnspentIndex)) { - return AbortNode(state, "Failed to write address unspent index"); - } - } + + if (fAddressIndex) { + if (!pblocktree->EraseAddressIndex(addressIndex)) { + return AbortNode(state, "Failed to delete address index"); + } + if (!pblocktree->UpdateAddressUnspentIndex(addressUnspentIndex)) { + return AbortNode(state, "Failed to write address unspent index"); + } + } return fClean; } @@ -2242,8 +2244,8 @@ bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pin std::vector > vPos; vPos.reserve(block.vtx.size()); blockundo.vtxundo.reserve(block.vtx.size() - 1); - - std::vector > addressIndex; + + std::vector > addressIndex; std::vector > addressUnspentIndex; std::vector > spentIndex; @@ -2268,8 +2270,8 @@ bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pin for (unsigned int i = 0; i < block.vtx.size(); i++) { const CTransaction &tx = block.vtx[i]; - - const uint256 txhash = tx.GetHash(); + + const uint256 txhash = tx.GetHash(); nInputs += tx.vin.size(); nSigOps += GetLegacySigOpCount(tx); @@ -2287,43 +2289,43 @@ bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pin if (!view.HaveJoinSplitRequirements(tx)) return state.DoS(100, error("ConnectBlock(): JoinSplit requirements not met"), REJECT_INVALID, "bad-txns-joinsplit-requirements-not-met"); - - if (fAddressIndex || fSpentIndex) - { - for (size_t j = 0; j < tx.vin.size(); j++) { - - const CTxIn input = tx.vin[j]; - const CTxOut &prevout = view.GetOutputFor(tx.vin[j]); - uint160 hashBytes; - int addressType; - - if (prevout.scriptPubKey.IsPayToScriptHash()) { - hashBytes = uint160(vector (prevout.scriptPubKey.begin()+2, prevout.scriptPubKey.begin()+22)); - addressType = 2; - } else if (prevout.scriptPubKey.IsPayToPublicKeyHash()) { - hashBytes = uint160(vector (prevout.scriptPubKey.begin()+3, prevout.scriptPubKey.begin()+23)); - addressType = 1; - } else { - hashBytes.SetNull(); - addressType = 0; - } - - if (fAddressIndex && addressType > 0) { - // record spending activity - addressIndex.push_back(make_pair(CAddressIndexKey(addressType, hashBytes, pindex->nHeight, i, txhash, j, true), prevout.nValue * -1)); - - // remove address from unspent index - addressUnspentIndex.push_back(make_pair(CAddressUnspentKey(addressType, hashBytes, input.prevout.hash, input.prevout.n), CAddressUnspentValue())); - } - - if (fSpentIndex) { - // add the spent index to determine the txid and input that spent an output - // and to find the amount and address from an input - spentIndex.push_back(make_pair(CSpentIndexKey(input.prevout.hash, input.prevout.n), CSpentIndexValue(txhash, j, pindex->nHeight, prevout.nValue, addressType, hashBytes))); - } - } - - } + + if (fAddressIndex || fSpentIndex) + { + for (size_t j = 0; j < tx.vin.size(); j++) { + + const CTxIn input = tx.vin[j]; + const CTxOut &prevout = view.GetOutputFor(tx.vin[j]); + uint160 hashBytes; + int addressType; + + if (prevout.scriptPubKey.IsPayToScriptHash()) { + hashBytes = uint160(vector (prevout.scriptPubKey.begin()+2, prevout.scriptPubKey.begin()+22)); + addressType = 2; + } else if (prevout.scriptPubKey.IsPayToPublicKeyHash()) { + hashBytes = uint160(vector (prevout.scriptPubKey.begin()+3, prevout.scriptPubKey.begin()+23)); + addressType = 1; + } else { + hashBytes.SetNull(); + addressType = 0; + } + + if (fAddressIndex && addressType > 0) { + // record spending activity + addressIndex.push_back(make_pair(CAddressIndexKey(addressType, hashBytes, pindex->nHeight, i, txhash, j, true), prevout.nValue * -1)); + + // remove address from unspent index + addressUnspentIndex.push_back(make_pair(CAddressUnspentKey(addressType, hashBytes, input.prevout.hash, input.prevout.n), CAddressUnspentValue())); + } + + if (fSpentIndex) { + // add the spent index to determine the txid and input that spent an output + // and to find the amount and address from an input + spentIndex.push_back(make_pair(CSpentIndexKey(input.prevout.hash, input.prevout.n), CSpentIndexValue(txhash, j, pindex->nHeight, prevout.nValue, addressType, hashBytes))); + } + } + + } // Add in sigops done by pay-to-script-hash inputs; // this is to prevent a "rogue miner" from creating @@ -2340,35 +2342,35 @@ bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pin return false; control.Add(vChecks); } - - if (fAddressIndex) { - for (unsigned int k = 0; k < tx.vout.size(); k++) { - const CTxOut &out = tx.vout[k]; - - if (out.scriptPubKey.IsPayToScriptHash()) { - vector hashBytes(out.scriptPubKey.begin()+2, out.scriptPubKey.begin()+22); - - // record receiving activity - addressIndex.push_back(make_pair(CAddressIndexKey(2, uint160(hashBytes), pindex->nHeight, i, txhash, k, false), out.nValue)); - - // record unspent output - addressUnspentIndex.push_back(make_pair(CAddressUnspentKey(2, uint160(hashBytes), txhash, k), CAddressUnspentValue(out.nValue, out.scriptPubKey, pindex->nHeight))); - - } else if (out.scriptPubKey.IsPayToPublicKeyHash()) { - vector hashBytes(out.scriptPubKey.begin()+3, out.scriptPubKey.begin()+23); - - // record receiving activity - addressIndex.push_back(make_pair(CAddressIndexKey(1, uint160(hashBytes), pindex->nHeight, i, txhash, k, false), out.nValue)); - - // record unspent output - addressUnspentIndex.push_back(make_pair(CAddressUnspentKey(1, uint160(hashBytes), txhash, k), CAddressUnspentValue(out.nValue, out.scriptPubKey, pindex->nHeight))); - - } else { - continue; - } - - } - } + + if (fAddressIndex) { + for (unsigned int k = 0; k < tx.vout.size(); k++) { + const CTxOut &out = tx.vout[k]; + + if (out.scriptPubKey.IsPayToScriptHash()) { + vector hashBytes(out.scriptPubKey.begin()+2, out.scriptPubKey.begin()+22); + + // record receiving activity + addressIndex.push_back(make_pair(CAddressIndexKey(2, uint160(hashBytes), pindex->nHeight, i, txhash, k, false), out.nValue)); + + // record unspent output + addressUnspentIndex.push_back(make_pair(CAddressUnspentKey(2, uint160(hashBytes), txhash, k), CAddressUnspentValue(out.nValue, out.scriptPubKey, pindex->nHeight))); + + } else if (out.scriptPubKey.IsPayToPublicKeyHash()) { + vector hashBytes(out.scriptPubKey.begin()+3, out.scriptPubKey.begin()+23); + + // record receiving activity + addressIndex.push_back(make_pair(CAddressIndexKey(1, uint160(hashBytes), pindex->nHeight, i, txhash, k, false), out.nValue)); + + // record unspent output + addressUnspentIndex.push_back(make_pair(CAddressUnspentKey(1, uint160(hashBytes), txhash, k), CAddressUnspentValue(out.nValue, out.scriptPubKey, pindex->nHeight))); + + } else { + continue; + } + + } + } CTxUndo undoDummy; if (i > 0) { @@ -2435,40 +2437,41 @@ bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pin if (!pblocktree->WriteTxIndex(vPos)) return AbortNode(state, "Failed to write transaction index"); - if (fAddressIndex) { - if (!pblocktree->WriteAddressIndex(addressIndex)) { - return AbortNode(state, "Failed to write address index"); - } - - if (!pblocktree->UpdateAddressUnspentIndex(addressUnspentIndex)) { - return AbortNode(state, "Failed to write address unspent index"); - } - } - - if (fSpentIndex) - if (!pblocktree->UpdateSpentIndex(spentIndex)) - return AbortNode(state, "Failed to write transaction index"); - - if (fTimestampIndex) { - unsigned int logicalTS = pindex->nTime; - unsigned int prevLogicalTS = 0; - - // retrieve logical timestamp of the previous block - if (pindex->pprev) - if (!pblocktree->ReadTimestampBlockIndex(pindex->pprev->GetBlockHash(), prevLogicalTS)) - LogPrintf("%s: Failed to read previous block's logical timestamp\n", __func__); - - if (logicalTS <= prevLogicalTS) { - logicalTS = prevLogicalTS + 1; - LogPrintf("%s: Previous logical timestamp is newer Actual[%d] prevLogical[%d] Logical[%d]\n", __func__, pindex->nTime, prevLogicalTS, logicalTS); - } - - if (!pblocktree->WriteTimestampIndex(CTimestampIndexKey(logicalTS, pindex->GetBlockHash()))) - return AbortNode(state, "Failed to write timestamp index"); - - if (!pblocktree->WriteTimestampBlockIndex(CTimestampBlockIndexKey(pindex->GetBlockHash()), CTimestampBlockIndexValue(logicalTS))) - return AbortNode(state, "Failed to write blockhash index"); - } + if (fAddressIndex) { + if (!pblocktree->WriteAddressIndex(addressIndex)) { + return AbortNode(state, "Failed to write address index"); + } + + if (!pblocktree->UpdateAddressUnspentIndex(addressUnspentIndex)) { + return AbortNode(state, "Failed to write address unspent index"); + } + } + + if (fSpentIndex) + if (!pblocktree->UpdateSpentIndex(spentIndex)) + return AbortNode(state, "Failed to write transaction index"); + + if (fTimestampIndex) { + unsigned int logicalTS = pindex->nTime; + unsigned int prevLogicalTS = 0; + + // retrieve logical timestamp of the previous block + if (pindex->pprev) + if (!pblocktree->ReadTimestampBlockIndex(pindex->pprev->GetBlockHash(), prevLogicalTS)) + LogPrintf("%s: Failed to read previous block's logical timestamp\n", __func__); + + if (logicalTS <= prevLogicalTS) { + logicalTS = prevLogicalTS + 1; + LogPrintf("%s: Previous logical timestamp is newer Actual[%d] prevLogical[%d] Logical[%d]\n", __func__, pindex->nTime, prevLogicalTS, logicalTS); + } + + if (!pblocktree->WriteTimestampIndex(CTimestampIndexKey(logicalTS, pindex->GetBlockHash()))) + return AbortNode(state, "Failed to write timestamp index"); + + if (!pblocktree->WriteTimestampBlockIndex(CTimestampBlockIndexKey(pindex->GetBlockHash()), CTimestampBlockIndexValue(logicalTS))) + return AbortNode(state, "Failed to write blockhash index"); + } + // add this block to the view's block chain view.SetBestBlock(pindex->GetBlockHash()); @@ -3815,18 +3818,18 @@ bool static LoadBlockIndexDB() // Check whether we have a transaction index pblocktree->ReadFlag("txindex", fTxIndex); LogPrintf("%s: transaction index %s\n", __func__, fTxIndex ? "enabled" : "disabled"); - - // Check whether we have an address index - pblocktree->ReadFlag("addressindex", fAddressIndex); - LogPrintf("%s: address index %s\n", __func__, fAddressIndex ? "enabled" : "disabled"); - - // Check whether we have a timestamp index - pblocktree->ReadFlag("timestampindex", fTimestampIndex); - LogPrintf("%s: timestamp index %s\n", __func__, fTimestampIndex ? "enabled" : "disabled"); - - // Check whether we have a spent index - pblocktree->ReadFlag("spentindex", fSpentIndex); - LogPrintf("%s: spent index %s\n", __func__, fSpentIndex ? "enabled" : "disabled"); + + // Check whether we have an address index + pblocktree->ReadFlag("addressindex", fAddressIndex); + LogPrintf("%s: address index %s\n", __func__, fAddressIndex ? "enabled" : "disabled"); + + // Check whether we have a timestamp index + pblocktree->ReadFlag("timestampindex", fTimestampIndex); + LogPrintf("%s: timestamp index %s\n", __func__, fTimestampIndex ? "enabled" : "disabled"); + + // Check whether we have a spent index + pblocktree->ReadFlag("spentindex", fSpentIndex); + LogPrintf("%s: spent index %s\n", __func__, fSpentIndex ? "enabled" : "disabled"); // Fill in-memory data BOOST_FOREACH(const PAIRTYPE(uint256, CBlockIndex*)& item, mapBlockIndex) @@ -4006,18 +4009,18 @@ bool InitBlockIndex() { // Use the provided setting for -txindex in the new database fTxIndex = GetBoolArg("-txindex", false); pblocktree->WriteFlag("txindex", fTxIndex); - - // Use the provided setting for -addressindex in the new database - fAddressIndex = GetBoolArg("-addressindex", DEFAULT_ADDRESSINDEX); - pblocktree->WriteFlag("addressindex", fAddressIndex); - - // Use the provided setting for -timestampindex in the new database - fTimestampIndex = GetBoolArg("-timestampindex", DEFAULT_TIMESTAMPINDEX); - pblocktree->WriteFlag("timestampindex", fTimestampIndex); - - fSpentIndex = GetBoolArg("-spentindex", DEFAULT_SPENTINDEX); - pblocktree->WriteFlag("spentindex", fSpentIndex); - + + // Use the provided setting for -addressindex in the new database + fAddressIndex = GetBoolArg("-addressindex", DEFAULT_ADDRESSINDEX); + pblocktree->WriteFlag("addressindex", fAddressIndex); + + // Use the provided setting for -timestampindex in the new database + fTimestampIndex = GetBoolArg("-timestampindex", DEFAULT_TIMESTAMPINDEX); + pblocktree->WriteFlag("timestampindex", fTimestampIndex); + + fSpentIndex = GetBoolArg("-spentindex", DEFAULT_SPENTINDEX); + pblocktree->WriteFlag("spentindex", fSpentIndex); + LogPrintf("Initializing databases...\n"); // Only add the genesis block if not reindexing (in which case we reuse the one already on disk) diff --git a/src/main.h b/src/main.h index d9120ff8ce4..9b27a681035 100755 --- a/src/main.h +++ b/src/main.h @@ -21,6 +21,7 @@ #include "script/script.h" #include "script/sigcache.h" #include "script/standard.h" + #include "spentindex.h" #include "sync.h" #include "tinyformat.h" @@ -95,10 +96,10 @@ static const unsigned int DATABASE_FLUSH_INTERVAL = 24 * 60 * 60; static const unsigned int MAX_REJECT_MESSAGE_LENGTH = 111; static const bool DEFAULT_ADDRESSINDEX = false; - static const bool DEFAULT_TIMESTAMPINDEX = false; - static const bool DEFAULT_SPENTINDEX = false; - static const unsigned int DEFAULT_DB_MAX_OPEN_FILES = 1000; - static const bool DEFAULT_DB_COMPRESSION = true; +static const bool DEFAULT_TIMESTAMPINDEX = false; +static const bool DEFAULT_SPENTINDEX = false; +static const unsigned int DEFAULT_DB_MAX_OPEN_FILES = 1000; +static const bool DEFAULT_DB_COMPRESSION = true; // Sanity check the magic numbers when we change them BOOST_STATIC_ASSERT(DEFAULT_BLOCK_MAX_SIZE <= MAX_BLOCK_SIZE); @@ -349,7 +350,7 @@ struct CTimestampBlockIndexKey { } CTimestampBlockIndexKey() { - SetNull(); + SetNull(); } void SetNull() { @@ -732,6 +733,7 @@ class CScriptCheck ScriptError GetScriptError() const { return error; } }; + bool GetTimestampIndex(const unsigned int &high, const unsigned int &low, const bool fActiveOnly, std::vector > &hashes); bool GetSpentIndex(CSpentIndexKey &key, CSpentIndexValue &value); bool GetAddressIndex(uint160 addressHash, int type, diff --git a/src/rpcclient.cpp b/src/rpcclient.cpp index c87fa10b323..4906a895ef6 100755 --- a/src/rpcclient.cpp +++ b/src/rpcclient.cpp @@ -96,7 +96,7 @@ static const CRPCConvertParam vRPCConvertParams[] = { "prioritisetransaction", 2 }, { "setban", 2 }, { "setban", 3 }, - { "getblockhashes", 0 }, + { "getblockhashes", 0 }, { "getblockhashes", 1 }, { "getblockhashes", 2 }, { "getspentinfo", 0}, @@ -112,12 +112,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 }, diff --git a/src/rpcmisc.cpp b/src/rpcmisc.cpp index 40bdabb4221..cb304a5b182 100755 --- a/src/rpcmisc.cpp +++ b/src/rpcmisc.cpp @@ -987,4 +987,3 @@ UniValue getspentinfo(const UniValue& params, bool fHelp) return obj; } - diff --git a/src/rpcrawtransaction.cpp b/src/rpcrawtransaction.cpp index 7a0ac935788..332f6931eff 100755 --- a/src/rpcrawtransaction.cpp +++ b/src/rpcrawtransaction.cpp @@ -62,9 +62,9 @@ UniValue TxJoinSplitToJSON(const CTransaction& tx) { UniValue joinsplit(UniValue::VOBJ); joinsplit.push_back(Pair("vpub_old", ValueFromAmount(jsdescription.vpub_old))); - joinsplit.push_back(Pair("vpub_oldZat", jsdescription.vpub_old)); + joinsplit.push_back(Pair("vpub_oldZat", jsdescription.vpub_old)); joinsplit.push_back(Pair("vpub_new", ValueFromAmount(jsdescription.vpub_new))); - joinsplit.push_back(Pair("vpub_newZat", jsdescription.vpub_new)); + joinsplit.push_back(Pair("vpub_newZat", jsdescription.vpub_new)); joinsplit.push_back(Pair("anchor", jsdescription.anchor.GetHex())); @@ -130,18 +130,20 @@ void TxToJSONExpanded(const CTransaction& tx, const uint256 hashBlock, UniValue& o.push_back(Pair("asm", txin.scriptSig.ToString())); o.push_back(Pair("hex", HexStr(txin.scriptSig.begin(), txin.scriptSig.end()))); in.push_back(Pair("scriptSig", o)); - // Add address and value info if spentindex enabled - CSpentIndexValue spentInfo; - CSpentIndexKey spentKey(txin.prevout.hash, txin.prevout.n); - if (GetSpentIndex(spentKey, spentInfo)) { - in.push_back(Pair("value", ValueFromAmount(spentInfo.satoshis))); - in.push_back(Pair("valueSat", spentInfo.satoshis)); - if (spentInfo.addressType == 1) { - in.push_back(Pair("address", CBitcoinAddress(CKeyID(spentInfo.addressHash)).ToString())); - } else if (spentInfo.addressType == 2) { - in.push_back(Pair("address", CBitcoinAddress(CScriptID(spentInfo.addressHash)).ToString())); - } - } + + // Add address and value info if spentindex enabled + CSpentIndexValue spentInfo; + CSpentIndexKey spentKey(txin.prevout.hash, txin.prevout.n); + if (GetSpentIndex(spentKey, spentInfo)) { + in.push_back(Pair("value", ValueFromAmount(spentInfo.satoshis))); + in.push_back(Pair("valueSat", spentInfo.satoshis)); + if (spentInfo.addressType == 1) { + in.push_back(Pair("address", CBitcoinAddress(CKeyID(spentInfo.addressHash)).ToString())); + } else if (spentInfo.addressType == 2) { + in.push_back(Pair("address", CBitcoinAddress(CScriptID(spentInfo.addressHash)).ToString())); + } + } + } in.push_back(Pair("sequence", (int64_t)txin.nSequence)); vin.push_back(in); @@ -157,7 +159,8 @@ void TxToJSONExpanded(const CTransaction& tx, const uint256 hashBlock, UniValue& UniValue o(UniValue::VOBJ); ScriptPubKeyToJSON(txout.scriptPubKey, o, true); out.push_back(Pair("scriptPubKey", o)); - // Add spent information if spentindex is enabled + + // Add spent information if spentindex is enabled CSpentIndexValue spentInfo; CSpentIndexKey spentKey(txid, i); if (GetSpentIndex(spentKey, spentInfo)) { @@ -165,69 +168,70 @@ void TxToJSONExpanded(const CTransaction& tx, const uint256 hashBlock, UniValue& out.push_back(Pair("spentIndex", (int)spentInfo.inputIndex)); out.push_back(Pair("spentHeight", spentInfo.blockHeight)); } + vout.push_back(out); } entry.push_back(Pair("vout", vout)); UniValue vjoinsplit = TxJoinSplitToJSON(tx); entry.push_back(Pair("vjoinsplit", vjoinsplit)); - - if (!hashBlock.IsNull()) { - entry.push_back(Pair("blockhash", hashBlock.GetHex())); - - if (nConfirmations > 0) { - entry.push_back(Pair("height", nHeight)); - entry.push_back(Pair("confirmations", nConfirmations)); - entry.push_back(Pair("time", nBlockTime)); - entry.push_back(Pair("blocktime", nBlockTime)); - } else { - entry.push_back(Pair("height", -1)); - entry.push_back(Pair("confirmations", 0)); - } - } - - } - - void TxToJSON(const CTransaction& tx, const uint256 hashBlock, UniValue& entry) - { - - uint256 txid = tx.GetHash(); - entry.push_back(Pair("txid", txid.GetHex())); - entry.push_back(Pair("size", (int)::GetSerializeSize(tx, SER_NETWORK, PROTOCOL_VERSION))); - entry.push_back(Pair("version", tx.nVersion)); - entry.push_back(Pair("locktime", (int64_t)tx.nLockTime)); - - UniValue vin(UniValue::VARR); - BOOST_FOREACH(const CTxIn& txin, tx.vin) { - UniValue in(UniValue::VOBJ); - if (tx.IsCoinBase()) - in.push_back(Pair("coinbase", HexStr(txin.scriptSig.begin(), txin.scriptSig.end()))); - else { - in.push_back(Pair("txid", txin.prevout.hash.GetHex())); - in.push_back(Pair("vout", (int64_t)txin.prevout.n)); - UniValue o(UniValue::VOBJ); - o.push_back(Pair("asm", txin.scriptSig.ToString())); - o.push_back(Pair("hex", HexStr(txin.scriptSig.begin(), txin.scriptSig.end()))); - in.push_back(Pair("scriptSig", o)); - } - in.push_back(Pair("sequence", (int64_t)txin.nSequence)); - vin.push_back(in); - } - entry.push_back(Pair("vin", vin)); - - UniValue vout(UniValue::VARR); - for (unsigned int i = 0; i < tx.vout.size(); i++) { - const CTxOut& txout = tx.vout[i]; - UniValue out(UniValue::VOBJ); - out.push_back(Pair("value", ValueFromAmount(txout.nValue))); - out.push_back(Pair("valueSat", txout.nValue)); - out.push_back(Pair("n", (int64_t)i)); - UniValue o(UniValue::VOBJ); - ScriptPubKeyToJSON(txout.scriptPubKey, o, true); - out.push_back(Pair("scriptPubKey", o)); - vout.push_back(out); - } - entry.push_back(Pair("vout", vout)); + + if (!hashBlock.IsNull()) { + entry.push_back(Pair("blockhash", hashBlock.GetHex())); + + if (nConfirmations > 0) { + entry.push_back(Pair("height", nHeight)); + entry.push_back(Pair("confirmations", nConfirmations)); + entry.push_back(Pair("time", nBlockTime)); + entry.push_back(Pair("blocktime", nBlockTime)); + } else { + entry.push_back(Pair("height", -1)); + entry.push_back(Pair("confirmations", 0)); + } + } + +} + +void TxToJSON(const CTransaction& tx, const uint256 hashBlock, UniValue& entry) +{ + + uint256 txid = tx.GetHash(); + entry.push_back(Pair("txid", txid.GetHex())); + entry.push_back(Pair("size", (int)::GetSerializeSize(tx, SER_NETWORK, PROTOCOL_VERSION))); + entry.push_back(Pair("version", tx.nVersion)); + entry.push_back(Pair("locktime", (int64_t)tx.nLockTime)); + + UniValue vin(UniValue::VARR); + BOOST_FOREACH(const CTxIn& txin, tx.vin) { + UniValue in(UniValue::VOBJ); + if (tx.IsCoinBase()) + in.push_back(Pair("coinbase", HexStr(txin.scriptSig.begin(), txin.scriptSig.end()))); + else { + in.push_back(Pair("txid", txin.prevout.hash.GetHex())); + in.push_back(Pair("vout", (int64_t)txin.prevout.n)); + UniValue o(UniValue::VOBJ); + o.push_back(Pair("asm", txin.scriptSig.ToString())); + o.push_back(Pair("hex", HexStr(txin.scriptSig.begin(), txin.scriptSig.end()))); + in.push_back(Pair("scriptSig", o)); + } + in.push_back(Pair("sequence", (int64_t)txin.nSequence)); + vin.push_back(in); + } + entry.push_back(Pair("vin", vin)); + + UniValue vout(UniValue::VARR); + for (unsigned int i = 0; i < tx.vout.size(); i++) { + const CTxOut& txout = tx.vout[i]; + UniValue out(UniValue::VOBJ); + out.push_back(Pair("value", ValueFromAmount(txout.nValue))); + out.push_back(Pair("valueSat", txout.nValue)); + out.push_back(Pair("n", (int64_t)i)); + UniValue o(UniValue::VOBJ); + ScriptPubKeyToJSON(txout.scriptPubKey, o, true); + out.push_back(Pair("scriptPubKey", o)); + vout.push_back(out); + } + entry.push_back(Pair("vout", vout)); if (!hashBlock.IsNull()) { entry.push_back(Pair("blockhash", hashBlock.GetHex())); @@ -235,14 +239,14 @@ void TxToJSONExpanded(const CTransaction& tx, const uint256 hashBlock, UniValue& if (mi != mapBlockIndex.end() && (*mi).second) { CBlockIndex* pindex = (*mi).second; if (chainActive.Contains(pindex)) { - entry.push_back(Pair("height", pindex->nHeight)); + entry.push_back(Pair("height", pindex->nHeight)); entry.push_back(Pair("confirmations", 1 + chainActive.Height() - pindex->nHeight)); entry.push_back(Pair("time", pindex->GetBlockTime())); entry.push_back(Pair("blocktime", pindex->GetBlockTime())); } else { - entry.push_back(Pair("height", -1)); + entry.push_back(Pair("height", -1)); entry.push_back(Pair("confirmations", 0)); - } + } } } } @@ -340,6 +344,8 @@ UniValue getrawtransaction(const UniValue& params, bool fHelp) + HelpExampleRpc("getrawtransaction", "\"mytxid\", 1") ); + + uint256 hash = ParseHashV(params[0], "parameter 1"); bool fVerbose = false; @@ -348,29 +354,29 @@ UniValue getrawtransaction(const UniValue& params, bool fHelp) CTransaction tx; uint256 hashBlock; - int nHeight = 0; - int nConfirmations = 0; - int nBlockTime = 0; - - { - LOCK(cs_main); - if (!GetTransaction(hash, tx, hashBlock, true)) - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "No information available about transaction"); - - BlockMap::iterator mi = mapBlockIndex.find(hashBlock); - if (mi != mapBlockIndex.end() && (*mi).second) { - CBlockIndex* pindex = (*mi).second; - if (chainActive.Contains(pindex)) { - nHeight = pindex->nHeight; - nConfirmations = 1 + chainActive.Height() - pindex->nHeight; - nBlockTime = pindex->GetBlockTime(); - } else { - nHeight = -1; - nConfirmations = 0; - nBlockTime = pindex->GetBlockTime(); - } - } - } + int nHeight = 0; + int nConfirmations = 0; + int nBlockTime = 0; + + { + LOCK(cs_main); + if (!GetTransaction(hash, tx, hashBlock, true)) + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "No information available about transaction"); + + BlockMap::iterator mi = mapBlockIndex.find(hashBlock); + if (mi != mapBlockIndex.end() && (*mi).second) { + CBlockIndex* pindex = (*mi).second; + if (chainActive.Contains(pindex)) { + nHeight = pindex->nHeight; + nConfirmations = 1 + chainActive.Height() - pindex->nHeight; + nBlockTime = pindex->GetBlockTime(); + } else { + nHeight = -1; + nConfirmations = 0; + nBlockTime = pindex->GetBlockTime(); + } + } + } string strHex = EncodeHexTx(tx); @@ -379,8 +385,7 @@ UniValue getrawtransaction(const UniValue& params, bool fHelp) UniValue result(UniValue::VOBJ); result.push_back(Pair("hex", strHex)); - TxToJSONExpanded(tx, hashBlock, result, nHeight, nConfirmations, nBlockTime); - + TxToJSONExpanded(tx, hashBlock, result, nHeight, nConfirmations, nBlockTime); return result; } diff --git a/src/rpcserver.cpp b/src/rpcserver.cpp index 8b3134ff149..324a54e3690 100755 --- a/src/rpcserver.cpp +++ b/src/rpcserver.cpp @@ -396,6 +396,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 }, diff --git a/src/rpcserver.h b/src/rpcserver.h index 214eef5f7fc..3eacd66ce94 100755 --- a/src/rpcserver.h +++ b/src/rpcserver.h @@ -300,6 +300,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 diff --git a/src/script/script.h b/src/script/script.h index 40658bfca99..c996c7538cc 100755 --- a/src/script/script.h +++ b/src/script/script.h @@ -562,7 +562,7 @@ class CScript : public std::vector unsigned int GetSigOpCount(const CScript& scriptSig) const; bool IsPayToPublicKeyHash() const; - + bool IsPayToScriptHash() const; /** Called by IsStandardTx and P2SH/BIP62 VerifyScript (which makes it consensus-critical). */ diff --git a/src/test/rpc_wallet_tests.cpp b/src/test/rpc_wallet_tests.cpp index 713f9c38b27..c40e4dfbcb4 100755 --- a/src/test/rpc_wallet_tests.cpp +++ b/src/test/rpc_wallet_tests.cpp @@ -17,6 +17,8 @@ #include "asyncrpcqueue.h" #include "asyncrpcoperation.h" #include "wallet/asyncrpcoperation_sendmany.h" +#include "wallet/asyncrpcoperation_shieldcoinbase.h" + #include "rpcprotocol.h" #include "init.h" @@ -289,20 +291,20 @@ BOOST_AUTO_TEST_CASE(rpc_wallet_getbalance) LOCK(pwalletMain->cs_wallet); - + BOOST_CHECK_THROW(CallRPC("z_getbalance too many args"), runtime_error); BOOST_CHECK_THROW(CallRPC("z_getbalance invalidaddress"), runtime_error); BOOST_CHECK_NO_THROW(CallRPC("z_getbalance tmC6YZnCUhm19dEXxh3Jb7srdBJxDawaCab")); BOOST_CHECK_THROW(CallRPC("z_getbalance tmC6YZnCUhm19dEXxh3Jb7srdBJxDawaCab -1"), runtime_error); BOOST_CHECK_NO_THROW(CallRPC("z_getbalance tmC6YZnCUhm19dEXxh3Jb7srdBJxDawaCab 0")); BOOST_CHECK_THROW(CallRPC("z_getbalance tnRZ8bPq2pff3xBWhTJhNkVUkm2uhzksDeW5PvEa7aFKGT9Qi3YgTALZfjaY4jU3HLVKBtHdSXxoPoLA3naMPcHBcY88FcF 1"), runtime_error); - - + + BOOST_CHECK_THROW(CallRPC("z_gettotalbalance too manyargs"), runtime_error); BOOST_CHECK_THROW(CallRPC("z_gettotalbalance -1"), runtime_error); BOOST_CHECK_NO_THROW(CallRPC("z_gettotalbalance 0")); - - + + BOOST_CHECK_THROW(CallRPC("z_listreceivedbyaddress too many args"), runtime_error); // negative minconf not allowed BOOST_CHECK_THROW(CallRPC("z_listreceivedbyaddress tmC6YZnCUhm19dEXxh3Jb7srdBJxDawaCab -1"), runtime_error); @@ -374,7 +376,7 @@ BOOST_AUTO_TEST_CASE(rpc_wallet_z_exportwallet) CZCPaymentAddress paymentAddress = pwalletMain->GenerateNewZKey(); pwalletMain->GetPaymentAddresses(addrs); BOOST_CHECK(addrs.size()==1); - + // Set up paths boost::filesystem::path tmppath = boost::filesystem::temp_directory_path(); boost::filesystem::path tmpfilename = boost::filesystem::unique_path("%%%%%%%%"); @@ -402,10 +404,10 @@ BOOST_AUTO_TEST_CASE(rpc_wallet_z_exportwallet) std::string s1 = paymentAddress.ToString(); std::string s2 = CZCSpendingKey(key).ToString(); - + // There's no way to really delete a private key so we will read in the // exported wallet file and search for the spending key and payment address. - + EnsureWalletIsUnlocked(); ifstream file; @@ -434,7 +436,7 @@ BOOST_AUTO_TEST_CASE(rpc_wallet_z_exportwallet) BOOST_AUTO_TEST_CASE(rpc_wallet_z_importwallet) { LOCK2(cs_main, pwalletMain->cs_wallet); - + // error if no args BOOST_CHECK_THROW(CallRPC("z_importwallet"), runtime_error); @@ -446,7 +448,7 @@ BOOST_AUTO_TEST_CASE(rpc_wallet_z_importwallet) auto testPaymentAddress = testSpendingKey.address(); std::string testAddr = CZCPaymentAddress(testPaymentAddress).ToString(); std::string testKey = CZCSpendingKey(testSpendingKey).ToString(); - + // create test data using the random key std::string format_str = "# Wallet dump created by Zcash v0.11.2.0.z8-9155cc6-dirty (2016-08-11 11:37:00 -0700)\n" "# * Created on 2016-08-12T21:55:36Z\n" @@ -458,10 +460,10 @@ BOOST_AUTO_TEST_CASE(rpc_wallet_z_importwallet) "%s 2016-08-12T21:55:36Z # zaddr=%s\n" "\n" "\n# End of dump"; - + boost::format formatobject(format_str); std::string testWalletDump = (formatobject % testKey % testAddr).str(); - + // write test data to file boost::filesystem::path temp = boost::filesystem::temp_directory_path() / boost::filesystem::unique_path(); @@ -474,19 +476,19 @@ BOOST_AUTO_TEST_CASE(rpc_wallet_z_importwallet) std::set addrs; pwalletMain->GetPaymentAddresses(addrs); BOOST_CHECK(addrs.size()==0); - + // import test data from file into wallet BOOST_CHECK_NO_THROW(CallRPC(string("z_importwallet ") + path)); - + // wallet should now have one zkey pwalletMain->GetPaymentAddresses(addrs); BOOST_CHECK(addrs.size()==1); - + // check that we have the spending key for the address CZCPaymentAddress address(testAddr); auto addr = address.Get(); BOOST_CHECK(pwalletMain->HaveSpendingKey(addr)); - + // Verify the spending key is the same as the test data libzcash::SpendingKey k; BOOST_CHECK(pwalletMain->GetSpendingKey(addr, k)); @@ -504,10 +506,10 @@ BOOST_AUTO_TEST_CASE(rpc_wallet_z_importexport) UniValue retValue; int n1 = 1000; // number of times to import/export int n2 = 1000; // number of addresses to create and list - + // error if no args - BOOST_CHECK_THROW(CallRPC("z_importkey"), runtime_error); - BOOST_CHECK_THROW(CallRPC("z_exportkey"), runtime_error); + BOOST_CHECK_THROW(CallRPC("z_importkey"), runtime_error); + BOOST_CHECK_THROW(CallRPC("z_exportkey"), runtime_error); // error if too many args BOOST_CHECK_THROW(CallRPC("z_importkey way too many args"), runtime_error); @@ -548,7 +550,7 @@ BOOST_AUTO_TEST_CASE(rpc_wallet_z_importexport) for (UniValue element : arr.getValues()) { myaddrs.insert(element.get_str()); } - + // Make new addresses for the set for (int i=0; iGenerateNewZKey()).ToString()); @@ -558,19 +560,19 @@ BOOST_AUTO_TEST_CASE(rpc_wallet_z_importexport) int numAddrs = myaddrs.size(); BOOST_CHECK(numAddrs == n1+n2); pwalletMain->GetPaymentAddresses(addrs); - BOOST_CHECK(addrs.size()==numAddrs); - + BOOST_CHECK(addrs.size()==numAddrs); + // Ask wallet to list addresses BOOST_CHECK_NO_THROW(retValue = CallRPC("z_listaddresses")); arr = retValue.get_array(); BOOST_CHECK(arr.size() == numAddrs); - + // Create a set from them std::unordered_set listaddrs; for (UniValue element : arr.getValues()) { listaddrs.insert(element.get_str()); } - + // Verify the two sets of addresses are the same BOOST_CHECK(listaddrs.size() == numAddrs); BOOST_CHECK(myaddrs == listaddrs); @@ -623,19 +625,19 @@ BOOST_AUTO_TEST_CASE(rpc_wallet_async_operations) BOOST_CHECK(ids.size()==0); std::shared_ptr op1 = std::make_shared(); - q->addOperation(op1); + q->addOperation(op1); BOOST_CHECK(q->getOperationCount() == 1); - + OperationStatus status = op1->getState(); BOOST_CHECK(status == OperationStatus::READY); - + AsyncRPCOperationId id1 = op1->getId(); int64_t creationTime1 = op1->getCreationTime(); - + q->addWorker(); BOOST_CHECK(q->getNumberOfWorkers() == 1); - - // an AsyncRPCOperation doesn't do anything so will finish immediately + + // an AsyncRPCOperation doesn't do anything so will finish immediately std::this_thread::sleep_for(std::chrono::seconds(1)); BOOST_CHECK(q->getOperationCount() == 0); @@ -649,7 +651,7 @@ BOOST_AUTO_TEST_CASE(rpc_wallet_async_operations) BOOST_CHECK_EQUAL(op1->getResult().isNull(), false); BOOST_CHECK_EQUAL(op1->getStateAsString(), "success"); BOOST_CHECK_NE(op1->getStateAsString(), "executing"); - + // Create a second operation which just sleeps std::shared_ptr op2(new MockSleepOperation(2500)); AsyncRPCOperationId id2 = op2->getId(); @@ -683,8 +685,8 @@ BOOST_AUTO_TEST_CASE(rpc_wallet_async_operations) BOOST_CHECK_EQUAL(op2->isSuccess(), true); BOOST_CHECK_EQUAL(op2->isCancelled(), false); BOOST_CHECK_EQUAL(op3->isCancelled(), true); - - + + v = q->getAllOperationIds(); std::copy( v.begin(), v.end(), std::inserter( opids, opids.end() ) ); BOOST_CHECK(opids.size() == 3); @@ -702,7 +704,7 @@ class CountOperation : public AsyncRPCOperation { public: CountOperation() {} virtual ~CountOperation() {} - virtual void main() { + virtual void main() { set_state(OperationStatus::EXECUTING); gCounter++; std::this_thread::sleep_for(std::chrono::milliseconds(1000)); @@ -714,7 +716,7 @@ class CountOperation : public AsyncRPCOperation { BOOST_AUTO_TEST_CASE(rpc_wallet_async_operations_parallel_wait) { gCounter = 0; - + std::shared_ptr q = std::make_shared(); q->addWorker(); q->addWorker(); @@ -739,7 +741,7 @@ BOOST_AUTO_TEST_CASE(rpc_wallet_async_operations_parallel_wait) BOOST_AUTO_TEST_CASE(rpc_wallet_async_operations_parallel_cancel) { gCounter = 0; - + std::shared_ptr q = std::make_shared(); q->addWorker(); q->addWorker(); @@ -755,7 +757,7 @@ BOOST_AUTO_TEST_CASE(rpc_wallet_async_operations_parallel_cancel) q->closeAndWait(); int numSuccess = 0; - int numCancelled = 0; + int numCancelled = 0; for (auto & id : ids) { std::shared_ptr ptr = q->popOperationForId(id); if (ptr->isCancelled()) { @@ -764,7 +766,7 @@ BOOST_AUTO_TEST_CASE(rpc_wallet_async_operations_parallel_cancel) numSuccess++; } } - + BOOST_CHECK_EQUAL(numOperations, numSuccess+numCancelled); BOOST_CHECK_EQUAL(gCounter.load(), numSuccess); BOOST_CHECK(q->getOperationCount() == 0); @@ -790,19 +792,19 @@ BOOST_AUTO_TEST_CASE(rpc_z_getoperations) BOOST_CHECK_NO_THROW(CallRPC("z_getoperationresult [\"opid-1234\"]")); BOOST_CHECK_THROW(CallRPC("z_getoperationresult [] toomanyargs"), runtime_error); BOOST_CHECK_THROW(CallRPC("z_getoperationresult not_an_array"), runtime_error); - + std::shared_ptr op1 = std::make_shared(); q->addOperation(op1); std::shared_ptr op2 = std::make_shared(); q->addOperation(op2); - + BOOST_CHECK(q->getOperationCount() == 2); BOOST_CHECK(q->getNumberOfWorkers() == 0); q->addWorker(); BOOST_CHECK(q->getNumberOfWorkers() == 1); std::this_thread::sleep_for(std::chrono::milliseconds(1000)); BOOST_CHECK(q->getOperationCount() == 0); - + // Check if too many args BOOST_CHECK_THROW(CallRPC("z_listoperationids toomany args"), runtime_error); @@ -817,28 +819,28 @@ BOOST_AUTO_TEST_CASE(rpc_z_getoperations) // idempotent BOOST_CHECK_NO_THROW(retValue = CallRPC("z_getoperationstatus")); array = retValue.get_array(); - BOOST_CHECK(array.size() == 2); - + BOOST_CHECK(array.size() == 2); + for (UniValue v : array.getValues()) { UniValue obj = v.get_obj(); UniValue id = find_value(obj, "id"); - + UniValue result; // removes result from internal storage BOOST_CHECK_NO_THROW(result = CallRPC("z_getoperationresult [\"" + id.get_str() + "\"]")); UniValue resultArray = result.get_array(); BOOST_CHECK(resultArray.size() == 1); - + UniValue resultObj = resultArray[0].get_obj(); UniValue resultId = find_value(resultObj, "id"); BOOST_CHECK_EQUAL(id.get_str(), resultId.get_str()); - - // verify the operation has been removed + + // verify the operation has been removed BOOST_CHECK_NO_THROW(result = CallRPC("z_getoperationresult [\"" + id.get_str() + "\"]")); resultArray = result.get_array(); BOOST_CHECK(resultArray.size() == 0); } - + // operations removed BOOST_CHECK_NO_THROW(retValue = CallRPC("z_getoperationstatus")); array = retValue.get_array(); @@ -905,8 +907,8 @@ BOOST_AUTO_TEST_CASE(rpc_z_sendmany_parameters) std::string zaddr1 = pa.ToString(); BOOST_CHECK_THROW(CallRPC(string("z_sendmany tmRr6yJonqGK23UVhrKuyvTpF8qxQQjKigJ ") + "[{\"address\":\"" + zaddr1 + "\", \"amount\":123.456}]"), runtime_error); - - // Test constructor of AsyncRPCOperation_sendmany + + // Test constructor of AsyncRPCOperation_sendmany try { std::shared_ptr operation(new AsyncRPCOperation_sendmany("",{}, {}, -1)); } catch (const UniValue& objError) { @@ -959,13 +961,13 @@ BOOST_AUTO_TEST_CASE(rpc_z_sendmany_internals) LOCK(pwalletMain->cs_wallet); UniValue retValue; - + // add keys manually BOOST_CHECK_NO_THROW(retValue = CallRPC("getnewaddress")); std::string taddr1 = retValue.get_str(); CZCPaymentAddress pa = pwalletMain->GenerateNewZKey(); std::string zaddr1 = pa.ToString(); - + // there are no utxos to spend { std::vector recipients = { SendManyRecipient(zaddr1,100.0, "DEADBEEF") }; @@ -975,7 +977,18 @@ BOOST_AUTO_TEST_CASE(rpc_z_sendmany_internals) std::string msg = operation->getErrorMessage(); BOOST_CHECK( msg.find("Insufficient funds, no UTXOs found") != string::npos); } - + + // minconf cannot be zero when sending from zaddr + { + try { + std::vector recipients = {SendManyRecipient(taddr1, 100.0, "DEADBEEF")}; + std::shared_ptr operation(new AsyncRPCOperation_sendmany(zaddr1, recipients, {}, 0)); + BOOST_CHECK(false); // Fail test if an exception is not thrown + } catch (const UniValue& objError) { + BOOST_CHECK(find_error(objError, "Minconf cannot be zero when sending from zaddr")); + } + } + // there are no unspent notes to spend { std::vector recipients = { SendManyRecipient(taddr1,100.0, "DEADBEEF") }; @@ -992,7 +1005,7 @@ BOOST_AUTO_TEST_CASE(rpc_z_sendmany_internals) std::shared_ptr operation( new AsyncRPCOperation_sendmany(zaddr1, recipients, {}, 1) ); std::shared_ptr ptr = std::dynamic_pointer_cast (operation); TEST_FRIEND_AsyncRPCOperation_sendmany proxy(ptr); - + std::string memo = "DEADBEEF"; boost::array array = proxy.get_memo_from_hex_string(memo); BOOST_CHECK_EQUAL(array[0], 0xDE); @@ -1002,28 +1015,28 @@ BOOST_AUTO_TEST_CASE(rpc_z_sendmany_internals) for (int i=4; i 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); @@ -1035,25 +1048,25 @@ BOOST_AUTO_TEST_CASE(rpc_z_sendmany_internals) BOOST_CHECK( find_error(objError, "hexadecimal format")); } } - - + + // add_taddr_change_output_to_tx() will append a vout to a raw transaction { std::vector recipients = { SendManyRecipient(zaddr1,100.0, "DEADBEEF") }; std::shared_ptr operation( new AsyncRPCOperation_sendmany(zaddr1, recipients, {}, 1) ); std::shared_ptr ptr = std::dynamic_pointer_cast (operation); TEST_FRIEND_AsyncRPCOperation_sendmany proxy(ptr); - + CTransaction tx = proxy.getTx(); BOOST_CHECK(tx.vout.size() == 0); - + CAmount amount = 123.456; proxy.add_taddr_change_output_to_tx(amount); tx = proxy.getTx(); BOOST_CHECK(tx.vout.size() == 1); CTxOut out = tx.vout[0]; BOOST_CHECK_EQUAL(out.nValue, amount); - + amount = 1.111; proxy.add_taddr_change_output_to_tx(amount); tx = proxy.getTx(); @@ -1061,7 +1074,7 @@ BOOST_AUTO_TEST_CASE(rpc_z_sendmany_internals) out = tx.vout[1]; BOOST_CHECK_EQUAL(out.nValue, amount); } - + // add_taddr_outputs_to_tx() will append many vouts to a raw transaction { std::vector recipients = { @@ -1072,33 +1085,33 @@ BOOST_AUTO_TEST_CASE(rpc_z_sendmany_internals) std::shared_ptr operation( new AsyncRPCOperation_sendmany(zaddr1, recipients, {}, 1) ); std::shared_ptr ptr = std::dynamic_pointer_cast (operation); TEST_FRIEND_AsyncRPCOperation_sendmany proxy(ptr); - + proxy.add_taddr_outputs_to_tx(); - + CTransaction tx = proxy.getTx(); BOOST_CHECK(tx.vout.size() == 3); BOOST_CHECK_EQUAL(tx.vout[0].nValue, CAmount(1.23)); BOOST_CHECK_EQUAL(tx.vout[1].nValue, CAmount(4.56)); BOOST_CHECK_EQUAL(tx.vout[2].nValue, CAmount(7.89)); } - + // Raw joinsplit is a zaddr->zaddr { std::string raw = "020000000000000000000100000000000000001027000000000000183a0d4c46c369078705e39bcfebee59a978dbd210ce8de3efc9555a03fbabfd3cea16693d730c63850d7e48ccde79854c19adcb7e9dcd7b7d18805ee09083f6b16e1860729d2d4a90e2f2acd009cf78b5eb0f4a6ee4bdb64b1262d7ce9eb910c460b02022991e968d0c50ee44908e4ccccbc591d0053bcca154dd6d6fc400a29fa686af4682339832ccea362a62aeb9df0d5aa74f86a1e75ac0f48a8ccc41e0a940643c6c33e1d09223b0a46eaf47a1bb4407cfc12b1dcf83a29c0cef51e45c7876ca5b9e5bae86d92976eb3ef68f29cd29386a8be8451b50f82bf9da10c04651868655194da8f6ed3d241bb5b5ff93a3e2bbe44644544d88bcde5cc35978032ee92699c7a61fcbb395e7583f47e698c4d53ede54f956629400bf510fb5e22d03158cc10bdcaaf29e418ef18eb6480dd9c8b9e2a377809f9f32a556ef872febd0021d4ad013aa9f0b7255e98e408d302abefd33a71180b720271835b487ab309e160b06dfe51932120fb84a7ede16b20c53599a11071592109e10260f265ee60d48c62bfe24074020e9b586ce9e9356e68f2ad1a9538258234afe4b83a209f178f45202270eaeaeecaf2ce3100b2c5a714f75f35777a9ebff5ebf47059d2bbf6f3726190216468f2b152673b766225b093f3a2f827c86d6b48b42117fec1d0ac38dd7af700308dcfb02eba821612b16a2c164c47715b9b0c93900893b1aba2ea03765c94d87022db5be06ab338d1912e0936dfe87586d0a8ee49144a6cd2e306abdcb652faa3e0222739deb23154d778b50de75069a4a2cce1208cd1ced3cb4744c9888ce1c2fcd2e66dc31e62d3aa9e423d7275882525e9981f92e84ac85975b8660739407efbe1e34c2249420fde7e17db3096d5b22e83d051d01f0e6e7690dca7d168db338aadf0897fedac10de310db2b1bff762d322935dddbb60c2efb8b15d231fa17b84630371cb275c209f0c4c7d0c68b150ea5cd514122215e3f7fcfb351d69514788d67c2f3c8922581946e3a04bdf1f07f15696ca76eb95b10698bf1188fd882945c57657515889d042a6fc45d38cbc943540c4f0f6d1c45a1574c81f3e42d1eb8702328b729909adee8a5cfed7c79d54627d1fd389af941d878376f7927b9830ca659bf9ab18c5ca5192d52d02723008728d03701b8ab3e1c4a3109409ec0b13df334c7deec3523eeef4c97b5603e643de3a647b873f4c1b47fbfc6586ba66724f112e51fc93839648005043620aa3ce458e246d77977b19c53d98e3e812de006afc1a79744df236582943631d04cc02941ac4be500e4ed9fb9e3e7cc187b1c4050fad1d9d09d5fd70d5d01d615b439d8c0015d2eb10398bcdbf8c4b2bd559dbe4c288a186aed3f86f608da4d582e120c4a896e015e2241900d1daeccd05db968852677c71d752bec46de9962174b46f980e8cc603654daf8b98a3ee92dac066033954164a89568b70b1780c2ce2410b2f816dbeddb2cd463e0c8f21a52cf6427d9647a6fd4bafa8fb4cd4d47ac057b0160bee86c6b2fb8adce214c2bcdda277512200adf0eaa5d2114a2c077b009836a68ec254bfe56f51d147b9afe2ddd9cb917c0c2de19d81b7b8fd9f4574f51fa1207630dc13976f4d7587c962f761af267de71f3909a576e6bedaf6311633910d291ac292c467cc8331ef577aef7646a5d949322fa0367a49f20597a13def53136ee31610395e3e48d291fd8f58504374031fe9dcfba5e06086ebcf01a9106f6a4d6e16e19e4c5bb893f7da79419c94eca31a384be6fa1747284dee0fc3bbc8b1b860172c10b29c1594bb8c747d7fe05827358ff2160f49050001625ffe2e880bd7fc26cd0ffd89750745379a8e862816e08a5a2008043921ab6a4976064ac18f7ee37b6628bc0127d8d5ebd3548e41d8881a082d86f20b32e33094f15a0e6ea6074b08c6cd28142de94713451640a55985051f5577eb54572699d838cb34a79c8939e981c0c277d06a6e2ce69ccb74f8a691ff08f81d8b99e6a86223d29a2b7c8e7b041aba44ea678ae654277f7e91cbfa79158b989164a3d549d9f4feb0cc43169699c13e321fe3f4b94258c75d198ff9184269cd6986c55409e07528c93f64942c6c283ce3917b4bf4c3be2fe3173c8c38cccb35f1fbda0ca88b35a599c0678cb22aa8eabea8249dbd2e4f849fffe69803d299e435ebcd7df95854003d8eda17a74d98b4be0e62d45d7fe48c06a6f464a14f8e0570077cc631279092802a89823f031eef5e1028a6d6fdbd502869a731ee7d28b4d6c71b419462a30d31442d3ee444ffbcbd16d558c9000c97e949c2b1f9d6f6d8db7b9131ebd963620d3fc8595278d6f8fdf49084325373196d53e64142fa5a23eccd6ef908c4d80b8b3e6cc334b7f7012103a3682e4678e9b518163d262a39a2c1a69bf88514c52b7ccd7cc8dc80e71f7c2ec0701cff982573eb0c2c4daeb47fa0b586f4451c10d1da2e5d182b03dd067a5e971b3a6138ca6667aaf853d2ac03b80a1d5870905f2cfb6c78ec3c3719c02f973d638a0f973424a2b0f2b0023f136d60092fe15fba4bc180b9176bd0ff576e053f1af6939fe9ca256203ffaeb3e569f09774d2a6cbf91873e56651f4d6ff77e0b5374b0a1a201d7e523604e0247644544cc571d48c458a4f96f45580b"; UniValue obj(UniValue::VOBJ); obj.push_back(Pair("rawtxn", raw)); - + // we have the spending key for the dummy recipient zaddr1 std::vector recipients = { SendManyRecipient(zaddr1, 0.0005, "ABCD") }; - + std::shared_ptr operation( new AsyncRPCOperation_sendmany(zaddr1, {}, recipients, 1) ); std::shared_ptr ptr = std::dynamic_pointer_cast (operation); TEST_FRIEND_AsyncRPCOperation_sendmany proxy(ptr); - + // Enable test mode so tx is not sent static_cast(operation.get())->testmode = true; - + // Pretend that the operation completed successfully proxy.set_state(OperationStatus::SUCCESS); @@ -1110,21 +1123,21 @@ BOOST_AUTO_TEST_CASE(rpc_z_sendmany_internals) std::string hex = find_value(resultObj, "hex").get_str(); BOOST_CHECK_EQUAL(hex, raw); } - - + + // Test the perform_joinsplit methods. { // Dummy input so the operation object can be instantiated. std::vector recipients = { SendManyRecipient(zaddr1, 0.0005, "ABCD") }; - + std::shared_ptr operation( new AsyncRPCOperation_sendmany(zaddr1, {}, recipients, 1) ); std::shared_ptr ptr = std::dynamic_pointer_cast (operation); - TEST_FRIEND_AsyncRPCOperation_sendmany proxy(ptr); + TEST_FRIEND_AsyncRPCOperation_sendmany proxy(ptr); // Enable test mode so tx is not sent and proofs are not generated static_cast(operation.get())->testmode = true; - - AsyncJoinSplitInfo info; + + AsyncJoinSplitInfo info; std::vector> witnesses; uint256 anchor; try { @@ -1146,7 +1159,7 @@ BOOST_AUTO_TEST_CASE(rpc_z_sendmany_internals) } catch (const std::runtime_error & e) { BOOST_CHECK( string(e.what()).find("number of notes")!= string::npos); } - + info.notes.clear(); info.vjsin.push_back(JSInput()); info.vjsin.push_back(JSInput()); @@ -1156,7 +1169,7 @@ BOOST_AUTO_TEST_CASE(rpc_z_sendmany_internals) } catch (const std::runtime_error & e) { BOOST_CHECK( string(e.what()).find("unsupported joinsplit input")!= string::npos); } - + info.vjsin.clear(); try { proxy.perform_joinsplit(info); @@ -1164,7 +1177,7 @@ BOOST_AUTO_TEST_CASE(rpc_z_sendmany_internals) BOOST_CHECK( string(e.what()).find("JoinSplit verifying key not loaded")!= string::npos); } } - + } @@ -1202,29 +1215,160 @@ BOOST_AUTO_TEST_CASE(rpc_wallet_encrypted_wallet_zkeys) boost::filesystem::current_path(GetArg("-datadir","/tmp/thisshouldnothappen")); BOOST_CHECK(pwalletMain->EncryptWallet(strWalletPass)); - + // Verify we can still list the keys imported BOOST_CHECK_NO_THROW(retValue = CallRPC("z_listaddresses")); arr = retValue.get_array(); BOOST_CHECK(arr.size() == n); - + // Try to add a new key, but we can't as the wallet is locked BOOST_CHECK_THROW(CallRPC("z_getnewaddress"), runtime_error); - + // We can't call RPC walletpassphrase as that invokes RPCRunLater which breaks tests. // So we manually unlock. BOOST_CHECK(pwalletMain->Unlock(strWalletPass)); - + // Now add a key BOOST_CHECK_NO_THROW(CallRPC("z_getnewaddress")); - + // Verify the key has been added BOOST_CHECK_NO_THROW(retValue = CallRPC("z_listaddresses")); arr = retValue.get_array(); - BOOST_CHECK(arr.size() == n+1); + BOOST_CHECK(arr.size() == n+1); // We can't simulate over RPC the wallet closing and being reloaded // but there are tests for this in gtest. } + + +BOOST_AUTO_TEST_CASE(rpc_z_shieldcoinbase_parameters) +{ + SelectParams(CBaseChainParams::TESTNET); + + LOCK(pwalletMain->cs_wallet); + + BOOST_CHECK_THROW(CallRPC("z_shieldcoinbase"), runtime_error); + BOOST_CHECK_THROW(CallRPC("z_shieldcoinbase toofewargs"), runtime_error); + BOOST_CHECK_THROW(CallRPC("z_shieldcoinbase too many args here"), runtime_error); + + // bad from address + BOOST_CHECK_THROW(CallRPC("z_shieldcoinbase " + "INVALIDtmRr6yJonqGK23UVhrKuyvTpF8qxQQjKigJ tnpoQJVnYBZZqkFadj2bJJLThNCxbADGB5gSGeYTAGGrT5tejsxY9Zc1BtY8nnHmZkB"), runtime_error); + + // bad from address + BOOST_CHECK_THROW(CallRPC("z_shieldcoinbase " + "** tnpoQJVnYBZZqkFadj2bJJLThNCxbADGB5gSGeYTAGGrT5tejsxY9Zc1BtY8nnHmZkB"), runtime_error); + + // bad to address + BOOST_CHECK_THROW(CallRPC("z_shieldcoinbase " + "tmRr6yJonqGK23UVhrKuyvTpF8qxQQjKigJ INVALIDtnpoQJVnYBZZqkFadj2bJJLThNCxbADGB5gSGeYTAGGrT5tejsxY9Zc1BtY8nnHmZkB"), runtime_error); + + // invalid fee amount, cannot be negative + BOOST_CHECK_THROW(CallRPC("z_shieldcoinbase " + "tmRr6yJonqGK23UVhrKuyvTpF8qxQQjKigJ " + "tnpoQJVnYBZZqkFadj2bJJLThNCxbADGB5gSGeYTAGGrT5tejsxY9Zc1BtY8nnHmZkB " + "-0.0001" + ), runtime_error); + + // invalid fee amount, bigger than MAX_MONEY + BOOST_CHECK_THROW(CallRPC("z_shieldcoinbase " + "tmRr6yJonqGK23UVhrKuyvTpF8qxQQjKigJ " + "tnpoQJVnYBZZqkFadj2bJJLThNCxbADGB5gSGeYTAGGrT5tejsxY9Zc1BtY8nnHmZkB " + "21000001" + ), runtime_error); + + // Test constructor of AsyncRPCOperation_sendmany + std::string testnetzaddr = "ztjiDe569DPNbyTE6TSdJTaSDhoXEHLGvYoUnBU1wfVNU52TEyT6berYtySkd21njAeEoh8fFJUT42kua9r8EnhBaEKqCpP"; + std::string mainnetzaddr = "zcMuhvq8sEkHALuSU2i4NbNQxshSAYrpCExec45ZjtivYPbuiFPwk6WHy4SvsbeZ4siy1WheuRGjtaJmoD1J8bFqNXhsG6U"; + + try { + std::shared_ptr operation(new AsyncRPCOperation_shieldcoinbase({}, testnetzaddr, -1 )); + } catch (const UniValue& objError) { + BOOST_CHECK( find_error(objError, "Fee is out of range")); + } + + try { + std::shared_ptr operation(new AsyncRPCOperation_shieldcoinbase({}, testnetzaddr, 1)); + } catch (const UniValue& objError) { + BOOST_CHECK( find_error(objError, "Empty inputs")); + } + + // Testnet payment addresses begin with 'zt'. This test detects an incorrect prefix. + try { + std::vector inputs = { ShieldCoinbaseUTXO{uint256(),0,0} }; + std::shared_ptr operation( new AsyncRPCOperation_shieldcoinbase(inputs, mainnetzaddr, 1) ); + } catch (const UniValue& objError) { + BOOST_CHECK( find_error(objError, "payment address is for wrong network type")); + } + +} + + + +BOOST_AUTO_TEST_CASE(rpc_z_shieldcoinbase_internals) +{ + SelectParams(CBaseChainParams::TESTNET); + + LOCK(pwalletMain->cs_wallet); + + UniValue retValue; + + // Test that option -mempooltxinputlimit is respected. + mapArgs["-mempooltxinputlimit"] = "1"; + + // Add keys manually + CZCPaymentAddress pa = pwalletMain->GenerateNewZKey(); + std::string zaddr = pa.ToString(); + + // Supply 2 inputs when mempool limit is 1 + { + std::vector inputs = { ShieldCoinbaseUTXO{uint256(),0,0}, ShieldCoinbaseUTXO{uint256(),0,0} }; + std::shared_ptr operation( new AsyncRPCOperation_shieldcoinbase(inputs, zaddr) ); + operation->main(); + BOOST_CHECK(operation->isFailed()); + std::string msg = operation->getErrorMessage(); + BOOST_CHECK( msg.find("Number of inputs 2 is greater than mempooltxinputlimit of 1") != string::npos); + } + + // Insufficient funds + { + std::vector inputs = { ShieldCoinbaseUTXO{uint256(),0,0} }; + std::shared_ptr operation( new AsyncRPCOperation_shieldcoinbase(inputs, zaddr) ); + operation->main(); + BOOST_CHECK(operation->isFailed()); + std::string msg = operation->getErrorMessage(); + BOOST_CHECK( msg.find("Insufficient coinbase funds") != string::npos); + } + + // Test the perform_joinsplit methods. + { + // Dummy input so the operation object can be instantiated. + std::vector inputs = { ShieldCoinbaseUTXO{uint256(),0,100000} }; + std::shared_ptr operation( new AsyncRPCOperation_shieldcoinbase(inputs, zaddr) ); + std::shared_ptr ptr = std::dynamic_pointer_cast (operation); + TEST_FRIEND_AsyncRPCOperation_shieldcoinbase proxy(ptr); + static_cast(operation.get())->testmode = true; + + ShieldCoinbaseJSInfo info; + info.vjsin.push_back(JSInput()); + info.vjsin.push_back(JSInput()); + info.vjsin.push_back(JSInput()); + try { + proxy.perform_joinsplit(info); + } catch (const std::runtime_error & e) { + BOOST_CHECK( string(e.what()).find("unsupported joinsplit input")!= string::npos); + } + + info.vjsin.clear(); + try { + proxy.perform_joinsplit(info); + } catch (const std::runtime_error & e) { + BOOST_CHECK( string(e.what()).find("JoinSplit verifying key not loaded")!= string::npos); + } + } + +} + + BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/script_P2PKH_tests.cpp b/src/test/script_P2PKH_tests.cpp new file mode 100644 index 00000000000..8fbb054b9f0 --- /dev/null +++ b/src/test/script_P2PKH_tests.cpp @@ -0,0 +1,59 @@ +// Copyright (c) 2012-2015 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include "script/script.h" +#include "test/test_bitcoin.h" + +#include + +using namespace std; + +BOOST_FIXTURE_TEST_SUITE(script_P2PKH_tests, BasicTestingSetup) + +BOOST_AUTO_TEST_CASE(IsPayToPublicKeyHash) +{ + // Test CScript::IsPayToPublicKeyHash() + uint160 dummy; + CScript p2pkh; + p2pkh << OP_DUP << OP_HASH160 << ToByteVector(dummy) << OP_EQUALVERIFY << OP_CHECKSIG; + BOOST_CHECK(p2pkh.IsPayToPublicKeyHash()); + + static const unsigned char direct[] = { + OP_DUP, OP_HASH160, 20, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, OP_EQUALVERIFY, OP_CHECKSIG + }; + BOOST_CHECK(CScript(direct, direct+sizeof(direct)).IsPayToPublicKeyHash()); + + static const unsigned char notp2pkh1[] = { + OP_DUP, OP_HASH160, 20, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, OP_EQUALVERIFY, OP_CHECKSIG, OP_CHECKSIG + }; + BOOST_CHECK(!CScript(notp2pkh1, notp2pkh1+sizeof(notp2pkh1)).IsPayToPublicKeyHash()); + + static const unsigned char p2sh[] = { + OP_HASH160, 20, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, OP_EQUAL + }; + BOOST_CHECK(!CScript(p2sh, p2sh+sizeof(p2sh)).IsPayToPublicKeyHash()); + + static const unsigned char extra[] = { + OP_DUP, OP_HASH160, 20, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, OP_EQUALVERIFY, OP_CHECKSIG, OP_CHECKSIG + }; + BOOST_CHECK(!CScript(extra, extra+sizeof(extra)).IsPayToPublicKeyHash()); + + static const unsigned char missing[] = { + OP_HASH160, 20, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, OP_EQUALVERIFY, OP_CHECKSIG, OP_RETURN + }; + BOOST_CHECK(!CScript(missing, missing+sizeof(missing)).IsPayToPublicKeyHash()); + + static const unsigned char missing2[] = { + OP_DUP, OP_HASH160, 20, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 + }; + BOOST_CHECK(!CScript(missing2, missing2+sizeof(missing)).IsPayToPublicKeyHash()); + + static const unsigned char tooshort[] = { + OP_DUP, OP_HASH160, 2, 0,0, OP_EQUALVERIFY, OP_CHECKSIG + }; + BOOST_CHECK(!CScript(tooshort, tooshort+sizeof(direct)).IsPayToPublicKeyHash()); + +} + +BOOST_AUTO_TEST_SUITE_END() \ No newline at end of file diff --git a/src/test/test_bitcoin.h b/src/test/test_bitcoin.h index 205e5b5c46c..5ed340d0b73 100755 --- a/src/test/test_bitcoin.h +++ b/src/test/test_bitcoin.h @@ -1,6 +1,7 @@ #ifndef BITCOIN_TEST_TEST_BITCOIN_H #define BITCOIN_TEST_TEST_BITCOIN_H +#include "chainparamsbase.h" #include "pubkey.h" #include "txdb.h" diff --git a/src/test/wallet-utility.py b/src/test/wallet-utility.py new file mode 100644 index 00000000000..c0a7bfb9ea9 --- /dev/null +++ b/src/test/wallet-utility.py @@ -0,0 +1,61 @@ +#!/usr/bin/python +# Copyright 2014 BitPay, Inc. +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. + +import subprocess +import os +import json +import sys +import buildenv +import shutil + +def assert_equal(thing1, thing2): + if thing1 != thing2: + raise AssertionError("%s != %s"%(str(thing1),str(thing2))) + +if __name__ == '__main__': + datadir = os.environ["srcdir"] + "/test/data" + execprog = './wallet-utility' + buildenv.exeext + execargs = '-datadir=' + datadir + execrun = execprog + ' ' + execargs + + proc = subprocess.Popen(execrun, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True, shell=True) + try: + outs = proc.communicate() + except OSError: + print("OSError, Failed to execute " + execprog) + sys.exit(1) + + output = json.loads(outs[0]) + + assert_equal(output[0], "13EngsxkRi7SJPPqCyJsKf34U8FoX9E9Av") + assert_equal(output[1], "1FKCLGTpPeYBUqfNxktck8k5nqxB8sjim8") + assert_equal(output[2], "13cdtE9tnNeXCZJ8KQ5WELgEmLSBLnr48F") + + execargs = '-datadir=' + datadir + ' -dumppass' + execrun = execprog + ' ' + execargs + + proc = subprocess.Popen(execrun, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True, shell=True) + try: + outs = proc.communicate() + except OSError: + print("OSError, Failed to execute " + execprog) + sys.exit(1) + + output = json.loads(outs[0]) + + assert_equal(output[0]['addr'], "13EngsxkRi7SJPPqCyJsKf34U8FoX9E9Av") + assert_equal(output[0]['pkey'], "5Jz5BWE2WQxp1hGqDZeisQFV1mRFR2AVBAgiXCbNcZyXNjD9aUd") + assert_equal(output[1]['addr'], "1FKCLGTpPeYBUqfNxktck8k5nqxB8sjim8") + assert_equal(output[1]['pkey'], "5HsX2b3v2GjngYQ5ZM4mLp2b2apw6aMNVaPELV1YmpiYR1S4jzc") + assert_equal(output[2]['addr'], "13cdtE9tnNeXCZJ8KQ5WELgEmLSBLnr48F") + assert_equal(output[2]['pkey'], "5KCWAs1wX2ESiL4PfDR8XYVSSETHFd2jaRGxt1QdanBFTit4XcH") + + if os.path.exists(datadir + '/database'): + if os.path.isdir(datadir + '/database'): + shutil.rmtree(datadir + '/database') + + if os.path.exists(datadir + '/db.log'): + os.remove(datadir + '/db.log') + sys.exit(0) \ No newline at end of file diff --git a/src/txdb.cpp b/src/txdb.cpp index 012d44cc95a..4183691a89a 100755 --- a/src/txdb.cpp +++ b/src/txdb.cpp @@ -267,210 +267,200 @@ bool CBlockTreeDB::WriteTxIndex(const std::vector } bool CBlockTreeDB::ReadSpentIndex(CSpentIndexKey &key, CSpentIndexValue &value) { - return Read(make_pair(DB_SPENTINDEX, key), value); - } - - bool CBlockTreeDB::UpdateSpentIndex(const std::vector >&vect) { - CLevelDBBatch batch; - for (std::vector >::const_iterator it=vect.begin(); it!=vect.end(); it++) { - if (it->second.IsNull()) { - batch.Erase(make_pair(DB_SPENTINDEX, it->first)); - } else { - batch.Write(make_pair(DB_SPENTINDEX, it->first), it->second); - } - } - return WriteBatch(batch); - } - - bool CBlockTreeDB::UpdateAddressUnspentIndex(const std::vector >&vect) { - CLevelDBBatch batch; - for (std::vector >::const_iterator it=vect.begin(); it!=vect.end(); it++) { - if (it->second.IsNull()) { - batch.Erase(make_pair(DB_ADDRESSUNSPENTINDEX, it->first)); - } else { - batch.Write(make_pair(DB_ADDRESSUNSPENTINDEX, it->first), it->second); - } - } - return WriteBatch(batch); - } - - bool CBlockTreeDB::ReadAddressUnspentIndex(uint160 addressHash, int type, - std::vector > &unspentOutputs) { - - boost::scoped_ptr pcursor(NewIterator()); - - CDataStream ssKeySet(SER_DISK, CLIENT_VERSION); - ssKeySet << make_pair(DB_ADDRESSUNSPENTINDEX, CAddressIndexIteratorKey(type, addressHash)); - pcursor->Seek(ssKeySet.str()); - - while (pcursor->Valid()) { - boost::this_thread::interruption_point(); - try { - leveldb::Slice slKey = pcursor->key(); - CDataStream ssKey(slKey.data(), slKey.data()+slKey.size(), SER_DISK, CLIENT_VERSION); - char chType; - CAddressUnspentKey indexKey; - ssKey >> chType; - ssKey >> indexKey; - if (chType == DB_ADDRESSUNSPENTINDEX && indexKey.hashBytes == addressHash) { - try { - leveldb::Slice slValue = pcursor->value(); - CDataStream ssValue(slValue.data(), slValue.data()+slValue.size(), SER_DISK, CLIENT_VERSION); - CAddressUnspentValue nValue; - ssValue >> nValue; - unspentOutputs.push_back(make_pair(indexKey, nValue)); - pcursor->Next(); - } catch (const std::exception& e) { - return error("failed to get address unspent value"); - } - } else { - break; - } - } catch (const std::exception& e) { - break; - } - } - - return true; - } - - bool CBlockTreeDB::WriteAddressIndex(const std::vector >&vect) { - CLevelDBBatch batch; - for (std::vector >::const_iterator it=vect.begin(); it!=vect.end(); it++) - batch.Write(make_pair(DB_ADDRESSINDEX, it->first), it->second); - return WriteBatch(batch); - } - - bool CBlockTreeDB::EraseAddressIndex(const std::vector >&vect) { - CLevelDBBatch batch; - for (std::vector >::const_iterator it=vect.begin(); it!=vect.end(); it++) - batch.Erase(make_pair(DB_ADDRESSINDEX, it->first)); - return WriteBatch(batch); - } - - bool CBlockTreeDB::ReadAddressIndex(uint160 addressHash, int type, - std::vector > &addressIndex, - int start, int end) { - - boost::scoped_ptr pcursor(NewIterator()); - - CDataStream ssKeySet(SER_DISK, CLIENT_VERSION); - if (start > 0 && end > 0) { - ssKeySet << make_pair(DB_ADDRESSINDEX, CAddressIndexIteratorHeightKey(type, addressHash, start)); - } else { - ssKeySet << make_pair(DB_ADDRESSINDEX, CAddressIndexIteratorKey(type, addressHash)); - } - pcursor->Seek(ssKeySet.str()); - - while (pcursor->Valid()) { - boost::this_thread::interruption_point(); - try { - leveldb::Slice slKey = pcursor->key(); - CDataStream ssKey(slKey.data(), slKey.data()+slKey.size(), SER_DISK, CLIENT_VERSION); - char chType; - CAddressIndexKey indexKey; - ssKey >> chType; - ssKey >> indexKey; - if (chType == DB_ADDRESSINDEX && indexKey.hashBytes == addressHash) { - if (end > 0 && indexKey.blockHeight > end) { - break; - } - try { - leveldb::Slice slValue = pcursor->value(); - CDataStream ssValue(slValue.data(), slValue.data()+slValue.size(), SER_DISK, CLIENT_VERSION); - CAmount nValue; - ssValue >> nValue; - - addressIndex.push_back(make_pair(indexKey, nValue)); - pcursor->Next(); - } catch (const std::exception& e) { - return error("failed to get address index value"); - } - } else { - break; - } - } catch (const std::exception& e) { - break; - } - } - - return true; - } - - bool CBlockTreeDB::WriteTimestampIndex(const CTimestampIndexKey ×tampIndex) { - CLevelDBBatch batch; - batch.Write(make_pair(DB_TIMESTAMPINDEX, timestampIndex), 0); - return WriteBatch(batch); - } - - bool CBlockTreeDB::ReadTimestampIndex(const unsigned int &high, const unsigned int &low, const bool fActiveOnly, std::vector > &hashes) { - - boost::scoped_ptr pcursor(NewIterator()); - - CDataStream ssKeySet(SER_DISK, CLIENT_VERSION); - ssKeySet << make_pair(DB_TIMESTAMPINDEX, CTimestampIndexIteratorKey(low)); - pcursor->Seek(ssKeySet.str()); - - while (pcursor->Valid()) { - boost::this_thread::interruption_point(); - try { - leveldb::Slice slKey = pcursor->key(); - CDataStream ssKey(slKey.data(), slKey.data()+slKey.size(), SER_DISK, CLIENT_VERSION); - char chType; - CTimestampIndexKey indexKey; - ssKey >> chType; - ssKey >> indexKey; - if (chType == DB_TIMESTAMPINDEX && indexKey.timestamp < high) { - if (fActiveOnly) { - if (blockOnchainActive(indexKey.blockHash)) { - hashes.push_back(std::make_pair(indexKey.blockHash, indexKey.timestamp)); - } - } else { - hashes.push_back(std::make_pair(indexKey.blockHash, indexKey.timestamp)); - } - - pcursor->Next(); - } else { - break; - } - } catch (const std::exception& e) { - break; - } - } - - return true; - } - - bool CBlockTreeDB::WriteTimestampBlockIndex(const CTimestampBlockIndexKey &blockhashIndex, const CTimestampBlockIndexValue &logicalts) { - CLevelDBBatch batch; - batch.Write(make_pair(DB_BLOCKHASHINDEX, blockhashIndex), logicalts); - return WriteBatch(batch); - } - - bool CBlockTreeDB::ReadTimestampBlockIndex(const uint256 &hash, unsigned int <imestamp) { - - CTimestampBlockIndexValue(lts); - if (!Read(std::make_pair(DB_BLOCKHASHINDEX, hash), lts)) - return false; - - ltimestamp = lts.ltimestamp; - return true; - } + return Read(make_pair(DB_SPENTINDEX, key), value); +} + +bool CBlockTreeDB::UpdateSpentIndex(const std::vector >&vect) { + CLevelDBBatch batch; + for (std::vector >::const_iterator it=vect.begin(); it!=vect.end(); it++) { + if (it->second.IsNull()) { + batch.Erase(make_pair(DB_SPENTINDEX, it->first)); + } else { + batch.Write(make_pair(DB_SPENTINDEX, it->first), it->second); + } + } + return WriteBatch(batch); +} + +bool CBlockTreeDB::UpdateAddressUnspentIndex(const std::vector >&vect) { + CLevelDBBatch batch; + for (std::vector >::const_iterator it=vect.begin(); it!=vect.end(); it++) { + if (it->second.IsNull()) { + batch.Erase(make_pair(DB_ADDRESSUNSPENTINDEX, it->first)); + } else { + batch.Write(make_pair(DB_ADDRESSUNSPENTINDEX, it->first), it->second); + } + } + return WriteBatch(batch); +} + +bool CBlockTreeDB::ReadAddressUnspentIndex(uint160 addressHash, int type, + std::vector > &unspentOutputs) { + + boost::scoped_ptr pcursor(NewIterator()); + + CDataStream ssKeySet(SER_DISK, CLIENT_VERSION); + ssKeySet << make_pair(DB_ADDRESSUNSPENTINDEX, CAddressIndexIteratorKey(type, addressHash)); + pcursor->Seek(ssKeySet.str()); + + while (pcursor->Valid()) { + boost::this_thread::interruption_point(); + try { + leveldb::Slice slKey = pcursor->key(); + CDataStream ssKey(slKey.data(), slKey.data()+slKey.size(), SER_DISK, CLIENT_VERSION); + char chType; + CAddressUnspentKey indexKey; + ssKey >> chType; + ssKey >> indexKey; + if (chType == DB_ADDRESSUNSPENTINDEX && indexKey.hashBytes == addressHash) { + try { + leveldb::Slice slValue = pcursor->value(); + CDataStream ssValue(slValue.data(), slValue.data()+slValue.size(), SER_DISK, CLIENT_VERSION); + CAddressUnspentValue nValue; + ssValue >> nValue; + unspentOutputs.push_back(make_pair(indexKey, nValue)); + pcursor->Next(); + } catch (const std::exception& e) { + return error("failed to get address unspent value"); + } + } else { + break; + } + } catch (const std::exception& e) { + break; + } + } + + return true; +} + +bool CBlockTreeDB::WriteAddressIndex(const std::vector >&vect) { + CLevelDBBatch batch; + for (std::vector >::const_iterator it=vect.begin(); it!=vect.end(); it++) + batch.Write(make_pair(DB_ADDRESSINDEX, it->first), it->second); + return WriteBatch(batch); +} + +bool CBlockTreeDB::EraseAddressIndex(const std::vector >&vect) { + CLevelDBBatch batch; + for (std::vector >::const_iterator it=vect.begin(); it!=vect.end(); it++) + batch.Erase(make_pair(DB_ADDRESSINDEX, it->first)); + return WriteBatch(batch); +} + +bool CBlockTreeDB::ReadAddressIndex(uint160 addressHash, int type, + std::vector > &addressIndex, + int start, int end) { + + boost::scoped_ptr pcursor(NewIterator()); + + CDataStream ssKeySet(SER_DISK, CLIENT_VERSION); + if (start > 0 && end > 0) { + ssKeySet << make_pair(DB_ADDRESSINDEX, CAddressIndexIteratorHeightKey(type, addressHash, start)); + } else { + ssKeySet << make_pair(DB_ADDRESSINDEX, CAddressIndexIteratorKey(type, addressHash)); + } + pcursor->Seek(ssKeySet.str()); + + while (pcursor->Valid()) { + boost::this_thread::interruption_point(); + try { + leveldb::Slice slKey = pcursor->key(); + CDataStream ssKey(slKey.data(), slKey.data()+slKey.size(), SER_DISK, CLIENT_VERSION); + char chType; + CAddressIndexKey indexKey; + ssKey >> chType; + ssKey >> indexKey; + if (chType == DB_ADDRESSINDEX && indexKey.hashBytes == addressHash) { + if (end > 0 && indexKey.blockHeight > end) { + break; + } + try { + leveldb::Slice slValue = pcursor->value(); + CDataStream ssValue(slValue.data(), slValue.data()+slValue.size(), SER_DISK, CLIENT_VERSION); + CAmount nValue; + ssValue >> nValue; + + addressIndex.push_back(make_pair(indexKey, nValue)); + pcursor->Next(); + } catch (const std::exception& e) { + return error("failed to get address index value"); + } + } else { + break; + } + } catch (const std::exception& e) { + break; + } + } + + return true; +} + +bool CBlockTreeDB::WriteTimestampIndex(const CTimestampIndexKey ×tampIndex) { + CLevelDBBatch batch; + batch.Write(make_pair(DB_TIMESTAMPINDEX, timestampIndex), 0); + return WriteBatch(batch); +} + +bool CBlockTreeDB::ReadTimestampIndex(const unsigned int &high, const unsigned int &low, const bool fActiveOnly, std::vector > &hashes) { + + boost::scoped_ptr pcursor(NewIterator()); + + CDataStream ssKeySet(SER_DISK, CLIENT_VERSION); + ssKeySet << make_pair(DB_TIMESTAMPINDEX, CTimestampIndexIteratorKey(low)); + pcursor->Seek(ssKeySet.str()); + + while (pcursor->Valid()) { + boost::this_thread::interruption_point(); + try { + leveldb::Slice slKey = pcursor->key(); + CDataStream ssKey(slKey.data(), slKey.data()+slKey.size(), SER_DISK, CLIENT_VERSION); + char chType; + CTimestampIndexKey indexKey; + ssKey >> chType; + ssKey >> indexKey; + if (chType == DB_TIMESTAMPINDEX && indexKey.timestamp < high) { + if (fActiveOnly) { + if (blockOnchainActive(indexKey.blockHash)) { + hashes.push_back(std::make_pair(indexKey.blockHash, indexKey.timestamp)); + } + } else { + hashes.push_back(std::make_pair(indexKey.blockHash, indexKey.timestamp)); + } + + pcursor->Next(); + } else { + break; + } + } catch (const std::exception& e) { + break; + } + } + + return true; +} + +bool CBlockTreeDB::WriteTimestampBlockIndex(const CTimestampBlockIndexKey &blockhashIndex, const CTimestampBlockIndexValue &logicalts) { + CLevelDBBatch batch; + batch.Write(make_pair(DB_BLOCKHASHINDEX, blockhashIndex), logicalts); + return WriteBatch(batch); +} + +bool CBlockTreeDB::ReadTimestampBlockIndex(const uint256 &hash, unsigned int <imestamp) { + + CTimestampBlockIndexValue(lts); + if (!Read(std::make_pair(DB_BLOCKHASHINDEX, hash), lts)) + return false; + + ltimestamp = lts.ltimestamp; + return true; +} bool CBlockTreeDB::WriteFlag(const std::string &name, bool fValue) { return Write(std::make_pair(DB_FLAG, name), fValue ? '1' : '0'); } -bool CBlockTreeDB::blockOnchainActive(const uint256 &hash) { - CBlockIndex* pblockindex = mapBlockIndex[hash]; - - if (!chainActive.Contains(pblockindex)) { - return false; - } - - return true; - } - bool CBlockTreeDB::ReadFlag(const std::string &name, bool &fValue) { char ch; if (!Read(std::make_pair(DB_FLAG, name), ch)) @@ -479,6 +469,16 @@ bool CBlockTreeDB::ReadFlag(const std::string &name, bool &fValue) { return true; } +bool CBlockTreeDB::blockOnchainActive(const uint256 &hash) { + CBlockIndex* pblockindex = mapBlockIndex[hash]; + + if (!chainActive.Contains(pblockindex)) { + return false; + } + + return true; +} + bool CBlockTreeDB::LoadBlockIndexGuts() { boost::scoped_ptr pcursor(NewIterator()); diff --git a/src/txmempool.cpp b/src/txmempool.cpp index 587c758c85c..71dcab0d6b9 100755 --- a/src/txmempool.cpp +++ b/src/txmempool.cpp @@ -300,6 +300,8 @@ void CTxMemPool::remove(const CTransaction &origTx, std::list& rem mapTx.erase(hash); nTransactionsUpdated++; minerPolicyEstimator->removeTx(hash); + removeAddressIndex(hash); + removeSpentIndex(hash); } } } diff --git a/src/txmempool.h b/src/txmempool.h index af095898a5c..b7320115d77 100755 --- a/src/txmempool.h +++ b/src/txmempool.h @@ -103,7 +103,7 @@ class CTxMemPool public: mutable CCriticalSection cs; std::map mapTx; - + private: typedef std::map addressDeltaMap; addressDeltaMap mapAddress; @@ -135,8 +135,8 @@ class CTxMemPool void setSanityCheck(bool _fSanityCheck) { fSanityCheck = _fSanityCheck; } bool addUnchecked(const uint256& hash, const CTxMemPoolEntry &entry, bool fCurrentEstimate = true); - - void addAddressIndex(const CTxMemPoolEntry &entry, const CCoinsViewCache &view); + + void addAddressIndex(const CTxMemPoolEntry &entry, const CCoinsViewCache &view); bool getAddressIndex(std::vector > &addresses, std::vector > &results); bool removeAddressIndex(const uint256 txhash); @@ -144,7 +144,7 @@ class CTxMemPool void addSpentIndex(const CTxMemPoolEntry &entry, const CCoinsViewCache &view); bool getSpentIndex(CSpentIndexKey &key, CSpentIndexValue &value); bool removeSpentIndex(const uint256 txhash); - + void remove(const CTransaction &tx, std::list& removed, bool fRecursive = false); void removeWithAnchor(const uint256 &invalidRoot); void removeCoinbaseSpends(const CCoinsViewCache *pcoins, unsigned int nMemPoolHeight); diff --git a/src/wallet/asyncrpcoperation_shieldcoinbase.cpp b/src/wallet/asyncrpcoperation_shieldcoinbase.cpp new file mode 100644 index 00000000000..20d659cc226 --- /dev/null +++ b/src/wallet/asyncrpcoperation_shieldcoinbase.cpp @@ -0,0 +1,446 @@ +// 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. + +#include "asyncrpcqueue.h" +#include "amount.h" +#include "core_io.h" +#include "init.h" +#include "main.h" +#include "net.h" +#include "netbase.h" +#include "rpcserver.h" +#include "timedata.h" +#include "util.h" +#include "utilmoneystr.h" +#include "wallet.h" +#include "walletdb.h" +#include "script/interpreter.h" +#include "utiltime.h" +#include "rpcprotocol.h" +#include "zcash/IncrementalMerkleTree.hpp" +#include "sodium.h" +#include "miner.h" + +#include +#include +#include +#include + +#include "asyncrpcoperation_shieldcoinbase.h" + +using namespace libzcash; + +static int find_output(UniValue obj, int n) { + UniValue outputMapValue = find_value(obj, "outputmap"); + if (!outputMapValue.isArray()) { + throw JSONRPCError(RPC_WALLET_ERROR, "Missing outputmap for JoinSplit operation"); + } + + UniValue outputMap = outputMapValue.get_array(); + assert(outputMap.size() == ZC_NUM_JS_OUTPUTS); + for (size_t i = 0; i < outputMap.size(); i++) { + if (outputMap[i].get_int() == n) { + return i; + } + } + + throw std::logic_error("n is not present in outputmap"); +} + +AsyncRPCOperation_shieldcoinbase::AsyncRPCOperation_shieldcoinbase( + std::vector inputs, + std::string toAddress, + CAmount fee, + UniValue contextInfo) : + inputs_(inputs), fee_(fee), contextinfo_(contextInfo) +{ + if (fee < 0 || fee > MAX_MONEY) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Fee is out of range"); + } + + if (inputs.size() == 0) { + throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, "Empty inputs"); + } + + // Check the destination address is valid for this network i.e. not testnet being used on mainnet + CZCPaymentAddress address(toAddress); + try { + tozaddr_ = address.Get(); + } catch (const std::runtime_error& e) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, string("runtime error: ") + e.what()); + } + + // Log the context info + if (LogAcceptCategory("zrpcunsafe")) { + LogPrint("zrpcunsafe", "%s: z_shieldcoinbase initialized (context=%s)\n", getId(), contextInfo.write()); + } else { + LogPrint("zrpc", "%s: z_shieldcoinbase initialized\n", getId()); + } + + // Lock UTXOs + lock_utxos(); +} + +AsyncRPCOperation_shieldcoinbase::~AsyncRPCOperation_shieldcoinbase() { +} + +void AsyncRPCOperation_shieldcoinbase::main() { + if (isCancelled()) { + unlock_utxos(); // clean up + return; + } + + set_state(OperationStatus::EXECUTING); + start_execution_clock(); + + bool success = false; + +#ifdef ENABLE_MINING + #ifdef ENABLE_WALLET + GenerateBitcoins(false, NULL, 0); + #else + GenerateBitcoins(false, 0); + #endif +#endif + + try { + success = main_impl(); + } catch (const UniValue& objError) { + int code = find_value(objError, "code").get_int(); + std::string message = find_value(objError, "message").get_str(); + set_error_code(code); + set_error_message(message); + } catch (const runtime_error& e) { + set_error_code(-1); + set_error_message("runtime error: " + string(e.what())); + } catch (const logic_error& e) { + set_error_code(-1); + set_error_message("logic error: " + string(e.what())); + } catch (const exception& e) { + set_error_code(-1); + set_error_message("general exception: " + string(e.what())); + } catch (...) { + set_error_code(-2); + set_error_message("unknown error"); + } + +#ifdef ENABLE_MINING + #ifdef ENABLE_WALLET + GenerateBitcoins(GetBoolArg("-gen",false), pwalletMain, GetArg("-genproclimit", 1)); + #else + GenerateBitcoins(GetBoolArg("-gen",false), GetArg("-genproclimit", 1)); + #endif +#endif + + stop_execution_clock(); + + if (success) { + set_state(OperationStatus::SUCCESS); + } else { + set_state(OperationStatus::FAILED); + } + + std::string s = strprintf("%s: z_shieldcoinbase finished (status=%s", getId(), getStateAsString()); + if (success) { + s += strprintf(", txid=%s)\n", tx_.GetHash().ToString()); + } else { + s += strprintf(", error=%s)\n", getErrorMessage()); + } + LogPrintf("%s",s); + + unlock_utxos(); // clean up +} + + +bool AsyncRPCOperation_shieldcoinbase::main_impl() { + + CAmount minersFee = fee_; + + size_t numInputs = inputs_.size(); + + // Check mempooltxinputlimit to avoid creating a transaction which the local mempool rejects + size_t limit = (size_t)GetArg("-mempooltxinputlimit", 0); + if (limit>0 && numInputs > limit) { + throw JSONRPCError(RPC_WALLET_ERROR, + strprintf("Number of inputs %d is greater than mempooltxinputlimit of %d", + numInputs, limit)); + } + + CAmount targetAmount = 0; + for (ShieldCoinbaseUTXO & utxo : inputs_) { + targetAmount += utxo.amount; + } + + if (targetAmount <= minersFee) { + throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, + strprintf("Insufficient coinbase funds, have %s and miners fee is %s", + FormatMoney(targetAmount), FormatMoney(minersFee))); + } + + CAmount sendAmount = targetAmount - minersFee; + LogPrint("zrpc", "%s: spending %s to shield %s with fee %s\n", + getId(), FormatMoney(targetAmount), FormatMoney(sendAmount), FormatMoney(minersFee)); + + // update the transaction with these inputs + CMutableTransaction rawTx(tx_); + for (ShieldCoinbaseUTXO & t : inputs_) { + CTxIn in(COutPoint(t.txid, t.vout)); + rawTx.vin.push_back(in); + } + tx_ = CTransaction(rawTx); + + // Prepare raw transaction to handle JoinSplits + CMutableTransaction mtx(tx_); + mtx.nVersion = 2; + crypto_sign_keypair(joinSplitPubKey_.begin(), joinSplitPrivKey_); + mtx.joinSplitPubKey = joinSplitPubKey_; + tx_ = CTransaction(mtx); + + // Create joinsplit + UniValue obj(UniValue::VOBJ); + ShieldCoinbaseJSInfo info; + info.vpub_old = sendAmount; + info.vpub_new = 0; + JSOutput jso = JSOutput(tozaddr_, sendAmount); + info.vjsout.push_back(jso); + obj = perform_joinsplit(info); + + sign_send_raw_transaction(obj); + return true; +} + + +/** + * Sign and send a raw transaction. + * Raw transaction as hex string should be in object field "rawtxn" + */ +void AsyncRPCOperation_shieldcoinbase::sign_send_raw_transaction(UniValue obj) +{ + // Sign the raw transaction + UniValue rawtxnValue = find_value(obj, "rawtxn"); + if (rawtxnValue.isNull()) { + throw JSONRPCError(RPC_WALLET_ERROR, "Missing hex data for raw transaction"); + } + std::string rawtxn = rawtxnValue.get_str(); + + UniValue params = UniValue(UniValue::VARR); + params.push_back(rawtxn); + UniValue signResultValue = signrawtransaction(params, false); + UniValue signResultObject = signResultValue.get_obj(); + UniValue completeValue = find_value(signResultObject, "complete"); + bool complete = completeValue.get_bool(); + if (!complete) { + // TODO: #1366 Maybe get "errors" and print array vErrors into a string + throw JSONRPCError(RPC_WALLET_ENCRYPTION_FAILED, "Failed to sign transaction"); + } + + UniValue hexValue = find_value(signResultObject, "hex"); + if (hexValue.isNull()) { + throw JSONRPCError(RPC_WALLET_ERROR, "Missing hex data for signed transaction"); + } + std::string signedtxn = hexValue.get_str(); + + // Send the signed transaction + if (!testmode) { + params.clear(); + params.setArray(); + params.push_back(signedtxn); + UniValue sendResultValue = sendrawtransaction(params, false); + if (sendResultValue.isNull()) { + throw JSONRPCError(RPC_WALLET_ERROR, "Send raw transaction did not return an error or a txid."); + } + + std::string txid = sendResultValue.get_str(); + + UniValue o(UniValue::VOBJ); + o.push_back(Pair("txid", txid)); + set_result(o); + } else { + // Test mode does not send the transaction to the network. + + CDataStream stream(ParseHex(signedtxn), SER_NETWORK, PROTOCOL_VERSION); + CTransaction tx; + stream >> tx; + + UniValue o(UniValue::VOBJ); + o.push_back(Pair("test", 1)); + o.push_back(Pair("txid", tx.GetHash().ToString())); + o.push_back(Pair("hex", signedtxn)); + set_result(o); + } + + // Keep the signed transaction so we can hash to the same txid + CDataStream stream(ParseHex(signedtxn), SER_NETWORK, PROTOCOL_VERSION); + CTransaction tx; + stream >> tx; + tx_ = tx; +} + + +UniValue AsyncRPCOperation_shieldcoinbase::perform_joinsplit(ShieldCoinbaseJSInfo & info) { + uint256 anchor; + { + LOCK(cs_main); + anchor = pcoinsTip->GetBestAnchor(); + } + + if (anchor.IsNull()) { + throw std::runtime_error("anchor is null"); + } + + // Make sure there are two inputs and two outputs + while (info.vjsin.size() < ZC_NUM_JS_INPUTS) { + info.vjsin.push_back(JSInput()); + } + + while (info.vjsout.size() < ZC_NUM_JS_OUTPUTS) { + info.vjsout.push_back(JSOutput()); + } + + if (info.vjsout.size() != ZC_NUM_JS_INPUTS || info.vjsin.size() != ZC_NUM_JS_OUTPUTS) { + throw runtime_error("unsupported joinsplit input/output counts"); + } + + CMutableTransaction mtx(tx_); + + LogPrint("zrpcunsafe", "%s: creating joinsplit at index %d (vpub_old=%s, vpub_new=%s, in[0]=%s, in[1]=%s, out[0]=%s, out[1]=%s)\n", + getId(), + tx_.vjoinsplit.size(), + FormatMoney(info.vpub_old), FormatMoney(info.vpub_new), + FormatMoney(info.vjsin[0].note.value), FormatMoney(info.vjsin[1].note.value), + FormatMoney(info.vjsout[0].value), FormatMoney(info.vjsout[1].value) + ); + + // Generate the proof, this can take over a minute. + boost::array inputs + {info.vjsin[0], info.vjsin[1]}; + boost::array outputs + {info.vjsout[0], info.vjsout[1]}; + boost::array inputMap; + boost::array outputMap; + JSDescription jsdesc = JSDescription::Randomized( + *pzcashParams, + joinSplitPubKey_, + anchor, + inputs, + outputs, + inputMap, + outputMap, + info.vpub_old, + info.vpub_new, + !this->testmode); + + { + auto verifier = libzcash::ProofVerifier::Strict(); + if (!(jsdesc.Verify(*pzcashParams, verifier, joinSplitPubKey_))) { + throw std::runtime_error("error verifying joinsplit"); + } + } + + mtx.vjoinsplit.push_back(jsdesc); + + // Empty output script. + CScript scriptCode; + CTransaction signTx(mtx); + uint256 dataToBeSigned = SignatureHash(scriptCode, signTx, NOT_AN_INPUT, SIGHASH_ALL); + + // Add the signature + if (!(crypto_sign_detached(&mtx.joinSplitSig[0], NULL, + dataToBeSigned.begin(), 32, + joinSplitPrivKey_ + ) == 0)) + { + throw std::runtime_error("crypto_sign_detached failed"); + } + + // Sanity check + if (!(crypto_sign_verify_detached(&mtx.joinSplitSig[0], + dataToBeSigned.begin(), 32, + mtx.joinSplitPubKey.begin() + ) == 0)) + { + throw std::runtime_error("crypto_sign_verify_detached 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 << jsdesc.h_sig(*pzcashParams, 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 << jsdesc.h_sig(*pzcashParams, 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(inputMap[i]); + } + for (size_t i = 0; i < ZC_NUM_JS_OUTPUTS; i++) { + arrOutputMap.push_back(outputMap[i]); + } + + UniValue obj(UniValue::VOBJ); + obj.push_back(Pair("encryptednote1", encryptedNote1)); + obj.push_back(Pair("encryptednote2", encryptedNote2)); + obj.push_back(Pair("rawtxn", HexStr(ss.begin(), ss.end()))); + obj.push_back(Pair("inputmap", arrInputMap)); + obj.push_back(Pair("outputmap", arrOutputMap)); + return obj; +} + +/** + * Override getStatus() to append the operation's context object to the default status object. + */ +UniValue AsyncRPCOperation_shieldcoinbase::getStatus() const { + UniValue v = AsyncRPCOperation::getStatus(); + if (contextinfo_.isNull()) { + return v; + } + + UniValue obj = v.get_obj(); + obj.push_back(Pair("method", "z_shieldcoinbase")); + obj.push_back(Pair("params", contextinfo_ )); + return obj; +} + +/** + * Lock input utxos + */ + void AsyncRPCOperation_shieldcoinbase::lock_utxos() { + LOCK2(cs_main, pwalletMain->cs_wallet); + for (auto utxo : inputs_) { + COutPoint outpt(utxo.txid, utxo.vout); + pwalletMain->LockCoin(outpt); + } +} + +/** + * Unlock input utxos + */ +void AsyncRPCOperation_shieldcoinbase::unlock_utxos() { + LOCK2(cs_main, pwalletMain->cs_wallet); + for (auto utxo : inputs_) { + COutPoint outpt(utxo.txid, utxo.vout); + pwalletMain->UnlockCoin(outpt); + } +} diff --git a/src/wallet/asyncrpcoperation_shieldcoinbase.h b/src/wallet/asyncrpcoperation_shieldcoinbase.h new file mode 100644 index 00000000000..981b2fbe912 --- /dev/null +++ b/src/wallet/asyncrpcoperation_shieldcoinbase.h @@ -0,0 +1,122 @@ +// 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. + +#ifndef ASYNCRPCOPERATION_SHIELDCOINBASE_H +#define ASYNCRPCOPERATION_SHIELDCOINBASE_H + +#include "asyncrpcoperation.h" +#include "amount.h" +#include "base58.h" +#include "primitives/transaction.h" +#include "zcash/JoinSplit.hpp" +#include "zcash/Address.hpp" +#include "wallet.h" + +#include +#include + +#include + +// Default transaction fee if caller does not specify one. +#define SHIELD_COINBASE_DEFAULT_MINERS_FEE 10000 + +using namespace libzcash; + +struct ShieldCoinbaseUTXO { + uint256 txid; + int vout; + CAmount amount; +}; + +// Package of info which is passed to perform_joinsplit methods. +struct ShieldCoinbaseJSInfo +{ + std::vector vjsin; + std::vector vjsout; + CAmount vpub_old = 0; + CAmount vpub_new = 0; +}; + +class AsyncRPCOperation_shieldcoinbase : public AsyncRPCOperation { +public: + AsyncRPCOperation_shieldcoinbase(std::vector inputs, std::string toAddress, CAmount fee = SHIELD_COINBASE_DEFAULT_MINERS_FEE, UniValue contextInfo = NullUniValue); + virtual ~AsyncRPCOperation_shieldcoinbase(); + + // We don't want to be copied or moved around + AsyncRPCOperation_shieldcoinbase(AsyncRPCOperation_shieldcoinbase const&) = delete; // Copy construct + AsyncRPCOperation_shieldcoinbase(AsyncRPCOperation_shieldcoinbase&&) = delete; // Move construct + AsyncRPCOperation_shieldcoinbase& operator=(AsyncRPCOperation_shieldcoinbase const&) = delete; // Copy assign + AsyncRPCOperation_shieldcoinbase& operator=(AsyncRPCOperation_shieldcoinbase &&) = delete; // Move assign + + virtual void main(); + + virtual UniValue getStatus() const; + + bool testmode = false; // Set to true to disable sending txs and generating proofs + +private: + friend class TEST_FRIEND_AsyncRPCOperation_shieldcoinbase; // class for unit testing + + UniValue contextinfo_; // optional data to include in return value from getStatus() + + CAmount fee_; + PaymentAddress tozaddr_; + + uint256 joinSplitPubKey_; + unsigned char joinSplitPrivKey_[crypto_sign_SECRETKEYBYTES]; + + std::vector inputs_; + + CTransaction tx_; + + bool main_impl(); + + // JoinSplit without any input notes to spend + UniValue perform_joinsplit(ShieldCoinbaseJSInfo &); + + void sign_send_raw_transaction(UniValue obj); // throws exception if there was an error + + void lock_utxos(); + + void unlock_utxos(); +}; + + +// To test private methods, a friend class can act as a proxy +class TEST_FRIEND_AsyncRPCOperation_shieldcoinbase { +public: + std::shared_ptr delegate; + + TEST_FRIEND_AsyncRPCOperation_shieldcoinbase(std::shared_ptr ptr) : delegate(ptr) {} + + CTransaction getTx() { + return delegate->tx_; + } + + void setTx(CTransaction tx) { + delegate->tx_ = tx; + } + + // Delegated methods + + bool main_impl() { + return delegate->main_impl(); + } + + UniValue perform_joinsplit(ShieldCoinbaseJSInfo &info) { + return delegate->perform_joinsplit(info); + } + + void sign_send_raw_transaction(UniValue obj) { + delegate->sign_send_raw_transaction(obj); + } + + void set_state(OperationStatus state) { + delegate->state_.store(state); + } +}; + + +#endif /* ASYNCRPCOPERATION_SHIELDCOINBASE_H */ + diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index aa1ad99552e..903afabc2d5 100755 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -24,6 +24,7 @@ #include "asyncrpcoperation.h" #include "asyncrpcqueue.h" #include "wallet/asyncrpcoperation_sendmany.h" +#include "wallet/asyncrpcoperation_shieldcoinbase.h" #include "sodium.h" @@ -2335,6 +2336,7 @@ UniValue listunspent(const UniValue& params, bool fHelp) " \"txid\" : \"txid\", (string) the transaction id \n" " \"vout\" : n, (numeric) the vout value\n" " \"address\" : \"address\", (string) the zcash address\n" + " \"generated\" : true|false (boolean) true if txout is a coinbase transaction output\n" " \"account\" : \"account\", (string) DEPRECATED. The associated account, or \"\" for the default account\n" " \"scriptPubKey\" : \"key\", (string) the script key\n" " \"amount\" : x.xxx, (numeric) the transaction amount in btc\n" @@ -2396,6 +2398,7 @@ UniValue listunspent(const UniValue& params, bool fHelp) UniValue entry(UniValue::VOBJ); entry.push_back(Pair("txid", out.tx->GetHash().GetHex())); entry.push_back(Pair("vout", out.i)); + entry.push_back(Pair("generated", out.tx->IsCoinBase())); CTxDestination address; if (ExtractDestination(out.tx->vout[out.i].scriptPubKey, address)) { entry.push_back(Pair("address", CBitcoinAddress(address).ToString())); @@ -3492,6 +3495,182 @@ UniValue z_sendmany(const UniValue& params, bool fHelp) } +/** +When estimating the number of coinbase utxos we can shield in a single transaction: +1. Joinsplit description is 1802 bytes. +2. Transaction overhead ~ 100 bytes +3. Spending a typical P2PKH is >=148 bytes, as defined in CTXIN_SPEND_DUST_SIZE. +4. Spending a multi-sig P2SH address can vary greatly: + https://github.com/bitcoin/bitcoin/blob/c3ad56f4e0b587d8d763af03d743fdfc2d180c9b/src/main.cpp#L517 + In real-world coinbase utxos, we consider a 3-of-3 multisig, where the size is roughly: + (3*(33+1))+3 = 105 byte redeem script + 105 + 1 + 3*(73+1) = 328 bytes of scriptSig, rounded up to 400 based on testnet experiments. +*/ +#define CTXIN_SPEND_P2SH_SIZE 400 + +UniValue z_shieldcoinbase(const UniValue& params, bool fHelp) +{ + if (!EnsureWalletIsAvailable(fHelp)) + return NullUniValue; + + if (fHelp || params.size() < 2 || params.size() > 3) + throw runtime_error( + "z_shieldcoinbase \"fromaddress\" \"tozaddress\" ( fee )\n" + "\nShield transparent coinbase funds by sending to a shielded zaddr. This is an asynchronous operation and utxos" + "\nselected for shielding will be locked. If there is an error, they are unlocked. The RPC call `listlockunspent`" + "\ncan be used to return a list of locked utxos. The number of coinbase utxos selected for shielding is limited by" + "\nboth the -mempooltxinputlimit=xxx option and a consensus rule defining a maximum transaction size of " + + strprintf("%d bytes.", MAX_TX_SIZE) + + HelpRequiringPassphrase() + "\n" + "\nArguments:\n" + "1. \"fromaddress\" (string, required) The address is a taddr or \"*\" for all taddrs belonging to the wallet.\n" + "2. \"toaddress\" (string, required) The address is a zaddr.\n" + "3. fee (numeric, optional, default=" + + strprintf("%s", FormatMoney(SHIELD_COINBASE_DEFAULT_MINERS_FEE)) + ") The fee amount to attach to this transaction.\n" + "\nResult:\n" + "{\n" + " \"operationid\": xxx (string) An operationid to pass to z_getoperationstatus to get the result of the operation.\n" + " \"shieldedUTXOs\": xxx (numeric) Number of coinbase utxos being shielded.\n" + " \"shieldedValue\": xxx (numeric) Value of coinbase utxos being shielded.\n" + " \"remainingUTXOs\": xxx (numeric) Number of coinbase utxos still available for shielding.\n" + " \"remainingValue\": xxx (numeric) Value of coinbase utxos still available for shielding.\n" + "}\n" + ); + + LOCK2(cs_main, pwalletMain->cs_wallet); + + // Validate the from address + auto fromaddress = params[0].get_str(); + bool isFromWildcard = fromaddress == "*"; + CBitcoinAddress taddr; + if (!isFromWildcard) { + taddr = CBitcoinAddress(fromaddress); + if (!taddr.IsValid()) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid from address, should be a taddr or \"*\"."); + } + } + + // Validate the destination address + auto destaddress = params[1].get_str(); + try { + CZCPaymentAddress pa(destaddress); + libzcash::PaymentAddress zaddr = pa.Get(); + } catch (const std::runtime_error&) { + throw JSONRPCError(RPC_INVALID_PARAMETER, string("Invalid parameter, unknown address format: ") + destaddress ); + } + + // Convert fee from currency format to zatoshis + CAmount nFee = SHIELD_COINBASE_DEFAULT_MINERS_FEE; + if (params.size() > 2) { + if (params[2].get_real() == 0.0) { + nFee = 0; + } else { + nFee = AmountFromValue( params[2] ); + } + } + + // Prepare to get coinbase utxos + std::vector inputs; + CAmount shieldedValue = 0; + CAmount remainingValue = 0; + size_t estimatedTxSize = 2000; // 1802 joinsplit description + tx overhead + wiggle room + size_t utxoCounter = 0; + bool maxedOutFlag = false; + size_t mempoolLimit = (size_t)GetArg("-mempooltxinputlimit", 0); + + // Set of addresses to filter utxos by + set setAddress = {}; + if (!isFromWildcard) { + setAddress.insert(taddr); + } + + // Get available utxos + vector vecOutputs; + pwalletMain->AvailableCoins(vecOutputs, true, NULL, false, true); + + // Find unspent coinbase utxos and update estimated size + BOOST_FOREACH(const COutput& out, vecOutputs) { + if (!out.fSpendable) { + continue; + } + + CTxDestination address; + if (!ExtractDestination(out.tx->vout[out.i].scriptPubKey, address)) { + continue; + } + // If taddr is not wildcard "*", filter utxos + if (setAddress.size()>0 && !setAddress.count(address)) { + continue; + } + + if (!out.tx->IsCoinBase()) { + continue; + } + + utxoCounter++; + CAmount nValue = out.tx->vout[out.i].nValue; + + if (!maxedOutFlag) { + CBitcoinAddress ba(address); + size_t increase = (ba.IsScript()) ? CTXIN_SPEND_P2SH_SIZE : CTXIN_SPEND_DUST_SIZE; + if (estimatedTxSize + increase >= MAX_TX_SIZE || + (mempoolLimit > 0 && utxoCounter > mempoolLimit)) + { + maxedOutFlag = true; + } else { + estimatedTxSize += increase; + ShieldCoinbaseUTXO utxo = {out.tx->GetHash(), out.i, nValue}; + inputs.push_back(utxo); + shieldedValue += nValue; + } + } + + if (maxedOutFlag) { + remainingValue += nValue; + } + } + + size_t numUtxos = inputs.size(); + + if (numUtxos == 0) { + throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, "Could not find any coinbase funds to shield."); + } + + if (shieldedValue < nFee) { + throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, + strprintf("Insufficient coinbase funds, have %s, which is less than miners fee %s", + FormatMoney(shieldedValue), FormatMoney(nFee))); + } + + // Check that the user specified fee is sane (if too high, it can result in error -25 absurd fee) + CAmount netAmount = shieldedValue - nFee; + if (nFee > netAmount) { + throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Fee %s is greater than the net amount to be shielded %s", FormatMoney(nFee), FormatMoney(netAmount))); + } + + // Keep record of parameters in context object + UniValue contextInfo(UniValue::VOBJ); + contextInfo.push_back(Pair("fromaddress", params[0])); + contextInfo.push_back(Pair("toaddress", params[1])); + contextInfo.push_back(Pair("fee", ValueFromAmount(nFee))); + + // Create operation and add to global queue + std::shared_ptr q = getAsyncRPCQueue(); + std::shared_ptr operation( new AsyncRPCOperation_shieldcoinbase(inputs, destaddress, nFee, contextInfo) ); + q->addOperation(operation); + AsyncRPCOperationId operationId = operation->getId(); + + // Return continuation information + UniValue o(UniValue::VOBJ); + o.push_back(Pair("remainingUTXOs", utxoCounter - numUtxos)); + o.push_back(Pair("remainingValue", ValueFromAmount(remainingValue))); + o.push_back(Pair("shieldingUTXOs", numUtxos)); + o.push_back(Pair("shieldingValue", ValueFromAmount(shieldedValue))); + o.push_back(Pair("opid", operationId)); + return o; +} + + UniValue z_listoperationids(const UniValue& params, bool fHelp) { if (!EnsureWalletIsAvailable(fHelp))