rlp: don't use ranges / experimental features (#495)
This commit is contained in:
parent
73e9199ebf
commit
4ade5797ee
|
@ -8,7 +8,7 @@
|
||||||
# those terms.
|
# those terms.
|
||||||
|
|
||||||
import
|
import
|
||||||
parseopt, strutils, macros, os, times, json, stew/[byteutils, ranges],
|
parseopt, strutils, macros, os, times, json, stew/[byteutils],
|
||||||
chronos, eth/[keys, common, p2p, net/nat], chronicles, nimcrypto/hash,
|
chronos, eth/[keys, common, p2p, net/nat], chronicles, nimcrypto/hash,
|
||||||
eth/p2p/bootnodes, eth/p2p/rlpx_protocols/whisper_protocol,
|
eth/p2p/bootnodes, eth/p2p/rlpx_protocols/whisper_protocol,
|
||||||
./db/select_backend,
|
./db/select_backend,
|
||||||
|
|
|
@ -19,7 +19,7 @@ type
|
||||||
RefAccount = ref object
|
RefAccount = ref object
|
||||||
account: Account
|
account: Account
|
||||||
flags: AccountFlags
|
flags: AccountFlags
|
||||||
code: ByteRange
|
code: seq[byte]
|
||||||
originalStorage: TableRef[UInt256, UInt256]
|
originalStorage: TableRef[UInt256, UInt256]
|
||||||
overlayStorage: Table[UInt256, UInt256]
|
overlayStorage: Table[UInt256, UInt256]
|
||||||
|
|
||||||
|
@ -94,13 +94,6 @@ proc safeDispose*(ac: var AccountsCache, sp: Savepoint) {.inline.} =
|
||||||
if (not isNil(sp)) and (sp.state == Pending):
|
if (not isNil(sp)) and (sp.state == Pending):
|
||||||
ac.rollback(sp)
|
ac.rollback(sp)
|
||||||
|
|
||||||
template createRangeFromAddress(address: EthAddress): ByteRange =
|
|
||||||
## XXX: The name of this proc is intentionally long, because it
|
|
||||||
## performs a memory allocation and data copying that may be eliminated
|
|
||||||
## in the future. Avoid renaming it to something similar as `toRange`, so
|
|
||||||
## it can remain searchable in the code.
|
|
||||||
toRange(@address)
|
|
||||||
|
|
||||||
proc getAccount(ac: AccountsCache, address: EthAddress, shouldCreate = true): RefAccount =
|
proc getAccount(ac: AccountsCache, address: EthAddress, shouldCreate = true): RefAccount =
|
||||||
# search account from layers of cache
|
# search account from layers of cache
|
||||||
var sp = ac.savePoint
|
var sp = ac.savePoint
|
||||||
|
@ -111,7 +104,7 @@ proc getAccount(ac: AccountsCache, address: EthAddress, shouldCreate = true): Re
|
||||||
sp = sp.parentSavepoint
|
sp = sp.parentSavepoint
|
||||||
|
|
||||||
# not found in cache, look into state trie
|
# not found in cache, look into state trie
|
||||||
let recordFound = ac.trie.get(createRangeFromAddress address)
|
let recordFound = ac.trie.get(address)
|
||||||
if recordFound.len > 0:
|
if recordFound.len > 0:
|
||||||
# we found it
|
# we found it
|
||||||
result = RefAccount(
|
result = RefAccount(
|
||||||
|
@ -154,11 +147,11 @@ proc exists(acc: RefAccount): bool =
|
||||||
else:
|
else:
|
||||||
result = IsNew notin acc.flags
|
result = IsNew notin acc.flags
|
||||||
|
|
||||||
template createTrieKeyFromSlot(slot: UInt256): ByteRange =
|
template createTrieKeyFromSlot(slot: UInt256): auto =
|
||||||
# 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
|
||||||
# prefix and leading zeros:
|
# prefix and leading zeros:
|
||||||
@(slot.toByteArrayBE).toRange
|
slot.toByteArrayBE
|
||||||
# Original py-evm code:
|
# Original py-evm code:
|
||||||
# pad32(int_to_big_endian(slot))
|
# pad32(int_to_big_endian(slot))
|
||||||
# morally equivalent to toByteRange_Unnecessary but with different types
|
# morally equivalent to toByteRange_Unnecessary but with different types
|
||||||
|
@ -204,7 +197,7 @@ proc kill(acc: RefAccount) =
|
||||||
acc.overlayStorage.clear()
|
acc.overlayStorage.clear()
|
||||||
acc.originalStorage = nil
|
acc.originalStorage = nil
|
||||||
acc.account = newAccount()
|
acc.account = newAccount()
|
||||||
acc.code = default(ByteRange)
|
acc.code = default(seq[byte])
|
||||||
|
|
||||||
type
|
type
|
||||||
PersistMode = enum
|
PersistMode = enum
|
||||||
|
@ -223,7 +216,7 @@ proc persistMode(acc: RefAccount): PersistMode =
|
||||||
|
|
||||||
proc persistCode(acc: RefAccount, db: TrieDatabaseRef) =
|
proc persistCode(acc: RefAccount, db: TrieDatabaseRef) =
|
||||||
if acc.code.len != 0:
|
if acc.code.len != 0:
|
||||||
db.put(contractHashKey(acc.account.codeHash).toOpenArray, acc.code.toOpenArray)
|
db.put(contractHashKey(acc.account.codeHash).toOpenArray, acc.code)
|
||||||
|
|
||||||
proc persistStorage(acc: RefAccount, db: TrieDatabaseRef) =
|
proc persistStorage(acc: RefAccount, db: TrieDatabaseRef) =
|
||||||
if acc.overlayStorage.len == 0:
|
if acc.overlayStorage.len == 0:
|
||||||
|
@ -237,7 +230,7 @@ proc persistStorage(acc: RefAccount, db: TrieDatabaseRef) =
|
||||||
let slotAsKey = createTrieKeyFromSlot slot
|
let slotAsKey = createTrieKeyFromSlot slot
|
||||||
|
|
||||||
if value > 0:
|
if value > 0:
|
||||||
let encodedValue = rlp.encode(value).toRange
|
let encodedValue = rlp.encode(value)
|
||||||
accountTrie.put(slotAsKey, encodedValue)
|
accountTrie.put(slotAsKey, encodedValue)
|
||||||
else:
|
else:
|
||||||
accountTrie.del(slotAsKey)
|
accountTrie.del(slotAsKey)
|
||||||
|
@ -245,7 +238,7 @@ proc persistStorage(acc: RefAccount, db: TrieDatabaseRef) =
|
||||||
# map slothash back to slot value
|
# map slothash back to slot value
|
||||||
# see iterator storage below
|
# see iterator storage below
|
||||||
# slotHash can be obtained from accountTrie.put?
|
# slotHash can be obtained from accountTrie.put?
|
||||||
let slotHash = keccakHash(slotAsKey.toOpenArray)
|
let slotHash = keccakHash(slotAsKey)
|
||||||
db.put(slotHashToSlotKey(slotHash.data).toOpenArray, rlp.encode(slot))
|
db.put(slotHashToSlotKey(slotHash.data).toOpenArray, rlp.encode(slot))
|
||||||
acc.account.storageRoot = accountTrie.rootHash
|
acc.account.storageRoot = accountTrie.rootHash
|
||||||
|
|
||||||
|
@ -276,7 +269,7 @@ proc getNonce*(ac: AccountsCache, address: EthAddress): AccountNonce {.inline.}
|
||||||
if acc.isNil: emptyAcc.nonce
|
if acc.isNil: emptyAcc.nonce
|
||||||
else: acc.account.nonce
|
else: acc.account.nonce
|
||||||
|
|
||||||
proc getCode*(ac: AccountsCache, address: EthAddress): ByteRange =
|
proc getCode*(ac: AccountsCache, address: EthAddress): seq[byte] =
|
||||||
let acc = ac.getAccount(address, false)
|
let acc = ac.getAccount(address, false)
|
||||||
if acc.isNil:
|
if acc.isNil:
|
||||||
return
|
return
|
||||||
|
@ -285,7 +278,7 @@ proc getCode*(ac: AccountsCache, address: EthAddress): ByteRange =
|
||||||
result = acc.code
|
result = acc.code
|
||||||
else:
|
else:
|
||||||
let data = ac.db.get(contractHashKey(acc.account.codeHash).toOpenArray)
|
let data = ac.db.get(contractHashKey(acc.account.codeHash).toOpenArray)
|
||||||
acc.code = data.toRange
|
acc.code = data
|
||||||
acc.flags.incl CodeLoaded
|
acc.flags.incl CodeLoaded
|
||||||
result = acc.code
|
result = acc.code
|
||||||
|
|
||||||
|
@ -353,10 +346,10 @@ proc setNonce*(ac: var AccountsCache, address: EthAddress, nonce: AccountNonce)
|
||||||
proc incNonce*(ac: var AccountsCache, address: EthAddress) {.inline.} =
|
proc incNonce*(ac: var AccountsCache, address: EthAddress) {.inline.} =
|
||||||
ac.setNonce(address, ac.getNonce(address) + 1)
|
ac.setNonce(address, ac.getNonce(address) + 1)
|
||||||
|
|
||||||
proc setCode*(ac: var AccountsCache, address: EthAddress, code: ByteRange) =
|
proc setCode*(ac: var AccountsCache, address: EthAddress, code: seq[byte]) =
|
||||||
let acc = ac.getAccount(address)
|
let acc = ac.getAccount(address)
|
||||||
acc.flags.incl {IsTouched, IsAlive}
|
acc.flags.incl {IsTouched, IsAlive}
|
||||||
let codeHash = keccakHash(code.toOpenArray)
|
let codeHash = keccakHash(code)
|
||||||
if acc.account.codeHash != codeHash:
|
if acc.account.codeHash != codeHash:
|
||||||
var acc = ac.makeDirty(address)
|
var acc = ac.makeDirty(address)
|
||||||
acc.account.codeHash = codeHash
|
acc.account.codeHash = codeHash
|
||||||
|
@ -406,9 +399,9 @@ proc persist*(ac: var AccountsCache) =
|
||||||
# storageRoot must be updated first
|
# storageRoot must be updated first
|
||||||
# before persisting account into merkle trie
|
# before persisting account into merkle trie
|
||||||
acc.persistStorage(ac.db)
|
acc.persistStorage(ac.db)
|
||||||
ac.trie.put createRangeFromAddress(address), rlp.encode(acc.account).toRange
|
ac.trie.put address, rlp.encode(acc.account)
|
||||||
of Remove:
|
of Remove:
|
||||||
ac.trie.del createRangeFromAddress(address)
|
ac.trie.del address
|
||||||
of DoNothing:
|
of DoNothing:
|
||||||
discard
|
discard
|
||||||
ac.savePoint.cache.clear()
|
ac.savePoint.cache.clear()
|
||||||
|
@ -421,5 +414,5 @@ iterator storage*(ac: AccountsCache, address: EthAddress): (UInt256, UInt256) =
|
||||||
|
|
||||||
for slot, value in trie:
|
for slot, value in trie:
|
||||||
if slot.len != 0:
|
if slot.len != 0:
|
||||||
var keyData = ac.db.get(slotHashToSlotKey(slot.toOpenArray).toOpenArray).toRange
|
var keyData = ac.db.get(slotHashToSlotKey(slot).toOpenArray)
|
||||||
yield (rlp.decode(keyData, UInt256), rlp.decode(value, UInt256))
|
yield (rlp.decode(keyData, UInt256), rlp.decode(value, UInt256))
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import eth/trie/db, stew/ranges
|
import eth/trie/db
|
||||||
|
|
||||||
type
|
type
|
||||||
CaptureFlags* {.pure.} = enum
|
CaptureFlags* {.pure.} = enum
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
|
|
||||||
import
|
import
|
||||||
sequtils, algorithm,
|
sequtils, algorithm,
|
||||||
stew/[byteutils, ranges], eth/trie/[hexary, db],
|
stew/[byteutils], eth/trie/[hexary, db],
|
||||||
eth/[common, rlp], chronicles,
|
eth/[common, rlp], chronicles,
|
||||||
../errors, ../constants, ./storage_types,
|
../errors, ../constants, ./storage_types,
|
||||||
../utils, ../config
|
../utils, ../config
|
||||||
|
@ -39,7 +39,7 @@ proc exists*(self: BaseChainDB, hash: Hash256): bool =
|
||||||
self.db.contains(hash.data)
|
self.db.contains(hash.data)
|
||||||
|
|
||||||
proc getBlockHeader*(self: BaseChainDB; blockHash: Hash256, output: var BlockHeader): bool =
|
proc getBlockHeader*(self: BaseChainDB; blockHash: Hash256, output: var BlockHeader): bool =
|
||||||
let data = self.db.get(genericHashKey(blockHash).toOpenArray).toRange
|
let data = self.db.get(genericHashKey(blockHash).toOpenArray)
|
||||||
if data.len != 0:
|
if data.len != 0:
|
||||||
output = rlp.decode(data, BlockHeader)
|
output = rlp.decode(data, BlockHeader)
|
||||||
result = true
|
result = true
|
||||||
|
@ -52,7 +52,7 @@ proc getBlockHeader*(self: BaseChainDB, blockHash: Hash256): BlockHeader =
|
||||||
raise newException(BlockNotFound, "No block with hash " & blockHash.data.toHex)
|
raise newException(BlockNotFound, "No block with hash " & blockHash.data.toHex)
|
||||||
|
|
||||||
proc getHash(self: BaseChainDB, key: DbKey, output: var Hash256): bool {.inline.} =
|
proc getHash(self: BaseChainDB, key: DbKey, output: var Hash256): bool {.inline.} =
|
||||||
let data = self.db.get(key.toOpenArray).toRange
|
let data = self.db.get(key.toOpenArray)
|
||||||
if data.len != 0:
|
if data.len != 0:
|
||||||
output = rlp.decode(data, Hash256)
|
output = rlp.decode(data, Hash256)
|
||||||
result = true
|
result = true
|
||||||
|
@ -85,7 +85,7 @@ proc getBlockHeader*(self: BaseChainDB; n: BlockNumber): BlockHeader =
|
||||||
self.getBlockHeader(self.getBlockHash(n))
|
self.getBlockHeader(self.getBlockHash(n))
|
||||||
|
|
||||||
proc getScore*(self: BaseChainDB; blockHash: Hash256): Uint256 =
|
proc getScore*(self: BaseChainDB; blockHash: Hash256): Uint256 =
|
||||||
rlp.decode(self.db.get(blockHashToScoreKey(blockHash).toOpenArray).toRange, Uint256)
|
rlp.decode(self.db.get(blockHashToScoreKey(blockHash).toOpenArray), Uint256)
|
||||||
|
|
||||||
proc getAncestorsHashes*(self: BaseChainDB, limit: Uint256, header: BlockHeader): seq[Hash256] =
|
proc getAncestorsHashes*(self: BaseChainDB, limit: Uint256, header: BlockHeader): seq[Hash256] =
|
||||||
var ancestorCount = min(header.blockNumber, limit).truncate(int)
|
var ancestorCount = min(header.blockNumber, limit).truncate(int)
|
||||||
|
@ -121,17 +121,17 @@ proc persistTransactions*(self: BaseChainDB, blockNumber: BlockNumber, transacti
|
||||||
var trie = initHexaryTrie(self.db)
|
var trie = initHexaryTrie(self.db)
|
||||||
for idx, tx in transactions:
|
for idx, tx in transactions:
|
||||||
let
|
let
|
||||||
encodedTx = rlp.encode(tx).toRange
|
encodedTx = rlp.encode(tx)
|
||||||
txHash = keccakHash(encodedTx.toOpenArray)
|
txHash = keccakHash(encodedTx)
|
||||||
txKey: TransactionKey = (blockNumber, idx)
|
txKey: TransactionKey = (blockNumber, idx)
|
||||||
trie.put(rlp.encode(idx).toRange, encodedTx)
|
trie.put(rlp.encode(idx), encodedTx)
|
||||||
self.db.put(transactionHashToBlockKey(txHash).toOpenArray, rlp.encode(txKey))
|
self.db.put(transactionHashToBlockKey(txHash).toOpenArray, rlp.encode(txKey))
|
||||||
|
|
||||||
iterator getBlockTransactionData(self: BaseChainDB, transactionRoot: Hash256): BytesRange =
|
iterator getBlockTransactionData(self: BaseChainDB, transactionRoot: Hash256): seq[byte] =
|
||||||
var transactionDb = initHexaryTrie(self.db, transactionRoot)
|
var transactionDb = initHexaryTrie(self.db, transactionRoot)
|
||||||
var transactionIdx = 0
|
var transactionIdx = 0
|
||||||
while true:
|
while true:
|
||||||
let transactionKey = rlp.encode(transactionIdx).toRange
|
let transactionKey = rlp.encode(transactionIdx)
|
||||||
if transactionKey in transactionDb:
|
if transactionKey in transactionDb:
|
||||||
yield transactionDb.get(transactionKey)
|
yield transactionDb.get(transactionKey)
|
||||||
else:
|
else:
|
||||||
|
@ -142,7 +142,7 @@ iterator getBlockTransactionHashes(self: BaseChainDB, blockHeader: BlockHeader):
|
||||||
## Returns an iterable of the transaction hashes from th block specified
|
## Returns an iterable of the transaction hashes from th block specified
|
||||||
## by the given block header.
|
## by the given block header.
|
||||||
for encodedTx in self.getBlockTransactionData(blockHeader.txRoot):
|
for encodedTx in self.getBlockTransactionData(blockHeader.txRoot):
|
||||||
yield keccakHash(encodedTx.toOpenArray)
|
yield keccakHash(encodedTx)
|
||||||
|
|
||||||
proc getBlockBody*(self: BaseChainDB, blockHash: Hash256, output: var BlockBody): bool =
|
proc getBlockBody*(self: BaseChainDB, blockHash: Hash256, output: var BlockBody): bool =
|
||||||
var header: BlockHeader
|
var header: BlockHeader
|
||||||
|
@ -154,7 +154,7 @@ proc getBlockBody*(self: BaseChainDB, blockHash: Hash256, output: var BlockBody)
|
||||||
output.transactions.add(rlp.decode(encodedTx, Transaction))
|
output.transactions.add(rlp.decode(encodedTx, Transaction))
|
||||||
|
|
||||||
if header.ommersHash != EMPTY_UNCLE_HASH:
|
if header.ommersHash != EMPTY_UNCLE_HASH:
|
||||||
let encodedUncles = self.db.get(genericHashKey(header.ommersHash).toOpenArray).toRange
|
let encodedUncles = self.db.get(genericHashKey(header.ommersHash).toOpenArray)
|
||||||
if encodedUncles.len != 0:
|
if encodedUncles.len != 0:
|
||||||
output.uncles = rlp.decode(encodedUncles, seq[BlockHeader])
|
output.uncles = rlp.decode(encodedUncles, seq[BlockHeader])
|
||||||
else:
|
else:
|
||||||
|
@ -172,7 +172,7 @@ proc getUncleHashes*(self: BaseChainDB, blockHashes: openArray[Hash256]): seq[Ha
|
||||||
|
|
||||||
proc getTransactionKey*(self: BaseChainDB, transactionHash: Hash256): tuple[blockNumber: BlockNumber, index: int] {.inline.} =
|
proc getTransactionKey*(self: BaseChainDB, transactionHash: Hash256): tuple[blockNumber: BlockNumber, index: int] {.inline.} =
|
||||||
let
|
let
|
||||||
tx = self.db.get(transactionHashToBlockKey(transactionHash).toOpenArray).toRange
|
tx = self.db.get(transactionHashToBlockKey(transactionHash).toOpenArray)
|
||||||
key = rlp.decode(tx, TransactionKey)
|
key = rlp.decode(tx, TransactionKey)
|
||||||
return (key.blockNumber, key.index)
|
return (key.blockNumber, key.index)
|
||||||
|
|
||||||
|
@ -217,13 +217,13 @@ proc headerExists*(self: BaseChainDB; blockHash: Hash256): bool =
|
||||||
proc persistReceipts*(self: BaseChainDB, receipts: openArray[Receipt]) =
|
proc persistReceipts*(self: BaseChainDB, receipts: openArray[Receipt]) =
|
||||||
var trie = initHexaryTrie(self.db)
|
var trie = initHexaryTrie(self.db)
|
||||||
for idx, rec in receipts:
|
for idx, rec in receipts:
|
||||||
trie.put(rlp.encode(idx).toRange, rlp.encode(rec).toRange)
|
trie.put(rlp.encode(idx), rlp.encode(rec))
|
||||||
|
|
||||||
iterator getReceipts*(self: BaseChainDB; header: BlockHeader): Receipt =
|
iterator getReceipts*(self: BaseChainDB; header: BlockHeader): Receipt =
|
||||||
var receiptDb = initHexaryTrie(self.db, header.receiptRoot)
|
var receiptDb = initHexaryTrie(self.db, header.receiptRoot)
|
||||||
var receiptIdx = 0
|
var receiptIdx = 0
|
||||||
while true:
|
while true:
|
||||||
let receiptKey = rlp.encode(receiptIdx).toRange
|
let receiptKey = rlp.encode(receiptIdx)
|
||||||
if receiptKey in receiptDb:
|
if receiptKey in receiptDb:
|
||||||
let receiptData = receiptDb.get(receiptKey)
|
let receiptData = receiptDb.get(receiptKey)
|
||||||
yield rlp.decode(receiptData, Receipt)
|
yield rlp.decode(receiptData, Receipt)
|
||||||
|
@ -231,11 +231,6 @@ iterator getReceipts*(self: BaseChainDB; header: BlockHeader): Receipt =
|
||||||
break
|
break
|
||||||
inc receiptIdx
|
inc receiptIdx
|
||||||
|
|
||||||
iterator getBlockTransactions(self: BaseChainDB; transactionRoot: Hash256;
|
|
||||||
transactionClass: typedesc): transactionClass =
|
|
||||||
for encodedTransaction in self.getBlockTransactionData(transactionRoot):
|
|
||||||
yield rlp.decode(encodedTransaction, transactionClass)
|
|
||||||
|
|
||||||
proc persistHeaderToDb*(self: BaseChainDB; header: BlockHeader): seq[BlockHeader] =
|
proc persistHeaderToDb*(self: BaseChainDB; header: BlockHeader): seq[BlockHeader] =
|
||||||
let isGenesis = header.parentHash == GENESIS_PARENT_HASH
|
let isGenesis = header.parentHash == GENESIS_PARENT_HASH
|
||||||
let headerHash = header.blockHash
|
let headerHash = header.blockHash
|
||||||
|
@ -259,40 +254,9 @@ proc persistHeaderToDb*(self: BaseChainDB; header: BlockHeader): seq[BlockHeader
|
||||||
if score > headScore:
|
if score > headScore:
|
||||||
result = self.setAsCanonicalChainHead(headerHash)
|
result = self.setAsCanonicalChainHead(headerHash)
|
||||||
|
|
||||||
proc addTransactionToCanonicalChain(self: BaseChainDB, txHash: Hash256,
|
|
||||||
blockHeader: BlockHeader, index: int) =
|
|
||||||
let k: TransactionKey = (blockHeader.blockNumber, index)
|
|
||||||
self.db.put(transactionHashToBlockKey(txHash).toOpenArray, rlp.encode(k))
|
|
||||||
|
|
||||||
proc persistUncles*(self: BaseChainDB, uncles: openarray[BlockHeader]): Hash256 =
|
proc persistUncles*(self: BaseChainDB, uncles: openarray[BlockHeader]): Hash256 =
|
||||||
## Persists the list of uncles to the database.
|
## Persists the list of uncles to the database.
|
||||||
## Returns the uncles hash.
|
## Returns the uncles hash.
|
||||||
let enc = rlp.encode(uncles)
|
let enc = rlp.encode(uncles)
|
||||||
result = keccakHash(enc)
|
result = keccakHash(enc)
|
||||||
self.db.put(genericHashKey(result).toOpenArray, enc)
|
self.db.put(genericHashKey(result).toOpenArray, enc)
|
||||||
|
|
||||||
#proc persistBlockToDb*(self: BaseChainDB; blk: Block): ValidationResult =
|
|
||||||
# ## Persist the given block's header and uncles.
|
|
||||||
# ## Assumes all block transactions have been persisted already.
|
|
||||||
# let newCanonicalHeaders = self.persistHeaderToDb(blk.header)
|
|
||||||
# for header in newCanonicalHeaders:
|
|
||||||
# var index = 0
|
|
||||||
# for txHash in self.getBlockTransactionHashes(header):
|
|
||||||
# self.addTransactionToCanonicalChain(txHash, header, index)
|
|
||||||
# inc index
|
|
||||||
#
|
|
||||||
# if blk.uncles.len != 0:
|
|
||||||
# let ommersHash = self.persistUncles(blk.uncles)
|
|
||||||
# if ommersHash != blk.header.ommersHash:
|
|
||||||
# debug "ommersHash mismatch"
|
|
||||||
# return ValidationResult.Error
|
|
||||||
|
|
||||||
# Deprecated:
|
|
||||||
proc getBlockHeaderByHash*(self: BaseChainDB; blockHash: Hash256): BlockHeader {.deprecated.} =
|
|
||||||
self.getBlockHeader(blockHash)
|
|
||||||
|
|
||||||
proc lookupBlockHash*(self: BaseChainDB; n: BlockNumber): Hash256 {.deprecated.} =
|
|
||||||
self.getBlockHash(n)
|
|
||||||
|
|
||||||
proc getCanonicalBlockHeaderByNumber*(self: BaseChainDB; n: BlockNumber): BlockHeader {.deprecated.} =
|
|
||||||
self.getBlockHeader(n)
|
|
||||||
|
|
|
@ -72,25 +72,18 @@ proc newAccountStateDB*(backingStore: TrieDatabaseRef,
|
||||||
when aleth_compat:
|
when aleth_compat:
|
||||||
result.cleared = initHashSet[EthAddress]()
|
result.cleared = initHashSet[EthAddress]()
|
||||||
|
|
||||||
template createRangeFromAddress(address: EthAddress): ByteRange =
|
|
||||||
## XXX: The name of this proc is intentionally long, because it
|
|
||||||
## performs a memory allocation and data copying that may be eliminated
|
|
||||||
## in the future. Avoid renaming it to something similar as `toRange`, so
|
|
||||||
## it can remain searchable in the code.
|
|
||||||
toRange(@address)
|
|
||||||
|
|
||||||
proc getAccount*(db: AccountStateDB, address: EthAddress): Account =
|
proc getAccount*(db: AccountStateDB, address: EthAddress): Account =
|
||||||
let recordFound = db.trie.get(createRangeFromAddress address)
|
let recordFound = db.trie.get(address)
|
||||||
if recordFound.len > 0:
|
if recordFound.len > 0:
|
||||||
result = rlp.decode(recordFound, Account)
|
result = rlp.decode(recordFound, Account)
|
||||||
else:
|
else:
|
||||||
result = newAccount()
|
result = newAccount()
|
||||||
|
|
||||||
proc setAccount*(db: AccountStateDB, address: EthAddress, account: Account) =
|
proc setAccount*(db: AccountStateDB, address: EthAddress, account: Account) =
|
||||||
db.trie.put createRangeFromAddress(address), rlp.encode(account).toRange
|
db.trie.put(address, rlp.encode(account))
|
||||||
|
|
||||||
proc deleteAccount*(db: AccountStateDB, address: EthAddress) =
|
proc deleteAccount*(db: AccountStateDB, address: EthAddress) =
|
||||||
db.trie.del createRangeFromAddress(address)
|
db.trie.del(address)
|
||||||
|
|
||||||
proc getCodeHash*(db: AccountStateDB, address: EthAddress): Hash256 =
|
proc getCodeHash*(db: AccountStateDB, address: EthAddress): Hash256 =
|
||||||
let account = db.getAccount(address)
|
let account = db.getAccount(address)
|
||||||
|
@ -111,11 +104,10 @@ proc addBalance*(db: var AccountStateDB, address: EthAddress, delta: UInt256) =
|
||||||
proc subBalance*(db: var AccountStateDB, address: EthAddress, delta: UInt256) =
|
proc subBalance*(db: var AccountStateDB, address: EthAddress, delta: UInt256) =
|
||||||
db.setBalance(address, db.getBalance(address) - delta)
|
db.setBalance(address, db.getBalance(address) - delta)
|
||||||
|
|
||||||
template createTrieKeyFromSlot(slot: UInt256): ByteRange =
|
template createTrieKeyFromSlot(slot: UInt256): auto =
|
||||||
# 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
|
||||||
# prefix and leading zeros:
|
# prefix and leading zeros:
|
||||||
@(slot.toByteArrayBE).toRange
|
slot.toByteArrayBE
|
||||||
# Original py-evm code:
|
# Original py-evm code:
|
||||||
# pad32(int_to_big_endian(slot))
|
# pad32(int_to_big_endian(slot))
|
||||||
# morally equivalent to toByteRange_Unnecessary but with different types
|
# morally equivalent to toByteRange_Unnecessary but with different types
|
||||||
|
@ -147,7 +139,7 @@ proc setStorage*(db: var AccountStateDB,
|
||||||
let slotAsKey = createTrieKeyFromSlot slot
|
let slotAsKey = createTrieKeyFromSlot slot
|
||||||
|
|
||||||
if value > 0:
|
if value > 0:
|
||||||
let encodedValue = rlp.encode(value).toRange
|
let encodedValue = rlp.encode(value)
|
||||||
accountTrie.put(slotAsKey, encodedValue)
|
accountTrie.put(slotAsKey, encodedValue)
|
||||||
else:
|
else:
|
||||||
accountTrie.del(slotAsKey)
|
accountTrie.del(slotAsKey)
|
||||||
|
@ -171,7 +163,7 @@ iterator storage*(db: AccountStateDB, address: EthAddress): (UInt256, UInt256) =
|
||||||
|
|
||||||
for key, value in trie:
|
for key, value in trie:
|
||||||
if key.len != 0:
|
if key.len != 0:
|
||||||
var keyData = triedb.get(slotHashToSlotKey(key.toOpenArray).toOpenArray).toRange
|
var keyData = triedb.get(slotHashToSlotKey(key).toOpenArray)
|
||||||
yield (rlp.decode(keyData, UInt256), rlp.decode(value, UInt256))
|
yield (rlp.decode(keyData, UInt256), rlp.decode(value, UInt256))
|
||||||
|
|
||||||
proc getStorage*(db: AccountStateDB, address: EthAddress, slot: UInt256): (UInt256, bool) =
|
proc getStorage*(db: AccountStateDB, address: EthAddress, slot: UInt256): (UInt256, bool) =
|
||||||
|
@ -201,25 +193,24 @@ proc getNonce*(db: AccountStateDB, address: EthAddress): AccountNonce =
|
||||||
proc incNonce*(db: AccountStateDB, address: EthAddress) {.inline.} =
|
proc incNonce*(db: AccountStateDB, address: EthAddress) {.inline.} =
|
||||||
db.setNonce(address, db.getNonce(address) + 1)
|
db.setNonce(address, db.getNonce(address) + 1)
|
||||||
|
|
||||||
proc setCode*(db: AccountStateDB, address: EthAddress, code: ByteRange) =
|
proc setCode*(db: AccountStateDB, address: EthAddress, code: openArray[byte]) =
|
||||||
var account = db.getAccount(address)
|
var account = db.getAccount(address)
|
||||||
# TODO: implement JournalDB to store code and storage
|
# TODO: implement JournalDB to store code and storage
|
||||||
# also use JournalDB to revert state trie
|
# also use JournalDB to revert state trie
|
||||||
|
|
||||||
let
|
let
|
||||||
newCodeHash = keccakHash(code.toOpenArray)
|
newCodeHash = keccakHash(code)
|
||||||
triedb = trieDB(db)
|
triedb = trieDB(db)
|
||||||
|
|
||||||
if code.len != 0:
|
if code.len != 0:
|
||||||
triedb.put(contractHashKey(newCodeHash).toOpenArray, code.toOpenArray)
|
triedb.put(contractHashKey(newCodeHash).toOpenArray, code)
|
||||||
|
|
||||||
account.codeHash = newCodeHash
|
account.codeHash = newCodeHash
|
||||||
db.setAccount(address, account)
|
db.setAccount(address, account)
|
||||||
|
|
||||||
proc getCode*(db: AccountStateDB, address: EthAddress): ByteRange =
|
proc getCode*(db: AccountStateDB, address: EthAddress): seq[byte] =
|
||||||
let triedb = trieDB(db)
|
let triedb = trieDB(db)
|
||||||
let data = triedb.get(contractHashKey(db.getCodeHash(address)).toOpenArray)
|
triedb.get(contractHashKey(db.getCodeHash(address)).toOpenArray)
|
||||||
data.toRange
|
|
||||||
|
|
||||||
proc hasCodeOrNonce*(db: AccountStateDB, address: EthAddress): bool {.inline.} =
|
proc hasCodeOrNonce*(db: AccountStateDB, address: EthAddress): bool {.inline.} =
|
||||||
db.getNonce(address) != 0 or db.getCodeHash(address) != EMPTY_SHA3
|
db.getNonce(address) != 0 or db.getCodeHash(address) != EMPTY_SHA3
|
||||||
|
@ -229,10 +220,10 @@ proc dumpAccount*(db: AccountStateDB, addressS: string): string =
|
||||||
return fmt"{addressS}: Storage: {db.getStorage(address, 0.u256)}; getAccount: {db.getAccount address}"
|
return fmt"{addressS}: Storage: {db.getStorage(address, 0.u256)}; getAccount: {db.getAccount address}"
|
||||||
|
|
||||||
proc accountExists*(db: AccountStateDB, address: EthAddress): bool =
|
proc accountExists*(db: AccountStateDB, address: EthAddress): bool =
|
||||||
db.trie.get(createRangeFromAddress address).len > 0
|
db.trie.get(address).len > 0
|
||||||
|
|
||||||
proc isEmptyAccount*(db: AccountStateDB, address: EthAddress): bool =
|
proc isEmptyAccount*(db: AccountStateDB, address: EthAddress): bool =
|
||||||
let recordFound = db.trie.get(createRangeFromAddress address)
|
let recordFound = db.trie.get(address)
|
||||||
assert(recordFound.len > 0)
|
assert(recordFound.len > 0)
|
||||||
|
|
||||||
let account = rlp.decode(recordFound, Account)
|
let account = rlp.decode(recordFound, Account)
|
||||||
|
@ -241,7 +232,7 @@ proc isEmptyAccount*(db: AccountStateDB, address: EthAddress): bool =
|
||||||
account.nonce == 0
|
account.nonce == 0
|
||||||
|
|
||||||
proc isDeadAccount*(db: AccountStateDB, address: EthAddress): bool =
|
proc isDeadAccount*(db: AccountStateDB, address: EthAddress): bool =
|
||||||
let recordFound = db.trie.get(createRangeFromAddress address)
|
let recordFound = db.trie.get(address)
|
||||||
if recordFound.len > 0:
|
if recordFound.len > 0:
|
||||||
let account = rlp.decode(recordFound, Account)
|
let account = rlp.decode(recordFound, Account)
|
||||||
result = account.codeHash == EMPTY_SHA3 and
|
result = account.codeHash == EMPTY_SHA3 and
|
||||||
|
@ -281,7 +272,7 @@ proc getBalance*(db: ReadOnlyStateDB, address: EthAddress): UInt256 {.borrow.}
|
||||||
proc getStorageRoot*(db: ReadOnlyStateDB, address: EthAddress): Hash256 {.borrow.}
|
proc getStorageRoot*(db: ReadOnlyStateDB, address: EthAddress): Hash256 {.borrow.}
|
||||||
proc getStorage*(db: ReadOnlyStateDB, address: EthAddress, slot: UInt256): (UInt256, bool) {.borrow.}
|
proc getStorage*(db: ReadOnlyStateDB, address: EthAddress, slot: UInt256): (UInt256, bool) {.borrow.}
|
||||||
proc getNonce*(db: ReadOnlyStateDB, address: EthAddress): AccountNonce {.borrow.}
|
proc getNonce*(db: ReadOnlyStateDB, address: EthAddress): AccountNonce {.borrow.}
|
||||||
proc getCode*(db: ReadOnlyStateDB, address: EthAddress): ByteRange {.borrow.}
|
proc getCode*(db: ReadOnlyStateDB, address: EthAddress): seq[byte] {.borrow.}
|
||||||
proc hasCodeOrNonce*(db: ReadOnlyStateDB, address: EthAddress): bool {.borrow.}
|
proc hasCodeOrNonce*(db: ReadOnlyStateDB, address: EthAddress): bool {.borrow.}
|
||||||
proc accountExists*(db: ReadOnlyStateDB, address: EthAddress): bool {.borrow.}
|
proc accountExists*(db: ReadOnlyStateDB, address: EthAddress): bool {.borrow.}
|
||||||
proc isDeadAccount*(db: ReadOnlyStateDB, address: EthAddress): bool {.borrow.}
|
proc isDeadAccount*(db: ReadOnlyStateDB, address: EthAddress): bool {.borrow.}
|
||||||
|
|
|
@ -52,25 +52,12 @@ proc contractHashKey*(h: Hash256): DbKey {.inline.} =
|
||||||
result.data[1 .. 32] = h.data
|
result.data[1 .. 32] = h.data
|
||||||
result.dataEndPos = uint8 32
|
result.dataEndPos = uint8 32
|
||||||
|
|
||||||
const hashHolderKinds = {genericHash, blockHashToScore, transactionHashToBlock}
|
|
||||||
|
|
||||||
template toOpenArray*(k: DbKey): openarray[byte] =
|
template toOpenArray*(k: DbKey): openarray[byte] =
|
||||||
k.data.toOpenArray(0, int(k.dataEndPos))
|
k.data.toOpenArray(0, int(k.dataEndPos))
|
||||||
|
|
||||||
proc hash*(k: DbKey): Hash =
|
proc hash*(k: DbKey): Hash =
|
||||||
result = hash(k.toOpenArray)
|
result = hash(k.toOpenArray)
|
||||||
|
|
||||||
# TODO: this should be added to Nim
|
|
||||||
proc `==`*[T](lhs, rhs: openarray[T]): bool =
|
|
||||||
if lhs.len != rhs.len:
|
|
||||||
return false
|
|
||||||
|
|
||||||
for i in 0 ..< lhs.len:
|
|
||||||
if lhs[i] != rhs[i]:
|
|
||||||
return false
|
|
||||||
|
|
||||||
return true
|
|
||||||
|
|
||||||
proc `==`*(a, b: DbKey): bool {.inline.} =
|
proc `==`*(a, b: DbKey): bool {.inline.} =
|
||||||
a.toOpenArray == b.toOpenArray
|
a.toOpenArray == b.toOpenArray
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import
|
import
|
||||||
tables, json, times,
|
tables, json, times,
|
||||||
eth/[common, rlp, trie], stint, stew/[byteutils, ranges],
|
eth/[common, rlp, trie], stint, stew/[byteutils],
|
||||||
chronicles, eth/trie/db,
|
chronicles, eth/trie/db,
|
||||||
db/[db_chain, state_db], genesis_alloc, config, constants
|
db/[db_chain, state_db], genesis_alloc, config, constants
|
||||||
|
|
||||||
|
@ -29,7 +29,7 @@ func toAddress(n: UInt256): EthAddress =
|
||||||
|
|
||||||
func decodePrealloc(data: seq[byte]): GenesisAlloc =
|
func decodePrealloc(data: seq[byte]): GenesisAlloc =
|
||||||
result = newTable[EthAddress, GenesisAccount]()
|
result = newTable[EthAddress, GenesisAccount]()
|
||||||
for tup in rlp.decode(data.toRange, seq[(UInt256, UInt256)]):
|
for tup in rlp.decode(data, seq[(UInt256, UInt256)]):
|
||||||
result[toAddress(tup[0])] = GenesisAccount(balance: tup[1])
|
result[toAddress(tup[0])] = GenesisAccount(balance: tup[1])
|
||||||
|
|
||||||
proc customNetPrealloc(genesisBlock: JsonNode): GenesisAlloc =
|
proc customNetPrealloc(genesisBlock: JsonNode): GenesisAlloc =
|
||||||
|
@ -105,7 +105,7 @@ proc toBlock*(g: Genesis, db: BaseChainDB = nil): BlockHeader =
|
||||||
|
|
||||||
for address, account in g.alloc:
|
for address, account in g.alloc:
|
||||||
sdb.setAccount(address, newAccount(account.nonce, account.balance))
|
sdb.setAccount(address, newAccount(account.nonce, account.balance))
|
||||||
sdb.setCode(address, account.code.toRange)
|
sdb.setCode(address, account.code)
|
||||||
for k, v in account.storage:
|
for k, v in account.storage:
|
||||||
sdb.setStorage(address, k, v)
|
sdb.setStorage(address, k, v)
|
||||||
|
|
||||||
|
|
|
@ -25,9 +25,6 @@ import
|
||||||
const
|
const
|
||||||
nimbusClientId = "nimbus 0.1.0"
|
nimbusClientId = "nimbus 0.1.0"
|
||||||
|
|
||||||
when not defined(windows):
|
|
||||||
from posix import SIGINT, SIGTERM
|
|
||||||
|
|
||||||
type
|
type
|
||||||
NimbusState = enum
|
NimbusState = enum
|
||||||
Starting, Running, Stopping, Stopped
|
Starting, Running, Stopping, Stopped
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import ../db/db_chain, eth/common, chronicles, ../vm_state, ../vm_types, stew/ranges,
|
import ../db/db_chain, eth/common, chronicles, ../vm_state, ../vm_types,
|
||||||
../vm/[computation, message], stint, nimcrypto,
|
../vm/[computation, message], stint, nimcrypto,
|
||||||
../utils, eth/trie/db, ../tracer, ./executor
|
../utils, eth/trie/db, ../tracer, ./executor
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import options, sets,
|
import options, sets,
|
||||||
eth/[common, bloom, trie/db], stew/ranges, chronicles, nimcrypto,
|
eth/[common, bloom, trie/db], chronicles, nimcrypto,
|
||||||
../db/[db_chain, state_db],
|
../db/[db_chain, state_db],
|
||||||
../utils, ../constants, ../transaction,
|
../utils, ../constants, ../transaction,
|
||||||
../vm_state, ../vm_types, ../vm_state_transactions,
|
../vm_state, ../vm_types, ../vm_state_transactions,
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
import
|
import
|
||||||
strutils,
|
strutils,
|
||||||
nimcrypto, eth/common as eth_common, stint, json_rpc/server,
|
nimcrypto, eth/common as eth_common, stint, json_rpc/server,
|
||||||
../vm_state, ../db/[db_chain, state_db], ../constants, ../config, hexstrings
|
../config, hexstrings
|
||||||
|
|
||||||
proc setupCommonRPC*(server: RpcServer) =
|
proc setupCommonRPC*(server: RpcServer) =
|
||||||
server.rpc("web3_clientVersion") do() -> string:
|
server.rpc("web3_clientVersion") do() -> string:
|
||||||
|
|
|
@ -10,8 +10,8 @@
|
||||||
import
|
import
|
||||||
strutils, json, options,
|
strutils, json, options,
|
||||||
json_rpc/rpcserver, rpc_utils, eth/common,
|
json_rpc/rpcserver, rpc_utils, eth/common,
|
||||||
hexstrings, ../tracer, ../vm_state, ../vm_types,
|
hexstrings, ../tracer, ../vm_types,
|
||||||
../db/[db_chain, storage_types]
|
../db/[db_chain]
|
||||||
|
|
||||||
type
|
type
|
||||||
TraceOptions = object
|
TraceOptions = object
|
||||||
|
|
|
@ -204,7 +204,7 @@ proc `%`*(value: SymKey): JsonNode =
|
||||||
proc `%`*(value: whisper_protocol.Topic): JsonNode =
|
proc `%`*(value: whisper_protocol.Topic): JsonNode =
|
||||||
result = %("0x" & value.toHex)
|
result = %("0x" & value.toHex)
|
||||||
|
|
||||||
proc `%`*(value: Bytes): JsonNode =
|
proc `%`*(value: seq[byte]): JsonNode =
|
||||||
result = %("0x" & value.toHex)
|
result = %("0x" & value.toHex)
|
||||||
|
|
||||||
|
|
||||||
|
@ -299,7 +299,7 @@ proc fromJson*(n: JsonNode, argName: string, result: var whisper_protocol.Topic)
|
||||||
# Following procs currently required only for testing, the `createRpcSigs` macro
|
# Following procs currently required only for testing, the `createRpcSigs` macro
|
||||||
# requires it as it will convert the JSON results back to the original Nim
|
# requires it as it will convert the JSON results back to the original Nim
|
||||||
# types, but it needs the `fromJson` calls for those specific Nim types to do so
|
# types, but it needs the `fromJson` calls for those specific Nim types to do so
|
||||||
proc fromJson*(n: JsonNode, argName: string, result: var Bytes) =
|
proc fromJson*(n: JsonNode, argName: string, result: var seq[byte]) =
|
||||||
n.kind.expect(JString, argName)
|
n.kind.expect(JString, argName)
|
||||||
let hexStr = n.getStr()
|
let hexStr = n.getStr()
|
||||||
if not hexStr.isValidHexData:
|
if not hexStr.isValidHexData:
|
||||||
|
|
|
@ -9,11 +9,11 @@
|
||||||
|
|
||||||
import
|
import
|
||||||
strutils, times, options,
|
strutils, times, options,
|
||||||
json_rpc/rpcserver, hexstrings, stint, stew/byteutils, stew/ranges/typedranges,
|
json_rpc/rpcserver, hexstrings, stint, stew/byteutils,
|
||||||
eth/[common, keys, rlp, p2p], eth/trie/db, nimcrypto,
|
eth/[common, keys, rlp, p2p], nimcrypto,
|
||||||
../transaction, ../config, ../vm_state, ../constants, ../vm_types,
|
../transaction, ../config, ../vm_state, ../constants, ../vm_types,
|
||||||
../vm_state_transactions, ../utils,
|
../vm_state_transactions, ../utils,
|
||||||
../db/[db_chain, state_db, storage_types],
|
../db/[db_chain, state_db],
|
||||||
rpc_types, rpc_utils, ../vm/[message, computation],
|
rpc_types, rpc_utils, ../vm/[message, computation],
|
||||||
../vm/interpreter/vm_forks
|
../vm/interpreter/vm_forks
|
||||||
|
|
||||||
|
@ -224,7 +224,7 @@ proc setupEthRpc*(node: EthereumNode, chain: BaseChainDB, rpcsrv: RpcServer) =
|
||||||
addrBytes = toAddress(data)
|
addrBytes = toAddress(data)
|
||||||
storage = accountDb.getCode(addrBytes)
|
storage = accountDb.getCode(addrBytes)
|
||||||
# Easier to return the string manually here rather than expect ByteRange to be marshalled
|
# Easier to return the string manually here rather than expect ByteRange to be marshalled
|
||||||
result = byteutils.toHex(storage.toOpenArray).HexDataStr
|
result = byteutils.toHex(storage).HexDataStr
|
||||||
|
|
||||||
template sign(privateKey: PrivateKey, message: string): string =
|
template sign(privateKey: PrivateKey, message: string): string =
|
||||||
# TODO: Is message length encoded as bytes or characters?
|
# TODO: Is message length encoded as bytes or characters?
|
||||||
|
|
|
@ -146,8 +146,8 @@ type
|
||||||
ttl*: uint64 # Time-to-live in seconds.
|
ttl*: uint64 # Time-to-live in seconds.
|
||||||
timestamp*: uint64 # Unix timestamp of the message generation.
|
timestamp*: uint64 # Unix timestamp of the message generation.
|
||||||
topic*: whisper_protocol.Topic # 4 Bytes: Message topic.
|
topic*: whisper_protocol.Topic # 4 Bytes: Message topic.
|
||||||
payload*: Bytes # Decrypted payload.
|
payload*: seq[byte] # Decrypted payload.
|
||||||
padding*: Bytes # (Optional) Padding (byte array of arbitrary length).
|
padding*: seq[byte] # (Optional) Padding (byte array of arbitrary length).
|
||||||
pow*: float64 # Proof of work value.
|
pow*: float64 # Proof of work value.
|
||||||
hash*: Hash # Hash of the enveloped message.
|
hash*: Hash # Hash of the enveloped message.
|
||||||
|
|
||||||
|
|
|
@ -7,8 +7,8 @@
|
||||||
# This file may not be copied, modified, or distributed except according to
|
# This file may not be copied, modified, or distributed except according to
|
||||||
# those terms.
|
# those terms.
|
||||||
|
|
||||||
import hexstrings, nimcrypto, eth/common, stew/byteutils,
|
import hexstrings, eth/common, stew/byteutils,
|
||||||
../db/[db_chain, state_db, storage_types], strutils,
|
../db/[db_chain], strutils,
|
||||||
../constants, stint
|
../constants, stint
|
||||||
|
|
||||||
func toAddress*(value: EthAddressStr): EthAddress = hexToPaddedByteArray[20](value.string)
|
func toAddress*(value: EthAddressStr): EthAddress = hexToPaddedByteArray[20](value.string)
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import
|
import
|
||||||
json_rpc/rpcserver, tables, options,
|
json_rpc/rpcserver, tables, options, sequtils,
|
||||||
eth/[common, rlp, keys, p2p], eth/p2p/rlpx_protocols/waku_protocol,
|
eth/[common, rlp, keys, p2p], eth/p2p/rlpx_protocols/waku_protocol,
|
||||||
nimcrypto/[sysrand, hmac, sha2, pbkdf2],
|
nimcrypto/[sysrand, hmac, sha2, pbkdf2],
|
||||||
rpc_types, hexstrings, key_storage
|
rpc_types, hexstrings, key_storage
|
||||||
|
@ -330,7 +330,7 @@ proc setupWakuRPC*(node: EthereumNode, keys: KeyStorage, rpcsrv: RpcServer) =
|
||||||
sigPrivKey: Option[PrivateKey]
|
sigPrivKey: Option[PrivateKey]
|
||||||
symKey: Option[SymKey]
|
symKey: Option[SymKey]
|
||||||
topic: waku_protocol.Topic
|
topic: waku_protocol.Topic
|
||||||
padding: Option[Bytes]
|
padding: Option[seq[byte]]
|
||||||
targetPeer: Option[NodeId]
|
targetPeer: Option[NodeId]
|
||||||
|
|
||||||
if message.sig.isSome():
|
if message.sig.isSome():
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import
|
import
|
||||||
json_rpc/rpcserver, tables, options, sequtils,
|
json_rpc/rpcserver, tables, options,
|
||||||
eth/[common, rlp, keys, p2p], eth/p2p/rlpx_protocols/whisper_protocol,
|
eth/[common, rlp, keys, p2p], eth/p2p/rlpx_protocols/whisper_protocol,
|
||||||
nimcrypto/[sysrand, hmac, sha2, pbkdf2],
|
nimcrypto/[sysrand, hmac, sha2, pbkdf2],
|
||||||
rpc_types, hexstrings, key_storage
|
rpc_types, hexstrings, key_storage
|
||||||
|
@ -311,7 +311,7 @@ proc setupWhisperRPC*(node: EthereumNode, keys: KeyStorage, rpcsrv: RpcServer) =
|
||||||
sigPrivKey: Option[PrivateKey]
|
sigPrivKey: Option[PrivateKey]
|
||||||
symKey: Option[SymKey]
|
symKey: Option[SymKey]
|
||||||
topic: whisper_protocol.Topic
|
topic: whisper_protocol.Topic
|
||||||
padding: Option[Bytes]
|
padding: Option[seq[byte]]
|
||||||
targetPeer: Option[NodeId]
|
targetPeer: Option[NodeId]
|
||||||
|
|
||||||
if message.sig.isSome():
|
if message.sig.isSome():
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import
|
import
|
||||||
db/[db_chain, state_db, capturedb], eth/common, utils, json,
|
db/[db_chain, state_db, capturedb], eth/common, utils, json,
|
||||||
constants, vm_state, vm_types, transaction, p2p/executor,
|
constants, vm_state, vm_types, transaction, p2p/executor,
|
||||||
eth/trie/db, nimcrypto, strutils, stew/ranges,
|
eth/trie/db, nimcrypto, strutils,
|
||||||
chronicles, rpc/hexstrings, launcher,
|
chronicles, rpc/hexstrings, launcher,
|
||||||
vm/interpreter/vm_forks, ./config
|
vm/interpreter/vm_forks, ./config
|
||||||
|
|
||||||
|
@ -43,7 +43,7 @@ proc captureAccount(n: JsonNode, db: AccountStateDB, address: EthAddress, name:
|
||||||
|
|
||||||
let code = db.getCode(address)
|
let code = db.getCode(address)
|
||||||
jaccount["codeHash"] = %("0x" & ($account.codeHash).toLowerAscii)
|
jaccount["codeHash"] = %("0x" & ($account.codeHash).toLowerAscii)
|
||||||
jaccount["code"] = %("0x" & toHex(code.toOpenArray, true))
|
jaccount["code"] = %("0x" & toHex(code, true))
|
||||||
jaccount["storageRoot"] = %("0x" & ($account.storageRoot).toLowerAscii)
|
jaccount["storageRoot"] = %("0x" & ($account.storageRoot).toLowerAscii)
|
||||||
|
|
||||||
var storage = newJObject()
|
var storage = newJObject()
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
# at your option. This file may not be copied, modified, or distributed except according to those terms.
|
# at your option. This file may not be copied, modified, or distributed except according to those terms.
|
||||||
|
|
||||||
import
|
import
|
||||||
constants, errors, eth/[common, rlp, keys], utils,
|
constants, errors, eth/[common, keys], utils,
|
||||||
./vm/interpreter/[vm_forks, gas_costs], constants
|
./vm/interpreter/[vm_forks, gas_costs], constants
|
||||||
|
|
||||||
import eth/common/transaction as common_transaction
|
import eth/common/transaction as common_transaction
|
||||||
|
|
|
@ -5,7 +5,7 @@ export nimcrypto.`$`
|
||||||
proc calcRootHash[T](items: openArray[T]): Hash256 =
|
proc calcRootHash[T](items: openArray[T]): Hash256 =
|
||||||
var tr = initHexaryTrie(newMemoryDB())
|
var tr = initHexaryTrie(newMemoryDB())
|
||||||
for i, t in items:
|
for i, t in items:
|
||||||
tr.put(rlp.encode(i).toRange, rlp.encode(t).toRange)
|
tr.put(rlp.encode(i), rlp.encode(t))
|
||||||
return tr.rootHash
|
return tr.rootHash
|
||||||
|
|
||||||
template calcTxRoot*(transactions: openArray[Transaction]): Hash256 =
|
template calcTxRoot*(transactions: openArray[Transaction]): Hash256 =
|
||||||
|
|
|
@ -11,7 +11,7 @@ import
|
||||||
../constants, ../errors, ../vm_state, ../vm_types,
|
../constants, ../errors, ../vm_state, ../vm_types,
|
||||||
./interpreter/[opcode_values, gas_meter, gas_costs, vm_forks],
|
./interpreter/[opcode_values, gas_meter, gas_costs, vm_forks],
|
||||||
./code_stream, ./memory, ./message, ./stack, ../db/[state_db, db_chain],
|
./code_stream, ./memory, ./message, ./stack, ../db/[state_db, db_chain],
|
||||||
../utils/header, stew/[byteutils, ranges, ranges/ptr_arith], precompiles,
|
../utils/header, stew/[byteutils, ranges/ptr_arith], precompiles,
|
||||||
transaction_tracer, ../utils
|
transaction_tracer, ../utils
|
||||||
|
|
||||||
when defined(evmc_enabled):
|
when defined(evmc_enabled):
|
||||||
|
@ -120,9 +120,9 @@ template selfDestruct*(c: Computation, address: EthAddress) =
|
||||||
else:
|
else:
|
||||||
c.execSelfDestruct(address)
|
c.execSelfDestruct(address)
|
||||||
|
|
||||||
template getCode*(c: Computation, address: EthAddress): ByteRange =
|
template getCode*(c: Computation, address: EthAddress): seq[byte] =
|
||||||
when evmc_enabled:
|
when evmc_enabled:
|
||||||
c.host.copyCode(address).toRange
|
c.host.copyCode(address)
|
||||||
else:
|
else:
|
||||||
c.vmState.readOnlyStateDB.getCode(address)
|
c.vmState.readOnlyStateDB.getCode(address)
|
||||||
|
|
||||||
|
@ -148,7 +148,7 @@ proc newComputation*(vmState: BaseVMState, message: Message, salt= 0.u256): Comp
|
||||||
result.code = newCodeStream(message.data)
|
result.code = newCodeStream(message.data)
|
||||||
message.data = @[]
|
message.data = @[]
|
||||||
else:
|
else:
|
||||||
result.code = newCodeStream(vmState.readOnlyStateDb.getCode(message.codeAddress).toSeq)
|
result.code = newCodeStream(vmState.readOnlyStateDb.getCode(message.codeAddress))
|
||||||
|
|
||||||
when evmc_enabled:
|
when evmc_enabled:
|
||||||
result.host.init(
|
result.host.init(
|
||||||
|
@ -213,7 +213,7 @@ proc writeContract*(c: Computation, fork: Fork): bool {.gcsafe.} =
|
||||||
if c.gasMeter.gasRemaining >= codeCost:
|
if c.gasMeter.gasRemaining >= codeCost:
|
||||||
c.gasMeter.consumeGas(codeCost, reason = "Write contract code for CREATE")
|
c.gasMeter.consumeGas(codeCost, reason = "Write contract code for CREATE")
|
||||||
c.vmState.mutateStateDb:
|
c.vmState.mutateStateDb:
|
||||||
db.setCode(storageAddr, contractCode.toRange)
|
db.setCode(storageAddr, contractCode)
|
||||||
result = true
|
result = true
|
||||||
else:
|
else:
|
||||||
if fork < FkHomestead or fork >= FkByzantium: c.output = @[]
|
if fork < FkHomestead or fork >= FkByzantium: c.output = @[]
|
||||||
|
|
|
@ -6,8 +6,8 @@
|
||||||
# at your option. This file may not be copied, modified, or distributed except according to those terms.
|
# at your option. This file may not be copied, modified, or distributed except according to those terms.
|
||||||
|
|
||||||
import
|
import
|
||||||
strformat, times, sets, stew/ranges, sequtils, options,
|
strformat, times, sets, sequtils, options,
|
||||||
chronicles, stint, nimcrypto, stew/ranges/[typedranges, ptr_arith], eth/common,
|
chronicles, stint, nimcrypto, stew/ranges/[ptr_arith], eth/common,
|
||||||
./utils/[macros_procs_opcodes, utils_numeric],
|
./utils/[macros_procs_opcodes, utils_numeric],
|
||||||
./gas_meter, ./gas_costs, ./opcode_values, ./vm_forks,
|
./gas_meter, ./gas_costs, ./opcode_values, ./vm_forks,
|
||||||
../memory, ../stack, ../code_stream, ../computation,
|
../memory, ../stack, ../code_stream, ../computation,
|
||||||
|
@ -322,7 +322,7 @@ op extCodeCopy, inline = true:
|
||||||
reason="ExtCodeCopy fee")
|
reason="ExtCodeCopy fee")
|
||||||
|
|
||||||
let codeBytes = c.getCode(address)
|
let codeBytes = c.getCode(address)
|
||||||
c.memory.writePaddedResult(codeBytes.toOpenArray, memPos, codePos, len)
|
c.memory.writePaddedResult(codeBytes, memPos, codePos, len)
|
||||||
|
|
||||||
op returnDataSize, inline = true:
|
op returnDataSize, inline = true:
|
||||||
## 0x3d, Get size of output data from the previous call from the current environment.
|
## 0x3d, Get size of output data from the previous call from the current environment.
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
|
|
||||||
import
|
import
|
||||||
macros,
|
macros,
|
||||||
eth/common/eth_types, eth/rlp,
|
eth/common/eth_types,
|
||||||
../../../constants
|
../../../constants
|
||||||
|
|
||||||
type
|
type
|
||||||
|
|
|
@ -2,7 +2,7 @@ import
|
||||||
json, strutils, sets, hashes,
|
json, strutils, sets, hashes,
|
||||||
chronicles, nimcrypto, eth/common, stint,
|
chronicles, nimcrypto, eth/common, stint,
|
||||||
../vm_types, memory, stack, ../db/state_db,
|
../vm_types, memory, stack, ../db/state_db,
|
||||||
eth/trie/hexary, stew/ranges/typedranges,
|
eth/trie/hexary,
|
||||||
./interpreter/opcode_values
|
./interpreter/opcode_values
|
||||||
|
|
||||||
logScope:
|
logScope:
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
# at your option. This file may not be copied, modified, or distributed except according to those terms.
|
# at your option. This file may not be copied, modified, or distributed except according to those terms.
|
||||||
|
|
||||||
import
|
import
|
||||||
stew/ranges/typedranges, options, sets,
|
options, sets,
|
||||||
eth/common, chronicles, ./db/state_db,
|
eth/common, chronicles, ./db/state_db,
|
||||||
transaction, vm_types, vm_state,
|
transaction, vm_types, vm_state,
|
||||||
./vm/[computation, interpreter]
|
./vm/[computation, interpreter]
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import
|
import
|
||||||
json, downloader, stint, strutils, stew/byteutils, parser,
|
json, downloader, stint, stew/byteutils, parser,
|
||||||
chronicles, ../nimbus/[tracer, vm_state, utils], eth/trie/[trie_defs, db],
|
chronicles, ../nimbus/[tracer, vm_state, utils], eth/trie/[trie_defs, db],
|
||||||
../nimbus/db/[db_chain, state_db], ../nimbus/p2p/executor, premixcore,
|
../nimbus/db/[db_chain, state_db], ../nimbus/p2p/executor, premixcore,
|
||||||
eth/common, configuration, tables, ../nimbus/vm_types, hashes
|
eth/common, configuration, tables, ../nimbus/vm_types, hashes
|
||||||
|
@ -48,7 +48,7 @@ proc prepareBlockEnv(parent: BlockHeader, thisBlock: Block): TrieDatabaseRef =
|
||||||
|
|
||||||
if acc.codeHash != emptyCodeHash:
|
if acc.codeHash != emptyCodeHash:
|
||||||
let codeStr = request("eth_getCode", %[%address.prefixHex, parentNumber])
|
let codeStr = request("eth_getCode", %[%address.prefixHex, parentNumber])
|
||||||
let code = hexToSeqByte(codeStr.getStr).toRange
|
let code = hexToSeqByte(codeStr.getStr)
|
||||||
accountDB.setCode(address, code)
|
accountDB.setCode(address, code)
|
||||||
|
|
||||||
accountDB.setAccount(address, acc)
|
accountDB.setAccount(address, acc)
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
# use this module to quickly populate db with data from geth/parity
|
# use this module to quickly populate db with data from geth/parity
|
||||||
|
|
||||||
import
|
import
|
||||||
eth/[common, rlp], stint, stew/byteutils, nimcrypto,
|
eth/[common, rlp], stint,
|
||||||
chronicles, downloader, configuration,
|
chronicles, downloader, configuration,
|
||||||
../nimbus/errors
|
../nimbus/errors
|
||||||
|
|
||||||
import
|
import
|
||||||
eth/trie/[hexary, db, trie_defs],
|
eth/trie/[hexary, db],
|
||||||
../nimbus/db/[storage_types, db_chain, select_backend],
|
../nimbus/db/[storage_types, db_chain, select_backend],
|
||||||
../nimbus/[genesis, utils],
|
../nimbus/[genesis],
|
||||||
../nimbus/p2p/chain
|
../nimbus/p2p/chain
|
||||||
|
|
||||||
const
|
const
|
||||||
|
|
|
@ -224,7 +224,7 @@ proc initComputation(vmState: BaseVMState, tx: Transaction, sender: EthAddress,
|
||||||
)
|
)
|
||||||
|
|
||||||
vmState.mutateStateDb:
|
vmState.mutateStateDb:
|
||||||
db.setCode(contractAddress, tx.payload.toRange)
|
db.setCode(contractAddress, tx.payload)
|
||||||
|
|
||||||
newComputation(vmState, msg)
|
newComputation(vmState, msg)
|
||||||
|
|
||||||
|
@ -309,8 +309,8 @@ proc runVM*(blockNumber: Uint256, chainDB: BaseChainDB, boa: Assembler): bool =
|
||||||
for kv in boa.storage:
|
for kv in boa.storage:
|
||||||
let key = kv[0].toHex()
|
let key = kv[0].toHex()
|
||||||
let val = kv[1].toHex()
|
let val = kv[1].toHex()
|
||||||
let keyBytes = (@(kv[0])).toRange
|
let keyBytes = (@(kv[0]))
|
||||||
let actual = trie.get(keyBytes).toOpenArray().toHex()
|
let actual = trie.get(keyBytes).toHex()
|
||||||
let zerosLen = 64 - (actual.len)
|
let zerosLen = 64 - (actual.len)
|
||||||
let value = repeat('0', zerosLen) & actual
|
let value = repeat('0', zerosLen) & actual
|
||||||
if val != value:
|
if val != value:
|
||||||
|
|
|
@ -136,7 +136,7 @@ proc setupStateDB*(wantedState: JsonNode, stateDB: var AccountStateDB) =
|
||||||
stateDB.setStorage(account, fromHex(UInt256, slot), fromHex(UInt256, value.getStr))
|
stateDB.setStorage(account, fromHex(UInt256, slot), fromHex(UInt256, value.getStr))
|
||||||
|
|
||||||
let nonce = accountData{"nonce"}.getHexadecimalInt.AccountNonce
|
let nonce = accountData{"nonce"}.getHexadecimalInt.AccountNonce
|
||||||
let code = accountData{"code"}.getStr.safeHexToSeqByte.toRange
|
let code = accountData{"code"}.getStr.safeHexToSeqByte
|
||||||
let balance = UInt256.fromHex accountData{"balance"}.getStr
|
let balance = UInt256.fromHex accountData{"balance"}.getStr
|
||||||
|
|
||||||
stateDB.setNonce(account, nonce)
|
stateDB.setNonce(account, nonce)
|
||||||
|
@ -158,7 +158,7 @@ proc verifyStateDB*(wantedState: JsonNode, stateDB: ReadOnlyStateDB) =
|
||||||
raise newException(ValidationError, &"{ac} storageDiff: [{slot}] {actualValue.toHex} != {wantedValue.toHex}")
|
raise newException(ValidationError, &"{ac} storageDiff: [{slot}] {actualValue.toHex} != {wantedValue.toHex}")
|
||||||
|
|
||||||
let
|
let
|
||||||
wantedCode = hexToSeqByte(accountData{"code"}.getStr).toRange
|
wantedCode = hexToSeqByte(accountData{"code"}.getStr)
|
||||||
wantedBalance = UInt256.fromHex accountData{"balance"}.getStr
|
wantedBalance = UInt256.fromHex accountData{"balance"}.getStr
|
||||||
wantedNonce = accountData{"nonce"}.getHexadecimalInt.AccountNonce
|
wantedNonce = accountData{"nonce"}.getHexadecimalInt.AccountNonce
|
||||||
|
|
||||||
|
|
|
@ -191,7 +191,7 @@ proc opEnvMain*() =
|
||||||
"51602001600a5254516040016014525451606001601e52545160800160285254" &
|
"51602001600a5254516040016014525451606001601e52545160800160285254" &
|
||||||
"60a052546016604860003960166000f26000603f556103e756600054600053602002351234")
|
"60a052546016604860003960166000f26000603f556103e756600054600053602002351234")
|
||||||
|
|
||||||
stateDB.setCode(acc, code.toRange)
|
stateDB.setCode(acc, code)
|
||||||
parent.stateRoot = stateDB.rootHash
|
parent.stateRoot = stateDB.rootHash
|
||||||
chainDB.setHead(parent, true)
|
chainDB.setHead(parent, true)
|
||||||
|
|
||||||
|
|
|
@ -25,7 +25,7 @@ proc stateDBMain*() =
|
||||||
trie = initHexaryTrie(memDB)
|
trie = initHexaryTrie(memDB)
|
||||||
stateDB = newAccountStateDB(memDB, trie.rootHash, true)
|
stateDB = newAccountStateDB(memDB, trie.rootHash, true)
|
||||||
address = hexToByteArray[20]("0x0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6")
|
address = hexToByteArray[20]("0x0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6")
|
||||||
code = hexToSeqByte("0x0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6").toRange
|
code = hexToSeqByte("0x0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6")
|
||||||
rootHash: KeccakHash
|
rootHash: KeccakHash
|
||||||
|
|
||||||
test "accountExists and isDeadAccount":
|
test "accountExists and isDeadAccount":
|
||||||
|
@ -48,7 +48,7 @@ proc stateDBMain*() =
|
||||||
stateDB.setNonce(address, 0)
|
stateDB.setNonce(address, 0)
|
||||||
check stateDB.isDeadAccount(address) == false
|
check stateDB.isDeadAccount(address) == false
|
||||||
|
|
||||||
stateDB.setCode(address, BytesRange())
|
stateDB.setCode(address, [])
|
||||||
check stateDB.isDeadAccount(address) == true
|
check stateDB.isDeadAccount(address) == true
|
||||||
check stateDB.accountExists(address) == true
|
check stateDB.accountExists(address) == true
|
||||||
|
|
||||||
|
@ -128,7 +128,7 @@ proc stateDBMain*() =
|
||||||
check ac.getCodeHash(addr2) == emptyAcc.codeHash
|
check ac.getCodeHash(addr2) == emptyAcc.codeHash
|
||||||
check ac.getBalance(addr2) == emptyAcc.balance
|
check ac.getBalance(addr2) == emptyAcc.balance
|
||||||
check ac.getNonce(addr2) == emptyAcc.nonce
|
check ac.getNonce(addr2) == emptyAcc.nonce
|
||||||
check ac.getCode(addr2) == BytesRange()
|
check ac.getCode(addr2) == []
|
||||||
check ac.getCodeSize(addr2) == 0
|
check ac.getCodeSize(addr2) == 0
|
||||||
check ac.getCommittedStorage(addr2, 1.u256) == 0.u256
|
check ac.getCommittedStorage(addr2, 1.u256) == 0.u256
|
||||||
check ac.getStorage(addr2, 1.u256) == 0.u256
|
check ac.getStorage(addr2, 1.u256) == 0.u256
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
Subproject commit 7c709551a5ad2a42f66ca004cdc064bb70b60bd1
|
Subproject commit 0816bd1e945ef4fa3d030c9c6cc27f260bbe639b
|
|
@ -1 +1 @@
|
||||||
Subproject commit 08fec021c0f28f63d1221d40a655078b5b923d1b
|
Subproject commit 8da2f119514d933f24efc009182cdea24a5fe107
|
|
@ -1 +1 @@
|
||||||
Subproject commit ff755bbf75d0d3f387b9f352b241d98eb3372323
|
Subproject commit 2a1df5d2dd4963369f2dbae583ab714a1601a8cd
|
|
@ -278,8 +278,8 @@ proc nimbus_post(message: ptr CPostMessage): bool {.exportc, dynlib.} =
|
||||||
sigPrivKey: Option[PrivateKey]
|
sigPrivKey: Option[PrivateKey]
|
||||||
asymKey: Option[PublicKey]
|
asymKey: Option[PublicKey]
|
||||||
symKey: Option[SymKey]
|
symKey: Option[SymKey]
|
||||||
padding: Option[Bytes]
|
padding: Option[seq[byte]]
|
||||||
payload: Bytes
|
payload: seq[byte]
|
||||||
|
|
||||||
if not message.pubKey.isNil() and not message.symKeyID.isNil():
|
if not message.pubKey.isNil() and not message.symKeyID.isNil():
|
||||||
warn "Both symmetric and asymmetric keys are provided, choose one."
|
warn "Both symmetric and asymmetric keys are provided, choose one."
|
||||||
|
@ -473,7 +473,7 @@ proc nimbus_post_public(channel: cstring, payload: cstring)
|
||||||
|
|
||||||
var ctx: HMAC[sha256]
|
var ctx: HMAC[sha256]
|
||||||
var symKey: SymKey
|
var symKey: SymKey
|
||||||
var npayload = cast[Bytes]($payload)
|
var npayload = cast[seq[byte]]($payload)
|
||||||
discard ctx.pbkdf2($channel, "", 65356, symKey)
|
discard ctx.pbkdf2($channel, "", 65356, symKey)
|
||||||
|
|
||||||
let channelHash = digest(keccak256, $channel)
|
let channelHash = digest(keccak256, $channel)
|
||||||
|
|
Loading…
Reference in New Issue