aboutsummaryrefslogblamecommitdiffstats
path: root/eth/block_pool_test.go
blob: 94c3b43d29f1c9357af232c1065e7db827e91693 (plain) (tree)
1
2
3
4
5
6
7
8
9


           

             
                  


                 
              




                                                          
                                             

 
                                 
 
                                                                                                                
 
               
 



                                              
         

 


                               

                            


                                    
                 
         
                   





                             
                                                                                     


                          
                       





                                             
                                                                            

                                












                                                                            

                                









                                                        






















































































































                                                                                                        

                                   


                                        
                                         




                                        

 









                                                                               
         

 















                                                                                                                                  
         

 











































                                                                                                          
         

 







































                                                                                                                                             




                                                             
              






















































                                                                                                                                                  
                                
















                                                             
                                            







                                                             
                                   














                                                                 
                  
                                                             
         





                                                             
                                   























                                                                            
                                   









                                                                            
                                   
















                                                              
                                   









                                                            




                                                   
                                                 




                                        


















                                                                      









                                                            
                             
                                        
                             






                                                                      



















                                                                      










                                                            
                                
                                           
                                









                                                                                                          
                                                                                          











                                                                                        
                                              









                                    
                                                       
                       

                                           
                                
 

                                                    
                        

                                                  




















                                                                                                        
                                
                                        
                                   


















                                                                      
                                                              
                                        
                                                           



                                 
                                
                                        

                                








                                                                                                          
                                     


                                                            
                                            


                         

                                                       


                                                         

                                              

                                                                                                                       


                                                                                                         





                                                                                                                                                    
                                                  


                                                                      
                                       










                                                            
                                                                                               

                                                 


                                                                                         


























                                                                                                                                                                          




















                                                                      
                                



                                                                                

                                                                            

























                                                                                                                       
                                
                                              
                                      
                                                                                
                                                 
                                                                                   
                                         




























                                                                      


                                                                 
                                                 
                                                 
                                                                        
                                                 




                                 
                                  
                                          

                                                                                                    
                                                                                                                                               
                             


























                                                                      
                                
                                              


                                                 
                                                                        
                                                 
                                                                                                         

                                                                                                                                                              
                                                 
                             


























                                                                      
                                
                                        
                                                                      

                                                                               
                                                                                             
                                                 
                                                 
                                                                        
                                                                        
                                                                                                         

                                                                                                                                               


                                                 
                        





                                                                      

 
package eth

import (
    "fmt"
    "log"
    "math/big"
    "os"
    "sync"
    "testing"
    "time"

    "github.com/ethereum/go-ethereum/core/types"
    "github.com/ethereum/go-ethereum/crypto"
    "github.com/ethereum/go-ethereum/ethutil"
    ethlogger "github.com/ethereum/go-ethereum/logger"
    "github.com/ethereum/go-ethereum/pow"
)

const waitTimeout = 60 // seconds

var logsys = ethlogger.NewStdLogSystem(os.Stdout, log.LstdFlags, ethlogger.LogLevel(ethlogger.DebugDetailLevel))

var ini = false

func logInit() {
    if !ini {
        ethlogger.AddLogSystem(logsys)
        ini = true
    }
}

// test helpers
func arrayEq(a, b []int) bool {
    if len(a) != len(b) {
        return false
    }
    for i := range a {
        if a[i] != b[i] {
            return false
        }
    }
    return true
}

type intToHash map[int][]byte

type hashToInt map[string]int

// hashPool is a test helper, that allows random hashes to be referred to by integers
type testHashPool struct {
    intToHash
    hashToInt
    lock sync.Mutex
}

func newHash(i int) []byte {
    return crypto.Sha3([]byte(string(i)))
}

func (self *testHashPool) indexesToHashes(indexes []int) (hashes [][]byte) {
    self.lock.Lock()
    defer self.lock.Unlock()
    for _, i := range indexes {
        hash, found := self.intToHash[i]
        if !found {
            hash = newHash(i)
            self.intToHash[i] = hash
            self.hashToInt[string(hash)] = i
        }
        hashes = append(hashes, hash)
    }
    return
}

func (self *testHashPool) hashesToIndexes(hashes [][]byte) (indexes []int) {
    self.lock.Lock()
    defer self.lock.Unlock()
    for _, hash := range hashes {
        i, found := self.hashToInt[string(hash)]
        if !found {
            i = -1
        }
        indexes = append(indexes, i)
    }
    return
}

// test blockChain is an integer trie
type blockChain map[int][]int

// blockPoolTester provides the interface between tests and a blockPool
//
// refBlockChain is used to guide which blocks will be accepted as valid
// blockChain gives the current state of the blockchain and
// accumulates inserts so that we can check the resulting chain
type blockPoolTester struct {
    hashPool      *testHashPool
    lock          sync.RWMutex
    refBlockChain blockChain
    blockChain    blockChain
    blockPool     *BlockPool
    t             *testing.T
}

