Skip to content
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
34 changes: 19 additions & 15 deletions Blockchain/Sources/Blockchain/RuntimeProtocols/Assurances.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,29 @@ public protocol Assurances {
}

extension Assurances {
public func validateAssurances(
extrinsics: ExtrinsicAvailability,
parentHash: Data32
) throws {
for assurance in extrinsics.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
}
}
}

public func update(
config: ProtocolConfigRef,
timeslot: TimeslotIndex,
extrinsic: ExtrinsicAvailability,
parentHash: Data32
) throws -> (
newReports: ConfigFixedSizeArray<
ReportItem?,
Expand All @@ -45,20 +63,6 @@ extension Assurances {
}
}

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 {
Expand Down
51 changes: 30 additions & 21 deletions Blockchain/Sources/Blockchain/RuntimeProtocols/Guaranteeing.swift
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,36 @@ extension Guaranteeing {
.map { StateKeys.ServiceAccountKey(index: $0.serviceIndex) }
}

public func validateGuarantees(
config: ProtocolConfigRef,
extrinsic: ExtrinsicGuarantees
) async throws(GuaranteeingError) {
for guarantee in extrinsic.guarantees {
var totalGasUsage = Gas(0)
let report = guarantee.workReport

for digest in report.digests {
guard let acc = try? await serviceAccount(index: digest.serviceIndex) else {
throw .invalidServiceIndex
}

guard acc.codeHash == digest.codeHash else {
throw .invalidResultCodeHash
}

guard digest.gasLimit >= acc.minAccumlateGas else {
throw .invalidServiceGas
}

totalGasUsage += digest.gasLimit
}

guard totalGasUsage <= config.value.workReportAccumulationGas else {
throw .outOfGas
}
}
}

public func update(
config: ProtocolConfigRef,
timeslot: TimeslotIndex,
Expand Down Expand Up @@ -120,7 +150,6 @@ extension Guaranteeing {
var reporters = Set<Ed25519PublicKey>()

for guarantee in extrinsic.guarantees {
var totalGasUsage = Gas(0)
let report = guarantee.workReport

guard guarantee.timeslot <= timeslot else {
Expand Down Expand Up @@ -162,26 +191,6 @@ extension Guaranteeing {
guard coreAuthorizationPool[coreIndex].contains(report.authorizerHash) else {
throw .invalidReportAuthorizer
}

for digest in report.digests {
guard let acc = try? await serviceAccount(index: digest.serviceIndex) else {
throw .invalidServiceIndex
}

guard acc.codeHash == digest.codeHash else {
throw .invalidResultCodeHash
}

guard digest.gasLimit >= acc.minAccumlateGas else {
throw .invalidServiceGas
}

totalGasUsage += digest.gasLimit
}

guard totalGasUsage <= config.value.workReportAccumulationGas else {
throw .outOfGas
}
}

let recentWorkPackageHashes: Set<Data32> = Set(recentHistory.items.flatMap(\.lookup.keys))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,6 @@ extension Preimages {
let preimages = preimages.preimages
var updates: [PreimageUpdate] = []

guard preimages.isSortedAndUnique() else {
throw PreimagesError.preimagesNotSorted
}

for preimage in preimages {
let hash = preimage.data.blake2b256hash()

Expand Down
48 changes: 26 additions & 22 deletions Blockchain/Sources/Blockchain/RuntimeProtocols/Runtime.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ public final class Runtime {
public enum Error: Swift.Error {
case safroleError(SafroleError)
case disputesError(DisputesError)
case invalidTimeslot(got: TimeslotIndex, context: TimeslotIndex)
case invalidTimeslot(got: TimeslotIndex, exp: TimeslotIndex)
case authorizationError(AuthorizationError)
case encodeError(any Swift.Error)
case invalidExtrinsicHash
Expand All @@ -19,18 +19,13 @@ public final class Runtime {
case invalidHeaderEpochMarker
case invalidHeaderWinningTickets
case invalidHeaderOffendersMarkers
case invalidAssuranceParentHash
case invalidAssuranceSignature
case assuranceForEmptyCore
case preimagesNotSorted
case invalidPreimageServiceIndex
case duplicatedPreimage
case invalidAuthorTicket
case invalidAuthorKey
case invalidBlockSeal(any Swift.Error)
case invalidVrfSignature
case other(any Swift.Error)
case validateError(any Swift.Error)
case headerTimeslotTooSmall
}

public struct ApplyContext {
Expand Down Expand Up @@ -65,7 +60,7 @@ public final class Runtime {
}

guard block.header.timeslot <= context.timeslot else {
throw Error.invalidTimeslot(got: block.header.timeslot, context: context.timeslot)
throw Error.invalidTimeslot(got: block.header.timeslot, exp: context.timeslot)
}

// epoch is validated at apply time by Safrole
Expand Down Expand Up @@ -126,27 +121,37 @@ public final class Runtime {
}.mapError { _ in Error.invalidVrfSignature }.get()
}

public func validate(block: Validated<BlockRef>, state: StateRef, context: ApplyContext) throws(Error) {
public func validate(block: Validated<BlockRef>, state: StateRef, context: ApplyContext) async throws(Error) {
try validateHeader(block: block, state: state, context: context)

let block = block.value
let extrinsic = block.extrinsic

for ext in block.extrinsic.availability.assurances {
guard ext.parentHash == block.header.parentHash else {
throw Error.invalidAssuranceParentHash
}
// NOTE: extrinsic validations not related to header or state are not here, but in their own Validate impl
do {
// tickets
try state.value.validateTickets(config: config, slot: block.header.timeslot, extrinsics: extrinsic.tickets)
// assurances
try state.value.validateAssurances(extrinsics: extrinsic.availability, parentHash: block.header.parentHash)
// guarantees
try await state.value.validateGuarantees(config: config, extrinsic: extrinsic.reports)
} catch {
throw Error.validateError(error)
}

// TODO: abstract input validation logic from Safrole state update function and call it here
// TODO: validate other things
}

public func apply(block: BlockRef, state prevState: StateRef, context: ApplyContext) async throws(Error) -> StateRef {
public func apply(block: BlockRef, state prevState: StateRef, context: ApplyContext? = nil) async throws(Error) -> StateRef {
let validatedBlock = try Result(catching: { try block.toValidated(config: config) })
.mapError(Error.validateError)
.get()

try validate(block: validatedBlock, state: prevState, context: context)
let prevStateRoot = await prevState.value.stateRoot

try await validate(
block: validatedBlock,
state: prevState,
context: context ?? .init(timeslot: block.value.header.timeslot, stateRoot: prevStateRoot)
)

return try await apply(block: validatedBlock, state: prevState)
}
Expand Down Expand Up @@ -187,9 +192,9 @@ public final class Runtime {

newState.recentHistory.updatePartial(parentStateRoot: block.header.priorStateRoot)

let reporters = try await updateGuarantees(block: block, state: &newState)
let reporters = try await updateReports(block: block, state: &newState)

// after reports as it need old recent history
// after reports as reports need old recent history
try updateRecentHistory(block: block, state: &newState, accumulateRoot: accumulateRoot)

// update authorization pool and queue
Expand Down Expand Up @@ -279,14 +284,13 @@ public final class Runtime {
config: config,
timeslot: block.header.timeslot,
extrinsic: block.extrinsic.availability,
parentHash: block.header.parentHash
)

newState.reports = newReports
return availableReports
}

public func updateGuarantees(block: BlockRef, state newState: inout State) async throws -> [Ed25519PublicKey] {
public func updateReports(block: BlockRef, state newState: inout State) async throws -> [Ed25519PublicKey] {
let result = try await newState.update(
config: config, timeslot: newState.timeslot, extrinsic: block.extrinsic.reports
)
Expand Down
34 changes: 20 additions & 14 deletions Blockchain/Sources/Blockchain/RuntimeProtocols/Safrole.swift
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,26 @@ func withoutOffenders(
}

extension Safrole {
public func validateTickets(
config: ProtocolConfigRef,
slot: TimeslotIndex,
extrinsics: ExtrinsicTickets,
) throws(SafroleError) {
guard slot > timeslot else {
throw .invalidTimeslot
}

if (slot % UInt32(config.value.epochLength)) < UInt32(config.value.ticketSubmissionEndSlot) {
guard extrinsics.tickets.count <= config.value.maxTicketsPerExtrinsic else {
throw .tooManyExtrinsics
}
} else {
guard extrinsics.tickets.isEmpty else {
throw .extrinsicsNotAllowed
}
}
}

public func updateSafrole(
config: ProtocolConfigRef,
slot: TimeslotIndex,
Expand Down Expand Up @@ -230,20 +250,6 @@ extension Safrole {
let newPhase = slot % epochLength
let isEpochChange = currentEpoch != newEpoch

guard slot > timeslot else {
throw .invalidTimeslot
}

if newPhase < ticketSubmissionEndSlot {
guard extrinsics.tickets.count <= config.value.maxTicketsPerExtrinsic else {
throw .tooManyExtrinsics
}
} else {
guard extrinsics.tickets.isEmpty else {
throw .extrinsicsNotAllowed
}
}

do {
let ctx = try Bandersnatch.RingContext(size: UInt(config.value.totalNumberOfValidators))
let commitment = try Bandersnatch.RingCommitment(data: ticketsVerifier)
Expand Down
12 changes: 12 additions & 0 deletions Blockchain/Sources/Blockchain/Types/ExtrinsicPreimages.swift
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,15 @@ extension ExtrinsicPreimages.PreimageItem: Comparable {
return lhs.data.lexicographicallyPrecedes(rhs.data)
}
}

extension ExtrinsicPreimages: Validate {
public enum Error: Swift.Error {
case preimagesNotSorted
}

public func validate(config _: Config) throws {
guard preimages.isSortedAndUnique() else {
throw Error.preimagesNotSorted
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1008,6 +1008,8 @@ public class Upgrade: HostCall {

let codeHash: Data32? = try? Data32(state.readMemory(address: regs[0], length: 32))

logger.debug("new codeHash: \(codeHash?.description ?? "nil")")

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A debug log has been added. Please remove this temporary logging before merging.


if let codeHash, var acc = try await x.state.accounts.value.get(serviceAccount: x.serviceIndex) {
acc.codeHash = codeHash
acc.minAccumlateGas = Gas(regs[1])
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ public func accumulate(
return .init(state: state, transfers: [], commitment: nil, gasUsed: Gas(0), provide: [])
}

let initialIndex = try Blake2b256.hash(JamEncoder.encode(serviceIndex, state.entropy, timeslot)).data.decode(UInt32.self)
let initialIndex = try Blake2b256.hash(JamEncoder.encode(UInt(serviceIndex), state.entropy, UInt(timeslot))).data.decode(UInt32.self)
let nextAccountIndex = try await AccumulateContext.check(
i: (initialIndex % serviceIndexModValue) + 256,
accounts: state.accounts.toRef()
Expand Down
2 changes: 1 addition & 1 deletion Fuzzing/Sources/Fuzzing/FuzzingClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ public class FuzzingClient {
)

// import block locally
currentStateRef = try await runtime.apply(block: blockRef.toValidated(config: config), state: currentStateRef!)
currentStateRef = try await runtime.apply(block: blockRef, state: currentStateRef!)
let currentStateRoot = await currentStateRef!.value.stateRoot

// import block on target
Expand Down
20 changes: 14 additions & 6 deletions Fuzzing/Sources/Fuzzing/FuzzingTarget.swift
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,21 @@ public class FuzzingTarget {
public func run() async throws {
try socket.create()

let connection = try socket.acceptConnection()
logger.info("Fuzzing target listening for connections")

try await handleFuzzer(connection: connection)
while true {
do {
logger.info("Waiting for new connection")
let connection = try socket.acceptConnection()

try await handleFuzzer(connection: connection)

connection.close()
logger.info("Connection closed")
connection.close()
logger.info("Connection closed, waiting for next connection")
} catch {
logger.error("Error handling connection: \(error)")
}
}
}

private func handleFuzzer(connection: FuzzingSocketConnection) async throws {
Expand Down Expand Up @@ -91,8 +100,7 @@ public class FuzzingTarget {
do {
guard let stateRef = currentStateRef else { throw FuzzingTargetError.stateNotSet }

let blockRef = try block.asRef().toValidated(config: config)
let newStateRef = try await runtime.apply(block: blockRef, state: stateRef)
let newStateRef = try await runtime.apply(block: block.asRef(), state: stateRef)

currentStateRef = newStateRef

Expand Down
7 changes: 3 additions & 4 deletions JAMTests/Sources/JAMTests/JamTestnet.swift
Original file line number Diff line number Diff line change
Expand Up @@ -66,12 +66,11 @@ enum JamTestnet {
config: ProtocolConfigRef = TestVariants.tiny.config
) async throws -> Result<StateRef, Error> {
let runtime = Runtime(config: config)
let blockRef = testcase.block.asRef()
let stateRef = try await testcase.preState.toState(config: config).asRef()

let result = await Result {
// NOTE: skip block validate first, some tests does not ensure this
let blockRef = try testcase.block.asRef().toValidated(config: config)
let stateRef = try await testcase.preState.toState(config: config).asRef()
return try await runtime.apply(block: blockRef, state: stateRef)
try await runtime.apply(block: blockRef, state: stateRef)
}

return result
Expand Down
Loading