diff --git a/Blockchain/Sources/Blockchain/Blockchain.swift b/Blockchain/Sources/Blockchain/Blockchain.swift index 0892785a..4bf85000 100644 --- a/Blockchain/Sources/Blockchain/Blockchain.swift +++ b/Blockchain/Sources/Blockchain/Blockchain.swift @@ -9,10 +9,12 @@ public final class Blockchain: Sendable { public let config: ProtocolConfigRef private let dataProvider: BlockchainDataProvider + private let timeProvider: TimeProvider - public init(config: ProtocolConfigRef, dataProvider: BlockchainDataProvider) async { + public init(config: ProtocolConfigRef, dataProvider: BlockchainDataProvider, timeProvider: TimeProvider) async { self.config = config self.dataProvider = dataProvider + self.timeProvider = timeProvider } public func importBlock(_ block: BlockRef) async throws { @@ -21,7 +23,8 @@ public final class Blockchain: Sendable { let runtime = Runtime(config: config) let parent = try await dataProvider.getState(hash: block.header.parentHash) - let state = try runtime.apply(block: block, state: parent) + let timeslot = timeProvider.getTime() / UInt32(config.value.slotPeriodSeconds) + let state = try runtime.apply(block: block, state: parent, context: .init(timeslot: timeslot)) try await dataProvider.add(state: state) } } diff --git a/Blockchain/Sources/Blockchain/Runtime.swift b/Blockchain/Sources/Blockchain/Runtime.swift index a5397147..90e73244 100644 --- a/Blockchain/Sources/Blockchain/Runtime.swift +++ b/Blockchain/Sources/Blockchain/Runtime.swift @@ -5,6 +5,15 @@ public final class Runtime { public enum Error: Swift.Error { case safroleError(SafroleError) case invalidValidatorEd25519Key + case invalidTimeslot + } + + public struct ApplyContext { + public let timeslot: TimeslotIndex + + public init(timeslot: TimeslotIndex) { + self.timeslot = timeslot + } } public let config: ProtocolConfigRef @@ -13,7 +22,18 @@ public final class Runtime { self.config = config } - public func apply(block: BlockRef, state prevState: StateRef) throws(Error) -> StateRef { + public func validate(block: BlockRef, state _: StateRef, context: ApplyContext) throws(Error) { + guard context.timeslot >= block.header.timeslotIndex else { + throw Error.invalidTimeslot + } + + // TODO: validate block.header.seal + // TODO: abstract input validation logic from Safrole state update function and call it here + } + + public func apply(block: BlockRef, state prevState: StateRef, context: ApplyContext) throws(Error) -> StateRef { + try validate(block: block, state: prevState, context: context) + var newState = prevState.value newState.lastBlock = block diff --git a/Blockchain/Sources/Blockchain/TimeProvider.swift b/Blockchain/Sources/Blockchain/TimeProvider.swift new file mode 100644 index 00000000..6279a5a8 --- /dev/null +++ b/Blockchain/Sources/Blockchain/TimeProvider.swift @@ -0,0 +1,35 @@ +import Foundation + +public protocol TimeProvider: Sendable { + func getTime() -> UInt32 +} + +public struct SystemTimeProvider: TimeProvider { + public init() {} + + public func getTime() -> UInt32 { + Date().timeIntervalSinceJamCommonEra + } +} + +public struct FixedTimeProvider: TimeProvider { + private let time: UInt32 + + public init(time: UInt32) { + self.time = time + } + + public func getTime() -> UInt32 { + time + } +} + +extension Date { + public var timeIntervalSinceJamCommonEra: UInt32 { + // the Jam Common Era: 1200 UTC on January 1, 2024 + // number of seconds since the Unix epoch + let beginning = 1_704_110_400.0 + let now = timeIntervalSince1970 + return UInt32(now - beginning) + } +} diff --git a/Node/Sources/Node/Node.swift b/Node/Sources/Node/Node.swift index 9dee611b..9b2d5465 100644 --- a/Node/Sources/Node/Node.swift +++ b/Node/Sources/Node/Node.swift @@ -25,7 +25,8 @@ public class Node { let genesisState = try genesis.toState(config: config.protcol) let dataProvider = await InMemoryDataProvider(genesis: genesisState) - blockchain = await Blockchain(config: config.protcol, dataProvider: dataProvider) + let timeProvider = SystemTimeProvider() + blockchain = await Blockchain(config: config.protcol, dataProvider: dataProvider, timeProvider: timeProvider) rpcServer = try Server(config: config.rpc, source: blockchain) }