From 5458dbf67cbd7571327e9a470aca7767d669cd3f Mon Sep 17 00:00:00 2001 From: Bryan Chen Date: Mon, 6 Jan 2025 21:36:33 +1300 Subject: [PATCH] tests for authorization --- .../RuntimeProtocols/Authorization.swift | 92 +++++++++++++++++++ .../Blockchain/RuntimeProtocols/Runtime.swift | 52 +++-------- .../Sources/Blockchain/State/State.swift | 6 ++ .../Tests/JAMTests/AuthorizationsTests.swift | 79 ++++++++++++++++ 4 files changed, 188 insertions(+), 41 deletions(-) create mode 100644 Blockchain/Sources/Blockchain/RuntimeProtocols/Authorization.swift create mode 100644 JAMTests/Tests/JAMTests/AuthorizationsTests.swift diff --git a/Blockchain/Sources/Blockchain/RuntimeProtocols/Authorization.swift b/Blockchain/Sources/Blockchain/RuntimeProtocols/Authorization.swift new file mode 100644 index 00000000..2c2e5817 --- /dev/null +++ b/Blockchain/Sources/Blockchain/RuntimeProtocols/Authorization.swift @@ -0,0 +1,92 @@ +import Utils + +public enum AuthorizationError: Error { + case invalidReportAuthorizer + case emptyAuthorizationQueue +} + +public struct AuthorizationPostState: Sendable, Equatable { + public var coreAuthorizationPool: ConfigFixedSizeArray< + ConfigLimitedSizeArray< + Data32, + ProtocolConfig.Int0, + ProtocolConfig.MaxAuthorizationsPoolItems + >, + ProtocolConfig.TotalNumberOfCores + > + + public init( + coreAuthorizationPool: ConfigFixedSizeArray< + ConfigLimitedSizeArray< + Data32, + ProtocolConfig.Int0, + ProtocolConfig.MaxAuthorizationsPoolItems + >, + ProtocolConfig.TotalNumberOfCores + > + ) { + self.coreAuthorizationPool = coreAuthorizationPool + } +} + +public protocol Authorization { + var coreAuthorizationPool: ConfigFixedSizeArray< + ConfigLimitedSizeArray< + Data32, + ProtocolConfig.Int0, + ProtocolConfig.MaxAuthorizationsPoolItems + >, + ProtocolConfig.TotalNumberOfCores + > { get } + + var authorizationQueue: ConfigFixedSizeArray< + ConfigFixedSizeArray< + Data32, + ProtocolConfig.MaxAuthorizationsQueueItems + >, + ProtocolConfig.TotalNumberOfCores + > { get } + + mutating func mergeWith(postState: AuthorizationPostState) +} + +extension Authorization { + public func update( + config _: ProtocolConfigRef, + timeslot: TimeslotIndex, + auths: [(core: CoreIndex, auth: Data32)] + ) throws -> AuthorizationPostState { + var pool = coreAuthorizationPool + + // Create lookup for core authorizations + let authsByCoreIndex = Dictionary(grouping: auths) { $0.core } + + for coreIndex in 0 ..< pool.count { + var corePool = pool[coreIndex] + let coreQueue = authorizationQueue[coreIndex] + + if coreQueue.isEmpty { + continue + } + + let newItem = coreQueue[Int(timeslot) % coreQueue.count] + + // Remove used authorizers from pool + if let coreAuths = authsByCoreIndex[CoreIndex(coreIndex)] { + for (_, auth) in coreAuths { + if let idx = corePool.firstIndex(of: auth) { + _ = try corePool.remove(at: idx) + } else { + throw AuthorizationError.invalidReportAuthorizer + } + } + } + + // Add new item from queue + corePool.safeAppend(newItem) + pool[coreIndex] = corePool + } + + return AuthorizationPostState(coreAuthorizationPool: pool) + } +} diff --git a/Blockchain/Sources/Blockchain/RuntimeProtocols/Runtime.swift b/Blockchain/Sources/Blockchain/RuntimeProtocols/Runtime.swift index 0483e721..6801bcdc 100644 --- a/Blockchain/Sources/Blockchain/RuntimeProtocols/Runtime.swift +++ b/Blockchain/Sources/Blockchain/RuntimeProtocols/Runtime.swift @@ -11,7 +11,7 @@ public final class Runtime { case safroleError(SafroleError) case disputesError(DisputesError) case invalidTimeslot(got: TimeslotIndex, context: TimeslotIndex) - case invalidReportAuthorizer + case authorizationError(AuthorizationError) case encodeError(any Swift.Error) case invalidExtrinsicHash case invalidParentHash(state: Data32, header: Data32) @@ -179,9 +179,16 @@ public final class Runtime { prevTimeslot: prevState.value.timeslot ) - newState.coreAuthorizationPool = try updateAuthorizationPool( - block: block, state: prevState - ) + do { + let authorizationResult = try newState.update( + config: config, + timeslot: block.header.timeslot, + auths: block.extrinsic.reports.guarantees.map { ($0.workReport.coreIndex, $0.workReport.authorizerHash) } + ) + newState.mergeWith(postState: authorizationResult) + } catch let error as AuthorizationError { + throw Error.authorizationError(error) + } newState.activityStatistics = try updateValidatorActivityStatistics( block: block, state: prevState @@ -304,43 +311,6 @@ public final class Runtime { } } - // TODO: add tests - public func updateAuthorizationPool(block: BlockRef, state: StateRef) throws -> ConfigFixedSizeArray< - ConfigLimitedSizeArray< - Data32, - ProtocolConfig.Int0, - ProtocolConfig.MaxAuthorizationsPoolItems - >, - ProtocolConfig.TotalNumberOfCores - > { - var pool = state.value.coreAuthorizationPool - - for coreIndex in 0 ..< pool.count { - var corePool = pool[coreIndex] - let coreQueue = state.value.authorizationQueue[coreIndex] - if coreQueue.count == 0 { - continue - } - let newItem = coreQueue[Int(block.header.timeslot) % coreQueue.count] - - // remove used authorizers from pool - for report in block.extrinsic.reports.guarantees { - let authorizer = report.workReport.authorizerHash - if let idx = corePool.firstIndex(of: authorizer) { - _ = try corePool.remove(at: idx) - } else { - throw Error.invalidReportAuthorizer - } - } - - // add new item from queue - corePool.safeAppend(newItem) - pool[coreIndex] = corePool - } - - return pool - } - // returns available reports public func updateReports(block: BlockRef, state newState: inout State) throws -> [WorkReport] { let ( diff --git a/Blockchain/Sources/Blockchain/State/State.swift b/Blockchain/Sources/Blockchain/State/State.swift index 836e79dd..8947bc4c 100644 --- a/Blockchain/Sources/Blockchain/State/State.swift +++ b/Blockchain/Sources/Blockchain/State/State.swift @@ -463,6 +463,12 @@ extension State: Guaranteeing { } } +extension State: Authorization { + public mutating func mergeWith(postState: AuthorizationPostState) { + coreAuthorizationPool = postState.coreAuthorizationPool + } +} + struct DummyFunction: AccumulateFunction, OnTransferFunction { func invoke( config _: ProtocolConfigRef, diff --git a/JAMTests/Tests/JAMTests/AuthorizationsTests.swift b/JAMTests/Tests/JAMTests/AuthorizationsTests.swift new file mode 100644 index 00000000..0c976571 --- /dev/null +++ b/JAMTests/Tests/JAMTests/AuthorizationsTests.swift @@ -0,0 +1,79 @@ +import Blockchain +import Codec +import Foundation +import Testing +import Utils + +@testable import JAMTests + +struct CoreAuthorizer: Codable { + var core: CoreIndex + var auth: Data32 +} + +struct AuthorizationsInput: Codable { + var slot: TimeslotIndex + var auths: [CoreAuthorizer] +} + +struct AuthorizationsState: Equatable, Codable, Authorization { + var coreAuthorizationPool: ConfigFixedSizeArray< + ConfigLimitedSizeArray< + Data32, + ProtocolConfig.Int0, + ProtocolConfig.MaxAuthorizationsPoolItems + >, + ProtocolConfig.TotalNumberOfCores + > + + var authorizationQueue: ConfigFixedSizeArray< + ConfigFixedSizeArray< + Data32, + ProtocolConfig.MaxAuthorizationsQueueItems + >, + ProtocolConfig.TotalNumberOfCores + > + + mutating func mergeWith(postState: AuthorizationPostState) { + coreAuthorizationPool = postState.coreAuthorizationPool + } +} + +struct AuthorizationsTestcase: Codable { + var input: AuthorizationsInput + var preState: AuthorizationsState + var postState: AuthorizationsState +} + +struct AuthorizationsTests { + static func loadTests(variant: TestVariants) throws -> [Testcase] { + try TestLoader.getTestcases(path: "authorizations/\(variant)", extension: "bin") + } + + func authorizationsTests(_ testcase: Testcase, variant: TestVariants) throws { + let config = variant.config + let decoder = JamDecoder(data: testcase.data, config: config) + let testcase = try decoder.decode(AuthorizationsTestcase.self) + + var state = testcase.preState + let result = try state.update( + config: config, + timeslot: testcase.input.slot, + auths: testcase.input.auths.map { ($0.core, $0.auth) } + ) + + state.mergeWith(postState: result) + + #expect(state == testcase.postState) + } + + @Test(arguments: try AuthorizationsTests.loadTests(variant: .tiny)) + func tinyTests(_ testcase: Testcase) throws { + try authorizationsTests(testcase, variant: .tiny) + } + + @Test(arguments: try AuthorizationsTests.loadTests(variant: .full)) + func fullTests(_ testcase: Testcase) throws { + try authorizationsTests(testcase, variant: .full) + } +}