Skip to content

Commit

Permalink
lightningd: refuse to upgrade db on non-released versions by default.
Browse files Browse the repository at this point in the history
This is a good sanity check that users understand that if they upgrade
to master mid-cycle they can't go back!

Suggested-by: @wtogami
Changelog-Added: Config: `--database-upgrade=true` required if a non-release version wants to (irrevocably!) upgrade the db.
Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
  • Loading branch information
rustyrussell committed Sep 15, 2022
1 parent 0a85677 commit 4ca6b36
Show file tree
Hide file tree
Showing 9 changed files with 88 additions and 11 deletions.
3 changes: 2 additions & 1 deletion doc/lightning-listconfigs.7.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ On success, an object is returned, containing:
- **experimental-offers** (boolean, optional): `experimental-offers` field from config or cmdline, or default
- **experimental-shutdown-wrong-funding** (boolean, optional): `experimental-shutdown-wrong-funding` field from config or cmdline, or default
- **experimental-websocket-port** (u16, optional): `experimental-websocket-port` field from config or cmdline, or default
- **database-upgrade** (boolean, optional): `database-upgrade` field from config or cmdline
- **rgb** (hex, optional): `rgb` field from config or cmdline, or default (always 6 characters)
- **alias** (string, optional): `alias` field from config or cmdline, or default
- **pid-file** (string, optional): `pid-file` field from config or cmdline, or default
Expand Down Expand Up @@ -215,4 +216,4 @@ RESOURCES
---------

Main web site: <https://github.com/ElementsProject/lightning>
[comment]: # ( SHA256STAMP:14fa07df1432e7104983afc4fba02cc462237bd1a154ba3f3750355d10ffdadb)
[comment]: # ( SHA256STAMP:dcab86f29b946fed925de5e05cb79faa03cc4421cefeab3561a596ed5e64962d)
10 changes: 10 additions & 0 deletions doc/lightningd-config.5.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,18 @@ fun to put in other's config files while their computer is unattended.
putting this in someone's config file may convince them to read this man
page.

* **database-upgrade**=*BOOL*

Upgrades to Core Lightning often change the database: once this is done,
downgrades are not generally possible. By default, Core Lightning will
exit with an error rather than upgrade, unless this is an official released
version. If you really want to upgrade to a non-release version, you can
set this to *true* (or *false* to never allow a non-reversible upgrade!).

### Bitcoin control options:

Bitcoin control options:

* **network**=*NETWORK*

Select the network parameters (*bitcoin*, *testnet*, *signet*, or *regtest*).
Expand Down
4 changes: 4 additions & 0 deletions doc/schemas/listconfigs.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,10 @@
"type": "u16",
"description": "`experimental-websocket-port` field from config or cmdline, or default"
},
"database-upgrade": {
"type": "boolean",
"description": "`database-upgrade` field from config or cmdline"
},
"rgb": {
"type": "hex",
"description": "`rgb` field from config or cmdline, or default",
Expand Down
1 change: 1 addition & 0 deletions lightningd/lightningd.c
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,7 @@ static struct lightningd *new_lightningd(const tal_t *ctx)
ld->autolisten = true;
ld->reconnect = true;
ld->try_reexec = false;
ld->db_upgrade_ok = NULL;

/*~ This is from ccan/timer: it is efficient for the case where timers
* are deleted before expiry (as is common with timeouts) using an
Expand Down
3 changes: 3 additions & 0 deletions lightningd/lightningd.h
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,9 @@ struct lightningd {
* FEERATE_PENALTY). */
u32 *force_feerates;

/* If they force db upgrade on or off this is set. */
bool *db_upgrade_ok;

#if DEVELOPER
/* If we want to debug a subdaemon/plugin. */
const char *dev_debug_subprocess;
Expand Down
15 changes: 15 additions & 0 deletions lightningd/options.c
Original file line number Diff line number Diff line change
Expand Up @@ -1025,6 +1025,12 @@ static char *opt_set_offers(struct lightningd *ld)
return opt_set_onion_messages(ld);
}

static char *opt_set_db_upgrade(const char *arg, struct lightningd *ld)
{
ld->db_upgrade_ok = tal(ld, bool);
return opt_set_bool_arg(arg, ld->db_upgrade_ok);
}

static void register_opts(struct lightningd *ld)
{
/* This happens before plugins started */
Expand Down Expand Up @@ -1205,6 +1211,10 @@ static void register_opts(struct lightningd *ld)
ld,
"experimental: alternate port for peers to connect"
" using WebSockets (RFC6455)");
opt_register_arg("--database-upgrade",
opt_set_db_upgrade, NULL,
ld,
"Set to true to allow database upgrades even on non-final releases (WARNING: you won't be able to downgrade!)");
opt_register_logging(ld);
opt_register_version();

