thruput_node_rpc/chunk.rs
1//! The protocol's chunked RPCs, as a Rust server receives them.
2//!
3//! The database publishes a committed block's balance deltas and auth-data
4//! changes as runs of [`NativeTokensTransferredChunk`] and [`KeyChangeChunk`] records,
5//! because a block can touch more accounts than one RPC record carries.
6//! Every record of a run carries the same `chunk_count`, each `chunk_index`
7//! in `[0, chunk_count)` is sent exactly once, and a run always has at least
8//! one record, so an empty message is a run of one empty record. The records
9//! of a run may be sent concurrently and over several connections, so they
10//! can arrive in any order. The `KeyChangeChunk` runs of several heights can
11//! interleave; the database sends one height's `NativeTokensTransferredChunk` run
12//! only once the previous one is acknowledged.
13//!
14//! The [`ChunkRecord`] impls here take the two records apart into height,
15//! [`ChunkPosition`] and items. A server implementing `SchedDb1`, `AuthDB1`
16//! or `MempoolAuth1` (over which the auth server forwards `KeyChangeChunk`
17//! runs to the mempool) hands every record it receives to a
18//! `thruput_rpc_driver::ChunkRunAssembler`, which returns the height's whole message
19//! once every chunk of its run is in, with the parts read as one sequence.
20//! This crate depends on `thruput_rpc_api` alone, so the assembler is not
21//! named here:
22//!
23//! ```
24//! use thruput_rpc_driver::{ChunkRunAssembler, ProgramError};
25//! use thruput_node_rpc::{Delta, NativeTokensTransferredChunk};
26//! use thruput_xdr::types::BlockHeight;
27//! use xdrs::VecM;
28//!
29//! struct Mempool {
30//! transfers: ChunkRunAssembler<BlockHeight, VecM<Delta>>,
31//! }
32//!
33//! impl Mempool {
34//! fn scheddb_native_tokens_transferred_chunk(
35//! &self,
36//! chunk: NativeTokensTransferredChunk,
37//! ) -> Result<(), ProgramError> {
38//! // A chunk that breaks its run's framing is a sender bug; the
39//! // error converts into the ProgramError a handler returns.
40//! if let Some(run) = self.transfers.push_record(chunk)? {
41//! let block: &BlockHeight = &run.key;
42//! for delta in &run.parts {
43//! let _: &Delta = delta;
44//! // apply the block's delta ...
45//! }
46//! }
47//! Ok(())
48//! }
49//! }
50//! ```
51
52use thruput_rpc_api::{ChunkPosition, ChunkRecord};
53use thruput_xdr::types::BlockHeight;
54use xdrs::VecM;
55
56use crate::{
57 Delta, KeyChangeBatch, KeyChangeChunk, NativeTokensTransferredChunk,
58};
59
60impl ChunkRecord for NativeTokensTransferredChunk {
61 type Key = BlockHeight;
62 type Part = VecM<Delta>;
63
64 fn into_chunk(self) -> (BlockHeight, ChunkPosition, VecM<Delta>) {
65 (
66 self.block,
67 ChunkPosition::new(self.chunk_index, self.chunk_count),
68 self.deltas,
69 )
70 }
71}
72
73impl ChunkRecord for KeyChangeChunk {
74 type Key = BlockHeight;
75 type Part = KeyChangeBatch;
76
77 fn into_chunk(self) -> (BlockHeight, ChunkPosition, KeyChangeBatch) {
78 (
79 self.height,
80 ChunkPosition::new(self.chunk_index, self.chunk_count),
81 self.changes,
82 )
83 }
84}
85
86#[cfg(test)]
87mod tests {
88 use super::*;
89 use crate::KeyChange;
90 use thruput_xdr::types::{ContractAddress, int128_xdr};
91
92 fn delta(lowbits: u64) -> Delta {
93 Delta {
94 address: ContractAddress::default(),
95 native_token_delta: int128_xdr {
96 highbits: 0,
97 lowbits,
98 },
99 }
100 }
101
102 #[test]
103 fn a_transfer_chunk_comes_apart_into_block_position_and_deltas() {
104 // The record a handler receives is what the dispatcher decoded from
105 // the wire, so go through the wire form.
106 let chunk = NativeTokensTransferredChunk {
107 block: BlockHeight::new(42),
108 chunk_index: 1,
109 chunk_count: 3,
110 deltas: VecM(vec![delta(1), delta(2)]),
111 };
112 let bytes = xdrs::txs::encode(&chunk).expect("encodes");
113 let decoded: NativeTokensTransferredChunk =
114 xdrs::txs::decode(&bytes).expect("decodes");
115 let (block, position, deltas) = decoded.into_chunk();
116 assert_eq!(block, BlockHeight::new(42));
117 assert_eq!(position, ChunkPosition::new(1, 3));
118 assert_eq!(deltas, VecM(vec![delta(1), delta(2)]));
119 }
120
121 #[test]
122 fn a_key_change_chunk_comes_apart_into_height_position_and_changes() {
123 let change = KeyChange {
124 address: ContractAddress::default(),
125 auth_data: None,
126 };
127 let chunk = KeyChangeChunk {
128 height: BlockHeight::new(5),
129 chunk_index: 0,
130 chunk_count: 1,
131 changes: VecM(vec![change.clone()]),
132 };
133 let (height, position, changes) = chunk.into_chunk();
134 assert_eq!(height, BlockHeight::new(5));
135 assert_eq!(position, ChunkPosition::new(0, 1));
136 assert_eq!(changes, VecM(vec![change]));
137 }
138}