-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* preimages * preimages test
- Loading branch information
Showing
7 changed files
with
235 additions
and
38 deletions.
There are no files selected for viewing
78 changes: 78 additions & 0 deletions
78
Blockchain/Sources/Blockchain/RuntimeProtocols/Preimages.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
import Foundation | ||
import Utils | ||
|
||
public enum PreimagesError: Error { | ||
case preimagesNotSorted | ||
case duplicatedPreimage | ||
case invalidServiceIndex | ||
} | ||
|
||
public struct PreimageUpdate: Sendable, Equatable { | ||
public let serviceIndex: UInt32 | ||
public let hash: Data32 | ||
public let data: Data | ||
public let length: UInt32 | ||
public let timeslot: TimeslotIndex | ||
|
||
public init(serviceIndex: UInt32, hash: Data32, data: Data, length: UInt32, timeslot: TimeslotIndex) { | ||
self.serviceIndex = serviceIndex | ||
self.hash = hash | ||
self.data = data | ||
self.length = length | ||
self.timeslot = timeslot | ||
} | ||
} | ||
|
||
public struct PreimagesPostState: Sendable, Equatable { | ||
public let updates: [PreimageUpdate] | ||
|
||
public init(updates: [PreimageUpdate]) { | ||
self.updates = updates | ||
} | ||
} | ||
|
||
public protocol Preimages { | ||
func get(serviceAccount index: ServiceIndex, preimageHash hash: Data32) async throws -> Data? | ||
func get(serviceAccount index: ServiceIndex, preimageHash hash: Data32, length: UInt32) async throws | ||
-> LimitedSizeArray<TimeslotIndex, ConstInt0, ConstInt3>? | ||
|
||
mutating func mergeWith(postState: PreimagesPostState) | ||
} | ||
|
||
extension Preimages { | ||
public func updatePreimages( | ||
config _: ProtocolConfigRef, | ||
timeslot: TimeslotIndex, | ||
preimages: ExtrinsicPreimages | ||
) async throws(PreimagesError) -> PreimagesPostState { | ||
let preimages = preimages.preimages | ||
var updates: [PreimageUpdate] = [] | ||
|
||
guard preimages.isSortedAndUnique() else { | ||
throw PreimagesError.preimagesNotSorted | ||
} | ||
|
||
for preimage in preimages { | ||
let hash = preimage.data.blake2b256hash() | ||
|
||
// check prior state | ||
let prevPreimageData = try await Result { | ||
try await get(serviceAccount: preimage.serviceIndex, preimageHash: hash) | ||
}.mapError { _ in PreimagesError.invalidServiceIndex }.get() | ||
|
||
guard prevPreimageData == nil else { | ||
throw PreimagesError.duplicatedPreimage | ||
} | ||
|
||
updates.append(PreimageUpdate( | ||
serviceIndex: preimage.serviceIndex, | ||
hash: hash, | ||
data: preimage.data, | ||
length: UInt32(preimage.data.count), | ||
timeslot: timeslot | ||
)) | ||
} | ||
|
||
return PreimagesPostState(updates: updates) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,122 @@ | ||
import Blockchain | ||
import Codec | ||
import Foundation | ||
import Testing | ||
import Utils | ||
|
||
@testable import JAMTests | ||
|
||
struct PreimageInfo: Codable, Equatable, Hashable, Comparable { | ||
var hash: Data32 | ||
var blob: Data | ||
|
||
static func < (lhs: PreimageInfo, rhs: PreimageInfo) -> Bool { | ||
lhs.hash < rhs.hash | ||
} | ||
} | ||
|
||
struct HistoryEntry: Codable, Equatable { | ||
var key: HashAndLength | ||
var value: [TimeslotIndex] | ||
} | ||
|
||
struct AccountsMapEntry: Codable, Equatable { | ||
var index: ServiceIndex | ||
@CodingAs<SortedSet<PreimageInfo>> var preimages: Set<PreimageInfo> | ||
@CodingAs<SortedKeyValues<HashAndLength, [TimeslotIndex]>> var history: [HashAndLength: [TimeslotIndex]] | ||
} | ||
|
||
struct PreimagesState: Equatable, Codable, Preimages { | ||
var accounts: [AccountsMapEntry] = [] | ||
|
||
func get(serviceAccount index: ServiceIndex, preimageHash hash: Data32) async throws -> Data? { | ||
for account in accounts where account.index == index { | ||
for preimage in account.preimages where preimage.hash == hash { | ||
return preimage.blob | ||
} | ||
} | ||
return nil | ||
} | ||
|
||
func get(serviceAccount index: ServiceIndex, preimageHash hash: Data32, | ||
length: UInt32) async throws -> LimitedSizeArray<TimeslotIndex, ConstInt0, ConstInt3>? | ||
{ | ||
for account in accounts where account.index == index { | ||
for history in account.history where history.key.hash == hash && history.key.length == length { | ||
return .init(history.value) | ||
} | ||
} | ||
return nil | ||
} | ||
|
||
mutating func mergeWith(postState: PreimagesPostState) { | ||
for update in postState.updates { | ||
let accountIndex = accounts.firstIndex { account in | ||
account.index == update.serviceIndex | ||
} | ||
if let accountIndex { | ||
var account = accounts[accountIndex] | ||
account.preimages.insert(PreimageInfo(hash: update.hash, blob: update.data)) | ||
account.history[HashAndLength(hash: update.hash, length: update.length)] = [update.timeslot] | ||
accounts[accountIndex] = account | ||
} | ||
} | ||
} | ||
} | ||
|
||
struct PreimagesInput: Codable { | ||
var preimages: ExtrinsicPreimages | ||
var slot: TimeslotIndex | ||
} | ||
|
||
struct PreimagesTestcase: Codable { | ||
var input: PreimagesInput | ||
var preState: PreimagesState | ||
var output: UInt8? | ||
var postState: PreimagesState | ||
} | ||
|
||
struct PreimagesTests { | ||
static func loadTests() throws -> [Testcase] { | ||
try TestLoader.getTestcases(path: "preimages/data", extension: "bin") | ||
} | ||
|
||
func preimagesTests(_ testcase: Testcase, variant: TestVariants) async throws { | ||
let config = variant.config | ||
let decoder = JamDecoder(data: testcase.data, config: config) | ||
let testcase = try decoder.decode(PreimagesTestcase.self) | ||
|
||
var state = testcase.preState | ||
let result = await Result { | ||
try await state.updatePreimages( | ||
config: config, | ||
timeslot: testcase.input.slot, | ||
preimages: testcase.input.preimages | ||
) | ||
} | ||
|
||
switch result { | ||
case let .success(postState): | ||
switch testcase.output { | ||
case .none: | ||
state.mergeWith(postState: postState) | ||
#expect(state == testcase.postState) | ||
case .some: | ||
Issue.record("Expected error, got \(result)") | ||
} | ||
case .failure: | ||
switch testcase.output { | ||
case .none: | ||
Issue.record("Expected success, got \(result)") | ||
case .some: | ||
// ignore error code because it is unspecified | ||
break | ||
} | ||
} | ||
} | ||
|
||
@Test(arguments: try PreimagesTests.loadTests()) | ||
func tests(_ testcase: Testcase) async throws { | ||
try await preimagesTests(testcase, variant: .full) | ||
} | ||
} |
Submodule jamtestvectors
updated
14 files
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.