aboutsummaryrefslogblamecommitdiffstats
path: root/core/test/app.go
blob: a48b3c8743f63663170b2f4f1bbadc6c9d4f583a (plain) (tree)



















                                                                               

              


                                                                 
                                                                     

 

























                                                                                     








                                                                        
                                 
                          







                                                                       

 

                                                            







                                                               




                                        




                                                                               


         
                                                   

                                                                         


                                                   
                                                                      

                               
              

 

                                                      



                                                   
                                               

 

                                                      


                                    
                                                                      

 
                                                           
                                                                                


                                           
                                    
                                         
                                  








                                                                                          

 
                                                   

                                                                 


                                        

                                                       

                                                
                                                                    























                                                             
                                                                                       


























                                                                     
                                               



                                                               

                                                           


                                                              
                                            







                                                                              

                                                   














                                                                                      
 











                                                                     
// 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
// <http://www.gnu.org/licenses/>.

package test

import (
    "fmt"
    "sync"
    "time"

    "github.com/dexon-foundation/dexon-consensus-core/common"
    "github.com/dexon-foundation/dexon-consensus-core/core/types"
)

var (
    // ErrEmptyDeliverSequence means there is no delivery event in this App
    // instance.
    ErrEmptyDeliverSequence = fmt.Errorf("emptry deliver sequence")
    // ErrMismatchBlockHashSequence means the delivering sequence between two App
    // instances are different.
    ErrMismatchBlockHashSequence = fmt.Errorf("mismatch block hash sequence")
    // ErrMismatchConsensusTime means the consensus timestamp between two blocks
    // with the same hash from two App instances are different.
    ErrMismatchConsensusTime = fmt.Errorf("mismatch consensus time")
    // ErrApplicationIntegrityFailed means the internal datum in a App instance
    // is not integrated.
    ErrApplicationIntegrityFailed = fmt.Errorf("application integrity failed")
    // ErrConsensusTimestampOutOfOrder means the later delivered block has
    // consensus timestamp older than previous block.
    ErrConsensusTimestampOutOfOrder = fmt.Errorf(
        "consensus timestamp out of order")
    // ErrDeliveredBlockNotAcked means some block delivered (confirmed) but
    // not strongly acked.
    ErrDeliveredBlockNotAcked = fmt.Errorf("delivered block not acked")
    // ErrMismatchTotalOrderingAndDelivered mean the sequence of total ordering
    // and delivered are different.
    ErrMismatchTotalOrderingAndDelivered = fmt.Errorf(
        "mismatch total ordering and delivered sequence")
)

// AppAckedRecord caches information when this application received
// a strongly-acked notification.
type AppAckedRecord struct {
    When time.Time
}

// AppTotalOrderRecord caches information when this application received
// a total-ordering deliver notification.
type AppTotalOrderRecord struct {
    BlockHashes common.Hashes
    Mode        uint32
    When        time.Time
}

// AppDeliveredRecord caches information when this application received
// a block delivered notification.
type AppDeliveredRecord struct {
    ConsensusTime time.Time
    When          time.Time
}

// App implements Application interface for testing purpose.
type App struct {
    Acked              map[common.Hash]*AppAckedRecord
    ackedLock          sync.RWMutex
    TotalOrdered       []*AppTotalOrderRecord
    TotalOrderedByHash map[common.Hash]*AppTotalOrderRecord
    totalOrderedLock   sync.RWMutex
    Delivered          map[common.Hash]*AppDeliveredRecord
    DeliverSequence    common.Hashes
    deliveredLock      sync.RWMutex
}

// NewApp constructs a TestApp instance.
func NewApp() *App {
    return &App{
        Acked:              make(map[common.Hash]*AppAckedRecord),
        TotalOrdered:       []*AppTotalOrderRecord{},
        TotalOrderedByHash: make(map[common.Hash]*AppTotalOrderRecord),
        Delivered:          make(map[common.Hash]*AppDeliveredRecord),
        DeliverSequence:    common.Hashes{},
    }
}

// PreparePayload implements Application interface.
func (app *App) PreparePayload(position types.Position) ([]byte, error) {
    return []byte{}, nil
}

// PrepareWitness implements Application interface.
func (app *App) PrepareWitness(height uint64) (types.Witness, error) {
    return types.Witness{
        Height: height,
    }, nil
}

// VerifyBlock implements Application.
func (app *App) VerifyBlock(block *types.Block) bool {
    return true
}

// BlockConfirmed implements Application interface.
func (app *App) BlockConfirmed(_ types.Block) {
}

