github.com/lazyledger/lazyledger-core@v0.35.0-dev.0.20210613111200-4c651f053571/docs/lazy-adr/adr-002-ipld-da-sampling.md (about) 1 # ADR 002: Sampling erasure coded Block chunks 2 3 ## Changelog 4 5 - 26-2-2021: Created 6 7 ## Context 8 9 In Tendermint's block gossiping each peer gossips random parts of block data to peers. 10 For LazyLedger, we need nodes (from light-clients to validators) to be able to sample row-/column-chunks of the erasure coded 11 block (aka the extended data square) from the network. 12 This is necessary for Data Availability proofs. 13 14  15 16 A high-level, implementation-independent formalization of above mentioned sampling and Data Availability proofs can be found in: 17 [_Fraud and Data Availability Proofs: Detecting Invalid Blocks in Light Clients_](https://fc21.ifca.ai/papers/83.pdf). 18 19 For the time being, besides the academic paper, no other formalization or specification of the protocol exists. 20 Currently, the LazyLedger specification itself only describes the [erasure coding](https://github.com/lazyledger/lazyledger-specs/blob/master/specs/data_structures.md#erasure-coding) 21 and how to construct the extended data square from the block data. 22 23 This ADR: 24 - describes the high-level requirements 25 - defines the API that and how it can be used by different components of LazyLedger (block gossiping, block sync, DA proofs) 26 - documents decision on how to implement this. 27 28 29 The core data structures and the erasure coding of the block are already implemented in lazyledger-core ([#17], [#19], [#83]). 30 While there are no ADRs for these changes, we can refer to the LazyLedger specification in this case. 31 For this aspect, the existing implementation and specification should already be on par for the most part. 32 The exact arrangement of the data as described in this [rationale document](https://github.com/lazyledger/lazyledger-specs/blob/master/rationale/message_block_layout.md) 33 in the specification can happen at app-side of the ABCI boundary. 34 The latter was implemented in [lazyledger/lazyledger-app#21](https://github.com/lazyledger/lazyledger-app/pull/21) 35 leveraging a new ABCI method, added in [#110](https://github.com/lazyledger/lazyledger-core/pull/110). 36 This new method is a sub-set of the proposed ABCI changes aka [ABCI++](https://github.com/tendermint/spec/pull/254). 37 38 Mustafa Al-Bassam (@musalbas) implemented a [prototype](https://github.com/lazyledger/lazyledger-prototype) 39 whose main purpose is to realistically analyse the protocol. 40 Although the prototype does not make any network requests and only operates locally, it can partly serve as a reference implementation. 41 It uses the [rsmt2d] library. 42 43 The implementation will essentially use IPFS' APIs. For reading (and writing) chunks it 44 will use the IPLD [`DagService`](https://github.com/ipfs/go-ipld-format/blob/d2e09424ddee0d7e696d01143318d32d0fb1ae63/merkledag.go#L54), 45 more precisely the [`NodeGetter`](https://github.com/ipfs/go-ipld-format/blob/d2e09424ddee0d7e696d01143318d32d0fb1ae63/merkledag.go#L18-L27) 46 and [`NodeAdder`](https://github.com/ipfs/go-ipld-format/blob/d2e09424ddee0d7e696d01143318d32d0fb1ae63/merkledag.go#L29-L39). 47 As an optimization, we can also use a [`Batch`](https://github.com/ipfs/go-ipld-format/blob/d2e09424ddee0d7e696d01143318d32d0fb1ae63/batch.go#L29) 48 to batch adding and removing nodes. 49 This will be achieved by passing around a [CoreAPI](https://github.com/ipfs/interface-go-ipfs-core/blob/b935dfe5375eac7ea3c65b14b3f9a0242861d0b3/coreapi.go#L15) 50 object, which derive from the IPFS node which is created along a with a tendermint node (see [#152]). 51 This code snippet does exactly that (see the [go-ipfs documentation] for more examples): 52 ```go 53 // This constructs an IPFS node instance 54 node, _ := core.NewNode(ctx, nodeOptions) 55 // This attaches the Core API to the constructed node 56 coreApi := coreapi.NewCoreAPI(node) 57 ``` 58 59 The above mentioned IPLD methods operate on so called [ipld.Nodes]. 60 When computing the data root, we can pass in a [`NodeVisitor`](https://github.com/lazyledger/nmt/blob/b22170d6f23796a186c07e87e4ef9856282ffd1a/nmt.go#L22) 61 into the Namespaced Merkle Tree library to create these (each inner- and leaf-node in the tree becomes an ipld node). 62 As a peer that requests such an IPLD node, the LazyLedger IPLD plugin provides the [function](https://github.com/lazyledger/lazyledger-core/blob/ceb881a177b6a4a7e456c7c4ab1dd0eb2b263066/p2p/ipld/plugin/nodes/nodes.go#L175) 63 `NmtNodeParser` to transform the retrieved raw data back into an `ipld.Node`. 64 65 A more high-level description on the changes required to rip out the current block gossiping routine, 66 including changes to block storage-, RPC-layer, and potential changes to reactors is either handled in [LAZY ADR 001](./adr-001-block-propagation.md), 67 and/or in a few smaller, separate followup ADRs. 68 69 ## Alternative Approaches 70 71 Instead of creating a full IPFS node object and passing it around as explained above 72 - use API (http) 73 - use ipld-light 74 - use alternative client 75 76 Also, for better performance 77 - use [graph-sync], [IPLD selectors], e.g. via [ipld-prime] 78 79 Also, there is the idea, that nodes only receive the [Header] with the data root only 80 and, in an additional step/request, download the DA header using the library, too. 81 While this feature is not considered here, and we assume each node that uses this library has the DA header, this assumption 82 is likely to change when flesh out other parts of the system in more detail. 83 Note that this also means that light clients would still need to validate that the data root and merkelizing the DA header yield the same result. 84 85 ## Decision 86 87 > This section records the decision that was made. 88 > It is best to record as much info as possible from the discussion that happened. This aids in not having to go back to the Pull Request to get the needed information. 89 90 > - TODO: briefly summarize github, discord, and slack discussions (?) 91 > - also mention Mustafa's prototype and compare both apis briefly (RequestSamples, RespondSamples, ProcessSamplesResponse) 92 > - mention [ipld experiments] 93 94 95 96 ## Detailed Design 97 98 Add a package to the library that provides the following features: 99 1. sample a given number of random row/col indices of extended data square given a DA header and indicate if successful or timeout/other error occurred 100 2. store the block in the network by adding it to the peer's local Merkle-DAG whose content is discoverable via a DHT 101 3. store the sampled chunks in the network 102 4. reconstruct the whole block from a given DA header 103 5. get all messages of a particular namespace ID. 104 105 We mention 5. here mostly for completeness. Its details will be described / implemented in a separate ADR / PR. 106 107 Apart from the above mentioned features, we informally collect additional requirements: 108 - where randomness is needed, the randomness source should be configurable 109 - all replies by the network should be verified if this is not sufficiently covered by the used libraries already (IPFS) 110 - where possible, the requests to the network should happen in parallel (without DoSing the proposer for instance). 111 112 This library should be implemented as two new packages: 113 114 First, a sub-package should be added to the layzledger-core [p2p] package 115 which does not know anything about the core data structures (Block, DA header etc). 116 It handles the actual network requests to the IPFS network and operates on IPFS/IPLD objects 117 directly and hence should live under [p2p/ipld]. 118 To a some extent this part of the stack already exists. 119 120 Second, a high-level API that can "live" closer to the actual types, e.g., in a sub-package in [lazyledger-core/types] 121 or in a new sub-package `da`. 122 123 We first describe the high-level library here and describe functions in 124 more detail inline with their godoc comments below. 125 126 ### API that operates on lazyledger-core types 127 128 As mentioned above this part of the library has knowledge of the core types (and hence depends on them). 129 It does not deal with IPFS internals. 130 131 ```go 132 // ValidateAvailability implements the protocol described in https://fc21.ifca.ai/papers/83.pdf. 133 // Specifically all steps of the protocol described in section 134 // _5.2 Random Sampling and Network Block Recovery_ are carried out. 135 // 136 // In more detail it will first create numSamples random unique coordinates. 137 // Then, it will ask the network for the leaf data corresponding to these coordinates. 138 // Additionally to the number of requests, the caller can pass in a callback, 139 // which will be called on for each retrieved leaf with a verified Merkle proof. 140 // 141 // Among other use-cases, the callback can be useful to monitoring (progress), or, 142 // to process the leaf data the moment it was validated. 143 // The context can be used to provide a timeout. 144 // TODO: Should there be a constant = lower bound for #samples 145 func ValidateAvailability( 146 ctx contex.Context, 147 dah *DataAvailabilityHeader, 148 numSamples int, 149 onLeafValidity func(namespace.PrefixedData8), 150 ) error { /* ... */} 151 152 // RetrieveBlockData can be used to recover the block Data. 153 // It will carry out a similar protocol as described for ValidateAvailability. 154 // The key difference is that it will sample enough chunks until it can recover the 155 // full extended data square, including original data (e.g. by using rsmt2d.RepairExtendedDataSquare). 156 func RetrieveBlockData( 157 ctx contex.Context, 158 dah *DataAvailabilityHeader, 159 api coreiface.CoreAPI, 160 codec rsmt2d.Codec, 161 ) (types.Data, error) {/* ... */} 162 163 // PutBlock operates directly on the Block. 164 // It first computes the erasure coding, aka the extended data square. 165 // Row by row ir calls a lower level library which handles adding the 166 // the row to the Merkle Dag, in our case a Namespaced Merkle Tree. 167 // Note, that this method could also fill the DA header. 168 // The data will be pinned by default. 169 func (b *Block) PutBlock(ctx contex.Context, nodeAdder ipld.NodeAdder) error 170 ``` 171 172 We now describe the lower-level library that will be used by above methods. 173 Again we provide more details inline in the godoc comments directly. 174 175 `PutBlock` is a method on `Block` as the erasure coding can then be cached, e.g. in a private field 176 in the block. 177 178 ### Changes to the lower level API closer to IPFS (p2p/ipld) 179 180 ```go 181 // GetLeafData takes in a Namespaced Merkle tree root transformed into a Cid 182 // and the leaf index to retrieve. 183 // Callers also need to pass in the total number of leaves of that tree. 184 // Internally, this will be translated to a IPLD path and corresponds to 185 // an ipfs dag get request, e.g. namespacedCID/0/1/0/0/1. 186 // The retrieved data should be pinned by default. 187 func GetLeafData( 188 ctx context.Context, 189 rootCid cid.Cid, 190 leafIndex uint32, 191 totalLeafs uint32, // this corresponds to the extended square width 192 api coreiface.CoreAPI, 193 ) ([]byte, error) 194 ``` 195 196 `GetLeafData` can be used by above `ValidateAvailability` and `RetrieveBlock` and 197 `PutLeaves` by `PutBlock`. 198 199 ### A Note on IPFS/IPLD 200 201 In IPFS all data is _content addressed_ which basically means the data is identified by its hash. 202 Particularly, in the LazyLedger case, the root CID identifies the Namespaced Merkle tree including all its contents (inner and leaf nodes). 203 This means that if a `GetLeafData` request succeeds, the retrieved leaf data is in fact the leaf data in the tree. 204 We do not need to additionally verify Merkle proofs per leaf as this will essentially be done via IPFS on each layer while 205 resolving and getting to the leaf data. 206 207 > TODO: validate this assumption and link to code that shows how this is done internally 208 209 ### Implementation plan 210 211 As fully integrating Data Available proofs into tendermint, is a rather larger change we break up the work into the 212 following packages (not mentioning the implementation work that was already done): 213 214 1. Flesh out the changes in the consensus messages ([lazyledger-specs#126], [lazyledger-specs#127]) 215 2. Flesh out the changes that would be necessary to replace the current block gossiping ([LAZY ADR 001](./adr-001-block-propagation.md)) 216 3. Add the possibility of storing and retrieving block data (samples or whole block) to lazyledger-core (this ADR and related PRs). 217 4. Integrate above API (3.) as an addition into lazyledger-core without directly replacing the tendermint counterparts (block gossip etc). 218 5. Rip out each component that will be redundant with above integration in one or even several smaller PRs: 219 - block gossiping (see LAZY ADR 001) 220 - modify block store (see LAZY ADR 001) 221 - make downloading full Blocks optional (flag/config) 222 - route some RPC requests to IPFS (see LAZY ADR 001) 223 224 225 ## Status 226 227 Proposed 228 229 ## Consequences 230 231 ### Positive 232 233 - simplicity & ease of implementation 234 - can re-use an existing networking and p2p stack (go-ipfs) 235 - potential support of large, cool, and helpful community 236 - high-level API definitions independent of the used stack 237 238 ### Negative 239 240 - latency 241 - being connected to the public IPFS network might be overkill if peers should in fact only care about a subset that participates in the LazyLedger protocol 242 - dependency on a large code-base with lots of features and options of which we only need a small subset of 243 244 ### Neutral 245 - two different p2p layers exist in lazyledger-core 246 247 ## References 248 249 - https://github.com/lazyledger/lazyledger-core/issues/85 250 - https://github.com/lazyledger/lazyledger-core/issues/167 251 252 - https://docs.ipld.io/#nodes 253 - https://arxiv.org/abs/1809.09044 254 - https://fc21.ifca.ai/papers/83.pdf 255 - https://github.com/tendermint/spec/pull/254 256 257 258 [#17]: https://github.com/lazyledger/lazyledger-core/pull/17 259 [#19]: https://github.com/lazyledger/lazyledger-core/pull/19 260 [#83]: https://github.com/lazyledger/lazyledger-core/pull/83 261 262 [#152]: https://github.com/lazyledger/lazyledger-core/pull/152 263 264 [lazyledger-specs#126]: https://github.com/lazyledger/lazyledger-specs/issues/126 265 [lazyledger-specs#127]: https://github.com/lazyledger/lazyledger-specs/pulls/127 266 [Header]: https://github.com/lazyledger/lazyledger-specs/blob/master/specs/data_structures.md#header 267 268 [go-ipfs documentation]: https://github.com/ipfs/go-ipfs/tree/master/docs/examples/go-ipfs-as-a-library#use-go-ipfs-as-a-library-to-spawn-a-node-and-add-a-file 269 [ipld experiments]: https://github.com/lazyledger/ipld-plugin-experiments 270 [ipld.Nodes]: https://github.com/ipfs/go-ipld-format/blob/d2e09424ddee0d7e696d01143318d32d0fb1ae63/format.go#L22-L45 271 [graph-sync]: https://github.com/ipld/specs/blob/master/block-layer/graphsync/graphsync.md 272 [IPLD selectors]: https://github.com/ipld/specs/blob/master/selectors/selectors.md 273 [ipld-prime]: https://github.com/ipld/go-ipld-prime 274 275 [rsmt2d]: https://github.com/lazyledger/rsmt2d 276 277 278 [p2p]: https://github.com/lazyledger/lazyledger-core/tree/0eccfb24e2aa1bb9c4428e20dd7828c93f300e60/p2p 279 [p2p/ipld]: https://github.com/lazyledger/lazyledger-core/tree/0eccfb24e2aa1bb9c4428e20dd7828c93f300e60/p2p/ipld 280 [lazyledger-core/types]: https://github.com/lazyledger/lazyledger-core/tree/0eccfb24e2aa1bb9c4428e20dd7828c93f300e60/types