@ -1,115 +1,65 @@
package eth
import (
"bytes"
"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"
)
var sys = ethlogger . NewStdLogSystem ( os . Stdout , log . LstdFlags , ethlogger . LogLevel ( ethlogger . DebugDetailLevel ) )
const waitTimeout = 60 // seconds
type testChainManager struct {
knownBlock func ( hash [ ] byte ) bool
addBlock func ( * types . Block ) error
checkPoW func ( * types . Block ) bool
}
func ( self * testChainManager ) KnownBlock ( hash [ ] byte ) bool {
if self . knownBlock != nil {
return self . knownBlock ( hash )
}
return false
}
var logsys = ethlogger . NewStdLogSystem ( os . Stdout , log . LstdFlags , ethlogger . LogLevel ( ethlogger . DebugLevel ) )
func ( self * testChainManager ) AddBlock ( block * types . Block ) error {
if self . addBlock != nil {
return self . addBlock ( block )
}
return nil
}
var ini = false
func ( self * testChainManager ) CheckPoW ( block * types . Block ) bool {
if self . checkPoW != nil {
return self . checkPoW ( block )
func logInit ( ) {
if ! ini {
ethlogger . AddLogSystem ( logsys )
ini = true
}
return false
}
func knownBlock ( hashes ... [ ] byte ) ( f func ( [ ] byte ) bool ) {
f = func ( block [ ] byte ) bool {
for _ , hash := range hashes {
if bytes . Compare ( block , hash ) == 0 {
return true
}
}
// test helpers
func arrayEq ( a , b [ ] int ) bool {
if len ( a ) != len ( b ) {
return false
}
return
}
func addBlock ( hashes ... [ ] byte ) ( f func ( * types . Block ) error ) {
f = func ( block * types . Block ) error {
for _ , hash := range hashes {
if bytes . Compare ( block . Hash ( ) , hash ) == 0 {
return fmt . Errorf ( "invalid by test" )
}
}
return nil
}
return
}
func checkPoW ( hashes ... [ ] byte ) ( f func ( * types . Block ) bool ) {
f = func ( block * types . Block ) bool {
for _ , hash := range hashes {
if bytes . Compare ( block . Hash ( ) , hash ) == 0 {
return false
}
for i := range a {
if a [ i ] != b [ i ] {
return false
}
return true
}
return
}
func newTestChainManager ( knownBlocks [ ] [ ] byte , invalidBlocks [ ] [ ] byte , invalidPoW [ ] [ ] byte ) * testChainManager {
return & testChainManager {
knownBlock : knownBlock ( knownBlocks ... ) ,
addBlock : addBlock ( invalidBlocks ... ) ,
checkPoW : checkPoW ( invalidPoW ... ) ,
}
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 newTestBlockPool ( knownBlockIndexes [ ] int , invalidBlockIndexes [ ] int , invalidPoWIndexes [ ] int ) ( hashPool * testHashPool , blockPool * BlockPool ) {
hashPool = & testHashPool { make ( intToHash ) , make ( hashToInt ) }
knownBlocks := hashPool . indexesToHashes ( knownBlockIndexes )
invalidBlocks := hashPool . indexesToHashes ( invalidBlockIndexes )
invalidPoW := hashPool . indexesToHashes ( invalidPoWIndexes )
blockPool = NewBlockPool ( newTestChainManager ( knownBlocks , invalidBlocks , invalidPoW ) )
return
}
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 {
@ -123,6 +73,8 @@ func (self *testHashPool) indexesToHashes(indexes []int) (hashes [][]byte) {
}
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 {
@ -133,66 +85,812 @@ func (self *testHashPool) hashesToIndexes(hashes [][]byte) (indexes []int) {
return
}
type protocolChecker struct {
// 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
invalidBlocks [ ] error
blocksRequestsMap map [ int ] bool
peerErrors [ ] int
blockPool * BlockPool
hashPool * testHashPool
lock sync . Mutex
lock sync . RWMutex
id string
td int
currentBlock int
t * testing . T
}
// -1 is special: not found (a hash never seen)
func ( self * protocolChecker ) requestBlockHashesCallBack ( ) ( requestBlockHashesCallBack func ( [ ] byte ) error ) {
requestBlockHashesCallBack = func ( hash [ ] byte ) error {
indexes := self . hashPool . hashesToIndexes ( [ ] [ ] byte { hash } )
self . lock . Lock ( )
defer self . lock . Unlock ( )
self . blockHashesRequests = append ( self . blockHashesRequests , indexes [ 0 ] )
return nil
// 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 ) ,
}
return
}
func ( self * protocolChecker ) requestBlocksCallBack ( ) ( requestBlocksCallBack func ( [ ] [ ] byte ) error ) {
requestBlocksCallBack = func ( hashes [ ] [ ] byte ) error {
indexes := self . hashPool . hashesToIndexes ( hashes )
self . lock . Lock ( )
defer self . lock . Unlock ( )
self . blocksRequests = append ( self . blocksRequests , indexes )
return nil
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 )
}
}
}
return
}
func ( self * protocolChecker ) invalidBlockCallBack ( ) ( invalidBlockCallBack func ( error ) ) {
invalidBlockCallBack = func ( err error ) {
self . invalidBlocks = append ( self . invalidBlocks , err )
// 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 :
}
}
return
}
// 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 ) {
i := 0
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 )
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 ) {
ethlogger . AddLogSystem ( sys )
knownBlockIndexes := [ ] int { 0 , 1 }
invalidBlockIndexes := [ ] int { 2 , 3 }
invalidPoWIndexes := [ ] int { 4 , 5 }
hashPool , blockPool := newTestBlockPool ( knownBlockIndexes , invalidBlockIndexes , invalidPoWIndexes )
// TODO:
// hashPool, blockPool, blockChainChecker = newTestBlockPool(knownBlockIndexes, invalidBlockIndexes, invalidPoWIndexes)
peer0 := & protocolChecker {
// blockHashesRequests: make([]int),
// blocksRequests: make([][]int),
// invalidBlocks: make([]error),
hashPool : hashPool ,
}
best := blockPool . AddPeer ( ethutil . Big1 , newHash ( 100 ) , "0" ,
peer0 . requestBlockHashesCallBack ( ) ,
peer0 . requestBlocksCallBack ( ) ,
peer0 . invalidBlockCallBack ( ) ,
)
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 . checkBlockHashesRequests ( 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 ( "peer not accepted as 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 . checkBlockHashesRequests ( 2 , 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 . checkBlockHashesRequests ( 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" )
}
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 . checkBlockHashesRequests ( 0 , 0 , 3 )
blockPool . Stop ( )
}
func TestPeerWithKnownBlock ( t * testing . T ) {
logInit ( )
_ , blockPool , blockPoolTester := newTestBlockPool ( t )
blockPoolTester . refBlockChain [ 0 ] = nil
blockPoolTester . blockChain [ 0 ] = nil
// hashPool, blockPool, blockPoolTester := newTestBlockPool()
blockPool . Start ( )
peer0 := blockPoolTester . newPeer ( "0" , 1 , 0 )
peer0 . AddPeer ( )
blockPool . Stop ( )
// no request on known block
peer0 . checkBlockHashesRequests ( )
}
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 ( )
go peer1 . AddBlockHashes ( 2 , 1 , 0 )
peer1 . AddBlocks ( 0 , 1 , 2 )
blockPool . Wait ( waitTimeout * time . Second )
blockPool . Stop ( )
blockPoolTester . refBlockChain [ 2 ] = [ ] 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 . AddBlockHashes ( 3 , 2 , 1 , 0 )
peer1 . AddBlocks ( 0 , 1 , 2 , 3 )
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" )
}
}
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 ] == 1 && ! first {
first = true
return false
} else {
return true
}
}
blockPool . Start ( )
peer1 := blockPoolTester . newPeer ( "peer1" , 1 , 2 )
peer1 . AddPeer ( )
go peer1 . AddBlockHashes ( 2 , 1 , 0 )
peer1 . AddBlocks ( 0 , 1 , 2 )
peer1 . AddBlocks ( 0 , 1 )
blockPool . Wait ( waitTimeout * time . Second )
blockPool . Stop ( )
blockPoolTester . refBlockChain [ 2 ] = [ ] int { }
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 . AddBlockHashes ( 5 , 4 , 3 )
go peer1 . AddBlocks ( 2 , 3 , 4 , 5 )
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 . AddBlockHashes ( 5 , 4 , 3 )
peer1 . AddBlocks ( 2 , 3 ) // partially complete section
// peer1 found new blocks
peer1 . td = 2
peer1 . currentBlock = 7
peer1 . AddPeer ( )
go peer1 . AddBlockHashes ( 7 , 6 , 5 )
go peer1 . AddBlocks ( 3 , 4 , 5 , 6 , 7 )
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 TestPeerSwitch ( t * testing . T ) {
logInit ( )
_ , blockPool , blockPoolTester := newTestBlockPool ( t )
blockPoolTester . blockChain [ 0 ] = nil
blockPoolTester . initRefBlockChain ( 6 )
blockPool . Start ( )
peer1 := blockPoolTester . newPeer ( "peer1" , 1 , 5 )
peer2 := blockPoolTester . newPeer ( "peer2" , 2 , 6 )
peer2 . blocksRequestsMap = peer1 . blocksRequestsMap
peer1 . AddPeer ( )
go peer1 . AddBlockHashes ( 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 . AddBlockHashes ( 6 , 5 ) //
go peer2 . AddBlocks ( 4 , 5 , 6 ) // 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 [ 6 ] = [ ] int { }
blockPoolTester . checkBlockChain ( blockPoolTester . refBlockChain )
}
func TestPeerDownSwitch ( 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 ( )
go peer2 . AddBlockHashes ( 6 , 5 , 4 )
peer2 . AddBlocks ( 5 , 6 ) // partially complete, section will be preserved
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 , 5 ) // tests that section set by demoted peer is remembered and blocks are accepted
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 . 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 . 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 . AddBlockHashes ( 9 , 8 , 7 , 3 , 2 )
peer1 . AddBlocks ( 1 , 2 , 3 , 7 , 8 , 9 )
peer2 . AddPeer ( ) // peer2 is promoted as best peer
go peer2 . AddBlockHashes ( 6 , 5 , 4 , 3 , 2 ) // fork on 3 -> 4 (earlier child: 7)
go peer2 . AddBlocks ( 1 , 2 , 3 , 4 , 5 , 6 )
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 ( )
go peer1 . AddBlockHashes ( 9 , 8 , 7 , 3 , 2 )
peer1 . AddBlocks ( 8 , 9 ) // partial section
peer2 . AddPeer ( ) //
go peer2 . AddBlockHashes ( 6 , 5 , 4 , 3 , 2 ) // peer2 forks on block 3
peer2 . AddBlocks ( 1 , 2 , 3 , 4 , 5 , 6 ) //
// peer1 finds new blocks
peer1 . td = 3
peer1 . currentBlock = 11
peer1 . AddPeer ( )
go peer1 . AddBlockHashes ( 11 , 10 , 9 )
peer1 . AddBlocks ( 7 , 8 , 9 , 10 , 11 )
go peer1 . AddBlockHashes ( 7 , 3 ) // tests that hash request from fork root is remembered
go peer1 . AddBlocks ( 3 , 7 ) // tests that block requests on earlier fork are remembered
// go peer1.AddBlockHashes(1, 0) // tests that hash request from root of connecting chain section (added by demoted peer) is 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 , 2 , 3 )
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 . AddBlockHashes ( 9 , 8 , 7 , 3 , 2 )
peer1 . AddBlocks ( 8 , 9 )
peer2 . AddPeer ( ) //
go peer2 . AddBlockHashes ( 6 , 5 , 4 , 3 , 2 ) // peer2 forks on block 3
peer2 . AddBlocks ( 2 , 3 , 4 , 5 , 6 ) //
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
go peer1 . AddBlocks ( 3 , 7 , 8 ) // tests that block requests on earlier fork are remembered
go peer1 . AddBlockHashes ( 2 , 1 , 0 ) //
peer1 . AddBlocks ( 0 , 1 , 2 , 3 )
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 . AddBlockHashes ( 9 , 8 , 7 )
peer1 . AddBlocks ( 3 , 7 , 8 , 9 ) // 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
peer2 . AddPeer ( ) //
go peer2 . AddBlockHashes ( 6 , 5 , 4 , 3 , 2 ) // peer2 forks on block 3
peer2 . AddBlocks ( 2 , 3 , 4 , 5 , 6 ) // 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 )
}