From 574979098f1b8d3e85b48a66c351f0fefa6ef7b4 Mon Sep 17 00:00:00 2001 From: Xiliang Chen Date: Fri, 3 Jan 2025 22:50:11 +1300 Subject: [PATCH 1/2] Update testvectors (#259) * reports tests * add shuffle tests * fixes * fix * enable full tests * fix * fix --- .../Config/ProtocolConfig+Preset.swift | 42 ++--- .../Blockchain/Config/ProtocolConfig.swift | 6 +- .../RuntimeProtocols/Guaranteeing.swift | 57 ++++--- .../Blockchain/RuntimeProtocols/Runtime.swift | 6 +- .../VMInvocations/HostCall/HostCalls.swift | 4 +- Boka/Package.resolved | 11 +- Database/Package.resolved | 11 +- JAMTests/Tests/JAMTests/ReportsTests.swift | 153 ++++++++++++++++++ JAMTests/Tests/JAMTests/ShuffleTests.swift | 35 ++++ JAMTests/jamtestvectors | 2 +- .../Utils/Extensions/Array+Utils.swift | 22 +-- Utils/Sources/Utils/LimitedSizeArray.swift | 31 +++- .../UtilsTests/LimitedSizeArrayTest.swift | 6 +- 13 files changed, 316 insertions(+), 70 deletions(-) create mode 100644 JAMTests/Tests/JAMTests/ReportsTests.swift create mode 100644 JAMTests/Tests/JAMTests/ShuffleTests.swift diff --git a/Blockchain/Sources/Blockchain/Config/ProtocolConfig+Preset.swift b/Blockchain/Sources/Blockchain/Config/ProtocolConfig+Preset.swift index d0769b09..b06e4b76 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, - workReportAccumulationGas: Gas(100_000), - workPackageAuthorizerGas: Gas(1_000_000), - workPackageRefineGas: Gas(500_000_000), - totalAccumulationGas: Gas(35_000_000), + workReportAccumulationGas: Gas(10_000_000), + workPackageAuthorizerGas: Gas(50_000_000), + workPackageRefineGas: Gas(5_000_000_000), + totalAccumulationGas: Gas(3_500_000_000), recentHistorySize: 8, maxWorkItems: 4, maxDepsInWorkReport: 8, @@ -33,7 +33,7 @@ extension Ref where T == ProtocolConfig { maxWorkPackageManifestEntries: 1 << 11, maxEncodedWorkPackageSize: 12 * 1 << 20, segmentSize: 4104, - maxWorkReportOutputSize: 96 * 1 << 10, + maxWorkReportOutputSize: 48 * 1 << 10, erasureCodedSegmentSize: 6, ticketSubmissionEndSlot: 2, pvmDynamicAddressAlignmentFactor: 2, @@ -52,10 +52,10 @@ extension Ref where T == ProtocolConfig { preimagePurgePeriod: 28800, epochLength: 12, auditBiasFactor: 2, - workReportAccumulationGas: Gas(100_000), - workPackageAuthorizerGas: Gas(1_000_000), - workPackageRefineGas: Gas(500_000_000), - totalAccumulationGas: Gas(35_000_000), + workReportAccumulationGas: Gas(10_000_000), + workPackageAuthorizerGas: Gas(50_000_000), + workPackageRefineGas: Gas(5_000_000_000), + totalAccumulationGas: Gas(3_500_000_000), recentHistorySize: 8, maxWorkItems: 4, maxDepsInWorkReport: 8, @@ -74,7 +74,7 @@ extension Ref where T == ProtocolConfig { maxWorkPackageManifestEntries: 1 << 11, maxEncodedWorkPackageSize: 12 * 1 << 20, segmentSize: 4104, - maxWorkReportOutputSize: 96 * 1 << 10, + maxWorkReportOutputSize: 48 * 1 << 10, erasureCodedSegmentSize: 6, ticketSubmissionEndSlot: 10, pvmDynamicAddressAlignmentFactor: 2, @@ -92,10 +92,10 @@ extension Ref where T == ProtocolConfig { preimagePurgePeriod: 28800, epochLength: 12, auditBiasFactor: 2, - workReportAccumulationGas: Gas(100_000), - workPackageAuthorizerGas: Gas(1_000_000), - workPackageRefineGas: Gas(500_000_000), - totalAccumulationGas: Gas(35_000_000), + workReportAccumulationGas: Gas(10_000_000), + workPackageAuthorizerGas: Gas(50_000_000), + workPackageRefineGas: Gas(5_000_000_000), + totalAccumulationGas: Gas(3_500_000_000), recentHistorySize: 8, maxWorkItems: 4, maxDepsInWorkReport: 8, @@ -106,7 +106,7 @@ extension Ref where T == ProtocolConfig { maxAuthorizationsPoolItems: 8, slotPeriodSeconds: 6, maxAuthorizationsQueueItems: 80, - coreAssignmentRotationPeriod: 10, + coreAssignmentRotationPeriod: 4, maxServiceCodeSize: 4_000_000, preimageReplacementPeriod: 5, totalNumberOfValidators: 6, @@ -114,7 +114,7 @@ extension Ref where T == ProtocolConfig { maxWorkPackageManifestEntries: 1 << 11, maxEncodedWorkPackageSize: 12 * 1 << 20, segmentSize: 4104, - maxWorkReportOutputSize: 96 * 1 << 10, + maxWorkReportOutputSize: 48 * 1 << 10, erasureCodedSegmentSize: 6, ticketSubmissionEndSlot: 10, pvmDynamicAddressAlignmentFactor: 2, @@ -132,10 +132,10 @@ extension Ref where T == ProtocolConfig { preimagePurgePeriod: 28800, epochLength: 600, auditBiasFactor: 2, - workReportAccumulationGas: Gas(100_000), - workPackageAuthorizerGas: Gas(1_000_000), - workPackageRefineGas: Gas(500_000_000), - totalAccumulationGas: Gas(35_000_000), + workReportAccumulationGas: Gas(10_000_000), + workPackageAuthorizerGas: Gas(50_000_000), + workPackageRefineGas: Gas(5_000_000_000), + totalAccumulationGas: Gas(3_500_000_000), recentHistorySize: 8, maxWorkItems: 4, maxDepsInWorkReport: 8, @@ -154,7 +154,7 @@ extension Ref where T == ProtocolConfig { maxWorkPackageManifestEntries: 1 << 11, maxEncodedWorkPackageSize: 12 * 1 << 20, segmentSize: 4104, - maxWorkReportOutputSize: 96 * 1 << 10, + maxWorkReportOutputSize: 48 * 1 << 10, erasureCodedSegmentSize: 6, ticketSubmissionEndSlot: 500, pvmDynamicAddressAlignmentFactor: 2, diff --git a/Blockchain/Sources/Blockchain/Config/ProtocolConfig.swift b/Blockchain/Sources/Blockchain/Config/ProtocolConfig.swift index a390b83e..997e4548 100644 --- a/Blockchain/Sources/Blockchain/Config/ProtocolConfig.swift +++ b/Blockchain/Sources/Blockchain/Config/ProtocolConfig.swift @@ -28,13 +28,13 @@ public struct ProtocolConfig: Sendable, Codable, Equatable { /// following tranche for each no-show in the previous. public var auditBiasFactor: Int - /// GA: The gas allocated to invoke a work-report's Accumulation logic. + /// GA = 10,000,000: 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 = 50,000,000: The gas allocated to invoke a work-package’s Is-Authorized logic. public var workPackageAuthorizerGas: Gas - /// GR: The gas allocated to invoke a work-package's Refine logic. + /// GR = 5,000,000,000: The gas allocated to invoke a work-package's Refine logic. public var workPackageRefineGas: Gas /// GT: The total gas allocated across for all Accumulation. diff --git a/Blockchain/Sources/Blockchain/RuntimeProtocols/Guaranteeing.swift b/Blockchain/Sources/Blockchain/RuntimeProtocols/Guaranteeing.swift index c0f533ca..c22ae879 100644 --- a/Blockchain/Sources/Blockchain/RuntimeProtocols/Guaranteeing.swift +++ b/Blockchain/Sources/Blockchain/RuntimeProtocols/Guaranteeing.swift @@ -15,11 +15,11 @@ public enum GuaranteeingError: Error { case invalidServiceGas case invalidPublicKey case invalidSegmentLookup + case futureReportSlot } public protocol Guaranteeing { var entropyPool: EntropyPool { get } - var timeslot: TimeslotIndex { get } var currentValidators: ConfigFixedSizeArray< ValidatorKey, ProtocolConfig.TotalNumberOfValidators > { get } @@ -87,11 +87,16 @@ extension Guaranteeing { public func update( config: ProtocolConfigRef, + timeslot: TimeslotIndex, extrinsic: ExtrinsicGuarantees - ) throws(GuaranteeingError) -> ConfigFixedSizeArray< - ReportItem?, - ProtocolConfig.TotalNumberOfCores - > { + ) throws(GuaranteeingError) -> ( + newReports: ConfigFixedSizeArray< + ReportItem?, + ProtocolConfig.TotalNumberOfCores + >, + reported: [WorkReport], + reporters: [Ed25519PublicKey] + ) { let coreAssignmentRotationPeriod = UInt32(config.value.coreAssignmentRotationPeriod) let currentCoreAssignment = getCoreAssignment(config: config, randomness: entropyPool.t2, timeslot: timeslot) @@ -108,15 +113,20 @@ extension Guaranteeing { ) let pareviousCoreKeys = withoutOffenders(keys: previousValidators.map(\.ed25519)) - var workReportHashes = Set() - - var totalMinGasRequirement = Gas(0) + var workPackageHashes = Set() var oldLookups = [Data32: Data32]() + var reporters = [Ed25519PublicKey]() + for guarantee in extrinsic.guarantees { + var totalGasUsage = Gas(0) let report = guarantee.workReport + guard guarantee.timeslot <= timeslot else { + throw .futureReportSlot + } + oldLookups[report.packageSpecification.workPackageHash] = report.packageSpecification.segmentRoot for credential in guarantee.credential { @@ -124,7 +134,7 @@ extension Guaranteeing { let keys = isCurrent ? currentCoreKeys : pareviousCoreKeys let key = keys[Int(credential.index)] let reportHash = report.hash() - workReportHashes.insert(reportHash) + workPackageHashes.insert(report.packageSpecification.workPackageHash) let payload = SigningContext.guarantee + reportHash.data let pubkey = try Result { try Ed25519.PublicKey(from: key) } .mapError { _ in GuaranteeingError.invalidPublicKey } @@ -137,6 +147,8 @@ extension Guaranteeing { guard coreAssignment[Int(credential.index)] == report.coreIndex else { // TODO: it should accepts the last core index? throw .invalidGuaranteeCore } + + reporters.append(key) } let coreIndex = Int(report.coreIndex) @@ -164,22 +176,22 @@ extension Guaranteeing { throw .invalidServiceGas } - totalMinGasRequirement += acc.minAccumlateGas + totalGasUsage += result.gasRatio } - } - guard totalMinGasRequirement <= config.value.workReportAccumulationGas else { - throw .outOfGas + guard totalGasUsage <= config.value.workReportAccumulationGas else { + throw .outOfGas + } } - let recentWorkReportHashes: Set = Set(recentHistory.items.flatMap(\.lookup.keys)) + let recentWorkPackageHashes: Set = Set(recentHistory.items.flatMap(\.lookup.keys)) let accumulateHistoryReports = Set(accumulationHistory.array.flatMap { $0 }) let accumulateQueueReports = Set(accumulationQueue.array.flatMap { $0 } .flatMap(\.workReport.refinementContext.prerequisiteWorkPackages)) let pendingWorkReportHashes = Set(reports.array.flatMap { $0?.workReport.refinementContext.prerequisiteWorkPackages ?? [] }) - let pipelinedWorkReportHashes = recentWorkReportHashes.union(accumulateHistoryReports).union(accumulateQueueReports) + let pipelinedWorkReportHashes = recentWorkPackageHashes.union(accumulateHistoryReports).union(accumulateQueueReports) .union(pendingWorkReportHashes) - guard pipelinedWorkReportHashes.isDisjoint(with: workReportHashes) else { + guard pipelinedWorkReportHashes.isDisjoint(with: workPackageHashes) else { throw .duplicatedWorkPackage } @@ -200,13 +212,13 @@ extension Guaranteeing { guard context.anchor.beefyRoot == history.mmr.superPeak() else { throw .invalidContext } - guard context.lookupAnchor.timeslot >= timeslot - UInt32(config.value.maxLookupAnchorAge) else { + guard context.lookupAnchor.timeslot >= Int64(timeslot) - Int64(config.value.maxLookupAnchorAge) else { throw .invalidContext } for prerequisiteWorkPackage in context.prerequisiteWorkPackages.union(report.lookup.keys) { - guard recentWorkReportHashes.contains(prerequisiteWorkPackage) || - workReportHashes.contains(prerequisiteWorkPackage) + guard recentWorkPackageHashes.contains(prerequisiteWorkPackage) || + workPackageHashes.contains(prerequisiteWorkPackage) else { throw .prerequisiteNotFound } @@ -220,6 +232,7 @@ extension Guaranteeing { } var newReports = reports + var reported = [WorkReport]() for guarantee in extrinsic.guarantees { let report = guarantee.workReport @@ -228,8 +241,12 @@ extension Guaranteeing { workReport: report, timeslot: timeslot ) + reported.append(report) } - return newReports + reported.sort { $0.packageSpecification.workPackageHash < $1.packageSpecification.workPackageHash } + reporters.sort() + + return (newReports, reported, reporters) } } diff --git a/Blockchain/Sources/Blockchain/RuntimeProtocols/Runtime.swift b/Blockchain/Sources/Blockchain/RuntimeProtocols/Runtime.swift index 4f6c6fc0..0483e721 100644 --- a/Blockchain/Sources/Blockchain/RuntimeProtocols/Runtime.swift +++ b/Blockchain/Sources/Blockchain/RuntimeProtocols/Runtime.swift @@ -353,7 +353,11 @@ public final class Runtime { ) newState.reports = newReports - newState.reports = try newState.update(config: config, extrinsic: block.extrinsic.reports) + let result = try newState.update( + config: config, timeslot: newState.timeslot, extrinsic: block.extrinsic.reports + ) + + newState.reports = result.newReports return availableReports } diff --git a/Blockchain/Sources/Blockchain/VMInvocations/HostCall/HostCalls.swift b/Blockchain/Sources/Blockchain/VMInvocations/HostCall/HostCalls.swift index 5cbb5c96..504ebef4 100644 --- a/Blockchain/Sources/Blockchain/VMInvocations/HostCall/HostCalls.swift +++ b/Blockchain/Sources/Blockchain/VMInvocations/HostCall/HostCalls.swift @@ -561,7 +561,7 @@ public class Solicit: HostCall { if notRequestedYet { x.serviceAccounts.set(serviceAccount: x.serviceIndex, preimageHash: Data32(hash!)!, length: length, value: []) } else if isPreviouslyAvailable, var preimageInfo { - preimageInfo.append(timeslot) + try preimageInfo.append(timeslot) x.serviceAccounts.set(serviceAccount: x.serviceIndex, preimageHash: Data32(hash!)!, length: length, value: preimageInfo) } } @@ -602,7 +602,7 @@ public class Forget: HostCall { x.serviceAccounts.set(serviceAccount: x.serviceIndex, preimageHash: Data32(hash!)!, length: length, value: nil) x.serviceAccounts.set(serviceAccount: x.serviceIndex, preimageHash: Data32(hash!)!, value: nil) } else if isAvailable1, var preimageInfo { - preimageInfo.append(timeslot) + try preimageInfo.append(timeslot) x.serviceAccounts.set(serviceAccount: x.serviceIndex, preimageHash: Data32(hash!)!, length: length, value: preimageInfo) } else if isAvailable3, var preimageInfo { preimageInfo = [preimageInfo[2], timeslot] diff --git a/Boka/Package.resolved b/Boka/Package.resolved index 6775d3b8..3221b295 100644 --- a/Boka/Package.resolved +++ b/Boka/Package.resolved @@ -1,5 +1,5 @@ { - "originHash" : "4c744cc14f723e954bb1925a42ca80b040439968821616ed067388fcd416689a", + "originHash" : "21fd16bf620160741466e113b8b7b90cd924166bfee31bc2fb85ad79912ba6dc", "pins" : [ { "identity" : "async-channels", @@ -55,6 +55,15 @@ "version" : "1.24.1" } }, + { + "identity" : "lrucache", + "kind" : "remoteSourceControl", + "location" : "https://github.com/nicklockwood/LRUCache.git", + "state" : { + "revision" : "542f0449556327415409ededc9c43a4bd0a397dc", + "version" : "1.0.7" + } + }, { "identity" : "multipart-kit", "kind" : "remoteSourceControl", diff --git a/Database/Package.resolved b/Database/Package.resolved index b535f4a9..66a2c042 100644 --- a/Database/Package.resolved +++ b/Database/Package.resolved @@ -1,5 +1,5 @@ { - "originHash" : "5ea33c0ee52a6037b6b2321cf0e43f8b560504514f98c234c42c9b035a87752a", + "originHash" : "71ac75472e411174f6e43bdf064f1565f45d087a55de16d382092ae064ca91eb", "pins" : [ { "identity" : "blake2.swift", @@ -10,6 +10,15 @@ "version" : "0.2.0" } }, + { + "identity" : "lrucache", + "kind" : "remoteSourceControl", + "location" : "https://github.com/nicklockwood/LRUCache.git", + "state" : { + "revision" : "542f0449556327415409ededc9c43a4bd0a397dc", + "version" : "1.0.7" + } + }, { "identity" : "swift-asn1", "kind" : "remoteSourceControl", diff --git a/JAMTests/Tests/JAMTests/ReportsTests.swift b/JAMTests/Tests/JAMTests/ReportsTests.swift new file mode 100644 index 00000000..2e7a9427 --- /dev/null +++ b/JAMTests/Tests/JAMTests/ReportsTests.swift @@ -0,0 +1,153 @@ +import Blockchain +import Codec +import Foundation +import Testing +import Utils + +@testable import JAMTests + +struct ReportsTestcaseState: Codable, Equatable { + var reports: ConfigFixedSizeArray + var currentValidators: + ConfigFixedSizeArray + var previousValidators: + ConfigFixedSizeArray + var entropyPool: EntropyPool + var offenders: [Ed25519PublicKey] + var recentHistory: RecentHistory + var coreAuthorizationPool: ConfigFixedSizeArray< + ConfigLimitedSizeArray, + ProtocolConfig.TotalNumberOfCores + > + @CodingAs> var services: [ServiceIndex: ServiceAccountDetails] +} + +struct ReportsInput: Codable { + var reports: ExtrinsicGuarantees + var timeslot: TimeslotIndex +} + +struct ReportedPackage: Codable, Equatable { + var workPackageHash: Data32 + var segmentRoot: Data32 +} + +struct ReportsOutput: Codable, Equatable { + var reported: [ReportedPackage] + var reporters: [Ed25519PublicKey] +} + +struct ReportsState: Guaranteeing { + var reports: ConfigFixedSizeArray + var currentValidators: + ConfigFixedSizeArray + var previousValidators: + ConfigFixedSizeArray + var entropyPool: EntropyPool + var offenders: Set + var recentHistory: RecentHistory + var coreAuthorizationPool: ConfigFixedSizeArray< + ConfigLimitedSizeArray, + ProtocolConfig.TotalNumberOfCores + > + var services: [ServiceIndex: ServiceAccountDetails] + + func serviceAccount(index: ServiceIndex) -> ServiceAccountDetails? { + services[index] + } + + var accumulationQueue: ConfigFixedSizeArray<[AccumulationQueueItem], ProtocolConfig.EpochLength> + var accumulationHistory: ConfigFixedSizeArray, ProtocolConfig.EpochLength> +} + +struct ReportsTestcase: Codable { + var input: ReportsInput + var preState: ReportsTestcaseState + var output: Either + var postState: ReportsTestcaseState +} + +struct ReportsTests { + static func loadTests(variant: TestVariants) throws -> [Testcase] { + try TestLoader.getTestcases(path: "reports/\(variant)", extension: "bin") + } + + func reportsTests(_ testcase: Testcase, variant: TestVariants) throws { + if testcase.description == "no_enough_guarantees-1.bin" { + // we can't decode such test because it is intentially invalid + return + } + + let config = variant.config + let decoder = JamDecoder(data: testcase.data, config: config) + let testcase = try decoder.decode(ReportsTestcase.self) + + let state = ReportsState( + reports: testcase.preState.reports, + currentValidators: testcase.preState.currentValidators, + previousValidators: testcase.preState.previousValidators, + entropyPool: testcase.preState.entropyPool, + offenders: Set(testcase.preState.offenders), + recentHistory: testcase.preState.recentHistory, + coreAuthorizationPool: testcase.preState.coreAuthorizationPool, + services: testcase.preState.services, + accumulationQueue: try! ConfigFixedSizeArray(config: config, defaultValue: []), + accumulationHistory: try! ConfigFixedSizeArray(config: config, defaultValue: []) + ) + let result = Result { + try testcase.input.reports.validate(config: config) + return try state.update( + config: config, + timeslot: testcase.input.timeslot, + extrinsic: testcase.input.reports + ) + } + switch result { + case let .success((newReports, reported, reporters)): + switch testcase.output { + case let .left(output): + let expectedPostState = ReportsTestcaseState( + reports: newReports, + currentValidators: state.currentValidators, + previousValidators: state.previousValidators, + entropyPool: state.entropyPool, + offenders: state.offenders.sorted(), + recentHistory: state.recentHistory, + coreAuthorizationPool: state.coreAuthorizationPool, + services: state.services + ) + let expectedOutput = ReportsOutput( + reported: reported.map { report in + ReportedPackage( + workPackageHash: report.packageSpecification.workPackageHash, + segmentRoot: report.packageSpecification.segmentRoot + ) + }, + reporters: reporters + ) + #expect(expectedPostState == testcase.postState) + #expect(expectedOutput == output) + case .right: + Issue.record("Expected error, got \(result)") + } + case .failure: + switch testcase.output { + case .left: + Issue.record("Expected success, got \(result)") + case .right: + // ignore error code because it is unspecified + break + } + } + } + + @Test(arguments: try ReportsTests.loadTests(variant: .tiny)) + func tinyTests(_ testcase: Testcase) throws { + try reportsTests(testcase, variant: .tiny) + } + + @Test(arguments: try ReportsTests.loadTests(variant: .full)) + func fullTests(_ testcase: Testcase) throws { + try reportsTests(testcase, variant: .full) + } +} diff --git a/JAMTests/Tests/JAMTests/ShuffleTests.swift b/JAMTests/Tests/JAMTests/ShuffleTests.swift new file mode 100644 index 00000000..00734f1a --- /dev/null +++ b/JAMTests/Tests/JAMTests/ShuffleTests.swift @@ -0,0 +1,35 @@ +import Foundation +import Testing +import Utils + +@testable import JAMTests + +struct ShuffleTestCase: Codable { + let input: Int + let entropy: String + let output: [Int] +} + +struct ShuffleTests { + static func loadTests() throws -> [ShuffleTestCase] { + // Load test vectors from the JSON file + let testData = try TestLoader.getFile(path: "shuffle/shuffle_tests", extension: "json") + let decoder = JSONDecoder() + return try decoder.decode([ShuffleTestCase].self, from: testData) + } + + @Test(arguments: try ShuffleTests.loadTests()) + func testShuffle(testCase: ShuffleTestCase) throws { + // Create input array [0.. RandomnessIterator { - RandomnessIterator(data: data, index: 0, source: data) + RandomnessIterator(data: data, index: 0) } } struct RandomnessIterator: IteratorProtocol { let data: Data32 var index: Int - var source: Data32 + var source: Data32 = .init() mutating func next() -> UInt32? { let idx = index % 8 @@ -17,7 +17,8 @@ struct RandomnessIterator: IteratorProtocol { source = Blake2b256.hash(data, UInt32(index / 8).encode()) } index += 1 - return source.data[4 * idx ..< 4 * (idx + 1)].decode(UInt32.self) + let offset = 4 * idx + return source.data[offset ..< offset + 4].decode(UInt32.self) } } @@ -46,14 +47,15 @@ extension Array { // requires randomness have at least count elements private mutating func shuffle(randomness: some Sequence) { + if count <= 1 { + return + } + var copy = self var iter = randomness.makeIterator() - // TODO: confirm this is matching to the defs in GP - for i in stride(from: count - 1, through: 1, by: -1) { - let j = Int((iter.next() ?? 0) % UInt32(i + 1)) - guard i != j else { - continue - } - swapAt(i, j) + for i in 0 ..< count { + let r0 = Int((iter.next() ?? 0) % UInt32(count - i)) + self[i] = copy[r0] + copy[r0] = copy[count - i - 1] } } diff --git a/Utils/Sources/Utils/LimitedSizeArray.swift b/Utils/Sources/Utils/LimitedSizeArray.swift index fbb94726..28a9665e 100644 --- a/Utils/Sources/Utils/LimitedSizeArray.swift +++ b/Utils/Sources/Utils/LimitedSizeArray.swift @@ -1,5 +1,10 @@ import Codec +public enum LimitedSizeArrayError: Swift.Error { + case tooFewElements + case tooManyElements +} + public struct LimitedSizeArray { public private(set) var array: [T] public static var minLength: Int { @@ -26,6 +31,15 @@ public struct LimitedSizeArray { assert(array.count >= Self.minLength) assert(array.count <= Self.maxLength) } + + private func validateThrowing() throws(LimitedSizeArrayError) { + guard array.count >= Self.minLength else { + throw LimitedSizeArrayError.tooFewElements + } + guard array.count <= Self.maxLength else { + throw LimitedSizeArrayError.tooManyElements + } + } } extension LimitedSizeArray: Sendable where T: Sendable {} @@ -100,19 +114,20 @@ extension LimitedSizeArray: RandomAccessCollection { } extension LimitedSizeArray { - public mutating func append(_ newElement: T) { + public mutating func append(_ newElement: T) throws(LimitedSizeArrayError) { array.append(newElement) - validate() + try validateThrowing() } - public mutating func insert(_ newElement: T, at i: Int) { + public mutating func insert(_ newElement: T, at i: Int) throws(LimitedSizeArrayError) { array.insert(newElement, at: i) - validate() + try validateThrowing() } - public mutating func remove(at i: Int) -> T { - defer { validate() } - return array.remove(at: i) + public mutating func remove(at i: Int) throws(LimitedSizeArrayError) -> T { + let ret = array.remove(at: i) + try validateThrowing() + return ret } } @@ -155,6 +170,8 @@ extension LimitedSizeArray: Decodable where T: Decodable { for _ in 0 ..< length { try array.append(container.decode(T.self)) } + + try validateThrowing() } } diff --git a/Utils/Tests/UtilsTests/LimitedSizeArrayTest.swift b/Utils/Tests/UtilsTests/LimitedSizeArrayTest.swift index 5858f804..77ae8f44 100644 --- a/Utils/Tests/UtilsTests/LimitedSizeArrayTest.swift +++ b/Utils/Tests/UtilsTests/LimitedSizeArrayTest.swift @@ -31,21 +31,21 @@ struct LimitedSizeArrayTests { @Test func appendElement() throws { var array = LimitedSizeArray([1, 2, 3, 4, 5]) - array.append(6) + try array.append(6) #expect(array.array == [1, 2, 3, 4, 5, 6]) #expect(array.count == 6) } @Test func insertElement() throws { var array = LimitedSizeArray([1, 2, 3, 4, 5]) - array.insert(0, at: 2) + try array.insert(0, at: 2) #expect(array.array == [1, 2, 0, 3, 4, 5]) #expect(array.count == 6) } @Test func removeElement() throws { var array = LimitedSizeArray([1, 2, 3, 4, 5, 6]) - let removed = array.remove(at: 2) + let removed = try array.remove(at: 2) #expect(removed == 3) #expect(array.array == [1, 2, 4, 5, 6]) #expect(array.count == 5) From 8960041881b809f9cdb1c065b774b7fbb0c8f2e5 Mon Sep 17 00:00:00 2001 From: Qiwei Yang Date: Mon, 6 Jan 2025 05:11:58 +0800 Subject: [PATCH 2/2] 0.5.3 updates (#260) * 0.5.3 updates * fix comment * add missing wp properties --- .../Sources/Blockchain/Types/WorkOutput.swift | 21 +++++++++++++++---- .../Blockchain/Types/WorkPackage.swift | 19 ++++++++++++++++- .../VMInvocations/HostCall/HostCalls.swift | 6 ++---- .../HostCall/ResultConstants.swift | 2 -- Node/Package.resolved | 11 +++++++++- .../chainfiles/devnet_allconfig_spec.json | 6 +++--- 6 files changed, 50 insertions(+), 15 deletions(-) diff --git a/Blockchain/Sources/Blockchain/Types/WorkOutput.swift b/Blockchain/Sources/Blockchain/Types/WorkOutput.swift index 22e32d09..2c56e0d4 100644 --- a/Blockchain/Sources/Blockchain/Types/WorkOutput.swift +++ b/Blockchain/Sources/Blockchain/Types/WorkOutput.swift @@ -4,8 +4,12 @@ import Foundation public enum WorkResultError: Error, CaseIterable { case outOfGas case panic + /// the number of exports made was invalidly reported + case badExports + /// the service's code was not available for lookup in state at the posterior state of the lookup-anchor block case invalidCode - case codeTooLarge // code larger than MaxServiceCodeSize + /// code larger than MaxServiceCodeSize + case codeTooLarge } public struct WorkOutput: Sendable, Equatable { @@ -21,6 +25,7 @@ extension WorkOutput: Codable { case success case outOfGas case panic + case badExports case invalidCode case codeTooLarge } @@ -37,8 +42,10 @@ extension WorkOutput: Codable { case 2: self = .init(.failure(.panic)) case 3: - self = .init(.failure(.invalidCode)) + self = .init(.failure(.badExports)) case 4: + self = .init(.failure(.invalidCode)) + case 5: self = .init(.failure(.codeTooLarge)) default: throw DecodingError.dataCorrupted( @@ -56,6 +63,8 @@ extension WorkOutput: Codable { self = .init(.failure(.outOfGas)) } else if container.contains(.panic) { self = .init(.failure(.panic)) + } else if container.contains(.badExports) { + self = .init(.failure(.badExports)) } else if container.contains(.invalidCode) { self = .init(.failure(.invalidCode)) } else if container.contains(.codeTooLarge) { @@ -84,10 +93,12 @@ extension WorkOutput: Codable { try container.encode(UInt8(1)) case .panic: try container.encode(UInt8(2)) - case .invalidCode: + case .badExports: try container.encode(UInt8(3)) - case .codeTooLarge: + case .invalidCode: try container.encode(UInt8(4)) + case .codeTooLarge: + try container.encode(UInt8(5)) } } } else { @@ -101,6 +112,8 @@ extension WorkOutput: Codable { try container.encodeNil(forKey: .outOfGas) case .panic: try container.encodeNil(forKey: .panic) + case .badExports: + try container.encodeNil(forKey: .badExports) case .invalidCode: try container.encodeNil(forKey: .invalidCode) case .codeTooLarge: diff --git a/Blockchain/Sources/Blockchain/Types/WorkPackage.swift b/Blockchain/Sources/Blockchain/Types/WorkPackage.swift index 620bf788..d283a134 100644 --- a/Blockchain/Sources/Blockchain/Types/WorkPackage.swift +++ b/Blockchain/Sources/Blockchain/Types/WorkPackage.swift @@ -9,7 +9,7 @@ public struct WorkPackage: Sendable, Equatable, Codable { // h public var authorizationServiceIndex: ServiceIndex - // c + // u public var authorizationCodeHash: Data32 // p @@ -59,3 +59,20 @@ extension WorkPackage: Dummy { ) } } + +extension WorkPackage { + /// a: work-package’s implied authorizer, the hash of the concatenation of the authorization code + /// and the parameterization + public func authorizer(serviceAccounts: some ServiceAccounts) async throws -> Data32 { + try await Blake2b256.hash(authorizationCode(serviceAccounts: serviceAccounts), parameterizationBlob) + } + + /// c: the authorization code + public func authorizationCode(serviceAccounts: some ServiceAccounts) async throws -> Data { + try await serviceAccounts.historicalLookup( + serviceAccount: authorizationServiceIndex, + timeslot: context.lookupAnchor.timeslot, + preimageHash: authorizationCodeHash + ) ?? Data() + } +} diff --git a/Blockchain/Sources/Blockchain/VMInvocations/HostCall/HostCalls.swift b/Blockchain/Sources/Blockchain/VMInvocations/HostCall/HostCalls.swift index 504ebef4..cc10ff5d 100644 --- a/Blockchain/Sources/Blockchain/VMInvocations/HostCall/HostCalls.swift +++ b/Blockchain/Sources/Blockchain/VMInvocations/HostCall/HostCalls.swift @@ -427,8 +427,8 @@ public class Transfer: HostCall { } public func gasCost(state: VMState) -> Gas { - let (reg8, reg9): (UInt32, UInt32) = state.readRegister(Registers.Index(raw: 8), Registers.Index(raw: 9)) - return Gas(10) + Gas(reg8) + Gas(0x1_0000_0000) * Gas(reg9) + let reg9: UInt64 = state.readRegister(Registers.Index(raw: 9)) + return Gas(10) + Gas(reg9) } public func _callImpl(config: ProtocolConfigRef, state: VMState) async throws { @@ -454,8 +454,6 @@ public class Transfer: HostCall { state.writeRegister(Registers.Index(raw: 7), HostCallResultCode.WHO.rawValue) } else if gasLimit < destAcc!.minOnTransferGas { state.writeRegister(Registers.Index(raw: 7), HostCallResultCode.LOW.rawValue) - } else if Gas(state.getGas()) < gasLimit { - state.writeRegister(Registers.Index(raw: 7), HostCallResultCode.HIGH.rawValue) } else if let acc, acc.balance - amount < acc.thresholdBalance(config: config) { state.writeRegister(Registers.Index(raw: 7), HostCallResultCode.CASH.rawValue) } else if var acc { diff --git a/Blockchain/Sources/Blockchain/VMInvocations/HostCall/ResultConstants.swift b/Blockchain/Sources/Blockchain/VMInvocations/HostCall/ResultConstants.swift index 7372f85c..8b67a35e 100644 --- a/Blockchain/Sources/Blockchain/VMInvocations/HostCall/ResultConstants.swift +++ b/Blockchain/Sources/Blockchain/VMInvocations/HostCall/ResultConstants.swift @@ -15,8 +15,6 @@ public enum HostCallResultCode: UInt64 { case CASH = 0xFFFF_FFFF_FFFF_FFF9 /// LOW = 2^64 − 8: Gas limit too low. case LOW = 0xFFFF_FFFF_FFFF_FFF8 - /// HIGH = 2^64 − 9: Gas limit too high. - case HIGH = 0xFFFF_FFFF_FFFF_FFF7 /// HUH = 2^64 − 10: The item is already solicited or cannot be forgotten. case HUH = 0xFFFF_FFFF_FFFF_FFF6 /// OK = 0: The return value indicating general success. diff --git a/Node/Package.resolved b/Node/Package.resolved index 21e00dc6..88a24089 100644 --- a/Node/Package.resolved +++ b/Node/Package.resolved @@ -1,5 +1,5 @@ { - "originHash" : "e6fc7ac1513fbfe8e482dc8b89ef5388fcc4ffb7a67e2def60644a806029f64e", + "originHash" : "fd50b22dcf258877cc79f49fe7cb855a71ce6974bd1a2732a13b0d3e49c057fc", "pins" : [ { "identity" : "async-channels", @@ -46,6 +46,15 @@ "version" : "4.14.3" } }, + { + "identity" : "lrucache", + "kind" : "remoteSourceControl", + "location" : "https://github.com/nicklockwood/LRUCache.git", + "state" : { + "revision" : "542f0449556327415409ededc9c43a4bd0a397dc", + "version" : "1.0.7" + } + }, { "identity" : "multipart-kit", "kind" : "remoteSourceControl", diff --git a/Node/Tests/NodeTests/chainfiles/devnet_allconfig_spec.json b/Node/Tests/NodeTests/chainfiles/devnet_allconfig_spec.json index 0146bf4c..ca787347 100644 --- a/Node/Tests/NodeTests/chainfiles/devnet_allconfig_spec.json +++ b/Node/Tests/NodeTests/chainfiles/devnet_allconfig_spec.json @@ -36,9 +36,9 @@ "totalNumberOfCores" : 1, "totalNumberOfValidators" : 3, "transferMemoSize" : 128, - "workPackageAuthorizerGas" : 10000000, - "workPackageRefineGas" : 10000000, - "totalAccumulationGas": 35000000, + "workPackageAuthorizerGas" : 50000000, + "workPackageRefineGas" : 5000000000, + "totalAccumulationGas": 3500000000, "maxDepsInWorkReport": 8 }, "state" : {},