github.com/avahowell/sia@v0.5.1-beta.0.20160524050156-83dcc3d37c94/modules/consensus/synchronize_ibd_test.go (about)

     1  package consensus
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"io"
     7  	"net"
     8  	"path/filepath"
     9  	"strconv"
    10  	"sync"
    11  	"testing"
    12  	"time"
    13  
    14  	"github.com/NebulousLabs/Sia/build"
    15  	"github.com/NebulousLabs/Sia/modules"
    16  	"github.com/NebulousLabs/Sia/modules/gateway"
    17  )
    18  
    19  // TestSimpleInitialBlockchainDownload tests that
    20  // threadedInitialBlockchainDownload synchronizes with peers in the simple case
    21  // where there are 8 outbound peers with the same blockchain.
    22  func TestSimpleInitialBlockchainDownload(t *testing.T) {
    23  	if testing.Short() {
    24  		t.SkipNow()
    25  	}
    26  
    27  	// Create 8 remote peers.
    28  	remoteCSTs := make([]*consensusSetTester, 8)
    29  	for i := range remoteCSTs {
    30  		cst, err := blankConsensusSetTester(fmt.Sprintf("TestSimpleInitialBlockchainDownload - %v", i))
    31  		if err != nil {
    32  			t.Fatal(err)
    33  		}
    34  		defer cst.Close()
    35  		remoteCSTs[i] = cst
    36  	}
    37  	// Create the "local" peer.
    38  	localCST, err := blankConsensusSetTester("TestSimpleInitialBlockchainDownload - local")
    39  	if err != nil {
    40  		t.Fatal(err)
    41  	}
    42  	defer localCST.Close()
    43  	for _, cst := range remoteCSTs {
    44  		err = localCST.cs.gateway.Connect(cst.cs.gateway.Address())
    45  		if err != nil {
    46  			t.Fatal(err)
    47  		}
    48  	}
    49  	// Give the OnConnectRPCs time to finish.
    50  	time.Sleep(1 * time.Second)
    51  
    52  	// Test IBD when all peers have only the genesis block.
    53  	doneChan := make(chan struct{})
    54  	go func() {
    55  		localCST.cs.threadedInitialBlockchainDownload()
    56  		doneChan <- struct{}{}
    57  	}()
    58  	select {
    59  	case <-doneChan:
    60  	case <-time.After(5 * time.Second):
    61  		t.Fatal("initialBlockchainDownload never completed")
    62  	}
    63  	if localCST.cs.CurrentBlock().ID() != remoteCSTs[0].cs.CurrentBlock().ID() {
    64  		t.Fatalf("current block ids do not match: expected '%v', got '%v'", remoteCSTs[0].cs.CurrentBlock().ID(), localCST.cs.CurrentBlock().ID())
    65  	}
    66  
    67  	// Test IBD when all remote peers have the same longest chain.
    68  	for i := 0; i < 20; i++ {
    69  		b, err := remoteCSTs[0].miner.FindBlock()
    70  		if err != nil {
    71  			t.Fatal(err)
    72  		}
    73  		for _, cst := range remoteCSTs {
    74  			err = cst.cs.managedAcceptBlock(b)
    75  			if err != nil {
    76  				t.Fatal(err)
    77  			}
    78  		}
    79  	}
    80  	go func() {
    81  		localCST.cs.threadedInitialBlockchainDownload()
    82  		doneChan <- struct{}{}
    83  	}()
    84  	select {
    85  	case <-doneChan:
    86  	case <-time.After(5 * time.Second):
    87  		t.Fatal("initialBlockchainDownload never completed")
    88  	}
    89  	if localCST.cs.CurrentBlock().ID() != remoteCSTs[0].cs.CurrentBlock().ID() {
    90  		t.Fatalf("current block ids do not match: expected '%v', got '%v'", remoteCSTs[0].cs.CurrentBlock().ID(), localCST.cs.CurrentBlock().ID())
    91  	}
    92  
    93  	// Test IBD when not starting from the genesis block.
    94  	for i := 0; i < 4; i++ {
    95  		b, err := remoteCSTs[0].miner.FindBlock()
    96  		if err != nil {
    97  			t.Fatal(err)
    98  		}
    99  		for _, cst := range remoteCSTs {
   100  			err = cst.cs.managedAcceptBlock(b)
   101  			if err != nil {
   102  				t.Fatal(err)
   103  			}
   104  		}
   105  	}
   106  	go func() {
   107  		localCST.cs.threadedInitialBlockchainDownload()
   108  		doneChan <- struct{}{}
   109  	}()
   110  	select {
   111  	case <-doneChan:
   112  	case <-time.After(5 * time.Second):
   113  		t.Fatal("initialBlockchainDownload never completed")
   114  	}
   115  	if localCST.cs.CurrentBlock().ID() != remoteCSTs[0].cs.CurrentBlock().ID() {
   116  		t.Fatalf("current block ids do not match: expected '%v', got '%v'", remoteCSTs[0].cs.CurrentBlock().ID(), localCST.cs.CurrentBlock().ID())
   117  	}
   118  
   119  	// Test IBD when the remote peers are on a longer fork.
   120  	for i := 0; i < 5; i++ {
   121  		b, err := localCST.miner.FindBlock()
   122  		if err != nil {
   123  			t.Fatal(err)
   124  		}
   125  		err = localCST.cs.managedAcceptBlock(b)
   126  		if err != nil {
   127  			t.Fatal(err)
   128  		}
   129  	}
   130  	for i := 0; i < 10; i++ {
   131  		b, err := remoteCSTs[0].miner.FindBlock()
   132  		if err != nil {
   133  			t.Fatal(err)
   134  		}
   135  		for _, cst := range remoteCSTs {
   136  			err = cst.cs.managedAcceptBlock(b)
   137  			if err != nil {
   138  				t.Fatal(err)
   139  			}
   140  		}
   141  	}
   142  	go func() {
   143  		localCST.cs.threadedInitialBlockchainDownload()
   144  		doneChan <- struct{}{}
   145  	}()
   146  	select {
   147  	case <-doneChan:
   148  	case <-time.After(5 * time.Second):
   149  		t.Fatal("initialBlockchainDownload never completed")
   150  	}
   151  	if localCST.cs.CurrentBlock().ID() != remoteCSTs[0].cs.CurrentBlock().ID() {
   152  		t.Fatalf("current block ids do not match: expected '%v', got '%v'", remoteCSTs[0].cs.CurrentBlock().ID(), localCST.cs.CurrentBlock().ID())
   153  	}
   154  
   155  	// Test IBD when the remote peers are on a shorter fork.
   156  	for i := 0; i < 10; i++ {
   157  		b, err := localCST.miner.FindBlock()
   158  		if err != nil {
   159  			t.Fatal(err)
   160  		}
   161  		err = localCST.cs.managedAcceptBlock(b)
   162  		if err != nil {
   163  			t.Fatal(err)
   164  		}
   165  	}
   166  	for i := 0; i < 5; i++ {
   167  		b, err := remoteCSTs[0].miner.FindBlock()
   168  		if err != nil {
   169  			t.Fatal(err)
   170  		}
   171  		for _, cst := range remoteCSTs {
   172  			err = cst.cs.managedAcceptBlock(b)
   173  			if err != nil {
   174  				t.Fatal(err)
   175  			}
   176  		}
   177  	}
   178  	localCurrentBlock := localCST.cs.CurrentBlock()
   179  	go func() {
   180  		localCST.cs.threadedInitialBlockchainDownload()
   181  		doneChan <- struct{}{}
   182  	}()
   183  	select {
   184  	case <-doneChan:
   185  	case <-time.After(5 * time.Second):
   186  		t.Fatal("initialBlockchainDownload never completed")
   187  	}
   188  	if localCST.cs.CurrentBlock().ID() != localCurrentBlock.ID() {
   189  		t.Fatalf("local was on a longer fork and should not have reorged")
   190  	}
   191  	if localCST.cs.CurrentBlock().ID() == remoteCSTs[0].cs.CurrentBlock().ID() {
   192  		t.Fatalf("ibd syncing is one way, and a longer fork on the local cs should not cause a reorg on the remote cs's")
   193  	}
   194  }
   195  
   196  type mockGatewayRPCError struct {
   197  	modules.Gateway
   198  	rpcErrs map[modules.NetAddress]error
   199  	mu      sync.Mutex
   200  }
   201  
   202  func (g *mockGatewayRPCError) RPC(addr modules.NetAddress, name string, fn modules.RPCFunc) error {
   203  	g.mu.Lock()
   204  	defer g.mu.Unlock()
   205  	return g.rpcErrs[addr]
   206  }
   207  
   208  // TestInitialBlockChainDownloadDisconnects tests that
   209  // threadedInitialBlockchainDownload only disconnects from peers that error
   210  // with anything but a timeout.
   211  func TestInitialBlockchainDownloadDisconnects(t *testing.T) {
   212  	if testing.Short() {
   213  		t.SkipNow()
   214  	}
   215  
   216  	testdir := build.TempDir(modules.ConsensusDir, "TestInitialBlockchainDownloadDisconnects")
   217  	g, err := gateway.New("localhost:0", filepath.Join(testdir, "local", modules.GatewayDir))
   218  	if err != nil {
   219  		t.Fatal(err)
   220  	}
   221  	defer g.Close()
   222  	mg := mockGatewayRPCError{
   223  		Gateway: g,
   224  		rpcErrs: make(map[modules.NetAddress]error),
   225  	}
   226  	localCS, err := New(&mg, filepath.Join(testdir, "local", modules.ConsensusDir))
   227  	if err != nil {
   228  		t.Fatal(err)
   229  	}
   230  	defer localCS.Close()
   231  
   232  	rpcErrs := []error{
   233  		// rpcErrs that should cause a a disconnect.
   234  		io.EOF,
   235  		errors.New("random error"),
   236  		errSendBlocksStalled,
   237  		// rpcErrs that should not cause a disconnect.
   238  		mockNetError{
   239  			error:   errors.New("Read timeout"),
   240  			timeout: true,
   241  		},
   242  		// Need at least minNumOutbound peers that return nil for
   243  		// threadedInitialBlockchainDownload to mark IBD done.
   244  		nil, nil, nil, nil, nil,
   245  	}
   246  	for i, rpcErr := range rpcErrs {
   247  		g, err := gateway.New("localhost:0", filepath.Join(testdir, "remote - "+strconv.Itoa(i), modules.GatewayDir))
   248  		if err != nil {
   249  			t.Fatal(err)
   250  		}
   251  		defer g.Close()
   252  		err = localCS.gateway.Connect(g.Address())
   253  		if err != nil {
   254  			t.Fatal(err)
   255  		}
   256  		mg.rpcErrs[g.Address()] = rpcErr
   257  	}
   258  	// Sleep to to give the OnConnectRPCs time to finish.
   259  	time.Sleep(500 * time.Millisecond)
   260  	// Do IBD.
   261  	localCS.threadedInitialBlockchainDownload()
   262  	// Check that localCS disconnected from peers that errored but did not time out during SendBlocks.
   263  	for _, p := range localCS.gateway.Peers() {
   264  		err = mg.rpcErrs[p.NetAddress]
   265  		if err == nil {
   266  			continue
   267  		}
   268  		if netErr, ok := err.(net.Error); ok && netErr.Timeout() {
   269  			continue
   270  		}
   271  		t.Fatalf("threadedInitialBlockchainDownload didn't disconnect from a peer that returned '%v'", err)
   272  	}
   273  	if len(localCS.gateway.Peers()) != 6 {
   274  		t.Error("threadedInitialBlockchainDownload disconnected from peers that timedout or didn't error")
   275  	}
   276  }
   277  
   278  // TestInitialBlockchainDownloadDoneRules tests that
   279  // threadedInitialBlockchainDownload only terminates under the appropriate
   280  // conditions. Appropriate conditions are:
   281  //  - at least minNumOutbound synced outbound peers
   282  //  - or at least 1 synced outbound peer and minIBDWaitTime has passed since beginning IBD.
   283  func TestInitialBlockchainDownloadDoneRules(t *testing.T) {
   284  	if testing.Short() {
   285  		t.SkipNow()
   286  	}
   287  
   288  	// Set minIBDWaitTime to 1s for just this test because no blocks are
   289  	// transferred between peers so the wait time can be very short.
   290  	actualMinIBDWaitTime := minIBDWaitTime
   291  	defer func() {
   292  		minIBDWaitTime = actualMinIBDWaitTime
   293  	}()
   294  	minIBDWaitTime = 1 * time.Second
   295  
   296  	testdir := build.TempDir(modules.ConsensusDir, "TestInitialBlockchainDownloadDoneRules")
   297  	g, err := gateway.New("localhost:0", filepath.Join(testdir, "local", modules.GatewayDir))
   298  	if err != nil {
   299  		t.Fatal(err)
   300  	}
   301  	defer g.Close()
   302  	mg := mockGatewayRPCError{
   303  		Gateway: g,
   304  		rpcErrs: make(map[modules.NetAddress]error),
   305  	}
   306  	cs, err := New(&mg, filepath.Join(testdir, "local", modules.ConsensusDir))
   307  	if err != nil {
   308  		t.Fatal(err)
   309  	}
   310  	defer cs.Close()
   311  
   312  	doneChan := make(chan struct{})
   313  
   314  	// Test when there are 0 peers.
   315  	go func() {
   316  		cs.threadedInitialBlockchainDownload()
   317  		doneChan <- struct{}{}
   318  	}()
   319  	select {
   320  	case <-doneChan:
   321  		t.Error("threadedInitialBlockchainDownload finished with 0 synced peers")
   322  	case <-time.After(minIBDWaitTime + ibdLoopDelay):
   323  	}
   324  
   325  	// Test when there are only inbound peers.
   326  	inboundCSTs := make([]*consensusSetTester, 8)
   327  	for i := 0; i < len(inboundCSTs); i++ {
   328  		inboundCST, err := blankConsensusSetTester(filepath.Join("TestInitialBlockchainDownloadDoneRules", fmt.Sprintf("remote - inbound %v", i)))
   329  		if err != nil {
   330  			t.Fatal(err)
   331  		}
   332  		defer inboundCST.Close()
   333  
   334  		inboundCST.cs.gateway.Connect(cs.gateway.Address())
   335  	}
   336  	select {
   337  	case <-doneChan:
   338  		t.Error("threadedInitialBlockchainDownload finished with only inbound peers")
   339  	case <-time.After(minIBDWaitTime + ibdLoopDelay):
   340  	}
   341  
   342  	// Test when there is 1 peer that isn't synced.
   343  	gatewayTimesout, err := gateway.New("localhost:0", filepath.Join(testdir, "remote - timesout", modules.GatewayDir))
   344  	if err != nil {
   345  		t.Fatal(err)
   346  	}
   347  	defer gatewayTimesout.Close()
   348  	mg.mu.Lock()
   349  	mg.rpcErrs[gatewayTimesout.Address()] = mockNetError{
   350  		error:   errors.New("Read timeout"),
   351  		timeout: true,
   352  	}
   353  	mg.mu.Unlock()
   354  	err = cs.gateway.Connect(gatewayTimesout.Address())
   355  	if err != nil {
   356  		t.Fatal(err)
   357  	}
   358  	select {
   359  	case <-doneChan:
   360  		t.Error("threadedInitialBlockchainDownload finished with 0 synced peers")
   361  	case <-time.After(minIBDWaitTime + ibdLoopDelay):
   362  	}
   363  
   364  	// Test when there is 1 peer that is synced and one that is not synced.
   365  	gatewayNoTimeout, err := gateway.New("localhost:0", filepath.Join(testdir, "remote - no timeout", modules.GatewayDir))
   366  	if err != nil {
   367  		t.Fatal(err)
   368  	}
   369  	defer gatewayNoTimeout.Close()
   370  	mg.mu.Lock()
   371  	mg.rpcErrs[gatewayNoTimeout.Address()] = nil
   372  	mg.mu.Unlock()
   373  	err = cs.gateway.Connect(gatewayNoTimeout.Address())
   374  	if err != nil {
   375  		t.Fatal(err)
   376  	}
   377  	select {
   378  	case <-doneChan:
   379  	case <-time.After(minIBDWaitTime + ibdLoopDelay):
   380  		t.Fatal("threadedInitialBlockchainDownload never finished with 1 synced peer")
   381  	}
   382  
   383  	// Test when there are >= minNumOutbound peers, but < minNumOutbound peers are synced.
   384  	gatewayTimesouts := make([]modules.Gateway, minNumOutbound-1)
   385  	for i := 0; i < len(gatewayTimesouts); i++ {
   386  		tmpG, err := gateway.New("localhost:0", filepath.Join(testdir, fmt.Sprintf("remote - timesout %v", i), modules.GatewayDir))
   387  		if err != nil {
   388  			t.Fatal(err)
   389  		}
   390  		defer tmpG.Close()
   391  		mg.mu.Lock()
   392  		mg.rpcErrs[tmpG.Address()] = mockNetError{
   393  			error:   errors.New("Write timeout"),
   394  			timeout: true,
   395  		}
   396  		mg.mu.Unlock()
   397  		gatewayTimesouts[i] = tmpG
   398  		err = cs.gateway.Connect(gatewayTimesouts[i].Address())
   399  		if err != nil {
   400  			t.Fatal(err)
   401  		}
   402  	}
   403  	go func() {
   404  		cs.threadedInitialBlockchainDownload()
   405  		doneChan <- struct{}{}
   406  	}()
   407  	select {
   408  	case <-doneChan:
   409  		t.Fatal("threadedInitialBlockchainDownload finished before minIBDWaitTime")
   410  	case <-time.After(minIBDWaitTime):
   411  	}
   412  	select {
   413  	case <-doneChan:
   414  	case <-time.After(minIBDWaitTime + ibdLoopDelay):
   415  		t.Fatal("threadedInitialBlockchainDownload didn't finish after minIBDWaitTime")
   416  	}
   417  
   418  	// Test when there are >= minNumOutbound peers and >= minNumOutbound peers are synced.
   419  	gatewayNoTimeouts := make([]modules.Gateway, minNumOutbound-1)
   420  	for i := 0; i < len(gatewayNoTimeouts); i++ {
   421  		tmpG, err := gateway.New("localhost:0", filepath.Join(testdir, fmt.Sprintf("remote - no timeout %v", i), modules.GatewayDir))
   422  		if err != nil {
   423  			t.Fatal(err)
   424  		}
   425  		defer tmpG.Close()
   426  		mg.mu.Lock()
   427  		mg.rpcErrs[tmpG.Address()] = nil
   428  		mg.mu.Unlock()
   429  		gatewayNoTimeouts[i] = tmpG
   430  		err = cs.gateway.Connect(gatewayNoTimeouts[i].Address())
   431  		if err != nil {
   432  			t.Fatal(err)
   433  		}
   434  	}
   435  	go func() {
   436  		cs.threadedInitialBlockchainDownload()
   437  		doneChan <- struct{}{}
   438  	}()
   439  	select {
   440  	case <-doneChan:
   441  	case <-time.After(minIBDWaitTime):
   442  		t.Fatal("threadedInitialBlockchainDownload didn't finish in less than minIBDWaitTime")
   443  	}
   444  }