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 }