Skip to content

Commit

Permalink
refactor(dot/parachain/types): Move BitVec to parachaintypes pack…
Browse files Browse the repository at this point in the history
…age (#4111)
  • Loading branch information
timwu20 authored Aug 1, 2024
1 parent 6fbff5f commit 8f0fd69
Show file tree
Hide file tree
Showing 11 changed files with 94 additions and 257 deletions.
3 changes: 1 addition & 2 deletions dot/parachain/backing/statement_table.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import (
"slices"

parachaintypes "github.com/ChainSafe/gossamer/dot/parachain/types"
"github.com/ChainSafe/gossamer/pkg/scale"
)

var errCandidateDataNotFound = errors.New("candidate data not found")
Expand Down Expand Up @@ -448,7 +447,7 @@ func (attested *attestedCandidate) toBackedCandidate(tableCtx *tableContext) *pa
return &parachaintypes.BackedCandidate{
Candidate: attested.committedCandidateReceipt,
ValidityVotes: validityAttestations,
ValidatorIndices: scale.NewBitVec(validatorIndices),
ValidatorIndices: parachaintypes.NewBitVec(validatorIndices),
}
}

Expand Down
87 changes: 61 additions & 26 deletions pkg/scale/bitvec.go → dot/parachain/types/bitvec.go
Original file line number Diff line number Diff line change
@@ -1,16 +1,22 @@
// Copyright 2023 ChainSafe Systems (ON)
// Copyright 2024 ChainSafe Systems (ON)
// SPDX-License-Identifier: LGPL-3.0-only

package scale
package parachaintypes

const (
// maxLen equivalent of `ARCH32BIT_BITSLICE_MAX_BITS` in parity-scale-codec
maxLen = 268435455
// byteSize is the number of bits in a byte
byteSize = 8
import (
"bytes"
"fmt"
"io"

"github.com/ChainSafe/gossamer/pkg/scale"
)

// BitVec is the implementation of the bit vector
const byteSize = 8
const bitVecMaxLength = 268435455

var errBitVecTooLong = fmt.Errorf("bitvec too long")

// BitVec is the implementation of a bit vector
type BitVec struct {
bits []bool
}
Expand All @@ -26,26 +32,12 @@ func NewBitVec(bits []bool) BitVec {
}
}

// Bits returns the bits in the BitVec
func (bv *BitVec) Bits() []bool {
return bv.bits
}

// Bytes returns the byte representation of the BitVec.Bits
func (bv *BitVec) Bytes() []byte {
return bitsToBytes(bv.bits)
}

// Size returns the number of bits in the BitVec
func (bv *BitVec) Size() uint {
return uint(len(bv.bits))
}

