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  }