Decred CSV test.
This commit is contained in:
		
							parent
							
								
									ab472c04be
								
							
						
					
					
						commit
						74c7072926
					
				@ -21,8 +21,8 @@ from basicswap.util.crypto import (
 | 
			
		||||
from basicswap.util.extkey import ExtKeyPair
 | 
			
		||||
from basicswap.util.integer import encode_varint
 | 
			
		||||
from basicswap.interface.dcr.rpc import make_rpc_func
 | 
			
		||||
from .messages import CTransaction, SigHashType, TxSerializeType
 | 
			
		||||
from .script import push_script_data
 | 
			
		||||
from .messages import CTransaction, CTxOut, SigHashType, TxSerializeType
 | 
			
		||||
from .script import push_script_data, OP_HASH160, OP_EQUAL, OP_DUP, OP_EQUALVERIFY, OP_CHECKSIG
 | 
			
		||||
 | 
			
		||||
from coincurve.keys import (
 | 
			
		||||
    PrivateKey
 | 
			
		||||
@ -128,6 +128,14 @@ class DCRInterface(Secp256k1Interface):
 | 
			
		||||
    def nbK() -> int:  # No. of bytes requires to encode a public key
 | 
			
		||||
        return 33
 | 
			
		||||
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def txVersion() -> int:
 | 
			
		||||
        return 2
 | 
			
		||||
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def txoType():
 | 
			
		||||
        return CTxOut
 | 
			
		||||
 | 
			
		||||
    def __init__(self, coin_settings, network, swap_client=None):
 | 
			
		||||
        super().__init__(network)
 | 
			
		||||
        self._rpc_host = coin_settings.get('rpchost', '127.0.0.1')
 | 
			
		||||
@ -169,6 +177,9 @@ class DCRInterface(Secp256k1Interface):
 | 
			
		||||
        else:
 | 
			
		||||
            self.rpc('getblockchaininfo')
 | 
			
		||||
 | 
			
		||||
    def getChainHeight(self) -> int:
 | 
			
		||||
        return self.rpc('getblockcount')
 | 
			
		||||
 | 
			
		||||
    def checkWallets(self) -> int:
 | 
			
		||||
        # Only one wallet possible?
 | 
			
		||||
        return 1
 | 
			
		||||
@ -239,3 +250,16 @@ class DCRInterface(Secp256k1Interface):
 | 
			
		||||
    def stripTxSignature(self, tx_bytes) -> bytes:
 | 
			
		||||
        tx = self.loadTx(tx_bytes)
 | 
			
		||||
        return tx.serialize(TxSerializeType.NoWitness)
 | 
			
		||||
 | 
			
		||||
    def getScriptDest(self, script: bytes) -> bytes:
 | 
			
		||||
        # P2SH
 | 
			
		||||
        script_hash = self.pkh(script)
 | 
			
		||||
        assert len(script_hash) == 20
 | 
			
		||||
 | 
			
		||||
        return OP_HASH160.to_bytes(1) + len(script_hash).to_bytes(1) + script_hash + OP_EQUAL.to_bytes(1)
 | 
			
		||||
 | 
			
		||||
    def getPubkeyHashDest(self, pkh: bytes) -> bytes:
 | 
			
		||||
        # P2PKH
 | 
			
		||||
 | 
			
		||||
        assert len(pkh) == 20
 | 
			
		||||
        return OP_DUP.to_bytes(1) + OP_HASH160.to_bytes(1) + len(pkh).to_bytes(1) + pkh + OP_EQUALVERIFY.to_bytes(1) + OP_CHECKSIG.to_bytes(1)
 | 
			
		||||
 | 
			
		||||
@ -32,9 +32,14 @@ class SignatureType(IntEnum):
 | 
			
		||||
    STSchnorrSecp256k1 = 2
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class COutpoint:
 | 
			
		||||
class COutPoint:
 | 
			
		||||
    __slots__ = ('hash', 'n', 'tree')
 | 
			
		||||
 | 
			
		||||
    def __init__(self, hash=0, n=0, tree=0):
 | 
			
		||||
        self.hash = hash
 | 
			
		||||
        self.n = n
 | 
			
		||||
        self.tree = tree
 | 
			
		||||
 | 
			
		||||
    def get_hash(self) -> bytes:
 | 
			
		||||
        return self.hash.to_bytes(32, 'big')
 | 
			
		||||
 | 
			
		||||
@ -43,15 +48,23 @@ class CTxIn:
 | 
			
		||||
    __slots__ = ('prevout', 'sequence',
 | 
			
		||||
                 'value_in', 'block_height', 'block_index', 'signature_script')  # Witness
 | 
			
		||||
 | 
			
		||||
    def __init__(self, tx=None):
 | 
			
		||||
    def __init__(self, prevout=COutPoint(), sequence=0):
 | 
			
		||||
        self.prevout = prevout
 | 
			
		||||
        self.sequence = sequence
 | 
			
		||||
        self.value_in = -1
 | 
			
		||||
        self.block_height = 0
 | 
			
		||||
        self.block_index = 0xffffffff
 | 
			
		||||
        self.signature_script = bytes()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class CTxOut:
 | 
			
		||||
    __slots__ = ('value', 'version', 'script_pubkey')
 | 
			
		||||
 | 
			
		||||
    def __init__(self, value=0, script_pubkey=bytes()):
 | 
			
		||||
        self.value = value
 | 
			
		||||
        self.version = 0
 | 
			
		||||
        self.script_pubkey = script_pubkey
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class CTransaction:
 | 
			
		||||
    __slots__ = ('hash', 'version', 'vin', 'vout', 'locktime', 'expiry')
 | 
			
		||||
@ -83,7 +96,7 @@ class CTransaction:
 | 
			
		||||
 | 
			
		||||
            for i in range(num_txin):
 | 
			
		||||
                txi = CTxIn()
 | 
			
		||||
                txi.prevout = COutpoint()
 | 
			
		||||
                txi.prevout = COutPoint()
 | 
			
		||||
                txi.prevout.hash = int.from_bytes(data[o:o + 32], 'little')
 | 
			
		||||
                o += 32
 | 
			
		||||
                txi.prevout.n = int.from_bytes(data[o:o + 4], 'little')
 | 
			
		||||
 | 
			
		||||
@ -9,9 +9,15 @@ OP_0 = 0x00
 | 
			
		||||
OP_DATA_1 = 0x01
 | 
			
		||||
OP_1NEGATE = 0x4f
 | 
			
		||||
OP_1 = 0x51
 | 
			
		||||
OP_EQUAL = 0x87
 | 
			
		||||
OP_PUSHDATA1 = 0x4c
 | 
			
		||||
OP_PUSHDATA2 = 0x4d
 | 
			
		||||
OP_PUSHDATA4 = 0x4e
 | 
			
		||||
OP_DUP = 0x76
 | 
			
		||||
OP_EQUALVERIFY = 0x88
 | 
			
		||||
OP_HASH160 = 0xa9
 | 
			
		||||
OP_CHECKSIG = 0xac
 | 
			
		||||
OP_CHECKSEQUENCEVERIFY = 0xb2
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def push_script_data(data_array: bytearray, data: bytes) -> None:
 | 
			
		||||
@ -28,7 +34,7 @@ def push_script_data(data_array: bytearray, data: bytes) -> None:
 | 
			
		||||
        return
 | 
			
		||||
 | 
			
		||||
    if len_data < OP_PUSHDATA1:
 | 
			
		||||
        data_array += bytes(((OP_DATA_1 - 1) + len_data,))
 | 
			
		||||
        data_array += len_data.to_bytes(1)
 | 
			
		||||
    elif len_data <= 0xff:
 | 
			
		||||
        data_array += bytes((OP_PUSHDATA1, len_data))
 | 
			
		||||
    elif len_data <= 0xffff:
 | 
			
		||||
@ -36,4 +42,5 @@ def push_script_data(data_array: bytearray, data: bytes) -> None:
 | 
			
		||||
    else:
 | 
			
		||||
        data_array += bytes((OP_PUSHDATA4,)) + len_data.to_bytes(4, 'little')
 | 
			
		||||
 | 
			
		||||
    print('[rm] data_array', (data_array + data).hex())
 | 
			
		||||
    data_array += data
 | 
			
		||||
 | 
			
		||||
@ -488,7 +488,7 @@ class NAVInterface(BTCInterface):
 | 
			
		||||
 | 
			
		||||
        return block_rv
 | 
			
		||||
 | 
			
		||||
    def getScriptScriptSig(self, script: bytes) -> bytearray:
 | 
			
		||||
    def getScriptScriptSig(self, script: bytes) -> bytes:
 | 
			
		||||
        return self.getP2SHP2WSHScriptSig(script)
 | 
			
		||||
 | 
			
		||||
    def getScriptDest(self, script):
 | 
			
		||||
 | 
			
		||||
@ -36,6 +36,8 @@ from tests.basicswap.util import (
 | 
			
		||||
 | 
			
		||||
from tests.basicswap.test_xmr import BaseTest, test_delay_event
 | 
			
		||||
from basicswap.interface.dcr import DCRInterface
 | 
			
		||||
from basicswap.interface.dcr.messages import CTransaction, CTxIn, COutPoint
 | 
			
		||||
from basicswap.interface.dcr.script import OP_CHECKSEQUENCEVERIFY, push_script_data
 | 
			
		||||
from bin.basicswap_run import startDaemon
 | 
			
		||||
 | 
			
		||||
logger = logging.getLogger()
 | 
			
		||||
@ -276,6 +278,7 @@ class Test(BaseTest):
 | 
			
		||||
        frtx = ci0.rpc_wallet('fundrawtransaction', [rtx, account_from])
 | 
			
		||||
 | 
			
		||||
        f_decoded = ci0.rpc_wallet('decoderawtransaction', [frtx['hex'], ])
 | 
			
		||||
        assert (f_decoded['version'] == 1)
 | 
			
		||||
 | 
			
		||||
        sfrtx = ci0.rpc_wallet('signrawtransaction', [frtx['hex']])
 | 
			
		||||
        s_decoded = ci0.rpc_wallet('decoderawtransaction', [sfrtx['hex'], ])
 | 
			
		||||
@ -330,6 +333,7 @@ class Test(BaseTest):
 | 
			
		||||
 | 
			
		||||
        # Set prevout value
 | 
			
		||||
        ctx = ci0.loadTx(tx_bytes_signed)
 | 
			
		||||
        assert (ctx.vout[0].version == 0)
 | 
			
		||||
        ctx.vin[0].value_in = ci0.make_int(prevout['amount'])
 | 
			
		||||
        tx_bytes_signed = ctx.serialize()
 | 
			
		||||
        assert (tx_bytes_signed.hex() == sfrtx['hex'])
 | 
			
		||||
@ -337,6 +341,80 @@ class Test(BaseTest):
 | 
			
		||||
        sent_txid = ci0.rpc_wallet('sendrawtransaction', [tx_bytes_signed.hex(), ])
 | 
			
		||||
        assert (len(sent_txid) == 64)
 | 
			
		||||
 | 
			
		||||
    def test_004_csv(self):
 | 
			
		||||
        logging.info('---------- Test {} csv'.format(self.test_coin_from.name))
 | 
			
		||||
        swap_clients = self.swap_clients
 | 
			
		||||
        ci0 = swap_clients[0].ci(self.test_coin_from)
 | 
			
		||||
 | 
			
		||||
        script = bytearray()
 | 
			
		||||
        push_script_data(script, bytes((3,)))
 | 
			
		||||
        script += OP_CHECKSEQUENCEVERIFY.to_bytes(1)
 | 
			
		||||
 | 
			
		||||
        script_dest = ci0.getScriptDest(script)
 | 
			
		||||
 | 
			
		||||
        prevout_amount: int = ci0.make_int(1.1)
 | 
			
		||||
        tx = CTransaction()
 | 
			
		||||
        tx.version = ci0.txVersion()
 | 
			
		||||
        tx.vout.append(ci0.txoType()(prevout_amount, script_dest))
 | 
			
		||||
        tx_hex = tx.serialize().hex()
 | 
			
		||||
        tx_decoded = ci0.rpc_wallet('decoderawtransaction', [tx_hex, ])
 | 
			
		||||
 | 
			
		||||
        utxo_pos = None
 | 
			
		||||
        script_address = None
 | 
			
		||||
        for i, txo in enumerate(tx_decoded['vout']):
 | 
			
		||||
            script_address = tx_decoded['vout'][0]['scriptPubKey']['addresses'][0]
 | 
			
		||||
            addr_info = ci0.rpc_wallet('validateaddress', [script_address,])
 | 
			
		||||
            if addr_info['isscript'] is True:
 | 
			
		||||
                utxo_pos = i
 | 
			
		||||
                break
 | 
			
		||||
        assert (utxo_pos is not None)
 | 
			
		||||
 | 
			
		||||
        accounts = ci0.rpc_wallet('listaccounts')
 | 
			
		||||
        for account_from in accounts:
 | 
			
		||||
            try:
 | 
			
		||||
                frtx = ci0.rpc_wallet('fundrawtransaction', [tx_hex, account_from])
 | 
			
		||||
                break
 | 
			
		||||
            except Exception as e:
 | 
			
		||||
                logging.warning('fundrawtransaction failed {}'.format(e))
 | 
			
		||||
        sfrtx = ci0.rpc_wallet('signrawtransaction', [frtx['hex']])
 | 
			
		||||
        sent_txid = ci0.rpc_wallet('sendrawtransaction', [sfrtx['hex'], ])
 | 
			
		||||
 | 
			
		||||
        tx_spend = CTransaction()
 | 
			
		||||
        tx_spend.version = ci0.txVersion()
 | 
			
		||||
 | 
			
		||||
        tx_spend.vin.append(CTxIn(COutPoint(int(sent_txid, 16), utxo_pos), sequence=3))
 | 
			
		||||
        tx_spend.vin[0].value_in = prevout_amount
 | 
			
		||||
        signature_script = bytearray()
 | 
			
		||||
        push_script_data(signature_script, script)
 | 
			
		||||
        tx_spend.vin[0].signature_script = signature_script
 | 
			
		||||
 | 
			
		||||
        addr_out = ci0.rpc_wallet('getnewaddress')
 | 
			
		||||
        pkh = ci0.decode_address(addr_out)[2:]
 | 
			
		||||
 | 
			
		||||
        tx_spend.vout.append(ci0.txoType()())
 | 
			
		||||
        tx_spend.vout[0].value = ci0.make_int(1.09)
 | 
			
		||||
        tx_spend.vout[0].script_pubkey = ci0.getPubkeyHashDest(pkh)
 | 
			
		||||
 | 
			
		||||
        tx_spend_hex = tx_spend.serialize().hex()
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            sent_spend_txid = ci0.rpc_wallet('sendrawtransaction', [tx_spend_hex, ])
 | 
			
		||||
        except Exception as e:
 | 
			
		||||
            assert ('transaction sequence locks on inputs not met' in str(e))
 | 
			
		||||
        else:
 | 
			
		||||
            assert False, 'Should fail'
 | 
			
		||||
 | 
			
		||||
        sent_spend_txid = None
 | 
			
		||||
        for i in range(20):
 | 
			
		||||
            try:
 | 
			
		||||
                sent_spend_txid = ci0.rpc_wallet('sendrawtransaction', [tx_spend_hex, ])
 | 
			
		||||
                break
 | 
			
		||||
            except Exception as e:
 | 
			
		||||
                logging.info('sendrawtransaction failed {}, height {}'.format(e, ci0.getChainHeight()))
 | 
			
		||||
            test_delay_event.wait(1)
 | 
			
		||||
 | 
			
		||||
        assert (sent_spend_txid is not None)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
if __name__ == '__main__':
 | 
			
		||||
    unittest.main()
 | 
			
		||||
 | 
			
		||||
@ -425,7 +425,7 @@ class Test(TestFunctions):
 | 
			
		||||
        tx.vout.append(ci.txoType()(ci.make_int(1.1), script_dest))
 | 
			
		||||
        tx_hex = ToHex(tx)
 | 
			
		||||
        tx_funded = self.callnoderpc('fundrawtransaction', [tx_hex])
 | 
			
		||||
        utxo_pos = 0 if tx_funded['changepos'] == 1 else 1
 | 
			
		||||
        utxo_pos: int = 0 if tx_funded['changepos'] == 1 else 1
 | 
			
		||||
        tx_signed = self.callnoderpc('signrawtransaction', [tx_funded['hex'], ])['hex']
 | 
			
		||||
        self.sync_blocks()
 | 
			
		||||
        txid = self.callnoderpc('sendrawtransaction', [tx_signed, ])
 | 
			
		||||
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user