/
validate.go
162 lines (152 loc) · 5.72 KB
/
validate.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
package offchainreporting
import (
"time"
"github.com/multiformats/go-multiaddr"
"github.com/pelletier/go-toml"
"github.com/pkg/errors"
"github.com/SeerLink/seerlink/core/services/job"
"github.com/SeerLink/seerlink/core/services/pipeline"
"github.com/SeerLink/seerlink/core/store/orm"
"github.com/SeerLink/libocr/offchainreporting"
"github.com/SeerLink/libocr/offchainreporting/types"
"go.uber.org/multierr"
)
// ValidatedOracleSpecToml validates an oracle spec that came from TOML
func ValidatedOracleSpecToml(config *orm.Config, tomlString string) (job.SpecDB, error) {
var specDB = job.SpecDB{
Pipeline: *pipeline.NewTaskDAG(),
}
var spec job.OffchainReportingOracleSpec
tree, err := toml.Load(tomlString)
if err != nil {
return specDB, errors.Wrap(err, "toml error on load")
}
// Note this validates all the fields which implement an UnmarshalText
// i.e. TransmitterAddress, PeerID...
err = tree.Unmarshal(&spec)
if err != nil {
return specDB, errors.Wrap(err, "toml unmarshal error on spec")
}
err = tree.Unmarshal(&specDB)
if err != nil {
return specDB, errors.Wrap(err, "toml unmarshal error on specDB")
}
specDB.OffchainreportingOracleSpec = &spec
// TODO(#175801426): upstream a way to check for undecoded keys in go-toml
// TODO(#175801038): upstream support for time.Duration defaults in go-toml
if specDB.Type != job.OffchainReporting {
return specDB, errors.Errorf("the only supported type is currently 'offchainreporting', got %s", specDB.Type)
}
if specDB.SchemaVersion != uint32(1) {
return specDB, errors.Errorf("the only supported schema version is currently 1, got %v", specDB.SchemaVersion)
}
if !tree.Has("isBootstrapPeer") {
return specDB, errors.New("isBootstrapPeer is not defined")
}
for i := range spec.P2PBootstrapPeers {
if _, err := multiaddr.NewMultiaddr(spec.P2PBootstrapPeers[i]); err != nil {
return specDB, errors.Wrapf(err, "p2p bootstrap peer %v is invalid", spec.P2PBootstrapPeers[i])
}
}
if spec.IsBootstrapPeer {
if err := validateBootstrapSpec(tree, specDB); err != nil {
return specDB, err
}
} else if err := validateNonBootstrapSpec(tree, config, specDB); err != nil {
return specDB, err
}
if err := validateTimingParameters(config, spec); err != nil {
return specDB, err
}
return specDB, nil
}
// Parameters that must be explicitly set by the operator.
var (
// Common to both bootstrap and non-boostrap
params = map[string]struct{}{
"type": {},
"schemaVersion": {},
"contractAddress": {},
"isBootstrapPeer": {},
}
// Boostrap and non-bootstrap parameters
// are mutually exclusive.
bootstrapParams = map[string]struct{}{}
nonBootstrapParams = map[string]struct{}{
"observationSource": {},
}
)
func cloneSet(in map[string]struct{}) map[string]struct{} {
out := make(map[string]struct{})
for k, v := range in {
out[k] = v
}
return out
}
func validateTimingParameters(config *orm.Config, spec job.OffchainReportingOracleSpec) error {
lc := types.LocalConfig{
BlockchainTimeout: config.OCRBlockchainTimeout(time.Duration(spec.BlockchainTimeout)),
ContractConfigConfirmations: config.OCRContractConfirmations(spec.ContractConfigConfirmations),
ContractConfigTrackerPollInterval: config.OCRContractPollInterval(time.Duration(spec.ContractConfigTrackerPollInterval)),
ContractConfigTrackerSubscribeInterval: config.OCRContractSubscribeInterval(time.Duration(spec.ContractConfigTrackerSubscribeInterval)),
ContractTransmitterTransmitTimeout: config.OCRContractTransmitterTransmitTimeout(),
DatabaseTimeout: config.OCRDatabaseTimeout(),
DataSourceTimeout: config.OCRObservationTimeout(time.Duration(spec.ObservationTimeout)),
}
if config.Dev() {
lc.DevelopmentMode = types.EnableDangerousDevelopmentMode
}
return offchainreporting.SanityCheckLocalConfig(lc)
}
func validateBootstrapSpec(tree *toml.Tree, spec job.SpecDB) error {
expected, notExpected := cloneSet(params), cloneSet(nonBootstrapParams)
for k := range bootstrapParams {
expected[k] = struct{}{}
}
if err := validateExplicitlySetKeys(tree, expected, notExpected, "bootstrap"); err != nil {
return err
}
return nil
}
func validateNonBootstrapSpec(tree *toml.Tree, config *orm.Config, spec job.SpecDB) error {
expected, notExpected := cloneSet(params), cloneSet(bootstrapParams)
for k := range nonBootstrapParams {
expected[k] = struct{}{}
}
if err := validateExplicitlySetKeys(tree, expected, notExpected, "non-bootstrap"); err != nil {
return err
}
if spec.Pipeline.DOTSource == "" {
return errors.New("no pipeline specified")
}
observationTimeout := config.OCRObservationTimeout(time.Duration(spec.OffchainreportingOracleSpec.ObservationTimeout))
if time.Duration(spec.MaxTaskDuration) > observationTimeout {
return errors.Errorf("max task duration must be < observation timeout")
}
tasks, err := spec.Pipeline.TasksInDependencyOrder()
if err != nil {
return errors.Wrap(err, "invalid observation source")
}
for _, task := range tasks {
timeout, set := task.TaskTimeout()
if set && timeout > observationTimeout {
return errors.Errorf("individual max task duration must be < observation timeout")
}
}
return nil
}
func validateExplicitlySetKeys(tree *toml.Tree, expected map[string]struct{}, notExpected map[string]struct{}, peerType string) error {
var err error
// top level keys only
for _, k := range tree.Keys() {
// TODO(#175801577): upstream a way to check for children in go-toml
if _, ok := notExpected[k]; ok {
err = multierr.Append(err, errors.Errorf("unrecognised key for %s peer: %s", peerType, k))
}
delete(expected, k)
}
for missing := range expected {
err = multierr.Append(err, errors.Errorf("missing required key %s", missing))
}
return err
}