193 lines
3.2 KiB
Go
193 lines
3.2 KiB
Go
package types
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"sort"
|
|
|
|
"github.com/ipfs/go-cid"
|
|
logging "github.com/ipfs/go-log"
|
|
)
|
|
|
|
var log = logging.Logger("types")
|
|
|
|
type TipSet struct {
|
|
cids []cid.Cid
|
|
blks []*BlockHeader
|
|
height uint64
|
|
}
|
|
|
|
// why didnt i just export the fields? Because the struct has methods with the
|
|
// same names already
|
|
type ExpTipSet struct {
|
|
Cids []cid.Cid
|
|
Blocks []*BlockHeader
|
|
Height uint64
|
|
}
|
|
|
|
func (ts *TipSet) MarshalJSON() ([]byte, error) {
|
|
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 {
|
|
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 blks[i].Cid().KeyString() < blks[j].Cid().KeyString()
|
|
}
|
|
|
|
return ti.Less(tj)
|
|
}
|
|
}
|
|
|
|
func NewTipSet(blks []*BlockHeader) (*TipSet, error) {
|
|
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")
|
|
}
|
|
ts.cids = append(ts.cids, b.Cid())
|
|
|
|
// TODO: ensure the same parents
|
|
}
|
|
ts.height = blks[0].Height
|
|
|
|
return &ts, nil
|
|
}
|
|
|
|
func (ts *TipSet) Cids() []cid.Cid {
|
|
return ts.cids
|
|
}
|
|
|
|
func (ts *TipSet) Height() uint64 {
|
|
return ts.height
|
|
}
|
|
|
|
func (ts *TipSet) Parents() []cid.Cid {
|
|
return 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 len(ts.blks) != len(ots.blks) {
|
|
return false
|
|
}
|
|
|
|
for i, b := range ts.blks {
|
|
if b.Cid() != ots.blks[i].Cid() {
|
|
return false
|
|
}
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
func (t *Ticket) Less(o *Ticket) bool {
|
|
return bytes.Compare(t.VRFProof, o.VRFProof) < 0
|
|
}
|
|
|
|
func (ts *TipSet) MinTicket() *Ticket {
|
|
b := ts.MinTicketBlock()
|
|
return b.Tickets[len(b.Tickets)-1]
|
|
}
|
|
|
|
func (ts *TipSet) MinTimestamp() uint64 {
|
|
minTs := ts.Blocks()[0].Timestamp
|
|
for _, bh := range ts.Blocks()[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) ParentState() cid.Cid {
|
|
return ts.blks[0].ParentStateRoot
|
|
}
|
|
|
|
func (ts *TipSet) Contains(oc cid.Cid) bool {
|
|
for _, c := range ts.cids {
|
|
if c == oc {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|