Browse Source

Latest Zcash updates

metaverse
miketout 6 years ago
parent
commit
644c7445ec
  1. 4
      qa/pull-tester/rpc-tests.sh
  2. 19
      qa/rpc-tests/mempool_tx_input_limit.py
  3. 118
      qa/rpc-tests/overwinter_peer_management.py
  4. 192
      qa/rpc-tests/p2p_nu_peer_management.py
  5. 46
      qa/rpc-tests/signrawtransaction_offline.py
  6. 3
      qa/rpc-tests/test_framework/mininode.py
  7. 47
      qa/rpc-tests/test_framework/util.py
  8. 68
      qa/rpc-tests/wallet.py
  9. 82
      qa/rpc-tests/wallet_import_export.py
  10. 83
      qa/rpc-tests/wallet_nullifiers.py
  11. 52
      qa/rpc-tests/wallet_protectcoinbase.py
  12. 23
      qa/rpc-tests/wallet_sapling.py
  13. 29
      src/consensus/upgrades.cpp
  14. 12
      src/consensus/upgrades.h
  15. 15
      src/gtest/test_checktransaction.cpp
  16. 12
      src/gtest/test_transaction_builder.cpp
  17. 29
      src/gtest/test_upgrades.cpp
  18. 10
      src/keystore.cpp
  19. 5
      src/keystore.h
  20. 2
      src/komodo_bitcoind.h
  21. 39
      src/main.cpp
  22. 3
      src/main.h
  23. 32
      src/net.cpp
  24. 107
      src/rpc/rawtransaction.cpp
  25. 2
      src/test/rpc_tests.cpp
  26. 102
      src/test/rpc_wallet_tests.cpp
  27. 10
      src/transaction_builder.cpp
  28. 6
      src/transaction_builder.h
  29. 18
      src/wallet/asyncrpcoperation_sendmany.cpp
  30. 24
      src/wallet/gtest/test_wallet.cpp
  31. 10
      src/wallet/gtest/test_wallet_zkeys.cpp
  32. 132
      src/wallet/rpcdump.cpp
  33. 6
      src/wallet/rpcwallet.cpp
  34. 94
      src/wallet/wallet.cpp
  35. 38
      src/wallet/wallet.h
  36. 15
      src/wallet/walletdb.h
  37. 27
      src/zcash/zip32.cpp
  38. 3
      src/zcash/zip32.h

4
qa/pull-tester/rpc-tests.sh

