Core Lightning implementation of BOLT #11 invoices - part 7

LIVE #23February 29, 2024

In this live, we try to understand how Core Lightning stores invoices generated with the invoice command in lightningd.sqlite3 database. We also write a command that stores integers in a newly created table in lightningd.sqlite3 database.

Terminal session

Terminal 1

We ran the following commands in this order:

# TERMINAL 1

$ make clean
$ ./configure --disable-rust
$ make -j12
$ source contrib/startup_regtest.sh
$ start_ln
$ alias l1-cli
$ l1-cli getinfo | jq .id
$ l1-cli invoice 10000 label-0 desc
$ ps -ax | rg lightningd
$ l1-cli stop && /home/tony/work/repos/lightning/lightningd/lightningd --network=regtest --lightning-dir=/tmp/l1-regtest --bitcoin-datadir=/home/tony/.bitcoin --database-upgrade=true --daemon
$ l1-cli getinfo | jq .id
$ l1-cli stop && /home/tony/work/repos/lightning/lightningd/lightningd --network=regtest --lightning-dir=/tmp/l1-regtest --bitcoin-datadir=/home/tony/.bitcoin --database-upgrade=true --daemon
$ l1-cli foo
$ l1-cli foo 2
$ l1-cli stop && /home/tony/work/repos/lightning/lightningd/lightningd --network=regtest --lightning-dir=/tmp/l1-regtest --bitcoin-datadir=/home/tony/.bitcoin --database-upgrade=true --daemon
$ l1-cli foo 2
$ l1-cli foo 3
$ l1-cli stop && /home/tony/work/repos/lightning/lightningd/lightningd --network=regtest --lightning-dir=/tmp/l1-regtest --bitcoin-datadir=/home/tony/.bitcoin --database-upgrade=true --daemon
$ l1-cli foo 4
$ l1-cli stop && /home/tony/work/repos/lightning/lightningd/lightningd --network=regtest --lightning-dir=/tmp/l1-regtest --bitcoin-datadir=/home/tony/.bitcoin --database-upgrade=true --daemon
$ l1-cli foo 4
$ l1-cli foo 6
$ l1-cli stop && /home/tony/work/repos/lightning/lightningd/lightningd --network=regtest --lightning-dir=/tmp/l1-regtest --bitcoin-datadir=/home/tony/.bitcoin --database-upgrade=true --daemon
$ l1-cli foo 2
$ l1-cli foo 12

And below you can read the terminal session (command lines and outputs):

# TERMINAL 1

◉ tony@tony:~/work/repos/lightning:[git»(HEAD detached at v23.11)]
$ make clean
...
◉ tony@tony:~/work/repos/lightning:[git»(HEAD detached at v23.11)]
$ ./configure --disable-rust
...
◉ tony@tony:~/work/repos/lightning:[git»(HEAD detached at v23.11)]
$ make -j12
...
◉ tony@tony:~/work/repos/lightning:[git»(HEAD detached at v23.11)]
$ source contrib/startup_regtest.sh
lightning-cli is /home/tony/work/repos/lightning/cli/lightning-cli
lightningd is /home/tony/work/repos/lightning/lightningd/lightningd
Useful commands:
  start_ln 3: start three nodes, l1, l2, l3
  connect 1 2: connect l1 and l2
  fund_nodes: connect all nodes with channels, in a row
  stop_ln: shutdown
  destroy_ln: remove ln directories
◉ tony@tony:~/work/repos/lightning:[git»(HEAD detached at v23.11)]
$ start_ln
Bitcoin Core starting
awaiting bitcoind...
Making "default" bitcoind wallet.
[1] 3349814
[2] 3349853
WARNING: eatmydata not found: install it for faster testing
Commands:
        l1-cli, l1-log,
        l2-cli, l2-log,
        bt-cli, stop_ln, fund_nodes
◉ tony@tony:~/work/repos/lightning:[git»(HEAD detached at v23.11)]
$ alias l1-cli
alias l1-cli='/home/tony/work/repos/lightning/cli/lightning-cli --lightning-dir=/tmp/l1-regtest'
◉ tony@tony:~/work/repos/lightning:[git»(HEAD detached at v23.11)]
$ l1-cli getinfo | jq .id
"0278a62b03e9edf3d1cea4a15256969639b0e4ac4966c6100cd1886fefe2936b42"
◉ tony@tony:~/work/repos/lightning:[git»(HEAD detached at v23.11)]
$ l1-cli invoice 10000 label-0 desc
{
   "payment_hash": "341495b11798d5b2b93acb89e0a8f9479931f46431210608fec0c0778ef13a55",
   "expires_at": 1709824397,
   "bolt11": "lnbcrt100n1pj7p8cdsp5qz47kpw8j6z38smmcfpf68hsru4qc60uu9a04mven2503q74suxqpp5xs2ftvghnr2m9wf6ewy7p28eg7vnraryxyssvz87crq80rh38f2sdq8v3jhxccxqyjw5qcqp2fp4prl9c6yr7c2lfm74muuyrwuyusx99fz7kdfr2ecg4lk0hqjmh6n5q9qx3qysgq59smg540yslwz2nl92z33khkpsnglad7xpkc6gmu6u2zfs8v723h476z056k0f8uf94vnwnl5hp6s6r9tq83pw0kp0v07258sf2ufzqp7r3vsh",
   "payment_secret": "00abeb05c7968513c37bc2429d1ef01f2a0c69fce17afaed999aa8f883d5870c",
   "created_index": 1,
   "warning_capacity": "Insufficient incoming channel capacity to pay invoice"
}
◉ tony@tony:~/work/repos/lightning:[git»(HEAD detached at v23.11)]
$ ps -ax | rg lightningd
3349816 pts/0    S      0:00 /home/tony/work/repos/lightning/lightningd/lightningd --network=regtest --lightning-dir=/tmp/l1-regtest --bitcoin-datadir=/home/tony/.bitcoin --database-upgrade=true
3349855 pts/0    S      0:00 /home/tony/work/repos/lightning/lightningd/lightningd --network=regtest --lightning-dir=/tmp/l2-regtest --bitcoin-datadir=/home/tony/.bitcoin --database-upgrade=true
3349868 pts/0    SL     0:00 /home/tony/work/repos/lightning/lightningd/lightning_hsmd --developer
3349892 pts/0    SL     0:00 /home/tony/work/repos/lightning/lightningd/lightning_hsmd --developer
3349893 pts/0    S      0:00 /home/tony/work/repos/lightning/lightningd/lightning_connectd --developer
3349903 pts/0    S      0:00 /home/tony/work/repos/lightning/lightningd/lightning_connectd --developer
3349904 pts/0    S      0:00 /home/tony/work/repos/lightning/lightningd/lightning_gossipd --developer
3349917 pts/0    S      0:00 /home/tony/work/repos/lightning/lightningd/lightning_gossipd --developer
3350334 pts/1    S+     0:00 sqlite3 /tmp/l1-regtest/regtest/lightningd.sqlite3
3366956 pts/0    S+     0:00 rg lightningd
◉ tony@tony:~/work/repos/lightning:[git»(HEAD detached at v23.11)]
$ l1-cli stop && /home/tony/work/repos/lightning/lightningd/lightningd --network=regtest --lightning-dir=/tmp/l1-regtest --bitcoin-datadir=/home/tony/.bitcoin --database-upgrade=true --daemon
"Shutdown complete"
◉ tony@tony:~/work/repos/lightning:[git»(HEAD detached at v23.11)]
$ l1-cli getinfo | jq .id
"0278a62b03e9edf3d1cea4a15256969639b0e4ac4966c6100cd1886fefe2936b42"
◉ tony@tony:~/work/repos/lightning:[git»(HEAD detached at v23.11)]
$ l1-cli stop && /home/tony/work/repos/lightning/lightningd/lightningd --network=regtest --lightning-dir=/tmp/l1-regtest --bitcoin-datadir=/home/tony/.bitcoin --database-upgrade=true --daemon
"Shutdown complete"
◉ tony@tony:~/work/repos/lightning:[git»(HEAD detached at v23.11)]
$ l1-cli foo
{
   "added-to-database": 1
}
◉ tony@tony:~/work/repos/lightning:[git»(HEAD detached at v23.11)]
$ l1-cli foo 2
{
   "code": -32602,
   "message": "too many parameters: got 1, expected 0"
}
◉ tony@tony:~/work/repos/lightning:[git»(HEAD detached at v23.11)]
$ l1-cli stop && /home/tony/work/repos/lightning/lightningd/lightningd --network=regtest --lightning-dir=/tmp/l1-regtest --bitcoin-datadir=/home/tony/.bitcoin --database-upgrade=true --daemon
"Shutdown complete"
◉ tony@tony:~/work/repos/lightning:[git»(HEAD detached at v23.11)]
$ l1-cli foo 2
{
   "added-to-database": 2
}
◉ tony@tony:~/work/repos/lightning:[git»(HEAD detached at v23.11)]
$ l1-cli foo 3
{
   "added-to-database": 3
}
◉ tony@tony:~/work/repos/lightning:[git»(HEAD detached at v23.11)]
$ l1-cli stop && /home/tony/work/repos/lightning/lightningd/lightningd --network=regtest --lightning-dir=/tmp/l1-regtest --bitcoin-datadir=/home/tony/.bitcoin --database-upgrade=true --daemon
"Shutdown complete"
◉ tony@tony:~/work/repos/lightning:[git»(HEAD detached at v23.11)]
$ l1-cli foo 4
{
   "added-to-database": 4
}
◉ tony@tony:~/work/repos/lightning:[git»(HEAD detached at v23.11)]
$ l1-cli stop && /home/tony/work/repos/lightning/lightningd/lightningd --network=regtest --lightning-dir=/tmp/l1-regtest --bitcoin-datadir=/home/tony/.bitcoin --database-upgrade=true --daemon
"Shutdown complete"
◉ tony@tony:~/work/repos/lightning:[git»(HEAD detached at v23.11)]
$ l1-cli foo 4
{
   "added-to-database": 4
}
◉ tony@tony:~/work/repos/lightning:[git»(HEAD detached at v23.11)]
$ l1-cli foo 6
{
   "added-to-database": 6
}
◉ tony@tony:~/work/repos/lightning:[git»(HEAD detached at v23.11)]
$ l1-cli stop && /home/tony/work/repos/lightning/lightningd/lightningd --network=regtest --lightning-dir=/tmp/l1-regtest --bitcoin-datadir=/home/tony/.bitcoin --database-upgrade=true --daemon
"Shutdown complete"
◉ tony@tony:~/work/repos/lightning:[git»(HEAD detached at v23.11)]
$ l1-cli foo 2
{
   "added-to-database": 2
}
◉ tony@tony:~/work/repos/lightning:[git»(HEAD detached at v23.11)]
$ l1-cli foo 12
{
   "added-to-database": 12
}

