Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

tests for authorization #261

Merged
merged 1 commit into from
Jan 6, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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

Check warning on line 69 in Blockchain/Sources/Blockchain/RuntimeProtocols/Authorization.swift

View check run for this annotation

Codecov / codecov/patch

Blockchain/Sources/Blockchain/RuntimeProtocols/Authorization.swift#L69

Added line #L69 was not covered by tests
}

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

Check warning on line 80 in Blockchain/Sources/Blockchain/RuntimeProtocols/Authorization.swift

View check run for this annotation

Codecov / codecov/patch

Blockchain/Sources/Blockchain/RuntimeProtocols/Authorization.swift#L80

Added line #L80 was not covered by tests
}
}
}

// 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 @@
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 @@
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)

Check warning on line 190 in Blockchain/Sources/Blockchain/RuntimeProtocols/Runtime.swift

View check run for this annotation

Codecov / codecov/patch

Blockchain/Sources/Blockchain/RuntimeProtocols/Runtime.swift#L190

Added line #L190 was not covered by tests
}

newState.activityStatistics = try updateValidatorActivityStatistics(
block: block, state: prevState
Expand Down Expand Up @@ -304,43 +311,6 @@
}
}

// 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)
}
}