Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 25 additions & 3 deletions contrib/signet/miner
Original file line number Diff line number Diff line change
Expand Up @@ -332,7 +332,13 @@ class Generate:

return tmpl

def mine(self, bcli, wallets, grind_cmd, tmpl, reward_spk, poolid):
def mine(self, bcli, wallets, grind_cmd, tmpl, reward_spk, poolid, override_txs):
if override_txs is not None:
tmpl["transactions"] = [] # TODO: could keep them if there is enough block space left
for override_tx in override_txs:
# TODO: provide other fields as well
tmpl["transactions"].append({"data": override_tx})

psbt = generate_psbt(tmpl, reward_spk, blocktime=self.mine_time, poolid=poolid)
input_stream = os.linesep.join([psbt, "true", "ALL"]).encode('utf8')
if wallets:
Expand Down Expand Up @@ -416,6 +422,21 @@ def do_generate(args):
else:
prefer_cli = None

if args.custom_txs_file is not None:
if max_blocks != 1:
logging.error("--custom-txs-file is only allowed if a single block is mined")
return 1
override_txs = []
with open(args.custom_txs_file, 'r') as f:
for line in f:
line = line.strip()
if line.startswith('#'): # skip comment lines
continue
# TODO: sanity check input (deserialize to CTransaction?) to error early
override_txs.append(line)
else:
override_txs = None

poolid = get_poolid(args)
prefer_poolid = get_prefer_poolid(args)

Expand Down Expand Up @@ -479,7 +500,7 @@ def do_generate(args):
tmpl = None
if tmpl is not None:
logging.debug("Preferred GBT template: %s", tmpl)
block = gen.mine(args.bcli, wallets, args.grind_cmd, tmpl, reward_spk, poolid=prefer_poolid)
block = gen.mine(args.bcli, wallets, args.grind_cmd, tmpl, reward_spk, poolid=prefer_poolid, override_txs=override_txs)
if block is None:
logging.warning("Unable to mine preferred template")
else:
Expand All @@ -497,7 +518,7 @@ def do_generate(args):
continue

logging.debug("GBT template: %s", tmpl)
block = gen.mine(args.bcli, wallets, args.grind_cmd, tmpl, reward_spk, poolid=poolid)
block = gen.mine(args.bcli, wallets, args.grind_cmd, tmpl, reward_spk, poolid=poolid, override_txs=override_txs)
if block is None:
logging.error("Unable to mine template")
return 1
Expand Down Expand Up @@ -595,6 +616,7 @@ def main():
generate.add_argument("--max-interval", default=1800, type=int, help="Maximum interblock interval (seconds)")
generate.add_argument("--wallets", default=None, type=str, help="Wallets used for signing, separated by commas")
generate.add_argument("--nversion", default=None, type=str, help="Override block nVersion (specify as hex)")
generate.add_argument("--custom-txs-file", default=None, type=str, help="File with custom txs to override template (one tx per line, in hex)")

calibrate = cmds.add_parser("calibrate", help="Calibrate difficulty")
calibrate.set_defaults(fn=do_calibrate)
Expand Down
71 changes: 63 additions & 8 deletions test/functional/tool_signet_miner.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,12 @@
import sys
import time

from test_framework.blocktools import DIFF_1_N_BITS
from test_framework.blocktools import COINBASE_MATURITY, DIFF_1_N_BITS
from test_framework.key import ECKey
from test_framework.script_util import key_to_p2wpkh_script
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import assert_equal
from test_framework.wallet import MiniWallet
from test_framework.wallet_util import bytes_to_wif


Expand Down Expand Up @@ -41,14 +42,30 @@ def skip_test_if_missing_module(self):
self.skip_if_no_wallet()
self.skip_if_no_bitcoin_util()

def run_test(self):
node = self.nodes[0]
# import private key needed for signing block
node.importprivkey(bytes_to_wif(CHALLENGE_PRIVATE_KEY))
# TODO: this is a copy of `mine_block` below, the only difference
# being that `--max-blocks` is passed instead of `--set-block-time`
def mine_initial_blocks(self, node, num_blocks):
n_blocks = node.getblockcount()
base_dir = self.config["environment"]["SRCDIR"]
signet_miner_path = os.path.join(base_dir, "contrib", "signet", "miner")
subprocess.run([
sys.executable,
signet_miner_path,
f'--cli={node.cli.binary} -datadir={node.cli.datadir}',
'generate',
f'--address={self.wallet_addr}',
f'--grind-cmd={self.options.bitcoinutil} grind',
f'--nbits={DIFF_1_N_BITS:08x}',
f'--max-blocks={num_blocks}',
'--poolnum=99',
], check=True, stderr=subprocess.STDOUT)
assert_equal(node.getblockcount(), n_blocks + num_blocks)

# generate block with signet miner tool
def mine_block(self, node, custom_txs_file=None):
n_blocks = node.getblockcount()
base_dir = self.config["environment"]["SRCDIR"]
signet_miner_path = os.path.join(base_dir, "contrib", "signet", "miner")
custom_txs_arg = [f'--custom-txs-file={custom_txs_file}'] if custom_txs_file else []
subprocess.run([
sys.executable,
signet_miner_path,
Expand All @@ -59,8 +76,46 @@ def run_test(self):
f'--nbits={DIFF_1_N_BITS:08x}',
f'--set-block-time={int(time.time())}',
'--poolnum=99',
], check=True, stderr=subprocess.STDOUT)
assert_equal(node.getblockcount(), 1)
] + custom_txs_arg, check=True, stderr=subprocess.STDOUT)
assert_equal(node.getblockcount(), n_blocks + 1)

def run_test(self):
node = self.nodes[0]
# import private key needed for signing block
node.importprivkey(bytes_to_wif(CHALLENGE_PRIVATE_KEY))

self.log.info("Mine blocks to create spendable UTXOs (mature coinbase outputs)")
self.wallet = MiniWallet(node)
# translate MiniWallet address to Signet (due to different bech32 HRP)
self.wallet_addr = node.decodescript(self.wallet.get_output_script().hex())['address']

self.mine_initial_blocks(node, COINBASE_MATURITY + 5)
self.wallet.rescan_utxos()
assert len(self.wallet.get_utxos(include_immature_coinbase=False, mark_as_spent=False)) > 0

self.log.info("Mine block with overrided txs (provided by --custom-tx-file)")
# submit transaction to mempool, should be picked up by the miner
mempool_tx = self.wallet.send_self_transfer(from_node=node)
self.mine_block(node)
mined_block = node.getblock(node.getbestblockhash())
assert_equal(len(mined_block['tx']), 2) # coinbase + miniwallet tx
assert_equal(mined_block['tx'][1], mempool_tx['txid'])

# submit transaction to mempool, override with some custom (non-standard) txs
mempool_tx = self.wallet.send_self_transfer(from_node=node)
offband_txs = [self.wallet.create_self_transfer(target_vsize=333_000) for _ in range(3)]
custom_txs_file = os.path.join(self.options.tmpdir, "fancy_offband_txs.txt")
with open(custom_txs_file, 'w') as f:
f.write('# this is my fancy off-band transaction, please mine!\n')
for offband_tx in offband_txs:
f.write(offband_tx['hex'] + '\n')
self.mine_block(node, custom_txs_file)
mined_block = node.getblock(node.getbestblockhash())
assert mined_block['size'] > 990_000
assert_equal(len(mined_block['tx']), 1 + len(offband_txs))
assert mempool_tx['txid'] not in mined_block['tx']
for offband_tx in offband_txs:
assert offband_tx['txid'] in mined_block['tx']


if __name__ == "__main__":
Expand Down