// bitsToBytes converts a slice of bits to a slice of bytes
// Uses lsb ordering
// TODO: Implement msb ordering
// https://github.com/ChainSafe/gossamer/issues/3248
func bitsToBytes(bits []bool) []byte {
func (bv *BitVec) bytes() []byte {
bits := bv.bits
bitLength := len(bits)
numOfBytes := (bitLength + (byteSize - 1)) / byteSize
bytes := make([]byte, numOfBytes)
Expand All @@ -68,7 +60,7 @@ func bitsToBytes(bits []bool) []byte {
}

// bytesToBits converts a slice of bytes to a slice of bits
func bytesToBits(b []byte, size uint) []bool {
func (bv *BitVec) setBits(b []byte, size uint) {
var bits []bool
for _, uint8val := range b {
end := size
Expand All @@ -82,6 +74,49 @@ func bytesToBits(b []byte, size uint) []bool {
bits = append(bits, bit)
}
}
bv.bits = bits
}

// MarshalSCALE fulfils the SCALE interface for encoding
func (bv BitVec) MarshalSCALE() ([]byte, error) {
buf := bytes.NewBuffer(nil)
encoder := scale.NewEncoder(buf)
if len(bv.bits) > bitVecMaxLength {
return nil, errBitVecTooLong
}
size := uint(len(bv.bits))
err := encoder.Encode(size)
if err != nil {
return nil, err
}

bytes := bv.bytes()
_, err = buf.Write(bytes)
if err != nil {
return nil, err
}
return buf.Bytes(), nil
}

// UnmarshalSCALE fulfils the SCALE interface for decoding
func (bv *BitVec) UnmarshalSCALE(r io.Reader) error {
decoder := scale.NewDecoder(r)
var size uint
err := decoder.Decode(&size)
if err != nil {
return err
}
if size > bitVecMaxLength {
return errBitVecTooLong
}

numBytes := (size + (byteSize - 1)) / byteSize
b := make([]byte, numBytes)
_, err = r.Read(b)
if err != nil {
return err
}

return bits
bv.setBits(b, size)
return nil
}
90 changes: 27 additions & 63 deletions pkg/scale/bitvec_test.go → dot/parachain/types/bitvec_test.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
// Copyright 2023 ChainSafe Systems (ON)
// Copyright 2024 ChainSafe Systems (ON)
// SPDX-License-Identifier: LGPL-3.0-only

package scale
package parachaintypes

import (
"testing"

"github.com/ChainSafe/gossamer/lib/common"
"github.com/ChainSafe/gossamer/pkg/scale"
"github.com/stretchr/testify/require"
)

Expand Down Expand Up @@ -44,111 +45,72 @@ func TestBitVec(t *testing.T) {
}

for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
tt := tt
t.Parallel()
resultBytes, err := common.HexToBytes(tt.in)
require.NoError(t, err)

bv := NewBitVec(nil)
err = Unmarshal(resultBytes, &bv)
b, err := scale.Marshal(tt.wantBitVec)
require.NoError(t, err)
require.Equal(t, resultBytes, b)

require.Equal(t, tt.wantBitVec.Size(), bv.Size())
require.Equal(t, tt.wantBitVec.Size(), bv.Size())
bv := NewBitVec(nil)
err = scale.Unmarshal(resultBytes, &bv)
require.NoError(t, err)
require.Equal(t, tt.wantBitVec.bits, bv.bits)

b, err := Marshal(bv)
b, err = scale.Marshal(bv)
require.NoError(t, err)
require.Equal(t, resultBytes, b)
})
}
}

func TestBitVecBytes(t *testing.T) {
func TestBitVec_bytes(t *testing.T) {
t.Parallel()
tests := []struct {
name string
in BitVec
in []bool
want []byte
wantErr bool
}{
{
name: "empty_bitvec",
in: NewBitVec(nil),
name: "empty",
in: []bool(nil),
want: []byte{},
wantErr: false,
},
{
name: "1_byte",
in: NewBitVec([]bool{true, false, true, false, true, false, true, false}),
in: []bool{true, false, true, false, true, false, true, false},
want: []byte{0x55},
wantErr: false,
},
{
name: "4_bytes",
in: NewBitVec([]bool{
in: []bool{
true, false, true, false, true, false, true, false,
false, true, true, false, true, true, false, false,
false, true, false, true, false, true, false, true,
true,
}),
want: []byte{0x55, 0x36, 0xaa, 0x1},
wantErr: false,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tt := tt
t.Parallel()
require.Equal(t, tt.want, tt.in.Bytes())
})
}
}

func TestBitVecBytesToBits(t *testing.T) {
t.Parallel()
tests := []struct {
name string
in []byte
want []bool
wantErr bool
}{
{
name: "empty",
in: []byte(nil),
want: []bool(nil),
wantErr: false,
},
{
name: "1_byte",
in: []byte{0x55},
want: []bool{true, false, true, false, true, false, true, false},
wantErr: false,
},
{
name: "4_bytes",
in: []byte{0x55, 0x36, 0xaa, 0x1},
want: []bool{
true, false, true, false, true, false, true, false,
false, true, true, false, true, true, false, false,
false, true, false, true, false, true, false, true,
true, false, false, false, false, false, false, false,
},
wantErr: false,
want: []byte{0x55, 0x36, 0xaa, 0x1},
},
}

for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
tt := tt
t.Parallel()
require.Equal(t, tt.want, bytesToBits(tt.in, uint(len(tt.in)*byteSize)))
bv := BitVec{tt.in}
bytes := bv.bytes()
require.Equal(t, tt.want, bytes)
})
}
}