@ -17,6 +17,7 @@ testScripts=(
'wallet_treestate.py'
'wallet_anchorfork.py'
'wallet_changeindicator.py'
'wallet_import_export.py'
'wallet_protectcoinbase.py'
'wallet_shieldcoinbase.py'
'wallet_mergetoaddress.py'
@ -44,6 +45,7 @@ testScripts=(
'merkle_blocks.py'
# 'fundrawtransaction.py'
'signrawtransactions.py'
'signrawtransaction_offline.py'
'walletbackup.py'
'key_import_export.py'
'nodehandling.py'
@ -61,7 +63,7 @@ testScripts=(
'getblocktemplate.py'
'bip65-cltv-p2p.py'
'bipdersig-p2p.py'
'overwinter_peer_management.py'
'p2p_nu_peer_management.py'
'rewind_index.py'
'p2p_txexpiry_dos.py'
'p2p_node_bloom.py'

19
qa/rpc-tests/mempool_tx_input_limit.py

@ -8,7 +8,6 @@ from test_framework.authproxy import JSONRPCException
from test_framework.util import assert_equal, initialize_chain_clean, \
start_node, connect_nodes, wait_and_assert_operationid_status
import time
from decimal import Decimal
# Test -mempooltxinputlimit
@ -49,27 +48,13 @@ class MempoolTxInputLimitTest(BitcoinTestFramework):
node0_zaddr = self.nodes[0].z_getnewaddress()
# Send three inputs from node 0 taddr to zaddr to get out of coinbase
node0_taddr = self.nodes[0].getnewaddress();
node0_taddr = self.nodes[0].getnewaddress()
recipients = []
recipients.append({"address":node0_zaddr, "amount":Decimal('30.0')-Decimal('0.0001')}) # utxo amount less fee
myopid = self.nodes[0].z_sendmany(node0_taddr, recipients)
opids = []
opids.append(myopid)
# Spend should fail due to -mempooltxinputlimit
timeout = 120
status = None
for x in xrange(1, timeout):
results = self.nodes[0].z_getoperationresult(opids)
if len(results)==0:
time.sleep(1)
else:
status = results[0]["status"]
msg = results[0]["error"]["message"]
assert_equal("failed", status)
assert_equal("Too many transparent inputs 3 > limit 2", msg)
break
wait_and_assert_operationid_status(self.nodes[0], myopid, "failed", "Too many transparent inputs 3 > limit 2", 120)
# Mempool should be empty.
assert_equal(set(self.nodes[0].getrawmempool()), set())

118
qa/rpc-tests/overwinter_peer_management.py

@ -1,118 +0,0 @@
#!/usr/bin/env python2
# Copyright (c) 2018 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.mininode import NodeConn, NodeConnCB, NetworkThread, \
msg_ping, SPROUT_PROTO_VERSION, OVERWINTER_PROTO_VERSION
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import initialize_chain_clean, start_nodes, \
p2p_port, assert_equal
import time
#
# In this test we connect Sprout and Overwinter mininodes to a Zcashd node
# which will activate Overwinter at block 10.
#
# We test:
# 1. the mininodes stay connected to Zcash with Sprout consensus rules
# 2. when Overwinter activates, the Sprout mininodes are dropped
# 3. new Overwinter nodes can connect to Zcash
# 4. new Sprout nodes cannot connect to Zcash
#
# This test *does not* verify that prior to Overwinter activation, the Zcashd
# node will prefer connections with Overwinter nodes, with an eviction process
# that prioritizes Sprout connections.
#
class TestManager(NodeConnCB):
def __init__(self):
NodeConnCB.__init__(self)
self.create_callback_map()
def on_close(self, conn):
pass
def on_reject(self, conn, message):
conn.rejectMessage = message
class OverwinterPeerManagementTest(BitcoinTestFramework):
def setup_chain(self):
print "Initializing test directory "+self.options.tmpdir
initialize_chain_clean(self.options.tmpdir, 1)
def setup_network(self):
self.nodes = start_nodes(1, self.options.tmpdir,
extra_args=[['-nuparams=5ba81b19:10', '-debug', '-whitelist=127.0.0.1']])
def run_test(self):
test = TestManager()
# Launch 10 Sprout and 10 Overwinter mininodes
nodes = []
for x in xrange(10):
nodes.append(NodeConn('127.0.0.1', p2p_port(0), self.nodes[0],
test, "regtest", SPROUT_PROTO_VERSION))
nodes.append(NodeConn('127.0.0.1', p2p_port(0), self.nodes[0],
test, "regtest", OVERWINTER_PROTO_VERSION))
# Start up network handling in another thread
NetworkThread().start()
# Sprout consensus rules apply at block height 9
self.nodes[0].generate(9)
assert_equal(9, self.nodes[0].getblockcount())
# Verify mininodes are still connected to zcashd node
peerinfo = self.nodes[0].getpeerinfo()
versions = [x["version"] for x in peerinfo]
assert_equal(10, versions.count(SPROUT_PROTO_VERSION))
assert_equal(10, versions.count(OVERWINTER_PROTO_VERSION))
# Overwinter consensus rules activate at block height 10
self.nodes[0].generate(1)
assert_equal(10, self.nodes[0].getblockcount())
# Mininodes send ping message to zcashd node.
pingCounter = 1
for node in nodes:
node.send_message(msg_ping(pingCounter))
pingCounter = pingCounter + 1
time.sleep(3)
# Verify Sprout mininodes have been dropped and Overwinter mininodes are still connected.
peerinfo = self.nodes[0].getpeerinfo()
versions = [x["version"] for x in peerinfo]
assert_equal(0, versions.count(SPROUT_PROTO_VERSION))
assert_equal(10, versions.count(OVERWINTER_PROTO_VERSION))
# Extend the Overwinter chain with another block.
self.nodes[0].generate(1)
# Connect a new Overwinter mininode to the zcashd node, which is accepted.
nodes.append(NodeConn('127.0.0.1', p2p_port(0), self.nodes[0], test, "regtest", OVERWINTER_PROTO_VERSION))
time.sleep(3)
assert_equal(11, len(self.nodes[0].getpeerinfo()))
# Try to connect a new Sprout mininode to the zcashd node, which is rejected.
sprout = NodeConn('127.0.0.1', p2p_port(0), self.nodes[0], test, "regtest", SPROUT_PROTO_VERSION)
nodes.append(sprout)
time.sleep(3)
assert("Version must be 170003 or greater" in str(sprout.rejectMessage))
# Verify that only Overwinter mininodes are connected.
peerinfo = self.nodes[0].getpeerinfo()
versions = [x["version"] for x in peerinfo]
assert_equal(0, versions.count(SPROUT_PROTO_VERSION))
assert_equal(11, versions.count(OVERWINTER_PROTO_VERSION))
for node in nodes:
node.disconnect_node()
if __name__ == '__main__':
OverwinterPeerManagementTest().main()

192
qa/rpc-tests/p2p_nu_peer_management.py

@ -0,0 +1,192 @@
#!/usr/bin/env python2
# Copyright (c) 2018 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.mininode import (
NodeConn,
NodeConnCB,
NetworkThread,
msg_ping,
SPROUT_PROTO_VERSION,
OVERWINTER_PROTO_VERSION,
SAPLING_PROTO_VERSION,
)
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import initialize_chain_clean, start_nodes, \
p2p_port, assert_equal
import time
#
# In this test we connect Sprout, Overwinter, and Sapling mininodes to a Zcashd
# node which will activate Overwinter at block 10 and Sapling at block 15.
#
# We test:
# 1. the mininodes stay connected to Zcash with Sprout consensus rules
# 2. when Overwinter activates, the Sprout mininodes are dropped
# 3. new Overwinter and Sapling nodes can connect to Zcash
# 4. new Sprout nodes cannot connect to Zcash
# 5. when Sapling activates, the Overwinter mininodes are dropped
# 6. new Sapling nodes can connect to Zcash
# 7. new Sprout and Overwinter nodes cannot connect to Zcash
#
# This test *does not* verify that prior to each activation, the Zcashd
# node will prefer connections with NU-aware nodes, with an eviction process
# that prioritizes non-NU-aware connections.
#
class TestManager(NodeConnCB):
def __init__(self):
NodeConnCB.__init__(self)
self.create_callback_map()
def on_close(self, conn):
pass
def on_reject(self, conn, message):
conn.rejectMessage = message
class NUPeerManagementTest(BitcoinTestFramework):
def setup_chain(self):
print "Initializing test directory "+self.options.tmpdir
initialize_chain_clean(self.options.tmpdir, 1)
def setup_network(self):
self.nodes = start_nodes(1, self.options.tmpdir, extra_args=[[
'-nuparams=5ba81b19:10', # Overwinter
'-nuparams=76b809bb:15', # Sapling
'-debug',
'-whitelist=127.0.0.1',
]])
def run_test(self):
test = TestManager()
# Launch Sprout, Overwinter, and Sapling mininodes
nodes = []
for x in xrange(10):
nodes.append(NodeConn('127.0.0.1', p2p_port(0), self.nodes[0],
test, "regtest", SPROUT_PROTO_VERSION))
nodes.append(NodeConn('127.0.0.1', p2p_port(0), self.nodes[0],
test, "regtest", OVERWINTER_PROTO_VERSION))
nodes.append(NodeConn('127.0.0.1', p2p_port(0), self.nodes[0],
test, "regtest", SAPLING_PROTO_VERSION))
# Start up network handling in another thread
NetworkThread().start()
# Sprout consensus rules apply at block height 9
self.nodes[0].generate(9)
assert_equal(9, self.nodes[0].getblockcount())
# Verify mininodes are still connected to zcashd node
peerinfo = self.nodes[0].getpeerinfo()
versions = [x["version"] for x in peerinfo]
assert_equal(10, versions.count(SPROUT_PROTO_VERSION))
assert_equal(10, versions.count(OVERWINTER_PROTO_VERSION))
assert_equal(10, versions.count(SAPLING_PROTO_VERSION))
# Overwinter consensus rules activate at block height 10
self.nodes[0].generate(1)
assert_equal(10, self.nodes[0].getblockcount())
print('Overwinter active')
# Mininodes send ping message to zcashd node.
pingCounter = 1
for node in nodes:
node.send_message(msg_ping(pingCounter))
pingCounter = pingCounter + 1
time.sleep(3)
# Verify Sprout mininodes have been dropped, while Overwinter and
# Sapling mininodes are still connected.
peerinfo = self.nodes[0].getpeerinfo()
versions = [x["version"] for x in peerinfo]
assert_equal(0, versions.count(SPROUT_PROTO_VERSION))
assert_equal(10, versions.count(OVERWINTER_PROTO_VERSION))
assert_equal(10, versions.count(SAPLING_PROTO_VERSION))
# Extend the Overwinter chain with another block.
self.nodes[0].generate(1)
# Connect a new Overwinter mininode to the zcashd node, which is accepted.
nodes.append(NodeConn('127.0.0.1', p2p_port(0), self.nodes[0], test, "regtest", OVERWINTER_PROTO_VERSION))
time.sleep(3)
assert_equal(21, len(self.nodes[0].getpeerinfo()))
# Connect a new Sapling mininode to the zcashd node, which is accepted.
nodes.append(NodeConn('127.0.0.1', p2p_port(0), self.nodes[0], test, "regtest", SAPLING_PROTO_VERSION))
time.sleep(3)
assert_equal(22, len(self.nodes[0].getpeerinfo()))
# Try to connect a new Sprout mininode to the zcashd node, which is rejected.
sprout = NodeConn('127.0.0.1', p2p_port(0), self.nodes[0], test, "regtest", SPROUT_PROTO_VERSION)
nodes.append(sprout)
time.sleep(3)
assert("Version must be 170003 or greater" in str(sprout.rejectMessage))
# Verify that only Overwinter and Sapling mininodes are connected.
peerinfo = self.nodes[0].getpeerinfo()
versions = [x["version"] for x in peerinfo]
assert_equal(0, versions.count(SPROUT_PROTO_VERSION))
assert_equal(11, versions.count(OVERWINTER_PROTO_VERSION))
assert_equal(11, versions.count(SAPLING_PROTO_VERSION))
# Sapling consensus rules activate at block height 15
self.nodes[0].generate(4)
assert_equal(15, self.nodes[0].getblockcount())
print('Sapling active')
# Mininodes send ping message to zcashd node.
pingCounter = 1
for node in nodes:
node.send_message(msg_ping(pingCounter))
pingCounter = pingCounter + 1
time.sleep(3)
# Verify Sprout and Overwinter mininodes have been dropped, while
# Sapling mininodes are still connected.
peerinfo = self.nodes[0].getpeerinfo()
versions = [x["version"] for x in peerinfo]
assert_equal(0, versions.count(SPROUT_PROTO_VERSION))
assert_equal(0, versions.count(OVERWINTER_PROTO_VERSION))
assert_equal(11, versions.count(SAPLING_PROTO_VERSION))
# Extend the Sapling chain with another block.
self.nodes[0].generate(1)
# Connect a new Sapling mininode to the zcashd node, which is accepted.
nodes.append(NodeConn('127.0.0.1', p2p_port(0), self.nodes[0], test, "regtest", SAPLING_PROTO_VERSION))
time.sleep(3)
assert_equal(12, len(self.nodes[0].getpeerinfo()))
# Try to connect a new Sprout mininode to the zcashd node, which is rejected.
sprout = NodeConn('127.0.0.1', p2p_port(0), self.nodes[0], test, "regtest", SPROUT_PROTO_VERSION)
nodes.append(sprout)
time.sleep(3)
assert("Version must be 170006 or greater" in str(sprout.rejectMessage))
# Try to connect a new Overwinter mininode to the zcashd node, which is rejected.
sprout = NodeConn('127.0.0.1', p2p_port(0), self.nodes[0], test, "regtest", OVERWINTER_PROTO_VERSION)
nodes.append(sprout)
time.sleep(3)
assert("Version must be 170006 or greater" in str(sprout.rejectMessage))
# Verify that only Sapling mininodes are connected.
peerinfo = self.nodes[0].getpeerinfo()
versions = [x["version"] for x in peerinfo]
assert_equal(0, versions.count(SPROUT_PROTO_VERSION))
assert_equal(0, versions.count(OVERWINTER_PROTO_VERSION))
assert_equal(12, versions.count(SAPLING_PROTO_VERSION))
for node in nodes:
node.disconnect_node()
if __name__ == '__main__':
NUPeerManagementTest().main()

46
qa/rpc-tests/signrawtransaction_offline.py

@ -0,0 +1,46 @@
#!/usr/bin/env python2
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import assert_equal, assert_true, initialize_chain_clean, start_node
class SignOfflineTest (BitcoinTestFramework):
# Setup Methods
def setup_chain(self):
print "Initializing test directory " + self.options.tmpdir
initialize_chain_clean(self.options.tmpdir, 2)
def setup_network(self):
self.nodes = [ start_node(0, self.options.tmpdir, ["-nuparams=5ba81b19:10"]) ]
self.is_network_split = False
self.sync_all()
# Tests
def run_test(self):
print "Mining blocks..."
self.nodes[0].generate(101)
offline_node = start_node(1, self.options.tmpdir, ["-maxconnections=0", "-nuparams=5ba81b19:10"])
self.nodes.append(offline_node)
assert_equal(0, len(offline_node.getpeerinfo())) # make sure node 1 has no peers
privkeys = [self.nodes[0].dumpprivkey(self.nodes[0].getnewaddress())]
taddr = self.nodes[0].getnewaddress()
tx = self.nodes[0].listunspent()[0]
txid = tx['txid']
scriptpubkey = tx['scriptPubKey']
create_inputs = [{'txid': txid, 'vout': 0}]
sign_inputs = [{'txid': txid, 'vout': 0, 'scriptPubKey': scriptpubkey, 'amount': 10}]
create_hex = self.nodes[0].createrawtransaction(create_inputs, {taddr: 9.9999})
signed_tx = offline_node.signrawtransaction(create_hex, sign_inputs, privkeys)
# If we return the transaction hash, then we have have not thrown an error (success)
online_tx_hash = self.nodes[0].sendrawtransaction(signed_tx['hex'])
assert_true(len(online_tx_hash) > 0)
if __name__ == '__main__':
SignOfflineTest().main()

3
qa/rpc-tests/test_framework/mininode.py

@ -42,6 +42,7 @@ from .equihash import (
OVERWINTER_PROTO_VERSION = 170003
BIP0031_VERSION = 60000
SPROUT_PROTO_VERSION = 170002 # past bip-31 for ping/pong
SAPLING_PROTO_VERSION = 170006
MY_SUBVERSION = "/python-mininode-tester:0.0.1/"
OVERWINTER_VERSION_GROUP_ID = 0x03C48270
@ -1415,7 +1416,7 @@ class NodeConn(asyncore.dispatcher):
vt.addrFrom.port = 0
self.send_message(vt, True)
print 'MiniNode: Connecting to Bitcoin Node IP # ' + dstaddr + ':' \
+ str(dstport)
+ str(dstport) + ' using version ' + str(protocol_version)
try:
self.connect((dstaddr, dstport))

47
qa/rpc-tests/test_framework/util.py

@ -417,31 +417,36 @@ def assert_raises(exc, fun, *args, **kwds):
raise AssertionError("No exception raised")
# Returns txid if operation was a success or None
def wait_and_assert_operationid_status(node, myopid, in_status='success', in_errormsg=None):
def wait_and_assert_operationid_status(node, myopid, in_status='success', in_errormsg=None, timeout=300):
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 = node.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']
result = None
for _ in xrange(1, timeout):
results = node.z_getoperationresult([myopid])
if len(results) > 0:
result = results[0]
break
time.sleep(1)
assert_true(result is not None, "timeout occured")
status = result['status']
txid = None
errormsg = None
if status == "failed":
errormsg = result['error']['message']
elif status == "success":
txid = result['result']['txid']
if os.getenv("PYTHON_DEBUG", ""):
print('...returned status: {}'.format(status))
if errormsg is not None:
print('...returned error: {}'.format(errormsg))
assert_equal(in_status, status)
assert_equal(in_status, status, "Operation returned mismatched status. Error Message: {}".format(errormsg))
if errormsg is not None:
assert(in_errormsg is not None)
assert_equal(in_errormsg in errormsg, True)
return txid
assert_true(in_errormsg is not None, "No error retured. Expected: {}".format(errormsg))
assert_true(in_errormsg in errormsg, "Error returned: {}. Error expected: {}".format(errormsg, in_errormsg))
return result # if there was an error return the result
else:
return txid # otherwise return the txid

68
qa/rpc-tests/wallet.py

@ -8,9 +8,9 @@ from test_framework.test_framework import BitcoinTestFramework
from test_framework.authproxy import JSONRPCException
from test_framework.util import assert_equal, assert_greater_than, \
initialize_chain_clean, start_nodes, start_node, connect_nodes_bi, \
stop_nodes, sync_blocks, sync_mempools, wait_bitcoinds
stop_nodes, sync_blocks, sync_mempools, wait_and_assert_operationid_status, \
wait_bitcoinds
import time
from decimal import Decimal
class WalletTest (BitcoinTestFramework):
@ -219,7 +219,7 @@ class WalletTest (BitcoinTestFramework):
for uTx in unspentTxs:
if uTx['txid'] == zeroValueTxid:
found = True
assert_equal(uTx['amount'], Decimal('0.00000000'));
assert_equal(uTx['amount'], Decimal('0.00000000'))
assert(found)
#do some -walletbroadcast tests
@ -231,13 +231,13 @@ class WalletTest (BitcoinTestFramework):
connect_nodes_bi(self.nodes,0,2)
self.sync_all()
txIdNotBroadcasted = self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(), 2);
txIdNotBroadcasted = self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(), 2)
txObjNotBroadcasted = self.nodes[0].gettransaction(txIdNotBroadcasted)
self.sync_all()
self.nodes[1].generate(1) #mine a block, tx should not be in there
self.sync_all()
assert_equal(self.nodes[2].getbalance(), Decimal('9.99800000')); #should not be changed because tx was not broadcasted
assert_equal(self.nodes[2].getbalance("*"), Decimal('9.99800000')); #should not be changed because tx was not broadcasted
assert_equal(self.nodes[2].getbalance(), Decimal('9.99800000')) #should not be changed because tx was not broadcasted
assert_equal(self.nodes[2].getbalance("*"), Decimal('9.99800000')) #should not be changed because tx was not broadcasted
#now broadcast from another node, mine a block, sync, and check the balance
self.nodes[1].sendrawtransaction(txObjNotBroadcasted['hex'])
@ -245,11 +245,11 @@ class WalletTest (BitcoinTestFramework):
self.nodes[1].generate(1)
self.sync_all()
txObjNotBroadcasted = self.nodes[0].gettransaction(txIdNotBroadcasted)
assert_equal(self.nodes[2].getbalance(), Decimal('11.99800000')); #should not be
assert_equal(self.nodes[2].getbalance("*"), Decimal('11.99800000')); #should not be
assert_equal(self.nodes[2].getbalance(), Decimal('11.99800000')) #should not be
assert_equal(self.nodes[2].getbalance("*"), Decimal('11.99800000')) #should not be
#create another tx
txIdNotBroadcasted = self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(), 2);
txIdNotBroadcasted = self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(), 2)
#restart the nodes with -walletbroadcast=1
stop_nodes(self.nodes)
@ -264,18 +264,18 @@ class WalletTest (BitcoinTestFramework):
sync_blocks(self.nodes)
#tx should be added to balance because after restarting the nodes tx should be broadcastet
assert_equal(self.nodes[2].getbalance(), Decimal('13.99800000')); #should not be
assert_equal(self.nodes[2].getbalance("*"), Decimal('13.99800000')); #should not be
assert_equal(self.nodes[2].getbalance(), Decimal('13.99800000')) #should not be
assert_equal(self.nodes[2].getbalance("*"), Decimal('13.99800000')) #should not be
# send from node 0 to node 2 taddr
mytaddr = self.nodes[2].getnewaddress();
mytxid = self.nodes[0].sendtoaddress(mytaddr, 10.0);
mytaddr = self.nodes[2].getnewaddress()
mytxid = self.nodes[0].sendtoaddress(mytaddr, 10.0)
self.sync_all()
self.nodes[0].generate(1)
self.sync_all()
mybalance = self.nodes[2].z_getbalance(mytaddr)
assert_equal(mybalance, Decimal('10.0'));
assert_equal(mybalance, Decimal('10.0'))
mytxdetails = self.nodes[2].gettransaction(mytxid)
myvjoinsplits = mytxdetails["vjoinsplit"]
@ -348,23 +348,9 @@ class WalletTest (BitcoinTestFramework):
# send node 2 taddr to zaddr
recipients = []
recipients.append({"address":myzaddr, "amount":7})
myopid = self.nodes[2].z_sendmany(mytaddr, recipients)
opids = []
opids.append(myopid)
timeout = 300
status = None
for x in xrange(1, timeout):
results = self.nodes[2].z_getoperationresult(opids)
if len(results)==0:
time.sleep(1)
else:
status = results[0]["status"]
mytxid = results[0]["result"]["txid"]
break
assert_equal("success", status)
mytxid = wait_and_assert_operationid_status(self.nodes[2], self.nodes[2].z_sendmany(mytaddr, recipients))
self.sync_all()
self.nodes[2].generate(1)
self.sync_all()
@ -378,7 +364,7 @@ class WalletTest (BitcoinTestFramework):
assert_equal(self.nodes[2].getbalance("*"), node2utxobalance)
# check zaddr balance
assert_equal(self.nodes[2].z_getbalance(myzaddr), zsendmanynotevalue);
assert_equal(self.nodes[2].z_getbalance(myzaddr), zsendmanynotevalue)
# check via z_gettotalbalance
resp = self.nodes[2].z_gettotalbalance()
@ -399,7 +385,6 @@ class WalletTest (BitcoinTestFramework):
assert("randomSeed" in myjoinsplit.keys())
assert("ciphertexts" in myjoinsplit.keys())
# send from private note to node 0 and node 2
node0balance = self.nodes[0].getbalance() # 25.99794745
node2balance = self.nodes[2].getbalance() # 16.99790000
@ -407,20 +392,9 @@ class WalletTest (BitcoinTestFramework):
recipients = []
recipients.append({"address":self.nodes[0].getnewaddress(), "amount":1})
recipients.append({"address":self.nodes[2].getnewaddress(), "amount":1.0})
myopid = self.nodes[2].z_sendmany(myzaddr, recipients)
status = None
opids = []
opids.append(myopid)
for x in xrange(1, timeout):
results = self.nodes[2].z_getoperationresult(opids)
if len(results)==0:
time.sleep(1)
else:
status = results[0]["status"]
break
wait_and_assert_operationid_status(self.nodes[2], self.nodes[2].z_sendmany(myzaddr, recipients))
assert_equal("success", status)
self.sync_all()
self.nodes[2].generate(1)
self.sync_all()
@ -453,7 +427,7 @@ class WalletTest (BitcoinTestFramework):
except JSONRPCException,e:
errorString = e.error['message']
assert_equal("Invalid amount" in errorString, True);
assert_equal("Invalid amount" in errorString, True)
errorString = ""
try:
@ -461,7 +435,7 @@ class WalletTest (BitcoinTestFramework):
except JSONRPCException,e:
errorString = e.error['message']
assert_equal("not an integer" in errorString, True);
assert_equal("not an integer" in errorString, True)
myzaddr = self.nodes[0].z_getnewaddress()
recipients = [ {"address": myzaddr, "amount": Decimal('0.0') } ]

82
qa/rpc-tests/wallet_import_export.py