func newTestBlockPool(t *testing.T) (hashPool *testHashPool, blockPool *BlockPool, b *blockPoolTester) {
    hashPool = &testHashPool{intToHash: make(intToHash), hashToInt: make(hashToInt)}
    b = &blockPoolTester{
        t:             t,
        hashPool:      hashPool,
        blockChain:    make(blockChain),
        refBlockChain: make(blockChain),
    }
    b.blockPool = NewBlockPool(b.hasBlock, b.insertChain, b.verifyPoW)
    blockPool = b.blockPool
    return
}

func (self *blockPoolTester) Errorf(format string, params ...interface{}) {
    fmt.Printf(format+"\n", params...)
    self.t.Errorf(format, params...)
}

// blockPoolTester implements the 3 callbacks needed by the blockPool:
// hasBlock, insetChain, verifyPoW
func (self *blockPoolTester) hasBlock(block []byte) (ok bool) {
    self.lock.RLock()
    defer self.lock.RUnlock()
    indexes := self.hashPool.hashesToIndexes([][]byte{block})
    i := indexes[0]
    _, ok = self.blockChain[i]
    fmt.Printf("has block %v (%x...): %v\n", i, block[0:4], ok)
    return
}

func (self *blockPoolTester) insertChain(blocks types.Blocks) error {
    self.lock.RLock()
    defer self.lock.RUnlock()
    var parent, child int
    var children, refChildren []int
    var ok bool
    for _, block := range blocks {
        child = self.hashPool.hashesToIndexes([][]byte{block.Hash()})[0]
        _, ok = self.blockChain[child]
        if ok {
            fmt.Printf("block %v already in blockchain\n", child)
            continue // already in chain
        }
        parent = self.hashPool.hashesToIndexes([][]byte{block.ParentHeaderHash})[0]
        children, ok = self.blockChain[parent]
        if !ok {
            return fmt.Errorf("parent %v not in blockchain ", parent)
        }
        ok = false
        var found bool
        refChildren, found = self.refBlockChain[parent]
        if found {
            for _, c := range refChildren {
                if c == child {
                    ok = true
                }
            }
            if !ok {
                return fmt.Errorf("invalid block %v", child)
            }
        } else {
            ok = true
        }
        if ok {
            // accept any blocks if parent not in refBlockChain
            fmt.Errorf("blockchain insert %v -> %v\n", parent, child)
            self.blockChain[parent] = append(children, child)
            self.blockChain[child] = nil
        }
    }
    return nil
}

func (self *blockPoolTester) verifyPoW(pblock pow.Block) bool {
    return true
}

// test helper that compares the resulting blockChain to the desired blockChain
func (self *blockPoolTester) checkBlockChain(blockChain map[int][]int) {
    for k, v := range self.blockChain {
        fmt.Printf("got: %v -> %v\n", k, v)
    }
    for k, v := range blockChain {
        fmt.Printf("expected: %v -> %v\n", k, v)
    }
    if len(blockChain) != len(self.blockChain) {
        self.Errorf("blockchain incorrect (zlength differ)")
    }
    for k, v := range blockChain {
        vv, ok := self.blockChain[k]
        if !ok || !arrayEq(v, vv) {
            self.Errorf("blockchain incorrect on %v -> %v (!= %v)", k, vv, v)
        }
    }
}

//

// peerTester provides the peer callbacks for the blockPool
// it registers actual callbacks so that result can be compared to desired behaviour
// provides helper functions to mock the protocol calls to the blockPool
type peerTester struct {
    blockHashesRequests []int
    blocksRequests      [][]int
    blocksRequestsMap   map[int]bool
    peerErrors          []int
    blockPool           *BlockPool
    hashPool            *testHashPool
    lock                sync.RWMutex
    id                  string
    td                  int
    currentBlock        int
    t                   *testing.T
}

// peerTester constructor takes hashPool and blockPool from the blockPoolTester
func (self *blockPoolTester) newPeer(id string, td int, cb int) *peerTester {
    return &peerTester{
        id:                id,
        td:                td,
        currentBlock:      cb,
        hashPool:          self.hashPool,
        blockPool:         self.blockPool,
        t:                 self.t,
        blocksRequestsMap: make(map[int]bool),
    }
}

func (self *peerTester) Errorf(format string, params ...interface{}) {
    fmt.Printf(format+"\n", params...)
    self.t.Errorf(format, params...)
}

// helper to compare actual and expected block requests
func (self *peerTester) checkBlocksRequests(blocksRequests ...[]int) {
    if len(blocksRequests) > len(self.blocksRequests) {
        self.Errorf("blocks requests incorrect (length differ)\ngot %v\nexpected %v", self.blocksRequests, blocksRequests)
    } else {
        for i, rr := range blocksRequests {
            r := self.blocksRequests[i]
            if !arrayEq(r, rr) {
                self.Errorf("blocks requests incorrect\ngot %v\nexpected %v", self.blocksRequests, blocksRequests)
            }
        }
    }
}