// StronglyAcked implements Application interface.
func (app *App) StronglyAcked(blockHash common.Hash) {
    app.ackedLock.Lock()
    defer app.ackedLock.Unlock()

    app.Acked[blockHash] = &AppAckedRecord{When: time.Now().UTC()}
}

// TotalOrderingDelivered implements Application interface.
func (app *App) TotalOrderingDelivered(blockHashes common.Hashes, mode uint32) {
    app.totalOrderedLock.Lock()
    defer app.totalOrderedLock.Unlock()

    rec := &AppTotalOrderRecord{
        BlockHashes: blockHashes,
        Mode:        mode,
        When:        time.Now().UTC(),
    }
    app.TotalOrdered = append(app.TotalOrdered, rec)
    for _, h := range blockHashes {
        if _, exists := app.TotalOrderedByHash[h]; exists {
            panic(fmt.Errorf("deliver duplicated blocks from total ordering"))
        }
        app.TotalOrderedByHash[h] = rec
    }
}

// BlockDelivered implements Application interface.
func (app *App) BlockDelivered(
    blockHash common.Hash, result types.FinalizationResult) {
    app.deliveredLock.Lock()
    defer app.deliveredLock.Unlock()

    app.Delivered[blockHash] = &AppDeliveredRecord{
        ConsensusTime: result.Timestamp,
        When:          time.Now().UTC(),
    }
    app.DeliverSequence = append(app.DeliverSequence, blockHash)
}

// Compare performs these checks against another App instance
// and return erros if not passed:
// - deliver sequence by comparing block hashes.
// - consensus timestamp of each block are equal.
func (app *App) Compare(other *App) error {
    app.deliveredLock.RLock()
    defer app.deliveredLock.RUnlock()
    other.deliveredLock.RLock()
    defer other.deliveredLock.RUnlock()

    minLength := len(app.DeliverSequence)
    if minLength > len(other.DeliverSequence) {
        minLength = len(other.DeliverSequence)
    }
    if minLength == 0 {
        return ErrEmptyDeliverSequence
    }
    for idx, h := range app.DeliverSequence[:minLength] {
        hOther := other.DeliverSequence[idx]
        if hOther != h {
            return ErrMismatchBlockHashSequence
        }
        if app.Delivered[h].ConsensusTime != other.Delivered[h].ConsensusTime {
            return ErrMismatchConsensusTime
        }
    }
    return nil
}

// Verify checks the integrity of date received by this App instance.
func (app *App) Verify() error {
    app.deliveredLock.RLock()
    defer app.deliveredLock.RUnlock()

    if len(app.DeliverSequence) == 0 {
        return ErrEmptyDeliverSequence
    }
    if len(app.DeliverSequence) != len(app.Delivered) {
        return ErrApplicationIntegrityFailed
    }

    app.ackedLock.RLock()
    defer app.ackedLock.RUnlock()

    prevTime := time.Time{}
    for _, h := range app.DeliverSequence {
        // Make sure delivered block is strongly acked.
        if _, acked := app.Acked[h]; !acked {
            return ErrDeliveredBlockNotAcked
        }
        rec, exists := app.Delivered[h]
        if !exists {
            return ErrApplicationIntegrityFailed
        }
        // Make sure the consensus time is incremental.
        ok := prevTime.Before(rec.ConsensusTime) ||
            prevTime.Equal(rec.ConsensusTime)
        if !ok {
            return ErrConsensusTimestampOutOfOrder
        }
        prevTime = rec.ConsensusTime
    }
    // Make sure the order of delivered and total ordering are the same by
    // comparing the concated string.
    app.totalOrderedLock.RLock()
    defer app.totalOrderedLock.RUnlock()

    hashSequenceIdx := 0
Loop:
    for _, rec := range app.TotalOrdered {
        for _, h := range rec.BlockHashes {
            if hashSequenceIdx >= len(app.DeliverSequence) {
                break Loop
            }
            if h != app.DeliverSequence[hashSequenceIdx] {
                return ErrMismatchTotalOrderingAndDelivered
            }
            hashSequenceIdx++
        }
    }
    if hashSequenceIdx != len(app.DeliverSequence) {
        // The count of delivered blocks should be larger than those delivered
        // by total ordering.
        return ErrMismatchTotalOrderingAndDelivered
    }
    return nil
}

// Check provides a backdoor to check status of App with reader lock.
func (app *App) Check(checker func(*App)) {
    app.ackedLock.RLock()
    defer app.ackedLock.RUnlock()
    app.totalOrderedLock.RLock()
    defer app.totalOrderedLock.RUnlock()
    app.deliveredLock.RLock()
    defer app.deliveredLock.RUnlock()

    checker(app)
}