github.com/decred/dcrlnd@v0.7.6/htlcswitch/decayedlog_test.go (about)

     1  package htlcswitch
     2  
     3  import (
     4  	"crypto/rand"
     5  	"fmt"
     6  	"io/ioutil"
     7  	"os"
     8  	"testing"
     9  	"time"
    10  
    11  	"github.com/decred/dcrlnd/chainntnfs"
    12  	"github.com/decred/dcrlnd/kvdb"
    13  	"github.com/decred/dcrlnd/lntest/mock"
    14  	sphinx "github.com/decred/lightning-onion/v4"
    15  )
    16  
    17  const (
    18  	cltv uint32 = 100000
    19  )
    20  
    21  // tempDecayedLogPath creates a new temporary database path to back a single
    22  // decayed log instance.
    23  func tempDecayedLogPath(t *testing.T) string {
    24  	dir, err := ioutil.TempDir("", "decayedlog")
    25  	if err != nil {
    26  		t.Fatalf("unable to create temporary decayed log dir: %v", err)
    27  	}
    28  
    29  	return dir
    30  }
    31  
    32  // startup sets up the DecayedLog and possibly the garbage collector.
    33  func startup(dbPath string, notifier bool) (sphinx.ReplayLog,
    34  	*mock.ChainNotifier, *sphinx.HashPrefix, func(), error) {
    35  
    36  	cfg := &kvdb.BoltConfig{
    37  		DBTimeout: time.Second,
    38  	}
    39  	backend, err := NewBoltBackendCreator(dbPath, "sphinxreplay.db")(cfg)
    40  	if err != nil {
    41  		return nil, nil, nil, nil, fmt.Errorf("unable to create temporary "+
    42  			"decayed log db: %v", err)
    43  	}
    44  
    45  	var log sphinx.ReplayLog
    46  	var chainNotifier *mock.ChainNotifier
    47  	if notifier {
    48  
    49  		// Create the MockNotifier which triggers the garbage collector
    50  		chainNotifier = &mock.ChainNotifier{
    51  			SpendChan: make(chan *chainntnfs.SpendDetail),
    52  			EpochChan: make(chan *chainntnfs.BlockEpoch, 1),
    53  			ConfChan:  make(chan *chainntnfs.TxConfirmation),
    54  		}
    55  
    56  		// Initialize the DecayedLog object
    57  		log = NewDecayedLog(backend, chainNotifier)
    58  	} else {
    59  		// Initialize the DecayedLog object
    60  		log = NewDecayedLog(backend, nil)
    61  	}
    62  
    63  	// Open the channeldb (start the garbage collector)
    64  	err = log.Start()
    65  	if err != nil {
    66  		return nil, nil, nil, nil, err
    67  	}
    68  
    69  	// Create a HashPrefix identifier for a packet. Instead of actually
    70  	// generating an ECDH secret and hashing it, simulate with random bytes.
    71  	// This is used as a key to retrieve the cltv value.
    72  	var hashedSecret sphinx.HashPrefix
    73  	_, err = rand.Read(hashedSecret[:])
    74  	if err != nil {
    75  		return nil, nil, nil, nil, err
    76  	}
    77  
    78  	stop := func() {
    79  		_ = log.Stop()
    80  		backend.Close()
    81  	}
    82  
    83  	return log, chainNotifier, &hashedSecret, stop, nil
    84  }
    85  
    86  // shutdown deletes the temporary directory that the test database uses
    87  // and handles closing the database.
    88  func shutdown(dir string, d sphinx.ReplayLog) {
    89  	d.Stop()
    90  	os.RemoveAll(dir)
    91  }
    92  
    93  // TestDecayedLogGarbageCollector tests the ability of the garbage collector
    94  // to delete expired cltv values every time a block is received. Expired cltv
    95  // values are cltv values that are < current block height.
    96  func TestDecayedLogGarbageCollector(t *testing.T) {
    97  	t.Parallel()
    98  
    99  	dbPath := tempDecayedLogPath(t)
   100  
   101  	d, notifier, hashedSecret, _, err := startup(dbPath, true)
   102  	if err != nil {
   103  		t.Fatalf("Unable to start up DecayedLog: %v", err)
   104  	}
   105  	defer shutdown(dbPath, d)
   106  
   107  	// Store <hashedSecret, cltv> in the sharedHashBucket.
   108  	err = d.Put(hashedSecret, cltv)
   109  	if err != nil {
   110  		t.Fatalf("Unable to store in channeldb: %v", err)
   111  	}
   112  
   113  	// Wait for database write (GC is in a goroutine)
   114  	time.Sleep(500 * time.Millisecond)
   115  
   116  	// Send block notifications to garbage collector. The garbage collector
   117  	// should remove the entry by block 100001.
   118  
   119  	// Send block 100000
   120  	notifier.EpochChan <- &chainntnfs.BlockEpoch{
   121  		Height: 100000,
   122  	}
   123  
   124  	// Assert that hashedSecret is still in the sharedHashBucket
   125  	val, err := d.Get(hashedSecret)
   126  	if err != nil {
   127  		t.Fatalf("Get failed - received an error upon Get: %v", err)
   128  	}
   129  
   130  	if val != cltv {
   131  		t.Fatalf("GC incorrectly deleted CLTV")
   132  	}
   133  
   134  	// Send block 100001 (expiry block)
   135  	notifier.EpochChan <- &chainntnfs.BlockEpoch{
   136  		Height: 100001,
   137  	}
   138  
   139  	// Wait for database write (GC is in a goroutine)
   140  	time.Sleep(500 * time.Millisecond)
   141  
   142  	// Assert that hashedSecret is not in the sharedHashBucket
   143  	_, err = d.Get(hashedSecret)
   144  	if err == nil {
   145  		t.Fatalf("CLTV was not deleted")
   146  	}
   147  	if err != sphinx.ErrLogEntryNotFound {
   148  		t.Fatalf("Get failed - received unexpected error upon Get: %v", err)
   149  	}
   150  }
   151  
   152  // TestDecayedLogPersistentGarbageCollector tests the persistence property of
   153  // the garbage collector. The garbage collector will be restarted immediately and
   154  // a block that expires the stored CLTV value will be sent to the ChainNotifier.
   155  // We test that this causes the <hashedSecret, CLTV> pair to be deleted even
   156  // on GC restarts.
   157  func TestDecayedLogPersistentGarbageCollector(t *testing.T) {
   158  	t.Parallel()
   159  
   160  	dbPath := tempDecayedLogPath(t)
   161  
   162  	d, _, hashedSecret, stop, err := startup(dbPath, true)
   163  	if err != nil {
   164  		t.Fatalf("Unable to start up DecayedLog: %v", err)
   165  	}
   166  	defer shutdown(dbPath, d)
   167  
   168  	// Store <hashedSecret, cltv> in the sharedHashBucket
   169  	if err = d.Put(hashedSecret, cltv); err != nil {
   170  		t.Fatalf("Unable to store in channeldb: %v", err)
   171  	}
   172  
   173  	// The hash prefix should be retrievable from the decayed log.
   174  	_, err = d.Get(hashedSecret)
   175  	if err != nil {
   176  		t.Fatalf("Get failed - received unexpected error upon Get: %v", err)
   177  	}
   178  
   179  	// Shut down DecayedLog and the garbage collector along with it.
   180  	stop()
   181  
   182  	d2, notifier2, _, _, err := startup(dbPath, true)
   183  	if err != nil {
   184  		t.Fatalf("Unable to restart DecayedLog: %v", err)
   185  	}
   186  	defer shutdown(dbPath, d2)
   187  
   188  	// Check that the hash prefix still exists in the new db instance.
   189  	_, err = d2.Get(hashedSecret)
   190  	if err != nil {
   191  		t.Fatalf("Get failed - received unexpected error upon Get: %v", err)
   192  	}
   193  
   194  	// Send a block notification to the garbage collector that expires
   195  	// the stored CLTV.
   196  	notifier2.EpochChan <- &chainntnfs.BlockEpoch{
   197  		Height: int32(100001),
   198  	}
   199  
   200  	// Wait for database write (GC is in a goroutine)
   201  	time.Sleep(500 * time.Millisecond)
   202  
   203  	// Assert that hashedSecret is not in the sharedHashBucket
   204  	_, err = d2.Get(hashedSecret)
   205  	if err != sphinx.ErrLogEntryNotFound {
   206  		t.Fatalf("Get failed - received unexpected error upon Get: %v", err)
   207  	}
   208  }
   209  
   210  // TestDecayedLogInsertionAndRetrieval inserts a cltv value into the
   211  // sharedHashBucket and then deletes it and finally asserts that we can no
   212  // longer retrieve it.
   213  func TestDecayedLogInsertionAndDeletion(t *testing.T) {
   214  	t.Parallel()
   215  
   216  	dbPath := tempDecayedLogPath(t)
   217  
   218  	d, _, hashedSecret, _, err := startup(dbPath, false)
   219  	if err != nil {
   220  		t.Fatalf("Unable to start up DecayedLog: %v", err)
   221  	}
   222  	defer shutdown(dbPath, d)
   223  
   224  	// Store <hashedSecret, cltv> in the sharedHashBucket.
   225  	err = d.Put(hashedSecret, cltv)
   226  	if err != nil {
   227  		t.Fatalf("Unable to store in channeldb: %v", err)
   228  	}
   229  
   230  	// Delete hashedSecret from the sharedHashBucket.
   231  	err = d.Delete(hashedSecret)
   232  	if err != nil {
   233  		t.Fatalf("Unable to delete from channeldb: %v", err)
   234  	}
   235  
   236  	// Assert that hashedSecret is not in the sharedHashBucket
   237  	_, err = d.Get(hashedSecret)
   238  	if err == nil {
   239  		t.Fatalf("CLTV was not deleted")
   240  	}
   241  	if err != sphinx.ErrLogEntryNotFound {
   242  		t.Fatalf("Get failed - received unexpected error upon Get: %v", err)
   243  	}
   244  }
   245  
   246  // TestDecayedLogStartAndStop tests for persistence. The DecayedLog is started,
   247  // a cltv value is stored in the sharedHashBucket, and then it the DecayedLog
   248  // is stopped. The DecayedLog is then started up again and we test that the
   249  // cltv value is indeed still stored in the sharedHashBucket. We then delete
   250  // the cltv value and check that it persists upon startup.
   251  func TestDecayedLogStartAndStop(t *testing.T) {
   252  	t.Parallel()
   253  
   254  	dbPath := tempDecayedLogPath(t)
   255  
   256  	d, _, hashedSecret, stop, err := startup(dbPath, false)
   257  	if err != nil {
   258  		t.Fatalf("Unable to start up DecayedLog: %v", err)
   259  	}
   260  	defer shutdown(dbPath, d)
   261  
   262  	// Store <hashedSecret, cltv> in the sharedHashBucket.
   263  	err = d.Put(hashedSecret, cltv)
   264  	if err != nil {
   265  		t.Fatalf("Unable to store in channeldb: %v", err)
   266  	}
   267  
   268  	// Shutdown the DecayedLog's channeldb
   269  	stop()
   270  
   271  	d2, _, hashedSecret2, stop, err := startup(dbPath, false)
   272  	if err != nil {
   273  		t.Fatalf("Unable to restart DecayedLog: %v", err)
   274  	}
   275  	defer shutdown(dbPath, d2)
   276  
   277  	// Retrieve the stored cltv value given the hashedSecret key.
   278  	value, err := d2.Get(hashedSecret)
   279  	if err != nil {
   280  		t.Fatalf("Unable to retrieve from channeldb: %v", err)
   281  	}
   282  
   283  	// Check that the original cltv value matches the retrieved cltv
   284  	// value.
   285  	if cltv != value {
   286  		t.Fatalf("Value retrieved doesn't match value stored")
   287  	}
   288  
   289  	// Delete hashedSecret from sharedHashBucket
   290  	err = d2.Delete(hashedSecret2)
   291  	if err != nil {
   292  		t.Fatalf("Unable to delete from channeldb: %v", err)
   293  	}
   294  
   295  	// Shutdown the DecayedLog's channeldb
   296  	stop()
   297  
   298  	d3, _, hashedSecret3, _, err := startup(dbPath, false)
   299  	if err != nil {
   300  		t.Fatalf("Unable to restart DecayedLog: %v", err)
   301  	}
   302  	defer shutdown(dbPath, d3)
   303  
   304  	// Assert that hashedSecret is not in the sharedHashBucket
   305  	_, err = d3.Get(hashedSecret3)
   306  	if err == nil {
   307  		t.Fatalf("CLTV was not deleted")
   308  	}
   309  	if err != sphinx.ErrLogEntryNotFound {
   310  		t.Fatalf("Get failed - received unexpected error upon Get: %v", err)
   311  	}
   312  }
   313  
   314  // TestDecayedLogStorageAndRetrieval stores a cltv value and then retrieves it
   315  // via the nested sharedHashBucket and finally asserts that the original stored
   316  // and retrieved cltv values are equal.
   317  func TestDecayedLogStorageAndRetrieval(t *testing.T) {
   318  	t.Parallel()
   319  
   320  	dbPath := tempDecayedLogPath(t)
   321  
   322  	d, _, hashedSecret, _, err := startup(dbPath, false)
   323  	if err != nil {
   324  		t.Fatalf("Unable to start up DecayedLog: %v", err)
   325  	}
   326  	defer shutdown(dbPath, d)
   327  
   328  	// Store <hashedSecret, cltv> in the sharedHashBucket
   329  	err = d.Put(hashedSecret, cltv)
   330  	if err != nil {
   331  		t.Fatalf("Unable to store in channeldb: %v", err)
   332  	}
   333  
   334  	// Retrieve the stored cltv value given the hashedSecret key.
   335  	value, err := d.Get(hashedSecret)
   336  	if err != nil {
   337  		t.Fatalf("Unable to retrieve from channeldb: %v", err)
   338  	}
   339  
   340  	// If the original cltv value does not match the value retrieved,
   341  	// then the test failed.
   342  	if cltv != value {
   343  		t.Fatalf("Value retrieved doesn't match value stored")
   344  	}
   345  }