Terminal 2

# TERMINAL 2

◉ tony@tony:~:
$ cd -- ./work/repos/lightning
◉ tony@tony:~/work/repos/lightning:[git»(HEAD detached at v23.11)]
$ sqlite3 /tmp/l1-regtest/regtest/
accounts.sqlite3    lightningd.sqlite3
◉ tony@tony:~/work/repos/lightning:[git»(HEAD detached at v23.11)]
$ sqlite3 /tmp/l1-regtest/regtest/lightningd.sqlite3
SQLite version 3.37.2 2022-01-06 13:25:41
Enter ".help" for usage hints.
sqlite> .tables
blocks                     local_anchors
channel_blockheights       offers
channel_configs            outputs
channel_feerates           payments
channel_funding_inflights  peers
channel_htlcs              penalty_bases
channel_state_changes      runes
channels                   runes_blacklist
channeltxs                 shachain_known
datastore                  shachains
db_upgrades                transaction_annotations
forwards                   transactions
htlc_sigs                  utxoset
invoice_fallbacks          vars
invoicerequests            version
invoices
sqlite> SELECT * FROM version;
249
sqlite> SELECT * FROM invoices;
sqlite>
sqlite> SELECT * FROM invoices;
1|0|10000|<...>|<...>|label-0|1709824397||||lnbcrt100n1pj7p8cdsp5qz47kpw8j6z38smmcfpf68hsru4qc60uu9a04mven2503q74suxqpp5xs2ftvghnr2m9wf6ewy7p28eg7vnraryxyssvz87crq80rh38f2sdq8v3jhxccxqyjw5qcqp2fp4prl9c6yr7c2lfm74muuyrwuyusx99fz7kdfr2ecg4lk0hqjmh6n5q9qx3qysgq59smg540yslwz2nl92z33khkpsnglad7xpkc6gmu6u2zfs8v723h476z056k0f8uf94vnwnl5hp6s6r9tq83pw0kp0v07258sf2ufzqp7r3vsh|desc|||0||
sqlite> .tables
blocks                     invoices
channel_blockheights       local_anchors
channel_configs            offers
channel_feerates           outputs
channel_funding_inflights  payments
channel_htlcs              peers
channel_state_changes      penalty_bases
channels                   runes
channeltxs                 runes_blacklist
datastore                  shachain_known
db_upgrades                shachains
foo                        transaction_annotations
forwards                   transactions
htlc_sigs                  utxoset
invoice_fallbacks          vars
invoicerequests            version
sqlite> SELECT * FROM foo;
sqlite> SELECT * FROM foo;
sqlite>
sqlite> SELECT * FROM foo;
sqlite> SELECT * FROM foo;
1
sqlite> SELECT * FROM foo;
1
1
sqlite> SELECT * FROM foo;
1
1
sqlite> SELECT * FROM foo;
1
1
2
sqlite> SELECT * FROM foo;
1
1
2
12

Core Lightning Source Code

dbmigrations

dbmigrations in lightning:wallet/db.c

/* Do not reorder or remove elements from this array, it is used to
 * migrate existing databases from a previous state, based on the
 * string indices */
