diff --git a/lbry/wallet/server/db/revertable.py b/lbry/wallet/server/db/revertable.py index e82c36f12..2a05f2a7d 100644 --- a/lbry/wallet/server/db/revertable.py +++ b/lbry/wallet/server/db/revertable.py @@ -83,11 +83,26 @@ class OpStackIntegrity(Exception): class RevertableOpStack: def __init__(self, get_fn: Callable[[bytes], Optional[bytes]], unsafe_prefixes=None): + """ + This represents a sequence of revertable puts and deletes to a key-value database that checks for integrity + violations when applying the puts and deletes. The integrity checks assure that keys that do not exist + are not deleted, and that when keys are deleted the current value is correctly known so that the delete + may be undone. When putting values, the integrity checks assure that existing values are not overwritten + without first being deleted. Updates are performed by applying a delete op for the old value and a put op + for the new value. + + :param get_fn: getter function from an object implementing `KeyValueStorage` + :param unsafe_prefixes: optional set of prefixes to ignore integrity errors for, violations are still logged + """ self._get = get_fn self._items = defaultdict(list) self._unsafe_prefixes = unsafe_prefixes or set() def append_op(self, op: RevertableOp): + """ + Apply a put or delete op, checking that it introduces no integrity errors + """ + inverted = op.invert() if self._items[op.key] and inverted == self._items[op.key][-1]: self._items[op.key].pop() # if the new op is the inverse of the last op, we can safely null both @@ -119,6 +134,9 @@ class RevertableOpStack: self._items[op.key].append(op) def extend_ops(self, ops: Iterable[RevertableOp]): + """ + Apply a sequence of put or delete ops, checking that they introduce no integrity errors + """ for op in ops: self.append_op(op) @@ -139,9 +157,15 @@ class RevertableOpStack: yield op def get_undo_ops(self) -> bytes: + """ + Get the serialized bytes to undo all of the changes made by the pending ops + """ return b''.join(op.invert().pack() for op in reversed(self)) def apply_packed_undo_ops(self, packed: bytes): + """ + Unpack and apply a sequence of undo ops from serialized undo bytes + """ while packed: op, packed = RevertableOp.unpack(packed) self.append_op(op)