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

View File

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

View File

@ -4,6 +4,8 @@ import (
"context"
"sync"
"github.com/Secured-Finance/dione/beacon"
"github.com/libp2p/go-libp2p-core/peer"
"github.com/Secured-Finance/dione/ethclient"
@ -15,10 +17,14 @@ import (
)
type Miner struct {
address peer.ID
ethAddress common.Address
api MinerAPI
mutex sync.Mutex
address peer.ID
ethAddress common.Address
api MinerAPI
mutex sync.Mutex
beacon beacon.BeaconNetworks
ethClient *ethclient.EthereumClient
minerStake types.BigInt
networkStake types.BigInt
}
type MinerAPI interface {
@ -26,84 +32,46 @@ type MinerAPI interface {
// TODO: get miner base based on epoch;
}
type MinerBase struct {
MinerStake types.BigInt
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)
func (m *Miner) UpdateCurrentStakeInfo() error {
mStake, err := m.ethClient.GetMinerStake(m.ethAddress)
if err != nil {
logrus.Warn("Can't get miner stake", err)
return err
}
nStake, err := c.GetTotalStake()
nStake, err := m.ethClient.GetTotalStake()
if err != nil {
logrus.Warn("Can't get miner stake", err)
return err
}
m.MinerStake = *mStake
m.NetworkStake = *nStake
m.minerStake = *mStake
m.networkStake = *nStake
return nil
}
// Start, Stop mining functions
func (m *Miner) MineTask(ctx context.Context, base *MiningBase, mb *MinerBase, ethClient *ethclient.EthereumClient) (*types.DioneTask, error) {
round := base.epoch + base.nullRounds + 1
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]
func (m *Miner) MineTask(ctx context.Context) (*types.DioneTask, error) {
bvals, err := beacon.BeaconEntriesForTask(ctx, m.beacon)
if err != nil {
return nil, xerrors.Errorf("failed to get beacon entries: %w", err)
}
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)
}
ticket, err := m.computeTicket(ctx, &rbase, base, mb)
ticket, err := m.computeTicket(ctx, &rbase)
if err != nil {
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 {
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,
Ticket: ticket,
ElectionProof: winner,
BeaconEntries: mb.BeaconEntries, // TODO decide what we need to do with multiple beacon entries
BeaconEntries: bvals,
// TODO: signature
Epoch: round,
DrandRound: types.DrandRound(rbase.Round),
}, 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()
if err != nil {
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)
if err != nil {
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 {
return nil, err
}

View File

@ -9,7 +9,7 @@ import (
"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()
if err := binary.Write(h, binary.BigEndian, int64(pers)); err != nil {
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
}
func (db *DrandBeacon) MaxBeaconRoundForEpoch(taskEpoch types.TaskEpoch) uint64 {
var latestTs uint64
if taskEpoch == 0 {
latestTs = db.chainGenesisTime
} else {
latestTs = ((uint64(taskEpoch) * db.chainRoundTime) + db.chainGenesisTime) - db.chainRoundTime
func (db *DrandBeacon) LatestBeaconRound() uint64 {
latestDround, err := db.DrandClient.Get(context.TODO(), 0)
if err != nil {
logrus.Errorf("failed to get latest drand round: %w", err)
}
dround := (latestTs - db.drandGenesisTime) / uint64(db.Interval.Seconds())
return dround
return latestDround.Round()
}
var _ beacon.BeaconAPI = (*DrandBeacon)(nil)

View File

@ -3,19 +3,22 @@ package node
import (
"fmt"
"github.com/Secured-Finance/dione/types"
"github.com/Secured-Finance/dione/beacon"
"github.com/Secured-Finance/dione/config"
"github.com/Secured-Finance/dione/drand"
)
// NewBeaconQueue creates a new beacon chain schedule
func (n *Node) NewBeaconQueue() (beacon.BeaconNetworks, error) {
schedule := beacon.BeaconNetworks{}
// NewBeaconClient creates a new beacon chain client
func (n *Node) NewBeaconClient() (beacon.BeaconNetworks, error) {
networks := beacon.BeaconNetworks{}
bc, err := drand.NewDrandBeacon(config.ChainGenesis, config.TaskEpochInterval, n.PubSubRouter.Pubsub)
if err != nil {
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
)
// TaskEpoch represents the timestamp of Task computed by the Dione miner
type TaskEpoch int64
// DrandRound represents the round number in DRAND
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)
}
@ -34,7 +34,7 @@ type DioneTask struct {
ElectionProof *ElectionProof
BeaconEntries []BeaconEntry
Signature *Signature
Epoch TaskEpoch
DrandRound DrandRound
Payload []byte
}