[NOD-1210] Add integration test for IBD and fix bug where requestSelectedTipsIfRequired ran in handshake's goroutine (#834)

* [NOD-1162] Separate kaspad to it's own package, so that I can use it out of integration test

* [NOD-1162] Begin integration tests

* [NOD-1162] [FIX] Assign cfg to RPCServer

* [NOD-1162] Basic integration test ready

* [NOD-1162] Wait for connection for real

* [NOD-1162] [FIX] Connection manager should run the moment it adds a request

* [NOD-1162] Make connect something that can be invoked in middle of test

* [NOD-1162] Complete first integration test

* [NOD-1162] Undo refactor error

* [NOD-1162] Rename Kaspad to App

* [NOD-1162] Convert checking connection to polling

* [NOD-1162] [FIX] Set peerID on handshake

* [NOD-1162] [FIX] Broadcast should send to outgoing route, not incoming

* [NOD-1162] [FIX] Add CmdInvRelayBlock to MakeEmptyMessage

* [NOD-1162] [FIX] Initialize Hash before decoding MsgInvRelayBlock

* [NOD-1162] [FIX] Invert condition

* [NOD-1162] [FIX] Fixes to encoding of MsgGetRelayBlocks

* [NOD-1162] [FIX] Add MsgGetRelayBlocks to MakeEmptyMessage

* [NOD-1162] [FIX] Connection manager should run the moment it adds a request

* [NOD-1162] [FIX] Set peerID on handshake

* [NOD-1162] [FIX] Broadcast should send to outgoing route, not incoming

* [NOD-1162] [FIX] Add CmdInvRelayBlock to MakeEmptyMessage

* [NOD-1162] [FIX] Initialize Hash before decoding MsgInvRelayBlock

* [NOD-1162] [FIX] Invert condition

* [NOD-1162] [FIX] Fixes to encoding of MsgGetRelayBlocks

* [NOD-1162] [FIX] Add MsgGetRelayBlocks to MakeEmptyMessage

* [NOD-1162] Add comment

* [NOD-1162] Added support for 3 nodes and clients in integration tests

* [NOD-1162] Add third node to integration test

* [NOD-1192] Use lock-less functions in TxPool.HandleNewBlock

* [NOD-1192] Broadcast transactions only if there's more then 0

* [NOD-1162] Removed double waitTillNextIteration

* [NOD-1192] Rename: broadcastTransactions -> broadcastTransactionsAfterBlockAdded

* [NOD-1162] Call NotifyBlocks on client3 as well

* [NOD-1162] ErrTimeout and ErrRouteClosed should be ProtocolErrors

* [NOD-1162] Added comment and removed redundant type PeerAddedCallback

* [NOD-1162] Revert overly eager rename

* [NOD-1162] Move DisalbeTLS to common config + minimize call for ioutil.TempDir()

* [NOD-1162] Add some clarifications in code

* [NOD-1193] Skip closed connections in NetAdapter.Broadcast

* [NOD-1193] Make sure to protect connectionsToRouters from concurrent access

* [NOD-1162] Add _test to all files in integration package

* [NOD-1162] Introduced appHarness to better encapsulate a single node

* [NOD-1162] Removed onChainChanged handler

* [NOD-1162] Remove redundant closure

* [NOD-1162] Correctly mark integration_test config as Simnet

* [NOD-1162] Rename app.ID -> app.P2PNodeID

* [NOD-1162] Move TestIntegrationBasicSync to basic_sync_test.go

* [NOD-1210] Made it possible to setup any number of harnesses needed

* [NOD-1210] Rename appHarness1/2 to incoming/outgoing in connect function

* [NOD-1210] Add the 117-incoming-connections integration test

* [NOD-1210] Delete 117-incoming-connections test because it opens too much files

* [NOD-1210] Added function to notify of blocks conveniently

* [NOD-1210] Added function to mine a block from-A-to-Z

* [NOD-1210] Added IBD integration test

* [NOD-1210] Finish test for IBD and fix bug where
requestSelectedTipsIfRequired ran in handshake's goroutine

* [NOD-1210] Set log level to debug

* [NOD-1210] A bunch of renamings
This commit is contained in:
Svarog 2020-08-02 09:42:27 +03:00 committed by GitHub
parent 42e50e6dc2
commit 16a658a5be
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 167 additions and 69 deletions

View File

@ -5,12 +5,10 @@ import (
"time"
"github.com/kaspanet/kaspad/wire"
clientpkg "github.com/kaspanet/kaspad/rpc/client"
)
func TestIntegrationBasicSync(t *testing.T) {
appHarness1, appHarness2, appHarness3, teardown := setup(t)
appHarness1, appHarness2, appHarness3, teardown := standardSetup(t)
defer teardown()
// Connect nodes in chain: 1 <--> 2 <--> 3
@ -18,40 +16,17 @@ func TestIntegrationBasicSync(t *testing.T) {
connect(t, appHarness1, appHarness2)
connect(t, appHarness2, appHarness3)
blockTemplate, err := appHarness1.rpcClient.GetBlockTemplate(testAddress1, "")
if err != nil {
t.Fatalf("Error getting block template: %+v", err)
}
block, err := clientpkg.ConvertGetBlockTemplateResultToBlock(blockTemplate)
if err != nil {
t.Fatalf("Error parsing blockTemplate: %s", err)
}
solveBlock(t, block)
err = appHarness2.rpcClient.NotifyBlocks()
if err != nil {
t.Fatalf("Error from NotifyBlocks: %+v", err)
}
app2OnBlockAddedChan := make(chan *wire.BlockHeader)
appHarness2.rpcClient.onBlockAdded = func(header *wire.BlockHeader) {
setOnBlockAddedHandler(t, appHarness2, func(header *wire.BlockHeader) {
app2OnBlockAddedChan <- header
}
})
err = appHarness3.rpcClient.NotifyBlocks()
if err != nil {
t.Fatalf("Error from NotifyBlocks: %+v", err)
}
app3OnBlockAddedChan := make(chan *wire.BlockHeader)
appHarness3.rpcClient.onBlockAdded = func(header *wire.BlockHeader) {
setOnBlockAddedHandler(t, appHarness3, func(header *wire.BlockHeader) {
app3OnBlockAddedChan <- header
}
})
err = appHarness1.rpcClient.SubmitBlock(block, nil)
if err != nil {
t.Fatalf("Error submitting block: %s", err)
}
block := mineNextBlock(t, appHarness1)
var header *wire.BlockHeader
select {

View File

@ -21,14 +21,14 @@ const (
rpcUser = "user"
rpcPass = "pass"
testAddress1 = "kaspasim:qz3tm5pew9lrdpnn8kytgtm6a0mx772j4uw02snetn"
testAddress1PK = "69f470ff9cd4010de7f4a95161867c49834435423526d9bab83781821cdf95bf"
miningAddress1 = "kaspasim:qz3tm5pew9lrdpnn8kytgtm6a0mx772j4uw02snetn"
miningAddress1PK = "69f470ff9cd4010de7f4a95161867c49834435423526d9bab83781821cdf95bf"
testAddress2 = "kaspasim:qqdf0vrh3u576eqzkp0s8qagc04tuj2xnu4sfskhx0"
testAddress2PK = "aed46ef760223032d2641e086dd48d0b0a4d581811e68ccf15bed2b8fe87348e"
miningAddress2 = "kaspasim:qqdf0vrh3u576eqzkp0s8qagc04tuj2xnu4sfskhx0"
miningAddress2PK = "aed46ef760223032d2641e086dd48d0b0a4d581811e68ccf15bed2b8fe87348e"
testAddress3 = "kaspasim:qq2wz0hl73a0qcl8872wr3djplwmyulurscsqxehu2"
testAddress3PK = "cc94a79bbccca30b0e3edff1895cbdf8d4ddcc119eacfd692970151dcc2881c2"
miningAddress3 = "kaspasim:qq2wz0hl73a0qcl8872wr3djplwmyulurscsqxehu2"
miningAddress3PK = "cc94a79bbccca30b0e3edff1895cbdf8d4ddcc119eacfd692970151dcc2881c2"
defaultTimeout = 10 * time.Second
)

View File

@ -5,8 +5,8 @@ import (
"time"
)
func connect(t *testing.T, appHarness1, appHarness2 *appHarness) {
err := appHarness2.rpcClient.ConnectNode(appHarness1.p2pAddress)
func connect(t *testing.T, incoming, outgoing *appHarness) {
err := outgoing.rpcClient.ConnectNode(incoming.p2pAddress)
if err != nil {
t.Fatalf("Error connecting the nodes")
}
@ -17,7 +17,7 @@ func connect(t *testing.T, appHarness1, appHarness2 *appHarness) {
spawn("integration.connect-Wait for connection", func() {
for range time.Tick(10 * time.Millisecond) {
if isConnected(t, appHarness1, appHarness2) {
if isConnected(t, incoming, outgoing) {
close(onConnectedChan)
return
}

47
integration/ibd_test.go Normal file
View File

@ -0,0 +1,47 @@
package integration
import (
"sync"
"testing"
"time"
"github.com/kaspanet/kaspad/util/locks"
"github.com/kaspanet/kaspad/wire"
)
func TestIBD(t *testing.T) {
const numBlocks = 100
syncer, syncee, _, teardown := standardSetup(t)
defer teardown()
for i := 0; i < numBlocks; i++ {
mineNextBlock(t, syncer)
}
blockAddedWG := sync.WaitGroup{}
blockAddedWG.Add(numBlocks)
setOnBlockAddedHandler(t, syncee, func(header *wire.BlockHeader) { blockAddedWG.Done() })
connect(t, syncer, syncee)
select {
case <-time.After(defaultTimeout):
t.Fatalf("Timeout waiting for IBD to finish")
case <-locks.ReceiveFromChanWhenDone(func() { blockAddedWG.Wait() }):
}
tip1, err := syncer.rpcClient.GetSelectedTip()
if err != nil {
t.Fatalf("Error getting tip for syncer")
}
tip2, err := syncee.rpcClient.GetSelectedTip()
if err != nil {
t.Fatalf("Error getting tip for syncee")
}
if tip1.Hash != tip2.Hash {
t.Errorf("Tips of syncer: '%s' and syncee '%s' are not equal", tip1.Hash, tip2.Hash)
}
}

14
integration/main_test.go Normal file
View File

@ -0,0 +1,14 @@
package integration
import (
"os"
"testing"
"github.com/kaspanet/kaspad/logger"
)
func TestMain(m *testing.M) {
logger.SetLogLevels("debug")
os.Exit(m.Run())
}

View File

@ -4,12 +4,14 @@ import (
"math/rand"
"testing"
clientpkg "github.com/kaspanet/kaspad/rpc/client"
"github.com/kaspanet/kaspad/util"
"github.com/kaspanet/kaspad/util/daghash"
"github.com/kaspanet/kaspad/wire"
)
func solveBlock(t *testing.T, block *util.Block) *wire.MsgBlock {
func solveBlock(block *util.Block) *wire.MsgBlock {
msgBlock := block.MsgBlock()
targetDifficulty := util.CompactToBig(msgBlock.Header.Bits)
initialNonce := rand.Uint64()
@ -23,3 +25,24 @@ func solveBlock(t *testing.T, block *util.Block) *wire.MsgBlock {
panic("Failed to solve block! This should never happen")
}
func mineNextBlock(t *testing.T, harness *appHarness) *util.Block {
blockTemplate, err := harness.rpcClient.GetBlockTemplate(harness.miningAddress, "")
if err != nil {
t.Fatalf("Error getting block template: %+v", err)
}
block, err := clientpkg.ConvertGetBlockTemplateResultToBlock(blockTemplate)
if err != nil {
t.Fatalf("Error parsing blockTemplate: %s", err)
}
solveBlock(block)
err = harness.rpcClient.SubmitBlock(block, nil)
if err != nil {
t.Fatalf("Error submitting block: %s", err)
}
return block
}

View File

@ -0,0 +1,15 @@
package integration
import (
"testing"
"github.com/kaspanet/kaspad/wire"
)
func setOnBlockAddedHandler(t *testing.T, harness *appHarness, handler func(header *wire.BlockHeader)) {
err := harness.rpcClient.NotifyBlocks()
if err != nil {
t.Fatalf("Error from NotifyBlocks: %s", err)
}
harness.rpcClient.onBlockAdded = handler
}

View File

@ -14,41 +14,64 @@ type appHarness struct {
rpcClient *rpcClient
p2pAddress string
rpcAddress string
miningAddress string
miningAddressPK string
config *config.Config
databaseContext *dbaccess.DatabaseContext
}
func setup(t *testing.T) (appHarness1, appHarness2, appHarness3 *appHarness, teardownFunc func()) {
appHarness1 = &appHarness{p2pAddress: p2pAddress1, rpcAddress: rpcAddress1}
appHarness2 = &appHarness{p2pAddress: p2pAddress2, rpcAddress: rpcAddress2}
appHarness3 = &appHarness{p2pAddress: p2pAddress3, rpcAddress: rpcAddress3}
type harnessParams struct {
p2pAddress string
rpcAddress string
miningAddress string
miningAddressPK string
}
setConfig(t, appHarness1)
setConfig(t, appHarness2)
setConfig(t, appHarness3)
// setupHarness creates a single appHarness with given parameters
func setupHarness(t *testing.T, params *harnessParams) (harness *appHarness, teardownFunc func()) {
harness = &appHarness{
p2pAddress: params.p2pAddress,
rpcAddress: params.rpcAddress,
miningAddress: params.miningAddress,
miningAddressPK: params.miningAddressPK,
}
setDatabaseContext(t, appHarness1)
setDatabaseContext(t, appHarness2)
setDatabaseContext(t, appHarness3)
setConfig(t, harness)
setDatabaseContext(t, harness)
setApp(t, harness)
harness.app.Start()
setRPCClient(t, harness)
setApp(t, appHarness1)
setApp(t, appHarness2)
setApp(t, appHarness3)
return harness, func() {
teardown(t, harness)
}
}
appHarness1.app.Start()
appHarness2.app.Start()
appHarness3.app.Start()
// setupHarnesses creates multiple appHarnesses, according to number of parameters passed
func setupHarnesses(t *testing.T, harnessesParams []*harnessParams) (harnesses []*appHarness, teardownFunc func()) {
var teardowns []func()
for _, params := range harnessesParams {
harness, teardownFunc := setupHarness(t, params)
harnesses = append(harnesses, harness)
teardowns = append(teardowns, teardownFunc)
}
setRPCClient(t, appHarness1)
setRPCClient(t, appHarness2)
setRPCClient(t, appHarness3)
return appHarness1, appHarness2, appHarness3,
func() {
teardown(t, appHarness1)
teardown(t, appHarness2)
teardown(t, appHarness3)
return harnesses, func() {
for _, teardownFunc := range teardowns {
teardownFunc()
}
}
}
// standardSetup creates a standard setup of 3 appHarnesses that should work for most tests
func standardSetup(t *testing.T) (appHarness1, appHarness2, appHarness3 *appHarness, teardownFunc func()) {
harnesses, teardown := setupHarnesses(t, []*harnessParams{
{p2pAddress: p2pAddress1, rpcAddress: rpcAddress1, miningAddress: miningAddress1},
{p2pAddress: p2pAddress2, rpcAddress: rpcAddress2, miningAddress: miningAddress2},
{p2pAddress: p2pAddress3, rpcAddress: rpcAddress3, miningAddress: miningAddress3},
})
return harnesses[0], harnesses[1], harnesses[2], teardown
}
func setRPCClient(t *testing.T, harness *appHarness) {

View File

@ -1,10 +1,11 @@
package flowcontext
import (
"github.com/kaspanet/kaspad/blockdag"
peerpkg "github.com/kaspanet/kaspad/protocol/peer"
"sync/atomic"
"time"
"github.com/kaspanet/kaspad/blockdag"
peerpkg "github.com/kaspanet/kaspad/protocol/peer"
)
// StartIBDIfRequired selects a peer and starts IBD against it
@ -19,7 +20,7 @@ func (f *FlowContext) StartIBDIfRequired() {
peer := f.selectPeerForIBD(f.dag)
if peer == nil {
f.requestSelectedTipsIfRequired()
spawn("StartIBDIfRequired-requestSelectedTipsIfRequired", f.requestSelectedTipsIfRequired)
return
}