github.com/deso-protocol/core@v1.2.9/lib/mempool_test.go (about)

     1  package lib
     2  
     3  import (
     4  	"fmt"
     5  	"testing"
     6  
     7  	"github.com/stretchr/testify/require"
     8  )
     9  
    10  func _filterOutBlockRewards(utxoEntries []*UtxoEntry) []*UtxoEntry {
    11  	nonBlockRewardUtxos := []*UtxoEntry{}
    12  	for _, utxoEntry := range utxoEntries {
    13  		if utxoEntry.UtxoType != UtxoTypeBlockReward {
    14  			nonBlockRewardUtxos = append(nonBlockRewardUtxos, utxoEntry)
    15  		}
    16  	}
    17  	return nonBlockRewardUtxos
    18  }
    19  
    20  func _setupFiveBlocks(t *testing.T) (*Blockchain, *DeSoParams, []byte, []byte) {
    21  	require := require.New(t)
    22  	chain, params, db := NewLowDifficultyBlockchain()
    23  	_ = db
    24  
    25  	_, _, blockB1, blockB2, blockB3, blockB4, blockB5 := getForkedChain(t)
    26  
    27  	// Connect 5 blocks so we have some block reward to spend.
    28  	_shouldConnectBlock(blockB1, t, chain)
    29  	_shouldConnectBlock(blockB2, t, chain)
    30  	_shouldConnectBlock(blockB3, t, chain)
    31  	_shouldConnectBlock(blockB4, t, chain)
    32  	_shouldConnectBlock(blockB5, t, chain)
    33  
    34  	// Define a sender and a recipient.
    35  	senderPkBytes, _, err := Base58CheckDecode(senderPkString)
    36  	require.NoError(err)
    37  	recipientPkBytes, _, err := Base58CheckDecode(recipientPkString)
    38  	require.NoError(err)
    39  
    40  	return chain, params, senderPkBytes, recipientPkBytes
    41  }
    42  
    43  // Create a chain of transactions that is too long for our mempool to
    44  // handle and ensure it gets rejected.
    45  func TestMempoolLongChainOfDependencies(t *testing.T) {
    46  	require := require.New(t)
    47  
    48  	chain, _, senderPkBytes, recipientPkBytes := _setupFiveBlocks(t)
    49  
    50  	// Create a transaction that sends 1 DeSo to the recipient as its
    51  	// zeroth output.
    52  	txn1 := _assembleBasicTransferTxnFullySigned(t, chain, 1, 0,
    53  		senderPkString, recipientPkString, senderPrivString, nil)
    54  
    55  	// Validate this txn.
    56  	mp := NewDeSoMempool(
    57  		chain, 0, /* rateLimitFeeRateNanosPerKB */
    58  		0 /* minFeeRateNanosPerKB */, "", true,
    59  		"" /*dataDir*/, "")
    60  	_, err := mp.processTransaction(txn1, false /*allowUnconnectedTxn*/, false /*rateLimit*/, 0 /*peerID*/, true /*verifySignatures*/)
    61  	require.NoError(err)
    62  
    63  	prevTxn := txn1
    64  	// Create fewer than the maximum number of dependencies allowed by the
    65  	// mempool and make sure all of these transactions are accepted. Then,
    66  	// add one more transaction and make sure it's rejected.
    67  	chainLen := 2500
    68  	for ii := 0; ii < chainLen+1; ii++ {
    69  		if ii%100 == 0 {
    70  			fmt.Printf("TestMempoolRateLimit: Processing txn %d\n", ii)
    71  		}
    72  		prevTxnHash := prevTxn.Hash()
    73  		newTxn := &MsgDeSoTxn{
    74  			TxInputs: []*DeSoInput{
    75  				&DeSoInput{
    76  					TxID:  *prevTxnHash,
    77  					Index: 0,
    78  				},
    79  			},
    80  			TxOutputs: []*DeSoOutput{
    81  				&DeSoOutput{
    82  					PublicKey:   recipientPkBytes,
    83  					AmountNanos: 1,
    84  				},
    85  			},
    86  			TxnMeta:   &BasicTransferMetadata{},
    87  			PublicKey: recipientPkBytes,
    88  		}
    89  		//_signTxn(t, newTxn, false [>isSender is false since this is the recipient<])
    90  
    91  		_, err := mp.processTransaction(newTxn, false /*allowUnconnectedTxn*/, false /*rateLimit*/, 0 /*peerID*/, false /*verifySignatures*/)
    92  		require.NoErrorf(err, "Error processing txn %d", ii)
    93  
    94  		prevTxn = newTxn
    95  	}
    96  	_, _ = require, senderPkBytes
    97  }
    98  
    99  // Create a chain of transactions with zero fees. Have one public key just
   100  // send 1 DeSo to itself over and over again. Then run all the txns
   101  // through the mempool at once and verify that they are rejected when
   102  // a ratelimit is set.
   103  func TestMempoolRateLimit(t *testing.T) {
   104  	require := require.New(t)
   105  
   106  	// Set the LowFeeTxLimitBytesPerMinute very low so that we can trigger
   107  	// rate limiting without having to generate too many transactions. 1000
   108  	// bytes per ten minutes should be about 10 transactions.
   109  	LowFeeTxLimitBytesPerTenMinutes = 1000
   110  
   111  	chain, _, senderPkBytes, recipientPkBytes := _setupFiveBlocks(t)
   112  
   113  	// Create a new pool object that sets the min fees to zero. This object should
   114  	// accept all of the transactions we're about to create without fail.
   115  	mpNoMinFees := NewDeSoMempool(
   116  		chain, 0, /* rateLimitFeeRateNanosPerKB */
   117  		0 /* minFeeRateNanosPerKB */, "", true,
   118  		"" /*dataDir*/, "")
   119  
   120  	// Create a transaction that sends 1 DeSo to the recipient as its
   121  	// zeroth output.
   122  	txn1 := _assembleBasicTransferTxnFullySigned(t, chain, 1, 0,
   123  		senderPkString, recipientPkString, senderPrivString, nil)
   124  
   125  	// Validate this txn with the no-fee mempool.
   126  	_, err := mpNoMinFees.processTransaction(txn1, false /*allowUnconnectedTxn*/, false /*rateLimit*/, 0 /*peerID*/, true /*verifySignatures*/)
   127  	require.NoError(err)
   128  
   129  	// If we set a min fee, the transactions should just be immediately rejected
   130  	// if we set rateLimit to true.
   131  	mpWithMinFee := NewDeSoMempool(
   132  		chain, 0, /* rateLimitFeeRateNanosPerKB */
   133  		100 /* minFeeRateNanosPerKB */, "", true,
   134  		"" /*dataDir*/, "")
   135  	_, err = mpWithMinFee.processTransaction(txn1, false /*allowUnconnectedTxn*/, true /*rateLimit*/, 0 /*peerID*/, false /*verifySignatures*/)
   136  	require.Error(err)
   137  	require.Contains(err.Error(), TxErrorInsufficientFeeMinFee)
   138  
   139  	// It shoud be accepted if we set rateLimit to false.
   140  	_, err = mpWithMinFee.processTransaction(txn1, false /*allowUnconnectedTxn*/, false /*rateLimit*/, 0 /*peerID*/, false /*verifySignatures*/)
   141  	require.NoError(err)
   142  
   143  	txnsCreated := []*MsgDeSoTxn{txn1}
   144  	prevTxn := txn1
   145  	// Create fewer than the maximum number of dependencies allowed by the
   146  	// mempool to avoid transactions being rejected.
   147  	for ii := 0; ii < 24; ii++ {
   148  		if ii%100 == 0 {
   149  			fmt.Printf("TestMempoolRateLimit: Processing txn %d\n", ii)
   150  		}
   151  		prevTxnHash := prevTxn.Hash()
   152  		newTxn := &MsgDeSoTxn{
   153  			TxInputs: []*DeSoInput{
   154  				&DeSoInput{
   155  					TxID:  *prevTxnHash,
   156  					Index: 0,
   157  				},
   158  			},
   159  			TxOutputs: []*DeSoOutput{
   160  				&DeSoOutput{
   161  					PublicKey:   recipientPkBytes,
   162  					AmountNanos: 1,
   163  				},
   164  			},
   165  			TxnMeta:   &BasicTransferMetadata{},
   166  			PublicKey: recipientPkBytes,
   167  		}
   168  		//_signTxn(t, newTxn, false [>isSender is false since this is the recipient<])
   169  
   170  		_, err := mpNoMinFees.processTransaction(newTxn, false /*allowUnconnectedTxn*/, false /*rateLimit*/, 0 /*peerID*/, false /*verifySignatures*/)
   171  		require.NoErrorf(err, "Error processing txn %d", ii)
   172  
   173  		txnsCreated = append(txnsCreated, newTxn)
   174  		prevTxn = newTxn
   175  	}
   176  
   177  	// Processing 24 transactions very quickly should cause our rate
   178  	// limit to trigger if it's set even if we don't have a hard min
   179  	// feerate set since 24 transactions should be ~2400 bytes.
   180  	mpWithRateLimit := NewDeSoMempool(
   181  		chain, 100, /* rateLimitFeeRateNanosPerKB */
   182  		0 /* minFeeRateNanosPerKB */, "", true,
   183  		"" /*dataDir*/, "")
   184  	processingErrors := []error{}
   185  	for _, txn := range txnsCreated {
   186  		_, err := mpWithRateLimit.processTransaction(txn, false /*allowUnconnectedTxn*/, true /*rateLimit*/, 0 /*peerID*/, false /*verifySignatures*/)
   187  		processingErrors = append(processingErrors, err)
   188  	}
   189  
   190  	// If we got rate-limited, the first transaction should be error-free.
   191  	firstError := processingErrors[0]
   192  	require.NoError(firstError, "First transaction should not be rate-limited")
   193  	// If we got rate-limited, there should be at least one transaction in
   194  	// the list that has the rate-limited error.
   195  	require.Contains(processingErrors, TxErrorInsufficientFeeRateLimit)
   196  
   197  	_, _ = require, senderPkBytes
   198  }
   199  
   200  // A chain of transactions one after the other each spending the change
   201  // output of the previous transaction with the same key.
   202  func TestMempoolAugmentedUtxoViewTransactionChain(t *testing.T) {
   203  	require := require.New(t)
   204  
   205  	chain, params, senderPkBytes, recipientPkBytes := _setupFiveBlocks(t)
   206  
   207  	// Create a transaction that spends very little so that it creates
   208  	// a lot of change.
   209  	txn1 := _assembleBasicTransferTxnFullySigned(t, chain, 1, 0,
   210  		senderPkString, recipientPkString, senderPrivString, nil)
   211  
   212  	// There should be two outputs, the second of which should be change to
   213  	// the sender.
   214  	require.Equal(2, len(txn1.TxOutputs))
   215  	changeOutput := txn1.TxOutputs[1]
   216  	require.Equal(senderPkString,
   217  		Base58CheckEncode(changeOutput.PublicKey, false, chain.params))
   218  
   219  	// Construct a second transaction that depends on the first. Send 1
   220  	// DeSo to the recipient and set the rest as change.
   221  	txn1Hash := txn1.Hash()
   222  	txn2 := &MsgDeSoTxn{
   223  		// Set the change of the previous transaction as input.
   224  		TxInputs: []*DeSoInput{
   225  			&DeSoInput{
   226  				TxID:  *txn1Hash,
   227  				Index: 1,
   228  			},
   229  		},
   230  		TxOutputs: []*DeSoOutput{
   231  			&DeSoOutput{
   232  				PublicKey:   recipientPkBytes,
   233  				AmountNanos: 1,
   234  			}, &DeSoOutput{
   235  				PublicKey:   senderPkBytes,
   236  				AmountNanos: changeOutput.AmountNanos - 1,
   237  			},
   238  		},
   239  		PublicKey: senderPkBytes,
   240  		TxnMeta:   &BasicTransferMetadata{},
   241  	}
   242  	_signTxn(t, txn2, senderPrivString)
   243  
   244  	// Construct a third transaction that depends on the second.
   245  	txn2Hash := txn2.Hash()
   246  	txn3 := &MsgDeSoTxn{
   247  		// Set the change of the previous transaction as input.
   248  		TxInputs: []*DeSoInput{
   249  			&DeSoInput{
   250  				TxID:  *txn2Hash,
   251  				Index: 1,
   252  			},
   253  		},
   254  		TxOutputs: []*DeSoOutput{
   255  			&DeSoOutput{
   256  				PublicKey:   recipientPkBytes,
   257  				AmountNanos: 1,
   258  			}, &DeSoOutput{
   259  				PublicKey:   senderPkBytes,
   260  				AmountNanos: changeOutput.AmountNanos - 2,
   261  			},
   262  		},
   263  		PublicKey: senderPkBytes,
   264  		TxnMeta:   &BasicTransferMetadata{},
   265  	}
   266  	_signTxn(t, txn3, senderPrivString)
   267  	txn3Hash := txn3.Hash()
   268  
   269  	// Construct a fourth transaction that spends an output from the recipient's
   270  	// key sending the DeSo back to the sender with some change going back to
   271  	// herself. Make the output come from the first and second transaction above.
   272  	txn4 := &MsgDeSoTxn{
   273  		// Set the change of the previous transaction as input.
   274  		TxInputs: []*DeSoInput{
   275  			&DeSoInput{
   276  				TxID:  *txn1Hash,
   277  				Index: 0,
   278  			},
   279  			&DeSoInput{
   280  				TxID:  *txn2Hash,
   281  				Index: 0,
   282  			},
   283  		},
   284  		TxOutputs: []*DeSoOutput{
   285  			&DeSoOutput{
   286  				PublicKey:   senderPkBytes,
   287  				AmountNanos: 1,
   288  			}, &DeSoOutput{
   289  				PublicKey:   recipientPkBytes,
   290  				AmountNanos: 1,
   291  			},
   292  		},
   293  		PublicKey: recipientPkBytes,
   294  		TxnMeta:   &BasicTransferMetadata{},
   295  	}
   296  	_signTxn(t, txn4, recipientPrivString)
   297  	txn4Hash := txn4.Hash()
   298  
   299  	// Create a new pool object. Set the min fees to zero since we're
   300  	// not testing that here.
   301  	mp := NewDeSoMempool(
   302  		chain, 0, /* rateLimitFeeRateNanosPerKB */
   303  		0 /* minFeeRateNanosPerKB */, "", true,
   304  		"" /*dataDir*/, "")
   305  
   306  	// Process the first transaction.
   307  	mempoolTx1, err := mp.processTransaction(txn1, false /*allowUnconnectedTxn*/, false /*rateLimit*/, 0 /*peerID*/, true /*verifySignatures*/)
   308  	require.NoError(err)
   309  	{
   310  		// Verify the augmented UtxoView has the change output from the
   311  		// first transaction in it. This output should have the hash of
   312  		// the first transaction with an index of 1.
   313  		utxoView, err := mp.GetAugmentedUtxoViewForPublicKey(senderPkBytes, nil)
   314  		require.NoError(err)
   315  		utxoEntries, err := utxoView.GetUnspentUtxoEntrysForPublicKey(senderPkBytes)
   316  		require.NoError(err)
   317  		// The block reward transactions should be included in the list of
   318  		// outputs spendable by this public key.
   319  		require.LessOrEqual(1, len(utxoEntries))
   320  		nonBlockRewardUtxos := _filterOutBlockRewards(utxoEntries)
   321  		require.Equal(1, len(nonBlockRewardUtxos))
   322  		require.Equal(false, nonBlockRewardUtxos[0].isSpent)
   323  		require.Equal(*txn1Hash, nonBlockRewardUtxos[0].UtxoKey.TxID)
   324  	}
   325  
   326  	{
   327  		// Verify that the recipient's payment is returned when we do a lookup
   328  		// with her key.
   329  		utxoView, err := mp.GetAugmentedUtxoViewForPublicKey(recipientPkBytes, nil)
   330  		require.NoError(err)
   331  		utxoEntries, err := utxoView.GetUnspentUtxoEntrysForPublicKey(recipientPkBytes)
   332  		require.NoError(err)
   333  		// The number of utxos for the recipient should be exactly 1 since she doesn't
   334  		// get any block rewards.
   335  		require.Equal(1, len(utxoEntries))
   336  		require.Equal(false, utxoEntries[0].isSpent)
   337  		require.Equal(*txn1Hash, utxoEntries[0].UtxoKey.TxID)
   338  		require.Equal(uint64(1), utxoEntries[0].AmountNanos)
   339  	}
   340  
   341  	// Process the second transaction, which is dependent on the first.
   342  	mempoolTx2, err := mp.processTransaction(txn2, false /*allowUnconnectedTxn*/, false /*rateLimit*/, 0 /*peerID*/, true /*verifySignatures*/)
   343  	require.NoError(err)
   344  	{
   345  		// Verify the augmented UtxoView has the change output from the second
   346  		// transaction in it. The second transaction's output should have replaced
   347  		// the utxo corresponding to the first transaction from before.
   348  		utxoView, err := mp.GetAugmentedUtxoViewForPublicKey(senderPkBytes, nil)
   349  		require.NoError(err)
   350  		utxoEntries, err := utxoView.GetUnspentUtxoEntrysForPublicKey(senderPkBytes)
   351  		require.NoError(err)
   352  		require.LessOrEqual(1, len(utxoEntries))
   353  		nonBlockRewardUtxos := _filterOutBlockRewards(utxoEntries)
   354  		require.Equal(1, len(nonBlockRewardUtxos))
   355  		require.Equal(false, nonBlockRewardUtxos[0].isSpent)
   356  		require.Equal(*txn2Hash, nonBlockRewardUtxos[0].UtxoKey.TxID)
   357  	}
   358  
   359  	// Process the third transaction, which is dependent on the second.
   360  	mempoolTx3, err := mp.processTransaction(txn3, false /*allowUnconnectedTxn*/, false /*rateLimit*/, 0 /*peerID*/, true /*verifySignatures*/)
   361  	require.NoError(err)
   362  	{
   363  		// Verify the augmented UtxoView has the change output from the third
   364  		// transaction in it. The third transaction's output should have replaced
   365  		// the utxo corresponding to the first transaction from before.
   366  		utxoView, err := mp.GetAugmentedUtxoViewForPublicKey(senderPkBytes, nil)
   367  		require.NoError(err)
   368  		utxoEntries, err := utxoView.GetUnspentUtxoEntrysForPublicKey(senderPkBytes)
   369  		require.NoError(err)
   370  		require.LessOrEqual(1, len(utxoEntries))
   371  		nonBlockRewardUtxos := _filterOutBlockRewards(utxoEntries)
   372  		require.Equal(1, len(nonBlockRewardUtxos))
   373  		require.Equal(false, nonBlockRewardUtxos[0].isSpent)
   374  		require.Equal(*txn3Hash, nonBlockRewardUtxos[0].UtxoKey.TxID)
   375  	}
   376  
   377  	// Process the fourth transaction, which is dependent on the first and second.
   378  	mempoolTx4, err := mp.processTransaction(txn4, false /*allowUnconnectedTxn*/, false /*rateLimit*/, 0 /*peerID*/, true /*verifySignatures*/)
   379  	require.NoError(err)
   380  	{
   381  		// When we lookup the utxos for the sender we should now have two, one
   382  		// of which should have an amount of exactly 1.
   383  		utxoView, err := mp.GetAugmentedUtxoViewForPublicKey(senderPkBytes, nil)
   384  		require.NoError(err)
   385  		utxoEntries, err := utxoView.GetUnspentUtxoEntrysForPublicKey(senderPkBytes)
   386  		require.NoError(err)
   387  		require.LessOrEqual(2, len(utxoEntries))
   388  		nonBlockRewardUtxos := _filterOutBlockRewards(utxoEntries)
   389  		require.Equal(2, len(nonBlockRewardUtxos))
   390  		// Aggregate the txids and amounts to check them.
   391  		txids := []BlockHash{}
   392  		amounts := []uint64{}
   393  		for ii, utxoEntry := range nonBlockRewardUtxos {
   394  			txids = append(txids, utxoEntry.UtxoKey.TxID)
   395  			amounts = append(amounts, utxoEntry.AmountNanos)
   396  			require.Equalf(false, utxoEntry.isSpent, "index: %d", ii)
   397  		}
   398  		require.Contains(txids, *txn3Hash)
   399  		require.Contains(txids, *txn4Hash)
   400  		require.Contains(amounts, uint64(1))
   401  	}
   402  
   403  	{
   404  		// Verify that the recipient's payments are returned when we do a lookup
   405  		// with her key.
   406  		utxoView, err := mp.GetAugmentedUtxoViewForPublicKey(recipientPkBytes, nil)
   407  		require.NoError(err)
   408  		utxoEntries, err := utxoView.GetUnspentUtxoEntrysForPublicKey(recipientPkBytes)
   409  		require.NoError(err)
   410  		// She should have exactly 2 utxos at this point from txn3 and txn4.
   411  		// Aggregate the txids and amounts to check them.
   412  		require.Equal(2, len(utxoEntries))
   413  		txids := []BlockHash{}
   414  		for ii, utxoEntry := range utxoEntries {
   415  			txids = append(txids, utxoEntry.UtxoKey.TxID)
   416  			require.Equalf(uint64(1), utxoEntry.AmountNanos, "index: %d", ii)
   417  			require.Equalf(false, utxoEntry.isSpent, "index: %d", ii)
   418  		}
   419  		require.Contains(txids, *txn3Hash)
   420  		require.Contains(txids, *txn4Hash)
   421  	}
   422  
   423  	_, _, _, _, _ = mempoolTx1, mempoolTx2, mempoolTx3, mempoolTx4, params
   424  }