Co-authored with @AaronDGoldman
Related to CIP-124
Motivation
Recon is the set reconciliation of key-value pairs. It is a filter-then-divide-and-conquer protocol. As it currently exists, values you wish to filter on MUST be encoded in the Event ID.
This has a few adverse effects:
- The size of the Event ID grows with the number of filters encoded.
- The order of the filters matters. An application that would like to apply a different filter order would need a new Event ID.
- The choice of filters is limited and inflexible.
Proposal
Flat Recon proposes moving the tags to filter on from keys to values, and simplifying the key to be the (abbreviated) hash of the value.
Under Flat Recon, each side begins the conversation by sending its Interests. Each side then calculates its associative hash only on values that survive the filters corresponding to these Interests. Now we can express ranges and perform divide-and-conquer in terms of the (much shorter) keys.
Example value:
[
{
"model": "Model_ID",
"controller": "DID_1",
"after": "Timestamp_1",
},
"payload"
]
This makes Recon for Models more analogous to the SQL query:
SELECT Sum(hash)
WHERE model = <Model_ID>
AND controller in (<DID_1>, <DID_2>)
AND after > <Timestamp>
AND <left_fencepost> < hash
AND hash < <right_fencepost>
model
, controller
, and after
become filters and we use the left and right fenceposts to narrow down the range.
For specifying Interests, we want a more restricted language than SQL. Since both the tags and values are strings, queries for Interests would likely be restricted to the operations =
, <
, <=
, >
, >=
, in
, and not in
.
Once Recon finds a key that is on one side but not the other, it will send the value that contains the tags and the payload.
With Flat Recon, we no longer care about the order the filters are in because the SQL engine can choose the query plan. This enables us to make the wire protocol independent of the query plan.
An option we could use for the keys under this proposal would be the first 96 bits (12 bytes) of the SHA256 of the payload. For the SHA sums, we could use the first 12 bytes of the sum of the SHA256 of the values.
This also lets us separate all the Ceramic logic from Recon logic.
The API to Recon now becomes:
Recon.Add([ { "model": "Model_ID", "controller": "DID_1" }, "payload" ])
Recon.Subscribe("controller in (DID_1, DID_2)")
// Streams new events matching the Interests you're subscribed to
Stream<Events> <- Recon.Listen()
// Gives you everything currently stored matching the specified Interest
Set<Events> <- Recon.Query("controller in (DID_1, DID_2)")
// Not relevant to flat recon
Recon.Connect(peer)