github.com/osdi23p228/fabric@v0.0.0-20221218062954-77808885f5db/core/ledger/kvledger/txmgmt/validation/combined_iterator.go (about)

     1  /*
     2  Copyright IBM Corp. All Rights Reserved.
     3  
     4  SPDX-License-Identifier: Apache-2.0
     5  */
     6  
     7  package validation
     8  
     9  import (
    10  	"strings"
    11  
    12  	"github.com/osdi23p228/fabric/core/ledger/kvledger/txmgmt/statedb"
    13  )
    14  
    15  // combinedIterator implements the interface statedb.ResultsIterator.
    16  // Internally, it maintains two iterators
    17  // - (1) dbItr - an iterator that iterates over keys present in the db
    18  // - (2) updatesItr - an iterator that iterates over keys present in the update batch
    19  //       (i.e, the keys that are inserted/updated/deleted by preceding valid transactions
    20  //        in the block and to be committed to the db as a part of block commit operation)
    21  //
    22  // This can be used where the caller wants to see what would be the final results of
    23  // iterating over a key range if the modifications of the preceding valid transactions
    24  // were to be applied to the db
    25  //
    26  // This can be used to perform validation for phantom reads in a transactions rwset
    27  type combinedIterator struct {
    28  	// input
    29  	ns            string
    30  	db            statedb.VersionedDB
    31  	updates       *statedb.UpdateBatch
    32  	endKey        string
    33  	includeEndKey bool
    34  
    35  	// internal state
    36  	dbItr        statedb.ResultsIterator
    37  	updatesItr   statedb.ResultsIterator
    38  	dbItem       statedb.QueryResult
    39  	updatesItem  statedb.QueryResult
    40  	endKeyServed bool
    41  }
    42  
    43  func newCombinedIterator(db statedb.VersionedDB, updates *statedb.UpdateBatch,
    44  	ns string, startKey string, endKey string, includeEndKey bool) (*combinedIterator, error) {
    45  
    46  	var dbItr statedb.ResultsIterator
    47  	var updatesItr statedb.ResultsIterator
    48  	var err error
    49  	if dbItr, err = db.GetStateRangeScanIterator(ns, startKey, endKey); err != nil {
    50  		return nil, err
    51  	}
    52  	updatesItr = updates.GetRangeScanIterator(ns, startKey, endKey)
    53  	var dbItem, updatesItem statedb.QueryResult
    54  	if dbItem, err = dbItr.Next(); err != nil {
    55  		return nil, err
    56  	}
    57  	if updatesItem, err = updatesItr.Next(); err != nil {
    58  		return nil, err
    59  	}
    60  	logger.Debugf("Combined iterator initialized. dbItem=%#v, updatesItem=%#v", dbItem, updatesItem)
    61  	return &combinedIterator{ns, db, updates, endKey, includeEndKey,
    62  		dbItr, updatesItr, dbItem, updatesItem, false}, nil
    63  }
    64  
    65  // Next returns the KV from either dbItr or updatesItr that gives the next smaller key
    66  // If both gives the same keys, then it returns the KV from updatesItr.
    67  func (itr *combinedIterator) Next() (statedb.QueryResult, error) {
    68  	if itr.dbItem == nil && itr.updatesItem == nil {
    69  		logger.Debugf("dbItem and updatesItem both are nil.")
    70  		return itr.serveEndKeyIfNeeded()
    71  	}
    72  	var moveDBItr bool
    73  	var moveUpdatesItr bool
    74  	var selectedItem statedb.QueryResult
    75  	compResult := compareKeys(itr.dbItem, itr.updatesItem)
    76  	logger.Debugf("compResult=%d", compResult)
    77  	switch compResult {
    78  	case -1:
    79  		// dbItem is smaller
    80  		selectedItem = itr.dbItem
    81  		moveDBItr = true
    82  	case 0:
    83  		//both items are same so, choose the updatesItem (latest)
    84  		selectedItem = itr.updatesItem
    85  		moveUpdatesItr = true
    86  		moveDBItr = true
    87  	case 1:
    88  		// updatesItem is smaller
    89  		selectedItem = itr.updatesItem
    90  		moveUpdatesItr = true
    91  	}
    92  	var err error
    93  	if moveDBItr {
    94  		if itr.dbItem, err = itr.dbItr.Next(); err != nil {
    95  			return nil, err
    96  		}
    97  	}
    98  
    99  	if moveUpdatesItr {
   100  		if itr.updatesItem, err = itr.updatesItr.Next(); err != nil {
   101  			return nil, err
   102  		}
   103  	}
   104  	if isDelete(selectedItem) {
   105  		return itr.Next()
   106  	}
   107  	logger.Debugf("Returning item=%#v. Next dbItem=%#v, Next updatesItem=%#v", selectedItem, itr.dbItem, itr.updatesItem)
   108  	return selectedItem, nil
   109  }
   110  
   111  func (itr *combinedIterator) Close() {
   112  	itr.dbItr.Close()
   113  }
   114  
   115  func (itr *combinedIterator) GetBookmarkAndClose() string {
   116  	itr.Close()
   117  	return ""
   118  }
   119  
   120  // serveEndKeyIfNeeded returns the endKey only once and only if includeEndKey was set to true
   121  // in the constructor of combinedIterator.
   122  func (itr *combinedIterator) serveEndKeyIfNeeded() (statedb.QueryResult, error) {
   123  	if !itr.includeEndKey || itr.endKeyServed {
   124  		logger.Debugf("Endkey not to be served. Returning nil... [toInclude=%t, alreadyServed=%t]",
   125  			itr.includeEndKey, itr.endKeyServed)
   126  		return nil, nil
   127  	}
   128  	logger.Debug("Serving the endKey")
   129  	var vv *statedb.VersionedValue
   130  	var err error
   131  	vv = itr.updates.Get(itr.ns, itr.endKey)
   132  	logger.Debugf("endKey value from updates:%s", vv)
   133  	if vv == nil {
   134  		if vv, err = itr.db.GetState(itr.ns, itr.endKey); err != nil {
   135  			return nil, err
   136  		}
   137  		logger.Debugf("endKey value from stateDB:%s", vv)
   138  	}
   139  	itr.endKeyServed = true
   140  	if vv == nil {
   141  		return nil, nil
   142  	}
   143  	vkv := &statedb.VersionedKV{
   144  		CompositeKey:   statedb.CompositeKey{Namespace: itr.ns, Key: itr.endKey},
   145  		VersionedValue: statedb.VersionedValue{Value: vv.Value, Version: vv.Version}}
   146  
   147  	if isDelete(vkv) {
   148  		return nil, nil
   149  	}
   150  	return vkv, nil
   151  }
   152  
   153  func compareKeys(item1 statedb.QueryResult, item2 statedb.QueryResult) int {
   154  	if item1 == nil {
   155  		if item2 == nil {
   156  			return 0
   157  		}
   158  		return 1
   159  	}
   160  	if item2 == nil {
   161  		return -1
   162  	}
   163  	// at this stage both items are not nil
   164  	return strings.Compare(item1.(*statedb.VersionedKV).Key, item2.(*statedb.VersionedKV).Key)
   165  }
   166  
   167  func isDelete(item statedb.QueryResult) bool {
   168  	return item.(*statedb.VersionedKV).Value == nil
   169  }