gitlab.com/SiaPrime/SiaPrime@v1.4.1/node/api/transactionpool_test.go (about)

     1  package api
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/base64"
     6  	"net/url"
     7  	"strings"
     8  	"testing"
     9  	"time"
    10  
    11  	"gitlab.com/SiaPrime/SiaPrime/build"
    12  	"gitlab.com/SiaPrime/SiaPrime/encoding"
    13  	"gitlab.com/SiaPrime/SiaPrime/types"
    14  )
    15  
    16  // TestTransactionPoolRawHandler verifies that the transaction pools' raw
    17  // transaction post endpoint works correctly.
    18  func TestTransactionPoolRawHandlerPOST(t *testing.T) {
    19  	if testing.Short() {
    20  		t.SkipNow()
    21  	}
    22  	t.Parallel()
    23  	st, err := createServerTester(t.Name())
    24  	if err != nil {
    25  		t.Fatal(err)
    26  	}
    27  
    28  	// Spin up a second and fourth server tester, and get them all on the same
    29  	// block. The fourth server tester will be used later, after a third is
    30  	// created and used.
    31  	st2, err := blankServerTester(t.Name() + "-st2")
    32  	if err != nil {
    33  		t.Fatal(err)
    34  	}
    35  	st4, err := blankServerTester(t.Name() + "-st4")
    36  	if err != nil {
    37  		t.Fatal(err)
    38  	}
    39  	err = fullyConnectNodes([]*serverTester{st, st2, st4})
    40  	if err != nil {
    41  		t.Fatal(err)
    42  	}
    43  
    44  	// Reset the peers, giving them different ip addresses, preventing them
    45  	// from connecting to eachother.
    46  	err = st.server.Close()
    47  	if err != nil {
    48  		t.Fatal(err)
    49  	}
    50  	err = st2.server.Close()
    51  	if err != nil {
    52  		t.Fatal(err)
    53  	}
    54  	err = st4.server.Close()
    55  	if err != nil {
    56  		t.Fatal(err)
    57  	}
    58  	st, err = assembleServerTester(st.walletKey, st.dir)
    59  	if err != nil {
    60  		t.Fatal(err)
    61  	}
    62  	defer st.panicClose()
    63  	st2, err = assembleServerTester(st2.walletKey, st2.dir)
    64  	if err != nil {
    65  		t.Fatal(err)
    66  	}
    67  	defer st2.panicClose()
    68  	st4, err = assembleServerTester(st4.walletKey, st4.dir)
    69  	if err != nil {
    70  		t.Fatal(err)
    71  	}
    72  	defer st4.panicClose()
    73  
    74  	// Create a transaction on one node and fetch it.
    75  	sentValue := types.SiacoinPrecision.Mul64(1000)
    76  	txns, err := st.wallet.SendSiacoins(sentValue, types.UnlockHash{})
    77  	if err != nil {
    78  		t.Fatal(err)
    79  	}
    80  	lastTxn := txns[len(txns)-1]
    81  	var trg TpoolRawGET
    82  	err = st.getAPI("/tpool/raw/"+lastTxn.ID().String(), &trg)
    83  	if err != nil {
    84  		t.Fatal(err)
    85  	}
    86  
    87  	// Verify the correctness of the transaction.
    88  	var decodedTxn types.Transaction
    89  	err = encoding.Unmarshal(trg.Transaction, &decodedTxn)
    90  	if err != nil {
    91  		t.Fatal(err)
    92  	}
    93  	if decodedTxn.ID() != lastTxn.ID() {
    94  		t.Fatal("tpool raw get returned the wrong transaction")
    95  	}
    96  	// Verify the correctness of the parents.
    97  	var decodedParents []types.Transaction
    98  	err = encoding.Unmarshal(trg.Parents, &decodedParents)
    99  	if err != nil {
   100  		t.Fatal(err)
   101  	}
   102  	if len(decodedParents) != len(txns)-1 {
   103  		t.Fatal("returned the incorrect number of parents")
   104  	}
   105  
   106  	// Transaction should not be visible on node 2.
   107  	var trg2 TpoolRawGET
   108  	err = st2.getAPI("/tpool/raw/"+lastTxn.ID().String(), &trg2)
   109  	if err.Error() != "transaction not found in transaction pool" {
   110  		t.Fatal("transaction should be missing initially from the second tpool")
   111  	}
   112  
   113  	// Try posting the transaction to node 2.
   114  	postValues := url.Values{}
   115  	postValues.Set("parents", string(trg.Parents))
   116  	postValues.Set("transaction", string(trg.Transaction))
   117  	err = st2.stdPostAPI("/tpool/raw", postValues)
   118  	if err != nil {
   119  		t.Fatal(err)
   120  	}
   121  
   122  	// Verify that the two transactions returned from each server are
   123  	// identical.
   124  	err = st2.getAPI("/tpool/raw/"+lastTxn.ID().String(), &trg2)
   125  	if err != nil {
   126  		t.Fatal(err)
   127  	}
   128  	if !bytes.Equal(trg2.Parents, trg.Parents) {
   129  		t.Error("transaction parents mismatch")
   130  	}
   131  	if !bytes.Equal(trg2.Transaction, trg.Transaction) {
   132  		t.Error("transaction parents mismatch")
   133  	}
   134  
   135  	// Create a third server tester, connect it to the second one.
   136  	st3, err := blankServerTester(t.Name() + "-st3")
   137  	if err != nil {
   138  		t.Fatal(err)
   139  	}
   140  	defer st3.server.panicClose()
   141  	err = fullyConnectNodes([]*serverTester{st2, st3})
   142  	if err != nil {
   143  		t.Fatal(err)
   144  	}
   145  	// Posting the raw transaction to the second server again should cause it
   146  	// to be broadcast to the third server.
   147  	err = st2.stdPostAPI("/tpool/raw", postValues)
   148  	if err != nil {
   149  		t.Fatal(err)
   150  	}
   151  	err = build.Retry(100, time.Millisecond*100, func() error {
   152  		return st3.getAPI("/tpool/raw/"+lastTxn.ID().String(), &trg)
   153  	})
   154  	if err != nil {
   155  		t.Fatal("Txn was not broadcast to the third server", err)
   156  	}
   157  
   158  	// Mine a block on the first server, which should clear its transaction
   159  	// pool.
   160  	_, err = st.miner.AddBlock()
   161  	if err != nil {
   162  		t.Fatal(err)
   163  	}
   164  	err = st.getAPI("/tpool/raw/"+lastTxn.ID().String(), &trg)
   165  	if err.Error() != "transaction not found in transaction pool" {
   166  		t.Fatal("transaction should be gone from the pool after mining a block")
   167  	}
   168  
   169  	// Convert the returned transactions to base64, which is how they will be
   170  	// presetned to someone using curl. Submit those to the POST endpoint. The
   171  	// POST endpoint should gracefully handle that submission as base64.
   172  	//
   173  	// The first 3 st's all have the transactions already, so now we'll use st4.
   174  	b64Parents := base64.StdEncoding.EncodeToString(trg.Parents)
   175  	b64Transaction := base64.StdEncoding.EncodeToString(trg.Transaction)
   176  	postValues = url.Values{}
   177  	postValues.Set("parents", b64Parents)
   178  	postValues.Set("transaction", b64Transaction)
   179  	err = st4.stdPostAPI("/tpool/raw", postValues)
   180  	if err != nil {
   181  		t.Fatal(err)
   182  	}
   183  }
   184  
   185  // TestTransactionPoolFee tests the /tpool/fee endpoint.
   186  func TestTransactionPoolFee(t *testing.T) {
   187  	if testing.Short() {
   188  		t.SkipNow()
   189  	}
   190  	t.Parallel()
   191  	st, err := createServerTester(t.Name())
   192  	if err != nil {
   193  		t.Fatal(err)
   194  	}
   195  
   196  	var fees TpoolFeeGET
   197  	err = st.getAPI("/tpool/fee", &fees)
   198  	if err != nil {
   199  		t.Fatal(err)
   200  	}
   201  
   202  	min, max := st.tpool.FeeEstimation()
   203  	if !min.Equals(fees.Minimum) || !max.Equals(fees.Maximum) {
   204  		t.Fatal("fee mismatch")
   205  	}
   206  }
   207  
   208  // TestTransactionPoolConfirmed tests the /tpool/confirmed endpoint.
   209  func TestTransactionPoolConfirmed(t *testing.T) {
   210  	if testing.Short() {
   211  		t.SkipNow()
   212  	}
   213  	t.Parallel()
   214  	st, err := createServerTester(t.Name())
   215  	if err != nil {
   216  		t.Fatal(err)
   217  	}
   218  
   219  	// Create a transaction.
   220  	sentValue := types.SiacoinPrecision.Mul64(1000)
   221  	txns, err := st.wallet.SendSiacoins(sentValue, types.UnlockHash{})
   222  	if err != nil {
   223  		t.Fatal(err)
   224  	}
   225  	txnID := txns[len(txns)-1].ID().String()
   226  
   227  	// Transaction should not be confirmed yet.
   228  	var tcg TpoolConfirmedGET
   229  	err = st.getAPI("/tpool/confirmed/"+txnID, &tcg)
   230  	if err != nil {
   231  		t.Fatal(err)
   232  	} else if tcg.Confirmed {
   233  		t.Fatal("transaction should not be confirmed")
   234  	}
   235  
   236  	// Mine the block containing the transaction
   237  	_, err = st.miner.AddBlock()
   238  	if err != nil {
   239  		t.Fatal(err)
   240  	}
   241  
   242  	// Transaction should now be confirmed.
   243  	err = st.getAPI("/tpool/confirmed/"+txnID, &tcg)
   244  	if err != nil {
   245  		t.Fatal(err)
   246  	} else if !tcg.Confirmed {
   247  		t.Fatal("transaction should be confirmed")
   248  	}
   249  
   250  	// Check for a nonexistent transaction.
   251  	badID := strings.Repeat("0", len(txnID))
   252  	err = st.getAPI("/tpool/confirmed/"+badID, &tcg)
   253  	if err != nil {
   254  		t.Fatal(err)
   255  	} else if tcg.Confirmed {
   256  		t.Fatal("transaction should not be confirmed")
   257  	}
   258  }