Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

subcommand generate to generate chainspec file #212

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

Filter by extension

Filter by extension

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