dione/blockchain/miner.go

200 lines
4.9 KiB
Go

package blockchain
import (
"errors"
"fmt"
"math/big"
"github.com/libp2p/go-libp2p-core/host"
"github.com/asaskevich/EventBus"
"github.com/Secured-Finance/dione/beacon"
"github.com/Secured-Finance/dione/types"
"github.com/Secured-Finance/dione/blockchain/pool"
"github.com/libp2p/go-libp2p-core/crypto"
types2 "github.com/Secured-Finance/dione/blockchain/types"
"github.com/libp2p/go-libp2p-core/peer"
"github.com/Secured-Finance/dione/ethclient"
"github.com/ethereum/go-ethereum/common"
"github.com/sirupsen/logrus"
)
var (
ErrNoTxForBlock = fmt.Errorf("no transactions for including into block")
)
type Miner struct {
bus EventBus.Bus
address peer.ID
ethClient *ethclient.EthereumClient
minerStake *big.Int
networkStake *big.Int
privateKey crypto.PrivKey
mempool *pool.Mempool
latestBlockHeader *types2.BlockHeader
blockchain *BlockChain
}
func NewMiner(
h host.Host,
ethClient *ethclient.EthereumClient,
privateKey crypto.PrivKey,
mempool *pool.Mempool,
bus EventBus.Bus,
) *Miner {
m := &Miner{
address: h.ID(),
ethClient: ethClient,
privateKey: privateKey,
mempool: mempool,
bus: bus,
}
return m
}
func (m *Miner) SetBlockchainInstance(b *BlockChain) {
m.blockchain = b
m.bus.SubscribeAsync("blockchain:latestBlockHeightUpdated", func(block *types2.Block) {
m.latestBlockHeader = block.Header
}, true)
height, _ := m.blockchain.GetLatestBlockHeight()
header, err := m.blockchain.FetchBlockHeaderByHeight(height)
if err != nil {
logrus.WithField("err", err.Error()).Fatal("Failed to initialize miner subsystem")
}
m.latestBlockHeader = header
logrus.Info("Mining subsystem has been initialized!")
}
func (m *Miner) UpdateCurrentStakeInfo() error {
mStake, err := m.ethClient.GetMinerStake(*m.ethClient.GetEthAddress())
if err != nil {
logrus.Warn("Can't get miner stake", err)
return err
}
nStake, err := m.ethClient.GetTotalStake()
if err != nil {
logrus.Warn("Can't get miner stake", err)
return err
}
m.minerStake = mStake
m.networkStake = nStake
return nil
}
func (m *Miner) GetStakeInfo(miner common.Address) (*big.Int, *big.Int, error) {
mStake, err := m.ethClient.GetMinerStake(miner)
if err != nil {
logrus.Warn("Can't get miner stake", err)
return nil, nil, err
}
nStake, err := m.ethClient.GetTotalStake()
if err != nil {
logrus.Warn("Can't get miner stake", err)
return nil, nil, err
}
return mStake, nStake, nil
}
func (m *Miner) MineBlock(randomness []byte, randomnessRound uint64) (*types2.Block, error) {
if m.latestBlockHeader == nil {
return nil, fmt.Errorf("latest block header is null")
}
logrus.WithField("height", m.latestBlockHeader.Height+1).Debug("Trying to mine new block...")
if err := m.UpdateCurrentStakeInfo(); err != nil {
return nil, fmt.Errorf("failed to update miner stake: %w", err)
}
winner, err := isRoundWinner(
m.latestBlockHeader.Height+1,
m.address,
randomness,
randomnessRound,
m.minerStake,
m.networkStake,
m.privateKey,
)
if err != nil {
return nil, fmt.Errorf("failed to check if we winned in next round: %w", err)
}
if winner == nil {
logrus.WithField("height", m.latestBlockHeader.Height+1).Debug("Block is not mined because we are not leader in consensus round")
return nil, nil
}
logrus.WithField("height", m.latestBlockHeader.Height+1).Infof("We have been elected in the current consensus round")
txs := m.mempool.GetTransactionsForNewBlock()
if txs == nil {
return nil, ErrNoTxForBlock // skip new consensus round because there is no transaction for processing
}
newBlock, err := types2.CreateBlock(m.latestBlockHeader, txs, *m.ethClient.GetEthAddress(), m.privateKey, winner)
if err != nil {
return nil, fmt.Errorf("failed to create new block: %w", err)
}
return newBlock, nil
}
func (m *Miner) IsMinerEligibleToProposeBlock(ethAddress common.Address) error {
mStake, err := m.ethClient.GetMinerStake(ethAddress)
if err != nil {
return err
}
if mStake.Cmp(big.NewInt(ethclient.MinMinerStake)) == -1 {
return errors.New("miner doesn't have enough staked tokens")
}
return nil
}
func isRoundWinner(round uint64,
worker peer.ID, randomness []byte, randomnessRound uint64, minerStake, networkStake *big.Int, privKey crypto.PrivKey) (*types.ElectionProof, error) {
buf, err := worker.MarshalBinary()
if err != nil {
return nil, fmt.Errorf("failed to marshal address: %w", err)
}
electionRand, err := beacon.DrawRandomness(randomness, beacon.RandomnessTypeElectionProofProduction, round, buf)
if err != nil {
return nil, fmt.Errorf("failed to draw randomness: %w", err)
}
vrfout, err := beacon.ComputeVRF(privKey, electionRand)
if err != nil {
return nil, fmt.Errorf("failed to compute VRF: %w", err)
}
ep := &types.ElectionProof{VRFProof: vrfout, RandomnessRound: randomnessRound}
j := ep.ComputeWinCount(minerStake, networkStake)
ep.WinCount = j
if j < 1 {
return nil, nil
}
return ep, nil
}