2020-08-05 17:11:14 +00:00
package consensus
2020-10-21 19:54:40 +00:00
import (
2021-06-08 21:30:23 +00:00
"errors"
2020-11-15 13:46:58 +00:00
"math/big"
2020-11-20 21:29:30 +00:00
"sync"
2021-07-11 23:23:00 +00:00
"github.com/Secured-Finance/dione/beacon"
2021-07-10 22:07:35 +00:00
"github.com/fxamacker/cbor/v2"
2021-06-14 21:45:35 +00:00
"github.com/Secured-Finance/dione/cache"
2021-06-11 11:40:32 +00:00
"github.com/asaskevich/EventBus"
2021-06-08 21:30:23 +00:00
"github.com/Secured-Finance/dione/blockchain"
"github.com/Arceliar/phony"
types3 "github.com/Secured-Finance/dione/blockchain/types"
"github.com/libp2p/go-libp2p-core/crypto"
"github.com/Secured-Finance/dione/blockchain/pool"
2020-12-02 13:42:02 +00:00
2020-11-18 19:53:52 +00:00
"github.com/Secured-Finance/dione/consensus/types"
2021-07-10 22:07:35 +00:00
types2 "github.com/Secured-Finance/dione/types"
2020-11-18 19:53:52 +00:00
2020-11-15 13:46:58 +00:00
"github.com/Secured-Finance/dione/ethclient"
"github.com/sirupsen/logrus"
2020-10-21 19:54:40 +00:00
2020-11-18 19:53:52 +00:00
"github.com/Secured-Finance/dione/pubsub"
2021-06-08 21:30:23 +00:00
)
2021-07-11 00:32:58 +00:00
var (
ErrNoAcceptedBlocks = errors . New ( "there is no accepted blocks" )
)
2021-06-08 21:30:23 +00:00
type StateStatus uint8
const (
StateStatusUnknown = iota
StateStatusPrePrepared
StateStatusPrepared
StateStatusCommited
2020-10-21 19:54:40 +00:00
)
type PBFTConsensusManager struct {
2021-06-08 21:30:23 +00:00
phony . Inbox
2021-06-11 11:40:32 +00:00
bus EventBus . Bus
2020-11-20 21:29:30 +00:00
psb * pubsub . PubSubRouter
2021-06-08 21:30:23 +00:00
minApprovals int // FIXME
privKey crypto . PrivKey
msgLog * ConsensusMessageLog
2021-04-30 19:55:12 +00:00
validator * ConsensusValidator
2020-11-20 21:29:30 +00:00
ethereumClient * ethclient . EthereumClient
2020-11-27 16:16:08 +00:00
miner * Miner
2021-07-11 00:32:58 +00:00
blockPool * pool . BlockPool
2021-07-11 23:23:00 +00:00
mempool * pool . Mempool
blockchain * blockchain . BlockChain
2021-06-08 21:30:23 +00:00
state * State
2020-10-21 19:54:40 +00:00
}
2021-06-08 21:30:23 +00:00
type State struct {
mutex sync . Mutex
drandRound uint64
randomness [ ] byte
blockHeight uint64
status StateStatus
2021-07-11 23:23:00 +00:00
ready bool
2020-11-20 21:29:30 +00:00
}
2020-10-21 19:54:40 +00:00
2021-07-11 00:32:58 +00:00
func NewPBFTConsensusManager (
bus EventBus . Bus ,
psb * pubsub . PubSubRouter ,
minApprovals int ,
privKey crypto . PrivKey ,
ethereumClient * ethclient . EthereumClient ,
miner * Miner ,
bc * blockchain . BlockChain ,
bp * pool . BlockPool ,
2021-07-11 23:23:00 +00:00
b beacon . BeaconNetwork ,
mempool * pool . Mempool ,
2021-07-11 00:32:58 +00:00
) * PBFTConsensusManager {
2020-10-21 19:54:40 +00:00
pcm := & PBFTConsensusManager { }
pcm . psb = psb
2020-11-27 16:16:08 +00:00
pcm . miner = miner
2021-07-11 23:23:00 +00:00
pcm . validator = NewConsensusValidator ( miner , bc , b )
2021-06-08 21:30:23 +00:00
pcm . msgLog = NewConsensusMessageLog ( )
2020-11-15 13:46:58 +00:00
pcm . minApprovals = minApprovals
pcm . privKey = privKey
pcm . ethereumClient = ethereumClient
2021-06-11 11:40:32 +00:00
pcm . state = & State {
2021-07-11 23:23:00 +00:00
ready : false ,
2021-06-11 11:40:32 +00:00
status : StateStatusUnknown ,
}
pcm . bus = bus
2021-07-11 00:32:58 +00:00
pcm . blockPool = bp
2021-07-11 23:23:00 +00:00
pcm . mempool = mempool
pcm . blockchain = bc
pcm . psb . Hook ( pubsub . PrePrepareMessageType , pcm . handlePrePrepare )
pcm . psb . Hook ( pubsub . PrepareMessageType , pcm . handlePrepare )
pcm . psb . Hook ( pubsub . CommitMessageType , pcm . handleCommit )
//bus.SubscribeOnce("sync:initialSyncCompleted", func() {
// pcm.state.ready = true
//})
height , _ := pcm . blockchain . GetLatestBlockHeight ( )
pcm . state . blockHeight = height + 1
go func ( ) {
for {
select {
case e := <- b . Beacon . NewEntries ( ) :
{
pcm . NewDrandRound ( nil , e )
}
}
}
} ( )
2020-10-21 19:54:40 +00:00
return pcm
}
2021-06-08 21:30:23 +00:00
func ( pcm * PBFTConsensusManager ) propose ( blk * types3 . Block ) error {
prePrepareMsg , err := NewMessage ( types . ConsensusMessage { Block : blk } , types . ConsensusMessageTypePrePrepare , pcm . privKey )
2020-11-15 13:46:58 +00:00
if err != nil {
return err
}
pcm . psb . BroadcastToServiceTopic ( prePrepareMsg )
2021-06-14 21:45:35 +00:00
pcm . blockPool . AddBlock ( blk )
2021-06-08 21:30:23 +00:00
pcm . state . status = StateStatusPrePrepared
2020-11-15 13:46:58 +00:00
return nil
}
2020-11-12 14:18:30 +00:00
2021-07-11 23:23:00 +00:00
func ( pcm * PBFTConsensusManager ) handlePrePrepare ( message * pubsub . PubSubMessage ) {
2021-06-08 21:30:23 +00:00
pcm . state . mutex . Lock ( )
defer pcm . state . mutex . Unlock ( )
2021-07-11 23:23:00 +00:00
var prePrepare types . PrePrepareMessage
err := cbor . Unmarshal ( message . Payload , & prePrepare )
if err != nil {
logrus . Errorf ( "failed to convert payload to PrePrepare message: %s" , err . Error ( ) )
2021-06-02 21:19:52 +00:00
return
}
2021-07-11 23:23:00 +00:00
if * prePrepare . Block . Header . Proposer == pcm . miner . address {
2021-03-15 20:39:52 +00:00
return
}
2021-06-08 21:30:23 +00:00
cmsg := types . ConsensusMessage {
2021-07-11 23:23:00 +00:00
Type : types . ConsensusMessageTypePrePrepare ,
From : message . From ,
Block : prePrepare . Block ,
Blockhash : prePrepare . Block . Header . Hash ,
2021-06-08 21:30:23 +00:00
}
2021-06-02 21:19:52 +00:00
if pcm . msgLog . Exists ( cmsg ) {
2021-07-11 23:23:00 +00:00
logrus . Tracef ( "received existing pre_prepare msg for block %x" , cmsg . Block . Header . Hash )
2020-11-15 13:46:58 +00:00
return
}
2021-06-11 11:40:32 +00:00
if ! pcm . validator . Valid ( cmsg , map [ string ] interface { } { "randomness" : pcm . state . randomness } ) {
2021-07-11 23:23:00 +00:00
logrus . Warnf ( "received invalid pre_prepare msg for block %x" , cmsg . Block . Header . Hash )
2020-11-15 13:46:58 +00:00
return
}
2020-10-21 19:54:40 +00:00
2021-06-02 21:19:52 +00:00
pcm . msgLog . AddMessage ( cmsg )
2021-06-08 21:30:23 +00:00
pcm . blockPool . AddBlock ( cmsg . Block )
2020-11-20 21:29:30 +00:00
2021-06-08 21:30:23 +00:00
prepareMsg , err := NewMessage ( cmsg , types . ConsensusMessageTypePrepare , pcm . privKey )
2020-11-15 13:46:58 +00:00
if err != nil {
2021-04-30 20:09:55 +00:00
logrus . Errorf ( "failed to create prepare message: %v" , err )
2021-06-03 21:15:32 +00:00
return
2020-11-15 13:46:58 +00:00
}
2021-03-15 20:39:52 +00:00
2021-06-08 21:30:23 +00:00
pcm . psb . BroadcastToServiceTopic ( prepareMsg )
pcm . state . status = StateStatusPrePrepared
2020-10-21 19:54:40 +00:00
}
2021-07-11 23:23:00 +00:00
func ( pcm * PBFTConsensusManager ) handlePrepare ( message * pubsub . PubSubMessage ) {
2021-06-08 21:30:23 +00:00
pcm . state . mutex . Lock ( )
defer pcm . state . mutex . Unlock ( )
2021-07-11 23:23:00 +00:00
var prepare types . PrepareMessage
err := cbor . Unmarshal ( message . Payload , & prepare )
if err != nil {
logrus . Errorf ( "failed to convert payload to Prepare message: %s" , err . Error ( ) )
2021-06-08 21:30:23 +00:00
return
}
cmsg := types . ConsensusMessage {
Type : types . ConsensusMessageTypePrepare ,
From : message . From ,
Blockhash : prepare . Blockhash ,
2021-06-14 21:45:35 +00:00
Signature : prepare . Signature ,
2021-06-08 21:30:23 +00:00
}
2021-06-14 21:45:35 +00:00
if _ , err := pcm . blockPool . GetBlock ( cmsg . Blockhash ) ; errors . Is ( err , cache . ErrNotFound ) {
2021-07-11 23:23:00 +00:00
logrus . Warnf ( "received unknown block %x" , cmsg . Blockhash )
2021-06-02 21:19:52 +00:00
return
}
if pcm . msgLog . Exists ( cmsg ) {
2021-07-11 23:23:00 +00:00
logrus . Tracef ( "received existing prepare msg for block %x" , cmsg . Blockhash )
2020-11-15 13:46:58 +00:00
return
}
2021-06-14 21:45:35 +00:00
2021-06-11 11:40:32 +00:00
if ! pcm . validator . Valid ( cmsg , nil ) {
2021-07-11 23:23:00 +00:00
logrus . Warnf ( "received invalid prepare msg for block %x" , cmsg . Blockhash )
2020-10-21 19:54:40 +00:00
return
}
2020-11-15 13:46:58 +00:00
2021-06-02 21:19:52 +00:00
pcm . msgLog . AddMessage ( cmsg )
2020-11-15 13:46:58 +00:00
2021-06-08 21:30:23 +00:00
if len ( pcm . msgLog . Get ( types . ConsensusMessageTypePrepare , cmsg . Blockhash ) ) >= pcm . minApprovals {
commitMsg , err := NewMessage ( cmsg , types . ConsensusMessageTypeCommit , pcm . privKey )
2020-10-21 19:54:40 +00:00
if err != nil {
2021-06-08 21:30:23 +00:00
logrus . Errorf ( "failed to create commit message: %v" , err )
2021-07-11 23:23:00 +00:00
return
2020-10-21 19:54:40 +00:00
}
2021-06-08 21:30:23 +00:00
pcm . psb . BroadcastToServiceTopic ( commitMsg )
pcm . state . status = StateStatusPrepared
2020-10-21 19:54:40 +00:00
}
}
2021-07-11 23:23:00 +00:00
func ( pcm * PBFTConsensusManager ) handleCommit ( message * pubsub . PubSubMessage ) {
2021-06-08 21:30:23 +00:00
pcm . state . mutex . Lock ( )
defer pcm . state . mutex . Unlock ( )
2021-07-11 23:23:00 +00:00
var commit types . CommitMessage
err := cbor . Unmarshal ( message . Payload , & commit )
if err != nil {
logrus . Errorf ( "failed to convert payload to Commit message: %s" , err . Error ( ) )
2021-06-08 21:30:23 +00:00
return
}
cmsg := types . ConsensusMessage {
Type : types . ConsensusMessageTypeCommit ,
From : message . From ,
Blockhash : commit . Blockhash ,
2021-07-11 23:23:00 +00:00
Signature : commit . Signature ,
2021-06-08 21:30:23 +00:00
}
2021-06-14 21:45:35 +00:00
if _ , err := pcm . blockPool . GetBlock ( cmsg . Blockhash ) ; errors . Is ( err , cache . ErrNotFound ) {
2021-07-11 23:23:00 +00:00
logrus . Warnf ( "received unknown block %x" , cmsg . Blockhash )
2021-06-02 21:19:52 +00:00
return
}
if pcm . msgLog . Exists ( cmsg ) {
2021-07-11 23:23:00 +00:00
logrus . Tracef ( "received existing commit msg for block %x" , cmsg . Blockhash )
2020-10-21 19:54:40 +00:00
return
}
2021-06-11 11:40:32 +00:00
if ! pcm . validator . Valid ( cmsg , nil ) {
2021-07-11 23:23:00 +00:00
logrus . Warnf ( "received invalid commit msg for block %x" , cmsg . Blockhash )
2020-10-22 14:37:31 +00:00
return
}
2021-06-02 21:19:52 +00:00
pcm . msgLog . AddMessage ( cmsg )
2020-10-22 14:37:31 +00:00
2021-06-08 21:30:23 +00:00
if len ( pcm . msgLog . Get ( types . ConsensusMessageTypeCommit , cmsg . Blockhash ) ) >= pcm . minApprovals {
block , err := pcm . blockPool . GetBlock ( cmsg . Blockhash )
if err != nil {
2021-07-11 23:23:00 +00:00
logrus . Error ( err )
2021-04-21 19:37:32 +00:00
return
}
2021-06-08 21:30:23 +00:00
pcm . blockPool . AddAcceptedBlock ( block )
pcm . state . status = StateStatusCommited
}
}
2021-07-11 23:23:00 +00:00
func ( pcm * PBFTConsensusManager ) NewDrandRound ( from phony . Actor , entry types2 . BeaconEntry ) {
2021-06-08 21:30:23 +00:00
pcm . Act ( from , func ( ) {
pcm . state . mutex . Lock ( )
defer pcm . state . mutex . Unlock ( )
block , err := pcm . commitAcceptedBlocks ( )
if err != nil {
2021-07-11 00:32:58 +00:00
if errors . Is ( err , ErrNoAcceptedBlocks ) {
2021-07-11 23:23:00 +00:00
logrus . Infof ( "No accepted blocks for consensus round %d" , pcm . state . blockHeight )
2021-07-11 00:32:58 +00:00
} else {
logrus . Errorf ( "Failed to select the block in consensus round %d: %s" , pcm . state . blockHeight , err . Error ( ) )
2021-07-11 23:23:00 +00:00
return
2021-07-11 00:32:58 +00:00
}
2021-03-15 20:39:52 +00:00
}
2021-07-10 22:07:35 +00:00
2021-07-11 23:23:00 +00:00
if block != nil {
// broadcast new block
var newBlockMessage pubsub . PubSubMessage
newBlockMessage . Type = pubsub . NewBlockMessageType
blockSerialized , err := cbor . Marshal ( block )
if err != nil {
logrus . Errorf ( "Failed to serialize block %x for broadcasting!" , block . Header . Hash )
} else {
newBlockMessage . Payload = blockSerialized
pcm . psb . BroadcastToServiceTopic ( & newBlockMessage )
}
2021-07-10 22:07:35 +00:00
2021-07-11 23:23:00 +00:00
// if we are miner for this block
// then post dione tasks to target chains (currently, only Ethereum)
if * block . Header . Proposer == pcm . miner . address {
for _ , v := range block . Data {
var task types2 . DioneTask
err := cbor . Unmarshal ( v . Data , & task )
if err != nil {
logrus . Errorf ( "Failed to unmarshal transaction %x payload: %s" , v . Hash , err . Error ( ) )
continue // FIXME
}
reqIDNumber , ok := big . NewInt ( 0 ) . SetString ( task . RequestID , 10 )
if ! ok {
logrus . Errorf ( "Failed to parse request id number in task of tx %x" , v . Hash )
continue // FIXME
}
err = pcm . ethereumClient . SubmitRequestAnswer ( reqIDNumber , task . Payload )
if err != nil {
logrus . Errorf ( "Failed to submit task in tx %x: %s" , v . Hash , err . Error ( ) )
continue // FIXME
}
2021-07-10 22:07:35 +00:00
}
}
2021-07-11 23:23:00 +00:00
pcm . state . blockHeight = pcm . state . blockHeight + 1
}
2021-03-05 19:29:09 +00:00
2021-07-11 23:23:00 +00:00
// get latest block
height , err := pcm . blockchain . GetLatestBlockHeight ( )
if err != nil {
logrus . Error ( err )
return
}
blockHeader , err := pcm . blockchain . FetchBlockHeaderByHeight ( height )
2021-06-08 21:30:23 +00:00
if err != nil {
2021-07-11 23:23:00 +00:00
logrus . Error ( err )
2021-06-08 21:30:23 +00:00
return
2020-11-15 13:46:58 +00:00
}
2021-03-15 20:39:52 +00:00
2021-07-11 23:23:00 +00:00
pcm . state . drandRound = entry . Round
pcm . state . randomness = entry . Data
minedBlock , err := pcm . miner . MineBlock ( entry . Data , entry . Round , blockHeader )
if err != nil {
if errors . Is ( err , ErrNoTxForBlock ) {
logrus . Info ( "Skipping consensus round, because we don't have transactions in mempool for including into block" )
} else {
logrus . Errorf ( "Failed to mine the block: %s" , err . Error ( ) )
}
return
}
2021-03-15 20:39:52 +00:00
2021-06-08 21:30:23 +00:00
// if we are round winner
if minedBlock != nil {
2021-07-11 23:23:00 +00:00
logrus . Infof ( "We are elected in consensus round %d" , pcm . state . blockHeight )
2021-06-08 21:30:23 +00:00
err = pcm . propose ( minedBlock )
if err != nil {
logrus . Errorf ( "Failed to propose the block: %s" , err . Error ( ) )
return
}
2021-04-15 21:01:27 +00:00
}
2021-06-08 21:30:23 +00:00
} )
2021-03-15 20:39:52 +00:00
}
2021-06-08 21:30:23 +00:00
func ( pcm * PBFTConsensusManager ) commitAcceptedBlocks ( ) ( * types3 . Block , error ) {
blocks := pcm . blockPool . GetAllAcceptedBlocks ( )
if blocks == nil {
2021-07-11 00:32:58 +00:00
return nil , ErrNoAcceptedBlocks
2021-03-15 20:39:52 +00:00
}
2021-06-08 21:30:23 +00:00
var maxStake * big . Int
var selectedBlock * types3 . Block
for _ , v := range blocks {
stake , err := pcm . ethereumClient . GetMinerStake ( v . Header . ProposerEth )
if err != nil {
return nil , err
2021-06-02 21:19:52 +00:00
}
2021-06-08 21:30:23 +00:00
if maxStake != nil {
if stake . Cmp ( maxStake ) == - 1 {
continue
}
2021-06-02 21:19:52 +00:00
}
2021-06-08 21:30:23 +00:00
maxStake = stake
selectedBlock = v
2021-06-02 21:19:52 +00:00
}
2021-07-11 23:23:00 +00:00
logrus . Infof ( "Committed block %x with height %d of miner %s" , selectedBlock . Header . Hash , selectedBlock . Header . Height , selectedBlock . Header . Proposer . String ( ) )
pcm . blockPool . PruneAcceptedBlocks ( selectedBlock )
for _ , v := range selectedBlock . Data {
pcm . mempool . DeleteTx ( v . Hash )
}
2021-06-08 21:30:23 +00:00
return selectedBlock , pcm . blockchain . StoreBlock ( selectedBlock )
2021-06-02 21:19:52 +00:00
}