dione/lib/drand/drand.go
2020-10-23 16:35:30 +03:00

158 lines
3.6 KiB
Go

package drand
import (
"bytes"
"context"
"fmt"
"sync"
"time"
"github.com/drand/drand/chain"
"github.com/drand/drand/client"
httpClient "github.com/drand/drand/client/http"
libp2pClient "github.com/drand/drand/lp2p/client"
"github.com/drand/kyber"
"github.com/sirupsen/logrus"
pubsub "github.com/libp2p/go-libp2p-pubsub"
"github.com/Secured-Finance/dione/config"
"github.com/Secured-Finance/dione/lib"
)
type DrandRes struct {
// PreviousSig is the previous signature generated
PreviousSig []byte
// Round is the round number this beacon is tied to
Round uint64
// Signature is the BLS deterministic signature over Round || PreviousRand
Signature []byte
// Randomness for specific round generated by Drand
Randomness []byte
}
type Beacon struct {
DrandResponse DrandRes
Error error
}
type DrandBeacon struct {
DrandClient client.Client
PublicKey kyber.Point
Interval time.Duration
drandGenesisTime uint64
cacheLock sync.Mutex
localCache map[uint64]DrandRes
}
func NewDrandBeacon(ps *pubsub.PubSub) (*DrandBeacon, error) {
cfg := config.NewDrandConfig()
drandChain, err := chain.InfoFromJSON(bytes.NewReader([]byte(cfg.ChainInfo)))
if err != nil {
return nil, fmt.Errorf("unable to unmarshal drand chain info: %w", err)
}
var clients []client.Client
for _, url := range cfg.Servers {
client, err := httpClient.NewWithInfo(url, drandChain, nil)
if err != nil {
return nil, fmt.Errorf("could not create http drand client: %w", err)
}
clients = append(clients, client)
}
opts := []client.Option{
client.WithChainInfo(drandChain),
client.WithCacheSize(1024),
client.WithAutoWatch(),
}
if ps != nil {
opts = append(opts, libp2pClient.WithPubsub(ps))
} else {
logrus.Info("Initiated drand with PubSub")
}
drandClient, err := client.Wrap(clients, opts...)
if err != nil {
return nil, fmt.Errorf("Couldn't create Drand clients")
}
db := &DrandBeacon{
DrandClient: drandClient,
localCache: make(map[uint64]DrandRes),
}
db.PublicKey = drandChain.PublicKey
db.Interval = drandChain.Period
db.drandGenesisTime = uint64(drandChain.GenesisTime)
return db, nil
}
func (db *DrandBeacon) Entry(ctx context.Context, round uint64) <-chan Beacon {
out := make(chan Beacon, 1)
if round != 0 {
res := db.getCachedValue(round)
if res != nil {
out <- Beacon{DrandResponse: *res}
close(out)
return out
}
}
go func() {
start := lib.Clock.Now()
logrus.Info("start fetching randomness", "round", round)
resp, err := db.DrandClient.Get(ctx, round)
var res Beacon
if err != nil {
res.Error = fmt.Errorf("drand failed Get request: %w", err)
} else {
res.DrandResponse.Round = resp.Round()
res.DrandResponse.Signature = resp.Signature()
}
logrus.Info("done fetching randomness", "round", round, "took", lib.Clock.Since(start))
out <- res
close(out)
}()
return out
}
func (db *DrandBeacon) cacheValue(res DrandRes) {
db.cacheLock.Lock()
defer db.cacheLock.Unlock()
db.localCache[res.Round] = res
}
func (db *DrandBeacon) getCachedValue(round uint64) *DrandRes {
db.cacheLock.Lock()
defer db.cacheLock.Unlock()
v, ok := db.localCache[round]
if !ok {
return nil
}
return &v
}
func (db *DrandBeacon) VerifyEntry(curr DrandRes, prev DrandRes) error {
if prev.Round == 0 {
return nil
}
if be := db.getCachedValue(curr.Round); be != nil {
return nil
}
b := &chain.Beacon{
PreviousSig: prev.PreviousSig,
Round: curr.Round,
Signature: curr.Signature,
}
err := chain.VerifyBeacon(db.PublicKey, b)
if err == nil {
db.cacheValue(curr)
}
return err
}