From 5817543ad90610ba7e200abd682c6d8b269c338f Mon Sep 17 00:00:00 2001 From: Elle Mouton Date: Mon, 4 Sep 2023 17:33:13 +0200 Subject: [PATCH 1/5] itest: call the rate limit and priv map itest --- autopilotserver/mock/server.go | 5 ++ itest/litd_firewall_test.go | 102 +++++++++++++++------------------ 2 files changed, 51 insertions(+), 56 deletions(-) diff --git a/autopilotserver/mock/server.go b/autopilotserver/mock/server.go index fc81fe8ad..5940bdf5e 100644 --- a/autopilotserver/mock/server.go +++ b/autopilotserver/mock/server.go @@ -103,6 +103,11 @@ func (m *Server) SetFeatures(f map[string]*Feature) { m.featureSet = f } +// ResetDefaultFeatures resets the servers features set to the default set. +func (m *Server) ResetDefaultFeatures() { + m.featureSet = defaultFeatures +} + // Terms returns any meta data from the autopilot server. // // Note: this is part of the autopilotrpc.AutopilotServer interface. diff --git a/itest/litd_firewall_test.go b/itest/litd_firewall_test.go index 76bba0621..da2c8c633 100644 --- a/itest/litd_firewall_test.go +++ b/itest/litd_firewall_test.go @@ -6,6 +6,7 @@ import ( "encoding/hex" "fmt" "io/ioutil" + "os" "strconv" "strings" "testing" @@ -13,7 +14,6 @@ import ( "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcutil" - "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/lightninglabs/lightning-node-connect/mailbox" "github.com/lightninglabs/lightning-terminal/autopilotserver/mock" "github.com/lightninglabs/lightning-terminal/firewall" @@ -133,11 +133,16 @@ var ( } ) -// testFWRateLimitAndPrivacyMapper tests that an Autopilot session is forced to -// adhere to the rate limits applied to the features of a session. Along the -// way, the privacy mapper is also tested. -func testFWRateLimitAndPrivacyMapper(net *NetworkHarness, t *harnessTest) { - ctx := context.Background() +// assertStatusErr asserts that the given error contains the given status code. +func assertStatusErr(t *testing.T, err error, code codes.Code) { + require.Error(t, err) + require.Contains(t, err.Error(), code.String()) +} + +// testFirewallRules tests that the various firewall rules are enforced +// correctly. +func testFirewallRules(ctx context.Context, net *NetworkHarness, + t *harnessTest) { // Some very basic functionality tests to make sure lnd is working fine // in integrated mode. @@ -158,14 +163,40 @@ func testFWRateLimitAndPrivacyMapper(net *NetworkHarness, t *harnessTest) { ) defer closeChannelAndAssert(t, net, net.Alice, channelOp, true) + t.t.Run("history limit rule", func(_ *testing.T) { + testHistoryLimitRule(net, t) + }) + + t.t.Run("channel policy bounds rule", func(_ *testing.T) { + testChanPolicyBoundsRule(net, t) + }) + + t.t.Run("peer and channel restrict rules", func(_ *testing.T) { + testPeerAndChannelRestrictRules(net, t) + }) + + t.t.Run("rate limit and privacy mapper", func(_ *testing.T) { + testRateLimitAndPrivacyMapper(net, t) + }) +} + +// testRateLimitAndPrivacyMapper tests that an Autopilot session is forced to +// adhere to the rate limits applied to the features of a session. Along the +// way, the privacy mapper is also tested. +func testRateLimitAndPrivacyMapper(net *NetworkHarness, t *harnessTest) { + ctx := context.Background() + + // Fetch the channel that Alice has so that we can get the channel + // point. + resp, err := net.Alice.ListChannels(ctx, &lnrpc.ListChannelsRequest{}) + require.NoError(t.t, err) + require.Len(t.t, resp.Channels, 1) + // We extract the txid of the channel so that we can use it later to // check that the autopilot's actions successfully completed and to // check that the txid that the autopilot server sees is not the same // as this one. - realTxidBytes, err := getChanPointFundingTxid(channelOp) - require.NoError(t.t, err) - realTxid, err := chainhash.NewHash(realTxidBytes) - require.NoError(t.t, err) + realTxid := strings.Split(resp.Channels[0].ChannelPoint, ":")[0] // We create a connection to the Alice node's RPC server. cfg := net.Alice.Cfg @@ -173,7 +204,7 @@ func testFWRateLimitAndPrivacyMapper(net *NetworkHarness, t *harnessTest) { require.NoError(t.t, err) defer rawConn.Close() - macBytes, err := ioutil.ReadFile(cfg.LitMacPath) + macBytes, err := os.ReadFile(cfg.LitMacPath) require.NoError(t.t, err) ctxm := macaroonContext(ctx, macBytes) @@ -186,6 +217,8 @@ func testFWRateLimitAndPrivacyMapper(net *NetworkHarness, t *harnessTest) { require.NoError(t.t, err) require.NotEmpty(t.t, featResp) + net.autopilotServer.ResetDefaultFeatures() + // Add a new Autopilot session that subscribes to both a "HealthCheck", // and an "AutoFees" feature. Apply rate limits to the two features. // This call is expected to also result in Litd registering this session @@ -305,7 +338,7 @@ func testFWRateLimitAndPrivacyMapper(net *NetworkHarness, t *harnessTest) { // Make sure that the txid returned by the call for Alice's channel is // not the same as the real txid of the channel. - require.NotEqual(t.t, txid, realTxid.String()) + require.NotEqual(t.t, txid, realTxid) chanPoint := &lnrpc.ChannelPoint{ FundingTxid: &lnrpc.ChannelPoint_FundingTxidStr{ @@ -336,7 +369,7 @@ func testFWRateLimitAndPrivacyMapper(net *NetworkHarness, t *harnessTest) { txid2, _, err := decodeChannelPoint(feeResp.ChannelFees[0].ChannelPoint) require.NoError(t.t, err) - require.Equal(t.t, realTxid.String(), txid2) + require.Equal(t.t, realTxid, txid2) require.Equal(t.t, float64(8), feeResp.ChannelFees[0].FeeRate) // Now we will check the same thing but from the PoV of the autopilot @@ -371,49 +404,6 @@ func testFWRateLimitAndPrivacyMapper(net *NetworkHarness, t *harnessTest) { assertStatusErr(t.t, err, codes.ResourceExhausted) } -// assertStatusErr asserts that the given error contains the given status code. -func assertStatusErr(t *testing.T, err error, code codes.Code) { - require.Error(t, err) - require.True(t, strings.Contains(err.Error(), code.String())) -} - -// testFirewallRules tests that the various firewall rules are enforced -// correctly. -func testFirewallRules(ctx context.Context, net *NetworkHarness, - t *harnessTest) { - - // Some very basic functionality tests to make sure lnd is working fine - // in integrated mode. - net.SendCoins(t.t, btcutil.SatoshiPerBitcoin, net.Alice) - - // We expect a non-empty alias (truncated node ID) to be returned. - resp, err := net.Alice.GetInfo(ctx, &lnrpc.GetInfoRequest{}) - require.NoError(t.t, err) - require.NotEmpty(t.t, resp.Alias) - require.Contains(t.t, resp.Alias, "0") - - // Open a channel between Alice and Bob so that we have something to - // query later. - channelOp := openChannelAndAssert( - t, net, net.Alice, net.Bob, lntest.OpenChannelParams{ - Amt: 100000, - }, - ) - defer closeChannelAndAssert(t, net, net.Alice, channelOp, true) - - t.t.Run("history limit rule", func(_ *testing.T) { - testHistoryLimitRule(net, t) - }) - - t.t.Run("channel policy bounds rule", func(_ *testing.T) { - testChanPolicyBoundsRule(net, t) - }) - - t.t.Run("peer and channel restrict rules", func(_ *testing.T) { - testPeerAndChannelRestrictRules(net, t) - }) -} - // testHistoryLimitRule tests that the autopilot server is forced to adhere to // the history-limit rule. func testHistoryLimitRule(net *NetworkHarness, t *harnessTest) { From d0db03a07c3cc2369138d24d236367304a3afc87 Mon Sep 17 00:00:00 2001 From: Elle Mouton Date: Thu, 31 Aug 2023 14:25:34 +0200 Subject: [PATCH 2/5] autopilotserver/mock: upgrade mock for session linking --- autopilotserver/mock/server.go | 36 +++++++++++++++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/autopilotserver/mock/server.go b/autopilotserver/mock/server.go index 5940bdf5e..b062cdb23 100644 --- a/autopilotserver/mock/server.go +++ b/autopilotserver/mock/server.go @@ -8,6 +8,8 @@ import ( "sync" "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcec/v2/ecdsa" + "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/lightninglabs/lightning-terminal/autopilotserverrpc" "github.com/lightninglabs/lightning-terminal/rules" "github.com/lightningnetwork/lnd/lntest/node" @@ -44,6 +46,7 @@ type ClientState uint8 const ( ClientStateActive = iota ClientStateInactive + ClientStateRevoked ) type clientSession struct { @@ -172,6 +175,32 @@ func (m *Server) RegisterSession(_ context.Context, return nil, err } + // If linked session, check that signature is valid. + if len(req.GroupResponderKey) != 0 { + // Check that the group key is a known key. + _, ok := m.sessions[hex.EncodeToString(req.GroupResponderKey)] + if !ok { + return nil, fmt.Errorf("unknown group key") + } + + // Check that the signature provided is valid. + sig, err := ecdsa.ParseDERSignature(req.GroupResponderSig) + if err != nil { + return nil, err + } + + msg := chainhash.HashB(req.ResponderPubKey) + + groupKey, err := btcec.ParsePubKey(req.GroupResponderKey) + if err != nil { + return nil, err + } + + if !sig.Verify(msg, groupKey) { + return nil, fmt.Errorf("invalid signature") + } + } + m.sessions[hex.EncodeToString(req.ResponderPubKey)] = &clientSession{ key: priv, state: ClientStateActive, @@ -209,7 +238,12 @@ func (m *Server) RevokeSession(_ context.Context, m.sessMu.Lock() defer m.sessMu.Unlock() - delete(m.sessions, hex.EncodeToString(req.ResponderPubKey)) + sess, ok := m.sessions[hex.EncodeToString(req.ResponderPubKey)] + if !ok { + return nil, nil + } + + sess.state = ClientStateRevoked return &autopilotserverrpc.RevokeSessionResponse{}, nil } From c56b98156929355a9dfe691a7458b54516c83cc6 Mon Sep 17 00:00:00 2001 From: Elle Mouton Date: Mon, 4 Sep 2023 17:03:30 +0200 Subject: [PATCH 3/5] autopilotserver/mock: export RateLimitRule --- autopilotserver/mock/server.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/autopilotserver/mock/server.go b/autopilotserver/mock/server.go index b062cdb23..9d18f74d1 100644 --- a/autopilotserver/mock/server.go +++ b/autopilotserver/mock/server.go @@ -310,7 +310,7 @@ var defaultFeatures = map[string]*Feature{ "HealthCheck": { Description: "check that your node is up", Rules: map[string]*RuleRanges{ - rules.RateLimitName: rateLimitRule, + rules.RateLimitName: RateLimitRule, }, Permissions: map[string][]bakery.Op{ "/lnrpc.Lightning/GetInfo": {{ @@ -322,7 +322,7 @@ var defaultFeatures = map[string]*Feature{ "AutoFees": { Description: "manages your channel fees", Rules: map[string]*RuleRanges{ - rules.RateLimitName: rateLimitRule, + rules.RateLimitName: RateLimitRule, }, Permissions: map[string][]bakery.Op{ "/lnrpc.Lightning/ListChannels": {{ @@ -341,7 +341,7 @@ var defaultFeatures = map[string]*Feature{ }, } -var rateLimitRule = &RuleRanges{ +var RateLimitRule = &RuleRanges{ Default: &rules.RateLimit{ WriteLimit: &rules.Rate{ Iterations: 1, From dd0a7f55c211f5ab2a00130e9a20e0144f224170 Mon Sep 17 00:00:00 2001 From: Elle Mouton Date: Mon, 4 Sep 2023 17:35:07 +0200 Subject: [PATCH 4/5] itest: add session linking test --- itest/litd_firewall_test.go | 470 ++++++++++++++++++++++++++++++++++++ 1 file changed, 470 insertions(+) diff --git a/itest/litd_firewall_test.go b/itest/litd_firewall_test.go index da2c8c633..989490ad1 100644 --- a/itest/litd_firewall_test.go +++ b/itest/litd_firewall_test.go @@ -4,6 +4,7 @@ import ( "context" "crypto/tls" "encoding/hex" + "encoding/json" "fmt" "io/ioutil" "os" @@ -17,6 +18,7 @@ import ( "github.com/lightninglabs/lightning-node-connect/mailbox" "github.com/lightninglabs/lightning-terminal/autopilotserver/mock" "github.com/lightninglabs/lightning-terminal/firewall" + "github.com/lightninglabs/lightning-terminal/firewalldb" "github.com/lightninglabs/lightning-terminal/litrpc" "github.com/lightninglabs/lightning-terminal/rules" "github.com/lightninglabs/lightning-terminal/session" @@ -178,6 +180,474 @@ func testFirewallRules(ctx context.Context, net *NetworkHarness, t.t.Run("rate limit and privacy mapper", func(_ *testing.T) { testRateLimitAndPrivacyMapper(net, t) }) + + t.t.Run("session linking", func(_ *testing.T) { + testSessionLinking(net, t) + }) +} + +// testSessionLinking will test the expected behaviour across linked sessions. +func testSessionLinking(net *NetworkHarness, t *harnessTest) { + ctx := context.Background() + + /* + Open up a few more channels for Alice so that we can make use + of the channel restriction rule in the sessions that we will + create in this test. One channel already exists, so let's open + two more. + */ + channelAB2Op := openChannelAndAssert( + t, net, net.Alice, net.Bob, lntest.OpenChannelParams{ + Amt: 100000, + }, + ) + defer closeChannelAndAssert(t, net, net.Alice, channelAB2Op, true) + + channelAB3Op := openChannelAndAssert( + t, net, net.Alice, net.Bob, lntest.OpenChannelParams{ + Amt: 100000, + }, + ) + defer closeChannelAndAssert(t, net, net.Alice, channelAB3Op, true) + + // List Alice's channels so that we can extract the channel points for + // the three channels. + chans, err := net.Alice.ListChannels(ctx, &lnrpc.ListChannelsRequest{}) + require.NoError(t.t, err) + require.Len(t.t, chans.Channels, 3) + + // Create a lookup map with Alice's channels. Also let the first 2 + // channels be in the "restricted" list. + var ( + aliceChans = make( + map[string]bool, len(chans.Channels), + ) + chansToRestrict = make([]uint64, 2) + unrestrictedChanID uint64 + unrestrictedChanPoint string + ) + for i, c := range chans.Channels { + aliceChans[c.ChannelPoint] = true + + if i >= 2 { + unrestrictedChanID = c.ChanId + unrestrictedChanPoint = c.ChannelPoint + + continue + } + + chansToRestrict[i] = c.ChanId + } + + channelRestrict := &litrpc.RuleValue_ChannelRestrict{ + ChannelRestrict: &litrpc.ChannelRestrict{ + ChannelIds: chansToRestrict, + }, + } + + // Construct a new feature definition that allows for both channel + // restriction and rate limit rules. + autofeesFeature := &mock.Feature{ + Description: "manages your channel fees", + Rules: map[string]*mock.RuleRanges{ + rules.ChannelRestrictName: { + Default: &rules.ChannelRestrict{}, + MinVal: &rules.ChannelRestrict{}, + MaxVal: &rules.ChannelRestrict{}, + }, + rules.RateLimitName: mock.RateLimitRule, + }, + Permissions: map[string][]bakery.Op{ + "/lnrpc.Lightning/UpdateChannelPolicy": {{ + Entity: "offchain", + Action: "write", + }}, + "/lnrpc.Lightning/FeeReport": {{ + Entity: "offchain", + Action: "read", + }}, + }, + } + + // Override the autopilots feature set with two identical looking + // features with different names. The reason we do this is so that + // we can test that obfuscated values are shared amongst features in + // the same session. + net.autopilotServer.SetFeatures(map[string]*mock.Feature{ + "AutoFees": autofeesFeature, + "AutoFees2": autofeesFeature, + }) + + // Set up a connection to Alice's RPC server. + cfg := net.Alice.Cfg + rawConn, err := connectRPC(ctx, cfg.LitAddr(), cfg.LitTLSCertPath) + require.NoError(t.t, err) + defer rawConn.Close() + + macBytes, err := os.ReadFile(cfg.LitMacPath) + require.NoError(t.t, err) + ctxm := macaroonContext(ctx, macBytes) + + // Test that the connection to Alice's rpc server is working and that + // the autopilot server is returning a non-empty feature list. + litFWClient := litrpc.NewFirewallClient(rawConn) + litAutopilotClient := litrpc.NewAutopilotClient(rawConn) + featResp, err := litAutopilotClient.ListAutopilotFeatures( + ctxm, &litrpc.ListAutopilotFeaturesRequest{}, + ) + require.NoError(t.t, err) + require.NotEmpty(t.t, featResp) + + // Create a feature configuration map. + config := struct { + PubKeys []string `json:"pubkeys"` + ChanPoints []string `json:"chanpoints"` + }{ + PubKeys: []string{ + hex.EncodeToString(net.Bob.PubKey[:]), + "0e092708c9e737115ff14a85b65466561280d" + + "77c1b8cd666bc655536ad81ccca85", + }, + ChanPoints: []string{ + unrestrictedChanPoint, + "0e092708c9e737115ff14a85b65466561280d" + + "77c1b8cd666bc655536ad81ccca3:1", + }, + } + + configBytes, err := json.Marshal(config) + require.NoError(t.t, err) + + // Now we set up an initial autopilot session. The session will register + // to both features, and it will place the channel restriction on both + // features. It will also apply a low rate limit on both features, and + // it will have some feature configs. + sessFeatures := map[string]*litrpc.FeatureConfig{ + "AutoFees": { + Rules: &litrpc.RulesMap{ + Rules: map[string]*litrpc.RuleValue{ + rules.ChannelRestrictName: { + Value: channelRestrict, + }, + rules.RateLimitName: { + Value: rateLimit, + }, + }, + }, + Config: configBytes, + }, + "AutoFees2": { + Rules: &litrpc.RulesMap{ + Rules: map[string]*litrpc.RuleValue{ + rules.ChannelRestrictName: { + Value: channelRestrict, + }, + rules.RateLimitName: { + Value: rateLimit, + }, + }, + }, + Config: configBytes, + }, + } + + sessResp, err := litAutopilotClient.AddAutopilotSession( + ctxm, &litrpc.AddAutopilotSessionRequest{ + Label: "integration-test", + ExpiryTimestampSeconds: uint64( + time.Now().Add(5 * time.Minute).Unix(), + ), + MailboxServerAddr: mailboxServerAddr, + Features: sessFeatures, + }, + ) + require.NoError(t.t, err) + + // getPseudo is a helper that can be used to query Alice's privacy map + // DB to get the pseudo value for a given real value. + getPseudo := func(groupID []byte, input any, expError string) string { + var in string + + switch inp := input.(type) { + case string: + in = inp + case uint64: + in = firewalldb.Uint64ToStr(inp) + default: + t.Fatalf("unhandled input type: %T", input) + } + + privMapResp, err := litFWClient.PrivacyMapConversion( + ctxm, &litrpc.PrivacyMapConversionRequest{ + GroupId: groupID, + RealToPseudo: true, + Input: in, + }, + ) + if expError != "" { + require.ErrorContains(t.t, err, expError) + + return "" + } + require.NoError(t.t, err) + + return privMapResp.Output + } + + // At this point, we already expect there to be entries in the privacy + // map DB for the restricted channel IDs. Collect them now. + obfuscatedChansToRestrict := make(map[uint64]bool) + for _, c := range chansToRestrict { + oc := getPseudo(sessResp.Session.GroupId, c, "") + ocInt, err := firewalldb.StrToUint64(oc) + require.NoError(t.t, err) + + obfuscatedChansToRestrict[ocInt] = true + } + + // Also try to query the channel that does not have a restriction. At + // this point we do not expect an entry in the privacy mapper. + getPseudo( + sessResp.Session.GroupId, unrestrictedChanID, + "no such key found", + ) + + // We do expect an entry in the privacy mapper for all items in the + // config map though. So we make sure that the channel point of the + // unrestricted channel is in the db. + obfUnrestrictedChanPoint := getPseudo( + sessResp.Session.GroupId, unrestrictedChanPoint, "", + ) + + // Now, let's connect to the LiT node from the point of view of the + // autopilot server. + + // From the session creation response, we can extract Lit's local public + // key. + litdPub, err := btcec.ParsePubKey(sessResp.Session.LocalPublicKey) + require.NoError(t.t, err) + + // We then query the autopilot server to extract the private key that + // it will be using for this session. + pilotPriv, err := net.autopilotServer.GetPrivKey(litdPub) + require.NoError(t.t, err) + + // Now we can connect to the mailbox from the PoV of the autopilot + // server. + pilotConn, metaDataInjector, err := connectMailboxWithRemoteKey( + ctx, pilotPriv, litdPub, + ) + require.NoError(t.t, err) + defer pilotConn.Close() + + lndConn := lnrpc.NewLightningClient(pilotConn) + + // The autopilot server is expected to add a MetaInfo caveat to any + // request that it makes. So we add that now and specify that it is + // initially making requests on behalf of the AutoFees feature. + metaInfo1 := &firewall.InterceptMetaInfo{ + ActorName: "Autopilot Server", + Feature: "AutoFees", + } + caveat1, err := metaInfo1.ToCaveat() + require.NoError(t.t, err) + caveatCreds1 := metaDataInjector.addCaveat(caveat1) + + // From the PoV of the Autopilot server, we do a quick FeeReport call. + // This will force the Privacy Mapper on Alice's node to create the + // real-pseudo pairs for all her channel id's and points. Note that + // this should only create one new ChanID entry since two were already + // created previously. + feeReport, err := lndConn.FeeReport( + ctx, &lnrpc.FeeReportRequest{}, caveatCreds1, + ) + require.NoError(t.t, err) + require.Len(t.t, feeReport.ChannelFees, 3) + + // For completeness, we do the same call from the autopilot server but + // from the "AutoFees2" feature. This should return the same result. + metaInfo2 := &firewall.InterceptMetaInfo{ + ActorName: "Autopilot Server", + Feature: "AutoFees2", + } + caveat2, err := metaInfo2.ToCaveat() + require.NoError(t.t, err) + caveatCreds2 := metaDataInjector.addCaveat(caveat2) + feeReport2, err := lndConn.FeeReport( + ctx, &lnrpc.FeeReportRequest{}, caveatCreds2, + ) + require.NoError(t.t, err) + require.ElementsMatch( + t.t, feeReport.ChannelFees, feeReport2.ChannelFees, + ) + + // Once again query Alice's privacy mapper to ensure that the obfuscated + // channel IDs of the restricted channels have not changed. + aliceObfchans := make(map[uint64]bool) + for _, c := range chansToRestrict { + oc := getPseudo(sessResp.Session.GroupId, c, "") + ocInt, err := firewalldb.StrToUint64(oc) + require.NoError(t.t, err) + require.True(t.t, obfuscatedChansToRestrict[ocInt]) + + aliceObfchans[ocInt] = true + } + + // This time, there should also be an entry for the unrestricted + // channel. + obfuscatedUnrestrictedChan := getPseudo( + sessResp.Session.GroupId, unrestrictedChanID, "", + ) + obfUnrestrictedChanID, err := firewalldb.StrToUint64( + obfuscatedUnrestrictedChan, + ) + require.NoError(t.t, err) + + aliceObfchans[obfUnrestrictedChanID] = true + + // Iterate over the channels list sent to the autopilot server in the + // fee report and ensure that they match the set of obfuscated channel + // IDs we have for Alice. + for _, channel := range feeReport.ChannelFees { + require.True(t.t, aliceObfchans[channel.ChanId]) + + // Check that the obfuscated channel point for the unrestricted + // channel's ID is equal to the one created before due to the + // contents of the feature config. + if channel.ChanId == obfUnrestrictedChanID { + require.Equal(t.t, channel.ChannelPoint, + obfUnrestrictedChanPoint) + } + } + + // Now we will perform a single write call from the autopilot server on + // the unrestricted channel. + pseudoTxid, pseudoIndex, err := decodeChannelPoint( + obfUnrestrictedChanPoint, + ) + require.NoError(t.t, err) + + chanPoint := &lnrpc.ChannelPoint{ + FundingTxid: &lnrpc.ChannelPoint_FundingTxidStr{ + FundingTxidStr: pseudoTxid, + }, + OutputIndex: pseudoIndex, + } + _, err = lndConn.UpdateChannelPolicy( + ctx, &lnrpc.PolicyUpdateRequest{ + BaseFeeMsat: 9, + FeeRate: 8, + Scope: &lnrpc.PolicyUpdateRequest_ChanPoint{ + ChanPoint: chanPoint, + }, + TimeLockDelta: 20, + MaxHtlcMsat: 100000, + }, caveatCreds1, + ) + require.NoError(t.t, err) + + // Try to create a new session linked to the previous one. This should + // fail due to the previous one still being active. + _, err = litAutopilotClient.AddAutopilotSession( + ctxm, &litrpc.AddAutopilotSessionRequest{ + Label: "integration-test", + ExpiryTimestampSeconds: uint64( + time.Now().Add(5 * time.Minute).Unix(), + ), + MailboxServerAddr: mailboxServerAddr, + Features: sessFeatures, + LinkedGroupId: sessResp.Session.GroupId, + }, + ) + require.ErrorContains(t.t, err, "is still active") + + // Revoke the previous one and repeat. + _, err = litAutopilotClient.RevokeAutopilotSession( + ctxm, &litrpc.RevokeAutopilotSessionRequest{ + LocalPublicKey: sessResp.Session.LocalPublicKey, + }, + ) + require.NoError(t.t, err) + + // Also close the autopilot connection to the first session so that it + // doesn't continue to try and connect. + require.NoError(t.t, pilotConn.Close()) + pilotConn = nil + + sessResp2, err := litAutopilotClient.AddAutopilotSession( + ctxm, &litrpc.AddAutopilotSessionRequest{ + Label: "integration-test", + ExpiryTimestampSeconds: uint64( + time.Now().Add(5 * time.Minute).Unix(), + ), + MailboxServerAddr: mailboxServerAddr, + Features: sessFeatures, + LinkedGroupId: sessResp.Session.GroupId, + }, + ) + require.NoError(t.t, err) + + // Since the new session is linked to the first one, we expect the + // channel IDs in the restricted list to have the same privacy mapping + // as before. + for _, c := range chansToRestrict { + oc := getPseudo(sessResp2.Session.GroupId, c, "") + ocInt, err := firewalldb.StrToUint64(oc) + require.NoError(t.t, err) + + require.True(t.t, obfuscatedChansToRestrict[ocInt]) + } + + // Now we connect to LiT from the PoV of the autopilot server but this + // time using the new session. + litdPub, err = btcec.ParsePubKey(sessResp2.Session.LocalPublicKey) + require.NoError(t.t, err) + + pilotPriv, err = net.autopilotServer.GetPrivKey(litdPub) + require.NoError(t.t, err) + + pilotConn, metaDataInjector, err = connectMailboxWithRemoteKey( + ctx, pilotPriv, litdPub, + ) + require.NoError(t.t, err) + defer pilotConn.Close() + + lndConn = lnrpc.NewLightningClient(pilotConn) + caveatCreds1 = metaDataInjector.addCaveat(caveat1) + + // List the channels and ensure that the same mapping was used as for + // the previous session. + feeReport3, err := lndConn.FeeReport( + ctx, &lnrpc.FeeReportRequest{}, caveatCreds1, + ) + require.NoError(t.t, err) + + for _, channel := range feeReport3.ChannelFees { + require.True(t.t, aliceObfchans[channel.ChanId]) + } + + // Now we will test that the rule enforcer's DB is shared across the two + // linked sessions. In the first session, we made one read call and one + // write call with the "AutoFees" feature. Since this new session is + // linked to that one, we should have one read call remaining and no + // write calls remaining. We used the second read call above for the + // call to FeeReport, and so we now expect a second call to FeeReport + // to fail too. + _, err = lndConn.FeeReport(ctx, &lnrpc.FeeReportRequest{}, caveatCreds1) + assertStatusErr(t.t, err, codes.ResourceExhausted) + + _, err = lndConn.UpdateChannelPolicy( + ctx, &lnrpc.PolicyUpdateRequest{ + BaseFeeMsat: 9, + FeeRate: 8, + Scope: &lnrpc.PolicyUpdateRequest_ChanPoint{ + ChanPoint: chanPoint, + }, + TimeLockDelta: 20, + MaxHtlcMsat: 100000, + }, caveatCreds1, + ) + assertStatusErr(t.t, err, codes.ResourceExhausted) } // testRateLimitAndPrivacyMapper tests that an Autopilot session is forced to From bab43e37ef10b036853595fff5f9950769b5330e Mon Sep 17 00:00:00 2001 From: Elle Mouton Date: Tue, 5 Sep 2023 08:45:36 +0200 Subject: [PATCH 5/5] itest: use coop closes instead of force closes --- itest/litd_firewall_test.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/itest/litd_firewall_test.go b/itest/litd_firewall_test.go index 989490ad1..d93775d72 100644 --- a/itest/litd_firewall_test.go +++ b/itest/litd_firewall_test.go @@ -163,7 +163,7 @@ func testFirewallRules(ctx context.Context, net *NetworkHarness, Amt: 100000, }, ) - defer closeChannelAndAssert(t, net, net.Alice, channelOp, true) + defer closeChannelAndAssert(t, net, net.Alice, channelOp, false) t.t.Run("history limit rule", func(_ *testing.T) { testHistoryLimitRule(net, t) @@ -201,14 +201,14 @@ func testSessionLinking(net *NetworkHarness, t *harnessTest) { Amt: 100000, }, ) - defer closeChannelAndAssert(t, net, net.Alice, channelAB2Op, true) + defer closeChannelAndAssert(t, net, net.Alice, channelAB2Op, false) channelAB3Op := openChannelAndAssert( t, net, net.Alice, net.Bob, lntest.OpenChannelParams{ Amt: 100000, }, ) - defer closeChannelAndAssert(t, net, net.Alice, channelAB3Op, true) + defer closeChannelAndAssert(t, net, net.Alice, channelAB3Op, false) // List Alice's channels so that we can extract the channel points for // the three channels. @@ -1271,7 +1271,7 @@ func testPeerAndChannelRestrictRules(net *NetworkHarness, t *harnessTest) { Amt: 100000, }, ) - defer closeChannelAndAssert(t, net, net.Alice, channelAB2Op, true) + defer closeChannelAndAssert(t, net, net.Alice, channelAB2Op, false) // Open two channels between Alice and Charlie. channelAC1Op := openChannelAndAssert( @@ -1279,14 +1279,14 @@ func testPeerAndChannelRestrictRules(net *NetworkHarness, t *harnessTest) { Amt: 100000, }, ) - defer closeChannelAndAssert(t, net, net.Alice, channelAC1Op, true) + defer closeChannelAndAssert(t, net, net.Alice, channelAC1Op, false) channelAC2Op := openChannelAndAssert( t, net, net.Alice, charlie, lntest.OpenChannelParams{ Amt: 100000, }, ) - defer closeChannelAndAssert(t, net, net.Alice, channelAC2Op, true) + defer closeChannelAndAssert(t, net, net.Alice, channelAC2Op, false) // List Alice's channels so that we can extract the channel points // for the four channels.