Skip to content

Commit

Permalink
Fix block production (#195)
Browse files Browse the repository at this point in the history
* add failing test

* fix schedule block timing on short epoch
  • Loading branch information
xlc authored Oct 24, 2024
1 parent c095281 commit 0ea4adf
Show file tree
Hide file tree
Showing 7 changed files with 79 additions and 18 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,10 @@ extension ProtocolConfigRef {
}

public var prepareEpochStartTimeDelta: TimeInterval {
-TimeInterval(value.slotPeriodSeconds) * 3
if value.epochLength < 15 {
return -TimeInterval(value.slotPeriodSeconds)
}
return -TimeInterval(value.slotPeriodSeconds) * 3
}

public func scheduleTimeForAuthoring(timeslot: TimeslotIndex) -> TimeInterval {
Expand Down
20 changes: 14 additions & 6 deletions Blockchain/Sources/Blockchain/RuntimeProtocols/Runtime.swift
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ public final class Runtime {
// offendersMarkers is validated at apply time by Disputes
}

public func validateHeaderSeal(block: BlockRef, state: inout State) throws(Error) {
public func validateHeaderSeal(block: BlockRef, state: inout State, prevState: StateRef) throws(Error) {
let vrfOutput: Data32
let blockAuthorKey = try Result {
try Bandersnatch.PublicKey(data: state.currentValidators[Int(block.header.authorIndex)].bandersnatch)
Expand All @@ -87,7 +87,7 @@ public final class Runtime {
switch state.safroleState.ticketsOrKeys {
case let .left(tickets):
let ticket = tickets[Int(index)]
let vrfInputData = SigningContext.safroleTicketInputData(entropy: state.entropyPool.t3, attempt: ticket.attempt)
let vrfInputData = SigningContext.safroleTicketInputData(entropy: prevState.value.entropyPool.t3, attempt: ticket.attempt)
vrfOutput = try Result {
try blockAuthorKey.ietfVRFVerify(
vrfInputData: vrfInputData,
Expand All @@ -107,16 +107,17 @@ public final class Runtime {
logger.debug("expected key: \(key.toHexString()), got key: \(blockAuthorKey.data.toHexString())")
throw Error.invalidAuthorKey
}
let vrfInputData = SigningContext.fallbackSealInputData(entropy: state.entropyPool.t3)
let vrfInputData = SigningContext.fallbackSealInputData(entropy: prevState.value.entropyPool.t3)
vrfOutput = try Result {
try blockAuthorKey.ietfVRFVerify(
logger.trace("verifying ticket", metadata: ["key": "\(blockAuthorKey.data.toHexString())"])
return try blockAuthorKey.ietfVRFVerify(
vrfInputData: vrfInputData,
auxData: encodedHeader,
signature: block.header.seal
)
}.mapError(Error.invalidBlockSeal).get()

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

_ = try Result {
Expand Down Expand Up @@ -156,7 +157,14 @@ public final class Runtime {
do {
try updateSafrole(block: block, state: &newState)

try validateHeaderSeal(block: block, state: &newState)
if newState.ticketsOrKeys != prevState.value.ticketsOrKeys {
logger.trace("state tickets changed", metadata: [
"old": "\(prevState.value.ticketsOrKeys)",
"new": "\(newState.ticketsOrKeys)",
])
}

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

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

Expand Down
21 changes: 16 additions & 5 deletions Blockchain/Sources/Blockchain/Validator/BlockAuthor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -44,13 +44,13 @@ public final class BlockAuthor: ServiceBase2, @unchecked Sendable {
public func createNewBlock(
timeslot: TimeslotIndex,
claim: Either<(TicketItemAndOutput, Bandersnatch.PublicKey), Bandersnatch.PublicKey>
) async throws
-> BlockRef
{
) async throws -> BlockRef {
let parentHash = await dataProvider.bestHead.hash

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

// TODO: verify we are indeed the block author

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

Expand Down Expand Up @@ -175,8 +175,15 @@ public final class BlockAuthor: ServiceBase2, @unchecked Sendable {
tickets.value = []
let timeslot = epoch.epochToTimeslotIndex(config: config)

let bestHead = await dataProvider.bestHead.hash
let state = try await dataProvider.getState(hash: bestHead)
let bestHead = await dataProvider.bestHead

let bestHeadTimeslot = bestHead.timeslot
let bestHeadEpoch = bestHeadTimeslot.timeslotToEpochIndex(config: config)
if bestHeadEpoch + 1 != epoch {
logger.warning("best head epoch \(bestHeadEpoch) is too far from current epoch \(epoch)")
}

let state = try await dataProvider.getState(hash: bestHead.hash)

// simulate next block to determine the block authors for next epoch
let res = try state.value.updateSafrole(
Expand All @@ -187,6 +194,10 @@ public final class BlockAuthor: ServiceBase2, @unchecked Sendable {
extrinsics: .dummy(config: config)
)

logger.trace("expected safrole tickets", metadata: [
"tickets": "\(res.state.ticketsOrKeys)", "epoch": "\(epoch)", "parentTimeslot": "\(bestHead.timeslot)",
])

await scheduleNewBlocks(ticketsOrKeys: res.state.ticketsOrKeys, timeslot: timeslot)
}
}
Expand Down
1 change: 1 addition & 0 deletions Blockchain/Sources/Blockchain/Validator/DevKeyStore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ public final class DevKeyStore: KeyStore {
await keystore.get(type, publicKey: publicKey)
}

@discardableResult
public func addDevKeys(seed: UInt32) async throws -> KeySet {
var seedData = Data(repeating: 0, count: 32)
seedData[0 ..< 4] = seed.encode()
Expand Down
7 changes: 4 additions & 3 deletions Blockchain/Tests/BlockchainTests/BlockchainServices.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ class BlockchainServices {
let dataProvider: BlockchainDataProvider
let eventBus: EventBus
let scheduler: MockScheduler
let keystore: KeyStore
let keystore: DevKeyStore
let storeMiddleware: StoreMiddleware
let genesisBlock: BlockRef
let genesisState: StateRef
Expand All @@ -20,7 +20,8 @@ class BlockchainServices {

init(
config: ProtocolConfigRef = .dev,
timeProvider: MockTimeProvider = MockTimeProvider(time: 988)
timeProvider: MockTimeProvider = MockTimeProvider(time: 988),
keysCount: Int = 12
) async {
self.config = config
self.timeProvider = timeProvider
Expand All @@ -35,7 +36,7 @@ class BlockchainServices {

scheduler = MockScheduler(timeProvider: timeProvider)

keystore = try! await DevKeyStore()
keystore = try! await DevKeyStore(devKeysCount: keysCount)
}

deinit {
Expand Down
1 change: 1 addition & 0 deletions Blockchain/Tests/BlockchainTests/MockScheduler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ final class MockScheduler: Scheduler, Sendable {
func advance(by interval: TimeInterval) async {
let to = timeProvider.getTimeInterval() + interval
while await advanceNext(to: to) {}
mockTimeProvider.advance(to: to)
}

func advanceNext(to time: TimeInterval) async -> Bool {
Expand Down
42 changes: 39 additions & 3 deletions Blockchain/Tests/BlockchainTests/ValidatorServiceTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,17 @@ import Utils
@testable import Blockchain

struct ValidatorServiceTests {
func setup(time: TimeInterval = 988) async throws -> (BlockchainServices, ValidatorService) {
// setupTestLogger()
func setup(
config: ProtocolConfigRef = .dev,
time: TimeInterval = 988,
keysCount: Int = 12
) async throws -> (BlockchainServices, ValidatorService) {
setupTestLogger()

let services = await BlockchainServices(
timeProvider: MockTimeProvider(time: time)
config: config,
timeProvider: MockTimeProvider(time: time),
keysCount: keysCount
)
let validatorService = await ValidatorService(
blockchain: services.blockchain,
Expand Down Expand Up @@ -112,4 +118,34 @@ struct ValidatorServiceTests {

#expect(blockAuthoredEvents.count == 25)
}

@Test
func makeManyBlocksWithSingleKey() async throws {
let (services, validatorService) = try await setup(
config: .minimal,
keysCount: 0
)
let genesisState = services.genesisState
let storeMiddleware = services.storeMiddleware
let config = services.config
let scheduler = services.scheduler
let keystore = services.keystore

try await keystore.addDevKeys(seed: 0)

await validatorService.on(genesis: genesisState)

await storeMiddleware.wait()

for _ in 0 ..< 50 {
await scheduler.advance(by: TimeInterval(config.value.slotPeriodSeconds))
await storeMiddleware.wait() // let events to be processed
}

let events = await storeMiddleware.wait()

let blockAuthoredEvents = events.filter { $0 is RuntimeEvents.BlockAuthored }

#expect(blockAuthoredEvents.count > 0)
}
}

0 comments on commit 0ea4adf

Please sign in to comment.