Merge #97: daemon: rename Minisafe to Liana

968a7d17a02bc776c38274fc9e61235163c22442 daemon: rename Minisafe to Liana (Antoine Poinsot)

Pull request description:

  We decided on a new name for this project. Farewell Minisafe, "not a Bitcoin vault".

  To avoid conflicts with ongoing work in the GUI i have only made the change on the daemon for now.

  Fixes #91

ACKs for top commit:
  edouardparis:
    utACK 968a7d17a02bc776c38274fc9e61235163c22442

Tree-SHA512: e0d9324423b650bf4c8ec3084fb0002b2eb9b29e028f6f7c7be2062c10cc8ea3a2c3186689700495a0bfbd5c7cb75b549fffa06604bdd182c589aad3bbf61e0d
This commit is contained in:
Antoine Poinsot 2022-11-18 10:11:46 +01:00
commit 9f4d6ba064
No known key found for this signature in database
GPG Key ID: E13FC145CD3F4304
23 changed files with 280 additions and 280 deletions

View File

@ -31,7 +31,7 @@ task:
fingerprint_script:
- rustc --version
- cat Cargo.lock
minisafed_build_script: cargo build --release
lianad_build_script: cargo build --release
deps_script: apt update && apt install -y python3 python3-pip
@ -50,6 +50,6 @@ task:
export BITCOIND_PATH=bitcoin-24.0rc1/bin/bitcoind
# Run the functional tests
MINISAFED_PATH=$PWD/target/release/minisafed pytest $TEST_GROUP -vvv -n 2
LIANAD_PATH=$PWD/target/release/lianad pytest $TEST_GROUP -vvv -n 2
before_cache_script: rm -rf $CARGO_HOME/registry/index

View File

@ -26,7 +26,7 @@ jobs:
- nightly
os:
- ubuntu-latest
# See issue https://github.com/revault/minisafe/issues/69
# See issue https://github.com/revault/liana/issues/69
#- macOS-latest
- windows-latest
runs-on: ${{ matrix.os }}

36
Cargo.lock generated
View File

@ -203,6 +203,24 @@ dependencies = [
"serde_json",
]
[[package]]
name = "liana"
version = "0.0.1"
dependencies = [
"backtrace",
"base64",
"dirs",
"fern",
"jsonrpc",
"libc",
"log",
"miniscript",
"rusqlite",
"serde",
"serde_json",
"toml",
]
[[package]]
name = "libc"
version = "0.2.135"
@ -235,24 +253,6 @@ version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
[[package]]
name = "minisafe"
version = "0.0.1"
dependencies = [
"backtrace",
"base64",
"dirs",
"fern",
"jsonrpc",
"libc",
"log",
"miniscript",
"rusqlite",
"serde",
"serde_json",
"toml",
]
[[package]]
name = "miniscript"
version = "8.0.0"

View File

@ -1,21 +1,21 @@
[package]
name = "minisafe"
name = "liana"
version = "0.0.1"
authors = ["Antoine Poinsot <darosior@protonmail.com>"]
edition = "2018"
repository = "https://github.com/revault/minisafed"
repository = "https://github.com/revault/liana"
license-file = "LICENCE"
keywords = ["bitcoin", "wallet", "safe", "script", "miniscript", "inheritance", "recovery"]
description = "Minisafe wallet daemon"
description = "Liana wallet daemon"
exclude = [".github/", ".cirrus.yml", "tests/", "test_data/", "contrib/", "pyproject.toml"]
[[bin]]
name = "minisafed"
name = "lianad"
path = "src/bin/daemon.rs"
required-features = ["jsonrpc_server"]
[[bin]]
name = "minisafe-cli"
name = "liana-cli"
path = "src/bin/cli.rs"
required-features = ["jsonrpc_server"]

View File

@ -1,6 +1,6 @@
BSD 3-Clause License
Copyright (c) 2022, The Minisafe developers
Copyright (c) 2022, The Liana developers
All rights reserved.
Redistribution and use in source and binary forms, with or without

View File