static struct migration dbmigrations[] = {
  {SQL("CREATE TABLE version (version INTEGER)"), NULL},
  {SQL("INSERT INTO version VALUES (1)"), NULL},
  {SQL("CREATE TABLE outputs ("
       "  prev_out_tx BLOB"
       ", prev_out_index INTEGER"
       ", value BIGINT"
       ", type INTEGER"
       ", status INTEGER"
       ", keyindex INTEGER"
       ", PRIMARY KEY (prev_out_tx, prev_out_index));"),
   NULL},
  {SQL("CREATE TABLE vars ("
       "  name VARCHAR(32)"
       ", val VARCHAR(255)"
       ", PRIMARY KEY (name)"
       ");"),
   NULL},
  {SQL("CREATE TABLE shachains ("
       "  id BIGSERIAL"
       ", min_index BIGINT"
       ", num_valid BIGINT"
       ", PRIMARY KEY (id)"
       ");"),
   NULL},
  {SQL("CREATE TABLE shachain_known ("
       "  shachain_id BIGINT REFERENCES shachains(id) ON DELETE CASCADE"
       ", pos INTEGER"
       ", idx BIGINT"
       ", hash BLOB"
       ", PRIMARY KEY (shachain_id, pos)"
       ");"),
   NULL},
  {SQL("CREATE TABLE peers ("
       "  id BIGSERIAL"
       ", node_id BLOB UNIQUE" /* pubkey */
       ", address TEXT"
       ", PRIMARY KEY (id)"
       ");"),
   NULL},
  {SQL("CREATE TABLE channels ("
       "  id BIGSERIAL," /* chan->id */
       /* FIXME: We deliberately never delete a peer with channels, so this constraint is
        * unnecessary! */
       "  peer_id BIGINT REFERENCES peers(id) ON DELETE CASCADE,"
       "  short_channel_id TEXT,"
       "  channel_config_local BIGINT,"
       "  channel_config_remote BIGINT,"
       "  state INTEGER,"
       "  funder INTEGER,"
       "  channel_flags INTEGER,"
       "  minimum_depth INTEGER,"
       "  next_index_local BIGINT,"
       "  next_index_remote BIGINT,"
       "  next_htlc_id BIGINT,"
       "  funding_tx_id BLOB,"
       "  funding_tx_outnum INTEGER,"
       "  funding_satoshi BIGINT,"
       "  funding_locked_remote INTEGER,"
       "  push_msatoshi BIGINT,"
       "  msatoshi_local BIGINT," /* our_msatoshi */
       /* START channel_info */
       "  fundingkey_remote BLOB,"
       "  revocation_basepoint_remote BLOB,"
       "  payment_basepoint_remote BLOB,"
       "  htlc_basepoint_remote BLOB,"
       "  delayed_payment_basepoint_remote BLOB,"
       "  per_commit_remote BLOB,"
       "  old_per_commit_remote BLOB,"
       "  local_feerate_per_kw INTEGER,"
       "  remote_feerate_per_kw INTEGER,"
       /* END channel_info */
       "  shachain_remote_id BIGINT,"
       "  shutdown_scriptpubkey_remote BLOB,"
       "  shutdown_keyidx_local BIGINT,"
       "  last_sent_commit_state BIGINT,"
       "  last_sent_commit_id INTEGER,"
       "  last_tx BLOB,"
       "  last_sig BLOB,"
       "  closing_fee_received INTEGER,"
       "  closing_sig_received BLOB,"
       "  PRIMARY KEY (id)"
       ");"),
   NULL},
  {SQL("CREATE TABLE channel_configs ("
       "  id BIGSERIAL,"
       "  dust_limit_satoshis BIGINT,"
       "  max_htlc_value_in_flight_msat BIGINT,"
       "  channel_reserve_satoshis BIGINT,"
       "  htlc_minimum_msat BIGINT,"
       "  to_self_delay INTEGER,"
       "  max_accepted_htlcs INTEGER,"
       "  PRIMARY KEY (id)"
       ");"),
   NULL},
  {SQL("CREATE TABLE channel_htlcs ("
       "  id BIGSERIAL,"
       "  channel_id BIGINT REFERENCES channels(id) ON DELETE CASCADE,"
       "  channel_htlc_id BIGINT,"
       "  direction INTEGER,"
       "  origin_htlc BIGINT,"
       "  msatoshi BIGINT,"
       "  cltv_expiry INTEGER,"
       "  payment_hash BLOB,"
       "  payment_key BLOB,"
       "  routing_onion BLOB,"
       "  failuremsg BLOB," /* Note: This is in fact the failure onionreply,
                             * but renaming columns is hard! */
       "  malformed_onion INTEGER,"
       "  hstate INTEGER,"
       "  shared_secret BLOB,"
       "  PRIMARY KEY (id),"
       "  UNIQUE (channel_id, channel_htlc_id, direction)"
       ");"),
   NULL},
  {SQL("CREATE TABLE invoices ("
       "  id BIGSERIAL,"
       "  state INTEGER,"
       "  msatoshi BIGINT,"
       "  payment_hash BLOB,"
       "  payment_key BLOB,"
       "  label TEXT,"
       "  PRIMARY KEY (id),"
       "  UNIQUE (label),"
       "  UNIQUE (payment_hash)"
       ");"),
   NULL},
  {SQL("CREATE TABLE payments ("
       "  id BIGSERIAL,"
       "  timestamp INTEGER,"
       "  status INTEGER,"
       "  payment_hash BLOB,"
       "  direction INTEGER,"
       "  destination BLOB,"
       "  msatoshi BIGINT,"
       "  PRIMARY KEY (id),"
       "  UNIQUE (payment_hash)"
       ");"),
   NULL},
  /* Add expiry field to invoices (effectively infinite). */
  {SQL("ALTER TABLE invoices ADD expiry_time BIGINT;"), NULL},
  {SQL("UPDATE invoices SET expiry_time=9223372036854775807;"), NULL},
  /* Add pay_index field to paid invoices (initially, same order as id). */
  {SQL("ALTER TABLE invoices ADD pay_index BIGINT;"), NULL},
  {SQL("CREATE UNIQUE INDEX invoices_pay_index ON invoices(pay_index);"),
   NULL},
  {SQL("UPDATE invoices SET pay_index=id WHERE state=1;"),
   NULL}, /* only paid invoice */
  /* Create next_pay_index variable (highest pay_index). */
  {SQL("INSERT INTO vars(name, val)"
       "  VALUES('next_pay_index', "
       "    COALESCE((SELECT MAX(pay_index) FROM invoices WHERE state=1), 0) "
       "+ 1"
       "  );"),
   NULL},
  /* Create first_block field; initialize from channel id if any.
   * This fails for channels still awaiting lockin, but that only applies to
   * pre-release software, so it's forgivable. */
  {SQL("ALTER TABLE channels ADD first_blocknum BIGINT;"), NULL},
  {SQL("UPDATE channels SET first_blocknum=1 WHERE short_channel_id IS NOT NULL;"),
   NULL},
  {SQL("ALTER TABLE outputs ADD COLUMN channel_id BIGINT;"), NULL},
  {SQL("ALTER TABLE outputs ADD COLUMN peer_id BLOB;"), NULL},
  {SQL("ALTER TABLE outputs ADD COLUMN commitment_point BLOB;"), NULL},
  {SQL("ALTER TABLE invoices ADD COLUMN msatoshi_received BIGINT;"), NULL},
  /* Normally impossible, so at least we'll know if databases are ancient. */
  {SQL("UPDATE invoices SET msatoshi_received=0 WHERE state=1;"), NULL},
  {SQL("ALTER TABLE channels ADD COLUMN last_was_revoke INTEGER;"), NULL},
  /* We no longer record incoming payments: invoices cover that.
   * Without ALTER_TABLE DROP COLUMN support we need to do this by
   * rename & copy, which works because there are no triggers etc. */
  {SQL("ALTER TABLE payments RENAME TO temp_payments;"), NULL},
  {SQL("CREATE TABLE payments ("
       "  id BIGSERIAL,"
       "  timestamp INTEGER,"
       "  status INTEGER,"
       "  payment_hash BLOB,"
       "  destination BLOB,"
       "  msatoshi BIGINT,"
       "  PRIMARY KEY (id),"
       "  UNIQUE (payment_hash)"
       ");"),
   NULL},
  {SQL("INSERT INTO payments SELECT id, timestamp, status, payment_hash, "
       "destination, msatoshi FROM temp_payments WHERE direction=1;"),
   NULL},
  {SQL("DROP TABLE temp_payments;"), NULL},
  /* We need to keep the preimage in case they ask to pay again. */
  {SQL("ALTER TABLE payments ADD COLUMN payment_preimage BLOB;"), NULL},
  /* We need to keep the shared secrets to decode error returns. */
  {SQL("ALTER TABLE payments ADD COLUMN path_secrets BLOB;"), NULL},
  /* Create time-of-payment of invoice, default already-paid
   * invoices to current time. */
  {SQL("ALTER TABLE invoices ADD paid_timestamp BIGINT;"), NULL},
  {SQL("UPDATE invoices"
       "   SET paid_timestamp = CURRENT_TIMESTAMP()"
       " WHERE state = 1;"),
   NULL},
  /* We need to keep the route node pubkeys and short channel ids to
   * correctly mark routing failures. We separate short channel ids
   * because we cannot safely save them as blobs due to byteorder
   * concerns. */
  {SQL("ALTER TABLE payments ADD COLUMN route_nodes BLOB;"), NULL},
  {SQL("ALTER TABLE payments ADD COLUMN route_channels BLOB;"), NULL},
  {SQL("CREATE TABLE htlc_sigs (channelid INTEGER REFERENCES channels(id) ON "
       "DELETE CASCADE, signature BLOB);"),
   NULL},
  {SQL("CREATE INDEX channel_idx ON htlc_sigs (channelid)"), NULL},
  /* Get rid of OPENINGD entries; we don't put them in db any more */
  {SQL("DELETE FROM channels WHERE state=1"), NULL},
  /* Keep track of db upgrades, for debugging */
  {SQL("CREATE TABLE db_upgrades (upgrade_from INTEGER, lightning_version "
       "TEXT);"),
   NULL},
  /* We used not to clean up peers when their channels were gone. */
  {SQL("DELETE FROM peers WHERE id NOT IN (SELECT peer_id FROM channels);"),
   NULL},
  /* The ONCHAIND_CHEATED/THEIR_UNILATERAL/OUR_UNILATERAL/MUTUAL are now one
   */
  {SQL("UPDATE channels SET STATE = 8 WHERE state > 8;"), NULL},
  /* Add bolt11 to invoices table*/
  {SQL("ALTER TABLE invoices ADD bolt11 TEXT;"), NULL},
  /* What do we think the head of the blockchain looks like? Used
   * primarily to track confirmations across restarts and making
   * sure we handle reorgs correctly. */
  {SQL("CREATE TABLE blocks (height INT, hash BLOB, prev_hash BLOB, "
       "UNIQUE(height));"),
   NULL},
  /* ON DELETE CASCADE would have been nice for confirmation_height,
   * so that we automatically delete outputs that fall off the
   * blockchain and then we rediscover them if they are included
   * again. However, we have the their_unilateral/to_us which we
   * can't simply recognize from the chain without additional
   * hints. So we just mark them as unconfirmed should the block
   * die. */
  {SQL("ALTER TABLE outputs ADD COLUMN confirmation_height INTEGER "
       "REFERENCES blocks(height) ON DELETE SET NULL;"),
   NULL},
  {SQL("ALTER TABLE outputs ADD COLUMN spend_height INTEGER REFERENCES "
       "blocks(height) ON DELETE SET NULL;"),
   NULL},
  /* Create a covering index that covers both fields */
  {SQL("CREATE INDEX output_height_idx ON outputs (confirmation_height, "
       "spend_height);"),
   NULL},
  {SQL("CREATE TABLE utxoset ("
       " txid BLOB,"
       " outnum INT,"
       " blockheight INT REFERENCES blocks(height) ON DELETE CASCADE,"
       " spendheight INT REFERENCES blocks(height) ON DELETE SET NULL,"
       " txindex INT,"
       " scriptpubkey BLOB,"
       " satoshis BIGINT,"
       " PRIMARY KEY(txid, outnum));"),
   NULL},
  {SQL("CREATE INDEX short_channel_id ON utxoset (blockheight, txindex, "
       "outnum)"),
   NULL},
  /* Necessary index for long rollbacks of the blockchain, otherwise we're
   * doing table scans for every block removed. */
  {SQL("CREATE INDEX utxoset_spend ON utxoset (spendheight)"), NULL},
  /* Assign key 0 to unassigned shutdown_keyidx_local. */
  {SQL("UPDATE channels SET shutdown_keyidx_local=0 WHERE "
       "shutdown_keyidx_local = -1;"),
   NULL},
  /* FIXME: We should rename shutdown_keyidx_local to final_key_index */
  /* -- Payment routing failure information -- */
  /* BLOB if failure was due to unparseable onion, NULL otherwise */
  {SQL("ALTER TABLE payments ADD failonionreply BLOB;"), NULL},
  /* 0 if we could theoretically retry, 1 if PERM fail at payee */
  {SQL("ALTER TABLE payments ADD faildestperm INTEGER;"), NULL},
  /* Contents of routing_failure (only if not unparseable onion) */
  {SQL("ALTER TABLE payments ADD failindex INTEGER;"),
   NULL}, /* erring_index */
  {SQL("ALTER TABLE payments ADD failcode INTEGER;"), NULL}, /* failcode */
  {SQL("ALTER TABLE payments ADD failnode BLOB;"), NULL},    /* erring_node */
  {SQL("ALTER TABLE payments ADD failchannel TEXT;"),
   NULL}, /* erring_channel */
  {SQL("ALTER TABLE payments ADD failupdate BLOB;"),
   NULL}, /* channel_update - can be NULL*/
  /* -- Payment routing failure information ends -- */
  /* Delete route data for already succeeded or failed payments */
  {SQL("UPDATE payments"
       "   SET path_secrets = NULL"
       "     , route_nodes = NULL"
       "     , route_channels = NULL"
       " WHERE status <> 0;"),
   NULL}, /* PAYMENT_PENDING */
  /* -- Routing statistics -- */
  {SQL("ALTER TABLE channels ADD in_payments_offered INTEGER DEFAULT 0;"), NULL},
  {SQL("ALTER TABLE channels ADD in_payments_fulfilled INTEGER DEFAULT 0;"), NULL},
  {SQL("ALTER TABLE channels ADD in_msatoshi_offered BIGINT DEFAULT 0;"), NULL},
  {SQL("ALTER TABLE channels ADD in_msatoshi_fulfilled BIGINT DEFAULT 0;"), NULL},
  {SQL("ALTER TABLE channels ADD out_payments_offered INTEGER DEFAULT 0;"), NULL},
  {SQL("ALTER TABLE channels ADD out_payments_fulfilled INTEGER DEFAULT 0;"), NULL},
  {SQL("ALTER TABLE channels ADD out_msatoshi_offered BIGINT DEFAULT 0;"), NULL},
  {SQL("ALTER TABLE channels ADD out_msatoshi_fulfilled BIGINT DEFAULT 0;"), NULL},
  {SQL("UPDATE channels"
       "   SET  in_payments_offered = 0,  in_payments_fulfilled = 0"
       "     ,  in_msatoshi_offered = 0,  in_msatoshi_fulfilled = 0"
       "     , out_payments_offered = 0, out_payments_fulfilled = 0"
       "     , out_msatoshi_offered = 0, out_msatoshi_fulfilled = 0"
       "     ;"),
   NULL},
  /* -- Routing statistics ends --*/
  /* Record the msatoshi actually sent in a payment. */
  {SQL("ALTER TABLE payments ADD msatoshi_sent BIGINT;"), NULL},
  {SQL("UPDATE payments SET msatoshi_sent = msatoshi;"), NULL},
  /* Delete dangling utxoset entries due to Issue #1280  */
  {SQL("DELETE FROM utxoset WHERE blockheight IN ("
       "  SELECT DISTINCT(blockheight)"
       "  FROM utxoset LEFT OUTER JOIN blocks on (blockheight = "
       "blocks.height) "
       "  WHERE blocks.hash IS NULL"
       ");"),
   NULL},
  /* Record feerate range, to optimize onchaind grinding actual fees. */
  {SQL("ALTER TABLE channels ADD min_possible_feerate INTEGER;"), NULL},
  {SQL("ALTER TABLE channels ADD max_possible_feerate INTEGER;"), NULL},
  /* https://bitcoinfees.github.io/#1d says Dec 17 peak was ~1M sat/kb
   * which is 250,000 sat/Sipa */
  {SQL("UPDATE channels SET min_possible_feerate=0, "
       "max_possible_feerate=250000;"),
   NULL},
  /* -- Min and max msatoshi_to_us -- */
  {SQL("ALTER TABLE channels ADD msatoshi_to_us_min BIGINT;"), NULL},
  {SQL("ALTER TABLE channels ADD msatoshi_to_us_max BIGINT;"), NULL},
  {SQL("UPDATE channels"
       "   SET msatoshi_to_us_min = msatoshi_local"
       "     , msatoshi_to_us_max = msatoshi_local"
       "     ;"),
   NULL},
  /* -- Min and max msatoshi_to_us ends -- */
  /* Transactions we are interested in. Either we sent them ourselves or we
   * are watching them. We don't cascade block height deletes so we don't
   * forget any of them by accident.*/
  {SQL("CREATE TABLE transactions ("
       "  id BLOB"
       ", blockheight INTEGER REFERENCES blocks(height) ON DELETE SET NULL"
       ", txindex INTEGER"
       ", rawtx BLOB"
       ", PRIMARY KEY (id)"
       ");"),
   NULL},
  /* -- Detailed payment failure -- */
  {SQL("ALTER TABLE payments ADD faildetail TEXT;"), NULL},
  {SQL("UPDATE payments"
       "   SET faildetail = 'unspecified payment failure reason'"
       " WHERE status = 2;"),
   NULL}, /* PAYMENT_FAILED */
  /* -- Detailed payment faiure ends -- */
  {SQL("CREATE TABLE channeltxs ("
       /* The id serves as insertion order and short ID */
       "  id BIGSERIAL"
       ", channel_id BIGINT REFERENCES channels(id) ON DELETE CASCADE"
       ", type INTEGER"
       ", transaction_id BLOB REFERENCES transactions(id) ON DELETE CASCADE"
       /* The input_num is only used by the txo_watch, 0 if txwatch */
       ", input_num INTEGER"
       /* The height at which we sent the depth notice */
       ", blockheight INTEGER REFERENCES blocks(height) ON DELETE CASCADE"
       ", PRIMARY KEY(id)"
       ");"),
   NULL},
  /* -- Set the correct rescan height for PR #1398 -- */
  /* Delete blocks that are higher than our initial scan point, this is a
   * no-op if we don't have a channel. */
  {SQL("DELETE FROM blocks WHERE height > (SELECT MIN(first_blocknum) FROM "
       "channels);"),
   NULL},
  /* Now make sure we have the lower bound block with the first_blocknum
   * height. This may introduce a block with NULL height if we didn't have any
   * blocks, remove that in the next. */
  {SQL("INSERT INTO blocks (height) VALUES ((SELECT "
       "MIN(first_blocknum) FROM channels)) "
       "ON CONFLICT(height) DO NOTHING;"),
   NULL},
  {SQL("DELETE FROM blocks WHERE height IS NULL;"), NULL},
  /* -- End of  PR #1398 -- */
  {SQL("ALTER TABLE invoices ADD description TEXT;"), NULL},
  /* FIXME: payments table 'description' is really a 'label' */
  {SQL("ALTER TABLE payments ADD description TEXT;"), NULL},
  /* future_per_commitment_point if other side proves we're out of date -- */
  {SQL("ALTER TABLE channels ADD future_per_commitment_point BLOB;"), NULL},
  /* last_sent_commit array fix */
  {SQL("ALTER TABLE channels ADD last_sent_commit BLOB;"), NULL},
  /* Stats table to track forwarded HTLCs. The values in the HTLCs
   * and their states are replicated here and the entries are not
   * deleted when the HTLC entries or the channel entries are
   * deleted to avoid unexpected drops in statistics. */
  {SQL("CREATE TABLE forwarded_payments ("
       "  in_htlc_id BIGINT REFERENCES channel_htlcs(id) ON DELETE SET NULL"
       ", out_htlc_id BIGINT REFERENCES channel_htlcs(id) ON DELETE SET NULL"
       ", in_channel_scid BIGINT"
       ", out_channel_scid BIGINT"
       ", in_msatoshi BIGINT"
       ", out_msatoshi BIGINT"
       ", state INTEGER"
       ", UNIQUE(in_htlc_id, out_htlc_id)"
       ");"),
   NULL},
  /* Add a direction for failed payments. */
  {SQL("ALTER TABLE payments ADD faildirection INTEGER;"),
   NULL}, /* erring_direction */
  /* Fix dangling peers with no channels. */
  {SQL("DELETE FROM peers WHERE id NOT IN (SELECT peer_id FROM channels);"),
   NULL},
  {SQL("ALTER TABLE outputs ADD scriptpubkey BLOB;"), NULL},
  /* Keep bolt11 string for payments. */
  {SQL("ALTER TABLE payments ADD bolt11 TEXT;"), NULL},
  /* PR #2342 feerate per channel */
  {SQL("ALTER TABLE channels ADD feerate_base INTEGER;"), NULL},
  {SQL("ALTER TABLE channels ADD feerate_ppm INTEGER;"), NULL},
  {NULL, migrate_pr2342_feerate_per_channel},
  {SQL("ALTER TABLE channel_htlcs ADD received_time BIGINT"), NULL},
  {SQL("ALTER TABLE forwarded_payments ADD received_time BIGINT"), NULL},
  {SQL("ALTER TABLE forwarded_payments ADD resolved_time BIGINT"), NULL},
  {SQL("ALTER TABLE channels ADD remote_upfront_shutdown_script BLOB;"),
   NULL},
  /* PR #2524: Add failcode into forward_payment */
  {SQL("ALTER TABLE forwarded_payments ADD failcode INTEGER;"), NULL},
  /* remote signatures for channel announcement */
  {SQL("ALTER TABLE channels ADD remote_ann_node_sig BLOB;"), NULL},
  {SQL("ALTER TABLE channels ADD remote_ann_bitcoin_sig BLOB;"), NULL},
  /* FIXME: We now use the transaction_annotations table to type each
   * input and output instead of type and channel_id! */
  /* Additional information for transaction tracking and listing */
  {SQL("ALTER TABLE transactions ADD type BIGINT;"), NULL},
  /* Not a foreign key on purpose since we still delete channels from
   * the DB which would remove this. It is mainly used to group payments
   * in the list view anyway, e.g., show all close and htlc transactions
   * as a single bundle. */
  {SQL("ALTER TABLE transactions ADD channel_id BIGINT;"), NULL},
  /* Convert pre-Adelaide short_channel_ids */
  {SQL("UPDATE channels"
       " SET short_channel_id = REPLACE(short_channel_id, ':', 'x')"
       " WHERE short_channel_id IS NOT NULL;"), NULL },
  {SQL("UPDATE payments SET failchannel = REPLACE(failchannel, ':', 'x')"
       " WHERE failchannel IS NOT NULL;"), NULL },
  /* option_static_remotekey is nailed at creation time. */
  {SQL("ALTER TABLE channels ADD COLUMN option_static_remotekey INTEGER"
       " DEFAULT 0;"), NULL },
  {SQL("ALTER TABLE vars ADD COLUMN intval INTEGER"), NULL},
  {SQL("ALTER TABLE vars ADD COLUMN blobval BLOB"), NULL},
  {SQL("UPDATE vars SET intval = CAST(val AS INTEGER) WHERE name IN ('bip32_max_index', 'last_processed_block', 'next_pay_index')"), NULL},
  {SQL("UPDATE vars SET blobval = CAST(val AS BLOB) WHERE name = 'genesis_hash'"), NULL},
  {SQL("CREATE TABLE transaction_annotations ("
       /* Not making this a reference since we usually filter the TX by
        * walking its inputs and outputs, and only afterwards storing it in
        * the DB. Having a reference here would point into the void until we
        * add the matching TX. */
       "  txid BLOB"
       ", idx INTEGER" /* 0 when location is the tx, the index of the output or input otherwise */
       ", location INTEGER" /* The transaction itself, the output at idx, or the input at idx */
       ", type INTEGER"
       ", channel BIGINT REFERENCES channels(id)"
       ", UNIQUE(txid, idx)"
       ");"), NULL},
  {SQL("ALTER TABLE channels ADD shutdown_scriptpubkey_local BLOB;"),
         NULL},
  /* See https://github.com/ElementsProject/lightning/issues/3189 */
  {SQL("UPDATE forwarded_payments SET received_time=0 WHERE received_time IS NULL;"),
         NULL},
  {SQL("ALTER TABLE invoices ADD COLUMN features BLOB DEFAULT '';"), NULL},
  /* We can now have multiple payments in progress for a single hash, so
   * add two fields; combination of payment_hash & partid is unique. */
  {SQL("ALTER TABLE payments RENAME TO temp_payments;"), NULL},
  {SQL("CREATE TABLE payments ("
       " id BIGSERIAL"
       ", timestamp INTEGER"
       ", status INTEGER"
       ", payment_hash BLOB"
       ", destination BLOB"
       ", msatoshi BIGINT"
       ", payment_preimage BLOB"
       ", path_secrets BLOB"
       ", route_nodes BLOB"
       ", route_channels BLOB"
       ", failonionreply BLOB"
       ", faildestperm INTEGER"
       ", failindex INTEGER"
       ", failcode INTEGER"
       ", failnode BLOB"
       ", failchannel TEXT"
       ", failupdate BLOB"
       ", msatoshi_sent BIGINT"
       ", faildetail TEXT"
       ", description TEXT"
       ", faildirection INTEGER"
       ", bolt11 TEXT"
       ", total_msat BIGINT"
       ", partid BIGINT"
       ", PRIMARY KEY (id)"
       ", UNIQUE (payment_hash, partid))"), NULL},
  {SQL("INSERT INTO payments ("
       "id"
       ", timestamp"
       ", status"
       ", payment_hash"
       ", destination"
       ", msatoshi"
       ", payment_preimage"
       ", path_secrets"
       ", route_nodes"
       ", route_channels"
       ", failonionreply"
       ", faildestperm"
       ", failindex"
       ", failcode"
       ", failnode"
       ", failchannel"
       ", failupdate"
       ", msatoshi_sent"
       ", faildetail"
       ", description"
       ", faildirection"
       ", bolt11)"
       "SELECT id"
       ", timestamp"
       ", status"
       ", payment_hash"
       ", destination"
       ", msatoshi"
       ", payment_preimage"
       ", path_secrets"
       ", route_nodes"
       ", route_channels"
       ", failonionreply"
       ", faildestperm"
       ", failindex"
       ", failcode"
       ", failnode"
       ", failchannel"
       ", failupdate"
       ", msatoshi_sent"
       ", faildetail"
       ", description"
       ", faildirection"
       ", bolt11 FROM temp_payments;"), NULL},
  {SQL("UPDATE payments SET total_msat = msatoshi;"), NULL},
  {SQL("UPDATE payments SET partid = 0;"), NULL},
  {SQL("DROP TABLE temp_payments;"), NULL},
  {SQL("ALTER TABLE channel_htlcs ADD partid BIGINT;"), NULL},
  {SQL("UPDATE channel_htlcs SET partid = 0;"), NULL},
  {SQL("CREATE TABLE channel_feerates ("
       "  channel_id BIGINT REFERENCES channels(id) ON DELETE CASCADE,"
       "  hstate INTEGER,"
       "  feerate_per_kw INTEGER,"
       "  UNIQUE (channel_id, hstate)"
       ");"),
   NULL},
  /* Cast old-style per-side feerates into most likely layout for statewise
   * feerates. */
  /* If we're funder (LOCAL=0):
   *   Then our feerate is set last (SENT_ADD_ACK_REVOCATION = 4) */
  {SQL("INSERT INTO channel_feerates(channel_id, hstate, feerate_per_kw)"
       " SELECT id, 4, local_feerate_per_kw FROM channels WHERE funder = 0;"),
   NULL},
  /*   If different, assume their feerate is in state SENT_ADD_COMMIT = 1 */
  {SQL("INSERT INTO channel_feerates(channel_id, hstate, feerate_per_kw)"
       " SELECT id, 1, remote_feerate_per_kw FROM channels WHERE funder = 0 and local_feerate_per_kw != remote_feerate_per_kw;"),
   NULL},
  /* If they're funder (REMOTE=1):
   *   Then their feerate is set last (RCVD_ADD_ACK_REVOCATION = 14) */
  {SQL("INSERT INTO channel_feerates(channel_id, hstate, feerate_per_kw)"
       " SELECT id, 14, remote_feerate_per_kw FROM channels WHERE funder = 1;"),
   NULL},
  /*   If different, assume their feerate is in state RCVD_ADD_COMMIT = 11 */
  {SQL("INSERT INTO channel_feerates(channel_id, hstate, feerate_per_kw)"
       " SELECT id, 11, local_feerate_per_kw FROM channels WHERE funder = 1 and local_feerate_per_kw != remote_feerate_per_kw;"),
   NULL},
  /* FIXME: Remove now-unused local_feerate_per_kw and remote_feerate_per_kw from channels */
  {SQL("INSERT INTO vars (name, intval) VALUES ('data_version', 0);"), NULL},
  /* For outgoing HTLCs, we now keep a localmsg instead of a failcode.
   * Turn anything in transition into a WIRE_TEMPORARY_NODE_FAILURE. */
  {SQL("ALTER TABLE channel_htlcs ADD localfailmsg BLOB;"), NULL},
  {SQL("UPDATE channel_htlcs SET localfailmsg=decode('2002', 'hex') WHERE malformed_onion != 0 AND direction = 1;"), NULL},
  {SQL("ALTER TABLE channels ADD our_funding_satoshi BIGINT DEFAULT 0;"), migrate_our_funding},
  {SQL("CREATE TABLE penalty_bases ("
       "  channel_id BIGINT REFERENCES channels(id) ON DELETE CASCADE"
       ", commitnum BIGINT"
       ", txid BLOB"
       ", outnum INTEGER"
       ", amount BIGINT"
       ", PRIMARY KEY (channel_id, commitnum)"
       ");"), NULL},
  /* For incoming HTLCs, we now keep track of whether or not we provided
   * the preimage for it, or not. */
  {SQL("ALTER TABLE channel_htlcs ADD we_filled INTEGER;"), NULL},
  /* We track the counter for coin_moves, as a convenience for notification consumers */
  {SQL("INSERT INTO vars (name, intval) VALUES ('coin_moves_count', 0);"), NULL},
  {NULL, migrate_last_tx_to_psbt},
  {SQL("ALTER TABLE outputs ADD reserved_til INTEGER DEFAULT NULL;"), NULL},
  {NULL, fillin_missing_scriptpubkeys},
  /* option_anchor_outputs is nailed at creation time. */
  {SQL("ALTER TABLE channels ADD COLUMN option_anchor_outputs INTEGER"
       " DEFAULT 0;"), NULL },
  /* We need to know if it was option_anchor_outputs to spend to_remote */
  {SQL("ALTER TABLE outputs ADD option_anchor_outputs INTEGER"
       " DEFAULT 0;"), NULL},
  {SQL("ALTER TABLE channels ADD full_channel_id BLOB DEFAULT NULL;"), fillin_missing_channel_id},
  {SQL("ALTER TABLE channels ADD funding_psbt BLOB DEFAULT NULL;"), NULL},
  /* Channel closure reason */
  {SQL("ALTER TABLE channels ADD closer INTEGER DEFAULT 2;"), NULL},
  {SQL("ALTER TABLE channels ADD state_change_reason INTEGER DEFAULT 0;"), NULL},
  {SQL("CREATE TABLE channel_state_changes ("
       "  channel_id BIGINT REFERENCES channels(id) ON DELETE CASCADE,"
       "  timestamp BIGINT,"
       "  old_state INTEGER,"
       "  new_state INTEGER,"
       "  cause INTEGER,"
       "  message TEXT"
       ");"), NULL},
  {SQL("CREATE TABLE offers ("
       "  offer_id BLOB"
       ", bolt12 TEXT"
       ", label TEXT"
       ", status INTEGER"
       ", PRIMARY KEY (offer_id)"
       ");"), NULL},
  /* A reference into our own offers table, if it was made from one */
  {SQL("ALTER TABLE invoices ADD COLUMN local_offer_id BLOB DEFAULT NULL REFERENCES offers(offer_id);"), NULL},
  /* A reference into our own offers table, if it was made from one */
  {SQL("ALTER TABLE payments ADD COLUMN local_offer_id BLOB DEFAULT NULL REFERENCES offers(offer_id);"), NULL},
  {SQL("ALTER TABLE channels ADD funding_tx_remote_sigs_received INTEGER DEFAULT 0;"), NULL},
  /* Speeds up deletion of one peer from the database, measurements suggest
   * it cuts down the time by 80%.  */
  {SQL("CREATE INDEX forwarded_payments_out_htlc_id"
       " ON forwarded_payments (out_htlc_id);"), NULL},
  {SQL("UPDATE channel_htlcs SET malformed_onion = 0 WHERE malformed_onion IS NULL"), NULL},
  /*  Speed up forwarded_payments lookup based on state */
  {SQL("CREATE INDEX forwarded_payments_state ON forwarded_payments (state)"), NULL},
  {SQL("CREATE TABLE channel_funding_inflights ("
       "  channel_id BIGSERIAL REFERENCES channels(id) ON DELETE CASCADE"
       ", funding_tx_id BLOB"
       ", funding_tx_outnum INTEGER"
       ", funding_feerate INTEGER"
       ", funding_satoshi BIGINT"
       ", our_funding_satoshi BIGINT"
       ", funding_psbt BLOB"
       ", last_tx BLOB"
       ", last_sig BLOB"
       ", funding_tx_remote_sigs_received INTEGER"
       ", PRIMARY KEY (channel_id, funding_tx_id)"
       ");"),
   NULL},
  {SQL("ALTER TABLE channels ADD revocation_basepoint_local BLOB"), NULL},
  {SQL("ALTER TABLE channels ADD payment_basepoint_local BLOB"), NULL},
  {SQL("ALTER TABLE channels ADD htlc_basepoint_local BLOB"), NULL},
  {SQL("ALTER TABLE channels ADD delayed_payment_basepoint_local BLOB"), NULL},
  {SQL("ALTER TABLE channels ADD funding_pubkey_local BLOB"), NULL},
  {NULL, fillin_missing_local_basepoints},
  /* Oops, can I haz money back plz? */
  {SQL("ALTER TABLE channels ADD shutdown_wrong_txid BLOB DEFAULT NULL"), NULL},
  {SQL("ALTER TABLE channels ADD shutdown_wrong_outnum INTEGER DEFAULT NULL"), NULL},
  {NULL, migrate_inflight_last_tx_to_psbt},
  /* Channels can now change their type at specific commit indexes. */
  {SQL("ALTER TABLE channels ADD local_static_remotekey_start BIGINT DEFAULT 0"),
   NULL},
  {SQL("ALTER TABLE channels ADD remote_static_remotekey_start BIGINT DEFAULT 0"),
   NULL},
  /* Set counter past 2^48 if they don't have option */
  {SQL("UPDATE channels SET"
       " remote_static_remotekey_start = 9223372036854775807,"
       " local_static_remotekey_start = 9223372036854775807"
       " WHERE option_static_remotekey = 0"),
   NULL},
  {SQL("ALTER TABLE channel_funding_inflights ADD lease_commit_sig BLOB DEFAULT NULL"), NULL},
  {SQL("ALTER TABLE channel_funding_inflights ADD lease_chan_max_msat BIGINT DEFAULT NULL"), NULL},
  {SQL("ALTER TABLE channel_funding_inflights ADD lease_chan_max_ppt INTEGER DEFAULT NULL"), NULL},
  {SQL("ALTER TABLE channel_funding_inflights ADD lease_expiry INTEGER DEFAULT 0"), NULL},
  {SQL("ALTER TABLE channel_funding_inflights ADD lease_blockheight_start INTEGER DEFAULT 0"), NULL},
  {SQL("ALTER TABLE channels ADD lease_commit_sig BLOB DEFAULT NULL"), NULL},
  {SQL("ALTER TABLE channels ADD lease_chan_max_msat INTEGER DEFAULT NULL"), NULL},
  {SQL("ALTER TABLE channels ADD lease_chan_max_ppt INTEGER DEFAULT NULL"), NULL},
  {SQL("ALTER TABLE channels ADD lease_expiry INTEGER DEFAULT 0"), NULL},
  {SQL("CREATE TABLE channel_blockheights ("
       "  channel_id BIGINT REFERENCES channels(id) ON DELETE CASCADE,"
       "  hstate INTEGER,"
       "  blockheight INTEGER,"
       "  UNIQUE (channel_id, hstate)"
       ");"),
   fillin_missing_channel_blockheights},
  {SQL("ALTER TABLE outputs ADD csv_lock INTEGER DEFAULT 1;"), NULL},
  {SQL("CREATE TABLE datastore ("
       "  key BLOB,"
       "  data BLOB,"
       "  generation BIGINT,"
       "  PRIMARY KEY (key)"
       ");"),
   NULL},
  {SQL("CREATE INDEX channel_state_changes_channel_id"
       " ON channel_state_changes (channel_id);"), NULL},
  /* We need to switch the unique key to cover the groupid as well,
   * so we can attempt payments multiple times. */
  {SQL("ALTER TABLE payments RENAME TO temp_payments;"), NULL},
  {SQL("CREATE TABLE payments ("
       " id BIGSERIAL"
       ", timestamp INTEGER"
       ", status INTEGER"
       ", payment_hash BLOB"
       ", destination BLOB"
       ", msatoshi BIGINT"
       ", payment_preimage BLOB"
       ", path_secrets BLOB"
       ", route_nodes BLOB"
       ", route_channels BLOB"
       ", failonionreply BLOB"
       ", faildestperm INTEGER"
       ", failindex INTEGER"
       ", failcode INTEGER"
       ", failnode BLOB"
       ", failchannel TEXT"
       ", failupdate BLOB"
       ", msatoshi_sent BIGINT"
       ", faildetail TEXT"
       ", description TEXT"
       ", faildirection INTEGER"
       ", bolt11 TEXT"
       ", total_msat BIGINT"
       ", partid BIGINT"
       ", groupid BIGINT NOT NULL DEFAULT 0"
       ", local_offer_id BLOB DEFAULT NULL REFERENCES offers(offer_id)"
       ", PRIMARY KEY (id)"
       ", UNIQUE (payment_hash, partid, groupid))"), NULL},
  {SQL("INSERT INTO payments ("
       "id"
       ", timestamp"
       ", status"
       ", payment_hash"
       ", destination"
       ", msatoshi"
       ", payment_preimage"
       ", path_secrets"
       ", route_nodes"
       ", route_channels"
       ", failonionreply"
       ", faildestperm"
       ", failindex"
       ", failcode"
       ", failnode"
       ", failchannel"
       ", failupdate"
       ", msatoshi_sent"
       ", faildetail"
       ", description"
       ", faildirection"
       ", bolt11"
       ", groupid"
       ", local_offer_id)"
       "SELECT id"
       ", timestamp"
       ", status"
       ", payment_hash"
       ", destination"
       ", msatoshi"
       ", payment_preimage"
       ", path_secrets"
       ", route_nodes"
       ", route_channels"
       ", failonionreply"
       ", faildestperm"
       ", failindex"
       ", failcode"
       ", failnode"
       ", failchannel"
       ", failupdate"
       ", msatoshi_sent"
       ", faildetail"
       ", description"
       ", faildirection"
       ", bolt11"
       ", 0"
       ", local_offer_id FROM temp_payments;"), NULL},
  {SQL("DROP TABLE temp_payments;"), NULL},
  /* HTLCs also need to carry the groupid around so we can
   * selectively update them. */
  {SQL("ALTER TABLE channel_htlcs ADD groupid BIGINT;"), NULL},
  {SQL("ALTER TABLE channel_htlcs ADD COLUMN"
       " min_commit_num BIGINT default 0;"), NULL},
  {SQL("ALTER TABLE channel_htlcs ADD COLUMN"
       " max_commit_num BIGINT default NULL;"), NULL},
  /* Set max_commit_num for dead (RCVD_REMOVE_ACK_REVOCATION or SENT_REMOVE_ACK_REVOCATION) HTLCs based on latest indexes */
  {SQL("UPDATE channel_htlcs SET max_commit_num ="
       " (SELECT GREATEST(next_index_local, next_index_remote)"
       "  FROM channels WHERE id=channel_id)"
       " WHERE (hstate=9 OR hstate=19);"), NULL},
  /* Remove unused fields which take much room in db. */
  {SQL("UPDATE channel_htlcs SET"
       " payment_key=NULL,"
       " routing_onion=NULL,"
       " failuremsg=NULL,"
       " shared_secret=NULL,"
       " localfailmsg=NULL"
       " WHERE (hstate=9 OR hstate=19);"), NULL},
  /* We default to 50k sats */
  {SQL("ALTER TABLE channel_configs ADD max_dust_htlc_exposure_msat BIGINT DEFAULT 50000000"), NULL},
  {SQL("ALTER TABLE channel_htlcs ADD fail_immediate INTEGER DEFAULT 0"), NULL},

