Skip to content

Commit

Permalink
Bump testvectors (#253)
Browse files Browse the repository at this point in the history
* codec tests passing

* pass safrole

* fix

* assurances tests
  • Loading branch information
xlc authored Dec 19, 2024
1 parent 1a3e704 commit 1e86e55
Show file tree
Hide file tree
Showing 16 changed files with 289 additions and 82 deletions.
2 changes: 1 addition & 1 deletion .swiftlint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ line_length:
ignores_function_declarations: true

function_body_length:
warning: 200
warning: 300
error: 500

redundant_void_return:
Expand Down
40 changes: 40 additions & 0 deletions Blockchain/Sources/Blockchain/Config/ProtocolConfig+Preset.swift
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,46 @@ extension Ref where T == ProtocolConfig {
pvmMemoryPageSize: 1 << 12
))

public static let tiny = Ref(ProtocolConfig(
auditTranchePeriod: 8,
additionalMinBalancePerStateItem: 10,
additionalMinBalancePerStateByte: 1,
serviceMinBalance: 100,
totalNumberOfCores: 2,
preimagePurgePeriod: 28800,
epochLength: 12,
auditBiasFactor: 2,
coreAccumulationGas: Gas(100_000),
workPackageAuthorizerGas: Gas(1_000_000),
workPackageRefineGas: Gas(500_000_000),
totalAccumulationGas: Gas(341_000_000),
recentHistorySize: 8,
maxWorkItems: 4,
maxDepsInWorkReport: 8,
maxTicketsPerExtrinsic: 3,
maxLookupAnchorAge: 14400,
transferMemoSize: 128,
ticketEntriesPerValidator: 3,
maxAuthorizationsPoolItems: 8,
slotPeriodSeconds: 6,
maxAuthorizationsQueueItems: 80,
coreAssignmentRotationPeriod: 10,
maxServiceCodeSize: 4_000_000,
preimageReplacementPeriod: 5,
totalNumberOfValidators: 6,
erasureCodedPieceSize: 684,
maxWorkPackageManifestEntries: 1 << 11,
maxEncodedWorkPackageSize: 12 * 1 << 20,
segmentSize: 4104,
maxWorkReportOutputSize: 96 * 1 << 10,
erasureCodedSegmentSize: 6,
ticketSubmissionEndSlot: 10,
pvmDynamicAddressAlignmentFactor: 2,
pvmProgramInitInputDataSize: 1 << 24,
pvmProgramInitZoneSize: 1 << 16,
pvmMemoryPageSize: 1 << 12
))

