mirror of
https://github.com/embarklabs/embark.git
synced 2025-02-16 15:47:25 +00:00
refactor(stack/proxy): make rpc-manager stack component
This commit refactors the `rpc-manager` to be a stack component and therefore making it available to a variaty of plugins that might need access to its APIs so they can register new RPC interceptors. RpcModifiers have been renamed to `rpc-interceptors` and extracted from the `rpc-manager` source into their own plugin. The reason for the renaming was that modifiers aren't always modifying RPC APIs, but potentially introduce brand new ones. There are a bunch of new APIs that can be used to register new RPC APIs: ``` events.request('rpc:request:interceptor:register', 'method_name', requestInterceptorFn); events.request('rpc:response:interceptor:register', 'method_name', responseInterceptorFn); function requestInterceptorFn(params: ProxyRequestParams<T>) { // mutate `params` here return params; } function responseInterceptorFn(params: ProxyResponseParams<T, S>) { // mutate `params` here return params; } ``` A few things to note here: - `method_name` is either `string`, `string[]`, or `Regex`. This make registering custom RPC APIs extremely flexible. - A `requestInterceptorFn()` gets access to `ProxyRequestParams<T>` where `T` is the type of the params being send to the RPC API. - A `responseInterceptorFn()` gets access to `ProxyResponseParams<T, S>` where `T` is the type of the original request parameters and `S` the type of the response coming from the RPC API. These APIs are used by all `rpc-interceptors` and can be used the same way by any oother plugin that aims to create new or intercept existing APIs.
This commit is contained in:
parent
4199124e40
commit
947ea23c35
@ -71,6 +71,7 @@
|
|||||||
"eth_getTransactionReceipt": "eth_getTransactionReceipt",
|
"eth_getTransactionReceipt": "eth_getTransactionReceipt",
|
||||||
"eth_call": "eth_call",
|
"eth_call": "eth_call",
|
||||||
"eth_accounts": "eth_accounts",
|
"eth_accounts": "eth_accounts",
|
||||||
|
"eth_sign": "eth_sign",
|
||||||
"eth_signTypedData": "eth_signTypedData",
|
"eth_signTypedData": "eth_signTypedData",
|
||||||
"personal_listAccounts": "personal_listAccounts",
|
"personal_listAccounts": "personal_listAccounts",
|
||||||
"personal_newAccount": "personal_newAccount"
|
"personal_newAccount": "personal_newAccount"
|
||||||
@ -114,8 +115,10 @@
|
|||||||
"development": "development"
|
"development": "development"
|
||||||
},
|
},
|
||||||
"defaultMigrationsDir": "migrations",
|
"defaultMigrationsDir": "migrations",
|
||||||
"defaultEmbarkConfig": {
|
"defaultEmbarkConfig": {
|
||||||
"contracts": ["contracts/**"],
|
"contracts": [
|
||||||
|
"contracts/**"
|
||||||
|
],
|
||||||
"app": {},
|
"app": {},
|
||||||
"buildDir": "dist/",
|
"buildDir": "dist/",
|
||||||
"config": "config/",
|
"config": "config/",
|
||||||
@ -123,8 +126,7 @@
|
|||||||
"versions": {
|
"versions": {
|
||||||
"solc": "0.6.1"
|
"solc": "0.6.1"
|
||||||
},
|
},
|
||||||
"plugins": {
|
"plugins": {},
|
||||||
},
|
|
||||||
"options": {
|
"options": {
|
||||||
"solc": {
|
"solc": {
|
||||||
"optimize": true,
|
"optimize": true,
|
||||||
|
@ -5,6 +5,11 @@ export type Maybe<T> = false | 0 | undefined | null | T;
|
|||||||
import { AbiItem } from "web3-utils";
|
import { AbiItem } from "web3-utils";
|
||||||
import { EmbarkConfig as _EmbarkConfig } from './config';
|
import { EmbarkConfig as _EmbarkConfig } from './config';
|
||||||
|
|
||||||
|
export interface Account {
|
||||||
|
address: string;
|
||||||
|
privateKey: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface Contract {
|
export interface Contract {
|
||||||
abiDefinition: AbiItem[];
|
abiDefinition: AbiItem[];
|
||||||
deployedAddress: string;
|
deployedAddress: string;
|
||||||
|
@ -276,7 +276,7 @@ export class Engine {
|
|||||||
this.registerModulePackage('embark-ethereum-blockchain-client');
|
this.registerModulePackage('embark-ethereum-blockchain-client');
|
||||||
this.registerModulePackage('embark-web3');
|
this.registerModulePackage('embark-web3');
|
||||||
this.registerModulePackage('embark-accounts-manager');
|
this.registerModulePackage('embark-accounts-manager');
|
||||||
this.registerModulePackage('embark-rpc-manager');
|
this.registerModulePackage('embark-rpc-interceptors');
|
||||||
this.registerModulePackage('embark-specialconfigs', { plugins: this.plugins });
|
this.registerModulePackage('embark-specialconfigs', { plugins: this.plugins });
|
||||||
this.registerModulePackage('embark-transaction-logger');
|
this.registerModulePackage('embark-transaction-logger');
|
||||||
this.registerModulePackage('embark-transaction-tracker');
|
this.registerModulePackage('embark-transaction-tracker');
|
||||||
|
@ -40,9 +40,6 @@
|
|||||||
{
|
{
|
||||||
"path": "../../plugins/plugin-cmd"
|
"path": "../../plugins/plugin-cmd"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"path": "../../plugins/rpc-manager"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"path": "../../plugins/scaffolding"
|
"path": "../../plugins/scaffolding"
|
||||||
},
|
},
|
||||||
|
@ -45,7 +45,8 @@
|
|||||||
"@babel/runtime-corejs3": "7.8.4",
|
"@babel/runtime-corejs3": "7.8.4",
|
||||||
"core-js": "3.4.3",
|
"core-js": "3.4.3",
|
||||||
"embark-core": "^5.3.0-nightly.16",
|
"embark-core": "^5.3.0-nightly.16",
|
||||||
"embark-i18n": "^5.3.0-nightly.4",
|
"embark-i18n": "^5.3.0-nightly.5",
|
||||||
|
"embark-proxy": "^5.3.0-nightly.16",
|
||||||
"embark-utils": "^5.3.0-nightly.16",
|
"embark-utils": "^5.3.0-nightly.16",
|
||||||
"ethers": "4.0.40",
|
"ethers": "4.0.40",
|
||||||
"quorum-js": "0.3.4",
|
"quorum-js": "0.3.4",
|
||||||
|
@ -1,11 +1,10 @@
|
|||||||
import { __ } from 'embark-i18n';
|
|
||||||
import Web3 from 'web3';
|
import Web3 from 'web3';
|
||||||
import { Embark, EmbarkEvents, ContractsConfig } from 'embark-core';
|
import { Embark, EmbarkEvents, ContractsConfig } from 'embark-core';
|
||||||
|
|
||||||
declare module "embark-core" {
|
declare module "embark-core" {
|
||||||
interface ContractConfig {
|
interface ContractConfig {
|
||||||
privateFor: string[];
|
privateFor?: string[];
|
||||||
privateFrom: string;
|
privateFrom?: string;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
157
packages/plugins/quorum/src/eth_sendRawTransaction.ts
Normal file
157
packages/plugins/quorum/src/eth_sendRawTransaction.ts
Normal file
@ -0,0 +1,157 @@
|
|||||||
|
import { Account, Embark } from "embark-core";
|
||||||
|
import Web3 from "web3";
|
||||||
|
const { blockchain: blockchainConstants } = require("embark-core/constants");
|
||||||
|
import { ProxyRequestParams, ProxyResponseParams } from "embark-proxy";
|
||||||
|
import { AccountParser, dappPath } from "embark-utils";
|
||||||
|
import quorumjs from "quorum-js";
|
||||||
|
|
||||||
|
const TESSERA_PRIVATE_URL_DEFAULT = "http://localhost:9081";
|
||||||
|
|
||||||
|
declare module "embark-core" {
|
||||||
|
interface ClientConfig {
|
||||||
|
tesseraPrivateUrl?: string;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export default class EthSendRawTransaction {
|
||||||
|
|
||||||
|
private _rawTransactionManager: any;
|
||||||
|
|
||||||
|
private _web3: Web3 | null = null;
|
||||||
|
|
||||||
|
public _accounts: Account[] | null = null;
|
||||||
|
|
||||||
|
public _nodeAccounts: string[] | null = null;
|
||||||
|
|
||||||
|
constructor(private embark: Embark) { }
|
||||||
|
|
||||||
|
protected get web3() {
|
||||||
|
return (async () => {
|
||||||
|
if (!this._web3) {
|
||||||
|
await this.embark.events.request2("blockchain:started");
|
||||||
|
// get connection directly to the node
|
||||||
|
const provider = await this.embark.events.request2("blockchain:node:provider", "ethereum");
|
||||||
|
this._web3 = new Web3(provider);
|
||||||
|
}
|
||||||
|
return this._web3;
|
||||||
|
})();
|
||||||
|
}
|
||||||
|
|
||||||
|
private get nodeAccounts() {
|
||||||
|
return (async () => {
|
||||||
|
if (!this._nodeAccounts) {
|
||||||
|
const web3 = await this.web3;
|
||||||
|
this._nodeAccounts = await web3.eth.getAccounts();
|
||||||
|
}
|
||||||
|
return this._nodeAccounts || [];
|
||||||
|
})();
|
||||||
|
}
|
||||||
|
|
||||||
|
private get accounts() {
|
||||||
|
return (async () => {
|
||||||
|
if (!this._accounts) {
|
||||||
|
const web3 = await this.web3;
|
||||||
|
const nodeAccounts = await this.nodeAccounts;
|
||||||
|
this._accounts = AccountParser.parseAccountsConfig(this.embark.config.blockchainConfig.accounts, web3, dappPath(), this.embark.logger, nodeAccounts);
|
||||||
|
}
|
||||||
|
return this._accounts || [];
|
||||||
|
})();
|
||||||
|
}
|
||||||
|
|
||||||
|
getFilter() {
|
||||||
|
return blockchainConstants.transactionMethods.eth_sendRawTransaction;
|
||||||
|
}
|
||||||
|
|
||||||
|
private get rawTransactionManager() {
|
||||||
|
return (async () => {
|
||||||
|
if (this._rawTransactionManager) {
|
||||||
|
return this._rawTransactionManager;
|
||||||
|
}
|
||||||
|
// RawTransactionManager doesn't support websockets, and uses the
|
||||||
|
// currentProvider.host URI to communicate with the node over HTTP. Therefore, we can
|
||||||
|
// populate currentProvider.host with our proxy HTTP endpoint, without affecting
|
||||||
|
// web3's websocket connection.
|
||||||
|
// @ts-ignore
|
||||||
|
web3.eth.currentProvider.host = await this.embark.events.request2("proxy:endpoint:http:get");
|
||||||
|
const web3 = await this.web3;
|
||||||
|
this._rawTransactionManager = quorumjs.RawTransactionManager(web3, {
|
||||||
|
privateUrl: this.embark.config.blockchainConfig.clientConfig?.tesseraPrivateUrl ?? TESSERA_PRIVATE_URL_DEFAULT
|
||||||
|
});
|
||||||
|
return this._rawTransactionManager;
|
||||||
|
})();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async shouldHandle(params) {
|
||||||
|
if (params.request.method !== blockchainConstants.transactionMethods.eth_sendRawTransaction) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const accounts = await this.accounts;
|
||||||
|
if (!(accounts && accounts.length)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only handle quorum requests that came via eth_sendTransaction
|
||||||
|
// If the user wants to send a private raw tx directly,
|
||||||
|
// web3.eth.sendRawPrivateTransaction should be used (which calls
|
||||||
|
// eth_sendRawPrivateTransaction)
|
||||||
|
const originalPayload = params.originalRequest?.params[0];
|
||||||
|
const privateFor = originalPayload?.privateFor;
|
||||||
|
const privateFrom = originalPayload?.privateFrom;
|
||||||
|
if (!privateFor && !privateFrom) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const from = accounts.find((acc) => Web3.utils.toChecksumAddress(acc.address) === Web3.utils.toChecksumAddress(originalPayload.from));
|
||||||
|
if (!from?.privateKey) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return { originalPayload, privateFor, privateFrom, from };
|
||||||
|
}
|
||||||
|
|
||||||
|
private stripPrefix(value) {
|
||||||
|
return value?.indexOf('0x') === 0 ? value.substring(2) : value;
|
||||||
|
}
|
||||||
|
|
||||||
|
async interceptRequest(params: ProxyRequestParams<string>) {
|
||||||
|
const shouldHandle = await this.shouldHandle(params);
|
||||||
|
if (!shouldHandle) {
|
||||||
|
return params;
|
||||||
|
}
|
||||||
|
|
||||||
|
// manually send to the node in the response
|
||||||
|
params.sendToNode = false;
|
||||||
|
return params;
|
||||||
|
}
|
||||||
|
|
||||||
|
async interceptResponse(params: ProxyResponseParams<string, any>) {
|
||||||
|
const shouldHandle = await this.shouldHandle(params);
|
||||||
|
if (!shouldHandle) {
|
||||||
|
return params;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { originalPayload, privateFor, privateFrom, from } = shouldHandle;
|
||||||
|
const { gas, gasPrice, gasLimit, to, value, nonce, bytecodeWithInitParam, data } = originalPayload;
|
||||||
|
|
||||||
|
const rawTransactionManager = await this.rawTransactionManager;
|
||||||
|
|
||||||
|
const rawTx = {
|
||||||
|
gasPrice: this.stripPrefix(gasPrice) ?? (0).toString(16),
|
||||||
|
gasLimit: this.stripPrefix(gasLimit) ?? (4300000).toString(16),
|
||||||
|
gas: this.stripPrefix(gas),
|
||||||
|
to,
|
||||||
|
value: this.stripPrefix(value) ?? (0).toString(16),
|
||||||
|
data: bytecodeWithInitParam ?? data,
|
||||||
|
from,
|
||||||
|
isPrivate: true,
|
||||||
|
privateFrom,
|
||||||
|
privateFor,
|
||||||
|
nonce
|
||||||
|
};
|
||||||
|
|
||||||
|
const { transactionHash } = await rawTransactionManager.sendRawTransaction(rawTx);
|
||||||
|
params.response.result = transactionHash;
|
||||||
|
|
||||||
|
return params;
|
||||||
|
}
|
||||||
|
}
|
@ -3,6 +3,7 @@ import {testRpcWithEndpoint, testWsEndpoint} from "embark-utils";
|
|||||||
import constants from "embark-core/constants";
|
import constants from "embark-core/constants";
|
||||||
import { QuorumWeb3Extensions } from "./quorumWeb3Extensions";
|
import { QuorumWeb3Extensions } from "./quorumWeb3Extensions";
|
||||||
import QuorumDeployer from "./deployer";
|
import QuorumDeployer from "./deployer";
|
||||||
|
import EthSendRawTransaction from "./eth_sendRawTransaction";
|
||||||
export { getBlock, getTransaction, getTransactionReceipt, decodeParameters } from "./quorumWeb3Extensions";
|
export { getBlock, getTransaction, getTransactionReceipt, decodeParameters } from "./quorumWeb3Extensions";
|
||||||
|
|
||||||
class Quorum {
|
class Quorum {
|
||||||
@ -51,6 +52,9 @@ class Quorum {
|
|||||||
|
|
||||||
const deployer = new QuorumDeployer(this.embark);
|
const deployer = new QuorumDeployer(this.embark);
|
||||||
await deployer.registerDeployer();
|
await deployer.registerDeployer();
|
||||||
|
|
||||||
|
const ethSendRawTransaction = new EthSendRawTransaction(this.embark);
|
||||||
|
await ethSendRawTransaction.registerRpcInterceptors();
|
||||||
}
|
}
|
||||||
|
|
||||||
shouldInit() {
|
shouldInit() {
|
||||||
|
@ -18,6 +18,9 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "../../core/utils"
|
"path": "../../core/utils"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "../../stack/proxy"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
embark-rpc-manager
|
embark-rpc-interceptors
|
||||||
==========================
|
==========================
|
||||||
|
|
||||||
> Embark RPC Manager
|
> Embark RPC Interceptors
|
||||||
|
|
||||||
Modifies RPC calls to/from Embark (to/from `embark-proxy`).
|
Intercept RPC calls to/from Embark's proxy (`embark-proxy`) by registering the interceptors.
|
||||||
|
|
||||||
Visit [framework.embarklabs.io](https://framework.embarklabs.io/) to get started with
|
Visit [framework.embarklabs.io](https://framework.embarklabs.io/) to get started with
|
||||||
[Embark](https://github.com/embarklabs/embark).
|
[Embark](https://github.com/embarklabs/embark).
|
@ -1,9 +1,9 @@
|
|||||||
{
|
{
|
||||||
"name": "embark-rpc-manager",
|
"name": "embark-rpc-interceptors",
|
||||||
"version": "5.3.0-nightly.16",
|
"version": "5.3.0-nightly.16",
|
||||||
"description": "Embark RPC Manager",
|
"description": "Embark RPC Manager",
|
||||||
"repository": {
|
"repository": {
|
||||||
"directory": "packages/plugins/embark-rpc-manager",
|
"directory": "packages/plugins/embark-rpc-interceptors",
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/embarklabs/embark/"
|
"url": "https://github.com/embarklabs/embark/"
|
||||||
},
|
},
|
||||||
@ -58,8 +58,8 @@
|
|||||||
"embark-i18n": "^5.3.0-nightly.5",
|
"embark-i18n": "^5.3.0-nightly.5",
|
||||||
"embark-logger": "^5.3.0-nightly.16",
|
"embark-logger": "^5.3.0-nightly.16",
|
||||||
"embark-utils": "^5.3.0-nightly.16",
|
"embark-utils": "^5.3.0-nightly.16",
|
||||||
|
"embark-proxy": "^5.3.0-nightly.16",
|
||||||
"lodash.clonedeep": "4.5.0",
|
"lodash.clonedeep": "4.5.0",
|
||||||
"quorum-js": "0.3.4",
|
|
||||||
"web3": "1.2.6"
|
"web3": "1.2.6"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
@ -0,0 +1,24 @@
|
|||||||
|
import { Embark } from 'embark-core';
|
||||||
|
import { ProxyRequestParams, ProxyResponseParams } from 'embark-proxy';
|
||||||
|
import RpcInterceptor from "./rpcInterceptor";
|
||||||
|
|
||||||
|
export default class EmbarkSmartContracts extends RpcInterceptor {
|
||||||
|
|
||||||
|
constructor(embark: Embark) {
|
||||||
|
super(embark);
|
||||||
|
}
|
||||||
|
|
||||||
|
getFilter() {
|
||||||
|
return 'embark_getSmartContracts';
|
||||||
|
}
|
||||||
|
|
||||||
|
async interceptRequest(params: ProxyRequestParams<any>) {
|
||||||
|
params.sendToNode = false;
|
||||||
|
return params;
|
||||||
|
}
|
||||||
|
|
||||||
|
async interceptResponse(params: ProxyResponseParams<any, any>) {
|
||||||
|
params.response.result = await this.embark.events.request2('contracts:list');
|
||||||
|
return params;
|
||||||
|
}
|
||||||
|
}
|
31
packages/plugins/rpc-interceptors/src/lib/eth_accounts.ts
Normal file
31
packages/plugins/rpc-interceptors/src/lib/eth_accounts.ts
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
import { Embark } from "embark-core";
|
||||||
|
const { blockchain: blockchainConstants } = require("embark-core/constants");
|
||||||
|
import RpcInterceptor from "./rpcInterceptor";
|
||||||
|
import { ProxyRequestParams, ProxyResponseParams } from "embark-proxy";
|
||||||
|
|
||||||
|
export default class EthAccounts extends RpcInterceptor {
|
||||||
|
|
||||||
|
constructor(embark: Embark) {
|
||||||
|
super(embark);
|
||||||
|
}
|
||||||
|
|
||||||
|
getFilter() {
|
||||||
|
return [
|
||||||
|
blockchainConstants.transactionMethods.eth_accounts,
|
||||||
|
blockchainConstants.transactionMethods.personal_listAccounts
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
async interceptRequest(params: ProxyRequestParams<string>) {
|
||||||
|
return params;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async interceptResponse(params: ProxyResponseParams<string, any>) {
|
||||||
|
const accounts = await this.accounts;
|
||||||
|
if (!accounts?.length) {
|
||||||
|
return params;
|
||||||
|
}
|
||||||
|
params.response.result = accounts.map((acc) => acc.address);
|
||||||
|
return params;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,98 @@
|
|||||||
|
import async from "async";
|
||||||
|
import { Account, Callback, Embark, EmbarkPlugins } from "embark-core";
|
||||||
|
import { AccountParser, dappPath } from "embark-utils";
|
||||||
|
import Web3 from "web3";
|
||||||
|
const { blockchain: blockchainConstants } = require("embark-core/constants");
|
||||||
|
import RpcInterceptor from "./rpcInterceptor";
|
||||||
|
import cloneDeep from "lodash.clonedeep";
|
||||||
|
import { ProxyRequestParams, ProxyResponseParams } from "embark-proxy";
|
||||||
|
|
||||||
|
export default class EthSendTransaction extends RpcInterceptor {
|
||||||
|
|
||||||
|
private signTransactionQueue: any;
|
||||||
|
|
||||||
|
private nonceCache: any = {};
|
||||||
|
|
||||||
|
private plugins: EmbarkPlugins;
|
||||||
|
|
||||||
|
constructor(embark: Embark) {
|
||||||
|
super(embark);
|
||||||
|
|
||||||
|
this.plugins = embark.config.plugins;
|
||||||
|
this.signTransactionQueue = async.queue(({ payload, account, web3 }, callback: Callback<string>) => {
|
||||||
|
this.getNonce(web3, payload.from, async (err: any, newNonce: number) => {
|
||||||
|
if (err) {
|
||||||
|
return callback(err);
|
||||||
|
}
|
||||||
|
payload.nonce = newNonce;
|
||||||
|
try {
|
||||||
|
const result = await web3.eth.accounts.signTransaction(payload, account.privateKey);
|
||||||
|
callback(null, result.rawTransaction);
|
||||||
|
} catch (err) {
|
||||||
|
callback(err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
getFilter() {
|
||||||
|
return blockchainConstants.transactionMethods.eth_sendTransaction;
|
||||||
|
}
|
||||||
|
// TODO: pull this out in to rpc-manager/utils once https://github.com/embarklabs/embark/pull/2150 is merged.
|
||||||
|
private async getNonce(web3: Web3, address: string, callback: Callback<any>) {
|
||||||
|
web3.eth.getTransactionCount(address, (error: any, transactionCount: number) => {
|
||||||
|
if (error) {
|
||||||
|
return callback(error, null);
|
||||||
|
}
|
||||||
|
if (this.nonceCache[address] === undefined) {
|
||||||
|
this.nonceCache[address] = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (transactionCount > this.nonceCache[address]) {
|
||||||
|
this.nonceCache[address] = transactionCount;
|
||||||
|
return callback(null, this.nonceCache[address]);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.nonceCache[address]++;
|
||||||
|
callback(null, this.nonceCache[address]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async interceptRequest(params: ProxyRequestParams<any>) {
|
||||||
|
const accounts = await this.accounts;
|
||||||
|
const web3 = await this.web3;
|
||||||
|
|
||||||
|
if (!accounts?.length) {
|
||||||
|
return params;
|
||||||
|
}
|
||||||
|
// Check if we have that account in our wallet
|
||||||
|
const account = accounts.find((acc) => Web3.utils.toChecksumAddress(acc.address) === Web3.utils.toChecksumAddress(params.request.params[0].from));
|
||||||
|
|
||||||
|
if (!account?.privateKey) {
|
||||||
|
return params;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
|
||||||
|
return this.signTransactionQueue.push({ payload: params.request.params[0], account, web3 }, (err: any, newPayload: any) => {
|
||||||
|
if (err) {
|
||||||
|
return reject(err);
|
||||||
|
}
|
||||||
|
params.originalRequest = cloneDeep(params.request);
|
||||||
|
params.request.method = blockchainConstants.transactionMethods.eth_sendRawTransaction;
|
||||||
|
params.request.params = [newPayload];
|
||||||
|
// allow for any mods to eth_sendRawTransaction
|
||||||
|
this.plugins.runActionsForEvent('blockchain:proxy:request', params, (error, requestParams) => {
|
||||||
|
if (err) {
|
||||||
|
return reject(err);
|
||||||
|
}
|
||||||
|
resolve(requestParams);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async interceptResponse(params: ProxyResponseParams<any, any>) {
|
||||||
|
return params;
|
||||||
|
}
|
||||||
|
}
|
55
packages/plugins/rpc-interceptors/src/lib/eth_signData.ts
Normal file
55
packages/plugins/rpc-interceptors/src/lib/eth_signData.ts
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
import { Account, Embark } from "embark-core";
|
||||||
|
import { __ } from "embark-i18n";
|
||||||
|
import Web3 from "web3";
|
||||||
|
import RpcInterceptor from "./rpcInterceptor";
|
||||||
|
import { handleSignRequest } from './utils/signUtils';
|
||||||
|
const { blockchain: blockchainConstants } = require("embark-core/constants");
|
||||||
|
import { ProxyRequestParams, ProxyResponseParams } from "embark-proxy";
|
||||||
|
import { AccountParser, dappPath } from "embark-utils";
|
||||||
|
|
||||||
|
export default class EthSignData extends RpcInterceptor {
|
||||||
|
|
||||||
|
constructor(embark: Embark) {
|
||||||
|
super(embark);
|
||||||
|
}
|
||||||
|
|
||||||
|
getFilter() {
|
||||||
|
return blockchainConstants.transactionMethods.eth_sign;
|
||||||
|
}
|
||||||
|
|
||||||
|
async interceptRequest(params: ProxyRequestParams<string>) {
|
||||||
|
const nodeAccounts = await this.nodeAccounts;
|
||||||
|
return handleSignRequest(nodeAccounts, params);
|
||||||
|
}
|
||||||
|
|
||||||
|
async interceptResponse(params: ProxyResponseParams<string, string>) {
|
||||||
|
|
||||||
|
const [fromAddr, data] = params.request.params;
|
||||||
|
|
||||||
|
const accounts = await this.accounts;
|
||||||
|
const nodeAccounts = await this.nodeAccounts;
|
||||||
|
|
||||||
|
const nodeAccount = nodeAccounts.find(acc => (
|
||||||
|
Web3.utils.toChecksumAddress(acc) ===
|
||||||
|
Web3.utils.toChecksumAddress(fromAddr)
|
||||||
|
));
|
||||||
|
if (nodeAccount) {
|
||||||
|
return params;
|
||||||
|
}
|
||||||
|
|
||||||
|
const account = accounts.find(acc => (
|
||||||
|
Web3.utils.toChecksumAddress(acc.address) ===
|
||||||
|
Web3.utils.toChecksumAddress(fromAddr)
|
||||||
|
));
|
||||||
|
|
||||||
|
if (!(account && account.privateKey)) {
|
||||||
|
throw new Error(__("Could not sign transaction because Embark does not have a private key associated with '%s'. " +
|
||||||
|
"Please ensure you have configured your account(s) to use a mnemonic, privateKey, or privateKeyFile.", fromAddr));
|
||||||
|
}
|
||||||
|
|
||||||
|
const signature = new Web3().eth.accounts.privateKeyToAccount(account.privateKey).sign(data).signature;
|
||||||
|
params.response.result = signature;
|
||||||
|
|
||||||
|
return params;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,47 @@
|
|||||||
|
import { sign, transaction } from "@omisego/omg-js-util";
|
||||||
|
import { Account, Embark } from "embark-core";
|
||||||
|
import { AccountParser, dappPath } from "embark-utils";
|
||||||
|
import { __ } from "embark-i18n";
|
||||||
|
import Web3 from "web3";
|
||||||
|
import RpcInterceptor from "./rpcInterceptor";
|
||||||
|
import { handleSignRequest, isNodeAccount } from './utils/signUtils';
|
||||||
|
const { blockchain: blockchainConstants } = require("embark-core/constants");
|
||||||
|
import { ProxyRequestParams, ProxyResponseParams } from "embark-proxy";
|
||||||
|
|
||||||
|
export default class EthSignTypedData extends RpcInterceptor {
|
||||||
|
|
||||||
|
constructor(embark: Embark) {
|
||||||
|
super(embark);
|
||||||
|
}
|
||||||
|
|
||||||
|
getFilter() {
|
||||||
|
return /.*signTypedData.*/;
|
||||||
|
}
|
||||||
|
|
||||||
|
async interceptRequest(params: ProxyRequestParams<string>) {
|
||||||
|
const nodeAccounts = await this.nodeAccounts;
|
||||||
|
return handleSignRequest(nodeAccounts, params);
|
||||||
|
}
|
||||||
|
|
||||||
|
async interceptResponse(params: ProxyResponseParams<string, string>) {
|
||||||
|
const [fromAddr, typedData] = params.request.params;
|
||||||
|
const accounts = await this.accounts;
|
||||||
|
const nodeAccounts = await this.nodeAccounts;
|
||||||
|
|
||||||
|
if (isNodeAccount(nodeAccounts, fromAddr)) {
|
||||||
|
// If it's a node account, we send the result because it should already be signed
|
||||||
|
return params;
|
||||||
|
}
|
||||||
|
|
||||||
|
const account = accounts.find((acc) => Web3.utils.toChecksumAddress(acc.address) === Web3.utils.toChecksumAddress(fromAddr));
|
||||||
|
if (!(account && account.privateKey)) {
|
||||||
|
throw new Error(__("Could not sign transaction because Embark does not have a private key associated with '%s'. " +
|
||||||
|
"Please ensure you have configured your account(s) to use a mnemonic, privateKey, or privateKeyFile.", fromAddr));
|
||||||
|
}
|
||||||
|
const toSign = transaction.getToSignHash(typeof typedData === "string" ? JSON.parse(typedData) : typedData);
|
||||||
|
const signature = sign(toSign, [account.privateKey]);
|
||||||
|
|
||||||
|
params.response.result = signature[0];
|
||||||
|
return params;
|
||||||
|
}
|
||||||
|
}
|
37
packages/plugins/rpc-interceptors/src/lib/eth_subscribe.ts
Normal file
37
packages/plugins/rpc-interceptors/src/lib/eth_subscribe.ts
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
import { Embark } from "embark-core";
|
||||||
|
import RpcInterceptor from "./rpcInterceptor";
|
||||||
|
import { ProxyRequestParams, ProxyResponseParams } from "embark-proxy";
|
||||||
|
|
||||||
|
export default class EthSubscribe extends RpcInterceptor {
|
||||||
|
|
||||||
|
constructor(embark: Embark) {
|
||||||
|
super(embark);
|
||||||
|
}
|
||||||
|
|
||||||
|
getFilter() {
|
||||||
|
return 'eth_subscribe';
|
||||||
|
}
|
||||||
|
|
||||||
|
async interceptRequest(params: ProxyRequestParams<string>) {
|
||||||
|
// check for websockets
|
||||||
|
if (params.isWs) {
|
||||||
|
// indicate that we do not want this call to go to the node
|
||||||
|
params.sendToNode = false;
|
||||||
|
}
|
||||||
|
return params;
|
||||||
|
}
|
||||||
|
async interceptResponse(params: ProxyResponseParams<string, any>) {
|
||||||
|
|
||||||
|
const { isWs, transport, request, response } = params;
|
||||||
|
|
||||||
|
// check for websockets
|
||||||
|
if (!isWs) {
|
||||||
|
return params;
|
||||||
|
}
|
||||||
|
|
||||||
|
const nodeResponse = await this.events.request2("proxy:websocket:subscribe", transport, request, response);
|
||||||
|
params.response = nodeResponse;
|
||||||
|
|
||||||
|
return params;
|
||||||
|
}
|
||||||
|
}
|
37
packages/plugins/rpc-interceptors/src/lib/eth_unsubscribe.ts
Normal file
37
packages/plugins/rpc-interceptors/src/lib/eth_unsubscribe.ts
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
import { Embark } from "embark-core";
|
||||||
|
import RpcInterceptor from "./rpcInterceptor";
|
||||||
|
import { ProxyRequestParams, ProxyResponseParams } from "embark-proxy";
|
||||||
|
|
||||||
|
export default class EthUnsubscribe extends RpcInterceptor {
|
||||||
|
|
||||||
|
constructor(embark: Embark) {
|
||||||
|
super(embark);
|
||||||
|
}
|
||||||
|
|
||||||
|
getFilter() {
|
||||||
|
return 'eth_unsubscribe';
|
||||||
|
}
|
||||||
|
|
||||||
|
async interceptRequest(params: ProxyRequestParams<string>) {
|
||||||
|
// check for websockets
|
||||||
|
if (params.isWs) {
|
||||||
|
// indicate that we do not want this call to go to the node
|
||||||
|
params.sendToNode = false;
|
||||||
|
}
|
||||||
|
return params;
|
||||||
|
}
|
||||||
|
|
||||||
|
async interceptResponse(params: ProxyResponseParams<string, any>) {
|
||||||
|
|
||||||
|
const { isWs, request, response } = params;
|
||||||
|
|
||||||
|
// check for eth_subscribe and websockets
|
||||||
|
if (!isWs) {
|
||||||
|
return params;
|
||||||
|
}
|
||||||
|
|
||||||
|
const nodeResponse = await this.events.request2("proxy:websocket:unsubscribe", request, response);
|
||||||
|
params.response = nodeResponse;
|
||||||
|
return params;
|
||||||
|
}
|
||||||
|
}
|
39
packages/plugins/rpc-interceptors/src/lib/index.ts
Normal file
39
packages/plugins/rpc-interceptors/src/lib/index.ts
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
import { Embark } from "embark-core";
|
||||||
|
import EthAccounts from "./eth_accounts";
|
||||||
|
import EthSendTransaction from "./eth_sendTransaction";
|
||||||
|
import EthSignData from "./eth_signData";
|
||||||
|
import EthSignTypedData from "./eth_signTypedData";
|
||||||
|
import EthSubscribe from "./eth_subscribe";
|
||||||
|
import EthUnsubscribe from "./eth_unsubscribe";
|
||||||
|
import PersonalNewAccount from "./personal_newAccount";
|
||||||
|
|
||||||
|
export default class RpcInterceptors {
|
||||||
|
constructor(private readonly embark: Embark) {
|
||||||
|
this.init();
|
||||||
|
}
|
||||||
|
|
||||||
|
private init() {
|
||||||
|
[
|
||||||
|
PersonalNewAccount,
|
||||||
|
EthAccounts,
|
||||||
|
EthSendTransaction,
|
||||||
|
EthSignTypedData,
|
||||||
|
EthSignData,
|
||||||
|
EthSubscribe,
|
||||||
|
EthUnsubscribe,
|
||||||
|
EmbarkSmartContracts
|
||||||
|
].map((RpcMod) => {
|
||||||
|
const interceptor = new RpcMod(this.embark);
|
||||||
|
this.embark.events.request(
|
||||||
|
'rpc:request:interceptor:register',
|
||||||
|
interceptor.getFilter(),
|
||||||
|
(params, cb) => interceptor.interceptRequest(params)
|
||||||
|
);
|
||||||
|
this.embark.events.request(
|
||||||
|
'rpc:response:interceptor:register',
|
||||||
|
interceptor.getFilter(),
|
||||||
|
params => interceptor.interceptResponse(params)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,25 @@
|
|||||||
|
import { Embark } from "embark-core";
|
||||||
|
const { blockchain: blockchainConstants } = require("embark-core/constants");
|
||||||
|
import RpcInterceptor from "./rpcInterceptor";
|
||||||
|
import { ProxyRequestParams, ProxyResponseParams } from "embark-proxy";
|
||||||
|
|
||||||
|
export default class PersonalNewAccount extends RpcInterceptor {
|
||||||
|
|
||||||
|
constructor(embark: Embark) {
|
||||||
|
super(embark);
|
||||||
|
}
|
||||||
|
|
||||||
|
getFilter() {
|
||||||
|
return blockchainConstants.transactionMethods.personal_newAccount;
|
||||||
|
}
|
||||||
|
|
||||||
|
async interceptRequest(params: ProxyRequestParams<string>) {
|
||||||
|
return params;
|
||||||
|
}
|
||||||
|
|
||||||
|
async interceptResponse(params: ProxyResponseParams<string, string>) {
|
||||||
|
// emit event so tx modifiers can refresh accounts
|
||||||
|
await this.events.request2("rpc:accounts:reset");
|
||||||
|
return params;
|
||||||
|
}
|
||||||
|
}
|
76
packages/plugins/rpc-interceptors/src/lib/rpcInterceptor.ts
Normal file
76
packages/plugins/rpc-interceptors/src/lib/rpcInterceptor.ts
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
import { Account, Callback, Embark, EmbarkEvents } from "embark-core";
|
||||||
|
import { Logger } from "embark-logger";
|
||||||
|
import { ProxyRequestParams, ProxyResponseParams } from "embark-proxy";
|
||||||
|
import { AccountParser, dappPath } from "embark-utils";
|
||||||
|
import Web3 from "web3";
|
||||||
|
|
||||||
|
export default abstract class RpcInterceptor {
|
||||||
|
|
||||||
|
public events: EmbarkEvents;
|
||||||
|
|
||||||
|
public logger: Logger;
|
||||||
|
|
||||||
|
protected _nodeAccounts: any[] | null = null;
|
||||||
|
|
||||||
|
protected _accounts: Account[] | null = null;
|
||||||
|
|
||||||
|
protected _web3: Web3 | null = null;
|
||||||
|
|
||||||
|
constructor(readonly embark: Embark) {
|
||||||
|
this.embark = embark;
|
||||||
|
this.events = embark.events;
|
||||||
|
this.logger = embark.logger;
|
||||||
|
|
||||||
|
this.events.setCommandHandler("rpc:accounts:reset", this.resetAccounts.bind(this));
|
||||||
|
this.embark.registerActionForEvent("tests:config:updated", { priority: 40 }, (_params, cb) => {
|
||||||
|
// blockchain configs may have changed (ie endpoint)
|
||||||
|
// web.eth.getAccounts may return a different value now
|
||||||
|
// reset accounts, so that on next request they'll be re-fetched
|
||||||
|
this.resetAccounts(cb);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected get web3() {
|
||||||
|
return (async () => {
|
||||||
|
if (!this._web3) {
|
||||||
|
await this.events.request2("blockchain:started");
|
||||||
|
// get connection directly to the node
|
||||||
|
const provider = await this.events.request2("blockchain:node:provider", "ethereum");
|
||||||
|
this._web3 = new Web3(provider);
|
||||||
|
}
|
||||||
|
return this._web3;
|
||||||
|
})();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected get accounts() {
|
||||||
|
return (async () => {
|
||||||
|
if (!this._accounts) {
|
||||||
|
const web3 = await this.web3;
|
||||||
|
const nodeAccounts = await this.nodeAccounts;
|
||||||
|
this._accounts = AccountParser.parseAccountsConfig(this.embark.config.blockchainConfig.accounts, web3, dappPath(), this.logger, nodeAccounts);
|
||||||
|
}
|
||||||
|
return this._accounts || [];
|
||||||
|
})();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected get nodeAccounts() {
|
||||||
|
return (async () => {
|
||||||
|
if (!this._nodeAccounts) {
|
||||||
|
const web3 = await this.web3;
|
||||||
|
this._nodeAccounts = await web3.eth.getAccounts();
|
||||||
|
}
|
||||||
|
return this._nodeAccounts || [];
|
||||||
|
})();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async resetAccounts(cb: Callback<null>) {
|
||||||
|
this._web3 = null;
|
||||||
|
this._nodeAccounts = null;
|
||||||
|
this._accounts = null;
|
||||||
|
cb();
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract getFilter();
|
||||||
|
public abstract async interceptRequest(params);
|
||||||
|
public abstract async interceptResponse(params);
|
||||||
|
}
|
19
packages/plugins/rpc-interceptors/src/lib/utils/signUtils.ts
Normal file
19
packages/plugins/rpc-interceptors/src/lib/utils/signUtils.ts
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import Web3 from "web3";
|
||||||
|
|
||||||
|
export function isNodeAccount(nodeAccounts, fromAddr) {
|
||||||
|
const account = nodeAccounts.find(acc => (
|
||||||
|
Web3.utils.toChecksumAddress(acc) ===
|
||||||
|
Web3.utils.toChecksumAddress(fromAddr)
|
||||||
|
));
|
||||||
|
return !!account;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function handleSignRequest(nodeAccounts, params) {
|
||||||
|
const [fromAddr] = params.request.params;
|
||||||
|
|
||||||
|
// If it's not a node account, we don't send it to the Node as it won't understand it
|
||||||
|
if (!isNodeAccount(nodeAccounts, fromAddr)) {
|
||||||
|
params.sendToNode = false;
|
||||||
|
}
|
||||||
|
return params;
|
||||||
|
}
|
@ -3,7 +3,7 @@
|
|||||||
"composite": true,
|
"composite": true,
|
||||||
"declarationDir": "./dist",
|
"declarationDir": "./dist",
|
||||||
"rootDir": "./src",
|
"rootDir": "./src",
|
||||||
"tsBuildInfoFile": "./node_modules/.cache/tsc/tsconfig.embark-rpc-manager.tsbuildinfo"
|
"tsBuildInfoFile": "./node_modules/.cache/tsc/tsconfig.embark-rpc-interceptors.tsbuildinfo"
|
||||||
},
|
},
|
||||||
"extends": "../../../tsconfig.base.json",
|
"extends": "../../../tsconfig.base.json",
|
||||||
"include": [
|
"include": [
|
||||||
@ -21,6 +21,9 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "../../core/utils"
|
"path": "../../core/utils"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "../../stack/proxy"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
@ -1,41 +0,0 @@
|
|||||||
import { Callback, Embark, EmbarkEvents } from "embark-core";
|
|
||||||
const { blockchain: blockchainConstants } = require("embark-core/constants");
|
|
||||||
import { __ } from "embark-i18n";
|
|
||||||
import Web3 from "web3";
|
|
||||||
import RpcModifier from "./rpcModifier";
|
|
||||||
|
|
||||||
const METHODS_TO_MODIFY = [
|
|
||||||
blockchainConstants.transactionMethods.eth_accounts,
|
|
||||||
blockchainConstants.transactionMethods.personal_listAccounts,
|
|
||||||
];
|
|
||||||
|
|
||||||
export default class EthAccounts extends RpcModifier {
|
|
||||||
constructor(embark: Embark, rpcModifierEvents: EmbarkEvents, public nodeAccounts: string[], public accounts: any[], protected web3: Web3) {
|
|
||||||
super(embark, rpcModifierEvents, nodeAccounts, accounts, web3);
|
|
||||||
|
|
||||||
this.embark.registerActionForEvent("blockchain:proxy:response", this.ethAccountsResponse.bind(this));
|
|
||||||
}
|
|
||||||
|
|
||||||
private async ethAccountsResponse(params: any, callback: Callback<any>) {
|
|
||||||
|
|
||||||
if (!(METHODS_TO_MODIFY.includes(params.request.method))) {
|
|
||||||
return callback(null, params);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.logger.trace(__(`Modifying blockchain '${params.request.method}' response:`));
|
|
||||||
this.logger.trace(__(`Original request/response data: ${JSON.stringify({ request: params.request, response: params.response })}`));
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (!(this.accounts && this.accounts.length)) {
|
|
||||||
return callback(null, params);
|
|
||||||
}
|
|
||||||
|
|
||||||
params.response.result = this.accounts.map((acc) => acc.address);
|
|
||||||
this.logger.trace(__(`Modified request/response data: ${JSON.stringify({ request: params.request, response: params.response })}`));
|
|
||||||
} catch (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
return callback(null, params);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,137 +0,0 @@
|
|||||||
import { Callback, Embark, EmbarkEvents } from "embark-core";
|
|
||||||
import { __ } from "embark-i18n";
|
|
||||||
import Web3 from "web3";
|
|
||||||
const { blockchain: blockchainConstants } = require("embark-core/constants");
|
|
||||||
import RpcModifier from "./rpcModifier";
|
|
||||||
import quorumjs from "quorum-js";
|
|
||||||
|
|
||||||
const TESSERA_PRIVATE_URL_DEFAULT = "http://localhost:9081";
|
|
||||||
|
|
||||||
declare module "embark-core" {
|
|
||||||
interface ClientConfig {
|
|
||||||
tesseraPrivateUrl?: string;
|
|
||||||
}
|
|
||||||
interface ContractConfig {
|
|
||||||
privateFor?: string[];
|
|
||||||
privateFrom?: string;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Because this is quorum-specific, move this entire file to the embark-quorum plugin where it
|
|
||||||
// should be registered in the RPC modifier (which doesn't yet exist). RPC modifier registration should be created as follows:
|
|
||||||
// TODO: 1. move embark-rpc-manager to proxy so it can be a stack component
|
|
||||||
// TODO: 2. Create command handler that allows registration of an RPC modifier (rpcMethodName: string | Regex, action)
|
|
||||||
// TODO: 3. add only 1 instance of registerActionForEvent('blockchain:proxy:request/response')
|
|
||||||
// TODO: 4. For each request/response, loop through registered RPC modifiers finding matches against RPC method name
|
|
||||||
// TODO: 5. run matched action for request/response
|
|
||||||
// TODO: This should be done after https://github.com/embarklabs/embark/pull/2150 is merged.
|
|
||||||
export default class EthSendRawTransaction extends RpcModifier {
|
|
||||||
private _rawTransactionManager: any;
|
|
||||||
constructor(embark: Embark, rpcModifierEvents: EmbarkEvents, public nodeAccounts: string[], public accounts: any[], protected web3: Web3) {
|
|
||||||
super(embark, rpcModifierEvents, nodeAccounts, accounts, web3);
|
|
||||||
|
|
||||||
embark.registerActionForEvent("blockchain:proxy:request", this.ethSendRawTransactionRequest.bind(this));
|
|
||||||
embark.registerActionForEvent("blockchain:proxy:response", this.ethSendRawTransactionResponse.bind(this));
|
|
||||||
}
|
|
||||||
|
|
||||||
protected get rawTransactionManager() {
|
|
||||||
return (async () => {
|
|
||||||
if (!this._rawTransactionManager) {
|
|
||||||
const web3 = await this.web3;
|
|
||||||
// RawTransactionManager doesn't support websockets, and uses the
|
|
||||||
// currentProvider.host URI to communicate with the node over HTTP. Therefore, we can
|
|
||||||
// populate currentProvider.host with our proxy HTTP endpoint, without affecting
|
|
||||||
// web3's websocket connection.
|
|
||||||
// @ts-ignore
|
|
||||||
web3.eth.currentProvider.host = await this.events.request2("proxy:endpoint:http:get");
|
|
||||||
|
|
||||||
this._rawTransactionManager = quorumjs.RawTransactionManager(web3, {
|
|
||||||
privateUrl: this.embark.config.blockchainConfig.clientConfig?.tesseraPrivateUrl ?? TESSERA_PRIVATE_URL_DEFAULT
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return this._rawTransactionManager;
|
|
||||||
})();
|
|
||||||
}
|
|
||||||
|
|
||||||
private async shouldHandle(params) {
|
|
||||||
if (params.request.method !== blockchainConstants.transactionMethods.eth_sendRawTransaction) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const accounts = await this.accounts;
|
|
||||||
if (!(accounts && accounts.length)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Only handle quorum requests that came via eth_sendTransaction
|
|
||||||
// If the user wants to send a private raw tx directly,
|
|
||||||
// web3.eth.sendRawPrivateTransaction should be used (which calls
|
|
||||||
// eth_sendRawPrivateTransaction)
|
|
||||||
const originalPayload = params.originalRequest?.params[0];
|
|
||||||
const privateFor = originalPayload?.privateFor;
|
|
||||||
const privateFrom = originalPayload?.privateFrom;
|
|
||||||
if (!privateFor && !privateFrom) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const from = accounts.find((acc) => Web3.utils.toChecksumAddress(acc.address) === Web3.utils.toChecksumAddress(originalPayload.from));
|
|
||||||
if (!from?.privateKey) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return { originalPayload, privateFor, privateFrom, from };
|
|
||||||
}
|
|
||||||
|
|
||||||
private stripPrefix(value) {
|
|
||||||
return value?.indexOf('0x') === 0 ? value.substring(2) : value;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async ethSendRawTransactionRequest(params: any, callback: Callback<any>) {
|
|
||||||
const shouldHandle = await this.shouldHandle(params);
|
|
||||||
if (!shouldHandle) {
|
|
||||||
return callback(null, params);
|
|
||||||
}
|
|
||||||
|
|
||||||
// manually send to the node in the response
|
|
||||||
params.sendToNode = false;
|
|
||||||
callback(null, params);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async ethSendRawTransactionResponse(params: any, callback: Callback<any>) {
|
|
||||||
const shouldHandle = await this.shouldHandle(params);
|
|
||||||
if (!shouldHandle) {
|
|
||||||
return callback(null, params);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.logger.trace(__(`Modifying blockchain '${params.request.method}' request:`));
|
|
||||||
this.logger.trace(__(`Original request data: ${JSON.stringify({ request: params.request, response: params.response })}`));
|
|
||||||
|
|
||||||
const { originalPayload, privateFor, privateFrom, from } = shouldHandle;
|
|
||||||
const { gas, gasPrice, gasLimit, to, value, nonce, bytecodeWithInitParam, data } = originalPayload;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const rawTransactionManager = await this.rawTransactionManager;
|
|
||||||
|
|
||||||
const rawTx = {
|
|
||||||
gasPrice: this.stripPrefix(gasPrice) ?? (0).toString(16),
|
|
||||||
gasLimit: this.stripPrefix(gasLimit) ?? (4300000).toString(16),
|
|
||||||
gas: this.stripPrefix(gas),
|
|
||||||
to,
|
|
||||||
value: this.stripPrefix(value) ?? (0).toString(16),
|
|
||||||
data: bytecodeWithInitParam ?? data,
|
|
||||||
from,
|
|
||||||
isPrivate: true,
|
|
||||||
privateFrom,
|
|
||||||
privateFor,
|
|
||||||
nonce
|
|
||||||
};
|
|
||||||
|
|
||||||
const { transactionHash } = await rawTransactionManager.sendRawTransaction(rawTx);
|
|
||||||
params.response.result = transactionHash;
|
|
||||||
this.logger.trace(__(`Modified request/response data: ${JSON.stringify({ request: params.request, response: params.response })}`));
|
|
||||||
return callback(null, params);
|
|
||||||
} catch (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,92 +0,0 @@
|
|||||||
import async from "async";
|
|
||||||
import { Callback, Embark, EmbarkEvents, EmbarkPlugins } from "embark-core";
|
|
||||||
import { __ } from "embark-i18n";
|
|
||||||
import Web3 from "web3";
|
|
||||||
const { blockchain: blockchainConstants } = require("embark-core/constants");
|
|
||||||
import RpcModifier from "./rpcModifier";
|
|
||||||
import cloneDeep from "lodash.clonedeep";
|
|
||||||
|
|
||||||
export default class EthSendTransaction extends RpcModifier {
|
|
||||||
private signTransactionQueue: any;
|
|
||||||
private nonceCache: any = {};
|
|
||||||
private plugins: EmbarkPlugins;
|
|
||||||
constructor(embark: Embark, rpcModifierEvents: EmbarkEvents, public nodeAccounts: string[], public accounts: any[], protected web3: Web3) {
|
|
||||||
super(embark, rpcModifierEvents, nodeAccounts, accounts, web3);
|
|
||||||
|
|
||||||
this.plugins = embark.config.plugins;
|
|
||||||
|
|
||||||
embark.registerActionForEvent("blockchain:proxy:request", this.ethSendTransactionRequest.bind(this));
|
|
||||||
|
|
||||||
// TODO: pull this out in to rpc-manager/utils once https://github.com/embarklabs/embark/pull/2150 is merged.
|
|
||||||
// Allow to run transaction in parallel by resolving the nonce manually.
|
|
||||||
// For each transaction, resolve the nonce by taking the max of current transaction count and the cache we keep locally.
|
|
||||||
// Update the nonce and sign it
|
|
||||||
this.signTransactionQueue = async.queue(({ payload, account }, callback: Callback<any>) => {
|
|
||||||
this.getNonce(payload.from, async (err: any, newNonce: number) => {
|
|
||||||
if (err) {
|
|
||||||
return callback(err, null);
|
|
||||||
}
|
|
||||||
payload.nonce = newNonce;
|
|
||||||
try {
|
|
||||||
const result = await web3.eth.accounts.signTransaction(payload, account.privateKey);
|
|
||||||
callback(null, result.rawTransaction);
|
|
||||||
} catch (err) {
|
|
||||||
callback(err);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: pull this out in to rpc-manager/utils once https://github.com/embarklabs/embark/pull/2150 is merged.
|
|
||||||
private async getNonce(address: string, callback: Callback<any>) {
|
|
||||||
const web3 = await this.web3;
|
|
||||||
web3.eth.getTransactionCount(address, (error: any, transactionCount: number) => {
|
|
||||||
if (error) {
|
|
||||||
return callback(error, null);
|
|
||||||
}
|
|
||||||
if (this.nonceCache[address] === undefined) {
|
|
||||||
this.nonceCache[address] = -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (transactionCount > this.nonceCache[address]) {
|
|
||||||
this.nonceCache[address] = transactionCount;
|
|
||||||
return callback(null, this.nonceCache[address]);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.nonceCache[address]++;
|
|
||||||
callback(null, this.nonceCache[address]);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
private async ethSendTransactionRequest(params: any, callback: Callback<any>) {
|
|
||||||
if (!(params.request.method === blockchainConstants.transactionMethods.eth_sendTransaction)) {
|
|
||||||
return callback(null, params);
|
|
||||||
}
|
|
||||||
if (!(this.accounts && this.accounts.length)) {
|
|
||||||
return callback(null, params);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.logger.trace(__(`Modifying blockchain '${params.request.method}' request:`));
|
|
||||||
this.logger.trace(__(`Original request data: ${JSON.stringify({ request: params.request, response: params.response })}`));
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Check if we have that account in our wallet
|
|
||||||
const account = this.accounts.find((acc) => Web3.utils.toChecksumAddress(acc.address) === Web3.utils.toChecksumAddress(params.request.params[0].from));
|
|
||||||
if (account && account.privateKey) {
|
|
||||||
return this.signTransactionQueue.push({ payload: params.request.params[0], account }, (err: any, newPayload: any) => {
|
|
||||||
if (err) {
|
|
||||||
return callback(err, null);
|
|
||||||
}
|
|
||||||
params.originalRequest = cloneDeep(params.request);
|
|
||||||
params.request.method = blockchainConstants.transactionMethods.eth_sendRawTransaction;
|
|
||||||
params.request.params = [newPayload];
|
|
||||||
// allow for any mods to eth_sendRawTransaction
|
|
||||||
this.plugins.runActionsForEvent('blockchain:proxy:request', params, callback);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
this.logger.trace(__(`Modified request/response data: ${JSON.stringify({ request: params.request, response: params.response })}`));
|
|
||||||
callback(null, params);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,63 +0,0 @@
|
|||||||
import { Callback, Embark, EmbarkEvents } from "embark-core";
|
|
||||||
import { __ } from "embark-i18n";
|
|
||||||
import Web3 from "web3";
|
|
||||||
import RpcModifier from "./rpcModifier";
|
|
||||||
import {handleSignRequest} from './utils/signUtils';
|
|
||||||
|
|
||||||
export default class EthSignData extends RpcModifier {
|
|
||||||
constructor(embark: Embark, rpcModifierEvents: EmbarkEvents, public nodeAccounts: string[], public accounts: any[], protected web3: Web3) {
|
|
||||||
super(embark, rpcModifierEvents, nodeAccounts, accounts, web3);
|
|
||||||
|
|
||||||
this.embark.registerActionForEvent("blockchain:proxy:request", this.ethSignDataRequest.bind(this));
|
|
||||||
this.embark.registerActionForEvent("blockchain:proxy:response", this.ethSignDataResponse.bind(this));
|
|
||||||
}
|
|
||||||
|
|
||||||
private async ethSignDataRequest(params: any, callback: Callback<any>) {
|
|
||||||
if (params.request.method !== "eth_sign") {
|
|
||||||
return callback(null, params);
|
|
||||||
}
|
|
||||||
|
|
||||||
handleSignRequest(this.nodeAccounts, params, callback);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async ethSignDataResponse(params: any, callback: Callback<any>) {
|
|
||||||
if (params.request.method !== "eth_sign") {
|
|
||||||
return callback(null, params);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const [fromAddr, data] = params.request.params;
|
|
||||||
|
|
||||||
const nodeAccount = this.nodeAccounts.find(acc => (
|
|
||||||
Web3.utils.toChecksumAddress(acc) ===
|
|
||||||
Web3.utils.toChecksumAddress(fromAddr)
|
|
||||||
));
|
|
||||||
if (nodeAccount) {
|
|
||||||
return callback(null, params);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.logger.trace(__(`Modifying blockchain '${params.request.method}' response:`));
|
|
||||||
this.logger.trace(__(`Original request/response data: ${JSON.stringify(params)}`));
|
|
||||||
|
|
||||||
const account = this.accounts.find(acc => (
|
|
||||||
Web3.utils.toChecksumAddress(acc.address) ===
|
|
||||||
Web3.utils.toChecksumAddress(fromAddr)
|
|
||||||
));
|
|
||||||
|
|
||||||
if (!(account && account.privateKey)) {
|
|
||||||
return callback(
|
|
||||||
new Error(__("Could not sign transaction because Embark does not have a private key associated with '%s'. " +
|
|
||||||
"Please ensure you have configured your account(s) to use a mnemonic, privateKey, or privateKeyFile.", fromAddr)));
|
|
||||||
}
|
|
||||||
|
|
||||||
const signature = new Web3().eth.accounts.privateKeyToAccount(account.privateKey).sign(data).signature;
|
|
||||||
params.response.result = signature;
|
|
||||||
|
|
||||||
this.logger.trace(__(`Modified request/response data: ${JSON.stringify(params)}`));
|
|
||||||
} catch (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
callback(null, params);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,71 +0,0 @@
|
|||||||
import {sign, transaction} from "@omisego/omg-js-util";
|
|
||||||
import {Callback, Embark, EmbarkEvents} from "embark-core";
|
|
||||||
import {__} from "embark-i18n";
|
|
||||||
import Web3 from "web3";
|
|
||||||
import RpcModifier from "./rpcModifier";
|
|
||||||
import {handleSignRequest, isNodeAccount} from './utils/signUtils';
|
|
||||||
|
|
||||||
export default class EthSignTypedData extends RpcModifier {
|
|
||||||
constructor(embark: Embark, rpcModifierEvents: EmbarkEvents, public nodeAccounts: string[], public accounts: any[], protected web3: Web3) {
|
|
||||||
super(embark, rpcModifierEvents, nodeAccounts, accounts, web3);
|
|
||||||
|
|
||||||
this.embark.registerActionForEvent("blockchain:proxy:request", this.ethSignTypedDataRequest.bind(this));
|
|
||||||
this.embark.registerActionForEvent("blockchain:proxy:response", this.ethSignTypedDataResponse.bind(this));
|
|
||||||
}
|
|
||||||
|
|
||||||
private async ethSignTypedDataRequest(params: any, callback: Callback<any>) {
|
|
||||||
// check for:
|
|
||||||
// - eth_signTypedData
|
|
||||||
// - eth_signTypedData_v3
|
|
||||||
// - eth_signTypedData_v4
|
|
||||||
// - personal_signTypedData (parity)
|
|
||||||
if (!params.request.method.includes("signTypedData")) {
|
|
||||||
return callback(null, params);
|
|
||||||
}
|
|
||||||
|
|
||||||
handleSignRequest(this.nodeAccounts, params, callback);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async ethSignTypedDataResponse(params: any, callback: Callback<any>) {
|
|
||||||
// check for:
|
|
||||||
// - eth_signTypedData
|
|
||||||
// - eth_signTypedData_v3
|
|
||||||
// - eth_signTypedData_v4
|
|
||||||
// - personal_signTypedData (parity)
|
|
||||||
if (!params.request.method.includes("signTypedData")) {
|
|
||||||
return callback(null, params);
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
const [fromAddr, typedData] = params.request.params;
|
|
||||||
|
|
||||||
if (isNodeAccount(this.nodeAccounts, fromAddr)) {
|
|
||||||
// If it's a node account, we send the result because it should already be signed
|
|
||||||
return callback(null, params);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.logger.trace(__(`Modifying blockchain '${params.request.method}' response:`));
|
|
||||||
this.logger.trace(__(`Original request/response data: ${JSON.stringify({
|
|
||||||
request: params.request,
|
|
||||||
response: params.response
|
|
||||||
})}`));
|
|
||||||
|
|
||||||
const account = this.accounts.find((acc) => Web3.utils.toChecksumAddress(acc.address) === Web3.utils.toChecksumAddress(fromAddr));
|
|
||||||
if (!(account && account.privateKey)) {
|
|
||||||
return callback(
|
|
||||||
new Error(__("Could not sign transaction because Embark does not have a private key associated with '%s'. " +
|
|
||||||
"Please ensure you have configured your account(s) to use a mnemonic, privateKey, or privateKeyFile.", fromAddr)));
|
|
||||||
}
|
|
||||||
const toSign = transaction.getToSignHash(typeof typedData === "string" ? JSON.parse(typedData) : typedData);
|
|
||||||
const signature = sign(toSign, [account.privateKey]);
|
|
||||||
|
|
||||||
params.response.result = signature[0];
|
|
||||||
this.logger.trace(__(`Modified request/response data: ${JSON.stringify({
|
|
||||||
request: params.request,
|
|
||||||
response: params.response
|
|
||||||
})}`));
|
|
||||||
} catch (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
callback(null, params);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,44 +0,0 @@
|
|||||||
import { Callback, Embark, EmbarkEvents } from "embark-core";
|
|
||||||
import { __ } from "embark-i18n";
|
|
||||||
import RpcModifier from "./rpcModifier";
|
|
||||||
import Web3 from "web3";
|
|
||||||
|
|
||||||
export default class EthSubscribe extends RpcModifier {
|
|
||||||
constructor(embark: Embark, rpcModifierEvents: EmbarkEvents, public nodeAccounts: string[], public accounts: any[], protected web3: Web3) {
|
|
||||||
super(embark, rpcModifierEvents, nodeAccounts, accounts, web3);
|
|
||||||
|
|
||||||
embark.registerActionForEvent("blockchain:proxy:request", this.ethSubscribeRequest.bind(this));
|
|
||||||
embark.registerActionForEvent("blockchain:proxy:response", this.ethSubscribeResponse.bind(this));
|
|
||||||
}
|
|
||||||
|
|
||||||
private async ethSubscribeRequest(params: any, callback: Callback<any>) {
|
|
||||||
// check for eth_subscribe and websockets
|
|
||||||
if (params.isWs && params.request.method === "eth_subscribe") {
|
|
||||||
// indicate that we do not want this call to go to the node
|
|
||||||
params.sendToNode = false;
|
|
||||||
return callback(null, params);
|
|
||||||
}
|
|
||||||
callback(null, params);
|
|
||||||
}
|
|
||||||
private async ethSubscribeResponse(params: any, callback: Callback<any>) {
|
|
||||||
|
|
||||||
const { isWs, transport, request, response } = params;
|
|
||||||
|
|
||||||
// check for eth_subscribe and websockets
|
|
||||||
if (!(isWs && request.method.includes("eth_subscribe"))) {
|
|
||||||
return callback(null, params);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.logger.trace(__(`Modifying blockchain '${request.method}' response:`));
|
|
||||||
this.logger.trace(__(`Original request/response data: ${JSON.stringify({ request, response })}`));
|
|
||||||
|
|
||||||
try {
|
|
||||||
const nodeResponse = await this.events.request2("proxy:websocket:subscribe", transport, request, response);
|
|
||||||
params.response = nodeResponse;
|
|
||||||
this.logger.trace(__(`Modified request/response data: ${JSON.stringify({ request, response: params.response })}`));
|
|
||||||
} catch (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
callback(null, params);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,45 +0,0 @@
|
|||||||
import { Callback, Embark, EmbarkEvents } from "embark-core";
|
|
||||||
import { __ } from "embark-i18n";
|
|
||||||
import RpcModifier from "./rpcModifier";
|
|
||||||
import Web3 from "web3";
|
|
||||||
|
|
||||||
export default class EthUnsubscribe extends RpcModifier {
|
|
||||||
|
|
||||||
constructor(embark: Embark, rpcModifierEvents: EmbarkEvents, public nodeAccounts: string[], public accounts: any[], protected web3: Web3) {
|
|
||||||
super(embark, rpcModifierEvents, nodeAccounts, accounts, web3);
|
|
||||||
|
|
||||||
embark.registerActionForEvent("blockchain:proxy:request", this.ethUnsubscribeRequest.bind(this));
|
|
||||||
embark.registerActionForEvent("blockchain:proxy:response", this.ethUnsubscribeResponse.bind(this));
|
|
||||||
}
|
|
||||||
|
|
||||||
private async ethUnsubscribeRequest(params: any, callback: Callback<any>) {
|
|
||||||
// check for eth_subscribe and websockets
|
|
||||||
if (params.isWs && params.request.method === "eth_unsubscribe") {
|
|
||||||
// indicate that we do not want this call to go to the node
|
|
||||||
params.sendToNode = false;
|
|
||||||
return callback(null, params);
|
|
||||||
}
|
|
||||||
callback(null, params);
|
|
||||||
}
|
|
||||||
private async ethUnsubscribeResponse(params: any, callback: Callback<any>) {
|
|
||||||
|
|
||||||
const { isWs, request, response } = params;
|
|
||||||
|
|
||||||
// check for eth_subscribe and websockets
|
|
||||||
if (!(isWs && request.method.includes("eth_unsubscribe"))) {
|
|
||||||
return callback(null, params);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.logger.trace(__(`Modifying blockchain '${request.method}' response:`));
|
|
||||||
this.logger.trace(__(`Original request/response data: ${JSON.stringify({ request, response })}`));
|
|
||||||
|
|
||||||
try {
|
|
||||||
const nodeResponse = await this.events.request2("proxy:websocket:unsubscribe", request, response);
|
|
||||||
params.response = nodeResponse;
|
|
||||||
this.logger.trace(__(`Modified request/response data: ${JSON.stringify({ request, response: params.response })}`));
|
|
||||||
} catch (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
callback(null, params);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,103 +0,0 @@
|
|||||||
import { Callback, Embark, Configuration, EmbarkEvents } from "embark-core";
|
|
||||||
import { Events } from "embark-core";
|
|
||||||
import { Logger } from 'embark-logger';
|
|
||||||
import { AccountParser, dappPath } from "embark-utils";
|
|
||||||
import Web3 from "web3";
|
|
||||||
import EthAccounts from "./eth_accounts";
|
|
||||||
import EthSendTransaction from "./eth_sendTransaction";
|
|
||||||
import EthSendRawTransaction from "./eth_sendRawTransaction";
|
|
||||||
import EthSignData from "./eth_signData";
|
|
||||||
import EthSignTypedData from "./eth_signTypedData";
|
|
||||||
import EthSubscribe from "./eth_subscribe";
|
|
||||||
import EthUnsubscribe from "./eth_unsubscribe";
|
|
||||||
import PersonalNewAccount from "./personal_newAccount";
|
|
||||||
import RpcModifier from "./rpcModifier";
|
|
||||||
|
|
||||||
export default class RpcManager {
|
|
||||||
|
|
||||||
private modifiers: RpcModifier[] = [];
|
|
||||||
private _web3: Web3 | null = null;
|
|
||||||
private rpcModifierEvents: EmbarkEvents;
|
|
||||||
private logger: Logger;
|
|
||||||
private events: EmbarkEvents;
|
|
||||||
public _accounts: any[] | null = null;
|
|
||||||
public _nodeAccounts: any[] | null = null;
|
|
||||||
constructor(private readonly embark: Embark) {
|
|
||||||
this.events = embark.events;
|
|
||||||
this.logger = embark.logger;
|
|
||||||
this.rpcModifierEvents = new Events() as EmbarkEvents;
|
|
||||||
this.init();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected get web3() {
|
|
||||||
return (async () => {
|
|
||||||
if (!this._web3) {
|
|
||||||
await this.events.request2("blockchain:started");
|
|
||||||
// get connection directly to the node
|
|
||||||
const provider = await this.events.request2("blockchain:node:provider", "ethereum");
|
|
||||||
this._web3 = new Web3(provider);
|
|
||||||
}
|
|
||||||
return this._web3;
|
|
||||||
})();
|
|
||||||
}
|
|
||||||
|
|
||||||
private get nodeAccounts() {
|
|
||||||
return (async () => {
|
|
||||||
if (!this._nodeAccounts) {
|
|
||||||
const web3 = await this.web3;
|
|
||||||
this._nodeAccounts = await web3.eth.getAccounts();
|
|
||||||
}
|
|
||||||
return this._nodeAccounts || [];
|
|
||||||
})();
|
|
||||||
}
|
|
||||||
|
|
||||||
private get accounts() {
|
|
||||||
return (async () => {
|
|
||||||
if (!this._accounts) {
|
|
||||||
const web3 = await this.web3;
|
|
||||||
const nodeAccounts = await this.nodeAccounts;
|
|
||||||
this._accounts = AccountParser.parseAccountsConfig(this.embark.config.blockchainConfig.accounts, web3, dappPath(), this.logger, nodeAccounts);
|
|
||||||
}
|
|
||||||
return this._accounts || [];
|
|
||||||
})();
|
|
||||||
}
|
|
||||||
|
|
||||||
private async init() {
|
|
||||||
|
|
||||||
this.embark.registerActionForEvent("tests:config:updated", { priority: 40 }, (_params, cb) => {
|
|
||||||
// blockchain configs may have changed (ie endpoint)
|
|
||||||
this._web3 = null;
|
|
||||||
|
|
||||||
// web.eth.getAccounts may return a different value now
|
|
||||||
// update accounts across all modifiers
|
|
||||||
this.updateAccounts(cb);
|
|
||||||
});
|
|
||||||
|
|
||||||
this.rpcModifierEvents.setCommandHandler("nodeAccounts:updated", this.updateAccounts.bind(this));
|
|
||||||
|
|
||||||
const web3 = await this.web3;
|
|
||||||
const nodeAccounts = await this.nodeAccounts;
|
|
||||||
const accounts = await this.accounts;
|
|
||||||
|
|
||||||
this.modifiers = [
|
|
||||||
PersonalNewAccount,
|
|
||||||
EthAccounts,
|
|
||||||
EthSendTransaction,
|
|
||||||
EthSendRawTransaction,
|
|
||||||
EthSignTypedData,
|
|
||||||
EthSignData,
|
|
||||||
EthSubscribe,
|
|
||||||
EthUnsubscribe
|
|
||||||
].map((RpcMod) => new RpcMod(this.embark, this.rpcModifierEvents, nodeAccounts, accounts, web3));
|
|
||||||
}
|
|
||||||
|
|
||||||
private async updateAccounts(cb: Callback<null>) {
|
|
||||||
this._nodeAccounts = null;
|
|
||||||
this._accounts = null;
|
|
||||||
for (const modifier of this.modifiers) {
|
|
||||||
modifier.nodeAccounts = await this.nodeAccounts;
|
|
||||||
modifier.accounts = await this.accounts;
|
|
||||||
}
|
|
||||||
cb();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,23 +0,0 @@
|
|||||||
import { Callback, Embark, EmbarkEvents } from "embark-core";
|
|
||||||
import Web3 from "web3";
|
|
||||||
const { blockchain: blockchainConstants } = require("embark-core/constants");
|
|
||||||
import { __ } from "embark-i18n";
|
|
||||||
import RpcModifier from "./rpcModifier";
|
|
||||||
export default class PersonalNewAccount extends RpcModifier {
|
|
||||||
constructor(embark: Embark, rpcModifierEvents: EmbarkEvents, public nodeAccounts: string[], public accounts: any[], protected web3: Web3) {
|
|
||||||
super(embark, rpcModifierEvents, nodeAccounts, accounts, web3);
|
|
||||||
|
|
||||||
embark.registerActionForEvent("blockchain:proxy:response", this.personalNewAccountResponse.bind(this));
|
|
||||||
}
|
|
||||||
|
|
||||||
private async personalNewAccountResponse(params: any, callback: Callback<any>) {
|
|
||||||
if (params.request.method !== blockchainConstants.transactionMethods.personal_newAccount) {
|
|
||||||
return callback(null, params);
|
|
||||||
}
|
|
||||||
|
|
||||||
// emit event so tx modifiers can refresh accounts
|
|
||||||
await this.rpcModifierEvents.request2("nodeAccounts:updated");
|
|
||||||
|
|
||||||
callback(null, params);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,13 +0,0 @@
|
|||||||
import { Embark, EmbarkConfig, EmbarkEvents } /* supplied by @types/embark in packages/core/typings */ from "embark-core";
|
|
||||||
import { Logger } from "embark-logger";
|
|
||||||
import Web3 from "web3";
|
|
||||||
|
|
||||||
export default class RpcModifier {
|
|
||||||
public events: EmbarkEvents;
|
|
||||||
public logger: Logger;
|
|
||||||
protected _nodeAccounts: any[] | null = null;
|
|
||||||
constructor(readonly embark: Embark, readonly rpcModifierEvents: EmbarkEvents, public nodeAccounts: string[], public accounts: any[], protected web: Web3) {
|
|
||||||
this.events = embark.events;
|
|
||||||
this.logger = embark.logger;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,23 +0,0 @@
|
|||||||
import Web3 from "web3";
|
|
||||||
|
|
||||||
export function isNodeAccount(nodeAccounts, fromAddr) {
|
|
||||||
const account = nodeAccounts.find(acc => (
|
|
||||||
Web3.utils.toChecksumAddress(acc) ===
|
|
||||||
Web3.utils.toChecksumAddress(fromAddr)
|
|
||||||
));
|
|
||||||
return !!account;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function handleSignRequest(nodeAccounts, params, callback) {
|
|
||||||
try {
|
|
||||||
const [fromAddr] = params.request.params;
|
|
||||||
|
|
||||||
// If it's not a node account, we don't send it to the Node as it won't understand it
|
|
||||||
if (!isNodeAccount(nodeAccounts, fromAddr)) {
|
|
||||||
params.sendToNode = false;
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
callback(null, params);
|
|
||||||
}
|
|
@ -67,8 +67,8 @@
|
|||||||
"embark-utils": "^5.3.0-nightly.16",
|
"embark-utils": "^5.3.0-nightly.16",
|
||||||
"express": "4.17.1",
|
"express": "4.17.1",
|
||||||
"express-ws": "4.0.0",
|
"express-ws": "4.0.0",
|
||||||
"web3-core-requestmanager": "1.2.6",
|
"web3": "1.2.6",
|
||||||
"web3-providers-ws": "1.2.6"
|
"web3-core-requestmanager": "1.2.6"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "7.8.3",
|
"@babel/core": "7.8.3",
|
||||||
|
@ -4,9 +4,30 @@ import { Logger } from "embark-logger";
|
|||||||
import { buildUrl, findNextPort } from "embark-utils";
|
import { buildUrl, findNextPort } from "embark-utils";
|
||||||
|
|
||||||
import { Proxy } from "./proxy";
|
import { Proxy } from "./proxy";
|
||||||
|
import { RpcRequest, RpcResponse } from './json-rpc';
|
||||||
|
import { WebsocketMethod } from "express-ws";
|
||||||
|
import { Response } from "express";
|
||||||
|
import RpcManager from "./rpc-manager";
|
||||||
|
|
||||||
const constants = require("embark-core/constants");
|
const constants = require("embark-core/constants");
|
||||||
|
|
||||||
|
export interface ProxyParams<T> {
|
||||||
|
sendToNode?: boolean;
|
||||||
|
originalRequest?: RpcRequest<T>;
|
||||||
|
request: RpcRequest<T>;
|
||||||
|
isWs: boolean;
|
||||||
|
transport: WebsocketMethod<T> | Response;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ProxyRequestParams<T> extends ProxyParams<T> { }
|
||||||
|
|
||||||
|
export interface ProxyResponseParams<T, R> extends ProxyRequestParams<T> {
|
||||||
|
response: RpcResponse<R>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export * from "./rpc-manager";
|
||||||
|
export * from "./json-rpc";
|
||||||
|
|
||||||
export default class ProxyManager {
|
export default class ProxyManager {
|
||||||
private readonly logger: Logger;
|
private readonly logger: Logger;
|
||||||
private readonly events: EmbarkEvents;
|
private readonly events: EmbarkEvents;
|
||||||
@ -34,6 +55,8 @@ export default class ProxyManager {
|
|||||||
this.logger.warn(__("The proxy has been disabled -- some Embark features will not work."));
|
this.logger.warn(__("The proxy has been disabled -- some Embark features will not work."));
|
||||||
this.logger.warn(__("Configured wallet accounts will be ignored and cannot be used in the DApp, and transactions will not be logged."));
|
this.logger.warn(__("Configured wallet accounts will be ignored and cannot be used in the DApp, and transactions will not be logged."));
|
||||||
}
|
}
|
||||||
|
const rpcManager = new RpcManager(embark);
|
||||||
|
rpcManager.init();
|
||||||
|
|
||||||
this.setupEvents();
|
this.setupEvents();
|
||||||
}
|
}
|
||||||
|
114
packages/stack/proxy/src/json-rpc.ts
Normal file
114
packages/stack/proxy/src/json-rpc.ts
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
/**
|
||||||
|
* A rpc call is represented by sending a Request object to a Server.
|
||||||
|
*/
|
||||||
|
export interface RpcRequest<T> {
|
||||||
|
/**
|
||||||
|
* A String specifying the version of the JSON-RPC protocol. **MUST** be exactly "2.0".
|
||||||
|
*/
|
||||||
|
jsonrpc: '2.0';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A String containing the name of the method to be invoked.
|
||||||
|
*/
|
||||||
|
method: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A Structured value that holds the parameter values
|
||||||
|
* to be used during the invocation of the method.
|
||||||
|
*/
|
||||||
|
params: T;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An identifier established by the Client that **MUST** contain a `String`, `Number`,
|
||||||
|
* or `NULL` value if included.
|
||||||
|
* If it is not included it is assumed to be a notification.
|
||||||
|
* The value **SHOULD** normally not be Null and Numbers **SHOULD NOT** contain fractional parts
|
||||||
|
*/
|
||||||
|
id?: string | number | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When a rpc call is made, the Server **MUST** reply with a Response
|
||||||
|
* except for in the case of Notifications.
|
||||||
|
* The Response is expressed as a single JSON Object
|
||||||
|
*/
|
||||||
|
export interface RpcResponse<T, E = any> {
|
||||||
|
/**
|
||||||
|
* A String specifying the version of the JSON-RPC protocol.
|
||||||
|
* **MUST** be exactly "2.0".
|
||||||
|
*/
|
||||||
|
jsonrpc: '2.0';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This member is **REQUIRED** on success.
|
||||||
|
* This member **MUST NOT** exist if there was an error invoking the method.
|
||||||
|
* The value of this member is determined by the method invoked on the Server.
|
||||||
|
*/
|
||||||
|
result?: T;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This member is REQUIRED on error.
|
||||||
|
* This member MUST NOT exist if there was no error triggered during invocation.
|
||||||
|
* The value for this member MUST be an Object of Type `RpcResponseError`.
|
||||||
|
*/
|
||||||
|
error?: RpcResponseError<E>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An identifier established by the Client that **MUST** contain a `String`, `Number`,
|
||||||
|
* or `NULL` value if included.
|
||||||
|
* It **MUST** be the same as the value of the id member in the Request Object.
|
||||||
|
* If there was an error
|
||||||
|
* in detecting the `id` in the Request object (e.g. `Parse error`/`Invalid Request`)
|
||||||
|
* it **MUST** be `Null`.
|
||||||
|
*/
|
||||||
|
id: string | number | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When a rpc call encounters an error,
|
||||||
|
* the Response Object MUST contain the error member with a value that is that object
|
||||||
|
*/
|
||||||
|
export interface RpcResponseError<T = any> {
|
||||||
|
/**
|
||||||
|
* A Number that indicates the error type that occurred.
|
||||||
|
*/
|
||||||
|
code: number | RpcErrorCode;
|
||||||
|
/**
|
||||||
|
* A String providing a short description of the error.
|
||||||
|
*/
|
||||||
|
message: string;
|
||||||
|
/**
|
||||||
|
* A Primitive or Structured value that contains additional information about the error.
|
||||||
|
* The value of this member is defined by the Server
|
||||||
|
* (e.g. detailed error information, nested errors etc.).
|
||||||
|
*/
|
||||||
|
data?: T;
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum RpcErrorCode {
|
||||||
|
/**
|
||||||
|
* Invalid JSON was received by the server.
|
||||||
|
* or An error occurred on the server while parsing the JSON text.
|
||||||
|
*/
|
||||||
|
PARSE_ERROR = -32700,
|
||||||
|
/**
|
||||||
|
* The JSON sent is not a valid Request object.
|
||||||
|
*/
|
||||||
|
INVALID_REQUEST = -32600,
|
||||||
|
/**
|
||||||
|
* The method does not exist / is not available.
|
||||||
|
*/
|
||||||
|
METHOD_NOT_FOUND = -32601,
|
||||||
|
/**
|
||||||
|
* Invalid method parameter(s).
|
||||||
|
*/
|
||||||
|
INVALID_PARAMS = -32602,
|
||||||
|
/**
|
||||||
|
* Internal JSON-RPC error.
|
||||||
|
*/
|
||||||
|
INTERNAL_ERROR = -32603,
|
||||||
|
/**
|
||||||
|
* Reserved for implementation-defined server-errors.
|
||||||
|
*/
|
||||||
|
SERVER_ERROR = -32000,
|
||||||
|
}
|
@ -3,7 +3,7 @@ import express from 'express';
|
|||||||
import expressWs from 'express-ws';
|
import expressWs from 'express-ws';
|
||||||
import cors from 'cors';
|
import cors from 'cors';
|
||||||
import { isDebug } from 'embark-utils';
|
import { isDebug } from 'embark-utils';
|
||||||
const Web3RequestManager = require('web3-core-requestmanager');
|
import Web3RequestManager from 'web3-core-requestmanager';
|
||||||
|
|
||||||
const ACTION_TIMEOUT = isDebug() ? 30000 : 10000;
|
const ACTION_TIMEOUT = isDebug() ? 30000 : 10000;
|
||||||
|
|
||||||
@ -40,7 +40,7 @@ export class Proxy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_createWeb3RequestManager(provider) {
|
_createWeb3RequestManager(provider) {
|
||||||
const manager = new Web3RequestManager.Manager(provider);
|
const manager = new Web3RequestManager.Manager(provider);
|
||||||
// Up max listener because the default 10 limit is too low for all the events the proxy handles
|
// Up max listener because the default 10 limit is too low for all the events the proxy handles
|
||||||
// Warning mostly appeared in tests. The warning is also only with the Ganache provider
|
// Warning mostly appeared in tests. The warning is also only with the Ganache provider
|
||||||
// eslint-disable-next-line no-unused-expressions
|
// eslint-disable-next-line no-unused-expressions
|
||||||
|
136
packages/stack/proxy/src/rpc-manager.ts
Normal file
136
packages/stack/proxy/src/rpc-manager.ts
Normal file
@ -0,0 +1,136 @@
|
|||||||
|
import { __ } from "embark-i18n";
|
||||||
|
import { Events } from "embark-core";
|
||||||
|
import { Account, Callback, Embark, EmbarkEvents } from "embark-core";
|
||||||
|
import { Logger } from 'embark-logger';
|
||||||
|
import { AccountParser, dappPath } from "embark-utils";
|
||||||
|
import { ProxyRequestParams, ProxyResponseParams } from '.';
|
||||||
|
|
||||||
|
export type RpcRequestInterceptor<T> = (params: ProxyRequestParams<T>) => ProxyRequestParams<T>;
|
||||||
|
export type RpcResponseInterceptor<T, R> = (params: ProxyRequestParams<T> | ProxyResponseParams<T, R>) => ProxyResponseParams<T, R>;
|
||||||
|
|
||||||
|
interface RegistrationOptions {
|
||||||
|
priority: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface RpcRegistration {
|
||||||
|
filter: string | string[] | RegExp;
|
||||||
|
options: RegistrationOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface RpcRequestRegistration<T> extends RpcRegistration {
|
||||||
|
interceptor: RpcRequestInterceptor<T>;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface RpcResponseRegistration<T, R> extends RpcRegistration {
|
||||||
|
interceptor: RpcResponseInterceptor<T, R>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class RpcManager {
|
||||||
|
|
||||||
|
private logger: Logger;
|
||||||
|
|
||||||
|
private events: EmbarkEvents;
|
||||||
|
|
||||||
|
private requestInterceptors: Array<RpcRequestRegistration<any>> = [];
|
||||||
|
|
||||||
|
private responseInterceptors: Array<RpcResponseRegistration<any, any>> = [];
|
||||||
|
|
||||||
|
constructor(private readonly embark: Embark) {
|
||||||
|
this.events = embark.events;
|
||||||
|
this.logger = embark.logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public init() {
|
||||||
|
this.registerActions();
|
||||||
|
this.setCommandHandlers();
|
||||||
|
}
|
||||||
|
|
||||||
|
private registerActions() {
|
||||||
|
this.embark.registerActionForEvent("blockchain:proxy:request", this.onProxyRequest.bind(this));
|
||||||
|
this.embark.registerActionForEvent("blockchain:proxy:response", this.onProxyResponse.bind(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
private setCommandHandlers() {
|
||||||
|
this.events.setCommandHandler("rpc:request:interceptor:register", this.registerRequestInterceptor.bind(this));
|
||||||
|
this.events.setCommandHandler("rpc:response:interceptor:register", this.registerResponseInterceptor.bind(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
private async onProxyRequest<TRequest>(params: ProxyRequestParams<TRequest>, callback: Callback<ProxyRequestParams<TRequest>>) {
|
||||||
|
try {
|
||||||
|
params = await this.executeInterceptors(this.requestInterceptors, params);
|
||||||
|
} catch (err) {
|
||||||
|
err.message = __("Error executing RPC request modifications for '%s': %s", params?.request?.method, err.message);
|
||||||
|
return callback(err);
|
||||||
|
}
|
||||||
|
callback(null, params);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async onProxyResponse<TRequest, TResponse>(params: ProxyResponseParams<TRequest, TResponse>, callback: Callback<ProxyResponseParams<TRequest, TResponse>>) {
|
||||||
|
try {
|
||||||
|
params = await this.executeInterceptors(this.responseInterceptors, params);
|
||||||
|
} catch (err) {
|
||||||
|
err.message = __("Error executing RPC response modifications for '%s': %s", params?.request?.method, err.message);
|
||||||
|
return callback(err);
|
||||||
|
}
|
||||||
|
callback(null, params);
|
||||||
|
}
|
||||||
|
|
||||||
|
private registerRequestInterceptor<TRequest>(filter: string | RegExp, interceptor: RpcRequestInterceptor<TRequest>, options: RegistrationOptions = { priority: 50 }, callback?: Callback<null>) {
|
||||||
|
this.requestInterceptors.push({ filter, options, interceptor });
|
||||||
|
if (callback) {
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private registerResponseInterceptor<TRequest, TResponse>(filter: string | RegExp, interceptor: RpcResponseInterceptor<TRequest, TResponse>, options: RegistrationOptions = { priority: 50 }, callback?: Callback<null>) {
|
||||||
|
this.responseInterceptors.push({ filter, options, interceptor });
|
||||||
|
if (callback) {
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async executeInterceptors<T>(
|
||||||
|
registrations: Array<RpcRequestRegistration<T>>,
|
||||||
|
params: ProxyRequestParams<T>
|
||||||
|
): Promise<ProxyRequestParams<T>>;
|
||||||
|
private async executeInterceptors<T, R>(
|
||||||
|
registrations: Array<RpcResponseRegistration<T, R>>,
|
||||||
|
params: ProxyResponseParams<T, R>
|
||||||
|
): Promise<ProxyResponseParams<T, R>>;
|
||||||
|
private async executeInterceptors<T, R>(
|
||||||
|
registrations: Array<RpcRequestRegistration<T> | RpcResponseRegistration<T, R>>,
|
||||||
|
params: ProxyRequestParams<T> | ProxyResponseParams<T, R>
|
||||||
|
): Promise<ProxyRequestParams<T> | ProxyResponseParams<T, R>> {
|
||||||
|
const { method } = params.request;
|
||||||
|
const registrationsToRun = registrations
|
||||||
|
.filter(registration => this.shouldIntercept(registration.filter, method))
|
||||||
|
.sort((a, b) => this.sortByPriority(a.options.priority, b.options.priority));
|
||||||
|
|
||||||
|
for (const registration of registrationsToRun) {
|
||||||
|
params = await registration.interceptor(params);
|
||||||
|
}
|
||||||
|
return params;
|
||||||
|
}
|
||||||
|
|
||||||
|
private shouldIntercept(filter: string | string[] | RegExp, method: string) {
|
||||||
|
let applyModification = false;
|
||||||
|
if (filter instanceof RegExp) {
|
||||||
|
applyModification = filter.test(method);
|
||||||
|
} else if (typeof filter === "string") {
|
||||||
|
applyModification = (filter === method);
|
||||||
|
} else if (Array.isArray(filter)) {
|
||||||
|
applyModification = filter.includes(method);
|
||||||
|
}
|
||||||
|
return applyModification;
|
||||||
|
}
|
||||||
|
|
||||||
|
private sortByPriority<T>(a: number, b: number) {
|
||||||
|
if (a < b) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (a > b) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
@ -27,6 +27,7 @@ describe('stack/proxy', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
embark = testBed.embark;
|
embark = testBed.embark;
|
||||||
|
|
||||||
proxyManager = new ProxyManager(embark, {
|
proxyManager = new ProxyManager(embark, {
|
||||||
plugins: testBed.embark,
|
plugins: testBed.embark,
|
||||||
requestManager: mockRequestManager
|
requestManager: mockRequestManager
|
||||||
|
@ -143,7 +143,7 @@ class PluginsAssert {
|
|||||||
const index = (method + endpoint).toLowerCase();
|
const index = (method + endpoint).toLowerCase();
|
||||||
assert(this.plugins.plugin.apiCalls[index], `API call for '${method} ${endpoint}' wanted, but not registered`);
|
assert(this.plugins.plugin.apiCalls[index], `API call for '${method} ${endpoint}' wanted, but not registered`);
|
||||||
}
|
}
|
||||||
|
|
||||||
consoleCommandRegistered(command) {
|
consoleCommandRegistered(command) {
|
||||||
const registered = this.plugins.plugin.console.some(cmd => {
|
const registered = this.plugins.plugin.console.some(cmd => {
|
||||||
if (!cmd.matches) {
|
if (!cmd.matches) {
|
||||||
|
@ -107,7 +107,7 @@
|
|||||||
"path": "packages/plugins/quorum"
|
"path": "packages/plugins/quorum"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "packages/plugins/rpc-manager"
|
"path": "packages/plugins/rpc-interceptors"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "packages/plugins/scaffolding"
|
"path": "packages/plugins/scaffolding"
|
||||||
|
17
yarn.lock
17
yarn.lock
@ -8506,6 +8506,23 @@ elliptic@^6.0.0, elliptic@^6.4.0, elliptic@^6.4.1:
|
|||||||
minimalistic-assert "^1.0.0"
|
minimalistic-assert "^1.0.0"
|
||||||
minimalistic-crypto-utils "^1.0.0"
|
minimalistic-crypto-utils "^1.0.0"
|
||||||
|
|
||||||
|
embark-rpc-manager@^5.3.0-nightly.16:
|
||||||
|
version "5.3.0-nightly.16"
|
||||||
|
resolved "https://registry.yarnpkg.com/embark-rpc-manager/-/embark-rpc-manager-5.3.0-nightly.16.tgz#c80b7e7c280d3b3171fb45199ed4d497a99e4db9"
|
||||||
|
integrity sha512-oZZFXkPtScLqIr1QjzwI06ty/uUN4QaUiOXyGOfJVAOYy28IwSsD50G3hty3hA/lpcMWeNcIWqzJvpGCy3uGgQ==
|
||||||
|
dependencies:
|
||||||
|
"@babel/runtime-corejs3" "7.8.4"
|
||||||
|
"@omisego/omg-js-util" "2.0.0-v0.2"
|
||||||
|
"@types/async" "2.0.50"
|
||||||
|
async "3.2.0"
|
||||||
|
embark-core "^5.3.0-nightly.16"
|
||||||
|
embark-i18n "^5.3.0-nightly.5"
|
||||||
|
embark-logger "^5.3.0-nightly.16"
|
||||||
|
embark-utils "^5.3.0-nightly.16"
|
||||||
|
lodash.clonedeep "4.5.0"
|
||||||
|
quorum-js "0.3.4"
|
||||||
|
web3 "1.2.6"
|
||||||
|
|
||||||
embark-test-contract-0@0.0.2:
|
embark-test-contract-0@0.0.2:
|
||||||
version "0.0.2"
|
version "0.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/embark-test-contract-0/-/embark-test-contract-0-0.0.2.tgz#53913fb40e3df4b816a7bef9f00a5f78fa3d56b4"
|
resolved "https://registry.yarnpkg.com/embark-test-contract-0/-/embark-test-contract-0-0.0.2.tgz#53913fb40e3df4b816a7bef9f00a5f78fa3d56b4"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user