Skip to content

Commit 2972001

Browse files
Update to df03d866237f44c6d0f81d05783da493654f0f8c (#66)
1 parent eb7f774 commit 2972001

78 files changed

Lines changed: 52185 additions & 33469 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

Cargo.lock

Lines changed: 323 additions & 43 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -33,14 +33,18 @@ axum = "0.8.1"
3333
base64 = "0.22.1"
3434
hex = "0.4"
3535
regex = "1"
36-
commonware-codec = { git = "https://github.com/commonwarexyz/monorepo", rev = "2cc941653cb936952c00587f8cb037c8021241a5", package = "commonware-codec" }
37-
commonware-consensus = { git = "https://github.com/commonwarexyz/monorepo", rev = "2cc941653cb936952c00587f8cb037c8021241a5", package = "commonware-consensus" }
38-
commonware-cryptography = { git = "https://github.com/commonwarexyz/monorepo", rev = "2cc941653cb936952c00587f8cb037c8021241a5", package = "commonware-cryptography" }
39-
commonware-math = { git = "https://github.com/commonwarexyz/monorepo", rev = "2cc941653cb936952c00587f8cb037c8021241a5", package = "commonware-math" }
40-
commonware-parallel = { git = "https://github.com/commonwarexyz/monorepo", rev = "2cc941653cb936952c00587f8cb037c8021241a5", package = "commonware-parallel" }
41-
commonware-runtime = { git = "https://github.com/commonwarexyz/monorepo", rev = "2cc941653cb936952c00587f8cb037c8021241a5", package = "commonware-runtime" }
42-
commonware-storage = { git = "https://github.com/commonwarexyz/monorepo", rev = "2cc941653cb936952c00587f8cb037c8021241a5", package = "commonware-storage" }
43-
commonware-utils = { git = "https://github.com/commonwarexyz/monorepo", rev = "2cc941653cb936952c00587f8cb037c8021241a5", package = "commonware-utils" }
36+
commonware-codec = { git = "https://github.com/commonwarexyz/monorepo", rev = "df03d866237f44c6d0f81d05783da493654f0f8c", package = "commonware-codec" }
37+
commonware-actor = { git = "https://github.com/commonwarexyz/monorepo", rev = "df03d866237f44c6d0f81d05783da493654f0f8c", package = "commonware-actor" }
38+
commonware-consensus = { git = "https://github.com/commonwarexyz/monorepo", rev = "df03d866237f44c6d0f81d05783da493654f0f8c", package = "commonware-consensus" }
39+
commonware-cryptography = { git = "https://github.com/commonwarexyz/monorepo", rev = "df03d866237f44c6d0f81d05783da493654f0f8c", package = "commonware-cryptography" }
40+
commonware-glue = { git = "https://github.com/commonwarexyz/monorepo", rev = "df03d866237f44c6d0f81d05783da493654f0f8c", package = "commonware-glue" }
41+
commonware-math = { git = "https://github.com/commonwarexyz/monorepo", rev = "df03d866237f44c6d0f81d05783da493654f0f8c", package = "commonware-math" }
42+
commonware-parallel = { git = "https://github.com/commonwarexyz/monorepo", rev = "df03d866237f44c6d0f81d05783da493654f0f8c", package = "commonware-parallel" }
43+
commonware-p2p = { git = "https://github.com/commonwarexyz/monorepo", rev = "df03d866237f44c6d0f81d05783da493654f0f8c", package = "commonware-p2p" }
44+
commonware-resolver = { git = "https://github.com/commonwarexyz/monorepo", rev = "df03d866237f44c6d0f81d05783da493654f0f8c", package = "commonware-resolver" }
45+
commonware-runtime = { git = "https://github.com/commonwarexyz/monorepo", rev = "df03d866237f44c6d0f81d05783da493654f0f8c", package = "commonware-runtime" }
46+
commonware-storage = { git = "https://github.com/commonwarexyz/monorepo", rev = "df03d866237f44c6d0f81d05783da493654f0f8c", package = "commonware-storage" }
47+
commonware-utils = { git = "https://github.com/commonwarexyz/monorepo", rev = "df03d866237f44c6d0f81d05783da493654f0f8c", package = "commonware-utils" }
4448
rocksdb = "0.22.0"
4549
rand = "0.8.5"
4650
bincode = "1.3.3"
@@ -55,10 +59,10 @@ portpicker = "0.1.1"
5559
http = "1.3.1"
5660
tower-http = { version = "0.5.2", features = ["cors"] }
5761
bytes = "1"
58-
connectrpc = { version = "0.3.1", features = ["client", "axum"] }
62+
connectrpc = { version = "0.6.0", features = ["client", "axum"] }
5963
hyper = { version = "1", features = ["full"] }
60-
buffa = { version = "0.3", features = ["json"] }
61-
buffa-types = { version = "0.3", features = ["json"] }
64+
buffa = { version = "0.6", features = ["json"] }
65+
buffa-types = { version = "0.6", features = ["json"] }
6266
http-body-util = "0.1"
6367
tower = { version = "0.5", features = ["util"] }
6468
anyhow = "1"

examples/sandbox/README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,8 @@ In addition to the simulator running above:
104104
- **Get Many** verifies current hit/miss lookup proofs against the same root and reports proof size.
105105
- **Get Range** verifies an ordered current range plus boundary proofs
106106
against the same root and reports proof size.
107+
- **Get Historical Operation Range** verifies a contiguous historical
108+
operation-log range against the same trusted root and reports proof size.
107109
- **Subscribe** verifies each emitted historical proof from the trusted
108110
current root for that emitted tip and reports proof size. Paste seed
109111
output lines into Trusted Roots when replaying or following multiple tips.

examples/sandbox/src/QmdbPanel.tsx

Lines changed: 111 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,12 @@ import {
44
matchPrefix,
55
matchRegex,
66
OrderedQmdbClient,
7+
type OrderedOperation,
78
type OrderedSubscribeProof,
89
type VerifiedCurrentKeyLookupProof,
910
type VerifiedCurrentKeyRangeProof,
1011
type VerifiedCurrentKeyValueProof,
12+
type VerifiedHistoricalMultiProof,
1113
} from '@qmdb-ts';
1214

1315
export const QMDB_URL = import.meta.env.VITE_QMDB_URL as string | undefined;
@@ -56,21 +58,23 @@ function formatProofSize(bytes: number): string {
5658
return `${(bytes / 1024).toFixed(1)} KiB`;
5759
}
5860

59-
function parseTip(value: string): bigint {
61+
function parseNonNegativeBigInt(value: string, label: string): bigint {
6062
const trimmed = value.trim();
6163
if (!trimmed) {
62-
throw new Error('Tip is required');
64+
throw new Error(`${label} is required`);
6365
}
64-
const tip = BigInt(trimmed);
65-
if (tip < 0n) {
66-
throw new Error('Tip must be non-negative');
66+
const parsed = BigInt(trimmed);
67+
if (parsed < 0n) {
68+
throw new Error(`${label} must be non-negative`);
6769
}
68-
return tip;
70+
return parsed;
71+
}
72+
73+
function parseTip(value: string): bigint {
74+
return parseNonNegativeBigInt(value, 'Tip');
6975
}
7076

71-
function renderOperation(
72-
proofOperation: OrderedSubscribeProof['proof']['operations'][number]['operation'],
73-
) {
77+
function renderOperation(proofOperation: OrderedOperation) {
7478
switch (proofOperation.type) {
7579
case 'update':
7680
return (
@@ -132,6 +136,11 @@ export function QmdbPanel({
132136
const [rangeProof, setRangeProof] = useState<VerifiedCurrentKeyRangeProof | null>(null);
133137
const [isGettingRange, setIsGettingRange] = useState(false);
134138

139+
const [historyStartLocation, setHistoryStartLocation] = useState('0');
140+
const [historyMaxLocations, setHistoryMaxLocations] = useState('5');
141+
const [historyProof, setHistoryProof] = useState<VerifiedHistoricalMultiProof | null>(null);
142+
const [isGettingHistory, setIsGettingHistory] = useState(false);
143+
135144
const [keyMatcherKind, setKeyMatcherKind] = useState<'exact' | 'prefix' | 'regex' | 'none'>(
136145
'none',
137146
);
@@ -245,6 +254,35 @@ export function QmdbPanel({
245254
}
246255
};
247256

257+
const handleGetOperationRange = async () => {
258+
setIsGettingHistory(true);
259+
setHistoryProof(null);
260+
try {
261+
const maxLocations = Number(historyMaxLocations);
262+
if (!Number.isInteger(maxLocations) || maxLocations <= 0) {
263+
throw new Error('Max Locations must be a positive integer');
264+
}
265+
const proof = await client.getOperationRange(
266+
{
267+
tip: parseTip(tip),
268+
startLocation: parseNonNegativeBigInt(historyStartLocation, 'Start Location'),
269+
maxLocations,
270+
},
271+
parseHexRoot(expectedCurrentRoot),
272+
);
273+
setHistoryProof(proof);
274+
showNotification(
275+
'success',
276+
'QMDB Historical Range',
277+
`Verified ${proof.operations.length} historical operations against expected root (${formatProofSize(proof.proofSizeBytes)})`,
278+
);
279+
} catch (error) {
280+
showNotification('error', 'QMDB Historical Range Failed', String(error));
281+
} finally {
282+
setIsGettingHistory(false);
283+
}
284+
};
285+
248286
function buildFilter(
249287
kind: 'exact' | 'prefix' | 'regex' | 'none',
250288
value: string,
@@ -310,9 +348,9 @@ export function QmdbPanel({
310348
<p className="section-note">
311349
Proofs are anchored to roots the writer emits per batch. Run `qmdb run`
312350
locally and `qmdb seed` to stream fresh tips; each line prints
313-
`tip=N root=0x..`. Get Proof, Get Many, and Get Range verify
314-
against that current root. Subscribe streams each proof with its tip
315-
and included operations.
351+
`tip=N root=0x..`. Get Proof, Get Many, Get Range, and historical
352+
operation ranges verify against that current root. Subscribe streams
353+
each proof with its tip and included operations.
316354
</p>
317355
<p><strong>Server:</strong> {qmdbUrl}</p>
318356
<p><strong>Merkle Family:</strong> MMB</p>
@@ -645,6 +683,67 @@ export function QmdbPanel({
645683
)}
646684
</div>
647685
</div>
686+
687+
<div className="form-section">
688+
<h3>Get Historical Operation Range</h3>
689+
<p className="section-note">
690+
Fetches a contiguous historical operation proof for the operation log and verifies it
691+
against the expected root for the selected tip.
692+
</p>
693+
<div className="form-row">
694+
<div className="form-group">
695+
<label htmlFor="qmdb-history-start">Start Location</label>
696+
<input
697+
id="qmdb-history-start"
698+
type="number"
699+
min="0"
700+
value={historyStartLocation}
701+
onChange={(event) => setHistoryStartLocation(event.target.value)}
702+
/>
703+
</div>
704+
<div className="form-group">
705+
<label htmlFor="qmdb-history-max">Max Locations</label>
706+
<input
707+
id="qmdb-history-max"
708+
type="number"
709+
min="1"
710+
value={historyMaxLocations}
711+
onChange={(event) => setHistoryMaxLocations(event.target.value)}
712+
/>
713+
</div>
714+
</div>
715+
<button
716+
className={`btn-primary ${isGettingHistory ? 'loading' : ''}`}
717+
onClick={handleGetOperationRange}
718+
disabled={
719+
isGettingHistory ||
720+
!historyStartLocation.trim() ||
721+
!historyMaxLocations.trim() ||
722+
!tip.trim() ||
723+
!expectedCurrentRoot.trim()
724+
}
725+
>
726+
{isGettingHistory ? 'Verifying...' : 'Get Historical Range'}
727+
</button>
728+
{historyProof && (
729+
<div className="result fade-in">
730+
<h4>Verified Historical Operations</h4>
731+
<p><strong>Proof Size:</strong> {formatProofSize(historyProof.proofSizeBytes)}</p>
732+
<p><strong>Operations:</strong> {historyProof.operations.length}</p>
733+
<div className="result-list">
734+
{historyProof.operations.map((op, index) => (
735+
<div
736+
key={`${op.location.toString()}-${index}`}
737+
className="result-row-block"
738+
>
739+
<p><strong>Location:</strong> {op.location.toString()}</p>
740+
{renderOperation(op.operation)}
741+
</div>
742+
))}
743+
</div>
744+
</div>
745+
)}
746+
</div>
648747
</div>
649748
);
650749
}

examples/simulator/src/rocks.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -482,11 +482,11 @@ impl Ingest for RocksStore {
482482
impl Query for RocksStore {
483483
type RangeScan = RocksRangeScanCursor;
484484

485-
async fn get(&self, key: Bytes) -> Result<(Option<Vec<u8>>, QueryExtra), String> {
485+
async fn get(&self, key: Bytes) -> Result<(Option<Bytes>, QueryExtra), String> {
486486
let store = self.clone();
487487
store
488488
.get_rocksdb(&key)
489-
.map(|value| (value, QueryExtra::default()))
489+
.map(|value| (value.map(Bytes::from), QueryExtra::default()))
490490
.map_err(|e| e.to_string())
491491
}
492492

@@ -504,15 +504,15 @@ impl Query for RocksStore {
504504
async fn get_many(
505505
&self,
506506
keys: Vec<Bytes>,
507-
) -> Result<(Vec<(Vec<u8>, Option<Vec<u8>>)>, QueryExtra), String> {
507+
) -> Result<(Vec<(Bytes, Option<Bytes>)>, QueryExtra), String> {
508508
let store = self.clone();
509509
let results = store.db.multi_get(keys.iter().map(|key| key.as_ref()));
510510
let entries = keys
511511
.into_iter()
512512
.zip(results)
513513
.map(|(k, r)| {
514514
let value = r.map_err(|e| e.to_string())?;
515-
Ok((k.to_vec(), value))
515+
Ok((k, value.map(Bytes::from)))
516516
})
517517
.collect::<Result<Vec<_>, String>>()?;
518518
Ok((entries, QueryExtra::default()))

examples/simulator/tests/e2e_connect.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -272,7 +272,7 @@ async fn prune_drop_all_removes_keys() {
272272
assert!(client.query().get(&ka).await.expect("get a").is_some());
273273

274274
let compact_config = ClientConfig::new(url.parse::<http::Uri>().unwrap())
275-
.compression(connect_compression_registry());
275+
.with_compression(connect_compression_registry());
276276
let compact_client =
277277
CompactServiceClient::new(PreferZstdHttpClient::plaintext(), compact_config);
278278
compact_client
@@ -439,7 +439,7 @@ async fn prune_keep_latest_retains_newest() {
439439
.expect("put");
440440

441441
let compact_config = ClientConfig::new(url.parse::<http::Uri>().unwrap())
442-
.compression(connect_compression_registry());
442+
.with_compression(connect_compression_registry());
443443
let compact_client =
444444
CompactServiceClient::new(PreferZstdHttpClient::plaintext(), compact_config);
445445
compact_client
@@ -662,7 +662,7 @@ async fn prune_greater_than_retains_above_threshold() {
662662
.expect("put");
663663

664664
let compact_config = ClientConfig::new(url.parse::<http::Uri>().unwrap())
665-
.compression(connect_compression_registry());
665+
.with_compression(connect_compression_registry());
666666
let compact_client =
667667
CompactServiceClient::new(PreferZstdHttpClient::plaintext(), compact_config);
668668
compact_client

examples/simulator/tests/prune_contract.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ fn put_batch(store: &RocksStore, kvs: Vec<(Bytes, Bytes)>) -> u64 {
4141
block_on(store.put_batch(kvs)).expect("put_batch")
4242
}
4343

44-
fn get_value(store: &RocksStore, key: &Bytes) -> Option<Vec<u8>> {
44+
fn get_value(store: &RocksStore, key: &Bytes) -> Option<Bytes> {
4545
block_on(store.get(key.clone())).expect("get").0
4646
}
4747

examples/simulator/tests/range_scan_contract.rs

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -59,13 +59,13 @@ fn scan(
5959
rows
6060
}
6161

62-
fn get_value(store: &RocksStore, key: &[u8]) -> Option<Vec<u8>> {
62+
fn get_value(store: &RocksStore, key: &[u8]) -> Option<Bytes> {
6363
block_on(store.get(Bytes::copy_from_slice(key)))
6464
.expect("get")
6565
.0
6666
}
6767

68-
fn get_many_values(store: &RocksStore, keys: &[&[u8]]) -> Vec<(Vec<u8>, Option<Vec<u8>>)> {
68+
fn get_many_values(store: &RocksStore, keys: &[&[u8]]) -> Vec<(Bytes, Option<Bytes>)> {
6969
let keys = keys.iter().map(|key| Bytes::copy_from_slice(key)).collect();
7070
block_on(store.get_many(keys)).expect("get_many").0
7171
}
@@ -242,7 +242,13 @@ fn get_many_returns_found_and_missing() {
242242

243243
let results = get_many_values(&store, &[b"a", b"missing", b"c"]);
244244
assert_eq!(results.len(), 3);
245-
assert_eq!(results[0], (b"a".to_vec(), Some(b"1".to_vec())));
246-
assert_eq!(results[1], (b"missing".to_vec(), None));
247-
assert_eq!(results[2], (b"c".to_vec(), Some(b"3".to_vec())));
245+
assert_eq!(
246+
results[0],
247+
(Bytes::from_static(b"a"), Some(Bytes::from_static(b"1")))
248+
);
249+
assert_eq!(results[1], (Bytes::from_static(b"missing"), None));
250+
assert_eq!(
251+
results[2],
252+
(Bytes::from_static(b"c"), Some(Bytes::from_static(b"3")))
253+
);
248254
}

proto/qmdb/v1/key_lookup.proto

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ service KeyLookupService {
1919

2020
// Current key proof request.
2121
message GetRequest {
22-
// Raw logical QMDB key (`K::as_ref()` bytes), not a store row key.
22+
// Codec-encoded logical QMDB key (`K::encode()` bytes), not a store row key.
2323
bytes key = 1;
2424

2525
// Published ordered-QMDB batch-boundary to prove against. The client must
@@ -29,7 +29,7 @@ message GetRequest {
2929

3030
// Current key proof request for one or more logical keys.
3131
message GetManyRequest {
32-
// Raw logical QMDB keys (`K::as_ref()` bytes), not store row keys.
32+
// Codec-encoded logical QMDB keys (`K::encode()` bytes), not store row keys.
3333
repeated bytes keys = 1 [
3434
(buf.validate.field).repeated.min_items = 1,
3535
(buf.validate.field).repeated.max_items = 1024

proto/qmdb/v1/key_range.proto

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,17 +12,20 @@ service OrderedKeyRangeService {
1212
rpc GetRange(GetRangeRequest) returns (GetRangeResponse);
1313
}
1414

15-
// Current ordered key range proof request. The range is half-open:
16-
// `start_key <= key < end_key` when `end_key` is set; otherwise it scans to the
17-
// end of the ordered keyspace. `limit` must be non-zero.
15+
// Current ordered key range proof request. Key fields are codec-encoded logical
16+
// QMDB keys (`K::encode()` bytes), not store row keys. The range is half-open:
17+
// `start_key <= key < end_key` after decoding `K` when `end_key` is set;
18+
// otherwise it scans to the end of the ordered keyspace. `limit` must be
19+
// non-zero.
1820
message GetRangeRequest {
1921
bytes start_key = 1;
2022
optional bytes end_key = 2;
2123
uint32 limit = 3 [(buf.validate.field).uint32.gt = 0];
2224
uint64 tip = 4;
2325
}
2426

25-
// Ordered current key-range proof response. `start_proof`, when present,
27+
// Ordered current key-range proof response. Key fields are codec-encoded
28+
// logical QMDB keys (`K::encode()` bytes). `start_proof`, when present,
2629
// authenticates the boundary before the first returned key (or the entire empty
2730
// range). `end_proof`, when present, authenticates the exclusive end boundary.
2831
message GetRangeResponse {

0 commit comments

Comments
 (0)