  /* Issue #4887: reset the payments.id sequence after the migration above. Since this is a SELECT statement that would otherwise fail, make it an INSERT into the `vars` table.*/
  {SQL("/*PSQL*/INSERT INTO vars (name, intval) VALUES ('payment_id_reset', setval(pg_get_serial_sequence('payments', 'id'), COALESCE((SELECT MAX(id)+1 FROM payments), 1)))"), NULL},

  /* Issue #4901: Partial index speeds up startup on nodes with ~1000 channels.  */
  {&SQL("CREATE INDEX channel_htlcs_speedup_unresolved_idx"
        "    ON channel_htlcs(channel_id, direction)"
        " WHERE hstate NOT IN (9, 19);")
   [BUILD_ASSERT_OR_ZERO( 9 == RCVD_REMOVE_ACK_REVOCATION) +
    BUILD_ASSERT_OR_ZERO(19 == SENT_REMOVE_ACK_REVOCATION)],
   NULL},
  {SQL("ALTER TABLE channel_htlcs ADD fees_msat BIGINT DEFAULT 0"), NULL},
  {SQL("ALTER TABLE channel_funding_inflights ADD lease_fee BIGINT DEFAULT 0"), NULL},
  /* Default is too big; we set to max after loading */
  {SQL("ALTER TABLE channels ADD htlc_maximum_msat BIGINT DEFAULT 2100000000000000"), NULL},
  {SQL("ALTER TABLE channels ADD htlc_minimum_msat BIGINT DEFAULT 0"), NULL},
  {SQL("ALTER TABLE forwarded_payments ADD forward_style INTEGER DEFAULT NULL"), NULL},
  /* "description" is used for label, so we use "paydescription" here */
  {SQL("ALTER TABLE payments ADD paydescription TEXT;"), NULL},
  /* Alias we sent to the remote side, for zeroconf and
   * option_scid_alias, can be a list of short_channel_ids if
   * required, but keeping it a single SCID for now. */
  {SQL("ALTER TABLE channels ADD alias_local BIGINT DEFAULT NULL"), NULL},
  /* Alias we received from the peer, and which we should be using
   * in routehints in invoices. The peer will remember all the
   * aliases, but we only ever need one. */
  {SQL("ALTER TABLE channels ADD alias_remote BIGINT DEFAULT NULL"), NULL},
  /* Cheeky immediate completion as best effort approximation of real completion time */
  {SQL("ALTER TABLE payments ADD completed_at INTEGER DEFAULT NULL;"), NULL},
  {SQL("UPDATE payments SET completed_at = timestamp WHERE status != 0;"), NULL},
  {SQL("CREATE INDEX payments_idx ON payments (payment_hash)"), NULL},
  /* forwards table outlives the channels, so we move there from old forwarded_payments table;
   * but here the ids are the HTLC numbers, not the internal db ids. */
  {SQL("CREATE TABLE forwards ("
       "in_channel_scid BIGINT"
       ", in_htlc_id BIGINT"
       ", out_channel_scid BIGINT"
       ", out_htlc_id BIGINT"
       ", in_msatoshi BIGINT"
       ", out_msatoshi BIGINT"
       ", state INTEGER"
       ", received_time BIGINT"
       ", resolved_time BIGINT"
       ", failcode INTEGER"
       ", forward_style INTEGER"
       ", PRIMARY KEY(in_channel_scid, in_htlc_id))"), NULL},
  {SQL("INSERT INTO forwards SELECT"
       " in_channel_scid"
       ", COALESCE("
       "    (SELECT channel_htlc_id FROM channel_htlcs WHERE id = forwarded_payments.in_htlc_id),"
       "    -_ROWID_"
       "  )"
       ", out_channel_scid"
       ", (SELECT channel_htlc_id FROM channel_htlcs WHERE id = forwarded_payments.out_htlc_id)"
       ", in_msatoshi"
       ", out_msatoshi"
       ", state"
       ", received_time"
       ", resolved_time"
       ", failcode"
       ", forward_style"
       " FROM forwarded_payments"), NULL},
  {SQL("DROP INDEX forwarded_payments_state;"), NULL},
  {SQL("DROP INDEX forwarded_payments_out_htlc_id;"), NULL},
  {SQL("DROP TABLE forwarded_payments;"), NULL},
  /* Adds scid column, then moves short_channel_id across to it */
  {SQL("ALTER TABLE channels ADD scid BIGINT;"), migrate_channels_scids_as_integers},
  {SQL("ALTER TABLE payments ADD failscid BIGINT;"), migrate_payments_scids_as_integers},
  {SQL("ALTER TABLE outputs ADD is_in_coinbase INTEGER DEFAULT 0;"), NULL},
  {SQL("CREATE TABLE invoicerequests ("
       "  invreq_id BLOB"
       ", bolt12 TEXT"
       ", label TEXT"
       ", status INTEGER"
       ", PRIMARY KEY (invreq_id)"
       ");"), NULL},
  /* A reference into our own invoicerequests table, if it was made from one */
  {SQL("ALTER TABLE payments ADD COLUMN local_invreq_id BLOB DEFAULT NULL REFERENCES invoicerequests(invreq_id);"), NULL},
  /* FIXME: Remove payments local_offer_id column! */
  {SQL("ALTER TABLE channel_funding_inflights ADD COLUMN lease_satoshi BIGINT;"), NULL},
  {SQL("ALTER TABLE channels ADD require_confirm_inputs_remote INTEGER DEFAULT 0;"), NULL},
  {SQL("ALTER TABLE channels ADD require_confirm_inputs_local INTEGER DEFAULT 0;"), NULL},
  {NULL, fillin_missing_lease_satoshi},
  {NULL, migrate_invalid_last_tx_psbts},
  {SQL("ALTER TABLE channels ADD channel_type BLOB DEFAULT NULL;"), NULL},
  {NULL, migrate_fill_in_channel_type},
  {SQL("ALTER TABLE peers ADD feature_bits BLOB DEFAULT NULL;"), NULL},
  {NULL, migrate_normalize_invstr},
  {SQL("CREATE TABLE runes (id BIGSERIAL, rune TEXT, PRIMARY KEY (id));"), NULL},
  {SQL("CREATE TABLE runes_blacklist (start_index BIGINT, end_index BIGINT);"), NULL},
  {SQL("ALTER TABLE channels ADD ignore_fee_limits INTEGER DEFAULT 0;"), NULL},
  {NULL, migrate_initialize_invoice_wait_indexes},
  {SQL("ALTER TABLE invoices ADD updated_index BIGINT DEFAULT 0"), NULL},
  {SQL("CREATE INDEX invoice_update_idx ON invoices (updated_index)"), NULL},
  {NULL, migrate_datastore_commando_runes},
  {NULL, migrate_invoice_created_index_var},
  /* Splicing requires us to store HTLC sigs for inflight splices and allows us to discard old sigs after splice confirmation. */
  {SQL("ALTER TABLE htlc_sigs ADD inflight_tx_id BLOB"), NULL},
  {SQL("ALTER TABLE htlc_sigs ADD inflight_tx_outnum INTEGER"), NULL},
  {SQL("ALTER TABLE channel_funding_inflights ADD splice_amnt BIGINT DEFAULT 0"), NULL},
  {SQL("ALTER TABLE channel_funding_inflights ADD i_am_initiator INTEGER DEFAULT 0"), NULL},
  {NULL, migrate_runes_idfix},
  {SQL("ALTER TABLE runes ADD last_used_nsec BIGINT DEFAULT NULL"), NULL},
    {SQL("DELETE FROM vars WHERE name = 'runes_uniqueid'"), NULL},
    {SQL("CREATE TABLE invoice_fallbacks ("
     "  scriptpubkey BLOB,"
     "  invoice_id BIGINT REFERENCES invoices(id) ON DELETE CASCADE,"
     "  PRIMARY KEY (scriptpubkey)"
     ");"),
     NULL},
    {SQL("ALTER TABLE invoices ADD paid_txid BLOB DEFAULT NULL"), NULL},
    {SQL("ALTER TABLE invoices ADD paid_outnum INTEGER DEFAULT NULL"), NULL},
    {SQL("CREATE TABLE local_anchors ("
         "  channel_id BIGSERIAL REFERENCES channels(id),"
         "  commitment_index BIGINT,"
         "  commitment_txid BLOB,"
         "  commitment_anchor_outnum INTEGER,"
         "  commitment_fee BIGINT,"
         "  commitment_weight INTEGER)"), NULL},
    {SQL("CREATE INDEX local_anchors_idx ON local_anchors (channel_id)"), NULL},
    {SQL("ALTER TABLE payments ADD updated_index BIGINT DEFAULT 0"), NULL},
    {SQL("CREATE INDEX payments_update_idx ON payments (updated_index)"), NULL},
    {NULL, migrate_initialize_payment_wait_indexes},
    {NULL, migrate_forwards_add_rowid},
    {SQL("ALTER TABLE forwards ADD updated_index BIGINT DEFAULT 0"), NULL},
    {SQL("CREATE INDEX forwards_updated_idx ON forwards (updated_index)"), NULL},
    {NULL, migrate_initialize_forwards_wait_indexes},
    {SQL("ALTER TABLE channel_funding_inflights ADD force_sign_first INTEGER DEFAULT 0"), NULL},
};

