-
Notifications
You must be signed in to change notification settings - Fork 1.7k
/
validate.go
168 lines (153 loc) · 5.36 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
163
164
165
166
167
168
package ocr
import (
"math/big"
"time"
"github.com/lib/pq"
"github.com/multiformats/go-multiaddr"
"github.com/pelletier/go-toml"
"github.com/pkg/errors"
"github.com/smartcontractkit/libocr/offchainreporting"
"github.com/smartcontractkit/chainlink/v2/core/chains/evm"
config2 "github.com/smartcontractkit/chainlink/v2/core/chains/evm/config"
"github.com/smartcontractkit/chainlink/v2/core/config"
"github.com/smartcontractkit/chainlink/v2/core/services/job"
"github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/ethkey"
"github.com/smartcontractkit/chainlink/v2/core/services/keystore/keys/p2pkey"
"github.com/smartcontractkit/chainlink/v2/core/services/ocrcommon"
)
type ValidationConfig interface {
ChainType() config.ChainType
OCRBlockchainTimeout() time.Duration
OCRContractConfirmations() uint16
OCRContractPollInterval() time.Duration
OCRContractSubscribeInterval() time.Duration
OCRContractTransmitterTransmitTimeout() time.Duration
OCRDatabaseTimeout() time.Duration
OCRKeyBundleID() (string, error)
OCRObservationGracePeriod() time.Duration
OCRObservationTimeout() time.Duration
OCRTransmitterAddress() (ethkey.EIP55Address, error)
P2PPeerID() p2pkey.PeerID
OCRCaptureEATelemetry() bool
OCRDevelopmentMode() bool
}
// ValidatedOracleSpecToml validates an oracle spec that came from TOML
func ValidatedOracleSpecToml(chainSet evm.ChainSet, tomlString string) (job.Job, error) {
return ValidatedOracleSpecTomlCfg(func(id *big.Int) (config2.ChainScopedConfig, error) {
c, err := chainSet.Get(id)
if err != nil {
return nil, err
}
return c.Config(), nil
}, tomlString)
}
func ValidatedOracleSpecTomlCfg(configFn func(id *big.Int) (config2.ChainScopedConfig, error), tomlString string) (job.Job, error) {
var jb = job.Job{}
var spec job.OCROracleSpec
tree, err := toml.Load(tomlString)
if err != nil {
return jb, 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 jb, errors.Wrap(err, "toml unmarshal error on spec")
}
err = tree.Unmarshal(&jb)
if err != nil {
return jb, errors.Wrap(err, "toml unmarshal error on job")
}
jb.OCROracleSpec = &spec
if jb.OCROracleSpec.P2PV2Bootstrappers == nil {
// Empty but non-null, field is non-nullable.
jb.OCROracleSpec.P2PV2Bootstrappers = pq.StringArray{}
}
if jb.Type != job.OffchainReporting {
return jb, errors.Errorf("the only supported type is currently 'offchainreporting', got %s", jb.Type)
}
if !tree.Has("isBootstrapPeer") {
return jb, errors.New("isBootstrapPeer is not defined")
}
for i := range spec.P2PBootstrapPeers {
if _, err = multiaddr.NewMultiaddr(spec.P2PBootstrapPeers[i]); err != nil {
return jb, errors.Wrapf(err, "p2p bootstrap peer %v is invalid", spec.P2PBootstrapPeers[i])
}
}
if len(spec.P2PV2Bootstrappers) > 0 {
_, err = ocrcommon.ParseBootstrapPeers(spec.P2PV2Bootstrappers)
if err != nil {
return jb, err
}
}
cfg, err := configFn(jb.OCROracleSpec.EVMChainID.ToInt())
if err != nil {
return jb, err
}
if spec.IsBootstrapPeer {
if err := validateBootstrapSpec(tree, jb); err != nil {
return jb, err
}
} else if err := validateNonBootstrapSpec(tree, cfg, jb); err != nil {
return jb, err
}
if err := validateTimingParameters(cfg, spec); err != nil {
return jb, err
}
return jb, 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 validateTimingParameters(cfg ValidationConfig, spec job.OCROracleSpec) error {
lc := toLocalConfig(cfg, spec)
return errors.Wrap(offchainreporting.SanityCheckLocalConfig(lc), "offchainreporting.SanityCheckLocalConfig failed")
}
func validateBootstrapSpec(tree *toml.Tree, spec job.Job) error {
expected, notExpected := ocrcommon.CloneSet(params), ocrcommon.CloneSet(nonBootstrapParams)
for k := range bootstrapParams {
expected[k] = struct{}{}
}
return ocrcommon.ValidateExplicitlySetKeys(tree, expected, notExpected, "bootstrap")
}
func validateNonBootstrapSpec(tree *toml.Tree, config ValidationConfig, spec job.Job) error {
expected, notExpected := ocrcommon.CloneSet(params), ocrcommon.CloneSet(bootstrapParams)
for k := range nonBootstrapParams {
expected[k] = struct{}{}
}
if err := ocrcommon.ValidateExplicitlySetKeys(tree, expected, notExpected, "non-bootstrap"); err != nil {
return err
}
if spec.Pipeline.Source == "" {
return errors.New("no pipeline specified")
}
var observationTimeout time.Duration
if spec.OCROracleSpec.ObservationTimeout != 0 {
observationTimeout = spec.OCROracleSpec.ObservationTimeout.Duration()
} else {
observationTimeout = config.OCRObservationTimeout()
}
if time.Duration(spec.MaxTaskDuration) > observationTimeout {
return errors.Errorf("max task duration must be < observation timeout")
}
for _, task := range spec.Pipeline.Tasks {
timeout, set := task.TaskTimeout()
if set && timeout > observationTimeout {
return errors.Errorf("individual max task duration must be < observation timeout")
}
}
return nil
}