Skip to content

Commit

Permalink
Merge branch 'master' of github.com:AcalaNetwork/boka
Browse files Browse the repository at this point in the history
* 'master' of github.com:AcalaNetwork/boka:
  Block author (#131)
  • Loading branch information
MacOMNI committed Sep 27, 2024
2 parents e7ee0c9 + 8f68016 commit 589a1b6
Show file tree
Hide file tree
Showing 45 changed files with 757 additions and 249 deletions.
3 changes: 0 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -105,9 +105,6 @@ jobs:
${{ runner.os }}-libs-libec
- name: Setup Swift
uses: SwiftyLab/setup-swift@latest
with:
swift-version: '6.0'
development: true
- name: Setup Rust
uses: dtolnay/rust-toolchain@nightly
with:
Expand Down
2 changes: 1 addition & 1 deletion Blockchain/Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -42,5 +42,5 @@ let package = Package(
]
),
],
swiftLanguageVersions: [.version("6")]
swiftLanguageModes: [.version("6")]
)
25 changes: 16 additions & 9 deletions Blockchain/Sources/Blockchain/Blockchain.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import Foundation
import TracingUtils
import Utils

private let logger = Logger(label: "Blockchain")

private struct BlockchainStorage: Sendable {
var bestHead: Data32?
var bestHeadTimeslot: TimeslotIndex?
Expand All @@ -11,24 +13,19 @@ private struct BlockchainStorage: Sendable {
/// Holds the state of the blockchain.
/// Includes the canonical chain as well as pending forks.
/// Assume all blocks and states are valid and have been validated.
public final class Blockchain: Sendable {
public let config: ProtocolConfigRef

public final class Blockchain: ServiceBase, @unchecked Sendable {
private let storage: ThreadSafeContainer<BlockchainStorage>
private let dataProvider: BlockchainDataProvider
private let timeProvider: TimeProvider
private let eventBus: EventBus

public init(
config: ProtocolConfigRef,
dataProvider: BlockchainDataProvider,
timeProvider: TimeProvider,
eventBus: EventBus
) async throws {
self.config = config
self.dataProvider = dataProvider
self.timeProvider = timeProvider
self.eventBus = eventBus

let heads = try await dataProvider.getHeads()
var bestHead: (HeaderRef, Data32)?
Expand All @@ -47,6 +44,16 @@ public final class Blockchain: Sendable {
bestHeadTimeslot: bestHead?.0.value.timeslot,
finalizedHead: finalizedHead
))

super.init(config, eventBus)

await subscribe(RuntimeEvents.BlockAuthored.self) { [weak self] event in
try await self?.on(blockAuthored: event)
}
}

private func on(blockAuthored event: RuntimeEvents.BlockAuthored) async throws {
try await importBlock(event.block)
}

public func importBlock(_ block: BlockRef) async throws {
Expand All @@ -61,13 +68,13 @@ public final class Blockchain: Sendable {

// update best head
if state.value.timeslot > storage.value.bestHeadTimeslot ?? 0 {
storage.mutate { storage in
storage.write { storage in
storage.bestHead = block.hash
storage.bestHeadTimeslot = state.value.timeslot
}
}

await eventBus.publish(RuntimeEvents.BlockImported(block: block, state: state, parentState: parent))
await publish(RuntimeEvents.BlockImported(block: block, state: state, parentState: parent))
}
}

Expand All @@ -79,7 +86,7 @@ public final class Blockchain: Sendable {
storage.finalizedHead = hash
}

await eventBus.publish(RuntimeEvents.BlockFinalized(hash: hash))
await publish(RuntimeEvents.BlockFinalized(hash: hash))
}

public func getBestBlock() async throws -> BlockRef {
Expand Down
15 changes: 9 additions & 6 deletions Blockchain/Sources/Blockchain/RuntimeProtocols/Runtime.swift
Original file line number Diff line number Diff line change
Expand Up @@ -78,16 +78,19 @@ public final class Runtime {
let index = block.header.timeslot % UInt32(config.value.epochLength)
let encodedHeader = try Result { try JamEncoder.encode(block.header) }.mapError(Error.invalidBlockSeal).get()
switch state.value.safroleState.ticketsOrKeys {
case let .left(keys):
let ticket = keys[Int(index)]
let vrfInputData = SigningContext.ticketSealInputData(entropy: state.value.entropyPool.t3, attempt: ticket.attempt)
case let .left(tickets):
let ticket = tickets[Int(index)]
let vrfInputData = SigningContext.safroleTicketInputData(entropy: state.value.entropyPool.t3, attempt: ticket.attempt)
vrfOutput = try Result {
try blockAuthorKey.ietfVRFVerify(
vrfInputData: vrfInputData,
auxData: encodedHeader,
signature: block.header.seal.data
signature: block.header.seal
)
}.mapError(Error.invalidBlockSeal).get()
guard ticket.id == vrfOutput else {
throw Error.notBlockAuthor
}

case let .right(keys):
let key = keys[Int(index)]
Expand All @@ -99,14 +102,14 @@ public final class Runtime {
try blockAuthorKey.ietfVRFVerify(
vrfInputData: vrfInputData,
auxData: encodedHeader,
signature: block.header.seal.data
signature: block.header.seal
)
}.mapError(Error.invalidBlockSeal).get()
}

let vrfInputData = SigningContext.entropyInputData(entropy: vrfOutput)
_ = try Result {
try blockAuthorKey.ietfVRFVerify(vrfInputData: vrfInputData, signature: block.header.vrfSignature.data)
try blockAuthorKey.ietfVRFVerify(vrfInputData: vrfInputData, signature: block.header.vrfSignature)
}.mapError { _ in Error.invalidVrfSignature }.get()
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,19 @@ public enum RuntimeEvents {
public let hash: Data32
}

public struct NewSafroleTickets: Event {
// New safrole ticket generated from SafroleService
public struct SafroleTicketsGenerated: Event {
public let items: [TicketItemAndOutput]
public let publicKey: Bandersnatch.PublicKey
}

// New safrole ticket received from network
public struct SafroleTicketsReceived: Event {
public let items: [ExtrinsicTickets.TicketItem]
}

// New block authored by BlockAuthor service
public struct BlockAuthored: Event {
public let block: BlockRef
}
}
33 changes: 13 additions & 20 deletions Blockchain/Sources/Blockchain/RuntimeProtocols/Safrole.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,17 @@ public struct EntropyPool: Sendable, Equatable, Codable {
}
}

public typealias SafroleTicketsOrKeys = Either<
ConfigFixedSizeArray<
Ticket,
ProtocolConfig.EpochLength
>,
ConfigFixedSizeArray<
BandersnatchPublicKey,
ProtocolConfig.EpochLength
>
>

public struct SafrolePostState: Sendable, Equatable {
public var timeslot: TimeslotIndex
public var entropyPool: EntropyPool
Expand All @@ -48,16 +59,7 @@ public struct SafrolePostState: Sendable, Equatable {
ProtocolConfig.Int0,
ProtocolConfig.EpochLength
>
public var ticketsOrKeys: Either<
ConfigFixedSizeArray<
Ticket,
ProtocolConfig.EpochLength
>,
ConfigFixedSizeArray<
BandersnatchPublicKey,
ProtocolConfig.EpochLength
>
>
public var ticketsOrKeys: SafroleTicketsOrKeys
public var ticketsVerifier: BandersnatchRingVRFRoot

public init(
Expand Down Expand Up @@ -124,16 +126,7 @@ public protocol Safrole {
ProtocolConfig.Int0,
ProtocolConfig.EpochLength
> { get }
var ticketsOrKeys: Either<
ConfigFixedSizeArray<
Ticket,
ProtocolConfig.EpochLength
>,
ConfigFixedSizeArray<
BandersnatchPublicKey,
ProtocolConfig.EpochLength
>
> { get }
var ticketsOrKeys: SafroleTicketsOrKeys { get }
var ticketsVerifier: BandersnatchRingVRFRoot { get }

func updateSafrole(
Expand Down
15 changes: 15 additions & 0 deletions Blockchain/Sources/Blockchain/Scheduler/Date+Extension.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import Foundation

extension Date {
public static var jamCommonEraBeginning: UInt32 {
// the Jam Common Era: 1200 UTC on January 1, 2024
// number of seconds since the Unix epoch
1_704_110_400
}

public var timeIntervalSinceJamCommonEra: UInt32 {
let beginning = Double(Date.jamCommonEraBeginning)
let now = timeIntervalSince1970
return UInt32(now - beginning)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
@preconcurrency import Foundation
import TracingUtils

private let logger = Logger(label: "Scheduler")

public final class DispatchQueueScheduler: Scheduler {
public let timeProvider: TimeProvider
private let queue: DispatchQueue

public init(timeProvider: TimeProvider, queue: DispatchQueue = .global()) {
self.timeProvider = timeProvider
self.queue = queue
}

public func schedule(
delay: TimeInterval,
repeats: Bool,
task: @escaping @Sendable () -> Void,
onCancel: (@Sendable () -> Void)?
) -> Cancellable {
logger.trace("scheduling task in \(delay) seconds, repeats: \(repeats)")
let timer = DispatchSource.makeTimerSource(queue: queue)
timer.setEventHandler(handler: task)
timer.setCancelHandler(handler: onCancel)
timer.schedule(deadline: .now() + delay, repeating: repeats ? delay : .infinity)
timer.activate()
return Cancellable {
timer.cancel()
}
}
}
108 changes: 108 additions & 0 deletions Blockchain/Sources/Blockchain/Scheduler/MockScheduler.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import Atomics
import Foundation
import Utils

private final class SchedulerTask: Sendable {
let id: Int
let scheduleTime: UInt32
let repeats: TimeInterval?
let task: @Sendable () -> Void
let cancel: (@Sendable () -> Void)?

init(
id: Int,
scheduleTime: UInt32,
repeats: TimeInterval?,
task: @escaping @Sendable () -> Void,
cancel: (@Sendable () -> Void)?
) {
self.id = id
self.scheduleTime = scheduleTime
self.repeats = repeats
self.task = task
self.cancel = cancel
}
}

private struct Storage: Sendable {
var tasks: [SchedulerTask] = []
var prevTime: UInt32 = 0
}

public final class MockScheduler: Scheduler, Sendable {
static let idGenerator = ManagedAtomic<Int>(0)

private let mockTimeProvider: MockTimeProvider
public var timeProvider: TimeProvider {
mockTimeProvider
}

private let storage: ThreadSafeContainer<Storage> = .init(.init())

public init(timeProvider: MockTimeProvider) {
mockTimeProvider = timeProvider
}

public func schedule(
delay: TimeInterval,
repeats: Bool,
task: @escaping @Sendable () -> Void,
onCancel: (@Sendable () -> Void)?
) -> Cancellable {
let now = timeProvider.getTime()
let scheduleTime = now + UInt32(delay)
let id = Self.idGenerator.loadThenWrappingIncrement(ordering: .relaxed)
let task = SchedulerTask(id: id, scheduleTime: scheduleTime, repeats: repeats ? delay : nil, task: task, cancel: onCancel)
storage.write { storage in
storage.tasks.append(task)
}
return Cancellable {
self.storage.mutate { storage in
if let index = storage.tasks.firstIndex(where: { $0.id == id }) {
let task = storage.tasks.remove(at: index)
task.cancel?()
}
}
}
}

public func advance(by interval: UInt32) {
mockTimeProvider.advance(by: interval)
trigger()
}

public func trigger() {
let now = timeProvider.getTime()
let tasks = storage.mutate { storage in
var tasksToDispatch: [SchedulerTask] = []
var remainingTasks: [SchedulerTask] = []

for task in storage.tasks {
if task.scheduleTime <= now {
tasksToDispatch.append(task)
} else {
remainingTasks.append(task)
}
}

storage.tasks = remainingTasks
storage.prevTime = now
for task in tasksToDispatch {
if let repeats = task.repeats {
storage.tasks.append(SchedulerTask(
id: task.id,
scheduleTime: task.scheduleTime + UInt32(repeats),
repeats: repeats,
task: task.task,
cancel: task.cancel
))
}
}
return tasksToDispatch
}

for task in tasks {
task.task()
}
}
}
Loading

0 comments on commit 589a1b6

Please sign in to comment.