From 34972c6cea8854c9b49aa907b1e080659db5e817 Mon Sep 17 00:00:00 2001 From: jangko Date: Tue, 7 Sep 2021 19:30:12 +0700 Subject: [PATCH] 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 --- nimbus/accounts/manager.nim | 104 ++++++++++++++++++++++++++++++++++++ nimbus/config.nim | 30 +---------- nimbus/context.nim | 22 ++++++++ nimbus/rpc/p2p.nim | 24 ++++----- nimbus/utils.nim | 41 +------------- tests/test_clique.nim | 18 ++++--- tests/test_rpc.nim | 16 +++--- 7 files changed, 160 insertions(+), 95 deletions(-) create mode 100644 nimbus/accounts/manager.nim create mode 100644 nimbus/context.nim diff --git a/nimbus/accounts/manager.nim b/nimbus/accounts/manager.nim new file mode 100644 index 000000000..3d4db9394 --- /dev/null +++ b/nimbus/accounts/manager.nim @@ -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) diff --git a/nimbus/config.nim b/nimbus/config.nim index 530a58d69..ab1afa7dd 100644 --- a/nimbus/config.nim +++ b/nimbus/config.nim @@ -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 diff --git a/nimbus/context.nim b/nimbus/context.nim new file mode 100644 index 000000000..96c53fd19 --- /dev/null +++ b/nimbus/context.nim @@ -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() diff --git a/nimbus/rpc/p2p.nim b/nimbus/rpc/p2p.nim index 25c38dd84..c228cfccf 100644 --- a/nimbus/rpc/p2p.nim +++ b/nimbus/rpc/p2p.nim @@ -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") diff --git a/nimbus/utils.nim b/nimbus/utils.nim index 980b08575..3cdb4e5d4 100644 --- a/nimbus/utils.nim +++ b/nimbus/utils.nim @@ -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) diff --git a/tests/test_clique.nim b/tests/test_clique.nim index 8642e2896..2d2571c3b 100644 --- a/tests/test_clique.nim +++ b/tests/test_clique.nim @@ -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) diff --git a/tests/test_rpc.nim b/tests/test_rpc.nim index f054d0ef3..73e86a15d 100644 --- a/tests/test_rpc.nim +++ b/tests/test_rpc.nim @@ -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()