Add TipSetKey as a concatenation of block CIDs, and use if for ChainGetTipSet, ChainGetRandomness.

This commit is contained in:
anorth 2019-11-08 16:36:50 +11:00
parent 745df10bec
commit 84a4766d8f
12 changed files with 220 additions and 29 deletions

View File

@ -23,9 +23,9 @@ type FullNode interface {
// First message is guaranteed to be of len == 1, and type == 'current'
ChainNotify(context.Context) (<-chan []*store.HeadChange, error)
ChainHead(context.Context) (*types.TipSet, error)
ChainGetRandomness(context.Context, *types.TipSet, []*types.Ticket, int) ([]byte, error)
ChainGetRandomness(context.Context, types.TipSetKey, []*types.Ticket, int) ([]byte, error)
ChainGetBlock(context.Context, cid.Cid) (*types.BlockHeader, error)
ChainGetTipSet(context.Context, []cid.Cid) (*types.TipSet, error)
ChainGetTipSet(context.Context, types.TipSetKey) (*types.TipSet, error)
ChainGetBlockMessages(context.Context, cid.Cid) (*BlockMessages, error)
ChainGetParentReceipts(context.Context, cid.Cid) ([]*types.MessageReceipt, error)
ChainGetParentMessages(context.Context, cid.Cid) ([]Message, error)

View File

@ -39,9 +39,9 @@ type FullNodeStruct struct {
Internal struct {
ChainNotify func(context.Context) (<-chan []*store.HeadChange, error) `perm:"read"`
ChainHead func(context.Context) (*types.TipSet, error) `perm:"read"`
ChainGetRandomness func(context.Context, *types.TipSet, []*types.Ticket, int) ([]byte, error) `perm:"read"`
ChainGetRandomness func(context.Context, types.TipSetKey, []*types.Ticket, int) ([]byte, error) `perm:"read"`
ChainGetBlock func(context.Context, cid.Cid) (*types.BlockHeader, error) `perm:"read"`
ChainGetTipSet func(context.Context, []cid.Cid) (*types.TipSet, error) `perm:"read"`
ChainGetTipSet func(context.Context, types.TipSetKey) (*types.TipSet, error) `perm:"read"`
ChainGetBlockMessages func(context.Context, cid.Cid) (*BlockMessages, error) `perm:"read"`
ChainGetParentReceipts func(context.Context, cid.Cid) ([]*types.MessageReceipt, error) `perm:"read"`
ChainGetParentMessages func(context.Context, cid.Cid) ([]Message, error) `perm:"read"`
@ -245,7 +245,7 @@ func (c *FullNodeStruct) ChainHead(ctx context.Context) (*types.TipSet, error) {
return c.Internal.ChainHead(ctx)
}
func (c *FullNodeStruct) ChainGetRandomness(ctx context.Context, pts *types.TipSet, ticks []*types.Ticket, lb int) ([]byte, error) {
func (c *FullNodeStruct) ChainGetRandomness(ctx context.Context, pts types.TipSetKey, ticks []*types.Ticket, lb int) ([]byte, error) {
return c.Internal.ChainGetRandomness(ctx, pts, ticks, lb)
}
@ -301,8 +301,8 @@ func (c *FullNodeStruct) ChainGetBlock(ctx context.Context, b cid.Cid) (*types.B
return c.Internal.ChainGetBlock(ctx, b)
}
func (c *FullNodeStruct) ChainGetTipSet(ctx context.Context, cids []cid.Cid) (*types.TipSet, error) {
return c.Internal.ChainGetTipSet(ctx, cids)
func (c *FullNodeStruct) ChainGetTipSet(ctx context.Context, key types.TipSetKey) (*types.TipSet, error) {
return c.Internal.ChainGetTipSet(ctx, key)
}
func (c *FullNodeStruct) ChainGetBlockMessages(ctx context.Context, b cid.Cid) (*BlockMessages, error) {

View File

@ -354,7 +354,7 @@ func (cg *ChainGen) YieldRepo() (repo.Repo, error) {
}
type MiningCheckAPI interface {
ChainGetRandomness(context.Context, *types.TipSet, []*types.Ticket, int) ([]byte, error)
ChainGetRandomness(context.Context, types.TipSetKey, []*types.Ticket, int) ([]byte, error)
StateMinerPower(context.Context, address.Address, *types.TipSet) (api.MinerPower, error)
@ -368,7 +368,7 @@ type mca struct {
sm *stmgr.StateManager
}
func (mca mca) ChainGetRandomness(ctx context.Context, pts *types.TipSet, ticks []*types.Ticket, lb int) ([]byte, error) {
func (mca mca) ChainGetRandomness(ctx context.Context, pts types.TipSetKey, ticks []*types.Ticket, lb int) ([]byte, error) {
return mca.sm.ChainStore().GetRandomness(ctx, pts.Cids(), ticks, int64(lb))
}
@ -393,7 +393,7 @@ func (mca mca) WalletSign(ctx context.Context, a address.Address, v []byte) (*ty
}
func IsRoundWinner(ctx context.Context, ts *types.TipSet, ticks []*types.Ticket, miner address.Address, a MiningCheckAPI) (bool, types.ElectionProof, error) {
r, err := a.ChainGetRandomness(ctx, ts, ticks, build.EcRandomnessLookback)
r, err := a.ChainGetRandomness(ctx, ts.Key(), ticks, build.EcRandomnessLookback)
if err != nil {
return false, nil, xerrors.Errorf("chain get randomness: %w", err)
}

View File

@ -123,6 +123,10 @@ func (ts *TipSet) Cids() []cid.Cid {
return ts.cids
}
func (ts *TipSet) Key() TipSetKey {
return NewTipSetKey(ts.cids...)
}
func (ts *TipSet) Height() uint64 {
return ts.height
}

132
chain/types/tipset_key.go Normal file
View File

@ -0,0 +1,132 @@
package types
import (
"bytes"
"encoding/binary"
"strings"
"github.com/ipfs/go-cid"
)
// A TipSetKey is an immutable collection of CIDs forming a unique key for a tipset.
// The CIDs are assumed to be distinct and in canonical order. Two keys with the same
// CIDs in a different order are not considered equal.
// TipSetKey is a lightweight value type, and may be compared for equality with ==.
type TipSetKey struct {
// The internal representation is a concatenation of the bytes of the CIDs, each preceded by
// uint32 specifying the length of the CIDs bytes, and the whole preceded by a uint32
// giving the number of CIDs.
// These gymnastics make the a TipSetKey usable as a map key.
// This representation is slightly larger than strictly necessary: CIDs do carry a prefix
// including their length, but the cid package doesn't quite expose the functions needed to
// safely parse a sequence of concatenated CIDs from a stream, and we probably don't want to
// (re-)implement that here. See https://github.com/ipfs/go-cid/issues/93.
// The empty key has value "" (no encoded-zero prefix).
value string
}
// NewTipSetKey builds a new key from a slice of CIDs.
// The CIDs are assumed to be ordered correctly.
func NewTipSetKey(cids ...cid.Cid) TipSetKey {
encoded, err := encodeKey(cids)
if err != nil {
panic("failed to encode CIDs: " + err.Error())
}
return TipSetKey{string(encoded)}
}
// TipSetKeyFromBytes wraps an encoded key, validating correct decoding.
func TipSetKeyFromBytes(encoded []byte) (TipSetKey, error) {
_, err := decodeKey(encoded)
if err != nil {
return TipSetKey{}, err
}
return TipSetKey{string(encoded)}, nil
}
// Cids returns a slice of the CIDs comprising this key.
func (k TipSetKey) Cids() []cid.Cid {
cids, err := decodeKey([]byte(k.value))
if err != nil {
panic("invalid tipset key: " + err.Error())
}
return cids
}
// String() returns a human-readable representation of the key.
func (k TipSetKey) String() string {
b := strings.Builder{}
b.WriteString("{")
cids := k.Cids()
for i, c := range cids {
b.WriteString(c.String())
if i < len(cids)-1 {
b.WriteString(",")
}
}
b.WriteString("}")
return b.String()
}
// Bytes() returns a binary representation of the key.
func (k TipSetKey) Bytes() []byte {
return []byte(k.value)
}
func encodeKey(cids []cid.Cid) ([]byte, error) {
length := uint32(len(cids))
if length == uint32(0) {
return []byte{}, nil
}
buffer := new(bytes.Buffer)
err := binary.Write(buffer, binary.LittleEndian, length)
if err != nil {
return nil, err
}
for _, c := range cids {
b := c.Bytes()
l := uint32(len(b))
err = binary.Write(buffer, binary.LittleEndian, l)
if err != nil {
return nil, err
}
err = binary.Write(buffer, binary.LittleEndian, c.Bytes())
if err != nil {
return nil, err
}
}
return buffer.Bytes(), nil
}
func decodeKey(encoded []byte) ([]cid.Cid, error) {
if len(encoded) == 0 {
return []cid.Cid{}, nil
}
buffer := bytes.NewReader(encoded)
var length uint32
err := binary.Read(buffer, binary.LittleEndian, &length)
if err != nil {
return nil, err
}
var cids []cid.Cid
for idx := uint32(0); idx < length; idx++ {
var l uint32
err = binary.Read(buffer, binary.LittleEndian, &l)
if err != nil {
return nil, err
}
buf := make([]byte, l)
err = binary.Read(buffer, binary.LittleEndian, &buf)
if err != nil {
return nil, err
}
blockCid, err := cid.Cast(buf)
if err != nil {
return nil, err
}
cids = append(cids, blockCid)
}
return cids, nil
}

View File

@ -0,0 +1,55 @@
package types
import (
"testing"
"github.com/ipfs/go-cid"
"github.com/multiformats/go-multihash"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestTipSetKey(t *testing.T) {
cb := cid.V1Builder{Codec: cid.DagCBOR, MhType: multihash.SHA2_256}
c1, _ := cb.Sum([]byte("a"))
c2, _ := cb.Sum([]byte("b"))
c3, _ := cb.Sum([]byte("c"))
t.Run("zero value", func(t *testing.T) {
assert.Equal(t, TipSetKey{}, NewTipSetKey())
})
t.Run("CID extraction", func(t *testing.T) {
assert.Equal(t, []cid.Cid{}, NewTipSetKey().Cids())
assert.Equal(t, []cid.Cid{c1}, NewTipSetKey(c1).Cids())
assert.Equal(t, []cid.Cid{c1, c2, c3}, NewTipSetKey(c1, c2, c3).Cids())
// The key doesn't check for duplicates.
assert.Equal(t, []cid.Cid{c1, c1}, NewTipSetKey(c1, c1).Cids())
})
t.Run("equality", func(t *testing.T) {
assert.Equal(t, NewTipSetKey(), NewTipSetKey())
assert.Equal(t, NewTipSetKey(c1), NewTipSetKey(c1))
assert.Equal(t, NewTipSetKey(c1, c2, c3), NewTipSetKey(c1, c2, c3))
assert.NotEqual(t, NewTipSetKey(), NewTipSetKey(c1))
assert.NotEqual(t, NewTipSetKey(c2), NewTipSetKey(c1))
// The key doesn't normalize order.
assert.NotEqual(t, NewTipSetKey(c1, c2), NewTipSetKey(c2, c1))
})
t.Run("encoding", func(t *testing.T) {
keys := []TipSetKey {
NewTipSetKey(),
NewTipSetKey(c1),
NewTipSetKey(c1, c2, c3),
}
for _, tk := range keys {
roundTrip, err := TipSetKeyFromBytes(tk.Bytes())
require.NoError(t, err)
assert.Equal(t, tk, roundTrip)
}
})
}

View File

@ -319,7 +319,7 @@ var chainListCmd = &cli.Command{
break
}
head, err = api.ChainGetTipSet(ctx, head.Parents())
head, err = api.ChainGetTipSet(ctx, types.NewTipSetKey(head.Parents()...))
if err != nil {
return err
}

View File

@ -28,7 +28,7 @@ func (a *ChainAPI) ChainHead(context.Context) (*types.TipSet, error) {
return a.Chain.GetHeaviestTipSet(), nil
}
func (a *ChainAPI) ChainGetRandomness(ctx context.Context, pts *types.TipSet, tickets []*types.Ticket, lb int) ([]byte, error) {
func (a *ChainAPI) ChainGetRandomness(ctx context.Context, pts types.TipSetKey, tickets []*types.Ticket, lb int) ([]byte, error) {
return a.Chain.GetRandomness(ctx, pts.Cids(), tickets, int64(lb))
}
@ -36,8 +36,8 @@ func (a *ChainAPI) ChainGetBlock(ctx context.Context, msg cid.Cid) (*types.Block
return a.Chain.GetBlock(msg)
}
func (a *ChainAPI) ChainGetTipSet(ctx context.Context, cids []cid.Cid) (*types.TipSet, error) {
return a.Chain.LoadTipSet(cids)
func (a *ChainAPI) ChainGetTipSet(ctx context.Context, key types.TipSetKey) (*types.TipSet, error) {
return a.Chain.LoadTipSet(key.Cids())
}
func (a *ChainAPI) ChainGetBlockMessages(ctx context.Context, msg cid.Cid) (*api.BlockMessages, error) {

View File

@ -221,7 +221,7 @@ func SealTicketGen(api api.FullNode) storage.TicketFn {
return nil, xerrors.Errorf("getting head ts for SealTicket failed: %w", err)
}
r, err := api.ChainGetRandomness(ctx, ts, nil, build.SealRandomnessLookback)
r, err := api.ChainGetRandomness(ctx, ts.Key(), nil, build.SealRandomnessLookback)
if err != nil {
return nil, xerrors.Errorf("getting randomness for SealTicket failed: %w", err)
}

View File

@ -64,7 +64,7 @@ type storageMinerApi interface {
ChainHead(context.Context) (*types.TipSet, error)
ChainNotify(context.Context) (<-chan []*store.HeadChange, error)
ChainGetRandomness(context.Context, *types.TipSet, []*types.Ticket, int) ([]byte, error)
ChainGetRandomness(context.Context, types.TipSetKey, []*types.Ticket, int) ([]byte, error)
ChainGetTipSetByHeight(context.Context, uint64, *types.TipSet) (*types.TipSet, error)
ChainGetBlockMessages(context.Context, cid.Cid) (*api.BlockMessages, error)

View File

@ -156,7 +156,7 @@ func (p *post) preparePost(ctx context.Context) error {
// Compute how many blocks back we have to look from the given tipset for the PoSt challenge
challengeLookback := int((int64(p.ts.Height()) - int64(p.ppe)) + int64(build.PoStChallangeTime) + int64(build.PoStRandomnessLookback))
r, err := p.m.api.ChainGetRandomness(ctx, p.ts, nil, challengeLookback)
r, err := p.m.api.ChainGetRandomness(ctx, p.ts.Key(), nil, challengeLookback)
if err != nil {
return xerrors.Errorf("failed to get chain randomness for post (ts=%d; ppe=%d): %w", p.ts.Height(), p.ppe, err)
}

View File

@ -145,7 +145,7 @@ func (m *Miner) preCommitted(ctx context.Context, sector SectorInfo) (func(*Sect
log.Infof("precommit for sector %d made it on chain, will start proof computation at height %d", sector.SectorID, randHeight)
err = m.events.ChainAt(func(ctx context.Context, ts *types.TipSet, curH uint64) error {
rand, err := m.api.ChainGetRandomness(ctx, ts, nil, int(ts.Height()-randHeight))
rand, err := m.api.ChainGetRandomness(ctx, ts.Key(), nil, int(ts.Height()-randHeight))
if err != nil {
return xerrors.Errorf("failed to get randomness for computing seal proof: %w", err)
}