Merge bitcoin/bitcoin#25625: test: add test for decoding PSBT with per-input preimage types

71a751f6c3 test: add test for decoding PSBT with per-input preimage types (Sebastian Falbesoner)
faf43378e2 refactor: move helper `random_bytes` to util library (Sebastian Falbesoner)
fdc1ca3896 test: add constants for PSBT key types (BIP 174) (Sebastian Falbesoner)
1b035c03f9 refactor: move PSBT(Map) helpers from signet miner to test framework (Sebastian Falbesoner)
7c0dfec2dd refactor: move `from_binary` helper from signet miner to test framework (Sebastian Falbesoner)
597a4b35f6 scripted-diff: rename `FromBinary` helper to `from_binary` (signet miner) (Sebastian Falbesoner)

Pull request description:

  This PR adds missing test coverage for the `decodepsbt` RPC in the case that a PSBT with on of the per-input preimage types (`PSBT_IN_RIPEMD160`, `PSBT_IN_SHA256`, `PSBT_IN_HASH160`, `PSBT_IN_HASH256`; see [BIP 174](https://github.com/bitcoin/bips/blob/master/bip-0174.mediawiki#Specification)) is passed. As preparation, the first four commits move the already existing helpers for (de)serialization of PSBTs and PSBTMaps from the signet miner to the test framework (in a new module `psbt.py`), which should be quite useful for further tests to easily create PSBTs.

ACKs for top commit:
  achow101:
    ACK 71a751f6c3

Tree-SHA512: 04f2671612d94029da2ac8dc15ff93c4c8fcb73fe0b8cf5970509208564df1f5e32319b53ae998dd6e544d37637a9b75609f27a3685da51f603f6ed0555635fb
This commit is contained in:
Andrew Chow
2022-07-20 16:36:21 -04:00
6 changed files with 212 additions and 98 deletions

View File

@@ -4,7 +4,6 @@
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
import argparse
import base64
import json
import logging
import math
@@ -15,14 +14,13 @@ import sys
import time
import subprocess
from io import BytesIO
PATH_BASE_CONTRIB_SIGNET = os.path.abspath(os.path.dirname(os.path.realpath(__file__)))
PATH_BASE_TEST_FUNCTIONAL = os.path.abspath(os.path.join(PATH_BASE_CONTRIB_SIGNET, "..", "..", "test", "functional"))
sys.path.insert(0, PATH_BASE_TEST_FUNCTIONAL)
from test_framework.blocktools import get_witness_script, script_BIP34_coinbase_height # noqa: E402
from test_framework.messages import CBlock, CBlockHeader, COutPoint, CTransaction, CTxIn, CTxInWitness, CTxOut, from_hex, deser_string, ser_compact_size, ser_string, ser_uint256, tx_from_hex # noqa: E402
from test_framework.messages import CBlock, CBlockHeader, COutPoint, CTransaction, CTxIn, CTxInWitness, CTxOut, from_binary, from_hex, ser_string, ser_uint256, tx_from_hex # noqa: E402
from test_framework.psbt import PSBT, PSBTMap, PSBT_GLOBAL_UNSIGNED_TX, PSBT_IN_FINAL_SCRIPTSIG, PSBT_IN_FINAL_SCRIPTWITNESS, PSBT_IN_NON_WITNESS_UTXO, PSBT_IN_SIGHASH_TYPE # noqa: E402
from test_framework.script import CScriptOp # noqa: E402
logging.basicConfig(
@@ -34,89 +32,6 @@ SIGNET_HEADER = b"\xec\xc7\xda\xa2"
PSBT_SIGNET_BLOCK = b"\xfc\x06signetb" # proprietary PSBT global field holding the block being signed
RE_MULTIMINER = re.compile("^(\d+)(-(\d+))?/(\d+)$")
# #### some helpers that could go into test_framework
# like from_hex, but without the hex part
def FromBinary(cls, stream):
"""deserialize a binary stream (or bytes object) into an object"""
# handle bytes object by turning it into a stream
was_bytes = isinstance(stream, bytes)
if was_bytes:
stream = BytesIO(stream)
obj = cls()
obj.deserialize(stream)
if was_bytes:
assert len(stream.read()) == 0
return obj
class PSBTMap:
"""Class for serializing and deserializing PSBT maps"""
def __init__(self, map=None):
self.map = map if map is not None else {}
def deserialize(self, f):
m = {}
while True:
k = deser_string(f)
if len(k) == 0:
break
v = deser_string(f)
if len(k) == 1:
k = k[0]
assert k not in m
m[k] = v
self.map = m
def serialize(self):
m = b""
for k,v in self.map.items():
if isinstance(k, int) and 0 <= k and k <= 255:
k = bytes([k])
m += ser_compact_size(len(k)) + k
m += ser_compact_size(len(v)) + v
m += b"\x00"
return m
class PSBT:
"""Class for serializing and deserializing PSBTs"""
def __init__(self):
self.g = PSBTMap()
self.i = []
self.o = []
self.tx = None
def deserialize(self, f):
assert f.read(5) == b"psbt\xff"
self.g = FromBinary(PSBTMap, f)
assert 0 in self.g.map
self.tx = FromBinary(CTransaction, self.g.map[0])
self.i = [FromBinary(PSBTMap, f) for _ in self.tx.vin]
self.o = [FromBinary(PSBTMap, f) for _ in self.tx.vout]
return self
def serialize(self):
assert isinstance(self.g, PSBTMap)
assert isinstance(self.i, list) and all(isinstance(x, PSBTMap) for x in self.i)
assert isinstance(self.o, list) and all(isinstance(x, PSBTMap) for x in self.o)
assert 0 in self.g.map
tx = FromBinary(CTransaction, self.g.map[0])
assert len(tx.vin) == len(self.i)
assert len(tx.vout) == len(self.o)
psbt = [x.serialize() for x in [self.g] + self.i + self.o]
return b"psbt\xff" + b"".join(psbt)
def to_base64(self):
return base64.b64encode(self.serialize()).decode("utf8")
@classmethod
def from_base64(cls, b64psbt):
return FromBinary(cls, base64.b64decode(b64psbt))
# #####
def create_coinbase(height, value, spk):
cb = CTransaction()
cb.vin = [CTxIn(COutPoint(0, 0xffffffff), script_BIP34_coinbase_height(height), 0xffffffff)]
@@ -159,11 +74,11 @@ def signet_txs(block, challenge):
def do_createpsbt(block, signme, spendme):
psbt = PSBT()
psbt.g = PSBTMap( {0: signme.serialize(),
psbt.g = PSBTMap( {PSBT_GLOBAL_UNSIGNED_TX: signme.serialize(),
PSBT_SIGNET_BLOCK: block.serialize()
} )
psbt.i = [ PSBTMap( {0: spendme.serialize(),
3: bytes([1,0,0,0])})
psbt.i = [ PSBTMap( {PSBT_IN_NON_WITNESS_UTXO: spendme.serialize(),
PSBT_IN_SIGHASH_TYPE: bytes([1,0,0,0])})
]
psbt.o = [ PSBTMap() ]
return psbt.to_base64()
@@ -175,10 +90,10 @@ def do_decode_psbt(b64psbt):
assert len(psbt.tx.vout) == 1
assert PSBT_SIGNET_BLOCK in psbt.g.map
scriptSig = psbt.i[0].map.get(7, b"")
scriptWitness = psbt.i[0].map.get(8, b"\x00")
scriptSig = psbt.i[0].map.get(PSBT_IN_FINAL_SCRIPTSIG, b"")
scriptWitness = psbt.i[0].map.get(PSBT_IN_FINAL_SCRIPTWITNESS, b"\x00")
return FromBinary(CBlock, psbt.g.map[PSBT_SIGNET_BLOCK]), ser_string(scriptSig) + scriptWitness
return from_binary(CBlock, psbt.g.map[PSBT_SIGNET_BLOCK]), ser_string(scriptSig) + scriptWitness
def finish_block(block, signet_solution, grind_cmd):
block.vtx[0].vout[-1].scriptPubKey += CScriptOp.encode_op_pushdata(SIGNET_HEADER + signet_solution)