code.vegaprotocol.io/vega@v0.79.0/core/validators/signatures.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 validators
    17  
    18  import (
    19  	"context"
    20  	"encoding/hex"
    21  	"errors"
    22  	"fmt"
    23  	"sort"
    24  	"time"
    25  
    26  	"code.vegaprotocol.io/vega/core/bridges"
    27  	"code.vegaprotocol.io/vega/core/events"
    28  	"code.vegaprotocol.io/vega/core/types"
    29  	vgcrypto "code.vegaprotocol.io/vega/libs/crypto"
    30  	"code.vegaprotocol.io/vega/libs/num"
    31  	"code.vegaprotocol.io/vega/logging"
    32  	eventspb "code.vegaprotocol.io/vega/protos/vega/events/v1"
    33  	snapshot "code.vegaprotocol.io/vega/protos/vega/snapshot/v1"
    34  )
    35  
    36  var (
    37  	ErrNoPendingSignaturesForNodeID = errors.New("there are no pending signatures for the given nodeID")
    38  	ErrUnknownChainID               = errors.New("chain id does not correspond to any bridge")
    39  )
    40  
    41  type Signatures interface {
    42  	PreparePromotionsSignatures(
    43  		ctx context.Context,
    44  		currentTime time.Time,
    45  		epochSeq uint64,
    46  		previousState map[string]StatusAddress,
    47  		newState map[string]StatusAddress,
    48  	)
    49  	SetNonce(currentTime time.Time)
    50  	PrepareValidatorSignatures(ctx context.Context, validators []NodeIDAddress, epochSeq uint64, added bool)
    51  	EmitValidatorAddedSignatures(ctx context.Context, submitter, nodeID, chainID string, currentTime time.Time) error
    52  	EmitValidatorRemovedSignatures(ctx context.Context, submitter, nodeID, chainID string, currentTime time.Time) error
    53  	ClearStaleSignatures()
    54  	SerialisePendingSignatures() *snapshot.ToplogySignatures
    55  	RestorePendingSignatures(*snapshot.ToplogySignatures)
    56  	OfferSignatures()
    57  }
    58  
    59  type signatureData struct {
    60  	NodeID     string
    61  	EthAddress string
    62  	Nonce      *num.Uint
    63  	EpochSeq   uint64
    64  	Added      bool
    65  }
    66  
    67  type issuedSignature struct {
    68  	EthAddress       string
    69  	SubmitterAddress string
    70  	ChainID          string
    71  }
    72  
    73  type signatureWithSubmitter struct {
    74  	signatureData
    75  	SubmitterAddress string
    76  	chainID          string
    77  }
    78  
    79  type ERC20Signatures struct {
    80  	log             *logging.Logger
    81  	notary          Notary
    82  	primaryMultisig MultiSigTopology
    83  	// primaryBridge     *bridges.ERC20MultiSigControl
    84  	secondaryMultisig MultiSigTopology
    85  	// secondaryBridge   *bridges.ERC20MultiSigControl
    86  	lastNonce        *num.Uint
    87  	broker           Broker
    88  	isValidatorSetup bool
    89  	signer           Signer
    90  
    91  	// stored nonce's etc. to be able to generate signatures to remove/add an ethereum address from the multisig bundle
    92  	pendingSignatures map[string]*signatureData
    93  	issuedSignatures  map[string]issuedSignature
    94  }
    95  
    96  func NewSignatures(
    97  	log *logging.Logger,
    98  	primaryMultisig MultiSigTopology,
    99  	secondaryMultisig MultiSigTopology,
   100  	notary Notary,
   101  	nw NodeWallets,
   102  	broker Broker,
   103  	isValidatorSetup bool,
   104  ) *ERC20Signatures {
   105  	s := &ERC20Signatures{
   106  		log:               log,
   107  		notary:            notary,
   108  		primaryMultisig:   primaryMultisig,
   109  		secondaryMultisig: secondaryMultisig,
   110  		lastNonce:         num.UintZero(),
   111  		broker:            broker,
   112  		isValidatorSetup:  isValidatorSetup,
   113  		pendingSignatures: map[string]*signatureData{},
   114  		issuedSignatures:  map[string]issuedSignature{},
   115  	}
   116  	if isValidatorSetup {
   117  		s.signer = nw.GetEthereum()
   118  	}
   119  	return s
   120  }
   121  
   122  type StatusAddress struct {
   123  	Status           ValidatorStatus
   124  	EthAddress       string
   125  	SubmitterAddress string
   126  }
   127  
   128  type NodeIDAddress struct {
   129  	NodeID           string
   130  	EthAddress       string
   131  	SubmitterAddress string
   132  }
   133  
   134  // isBridge returns whether the given chainID corresponds to one of the bridges, and returns if it is the Ethereum bridge.
   135  func (s *ERC20Signatures) isBridge(chainID string) (isBridge bool) {
   136  	switch chainID {
   137  	case s.primaryMultisig.ChainID():
   138  		isBridge = true
   139  	case s.secondaryMultisig.ChainID():
   140  		isBridge = true
   141  	}
   142  	return
   143  }
   144  
   145  func (s *ERC20Signatures) OfferSignatures() {
   146  	s.notary.OfferSignatures(types.NodeSignatureKindERC20MultiSigSignerAdded, s.offerValidatorAddedSignatures)
   147  	s.notary.OfferSignatures(types.NodeSignatureKindERC20MultiSigSignerRemoved, s.offerValidatorRemovedSignatures)
   148  }
   149  
   150  func (s *ERC20Signatures) getSignatureWithSubmitterByResID(resID string) (*signatureWithSubmitter, error) {
   151  	is, ok := s.issuedSignatures[resID]
   152  	if !ok {
   153  		return nil, fmt.Errorf("unable to find issued signature with resource id %q", resID)
   154  	}
   155  
   156  	sd, ok := s.pendingSignatures[is.EthAddress]
   157  	if !ok {
   158  		return nil, fmt.Errorf("unable to find pending signature by ethereum address %q", is.EthAddress)
   159  	}
   160  
   161  	return &signatureWithSubmitter{
   162  		signatureData:    *sd,
   163  		SubmitterAddress: is.SubmitterAddress,
   164  		chainID:          is.ChainID,
   165  	}, nil
   166  }
   167  
   168  func (s *ERC20Signatures) offerValidatorAddedSignatures(resID string) []byte {
   169  	if !s.isValidatorSetup {
   170  		return nil
   171  	}
   172  
   173  	sig, err := s.getSignatureWithSubmitterByResID(resID)
   174  	if err != nil {
   175  		s.log.Panic("unable to find signature", logging.Error(err))
   176  	}
   177  
   178  	if !sig.Added {
   179  		s.log.Panic("expected added signature but got removed signature instead", logging.String("ethereumAddress", sig.EthAddress))
   180  	}
   181  
   182  	isBridge := s.isBridge(sig.chainID)
   183  	if !isBridge {
   184  		s.log.Panic("unexpected bridge chainID", logging.String("chain-id", sig.chainID))
   185  	}
   186  
   187  	signature, err := bridges.NewERC20MultiSigControl(s.signer, sig.chainID, false).AddSigner(sig.EthAddress, sig.SubmitterAddress, sig.Nonce.Clone())
   188  	if err != nil {
   189  		s.log.Panic("could not sign remove signer event, wallet not configured properly",
   190  			logging.Error(err))
   191  	}
   192  
   193  	return signature.Signature.Bytes()
   194  }
   195  
   196  func (s *ERC20Signatures) offerValidatorRemovedSignatures(resID string) []byte {
   197  	if !s.isValidatorSetup {
   198  		return nil
   199  	}
   200  
   201  	sig, err := s.getSignatureWithSubmitterByResID(resID)
   202  	if err != nil {
   203  		s.log.Panic("unable to find signature", logging.Error(err))
   204  	}
   205  
   206  	if sig.Added {
   207  		s.log.Panic("expected removed signature but got added signature instead", logging.String("ethereumAddress", sig.EthAddress))
   208  	}
   209  
   210  	isBridge := s.isBridge(sig.chainID)
   211  	if !isBridge {
   212  		s.log.Panic("unexpected bridge chainID", logging.String("chain-id", sig.chainID))
   213  	}
   214  
   215  	signature, err := bridges.NewERC20MultiSigControl(s.signer, sig.chainID, false).RemoveSigner(sig.EthAddress, sig.SubmitterAddress, sig.Nonce.Clone())
   216  	if err != nil {
   217  		s.log.Panic("could not sign remove signer event, wallet not configured properly",
   218  			logging.Error(err))
   219  	}
   220  
   221  	return signature.Signature.Bytes()
   222  }
   223  
   224  func (s *ERC20Signatures) getSignatureData(nodeID string, added bool) []*signatureData {
   225  	r := []*signatureData{}
   226  	for _, p := range s.pendingSignatures {
   227  		if p.NodeID == nodeID && p.Added == added {
   228  			r = append(r, p)
   229  		}
   230  	}
   231  	sort.SliceStable(r, func(i, j int) bool {
   232  		return r[i].EthAddress < r[j].EthAddress
   233  	})
   234  	return r
   235  }
   236  
   237  func (s *ERC20Signatures) PreparePromotionsSignatures(
   238  	ctx context.Context,
   239  	currentTime time.Time,
   240  	epochSeq uint64,
   241  	previousState map[string]StatusAddress,
   242  	newState map[string]StatusAddress,
   243  ) {
   244  	toAdd := []NodeIDAddress{}
   245  	toRemove := []NodeIDAddress{}
   246  
   247  	// first let's cover all the previous validators
   248  	for k, state := range previousState {
   249  		if val, ok := newState[k]; !ok {
   250  			// in this case we were a validator before, but not even in the validator set anymore,
   251  			// we can remove it.
   252  			if state.Status == ValidatorStatusTendermint {
   253  				toRemove = append(toRemove, NodeIDAddress{k, state.EthAddress, state.SubmitterAddress})
   254  			}
   255  		} else {
   256  			// we've been removed from the validator set then
   257  			if state.Status == ValidatorStatusTendermint && val.Status != ValidatorStatusTendermint {
   258  				toRemove = append(toRemove, NodeIDAddress{k, state.EthAddress, state.SubmitterAddress})
   259  			} else if state.Status != ValidatorStatusTendermint && val.Status == ValidatorStatusTendermint {
   260  				// now we've become a validator
   261  				toAdd = append(toAdd, NodeIDAddress{k, state.EthAddress, state.SubmitterAddress})
   262  			}
   263  		}
   264  	}
   265  
   266  	// now let's cover all which might have been added but might not have been in the previousStates?
   267  	// is that even possible?
   268  	for k, val := range newState {
   269  		if _, ok := previousState[k]; !ok {
   270  			// this is a new validator which didn't exist before
   271  			if val.Status == ValidatorStatusTendermint {
   272  				toAdd = append(toAdd, NodeIDAddress{k, val.EthAddress, val.SubmitterAddress})
   273  			}
   274  		}
   275  	}
   276  
   277  	s.PrepareValidatorSignatures(ctx, toAdd, epochSeq, true)
   278  	s.PrepareValidatorSignatures(ctx, toRemove, epochSeq, false)
   279  
   280  	// check if the node being added has supplied a submitterAddress because if it has we can automatically emit
   281  	// signatures for the node to add itself
   282  	for _, v := range toAdd {
   283  		if v.SubmitterAddress != "" {
   284  			s.log.Debug("sending automatic add signatures", logging.String("submitter", v.SubmitterAddress), logging.String("nodeID", v.NodeID))
   285  			s.EmitValidatorAddedSignatures(ctx, v.SubmitterAddress, v.NodeID, s.primaryMultisig.ChainID(), currentTime)
   286  			s.EmitValidatorAddedSignatures(ctx, v.SubmitterAddress, v.NodeID, s.secondaryMultisig.ChainID(), currentTime)
   287  		}
   288  	}
   289  
   290  	// for each node being removed check if the Tendermint nodes have a submitter address because if they do
   291  	// we can automatically emit remove signatures for them
   292  	for _, r := range toRemove {
   293  		for _, v := range newState {
   294  			if v.SubmitterAddress != "" && v.Status == ValidatorStatusTendermint {
   295  				s.log.Debug("sending automatic remove signatures", logging.String("submitter", v.SubmitterAddress), logging.String("nodeID", r.NodeID))
   296  				s.EmitValidatorRemovedSignatures(ctx, v.SubmitterAddress, r.NodeID, s.primaryMultisig.ChainID(), currentTime)
   297  				s.EmitValidatorRemovedSignatures(ctx, v.SubmitterAddress, r.NodeID, s.secondaryMultisig.ChainID(), currentTime)
   298  			}
   299  		}
   300  	}
   301  }
   302  
   303  func (s *ERC20Signatures) SetNonce(t time.Time) {
   304  	s.lastNonce = num.NewUint(uint64(t.Unix()) + 1)
   305  }
   306  
   307  // PrepareValidatorSignatures make nonces and store the data needed to generate signatures to add/remove from the multisig control contract.
   308  func (s *ERC20Signatures) PrepareValidatorSignatures(ctx context.Context, validators []NodeIDAddress, epochSeq uint64, added bool) {
   309  	sort.Slice(validators, func(i, j int) bool {
   310  		return validators[i].EthAddress < validators[j].EthAddress
   311  	})
   312  
   313  	for _, signer := range validators {
   314  		d := &signatureData{
   315  			NodeID:     signer.NodeID,
   316  			EthAddress: signer.EthAddress,
   317  			Nonce:      s.lastNonce.Clone(),
   318  			EpochSeq:   epochSeq,
   319  			Added:      added,
   320  		}
   321  		s.lastNonce.AddUint64(s.lastNonce, 1)
   322  
   323  		// we're ok to override whatever is in here since we can't need to both add and remove an eth-address at the same time
   324  		// so we'll replace it either with the correct action, or with the same action but with a later epoch which is fine
   325  		// because you can still get signatures to do what you need
   326  		s.pendingSignatures[signer.EthAddress] = d
   327  		s.log.Debug("prepared multisig signatures for", logging.Bool("added", added), logging.String("id", signer.NodeID), logging.String("eth-address", signer.EthAddress))
   328  	}
   329  }
   330  
   331  // EmitValidatorAddedSignatures emit signatures to add nodeID's ethereum address onto that can be submitter to the contract by submitter.
   332  func (s *ERC20Signatures) EmitValidatorAddedSignatures(ctx context.Context, submitter, nodeID, chainID string, currentTime time.Time) error {
   333  	toEmit := s.getSignatureData(nodeID, true)
   334  	if len(toEmit) == 0 {
   335  		return ErrNoPendingSignaturesForNodeID
   336  	}
   337  
   338  	isBridge := s.isBridge(chainID)
   339  	if !isBridge {
   340  		return ErrUnknownChainID
   341  	}
   342  
   343  	evts := []events.Event{}
   344  	for _, pending := range toEmit {
   345  		var sig []byte
   346  		nonce := pending.Nonce
   347  
   348  		resid := hex.EncodeToString(vgcrypto.Hash([]byte(submitter + nonce.String() + chainID)))
   349  		if _, ok := s.issuedSignatures[resid]; ok {
   350  			// we've already issued a signature for this submitter we don't want to do it again, it'll annoy the notary engine
   351  			s.log.Debug("add signatures already issued",
   352  				logging.String("chain-id", chainID),
   353  				logging.String("submitter", submitter),
   354  				logging.String("add-address",
   355  					pending.EthAddress))
   356  			continue
   357  		}
   358  
   359  		if s.isValidatorSetup {
   360  			signature, err := bridges.NewERC20MultiSigControl(s.signer, chainID, false).AddSigner(pending.EthAddress, submitter, nonce)
   361  			if err != nil {
   362  				s.log.Panic("could not sign remove signer event, wallet not configured properly",
   363  					logging.Error(err))
   364  			}
   365  			sig = signature.Signature
   366  		}
   367  
   368  		s.notary.StartAggregate(resid, types.NodeSignatureKindERC20MultiSigSignerAdded, sig)
   369  		evts = append(evts, events.NewERC20MultiSigSignerAdded(
   370  			ctx,
   371  			eventspb.ERC20MultiSigSignerAdded{
   372  				SignatureId: resid,
   373  				ValidatorId: nodeID,
   374  				Timestamp:   currentTime.UnixNano(),
   375  				EpochSeq:    num.NewUint(pending.EpochSeq).String(),
   376  				NewSigner:   pending.EthAddress,
   377  				Submitter:   submitter,
   378  				Nonce:       nonce.String(),
   379  				ChainId:     chainID,
   380  			},
   381  		))
   382  
   383  		// store that we issued it for this submitter
   384  		s.issuedSignatures[resid] = issuedSignature{
   385  			EthAddress:       pending.EthAddress,
   386  			SubmitterAddress: submitter,
   387  			ChainID:          chainID,
   388  		}
   389  	}
   390  	s.broker.SendBatch(evts)
   391  	return nil
   392  }
   393  
   394  // EmitValidatorRemovedSignatures emit signatures to remove nodeID's ethereum address onto that can be submitter to the contract by submitter.
   395  func (s *ERC20Signatures) EmitValidatorRemovedSignatures(ctx context.Context, submitter, nodeID, chainID string, currentTime time.Time) error {
   396  	toEmit := s.getSignatureData(nodeID, false)
   397  	if len(toEmit) == 0 {
   398  		return ErrNoPendingSignaturesForNodeID
   399  	}
   400  
   401  	isBridge := s.isBridge(chainID)
   402  	if !isBridge {
   403  		return ErrUnknownChainID
   404  	}
   405  
   406  	evts := []events.Event{}
   407  	// its possible for a nodeID to need to remove 2 of their etheruem addresses from the contract. For example if they initiate a key rotation
   408  	// and after adding their new key but before they've removed their old key they get demoted. At that point they can have 2 signers on the
   409  	// contract that will need to be removed and that all vaidators could want to issue signatures for
   410  	for _, pending := range toEmit {
   411  		var sig []byte
   412  		nonce := pending.Nonce
   413  
   414  		resid := hex.EncodeToString(vgcrypto.Hash([]byte(pending.EthAddress + submitter + nonce.String() + chainID)))
   415  		if _, ok := s.issuedSignatures[resid]; ok {
   416  			// we've already issued a signature for this submitter we don't want to do it again, it'll annoy the notary engine
   417  			s.log.Debug("remove signatures already issued", logging.String("submitter", submitter), logging.String("add-address", pending.EthAddress))
   418  			continue
   419  		}
   420  
   421  		if s.isValidatorSetup {
   422  			signature, err := bridges.NewERC20MultiSigControl(s.signer, chainID, false).RemoveSigner(pending.EthAddress, submitter, nonce)
   423  			if err != nil {
   424  				s.log.Panic("could not sign remove signer event, wallet not configured properly",
   425  					logging.Error(err))
   426  			}
   427  			sig = signature.Signature
   428  		}
   429  		s.notary.StartAggregate(
   430  			resid, types.NodeSignatureKindERC20MultiSigSignerRemoved, sig)
   431  
   432  		submitters := []*eventspb.ERC20MultiSigSignerRemovedSubmitter{}
   433  		submitters = append(submitters, &eventspb.ERC20MultiSigSignerRemovedSubmitter{
   434  			SignatureId: resid,
   435  			Submitter:   submitter,
   436  		})
   437  
   438  		evts = append(evts, events.NewERC20MultiSigSignerRemoved(
   439  			ctx,
   440  			eventspb.ERC20MultiSigSignerRemoved{
   441  				SignatureSubmitters: submitters,
   442  				ValidatorId:         nodeID,
   443  				Timestamp:           currentTime.UnixNano(),
   444  				EpochSeq:            num.NewUint(pending.EpochSeq).String(),
   445  				OldSigner:           pending.EthAddress,
   446  				Nonce:               nonce.String(),
   447  				ChainId:             chainID,
   448  			},
   449  		))
   450  
   451  		// store that we issued it for this submitter
   452  		s.issuedSignatures[resid] = issuedSignature{
   453  			EthAddress:       pending.EthAddress,
   454  			SubmitterAddress: submitter,
   455  			ChainID:          chainID,
   456  		}
   457  	}
   458  	s.broker.SendBatch(evts)
   459  	return nil
   460  }
   461  
   462  // ClearStaleSignatures checks core's view of who is an isn't on the multisig contract and remove any pending signatures that have
   463  // been resolve e.g if a pending sig to add an address X exists but X is on the contract we can remove the pending sig.
   464  func (s *ERC20Signatures) ClearStaleSignatures() {
   465  	toRemove := []string{}
   466  	for e, p := range s.pendingSignatures {
   467  		if p.Added == s.primaryMultisig.IsSigner(e) && p.Added == s.secondaryMultisig.IsSigner(e) {
   468  			toRemove = append(toRemove, e)
   469  		}
   470  	}
   471  
   472  	for _, e := range toRemove {
   473  		s.log.Debug("removing stale pending signature", logging.String("eth-address", e))
   474  		delete(s.pendingSignatures, e)
   475  	}
   476  }
   477  
   478  type noopSignatures struct {
   479  	log *logging.Logger
   480  }
   481  
   482  func (n *noopSignatures) EmitValidatorAddedSignatures(_ context.Context, _, _, _ string, _ time.Time) error {
   483  	n.log.Error("noopSignatures implementation in use in production")
   484  	return nil
   485  }
   486  
   487  func (n *noopSignatures) EmitValidatorRemovedSignatures(_ context.Context, _, _, _ string, _ time.Time) error {
   488  	n.log.Error("noopSignatures implementation in use in production")
   489  	return nil
   490  }
   491  
   492  func (n *noopSignatures) PrepareValidatorSignatures(
   493  	_ context.Context, _ []NodeIDAddress, _ uint64, _ bool,
   494  ) {
   495  	n.log.Error("noopSignatures implementation in use in production")
   496  }
   497  
   498  func (n *noopSignatures) PreparePromotionsSignatures(
   499  	_ context.Context, _ time.Time, _ uint64, _ map[string]StatusAddress, _ map[string]StatusAddress,
   500  ) {
   501  	n.log.Error("noopSignatures implementation in use in production")
   502  }
   503  
   504  func (n *noopSignatures) SetNonce(_ time.Time) {
   505  	n.log.Error("noopSignatures implementation in use in production")
   506  }
   507  
   508  func (n *noopSignatures) ClearStaleSignatures() {
   509  	n.log.Error("noopSignatures implementation in use in production")
   510  }
   511  
   512  func (n *noopSignatures) SerialisePendingSignatures() *snapshot.ToplogySignatures {
   513  	n.log.Error("noopSignatures implementation in use in production")
   514  	return &snapshot.ToplogySignatures{}
   515  }
   516  
   517  func (n *noopSignatures) RestorePendingSignatures(*snapshot.ToplogySignatures) {
   518  	n.log.Error("noopSignatures implementation in use in production")
   519  }
   520  
   521  func (n *noopSignatures) OfferSignatures() {
   522  	n.log.Error("noopSignatures implementation in use in production")
   523  }