// Copyright 2018 The dexon-consensus-core Authors // This file is part of the dexon-consensus-core library. // // The dexon-consensus-core library is free software: you can redistribute it // and/or modify it under the terms of the GNU Lesser General Public License as // published by the Free Software Foundation, either version 3 of the License, // or (at your option) any later version. // // The dexon-consensus-core library is distributed in the hope that it will be // useful, but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser // General Public License for more details. // // You should have received a copy of the GNU Lesser General Public License // along with the dexon-consensus-core library. If not, see // . // TODO(jimmy-dexon): remove those comments before open source. package core import ( "fmt" "sync" "time" "github.com/dexon-foundation/dexon-consensus-core/blockdb" "github.com/dexon-foundation/dexon-consensus-core/common" "github.com/dexon-foundation/dexon-consensus-core/core/types" "github.com/dexon-foundation/dexon-consensus-core/crypto" ) // Errors for compaction chain. var ( ErrNoNotaryToAck = fmt.Errorf( "no notary to ack") ErrIncorrectNotaryHash = fmt.Errorf( "hash of notary ack is incorrect") ErrIncorrectNotarySignature = fmt.Errorf( "signature of notary ack is incorrect") ) type pendingAck struct { receivedTime time.Time notaryAck *types.NotaryAck } type compactionChain struct { db blockdb.Reader pendingAckLock sync.RWMutex pendingAck map[common.Hash]*pendingAck prevBlockLock sync.RWMutex prevBlock *types.Block notaryAcksLock sync.RWMutex latestNotaryAcks map[types.ValidatorID]*types.NotaryAck sigToPub SigToPubFn } func newCompactionChain( db blockdb.Reader, sigToPub SigToPubFn, ) *compactionChain { return &compactionChain{ db: db, pendingAck: make(map[common.Hash]*pendingAck), latestNotaryAcks: make(map[types.ValidatorID]*types.NotaryAck), sigToPub: sigToPub, } } func (cc *compactionChain) sanityCheck( notaryAck *types.NotaryAck, notaryBlock *types.Block) error { if notaryBlock != nil { hash, err := hashNotary(notaryBlock) if err != nil { return err } if hash != notaryAck.Hash { return ErrIncorrectNotaryHash } } pubKey, err := cc.sigToPub(notaryAck.Hash, notaryAck.Signature) if err != nil { return err } if notaryAck.ProposerID != types.NewValidatorID(pubKey) { return ErrIncorrectNotarySignature } return nil } // TODO(jimmy-dexon): processBlock and prepareNotaryAck can be extraced to // another struct. func (cc *compactionChain) processBlock(block *types.Block) error { prevBlock := cc.lastBlock() if prevBlock != nil { hash, err := hashNotary(prevBlock) if err != nil { return err } block.Notary.Height = prevBlock.Notary.Height + 1 block.Notary.ParentHash = hash } cc.prevBlockLock.Lock() defer cc.prevBlockLock.Unlock() cc.prevBlock = block return nil } func (cc *compactionChain) prepareNotaryAck(prvKey crypto.PrivateKey) ( notaryAck *types.NotaryAck, err error) { lastBlock := cc.lastBlock() if lastBlock == nil { err = ErrNoNotaryToAck return } hash, err := hashNotary(lastBlock) if err != nil { return } sig, err := prvKey.Sign(hash) if err != nil { return } notaryAck = &types.NotaryAck{ ProposerID: types.NewValidatorID(prvKey.PublicKey()), NotaryBlockHash: lastBlock.Hash, Signature: sig, Hash: hash, } return } func (cc *compactionChain) processNotaryAck(notaryAck *types.NotaryAck) ( err error) { // Before getting the Block from notaryAck.NotaryBlockHash, we can still // do some sanityCheck to prevent invalid ack appending to pendingAck. if err = cc.sanityCheck(notaryAck, nil); err != nil { return } pendingFinished := make(chan struct{}) go func() { cc.processPendingNotaryAcks() pendingFinished <- struct{}{} }() defer func() { <-pendingFinished }() notaryBlock, err := cc.db.Get(notaryAck.NotaryBlockHash) if err != nil { if err == blockdb.ErrBlockDoesNotExist { cc.pendingAckLock.Lock() defer cc.pendingAckLock.Unlock() cc.pendingAck[notaryAck.Hash] = &pendingAck{ receivedTime: time.Now().UTC(), notaryAck: notaryAck, } err = nil } return } return cc.processOneNotaryAck(notaryAck, ¬aryBlock) } func (cc *compactionChain) processOneNotaryAck( notaryAck *types.NotaryAck, notaryBlock *types.Block) ( err error) { if err = cc.sanityCheck(notaryAck, notaryBlock); err != nil { return } lastNotaryAck, exist := func() (ack *types.NotaryAck, exist bool) { cc.notaryAcksLock.RLock() defer cc.notaryAcksLock.RUnlock() ack, exist = cc.latestNotaryAcks[notaryAck.ProposerID] return }() if exist { lastNotaryBlock, err2 := cc.db.Get(lastNotaryAck.NotaryBlockHash) err = err2 if err != nil { return } if lastNotaryBlock.Notary.Height > notaryBlock.Notary.Height { return } } cc.notaryAcksLock.Lock() defer cc.notaryAcksLock.Unlock() cc.latestNotaryAcks[notaryAck.ProposerID] = notaryAck return } func (cc *compactionChain) processPendingNotaryAcks() { pendingAck := func() map[common.Hash]*pendingAck { pendingAck := make(map[common.Hash]*pendingAck) cc.pendingAckLock.RLock() defer cc.pendingAckLock.RUnlock() for k, v := range cc.pendingAck { pendingAck[k] = v } return pendingAck }() for hash, ack := range pendingAck { // TODO(jimmy-dexon): customizable timeout. if ack.receivedTime.Add(30 * time.Second).Before(time.Now().UTC()) { delete(pendingAck, hash) continue } } for hash, ack := range pendingAck { notaryBlock, err := cc.db.Get(ack.notaryAck.NotaryBlockHash) if err != nil { if err == blockdb.ErrBlockDoesNotExist { continue } // TODO(jimmy-dexon): this error needs to be handled properly. fmt.Println(err) delete(pendingAck, hash) } delete(pendingAck, hash) cc.processOneNotaryAck(ack.notaryAck, ¬aryBlock) } cc.pendingAckLock.Lock() defer cc.pendingAckLock.Unlock() for k, v := range cc.pendingAck { pendingAck[k] = v } cc.pendingAck = pendingAck } func (cc *compactionChain) notaryAcks() map[types.ValidatorID]*types.NotaryAck { cc.notaryAcksLock.RLock() defer cc.notaryAcksLock.RUnlock() acks := make(map[types.ValidatorID]*types.NotaryAck) for k, v := range cc.latestNotaryAcks { acks[k] = v.Clone() } return acks } func (cc *compactionChain) lastBlock() *types.Block { cc.prevBlockLock.RLock() defer cc.prevBlockLock.RUnlock() return cc.prevBlock }