5
5
"database/sql"
6
6
"database/sql/driver"
7
7
_ "embed"
8
+ "encoding/json"
8
9
"errors"
9
10
"fmt"
10
11
"net/http"
@@ -17,6 +18,7 @@ import (
17
18
"github.com/filecoin-project/go-f3/certexchange"
18
19
"github.com/filecoin-project/go-f3/certexchange/polling"
19
20
"github.com/filecoin-project/go-f3/certstore"
21
+ "github.com/filecoin-project/go-f3/chainexchange"
20
22
"github.com/filecoin-project/go-f3/gpbft"
21
23
"github.com/filecoin-project/go-f3/internal/encoding"
22
24
"github.com/filecoin-project/go-f3/internal/lotus"
@@ -33,6 +35,7 @@ import (
33
35
"github.com/libp2p/go-libp2p/core/peer"
34
36
"github.com/marcboeker/go-duckdb"
35
37
"go.uber.org/multierr"
38
+ "golang.org/x/exp/maps"
36
39
"golang.org/x/sync/errgroup"
37
40
)
38
41
@@ -51,7 +54,7 @@ type Observer struct {
51
54
dht * dht.IpfsDHT
52
55
53
56
messageObserved chan * Message
54
- msgEncoding * encoding.ZSTD [* gpbft.PartialGMessage ]
57
+ gMsgEncoding * encoding.ZSTD [* gpbft.PartialGMessage ]
55
58
56
59
dbConnector * duckdb.Connector
57
60
dbLatestMessages * duckdb.Appender
@@ -62,21 +65,32 @@ type Observer struct {
62
65
dbFinalityCertificates * duckdb.Appender
63
66
unflushedCertificateCount int
64
67
lastFlushedCertificatesAt time.Time
68
+
69
+ chainExchangeObserved chan * chainexchange.Message
70
+ ceMsgEncoding * encoding.ZSTD [* chainexchange.Message ]
65
71
}
66
72
67
73
func New (o ... Option ) (* Observer , error ) {
68
74
opts , err := newOptions (o ... )
69
75
if err != nil {
70
76
return nil , err
71
77
}
72
- msgEncoding , err := encoding .NewZSTD [* gpbft.PartialGMessage ]()
78
+ gMsgEncoding , err := encoding .NewZSTD [* gpbft.PartialGMessage ]()
79
+ if err != nil {
80
+ return nil , err
81
+ }
82
+
83
+ ceMsgEncoding , err := encoding .NewZSTD [* chainexchange.Message ]()
73
84
if err != nil {
74
85
return nil , err
75
86
}
87
+
76
88
return & Observer {
77
- options : opts ,
78
- messageObserved : make (chan * Message , opts .messageBufferSize ),
79
- msgEncoding : msgEncoding ,
89
+ options : opts ,
90
+ messageObserved : make (chan * Message , opts .messageBufferSize ),
91
+ gMsgEncoding : gMsgEncoding ,
92
+ chainExchangeObserved : make (chan * chainexchange.Message , opts .chainExchangeBufferSize ),
93
+ ceMsgEncoding : ceMsgEncoding ,
80
94
}, nil
81
95
}
82
96
@@ -88,6 +102,7 @@ func (o *Observer) Start(ctx context.Context) error {
88
102
ctx , stop := context .WithCancel (ctx )
89
103
eg , ctx := errgroup .WithContext (ctx )
90
104
eg .Go (func () error { return o .observeMessages (ctx ) })
105
+ eg .Go (func () error { return o .observeChainExchanges (ctx ) })
91
106
eg .Go (func () error { return o .observeFinalityCertificates (ctx ) })
92
107
eg .Go (func () error { return o .stayConnected (ctx ) })
93
108
eg .Go (o .listenAndServeQueries )
@@ -211,7 +226,7 @@ func (o *Observer) createOrReplaceMessagesView(ctx context.Context, includeParqu
211
226
func (o * Observer ) observeMessages (ctx context.Context ) error {
212
227
rotation := time .NewTicker (o .rotateInterval )
213
228
flush := time .NewTicker (o .maxBatchDelay )
214
- stopObserverForNetwork , err := o .startObserverFor (ctx , o .networkName )
229
+ stopObserverForNetwork , err := o .startMessageObserverFor (ctx , o .networkName )
215
230
if err != nil {
216
231
return fmt .Errorf ("failed to start observer for network %s: %w" , o .networkName , err )
217
232
}
@@ -245,6 +260,103 @@ func (o *Observer) observeMessages(ctx context.Context) error {
245
260
return nil
246
261
}
247
262
263
+ func (o * Observer ) observeChainExchanges (ctx context.Context ) error {
264
+ flush := time .NewTicker (o .maxBatchDelay )
265
+ stopObserverForNetwork , err := o .startChainExchangeObserverFor (ctx , o .networkName )
266
+ if err != nil {
267
+ return fmt .Errorf ("failed to start observer for network %s: %w" , o .networkName , err )
268
+ }
269
+
270
+ defer stopObserverForNetwork ()
271
+
272
+ seenKeys := make (map [string ]struct {})
273
+ bufferedChains := make ([]* ChainExchange , 0 , o .maxBatchSize )
274
+
275
+ tryStoreAllChainExchanges := func () {
276
+ start := time .Now ()
277
+ if err := o .storeAllChainExchanges (ctx , bufferedChains ); err != nil {
278
+ logger .Errorw ("Failed to store chain exchanges" , "count" , len (bufferedChains ), "err" , err )
279
+ // Don't clear; let it retry upon the next message.
280
+ } else {
281
+ logger .Infow ("Stored batch of chain exchanges" , "count" , len (bufferedChains ), "took" , time .Since (start ))
282
+ bufferedChains = bufferedChains [:0 ]
283
+ maps .Clear (seenKeys )
284
+ }
285
+ }
286
+
287
+ for ctx .Err () == nil {
288
+ select {
289
+ case <- ctx .Done ():
290
+ return nil
291
+ case oc := <- o .chainExchangeObserved :
292
+ timestamp := time .UnixMilli (oc .Timestamp )
293
+ // Only key on vote value key instead of vote value key plus instance. Because,
294
+ // the purpose of observing the chain exchanges is to be able to infer what a
295
+ // vote value key corresponds to. We can use the flow of messages captured to
296
+ // infer if an instance is re-using a key from previous instances.
297
+ allPrefixes := oc .Chain .AllPrefixes ()
298
+ for i := len (allPrefixes ) - 1 ; i >= 0 && ctx .Err () == nil ; i -- {
299
+ prefix := allPrefixes [i ]
300
+ key := prefix .Key ()
301
+ bufferKey := string (key [:])
302
+ if _ , exists := seenKeys [bufferKey ]; exists {
303
+ break
304
+ }
305
+ seenKeys [bufferKey ] = struct {}{}
306
+ exchange := newChainExchange (timestamp , o .networkName , oc .Instance , prefix )
307
+ bufferedChains = append (bufferedChains , exchange )
308
+ logger .Debugw ("Observed chain exchange message" , "message" , exchange )
309
+ if len (bufferedChains ) >= o .maxBatchSize {
310
+ tryStoreAllChainExchanges ()
311
+ }
312
+ }
313
+ case <- flush .C :
314
+ tryStoreAllChainExchanges ()
315
+ }
316
+ }
317
+ return nil
318
+ }
319
+
320
+ func (o * Observer ) storeAllChainExchanges (ctx context.Context , exchanges []* ChainExchange ) error {
321
+ tx , err := o .db .BeginTx (ctx , nil )
322
+ if err != nil {
323
+ return err
324
+ }
325
+
326
+ stmt , err := tx .PrepareContext (ctx , `
327
+ INSERT OR IGNORE INTO chain_exchanges (Timestamp, NetworkName, Instance, VoteValueKey, VoteValue)
328
+ VALUES (?, ?, ?, ?, ?::json)` )
329
+ if err != nil {
330
+ _ = tx .Rollback ()
331
+ return fmt .Errorf ("failed to prepare statement while storing chain exchanges: %w" , err )
332
+ }
333
+ defer func () {
334
+ if err := stmt .Close (); err != nil {
335
+ logger .Errorw ("Failed to close prepared statement while storing chain exchanges" , "err" , err )
336
+ }
337
+ }()
338
+
339
+ for row , cx := range exchanges {
340
+ voteAsJson , err := json .Marshal (cx .VoteValue )
341
+ if err != nil {
342
+ _ = tx .Rollback ()
343
+ return fmt .Errorf ("failed to marshal chain exchange vote value at row %d: %w" , row , err )
344
+ }
345
+ _ , err = stmt .Exec (
346
+ cx .Timestamp ,
347
+ cx .NetworkName ,
348
+ cx .Instance ,
349
+ cx .VoteValueKey ,
350
+ string (voteAsJson ),
351
+ )
352
+ if err != nil {
353
+ _ = tx .Rollback ()
354
+ return fmt .Errorf ("failed to insert chain exchange at row %d: %w" , row , err )
355
+ }
356
+ }
357
+ return tx .Commit ()
358
+ }
359
+
248
360
func (o * Observer ) observeFinalityCertificates (ctx context.Context ) error {
249
361
if o .initialPowerTableCID == cid .Undef {
250
362
logger .Warn ("Initial power table CID is not set. Finality certificates will not be collected." )
@@ -542,7 +654,7 @@ func (o *Observer) tryConnectToBootstrapPeers(ctx context.Context) {
542
654
_ = eg .Wait ()
543
655
}
544
656
545
- func (o * Observer ) startObserverFor (ctx context.Context , networkName gpbft.NetworkName ) (_stop func (), _err error ) {
657
+ func (o * Observer ) startMessageObserverFor (ctx context.Context , networkName gpbft.NetworkName ) (_stop func (), _err error ) {
546
658
topicName := manifest .PubSubTopicFromNetworkName (networkName )
547
659
var (
548
660
topic * pubsub.Topic
@@ -564,7 +676,7 @@ func (o *Observer) startObserverFor(ctx context.Context, networkName gpbft.Netwo
564
676
}
565
677
}()
566
678
if ! o .pubSubValidatorDisabled {
567
- if err := o .pubSub .RegisterTopicValidator (topicName , o .validatePubSubMessage ); err != nil {
679
+ if err := o .pubSub .RegisterTopicValidator (topicName , o .validatePubSubGMessage ); err != nil {
568
680
return nil , fmt .Errorf ("failed to register topic validator: %w" , err )
569
681
}
570
682
} else {
@@ -621,6 +733,81 @@ func (o *Observer) startObserverFor(ctx context.Context, networkName gpbft.Netwo
621
733
}, nil
622
734
}
623
735
736
+ func (o * Observer ) startChainExchangeObserverFor (ctx context.Context , networkName gpbft.NetworkName ) (_stop func (), _err error ) {
737
+ topicName := manifest .ChainExchangeTopicFromNetworkName (networkName )
738
+ var (
739
+ topic * pubsub.Topic
740
+ subscription * pubsub.Subscription
741
+ err error
742
+ )
743
+
744
+ defer func () {
745
+ if _err != nil {
746
+ if ! o .pubSubValidatorDisabled {
747
+ _ = o .pubSub .UnregisterTopicValidator (topicName )
748
+ if topic != nil {
749
+ _ = topic .Close ()
750
+ }
751
+ }
752
+ if subscription != nil {
753
+ subscription .Cancel ()
754
+ }
755
+ }
756
+ }()
757
+ if err := o .pubSub .RegisterTopicValidator (topicName , o .validatePubSubChainExchangeMessage ); err != nil {
758
+ return nil , fmt .Errorf ("failed to register chain exchange topic validator: %w" , err )
759
+ }
760
+ topic , err = o .pubSub .Join (topicName , pubsub .WithTopicMessageIdFn (psutil .ChainExchangeMessageIdFn ))
761
+ if err != nil {
762
+ return nil , fmt .Errorf ("failed to join topic: %w" , err )
763
+ }
764
+ if err = topic .SetScoreParams (psutil .PubsubTopicScoreParams ); err != nil {
765
+ logger .Warnw ("failed to set topic score params for chain exchange" , "err" , err )
766
+ }
767
+ subscription , err = topic .Subscribe (pubsub .WithBufferSize (o .subBufferSize ))
768
+ if err != nil {
769
+ return nil , fmt .Errorf ("failed to subscribe to chain exchange topic: %w" , err )
770
+ }
771
+
772
+ ctx , cancel := context .WithCancel (ctx )
773
+ var wg sync.WaitGroup
774
+ wg .Add (1 )
775
+ go func () {
776
+ defer func () {
777
+ subscription .Cancel ()
778
+ _ = topic .Close ()
779
+ wg .Done ()
780
+ }()
781
+
782
+ for ctx .Err () == nil {
783
+ msg , err := subscription .Next (ctx )
784
+ if err != nil && ! errors .Is (err , context .Canceled ) {
785
+ logger .Errorw ("Failed to get next pubsub message from chain exchange topic" , "network" , networkName , "err" , err )
786
+ continue
787
+ }
788
+ if msg == nil || msg .ValidatorData == nil {
789
+ continue
790
+ }
791
+
792
+ ceMsg , ok := msg .ValidatorData .(chainexchange.Message )
793
+ if ! ok {
794
+ logger .Errorw ("Received message with invalid ValidatorData type" , "expected" , "chainexchange.Message" , "got" , fmt .Sprintf ("%T" , msg .ValidatorData ))
795
+ continue
796
+ }
797
+
798
+ select {
799
+ case <- ctx .Done ():
800
+ return
801
+ case o .chainExchangeObserved <- & ceMsg :
802
+ }
803
+ }
804
+ }()
805
+ return func () {
806
+ cancel ()
807
+ wg .Wait ()
808
+ }, nil
809
+ }
810
+
624
811
func (o * Observer ) anyParquetFilesPresent () (bool , error ) {
625
812
dir , err := os .ReadDir (o .rotatePath )
626
813
if err != nil {
@@ -643,11 +830,35 @@ func (o *Observer) Stop(ctx context.Context) error {
643
830
return err
644
831
}
645
832
646
- func (o * Observer ) validatePubSubMessage (_ context.Context , _ peer.ID , msg * pubsub.Message ) pubsub.ValidationResult {
833
+ func (o * Observer ) validatePubSubGMessage (_ context.Context , _ peer.ID , msg * pubsub.Message ) pubsub.ValidationResult {
647
834
var pgmsg gpbft.PartialGMessage
648
- if err := o .msgEncoding .Decode (msg .Data , & pgmsg ); err != nil {
835
+ if err := o .gMsgEncoding .Decode (msg .Data , & pgmsg ); err != nil {
649
836
return pubsub .ValidationReject
650
837
}
651
838
msg .ValidatorData = pgmsg
652
839
return pubsub .ValidationAccept
653
840
}
841
+
842
+ func (o * Observer ) validatePubSubChainExchangeMessage (_ context.Context , _ peer.ID , msg * pubsub.Message ) pubsub.ValidationResult {
843
+ var ceMsg chainexchange.Message
844
+ if err := o .ceMsgEncoding .Decode (msg .Data , & ceMsg ); err != nil {
845
+ logger .Debugw ("Failed to decode chain exchange message" , "err" , err , "message" , msg )
846
+ return pubsub .ValidationReject
847
+ }
848
+ if err := ceMsg .Chain .Validate (); err != nil {
849
+ logger .Debugw ("Invalid chain in chain exchange message" , "err" , err , "message" , ceMsg )
850
+ return pubsub .ValidationReject
851
+ }
852
+ if ceMsg .Chain .IsZero () {
853
+ logger .Debugw ("Chain in chain exchange message is zero" , "message" , ceMsg )
854
+ return pubsub .ValidationReject
855
+ }
856
+ now := time .Now ().UnixMilli ()
857
+ lowerBound := now - o .chainExchangeMaxMessageAge .Milliseconds ()
858
+ if lowerBound > ceMsg .Timestamp || ceMsg .Timestamp > now {
859
+ logger .Debugw ("Timestamp too old or too far ahead" , "from" , msg .GetFrom (), "timestamp" , ceMsg .Timestamp , "lowerBound" , lowerBound )
860
+ return pubsub .ValidationIgnore
861
+ }
862
+ msg .ValidatorData = ceMsg
863
+ return pubsub .ValidationAccept
864
+ }
0 commit comments