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  }