|
7 | 7 | import pathlib |
8 | 8 | import random |
9 | 9 | import signal |
| 10 | +import struct |
10 | 11 | import sys |
11 | 12 | import tempfile |
12 | 13 | import threading |
13 | 14 | from time import sleep |
14 | 15 |
|
15 | 16 | from kubernetes import client, config |
| 17 | +from kubernetes.stream import stream |
16 | 18 | from ln_framework.ln import CLN, LND, LNNode |
17 | 19 | from test_framework.authproxy import AuthServiceProxy |
| 20 | +from test_framework.blocktools import get_witness_script, script_BIP34_coinbase_height |
| 21 | +from test_framework.messages import ( |
| 22 | + CBlock, |
| 23 | + CBlockHeader, |
| 24 | + COutPoint, |
| 25 | + CTransaction, |
| 26 | + CTxIn, |
| 27 | + CTxInWitness, |
| 28 | + CTxOut, |
| 29 | + from_binary, |
| 30 | + from_hex, |
| 31 | + ser_string, |
| 32 | + ser_uint256, |
| 33 | + tx_from_hex, |
| 34 | +) |
18 | 35 | from test_framework.p2p import NetworkThread |
| 36 | +from test_framework.psbt import ( |
| 37 | + PSBT, |
| 38 | + PSBT_GLOBAL_UNSIGNED_TX, |
| 39 | + PSBT_IN_FINAL_SCRIPTSIG, |
| 40 | + PSBT_IN_FINAL_SCRIPTWITNESS, |
| 41 | + PSBT_IN_NON_WITNESS_UTXO, |
| 42 | + PSBT_IN_SIGHASH_TYPE, |
| 43 | + PSBTMap, |
| 44 | +) |
| 45 | +from test_framework.script import CScriptOp |
19 | 46 | from test_framework.test_framework import ( |
20 | 47 | TMPDIR_PREFIX, |
21 | 48 | BitcoinTestFramework, |
|
24 | 51 | from test_framework.test_node import TestNode |
25 | 52 | from test_framework.util import PortSeed, get_rpc_proxy |
26 | 53 |
|
| 54 | +SIGNET_HEADER = b"\xec\xc7\xda\xa2" |
| 55 | +PSBT_SIGNET_BLOCK = ( |
| 56 | + b"\xfc\x06signetb" # proprietary PSBT global field holding the block being signed |
| 57 | +) |
| 58 | + |
27 | 59 | NAMESPACE = None |
28 | 60 | pods = client.V1PodList(items=[]) |
29 | 61 | cmaps = client.V1ConfigMapList(items=[]) |
@@ -502,3 +534,144 @@ def connect_nodes(self, a, b, *, peer_advertises_v2=None, wait_for_connect: bool |
502 | 534 | ) |
503 | 535 | == to_num_peers |
504 | 536 | ) |
| 537 | + |
| 538 | + def generatetoaddress(self, generator, n, addr, sync_fun=None, **kwargs): |
| 539 | + if generator.chain == "regtest": |
| 540 | + blocks = generator.generatetoaddress(n, addr, invalid_call=False, **kwargs) |
| 541 | + sync_fun() if sync_fun else self.sync_all() |
| 542 | + return blocks |
| 543 | + if generator.chain == "signet": |
| 544 | + mined_blocks = 0 |
| 545 | + block_hashes = [] |
| 546 | + |
| 547 | + def bcli(method, *args, **kwargs): |
| 548 | + return generator.__getattr__(method)(*args, **kwargs) |
| 549 | + |
| 550 | + while mined_blocks < n: |
| 551 | + # gbt |
| 552 | + tmpl = bcli("getblocktemplate", {"rules": ["signet", "segwit"]}) |
| 553 | + # address for reward |
| 554 | + reward_spk = bytes.fromhex(bcli("getaddressinfo", addr)["scriptPubKey"]) |
| 555 | + # create coinbase tx |
| 556 | + cbtx = CTransaction() |
| 557 | + cbtx.vin = [ |
| 558 | + CTxIn( |
| 559 | + COutPoint(0, 0xFFFFFFFF), |
| 560 | + script_BIP34_coinbase_height(tmpl["height"]), |
| 561 | + 0xFFFFFFFF, |
| 562 | + ) |
| 563 | + ] |
| 564 | + cbtx.vout = [CTxOut(tmpl["coinbasevalue"], reward_spk)] |
| 565 | + cbtx.vin[0].nSequence = 2**32 - 2 |
| 566 | + cbtx.rehash() |
| 567 | + # assemble block |
| 568 | + block = CBlock() |
| 569 | + block.nVersion = tmpl["version"] |
| 570 | + block.hashPrevBlock = int(tmpl["previousblockhash"], 16) |
| 571 | + block.nTime = tmpl["curtime"] |
| 572 | + if block.nTime < tmpl["mintime"]: |
| 573 | + block.nTime = tmpl["mintime"] |
| 574 | + block.nBits = int(tmpl["bits"], 16) |
| 575 | + block.nNonce = 0 |
| 576 | + block.vtx = [cbtx] + [tx_from_hex(t["data"]) for t in tmpl["transactions"]] |
| 577 | + witnonce = 0 |
| 578 | + witroot = block.calc_witness_merkle_root() |
| 579 | + cbwit = CTxInWitness() |
| 580 | + cbwit.scriptWitness.stack = [ser_uint256(witnonce)] |
| 581 | + block.vtx[0].wit.vtxinwit = [cbwit] |
| 582 | + block.vtx[0].vout.append(CTxOut(0, bytes(get_witness_script(witroot, witnonce)))) |
| 583 | + # create signet txs for signing |
| 584 | + signet_spk = tmpl["signet_challenge"] |
| 585 | + signet_spk_bin = bytes.fromhex(signet_spk) |
| 586 | + txs = block.vtx[:] |
| 587 | + txs[0] = CTransaction(txs[0]) |
| 588 | + txs[0].vout[-1].scriptPubKey += CScriptOp.encode_op_pushdata(SIGNET_HEADER) |
| 589 | + hashes = [] |
| 590 | + for tx in txs: |
| 591 | + tx.rehash() |
| 592 | + hashes.append(ser_uint256(tx.sha256)) |
| 593 | + mroot = block.get_merkle_root(hashes) |
| 594 | + sd = b"" |
| 595 | + sd += struct.pack("<i", block.nVersion) |
| 596 | + sd += ser_uint256(block.hashPrevBlock) |
| 597 | + sd += ser_uint256(mroot) |
| 598 | + sd += struct.pack("<I", block.nTime) |
| 599 | + to_spend = CTransaction() |
| 600 | + to_spend.nVersion = 0 |
| 601 | + to_spend.nLockTime = 0 |
| 602 | + to_spend.vin = [ |
| 603 | + CTxIn(COutPoint(0, 0xFFFFFFFF), b"\x00" + CScriptOp.encode_op_pushdata(sd), 0) |
| 604 | + ] |
| 605 | + to_spend.vout = [CTxOut(0, signet_spk_bin)] |
| 606 | + to_spend.rehash() |
| 607 | + spend = CTransaction() |
| 608 | + spend.nVersion = 0 |
| 609 | + spend.nLockTime = 0 |
| 610 | + spend.vin = [CTxIn(COutPoint(to_spend.sha256, 0), b"", 0)] |
| 611 | + spend.vout = [CTxOut(0, b"\x6a")] |
| 612 | + # create PSBT for miner wallet signing |
| 613 | + psbt = PSBT() |
| 614 | + psbt.g = PSBTMap( |
| 615 | + { |
| 616 | + PSBT_GLOBAL_UNSIGNED_TX: spend.serialize(), |
| 617 | + PSBT_SIGNET_BLOCK: block.serialize(), |
| 618 | + } |
| 619 | + ) |
| 620 | + psbt.i = [ |
| 621 | + PSBTMap( |
| 622 | + { |
| 623 | + PSBT_IN_NON_WITNESS_UTXO: to_spend.serialize(), |
| 624 | + PSBT_IN_SIGHASH_TYPE: bytes([1, 0, 0, 0]), |
| 625 | + } |
| 626 | + ) |
| 627 | + ] |
| 628 | + psbt.o = [PSBTMap()] |
| 629 | + psbt = psbt.to_base64() |
| 630 | + # sign PSBT |
| 631 | + psbt_signed = bcli("walletprocesspsbt", psbt=psbt, sign=True, sighashtype="ALL") |
| 632 | + if not psbt_signed.get("complete", False): |
| 633 | + self.log.error("PSBT signing failed, aborting...") |
| 634 | + return block_hashes |
| 635 | + # decode signed PSBT |
| 636 | + signed_psbt = PSBT.from_base64(psbt_signed["psbt"]) |
| 637 | + scriptSig = signed_psbt.i[0].map.get(PSBT_IN_FINAL_SCRIPTSIG, b"") |
| 638 | + scriptWitness = signed_psbt.i[0].map.get(PSBT_IN_FINAL_SCRIPTWITNESS, b"\x00") |
| 639 | + signed_block = from_binary(CBlock, signed_psbt.g.map[PSBT_SIGNET_BLOCK]) |
| 640 | + signet_solution = ser_string(scriptSig) + scriptWitness |
| 641 | + # finish block |
| 642 | + signed_block.vtx[0].vout[-1].scriptPubKey += CScriptOp.encode_op_pushdata( |
| 643 | + SIGNET_HEADER + signet_solution |
| 644 | + ) |
| 645 | + signed_block.vtx[0].rehash() |
| 646 | + signed_block.hashMerkleRoot = signed_block.calc_merkle_root() |
| 647 | + try: |
| 648 | + headhex = CBlockHeader.serialize(signed_block).hex() |
| 649 | + cmd = ["bitcoin-util", "grind", headhex] |
| 650 | + newheadhex = stream( |
| 651 | + sclient.connect_get_namespaced_pod_exec, |
| 652 | + name=generator.tank, |
| 653 | + namespace=NAMESPACE, |
| 654 | + command=cmd, |
| 655 | + stderr=True, |
| 656 | + stdin=False, |
| 657 | + stdout=True, |
| 658 | + tty=False, |
| 659 | + ) |
| 660 | + if "not found" in newheadhex: |
| 661 | + raise Exception(newheadhex) |
| 662 | + newhead = from_hex(CBlockHeader(), newheadhex.strip()) |
| 663 | + signed_block.nNonce = newhead.nNonce |
| 664 | + signed_block.rehash() |
| 665 | + except Exception as e: |
| 666 | + self.log.info( |
| 667 | + f"Error grinding signet PoW with bitcoin-util in {generator.tank}: {e}".strip() |
| 668 | + ) |
| 669 | + self.log.info(" re-attempting with a single python thread...") |
| 670 | + signed_block.solve() |
| 671 | + # submit block |
| 672 | + bcli("submitblock", signed_block.serialize().hex()) |
| 673 | + block_hashes.append(signed_block.hash) |
| 674 | + mined_blocks += 1 |
| 675 | + self.log.info(f"Generated {mined_blocks} signet blocks") |
| 676 | + |
| 677 | + return block_hashes |
0 commit comments