code.vegaprotocol.io/vega@v0.79.0/core/datasource/spec/spec.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 spec
    17  
    18  import (
    19  	"strconv"
    20  	"strings"
    21  	"time"
    22  
    23  	"code.vegaprotocol.io/vega/core/datasource"
    24  	"code.vegaprotocol.io/vega/core/datasource/common"
    25  	datapb "code.vegaprotocol.io/vega/protos/vega/data/v1"
    26  )
    27  
    28  type SpecID string
    29  
    30  type Spec struct {
    31  	// id is a unique identifier for the Spec
    32  	id SpecID
    33  
    34  	// signers list all the authorized public keys from where a Data can
    35  	// come from.
    36  	signers map[string]struct{}
    37  
    38  	// any time triggers on the spec
    39  	triggers common.InternalTimeTriggers
    40  
    41  	// filters holds all the expected property keys with the conditions they
    42  	// should match.
    43  	filters common.Filters
    44  	// OriginalSpec is the protobuf description of Spec
    45  	OriginalSpec *datasource.Spec
    46  }
    47  
    48  // New builds a new Spec from a common.Spec (currently uses one level below - common.ExternalDataSourceSpec) in a form that
    49  // suits the processing of the filters.
    50  // Spec allows the existence of one and only one.
    51  // Currently VEGA network utilises internal triggers in the oracle function path, even though
    52  // the oracles are treated as external data sources.
    53  // For this reason this function checks if the provided external type of data source definition
    54  // contains a key name that indicates a builtin type of logic
    55  // and if the given data source definition is an internal type of data source, for more context refer to
    56  // https://github.com/vegaprotocol/specs/blob/master/protocol/0048-DSRI-data_source_internal.md#13-vega-time-changed
    57  func New(originalSpec datasource.Spec) (*Spec, error) {
    58  	filtersFromSpec := []*common.SpecFilter{}
    59  	signersFromSpec := []*common.Signer{}
    60  	var triggersFromSpec common.InternalTimeTriggers
    61  
    62  	isExtType := false
    63  	var err error
    64  	// if originalSpec != nil {
    65  	if originalSpec.Data != nil {
    66  		filtersFromSpec = originalSpec.Data.GetFilters()
    67  		isExtType, err = originalSpec.Data.IsExternal()
    68  		if err != nil {
    69  			return nil, err
    70  		}
    71  	}
    72  	//}
    73  
    74  	builtInKey := false
    75  	for _, f := range filtersFromSpec {
    76  		if isExtType {
    77  			if strings.HasPrefix(f.Key.Name, "vegaprotocol.builtin") && f.Key.Type == datapb.PropertyKey_TYPE_TIMESTAMP {
    78  				builtInKey = true
    79  			}
    80  		}
    81  	}
    82  
    83  	builtInTrigger := false
    84  	for _, f := range filtersFromSpec {
    85  		if strings.HasPrefix(f.Key.Name, "vegaprotocol.builtin.timetrigger") && f.Key.Type == datapb.PropertyKey_TYPE_TIMESTAMP {
    86  			builtInTrigger = true
    87  		}
    88  	}
    89  
    90  	typedFilters, err := common.NewFilters(filtersFromSpec, isExtType)
    91  	if err != nil {
    92  		return nil, err
    93  	}
    94  	// We check if the filters list is empty in the proposal submission step.
    95  	// We do not need to double that logic here.
    96  
    97  	signers := map[string]struct{}{}
    98  	if !builtInTrigger && !builtInKey && isExtType {
    99  		// if originalSpec != nil {
   100  		if originalSpec.Data != nil {
   101  			src := *originalSpec.Data
   102  
   103  			signersFromSpec = src.GetSigners()
   104  		}
   105  		//}
   106  
   107  		// We check if the signers list is empty h in the proposal submission step.
   108  		// We do not need to duble that logic here.
   109  
   110  		for _, pk := range signersFromSpec {
   111  			signers[pk.String()] = struct{}{}
   112  		}
   113  	}
   114  
   115  	if builtInTrigger {
   116  		triggersFromSpec = originalSpec.Data.GetTimeTriggers()
   117  	}
   118  
   119  	os := &Spec{
   120  		id:           SpecID(originalSpec.ID),
   121  		signers:      signers,
   122  		filters:      typedFilters,
   123  		triggers:     triggersFromSpec,
   124  		OriginalSpec: &originalSpec,
   125  	}
   126  
   127  	return os, nil
   128  }
   129  
   130  func (s Spec) EnsureBoundableProperty(property string, propType datapb.PropertyKey_Type) error {
   131  	return s.filters.EnsureBoundableProperty(property, propType)
   132  }
   133  
   134  func isInternalData(data common.Data) bool {
   135  	for k := range data.Data {
   136  		if !strings.HasPrefix(k, BuiltinPrefix) {
   137  			return false
   138  		}
   139  	}
   140  
   141  	return true
   142  }
   143  
   144  func isInternalTimeTrigger(data common.Data) (bool, time.Time) {
   145  	for k, v := range data.Data {
   146  		if k == BuiltinTimeTrigger {
   147  			// convert string to time
   148  			if t, err := strconv.ParseInt(v, 10, 0); err == nil {
   149  				return true, time.Unix(t, 0)
   150  			}
   151  		}
   152  	}
   153  	return false, time.Time{}
   154  }
   155  
   156  // MatchSigners tries to match the public keys from the provided Data object with the ones
   157  // present in the Spec.
   158  func (s *Spec) MatchSigners(data common.Data) bool {
   159  	return containsRequiredSigners(data.Signers, s.signers)
   160  }
   161  
   162  // MatchData indicates if a given Data matches the spec or not.
   163  func (s *Spec) MatchData(data common.Data) (bool, error) {
   164  	// if the data contains the internal source timestamp key, and only that key,
   165  	// then we do not need to verify the public keys as there will not be one
   166  
   167  	if !isInternalData(data) && !containsRequiredSigners(data.Signers, s.signers) {
   168  		return false, nil
   169  	}
   170  
   171  	// Don't broadcast ethcall data based unless it's 'EthKey' matches
   172  	// (which is currently the SpecID - see comment on the datasource.common.Data struct)
   173  	if data.EthKey != "" && data.EthKey != string(s.id) {
   174  		return false, nil
   175  	}
   176  
   177  	// if it is internal time data and we have a time-trigger check that we're past it
   178  	if ok, tt := isInternalTimeTrigger(data); ok && s.triggers[0] != nil {
   179  		if !s.triggers.IsTriggered(tt) {
   180  			return false, nil
   181  		}
   182  	}
   183  
   184  	return s.filters.Match(data.Data)
   185  }
   186  
   187  // containsRequiredSigners verifies if all the public keys in the Data
   188  // are within the list of currently authorized by the Spec.
   189  func containsRequiredSigners(dataSigners []*common.Signer, authPks map[string]struct{}) bool {
   190  	for _, signer := range dataSigners {
   191  		if _, ok := authPks[signer.String()]; !ok {
   192  			return false
   193  		}
   194  	}
   195  	return true
   196  }