Initially I read specification incorrectly and there was growing confusion between what I though I understood and what I was implementing.
Eventually we talked with @dariolina and we were able to understand each other: I understood what specification was saying and she understood what I have actually implemented and why.
TL;DR: I don’t think the way specification is written is possible to audit correctly, but I have re-designed it slightly in such a way that is possible to efficiently construct and audit and will describe it below (should also be easier to visualize and implement).
The reason audit is not possible to do efficiently is that each s-bucket has probabilistic size and we can’t just do quick lookup by offset, we’d have to store some kind of mapping that’ll tell us where each s-bucket starts. This also hurts data retrievability.
Proposed sector construction
As an input for sector we have a set of records extracted from pieces using verifiable sector construction.
Each record contains 2^15
32-bytes chunks.
Essentially we have a grid, where 1299
records are rows and 2^15
chunks are columns:
record_0_chunk_0 | record_0_chunk_1 | … | record_0_chunk_(2^15-1) |
record_1_chunk_0 | record_1_chunk_1 | … | record_1_chunk_(2^15-1) |
… | … | … | … |
record_1299_chunk_0 | record_1299_chunk_1 | … | record_1299_chunk_(2^15-1) |
Then we do following transformation for each record individually:
- apply erasure coding to get
2^16
chunks out of each record - generate PoSpace table
- allocate a bitfield with
2^16
bits (one for each erasure coded chunk) - treat each chunk index as challenge index and query PoSpace table
- keep first 2^15 chunks that have PoSpace proofs and
xor
them with PoSpace quality (encoding) - in case there isn’t enough proofs to collect
2^15
chunks (from empiric tests seems to happen for ~0.8% of records) append chunks to those collected in previous step using following selection algorithm:- out of
2^16
erasure coded chunks take as many chunks without PoSpace proofs as necessary to get to2^15
chunks total
- out of
- assemble chunks back into “record” (same size, but not real records anymore, most chunks are encoded)
As the result we have two things:
-
2^15
bytes “record” with most chunks encoded -
2^16
bits bitfield that allows us to understand what above “record” actually contains and how to decode it afterwards
Now that we have done encoding, we essentially have the same set of “records” as before arranged the same exact way: “records” are rows and chunks are columns. Bitfields form identical table, but columns are 1 bit in size rather than 32 bytes.
Each s-bucket in this case is chunks and bitfields at the same column.
When auditing each s-bucket has exactly 1300 32-byte chunks, so it is easy to read and bitfields explain which out of those chunks are eligible for solution creation (correspond to encoded chunk) and which are not.
Similarly for data retrieval we take one chunk and one bitfield bit at the same offset from each s-bucket and we know which chunks need to be decoded and how to arrange chunks such that they can be erasure-decoded.
The mechanism overall seems to be similar in nature as what is described in the paper, I’m wondering if there is something I’m missing that would cause issues. If nothing problematic is found, we should update the specification accordingly (I have actually updated it partially due to mentioned misunderstanding).
For now continuing with implementation as described above.