From 5777f3e15cf409eac7e5ac1c3a25c7ce735eb4d6 Mon Sep 17 00:00:00 2001 From: Lex Berezhny Date: Fri, 4 Mar 2022 10:53:44 -0500 Subject: [PATCH] wip --- lbry/wallet/orchstr8/node.py | 5 +++++ lbry/wallet/script.py | 20 ++++++++++++++++++- lbry/wallet/transaction.py | 16 +++++++++++++++ .../blockchain/test_account_commands.py | 17 +++++++++++++++- 4 files changed, 56 insertions(+), 2 deletions(-) diff --git a/lbry/wallet/orchstr8/node.py b/lbry/wallet/orchstr8/node.py index 640a74cfb..70a157b49 100644 --- a/lbry/wallet/orchstr8/node.py +++ b/lbry/wallet/orchstr8/node.py @@ -679,6 +679,11 @@ class LBCWalletNode: def get_raw_transaction(self, txid): return self._cli_cmnd('getrawtransaction', txid, '1') + async def add_time_locked_address(self, height, address): + return json.loads( + await self._cli_cmnd('addtimelockedaddress', str(height), address) + ) + class HubProcess(asyncio.SubprocessProtocol): def __init__(self, ready, stopped): diff --git a/lbry/wallet/script.py b/lbry/wallet/script.py index 74e23aa25..dcf3cf10a 100644 --- a/lbry/wallet/script.py +++ b/lbry/wallet/script.py @@ -17,6 +17,7 @@ OP_HASH160 = 0xa9 OP_EQUALVERIFY = 0x88 OP_CHECKSIG = 0xac OP_CHECKMULTISIG = 0xae +OP_CHECKLOCKTIMEVERIFY = 0xb1 OP_EQUAL = 0x87 OP_PUSHDATA1 = 0x4c OP_PUSHDATA2 = 0x4d @@ -364,12 +365,18 @@ class InputScript(Script): REDEEM_SCRIPT_HASH = Template('script_hash', ( OP_0, PUSH_MANY('signatures'), PUSH_SUBSCRIPT('script', REDEEM_SCRIPT) )) + REDEEM_TIME_LOCK = Template('timelock', ( + SMALL_INTEGER('height'), OP_CHECKLOCKTIMEVERIFY, OP_DROP, + # rest is identical to OutputScript.PAY_PUBKEY_HASH: + OP_DUP, OP_HASH160, PUSH_SINGLE('pubkey_hash'), OP_EQUALVERIFY, OP_CHECKSIG + )) templates = [ REDEEM_PUBKEY, REDEEM_PUBKEY_HASH, REDEEM_SCRIPT_HASH, - REDEEM_SCRIPT + REDEEM_SCRIPT, + REDEEM_TIME_LOCK ] @classmethod @@ -394,6 +401,17 @@ class InputScript(Script): 'pubkeys_count': len(pubkeys) }) + @classmethod + def redeem_time_lock(cls, height, pubkey_hash): + return cls(template=cls.REDEEM_SCRIPT, values={ + 'height': height, + 'pubkey_hash': pubkey_hash + }) + + @classmethod + def redeem_time_lock_from_script(cls, script: bytes): + return cls.from_source_with_template(script, cls.REDEEM_SCRIPT) + class OutputScript(Script): diff --git a/lbry/wallet/transaction.py b/lbry/wallet/transaction.py index 120482c55..59f31aec1 100644 --- a/lbry/wallet/transaction.py +++ b/lbry/wallet/transaction.py @@ -145,6 +145,12 @@ class Input(InputOutput): script = InputScript.redeem_pubkey_hash(cls.NULL_SIGNATURE, cls.NULL_PUBLIC_KEY) return cls(txo.ref, script) + @classmethod + def spend_time_lock(cls, txo: 'Output', script_source: bytes) -> 'Input': + """ Create an input to spend time lock script.""" + script = InputScript.redeem_time_lock_from_script(script_source) + return cls(txo.ref, script) + @property def amount(self) -> int: """ Amount this input adds to the transaction. """ @@ -937,6 +943,16 @@ class Transaction: data = Output.add_purchase_data(Purchase(claim_id)) return cls.create([], [payment, data], funding_accounts, change_account) + @classmethod + def spend_time_lock( + cls, time_locked_txo: Output, amount: int, holding_address: str, script_source: str, + funding_accounts: List['Account'], change_account: 'Account' + ): + ledger, _ = cls.ensure_all_have_same_ledger_and_wallet(funding_accounts, change_account) + txi = Input.spend_time_lock(time_locked_txo, unhexlify(script_source)) + txo = Output.pay_pubkey_hash(amount, ledger.address_to_hash160(holding_address)) + return cls.create([txi], [txo], funding_accounts, change_account) + @property def my_inputs(self): for txi in self.inputs: diff --git a/tests/integration/blockchain/test_account_commands.py b/tests/integration/blockchain/test_account_commands.py index 9209fe0c6..782384367 100644 --- a/tests/integration/blockchain/test_account_commands.py +++ b/tests/integration/blockchain/test_account_commands.py @@ -1,8 +1,9 @@ from binascii import unhexlify from lbry.testcase import CommandTestCase -from lbry.wallet.dewies import dewies_to_lbc +from lbry.wallet.dewies import dewies_to_lbc, lbc_to_dewies from lbry.wallet.account import DeterministicChannelKeyManager +from lbry.wallet.transaction import Transaction, Input, Output def extract(d, keys): @@ -289,3 +290,17 @@ class AccountManagement(CommandTestCase): self.assertTrue(channel2c.has_private_key) self.assertTrue(channel3c.has_private_key) + async def test_time_locked_transactions(self): + from lbry.wallet.script import InputScript + address = await self.account.receiving.get_or_create_usable_address() + redeem = await self.blockchain.add_time_locked_address(210, address) + tx = await self.daemon.jsonrpc_account_send('4.0', redeem['address']) + await self.confirm_tx(tx.id) + await self.blockchain.generate(10) + txi = Input.spend(tx.outputs[0]) + txi.script.source = unhexlify(redeem['redeemScript']) + txo = Output.pay_pubkey_hash(lbc_to_dewies('3.9'), self.ledger.address_to_hash160(address)) + new_tx = await Transaction.create([txi], [txo], self.wallet.accounts, self.wallet.default_account) + src = new_tx.raw + print(new_tx.raw) +