From 74aa89778f2678f864bea5b69b5f4146c3e86bd4 Mon Sep 17 00:00:00 2001 From: Xiliang Chen Date: Tue, 7 Jan 2025 00:16:10 +1300 Subject: [PATCH] ActivityStatistics test (#262) --- .../RuntimeProtocols/ActivityStatistics.swift | 56 ++++++++++++++++++ .../Blockchain/RuntimeProtocols/Runtime.swift | 45 ++------------ .../Sources/Blockchain/State/State.swift | 2 + JAMTests/Tests/JAMTests/StatisticsTests.swift | 58 +++++++++++++++++++ 4 files changed, 121 insertions(+), 40 deletions(-) create mode 100644 Blockchain/Sources/Blockchain/RuntimeProtocols/ActivityStatistics.swift create mode 100644 JAMTests/Tests/JAMTests/StatisticsTests.swift diff --git a/Blockchain/Sources/Blockchain/RuntimeProtocols/ActivityStatistics.swift b/Blockchain/Sources/Blockchain/RuntimeProtocols/ActivityStatistics.swift new file mode 100644 index 00000000..f20a2784 --- /dev/null +++ b/Blockchain/Sources/Blockchain/RuntimeProtocols/ActivityStatistics.swift @@ -0,0 +1,56 @@ +import Foundation +import Utils + +enum ActivityStatisticsError: Error { + case invalidAuthorKey +} + +public protocol ActivityStatistics { + var activityStatistics: ValidatorActivityStatistics { get } + var timeslot: TimeslotIndex { get } + var currentValidators: ConfigFixedSizeArray { get } +} + +extension ActivityStatistics { + public func update( + config: ProtocolConfigRef, + newTimeslot: TimeslotIndex, + extrinsic: Extrinsic, + authorIndex: ValidatorIndex + ) throws -> ValidatorActivityStatistics { + let epochLength = UInt32(config.value.epochLength) + let currentEpoch = timeslot / epochLength + let newEpoch = newTimeslot / epochLength + let isEpochChange = currentEpoch != newEpoch + + var acc = try isEpochChange + ? ConfigFixedSizeArray<_, ProtocolConfig.TotalNumberOfValidators>( + config: config, + defaultValue: ValidatorActivityStatistics.StatisticsItem.dummy(config: config) + ) : activityStatistics.accumulator + + let prev = isEpochChange ? activityStatistics.accumulator : activityStatistics.previous + + var item = acc[authorIndex] + item.blocks += 1 + item.tickets += UInt32(extrinsic.tickets.tickets.count) + item.preimages += UInt32(extrinsic.preimages.preimages.count) + item.preimagesBytes += UInt32(extrinsic.preimages.preimages.reduce(into: 0) { $0 += $1.data.count }) + acc[authorIndex] = item + + for report in extrinsic.reports.guarantees { + for cred in report.credential { + acc[cred.index].guarantees += 1 + } + } + + for assurance in extrinsic.availability.assurances { + acc[assurance.validatorIndex].assurances += 1 + } + + return ValidatorActivityStatistics( + accumulator: acc, + previous: prev + ) + } +} diff --git a/Blockchain/Sources/Blockchain/RuntimeProtocols/Runtime.swift b/Blockchain/Sources/Blockchain/RuntimeProtocols/Runtime.swift index 6801bcdc..b73bc051 100644 --- a/Blockchain/Sources/Blockchain/RuntimeProtocols/Runtime.swift +++ b/Blockchain/Sources/Blockchain/RuntimeProtocols/Runtime.swift @@ -190,8 +190,11 @@ public final class Runtime { throw Error.authorizationError(error) } - newState.activityStatistics = try updateValidatorActivityStatistics( - block: block, state: prevState + newState.activityStatistics = try prevState.value.update( + config: config, + newTimeslot: block.header.timeslot, + extrinsic: block.extrinsic, + authorIndex: block.header.authorIndex ) // after reports as it need old recent history @@ -367,42 +370,4 @@ public final class Runtime { ] = .init([newState.timeslot]) } } - - // 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.timeslot / epochLength - let isEpochChange = currentEpoch != newEpoch - - 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 - - var item = acc[block.header.authorIndex] - item.blocks += 1 - item.tickets += UInt32(block.extrinsic.tickets.tickets.count) - item.preimages += UInt32(block.extrinsic.preimages.preimages.count) - item.preimagesBytes += UInt32(block.extrinsic.preimages.preimages.reduce(into: 0) { $0 += $1.data.count }) - acc[block.header.authorIndex] = item - - for report in block.extrinsic.reports.guarantees { - for cred in report.credential { - acc[cred.index].guarantees += 1 - } - } - - for assurance in block.extrinsic.availability.assurances { - acc[assurance.validatorIndex].assurances += 1 - } - - return ValidatorActivityStatistics( - accumulator: acc, - previous: prev - ) - } } diff --git a/Blockchain/Sources/Blockchain/State/State.swift b/Blockchain/Sources/Blockchain/State/State.swift index 8947bc4c..ec58a373 100644 --- a/Blockchain/Sources/Blockchain/State/State.swift +++ b/Blockchain/Sources/Blockchain/State/State.swift @@ -469,6 +469,8 @@ extension State: Authorization { } } +extension State: ActivityStatistics {} + struct DummyFunction: AccumulateFunction, OnTransferFunction { func invoke( config _: ProtocolConfigRef, diff --git a/JAMTests/Tests/JAMTests/StatisticsTests.swift b/JAMTests/Tests/JAMTests/StatisticsTests.swift new file mode 100644 index 00000000..c6672095 --- /dev/null +++ b/JAMTests/Tests/JAMTests/StatisticsTests.swift @@ -0,0 +1,58 @@ +import Blockchain +import Codec +import Foundation +import Testing +import Utils + +@testable import JAMTests + +struct StatisticsState: Equatable, Codable, ActivityStatistics { + var activityStatistics: ValidatorActivityStatistics + var timeslot: TimeslotIndex + var currentValidators: ConfigFixedSizeArray +} + +struct StatisticsInput: Codable { + var timeslot: TimeslotIndex + var author: ValidatorIndex + var extrinsic: Extrinsic +} + +struct StatisticsTestcase: Codable { + var input: StatisticsInput + var preState: StatisticsState + var postState: StatisticsState +} + +struct StatisticsTests { + static func loadTests(variant: TestVariants) throws -> [Testcase] { + try TestLoader.getTestcases(path: "statistics/\(variant)", extension: "bin") + } + + func statisticsTests(_ testcase: Testcase, variant: TestVariants) throws { + let config = variant.config + let decoder = JamDecoder(data: testcase.data, config: config) + let testcase = try decoder.decode(StatisticsTestcase.self) + + var state = testcase.preState + let result = try state.update( + config: config, + newTimeslot: testcase.input.timeslot, + extrinsic: testcase.input.extrinsic, + authorIndex: testcase.input.author + ) + state.activityStatistics = result + + #expect(state == testcase.postState) + } + + @Test(arguments: try StatisticsTests.loadTests(variant: .tiny)) + func tinyTests(_ testcase: Testcase) throws { + try statisticsTests(testcase, variant: .tiny) + } + + @Test(arguments: try StatisticsTests.loadTests(variant: .full)) + func fullTests(_ testcase: Testcase) throws { + try statisticsTests(testcase, variant: .full) + } +}