Skip to content

Commit 342f8e6

Browse files
committed
fixup! Add integration and FFI tests for tiered storage
Python test fix and update
1 parent 1eefc14 commit 342f8e6

File tree

2 files changed

+86
-38
lines changed

2 files changed

+86
-38
lines changed

bindings/python/src/ldk_node/kv_store.py

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -57,28 +57,23 @@ def dump(self):
5757

5858
def read(self, primary_namespace: str, secondary_namespace: str, key: str) -> List[int]:
5959
with self._lock:
60-
print(f"[{self.name}] READ: {primary_namespace}/{secondary_namespace}/{key}")
6160
namespace_key = (primary_namespace, secondary_namespace)
62-
61+
6362
if namespace_key not in self.storage:
64-
print(f" -> namespace not found, keys: {list(self.storage.keys())}")
6563
raise IoError.NotFound()
66-
64+
6765
if key not in self.storage[namespace_key]:
68-
print(f" -> key not found, keys: {list(self.storage[namespace_key].keys())}")
6966
raise IoError.NotFound()
7067

71-
data = self.storage[namespace_key][key]
72-
print(f" -> returning {len(data)} bytes")
73-
return data
68+
return list(self.storage[namespace_key][key])
7469

7570
def write(self, primary_namespace: str, secondary_namespace: str, key: str, buf: List[int]) -> None:
7671
with self._lock:
7772
namespace_key = (primary_namespace, secondary_namespace)
7873
if namespace_key not in self.storage:
7974
self.storage[namespace_key] = {}
8075

81-
self.storage[namespace_key][key] = buf
76+
self.storage[namespace_key][key] = list(buf)
8277

8378
def remove(self, primary_namespace: str, secondary_namespace: str, key: str, lazy: bool) -> None:
8479
with self._lock:
@@ -98,7 +93,7 @@ def list(self, primary_namespace: str, secondary_namespace: str) -> List[str]:
9893
with self._lock:
9994
namespace_key = (primary_namespace, secondary_namespace)
10095
if namespace_key in self.storage:
101-
return list(self.storage[namespace_key].keys())
96+
return sorted(self.storage[namespace_key].keys())
10297
return []
10398

10499
async def read_async(self, primary_namespace: str, secondary_namespace: str, key: str) -> List[int]:

bindings/python/src/ldk_node/test_ldk_node.py

Lines changed: 81 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -174,10 +174,10 @@ def setup_node_with_tier_store(tmp_dir, esplora_endpoint, listening_addresses) -
174174
builder.set_chain_source_esplora(esplora_endpoint, None)
175175
builder.set_network(DEFAULT_TEST_NETWORK)
176176
builder.set_listening_addresses(listening_addresses)
177-
builder.set_backup_store(FfiDynStore.from_store(backup))
178-
builder.set_ephemeral_store(FfiDynStore.from_store(ephemeral))
177+
builder.set_backup_store(backup)
178+
builder.set_ephemeral_store(ephemeral)
179179

180-
return builder.build_with_store(node_entropy, FfiDynStore.from_store(primary)), (primary, backup, ephemeral)
180+
return builder.build_with_store(node_entropy, primary), (primary, backup, ephemeral)
181181

182182
def do_channel_full_cycle(setup_1: NodeSetup, setup_2: NodeSetup, esplora_endpoint):
183183
# Fund both nodes
@@ -269,6 +269,32 @@ def expect_event(node, expected_event_type):
269269
node.event_handled()
270270
return event
271271

272+
def wait_until(predicate, timeout=10.0, interval=0.1, message="condition not met before timeout"):
273+
deadline = time.time() + timeout
274+
while time.time() < deadline:
275+
if predicate():
276+
return
277+
time.sleep(interval)
278+
raise AssertionError(message)
279+
280+
281+
def stores_match(store_a, store_b):
282+
if set(store_a.storage.keys()) != set(store_b.storage.keys()):
283+
return False
284+
285+
for namespace_key in store_a.storage:
286+
keys_a = store_a.storage[namespace_key]
287+
keys_b = store_b.storage[namespace_key]
288+
289+
if set(keys_a.keys()) != set(keys_b.keys()):
290+
return False
291+
292+
for key in keys_a:
293+
if keys_a[key] != keys_b[key]:
294+
return False
295+
296+
return True
297+
272298
class TestLdkNode(unittest.TestCase):
273299
def setUp(self):
274300
bitcoin_cli("createwallet ldk_node_test")
@@ -287,40 +313,67 @@ def test_channel_full_cycle(self):
287313
setup_2.cleanup()
288314

