diff --git a/Blockchain/Sources/Blockchain/BlockchainDataProvider/InMemoryDataProvider.swift b/Blockchain/Sources/Blockchain/BlockchainDataProvider/InMemoryDataProvider.swift index 8f43b916..fadcba39 100644 --- a/Blockchain/Sources/Blockchain/BlockchainDataProvider/InMemoryDataProvider.swift +++ b/Blockchain/Sources/Blockchain/BlockchainDataProvider/InMemoryDataProvider.swift @@ -70,7 +70,7 @@ extension InMemoryDataProvider: BlockchainDataProvider { public func add(block: BlockRef) { blockByHash[block.hash] = block - hashByTimeslot[block.header.timeslotIndex, default: Set()].insert(block.hash) + hashByTimeslot[block.header.timeslot, default: Set()].insert(block.hash) } public func setFinalizedHead(hash: Data32) { @@ -85,7 +85,7 @@ extension InMemoryDataProvider: BlockchainDataProvider { } public func remove(hash: Data32) { - let timeslot = blockByHash[hash]?.header.timeslotIndex ?? stateByBlockHash[hash]?.value.timeslot + let timeslot = blockByHash[hash]?.header.timeslot ?? stateByBlockHash[hash]?.value.timeslot stateByBlockHash.removeValue(forKey: hash) if let timeslot { diff --git a/Blockchain/Sources/Blockchain/Runtime.swift b/Blockchain/Sources/Blockchain/Runtime.swift index 14e08be7..58efb687 100644 --- a/Blockchain/Sources/Blockchain/Runtime.swift +++ b/Blockchain/Sources/Blockchain/Runtime.swift @@ -1,3 +1,4 @@ +import Codec import Utils // the STF @@ -6,6 +7,12 @@ public final class Runtime { case safroleError(SafroleError) case invalidTimeslot case invalidReportAuthorizer + case unableToComputeExtrinsicHash(any Swift.Error) + case invalidExtrinsicHash + case invalidParentHash + case invalidHeaderStateRoot + case invalidHeaderEpochMarker + case invalidHeaderWinningTickets case other(any Swift.Error) } @@ -23,12 +30,39 @@ public final class Runtime { self.config = config } - public func validate(block: BlockRef, state _: StateRef, context: ApplyContext) throws(Error) { - guard context.timeslot >= block.header.timeslotIndex else { + public func validateHeader(block: BlockRef, state: StateRef, context: ApplyContext) throws(Error) { + guard block.header.parentHash == state.value.lastBlockHash else { + throw Error.invalidParentHash + } + + guard block.header.priorStateRoot == state.stateRoot else { + throw Error.invalidHeaderStateRoot + } + + let expectedExtrinsicHash = try Result { try blake2b256(JamEncoder.encode(block.extrinsic)) } + .mapError(Error.unableToComputeExtrinsicHash).get() + + guard block.header.extrinsicsHash == expectedExtrinsicHash else { + throw Error.invalidExtrinsicHash + } + + guard block.header.timeslot <= context.timeslot else { throw Error.invalidTimeslot } + // epoch is validated at apply time + + // winning tickets is validated at apply time + + // TODO: validate judgementsMarkers + // TODO: validate offendersMarkers + // TODO: validate block.header.seal + } + + public func validate(block: BlockRef, state: StateRef, context: ApplyContext) throws(Error) { + try validateHeader(block: block, state: state, context: context) + // TODO: abstract input validation logic from Safrole state update function and call it here // TODO: validate other things } @@ -41,10 +75,18 @@ public final class Runtime { do { newState.recentHistory = try updateRecentHistory(block: block, state: prevState) - let res = try newState.updateSafrole( - config: config, slot: block.header.timeslotIndex, entropy: newState.entropyPool.t0, extrinsics: block.extrinsic.tickets + let safroleResult = try newState.updateSafrole( + config: config, slot: block.header.timeslot, entropy: newState.entropyPool.t0, extrinsics: block.extrinsic.tickets ) - newState.mergeWith(postState: res.state) + newState.mergeWith(postState: safroleResult.state) + + guard safroleResult.epochMark == block.header.epoch else { + throw Error.invalidHeaderEpochMarker + } + + guard safroleResult.ticketsMark == block.header.winningTickets else { + throw Error.invalidHeaderWinningTickets + } newState.coreAuthorizationPool = try updateAuthorizationPool( block: block, state: prevState @@ -102,7 +144,7 @@ public final class Runtime { if coreQueue.count == 0 { continue } - let newItem = coreQueue[Int(block.header.timeslotIndex) % coreQueue.count] + let newItem = coreQueue[Int(block.header.timeslot) % coreQueue.count] // remove used authorizers from pool for report in block.extrinsic.reports.guarantees { @@ -126,7 +168,7 @@ public final class Runtime { public func updateValidatorActivityStatistics(block: BlockRef, state: StateRef) throws -> ValidatorActivityStatistics { let epochLength = UInt32(config.value.epochLength) let currentEpoch = state.value.timeslot / epochLength - let newEpoch = block.header.timeslotIndex / epochLength + let newEpoch = block.header.timeslot / epochLength let isEpochChange = currentEpoch != newEpoch var acc = try isEpochChange diff --git a/Blockchain/Sources/Blockchain/Types/Header.swift b/Blockchain/Sources/Blockchain/Types/Header.swift index db799f21..ab50c5aa 100644 --- a/Blockchain/Sources/Blockchain/Types/Header.swift +++ b/Blockchain/Sources/Blockchain/Types/Header.swift @@ -10,10 +10,10 @@ public struct Header: Sendable, Equatable, Codable { public var priorStateRoot: Data32 // state root of the after parent block execution // Hx: extrinsic hash - public var extrinsicsRoot: Data32 + public var extrinsicsHash: Data32 // Ht: timeslot index - public var timeslotIndex: TimeslotIndex + public var timeslot: TimeslotIndex // He: the epoch // the header’s epoch marker He is either empty or, if the block is the first in a new epoch, @@ -47,8 +47,8 @@ public struct Header: Sendable, Equatable, Codable { public init( parentHash: Data32, priorStateRoot: Data32, - extrinsicsRoot: Data32, - timeslotIndex: TimeslotIndex, + extrinsicsHash: Data32, + timeslot: TimeslotIndex, epoch: EpochMarker?, winningTickets: ConfigFixedSizeArray< Ticket, @@ -61,8 +61,8 @@ public struct Header: Sendable, Equatable, Codable { ) { self.parentHash = parentHash self.priorStateRoot = priorStateRoot - self.extrinsicsRoot = extrinsicsRoot - self.timeslotIndex = timeslotIndex + self.extrinsicsHash = extrinsicsHash + self.timeslot = timeslot self.epoch = epoch self.winningTickets = winningTickets self.judgementsMarkers = judgementsMarkers @@ -97,8 +97,8 @@ extension Header.Unsigned: Dummy { Header.Unsigned( parentHash: Data32(), priorStateRoot: Data32(), - extrinsicsRoot: Data32(), - timeslotIndex: 0, + extrinsicsHash: Data32(), + timeslot: 0, epoch: nil, winningTickets: nil, judgementsMarkers: [], @@ -130,8 +130,8 @@ extension Header { public var parentHash: Data32 { unsigned.parentHash } public var priorStateRoot: Data32 { unsigned.priorStateRoot } - public var extrinsicsRoot: Data32 { unsigned.extrinsicsRoot } - public var timeslotIndex: TimeslotIndex { unsigned.timeslotIndex } + public var extrinsicsHash: Data32 { unsigned.extrinsicsHash } + public var timeslot: TimeslotIndex { unsigned.timeslot } public var epoch: EpochMarker? { unsigned.epoch } public var winningTickets: ConfigFixedSizeArray? { unsigned.winningTickets } public var judgementsMarkers: [Data32] { unsigned.judgementsMarkers }