diff --git a/execution_chain/db/core_db/base.nim b/execution_chain/db/core_db/base.nim index 59729d7ae2..369c2c2f6f 100644 --- a/execution_chain/db/core_db/base.nim +++ b/execution_chain/db/core_db/base.nim @@ -248,6 +248,16 @@ proc hasKey*(kvt: CoreDbTxRef; key: openArray[byte]): bool = ## kvt.kTx.hasKeyRc(key).valueOr(false) +proc getCodeSize*(kvt: CoreDbTxRef; codeHash: Hash32): CoreDbRc[int] = + ## This function returns the size of the code associated with `codeHash`. + let rc = kvt.kTx.getCodeSize(codeHash) + if rc.isOk: + ok(rc.value) + elif rc.error == GetNotFound: + err(rc.error.toError("", KvtNotFound)) + else: + err(rc.error.toError("")) + # ------------------------------------------------------------------------------ # Public methods for accounts # ------------------------------------------------------------------------------ diff --git a/execution_chain/db/kvt/kvt_desc.nim b/execution_chain/db/kvt/kvt_desc.nim index 9cb5800ff4..7fc570ca5c 100644 --- a/execution_chain/db/kvt/kvt_desc.nim +++ b/execution_chain/db/kvt/kvt_desc.nim @@ -16,12 +16,16 @@ import std/[hashes, tables], results, + minilru, + eth/common/hashes, ./kvt_init/init_common, ./kvt_constants, ./kvt_desc/desc_error # Not auto-exporting backend -export hashes, tables, kvt_constants, desc_error +export hashes, tables, minilru, kvt_constants, desc_error + +const CODE_SIZE_LRU_SIZE* = 1_000_000 type GetKvpFn* = @@ -95,6 +99,9 @@ type ## Tx holding data scheduled to be written to disk during the next ## `persist` call + codeSizeCache*: LruCache[Hash32, int] + ## LRU cache for contract code sizes by code hash. + # ------------------------------------------------------------------------------ # Public helpers # ------------------------------------------------------------------------------ diff --git a/execution_chain/db/kvt/kvt_init/memory_db.nim b/execution_chain/db/kvt/kvt_init/memory_db.nim index d1fb0b98cc..c9dfb9d425 100644 --- a/execution_chain/db/kvt/kvt_init/memory_db.nim +++ b/execution_chain/db/kvt/kvt_init/memory_db.nim @@ -198,6 +198,7 @@ proc memoryBackend*: KvtDbRef = db.closeFn = closeFn be db.getBackendFn = getBackendFn be + db.codeSizeCache = typeof(db.codeSizeCache).init(CODE_SIZE_LRU_SIZE) db # ------------------------------------------------------------------------------ diff --git a/execution_chain/db/kvt/kvt_init/rocks_db.nim b/execution_chain/db/kvt/kvt_init/rocks_db.nim index 7f7a0a127b..c8462aed60 100644 --- a/execution_chain/db/kvt/kvt_init/rocks_db.nim +++ b/execution_chain/db/kvt/kvt_init/rocks_db.nim @@ -218,6 +218,7 @@ proc rocksDbKvtBackend*(baseDb: RocksDbInstanceRef, cf: static[KvtCFs]): KvtDbRe db.closeFn = closeFn be db.getBackendFn = getBackendFn be + db.codeSizeCache = typeof(db.codeSizeCache).init(CODE_SIZE_LRU_SIZE) db proc getBaseDb*(db: RdbBackendRef): RocksDbInstanceRef = diff --git a/execution_chain/db/kvt/kvt_utils.nim b/execution_chain/db/kvt/kvt_utils.nim index ce35db652e..92f0cfff65 100644 --- a/execution_chain/db/kvt/kvt_utils.nim +++ b/execution_chain/db/kvt/kvt_utils.nim @@ -15,6 +15,7 @@ import results, + ../storage_types, "."/[kvt_desc, kvt_layers] export results @@ -67,7 +68,7 @@ proc delRangeBe*( # ------------ proc put*( - db: KvtTxRef; # Database + txRef: KvtTxRef; # Database key: openArray[byte]; # Key of database record to store data: openArray[byte]; # Value of database record to store ): Result[void,KvtError] = @@ -78,12 +79,12 @@ proc put*( if data.len == 0: return err(DataInvalid) - db.layersPut(key, data) + txRef.layersPut(key, data) ok() proc del*( - db: KvtTxRef; # Database + txRef: KvtTxRef; # Database key: openArray[byte]; # Key of database record to delete ): Result[void,KvtError] = ## For the argument `key` delete the associated value (which will be marked @@ -91,13 +92,13 @@ proc del*( if key.len == 0: return err(KeyInvalid) - db.layersPut(key, EmptyBlob) + txRef.layersPut(key, EmptyBlob) ok() # ------------ proc get*( - db: KvtTxRef; # Database + txRef: KvtTxRef; # Database key: openArray[byte]; # Key of database record ): Result[seq[byte],KvtError] = ## For the argument `key` return the associated value preferably from the @@ -106,27 +107,42 @@ proc get*( if key.len == 0: return err(KeyInvalid) - var data = db.layersGet(key).valueOr: - return db.db.getBe key + var data = txRef.layersGet(key).valueOr: + return txRef.db.getBe key return ok(move(data)) proc len*( - db: KvtTxRef; # Database + txRef: KvtTxRef; # Database key: openArray[byte]; # Key of database record ): Result[int,KvtError] = ## For the argument `key` return the length of the associated value, ## preferably from the top layer, or the database otherwise. - ## if key.len == 0: return err(KeyInvalid) - let len = db.layersLen(key).valueOr: - return db.db.getBeLen key + let len = txRef.layersLen(key).valueOr: + return txRef.db.getBeLen key ok(len) +proc getCodeSize*( + txRef: KvtTxRef; + codeHash: Hash32; + ): Result[int, KvtError] = + let key = contractHashKey(codeHash) + + txRef.layersLen(key.toOpenArray).isErrOr: + return ok(value) + txRef.db.codeSizeCache.get(codeHash).isErrOr: + return ok(value) + + let size = ?txRef.db.getBeLen(key.toOpenArray) + txRef.db.codeSizeCache.put(codeHash, size) + + ok(size) + proc multiGet*( - db: KvtTxRef, + txRef: KvtTxRef, keys: openArray[seq[byte]], values: var openArray[Opt[seq[byte]]], sortedInput = false, @@ -138,7 +154,7 @@ proc multiGet*( # First fetch each key from the in memory layers for i, k in keys: - let value = db.layersGet(k) + let value = txRef.layersGet(k) if value.isSome(): values[i] = value else: @@ -148,7 +164,7 @@ proc multiGet*( # Fetch the remaining keys from the db backend if remainingKeys.len() > 0: var remainingValues = newSeq[Opt[seq[byte]]](remainingKeys.len()) - ?db.db.multiGetBe(remainingKeys, remainingValues) + ?txRef.db.multiGetBe(remainingKeys, remainingValues) for i, v in remainingValues: let index = keyIndexes[i] @@ -157,7 +173,7 @@ proc multiGet*( ok() proc hasKeyRc*( - db: KvtTxRef; # Database + txRef: KvtTxRef; # Database key: openArray[byte]; # Key of database record ): Result[bool,KvtError] = ## For the argument `key` return `true` if `get()` returned a value on @@ -167,10 +183,10 @@ proc hasKeyRc*( if key.len == 0: return err(KeyInvalid) - if db.layersHasKey key: + if txRef.layersHasKey key: return ok(true) - let rc = db.db.getBe key + let rc = txRef.db.getBe key if rc.isOk: return ok(true) if rc.error == GetNotFound: @@ -178,12 +194,12 @@ proc hasKeyRc*( err(rc.error) proc hasKey*( - db: KvtTxRef; # Database + txRef: KvtTxRef; # Database key: openArray[byte]; # Key of database record ): bool = ## Simplified version of `hasKeyRc` where `false` is returned instead of ## an error. - db.hasKeyRc(key).valueOr: false + txRef.hasKeyRc(key).valueOr: false proc close*(db: KvtDbRef; wipe = false) = ## Backend destructor. The argument `wipe` indicates that a full diff --git a/execution_chain/db/ledger.nim b/execution_chain/db/ledger.nim index 3d7695b2d5..863865c094 100644 --- a/execution_chain/db/ledger.nim +++ b/execution_chain/db/ledger.nim @@ -465,7 +465,7 @@ proc getCodeSize*(ledger: LedgerRef, address: Address): int = # cached and easily accessible in the database layer - this is to prevent # EXTCODESIZE calls from messing up the code cache and thus causing # recomputation of the jump destination table - var rc = ledger.txFrame.len(contractHashKey(acc.statement.codeHash).toOpenArray) + var rc = ledger.txFrame.getCodeSize(acc.statement.codeHash) return rc.valueOr: warn logTxt "getCodeSize()", codeHash=acc.statement.codeHash, error=($$rc.error) diff --git a/tests/test_ledger.nim b/tests/test_ledger.nim index 1079b45600..6691bff9f1 100644 --- a/tests/test_ledger.nim +++ b/tests/test_ledger.nim @@ -21,6 +21,7 @@ import ../execution_chain/core/chain, ../execution_chain/core/tx_pool, ../execution_chain/db/core_db/memory_only, + ../execution_chain/db/kvt/kvt_desc, ../execution_chain/transaction, ../execution_chain/constants, ../execution_chain/db/ledger {.all.}, # import all private symbols @@ -496,6 +497,51 @@ proc runLedgerBasicOperationsTests() = val = memDB.baseTxFrame().get(key.toOpenArray).valueOr: EmptyBlob check val == code + test "getCodeSize - no account": + var ledger = LedgerRef.init(memDB.baseTxFrame()) + let addrNoAcc = initAddr(100) + + check ledger.getCodeSize(addrNoAcc) == 0 + + test "getCodeSize - empty code": + var ledger = LedgerRef.init(memDB.baseTxFrame()) + let addrEmpty = initAddr(101) + ledger.setBalance(addrEmpty, 1.u256) + + check ledger.getCodeSize(addrEmpty) == 0 + + test "getCodeSize - cache miss then cache hit": + var ledger1 = LedgerRef.init(memDB.baseTxFrame()) + let addrCode = initAddr(102) + ledger1.setCode(addrCode, code) + ledger1.persist() + ledger1.txFrame.checkpoint(0, skipSnapshot = true) + memDB.persist(ledger1.txFrame) + + let codeHash = keccak256(code) + + var ledger2 = LedgerRef.init(memDB.baseTxFrame()) + + check: + ledger2.txFrame.kTx.db.codeSizeCache.get(codeHash).isNone() + ledger2.getCodeSize(addrCode) == code.len + + let cached = ledger2.txFrame.kTx.db.codeSizeCache.get(codeHash) + check: + cached.isSome() + cached.get() == code.len + ledger2.getCodeSize(addrCode) == code.len + + test "getCodeSize - code loaded in account cache": + var ledger = LedgerRef.init(memDB.baseTxFrame()) + let addrCode = initAddr(103) + ledger.setCode(addrCode, code) + ledger.persist() + + discard ledger.getCode(addrCode) + + check ledger.getCodeSize(addrCode) == code.len + test "accessList operations": proc verifyAddrs(ledger: LedgerRef, addrs: varargs[int]): bool = for c in addrs: