From 75c73498237f15d42bf09f97377a593beae9b1e5 Mon Sep 17 00:00:00 2001 From: Conner Fromknecht Date: Mon, 12 Mar 2018 13:14:50 -0700 Subject: [PATCH] htlcswitch/circuit_test: use circuit map config and test rextraction --- htlcswitch/circuit_test.go | 260 +++++++++++++++++++++++++------------ 1 file changed, 175 insertions(+), 85 deletions(-) diff --git a/htlcswitch/circuit_test.go b/htlcswitch/circuit_test.go index 2c777524..1044903a 100644 --- a/htlcswitch/circuit_test.go +++ b/htlcswitch/circuit_test.go @@ -6,9 +6,12 @@ import ( "reflect" "testing" + "github.com/lightningnetwork/lightning-onion" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/htlcswitch" "github.com/lightningnetwork/lnd/lnwire" + "github.com/roasbeef/btcd/btcec" + bitcoinCfg "github.com/roasbeef/btcd/chaincfg" "github.com/roasbeef/btcutil" ) @@ -16,19 +19,114 @@ var ( hash1 = [32]byte{0x01} hash2 = [32]byte{0x02} hash3 = [32]byte{0x03} + + // sphinxPrivKey is the private key given to freshly created sphinx + // routers. + sphinxPrivKey *btcec.PrivateKey + + // testEphemeralKey is the ephemeral key that will be extracted to + // create onion obfuscators. + testEphemeralKey *btcec.PublicKey + + // testExtracter is a precomputed extraction of testEphemeralKey, using + // the sphinxPrivKey. + testExtracter *htlcswitch.SphinxErrorEncrypter ) -func TestCircuitMapInit(t *testing.T) { - t.Parallel() +func init() { + // Generate a fresh key for our sphinx router. + var err error + sphinxPrivKey, err = btcec.NewPrivateKey(btcec.S256()) + if err != nil { + panic(err) + } - // Initialize new database for circuit map. - cdb := makeCircuitDB(t, "") - _, err := htlcswitch.NewCircuitMap(cdb) + // And another, whose public key will serve as the test ephemeral key. + testEphemeralPriv, err := btcec.NewPrivateKey(btcec.S256()) + if err != nil { + panic(err) + } + testEphemeralKey = testEphemeralPriv.PubKey() + + // Finally, properly initialize the test extracter + initTestExtracter() +} + +// initTestExtracter spins up a new onion processor specifically for the purpose +// of generating our testExtracter, which should be derived from the +// testEphemeralKey, and which randomly-generated key is used to init the sphinx +// router. +// +// NOTE: This should be called in init(), after testEphemeralKey has been +// properly initialized. +func initTestExtracter() { + onionProcessor := newOnionProcessor(nil) + defer onionProcessor.Stop() + + obfuscator, _ := onionProcessor.ExtractErrorEncrypter( + testEphemeralKey, + ) + + sphinxExtracter, ok := obfuscator.(*htlcswitch.SphinxErrorEncrypter) + if !ok { + panic("did not extract sphinx error encrypter") + } + + testExtracter = sphinxExtracter + + // We also set this error extracter on startup, otherwise it will be nil + // at compile-time. + halfCircuitTests[2].encrypter = testExtracter +} + +// newOnionProcessor creates starts a new htlcswitch.OnionProcessor using a temp +// db and no garbage collection. +func newOnionProcessor(t *testing.T) *htlcswitch.OnionProcessor { + sharedSecretFile, err := ioutil.TempFile("", "sphinxreplay.db") + if err != nil { + t.Fatalf("unable to create temp path: %v", err) + } + + sharedSecretPath := sharedSecretFile.Name() + + sphinxRouter := sphinx.NewRouter( + sharedSecretPath, sphinxPrivKey, &bitcoinCfg.SimNetParams, nil, + ) + + if err := sphinxRouter.Start(); err != nil { + t.Fatalf("unable to start sphinx router: %v", err) + } + + return htlcswitch.NewOnionProcessor(sphinxRouter) +} + +// newCircuitMap creates a new htlcswitch.CircuitMap using a temp db and a +// fresh sphinx router. +func newCircuitMap(t *testing.T) (*htlcswitch.CircuitMapConfig, + htlcswitch.CircuitMap) { + + onionProcessor := newOnionProcessor(t) + + circuitMapCfg := &htlcswitch.CircuitMapConfig{ + DB: makeCircuitDB(t, ""), + ExtractErrorEncrypter: onionProcessor.ExtractErrorEncrypter, + } + + circuitMap, err := htlcswitch.NewCircuitMap(circuitMapCfg) if err != nil { t.Fatalf("unable to create persistent circuit map: %v", err) } - restartCircuitMap(t, cdb) + return circuitMapCfg, circuitMap +} + +// TestCircuitMapInit is a quick check to ensure that we can start and restore +// the circuit map, as this will be used extensively in this suite. +func TestCircuitMapInit(t *testing.T) { + t.Parallel() + + cfg, _ := newCircuitMap(t) + restartCircuitMap(t, cfg) } var halfCircuitTests = []struct { @@ -56,12 +154,15 @@ var halfCircuitTests = []struct { encrypter: htlcswitch.NewMockObfuscator(), }, { - hash: hash3, - inValue: 10000, - outValue: 9000, - chanID: lnwire.NewShortChanIDFromInt(3), - htlcID: 3, - encrypter: htlcswitch.NewSphinxErrorEncrypter(), + hash: hash3, + inValue: 10000, + outValue: 9000, + chanID: lnwire.NewShortChanIDFromInt(3), + htlcID: 3, + // NOTE: The value of testExtracter is nil at compile-time, it + // is fully-initialized in initTestExtracter, which should + // repopulate this encrypter. + encrypter: testExtracter, }, } @@ -72,6 +173,8 @@ var halfCircuitTests = []struct { func TestHalfCircuitSerialization(t *testing.T) { t.Parallel() + onionProcessor := newOnionProcessor(t) + for i, test := range halfCircuitTests { circuit := &htlcswitch.PaymentCircuit{ PaymentHash: test.hash, @@ -97,6 +200,20 @@ func TestHalfCircuitSerialization(t *testing.T) { t.Fatalf("unable to decode half payment circuit test=%d: %v", i, err) } + // If the error encrypter is initialized, we will need to + // reextract it from it's decoded state, as this requires an + // ECDH with the onion processor's private key. For mock error + // encrypters, this will be a NOP. + if circuit2.ErrorEncrypter != nil { + err := circuit2.ErrorEncrypter.Reextract( + onionProcessor.ExtractErrorEncrypter, + ) + if err != nil { + t.Fatalf("unable to reextract sphinx error "+ + "encrypter: %v", err) + } + } + // Reconstructed half circuit should match the original. if !equalIgnoreLFD(circuit, &circuit2) { t.Fatalf("unexpected half circuit test=%d, want %v, got %v", @@ -115,11 +232,7 @@ func TestCircuitMapPersistence(t *testing.T) { err error ) - cdb := makeCircuitDB(t, "") - circuitMap, err = htlcswitch.NewCircuitMap(cdb) - if err != nil { - t.Fatalf("unable to create persistent circuit map: %v", err) - } + cfg, circuitMap := newCircuitMap(t) circuit := circuitMap.LookupCircuit(htlcswitch.CircuitKey{chan1, 0}) if circuit != nil { @@ -143,7 +256,7 @@ func TestCircuitMapPersistence(t *testing.T) { assertNumCircuitsWithHash(t, circuitMap, hash1, 0) assertHasCircuit(t, circuitMap, circuit1) - cdb, circuitMap = restartCircuitMap(t, cdb) + cfg, circuitMap = restartCircuitMap(t, cfg) assertNumCircuitsWithHash(t, circuitMap, hash1, 0) assertHasCircuit(t, circuitMap, circuit1) @@ -168,7 +281,7 @@ func TestCircuitMapPersistence(t *testing.T) { assertHasCircuit(t, circuitMap, circuit1) assertHasKeystone(t, circuitMap, keystone1.OutKey, circuit1) - cdb, circuitMap = restartCircuitMap(t, cdb) + cfg, circuitMap = restartCircuitMap(t, cfg) assertNumCircuitsWithHash(t, circuitMap, hash1, 1) assertHasCircuit(t, circuitMap, circuit1) @@ -213,7 +326,7 @@ func TestCircuitMapPersistence(t *testing.T) { assertNumCircuitsWithHash(t, circuitMap, hash3, 0) - cdb, circuitMap = restartCircuitMap(t, cdb) + cfg, circuitMap = restartCircuitMap(t, cfg) assertNumCircuitsWithHash(t, circuitMap, hash1, 1) assertHasCircuit(t, circuitMap, circuit1) @@ -238,7 +351,7 @@ func TestCircuitMapPersistence(t *testing.T) { } assertHasCircuit(t, circuitMap, circuit3) - cdb, circuitMap = restartCircuitMap(t, cdb) + cfg, circuitMap = restartCircuitMap(t, cfg) assertHasCircuit(t, circuitMap, circuit3) // Add another circuit with an already-used HTLC ID but different @@ -260,7 +373,7 @@ func TestCircuitMapPersistence(t *testing.T) { assertHasKeystone(t, circuitMap, keystone1.OutKey, circuit1) assertHasKeystone(t, circuitMap, keystone2.OutKey, circuit2) assertHasKeystone(t, circuitMap, keystone3.OutKey, circuit3) - cdb, circuitMap = restartCircuitMap(t, cdb) + cfg, circuitMap = restartCircuitMap(t, cfg) assertHasKeystone(t, circuitMap, keystone1.OutKey, circuit1) assertHasKeystone(t, circuitMap, keystone2.OutKey, circuit2) assertHasKeystone(t, circuitMap, keystone3.OutKey, circuit3) @@ -294,7 +407,7 @@ func TestCircuitMapPersistence(t *testing.T) { assertNumCircuitsWithHash(t, circuitMap, hash1, 1) assertHasCircuit(t, circuitMap, circuit4) - cdb, circuitMap = restartCircuitMap(t, cdb) + cfg, circuitMap = restartCircuitMap(t, cfg) assertNumCircuitsWithHash(t, circuitMap, hash1, 1) assertHasCircuit(t, circuitMap, circuit4) @@ -335,7 +448,7 @@ func TestCircuitMapPersistence(t *testing.T) { assertHasCircuitForHash(t, circuitMap, hash3, circuit3) // Restart, then run checks again. - cdb, circuitMap = restartCircuitMap(t, cdb) + cfg, circuitMap = restartCircuitMap(t, cfg) // Verify that all circuits have been fully added. assertHasCircuit(t, circuitMap, circuit1) @@ -368,7 +481,7 @@ func TestCircuitMapPersistence(t *testing.T) { // should be circuit4. assertNumCircuitsWithHash(t, circuitMap, hash1, 1) assertHasCircuitForHash(t, circuitMap, hash1, circuit4) - cdb, circuitMap = restartCircuitMap(t, cdb) + cfg, circuitMap = restartCircuitMap(t, cfg) assertNumCircuitsWithHash(t, circuitMap, hash1, 1) assertHasCircuitForHash(t, circuitMap, hash1, circuit4) @@ -391,7 +504,7 @@ func TestCircuitMapPersistence(t *testing.T) { assertNumCircuitsWithHash(t, circuitMap, hash1, 0) assertNumCircuitsWithHash(t, circuitMap, hash2, 1) assertNumCircuitsWithHash(t, circuitMap, hash3, 1) - cdb, circuitMap = restartCircuitMap(t, cdb) + cfg, circuitMap = restartCircuitMap(t, cfg) assertNumCircuitsWithHash(t, circuitMap, hash1, 0) assertNumCircuitsWithHash(t, circuitMap, hash2, 1) assertNumCircuitsWithHash(t, circuitMap, hash3, 1) @@ -405,7 +518,7 @@ func TestCircuitMapPersistence(t *testing.T) { // There should now only be one remaining circuit, with hash3. assertNumCircuitsWithHash(t, circuitMap, hash2, 0) assertNumCircuitsWithHash(t, circuitMap, hash3, 1) - cdb, circuitMap = restartCircuitMap(t, cdb) + cfg, circuitMap = restartCircuitMap(t, cfg) assertNumCircuitsWithHash(t, circuitMap, hash2, 0) assertNumCircuitsWithHash(t, circuitMap, hash3, 1) @@ -417,7 +530,7 @@ func TestCircuitMapPersistence(t *testing.T) { // Check that the circuit map is empty, even after restarting. assertNumCircuitsWithHash(t, circuitMap, hash3, 0) - cdb, circuitMap = restartCircuitMap(t, cdb) + cfg, circuitMap = restartCircuitMap(t, cfg) assertNumCircuitsWithHash(t, circuitMap, hash3, 0) } @@ -534,21 +647,24 @@ func makeCircuitDB(t *testing.T, path string) *channeldb.DB { // Creates a new circuit map, backed by a freshly opened channeldb. The existing // channeldb is closed in order to simulate a complete restart. -func restartCircuitMap(t *testing.T, cdb *channeldb.DB) (*channeldb.DB, - htlcswitch.CircuitMap) { +func restartCircuitMap(t *testing.T, cfg *htlcswitch.CircuitMapConfig) ( + *htlcswitch.CircuitMapConfig, htlcswitch.CircuitMap) { // Record the current temp path and close current db. - dbPath := cdb.Path() - cdb.Close() + dbPath := cfg.DB.Path() + cfg.DB.Close() // Reinitialize circuit map with same db path. - cdb2 := makeCircuitDB(t, dbPath) - cm2, err := htlcswitch.NewCircuitMap(cdb2) + cfg2 := &htlcswitch.CircuitMapConfig{ + DB: makeCircuitDB(t, dbPath), + ExtractErrorEncrypter: cfg.ExtractErrorEncrypter, + } + cm2, err := htlcswitch.NewCircuitMap(cfg2) if err != nil { t.Fatalf("unable to recreate persistent circuit map: %v", err) } - return cdb2, cm2 + return cfg2, cm2 } // TestCircuitMapCommitCircuits tests the following behavior of CommitCircuits: @@ -564,18 +680,14 @@ func TestCircuitMapCommitCircuits(t *testing.T) { err error ) - cdb := makeCircuitDB(t, "") - circuitMap, err = htlcswitch.NewCircuitMap(cdb) - if err != nil { - t.Fatalf("unable to create persistent circuit map: %v", err) - } + cfg, circuitMap := newCircuitMap(t) circuit := &htlcswitch.PaymentCircuit{ Incoming: htlcswitch.CircuitKey{ ChanID: chan1, HtlcID: 3, }, - ErrorEncrypter: htlcswitch.NewSphinxErrorEncrypter(), + ErrorEncrypter: testExtracter, } // First we will try to add an new circuit to the circuit map, this @@ -623,7 +735,7 @@ func TestCircuitMapCommitCircuits(t *testing.T) { // to be loaded from disk. Since the keystone was never set, subsequent // attempts to commit the circuit should cause the circuit map to // indicate that that the HTLC should be failed back. - cdb, circuitMap = restartCircuitMap(t, cdb) + cfg, circuitMap = restartCircuitMap(t, cfg) actions, err = circuitMap.CommitCircuits(circuit) if err != nil { @@ -664,18 +776,14 @@ func TestCircuitMapOpenCircuits(t *testing.T) { err error ) - cdb := makeCircuitDB(t, "") - circuitMap, err = htlcswitch.NewCircuitMap(cdb) - if err != nil { - t.Fatalf("unable to create persistent circuit map: %v", err) - } + cfg, circuitMap := newCircuitMap(t) circuit := &htlcswitch.PaymentCircuit{ Incoming: htlcswitch.CircuitKey{ ChanID: chan1, HtlcID: 3, }, - ErrorEncrypter: htlcswitch.NewSphinxErrorEncrypter(), + ErrorEncrypter: testExtracter, } // First we will try to add an new circuit to the circuit map, this @@ -747,7 +855,7 @@ func TestCircuitMapOpenCircuits(t *testing.T) { // // NOTE: The channel db doesn't have any channel data, so no keystones // will be trimmed. - cdb, circuitMap = restartCircuitMap(t, cdb) + cfg, circuitMap = restartCircuitMap(t, cfg) // Check that we can still query for the open circuit. circuit2 = circuitMap.LookupOpenCircuit(keystone.OutKey) @@ -874,11 +982,7 @@ func TestCircuitMapTrimOpenCircuits(t *testing.T) { err error ) - cdb := makeCircuitDB(t, "") - circuitMap, err = htlcswitch.NewCircuitMap(cdb) - if err != nil { - t.Fatalf("unable to create persistent circuit map: %v", err) - } + cfg, circuitMap := newCircuitMap(t) const nCircuits = 10 const firstTrimIndex = 7 @@ -895,7 +999,7 @@ func TestCircuitMapTrimOpenCircuits(t *testing.T) { ChanID: chan1, HtlcID: uint64(i + 3), }, - ErrorEncrypter: htlcswitch.NewSphinxErrorEncrypter(), + ErrorEncrypter: htlcswitch.NewMockObfuscator(), } } @@ -953,7 +1057,7 @@ func TestCircuitMapTrimOpenCircuits(t *testing.T) { // Restart the circuit map, verify that that the trim is reflected on // startup. - cdb, circuitMap = restartCircuitMap(t, cdb) + cfg, circuitMap = restartCircuitMap(t, cfg) assertCircuitsOpenedPostRestart( t, @@ -995,7 +1099,7 @@ func TestCircuitMapTrimOpenCircuits(t *testing.T) { // Restart the circuit map one last time to make sure the changes are // persisted. - cdb, circuitMap = restartCircuitMap(t, cdb) + cfg, circuitMap = restartCircuitMap(t, cfg) assertCircuitsOpenedPostRestart( t, @@ -1027,18 +1131,16 @@ func TestCircuitMapCloseOpenCircuits(t *testing.T) { err error ) - cdb := makeCircuitDB(t, "") - circuitMap, err = htlcswitch.NewCircuitMap(cdb) - if err != nil { - t.Fatalf("unable to create persistent circuit map: %v", err) - } + cfg, circuitMap := newCircuitMap(t) circuit := &htlcswitch.PaymentCircuit{ Incoming: htlcswitch.CircuitKey{ ChanID: chan1, HtlcID: 3, }, - ErrorEncrypter: htlcswitch.NewSphinxErrorEncrypter(), + ErrorEncrypter: &htlcswitch.SphinxErrorEncrypter{ + EphemeralKey: testEphemeralKey, + }, } // First we will try to add an new circuit to the circuit map, this @@ -1095,7 +1197,7 @@ func TestCircuitMapCloseOpenCircuits(t *testing.T) { // // NOTE: The channel db doesn't have any channel data, so no keystones // will be trimmed. - cdb, circuitMap = restartCircuitMap(t, cdb) + cfg, circuitMap = restartCircuitMap(t, cfg) // Close the open circuit for the first time, which should succeed. _, err = circuitMap.FailCircuit(circuit.Incoming) @@ -1122,18 +1224,14 @@ func TestCircuitMapCloseUnopenedCircuit(t *testing.T) { err error ) - cdb := makeCircuitDB(t, "") - circuitMap, err = htlcswitch.NewCircuitMap(cdb) - if err != nil { - t.Fatalf("unable to create persistent circuit map: %v", err) - } + cfg, circuitMap := newCircuitMap(t) circuit := &htlcswitch.PaymentCircuit{ Incoming: htlcswitch.CircuitKey{ ChanID: chan1, HtlcID: 3, }, - ErrorEncrypter: htlcswitch.NewSphinxErrorEncrypter(), + ErrorEncrypter: testExtracter, } // First we will try to add an new circuit to the circuit map, this @@ -1157,7 +1255,7 @@ func TestCircuitMapCloseUnopenedCircuit(t *testing.T) { // Now, restart the circuit map, which will result in the circuit being // reopened, since no attempt to delete the circuit was made. - cdb, circuitMap = restartCircuitMap(t, cdb) + cfg, circuitMap = restartCircuitMap(t, cfg) // Close the open circuit for the first time, which should succeed. _, err = circuitMap.FailCircuit(circuit.Incoming) @@ -1183,18 +1281,14 @@ func TestCircuitMapDeleteUnopenedCircuit(t *testing.T) { err error ) - cdb := makeCircuitDB(t, "") - circuitMap, err = htlcswitch.NewCircuitMap(cdb) - if err != nil { - t.Fatalf("unable to create persistent circuit map: %v", err) - } + cfg, circuitMap := newCircuitMap(t) circuit := &htlcswitch.PaymentCircuit{ Incoming: htlcswitch.CircuitKey{ ChanID: chan1, HtlcID: 3, }, - ErrorEncrypter: htlcswitch.NewSphinxErrorEncrypter(), + ErrorEncrypter: testExtracter, } // First we will try to add an new circuit to the circuit map, this @@ -1225,7 +1319,7 @@ func TestCircuitMapDeleteUnopenedCircuit(t *testing.T) { // Now, restart the circuit map, and check that the deletion survived // the restart. - cdb, circuitMap = restartCircuitMap(t, cdb) + cfg, circuitMap = restartCircuitMap(t, cfg) circuit2 = circuitMap.LookupCircuit(circuit.Incoming) if circuit2 != nil { @@ -1246,18 +1340,14 @@ func TestCircuitMapDeleteOpenCircuit(t *testing.T) { err error ) - cdb := makeCircuitDB(t, "") - circuitMap, err = htlcswitch.NewCircuitMap(cdb) - if err != nil { - t.Fatalf("unable to create persistent circuit map: %v", err) - } + cfg, circuitMap := newCircuitMap(t) circuit := &htlcswitch.PaymentCircuit{ Incoming: htlcswitch.CircuitKey{ ChanID: chan1, HtlcID: 3, }, - ErrorEncrypter: htlcswitch.NewSphinxErrorEncrypter(), + ErrorEncrypter: testExtracter, } // First we will try to add an new circuit to the circuit map, this @@ -1302,7 +1392,7 @@ func TestCircuitMapDeleteOpenCircuit(t *testing.T) { // Now, restart the circuit map, and check that the deletion survived // the restart. - cdb, circuitMap = restartCircuitMap(t, cdb) + cfg, circuitMap = restartCircuitMap(t, cfg) circuit2 = circuitMap.LookupOpenCircuit(keystone.OutKey) if circuit2 != nil {