diff --git a/Blockchain/Sources/Blockchain/Blockchain.swift b/Blockchain/Sources/Blockchain/Blockchain.swift index 813ac9e2..0892785a 100644 --- a/Blockchain/Sources/Blockchain/Blockchain.swift +++ b/Blockchain/Sources/Blockchain/Blockchain.swift @@ -5,7 +5,7 @@ import Utils /// 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 class Blockchain { +public final class Blockchain: Sendable { public let config: ProtocolConfigRef private let dataProvider: BlockchainDataProvider @@ -30,4 +30,15 @@ public class Blockchain { // TODO: purge forks try await dataProvider.setFinalizedHead(hash: hash) } + + public func getBestBlock() async throws -> BlockRef { + guard let hash = try await dataProvider.getHeads().first else { + try throwUnreachable("no head") + } + return try await dataProvider.getBlock(hash: hash) + } + + public func getBlock(hash: Data32) async throws -> BlockRef? { + try await dataProvider.getBlock(hash: hash) + } } diff --git a/Blockchain/Sources/Blockchain/BlockchainDataProvider/BlockchainDataProvider.swift b/Blockchain/Sources/Blockchain/BlockchainDataProvider/BlockchainDataProvider.swift index 0a849aaf..cc767f4d 100644 --- a/Blockchain/Sources/Blockchain/BlockchainDataProvider/BlockchainDataProvider.swift +++ b/Blockchain/Sources/Blockchain/BlockchainDataProvider/BlockchainDataProvider.swift @@ -4,7 +4,7 @@ public enum BlockchainDataProviderError: Error { case unknownHash } -public protocol BlockchainDataProvider { +public protocol BlockchainDataProvider: Sendable { func hasHeader(hash: Data32) async throws -> Bool func isHead(hash: Data32) async throws -> Bool diff --git a/Boka/Package.resolved b/Boka/Package.resolved index 06ae0b6e..46a844e8 100644 --- a/Boka/Package.resolved +++ b/Boka/Package.resolved @@ -1,6 +1,24 @@ { - "originHash" : "b09a7b54cb34c5b40b9c30546e215da07c64f3935c5d0c0b0daca60d07584231", + "originHash" : "8311dd4d27e8fd2abbee30df498eef1b06b76ecc7d9e7ff836f014d67d6698c7", "pins" : [ + { + "identity" : "async-http-client", + "kind" : "remoteSourceControl", + "location" : "https://github.com/swift-server/async-http-client.git", + "state" : { + "revision" : "0ae99db85b2b9d1e79b362bd31fd1ffe492f7c47", + "version" : "1.21.2" + } + }, + { + "identity" : "async-kit", + "kind" : "remoteSourceControl", + "location" : "https://github.com/vapor/async-kit.git", + "state" : { + "revision" : "e048c8ee94967e8d8a1c2ec0e1156d6f7fa34d31", + "version" : "1.20.0" + } + }, { "identity" : "blake2.swift", "kind" : "remoteSourceControl", @@ -10,6 +28,15 @@ "version" : "0.2.0" } }, + { + "identity" : "console-kit", + "kind" : "remoteSourceControl", + "location" : "https://github.com/vapor/console-kit.git", + "state" : { + "revision" : "9f7932f22ab6f64aafadc14491e694179b7d0f6f", + "version" : "4.14.3" + } + }, { "identity" : "grpc-swift", "kind" : "remoteSourceControl", @@ -19,6 +46,24 @@ "version" : "1.23.0" } }, + { + "identity" : "multipart-kit", + "kind" : "remoteSourceControl", + "location" : "https://github.com/vapor/multipart-kit.git", + "state" : { + "revision" : "a31236f24bfd2ea2f520a74575881f6731d7ae68", + "version" : "4.7.0" + } + }, + { + "identity" : "routing-kit", + "kind" : "remoteSourceControl", + "location" : "https://github.com/vapor/routing-kit.git", + "state" : { + "revision" : "8c9a227476555c55837e569be71944e02a056b72", + "version" : "4.9.1" + } + }, { "identity" : "scalecodec.swift", "kind" : "remoteSourceControl", @@ -28,6 +73,15 @@ "revision" : "dac3e7161de34c60c82794d031de0231b5a5746e" } }, + { + "identity" : "swift-algorithms", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-algorithms.git", + "state" : { + "revision" : "f6919dfc309e7f1b56224378b11e28bab5bccc42", + "version" : "1.2.0" + } + }, { "identity" : "swift-argument-parser", "kind" : "remoteSourceControl", @@ -154,6 +208,15 @@ "version" : "1.21.0" } }, + { + "identity" : "swift-numerics", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-numerics.git", + "state" : { + "revision" : "0a5bc04095a675662cf24757cc0640aa2204253b", + "version" : "1.0.2" + } + }, { "identity" : "swift-otel", "kind" : "remoteSourceControl", @@ -207,6 +270,24 @@ "revision" : "4d2cf7c64443cdf4df833d0bedd767bf9dbc49d9", "version" : "0.1.3" } + }, + { + "identity" : "vapor", + "kind" : "remoteSourceControl", + "location" : "https://github.com/vapor/vapor.git", + "state" : { + "revision" : "a823735db57b46100b0c61cdfc5a08525b1e7cad", + "version" : "4.102.1" + } + }, + { + "identity" : "websocket-kit", + "kind" : "remoteSourceControl", + "location" : "https://github.com/vapor/websocket-kit.git", + "state" : { + "revision" : "4232d34efa49f633ba61afde365d3896fc7f8740", + "version" : "2.15.0" + } } ], "version" : 3 diff --git a/Boka/Package.swift b/Boka/Package.swift index 1cdd988c..d07e00b0 100644 --- a/Boka/Package.swift +++ b/Boka/Package.swift @@ -14,6 +14,7 @@ let package = Package( .package(url: "https://github.com/apple/swift-argument-parser", from: "1.4.0"), .package(url: "https://github.com/slashmo/swift-otel.git", from: "0.9.0"), .package(url: "https://github.com/swift-server/swift-service-lifecycle.git", from: "2.6.0"), + .package(url: "https://github.com/vapor/console-kit.git", from: "4.14.3"), ], targets: [ // Targets are the basic building blocks of a package, defining a module or a test suite. @@ -27,6 +28,7 @@ let package = Package( .product(name: "ServiceLifecycle", package: "swift-service-lifecycle"), .product(name: "OTel", package: "swift-otel"), .product(name: "OTLPGRPC", package: "swift-otel"), + .product(name: "ConsoleKit", package: "console-kit"), ] ), .testTarget( diff --git a/Boka/Sources/Boka.swift b/Boka/Sources/Boka.swift index aea94e87..188f3095 100644 --- a/Boka/Sources/Boka.swift +++ b/Boka/Sources/Boka.swift @@ -13,12 +13,24 @@ import TracingUtils struct Boka: AsyncParsableCommand { mutating func run() async throws { let services = try await Tracing.bootstrap("Boka") - let node = try await Node(genesis: .dev, config: .dev) + let logger = Logger(label: "boka") + + logger.info("Starting Boka...") + + let config = Node.Config(rpc: RPCConfig(listenAddress: "127.0.0.1", port: 9955), protocol: .dev) + var node: Node! = try await Node(genesis: .dev, config: config) node.sayHello() - let config = ServiceGroupConfiguration(services: services, logger: Logger(label: "boka")) - let serviceGroup = ServiceGroup(configuration: config) + for service in services { + Task { + try await service.run() + } + } + + try await node.wait() + + node = nil - try await serviceGroup.run() + logger.info("Exiting...") } } diff --git a/Boka/Sources/Tracing.swift b/Boka/Sources/Tracing.swift index 43e165e7..da4514cc 100644 --- a/Boka/Sources/Tracing.swift +++ b/Boka/Sources/Tracing.swift @@ -1,3 +1,4 @@ +import ConsoleKit import OTel import OTLPGRPC import ServiceLifecycle @@ -6,11 +7,12 @@ import TracingUtils public enum Tracing { public static func bootstrap(_ serviceName: String) async throws -> [Service] { // Bootstrap the logging backend with the OTel metadata provider which includes span IDs in logging messages. - LoggingSystem.bootstrap { label in - var handler = StreamLogHandler.standardError(label: label, metadataProvider: .otel) - handler.logLevel = .trace - return handler - } + LoggingSystem.bootstrap( + fragment: timestampDefaultLoggerFragment(), + console: Terminal(), + level: .trace, + metadataProvider: .otel + ) // Configure OTel resource detection to automatically apply helpful attributes to events. let environment = OTelEnvironment.detected() diff --git a/Makefile b/Makefile index 8635761b..544b9d98 100644 --- a/Makefile +++ b/Makefile @@ -47,3 +47,7 @@ lint: githooks .PHONY: format format: githooks swiftformat . + +.PHONY: run +run: githooks + swift run --package-path Boka diff --git a/Node/Package.resolved b/Node/Package.resolved index 7dcbaed3..a65ae019 100644 --- a/Node/Package.resolved +++ b/Node/Package.resolved @@ -1,6 +1,24 @@ { - "originHash" : "9029b5282681161e046bf609299ad38f9148736fe1e0db6b71446a8b1ce5274b", + "originHash" : "9e44dd111684519469ea1d4307ee132a892ac71a51c358d02a5b1ff1d5323474", "pins" : [ + { + "identity" : "async-http-client", + "kind" : "remoteSourceControl", + "location" : "https://github.com/swift-server/async-http-client.git", + "state" : { + "revision" : "0ae99db85b2b9d1e79b362bd31fd1ffe492f7c47", + "version" : "1.21.2" + } + }, + { + "identity" : "async-kit", + "kind" : "remoteSourceControl", + "location" : "https://github.com/vapor/async-kit.git", + "state" : { + "revision" : "e048c8ee94967e8d8a1c2ec0e1156d6f7fa34d31", + "version" : "1.20.0" + } + }, { "identity" : "blake2.swift", "kind" : "remoteSourceControl", @@ -11,12 +29,30 @@ } }, { - "identity" : "grpc-swift", + "identity" : "console-kit", "kind" : "remoteSourceControl", - "location" : "https://github.com/grpc/grpc-swift.git", + "location" : "https://github.com/vapor/console-kit.git", "state" : { - "revision" : "6a90b7e77e29f9bda6c2b3a4165a40d6c02cfda1", - "version" : "1.23.0" + "revision" : "9f7932f22ab6f64aafadc14491e694179b7d0f6f", + "version" : "4.14.3" + } + }, + { + "identity" : "multipart-kit", + "kind" : "remoteSourceControl", + "location" : "https://github.com/vapor/multipart-kit.git", + "state" : { + "revision" : "a31236f24bfd2ea2f520a74575881f6731d7ae68", + "version" : "4.7.0" + } + }, + { + "identity" : "routing-kit", + "kind" : "remoteSourceControl", + "location" : "https://github.com/vapor/routing-kit.git", + "state" : { + "revision" : "8c9a227476555c55837e569be71944e02a056b72", + "version" : "4.9.1" } }, { @@ -29,12 +65,12 @@ } }, { - "identity" : "swift-async-algorithms", + "identity" : "swift-algorithms", "kind" : "remoteSourceControl", - "location" : "https://github.com/apple/swift-async-algorithms.git", + "location" : "https://github.com/apple/swift-algorithms.git", "state" : { - "revision" : "6ae9a051f76b81cc668305ceed5b0e0a7fd93d20", - "version" : "1.0.1" + "revision" : "f6919dfc309e7f1b56224378b11e28bab5bccc42", + "version" : "1.2.0" } }, { @@ -146,21 +182,12 @@ } }, { - "identity" : "swift-otel", - "kind" : "remoteSourceControl", - "location" : "https://github.com/slashmo/swift-otel.git", - "state" : { - "revision" : "8c271c7fed34a39f29c728598b3358fbdddf8ff4", - "version" : "0.9.0" - } - }, - { - "identity" : "swift-protobuf", + "identity" : "swift-numerics", "kind" : "remoteSourceControl", - "location" : "https://github.com/apple/swift-protobuf.git", + "location" : "https://github.com/apple/swift-numerics.git", "state" : { - "revision" : "e17d61f26df0f0e06f58f6977ba05a097a720106", - "version" : "1.27.1" + "revision" : "0a5bc04095a675662cf24757cc0640aa2204253b", + "version" : "1.0.2" } }, { @@ -172,15 +199,6 @@ "version" : "1.1.0" } }, - { - "identity" : "swift-service-lifecycle", - "kind" : "remoteSourceControl", - "location" : "https://github.com/swift-server/swift-service-lifecycle.git", - "state" : { - "revision" : "24c800fb494fbee6e42bc156dc94232dc08971af", - "version" : "2.6.1" - } - }, { "identity" : "swift-system", "kind" : "remoteSourceControl", @@ -198,6 +216,24 @@ "revision" : "4d2cf7c64443cdf4df833d0bedd767bf9dbc49d9", "version" : "0.1.3" } + }, + { + "identity" : "vapor", + "kind" : "remoteSourceControl", + "location" : "https://github.com/vapor/vapor.git", + "state" : { + "revision" : "a823735db57b46100b0c61cdfc5a08525b1e7cad", + "version" : "4.102.1" + } + }, + { + "identity" : "websocket-kit", + "kind" : "remoteSourceControl", + "location" : "https://github.com/vapor/websocket-kit.git", + "state" : { + "revision" : "4232d34efa49f633ba61afde365d3896fc7f8740", + "version" : "2.15.0" + } } ], "version" : 3 diff --git a/Node/Package.swift b/Node/Package.swift index 99f96c09..3ec1ded5 100644 --- a/Node/Package.swift +++ b/Node/Package.swift @@ -19,6 +19,7 @@ let package = Package( .package(path: "../Utils"), .package(path: "../Blockchain"), .package(path: "../TracingUtils"), + .package(path: "../RPC"), ], targets: [ // Targets are the basic building blocks of a package, defining a module or a test suite. @@ -28,6 +29,7 @@ let package = Package( "Utils", "Blockchain", "TracingUtils", + "RPC", ] ), .testTarget( diff --git a/Node/Sources/Node/Node.swift b/Node/Sources/Node/Node.swift index 60872856..b1727857 100644 --- a/Node/Sources/Node/Node.swift +++ b/Node/Sources/Node/Node.swift @@ -1,20 +1,40 @@ import Blockchain +import RPC import TracingUtils let logger = Logger(label: "node") +public typealias RPCConfig = RPCServer.Config + public class Node { + public class Config { + public let rpc: RPCServer.Config + public let protcol: ProtocolConfigRef + + public init(rpc: RPCServer.Config, protocol: ProtocolConfigRef) { + self.rpc = rpc + protcol = `protocol` + } + } + public private(set) var blockchain: Blockchain + public private(set) var rpcServer: RPCServer - public init(genesis: Genesis, config: ProtocolConfigRef) async throws { + public init(genesis: Genesis, config: Config) async throws { logger.debug("Initializing node") - let genesisState = try genesis.toState(config: config) + let genesisState = try genesis.toState(config: config.protcol) let dataProvider = await InMemoryDataProvider(genesis: genesisState) - blockchain = await Blockchain(config: config, dataProvider: dataProvider) + blockchain = await Blockchain(config: config.protcol, dataProvider: dataProvider) + + rpcServer = try RPCServer(config: config.rpc, source: blockchain) } public func sayHello() { logger.info("Hello, World!") } + + public func wait() async throws { + try await rpcServer.wait() + } } diff --git a/RPC/Package.swift b/RPC/Package.swift index 24133223..83db47f3 100644 --- a/RPC/Package.swift +++ b/RPC/Package.swift @@ -17,6 +17,7 @@ let package = Package( ], dependencies: [ .package(path: "../Blockchain"), + .package(path: "../Utils"), .package(url: "https://github.com/vapor/vapor.git", from: "4.102.1"), .package(url: "https://github.com/vapor/async-kit.git", from: "1.19.1"), .package(url: "https://github.com/apple/swift-testing.git", branch: "0.10.0"), @@ -28,6 +29,7 @@ let package = Package( name: "RPC", dependencies: [ "Blockchain", + "Utils", .product(name: "Vapor", package: "vapor"), .product(name: "AsyncKit", package: "async-kit"), ] diff --git a/RPC/Sources/RPC/DataSource/Blockchain+DataSource.swift b/RPC/Sources/RPC/DataSource/Blockchain+DataSource.swift new file mode 100644 index 00000000..77df1cd1 --- /dev/null +++ b/RPC/Sources/RPC/DataSource/Blockchain+DataSource.swift @@ -0,0 +1,4 @@ +import Blockchain +import Utils + +extension Blockchain: DataSource {} diff --git a/RPC/Sources/RPC/DataSource/DataSource.swift b/RPC/Sources/RPC/DataSource/DataSource.swift new file mode 100644 index 00000000..00fffdce --- /dev/null +++ b/RPC/Sources/RPC/DataSource/DataSource.swift @@ -0,0 +1,9 @@ +import Blockchain +import Utils + +public protocol DataSource: Sendable { + func getBestBlock() async throws -> BlockRef + func getBlock(hash: Data32) async throws -> BlockRef? + + func importBlock(_: BlockRef) async throws +} diff --git a/RPC/Sources/RPC/RPC.swift b/RPC/Sources/RPC/RPC.swift deleted file mode 100644 index 7b45c1e6..00000000 --- a/RPC/Sources/RPC/RPC.swift +++ /dev/null @@ -1,17 +0,0 @@ -// The Swift Programming Language -// https://docs.swift.org/swift-book -import Vapor - -public func startServer() throws { - let env = try Environment.detect() - let app = Application(env) - defer { app.shutdown() } - try configure(app) - try app.run() -} - -public func configure(_ app: Application) throws { - // Register routes - let rpcController = RPCController() - try app.register(collection: rpcController) -} diff --git a/RPC/Sources/RPC/RPCController.swift b/RPC/Sources/RPC/RPCController.swift index bf330fd9..f2abcdb2 100644 --- a/RPC/Sources/RPC/RPCController.swift +++ b/RPC/Sources/RPC/RPCController.swift @@ -1,32 +1,39 @@ import Blockchain +import Utils import Vapor -final class RPCController: RouteCollection { +final class RPCController: RouteCollection, Sendable { + let source: DataSource + + init(source: DataSource) { + self.source = source + } + func boot(routes: RoutesBuilder) throws { // HTTP JSON-RPC route - routes.post("rpc", use: handleRPCRequest) + routes.post("", use: handleRPCRequest) // WebSocket JSON-RPC route - routes.webSocket("ws", onUpgrade: handleWebSocket) + routes.webSocket("", onUpgrade: handleWebSocket) } - func handleRPCRequest(_ req: Request) -> EventLoopFuture { + func handleRPCRequest(_ req: Request) async throws -> Response { do { let rpcRequest = try req.content.decode(RPCRequest.self) // Handle the JSON-RPC request - let result = try RPCController.handleMethod(rpcRequest.method, params: rpcRequest.params) + let result = try await handleMethod(rpcRequest.method, params: rpcRequest.params) let rpcResponse = RPCResponse(jsonrpc: "2.0", result: AnyContent(result ?? ""), error: nil, id: rpcRequest.id) - return try req.eventLoop.makeSucceededFuture(Response(status: .ok, body: .init(data: JSONEncoder().encode(rpcResponse)))) + return try Response(status: .ok, body: .init(data: JSONEncoder().encode(rpcResponse))) } catch { let rpcError = RPCError(code: -32600, message: "Invalid Request") let rpcResponse = RPCResponse(jsonrpc: "2.0", result: nil, error: rpcError, id: nil) do { let responseData = try JSONEncoder().encode(rpcResponse) - return req.eventLoop.makeSucceededFuture(Response(status: .badRequest, body: .init(data: responseData))) + return Response(status: .badRequest, body: .init(data: responseData)) } catch { print("Failed to encode error response: \(error)") - return req.eventLoop.makeSucceededFuture(Response(status: .badRequest, body: .init(data: Data()))) + return Response(status: .badRequest, body: .init(data: Data())) } } } @@ -34,15 +41,15 @@ final class RPCController: RouteCollection { func handleWebSocket(req _: Request, ws: WebSocket) { ws.onText { ws, text in Task { - await RPCController.processWebSocketRequest(ws, text: text) + await self.processWebSocketRequest(ws, text: text) } } } - private static func processWebSocketRequest(_ ws: WebSocket, text: String) async { + private func processWebSocketRequest(_ ws: WebSocket, text: String) async { do { let rpcRequest = try JSONDecoder().decode(RPCRequest.self, from: Data(text.utf8)) - let result = try handleMethod(rpcRequest.method, params: rpcRequest.params?.value) + let result = try await handleMethod(rpcRequest.method, params: rpcRequest.params?.value) let rpcResponse = RPCResponse(jsonrpc: "2.0", result: AnyContent(result ?? ""), error: nil, id: rpcRequest.id) let responseData = try JSONEncoder().encode(rpcResponse) try await ws.send(String(decoding: responseData, as: UTF8.self)) @@ -59,28 +66,35 @@ final class RPCController: RouteCollection { } } - static func handleChainGetBlock(params _: BlockParams?) -> CodableBlock? { + func handleChainGetBlock(params: BlockParams?) async throws -> CodableBlock? { // Fetch the block by hash or number - nil + if let hash = params?.blockHash { + guard let data = Data(fromHexString: hash), let data32 = Data32(data) else { + throw RPCError(code: -32602, message: "Invalid block hash") + } + let block = try await source.getBlock(hash: data32) + return block.map { CodableBlock(from: $0) } + } else { + let block = try await source.getBestBlock() + return CodableBlock(from: block) + } } - static func handleChainGetHeader(params _: HeaderParams?) -> CodableHeader? { + func handleChainGetHeader(params _: HeaderParams?) async throws -> CodableHeader? { // Fetch the header by hash or number nil } - static func handleMethod(_ method: String, params: Any?) throws -> Any? { + func handleMethod(_ method: String, params: Any?) async throws -> Any? { switch method { case "health": return true case "chain_getBlock": - return handleChainGetBlock(params: params as? BlockParams) + return try await handleChainGetBlock(params: params as? BlockParams) case "chain_getHeader": - return handleChainGetHeader(params: params as? HeaderParams) + return try await handleChainGetHeader(params: params as? HeaderParams) default: throw RPCError(code: -32601, message: "Method not found") } } } - -extension RPCController: @unchecked Sendable {} diff --git a/RPC/Sources/RPC/RPCModels.swift b/RPC/Sources/RPC/RPCModels.swift index 20c4edf4..f2683093 100644 --- a/RPC/Sources/RPC/RPCModels.swift +++ b/RPC/Sources/RPC/RPCModels.swift @@ -42,7 +42,7 @@ public struct CodableBlock: Codable { let property2: String // Add all properties from the original Block type - init(from block: Block) { + init(from block: BlockRef) { property1 = block.header.parentHash.description property2 = block.header.extrinsicsRoot.description // Initialize all properties from the original Block type diff --git a/RPC/Sources/RPC/RPCServer.swift b/RPC/Sources/RPC/RPCServer.swift new file mode 100644 index 00000000..e329faf2 --- /dev/null +++ b/RPC/Sources/RPC/RPCServer.swift @@ -0,0 +1,46 @@ +import Foundation +import Vapor + +public class RPCServer { + public enum Error: Swift.Error { + case invalidListenAddress(address: String) + } + + public class Config { + public let listenAddress: String + public let port: Int + + public init(listenAddress: String, port: Int) { + self.listenAddress = listenAddress + self.port = port + } + } + + private let config: Config + private let source: DataSource + private let app: Application + + public init(config: Config, source: DataSource) throws { + self.config = config + self.source = source + + let env = try Environment.detect() + app = Application(env) + + // Register routes + let rpcController = RPCController(source: source) + try app.register(collection: rpcController) + + app.http.server.configuration.address = .hostname(config.listenAddress, port: config.port) + + try app.start() + } + + deinit { + app.shutdown() + } + + public func wait() async throws { + try await app.running?.onStop.get() + } +} diff --git a/TracingUtils/Sources/TracingUtils/Tracing.swift b/TracingUtils/Sources/TracingUtils/export.swift similarity index 100% rename from TracingUtils/Sources/TracingUtils/Tracing.swift rename to TracingUtils/Sources/TracingUtils/export.swift diff --git a/TracingUtils/Sources/TracingUtils/unreachable.swift b/TracingUtils/Sources/TracingUtils/unreachable.swift new file mode 100644 index 00000000..fb382c29 --- /dev/null +++ b/TracingUtils/Sources/TracingUtils/unreachable.swift @@ -0,0 +1,17 @@ +import Logging + +let logger = Logger(label: "tracing-utils.assertions") + +enum AssertionError: Error { + case unreachable(String) +} + +public func throwUnreachable(_ msg: String, file: String = #fileID, function: String = #function, line: UInt = #line) throws -> Never { + unreachable(msg, file: file, function: function, line: line) + throw AssertionError.unreachable(msg) +} + +public func unreachable(_ msg: String, file: String = #fileID, function: String = #function, line: UInt = #line) { + logger.error("unreachable: \(msg)", metadata: nil, source: nil, file: file, function: function, line: line) + assertionFailure(msg) +} diff --git a/Utils/Sources/Utils/Data+Utils.swift b/Utils/Sources/Utils/Data+Utils.swift index a8340d33..4508b6a6 100644 --- a/Utils/Sources/Utils/Data+Utils.swift +++ b/Utils/Sources/Utils/Data+Utils.swift @@ -1,7 +1,7 @@ import Foundation extension Data { - init?(fromHexString hexString: String) { + public init?(fromHexString hexString: String) { guard !hexString.isEmpty else { return nil }