// helper to compare actual and expected block hash requests
func (self *peerTester) checkBlockHashesRequests(blocksHashesRequests ...int) {
    rr := blocksHashesRequests
    self.lock.RLock()
    r := self.blockHashesRequests
    self.lock.RUnlock()
    if len(r) != len(rr) {
        self.Errorf("block hashes requests incorrect (length differ)\ngot %v\nexpected %v", r, rr)
    } else {
        if !arrayEq(r, rr) {
            self.Errorf("block hashes requests incorrect\ngot %v\nexpected %v", r, rr)
        }
    }
}

// waiter function used by peer.AddBlocks
// blocking until requests appear
// since block requests are sent to any random peers
// block request map is shared between peers
// times out after a period
func (self *peerTester) waitBlocksRequests(blocksRequest ...int) {
    timeout := time.After(waitTimeout * time.Second)
    rr := blocksRequest
    for {
        self.lock.RLock()
        r := self.blocksRequestsMap
        fmt.Printf("[%s] blocks request check %v (%v)\n", self.id, rr, r)
        i := 0
        for i = 0; i < len(rr); i++ {
            _, ok := r[rr[i]]
            if !ok {
                break
            }
        }
        self.lock.RUnlock()

        if i == len(rr) {
            return
        }
        time.Sleep(100 * time.Millisecond)
        select {
        case <-timeout:
        default:
        }
    }
}

// waiter function used by peer.AddBlockHashes
// blocking until requests appear
// times out after a period
func (self *peerTester) waitBlockHashesRequests(blocksHashesRequest int) {
    timeout := time.After(waitTimeout * time.Second)
    rr := blocksHashesRequest
    for i := 0; ; {
        self.lock.RLock()
        r := self.blockHashesRequests
        self.lock.RUnlock()
        fmt.Printf("[%s] block hash request check %v (%v)\n", self.id, rr, r)
        for ; i < len(r); i++ {
            if rr == r[i] {
                return
            }
        }
        time.Sleep(100 * time.Millisecond)
        select {
        case <-timeout:
        default:
        }
    }
}

// mocks a simple blockchain 0 (genesis) ... n (head)
func (self *blockPoolTester) initRefBlockChain(n int) {
    for i := 0; i < n; i++ {
        self.refBlockChain[i] = []int{i + 1}
    }
}

// peerTester functions that mimic protocol calls to the blockpool
//  registers the peer with the blockPool
func (self *peerTester) AddPeer() bool {
    hash := self.hashPool.indexesToHashes([]int{self.currentBlock})[0]
    return self.blockPool.AddPeer(big.NewInt(int64(self.td)), hash, self.id, self.requestBlockHashes, self.requestBlocks, self.peerError)
}

// peer sends blockhashes if and when gets a request
func (self *peerTester) AddBlockHashes(indexes ...int) {
    fmt.Printf("ready to add block hashes %v\n", indexes)

    self.waitBlockHashesRequests(indexes[0])
    fmt.Printf("adding block hashes %v\n", indexes)
    hashes := self.hashPool.indexesToHashes(indexes)
    i := 1
    next := func() (hash []byte, ok bool) {
        if i < len(hashes) {
            hash = hashes[i]
            ok = true
            i++
        }
        return
    }
    self.blockPool.AddBlockHashes(next, self.id)
}

// peer sends blocks if and when there is a request
// (in the shared request store, not necessarily to a person)
func (self *peerTester) AddBlocks(indexes ...int) {
    hashes := self.hashPool.indexesToHashes(indexes)
    fmt.Printf("ready to add blocks %v\n", indexes[1:])
    self.waitBlocksRequests(indexes[1:]...)
    fmt.Printf("adding blocks %v \n", indexes[1:])
    for i := 1; i < len(hashes); i++ {
        fmt.Printf("adding block %v %x\n", indexes[i], hashes[i][:4])
        self.blockPool.AddBlock(&types.Block{HeaderHash: ethutil.Bytes(hashes[i]), ParentHeaderHash: ethutil.Bytes(hashes[i-1])}, self.id)
    }
}

// peer callbacks
// -1 is special: not found (a hash never seen)
// records block hashes requests by the blockPool
func (self *peerTester) requestBlockHashes(hash []byte) error {
    indexes := self.hashPool.hashesToIndexes([][]byte{hash})
    fmt.Printf("[%s] blocks hash request %v %x\n", self.id, indexes[0], hash[:4])
    self.lock.Lock()
    defer self.lock.Unlock()
    self.blockHashesRequests = append(self.blockHashesRequests, indexes[0])
    return nil
}

// records block requests by the blockPool
func (self *peerTester) requestBlocks(hashes [][]byte) error {
    indexes := self.hashPool.hashesToIndexes(hashes)
    fmt.Printf("blocks request %v %x...\n", indexes, hashes[0][:4])
    self.lock.Lock()
    defer self.lock.Unlock()
    self.blocksRequests = append(self.blocksRequests, indexes)
    for _, i := range indexes {
        self.blocksRequestsMap[i] = true
    }
    return nil
}

// records the error codes of all the peerErrors found the blockPool
func (self *peerTester) peerError(code int, format string, params ...interface{}) {
    self.peerErrors = append(self.peerErrors, code)
}

