Skip to content

Commit

Permalink
subcommand generate to generate chainspec file (#212)
Browse files Browse the repository at this point in the history
* subcommand generate to generate chainspec file

* fix codec and chainspec

* fix
  • Loading branch information
xlc authored Oct 31, 2024
1 parent e2e9beb commit 0bd14b7
Show file tree
Hide file tree
Showing 26 changed files with 540 additions and 206 deletions.
2 changes: 1 addition & 1 deletion Blockchain/Sources/Blockchain/Config/ProtocolConfig.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
7 changes: 5 additions & 2 deletions Blockchain/Sources/Blockchain/Types/Header.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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<Ticket, ProtocolConfig.EpochLength>?.self, forKey: .winningTickets),
epoch: container.decodeIfPresent(EpochMarker.self, forKey: .epoch),
winningTickets: container.decodeIfPresent(
ConfigFixedSizeArray<Ticket, ProtocolConfig.EpochLength>.self,
forKey: .winningTickets
),
offendersMarkers: container.decode([Ed25519PublicKey].self, forKey: .offendersMarkers),
authorIndex: container.decode(ValidatorIndex.self, forKey: .authorIndex),
vrfSignature: container.decode(BandersnatchSignature.self, forKey: .vrfSignature)
Expand Down
5 changes: 4 additions & 1 deletion Blockchain/Sources/Blockchain/Types/PrivilegedServices.swift
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import Codec
import Utils

public struct PrivilegedServices: Sendable, Equatable, Codable {
// m
public var empower: ServiceIndex
Expand All @@ -6,7 +9,7 @@ public struct PrivilegedServices: Sendable, Equatable, Codable {
// v
public var designate: ServiceIndex
// g
public var basicGas: [ServiceIndex: Gas]
@CodingAs<SortedKeyValues<ServiceIndex, Gas>> public var basicGas: [ServiceIndex: Gas]

public init(empower: ServiceIndex, assign: ServiceIndex, designate: ServiceIndex, basicGas: [ServiceIndex: Gas]) {
self.empower = empower
Expand Down
6 changes: 5 additions & 1 deletion Blockchain/Sources/Blockchain/Types/State.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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<SortedKeyValues<ServiceIndex, ServiceAccount>> public var serviceAccounts: [ServiceIndex: ServiceAccount]

// η: The eηtropy accumulator and epochal raηdomness.
public var entropyPool: EntropyPool
Expand Down Expand Up @@ -126,6 +126,10 @@ extension State {
public var lastBlockHash: Data32 {
recentHistory.items.last.map(\.headerHash)!
}

public func asRef() -> StateRef {
StateRef(self)
}
}

extension State: Dummy {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
3 changes: 0 additions & 3 deletions Boka/Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,6 @@ let package = Package(
dependencies: [
"Boka",
.product(name: "Testing", package: "swift-testing"),
],
resources: [
.copy("chainfiles"),
]
),
],
Expand Down
3 changes: 2 additions & 1 deletion Boka/Sources/Boka.swift
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,8 @@ enum MaybeEnabled<T: ExpressibleByArgument>: 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.")
Expand Down
44 changes: 44 additions & 0 deletions Boka/Sources/Generate.swift
Original file line number Diff line number Diff line change
@@ -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)")
}
}
33 changes: 0 additions & 33 deletions Boka/Tests/BokaTests/BokaTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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)
}
}
42 changes: 0 additions & 42 deletions Boka/Tests/BokaTests/chainfiles/devnet_allconfig_spec.json

This file was deleted.

11 changes: 0 additions & 11 deletions Boka/Tests/BokaTests/chainfiles/devnet_noconfig_spec.json

This file was deleted.

26 changes: 0 additions & 26 deletions Boka/Tests/BokaTests/chainfiles/mainnet_someconfig_spec.json

This file was deleted.

22 changes: 21 additions & 1 deletion Codec/Sources/Codec/CodingUserInfoKey+Utils.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,14 @@ extension CodingUserInfoKey {
public static let isJamCodec = CodingUserInfoKey(rawValue: "isJamCodec")!
}

public class ConfigRef<C> {
public var value: C?

public init(_ value: C? = nil) {
self.value = value
}
}

extension Encoder {
public var isJamCodec: Bool {
userInfo[.isJamCodec] as? Bool ?? false
Expand All @@ -19,6 +27,18 @@ extension Decoder {
}

public func getConfig<C>(_: C.Type) -> C? {
userInfo[.config] as? C
if let config = userInfo[.config] as? C {
return config
}
if let config = userInfo[.config] as? ConfigRef<C> {
return config.value
}
return nil
}

public func setConfig<C>(_ config: C) {
if let ref = userInfo[.config] as? ConfigRef<C> {
ref.value = config
}
}
}
17 changes: 17 additions & 0 deletions Codec/Sources/Codec/JamDecoder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,23 @@ private struct JamKeyedDecodingContainer<K: CodingKey>: KeyedDecodingContainerPr
try decoder.decode(type, key: key)
}

func decodeIfPresent<T: Decodable>(_ 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<NestedKey>(keyedBy _: NestedKey.Type, forKey _: K) throws -> KeyedDecodingContainer<NestedKey>
where NestedKey: CodingKey
{
Expand Down
48 changes: 48 additions & 0 deletions Codec/Sources/Codec/SortedKeyValues.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import Foundation

struct KeyValuePair<Key: Codable & Hashable & Comparable, Value: Codable>: Codable {
var key: Key
var value: Value
}

public struct SortedKeyValues<Key: Codable & Hashable & Comparable, Value: Codable>: 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<Key, Value>].self)

// ensure array is sorted and unique
var previous: KeyValuePair<Key, Value>?
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 {}
3 changes: 3 additions & 0 deletions Node/Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ let package = Package(
dependencies: [
"Node",
.product(name: "Testing", package: "swift-testing"),
],
resources: [
.copy("chainfiles"),
]
),
],
Expand Down
Loading

0 comments on commit 0bd14b7

Please sign in to comment.