Skip to content

Commit cebb397

Browse files
authored
fix a corner case (#53)
1 parent 0a7a0da commit cebb397

File tree

3 files changed

+125
-1
lines changed

3 files changed

+125
-1
lines changed

node/rpc/src/kv_rpc_server/impl.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,13 @@ impl KeyValueRpcServerImpl {
2424
start_index: u64,
2525
len: u64,
2626
) -> RpcResult<Option<ValueSegment>> {
27+
if len == 0 {
28+
return Ok(Some(ValueSegment {
29+
version: pair.version,
30+
data: vec![],
31+
size: pair.end_index - pair.start_index,
32+
}));
33+
}
2734
if start_index > pair.end_index - pair.start_index {
2835
return Err(error::invalid_params(
2936
"start_index",
@@ -63,6 +70,14 @@ impl KeyValueRpcServerImpl {
6370
start_index: u64,
6471
len: u64,
6572
) -> RpcResult<Option<KeyValueSegment>> {
73+
if len == 0 {
74+
return Ok(Some(KeyValueSegment {
75+
version: pair.version,
76+
key: pair.key,
77+
data: vec![],
78+
size: pair.end_index - pair.start_index,
79+
}));
80+
}
6681
if start_index > pair.end_index - pair.start_index {
6782
return Err(error::invalid_params(
6883
"start_index",

tests/config/cosmos-genesis.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"app_name": "0gchaind",
33
"app_version": "v0.2.0-alpha.4-892-g6b920eb40",
4-
"genesis_time": "2026-02-24T15:30:05.289745Z",
4+
"genesis_time": "2026-02-27T06:41:33.885477Z",
55
"chain_id": "0gchaind-local",
66
"initial_height": 1,
77
"app_hash": null,

tests/kv_iterator_test.py

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
#!/usr/bin/env python3
2+
import base64
23
import random
34
from kv_test_framework.test_framework import KVTestFramework
45
from utility.kv import (
@@ -39,6 +40,13 @@ def run_test(self):
3940
# write empty stream
4041
self.write_streams()
4142

43+
# rpc edge case tests
44+
self.test_len_zero()
45+
self.test_len_one()
46+
self.test_len_larger_than_value()
47+
self.test_start_index_at_boundary()
48+
self.test_nonexistent_key()
49+
4250
def submit(
4351
self,
4452
version,
@@ -207,6 +215,107 @@ def write_streams(self):
207215
pair = self.kv_nodes[0].prev(stream_id, current_key, second_version)
208216
assert cnt == len(self.data.items()) - deleted
209217

218+
self.update_data(writes)
219+
220+
def _get_first_key_and_value(self):
221+
"""Get the first non-deleted key and its value from self.data."""
222+
stream_id = to_stream_id(1)
223+
for stream_id_key, value in sorted(self.data.items()):
224+
sid, key = stream_id_key.split(",")
225+
if sid == stream_id and value is not None:
226+
return stream_id, key, value
227+
raise AssertionError("no non-deleted key found")
228+
229+
def test_len_zero(self):
230+
"""len=0 should return metadata with empty data, not crash."""
231+
stream_id, key, value = self._get_first_key_and_value()
232+
node = self.kv_nodes[0]
233+
234+
# kv_getValue with len=0
235+
res = node.kv_get_value(stream_id, key, 0, 0)
236+
assert res is not None, "kv_getValue(len=0) returned None"
237+
assert_equal(base64.b64decode(res["data"].encode("utf-8")), b"")
238+
assert_equal(res["size"], len(value))
239+
240+
# kv_getFirst with len=0
241+
res = node.kv_get_first(stream_id, 0, 0)
242+
assert res is not None, "kv_getFirst(len=0) returned None"
243+
assert_equal(base64.b64decode(res["data"].encode("utf-8")), b"")
244+
assert res["size"] > 0
245+
246+
# kv_getLast with len=0
247+
res = node.kv_get_last(stream_id, 0, 0)
248+
assert res is not None, "kv_getLast(len=0) returned None"
249+
assert_equal(base64.b64decode(res["data"].encode("utf-8")), b"")
250+
assert res["size"] > 0
251+
252+
# kv_getNext with len=0
253+
res = node.kv_get_next(stream_id, key, 0, 0)
254+
assert res is not None, "kv_getNext(len=0) returned None"
255+
assert_equal(base64.b64decode(res["data"].encode("utf-8")), b"")
256+
assert res["size"] > 0
257+
258+
# kv_getPrev with len=0 (use last key so there's a prev)
259+
last_pair = node.seek_to_last(stream_id)
260+
res = node.kv_get_prev(stream_id, last_pair["key"], 0, 0)
261+
assert res is not None, "kv_getPrev(len=0) returned None"
262+
assert_equal(base64.b64decode(res["data"].encode("utf-8")), b"")
263+
assert res["size"] > 0
264+
265+
def test_len_one(self):
266+
"""len=1 should return exactly 1 byte of data."""
267+
stream_id, key, value = self._get_first_key_and_value()
268+
node = self.kv_nodes[0]
269+
270+
res = node.kv_get_value(stream_id, key, 0, 1)
271+
assert res is not None
272+
data = base64.b64decode(res["data"].encode("utf-8"))
273+
assert_equal(len(data), 1)
274+
assert_equal(data, value[:1])
275+
assert_equal(res["size"], len(value))
276+
277+
res = node.kv_get_first(stream_id, 0, 1)
278+
assert res is not None
279+
data = base64.b64decode(res["data"].encode("utf-8"))
280+
assert_equal(len(data), 1)
281+
282+
def test_len_larger_than_value(self):
283+
"""len larger than remaining bytes should return clamped data."""
284+
stream_id, key, value = self._get_first_key_and_value()
285+
node = self.kv_nodes[0]
286+
287+
# read from midpoint with len larger than remaining bytes
288+
mid = len(value) // 2
289+
remaining = len(value) - mid
290+
oversized_len = remaining + 1024
291+
res = node.kv_get_value(stream_id, key, mid, oversized_len)
292+
assert res is not None
293+
data = base64.b64decode(res["data"].encode("utf-8"))
294+
assert_equal(data, value[mid:])
295+
assert_equal(res["size"], len(value))
296+
297+
def test_start_index_at_boundary(self):
298+
"""start_index at or past value size should return error."""
299+
stream_id, key, value = self._get_first_key_and_value()
300+
node = self.kv_nodes[0]
301+
302+
try:
303+
node.kv_get_value(stream_id, key, len(value) + 1, 1)
304+
assert False, "expected error for start_index past value size"
305+
except Exception:
306+
pass
307+
308+
def test_nonexistent_key(self):
309+
"""Querying a key that doesn't exist should return empty."""
310+
stream_id = to_stream_id(1)
311+
node = self.kv_nodes[0]
312+
fake_key = "ff" * 32 # key that was never written
313+
314+
res = node.kv_get_value(stream_id, fake_key, 0, 1)
315+
assert res is not None
316+
assert_equal(base64.b64decode(res["data"].encode("utf-8")), b"")
317+
assert_equal(res["size"], 0)
318+
210319

211320
if __name__ == "__main__":
212321
KVPutGetTest().main()

0 commit comments

Comments
 (0)