-
Notifications
You must be signed in to change notification settings - Fork 997
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Generalized Merkle Tree Index: use 0-based indexing #1008
Comments
The memory saving is really negligible, and I think it actually improves alignment; " If we have N nodes in the merkle tree where N is a power of 2 we will waste N-1 node space." is incorrect because most of the Merkle trees we use have 2^N leaves, and so 2^(N+1) - 1 total nodes, so adding an empty value in the zero position just gets us to 2^(N+1) Also, and much more importantly, generalized indices are not meant to correspond to memory positions and are you are not meant to think of it as an array; for most complex SSZ objects, that would lead to a lot of empty space in your serialization because of regions that are unused. The purpose of a generalized index is to serialize a binary path, and you can think of the algorithm as follows: the highest 1-bit is a flag saying "the bits before this were just zeroes but the bits after this are where the actual path starts", followed by the bits of the path from top to bottom. Viewed this way, offsetting everything down by 1 would break this abstraction. |
The issue with that is that we will need a translation layer between generalized indices and the actual implementation which is 0-based raising the risk of off-by-one errors. Assuming we implement as sparse Merkle trees, the space difference disappears for both 0-based and 1-based but the risk of errors stays. Regarding the bit abstraction part, this is indeed the same coding as used in Fenwick Trees/Binary Indexed Trees
However in practice going through the branches would just use recursive "if" test, the binary coding would only be used for visual inspection and we can just Comparatively, off-by-one errors are much harder to notice, debug and track down and I'd rather pay the small cost of breaking this abstraction to avoid off-by-ones. |
Generalized indices are intended for all kinds of SSZ objects, the great majority of which will be highly unbalanced (eg. think of the So for example, in the data structure below (eg. the SSZ structure
The indices would be: R=1, A=2, B=3, C=6, D=7. Nothing would correspond to 4 or 5. The reason I don't use the sequential addressing as in the image of the chart in that wiki article is that it's a desideratum for a description of a given Merkle path to not depend on anything outside that path. You especially don't want a description of the path to depend on the size of a variable length list somewhere else in the data structure. |
This was discussed on today's call (cc @protolambda and @djrtwo)—sticking with 1-based indexing for generalised indices lead to cleaner implementations. |
The current specs for generalized merkle tree uses 1-based indexing (https://github.com/ethereum/eth2.0-specs/blob/2787fea5feb8d5977ebee7c578c5d835cff6dc21/specs/light_client/merkle_proofs.md#generalized-merkle-tree-index)
The differences between 0 and 1 based indexing are the following:
Convention
Using 0-based indexing would be using the common convention of most programming languages (save Fortran, Matlab and Julia).
Errors
It is well known that mixing 0-based and 1-based indexing tends to create off-by-one errors, we already had several such spec bugs in the past. And using 1-based indexing is introducing new risks.
Memory saving
I expect clients to allocate binary trees with power-of-2 sizes for efficiency reasons.
If we have N nodes in the merkle tree where N is a power of 2 we will waste N-1 node space.
Concrete example: 1 byte can store 256 values in the range [0, 255]
Assuming we have values in range [1, 256] instead like the current proposition we would need 2 bytes of storage and will waste the [257-511] range which represent 255 values.
Alignment issues
Assuming we store merkle proofs in arrays of uint32 at a low-level, they would need to be aligned to 32-bit boundaries on many architecture that would benefit the most from light clients (ARM, MIPS, ...).
Furthermore Merkle proofs would probably be excellent candidates to be accelerated by SIMD/vector hardware which requires also requires alignment.
This off-by-one will cause unaligned loads/store that implementers will have to work around, probably via padding further increasing memory waste or via temporaries.
Potential counter-argument: extra computation
Some might be worried that 0-based is slower than 1-based indexing
where we can just use
2*i
and2*i+1
to find children instead of2*i+1
and2*i+2
and
i/2
to find parents instead of(i-1)/2
.addressing mode if scale is a power of 2. Execution time is the same. (This is called SIB addressing in x86 and scaled register in ARM).
the previous memory fetch, the CPU can precompute the next location to fetch.
as there is no data dependencies.
The text was updated successfully, but these errors were encountered: