implement more eth rpc and keystore management
This commit is contained in:
parent
336efdb0c3
commit
f82dff64fa
|
@ -8,7 +8,7 @@
|
||||||
# those terms.
|
# those terms.
|
||||||
|
|
||||||
import
|
import
|
||||||
parseopt, strutils, macros, os, times, json, stew/[byteutils],
|
parseopt, strutils, macros, os, times, json, tables, 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, eth/keys,
|
./db/select_backend, eth/keys,
|
||||||
|
@ -121,6 +121,11 @@ type
|
||||||
Full
|
Full
|
||||||
Archive
|
Archive
|
||||||
|
|
||||||
|
NimbusAccount* = object
|
||||||
|
privateKey*: PrivateKey
|
||||||
|
keystore*: JsonNode
|
||||||
|
unlocked*: bool
|
||||||
|
|
||||||
ChainConfig* = object
|
ChainConfig* = object
|
||||||
chainId*: uint
|
chainId*: uint
|
||||||
homesteadBlock*: BlockNumber
|
homesteadBlock*: BlockNumber
|
||||||
|
@ -143,7 +148,7 @@ type
|
||||||
NimbusConfiguration* = ref object
|
NimbusConfiguration* = ref object
|
||||||
## Main Nimbus configuration object
|
## Main Nimbus configuration object
|
||||||
dataDir*: string
|
dataDir*: string
|
||||||
keyFile*: string
|
keyStore*: string
|
||||||
prune*: PruneMode
|
prune*: PruneMode
|
||||||
rpc*: RpcConfiguration ## JSON-RPC configuration
|
rpc*: RpcConfiguration ## JSON-RPC configuration
|
||||||
net*: NetConfiguration ## Network configuration
|
net*: NetConfiguration ## Network configuration
|
||||||
|
@ -153,6 +158,7 @@ type
|
||||||
# You should only create one instance of the RNG per application / library
|
# You should only create one instance of the RNG per application / library
|
||||||
# Ref is used so that it can be shared between components
|
# Ref is used so that it can be shared between components
|
||||||
rng*: ref BrHmacDrbgContext
|
rng*: ref BrHmacDrbgContext
|
||||||
|
accounts*: Table[EthAddress, NimbusAccount]
|
||||||
|
|
||||||
CustomGenesisConfig = object
|
CustomGenesisConfig = object
|
||||||
chainId*: uint
|
chainId*: uint
|
||||||
|
@ -557,11 +563,8 @@ proc processEthArguments(key, value: string): ConfigStatus =
|
||||||
result = Success
|
result = Success
|
||||||
let config = getConfiguration()
|
let config = getConfiguration()
|
||||||
case key.toLowerAscii()
|
case key.toLowerAscii()
|
||||||
of "keyfile":
|
of "keystore":
|
||||||
if fileExists(value):
|
config.keyStore = value
|
||||||
config.keyFile = value
|
|
||||||
else:
|
|
||||||
result = ErrorIncorrectOption
|
|
||||||
of "datadir":
|
of "datadir":
|
||||||
config.dataDir = value
|
config.dataDir = value
|
||||||
of "prune":
|
of "prune":
|
||||||
|
@ -825,16 +828,20 @@ template processArgument(processor, key, value, msg: untyped) =
|
||||||
|
|
||||||
proc getDefaultDataDir*(): string =
|
proc getDefaultDataDir*(): string =
|
||||||
when defined(windows):
|
when defined(windows):
|
||||||
"AppData" / "Roaming" / "Nimbus" / "DB"
|
"AppData" / "Roaming" / "Nimbus"
|
||||||
elif defined(macosx):
|
elif defined(macosx):
|
||||||
"Library" / "Application Support" / "Nimbus" / "DB"
|
"Library" / "Application Support" / "Nimbus"
|
||||||
else:
|
else:
|
||||||
".cache" / "nimbus" / "db"
|
".cache" / "nimbus"
|
||||||
|
|
||||||
|
proc getDefaultKeystoreDir*(): string =
|
||||||
|
getDefaultDataDir() / "keystore"
|
||||||
|
|
||||||
proc initConfiguration(): NimbusConfiguration =
|
proc initConfiguration(): NimbusConfiguration =
|
||||||
## Allocates and initializes `NimbusConfiguration` with default values
|
## Allocates and initializes `NimbusConfiguration` with default values
|
||||||
result = new NimbusConfiguration
|
result = new NimbusConfiguration
|
||||||
result.rng = newRng()
|
result.rng = newRng()
|
||||||
|
result.accounts = initTable[EthAddress, NimbusAccount]()
|
||||||
|
|
||||||
## RPC defaults
|
## RPC defaults
|
||||||
result.rpc.flags = {}
|
result.rpc.flags = {}
|
||||||
|
@ -853,9 +860,12 @@ proc initConfiguration(): NimbusConfiguration =
|
||||||
result.net.protocols = defaultProtocols
|
result.net.protocols = defaultProtocols
|
||||||
result.net.nodekey = random(PrivateKey, result.rng[])
|
result.net.nodekey = random(PrivateKey, result.rng[])
|
||||||
|
|
||||||
const dataDir = getDefaultDataDir()
|
const
|
||||||
|
dataDir = getDefaultDataDir()
|
||||||
|
keystore = getDefaultKeystoreDir()
|
||||||
|
|
||||||
result.dataDir = getHomeDir() / dataDir
|
result.dataDir = getHomeDir() / dataDir
|
||||||
|
result.keystore = getHomeDir() / keystore
|
||||||
result.prune = PruneMode.Full
|
result.prune = PruneMode.Full
|
||||||
|
|
||||||
## Whisper defaults
|
## Whisper defaults
|
||||||
|
@ -897,7 +907,7 @@ USAGE:
|
||||||
nimbus [options]
|
nimbus [options]
|
||||||
|
|
||||||
ETHEREUM OPTIONS:
|
ETHEREUM OPTIONS:
|
||||||
--keyfile:<value> Use keyfile storage file
|
--keystore:<value> Directory for the keystore (default = inside the datadir)
|
||||||
--datadir:<value> Base directory for all blockchain-related data
|
--datadir:<value> Base directory for all blockchain-related data
|
||||||
--prune:<value> Blockchain prune mode(full or archive)
|
--prune:<value> Blockchain prune mode(full or archive)
|
||||||
|
|
||||||
|
|
|
@ -159,16 +159,21 @@ iterator getBlockTransactionHashes(self: BaseChainDB, blockHeader: BlockHeader):
|
||||||
for encodedTx in self.getBlockTransactionData(blockHeader.txRoot):
|
for encodedTx in self.getBlockTransactionData(blockHeader.txRoot):
|
||||||
yield keccakHash(encodedTx)
|
yield keccakHash(encodedTx)
|
||||||
|
|
||||||
proc getTransactionCount*(chain: BaseChainDB, blockHash: Hash256): int =
|
proc getTransactionCount*(chain: BaseChainDB, txRoot: Hash256): int =
|
||||||
var header: BlockHeader
|
var trie = initHexaryTrie(chain.db, txRoot)
|
||||||
if chain.getBlockHeader(blockHash, header):
|
var txCount = 0
|
||||||
var trie = initHexaryTrie(chain.db, header.txRoot)
|
while true:
|
||||||
var txCount = 0
|
let txKey = rlp.encode(txCount)
|
||||||
while true:
|
if txKey notin trie:
|
||||||
let txKey = rlp.encode(txCount)
|
break
|
||||||
if txKey notin trie:
|
inc txCount
|
||||||
break
|
|
||||||
inc txCount
|
proc getUnclesCount*(self: BaseChainDB, ommersHash: Hash256): int =
|
||||||
|
if ommersHash != EMPTY_UNCLE_HASH:
|
||||||
|
let encodedUncles = self.db.get(genericHashKey(ommersHash).toOpenArray)
|
||||||
|
if encodedUncles.len != 0:
|
||||||
|
let r = rlpFromBytes(encodedUncles)
|
||||||
|
result = r.listLen
|
||||||
|
|
||||||
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
|
||||||
|
|
|
@ -15,7 +15,7 @@ import
|
||||||
eth/p2p/rlpx_protocols/[eth_protocol, les_protocol, whisper_protocol],
|
eth/p2p/rlpx_protocols/[eth_protocol, les_protocol, whisper_protocol],
|
||||||
eth/p2p/blockchain_sync, eth/net/nat, eth/p2p/peer_pool,
|
eth/p2p/blockchain_sync, eth/net/nat, eth/p2p/peer_pool,
|
||||||
config, genesis, rpc/[common, p2p, debug, whisper, key_storage], p2p/chain,
|
config, genesis, rpc/[common, p2p, debug, whisper, key_storage], p2p/chain,
|
||||||
eth/trie/db, metrics, metrics/chronicles_support
|
eth/trie/db, metrics, metrics/chronicles_support, utils
|
||||||
|
|
||||||
## TODO:
|
## TODO:
|
||||||
## * No IPv6 support
|
## * No IPv6 support
|
||||||
|
@ -36,6 +36,7 @@ type
|
||||||
|
|
||||||
proc start(nimbus: NimbusNode) =
|
proc start(nimbus: NimbusNode) =
|
||||||
var conf = getConfiguration()
|
var conf = getConfiguration()
|
||||||
|
conf.loadKeystoreFiles()
|
||||||
|
|
||||||
## logging
|
## logging
|
||||||
setLogLevel(conf.debug.logLevel)
|
setLogLevel(conf.debug.logLevel)
|
||||||
|
|
|
@ -96,15 +96,13 @@ proc setupEthRpc*(node: EthereumNode, chain: BaseChainDB , server: RpcServer) =
|
||||||
proc accountDbFromTag(tag: string, readOnly = true): ReadOnlyStateDB =
|
proc accountDbFromTag(tag: string, readOnly = true): ReadOnlyStateDB =
|
||||||
result = getAccountDb(chain.headerFromTag(tag))
|
result = getAccountDb(chain.headerFromTag(tag))
|
||||||
|
|
||||||
#[proc getBlockBody(hash: KeccakHash): BlockBody =
|
|
||||||
if not chain.getBlockBody(hash, result):
|
|
||||||
raise newException(ValueError, "Cannot find hash")]#
|
|
||||||
|
|
||||||
server.rpc("eth_protocolVersion") do() -> string:
|
server.rpc("eth_protocolVersion") do() -> string:
|
||||||
result = $eth_protocol.protocolVersion
|
result = $eth_protocol.protocolVersion
|
||||||
|
|
||||||
server.rpc("eth_syncing") do() -> JsonNode:
|
server.rpc("eth_syncing") do() -> JsonNode:
|
||||||
## Returns SyncObject or false when not syncing.
|
## Returns SyncObject or false when not syncing.
|
||||||
|
# TODO: make sure we are not syncing
|
||||||
|
# when we reach the recent block
|
||||||
let numPeers = node.peerPool.connectedNodes.len
|
let numPeers = node.peerPool.connectedNodes.len
|
||||||
if numPeers > 0:
|
if numPeers > 0:
|
||||||
var sync = SyncState(
|
var sync = SyncState(
|
||||||
|
@ -137,7 +135,10 @@ proc setupEthRpc*(node: EthereumNode, chain: BaseChainDB , server: RpcServer) =
|
||||||
|
|
||||||
server.rpc("eth_accounts") do() -> seq[EthAddressStr]:
|
server.rpc("eth_accounts") do() -> seq[EthAddressStr]:
|
||||||
## Returns a list of addresses owned by client.
|
## Returns a list of addresses owned by client.
|
||||||
result = @[]
|
let conf = getConfiguration()
|
||||||
|
result = newSeqOfCap[EthAddressStr](conf.accounts.len)
|
||||||
|
for k in keys(conf.accounts):
|
||||||
|
result.add ethAddressStr(k)
|
||||||
|
|
||||||
server.rpc("eth_blockNumber") do() -> HexQuantityStr:
|
server.rpc("eth_blockNumber") do() -> HexQuantityStr:
|
||||||
## Returns integer of the current block number the client is on.
|
## Returns integer of the current block number the client is on.
|
||||||
|
@ -150,9 +151,9 @@ proc setupEthRpc*(node: EthereumNode, chain: BaseChainDB , server: RpcServer) =
|
||||||
## quantityTag: integer block number, or the string "latest", "earliest" or "pending", see the default block parameter.
|
## quantityTag: integer block number, or the string "latest", "earliest" or "pending", see the default block parameter.
|
||||||
## Returns integer of the current balance in wei.
|
## Returns integer of the current balance in wei.
|
||||||
let
|
let
|
||||||
accountDb = accountDbFromTag(quantityTag)
|
accDB = accountDbFromTag(quantityTag)
|
||||||
address = data.toAddress
|
address = data.toAddress
|
||||||
balance = accountDb.getBalance(address)
|
balance = accDB.getBalance(address)
|
||||||
result = encodeQuantity(balance)
|
result = encodeQuantity(balance)
|
||||||
|
|
||||||
server.rpc("eth_getStorageAt") do(data: EthAddressStr, quantity: HexQuantityStr, quantityTag: string) -> HexDataStr:
|
server.rpc("eth_getStorageAt") do(data: EthAddressStr, quantity: HexQuantityStr, quantityTag: string) -> HexDataStr:
|
||||||
|
@ -163,10 +164,10 @@ proc setupEthRpc*(node: EthereumNode, chain: BaseChainDB , server: RpcServer) =
|
||||||
## quantityTag: integer block number, or the string "latest", "earliest" or "pending", see the default block parameter.
|
## quantityTag: integer block number, or the string "latest", "earliest" or "pending", see the default block parameter.
|
||||||
## Returns: the value at this storage position.
|
## Returns: the value at this storage position.
|
||||||
let
|
let
|
||||||
accountDb = accountDbFromTag(quantityTag)
|
accDB = accountDbFromTag(quantityTag)
|
||||||
address = data.toAddress
|
address = data.toAddress
|
||||||
key = fromHex(Uint256, quantity.string)
|
key = fromHex(Uint256, quantity.string)
|
||||||
value = accountDb.getStorage(address, key)[0]
|
value = accDB.getStorage(address, key)[0]
|
||||||
result = hexDataStr(value)
|
result = hexDataStr(value)
|
||||||
|
|
||||||
server.rpc("eth_getTransactionCount") do(data: EthAddressStr, quantityTag: string) -> HexQuantityStr:
|
server.rpc("eth_getTransactionCount") do(data: EthAddressStr, quantityTag: string) -> HexQuantityStr:
|
||||||
|
@ -177,8 +178,8 @@ proc setupEthRpc*(node: EthereumNode, chain: BaseChainDB , server: RpcServer) =
|
||||||
## Returns integer of the number of transactions send from this address.
|
## Returns integer of the number of transactions send from this address.
|
||||||
let
|
let
|
||||||
address = data.toAddress
|
address = data.toAddress
|
||||||
accountDb = accountDbFromTag(quantityTag)
|
accDB = accountDbFromTag(quantityTag)
|
||||||
result = encodeQuantity(accountDb.getNonce(address))
|
result = encodeQuantity(accDB.getNonce(address))
|
||||||
|
|
||||||
server.rpc("eth_getBlockTransactionCountByHash") do(data: EthHashStr) -> HexQuantityStr:
|
server.rpc("eth_getBlockTransactionCountByHash") do(data: EthHashStr) -> HexQuantityStr:
|
||||||
## Returns the number of transactions in a block from a block matching the given block hash.
|
## Returns the number of transactions in a block from a block matching the given block hash.
|
||||||
|
@ -186,33 +187,41 @@ proc setupEthRpc*(node: EthereumNode, chain: BaseChainDB , server: RpcServer) =
|
||||||
## data: hash of a block
|
## data: hash of a block
|
||||||
## Returns integer of the number of transactions in this block.
|
## Returns integer of the number of transactions in this block.
|
||||||
let
|
let
|
||||||
hashData = data.toHash
|
blockHash = data.toHash
|
||||||
txCount = chain.getTransactionCount(hashData)
|
header = chain.getBlockHeader(blockHash)
|
||||||
|
txCount = chain.getTransactionCount(header.txRoot)
|
||||||
result = encodeQuantity(txCount.uint)
|
result = encodeQuantity(txCount.uint)
|
||||||
#[
|
|
||||||
server.rpc("eth_getBlockTransactionCountByNumber") do(quantityTag: string) -> int:
|
server.rpc("eth_getBlockTransactionCountByNumber") do(quantityTag: string) -> HexQuantityStr:
|
||||||
## Returns the number of transactions in a block matching the given block number.
|
## Returns the number of transactions in a block matching the given block number.
|
||||||
##
|
##
|
||||||
## data: integer of a block number, or the string "earliest", "latest" or "pending", as in the default block parameter.
|
## data: integer of a block number, or the string "earliest", "latest" or "pending", as in the default block parameter.
|
||||||
## Returns integer of the number of transactions in this block.
|
## Returns integer of the number of transactions in this block.
|
||||||
let header = chain.headerFromTag(quantityTag)
|
let
|
||||||
result = getBlockBody(header.hash).transactions.len
|
header = chain.headerFromTag(quantityTag)
|
||||||
|
txCount = chain.getTransactionCount(header.txRoot)
|
||||||
|
result = encodeQuantity(txCount.uint)
|
||||||
|
|
||||||
server.rpc("eth_getUncleCountByBlockHash") do(data: EthHashStr) -> int:
|
server.rpc("eth_getUncleCountByBlockHash") do(data: EthHashStr) -> HexQuantityStr:
|
||||||
## Returns the number of uncles in a block from a block matching the given block hash.
|
## Returns the number of uncles in a block from a block matching the given block hash.
|
||||||
##
|
##
|
||||||
## data: hash of a block.
|
## data: hash of a block.
|
||||||
## Returns integer of the number of uncles in this block.
|
## Returns integer of the number of uncles in this block.
|
||||||
var hashData = data.toHash
|
let
|
||||||
result = getBlockBody(hashData).uncles.len
|
blockHash = data.toHash
|
||||||
|
header = chain.getBlockHeader(blockHash)
|
||||||
|
unclesCount = chain.getUnclesCount(header.ommersHash)
|
||||||
|
result = encodeQuantity(unclesCount.uint)
|
||||||
|
|
||||||
server.rpc("eth_getUncleCountByBlockNumber") do(quantityTag: string) -> int:
|
server.rpc("eth_getUncleCountByBlockNumber") do(quantityTag: string) -> HexQuantityStr:
|
||||||
## Returns the number of uncles in a block from a block matching the given block number.
|
## Returns the number of uncles in a block from a block matching the given block number.
|
||||||
##
|
##
|
||||||
## quantityTag: integer of a block number, or the string "latest", "earliest" or "pending", see the default block parameter.
|
## quantityTag: integer of a block number, or the string "latest", "earliest" or "pending", see the default block parameter.
|
||||||
## Returns integer of uncles in this block.
|
## Returns integer of uncles in this block.
|
||||||
let header = chain.headerFromTag(quantityTag)
|
let
|
||||||
result = getBlockBody(header.hash).uncles.len
|
header = chain.headerFromTag(quantityTag)
|
||||||
|
unclesCount = chain.getUnclesCount(header.ommersHash)
|
||||||
|
result = encodeQuantity(unclesCount.uint)
|
||||||
|
|
||||||
server.rpc("eth_getCode") do(data: EthAddressStr, quantityTag: string) -> HexDataStr:
|
server.rpc("eth_getCode") do(data: EthAddressStr, quantityTag: string) -> HexDataStr:
|
||||||
## Returns code at a given address.
|
## Returns code at a given address.
|
||||||
|
@ -221,14 +230,13 @@ proc setupEthRpc*(node: EthereumNode, chain: BaseChainDB , server: RpcServer) =
|
||||||
## quantityTag: integer block number, or the string "latest", "earliest" or "pending", see the default block parameter.
|
## quantityTag: integer block number, or the string "latest", "earliest" or "pending", see the default block parameter.
|
||||||
## Returns the code from the given address.
|
## Returns the code from the given address.
|
||||||
let
|
let
|
||||||
accountDb = accountDbFromTag(quantityTag)
|
accDB = accountDbFromTag(quantityTag)
|
||||||
address = toAddress(data)
|
address = data.toAddress
|
||||||
storage = accountDb.getCode(address)
|
storage = accDB.getCode(address)
|
||||||
# Easier to return the string manually here rather than expect ByteRange to be marshalled
|
result = hexDataStr(storage)
|
||||||
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?
|
# message length encoded as ASCII representation of decimal
|
||||||
let msgData = "\x19Ethereum Signed Message:\n" & $message.len & message
|
let msgData = "\x19Ethereum Signed Message:\n" & $message.len & message
|
||||||
$sign(privateKey, msgData.toBytes())
|
$sign(privateKey, msgData.toBytes())
|
||||||
|
|
||||||
|
@ -241,9 +249,16 @@ proc setupEthRpc*(node: EthereumNode, chain: BaseChainDB , server: RpcServer) =
|
||||||
## data: address.
|
## data: address.
|
||||||
## message: message to sign.
|
## message: message to sign.
|
||||||
## Returns signature.
|
## Returns signature.
|
||||||
var privateKey: PrivateKey # TODO: Get from key store
|
let
|
||||||
result = ("0x" & sign(privateKey, message.string)).HexDataStr
|
address = data.toAddress
|
||||||
|
conf = getConfiguration()
|
||||||
|
acc = conf.getAccount(address).tryGet()
|
||||||
|
msg = hexToSeqByte(message.string)
|
||||||
|
|
||||||
|
if not acc.unlocked:
|
||||||
|
raise newException(ValueError, "Account locked, please unlock it first")
|
||||||
|
result = ("0x" & sign(acc.privateKey, cast[string](msg))).HexDataStr
|
||||||
|
#[
|
||||||
# proc setupTransaction(send: EthSend): Transaction =
|
# proc setupTransaction(send: EthSend): Transaction =
|
||||||
# let
|
# let
|
||||||
# source = send.source.toAddress
|
# source = send.source.toAddress
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
import eth/trie/db, eth/[trie, rlp, common], nimcrypto
|
import
|
||||||
|
os, tables, json, ./config, stew/[results, byteutils],
|
||||||
|
eth/trie/db, eth/[trie, rlp, common, keyfile], nimcrypto
|
||||||
|
|
||||||
export nimcrypto.`$`
|
export nimcrypto.`$`
|
||||||
|
|
||||||
|
@ -50,3 +52,40 @@ proc crc32*(crc: uint32, buf: openArray[byte]): uint32 =
|
||||||
crcu32 = (crcu32 shr 4) xor kcrc32[int((crcu32 and 0xF) xor (uint32(b) shr 4'u32))]
|
crcu32 = (crcu32 shr 4) xor kcrc32[int((crcu32 and 0xF) xor (uint32(b) shr 4'u32))]
|
||||||
|
|
||||||
result = not crcu32
|
result = not crcu32
|
||||||
|
|
||||||
|
proc loadKeystoreFiles*(conf: NimbusConfiguration): Result[void, string] =
|
||||||
|
try:
|
||||||
|
createDir(conf.keyStore)
|
||||||
|
except OSError, IOError:
|
||||||
|
return err("keystore: cannot create directory")
|
||||||
|
|
||||||
|
for filename in walkDirRec(conf.keyStore):
|
||||||
|
try:
|
||||||
|
var data = json.parseFile(filename)
|
||||||
|
let address: EthAddress = hexToByteArray[20](data["address"].getStr())
|
||||||
|
conf.accounts[address] = NimbusAccount(keystore: data, unlocked: false)
|
||||||
|
except JsonParsingError:
|
||||||
|
return err("keystore: json parsing error " & filename)
|
||||||
|
except ValueError:
|
||||||
|
return err("keystore: data parsing error")
|
||||||
|
except Exception: # json raises Exception
|
||||||
|
return err("keystore: " & getCurrentExceptionMsg())
|
||||||
|
|
||||||
|
result = ok()
|
||||||
|
|
||||||
|
proc getAccount*(conf: NimbusConfiguration, address: EthAddress): Result[NimbusAccount, string] =
|
||||||
|
conf.accounts.withValue(address, val) do:
|
||||||
|
result = ok(val[])
|
||||||
|
do:
|
||||||
|
result = err("getAccount: not available " & address.toHex)
|
||||||
|
|
||||||
|
proc unlockAccount*(conf: NimbusConfiguration, address: EthAddress, password: string): Result[void, string] =
|
||||||
|
var acc = conf.getAccount(address).tryGet()
|
||||||
|
let res = decodeKeyFileJson(acc.keystore, password)
|
||||||
|
if res.isOk:
|
||||||
|
acc.privateKey = res.get()
|
||||||
|
acc.unlocked = true
|
||||||
|
conf.accounts[address] = acc
|
||||||
|
result = ok()
|
||||||
|
else:
|
||||||
|
result = err($res.error)
|
||||||
|
|
|
@ -19,7 +19,7 @@ proc prefixHex*(x: Hash256): string =
|
||||||
"0x" & toLowerAscii($x)
|
"0x" & toLowerAscii($x)
|
||||||
|
|
||||||
proc prefixHex*(x: int64 | uint64 | byte | int): string =
|
proc prefixHex*(x: int64 | uint64 | byte | int): string =
|
||||||
encodeQuantity(x.uint64).toLowerAscii
|
toLowerAscii(encodeQuantity(x.uint64).string)
|
||||||
|
|
||||||
proc prefixHex*(x: openArray[byte]): string =
|
proc prefixHex*(x: openArray[byte]): string =
|
||||||
"0x" & toHex(x, true)
|
"0x" & toHex(x, true)
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
{"password":"applebanana","address":"0e69cde81b1aa07a45c32c6cd85d67229d36bb1b","crypto":{"cipher":"aes-128-ctr","cipherparams":{"iv":"7ead94c4f16f3bf31e1cdffd44227403"},"ciphertext":"f023cbdd0ebb26bea0342e2c5719da6a0499e9f675fff3d3c4beb84e1071174f","kdf":"pbkdf2","kdfparams":{"dklen":32,"c":1000000,"prf":"hmac-sha256","salt":"91edacb4f74698e6516062b230064385"},"mac":"f37290005290f56c71cce329f61945d9b2a1c719bc84e966d30c8c395477d625"},"id":"29014ab9-38c0-42fa-b5cf-f049d8568a41","version":3}
|
|
@ -0,0 +1 @@
|
||||||
|
{"password":"bananamonkey","address":"597176e9a64aad0845d83afdaf698fbeff77703b","crypto":{"cipher":"aes-128-ctr","cipherparams":{"iv":"5ad7a73aa1b46b336bd9640a8cf19436"},"ciphertext":"068c556204f86b9e0d6670e1e80f8d34dfb55416b605c0b9f9d833ba962cc64c","kdf":"pbkdf2","kdfparams":{"dklen":32,"c":1000000,"prf":"hmac-sha256","salt":"aea9c4e3dd598d41926268ed2358965e"},"mac":"30cd4c38da212a85aa277c60ee8be7ae4e9cceea75cedeae516827d339d90550"},"id":"7667da26-1e5b-4281-98a3-c9aad03e8491","version":3}
|
|
@ -0,0 +1 @@
|
||||||
|
{"password":"monkeyelephant","address":"a3b2222afa5c987da6ef773fde8d01b9f23d481f","crypto":{"cipher":"aes-128-ctr","cipherparams":{"iv":"3ca1d5c5151fba8281f8880ece920740"},"ciphertext":"ec8b7626f3494605e4b66a889c3dbf9bae4d8fe249718d25f010441af3cf6c61","kdf":"pbkdf2","kdfparams":{"dklen":32,"c":1000000,"prf":"hmac-sha256","salt":"364a67e8bc0a782d715832946662850c"},"mac":"7001a9fb0f63db2b7538bab2a34d2103c67035f4754a3bcf4760359f530d5c21"},"id":"5098841b-9060-4ce7-baf7-c748b12d79c9","version":3}
|
|
@ -14,7 +14,7 @@ import
|
||||||
../../nimbus/rpc/hexstrings, ../../nimbus/rpc/rpc_types
|
../../nimbus/rpc/hexstrings, ../../nimbus/rpc/rpc_types
|
||||||
|
|
||||||
proc web3_clientVersion(): string
|
proc web3_clientVersion(): string
|
||||||
proc web3_sha3(data: string): string
|
proc web3_sha3(data: HexDataStr): string
|
||||||
proc net_version(): string
|
proc net_version(): string
|
||||||
proc net_peerCount(): HexQuantityStr
|
proc net_peerCount(): HexQuantityStr
|
||||||
proc net_listening(): bool
|
proc net_listening(): bool
|
||||||
|
@ -30,9 +30,9 @@ proc eth_getBalance(data: EthAddressStr, quantityTag: string): HexQuantityStr
|
||||||
proc eth_getStorageAt(data: EthAddressStr, quantity: HexQuantityStr, quantityTag: string): seq[byte]
|
proc eth_getStorageAt(data: EthAddressStr, quantity: HexQuantityStr, quantityTag: string): seq[byte]
|
||||||
proc eth_getTransactionCount(data: EthAddressStr, quantityTag: string): HexQuantityStr
|
proc eth_getTransactionCount(data: EthAddressStr, quantityTag: string): HexQuantityStr
|
||||||
proc eth_getBlockTransactionCountByHash(data: Hash256): HexQuantityStr
|
proc eth_getBlockTransactionCountByHash(data: Hash256): HexQuantityStr
|
||||||
proc eth_getBlockTransactionCountByNumber(quantityTag: string)
|
proc eth_getBlockTransactionCountByNumber(quantityTag: string): HexQuantityStr
|
||||||
proc eth_getUncleCountByBlockHash(data: array[32, byte])
|
proc eth_getUncleCountByBlockHash(data: Hash256): HexQuantityStr
|
||||||
proc eth_getUncleCountByBlockNumber(quantityTag: string)
|
proc eth_getUncleCountByBlockNumber(quantityTag: string): HexQuantityStr
|
||||||
proc eth_getCode(data: EthAddressStr, quantityTag: string): HexDataStr
|
proc eth_getCode(data: EthAddressStr, quantityTag: string): HexDataStr
|
||||||
proc eth_sign(data:EthAddressStr, message: HexDataStr): HexDataStr
|
proc eth_sign(data:EthAddressStr, message: HexDataStr): HexDataStr
|
||||||
#proc eth_sendRawTransaction(data: string, quantityTag: int): UInt256
|
#proc eth_sendRawTransaction(data: string, quantityTag: int): UInt256
|
||||||
|
|
|
@ -6,11 +6,12 @@
|
||||||
# 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
|
||||||
unittest, json, strformat, strutils, options, tables, nimcrypto, stew/byteutils,
|
unittest, json, strformat, strutils, options, tables, os,
|
||||||
|
nimcrypto, stew/byteutils,
|
||||||
json_rpc/[rpcserver, rpcclient], eth/common as eth_common,
|
json_rpc/[rpcserver, rpcclient], eth/common as eth_common,
|
||||||
eth/[rlp, keys], eth/trie/db, eth/p2p/rlpx_protocols/eth_protocol,
|
eth/[rlp, keys], eth/trie/db, eth/p2p/rlpx_protocols/eth_protocol,
|
||||||
../nimbus/rpc/[common, p2p, hexstrings, rpc_types],
|
../nimbus/rpc/[common, p2p, hexstrings, rpc_types],
|
||||||
../nimbus/[constants, vm_state, config, genesis],
|
../nimbus/[constants, vm_state, config, genesis, utils],
|
||||||
../nimbus/db/[accounts_cache, db_chain, storage_types],
|
../nimbus/db/[accounts_cache, db_chain, storage_types],
|
||||||
../nimbus/p2p/chain,
|
../nimbus/p2p/chain,
|
||||||
./rpcclient/test_hexstrings, ./test_helpers
|
./rpcclient/test_hexstrings, ./test_helpers
|
||||||
|
@ -29,10 +30,6 @@ template sourceDir: string = currentSourcePath.rsplit(DirSep, 1)[0]
|
||||||
const sigPath = &"{sourceDir}{DirSep}rpcclient{DirSep}ethcallsigs.nim"
|
const sigPath = &"{sourceDir}{DirSep}rpcclient{DirSep}ethcallsigs.nim"
|
||||||
createRpcSigs(RpcSocketClient, sigPath)
|
createRpcSigs(RpcSocketClient, sigPath)
|
||||||
|
|
||||||
proc toEthAddressStr(address: EthAddress): EthAddressStr =
|
|
||||||
result = ("0x" & address.toHex).ethAddressStr
|
|
||||||
|
|
||||||
|
|
||||||
proc doTests {.async.} =
|
proc doTests {.async.} =
|
||||||
# TODO: Include other transports such as Http
|
# TODO: Include other transports such as Http
|
||||||
var ethNode = setupEthNode(eth)
|
var ethNode = setupEthNode(eth)
|
||||||
|
@ -47,7 +44,25 @@ proc doTests {.async.} =
|
||||||
let
|
let
|
||||||
balance = 100.u256
|
balance = 100.u256
|
||||||
address: EthAddress = hexToByteArray[20]("0x0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6")
|
address: EthAddress = hexToByteArray[20]("0x0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6")
|
||||||
|
|
||||||
|
signer: EthAddress = hexToByteArray[20]("0x0e69cde81b1aa07a45c32c6cd85d67229d36bb1b")
|
||||||
|
ks2: EthAddress = hexToByteArray[20]("0xa3b2222afa5c987da6ef773fde8d01b9f23d481f")
|
||||||
|
ks3: EthAddress = hexToByteArray[20]("0x597176e9a64aad0845d83afdaf698fbeff77703b")
|
||||||
|
|
||||||
conf = getConfiguration()
|
conf = getConfiguration()
|
||||||
|
|
||||||
|
conf.keyStore = "tests" / "keystore"
|
||||||
|
let res = conf.loadKeystoreFiles()
|
||||||
|
if res.isErr:
|
||||||
|
debugEcho res.error
|
||||||
|
doAssert(res.isOk)
|
||||||
|
|
||||||
|
let acc1 = conf.getAccount(signer).tryGet()
|
||||||
|
let unlock = conf.unlockAccount(signer, acc1.keystore["password"].getStr())
|
||||||
|
if unlock.isErr:
|
||||||
|
debugEcho unlock.error
|
||||||
|
doAssert(unlock.isOk)
|
||||||
|
|
||||||
defaultGenesisBlockForNetwork(conf.net.networkId.toPublicNetwork()).commit(chain)
|
defaultGenesisBlockForNetwork(conf.net.networkId.toPublicNetwork()).commit(chain)
|
||||||
state.mutateStateDB:
|
state.mutateStateDB:
|
||||||
db.setBalance(address, balance)
|
db.setBalance(address, balance)
|
||||||
|
@ -73,10 +88,10 @@ proc doTests {.async.} =
|
||||||
|
|
||||||
test "web3_sha3":
|
test "web3_sha3":
|
||||||
expect ValueError:
|
expect ValueError:
|
||||||
discard await client.web3_sha3(NimbusName)
|
discard await client.web3_sha3(NimbusName.HexDataStr)
|
||||||
|
|
||||||
let data = "0x" & byteutils.toHex(NimbusName.toOpenArrayByte(0, NimbusName.len-1))
|
let data = "0x" & byteutils.toHex(NimbusName.toOpenArrayByte(0, NimbusName.len-1))
|
||||||
let res = await client.web3_sha3(data)
|
let res = await client.web3_sha3(data.hexDataStr)
|
||||||
let rawdata = nimcrypto.fromHex(data[2 .. ^1])
|
let rawdata = nimcrypto.fromHex(data[2 .. ^1])
|
||||||
let hash = "0x" & $keccak_256.digest(rawdata)
|
let hash = "0x" & $keccak_256.digest(rawdata)
|
||||||
check hash == res
|
check hash == res
|
||||||
|
@ -135,8 +150,9 @@ proc doTests {.async.} =
|
||||||
|
|
||||||
test "eth_accounts":
|
test "eth_accounts":
|
||||||
let res = await client.eth_accounts()
|
let res = await client.eth_accounts()
|
||||||
# we do not own any accounts, yet
|
check signer.ethAddressStr in res
|
||||||
check res.len == 0
|
check ks2.ethAddressStr in res
|
||||||
|
check ks3.ethAddressStr in res
|
||||||
|
|
||||||
test "eth_blockNumber":
|
test "eth_blockNumber":
|
||||||
let res = await client.eth_blockNumber()
|
let res = await client.eth_blockNumber()
|
||||||
|
@ -163,6 +179,41 @@ proc doTests {.async.} =
|
||||||
let res = await client.eth_getBlockTransactionCountByHash(hash)
|
let res = await client.eth_getBlockTransactionCountByHash(hash)
|
||||||
check res.string == "0x0"
|
check res.string == "0x0"
|
||||||
|
|
||||||
|
test "eth_getBlockTransactionCountByNumber":
|
||||||
|
let res = await client.eth_getBlockTransactionCountByNumber("0x0")
|
||||||
|
check res.string == "0x0"
|
||||||
|
|
||||||
|
test "eth_getUncleCountByBlockHash":
|
||||||
|
let hash = chain.getBlockHash(0.toBlockNumber)
|
||||||
|
let res = await client.eth_getUncleCountByBlockHash(hash)
|
||||||
|
check res.string == "0x0"
|
||||||
|
|
||||||
|
test "eth_getUncleCountByBlockNumber":
|
||||||
|
let res = await client.eth_getUncleCountByBlockNumber("0x0")
|
||||||
|
check res.string == "0x0"
|
||||||
|
|
||||||
|
test "eth_getCode":
|
||||||
|
let res = await client.eth_getCode(ethAddressStr("0xfff7ac99c8e4feb60c9750054bdc14ce1857f181"), "0x0")
|
||||||
|
check res.string == "0x"
|
||||||
|
|
||||||
|
test "eth_sign":
|
||||||
|
let msg = "hello world"
|
||||||
|
let msgHex = hexDataStr(msg.toOpenArrayByte(0, msg.len-1))
|
||||||
|
|
||||||
|
expect ValueError:
|
||||||
|
discard await client.eth_sign(ethAddressStr(ks2), msgHex)
|
||||||
|
|
||||||
|
let res = await client.eth_sign(ethAddressStr(signer), msgHex)
|
||||||
|
let sig = Signature.fromHex(res.string).tryGet()
|
||||||
|
|
||||||
|
# now let us try to verify signature
|
||||||
|
let msgData = "\x19Ethereum Signed Message:\n" & $msg.len & msg
|
||||||
|
let msgDataHex = hexDataStr(msgData.toOpenArrayByte(0, msgData.len-1))
|
||||||
|
let sha3Data = await client.web3_sha3(msgDataHex)
|
||||||
|
let msgHash = hexToByteArray[32](sha3Data)
|
||||||
|
let pubkey = recover(sig, SkMessage(msgHash)).tryGet()
|
||||||
|
let recoveredAddr = pubkey.toCanonicalAddress()
|
||||||
|
check recoveredAddr == signer # verified
|
||||||
|
|
||||||
#test "eth_call":
|
#test "eth_call":
|
||||||
# let
|
# let
|
||||||
|
|
Loading…
Reference in New Issue