@@ -369,22 +369,24 @@ func (w *watcher) handleReobservationRequest(ctx context.Context, txHash, rpcURL
369369 continue
370370 }
371371
372- mp := w .parseEventJSON (e , logger )
373- if mp == nil {
372+ parsed := w .parseEventJSON (e , logger )
373+ if parsed == nil {
374374 continue
375375 }
376376
377- mp .TxID = txIDBytes
378- mp .Timestamp = timestamp
379- mp .EmitterChain = w .chainID
380- mp .IsReobservation = true
377+ mp := & common.MessagePublication {
378+ TxID : txIDBytes ,
379+ Timestamp : timestamp ,
380+ Nonce : parsed .nonce ,
381+ Sequence : parsed .sequence ,
382+ ConsistencyLevel : parsed .consistencyLevel ,
383+ EmitterChain : w .chainID ,
384+ EmitterAddress : parsed .emitterAddress ,
385+ Payload : parsed .payload ,
386+ IsReobservation : true ,
387+ }
381388
382- logger .Info ("reobserved stellar message" ,
383- zap .Uint64 ("ledger" , ledger ),
384- zap .String ("tx" , txHash ),
385- zap .Uint64 ("seq" , mp .Sequence ),
386- zap .Uint8 ("consistency" , mp .ConsistencyLevel ),
387- )
389+ logger .Info ("reobserved stellar message" , mp .ZapFields (zap .Uint64 ("ledger" , ledger ))... )
388390
389391 select {
390392 case w .msgC <- mp :
@@ -511,8 +513,8 @@ func (w *watcher) pollOnce(ctx context.Context, logger *zap.Logger) (bool, error
511513 ledger := e .Get ("ledger" ).Uint ()
512514 txHash := e .Get ("txHash" ).Str
513515
514- mp := w .parseEventJSON (e , logger )
515- if mp == nil {
516+ parsed := w .parseEventJSON (e , logger )
517+ if parsed == nil {
516518 continue
517519 }
518520
@@ -521,8 +523,6 @@ func (w *watcher) pollOnce(ctx context.Context, logger *zap.Logger) (bool, error
521523 logger .Warn ("failed to decode txHash" , zap .String ("txHash" , txHash ), zap .Error (err ))
522524 continue
523525 }
524- mp .TxID = txIDBytes
525- mp .EmitterChain = w .chainID
526526
527527 // Use ledgerClosedAt from the event for a deterministic timestamp.
528528 // All guardians observing the same event will use the same timestamp,
@@ -536,16 +536,21 @@ func (w *watcher) pollOnce(ctx context.Context, logger *zap.Logger) (bool, error
536536 )
537537 continue
538538 }
539- mp .Timestamp = ts
539+
540+ mp := & common.MessagePublication {
541+ TxID : txIDBytes ,
542+ Timestamp : ts ,
543+ Nonce : parsed .nonce ,
544+ Sequence : parsed .sequence ,
545+ ConsistencyLevel : parsed .consistencyLevel ,
546+ EmitterChain : w .chainID ,
547+ EmitterAddress : parsed .emitterAddress ,
548+ Payload : parsed .payload ,
549+ }
540550
541551 stellarMessagesObserved .WithLabelValues (w .networkName ).Inc ()
542552
543- logger .Info ("stellar message published" ,
544- zap .Uint64 ("ledger" , ledger ),
545- zap .String ("tx" , txHash ),
546- zap .Uint64 ("seq" , mp .Sequence ),
547- zap .Uint8 ("consistency" , mp .ConsistencyLevel ),
548- )
553+ logger .Info ("stellar message published" , mp .ZapFields (zap .Uint64 ("ledger" , ledger ))... )
549554
550555 select {
551556 case w .msgC <- mp :
@@ -574,9 +579,20 @@ func (w *watcher) pollOnce(ctx context.Context, logger *zap.Logger) (bool, error
574579 return advanced , nil
575580}
576581
582+ // parsedMessage holds the message fields parsed from a message_published event value.
583+ // The caller combines these with the transaction context (TxID, timestamp, chain ID)
584+ // to build a complete MessagePublication.
585+ type parsedMessage struct {
586+ nonce uint32
587+ sequence uint64
588+ consistencyLevel uint8
589+ emitterAddress vaa.Address
590+ payload []byte
591+ }
592+
577593// parseEventJSON checks the event topics for a message_published event and parses the event data.
578594// Returns nil if the event is not a message_published event or cannot be parsed.
579- func (w * watcher ) parseEventJSON (e gjson.Result , logger * zap.Logger ) * common. MessagePublication {
595+ func (w * watcher ) parseEventJSON (e gjson.Result , logger * zap.Logger ) * parsedMessage {
580596 topics := e .Get ("topic" ).Array ()
581597 if len (topics ) < minEventTopicCount {
582598 return nil
@@ -596,30 +612,29 @@ func (w *watcher) parseEventJSON(e gjson.Result, logger *zap.Logger) *common.Mes
596612 return nil
597613 }
598614
599- return parseMessageFromXDR (valueBytes , logger )
615+ parsed , err := w .parseMessageFromXDR (valueBytes )
616+ if err != nil {
617+ logger .Warn ("failed to parse message_published event, skipping" , zap .Error (err ))
618+ return nil
619+ }
620+ return parsed
600621}
601622
602- // parseMessageFromXDR parses the XDR-encoded Soroban event value into a MessagePublication .
603- func parseMessageFromXDR (data []byte , logger * zap. Logger ) * common. MessagePublication {
623+ // parseMessageFromXDR parses the XDR-encoded Soroban event value into the published message fields .
624+ func ( w * watcher ) parseMessageFromXDR (data []byte ) ( * parsedMessage , error ) {
604625 var scVal stellarxdr.ScVal
605626
606- _ , err := stellarxdr .Unmarshal (bytes .NewReader (data ), & scVal )
607- if err != nil {
608- logger .Debug ("failed to unmarshal XDR" , zap .Error (err ))
609- return nil
627+ if _ , err := stellarxdr .Unmarshal (bytes .NewReader (data ), & scVal ); err != nil {
628+ return nil , fmt .Errorf ("failed to unmarshal XDR: %w" , err )
610629 }
611630
612631 eventMap , ok := scVal .GetMap ()
613632 if ! ok {
614- logger .Debug ("event value is not a map" )
615- return nil
633+ return nil , fmt .Errorf ("event value is not a map" )
616634 }
617635
618- var nonce uint32
619- var sequence uint64
636+ msg := & parsedMessage {}
620637 var emitterAddress []byte
621- var payload []byte
622- var consistencyLevel uint32
623638
624639 for _ , entry := range * eventMap {
625640 keySymbol , ok := entry .Key .GetSym ()
@@ -630,49 +645,39 @@ func parseMessageFromXDR(data []byte, logger *zap.Logger) *common.MessagePublica
630645 switch string (keySymbol ) {
631646 case "nonce" :
632647 if val , ok := entry .Val .GetU32 (); ok {
633- nonce = uint32 (val )
648+ msg . nonce = uint32 (val )
634649 }
635650 case "sequence" :
636651 if val , ok := entry .Val .GetU64 (); ok {
637- sequence = uint64 (val )
652+ msg . sequence = uint64 (val )
638653 }
639654 case "emitter_address" :
640655 if val , ok := entry .Val .GetBytes (); ok {
641656 emitterAddress = val
642657 }
643658 case "payload" :
644659 if val , ok := entry .Val .GetBytes (); ok {
645- payload = val
660+ msg . payload = val
646661 }
647662 case "consistency_level" :
648663 if val , ok := entry .Val .GetU32 (); ok {
649- consistencyLevel = uint32 (val )
664+ if val > math .MaxUint8 {
665+ return nil , fmt .Errorf ("consistency level %d overflows uint8" , val )
666+ }
667+ msg .consistencyLevel = uint8 (val )
650668 }
651669 }
652670 }
653671
654672 if len (emitterAddress ) == 0 {
655- logger .Warn ("message_published event has empty emitter address, skipping" )
656- return nil
673+ return nil , fmt .Errorf ("message_published event has empty emitter address" )
657674 }
658675
659- var emitter vaa.Address
660676 if len (emitterAddress ) >= stellarAddressLen {
661- copy (emitter [:], emitterAddress [:stellarAddressLen ])
677+ copy (msg . emitterAddress [:], emitterAddress [:stellarAddressLen ])
662678 } else {
663- copy (emitter [:], emitterAddress )
679+ copy (msg . emitterAddress [:], emitterAddress )
664680 }
665681
666- return & common.MessagePublication {
667- TxID : nil ,
668- Timestamp : time.Time {},
669- Nonce : nonce ,
670- Sequence : sequence ,
671- ConsistencyLevel : uint8 (consistencyLevel ),
672- EmitterChain : vaa .ChainIDUnset ,
673- EmitterAddress : emitter ,
674- Payload : payload ,
675- IsReobservation : false ,
676- Unreliable : false ,
677- }
682+ return msg , nil
678683}
0 commit comments