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

Custom JSON serialization of TipSetKey for array-of-CIDs #756

Merged
merged 3 commits into from
Nov 28, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
60 changes: 60 additions & 0 deletions gpbft/chain.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"bytes"
"encoding/base32"
"encoding/binary"
"encoding/json"
"errors"
"fmt"
"strings"
Expand Down Expand Up @@ -56,6 +57,29 @@
Commitments [32]byte
}

func cidsFromTipSetKey(encoded []byte) ([]cid.Cid, error) {
var cids []cid.Cid
for nextIdx := 0; nextIdx < len(encoded); {
nr, c, err := cid.CidFromBytes(encoded[nextIdx:])
if err != nil {
return nil, err
}

Check warning on line 66 in gpbft/chain.go

View check run for this annotation

Codecov / codecov/patch

gpbft/chain.go#L65-L66

Added lines #L65 - L66 were not covered by tests
cids = append(cids, c)
nextIdx += nr
}
return cids, nil
}

func tipSetKeyFromCids(cids []cid.Cid) (TipSetKey, error) {
var buf bytes.Buffer
for _, c := range cids {
if _, err := buf.Write(c.Bytes()); err != nil {
return nil, err
}

Check warning on line 78 in gpbft/chain.go

View check run for this annotation

Codecov / codecov/patch

gpbft/chain.go#L77-L78

Added lines #L77 - L78 were not covered by tests
}
return buf.Bytes(), nil
}

// Validates a tipset.
// Note the zero value is invalid.
func (ts *TipSet) Validate() error {
Expand Down Expand Up @@ -111,6 +135,42 @@
return fmt.Sprintf("%s@%d", encTs[:min(16, len(encTs))], ts.Epoch)
}

// Custom JSON marshalling for TipSet to achieve a standard TipSetKey
// representation that presents an array of dag-json CIDs.

func (ts TipSet) MarshalJSON() ([]byte, error) {
type TipSetSub TipSet
cids, err := cidsFromTipSetKey(ts.Key)
if err != nil {
return nil, err
}

Check warning on line 146 in gpbft/chain.go

View check run for this annotation

Codecov / codecov/patch

gpbft/chain.go#L145-L146

Added lines #L145 - L146 were not covered by tests
return json.Marshal(&struct {
rvagg marked this conversation as resolved.
Show resolved Hide resolved
Key []cid.Cid
TipSetSub
}{
Key: cids,
TipSetSub: (TipSetSub)(ts),
})
}

func (ts *TipSet) UnmarshalJSON(data []byte) error {
type TipSetSub TipSet
aux := &struct {
Key []cid.Cid
*TipSetSub
}{
TipSetSub: (*TipSetSub)(ts),
}
var err error
if err = json.Unmarshal(data, &aux); err != nil {
return err
}

Check warning on line 167 in gpbft/chain.go

View check run for this annotation

Codecov / codecov/patch

gpbft/chain.go#L166-L167

Added lines #L166 - L167 were not covered by tests
if ts.Key, err = tipSetKeyFromCids(aux.Key); err != nil {
return err
}

Check warning on line 170 in gpbft/chain.go

View check run for this annotation

Codecov / codecov/patch

gpbft/chain.go#L169-L170

Added lines #L169 - L170 were not covered by tests
return nil
}

// A chain of tipsets comprising a base (the last finalised tipset from which the chain extends).
// and (possibly empty) suffix.
// Tipsets are assumed to be built contiguously on each other,
Expand Down
58 changes: 58 additions & 0 deletions gpbft/chain_test.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
package gpbft_test

import (
"bytes"
"encoding/json"
"testing"

"github.com/filecoin-project/go-f3/gpbft"
"github.com/ipfs/go-cid"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
Expand Down Expand Up @@ -184,3 +187,58 @@ func TestECChain_Eq(t *testing.T) {
})
}
}

func TestTipSetKeySerialization(t *testing.T) {
t.Parallel()
var (
c1 = gpbft.MakeCid([]byte("barreleye1"))
c2 = gpbft.MakeCid([]byte("barreleye2"))
c3 = gpbft.MakeCid([]byte("barreleye3"))
ts1 = gpbft.TipSet{
Epoch: 1,
Key: append(append(c1.Bytes(), c2.Bytes()...), c3.Bytes()...),
PowerTable: gpbft.MakeCid([]byte("fish")),
Commitments: [32]byte{0x01},
}
ts2 = gpbft.TipSet{
Epoch: 101,
Key: c1.Bytes(),
PowerTable: gpbft.MakeCid([]byte("lobster")),
Commitments: [32]byte{0x02},
}
)

t.Run("cbor round trip", func(t *testing.T) {
req := require.New(t)
for _, ts := range []gpbft.TipSet{ts1, ts2} {
var buf bytes.Buffer
req.NoError(ts.MarshalCBOR(&buf))
t.Logf("cbor: %x", buf.Bytes())
var rt gpbft.TipSet
req.NoError(rt.UnmarshalCBOR(&buf))
req.Equal(ts, rt)
}
})

t.Run("json round trip", func(t *testing.T) {
req := require.New(t)
for _, ts := range []gpbft.TipSet{ts1, ts2} {
data, err := ts.MarshalJSON()
req.NoError(err)
t.Logf("json: %s", data)
var rt gpbft.TipSet
req.NoError(rt.UnmarshalJSON(data))
req.Equal(ts, rt)

// check that we serialized the CIDs in the standard dag-json form
var bareMap map[string]interface{}
rvagg marked this conversation as resolved.
Show resolved Hide resolved
req.NoError(json.Unmarshal(data, &bareMap))
keyField, ok := bareMap["Key"].([]interface{})
req.True(ok)
req.Len(keyField, len(ts.Key)/38)
for j, c := range []cid.Cid{c1, c2, c3}[:len(ts.Key)/38] {
req.Equal(map[string]interface{}{"/": c.String()}, keyField[j])
}
}
})
}
Loading