github.com/btcsuite/btcd@v0.24.0/mempool/estimatefee_test.go (about) 1 // Copyright (c) 2016 The btcsuite developers 2 // Use of this source code is governed by an ISC 3 // license that can be found in the LICENSE file. 4 5 package mempool 6 7 import ( 8 "bytes" 9 "math/rand" 10 "testing" 11 12 "github.com/btcsuite/btcd/btcutil" 13 "github.com/btcsuite/btcd/chaincfg/chainhash" 14 "github.com/btcsuite/btcd/mining" 15 "github.com/btcsuite/btcd/wire" 16 ) 17 18 // newTestFeeEstimator creates a feeEstimator with some different parameters 19 // for testing purposes. 20 func newTestFeeEstimator(binSize, maxReplacements, maxRollback uint32) *FeeEstimator { 21 return &FeeEstimator{ 22 maxRollback: maxRollback, 23 lastKnownHeight: 0, 24 binSize: int32(binSize), 25 minRegisteredBlocks: 0, 26 maxReplacements: int32(maxReplacements), 27 observed: make(map[chainhash.Hash]*observedTransaction), 28 dropped: make([]*registeredBlock, 0, maxRollback), 29 } 30 } 31 32 // lastBlock is a linked list of the block hashes which have been 33 // processed by the test FeeEstimator. 34 type lastBlock struct { 35 hash *chainhash.Hash 36 prev *lastBlock 37 } 38 39 // estimateFeeTester interacts with the FeeEstimator to keep track 40 // of its expected state. 41 type estimateFeeTester struct { 42 ef *FeeEstimator 43 t *testing.T 44 version int32 45 height int32 46 last *lastBlock 47 } 48 49 func (eft *estimateFeeTester) testTx(fee btcutil.Amount) *TxDesc { 50 eft.version++ 51 return &TxDesc{ 52 TxDesc: mining.TxDesc{ 53 Tx: btcutil.NewTx(&wire.MsgTx{ 54 Version: eft.version, 55 }), 56 Height: eft.height, 57 Fee: int64(fee), 58 }, 59 StartingPriority: 0, 60 } 61 } 62 63 func expectedFeePerKilobyte(t *TxDesc) BtcPerKilobyte { 64 size := float64(t.TxDesc.Tx.MsgTx().SerializeSize()) 65 fee := float64(t.TxDesc.Fee) 66 67 return SatoshiPerByte(fee / size).ToBtcPerKb() 68 } 69 70 func (eft *estimateFeeTester) newBlock(txs []*wire.MsgTx) { 71 eft.height++ 72 73 block := btcutil.NewBlock(&wire.MsgBlock{ 74 Transactions: txs, 75 }) 76 block.SetHeight(eft.height) 77 78 eft.last = &lastBlock{block.Hash(), eft.last} 79 80 eft.ef.RegisterBlock(block) 81 } 82 83 func (eft *estimateFeeTester) rollback() { 84 if eft.last == nil { 85 return 86 } 87 88 err := eft.ef.Rollback(eft.last.hash) 89 90 if err != nil { 91 eft.t.Errorf("Could not rollback: %v", err) 92 } 93 94 eft.height-- 95 eft.last = eft.last.prev 96 } 97 98 // TestEstimateFee tests basic functionality in the FeeEstimator. 99 func TestEstimateFee(t *testing.T) { 100 ef := newTestFeeEstimator(5, 3, 1) 101 eft := estimateFeeTester{ef: ef, t: t} 102 103 // Try with no txs and get zero for all queries. 104 expected := BtcPerKilobyte(0.0) 105 for i := uint32(1); i <= estimateFeeDepth; i++ { 106 estimated, _ := ef.EstimateFee(i) 107 108 if estimated != expected { 109 t.Errorf("Estimate fee error: expected %f when estimator is empty; got %f", expected, estimated) 110 } 111 } 112 113 // Now insert a tx. 114 tx := eft.testTx(1000000) 115 ef.ObserveTransaction(tx) 116 117 // Expected should still be zero because this is still in the mempool. 118 expected = BtcPerKilobyte(0.0) 119 for i := uint32(1); i <= estimateFeeDepth; i++ { 120 estimated, _ := ef.EstimateFee(i) 121 122 if estimated != expected { 123 t.Errorf("Estimate fee error: expected %f when estimator has one tx in mempool; got %f", expected, estimated) 124 } 125 } 126 127 // Change minRegisteredBlocks to make sure that works. Error return 128 // value expected. 129 ef.minRegisteredBlocks = 1 130 expected = BtcPerKilobyte(-1.0) 131 for i := uint32(1); i <= estimateFeeDepth; i++ { 132 estimated, _ := ef.EstimateFee(i) 133 134 if estimated != expected { 135 t.Errorf("Estimate fee error: expected %f before any blocks have been registered; got %f", expected, estimated) 136 } 137 } 138 139 // Record a block with the new tx. 140 eft.newBlock([]*wire.MsgTx{tx.Tx.MsgTx()}) 141 expected = expectedFeePerKilobyte(tx) 142 for i := uint32(1); i <= estimateFeeDepth; i++ { 143 estimated, _ := ef.EstimateFee(i) 144 145 if estimated != expected { 146 t.Errorf("Estimate fee error: expected %f when one tx is binned; got %f", expected, estimated) 147 } 148 } 149 150 // Roll back the last block; this was an orphan block. 151 ef.minRegisteredBlocks = 0 152 eft.rollback() 153 expected = BtcPerKilobyte(0.0) 154 for i := uint32(1); i <= estimateFeeDepth; i++ { 155 estimated, _ := ef.EstimateFee(i) 156 157 if estimated != expected { 158 t.Errorf("Estimate fee error: expected %f after rolling back block; got %f", expected, estimated) 159 } 160 } 161 162 // Record an empty block and then a block with the new tx. 163 // This test was made because of a bug that only appeared when there 164 // were no transactions in the first bin. 165 eft.newBlock([]*wire.MsgTx{}) 166 eft.newBlock([]*wire.MsgTx{tx.Tx.MsgTx()}) 167 expected = expectedFeePerKilobyte(tx) 168 for i := uint32(1); i <= estimateFeeDepth; i++ { 169 estimated, _ := ef.EstimateFee(i) 170 171 if estimated != expected { 172 t.Errorf("Estimate fee error: expected %f when one tx is binned; got %f", expected, estimated) 173 } 174 } 175 176 // Create some more transactions. 177 txA := eft.testTx(500000) 178 txB := eft.testTx(2000000) 179 txC := eft.testTx(4000000) 180 ef.ObserveTransaction(txA) 181 ef.ObserveTransaction(txB) 182 ef.ObserveTransaction(txC) 183 184 // Record 7 empty blocks. 185 for i := 0; i < 7; i++ { 186 eft.newBlock([]*wire.MsgTx{}) 187 } 188 189 // Mine the first tx. 190 eft.newBlock([]*wire.MsgTx{txA.Tx.MsgTx()}) 191 192 // Now the estimated amount should depend on the value 193 // of the argument to estimate fee. 194 for i := uint32(1); i <= estimateFeeDepth; i++ { 195 estimated, _ := ef.EstimateFee(i) 196 if i > 2 { 197 expected = expectedFeePerKilobyte(txA) 198 } else { 199 expected = expectedFeePerKilobyte(tx) 200 } 201 if estimated != expected { 202 t.Errorf("Estimate fee error: expected %f on round %d; got %f", expected, i, estimated) 203 } 204 } 205 206 // Record 5 more empty blocks. 207 for i := 0; i < 5; i++ { 208 eft.newBlock([]*wire.MsgTx{}) 209 } 210 211 // Mine the next tx. 212 eft.newBlock([]*wire.MsgTx{txB.Tx.MsgTx()}) 213 214 // Now the estimated amount should depend on the value 215 // of the argument to estimate fee. 216 for i := uint32(1); i <= estimateFeeDepth; i++ { 217 estimated, _ := ef.EstimateFee(i) 218 if i <= 2 { 219 expected = expectedFeePerKilobyte(txB) 220 } else if i <= 8 { 221 expected = expectedFeePerKilobyte(tx) 222 } else { 223 expected = expectedFeePerKilobyte(txA) 224 } 225 226 if estimated != expected { 227 t.Errorf("Estimate fee error: expected %f on round %d; got %f", expected, i, estimated) 228 } 229 } 230 231 // Record 9 more empty blocks. 232 for i := 0; i < 10; i++ { 233 eft.newBlock([]*wire.MsgTx{}) 234 } 235 236 // Mine txC. 237 eft.newBlock([]*wire.MsgTx{txC.Tx.MsgTx()}) 238 239 // This should have no effect on the outcome because too 240 // many blocks have been mined for txC to be recorded. 241 for i := uint32(1); i <= estimateFeeDepth; i++ { 242 estimated, _ := ef.EstimateFee(i) 243 if i <= 2 { 244 expected = expectedFeePerKilobyte(txC) 245 } else if i <= 8 { 246 expected = expectedFeePerKilobyte(txB) 247 } else if i <= 8+6 { 248 expected = expectedFeePerKilobyte(tx) 249 } else { 250 expected = expectedFeePerKilobyte(txA) 251 } 252 253 if estimated != expected { 254 t.Errorf("Estimate fee error: expected %f on round %d; got %f", expected, i, estimated) 255 } 256 } 257 } 258 259 func (eft *estimateFeeTester) estimates() [estimateFeeDepth]BtcPerKilobyte { 260 261 // Generate estimates 262 var estimates [estimateFeeDepth]BtcPerKilobyte 263 for i := 0; i < estimateFeeDepth; i++ { 264 estimates[i], _ = eft.ef.EstimateFee(uint32(i + 1)) 265 } 266 267 // Check that all estimated fee results go in descending order. 268 for i := 1; i < estimateFeeDepth; i++ { 269 if estimates[i] > estimates[i-1] { 270 eft.t.Error("Estimates not in descending order; got ", 271 estimates[i], " for estimate ", i, " and ", estimates[i-1], " for ", (i - 1)) 272 panic("invalid state.") 273 } 274 } 275 276 return estimates 277 } 278 279 func (eft *estimateFeeTester) round(txHistory [][]*TxDesc, 280 estimateHistory [][estimateFeeDepth]BtcPerKilobyte, 281 txPerRound, txPerBlock uint32) ([][]*TxDesc, [][estimateFeeDepth]BtcPerKilobyte) { 282 283 // generate new txs. 284 var newTxs []*TxDesc 285 for i := uint32(0); i < txPerRound; i++ { 286 newTx := eft.testTx(btcutil.Amount(rand.Intn(1000000))) 287 eft.ef.ObserveTransaction(newTx) 288 newTxs = append(newTxs, newTx) 289 } 290 291 // Generate mempool. 292 mempool := make(map[*observedTransaction]*TxDesc) 293 for _, h := range txHistory { 294 for _, t := range h { 295 if o, exists := eft.ef.observed[*t.Tx.Hash()]; exists && o.mined == mining.UnminedHeight { 296 mempool[o] = t 297 } 298 } 299 } 300 301 // generate new block, with no duplicates. 302 i := uint32(0) 303 newBlockList := make([]*wire.MsgTx, 0, txPerBlock) 304 for _, t := range mempool { 305 newBlockList = append(newBlockList, t.TxDesc.Tx.MsgTx()) 306 i++ 307 308 if i == txPerBlock { 309 break 310 } 311 } 312 313 // Register a new block. 314 eft.newBlock(newBlockList) 315 316 // return results. 317 estimates := eft.estimates() 318 319 // Return results 320 return append(txHistory, newTxs), append(estimateHistory, estimates) 321 } 322 323 // TestEstimateFeeRollback tests the rollback function, which undoes the 324 // effect of a adding a new block. 325 func TestEstimateFeeRollback(t *testing.T) { 326 txPerRound := uint32(7) 327 txPerBlock := uint32(5) 328 binSize := uint32(6) 329 maxReplacements := uint32(4) 330 stepsBack := 2 331 rounds := 30 332 333 eft := estimateFeeTester{ef: newTestFeeEstimator(binSize, maxReplacements, uint32(stepsBack)), t: t} 334 var txHistory [][]*TxDesc 335 estimateHistory := [][estimateFeeDepth]BtcPerKilobyte{eft.estimates()} 336 337 for round := 0; round < rounds; round++ { 338 // Go forward a few rounds. 339 for step := 0; step <= stepsBack; step++ { 340 txHistory, estimateHistory = 341 eft.round(txHistory, estimateHistory, txPerRound, txPerBlock) 342 } 343 344 // Now go back. 345 for step := 0; step < stepsBack; step++ { 346 eft.rollback() 347 348 // After rolling back, we should have the same estimated 349 // fees as before. 350 expected := estimateHistory[len(estimateHistory)-step-2] 351 estimates := eft.estimates() 352 353 // Ensure that these are both the same. 354 for i := 0; i < estimateFeeDepth; i++ { 355 if expected[i] != estimates[i] { 356 t.Errorf("Rollback value mismatch. Expected %f, got %f. ", 357 expected[i], estimates[i]) 358 return 359 } 360 } 361 } 362 363 // Erase history. 364 txHistory = txHistory[0 : len(txHistory)-stepsBack] 365 estimateHistory = estimateHistory[0 : len(estimateHistory)-stepsBack] 366 } 367 } 368 369 func (eft *estimateFeeTester) checkSaveAndRestore( 370 previousEstimates [estimateFeeDepth]BtcPerKilobyte) { 371 372 // Get the save state. 373 save := eft.ef.Save() 374 375 // Save and restore database. 376 var err error 377 eft.ef, err = RestoreFeeEstimator(save) 378 if err != nil { 379 eft.t.Fatalf("Could not restore database: %s", err) 380 } 381 382 // Save again and check that it matches the previous one. 383 redo := eft.ef.Save() 384 if !bytes.Equal(save, redo) { 385 eft.t.Fatalf("Restored states do not match: %v %v", save, redo) 386 } 387 388 // Check that the results match. 389 newEstimates := eft.estimates() 390 391 for i, prev := range previousEstimates { 392 if prev != newEstimates[i] { 393 eft.t.Error("Mismatch in estimate ", i, " after restore; got ", newEstimates[i], " but expected ", prev) 394 } 395 } 396 } 397 398 // TestSave tests saving and restoring to a []byte. 399 func TestDatabase(t *testing.T) { 400 401 txPerRound := uint32(7) 402 txPerBlock := uint32(5) 403 binSize := uint32(6) 404 maxReplacements := uint32(4) 405 rounds := 8 406 407 eft := estimateFeeTester{ef: newTestFeeEstimator(binSize, maxReplacements, uint32(rounds)+1), t: t} 408 var txHistory [][]*TxDesc 409 estimateHistory := [][estimateFeeDepth]BtcPerKilobyte{eft.estimates()} 410 411 for round := 0; round < rounds; round++ { 412 eft.checkSaveAndRestore(estimateHistory[len(estimateHistory)-1]) 413 414 // Go forward one step. 415 txHistory, estimateHistory = 416 eft.round(txHistory, estimateHistory, txPerRound, txPerBlock) 417 } 418 419 // Reverse the process and try again. 420 for round := 1; round <= rounds; round++ { 421 eft.rollback() 422 eft.checkSaveAndRestore(estimateHistory[len(estimateHistory)-round-1]) 423 } 424 }