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

erasure coding lib #53

Merged
merged 13 commits into from
Aug 12, 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
7 changes: 7 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,13 @@ jobs:
key: ${{ runner.os }}-libs-librocksdb-${{ steps.rocksdb-commit-hash.outputs.commit-hash }}
restore-keys: |
${{ runner.os }}-libs-librocksdb
- name: Cache erasure-coding static lib
uses: actions/cache@v4
with:
path: .lib/libec.a
key: ${{ runner.os }}-libs-libec-${{ hashFiles('Utils/Sources/erasure-coding/**') }}
restore-keys: |
${{ runner.os }}-libs-libec
- name: Setup Swift
uses: SwiftyLab/setup-swift@latest
with:
Expand Down
5 changes: 4 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,17 @@ default: build
githooks: .git/hooks/pre-commit

.PHONY: deps
deps: .lib/libblst.a .lib/libbandersnatch_vrfs.a .lib/librocksdb.a
deps: .lib/libblst.a .lib/libbandersnatch_vrfs.a .lib/librocksdb.a .lib/libec.a

.lib/libblst.a:
./scripts/blst.sh

.lib/libbandersnatch_vrfs.a: $(wildcard Utils/Sources/bandersnatch/src/*)
./scripts/bandersnatch.sh

.lib/libec.a: $(wildcard Utils/Sources/erasure-coding/src/*)
./scripts/erasure-coding.sh

.lib/librocksdb.a:
./scripts/rocksdb.sh

Expand Down
8 changes: 7 additions & 1 deletion Utils/Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ let package = Package(
.product(name: "Atomics", package: "swift-atomics"),
"blst",
"bandersnatch_vrfs",
"erasure_coding",
],
swiftSettings: [
.define("DEBUG_ASSERT", .when(configuration: .debug)),
Expand All @@ -50,12 +51,17 @@ let package = Package(
name: "bandersnatch_vrfs",
path: "Sources"
),
.systemLibrary(
name: "erasure_coding",
path: "Sources"
),
.testTarget(
name: "UtilsTests",
dependencies: [
"Utils",
.product(name: "Testing", package: "swift-testing"),
]
],
resources: [.copy("TestData")]
),
],
swiftLanguageVersions: [.version("6")]
Expand Down
147 changes: 147 additions & 0 deletions Utils/Sources/Utils/ErasureCoding.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
import erasure_coding
import Foundation

// TODO: note the underlying rust lib is not compatible with GP yet, so these will be changed

public enum ErasureCodeError: Error {
case constructFailed
case reconstructFailed
}

public struct Segment {
public var csegment: CSegment

public let data: Data
public let index: Int

public init?(data: Data, index: UInt32) {
guard data.count == SEGMENT_SIZE else {
return nil
}
csegment = CSegment(
data: UnsafeMutablePointer(mutating: data.withUnsafeBytes { $0.baseAddress!.assumingMemoryBound(to: UInt8.self) }),
index: index
)
self.data = Data(bytes: csegment.data, count: Int(SEGMENT_SIZE))
self.index = Int(csegment.index)
}
}

/// Split original data into segments
public func split(data: Data) -> [Segment] {
var segments: [Segment] = []
qiweiii marked this conversation as resolved.
Show resolved Hide resolved
let segmentSize = Int(SEGMENT_SIZE)
let remainder = data.count % segmentSize
segments.reserveCapacity((data.count / segmentSize) + (remainder > 0 ? 1 : 0))

// Create a new data with padding
var paddedData = data
if remainder != 0 {
paddedData.append(Data(repeating: 0, count: segmentSize - remainder))
}

for i in stride(from: 0, to: paddedData.count, by: segmentSize) {
let end = min(i + segmentSize, paddedData.count)
let segmentData = paddedData[i ..< end]
let index = UInt32(i / segmentSize)

let segment = Segment(data: segmentData, index: index)!
segments.append(segment)
}

return segments
}

/// Join segments into original data (padding not removed)
public func join(segments: [Segment]) -> Data {
var data = Data(capacity: segments.count * Int(SEGMENT_SIZE))
let sortedSegments = segments.sorted { $0.index < $1.index }

for segment in sortedSegments {
data.append(segment.data)
}

return data
}

public class SubShardEncoder {
private let encoder: OpaquePointer

public init() {
encoder = subshard_encoder_new()
}

deinit {
subshard_encoder_free(encoder)
}

/// Construct erasure-coded chunks from segments
public func construct(segments: [Segment]) -> Result<[UInt8], ErasureCodeError> {
var success = false
var out_len: UInt = 0

let expectedOutLen = Int(SUBSHARD_SIZE) * Int(TOTAL_SHARDS) * segments.count
var out_chunks = [UInt8](repeating: 0, count: expectedOutLen)

segments.map(\.csegment).withUnsafeBufferPointer { segmentsPtr in
subshard_encoder_construct(encoder, segmentsPtr.baseAddress, UInt(segments.count), &success, &out_chunks, &out_len)
}

guard success, expectedOutLen == Int(out_len) else {
return .failure(.constructFailed)
}

return .success(out_chunks)
}
}

public class SubShardDecoder {
private let decoder: OpaquePointer

public init() {
decoder = subshard_decoder_new()
}

deinit {
subshard_decoder_free(decoder)
}

/// Decoded reconstruct result
public class Decoded {
private var result: UnsafeMutablePointer<ReconstructResult>

public let segments: [SegmentTuple]
public let numDecoded: UInt

init(_ res: UnsafeMutablePointer<ReconstructResult>) {
result = res
let numSegments = Int(result.pointee.num_segments)
let segmentTuplesPtr = result.pointee.segments

let bufferPtr = UnsafeMutableBufferPointer<SegmentTuple>(start: segmentTuplesPtr, count: numSegments)
segments = Array(bufferPtr)

numDecoded = result.pointee.num_decodes
}

deinit {
reconstruct_result_free(result)
}
}

/// Reconstruct erasure-coded chunks to segments
public func reconstruct(subshards: [SubShardTuple]) -> Result<Decoded, ErasureCodeError> {
var success = false

let reconstructResult = subshards.withUnsafeBufferPointer { subshardsPtr in
subshard_decoder_reconstruct(decoder, subshardsPtr.baseAddress, UInt(subshards.count), &success)
}

guard success, let result = reconstructResult
else {
return .failure(.reconstructFailed)
}

return .success(Decoded(result))
}
}
Loading