public static let mainnet = Ref(ProtocolConfig(
auditTranchePeriod: 8,
additionalMinBalancePerStateItem: 10,
Expand Down
87 changes: 87 additions & 0 deletions Blockchain/Sources/Blockchain/RuntimeProtocols/Assurances.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import Utils

public enum AssurancesError: Error {
case invalidAssuranceSignature
case assuranceForEmptyCore
case invalidAssuranceParentHash
}

public protocol Assurances {
var reports:
ConfigFixedSizeArray<
ReportItem?,
ProtocolConfig.TotalNumberOfCores
>
{ get }

var currentValidators:
ConfigFixedSizeArray<
ValidatorKey,
ProtocolConfig.TotalNumberOfValidators
>
{ get }
}

extension Assurances {
public func update(
config: ProtocolConfigRef,
timeslot: TimeslotIndex,
extrinsic: ExtrinsicAvailability,
parentHash: Data32
) throws -> (
newReports: ConfigFixedSizeArray<
ReportItem?,
ProtocolConfig.TotalNumberOfCores
>,
availableReports: [WorkReport]
) {
var newReports = reports

for i in 0 ..< newReports.count {
if let report = newReports[i] {
if (report.timeslot + UInt32(config.value.preimageReplacementPeriod)) <= timeslot {
newReports[i] = nil
}
}
}

for assurance in extrinsic.assurances {
guard assurance.parentHash == parentHash else {
throw AssurancesError.invalidAssuranceParentHash
}

let hash = Blake2b256.hash(assurance.parentHash, assurance.assurance)
let payload = SigningContext.available + hash.data
let validatorKey = try currentValidators.at(Int(assurance.validatorIndex))
let pubkey = try Ed25519.PublicKey(from: validatorKey.ed25519)
guard pubkey.verify(signature: assurance.signature, message: payload) else {
throw AssurancesError.invalidAssuranceSignature
}
}

var availabilityCount = Array(repeating: 0, count: config.value.totalNumberOfCores)
for assurance in extrinsic.assurances {
for (coreIdx, bit) in assurance.assurance.enumerated() where bit {
// ExtrinsicAvailability.validate() ensures that validatorIndex is in range
availabilityCount[coreIdx] += 1
}
}

var availableReports = [WorkReport]()

for (idx, count) in availabilityCount.enumerated() where count > 0 {
guard let report = reports[idx] else {
throw AssurancesError.assuranceForEmptyCore
}
if count >= ProtocolConfig.TwoThirdValidatorsPlusOne.read(config: config) {
availableReports.append(report.workReport)
newReports[idx] = nil // remove available report from pending reports
}
}

return (
newReports: newReports,
availableReports: availableReports
)
}
}
38 changes: 9 additions & 29 deletions Blockchain/Sources/Blockchain/RuntimeProtocols/Runtime.swift
Original file line number Diff line number Diff line change
Expand Up @@ -343,36 +343,16 @@ public final class Runtime {

// returns available reports
public func updateReports(block: BlockRef, state newState: inout State) throws -> [WorkReport] {
for assurance in block.extrinsic.availability.assurances {
let hash = Blake2b256.hash(assurance.parentHash, assurance.assurance)
let payload = SigningContext.available + hash.data
let validatorKey = try newState.currentValidators.at(Int(assurance.validatorIndex))
let pubkey = try Ed25519.PublicKey(from: validatorKey.ed25519)
guard pubkey.verify(signature: assurance.signature, message: payload) else {
throw Error.invalidAssuranceSignature
}
}

var availabilityCount = Array(repeating: 0, count: config.value.totalNumberOfCores)
for assurance in block.extrinsic.availability.assurances {
for bit in assurance.assurance where bit {
// ExtrinsicAvailability.validate() ensures that validatorIndex is in range
availabilityCount[Int(assurance.validatorIndex)] += 1
}
}

var availableReports = [WorkReport]()

for (idx, count) in availabilityCount.enumerated() where count > 0 {
guard let report = newState.reports[idx] else {
throw Error.assuranceForEmptyCore
}
if count >= ProtocolConfig.TwoThirdValidatorsPlusOne.read(config: config) {
availableReports.append(report.workReport)
newState.reports[idx] = nil // remove available report from pending reports
}
}
let (
newReports: newReports, availableReports: availableReports
) = try newState.update(
config: config,
timeslot: block.header.timeslot,
extrinsic: block.extrinsic.availability,
parentHash: block.header.parentHash
)

newState.reports = newReports
newState.reports = try newState.update(config: config, extrinsic: block.extrinsic.reports)

return availableReports
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,7 @@ extension Safrole {

let epochMark = try isEpochChange ? EpochMarker(
entropy: newEntropyPool.1,
ticketsEntropy: newEntropyPool.2,
validators: ConfigFixedSizeArray(config: config, array: newNextValidators.map(\.bandersnatch))
) : nil

Expand Down
2 changes: 2 additions & 0 deletions Blockchain/Sources/Blockchain/State/State.swift
Original file line number Diff line number Diff line change
Expand Up @@ -444,6 +444,8 @@ extension State: Safrole {
}
}

extension State: Assurances {}

extension State: Disputes {
public mutating func mergeWith(postState: DisputePostState) {
judgements = postState.judgements
Expand Down
3 changes: 3 additions & 0 deletions Blockchain/Sources/Blockchain/Types/EpochMarker.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,22 @@ import Utils

public struct EpochMarker: Sendable, Equatable, Codable {
public var entropy: Data32
public var ticketsEntropy: Data32
public var validators: ConfigFixedSizeArray<
BandersnatchPublicKey,
ProtocolConfig.TotalNumberOfValidators
>

public init(
entropy: Data32,
ticketsEntropy: Data32,
validators: ConfigFixedSizeArray<
BandersnatchPublicKey,
ProtocolConfig.TotalNumberOfValidators
>
) {
self.entropy = entropy
self.ticketsEntropy = ticketsEntropy
self.validators = validators
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ extension ExtrinsicAvailability: Validate {
throw .assurancesNotSorted
}
for assurance in assurances {
guard assurance.validatorIndex < UInt32(config.value.totalNumberOfCores) else {
guard assurance.validatorIndex < UInt32(config.value.totalNumberOfValidators) else {
throw .invalidValidatorIndex
}
}
Expand Down
34 changes: 25 additions & 9 deletions Blockchain/Sources/Blockchain/Types/WorkItem.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@ public struct WorkItem: Sendable, Equatable, Codable {
case workPackageHash(Data32)
}

enum CodingKeys: String, CodingKey {
case root
case index
}

public var root: DataSegmentRootKind
public var index: UInt16

Expand All @@ -19,16 +24,27 @@ public struct WorkItem: Sendable, Equatable, Codable {

// Encodable
public func encode(to encoder: Encoder) throws {
var container = encoder.unkeyedContainer()
var indexValue = index
switch root {
case let .segmentRoot(root):
try container.encode(root)
case let .workPackageHash(hash):
try container.encode(hash)
indexValue |= 1 << 15
if encoder.isJamCodec {
var container = encoder.unkeyedContainer()
var indexValue = index
switch root {
case let .segmentRoot(root):
try container.encode(root)
case let .workPackageHash(hash):
try container.encode(hash)
indexValue |= 1 << 15
}
try container.encode(indexValue)
} else {
var container = encoder.container(keyedBy: CodingKeys.self)
switch root {
case let .segmentRoot(root):
try container.encode(root, forKey: .root)
case let .workPackageHash(hash):
try container.encode(hash, forKey: .root)
}
try container.encode(index, forKey: .index)
}
try container.encode(indexValue)
}

// Decodable
Expand Down
91 changes: 91 additions & 0 deletions JAMTests/Tests/JAMTests/AssurancesTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import Blockchain
import Codec
import Foundation
import Testing
import Utils

@testable import JAMTests

struct AssurancesInput: Codable {
var assurances: ExtrinsicAvailability
var timeslot: TimeslotIndex
var parentHash: Data32
}

struct AssuranceState: Equatable, Codable, Assurances {
var reports: ConfigFixedSizeArray<ReportItem?, ProtocolConfig.TotalNumberOfCores>
var currentValidators:
ConfigFixedSizeArray<ValidatorKey, ProtocolConfig.TotalNumberOfValidators>
}

struct AssurancesTestcase: Codable {
var input: AssurancesInput
var preState: AssuranceState
var output: Either<[WorkReport], UInt8>
var postState: AssuranceState
}

enum AssurancesTestVariants: String, CaseIterable {
case tiny
case full

var config: ProtocolConfigRef {
switch self {
case .tiny:
ProtocolConfigRef.tiny
case .full:
ProtocolConfigRef.mainnet
}
}
}

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

func assurancesTests(_ testcase: Testcase, variant: AssurancesTestVariants) throws {
let config = variant.config
let decoder = JamDecoder(data: testcase.data, config: config)
let testcase = try decoder.decode(AssurancesTestcase.self)

var state = testcase.preState
let result = Result {
try testcase.input.assurances.validate(config: config)
return try state.update(
config: config, timeslot: testcase.input.timeslot,
extrinsic: testcase.input.assurances,
parentHash: testcase.input.parentHash
)
}
switch result {
case let .success((newReports, availableReports)):
switch testcase.output {
case let .left(reports):
state.reports = newReports
#expect(state == testcase.postState)
#expect(availableReports == reports)
case .right:
Issue.record("Expected error, got \(result)")
}
case .failure:
switch testcase.output {
case .left:
Issue.record("Expected success, got \(result)")
case .right:
// ignore error code because it is unspecified
break
}
}
}

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

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

0 comments on commit 1e86e55

Please sign in to comment.