diff --git a/.circleci/config.yml b/.circleci/config.yml index b4eefb6c1..6efc087a7 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -35,7 +35,7 @@ jobs: - run: # sudo is needed, so that integration test binary have proper access to nodes keyring name: Run integration tests command: | - make localnet-start + make localnet-start-test sudo -E env "PATH=$PATH" make test-babylon-integration make localnet-stop diff --git a/Makefile b/Makefile index 06a636f52..44de7c01b 100644 --- a/Makefile +++ b/Makefile @@ -443,16 +443,28 @@ localnet-build-env: localnet-build-dlv: $(MAKE) -C contrib/images babylond-dlv +localnet-build-nodes-test: + $(DOCKER) run --rm -v $(CURDIR)/.testnets:/data babylonchain/babylond \ + testnet init-files --v 4 -o /data \ + --starting-ip-address 192.168.10.2 --keyring-backend=test \ + --chain-id chain-test --btc-confirmation-depth 2 --additional-sender-account true \ + --epoch-interval 5 + docker-compose up -d + localnet-build-nodes: $(DOCKER) run --rm -v $(CURDIR)/.testnets:/data babylonchain/babylond \ testnet init-files --v 4 -o /data \ --starting-ip-address 192.168.10.2 --keyring-backend=test \ - --chain-id chain-test --btc-confirmation-depth 2 + --chain-id chain-test docker-compose up -d -# localnet-start will run a testnet with 4 nodes, a bitcoin instance, and a vigilante instance +# localnet-start will run a with 4 nodes with 4 nodes, a bitcoin instance, and a vigilante instance localnet-start: localnet-stop localnet-build-env localnet-build-nodes +# localnet-start-test will start with 4 nodes with test confiuration and low +# epoch interval +localnet-start-test: localnet-stop localnet-build-env localnet-build-nodes-test + # localnet-debug will run a 4-node testnet locally in debug mode # you can read more about the debug mode here: ./contrib/images/babylond-dlv/README.md localnet-debug: localnet-stop localnet-build-dlv localnet-build-nodes diff --git a/cmd/babylond/cmd/testnet.go b/cmd/babylond/cmd/testnet.go index 3b9ab13f8..f3df1bdc2 100644 --- a/cmd/babylond/cmd/testnet.go +++ b/cmd/babylond/cmd/testnet.go @@ -50,19 +50,20 @@ import ( ) var ( - flagNodeDirPrefix = "node-dir-prefix" - flagNumValidators = "v" - flagOutputDir = "output-dir" - flagNodeDaemonHome = "node-daemon-home" - flagStartingIPAddress = "starting-ip-address" - flagBtcNetwork = "btc-network" - flagBtcCheckpointTag = "btc-checkpoint-tag" - flagBtcConfirmationDepth = "btc-confirmation-depth" - flagBtcFinalizationTimeout = "btc-finalization-timeout" - flagEpochInterval = "epoch-interval" - flagBaseBtcHeaderHex = "btc-base-header" - flagBaseBtcHeaderHeight = "btc-base-header-height" - flagMaxActiveValidators = "max-active-validators" + flagNodeDirPrefix = "node-dir-prefix" + flagNumValidators = "v" + flagOutputDir = "output-dir" + flagNodeDaemonHome = "node-daemon-home" + flagStartingIPAddress = "starting-ip-address" + flagBtcNetwork = "btc-network" + flagBtcCheckpointTag = "btc-checkpoint-tag" + flagBtcConfirmationDepth = "btc-confirmation-depth" + flagBtcFinalizationTimeout = "btc-finalization-timeout" + flagEpochInterval = "epoch-interval" + flagBaseBtcHeaderHex = "btc-base-header" + flagBaseBtcHeaderHeight = "btc-base-header-height" + flagMaxActiveValidators = "max-active-validators" + flagAdditionalSenderAccount = "additional-sender-account" ) // get cmd to initialize all files for tendermint testnet and application @@ -108,6 +109,7 @@ Example: // btclightclient args baseBtcHeaderHex, _ := cmd.Flags().GetString(flagBaseBtcHeaderHex) baseBtcHeaderHeight, err := cmd.Flags().GetUint64(flagBaseBtcHeaderHeight) + additionalAccount, _ := cmd.Flags().GetBool(flagAdditionalSenderAccount) if err != nil { return errors.New("base Bitcoin header height should be a uint64") } @@ -116,7 +118,7 @@ Example: clientCtx, cmd, config, mbm, genBalIterator, outputDir, chainID, minGasPrices, nodeDirPrefix, nodeDaemonHome, startingIPAddress, keyringBackend, algo, numValidators, maxActiveValidators, btcNetwork, btcCheckpointTag, btcConfirmationDepth, btcFinalizationTimeout, - epochInterval, baseBtcHeaderHex, baseBtcHeaderHeight, + epochInterval, baseBtcHeaderHex, baseBtcHeaderHeight, additionalAccount, ) }, } @@ -142,6 +144,7 @@ Example: cmd.Flags().String(flagBaseBtcHeaderHex, "0100000000000000000000000000000000000000000000000000000000000000000000003ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a45068653ffff7f2002000000", "Hex of the base Bitcoin header.") cmd.Flags().Uint64(flagBaseBtcHeaderHeight, 0, "Height of the base Bitcoin header.") cmd.Flags().Uint32(flagMaxActiveValidators, 10, "Maximum number of validators.") + cmd.Flags().Bool(flagAdditionalSenderAccount, false, "If there should be additional pre funded account per validator") return cmd } @@ -172,6 +175,7 @@ func InitTestnet( epochInterval uint64, baseBtcHeaderHex string, baseBtcHeaderHeight uint64, + additionalAccount bool, ) error { if chainID == "" { @@ -234,6 +238,7 @@ func InitTestnet( // generate account key kb, err := keyring.New(sdk.KeyringServiceName(), keyringBackend, nodeDir, inBuf) + if err != nil { return err } @@ -344,6 +349,38 @@ func InitTestnet( } } + if additionalAccount { + for i := 0; i < numValidators; i++ { + nodeDirName := fmt.Sprintf("%s%d", nodeDirPrefix, i) + nodeDir := filepath.Join(outputDir, nodeDirName, nodeDaemonHome) + + // generate account key + kb, err := keyring.New(sdk.KeyringServiceName(), keyringBackend, nodeDir, inBuf) + + if err != nil { + return err + } + keyringAlgos, _ := kb.SupportedAlgorithms() + algo, err := keyring.NewSigningAlgoFromString(algoStr, keyringAlgos) + if err != nil { + return err + } + addr, _, err := testutil.GenerateSaveCoinKey(kb, "test-spending-key", "", true, algo) + if err != nil { + _ = os.RemoveAll(outputDir) + return err + } + + coins := sdk.Coins{ + sdk.NewCoin("testtoken", sdk.NewInt(1000000000)), + sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(500000000)), + } + + genBalances = append(genBalances, banktypes.Balance{Address: addr.String(), Coins: coins.Sort()}) + genAccounts = append(genAccounts, authtypes.NewBaseAccount(addr, nil, 0, 0)) + } + } + if err := initGenFiles(clientCtx, mbm, chainID, genAccounts, genBalances, genFiles, genKeys, numValidators, maxActiveValidators, btcConfirmationDepth, btcFinalizationTimeout, epochInterval, baseBtcHeaderHex, baseBtcHeaderHeight); err != nil { diff --git a/test/integration_test.go b/test/integration_test.go index bfc28bffb..447ba0d87 100644 --- a/test/integration_test.go +++ b/test/integration_test.go @@ -81,10 +81,26 @@ func waitForBlock(clients []*grpc.ClientConn, blockNumber int64) { return } - <-time.After(2 * time.Second) + <-time.After(1 * time.Second) } } +func getCurrentEpoch(conn *grpc.ClientConn) uint64 { + epochingClient := epochingtypes.NewQueryClient(conn) + + currentEpochResponse, err := epochingClient.CurrentEpoch( + context.Background(), + &epochingtypes.QueryCurrentEpochRequest{}, + ) + + if err != nil { + errorString := fmt.Sprintf("Query failed, testnet not running. Error: %v", err) + panic(errorString) + } + + return currentEpochResponse.CurrentEpoch +} + func TestMain(m *testing.M) { // This is needed so that all address prefixes are in Babylon format @@ -154,62 +170,29 @@ func TestBtcLightClientGenesis(t *testing.T) { } func TestNodeProgress(t *testing.T) { - // most probably nodes are after block 1 at this point, but to make sure we are waiting // for block 1 - // blocks 1-10 are epoch 1 blocks. + // blocks 1-5 are epoch 1 blocks. waitForBlock(clients, 1) for _, c := range clients { - epochingClient := epochingtypes.NewQueryClient(c) - - currentEpochResponse, err := epochingClient.CurrentEpoch( - context.Background(), - &epochingtypes.QueryCurrentEpochRequest{}, - ) - - if err != nil { - errorString := fmt.Sprintf("Query failed, testnet not running. Error: %v", err) - panic(errorString) - } - - if currentEpochResponse.CurrentEpoch != 1 { - t.Fatalf("Initial epoch should equal 1. Current epoch %d", currentEpochResponse.CurrentEpoch) + currentEpoch := getCurrentEpoch(c) + if currentEpoch != 1 { + t.Fatalf("Initial epoch should equal 1. Current epoch %d", currentEpoch) } } - // TODO default epoch interval is equal to 10, we should retrieve it from config - // block 11 is first block of epoch 2, so if all clients are after block 12, they - // should be at epoch 2 - waitForBlock(clients, 12) + waitForBlock(clients, 7) for _, c := range clients { - epochingClient := epochingtypes.NewQueryClient(c) - - currentEpochResponse, err := epochingClient.CurrentEpoch( - context.Background(), - &epochingtypes.QueryCurrentEpochRequest{}, - ) - - if err != nil { - errorString := fmt.Sprintf("Query failed, testnet not running. Error: %v", err) - panic(errorString) - } - - if currentEpochResponse.CurrentEpoch != 2 { - t.Errorf("Epoch after 10 blocks, should equal 2. Curent epoch %d", currentEpochResponse.CurrentEpoch) + currentEpoch := getCurrentEpoch(c) + if currentEpoch != 2 { + t.Errorf("Epoch after 3 blocks, should equal 2. Curent epoch %d", currentEpoch) } } } func TestSendTx(t *testing.T) { - // we are waiting for middle of the epoch to avoid race condidions with bls - // signer sending transaction and incrementing account sequence numbers - // which may cause header tx to fail. - // TODO: Create separate account for sending transactions to avoid race - // conditions with validator acounts. - waitForBlock(clients, 14) - // TODO fix hard coded paths node0dataPath := "../.testnets/node0/babylond" node0genesisPath := "../.testnets/node0/babylond/config/genesis.json" @@ -311,30 +294,12 @@ func TestSubmitCheckpoint(t *testing.T) { secondSubmission := datagen.CreateBlockWithTransaction(firstSubmission.HeaderBytes.ToBlockHeader(), p2) // first insert all headers - hresp1, err := sender.insertNewHeader(firstSubmission.HeaderBytes) - - if err != nil { - t.Fatalf("Could not insert first header") - } - - _, err = WaitBtcForHeight(clients[0], currentTip.Height+1) - - if err != nil { - t.Log(hresp1.TxResponse) - t.Fatalf("failed waiting for btc lightclient block") - } - - hresp2, err := sender.insertNewHeader(secondSubmission.HeaderBytes) + err = sender.insertBTCHeaders( + []bbn.BTCHeaderBytes{firstSubmission.HeaderBytes, secondSubmission.HeaderBytes}, + ) if err != nil { - t.Fatalf("Could not insert second header") - } - - _, err = WaitBtcForHeight(clients[0], currentTip.Height+2) - - if err != nil { - t.Log(hresp2.TxResponse) - t.Fatalf("failed waiting for btc lightclient block") + t.Fatalf("Could not insert two headers. Err: %s", err) } // At this point light client chain should be 3 long and inserting spv proofs @@ -373,40 +338,11 @@ func TestConfirmCheckpoint(t *testing.T) { panic("failed to init sender") } - currentTip, err := sender.getBtcTip() - - if err != nil { - t.Fatalf("Could not retrieve btc tip") - } - - h1 := generateEmptyChildHeaderBytes(currentTip.Header.ToBlockHeader()) - h2 := generateEmptyChildHeaderBytes(h1.ToBlockHeader()) - - // first insert 2 new headers, - hresp1, err := sender.insertNewHeader(h1) - - if err != nil { - t.Fatalf("Could not insert first header") - } - - _, err = WaitBtcForHeight(clients[0], currentTip.Height+1) + // insert two empty btc headers + err = sender.insertBTCHeaders([]bbn.BTCHeaderBytes{nil, nil}) if err != nil { - t.Log(hresp1.TxResponse) - t.Fatalf("failed waiting for btc lightclient block") - } - - hresp2, err := sender.insertNewHeader(h2) - - if err != nil { - t.Fatalf("Could not insert second header") - } - - _, err = WaitBtcForHeight(clients[0], currentTip.Height+2) - - if err != nil { - t.Log(hresp2.TxResponse) - t.Fatalf("failed waiting for btc lightclient block") + t.Fatalf("Could not insert two headers. Err: %s", err) } // Btc light client chain has been extended by 2 blocks, it means that our checkpoint diff --git a/test/utils.go b/test/utils.go index f715c1f0b..a5d02a127 100644 --- a/test/utils.go +++ b/test/utils.go @@ -49,9 +49,13 @@ func NewTestTxSender( return nil, err } - infos, _ := kb.List() + signer, err := kb.Key("test-spending-key") - signerInfo := infos[0] + if err != nil { + panic("test-spending-key should be defined for each node in integration test") + } + + signerInfo := signer return &TestTxSender{ keyring: kb, @@ -66,7 +70,7 @@ func (b *TestTxSender) getSenderAddress() ctypes.AccAddress { return b.signerInfo.GetAddress() } -func (b *TestTxSender) buildTx(msg ctypes.Msg, fees string, gas uint64, seqNr uint64) []byte { +func (b *TestTxSender) buildTx(msg ctypes.Msg, fees string, gas uint64, seqNr uint64, accNumber uint64) []byte { txFactory := tx.Factory{} txFactory = txFactory. @@ -75,7 +79,8 @@ func (b *TestTxSender) buildTx(msg ctypes.Msg, fees string, gas uint64, seqNr ui WithChainID(b.chainId). WithFees(fees). WithGas(gas). - WithSequence(seqNr) + WithSequence(seqNr). + WithAccountNumber(accNumber) txb1, _ := txFactory.BuildUnsignedTx(msg) @@ -108,7 +113,7 @@ func (b *TestTxSender) insertNewHeader(headerBytes bbn.BTCHeaderBytes) (*txservi panic("creating new header message must success ") } - acc, err := b.getAccount() + acc, err := b.getSelfAccount() if err != nil { panic("retrieving sending account must succeed") @@ -116,7 +121,7 @@ func (b *TestTxSender) insertNewHeader(headerBytes bbn.BTCHeaderBytes) (*txservi //TODO 3stake and 300000 should probably not be hardcoded by taken from tx //simulation. For now this enough to pay for insert header transaction. - txBytes := b.buildTx(msg, "3stake", 300000, acc.GetSequence()) + txBytes := b.buildTx(msg, "3stake", 300000, acc.GetSequence(), acc.GetAccountNumber()) req := txservice.BroadcastTxRequest{TxBytes: txBytes, Mode: txservice.BroadcastMode_BROADCAST_MODE_SYNC} @@ -133,7 +138,7 @@ func (b *TestTxSender) insertSpvProof(p1 *btccheckpoint.BTCSpvProof, p2 *btcchec Proofs: []*btccheckpoint.BTCSpvProof{p1, p2}, } - acc, err := b.getAccount() + acc, err := b.getSelfAccount() if err != nil { panic("retrieving sending account must succeed") @@ -141,7 +146,7 @@ func (b *TestTxSender) insertSpvProof(p1 *btccheckpoint.BTCSpvProof, p2 *btcchec //TODO 3stake and 300000 should probably not be hardcoded by taken from tx //simulation. For now this enough to pay for insert header transaction. - txBytes := b.buildTx(&msg, "3stake", 300000, acc.GetSequence()) + txBytes := b.buildTx(&msg, "3stake", 300000, acc.GetSequence(), acc.GetAccountNumber()) req := txservice.BroadcastTxRequest{TxBytes: txBytes, Mode: txservice.BroadcastMode_BROADCAST_MODE_SYNC} @@ -162,12 +167,12 @@ func (b *TestTxSender) getBtcTip() (*lightclient.BTCHeaderInfo, error) { return res.Header, nil } -func (b *TestTxSender) getAccount() (acctypes.AccountI, error) { +func (b *TestTxSender) getAccount(addr ctypes.AccAddress) (acctypes.AccountI, error) { queryClient := acctypes.NewQueryClient(b.Conn) res, _ := queryClient.Account( context.Background(), - &acctypes.QueryAccountRequest{Address: b.getSenderAddress().String()}, + &acctypes.QueryAccountRequest{Address: addr.String()}, ) var acc acctypes.AccountI @@ -178,6 +183,44 @@ func (b *TestTxSender) getAccount() (acctypes.AccountI, error) { return acc, nil } +func (b *TestTxSender) getSelfAccount() (acctypes.AccountI, error) { + return b.getAccount(b.getSenderAddress()) +} + +func (b *TestTxSender) insertBTCHeaders(headers []bbn.BTCHeaderBytes) error { + if len(headers) == 0 { + return nil + } + + for _, h := range headers { + currentTip, err := b.getBtcTip() + + if err != nil { + return err + } + + if h == nil { + _, err := b.insertNewEmptyHeader(currentTip) + if err != nil { + return err + } + } else { + _, err := b.insertNewHeader(h) + if err != nil { + return err + } + } + + _, err = WaitBtcForHeight(b.Conn, currentTip.Height+1) + + if err != nil { + return err + } + } + + return nil +} + func generateEmptyChildHeader(bh *wire.BlockHeader) *wire.BlockHeader { randHeader := datagen.GenRandomBtcdHeader() @@ -284,7 +327,7 @@ func WaitForBtcHeightWithTimeout(c *grpc.ClientConn, h uint64, t time.Duration) } func WaitBtcForHeight(c *grpc.ClientConn, h uint64) (uint64, error) { - return WaitForBtcHeightWithTimeout(c, h, 30*time.Second) + return WaitForBtcHeightWithTimeout(c, h, 15*time.Second) } func WaitForNextBtcBlock(c *grpc.ClientConn) error {