invoice_complete

invoice_complete in lightningd/invoice.c

static struct command_result *
invoice_complete(struct invoice_info *info,
                 bool warning_no_listincoming,
                 bool warning_mpp,
                 bool warning_capacity,
                 bool warning_deadends,
                 bool warning_offline,
                 bool warning_private_unused)
{
        struct json_stream *response;
        u64 inv_dbid;
        char *b11enc;
        const struct invoice_details *details;
        struct secret payment_secret;
        struct wallet *wallet = info->cmd->ld->wallet;

        b11enc = bolt11_encode(info, info->b11, false,
                         hsm_sign_b11, info->cmd->ld);

        /* Check duplicate preimage (unlikely unless they specified it!) */
        if (invoices_find_by_rhash(wallet->invoices,
                             &inv_dbid, &info->b11->payment_hash)) {
                return command_fail(info->cmd,
                        INVOICE_PREIMAGE_ALREADY_EXISTS,
                        "preimage already used");
        }

        if (!invoices_create(wallet->invoices,
                       &inv_dbid,
                       info->b11->msat,
                       info->label,
                       info->b11->expiry,
                       b11enc,
                       info->b11->description,
                       info->b11->features,
                       &info->payment_preimage,
                       &info->b11->payment_hash,
                       NULL)) {
                return command_fail(info->cmd, INVOICE_LABEL_ALREADY_EXISTS,
                        "Duplicate label '%s'",
                        info->label->s);
        }

        if (info->cmd->ld->unified_invoices && info->b11->fallbacks && !info->custom_fallbacks) {
                for (size_t i = 0; i < tal_count(info->b11->fallbacks); i++) {
                        const u8 *fallback_script = info->b11->fallbacks[i];
                        invoices_create_fallback(wallet->invoices, inv_dbid, fallback_script);
                }
        }

        /* Get details */
        details = invoices_get_details(info, wallet->invoices, inv_dbid);

        response = json_stream_success(info->cmd);
        json_add_sha256(response, "payment_hash", &details->rhash);
        json_add_u64(response, "expires_at", details->expiry_time);
        json_add_string(response, "bolt11", details->invstring);
        invoice_secret(&details->r, &payment_secret);
        json_add_secret(response, "payment_secret", &payment_secret);
        json_add_u64(response, "created_index", details->created_index);

        notify_invoice_creation(info->cmd->ld, info->b11->msat,
                          info->payment_preimage, info->label);

        if (warning_no_listincoming)
                json_add_string(response, "warning_listincoming",
                    "No listincoming command available, cannot add routehints to invoice");
        if (warning_mpp)
                json_add_string(response, "warning_mpp",
                    "The invoice is only payable by MPP-capable payers.");
        if (warning_capacity)
                json_add_string(response, "warning_capacity",
                    "Insufficient incoming channel capacity to pay invoice");

        if (warning_deadends)
                json_add_string(response, "warning_deadends",
                                "Insufficient incoming capacity, once dead-end peers were excluded");

        if (warning_offline)
          json_add_string(response, "warning_offline",
                          "Insufficient incoming capacity, once offline peers were excluded");

        if (warning_private_unused)
          json_add_string(response, "warning_private_unused",
                          "Insufficient incoming capacity, once private channels were excluded (try exposeprivatechannels=true?)");

        if (info->cmd->ld->unified_invoices && info->custom_fallbacks)
          json_add_string(response, "warning_custom_fallbacks",
                                "WARNING: Not tracking on-chain payments for custom fallback addresses");

        return command_success(info->cmd, response);
}

