config: remove accounts management from NimbusConfiguration

a new AccountsManager and EthContext is created for managing
keystore and accounts

this is a preparation for new config using ConfUtils
This commit is contained in:
jangko 2021-09-07 19:30:12 +07:00
parent 07b2116f6b
commit 34972c6cea
No known key found for this signature in database
GPG Key ID: 31702AE10541E6B9
7 changed files with 160 additions and 95 deletions

104
nimbus/accounts/manager.nim Normal file
View File

@ -0,0 +1,104 @@
# Nimbus
# Copyright (c) 2021 Status Research & Development GmbH
# Licensed under either of
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
# * MIT license ([LICENSE-MIT](LICENSE-MIT))
# at your option.
# This file may not be copied, modified, or distributed except according to
# those terms.
import
std/[os, json, tables],
stew/[byteutils, results],
eth/[keyfile, common, keys],
chronicles
from nimcrypto/utils import burnMem
type
NimbusAccount* = object
privateKey*: PrivateKey
keystore*: JsonNode
unlocked*: bool
AccountsManager* = object
accounts: Table[EthAddress, NimbusAccount]
proc init*(_: type AccountsManager): AccountsManager =
discard
proc loadKeystores*(am: var AccountsManager, path: string): Result[void, string] =
try:
createDir(path)
except OSError, IOError:
return err("keystore: cannot create directory")
for filename in walkDirRec(path):
try:
var data = json.parseFile(filename)
let address: EthAddress = hexToByteArray[20](data["address"].getStr())
am.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())
ok()
proc getAccount*(am: var AccountsManager, address: EthAddress): Result[NimbusAccount, string] =
am.accounts.withValue(address, value) do:
return ok(value[])
do:
return err("getAccount: not available " & address.toHex)
proc unlockAccount*(am: var AccountsManager, address: EthAddress, password: string): Result[void, string] =
let accRes = am.getAccount(address)
if accRes.isErr:
return err(accRes.error)
var acc = accRes.get()
let res = decodeKeyFileJson(acc.keystore, password)
if res.isOk:
acc.privateKey = res.get()
acc.unlocked = true
am.accounts[address] = acc
return ok()
err($res.error)
proc lockAccount*(am: var AccountsManager, address: EthAddress): Result[void, string] =
am.accounts.withValue(address, acc) do:
acc.unlocked = false
burnMem(acc.privateKey)
am.accounts[address] = acc[]
return ok()
do:
return err("getAccount: not available " & address.toHex)
proc numAccounts*(am: AccountsManager): int =
am.accounts.len
iterator addresses*(am: AccountsManager): EthAddress =
for a in am.accounts.keys:
yield a
proc importPrivateKey*(am: var AccountsManager, fileName: string): Result[void, string] =
try:
let pkhex = readFile(fileName)
let res = PrivateKey.fromHex(pkhex)
if res.isErr:
return err("not a valid private key, expect 32 bytes hex")
let seckey = res.get()
let acc = seckey.toPublicKey().toCanonicalAddress()
am.accounts[acc] = NimbusAccount(
privateKey: seckey,
unlocked: true
)
return ok()
except CatchableError as ex:
return err(ex.msg)

View File

@ -114,11 +114,6 @@ type
Full
Archive
NimbusAccount* = object
privateKey*: PrivateKey
keystore*: JsonNode
unlocked*: bool
NimbusConfiguration* = ref object
## Main Nimbus configuration object
dataDir*: string
@ -133,7 +128,7 @@ type
# You should only create one instance of the RNG per application / library
# Ref is used so that it can be shared between components
rng*: ref BrHmacDrbgContext
accounts*: Table[EthAddress, NimbusAccount]
importKey*: string
importFile*: string
verifyFromOk*: bool ## activate `verifyFrom` setting
verifyFrom*: uint64 ## verification start block, 0 for disable
@ -383,26 +378,6 @@ proc processEthAddress(value: string, address: var EthAddress): ConfigStatus =
except CatchableError:
return ErrorParseOption
proc importPrivateKey(conf: NimbusConfiguration, fileName: string): ConfigStatus =
try:
let pkhex = readFile(fileName)
let res = PrivateKey.fromHex(pkhex)
if res.isErr:
error "not a valid private key, expect 32 bytes hex"
return ErrorParseOption
let seckey = res.get()
let acc = seckey.toPublicKey().toCanonicalAddress()
conf.accounts[acc] = NimbusAccount(
privateKey: seckey,
unlocked: true
)
except CatchableError:
return ErrorParseOption
proc processEthArguments(key, value: string): ConfigStatus =
result = Success
let config = getConfiguration()
@ -423,7 +398,7 @@ proc processEthArguments(key, value: string): ConfigStatus =
of "engine-signer":
result = processEthAddress(value, config.engineSigner)
of "import-key":
result = config.importPrivateKey(value)
config.importKey = value
else:
result = EmptyOption
@ -686,7 +661,6 @@ proc initConfiguration(): NimbusConfiguration =
## Allocates and initializes `NimbusConfiguration` with default values
result = new NimbusConfiguration
result.rng = newRng()
result.accounts = initTable[EthAddress, NimbusAccount]()
## Graphql defaults
result.graphql.enabled = false

