aboutsummaryrefslogblamecommitdiffstats
path: root/simulation/app.go
blob: 1a67a01eacef315b2d8aec8bd03b6d148cac87a9 (plain) (tree)
1
2
3
4
5
6
7
8
9

                                                    
  
                                                                        



                                                                               
                                                                         




                                                                           
                                                      




                                  
                       
             
              
              
 
                                                            
                                                               
                                                                

 















                                                                                






                                                          
                                                 
                       

                           



                       

                                         







                                                   
                                                                           




                                                         
                                       

 
                                                       

                                                                                    
                       

                                              
                                                

                                                                    
                                                                      


                                                                         


         
                                              


                                                    

                                                                                  
                                                  
                                      

 
                                           

                                                                          

 

                                                                            
                                                                             
                                                                 
                                                 







                                                                 
                                                                                        



                                                                                                 
 






                                                                                          
                                              
                                                                          
                                           


                                              
                                                                       




                                             
                                   

 
                                                                          
                                
                                                                                     
 





                                                                            
                                                                    



                                                                                  
                         




                                                                                                  

                                                                                      
                 
           
                

                                                     



                                                    
                                                
                                              
                                     


                                                
 

                                     
                                                 



                         
                                      
                 
                                             



                                             
                                             








                                                    
                        


                                        
                      
                               
 















                                                                                   
                                               


                                                            


                                                               


                                               
// Copyright 2018 The dexon-consensus Authors
// This file is part of the dexon-consensus library.
//
// The dexon-consensus 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 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 library. If not, see
// <http://www.gnu.org/licenses/>.

package simulation

