code.vegaprotocol.io/vega@v0.79.0/core/datasource/external/ethverifier/verifier_snapshot.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 ethverifier
    17  
    18  import (
    19  	"context"
    20  	"fmt"
    21  	"slices"
    22  
    23  	"code.vegaprotocol.io/vega/core/datasource/external/ethcall"
    24  	"code.vegaprotocol.io/vega/core/metrics"
    25  	"code.vegaprotocol.io/vega/core/types"
    26  	vgcontext "code.vegaprotocol.io/vega/libs/context"
    27  	"code.vegaprotocol.io/vega/libs/proto"
    28  	"code.vegaprotocol.io/vega/logging"
    29  	snapshotpb "code.vegaprotocol.io/vega/protos/vega/snapshot/v1"
    30  
    31  	"golang.org/x/exp/maps"
    32  )
    33  
    34  var (
    35  	contractCall = (&types.PayloadEthContractCallEvent{}).Key()
    36  	lastEthBlock = (&types.PayloadEthOracleLastBlock{}).Key()
    37  	misc         = (&types.PayloadEthVerifierMisc{}).Key()
    38  	hashKeys     = []string{
    39  		contractCall, lastEthBlock, misc,
    40  	}
    41  )
    42  
    43  func (s *Verifier) pendingContractCallEventsPayloadData() *types.PayloadEthContractCallEvent {
    44  	pendingCallEvents := make([]*ethcall.ContractCallEvent, 0, len(s.pendingCallEvents))
    45  
    46  	for _, p := range s.pendingCallEvents {
    47  		pendingCallEvents = append(pendingCallEvents, &p.callEvent)
    48  	}
    49  
    50  	return &types.PayloadEthContractCallEvent{
    51  		EthContractCallEvent: pendingCallEvents,
    52  	}
    53  }
    54  
    55  func (s *Verifier) serialisePendingContractCallEvents() ([]byte, error) {
    56  	s.log.Info("serialising pending call events", logging.Int("n", len(s.pendingCallEvents)))
    57  
    58  	pl := types.Payload{
    59  		Data: s.pendingContractCallEventsPayloadData(),
    60  	}
    61  
    62  	return proto.Marshal(pl.IntoProto())
    63  }
    64  
    65  func (s *Verifier) ethBlockPayloadData(bl *types.EthBlock) *types.PayloadEthOracleLastBlock {
    66  	if bl != nil {
    67  		return &types.PayloadEthOracleLastBlock{
    68  			EthOracleLastBlock: &types.EthBlock{
    69  				Height: bl.Height,
    70  				Time:   bl.Time,
    71  			},
    72  		}
    73  	}
    74  
    75  	return &types.PayloadEthOracleLastBlock{}
    76  }
    77  
    78  func (s *Verifier) serialiseLastEthBlock() ([]byte, error) {
    79  	s.log.Info("serialising last eth block", logging.String("last-eth-block", fmt.Sprintf("%+v", s.lastBlock)))
    80  
    81  	pl := types.Payload{
    82  		Data: s.ethBlockPayloadData(s.lastBlock),
    83  	}
    84  
    85  	return proto.Marshal(pl.IntoProto())
    86  }
    87  
    88  func (s *Verifier) serialiseMisc() ([]byte, error) {
    89  	s.log.Info("serialising last eth block", logging.String("last-eth-block", fmt.Sprintf("%+v", s.lastBlock)))
    90  
    91  	slice := make([]*snapshotpb.EthVerifierBucket, 0, s.ackedEvts.Size())
    92  	iter := s.ackedEvts.events.Iterator()
    93  	for iter.Next() {
    94  		v := (iter.Value().(*ackedEvtBucket))
    95  		hashes := maps.Keys(v.hashes)
    96  		slices.Sort(hashes)
    97  		slice = append(slice, &snapshotpb.EthVerifierBucket{
    98  			Ts:     v.ts,
    99  			Hashes: hashes,
   100  		})
   101  	}
   102  
   103  	pl := types.Payload{
   104  		Data: &types.PayloadEthVerifierMisc{
   105  			Misc: &snapshotpb.EthOracleVerifierMisc{
   106  				PatchBlock: s.ethBlockPayloadData(s.patchBlock).IntoProto().EthOracleVerifierLastBlock,
   107  				Buckets:    slice,
   108  			},
   109  		},
   110  	}
   111  
   112  	return proto.Marshal(pl.IntoProto())
   113  }
   114  
   115  func (s *Verifier) serialiseK(serialFunc func() ([]byte, error)) ([]byte, error) {
   116  	data, err := serialFunc()
   117  	if err != nil {
   118  		return nil, err
   119  	}
   120  	return data, nil
   121  }
   122  
   123  // get the serialised form and hash of the given key.
   124  func (s *Verifier) serialise(k string) ([]byte, error) {
   125  	switch k {
   126  	case contractCall:
   127  		return s.serialiseK(s.serialisePendingContractCallEvents)
   128  	case lastEthBlock:
   129  		return s.serialiseK(s.serialiseLastEthBlock)
   130  	case misc:
   131  		return s.serialiseK(s.serialiseMisc)
   132  	default:
   133  		return nil, types.ErrSnapshotKeyDoesNotExist
   134  	}
   135  }
   136  
   137  func (s *Verifier) Namespace() types.SnapshotNamespace {
   138  	return types.EthereumOracleVerifierSnapshot
   139  }
   140  
   141  func (s *Verifier) Keys() []string {
   142  	return hashKeys
   143  }
   144  
   145  func (s *Verifier) Stopped() bool {
   146  	return false
   147  }
   148  
   149  func (s *Verifier) GetState(k string) ([]byte, []types.StateProvider, error) {
   150  	data, err := s.serialise(k)
   151  	return data, nil, err
   152  }
   153  
   154  func (s *Verifier) LoadState(ctx context.Context, payload *types.Payload) ([]types.StateProvider, error) {
   155  	if s.Namespace() != payload.Data.Namespace() {
   156  		return nil, types.ErrInvalidSnapshotNamespace
   157  	}
   158  
   159  	switch pl := payload.Data.(type) {
   160  	case *types.PayloadEthContractCallEvent:
   161  		s.restorePendingCallEvents(ctx, pl.EthContractCallEvent)
   162  		return nil, nil
   163  	case *types.PayloadEthOracleLastBlock:
   164  		s.restoreLastEthBlock(ctx, pl.EthOracleLastBlock)
   165  		return nil, nil
   166  	case *types.PayloadEthVerifierMisc:
   167  		s.restoreMisc(ctx, pl.Misc)
   168  		return nil, nil
   169  	default:
   170  		return nil, types.ErrUnknownSnapshotType
   171  	}
   172  }
   173  
   174  func (s *Verifier) OnStateLoaded(ctx context.Context) error {
   175  	if vgcontext.InProgressUpgradeFrom(ctx, "v0.74.7") {
   176  		s.patchBlock = s.lastBlock
   177  	}
   178  
   179  	// tell the eth call engine what the last block seen was, so it does not re-trigger calls
   180  	if s.lastBlock != nil && s.lastBlock.Height > 0 {
   181  		s.ethEngine.StartAtHeight(s.lastBlock.Height, s.lastBlock.Time)
   182  	} else {
   183  		s.ethEngine.Start()
   184  	}
   185  
   186  	return nil
   187  }
   188  
   189  func (s *Verifier) restoreSeen(ctx context.Context, buckets []*snapshotpb.EthVerifierBucket) {
   190  	// if we are executing a protocol upgrade,
   191  	// let's force bucketing things. This will reduce
   192  	// increase performance at startup, and everyone is starting
   193  	// from the same snapshot, so that will keep state consistent
   194  	if vgcontext.InProgressUpgrade(ctx) {
   195  		for _, v := range buckets {
   196  			s.ackedEvts.AddAt(v.Ts, v.Hashes...)
   197  		}
   198  		return
   199  	}
   200  
   201  	for _, v := range buckets {
   202  		s.ackedEvts.RestoreExactAt(v.Ts, v.Hashes...)
   203  	}
   204  }
   205  
   206  func (s *Verifier) restoreLastEthBlock(_ context.Context, lastBlock *types.EthBlock) {
   207  	s.log.Info("restoring last eth block", logging.String("last-eth-block", fmt.Sprintf("%+v", lastBlock)))
   208  	s.lastBlock = lastBlock
   209  }
   210  
   211  func (s *Verifier) restorePatchBlock(_ context.Context, patchBlock *types.EthBlock) {
   212  	s.log.Info("restoring patch eth block", logging.String("patch-block", fmt.Sprintf("%+v", patchBlock)))
   213  
   214  	// we have no history of what eth events we've seen from before this patch, so we will reject
   215  	// any that come in that are older
   216  	s.patchBlock = patchBlock
   217  }
   218  
   219  func (s *Verifier) restoreMisc(ctx context.Context, pl *snapshotpb.EthOracleVerifierMisc) {
   220  	if pl.PatchBlock != nil {
   221  		s.patchBlock = &types.EthBlock{
   222  			Height: pl.PatchBlock.BlockHeight,
   223  			Time:   pl.PatchBlock.BlockTime,
   224  		}
   225  	}
   226  	s.restoreSeen(ctx, pl.Buckets)
   227  }
   228  
   229  func (s *Verifier) restorePendingCallEvents(ctx context.Context,
   230  	results []*ethcall.ContractCallEvent,
   231  ) {
   232  	s.log.Debug("restoring pending call events snapshot", logging.Int("n_pending", len(results)))
   233  	s.pendingCallEvents = make([]*pendingCallEvent, 0, len(results))
   234  
   235  	// clear up all the metrics
   236  	seenSpecId := map[string]struct{}{}
   237  
   238  	for _, callEvent := range results {
   239  		if _, ok := seenSpecId[callEvent.SpecId]; !ok {
   240  			metrics.DataSourceEthVerifierCallGaugeReset(callEvent.SpecId)
   241  			seenSpecId[callEvent.SpecId] = struct{}{}
   242  		}
   243  
   244  		// if we've upgraded from the patch we need to add the pending events in, but after the upgrade
   245  		// we don't need to because they will already be there
   246  		if vgcontext.InProgressUpgradeFrom(ctx, "v0.74.7") {
   247  			if !s.ensureNotDuplicate(*callEvent) {
   248  				s.log.Panic("pendingCallEvents's unexpectedly pre-populated when restoring from snapshot")
   249  			}
   250  		}
   251  
   252  		pending := &pendingCallEvent{
   253  			callEvent: *callEvent,
   254  			check:     func(ctx context.Context) error { return s.checkCallEventResult(ctx, *callEvent) },
   255  		}
   256  
   257  		s.pendingCallEvents = append(s.pendingCallEvents, pending)
   258  
   259  		if err := s.witness.RestoreResource(pending, s.onCallEventVerified); err != nil {
   260  			s.log.Panic("unable to restore pending call event resource", logging.String("ID", pending.GetID()), logging.Error(err))
   261  		}
   262  
   263  		metrics.DataSourceEthVerifierCallGaugeAdd(1, callEvent.SpecId)
   264  	}
   265  }