invoices_create

invoices_create in wallet/invoices.c

bool invoices_create(struct invoices *invoices,
                     u64 *inv_dbid,
                     const struct amount_msat *msat TAKES,
                     const struct json_escape *label TAKES,
                     u64 expiry,
                     const char *b11enc,
                     const char *description,
                     const u8 *features,
                     const struct preimage *r,
                     const struct sha256 *rhash,
                     const struct sha256 *local_offer_id)
{
        struct db_stmt *stmt;
        u64 expiry_time;
        u64 now = time_now().ts.tv_sec;

        if (invoices_find_by_label(invoices, inv_dbid, label)) {
                if (taken(msat))
                        tal_free(msat);
                if (taken(label))
                        tal_free(label);
                return false;
        }

        /* Compute expiration. */
        expiry_time = now + expiry;

        *inv_dbid = invoice_index_created(invoices->wallet->ld, UNPAID, label, b11enc);

        /* Save to database. */
        stmt = db_prepare_v2(
                       invoices->wallet->db,
                       SQL("INSERT INTO invoices"
                           "            ( id, payment_hash, payment_key, state"
                           "            , msatoshi, label, expiry_time"
                           "            , pay_index, msatoshi_received"
                           "            , paid_timestamp, bolt11, description, features, local_offer_id)"
                           "     VALUES ( ?, ?, ?, ?"
                           "            , ?, ?, ?"
                           "            , NULL, NULL"
                           "            , NULL, ?, ?, ?, ?);"));

        db_bind_u64(stmt, *inv_dbid);
        db_bind_sha256(stmt, rhash);
        db_bind_preimage(stmt, r);
        db_bind_int(stmt, UNPAID);
        if (msat)
                db_bind_amount_msat(stmt, msat);
        else
                db_bind_null(stmt);
        db_bind_json_escape(stmt, label);
        db_bind_u64(stmt, expiry_time);
        db_bind_text(stmt, b11enc);
        if (!description)
                db_bind_null(stmt);
        else
                db_bind_text(stmt, description);
        db_bind_talarr(stmt, features);
        if (local_offer_id)
                db_bind_sha256(stmt, local_offer_id);
        else
                db_bind_null(stmt);

        db_exec_prepared_v2(stmt);
        tal_free(stmt);

        /* Install expiration trigger. */
        if (!invoices->expiration_timer ||
            expiry_time < invoices->min_expiry_time) {
                invoices->expiration_timer
                        = tal_free(invoices->expiration_timer);
                install_expiration_timer(invoices);
        }

        if (taken(msat))
          tal_free(msat);
        if (taken(label))
          tal_free(label);
        return true;
}

