From 1e0ef8a5b2b1aa604dc04bfff997cf0f3f97ae1a Mon Sep 17 00:00:00 2001 From: Akhil Kumar P <36399231+akhilkumarpilli@users.noreply.github.com> Date: Tue, 2 Feb 2021 19:26:48 +0530 Subject: [PATCH] Implement bottom up validation (#398) * WIP: Add path validation in config * Implement bottom up validation * Fetch single identifier data directly in validation * Address PR comments * Address new PR comments * Add channel and connection condition * Fix lint issues --- cmd/config.go | 124 +++++++++++++++++++++++++++++++++++++++++++++++- cmd/paths.go | 29 +++++++++++ relayer/path.go | 1 - 3 files changed, 151 insertions(+), 3 deletions(-) diff --git a/cmd/config.go b/cmd/config.go index d9aaecbd4..43f6a9877 100644 --- a/cmd/config.go +++ b/cmd/config.go @@ -221,15 +221,17 @@ func cfgFilesAdd(dir string) (cfg *Config, err error) { } pthName := strings.Split(f.Name(), ".")[0] + if err = config.ValidatePath(p); err != nil { + fmt.Printf("%s: %s\n", pth, err.Error()) + continue + } if err = cfg.AddPath(pthName, p); err != nil { fmt.Printf("%s: %s\n", pth, err.Error()) continue } - // TODO: Do bottom up validation // For now, we assume that all chain files must have same filename as chain-id // this is to ensure non-chain files (global config) does not get parsed into chain struct. - // Future work should implement bottom-up validation. if c.ChainID != pthName { fmt.Printf("Skipping non chain file: %s\n", f.Name()) continue @@ -420,3 +422,121 @@ func overWriteConfig(cmd *cobra.Command, cfg *Config) error { } return err } + +// ValidatePath checks that a path is valid +func (c *Config) ValidatePath(p *relayer.Path) (err error) { + if p.Src.Version == "" { + return fmt.Errorf("source must specify a version") + } + if err = c.ValidatePathEnd(p.Src); err != nil { + return err + } + if err = c.ValidatePathEnd(p.Dst); err != nil { + return err + } + if _, err = p.GetStrategy(); err != nil { + return err + } + if p.Src.Order != p.Dst.Order { + return fmt.Errorf("both sides must have same order ('ORDERED' or 'UNORDERED'), got src(%s) and dst(%s)", + p.Src.Order, p.Dst.Order) + } + return nil +} + +// ValidatePathEnd validates provided pathend and returns error for invalid identifiers +func (c *Config) ValidatePathEnd(pe *relayer.PathEnd) error { + if err := pe.ValidateBasic(); err != nil { + return err + } + + chain, err := c.Chains.Get(pe.ChainID) + if err != nil { + return err + } + + height, err := chain.QueryLatestHeight() + if err != nil { + return err + } + + if pe.ClientID != "" { + if err := c.ValidateClient(chain, height, pe); err != nil { + return err + } + + if pe.ConnectionID != "" { + if err := c.ValidateConnection(chain, height, pe); err != nil { + return err + } + + if pe.ChannelID != "" { + if err := c.ValidateChannel(chain, height, pe); err != nil { + return err + } + } + } + + if pe.ConnectionID == "" && pe.ChannelID != "" { + return fmt.Errorf("connectionID is not configured for the channel: %s", pe.ChannelID) + } + } + + if pe.ClientID == "" && pe.ConnectionID != "" { + return fmt.Errorf("clientID is not configured for the connection: %s", pe.ConnectionID) + } + + return nil +} + +// ValidateClient validates client id in provided pathend +func (c *Config) ValidateClient(chain *relayer.Chain, height int64, pe *relayer.PathEnd) error { + if err := pe.Vclient(); err != nil { + return err + } + + _, err := chain.QueryClientState(height) + if err != nil { + return err + } + + return nil +} + +// ValidateConnection validates connection id in provided pathend +func (c *Config) ValidateConnection(chain *relayer.Chain, height int64, pe *relayer.PathEnd) error { + if err := pe.Vconn(); err != nil { + return err + } + + connection, err := chain.QueryConnection(height) + if err != nil { + return err + } + + if connection.Connection.ClientId != pe.ClientID { + return fmt.Errorf("clientID of connection: %s didn't match with provided ClientID", pe.ConnectionID) + } + + return nil +} + +// ValidateChannel validates channel id in provided pathend +func (c *Config) ValidateChannel(chain *relayer.Chain, height int64, pe *relayer.PathEnd) error { + if err := pe.Vchan(); err != nil { + return err + } + + channel, err := chain.QueryChannel(height) + if err != nil { + return err + } + + for _, connection := range channel.Channel.ConnectionHops { + if connection == pe.ConnectionID { + return nil + } + } + + return fmt.Errorf("connectionID of channel: %s didn't match with provided ConnectionID", pe.ChannelID) +} diff --git a/cmd/paths.go b/cmd/paths.go index bd7e73a0c..54c8f66dc 100644 --- a/cmd/paths.go +++ b/cmd/paths.go @@ -155,6 +155,9 @@ $ %s pth gen ibc-0 ibc-1 demo-path --unordered false --version ics20-2`, appName path.GenSrcConnID() path.GenSrcChanID() path.GenDstChanID() + if err = config.ValidatePath(path); err != nil { + return err + } if err = config.Paths.Add(pth, path); err != nil { return err } @@ -166,6 +169,9 @@ $ %s pth gen ibc-0 ibc-1 demo-path --unordered false --version ics20-2`, appName path.GenSrcConnID() path.GenSrcChanID() path.GenDstChanID() + if err = config.ValidatePath(path); err != nil { + return err + } if err = config.Paths.Add(pth, path); err != nil { return err } @@ -177,6 +183,9 @@ $ %s pth gen ibc-0 ibc-1 demo-path --unordered false --version ics20-2`, appName path.GenSrcConnID() path.GenSrcChanID() path.GenDstChanID() + if err = config.ValidatePath(path); err != nil { + return err + } if err = config.Paths.Add(pth, path); err != nil { return err } @@ -225,6 +234,9 @@ $ %s pth gen ibc-0 ibc-1 demo-path --unordered false --version ics20-2`, appName path.GenDstConnID() path.GenSrcChanID() path.GenDstChanID() + if err = config.ValidatePath(path); err != nil { + return err + } if err = config.Paths.Add(pth, path); err != nil { return err } @@ -236,6 +248,9 @@ $ %s pth gen ibc-0 ibc-1 demo-path --unordered false --version ics20-2`, appName path.GenDstConnID() path.GenSrcChanID() path.GenDstChanID() + if err = config.ValidatePath(path); err != nil { + return err + } if err = config.Paths.Add(pth, path); err != nil { return err } @@ -289,6 +304,9 @@ $ %s pth gen ibc-0 ibc-1 demo-path --unordered false --version ics20-2`, appName path.GenSrcChanID() path.GenDstChanID() } + if err = config.ValidatePath(path); err != nil { + return err + } if err = config.Paths.Add(pth, path); err != nil { return err } @@ -297,6 +315,9 @@ $ %s pth gen ibc-0 ibc-1 demo-path --unordered false --version ics20-2`, appName default: path.GenSrcChanID() path.GenDstChanID() + if err = config.ValidatePath(path); err != nil { + return err + } if err = config.Paths.Add(pth, path); err != nil { return err } @@ -497,6 +518,10 @@ func fileInputPathAdd(file, name string) (cfg *Config, err error) { return nil, err } + if err = config.ValidatePath(p); err != nil { + return nil, err + } + if err = config.Paths.Add(name, p); err != nil { return nil, err } @@ -631,6 +656,10 @@ func userInputPathAdd(src, dst, name string) (*Config, error) { return nil, err } + if err = config.ValidatePath(path); err != nil { + return nil, err + } + if err = config.Paths.Add(name, path); err != nil { return nil, err } diff --git a/relayer/path.go b/relayer/path.go index e56121a4d..1c51e1d02 100644 --- a/relayer/path.go +++ b/relayer/path.go @@ -49,7 +49,6 @@ func (p Paths) MustGet(name string) *Path { // Add adds a path by its name func (p Paths) Add(name string, path *Path) error { - // TODO: Do bottomo up validation of path if _, found := p[name]; found { return fmt.Errorf("path with name %s already exists", name) }