@ -0,0 +1,82 @@
#!/usr/bin/env python2
# Copyright (c) 2018 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.util import assert_equal, assert_true, start_nodes
class WalletImportExportTest (BitcoinTestFramework):
def setup_network(self, split=False):
num_nodes = 3
extra_args = [["-exportdir={}/export{}".format(self.options.tmpdir, i)] for i in range(num_nodes)]
self.nodes = start_nodes(num_nodes, self.options.tmpdir, extra_args)
def run_test(self):
sapling_address2 = self.nodes[2].z_getnewaddress('sapling')
privkey2 = self.nodes[2].z_exportkey(sapling_address2)
self.nodes[0].z_importkey(privkey2)
sprout_address0 = self.nodes[0].z_getnewaddress('sprout')
sapling_address0 = self.nodes[0].z_getnewaddress('sapling')
# node 0 should have the keys
dump_path0 = self.nodes[0].z_exportwallet('walletdump')
(t_keys0, sprout_keys0, sapling_keys0) = parse_wallet_file(dump_path0)
sapling_line_lengths = [len(sapling_key0.split(' #')[0].split()) for sapling_key0 in sapling_keys0.splitlines()]
assert_equal(2, len(sapling_line_lengths), "Should have 2 sapling keys")
assert_true(2 in sapling_line_lengths, "Should have a key with 2 parameters")
assert_true(4 in sapling_line_lengths, "Should have a key with 4 parameters")
assert_true(sprout_address0 in sprout_keys0)
assert_true(sapling_address0 in sapling_keys0)
assert_true(sapling_address2 in sapling_keys0)
# node 1 should not have the keys
dump_path1 = self.nodes[1].z_exportwallet('walletdumpbefore')
(t_keys1, sprout_keys1, sapling_keys1) = parse_wallet_file(dump_path1)
assert_true(sprout_address0 not in sprout_keys1)
assert_true(sapling_address0 not in sapling_keys1)
# import wallet to node 1
self.nodes[1].z_importwallet(dump_path0)
# node 1 should now have the keys
dump_path1 = self.nodes[1].z_exportwallet('walletdumpafter')
(t_keys1, sprout_keys1, sapling_keys1) = parse_wallet_file(dump_path1)
assert_true(sprout_address0 in sprout_keys1)
assert_true(sapling_address0 in sapling_keys1)
assert_true(sapling_address2 in sapling_keys1)
# make sure we have perserved the metadata
for sapling_key0 in sapling_keys0.splitlines():
assert_true(sapling_key0 in sapling_keys1)
# Helper functions
def parse_wallet_file(dump_path):
file_lines = open(dump_path, "r").readlines()
# We expect information about the HDSeed and fingerpring in the header
assert_true("HDSeed" in file_lines[4], "Expected HDSeed")
assert_true("fingerprint" in file_lines[4], "Expected fingerprint")
(t_keys, i) = parse_wallet_file_lines(file_lines, 0)
(sprout_keys, i) = parse_wallet_file_lines(file_lines, i)
(sapling_keys, i) = parse_wallet_file_lines(file_lines, i)
return (t_keys, sprout_keys, sapling_keys)
def parse_wallet_file_lines(file_lines, i):
keys = []
# skip blank lines and comments
while i < len(file_lines) and (file_lines[i] == '\n' or file_lines[i].startswith("#")):
i += 1
# add keys until we hit another blank line or comment
while i < len(file_lines) and not (file_lines[i] == '\n' or file_lines[i].startswith("#")):
keys.append(file_lines[i])
i += 1
return ("".join(keys), i)
if __name__ == '__main__':
WalletImportExportTest().main()

83
qa/rpc-tests/wallet_nullifiers.py