// the actual tests
func TestAddPeer(t *testing.T) {
    logInit()
    _, blockPool, blockPoolTester := newTestBlockPool(t)
    peer0 := blockPoolTester.newPeer("peer0", 1, 0)
    peer1 := blockPoolTester.newPeer("peer1", 2, 1)
    peer2 := blockPoolTester.newPeer("peer2", 3, 2)
    var peer *peerInfo

    blockPool.Start()

    // pool
    best := peer0.AddPeer()
    if !best {
        t.Errorf("peer0 (TD=1) not accepted as best")
    }
    if blockPool.peer.id != "peer0" {
        t.Errorf("peer0 (TD=1) not set as best")
    }
    // peer0.checkBlockHashesRequests(0)

    best = peer2.AddPeer()
    if !best {
        t.Errorf("peer2 (TD=3) not accepted as best")
    }
    if blockPool.peer.id != "peer2" {
        t.Errorf("peer2 (TD=3) not set as best")
    }
    peer2.waitBlocksRequests(2)

    best = peer1.AddPeer()
    if best {
        t.Errorf("peer1 (TD=2) accepted as best")
    }
    if blockPool.peer.id != "peer2" {
        t.Errorf("peer2 (TD=3) not set any more as best")
    }
    if blockPool.peer.td.Cmp(big.NewInt(int64(3))) != 0 {
        t.Errorf("peer1 TD not set")
    }

    peer2.td = 4
    peer2.currentBlock = 3
    best = peer2.AddPeer()
    if !best {
        t.Errorf("peer2 (TD=4) not accepted as best")
    }
    if blockPool.peer.id != "peer2" {
        t.Errorf("peer2 (TD=4) not set as best")
    }
    if blockPool.peer.td.Cmp(big.NewInt(int64(4))) != 0 {
        t.Errorf("peer2 TD not updated")
    }
    peer2.waitBlocksRequests(3)

    peer1.td = 3
    peer1.currentBlock = 2
    best = peer1.AddPeer()
    if best {
        t.Errorf("peer1 (TD=3) should not be set as best")
    }
    if blockPool.peer.id == "peer1" {
        t.Errorf("peer1 (TD=3) should not be set as best")
    }
    peer, best = blockPool.getPeer("peer1")
    if peer.td.Cmp(big.NewInt(int64(3))) != 0 {
        t.Errorf("peer1 TD should be updated")
    }

    blockPool.RemovePeer("peer2")
    peer, best = blockPool.getPeer("peer2")
    if peer != nil {
        t.Errorf("peer2 not removed")
    }

    if blockPool.peer.id != "peer1" {
        t.Errorf("existing peer1 (TD=3) should be set as best peer")
    }
    peer1.waitBlocksRequests(2)

    blockPool.RemovePeer("peer1")
    peer, best = blockPool.getPeer("peer1")
    if peer != nil {
        t.Errorf("peer1 not removed")
    }

    if blockPool.peer.id != "peer0" {
        t.Errorf("existing peer0 (TD=1) should be set as best peer")
    }
    peer0.waitBlocksRequests(0)

    blockPool.RemovePeer("peer0")
    peer, best = blockPool.getPeer("peer0")
    if peer != nil {
        t.Errorf("peer1 not removed")
    }

    // adding back earlier peer ok
    peer0.currentBlock = 3
    best = peer0.AddPeer()
    if !best {
        t.Errorf("peer0 (TD=1) should be set as best")
    }

    if blockPool.peer.id != "peer0" {
        t.Errorf("peer0 (TD=1) should be set as best")
    }
    peer0.waitBlocksRequests(3)

    blockPool.Stop()

}

func TestPeerWithKnownBlock(t *testing.T) {
    logInit()
    _, blockPool, blockPoolTester := newTestBlockPool(t)
    blockPoolTester.refBlockChain[0] = nil
    blockPoolTester.blockChain[0] = nil
    blockPool.Start()

    peer0 := blockPoolTester.newPeer("0", 1, 0)
    peer0.AddPeer()

    blockPool.Wait(waitTimeout * time.Second)
    blockPool.Stop()
    // no request on known block
    peer0.checkBlockHashesRequests()
}

func TestPeerWithKnownParentBlock(t *testing.T) {
    logInit()
    _, blockPool, blockPoolTester := newTestBlockPool(t)
    blockPoolTester.initRefBlockChain(1)
    blockPoolTester.blockChain[0] = nil
    blockPool.Start()

    peer0 := blockPoolTester.newPeer("0", 1, 1)
    peer0.AddPeer()
    peer0.AddBlocks(0, 1)

    blockPool.Wait(waitTimeout * time.Second)
    blockPool.Stop()
    peer0.checkBlocksRequests([]int{1})
    peer0.checkBlockHashesRequests()
    blockPoolTester.refBlockChain[1] = []int{}
    blockPoolTester.checkBlockChain(blockPoolTester.refBlockChain)
}

