github.com/MetalBlockchain/metalgo@v1.11.9/chains/atomic/shared_memory.go (about)

     1  // Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved.
     2  // See the file LICENSE for licensing terms.
     3  
     4  package atomic
     5  
     6  import (
     7  	"github.com/MetalBlockchain/metalgo/database"
     8  	"github.com/MetalBlockchain/metalgo/database/versiondb"
     9  	"github.com/MetalBlockchain/metalgo/ids"
    10  	"github.com/MetalBlockchain/metalgo/utils"
    11  )
    12  
    13  var _ SharedMemory = (*sharedMemory)(nil)
    14  
    15  type Requests struct {
    16  	RemoveRequests [][]byte   `serialize:"true"`
    17  	PutRequests    []*Element `serialize:"true"`
    18  
    19  	peerChainID ids.ID
    20  }
    21  
    22  type Element struct {
    23  	Key    []byte   `serialize:"true"`
    24  	Value  []byte   `serialize:"true"`
    25  	Traits [][]byte `serialize:"true"`
    26  }
    27  
    28  type SharedMemory interface {
    29  	// Get fetches the values corresponding to [keys] that have been sent from
    30  	// [peerChainID]
    31  	//
    32  	// Invariant: Get guarantees that the resulting values array is the same
    33  	//            length as keys.
    34  	Get(peerChainID ids.ID, keys [][]byte) (values [][]byte, err error)
    35  	// Indexed returns a paginated result of values that possess any of the
    36  	// given traits and were sent from [peerChainID].
    37  	Indexed(
    38  		peerChainID ids.ID,
    39  		traits [][]byte,
    40  		startTrait,
    41  		startKey []byte,
    42  		limit int,
    43  	) (
    44  		values [][]byte,
    45  		lastTrait,
    46  		lastKey []byte,
    47  		err error,
    48  	)
    49  	// Apply performs the requested set of operations by atomically applying
    50  	// [requests] to their respective chainID keys in the map along with the
    51  	// batches on the underlying DB.
    52  	//
    53  	// Invariant: The underlying database of [batches] must be the same as the
    54  	//            underlying database for SharedMemory.
    55  	Apply(requests map[ids.ID]*Requests, batches ...database.Batch) error
    56  }
    57  
    58  // sharedMemory provides the API for a blockchain to interact with shared memory
    59  // of another blockchain
    60  type sharedMemory struct {
    61  	m           *Memory
    62  	thisChainID ids.ID
    63  }
    64  
    65  func (sm *sharedMemory) Get(peerChainID ids.ID, keys [][]byte) ([][]byte, error) {
    66  	sharedID := sharedID(peerChainID, sm.thisChainID)
    67  	db := sm.m.GetSharedDatabase(sm.m.db, sharedID)
    68  	defer sm.m.ReleaseSharedDatabase(sharedID)
    69  
    70  	s := state{
    71  		valueDB: inbound.getValueDB(sm.thisChainID, peerChainID, db),
    72  	}
    73  
    74  	values := make([][]byte, len(keys))
    75  	for i, key := range keys {
    76  		elem, err := s.Value(key)
    77  		if err != nil {
    78  			return nil, err
    79  		}
    80  		values[i] = elem.Value
    81  	}
    82  	return values, nil
    83  }
    84  
    85  func (sm *sharedMemory) Indexed(
    86  	peerChainID ids.ID,
    87  	traits [][]byte,
    88  	startTrait,
    89  	startKey []byte,
    90  	limit int,
    91  ) ([][]byte, []byte, []byte, error) {
    92  	sharedID := sharedID(peerChainID, sm.thisChainID)
    93  	db := sm.m.GetSharedDatabase(sm.m.db, sharedID)
    94  	defer sm.m.ReleaseSharedDatabase(sharedID)
    95  
    96  	s := state{}
    97  	s.valueDB, s.indexDB = inbound.getValueAndIndexDB(sm.thisChainID, peerChainID, db)
    98  
    99  	keys, lastTrait, lastKey, err := s.getKeys(traits, startTrait, startKey, limit)
   100  	if err != nil {
   101  		return nil, nil, nil, err
   102  	}
   103  
   104  	values := make([][]byte, len(keys))
   105  	for i, key := range keys {
   106  		elem, err := s.Value(key)
   107  		if err != nil {
   108  			return nil, nil, nil, err
   109  		}
   110  		values[i] = elem.Value
   111  	}
   112  	return values, lastTrait, lastKey, nil
   113  }
   114  
   115  func (sm *sharedMemory) Apply(requests map[ids.ID]*Requests, batches ...database.Batch) error {
   116  	// Sorting here introduces an ordering over the locks to prevent any
   117  	// deadlocks
   118  	sharedIDs := make([]ids.ID, 0, len(requests))
   119  	sharedOperations := make(map[ids.ID]*Requests, len(requests))
   120  	for peerChainID, request := range requests {
   121  		sharedID := sharedID(sm.thisChainID, peerChainID)
   122  		sharedIDs = append(sharedIDs, sharedID)
   123  
   124  		request.peerChainID = peerChainID
   125  		sharedOperations[sharedID] = request
   126  	}
   127  	utils.Sort(sharedIDs)
   128  
   129  	// Make sure all operations are committed atomically
   130  	vdb := versiondb.New(sm.m.db)
   131  
   132  	for _, sharedID := range sharedIDs {
   133  		req := sharedOperations[sharedID]
   134  
   135  		db := sm.m.GetSharedDatabase(vdb, sharedID)
   136  		defer sm.m.ReleaseSharedDatabase(sharedID)
   137  
   138  		s := state{}
   139  
   140  		// Perform any remove requests on the inbound database
   141  		s.valueDB, s.indexDB = inbound.getValueAndIndexDB(sm.thisChainID, req.peerChainID, db)
   142  		for _, removeRequest := range req.RemoveRequests {
   143  			if err := s.RemoveValue(removeRequest); err != nil {
   144  				return err
   145  			}
   146  		}
   147  
   148  		// Add Put requests to the outbound database.
   149  		s.valueDB, s.indexDB = outbound.getValueAndIndexDB(sm.thisChainID, req.peerChainID, db)
   150  		for _, putRequest := range req.PutRequests {
   151  			if err := s.SetValue(putRequest); err != nil {
   152  				return err
   153  			}
   154  		}
   155  	}
   156  
   157  	// Commit the operations on shared memory atomically with the contents of
   158  	// [batches].
   159  	batch, err := vdb.CommitBatch()
   160  	if err != nil {
   161  		return err
   162  	}
   163  
   164  	return WriteAll(batch, batches...)
   165  }