diff --git a/cmd/f3sim/f3sim.go b/cmd/f3sim/f3sim.go index e347b4f1..026039c9 100644 --- a/cmd/f3sim/f3sim.go +++ b/cmd/f3sim/f3sim.go @@ -40,7 +40,7 @@ func main() { sm := sim.NewSimulation(simConfig, graniteConfig, *traceLevel) // Same chain for everyone. - candidate := sm.Base(0).Extend(sm.CIDGen.Sample()) + candidate := sm.Base(0).Extend(sm.TipGen.Sample()) sm.SetChains(sim.ChainCount{Count: *participantCount, Chain: candidate}) err := sm.Run(1, *maxRounds) diff --git a/gen/main.go b/gen/main.go index 769effcd..49045860 100644 --- a/gen/main.go +++ b/gen/main.go @@ -15,7 +15,6 @@ func main() { gpbft.GMessage{}, gpbft.Payload{}, gpbft.Justification{}, - gpbft.TipSet{}, ) if err != nil { fmt.Println(err) diff --git a/gpbft/chain.go b/gpbft/chain.go index 6862561d..5295b2e8 100644 --- a/gpbft/chain.go +++ b/gpbft/chain.go @@ -2,73 +2,25 @@ package gpbft import ( "bytes" - "encoding/binary" + "encoding/hex" "errors" - "io" - "strconv" "strings" ) -// Information about a tipset that is relevant to the F3 protocol. -// This is a lightweight value type comprising 3 machine words. -// Fields are exported for CBOR generation, but are opqaue and should not be accessed -// within the protocol implementation. -type TipSet struct { - // The epoch of the blocks in the tipset. - Epoch int64 - // The identifier of the tipset. - ID TipSetID -} - -// Creates a new tipset. -func NewTipSet(epoch int64, cid TipSetID) TipSet { - return TipSet{ - Epoch: epoch, - ID: cid, - } -} - -// Returns a zero value tipset. -// The zero value is not a meaningful tipset and may be used to represent bottom. -func ZeroTipSet() TipSet { - return TipSet{} -} - -func (t TipSet) IsZero() bool { - return t.Epoch == 0 && t.ID.IsZero() -} - -func (t TipSet) Eq(other TipSet) bool { - return t.Epoch == other.Epoch && t.ID.Eq(other.ID) -} - -// An identifier for this tipset suitable for use as a map key. -// This must completely and verifiably determine the tipset data; it is not sufficient to use -// the block CIDs and rely on external verification of the attached metadata. -func (t TipSet) Key() string { - buf := bytes.Buffer{} - buf.Write(t.ID.Bytes()) - _ = binary.Write(&buf, binary.BigEndian, t.Epoch) - return buf.String() -} - -func (t TipSet) String() string { - var b strings.Builder - b.Write(t.ID.Bytes()) - b.WriteString("@") - b.WriteString(strconv.FormatInt(t.Epoch, 10)) - return b.String() -} - -func (t TipSet) MarshalForSigning(w io.Writer) { - _ = binary.Write(w, binary.BigEndian, t.Epoch) - _, _ = w.Write(t.ID.Bytes()) -} +// Opaque type representing a tipset. +// This is expected to be: +// - a canonical sequence of CIDs of block headers identifying a tipset, +// - a commitment to the resulting power table, +// - a commitment to additional derived values. +// However, GossipPBFT doesn't need to know anything about that structure. +type TipSet = []byte // 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, though epochs may be missing due to null rounds. -// The zero value is not a valid chain, and represents a "bottom" value when used in a Granite message. +// Tipsets are assumed to be built contiguously on each other, +// though epochs may be missing due to null rounds. +// The zero value is not a valid chain, and represents a "bottom" value +// when used in a Granite message. type ECChain []TipSet // A map key for a chain. The zero value means "bottom". @@ -115,13 +67,8 @@ func (c ECChain) BaseChain() ECChain { return ECChain{c[0]} } -// Returns a new chain extending this chain with one tipset. -// The new tipset is given an epoch and weight one greater than the previous head. -func (c ECChain) Extend(cid TipSetID) ECChain { - return append(c, TipSet{ - Epoch: c.Head().Epoch + 1, - ID: cid, - }) +func (c ECChain) Extend(tip TipSet) ECChain { + return append(c, tip) } // Returns a chain with suffix (after the base) truncated to a maximum length. @@ -137,7 +84,7 @@ func (c ECChain) Eq(other ECChain) bool { return false } for i := range c { - if !c[i].Eq(other[i]) { + if !bytes.Equal(c[i], other[i]) { return false } } @@ -150,16 +97,16 @@ func (c ECChain) SameBase(other ECChain) bool { if c.IsZero() || other.IsZero() { return false } - return c.Base().Eq(other.Base()) + return bytes.Equal(c.Base(), other.Base()) } // Check whether a chain has a specific base tipset. // Always false for a zero value. func (c ECChain) HasBase(t TipSet) bool { - if c.IsZero() || t.IsZero() { + if c.IsZero() || len(t) == 0 { return false } - return c[0].Eq(t) + return bytes.Equal(c[0], t) } // Checks whether a chain has some prefix (including the base). @@ -172,7 +119,7 @@ func (c ECChain) HasPrefix(other ECChain) bool { return false } for i := range other { - if !c[i].Eq(other[i]) { + if !bytes.Equal(c[i], other[i]) { return false } } @@ -181,12 +128,12 @@ func (c ECChain) HasPrefix(other ECChain) bool { // Checks whether a chain has some tipset (including as its base). func (c ECChain) HasTipset(t TipSet) bool { - if t.IsZero() { + if len(t) == 0 { // Chain can never contain zero-valued TipSet. return false } for _, t2 := range c { - if t2.Eq(t) { + if bytes.Equal(t, t2) { return true } } @@ -202,15 +149,9 @@ func (c ECChain) Validate() error { if c.IsZero() { return nil } - var epochSoFar int64 for _, tipSet := range c { - switch { - case tipSet.IsZero(): + if len(tipSet) == 0 { return errors.New("chain cannot contain zero-valued tip sets") - case tipSet.Epoch <= epochSoFar: - return errors.New("chain epoch must be in order and unique") - default: - epochSoFar = tipSet.Epoch } } return nil @@ -221,7 +162,7 @@ func (c ECChain) Validate() error { func (c ECChain) Key() ChainKey { buf := bytes.Buffer{} for _, t := range c { - buf.Write([]byte(t.Key())) + buf.Write(t) } return ChainKey(buf.String()) } @@ -230,7 +171,7 @@ func (c ECChain) String() string { var b strings.Builder b.WriteString("[") for i, t := range c { - b.WriteString(t.String()) + b.WriteString(hex.EncodeToString(t)) if i < len(c)-1 { b.WriteString(", ") } diff --git a/gpbft/chain_test.go b/gpbft/chain_test.go index 9e7a5597..42630b63 100644 --- a/gpbft/chain_test.go +++ b/gpbft/chain_test.go @@ -1,8 +1,6 @@ package gpbft_test import ( - "bytes" - "encoding/binary" "testing" "github.com/filecoin-project/go-f3/gpbft" @@ -20,56 +18,39 @@ func TestTipSet(t *testing.T) { { name: "zero-value struct is zero", wantZero: true, - wantString: "@0", + wantString: "", }, { name: "ZeroTipSet is zero", - subject: gpbft.ZeroTipSet(), + subject: []byte{}, wantZero: true, - wantString: "@0", + wantString: "", }, { name: "NewTipSet with zero values is zero", - subject: gpbft.NewTipSet(0, gpbft.NewTipSetID(nil)), + subject: nil, wantZero: true, - wantString: "@0", + wantString: "", }, { name: "Non-zero is not zero", - subject: gpbft.NewTipSet(1413, gpbft.NewTipSetID([]byte("fish"))), - wantString: "fish@1413", - }, - { - name: "negative epoch is accepted", - subject: gpbft.NewTipSet(-1413, gpbft.NewTipSetID([]byte("fish"))), - wantString: "fish@-1413", + subject: gpbft.TipSet("fish"), + wantString: "fish", }, } for _, test := range tests { test := test t.Run(test.name, func(t *testing.T) { - require.Equal(t, test.wantZero, test.subject.IsZero()) - require.Equal(t, test.wantString, test.subject.String()) - requireTipSetMarshaledForSigning(t, test.subject) + require.Equal(t, test.wantZero, len(test.subject) == 0) + require.Equal(t, test.wantString, string(test.subject)) }) } } -func requireTipSetMarshaledForSigning(t *testing.T, subject gpbft.TipSet) { - t.Helper() - var gotSigningMarshal bytes.Buffer - subject.MarshalForSigning(&gotSigningMarshal) - wantPrefix := binary.BigEndian.AppendUint64(nil, uint64(subject.Epoch)) - wantSuffix := subject.ID.Bytes() - require.Equal(t, len(wantPrefix)+len(wantSuffix), gotSigningMarshal.Len()) - require.True(t, bytes.HasPrefix(gotSigningMarshal.Bytes(), wantPrefix)) - require.True(t, bytes.HasSuffix(gotSigningMarshal.Bytes(), wantSuffix)) -} - func TestECChain(t *testing.T) { t.Parallel() - zeroTipSet := gpbft.ZeroTipSet() + zeroTipSet := []byte{} t.Run("zero-value is zero", func(t *testing.T) { var subject gpbft.ECChain require.True(t, subject.IsZero()) @@ -83,73 +64,39 @@ func TestECChain(t *testing.T) { require.Panics(t, func() { subject.Prefix(0) }) require.Panics(t, func() { subject.Base() }) require.Panics(t, func() { subject.Head() }) - require.Equal(t, zeroTipSet, subject.HeadOrZero()) require.NoError(t, subject.Validate()) - requireMonotonicallyIncreasingEpochs(t, subject) }) t.Run("NewChain with zero-value base is error", func(t *testing.T) { subject, err := gpbft.NewChain(zeroTipSet) require.Error(t, err) require.Nil(t, subject) }) - t.Run("NewChain with repeated epochs is error", func(t *testing.T) { - tipSet := gpbft.NewTipSet(1413, gpbft.ZeroTipSetID()) - subject, err := gpbft.NewChain(tipSet, tipSet) - require.Error(t, err) - require.Nil(t, subject) - }) - t.Run("NewChain with epoch gaps is not error", func(t *testing.T) { - subject, err := gpbft.NewChain(gpbft.NewTipSet(1413, gpbft.ZeroTipSetID()), gpbft.NewTipSet(1414, gpbft.ZeroTipSetID())) - require.NoError(t, err) - require.NoError(t, subject.Validate()) - requireMonotonicallyIncreasingEpochs(t, subject) - }) - t.Run("NewChain with unordered tipSets is error", func(t *testing.T) { - subject, err := gpbft.NewChain(gpbft.NewTipSet(2, gpbft.ZeroTipSetID()), zeroTipSet) - require.Error(t, err) - require.Nil(t, subject) - }) - t.Run("NewChain with ordered duplicate epoch is error", func(t *testing.T) { - subject, err := gpbft.NewChain(zeroTipSet, - gpbft.NewTipSet(2, gpbft.ZeroTipSetID()), - gpbft.NewTipSet(2, gpbft.NewTipSetID([]byte("fish"))), - gpbft.NewTipSet(2, gpbft.NewTipSetID([]byte("lobster")))) - require.Error(t, err) - require.Nil(t, subject) - require.NoError(t, subject.Validate()) - requireMonotonicallyIncreasingEpochs(t, subject) - }) t.Run("extended chain is as expected", func(t *testing.T) { - wantBase := gpbft.NewTipSet(1413, gpbft.NewTipSetID([]byte("fish"))) + wantBase := []byte("fish") subject, err := gpbft.NewChain(wantBase) require.NoError(t, err) require.Len(t, subject, 1) require.Equal(t, wantBase, subject.Base()) require.Equal(t, wantBase, subject.Head()) - require.Equal(t, wantBase, subject.HeadOrZero()) require.NoError(t, subject.Validate()) - requireMonotonicallyIncreasingEpochs(t, subject) - wantNextID := gpbft.NewTipSetID([]byte("lobster")) - wantNextTipSet := gpbft.NewTipSet(wantBase.Epoch+1, wantNextID) - subjectExtended := subject.Extend(wantNextID) + wantNext := []byte("lobster") + subjectExtended := subject.Extend(wantNext) require.Len(t, subjectExtended, 2) require.NoError(t, subjectExtended.Validate()) - requireMonotonicallyIncreasingEpochs(t, subjectExtended) require.Equal(t, wantBase, subjectExtended.Base()) - require.Equal(t, []gpbft.TipSet{wantNextTipSet}, subjectExtended.Suffix()) - require.Equal(t, wantNextTipSet, subjectExtended.Head()) - require.Equal(t, wantNextTipSet, subjectExtended.HeadOrZero()) - require.Equal(t, wantNextTipSet, subjectExtended.Prefix(1).Head()) - require.True(t, subjectExtended.HasTipset(gpbft.NewTipSet(wantBase.Epoch+1, wantNextID))) + require.Equal(t, []gpbft.TipSet{wantNext}, subjectExtended.Suffix()) + require.Equal(t, wantNext, subjectExtended.Head()) + require.Equal(t, wantNext, subjectExtended.Prefix(1).Head()) + require.True(t, subjectExtended.HasTipset(wantBase)) require.False(t, subject.HasPrefix(subjectExtended)) require.True(t, subjectExtended.HasPrefix(subject)) - require.False(t, subject.Extend(wantBase.ID).HasPrefix(subjectExtended.Extend(wantNextID))) + require.False(t, subject.Extend(wantBase).HasPrefix(subjectExtended.Extend(wantNext))) }) t.Run("SameBase is false when either chain is zero", func(t *testing.T) { var zeroChain gpbft.ECChain - nonZeroChain, err := gpbft.NewChain(gpbft.NewTipSet(2, gpbft.ZeroTipSetID())) + nonZeroChain, err := gpbft.NewChain([]byte{1}) require.NoError(t, err) require.False(t, nonZeroChain.SameBase(zeroChain)) require.False(t, zeroChain.SameBase(nonZeroChain)) @@ -157,7 +104,7 @@ func TestECChain(t *testing.T) { }) t.Run("HasPrefix is false when either chain is zero", func(t *testing.T) { var zeroChain gpbft.ECChain - nonZeroChain, err := gpbft.NewChain(gpbft.NewTipSet(2, gpbft.ZeroTipSetID())) + nonZeroChain, err := gpbft.NewChain([]byte{1}) require.NoError(t, err) require.False(t, nonZeroChain.HasPrefix(zeroChain)) require.False(t, zeroChain.HasPrefix(nonZeroChain)) @@ -168,20 +115,7 @@ func TestECChain(t *testing.T) { require.NoError(t, zeroChain.Validate()) }) t.Run("ordered chain with zero-valued base is invalid", func(t *testing.T) { - subject := gpbft.ECChain{zeroTipSet, gpbft.NewTipSet(1, gpbft.ZeroTipSetID())} + subject := gpbft.ECChain{zeroTipSet, []byte{1}} require.Error(t, subject.Validate()) }) - t.Run("unordered chain is invalid", func(t *testing.T) { - subject := gpbft.ECChain{gpbft.NewTipSet(2, gpbft.ZeroTipSetID()), gpbft.NewTipSet(1, gpbft.ZeroTipSetID())} - require.Error(t, subject.Validate()) - }) -} - -func requireMonotonicallyIncreasingEpochs(t *testing.T, subject gpbft.ECChain) { - t.Helper() - var latestEpoch int64 - for index, tipSet := range subject { - require.Less(t, latestEpoch, tipSet.Epoch, "not monotonically increasing at index %d", index) - latestEpoch = tipSet.Epoch - } } diff --git a/gpbft/gen.go b/gpbft/gen.go index 2b7794f1..0d52e192 100644 --- a/gpbft/gen.go +++ b/gpbft/gen.go @@ -228,7 +228,15 @@ func (t *Payload) MarshalCBOR(w io.Writer) error { return err } for _, v := range t.Value { - if err := v.MarshalCBOR(cw); err != nil { + if len(v) > 2097152 { + return xerrors.Errorf("Byte array in field v was too long") + } + + if err := cw.WriteMajorTypeHeader(cbg.MajByteString, uint64(len(v))); err != nil { + return err + } + + if _, err := cw.Write(v); err != nil { return err } @@ -316,7 +324,7 @@ func (t *Payload) UnmarshalCBOR(r io.Reader) (err error) { } if extra > 0 { - t.Value = make([]TipSet, extra) + t.Value = make([][]uint8, extra) } for i := 0; i < int(extra); i++ { @@ -328,12 +336,24 @@ func (t *Payload) UnmarshalCBOR(r io.Reader) (err error) { _ = extra _ = err - { + maj, extra, err = cr.ReadHeader() + if err != nil { + return err + } + + if extra > 2097152 { + return fmt.Errorf("t.Value[i]: byte array too large (%d)", extra) + } + if maj != cbg.MajByteString { + return fmt.Errorf("expected byte array") + } - if err := t.Value[i].UnmarshalCBOR(cr); err != nil { - return xerrors.Errorf("unmarshaling t.Value[i]: %w", err) - } + if extra > 0 { + t.Value[i] = make([]uint8, extra) + } + if _, err := io.ReadFull(cr, t.Value[i]); err != nil { + return err } } @@ -446,154 +466,3 @@ func (t *Justification) UnmarshalCBOR(r io.Reader) (err error) { return nil } - -var lengthBufTipSet = []byte{130} - -func (t *TipSet) MarshalCBOR(w io.Writer) error { - if t == nil { - _, err := w.Write(cbg.CborNull) - return err - } - - cw := cbg.NewCborWriter(w) - - if _, err := cw.Write(lengthBufTipSet); err != nil { - return err - } - - // t.Epoch (int64) (int64) - if t.Epoch >= 0 { - if err := cw.WriteMajorTypeHeader(cbg.MajUnsignedInt, uint64(t.Epoch)); err != nil { - return err - } - } else { - if err := cw.WriteMajorTypeHeader(cbg.MajNegativeInt, uint64(-t.Epoch-1)); err != nil { - return err - } - } - - // t.CID (gpbft.TipSetID) (slice) - if len(t.ID) > 8192 { - return xerrors.Errorf("Slice value in field t.CID was too long") - } - - if err := cw.WriteMajorTypeHeader(cbg.MajArray, uint64(len(t.ID))); err != nil { - return err - } - for _, v := range t.ID { - if len(v) > 2097152 { - return xerrors.Errorf("Byte array in field v was too long") - } - - if err := cw.WriteMajorTypeHeader(cbg.MajByteString, uint64(len(v))); err != nil { - return err - } - - if _, err := cw.Write(v); err != nil { - return err - } - - } - return nil -} - -func (t *TipSet) UnmarshalCBOR(r io.Reader) (err error) { - *t = TipSet{} - - cr := cbg.NewCborReader(r) - - maj, extra, err := cr.ReadHeader() - if err != nil { - return err - } - defer func() { - if err == io.EOF { - err = io.ErrUnexpectedEOF - } - }() - - if maj != cbg.MajArray { - return fmt.Errorf("cbor input should be of type array") - } - - if extra != 2 { - return fmt.Errorf("cbor input had wrong number of fields") - } - - // t.Epoch (int64) (int64) - { - maj, extra, err := cr.ReadHeader() - if err != nil { - return err - } - var extraI int64 - switch maj { - case cbg.MajUnsignedInt: - extraI = int64(extra) - if extraI < 0 { - return fmt.Errorf("int64 positive overflow") - } - case cbg.MajNegativeInt: - extraI = int64(extra) - if extraI < 0 { - return fmt.Errorf("int64 negative overflow") - } - extraI = -1 - extraI - default: - return fmt.Errorf("wrong type for int64 field: %d", maj) - } - - t.Epoch = int64(extraI) - } - // t.CID (gpbft.TipSetID) (slice) - - maj, extra, err = cr.ReadHeader() - if err != nil { - return err - } - - if extra > 8192 { - return fmt.Errorf("t.CID: array too large (%d)", extra) - } - - if maj != cbg.MajArray { - return fmt.Errorf("expected cbor array") - } - - if extra > 0 { - t.ID = make([][]uint8, extra) - } - - for i := 0; i < int(extra); i++ { - { - var maj byte - var extra uint64 - var err error - _ = maj - _ = extra - _ = err - - maj, extra, err = cr.ReadHeader() - if err != nil { - return err - } - - if extra > 2097152 { - return fmt.Errorf("t.CID[i]: byte array too large (%d)", extra) - } - if maj != cbg.MajByteString { - return fmt.Errorf("expected byte array") - } - - if extra > 0 { - t.ID[i] = make([]uint8, extra) - } - - if _, err := io.ReadFull(cr, t.ID[i]); err != nil { - return err - } - - } - } - return nil -} diff --git a/gpbft/gpbft.go b/gpbft/gpbft.go index c64f472c..a3d8942c 100644 --- a/gpbft/gpbft.go +++ b/gpbft/gpbft.go @@ -114,7 +114,7 @@ func (p Payload) MarshalForSigning(nn NetworkName) []byte { _ = binary.Write(&buf, binary.BigEndian, p.Round) _ = binary.Write(&buf, binary.BigEndian, p.Step) for _, t := range p.Value { - t.MarshalForSigning(&buf) + buf.Write(t) } return buf.Bytes() } diff --git a/gpbft/types.go b/gpbft/types.go index 902e80ba..5f8b1fc8 100644 --- a/gpbft/types.go +++ b/gpbft/types.go @@ -1,7 +1,6 @@ package gpbft import ( - "bytes" "github.com/ipfs/go-datastore" "math/big" ) @@ -27,46 +26,3 @@ func (nn NetworkName) PubSubTopic() string { func NewStoragePower(value int64) *StoragePower { return new(big.Int).SetInt64(value) } - -// Opaque type identifying a tipset. -// This is expected to be a canonical sequence of CIDs of block headers identifying a tipset. -// GossipPBFT doesn't need to know anything about CID format or behaviour, so adapts to this simple type -// rather than import all the dependencies of the CID package. -// This type is intentionally unsuitable for use as a map key. -type TipSetID [][]byte - -// Creates a new TipSetID from a slice of block CIDs. -func NewTipSetID(blocks [][]byte) TipSetID { - return blocks -} - -// Creates a new singleton TipSetID from a string, treated as a CID. -func NewTipSetIDFromString(s string) TipSetID { - return TipSetID{[]byte(s)} -} - -// Checks whether a tipset ID is zero. -func (t TipSetID) IsZero() bool { - return len(t) == 0 -} - -func (t TipSetID) Eq(other TipSetID) bool { - if len(t) != len(other) { - return false - } - for i, b := range t { - if !bytes.Equal(b, other[i]) { - return false - } - } - return true -} - -// FIXME this is not unique without a delimiter -func (t TipSetID) Bytes() []byte { - buf := bytes.Buffer{} - for _, b := range t { - buf.Write(b) - } - return buf.Bytes() -} diff --git a/gpbft/types_test.go b/gpbft/types_test.go index ca9c8a70..8420d4d6 100644 --- a/gpbft/types_test.go +++ b/gpbft/types_test.go @@ -1,7 +1,6 @@ package gpbft_test import ( - "sort" "strings" "testing" @@ -9,60 +8,6 @@ import ( "github.com/stretchr/testify/require" ) -func TestNewTipSetID(t *testing.T) { - t.Parallel() - tests := []struct { - name string - subject gpbft.TipSetID - wantZero bool - }{ - { - name: "zero-value struct is zero", - wantZero: true, - }, - { - name: "ZeroTipSetID is zero", - subject: gpbft.ZeroTipSetID(), - wantZero: true, - }, - { - name: "NewTipSet with zero values is zero", - subject: gpbft.NewTipSetID(nil), - wantZero: true, - }, - { - name: "Non-zero is not zero", - subject: gpbft.NewTipSetID([]byte("fish")), - }, - } - for _, test := range tests { - test := test - t.Run(test.name, func(t *testing.T) { - require.Equal(t, test.wantZero, test.subject.IsZero()) - gotBytes := test.subject.Bytes() - if test.wantZero { - require.Empty(t, gotBytes) - } else { - require.NotEmpty(t, gotBytes) - } - require.Zero(t, test.subject.Compare(test.subject)) - }) - } - t.Run("compare", func(t *testing.T) { - wantFirst := gpbft.NewTipSetID([]byte("1 fish")) - wantSecond := gpbft.NewTipSetID([]byte("2 lobster")) - wantThird := gpbft.NewTipSetID([]byte("3 barreleye")) - subject := []gpbft.TipSetID{wantSecond, wantFirst, wantThird} - - sort.Slice(subject, func(one, other int) bool { - return subject[one].Compare(subject[other]) < 0 - }) - require.Equal(t, wantFirst, subject[0]) - require.Equal(t, wantSecond, subject[1]) - require.Equal(t, wantThird, subject[2]) - }) -} - func TestNetworkName(t *testing.T) { t.Parallel() tests := []struct { diff --git a/sim/ec.go b/sim/ec.go index b6cd584d..de153f81 100644 --- a/sim/ec.go +++ b/sim/ec.go @@ -7,9 +7,7 @@ import ( // Simulated EC state for each protocol instance. type EC struct { - // The first instance's base chain's first epoch. - BaseEpoch int64 - // Timestamp of the base epoch. + // Timestamp of the base chain. BaseTimestamp time.Time Instances []*ECInstance } @@ -27,7 +25,6 @@ type ECInstance struct { func NewEC(base gpbft.ECChain, beacon []byte) *EC { return &EC{ - BaseEpoch: base.Base().Epoch, BaseTimestamp: time.Time{}, Instances: []*ECInstance{ { diff --git a/sim/sim.go b/sim/sim.go index 5e6e9d55..723df214 100644 --- a/sim/sim.go +++ b/sim/sim.go @@ -60,7 +60,7 @@ type Simulation struct { Participants []*gpbft.Participant Adversary AdversaryReceiver Decisions *DecisionLog - CIDGen *TipIDGen + TipGen *TipGen } type AdversaryFactory func(id string, ntwk gpbft.Network) gpbft.Receiver @@ -79,10 +79,10 @@ func NewSimulation(simConfig Config, graniteConfig gpbft.GraniteConfig, traceLev } ntwk := NewNetwork(lat, traceLevel, *sb, "sim") - baseChain, _ := gpbft.NewChain(gpbft.NewTipSet(100, gpbft.NewTipSetIDFromString("genesis"))) + baseChain, _ := gpbft.NewChain([]byte(("genesis"))) beacon := []byte("beacon") ec := NewEC(baseChain, beacon) - cidGen := NewCIDGen(0x264803e715714f95) // Seed from Drand + tipGen := NewTipGen(0x264803e715714f95) // Seed from Drand decisions := NewDecisionLog(ntwk, sb.Verifier) // Create participants. @@ -94,7 +94,7 @@ func NewSimulation(simConfig Config, graniteConfig gpbft.GraniteConfig, traceLev Network: ntwk, EC: ec, DecisionLog: decisions, - TipIDGen: cidGen, + TipGen: tipGen, id: id, } participants[i] = gpbft.NewParticipant(id, graniteConfig, host, host) @@ -111,7 +111,7 @@ func NewSimulation(simConfig Config, graniteConfig gpbft.GraniteConfig, traceLev Participants: participants, Adversary: nil, Decisions: decisions, - CIDGen: cidGen, + TipGen: tipGen, } } @@ -136,7 +136,7 @@ func (s *Simulation) HostFor(id gpbft.ActorID) *SimHost { Network: s.Network, EC: s.EC, DecisionLog: s.Decisions, - TipIDGen: s.CIDGen, + TipGen: s.TipGen, id: id, } } @@ -235,7 +235,7 @@ type SimHost struct { *Network *EC *DecisionLog - *TipIDGen + *TipGen id gpbft.ActorID } @@ -277,13 +277,13 @@ func (v *SimHost) ReceiveDecision(decision *gpbft.Justification) time.Time { // Create a new chain for all participants. // There's no facility yet for them to observe different chains after the first instance. // See https://github.com/filecoin-project/go-f3/issues/115. - newTip := gpbft.NewTipSet(nextBase.Epoch+1, v.TipIDGen.Sample()) + newTip := v.TipGen.Sample() nextChain, _ := gpbft.NewChain(nextBase, newTip) v.EC.AddInstance(nextChain, nextPowerTable, nextBeacon) v.DecisionLog.BeginInstance(decision.Vote.Instance+1, nextBase, nextPowerTable) } - elapsedEpochs := decision.Vote.Value.Head().Epoch - v.EC.BaseEpoch + elapsedEpochs := 1 //decision.Vote.Value.Head().Epoch - v.EC.BaseEpoch finalTimestamp := v.EC.BaseTimestamp.Add(time.Duration(elapsedEpochs) * v.Config.ECEpochDuration) // Next instance starts some fixed time after the next EC epoch is due. nextInstanceStart := finalTimestamp.Add(v.Config.ECEpochDuration).Add(v.Config.ECStabilisationDelay) @@ -448,26 +448,26 @@ func (dl *DecisionLog) verifyDecision(decision *gpbft.Justification) error { return nil } -// A tipset ID generator. +// A tipset generator. // This uses a fast xorshift PRNG to generate random tipset IDs. // The statistical properties of these are not important to correctness. -type TipIDGen struct { +type TipGen struct { xorshiftState uint64 } -func NewCIDGen(seed uint64) *TipIDGen { - return &TipIDGen{seed} +func NewTipGen(seed uint64) *TipGen { + return &TipGen{seed} } -func (c *TipIDGen) Sample() gpbft.TipSetID { +func (c *TipGen) Sample() gpbft.TipSet { b := make([]byte, 8) for i := range b { b[i] = alphanum[c.nextN(len(alphanum))] } - return gpbft.NewTipSetID([][]byte{b}) + return b } -func (c *TipIDGen) nextN(n int) uint64 { +func (c *TipGen) nextN(n int) uint64 { bucketSize := uint64(1<<63) / uint64(n) limit := bucketSize * uint64(n) for { @@ -478,7 +478,7 @@ func (c *TipIDGen) nextN(n int) uint64 { } } -func (c *TipIDGen) next() uint64 { +func (c *TipGen) next() uint64 { x := c.xorshiftState x ^= x << 13 x ^= x >> 7 diff --git a/test/absent_test.go b/test/absent_test.go index 65d118e6..cc936887 100644 --- a/test/absent_test.go +++ b/test/absent_test.go @@ -15,7 +15,7 @@ func TestAbsent(t *testing.T) { // Adversary has 1/4 of power. sm.SetAdversary(adversary.NewAbsent(99, sm.HostFor(99)), 1) - a := sm.Base(0).Extend(sm.CIDGen.Sample()) + a := sm.Base(0).Extend(sm.TipGen.Sample()) sm.SetChains(sim.ChainCount{Count: len(sm.Participants), Chain: a}) require.NoErrorf(t, sm.Run(1, MAX_ROUNDS), "%s", sm.Describe()) diff --git a/test/decide_test.go b/test/decide_test.go index 0a4d12ad..1a757ba5 100644 --- a/test/decide_test.go +++ b/test/decide_test.go @@ -12,14 +12,14 @@ func TestImmediateDecide(t *testing.T) { sm := sim.NewSimulation(AsyncConfig(1, 0), GraniteConfig(), sim.TraceNone) // Create adversarial node - value := sm.Base(0).Extend(sm.CIDGen.Sample()) + value := sm.Base(0).Extend(sm.TipGen.Sample()) adv := adversary.NewImmediateDecide(99, sm.HostFor(99), sm.PowerTable(0), value) // Add the adversary to the simulation with 3/4 of total power. sm.SetAdversary(adv, 3) // The honest node starts with a different chain (on the same base). - sm.SetChains(sim.ChainCount{Count: 1, Chain: sm.Base(0).Extend(sm.CIDGen.Sample())}) + sm.SetChains(sim.ChainCount{Count: 1, Chain: sm.Base(0).Extend(sm.TipGen.Sample())}) adv.Begin() err := sm.Run(1, MAX_ROUNDS) if err != nil { diff --git a/test/honest_test.go b/test/honest_test.go index f16b7c25..dc2def7f 100644 --- a/test/honest_test.go +++ b/test/honest_test.go @@ -12,7 +12,7 @@ import ( func TestSingleton(t *testing.T) { sm := sim.NewSimulation(SyncConfig(1), GraniteConfig(), sim.TraceNone) - a := sm.Base(0).Extend(sm.CIDGen.Sample()) + a := sm.Base(0).Extend(sm.TipGen.Sample()) sm.SetChains(sim.ChainCount{Count: 1, Chain: a}) require.NoErrorf(t, sm.Run(1, MAX_ROUNDS), "%s", sm.Describe()) @@ -38,7 +38,7 @@ func TestSyncPair(t *testing.T) { test := test t.Run(test.name, func(t *testing.T) { sm := sim.NewSimulation(test.config, GraniteConfig(), sim.TraceNone) - a := sm.Base(0).Extend(sm.CIDGen.Sample()) + a := sm.Base(0).Extend(sm.TipGen.Sample()) sm.SetChains(sim.ChainCount{Count: len(sm.Participants), Chain: a}) require.NoErrorf(t, sm.Run(1, MAX_ROUNDS), "%s", sm.Describe()) @@ -51,7 +51,7 @@ func TestASyncPair(t *testing.T) { t.Parallel() repeatInParallel(t, ASYNC_ITERS, func(t *testing.T, repetition int) { sm := sim.NewSimulation(AsyncConfig(2, repetition), GraniteConfig(), sim.TraceNone) - a := sm.Base(0).Extend(sm.CIDGen.Sample()) + a := sm.Base(0).Extend(sm.TipGen.Sample()) sm.SetChains(sim.ChainCount{Count: len(sm.Participants), Chain: a}) require.NoErrorf(t, sm.Run(1, MAX_ROUNDS), "%s", sm.Describe()) @@ -78,8 +78,8 @@ func TestSyncPairDisagree(t *testing.T) { test := test t.Run(test.name, func(t *testing.T) { sm := sim.NewSimulation(test.config, GraniteConfig(), sim.TraceNone) - a := sm.Base(0).Extend(sm.CIDGen.Sample()) - b := sm.Base(0).Extend(sm.CIDGen.Sample()) + a := sm.Base(0).Extend(sm.TipGen.Sample()) + b := sm.Base(0).Extend(sm.TipGen.Sample()) sm.SetChains(sim.ChainCount{Count: 1, Chain: a}, sim.ChainCount{Count: 1, Chain: b}) require.NoErrorf(t, sm.Run(1, MAX_ROUNDS), "%s", sm.Describe()) @@ -92,8 +92,8 @@ func TestSyncPairDisagree(t *testing.T) { func TestAsyncPairDisagree(t *testing.T) { repeatInParallel(t, ASYNC_ITERS, func(t *testing.T, repetition int) { sm := sim.NewSimulation(AsyncConfig(2, repetition), GraniteConfig(), sim.TraceNone) - a := sm.Base(0).Extend(sm.CIDGen.Sample()) - b := sm.Base(0).Extend(sm.CIDGen.Sample()) + a := sm.Base(0).Extend(sm.TipGen.Sample()) + b := sm.Base(0).Extend(sm.TipGen.Sample()) sm.SetChains(sim.ChainCount{Count: 1, Chain: a}, sim.ChainCount{Count: 1, Chain: b}) require.NoErrorf(t, sm.Run(1, MAX_ROUNDS), "%s", sm.Describe()) @@ -106,7 +106,7 @@ func TestSyncAgreement(t *testing.T) { repeatInParallel(t, 50, func(t *testing.T, repetition int) { honestCount := 3 + repetition sm := sim.NewSimulation(SyncConfig(honestCount), GraniteConfig(), sim.TraceNone) - a := sm.Base(0).Extend(sm.CIDGen.Sample()) + a := sm.Base(0).Extend(sm.TipGen.Sample()) sm.SetChains(sim.ChainCount{Count: len(sm.Participants), Chain: a}) require.NoErrorf(t, sm.Run(1, MAX_ROUNDS), "%s", sm.Describe()) // Synchronous, agreeing groups always decide the candidate. @@ -122,7 +122,7 @@ func TestAsyncAgreement(t *testing.T) { t.Run(fmt.Sprintf("honest count %d", honestCount), func(t *testing.T) { repeatInParallel(t, ASYNC_ITERS, func(t *testing.T, repetition int) { sm := sim.NewSimulation(AsyncConfig(honestCount, repetition), GraniteConfig(), sim.TraceNone) - a := sm.Base(0).Extend(sm.CIDGen.Sample()) + a := sm.Base(0).Extend(sm.TipGen.Sample()) sm.SetChains(sim.ChainCount{Count: len(sm.Participants), Chain: a}) require.NoErrorf(t, sm.Run(1, MAX_ROUNDS), "%s", sm.Describe()) @@ -137,8 +137,8 @@ func TestSyncHalves(t *testing.T) { repeatInParallel(t, 15, func(t *testing.T, repetition int) { honestCount := repetition*2 + 2 sm := sim.NewSimulation(SyncConfig(honestCount), GraniteConfig(), sim.TraceNone) - a := sm.Base(0).Extend(sm.CIDGen.Sample()) - b := sm.Base(0).Extend(sm.CIDGen.Sample()) + a := sm.Base(0).Extend(sm.TipGen.Sample()) + b := sm.Base(0).Extend(sm.TipGen.Sample()) sm.SetChains(sim.ChainCount{Count: honestCount / 2, Chain: a}, sim.ChainCount{Count: honestCount / 2, Chain: b}) require.NoErrorf(t, sm.Run(1, MAX_ROUNDS), "%s", sm.Describe()) @@ -152,8 +152,8 @@ func TestSyncHalvesBLS(t *testing.T) { repeatInParallel(t, 3, func(t *testing.T, repetition int) { honestCount := repetition*2 + 2 sm := sim.NewSimulation(SyncConfig(honestCount).UseBLS(), GraniteConfig(), sim.TraceNone) - a := sm.Base(0).Extend(sm.CIDGen.Sample()) - b := sm.Base(0).Extend(sm.CIDGen.Sample()) + a := sm.Base(0).Extend(sm.TipGen.Sample()) + b := sm.Base(0).Extend(sm.TipGen.Sample()) sm.SetChains(sim.ChainCount{Count: honestCount / 2, Chain: a}, sim.ChainCount{Count: honestCount / 2, Chain: b}) require.NoErrorf(t, sm.Run(1, MAX_ROUNDS), "%s", sm.Describe()) @@ -169,8 +169,8 @@ func TestAsyncHalves(t *testing.T) { t.Run(fmt.Sprintf("honest count %d", honestCount), func(t *testing.T) { repeatInParallel(t, ASYNC_ITERS, func(t *testing.T, repetition int) { sm := sim.NewSimulation(AsyncConfig(honestCount, repetition), GraniteConfig(), sim.TraceNone) - a := sm.Base(0).Extend(sm.CIDGen.Sample()) - b := sm.Base(0).Extend(sm.CIDGen.Sample()) + a := sm.Base(0).Extend(sm.TipGen.Sample()) + b := sm.Base(0).Extend(sm.TipGen.Sample()) sm.SetChains(sim.ChainCount{Count: honestCount / 2, Chain: a}, sim.ChainCount{Count: honestCount / 2, Chain: b}) require.NoErrorf(t, sm.Run(1, MAX_ROUNDS), "%s", sm.Describe()) @@ -185,8 +185,8 @@ func TestRequireStrongQuorumToProgress(t *testing.T) { t.Parallel() repeatInParallel(t, ASYNC_ITERS, func(t *testing.T, repetition int) { sm := sim.NewSimulation(AsyncConfig(30, repetition), GraniteConfig(), sim.TraceNone) - a := sm.Base(0).Extend(sm.CIDGen.Sample()) - b := sm.Base(0).Extend(sm.CIDGen.Sample()) + a := sm.Base(0).Extend(sm.TipGen.Sample()) + b := sm.Base(0).Extend(sm.TipGen.Sample()) // No strict > quorum. sm.SetChains(sim.ChainCount{Count: 20, Chain: a}, sim.ChainCount{Count: 10, Chain: b}) @@ -200,11 +200,11 @@ func TestLongestCommonPrefix(t *testing.T) { // This test uses a synchronous configuration to ensure timely message delivery. // If async, it is possible to decide the base chain if QUALITY messages are delayed. sm := sim.NewSimulation(SyncConfig(4), GraniteConfig(), sim.TraceNone) - ab := sm.Base(0).Extend(sm.CIDGen.Sample()) - abc := ab.Extend(sm.CIDGen.Sample()) - abd := ab.Extend(sm.CIDGen.Sample()) - abe := ab.Extend(sm.CIDGen.Sample()) - abf := ab.Extend(sm.CIDGen.Sample()) + ab := sm.Base(0).Extend(sm.TipGen.Sample()) + abc := ab.Extend(sm.TipGen.Sample()) + abd := ab.Extend(sm.TipGen.Sample()) + abe := ab.Extend(sm.TipGen.Sample()) + abf := ab.Extend(sm.TipGen.Sample()) sm.SetChains( sim.ChainCount{Count: 1, Chain: abc}, sim.ChainCount{Count: 1, Chain: abd}, diff --git a/test/multi_instance_test.go b/test/multi_instance_test.go index c7f13c62..fb15e241 100644 --- a/test/multi_instance_test.go +++ b/test/multi_instance_test.go @@ -13,7 +13,7 @@ const INSTANCE_COUNT = 4000 func TestMultiSingleton(t *testing.T) { sm := sim.NewSimulation(SyncConfig(1), GraniteConfig(), sim.TraceNone) - a := sm.Base(0).Extend(sm.CIDGen.Sample()) + a := sm.Base(0).Extend(sm.TipGen.Sample()) sm.SetChains(sim.ChainCount{Count: 1, Chain: a}) require.NoErrorf(t, sm.Run(INSTANCE_COUNT, MAX_ROUNDS), "%s", sm.Describe()) @@ -23,7 +23,7 @@ func TestMultiSingleton(t *testing.T) { func TestMultiSyncPair(t *testing.T) { sm := sim.NewSimulation(SyncConfig(2), GraniteConfig(), sim.TraceNone) - a := sm.Base(0).Extend(sm.CIDGen.Sample()) + a := sm.Base(0).Extend(sm.TipGen.Sample()) sm.SetChains(sim.ChainCount{Count: len(sm.Participants), Chain: a}) require.NoErrorf(t, sm.Run(INSTANCE_COUNT, MAX_ROUNDS), "%s", sm.Describe()) @@ -33,7 +33,7 @@ func TestMultiSyncPair(t *testing.T) { func TestMultiASyncPair(t *testing.T) { sm := sim.NewSimulation(AsyncConfig(2, 0), GraniteConfig(), sim.TraceNone) - a := sm.Base(0).Extend(sm.CIDGen.Sample()) + a := sm.Base(0).Extend(sm.TipGen.Sample()) sm.SetChains(sim.ChainCount{Count: len(sm.Participants), Chain: a}) require.NoErrorf(t, sm.Run(INSTANCE_COUNT, MAX_ROUNDS), "%s", sm.Describe()) @@ -49,7 +49,7 @@ func TestMultiSyncAgreement(t *testing.T) { repeatInParallel(t, 9, func(t *testing.T, repetition int) { honestCount := repetition + 3 sm := sim.NewSimulation(SyncConfig(honestCount), GraniteConfig(), sim.TraceNone) - a := sm.Base(0).Extend(sm.CIDGen.Sample()) + a := sm.Base(0).Extend(sm.TipGen.Sample()) // All nodes start with the same chain and will observe the same extensions of that chain // in subsequent instances. sm.SetChains(sim.ChainCount{Count: len(sm.Participants), Chain: a}) @@ -65,7 +65,7 @@ func TestMultiAsyncAgreement(t *testing.T) { repeatInParallel(t, 9, func(t *testing.T, repetition int) { honestCount := repetition + 3 sm := sim.NewSimulation(AsyncConfig(honestCount, 0), GraniteConfig(), sim.TraceNone) - sm.SetChains(sim.ChainCount{Count: honestCount, Chain: sm.Base(0).Extend(sm.CIDGen.Sample())}) + sm.SetChains(sim.ChainCount{Count: honestCount, Chain: sm.Base(0).Extend(sm.TipGen.Sample())}) require.NoErrorf(t, sm.Run(INSTANCE_COUNT, MAX_ROUNDS), "%s", sm.Describe()) // Note: The expected decision only needs to be something recent. diff --git a/test/util.go b/test/util.go index 82dea4f6..c0f54c2a 100644 --- a/test/util.go +++ b/test/util.go @@ -1,6 +1,7 @@ package test import ( + "bytes" "os" "runtime" "strconv" @@ -39,7 +40,7 @@ nextParticipant: decision, ok := sm.GetDecision(instance, participant.ID()) require.True(t, ok, "no decision for participant %d in instance %d", participant.ID(), instance) for _, e := range expected { - if decision.Head().Eq(e) { + if bytes.Equal(decision.Head(), e) { continue nextParticipant } } diff --git a/test/withhold_test.go b/test/withhold_test.go index 9e3fa9f2..cc869d02 100644 --- a/test/withhold_test.go +++ b/test/withhold_test.go @@ -19,8 +19,8 @@ func TestWitholdCommit1(t *testing.T) { adv := adversary.NewWitholdCommit(99, sm.HostFor(99), sm.PowerTable(0)) sm.SetAdversary(adv, 3) // Adversary has 30% of 10 total power. - a := sm.Base(0).Extend(sm.CIDGen.Sample()) - b := sm.Base(0).Extend(sm.CIDGen.Sample()) + a := sm.Base(0).Extend(sm.TipGen.Sample()) + b := sm.Base(0).Extend(sm.TipGen.Sample()) // Of 7 nodes, 4 victims will prefer chain A, 3 others will prefer chain B. // The adversary will target the first to decide, and withhold COMMIT from the rest. // After the victim decides in round 0, the adversary stops participating.