From 22e15e025d15e8b8db1002667c90f1a3a1d13a8c Mon Sep 17 00:00:00 2001 From: Xiliang Chen Date: Sat, 17 Aug 2024 22:28:19 +1200 Subject: [PATCH] implement core authorization pool stf (#62) --- .swiftlint.yml | 1 + Blockchain/Sources/Blockchain/Runtime.swift | 70 +++++++++++++++++-- Blockchain/Sources/Blockchain/Safrole.swift | 22 +++--- .../Types/ExtrinsicAvailability.swift | 2 +- .../Types/ExtrinsicGuarantees.swift | 2 +- .../Blockchain/Types/ExtrinsicTickets.swift | 2 +- .../Blockchain/Types/SafroleState.swift | 2 +- .../Sources/Blockchain/Types/State.swift | 2 +- .../Types/ValidatorActivityStatistics.swift | 4 +- .../Sources/Blockchain/Types/WorkReport.swift | 2 +- .../Utils/ConfigLimitedSizeArray.swift | 68 +++++++++++++----- Utils/Sources/Utils/merklization.swift | 4 +- 12 files changed, 138 insertions(+), 43 deletions(-) diff --git a/.swiftlint.yml b/.swiftlint.yml index ac56da13..763651ea 100644 --- a/.swiftlint.yml +++ b/.swiftlint.yml @@ -10,6 +10,7 @@ disabled_rules: - type_body_length - identifier_name - function_parameter_count + - force_try excluded: - "**/.build" diff --git a/Blockchain/Sources/Blockchain/Runtime.swift b/Blockchain/Sources/Blockchain/Runtime.swift index 90e73244..7fa91749 100644 --- a/Blockchain/Sources/Blockchain/Runtime.swift +++ b/Blockchain/Sources/Blockchain/Runtime.swift @@ -6,6 +6,8 @@ public final class Runtime { case safroleError(SafroleError) case invalidValidatorEd25519Key case invalidTimeslot + case invalidReportAuthorizer + case other(any Swift.Error) } public struct ApplyContext { @@ -47,21 +49,79 @@ public final class Runtime { throw .safroleError(err) } - newState.activityStatistics = updateValidatorActivityStatistics(block: block, state: prevState) + do { + newState.coreAuthorizationPool = try updateAuthorizationPool( + block: block, state: prevState + ) + + newState.activityStatistics = try updateValidatorActivityStatistics( + block: block, state: prevState + ) + } catch let error as Error { + throw error + } catch { + throw .other(error) + } return StateRef(newState) } // TODO: add tests - public func updateValidatorActivityStatistics(block: BlockRef, state: StateRef) -> ValidatorActivityStatistics { + 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.timeslotIndex) % 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 + if corePool.count < corePool.maxLength { + try corePool.append(newItem) + } else { + try corePool.mutate { + $0.remove(at: 0) + $0.append(newItem) + } + } + pool[coreIndex] = corePool + } + + return pool + } + + // TODO: add tests + public func updateValidatorActivityStatistics(block: BlockRef, state: StateRef) throws -> ValidatorActivityStatistics { let epochLength = UInt32(config.value.epochLength) let currentEpoch = state.value.timeslot / epochLength let newEpoch = block.header.timeslotIndex / epochLength let isEpochChange = currentEpoch != newEpoch - var acc = isEpochChange ? ConfigFixedSizeArray<_, ProtocolConfig.TotalNumberOfValidators>( - config: config, defaultValue: ValidatorActivityStatistics.StatisticsItem.dummy(config: config) - ) : state.value.activityStatistics.accumulator + var acc = try isEpochChange + ? ConfigFixedSizeArray<_, ProtocolConfig.TotalNumberOfValidators>( + config: config, + defaultValue: ValidatorActivityStatistics.StatisticsItem.dummy(config: config) + ) : state.value.activityStatistics.accumulator let prev = isEpochChange ? state.value.activityStatistics.accumulator : state.value.activityStatistics.previous diff --git a/Blockchain/Sources/Blockchain/Safrole.swift b/Blockchain/Sources/Blockchain/Safrole.swift index 5b56015d..e4ace260 100644 --- a/Blockchain/Sources/Blockchain/Safrole.swift +++ b/Blockchain/Sources/Blockchain/Safrole.swift @@ -13,8 +13,8 @@ public enum SafroleError: Error { case extrinsicsTooManyEntry case hashingError case bandersnatchError(BandersnatchError) - case decodingError - case unspecified + case decodingError(DecodingError) + case other(any Swift.Error) } public struct SafrolePostState: Sendable, Equatable { @@ -264,7 +264,7 @@ extension Safrole { currentPhase >= ticketSubmissionEndSlot, ticketsAccumulator.count == config.value.epochLength { - .left(ConfigFixedSizeArray(config: config, array: outsideInReorder(ticketsAccumulator.array))) + try .left(ConfigFixedSizeArray(config: config, array: outsideInReorder(ticketsAccumulator.array))) } else if newEpoch == currentEpoch { ticketsOrKeys } else { @@ -278,7 +278,7 @@ extension Safrole { )) } - let epochMark = isEpochChange ? EpochMarker( + let epochMark = try isEpochChange ? EpochMarker( entropy: newEntropyPool.1, validators: ConfigFixedSizeArray(config: config, array: newNextValidators.map(\.bandersnatch)) ) : nil @@ -288,7 +288,7 @@ extension Safrole { currentPhase < ticketSubmissionEndSlot, ticketSubmissionEndSlot <= newPhase, ticketsAccumulator.count == config.value.epochLength { - ConfigFixedSizeArray( + try ConfigFixedSizeArray( config: config, array: outsideInReorder(ticketsAccumulator.array) ) } else { @@ -330,7 +330,9 @@ extension Safrole { newTicketsAccumulatorArr.removeLast(newTicketsAccumulatorArr.count - config.value.epochLength) } - let newTicketsAccumulator = ConfigLimitedSizeArray( + let newTicketsAccumulator = try ConfigLimitedSizeArray< + Ticket, ProtocolConfig.Int0, ProtocolConfig.EpochLength + >( config: config, array: newTicketsAccumulatorArr ) @@ -353,12 +355,10 @@ extension Safrole { return .failure(.bandersnatchError(e)) } catch Blake2Error.hashingError { return .failure(.hashingError) - } catch is DecodingError { - // TODO: log details - return .failure(.decodingError) + } catch let e as DecodingError { + return .failure(.decodingError(e)) } catch { - // TODO: log details - return .failure(.unspecified) + return .failure(.other(error)) } } } diff --git a/Blockchain/Sources/Blockchain/Types/ExtrinsicAvailability.swift b/Blockchain/Sources/Blockchain/Types/ExtrinsicAvailability.swift index 13e30408..3df08737 100644 --- a/Blockchain/Sources/Blockchain/Types/ExtrinsicAvailability.swift +++ b/Blockchain/Sources/Blockchain/Types/ExtrinsicAvailability.swift @@ -44,7 +44,7 @@ public struct ExtrinsicAvailability: Sendable, Equatable { extension ExtrinsicAvailability: Dummy { public typealias Config = ProtocolConfigRef public static func dummy(config: Config) -> ExtrinsicAvailability { - ExtrinsicAvailability(assurances: ConfigLimitedSizeArray(config: config)) + try! ExtrinsicAvailability(assurances: ConfigLimitedSizeArray(config: config)) } } diff --git a/Blockchain/Sources/Blockchain/Types/ExtrinsicGuarantees.swift b/Blockchain/Sources/Blockchain/Types/ExtrinsicGuarantees.swift index 822e5ab5..855e4673 100644 --- a/Blockchain/Sources/Blockchain/Types/ExtrinsicGuarantees.swift +++ b/Blockchain/Sources/Blockchain/Types/ExtrinsicGuarantees.swift @@ -60,7 +60,7 @@ public struct ExtrinsicGuarantees: Sendable, Equatable { extension ExtrinsicGuarantees: Dummy { public typealias Config = ProtocolConfigRef public static func dummy(config: Config) -> ExtrinsicGuarantees { - ExtrinsicGuarantees(guarantees: ConfigLimitedSizeArray(config: config)) + try! ExtrinsicGuarantees(guarantees: ConfigLimitedSizeArray(config: config)) } } diff --git a/Blockchain/Sources/Blockchain/Types/ExtrinsicTickets.swift b/Blockchain/Sources/Blockchain/Types/ExtrinsicTickets.swift index 8398663f..c60f67f4 100644 --- a/Blockchain/Sources/Blockchain/Types/ExtrinsicTickets.swift +++ b/Blockchain/Sources/Blockchain/Types/ExtrinsicTickets.swift @@ -36,7 +36,7 @@ public struct ExtrinsicTickets: Sendable, Equatable { extension ExtrinsicTickets: Dummy { public typealias Config = ProtocolConfigRef public static func dummy(config: Config) -> ExtrinsicTickets { - ExtrinsicTickets(tickets: ConfigLimitedSizeArray(config: config)) + ExtrinsicTickets(tickets: try! ConfigLimitedSizeArray(config: config)) } } diff --git a/Blockchain/Sources/Blockchain/Types/SafroleState.swift b/Blockchain/Sources/Blockchain/Types/SafroleState.swift index d2e808a2..1451fc32 100644 --- a/Blockchain/Sources/Blockchain/Types/SafroleState.swift +++ b/Blockchain/Sources/Blockchain/Types/SafroleState.swift @@ -60,7 +60,7 @@ public struct SafroleState: Sendable, Equatable { extension SafroleState: Dummy { public typealias Config = ProtocolConfigRef public static func dummy(config: Config) -> SafroleState { - SafroleState( + try! SafroleState( nextValidators: ConfigFixedSizeArray(config: config, defaultValue: ValidatorKey.dummy(config: config)), ticketsVerifier: BandersnatchRingVRFRoot(), ticketsOrKeys: .right(ConfigFixedSizeArray(config: config, defaultValue: BandersnatchPublicKey())), diff --git a/Blockchain/Sources/Blockchain/Types/State.swift b/Blockchain/Sources/Blockchain/Types/State.swift index 830b23aa..fec94ad3 100644 --- a/Blockchain/Sources/Blockchain/Types/State.swift +++ b/Blockchain/Sources/Blockchain/Types/State.swift @@ -173,7 +173,7 @@ extension State: Equatable { extension State: Dummy { public typealias Config = ProtocolConfigRef public static func dummy(config: Config) -> State { - State( + try! State( config: config, coreAuthorizationPool: ConfigFixedSizeArray(config: config, defaultValue: ConfigLimitedSizeArray(config: config)), lastBlock: BlockRef.dummy(config: config), diff --git a/Blockchain/Sources/Blockchain/Types/ValidatorActivityStatistics.swift b/Blockchain/Sources/Blockchain/Types/ValidatorActivityStatistics.swift index 7050528d..116aec35 100644 --- a/Blockchain/Sources/Blockchain/Types/ValidatorActivityStatistics.swift +++ b/Blockchain/Sources/Blockchain/Types/ValidatorActivityStatistics.swift @@ -43,10 +43,10 @@ extension ValidatorActivityStatistics: Dummy { public typealias Config = ProtocolConfigRef public static func dummy(config: Config) -> ValidatorActivityStatistics { ValidatorActivityStatistics( - accumulator: ConfigFixedSizeArray( + accumulator: try! ConfigFixedSizeArray( config: config, defaultValue: StatisticsItem.dummy(config: config) ), - previous: ConfigFixedSizeArray( + previous: try! ConfigFixedSizeArray( config: config, defaultValue: StatisticsItem.dummy(config: config) ) ) diff --git a/Blockchain/Sources/Blockchain/Types/WorkReport.swift b/Blockchain/Sources/Blockchain/Types/WorkReport.swift index fd85d718..6982c885 100644 --- a/Blockchain/Sources/Blockchain/Types/WorkReport.swift +++ b/Blockchain/Sources/Blockchain/Types/WorkReport.swift @@ -49,7 +49,7 @@ extension WorkReport: Dummy { output: Data(), refinementContext: RefinementContext.dummy(config: config), packageSpecification: AvailabilitySpecifications.dummy(config: config), - results: ConfigLimitedSizeArray(config: config, defaultValue: WorkResult.dummy(config: config)) + results: try! ConfigLimitedSizeArray(config: config, defaultValue: WorkResult.dummy(config: config)) ) } } diff --git a/Utils/Sources/Utils/ConfigLimitedSizeArray.swift b/Utils/Sources/Utils/ConfigLimitedSizeArray.swift index 7b1b1a73..feba2920 100644 --- a/Utils/Sources/Utils/ConfigLimitedSizeArray.swift +++ b/Utils/Sources/Utils/ConfigLimitedSizeArray.swift @@ -2,6 +2,14 @@ import ScaleCodec // TODO: add tests +public enum ConfigLimitedSizeArrayError: Swift.Error { + case tooManyElements + case tooFewElements + case invalidMinLength + case invalidMaxLength + case invalidIndex +} + public struct ConfigLimitedSizeArray where TMinLength.TConfig == TMaxLength.TConfig { @@ -10,43 +18,56 @@ public struct ConfigLimitedSizeArray= 0) - assert(maxLength >= minLength) + private init(_ array: [T], minLength: Int, maxLength: Int) throws(ConfigLimitedSizeArrayError) { + guard minLength >= 0 else { + throw ConfigLimitedSizeArrayError.invalidMinLength + } + guard maxLength >= minLength else { + throw ConfigLimitedSizeArrayError.invalidMaxLength + } self.array = array self.minLength = minLength self.maxLength = maxLength - validate() + try validateThrowing() } private func validate() { assert(array.count >= minLength, "count \(array.count) >= minLength \(minLength)") assert(array.count <= maxLength, "count \(array.count) <= maxLength \(maxLength)") } + + private func validateThrowing() throws(ConfigLimitedSizeArrayError) { + guard array.count >= minLength else { + throw ConfigLimitedSizeArrayError.tooFewElements + } + guard array.count <= maxLength else { + throw ConfigLimitedSizeArrayError.tooManyElements + } + } } extension ConfigLimitedSizeArray: Equatable where T: Equatable {} @@ -119,19 +140,32 @@ extension ConfigLimitedSizeArray: RandomAccessCollection { } extension ConfigLimitedSizeArray { - public mutating func append(_ newElement: T) { + public mutating func append(_ newElement: T) throws(ConfigLimitedSizeArrayError) { 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(ConfigLimitedSizeArrayError) { + if i < 0 || i > array.count { + throw ConfigLimitedSizeArrayError.invalidIndex + } array.insert(newElement, at: i) - validate() + try validateThrowing() + } + + public mutating func remove(at i: Int) throws -> T { + if i < 0 || i >= array.count { + throw ConfigLimitedSizeArrayError.invalidIndex + } + let res = array.remove(at: i) + try validateThrowing() + return res } - public mutating func remove(at i: Int) -> T { - defer { validate() } - return array.remove(at: i) + public mutating func mutate(_ fn: (inout [T]) -> R) throws(ConfigLimitedSizeArrayError) -> R { + let ret = fn(&array) + try validateThrowing() + return ret } } diff --git a/Utils/Sources/Utils/merklization.swift b/Utils/Sources/Utils/merklization.swift index 3c16c855..5dd057a8 100644 --- a/Utils/Sources/Utils/merklization.swift +++ b/Utils/Sources/Utils/merklization.swift @@ -35,9 +35,9 @@ public func stateMerklize(kv: [Data32: Data], i: Int = 0) throws -> Data32 { func leaf(key: Data32, value: Data) throws -> Data64 { if value.count <= 32 { - return embeddedLeaf(key: key, value: value, size: UInt8(value.count)) + embeddedLeaf(key: key, value: value, size: UInt8(value.count)) } else { - return try regularLeaf(key: key, value: value) + try regularLeaf(key: key, value: value) } }