1
0
forked from cheng/wallet
wallet/docs/immutable_append_only_data_structure.md

210 lines
12 KiB
Markdown
Raw Normal View History

---
title:
Immutable Append Only Data Structure
# katex
...
The primary chain is a chain of hashes of merkle links, with for each link we have a zeek proving that the entire merkle tree, including data that only one person has, also including long lost or discarded data that no one has any more, is valid. And each block in that chain includes a bunch of hashes of transactions, each of which is the most recent link in a child chain. So the primary chain has a bunch of other chains hanging off it. Each proof proves that the latest block is valid, and that the peer that produced this proof verified the previous proof.
We want wallet chains hanging off the primary chain, and child chains. A wallet chain needs no consensus, for the latest transaction is valid if the machine that knows the wallet secret signs it. Child chains operating by consensus will also hang off it, when we implement corporations and traded shares on the blockchain. Each child chain can have its own rules defining the validity of the latest transaction in that chain. For a child chain operating by consensus, as for example the market in a corporation's shares, the rule will be that a peer can incorporate the transaction that represents a block ancestral to his current block into the parent chain when it has enough weight piled on top it by subsequent blocks, and that every peer in the child chain, when it sees a fork in the child chain by blocks with different incorporation in the forks of the parent chain, has to prefer the branch of the child fork that is incorporated by the branch of the parent fork with the greatest weight.
Each wallet will be a merkle chain,that links to the consensus chain and is linked by the consensus chain, and it consists of a linear chain of transactions each, with a sequence number, each of which commits a non dust amount to a transaction output owned by the wallet - which only owns one such output, because all outputs by other people have to be committed into the destination wallet in a reasonable time.
A wallet chain has a single authority, but we could also have chains hanging off it that represent the consensus of smaller groups, for example the shareholders of a company with their shares in a merkle child chain, and their own total order broadcast channel.
The total order broadcast channel has to keep the complete set of hashes of the most recent transaction outputs of a wallet that commits more than a dust amount of money to the continuing wallet state and keep the path to those states around forever, but with the merkle chain structure envisaged, the path to those states will only grow logarithmically.
I discarded the data on infix and postfix positioning of parents, and the tree depicted does not allow us to reach an arbitrary item from a leaf node, but the final additional merkle vertex produced along with each leaf node does reach every item. At the cost of including a third hash for backtracking in some of the merkle vertexes.
I think I should redraw this by drawing different kinds of vertexes differently, the binary with a two fanged arrow head, the trinary with a three fanged arrow head, and the blocks as blocks.
The blocks one color, the intermediate vertexes another, and final vertex defining a block the final color and larger.
<div style="width: 100%; height: auto; overflow: auto;">
<svg
xmlns="http://www.w3.org/2000/svg"
width="width: 100%" height="100%"
viewBox="0 0 100 60"
style="background-color:white">
<g stroke-width="2">
<path fill=#f00 stroke=#a00
d="
M10,22 c5,-5 15,-10 20,-10 c-5,0 -15,-5 -20,-10 q10,10 0,20 z
M50,22 c5,-5 15,-10 20,-10 c-5,0 -15,-5 -20,-10
q6,8 0,10 q6,2 0,10 z
M20,55 v-20 h36 c0,5 5,8 10,10 c-5,2 -10,5 -10,10 z
" />
</g>
</svg>
</div>
You really need a physical implementation that is a physically an append
only file of irregular sized merkle vertexes, plus a rule for finding the
physical location of the item hashed, plus a collection of quick lookup
files that contain only the upper vertexes. On the other hand, sqlite is
already written, so one might well want to think of implementing this as an
ever incrementing rowid, which we can get around to making into a physically
append only file another day. Stashing hashes of variable heights into a
single table with an ever incrementing rowid is the same problem as the
physical file, except that sqlite allows variable length records. (and does
that housekeeping for us.)
The data defined by hash is immutable but discardable.
One appends data by endlessly adding a new merkle vertex, which contains the hash of the previous vertex.
<svg
xmlns="http://www.w3.org/2000/svg"
width="30em" height="19.6em"
viewBox="0 170 214 140"
style="background-color:ivory"
stroke-width="1"
stroke-linecap="round" >
<g font-family="'Times New Roman'" font-size="10"
font-weight="400" fill-rule="evenodd" fill="black" >
<g id="blockchain_id" >
<ellipse cx="10" cy="240" fill="#0D0" rx="8" ry="5"/>
<text fill="black">
<tspan x="6" y="243.2">id</tspan>
</text>
</g>
<path stroke="#0D0" fill="none" d="
M9,236 c24,-36 55,-28 84,-28 s70,6 70,-9
M11,236 c30,-36 64,2 70,-14
M13,236 c20,-16 22,-6 22,0 c 0,3 0,6.3 2,6.3 q 2,0 3,-3.5
M14,237 c29,-14 4,12 2,15c -2,3 -1,6.5 1,6.5 q 2,0 3,-3.5
M15,238 c24,-10 -11,20.5 -11,28.5c -0,2 1,4 2,4 q 2,0 3,-4
"/>
<g id="balanced merkle tree" fill="none">
<path stroke="#F00" d="
M201,238 q 1,-4 4,-4 c6,0 -8,36.5 -2,36.5 q 2,0 3,-4
M164,200 c1,-4 10,-6 16,-6 c22,0 10,48 17,48 q 2,0 3,-3.5
M164,200 c2,-3 4,-4 10,-4 c22,0 -7,62 3,62 q 2,0 3,-3.5
M164,200 c2,-2 2,-2.3 6,-2.3 c16,0 -12,73 -4,73 q 2,0 3,-4
"/>
<g id="height_4_tree" >
<path stroke="#F00" d="
M82,221 c1,-4 6,-6 16,-6 c18,0 10, 27 16,27 q 2,0 3,-3
M82,221 c2,-3 2,-4 10,-4 c18,0 12, 41.5 18,41.5 q 2,0 3,-3.5
M82,221 c2,-2 2,-2.3 6,-2.3 c18,0 -14, 51.9 -5,51.9 q 2,0 3,-4
"/>
<path stroke="#000" d="
M81,222 c -4,-20 80,0 83,-22
c 3,6 -6,8 -6,22
"/>
<g id="height_3_tree">
<path stroke="#000" d="
M41,238 c 2,-14 39,-4 40,-16
c 3,4 -6,8 -6,16
"/>
<path stroke="#F00" d="
M42,237 c1,-1 1,-4 6,-4 c7,0 -4,25.5 3,25.5 q 2,0 3,-3.5
M42,237 c1,-1 2,-2.5 4,-2.5 c7,0 -14,36 -6,36 q 2,0 3,-4
"/>
<g id="height_2_tree">
<path stroke="#F00"
d="
M22,254 q 0,-4.5 3,-4.5 c5,0 -8,21 -3,21 q 2,0 3,-4
"/>
<path stroke="#000" d="
M21,254
c 2,-14 19,-4 20,-16
c 3,4 -4,8 -4,16
x "/>
<g id="height_1_tree">
<path stroke="#000"
d="
M10,266 c0,-10 10,1 11.5,-13
M16,266 c0,-6 4,0 5.5,-13
"/>
<g id="leaf_vertex" >
<g style="stroke:#000;">
<path id="path1024"
d="
M 10,265 L9,272
M 10,265 L11,272
">
</g>
<rect id="merkle_vertex" width="4" height="4" x="8" y="264" fill="#00F"/>
</g><!-- end id="leaf vertex" -->
<use transform="translate(6)" href="#leaf_vertex"/>
<use transform="translate(11 -12)" href="#merkle_vertex"/>
</g><!-- end id="height_1_tree" -->
<use transform="translate(16)" href="#height_1_tree"/>
<use transform="translate(31 -28)" href="#merkle_vertex"/>
</g><!-- end id="height_2_tree" -->
<g >
<use transform="translate(34)" href="#height_2_tree"/>
<use transform="translate(71 -44)" href="#merkle_vertex"/>
</g>
</g><!-- end id="height_3_tree" -->
<use transform="translate(77)" href="#height_3_tree"/>
<use transform="translate(154 -66)" href="#merkle_vertex"/>
<use transform="translate(160)" href="#height_2_tree"/>
<use transform="translate(197)" href="#leaf_vertex"/>
</g><!-- end id="height_4_tree" -->
</g> <!-- end id="balanced merkle tree" -->
<text y="168">
<tspan dy="12" x="6" >Immutable append only data structure
</tspan>
<tspan dy="12" x="6" >as a collection of balanced Merkle</tspan>
<tspan dy="12" x="6" >dags in postfix order</tspan>
</text>
<g id="merkle_chain">
<use transform="translate(0,60)" href="#blockchain_id"/>
<path
style="fill:none;stroke:#00D000;"
d="m 16,297 c 6,-14 9,16 14,0"/>
<g id="16_leaf_links">
<g id="8_leaf_links">
<g id="4_leaf_links">
<g id="2_leaf_links">
<g id="leaf_link">
<path
style="fill:none;stroke:#000;"
d="m 29,299 c 4,-6 4,-6 5.6,3 C 35,305 38,304 38.5,300"/>
<use transform="translate(20,33)" href="#leaf_vertex"/>
</g><!-- end id="leaf link" -->
<use transform="translate(10,0)"
href="#leaf_link"
/>
</g> <!-- end id="2 leaf links" -->
<use transform="translate(20,0)"
href="#2_leaf_links"
/>
</g> <!-- end id="4 leaf links" -->
<use transform="translate(40,0)"
href="#4_leaf_links"
/>
</g> <!-- end id="8 leaf links" -->
<use transform="translate(80,0)"
href="#8_leaf_links"
/>
</g> <!-- end id="16 leaf links" -->
<use transform="translate(160,0)"
href="#16_leaf_links"
/>
</g> <!-- end id="merkle chain" -->
<rect width="210" height=".4" x="8" y="276" fill="#000"/>
<text y="280">
<tspan dy="8" x="6" >Immutable append only data structure as</tspan>
<tspan dy="8" x="6" >a Merkle chain</tspan>
</text>
</g>
</svg>
Each merkle vertex is associated with a block number, a height, an infix position equal to the block number, plus the block number with the hight bits zeroed and a one bit appended, the infix position, plus the postfix position, which is twice the block number minus the number of one bits in the infix position.
Some of them are two hash groups, and some of them are three hash groups, the number of additional hashes, the number of three hash groups, is equal to the block number, so the position in a physical flat file is equal to the postfix position times the size of a two hash group, plus the block number times the size of the additional hash plus a fixed size region for the common essential fixed sized data of a variable length block. The physical file is a collection of physical files where the name of each file represents its offset in the conceptual very large ever growing index file of fixed sized records, and its offset into the conceptual ever growing file of variable length records, so that simple concantenation of the files generates a valid file.
From time to time we aggregate them into a smaller number of larger files, with a binary or roughly fibonacci size distribution. We do not want aggregation to happen too often with files that are too big, as this screws up standard backup systems, which treat them as fresh files, but on the other hand, do not want an outrageously large number of very tiny files, as standard backup for very large numbers of very tiny files is also apt to be costly, and mapping an offset into a very large number of very tiny files also costly, so a background process aggregating the files, and then checking them, then after check fixing the mapping to map into the new bigger files, then deleting the now unreferenced small files. The rule that each file has to be at least half of the remaining data gives us the binary distribution, while a gentler rule, say at least a quarter, gives us a larger number of files, but less churn.
Every time a fresh tiny file is created, a background process is started to check the files, starting with the tiniest to see if it is time for aggregation. Terminates when the largest aggregation is done.
So that files sort in the correct order, we name them in base 36, with a suffix indicating whether they are the fixed length index records, or the variable sized records that the fixed length records point into.
Although we should make the files friendly for conventional backup for the sake of cold storage, we cannot rely on conventional backup mechanisms, because we have to always have to have very latest state securely backed up before committing it into the blockchain. Which is best done by having a set of remote Sqlite files.