Implement consensus state change watching to fix stage race conditions

This commit is contained in:
ChronosX88 2021-07-27 00:18:16 +03:00
parent 430a994a76
commit d179ffcd76
Signed by: ChronosXYZ
GPG Key ID: 085A69A82C8C511A
13 changed files with 322 additions and 247 deletions

View File

@ -181,10 +181,6 @@ func (bc *BlockChain) StoreBlock(block *types2.Block) error {
if err = bc.setLatestBlockHeight(block.Header.Height); err != nil { if err = bc.setLatestBlockHeight(block.Header.Height); err != nil {
return err return err
} }
} else if block.Header.Height > height {
if err = bc.setLatestBlockHeight(block.Header.Height); err != nil {
return err
}
bc.bus.Publish("blockchain:latestBlockHeightUpdated", block) bc.bus.Publish("blockchain:latestBlockHeightUpdated", block)
} }
bc.bus.Publish("blockchain:blockCommitted", block) bc.bus.Publish("blockchain:blockCommitted", block)

View File

@ -4,7 +4,10 @@ import (
"errors" "errors"
"fmt" "fmt"
"math/big" "math/big"
"sync"
"github.com/libp2p/go-libp2p-core/host"
"github.com/asaskevich/EventBus"
"github.com/Secured-Finance/dione/beacon" "github.com/Secured-Finance/dione/beacon"
"github.com/Secured-Finance/dione/types" "github.com/Secured-Finance/dione/types"
@ -27,34 +30,54 @@ var (
) )
type Miner struct { type Miner struct {
address peer.ID bus EventBus.Bus
ethAddress common.Address address peer.ID
mutex sync.Mutex ethClient *ethclient.EthereumClient
ethClient *ethclient.EthereumClient minerStake *big.Int
minerStake *big.Int networkStake *big.Int
networkStake *big.Int privateKey crypto.PrivKey
privateKey crypto.PrivKey mempool *pool.Mempool
mempool *pool.Mempool latestBlockHeader *types2.BlockHeader
blockchain *BlockChain
} }
func NewMiner( func NewMiner(
address peer.ID, h host.Host,
ethAddress common.Address,
ethClient *ethclient.EthereumClient, ethClient *ethclient.EthereumClient,
privateKey crypto.PrivKey, privateKey crypto.PrivKey,
mempool *pool.Mempool, mempool *pool.Mempool,
bus EventBus.Bus,
) *Miner { ) *Miner {
return &Miner{ m := &Miner{
address: address, address: h.ID(),
ethAddress: ethAddress,
ethClient: ethClient, ethClient: ethClient,
privateKey: privateKey, privateKey: privateKey,
mempool: mempool, 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 { func (m *Miner) UpdateCurrentStakeInfo() error {
mStake, err := m.ethClient.GetMinerStake(m.ethAddress) mStake, err := m.ethClient.GetMinerStake(*m.ethClient.GetEthAddress())
if err != nil { if err != nil {
logrus.Warn("Can't get miner stake", err) logrus.Warn("Can't get miner stake", err)
@ -92,15 +115,19 @@ func (m *Miner) GetStakeInfo(miner common.Address) (*big.Int, *big.Int, error) {
return mStake, nStake, nil return mStake, nStake, nil
} }
func (m *Miner) MineBlock(randomness []byte, randomnessRound uint64, lastBlockHeader *types2.BlockHeader) (*types2.Block, error) { func (m *Miner) MineBlock(randomness []byte, randomnessRound uint64) (*types2.Block, error) {
logrus.WithField("height", lastBlockHeader.Height+1).Debug("Trying to mine new block...") 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 { if err := m.UpdateCurrentStakeInfo(); err != nil {
return nil, fmt.Errorf("failed to update miner stake: %w", err) return nil, fmt.Errorf("failed to update miner stake: %w", err)
} }
winner, err := isRoundWinner( winner, err := isRoundWinner(
lastBlockHeader.Height+1, m.latestBlockHeader.Height+1,
m.address, m.address,
randomness, randomness,
randomnessRound, randomnessRound,
@ -113,16 +140,18 @@ func (m *Miner) MineBlock(randomness []byte, randomnessRound uint64, lastBlockHe
} }
if winner == nil { if winner == nil {
logrus.WithField("height", lastBlockHeader.Height+1).Debug("Block is not mined because we are not leader in consensus round") logrus.WithField("height", m.latestBlockHeader.Height+1).Debug("Block is not mined because we are not leader in consensus round")
return nil, nil return nil, nil
} }
logrus.WithField("height", m.latestBlockHeader.Height+1).Infof("We have been elected in the current consensus round")
txs := m.mempool.GetTransactionsForNewBlock() txs := m.mempool.GetTransactionsForNewBlock()
if txs == nil { if txs == nil {
return nil, ErrNoTxForBlock // skip new consensus round because there is no transaction for processing return nil, ErrNoTxForBlock // skip new consensus round because there is no transaction for processing
} }
newBlock, err := types2.CreateBlock(lastBlockHeader, txs, m.ethAddress, m.privateKey, winner) newBlock, err := types2.CreateBlock(m.latestBlockHeader, txs, *m.ethClient.GetEthAddress(), m.privateKey, winner)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to create new block: %w", err) return nil, fmt.Errorf("failed to create new block: %w", err)
} }

View File

@ -1,101 +0,0 @@
package pool
import (
"bytes"
"encoding/hex"
"fmt"
"time"
"github.com/asaskevich/EventBus"
"github.com/sirupsen/logrus"
"github.com/Secured-Finance/dione/blockchain/types"
"github.com/Secured-Finance/dione/cache"
)
// BlockPool is pool for blocks that isn't not validated or committed yet
type BlockPool struct {
mempool *Mempool
knownBlocks cache.Cache
acceptedBlocks cache.Cache
bus EventBus.Bus
}
func NewBlockPool(mp *Mempool, bus EventBus.Bus) (*BlockPool, error) {
bp := &BlockPool{
acceptedBlocks: cache.NewInMemoryCache(), // here we need to use separate cache
knownBlocks: cache.NewInMemoryCache(),
mempool: mp,
bus: bus,
}
return bp, nil
}
func (bp *BlockPool) AddBlock(block *types.Block) error {
err := bp.knownBlocks.StoreWithTTL(hex.EncodeToString(block.Header.Hash), block, 10*time.Minute)
if err != nil {
return err
}
logrus.WithField("hash", fmt.Sprintf("%x", block.Header.Hash)).Debug("New block discovered")
bp.bus.Publish("blockpool:knownBlockAdded", block)
return nil
}
func (bp *BlockPool) GetBlock(blockhash []byte) (*types.Block, error) {
var block types.Block
err := bp.knownBlocks.Get(hex.EncodeToString(blockhash), &block)
return &block, err
}
// PruneBlocks cleans known blocks list. It is called when new consensus round starts.
func (bp *BlockPool) PruneBlocks() {
for k := range bp.knownBlocks.Items() {
bp.knownBlocks.Delete(k)
}
bp.bus.Publish("blockpool:pruned")
}
func (bp *BlockPool) AddAcceptedBlock(block *types.Block) error {
err := bp.acceptedBlocks.Store(hex.EncodeToString(block.Header.Hash), block)
if err != nil {
return err
}
bp.bus.Publish("blockpool:acceptedBlockAdded", block)
return nil
}
func (bp *BlockPool) GetAllAcceptedBlocks() []*types.Block {
var blocks []*types.Block
for _, v := range bp.acceptedBlocks.Items() {
blocks = append(blocks, v.(*types.Block))
}
return blocks
}
// PruneAcceptedBlocks cleans accepted blocks list. It is called when new consensus round starts.
func (bp *BlockPool) PruneAcceptedBlocks(committedBlock *types.Block) {
for k, v := range bp.acceptedBlocks.Items() {
block := v.(*types.Block)
for _, v := range block.Data {
if !containsTx(committedBlock.Data, v) {
v.MerkleProof = nil
err := bp.mempool.StoreTx(v) // return transactions back to mempool
if err != nil {
logrus.Error(err)
}
}
}
bp.acceptedBlocks.Delete(k)
}
}
func containsTx(s []*types.Transaction, e *types.Transaction) bool {
for _, a := range s {
if bytes.Equal(a.Hash, e.Hash) {
return true
}
}
return false
}

View File

@ -98,21 +98,14 @@ func (sm *syncManager) doInitialBlockPoolSync() error {
return nil return nil
} }
ourLastHeight, err := sm.blockpool.GetLatestBlockHeight() ourLastHeight, _ := sm.blockpool.GetLatestBlockHeight()
if err == blockchain.ErrLatestHeightNil {
gBlock := types2.GenesisBlock()
err = sm.blockpool.StoreBlock(gBlock) // commit genesis block
if err != nil {
return err
}
}
if sm.bootstrapPeer == "" { if sm.bootstrapPeer == "" {
return nil // FIXME return nil // FIXME
} }
var reply wire.LastBlockHeightReply var reply wire.LastBlockHeightReply
err = sm.rpcClient.Call(sm.bootstrapPeer, "NetworkService", "LastBlockHeight", nil, &reply) err := sm.rpcClient.Call(sm.bootstrapPeer, "NetworkService", "LastBlockHeight", nil, &reply)
if err != nil { if err != nil {
return err return err
} }

1
cache/cache.go vendored
View File

@ -13,4 +13,5 @@ type Cache interface {
Get(key string, value interface{}) error Get(key string, value interface{}) error
Delete(key string) Delete(key string)
Items() map[string]interface{} Items() map[string]interface{}
Exists(key string) bool
} }

View File

@ -62,3 +62,8 @@ func (imc *InMemoryCache) Items() map[string]interface{} {
} }
return m return m
} }
func (imc *InMemoryCache) Exists(key string) bool {
_, exists := imc.cache.Get(key)
return exists
}

11
cache/redis_cache.go vendored
View File

@ -85,3 +85,14 @@ func (rc *RedisCache) Delete(key string) {
func (rc *RedisCache) Items() map[string]interface{} { func (rc *RedisCache) Items() map[string]interface{} {
return nil // TODO return nil // TODO
} }
func (rc *RedisCache) Exists(key string) bool {
res := rc.Client.Exists(context.TODO(), key)
if res.Err() != nil {
return false
}
if res.Val() == 0 {
return false
}
return true
}

View File

@ -2,4 +2,4 @@ package config
import "math/big" import "math/big"
var ExpectedLeadersPerEpoch = big.NewInt(1) var ExpectedLeadersPerEpoch = big.NewInt(2)

View File

@ -5,7 +5,6 @@ import (
"errors" "errors"
"fmt" "fmt"
"math/big" "math/big"
"sync"
drand2 "github.com/Secured-Finance/dione/beacon/drand" drand2 "github.com/Secured-Finance/dione/beacon/drand"
@ -38,39 +37,20 @@ var (
ErrNoAcceptedBlocks = errors.New("there is no accepted blocks") ErrNoAcceptedBlocks = errors.New("there is no accepted blocks")
) )
type StateStatus uint8
const (
StateStatusUnknown = iota
StateStatusPrePrepared
StateStatusPrepared
StateStatusCommited
)
type PBFTConsensusManager struct { type PBFTConsensusManager struct {
bus EventBus.Bus bus EventBus.Bus
psb *pubsub.PubSubRouter psb *pubsub.PubSubRouter
minApprovals int // FIXME minApprovals int // FIXME
privKey crypto.PrivKey privKey crypto.PrivKey
msgLog *ConsensusMessageLog msgLog *ConsensusMessageLog
validator *ConsensusValidator validator *ConsensusValidator
ethereumClient *ethclient.EthereumClient ethereumClient *ethclient.EthereumClient
miner *blockchain.Miner miner *blockchain.Miner
blockPool *pool.BlockPool consensusRoundPool *ConsensusRoundPool
mempool *pool.Mempool mempool *pool.Mempool
blockchain *blockchain.BlockChain blockchain *blockchain.BlockChain
state *State address peer.ID
address peer.ID stateChangeChannels map[string]map[State][]chan bool
}
type State struct {
mutex sync.Mutex
drandRound uint64
randomness []byte
blockHeight uint64
status StateStatus
ready bool
} }
func NewPBFTConsensusManager( func NewPBFTConsensusManager(
@ -81,28 +61,25 @@ func NewPBFTConsensusManager(
ethereumClient *ethclient.EthereumClient, ethereumClient *ethclient.EthereumClient,
miner *blockchain.Miner, miner *blockchain.Miner,
bc *blockchain.BlockChain, bc *blockchain.BlockChain,
bp *pool.BlockPool, bp *ConsensusRoundPool,
db *drand2.DrandBeacon, db *drand2.DrandBeacon,
mempool *pool.Mempool, mempool *pool.Mempool,
address peer.ID, address peer.ID,
) *PBFTConsensusManager { ) *PBFTConsensusManager {
pcm := &PBFTConsensusManager{ pcm := &PBFTConsensusManager{
psb: psb, psb: psb,
miner: miner, miner: miner,
validator: NewConsensusValidator(miner, bc, db), validator: NewConsensusValidator(miner, bc, db),
msgLog: NewConsensusMessageLog(), msgLog: NewConsensusMessageLog(),
minApprovals: minApprovals, minApprovals: minApprovals,
privKey: privKey, privKey: privKey,
ethereumClient: ethereumClient, ethereumClient: ethereumClient,
state: &State{ bus: bus,
ready: false, consensusRoundPool: bp,
status: StateStatusUnknown, mempool: mempool,
}, blockchain: bc,
bus: bus, address: address,
blockPool: bp, stateChangeChannels: map[string]map[State][]chan bool{},
mempool: mempool,
blockchain: bc,
address: address,
} }
return pcm return pcm
@ -115,8 +92,6 @@ func (pcm *PBFTConsensusManager) Run() {
pcm.bus.SubscribeAsync("beacon:newEntry", func(entry types2.BeaconEntry) { pcm.bus.SubscribeAsync("beacon:newEntry", func(entry types2.BeaconEntry) {
pcm.onNewBeaconEntry(entry) pcm.onNewBeaconEntry(entry)
}, true) }, true)
height, _ := pcm.blockchain.GetLatestBlockHeight()
pcm.state.blockHeight = height + 1
} }
func (pcm *PBFTConsensusManager) propose(blk *types3.Block) error { func (pcm *PBFTConsensusManager) propose(blk *types3.Block) error {
@ -125,15 +100,12 @@ func (pcm *PBFTConsensusManager) propose(blk *types3.Block) error {
return err return err
} }
pcm.psb.BroadcastToServiceTopic(prePrepareMsg) pcm.psb.BroadcastToServiceTopic(prePrepareMsg)
pcm.blockPool.AddBlock(blk) pcm.consensusRoundPool.AddConsensusInfo(blk)
logrus.WithField("blockHash", fmt.Sprintf("%x", blk.Header.Hash)).Debugf("Entered into PREPREPARED state") logrus.WithField("blockHash", fmt.Sprintf("%x", blk.Header.Hash)).Debugf("Entered into PREPREPARED state")
pcm.state.status = StateStatusPrePrepared
return nil return nil
} }
func (pcm *PBFTConsensusManager) handlePrePrepare(message *pubsub.PubSubMessage) { func (pcm *PBFTConsensusManager) handlePrePrepare(message *pubsub.PubSubMessage) {
//pcm.state.mutex.Lock()
//defer pcm.state.mutex.Unlock()
var prePrepare types.PrePrepareMessage var prePrepare types.PrePrepareMessage
err := cbor.Unmarshal(message.Payload, &prePrepare) err := cbor.Unmarshal(message.Payload, &prePrepare)
if err != nil { if err != nil {
@ -166,7 +138,18 @@ func (pcm *PBFTConsensusManager) handlePrePrepare(message *pubsub.PubSubMessage)
"blockHash": fmt.Sprintf("%x", cmsg.Block.Header.Hash), "blockHash": fmt.Sprintf("%x", cmsg.Block.Header.Hash),
"from": message.From.String(), "from": message.From.String(),
}).Debug("Received PREPREPARE message") }).Debug("Received PREPREPARE message")
pcm.blockPool.AddBlock(cmsg.Block) pcm.consensusRoundPool.AddConsensusInfo(cmsg.Block)
encodedHash := hex.EncodeToString(cmsg.Blockhash)
if m, ok := pcm.stateChangeChannels[encodedHash]; ok {
if channels, ok := m[StateStatusPrePrepared]; ok {
for _, v := range channels {
v <- true
close(v)
delete(pcm.stateChangeChannels, encodedHash)
}
}
}
prepareMsg, err := NewMessage(cmsg, types.ConsensusMessageTypePrepare, pcm.privKey) prepareMsg, err := NewMessage(cmsg, types.ConsensusMessageTypePrepare, pcm.privKey)
if err != nil { if err != nil {
@ -176,12 +159,9 @@ func (pcm *PBFTConsensusManager) handlePrePrepare(message *pubsub.PubSubMessage)
logrus.WithField("blockHash", fmt.Sprintf("%x", prePrepare.Block.Header.Hash)).Debugf("Entered into PREPREPARED state") logrus.WithField("blockHash", fmt.Sprintf("%x", prePrepare.Block.Header.Hash)).Debugf("Entered into PREPREPARED state")
pcm.psb.BroadcastToServiceTopic(prepareMsg) pcm.psb.BroadcastToServiceTopic(prepareMsg)
pcm.state.status = StateStatusPrePrepared
} }
func (pcm *PBFTConsensusManager) handlePrepare(message *pubsub.PubSubMessage) { func (pcm *PBFTConsensusManager) handlePrepare(message *pubsub.PubSubMessage) {
//pcm.state.mutex.Lock()
//defer pcm.state.mutex.Unlock()
var prepare types.PrepareMessage var prepare types.PrepareMessage
err := cbor.Unmarshal(message.Payload, &prepare) err := cbor.Unmarshal(message.Payload, &prepare)
if err != nil { if err != nil {
@ -196,9 +176,18 @@ func (pcm *PBFTConsensusManager) handlePrepare(message *pubsub.PubSubMessage) {
Signature: prepare.Signature, Signature: prepare.Signature,
} }
if _, err := pcm.blockPool.GetBlock(cmsg.Blockhash); errors.Is(err, cache.ErrNotFound) { if _, err := pcm.consensusRoundPool.GetConsensusInfo(cmsg.Blockhash); errors.Is(err, cache.ErrNotFound) {
logrus.WithField("blockHash", hex.EncodeToString(cmsg.Blockhash)).Warnf("received unknown block %x", cmsg.Blockhash) encodedHash := hex.EncodeToString(cmsg.Blockhash)
return logrus.WithField("blockHash", encodedHash).Warn("received PREPARE for unknown block")
waitingCh := make(chan bool)
if _, ok := pcm.stateChangeChannels[encodedHash]; !ok {
pcm.stateChangeChannels[encodedHash] = map[State][]chan bool{}
}
pcm.stateChangeChannels[encodedHash][StateStatusPrePrepared] = append(pcm.stateChangeChannels[encodedHash][StateStatusPrePrepared], waitingCh)
result := <-waitingCh
if !result {
return
}
} }
if pcm.msgLog.Exists(cmsg) { if pcm.msgLog.Exists(cmsg) {
@ -225,13 +214,23 @@ func (pcm *PBFTConsensusManager) handlePrepare(message *pubsub.PubSubMessage) {
} }
pcm.psb.BroadcastToServiceTopic(commitMsg) pcm.psb.BroadcastToServiceTopic(commitMsg)
logrus.WithField("blockHash", fmt.Sprintf("%x", cmsg.Blockhash)).Debugf("Entered into PREPARED state") logrus.WithField("blockHash", fmt.Sprintf("%x", cmsg.Blockhash)).Debugf("Entered into PREPARED state")
pcm.state.status = StateStatusPrepared pcm.consensusRoundPool.UpdateConsensusState(cmsg.Blockhash, StateStatusPrepared)
// pull watchers
encodedHash := hex.EncodeToString(cmsg.Blockhash)
if m, ok := pcm.stateChangeChannels[encodedHash]; ok {
if channels, ok := m[StateStatusPrepared]; ok {
for _, v := range channels {
v <- true
close(v)
delete(pcm.stateChangeChannels, encodedHash)
}
}
}
} }
} }
func (pcm *PBFTConsensusManager) handleCommit(message *pubsub.PubSubMessage) { func (pcm *PBFTConsensusManager) handleCommit(message *pubsub.PubSubMessage) {
//pcm.state.mutex.Lock()
//defer pcm.state.mutex.Unlock()
var commit types.CommitMessage var commit types.CommitMessage
err := cbor.Unmarshal(message.Payload, &commit) err := cbor.Unmarshal(message.Payload, &commit)
if err != nil { if err != nil {
@ -246,11 +245,27 @@ func (pcm *PBFTConsensusManager) handleCommit(message *pubsub.PubSubMessage) {
Signature: commit.Signature, Signature: commit.Signature,
} }
if _, err := pcm.blockPool.GetBlock(cmsg.Blockhash); errors.Is(err, cache.ErrNotFound) { ci, err := pcm.consensusRoundPool.GetConsensusInfo(cmsg.Blockhash)
logrus.Warnf("received unknown block %x", cmsg.Blockhash)
if errors.Is(err, cache.ErrNotFound) {
logrus.WithField("blockHash", hex.EncodeToString(cmsg.Blockhash)).Warnf("received COMMIT for unknown block")
return return
} }
if ci.State < StateStatusPrepared {
encodedHash := hex.EncodeToString(cmsg.Blockhash)
logrus.WithField("blockHash", encodedHash).Warnf("incorrect state of block consensus")
waitingCh := make(chan bool)
if _, ok := pcm.stateChangeChannels[encodedHash]; !ok {
pcm.stateChangeChannels[encodedHash] = map[State][]chan bool{}
}
pcm.stateChangeChannels[encodedHash][StateStatusPrepared] = append(pcm.stateChangeChannels[encodedHash][StateStatusPrepared], waitingCh)
result := <-waitingCh
if !result {
return
}
}
if pcm.msgLog.Exists(cmsg) { if pcm.msgLog.Exists(cmsg) {
logrus.Tracef("received existing commit msg for block %x", cmsg.Blockhash) logrus.Tracef("received existing commit msg for block %x", cmsg.Blockhash)
return return
@ -268,27 +283,22 @@ func (pcm *PBFTConsensusManager) handleCommit(message *pubsub.PubSubMessage) {
}).Debug("Received COMMIT message") }).Debug("Received COMMIT message")
if len(pcm.msgLog.Get(types.ConsensusMessageTypeCommit, cmsg.Blockhash)) >= pcm.minApprovals { if len(pcm.msgLog.Get(types.ConsensusMessageTypeCommit, cmsg.Blockhash)) >= pcm.minApprovals {
block, err := pcm.blockPool.GetBlock(cmsg.Blockhash)
if err != nil {
logrus.Error(err)
return
}
pcm.blockPool.AddAcceptedBlock(block)
logrus.WithField("blockHash", fmt.Sprintf("%x", cmsg.Blockhash)).Debugf("Entered into COMMIT state") logrus.WithField("blockHash", fmt.Sprintf("%x", cmsg.Blockhash)).Debugf("Entered into COMMIT state")
pcm.state.status = StateStatusCommited pcm.consensusRoundPool.UpdateConsensusState(cmsg.Blockhash, StateStatusCommited)
} }
} }
func (pcm *PBFTConsensusManager) onNewBeaconEntry(entry types2.BeaconEntry) { func (pcm *PBFTConsensusManager) onNewBeaconEntry(entry types2.BeaconEntry) {
block, err := pcm.commitAcceptedBlocks() block, err := pcm.commitAcceptedBlocks()
height, _ := pcm.blockchain.GetLatestBlockHeight()
if err != nil { if err != nil {
if errors.Is(err, ErrNoAcceptedBlocks) { if errors.Is(err, ErrNoAcceptedBlocks) {
logrus.WithFields(logrus.Fields{ logrus.WithFields(logrus.Fields{
"height": pcm.state.blockHeight, "height": height + 1,
}).Infof("No accepted blocks in the current consensus round") }).Infof("No accepted blocks in the current consensus round")
} else { } else {
logrus.WithFields(logrus.Fields{ logrus.WithFields(logrus.Fields{
"height": pcm.state.blockHeight, "height": height + 1,
"err": err.Error(), "err": err.Error(),
}).Errorf("Failed to select the block in the current consensus round") }).Errorf("Failed to select the block in the current consensus round")
return return
@ -312,26 +322,20 @@ func (pcm *PBFTConsensusManager) onNewBeaconEntry(entry types2.BeaconEntry) {
if block.Header.Proposer.String() == pcm.address.String() { if block.Header.Proposer.String() == pcm.address.String() {
pcm.submitTasksFromBlock(block) pcm.submitTasksFromBlock(block)
} }
pcm.state.blockHeight = pcm.state.blockHeight + 1
} }
// get latest block for k, v := range pcm.stateChangeChannels {
height, err := pcm.blockchain.GetLatestBlockHeight() for k1, j := range v {
if err != nil { for _, ch := range j {
logrus.Error(err) ch <- true
return close(ch)
} }
blockHeader, err := pcm.blockchain.FetchBlockHeaderByHeight(height) delete(v, k1)
if err != nil { }
logrus.Error(err) delete(pcm.stateChangeChannels, k)
return
} }
pcm.state.drandRound = entry.Round minedBlock, err := pcm.miner.MineBlock(entry.Data, entry.Round)
pcm.state.randomness = entry.Data
minedBlock, err := pcm.miner.MineBlock(entry.Data, entry.Round, blockHeader)
if err != nil { if err != nil {
if errors.Is(err, blockchain.ErrNoTxForBlock) { if errors.Is(err, blockchain.ErrNoTxForBlock) {
logrus.Info("Sealing skipped, no transactions in mempool") logrus.Info("Sealing skipped, no transactions in mempool")
@ -343,7 +347,6 @@ func (pcm *PBFTConsensusManager) onNewBeaconEntry(entry types2.BeaconEntry) {
// if we are round winner // if we are round winner
if minedBlock != nil { if minedBlock != nil {
logrus.WithField("height", pcm.state.blockHeight).Infof("We have been elected in the current consensus round")
err = pcm.propose(minedBlock) err = pcm.propose(minedBlock)
if err != nil { if err != nil {
logrus.Errorf("Failed to propose the block: %s", err.Error()) logrus.Errorf("Failed to propose the block: %s", err.Error())
@ -388,32 +391,34 @@ func (pcm *PBFTConsensusManager) submitTasksFromBlock(block *types3.Block) {
} }
func (pcm *PBFTConsensusManager) commitAcceptedBlocks() (*types3.Block, error) { func (pcm *PBFTConsensusManager) commitAcceptedBlocks() (*types3.Block, error) {
blocks := pcm.blockPool.GetAllAcceptedBlocks() blocks := pcm.consensusRoundPool.GetAllBlocksWithCommit()
if blocks == nil { if blocks == nil {
return nil, ErrNoAcceptedBlocks return nil, ErrNoAcceptedBlocks
} }
var maxStake *big.Int var maxStake *big.Int
var maxWinCount int64 = -1
var selectedBlock *types3.Block var selectedBlock *types3.Block
for _, v := range blocks { for _, v := range blocks {
stake, err := pcm.ethereumClient.GetMinerStake(v.Header.ProposerEth) stake, err := pcm.ethereumClient.GetMinerStake(v.Block.Header.ProposerEth)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if maxStake != nil { if maxStake != nil && maxWinCount != -1 {
if stake.Cmp(maxStake) == -1 { if stake.Cmp(maxStake) == -1 || v.Block.Header.ElectionProof.WinCount < maxWinCount {
continue continue
} }
} }
maxStake = stake maxStake = stake
selectedBlock = v maxWinCount = v.Block.Header.ElectionProof.WinCount
selectedBlock = v.Block
} }
logrus.WithFields(logrus.Fields{ logrus.WithFields(logrus.Fields{
"hash": hex.EncodeToString(selectedBlock.Header.Hash), "hash": hex.EncodeToString(selectedBlock.Header.Hash),
"height": selectedBlock.Header.Height, "height": selectedBlock.Header.Height,
"miner": selectedBlock.Header.Proposer.String(), "miner": selectedBlock.Header.Proposer.String(),
}).Info("Committed new block") }).Info("Committed new block")
pcm.blockPool.PruneAcceptedBlocks(selectedBlock) pcm.consensusRoundPool.Prune()
for _, v := range selectedBlock.Data { for _, v := range selectedBlock.Data {
err := pcm.mempool.DeleteTx(v.Hash) err := pcm.mempool.DeleteTx(v.Hash)
if err != nil { if err != nil {

View File

@ -0,0 +1,119 @@
package consensus
import (
"bytes"
"encoding/hex"
"fmt"
"time"
"github.com/Secured-Finance/dione/blockchain/pool"
"github.com/asaskevich/EventBus"
"github.com/sirupsen/logrus"
"github.com/Secured-Finance/dione/blockchain/types"
"github.com/Secured-Finance/dione/cache"
)
type State uint8
const (
StateStatusUnknown = iota
StateStatusPrePrepared
StateStatusPrepared
StateStatusCommited
)
// ConsensusRoundPool is pool for blocks that isn't not validated or committed yet
type ConsensusRoundPool struct {
mempool *pool.Mempool
consensusInfoStorage cache.Cache
bus EventBus.Bus
}
func NewConsensusRoundPool(mp *pool.Mempool, bus EventBus.Bus) (*ConsensusRoundPool, error) {
bp := &ConsensusRoundPool{
consensusInfoStorage: cache.NewInMemoryCache(),
mempool: mp,
bus: bus,
}
return bp, nil
}
type ConsensusInfo struct {
Block *types.Block
State State
}
func (crp *ConsensusRoundPool) AddConsensusInfo(block *types.Block) error {
encodedHash := hex.EncodeToString(block.Header.Hash)
if crp.consensusInfoStorage.Exists(encodedHash) {
return nil
}
err := crp.consensusInfoStorage.StoreWithTTL(encodedHash, &ConsensusInfo{
Block: block,
State: StateStatusPrePrepared,
}, 10*time.Minute)
if err != nil {
return err
}
logrus.WithField("hash", fmt.Sprintf("%x", block.Header.Hash)).Debug("New block discovered")
crp.bus.Publish("blockpool:knownBlockAdded", block)
return nil
}
func (crp *ConsensusRoundPool) UpdateConsensusState(blockhash []byte, newState State) error {
encodedHash := hex.EncodeToString(blockhash)
var consensusInfo ConsensusInfo
err := crp.consensusInfoStorage.Get(encodedHash, &consensusInfo)
if err != nil {
return err
}
if newState < consensusInfo.State {
return fmt.Errorf("attempt to set incorrect state")
}
consensusInfo.State = newState
crp.bus.Publish("blockpool:newConsensusState", blockhash, newState)
return crp.consensusInfoStorage.StoreWithTTL(encodedHash, &consensusInfo, 10*time.Minute)
}
func (crp *ConsensusRoundPool) GetConsensusInfo(blockhash []byte) (*ConsensusInfo, error) {
var consensusInfo ConsensusInfo
err := crp.consensusInfoStorage.Get(hex.EncodeToString(blockhash), &consensusInfo)
return &consensusInfo, err
}
// Prune cleans known blocks list. It is called when new consensus round starts.
func (crp *ConsensusRoundPool) Prune() {
for k := range crp.consensusInfoStorage.Items() {
crp.consensusInfoStorage.Delete(k)
}
crp.bus.Publish("blockpool:pruned")
}
func (crp *ConsensusRoundPool) GetAllBlocksWithCommit() []*ConsensusInfo {
var consensusInfos []*ConsensusInfo
for _, v := range crp.consensusInfoStorage.Items() {
ci := v.(*ConsensusInfo)
if ci.State == StateStatusCommited {
consensusInfos = append(consensusInfos, ci)
}
}
return consensusInfos
}
func containsTx(s []*types.Transaction, e *types.Transaction) bool {
for _, a := range s {
if bytes.Equal(a.Hash, e.Hash) {
return true
}
}
return false
}

View File

@ -4,6 +4,8 @@ import (
"context" "context"
"time" "time"
"github.com/Secured-Finance/dione/blockchain"
drand2 "github.com/Secured-Finance/dione/beacon/drand" drand2 "github.com/Secured-Finance/dione/beacon/drand"
"github.com/Secured-Finance/dione/pubsub" "github.com/Secured-Finance/dione/pubsub"
@ -50,6 +52,7 @@ func runNode(
pubSubRouter *pubsub.PubSubRouter, pubSubRouter *pubsub.PubSubRouter,
disputeManager *consensus.DisputeManager, disputeManager *consensus.DisputeManager,
db *drand2.DrandBeacon, db *drand2.DrandBeacon,
bc *blockchain.BlockChain,
) { ) {
lc.Append(fx.Hook{ lc.Append(fx.Hook{
OnStart: func(ctx context.Context) error { OnStart: func(ctx context.Context) error {
@ -200,7 +203,7 @@ func Start() {
providePeerDiscovery, providePeerDiscovery,
provideDrandBeacon, provideDrandBeacon,
provideMempool, provideMempool,
provideMiner, blockchain.NewMiner,
provideBlockChain, provideBlockChain,
provideBlockPool, provideBlockPool,
provideSyncManager, provideSyncManager,
@ -214,8 +217,10 @@ func Start() {
configureLogger, configureLogger,
configureDirectRPC, configureDirectRPC,
configureForeignBlockchainRPC, configureForeignBlockchainRPC,
initializeBlockchain,
configureMiner,
runNode, runNode,
), ),
fx.NopLogger, //fx.NopLogger,
).Run() ).Run()
} }

View File

@ -10,6 +10,8 @@ import (
"path" "path"
"runtime" "runtime"
types2 "github.com/Secured-Finance/dione/blockchain/types"
"github.com/Secured-Finance/dione/rpc" "github.com/Secured-Finance/dione/rpc"
"github.com/Secured-Finance/dione/rpc/filecoin" "github.com/Secured-Finance/dione/rpc/filecoin"
solana2 "github.com/Secured-Finance/dione/rpc/solana" solana2 "github.com/Secured-Finance/dione/rpc/solana"
@ -76,12 +78,6 @@ func provideDisputeManager(ethClient *ethclient.EthereumClient, pcm *consensus.P
return dm return dm
} }
func provideMiner(h host.Host, ethClient *ethclient.EthereumClient, privateKey crypto.PrivKey, mempool *pool.Mempool) *blockchain.Miner {
miner := blockchain.NewMiner(h.ID(), *ethClient.GetEthAddress(), ethClient, privateKey, mempool)
logrus.Info("Mining subsystem has been initialized!")
return miner
}
func provideDrandBeacon(ps *pubsub.PubSubRouter, bus EventBus.Bus) *drand2.DrandBeacon { func provideDrandBeacon(ps *pubsub.PubSubRouter, bus EventBus.Bus) *drand2.DrandBeacon {
db, err := drand2.NewDrandBeacon(ps.Pubsub, bus) db, err := drand2.NewDrandBeacon(ps.Pubsub, bus)
if err != nil { if err != nil {
@ -135,7 +131,7 @@ func provideConsensusManager(
bc *blockchain.BlockChain, bc *blockchain.BlockChain,
ethClient *ethclient.EthereumClient, ethClient *ethclient.EthereumClient,
privateKey crypto.PrivKey, privateKey crypto.PrivKey,
bp *pool.BlockPool, bp *consensus.ConsensusRoundPool,
db *drand2.DrandBeacon, db *drand2.DrandBeacon,
mp *pool.Mempool, mp *pool.Mempool,
) *consensus.PBFTConsensusManager { ) *consensus.PBFTConsensusManager {
@ -268,8 +264,8 @@ func provideNetworkService(bp *blockchain.BlockChain, mp *pool.Mempool) *Network
return ns return ns
} }
func provideBlockPool(mp *pool.Mempool, bus EventBus.Bus) *pool.BlockPool { func provideBlockPool(mp *pool.Mempool, bus EventBus.Bus) *consensus.ConsensusRoundPool {
bp, err := pool.NewBlockPool(mp, bus) bp, err := consensus.NewConsensusRoundPool(mp, bus)
if err != nil { if err != nil {
logrus.Fatalf("Failed to initialize blockpool: %s", err.Error()) logrus.Fatalf("Failed to initialize blockpool: %s", err.Error())
} }
@ -392,3 +388,19 @@ func configureForeignBlockchainRPC() {
logrus.Info("Foreign Blockchain RPC clients has been successfully configured!") logrus.Info("Foreign Blockchain RPC clients has been successfully configured!")
} }
func configureMiner(m *blockchain.Miner, b *blockchain.BlockChain) {
m.SetBlockchainInstance(b)
}
func initializeBlockchain(bc *blockchain.BlockChain) {
_, err := bc.GetLatestBlockHeight()
if err == blockchain.ErrLatestHeightNil {
gBlock := types2.GenesisBlock()
err = bc.StoreBlock(gBlock) // commit genesis block
if err != nil {
logrus.Fatal(err)
}
logrus.Info("Committed genesis block")
}
}

View File

@ -106,7 +106,7 @@ func lambda(power, totalPower *big.Int) *big.Int {
return lam return lam
} }
var MaxWinCount = 3 * config.ExpectedLeadersPerEpoch.Int64() var MaxWinCount = 10 * config.ExpectedLeadersPerEpoch.Int64()
type poiss struct { type poiss struct {
lam *big.Int lam *big.Int