code.vegaprotocol.io/vega@v0.79.0/core/staking/checkpoint.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 staking
    17  
    18  import (
    19  	"context"
    20  	"sort"
    21  
    22  	"code.vegaprotocol.io/vega/core/events"
    23  	"code.vegaprotocol.io/vega/core/types"
    24  	"code.vegaprotocol.io/vega/libs/proto"
    25  	"code.vegaprotocol.io/vega/logging"
    26  	checkpoint "code.vegaprotocol.io/vega/protos/vega/checkpoint/v1"
    27  	pbevents "code.vegaprotocol.io/vega/protos/vega/events/v1"
    28  )
    29  
    30  type Checkpoint struct {
    31  	log            *logging.Logger
    32  	accounting     *Accounting
    33  	stakeVerifier  *StakeVerifier
    34  	ethEventSource EthereumEventSource
    35  }
    36  
    37  func NewCheckpoint(
    38  	log *logging.Logger,
    39  	accounting *Accounting,
    40  	stakeVerifier *StakeVerifier,
    41  	ethEventSource EthereumEventSource,
    42  ) *Checkpoint {
    43  	return &Checkpoint{
    44  		log:            log,
    45  		accounting:     accounting,
    46  		stakeVerifier:  stakeVerifier,
    47  		ethEventSource: ethEventSource,
    48  	}
    49  }
    50  
    51  func (c *Checkpoint) Name() types.CheckpointName {
    52  	return types.StakingCheckpoint
    53  }
    54  
    55  func (c *Checkpoint) Checkpoint() ([]byte, error) {
    56  	msg := &checkpoint.Staking{
    57  		Accepted:      c.getAcceptedEvents(),
    58  		LastBlockSeen: c.getLastBlockSeen(),
    59  	}
    60  	ret, err := proto.Marshal(msg)
    61  	if err != nil {
    62  		return nil, err
    63  	}
    64  	return ret, nil
    65  }
    66  
    67  func (c *Checkpoint) Load(ctx context.Context, data []byte) error {
    68  	b := checkpoint.Staking{}
    69  	if err := proto.Unmarshal(data, &b); err != nil {
    70  		return err
    71  	}
    72  
    73  	// first we deduplicates those events, this is a fix for v0.50.4
    74  	dedup := dedupEvents(b.Accepted)
    75  
    76  	for _, evt := range dedup {
    77  		stakeLinking := types.StakeLinkingFromProto(evt)
    78  
    79  		// this will send all necessary events as well
    80  		c.accounting.AddEvent(ctx, stakeLinking)
    81  		// now add event to the hash mapping
    82  		if !c.stakeVerifier.ensureNotDuplicate(stakeLinking.ID, stakeLinking.Hash()) {
    83  			c.log.Panic("invalid checkpoint, duplicate event stored",
    84  				logging.String("event-id", stakeLinking.ID),
    85  			)
    86  		}
    87  	}
    88  
    89  	stakeLinkingEvents := make([]events.Event, 0, len(b.Accepted))
    90  	for _, acc := range c.accounting.hashableAccounts {
    91  		for _, e := range acc.Events {
    92  			stakeLinkingEvents = append(stakeLinkingEvents, events.NewStakeLinking(ctx, *e))
    93  		}
    94  	}
    95  
    96  	c.accounting.broker.SendBatch(stakeLinkingEvents)
    97  
    98  	// 0 is default value, we assume that it was then not set
    99  	if b.LastBlockSeen != 0 {
   100  		for _, addr := range c.stakeVerifier.ocv.GetStakingBridgeAddresses() {
   101  			c.ethEventSource.UpdateContractBlock(addr, c.accounting.chainID, b.LastBlockSeen)
   102  		}
   103  	}
   104  
   105  	return nil
   106  }
   107  
   108  func (c *Checkpoint) getAcceptedEvents() []*pbevents.StakeLinking {
   109  	out := make([]*pbevents.StakeLinking, 0, len(c.accounting.hashableAccounts))
   110  
   111  	for _, acc := range c.accounting.hashableAccounts {
   112  		for _, evt := range acc.Events {
   113  			out = append(out, evt.IntoProto())
   114  		}
   115  	}
   116  	return out
   117  }
   118  
   119  // getLastBlockSeen will return the oldest pending transaction block
   120  // from the stake verifier. By doing so we can restart listening to ethereum
   121  // from the block of the oldest non accepted / verified stake linking event
   122  // which should ensure that we haven't missed any.
   123  func (c *Checkpoint) getLastBlockSeen() uint64 {
   124  	if block := c.stakeVerifier.getLastBlockSeen(); block != 0 {
   125  		return block
   126  	}
   127  
   128  	// now if block is still 0, we use the accounting stuff to find
   129  	// the newest block verified then instead ...
   130  	return c.accounting.getLastBlockSeen()
   131  }
   132  
   133  type key struct {
   134  	txHash                string
   135  	logIndex, blockHeight uint64
   136  }
   137  
   138  func dedupEvents(evts []*pbevents.StakeLinking) []*pbevents.StakeLinking {
   139  	evtsM := map[key]*pbevents.StakeLinking{}
   140  	for _, v := range evts {
   141  		k := key{v.TxHash, v.LogIndex, v.BlockHeight}
   142  		evt, ok := evtsM[k]
   143  		if !ok {
   144  			// we haven't seen this event, just add it and move on
   145  			evtsM[k] = v
   146  			continue
   147  		}
   148  		// we have seen this one already, let's save to earliest one only
   149  		if evt.FinalizedAt > v.FinalizedAt {
   150  			evtsM[k] = v
   151  		}
   152  	}
   153  
   154  	// now we sort and return
   155  	out := make([]*pbevents.StakeLinking, 0, len(evtsM))
   156  	for _, v := range evtsM {
   157  		out = append(out, v)
   158  	}
   159  
   160  	sort.Slice(out, func(i, j int) bool { return out[i].Id < out[j].Id })
   161  	return out
   162  }