Latest Zcash updates
This commit is contained in:
@@ -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'
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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
Executable file
192
qa/rpc-tests/p2p_nu_peer_management.py
Executable file
@@ -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
Executable file
46
qa/rpc-tests/signrawtransaction_offline.py
Executable file
@@ -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()
|
||||
@@ -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))
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
mytxid = wait_and_assert_operationid_status(self.nodes[2], self.nodes[2].z_sendmany(mytaddr, recipients))
|
||||
|
||||
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)
|
||||
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)
|
||||
|
||||
wait_and_assert_operationid_status(self.nodes[2], 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
|
||||
|
||||
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
Executable file
82
qa/rpc-tests/wallet_import_export.py
Executable file
@@ -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()
|
||||
@@ -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()}, {
|
||||
|
||||
@@ -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"]
|
||||
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(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)
|
||||
# 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)
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
30
src/net.cpp
30
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;
|
||||
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 Overwinter protocol version
|
||||
BOOST_FOREACH(const CNodeRef &node, vEvictionCandidates) {
|
||||
if (node->nVersion < params.vUpgrades[Consensus::UPGRADE_SAPLING].nProtocolVersion) {
|
||||
vTmpEvictionCandidates.push_back(node);
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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 ¶ms;
|
||||
public:
|
||||
AddSpendingKeyToWallet(CWallet *wallet, const Consensus::Params ¶ms) :
|
||||
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'
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
@@ -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 ¶ms;
|
||||
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 ¶ms) :
|
||||
m_wallet(wallet), params(params), nTime(1), hdKeypath(boost::none), seedFpStr(boost::none), log(false) {}
|
||||
AddSpendingKeyToWallet(
|
||||
CWallet *wallet,
|
||||
const Consensus::Params ¶ms,
|
||||
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
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user