cln-plugins/archived/datastore/test_datastore.py
2025-09-09 14:30:48 -06:00

269 lines
8.9 KiB
Python

import os
import shelve
import time
import pytest
from pyln.client import RpcError
from pyln.testing.fixtures import * # noqa: F401,F403
from pyln.testing.utils import only_one, wait_for
plugin_path = os.path.join(os.path.dirname(__file__), "datastore.py")
# Test taken from lightning/tests/test_misc.py
def test_datastore(node_factory):
l1 = node_factory.get_node(options={"plugin": plugin_path})
time.sleep(5)
# Starts empty
assert l1.rpc.listdatastore() == {"datastore": []} or l1.rpc.listdatastore() == {
"datastore": [{"key": ["askrene"]}]
}
assert l1.rpc.listdatastore("somekey") == {"datastore": []}
# Add entries.
somedata = b"somedata".hex()
somedata_expect = {
"key": ["somekey"],
"generation": 0,
"hex": somedata,
"string": "somedata",
}
assert l1.rpc.datastore(key="somekey", hex=somedata) == somedata_expect
assert somedata_expect in l1.rpc.listdatastore()["datastore"]
assert l1.rpc.listdatastore("somekey") == {"datastore": [somedata_expect]}
assert l1.rpc.listdatastore("otherkey") == {"datastore": []}
# Cannot add by default.
with pytest.raises(RpcError, match="already exists"):
l1.rpc.datastore(key="somekey", hex=somedata)
with pytest.raises(RpcError, match="already exists"):
l1.rpc.datastore(key="somekey", hex=somedata, mode="must-create")
# But can insist on replace.
l1.rpc.datastore(key="somekey", hex=somedata[:-4], mode="must-replace")
assert (
only_one(l1.rpc.listdatastore("somekey")["datastore"])["hex"] == somedata[:-4]
)
# And append works.
l1.rpc.datastore(key="somekey", hex=somedata[-4:-2], mode="must-append")
assert (
only_one(l1.rpc.listdatastore("somekey")["datastore"])["hex"] == somedata[:-2]
)
l1.rpc.datastore(key="somekey", hex=somedata[-2:], mode="create-or-append")
assert only_one(l1.rpc.listdatastore("somekey")["datastore"])["hex"] == somedata
# Generation will have increased due to three ops above.
somedata_expect["generation"] += 3
assert somedata_expect in l1.rpc.listdatastore()["datastore"]
# Can't replace or append non-existing records if we say not to
with pytest.raises(RpcError, match="does not exist"):
l1.rpc.datastore(key="otherkey", hex=somedata, mode="must-replace")
with pytest.raises(RpcError, match="does not exist"):
l1.rpc.datastore(key="otherkey", hex=somedata, mode="must-append")
otherdata = b"otherdata".hex()
otherdata_expect = {
"key": ["otherkey"],
"generation": 0,
"hex": otherdata,
"string": "otherdata",
}
assert (
l1.rpc.datastore(key="otherkey", string="otherdata", mode="create-or-append")
== otherdata_expect
)
assert l1.rpc.listdatastore("somekey") == {"datastore": [somedata_expect]}
assert l1.rpc.listdatastore("otherkey") == {"datastore": [otherdata_expect]}
assert l1.rpc.listdatastore("badkey") == {"datastore": []}
ds = l1.rpc.listdatastore()["datastore"]
# Order is undefined!
assert somedata_expect in ds
assert otherdata_expect in ds
assert l1.rpc.deldatastore("somekey") == somedata_expect
assert otherdata_expect in l1.rpc.listdatastore()["datastore"]
assert l1.rpc.listdatastore("somekey") == {"datastore": []}
assert l1.rpc.listdatastore("otherkey") == {"datastore": [otherdata_expect]}
assert l1.rpc.listdatastore("badkey") == {"datastore": []}
assert otherdata_expect in l1.rpc.listdatastore()["datastore"]
# if it's not a string, won't print
badstring_expect = {"key": ["badstring"], "generation": 0, "hex": "00"}
assert l1.rpc.datastore(key="badstring", hex="00") == badstring_expect
assert l1.rpc.listdatastore("badstring") == {"datastore": [badstring_expect]}
assert l1.rpc.deldatastore("badstring") == badstring_expect
# It's persistent
l1.restart()
assert otherdata_expect in l1.rpc.listdatastore()["datastore"]
# We can insist generation match on update.
with pytest.raises(RpcError, match="generation is different"):
l1.rpc.datastore(
key="otherkey",
hex="00",
mode="must-replace",
generation=otherdata_expect["generation"] + 1,
)
otherdata_expect["generation"] += 1
otherdata_expect["string"] += "a"
otherdata_expect["hex"] += "61"
assert (
l1.rpc.datastore(
key="otherkey",
string="otherdataa",
mode="must-replace",
generation=otherdata_expect["generation"] - 1,
)
== otherdata_expect
)
assert otherdata_expect in l1.rpc.listdatastore()["datastore"]
# We can insist generation match on delete.
with pytest.raises(RpcError, match="generation is different"):
l1.rpc.deldatastore(
key="otherkey", generation=otherdata_expect["generation"] + 1
)
assert (
l1.rpc.deldatastore(key="otherkey", generation=otherdata_expect["generation"])
== otherdata_expect
)
assert l1.rpc.listdatastore() == {"datastore": []} or l1.rpc.listdatastore() == {
"datastore": [{"key": ["askrene"]}]
}
def test_upgrade(node_factory):
l1 = node_factory.get_node()
datastore = shelve.open(
os.path.join(l1.daemon.lightning_dir, "regtest", "datastore.dat"), "c"
)
datastore["foo"] = b"foodata"
datastore["bar"] = b"bardata"
datastore.close()
# This "fails" because it unloads itself.
try:
l1.rpc.plugin_start(plugin_path)
except RpcError:
pass
# There's no upgrade if there's a real datastore.
if l1.daemon.is_in_log("there is a real datastore command"):
return
l1.daemon.wait_for_log("Upgrading store to have generation numbers")
wait_for(
lambda: not os.path.exists(
os.path.join(l1.daemon.lightning_dir, "regtest", "datastore.dat")
)
)
vals = l1.rpc.listdatastore()["datastore"]
assert vals == [
{"key": ["bar"], "generation": 0, "hex": b"bardata".hex(), "string": "bardata"},
{"key": ["foo"], "generation": 0, "hex": b"foodata".hex(), "string": "foodata"},
]
def test_datastore_keylist(node_factory):
l1 = node_factory.get_node(options={"plugin": plugin_path})
time.sleep(5)
# Starts empty
assert l1.rpc.listdatastore() == {"datastore": []} or l1.rpc.listdatastore() == {
"datastore": [{"key": ["askrene"]}]
}
assert l1.rpc.listdatastore(["a"]) == {"datastore": []}
assert l1.rpc.listdatastore(["a", "b"]) == {"datastore": []}
# Cannot add child to existing!
l1.rpc.datastore(key="a", string="aval")
with pytest.raises(RpcError, match=r"1206.*Parent key \[a\] exists"):
l1.rpc.datastore(key=["a", "b"], string="abval", mode="create-or-replace")
# Listing subkey gives DNE.
assert l1.rpc.listdatastore(["a", "b"]) == {"datastore": []}
l1.rpc.deldatastore(key=["a"])
# Create child key.
l1.rpc.datastore(key=["a", "b"], string="abval")
assert {"key": ["a"]} in l1.rpc.listdatastore()["datastore"]
assert l1.rpc.listdatastore(key=["a"]) == {
"datastore": [
{
"key": ["a", "b"],
"generation": 0,
"string": "abval",
"hex": b"abval".hex(),
}
]
}
# Cannot create key over that
with pytest.raises(RpcError, match="has children"):
l1.rpc.datastore(key="a", string="aval", mode="create-or-replace")
# Can create another key.
l1.rpc.datastore(key=["a", "b2"], string="ab2val")
assert {"key": ["a"]} in l1.rpc.listdatastore()["datastore"]
assert l1.rpc.listdatastore(key=["a"]) == {
"datastore": [
{
"key": ["a", "b"],
"string": "abval",
"generation": 0,
"hex": b"abval".hex(),
},
{
"key": ["a", "b2"],
"string": "ab2val",
"generation": 0,
"hex": b"ab2val".hex(),
},
]
}
# Can create subkey.
l1.rpc.datastore(key=["a", "b3", "c"], string="ab2val")
assert {"key": ["a"]} in l1.rpc.listdatastore()["datastore"]
assert l1.rpc.listdatastore(key=["a"]) == {
"datastore": [
{
"key": ["a", "b"],
"string": "abval",
"generation": 0,
"hex": b"abval".hex(),
},
{
"key": ["a", "b2"],
"string": "ab2val",
"generation": 0,
"hex": b"ab2val".hex(),
},
{"key": ["a", "b3"]},
]
}
# Can update subkey
l1.rpc.datastore(key=["a", "b3", "c"], string="2", mode="must-append")
assert l1.rpc.listdatastore(key=["a", "b3", "c"]) == {
"datastore": [
{
"key": ["a", "b3", "c"],
"string": "ab2val2",
"generation": 1,
"hex": b"ab2val2".hex(),
}
]
}