code.vegaprotocol.io/vega@v0.79.0/core/notary/notary.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 notary 17 18 import ( 19 "context" 20 "sort" 21 "strings" 22 "sync" 23 "time" 24 25 "code.vegaprotocol.io/vega/core/events" 26 "code.vegaprotocol.io/vega/core/txn" 27 "code.vegaprotocol.io/vega/libs/num" 28 "code.vegaprotocol.io/vega/logging" 29 commandspb "code.vegaprotocol.io/vega/protos/vega/commands/v1" 30 31 "github.com/cenkalti/backoff" 32 "github.com/golang/protobuf/proto" 33 "github.com/pkg/errors" 34 "golang.org/x/exp/maps" 35 ) 36 37 // by default all validators needs to sign. 38 var defaultValidatorsVoteRequired = num.MustDecimalFromString("1.0") 39 40 var ( 41 ErrAggregateSigAlreadyStartedForResource = errors.New("aggregate signature already started for resource") 42 ErrUnknownResourceID = errors.New("unknown resource ID") 43 ErrNotAValidatorSignature = errors.New("not a validator signature") 44 ) 45 46 // ValidatorTopology... 47 // 48 //go:generate go run github.com/golang/mock/mockgen -destination mocks/validator_topology_mock.go -package mocks code.vegaprotocol.io/vega/core/notary ValidatorTopology 49 type ValidatorTopology interface { 50 IsValidator() bool 51 IsValidatorVegaPubKey(string) bool 52 IsTendermintValidator(string) bool 53 SelfVegaPubKey() string 54 Len() int 55 } 56 57 // Broker needs no mocks. 58 type Broker interface { 59 Send(event events.Event) 60 SendBatch(events []events.Event) 61 } 62 63 //go:generate go run github.com/golang/mock/mockgen -destination mocks/commander_mock.go -package mocks code.vegaprotocol.io/vega/core/notary Commander 64 type Commander interface { 65 Command(ctx context.Context, cmd txn.Command, payload proto.Message, f func(string, error), bo *backoff.ExponentialBackOff) 66 CommandSync(ctx context.Context, cmd txn.Command, payload proto.Message, f func(string, error), bo *backoff.ExponentialBackOff) 67 } 68 69 // Notary will aggregate all signatures of a node for 70 // a specific Command 71 // e.g: asset withdrawal, asset allowlisting, etc. 72 type Notary struct { 73 cfg Config 74 log *logging.Logger 75 76 // resource to be signed -> signatures 77 sigs map[idKind]map[nodeSig]struct{} 78 pendingSignatures map[idKind]struct{} 79 retries *txTracker 80 top ValidatorTopology 81 cmd Commander 82 broker Broker 83 84 validatorVotesRequired num.Decimal 85 } 86 87 type idKind struct { 88 id string 89 kind commandspb.NodeSignatureKind 90 } 91 92 // nodeSig is a pair of a node and it signature. 93 type nodeSig struct { 94 node string 95 sig string 96 } 97 98 func New( 99 log *logging.Logger, 100 cfg Config, 101 top ValidatorTopology, 102 broker Broker, 103 cmd Commander, 104 ) (n *Notary) { 105 log.SetLevel(cfg.Level.Get()) 106 log = log.Named(namedLogger) 107 return &Notary{ 108 cfg: cfg, 109 log: log, 110 sigs: map[idKind]map[nodeSig]struct{}{}, 111 pendingSignatures: map[idKind]struct{}{}, 112 top: top, 113 broker: broker, 114 cmd: cmd, 115 validatorVotesRequired: defaultValidatorsVoteRequired, 116 retries: &txTracker{ 117 txs: map[idKind]*signatureTime{}, 118 }, 119 } 120 } 121 122 func (n *Notary) OnDefaultValidatorsVoteRequiredUpdate(ctx context.Context, d num.Decimal) error { 123 n.validatorVotesRequired = d 124 return nil 125 } 126 127 // ReloadConf updates the internal configuration. 128 func (n *Notary) ReloadConf(cfg Config) { 129 n.log.Info("reloading configuration") 130 if n.log.GetLevel() != cfg.Level.Get() { 131 n.log.Info("updating log level", 132 logging.String("old", n.log.GetLevel().String()), 133 logging.String("new", cfg.Level.String()), 134 ) 135 n.log.SetLevel(cfg.Level.Get()) 136 } 137 138 n.cfg = cfg 139 } 140 141 // StartAggregate will register a new signature to be 142 // sent for a validator, or just ignore the signature and 143 // start aggregating signature for now validators, 144 // nil for the signature is OK for non-validators. 145 func (n *Notary) StartAggregate( 146 resource string, 147 kind commandspb.NodeSignatureKind, 148 signature []byte, 149 ) { 150 // start aggregating for the resource 151 idkind := idKind{resource, kind} 152 if _, ok := n.sigs[idkind]; ok { 153 n.log.Panic("aggregate already started for a resource", 154 logging.String("resource", resource), 155 logging.String("signature-kind", kind.String()), 156 ) 157 } 158 n.sigs[idkind] = map[nodeSig]struct{}{} 159 n.pendingSignatures[idkind] = struct{}{} 160 161 // we are not a validator, then just return, job 162 // done from here 163 if !n.top.IsValidator() { 164 return 165 } 166 167 // now let's just add the transaction to the retry list 168 // no need to send the signature, it will be done on next onTick 169 n.retries.Add(idkind, signature) 170 } 171 172 func (n *Notary) RegisterSignature( 173 ctx context.Context, 174 pubKey string, 175 ns commandspb.NodeSignature, 176 ) error { 177 idkind := idKind{ns.Id, ns.Kind} 178 sigs, ok := n.sigs[idkind] 179 if !ok { 180 return ErrUnknownResourceID 181 } 182 183 // not a validator signature 184 if !n.top.IsValidatorVegaPubKey(pubKey) { 185 return ErrNotAValidatorSignature 186 } 187 188 // if this is our own signature, remove it from the retries thing 189 if strings.EqualFold(pubKey, n.top.SelfVegaPubKey()) { 190 n.retries.Ack(idkind) 191 } 192 193 sigs[nodeSig{pubKey, string(ns.Sig)}] = struct{}{} 194 195 signatures, ok := n.IsSigned(ctx, ns.Id, ns.Kind) 196 if ok { 197 // remove from the pending 198 delete(n.pendingSignatures, idkind) 199 // enough signature to reach the threshold have been received, let's send them to the 200 // the api 201 n.sendSignatureEvents(ctx, signatures) 202 } 203 return nil 204 } 205 206 func (n *Notary) IsSigned( 207 ctx context.Context, 208 resource string, 209 kind commandspb.NodeSignatureKind, 210 ) ([]commandspb.NodeSignature, bool) { 211 idkind := idKind{resource, kind} 212 213 // early exit if we don't have enough sig anyway 214 if !n.votePassed(len(n.sigs[idkind]), n.top.Len()) { 215 return nil, false 216 } 217 218 // aggregate node sig 219 sig := map[string]struct{}{} 220 out := []commandspb.NodeSignature{} 221 222 for k := range n.sigs[idkind] { 223 // is node sig is part of the registered nodes, and is a tendermint validator 224 // add it to the map 225 // we may have a node are validators but with a lesser status sending in votes 226 if n.top.IsTendermintValidator(k.node) { 227 sig[k.node] = struct{}{} 228 out = append(out, commandspb.NodeSignature{ 229 Id: resource, 230 Kind: kind, 231 Sig: []byte(k.sig), 232 }) 233 } 234 } 235 236 // now we check the number of required node sigs 237 if n.votePassed(len(sig), n.top.Len()) { 238 sort.Slice(out, func(i, j int) bool { 239 return string(out[i].Sig) < string(out[j].Sig) 240 }) 241 return out, true 242 } 243 244 return nil, false 245 } 246 247 // onTick is only use to trigger resending transaction. 248 func (n *Notary) OnTick(ctx context.Context, t time.Time) { 249 toRetry := n.retries.getRetries(t) 250 for k, v := range toRetry { 251 n.send(k.id, k.kind, v.signature) 252 } 253 254 pendings := maps.Keys(n.pendingSignatures) 255 sort.Slice(pendings, func(i, j int) bool { 256 return pendings[i].id < pendings[j].id 257 }) 258 259 for _, v := range pendings { 260 if signatures, ok := n.IsSigned(ctx, v.id, v.kind); ok { 261 // remove from the pending 262 delete(n.pendingSignatures, v) 263 // enough signature to reach the threshold have been received, let's send them to the 264 // the api 265 n.sendSignatureEvents(ctx, signatures) 266 } 267 } 268 } 269 270 func (n *Notary) send(id string, kind commandspb.NodeSignatureKind, signature []byte) { 271 nsig := &commandspb.NodeSignature{Id: id, Sig: signature, Kind: kind} 272 // we send a background context here because the actual context is ignore by the commander 273 // which use a timeout of 5 seconds, this API may need to be addressed another day 274 n.cmd.Command(context.Background(), txn.NodeSignatureCommand, nsig, func(_ string, err error) { 275 // just a log is enough here, the transaction will be retried 276 // later 277 n.log.Error("could not send the transaction to tendermint", logging.Error(err)) 278 }, nil) 279 } 280 281 func (n *Notary) votePassed(votesCount, topLen int) bool { 282 return num.DecimalFromInt64(int64(votesCount)).Div(num.DecimalFromInt64(int64(topLen))).GreaterThanOrEqual(n.validatorVotesRequired) 283 } 284 285 func (n *Notary) sendSignatureEvents(ctx context.Context, signatures []commandspb.NodeSignature) { 286 evts := make([]events.Event, 0, len(signatures)) 287 for _, ns := range signatures { 288 evts = append(evts, events.NewNodeSignatureEvent(ctx, ns)) 289 } 290 n.broker.SendBatch(evts) 291 } 292 293 // txTracker is a simple data structure 294 // to keep track of what transactions have been 295 // sent by this notary, and if a retry is necessary. 296 type txTracker struct { 297 mu sync.Mutex 298 // idKind -> time the tx was sent 299 txs map[idKind]*signatureTime 300 } 301 302 type signatureTime struct { 303 signature []byte 304 time time.Time 305 } 306 307 func (t *txTracker) getRetries(tm time.Time) map[idKind]signatureTime { 308 t.mu.Lock() 309 defer t.mu.Unlock() 310 311 retries := map[idKind]signatureTime{} 312 for k, v := range t.txs { 313 if tm.After(v.time.Add(10 * time.Second)) { 314 // add this signature to the retries list 315 retries[k] = *v 316 // update the entry with the current time of the retry 317 v.time = tm 318 } 319 } 320 321 return retries 322 } 323 324 func (t *txTracker) Ack(key idKind) { 325 t.mu.Lock() 326 defer t.mu.Unlock() 327 delete(t.txs, key) 328 } 329 330 func (t *txTracker) Add(key idKind, signature []byte) { 331 t.mu.Lock() 332 defer t.mu.Unlock() 333 // we use the zero value here for the time, meaning it will need a retry 334 // straight away 335 t.txs[key] = &signatureTime{signature: signature, time: time.Time{}} 336 }