@ -1,4 +1,4 @@
use minisafe::config::{config_folder_path, Config};
use liana::config::{config_folder_path, Config};
use std::{
env,
@ -14,7 +14,7 @@ use std::os::unix::net::UnixStream;
// Exits with error
fn show_usage() {
eprintln!("Usage:");
eprintln!(" minisafe-cli [--conf conf_path] [--raw] <command> [<param 1> <param 2> ...]");
eprintln!(" liana-cli [--conf conf_path] [--raw] <command> [<param 1> <param 2> ...]");
process::exit(1);
}
@ -73,7 +73,7 @@ fn rpc_request(method: String, params: Vec<String>) -> Json {
object.insert("jsonrpc".to_string(), Json::String("2.0".to_string()));
object.insert(
"id".to_string(),
Json::String(format!("minisafe-cli-{}", process::id())),
Json::String(format!("liana-cli-{}", process::id())),
);
object.insert("method".to_string(), method);
object.insert("params".to_string(), params);
@ -94,7 +94,7 @@ fn socket_file(conf_file: Option<PathBuf>) -> PathBuf {
[
data_dir,
config.bitcoin_config.network.to_string().as_str(),
"minisafed_rpc",
"lianad_rpc",
]
.iter()
.collect()
@ -160,7 +160,7 @@ fn main() {
println!("{:#}", serde_json::json!({ "error": e }));
} else {
log::warn!(
"minisafed response doesn't contain result or error: '{}'",
"lianad response doesn't contain result or error: '{}'",
response
);
println!("{:#}", response);

View File

@ -5,7 +5,7 @@ use std::{
process, thread, time,
};
use minisafe::{config::Config, DaemonHandle};
use liana::{config::Config, DaemonHandle};
fn parse_args(args: Vec<String>) -> Option<PathBuf> {
if args.len() == 1 {
@ -61,7 +61,7 @@ fn main() {
let daemon = DaemonHandle::start_default(config).unwrap_or_else(|e| {
// The panic hook will log::error
panic!("Starting Minisafe daemon: {}", e);
panic!("Starting Liana daemon: {}", e);
});
daemon
.rpc_server()

View File

@ -1,6 +1,6 @@
//! # Minisafe commands
//! # Liana commands
//!
//! External interface to the Minisafe daemon.
//! External interface to the Liana daemon.
mod utils;
@ -632,7 +632,7 @@ mod tests {
#[test]
fn getinfo() {
let ms = DummyMinisafe::new();
let ms = DummyLiana::new();
// We can query getinfo
ms.handle.control.get_info();
ms.shutdown();
@ -640,7 +640,7 @@ mod tests {
#[test]
fn getnewaddress() {
let ms = DummyMinisafe::new();
let ms = DummyLiana::new();
let control = &ms.handle.control;
// We can get an address
@ -661,7 +661,7 @@ mod tests {
#[test]
fn create_spend() {
let ms = DummyMinisafe::new();
let ms = DummyLiana::new();
let control = &ms.handle.control;
// Arguments sanity checking
@ -777,7 +777,7 @@ mod tests {
#[test]
fn update_spend() {
let ms = DummyMinisafe::new();
let ms = DummyLiana::new();
let control = &ms.handle.control;
let mut db_conn = control.db().lock().unwrap().connection();

View File

@ -144,11 +144,11 @@ impl From<std::io::Error> for ConfigError {
impl std::error::Error for ConfigError {}
/// Get the absolute path to the minisafe configuration folder.
/// Get the absolute path to the liana configuration folder.
///
/// It's a "minisafe/<network>/" directory in the XDG standard configuration directory for
/// all OSes but Linux-based ones, for which it's `~/.minisafe/<network>/`.
/// There is only one config file at `minisafe/config.toml`, which specifies the network.
/// It's a "liana/<network>/" directory in the XDG standard configuration directory for
/// all OSes but Linux-based ones, for which it's `~/.liana/<network>/`.
/// There is only one config file at `liana/config.toml`, which specifies the network.
/// Rationale: we want to have the database, RPC socket, etc.. in the same folder as the
/// configuration file but for Linux the XDG specifoes a data directory (`~/.local/share/`)
/// different from the configuration one (`~/.config/`).
@ -161,10 +161,10 @@ pub fn config_folder_path() -> Option<PathBuf> {
if let Some(mut path) = configs_dir {
#[cfg(target_os = "linux")]
path.push(".minisafe");
path.push(".liana");
#[cfg(not(target_os = "linux"))]
path.push("Minisafe");
path.push("Liana");
return Some(path);
}
@ -174,7 +174,7 @@ pub fn config_folder_path() -> Option<PathBuf> {
fn config_file_path() -> Option<PathBuf> {
config_folder_path().map(|mut path| {
path.push("minisafe.toml");
path.push("liana.toml");
path
})
}
@ -306,17 +306,17 @@ mod tests {
#[cfg(target_os = "linux")]
{
assert!(filepath.as_path().starts_with("/home/"));
assert!(filepath.as_path().ends_with(".minisafe/minisafe.toml"));
assert!(filepath.as_path().ends_with(".liana/liana.toml"));
}
#[cfg(target_os = "macos")]
assert!(filepath
.as_path()
.ends_with("Library/Application Support/Minisafe/minisafe.toml"));
.ends_with("Library/Application Support/Liana/liana.toml"));
#[cfg(target_os = "windows")]
assert!(filepath
.as_path()
.ends_with(r#"AppData\Roaming\Minisafe\minisafe.toml"#));
.ends_with(r#"AppData\Roaming\Liana\liana.toml"#));
}
}

View File

@ -1,4 +1,4 @@
///! Database interface for Minisafe.
///! Database interface for Liana.
///!
///! Record wallet metadata, spent and unspent coins, ongoing transactions.
pub mod sqlite;

View File

@ -563,7 +563,7 @@ mod tests {
fs::create_dir_all(&tmp_dir).unwrap();
let secp = secp256k1::Secp256k1::verification_only();
let db_path: path::PathBuf = [tmp_dir.as_path(), path::Path::new("minisafed.sqlite3")]
let db_path: path::PathBuf = [tmp_dir.as_path(), path::Path::new("lianad.sqlite3")]
.iter()
.collect();
let options = dummy_options();
@ -578,7 +578,7 @@ mod tests {
fs::create_dir_all(&tmp_dir).unwrap();
let secp = secp256k1::Secp256k1::verification_only();
let db_path: path::PathBuf = [tmp_dir.as_path(), path::Path::new("minisafed.sqlite3")]
let db_path: path::PathBuf = [tmp_dir.as_path(), path::Path::new("lianad.sqlite3")]
.iter()
.collect();
assert!(SqliteDb::new(db_path.clone(), None, &secp)

View File

@ -101,7 +101,7 @@ fn connection_handler(
let req_id = req.id.clone();
if &req.method == "stop" {
shutdown.store(true, atomic::Ordering::Relaxed);
log::info!("Stopping the minisafe daemon.");
log::info!("Stopping the liana daemon.");
}
log::trace!("JSONRPC request: {:?}", serde_json::to_string(&req));
@ -146,7 +146,7 @@ pub fn rpcserver_loop(
let handler_id = connections_counter.load(atomic::Ordering::Relaxed);
thread::Builder::new()
.name(format!("minisafe-jsonrpc-{}", handler_id))
.name(format!("liana-jsonrpc-{}", handler_id))
.spawn({
let control = daemon_control.clone();
let counter = connections_counter.clone();
@ -260,7 +260,7 @@ mod tests {
#[test]
fn command_read_single() {
let socket_path = env::temp_dir().join(format!(
"minisafed-jsonrpc-socket-{}-{:?}",
"lianad-jsonrpc-socket-{}-{:?}",
process::id(),
thread::current().id()
));
@ -287,7 +287,7 @@ mod tests {
#[test]
fn command_read_parts() {
let socket_path = env::temp_dir().join(format!(
"minisafed-jsonrpc-socket-{}-{:?}",
"lianad-jsonrpc-socket-{}-{:?}",
process::id(),
thread::current().id()
));
@ -320,7 +320,7 @@ mod tests {
#[test]
fn command_read_multiple() {
let socket_path = env::temp_dir().join(format!(
"minisafed-jsonrpc-socket-{}-{:?}",
"lianad-jsonrpc-socket-{}-{:?}",
process::id(),
thread::current().id()
));
@ -367,7 +367,7 @@ mod tests {
#[test]
fn command_read_linebreak() {
let socket_path = env::temp_dir().join(format!(
"minisafed-jsonrpc-socket-{}-{:?}",
"lianad-jsonrpc-socket-{}-{:?}",
process::id(),
thread::current().id()
));
@ -400,12 +400,12 @@ mod tests {
#[cfg(not(target_os = "macos"))]
#[test]
fn server_sanity_check() {
let ms = DummyMinisafe::new();
let ms = DummyLiana::new();
let socket_path: path::PathBuf = [
ms.tmp_dir.as_path(),
path::Path::new("d"),
path::Path::new("bitcoin"),
path::Path::new("minisafed_rpc"),
path::Path::new("lianad_rpc"),
]
.iter()
.collect();

View File

@ -163,7 +163,7 @@ fn setup_sqlite(
fresh_data_dir: bool,
secp: &secp256k1::Secp256k1<secp256k1::VerifyOnly>,
) -> Result<SqliteDb, StartupError> {
let db_path: path::PathBuf = [data_dir, path::Path::new("minisafed.sqlite3")]
let db_path: path::PathBuf = [data_dir, path::Path::new("lianad.sqlite3")]
.iter()
.collect();
let options = if fresh_data_dir {
@ -189,7 +189,7 @@ fn setup_bitcoind(
fresh_data_dir: bool,
) -> Result<BitcoinD, StartupError> {
// Now set up the bitcoind interface
let wo_path: path::PathBuf = [data_dir, path::Path::new("minisafed_watchonly_wallet")]
let wo_path: path::PathBuf = [data_dir, path::Path::new("lianad_watchonly_wallet")]
.iter()
.collect();
let bitcoind = BitcoinD::new(
@ -247,7 +247,7 @@ pub struct DaemonHandle {
}
impl DaemonHandle {
/// This starts the Minisafe daemon. Call `shutdown` to shut it down.
/// This starts the Liana daemon. Call `shutdown` to shut it down.
///
/// You may specify a custom Bitcoin interface through the `bitcoin` parameter. If `None`, the
/// default Bitcoin interface (`bitcoind` JSONRPC) will be used.
@ -329,7 +329,7 @@ impl DaemonHandle {
})
}
/// Start the Minisafe daemon with the default Bitcoin and database interfaces (`bitcoind` RPC
/// Start the Liana daemon with the default Bitcoin and database interfaces (`bitcoind` RPC
/// and SQLite).
pub fn start_default(config: Config) -> Result<DaemonHandle, StartupError> {
DaemonHandle::start(config, Option::<BitcoinD>::None, Option::<SqliteDb>::None)
@ -351,7 +351,7 @@ impl DaemonHandle {
.expect("Didn't fail at startup, must not now")
.as_path(),
path::Path::new(&control.config.bitcoin_config.network.to_string()),
path::Path::new("minisafed_rpc"),
path::Path::new("lianad_rpc"),
]
.iter()
.collect();
@ -367,12 +367,12 @@ impl DaemonHandle {
}
// NOTE: this moves out the data as it should not be reused after shutdown
/// Shut down the Minisafe daemon.
/// Shut down the Liana daemon.
pub fn shutdown(self) {
self.bitcoin_poller.stop();
}
// We need a shutdown utility that does not move for implementing Drop for the DummyMinisafe
// We need a shutdown utility that does not move for implementing Drop for the DummyLiana
#[cfg(test)]
pub fn test_shutdown(&mut self) {
self.bitcoin_poller.test_stop();
@ -553,7 +553,7 @@ mod tests {
}
// TODO: we could move the dummy bitcoind thread stuff to the bitcoind module to test the
// bitcoind interface, and use the DummyMinisafe from testutils to sanity check the startup.
// bitcoind interface, and use the DummyLiana from testutils to sanity check the startup.
// Note that startup as checked by this unit test is also tested in the functional test
// framework.
#[test]
@ -566,7 +566,7 @@ mod tests {
let wo_path: path::PathBuf = [
data_dir.as_path(),
path::Path::new("bitcoin"),
path::Path::new("minisafed_watchonly_wallet"),
path::Path::new("lianad_watchonly_wallet"),
]
.iter()
.collect();

View File

@ -286,7 +286,7 @@ impl DatabaseConnection for DummyDbConn {
}
}
pub struct DummyMinisafe {
pub struct DummyLiana {
pub tmp_dir: path::PathBuf,
pub handle: DaemonHandle,
}
@ -302,15 +302,15 @@ fn uid() -> usize {
pub fn tmp_dir() -> path::PathBuf {
env::temp_dir().join(format!(
"minisafed-{}-{:?}-{}",
"lianad-{}-{:?}-{}",
process::id(),
thread::current().id(),
uid(),
))
}
impl DummyMinisafe {
pub fn new() -> DummyMinisafe {
impl DummyLiana {
pub fn new() -> DummyLiana {
let tmp_dir = tmp_dir();
fs::create_dir_all(&tmp_dir).unwrap();
// Use a shorthand for 'datadir', to avoid overflowing SUN_LEN on MacOS.
@ -338,7 +338,7 @@ impl DummyMinisafe {
let db = sync::Arc::from(sync::RwLock::from(DummyDb::new()));
let handle = DaemonHandle::start(config, Some(DummyBitcoind {}), Some(db)).unwrap();
DummyMinisafe { tmp_dir, handle }
DummyLiana { tmp_dir, handle }
}
#[cfg(feature = "jsonrpc_server")]

View File

@ -1,6 +1,6 @@
## Minisafed blackbox tests
## Lianad blackbox tests
Here we test `minisafed` by starting it on a regression testing Bitcoin network,
Here we test `lianad` by starting it on a regression testing Bitcoin network,
and by then talking to it as an user would, from the outside.
Python scripts are used for the automation, and specifically the [`pytest` framework](https://docs.pytest.org/en/stable/index.html).

View File

@ -3,7 +3,7 @@ from bip380.descriptors import Descriptor
from concurrent import futures
from ephemeral_port_reserve import reserve
from test_framework.bitcoind import Bitcoind
from test_framework.minisafed import Minisafed
from test_framework.lianad import Lianad
from test_framework.utils import (
EXECUTOR_WORKERS,
)
@ -24,7 +24,7 @@ ATTEMPTS = {}
def test_base_dir():
d = os.getenv("TEST_DIR", "/tmp")
directory = tempfile.mkdtemp(prefix="minisafed-tests-", dir=d)
directory = tempfile.mkdtemp(prefix="lianad-tests-", dir=d)
print("Running tests in {}".format(directory))
yield directory
@ -114,8 +114,8 @@ def bitcoind(directory):
@pytest.fixture
def minisafed(bitcoind, directory):
datadir = os.path.join(directory, "minisafed")
def lianad(bitcoind, directory):
datadir = os.path.join(directory, "lianad")
os.makedirs(datadir, exist_ok=True)
bitcoind_cookie = os.path.join(bitcoind.bitcoin_dir, "regtest", ".cookie")
@ -123,7 +123,7 @@ def minisafed(bitcoind, directory):
owner_xpub = owner_hd.get_xpub()
main_desc = Descriptor.from_str(f"wsh(or_d(pk({owner_xpub}/<0;1>/*),and_v(v:pkh(tpubD9vQiBdDxYzU4cVFtApWj4devZrvcfWaPXX1zHdDc7GPfUsDKqGnbhraccfm7BAXgRgUbVQUV2v2o4NitjGEk7hpbuP85kvBrD4ahFDtNBJ/<0;1>/*),older(65000))))")
minisafed = Minisafed(
lianad = Lianad(
datadir,
owner_hd,
main_desc,
@ -132,10 +132,10 @@ def minisafed(bitcoind, directory):
)
try:
minisafed.start()
yield minisafed
lianad.start()
yield lianad
except Exception:
minisafed.cleanup()
lianad.cleanup()
raise
minisafed.cleanup()
lianad.cleanup()

View File

@ -4,88 +4,88 @@ from fixtures import *
from test_framework.utils import wait_for, get_txid, spend_coins
def get_coin(minisafed, outpoint_or_txid):
def get_coin(lianad, outpoint_or_txid):
return next(
c
for c in minisafed.rpc.listcoins()["coins"]
for c in lianad.rpc.listcoins()["coins"]
if outpoint_or_txid in c["outpoint"]
)
def test_reorg_detection(minisafed, bitcoind):
def test_reorg_detection(lianad, bitcoind):
"""Test we detect block chain reorganization under various conditions."""
initial_height = bitcoind.rpc.getblockcount()
wait_for(lambda: minisafed.rpc.getinfo()["blockheight"] == initial_height)
wait_for(lambda: lianad.rpc.getinfo()["blockheight"] == initial_height)
# Re-mine the last block. We should detect it as a reorg.
bitcoind.invalidate_remine(initial_height)
minisafed.wait_for_logs(
lianad.wait_for_logs(
["Block chain reorganization detected.", "Tip was rolled back."]
)
wait_for(lambda: minisafed.rpc.getinfo()["blockheight"] == initial_height)
wait_for(lambda: lianad.rpc.getinfo()["blockheight"] == initial_height)
# Same if we re-mine the next-to-last block.
bitcoind.invalidate_remine(initial_height - 1)
minisafed.wait_for_logs(
lianad.wait_for_logs(
["Block chain reorganization detected.", "Tip was rolled back."]
)
wait_for(lambda: minisafed.rpc.getinfo()["blockheight"] == initial_height)
wait_for(lambda: lianad.rpc.getinfo()["blockheight"] == initial_height)
# Same if we re-mine a deep block.
bitcoind.invalidate_remine(initial_height - 50)
minisafed.wait_for_logs(
lianad.wait_for_logs(
["Block chain reorganization detected.", "Tip was rolled back."]
)
wait_for(lambda: minisafed.rpc.getinfo()["blockheight"] == initial_height)
wait_for(lambda: lianad.rpc.getinfo()["blockheight"] == initial_height)
# Same if the new chain is longer.
bitcoind.simple_reorg(initial_height - 10, shift=20)
minisafed.wait_for_logs(
lianad.wait_for_logs(
["Block chain reorganization detected.", "Tip was rolled back."]
)
wait_for(lambda: minisafed.rpc.getinfo()["blockheight"] == initial_height + 10)
wait_for(lambda: lianad.rpc.getinfo()["blockheight"] == initial_height + 10)
def test_reorg_exclusion(minisafed, bitcoind):
def test_reorg_exclusion(lianad, bitcoind):
"""Test the unconfirmation by a reorg of a coin in various states."""
initial_height = bitcoind.rpc.getblockcount()
wait_for(lambda: minisafed.rpc.getinfo()["blockheight"] == initial_height)
wait_for(lambda: lianad.rpc.getinfo()["blockheight"] == initial_height)
# A confirmed received coin
addr = minisafed.rpc.getnewaddress()["address"]
addr = lianad.rpc.getnewaddress()["address"]
txid = bitcoind.rpc.sendtoaddress(addr, 1)
bitcoind.generate_block(1, wait_for_mempool=txid)
wait_for(lambda: len(minisafed.rpc.listcoins()["coins"]) == 1)
coin_a = minisafed.rpc.listcoins()["coins"][0]
wait_for(lambda: len(lianad.rpc.listcoins()["coins"]) == 1)
coin_a = lianad.rpc.listcoins()["coins"][0]
# A confirmed and 'spending' (unconfirmed spend) coin
addr = minisafed.rpc.getnewaddress()["address"]
addr = lianad.rpc.getnewaddress()["address"]
txid = bitcoind.rpc.sendtoaddress(addr, 2)
bitcoind.generate_block(1, wait_for_mempool=txid)
wait_for(lambda: len(minisafed.rpc.listcoins()["coins"]) == 2)
coin_b = get_coin(minisafed, txid)
b_spend_tx = spend_coins(minisafed, bitcoind, [coin_b])
wait_for(lambda: len(lianad.rpc.listcoins()["coins"]) == 2)
coin_b = get_coin(lianad, txid)
b_spend_tx = spend_coins(lianad, bitcoind, [coin_b])
# A confirmed and spent coin
addr = minisafed.rpc.getnewaddress()["address"]
addr = lianad.rpc.getnewaddress()["address"]
txid = bitcoind.rpc.sendtoaddress(addr, 3)
bitcoind.generate_block(1, wait_for_mempool=txid)
wait_for(lambda: len(minisafed.rpc.listcoins()["coins"]) == 3)
coin_c = get_coin(minisafed, txid)
c_spend_tx = spend_coins(minisafed, bitcoind, [coin_c])
wait_for(lambda: len(lianad.rpc.listcoins()["coins"]) == 3)
coin_c = get_coin(lianad, txid)
c_spend_tx = spend_coins(lianad, bitcoind, [coin_c])
bitcoind.generate_block(1, wait_for_mempool=1)
# Reorg the chain down to the initial height, excluding all transactions.
current_height = bitcoind.rpc.getblockcount()
bitcoind.simple_reorg(initial_height, shift=-1)
wait_for(lambda: minisafed.rpc.getinfo()["blockheight"] == current_height + 1)
wait_for(lambda: lianad.rpc.getinfo()["blockheight"] == current_height + 1)
# They must all be marked as unconfirmed.
new_coin_a = get_coin(minisafed, coin_a["outpoint"])
new_coin_a = get_coin(lianad, coin_a["outpoint"])
assert new_coin_a["block_height"] is None
new_coin_b = get_coin(minisafed, coin_b["outpoint"])
new_coin_b = get_coin(lianad, coin_b["outpoint"])
assert new_coin_b["block_height"] is None
new_coin_c = get_coin(minisafed, coin_c["outpoint"])
new_coin_c = get_coin(lianad, coin_c["outpoint"])
assert new_coin_c["block_height"] is None
# And if we now confirm everything, they'll be marked as such. The one that was 'spending'
@ -99,13 +99,13 @@ def test_reorg_exclusion(minisafed, bitcoind):
bitcoind.rpc.sendrawtransaction(c_spend_tx)
bitcoind.generate_block(1, wait_for_mempool=5)
new_height = bitcoind.rpc.getblockcount()
wait_for(lambda: minisafed.rpc.getinfo()["blockheight"] == new_height)
wait_for(lambda: lianad.rpc.getinfo()["blockheight"] == new_height)
assert all(
c["block_height"] == new_height for c in minisafed.rpc.listcoins()["coins"]
), (minisafed.rpc.listcoins()["coins"], new_height)
c["block_height"] == new_height for c in lianad.rpc.listcoins()["coins"]
), (lianad.rpc.listcoins()["coins"], new_height)
new_coin_b = next(
c
for c in minisafed.rpc.listcoins()["coins"]
for c in lianad.rpc.listcoins()["coins"]
if coin_b["outpoint"] == c["outpoint"]
)
b_spend_txid = get_txid(b_spend_tx)
@ -113,7 +113,7 @@ def test_reorg_exclusion(minisafed, bitcoind):
assert new_coin_b["spend_info"]["height"] == new_height
new_coin_c = next(
c
for c in minisafed.rpc.listcoins()["coins"]
for c in lianad.rpc.listcoins()["coins"]
if coin_c["outpoint"] == c["outpoint"]
)
c_spend_txid = get_txid(c_spend_tx)
@ -123,8 +123,8 @@ def test_reorg_exclusion(minisafed, bitcoind):
# TODO: maybe test with some malleation for the deposit and spending txs?
def spend_confirmed_noticed(minisafed, outpoint):
c = get_coin(minisafed, outpoint)
def spend_confirmed_noticed(lianad, outpoint):
c = get_coin(lianad, outpoint)
if c["spend_info"] is None:
return False
if c["spend_info"]["height"] is None:
@ -132,53 +132,53 @@ def spend_confirmed_noticed(minisafed, outpoint):
return True
def test_reorg_status_recovery(minisafed, bitcoind):
def test_reorg_status_recovery(lianad, bitcoind):
"""
Test the coins that were not unconfirmed recover their initial state after a reorg.
"""
list_coins = lambda: minisafed.rpc.listcoins()["coins"]
list_coins = lambda: lianad.rpc.listcoins()["coins"]
# Create two confirmed coins. Note how we take the initial_height after having
# mined them, as we'll reorg back to this height and due to anti fee-sniping
# these deposit transactions might not be valid anymore!
addresses = (minisafed.rpc.getnewaddress()["address"] for _ in range(2))
addresses = (lianad.rpc.getnewaddress()["address"] for _ in range(2))
txids = [bitcoind.rpc.sendtoaddress(addr, 0.5670) for addr in addresses]
bitcoind.generate_block(1, wait_for_mempool=txids)
initial_height = bitcoind.rpc.getblockcount()
wait_for(lambda: minisafed.rpc.getinfo()["blockheight"] == initial_height)
wait_for(lambda: lianad.rpc.getinfo()["blockheight"] == initial_height)
# Both coins are confirmed. Spend the second one then get their infos.
wait_for(lambda: len(list_coins()) == 2)
wait_for(lambda: all(c["block_height"] is not None for c in list_coins()))
coin_b = get_coin(minisafed, txids[1])
spend_coins(minisafed, bitcoind, [coin_b])
coin_b = get_coin(lianad, txids[1])
spend_coins(lianad, bitcoind, [coin_b])
bitcoind.generate_block(1, wait_for_mempool=1)
wait_for(lambda: spend_confirmed_noticed(minisafed, coin_b["outpoint"]))
coin_a = get_coin(minisafed, txids[0])
coin_b = get_coin(minisafed, txids[1])
wait_for(lambda: spend_confirmed_noticed(lianad, coin_b["outpoint"]))
coin_a = get_coin(lianad, txids[0])
coin_b = get_coin(lianad, txids[1])
# Reorg the chain down to the initial height without shifting nor malleating
# any transaction. The coin info should be identical (except the transaction
# spending the second coin will be mined at the height the reorg happened).
bitcoind.simple_reorg(initial_height, shift=0)
new_height = bitcoind.rpc.getblockcount()
wait_for(lambda: minisafed.rpc.getinfo()["blockheight"] == new_height)
new_coin_a = get_coin(minisafed, coin_a["outpoint"])
wait_for(lambda: lianad.rpc.getinfo()["blockheight"] == new_height)
new_coin_a = get_coin(lianad, coin_a["outpoint"])
assert coin_a == new_coin_a
new_coin_b = get_coin(minisafed, coin_b["outpoint"])
new_coin_b = get_coin(lianad, coin_b["outpoint"])
coin_b["spend_info"]["height"] = initial_height
assert new_coin_b == coin_b
def test_rescan_edge_cases(minisafed, bitcoind):
def test_rescan_edge_cases(lianad, bitcoind):
"""Test some specific cases that could arise when rescanning the chain."""
initial_tip = bitcoind.rpc.getblockheader(bitcoind.rpc.getbestblockhash())
# Some helpers
list_coins = lambda: minisafed.rpc.listcoins()["coins"]
list_coins = lambda: lianad.rpc.listcoins()["coins"]
sorted_coins = lambda: sorted(list_coins(), key=lambda c: c["outpoint"])
wait_synced = lambda: wait_for(
lambda: minisafed.rpc.getinfo()["blockheight"] == bitcoind.rpc.getblockcount()
lambda: lianad.rpc.getinfo()["blockheight"] == bitcoind.rpc.getblockcount()
)
def reorg_shift(height, txs):
@ -196,12 +196,12 @@ def test_rescan_edge_cases(minisafed, bitcoind):
# rebroadcast them on reorgs.
txs = []
for _ in range(3):
addr = minisafed.rpc.getnewaddress()["address"]
addr = lianad.rpc.getnewaddress()["address"]
amount = 0.356
txid = bitcoind.rpc.sendtoaddress(addr, amount)
txs.append(bitcoind.rpc.gettransaction(txid)["hex"])
wait_for(lambda: len(list_coins()) == 3)
txs.append(spend_coins(minisafed, bitcoind, list_coins()[:2]))
txs.append(spend_coins(lianad, bitcoind, list_coins()[:2]))
bitcoind.generate_block(1, wait_for_mempool=4)
wait_synced()
@ -214,27 +214,27 @@ def test_rescan_edge_cases(minisafed, bitcoind):
coins_before = sorted_coins()
outpoints_before = set(c["outpoint"] for c in coins_before)
bitcoind.generate_block(1)
minisafed.restart_fresh(bitcoind)
lianad.restart_fresh(bitcoind)
assert len(list_coins()) == 0
# We can be stopped while we are rescanning
minisafed.rpc.startrescan(initial_tip["time"])
minisafed.stop()
minisafed.start()
wait_for(lambda: minisafed.rpc.getinfo()["rescan_progress"] is None)
lianad.rpc.startrescan(initial_tip["time"])
lianad.stop()
lianad.start()
wait_for(lambda: lianad.rpc.getinfo()["rescan_progress"] is None)
assert coins_before == sorted_coins()
# Lose our state again
bitcoind.generate_block(1)
minisafed.restart_fresh(bitcoind)
wait_for(lambda: minisafed.rpc.getinfo()["rescan_progress"] is None)
lianad.restart_fresh(bitcoind)
wait_for(lambda: lianad.rpc.getinfo()["rescan_progress"] is None)
assert len(list_coins()) == 0
# There can be a reorg when we start rescanning
reorg_shift(initial_tip["height"], txs)
minisafed.rpc.startrescan(initial_tip["time"])
lianad.rpc.startrescan(initial_tip["time"])
wait_synced()
wait_for(lambda: minisafed.rpc.getinfo()["rescan_progress"] is None)
wait_for(lambda: lianad.rpc.getinfo()["rescan_progress"] is None)
assert len(sorted_coins()) == len(coins_before)
assert all(c["outpoint"] in outpoints_before for c in list_coins())
@ -244,15 +244,15 @@ def test_rescan_edge_cases(minisafed, bitcoind):
# Lose our state again
bitcoind.generate_block(1)
minisafed.restart_fresh(bitcoind)
lianad.restart_fresh(bitcoind)
wait_synced()
wait_for(lambda: minisafed.rpc.getinfo()["rescan_progress"] is None)
wait_for(lambda: lianad.rpc.getinfo()["rescan_progress"] is None)
assert len(list_coins()) == 0
# We can be rescanning when a reorg happens
minisafed.rpc.startrescan(initial_tip["time"])
lianad.rpc.startrescan(initial_tip["time"])
reorg_shift(initial_tip["height"] + 1, txs)
wait_synced()
wait_for(lambda: minisafed.rpc.getinfo()["rescan_progress"] is None)
wait_for(lambda: lianad.rpc.getinfo()["rescan_progress"] is None)
assert len(sorted_coins()) == len(coins_before)
assert all(c["outpoint"] in outpoints_before for c in list_coins())

View File

@ -71,7 +71,7 @@ class Bitcoind(TailableProc):
# An RPC interface with our internal wallet, and an RPC interface with no
# wallet to be able to call 'unloadwallet' on any wallet.
self.rpc = BitcoindRpcInterface(
bitcoin_dir, "regtest", rpcport, wallet="minisafed-tests"
bitcoin_dir, "regtest", rpcport, wallet="lianad-tests"
)
self.node_rpc = BitcoindRpcInterface(bitcoin_dir, "regtest", rpcport)

View File

@ -10,7 +10,7 @@ from test_framework.utils import (
TailableProc,
VERBOSE,
LOG_LEVEL,
MINISAFED_PATH,
LIANAD_PATH,
wait_for,
)
from test_framework.serializations import (
@ -25,7 +25,7 @@ from test_framework.serializations import (
)
class Minisafed(TailableProc):
class Lianad(TailableProc):
def __init__(
self,
datadir,
@ -44,8 +44,8 @@ class Minisafed(TailableProc):
self.receive_desc, self.change_desc = multi_desc.singlepath_descriptors()
self.conf_file = os.path.join(datadir, "config.toml")
self.cmd_line = [MINISAFED_PATH, "--conf", f"{self.conf_file}"]
socket_path = os.path.join(os.path.join(datadir, "regtest"), "minisafed_rpc")
self.cmd_line = [LIANAD_PATH, "--conf", f"{self.conf_file}"]
socket_path = os.path.join(os.path.join(datadir, "regtest"), "lianad_rpc")
self.rpc = UnixDomainSocketRpc(socket_path)
with open(self.conf_file, "w") as f:
@ -153,7 +153,7 @@ class Minisafed(TailableProc):
self.stop()
dir_path = os.path.join(self.datadir, "regtest")
shutil.rmtree(dir_path)
wallet_path = os.path.join(dir_path, "minisafed_watchonly_wallet")
wallet_path = os.path.join(dir_path, "lianad_watchonly_wallet")
bitcoind.node_rpc.unloadwallet(wallet_path)
self.start()
wait_for(
@ -174,7 +174,7 @@ class Minisafed(TailableProc):
try:
self.rpc.stop()
self.wait_for_log(
"Stopping the minisafe daemon.",
"Stopping the liana daemon.",
)
self.proc.wait(timeout)
except Exception as e:

View File

@ -17,9 +17,9 @@ VERBOSE = os.getenv("VERBOSE", "0") == "1"
LOG_LEVEL = os.getenv("LOG_LEVEL", "debug")
assert LOG_LEVEL in ["trace", "debug", "info", "warn", "error"]
DEFAULT_MS_PATH = os.path.join(
os.path.dirname(__file__), "..", "..", "target/debug/minisafed"
os.path.dirname(__file__), "..", "..", "target/debug/lianad"
)
MINISAFED_PATH = os.getenv("MINISAFED_PATH", DEFAULT_MS_PATH)
LIANAD_PATH = os.getenv("LIANAD_PATH", DEFAULT_MS_PATH)
DEFAULT_BITCOIND_PATH = "bitcoind"
BITCOIND_PATH = os.getenv("BITCOIND_PATH", DEFAULT_BITCOIND_PATH)
@ -53,7 +53,7 @@ def get_txid(hex_tx):
return tx.txid().hex()
def spend_coins(minisafed, bitcoind, coins):
def spend_coins(lianad, bitcoind, coins):
"""Spend these coins, no matter how.
This will create a single transaction spending them all at once at the minimum
feerate. This will broadcast but not confirm the transaction.
@ -65,10 +65,10 @@ def spend_coins(minisafed, bitcoind, coins):
destinations = {
bitcoind.rpc.getnewaddress(): total_value - 11 - 31 - 300 * len(coins)
}
res = minisafed.rpc.createspend(destinations, [c["outpoint"] for c in coins], 1)
res = lianad.rpc.createspend(destinations, [c["outpoint"] for c in coins], 1)
signed_psbt = minisafed.sign_psbt(PSBT.from_base64(res["psbt"]))
finalized_psbt = minisafed.finalize_psbt(signed_psbt)
signed_psbt = lianad.sign_psbt(PSBT.from_base64(res["psbt"]))
finalized_psbt = lianad.finalize_psbt(signed_psbt)
tx = finalized_psbt.tx.serialize_with_witness().hex()
bitcoind.rpc.sendrawtransaction(tx)
@ -175,7 +175,7 @@ class UnixDomainSocketRpc(object):
except json.JSONDecodeError:
# There is more to read, continue
# FIXME: this is a workaround for large reads taken from revaultd.
# We should use the '\n' marker instead since minisafed uses that.
# We should use the '\n' marker instead since lianad uses that.
continue
def __getattr__(self, name):

View File

@ -1,5 +1,5 @@
from fixtures import *
def test_startup(minisafed):
def test_startup(lianad):
pass

View File

@ -9,36 +9,36 @@ from test_framework.serializations import PSBT, PSBT_IN_PARTIAL_SIG
from test_framework.utils import wait_for, COIN, RpcError, get_txid, spend_coins
def test_getinfo(minisafed):
res = minisafed.rpc.getinfo()
def test_getinfo(lianad):
res = lianad.rpc.getinfo()
assert res["version"] == "0.1"
assert res["network"] == "regtest"
wait_for(lambda: minisafed.rpc.getinfo()["blockheight"] == 101)
res = minisafed.rpc.getinfo()
wait_for(lambda: lianad.rpc.getinfo()["blockheight"] == 101)
res = lianad.rpc.getinfo()
assert res["sync"] == 1.0
assert "main" in res["descriptors"]
assert res["rescan_progress"] is None
def test_getaddress(minisafed):
res = minisafed.rpc.getnewaddress()
def test_getaddress(lianad):
res = lianad.rpc.getnewaddress()
assert "address" in res
# We'll get a new one at every call
assert res["address"] != minisafed.rpc.getnewaddress()["address"]
assert res["address"] != lianad.rpc.getnewaddress()["address"]
def test_listcoins(minisafed, bitcoind):
def test_listcoins(lianad, bitcoind):
# Initially empty
res = minisafed.rpc.listcoins()
res = lianad.rpc.listcoins()
assert "coins" in res
assert len(res["coins"]) == 0
# If we send a coin, we'll get a new entry. Note we monitor for unconfirmed
# funds as well.
addr = minisafed.rpc.getnewaddress()["address"]
addr = lianad.rpc.getnewaddress()["address"]
txid = bitcoind.rpc.sendtoaddress(addr, 1)
wait_for(lambda: len(minisafed.rpc.listcoins()["coins"]) == 1)
res = minisafed.rpc.listcoins()["coins"]
wait_for(lambda: len(lianad.rpc.listcoins()["coins"]) == 1)
res = lianad.rpc.listcoins()["coins"]
assert txid == res[0]["outpoint"][:64]
assert res[0]["amount"] == 1 * COIN
assert res[0]["block_height"] is None
@ -48,66 +48,66 @@ def test_listcoins(minisafed, bitcoind):
bitcoind.generate_block(1, wait_for_mempool=txid)
block_height = bitcoind.rpc.getblockcount()
wait_for(
lambda: minisafed.rpc.listcoins()["coins"][0]["block_height"] == block_height
lambda: lianad.rpc.listcoins()["coins"][0]["block_height"] == block_height
)
# Same if the coin gets spent.
spend_tx = spend_coins(minisafed, bitcoind, (res[0],))
spend_tx = spend_coins(lianad, bitcoind, (res[0],))
spend_txid = get_txid(spend_tx)
wait_for(lambda: minisafed.rpc.listcoins()["coins"][0]["spend_info"] is not None)
spend_info = minisafed.rpc.listcoins()["coins"][0]["spend_info"]
wait_for(lambda: lianad.rpc.listcoins()["coins"][0]["spend_info"] is not None)
spend_info = lianad.rpc.listcoins()["coins"][0]["spend_info"]
assert spend_info["txid"] == spend_txid
assert spend_info["height"] is None
# And if this spending tx gets confirmed.
bitcoind.generate_block(1, wait_for_mempool=spend_txid)
curr_height = bitcoind.rpc.getblockcount()
wait_for(lambda: minisafed.rpc.getinfo()["blockheight"] == curr_height)
spend_info = minisafed.rpc.listcoins()["coins"][0]["spend_info"]
wait_for(lambda: lianad.rpc.getinfo()["blockheight"] == curr_height)
spend_info = lianad.rpc.listcoins()["coins"][0]["spend_info"]
assert spend_info["txid"] == spend_txid
assert spend_info["height"] == curr_height
def test_jsonrpc_server(minisafed, bitcoind):
def test_jsonrpc_server(lianad, bitcoind):
"""Test passing parameters as a list or a mapping."""
addr = minisafed.rpc.getnewaddress()["address"]
addr = lianad.rpc.getnewaddress()["address"]
bitcoind.rpc.sendtoaddress(addr, 1)
wait_for(lambda: len(minisafed.rpc.listcoins()["coins"]) == 1)
outpoints = [minisafed.rpc.listcoins()["coins"][0]["outpoint"]]
wait_for(lambda: len(lianad.rpc.listcoins()["coins"]) == 1)
outpoints = [lianad.rpc.listcoins()["coins"][0]["outpoint"]]
destinations = {
bitcoind.rpc.getnewaddress(): 20_000,
}
res = minisafed.rpc.createspend(destinations, outpoints, 18)
res = lianad.rpc.createspend(destinations, outpoints, 18)
assert "psbt" in res
res = minisafed.rpc.createspend(
res = lianad.rpc.createspend(
outpoints=outpoints, destinations=destinations, feerate=18
)
assert "psbt" in res
def test_create_spend(minisafed, bitcoind):
def test_create_spend(lianad, bitcoind):
# Receive a number of coins in different blocks on different addresses, and
# one more on the same address.
for _ in range(15):
addr = minisafed.rpc.getnewaddress()["address"]
addr = lianad.rpc.getnewaddress()["address"]
txid = bitcoind.rpc.sendtoaddress(addr, 0.01)
bitcoind.generate_block(1, wait_for_mempool=txid)
txid = bitcoind.rpc.sendtoaddress(addr, 0.3556)
bitcoind.generate_block(1, wait_for_mempool=txid)
wait_for(lambda: len(minisafed.rpc.listcoins()["coins"]) == 16)
wait_for(lambda: len(lianad.rpc.listcoins()["coins"]) == 16)
# Stop the daemon, should be a no-op
minisafed.stop()
minisafed.start()
lianad.stop()
lianad.start()
# Now create a transaction spending all those coins to a few addresses
outpoints = [c["outpoint"] for c in minisafed.rpc.listcoins()["coins"]]
outpoints = [c["outpoint"] for c in lianad.rpc.listcoins()["coins"]]
destinations = {
bitcoind.rpc.getnewaddress(): 200_000,
bitcoind.rpc.getnewaddress(): 400_000,
bitcoind.rpc.getnewaddress(): 1_000_000,
}
res = minisafed.rpc.createspend(destinations, outpoints, 18)
res = lianad.rpc.createspend(destinations, outpoints, 18)
assert "psbt" in res
# The transaction must contain a change output.
@ -116,45 +116,45 @@ def test_create_spend(minisafed, bitcoind):
assert len(spend_psbt.tx.vout) == 4
# We can sign it and broadcast it.
signed_psbt = minisafed.sign_psbt(PSBT.from_base64(res["psbt"]))
finalized_psbt = minisafed.finalize_psbt(signed_psbt)
signed_psbt = lianad.sign_psbt(PSBT.from_base64(res["psbt"]))
finalized_psbt = lianad.finalize_psbt(signed_psbt)
tx = finalized_psbt.tx.serialize_with_witness().hex()
bitcoind.rpc.sendrawtransaction(tx)
def test_list_spend(minisafed, bitcoind):
def test_list_spend(lianad, bitcoind):
# Start by creating two conflicting Spend PSBTs. The first one will have a change
# output but not the second one.
addr = minisafed.rpc.getnewaddress()["address"]
addr = lianad.rpc.getnewaddress()["address"]
value_a = 0.2567
bitcoind.rpc.sendtoaddress(addr, value_a)
wait_for(lambda: len(minisafed.rpc.listcoins()["coins"]) == 1)
outpoints = [c["outpoint"] for c in minisafed.rpc.listcoins()["coins"]]
wait_for(lambda: len(lianad.rpc.listcoins()["coins"]) == 1)
outpoints = [c["outpoint"] for c in lianad.rpc.listcoins()["coins"]]
destinations = {
bitcoind.rpc.getnewaddress(): int(value_a * COIN // 2),
}
res = minisafed.rpc.createspend(destinations, outpoints, 6)
res = lianad.rpc.createspend(destinations, outpoints, 6)
assert "psbt" in res
addr = minisafed.rpc.getnewaddress()["address"]
addr = lianad.rpc.getnewaddress()["address"]
value_b = 0.0987
bitcoind.rpc.sendtoaddress(addr, value_b)
wait_for(lambda: len(minisafed.rpc.listcoins()["coins"]) == 2)
outpoints = [c["outpoint"] for c in minisafed.rpc.listcoins()["coins"]]
wait_for(lambda: len(lianad.rpc.listcoins()["coins"]) == 2)
outpoints = [c["outpoint"] for c in lianad.rpc.listcoins()["coins"]]
destinations = {
bitcoind.rpc.getnewaddress(): int((value_a + value_b) * COIN - 1_000),
}
res_b = minisafed.rpc.createspend(destinations, outpoints, 2)
res_b = lianad.rpc.createspend(destinations, outpoints, 2)
assert "psbt" in res_b
# Store them both in DB.
assert len(minisafed.rpc.listspendtxs()["spend_txs"]) == 0
minisafed.rpc.updatespend(res["psbt"])
minisafed.rpc.updatespend(res_b["psbt"])
assert len(lianad.rpc.listspendtxs()["spend_txs"]) == 0
lianad.rpc.updatespend(res["psbt"])
lianad.rpc.updatespend(res_b["psbt"])
# Listing all Spend transactions will list them both. It'll tell us which one has
# change and which one doesn't.
list_res = minisafed.rpc.listspendtxs()["spend_txs"]
list_res = lianad.rpc.listspendtxs()["spend_txs"]
assert len(list_res) == 2
first_psbt = next(entry for entry in list_res if entry["psbt"] == res["psbt"])
assert first_psbt["change_index"] == 1
@ -163,34 +163,34 @@ def test_list_spend(minisafed, bitcoind):
# If we delete the first one, we'll get only the second one.
first_psbt = PSBT.from_base64(res["psbt"])
minisafed.rpc.delspendtx(first_psbt.tx.txid().hex())
list_res = minisafed.rpc.listspendtxs()["spend_txs"]
lianad.rpc.delspendtx(first_psbt.tx.txid().hex())
list_res = lianad.rpc.listspendtxs()["spend_txs"]
assert len(list_res) == 1
assert list_res[0]["psbt"] == res_b["psbt"]
# If we delete the second one, result will be empty.
second_psbt = PSBT.from_base64(res_b["psbt"])
minisafed.rpc.delspendtx(second_psbt.tx.txid().hex())
list_res = minisafed.rpc.listspendtxs()["spend_txs"]
lianad.rpc.delspendtx(second_psbt.tx.txid().hex())
list_res = lianad.rpc.listspendtxs()["spend_txs"]
assert len(list_res) == 0
def test_update_spend(minisafed, bitcoind):
def test_update_spend(lianad, bitcoind):
# Start by creating a Spend PSBT
addr = minisafed.rpc.getnewaddress()["address"]
addr = lianad.rpc.getnewaddress()["address"]
bitcoind.rpc.sendtoaddress(addr, 0.2567)
wait_for(lambda: len(minisafed.rpc.listcoins()["coins"]) > 0)
outpoints = [c["outpoint"] for c in minisafed.rpc.listcoins()["coins"]]
wait_for(lambda: len(lianad.rpc.listcoins()["coins"]) > 0)
outpoints = [c["outpoint"] for c in lianad.rpc.listcoins()["coins"]]
destinations = {
bitcoind.rpc.getnewaddress(): 200_000,
}
res = minisafed.rpc.createspend(destinations, outpoints, 6)
res = lianad.rpc.createspend(destinations, outpoints, 6)
assert "psbt" in res
# Now update it
assert len(minisafed.rpc.listspendtxs()["spend_txs"]) == 0
minisafed.rpc.updatespend(res["psbt"])
list_res = minisafed.rpc.listspendtxs()["spend_txs"]
assert len(lianad.rpc.listspendtxs()["spend_txs"]) == 0
lianad.rpc.updatespend(res["psbt"])
list_res = lianad.rpc.listspendtxs()["spend_txs"]
assert len(list_res) == 1
assert list_res[0]["psbt"] == res["psbt"]
@ -204,10 +204,10 @@ def test_update_spend(minisafed, bitcoind):
)
psbt_sig_a.i[0].map[PSBT_IN_PARTIAL_SIG] = {dummy_pk_a: dummy_sig_a}
psbt_sig_a_ser = psbt_sig_a.to_base64()
minisafed.rpc.updatespend(psbt_sig_a_ser)
lianad.rpc.updatespend(psbt_sig_a_ser)
# We'll get it when querying
list_res = minisafed.rpc.listspendtxs()["spend_txs"]
list_res = lianad.rpc.listspendtxs()["spend_txs"]
assert len(list_res) == 1
assert list_res[0]["psbt"] == psbt_sig_a_ser
@ -221,10 +221,10 @@ def test_update_spend(minisafed, bitcoind):
)
psbt_sig_b.i[0].map[PSBT_IN_PARTIAL_SIG] = {dummy_pk_b: dummy_sig_b}
psbt_sig_b_ser = psbt_sig_b.to_base64()
minisafed.rpc.updatespend(psbt_sig_b_ser)
lianad.rpc.updatespend(psbt_sig_b_ser)
# It will have merged both.
list_res = minisafed.rpc.listspendtxs()["spend_txs"]
list_res = lianad.rpc.listspendtxs()["spend_txs"]
assert len(list_res) == 1
psbt_merged = PSBT.from_base64(list_res[0]["psbt"])
assert len(psbt_merged.i[0].map[PSBT_IN_PARTIAL_SIG]) == 2
@ -232,45 +232,45 @@ def test_update_spend(minisafed, bitcoind):
assert psbt_merged.i[0].map[PSBT_IN_PARTIAL_SIG][dummy_pk_b] == dummy_sig_b
def test_broadcast_spend(minisafed, bitcoind):
def test_broadcast_spend(lianad, bitcoind):
# Create a new coin and a spending tx for it.
addr = minisafed.rpc.getnewaddress()["address"]
addr = lianad.rpc.getnewaddress()["address"]
bitcoind.rpc.sendtoaddress(addr, 0.2567)
wait_for(lambda: len(minisafed.rpc.listcoins()["coins"]) > 0)
outpoints = [c["outpoint"] for c in minisafed.rpc.listcoins()["coins"]]
wait_for(lambda: len(lianad.rpc.listcoins()["coins"]) > 0)
outpoints = [c["outpoint"] for c in lianad.rpc.listcoins()["coins"]]
destinations = {
bitcoind.rpc.getnewaddress(): 200_000,
}
res = minisafed.rpc.createspend(destinations, outpoints, 6)
res = lianad.rpc.createspend(destinations, outpoints, 6)
psbt = PSBT.from_base64(res["psbt"])
txid = psbt.tx.txid().hex()
# We can't broadcast an unknown Spend
with pytest.raises(RpcError, match="Unknown spend transaction.*"):
minisafed.rpc.broadcastspend(txid)
minisafed.rpc.updatespend(res["psbt"])
lianad.rpc.broadcastspend(txid)
lianad.rpc.updatespend(res["psbt"])
# We can't broadcast an unsigned transaction
with pytest.raises(RpcError, match="Failed to finalize the spend transaction.*"):
minisafed.rpc.broadcastspend(txid)
signed_psbt = minisafed.sign_psbt(PSBT.from_base64(res["psbt"]))
minisafed.rpc.updatespend(signed_psbt.to_base64())
lianad.rpc.broadcastspend(txid)
signed_psbt = lianad.sign_psbt(PSBT.from_base64(res["psbt"]))
lianad.rpc.updatespend(signed_psbt.to_base64())
# Now we've signed and stored it, the daemon will take care of finalizing
# the PSBT before broadcasting the transaction.
minisafed.rpc.broadcastspend(txid)
lianad.rpc.broadcastspend(txid)
def test_start_rescan(minisafed, bitcoind):
def test_start_rescan(lianad, bitcoind):
"""Test we successfully retrieve all our transactions after losing state by rescanning."""
initial_timestamp = int(time.time())
first_address = minisafed.rpc.getnewaddress()
second_address = minisafed.rpc.getnewaddress()
first_address = lianad.rpc.getnewaddress()
second_address = lianad.rpc.getnewaddress()
# Some utility functions to DRY
list_coins = lambda: minisafed.rpc.listcoins()["coins"]
list_coins = lambda: lianad.rpc.listcoins()["coins"]
unspent_coins = lambda: (
c for c in minisafed.rpc.listcoins()["coins"] if c["spend_info"] is None
c for c in lianad.rpc.listcoins()["coins"] if c["spend_info"] is None
)
sorted_coins = lambda: sorted(list_coins(), key=lambda c: c["outpoint"])
@ -283,24 +283,24 @@ def test_start_rescan(minisafed, bitcoind):
# We can rescan from one second before the tip timestamp, that's almost a no-op.
tip_timestamp = bitcoind.rpc.getblockheader(bitcoind.rpc.getbestblockhash())["time"]
minisafed.rpc.startrescan(tip_timestamp - 1)
wait_for(lambda: minisafed.rpc.getinfo()["rescan_progress"] is None)
lianad.rpc.startrescan(tip_timestamp - 1)
wait_for(lambda: lianad.rpc.getinfo()["rescan_progress"] is None)
# We can't rescan from an insane timestamp though.
with pytest.raises(RpcError, match="Insane timestamp.*"):
minisafed.rpc.startrescan(tip_timestamp)
assert minisafed.rpc.getinfo()["rescan_progress"] is None
lianad.rpc.startrescan(tip_timestamp)
assert lianad.rpc.getinfo()["rescan_progress"] is None
future_timestamp = tip_timestamp + 60 * 60
with pytest.raises(RpcError, match="Insane timestamp.*"):
minisafed.rpc.startrescan(future_timestamp)
assert minisafed.rpc.getinfo()["rescan_progress"] is None
lianad.rpc.startrescan(future_timestamp)
assert lianad.rpc.getinfo()["rescan_progress"] is None
prebitcoin_timestamp = 1231006505 - 1
with pytest.raises(RpcError, match="Insane timestamp."):
minisafed.rpc.startrescan(prebitcoin_timestamp)
assert minisafed.rpc.getinfo()["rescan_progress"] is None
lianad.rpc.startrescan(prebitcoin_timestamp)
assert lianad.rpc.getinfo()["rescan_progress"] is None
# First, get some coins
for _ in range(10):
addr = minisafed.rpc.getnewaddress()["address"]
addr = lianad.rpc.getnewaddress()["address"]
amount = random.randint(1, COIN * 10) / COIN
txid = bitcoind.rpc.sendtoaddress(addr, amount)
bitcoind.generate_block(random.randint(1, 10), wait_for_mempool=txid)
@ -310,20 +310,20 @@ def test_start_rescan(minisafed, bitcoind):
# TODO: instead of having randomness we should lay down all different cases (with or
# without change, single or multiple inputs, sending externally or to self).
for _ in range(5):
addr = minisafed.rpc.getnewaddress()["address"]
addr = lianad.rpc.getnewaddress()["address"]
amount = random.randint(1, COIN * 10) / COIN
txid = bitcoind.rpc.sendtoaddress(addr, amount)
avail = list(unspent_coins())
to_spend = random.sample(avail, random.randint(1, len(avail)))
spend_coins(minisafed, bitcoind, to_spend)
spend_coins(lianad, bitcoind, to_spend)
bitcoind.generate_block(random.randint(1, 5), wait_for_mempool=2)
wait_for(lambda: all_spent(to_spend))
wait_for(
lambda: minisafed.rpc.getinfo()["blockheight"] == bitcoind.rpc.getblockcount()
lambda: lianad.rpc.getinfo()["blockheight"] == bitcoind.rpc.getblockcount()
)
# Receiving addresses are derived at much higher indexes now.
assert minisafed.rpc.getnewaddress() not in (first_address, second_address)
assert lianad.rpc.getnewaddress() not in (first_address, second_address)
# Move time forward one day as bitcoind will rescan the last 2 hours of block upon
# importing a descriptor.
@ -336,23 +336,23 @@ def test_start_rescan(minisafed, bitcoind):
# and watchonly wallet. Those won't be aware of past coins for the configured
# descriptor.
coins_before = sorted_coins()
minisafed.restart_fresh(bitcoind)
lianad.restart_fresh(bitcoind)
assert len(list_coins()) == 0
# The wallet isn't aware what derivation indexes were used. Necessarily it'll start
# from 0.
assert minisafed.rpc.getnewaddress() == first_address
assert lianad.rpc.getnewaddress() == first_address
# Once the rescan is done, we must have detected all previous transactions.
minisafed.rpc.startrescan(initial_timestamp)
rescan_progress = minisafed.rpc.getinfo()["rescan_progress"]
lianad.rpc.startrescan(initial_timestamp)
rescan_progress = lianad.rpc.getinfo()["rescan_progress"]
assert rescan_progress is None or 0 <= rescan_progress <= 1
wait_for(lambda: minisafed.rpc.getinfo()["rescan_progress"] is None)
wait_for(lambda: lianad.rpc.getinfo()["rescan_progress"] is None)
wait_for(
lambda: minisafed.rpc.getinfo()["blockheight"] == bitcoind.rpc.getblockcount()
lambda: lianad.rpc.getinfo()["blockheight"] == bitcoind.rpc.getblockcount()
)
assert coins_before == sorted_coins()
# Now that it caught up it noticed which one were used onchain, so it won't reuse
# this derivation indexes anymore.
assert minisafed.rpc.getnewaddress() not in (first_address, second_address)
assert lianad.rpc.getnewaddress() not in (first_address, second_address)

View File

@ -3,22 +3,22 @@ from test_framework.serializations import PSBT
from test_framework.utils import wait_for
def test_spend_change(minisafed, bitcoind):
def test_spend_change(lianad, bitcoind):
"""We can spend a coin that was received on a change address."""
# Receive a coin on a receive address
addr = minisafed.rpc.getnewaddress()["address"]
addr = lianad.rpc.getnewaddress()["address"]
txid = bitcoind.rpc.sendtoaddress(addr, 0.01)
bitcoind.generate_block(1, wait_for_mempool=txid)
wait_for(lambda: len(minisafed.rpc.listcoins()["coins"]) == 1)
wait_for(lambda: len(lianad.rpc.listcoins()["coins"]) == 1)
# Create a transaction that will spend this coin to 1) one of our receive
# addresses 2) an external address 3) one of our change addresses.
outpoints = [c["outpoint"] for c in minisafed.rpc.listcoins()["coins"]]
outpoints = [c["outpoint"] for c in lianad.rpc.listcoins()["coins"]]
destinations = {
bitcoind.rpc.getnewaddress(): 100_000,
minisafed.rpc.getnewaddress()["address"]: 100_000,
lianad.rpc.getnewaddress()["address"]: 100_000,
}
res = minisafed.rpc.createspend(destinations, outpoints, 2)
res = lianad.rpc.createspend(destinations, outpoints, 2)
assert "psbt" in res
# The transaction must contain a change output.
@ -27,29 +27,29 @@ def test_spend_change(minisafed, bitcoind):
assert len(spend_psbt.tx.vout) == 3
# Sign and broadcast this first Spend transaction.
signed_psbt = minisafed.sign_psbt(spend_psbt)
minisafed.rpc.updatespend(signed_psbt.to_base64())
signed_psbt = lianad.sign_psbt(spend_psbt)
lianad.rpc.updatespend(signed_psbt.to_base64())
spend_txid = signed_psbt.tx.txid().hex()
minisafed.rpc.broadcastspend(spend_txid)
lianad.rpc.broadcastspend(spend_txid)
bitcoind.generate_block(1, wait_for_mempool=spend_txid)
wait_for(lambda: len(minisafed.rpc.listcoins()["coins"]) == 3)
wait_for(lambda: len(lianad.rpc.listcoins()["coins"]) == 3)
# Now create a new transaction that spends the change output as well as
# the output sent to the receive address.
outpoints = [
c["outpoint"]
for c in minisafed.rpc.listcoins()["coins"]
for c in lianad.rpc.listcoins()["coins"]
if c["spend_info"] is None
]
destinations = {
bitcoind.rpc.getnewaddress(): 100_000,
}
res = minisafed.rpc.createspend(destinations, outpoints, 2)
res = lianad.rpc.createspend(destinations, outpoints, 2)
spend_psbt = PSBT.from_base64(res["psbt"])
# We can sign and broadcast it.
signed_psbt = minisafed.sign_psbt(spend_psbt)
minisafed.rpc.updatespend(signed_psbt.to_base64())
signed_psbt = lianad.sign_psbt(spend_psbt)
lianad.rpc.updatespend(signed_psbt.to_base64())
spend_txid = signed_psbt.tx.txid().hex()
minisafed.rpc.broadcastspend(spend_txid)
lianad.rpc.broadcastspend(spend_txid)
bitcoind.generate_block(1, wait_for_mempool=spend_txid)