Skip to content

Commit

Permalink
fix block author
Browse files Browse the repository at this point in the history
  • Loading branch information
xlc committed Oct 22, 2024
1 parent 52fde8a commit 66cad5c
Show file tree
Hide file tree
Showing 16 changed files with 172 additions and 109 deletions.
19 changes: 13 additions & 6 deletions Blockchain/Sources/Blockchain/RuntimeProtocols/Runtime.swift
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import Codec
import Foundation
import TracingUtils
import Utils

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

// the STF
public final class Runtime {
public enum Error: Swift.Error {
Expand Down Expand Up @@ -70,19 +73,20 @@ public final class Runtime {
// winning tickets is validated at apply time by Safrole

// offendersMarkers is validated at apply time by Disputes
}

// validate block.header.seal
public func validateHeaderSeal(block: BlockRef, state: inout State) throws(Error) {
let vrfOutput: Data32
let blockAuthorKey = try Result {
try Bandersnatch.PublicKey(data: state.value.currentValidators[Int(block.header.authorIndex)].bandersnatch)
try Bandersnatch.PublicKey(data: state.currentValidators[Int(block.header.authorIndex)].bandersnatch)
}.mapError(Error.invalidBlockSeal).get()
let index = block.header.timeslot % UInt32(config.value.epochLength)
let encodedHeader = try Result { try JamEncoder.encode(block.header.unsigned) }.mapError(Error.invalidBlockSeal).get()
let entropyVRFInputData: Data
switch state.value.safroleState.ticketsOrKeys {
switch state.safroleState.ticketsOrKeys {
case let .left(tickets):
let ticket = tickets[Int(index)]
let vrfInputData = SigningContext.safroleTicketInputData(entropy: state.value.entropyPool.t3, attempt: ticket.attempt)
let vrfInputData = SigningContext.safroleTicketInputData(entropy: state.entropyPool.t3, attempt: ticket.attempt)
vrfOutput = try Result {
try blockAuthorKey.ietfVRFVerify(
vrfInputData: vrfInputData,
Expand All @@ -99,9 +103,10 @@ public final class Runtime {
case let .right(keys):
let key = keys[Int(index)]
guard key == blockAuthorKey.data else {
logger.debug("expected key: \(key.toHexString()), got key: \(blockAuthorKey.data.toHexString())")
throw Error.notBlockAuthor
}
let vrfInputData = SigningContext.fallbackSealInputData(entropy: state.value.entropyPool.t3)
let vrfInputData = SigningContext.fallbackSealInputData(entropy: state.entropyPool.t3)
vrfOutput = try Result {
try blockAuthorKey.ietfVRFVerify(
vrfInputData: vrfInputData,
Expand All @@ -110,7 +115,7 @@ public final class Runtime {
)
}.mapError(Error.invalidBlockSeal).get()

entropyVRFInputData = SigningContext.fallbackSealInputData(entropy: state.value.entropyPool.t3)
entropyVRFInputData = SigningContext.fallbackSealInputData(entropy: state.entropyPool.t3)
}

_ = try Result {
Expand Down Expand Up @@ -150,6 +155,8 @@ public final class Runtime {
do {
try updateSafrole(block: block, state: &newState)

try validateHeaderSeal(block: block, state: &newState)

try updateDisputes(block: block, state: &newState)

// depends on Safrole and Disputes
Expand Down
4 changes: 4 additions & 0 deletions Blockchain/Sources/Blockchain/Types/StateRef.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ public final class StateRef: Ref<State>, @unchecked Sendable {
public var stateRoot: Data32 {
lazyStateRoot.value.value
}

override public var description: String {
"StateRef(\(stateRoot.toHexString()))"
}
}

extension StateRef: Codable {
Expand Down
22 changes: 13 additions & 9 deletions Blockchain/Sources/Blockchain/Validator/BlockAuthor.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import Codec
import Foundation
import Synchronization
import TracingUtils
import Utils

Expand All @@ -8,7 +9,7 @@ public final class BlockAuthor: ServiceBase2, @unchecked Sendable {
private let keystore: KeyStore
private let extrinsicPool: ExtrinsicPoolService

private var tickets: ThreadSafeContainer<[RuntimeEvents.SafroleTicketsGenerated]> = .init([])
private let tickets: ThreadSafeContainer<[RuntimeEvents.SafroleTicketsGenerated]> = .init([])

public init(
config: ProtocolConfigRef,
Expand All @@ -33,11 +34,11 @@ public final class BlockAuthor: ServiceBase2, @unchecked Sendable {
}
}

public func on(genesis state: StateRef) async {
await scheduleNewBlocks(
ticketsOrKeys: state.value.safroleState.ticketsOrKeys,
timeslot: timeProvider.getTime().timeToTimeslot(config: config)
)
public func on(genesis _: StateRef) async {
let nowTimeslot = timeProvider.getTime().timeToTimeslot(config: config)
// schedule for current epoch
let epoch = (nowTimeslot + 1).timeslotToEpochIndex(config: config)
await onBeforeEpoch(epoch: epoch)
}

public func createNewBlock(
Expand All @@ -47,6 +48,9 @@ public final class BlockAuthor: ServiceBase2, @unchecked Sendable {
-> BlockRef
{
let parentHash = dataProvider.bestHead

logger.trace("creating new block for timeslot: \(timeslot) with parent hash: \(parentHash)")

let state = try await dataProvider.getState(hash: parentHash)
let epoch = timeslot.timeslotToEpochIndex(config: config)

Expand Down Expand Up @@ -156,7 +160,7 @@ public final class BlockAuthor: ServiceBase2, @unchecked Sendable {
await withSpan("BlockAuthor.newBlock", logger: logger) { _ in
// TODO: add timeout
let block = try await createNewBlock(timeslot: timeslot, claim: claim)
logger.info("New block created: #\(block.header.timeslot) \(block.hash)")
logger.info("New block created: #\(block.header.timeslot) \(block.hash) on parent #\(block.header.parentHash)")
publish(RuntimeEvents.BlockAuthored(block: block))
}
}
Expand Down Expand Up @@ -205,7 +209,7 @@ public final class BlockAuthor: ServiceBase2, @unchecked Sendable {
if delay < 0 {
continue
}
logger.debug("Scheduling new block task at timeslot \(timeslot)")
logger.trace("Scheduling new block task at timeslot \(timeslot) for claim \(claim.1.data.toHexString())")
schedule(id: "BlockAuthor.newBlock", delay: delay) { [weak self] in
if let self {
await newBlock(timeslot: timeslot, claim: .left(claim))
Expand All @@ -223,7 +227,7 @@ public final class BlockAuthor: ServiceBase2, @unchecked Sendable {
if delay < 0 {
continue
}
logger.debug("Scheduling new block task at timeslot \(timeslot)")
logger.trace("Scheduling new block task at timeslot \(timeslot) for key \(pubkey.data.toHexString())")
schedule(id: "BlockAuthor.newBlock", delay: delay) { [weak self] in
if let self {
await newBlock(timeslot: timeslot, claim: .right(pubkey))
Expand Down
12 changes: 5 additions & 7 deletions Blockchain/Sources/Blockchain/Validator/ServiceBase2.swift
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,8 @@ public class ServiceBase2: ServiceBase, @unchecked Sendable {

@discardableResult
public func scheduleForNextEpoch(_ id: UniqueId, fn: @escaping @Sendable (EpochIndex) async -> Void) -> Cancellable {
let now = timeProvider.getTimeInterval()
let nowTimeslot = UInt32(now).timeToTimeslot(config: config)
let now = timeProvider.getTime()
let nowTimeslot = now.timeToTimeslot(config: config)
let nextEpoch = (nowTimeslot + 1).timeslotToEpochIndex(config: config) + 1
return scheduleFor(epoch: nextEpoch, id: id, fn: fn)
}
Expand All @@ -73,12 +73,10 @@ public class ServiceBase2: ServiceBase, @unchecked Sendable {
private func scheduleFor(epoch: EpochIndex, id: UniqueId, fn: @escaping @Sendable (EpochIndex) async -> Void) -> Cancellable {
let scheduleTime = config.scheduleTimeForPrepareEpoch(epoch: epoch)
let now = timeProvider.getTimeInterval()
let delay = scheduleTime - now
var delay = scheduleTime - now
if delay < 0 {
// too late / current epoch is about to end
// schedule for the one after
logger.debug("\(id): skipping epoch \(epoch) because it is too late")
return scheduleFor(epoch: epoch + 1, id: id, fn: fn)
logger.debug("\(id): late epoch start \(epoch), expectedDelay \(delay)")
delay = 0
}
logger.trace("\(id): scheduling epoch \(epoch) in \(delay)")
return schedule(id: id, delay: delay) { [weak self] in
Expand Down
6 changes: 3 additions & 3 deletions Blockchain/Tests/BlockchainTests/BlockAuthorTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ struct BlockAuthorTests {
dataProvider = try await BlockchainDataProvider(InMemoryDataProvider(genesisState: genesisState, genesisBlock: genesisBlock))

storeMiddleware = StoreMiddleware()
eventBus = EventBus(eventMiddleware: Middleware(storeMiddleware))
eventBus = EventBus(eventMiddleware: .serial(Middleware(storeMiddleware), .noError), handlerMiddleware: .noError)

scheduler = MockScheduler(timeProvider: timeProvider)

Expand Down Expand Up @@ -108,14 +108,14 @@ struct BlockAuthorTests {
}

@Test
func scheduleNewBlocks() async throws {
func firstBlock() async throws {
let genesisState = try await dataProvider.getState(hash: dataProvider.genesisBlockHash)

await blockAuthor.on(genesis: genesisState)

#expect(scheduler.storage.value.tasks.count > 0)

await scheduler.advance(by: 2)
// await scheduler.advance(by: 2)

let events = await storeMiddleware.wait()
#expect(events.count == 1)
Expand Down
42 changes: 42 additions & 0 deletions Blockchain/Tests/BlockchainTests/BlockchainServices.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import Blockchain
import Utils

struct BlockchainServices {
let config: ProtocolConfigRef
let timeProvider: MockTimeProvider
let dataProvider: BlockchainDataProvider
let eventBus: EventBus
let scheduler: MockScheduler
let keystore: KeyStore
let storeMiddleware: StoreMiddleware
let blockchain: Blockchain
let genesisBlock: BlockRef
let genesisState: StateRef

init(
config: ProtocolConfigRef = .dev,
timeProvider: MockTimeProvider = MockTimeProvider(time: 988)
) async throws {
self.config = config
self.timeProvider = timeProvider

let (genesisState, genesisBlock) = try State.devGenesis(config: config)
self.genesisBlock = genesisBlock
self.genesisState = genesisState
dataProvider = try await BlockchainDataProvider(InMemoryDataProvider(genesisState: genesisState, genesisBlock: genesisBlock))

storeMiddleware = StoreMiddleware()
eventBus = EventBus(eventMiddleware: .serial(Middleware(storeMiddleware), .noError), handlerMiddleware: .noError)

scheduler = MockScheduler(timeProvider: timeProvider)

keystore = try await DevKeyStore()

blockchain = try await Blockchain(
config: config,
dataProvider: dataProvider,
timeProvider: timeProvider,
eventBus: eventBus
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ struct ExtrinsicPoolServiceTests {
dataProvider = try await BlockchainDataProvider(InMemoryDataProvider(genesisState: genesisState, genesisBlock: genesisBlock))

storeMiddleware = StoreMiddleware()
eventBus = EventBus(eventMiddleware: Middleware(storeMiddleware))
eventBus = EventBus(eventMiddleware: .serial(Middleware(storeMiddleware), .noError), handlerMiddleware: .noError)

keystore = try await DevKeyStore(devKeysCount: config.value.totalNumberOfValidators)

Expand Down
13 changes: 7 additions & 6 deletions Blockchain/Tests/BlockchainTests/MockScheduler.swift
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
import Atomics
import Blockchain
import Foundation
import TracingUtils
import Utils

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

final class SchedulerTask: Sendable, Comparable {
let id: Int
let id: UniqueId
let scheduleTime: TimeInterval
let repeats: TimeInterval?
let task: @Sendable () async -> Void
let cancel: (@Sendable () -> Void)?

init(
id: Int,
id: UniqueId,
scheduleTime: TimeInterval,
repeats: TimeInterval?,
task: @escaping @Sendable () async -> Void,
Expand All @@ -38,8 +40,6 @@ struct Storage: Sendable {
}

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

let mockTimeProvider: MockTimeProvider
var timeProvider: TimeProvider {
mockTimeProvider
Expand All @@ -59,7 +59,7 @@ final class MockScheduler: Scheduler, Sendable {
) -> Cancellable {
let now = timeProvider.getTimeInterval()
let scheduleTime = now + delay
let id = Self.idGenerator.loadThenWrappingIncrement(ordering: .relaxed)
let id = UniqueId()
let task = SchedulerTask(id: id, scheduleTime: scheduleTime, repeats: repeats ? delay : nil, task: task, cancel: onCancel)
storage.write { storage in
storage.tasks.insert(task)
Expand Down Expand Up @@ -90,6 +90,7 @@ final class MockScheduler: Scheduler, Sendable {

if let task {
mockTimeProvider.advance(to: task.scheduleTime)
logger.debug("executing task \(task.id) at time \(task.scheduleTime)")
await task.task()

return true
Expand Down
2 changes: 1 addition & 1 deletion Blockchain/Tests/BlockchainTests/SafroleServiceTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ struct SafroleServiceTests {
(genesisState, _) = try State.devGenesis(config: config)

storeMiddleware = StoreMiddleware()
eventBus = EventBus(eventMiddleware: Middleware(storeMiddleware))
eventBus = EventBus(eventMiddleware: .serial(Middleware(storeMiddleware), .noError), handlerMiddleware: .noError)

keystore = try await DevKeyStore(devKeysCount: 2)

Expand Down
Loading

0 comments on commit 66cad5c

Please sign in to comment.