From 65bd1e8f9989aa96f492d56be66ed0f780266179 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20B=C3=B6ckli?= Date: Thu, 16 Nov 2023 17:42:06 +0100 Subject: [PATCH] run tests in branch --- test/arc_txt_endpoint_test.go | 300 ++-------------------------------- test/callback_test.go | 189 +++++++++++++++++++++ test/double_spend_test.go | 118 +++++++++++++ test/utils.go | 23 +-- 4 files changed, 331 insertions(+), 299 deletions(-) create mode 100644 test/callback_test.go create mode 100644 test/double_spend_test.go diff --git a/test/arc_txt_endpoint_test.go b/test/arc_txt_endpoint_test.go index 27fd00ef0..46438e781 100644 --- a/test/arc_txt_endpoint_test.go +++ b/test/arc_txt_endpoint_test.go @@ -13,16 +13,12 @@ import ( "testing" "time" - "github.com/bitcoin-sv/arc/api" - "github.com/bitcoin-sv/arc/api/handler" - "github.com/bitcoin-sv/arc/metamorph/metamorph_api" "github.com/bitcoinsv/bsvd/bsvec" "github.com/bitcoinsv/bsvutil" "github.com/libsv/go-bk/bec" "github.com/libsv/go-bt/v2" "github.com/libsv/go-bt/v2/bscript" "github.com/libsv/go-bt/v2/unlocker" - "github.com/stretchr/testify/require" ) type Response struct { @@ -45,6 +41,11 @@ type TxStatusResponse struct { Txid string `json:"txid"` } +var ( + address string + privateKey string +) + func TestMain(m *testing.M) { info, err := bitcoind.GetInfo() if err != nil { @@ -53,6 +54,13 @@ func TestMain(m *testing.M) { log.Printf("current block height: %d", info.Blocks) + address, privateKey, err := getNewWalletAddress() + if err != nil { + log.Fatalf("failed to get new wallet address: %v", err) + } + log.Println(fmt.Sprintf("new address: %s", address)) + log.Println(fmt.Sprintf("new private key: %s", privateKey)) + os.Exit(m.Run()) } @@ -104,181 +112,7 @@ func createTx(privateKey string, address string, utxo NodeUnspentUtxo) (*bt.Tx, return tx, nil } -func TestPostCallbackToken(t *testing.T) { - tt := []struct { - name string - }{ - { - name: "post transaction with callback url and token", - }, - } - - for _, tc := range tt { - t.Run(tc.name, func(t *testing.T) { - address, privateKey := getNewWalletAddress(t) - - generate(t, 100) - - t.Logf("generated address: %s", address) - - sendToAddress(t, address, 0.001) - - txID := sendToAddress(t, address, 0.02) - t.Logf("sent 0.02 BSV to: %s", txID) - - hash := generate(t, 1) - t.Logf("generated 1 block: %s", hash) - - utxos := getUtxos(t, address) - require.True(t, len(utxos) > 0, "No UTXOs available for the address") - - tx, err := createTx(privateKey, address, utxos[0]) - require.NoError(t, err) - - url := "http://arc:9090/" - - arcClient, err := api.NewClientWithResponses(url) - require.NoError(t, err) - - ctx := context.Background() - - hostname, err := os.Hostname() - require.NoError(t, err) - - waitForStatus := api.WaitForStatus(metamorph_api.Status_SEEN_ON_NETWORK) - params := &api.POSTTransactionParams{ - XWaitForStatus: &waitForStatus, - XCallbackUrl: handler.PtrTo(fmt.Sprintf("http://%s:9000/callback", hostname)), - XCallbackToken: handler.PtrTo("1234"), - } - - arcBody := api.POSTTransactionJSONRequestBody{ - RawTx: hex.EncodeToString(tx.ExtendedBytes()), - } - - var response *api.POSTTransactionResponse - response, err = arcClient.POSTTransactionWithResponse(ctx, params, arcBody) - require.NoError(t, err) - - require.Equal(t, http.StatusOK, response.StatusCode()) - require.NotNil(t, response.JSON200) - require.Equal(t, "SEEN_ON_NETWORK", response.JSON200.TxStatus) - - callbackReceivedChan := make(chan *api.TransactionStatus, 2) - errChan := make(chan error, 2) - - expectedAuthHeader := "Bearer 1234" - srv := &http.Server{Addr: ":9000"} - defer func() { - t.Log("shutting down callback listener") - if err = srv.Shutdown(context.TODO()); err != nil { - panic(err) - } - }() - - iterations := 0 - http.HandleFunc("/callback", func(w http.ResponseWriter, req *http.Request) { - - defer func() { - err := req.Body.Close() - if err != nil { - t.Log("failed to close body") - } - }() - - bodyBytes, err := io.ReadAll(req.Body) - if err != nil { - errChan <- err - } - - var status api.TransactionStatus - err = json.Unmarshal(bodyBytes, &status) - if err != nil { - errChan <- err - } - - if expectedAuthHeader != req.Header.Get("Authorization") { - errChan <- fmt.Errorf("auth header %s not as expected %s", expectedAuthHeader, req.Header.Get("Authorization")) - } - - // Let ARC send the callback 2 times. First one fails. - if iterations == 0 { - t.Log("callback received, responding bad request") - - err = respondToCallback(w, false) - if err != nil { - t.Fatalf("Failed to respond to callback: %v", err) - } - - callbackReceivedChan <- &status - - iterations++ - return - } - - t.Log("callback received, responding success") - - err = respondToCallback(w, true) - if err != nil { - t.Fatalf("Failed to respond to callback: %v", err) - } - callbackReceivedChan <- &status - }) - - go func(server *http.Server) { - t.Log("starting callback server") - err = server.ListenAndServe() - if err != nil { - return - } - }(srv) - - generate(t, 10) - - var statusResopnse *api.GETTransactionStatusResponse - statusResopnse, err = arcClient.GETTransactionStatusWithResponse(ctx, response.JSON200.Txid) - - for i := 0; i <= 1; i++ { - t.Logf("callback iteration %d", i) - select { - case callback := <-callbackReceivedChan: - require.Equal(t, statusResopnse.JSON200.Txid, callback.Txid) - require.Equal(t, statusResopnse.JSON200.BlockHeight, callback.BlockHeight) - require.Equal(t, statusResopnse.JSON200.BlockHash, callback.BlockHash) - case err := <-errChan: - t.Fatalf("callback received - failed to parse callback %v", err) - case <-time.NewTicker(time.Second * 15).C: - t.Fatal("callback not received") - } - } - }) - } -} - -func respondToCallback(w http.ResponseWriter, success bool) error { - resp := make(map[string]string) - if success { - resp["message"] = "Success" - w.WriteHeader(http.StatusOK) - } else { - resp["message"] = "Bad Request" - w.WriteHeader(http.StatusBadRequest) - } - - jsonResp, err := json.Marshal(resp) - if err != nil { - return err - } - - _, err = w.Write(jsonResp) - if err != nil { - return err - } - return nil -} - func TestHttpPost(t *testing.T) { - address, privateKey := getNewWalletAddress(t) generate(t, 100) @@ -462,113 +296,3 @@ func TestHttpPost(t *testing.T) { fmt.Println("Transaction status:", statusResponse.TxStatus) } - -func TestDoubleSpend(t *testing.T) { - tt := []struct { - name string - extFormat bool - }{ - { - name: "submit tx with a double spend tx before and after tx got mined - std format", - extFormat: false, - }, - { - name: "submit tx with a double spend tx before and after tx got mined - ext format", - extFormat: true, - }, - } - - for _, tc := range tt { - t.Run(tc.name, func(t *testing.T) { - address, privateKey := getNewWalletAddress(t) - - generate(t, 100) - - t.Logf("generated address: %s", address) - - sendToAddress(t, address, 0.001) - - txID := sendToAddress(t, address, 0.02) - t.Logf("sent 0.02 BSV to: %s", txID) - - hash := generate(t, 1) - t.Logf("generated 1 block: %s", hash) - - utxos := getUtxos(t, address) - require.True(t, len(utxos) > 0, "No UTXOs available for the address") - - tx, err := createTx(privateKey, address, utxos[0]) - require.NoError(t, err) - - url := "http://arc:9090/" - - arcClient, err := api.NewClientWithResponses(url) - require.NoError(t, err) - - ctx := context.Background() - waitForStatus := api.WaitForStatus(metamorph_api.Status_SEEN_ON_NETWORK) - params := &api.POSTTransactionParams{ - XWaitForStatus: &waitForStatus, - } - - arcBody := api.POSTTransactionJSONRequestBody{ - RawTx: hex.EncodeToString(tx.ExtendedBytes()), - } - - // submit first transaction - var response *api.POSTTransactionResponse - response, err = arcClient.POSTTransactionWithResponse(ctx, params, arcBody) - require.NoError(t, err) - - require.Equal(t, http.StatusOK, response.StatusCode()) - require.NotNil(t, response.JSON200) - require.Equal(t, "SEEN_ON_NETWORK", response.JSON200.TxStatus) - - // send double spending transaction when first tx is in mempool - arcBodyMempool := getArcBody(t, privateKey, utxos[0], tc.extFormat) - - var responseMempool *api.POSTTransactionResponse - responseMempool, err = arcClient.POSTTransactionWithResponse(ctx, params, arcBodyMempool) - require.NoError(t, err) - - require.Equal(t, http.StatusOK, responseMempool.StatusCode()) - require.NotNil(t, responseMempool.JSON200) - require.Equal(t, "REJECTED", responseMempool.JSON200.TxStatus) - - generate(t, 10) - var statusResponse *api.GETTransactionStatusResponse - statusResponse, err = arcClient.GETTransactionStatusWithResponse(ctx, response.JSON200.Txid) - - require.Equal(t, handler.PtrTo("MINED"), statusResponse.JSON200.TxStatus) - require.NotNil(t, statusResponse.JSON200.MerklePath) - - // send double spending transaction when first tx is mined - arcBodyMined := getArcBody(t, privateKey, utxos[0], tc.extFormat) - - var responseMined *api.POSTTransactionResponse - responseMempool, err = arcClient.POSTTransactionWithResponse(ctx, params, arcBodyMined) - require.NoError(t, err) - - require.Equal(t, http.StatusOK, responseMined.StatusCode()) - require.NotNil(t, responseMined.JSON200) - require.Equal(t, "REJECTED", responseMined.JSON200.TxStatus) - }) - } -} - -func getArcBody(t *testing.T, privateKey string, utxo NodeUnspentUtxo, extFormat bool) api.POSTTransactionJSONRequestBody { - sendToAddress, _ := getNewWalletAddress(t) - - tx1, err := createTx(privateKey, sendToAddress, utxo) - require.NoError(t, err) - var arcBodyTx string - if extFormat { - arcBodyTx = hex.EncodeToString(tx1.ExtendedBytes()) - } else { - arcBodyTx = hex.EncodeToString(tx1.Bytes()) - } - arcBody := api.POSTTransactionJSONRequestBody{ - RawTx: arcBodyTx, - } - return arcBody -} diff --git a/test/callback_test.go b/test/callback_test.go new file mode 100644 index 000000000..c4153752c --- /dev/null +++ b/test/callback_test.go @@ -0,0 +1,189 @@ +package test + +import ( + "context" + "encoding/hex" + "encoding/json" + "fmt" + "io" + "net/http" + "os" + "testing" + "time" + + "github.com/bitcoin-sv/arc/api" + "github.com/bitcoin-sv/arc/api/handler" + "github.com/bitcoin-sv/arc/metamorph/metamorph_api" + "github.com/stretchr/testify/require" +) + +func TestPostCallbackToken(t *testing.T) { + tt := []struct { + name string + }{ + { + name: "post transaction with callback url and token", + }, + } + + for _, tc := range tt { + t.Run(tc.name, func(t *testing.T) { + generate(t, 100) + + t.Logf("generated address: %s", address) + + sendToAddress(t, address, 0.001) + + txID := sendToAddress(t, address, 0.02) + t.Logf("sent 0.02 BSV to: %s", txID) + + hash := generate(t, 1) + t.Logf("generated 1 block: %s", hash) + + utxos := getUtxos(t, address) + require.True(t, len(utxos) > 0, "No UTXOs available for the address") + + tx, err := createTx(privateKey, address, utxos[0]) + require.NoError(t, err) + + url := "http://arc:9090/" + + arcClient, err := api.NewClientWithResponses(url) + require.NoError(t, err) + + ctx := context.Background() + + hostname, err := os.Hostname() + require.NoError(t, err) + + waitForStatus := api.WaitForStatus(metamorph_api.Status_SEEN_ON_NETWORK) + params := &api.POSTTransactionParams{ + XWaitForStatus: &waitForStatus, + XCallbackUrl: handler.PtrTo(fmt.Sprintf("http://%s:9000/callback", hostname)), + XCallbackToken: handler.PtrTo("1234"), + } + + arcBody := api.POSTTransactionJSONRequestBody{ + RawTx: hex.EncodeToString(tx.ExtendedBytes()), + } + + var response *api.POSTTransactionResponse + response, err = arcClient.POSTTransactionWithResponse(ctx, params, arcBody) + require.NoError(t, err) + + require.Equal(t, http.StatusOK, response.StatusCode()) + require.NotNil(t, response.JSON200) + require.Equal(t, "SEEN_ON_NETWORK", response.JSON200.TxStatus) + + callbackReceivedChan := make(chan *api.TransactionStatus, 2) + errChan := make(chan error, 2) + + expectedAuthHeader := "Bearer 1234" + srv := &http.Server{Addr: ":9000"} + defer func() { + t.Log("shutting down callback listener") + if err = srv.Shutdown(context.TODO()); err != nil { + panic(err) + } + }() + + iterations := 0 + http.HandleFunc("/callback", func(w http.ResponseWriter, req *http.Request) { + + defer func() { + err := req.Body.Close() + if err != nil { + t.Log("failed to close body") + } + }() + + bodyBytes, err := io.ReadAll(req.Body) + if err != nil { + errChan <- err + } + + var status api.TransactionStatus + err = json.Unmarshal(bodyBytes, &status) + if err != nil { + errChan <- err + } + + if expectedAuthHeader != req.Header.Get("Authorization") { + errChan <- fmt.Errorf("auth header %s not as expected %s", expectedAuthHeader, req.Header.Get("Authorization")) + } + + // Let ARC send the callback 2 times. First one fails. + if iterations == 0 { + t.Log("callback received, responding bad request") + + err = respondToCallback(w, false) + if err != nil { + t.Fatalf("Failed to respond to callback: %v", err) + } + + callbackReceivedChan <- &status + + iterations++ + return + } + + t.Log("callback received, responding success") + + err = respondToCallback(w, true) + if err != nil { + t.Fatalf("Failed to respond to callback: %v", err) + } + callbackReceivedChan <- &status + }) + + go func(server *http.Server) { + t.Log("starting callback server") + err = server.ListenAndServe() + if err != nil { + return + } + }(srv) + + generate(t, 10) + + var statusResopnse *api.GETTransactionStatusResponse + statusResopnse, err = arcClient.GETTransactionStatusWithResponse(ctx, response.JSON200.Txid) + + for i := 0; i <= 1; i++ { + t.Logf("callback iteration %d", i) + select { + case callback := <-callbackReceivedChan: + require.Equal(t, statusResopnse.JSON200.Txid, callback.Txid) + require.Equal(t, statusResopnse.JSON200.BlockHeight, callback.BlockHeight) + require.Equal(t, statusResopnse.JSON200.BlockHash, callback.BlockHash) + case err := <-errChan: + t.Fatalf("callback received - failed to parse callback %v", err) + case <-time.NewTicker(time.Second * 15).C: + t.Fatal("callback not received") + } + } + }) + } +} + +func respondToCallback(w http.ResponseWriter, success bool) error { + resp := make(map[string]string) + if success { + resp["message"] = "Success" + w.WriteHeader(http.StatusOK) + } else { + resp["message"] = "Bad Request" + w.WriteHeader(http.StatusBadRequest) + } + + jsonResp, err := json.Marshal(resp) + if err != nil { + return err + } + + _, err = w.Write(jsonResp) + if err != nil { + return err + } + return nil +} diff --git a/test/double_spend_test.go b/test/double_spend_test.go new file mode 100644 index 000000000..ec929f5c5 --- /dev/null +++ b/test/double_spend_test.go @@ -0,0 +1,118 @@ +package test + +import ( + "context" + "encoding/hex" + "github.com/bitcoin-sv/arc/api" + "github.com/bitcoin-sv/arc/api/handler" + "github.com/bitcoin-sv/arc/metamorph/metamorph_api" + "github.com/stretchr/testify/require" + "net/http" + "testing" +) + +func TestDoubleSpend(t *testing.T) { + tt := []struct { + name string + extFormat bool + }{ + { + name: "submit tx with a double spend tx before and after tx got mined - std format", + extFormat: false, + }, + { + name: "submit tx with a double spend tx before and after tx got mined - ext format", + extFormat: true, + }, + } + + for _, tc := range tt { + t.Run(tc.name, func(t *testing.T) { + generate(t, 100) + + t.Logf("generated address: %s", address) + + sendToAddress(t, address, 0.001) + + txID := sendToAddress(t, address, 0.02) + t.Logf("sent 0.02 BSV to: %s", txID) + + hash := generate(t, 1) + t.Logf("generated 1 block: %s", hash) + + utxos := getUtxos(t, address) + require.True(t, len(utxos) > 0, "No UTXOs available for the address") + + tx, err := createTx(privateKey, address, utxos[0]) + require.NoError(t, err) + + url := "http://arc:9090/" + + arcClient, err := api.NewClientWithResponses(url) + require.NoError(t, err) + + ctx := context.Background() + waitForStatus := api.WaitForStatus(metamorph_api.Status_SEEN_ON_NETWORK) + params := &api.POSTTransactionParams{ + XWaitForStatus: &waitForStatus, + } + + arcBody := api.POSTTransactionJSONRequestBody{ + RawTx: hex.EncodeToString(tx.ExtendedBytes()), + } + + // submit first transaction + var response *api.POSTTransactionResponse + response, err = arcClient.POSTTransactionWithResponse(ctx, params, arcBody) + require.NoError(t, err) + + require.Equal(t, http.StatusOK, response.StatusCode()) + require.NotNil(t, response.JSON200) + require.Equal(t, "SEEN_ON_NETWORK", response.JSON200.TxStatus) + + // send double spending transaction when first tx is in mempool + arcBodyMempool := getArcBody(t, privateKey, utxos[0], tc.extFormat) + + response, err = arcClient.POSTTransactionWithResponse(ctx, params, arcBodyMempool) + require.NoError(t, err) + + require.Equal(t, http.StatusOK, response.StatusCode()) + require.NotNil(t, response.JSON200) + require.Equal(t, "REJECTED", response.JSON200.TxStatus) + + generate(t, 10) + var statusResponse *api.GETTransactionStatusResponse + statusResponse, err = arcClient.GETTransactionStatusWithResponse(ctx, response.JSON200.Txid) + + require.Equal(t, handler.PtrTo("MINED"), statusResponse.JSON200.TxStatus) + require.NotNil(t, statusResponse.JSON200.MerklePath) + + // send double spending transaction when first tx is mined + arcBodyMined := getArcBody(t, privateKey, utxos[0], tc.extFormat) + + response, err = arcClient.POSTTransactionWithResponse(ctx, params, arcBodyMined) + require.NoError(t, err) + + require.Equal(t, http.StatusOK, response.StatusCode()) + require.NotNil(t, response.JSON200) + require.Equal(t, "REJECTED", response.JSON200.TxStatus) + }) + } +} + +func getArcBody(t *testing.T, privateKey string, utxo NodeUnspentUtxo, extFormat bool) api.POSTTransactionJSONRequestBody { + address, err := bitcoind.GetNewAddress() + + tx1, err := createTx(privateKey, address, utxo) + require.NoError(t, err) + var arcBodyTx string + if extFormat { + arcBodyTx = hex.EncodeToString(tx1.ExtendedBytes()) + } else { + arcBodyTx = hex.EncodeToString(tx1.Bytes()) + } + arcBody := api.POSTTransactionJSONRequestBody{ + RawTx: arcBodyTx, + } + return arcBody +} diff --git a/test/utils.go b/test/utils.go index 9467e2761..fafefd3d8 100644 --- a/test/utils.go +++ b/test/utils.go @@ -62,20 +62,21 @@ func init() { } -func getNewWalletAddress(t *testing.T) (address, privateKey string) { - address, err := bitcoind.GetNewAddress() - require.NoError(t, err) - t.Logf("new address: %s", address) +func getNewWalletAddress() (address, privateKey string, err error) { + address, err = bitcoind.GetNewAddress() + if err != nil { + return + } privateKey, err = bitcoind.DumpPrivKey(address) - require.NoError(t, err) - t.Logf("new private key: %s", privateKey) - - accountName := "test-account" - err = bitcoind.SetAccount(address, accountName) - require.NoError(t, err) + if err != nil { + return + } - t.Logf("account %s created", accountName) + err = bitcoind.SetAccount(address, "test-account") + if err != nil { + return + } return }