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
This commit is contained in:
jp1ac4 2024-03-19 10:45:32 +00:00
parent cde2adbdce
commit 3a7c151674
No known key found for this signature in database
GPG Key ID: A7ACD32423568D7B
3 changed files with 17 additions and 8 deletions

View File

@ -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,
};

View File

@ -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,

View File

@ -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."