Expand Down Expand Up @@ -1629,6 +1639,11 @@ static void add_config(struct lightningd *ld,
json_add_u32(response, name0,
ld->websocket_port);
return;
} else if (opt->cb_arg == (void *)opt_set_db_upgrade) {
if (ld->db_upgrade_ok)
json_add_bool(response, name0,
*ld->db_upgrade_ok);
return;
} else if (opt->cb_arg == (void *)opt_important_plugin) {
/* Do nothing, this is already handled by
* opt_add_plugin. */
Expand Down
43 changes: 35 additions & 8 deletions tests/test_db.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@
import base64
import os
import pytest
import re
import shutil
import subprocess
import time
import unittest

Expand All @@ -17,7 +19,8 @@ def test_db_dangling_peer_fix(node_factory, bitcoind):
# Make sure bitcoind doesn't think it's going backwards
bitcoind.generate_block(104)
# This was taken from test_fail_unconfirmed() node.
l1 = node_factory.get_node(dbfile='dangling-peer.sqlite3.xz')
l1 = node_factory.get_node(dbfile='dangling-peer.sqlite3.xz',
options={'database-upgrade': True})
l2 = node_factory.get_node()

# Must match entry in db
Expand Down Expand Up @@ -138,8 +141,26 @@ def test_scid_upgrade(node_factory, bitcoind):
bitcoind.generate_block(1)

# Created through the power of sed "s/X'\([0-9]*\)78\([0-9]*\)78\([0-9]*\)'/X'\13A\23A\3'/"
l1 = node_factory.get_node(dbfile='oldstyle-scids.sqlite3.xz')
l1 = node_factory.get_node(dbfile='oldstyle-scids.sqlite3.xz',
start=False, expect_fail=True,
allow_broken_log=True)

# Will refuse to upgrade (if not in a release!)
version = subprocess.check_output(['lightningd/lightningd',
'--version']).decode('utf-8').splitlines()[0]
if not re.match('^v[0-9.]*$', version):
l1.daemon.start(wait_for_initialized=False, stderr_redir=True)
# Will have exited with non-zero status.
assert l1.daemon.proc.wait(TIMEOUT) != 0
assert l1.daemon.is_in_stderr('Refusing to irreversibly upgrade db from version [0-9]* to [0-9]* in non-final version ' + version + r' \(use --database-upgrade=true to override\)')

l1.daemon.opts['database-upgrade'] = False
l1.daemon.start(wait_for_initialized=False, stderr_redir=True)
assert l1.daemon.proc.wait(TIMEOUT) != 0
assert l1.daemon.is_in_stderr(r'Refusing to upgrade db from version [0-9]* to [0-9]* \(database-upgrade=false\)')

l1.daemon.opts['database-upgrade'] = True
l1.daemon.start()
assert l1.db_query('SELECT short_channel_id from channels;') == [{'short_channel_id': '103x1x1'}]
assert l1.db_query('SELECT failchannel from payments;') == [{'failchannel': '103x1x1'}]

Expand All @@ -152,7 +173,8 @@ def test_last_tx_inflight_psbt_upgrade(node_factory, bitcoind):

prior_txs = ['02000000019CCCA2E59D863B00B5BD835BF7BA93CC257932D2C7CDBE51EFE2EE4A9D29DFCB01000000009DB0E280024A01000000000000220020BE7935A77CA9AB70A4B8B1906825637767FED3C00824AA90C988983587D68488F0820100000000002200209F4684DDB28ACDC73959BC194D1A25DF906F61ED030F52D163E6F1E247D32CBB9A3ED620', '020000000122F9EBE38F54208545B681AD7F73A7AE3504A09C8201F502673D34E28424687C01000000009DB0E280024A01000000000000220020BE7935A77CA9AB70A4B8B1906825637767FED3C00824AA90C988983587D68488F0820100000000002200209F4684DDB28ACDC73959BC194D1A25DF906F61ED030F52D163E6F1E247D32CBB9A3ED620']

l1 = node_factory.get_node(dbfile='upgrade_inflight.sqlite3.xz')
l1 = node_factory.get_node(dbfile='upgrade_inflight.sqlite3.xz',
options={'database-upgrade': True})

b64_last_txs = [base64.b64encode(x['last_tx']).decode('utf-8') for x in l1.db_query('SELECT last_tx FROM channel_funding_inflights ORDER BY channel_id, funding_feerate;')]
for i in range(len(b64_last_txs)):
Expand All @@ -174,7 +196,8 @@ def test_last_tx_psbt_upgrade(node_factory, bitcoind):

prior_txs = ['02000000018DD699861B00061E50937A233DB584BF8ED4C0BF50B44C0411F71B031A06455000000000000EF7A9800350C300000000000022002073356CFF7E1588F14935EF138E142ABEFB5F7E3D51DE942758DCD5A179449B6250A90600000000002200202DF545EA882889846C52FC5E111AC07CE07E0C09418AC15743A6F6284C2A4FA720A1070000000000160014E89954FAC8F7A2DCE51E095D7BEB5271C3F7DA56EF81DC20', '02000000018A0AE4C63BCDF9D78B07EB4501BB23404FDDBC73973C592793F047BE1495074B010000000074D99980010A2D0F00000000002200203B8CB644781CBECA96BE8B2BF1827AFD908B3CFB5569AC74DAB9395E8DDA39E4C9555420', '020000000135DAB2996E57762E3EC158C0D57D39F43CA657E882D93FC24F5FEBAA8F36ED9A0100000000566D1D800350C30000000000002200205679A7D06E1BD276AA25F56E9E4DF7E07D9837EFB0C5F63604F10CD9F766A03ED4DD0600000000001600147E5B5C8F4FC1A9484E259F92CA4CBB7FA2814EA49A6C070000000000220020AB6226DEBFFEFF4A741C01367FA3C875172483CFB3E327D0F8C7AA4C51EDECAA27AA4720']

l1 = node_factory.get_node(dbfile='last_tx_upgrade.sqlite3.xz')
l1 = node_factory.get_node(dbfile='last_tx_upgrade.sqlite3.xz',
options={'database-upgrade': True})

b64_last_txs = [base64.b64encode(x['last_tx']).decode('utf-8') for x in l1.db_query('SELECT last_tx FROM channels ORDER BY id;')]
for i in range(len(b64_last_txs)):
Expand All @@ -195,7 +218,8 @@ def test_last_tx_psbt_upgrade(node_factory, bitcoind):
# We need to give it a chance to update
time.sleep(2)

l2 = node_factory.get_node(dbfile='last_tx_closed.sqlite3.xz')
l2 = node_factory.get_node(dbfile='last_tx_closed.sqlite3.xz',
options={'database-upgrade': True})
last_txs = [x['last_tx'] for x in l2.db_query('SELECT last_tx FROM channels ORDER BY id;')]

# The first tx should be psbt, the second should still be hex
Expand Down Expand Up @@ -234,7 +258,8 @@ def test_backfill_scriptpubkeys(node_factory, bitcoind):
]

