github.com/hyperledger/burrow@v0.34.5-0.20220512172541-77f09336001d/acm/acmstate/state_cache.go (about)

     1  // Copyright Monax Industries Limited
     2  // SPDX-License-Identifier: Apache-2.0
     3  
     4  package acmstate
     5  
     6  import (
     7  	"fmt"
     8  	"sort"
     9  	"sync"
    10  
    11  	"github.com/hyperledger/burrow/acm"
    12  	"github.com/hyperledger/burrow/binary"
    13  	"github.com/hyperledger/burrow/crypto"
    14  	"github.com/hyperledger/burrow/execution/errors"
    15  )
    16  
    17  type Cache struct {
    18  	sync.RWMutex
    19  	name     string
    20  	backend  Reader
    21  	accounts map[crypto.Address]*accountInfo
    22  	readonly bool
    23  }
    24  
    25  type accountInfo struct {
    26  	sync.RWMutex
    27  	account *acm.Account
    28  	storage map[binary.Word256][]byte
    29  	removed bool
    30  	updated bool
    31  }
    32  
    33  type CacheOption func(*Cache) *Cache
    34  
    35  // Returns a Cache that wraps an underlying Reader to use on a cache miss, can write to an output Writer
    36  // via Sync. Goroutine safe for concurrent access.
    37  func NewCache(backend Reader, options ...CacheOption) *Cache {
    38  	cache := &Cache{
    39  		backend:  backend,
    40  		accounts: make(map[crypto.Address]*accountInfo),
    41  	}
    42  	for _, option := range options {
    43  		option(cache)
    44  	}
    45  	return cache
    46  }
    47  
    48  func Named(name string) CacheOption {
    49  	return func(cache *Cache) *Cache {
    50  		cache.name = name
    51  		return cache
    52  	}
    53  }
    54  
    55  var ReadOnly CacheOption = func(cache *Cache) *Cache {
    56  	cache.readonly = true
    57  	return cache
    58  }
    59  
    60  func (cache *Cache) GetAccount(address crypto.Address) (*acm.Account, error) {
    61  	accInfo, err := cache.get(address)
    62  	if err != nil {
    63  		return nil, err
    64  	}
    65  	accInfo.RLock()
    66  	defer accInfo.RUnlock()
    67  	if accInfo.removed {
    68  		return nil, nil
    69  	}
    70  	return accInfo.account.Copy(), nil
    71  }
    72  
    73  func (cache *Cache) UpdateAccount(account *acm.Account) error {
    74  	if account == nil {
    75  		return errors.Errorf(errors.Codes.IllegalWrite, "UpdateAccount called with nil account")
    76  	}
    77  	if cache.readonly {
    78  		return errors.Errorf(errors.Codes.IllegalWrite,
    79  			"UpdateAccount called in a read-only context on account %v", account.GetAddress())
    80  	}
    81  	accInfo, err := cache.get(account.GetAddress())
    82  	if err != nil {
    83  		return err
    84  	}
    85  	accInfo.Lock()
    86  	defer accInfo.Unlock()
    87  	if accInfo.removed {
    88  		return errors.Errorf(errors.Codes.IllegalWrite, "UpdateAccount on a removed account: %s", account.GetAddress())
    89  	}
    90  	accInfo.account = account.Copy()
    91  	accInfo.updated = true
    92  	return nil
    93  }
    94  
    95  func (cache *Cache) RemoveAccount(address crypto.Address) error {
    96  	if cache.readonly {
    97  		return errors.Errorf(errors.Codes.IllegalWrite, "RemoveAccount called on read-only account %v", address)
    98  	}
    99  	accInfo, err := cache.get(address)
   100  	if err != nil {
   101  		return err
   102  	}
   103  	accInfo.Lock()
   104  	defer accInfo.Unlock()
   105  	if accInfo.removed {
   106  		return fmt.Errorf("RemoveAccount on a removed account: %s", address)
   107  	}
   108  	accInfo.removed = true
   109  	return nil
   110  }
   111  
   112  // Iterates over all cached accounts first in cache and then in backend until consumer returns true for 'stop'
   113  func (cache *Cache) IterateCachedAccount(consumer func(*acm.Account) (stop bool)) (stopped bool, err error) {
   114  	// Try cache first for early exit
   115  	cache.RLock()
   116  	for _, info := range cache.accounts {
   117  		if consumer(info.account) {
   118  			cache.RUnlock()
   119  			return true, nil
   120  		}
   121  	}
   122  	cache.RUnlock()
   123  	return false, nil
   124  }
   125  
   126  func (cache *Cache) GetStorage(address crypto.Address, key binary.Word256) ([]byte, error) {
   127  	accInfo, err := cache.get(address)
   128  	if err != nil {
   129  		return []byte{}, err
   130  	}
   131  	// Check cache
   132  	accInfo.RLock()
   133  	value, ok := accInfo.storage[key]
   134  	accInfo.RUnlock()
   135  	if !ok {
   136  		accInfo.Lock()
   137  		defer accInfo.Unlock()
   138  		value, ok = accInfo.storage[key]
   139  		if !ok {
   140  			// Load from backend
   141  			value, err = cache.backend.GetStorage(address, key)
   142  			if err != nil {
   143  				return []byte{}, err
   144  			}
   145  			accInfo.storage[key] = value
   146  		}
   147  	}
   148  	return value, nil
   149  }
   150  
   151  // NOTE: Set value to zero to remove.
   152  func (cache *Cache) SetStorage(address crypto.Address, key binary.Word256, value []byte) error {
   153  	if cache.readonly {
   154  		return errors.Errorf(errors.Codes.IllegalWrite,
   155  			"SetStorage called in a read-only context on account %v", address)
   156  	}
   157  	accInfo, err := cache.get(address)
   158  	if accInfo.account == nil {
   159  		return errors.Errorf(errors.Codes.IllegalWrite,
   160  			"SetStorage called on an account that does not exist: %v", address)
   161  	}
   162  	accInfo.Lock()
   163  	defer accInfo.Unlock()
   164  	if err != nil {
   165  		return err
   166  	}
   167  	if accInfo.removed {
   168  		return errors.Errorf(errors.Codes.IllegalWrite, "SetStorage on a removed account: %s", address)
   169  	}
   170  	accInfo.storage[key] = value
   171  	accInfo.updated = true
   172  	return nil
   173  }
   174  
   175  // Iterates over all cached storage items first in cache and then in backend until consumer returns true for 'stop'
   176  func (cache *Cache) IterateCachedStorage(address crypto.Address,
   177  	consumer func(key binary.Word256, value []byte) error) error {
   178  	accInfo, err := cache.get(address)
   179  	if err != nil {
   180  		return err
   181  	}
   182  	accInfo.RLock()
   183  	// Try cache first for early exit
   184  	for key, value := range accInfo.storage {
   185  		if err := consumer(key, value); err != nil {
   186  			accInfo.RUnlock()
   187  			return err
   188  		}
   189  	}
   190  	accInfo.RUnlock()
   191  	return err
   192  }
   193  
   194  // Syncs changes to the backend in deterministic order. Sends storage updates before updating
   195  // the account they belong so that storage values can be taken account of in the update.
   196  func (cache *Cache) Sync(st Writer) error {
   197  	if cache.readonly {
   198  		// Sync is (should be) a no-op for read-only - any modifications should have been caught in respective methods
   199  		return nil
   200  	}
   201  	cache.Lock()
   202  	defer cache.Unlock()
   203  	var addresses crypto.Addresses
   204  	for address := range cache.accounts {
   205  		addresses = append(addresses, address)
   206  	}
   207  
   208  	sort.Sort(addresses)
   209  	for _, address := range addresses {
   210  		accInfo := cache.accounts[address]
   211  		accInfo.RLock()
   212  		if accInfo.removed {
   213  			err := st.RemoveAccount(address)
   214  			if err != nil {
   215  				return err
   216  			}
   217  		} else if accInfo.updated {
   218  			// First update account in case it needs to be created
   219  			err := st.UpdateAccount(accInfo.account)
   220  			if err != nil {
   221  				return err
   222  			}
   223  			// Sort keys
   224  			var keys binary.Words256
   225  			for key := range accInfo.storage {
   226  				keys = append(keys, key)
   227  			}
   228  			sort.Sort(keys)
   229  			// Update account's storage
   230  			for _, key := range keys {
   231  				value := accInfo.storage[key]
   232  				err := st.SetStorage(address, key, value)
   233  				if err != nil {
   234  					return err
   235  				}
   236  			}
   237  
   238  		}
   239  		accInfo.RUnlock()
   240  	}
   241  
   242  	return nil
   243  }
   244  
   245  // Resets the cache to empty initialising the backing map to the same size as the previous iteration.
   246  func (cache *Cache) Reset(backend Reader) {
   247  	cache.Lock()
   248  	defer cache.Unlock()
   249  	cache.backend = backend
   250  	cache.accounts = make(map[crypto.Address]*accountInfo, len(cache.accounts))
   251  }
   252  
   253  func (cache *Cache) String() string {
   254  	if cache.name == "" {
   255  		return fmt.Sprintf("StateCache{Length: %v}", len(cache.accounts))
   256  	}
   257  	return fmt.Sprintf("StateCache{Name: %v; Length: %v}", cache.name, len(cache.accounts))
   258  }
   259  
   260  // Get the cache accountInfo item creating it if necessary
   261  func (cache *Cache) get(address crypto.Address) (*accountInfo, error) {
   262  	cache.RLock()
   263  	accInfo := cache.accounts[address]
   264  	cache.RUnlock()
   265  	if accInfo != nil {
   266  		return accInfo, nil
   267  	}
   268  	// Take write lock to fill cache
   269  	cache.Lock()
   270  	defer cache.Unlock()
   271  	// Check for an interleaved cache fill
   272  	accInfo = cache.accounts[address]
   273  	if accInfo != nil {
   274  		return accInfo, nil
   275  	}
   276  	// Pull from backend
   277  	account, err := cache.backend.GetAccount(address)
   278  	if err != nil {
   279  		return nil, err
   280  	}
   281  	accInfo = &accountInfo{
   282  		account: account,
   283  		storage: make(map[binary.Word256][]byte),
   284  	}
   285  	cache.accounts[address] = accInfo
   286  	return accInfo, nil
   287  }