import std/sequtils import std/sugar import std/random import pkg/questionable/results import pkg/constantine/math/arithmetic import pkg/constantine/math/io/io_fields import pkg/poseidon2/types import pkg/poseidon2/io import pkg/poseidon2 import pkg/chronos import pkg/asynctest import pkg/codex/stores/cachestore import pkg/codex/chunker import pkg/codex/stores import pkg/codex/blocktype as bt import pkg/codex/contracts/requests import pkg/codex/contracts import pkg/codex/merkletree import pkg/codex/stores/cachestore import pkg/codex/proof/datasampler import pkg/codex/proof/misc import pkg/codex/proof/types import ../helpers import ../examples import testdatasampler_expected let bytesPerBlock = 64 * 1024 challenge: FieldElement = toF(12345) datasetRootHash: FieldElement = toF(6789) asyncchecksuite "Test proof datasampler - components": let numberOfSlotBlocks = 16 slot = Slot( request: StorageRequest( ask: StorageAsk( slots: 10, slotSize: u256(bytesPerBlock * numberOfSlotBlocks), ), content: StorageContent( cid: $Cid.example ) ), slotIndex: u256(3) ) test "Number of cells is a power of two": # This is to check that the data used for testing is sane. proc isPow2(value: int): bool = let log2 = ceilingLog2(value) return (1 shl log2) == value let numberOfCells = getNumberOfCellsInSlot(slot).int check: isPow2(numberOfCells) test "Extract low bits": proc extract(value: uint64, nBits: int): uint64 = let big = toF(value).toBig() return extractLowBits(big, nBits) check: extract(0x88, 4) == 0x8.uint64 extract(0x88, 7) == 0x8.uint64 extract(0x9A, 5) == 0x1A.uint64 extract(0x9A, 7) == 0x1A.uint64 extract(0x1248, 10) == 0x248.uint64 extract(0x1248, 12) == 0x248.uint64 extract(0x1248306A560C9AC0.uint64, 10) == 0x2C0.uint64 extract(0x1248306A560C9AC0.uint64, 12) == 0xAC0.uint64 extract(0x1248306A560C9AC0.uint64, 50) == 0x306A560C9AC0.uint64 extract(0x1248306A560C9AC0.uint64, 52) == 0x8306A560C9AC0.uint64 test "Should calculate total number of cells in Slot": let slotSizeInBytes = (slot.request.ask.slotSize).truncate(uint64) expectedNumberOfCells = slotSizeInBytes div CellSize check: expectedNumberOfCells == 512 expectedNumberOfCells == getNumberOfCellsInSlot(slot) asyncchecksuite "Test proof datasampler - main": let # The number of slot blocks and number of slots, combined with # the bytes per block, make it so that there are exactly 256 cells # in the dataset. numberOfSlotBlocks = 4 totalNumberOfSlots = 2 datasetSlotIndex = 1 localStore = CacheStore.new() datasetToSlotProof = MerkleProof.example var manifest: Manifest manifestBlock: bt.Block slot: Slot datasetBlocks: seq[bt.Block] slotPoseidonTree: MerkleTree dataSampler: DataSampler proc createDatasetBlocks(): Future[void] {.async.} = let numberOfCellsNeeded = (numberOfSlotBlocks * totalNumberOfSlots * bytesPerBlock).uint64 div CellSize var data: seq[byte] = @[] # This generates a number of blocks that have different data, such that # Each cell in each block is unique, but nothing is random. for i in 0 ..< numberOfCellsNeeded: data = data & (i.byte).repeat(CellSize) let chunker = MockChunker.new( dataset = data, chunkSize = bytesPerBlock) while true: let chunk = await chunker.getBytes() if chunk.len <= 0: break let b = bt.Block.new(chunk).tryGet() datasetBlocks.add(b) discard await localStore.putBlock(b) proc createManifest(): Future[void] {.async.} = let cids = datasetBlocks.mapIt(it.cid) tree = MerkleTree.init(cids).tryGet() treeCid = tree.rootCid().tryGet() for index, cid in cids: let proof = tree.getProof(index).tryget() discard await localStore.putBlockCidAndProof(treeCid, index, cid, proof) manifest = Manifest.new( treeCid = treeCid, blockSize = bytesPerBlock.NBytes, datasetSize = (bytesPerBlock * numberOfSlotBlocks * totalNumberOfSlots).NBytes) manifestBlock = bt.Block.new(manifest.encode().tryGet(), codec = DagPBCodec).tryGet() proc createSlot(): void = slot = Slot( request: StorageRequest( ask: StorageAsk( slotSize: u256(bytesPerBlock * numberOfSlotBlocks) ), content: StorageContent( cid: $manifestBlock.cid ), ), slotIndex: u256(datasetSlotIndex) ) proc createSlotPoseidonTree(): void = let slotSize = slot.request.ask.slotSize.truncate(uint64) blocksInSlot = slotSize div bytesPerBlock.uint64 datasetSlotIndex = slot.slotIndex.truncate(uint64) datasetBlockIndexFirst = datasetSlotIndex * blocksInSlot datasetBlockIndexLast = datasetBlockIndexFirst + numberOfSlotBlocks.uint64 slotBlocks = datasetBlocks[datasetBlockIndexFirst ..< datasetBlockIndexLast] slotBlockCids = slotBlocks.mapIt(it.cid) slotPoseidonTree = MerkleTree.init(slotBlockCids).tryGet() proc createDataSampler(): Future[void] {.async.} = dataSampler = (await DataSampler.new( slot, localStore, datasetRootHash, slotPoseidonTree, datasetToSlotProof )).tryGet() setup: await createDatasetBlocks() await createManifest() createSlot() discard await localStore.putBlock(manifestBlock) createSlotPoseidonTree() await createDataSampler() test "Number of cells is a power of two": # This is to check that the data used for testing is sane. proc isPow2(value: int): bool = let log2 = ceilingLog2(value) return (1 shl log2) == value let numberOfCells = getNumberOfCellsInSlot(slot).int check: isPow2(numberOfCells) let knownIndices = @[74.uint64, 41.uint64, 51.uint64] test "Can find single slot-cell index": proc slotCellIndex(i: int): uint64 = let counter: FieldElement = toF(i) return dataSampler.findSlotCellIndex(challenge, counter) proc getExpectedIndex(i: int): uint64 = let numberOfCellsInSlot = (bytesPerBlock * numberOfSlotBlocks) div CellSize.int slotRootHash = toF(1234) # TODO - replace with slotPoseidonTree.root when it is a poseidon tree. hash = Sponge.digest(@[slotRootHash, challenge, toF(i)], rate = 2) return extractLowBits(hash.toBig(), ceilingLog2(numberOfCellsInSlot)) check: slotCellIndex(1) == getExpectedIndex(1) slotCellIndex(1) == knownIndices[0] slotCellIndex(2) == getExpectedIndex(2) slotCellIndex(2) == knownIndices[1] slotCellIndex(3) == getExpectedIndex(3) slotCellIndex(3) == knownIndices[2] test "Can find sequence of slot-cell indices": proc slotCellIndices(n: int): seq[uint64] = dataSampler.findSlotCellIndices(challenge, n) proc getExpectedIndices(n: int): seq[uint64] = return collect(newSeq, (for i in 1..n: dataSampler.findSlotCellIndex(challenge, toF(i)))) check: slotCellIndices(3) == getExpectedIndices(3) slotCellIndices(3) == knownIndices let bytes = newSeqWith(bytesPerBlock, rand(uint8)) blk = bt.Block.new(bytes).tryGet() cell0Bytes = bytes[0.. " & $expected & ")": let slotCellIndex = input.uint64 slotBlockIndex = dataSampler.getSlotBlockIndexForSlotCellIndex(slotCellIndex) check: slotBlockIndex == expected.uint64 for (input, expected) in [(10, 10), (31, 31), (32, 0), (63, 31), (64, 0)]: test "Can get blockCellIndex from slotCellIndex (" & $input & " -> " & $expected & ")": let slotCellIndex = input.uint64 blockCellIndex = dataSampler.getBlockCellIndexForSlotCellIndex(slotCellIndex) check: blockCellIndex == expected.uint64