diff --git a/beacon/beacon.go b/beacon/beacon.go index 02336ec..5852a3f 100644 --- a/beacon/beacon.go +++ b/beacon/beacon.go @@ -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 } diff --git a/consensus/leader.go b/consensus/leader.go index 13091dd..7d06d30 100644 --- a/consensus/leader.go +++ b/consensus/leader.go @@ -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 diff --git a/consensus/miner.go b/consensus/miner.go index 430c97c..d9e3267 100644 --- a/consensus/miner.go +++ b/consensus/miner.go @@ -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 } diff --git a/consensus/randomness.go b/consensus/randomness.go index 91891e0..3353b15 100644 --- a/consensus/randomness.go +++ b/consensus/randomness.go @@ -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) diff --git a/drand/drand.go b/drand/drand.go index 21665ea..5ba6bc6 100644 --- a/drand/drand.go +++ b/drand/drand.go @@ -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) diff --git a/node/beacon.go b/node/beacon.go index 79c3196..978f7dd 100644 --- a/node/beacon.go +++ b/node/beacon.go @@ -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 } diff --git a/types/task.go b/types/task.go index 721c5fb..f419219 100644 --- a/types/task.go +++ b/types/task.go @@ -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 }