package types import ( "bytes" "encoding/json" "fmt" "io" "sort" "github.com/ipfs/go-cid" logging "github.com/ipfs/go-log/v2" "github.com/minio/blake2b-simd" cbg "github.com/whyrusleeping/cbor-gen" "golang.org/x/xerrors" "github.com/filecoin-project/go-state-types/abi" ) var log = logging.Logger("types") type TipSet struct { cids []cid.Cid blks []*BlockHeader height abi.ChainEpoch } type ExpTipSet struct { Cids []cid.Cid Blocks []*BlockHeader Height abi.ChainEpoch } func (ts *TipSet) MarshalJSON() ([]byte, error) { // why didnt i just export the fields? Because the struct has methods with the // same names already return json.Marshal(ExpTipSet{ Cids: ts.cids, Blocks: ts.blks, Height: ts.height, }) } func (ts *TipSet) UnmarshalJSON(b []byte) error { var ets ExpTipSet if err := json.Unmarshal(b, &ets); err != nil { return err } ots, err := NewTipSet(ets.Blocks) if err != nil { return err } *ts = *ots return nil } func (ts *TipSet) MarshalCBOR(w io.Writer) error { if ts == nil { _, err := w.Write(cbg.CborNull) return err } return (&ExpTipSet{ Cids: ts.cids, Blocks: ts.blks, Height: ts.height, }).MarshalCBOR(w) } func (ts *TipSet) UnmarshalCBOR(r io.Reader) error { var ets ExpTipSet if err := ets.UnmarshalCBOR(r); err != nil { return err } ots, err := NewTipSet(ets.Blocks) if err != nil { return err } *ts = *ots return nil } func tipsetSortFunc(blks []*BlockHeader) func(i, j int) bool { return func(i, j int) bool { ti := blks[i].LastTicket() tj := blks[j].LastTicket() if ti.Equals(tj) { log.Warnf("blocks have same ticket (%s %s)", blks[i].Miner, blks[j].Miner) return bytes.Compare(blks[i].Cid().Bytes(), blks[j].Cid().Bytes()) < 0 } return ti.Less(tj) } } // Checks: // - A tipset is composed of at least one block. (Because of our variable // number of blocks per tipset, determined by randomness, we do not impose // an upper limit.) // - All blocks have the same height. // - All blocks have the same parents (same number of them and matching CIDs). func NewTipSet(blks []*BlockHeader) (*TipSet, error) { if len(blks) == 0 { return nil, xerrors.Errorf("NewTipSet called with zero length array of blocks") } sort.Slice(blks, tipsetSortFunc(blks)) var ts TipSet ts.cids = []cid.Cid{blks[0].Cid()} ts.blks = blks for _, b := range blks[1:] { if b.Height != blks[0].Height { return nil, fmt.Errorf("cannot create tipset with mismatching heights") } if len(blks[0].Parents) != len(b.Parents) { return nil, fmt.Errorf("cannot create tipset with mismatching number of parents") } for i, cid := range b.Parents { if cid != blks[0].Parents[i] { return nil, fmt.Errorf("cannot create tipset with mismatching parents") } } ts.cids = append(ts.cids, b.Cid()) } ts.height = blks[0].Height return &ts, nil } func (ts *TipSet) Cids() []cid.Cid { return ts.cids } func (ts *TipSet) Key() TipSetKey { if ts == nil { return EmptyTSK } return NewTipSetKey(ts.cids...) } func (ts *TipSet) Height() abi.ChainEpoch { return ts.height } func (ts *TipSet) Parents() TipSetKey { return NewTipSetKey(ts.blks[0].Parents...) } func (ts *TipSet) Blocks() []*BlockHeader { return ts.blks } func (ts *TipSet) Equals(ots *TipSet) bool { if ts == nil && ots == nil { return true } if ts == nil || ots == nil { return false } if ts.height != ots.height { return false } if len(ts.cids) != len(ots.cids) { return false } for i, cid := range ts.cids { if cid != ots.cids[i] { return false } } return true } func (t *Ticket) Less(o *Ticket) bool { tDigest := blake2b.Sum256(t.VRFProof) oDigest := blake2b.Sum256(o.VRFProof) return bytes.Compare(tDigest[:], oDigest[:]) < 0 } func (ts *TipSet) MinTicket() *Ticket { return ts.MinTicketBlock().Ticket } func (ts *TipSet) MinTimestamp() uint64 { if ts == nil { return 0 } blks := ts.Blocks() // TODO::FVM @vyzo @magik Null rounds shouldn't ever be represented as // tipsets with no blocks; Null-round generally means that the tipset at // that epoch doesn't exist - and the next tipset that does exist links // straight to first epoch with blocks (@raulk agrees -- this is odd) if len(blks) == 0 { // null rounds make things crash -- it is threaded in every fvm instantiation return 0 } minTs := blks[0].Timestamp for _, bh := range blks[1:] { if bh.Timestamp < minTs { minTs = bh.Timestamp } } return minTs } func (ts *TipSet) MinTicketBlock() *BlockHeader { blks := ts.Blocks() min := blks[0] for _, b := range blks[1:] { if b.LastTicket().Less(min.LastTicket()) { min = b } } return min } func (ts *TipSet) ParentMessageReceipts() cid.Cid { return ts.blks[0].ParentMessageReceipts } func (ts *TipSet) ParentState() cid.Cid { return ts.blks[0].ParentStateRoot } func (ts *TipSet) ParentWeight() BigInt { return ts.blks[0].ParentWeight } func (ts *TipSet) Contains(oc cid.Cid) bool { for _, c := range ts.cids { if c == oc { return true } } return false } func (ts *TipSet) IsChildOf(parent *TipSet) bool { return CidArrsEqual(ts.Parents().Cids(), parent.Cids()) && // FIXME: The height check might go beyond what is meant by // "parent", but many parts of the code rely on the tipset's // height for their processing logic at the moment to obviate it. ts.height > parent.height } func (ts *TipSet) String() string { return fmt.Sprintf("%v", ts.cids) }