2019-07-26 04:54:22 +00:00
|
|
|
package types
|
|
|
|
|
|
|
|
import (
|
2019-08-16 00:17:09 +00:00
|
|
|
"bytes"
|
2019-07-26 04:54:22 +00:00
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
2019-11-01 13:58:48 +00:00
|
|
|
"io"
|
2019-10-01 18:47:42 +00:00
|
|
|
"sort"
|
2019-07-26 04:54:22 +00:00
|
|
|
|
|
|
|
"github.com/ipfs/go-cid"
|
2020-01-08 19:10:57 +00:00
|
|
|
logging "github.com/ipfs/go-log/v2"
|
2020-04-01 15:17:05 +00:00
|
|
|
"github.com/minio/blake2b-simd"
|
2019-11-01 22:44:55 +00:00
|
|
|
cbg "github.com/whyrusleeping/cbor-gen"
|
2019-12-10 21:37:51 +00:00
|
|
|
"golang.org/x/xerrors"
|
2022-06-14 15:00:51 +00:00
|
|
|
|
|
|
|
"github.com/filecoin-project/go-state-types/abi"
|
2019-07-26 04:54:22 +00:00
|
|
|
)
|
|
|
|
|
2019-08-21 23:24:59 +00:00
|
|
|
var log = logging.Logger("types")
|
|
|
|
|
2019-07-26 04:54:22 +00:00
|
|
|
type TipSet struct {
|
|
|
|
cids []cid.Cid
|
|
|
|
blks []*BlockHeader
|
2020-02-08 02:18:32 +00:00
|
|
|
height abi.ChainEpoch
|
2019-07-26 04:54:22 +00:00
|
|
|
}
|
|
|
|
|
2019-11-01 13:58:48 +00:00
|
|
|
type ExpTipSet struct {
|
2019-07-26 04:54:22 +00:00
|
|
|
Cids []cid.Cid
|
|
|
|
Blocks []*BlockHeader
|
2020-02-08 02:18:32 +00:00
|
|
|
Height abi.ChainEpoch
|
2019-07-26 04:54:22 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (ts *TipSet) MarshalJSON() ([]byte, error) {
|
2020-06-02 14:29:39 +00:00
|
|
|
// why didnt i just export the fields? Because the struct has methods with the
|
|
|
|
// same names already
|
2019-11-01 13:58:48 +00:00
|
|
|
return json.Marshal(ExpTipSet{
|
2019-07-26 04:54:22 +00:00
|
|
|
Cids: ts.cids,
|
|
|
|
Blocks: ts.blks,
|
|
|
|
Height: ts.height,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
func (ts *TipSet) UnmarshalJSON(b []byte) error {
|
2019-11-01 13:58:48 +00:00
|
|
|
var ets ExpTipSet
|
2019-07-26 04:54:22 +00:00
|
|
|
if err := json.Unmarshal(b, &ets); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2019-10-01 18:47:42 +00:00
|
|
|
ots, err := NewTipSet(ets.Blocks)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
*ts = *ots
|
|
|
|
|
2019-07-26 04:54:22 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2019-11-01 13:58:48 +00:00
|
|
|
func (ts *TipSet) MarshalCBOR(w io.Writer) error {
|
2019-11-01 22:44:55 +00:00
|
|
|
if ts == nil {
|
|
|
|
_, err := w.Write(cbg.CborNull)
|
|
|
|
return err
|
|
|
|
}
|
2019-11-01 13:58:48 +00:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2019-10-01 18:47:42 +00:00
|
|
|
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) {
|
2019-10-04 20:58:24 +00:00
|
|
|
log.Warnf("blocks have same ticket (%s %s)", blks[i].Miner, blks[j].Miner)
|
2020-04-01 01:34:23 +00:00
|
|
|
return bytes.Compare(blks[i].Cid().Bytes(), blks[j].Cid().Bytes()) < 0
|
2019-10-01 18:47:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return ti.Less(tj)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-08-04 17:44:48 +00:00
|
|
|
// Checks:
|
2022-08-29 14:25:30 +00:00
|
|
|
// - 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).
|
2019-07-26 04:54:22 +00:00
|
|
|
func NewTipSet(blks []*BlockHeader) (*TipSet, error) {
|
2019-12-10 21:37:51 +00:00
|
|
|
if len(blks) == 0 {
|
|
|
|
return nil, xerrors.Errorf("NewTipSet called with zero length array of blocks")
|
|
|
|
}
|
|
|
|
|
2019-10-01 18:47:42 +00:00
|
|
|
sort.Slice(blks, tipsetSortFunc(blks))
|
|
|
|
|
2019-07-26 04:54:22 +00:00
|
|
|
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")
|
|
|
|
}
|
2019-10-28 09:19:58 +00:00
|
|
|
|
2020-07-15 11:17:50 +00:00
|
|
|
if len(blks[0].Parents) != len(b.Parents) {
|
|
|
|
return nil, fmt.Errorf("cannot create tipset with mismatching number of parents")
|
|
|
|
}
|
|
|
|
|
2019-10-28 09:19:58 +00:00
|
|
|
for i, cid := range b.Parents {
|
|
|
|
if cid != blks[0].Parents[i] {
|
|
|
|
return nil, fmt.Errorf("cannot create tipset with mismatching parents")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-07-26 04:54:22 +00:00
|
|
|
ts.cids = append(ts.cids, b.Cid())
|
2019-10-13 01:18:59 +00:00
|
|
|
|
2019-07-26 04:54:22 +00:00
|
|
|
}
|
|
|
|
ts.height = blks[0].Height
|
|
|
|
|
|
|
|
return &ts, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (ts *TipSet) Cids() []cid.Cid {
|
|
|
|
return ts.cids
|
|
|
|
}
|
|
|
|
|
2019-11-08 05:36:50 +00:00
|
|
|
func (ts *TipSet) Key() TipSetKey {
|
2020-02-11 23:29:45 +00:00
|
|
|
if ts == nil {
|
|
|
|
return EmptyTSK
|
|
|
|
}
|
2019-11-08 05:36:50 +00:00
|
|
|
return NewTipSetKey(ts.cids...)
|
|
|
|
}
|
|
|
|
|
2020-02-08 02:18:32 +00:00
|
|
|
func (ts *TipSet) Height() abi.ChainEpoch {
|
2019-07-26 04:54:22 +00:00
|
|
|
return ts.height
|
|
|
|
}
|
|
|
|
|
2019-12-16 19:22:56 +00:00
|
|
|
func (ts *TipSet) Parents() TipSetKey {
|
|
|
|
return NewTipSetKey(ts.blks[0].Parents...)
|
2019-07-26 04:54:22 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (ts *TipSet) Blocks() []*BlockHeader {
|
|
|
|
return ts.blks
|
|
|
|
}
|
|
|
|
|
|
|
|
func (ts *TipSet) Equals(ots *TipSet) bool {
|
2019-10-04 22:43:04 +00:00
|
|
|
if ts == nil && ots == nil {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
if ts == nil || ots == nil {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2020-09-27 01:58:37 +00:00
|
|
|
if ts.height != ots.height {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2020-09-27 02:17:06 +00:00
|
|
|
if len(ts.cids) != len(ots.cids) {
|
2019-07-26 04:54:22 +00:00
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2020-09-27 02:17:06 +00:00
|
|
|
for i, cid := range ts.cids {
|
|
|
|
if cid != ots.cids[i] {
|
2019-07-26 04:54:22 +00:00
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return true
|
|
|
|
}
|
2019-08-16 00:17:09 +00:00
|
|
|
|
2019-09-06 06:26:02 +00:00
|
|
|
func (t *Ticket) Less(o *Ticket) bool {
|
2020-04-01 15:17:05 +00:00
|
|
|
tDigest := blake2b.Sum256(t.VRFProof)
|
|
|
|
oDigest := blake2b.Sum256(o.VRFProof)
|
|
|
|
return bytes.Compare(tDigest[:], oDigest[:]) < 0
|
2019-09-06 06:26:02 +00:00
|
|
|
}
|
2019-08-16 00:17:09 +00:00
|
|
|
|
2019-09-06 06:26:02 +00:00
|
|
|
func (ts *TipSet) MinTicket() *Ticket {
|
2019-11-19 15:53:00 +00:00
|
|
|
return ts.MinTicketBlock().Ticket
|
2019-08-16 00:17:09 +00:00
|
|
|
}
|
2019-08-30 02:59:54 +00:00
|
|
|
|
|
|
|
func (ts *TipSet) MinTimestamp() uint64 {
|
2023-01-13 19:11:13 +00:00
|
|
|
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:] {
|
2019-08-30 02:59:54 +00:00
|
|
|
if bh.Timestamp < minTs {
|
|
|
|
minTs = bh.Timestamp
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return minTs
|
|
|
|
}
|
2019-09-06 06:26:02 +00:00
|
|
|
|
|
|
|
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
|
|
|
|
}
|
2019-09-18 02:50:03 +00:00
|
|
|
|
2023-03-10 20:59:23 +00:00
|
|
|
func (ts *TipSet) ParentMessageReceipts() cid.Cid {
|
|
|
|
return ts.blks[0].ParentMessageReceipts
|
|
|
|
}
|
|
|
|
|
2019-10-02 20:03:27 +00:00
|
|
|
func (ts *TipSet) ParentState() cid.Cid {
|
|
|
|
return ts.blks[0].ParentStateRoot
|
|
|
|
}
|
|
|
|
|
2019-11-15 02:27:43 +00:00
|
|
|
func (ts *TipSet) ParentWeight() BigInt {
|
|
|
|
return ts.blks[0].ParentWeight
|
|
|
|
}
|
|
|
|
|
2019-09-18 02:50:03 +00:00
|
|
|
func (ts *TipSet) Contains(oc cid.Cid) bool {
|
|
|
|
for _, c := range ts.cids {
|
|
|
|
if c == oc {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
2020-07-27 15:31:36 +00:00
|
|
|
|
|
|
|
func (ts *TipSet) IsChildOf(parent *TipSet) bool {
|
2020-08-04 17:44:27 +00:00
|
|
|
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
|
2020-07-27 15:31:36 +00:00
|
|
|
}
|
2020-08-20 04:49:10 +00:00
|
|
|
|
|
|
|
func (ts *TipSet) String() string {
|
|
|
|
return fmt.Sprintf("%v", ts.cids)
|
|
|
|
}
|