297 lines
9.4 KiB
Nim
Raw Normal View History

2023-11-30 10:21:04 +01:00
import std/sequtils
2023-12-05 15:25:38 +01:00
import std/math
2023-12-22 17:12:57 -06:00
import std/importutils
import std/sugar
2023-11-30 09:13:48 +01:00
import pkg/chronos
import pkg/asynctest
import pkg/questionable/results
import pkg/codex/blocktype as bt
2023-11-30 10:21:04 +01:00
import pkg/codex/rng
import pkg/codex/stores
import pkg/codex/chunker
import pkg/codex/merkletree
2023-12-05 16:48:15 +01:00
import pkg/codex/utils
2023-12-22 17:12:57 -06:00
import pkg/codex/utils/digest
import pkg/datastore
import pkg/poseidon2
import pkg/poseidon2/io
import constantine/math/io/io_fields
2023-11-30 09:13:48 +01:00
import ../helpers
import ../examples
2023-12-22 17:12:57 -06:00
import ../merkletree/helpers
2023-11-30 09:13:48 +01:00
2023-12-22 17:12:57 -06:00
import pkg/codex/indexingstrategy {.all.}
import pkg/codex/slots/slotbuilder {.all.}
2023-11-30 09:13:48 +01:00
2023-12-22 17:12:57 -06:00
suite "Slot builder":
2023-11-30 10:21:04 +01:00
let
2023-12-22 17:12:57 -06:00
blockSize = 1024
cellSize = 64
ecK = 3
ecM = 2
numSlots = ecK + ecM
numDatasetBlocks = 100
numBlockCells = blockSize div cellSize
numTotalBlocks = calcEcBlocksCount(numDatasetBlocks, ecK, ecM) # total number of blocks in the dataset after
# EC (should will match number of slots)
originalDatasetSize = numDatasetBlocks * blockSize # size of the dataset before EC
totalDatasetSize = numTotalBlocks * blockSize # size of the dataset after EC
numTotalSlotBlocks = nextPowerOfTwo(numTotalBlocks div numSlots)
blockPadBytes =
newSeq[byte](numBlockCells.nextPowerOfTwoPad * cellSize) # power of two padding for blocks
slotsPadLeafs =
newSeqWith((numTotalBlocks div numSlots).nextPowerOfTwoPad, Poseidon2Zero) # power of two padding for block roots
rootsPadLeafs =
newSeqWith(numSlots.nextPowerOfTwoPad, Poseidon2Zero)
2023-11-30 09:13:48 +01:00
2023-11-30 10:21:04 +01:00
var
datasetBlocks: seq[bt.Block]
2023-12-22 17:12:57 -06:00
localStore: BlockStore
manifest: Manifest
2023-11-30 10:21:04 +01:00
protectedManifest: Manifest
2023-12-05 16:48:15 +01:00
expectedEmptyCid: Cid
2023-11-30 10:21:04 +01:00
slotBuilder: SlotBuilder
2023-12-22 17:12:57 -06:00
chunker: Chunker
2023-11-30 10:21:04 +01:00
proc createBlocks(): Future[void] {.async.} =
while true:
let chunk = await chunker.getBytes()
if chunk.len <= 0:
break
let blk = bt.Block.new(chunk).tryGet()
datasetBlocks.add(blk)
discard await localStore.putBlock(blk)
proc createProtectedManifest(): Future[void] {.async.} =
let
cids = datasetBlocks.mapIt(it.cid)
2023-12-22 17:12:57 -06:00
datasetTree = CodexTree.init(cids[0..<numDatasetBlocks]).tryGet()
datasetTreeCid = datasetTree.rootCid().tryGet()
protectedTree = CodexTree.init(cids).tryGet()
protectedTreeCid = protectedTree.rootCid().tryGet()
for index, cid in cids[0..<numDatasetBlocks]:
let proof = datasetTree.getProof(index).tryget()
(await localStore.putCidAndProof(datasetTreeCid, index, cid, proof)).tryGet
2023-11-30 10:21:04 +01:00
for index, cid in cids:
2023-12-22 17:12:57 -06:00
let proof = protectedTree.getProof(index).tryget()
(await localStore.putCidAndProof(protectedTreeCid, index, cid, proof)).tryGet
manifest = Manifest.new(
treeCid = datasetTreeCid,
blockSize = blockSize.NBytes,
datasetSize = originalDatasetSize.NBytes)
2023-11-30 10:21:04 +01:00
protectedManifest = Manifest.new(
2023-12-22 17:12:57 -06:00
manifest = manifest,
treeCid = protectedTreeCid,
datasetSize = totalDatasetSize.NBytes,
ecK = ecK,
ecM = ecM)
2023-11-30 10:21:04 +01:00
2023-12-22 17:12:57 -06:00
let
manifestBlock = bt.Block.new(
manifest.encode().tryGet(),
codec = ManifestCodec).tryGet()
protectedManifestBlock = bt.Block.new(
protectedManifest.encode().tryGet(),
codec = ManifestCodec).tryGet()
(await localStore.putBlock(manifestBlock)).tryGet()
(await localStore.putBlock(protectedManifestBlock)).tryGet()
expectedEmptyCid = emptyCid(
protectedManifest.version,
protectedManifest.hcodec,
protectedManifest.codec).tryGet()
privateAccess(SlotBuilder) # enable access to private fields
2023-11-30 10:21:04 +01:00
setup:
2023-12-22 17:12:57 -06:00
let
repoDs = SQLiteDatastore.new(Memory).tryGet()
metaDs = SQLiteDatastore.new(Memory).tryGet()
localStore = RepoStore.new(repoDs, metaDs)
chunker = RandomChunker.new(Rng.instance(), size = totalDatasetSize, chunkSize = blockSize)
2023-11-30 10:21:04 +01:00
await createBlocks()
await createProtectedManifest()
2023-12-22 17:12:57 -06:00
teardown:
await localStore.close()
# Need to reset all objects because otherwise they get
# captured by the test runner closures, not good!
reset(datasetBlocks)
reset(localStore)
reset(manifest)
reset(protectedManifest)
reset(expectedEmptyCid)
reset(slotBuilder)
reset(chunker)
test "Can only create slotBuilder with protected manifest":
2023-12-22 17:12:57 -06:00
let
unprotectedManifest = Manifest.new(
treeCid = Cid.example,
blockSize = blockSize.NBytes,
datasetSize = originalDatasetSize.NBytes)
check:
2023-12-22 17:12:57 -06:00
SlotBuilder.new(localStore, unprotectedManifest, cellSize = cellSize)
.error.msg == "Can only create SlotBuilder using protected manifests."
test "Number of blocks must be devisable by number of slots":
2023-12-22 17:12:57 -06:00
let
mismatchManifest = Manifest.new(
manifest = Manifest.new(
treeCid = Cid.example,
blockSize = blockSize.NBytes,
datasetSize = originalDatasetSize.NBytes),
treeCid = Cid.example,
2023-12-22 17:12:57 -06:00
datasetSize = totalDatasetSize.NBytes,
ecK = ecK - 1,
ecM = ecM)
check:
2023-12-22 17:12:57 -06:00
SlotBuilder.new(localStore, mismatchManifest, cellSize = cellSize)
.error.msg == "Number of blocks must be divisable by number of slots."
2023-11-30 10:21:04 +01:00
test "Block size must be divisable by cell size":
2023-12-22 17:12:57 -06:00
let
mismatchManifest = Manifest.new(
manifest = Manifest.new(
treeCid = Cid.example,
blockSize = (blockSize + 1).NBytes,
datasetSize = (originalDatasetSize - 1).NBytes),
treeCid = Cid.example,
2023-12-22 17:12:57 -06:00
datasetSize = (totalDatasetSize - 1).NBytes,
ecK = ecK,
ecM = ecM)
check:
2023-12-22 17:12:57 -06:00
SlotBuilder.new(localStore, mismatchManifest, cellSize = cellSize)
.error.msg == "Block size must be divisable by cell size."
2023-12-22 17:12:57 -06:00
test "Should build correct slot builder":
slotBuilder = SlotBuilder.new(
localStore,
protectedManifest,
cellSize = cellSize).tryGet()
2023-12-05 15:25:38 +01:00
2023-12-22 17:12:57 -06:00
check:
slotBuilder.numBlockPadBytes == blockPadBytes.len
slotBuilder.numSlotsPadLeafs == slotsPadLeafs.len
slotBuilder.numRootsPadLeafs == rootsPadLeafs.len
test "Should build slot hashes for all slots":
let
steppedStrategy = SteppedIndexingStrategy.new(0, numTotalBlocks - 1, numSlots)
slotBuilder = SlotBuilder.new(
localStore,
protectedManifest,
cellSize = cellSize).tryGet()
for i in 0 ..< numSlots:
2023-12-05 16:20:15 +01:00
let
2023-12-22 17:12:57 -06:00
expectedBlock = steppedStrategy
.getIndicies(i)
.mapIt( datasetBlocks[it] )
expectedHashes: seq[Poseidon2Hash] = collect(newSeq):
for blk in expectedBlock:
SpongeMerkle.digest(blk.data & blockPadBytes, cellSize)
2023-12-05 15:25:38 +01:00
2023-12-22 17:12:57 -06:00
cellHashes = (await slotBuilder.getCellHashes(i)).tryGet()
2023-12-05 15:25:38 +01:00
2023-12-05 16:20:15 +01:00
check:
2023-12-22 17:12:57 -06:00
expectedHashes == cellHashes
2023-12-05 15:25:38 +01:00
2023-12-22 17:12:57 -06:00
test "Should build slot trees for all slots":
let
steppedStrategy = SteppedIndexingStrategy.new(0, numTotalBlocks - 1, numSlots)
slotBuilder = SlotBuilder.new(
localStore,
protectedManifest,
cellSize = cellSize).tryGet()
for i in 0 ..< numSlots:
2023-12-05 16:48:15 +01:00
let
2023-12-22 17:12:57 -06:00
expectedBlock = steppedStrategy
.getIndicies(i)
.mapIt( datasetBlocks[it] )
expectedHashes: seq[Poseidon2Hash] = collect(newSeq):
for blk in expectedBlock:
SpongeMerkle.digest(blk.data & blockPadBytes, cellSize)
expectedRoot = Merkle.digest(expectedHashes & slotsPadLeafs)
2023-12-05 16:48:15 +01:00
2023-12-22 17:12:57 -06:00
slotTree = (await slotBuilder.buildSlotTree(i)).tryGet()
2023-12-05 16:48:15 +01:00
check:
2023-12-22 17:12:57 -06:00
expectedRoot == slotTree.root().tryGet()
2023-12-05 16:48:15 +01:00
2023-12-22 17:12:57 -06:00
test "Should persist trees for all slots":
let
slotBuilder = SlotBuilder.new(
localStore,
protectedManifest,
cellSize = cellSize).tryGet()
for i in 0 ..< numSlots:
let
slotTree = (await slotBuilder.buildSlotTree(i)).tryGet()
slotRoot = (await slotBuilder.buildSlot(i)).tryGet()
slotCid = slotRoot.toSlotCid().tryGet()
for cellIndex in 0..<numTotalSlotBlocks:
let
(cellCid, proof) = (await localStore.getCidAndProof(slotCid, cellIndex)).tryGet()
verifiableProof = proof.toVerifiableProof().tryGet()
posProof = slotTree.getProof(cellIndex).tryGet
2023-12-05 16:48:15 +01:00
check:
2023-12-22 17:12:57 -06:00
verifiableProof.index == posProof.index
verifiableProof.nleaves == posProof.nleaves
verifiableProof.path == posProof.path
2023-12-05 16:48:15 +01:00
2023-12-22 17:12:57 -06:00
test "Should build correct verification root":
2023-12-05 16:20:15 +01:00
let
2023-12-22 17:12:57 -06:00
steppedStrategy = SteppedIndexingStrategy.new(0, numTotalBlocks - 1, numSlots)
slotBuilder = SlotBuilder.new(
localStore,
protectedManifest,
cellSize = cellSize).tryGet()
2023-12-05 15:25:38 +01:00
2023-12-22 17:12:57 -06:00
slotsHashes = collect(newSeq):
for i in 0 ..< numSlots:
let
expectedBlocks = steppedStrategy
.getIndicies(i)
.mapIt( datasetBlocks[it] )
2023-12-05 15:25:38 +01:00
2023-12-22 17:12:57 -06:00
slotHashes: seq[Poseidon2Hash] = collect(newSeq):
for blk in expectedBlocks:
SpongeMerkle.digest(blk.data & blockPadBytes, cellSize)
2023-12-05 15:25:38 +01:00
2023-12-22 17:12:57 -06:00
Merkle.digest(slotHashes & slotsPadLeafs)
2023-12-05 15:25:38 +01:00
2023-12-22 17:12:57 -06:00
expectedRoot = Merkle.digest(slotsHashes & rootsPadLeafs)
manifest = (await slotBuilder.buildSlots()).tryGet()
mhash = manifest.verificationRoot.mhash.tryGet()
mhashBytes = mhash.digestBytes
rootHash = Poseidon2Hash.fromBytes(mhashBytes.toArray32).toResult.tryGet()
2023-12-04 09:23:18 +01:00
2023-12-22 17:12:57 -06:00
check:
expectedRoot == rootHash