func TestSimpleChain(t *testing.T) {
    logInit()
    _, blockPool, blockPoolTester := newTestBlockPool(t)
    blockPoolTester.blockChain[0] = nil
    blockPoolTester.initRefBlockChain(2)

    blockPool.Start()

    peer1 := blockPoolTester.newPeer("peer1", 1, 2)
    peer1.AddPeer()
    peer1.AddBlocks(1, 2)
    go peer1.AddBlockHashes(2, 1, 0)
    peer1.AddBlocks(0, 1)

    blockPool.Wait(waitTimeout * time.Second)
    blockPool.Stop()
    blockPoolTester.refBlockChain[2] = []int{}
    blockPoolTester.checkBlockChain(blockPoolTester.refBlockChain)
}

func TestChainConnectingWithParentHash(t *testing.T) {
    logInit()
    _, blockPool, blockPoolTester := newTestBlockPool(t)
    blockPoolTester.blockChain[0] = nil
    blockPoolTester.initRefBlockChain(3)

    blockPool.Start()

    peer1 := blockPoolTester.newPeer("peer1", 1, 3)
    peer1.AddPeer()
    go peer1.AddBlocks(2, 3)
    go peer1.AddBlockHashes(3, 2, 1)
    peer1.AddBlocks(0, 1, 2)

    blockPool.Wait(waitTimeout * time.Second)
    blockPool.Stop()
    blockPoolTester.refBlockChain[3] = []int{}
    blockPoolTester.checkBlockChain(blockPoolTester.refBlockChain)
}

func TestInvalidBlock(t *testing.T) {
    logInit()
    _, blockPool, blockPoolTester := newTestBlockPool(t)
    blockPoolTester.blockChain[0] = nil
    blockPoolTester.initRefBlockChain(2)
    blockPoolTester.refBlockChain[2] = []int{}

    blockPool.Start()

    peer1 := blockPoolTester.newPeer("peer1", 1, 3)
    peer1.AddPeer()
    go peer1.AddBlocks(2, 3)
    go peer1.AddBlockHashes(3, 2, 1, 0)
    peer1.AddBlocks(0, 1, 2)

    blockPool.Wait(waitTimeout * time.Second)
    blockPool.Stop()
    blockPoolTester.refBlockChain[2] = []int{}
    blockPoolTester.checkBlockChain(blockPoolTester.refBlockChain)
    if len(peer1.peerErrors) == 1 {
        if peer1.peerErrors[0] != ErrInvalidBlock {
            t.Errorf("wrong error, got %v, expected %v", peer1.peerErrors[0], ErrInvalidBlock)
        }
    } else {
        t.Errorf("expected invalid block error, got nothing %v", peer1.peerErrors)
    }
}

func TestVerifyPoW(t *testing.T) {
    logInit()
    _, blockPool, blockPoolTester := newTestBlockPool(t)
    blockPoolTester.blockChain[0] = nil
    blockPoolTester.initRefBlockChain(3)
    first := false
    blockPoolTester.blockPool.verifyPoW = func(b pow.Block) bool {
        bb, _ := b.(*types.Block)
        indexes := blockPoolTester.hashPool.hashesToIndexes([][]byte{bb.Hash()})
        if indexes[0] == 2 && !first {
            first = true
            return false
        } else {
            return true
        }

    }

    blockPool.Start()

    peer1 := blockPoolTester.newPeer("peer1", 1, 3)
    peer1.AddPeer()
    go peer1.AddBlocks(2, 3)
    go peer1.AddBlockHashes(3, 2, 1, 0)
    peer1.AddBlocks(0, 1, 2)

    // blockPool.Wait(waitTimeout * time.Second)
    time.Sleep(1 * time.Second)
    blockPool.Stop()
    blockPoolTester.refBlockChain[1] = []int{}
    delete(blockPoolTester.refBlockChain, 2)
    blockPoolTester.checkBlockChain(blockPoolTester.refBlockChain)
    if len(peer1.peerErrors) == 1 {
        if peer1.peerErrors[0] != ErrInvalidPoW {
            t.Errorf("wrong error, got %v, expected %v", peer1.peerErrors[0], ErrInvalidPoW)
        }
    } else {
        t.Errorf("expected invalid pow error, got nothing")
    }
}

func TestMultiSectionChain(t *testing.T) {
    logInit()
    _, blockPool, blockPoolTester := newTestBlockPool(t)
    blockPoolTester.blockChain[0] = nil
    blockPoolTester.initRefBlockChain(5)

    blockPool.Start()

    peer1 := blockPoolTester.newPeer("peer1", 1, 5)

    peer1.AddPeer()
    go peer1.AddBlocks(4, 5)
    go peer1.AddBlockHashes(5, 4, 3)
    go peer1.AddBlocks(2, 3, 4)
    go peer1.AddBlockHashes(3, 2, 1, 0)
    peer1.AddBlocks(0, 1, 2)

    blockPool.Wait(waitTimeout * time.Second)
    blockPool.Stop()
    blockPoolTester.refBlockChain[5] = []int{}
    blockPoolTester.checkBlockChain(blockPoolTester.refBlockChain)
}

