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 }