Files
plugins/datastore/datastore-plugin.py
Rusty Russell 2ec769e05b datastore.py: update to meet new datastore builtin PR.
The new datastore PR now has a generation count; this passes
the tests against that now.

Also copies tests from c-lightning, and adds ugprade test.

Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
2021-08-25 18:10:40 +02:00

140 lines
4.6 KiB
Python
Executable File

#!/usr/bin/env python3
"""This does the actual datastore work, if the main plugin says there's no
datastore support. We can't even load this if there's real datastore support.
"""
from pyln.client import Plugin, RpcException
from collections import namedtuple
import os
import shelve
# Error codes
DATASTORE_DEL_DOES_NOT_EXIST = 1200
DATASTORE_DEL_WRONG_GENERATION = 1201
DATASTORE_UPDATE_ALREADY_EXISTS = 1202
DATASTORE_UPDATE_DOES_NOT_EXIST = 1203
DATASTORE_UPDATE_WRONG_GENERATION = 1204
plugin = Plugin()
Entry = namedtuple('Entry', ['generation', 'data'])
def datastore_entry(key, entry: Entry):
"""Return a dict representing the entry"""
# Entry may be a simple tuple; convert
entry = Entry(*entry)
ret = {'key': key, 'generation': entry.generation, 'hex': entry.data.hex()}
# FFS, Python3 seems happy with \0 in UTF-8.
if 0 not in entry.data:
try:
ret['string'] = entry.data.decode('utf8')
except UnicodeDecodeError:
pass
return ret
@plugin.method("datastore")
def datastore(plugin, key, string=None, hex=None, mode="must-create", generation=None):
"""Add/modify a {key} and {hex}/{string} data to the data store,
optionally insisting it be {generation}"""
if string is not None:
if hex is not None:
raise RpcException("Cannot specify both string or hex")
data = bytes(string, encoding="utf8")
elif hex is None:
raise RpcException("Must specify string or hex")
else:
data = bytes.fromhex(hex)
if mode == "must-create":
if key in plugin.datastore:
raise RpcException("already exists", DATASTORE_UPDATE_ALREADY_EXISTS)
elif mode == "must-replace":
if key not in plugin.datastore:
raise RpcException("does not exist", DATASTORE_UPDATE_DOES_NOT_EXIST)
elif mode == "create-or-replace":
if generation is not None:
raise RpcException("generation only valid with"
" must-create/must-replace")
pass
elif mode == "must-append":
if generation is not None:
raise RpcException("generation only valid with"
" must-create/must-replace")
if key not in plugin.datastore:
raise RpcException("does not exist", DATASTORE_UPDATE_DOES_NOT_EXIST)
data = plugin.datastore[key].data + data
elif mode == "create-or-append":
if generation is not None:
raise RpcException("generation only valid with"
" must-create/must-replace")
data = plugin.datastore.get(key, Entry(0, bytes())).data + data
else:
raise RpcException("invalid mode")
if key in plugin.datastore:
entry = plugin.datastore[key]
if generation is not None:
if entry.generation != generation:
raise RpcException("generation is different",
DATASTORE_UPDATE_WRONG_GENERATION)
gen = entry.generation + 1
else:
gen = 0
plugin.datastore[key] = Entry(gen, data)
return datastore_entry(key, plugin.datastore[key])
@plugin.method("deldatastore")
def deldatastore(plugin, key, generation=None):
"""Remove a {key} from the data store"""
if not key in plugin.datastore:
raise RpcException("does not exist", DATASTORE_DEL_DOES_NOT_EXIST)
entry = plugin.datastore[key]
if generation is not None and entry.generation != generation:
raise RpcException("generation is different",
DATASTORE_DEL_WRONG_GENERATION)
ret = datastore_entry(key, entry)
del plugin.datastore[key]
return ret
@plugin.method("listdatastore")
def listdatastore(plugin, key=None):
"""List datastore entries"""
if key is None:
return {'datastore': [datastore_entry(k, e)
for k, e in plugin.datastore.items()]}
if key in plugin.datastore:
return {'datastore': [datastore_entry(key, plugin.datastore[key])]}
return {'datastore': []}
def upgrade_store(plugin):
"""Initial version of this plugin had no generation numbers"""
try:
oldstore = shelve.open('datastore.dat', 'r')
except:
return
plugin.log("Upgrading store to have generation numbers", level='unusual')
datastore = shelve.open('datastore_v1.dat', 'c')
for k, d in oldstore.items():
datastore[k] = Entry(0, d)
oldstore.close()
datastore.close()
os.unlink('datastore.dat')
@plugin.init()
def init(options, configuration, plugin):
upgrade_store(plugin)
plugin.datastore = shelve.open('datastore_v1.dat')
plugin.run()