diff --git a/Blockchain/Sources/Blockchain/Config/ProtocolConfig+Preset.swift b/Blockchain/Sources/Blockchain/Config/ProtocolConfig+Preset.swift index 5168f32f..d0769b09 100644 --- a/Blockchain/Sources/Blockchain/Config/ProtocolConfig+Preset.swift +++ b/Blockchain/Sources/Blockchain/Config/ProtocolConfig+Preset.swift @@ -11,10 +11,10 @@ extension Ref where T == ProtocolConfig { preimagePurgePeriod: 28800, epochLength: 6, auditBiasFactor: 2, - coreAccumulationGas: Gas(100_000), + workReportAccumulationGas: Gas(100_000), workPackageAuthorizerGas: Gas(1_000_000), workPackageRefineGas: Gas(500_000_000), - totalAccumulationGas: Gas(341_000_000), + totalAccumulationGas: Gas(35_000_000), recentHistorySize: 8, maxWorkItems: 4, maxDepsInWorkReport: 8, @@ -52,10 +52,10 @@ extension Ref where T == ProtocolConfig { preimagePurgePeriod: 28800, epochLength: 12, auditBiasFactor: 2, - coreAccumulationGas: Gas(100_000), + workReportAccumulationGas: Gas(100_000), workPackageAuthorizerGas: Gas(1_000_000), workPackageRefineGas: Gas(500_000_000), - totalAccumulationGas: Gas(341_000_000), + totalAccumulationGas: Gas(35_000_000), recentHistorySize: 8, maxWorkItems: 4, maxDepsInWorkReport: 8, @@ -92,10 +92,10 @@ extension Ref where T == ProtocolConfig { preimagePurgePeriod: 28800, epochLength: 12, auditBiasFactor: 2, - coreAccumulationGas: Gas(100_000), + workReportAccumulationGas: Gas(100_000), workPackageAuthorizerGas: Gas(1_000_000), workPackageRefineGas: Gas(500_000_000), - totalAccumulationGas: Gas(341_000_000), + totalAccumulationGas: Gas(35_000_000), recentHistorySize: 8, maxWorkItems: 4, maxDepsInWorkReport: 8, @@ -132,10 +132,10 @@ extension Ref where T == ProtocolConfig { preimagePurgePeriod: 28800, epochLength: 600, auditBiasFactor: 2, - coreAccumulationGas: Gas(100_000), + workReportAccumulationGas: Gas(100_000), workPackageAuthorizerGas: Gas(1_000_000), workPackageRefineGas: Gas(500_000_000), - totalAccumulationGas: Gas(341_000_000), + totalAccumulationGas: Gas(35_000_000), recentHistorySize: 8, maxWorkItems: 4, maxDepsInWorkReport: 8, diff --git a/Blockchain/Sources/Blockchain/Config/ProtocolConfig.swift b/Blockchain/Sources/Blockchain/Config/ProtocolConfig.swift index 3f8f8a34..a390b83e 100644 --- a/Blockchain/Sources/Blockchain/Config/ProtocolConfig.swift +++ b/Blockchain/Sources/Blockchain/Config/ProtocolConfig.swift @@ -1,119 +1,119 @@ import PolkaVM import Utils -// constants defined in the graypaper +/// constants defined in the graypaper public struct ProtocolConfig: Sendable, Codable, Equatable { - // A = 8: The period, in seconds, between audit tranches. + /// A = 8: The period, in seconds, between audit tranches. public var auditTranchePeriod: Int - // BI = 10: The additional minimum balance required per item of elective service state. + /// BI = 10: The additional minimum balance required per item of elective service state. public var additionalMinBalancePerStateItem: Int - // BL = 1: The additional minimum balance required per octet of elective service state. + /// BL = 1: The additional minimum balance required per octet of elective service state. public var additionalMinBalancePerStateByte: Int - // BS = 100: The basic minimum balance which all services require. + /// BS = 100: The basic minimum balance which all services require. public var serviceMinBalance: Int - // C = 341: The total number of cores. + /// C = 341: The total number of cores. public var totalNumberOfCores: Int - // D = 28, 800: The period in timeslots after which an unreferenced preimage may be expunged. + /// D = 28, 800: The period in timeslots after which an unreferenced preimage may be expunged. public var preimagePurgePeriod: Int - // E = 600: The length of an epoch in timeslots. + /// E = 600: The length of an epoch in timeslots. public var epochLength: Int - // F = 2: The audit bias factor, the expected number of additional validators who will audit a work-report in the - // following tranche for each no-show in the previous. + /// F = 2: The audit bias factor, the expected number of additional validators who will audit a work-report in the + /// following tranche for each no-show in the previous. public var auditBiasFactor: Int - // GA: The total gas allocated to a core for Accumulation. - public var coreAccumulationGas: Gas + /// GA: The gas allocated to invoke a work-report's Accumulation logic. + public var workReportAccumulationGas: Gas - // GI: The gas allocated to invoke a work-package’s Is-Authorized logic. + /// GI: The gas allocated to invoke a work-package’s Is-Authorized logic. public var workPackageAuthorizerGas: Gas - // GR: The total gas allocated for a work-package’s Refine logic. + /// GR: The gas allocated to invoke a work-package's Refine logic. public var workPackageRefineGas: Gas - // GT: The total gas allocated across all cores for Accumulation. + /// GT: The total gas allocated across for all Accumulation. public var totalAccumulationGas: Gas - // H = 8: The size of recent history, in blocks. + /// H = 8: The size of recent history, in blocks. public var recentHistorySize: Int - // I = 4: The maximum amount of work items in a package. + /// I = 4: The maximum amount of work items in a package. public var maxWorkItems: Int - // J = 8: The maximum sum of dependency items in a work-report. + /// J = 8: The maximum sum of dependency items in a work-report. public var maxDepsInWorkReport: Int - // K = 16: The maximum number of tickets which may be submitted in a single extrinsic. + /// K = 16: The maximum number of tickets which may be submitted in a single extrinsic. public var maxTicketsPerExtrinsic: Int - // L = 14, 400: The maximum age in timeslots of the lookup anchor. + /// L = 14, 400: The maximum age in timeslots of the lookup anchor. public var maxLookupAnchorAge: Int - // N = 2: The number of ticket entries per validator. + /// N = 2: The number of ticket entries per validator. public var ticketEntriesPerValidator: Int - // O = 8: The maximum number of items in the authorizations pool. + /// O = 8: The maximum number of items in the authorizations pool. public var maxAuthorizationsPoolItems: Int - // P = 6: The slot period, in seconds. + /// P = 6: The slot period, in seconds. public var slotPeriodSeconds: Int - // Q = 80: The number of items in the authorizations queue. + /// Q = 80: The number of items in the authorizations queue. public var maxAuthorizationsQueueItems: Int - // R = 10: The rotation period of validator-core assignments, in timeslots. + /// R = 10: The rotation period of validator-core assignments, in timeslots. public var coreAssignmentRotationPeriod: Int - // U = 5: The period in timeslots after which reported but unavailable work may be replaced. + /// U = 5: The period in timeslots after which reported but unavailable work may be replaced. public var preimageReplacementPeriod: Int - // V = 1023: The total number of validators. + /// V = 1023: The total number of validators. public var totalNumberOfValidators: Int - // WC = 4,000,000: The maximum size of service code in octets. + /// WC = 4,000,000: The maximum size of service code in octets. public var maxServiceCodeSize: Int - // WE = 684: The basic size of our erasure-coded pieces. + /// WE = 684: The basic size of our erasure-coded pieces. public var erasureCodedPieceSize: Int - // WM = 2^11: The maximum number of entries in a work-package manifest. + /// WM = 2^11: The maximum number of entries in a work-package manifest. public var maxWorkPackageManifestEntries: Int - // WB = 12 * 2^20: The maximum size of an encoded work-package together with its extrinsic data and import impli- - // cations, in octets. + /// WB = 12 * 2^20: The maximum size of an encoded work-package together with its extrinsic data and import impli- + /// cations, in octets. public var maxEncodedWorkPackageSize: Int - // WG = WP*WE = 4104: The size of a segment in octets. + /// WG = WP*WE = 4104: The size of a segment in octets. public var segmentSize: Int - // WR = 48 * 2^10: The maximum total size of all output blobs in a work-report, in octets. + /// WR = 48 * 2^10: The maximum total size of all output blobs in a work-report, in octets. public var maxWorkReportOutputSize: Int - // WP = 6: The number of erasure-coded pieces in a segment. + /// WP = 6: The number of erasure-coded pieces in a segment. public var erasureCodedSegmentSize: Int - // WT = 128: The size of a transfer memo in octets. + /// WT = 128: The size of a transfer memo in octets. public var transferMemoSize: Int - // Y = 500: The number of slots into an epoch at which ticket-submission ends. + /// Y = 500: The number of slots into an epoch at which ticket-submission ends. public var ticketSubmissionEndSlot: Int - // ZA = 2: The pvm dynamic address alignment factor. + /// ZA = 2: The pvm dynamic address alignment factor. public var pvmDynamicAddressAlignmentFactor: Int - // ZI = 2^24: The standard pvm program initialization input data size. + /// ZI = 2^24: The standard pvm program initialization input data size. public var pvmProgramInitInputDataSize: Int - // ZZ = 2^16: The standard pvm program initialization zone size. + /// ZZ = 2^16: The standard pvm program initialization zone size. public var pvmProgramInitZoneSize: Int - // ZP = 2^12: The pvm memory page size. + /// ZP = 2^12: The pvm memory page size. public var pvmMemoryPageSize: Int public init( @@ -125,7 +125,7 @@ public struct ProtocolConfig: Sendable, Codable, Equatable { preimagePurgePeriod: Int, epochLength: Int, auditBiasFactor: Int, - coreAccumulationGas: Gas, + workReportAccumulationGas: Gas, workPackageAuthorizerGas: Gas, workPackageRefineGas: Gas, totalAccumulationGas: Gas, @@ -163,7 +163,7 @@ public struct ProtocolConfig: Sendable, Codable, Equatable { self.preimagePurgePeriod = preimagePurgePeriod self.epochLength = epochLength self.auditBiasFactor = auditBiasFactor - self.coreAccumulationGas = coreAccumulationGas + self.workReportAccumulationGas = workReportAccumulationGas self.workPackageAuthorizerGas = workPackageAuthorizerGas self.workPackageRefineGas = workPackageRefineGas self.totalAccumulationGas = totalAccumulationGas @@ -198,7 +198,7 @@ public struct ProtocolConfig: Sendable, Codable, Equatable { public typealias ProtocolConfigRef = Ref extension ProtocolConfig: PvmConfig {} -// silence the warning about cross module conformances as we owns all the code +/// silence the warning about cross module conformances as we owns all the code extension Ref: @retroactive PvmConfig where T == ProtocolConfig { public var pvmDynamicAddressAlignmentFactor: Int { value.pvmDynamicAddressAlignmentFactor } public var pvmProgramInitInputDataSize: Int { value.pvmProgramInitInputDataSize } @@ -224,8 +224,8 @@ extension ProtocolConfig { epochLength: other.epochLength != 0 ? other.epochLength : epochLength, auditBiasFactor: other.auditBiasFactor != 0 ? other.auditBiasFactor : auditBiasFactor, - coreAccumulationGas: other.coreAccumulationGas.value != 0 - ? other.coreAccumulationGas : coreAccumulationGas, + workReportAccumulationGas: other.workReportAccumulationGas.value != 0 + ? other.workReportAccumulationGas : workReportAccumulationGas, workPackageAuthorizerGas: other.workPackageAuthorizerGas.value != 0 ? other.workPackageAuthorizerGas : workPackageAuthorizerGas, workPackageRefineGas: other.workPackageRefineGas.value != 0 @@ -306,8 +306,8 @@ extension ProtocolConfig { preimagePurgePeriod = try decode(.preimagePurgePeriod, defaultValue: 0, required: required) epochLength = try decode(.epochLength, defaultValue: 0, required: required) auditBiasFactor = try decode(.auditBiasFactor, defaultValue: 0, required: required) - coreAccumulationGas = try decode( - .coreAccumulationGas, defaultValue: Gas(0), required: required + workReportAccumulationGas = try decode( + .workReportAccumulationGas, defaultValue: Gas(0), required: required ) workPackageAuthorizerGas = try decode( .workPackageAuthorizerGas, defaultValue: Gas(0), required: required @@ -435,11 +435,11 @@ extension ProtocolConfig { } } - public enum CoreAccumulationGas: ReadGas { + public enum WorkReportAccumulationGas: ReadGas { public typealias TConfig = ProtocolConfigRef public typealias TOutput = Gas public static func read(config: ProtocolConfigRef) -> Gas { - config.value.coreAccumulationGas + config.value.workReportAccumulationGas } } diff --git a/Blockchain/Sources/Blockchain/RuntimeProtocols/Accumulation.swift b/Blockchain/Sources/Blockchain/RuntimeProtocols/Accumulation.swift index 31bac061..5ccfdc2e 100644 --- a/Blockchain/Sources/Blockchain/RuntimeProtocols/Accumulation.swift +++ b/Blockchain/Sources/Blockchain/RuntimeProtocols/Accumulation.swift @@ -332,7 +332,7 @@ extension Accumulation { workReports: [WorkReport] ) async throws -> (numAccumulated: Int, state: AccumulateState, commitments: Set) { let sumPrevilegedGas = privilegedServices.basicGas.values.reduce(Gas(0)) { $0 + $1.value } - let minTotalGas = config.value.coreAccumulationGas * Gas(config.value.totalNumberOfCores) + sumPrevilegedGas + let minTotalGas = config.value.workReportAccumulationGas * Gas(config.value.totalNumberOfCores) + sumPrevilegedGas let gasLimit = max(config.value.totalAccumulationGas, minTotalGas) let res = try await outerAccumulate( diff --git a/Blockchain/Sources/Blockchain/RuntimeProtocols/Guaranteeing.swift b/Blockchain/Sources/Blockchain/RuntimeProtocols/Guaranteeing.swift index 867db13f..c0f533ca 100644 --- a/Blockchain/Sources/Blockchain/RuntimeProtocols/Guaranteeing.swift +++ b/Blockchain/Sources/Blockchain/RuntimeProtocols/Guaranteeing.swift @@ -168,7 +168,7 @@ extension Guaranteeing { } } - guard totalMinGasRequirement <= config.value.coreAccumulationGas else { + guard totalMinGasRequirement <= config.value.workReportAccumulationGas else { throw .outOfGas } @@ -197,7 +197,7 @@ extension Guaranteeing { guard context.anchor.stateRoot == history.stateRoot else { throw .invalidContext } - guard context.anchor.beefyRoot == history.mmr.hash() else { + guard context.anchor.beefyRoot == history.mmr.superPeak() else { throw .invalidContext } guard context.lookupAnchor.timeslot >= timeslot - UInt32(config.value.maxLookupAnchorAge) else { diff --git a/Node/Tests/NodeTests/chainfiles/devnet_allconfig_spec.json b/Node/Tests/NodeTests/chainfiles/devnet_allconfig_spec.json index a44a2d30..0146bf4c 100644 --- a/Node/Tests/NodeTests/chainfiles/devnet_allconfig_spec.json +++ b/Node/Tests/NodeTests/chainfiles/devnet_allconfig_spec.json @@ -7,7 +7,7 @@ "additionalMinBalancePerStateItem" : 10, "auditBiasFactor" : 2, "auditTranchePeriod" : 8, - "coreAccumulationGas" : 10000000, + "workReportAccumulationGas" : 10000000, "coreAssignmentRotationPeriod" : 6, "epochLength" : 6, "erasureCodedPieceSize" : 684, @@ -38,7 +38,7 @@ "transferMemoSize" : 128, "workPackageAuthorizerGas" : 10000000, "workPackageRefineGas" : 10000000, - "totalAccumulationGas": 341000000, + "totalAccumulationGas": 35000000, "maxDepsInWorkReport": 8 }, "state" : {}, diff --git a/Utils/Sources/Utils/Merklization/MMR.swift b/Utils/Sources/Utils/Merklization/MMR.swift index ec459130..e782a224 100644 --- a/Utils/Sources/Utils/Merklization/MMR.swift +++ b/Utils/Sources/Utils/Merklization/MMR.swift @@ -23,7 +23,18 @@ public struct MMR: Sendable, Equatable, Codable { } } - public func hash() -> Data32 { - try! JamEncoder.encode(self).keccakHash() + public func superPeak() -> Data32 { + func helper(_ peaks: ArraySlice) -> Data32 { + if peaks.count == 0 { + Data32() + } else if peaks.count == 1 { + peaks[0] + } else { + Keccak.hash("node", helper(peaks[0 ..< peaks.count - 1]), peaks.last!) + } + } + + let nonNilPeaks = peaks.compactMap { $0 } + return helper(nonNilPeaks[...]) } } diff --git a/Utils/Sources/Utils/Merklization/Merklization.swift b/Utils/Sources/Utils/Merklization/Merklization.swift index dcfdce44..d71591d8 100644 --- a/Utils/Sources/Utils/Merklization/Merklization.swift +++ b/Utils/Sources/Utils/Merklization/Merklization.swift @@ -114,15 +114,59 @@ public enum Merklization { binaryMerklizeHelper(constancyPreprocessor(nodes, hasher: hasher)).unwrapped } + private static func calculatePathLength(size: Int, nodesCount: Int) -> Int { + // bitwise impl of max(0, Int(ceil(log2(Double(max(1, nodes.count))) - Double(size)))) + let nodesCount = max(1, nodesCount) + var log2Value = 0 + var n = nodesCount + while n > 1 { + n >>= 1 + log2Value += 1 + } + if (1 << log2Value) < nodesCount { + log2Value += 1 + } + return max(0, log2Value - size) + } + + // provides the Merkle path to a single page public static func generateJustification( _ nodes: T, + size: Int, + index: T.Index, // page index + hasher: Hashing.Type = Blake2b256.self + ) -> [Data32] + where T: RandomAccessCollection, T.Index == Int + { + var res: [Data32] = [] + let pageSize = 1 << size + + traceImpl(constancyPreprocessor(nodes, hasher: hasher), index: pageSize * index, hasher: hasher) { res.append($0.unwrapped) } + + return Array(res.prefix(calculatePathLength(size: size, nodesCount: nodes.count))) + } + + // provides a single page of leaves, themselves hashed, prefixed data + public static func leafPage( + _ nodes: T, + size: Int, index: T.Index, hasher: Hashing.Type = Blake2b256.self ) -> [Data32] where T: RandomAccessCollection, T.Index == Int { + let pageSize = 1 << size var res: [Data32] = [] - traceImpl(constancyPreprocessor(nodes, hasher: hasher), index: index, hasher: hasher) { res.append($0.unwrapped) } + res.reserveCapacity(pageSize) + let startIndex = index * pageSize + let endIndex = min(nodes.count, startIndex + pageSize) + for i in startIndex ..< endIndex { + if i < nodes.count { + res.append(hasher.hash("leaf", nodes[i])) + } else { + res.append(Data32()) + } + } return res } } diff --git a/Utils/Tests/UtilsTests/Merklization/MerklizationTests.swift b/Utils/Tests/UtilsTests/Merklization/MerklizationTests.swift index c37c338d..a36574ed 100644 --- a/Utils/Tests/UtilsTests/Merklization/MerklizationTests.swift +++ b/Utils/Tests/UtilsTests/Merklization/MerklizationTests.swift @@ -18,8 +18,15 @@ struct MerklizationTests { @Test func testHash() throws { let mmr = MMR([]) - let emptyHash = try JamEncoder.encode(mmr).keccakHash() - #expect(mmr.hash() == emptyHash) + #expect(mmr.superPeak() == Data32()) + + let peak = Data32.random() + let mmr1 = MMR([peak]) + #expect(mmr1.superPeak() == peak) + + let peaks = [Data32.random(), Data32.random(), Data32.random()] + let mmr2 = MMR(peaks) + #expect(mmr2.superPeak() == Keccak.hash("node", Keccak.hash("node", peaks[0], peaks[1]), peaks[2])) } @Test @@ -80,14 +87,53 @@ struct MerklizationTests { Data("node3".utf8), Data("node4".utf8), ] - let index = 2 - let result = Merklization.generateJustification(input, index: index) + let result = Merklization.generateJustification(input, size: 1, index: 1) let expected: [Data32] = [ Blake2b256.hash("node", Blake2b256.hash("leaf", "node3"), Blake2b256.hash("leaf", "node4")), ] + #expect(result == expected) - #expect(result.first == expected.first) + let result1 = Merklization.generateJustification(input, size: 1, index: 0) + let expected1: [Data32] = [ + Blake2b256.hash("node", Blake2b256.hash("leaf", "node1"), Blake2b256.hash("leaf", "node2")), + ] + #expect(result1 == expected1) + + let result2 = Merklization.generateJustification(input, size: 2, index: 0) + let expected2: [Data32] = [ + ] + #expect(result2 == expected2) + + let result3 = Merklization.generateJustification(input, size: 0, index: 0) + let expected3: [Data32] = [ + Blake2b256.hash("node", Blake2b256.hash("leaf", "node1"), Blake2b256.hash("leaf", "node2")), + Blake2b256.hash("leaf", "node3"), + ] + #expect(result3 == expected3) + + let result4 = Merklization.generateJustification(input, size: 0, index: 2) + let expected4: [Data32] = [ + Blake2b256.hash("node", Blake2b256.hash("leaf", "node3"), Blake2b256.hash("leaf", "node4")), + Blake2b256.hash("leaf", "node1"), + ] + #expect(result4 == expected4) + } + + @Test + func testLeafPage() { + let input: [Data] = [ + Data("node1".utf8), + Data("node2".utf8), + Data("node3".utf8), + Data("node4".utf8), + ] + + let result = Merklization.leafPage(input, size: 1, index: 1) + let expected: [Data32] = [ + Blake2b256.hash("leaf", "node3"), Blake2b256.hash("leaf", "node4"), + ] + #expect(result == expected) } @Test @@ -96,7 +142,7 @@ struct MerklizationTests { let binaryResult = Merklization.binaryMerklize(emptyInput) let constantDepthResult = Merklization.constantDepthMerklize(emptyInput) - let justificationResult = Merklization.generateJustification(emptyInput, index: 0) + let justificationResult = Merklization.generateJustification(emptyInput, size: 1, index: 0) #expect(binaryResult == Data32()) #expect(constantDepthResult == Data32())