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

sorted set #93

Merged
merged 1 commit into from
Aug 30, 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
10 changes: 5 additions & 5 deletions Blockchain/Sources/Blockchain/Types/JudgementsState.swift
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
import Codec
import Utils

// TODO: figure out how to deal with orders for items in Set
public struct JudgementsState: Sendable, Equatable, Codable {
// ψg: Work-reports judged to be correct
public var goodSet: Set<Data32>
@CodingAs<SortedSet<Data32>> public var goodSet: Set<Data32>

// ψb: Work-reports judged to be incorrect
public var banSet: Set<Data32>
@CodingAs<SortedSet<Data32>> public var banSet: Set<Data32>

// ψw: Work-reports whose validity is judged to be unknowable
public var wonkySet: Set<Data32>
@CodingAs<SortedSet<Data32>> public var wonkySet: Set<Data32>

// ψo: Validators who made a judgement found to be incorrect
public var punishSet: Set<Ed25519PublicKey>
@CodingAs<SortedSet<Data32>> public var punishSet: Set<Ed25519PublicKey>

public init(
goodSet: Set<Data32>,
Expand Down
29 changes: 29 additions & 0 deletions Codec/Sources/Codec/CodingAs.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
public protocol CodableAlias<Alias>: Codable {
associatedtype Alias: Codable

init(alias: Alias)
var alias: Alias { get }
}

@propertyWrapper
public struct CodingAs<T: CodableAlias>: Codable {
public var wrappedValue: T.Alias

public init(wrappedValue: T.Alias) {
self.wrappedValue = wrappedValue
}

public init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
wrappedValue = try container.decode(T.self).alias
}

public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(T(alias: wrappedValue))
}
}

extension CodingAs: Sendable where T: Sendable, T.Alias: Sendable {}

extension CodingAs: Equatable where T: Equatable, T.Alias: Equatable {}
20 changes: 16 additions & 4 deletions Codec/Sources/Codec/JamDecoder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,22 @@ public class JamDecoder {
return res
}

public static func decode<T: Decodable>(_ type: T.Type, from data: Data, withConfig config: some Any) throws -> T {
let context = DecodeContext(data: data)
context.userInfo[.config] = config
return try context.decode(type, key: nil)
public static func decode<T: Decodable>(_ type: T.Type, from data: Data, withConfig config: Any? = nil) throws -> T {
let decoder = JamDecoder(data: data, config: config)
let val = try decoder.decode(type)
try decoder.finalize()
return val
}

public func finalize() throws {
guard data.isEmpty else {
throw DecodingError.dataCorrupted(
DecodingError.Context(
codingPath: [],
debugDescription: "Not all data was consumed"
)
)
}
}
}

Expand Down
43 changes: 43 additions & 0 deletions Codec/Sources/Codec/SortedSet.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import Foundation

public struct SortedSet<T: Codable & Hashable & Comparable>: Codable, CodableAlias {
public typealias Alias = Set<T>

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([T].self)

// ensure array is sorted and unique
var previous: T?
for item in array {
guard previous == nil || item > previous! else {
throw DecodingError.dataCorrupted(
DecodingError.Context(
codingPath: container.codingPath,
debugDescription: "Array is not sorted"
)
)
}
previous = item
}

alias = Set(array)
}

public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
var array = Array(alias)
array.sort()
try container.encode(array)
}
}

extension SortedSet: Sendable where T: Sendable, Alias: Sendable {}

extension SortedSet: Equatable where T: Equatable, Alias: Equatable {}
33 changes: 33 additions & 0 deletions Codec/Tests/CodecTests/CodingAsTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import Foundation
import Testing

@testable import Codec

struct UInt8Alias: CodableAlias, Codable {
typealias T = UInt8

var extraByte: UInt8 = 0xAB
var value: UInt8

init(alias: UInt8) {
value = alias
}

var alias: UInt8 {
value
}
}

struct TestCodable: Codable {
@CodingAs<UInt8Alias> var value: UInt8
}

struct CodingAsTests {
@Test func testCodingAs() throws {
let testCase = TestCodable(value: 0x23)
let encoded = try JamEncoder.encode(testCase)
#expect(encoded == Data([0xAB, 0x23]))
let decoded = try JamDecoder.decode(TestCodable.self, from: encoded, withConfig: testCase)
#expect(decoded.value == 0x23)
}
}
27 changes: 27 additions & 0 deletions Codec/Tests/CodecTests/SortedSetTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import Foundation
import Testing

@testable import Codec

struct SortedSetTests {
@Test func encode() throws {
let testCase: SortedSet<UInt8> = SortedSet(alias: [1, 2, 3])
let encoded = try JamEncoder.encode(testCase)
#expect(encoded == Data([3, 1, 2, 3]))
}

@Test func decode() throws {
let decoded = try JamDecoder.decode(SortedSet<UInt8>.self, from: Data([12, 1, 2, 3]))
#expect(decoded.alias == [1, 2, 3])
}

@Test func invalidData() throws {
#expect(throws: DecodingError.self) {
try JamDecoder.decode(SortedSet<UInt>.self, from: Data([12, 1, 2, 2]))
}

#expect(throws: DecodingError.self) {
try JamDecoder.decode(SortedSet<UInt>.self, from: Data([12, 3, 2, 1]))
}
}
}
Loading