289315
def test_tier_store(self):
290-
# Set event loop for async Python callbacks from Rust
316+
# Set an event loop for async Python callbacks invoked from Rust.
291317
# (https://mozilla.github.io/uniffi-rs/0.27/futures.html#python-uniffi_set_event_loop)
292318
loop = asyncio.new_event_loop()
293-
319+
294320
def run_loop():
295321
asyncio.set_event_loop(loop)
296322
loop.run_forever()
297-
323+
298324
loop_thread = threading.Thread(target=run_loop, daemon=True)
299325
loop_thread.start()
300326
ldk_node.uniffi_set_event_loop(loop)
301-
327+
302328
esplora_endpoint = get_esplora_endpoint()
303-
setup_1, setup_2 = setup_two_nodes(esplora_endpoint, port_1=2325, port_2=2326, use_tier_store=True)
304-
305-
do_channel_full_cycle(setup_1, setup_2, esplora_endpoint)
306-
307-
primary, backup, ephemeral = setup_1.stores
308-
309-
# Wait for async backup
310-
time.sleep(2)
311-
312-
self.assertGreater(len(primary.storage), 0, "Primary should have data")
313-
self.assertGreater(len(backup.storage), 0, "Backup should have data")
314-
self.assertEqual(list(primary.storage.keys()), list(backup.storage.keys()),
315-
"Backup should mirror primary")
316-
317-
self.assertGreater(len(ephemeral.storage), 0, "Ephemeral should have data")
318-
ephemeral_keys = [key for namespace in ephemeral.storage.values() for key in namespace.keys()]
319-
has_scorer_or_graph = any(key in ['scorer', 'network_graph'] for key in ephemeral_keys)
320-
self.assertTrue(has_scorer_or_graph, "Ephemeral should contain scorer or network_graph data")
321-
322-
setup_1.cleanup()
323-
setup_2.cleanup()
329+
setup_1, setup_2 = setup_two_nodes(
330+
esplora_endpoint,
331+
port_1=2325,
332+
port_2=2326,
333+
use_tier_store=True,
334+
)
335+
336+
try:
337+
do_channel_full_cycle(setup_1, setup_2, esplora_endpoint)
338+
339+
primary, backup, ephemeral = setup_1.stores
340+
341+
wait_until(
342+
lambda: len(primary.storage) > 0,
343+
message="primary store did not receive any data",
344+
)
345+
wait_until(
346+
lambda: len(backup.storage) > 0,
347+
message="backup store did not receive any data",
348+
)
349+
wait_until(
350+
lambda: stores_match(primary, backup),
351+
timeout=15.0,
352+
message="backup store did not converge to primary contents",
353+
)
354+
355+
self.assertTrue(stores_match(primary, backup), "backup should mirror primary contents")
356+
357+
self.assertGreater(len(ephemeral.storage), 0, "ephemeral should have data")
358+
359+
ephemeral_keys = [
360+
key
361+
for namespace in ephemeral.storage.values()
362+
for key in namespace.keys()
363+
]
364+
has_scorer_or_graph = any(
365+
key in ["scorer", "network_graph"] for key in ephemeral_keys
366+
)
367+
self.assertTrue(
368+
has_scorer_or_graph,
369+
"ephemeral should contain scorer or network_graph data",
370+
)
371+
finally:
372+
setup_1.cleanup()
373+
setup_2.cleanup()
374+
loop.call_soon_threadsafe(loop.stop)
375+
loop_thread.join(timeout=5)
376+
loop.close()
324377

325378
if __name__ == '__main__':
326379
unittest.main()

0 commit comments

Comments
 (0)