# Test the first time, all entries are with option_static_remotekey
l1 = node_factory.get_node(node_id=3, dbfile='pubkey_regen.sqlite.xz')
l1 = node_factory.get_node(node_id=3, dbfile='pubkey_regen.sqlite.xz',
options={'database-upgrade': True})
results = l1.db_query('SELECT hex(prev_out_tx) AS txid, hex(scriptpubkey) AS script FROM outputs')
scripts = [{'txid': x['txid'], 'scriptpubkey': x['script']} for x in results]
for exp, actual in zip(script_map, scripts):
Expand Down Expand Up @@ -266,7 +291,8 @@ def test_backfill_scriptpubkeys(node_factory, bitcoind):
}
]

l2 = node_factory.get_node(node_id=3, dbfile='pubkey_regen_commitment_point.sqlite3.xz')
l2 = node_factory.get_node(node_id=3, dbfile='pubkey_regen_commitment_point.sqlite3.xz',
options={'database-upgrade': True})
results = l2.db_query('SELECT hex(prev_out_tx) AS txid, hex(scriptpubkey) AS script FROM outputs')
scripts = [{'txid': x['txid'], 'scriptpubkey': x['script']} for x in results]
for exp, actual in zip(script_map_2, scripts):
Expand Down Expand Up @@ -344,7 +370,8 @@ def test_local_basepoints_cache(bitcoind, node_factory):
bitcoind.generate_block(6)
l1 = node_factory.get_node(
dbfile='no-local-basepoints.sqlite3.xz',
start=False
start=False,
options={'database-upgrade': True}
)

fields = [
Expand Down
2 changes: 1 addition & 1 deletion tests/test_misc.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ def test_names(node_factory):

@unittest.skipIf(os.getenv('TEST_DB_PROVIDER', 'sqlite3') != 'sqlite3', "This migration is based on a sqlite3 snapshot")
def test_db_upgrade(node_factory):
l1 = node_factory.get_node()
l1 = node_factory.get_node(options={'database-upgrade': True})
l1.stop()

version = subprocess.check_output(['lightningd/lightningd',
Expand Down
18 changes: 17 additions & 1 deletion wallet/db.c
Original file line number Diff line number Diff line change
Expand Up @@ -888,6 +888,14 @@ static struct migration dbmigrations[] = {
{SQL("UPDATE payments SET completed_at = timestamp WHERE status != 0;"), NULL}
};

/* Released versions are of form v{num}[.{num}]* */
static bool is_released_version(void)
{
if (version()[0] != 'v')
return false;
return strcspn(version()+1, ".0123456789") == strlen(version()+1);
}

/**
* db_migrate - Apply all remaining migrations from the current version
*/
Expand All @@ -910,9 +918,17 @@ static bool db_migrate(struct lightningd *ld, struct db *db,
else if (available < current)
db_fatal("Refusing to migrate down from version %u to %u",
current, available);
else if (current != available)
else if (current != available) {
if (ld->db_upgrade_ok && *ld->db_upgrade_ok == false) {
db_fatal("Refusing to upgrade db from version %u to %u (database-upgrade=false)",
current, available);
} else if (!ld->db_upgrade_ok && !is_released_version()) {
db_fatal("Refusing to irreversibly upgrade db from version %u to %u in non-final version %s (use --database-upgrade=true to override)",
current, available, version());
}
log_info(ld->log, "Updating database from version %u to %u",
current, available);
}

while (current < available) {
current++;
Expand Down

0 comments on commit 4ca6b36

Please sign in to comment.