lotus/chain/beacon/beacon.go

148 lines
3.9 KiB
Go
Raw Normal View History

package beacon
import (
"bytes"
"context"
"encoding/binary"
"time"
"github.com/filecoin-project/lotus/chain/types"
"github.com/filecoin-project/specs-actors/actors/abi"
logging "github.com/ipfs/go-log"
"golang.org/x/xerrors"
"github.com/minio/blake2b-simd"
)
var log = logging.Logger("beacon")
type Response struct {
Entry types.BeaconEntry
Err error
}
type DrandBeacon interface {
2020-04-08 15:11:42 +00:00
//RoundTime() uint64
//StartTime() uint64
LastEntry() (types.BeaconEntry, error)
Entry(context.Context, uint64) <-chan Response
2020-04-08 15:11:42 +00:00
VerifyEntry(types.BeaconEntry, types.BeaconEntry) error
2020-04-08 17:49:50 +00:00
MaxBeaconRoundForEpoch(abi.ChainEpoch, types.BeaconEntry) uint64
IsEntryForEpoch(e types.BeaconEntry, epoch abi.ChainEpoch, nulls int) (bool, error)
}
2020-04-08 15:11:42 +00:00
func ValidateBlockValues(b DrandBeacon, h *types.BlockHeader, prevEntry types.BeaconEntry) error {
2020-04-08 17:49:50 +00:00
maxRound := b.MaxBeaconRoundForEpoch(h.Height, prevEntry)
if maxRound == prevEntry.Round {
if len(h.BeaconEntries) != 0 {
return xerrors.Errorf("expected not to have any beacon entries in this block, got %d", len(h.BeaconEntries))
}
return nil
2020-04-08 15:11:42 +00:00
}
2020-04-08 17:49:50 +00:00
last := h.BeaconEntries[len(h.BeaconEntries)-1]
if last.Round != maxRound {
return xerrors.Errorf("expected final beacon entry in block to be at round %d, got %d", maxRound, last.Round)
}
2020-04-08 17:49:50 +00:00
for i, e := range h.BeaconEntries {
2020-04-08 15:11:42 +00:00
if err := b.VerifyEntry(e, prevEntry); err != nil {
return xerrors.Errorf("beacon entry %d was invalid: %w", i, err)
}
prevEntry = e
}
return nil
}
2020-04-08 15:11:42 +00:00
func BeaconEntriesForBlock(ctx context.Context, beacon DrandBeacon, round abi.ChainEpoch, prev types.BeaconEntry) ([]types.BeaconEntry, error) {
start := time.Now()
2020-04-08 17:49:50 +00:00
maxRound := beacon.MaxBeaconRoundForEpoch(round, prev)
if maxRound == prev.Round {
return nil, nil
}
cur := maxRound
var out []types.BeaconEntry
2020-04-08 17:49:50 +00:00
for cur > prev.Round {
rch := beacon.Entry(ctx, cur)
select {
case resp := <-rch:
if resp.Err != nil {
return nil, xerrors.Errorf("beacon entry request returned error: %w", resp.Err)
}
out = append(out, resp.Entry)
2020-04-08 17:49:50 +00:00
cur = resp.Entry.Round - 1
case <-ctx.Done():
return nil, xerrors.Errorf("context timed out waiting on beacon entry to come back for round %d: %w", round, ctx.Err())
}
}
log.Debugw("fetching beacon entries", "took", time.Since(start), "numEntries", len(out))
2020-04-08 17:49:50 +00:00
reverse(out)
return out, nil
}
2020-04-08 17:49:50 +00:00
func reverse(arr []types.BeaconEntry) {
for i := 0; i < len(arr)/2; i++ {
arr[i], arr[len(arr)-(1+i)] = arr[len(arr)-(1+i)], arr[i]
}
}
2020-04-08 15:11:42 +00:00
// Mock beacon assumes that filecoin rounds are 1:1 mapped with the beacon rounds
type mockBeacon struct {
interval time.Duration
}
func NewMockBeacon(interval time.Duration) *mockBeacon {
mb := &mockBeacon{interval: interval}
return mb
}
func (mb *mockBeacon) RoundTime() time.Duration {
return mb.interval
}
func (mb *mockBeacon) LastEntry() (types.BeaconEntry, error) {
panic("NYI")
}
func (mb *mockBeacon) entryForIndex(index uint64) types.BeaconEntry {
buf := make([]byte, 8)
binary.BigEndian.PutUint64(buf, index)
rval := blake2b.Sum256(buf)
return types.BeaconEntry{
2020-04-08 15:11:42 +00:00
Round: index,
Data: rval[:],
}
}
func (mb *mockBeacon) Entry(ctx context.Context, index uint64) <-chan Response {
e := mb.entryForIndex(index)
out := make(chan Response, 1)
out <- Response{Entry: e}
return out
}
2020-04-08 15:11:42 +00:00
func (mb *mockBeacon) VerifyEntry(from types.BeaconEntry, to types.BeaconEntry) error {
// TODO: cache this, especially for bls
2020-04-08 15:11:42 +00:00
oe := mb.entryForIndex(from.Round)
if !bytes.Equal(from.Data, oe.Data) {
return xerrors.Errorf("mock beacon entry was invalid!")
}
return nil
}
func (mb *mockBeacon) IsEntryForEpoch(e types.BeaconEntry, epoch abi.ChainEpoch, nulls int) (bool, error) {
2020-04-08 15:11:42 +00:00
return int64(e.Round) <= int64(epoch) && int64(epoch)-int64(nulls) >= int64(e.Round), nil
}
2020-04-08 17:49:50 +00:00
func (mb *mockBeacon) MaxBeaconRoundForEpoch(epoch abi.ChainEpoch, prevEntry types.BeaconEntry) uint64 {
return uint64(epoch)
}
var _ DrandBeacon = (*mockBeacon)(nil)