import (
    "encoding/json"
    "fmt"
    "sync"
    "time"

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

type timestampEvent string

const (
    blockSeen        timestampEvent = "blockSeen"
    timestampConfirm timestampEvent = "timestampConfirm"
    timestampAck     timestampEvent = "timestampAck"
)

// TimestampMessage is a struct for peer sending consensus timestamp information
// to server.
type timestampMessage struct {
    BlockHash common.Hash    `json:"hash"`
    Event     timestampEvent `json:"event"`
    Timestamp time.Time      `json:"timestamp"`
}

const (
    // Block received or created in agreement.
    blockEventReceived int = iota
    // Block confirmed in agreement, sent into lattice
    blockEventConfirmed
    // Block delivered by lattice.
    blockEventDelivered
    // Block is ready (Randomness calculated)
    blockEventReady
    // Block is witness
    blockEventWitnessed

    blockEventCount
)

// simApp is an DEXON app for simulation.
type simApp struct {
    NodeID          types.NodeID
    Outputs         []*types.Block
    Early           bool
    netModule       *test.Network
    stateModule     *test.State
    DeliverID       int
    blockTimestamps map[common.Hash][]time.Time
    blockSeen       map[common.Hash]time.Time
    // uncofirmBlocks stores the blocks whose timestamps are not ready.
    unconfirmedBlocks  map[types.NodeID]common.Hashes
    blockByHash        map[common.Hash]*types.Block
    blockByHashMutex   sync.RWMutex
    latestWitness      types.Witness
    latestWitnessReady *sync.Cond
    lock               sync.RWMutex
}

// newSimApp returns point to a new instance of simApp.
func newSimApp(
    id types.NodeID, netModule *test.Network, stateModule *test.State) *simApp {
    return &simApp{
        NodeID:             id,
        netModule:          netModule,
        stateModule:        stateModule,
        DeliverID:          0,
        blockSeen:          make(map[common.Hash]time.Time),
        blockTimestamps:    make(map[common.Hash][]time.Time),
        unconfirmedBlocks:  make(map[types.NodeID]common.Hashes),
        blockByHash:        make(map[common.Hash]*types.Block),
        latestWitnessReady: sync.NewCond(&sync.Mutex{}),
    }
}

// BlockConfirmed implements core.Application.
func (a *simApp) BlockConfirmed(block types.Block) {
    a.blockByHashMutex.Lock()
    defer a.blockByHashMutex.Unlock()
    // TODO(jimmy-dexon) : Remove block in this hash if it's no longer needed.
    a.blockByHash[block.Hash] = &block
    a.blockSeen[block.Hash] = time.Now().UTC()
    a.updateBlockEvent(block.Hash)
}

// VerifyBlock implements core.Application.
func (a *simApp) VerifyBlock(block *types.Block) types.BlockVerifyStatus {
    return types.VerifyOK
}

// getAckedBlocks will return all unconfirmed blocks' hash with lower Height
// than the block with ackHash.
func (a *simApp) getAckedBlocks(ackHash common.Hash) (output common.Hashes) {
    // TODO(jimmy-dexon): Why there are some acks never seen?
    ackBlock, exist := a.blockByHash[ackHash]
    if !exist {
        return
    }
    hashes, exist := a.unconfirmedBlocks[ackBlock.ProposerID]
    if !exist {
        return
    }
    for i, blockHash := range hashes {
        if a.blockByHash[blockHash].Position.Height > ackBlock.Position.Height {
            output, a.unconfirmedBlocks[ackBlock.ProposerID] = hashes[:i], hashes[i:]
            break
        }
    }

    // All of the Height of unconfirmed blocks are lower than the acked block.
    if len(output) == 0 {
        output, a.unconfirmedBlocks[ackBlock.ProposerID] = hashes, common.Hashes{}
    }
    return
}

// PreparePayload implements core.Application.
func (a *simApp) PreparePayload(position types.Position) ([]byte, error) {
    return a.stateModule.PackRequests()
}

// PrepareWitness implements core.Application.
func (a *simApp) PrepareWitness(height uint64) (types.Witness, error) {
    a.latestWitnessReady.L.Lock()
    defer a.latestWitnessReady.L.Unlock()
    for a.latestWitness.Height < height {
        a.latestWitnessReady.Wait()
    }
    return a.latestWitness, nil
}

// BlockDelivered is called when a block in compaction chain is delivered.
func (a *simApp) BlockDelivered(
    blockHash common.Hash, pos types.Position, result types.FinalizationResult) {

    if len(result.Randomness) == 0 && pos.Round > 0 {
        panic(fmt.Errorf("Block %s randomness is empty", blockHash))
    }
    func() {
        a.blockByHashMutex.Lock()
        defer a.blockByHashMutex.Unlock()
        if block, exist := a.blockByHash[blockHash]; exist {
            if err := a.stateModule.Apply(block.Payload); err != nil {
                if err != test.ErrDuplicatedChange {
                    panic(err)
                }
            }
            var witnessBlockHash common.Hash
            if err := witnessBlockHash.UnmarshalText(block.Witness.Data); err != nil {
                panic(err)
            }
            a.updateBlockEvent(witnessBlockHash)
        } else {
            panic(fmt.Errorf("Block is not confirmed yet: %s", blockHash))
        }
    }()
    func() {
        a.latestWitnessReady.L.Lock()
        defer a.latestWitnessReady.L.Unlock()
        data, err := blockHash.MarshalText()
        if err != nil {
            panic(err)
        }
        a.latestWitness = types.Witness{
            Height: result.Height,
            Data:   data,
        }
        a.latestWitnessReady.Broadcast()
    }()

    a.updateBlockEvent(blockHash)

    seenTime, exist := a.blockSeen[blockHash]
    if !exist {
        return
    }
    now := time.Now()
    payload := []timestampMessage{
        {
            BlockHash: blockHash,
            Event:     blockSeen,
            Timestamp: seenTime,
        },
        {
            BlockHash: blockHash,
            Event:     timestampConfirm,
            Timestamp: now,
        },
    }
    jsonPayload, err := json.Marshal(payload)
    if err != nil {
        fmt.Println(err)
        return
    }
    msg := &message{
        Type:    blockTimestamp,
        Payload: jsonPayload,
    }
    // #nosec G104
    a.netModule.Report(msg)
}

// BlockReceived is called when a block is received in agreement.
func (a *simApp) BlockReceived(hash common.Hash) {
    a.updateBlockEvent(hash)
}

// BlockReady is called when a block is ready.
func (a *simApp) BlockReady(hash common.Hash) {
    a.updateBlockEvent(hash)
}

func (a *simApp) updateBlockEvent(hash common.Hash) {
    a.lock.Lock()
    defer a.lock.Unlock()
    a.blockTimestamps[hash] = append(a.blockTimestamps[hash], time.Now().UTC())
    if len(a.blockTimestamps[hash]) == blockEventCount {
        msg := &test.BlockEventMessage{
            BlockHash:  hash,
            Timestamps: a.blockTimestamps[hash],
        }
        if err := a.netModule.Report(msg); err != nil {
            panic(err)
        }
        delete(a.blockTimestamps, hash)
    }
}