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 }