Source Code

json_foo (in lightningd/invoices.c)

static struct command_result *json_foo(struct command *cmd,
                                       const char *buffer,
                                       const jsmntok_t *obj UNNEEDED,
                                       const jsmntok_t *params)
{
        struct json_stream *response;
        u64 * bar;

        if (!param(cmd, buffer, params,
                   p_req("bar", param_u64, &bar),
                   NULL))
          return command_param_failed();

        invoices_foo(cmd->ld->wallet->db, *bar);

        response = json_stream_success(cmd);
        json_add_u64(response, "added-to-database", *bar);
        return command_success(cmd, response);
}

static const struct json_command foo_command = {
        "foo",
        "dev",
        json_foo,
        "clnlive #23"};
AUTODATA(json_command, &foo_command);

dbmigrations (in wallet/db.c)

static struct migration dbmigrations[] = {
  {SQL("CREATE TABLE version (version INTEGER)"), NULL},
  /* ... */
  {SQL("CREATE TABLE foo (bar INTEGER)"), NULL},
};

invoices_foo (in wallet/invoices.c)

bool invoices_foo(struct db * db, u64 bar) {
        struct db_stmt *stmt;
        stmt = db_prepare_v2(db, SQL("INSERT INTO foo (bar) VALUES (?);"));
        db_bind_u64(stmt, bar);
        db_exec_prepared_v2(stmt);
        tal_free(stmt);

        return true;
}

invoices_foo (in wallet/invoices.h)

bool invoices_foo(struct db * db, u64 bar);

Resources