func TestNewBlocksOnPartialChain(t *testing.T) {
    logInit()
    _, blockPool, blockPoolTester := newTestBlockPool(t)
    blockPoolTester.blockChain[0] = nil
    blockPoolTester.initRefBlockChain(7)
    blockPool.Start()

    peer1 := blockPoolTester.newPeer("peer1", 1, 5)

    peer1.AddPeer()
    go peer1.AddBlocks(4, 5) // partially complete section
    go peer1.AddBlockHashes(5, 4, 3)
    peer1.AddBlocks(3, 4) // partially complete section
    // peer1 found new blocks
    peer1.td = 2
    peer1.currentBlock = 7
    peer1.AddPeer()
    go peer1.AddBlocks(6, 7)
    go peer1.AddBlockHashes(7, 6, 5)
    go peer1.AddBlocks(2, 3)
    go peer1.AddBlocks(5, 6)
    go peer1.AddBlockHashes(3, 2, 1, 0) // tests that hash request from known chain root is remembered
    peer1.AddBlocks(0, 1, 2)

    blockPool.Wait(waitTimeout * time.Second)
    blockPool.Stop()
    blockPoolTester.refBlockChain[7] = []int{}
    blockPoolTester.checkBlockChain(blockPoolTester.refBlockChain)
}

func TestPeerSwitchUp(t *testing.T) {
    logInit()
    _, blockPool, blockPoolTester := newTestBlockPool(t)
    blockPoolTester.blockChain[0] = nil
    blockPoolTester.initRefBlockChain(7)

    blockPool.Start()

    peer1 := blockPoolTester.newPeer("peer1", 1, 6)
    peer2 := blockPoolTester.newPeer("peer2", 2, 7)
    peer2.blocksRequestsMap = peer1.blocksRequestsMap

    peer1.AddPeer()
    go peer1.AddBlocks(5, 6)
    go peer1.AddBlockHashes(6, 5, 4, 3) //
    peer1.AddBlocks(2, 3)               // section partially complete, block 3 will be preserved after peer demoted
    peer2.AddPeer()                     // peer2 is promoted as best peer, peer1 is demoted
    go peer2.AddBlocks(6, 7)
    go peer2.AddBlockHashes(7, 6)       //
    go peer2.AddBlocks(4, 5)            // tests that block request for earlier section is remembered
    go peer1.AddBlocks(3, 4)            // tests that connecting section by demoted peer is remembered and blocks are accepted from demoted peer
    go peer2.AddBlockHashes(3, 2, 1, 0) // tests that known chain section is activated, hash requests from 3 is remembered
    peer2.AddBlocks(0, 1, 2)            // final blocks linking to blockchain sent

    blockPool.Wait(waitTimeout * time.Second)
    blockPool.Stop()
    blockPoolTester.refBlockChain[7] = []int{}
    blockPoolTester.checkBlockChain(blockPoolTester.refBlockChain)
}

func TestPeerSwitchDown(t *testing.T) {
    logInit()
    _, blockPool, blockPoolTester := newTestBlockPool(t)
    blockPoolTester.blockChain[0] = nil
    blockPoolTester.initRefBlockChain(6)
    blockPool.Start()

    peer1 := blockPoolTester.newPeer("peer1", 1, 4)
    peer2 := blockPoolTester.newPeer("peer2", 2, 6)
    peer2.blocksRequestsMap = peer1.blocksRequestsMap

    peer2.AddPeer()
    peer2.AddBlocks(5, 6)                  // partially complete, section will be preserved
    go peer2.AddBlockHashes(6, 5, 4)       //
    peer2.AddBlocks(4, 5)                  //
    blockPool.RemovePeer("peer2")          // peer2 disconnects
    peer1.AddPeer()                        // inferior peer1 is promoted as best peer
    go peer1.AddBlockHashes(4, 3, 2, 1, 0) //
    go peer1.AddBlocks(3, 4)               // tests that section set by demoted peer is remembered and blocks are accepted , this connects the chain sections together
    peer1.AddBlocks(0, 1, 2, 3)

    blockPool.Wait(waitTimeout * time.Second)
    blockPool.Stop()
    blockPoolTester.refBlockChain[6] = []int{}
    blockPoolTester.checkBlockChain(blockPoolTester.refBlockChain)
}

func TestPeerCompleteSectionSwitchDown(t *testing.T) {
    logInit()
    _, blockPool, blockPoolTester := newTestBlockPool(t)
    blockPoolTester.blockChain[0] = nil
    blockPoolTester.initRefBlockChain(6)
    blockPool.Start()

    peer1 := blockPoolTester.newPeer("peer1", 1, 4)
    peer2 := blockPoolTester.newPeer("peer2", 2, 6)
    peer2.blocksRequestsMap = peer1.blocksRequestsMap

    peer2.AddPeer()
    peer2.AddBlocks(5, 6)               // partially complete, section will be preserved
    go peer2.AddBlockHashes(6, 5, 4)    //
    peer2.AddBlocks(3, 4, 5)            // complete section
    blockPool.RemovePeer("peer2")       // peer2 disconnects
    peer1.AddPeer()                     // inferior peer1 is promoted as best peer
    peer1.AddBlockHashes(4, 3, 2, 1, 0) // tests that hash request are directly connecting if the head block exists
    peer1.AddBlocks(0, 1, 2, 3)

    blockPool.Wait(waitTimeout * time.Second)
    blockPool.Stop()
    blockPoolTester.refBlockChain[6] = []int{}
    blockPoolTester.checkBlockChain(blockPoolTester.refBlockChain)
}

