Skip to content

Commit

Permalink
feat(lib/parachain): Implement request and response message for /req_…
Browse files Browse the repository at this point in the history
…chunk/1 protocol (#3362)

- Added `ChunkFetchingRequest` and `ChunkFetchingResponse` types.

- implemented network.Message interface in `ChunkFetchingRequest` and 'network.ResponseMessage' interface in `ChunkFetchingResponse`
  • Loading branch information
axaysagathiya authored and timwu20 committed Jun 17, 2024
1 parent ba9f1c3 commit feaa780
Show file tree
Hide file tree
Showing 2 changed files with 200 additions and 0 deletions.
118 changes: 118 additions & 0 deletions lib/parachain/chunk_fetching.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
package parachain

import (
"fmt"

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

// ChunkFetchingRequest represents a request to retrieve chunks of a parachain candidate
type ChunkFetchingRequest struct {
// Hash of candidate we want a chunk for.
CandidateHash CandidateHash `scale:"1"`

// The index of the chunk to fetch.
Index ValidatorIndex `scale:"2"`
}

// Encode returns the SCALE encoding of the ChunkFetchingRequest
func (c ChunkFetchingRequest) Encode() ([]byte, error) {
return scale.Marshal(c)
}

type ChunkFetchingResponseValues interface {
ChunkResponse | NoSuchChunk
}

type ChunkFetchingResponse struct {
inner any
}

func setChunkFetchingResponse[Value ChunkFetchingResponseValues](mvdt *ChunkFetchingResponse, value Value) {
mvdt.inner = value
}

func (mvdt *ChunkFetchingResponse) SetValue(value any) (err error) {
switch value := value.(type) {
case ChunkResponse:
setChunkFetchingResponse(mvdt, value)
return

case NoSuchChunk:
setChunkFetchingResponse(mvdt, value)
return

default:
return fmt.Errorf("unsupported type")
}
}

func (mvdt ChunkFetchingResponse) IndexValue() (index uint, value any, err error) {
switch mvdt.inner.(type) {
case ChunkResponse:
return 0, mvdt.inner, nil

case NoSuchChunk:
return 1, mvdt.inner, nil

}
return 0, nil, scale.ErrUnsupportedVaryingDataTypeValue
}

func (mvdt ChunkFetchingResponse) Value() (value any, err error) {
_, value, err = mvdt.IndexValue()
return
}

func (mvdt ChunkFetchingResponse) ValueAt(index uint) (value any, err error) {
switch index {
case 0:
return *new(ChunkResponse), nil

case 1:
return *new(NoSuchChunk), nil

}
return nil, scale.ErrUnknownVaryingDataTypeValue
}

// NewChunkFetchingResponse returns a new chunk fetching response varying data type
func NewChunkFetchingResponse() ChunkFetchingResponse {
return ChunkFetchingResponse{}
}

// ChunkResponse represents the requested chunk data
type ChunkResponse struct {
// The erasure-encoded chunk of data belonging to the candidate block
Chunk []byte `scale:"1"`

// Proof for this chunk's branch in the Merkle tree
Proof [][]byte `scale:"2"`
}

// NoSuchChunk indicates that the requested chunk was not found
type NoSuchChunk struct{}

// Encode returns the SCALE encoding of the ChunkFetchingResponse
func (c *ChunkFetchingResponse) Encode() ([]byte, error) {
return scale.Marshal(*c)
}

// Decode returns the SCALE decoding of the ChunkFetchingResponse.
func (c *ChunkFetchingResponse) Decode(in []byte) (err error) {
return scale.Unmarshal(in, c)
}

// String formats a ChunkFetchingResponse as a string
func (c *ChunkFetchingResponse) String() string {
if c == nil {
return "ChunkFetchingResponse=nil"
}

v, _ := c.Value()
chunkRes, ok := v.(ChunkResponse)
if !ok {
return "ChunkFetchingResponse=NoSuchChunk"
}
return fmt.Sprintf("ChunkFetchingResponse ChunkResponse=%+v", chunkRes)
}
82 changes: 82 additions & 0 deletions lib/parachain/chunk_fetching_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package parachain

import (
"testing"

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

func TestEncodeChunkFetchingRequest(t *testing.T) {
chunkFetchingRequest := ChunkFetchingRequest{
CandidateHash: CandidateHash{
common.MustHexToHash("0x677811d2f3ded2489685468dbdb2e4fa280a249fba9356acceb2e823820e2c19"),
},
Index: ValidatorIndex(8),
}

actualEncode, err := chunkFetchingRequest.Encode()
require.NoError(t, err)

expextedEncode := common.MustHexToBytes("0x677811d2f3ded2489685468dbdb2e4fa280a249fba9356acceb2e823820e2c1908000000")
require.Equal(t, expextedEncode, actualEncode)
}

func TestChunkFetchingResponse(t *testing.T) {
t.Parallel()

testBytes := common.MustHexToBytes("0x677811d2f3ded2489685468dbdb2e4fa280a249fba9356acceb2e823820e2c19")
testCases := []struct {
name string
value any
encodeValue []byte
}{
{
name: "chunkResponse",
value: ChunkResponse{
Chunk: testBytes,
Proof: [][]byte{testBytes},
},
encodeValue: common.MustHexToBytes("0x0080677811d2f3ded2489685468dbdb2e4fa280a249fba9356acceb2e823820e2c190480677811d2f3ded2489685468dbdb2e4fa280a249fba9356acceb2e823820e2c19"), //nolint:lll
},
{
name: "NoSuchChunk",
value: NoSuchChunk{},
encodeValue: []byte{1},
},
}

for _, c := range testCases {
c := c
t.Run(c.name, func(t *testing.T) {
t.Parallel()

t.Run("encode", func(t *testing.T) {
t.Parallel()

chunkFetchingResponse := NewChunkFetchingResponse()
err := chunkFetchingResponse.SetValue(c.value)
require.NoError(t, err)

actualEncode, err := chunkFetchingResponse.Encode()
require.NoError(t, err)

require.Equal(t, c.encodeValue, actualEncode)
})

t.Run("decode", func(t *testing.T) {
t.Parallel()

chunkFetchingResponse := NewChunkFetchingResponse()
err := chunkFetchingResponse.Decode(c.encodeValue)
require.NoError(t, err)

actualData, err := chunkFetchingResponse.Value()
require.NoError(t, err)

require.EqualValues(t, c.value, actualData)
})

})
}
}

0 comments on commit feaa780

Please sign in to comment.