Skip to content

Commit

Permalink
disputests tests
Browse files Browse the repository at this point in the history
  • Loading branch information
xlc committed Dec 20, 2024
1 parent 69e1558 commit 7c89cf5
Show file tree
Hide file tree
Showing 4 changed files with 135 additions and 24 deletions.
67 changes: 47 additions & 20 deletions Blockchain/Sources/Blockchain/RuntimeProtocols/Disputes.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import Utils

public enum DisputeError: Error {
public enum DisputesError: Error {
case invalidEpoch
case invalidValidatorIndex
case invalidJudgementSignature
Expand All @@ -11,6 +11,8 @@ public enum DisputeError: Error {
case expectInFaults
case expectInCulprits
case invalidPublicKey
case invalidFaults
case invalidCulprit
}

public struct ReportItem: Sendable, Equatable, Codable {
Expand All @@ -30,7 +32,7 @@ extension ReportItem: Validate {
public typealias Config = ProtocolConfigRef
}

public struct DisputePostState: Sendable, Equatable {
public struct DisputesPostState: Sendable, Equatable {
public var judgements: JudgementsState
public var reports: ConfigFixedSizeArray<
ReportItem?,
Expand Down Expand Up @@ -63,17 +65,17 @@ public protocol Disputes {
ValidatorKey, ProtocolConfig.TotalNumberOfValidators
> { get }

func update(config: ProtocolConfigRef, disputes: ExtrinsicDisputes) throws(DisputeError) -> (
state: DisputePostState,
func update(config: ProtocolConfigRef, disputes: ExtrinsicDisputes) throws(DisputesError) -> (
state: DisputesPostState,
offenders: [Ed25519PublicKey]
)

mutating func mergeWith(postState: DisputePostState)
mutating func mergeWith(postState: DisputesPostState)
}

extension Disputes {
public func update(config: ProtocolConfigRef, disputes: ExtrinsicDisputes) throws(DisputeError) -> (
state: DisputePostState,
public func update(config: ProtocolConfigRef, disputes: ExtrinsicDisputes) throws(DisputesError) -> (
state: DisputesPostState,
offenders: [Ed25519PublicKey]
) {
var newJudgements = judgements
Expand Down Expand Up @@ -101,7 +103,7 @@ extension Disputes {
let prefix = judgement.isValid ? SigningContext.valid : SigningContext.invalid
let payload = prefix + verdict.reportHash.data
let pubkey = try Result { try Ed25519.PublicKey(from: signer) }
.mapError { _ in DisputeError.invalidPublicKey }
.mapError { _ in DisputesError.invalidPublicKey }
.get()
guard pubkey.verify(signature: judgement.signature, message: payload) else {
throw .invalidJudgementSignature
Expand Down Expand Up @@ -151,23 +153,39 @@ extension Disputes {
let two_third_plus_one_validators = config.value.totalNumberOfValidators * 2 / 3 + 1
for (hash, vote) in votes {
if vote == 0 {
// any verdict containing solely valid judgements
// implies the same report having at least one valid entry in the faults sequence f
guard disputes.faults.contains(where: { $0.reportHash == hash }) else {
throw .expectInFaults
// Any verdict containing solely invalid judgements
// implies the same report having at least two valid entries in the culprits sequence c
let culprits = disputes.culprits.filter { $0.reportHash == hash }
guard culprits.count >= 2 else {
throw .expectInCulprits
}

tobeRemoved.insert(hash)
newJudgements.banSet.insert(hash)

let faults = disputes.faults.filter { $0.reportHash == hash }
for fault in faults {
// check faults are indeed invalid
guard fault.vote else {
throw .invalidFaults
}

Check warning on line 171 in Blockchain/Sources/Blockchain/RuntimeProtocols/Disputes.swift

View check run for this annotation

Codecov / codecov/patch

Blockchain/Sources/Blockchain/RuntimeProtocols/Disputes.swift#L168-L171

Added lines #L168 - L171 were not covered by tests
}
} else if vote == third_validators {
// wonky
tobeRemoved.insert(hash)
newJudgements.wonkySet.insert(hash)
} else if vote == two_third_plus_one_validators {
// Any verdict containing solely invalid judgements
// implies the same report having at least two valid entries in the culprits sequence c
guard disputes.culprits.count(where: { $0.reportHash == hash }) >= 2 else {
throw .expectInCulprits
// any verdict containing solely valid judgements
// implies the same report having at least one valid entry in the faults sequence f
let faults = disputes.faults.filter { $0.reportHash == hash }
guard faults.count >= 1 else {
throw .expectInFaults
}
for fault in faults {
// check faults are indeed invalid
guard !fault.vote else {
throw .invalidFaults
}
}

newJudgements.goodSet.insert(hash)
Expand All @@ -176,6 +194,12 @@ extension Disputes {
}
}

for culprit in disputes.culprits {
guard newJudgements.banSet.contains(culprit.reportHash) else {
throw .invalidCulprit
}
}

for i in 0 ..< newReports.count {
if let report = newReports[i]?.workReport {
let hash = report.hash()
Expand All @@ -185,9 +209,12 @@ extension Disputes {
}
}

return (state: DisputePostState(
judgements: newJudgements,
reports: newReports
), offenders: offenders)
return (
state: DisputesPostState(
judgements: newJudgements,
reports: newReports
),
offenders: offenders
)
}
}
6 changes: 3 additions & 3 deletions Blockchain/Sources/Blockchain/RuntimeProtocols/Runtime.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ private let logger = Logger(label: "Runtime")
public final class Runtime {
public enum Error: Swift.Error {
case safroleError(SafroleError)
case DisputeError(DisputeError)
case disputesError(DisputesError)
case invalidTimeslot(got: TimeslotIndex, context: TimeslotIndex)
case invalidReportAuthorizer
case encodeError(any Swift.Error)
Expand Down Expand Up @@ -195,8 +195,8 @@ public final class Runtime {
throw error
} catch let error as SafroleError {
throw .safroleError(error)
} catch let error as DisputeError {
throw .DisputeError(error)
} catch let error as DisputesError {
throw .disputesError(error)

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

View check run for this annotation

Codecov / codecov/patch

Blockchain/Sources/Blockchain/RuntimeProtocols/Runtime.swift#L198-L199

Added lines #L198 - L199 were not covered by tests
} catch {
throw .other(error)
}
Expand Down
2 changes: 1 addition & 1 deletion Blockchain/Sources/Blockchain/State/State.swift
Original file line number Diff line number Diff line change
Expand Up @@ -447,7 +447,7 @@ extension State: Safrole {
extension State: Assurances {}

extension State: Disputes {
public mutating func mergeWith(postState: DisputePostState) {
public mutating func mergeWith(postState: DisputesPostState) {
judgements = postState.judgements
reports = postState.reports
}
Expand Down
84 changes: 84 additions & 0 deletions JAMTests/Tests/JAMTests/DisputesTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import Blockchain
import Codec
import Foundation
import Testing
import Utils

@testable import JAMTests

struct DisputesState: Equatable, Codable, Disputes {
var judgements: JudgementsState
var reports: ConfigFixedSizeArray<
ReportItem?,
ProtocolConfig.TotalNumberOfCores
>
var timeslot: TimeslotIndex
var currentValidators: ConfigFixedSizeArray<
ValidatorKey, ProtocolConfig.TotalNumberOfValidators
>
var previousValidators: ConfigFixedSizeArray<
ValidatorKey, ProtocolConfig.TotalNumberOfValidators
>

mutating func mergeWith(postState: DisputesPostState) {
judgements = postState.judgements
reports = postState.reports
}
}

struct DisputesTestcase: Codable {
var input: ExtrinsicDisputes
var preState: DisputesState
var output: Either<[Ed25519PublicKey], UInt8>
var postState: DisputesState
}

struct DisputesTests {
static func loadTests(variant: TestVariants) throws -> [Testcase] {
try TestLoader.getTestcases(path: "disputes/\(variant)", extension: "bin")
}

func disputesTests(_ testcase: Testcase, variant: TestVariants) throws {
let config = variant.config
let decoder = JamDecoder(data: testcase.data, config: config)
let testcase = try decoder.decode(DisputesTestcase.self)

var state = testcase.preState
let result = Result {
try testcase.input.validate(config: config)
return try state.update(
config: config,
disputes: testcase.input
)
}
switch result {
case let .success((postState, offenders)):
switch testcase.output {
case let .left(output):
state.mergeWith(postState: postState)
#expect(state == testcase.postState)
#expect(offenders == output)
case .right:
Issue.record("Expected error, got \(result)")

Check warning on line 62 in JAMTests/Tests/JAMTests/DisputesTests.swift

View check run for this annotation

Codecov / codecov/patch

JAMTests/Tests/JAMTests/DisputesTests.swift#L62

Added line #L62 was not covered by tests
}
case .failure:
switch testcase.output {
case .left:
Issue.record("Expected success, got \(result)")

Check warning on line 67 in JAMTests/Tests/JAMTests/DisputesTests.swift

View check run for this annotation

Codecov / codecov/patch

JAMTests/Tests/JAMTests/DisputesTests.swift#L67

Added line #L67 was not covered by tests
case .right:
// ignore error code because it is unspecified
break
}
}
}

@Test(arguments: try DisputesTests.loadTests(variant: .tiny))
func tinyTests(_ testcase: Testcase) throws {
try disputesTests(testcase, variant: .tiny)
}

@Test(arguments: try DisputesTests.loadTests(variant: .full))
func fullTests(_ testcase: Testcase) throws {
try disputesTests(testcase, variant: .full)
}
}

0 comments on commit 7c89cf5

Please sign in to comment.