func TestPeerSwitchBack(t *testing.T) {
    logInit()
    _, blockPool, blockPoolTester := newTestBlockPool(t)
    blockPoolTester.blockChain[0] = nil
    blockPoolTester.initRefBlockChain(8)

    blockPool.Start()

    peer1 := blockPoolTester.newPeer("peer1", 2, 11)
    peer2 := blockPoolTester.newPeer("peer2", 1, 8)
    peer2.blocksRequestsMap = peer1.blocksRequestsMap

    peer2.AddPeer()
    go peer2.AddBlocks(7, 8)
    go peer2.AddBlockHashes(8, 7, 6)
    go peer2.AddBlockHashes(6, 5, 4)
    peer2.AddBlocks(4, 5)                  // section partially complete
    peer1.AddPeer()                        // peer1 is promoted as best peer
    go peer1.AddBlocks(10, 11)             //
    peer1.AddBlockHashes(11, 10)           // only gives useless results
    blockPool.RemovePeer("peer1")          // peer1 disconnects
    go peer2.AddBlockHashes(4, 3, 2, 1, 0) // tests that asking for hashes from 4 is remembered
    go peer2.AddBlocks(3, 4, 5, 6, 7, 8)   // tests that section 4, 5, 6 and 7, 8 are remembered for missing blocks
    peer2.AddBlocks(0, 1, 2, 3)

    blockPool.Wait(waitTimeout * time.Second)
    blockPool.Stop()
    blockPoolTester.refBlockChain[8] = []int{}
    blockPoolTester.checkBlockChain(blockPoolTester.refBlockChain)
}

func TestForkSimple(t *testing.T) {
    logInit()
    _, blockPool, blockPoolTester := newTestBlockPool(t)
    blockPoolTester.blockChain[0] = nil
    blockPoolTester.initRefBlockChain(9)
    blockPoolTester.refBlockChain[3] = []int{4, 7}
    delete(blockPoolTester.refBlockChain, 6)

    blockPool.Start()

    peer1 := blockPoolTester.newPeer("peer1", 1, 9)
    peer2 := blockPoolTester.newPeer("peer2", 2, 6)
    peer2.blocksRequestsMap = peer1.blocksRequestsMap

    peer1.AddPeer()
    go peer1.AddBlocks(8, 9)
    go peer1.AddBlockHashes(9, 8, 7, 3, 2)
    peer1.AddBlocks(1, 2, 3, 7, 8)
    peer2.AddPeer()                        // peer2 is promoted as best peer
    go peer2.AddBlocks(5, 6)               //
    go peer2.AddBlockHashes(6, 5, 4, 3, 2) // fork on 3 -> 4 (earlier child: 7)
    go peer2.AddBlocks(1, 2, 3, 4, 5)
    go peer2.AddBlockHashes(2, 1, 0)
    peer2.AddBlocks(0, 1, 2)

    blockPool.Wait(waitTimeout * time.Second)
    blockPool.Stop()
    blockPoolTester.refBlockChain[6] = []int{}
    blockPoolTester.refBlockChain[3] = []int{4}
    delete(blockPoolTester.refBlockChain, 7)
    delete(blockPoolTester.refBlockChain, 8)
    delete(blockPoolTester.refBlockChain, 9)
    blockPoolTester.checkBlockChain(blockPoolTester.refBlockChain)

}

func TestForkSwitchBackByNewBlocks(t *testing.T) {
    logInit()
    _, blockPool, blockPoolTester := newTestBlockPool(t)
    blockPoolTester.blockChain[0] = nil
    blockPoolTester.initRefBlockChain(11)
    blockPoolTester.refBlockChain[3] = []int{4, 7}
    delete(blockPoolTester.refBlockChain, 6)

    blockPool.Start()

    peer1 := blockPoolTester.newPeer("peer1", 1, 9)
    peer2 := blockPoolTester.newPeer("peer2", 2, 6)
    peer2.blocksRequestsMap = peer1.blocksRequestsMap

    peer1.AddPeer()
    peer1.AddBlocks(8, 9)                  //
    go peer1.AddBlockHashes(9, 8, 7, 3, 2) //
    peer1.AddBlocks(7, 8)                  // partial section
    peer2.AddPeer()                        //
    peer2.AddBlocks(5, 6)                  //
    go peer2.AddBlockHashes(6, 5, 4, 3, 2) // peer2 forks on block 3
    peer2.AddBlocks(1, 2, 3, 4, 5)         //

    // peer1 finds new blocks
    peer1.td = 3
    peer1.currentBlock = 11
    peer1.AddPeer()
    go peer1.AddBlocks(10, 11)
    go peer1.AddBlockHashes(11, 10, 9)
    peer1.AddBlocks(9, 10)
    go peer1.AddBlocks(3, 7)         // tests that block requests on earlier fork are remembered
    go peer1.AddBlockHashes(2, 1, 0) // tests that hash request from root of connecting chain section (added by demoted peer) is remembered
    peer1.AddBlocks(0, 1)

    blockPool.Wait(waitTimeout * time.Second)
    blockPool.Stop()
    blockPoolTester.refBlockChain[11] = []int{}
    blockPoolTester.refBlockChain[3] = []int{7}
    delete(blockPoolTester.refBlockChain, 6)
    delete(blockPoolTester.refBlockChain, 5)
    delete(blockPoolTester.refBlockChain, 4)
    blockPoolTester.checkBlockChain(blockPoolTester.refBlockChain)

}

