diff --git a/Blockchain/Sources/Blockchain/Config/ProtocolConfig.swift b/Blockchain/Sources/Blockchain/Config/ProtocolConfig.swift index f2b68cbc..14e8aee0 100644 --- a/Blockchain/Sources/Blockchain/Config/ProtocolConfig.swift +++ b/Blockchain/Sources/Blockchain/Config/ProtocolConfig.swift @@ -2,7 +2,7 @@ import PolkaVM import Utils // constants defined in the graypaper -public struct ProtocolConfig: Sendable, Codable { +public struct ProtocolConfig: Sendable, Codable, Equatable { // A = 8: The period, in seconds, between audit tranches. public var auditTranchePeriod: Int diff --git a/Blockchain/Sources/Blockchain/Types/Header.swift b/Blockchain/Sources/Blockchain/Types/Header.swift index d4151ab4..8c742810 100644 --- a/Blockchain/Sources/Blockchain/Types/Header.swift +++ b/Blockchain/Sources/Blockchain/Types/Header.swift @@ -102,8 +102,11 @@ extension Header: Codable { priorStateRoot: container.decode(Data32.self, forKey: .priorStateRoot), extrinsicsHash: container.decode(Data32.self, forKey: .extrinsicsHash), timeslot: container.decode(UInt32.self, forKey: .timeslot), - epoch: container.decode(EpochMarker?.self, forKey: .epoch), - winningTickets: container.decode(ConfigFixedSizeArray?.self, forKey: .winningTickets), + epoch: container.decodeIfPresent(EpochMarker.self, forKey: .epoch), + winningTickets: container.decodeIfPresent( + ConfigFixedSizeArray.self, + forKey: .winningTickets + ), offendersMarkers: container.decode([Ed25519PublicKey].self, forKey: .offendersMarkers), authorIndex: container.decode(ValidatorIndex.self, forKey: .authorIndex), vrfSignature: container.decode(BandersnatchSignature.self, forKey: .vrfSignature) diff --git a/Blockchain/Sources/Blockchain/Types/PrivilegedServices.swift b/Blockchain/Sources/Blockchain/Types/PrivilegedServices.swift index a7f0c8cd..30061f48 100644 --- a/Blockchain/Sources/Blockchain/Types/PrivilegedServices.swift +++ b/Blockchain/Sources/Blockchain/Types/PrivilegedServices.swift @@ -1,3 +1,6 @@ +import Codec +import Utils + public struct PrivilegedServices: Sendable, Equatable, Codable { // m public var empower: ServiceIndex @@ -6,7 +9,7 @@ public struct PrivilegedServices: Sendable, Equatable, Codable { // v public var designate: ServiceIndex // g - public var basicGas: [ServiceIndex: Gas] + @CodingAs> public var basicGas: [ServiceIndex: Gas] public init(empower: ServiceIndex, assign: ServiceIndex, designate: ServiceIndex, basicGas: [ServiceIndex: Gas]) { self.empower = empower diff --git a/Blockchain/Sources/Blockchain/Types/State.swift b/Blockchain/Sources/Blockchain/Types/State.swift index e467bea4..d2f0aa4e 100644 --- a/Blockchain/Sources/Blockchain/Types/State.swift +++ b/Blockchain/Sources/Blockchain/Types/State.swift @@ -20,7 +20,7 @@ public struct State: Sendable, Equatable, Codable { public var safroleState: SafroleState // δ: The (prior) state of the service accounts. - public var serviceAccounts: [ServiceIndex: ServiceAccount] + @CodingAs> public var serviceAccounts: [ServiceIndex: ServiceAccount] // η: The eηtropy accumulator and epochal raηdomness. public var entropyPool: EntropyPool @@ -126,6 +126,10 @@ extension State { public var lastBlockHash: Data32 { recentHistory.items.last.map(\.headerHash)! } + + public func asRef() -> StateRef { + StateRef(self) + } } extension State: Dummy { diff --git a/Blockchain/Tests/BlockchainTests/BlockchainDataProviderTests.swift b/Blockchain/Tests/BlockchainTests/BlockchainDataProviderTests.swift index 06924fe4..27286e99 100644 --- a/Blockchain/Tests/BlockchainTests/BlockchainDataProviderTests.swift +++ b/Blockchain/Tests/BlockchainTests/BlockchainDataProviderTests.swift @@ -11,7 +11,7 @@ struct BlockchainDataProviderTests { let provider: BlockchainDataProvider init() async throws { - setupTestLogger() + // setupTestLogger() (genesisState, genesisBlock) = try State.devGenesis(config: config) provider = try await BlockchainDataProvider(InMemoryDataProvider(genesisState: genesisState, genesisBlock: genesisBlock)) diff --git a/Boka/Package.swift b/Boka/Package.swift index a6d59481..35a89f20 100644 --- a/Boka/Package.swift +++ b/Boka/Package.swift @@ -37,9 +37,6 @@ let package = Package( dependencies: [ "Boka", .product(name: "Testing", package: "swift-testing"), - ], - resources: [ - .copy("chainfiles"), ] ), ], diff --git a/Boka/Sources/Boka.swift b/Boka/Sources/Boka.swift index babaeb57..1af35e1e 100644 --- a/Boka/Sources/Boka.swift +++ b/Boka/Sources/Boka.swift @@ -51,7 +51,8 @@ enum MaybeEnabled: ExpressibleByArgument { struct Boka: AsyncParsableCommand { static let configuration = CommandConfiguration( abstract: "JAM built with Swift", - version: "0.0.1" + version: "0.0.1", + subcommands: [Generate.self] ) @Option(name: .shortAndLong, help: "Base path to database files.") diff --git a/Boka/Sources/Generate.swift b/Boka/Sources/Generate.swift new file mode 100644 index 00000000..b6eb3ff8 --- /dev/null +++ b/Boka/Sources/Generate.swift @@ -0,0 +1,44 @@ +import ArgumentParser +import Codec +import Foundation +import Node +import Utils + +enum OutputFormat: String, ExpressibleByArgument { + case json + case binary +} + +struct Generate: AsyncParsableCommand { + static let configuration = CommandConfiguration( + abstract: "Generate new chainspec file" + ) + + @Argument(help: "output file") + var output: String + + @Option(name: .long, help: "A preset config or path to chain config file.") + var chain: Genesis = .preset(.minimal) + + @Option(name: .long, help: "The output format. json or binary.") + var format: OutputFormat = .json + + @Option(name: .long, help: "The chain name.") + var name: String = "Devnet" + + @Option(name: .long, help: "The chain id.") + var id: String = "dev" + + func run() async throws { + let chainspec = try await chain.load() + let data = switch format { + case .json: + try chainspec.encode() + case .binary: + try chainspec.encodeBinary() + } + try data.write(to: URL(fileURLWithPath: output)) + + print("Chainspec generated at \(output)") + } +} diff --git a/Boka/Tests/BokaTests/BokaTests.swift b/Boka/Tests/BokaTests/BokaTests.swift index 1226f1f2..9c3e8bab 100644 --- a/Boka/Tests/BokaTests/BokaTests.swift +++ b/Boka/Tests/BokaTests/BokaTests.swift @@ -5,13 +5,6 @@ import Testing @testable import Boka -enum ResourceLoader { - static func loadResource(named name: String) -> URL? { - let bundle = Bundle.module - return bundle.url(forResource: name, withExtension: nil, subdirectory: "chainfiles") - } -} - struct BokaTests { @Test func commandWithWrongFilePath() async throws { let sepc = "/path/to/wrong/file.json" @@ -20,30 +13,4 @@ struct BokaTests { try await boka.run() } } - - @Test func commandWithAllConfig() async throws { - let sepc = ResourceLoader.loadResource(named: "devnet_allconfig_spec.json")!.path() - let genesis: Genesis = .file(path: sepc) - let (_, _, protocolConfig) = try await genesis.load() - #expect(protocolConfig.value.maxWorkItems == 2) - #expect(protocolConfig.value.serviceMinBalance == 100) - } - - @Test func commandWithSomeConfig() async throws { - let sepc = ResourceLoader.loadResource(named: "mainnet_someconfig_spec.json")!.path() - let genesis: Genesis = .file(path: sepc) - let config = ProtocolConfigRef.mainnet.value - let (_, _, protocolConfig) = try await genesis.load() - #expect(protocolConfig.value.auditTranchePeriod == 100) - #expect(protocolConfig.value.pvmProgramInitSegmentSize == config.pvmProgramInitSegmentSize) - } - - @Test func commandWithNoConfig() async throws { - let sepc = ResourceLoader.loadResource(named: "devnet_noconfig_spec.json")!.path() - let genesis: Genesis = .file(path: sepc) - let config = ProtocolConfigRef.dev.value - let (_, _, protocolConfig) = try await genesis.load() - #expect(protocolConfig.value.maxWorkItems == config.maxWorkItems) - #expect(protocolConfig.value.serviceMinBalance == config.serviceMinBalance) - } } diff --git a/Boka/Tests/BokaTests/chainfiles/devnet_allconfig_spec.json b/Boka/Tests/BokaTests/chainfiles/devnet_allconfig_spec.json deleted file mode 100644 index 7613c9a8..00000000 --- a/Boka/Tests/BokaTests/chainfiles/devnet_allconfig_spec.json +++ /dev/null @@ -1,42 +0,0 @@ -{ - "name": "Devnet", - "id": "devnet-1", - "bootnodes": ["127.0.0.1:9955"], - "config": { - "auditTranchePeriod": 10, - "additionalMinBalancePerStateItem": 1, - "additionalMinBalancePerStateByte": 1, - "serviceMinBalance": 100, - "totalNumberOfCores": 1, - "preimagePurgePeriod": 100, - "epochLength": 1000, - "auditBiasFactor": 1, - "coreAccumulationGas": 10, - "workPackageAuthorizerGas": 5, - "workPackageRefineGas": 2, - "recentHistorySize": 10, - "maxWorkItems": 2, - "maxTicketsPerExtrinsic": 1, - "maxLookupAnchorAge": 10, - "transferMemoSize": 64, - "ticketEntriesPerValidator": 1, - "maxAuthorizationsPoolItems": 10, - "slotPeriodSeconds": 1, - "maxAuthorizationsQueueItems": 5, - "coreAssignmentRotationPeriod": 100, - "maxServiceCodeSize": 256, - "preimageReplacementPeriod": 100, - "totalNumberOfValidators": 5, - "erasureCodedPieceSize": 128, - "maxWorkPackageManifestEntries": 2, - "maxEncodedWorkPackageSize": 512, - "maxEncodedWorkReportSize": 256, - "erasureCodedSegmentSize": 1024, - "ticketSubmissionEndSlot": 10, - "pvmDynamicAddressAlignmentFactor": 2, - "pvmProgramInitInputDataSize": 64, - "pvmProgramInitPageSize": 1024, - "pvmProgramInitSegmentSize": 2048 - }, - "state": "0x789abc" -} diff --git a/Boka/Tests/BokaTests/chainfiles/devnet_noconfig_spec.json b/Boka/Tests/BokaTests/chainfiles/devnet_noconfig_spec.json deleted file mode 100644 index 92f107cc..00000000 --- a/Boka/Tests/BokaTests/chainfiles/devnet_noconfig_spec.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "name": "MainNet", - "id": "mainnet-456", - "bootnodes": [ - "node1.mainnet.com", - "node2.mainnet.com" - ], - "preset": "dev", - "state": "0x1234567890abcdef" - } - \ No newline at end of file diff --git a/Boka/Tests/BokaTests/chainfiles/mainnet_someconfig_spec.json b/Boka/Tests/BokaTests/chainfiles/mainnet_someconfig_spec.json deleted file mode 100644 index 5bc02817..00000000 --- a/Boka/Tests/BokaTests/chainfiles/mainnet_someconfig_spec.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "name": "Mainnet", - "id": "mainnet-1", - "bootnodes": [ - "127.0.0.1:9955" - ], - "preset": "mainnet", - "config": { - "auditTranchePeriod": 100, - "additionalMinBalancePerStateItem": 10, - "additionalMinBalancePerStateByte": 1, - "serviceMinBalance": 1000, - "totalNumberOfCores": 4, - "preimagePurgePeriod": 1000, - "epochLength": 10000, - "auditBiasFactor": 5, - "coreAccumulationGas": 100, - "workPackageAuthorizerGas": 50, - "workPackageRefineGas": 20, - "recentHistorySize": 100, - "maxWorkItems": 10, - "maxTicketsPerExtrinsic": 5, - "maxLookupAnchorAge": 100 - }, - "state": "0xabcdef" -} diff --git a/Codec/Sources/Codec/CodingUserInfoKey+Utils.swift b/Codec/Sources/Codec/CodingUserInfoKey+Utils.swift index 008c1fc0..c2de2830 100644 --- a/Codec/Sources/Codec/CodingUserInfoKey+Utils.swift +++ b/Codec/Sources/Codec/CodingUserInfoKey+Utils.swift @@ -3,6 +3,14 @@ extension CodingUserInfoKey { public static let isJamCodec = CodingUserInfoKey(rawValue: "isJamCodec")! } +public class ConfigRef { + public var value: C? + + public init(_ value: C? = nil) { + self.value = value + } +} + extension Encoder { public var isJamCodec: Bool { userInfo[.isJamCodec] as? Bool ?? false @@ -19,6 +27,18 @@ extension Decoder { } public func getConfig(_: C.Type) -> C? { - userInfo[.config] as? C + if let config = userInfo[.config] as? C { + return config + } + if let config = userInfo[.config] as? ConfigRef { + return config.value + } + return nil + } + + public func setConfig(_ config: C) { + if let ref = userInfo[.config] as? ConfigRef { + ref.value = config + } } } diff --git a/Codec/Sources/Codec/JamDecoder.swift b/Codec/Sources/Codec/JamDecoder.swift index 641d54ba..5daa63d5 100644 --- a/Codec/Sources/Codec/JamDecoder.swift +++ b/Codec/Sources/Codec/JamDecoder.swift @@ -269,6 +269,23 @@ private struct JamKeyedDecodingContainer: KeyedDecodingContainerPr try decoder.decode(type, key: key) } + func decodeIfPresent(_ type: T.Type, forKey key: K) throws -> T? { + let byte = try decoder.input.read() + switch byte { + case 0: + return nil + case 1: + return try decoder.decode(type, key: key) + default: + throw DecodingError.dataCorrupted( + DecodingError.Context( + codingPath: decoder.codingPath, + debugDescription: "Invalid boolean value: \(byte)" + ) + ) + } + } + func nestedContainer(keyedBy _: NestedKey.Type, forKey _: K) throws -> KeyedDecodingContainer where NestedKey: CodingKey { diff --git a/Codec/Sources/Codec/SortedKeyValues.swift b/Codec/Sources/Codec/SortedKeyValues.swift new file mode 100644 index 00000000..d47e296c --- /dev/null +++ b/Codec/Sources/Codec/SortedKeyValues.swift @@ -0,0 +1,48 @@ +import Foundation + +struct KeyValuePair: Codable { + var key: Key + var value: Value +} + +public struct SortedKeyValues: Codable, CodableAlias { + public typealias Alias = [Key: Value] + + public var alias: Alias + + public init(alias: Alias) { + self.alias = alias + } + + public init(from decoder: Decoder) throws { + let container = try decoder.singleValueContainer() + let array = try container.decode([KeyValuePair].self) + + // ensure array is sorted and unique + var previous: KeyValuePair? + for item in array { + guard previous == nil || item.key > previous!.key else { + throw DecodingError.dataCorrupted( + DecodingError.Context( + codingPath: container.codingPath, + debugDescription: "Array is not sorted" + ) + ) + } + previous = item + } + + alias = .init(uniqueKeysWithValues: array.map { ($0.key, $0.value) }) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.singleValueContainer() + var array = alias.map { KeyValuePair(key: $0.key, value: $0.value) } + array.sort { $0.key < $1.key } + try container.encode(array) + } +} + +extension SortedKeyValues: Sendable where Key: Sendable, Value: Sendable, Alias: Sendable {} + +extension SortedKeyValues: Equatable where Key: Equatable, Value: Equatable, Alias: Equatable {} diff --git a/Node/Package.swift b/Node/Package.swift index cfb6c483..1da05f07 100644 --- a/Node/Package.swift +++ b/Node/Package.swift @@ -42,6 +42,9 @@ let package = Package( dependencies: [ "Node", .product(name: "Testing", package: "swift-testing"), + ], + resources: [ + .copy("chainfiles"), ] ), ], diff --git a/Node/Sources/Node/ChainSpec.swift b/Node/Sources/Node/ChainSpec.swift new file mode 100644 index 00000000..33b6e84c --- /dev/null +++ b/Node/Sources/Node/ChainSpec.swift @@ -0,0 +1,193 @@ +import Blockchain +import Codec +import Foundation +import Utils + +extension KeyedDecodingContainer { + func decode(_: ProtocolConfig.Type, forKey key: K, required: Bool = true) throws -> ProtocolConfig { + let nestedDecoder = try superDecoder(forKey: key) + return try ProtocolConfig(from: nestedDecoder, required) + } + + func decodeIfPresent(_: ProtocolConfig.Type, forKey key: K, required: Bool = false) throws -> ProtocolConfig? { + guard contains(key) else { return nil } + let nestedDecoder = try superDecoder(forKey: key) + return try ProtocolConfig(from: nestedDecoder, required) + } +} + +private func mergeConfig(preset: GenesisPreset?, config: ProtocolConfig?) throws -> ProtocolConfigRef { + if let preset { + let ret = preset.config.value + if let genesisConfig = config { + return Ref(ret.merged(with: genesisConfig)) + } + return Ref(ret) + } + if let config { + return Ref(config) + } + throw GenesisError.invalidFormat("One of 'preset' or 'config' is required") +} + +public struct ChainSpec: Codable, Equatable { + public var name: String + public var id: String + public var bootnodes: [String] + public var preset: GenesisPreset? + public var config: ProtocolConfig? + public var block: Block + public var state: State + + public init( + name: String, + id: String, + bootnodes: [String], + preset: GenesisPreset?, + config: ProtocolConfig?, + block: Block, + state: State + ) { + self.name = name + self.id = id + self.bootnodes = bootnodes + self.preset = preset + self.config = config + self.block = block + self.state = state + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + name = try container.decode(String.self, forKey: .name) + id = try container.decode(String.self, forKey: .id) + bootnodes = try container.decode([String].self, forKey: .bootnodes) + preset = try container.decodeIfPresent(GenesisPreset.self, forKey: .preset) + if preset == nil { + config = try container.decode(ProtocolConfig.self, forKey: .config, required: true) + } else { + config = try container.decodeIfPresent(ProtocolConfig.self, forKey: .config, required: false) + } + + try decoder.setConfig(mergeConfig(preset: preset, config: config)) + + block = try container.decode(Block.self, forKey: .block) + state = try container.decode(State.self, forKey: .state) + } + + public func getConfig() throws -> ProtocolConfigRef { + try mergeConfig(preset: preset, config: config) + } + + public func encode() throws -> Data { + let encoder = JSONEncoder() + encoder.dataEncodingStrategy = .hex + encoder.outputFormatting = [.prettyPrinted, .withoutEscapingSlashes, .sortedKeys] + encoder.userInfo[.config] = try getConfig() + return try encoder.encode(self) + } + + public func encodeBinary() throws -> Data { + let encoder = JSONEncoder() + encoder.dataEncodingStrategy = .hex + encoder.outputFormatting = [.prettyPrinted, .withoutEscapingSlashes, .sortedKeys] + encoder.userInfo[.config] = try getConfig() + let binary = try ChainSpecBinary( + name: name, + id: id, + bootnodes: bootnodes, + preset: preset, + config: config, + block: JamEncoder.encode(block), + state: JamEncoder.encode(state) + ) + return try encoder.encode(binary) + } + + public static func decode(from data: Data) throws -> ChainSpec { + let decoder = JSONDecoder() + decoder.dataDecodingStrategy = .hex + let ref: ConfigRef = .init() + decoder.userInfo[.config] = ref + if let chainspec = try? decoder.decode(ChainSpec.self, from: data) { + try chainspec.validate() + return chainspec + } + let binary = try decoder.decode(ChainSpecBinary.self, from: data) + let chainspec = try binary.toChainSpec() + try chainspec.validate() + return chainspec + } + + private func validate() throws { + // Validate required fields + if name.isEmpty { + throw GenesisError.invalidFormat("Missing 'name'") + } + if id.isEmpty { + throw GenesisError.invalidFormat("Missing 'id'") + } + if preset == nil, config == nil { + throw GenesisError.invalidFormat("One of 'preset' or 'config' is required") + } + } +} + +public struct ChainSpecBinary: Codable { + public var name: String + public var id: String + public var bootnodes: [String] + public var preset: GenesisPreset? + public var config: ProtocolConfig? + public var block: Data + public var state: Data + + public init( + name: String, + id: String, + bootnodes: [String], + preset: GenesisPreset?, + config: ProtocolConfig?, + block: Data, + state: Data + ) { + self.name = name + self.id = id + self.bootnodes = bootnodes + self.preset = preset + self.config = config + self.block = block + self.state = state + } + + public func toChainSpec() throws -> ChainSpec { + let finalConfig = try mergeConfig(preset: preset, config: config) + let block = try JamDecoder(data: block, config: finalConfig).decode(Block.self) + let state = try JamDecoder(data: state, config: finalConfig).decode(State.self) + return ChainSpec( + name: name, + id: id, + bootnodes: bootnodes, + preset: preset, + config: config, + block: block, + state: state + ) + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + name = try container.decode(String.self, forKey: .name) + id = try container.decode(String.self, forKey: .id) + bootnodes = try container.decode([String].self, forKey: .bootnodes) + preset = try container.decodeIfPresent(GenesisPreset.self, forKey: .preset) + if preset == nil { + config = try container.decode(ProtocolConfig.self, forKey: .config, required: true) + } else { + config = try container.decodeIfPresent(ProtocolConfig.self, forKey: .config, required: false) + } + + block = try container.decode(Data.self, forKey: .block) + state = try container.decode(Data.self, forKey: .state) + } +} diff --git a/Node/Sources/Node/Genesis.swift b/Node/Sources/Node/Genesis.swift index f81827e2..e176d87d 100644 --- a/Node/Sources/Node/Genesis.swift +++ b/Node/Sources/Node/Genesis.swift @@ -1,4 +1,5 @@ import Blockchain +import Codec import Foundation import Utils @@ -38,96 +39,32 @@ public enum GenesisError: Error { } extension Genesis { - public func load() async throws -> (StateRef, BlockRef, ProtocolConfigRef) { + public func load() async throws -> ChainSpec { switch self { case let .preset(preset): let config = preset.config let (state, block) = try State.devGenesis(config: config) - return (state, block, config) + return ChainSpec( + name: preset.rawValue, + id: preset.rawValue, + bootnodes: [], + preset: preset, + config: config.value, + block: block.value, + state: state.value + ) case let .file(path): - let genesis = try readAndValidateGenesis(from: path) - var config: ProtocolConfig - if let preset = genesis.preset { - config = preset.config.value - if let genesisConfig = genesis.config { - config = config.merged(with: genesisConfig) - } - } else { - // The decoder ensures that genesis.config is non-nil when there is no preset - config = genesis.config! - } - let configRef = Ref(config) - let (state, block) = try State.devGenesis(config: configRef) - return (state, block, configRef) + let data = try readFile(from: path) + return try ChainSpec.decode(from: data) } } - private func validate(_ genesis: GenesisData) throws { - // Validate required fields - if genesis.name.isEmpty { - throw GenesisError.invalidFormat("Invalid or missing 'name'") - } - if genesis.id.isEmpty { - throw GenesisError.invalidFormat("Invalid or missing 'id'") - } - if genesis.bootnodes.isEmpty { - throw GenesisError.invalidFormat("Invalid or missing 'bootnodes'") - } - if genesis.state.isEmpty { - throw GenesisError.invalidFormat("Invalid or missing 'state'") - } - } - - func readAndValidateGenesis(from filePath: String) throws -> GenesisData { + private func readFile(from filePath: String) throws -> Data { do { let fileContents = try String(contentsOfFile: filePath, encoding: .utf8) - let data = fileContents.data(using: .utf8)! - let decoder = JSONDecoder() - let genesis = try decoder.decode(GenesisData.self, from: data) - try validate(genesis) - return genesis - } catch let error as GenesisError { - throw error + return fileContents.data(using: .utf8)! } catch { throw GenesisError.fileReadError(error) } } } - -extension KeyedDecodingContainer { - func decode(_: ProtocolConfig.Type, forKey key: K, required: Bool = true) throws -> ProtocolConfig { - let nestedDecoder = try superDecoder(forKey: key) - return try ProtocolConfig(from: nestedDecoder, required) - } - - func decodeIfPresent(_: ProtocolConfig.Type, forKey key: K, required: Bool = false) throws -> ProtocolConfig? { - guard contains(key) else { return nil } - let nestedDecoder = try superDecoder(forKey: key) - return try ProtocolConfig(from: nestedDecoder, required) - } -} - -struct GenesisData: Codable { - var name: String - var id: String - var bootnodes: [String] - var preset: GenesisPreset? - var config: ProtocolConfig? - // TODO: check & deal with state - var state: String - - // ensure one of preset or config is present - init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - name = try container.decode(String.self, forKey: .name) - id = try container.decode(String.self, forKey: .id) - bootnodes = try container.decode([String].self, forKey: .bootnodes) - preset = try container.decodeIfPresent(GenesisPreset.self, forKey: .preset) - if preset == nil { - config = try container.decode(ProtocolConfig.self, forKey: .config, required: true) - } else { - config = try container.decodeIfPresent(ProtocolConfig.self, forKey: .config, required: false) - } - state = try container.decode(String.self, forKey: .state) - } -} diff --git a/Node/Sources/Node/Node.swift b/Node/Sources/Node/Node.swift index 9ab7da4d..35b0d83a 100644 --- a/Node/Sources/Node/Node.swift +++ b/Node/Sources/Node/Node.swift @@ -41,7 +41,10 @@ public class Node { ) async throws { self.config = config - let (genesisState, genesisBlock, protocolConfig) = try await genesis.load() + let chainspec = try await genesis.load() + let genesisBlock = chainspec.block.asRef() + let genesisState = chainspec.state.asRef() + let protocolConfig = try chainspec.getConfig() logger.info("Genesis: \(genesisBlock.hash)") diff --git a/Node/Tests/NodeTests/ChainSpecTests.swift b/Node/Tests/NodeTests/ChainSpecTests.swift new file mode 100644 index 00000000..b1d6a38e --- /dev/null +++ b/Node/Tests/NodeTests/ChainSpecTests.swift @@ -0,0 +1,77 @@ +import Blockchain +import Foundation +import Testing + +@testable import Node + +enum ResourceLoader { + static func loadResource(named name: String) -> URL? { + let bundle = Bundle.module + return bundle.url(forResource: name, withExtension: nil, subdirectory: "chainfiles") + } +} + +struct ChainSpecTests { + @Test func testPresetLoading() async throws { + // Test loading different presets + for preset in GenesisPreset.allCases { + let genesis = Genesis.preset(preset) + let chainspec = try await genesis.load() + let state = chainspec.state.asRef() + let block = chainspec.block.asRef() + let config = try chainspec.getConfig() + + #expect(state.value.lastBlockHash == block.hash) + + // Verify config matches preset + #expect(config == preset.config) + } + } + + @Test func encodeDecodeChainSpec() async throws { + let genesis = Genesis.preset(.minimal) + let chainspec = try await genesis.load() + + let data = try chainspec.encode() + let decoded = try ChainSpec.decode(from: data) + #expect(decoded == chainspec) + } + + @Test func encodeDecodeChainSpecBinary() async throws { + let genesis = Genesis.preset(.minimal) + let chainspec = try await genesis.load() + + let data = try chainspec.encodeBinary() + let decoded = try ChainSpec.decode(from: data) + #expect(decoded == chainspec) + } + + @Test func commandWithAllConfig() async throws { + let sepc = ResourceLoader.loadResource(named: "devnet_allconfig_spec.json")!.path() + let genesis: Genesis = .file(path: sepc) + let chainspec = try await genesis.load() + let protocolConfig = try chainspec.getConfig() + #expect(protocolConfig.value.maxWorkItems == 2) + #expect(protocolConfig.value.serviceMinBalance == 100) + } + + @Test func commandWithSomeConfig() async throws { + let sepc = ResourceLoader.loadResource(named: "mainnet_someconfig_spec.json")!.path() + let genesis: Genesis = .file(path: sepc) + let config = ProtocolConfigRef.mainnet.value + let chainspec = try await genesis.load() + let protocolConfig = try chainspec.getConfig() + #expect(protocolConfig.value.auditTranchePeriod == 100) + #expect(protocolConfig.value.slotPeriodSeconds == config.slotPeriodSeconds) + } + + @Test func commandWithNoConfig() async throws { + let sepc = ResourceLoader.loadResource(named: "devnet_noconfig_spec.json")!.path() + let genesis: Genesis = .file(path: sepc) + let config = ProtocolConfigRef.dev.value + let chainspec = try await genesis.load() + let protocolConfig = try chainspec.getConfig() + #expect(protocolConfig.value.maxWorkItems == config.maxWorkItems) + #expect(protocolConfig.value.serviceMinBalance == config.serviceMinBalance) + } +} diff --git a/Node/Tests/NodeTests/chainfiles/devnet_allconfig_spec.json b/Node/Tests/NodeTests/chainfiles/devnet_allconfig_spec.json new file mode 100644 index 00000000..9fcf12bf --- /dev/null +++ b/Node/Tests/NodeTests/chainfiles/devnet_allconfig_spec.json @@ -0,0 +1,43 @@ +{ + "name": "Devnet", + "id": "devnet-1", + "bootnodes": ["127.0.0.1:9955"], + "config": { + "additionalMinBalancePerStateByte" : 1, + "additionalMinBalancePerStateItem" : 10, + "auditBiasFactor" : 2, + "auditTranchePeriod" : 8, + "coreAccumulationGas" : 10000000, + "coreAssignmentRotationPeriod" : 6, + "epochLength" : 6, + "erasureCodedPieceSize" : 684, + "erasureCodedSegmentSize" : 6, + "maxAuthorizationsPoolItems" : 8, + "maxAuthorizationsQueueItems" : 10, + "maxEncodedWorkPackageSize" : 12582912, + "maxEncodedWorkReportSize" : 98304, + "maxLookupAnchorAge" : 14400, + "maxServiceCodeSize" : 4000000, + "maxTicketsPerExtrinsic" : 4, + "maxWorkItems" : 2, + "maxWorkPackageManifestEntries" : 2048, + "preimagePurgePeriod" : 28800, + "preimageReplacementPeriod" : 5, + "pvmDynamicAddressAlignmentFactor" : 2, + "pvmProgramInitInputDataSize" : 16777216, + "pvmProgramInitPageSize" : 16384, + "pvmProgramInitSegmentSize" : 65536, + "recentHistorySize" : 8, + "serviceMinBalance" : 100, + "slotPeriodSeconds" : 4, + "ticketEntriesPerValidator" : 2, + "ticketSubmissionEndSlot" : 2, + "totalNumberOfCores" : 1, + "totalNumberOfValidators" : 3, + "transferMemoSize" : 128, + "workPackageAuthorizerGas" : 10000000, + "workPackageRefineGas" : 10000000 + }, + "state" : "0x00020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c5ff86fc09879ff873cf95e3549ff2b0ebf1004b553d1fe44d76be15b1d6128000000000000000000000000000000000000000000000000000000000000000000005e465beb01dbafe160ce8216047f2155dd0569f058afd52dcea601025a8d161d3b6a27bcceb6a42d62a3a8d02a6f0d73653215771de243a63ac048a18b59da29b27150a1f1cd24bccc792ba7ba4220a1e8c36636e35a969d1d14b4c89bce7d1d463474fb186114a89dd70e88506fefc9830756c27a7845bec1cb6ee31e07211afd0dde34f0dc5d89231993cd323973faa23d84d521fd574e840b8617c75d1a1d0102aa3c71999137001a77464ced6bb2885c460be760c709009e26395716a52c8c52e6e23906a455b4264e7d0c75466e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002c5da3a09d66a5d43e7d523e6108736db99d2c2f08fbdcb72a4e8e5aced3482acecc1507dc1ddd7295951c290888f095adb9044d1b73d696e6df065d683bd4fca4cdfdee14ebd0473a45e76f3370eda3ed7ef39e340b03d710b378d5001d6b36206af562a7d50e78faafc4b7f228930db0404d52f70d8f289f3faea254290b2938bd2a4586d28125660638f837b4ed544f69b8485f90dbbfc8a56d0630c3dd5e15a112b764dfc7bc685da473cc9cd4cdb22a03401d6c572827d12aa473b30447037bd88969461751e94c4dcd46add92200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008552b36000b454fdf6b5418e22ef5d6609e8fc6b816822f02727e085c514d5606b79c57e6a095239282c04818e96112f3f03a4001ba97a564c23852a3f1ea5fc8a6dd26b3df37c94424c5c03c4c06e4a9aef72b2342fd97846ee273e9c700138af0f1d92881909a04cb68ecf5ca730e7ab6d43bbbd13a8bc7ecbf1797af4f9f81fefe5b7d4d30b7806eabcac490247867a3feda8c31957187c9a00a8c4e261ef198532d51f7111bf9bdec83304859e1dedede727786cee23daf4c83c6d68d92f5db7064f3fc4f2bcc175ead75b83fbc70000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b3bb3d7367b67e4de610b45db13e7de2816924867a2963ce30341cdecb43090540b7e34a06d89c6aba0a5f50a9c2c19e8245e5ee709929ccbede0467b995680b1b8f4214b1d109c820cdc832a4fdea994faae7c2c992db426c15cb1d53489c1a92e630ae2b14e758ab0960e372172203f4c9a41777dadd529971d7ab9d23ab29fe0e9c85ec450505dde7f5ac038274cf015e465beb01dbafe160ce8216047f2155dd0569f058afd52dcea601025a8d161d2c5da3a09d66a5d43e7d523e6108736db99d2c2f08fbdcb72a4e8e5aced3482a8552b36000b454fdf6b5418e22ef5d6609e8fc6b816822f02727e085c514d5605e465beb01dbafe160ce8216047f2155dd0569f058afd52dcea601025a8d161d2c5da3a09d66a5d43e7d523e6108736db99d2c2f08fbdcb72a4e8e5aced3482a8552b36000b454fdf6b5418e22ef5d6609e8fc6b816822f02727e085c514d560000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005e465beb01dbafe160ce8216047f2155dd0569f058afd52dcea601025a8d161d3b6a27bcceb6a42d62a3a8d02a6f0d73653215771de243a63ac048a18b59da29b27150a1f1cd24bccc792ba7ba4220a1e8c36636e35a969d1d14b4c89bce7d1d463474fb186114a89dd70e88506fefc9830756c27a7845bec1cb6ee31e07211afd0dde34f0dc5d89231993cd323973faa23d84d521fd574e840b8617c75d1a1d0102aa3c71999137001a77464ced6bb2885c460be760c709009e26395716a52c8c52e6e23906a455b4264e7d0c75466e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002c5da3a09d66a5d43e7d523e6108736db99d2c2f08fbdcb72a4e8e5aced3482acecc1507dc1ddd7295951c290888f095adb9044d1b73d696e6df065d683bd4fca4cdfdee14ebd0473a45e76f3370eda3ed7ef39e340b03d710b378d5001d6b36206af562a7d50e78faafc4b7f228930db0404d52f70d8f289f3faea254290b2938bd2a4586d28125660638f837b4ed544f69b8485f90dbbfc8a56d0630c3dd5e15a112b764dfc7bc685da473cc9cd4cdb22a03401d6c572827d12aa473b30447037bd88969461751e94c4dcd46add92200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008552b36000b454fdf6b5418e22ef5d6609e8fc6b816822f02727e085c514d5606b79c57e6a095239282c04818e96112f3f03a4001ba97a564c23852a3f1ea5fc8a6dd26b3df37c94424c5c03c4c06e4a9aef72b2342fd97846ee273e9c700138af0f1d92881909a04cb68ecf5ca730e7ab6d43bbbd13a8bc7ecbf1797af4f9f81fefe5b7d4d30b7806eabcac490247867a3feda8c31957187c9a00a8c4e261ef198532d51f7111bf9bdec83304859e1dedede727786cee23daf4c83c6d68d92f5db7064f3fc4f2bcc175ead75b83fbc700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005e465beb01dbafe160ce8216047f2155dd0569f058afd52dcea601025a8d161d3b6a27bcceb6a42d62a3a8d02a6f0d73653215771de243a63ac048a18b59da29b27150a1f1cd24bccc792ba7ba4220a1e8c36636e35a969d1d14b4c89bce7d1d463474fb186114a89dd70e88506fefc9830756c27a7845bec1cb6ee31e07211afd0dde34f0dc5d89231993cd323973faa23d84d521fd574e840b8617c75d1a1d0102aa3c71999137001a77464ced6bb2885c460be760c709009e26395716a52c8c52e6e23906a455b4264e7d0c75466e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002c5da3a09d66a5d43e7d523e6108736db99d2c2f08fbdcb72a4e8e5aced3482acecc1507dc1ddd7295951c290888f095adb9044d1b73d696e6df065d683bd4fca4cdfdee14ebd0473a45e76f3370eda3ed7ef39e340b03d710b378d5001d6b36206af562a7d50e78faafc4b7f228930db0404d52f70d8f289f3faea254290b2938bd2a4586d28125660638f837b4ed544f69b8485f90dbbfc8a56d0630c3dd5e15a112b764dfc7bc685da473cc9cd4cdb22a03401d6c572827d12aa473b30447037bd88969461751e94c4dcd46add92200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008552b36000b454fdf6b5418e22ef5d6609e8fc6b816822f02727e085c514d5606b79c57e6a095239282c04818e96112f3f03a4001ba97a564c23852a3f1ea5fc8a6dd26b3df37c94424c5c03c4c06e4a9aef72b2342fd97846ee273e9c700138af0f1d92881909a04cb68ecf5ca730e7ab6d43bbbd13a8bc7ecbf1797af4f9f81fefe5b7d4d30b7806eabcac490247867a3feda8c31957187c9a00a8c4e261ef198532d51f7111bf9bdec83304859e1dedede727786cee23daf4c83c6d68d92f5db7064f3fc4f2bcc175ead75b83fbc70000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "block":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" +} diff --git a/Node/Tests/NodeTests/chainfiles/devnet_noconfig_spec.json b/Node/Tests/NodeTests/chainfiles/devnet_noconfig_spec.json new file mode 100644 index 00000000..d65e28ca --- /dev/null +++ b/Node/Tests/NodeTests/chainfiles/devnet_noconfig_spec.json @@ -0,0 +1,11 @@ +{ + "name": "MainNet", + "id": "mainnet-456", + "bootnodes": [ + "node1.mainnet.com", + "node2.mainnet.com" + ], + "preset": "minimal", + "state" : "0x00020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c5ff86fc09879ff873cf95e3549ff2b0ebf1004b553d1fe44d76be15b1d6128000000000000000000000000000000000000000000000000000000000000000000005e465beb01dbafe160ce8216047f2155dd0569f058afd52dcea601025a8d161d3b6a27bcceb6a42d62a3a8d02a6f0d73653215771de243a63ac048a18b59da29b27150a1f1cd24bccc792ba7ba4220a1e8c36636e35a969d1d14b4c89bce7d1d463474fb186114a89dd70e88506fefc9830756c27a7845bec1cb6ee31e07211afd0dde34f0dc5d89231993cd323973faa23d84d521fd574e840b8617c75d1a1d0102aa3c71999137001a77464ced6bb2885c460be760c709009e26395716a52c8c52e6e23906a455b4264e7d0c75466e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002c5da3a09d66a5d43e7d523e6108736db99d2c2f08fbdcb72a4e8e5aced3482acecc1507dc1ddd7295951c290888f095adb9044d1b73d696e6df065d683bd4fca4cdfdee14ebd0473a45e76f3370eda3ed7ef39e340b03d710b378d5001d6b36206af562a7d50e78faafc4b7f228930db0404d52f70d8f289f3faea254290b2938bd2a4586d28125660638f837b4ed544f69b8485f90dbbfc8a56d0630c3dd5e15a112b764dfc7bc685da473cc9cd4cdb22a03401d6c572827d12aa473b30447037bd88969461751e94c4dcd46add92200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008552b36000b454fdf6b5418e22ef5d6609e8fc6b816822f02727e085c514d5606b79c57e6a095239282c04818e96112f3f03a4001ba97a564c23852a3f1ea5fc8a6dd26b3df37c94424c5c03c4c06e4a9aef72b2342fd97846ee273e9c700138af0f1d92881909a04cb68ecf5ca730e7ab6d43bbbd13a8bc7ecbf1797af4f9f81fefe5b7d4d30b7806eabcac490247867a3feda8c31957187c9a00a8c4e261ef198532d51f7111bf9bdec83304859e1dedede727786cee23daf4c83c6d68d92f5db7064f3fc4f2bcc175ead75b83fbc70000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b3bb3d7367b67e4de610b45db13e7de2816924867a2963ce30341cdecb43090540b7e34a06d89c6aba0a5f50a9c2c19e8245e5ee709929ccbede0467b995680b1b8f4214b1d109c820cdc832a4fdea994faae7c2c992db426c15cb1d53489c1a92e630ae2b14e758ab0960e372172203f4c9a41777dadd529971d7ab9d23ab29fe0e9c85ec450505dde7f5ac038274cf015e465beb01dbafe160ce8216047f2155dd0569f058afd52dcea601025a8d161d2c5da3a09d66a5d43e7d523e6108736db99d2c2f08fbdcb72a4e8e5aced3482a8552b36000b454fdf6b5418e22ef5d6609e8fc6b816822f02727e085c514d5605e465beb01dbafe160ce8216047f2155dd0569f058afd52dcea601025a8d161d2c5da3a09d66a5d43e7d523e6108736db99d2c2f08fbdcb72a4e8e5aced3482a8552b36000b454fdf6b5418e22ef5d6609e8fc6b816822f02727e085c514d560000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005e465beb01dbafe160ce8216047f2155dd0569f058afd52dcea601025a8d161d3b6a27bcceb6a42d62a3a8d02a6f0d73653215771de243a63ac048a18b59da29b27150a1f1cd24bccc792ba7ba4220a1e8c36636e35a969d1d14b4c89bce7d1d463474fb186114a89dd70e88506fefc9830756c27a7845bec1cb6ee31e07211afd0dde34f0dc5d89231993cd323973faa23d84d521fd574e840b8617c75d1a1d0102aa3c71999137001a77464ced6bb2885c460be760c709009e26395716a52c8c52e6e23906a455b4264e7d0c75466e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002c5da3a09d66a5d43e7d523e6108736db99d2c2f08fbdcb72a4e8e5aced3482acecc1507dc1ddd7295951c290888f095adb9044d1b73d696e6df065d683bd4fca4cdfdee14ebd0473a45e76f3370eda3ed7ef39e340b03d710b378d5001d6b36206af562a7d50e78faafc4b7f228930db0404d52f70d8f289f3faea254290b2938bd2a4586d28125660638f837b4ed544f69b8485f90dbbfc8a56d0630c3dd5e15a112b764dfc7bc685da473cc9cd4cdb22a03401d6c572827d12aa473b30447037bd88969461751e94c4dcd46add92200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008552b36000b454fdf6b5418e22ef5d6609e8fc6b816822f02727e085c514d5606b79c57e6a095239282c04818e96112f3f03a4001ba97a564c23852a3f1ea5fc8a6dd26b3df37c94424c5c03c4c06e4a9aef72b2342fd97846ee273e9c700138af0f1d92881909a04cb68ecf5ca730e7ab6d43bbbd13a8bc7ecbf1797af4f9f81fefe5b7d4d30b7806eabcac490247867a3feda8c31957187c9a00a8c4e261ef198532d51f7111bf9bdec83304859e1dedede727786cee23daf4c83c6d68d92f5db7064f3fc4f2bcc175ead75b83fbc700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005e465beb01dbafe160ce8216047f2155dd0569f058afd52dcea601025a8d161d3b6a27bcceb6a42d62a3a8d02a6f0d73653215771de243a63ac048a18b59da29b27150a1f1cd24bccc792ba7ba4220a1e8c36636e35a969d1d14b4c89bce7d1d463474fb186114a89dd70e88506fefc9830756c27a7845bec1cb6ee31e07211afd0dde34f0dc5d89231993cd323973faa23d84d521fd574e840b8617c75d1a1d0102aa3c71999137001a77464ced6bb2885c460be760c709009e26395716a52c8c52e6e23906a455b4264e7d0c75466e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002c5da3a09d66a5d43e7d523e6108736db99d2c2f08fbdcb72a4e8e5aced3482acecc1507dc1ddd7295951c290888f095adb9044d1b73d696e6df065d683bd4fca4cdfdee14ebd0473a45e76f3370eda3ed7ef39e340b03d710b378d5001d6b36206af562a7d50e78faafc4b7f228930db0404d52f70d8f289f3faea254290b2938bd2a4586d28125660638f837b4ed544f69b8485f90dbbfc8a56d0630c3dd5e15a112b764dfc7bc685da473cc9cd4cdb22a03401d6c572827d12aa473b30447037bd88969461751e94c4dcd46add92200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008552b36000b454fdf6b5418e22ef5d6609e8fc6b816822f02727e085c514d5606b79c57e6a095239282c04818e96112f3f03a4001ba97a564c23852a3f1ea5fc8a6dd26b3df37c94424c5c03c4c06e4a9aef72b2342fd97846ee273e9c700138af0f1d92881909a04cb68ecf5ca730e7ab6d43bbbd13a8bc7ecbf1797af4f9f81fefe5b7d4d30b7806eabcac490247867a3feda8c31957187c9a00a8c4e261ef198532d51f7111bf9bdec83304859e1dedede727786cee23daf4c83c6d68d92f5db7064f3fc4f2bcc175ead75b83fbc70000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "block":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + } diff --git a/Node/Tests/NodeTests/chainfiles/mainnet_someconfig_spec.json b/Node/Tests/NodeTests/chainfiles/mainnet_someconfig_spec.json new file mode 100644 index 00000000..423d4d93 --- /dev/null +++ b/Node/Tests/NodeTests/chainfiles/mainnet_someconfig_spec.json @@ -0,0 +1,21 @@ +{ + "name": "Mainnet", + "id": "mainnet-1", + "bootnodes": [ + "127.0.0.1:9955" + ], + "preset": "mainnet", + "config": { + "auditTranchePeriod" : 100, + "epochLength" : 6, + "erasureCodedPieceSize" : 684, + "erasureCodedSegmentSize" : 6, + "maxAuthorizationsPoolItems" : 8, + "maxAuthorizationsQueueItems" : 10, + "ticketSubmissionEndSlot" : 2, + "totalNumberOfCores" : 1, + "totalNumberOfValidators" : 3 + }, + "state" : "0x00020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c5ff86fc09879ff873cf95e3549ff2b0ebf1004b553d1fe44d76be15b1d6128000000000000000000000000000000000000000000000000000000000000000000005e465beb01dbafe160ce8216047f2155dd0569f058afd52dcea601025a8d161d3b6a27bcceb6a42d62a3a8d02a6f0d73653215771de243a63ac048a18b59da29b27150a1f1cd24bccc792ba7ba4220a1e8c36636e35a969d1d14b4c89bce7d1d463474fb186114a89dd70e88506fefc9830756c27a7845bec1cb6ee31e07211afd0dde34f0dc5d89231993cd323973faa23d84d521fd574e840b8617c75d1a1d0102aa3c71999137001a77464ced6bb2885c460be760c709009e26395716a52c8c52e6e23906a455b4264e7d0c75466e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002c5da3a09d66a5d43e7d523e6108736db99d2c2f08fbdcb72a4e8e5aced3482acecc1507dc1ddd7295951c290888f095adb9044d1b73d696e6df065d683bd4fca4cdfdee14ebd0473a45e76f3370eda3ed7ef39e340b03d710b378d5001d6b36206af562a7d50e78faafc4b7f228930db0404d52f70d8f289f3faea254290b2938bd2a4586d28125660638f837b4ed544f69b8485f90dbbfc8a56d0630c3dd5e15a112b764dfc7bc685da473cc9cd4cdb22a03401d6c572827d12aa473b30447037bd88969461751e94c4dcd46add92200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008552b36000b454fdf6b5418e22ef5d6609e8fc6b816822f02727e085c514d5606b79c57e6a095239282c04818e96112f3f03a4001ba97a564c23852a3f1ea5fc8a6dd26b3df37c94424c5c03c4c06e4a9aef72b2342fd97846ee273e9c700138af0f1d92881909a04cb68ecf5ca730e7ab6d43bbbd13a8bc7ecbf1797af4f9f81fefe5b7d4d30b7806eabcac490247867a3feda8c31957187c9a00a8c4e261ef198532d51f7111bf9bdec83304859e1dedede727786cee23daf4c83c6d68d92f5db7064f3fc4f2bcc175ead75b83fbc70000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b3bb3d7367b67e4de610b45db13e7de2816924867a2963ce30341cdecb43090540b7e34a06d89c6aba0a5f50a9c2c19e8245e5ee709929ccbede0467b995680b1b8f4214b1d109c820cdc832a4fdea994faae7c2c992db426c15cb1d53489c1a92e630ae2b14e758ab0960e372172203f4c9a41777dadd529971d7ab9d23ab29fe0e9c85ec450505dde7f5ac038274cf015e465beb01dbafe160ce8216047f2155dd0569f058afd52dcea601025a8d161d2c5da3a09d66a5d43e7d523e6108736db99d2c2f08fbdcb72a4e8e5aced3482a8552b36000b454fdf6b5418e22ef5d6609e8fc6b816822f02727e085c514d5605e465beb01dbafe160ce8216047f2155dd0569f058afd52dcea601025a8d161d2c5da3a09d66a5d43e7d523e6108736db99d2c2f08fbdcb72a4e8e5aced3482a8552b36000b454fdf6b5418e22ef5d6609e8fc6b816822f02727e085c514d560000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005e465beb01dbafe160ce8216047f2155dd0569f058afd52dcea601025a8d161d3b6a27bcceb6a42d62a3a8d02a6f0d73653215771de243a63ac048a18b59da29b27150a1f1cd24bccc792ba7ba4220a1e8c36636e35a969d1d14b4c89bce7d1d463474fb186114a89dd70e88506fefc9830756c27a7845bec1cb6ee31e07211afd0dde34f0dc5d89231993cd323973faa23d84d521fd574e840b8617c75d1a1d0102aa3c71999137001a77464ced6bb2885c460be760c709009e26395716a52c8c52e6e23906a455b4264e7d0c75466e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002c5da3a09d66a5d43e7d523e6108736db99d2c2f08fbdcb72a4e8e5aced3482acecc1507dc1ddd7295951c290888f095adb9044d1b73d696e6df065d683bd4fca4cdfdee14ebd0473a45e76f3370eda3ed7ef39e340b03d710b378d5001d6b36206af562a7d50e78faafc4b7f228930db0404d52f70d8f289f3faea254290b2938bd2a4586d28125660638f837b4ed544f69b8485f90dbbfc8a56d0630c3dd5e15a112b764dfc7bc685da473cc9cd4cdb22a03401d6c572827d12aa473b30447037bd88969461751e94c4dcd46add92200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008552b36000b454fdf6b5418e22ef5d6609e8fc6b816822f02727e085c514d5606b79c57e6a095239282c04818e96112f3f03a4001ba97a564c23852a3f1ea5fc8a6dd26b3df37c94424c5c03c4c06e4a9aef72b2342fd97846ee273e9c700138af0f1d92881909a04cb68ecf5ca730e7ab6d43bbbd13a8bc7ecbf1797af4f9f81fefe5b7d4d30b7806eabcac490247867a3feda8c31957187c9a00a8c4e261ef198532d51f7111bf9bdec83304859e1dedede727786cee23daf4c83c6d68d92f5db7064f3fc4f2bcc175ead75b83fbc700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005e465beb01dbafe160ce8216047f2155dd0569f058afd52dcea601025a8d161d3b6a27bcceb6a42d62a3a8d02a6f0d73653215771de243a63ac048a18b59da29b27150a1f1cd24bccc792ba7ba4220a1e8c36636e35a969d1d14b4c89bce7d1d463474fb186114a89dd70e88506fefc9830756c27a7845bec1cb6ee31e07211afd0dde34f0dc5d89231993cd323973faa23d84d521fd574e840b8617c75d1a1d0102aa3c71999137001a77464ced6bb2885c460be760c709009e26395716a52c8c52e6e23906a455b4264e7d0c75466e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002c5da3a09d66a5d43e7d523e6108736db99d2c2f08fbdcb72a4e8e5aced3482acecc1507dc1ddd7295951c290888f095adb9044d1b73d696e6df065d683bd4fca4cdfdee14ebd0473a45e76f3370eda3ed7ef39e340b03d710b378d5001d6b36206af562a7d50e78faafc4b7f228930db0404d52f70d8f289f3faea254290b2938bd2a4586d28125660638f837b4ed544f69b8485f90dbbfc8a56d0630c3dd5e15a112b764dfc7bc685da473cc9cd4cdb22a03401d6c572827d12aa473b30447037bd88969461751e94c4dcd46add92200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008552b36000b454fdf6b5418e22ef5d6609e8fc6b816822f02727e085c514d5606b79c57e6a095239282c04818e96112f3f03a4001ba97a564c23852a3f1ea5fc8a6dd26b3df37c94424c5c03c4c06e4a9aef72b2342fd97846ee273e9c700138af0f1d92881909a04cb68ecf5ca730e7ab6d43bbbd13a8bc7ecbf1797af4f9f81fefe5b7d4d30b7806eabcac490247867a3feda8c31957187c9a00a8c4e261ef198532d51f7111bf9bdec83304859e1dedede727786cee23daf4c83c6d68d92f5db7064f3fc4f2bcc175ead75b83fbc70000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "block":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" +} diff --git a/Utils/Sources/Utils/ConfigLimitedSizeArray.swift b/Utils/Sources/Utils/ConfigLimitedSizeArray.swift index 80b9ac68..d261378a 100644 --- a/Utils/Sources/Utils/ConfigLimitedSizeArray.swift +++ b/Utils/Sources/Utils/ConfigLimitedSizeArray.swift @@ -225,8 +225,16 @@ extension ConfigLimitedSizeArray: Decodable where T: Decodable { } else { // variable size array var container = try decoder.unkeyedContainer() - let array = try container.decode([T].self) - try self.init(array, minLength: minLength, maxLength: maxLength) + if decoder.isJamCodec { + let array = try container.decode([T].self) + try self.init(array, minLength: minLength, maxLength: maxLength) + } else { + var array = [T]() + while !container.isAtEnd { + try array.append(container.decode(T.self)) + } + try self.init(array, minLength: minLength, maxLength: maxLength) + } } } } diff --git a/Utils/Sources/Utils/Either.swift b/Utils/Sources/Utils/Either.swift index 625695bb..6e88dd25 100644 --- a/Utils/Sources/Utils/Either.swift +++ b/Utils/Sources/Utils/Either.swift @@ -45,10 +45,10 @@ extension Either: Codable where Left: Codable, Right: Codable { var container = encoder.unkeyedContainer() switch self { case let .left(a): - try container.encode(0) + try container.encode(UInt8(0)) try container.encode(a) case let .right(b): - try container.encode(1) + try container.encode(UInt8(1)) try container.encode(b) } } else { diff --git a/Utils/Sources/Utils/JSONEncoder+Utils.swift b/Utils/Sources/Utils/JSONEncoder+Utils.swift index b3b0359b..e6a69f34 100644 --- a/Utils/Sources/Utils/JSONEncoder+Utils.swift +++ b/Utils/Sources/Utils/JSONEncoder+Utils.swift @@ -9,3 +9,16 @@ extension JSONEncoder.DataEncodingStrategy { } } } + +extension JSONDecoder.DataDecodingStrategy { + public static var hex: JSONDecoder.DataDecodingStrategy { + .custom { decoder in + let container = try decoder.singleValueContainer() + let hexString = try container.decode(String.self) + guard hexString.hasPrefix("0x"), let data = Data(fromHexString: String(hexString.dropFirst(2))) else { + throw DecodingError.dataCorruptedError(in: container, debugDescription: "Invalid hex string") + } + return data + } + } +}