diff --git a/Blockchain/Sources/Blockchain/Runtime.swift b/Blockchain/Sources/Blockchain/Runtime.swift index 091395ae..175080c9 100644 --- a/Blockchain/Sources/Blockchain/Runtime.swift +++ b/Blockchain/Sources/Blockchain/Runtime.swift @@ -115,9 +115,14 @@ public final class Runtime { let workReportHashes = block.extrinsic.reports.guarantees.map(\.workReport.packageSpecification.workPackageHash) + let accumulationResult = Data32() // TODO: calculate accumulation result + + var mmr = history.items.last?.mmr ?? .init([]) + mmr.append(accumulationResult, hasher: Keccak.self) + let newItem = try RecentHistory.HistoryItem( headerHash: block.header.parentHash, - mmrRoots: [], // TODO: update MMR roots + mmr: mmr, stateRoot: Data32(), // empty and will be updated upon next block workReportHashes: ConfigLimitedSizeArray(config: config, array: workReportHashes) ) diff --git a/Blockchain/Sources/Blockchain/Types/RecentHistory.swift b/Blockchain/Sources/Blockchain/Types/RecentHistory.swift index ae0b4843..fc76ac0f 100644 --- a/Blockchain/Sources/Blockchain/Types/RecentHistory.swift +++ b/Blockchain/Sources/Blockchain/Types/RecentHistory.swift @@ -7,7 +7,7 @@ public struct RecentHistory: Sendable, Equatable, Codable { public var headerHash: Data32 // b: accumulation-result mmr - public var mmrRoots: [Data32] + public var mmr: MMR // s public var stateRoot: Data32 @@ -17,12 +17,12 @@ public struct RecentHistory: Sendable, Equatable, Codable { public init( headerHash: Data32, - mmrRoots: [Data32], + mmr: MMR, stateRoot: Data32, workReportHashes: ConfigLimitedSizeArray ) { self.headerHash = headerHash - self.mmrRoots = mmrRoots + self.mmr = mmr self.stateRoot = stateRoot self.workReportHashes = workReportHashes } diff --git a/Utils/Sources/Utils/Either.swift b/Utils/Sources/Utils/Either.swift index c1b362ba..a89f1616 100644 --- a/Utils/Sources/Utils/Either.swift +++ b/Utils/Sources/Utils/Either.swift @@ -1,13 +1,13 @@ -public enum Either { - case left(A) - case right(B) +public enum Either { + case left(Left) + case right(Right) } -extension Either: Equatable where A: Equatable, B: Equatable {} +extension Either: Equatable where Left: Equatable, Right: Equatable {} -extension Either: Sendable where A: Sendable, B: Sendable {} +extension Either: Sendable where Left: Sendable, Right: Sendable {} -extension Either: CustomStringConvertible where A: CustomStringConvertible, B: CustomStringConvertible { +extension Either: CustomStringConvertible where Left: CustomStringConvertible, Right: CustomStringConvertible { public var description: String { switch self { case let .left(a): @@ -18,7 +18,7 @@ extension Either: CustomStringConvertible where A: CustomStringConvertible, B: C } } -extension Either: Codable where A: Codable, B: Codable { +extension Either: Codable where Left: Codable, Right: Codable { enum CodingKeys: String, CodingKey { case left case right @@ -52,10 +52,10 @@ extension Either: Codable where A: Codable, B: Codable { let variant = try container.decode(UInt8.self) switch variant { case 0: - let a = try container.decode(A.self) + let a = try container.decode(Left.self) self = .left(a) case 1: - let b = try container.decode(B.self) + let b = try container.decode(Right.self) self = .right(b) default: throw DecodingError.dataCorrupted( @@ -68,10 +68,10 @@ extension Either: Codable where A: Codable, B: Codable { } else { let container = try decoder.container(keyedBy: CodingKeys.self) if container.contains(.left) { - let a = try container.decode(A.self, forKey: .left) + let a = try container.decode(Left.self, forKey: .left) self = .left(a) } else if container.contains(.right) { - let b = try container.decode(B.self, forKey: .right) + let b = try container.decode(Right.self, forKey: .right) self = .right(b) } else { throw DecodingError.dataCorrupted( diff --git a/Utils/Sources/Utils/Array+Utils.swift b/Utils/Sources/Utils/Extensions/Array+Utils.swift similarity index 100% rename from Utils/Sources/Utils/Array+Utils.swift rename to Utils/Sources/Utils/Extensions/Array+Utils.swift diff --git a/Utils/Sources/Utils/Collection+Utils.swift b/Utils/Sources/Utils/Extensions/Collection+Utils.swift similarity index 100% rename from Utils/Sources/Utils/Collection+Utils.swift rename to Utils/Sources/Utils/Extensions/Collection+Utils.swift diff --git a/Utils/Sources/Utils/Data+Utils.swift b/Utils/Sources/Utils/Extensions/Data+Utils.swift similarity index 100% rename from Utils/Sources/Utils/Data+Utils.swift rename to Utils/Sources/Utils/Extensions/Data+Utils.swift diff --git a/Utils/Sources/Utils/Result+Utils.swift b/Utils/Sources/Utils/Extensions/Result+Utils.swift similarity index 100% rename from Utils/Sources/Utils/Result+Utils.swift rename to Utils/Sources/Utils/Extensions/Result+Utils.swift diff --git a/Utils/Sources/Utils/Extensions/UnsignedInteger+Utils.swift b/Utils/Sources/Utils/Extensions/UnsignedInteger+Utils.swift new file mode 100644 index 00000000..3747d686 --- /dev/null +++ b/Utils/Sources/Utils/Extensions/UnsignedInteger+Utils.swift @@ -0,0 +1,10 @@ +extension FixedWidthInteger { + /// Return the next power of two that is equal or greater than self. + /// Returns nil if self is 0 or the next power of two is greater than `Self.max`. + public var nextPowerOfTwo: Self? { + guard self > 0 else { return nil } + let leadingZeroBitCount = (self - 1).leadingZeroBitCount + guard leadingZeroBitCount > 0 else { return nil } + return 1 << (bitWidth - leadingZeroBitCount) + } +} diff --git a/Utils/Sources/Utils/Hashing/Blake2b256.swift b/Utils/Sources/Utils/Hashing/Blake2b256.swift index fcf5cc45..dad617bc 100644 --- a/Utils/Sources/Utils/Hashing/Blake2b256.swift +++ b/Utils/Sources/Utils/Hashing/Blake2b256.swift @@ -1,7 +1,7 @@ import Blake2 import Foundation -public struct Blake2b256: ~Copyable, Hashing { +public struct Blake2b256: /* ~Copyable, */ Hashing { private var hasher: Blake2b public init() { diff --git a/Utils/Sources/Utils/Hashing/Hasher.swift b/Utils/Sources/Utils/Hashing/Hasher.swift deleted file mode 100644 index 455ef1a0..00000000 --- a/Utils/Sources/Utils/Hashing/Hasher.swift +++ /dev/null @@ -1,7 +0,0 @@ -import Blake2 - -public protocol Hashing: ~Copyable { - init() - mutating func update(_ data: some DataPtrRepresentable) - consuming func finalize() -> Data32 -} diff --git a/Utils/Sources/Utils/Hashing/Hashing.swift b/Utils/Sources/Utils/Hashing/Hashing.swift new file mode 100644 index 00000000..0d0bbeea --- /dev/null +++ b/Utils/Sources/Utils/Hashing/Hashing.swift @@ -0,0 +1,46 @@ +import Blake2 +import Foundation + +public typealias DataPtrRepresentable = Blake2.DataPtrRepresentable + +// Waiting for NoncopyableGenerics to be available +public protocol Hashing /*: ~Copyable */ { + init() + mutating func update(_ data: some DataPtrRepresentable) + consuming func finalize() -> Data32 +} + +extension Hashing { + public static func hash(data: some DataPtrRepresentable) -> Data32 { + var hasher = Self() + hasher.update(data) + return hasher.finalize() + } +} + +extension FixedSizeData: DataPtrRepresentable { + public typealias Ptr = UnsafeRawBufferPointer + + public func withPtr( + cb: (UnsafeRawBufferPointer) throws -> R + ) rethrows -> R { + try data.withUnsafeBytes(cb) + } +} + +extension Either: DataPtrRepresentable, PtrRepresentable where Left: DataPtrRepresentable, Right: DataPtrRepresentable, + Left.Ptr == Right.Ptr +{ + public typealias Ptr = Left.Ptr + + public func withPtr( + cb: (Left.Ptr) throws -> R + ) rethrows -> R { + switch self { + case let .left(left): + try left.withPtr(cb: cb) + case let .right(right): + try right.withPtr(cb: cb) + } + } +} diff --git a/Utils/Sources/Utils/Hashing/Keccak.swift b/Utils/Sources/Utils/Hashing/Keccak.swift index a71862de..595f5cb7 100644 --- a/Utils/Sources/Utils/Hashing/Keccak.swift +++ b/Utils/Sources/Utils/Hashing/Keccak.swift @@ -2,7 +2,7 @@ import Blake2 import Foundation import sha3_iuf -public struct Keccak: ~Copyable, Hashing { +public struct Keccak: /* ~Copyable, */ Hashing { private var ctx: sha3_context = .init() public init() { diff --git a/Utils/Sources/Utils/MaybeEither.swift b/Utils/Sources/Utils/MaybeEither.swift new file mode 100644 index 00000000..3828182e --- /dev/null +++ b/Utils/Sources/Utils/MaybeEither.swift @@ -0,0 +1,27 @@ +public struct MaybeEither { + public var value: Either + + public init(_ value: Either) { + self.value = value + } + + public init(left value: Left) { + self.value = .left(value) + } + + public init(right value: Right) { + self.value = .right(value) + } +} + +extension MaybeEither where Left == Right { + typealias Unwrapped = Left + public var unwrapped: Left { + switch value { + case let .left(left): + left + case let .right(right): + right + } + } +} diff --git a/Utils/Sources/Utils/Merklization/MMR.swift b/Utils/Sources/Utils/Merklization/MMR.swift new file mode 100644 index 00000000..109aa586 --- /dev/null +++ b/Utils/Sources/Utils/Merklization/MMR.swift @@ -0,0 +1,27 @@ +// TODO: add tests +// Merkle Mountain Range +public struct MMR: Sendable, Equatable, Codable { + public var peaks: [Data32?] + + public init(_ peaks: [Data32?]) { + self.peaks = peaks + } + + public mutating func append(_ data: Data32, hasher: Hashing.Type = Blake2b256.self) { + append(data, at: 0, hasher: hasher) + } + + private mutating func append(_ data: Data32, at index: Int, hasher: Hashing.Type = Blake2b256.self) { + if index >= peaks.count { + peaks.append(data) + } else if let current = peaks[index] { + var hash = hasher.init() + hash.update(current) + hash.update(data) + peaks[index] = nil + append(hash.finalize(), at: index + 1, hasher: hasher) + } else { + peaks[index] = data + } + } +} diff --git a/Utils/Sources/Utils/Merklization/Merklization.swift b/Utils/Sources/Utils/Merklization/Merklization.swift new file mode 100644 index 00000000..2f507395 --- /dev/null +++ b/Utils/Sources/Utils/Merklization/Merklization.swift @@ -0,0 +1,135 @@ +import Foundation + +// TODO: add tests +public enum Merklization { + // roundup of half + private static func half(_ i: Int) -> Int { + (i + 1) / 2 + } + + private static func binaryMerklizeHelper( + _ nodes: T, + hasher: Hashing.Type = Blake2b256.self + ) -> MaybeEither + where T: RandomAccessCollection, T.Index == Int, U: DataPtrRepresentable + { + switch nodes.count { + case 0: + return .init(right: Data32()) + case 1: + return .init(left: nodes.first!) + default: + let midIndex = nodes.startIndex + half(nodes.count) + let l = nodes[nodes.startIndex ..< midIndex] + let r = nodes[midIndex ..< nodes.endIndex] + var hash = hasher.init() + hash.update("node") + hash.update(binaryMerklizeHelper(l).value) + hash.update(binaryMerklizeHelper(r).value) + return .init(right: hash.finalize()) + } + } + + // well-balanced binary Merkle function defined in GP E.1.1 + public static func binaryMerklize>(_ nodes: T, hasher: Hashing.Type = Blake2b256.self) -> Data32 + where T.Index == Int + { + switch binaryMerklizeHelper(nodes, hasher: hasher).value { + case let .left(data): + hasher.hash(data: data) + case let .right(data): + data + } + } + + private static func traceImpl( + _ nodes: T, + index: T.Index, + hasher: Hashing.Type, + output: (MaybeEither) -> Void + ) + where T: RandomAccessCollection, T.Index == Int, U: DataPtrRepresentable + { + if nodes.count == 0 { + return + } + + func selectPart(left: Bool, nodes: T, index: T.Index) -> T.SubSequence { + let h = half(nodes.count) + if (index < h) == left { + return nodes[nodes.startIndex ..< nodes.startIndex + h] + } else { + return nodes[nodes.startIndex + h ..< nodes.endIndex] + } + } + + func selectIndex(nodes: T, index: T.Index) -> T.Index { + let h = half(nodes.count) + if index < h { + return 0 + } + return h + } + + let l = binaryMerklizeHelper(selectPart(left: true, nodes: nodes, index: index), hasher: hasher) + output(l) + traceImpl( + selectPart(left: false, nodes: nodes, index: index), + index: index - selectIndex(nodes: nodes, index: index), + hasher: hasher, + output: output + ) + } + + public static func trace( + _ nodes: T, + index: T.Index, + hasher: Hashing.Type = Blake2b256.self + ) -> [Either] + where T: RandomAccessCollection, T.Index == Int, U: DataPtrRepresentable + { + var res: [Either] = [] + traceImpl(nodes, index: index, hasher: hasher) { res.append($0.value) } + return res + } + + private static func constancyPreprocessor( + _ nodes: some RandomAccessCollection, + hasher: Hashing.Type = Blake2b256.self + ) -> [Data32] { + let length = UInt32(nodes.count) + let newLength = Int(length.nextPowerOfTwo ?? 0) + var res: [Data32] = [] + res.reserveCapacity(newLength) + for node in nodes { + var hash = hasher.init() + hash.update("leaf") + hash.update(node) + res.append(hash.finalize()) + } + // fill the rest with zeros + for _ in nodes.count ..< newLength { + res.append(Data32()) + } + return res + } + + // constant-depth binary merkle function defined in GP E.1.2 + public static func constantDepthMerklize>(_ nodes: T, hasher: Hashing.Type = Blake2b256.self) -> Data32 + where T.Index == Int + { + binaryMerklizeHelper(constancyPreprocessor(nodes, hasher: hasher)).unwrapped + } + + public static func generateJustification( + _ nodes: T, + index: T.Index, + hasher: Hashing.Type = Blake2b256.self + ) -> [Data32] + where T: RandomAccessCollection, T.Index == Int + { + var res: [Data32] = [] + traceImpl(constancyPreprocessor(nodes, hasher: hasher), index: index, hasher: hasher) { res.append($0.unwrapped) } + return res + } +} diff --git a/Utils/Sources/Utils/merklization.swift b/Utils/Sources/Utils/Merklization/StateMerklization.swift similarity index 100% rename from Utils/Sources/Utils/merklization.swift rename to Utils/Sources/Utils/Merklization/StateMerklization.swift diff --git a/Utils/Tests/UtilsTests/FixedWidthIntegerTests.swift b/Utils/Tests/UtilsTests/FixedWidthIntegerTests.swift new file mode 100644 index 00000000..7f2b9568 --- /dev/null +++ b/Utils/Tests/UtilsTests/FixedWidthIntegerTests.swift @@ -0,0 +1,30 @@ +import Testing + +@testable import Utils + +struct FixedWidthIntegerTests { + @Test func nextPowerOfTwo() throws { + #expect(UInt8(0).nextPowerOfTwo == nil) + #expect(UInt8(1).nextPowerOfTwo == UInt8(1)) + #expect(UInt8(2).nextPowerOfTwo == UInt8(2)) + #expect(UInt8(3).nextPowerOfTwo == UInt8(4)) + #expect(UInt8(4).nextPowerOfTwo == UInt8(4)) + #expect(UInt8(5).nextPowerOfTwo == UInt8(8)) + #expect(UInt8(8).nextPowerOfTwo == UInt8(8)) + #expect(UInt8(127).nextPowerOfTwo == UInt8(128)) + #expect(UInt8(128).nextPowerOfTwo == UInt8(128)) + #expect(UInt8(129).nextPowerOfTwo == nil) + #expect(UInt8(255).nextPowerOfTwo == nil) + + #expect(UInt32(0).nextPowerOfTwo == nil) + #expect(UInt32(1).nextPowerOfTwo == UInt32(1)) + #expect(UInt32(2).nextPowerOfTwo == UInt32(2)) + #expect(UInt32(511).nextPowerOfTwo == UInt32(512)) + #expect(UInt32(512).nextPowerOfTwo == UInt32(512)) + #expect(UInt32(513).nextPowerOfTwo == UInt32(1024)) + #expect(UInt32(0x7FFF_FFFF).nextPowerOfTwo == UInt32(0x8000_0000)) + #expect(UInt32(0xF000_0000).nextPowerOfTwo == nil) + #expect(UInt32(0xF000_0001).nextPowerOfTwo == nil) + #expect(UInt32.max.nextPowerOfTwo == nil) + } +}