func TestForkSwitchBackByPeerSwitchBack(t *testing.T) {
    logInit()
    _, blockPool, blockPoolTester := newTestBlockPool(t)
    blockPoolTester.blockChain[0] = nil
    blockPoolTester.initRefBlockChain(9)
    blockPoolTester.refBlockChain[3] = []int{4, 7}
    delete(blockPoolTester.refBlockChain, 6)

    blockPool.Start()

    peer1 := blockPoolTester.newPeer("peer1", 1, 9)
    peer2 := blockPoolTester.newPeer("peer2", 2, 6)
    peer2.blocksRequestsMap = peer1.blocksRequestsMap

    peer1.AddPeer()
    go peer1.AddBlocks(8, 9)
    go peer1.AddBlockHashes(9, 8, 7, 3, 2)
    peer1.AddBlocks(7, 8)
    peer2.AddPeer()
    go peer2.AddBlocks(5, 6)               //
    go peer2.AddBlockHashes(6, 5, 4, 3, 2) // peer2 forks on block 3
    peer2.AddBlocks(2, 3, 4, 5)            //
    blockPool.RemovePeer("peer2")          // peer2 disconnects, peer1 is promoted again as best peer
    go peer1.AddBlocks(3, 7)               // tests that block requests on earlier fork are remembered and orphan section relinks to existing parent block
    go peer1.AddBlocks(1, 2)               //
    go peer1.AddBlockHashes(2, 1, 0)       //
    peer1.AddBlocks(0, 1)

    blockPool.Wait(waitTimeout * time.Second)
    blockPool.Stop()
    blockPoolTester.refBlockChain[9] = []int{}
    blockPoolTester.refBlockChain[3] = []int{7}
    delete(blockPoolTester.refBlockChain, 6)
    delete(blockPoolTester.refBlockChain, 5)
    delete(blockPoolTester.refBlockChain, 4)
    blockPoolTester.checkBlockChain(blockPoolTester.refBlockChain)

}

func TestForkCompleteSectionSwitchBackByPeerSwitchBack(t *testing.T) {
    logInit()
    _, blockPool, blockPoolTester := newTestBlockPool(t)
    blockPoolTester.blockChain[0] = nil
    blockPoolTester.initRefBlockChain(9)
    blockPoolTester.refBlockChain[3] = []int{4, 7}
    delete(blockPoolTester.refBlockChain, 6)

    blockPool.Start()

    peer1 := blockPoolTester.newPeer("peer1", 1, 9)
    peer2 := blockPoolTester.newPeer("peer2", 2, 6)
    peer2.blocksRequestsMap = peer1.blocksRequestsMap

    peer1.AddPeer()
    go peer1.AddBlocks(8, 9)
    go peer1.AddBlockHashes(9, 8, 7)
    peer1.AddBlocks(3, 7, 8) // make sure this section is complete
    time.Sleep(1 * time.Second)
    go peer1.AddBlockHashes(7, 3, 2)       // block 3/7 is section boundary
    peer1.AddBlocks(2, 3)                  // partially complete sections block 2 missing
    peer2.AddPeer()                        //
    go peer2.AddBlocks(5, 6)               //
    go peer2.AddBlockHashes(6, 5, 4, 3, 2) // peer2 forks on block 3
    peer2.AddBlocks(2, 3, 4, 5)            // block 2 still missing.
    blockPool.RemovePeer("peer2")          // peer2 disconnects, peer1 is promoted again as best peer
    // peer1.AddBlockHashes(7, 3)             // tests that hash request from fork root is remembered even though section process completed
    go peer1.AddBlockHashes(2, 1, 0) //
    peer1.AddBlocks(0, 1, 2)

    blockPool.Wait(waitTimeout * time.Second)
    blockPool.Stop()
    blockPoolTester.refBlockChain[9] = []int{}
    blockPoolTester.refBlockChain[3] = []int{7}
    delete(blockPoolTester.refBlockChain, 6)
    delete(blockPoolTester.refBlockChain, 5)
    delete(blockPoolTester.refBlockChain, 4)
    blockPoolTester.checkBlockChain(blockPoolTester.refBlockChain)

}