lotus/chain/beacon/drand/drand.go

198 lines
4.8 KiB
Go
Raw Normal View History

2020-04-09 02:55:17 +00:00
package drand
import (
"bytes"
2020-04-09 02:55:17 +00:00
"context"
"sync"
2020-04-14 03:05:19 +00:00
"time"
2020-04-09 02:55:17 +00:00
dchain "github.com/drand/drand/chain"
dclient "github.com/drand/drand/client"
hclient "github.com/drand/drand/client/http"
dlog "github.com/drand/drand/log"
gclient "github.com/drand/drand/lp2p/client"
"github.com/drand/kyber"
kzap "github.com/go-kit/kit/log/zap"
"go.uber.org/zap/zapcore"
2020-04-14 03:05:19 +00:00
"golang.org/x/xerrors"
2020-04-09 02:55:17 +00:00
2020-04-14 03:05:19 +00:00
logging "github.com/ipfs/go-log"
pubsub "github.com/libp2p/go-libp2p-pubsub"
2020-04-14 03:05:19 +00:00
"github.com/filecoin-project/specs-actors/actors/abi"
2020-07-10 14:43:14 +00:00
"github.com/filecoin-project/lotus/build"
"github.com/filecoin-project/lotus/chain/beacon"
"github.com/filecoin-project/lotus/chain/types"
2020-06-23 20:23:06 +00:00
"github.com/filecoin-project/lotus/node/modules/dtypes"
2020-04-09 02:55:17 +00:00
)
2020-04-14 03:05:19 +00:00
var log = logging.Logger("drand")
type drandPeer struct {
addr string
tls bool
}
func (dp *drandPeer) Address() string {
return dp.addr
}
func (dp *drandPeer) IsTLS() bool {
return dp.tls
}
2020-06-23 21:51:25 +00:00
// DrandBeacon connects Lotus with a drand network in order to provide
// randomness to the system in a way that's aligned with Filecoin rounds/epochs.
//
// We connect to drand peers via their public HTTP endpoints. The peers are
// enumerated in the drandServers variable.
//
// The root trust for the Drand chain is configured from build.DrandChain.
2020-04-09 02:55:17 +00:00
type DrandBeacon struct {
client dclient.Client
2020-04-14 03:05:19 +00:00
pubkey kyber.Point
2020-04-14 03:05:19 +00:00
// seconds
interval time.Duration
drandGenTime uint64
filGenTime uint64
filRoundTime uint64
2020-04-09 02:55:17 +00:00
cacheLk sync.Mutex
2020-04-14 03:05:19 +00:00
localCache map[uint64]types.BeaconEntry
2020-04-09 02:55:17 +00:00
}
2020-06-23 20:23:06 +00:00
func NewDrandBeacon(genesisTs, interval uint64, ps *pubsub.PubSub, config dtypes.DrandConfig) (*DrandBeacon, error) {
2020-04-14 03:05:19 +00:00
if genesisTs == 0 {
panic("what are you doing this cant be zero")
}
drandChain, err := dchain.InfoFromJSON(bytes.NewReader([]byte(config.ChainInfoJSON)))
if err != nil {
return nil, xerrors.Errorf("unable to unmarshal drand chain info: %w", err)
2020-06-23 17:23:04 +00:00
}
dlogger := dlog.NewKitLoggerFrom(kzap.NewZapSugarLogger(
log.SugaredLogger.Desugar(), zapcore.InfoLevel))
var clients []dclient.Client
2020-06-23 17:23:04 +00:00
for _, url := range config.Servers {
hc, err := hclient.NewWithInfo(url, drandChain, nil)
if err != nil {
return nil, xerrors.Errorf("could not create http drand client: %w", err)
}
clients = append(clients, hc)
}
opts := []dclient.Option{
dclient.WithChainInfo(drandChain),
dclient.WithCacheSize(1024),
dclient.WithLogger(dlogger),
dclient.WithAutoWatch(),
}
if ps != nil {
opts = append(opts, gclient.WithPubsub(ps))
} else {
log.Info("drand beacon without pubsub")
}
client, err := dclient.Wrap(clients, opts...)
2020-04-14 03:05:19 +00:00
if err != nil {
return nil, xerrors.Errorf("creating drand client")
2020-04-14 03:05:19 +00:00
}
db := &DrandBeacon{
client: client,
localCache: make(map[uint64]types.BeaconEntry),
}
2020-04-14 03:05:19 +00:00
db.pubkey = drandChain.PublicKey
db.interval = drandChain.Period
db.drandGenTime = uint64(drandChain.GenesisTime)
2020-04-14 03:05:19 +00:00
db.filRoundTime = interval
db.filGenTime = genesisTs
return db, nil
2020-04-09 02:55:17 +00:00
}
func (db *DrandBeacon) Entry(ctx context.Context, round uint64) <-chan beacon.Response {
2020-04-09 17:13:09 +00:00
out := make(chan beacon.Response, 1)
if round != 0 {
be := db.getCachedValue(round)
if be != nil {
out <- beacon.Response{Entry: *be}
close(out)
return out
}
}
go func() {
2020-07-10 14:43:14 +00:00
start := build.Clock.Now()
log.Infow("start fetching randomness", "round", round)
resp, err := db.client.Get(ctx, round)
var br beacon.Response
if err != nil {
br.Err = xerrors.Errorf("drand failed Get request: %w", err)
} else {
br.Entry.Round = resp.Round()
br.Entry.Data = resp.Signature()
}
2020-07-10 14:43:14 +00:00
log.Infow("done fetching randomness", "round", round, "took", build.Clock.Since(start))
out <- br
close(out)
}()
2020-04-09 17:13:09 +00:00
return out
2020-04-09 02:55:17 +00:00
}
func (db *DrandBeacon) cacheValue(e types.BeaconEntry) {
2020-04-14 03:05:19 +00:00
db.cacheLk.Lock()
defer db.cacheLk.Unlock()
db.localCache[e.Round] = e
2020-04-14 03:05:19 +00:00
}
func (db *DrandBeacon) getCachedValue(round uint64) *types.BeaconEntry {
db.cacheLk.Lock()
defer db.cacheLk.Unlock()
v, ok := db.localCache[round]
if !ok {
return nil
}
return &v
}
func (db *DrandBeacon) VerifyEntry(curr types.BeaconEntry, prev types.BeaconEntry) error {
if prev.Round == 0 {
// TODO handle genesis better
return nil
}
2020-08-11 11:58:27 +00:00
if be := db.getCachedValue(curr.Round); be != nil {
2020-08-11 12:00:23 +00:00
// return no error if the value is in the cache already
return nil
2020-08-11 11:58:27 +00:00
}
b := &dchain.Beacon{
2020-04-30 02:53:24 +00:00
PreviousSig: prev.Data,
Round: curr.Round,
Signature: curr.Data,
}
err := dchain.VerifyBeacon(db.pubkey, b)
if err == nil {
db.cacheValue(curr)
}
return err
2020-04-09 02:55:17 +00:00
}
2020-04-14 03:05:19 +00:00
func (db *DrandBeacon) MaxBeaconRoundForEpoch(filEpoch abi.ChainEpoch, prevEntry types.BeaconEntry) uint64 {
// TODO: sometimes the genesis time for filecoin is zero and this goes negative
latestTs := ((uint64(filEpoch) * db.filRoundTime) + db.filGenTime) - db.filRoundTime
dround := (latestTs - db.drandGenTime) / uint64(db.interval.Seconds())
return dround
2020-04-09 02:55:17 +00:00
}
var _ beacon.RandomBeacon = (*DrandBeacon)(nil)