Fix task round/beacon system - remove task round and use latest drand round when creating task

This commit is contained in:
ChronosX88 2020-11-14 04:03:47 +04:00
parent 89f3bcda90
commit e8102dba70
Signed by: ChronosXYZ
GPG Key ID: 085A69A82C8C511A
7 changed files with 106 additions and 143 deletions

View File

@ -17,7 +17,7 @@ type BeaconResult struct {
type BeaconNetworks []BeaconNetwork type BeaconNetworks []BeaconNetwork
func (bn BeaconNetworks) BeaconNetworkForEpoch(e types.TaskEpoch) BeaconAPI { func (bn BeaconNetworks) BeaconNetworkForRound(e types.DrandRound) BeaconAPI {
for i := len(bn) - 1; i >= 0; i-- { for i := len(bn) - 1; i >= 0; i-- {
bp := bn[i] bp := bn[i]
if e >= bp.Start { if e >= bp.Start {
@ -28,7 +28,7 @@ func (bn BeaconNetworks) BeaconNetworkForEpoch(e types.TaskEpoch) BeaconAPI {
} }
type BeaconNetwork struct { type BeaconNetwork struct {
Start types.TaskEpoch Start types.DrandRound
Beacon BeaconAPI Beacon BeaconAPI
} }
@ -39,12 +39,12 @@ type BeaconNetwork struct {
type BeaconAPI interface { type BeaconAPI interface {
Entry(context.Context, uint64) <-chan BeaconResult Entry(context.Context, uint64) <-chan BeaconResult
VerifyEntry(types.BeaconEntry, types.BeaconEntry) error VerifyEntry(types.BeaconEntry, types.BeaconEntry) error
MaxBeaconRoundForEpoch(types.TaskEpoch) uint64 LatestBeaconRound() uint64
} }
func ValidateTaskBeacons(beaconNetworks BeaconNetworks, t *types.DioneTask, prevEpoch types.TaskEpoch, prevEntry types.BeaconEntry) error { func ValidateTaskBeacons(beaconNetworks BeaconNetworks, t *types.DioneTask, prevEpoch types.DrandRound, prevEntry types.BeaconEntry) error {
parentBeacon := beaconNetworks.BeaconNetworkForEpoch(prevEpoch) parentBeacon := beaconNetworks.BeaconNetworkForRound(prevEpoch)
currBeacon := beaconNetworks.BeaconNetworkForEpoch(t.Epoch) currBeacon := beaconNetworks.BeaconNetworkForRound(t.DrandRound)
if parentBeacon != currBeacon { if parentBeacon != currBeacon {
if len(t.BeaconEntries) != 2 { if len(t.BeaconEntries) != 2 {
return fmt.Errorf("expected two beacon entries at beacon fork, got %d", len(t.BeaconEntries)) return fmt.Errorf("expected two beacon entries at beacon fork, got %d", len(t.BeaconEntries))
@ -58,9 +58,8 @@ func ValidateTaskBeacons(beaconNetworks BeaconNetworks, t *types.DioneTask, prev
} }
// TODO: fork logic // TODO: fork logic
bNetwork := beaconNetworks.BeaconNetworkForEpoch(t.Epoch) bNetwork := beaconNetworks.BeaconNetworkForRound(t.DrandRound)
maxRound := bNetwork.MaxBeaconRoundForEpoch(t.Epoch) if uint64(t.DrandRound) == prevEntry.Round {
if maxRound == prevEntry.Round {
if len(t.BeaconEntries) != 0 { if len(t.BeaconEntries) != 0 {
return fmt.Errorf("expected not to have any beacon entries in this task, got %d", len(t.BeaconEntries)) return fmt.Errorf("expected not to have any beacon entries in this task, got %d", len(t.BeaconEntries))
} }
@ -72,8 +71,8 @@ func ValidateTaskBeacons(beaconNetworks BeaconNetworks, t *types.DioneTask, prev
} }
last := t.BeaconEntries[len(t.BeaconEntries)-1] last := t.BeaconEntries[len(t.BeaconEntries)-1]
if last.Round != maxRound { if last.Round != uint64(t.DrandRound) {
return fmt.Errorf("expected final beacon entry in task to be at round %d, got %d", maxRound, last.Round) return fmt.Errorf("expected final beacon entry in task to be at round %d, got %d", uint64(t.DrandRound), last.Round)
} }
for i, e := range t.BeaconEntries { for i, e := range t.BeaconEntries {
@ -86,61 +85,58 @@ func ValidateTaskBeacons(beaconNetworks BeaconNetworks, t *types.DioneTask, prev
return nil return nil
} }
func BeaconEntriesForTask(ctx context.Context, beaconNetworks BeaconNetworks, epoch types.TaskEpoch, prevEpoch types.TaskEpoch, prev types.BeaconEntry) ([]types.BeaconEntry, error) { func BeaconEntriesForTask(ctx context.Context, beaconNetworks BeaconNetworks) ([]types.BeaconEntry, error) {
prevBeacon := beaconNetworks.BeaconNetworkForEpoch(prevEpoch) beacon := beaconNetworks.BeaconNetworkForRound(0)
currBeacon := beaconNetworks.BeaconNetworkForEpoch(epoch) round := beacon.LatestBeaconRound()
if prevBeacon != currBeacon {
// Fork logic //prevBeacon := beaconNetworks.BeaconNetworkForRound(prevRound)
round := currBeacon.MaxBeaconRoundForEpoch(epoch) //currBeacon := beaconNetworks.BeaconNetworkForRound(round)
//if prevBeacon != currBeacon {
// // Fork logic
// round := currBeacon.LatestBeaconRound()
// out := make([]types.BeaconEntry, 2)
// rch := currBeacon.Entry(ctx, round-1)
// res := <-rch
// if res.Err != nil {
// return nil, fmt.Errorf("getting entry %d returned error: %w", round-1, res.Err)
// }
// out[0] = res.Entry
// rch = currBeacon.Entry(ctx, round)
// res = <-rch
// if res.Err != nil {
// return nil, fmt.Errorf("getting entry %d returned error: %w", round, res.Err)
// }
// out[1] = res.Entry
// return out, nil
//}
start := lib.Clock.Now()
//if round == prev.Round {
// return nil, nil
//}
//
//// TODO: this is a sketchy way to handle the genesis block not having a beacon entry
//if prev.Round == 0 {
// prev.Round = round - 1
//}
out := make([]types.BeaconEntry, 2) out := make([]types.BeaconEntry, 2)
rch := currBeacon.Entry(ctx, round-1) rch := beacon.Entry(ctx, round-1)
res := <-rch res := <-rch
if res.Err != nil { if res.Err != nil {
return nil, fmt.Errorf("getting entry %d returned error: %w", round-1, res.Err) return nil, fmt.Errorf("getting entry %d returned error: %w", round-1, res.Err)
} }
out[0] = res.Entry out[0] = res.Entry
rch = currBeacon.Entry(ctx, round) rch = beacon.Entry(ctx, round)
res = <-rch res = <-rch
if res.Err != nil { if res.Err != nil {
return nil, fmt.Errorf("getting entry %d returned error: %w", round, res.Err) return nil, fmt.Errorf("getting entry %d returned error: %w", round, res.Err)
} }
out[1] = res.Entry out[1] = res.Entry
return out, nil
}
beacon := beaconNetworks.BeaconNetworkForEpoch(epoch)
start := lib.Clock.Now()
maxRound := beacon.MaxBeaconRoundForEpoch(epoch)
if maxRound == prev.Round {
return nil, nil
}
// TODO: this is a sketchy way to handle the genesis block not having a beacon entry
if prev.Round == 0 {
prev.Round = maxRound - 1
}
cur := maxRound
var out []types.BeaconEntry
for cur > prev.Round {
rch := beacon.Entry(ctx, cur)
select {
case resp := <-rch:
if resp.Err != nil {
return nil, fmt.Errorf("beacon entry request returned error: %w", resp.Err)
}
out = append(out, resp.Entry)
cur = resp.Entry.Round - 1
case <-ctx.Done():
return nil, fmt.Errorf("context timed out waiting on beacon entry to come back for epoch %d: %w", epoch, ctx.Err())
}
}
logrus.Debug("fetching beacon entries", "took", lib.Clock.Since(start), "numEntries", len(out)) logrus.Debug("fetching beacon entries", "took", lib.Clock.Since(start), "numEntries", len(out))
reverse(out) //reverse(out)
return out, nil return out, nil
} }

View File

@ -40,8 +40,8 @@ func VerifyVRF(ctx context.Context, worker peer.ID, vrfBase, vrfproof []byte) er
return nil return nil
} }
func IsRoundWinner(ctx context.Context, round types.TaskEpoch, func IsRoundWinner(ctx context.Context, round types.DrandRound,
worker peer.ID, brand types.BeaconEntry, mb *MinerBase, a MinerAPI) (*types.ElectionProof, error) { worker peer.ID, brand types.BeaconEntry, minerStake, networkStake types.BigInt, a MinerAPI) (*types.ElectionProof, error) {
buf, err := worker.MarshalBinary() buf, err := worker.MarshalBinary()
if err != nil { if err != nil {
@ -53,13 +53,13 @@ func IsRoundWinner(ctx context.Context, round types.TaskEpoch,
return nil, xerrors.Errorf("failed to draw randomness: %w", err) return nil, xerrors.Errorf("failed to draw randomness: %w", err)
} }
vrfout, err := ComputeVRF(ctx, a.WalletSign, mb.WorkerKey, electionRand) vrfout, err := ComputeVRF(ctx, a.WalletSign, worker, electionRand)
if err != nil { if err != nil {
return nil, xerrors.Errorf("failed to compute VRF: %w", err) return nil, xerrors.Errorf("failed to compute VRF: %w", err)
} }
ep := &types.ElectionProof{VRFProof: vrfout} ep := &types.ElectionProof{VRFProof: vrfout}
j := ep.ComputeWinCount(mb.MinerStake, mb.NetworkStake) j := ep.ComputeWinCount(minerStake, networkStake)
ep.WinCount = j ep.WinCount = j
if j < 1 { if j < 1 {
return nil, nil return nil, nil

View File

@ -4,6 +4,8 @@ import (
"context" "context"
"sync" "sync"
"github.com/Secured-Finance/dione/beacon"
"github.com/libp2p/go-libp2p-core/peer" "github.com/libp2p/go-libp2p-core/peer"
"github.com/Secured-Finance/dione/ethclient" "github.com/Secured-Finance/dione/ethclient"
@ -19,6 +21,10 @@ type Miner struct {
ethAddress common.Address ethAddress common.Address
api MinerAPI api MinerAPI
mutex sync.Mutex mutex sync.Mutex
beacon beacon.BeaconNetworks
ethClient *ethclient.EthereumClient
minerStake types.BigInt
networkStake types.BigInt
} }
type MinerAPI interface { type MinerAPI interface {
@ -26,84 +32,46 @@ type MinerAPI interface {
// TODO: get miner base based on epoch; // TODO: get miner base based on epoch;
} }
type MinerBase struct { func (m *Miner) UpdateCurrentStakeInfo() error {
MinerStake types.BigInt mStake, err := m.ethClient.GetMinerStake(m.ethAddress)
NetworkStake types.BigInt
WorkerKey peer.ID
EthWallet common.Address
PrevBeaconEntry types.BeaconEntry
BeaconEntries []types.BeaconEntry
NullRounds types.TaskEpoch
}
type MiningBase struct {
epoch types.TaskEpoch
nullRounds types.TaskEpoch // currently not used
}
func NewMinerBase(minerStake, networkStake types.BigInt, minerAddress peer.ID,
minerEthWallet common.Address, prev types.BeaconEntry, entries []types.BeaconEntry) *MinerBase {
return &MinerBase{
MinerStake: minerStake,
NetworkStake: networkStake,
WorkerKey: minerAddress,
EthWallet: minerEthWallet,
PrevBeaconEntry: prev,
BeaconEntries: entries,
}
}
func NewMiningBase() *MiningBase {
return &MiningBase{
nullRounds: 0,
}
}
func (m *MinerBase) UpdateCurrentStakeInfo(c *ethclient.EthereumClient, miner common.Address) error {
mStake, err := c.GetMinerStake(miner)
if err != nil { if err != nil {
logrus.Warn("Can't get miner stake", err) logrus.Warn("Can't get miner stake", err)
return err return err
} }
nStake, err := c.GetTotalStake() nStake, err := m.ethClient.GetTotalStake()
if err != nil { if err != nil {
logrus.Warn("Can't get miner stake", err) logrus.Warn("Can't get miner stake", err)
return err return err
} }
m.MinerStake = *mStake m.minerStake = *mStake
m.NetworkStake = *nStake m.networkStake = *nStake
return nil return nil
} }
// Start, Stop mining functions func (m *Miner) MineTask(ctx context.Context) (*types.DioneTask, error) {
bvals, err := beacon.BeaconEntriesForTask(ctx, m.beacon)
func (m *Miner) MineTask(ctx context.Context, base *MiningBase, mb *MinerBase, ethClient *ethclient.EthereumClient) (*types.DioneTask, error) { if err != nil {
round := base.epoch + base.nullRounds + 1 return nil, xerrors.Errorf("failed to get beacon entries: %w", err)
logrus.Debug("attempting to mine the task at epoch: ", round)
prevEntry := mb.PrevBeaconEntry
bvals := mb.BeaconEntries
rbase := prevEntry
if len(bvals) > 0 {
rbase = bvals[len(bvals)-1]
} }
logrus.Debug("attempting to mine the task at epoch: ", bvals[1].Round)
if err := mb.UpdateCurrentStakeInfo(ethClient, m.ethAddress); err != nil { rbase := bvals[1]
if err := m.UpdateCurrentStakeInfo(); err != nil {
return nil, xerrors.Errorf("failed to update miner stake: %w", err) return nil, xerrors.Errorf("failed to update miner stake: %w", err)
} }
ticket, err := m.computeTicket(ctx, &rbase, base, mb) ticket, err := m.computeTicket(ctx, &rbase)
if err != nil { if err != nil {
return nil, xerrors.Errorf("scratching ticket failed: %w", err) return nil, xerrors.Errorf("scratching ticket failed: %w", err)
} }
winner, err := IsRoundWinner(ctx, round, m.address, rbase, mb, m.api) winner, err := IsRoundWinner(ctx, types.DrandRound(rbase.Round), m.address, rbase, m.minerStake, m.networkStake, m.api)
if err != nil { if err != nil {
return nil, xerrors.Errorf("failed to check if we win next round: %w", err) return nil, xerrors.Errorf("failed to check if we win next round: %w", err)
} }
@ -115,26 +83,26 @@ func (m *Miner) MineTask(ctx context.Context, base *MiningBase, mb *MinerBase, e
Miner: m.address, Miner: m.address,
Ticket: ticket, Ticket: ticket,
ElectionProof: winner, ElectionProof: winner,
BeaconEntries: mb.BeaconEntries, // TODO decide what we need to do with multiple beacon entries BeaconEntries: bvals,
// TODO: signature // TODO: signature
Epoch: round, DrandRound: types.DrandRound(rbase.Round),
}, nil }, nil
} }
func (m *Miner) computeTicket(ctx context.Context, brand *types.BeaconEntry, base *MiningBase, mb *MinerBase) (*types.Ticket, error) { func (m *Miner) computeTicket(ctx context.Context, brand *types.BeaconEntry) (*types.Ticket, error) {
buf, err := m.address.MarshalBinary() buf, err := m.address.MarshalBinary()
if err != nil { if err != nil {
return nil, xerrors.Errorf("failed to marshal address: %w", err) return nil, xerrors.Errorf("failed to marshal address: %w", err)
} }
round := base.epoch + base.nullRounds + 1 round := types.DrandRound(brand.Round)
input, err := DrawRandomness(brand.Data, crypto.DomainSeparationTag_TicketProduction, round-types.TicketRandomnessLookback, buf) input, err := DrawRandomness(brand.Data, crypto.DomainSeparationTag_TicketProduction, round-types.TicketRandomnessLookback, buf)
if err != nil { if err != nil {
return nil, err return nil, err
} }
vrfOut, err := ComputeVRF(ctx, m.api.WalletSign, mb.WorkerKey, input) vrfOut, err := ComputeVRF(ctx, m.api.WalletSign, m.address, input)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -9,7 +9,7 @@ import (
"golang.org/x/xerrors" "golang.org/x/xerrors"
) )
func DrawRandomness(rbase []byte, pers crypto.DomainSeparationTag, round types.TaskEpoch, entropy []byte) ([]byte, error) { func DrawRandomness(rbase []byte, pers crypto.DomainSeparationTag, round types.DrandRound, entropy []byte) ([]byte, error) {
h := blake2b.New256() h := blake2b.New256()
if err := binary.Write(h, binary.BigEndian, int64(pers)); err != nil { if err := binary.Write(h, binary.BigEndian, int64(pers)); err != nil {
return nil, xerrors.Errorf("deriving randomness: %w", err) return nil, xerrors.Errorf("deriving randomness: %w", err)

View File

@ -159,16 +159,12 @@ func (db *DrandBeacon) VerifyEntry(curr, prev types.BeaconEntry) error {
return err return err
} }
func (db *DrandBeacon) MaxBeaconRoundForEpoch(taskEpoch types.TaskEpoch) uint64 { func (db *DrandBeacon) LatestBeaconRound() uint64 {
var latestTs uint64 latestDround, err := db.DrandClient.Get(context.TODO(), 0)
if taskEpoch == 0 { if err != nil {
latestTs = db.chainGenesisTime logrus.Errorf("failed to get latest drand round: %w", err)
} else {
latestTs = ((uint64(taskEpoch) * db.chainRoundTime) + db.chainGenesisTime) - db.chainRoundTime
} }
return latestDround.Round()
dround := (latestTs - db.drandGenesisTime) / uint64(db.Interval.Seconds())
return dround
} }
var _ beacon.BeaconAPI = (*DrandBeacon)(nil) var _ beacon.BeaconAPI = (*DrandBeacon)(nil)

View File

@ -3,19 +3,22 @@ package node
import ( import (
"fmt" "fmt"
"github.com/Secured-Finance/dione/types"
"github.com/Secured-Finance/dione/beacon" "github.com/Secured-Finance/dione/beacon"
"github.com/Secured-Finance/dione/config" "github.com/Secured-Finance/dione/config"
"github.com/Secured-Finance/dione/drand" "github.com/Secured-Finance/dione/drand"
) )
// NewBeaconQueue creates a new beacon chain schedule // NewBeaconClient creates a new beacon chain client
func (n *Node) NewBeaconQueue() (beacon.BeaconNetworks, error) { func (n *Node) NewBeaconClient() (beacon.BeaconNetworks, error) {
schedule := beacon.BeaconNetworks{} networks := beacon.BeaconNetworks{}
bc, err := drand.NewDrandBeacon(config.ChainGenesis, config.TaskEpochInterval, n.PubSubRouter.Pubsub) bc, err := drand.NewDrandBeacon(config.ChainGenesis, config.TaskEpochInterval, n.PubSubRouter.Pubsub)
if err != nil { if err != nil {
return nil, fmt.Errorf("creating drand beacon: %w", err) return nil, fmt.Errorf("creating drand beacon: %w", err)
} }
schedule = append(schedule, beacon.BeaconNetwork{Start: 0, Beacon: bc}) networks = append(networks, beacon.BeaconNetwork{Start: types.DrandRound(config.ChainGenesis), Beacon: bc})
// NOTE: currently we use only one network
return schedule, nil return networks, nil
} }

View File

@ -16,12 +16,12 @@ const (
SolanaTaskType SolanaTaskType
) )
// TaskEpoch represents the timestamp of Task computed by the Dione miner // DrandRound represents the round number in DRAND
type TaskEpoch int64 type DrandRound int64
const TicketRandomnessLookback = TaskEpoch(1) const TicketRandomnessLookback = DrandRound(1)
func (e TaskEpoch) String() string { func (e DrandRound) String() string {
return strconv.FormatInt(int64(e), 10) return strconv.FormatInt(int64(e), 10)
} }
@ -34,7 +34,7 @@ type DioneTask struct {
ElectionProof *ElectionProof ElectionProof *ElectionProof
BeaconEntries []BeaconEntry BeaconEntries []BeaconEntry
Signature *Signature Signature *Signature
Epoch TaskEpoch DrandRound DrandRound
Payload []byte Payload []byte
} }