package spvchain_test import ( "fmt" "io/ioutil" "os" "testing" "time" "github.com/aakselrod/btctestlog" "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/rpctest" "github.com/btcsuite/btcd/wire" "github.com/btcsuite/btclog" "github.com/btcsuite/btcwallet/spvsvc/spvchain" "github.com/btcsuite/btcwallet/waddrmgr" "github.com/btcsuite/btcwallet/walletdb" _ "github.com/btcsuite/btcwallet/walletdb/bdb" ) var logLevel = btclog.TraceLvl func TestSetup(t *testing.T) { // Create a btcd SimNet node and generate 500 blocks h1, err := rpctest.New(&chaincfg.SimNetParams, nil, nil) if err != nil { t.Fatalf("Couldn't create harness: %s", err) } defer h1.TearDown() err = h1.SetUp(false, 0) if err != nil { t.Fatalf("Couldn't set up harness: %s", err) } _, err = h1.Node.Generate(500) if err != nil { t.Fatalf("Couldn't generate blocks: %s", err) } // Create a second btcd SimNet node h2, err := rpctest.New(&chaincfg.SimNetParams, nil, nil) if err != nil { t.Fatalf("Couldn't create harness: %s", err) } defer h2.TearDown() err = h2.SetUp(false, 0) if err != nil { t.Fatalf("Couldn't set up harness: %s", err) } // Create a third btcd SimNet node and generate 900 blocks h3, err := rpctest.New(&chaincfg.SimNetParams, nil, nil) if err != nil { t.Fatalf("Couldn't create harness: %s", err) } defer h3.TearDown() err = h3.SetUp(false, 0) if err != nil { t.Fatalf("Couldn't set up harness: %s", err) } _, err = h3.Node.Generate(900) if err != nil { t.Fatalf("Couldn't generate blocks: %s", err) } // Connect, sync, and disconnect h1 and h2 err = csd([]*rpctest.Harness{h1, h2}) if err != nil { t.Fatalf("Couldn't connect/sync/disconnect h1 and h2: %s", err) } // Generate 300 blocks on the first node and 350 on the second _, err = h1.Node.Generate(300) if err != nil { t.Fatalf("Couldn't generate blocks: %s", err) } _, err = h2.Node.Generate(350) if err != nil { t.Fatalf("Couldn't generate blocks: %s", err) } // Now we have a node with 800 blocks (h1), 850 blocks (h2), and // 900 blocks (h3). The chains of nodes h1 and h2 match up to block // 500. By default, a synchronizing wallet connected to all three // should synchronize to h3. However, we're going to take checkpoints // from h1 at 111, 333, 555, and 777, and add those to the // synchronizing wallet's chain parameters so that it should // disconnect from h3 at block 111, and from h2 at block 555, and // then synchronize to block 800 from h1. Order of connection is // unfortunately not guaranteed, so the reorg may not happen with every // test. // Copy parameters and insert checkpoints modParams := chaincfg.SimNetParams for _, height := range []int64{111, 333, 555, 777} { hash, err := h1.Node.GetBlockHash(height) if err != nil { t.Fatalf("Couldn't get block hash for height %d: %s", height, err) } modParams.Checkpoints = append(modParams.Checkpoints, chaincfg.Checkpoint{ Hash: hash, Height: int32(height), }) } // Create a temporary directory, initialize an empty walletdb with an // SPV chain namespace, and create a configuration for the ChainService. tempDir, err := ioutil.TempDir("", "spvchain") if err != nil { t.Fatalf("Failed to create temporary directory: %s", err) } defer os.RemoveAll(tempDir) db, err := walletdb.Create("bdb", tempDir+"/weks.db") defer db.Close() if err != nil { t.Fatalf("Error opening DB: %s\n", err) } ns, err := db.Namespace([]byte("weks")) if err != nil { t.Fatalf("Error geting namespace: %s\n", err) } config := spvchain.Config{ DataDir: tempDir, Namespace: ns, ChainParams: modParams, AddPeers: []string{ h3.P2PAddress(), h2.P2PAddress(), h1.P2PAddress(), }, } spvchain.Services = 0 spvchain.MaxPeers = 3 spvchain.BanDuration = 5 * time.Second spvchain.RequiredServices = wire.SFNodeNetwork logger, err := btctestlog.NewTestLogger(t) if err != nil { t.Fatalf("Could not set up logger: %s", err) } chainLogger := btclog.NewSubsystemLogger(logger, "CHAIN: ") chainLogger.SetLevel(logLevel) spvchain.UseLogger(chainLogger) svc, err := spvchain.NewChainService(config) if err != nil { t.Fatalf("Error creating ChainService: %s", err) } svc.Start() defer svc.Stop() // Make sure the client synchronizes with the correct node err = waitForSync(t, svc, h1, time.Second, 30*time.Second) if err != nil { t.Fatalf("Couldn't sync ChainService: %s", err) } // Generate 125 blocks on h1 to make sure it reorgs the other nodes. // Ensure the ChainService instance stays caught up. h1.Node.Generate(125) err = waitForSync(t, svc, h1, time.Second, 30*time.Second) if err != nil { t.Fatalf("Couldn't sync ChainService: %s", err) } // Connect/sync/disconnect h2 to make it reorg to the h1 chain. err = csd([]*rpctest.Harness{h1, h2}) if err != nil { t.Fatalf("Couldn't sync h2 to h1: %s", err) } // Generate 3 blocks on h1, one at a time, to make sure the // ChainService instance stays caught up. for i := 0; i < 3; i++ { h1.Node.Generate(1) err = waitForSync(t, svc, h1, time.Second, 30*time.Second) if err != nil { t.Fatalf("Couldn't sync ChainService: %s", err) } } // Generate 5 blocks on h2 and wait for ChainService to sync to the // newly-best chain on h2. h2.Node.Generate(5) err = waitForSync(t, svc, h2, time.Second, 30*time.Second) if err != nil { t.Fatalf("Couldn't sync ChainService: %s", err) } // Generate 7 blocks on h1 and wait for ChainService to sync to the // newly-best chain on h1. h1.Node.Generate(7) err = waitForSync(t, svc, h1, time.Second, 30*time.Second) if err != nil { t.Fatalf("Couldn't sync ChainService: %s", err) } } // csd does a connect-sync-disconnect between nodes in order to support // reorg testing. It brings up and tears down a temporary node, otherwise the // nodes try to reconnect to each other which results in unintended reorgs. func csd(harnesses []*rpctest.Harness) error { hTemp, err := rpctest.New(&chaincfg.SimNetParams, nil, nil) if err != nil { return err } // Tear down node at the end of the function. defer hTemp.TearDown() err = hTemp.SetUp(false, 0) if err != nil { return err } for _, harness := range harnesses { err = rpctest.ConnectNode(hTemp, harness) if err != nil { return err } } return rpctest.JoinNodes(harnesses, rpctest.Blocks) } // waitForSync waits for the ChainService to sync to the current chain state. func waitForSync(t *testing.T, svc *spvchain.ChainService, correctSyncNode *rpctest.Harness, checkInterval, timeout time.Duration) error { knownBestHash, knownBestHeight, err := correctSyncNode.Node.GetBestBlock() if err != nil { return err } if logLevel != btclog.Off { t.Logf("Syncing to %d (%s)", knownBestHeight, knownBestHash) } var haveBest *waddrmgr.BlockStamp haveBest, err = svc.BestSnapshot() if err != nil { return err } var total time.Duration for haveBest.Hash != *knownBestHash { if total > timeout { return fmt.Errorf("Timed out after %v.", timeout) } if haveBest.Height > knownBestHeight { return fmt.Errorf("Synchronized to the wrong chain.") } time.Sleep(checkInterval) total += checkInterval haveBest, err = svc.BestSnapshot() if err != nil { return fmt.Errorf("Couldn't get best snapshot from "+ "ChainService: %s", err) } if logLevel != btclog.Off { t.Logf("Synced to %d (%s)", haveBest.Height, haveBest.Hash) } } // Check if we're current if !svc.IsCurrent() { return fmt.Errorf("ChainService doesn't see itself as current!") } return nil }