[skip ci] add accounts cache test

This commit is contained in:
andri lim 2020-01-07 19:49:42 +07:00 committed by zah
parent 5795bf52e0
commit 727b477fca
2 changed files with 119 additions and 40 deletions

View File

@ -9,6 +9,7 @@ type
IsNew IsNew
IsDirty IsDirty
IsTouched IsTouched
IsClone
CodeLoaded CodeLoaded
CodeChanged CodeChanged
StorageChanged StorageChanged
@ -34,12 +35,11 @@ type
RolledBack RolledBack
SavePoint* = ref object SavePoint* = ref object
ac: AccountsCache
parentSavepoint: SavePoint parentSavepoint: SavePoint
cache: Table[EthAddress, RefAccount] cache: Table[EthAddress, RefAccount]
state: TransactionState state: TransactionState
proc beginTransaction*(ac: var AccountsCache): SavePoint {.gcsafe.} proc beginSavepoint*(ac: var AccountsCache): SavePoint {.gcsafe.}
# The AccountsCache is modeled after TrieDatabase for it's transaction style # The AccountsCache is modeled after TrieDatabase for it's transaction style
proc init*(x: typedesc[AccountsCache], db: TrieDatabaseRef, proc init*(x: typedesc[AccountsCache], db: TrieDatabaseRef,
@ -47,45 +47,43 @@ proc init*(x: typedesc[AccountsCache], db: TrieDatabaseRef,
result.db = db result.db = db
result.trie = initSecureHexaryTrie(db, root, pruneTrie) result.trie = initSecureHexaryTrie(db, root, pruneTrie)
result.unrevertablyTouched = initHashSet[EthAddress]() result.unrevertablyTouched = initHashSet[EthAddress]()
discard result.beginTransaction discard result.beginSavepoint
proc beginTransaction*(ac: var AccountsCache): SavePoint = proc beginSavepoint*(ac: var AccountsCache): SavePoint =
new result new result
result.ac = ac
result.cache = initTable[EthAddress, RefAccount]() result.cache = initTable[EthAddress, RefAccount]()
result.state = Pending result.state = Pending
result.parentSavepoint = ac.savePoint result.parentSavepoint = ac.savePoint
ac.savePoint = result ac.savePoint = result
proc rollback*(sp: Savepoint) = proc rollback*(ac: var AccountsCache, sp: Savepoint) =
# Transactions should be handled in a strictly nested fashion. # Transactions should be handled in a strictly nested fashion.
# Any child transaction must be committed or rolled-back before # Any child transaction must be committed or rolled-back before
# its parent transactions: # its parent transactions:
doAssert sp.ac.savePoint == sp and sp.state == Pending doAssert ac.savePoint == sp and sp.state == Pending
sp.ac.savePoint = sp.parentSavepoint ac.savePoint = sp.parentSavepoint
sp.state = RolledBack sp.state = RolledBack
proc commit*(sp: Savepoint) = proc commit*(ac: var AccountsCache, sp: Savepoint) =
# Transactions should be handled in a strictly nested fashion. # Transactions should be handled in a strictly nested fashion.
# Any child transaction must be committed or rolled-back before # Any child transaction must be committed or rolled-back before
# its parent transactions: # its parent transactions:
doAssert sp.ac.savePoint == sp and sp.state == Pending doAssert ac.savePoint == sp and sp.state == Pending
sp.ac.savePoint = sp.parentSavepoint
if isNil sp.parentSavepoint:
# cannot commit most inner savepoint # cannot commit most inner savepoint
doAssert(false) doAssert not sp.parentSavepoint.isNil
else:
ac.savePoint = sp.parentSavepoint
for k, v in sp.cache: for k, v in sp.cache:
sp.parentSavepoint.cache[k] = v sp.parentSavepoint.cache[k] = v
sp.state = Committed sp.state = Committed
proc dispose*(sp: Savepoint) {.inline.} = proc dispose*(ac: var AccountsCache, sp: Savepoint) {.inline.} =
if sp.state == Pending: if sp.state == Pending:
sp.rollback() ac.rollback(sp)
proc safeDispose*(sp: Savepoint) {.inline.} = proc safeDispose*(ac: var AccountsCache, sp: Savepoint) {.inline.} =
if (not isNil(sp)) and (sp.state == Pending): if (not isNil(sp)) and (sp.state == Pending):
sp.rollback() ac.rollback(sp)
template createRangeFromAddress(address: EthAddress): ByteRange = template createRangeFromAddress(address: EthAddress): ByteRange =
## XXX: The name of this proc is intentionally long, because it ## XXX: The name of this proc is intentionally long, because it
@ -124,24 +122,27 @@ proc getAccount(ac: AccountsCache, address: EthAddress): RefAccount =
proc clone(acc: RefAccount, cloneStorage: bool): RefAccount = proc clone(acc: RefAccount, cloneStorage: bool): RefAccount =
new(result) new(result)
result.account = acc.account result.account = acc.account
result.flags = acc.flags result.flags = acc.flags + {IsClone}
result.code = acc.code result.code = acc.code
if cloneStorage: if cloneStorage:
result.originalStorage = acc.originalStorage result.originalStorage = acc.originalStorage
if acc.overlayStorage.len > 0: # it's ok to clone a table this way
let initialLength = tables.rightSize(acc.overlayStorage.len) result.overlayStorage = acc.overlayStorage
result.overlayStorage = initTable[UInt256, UInt256](initialLength)
for k, v in acc.overlayStorage:
result.overlayStorage[k] = v
result.flags.incl IsDirty
proc isEmpty(acc: RefAccount): bool = proc isEmpty(acc: RefAccount): bool =
result = acc.account.codeHash == EMPTY_SHA3 and result = acc.account.codeHash == EMPTY_SHA3 and
acc.account.balance.isZero and acc.account.balance.isZero and
acc.account.nonce == 0 acc.account.nonce == 0
proc isExists(acc: RefAccount): bool =
if IsAlive notin acc.flags:
return false
if IsClone in acc.flags:
result = true
else:
result = IsNew notin acc.flags
template createTrieKeyFromSlot(slot: UInt256): ByteRange = template createTrieKeyFromSlot(slot: UInt256): ByteRange =
# XXX: This is too expensive. Similar to `createRangeFromAddress` # XXX: This is too expensive. Similar to `createRangeFromAddress`
# Converts a number to hex big-endian representation including # Converts a number to hex big-endian representation including
@ -160,6 +161,8 @@ template getAccountTrie(db: TrieDatabaseRef, acc: RefAccount): auto =
initSecureHexaryTrie(db, acc.account.storageRoot, false) initSecureHexaryTrie(db, acc.account.storageRoot, false)
proc originalStorageValue(acc: RefAccount, slot: UInt256, db: TrieDatabaseRef): UInt256 = proc originalStorageValue(acc: RefAccount, slot: UInt256, db: TrieDatabaseRef): UInt256 =
# share the same original storage between multiple
# versions of account
if acc.originalStorage.isNil: if acc.originalStorage.isNil:
acc.originalStorage = newTable[UInt256, UInt256]() acc.originalStorage = newTable[UInt256, UInt256]()
else: else:
@ -189,7 +192,7 @@ proc storageValue(acc: RefAccount, slot: UInt256, db: TrieDatabaseRef): UInt256
proc kill(acc: RefAccount) = proc kill(acc: RefAccount) =
acc.flags.excl IsAlive acc.flags.excl IsAlive
acc.overlayStorage.clear() acc.overlayStorage.clear()
acc.originalStorage.clear() acc.originalStorage = nil
acc.account = newAccount() acc.account = newAccount()
acc.code = default(ByteRange) acc.code = default(ByteRange)
@ -213,6 +216,11 @@ proc persistCode(acc: RefAccount, db: TrieDatabaseRef) =
db.put(contractHashKey(acc.account.codeHash).toOpenArray, acc.code.toOpenArray) db.put(contractHashKey(acc.account.codeHash).toOpenArray, acc.code.toOpenArray)
proc persistStorage(acc: RefAccount, db: TrieDatabaseRef) = proc persistStorage(acc: RefAccount, db: TrieDatabaseRef) =
if acc.account.storageRoot == emptyRlpHash:
# TODO: remove the storage too if we figure out
# how to create 'virtual' storage room for each account
return
var accountTrie = getAccountTrie(db, acc) var accountTrie = getAccountTrie(db, acc)
for slot, value in acc.overlayStorage: for slot, value in acc.overlayStorage:
@ -234,7 +242,7 @@ proc persistStorage(acc: RefAccount, db: TrieDatabaseRef) =
proc makeDirty(ac: AccountsCache, address: EthAddress, cloneStorage = true): RefAccount = proc makeDirty(ac: AccountsCache, address: EthAddress, cloneStorage = true): RefAccount =
result = ac.getAccount(address) result = ac.getAccount(address)
if address in ac.savePoint.cache: if address in ac.savePoint.cache:
# it's in latest savepoint # it's already in latest savepoint
result.flags.incl IsDirty result.flags.incl IsDirty
return return
@ -279,23 +287,23 @@ proc hasCodeOrNonce*(ac: AccountsCache, address: EthAddress): bool {.inline.} =
proc accountExists*(ac: AccountsCache, address: EthAddress): bool {.inline.} = proc accountExists*(ac: AccountsCache, address: EthAddress): bool {.inline.} =
let acc = ac.getAccount(address) let acc = ac.getAccount(address)
result = IsNew notin acc.flags acc.isExists()
proc isEmptyAccount*(ac: AccountsCache, address: EthAddress): bool {.inline.} = proc isEmptyAccount*(ac: AccountsCache, address: EthAddress): bool {.inline.} =
let acc = ac.getAccount(address) let acc = ac.getAccount(address)
doAssert IsNew notin acc.flags doAssert acc.isExists()
result = acc.isEmpty() result = acc.isEmpty()
proc isDeadAccount*(ac: AccountsCache, address: EthAddress): bool = proc isDeadAccount*(ac: AccountsCache, address: EthAddress): bool =
let acc = ac.getAccount(address) let acc = ac.getAccount(address)
if IsNew in acc.flags: if not acc.isExists():
result = true result = true
else: else:
result = acc.isEmpty() result = acc.isEmpty()
proc setBalance*(ac: var AccountsCache, address: EthAddress, balance: UInt256) = proc setBalance*(ac: var AccountsCache, address: EthAddress, balance: UInt256) =
let acc = ac.getAccount(address) let acc = ac.getAccount(address)
acc.flags.incl IsTouched acc.flags.incl {IsTouched, IsAlive}
if acc.account.balance != balance: if acc.account.balance != balance:
ac.makeDirty(address).account.balance = balance ac.makeDirty(address).account.balance = balance
@ -307,7 +315,7 @@ proc subBalance*(ac: var AccountsCache, address: EthAddress, delta: UInt256) {.i
proc setNonce*(ac: var AccountsCache, address: EthAddress, nonce: AccountNonce) = proc setNonce*(ac: var AccountsCache, address: EthAddress, nonce: AccountNonce) =
let acc = ac.getAccount(address) let acc = ac.getAccount(address)
acc.flags.incl IsTouched acc.flags.incl {IsTouched, IsAlive}
if acc.account.nonce != nonce: if acc.account.nonce != nonce:
ac.makeDirty(address).account.nonce = nonce ac.makeDirty(address).account.nonce = nonce
@ -316,7 +324,7 @@ proc incNonce*(ac: var AccountsCache, address: EthAddress) {.inline.} =
proc setCode*(ac: var AccountsCache, address: EthAddress, code: ByteRange) = proc setCode*(ac: var AccountsCache, address: EthAddress, code: ByteRange) =
let acc = ac.getAccount(address) let acc = ac.getAccount(address)
acc.flags.incl IsTouched acc.flags.incl {IsTouched, IsAlive}
let codeHash = keccakHash(code.toOpenArray) let codeHash = keccakHash(code.toOpenArray)
if acc.account.codeHash != codeHash: if acc.account.codeHash != codeHash:
var acc = ac.makeDirty(address) var acc = ac.makeDirty(address)
@ -326,7 +334,7 @@ proc setCode*(ac: var AccountsCache, address: EthAddress, code: ByteRange) =
proc setStorage*(ac: var AccountsCache, address: EthAddress, slot, value: UInt256) = proc setStorage*(ac: var AccountsCache, address: EthAddress, slot, value: UInt256) =
let acc = ac.getAccount(address) let acc = ac.getAccount(address)
acc.flags.incl IsTouched acc.flags.incl {IsTouched, IsAlive}
let oldValue = acc.storageValue(slot, ac.db) let oldValue = acc.storageValue(slot, ac.db)
if oldValue != value: if oldValue != value:
var acc = ac.makeDirty(address) var acc = ac.makeDirty(address)
@ -335,16 +343,20 @@ proc setStorage*(ac: var AccountsCache, address: EthAddress, slot, value: UInt25
proc clearStorage*(ac: var AccountsCache, address: EthAddress) = proc clearStorage*(ac: var AccountsCache, address: EthAddress) =
let acc = ac.getAccount(address) let acc = ac.getAccount(address)
acc.flags.incl IsTouched acc.flags.incl {IsTouched, IsAlive}
if acc.account.storageRoot != emptyRlpHash: if acc.account.storageRoot != emptyRlpHash:
# there is no point to clone the storage since we want to remove it # there is no point to clone the storage since we want to remove it
ac.makeDirty(address, cloneStorage = false).account.storageRoot = emptyRlpHash ac.makeDirty(address, cloneStorage = false).account.storageRoot = emptyRlpHash
#proc deleteAccount*(ac: var AccountsCache, address: EthAddress) =
# let acc = ac.getAccount(address)
# if IsAlive in acc.flags:
proc unrevertableTouch*(ac: var AccountsCache, address: EthAddress) = proc unrevertableTouch*(ac: var AccountsCache, address: EthAddress) =
ac.unrevertablyTouched.incl address ac.unrevertablyTouched.incl address
proc removeEmptyAccounts*(ac: var AccountsCache) = proc removeEmptyAccounts*(ac: var AccountsCache) =
# make sure all savepoint already committed # make sure all savepoints already committed
doAssert(ac.savePoint.parentSavePoint.isNil) doAssert(ac.savePoint.parentSavePoint.isNil)
for _, acc in ac.savePoint.cache: for _, acc in ac.savePoint.cache:
if IsTouched in acc.flags and acc.isEmpty: if IsTouched in acc.flags and acc.isEmpty:

View File

@ -9,6 +9,8 @@ import unittest2, eth/trie/[hexary, db],
../nimbus/db/state_db, stew/byteutils, eth/common, ../nimbus/db/state_db, stew/byteutils, eth/common,
stew/ranges stew/ranges
include ../nimbus/db/accounts_cache
proc stateDBMain*() = proc stateDBMain*() =
suite "Account State DB": suite "Account State DB":
setup: setup:
@ -45,3 +47,68 @@ proc stateDBMain*() =
stateDB.setCode(address, code.toRange) stateDB.setCode(address, code.toRange)
check stateDB.isDeadAccount(address) == true check stateDB.isDeadAccount(address) == true
check stateDB.accountExists(address) == true check stateDB.accountExists(address) == true
test "clone storage":
var x = RefAccount(
overlayStorage: initTable[UInt256, UInt256](),
originalStorage: newTable[UInt256, UInt256]()
)
x.overlayStorage[10.u256] = 11.u256
x.overlayStorage[11.u256] = 12.u256
x.originalStorage[10.u256] = 11.u256
x.originalStorage[11.u256] = 12.u256
var y = x.clone(cloneStorage = true)
y.overlayStorage[12.u256] = 13.u256
y.originalStorage[12.u256] = 13.u256
check 12.u256 notin x.overlayStorage
check 12.u256 in y.overlayStorage
check x.overlayStorage.len == 2
check y.overlayStorage.len == 3
check 12.u256 in x.originalStorage
check 12.u256 in y.originalStorage
check x.originalStorage.len == 3
check y.originalStorage.len == 3
test "accounts cache":
func initAddr(z: int): EthAddress =
result[^1] = z.byte
var ac = init(AccountsCache, memDB, emptyRlpHash, true)
var addr1 = initAddr(1)
check ac.isDeadAccount(addr1) == true
check ac.accountExists(addr1) == false
check ac.hasCodeOrNonce(addr1) == false
ac.setBalance(addr1, 1000.u256)
check ac.getBalance(addr1) == 1000.u256
ac.subBalance(addr1, 100.u256)
check ac.getBalance(addr1) == 900.u256
ac.addBalance(addr1, 200.u256)
check ac.getBalance(addr1) == 1100.u256
ac.setNonce(addr1, 1)
check ac.getNonce(addr1) == 1
ac.incNonce(addr1)
check ac.getNonce(addr1) == 2
var code = hexToSeqByte("0x0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6").toRange
ac.setCode(addr1, code)
check ac.getCode(addr1) == code
ac.setStorage(addr1, 1.u256, 10.u256)
check ac.getStorage(addr1, 1.u256) == 10.u256
check ac.getCommittedStorage(addr1, 1.u256) == 0.u256
check ac.hasCodeOrNonce(addr1) == true
check ac.getCodeSize(addr1) == code.len
when isMainModule:
stateDBMain()