Skip to content

Commit

Permalink
tests for authorization (#261)
Browse files Browse the repository at this point in the history
  • Loading branch information
xlc authored Jan 6, 2025
1 parent 8960041 commit 7c063ea
Show file tree
Hide file tree
Showing 4 changed files with 188 additions and 41 deletions.
92 changes: 92 additions & 0 deletions Blockchain/Sources/Blockchain/RuntimeProtocols/Authorization.swift
Original file line number Diff line number Diff line change
@@ -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)
}
}
52 changes: 11 additions & 41 deletions Blockchain/Sources/Blockchain/RuntimeProtocols/Runtime.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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 (
Expand Down
6 changes: 6 additions & 0 deletions Blockchain/Sources/Blockchain/State/State.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
79 changes: 79 additions & 0 deletions JAMTests/Tests/JAMTests/AuthorizationsTests.swift
Original file line number Diff line number Diff line change
@@ -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)
}
}

0 comments on commit 7c063ea

Please sign in to comment.