2025-05-02 17:46:49 +01:00

148 lines
5.0 KiB
Python

import logging
import os
import shutil
from bip380.descriptors import Descriptor
from bip380.miniscript import SatisfactionMaterial
from test_framework.utils import (
BITCOIN_BACKEND_TYPE,
BitcoinBackendType,
UnixDomainSocketRpc,
TailableProc,
VERBOSE,
LOG_LEVEL,
LIANAD_PATH,
wait_for,
)
from test_framework.serializations import (
PSBT,
CTxInWitness,
CScriptWitness,
PSBT_IN_BIP32_DERIVATION,
PSBT_IN_PARTIAL_SIG,
PSBT_IN_FINAL_SCRIPTWITNESS,
)
class Lianad(TailableProc):
def __init__(
self, datadir, signer, multi_desc, bitcoin_backend, legacy_datadir=False
):
TailableProc.__init__(self, datadir, verbose=VERBOSE)
self.datadir = datadir
self.prefix = os.path.split(datadir)[-1]
self.signer = signer
self._poll_interval_secs = 1
self.multi_desc = multi_desc
self.receive_desc, self.change_desc = multi_desc.singlepath_descriptors()
self.conf_file = os.path.join(datadir, "config.toml")
self.cmd_line = [LIANAD_PATH, "--conf", f"{self.conf_file}"]
data_directory = os.path.join(datadir, "regtest")
socket_path = os.path.join(data_directory, "lianad_rpc")
self.rpc = UnixDomainSocketRpc(socket_path)
self.bitcoin_backend = bitcoin_backend
with open(self.conf_file, "w") as f:
if legacy_datadir:
f.write(f"data_dir = '{datadir}'\n")
else:
f.write(f"data_directory = '{data_directory}'\n")
f.write(f"log_level = '{LOG_LEVEL}'\n")
f.write(f'main_descriptor = "{multi_desc}"\n')
f.write("[bitcoin_config]\n")
f.write('network = "regtest"\n')
f.write(f"poll_interval_secs = {self._poll_interval_secs}\n")
bitcoin_backend.append_to_lianad_conf(self.conf_file)
@property
def poll_interval_secs(self):
"""Return the poll interval in seconds as defined in the config file."""
return self._poll_interval_secs
def finalize_psbt(self, psbt):
"""Create a valid witness for all inputs in the PSBT.
This will fail if the PSBT input does not contain enough material.
:param psbt: PSBT of the transaction to be finalized.
:returns: PSBT with finalized inputs.
"""
assert isinstance(psbt, PSBT)
# Create a witness for each input of the transaction.
for i, psbt_in in enumerate(psbt.i):
# First, gather the needed information from the PSBT input.
# 'hd_keypaths' is of the form {pubkey: (fingerprint, derivation index)}
fing_der = next(iter(psbt_in.map[PSBT_IN_BIP32_DERIVATION].values()))
raw_der_path = fing_der[4:]
der_path = [
int.from_bytes(raw_der_path[i : i + 4], byteorder="little", signed=True)
for i in range(0, len(raw_der_path), 4)
]
assert len(der_path) == 2
# Create a copy of the descriptor to derive it at the index used in this input.
# Then create a satisfaction for it using the signature we just created.
desc = Descriptor.from_str(
str(self.receive_desc if der_path[0] == 0 else self.change_desc)
)
desc.derive(der_path[1])
sat_material = SatisfactionMaterial(
signatures=psbt_in.map[PSBT_IN_PARTIAL_SIG],
)
stack = desc.satisfy(sat_material)
logging.debug(f"Satisfaction for {desc} is {[e.hex() for e in stack]}")
# Update the transaction inside the PSBT directly.
assert stack is not None
psbt_in.map[PSBT_IN_FINAL_SCRIPTWITNESS] = CTxInWitness(
CScriptWitness(stack)
)
psbt.tx.wit.vtxinwit.append(psbt_in.map[PSBT_IN_FINAL_SCRIPTWITNESS])
return psbt
def restart_fresh(self, bitcoind):
"""Delete the internal state of the wallet and restart."""
self.stop()
dir_path = os.path.join(self.datadir, "regtest")
shutil.rmtree(dir_path)
if BITCOIN_BACKEND_TYPE is BitcoinBackendType.Bitcoind:
wallet_path = os.path.join(dir_path, "lianad_watchonly_wallet")
bitcoind.node_rpc.unloadwallet(wallet_path)
self.start()
wait_for(
lambda: self.rpc.getinfo()["block_height"] == bitcoind.rpc.getblockcount()
)
def start(self):
TailableProc.start(self)
self.wait_for_logs(
[
"Database initialized and checked",
"JSONRPC server started.",
]
)
def stop(self, timeout=5):
try:
self.rpc.stop()
self.wait_for_log(
"Stopping the liana daemon.",
)
self.proc.wait(timeout)
except Exception as e:
logging.error(f"{self.prefix} : error when calling stop: '{e}'")
return TailableProc.stop(self)
def cleanup(self):
try:
self.stop()
except Exception:
self.proc.kill()