[skip ci] add accounts cache test
This commit is contained in:
parent
5795bf52e0
commit
727b477fca
|
@ -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:
|
||||||
|
|
|
@ -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()
|
||||||
|
|
Loading…
Reference in New Issue