code.vegaprotocol.io/vega@v0.79.0/wallet/api/admin_import_network.go (about)

     1  // Copyright (C) 2023 Gobalsky Labs Limited
     2  //
     3  // This program is free software: you can redistribute it and/or modify
     4  // it under the terms of the GNU Affero General Public License as
     5  // published by the Free Software Foundation, either version 3 of the
     6  // License, or (at your option) any later version.
     7  //
     8  // This program is distributed in the hope that it will be useful,
     9  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    10  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    11  // GNU Affero General Public License for more details.
    12  //
    13  // You should have received a copy of the GNU Affero General Public License
    14  // along with this program.  If not, see <http://www.gnu.org/licenses/>.
    15  
    16  package api
    17  
    18  import (
    19  	"context"
    20  	"errors"
    21  	"fmt"
    22  	"regexp"
    23  	"strings"
    24  
    25  	vgfs "code.vegaprotocol.io/vega/libs/fs"
    26  	"code.vegaprotocol.io/vega/libs/jsonrpc"
    27  	"code.vegaprotocol.io/vega/paths"
    28  	"code.vegaprotocol.io/vega/wallet/network"
    29  
    30  	"github.com/mitchellh/mapstructure"
    31  )
    32  
    33  var (
    34  	ErrInvalidNetworkSource = errors.New("invalid network source")
    35  
    36  	githubToml = regexp.MustCompile(`(http[s]?://)(github\.com).*(.toml)$`)
    37  )
    38  
    39  type AdminImportNetworkParams struct {
    40  	Name      string `json:"name"`
    41  	URL       string `json:"url"`
    42  	Overwrite bool   `json:"overwrite"`
    43  }
    44  
    45  type AdminImportNetworkResult struct {
    46  	Name string `json:"name"`
    47  }
    48  
    49  type AdminImportNetwork struct {
    50  	networkStore NetworkStore
    51  }
    52  
    53  type Reader func(uri string, net interface{}) error
    54  
    55  type Readers struct {
    56  	ReadFromFile Reader
    57  	ReadFromURL  Reader
    58  }
    59  
    60  func NewReaders() Readers {
    61  	return Readers{
    62  		ReadFromFile: paths.ReadStructuredFile,
    63  		ReadFromURL:  paths.FetchStructuredFile,
    64  	}
    65  }
    66  
    67  func (h *AdminImportNetwork) Handle(_ context.Context, rawParams jsonrpc.Params) (jsonrpc.Result, *jsonrpc.ErrorDetails) {
    68  	params, err := validateImportNetworkParams(rawParams)
    69  	if err != nil {
    70  		return nil, InvalidParams(err)
    71  	}
    72  
    73  	net, err := readImportNetworkSource(params)
    74  	if errors.Is(err, ErrInvalidNetworkSource) {
    75  		return nil, InvalidParams(err)
    76  	}
    77  	if err != nil {
    78  		return nil, InternalError(err)
    79  	}
    80  
    81  	if len(params.Name) != 0 {
    82  		net.Name = params.Name
    83  	}
    84  
    85  	if len(net.Name) == 0 {
    86  		return nil, InvalidParams(ErrNetworkNameIsRequired)
    87  	}
    88  
    89  	if exist, err := h.networkStore.NetworkExists(net.Name); err != nil {
    90  		return nil, InternalError(fmt.Errorf("could not verify the network existence: %w", err))
    91  	} else if exist && !params.Overwrite {
    92  		return nil, InvalidParams(ErrNetworkAlreadyExists)
    93  	}
    94  
    95  	if err := h.networkStore.SaveNetwork(net); err != nil {
    96  		return nil, InternalError(err)
    97  	}
    98  
    99  	return AdminImportNetworkResult{
   100  		Name: net.Name,
   101  	}, nil
   102  }
   103  
   104  // urlPreCheck looks for basic user errors in the given URL. For example if a github
   105  // URL is supplied instead of a link to the raw-file-contents.
   106  func urlPreCheck(url string) error {
   107  	m := githubToml.FindString(url)
   108  	if len(m) == 0 {
   109  		return nil
   110  	}
   111  
   112  	// make a suggestion
   113  	suggestion := strings.Replace(url, "github.com", "raw.githubusercontent.com", 1)
   114  	suggestion = strings.Replace(suggestion, "/blob/", "/", 1)
   115  	return fmt.Errorf("this URL leads to a Github page and not the network configuration, did you mean %s", suggestion)
   116  }
   117  
   118  func validateImportNetworkParams(rawParams jsonrpc.Params) (AdminImportNetworkParams, error) {
   119  	if rawParams == nil {
   120  		return AdminImportNetworkParams{}, ErrParamsRequired
   121  	}
   122  
   123  	params := AdminImportNetworkParams{}
   124  	if err := mapstructure.Decode(rawParams, &params); err != nil {
   125  		return AdminImportNetworkParams{}, ErrParamsDoNotMatch
   126  	}
   127  
   128  	if params.URL == "" {
   129  		return AdminImportNetworkParams{}, ErrNetworkSourceIsRequired
   130  	}
   131  
   132  	if err := urlPreCheck(params.URL); err != nil {
   133  		return AdminImportNetworkParams{}, err
   134  	}
   135  	return params, nil
   136  }
   137  
   138  // readImportNetworkSource parse the network file given by the source in the params
   139  // into a `Network` which can then be saved to disk.
   140  func readImportNetworkSource(params AdminImportNetworkParams) (*network.Network, error) {
   141  	net := &network.Network{}
   142  	rs := NewReaders()
   143  
   144  	s, filePath, isFile := strings.Cut(params.URL, FileSchemePrefix)
   145  	if isFile && len(s) == 0 {
   146  		exists, err := vgfs.FileExists(filePath)
   147  		if err != nil {
   148  			return nil, fmt.Errorf("could not check file's existence at %q: %w", filePath, err)
   149  		}
   150  		if !exists {
   151  			return nil, fmt.Errorf("the network source file does not exist: %w", ErrInvalidNetworkSource)
   152  		}
   153  
   154  		err = rs.ReadFromFile(filePath, net)
   155  		if err == paths.ErrEmptyFile {
   156  			return nil, fmt.Errorf("network source file is empty: %w", ErrInvalidNetworkSource)
   157  		}
   158  		if err != nil {
   159  			return nil, fmt.Errorf("could not read the network configuration at %q: %w", filePath, err)
   160  		}
   161  		return net, nil
   162  	}
   163  
   164  	if len(params.URL) != 0 {
   165  		err := rs.ReadFromURL(params.URL, net)
   166  		if err == paths.ErrEmptyResponse {
   167  			return nil, fmt.Errorf("network source url points to an empty file: %w", ErrInvalidNetworkSource)
   168  		}
   169  		if err != nil {
   170  			return nil, fmt.Errorf("could not fetch the network configuration from %q: %w", params.URL, err)
   171  		}
   172  		return net, nil
   173  	}
   174  
   175  	return net, nil
   176  }
   177  
   178  func NewAdminImportNetwork(
   179  	networkStore NetworkStore,
   180  ) *AdminImportNetwork {
   181  	return &AdminImportNetwork{
   182  		networkStore: networkStore,
   183  	}
   184  }