From 3a7c151674c487dd11f4f2d1db78d468f5f087a9 Mon Sep 17 00:00:00 2001 From: jp1ac4 <121959000+jp1ac4@users.noreply.github.com> Date: Tue, 19 Mar 2024 10:45:32 +0000 Subject: [PATCH] database: allow for coinbase transactions to change addresses `is_change` is `true` for a coin if its address is derived from our change descriptor and could in principle be used for a coinbase transaction. The functional test was provided by darosior in a PR comment: https://github.com/wizardsardine/liana/pull/1001#pullrequestreview-1948564150 --- src/database/sqlite/mod.rs | 2 +- src/database/sqlite/schema.rs | 9 ++------- tests/test_misc.py | 14 ++++++++++++++ 3 files changed, 17 insertions(+), 8 deletions(-) diff --git a/src/database/sqlite/mod.rs b/src/database/sqlite/mod.rs index 0433c42e..52e660ad 100644 --- a/src/database/sqlite/mod.rs +++ b/src/database/sqlite/mod.rs @@ -1457,7 +1457,7 @@ CREATE TABLE spend_transactions ( block_info: None, amount: bitcoin::Amount::from_sat(424242), derivation_index: bip32::ChildNumber::from_normal_idx(4103).unwrap(), - is_change: false, // Cannot be both a coinbase deposit and change. + is_change: false, spend_txid: None, spend_block: None, }; diff --git a/src/database/sqlite/schema.rs b/src/database/sqlite/schema.rs index 6add975a..ac101bcd 100644 --- a/src/database/sqlite/schema.rs +++ b/src/database/sqlite/schema.rs @@ -39,8 +39,8 @@ CREATE TABLE wallets ( * transaction for this coin exists and was confirmed. * * The 'is_immature' field is for coinbase deposits that are not yet buried under 100 - * blocks. Note coinbase deposits can't be change. They also technically can't be - * unconfirmed but we keep them as such until they become mature. + * blocks. Note coinbase deposits can't technically be unconfirmed but we keep them + * as such until they become mature. */ CREATE TABLE coins ( id INTEGER PRIMARY KEY NOT NULL, @@ -56,7 +56,6 @@ CREATE TABLE coins ( spend_block_height INTEGER, spend_block_time INTEGER, is_immature BOOLEAN NOT NULL CHECK (is_immature IN (0,1)), - CHECK (is_change IS 0 OR is_immature IS 0), UNIQUE (txid, vout), FOREIGN KEY (wallet_id) REFERENCES wallets (id) ON UPDATE RESTRICT @@ -217,10 +216,6 @@ impl TryFrom<&rusqlite::Row<'_>> for DbCoin { }); let is_immature: bool = row.get(12)?; - assert!( - !is_immature || !is_change, - "A coin cannot be both created in a coinbase and be change" - ); Ok(DbCoin { id, diff --git a/tests/test_misc.py b/tests/test_misc.py index f4a81c5f..c7f29a6d 100644 --- a/tests/test_misc.py +++ b/tests/test_misc.py @@ -239,6 +239,20 @@ def test_coinbase_deposit(lianad, bitcoind): and coin["spend_info"] is not None ) + # We must also properly detect coinbase deposits to a change address. We used to have + # an assertion that a coin cannot both be change and a coinbase deposit. Since change + # is determined by the address... Technically we can. + change_desc = lianad.multi_desc.singlepath_descriptors()[1] + change_addr = bitcoind.rpc.deriveaddresses(str(change_desc), [0, 0])[0] + bitcoind.rpc.generatetoaddress(1, change_addr) + wait_for(lambda: any(c["is_immature"] for c in lianad.rpc.listcoins()["coins"])) + coin = next(c for c in lianad.rpc.listcoins()["coins"] if c["is_immature"]) + assert coin["is_change"] + bitcoind.generate_block(100) + wait_for_sync() + coin = next(c for c in lianad.rpc.listcoins()["coins"] if c["outpoint"] == coin["outpoint"]) + assert not coin["is_immature"] and coin["block_height"] is not None + @pytest.mark.skipif( OLD_LIANAD_PATH is None or USE_TAPROOT, reason="Need the old lianad binary to create the datadir."