lotus/chain/beacon/drand/drand.go

233 lines
5.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"
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"
lru "github.com/hashicorp/golang-lru"
logging "github.com/ipfs/go-log/v2"
pubsub "github.com/libp2p/go-libp2p-pubsub"
2022-06-14 15:00:51 +00:00
"go.uber.org/zap/zapcore"
"golang.org/x/xerrors"
2020-04-14 03:05:19 +00:00
2020-09-07 03:49:10 +00:00
"github.com/filecoin-project/go-state-types/abi"
2022-06-14 15:00:51 +00:00
"github.com/filecoin-project/go-state-types/network"
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
localCache *lru.Cache
2020-04-09 02:55:17 +00:00
}
// DrandHTTPClient interface overrides the user agent used by drand
type DrandHTTPClient interface {
SetUserAgent(string)
}
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)
}
hc.(DrandHTTPClient).SetUserAgent("drand-client-lotus/" + build.BuildVersion)
clients = append(clients, hc)
}
opts := []dclient.Option{
dclient.WithChainInfo(drandChain),
dclient.WithCacheSize(1024),
dclient.WithLogger(dlogger),
}
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
}
lc, err := lru.New(1024)
if err != nil {
return nil, err
}
db := &DrandBeacon{
client: client,
localCache: lc,
}
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.Debugw("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()
}
log.Debugw("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) {
db.localCache.Add(e.Round, e)
2020-04-14 03:05:19 +00:00
}
func (db *DrandBeacon) getCachedValue(round uint64) *types.BeaconEntry {
v, ok := db.localCache.Get(round)
2020-04-14 03:05:19 +00:00
if !ok {
return nil
}
2021-04-30 07:35:38 +00:00
e, _ := v.(types.BeaconEntry)
return &e
2020-04-14 03:05:19 +00:00
}
func (db *DrandBeacon) VerifyEntry(curr types.BeaconEntry, prev types.BeaconEntry) error {
if prev.Round == 0 {
// TODO handle genesis better
return nil
}
2021-09-02 16:07:23 +00:00
if curr.Round != prev.Round+1 {
return xerrors.Errorf("invalid beacon entry: cur (%d) != prev (%d) + 1", curr.Round, prev.Round)
}
2020-08-11 11:58:27 +00:00
if be := db.getCachedValue(curr.Round); be != nil {
2021-04-30 07:35:38 +00:00
if !bytes.Equal(curr.Data, be.Data) {
return xerrors.New("invalid beacon value, does not match cached good value")
}
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
}
func (db *DrandBeacon) MaxBeaconRoundForEpoch(nv network.Version, filEpoch abi.ChainEpoch) uint64 {
2020-04-14 03:05:19 +00:00
// TODO: sometimes the genesis time for filecoin is zero and this goes negative
latestTs := ((uint64(filEpoch) * db.filRoundTime) + db.filGenTime) - db.filRoundTime
if nv <= network.Version15 {
return db.maxBeaconRoundV1(latestTs)
}
return db.maxBeaconRoundV2(latestTs)
}
func (db *DrandBeacon) maxBeaconRoundV1(latestTs uint64) uint64 {
2020-04-14 03:05:19 +00:00
dround := (latestTs - db.drandGenTime) / uint64(db.interval.Seconds())
return dround
2020-04-09 02:55:17 +00:00
}
func (db *DrandBeacon) maxBeaconRoundV2(latestTs uint64) uint64 {
if latestTs < db.drandGenTime {
return 1
}
fromGenesis := latestTs - db.drandGenTime
// we take the time from genesis divided by the periods in seconds, that
// gives us the number of periods since genesis. We also add +1 because
// round 1 starts at genesis time.
return fromGenesis/uint64(db.interval.Seconds()) + 1
}
2020-04-09 02:55:17 +00:00
var _ beacon.RandomBeacon = (*DrandBeacon)(nil)