## Nim-Codex ## Copyright (c) 2022 Status Research & Development GmbH ## Licensed under either of ## * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE)) ## * MIT license ([LICENSE-MIT](LICENSE-MIT)) ## at your option. ## This file may not be copied, modified, or distributed except according to ## those terms. import pkg/upraises push: {.upraises: [].} import std/sequtils import pkg/chronos import pkg/chronicles import ../manifest import ../stores import ../blocktype as bt import ./backend export backend logScope: topics = "codex erasure" type ## Encode a manifest into one that is erasure protected. ## ## The new manifest has K `blocks` that are encoded into ## additional M `parity` blocks. The resulting dataset ## is padded with empty blocks if it doesn't have a square ## shape. ## ## NOTE: The padding blocks could be excluded ## from transmission, but they aren't for now. ## ## The resulting dataset is logically divided into rows ## where a row is made up of B blocks. There are then, ## K + M = N rows in total, each of length B blocks. Rows ## are assumed to be of the same number of (B) blocks. ## ## The encoding is systematic and the rows can be ## read sequentially by any node without decoding. ## ## Decoding is possible with any K rows or partial K ## columns (with up to M blocks missing per column), ## or any combination there of. ## EncoderProvider* = proc(size, blocks, parity: int): EncoderBackend {.raises: [Defect], noSideEffect.} DecoderProvider* = proc(size, blocks, parity: int): DecoderBackend {.raises: [Defect], noSideEffect.} Erasure* = ref object encoderProvider*: EncoderProvider decoderProvider*: DecoderProvider store*: BlockStore proc encode*( self: Erasure, manifest: Manifest, blocks: int, parity: int ): Future[?!Manifest] {.async.} = ## Encode a manifest into one that is erasure protected. ## ## `manifest` - the original manifest to be encoded ## `blocks` - the number of blocks to be encoded - K ## `parity` - the number of parity blocks to generate - M ## logScope: original_cid = manifest.cid.get() original_len = manifest.len blocks = blocks parity = parity trace "Erasure coding manifest", blocks, parity without var encoded =? Manifest.new(manifest, blocks, parity), error: trace "Unable to create manifest", msg = error.msg return error.failure logScope: steps = encoded.steps rounded_blocks = encoded.rounded new_manifest = encoded.len var encoder = self.encoderProvider(manifest.blockSize.int, blocks, parity) try: for i in 0..= encoded.ecK) or (idxPendingBlocks.len == 0): break let done = await one(idxPendingBlocks) idx = pendingBlocks.find(done) idxPendingBlocks.del(idxPendingBlocks.find(done)) without blk =? (await done), error: trace "Failed retrieving block", error = error.msg continue if idx >= encoded.ecK: trace "Retrieved parity block", cid = blk.cid, idx shallowCopy(parityData[idx - encoded.ecK], if blk.isEmpty: emptyBlock else: blk.data) else: trace "Retrieved data block", cid = blk.cid, idx shallowCopy(data[idx], if blk.isEmpty: emptyBlock else: blk.data) resolved.inc let dataPieces = data.filterIt( it.len > 0 ).len parityPieces = parityData.filterIt( it.len > 0 ).len if dataPieces >= encoded.ecK: trace "Retrieved all the required data blocks", data = dataPieces, parity = parityPieces continue trace "Erasure decoding data", data = dataPieces, parity = parityPieces if ( let err = decoder.decode(data, parityData, recovered); err.isErr): trace "Unable to decode manifest!", err = $err.error return failure($err.error) for i in 0..