Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix block production #195

Merged
merged 2 commits into from
Oct 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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)
}
}