@@ -11,6 +11,7 @@ import (
1111 "net/http/httptest"
1212 "sort"
1313 "strings"
14+ "sync/atomic"
1415 "testing"
1516 "time"
1617
@@ -2692,6 +2693,189 @@ channelDefinitionsContractFromBlock = %d`, serverURL, serverPubKey, donID, confi
26922693 })
26932694}
26942695
2696+ // TestIntegration_LLO_tombstone_stops_observations_and_reports checks that once a channel is
2697+ // tombstoned, the DON stops observing its streams (no bridge traffic for those stream jobs)
2698+ // and no longer transmits reports for that channel.
2699+ func TestIntegration_LLO_tombstone_stops_observations_and_reports (t * testing.T ) {
2700+ t .Parallel ()
2701+
2702+ const (
2703+ salt = 500
2704+ donID = uint32 (777666 )
2705+ streamIDActive = uint32 (190 )
2706+ streamIDTombstone = uint32 (191 )
2707+ )
2708+
2709+ offchainConfig := datastreamsllo.OffchainConfig {
2710+ ProtocolVersion : 1 ,
2711+ DefaultMinReportIntervalNanoseconds : uint64 (1 * time .Second ),
2712+ EnableObservationCompression : true ,
2713+ }
2714+
2715+ clientCSAKeys := make ([]csakey.KeyV2 , nNodes )
2716+ clientPubKeys := make ([]ed25519.PublicKey , nNodes )
2717+ for i := range nNodes {
2718+ k := big .NewInt (int64 (salt + i ))
2719+ key := csakey .MustNewV2XXXTestingOnly (k )
2720+ clientCSAKeys [i ] = key
2721+ clientPubKeys [i ] = key .PublicKey
2722+ }
2723+
2724+ steve , backend , configurator , configuratorAddress , _ , _ , _ , _ , configStore , configStoreAddress , _ , _ , _ , _ := setupBlockchain (t )
2725+ fromBlock := 1
2726+
2727+ bootstrapCSAKey := csakey .MustNewV2XXXTestingOnly (big .NewInt (salt - 1 ))
2728+ bootstrapNodePort := freeport .GetOne (t )
2729+ appBootstrap , bootstrapPeerID , _ , bootstrapKb , _ := setupNode (t , bootstrapNodePort , "bootstrap_llo_tombstone" , backend , bootstrapCSAKey , nil )
2730+ bootstrapNode := Node {App : appBootstrap , KeyBundle : bootstrapKb }
2731+
2732+ packetCh := make (chan * packet , 100000 )
2733+ serverKey := csakey .MustNewV2XXXTestingOnly (big .NewInt (salt - 2 ))
2734+ serverPubKey := serverKey .PublicKey
2735+ srv := NewMercuryServer (t , serverKey , packetCh )
2736+ serverURL := startMercuryServer (t , srv , clientPubKeys )
2737+
2738+ oracles , nodes := setupNodes (t , nNodes , backend , clientCSAKeys , func (c * chainlink.Config ) {
2739+ c .Mercury .Transmitter .Protocol = ptr (config .MercuryTransmitterProtocolGRPC )
2740+ })
2741+
2742+ chainID := testutils .SimulatedChainID
2743+ relayType := "evm"
2744+ relayConfig := fmt .Sprintf (`
2745+ chainID = "%s"
2746+ fromBlock = %d
2747+ lloDonID = %d
2748+ lloConfigMode = "bluegreen"
2749+ ` , chainID , fromBlock , donID )
2750+ addBootstrapJob (t , bootstrapNode , configuratorAddress , "job-tombstone" , relayType , relayConfig )
2751+
2752+ pluginConfig := fmt .Sprintf (`servers = { "%s" = "%x" }
2753+ donID = %d
2754+ channelDefinitionsContractAddress = "0x%x"
2755+ channelDefinitionsContractFromBlock = %d` , serverURL , serverPubKey , donID , configStoreAddress , fromBlock )
2756+
2757+ var streamACalls , streamBCalls atomic.Uint64
2758+ priceA := decimal .NewFromFloat (111.1 )
2759+ priceB := decimal .NewFromFloat (222.2 )
2760+ for i , node := range nodes {
2761+ bridgeA := createSingleDecimalCountingBridge (t , "tomb-active" , i , priceA , node .App .BridgeORM (), & streamACalls )
2762+ addSingleDecimalStreamJob (t , node , streamIDActive , bridgeA )
2763+ bridgeB := createSingleDecimalCountingBridge (t , "tomb-stone" , i , priceB , node .App .BridgeORM (), & streamBCalls )
2764+ addSingleDecimalStreamJob (t , node , streamIDTombstone , bridgeB )
2765+ addLLOJob (
2766+ t ,
2767+ node ,
2768+ configuratorAddress ,
2769+ bootstrapPeerID ,
2770+ bootstrapNodePort ,
2771+ clientPubKeys [i ],
2772+ "tombstone-stream-test" ,
2773+ pluginConfig ,
2774+ relayType ,
2775+ relayConfig ,
2776+ )
2777+ }
2778+
2779+ channelDefinitions := llotypes.ChannelDefinitions {
2780+ 1 : {
2781+ ReportFormat : llotypes .ReportFormatJSON ,
2782+ Streams : []llotypes.Stream {
2783+ {StreamID : streamIDActive , Aggregator : llotypes .AggregatorMedian },
2784+ },
2785+ },
2786+ 2 : {
2787+ ReportFormat : llotypes .ReportFormatJSON ,
2788+ Streams : []llotypes.Stream {
2789+ {StreamID : streamIDTombstone , Aggregator : llotypes .AggregatorMedian },
2790+ },
2791+ },
2792+ }
2793+ url , sha := newChannelDefinitionsServer (t , channelDefinitions )
2794+ _ , err := configStore .SetChannelDefinitions (steve , donID , url , sha )
2795+ require .NoError (t , err )
2796+ backend .Commit ()
2797+
2798+ setProductionConfig (
2799+ t , donID , steve , backend , configurator , configuratorAddress , nodes ,
2800+ WithOracles (oracles ), WithOffchainConfig (offchainConfig ),
2801+ )
2802+
2803+ seenChannels := make (map [uint32 ]bool )
2804+ require .Eventually (t , func () bool {
2805+ pckt , errReceive := receiveWithTimeout (t , packetCh , 2 * time .Second )
2806+ if errReceive != nil {
2807+ return false
2808+ }
2809+ req := pckt .req
2810+ if req .ReportFormat != uint32 (llotypes .ReportFormatJSON ) {
2811+ return len (seenChannels ) == 2
2812+ }
2813+ _ , _ , r , _ , errDecode := (datastreamsllo.JSONReportCodec {}).UnpackDecode (req .Payload )
2814+ if errDecode != nil {
2815+ return len (seenChannels ) == 2
2816+ }
2817+ if r .ChannelID == 1 || r .ChannelID == 2 {
2818+ seenChannels [r .ChannelID ] = true
2819+ }
2820+ return len (seenChannels ) == 2
2821+ }, reportTimeout , 100 * time .Millisecond , "expected reports for channel 1 and 2 before tombstone" )
2822+ require .Positive (t , streamBCalls .Load (), "stream for channel 2 should be observed before tombstone" )
2823+
2824+ // Tombstone channel 2 only; channel 1 keeps observing streamIDActive.
2825+ tombstonedDefs := llotypes.ChannelDefinitions {
2826+ 1 : {
2827+ ReportFormat : llotypes .ReportFormatJSON ,
2828+ Streams : []llotypes.Stream {
2829+ {StreamID : streamIDActive , Aggregator : llotypes .AggregatorMedian },
2830+ },
2831+ },
2832+ 2 : {
2833+ ReportFormat : llotypes .ReportFormatJSON ,
2834+ Tombstone : true ,
2835+ Streams : []llotypes.Stream {
2836+ {StreamID : streamIDTombstone , Aggregator : llotypes .AggregatorMedian },
2837+ },
2838+ },
2839+ }
2840+ url2 , sha2 := newChannelDefinitionsServer (t , tombstonedDefs )
2841+ _ , err = configStore .SetChannelDefinitions (steve , donID , url2 , sha2 )
2842+ require .NoError (t , err )
2843+ backend .Commit ()
2844+
2845+ tombstonedChannel := map [uint32 ]bool {2 : true }
2846+ checkNoReportsWindow := 5 * time .Second
2847+ require .Eventually (t , func () bool {
2848+ start := time .Now ()
2849+ sawTombstoned := false
2850+ for time .Since (start ) < checkNoReportsWindow {
2851+ pckt , err := receiveWithTimeout (t , packetCh , 1 * time .Second )
2852+ if err != nil {
2853+ continue
2854+ }
2855+ req := pckt .req
2856+ if req .ReportFormat != uint32 (llotypes .ReportFormatJSON ) {
2857+ continue
2858+ }
2859+ _ , _ , r , _ , err := (datastreamsllo.JSONReportCodec {}).UnpackDecode (req .Payload )
2860+ if err == nil && tombstonedChannel [r .ChannelID ] {
2861+ sawTombstoned = true
2862+ break
2863+ }
2864+ }
2865+ return ! sawTombstoned
2866+ }, 45 * time .Second , 200 * time .Millisecond , "channel 2 should stop producing reports after tombstone" )
2867+
2868+ // After reports for channel 2 have stopped, bridge traffic for streamIDTombstone should stop
2869+ // while streamIDActive continues to be observed.
2870+ bCallsAfterReportsStopped := streamBCalls .Load ()
2871+ aCallsAfterReportsStopped := streamACalls .Load ()
2872+ time .Sleep (1 * time .Second )
2873+ require .Equal (t , bCallsAfterReportsStopped , streamBCalls .Load (),
2874+ "tombstoned channel's stream should not be observed (no additional bridge calls)" )
2875+ require .Greater (t , streamACalls .Load (), aCallsAfterReportsStopped ,
2876+ "active channel's stream should still be observed" )
2877+ }
2878+
26952879func setupNodes (t * testing.T , nNodes int , backend evmtypes.Backend , clientCSAKeys []csakey.KeyV2 , f func (* chainlink.Config )) (oracles []confighelper.OracleIdentityExtra , nodes []Node ) {
26962880 ports := freeport .GetN (t , nNodes )
26972881 for i := range nNodes {
@@ -2730,7 +2914,6 @@ func newChannelDefinitionsServer(t *testing.T, channelDefinitions llotypes.Chann
27302914 return
27312915 }
27322916 w .Header ().Set ("Content-Type" , "application/json" )
2733- w .WriteHeader (http .StatusOK )
27342917 }))
27352918 t .Cleanup (channelDefinitionsServer .Close )
27362919 return channelDefinitionsServer .URL , channelDefinitionsSHA
0 commit comments