Skip to content

Commit

Permalink
Merge branch 'master' of github.com:AcalaNetwork/boka
Browse files Browse the repository at this point in the history
* 'master' of github.com:AcalaNetwork/boka:
  sorted set (#93)
  • Loading branch information
MacOMNI committed Aug 30, 2024
2 parents 7e0c3cd + 45e1760 commit 1e85be6
Show file tree
Hide file tree
Showing 6 changed files with 153 additions and 9 deletions.
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]))
}
}
}

0 comments on commit 1e85be6

Please sign in to comment.