@ -5,10 +5,9 @@
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import assert_equal, assert_true, start_node, \
start_nodes, connect_nodes_bi, bitcoind_processes
from test_framework.util import assert_equal, assert_true, bitcoind_processes, \
connect_nodes_bi, start_node, start_nodes, wait_and_assert_operationid_status
import time
from decimal import Decimal
class WalletNullifiersTest (BitcoinTestFramework):
@ -22,25 +21,11 @@ class WalletNullifiersTest (BitcoinTestFramework):
myzaddr0 = self.nodes[0].z_getnewaddress()
# send node 0 taddr to zaddr to get out of coinbase
mytaddr = self.nodes[0].getnewaddress();
mytaddr = self.nodes[0].getnewaddress()
recipients = []
recipients.append({"address":myzaddr0, "amount":Decimal('10.0')-Decimal('0.0001')}) # utxo amount less fee
myopid = self.nodes[0].z_sendmany(mytaddr, recipients)
opids = []
opids.append(myopid)
timeout = 120
status = None
for x in xrange(1, timeout):
results = self.nodes[0].z_getoperationresult(opids)
if len(results)==0:
time.sleep(1)
else:
status = results[0]["status"]
assert_equal("success", status)
mytxid = results[0]["result"]["txid"]
break
wait_and_assert_operationid_status(self.nodes[0], self.nodes[0].z_sendmany(mytaddr, recipients), timeout=120)
self.sync_all()
self.nodes[0].generate(1)
@ -66,22 +51,8 @@ class WalletNullifiersTest (BitcoinTestFramework):
# send node 0 zaddr to note 2 zaddr
recipients = []
recipients.append({"address":myzaddr, "amount":7.0})
myopid = self.nodes[0].z_sendmany(myzaddr0, recipients)
opids = []
opids.append(myopid)
timeout = 120
status = None
for x in xrange(1, timeout):
results = self.nodes[0].z_getoperationresult(opids)
if len(results)==0:
time.sleep(1)
else:
status = results[0]["status"]
assert_equal("success", status)
mytxid = results[0]["result"]["txid"]
break
wait_and_assert_operationid_status(self.nodes[0], self.nodes[0].z_sendmany(myzaddr0, recipients), timeout=120)
self.sync_all()
self.nodes[0].generate(1)
@ -98,22 +69,8 @@ class WalletNullifiersTest (BitcoinTestFramework):
# send node 2 zaddr to note 3 zaddr
recipients = []
recipients.append({"address":myzaddr3, "amount":2.0})
myopid = self.nodes[2].z_sendmany(myzaddr, recipients)
opids = []
opids.append(myopid)
timeout = 120
status = None
for x in xrange(1, timeout):
results = self.nodes[2].z_getoperationresult(opids)
if len(results)==0:
time.sleep(1)
else:
status = results[0]["status"]
assert_equal("success", status)
mytxid = results[0]["result"]["txid"]
break
wait_and_assert_operationid_status(self.nodes[2], self.nodes[2].z_sendmany(myzaddr, recipients), timeout=120)
self.sync_all()
self.nodes[2].generate(1)
@ -136,26 +93,11 @@ class WalletNullifiersTest (BitcoinTestFramework):
# This requires that node 1 be unlocked, which triggers caching of
# uncached nullifiers.
self.nodes[1].walletpassphrase("test", 600)
mytaddr1 = self.nodes[1].getnewaddress();
mytaddr1 = self.nodes[1].getnewaddress()
recipients = []
recipients.append({"address":mytaddr1, "amount":1.0})
myopid = self.nodes[1].z_sendmany(myzaddr, recipients)
opids = []
opids.append(myopid)
timeout = 120
status = None
for x in xrange(1, timeout):
results = self.nodes[1].z_getoperationresult(opids)
if len(results)==0:
time.sleep(1)
else:
status = results[0]["status"]
assert_equal("success", status)
mytxid = results[0]["result"]["txid"]
[mytxid] # hush pyflakes
break
wait_and_assert_operationid_status(self.nodes[1], self.nodes[1].z_sendmany(myzaddr, recipients), timeout=120)
self.sync_all()
self.nodes[1].generate(1)
@ -206,7 +148,6 @@ class WalletNullifiersTest (BitcoinTestFramework):
if key != 'change':
assert_equal(received2[key], received3[key])
# Node 3's balances should be unchanged without explicitly requesting
# to include watch-only balances
assert_equal({k: Decimal(v) for k, v in self.nodes[3].z_gettotalbalance().items()}, {

52
qa/rpc-tests/wallet_protectcoinbase.py

@ -11,7 +11,6 @@ from test_framework.util import assert_equal, initialize_chain_clean, \
start_nodes, connect_nodes_bi, wait_and_assert_operationid_status
import sys
import time
import timeit
from decimal import Decimal
@ -82,50 +81,25 @@ class WalletProtectCoinbaseTest (BitcoinTestFramework):
self.nodes[3].importaddress(mytaddr)
recipients= [{"address":myzaddr, "amount": Decimal('1')}]
myopid = self.nodes[3].z_sendmany(mytaddr, recipients)
errorString=""
status = None
opids = [myopid]
timeout = 10
for x in xrange(1, timeout):
results = self.nodes[3].z_getoperationresult(opids)
if len(results)==0:
time.sleep(1)
else:
status = results[0]["status"]
errorString = results[0]["error"]["message"]
break
assert_equal("failed", status)
assert_equal("no UTXOs found for taddr from address" in errorString, True)
wait_and_assert_operationid_status(self.nodes[3], myopid, "failed", "no UTXOs found for taddr from address", 10)
# This send will fail because our wallet does not allow any change when protecting a coinbase utxo,
# as it's currently not possible to specify a change address in z_sendmany.
recipients = []
recipients.append({"address":myzaddr, "amount":Decimal('1.23456789')})
errorString = ""
myopid = self.nodes[0].z_sendmany(mytaddr, recipients)
opids = []
opids.append(myopid)
timeout = 10
status = None
for x in xrange(1, timeout):
results = self.nodes[0].z_getoperationresult(opids)
if len(results)==0:
time.sleep(1)
else:
status = results[0]["status"]
errorString = results[0]["error"]["message"]
# Test that the returned status object contains a params field with the operation's input parameters
assert_equal(results[0]["method"], "z_sendmany")
params =results[0]["params"]
assert_equal(params["fee"], Decimal('0.0001')) # default
assert_equal(params["minconf"], Decimal('1')) # default
assert_equal(params["fromaddress"], mytaddr)
assert_equal(params["amounts"][0]["address"], myzaddr)
assert_equal(params["amounts"][0]["amount"], Decimal('1.23456789'))
break
assert_equal("failed", status)
assert_equal("wallet does not allow any change" in errorString, True)
error_result = wait_and_assert_operationid_status(self.nodes[0], myopid, "failed", "wallet does not allow any change", 10)
# Test that the returned status object contains a params field with the operation's input parameters
assert_equal(error_result["method"], "z_sendmany")
params = error_result["params"]
assert_equal(params["fee"], Decimal('0.0001')) # default
assert_equal(params["minconf"], Decimal('1')) # default
assert_equal(params["fromaddress"], mytaddr)
assert_equal(params["amounts"][0]["address"], myzaddr)
assert_equal(params["amounts"][0]["amount"], Decimal('1.23456789'))
# Add viewing key for myzaddr to Node 3
myviewingkey = self.nodes[0].z_exportviewingkey(myzaddr)

23
qa/rpc-tests/wallet_sapling.py

@ -89,7 +89,7 @@ class WalletSaplingTest(BitcoinTestFramework):
recipients.append({"address": saplingAddr0, "amount": Decimal('5')})
recipients.append({"address": taddr1, "amount": Decimal('5')})
myopid = self.nodes[1].z_sendmany(saplingAddr1, recipients, 1, 0)
wait_and_assert_operationid_status(self.nodes[1], myopid)
mytxid = wait_and_assert_operationid_status(self.nodes[1], myopid)
self.sync_all()
self.nodes[2].generate(1)
@ -100,5 +100,26 @@ class WalletSaplingTest(BitcoinTestFramework):
assert_equal(self.nodes[1].z_getbalance(saplingAddr1), Decimal('5'))
assert_equal(self.nodes[1].z_getbalance(taddr1), Decimal('5'))
# Verify existence of Sapling related JSON fields
resp = self.nodes[0].getrawtransaction(mytxid, 1)
assert_equal(resp['valueBalance'], Decimal('5'))
assert(len(resp['vShieldedSpend']) == 1)
assert(len(resp['vShieldedOutput']) == 2)
assert('bindingSig' in resp)
shieldedSpend = resp['vShieldedSpend'][0]
assert('cv' in shieldedSpend)
assert('anchor' in shieldedSpend)
assert('nullifier' in shieldedSpend)
assert('rk' in shieldedSpend)
assert('proof' in shieldedSpend)
assert('spendAuthSig' in shieldedSpend)
shieldedOutput = resp['vShieldedOutput'][0]
assert('cv' in shieldedOutput)
assert('cmu' in shieldedOutput)
assert('ephemeralKey' in shieldedOutput)
assert('encCiphertext' in shieldedOutput)
assert('outCiphertext' in shieldedOutput)
assert('proof' in shieldedOutput)
if __name__ == '__main__':
WalletSaplingTest().main()

29
src/consensus/upgrades.cpp

@ -82,6 +82,15 @@ uint32_t CurrentEpochBranchId(int nHeight, const Consensus::Params& params) {
return NetworkUpgradeInfo[CurrentEpoch(nHeight, params)].nBranchId;
}
bool IsConsensusBranchId(int branchId) {
for (int idx = Consensus::BASE_SPROUT; idx < Consensus::MAX_NETWORK_UPGRADES; idx++) {
if (branchId == NetworkUpgradeInfo[idx].nBranchId) {
return true;
}
}
return false;
}
bool IsActivationHeight(
int nHeight,
const Consensus::Params& params,
@ -114,20 +123,28 @@ bool IsActivationHeightForAnyUpgrade(
return false;
}
boost::optional<int> NextActivationHeight(
int nHeight,
const Consensus::Params& params)
{
boost::optional<int> NextEpoch(int nHeight, const Consensus::Params& params) {
if (nHeight < 0) {
return boost::none;
}
// Don't count Sprout as an activation height
// Sprout is never pending
for (auto idx = Consensus::BASE_SPROUT + 1; idx < Consensus::MAX_NETWORK_UPGRADES; idx++) {
if (NetworkUpgradeState(nHeight, params, Consensus::UpgradeIndex(idx)) == UPGRADE_PENDING) {
return params.vUpgrades[idx].nActivationHeight;
return idx;
}
}
return boost::none;
}
boost::optional<int> NextActivationHeight(
int nHeight,
const Consensus::Params& params)
{
auto idx = NextEpoch(nHeight, params);
if (idx) {
return params.vUpgrades[idx.get()].nActivationHeight;
}
return boost::none;
}

12
src/consensus/upgrades.h

@ -63,6 +63,12 @@ int CurrentEpoch(int nHeight, const Consensus::Params& params);
*/
uint32_t CurrentEpochBranchId(int nHeight, const Consensus::Params& params);
/**
* Returns true if a given branch id is a valid nBranchId for one of the network
* upgrades contained in NetworkUpgradeInfo.
*/
bool IsConsensusBranchId(int branchId);
/**
* Returns true if the given block height is the activation height for the given
* upgrade.
@ -79,6 +85,12 @@ bool IsActivationHeightForAnyUpgrade(
int nHeight,
const Consensus::Params& params);
/**
* Returns the index of the next upgrade after the given block height, or
* boost::none if there are no more known upgrades.
*/
boost::optional<int> NextEpoch(int nHeight, const Consensus::Params& params);
/**
* Returns the activation height for the next upgrade after the given block height,
* or boost::none if there are no more known upgrades.

15
src/gtest/test_checktransaction.cpp

@ -506,8 +506,11 @@ TEST(checktransaction_tests, bad_txns_invalid_joinsplit_signature) {
CTransaction tx(mtx);
MockCValidationState state;
// during initial block download, DoS ban score should be zero, else 100
EXPECT_CALL(state, DoS(0, false, REJECT_INVALID, "bad-txns-invalid-joinsplit-signature", false)).Times(1);
ContextualCheckTransaction(tx, state, 0, 100);
ContextualCheckTransaction(tx, state, 0, 100, []() { return true; });
EXPECT_CALL(state, DoS(100, false, REJECT_INVALID, "bad-txns-invalid-joinsplit-signature", false)).Times(1);
ContextualCheckTransaction(tx, state, 0, 100, []() { return false; });
}
TEST(checktransaction_tests, non_canonical_ed25519_signature) {
@ -539,8 +542,11 @@ TEST(checktransaction_tests, non_canonical_ed25519_signature) {
CTransaction tx(mtx);
MockCValidationState state;
// during initial block download, DoS ban score should be zero, else 100
EXPECT_CALL(state, DoS(0, false, REJECT_INVALID, "bad-txns-invalid-joinsplit-signature", false)).Times(1);
ContextualCheckTransaction(tx, state, 0, 100);
ContextualCheckTransaction(tx, state, 0, 100, []() { return true; });
EXPECT_CALL(state, DoS(100, false, REJECT_INVALID, "bad-txns-invalid-joinsplit-signature", false)).Times(1);
ContextualCheckTransaction(tx, state, 0, 100, []() { return false; });
}
TEST(checktransaction_tests, OverwinterConstructors) {
@ -829,8 +835,11 @@ TEST(checktransaction_tests, OverwinterNotActive) {
CTransaction tx(mtx);
MockCValidationState state;
// during initial block download, DoS ban score should be zero, else 100
EXPECT_CALL(state, DoS(0, false, REJECT_INVALID, "tx-overwinter-not-active", false)).Times(1);
ContextualCheckTransaction(tx, state, 1, 100);
ContextualCheckTransaction(tx, state, 1, 100, []() { return true; });
EXPECT_CALL(state, DoS(100, false, REJECT_INVALID, "tx-overwinter-not-active", false)).Times(1);
ContextualCheckTransaction(tx, state, 1, 100, []() { return false; });
}
// This tests a transaction without the fOverwintered flag set, against the Overwinter consensus rule set.

12
src/gtest/test_transaction_builder.cpp

@ -38,7 +38,7 @@ TEST(TransactionBuilder, Invoke)
// 0.0005 t-ZEC in, 0.0004 z-ZEC out, 0.0001 t-ZEC fee
auto builder1 = TransactionBuilder(consensusParams, 1, &keystore);
builder1.AddTransparentInput(COutPoint(), scriptPubKey, 50000);
builder1.AddSaplingOutput(fvk_from, pk, 40000, {});
builder1.AddSaplingOutput(fvk_from.ovk, pk, 40000, {});
auto maybe_tx1 = builder1.Build();
ASSERT_EQ(static_cast<bool>(maybe_tx1), true);
auto tx1 = maybe_tx1.get();
@ -73,7 +73,7 @@ TEST(TransactionBuilder, Invoke)
// Check that trying to add a different anchor fails
ASSERT_FALSE(builder2.AddSaplingSpend(expsk, note, uint256(), witness));
builder2.AddSaplingOutput(fvk, pk, 25000, {});
builder2.AddSaplingOutput(fvk.ovk, pk, 25000, {});
auto maybe_tx2 = builder2.Build();
ASSERT_EQ(static_cast<bool>(maybe_tx2), true);
auto tx2 = maybe_tx2.get();
@ -153,7 +153,7 @@ TEST(TransactionBuilder, FailsWithNegativeChange)
// Fail if there is only a Sapling output
// 0.0005 z-ZEC out, 0.0001 t-ZEC fee
auto builder = TransactionBuilder(consensusParams, 1);
builder.AddSaplingOutput(fvk, pk, 50000, {});
builder.AddSaplingOutput(fvk.ovk, pk, 50000, {});
EXPECT_FALSE(static_cast<bool>(builder.Build()));
// Fail if there is only a transparent output
@ -237,7 +237,7 @@ TEST(TransactionBuilder, ChangeOutput)
{
auto builder = TransactionBuilder(consensusParams, 1, &keystore);
builder.AddTransparentInput(COutPoint(), scriptPubKey, 25000);
builder.SendChangeTo(zChangeAddr, fvkOut);
builder.SendChangeTo(zChangeAddr, fvkOut.ovk);
auto maybe_tx = builder.Build();
ASSERT_EQ(static_cast<bool>(maybe_tx), true);
auto tx = maybe_tx.get();
@ -298,7 +298,7 @@ TEST(TransactionBuilder, SetFee)
{
auto builder = TransactionBuilder(consensusParams, 1);
ASSERT_TRUE(builder.AddSaplingSpend(expsk, note, anchor, witness));
builder.AddSaplingOutput(fvk, pk, 25000, {});
builder.AddSaplingOutput(fvk.ovk, pk, 25000, {});
auto maybe_tx = builder.Build();
ASSERT_EQ(static_cast<bool>(maybe_tx), true);
auto tx = maybe_tx.get();
@ -315,7 +315,7 @@ TEST(TransactionBuilder, SetFee)
{
auto builder = TransactionBuilder(consensusParams, 1);
ASSERT_TRUE(builder.AddSaplingSpend(expsk, note, anchor, witness));
builder.AddSaplingOutput(fvk, pk, 25000, {});
builder.AddSaplingOutput(fvk.ovk, pk, 25000, {});
builder.SetFee(20000);
auto maybe_tx = builder.Build();
ASSERT_EQ(static_cast<bool>(maybe_tx), true);

29
src/gtest/test_upgrades.cpp

@ -143,6 +143,35 @@ TEST_F(UpgradesTest, IsActivationHeightForAnyUpgrade) {
EXPECT_FALSE(IsActivationHeightForAnyUpgrade(1000000, params));
}
TEST_F(UpgradesTest, NextEpoch) {
SelectParams(CBaseChainParams::REGTEST);
const Consensus::Params& params = Params().GetConsensus();
// Consensus::NetworkUpgrade::NO_ACTIVATION_HEIGHT
EXPECT_EQ(NextEpoch(-1, params), boost::none);
EXPECT_EQ(NextEpoch(0, params), boost::none);
EXPECT_EQ(NextEpoch(1, params), boost::none);
EXPECT_EQ(NextEpoch(1000000, params), boost::none);
UpdateNetworkUpgradeParameters(Consensus::UPGRADE_TESTDUMMY, Consensus::NetworkUpgrade::ALWAYS_ACTIVE);
EXPECT_EQ(NextEpoch(-1, params), boost::none);
EXPECT_EQ(NextEpoch(0, params), boost::none);
EXPECT_EQ(NextEpoch(1, params), boost::none);
EXPECT_EQ(NextEpoch(1000000, params), boost::none);
int nActivationHeight = 100;
UpdateNetworkUpgradeParameters(Consensus::UPGRADE_TESTDUMMY, nActivationHeight);
EXPECT_EQ(NextEpoch(-1, params), boost::none);
EXPECT_EQ(NextEpoch(0, params), static_cast<int>(Consensus::UPGRADE_TESTDUMMY));
EXPECT_EQ(NextEpoch(1, params), static_cast<int>(Consensus::UPGRADE_TESTDUMMY));
EXPECT_EQ(NextEpoch(nActivationHeight - 1, params), static_cast<int>(Consensus::UPGRADE_TESTDUMMY));
EXPECT_EQ(NextEpoch(nActivationHeight, params), boost::none);
EXPECT_EQ(NextEpoch(nActivationHeight + 1, params), boost::none);
EXPECT_EQ(NextEpoch(1000000, params), boost::none);
}
TEST_F(UpgradesTest, NextActivationHeight) {
SelectParams(CBaseChainParams::REGTEST);
const Consensus::Params& params = Params().GetConsensus();

10
src/keystore.cpp

@ -226,3 +226,13 @@ bool CBasicKeyStore::GetSaplingIncomingViewingKey(const libzcash::SaplingPayment
}
return false;
}
bool CBasicKeyStore::GetSaplingExtendedSpendingKey(const libzcash::SaplingPaymentAddress &addr,
libzcash::SaplingExtendedSpendingKey &extskOut) const {
libzcash::SaplingIncomingViewingKey ivk;
libzcash::SaplingFullViewingKey fvk;
return GetSaplingIncomingViewingKey(addr, ivk) &&
GetSaplingFullViewingKey(ivk, fvk) &&
GetSaplingSpendingKey(fvk, extskOut);
}

5
src/keystore.h

@ -274,6 +274,11 @@ public:
virtual bool GetSaplingIncomingViewingKey(
const libzcash::SaplingPaymentAddress &addr,
libzcash::SaplingIncomingViewingKey& ivkOut) const;
bool GetSaplingExtendedSpendingKey(
const libzcash::SaplingPaymentAddress &addr,
libzcash::SaplingExtendedSpendingKey &extskOut) const;
void GetSaplingPaymentAddresses(std::set<libzcash::SaplingPaymentAddress> &setAddress) const
{
setAddress.clear();

2
src/komodo_bitcoind.h

@ -1504,7 +1504,7 @@ bool verusCheckPOSBlock(int32_t slowflag, CBlock *pblock, int32_t height)
}
if (newPOSActive && !(validHash && posHash <= target))
{
printf("ERROR: invalid nonce value for PoS block\nnNonce: %s\nrawHash: %s\nposHash: %s\nvalue: %ull\n",
printf("ERROR: invalid nonce value for PoS block\nnNonce: %s\nrawHash: %s\nposHash: %s\nvalue: %lu\n",
pblock->nNonce.GetHex().c_str(), rawHash.GetHex().c_str(), posHash.GetHex().c_str(), value);
}
//else

39
src/main.cpp

@ -305,8 +305,7 @@ namespace {
int GetHeight()
{
LOCK(cs_main);
return chainActive.Height();
return chainActive.LastTip()->nHeight;
}
void UpdatePreferredDownload(CNode* node, CNodeState* state)
@ -999,8 +998,14 @@ bool ContextualCheckCoinbaseTransaction(const CTransaction& tx, const int nHeigh
* 1. AcceptToMemoryPool calls CheckTransaction and this function.
* 2. ProcessNewBlock calls AcceptBlock, which calls CheckBlock (which calls CheckTransaction)
* and ContextualCheckBlock (which calls this function).
* 3. The isInitBlockDownload argument is only to assist with testing.
*/
bool ContextualCheckTransaction(const CTransaction& tx, CValidationState &state, const int nHeight, const int dosLevel)
bool ContextualCheckTransaction(
const CTransaction& tx,
CValidationState &state,
const int nHeight,
const int dosLevel,
bool (*isInitBlockDownload)())
{
bool overwinterActive = NetworkUpgradeActive(nHeight, Params().GetConsensus(), Consensus::UPGRADE_OVERWINTER);
bool saplingActive = NetworkUpgradeActive(nHeight, Params().GetConsensus(), Consensus::UPGRADE_SAPLING);
@ -1008,7 +1013,7 @@ bool ContextualCheckTransaction(const CTransaction& tx, CValidationState &state,
// If Sprout rules apply, reject transactions which are intended for Overwinter and beyond
if (isSprout && tx.fOverwintered) {
return state.DoS(IsInitialBlockDownload() ? 0 : dosLevel,
return state.DoS(isInitBlockDownload() ? 0 : dosLevel,
error("ContextualCheckTransaction(): overwinter is not active yet"),
REJECT_INVALID, "tx-overwinter-not-active");
}
@ -1111,7 +1116,7 @@ bool ContextualCheckTransaction(const CTransaction& tx, CValidationState &state,
dataToBeSigned.begin(), 32,
tx.joinSplitPubKey.begin()
) != 0) {
return state.DoS(IsInitialBlockDownload() ? 0 : 100,
return state.DoS(isInitBlockDownload() ? 0 : 100,
error("CheckTransaction(): invalid joinsplit signature"),
REJECT_INVALID, "bad-txns-invalid-joinsplit-signature");
}
@ -6302,16 +6307,16 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv,
pfrom->fDisconnect = true;
return false;
}
// When Overwinter is active, reject incoming connections from non-Overwinter nodes
// Reject incoming connections from nodes that don't know about the current epoch
const Consensus::Params& params = Params().GetConsensus();
if (NetworkUpgradeActive(GetHeight(), params, Consensus::UPGRADE_OVERWINTER)
&& nVersion < params.vUpgrades[Consensus::UPGRADE_OVERWINTER].nProtocolVersion)
auto currentEpoch = CurrentEpoch(GetHeight(), params);
if (pfrom->nVersion < params.vUpgrades[currentEpoch].nProtocolVersion)
{
LogPrintf("peer=%d using obsolete version %i; disconnecting\n", pfrom->id, nVersion);
pfrom->PushMessage("reject", strCommand, REJECT_OBSOLETE,
strprintf("Version must be %d or greater",
params.vUpgrades[Consensus::UPGRADE_OVERWINTER].nProtocolVersion));
strprintf("Version must be %d or greater",
params.vUpgrades[currentEpoch].nProtocolVersion));
pfrom->fDisconnect = true;
return false;
}
@ -6438,15 +6443,15 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv,
// Disconnect existing peer connection when:
// 1. The version message has been received
// 2. Overwinter is active
// 3. Peer version is pre-Overwinter
else if (NetworkUpgradeActive(GetHeight(), chainparams.GetConsensus(), Consensus::UPGRADE_OVERWINTER)
&& (pfrom->nVersion < chainparams.GetConsensus().vUpgrades[Consensus::UPGRADE_OVERWINTER].nProtocolVersion))
// 2. Peer version is below the minimum version for the current epoch
else if (pfrom->nVersion < chainparams.GetConsensus().vUpgrades[
CurrentEpoch(GetHeight(), chainparams.GetConsensus())].nProtocolVersion)
{
LogPrintf("peer=%d using obsolete version %i; disconnecting\n", pfrom->id, pfrom->nVersion);
pfrom->PushMessage("reject", strCommand, REJECT_OBSOLETE,
strprintf("Version must be %d or greater",
chainparams.GetConsensus().vUpgrades[Consensus::UPGRADE_OVERWINTER].nProtocolVersion));
strprintf("Version must be %d or greater",
chainparams.GetConsensus().vUpgrades[
CurrentEpoch(GetHeight(), chainparams.GetConsensus())].nProtocolVersion));
pfrom->fDisconnect = true;
return false;
}

3
src/main.h

@ -689,7 +689,8 @@ bool ContextualCheckInputs(const CTransaction& tx, CValidationState &state, cons
std::vector<CScriptCheck> *pvChecks = NULL);
/** Check a transaction contextually against a set of consensus rules */
bool ContextualCheckTransaction(const CTransaction& tx, CValidationState &state, int nHeight, int dosLevel);
bool ContextualCheckTransaction(const CTransaction& tx, CValidationState &state, int nHeight, int dosLevel,
bool (*isInitBlockDownload)() = IsInitialBlockDownload);
/** Apply the effects of this transaction on the UTXO set represented by view */
void UpdateCoins(const CTransaction& tx, CCoinsViewCache& inputs, int nHeight);

32
src/net.cpp

@ -824,22 +824,26 @@ static bool AttemptToEvictConnection(bool fPreferNewConnection) {
}
const Consensus::Params& params = Params().GetConsensus();
int nActivationHeight = params.vUpgrades[Consensus::UPGRADE_SAPLING].nActivationHeight;
if (nActivationHeight > 0 &&
height < nActivationHeight &&
height >= nActivationHeight - NETWORK_UPGRADE_PEER_PREFERENCE_BLOCK_PERIOD)
{
// Find any nodes which don't support Overwinter protocol version
BOOST_FOREACH(const CNodeRef &node, vEvictionCandidates) {
if (node->nVersion < params.vUpgrades[Consensus::UPGRADE_SAPLING].nProtocolVersion) {
vTmpEvictionCandidates.push_back(node);
auto nextEpoch = NextEpoch(height, params);
if (nextEpoch) {
auto idx = nextEpoch.get();
int nActivationHeight = params.vUpgrades[idx].nActivationHeight;
if (nActivationHeight > 0 &&
height < nActivationHeight &&
height >= nActivationHeight - NETWORK_UPGRADE_PEER_PREFERENCE_BLOCK_PERIOD)
{
// Find any nodes which don't support the protocol version for the next upgrade
for (const CNodeRef &node : vEvictionCandidates) {
if (node->nVersion < params.vUpgrades[idx].nProtocolVersion) {
vTmpEvictionCandidates.push_back(node);
}
}
}
// Prioritize these nodes by replacing eviction set with them
if (vTmpEvictionCandidates.size() > 0) {
vEvictionCandidates = vTmpEvictionCandidates;
// Prioritize these nodes by replacing eviction set with them
if (vTmpEvictionCandidates.size() > 0) {
vEvictionCandidates = vTmpEvictionCandidates;
}
}
}

107
src/rpc/rawtransaction.cpp

@ -7,6 +7,7 @@
#include "consensus/validation.h"
#include "core_io.h"
#include "init.h"
#include "deprecation.h"
#include "key_io.h"
#include "keystore.h"
#include "main.h"
@ -122,6 +123,36 @@ UniValue TxJoinSplitToJSON(const CTransaction& tx) {
uint64_t komodo_accrued_interest(int32_t *txheightp,uint32_t *locktimep,uint256 hash,int32_t n,int32_t checkheight,uint64_t checkvalue,int32_t tipheight);
UniValue TxShieldedSpendsToJSON(const CTransaction& tx) {
UniValue vdesc(UniValue::VARR);
for (const SpendDescription& spendDesc : tx.vShieldedSpend) {
UniValue obj(UniValue::VOBJ);
obj.push_back(Pair("cv", spendDesc.cv.GetHex()));
obj.push_back(Pair("anchor", spendDesc.anchor.GetHex()));
obj.push_back(Pair("nullifier", spendDesc.nullifier.GetHex()));
obj.push_back(Pair("rk", spendDesc.rk.GetHex()));
obj.push_back(Pair("proof", HexStr(spendDesc.zkproof.begin(), spendDesc.zkproof.end())));
obj.push_back(Pair("spendAuthSig", HexStr(spendDesc.spendAuthSig.begin(), spendDesc.spendAuthSig.end())));
vdesc.push_back(obj);
}
return vdesc;
}
UniValue TxShieldedOutputsToJSON(const CTransaction& tx) {
UniValue vdesc(UniValue::VARR);
for (const OutputDescription& outputDesc : tx.vShieldedOutput) {
UniValue obj(UniValue::VOBJ);
obj.push_back(Pair("cv", outputDesc.cv.GetHex()));
obj.push_back(Pair("cmu", outputDesc.cm.GetHex()));
obj.push_back(Pair("ephemeralKey", outputDesc.ephemeralKey.GetHex()));
obj.push_back(Pair("encCiphertext", HexStr(outputDesc.encCiphertext.begin(), outputDesc.encCiphertext.end())));
obj.push_back(Pair("outCiphertext", HexStr(outputDesc.outCiphertext.begin(), outputDesc.outCiphertext.end())));
obj.push_back(Pair("proof", HexStr(outputDesc.zkproof.begin(), outputDesc.zkproof.end())));
vdesc.push_back(obj);
}
return vdesc;
}
int32_t myIsutxo_spent(uint256 &spenttxid,uint256 txid,int32_t vout)
{
CSpentIndexValue spentInfo; CSpentIndexKey spentKey(txid,vout);
@ -205,7 +236,6 @@ void TxToJSONExpanded(const CTransaction& tx, const uint256 hashBlock, UniValue&
interest = komodo_accrued_interest(&txheight,&locktime,tx.GetHash(),i,0,txout.nValue,(int32_t)tipindex->nHeight);
out.push_back(Pair("interest", ValueFromAmount(interest)));
}
out.push_back(Pair("valueZat", txout.nValue));
out.push_back(Pair("valueSat", txout.nValue)); // [+] Decker
out.push_back(Pair("n", (int64_t)i));
UniValue o(UniValue::VOBJ);
@ -228,6 +258,17 @@ void TxToJSONExpanded(const CTransaction& tx, const uint256 hashBlock, UniValue&
UniValue vjoinsplit = TxJoinSplitToJSON(tx);
entry.push_back(Pair("vjoinsplit", vjoinsplit));
if (tx.fOverwintered && tx.nVersion >= SAPLING_TX_VERSION) {
entry.push_back(Pair("valueBalance", ValueFromAmount(tx.valueBalance)));
UniValue vspenddesc = TxShieldedSpendsToJSON(tx);
entry.push_back(Pair("vShieldedSpend", vspenddesc));
UniValue voutputdesc = TxShieldedOutputsToJSON(tx);
entry.push_back(Pair("vShieldedOutput", voutputdesc));
if (!(vspenddesc.empty() && voutputdesc.empty())) {
entry.push_back(Pair("bindingSig", HexStr(tx.bindingSig.begin(), tx.bindingSig.end())));
}
}
if (!hashBlock.IsNull()) {
entry.push_back(Pair("blockhash", hashBlock.GetHex()));
@ -246,12 +287,16 @@ void TxToJSONExpanded(const CTransaction& tx, const uint256 hashBlock, UniValue&
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("txid", tx.GetHash().GetHex()));
entry.push_back(Pair("overwintered", tx.fOverwintered));
entry.push_back(Pair("version", tx.nVersion));
if (tx.fOverwintered) {
entry.push_back(Pair("versiongroupid", HexInt(tx.nVersionGroupId)));
}
entry.push_back(Pair("locktime", (int64_t)tx.nLockTime));
if (tx.fOverwintered) {
entry.push_back(Pair("expiryheight", (int64_t)tx.nExpiryHeight));
}
UniValue vin(UniValue::VARR);
BOOST_FOREACH(const CTxIn& txin, tx.vin) {
UniValue in(UniValue::VOBJ);
@ -261,7 +306,7 @@ void TxToJSON(const CTransaction& tx, const uint256 hashBlock, UniValue& entry)
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("asm", ScriptToAsmStr(txin.scriptSig, true)));
o.push_back(Pair("hex", HexStr(txin.scriptSig.begin(), txin.scriptSig.end())));
in.push_back(Pair("scriptSig", o));
}
@ -269,13 +314,21 @@ void TxToJSON(const CTransaction& tx, const uint256 hashBlock, UniValue& entry)
vin.push_back(in);
}
entry.push_back(Pair("vin", vin));
UniValue vout(UniValue::VARR);
BlockMap::iterator it = mapBlockIndex.find(pcoinsTip->GetBestBlock());
CBlockIndex *tipindex,*pindex = it->second;
uint64_t interest;
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));
if ( ASSETCHAINS_SYMBOL[0] == 0 && pindex != 0 && tx.nLockTime >= 500000000 && (tipindex= chainActive.LastTip()) != 0 )
{
int64_t interest; int32_t txheight; uint32_t locktime;
interest = komodo_accrued_interest(&txheight,&locktime,tx.GetHash(),i,0,txout.nValue,(int32_t)tipindex->nHeight);
out.push_back(Pair("interest", ValueFromAmount(interest)));
}
out.push_back(Pair("valueZat", txout.nValue));
out.push_back(Pair("n", (int64_t)i));
UniValue o(UniValue::VOBJ);
ScriptPubKeyToJSON(txout.scriptPubKey, o, true);
@ -283,23 +336,33 @@ void TxToJSON(const CTransaction& tx, const uint256 hashBlock, UniValue& entry)
vout.push_back(out);
}
entry.push_back(Pair("vout", vout));
UniValue vjoinsplit = TxJoinSplitToJSON(tx);
entry.push_back(Pair("vjoinsplit", vjoinsplit));
if (tx.fOverwintered && tx.nVersion >= SAPLING_TX_VERSION) {
entry.push_back(Pair("valueBalance", ValueFromAmount(tx.valueBalance)));
UniValue vspenddesc = TxShieldedSpendsToJSON(tx);
entry.push_back(Pair("vShieldedSpend", vspenddesc));
UniValue voutputdesc = TxShieldedOutputsToJSON(tx);
entry.push_back(Pair("vShieldedOutput", voutputdesc));
if (!(vspenddesc.empty() && voutputdesc.empty())) {
entry.push_back(Pair("bindingSig", HexStr(tx.bindingSig.begin(), tx.bindingSig.end())));
}
}
if (!hashBlock.IsNull()) {
entry.push_back(Pair("blockhash", hashBlock.GetHex()));
BlockMap::iterator mi = mapBlockIndex.find(hashBlock);
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("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("confirmations", 0));
}
else
entry.push_back(Pair("confirmations", 0));
}
}
}
@ -870,7 +933,7 @@ static void TxInErrorToJSON(const CTxIn& txin, UniValue& vErrorsRet, const std::
UniValue signrawtransaction(const UniValue& params, bool fHelp)
{
if (fHelp || params.size() < 1 || params.size() > 4)
if (fHelp || params.size() < 1 || params.size() > 5)
throw runtime_error(
"signrawtransaction \"hexstring\" ( [{\"txid\":\"id\",\"vout\":n,\"scriptPubKey\":\"hex\",\"redeemScript\":\"hex\"},...] [\"privatekey1\",...] sighashtype )\n"
"\nSign inputs for raw transaction (serialized, hex-encoded).\n"
@ -907,6 +970,8 @@ UniValue signrawtransaction(const UniValue& params, bool fHelp)
" \"ALL|ANYONECANPAY\"\n"
" \"NONE|ANYONECANPAY\"\n"
" \"SINGLE|ANYONECANPAY\"\n"
"5. \"branchid\" (string, optional) The hex representation of the consensus branch id to sign with."
" This can be used to force signing with consensus rules that are ahead of the node's current height.\n"
"\nResult:\n"
"{\n"
@ -934,7 +999,7 @@ UniValue signrawtransaction(const UniValue& params, bool fHelp)
#else
LOCK(cs_main);
#endif
RPCTypeCheck(params, boost::assign::list_of(UniValue::VSTR)(UniValue::VARR)(UniValue::VARR)(UniValue::VSTR), true);
RPCTypeCheck(params, boost::assign::list_of(UniValue::VSTR)(UniValue::VARR)(UniValue::VARR)(UniValue::VSTR)(UniValue::VSTR), true);
vector<unsigned char> txData(ParseHexV(params[0], "argument 1"));
CDataStream ssData(txData, SER_NETWORK, PROTOCOL_VERSION);
@ -1070,10 +1135,20 @@ UniValue signrawtransaction(const UniValue& params, bool fHelp)
}
bool fHashSingle = ((nHashType & ~SIGHASH_ANYONECANPAY) == SIGHASH_SINGLE);
// Use the approximate release height if it is greater so offline nodes
// have a better estimation of the current height and will be more likely to
// determine the correct consensus branch ID.
int chainHeight = std::max(chainActive.Height() + 1, APPROX_RELEASE_HEIGHT);
// Grab the current consensus branch ID
auto consensusBranchId = CurrentEpochBranchId(chainActive.Height() + 1, Params().GetConsensus());
auto consensusBranchId = CurrentEpochBranchId(chainHeight, Params().GetConsensus());
if (params.size() > 4 && !params[4].isNull()) {
consensusBranchId = ParseHexToUInt32(params[4].get_str());
if (!IsConsensusBranchId(consensusBranchId)) {
throw runtime_error(params[4].get_str() + " is not a valid consensus branch id");
}
}
// Script verification errors
UniValue vErrors(UniValue::VARR);

2
src/test/rpc_tests.cpp

@ -92,6 +92,8 @@ BOOST_AUTO_TEST_CASE(rpc_rawparams)
BOOST_CHECK_NO_THROW(CallRPC(string("signrawtransaction ")+rawtx+" null null NONE|ANYONECANPAY"));
BOOST_CHECK_NO_THROW(CallRPC(string("signrawtransaction ")+rawtx+" [] [] NONE|ANYONECANPAY"));
BOOST_CHECK_THROW(CallRPC(string("signrawtransaction ")+rawtx+" null null badenum"), runtime_error);
BOOST_CHECK_NO_THROW(CallRPC(string("signrawtransaction ")+rawtx+" [] [] NONE|ANYONECANPAY 5ba81b19"));
BOOST_CHECK_THROW(CallRPC(string("signrawtransaction ")+rawtx+" [] [] ALL NONE|ANYONECANPAY 123abc"), runtime_error);
// Only check failure cases for sendrawtransaction, there's no network to send to...
BOOST_CHECK_THROW(CallRPC("sendrawtransaction"), runtime_error);

102
src/test/rpc_wallet_tests.cpp

@ -1252,6 +1252,108 @@ BOOST_AUTO_TEST_CASE(rpc_z_sendmany_internals)
}
BOOST_AUTO_TEST_CASE(rpc_z_sendmany_taddr_to_sapling)
{
SelectParams(CBaseChainParams::REGTEST);
UpdateNetworkUpgradeParameters(Consensus::UPGRADE_OVERWINTER, Consensus::NetworkUpgrade::ALWAYS_ACTIVE);
UpdateNetworkUpgradeParameters(Consensus::UPGRADE_SAPLING, Consensus::NetworkUpgrade::ALWAYS_ACTIVE);
LOCK(pwalletMain->cs_wallet);
if (!pwalletMain->HaveHDSeed()) {
pwalletMain->GenerateNewSeed();
}
UniValue retValue;
// add keys manually
auto taddr = pwalletMain->GenerateNewKey().GetID();
std::string taddr1 = EncodeDestination(taddr);
auto pa = pwalletMain->GenerateNewSaplingZKey();
std::string zaddr1 = EncodePaymentAddress(pa);
auto consensusParams = Params().GetConsensus();
retValue = CallRPC("getblockcount");
int nextBlockHeight = retValue.get_int() + 1;
// Add a fake transaction to the wallet
CMutableTransaction mtx = CreateNewContextualCMutableTransaction(consensusParams, nextBlockHeight);
CScript scriptPubKey = CScript() << OP_DUP << OP_HASH160 << ToByteVector(taddr) << OP_EQUALVERIFY << OP_CHECKSIG;
mtx.vout.push_back(CTxOut(5 * COIN, scriptPubKey));
CWalletTx wtx(pwalletMain, mtx);
pwalletMain->AddToWallet(wtx, true, NULL);
// Fake-mine the transaction
BOOST_CHECK_EQUAL(0, chainActive.Height());
CBlock block;
block.hashPrevBlock = chainActive.Tip()->GetBlockHash();
block.vtx.push_back(wtx);
block.hashMerkleRoot = block.BuildMerkleTree();
auto blockHash = block.GetHash();
CBlockIndex fakeIndex {block};
fakeIndex.nHeight = 1;
mapBlockIndex.insert(std::make_pair(blockHash, &fakeIndex));
chainActive.SetTip(&fakeIndex);
BOOST_CHECK(chainActive.Contains(&fakeIndex));
BOOST_CHECK_EQUAL(1, chainActive.Height());
wtx.SetMerkleBranch(block);
pwalletMain->AddToWallet(wtx, true, NULL);
// Context that z_sendmany requires
auto builder = TransactionBuilder(consensusParams, nextBlockHeight, pwalletMain);
mtx = CreateNewContextualCMutableTransaction(consensusParams, nextBlockHeight);
std::vector<SendManyRecipient> recipients = { SendManyRecipient(zaddr1, 1 * COIN, "ABCD") };
std::shared_ptr<AsyncRPCOperation> operation( new AsyncRPCOperation_sendmany(builder, mtx, taddr1, {}, recipients, 0) );
std::shared_ptr<AsyncRPCOperation_sendmany> ptr = std::dynamic_pointer_cast<AsyncRPCOperation_sendmany> (operation);
// Enable test mode so tx is not sent
static_cast<AsyncRPCOperation_sendmany *>(operation.get())->testmode = true;
// Generate the Sapling shielding transaction
operation->main();
BOOST_CHECK(operation->isSuccess());
// Get the transaction
auto result = operation->getResult();
BOOST_ASSERT(result.isObject());
auto hexTx = result["hex"].getValStr();
CDataStream ss(ParseHex(hexTx), SER_NETWORK, PROTOCOL_VERSION);
CTransaction tx;
ss >> tx;
BOOST_ASSERT(!tx.vShieldedOutput.empty());
// We shouldn't be able to decrypt with the empty ovk
BOOST_CHECK(!AttemptSaplingOutDecryption(
tx.vShieldedOutput[0].outCiphertext,
uint256(),
tx.vShieldedOutput[0].cv,
tx.vShieldedOutput[0].cm,
tx.vShieldedOutput[0].ephemeralKey));
// We should be able to decrypt the outCiphertext with the ovk
// generated for transparent addresses
HDSeed seed;
BOOST_ASSERT(pwalletMain->GetHDSeed(seed));
BOOST_CHECK(AttemptSaplingOutDecryption(
tx.vShieldedOutput[0].outCiphertext,
ovkForShieldingFromTaddr(seed),
tx.vShieldedOutput[0].cv,
tx.vShieldedOutput[0].cm,
tx.vShieldedOutput[0].ephemeralKey));
// Tear down
chainActive.SetTip(NULL);
mapBlockIndex.erase(blockHash);
mapArgs.erase("-developersapling");
mapArgs.erase("-experimentalfeatures");
// Revert to default
UpdateNetworkUpgradeParameters(Consensus::UPGRADE_SAPLING, Consensus::NetworkUpgrade::NO_ACTIVATION_HEIGHT);
UpdateNetworkUpgradeParameters(Consensus::UPGRADE_OVERWINTER, Consensus::NetworkUpgrade::NO_ACTIVATION_HEIGHT);
}
/*
* This test covers storing encrypted zkeys in the wallet.
*/

10
src/transaction_builder.cpp

@ -47,13 +47,13 @@ bool TransactionBuilder::AddSaplingSpend(
}
void TransactionBuilder::AddSaplingOutput(
libzcash::SaplingFullViewingKey from,
uint256 ovk,
libzcash::SaplingPaymentAddress to,
CAmount value,
std::array<unsigned char, ZC_MEMO_SIZE> memo)
{
auto note = libzcash::SaplingNote(to, value);
outputs.emplace_back(from.ovk, note, memo);
outputs.emplace_back(ovk, note, memo);
mtx.valueBalance -= value;
}
@ -84,9 +84,9 @@ void TransactionBuilder::SetFee(CAmount fee)
this->fee = fee;
}
void TransactionBuilder::SendChangeTo(libzcash::SaplingPaymentAddress changeAddr, libzcash::SaplingFullViewingKey fvkOut)
void TransactionBuilder::SendChangeTo(libzcash::SaplingPaymentAddress changeAddr, uint256 ovk)
{
zChangeAddr = std::make_pair(fvkOut, changeAddr);
zChangeAddr = std::make_pair(ovk, changeAddr);
tChangeAddr = boost::none;
}
@ -136,7 +136,7 @@ boost::optional<CTransaction> TransactionBuilder::Build()
auto fvk = spends[0].expsk.full_viewing_key();
auto note = spends[0].note;
libzcash::SaplingPaymentAddress changeAddr(note.d, note.pk_d);
AddSaplingOutput(fvk, changeAddr, change, {});
AddSaplingOutput(fvk.ovk, changeAddr, change, {});
} else {
return boost::none;
}

6
src/transaction_builder.h

@ -65,7 +65,7 @@ private:
std::vector<OutputDescriptionInfo> outputs;
std::vector<TransparentInputInfo> tIns;
boost::optional<std::pair<libzcash::SaplingFullViewingKey, libzcash::SaplingPaymentAddress>> zChangeAddr;
boost::optional<std::pair<uint256, libzcash::SaplingPaymentAddress>> zChangeAddr;
boost::optional<CTxDestination> tChangeAddr;
public:
@ -83,7 +83,7 @@ public:
SaplingWitness witness);
void AddSaplingOutput(
libzcash::SaplingFullViewingKey from,
uint256 ovk,
libzcash::SaplingPaymentAddress to,
CAmount value,
std::array<unsigned char, ZC_MEMO_SIZE> memo);
@ -93,7 +93,7 @@ public:
bool AddTransparentOutput(CTxDestination& to, CAmount value);
void SendChangeTo(libzcash::SaplingPaymentAddress changeAddr, libzcash::SaplingFullViewingKey fvkOut);
void SendChangeTo(libzcash::SaplingPaymentAddress changeAddr, uint256 ovk);
bool SendChangeTo(CTxDestination& changeAddr);

18
src/wallet/asyncrpcoperation_sendmany.cpp

@ -385,13 +385,23 @@ bool AsyncRPCOperation_sendmany::main_impl() {
// Get various necessary keys
SaplingExpandedSpendingKey expsk;
SaplingFullViewingKey from;
uint256 ovk;
if (isfromzaddr_) {
auto sk = boost::get<libzcash::SaplingExtendedSpendingKey>(spendingkey_);
expsk = sk.expsk;
from = expsk.full_viewing_key();
ovk = expsk.full_viewing_key().ovk;
} else {
// TODO: Set "from" to something!
// Sending from a t-address, which we don't have an ovk for. Instead,
// generate a common one from the HD seed. This ensures the data is
// recoverable, while keeping it logically separate from the ZIP 32
// Sapling key hierarchy, which the user might not be using.
HDSeed seed;
if (!pwalletMain->GetHDSeed(seed)) {
throw JSONRPCError(
RPC_WALLET_ERROR,
"CWallet::GenerateNewSaplingZKey(): HD seed not found");
}
ovk = ovkForShieldingFromTaddr(seed);
}
// Set change address if we are using transparent funds
@ -455,7 +465,7 @@ bool AsyncRPCOperation_sendmany::main_impl() {
auto memo = get_memo_from_hex_string(hexMemo);
builder_.AddSaplingOutput(from, to, value, memo);
builder_.AddSaplingOutput(ovk, to, value, memo);
}
// Add transparent outputs

24
src/wallet/gtest/test_wallet.cpp

@ -384,7 +384,7 @@ TEST(WalletTests, SetSaplingNoteAddrsInCWalletTx) {
auto builder = TransactionBuilder(consensusParams, 1);
ASSERT_TRUE(builder.AddSaplingSpend(expsk, note, anchor, witness));
builder.AddSaplingOutput(fvk, pk, 50000, {});
builder.AddSaplingOutput(fvk.ovk, pk, 50000, {});
builder.SetFee(0);
auto maybe_tx = builder.Build();
ASSERT_EQ(static_cast<bool>(maybe_tx), true);
@ -504,7 +504,7 @@ TEST(WalletTests, FindMySaplingNotes) {
// Generate transaction
auto builder = TransactionBuilder(consensusParams, 1);
ASSERT_TRUE(builder.AddSaplingSpend(expsk, note, anchor, witness));
builder.AddSaplingOutput(fvk, pk, 25000, {});
builder.AddSaplingOutput(fvk.ovk, pk, 25000, {});
auto maybe_tx = builder.Build();
ASSERT_EQ(static_cast<bool>(maybe_tx), true);
auto tx = maybe_tx.get();
@ -644,7 +644,7 @@ TEST(WalletTests, GetConflictedSaplingNotes) {
// Generate tx to create output note B
auto builder = TransactionBuilder(consensusParams, 1);
ASSERT_TRUE(builder.AddSaplingSpend(expsk, note, anchor, witness));
builder.AddSaplingOutput(fvk, pk, 35000, {});
builder.AddSaplingOutput(fvk.ovk, pk, 35000, {});
auto maybe_tx = builder.Build();
ASSERT_EQ(static_cast<bool>(maybe_tx), true);
auto tx = maybe_tx.get();
@ -700,7 +700,7 @@ TEST(WalletTests, GetConflictedSaplingNotes) {
// Create transaction to spend note B
auto builder2 = TransactionBuilder(consensusParams, 2);
ASSERT_TRUE(builder2.AddSaplingSpend(expsk, note2, anchor, spend_note_witness));
builder2.AddSaplingOutput(fvk, pk, 20000, {});
builder2.AddSaplingOutput(fvk.ovk, pk, 20000, {});
auto maybe_tx2 = builder2.Build();
ASSERT_EQ(static_cast<bool>(maybe_tx2), true);
auto tx2 = maybe_tx2.get();
@ -708,7 +708,7 @@ TEST(WalletTests, GetConflictedSaplingNotes) {
// Create conflicting transaction which also spends note B
auto builder3 = TransactionBuilder(consensusParams, 2);
ASSERT_TRUE(builder3.AddSaplingSpend(expsk, note2, anchor, spend_note_witness));
builder3.AddSaplingOutput(fvk, pk, 19999, {});
builder3.AddSaplingOutput(fvk.ovk, pk, 19999, {});
auto maybe_tx3 = builder3.Build();
ASSERT_EQ(static_cast<bool>(maybe_tx3), true);
auto tx3 = maybe_tx3.get();
@ -809,7 +809,7 @@ TEST(WalletTests, SaplingNullifierIsSpent) {
// Generate transaction
auto builder = TransactionBuilder(consensusParams, 1);
ASSERT_TRUE(builder.AddSaplingSpend(expsk, note, anchor, witness));
builder.AddSaplingOutput(fvk, pk, 25000, {});
builder.AddSaplingOutput(fvk.ovk, pk, 25000, {});
auto maybe_tx = builder.Build();
ASSERT_EQ(static_cast<bool>(maybe_tx), true);
auto tx = maybe_tx.get();
@ -906,7 +906,7 @@ TEST(WalletTests, NavigateFromSaplingNullifierToNote) {
// Generate transaction
auto builder = TransactionBuilder(consensusParams, 1);
ASSERT_TRUE(builder.AddSaplingSpend(expsk, note, anchor, witness));
builder.AddSaplingOutput(fvk, pk, 25000, {});
builder.AddSaplingOutput(fvk.ovk, pk, 25000, {});
auto maybe_tx = builder.Build();
ASSERT_EQ(static_cast<bool>(maybe_tx), true);
auto tx = maybe_tx.get();
@ -1042,7 +1042,7 @@ TEST(WalletTests, SpentSaplingNoteIsFromMe) {
// Generate transaction, which sends funds to note B
auto builder = TransactionBuilder(consensusParams, 1);
ASSERT_TRUE(builder.AddSaplingSpend(expsk, note, anchor, witness));
builder.AddSaplingOutput(fvk, pk, 25000, {});
builder.AddSaplingOutput(fvk.ovk, pk, 25000, {});
auto maybe_tx = builder.Build();
ASSERT_EQ(static_cast<bool>(maybe_tx), true);
auto tx = maybe_tx.get();
@ -1114,7 +1114,7 @@ TEST(WalletTests, SpentSaplingNoteIsFromMe) {
// Create transaction to spend note B
auto builder2 = TransactionBuilder(consensusParams, 2);
ASSERT_TRUE(builder2.AddSaplingSpend(expsk, note2, anchor, spend_note_witness));
builder2.AddSaplingOutput(fvk, pk, 12500, {});
builder2.AddSaplingOutput(fvk.ovk, pk, 12500, {});
auto maybe_tx2 = builder2.Build();
ASSERT_EQ(static_cast<bool>(maybe_tx2), true);
auto tx2 = maybe_tx2.get();
@ -1744,7 +1744,7 @@ TEST(WalletTests, UpdatedSaplingNoteData) {
// Generate transaction
auto builder = TransactionBuilder(consensusParams, 1);
ASSERT_TRUE(builder.AddSaplingSpend(expsk, note, anchor, witness));
builder.AddSaplingOutput(fvk, pk2, 25000, {});
builder.AddSaplingOutput(fvk.ovk, pk2, 25000, {});
auto maybe_tx = builder.Build();
ASSERT_EQ(static_cast<bool>(maybe_tx), true);
auto tx = maybe_tx.get();
@ -1894,7 +1894,7 @@ TEST(WalletTests, MarkAffectedSaplingTransactionsDirty) {
// 0.0005 t-ZEC in, 0.0004 z-ZEC out, 0.0001 t-ZEC fee
auto builder = TransactionBuilder(consensusParams, 1, &keystore);
builder.AddTransparentInput(COutPoint(), scriptPubKey, 50000);
builder.AddSaplingOutput(fvk, pk, 40000, {});
builder.AddSaplingOutput(fvk.ovk, pk, 40000, {});
auto maybe_tx = builder.Build();
ASSERT_EQ(static_cast<bool>(maybe_tx), true);
auto tx1 = maybe_tx.get();
@ -1951,7 +1951,7 @@ TEST(WalletTests, MarkAffectedSaplingTransactionsDirty) {
// 0.0004 z-ZEC in, 0.00025 z-ZEC out, 0.0001 t-ZEC fee, 0.00005 z-ZEC change
auto builder2 = TransactionBuilder(consensusParams, 2);
ASSERT_TRUE(builder2.AddSaplingSpend(expsk, note, anchor, witness));
builder2.AddSaplingOutput(fvk, pk, 25000, {});
builder2.AddSaplingOutput(fvk.ovk, pk, 25000, {});
auto maybe_tx2 = builder2.Build();
ASSERT_EQ(static_cast<bool>(maybe_tx2), true);
auto tx2 = maybe_tx2.get();

10
src/wallet/gtest/test_wallet_zkeys.cpp

@ -63,7 +63,7 @@ TEST(wallet_zkeys_tests, store_and_load_sapling_zkeys) {
/**
* This test covers methods on CWallet
* GenerateNewZKey()
* AddZKey()
* AddSproutZKey()
* LoadZKey()
* LoadZKeyMetadata()
*/
@ -89,7 +89,7 @@ TEST(wallet_zkeys_tests, store_and_load_zkeys) {
// manually add new spending key to wallet
auto sk = libzcash::SproutSpendingKey::random();
ASSERT_TRUE(wallet.AddZKey(sk));
ASSERT_TRUE(wallet.AddSproutZKey(sk));
// verify wallet did add it
addr = sk.address();
@ -116,7 +116,7 @@ TEST(wallet_zkeys_tests, store_and_load_zkeys) {
ASSERT_TRUE(wallet.LoadZKeyMetadata(addr, meta));
// check metadata is the same
CKeyMetadata m= wallet.mapZKeyMetadata[addr];
CKeyMetadata m= wallet.mapSproutZKeyMetadata[addr];
ASSERT_EQ(m.nCreateTime, now);
}
@ -215,7 +215,7 @@ TEST(wallet_zkeys_tests, write_zkey_direct_to_db) {
ASSERT_EQ(1, addrs.size());
// wallet should have default metadata for addr with null createtime
CKeyMetadata m = wallet.mapZKeyMetadata[addr];
CKeyMetadata m = wallet.mapSproutZKeyMetadata[addr];
ASSERT_EQ(m.nCreateTime, 0);
ASSERT_NE(m.nCreateTime, now);
@ -235,7 +235,7 @@ TEST(wallet_zkeys_tests, write_zkey_direct_to_db) {
ASSERT_EQ(2, addrs.size());
// check metadata is now the same
m = wallet.mapZKeyMetadata[addr];
m = wallet.mapSproutZKeyMetadata[addr];
ASSERT_EQ(m.nCreateTime, now);
}

132
src/wallet/rpcdump.cpp

@ -297,28 +297,23 @@ UniValue importwallet_impl(const UniValue& params, bool fHelp, bool fImportZKeys
// Let's see if the address is a valid Zcash spending key
if (fImportZKeys) {
auto spendingkey = DecodeSpendingKey(vstr[0]);
int64_t nTime = DecodeDumpTime(vstr[1]);
// Only include hdKeypath and seedFpStr if we have both
boost::optional<std::string> hdKeypath = (vstr.size() > 3) ? boost::optional<std::string>(vstr[2]) : boost::none;
boost::optional<std::string> seedFpStr = (vstr.size() > 3) ? boost::optional<std::string>(vstr[3]) : boost::none;
if (IsValidSpendingKey(spendingkey)) {
// TODO: Add Sapling support. For now, ensure we can freely convert.
assert(boost::get<libzcash::SproutSpendingKey>(&spendingkey) != nullptr);
auto key = boost::get<libzcash::SproutSpendingKey>(spendingkey);
auto addr = key.address();
if (pwalletMain->HaveSproutSpendingKey(addr)) {
LogPrint("zrpc", "Skipping import of zaddr %s (key already present)\n", EncodePaymentAddress(addr));
continue;
}
int64_t nTime = DecodeDumpTime(vstr[1]);
LogPrint("zrpc", "Importing zaddr %s...\n", EncodePaymentAddress(addr));
if (!pwalletMain->AddZKey(key)) {
auto addResult = boost::apply_visitor(
AddSpendingKeyToWallet(pwalletMain, Params().GetConsensus(), nTime, hdKeypath, seedFpStr, true), spendingkey);
if (addResult == KeyAlreadyExists){
LogPrint("zrpc", "Skipping import of zaddr (key already present)\n");
} else if (addResult == KeyNotAdded) {
// Something went wrong
fGood = false;
continue;
}
// Successfully imported zaddr. Now import the metadata.
pwalletMain->mapZKeyMetadata[addr].nCreateTime = nTime;
continue;
} else {
LogPrint("zrpc", "Importing detected an error: invalid spending key. Trying as a transparent key...\n");
// Not a valid spending key, so carry on and see if it's a Zcash style address.
// Not a valid spending key, so carry on and see if it's a Zcash style t-address.
}
}
@ -508,8 +503,14 @@ UniValue dumpwallet_impl(const UniValue& params, bool fHelp, bool fDumpZKeys)
// produce output
file << strprintf("# Wallet dump created by Komodo %s (%s)\n", CLIENT_BUILD, CLIENT_DATE);
file << strprintf("# * Created on %s\n", EncodeDumpTime(GetTime()));
file << strprintf("# * Best block at time of backup was %i (%s),\n", chainActive.Height(), chainActive.LastTip()->GetBlockHash().ToString());
file << strprintf("# mined on %s\n", EncodeDumpTime(chainActive.LastTip()->GetBlockTime()));
file << strprintf("# * Best block at time of backup was %i (%s),\n", chainActive.Height(), chainActive.Tip()->GetBlockHash().ToString());
file << strprintf("# mined on %s\n", EncodeDumpTime(chainActive.Tip()->GetBlockTime()));
{
HDSeed hdSeed;
pwalletMain->GetHDSeed(hdSeed);
file << strprintf("# HDSeed=%s fingerprint=%s", pwalletMain->GetHDChain().seedFp.GetHex(), hdSeed.Fingerprint().GetHex());
file << "\n";
}
file << "\n";
for (std::vector<std::pair<int64_t, CKeyID> >::const_iterator it = vKeyBirth.begin(); it != vKeyBirth.end(); it++) {
const CKeyID &keyid = it->second;
@ -529,18 +530,37 @@ UniValue dumpwallet_impl(const UniValue& params, bool fHelp, bool fDumpZKeys)
file << "\n";
if (fDumpZKeys) {
std::set<libzcash::SproutPaymentAddress> addresses;
pwalletMain->GetSproutPaymentAddresses(addresses);
std::set<libzcash::SproutPaymentAddress> sproutAddresses;
pwalletMain->GetSproutPaymentAddresses(sproutAddresses);
file << "\n";
file << "# Zkeys\n";
file << "\n";
for (auto addr : addresses ) {
for (auto addr : sproutAddresses) {
libzcash::SproutSpendingKey key;
if (pwalletMain->GetSproutSpendingKey(addr, key)) {
std::string strTime = EncodeDumpTime(pwalletMain->mapZKeyMetadata[addr].nCreateTime);
std::string strTime = EncodeDumpTime(pwalletMain->mapSproutZKeyMetadata[addr].nCreateTime);
file << strprintf("%s %s # zaddr=%s\n", EncodeSpendingKey(key), strTime, EncodePaymentAddress(addr));
}
}
std::set<libzcash::SaplingPaymentAddress> saplingAddresses;
pwalletMain->GetSaplingPaymentAddresses(saplingAddresses);
file << "\n";
file << "# Sapling keys\n";
file << "\n";
for (auto addr : saplingAddresses) {
libzcash::SaplingExtendedSpendingKey extsk;
if (pwalletMain->GetSaplingExtendedSpendingKey(addr, extsk)) {
auto ivk = extsk.expsk.full_viewing_key().in_viewing_key();
CKeyMetadata keyMeta = pwalletMain->mapSaplingZKeyMetadata[ivk];
std::string strTime = EncodeDumpTime(keyMeta.nCreateTime);
// Keys imported with z_importkey do not have zip32 metadata
if (keyMeta.hdKeypath.empty() || keyMeta.seedFp.IsNull()) {
file << strprintf("%s %s # zaddr=%s\n", EncodeSpendingKey(extsk), strTime, EncodePaymentAddress(addr));
} else {
file << strprintf("%s %s %s %s # zaddr=%s\n", EncodeSpendingKey(extsk), strTime, keyMeta.hdKeypath, keyMeta.seedFp.GetHex(), EncodePaymentAddress(addr));
}
}
}
file << "\n";
}
@ -550,67 +570,6 @@ UniValue dumpwallet_impl(const UniValue& params, bool fHelp, bool fDumpZKeys)
return exportfilepath.string();
}
class AddSpendingKeyToWallet : public boost::static_visitor<bool>
{
private:
CWallet *m_wallet;
const Consensus::Params &params;
public:
AddSpendingKeyToWallet(CWallet *wallet, const Consensus::Params &params) :
m_wallet(wallet), params(params) {}
bool operator()(const libzcash::SproutSpendingKey &sk) const {
auto addr = sk.address();
// Don't throw error in case a key is already there
if (m_wallet->HaveSproutSpendingKey(addr)) {
return true;
} else {
m_wallet->MarkDirty();
if (!m_wallet-> AddZKey(sk)) {
throw JSONRPCError(RPC_WALLET_ERROR, "Error adding spending key to wallet");
}
m_wallet->mapZKeyMetadata[addr].nCreateTime = 1;
return false;
}
}
bool operator()(const libzcash::SaplingExtendedSpendingKey &sk) const {
auto fvk = sk.expsk.full_viewing_key();
auto ivk = fvk.in_viewing_key();
auto addr = sk.DefaultAddress();
{
// Don't throw error in case a key is already there
if (m_wallet->HaveSaplingSpendingKey(fvk)) {
return true;
} else {
m_wallet->MarkDirty();
if (!m_wallet-> AddSaplingZKey(sk, addr)) {
throw JSONRPCError(RPC_WALLET_ERROR, "Error adding spending key to wallet");
}
// Sapling addresses can't have been used in transactions prior to activation.
if (params.vUpgrades[Consensus::UPGRADE_SAPLING].nActivationHeight == Consensus::NetworkUpgrade::ALWAYS_ACTIVE) {
m_wallet->mapSaplingZKeyMetadata[ivk].nCreateTime = 1;
} else {
// Friday, 26 October 2018 00:00:00 GMT - definitely before Sapling activates
m_wallet->mapSaplingZKeyMetadata[ivk].nCreateTime = 1540512000;
}
return false;
}
}
}
bool operator()(const libzcash::InvalidEncoding& no) const {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid spending key");
}
};
UniValue z_importkey(const UniValue& params, bool fHelp)
{
@ -683,11 +642,14 @@ UniValue z_importkey(const UniValue& params, bool fHelp)
}
// Sapling support
auto keyAlreadyExists = boost::apply_visitor(
AddSpendingKeyToWallet(pwalletMain, Params().GetConsensus()), spendingkey);
if (keyAlreadyExists && fIgnoreExistingKey) {
auto addResult = boost::apply_visitor(AddSpendingKeyToWallet(pwalletMain, Params().GetConsensus()), spendingkey);
if (addResult == KeyAlreadyExists && fIgnoreExistingKey) {
return NullUniValue;
}
pwalletMain->MarkDirty();
if (addResult == KeyNotAdded) {
throw JSONRPCError(RPC_WALLET_ERROR, "Error adding spending key to wallet");
}
// whenever a key is imported, we need to scan the whole chain
pwalletMain->nTimeFirstKey = 1; // 0 would be considered 'no value'

6
src/wallet/rpcwallet.cpp

@ -2574,7 +2574,8 @@ UniValue getwalletinfo(const UniValue& params, bool fHelp)
" \"keypoololdest\": xxxxxx, (numeric) the timestamp (seconds since GMT epoch) of the oldest pre-generated key in the key pool\n"
" \"keypoolsize\": xxxx, (numeric) how many new keys are pre-generated\n"
" \"unlocked_until\": ttt, (numeric) the timestamp in seconds since epoch (midnight Jan 1 1970 GMT) that the wallet is unlocked for transfers, or 0 if the wallet is locked\n"
" \"paytxfee\": x.xxxx, (numeric) the transaction fee configuration, set in KMD/KB\n"
" \"paytxfee\": x.xxxx, (numeric) the transaction fee configuration, set in " + CURRENCY_UNIT + "/kB\n"
" \"seedfp\": \"uint256\", (string) the BLAKE2b-256 hash of the HD seed\n"
"}\n"
"\nExamples:\n"
+ HelpExampleCli("getwalletinfo", "")
@ -2594,6 +2595,9 @@ UniValue getwalletinfo(const UniValue& params, bool fHelp)
if (pwalletMain->IsCrypted())
obj.push_back(Pair("unlocked_until", nWalletUnlockTime));
obj.push_back(Pair("paytxfee", ValueFromAmount(payTxFee.GetFeePerK())));
uint256 seedFp = pwalletMain->GetHDChain().seedFp;
if (!seedFp.IsNull())
obj.push_back(Pair("seedfp", seedFp.GetHex()));
return obj;
}

94
src/wallet/wallet.cpp

@ -14,6 +14,7 @@
#include "key_io.h"
#include "main.h"
#include "net.h"
#include "rpc/protocol.h"
#include "script/script.h"
#include "script/sign.h"
#include "timedata.h"
@ -91,7 +92,7 @@ const CWalletTx* CWallet::GetWalletTx(const uint256& hash) const
// Generate a new spending key and return its public payment address
libzcash::PaymentAddress CWallet::GenerateNewZKey()
{
AssertLockHeld(cs_wallet); // mapZKeyMetadata
AssertLockHeld(cs_wallet); // mapSproutZKeyMetadata
// TODO: Add Sapling support
auto k = SproutSpendingKey::random();
auto addr = k.address();
@ -102,10 +103,10 @@ libzcash::PaymentAddress CWallet::GenerateNewZKey()
// Create new metadata
int64_t nCreationTime = GetTime();
mapZKeyMetadata[addr] = CKeyMetadata(nCreationTime);
mapSproutZKeyMetadata[addr] = CKeyMetadata(nCreationTime);
if (!AddZKey(k))
throw std::runtime_error("CWallet::GenerateNewZKey(): AddZKey failed");
if (!AddSproutZKey(k))
throw std::runtime_error("CWallet::GenerateNewZKey(): AddSproutZKey failed");
return addr;
}
@ -124,18 +125,21 @@ SaplingPaymentAddress CWallet::GenerateNewSaplingZKey()
throw std::runtime_error("CWallet::GenerateNewSaplingZKey(): HD seed not found");
auto m = libzcash::SaplingExtendedSpendingKey::Master(seed);
uint32_t bip44CoinType = Params().BIP44CoinType();
// We use a fixed keypath scheme of m/32'/coin_type'/account'
// Derive m/32'
auto m_32h = m.Derive(32 | ZIP32_HARDENED_KEY_LIMIT);
// Derive m/32'/coin_type'
auto m_32h_cth = m_32h.Derive(Params().BIP44CoinType() | ZIP32_HARDENED_KEY_LIMIT);
auto m_32h_cth = m_32h.Derive(bip44CoinType | ZIP32_HARDENED_KEY_LIMIT);
// Derive account key at next index, skip keys already known to the wallet
libzcash::SaplingExtendedSpendingKey xsk;
do
{
xsk = m_32h_cth.Derive(hdChain.saplingAccountCounter | ZIP32_HARDENED_KEY_LIMIT);
metadata.hdKeypath = "m/32'/" + std::to_string(bip44CoinType) + "'/" + std::to_string(hdChain.saplingAccountCounter) + "'";
metadata.seedFp = hdChain.seedFp;
// Increment childkey index
hdChain.saplingAccountCounter++;
} while (HaveSaplingSpendingKey(xsk.expsk.full_viewing_key()));
@ -177,9 +181,9 @@ bool CWallet::AddSaplingZKey(
// Add spending key to keystore and persist to disk
bool CWallet::AddZKey(const libzcash::SproutSpendingKey &key)
bool CWallet::AddSproutZKey(const libzcash::SproutSpendingKey &key)
{
AssertLockHeld(cs_wallet); // mapZKeyMetadata
AssertLockHeld(cs_wallet); // mapSproutZKeyMetadata
auto addr = key.address();
if (!CCryptoKeyStore::AddSproutSpendingKey(key))
@ -195,7 +199,7 @@ bool CWallet::AddZKey(const libzcash::SproutSpendingKey &key)
if (!IsCrypted()) {
return CWalletDB(strWalletFile).WriteZKey(addr,
key,
mapZKeyMetadata[addr]);
mapSproutZKeyMetadata[addr]);
}
return true;
}
@ -286,12 +290,12 @@ bool CWallet::AddCryptedSproutSpendingKey(
return pwalletdbEncryption->WriteCryptedZKey(address,
rk,
vchCryptedSecret,
mapZKeyMetadata[address]);
mapSproutZKeyMetadata[address]);
} else {
return CWalletDB(strWalletFile).WriteCryptedZKey(address,
rk,
vchCryptedSecret,
mapZKeyMetadata[address]);
mapSproutZKeyMetadata[address]);
}
}
return false;
@ -323,8 +327,8 @@ bool CWallet::LoadKeyMetadata(const CPubKey &pubkey, const CKeyMetadata &meta)
bool CWallet::LoadZKeyMetadata(const SproutPaymentAddress &addr, const CKeyMetadata &meta)
{
AssertLockHeld(cs_wallet); // mapZKeyMetadata
mapZKeyMetadata[addr] = meta;
AssertLockHeld(cs_wallet); // mapSproutZKeyMetadata
mapSproutZKeyMetadata[addr] = meta;
return true;
}
@ -4948,14 +4952,9 @@ boost::optional<libzcash::SpendingKey> GetSpendingKeyForPaymentAddress::operator
boost::optional<libzcash::SpendingKey> GetSpendingKeyForPaymentAddress::operator()(
const libzcash::SaplingPaymentAddress &zaddr) const
{
libzcash::SaplingIncomingViewingKey ivk;
libzcash::SaplingFullViewingKey fvk;
libzcash::SaplingExtendedSpendingKey sk;
if (m_wallet->GetSaplingIncomingViewingKey(zaddr, ivk) &&
m_wallet->GetSaplingFullViewingKey(ivk, fvk) &&
m_wallet->GetSaplingSpendingKey(fvk, sk)) {
return libzcash::SpendingKey(sk);
libzcash::SaplingExtendedSpendingKey extsk;
if (m_wallet->GetSaplingExtendedSpendingKey(zaddr, extsk)) {
return libzcash::SpendingKey(extsk);
} else {
return boost::none;
}
@ -4967,3 +4966,58 @@ boost::optional<libzcash::SpendingKey> GetSpendingKeyForPaymentAddress::operator
// Defaults to InvalidEncoding
return libzcash::SpendingKey();
}
SpendingKeyAddResult AddSpendingKeyToWallet::operator()(const libzcash::SproutSpendingKey &sk) const {
auto addr = sk.address();
if (log){
LogPrint("zrpc", "Importing zaddr %s...\n", EncodePaymentAddress(addr));
}
if (m_wallet->HaveSproutSpendingKey(addr)) {
return KeyAlreadyExists;
} else if (m_wallet-> AddSproutZKey(sk)) {
m_wallet->mapSproutZKeyMetadata[addr].nCreateTime = nTime;
return KeyAdded;
} else {
return KeyNotAdded;
}
}
SpendingKeyAddResult AddSpendingKeyToWallet::operator()(const libzcash::SaplingExtendedSpendingKey &sk) const {
auto fvk = sk.expsk.full_viewing_key();
auto ivk = fvk.in_viewing_key();
auto addr = sk.DefaultAddress();
{
if (log){
LogPrint("zrpc", "Importing zaddr %s...\n", EncodePaymentAddress(addr));
}
// Don't throw error in case a key is already there
if (m_wallet->HaveSaplingSpendingKey(fvk)) {
return KeyAlreadyExists;
} else {
if (!m_wallet-> AddSaplingZKey(sk, addr)) {
return KeyNotAdded;
}
// Sapling addresses can't have been used in transactions prior to activation.
if (params.vUpgrades[Consensus::UPGRADE_SAPLING].nActivationHeight == Consensus::NetworkUpgrade::ALWAYS_ACTIVE) {
m_wallet->mapSaplingZKeyMetadata[ivk].nCreateTime = nTime;
} else {
// 154051200 seconds from epoch is Friday, 26 October 2018 00:00:00 GMT - definitely before Sapling activates
m_wallet->mapSaplingZKeyMetadata[ivk].nCreateTime = std::max((int64_t) 154051200, nTime);
}
if (hdKeypath) {
m_wallet->mapSaplingZKeyMetadata[ivk].hdKeypath = hdKeypath.get();
}
if (seedFpStr) {
uint256 seedFp;
seedFp.SetHex(seedFpStr.get());
m_wallet->mapSaplingZKeyMetadata[ivk].seedFp = seedFp;
}
return KeyAdded;
}
}
}
SpendingKeyAddResult AddSpendingKeyToWallet::operator()(const libzcash::InvalidEncoding& no) const {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid spending key");
}

38
src/wallet/wallet.h

@ -846,7 +846,7 @@ public:
std::set<int64_t> setKeyPool;
std::map<CKeyID, CKeyMetadata> mapKeyMetadata;
std::map<libzcash::SproutPaymentAddress, CKeyMetadata> mapZKeyMetadata;
std::map<libzcash::SproutPaymentAddress, CKeyMetadata> mapSproutZKeyMetadata;
std::map<libzcash::SaplingIncomingViewingKey, CKeyMetadata> mapSaplingZKeyMetadata;
typedef std::map<unsigned int, CMasterKey> MasterKeyMap;
@ -1032,7 +1032,7 @@ public:
//! Generates a new zaddr
libzcash::PaymentAddress GenerateNewZKey();
//! Adds spending key to the store, and saves it to disk
bool AddZKey(const libzcash::SproutSpendingKey &key);
bool AddSproutZKey(const libzcash::SproutSpendingKey &key);
//! Adds spending key to the store, without saving it to disk (used by LoadWallet)
bool LoadZKey(const libzcash::SproutSpendingKey &key);
//! Load spending key metadata (used by LoadWallet)
@ -1429,4 +1429,38 @@ public:
}
};
enum SpendingKeyAddResult {
KeyAlreadyExists,
KeyAdded,
KeyNotAdded,
};
class AddSpendingKeyToWallet : public boost::static_visitor<SpendingKeyAddResult>
{
private:
CWallet *m_wallet;
const Consensus::Params &params;
int64_t nTime;
boost::optional<std::string> hdKeypath; // currently sapling only
boost::optional<std::string> seedFpStr; // currently sapling only
bool log;
public:
AddSpendingKeyToWallet(CWallet *wallet, const Consensus::Params &params) :
m_wallet(wallet), params(params), nTime(1), hdKeypath(boost::none), seedFpStr(boost::none), log(false) {}
AddSpendingKeyToWallet(
CWallet *wallet,
const Consensus::Params &params,
int64_t _nTime,
boost::optional<std::string> _hdKeypath,
boost::optional<std::string> _seedFp,
bool _log
) : m_wallet(wallet), params(params), nTime(_nTime), hdKeypath(_hdKeypath), seedFpStr(_seedFp), log(_log) {}
SpendingKeyAddResult operator()(const libzcash::SproutSpendingKey &sk) const;
SpendingKeyAddResult operator()(const libzcash::SaplingExtendedSpendingKey &sk) const;
SpendingKeyAddResult operator()(const libzcash::InvalidEncoding& no) const;
};
#endif // BITCOIN_WALLET_WALLET_H

15
src/wallet/walletdb.h

@ -77,9 +77,13 @@ public:
class CKeyMetadata
{
public:
static const int CURRENT_VERSION=1;
static const int VERSION_BASIC=1;
static const int VERSION_WITH_HDDATA=10;
static const int CURRENT_VERSION=VERSION_WITH_HDDATA;
int nVersion;
int64_t nCreateTime; // 0 means unknown
std::string hdKeypath; //optional HD/zip32 keypath
uint256 seedFp;
CKeyMetadata()
{
@ -87,7 +91,7 @@ public:
}
CKeyMetadata(int64_t nCreateTime_)
{
nVersion = CKeyMetadata::CURRENT_VERSION;
SetNull();
nCreateTime = nCreateTime_;
}
@ -97,12 +101,19 @@ public:
inline void SerializationOp(Stream& s, Operation ser_action) {
READWRITE(this->nVersion);
READWRITE(nCreateTime);
if (this->nVersion >= VERSION_WITH_HDDATA)
{
READWRITE(hdKeypath);
READWRITE(seedFp);
}
}
void SetNull()
{
nVersion = CKeyMetadata::CURRENT_VERSION;
nCreateTime = 0;
hdKeypath.clear();
seedFp.SetNull();
}
};

27
src/zcash/zip32.cpp

@ -8,6 +8,7 @@
#include "random.h"
#include "streams.h"
#include "version.h"
#include "zcash/prf.h"
#include <librustzcash.h>
#include <sodium.h>
@ -15,6 +16,9 @@
const unsigned char ZCASH_HD_SEED_FP_PERSONAL[crypto_generichash_blake2b_PERSONALBYTES] =
{'Z', 'c', 'a', 's', 'h', '_', 'H', 'D', '_', 'S', 'e', 'e', 'd', '_', 'F', 'P'};
const unsigned char ZCASH_TADDR_OVK_PERSONAL[crypto_generichash_blake2b_PERSONALBYTES] =
{'Z', 'c', 'T', 'a', 'd', 'd', 'r', 'T', 'o', 'S', 'a', 'p', 'l', 'i', 'n', 'g'};
HDSeed HDSeed::Random(size_t len)
{
assert(len >= 32);
@ -30,6 +34,29 @@ uint256 HDSeed::Fingerprint() const
return h.GetHash();
}
uint256 ovkForShieldingFromTaddr(HDSeed& seed) {
auto rawSeed = seed.RawSeed();
// I = BLAKE2b-512("ZcTaddrToSapling", seed)
crypto_generichash_blake2b_state state;
assert(crypto_generichash_blake2b_init_salt_personal(
&state,
NULL, 0, // No key.
64,
NULL, // No salt.
ZCASH_TADDR_OVK_PERSONAL) == 0);
crypto_generichash_blake2b_update(&state, rawSeed.data(), rawSeed.size());
auto intermediate = std::array<unsigned char, 64>();
crypto_generichash_blake2b_final(&state, intermediate.data(), 64);
// I_L = I[0..32]
uint256 intermediate_L;
memcpy(intermediate_L.begin(), intermediate.data(), 32);
// ovk = truncate_32(PRF^expand(I_L, [0x02]))
return PRF_ovk(intermediate_L);
}
namespace libzcash {
boost::optional<SaplingExtendedFullViewingKey> SaplingExtendedFullViewingKey::Derive(uint32_t i) const

3
src/zcash/zip32.h

@ -42,6 +42,9 @@ public:
}
};
// This is not part of ZIP 32, but is here because it's linked to the HD seed.
uint256 ovkForShieldingFromTaddr(HDSeed& seed);
namespace libzcash {
typedef blob88 diversifier_index_t;

Loading…
Cancel
Save