func TestBitVecBitsToBytes(t *testing.T) {
func TestBitVec_setBits(t *testing.T) {
t.Parallel()
tests := []struct {
name string
Expand Down Expand Up @@ -181,10 +143,12 @@ func TestBitVecBitsToBytes(t *testing.T) {
}

for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
tt := tt
t.Parallel()
require.Equal(t, tt.want, bitsToBytes(tt.in))
bv := BitVec{}
bv.setBits(tt.want, uint(len(tt.in)))
require.Equal(t, tt.in, bv.bits)
})
}
}
6 changes: 3 additions & 3 deletions dot/parachain/types/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ type OccupiedCore struct {
// A bitfield with 1 bit for each validator in the set. `1` bits mean that the corresponding
// validators has attested to availability on-chain. A 2/3+ majority of `1` bits means that
// this will be available.
Availability scale.BitVec `scale:"5"`
Availability BitVec `scale:"5"`
// The group assigned to distribute availability pieces of this candidate.
GroupResponsible GroupIndex `scale:"6"`
// The hash of the candidate occupying the core.
Expand Down Expand Up @@ -693,7 +693,7 @@ type BackedCandidate struct {
// The validity votes themselves, expressed as signatures.
ValidityVotes []ValidityAttestation `scale:"2"`
// The indices of the validators within the group, expressed as a bitfield.
ValidatorIndices scale.BitVec `scale:"3"` // TODO: it's a bitvec in rust, figure out actual type
ValidatorIndices BitVec `scale:"3"` // TODO: it's a bitvec in rust, figure out actual type
}

// ProspectiveParachainsMode represents the mode of a relay parent in the context
Expand Down Expand Up @@ -721,7 +721,7 @@ type ProspectiveParachainsMode struct {
type UncheckedSignedAvailabilityBitfield struct {
// The payload is part of the signed data. The rest is the signing context,
// which is known both at signing and at validation.
Payload scale.BitVec `scale:"1"`
Payload BitVec `scale:"1"`

// The index of the validator signing this statement.
ValidatorIndex ValidatorIndex `scale:"2"`
Expand Down
2 changes: 1 addition & 1 deletion dot/parachain/validation-protocol/messages_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -245,7 +245,7 @@ func TestMarshalUnMarshalValidationProtocol(t *testing.T) {
bitfieldDistribution.SetValue(Bitfield{
Hash: hashA,
UncheckedSignedAvailabilityBitfield: parachaintypes.UncheckedSignedAvailabilityBitfield{
Payload: scale.NewBitVec([]bool{true, true, true, true, true, true, true, true, true, true, true,
Payload: parachaintypes.NewBitVec([]bool{true, true, true, true, true, true, true, true, true, true, true,
true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true,
true, true, true, true}),
ValidatorIndex: 0,
Expand Down
33 changes: 0 additions & 33 deletions pkg/scale/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,39 +77,6 @@ SCALE uses a compact encoding for variable width unsigned integers.
| `Compact<u64>` | `uint` |
| `Compact<u128>` | `*big.Int` |

### BitVec

SCALE uses a bit vector to encode a sequence of booleans. The bit vector is encoded as a compact length followed by a byte array.
The byte array is a sequence of bytes where each bit represents a boolean value.

**Note: This is a work in progress.**
The current implementation of BitVec is just bare bones. It does not implement any of the methods of the `BitVec` type in Rust.

```go
import (
"fmt"
"github.com/ChainSafe/gossamer/pkg/scale"
)

func ExampleBitVec() {
bitvec := NewBitVec([]bool{true, false, true, false, true, false, true, false})
bytes, err := scale.Marshal(bitvec)
if err != nil {
panic(err)
}

var unmarshaled BitVec
err = scale.Unmarshal(bytes, &unmarshaled)
if err != nil {
panic(err)
}

// [true false true false true false true false]
fmt.Printf("%v", unmarshaled.Bits())
}
```


## Usage

### Basic Example
Expand Down
Loading

0 comments on commit 8f0fd69

Please sign in to comment.