1+ package verifier
2+
3+ import (
4+ "context"
5+ "fmt"
6+ "strings"
7+
8+ "github.com/LumeraProtocol/supernode/pkg/lumera"
9+ "github.com/LumeraProtocol/supernode/pkg/logtrace"
10+ "github.com/LumeraProtocol/supernode/supernode/config"
11+ "github.com/cosmos/cosmos-sdk/crypto/keyring"
12+ sdk "github.com/cosmos/cosmos-sdk/types"
13+ sntypes "github.com/LumeraProtocol/lumera/x/supernode/v1/types"
14+ )
15+
16+ // ConfigVerifier implements ConfigVerifierService
17+ type ConfigVerifier struct {
18+ config * config.Config
19+ lumeraClient lumera.Client
20+ keyring keyring.Keyring
21+ }
22+
23+ // NewConfigVerifier creates a new config verifier service
24+ func NewConfigVerifier (cfg * config.Config , client lumera.Client , kr keyring.Keyring ) ConfigVerifierService {
25+ return & ConfigVerifier {
26+ config : cfg ,
27+ lumeraClient : client ,
28+ keyring : kr ,
29+ }
30+ }
31+
32+ // VerifyConfig performs comprehensive config validation against chain
33+ func (cv * ConfigVerifier ) VerifyConfig (ctx context.Context ) (* VerificationResult , error ) {
34+ result := & VerificationResult {
35+ Valid : true ,
36+ Errors : []ConfigError {},
37+ Warnings : []ConfigError {},
38+ }
39+
40+ logtrace .Debug (ctx , "Starting config verification" , logtrace.Fields {
41+ "identity" : cv .config .SupernodeConfig .Identity ,
42+ "key_name" : cv .config .SupernodeConfig .KeyName ,
43+ "p2p_port" : cv .config .P2PConfig .Port ,
44+ })
45+
46+ // Check 1: Verify keyring contains the key
47+ if err := cv .checkKeyExists (result ); err != nil {
48+ return result , err
49+ }
50+
51+ // Check 2: Verify key resolves to correct identity
52+ if err := cv .checkIdentityMatches (result ); err != nil {
53+ return result , err
54+ }
55+
56+ // If keyring checks failed, don't proceed with chain queries
57+ if ! result .IsValid () {
58+ return result , nil
59+ }
60+
61+ // Check 3: Query chain for supernode registration
62+ supernode , err := cv .checkSupernodeExists (ctx , result )
63+ if err != nil {
64+ return result , err
65+ }
66+
67+ // If supernode doesn't exist, don't proceed with field comparisons
68+ if supernode == nil {
69+ return result , nil
70+ }
71+
72+ // Check 4: Verify P2P port matches
73+ cv .checkP2PPortMatches (result , supernode )
74+
75+ // Check 5: Verify supernode state is active
76+ cv .checkSupernodeState (result , supernode )
77+
78+ // Check 6: Check supernode port alignment with on-chain registration
79+ cv .checkSupernodePortAlignment (result , supernode )
80+
81+ // Check 7: Check host alignment with on-chain registration (warning only - may differ due to load balancer)
82+ cv .checkHostAlignment (result , supernode )
83+
84+ logtrace .Info (ctx , "Config verification completed" , logtrace.Fields {
85+ "valid" : result .IsValid (),
86+ "errors" : len (result .Errors ),
87+ "warnings" : len (result .Warnings ),
88+ })
89+
90+ return result , nil
91+ }
92+
93+ // checkKeyExists verifies the configured key exists in keyring
94+ func (cv * ConfigVerifier ) checkKeyExists (result * VerificationResult ) error {
95+ _ , err := cv .keyring .Key (cv .config .SupernodeConfig .KeyName )
96+ if err != nil {
97+ result .Valid = false
98+ result .Errors = append (result .Errors , ConfigError {
99+ Field : "key_name" ,
100+ Actual : cv .config .SupernodeConfig .KeyName ,
101+ Message : fmt .Sprintf ("Key '%s' not found in keyring" , cv .config .SupernodeConfig .KeyName ),
102+ })
103+ }
104+ return nil
105+ }
106+
107+ // checkIdentityMatches verifies key resolves to configured identity
108+ func (cv * ConfigVerifier ) checkIdentityMatches (result * VerificationResult ) error {
109+ keyInfo , err := cv .keyring .Key (cv .config .SupernodeConfig .KeyName )
110+ if err != nil {
111+ // Already handled in checkKeyExists
112+ return nil
113+ }
114+
115+ pubKey , err := keyInfo .GetPubKey ()
116+ if err != nil {
117+ return fmt .Errorf ("failed to get public key for key '%s': %w" , cv .config .SupernodeConfig .KeyName , err )
118+ }
119+
120+ addr := sdk .AccAddress (pubKey .Address ())
121+ if addr .String () != cv .config .SupernodeConfig .Identity {
122+ result .Valid = false
123+ result .Errors = append (result .Errors , ConfigError {
124+ Field : "identity" ,
125+ Expected : addr .String (),
126+ Actual : cv .config .SupernodeConfig .Identity ,
127+ Message : fmt .Sprintf ("Key '%s' resolves to %s but config identity is %s" , cv .config .SupernodeConfig .KeyName , addr .String (), cv .config .SupernodeConfig .Identity ),
128+ })
129+ }
130+ return nil
131+ }
132+
133+ // checkSupernodeExists queries chain for supernode registration
134+ func (cv * ConfigVerifier ) checkSupernodeExists (ctx context.Context , result * VerificationResult ) (* sntypes.SuperNode , error ) {
135+ sn , err := cv .lumeraClient .SuperNode ().GetSupernodeBySupernodeAddress (ctx , cv .config .SupernodeConfig .Identity )
136+ if err != nil {
137+ result .Valid = false
138+ result .Errors = append (result .Errors , ConfigError {
139+ Field : "registration" ,
140+ Actual : "not_registered" ,
141+ Message : fmt .Sprintf ("Supernode not registered on chain for address %s" , cv .config .SupernodeConfig .Identity ),
142+ })
143+ return nil , nil
144+ }
145+ return sn , nil
146+ }
147+
148+ // checkP2PPortMatches compares config P2P port with chain
149+ func (cv * ConfigVerifier ) checkP2PPortMatches (result * VerificationResult , supernode * sntypes.SuperNode ) {
150+ configPort := fmt .Sprintf ("%d" , cv .config .P2PConfig .Port )
151+ chainPort := supernode .P2PPort
152+
153+ if chainPort != "" && chainPort != configPort {
154+ result .Valid = false
155+ result .Errors = append (result .Errors , ConfigError {
156+ Field : "p2p_port" ,
157+ Expected : chainPort ,
158+ Actual : configPort ,
159+ Message : fmt .Sprintf ("P2P port mismatch: config=%s, chain=%s" , configPort , chainPort ),
160+ })
161+ }
162+ }
163+
164+ // checkSupernodeState verifies supernode is in active state
165+ func (cv * ConfigVerifier ) checkSupernodeState (result * VerificationResult , supernode * sntypes.SuperNode ) {
166+ if len (supernode .States ) > 0 {
167+ lastState := supernode .States [len (supernode .States )- 1 ]
168+ if lastState .State .String () != "SUPERNODE_STATE_ACTIVE" {
169+ result .Valid = false
170+ result .Errors = append (result .Errors , ConfigError {
171+ Field : "state" ,
172+ Expected : "SUPERNODE_STATE_ACTIVE" ,
173+ Actual : lastState .State .String (),
174+ Message : fmt .Sprintf ("Supernode state is %s (expected ACTIVE)" , lastState .State .String ()),
175+ })
176+ }
177+ }
178+ }
179+
180+ // checkSupernodePortAlignment compares supernode port with on-chain registered port (error if mismatch)
181+ func (cv * ConfigVerifier ) checkSupernodePortAlignment (result * VerificationResult , supernode * sntypes.SuperNode ) {
182+ if len (supernode .PrevIpAddresses ) > 0 {
183+ chainAddress := supernode .PrevIpAddresses [len (supernode .PrevIpAddresses )- 1 ].Address
184+
185+ // Extract port from chain address
186+ var chainPort string
187+ if idx := strings .LastIndex (chainAddress , ":" ); idx != - 1 {
188+ chainPort = chainAddress [idx + 1 :]
189+ }
190+
191+ configPort := fmt .Sprintf ("%d" , cv .config .SupernodeConfig .Port )
192+ if chainPort != "" && chainPort != configPort {
193+ result .Valid = false
194+ result .Errors = append (result .Errors , ConfigError {
195+ Field : "supernode_port" ,
196+ Expected : chainPort ,
197+ Actual : configPort ,
198+ Message : fmt .Sprintf ("Supernode port mismatch: config=%s, chain=%s" , configPort , chainPort ),
199+ })
200+ }
201+ }
202+ }
203+
204+ // checkHostAlignment compares host with on-chain registered host (warning only - may differ due to load balancer)
205+ func (cv * ConfigVerifier ) checkHostAlignment (result * VerificationResult , supernode * sntypes.SuperNode ) {
206+ if len (supernode .PrevIpAddresses ) > 0 {
207+ chainAddress := supernode .PrevIpAddresses [len (supernode .PrevIpAddresses )- 1 ].Address
208+
209+ // Extract host from chain address
210+ chainHost := chainAddress
211+ if idx := strings .LastIndex (chainAddress , ":" ); idx != - 1 {
212+ chainHost = chainAddress [:idx ]
213+ }
214+
215+ if chainHost != cv .config .SupernodeConfig .Host {
216+ result .Warnings = append (result .Warnings , ConfigError {
217+ Field : "host" ,
218+ Expected : cv .config .SupernodeConfig .Host ,
219+ Actual : chainHost ,
220+ Message : fmt .Sprintf ("Host mismatch: config=%s, chain=%s" , cv .config .SupernodeConfig .Host , chainHost ),
221+ })
222+ }
223+ }
224+ }
0 commit comments