Skip to content

Commit

Permalink
Add ECChain CBOR encoding compatibility tests
Browse files Browse the repository at this point in the history
  • Loading branch information
masih committed Jan 22, 2025
1 parent b569f7f commit 3e5b007
Show file tree
Hide file tree
Showing 5 changed files with 133 additions and 108 deletions.
1 change: 0 additions & 1 deletion gen/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ func main() {
eg.Go(func() error {
return gen.WriteTupleEncodersToFile("../gpbft/cbor_gen.go", "gpbft",
gpbft.TipSet{},
gpbft.ECChain{},
gpbft.GMessage{},
gpbft.SupplementalData{},
gpbft.Payload{},
Expand Down
105 changes: 0 additions & 105 deletions gpbft/cbor_gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

51 changes: 49 additions & 2 deletions gpbft/chain.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,15 @@ import (
"encoding/json"
"errors"
"fmt"
"io"
"strings"
"sync"

"github.com/filecoin-project/go-f3/merkle"
"github.com/ipfs/go-cid"
"github.com/multiformats/go-multihash"
cbg "github.com/whyrusleeping/cbor-gen"
"golang.org/x/xerrors"
)

// TipSetKey is the canonically ordered concatenation of the block CIDs in a tipset.
Expand Down Expand Up @@ -178,8 +180,10 @@ func tipSetKeyFromCids(cids []cid.Cid) (TipSetKey, error) {
}

var (
_ json.Marshaler = (*ECChain)(nil)
_ json.Unmarshaler = (*ECChain)(nil)
_ json.Marshaler = (*ECChain)(nil)
_ json.Unmarshaler = (*ECChain)(nil)
_ cbg.CBORMarshaler = (*ECChain)(nil)
_ cbg.CBORMarshaler = (*ECChain)(nil)
)

// A chain of tipsets comprising a base (the last finalised tipset from which the chain extends).
Expand All @@ -203,6 +207,49 @@ func (c *ECChain) MarshalJSON() ([]byte, error) {
return json.Marshal(c.TipSets)
}

func (c *ECChain) UnmarshalCBOR(r io.Reader) (err error) {
// Unmarshall as []*TipSet for backward compatibility.
*c = ECChain{}
switch major, length, err := cbg.CborReadHeader(r); {
case err != nil:
return err
case length > ChainMaxLen:
return fmt.Errorf("chain too long: %d > %d", length, ChainMaxLen)
case major != cbg.MajArray:
return fmt.Errorf("expected cbor array")
case length > 0:
c.TipSets = make([]*TipSet, length)
for i := range length {
var ts TipSet
if err := ts.UnmarshalCBOR(r); err != nil {
return xerrors.Errorf("unmarshaling t.Value[i]: %w", err)
}
c.TipSets[i] = &ts
}
}
return nil
}

func (c *ECChain) MarshalCBOR(w io.Writer) error {
// Marshall as []*TipSet for backward compatibility.
length := c.Len()
if length > ChainMaxLen {
return fmt.Errorf("chain too long: %d > %d", length, ChainMaxLen)
}

if err := cbg.WriteMajorTypeHeader(w, cbg.MajArray, uint64(length)); err != nil {
return err
}
if c != nil {
for _, v := range c.TipSets {
if err := v.MarshalCBOR(w); err != nil {
return err
}
}
}
return nil
}

// A map key for a chain. The zero value means "bottom".
type ECChainKey merkle.Digest

Expand Down
60 changes: 60 additions & 0 deletions gpbft/chain_compatibility_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package gpbft_test

import (
"errors"
"fmt"
"io"

"github.com/filecoin-project/go-f3/gpbft"
cbg "github.com/whyrusleeping/cbor-gen"
)

// Most of the file here is manually generated using cbor-gen to test
// compatibility with the old format of ECChain.
type OldECChain []gpbft.TipSet

func (t *OldECChain) MarshalCBOR(w io.Writer) error {
cw := cbg.NewCborWriter(w)
if len((*t)) > 8192 {
return errors.New("Slice value in field (*t) was too long")
}

if err := cw.WriteMajorTypeHeader(cbg.MajArray, uint64(len((*t)))); err != nil {
return err
}
for _, v := range *t {
if err := v.MarshalCBOR(cw); err != nil {
return err
}

}
return nil
}

func (t *OldECChain) UnmarshalCBOR(r io.Reader) (err error) {
*t = OldECChain{}
cr := cbg.NewCborReader(r)
maj, extra, err := cr.ReadHeader()
if err != nil {
return err
}

if extra > 8192 {
return fmt.Errorf("(*t): array too large (%d)", extra)
}

if maj != cbg.MajArray {
return fmt.Errorf("expected cbor array")
}

if extra > 0 {
*t = make([]gpbft.TipSet, extra)
}

for i := 0; i < int(extra); i++ {
if err := (*t)[i].UnmarshalCBOR(cr); err != nil {
return fmt.Errorf("unmarshaling (*t)[i]: %w", err)
}
}
return nil
}
24 changes: 24 additions & 0 deletions gpbft/chain_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,30 @@ func TestECChain(t *testing.T) {
require.NoError(t, json.Unmarshal(data, &azStruct))
require.True(t, subject.Eq(&azStruct))
})
t.Run("marshals as array in CBOR", func(t *testing.T) {
subject := &gpbft.ECChain{
TipSets: []*gpbft.TipSet{
{Epoch: 0, Key: gpbft.MakeCid([]byte("fish")).Bytes(), PowerTable: gpbft.MakeCid([]byte("lbster"))},
},
}
var buf bytes.Buffer
require.NoError(t, subject.MarshalCBOR(&buf))

data, err := json.Marshal(subject)
require.NoError(t, err)

var asOldFormat OldECChain
require.NoError(t, asOldFormat.UnmarshalCBOR(&buf))
require.Equal(t, subject.Len(), len(asOldFormat))
for i, want := range subject.TipSets {
got := &asOldFormat[i]
require.True(t, want.Equal(got))
}

var asNewFormat gpbft.ECChain
require.NoError(t, json.Unmarshal(data, &asNewFormat))
require.True(t, subject.Eq(&asNewFormat))
})
}

func TestECChain_Eq(t *testing.T) {
Expand Down

0 comments on commit 3e5b007

Please sign in to comment.