Skip to content

Commit

Permalink
feat(lib/erasure): implement Go binding over rust for erasure coding (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
axaysagathiya authored and kishansagathiya committed Jan 23, 2024
1 parent 5bd66b0 commit 6f864af
Show file tree
Hide file tree
Showing 13 changed files with 4,584 additions and 148 deletions.
3 changes: 3 additions & 0 deletions .github/workflows/unit-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,9 @@ jobs:
go test -timeout=10m ./... && \
cd ..
- name: generate a shared library file for erasure
run: make compile-erasure

- name: Run unit tests
run: go test -coverprofile=coverage.out -covermode=atomic -timeout=45m ./...

Expand Down
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,8 @@ tmp
# node_modules used by polkadot.js/api tests
tests/polkadotjs_test/node_modules
!tests/polkadotjs_test/test/*.wasm

# Ignore rust target dir
lib/erasure/rustlib/target

*.so
3 changes: 3 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -147,3 +147,6 @@ endif

zombienet-test: install install-zombienet
zombienet test -p native zombienet_tests/functional/0001-basic-network.zndsl

compile-erasure:
cargo build --release --manifest-path=lib/erasure/rustlib/Cargo.toml
2 changes: 1 addition & 1 deletion dot/parachain/runtime/instance.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import (

parachaintypes "github.com/ChainSafe/gossamer/dot/parachain/types"
"github.com/ChainSafe/gossamer/lib/common"
runtimewasmer "github.com/ChainSafe/gossamer/lib/runtime/wasmer"
runtimewasmer "github.com/ChainSafe/gossamer/lib/runtime/wazero"
"github.com/ChainSafe/gossamer/pkg/scale"
)

Expand Down
3 changes: 1 addition & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ require (
github.com/ethereum/go-ethereum v1.13.10
github.com/fatih/color v1.16.0
github.com/go-playground/validator/v10 v10.17.0
github.com/golang/mock v1.6.0
github.com/google/go-cmp v0.6.0
github.com/google/uuid v1.5.0
github.com/gorilla/mux v1.8.1
Expand All @@ -25,7 +26,6 @@ require (
github.com/ipfs/go-ds-badger2 v0.1.3
github.com/jpillora/ipfilter v1.2.9
github.com/klauspost/compress v1.17.4
github.com/klauspost/reedsolomon v1.11.8
github.com/libp2p/go-libp2p v0.31.0
github.com/libp2p/go-libp2p-kad-dht v0.25.2
github.com/minio/sha256-simd v1.0.1
Expand Down Expand Up @@ -85,7 +85,6 @@ require (
github.com/godbus/dbus/v5 v5.1.0 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/glog v1.0.0 // indirect
github.com/golang/mock v1.6.0 // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb // indirect
github.com/google/gopacket v1.1.19 // indirect
Expand Down
2 changes: 0 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -393,8 +393,6 @@ github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6K
github.com/klauspost/cpuid v1.2.1/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
github.com/klauspost/cpuid/v2 v2.2.6 h1:ndNyv040zDGIDh8thGkXYjnFtiN02M1PVVF+JE/48xc=
github.com/klauspost/cpuid/v2 v2.2.6/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
github.com/klauspost/reedsolomon v1.11.8 h1:s8RpUW5TK4hjr+djiOpbZJB4ksx+TdYbRH7vHQpwPOY=
github.com/klauspost/reedsolomon v1.11.8/go.mod h1:4bXRN+cVzMdml6ti7qLouuYi32KHJ5MGv0Qd8a47h6A=
github.com/koron/go-ssdp v0.0.4 h1:1IDwrghSKYM7yLf7XCzbByg2sJ/JcNOZRXS2jczTwz0=
github.com/koron/go-ssdp v0.0.4/go.mod h1:oDXq+E5IL5q0U8uSBcoAXzTzInwy5lEgC91HoKtbmZk=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
Expand Down
6 changes: 6 additions & 0 deletions lib/erasure/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Building Rust code Binary

- Generate rust binary
```
cargo build --release --manifest-path=lib/erasure/rustlib/Cargo.toml
```
125 changes: 78 additions & 47 deletions lib/erasure/erasure.go
Original file line number Diff line number Diff line change
@@ -1,70 +1,101 @@
// Copyright 2021 ChainSafe Systems (ON)
// Copyright 2023 ChainSafe Systems (ON)
// SPDX-License-Identifier: LGPL-3.0-only

package erasure

// #cgo LDFLAGS: -Wl,-rpath,${SRCDIR}/rustlib/target/release -L${SRCDIR}/rustlib/target/release -lerasure
// #include "./erasure.h"
import (
"C"
)
import (
"bytes"
"errors"
"fmt"

"github.com/klauspost/reedsolomon"
"unsafe"
)

// ErrNotEnoughValidators cannot encode something for zero or one validator
var ErrNotEnoughValidators = errors.New("expected at least 2 validators")
var (
ErrZeroSizedData = errors.New("data can't be zero sized")
ErrZeroSizedChunks = errors.New("chunks can't be zero sized")
)

// ObtainChunks obtains erasure-coded chunks, divides data into number of validatorsQty chunks and
// creates parity chunks for reconstruction
func ObtainChunks(validatorsQty int, data []byte) ([][]byte, error) {
recoveryThres, err := recoveryThreshold(validatorsQty)
if err != nil {
return nil, err
}
enc, err := reedsolomon.New(validatorsQty, recoveryThres)
if err != nil {
return nil, fmt.Errorf("creating new reed solomon failed: %w", err)
// ObtainChunks obtains erasure-coded chunks, one for each validator.
// This works only up to 65536 validators, and `n_validators` must be non-zero and accepts
// number of validators and scale encoded data.
func ObtainChunks(nValidators uint, data []byte) ([][]byte, error) {
if len(data) == 0 {
return nil, ErrZeroSizedData
}
shards, err := enc.Split(data)
if err != nil {
return nil, err

var cFlattenedChunks *C.uchar
var cFlattenedChunksLen C.size_t

cnValidators := C.size_t(nValidators)
cData := (*C.uchar)(unsafe.Pointer(&data[0]))
cLen := C.size_t(len(data))

cErr := C.obtain_chunks(cnValidators, cData, cLen, &cFlattenedChunks, &cFlattenedChunksLen)
errStr := C.GoString(cErr)
C.free(unsafe.Pointer(cErr))

if len(errStr) > 0 {
return nil, errors.New(errStr)
}
err = enc.Encode(shards)
if err != nil {
return nil, err

resData := C.GoBytes(unsafe.Pointer(cFlattenedChunks), C.int(cFlattenedChunksLen))
C.free(unsafe.Pointer(cFlattenedChunks))

chunkSize := uint(len(resData)) / nValidators
chunks := make([][]byte, nValidators)

start := uint(0)
for i := start; i < nValidators; i++ {
end := start + chunkSize
chunks[i] = resData[start:end]
start = end
}

return shards, nil
return chunks, nil
}

// Reconstruct the missing data from a set of chunks
func Reconstruct(validatorsQty, originalDataLen int, chunks [][]byte) ([]byte, error) {
recoveryThres, err := recoveryThreshold(validatorsQty)
if err != nil {
return nil, err
// Reconstruct decodable data from a set of chunks.
//
// Provide an iterator containing chunk data and the corresponding index.
// The indices of the present chunks must be indicated. If too few chunks
// are provided, recovery is not possible.
//
// Works only up to 65536 validators, and `n_validators` must be non-zero
func Reconstruct(nValidators uint, chunks [][]byte) ([]byte, error) {
if len(chunks) == 0 {
return nil, ErrZeroSizedChunks
}

enc, err := reedsolomon.New(validatorsQty, recoveryThres)
if err != nil {
return nil, err
}
err = enc.Reconstruct(chunks)
if err != nil {
return nil, err
var cReconstructedData *C.uchar
var cReconstructedDataLen C.size_t
var flattenedChunks []byte

for _, chunk := range chunks {
flattenedChunks = append(flattenedChunks, chunk...)
}
buf := new(bytes.Buffer)
err = enc.Join(buf, chunks, originalDataLen)
return buf.Bytes(), err
}

// recoveryThreshold gives the max number of shards/chunks that we can afford to lose and still construct
// the full initial data. Total number of chunks will be validatorQty + recoveryThreshold
func recoveryThreshold(validators int) (int, error) {
if validators <= 1 {
return 0, ErrNotEnoughValidators
cChunkSize := C.size_t(len(chunks[0]))
cFlattenedChunks := (*C.uchar)(unsafe.Pointer(&flattenedChunks[0]))
cFlattenedChunksLen := C.size_t(len(flattenedChunks))

cErr := C.reconstruct(
C.size_t(nValidators),
cFlattenedChunks, cFlattenedChunksLen,
cChunkSize,
&cReconstructedData, &cReconstructedDataLen,
)
errStr := C.GoString(cErr)
C.free(unsafe.Pointer(cErr))

if len(errStr) > 0 {
return nil, errors.New(errStr)
}

needed := (validators - 1) / 3
res := C.GoBytes(unsafe.Pointer(cReconstructedData), C.int(cReconstructedDataLen))
C.free(unsafe.Pointer(cReconstructedData))

return needed + 1, nil
return res, nil
}
11 changes: 11 additions & 0 deletions lib/erasure/erasure.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/*
* Copyright 2023 ChainSafe Systems (ON)
* SPDX-License-Identifier: LGPL-3.0-only
*/

#include <stdlib.h>
#include <stddef.h>

int32_t add(int32_t a, int32_t b);
const char* obtain_chunks(size_t n_validators, unsigned char *data, size_t len, unsigned char **flattened_chunks, size_t *flattened_chunks_len);
const char* reconstruct(size_t n_validators, unsigned char *flattened_chunks, size_t flattened_chunks_len, size_t chunk_size, unsigned char **res_data, size_t *res_len);
Loading

0 comments on commit 6f864af

Please sign in to comment.