mirror of https://github.com/zcash/zcash.git
226 lines
9.2 KiB
Python
Executable File
226 lines
9.2 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
# Copyright (c) 2020-2024 The Zcash developers
|
|
# Distributed under the MIT software license, see the accompanying
|
|
# file COPYING or https://www.opensource.org/licenses/mit-license.php .
|
|
|
|
from decimal import Decimal
|
|
|
|
from test_framework.test_framework import BitcoinTestFramework
|
|
from test_framework.blocktools import create_block, create_coinbase
|
|
from test_framework.mininode import (
|
|
fundingstream,
|
|
nuparams,
|
|
uint256_from_reversed_hex,
|
|
CTxOut,
|
|
COIN,
|
|
)
|
|
from test_framework.script import (
|
|
CScript,
|
|
OP_CHECKSIG, OP_DUP, OP_EQUAL, OP_EQUALVERIFY, OP_HASH160,
|
|
)
|
|
from test_framework.util import (
|
|
assert_equal,
|
|
bitcoind_processes,
|
|
connect_nodes,
|
|
start_node,
|
|
BLOSSOM_BRANCH_ID,
|
|
CANOPY_BRANCH_ID,
|
|
NU6_BRANCH_ID,
|
|
)
|
|
|
|
from base58 import b58decode_check
|
|
|
|
def redeem_script(addr):
|
|
raw_addr = b58decode_check(addr)
|
|
if len(raw_addr) == 22:
|
|
addr_hash = raw_addr[2:]
|
|
if raw_addr.startswith(b'\x1C\xBD') or raw_addr.startswith(b'\x1C\xBA'):
|
|
return CScript([OP_HASH160, addr_hash, OP_EQUAL])
|
|
elif raw_addr.startswith(b'\x1C\xB8') or raw_addr.startswith(b'\x1D\x25'):
|
|
return CScript([OP_DUP, OP_HASH160, addr_hash, OP_EQUALVERIFY, OP_CHECKSIG])
|
|
|
|
raise ValueError("unrecognized address type")
|
|
|
|
class CoinbaseFundingStreamsTest(BitcoinTestFramework):
|
|
def __init__(self):
|
|
super().__init__()
|
|
self.num_nodes = 2
|
|
self.cache_behavior = 'clean'
|
|
|
|
def start_node_with(self, index, extra_args=[]):
|
|
args = [
|
|
nuparams(BLOSSOM_BRANCH_ID, 2),
|
|
nuparams(CANOPY_BRANCH_ID, 3),
|
|
nuparams(NU6_BRANCH_ID, 4),
|
|
"-nurejectoldversions=false",
|
|
"-allowdeprecated=getnewaddress",
|
|
"-allowdeprecated=z_getnewaddress",
|
|
"-allowdeprecated=z_getbalance",
|
|
"-allowdeprecated=z_gettotalbalance",
|
|
]
|
|
return start_node(index, self.options.tmpdir, args + extra_args)
|
|
|
|
def setup_network(self, split=False):
|
|
self.nodes = []
|
|
self.nodes.append(self.start_node_with(0))
|
|
self.nodes.append(self.start_node_with(1))
|
|
connect_nodes(self.nodes[1], 0)
|
|
self.is_network_split = False
|
|
self.sync_all()
|
|
|
|
def run_test(self):
|
|
# Generate a shielded address for node 1 for miner rewards,
|
|
miner_addr = self.nodes[1].z_getnewaddress('sapling')
|
|
|
|
# Generate Sapling and transparent addresses (belonging to node 0) for funding stream rewards.
|
|
# For transparent funding stream outputs, strictly speaking the protocol spec only specifies
|
|
# paying to a P2SH multisig, but P2PKH funding addresses also work in zcashd.
|
|
fs_sapling = self.nodes[0].z_getnewaddress('sapling')
|
|
fs_transparent = self.nodes[0].getnewaddress()
|
|
|
|
print("Activating Blossom")
|
|
self.nodes[1].generate(2)
|
|
self.sync_all()
|
|
# We won't need to mine on node 1 from this point onward.
|
|
|
|
assert_equal(self.nodes[1].getblockchaininfo()['blocks'], 2)
|
|
|
|
# Restart both nodes with funding streams.
|
|
self.nodes[0].stop()
|
|
bitcoind_processes[0].wait()
|
|
self.nodes[1].stop()
|
|
bitcoind_processes[1].wait()
|
|
new_args = [
|
|
"-mineraddress=%s" % miner_addr,
|
|
"-minetolocalwallet=0",
|
|
fundingstream(0, 3, 4, [fs_sapling]), # FS_ZIP214_BP: 7%
|
|
fundingstream(1, 3, 4, [fs_sapling]), # FS_ZIP214_ZF: 5%
|
|
fundingstream(2, 3, 4, [fs_sapling]), # FS_ZIP214_MG: 8%
|
|
fundingstream(3, 4, 8, [fs_transparent, fs_transparent]), # FS_FPF_ZCG: 8%
|
|
fundingstream(4, 4, 8, ["DEFERRED_POOL", "DEFERRED_POOL"]), # FS_DEFERRED: 12%
|
|
]
|
|
self.nodes[0] = self.start_node_with(0, new_args)
|
|
self.nodes[1] = self.start_node_with(1, new_args)
|
|
connect_nodes(self.nodes[1], 0)
|
|
self.sync_all()
|
|
|
|
print("Activating Heartwood & Canopy")
|
|
self.nodes[0].generate(1)
|
|
self.sync_all()
|
|
|
|
assert_equal(self.nodes[1].getblockchaininfo()['blocks'], 3)
|
|
|
|
# All miner addresses belong to node 1; check balances
|
|
# Miner rewards are uniformly 5 zec per block (all test heights are below the first halving)
|
|
walletinfo = self.nodes[1].getwalletinfo()
|
|
assert_equal(Decimal(walletinfo['immature_balance']), Decimal('15'))
|
|
assert_equal(Decimal(walletinfo['balance']), Decimal('0'))
|
|
assert_equal(Decimal(self.nodes[1].z_getbalance(miner_addr, 0)), Decimal('5'))
|
|
assert_equal(Decimal(self.nodes[1].z_getbalance(miner_addr)), Decimal('5'))
|
|
|
|
# Check that the node 0 private balance has been augmented by the funding stream payment.
|
|
assert_equal(Decimal(self.nodes[0].z_getbalance(fs_sapling, 0)), Decimal('1.25'))
|
|
assert_equal(Decimal(self.nodes[0].z_getbalance(fs_sapling)), Decimal('1.25'))
|
|
assert_equal(Decimal(self.nodes[0].z_gettotalbalance()['private']), Decimal('1.25'))
|
|
assert_equal(Decimal(self.nodes[0].z_gettotalbalance()['total']), Decimal('1.25'))
|
|
|
|
print("Activating NU5 & NU6")
|
|
self.nodes[0].generate(1)
|
|
self.sync_all()
|
|
|
|
assert_equal(self.nodes[1].getblockchaininfo()['blocks'], 4)
|
|
|
|
# check that miner payments made it to node 1's wallet
|
|
walletinfo = self.nodes[1].getwalletinfo()
|
|
assert_equal(Decimal(walletinfo['immature_balance']), Decimal('15'))
|
|
assert_equal(Decimal(walletinfo['balance']), Decimal('0'))
|
|
assert_equal(Decimal(self.nodes[1].z_getbalance(miner_addr, 0)), Decimal('10'))
|
|
assert_equal(Decimal(self.nodes[1].z_getbalance(miner_addr)), Decimal('10'))
|
|
|
|
# Check that the node 0 *immature* transparent balance has been augmented by the funding
|
|
# stream payment:
|
|
# * 0.5 ZEC per block to fs_transparent
|
|
# * 0.75 ZEC per block to the lockbox
|
|
walletinfo = self.nodes[0].getwalletinfo()
|
|
assert_equal(Decimal(walletinfo['immature_balance']), Decimal('0.5'))
|
|
assert_equal(Decimal(walletinfo['shielded_balance']), Decimal('1.25'))
|
|
|
|
# Check that the node 0 lockbox balance has been augmented by the funding stream payment.
|
|
lastBlock = self.nodes[0].getblock('4')
|
|
def check_lockbox(blk, expected):
|
|
lockbox = next(elem for elem in blk['valuePools'] if elem['id'] == "lockbox")
|
|
assert_equal(Decimal(lockbox['chainValue']), expected)
|
|
check_lockbox(lastBlock, Decimal('0.75'))
|
|
|
|
# Invalidate the last block so that we can steal its `hashBlockCommitments` field.
|
|
self.nodes[0].invalidateblock(lastBlock['hash'])
|
|
self.nodes[1].invalidateblock(lastBlock['hash'])
|
|
self.sync_all()
|
|
|
|
assert_equal(self.nodes[1].getblockchaininfo()['blocks'], 3)
|
|
|
|
walletinfo = self.nodes[0].getwalletinfo()
|
|
assert_equal(Decimal(walletinfo['immature_balance']), Decimal('0'))
|
|
assert_equal(Decimal(walletinfo['shielded_balance']), Decimal('1.25'))
|
|
|
|
coinbaseTx = create_coinbase(
|
|
4,
|
|
after_blossom=True,
|
|
outputs=[CTxOut(int(0.5 * COIN), redeem_script(fs_transparent))],
|
|
lockboxvalue=int(0.75 * COIN),
|
|
)
|
|
block = create_block(
|
|
uint256_from_reversed_hex(lastBlock['previousblockhash']),
|
|
coinbaseTx,
|
|
lastBlock['time'],
|
|
hashBlockCommitments=uint256_from_reversed_hex(lastBlock['blockcommitments']),
|
|
)
|
|
|
|
# A coinbase transaction that claims less than the maximum amount should be invalid after NU6.
|
|
block.vtx[0].vout[0].nValue -= 1 # too little
|
|
block.vtx[0].rehash()
|
|
block.rehash()
|
|
block.solve()
|
|
assert_equal(self.nodes[0].submitblock(block.serialize().hex()), 'bad-cb-not-exact')
|
|
|
|
# A coinbase transaction that claims more than the maximum amount should also be invalid
|
|
# (with a different error).
|
|
block.vtx[0].vout[0].nValue += 2 # too much
|
|
block.vtx[0].rehash()
|
|
block.rehash()
|
|
block.solve()
|
|
assert_equal(self.nodes[0].submitblock(block.serialize().hex()), 'bad-cb-amount')
|
|
|
|
# A coinbase transaction that claims exactly the maximum amount should be valid.
|
|
block.vtx[0].vout[0].nValue -= 1 # just right
|
|
block.vtx[0].rehash()
|
|
block.rehash()
|
|
block.solve()
|
|
assert_equal(self.nodes[0].submitblock(block.serialize().hex()), None)
|
|
self.sync_all()
|
|
|
|
assert_equal(self.nodes[1].getblockchaininfo()['blocks'], 4)
|
|
|
|
walletinfo = self.nodes[0].getwalletinfo()
|
|
assert_equal(Decimal(walletinfo['immature_balance']), Decimal('0.5'))
|
|
assert_equal(Decimal(walletinfo['shielded_balance']), Decimal('1.25'))
|
|
|
|
check_lockbox(self.nodes[0].getblock('4'), Decimal('0.75'))
|
|
|
|
print("Mining past the end of the funding streams")
|
|
self.nodes[0].generate(4)
|
|
self.sync_all()
|
|
|
|
assert_equal(self.nodes[1].getblockchaininfo()['blocks'], 8)
|
|
|
|
walletinfo = self.nodes[0].getwalletinfo()
|
|
assert_equal(Decimal(walletinfo['immature_balance']), 4 * Decimal('0.5'))
|
|
assert_equal(Decimal(walletinfo['shielded_balance']), Decimal('1.25'))
|
|
|
|
check_lockbox(self.nodes[0].getblock('7'), 4 * Decimal('0.75'))
|
|
check_lockbox(self.nodes[0].getblock('8'), 4 * Decimal('0.75'))
|
|
|
|
if __name__ == '__main__':
|
|
CoinbaseFundingStreamsTest().main()
|
|
|