github.com/yimialmonte/fabric@v2.1.1+incompatible/core/transientstore/store.go (about) 1 /* 2 Copyright IBM Corp. All Rights Reserved. 3 4 SPDX-License-Identifier: Apache-2.0 5 */ 6 7 package transientstore 8 9 import ( 10 "errors" 11 12 "github.com/golang/protobuf/proto" 13 "github.com/hyperledger/fabric-protos-go/ledger/rwset" 14 "github.com/hyperledger/fabric-protos-go/transientstore" 15 "github.com/hyperledger/fabric/common/flogging" 16 "github.com/hyperledger/fabric/common/ledger/util/leveldbhelper" 17 "github.com/hyperledger/fabric/common/util" 18 "github.com/hyperledger/fabric/core/ledger" 19 "github.com/syndtr/goleveldb/leveldb/iterator" 20 ) 21 22 var logger = flogging.MustGetLogger("transientstore") 23 24 var ( 25 emptyValue = []byte{} 26 nilByte = byte('\x00') 27 // ErrStoreEmpty is used to indicate that there are no entries in transient store 28 ErrStoreEmpty = errors.New("Transient store is empty") 29 ) 30 31 ////////////////////////////////////////////// 32 // Interfaces and data types 33 ///////////////////////////////////////////// 34 35 // StoreProvider provides an instance of a TransientStore 36 type StoreProvider interface { 37 OpenStore(ledgerID string) (*Store, error) 38 Close() 39 } 40 41 // RWSetScanner provides an iterator for EndorserPvtSimulationResults 42 type RWSetScanner interface { 43 // Next returns the next EndorserPvtSimulationResults from the RWSetScanner. 44 // It may return nil, nil when it has no further data, and also may return an error 45 // on failure 46 Next() (*EndorserPvtSimulationResults, error) 47 // Close frees the resources associated with this RWSetScanner 48 Close() 49 } 50 51 // EndorserPvtSimulationResults captures the details of the simulation results specific to an endorser 52 type EndorserPvtSimulationResults struct { 53 ReceivedAtBlockHeight uint64 54 PvtSimulationResultsWithConfig *transientstore.TxPvtReadWriteSetWithConfigInfo 55 } 56 57 ////////////////////////////////////////////// 58 // Implementation 59 ///////////////////////////////////////////// 60 61 // storeProvider encapsulates a leveldb provider which is used to store 62 // private write sets of simulated transactions, and implements TransientStoreProvider 63 // interface. 64 type storeProvider struct { 65 dbProvider *leveldbhelper.Provider 66 } 67 68 // store holds an instance of a levelDB. 69 type Store struct { 70 db *leveldbhelper.DBHandle 71 ledgerID string 72 } 73 74 // RwsetScanner helps iterating over results 75 type RwsetScanner struct { 76 txid string 77 dbItr iterator.Iterator 78 filter ledger.PvtNsCollFilter 79 } 80 81 // NewStoreProvider instantiates TransientStoreProvider 82 func NewStoreProvider(path string) (StoreProvider, error) { 83 dbProvider, err := leveldbhelper.NewProvider(&leveldbhelper.Conf{DBPath: path}) 84 if err != nil { 85 return nil, err 86 } 87 return &storeProvider{dbProvider: dbProvider}, nil 88 } 89 90 // OpenStore returns a handle to a ledgerId in Store 91 func (provider *storeProvider) OpenStore(ledgerID string) (*Store, error) { 92 dbHandle := provider.dbProvider.GetDBHandle(ledgerID) 93 return &Store{db: dbHandle, ledgerID: ledgerID}, nil 94 } 95 96 // Close closes the TransientStoreProvider 97 func (provider *storeProvider) Close() { 98 provider.dbProvider.Close() 99 } 100 101 // Persist stores the private write set of a transaction along with the collection config 102 // in the transient store based on txid and the block height the private data was received at 103 func (s *Store) Persist(txid string, blockHeight uint64, 104 privateSimulationResultsWithConfig *transientstore.TxPvtReadWriteSetWithConfigInfo) error { 105 106 logger.Debugf("Persisting private data to transient store for txid [%s] at block height [%d]", txid, blockHeight) 107 108 dbBatch := leveldbhelper.NewUpdateBatch() 109 110 // Create compositeKey with appropriate prefix, txid, uuid and blockHeight 111 // Due to the fact that the txid may have multiple private write sets persisted from different 112 // endorsers (via Gossip), we postfix an uuid with the txid to avoid collision. 113 uuid := util.GenerateUUID() 114 compositeKeyPvtRWSet := createCompositeKeyForPvtRWSet(txid, uuid, blockHeight) 115 privateSimulationResultsWithConfigBytes, err := proto.Marshal(privateSimulationResultsWithConfig) 116 if err != nil { 117 return err 118 } 119 120 // Note that some rwset.TxPvtReadWriteSet may exist in the transient store immediately after 121 // upgrading the peer to v1.2. In order to differentiate between new proto and old proto while 122 // retrieving, a nil byte is prepended to the new proto, i.e., privateSimulationResultsWithConfigBytes, 123 // as a marshaled message can never start with a nil byte. In v1.3, we can avoid prepending the 124 // nil byte. 125 value := append([]byte{nilByte}, privateSimulationResultsWithConfigBytes...) 126 dbBatch.Put(compositeKeyPvtRWSet, value) 127 128 // Create two index: (i) by txid, and (ii) by height 129 130 // Create compositeKey for purge index by height with appropriate prefix, blockHeight, 131 // txid, uuid and store the compositeKey (purge index) with a nil byte as value. Note that 132 // the purge index is used to remove orphan entries in the transient store (which are not removed 133 // by PurgeTxids()) using BTL policy by PurgeBelowHeight(). Note that orphan entries are due to transaction 134 // that gets endorsed but not submitted by the client for commit) 135 compositeKeyPurgeIndexByHeight := createCompositeKeyForPurgeIndexByHeight(blockHeight, txid, uuid) 136 dbBatch.Put(compositeKeyPurgeIndexByHeight, emptyValue) 137 138 // Create compositeKey for purge index by txid with appropriate prefix, txid, uuid, 139 // blockHeight and store the compositeKey (purge index) with a nil byte as value. 140 // Though compositeKeyPvtRWSet itself can be used to purge private write set by txid, 141 // we create a separate composite key with a nil byte as value. The reason is that 142 // if we use compositeKeyPvtRWSet, we unnecessarily read (potentially large) private write 143 // set associated with the key from db. Note that this purge index is used to remove non-orphan 144 // entries in the transient store and is used by PurgeTxids() 145 // Note: We can create compositeKeyPurgeIndexByTxid by just replacing the prefix of compositeKeyPvtRWSet 146 // with purgeIndexByTxidPrefix. For code readability and to be expressive, we use a 147 // createCompositeKeyForPurgeIndexByTxid() instead. 148 compositeKeyPurgeIndexByTxid := createCompositeKeyForPurgeIndexByTxid(txid, uuid, blockHeight) 149 dbBatch.Put(compositeKeyPurgeIndexByTxid, emptyValue) 150 151 return s.db.WriteBatch(dbBatch, true) 152 } 153 154 // GetTxPvtRWSetByTxid returns an iterator due to the fact that the txid may have multiple private 155 // write sets persisted from different endorsers. 156 func (s *Store) GetTxPvtRWSetByTxid(txid string, filter ledger.PvtNsCollFilter) (RWSetScanner, error) { 157 158 logger.Debugf("Getting private data from transient store for transaction %s", txid) 159 160 // Construct startKey and endKey to do an range query 161 startKey := createTxidRangeStartKey(txid) 162 endKey := createTxidRangeEndKey(txid) 163 164 iter := s.db.GetIterator(startKey, endKey) 165 return &RwsetScanner{txid, iter, filter}, nil 166 } 167 168 // PurgeByTxids removes private write sets of a given set of transactions from the 169 // transient store. PurgeByTxids() is expected to be called by coordinator after 170 // committing a block to ledger. 171 func (s *Store) PurgeByTxids(txids []string) error { 172 173 logger.Debug("Purging private data from transient store for committed txids") 174 175 dbBatch := leveldbhelper.NewUpdateBatch() 176 177 for _, txid := range txids { 178 // Construct startKey and endKey to do an range query 179 startKey := createPurgeIndexByTxidRangeStartKey(txid) 180 endKey := createPurgeIndexByTxidRangeEndKey(txid) 181 182 iter := s.db.GetIterator(startKey, endKey) 183 184 // Get all txid and uuid from above result and remove it from transient store (both 185 // write set and the corresponding indexes. 186 for iter.Next() { 187 // For each entry, remove the private read-write set and corresponding indexes 188 189 // Remove private write set 190 compositeKeyPurgeIndexByTxid := iter.Key() 191 // Note: We can create compositeKeyPvtRWSet by just replacing the prefix of compositeKeyPurgeIndexByTxid 192 // with prwsetPrefix. For code readability and to be expressive, we split and create again. 193 uuid, blockHeight, err := splitCompositeKeyOfPurgeIndexByTxid(compositeKeyPurgeIndexByTxid) 194 if err != nil { 195 return err 196 } 197 compositeKeyPvtRWSet := createCompositeKeyForPvtRWSet(txid, uuid, blockHeight) 198 dbBatch.Delete(compositeKeyPvtRWSet) 199 200 // Remove purge index -- purgeIndexByHeight 201 compositeKeyPurgeIndexByHeight := createCompositeKeyForPurgeIndexByHeight(blockHeight, txid, uuid) 202 dbBatch.Delete(compositeKeyPurgeIndexByHeight) 203 204 // Remove purge index -- purgeIndexByTxid 205 dbBatch.Delete(compositeKeyPurgeIndexByTxid) 206 } 207 iter.Release() 208 } 209 // If peer fails before/while writing the batch to golevelDB, these entries will be 210 // removed as per BTL policy later by PurgeBelowHeight() 211 return s.db.WriteBatch(dbBatch, true) 212 } 213 214 // PurgeBelowHeight removes private write sets at block height lesser than 215 // a given maxBlockNumToRetain. In other words, Purge only retains private write sets 216 // that were persisted at block height of maxBlockNumToRetain or higher. Though the private 217 // write sets stored in transient store is removed by coordinator using PurgebyTxids() 218 // after successful block commit, PurgeBelowHeight() is still required to remove orphan entries (as 219 // transaction that gets endorsed may not be submitted by the client for commit) 220 func (s *Store) PurgeBelowHeight(maxBlockNumToRetain uint64) error { 221 222 logger.Debugf("Purging orphaned private data from transient store received prior to block [%d]", maxBlockNumToRetain) 223 224 // Do a range query with 0 as startKey and maxBlockNumToRetain-1 as endKey 225 startKey := createPurgeIndexByHeightRangeStartKey(0) 226 endKey := createPurgeIndexByHeightRangeEndKey(maxBlockNumToRetain - 1) 227 iter := s.db.GetIterator(startKey, endKey) 228 229 dbBatch := leveldbhelper.NewUpdateBatch() 230 231 // Get all txid and uuid from above result and remove it from transient store (both 232 // write set and the corresponding index. 233 for iter.Next() { 234 // For each entry, remove the private read-write set and corresponding indexes 235 236 // Remove private write set 237 compositeKeyPurgeIndexByHeight := iter.Key() 238 txid, uuid, blockHeight, err := splitCompositeKeyOfPurgeIndexByHeight(compositeKeyPurgeIndexByHeight) 239 if err != nil { 240 return err 241 } 242 logger.Debugf("Purging from transient store private data simulated at block [%d]: txid [%s] uuid [%s]", blockHeight, txid, uuid) 243 244 compositeKeyPvtRWSet := createCompositeKeyForPvtRWSet(txid, uuid, blockHeight) 245 dbBatch.Delete(compositeKeyPvtRWSet) 246 247 // Remove purge index -- purgeIndexByTxid 248 compositeKeyPurgeIndexByTxid := createCompositeKeyForPurgeIndexByTxid(txid, uuid, blockHeight) 249 dbBatch.Delete(compositeKeyPurgeIndexByTxid) 250 251 // Remove purge index -- purgeIndexByHeight 252 dbBatch.Delete(compositeKeyPurgeIndexByHeight) 253 } 254 iter.Release() 255 256 return s.db.WriteBatch(dbBatch, true) 257 } 258 259 // GetMinTransientBlkHt returns the lowest block height remaining in transient store 260 func (s *Store) GetMinTransientBlkHt() (uint64, error) { 261 // Current approach performs a range query on purgeIndex with startKey 262 // as 0 (i.e., blockHeight) and returns the first key which denotes 263 // the lowest block height remaining in transient store. An alternative approach 264 // is to explicitly store the minBlockHeight in the transientStore. 265 startKey := createPurgeIndexByHeightRangeStartKey(0) 266 iter := s.db.GetIterator(startKey, nil) 267 defer iter.Release() 268 // Fetch the minimum transient block height 269 if iter.Next() { 270 dbKey := iter.Key() 271 _, _, blockHeight, err := splitCompositeKeyOfPurgeIndexByHeight(dbKey) 272 return blockHeight, err 273 } 274 // Returning an error may not be the right thing to do here. May be 275 // return a bool. -1 is not possible due to unsigned int as first 276 // return value 277 return 0, ErrStoreEmpty 278 } 279 280 func (s *Store) Shutdown() { 281 // do nothing because shared db is used 282 } 283 284 // Next moves the iterator to the next key/value pair. 285 // It returns <nil, nil> when the iterator is exhausted. 286 func (scanner *RwsetScanner) Next() (*EndorserPvtSimulationResults, error) { 287 if !scanner.dbItr.Next() { 288 return nil, nil 289 } 290 dbKey := scanner.dbItr.Key() 291 dbVal := scanner.dbItr.Value() 292 _, blockHeight, err := splitCompositeKeyOfPvtRWSet(dbKey) 293 if err != nil { 294 return nil, err 295 } 296 297 txPvtRWSet := &rwset.TxPvtReadWriteSet{} 298 var filteredTxPvtRWSet *rwset.TxPvtReadWriteSet = nil 299 txPvtRWSetWithConfig := &transientstore.TxPvtReadWriteSetWithConfigInfo{} 300 301 if dbVal[0] == nilByte { 302 // new proto, i.e., TxPvtReadWriteSetWithConfigInfo 303 if err := proto.Unmarshal(dbVal[1:], txPvtRWSetWithConfig); err != nil { 304 return nil, err 305 } 306 307 filteredTxPvtRWSet = trimPvtWSet(txPvtRWSetWithConfig.GetPvtRwset(), scanner.filter) 308 configs, err := trimPvtCollectionConfigs(txPvtRWSetWithConfig.CollectionConfigs, scanner.filter) 309 if err != nil { 310 return nil, err 311 } 312 txPvtRWSetWithConfig.CollectionConfigs = configs 313 } else { 314 // old proto, i.e., TxPvtReadWriteSet 315 if err := proto.Unmarshal(dbVal, txPvtRWSet); err != nil { 316 return nil, err 317 } 318 filteredTxPvtRWSet = trimPvtWSet(txPvtRWSet, scanner.filter) 319 } 320 321 txPvtRWSetWithConfig.PvtRwset = filteredTxPvtRWSet 322 323 return &EndorserPvtSimulationResults{ 324 ReceivedAtBlockHeight: blockHeight, 325 PvtSimulationResultsWithConfig: txPvtRWSetWithConfig, 326 }, nil 327 } 328 329 // Close releases resource held by the iterator 330 func (scanner *RwsetScanner) Close() { 331 scanner.dbItr.Release() 332 }