Skip to content

Commit

Permalink
Network handler (#182)
Browse files Browse the repository at this point in the history
* async data input

* NetAddr refactoring

* decode and pipe messages

* up message

* handler safrole message

* everything linked

* logging fix

* wip

* fix message parsing

* decoder fix

* fix

* workaround

* fix

* genesis block is known block

* fix

* fix test

* update

* fix

* fix

* allow more delays

* fix

* fix

* fix
  • Loading branch information
xlc authored Oct 21, 2024
1 parent 637ec4d commit 52fde8a
Show file tree
Hide file tree
Showing 52 changed files with 991 additions and 472 deletions.
4 changes: 4 additions & 0 deletions Blockchain/Sources/Blockchain/Blockchain.swift
Original file line number Diff line number Diff line change
Expand Up @@ -54,4 +54,8 @@ public final class Blockchain: ServiceBase, @unchecked Sendable {

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

public func publish(event: some Event) {
publish(event)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,15 @@ public final class BlockchainDataProvider: Sendable {
let heads = try await dataProvider.getHeads()
var bestHead: (HeaderRef, Data32)?
for head in heads {
guard let header = try? await dataProvider.getHeader(hash: head) else {
continue
}
let header = try await dataProvider.getHeader(hash: head)
if bestHead == nil || header.value.timeslot > bestHead!.0.value.timeslot {
bestHead = (header, head)
}
}
let finalizedHead = try await dataProvider.getFinalizedHead()

storage = ThreadSafeContainer(.init(
bestHead: bestHead?.1 ?? Data32(),
bestHead: bestHead?.1 ?? dataProvider.genesisBlockHash,
bestHeadTimeslot: bestHead?.0.value.timeslot ?? 0,
finalizedHead: finalizedHead
))
Expand Down Expand Up @@ -129,4 +127,8 @@ extension BlockchainDataProvider {

try await dataProvider.remove(hash: hash)
}

public var genesisBlockHash: Data32 {
dataProvider.genesisBlockHash
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,6 @@ public protocol BlockchainDataProviderProtocol: Sendable {

/// remove header, block and state
func remove(hash: Data32) async throws

var genesisBlockHash: Data32 { get }
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,15 @@ public actor InMemoryDataProvider: Sendable {
private var blockByHash: [Data32: BlockRef] = [:]
private var stateByBlockHash: [Data32: StateRef] = [:]
private var hashByTimeslot: [TimeslotIndex: Set<Data32>] = [:]
public let genesisBlockHash: Data32

public init(genesis: StateRef) async {
heads = [Data32()]
finalizedHead = Data32()
public init(genesisState: StateRef, genesisBlock: BlockRef) async {
genesisBlockHash = genesisBlock.hash
heads = [genesisBlockHash]
finalizedHead = genesisBlockHash

add(state: genesis)
add(block: genesisBlock)
add(state: genesisState)
}
}

Expand Down Expand Up @@ -80,8 +83,7 @@ extension InMemoryDataProvider: BlockchainDataProviderProtocol {
// parent needs to be either
// - existing head
// - known block
// - genesis / all zeros
guard heads.remove(parent) != nil || hasBlock(hash: parent) || parent == Data32() else {
guard heads.remove(parent) != nil || hasBlock(hash: parent) else {
throw BlockchainDataProviderError.noData(hash: parent)
}
heads.insert(hash)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ public enum RuntimeEvents {
// New safrole ticket received from network
public struct SafroleTicketsReceived: Event {
public let items: [ExtrinsicTickets.TicketItem]

public init(items: [ExtrinsicTickets.TicketItem]) {
self.items = items
}
}

// New block authored by BlockAuthor service
Expand Down
10 changes: 9 additions & 1 deletion Blockchain/Sources/Blockchain/Types/RecentHistory.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,15 @@ public struct RecentHistory: Sendable, Equatable, Codable {
extension RecentHistory: Dummy {
public typealias Config = ProtocolConfigRef
public static func dummy(config: Config) -> RecentHistory {
RecentHistory(items: try! ConfigLimitedSizeArray(config: config))
RecentHistory(items: try! ConfigLimitedSizeArray(
config: config,
array: [HistoryItem(
headerHash: Data32(),
mmr: MMR([]),
stateRoot: Data32(),
workReportHashes: ConfigLimitedSizeArray(config: config)
)]
))
}
}

Expand Down
12 changes: 10 additions & 2 deletions Blockchain/Sources/Blockchain/Types/State+Genesis.swift
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import Utils

extension State {
public static func devGenesis(config: ProtocolConfigRef) throws -> State {
public static func devGenesis(config: ProtocolConfigRef) throws -> (StateRef, BlockRef) {
var devKeys = [ValidatorKey]()

var state = State.dummy(config: config)
Expand Down Expand Up @@ -32,7 +32,15 @@ extension State {
)
state.safroleState.ticketsVerifier = commitment.data

return state
let block = BlockRef(Block.dummy(config: config))
try state.recentHistory.items.append(RecentHistory.HistoryItem(
headerHash: block.hash,
mmr: MMR([]),
stateRoot: Data32(),
workReportHashes: ConfigLimitedSizeArray(config: config)
))

return (StateRef(state), block)
}
// TODO: add file genesis
// public static func fileGenesis(config: ProtocolConfigRef) throws -> State
Expand Down
2 changes: 1 addition & 1 deletion Blockchain/Sources/Blockchain/Types/State.swift
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ public struct State: Sendable, Equatable, Codable {

extension State {
public var lastBlockHash: Data32 {
recentHistory.items.last.map(\.headerHash) ?? Data32()
recentHistory.items.last.map(\.headerHash)!
}
}

Expand Down
9 changes: 5 additions & 4 deletions Blockchain/Tests/BlockchainTests/BlockAuthorTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ struct BlockAuthorTests {
config = ProtocolConfigRef.dev
timeProvider = MockTimeProvider(time: 988)

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

storeMiddleware = StoreMiddleware()
eventBus = EventBus(eventMiddleware: Middleware(storeMiddleware))
Expand All @@ -45,7 +46,7 @@ struct BlockAuthorTests {

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

let timeslot = timeProvider.getTime().timeToTimeslot(config: config)

Expand All @@ -62,7 +63,7 @@ struct BlockAuthorTests {

@Test
func createNewBlockWithTicket() async throws {
let genesisState = try await dataProvider.getState(hash: Data32())
let genesisState = try await dataProvider.getState(hash: dataProvider.genesisBlockHash)
var state = genesisState.value

state.safroleState.ticketsVerifier = try Bandersnatch.RingCommitment(
Expand Down Expand Up @@ -108,7 +109,7 @@ struct BlockAuthorTests {

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

await blockAuthor.on(genesis: genesisState)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,23 +38,23 @@ struct DispatchQueueSchedulerTests {

let diff = try #require(end.value).timeIntervalSince(now) - delay
let diffAbs = abs(diff)
#expect(diffAbs < 0.5)
#expect(diffAbs < 1)
}
}

@Test func scheduleRepeatingTask() async throws {
try await confirmation(expectedCount: 3) { confirm in
let delay = 0.5
try await confirmation(expectedCount: 2) { confirm in
let delay = 1.5
let now = Date()
let executionTimes = ThreadSafeContainer<[Date]>([])
let expectedExecutions = 3
let expectedExecutions = 2

let cancel = scheduler.schedule(delay: delay, repeats: true) {
executionTimes.value.append(Date())
confirm()
}

try await Task.sleep(for: .seconds(1.6))
try await Task.sleep(for: .seconds(3.1))

_ = cancel

Expand All @@ -64,7 +64,7 @@ struct DispatchQueueSchedulerTests {
let expectedInterval = delay * Double(index + 1)
let actualInterval = time.timeIntervalSince(now)
let difference = abs(actualInterval - expectedInterval)
#expect(difference < 0.5)
#expect(difference < 1)
}
}
}
Expand All @@ -83,13 +83,13 @@ struct DispatchQueueSchedulerTests {

@Test func cancelRepeatingTask() async throws {
try await confirmation(expectedCount: 2) { confirm in
let delay = 0.5
let delay = 1.0

let cancel = scheduler.schedule(delay: delay, repeats: true) {
confirm()
}

try await Task.sleep(for: .seconds(1.2))
try await Task.sleep(for: .seconds(2.2))

cancel.cancel()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ struct ExtrinsicPoolServiceTests {
}
timeProvider = MockTimeProvider(time: 1000)

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

storeMiddleware = StoreMiddleware()
eventBus = EventBus(eventMiddleware: Middleware(storeMiddleware))
Expand Down Expand Up @@ -186,6 +187,7 @@ struct ExtrinsicPoolServiceTests {

let newBlock = BlockRef.dummy(config: config).mutate {
$0.header.unsigned.timeslot = nextTimeslot
$0.header.unsigned.parentHash = dataProvider.bestHead
}

let oldEntropyPool = state.value.entropyPool
Expand Down
42 changes: 23 additions & 19 deletions Blockchain/Tests/BlockchainTests/InMemoryDataProviderTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,18 @@ struct InMemoryDataProviderTests {
let config = ProtocolConfigRef.mainnet

@Test func testInitialization() async throws {
let genesis = StateRef(State.dummy(config: config))
let provider = await InMemoryDataProvider(genesis: genesis)
let (genesis, block) = try State.devGenesis(config: config)
let provider = await InMemoryDataProvider(genesisState: genesis, genesisBlock: block)

#expect(await (provider.getHeads()) == [Data32()])
#expect(await (provider.getFinalizedHead()) == Data32())
#expect(await (provider.getHeads()) == [block.hash])
#expect(await (provider.getFinalizedHead()) == block.hash)
}

@Test func testAddAndRetrieveBlock() async throws {
let genesis = StateRef(State.dummy(config: config))
let provider = await InMemoryDataProvider(genesis: genesis)
let (genesis, block) = try State.devGenesis(config: config)

let provider = await InMemoryDataProvider(genesisState: genesis, genesisBlock: block)

let block = BlockRef(Block.dummy(config: config))
await provider.add(block: block)

#expect(await (provider.hasBlock(hash: block.hash)) == true)
Expand All @@ -29,8 +29,9 @@ struct InMemoryDataProviderTests {
}

@Test func testAddAndRetrieveState() async throws {
let genesis = StateRef(State.dummy(config: config))
let provider = await InMemoryDataProvider(genesis: genesis)
let (genesis, block) = try State.devGenesis(config: config)

let provider = await InMemoryDataProvider(genesisState: genesis, genesisBlock: block)

let state = StateRef(State.dummy(config: config))
await provider.add(state: state)
Expand All @@ -40,16 +41,18 @@ struct InMemoryDataProviderTests {
}

@Test func testUpdateHead() async throws {
let genesis = StateRef(State.dummy(config: config))
let provider = await InMemoryDataProvider(genesis: genesis)
let (genesis, block) = try State.devGenesis(config: config)
let provider = await InMemoryDataProvider(genesisState: genesis, genesisBlock: block)

let newBlock = BlockRef(Block.dummy(config: config))
let newBlock = BlockRef(Block.dummy(config: config)).mutate {
$0.header.unsigned.timeslot = 123
}

await provider.add(block: newBlock)
try await provider.updateHead(hash: newBlock.hash, parent: Data32())
try await provider.updateHead(hash: newBlock.hash, parent: block.hash)

#expect(await provider.isHead(hash: newBlock.hash) == true)
#expect(await provider.isHead(hash: Data32()) == false)
#expect(await provider.isHead(hash: block.hash) == false)

let hash = Data32.random()
await #expect(throws: BlockchainDataProviderError.noData(hash: hash)) {
Expand All @@ -58,19 +61,20 @@ struct InMemoryDataProviderTests {
}

@Test func testSetFinalizedHead() async throws {
let genesis = StateRef(State.dummy(config: config))
let provider = await InMemoryDataProvider(genesis: genesis)
let (genesis, block) = try State.devGenesis(config: config)

let provider = await InMemoryDataProvider(genesisState: genesis, genesisBlock: block)

let block = BlockRef(Block.dummy(config: config))
await provider.add(block: block)
await provider.setFinalizedHead(hash: block.hash)

#expect(await (provider.getFinalizedHead()) == block.hash)
}

@Test func testRemoveHash() async throws {
let genesis = StateRef(State.dummy(config: config))
let provider = await InMemoryDataProvider(genesis: genesis)
let (genesis, block) = try State.devGenesis(config: config)

let provider = await InMemoryDataProvider(genesisState: genesis, genesisBlock: block)

let state = StateRef(State.dummy(config: ProtocolConfigRef.dev))
let timeslotIndex = state.value.timeslot
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 @@ -21,7 +21,7 @@ struct SafroleServiceTests {
}
timeProvider = MockTimeProvider(time: 1000)

genesisState = try StateRef(State.devGenesis(config: config))
(genesisState, _) = try State.devGenesis(config: config)

storeMiddleware = StoreMiddleware()
eventBus = EventBus(eventMiddleware: Middleware(storeMiddleware))
Expand Down
9 changes: 5 additions & 4 deletions Blockchain/Tests/BlockchainTests/ValidatorServiceTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ struct ValidatorServiceTests {
config = ProtocolConfigRef.dev
timeProvider = MockTimeProvider(time: 988)

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

storeMiddleware = StoreMiddleware()
eventBus = EventBus(eventMiddleware: Middleware(storeMiddleware))
Expand All @@ -48,7 +49,7 @@ struct ValidatorServiceTests {

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

await validatorService.on(genesis: genesisState)

Expand All @@ -64,7 +65,7 @@ struct ValidatorServiceTests {

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

await validatorService.on(genesis: genesisState)

Expand Down Expand Up @@ -103,7 +104,7 @@ struct ValidatorServiceTests {

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

await validatorService.on(genesis: genesisState)

Expand Down
6 changes: 6 additions & 0 deletions Boka/.swiftpm/xcode/xcshareddata/xcschemes/Boka.xcscheme
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,12 @@
ReferencedContainer = "container:">
</BuildableReference>
</BuildableProductRunnable>
<CommandLineArguments>
<CommandLineArgument
argument = "--validator --dev-seed 1"
isEnabled = "YES">
</CommandLineArgument>
</CommandLineArguments>
<EnvironmentVariables>
<EnvironmentVariable
key = "LOG_LEVEL"
Expand Down
Loading

0 comments on commit 52fde8a

Please sign in to comment.