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

Bump testvectors #253

Merged
merged 4 commits into from
Dec 19, 2024
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
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 @@
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 @@

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

Check warning on line 35 in Blockchain/Sources/Blockchain/Types/WorkItem.swift

View check run for this annotation

Codecov / codecov/patch

Blockchain/Sources/Blockchain/Types/WorkItem.swift#L34-L35

Added lines #L34 - L35 were not covered by tests
}
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)

Check warning on line 44 in Blockchain/Sources/Blockchain/Types/WorkItem.swift

View check run for this annotation

Codecov / codecov/patch

Blockchain/Sources/Blockchain/Types/WorkItem.swift#L44

Added line #L44 was not covered by tests
}
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)")

Check warning on line 69 in JAMTests/Tests/JAMTests/AssurancesTests.swift

View check run for this annotation

Codecov / codecov/patch

JAMTests/Tests/JAMTests/AssurancesTests.swift#L69

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

Check warning on line 74 in JAMTests/Tests/JAMTests/AssurancesTests.swift

View check run for this annotation

Codecov / codecov/patch

JAMTests/Tests/JAMTests/AssurancesTests.swift#L74

Added line #L74 was not covered by tests
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