22
nimbus/context.nim Normal file
View File

@ -0,0 +1,22 @@
# Nimbus
# Copyright (c) 2021 Status Research & Development GmbH
# Licensed under either of
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
# * MIT license ([LICENSE-MIT](LICENSE-MIT))
# at your option.
# This file may not be copied, modified, or distributed except according to
# those terms.
import
accounts/manager
export manager
type
EthContext* = ref object
am*: AccountsManager
proc newEthContext*(): EthContext =
result = new(EthContext)
result.am = AccountsManager.init()

View File

@ -11,8 +11,8 @@ import
times, options, tables,
json_rpc/rpcserver, hexstrings, stint, stew/byteutils,
eth/[common, keys, rlp, p2p], nimcrypto,
../transaction, ../config, ../vm_state, ../constants,
../utils, ../db/[db_chain, state_db],
".."/[transaction, config, vm_state, constants, utils, context],
../db/[db_chain, state_db],
rpc_types, rpc_utils,
../transaction/call_evm
@ -25,7 +25,7 @@ import
type cast to avoid extra processing.
]#
proc setupEthRpc*(node: EthereumNode, chain: BaseChainDB , server: RpcServer) =
proc setupEthRpc*(node: EthereumNode, ctx: EthContext, chain: BaseChainDB , server: RpcServer) =
proc getAccountDb(header: BlockHeader): ReadOnlyStateDB =
## Retrieves the account db from canonical head
@ -83,10 +83,9 @@ proc setupEthRpc*(node: EthereumNode, chain: BaseChainDB , server: RpcServer) =
result = encodeQuantity(calculateMedianGasPrice(chain).uint64)
server.rpc("eth_accounts") do() -> seq[EthAddressStr]:
## Returns a list of addresses owned by client.
let conf = getConfiguration()
result = newSeqOfCap[EthAddressStr](conf.accounts.len)
for k in keys(conf.accounts):
## Returns a list of addresses owned by client.
result = newSeqOfCap[EthAddressStr](ctx.am.numAccounts)
for k in ctx.am.addresses:
result.add ethAddressStr(k)
server.rpc("eth_blockNumber") do() -> HexQuantityStr:
@ -199,9 +198,8 @@ proc setupEthRpc*(node: EthereumNode, chain: BaseChainDB , server: RpcServer) =
## message: message to sign.
## Returns signature.
let
address = data.toAddress
conf = getConfiguration()
acc = conf.getAccount(address).tryGet()
address = data.toAddress
acc = ctx.am.getAccount(address).tryGet()
msg = hexToSeqByte(message.string)
if not acc.unlocked:
@ -213,8 +211,7 @@ proc setupEthRpc*(node: EthereumNode, chain: BaseChainDB , server: RpcServer) =
## eth_sendRawTransaction
let
address = data.source.toAddress
conf = getConfiguration()
acc = conf.getAccount(address).tryGet()
acc = ctx.am.getAccount(address).tryGet()
if not acc.unlocked:
raise newException(ValueError, "Account locked, please unlock it first")
@ -237,8 +234,7 @@ proc setupEthRpc*(node: EthereumNode, chain: BaseChainDB , server: RpcServer) =
# TODO: Relies on pending pool implementation
let
address = data.source.toAddress
conf = getConfiguration()
acc = conf.getAccount(address).tryGet()
acc = ctx.am.getAccount(address).tryGet()
if not acc.unlocked:
raise newException(ValueError, "Account locked, please unlock it first")

View File

@ -1,6 +1,6 @@
import
os, tables, json, ./config, stew/[results, byteutils],
eth/trie/db, eth/[trie, rlp, common, keyfile], nimcrypto
pkg/[nimcrypto],
eth/[trie, rlp, common, trie/db]
export nimcrypto.`$`
@ -58,40 +58,3 @@ proc crc32*(crc: uint32, buf: openArray[byte]): uint32 =
crcu32 = (crcu32 shr 4) xor kcrc32[int((crcu32 and 0xF) xor (uint32(b) shr 4'u32))]
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)

View File

@ -19,7 +19,7 @@ import
clique/clique_helpers
],
../nimbus/utils/ec_recover,
../nimbus/[config, utils, constants],
../nimbus/[config, utils, constants, context],
./test_clique/[pool, undump],
eth/[common, keys],
stint, stew/byteutils,
@ -248,15 +248,19 @@ proc cliqueMiscTests() =
var opt = initOptParser("--engine-signer:$1 --import-key:$2" % [engineSigner, privateKey])
let res = processArguments(msg, opt)
check res == Success
let signer = hexToByteArray[20](engineSigner)
let conf = getConfiguration()
check signer in conf.accounts
let
signer = hexToByteArray[20](engineSigner)
ctx = newEthContext()
conf = getConfiguration()
check ctx.am.importPrivateKey(conf.importKey).isOk()
check ctx.am.getAccount(signer).isOk()
proc signFunc(signer: EthAddress, message: openArray[byte]): Result[RawSignature, cstring] {.gcsafe.} =
let
hashData = keccakHash(message)
conf = getConfiguration()
acc = conf.accounts[signer]
hashData = keccakHash(message)
acc = ctx.am.getAccount(signer).tryGet()
rawSign = sign(acc.privateKey, SkMessage(hashData.data)).toRaw
ok(rawSign)

View File

@ -16,6 +16,7 @@ import
../nimbus/p2p/[chain, executor, executor/executor_helpers],
../nimbus/sync/protocol_eth65,
../nimbus/utils/difficulty,
../nimbus/context,
./rpcclient/test_hexstrings, ./test_helpers, ./macro_assembler
# Perform checks for hex string validation
@ -35,11 +36,11 @@ type
txHash: Hash256
blockHash: HAsh256
proc setupEnv(chain: BaseChainDB, signer, ks2: EthAddress, conf: NimbusConfiguration): TestEnv =
proc setupEnv(chain: BaseChainDB, signer, ks2: EthAddress, ctx: EthContext): TestEnv =
var
parent = chain.getCanonicalHead()
ac = newAccountStateDB(chain.db, parent.stateRoot, chain.pruneTrie)
acc = conf.getAccount(signer).tryGet()
acc = ctx.am.getAccount(signer).tryGet()
blockNumber = 1.toBlockNumber
parentHash = parent.blockHash
@ -131,23 +132,24 @@ proc rpcMain*() =
ks2: EthAddress = hexToByteArray[20]("0xa3b2222afa5c987da6ef773fde8d01b9f23d481f")
ks3: EthAddress = hexToByteArray[20]("0x597176e9a64aad0845d83afdaf698fbeff77703b")
conf = getConfiguration()
ctx = newEthContext()
ethNode.chain = newChain(chain)
conf.keyStore = "tests" / "keystore"
let res = conf.loadKeystoreFiles()
let res = ctx.am.loadKeystores(conf.keyStore)
if res.isErr:
debugEcho res.error
doAssert(res.isOk)
let acc1 = conf.getAccount(signer).tryGet()
let unlock = conf.unlockAccount(signer, acc1.keystore["password"].getStr())
let acc1 = ctx.am.getAccount(signer).tryGet()
let unlock = ctx.am.unlockAccount(signer, acc1.keystore["password"].getStr())
if unlock.isErr:
debugEcho unlock.error
doAssert(unlock.isOk)
defaultGenesisBlockForNetwork(conf.net.networkId).commit(chain)
doAssert(canonicalHeadHashKey().toOpenArray in chain.db)
let env = setupEnv(chain, signer, ks2, conf)
let env = setupEnv(chain, signer, ks2, ctx)
# Create Ethereum RPCs
let RPC_PORT = 8545
@ -155,7 +157,7 @@ proc rpcMain*() =
rpcServer = newRpcSocketServer(["localhost:" & $RPC_PORT])
client = newRpcSocketClient()
setupCommonRpc(ethNode, rpcServer)
setupEthRpc(ethNode, chain, rpcServer)
setupEthRpc(ethNode, ctx, chain, rpcServer)
# Begin tests
rpcServer.start()