From 4acec910b58efcb6c216f0d25c3a4483cab7d864 Mon Sep 17 00:00:00 2001 From: Alex Kwiatkowski Date: Tue, 29 Oct 2019 20:37:30 -0700 Subject: [PATCH 001/199] Extract json-api-client into separate package --- .circleci/config.yml | 21 ++ operator_ui/src/actions.ts | 17 +- operator_ui/src/api/index.ts | 3 +- operator_ui/src/api/sessions.ts | 2 +- operator_ui/src/api/v2/bridgeTypes.ts | 2 +- operator_ui/src/api/v2/bulkDeleteRuns.ts | 2 +- operator_ui/src/api/v2/config.ts | 3 +- operator_ui/src/api/v2/runs.ts | 2 +- operator_ui/src/api/v2/specs.ts | 2 +- operator_ui/src/api/v2/transactions.ts | 2 +- operator_ui/src/api/v2/user/balances.ts | 2 +- .../redux/reducers/notifications.js | 2 +- operator_ui/tsconfig.json | 5 +- package.json | 3 +- tools/json-api-client/.eslintignore | 3 + tools/json-api-client/.eslintrc.js | 4 + tools/json-api-client/.gitignore | 1 + tools/json-api-client/.prettierignore | 2 + tools/json-api-client/@types/global.d.ts | 5 + .../@types/json-api-normalizer.d.ts | 209 ++++++++++++++++++ .../__tests__}/fetchWithTimeout.test.ts | 4 +- tools/json-api-client/jest.config.js | 6 + tools/json-api-client/jest.setup.js | 2 + tools/json-api-client/package.json | 30 +++ .../json-api-client/src}/errors.ts | 0 .../json-api-client/src}/fetchWithTimeout.ts | 0 tools/json-api-client/src/formatRequestURI.ts | 20 ++ tools/json-api-client/src/index.ts | 3 + .../json-api-client/src}/transport/http.ts | 2 +- .../json-api-client/src}/transport/json.ts | 2 +- tools/json-api-client/tsconfig.json | 21 ++ yarn.lock | 9 +- 32 files changed, 366 insertions(+), 25 deletions(-) create mode 100644 tools/json-api-client/.eslintignore create mode 100644 tools/json-api-client/.eslintrc.js create mode 100644 tools/json-api-client/.gitignore create mode 100644 tools/json-api-client/.prettierignore create mode 100644 tools/json-api-client/@types/global.d.ts create mode 100644 tools/json-api-client/@types/json-api-normalizer.d.ts rename {operator_ui/__tests__/utils => tools/json-api-client/__tests__}/fetchWithTimeout.test.ts (91%) create mode 100644 tools/json-api-client/jest.config.js create mode 100644 tools/json-api-client/jest.setup.js create mode 100644 tools/json-api-client/package.json rename {operator_ui/src/api => tools/json-api-client/src}/errors.ts (100%) rename {operator_ui/src/utils => tools/json-api-client/src}/fetchWithTimeout.ts (100%) create mode 100644 tools/json-api-client/src/formatRequestURI.ts create mode 100644 tools/json-api-client/src/index.ts rename {operator_ui/src/api => tools/json-api-client/src}/transport/http.ts (95%) rename {operator_ui/src/api => tools/json-api-client/src}/transport/json.ts (99%) create mode 100644 tools/json-api-client/tsconfig.json diff --git a/.circleci/config.yml b/.circleci/config.yml index 345a8c51745..9374ce7ac09 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -194,6 +194,22 @@ jobs: - /usr/local/share/.cache/yarn - run: pip3 install -r requirements.txt - run: ./tools/ci/truffle_test + json-api-client: + docker: + - image: smartcontract/builder:1.0.25 + steps: + - checkout + - run: echo $CACHE_VERSION > cache.version + - restore_cache: + name: Restore Yarn Package Cache + key: v{{ checksum "cache.version" }}-yarn-vendor-{{ checksum "yarn.lock" }} + - run: yarn install + - save_cache: + name: Save Yarn Package Cache + key: v{{ checksum "cache.version" }}-yarn-vendor-{{ checksum "yarn.lock" }} + paths: + - /usr/local/share/.cache/yarn + - run: yarn workspace @chainlink/json-api-client operator-ui: docker: - image: smartcontract/builder:1.0.25 @@ -329,6 +345,10 @@ workflows: filters: tags: only: /^v.*/ + - json-api-client: + filters: + tags: + only: /^v.*/ - operator-ui: filters: tags: @@ -362,6 +382,7 @@ workflows: - truffle - geth-postgres - parity-postgres + - json-api-client - operator-ui - rust filters: diff --git a/operator_ui/src/actions.ts b/operator_ui/src/actions.ts index 41a2b7d2c1b..2458e996170 100644 --- a/operator_ui/src/actions.ts +++ b/operator_ui/src/actions.ts @@ -1,9 +1,10 @@ +import * as jsonapi from '@chainlink/json-api-client' +import * as api from './api' import * as models from 'core/store/models' import * as presenters from 'core/store/presenters' import normalize from 'json-api-normalizer' import { Action, Dispatch } from 'redux' import { ThunkAction } from 'redux-thunk' -import * as api from './api' import { AppState } from './connectors/redux/reducers' export type GetNormalizedData = ReturnType< @@ -13,10 +14,10 @@ export type GetNormalizedData = ReturnType< : never type Errors = - | api.errors.AuthenticationError - | api.errors.BadRequestError - | api.errors.ServerError - | api.errors.UnknownResponseError + | jsonapi.AuthenticationError + | jsonapi.BadRequestError + | jsonapi.ServerError + | jsonapi.UnknownResponseError const createAction = (type: string) => ({ type: type }) @@ -38,7 +39,7 @@ const redirectToSignOut = () => ({ const curryErrorHandler = (dispatch: Dispatch, type: string) => ( error: Error, ) => { - if (error instanceof api.errors.AuthenticationError) { + if (error instanceof jsonapi.AuthenticationError) { dispatch(redirectToSignOut()) } else { dispatch(createErrorAction(error, type)) @@ -120,7 +121,7 @@ function sendSignIn(data: Parameter) { .createSession(data) .then(doc => dispatch(signInSuccessAction(doc))) .catch((error: Errors) => { - if (error instanceof api.errors.AuthenticationError) { + if (error instanceof jsonapi.AuthenticationError) { dispatch(signInFailAction()) } else { dispatch(createErrorAction(error, RECEIVE_SIGNIN_ERROR)) @@ -283,7 +284,7 @@ export const updateBridge = ( // // The calls above will be converted gradually. const handleError = (dispatch: Dispatch) => (error: Error) => { - if (error instanceof api.errors.AuthenticationError) { + if (error instanceof jsonapi.AuthenticationError) { dispatch(redirectToSignOut()) } else { dispatch(notifyError(({ msg }: any) => msg, error)) diff --git a/operator_ui/src/api/index.ts b/operator_ui/src/api/index.ts index e4b1d639c33..6dd45e55dab 100644 --- a/operator_ui/src/api/index.ts +++ b/operator_ui/src/api/index.ts @@ -1,5 +1,4 @@ -import * as errors from './errors' import * as v2 from './v2' export * from './sessions' -export { v2, errors } +export { v2 } diff --git a/operator_ui/src/api/sessions.ts b/operator_ui/src/api/sessions.ts index 6e23c41eb10..15a8dfe35b5 100644 --- a/operator_ui/src/api/sessions.ts +++ b/operator_ui/src/api/sessions.ts @@ -1,4 +1,4 @@ -import * as jsonapi from 'api/transport/json' +import * as jsonapi from '@chainlink/json-api-client' import * as models from 'core/store/models' import * as sessionsController from 'core/web/sessions_controller' diff --git a/operator_ui/src/api/v2/bridgeTypes.ts b/operator_ui/src/api/v2/bridgeTypes.ts index fba22c9d282..0ec63529fc0 100644 --- a/operator_ui/src/api/v2/bridgeTypes.ts +++ b/operator_ui/src/api/v2/bridgeTypes.ts @@ -1,4 +1,4 @@ -import * as jsonapi from 'api/transport/json' +import * as jsonapi from '@chainlink/json-api-client' import * as models from 'core/store/models' // Create adds the BridgeType to the given context. diff --git a/operator_ui/src/api/v2/bulkDeleteRuns.ts b/operator_ui/src/api/v2/bulkDeleteRuns.ts index 4782f38876b..7bc03f0ca11 100644 --- a/operator_ui/src/api/v2/bulkDeleteRuns.ts +++ b/operator_ui/src/api/v2/bulkDeleteRuns.ts @@ -1,4 +1,4 @@ -import * as jsonapi from 'api/transport/json' +import * as jsonapi from '@chainlink/json-api-client' import * as models from 'core/store/models' /** diff --git a/operator_ui/src/api/v2/config.ts b/operator_ui/src/api/v2/config.ts index 9def73abcd8..98e94cb93bd 100644 --- a/operator_ui/src/api/v2/config.ts +++ b/operator_ui/src/api/v2/config.ts @@ -1,5 +1,6 @@ -import * as jsonapi from 'api/transport/json' +import * as jsonapi from '@chainlink/json-api-client' import * as presenters from 'core/store/presenters' + /** * Show returns the whitelist of config variables * diff --git a/operator_ui/src/api/v2/runs.ts b/operator_ui/src/api/v2/runs.ts index cdbacdaea4f..a28cfc3c165 100644 --- a/operator_ui/src/api/v2/runs.ts +++ b/operator_ui/src/api/v2/runs.ts @@ -1,4 +1,4 @@ -import * as jsonapi from 'api/transport/json' +import * as jsonapi from '@chainlink/json-api-client' import * as models from 'core/store/models' import * as presenters from 'core/store/presenters' /** diff --git a/operator_ui/src/api/v2/specs.ts b/operator_ui/src/api/v2/specs.ts index 6f3e7829ae5..fdbc9ce4cee 100644 --- a/operator_ui/src/api/v2/specs.ts +++ b/operator_ui/src/api/v2/specs.ts @@ -1,4 +1,4 @@ -import * as jsonapi from 'api/transport/json' +import * as jsonapi from '@chainlink/json-api-client' import * as models from 'core/store/models' import * as presenters from 'core/store/presenters' /** diff --git a/operator_ui/src/api/v2/transactions.ts b/operator_ui/src/api/v2/transactions.ts index ea4ab6d34d8..30219c0096d 100644 --- a/operator_ui/src/api/v2/transactions.ts +++ b/operator_ui/src/api/v2/transactions.ts @@ -1,4 +1,4 @@ -import * as jsonapi from 'api/transport/json' +import * as jsonapi from '@chainlink/json-api-client' import * as presenters from 'core/store/presenters' /** diff --git a/operator_ui/src/api/v2/user/balances.ts b/operator_ui/src/api/v2/user/balances.ts index 962caed5a74..db05e82286f 100644 --- a/operator_ui/src/api/v2/user/balances.ts +++ b/operator_ui/src/api/v2/user/balances.ts @@ -1,4 +1,4 @@ -import * as jsonapi from 'api/transport/json' +import * as jsonapi from '@chainlink/json-api-client' import * as presenters from 'core/store/presenters' /** diff --git a/operator_ui/src/connectors/redux/reducers/notifications.js b/operator_ui/src/connectors/redux/reducers/notifications.js index d6be33439b1..8e6aafd079f 100644 --- a/operator_ui/src/connectors/redux/reducers/notifications.js +++ b/operator_ui/src/connectors/redux/reducers/notifications.js @@ -5,7 +5,7 @@ import { NOTIFY_ERROR, } from 'actions' import { set } from 'utils/storage' -import { BadRequestError } from '../../../api/errors' +import { BadRequestError } from '@chainlink/json-api-client' const initialState = { errors: [], diff --git a/operator_ui/tsconfig.json b/operator_ui/tsconfig.json index 6748324fa83..077e5340640 100644 --- a/operator_ui/tsconfig.json +++ b/operator_ui/tsconfig.json @@ -22,5 +22,8 @@ }, "include": ["src", "./@types"], - "references": [{ "path": "../styleguide" }] + "references": [ + { "path": "../styleguide" }, + { "path": "../tools/json-api-client" } + ] } diff --git a/package.json b/package.json index cc7427d23f6..85bf2a988df 100644 --- a/package.json +++ b/package.json @@ -6,8 +6,9 @@ "evm/box", "evm/v0.5", "tools", - "tools/prettier-config", "tools/eslint-config", + "tools/json-api-client", + "tools/prettier-config", "operator_ui", "integration", "styleguide", diff --git a/tools/json-api-client/.eslintignore b/tools/json-api-client/.eslintignore new file mode 100644 index 00000000000..6ca845cd079 --- /dev/null +++ b/tools/json-api-client/.eslintignore @@ -0,0 +1,3 @@ +public/ +node_modules/ +dist/ diff --git a/tools/json-api-client/.eslintrc.js b/tools/json-api-client/.eslintrc.js new file mode 100644 index 00000000000..e81b54ce712 --- /dev/null +++ b/tools/json-api-client/.eslintrc.js @@ -0,0 +1,4 @@ +module.exports = { + root: true, + extends: ['@chainlink/eslint-config/react'], +} diff --git a/tools/json-api-client/.gitignore b/tools/json-api-client/.gitignore new file mode 100644 index 00000000000..1521c8b7652 --- /dev/null +++ b/tools/json-api-client/.gitignore @@ -0,0 +1 @@ +dist diff --git a/tools/json-api-client/.prettierignore b/tools/json-api-client/.prettierignore new file mode 100644 index 00000000000..7cb6210d43f --- /dev/null +++ b/tools/json-api-client/.prettierignore @@ -0,0 +1,2 @@ +.prettierignore +.gitignore diff --git a/tools/json-api-client/@types/global.d.ts b/tools/json-api-client/@types/global.d.ts new file mode 100644 index 00000000000..34dd032a93b --- /dev/null +++ b/tools/json-api-client/@types/global.d.ts @@ -0,0 +1,5 @@ +declare namespace NodeJS { + interface Global { + fetch: any + } +} diff --git a/tools/json-api-client/@types/json-api-normalizer.d.ts b/tools/json-api-client/@types/json-api-normalizer.d.ts new file mode 100644 index 00000000000..c31065eebcc --- /dev/null +++ b/tools/json-api-client/@types/json-api-normalizer.d.ts @@ -0,0 +1,209 @@ +/* eslint @typescript-eslint/class-name-casing: 'off' */ + +declare module 'json-api-normalizer' { + export interface JsonApiResponse< + TData extends + | ResourceObject[] + | ResourceObject + | null = + | ResourceObject[] + | ResourceObject, + TError extends ErrorsObject[] = ErrorsObject[], + TIncluded extends (ResourceObject[]) | never = never, + TMeta extends Record | never = never, + TLinks extends LinksObject | never = never + > { + data: TData + included?: TIncluded + links: TLinks + errors?: TError + meta: TMeta + } + + /** + * Where specified, a links member can be used to represent links. The value of each links + * member MUST be an object (a “links object”). + * + * Each member of a links object is a “link”. A link MUST be represented as either: + * + * - a string containing the link’s URL. + * - an object (“link object”) which can contain the following members: + * - href: a string containing the link’s URL. + * - meta: a meta object containing non-standard meta-information about the link. + */ + type LinksObject = Partial> + export interface LinkObject< + TLinkMeta extends Record = Record + > { + href: string + meta: TLinkMeta + } + + export interface ErrorsObject< + TMeta extends Record = Record, + TLinks extends LinksObject = LinksObject + > { + id?: string + links?: TLinks + status?: string + code?: string + title?: string + detail?: string + source?: { + pointer?: string + parameter?: string + [propName: string]: any + } + meta?: TMeta + } + + /** + * Resource Identifier Objects + * A “resource identifier object” is an object that identifies an individual resource. + * + * A “resource identifier object” MUST contain type and id members. + * + * A “resource identifier object” MAY also include a meta member, whose value is a meta object + * that contains non-standard meta-information. + */ + export interface ResourceIdentifierObject< + TMeta extends Record = Record + > { + type: string + id: string + meta?: TMeta + } + + /** + * Resource Linkage + * + * Resource linkage in a compound document allows a client to + * link together all of the included resource objects without + * having to GET any URLs via links. + * + * Resource linkage MUST be represented as one of the following: + * + * - null for empty to-one relationships. + * - an empty array ([]) for empty to-many relationships. + * - a single resource identifier object for non-empty to-one relationships. + * - an array of resource identifier objects for non-empty to-many relationships. + */ + export type ResourceLinkage = + | null + | [] + | ResourceIdentifierObject + | ResourceIdentifierObject[] + + /** + * The value of the relationships key MUST be an object (a “relationships object”). Members of + * the relationships object (“relationships”) represent references from the resource object in + * which it’s defined to other resource objects. + * + * Relationships may be to-one or to-many. + * + * A “relationship object” MUST contain at least one of the following: + * + * - links: a links object containing at least one of the following: + * - self: a link for the relationship itself (a “relationship link”). + * This link allows the client to directly manipulate the relationship. + * For example, removing an author through an article’s relationship URL would + * disconnect the person from the article without deleting the people resource itself. + * When fetched successfully, this link returns the linkage for the related resources + * as its primary data. (See Fetching Relationships.) + * - related: a related resource link + *- data: resource linkage + *- meta: a meta object that contains non-standard meta-information about the relationship. + * + * A relationship object that represents a to-many relationship MAY also contain pagination + * links under the links member, as described below. + * Any pagination links in a relationship object MUST paginate the relationship data, + * not the related resources. + */ + export type Relationship< + TMeta extends Record = Record, + TLinks extends LinksObject = LinksObject + > = _Relationship + export interface _Relationship< + TMeta extends Record, + TLinks extends LinksObject + > { + data?: JsonApiResponse | JsonApiResponse[] + links?: TLinks + meta?: TMeta + } + + /** + * The value of the attributes key MUST be an object (an “attributes object”). Members of the + * attributes object (“attributes”) represent information about the resource object in which + * it’s defined. + * + * Attributes may contain any valid JSON value. + * + * Complex data structures involving JSON objects and arrays are allowed as attribute values. + * However, any object that constitutes or is contained in an attribute MUST NOT contain a + * relationships or links member, as those members are reserved by this specification for + * future use. + * + * Although has-one foreign keys (e.g. author_id) are often stored internally alongside other + * information to be represented in a resource object, these keys SHOULD NOT appear as + * attributes. + */ + interface AttributesObject extends Record {} + + /** + * Resource Objects + * + * “Resource objects” appear in a JSON:API document to represent resources. + * + * A resource object MUST contain at least the following top-level members: + * - id + * - type + * + * Exception: The id member is not required when the resource object originates at the client + * and represents a new resource to be created on the server. + * + * In addition, a resource object MAY contain any of these top-level members: + * - attributes: an attributes object representing some of the resource’s data. + * - relationships: a relationships object describing relationships between the resource and other JSON:API resources. + * - links: a links object containing links related to the resource. + * - meta: a meta object containing non-standard meta-information about a resource that can not be represented as an attribute or relationship. + */ + export interface ResourceObject< + TAttributes extends AttributesObject | never = never, + TRelationships extends + | Record>> + | never = never, + TMeta extends Record | never = never, + TLinks extends LinksObject | never = never + > { + id: string + type: string + attributes: TAttributes + relationships: TRelationships + links: TLinks + meta: TMeta + } + + // we cant infer TNormalized from the arguments + // because of the transformations it does on the supplied data + // unfortunately, this means that the user will have to supply + // their own typing for TNormalized + + /** + * Normalize JSON:API spec compliant JSON + * + * @param json The JSON:API spec compliant JSON to normalize + * @param opts Options for normalizing + */ + export default function normalize( + json: JsonApiResponse, + opts?: Opts, + ): TNormalized + + interface Opts { + camelizeKeys?: boolean + camelizeTypeValues?: boolean + endpoint?: string + filterEndpoint?: boolean + } +} diff --git a/operator_ui/__tests__/utils/fetchWithTimeout.test.ts b/tools/json-api-client/__tests__/fetchWithTimeout.test.ts similarity index 91% rename from operator_ui/__tests__/utils/fetchWithTimeout.test.ts rename to tools/json-api-client/__tests__/fetchWithTimeout.test.ts index b64bda6beb6..09294dacbc7 100644 --- a/operator_ui/__tests__/utils/fetchWithTimeout.test.ts +++ b/tools/json-api-client/__tests__/fetchWithTimeout.test.ts @@ -1,15 +1,17 @@ -import fetchWithTimeout from 'utils/fetchWithTimeout' +import fetchWithTimeout from '../src/fetchWithTimeout' describe('fetchWithTimeout', () => { it('rejects fetch requests after timeout period', () => { const timeoutResponse = new Promise(res => setTimeout(() => res(200), 100)) global.fetch.getOnce('/test', timeoutResponse) + return expect(fetchWithTimeout('/test', {}, 1)).rejects.toThrow('timeout') }) it('resolves fetch requests before timeout period', () => { const timeoutResponse = new Promise(res => setTimeout(() => res(200), 1)) global.fetch.getOnce('/test', timeoutResponse) + return expect(fetchWithTimeout('/test', {}, 100)).resolves.toMatchObject({ status: 200, }) diff --git a/tools/json-api-client/jest.config.js b/tools/json-api-client/jest.config.js new file mode 100644 index 00000000000..957032f3d22 --- /dev/null +++ b/tools/json-api-client/jest.config.js @@ -0,0 +1,6 @@ +module.exports = { + preset: 'ts-jest', + testEnvironment: 'node', + setupFilesAfterEnv: ['/jest.setup.js'], + testPathIgnorePatterns: ['/node_modules/'], +} diff --git a/tools/json-api-client/jest.setup.js b/tools/json-api-client/jest.setup.js new file mode 100644 index 00000000000..4f37aed1a13 --- /dev/null +++ b/tools/json-api-client/jest.setup.js @@ -0,0 +1,2 @@ +global.fetch = require('fetch-mock').sandbox() +global.fetch.config.overwriteRoutes = true diff --git a/tools/json-api-client/package.json b/tools/json-api-client/package.json new file mode 100644 index 00000000000..71b81fe63e1 --- /dev/null +++ b/tools/json-api-client/package.json @@ -0,0 +1,30 @@ +{ + "name": "@chainlink/json-api-client", + "private": true, + "version": "0.0.0", + "main": "./dist/src", + "types": "./dist/src", + "scripts": { + "build": "rm -rf dist && tsc", + "lint": "eslint --ext .js,.ts .", + "format": "prettier --write \"*.ts\"", + "setup": "yarn build", + "test": "jest", + "test:ci": "yarn test --coverage --reporters jest-silent-reporter --maxWorkers=50%" + }, + "peerDependencies": {}, + "dependencies": { + "isomorphic-unfetch": "^3.0.0", + "json-api-normalizer": "^0.4.13", + "path-to-regexp": "^3.0.0", + "typescript": "^3.6.3" + }, + "devDependencies": { + "@types/jest": "^24.0.21", + "@types/fetch-mock": "^7.3.1", + "@types/path-to-regexp": "^1.7.0", + "fetch-mock": "^7.3.1", + "jest": "^24.9.0", + "ts-jest": "^24.0.0" + } +} diff --git a/operator_ui/src/api/errors.ts b/tools/json-api-client/src/errors.ts similarity index 100% rename from operator_ui/src/api/errors.ts rename to tools/json-api-client/src/errors.ts diff --git a/operator_ui/src/utils/fetchWithTimeout.ts b/tools/json-api-client/src/fetchWithTimeout.ts similarity index 100% rename from operator_ui/src/utils/fetchWithTimeout.ts rename to tools/json-api-client/src/fetchWithTimeout.ts diff --git a/tools/json-api-client/src/formatRequestURI.ts b/tools/json-api-client/src/formatRequestURI.ts new file mode 100644 index 00000000000..e3c3ac628af --- /dev/null +++ b/tools/json-api-client/src/formatRequestURI.ts @@ -0,0 +1,20 @@ +import url, { UrlObject } from 'url' + +export interface Options { + port?: string + hostname?: string +} + +export default (path: string, query = {}, options: Options = {}) => { + const formatOptions: UrlObject = { + pathname: path, + query: query, + } + + if (options.port) { + formatOptions.port = options.port + formatOptions.hostname = options.hostname + } + + return url.format(formatOptions) +} diff --git a/tools/json-api-client/src/index.ts b/tools/json-api-client/src/index.ts new file mode 100644 index 00000000000..1f145d31b82 --- /dev/null +++ b/tools/json-api-client/src/index.ts @@ -0,0 +1,3 @@ +export * from './errors' +export * from './transport/http' +export * from './transport/json' diff --git a/operator_ui/src/api/transport/http.ts b/tools/json-api-client/src/transport/http.ts similarity index 95% rename from operator_ui/src/api/transport/http.ts rename to tools/json-api-client/src/transport/http.ts index 8de25b9a423..1664c518bee 100644 --- a/operator_ui/src/api/transport/http.ts +++ b/tools/json-api-client/src/transport/http.ts @@ -1,5 +1,5 @@ import 'isomorphic-unfetch' -import formatRequestURI from 'utils/formatRequestURI' +import formatRequestURI from '../formatRequestURI' export enum Method { GET = 'GET', diff --git a/operator_ui/src/api/transport/json.ts b/tools/json-api-client/src/transport/json.ts similarity index 99% rename from operator_ui/src/api/transport/json.ts rename to tools/json-api-client/src/transport/json.ts index 09b9a1c07eb..98c56e31c3e 100644 --- a/operator_ui/src/api/transport/json.ts +++ b/tools/json-api-client/src/transport/json.ts @@ -5,7 +5,7 @@ import { ResourceObject, } from 'json-api-normalizer' import pathToRegexp from 'path-to-regexp' -import fetchWithTimeout from 'utils/fetchWithTimeout' +import fetchWithTimeout from '../fetchWithTimeout' import { AuthenticationError, BadRequestError, diff --git a/tools/json-api-client/tsconfig.json b/tools/json-api-client/tsconfig.json new file mode 100644 index 00000000000..57580c04c50 --- /dev/null +++ b/tools/json-api-client/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "composite": true, + "declaration": true, + "declarationMap": true, + "strict": true, + "skipLibCheck": true, + "noEmitOnError": true, + "allowSyntheticDefaultImports": true, + "moduleResolution": "node", + "target": "es2017", + "module": "es2015", + "lib": ["es2018", "dom"], + "outDir": "./dist", + "jsx": "preserve", + "sourceMap": true, + "removeComments": false + }, + + "include": ["src", "@types"] +} diff --git a/yarn.lock b/yarn.lock index e18603cc808..1c4fc7300fd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2853,6 +2853,13 @@ dependencies: "@types/jest-diff" "*" +"@types/jest@^24.0.21": + version "24.0.21" + resolved "https://registry.npmjs.org/@types/jest/-/jest-24.0.21.tgz#2c0a25440e025bb265f4a17d8b79b11b231426bf" + integrity sha512-uyqFvx78Tuy0h5iLCPWRCvi5HhWwEqhIj30doitp191oYLqlCxUyAJHdWVm5+Nr271/vPnkyt6rWeEIjGowBTg== + dependencies: + "@types/jest-diff" "*" + "@types/json-schema@^7.0.3": version "7.0.3" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.3.tgz#bdfd69d61e464dcc81b25159c270d75a73c1a636" @@ -20167,7 +20174,7 @@ sha.js@^2.4.0, sha.js@^2.4.8: sha3@^1.2.2, sha3@^2.0.7: version "2.0.7" - resolved "https://registry.yarnpkg.com/sha3/-/sha3-2.0.7.tgz#02ed270099647cbcc681b064ba091af72f561f35" + resolved "https://registry.npmjs.org/sha3/-/sha3-2.0.7.tgz#02ed270099647cbcc681b064ba091af72f561f35" integrity sha512-7Qsj/0J3pxCWfmyuDbTZWoKNSKY/rg2eecNRvTYE4EAPJ11Uh6xuEObyCG295AqgrtOjzdDbfwtGLDY52h11tA== dependencies: buffer "5.4.0" From b1c5f4f9b11ad0dc2f5575c83b36dbc54766dfd9 Mon Sep 17 00:00:00 2001 From: Alex Kwiatkowski Date: Wed, 30 Oct 2019 14:17:49 -0700 Subject: [PATCH 002/199] Bump versions for local packages to 0.0.1 --- explorer/client/package.json | 2 +- explorer/package.json | 2 +- operator_ui/package.json | 2 +- styleguide/package.json | 2 +- tools/json-api-client/package.json | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/explorer/client/package.json b/explorer/client/package.json index f28088a6410..a9880cfc372 100644 --- a/explorer/client/package.json +++ b/explorer/client/package.json @@ -1,7 +1,7 @@ { "name": "@chainlink/explorer-client", "private": true, - "version": "0.0.0", + "version": "0.0.1", "description": "LINK Explorer Client", "author": "Chainlink Dev Team", "license": "MIT", diff --git a/explorer/package.json b/explorer/package.json index 9236ed9e6d5..cb432a7bf75 100644 --- a/explorer/package.json +++ b/explorer/package.json @@ -1,7 +1,7 @@ { "name": "@chainlink/explorer", "private": true, - "version": "0.0.0", + "version": "0.0.1", "description": "LINK Explorer", "author": "Chainlink Dev Team", "license": "MIT", diff --git a/operator_ui/package.json b/operator_ui/package.json index 5b92294521c..8aa9e49cb64 100644 --- a/operator_ui/package.json +++ b/operator_ui/package.json @@ -22,7 +22,7 @@ }, "dependencies": { "@babel/polyfill": "^7.2.5", - "@chainlink/styleguide": "0.0.0", + "@chainlink/styleguide": "0.0.1", "@material-ui/core": "^3.9.2", "@material-ui/icons": "^3.0.1", "bignumber.js": "^9.0.0", diff --git a/styleguide/package.json b/styleguide/package.json index 09109d0f734..40f562f50d9 100644 --- a/styleguide/package.json +++ b/styleguide/package.json @@ -1,7 +1,7 @@ { "name": "@chainlink/styleguide", "private": true, - "version": "0.0.0", + "version": "0.0.1", "main": "./dist/src", "types": "./dist/src", "scripts": { diff --git a/tools/json-api-client/package.json b/tools/json-api-client/package.json index 71b81fe63e1..0a432e60f95 100644 --- a/tools/json-api-client/package.json +++ b/tools/json-api-client/package.json @@ -1,7 +1,7 @@ { "name": "@chainlink/json-api-client", "private": true, - "version": "0.0.0", + "version": "0.0.1", "main": "./dist/src", "types": "./dist/src", "scripts": { From cda2448c12803d9f36cfc0559c26e15000134c8e Mon Sep 17 00:00:00 2001 From: Alex Kwiatkowski Date: Wed, 30 Oct 2019 15:15:59 -0700 Subject: [PATCH 003/199] Use stricter types for formatRequestURI --- .../__tests__/utils/formatRequestURI.test.js | 17 -------------- operator_ui/src/utils/formatRequestURI.js | 14 ----------- .../__tests__/formatRequestURI.test.ts | 23 +++++++++++++++++++ tools/json-api-client/package.json | 5 ++-- tools/json-api-client/src/formatRequestURI.ts | 13 ++++++----- tools/json-api-client/src/transport/http.ts | 2 +- tools/json-api-client/src/transport/json.ts | 7 +++--- yarn.lock | 7 ------ 8 files changed, 37 insertions(+), 51 deletions(-) delete mode 100644 operator_ui/__tests__/utils/formatRequestURI.test.js delete mode 100644 operator_ui/src/utils/formatRequestURI.js create mode 100644 tools/json-api-client/__tests__/formatRequestURI.test.ts diff --git a/operator_ui/__tests__/utils/formatRequestURI.test.js b/operator_ui/__tests__/utils/formatRequestURI.test.js deleted file mode 100644 index f8c2e8fa4da..00000000000 --- a/operator_ui/__tests__/utils/formatRequestURI.test.js +++ /dev/null @@ -1,17 +0,0 @@ -import formatRequestURI from 'utils/formatRequestURI' - -describe('formatRequestURI', () => { - describe('port specified', () => { - it('returns host and port in URI', () => { - expect( - formatRequestURI('/api', {}, { hostname: 'localhost', port: 6689 }), - ).toEqual('localhost:6689/api') - }) - }) - - describe('no port specified', () => { - it('returns just the path in the URI', () => { - expect(formatRequestURI('/api')).toEqual('/api') - }) - }) -}) diff --git a/operator_ui/src/utils/formatRequestURI.js b/operator_ui/src/utils/formatRequestURI.js deleted file mode 100644 index 6eaf9cb5a73..00000000000 --- a/operator_ui/src/utils/formatRequestURI.js +++ /dev/null @@ -1,14 +0,0 @@ -import url from 'url' - -export default (path, query = {}, options = {}) => { - const formatOptions = { - pathname: path, - query: query, - } - - if (options.port) { - formatOptions['port'] = options.port - formatOptions['hostname'] = options.hostname - } - return url.format(formatOptions) -} diff --git a/tools/json-api-client/__tests__/formatRequestURI.test.ts b/tools/json-api-client/__tests__/formatRequestURI.test.ts new file mode 100644 index 00000000000..cb8cd265d19 --- /dev/null +++ b/tools/json-api-client/__tests__/formatRequestURI.test.ts @@ -0,0 +1,23 @@ +import formatRequestURI from '../src/formatRequestURI' + +describe('formatRequestURI', () => { + describe('port specified', () => { + it('returns host and port in URI', () => { + const uri = formatRequestURI( + '/api', + {}, + { hostname: 'localhost', port: '6689' }, + ) + + expect(uri).toEqual('localhost:6689/api') + }) + }) + + describe('no port specified', () => { + it('returns just the path in the URI', () => { + const uri = formatRequestURI('/api') + + expect(uri).toEqual('/api') + }) + }) +}) diff --git a/tools/json-api-client/package.json b/tools/json-api-client/package.json index 0a432e60f95..b64a02546d7 100644 --- a/tools/json-api-client/package.json +++ b/tools/json-api-client/package.json @@ -17,11 +17,12 @@ "isomorphic-unfetch": "^3.0.0", "json-api-normalizer": "^0.4.13", "path-to-regexp": "^3.0.0", - "typescript": "^3.6.3" + "typescript": "^3.6.3", + "url": "^0.11.0" }, "devDependencies": { - "@types/jest": "^24.0.21", "@types/fetch-mock": "^7.3.1", + "@types/jest": "^24.0.18", "@types/path-to-regexp": "^1.7.0", "fetch-mock": "^7.3.1", "jest": "^24.9.0", diff --git a/tools/json-api-client/src/formatRequestURI.ts b/tools/json-api-client/src/formatRequestURI.ts index e3c3ac628af..9deef8486ac 100644 --- a/tools/json-api-client/src/formatRequestURI.ts +++ b/tools/json-api-client/src/formatRequestURI.ts @@ -1,15 +1,16 @@ -import url, { UrlObject } from 'url' +import * as url from 'url' export interface Options { port?: string hostname?: string } -export default (path: string, query = {}, options: Options = {}) => { - const formatOptions: UrlObject = { - pathname: path, - query: query, - } +export default function( + pathname: string, + query: Record = {}, + options: Options = {}, +) { + const formatOptions: url.UrlObject = { pathname, query } if (options.port) { formatOptions.port = options.port diff --git a/tools/json-api-client/src/transport/http.ts b/tools/json-api-client/src/transport/http.ts index 1664c518bee..d74cad70af8 100644 --- a/tools/json-api-client/src/transport/http.ts +++ b/tools/json-api-client/src/transport/http.ts @@ -25,7 +25,7 @@ export function getOptions(method: Method): (val?: any) => FetchOptions { return CUDOptionFactory(method) } -export function formatURI(path: string, query: object = {}) { +export function formatURI(path: string, query: Record = {}) { return formatRequestURI(path, query, { hostname: location.hostname, port: process.env.CHAINLINK_PORT, diff --git a/tools/json-api-client/src/transport/json.ts b/tools/json-api-client/src/transport/json.ts index 98c56e31c3e..26cbe978f9c 100644 --- a/tools/json-api-client/src/transport/json.ts +++ b/tools/json-api-client/src/transport/json.ts @@ -77,10 +77,9 @@ function methodFactory(method: http.Method) { return (params, namedPathParams) => { // if required, compile our path with its named path parameters const path = namedPathParams ? toPath(namedPathParams) : url - const uri = http.formatURI( - path, - method === http.Method.GET ? params : undefined, // add query string options if its a GET method - ) + // add query string options if its a GET method + const query = method === http.Method.GET ? params : {} + const uri = http.formatURI(path, query) const options = http.getOptions(method) const fetch = fetchWithTimeout(uri, options(params)) diff --git a/yarn.lock b/yarn.lock index 1c4fc7300fd..2dc2626cf05 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2853,13 +2853,6 @@ dependencies: "@types/jest-diff" "*" -"@types/jest@^24.0.21": - version "24.0.21" - resolved "https://registry.npmjs.org/@types/jest/-/jest-24.0.21.tgz#2c0a25440e025bb265f4a17d8b79b11b231426bf" - integrity sha512-uyqFvx78Tuy0h5iLCPWRCvi5HhWwEqhIj30doitp191oYLqlCxUyAJHdWVm5+Nr271/vPnkyt6rWeEIjGowBTg== - dependencies: - "@types/jest-diff" "*" - "@types/json-schema@^7.0.3": version "7.0.3" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.3.tgz#bdfd69d61e464dcc81b25159c270d75a73c1a636" From 5016e345ea6c34efd37c7ace61b4505f08b2da19 Mon Sep 17 00:00:00 2001 From: Alex Kwiatkowski Date: Wed, 30 Oct 2019 15:21:44 -0700 Subject: [PATCH 004/199] Remove tsconfig dom lib --- tools/json-api-client/tsconfig.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/json-api-client/tsconfig.json b/tools/json-api-client/tsconfig.json index 57580c04c50..69df688103d 100644 --- a/tools/json-api-client/tsconfig.json +++ b/tools/json-api-client/tsconfig.json @@ -10,7 +10,7 @@ "moduleResolution": "node", "target": "es2017", "module": "es2015", - "lib": ["es2018", "dom"], + "lib": ["es2018"], "outDir": "./dist", "jsx": "preserve", "sourceMap": true, From 28ca42b76bd4de631c4a039d2973b49b97c7a050 Mon Sep 17 00:00:00 2001 From: Alex Kwiatkowski Date: Wed, 30 Oct 2019 15:22:56 -0700 Subject: [PATCH 005/199] Remove skipLibCheck --- tools/json-api-client/tsconfig.json | 1 - 1 file changed, 1 deletion(-) diff --git a/tools/json-api-client/tsconfig.json b/tools/json-api-client/tsconfig.json index 69df688103d..38b42d2702d 100644 --- a/tools/json-api-client/tsconfig.json +++ b/tools/json-api-client/tsconfig.json @@ -4,7 +4,6 @@ "declaration": true, "declarationMap": true, "strict": true, - "skipLibCheck": true, "noEmitOnError": true, "allowSyntheticDefaultImports": true, "moduleResolution": "node", From c9c3be5a6214d0f9e4514bdae47086babe420ba8 Mon Sep 17 00:00:00 2001 From: Alex Kwiatkowski Date: Wed, 30 Oct 2019 21:29:23 -0700 Subject: [PATCH 006/199] Don't need to lint .js files in json-api-client package --- tools/json-api-client/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/json-api-client/package.json b/tools/json-api-client/package.json index b64a02546d7..dfc5baa8687 100644 --- a/tools/json-api-client/package.json +++ b/tools/json-api-client/package.json @@ -6,7 +6,7 @@ "types": "./dist/src", "scripts": { "build": "rm -rf dist && tsc", - "lint": "eslint --ext .js,.ts .", + "lint": "eslint --ext .ts .", "format": "prettier --write \"*.ts\"", "setup": "yarn build", "test": "jest", From 08756a68e30c6c8a3f40a9fc35ed0a048726e2b4 Mon Sep 17 00:00:00 2001 From: spooktheducks Date: Tue, 29 Oct 2019 11:51:32 -0500 Subject: [PATCH 007/199] Allow aggregators to specify payment amounts per-oracle --- .solhintignore | 4 +++- evm/v0.5/contracts/dev/Coordinator.sol | 8 +++++--- evm/v0.5/contracts/tests/EmptyAggregator.sol | 3 ++- evm/v0.5/contracts/tests/MeanAggregator.sol | 11 +++++++++-- 4 files changed, 19 insertions(+), 7 deletions(-) diff --git a/.solhintignore b/.solhintignore index ddb6e7610a1..a8c1b38254b 100644 --- a/.solhintignore +++ b/.solhintignore @@ -9,4 +9,6 @@ examples/testnet/contracts/Aggregator.sol examples/testnet/contracts/AggregatorProxy.sol evm/contracts/lib evm/box/node_modules -evm/node_modules \ No newline at end of file +evm/node_modules +evm/contracts/vendor +evm/v0.5/contracts/vendor diff --git a/evm/v0.5/contracts/dev/Coordinator.sol b/evm/v0.5/contracts/dev/Coordinator.sol index df9b322671a..410295ccce3 100644 --- a/evm/v0.5/contracts/dev/Coordinator.sol +++ b/evm/v0.5/contracts/dev/Coordinator.sol @@ -230,12 +230,14 @@ contract Coordinator is ChainlinkRequestInterface, CoordinatorInterface { sA.aggFulfillSelector, _requestId, callback.sAId, msg.sender, _data)); require(ok, "aggregator.fulfill failed"); require(aggResponse.length > 0, "probably wrong address/selector"); - (bool aggSuccess, bool aggComplete, bytes memory response) = abi.decode( - aggResponse, (bool, bool, bytes)); + (bool aggSuccess, bool aggComplete, bytes memory response, int256[] memory paymentAmounts) = abi.decode( // solhint-disable-line + aggResponse, (bool, bool, bytes, int256[])); require(aggSuccess, string(response)); if (aggComplete) { + require(paymentAmounts.length == sA.oracles.length, "wrong paymentAmounts.length"); for (uint256 oIdx = 0; oIdx < sA.oracles.length; oIdx++) { // pay oracles - withdrawableTokens[sA.oracles[oIdx]] += sA.payment / sA.oracles.length; + withdrawableTokens[sA.oracles[oIdx]] = uint256(int256( + withdrawableTokens[sA.oracles[oIdx]]) + paymentAmounts[oIdx]); } // solhint-disable-next-line avoid-low-level-calls (bool success,) = callback.addr.call(abi.encodeWithSelector( // report final result callback.functionId, _requestId, abi.decode(response, (bytes32)))); diff --git a/evm/v0.5/contracts/tests/EmptyAggregator.sol b/evm/v0.5/contracts/tests/EmptyAggregator.sol index 4351fa20245..77f6d7b2a44 100644 --- a/evm/v0.5/contracts/tests/EmptyAggregator.sol +++ b/evm/v0.5/contracts/tests/EmptyAggregator.sol @@ -25,7 +25,8 @@ contract EmptyAggregator { function fulfill(bytes32 _requestId, bytes32 _saId, address _oracle, bytes32 _fulfillment) - public returns (bool success, bool complete, bytes memory response) { + public returns (bool success, bool complete, bytes memory response, + int256[] memory paymentAmounts) { success = true; complete = true; response = abi.encode(_fulfillment); diff --git a/evm/v0.5/contracts/tests/MeanAggregator.sol b/evm/v0.5/contracts/tests/MeanAggregator.sol index f993eeac6ed..203f7f6a452 100644 --- a/evm/v0.5/contracts/tests/MeanAggregator.sol +++ b/evm/v0.5/contracts/tests/MeanAggregator.sol @@ -12,6 +12,7 @@ contract MeanAggregator { mapping(bytes32 /* service agreement ID */ => uint256) numOracles; mapping(bytes32 /* request ID */ => mapping(address /* oracle */ => bool)) reported; + mapping(bytes32 /* service agreement ID */ => uint256) payment; mapping(bytes32 /* request ID */ => uint256) numberReported; // Current total for given request, divided by number of oracles reporting @@ -29,14 +30,16 @@ contract MeanAggregator { return (false, bytes("must depend on at least one oracle")); } numOracles[_sAId] = _sa.oracles.length; + payment[_sAId] = _sa.payment; success = true; } function fulfill(bytes32 _requestId, bytes32 _sAId, address _oracle, bytes32 _value) public - returns (bool success, bool complete, bytes memory response) { + returns (bool success, bool complete, bytes memory response, + int256[] memory paymentAmounts) { if (reported[_requestId][_oracle]) { - return (false, false, "oracle already reported"); + return (false, false, "oracle already reported", paymentAmounts); } uint256 oDividend = uint256(_value) / numOracles[_sAId]; uint256 oRemainder = uint256(_value) % numOracles[_sAId]; @@ -51,6 +54,10 @@ contract MeanAggregator { complete = (numberReported[_requestId] == numOracles[_sAId]); if (complete) { response = abi.encode(average[_requestId]); + paymentAmounts = new int256[](numOracles[_sAId]); + for (uint256 oIdx = 0; oIdx < numOracles[_sAId]; oIdx++) { + paymentAmounts[oIdx] = int256(payment[_sAId] / numOracles[_sAId]); + } } } } From 4f9ca45dc8a2d1efdbd1adaf360161b28e3c4285 Mon Sep 17 00:00:00 2001 From: henrynguyen5 <6404866+HenryNguyen5@users.noreply.github.com> Date: Fri, 18 Oct 2019 17:20:23 -0400 Subject: [PATCH 008/199] Rename evm/test/Oracle_test.js to .ts file --- evm/{test/Oracle_test.js => testv2/Oracle.test.ts} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename evm/{test/Oracle_test.js => testv2/Oracle.test.ts} (100%) diff --git a/evm/test/Oracle_test.js b/evm/testv2/Oracle.test.ts similarity index 100% rename from evm/test/Oracle_test.js rename to evm/testv2/Oracle.test.ts From c13179bfbf92ba6649ebb3c710ac422edbe45317 Mon Sep 17 00:00:00 2001 From: henrynguyen5 <6404866+HenryNguyen5@users.noreply.github.com> Date: Fri, 18 Oct 2019 20:02:49 -0400 Subject: [PATCH 009/199] Convert evm/testv2/Oracle.test.ts to ethers --- evm/testv2/Oracle.test.ts | 791 ++++++++++++++++++++++---------------- 1 file changed, 452 insertions(+), 339 deletions(-) diff --git a/evm/testv2/Oracle.test.ts b/evm/testv2/Oracle.test.ts index fcc06312dbc..40aee50dc96 100644 --- a/evm/testv2/Oracle.test.ts +++ b/evm/testv2/Oracle.test.ts @@ -1,36 +1,49 @@ -import * as h from '../src/helpers' -import { assertBigNum } from '../src/matchers' -const BasicConsumer = artifacts.require('BasicConsumer.sol') -const GetterSetter = artifacts.require('GetterSetter.sol') -const MaliciousRequester = artifacts.require('MaliciousRequester.sol') -const MaliciousConsumer = artifacts.require('MaliciousConsumer.sol') -const Oracle = artifacts.require('Oracle.sol') - -let roles - -before(async () => { - const rolesAndPersonas = await h.initializeRolesAndPersonas() +import * as h from '../src/helpersV2' +import { assertBigNum } from '../src/matchersV2' +import { BasicConsumerFactory } from '../src/generated/BasicConsumerFactory' +import { GetterSetterFactory } from '../src/generated/GetterSetterFactory' +import { MaliciousRequesterFactory } from '../src/generated/MaliciousRequesterFactory' +import { MaliciousConsumerFactory } from '../src/generated/MaliciousConsumerFactory' +import { OracleFactory } from '../src/generated/OracleFactory' +import { LinkTokenFactory } from '../src/generated/LinkTokenFactory' +import { EthersProviderWrapper } from '../src/provider' +import env from '@nomiclabs/buidler' +import { Instance } from '../src/contract' +import { ethers } from 'ethers' +import { assert } from 'chai' + +const basicConsumerFactory = new BasicConsumerFactory() +const getterSetterFactory = new GetterSetterFactory() +const maliciousRequesterFactory = new MaliciousRequesterFactory() +const maliciousConsumerFactory = new MaliciousConsumerFactory() +const oracleFactory = new OracleFactory() +const linkTokenFactory = new LinkTokenFactory() + +let roles: h.Roles +const provider = new EthersProviderWrapper(env.ethereum) + +beforeAll(async () => { + const rolesAndPersonas = await h.initializeRolesAndPersonas(provider) roles = rolesAndPersonas.roles }) -contract('Oracle', () => { - const fHash = h.functionSelector('requestedBytes32(bytes32,bytes32)') +describe('Oracle', () => { + const fHash = getterSetterFactory.interface.functions.requestedBytes32.sighash const specId = '0x4c7b7ffb66b344fbaa64995af81e355a00000000000000000000000000000000' const to = '0x80e29acb842498fe6591f020bd82766dce619d43' - let link, oc, withdraw + let link: Instance + let oc: Instance beforeEach(async () => { - link = await h.linkContract(roles.defaultAccount) - oc = await Oracle.new(link.address) - await oc.setFulfillmentPermission(roles.oracleNode, true) - withdraw = async (address, amount, options) => - oc.withdraw(address, amount.toString(), options) + link = await linkTokenFactory.connect(roles.defaultAccount).deploy() + oc = await oracleFactory.connect(roles.defaultAccount).deploy(link.address) + await oc.setFulfillmentPermission(roles.oracleNode.address, true) }) it('has a limited public interface', () => { - h.checkPublicABI(Oracle, [ + h.checkPublicABI(oracleFactory, [ 'EXPIRY_TIME', 'cancelOracleRequest', 'fulfillOracleRequest', @@ -48,60 +61,66 @@ contract('Oracle', () => { }) describe('#setFulfillmentPermission', () => { - context('when called by the owner', () => { + describe('when called by the owner', () => { beforeEach(async () => { - await oc.setFulfillmentPermission(roles.stranger, true, { - from: roles.defaultAccount, - }) + await oc + .connect(roles.defaultAccount) + .setFulfillmentPermission(roles.stranger.address, true) }) it('adds an authorized node', async () => { - const authorized = await oc.getAuthorizationStatus(roles.stranger) + const authorized = await oc.getAuthorizationStatus( + roles.stranger.address, + ) assert.equal(true, authorized) }) it('removes an authorized node', async () => { - await oc.setFulfillmentPermission(roles.stranger, false, { - from: roles.defaultAccount, - }) - const authorized = await oc.getAuthorizationStatus(roles.stranger) + await oc + .connect(roles.defaultAccount) + .setFulfillmentPermission(roles.stranger.address, false) + const authorized = await oc.getAuthorizationStatus( + roles.stranger.address, + ) assert.equal(false, authorized) }) }) - context('when called by a non-owner', () => { + describe('when called by a non-owner', () => { it('cannot add an authorized node', async () => { await h.assertActionThrows(async () => { - await oc.setFulfillmentPermission(roles.stranger, true, { - from: roles.stranger, - }) + await oc + .connect(roles.stranger) + .setFulfillmentPermission(roles.stranger.address, true) }) }) }) }) describe('#onTokenTransfer', () => { - context('when called from any address but the LINK token', () => { + describe('when called from any address but the LINK token', () => { it('triggers the intended method', async () => { - const callData = h.requestDataBytes(specId, to, fHash, 'id', '') + const callData = h.requestDataBytes(specId, to, fHash, 0, '0x0') await h.assertActionThrows(async () => { - await oc.onTokenTransfer(roles.defaultAccount, 0, callData) + await oc.onTokenTransfer(roles.defaultAccount.address, 0, callData) }) }) }) - context('when called from the LINK token', () => { + describe('when called from the LINK token', () => { it('triggers the intended method', async () => { - const callData = h.requestDataBytes(specId, to, fHash, 'id', '') + const callData = h.requestDataBytes(specId, to, fHash, 0, '0x0') const tx = await link.transferAndCall(oc.address, 0, callData, { value: 0, }) - assert.equal(3, tx.receipt.rawLogs.length) + const receipt = await tx.wait() + + assert.equal(3, receipt.logs!.length) }) - context('with no data', () => { + describe('with no data', () => { it('reverts', async () => { await h.assertActionThrows(async () => { await link.transferAndCall(oc.address, 0, '0x', { @@ -112,61 +131,59 @@ contract('Oracle', () => { }) }) - context('malicious requester', () => { - let mock, requester - const paymentAmount = h.toWei('1', 'ether') + describe('malicious requester', () => { + let mock: Instance + let requester: Instance + const paymentAmount = h.toWei('1') beforeEach(async () => { - mock = await MaliciousRequester.new(link.address, oc.address) + mock = await maliciousRequesterFactory + .connect(roles.defaultAccount) + .deploy(link.address, oc.address) await link.transfer(mock.address, paymentAmount) }) it('cannot withdraw from oracle', async () => { - const ocOriginalBalance = await link.balanceOf.call(oc.address) - const mockOriginalBalance = await link.balanceOf.call(mock.address) + const ocOriginalBalance = await link.balanceOf(oc.address) + const mockOriginalBalance = await link.balanceOf(mock.address) await h.assertActionThrows(async () => { await mock.maliciousWithdraw() }) - const ocNewBalance = await link.balanceOf.call(oc.address) - const mockNewBalance = await link.balanceOf.call(mock.address) + const ocNewBalance = await link.balanceOf(oc.address) + const mockNewBalance = await link.balanceOf(mock.address) assertBigNum(ocOriginalBalance, ocNewBalance) assertBigNum(mockNewBalance, mockOriginalBalance) }) - context( - 'if the requester tries to create a requestId for another contract', - () => { - it('the requesters ID will not match with the oracle contract', async () => { - const tx = await mock.maliciousTargetConsumer(to) - const events = await h.getEvents(oc) - const mockRequestId = tx.receipt.rawLogs[0].data - const requestId = events[0].args.requestId - assert.notEqual(mockRequestId, requestId) - }) + describe('if the requester tries to create a requestId for another contract', () => { + it('the requesters ID will not match with the oracle contract', async () => { + const tx = await mock.maliciousTargetConsumer(to) + const receipt = await tx.wait() - it('the target requester can still create valid requests', async () => { - requester = await BasicConsumer.new( - link.address, - oc.address, - specId, - ) - await link.transfer(requester.address, paymentAmount) - await mock.maliciousTargetConsumer(requester.address) - await requester.requestEthereumPrice('USD') - }) - }, - ) + const mockRequestId = receipt.logs![0].data + const requestId = (receipt.events![0].args! as any).requestId + assert.notEqual(mockRequestId, requestId) + }) + + it('the target requester can still create valid requests', async () => { + requester = await basicConsumerFactory + .connect(roles.defaultAccount) + .deploy(link.address, oc.address, specId) + await link.transfer(requester.address, paymentAmount) + await mock.maliciousTargetConsumer(requester.address) + await requester.requestEthereumPrice('USD') + }) + }) }) it('does not allow recursive calls of onTokenTransfer', async () => { - const requestPayload = h.requestDataBytes(specId, to, fHash, 'id', '') + const requestPayload = h.requestDataBytes(specId, to, fHash, 0, '0x0') - const ottSelector = h.functionSelector( - 'onTokenTransfer(address,uint256,bytes)', - ) + const ottSelector = + oracleFactory.interface.functions.onTokenTransfer.sighash const header = '000000000000000000000000c5fdf4076b8f3a5357c5e395ab970b5b54098fef' + // to '0000000000000000000000000000000000000000000000000000000000000539' + // amount @@ -184,24 +201,27 @@ contract('Oracle', () => { }) describe('#oracleRequest', () => { - context('when called through the LINK token', () => { + describe('when called through the LINK token', () => { const paid = 100 - let log, tx + let log: ethers.providers.Log + let receipt: ethers.providers.TransactionReceipt beforeEach(async () => { - const args = h.requestDataBytes(specId, to, fHash, 1, '') - tx = await h.requestDataFrom(oc, link, paid, args) - assert.equal(3, tx.receipt.rawLogs.length) + const args = h.requestDataBytes(specId, to, fHash, 1, '0x0') + const tx = await h.requestDataFrom(oc, link, paid, args) + receipt = await tx.wait() + assert.equal(3, receipt.logs!.length) - log = tx.receipt.rawLogs[2] + log = receipt.logs![2] }) it('logs an event', async () => { assert.equal(oc.address, log.address) - assert.equal(specId, log.topics[1]) - const req = h.decodeRunRequest(tx.receipt.rawLogs[2]) - assert.equal(roles.defaultAccount.toLowerCase(), req.requester) + assert.equal(log.topics[1], specId) + + const req = h.decodeRunRequest(receipt.logs![2]) + assert.equal(roles.defaultAccount.address, req.requester) assertBigNum(paid, req.payment) }) @@ -213,34 +233,29 @@ contract('Oracle', () => { }) it('does not allow the same requestId to be used twice', async () => { - const args2 = h.requestDataBytes(specId, to, fHash, 1, '') + const args2 = h.requestDataBytes(specId, to, fHash, 1, '0x0') await h.assertActionThrows(async () => { await h.requestDataFrom(oc, link, paid, args2) }) }) - context( - 'when called with a payload less than 2 EVM words + function selector', - () => { - const funcSelector = h.functionSelector( - 'oracleRequest(address,uint256,bytes32,address,bytes4,uint256,uint256,bytes)', - ) - const maliciousData = - funcSelector + - '0000000000000000000000000000000000000000000000000000000000000000000' + describe('when called with a payload less than 2 EVM words + function selector', () => { + const funcSelector = + oracleFactory.interface.functions.oracleRequest.sighash + const maliciousData = + funcSelector + + '0000000000000000000000000000000000000000000000000000000000000000000' - it('throws an error', async () => { - await h.assertActionThrows(async () => { - await h.requestDataFrom(oc, link, paid, maliciousData) - }) + it('throws an error', async () => { + await h.assertActionThrows(async () => { + await h.requestDataFrom(oc, link, paid, maliciousData) }) - }, - ) + }) + }) - context('when called with a payload between 3 and 9 EVM words', () => { - const funcSelector = h.functionSelector( - 'oracleRequest(address,uint256,bytes32,address,bytes4,uint256,uint256,bytes)', - ) + describe('when called with a payload between 3 and 9 EVM words', () => { + const funcSelector = + oracleFactory.interface.functions.oracleRequest.sighash const maliciousData = funcSelector + '000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001' @@ -253,20 +268,21 @@ contract('Oracle', () => { }) }) - context('when not called through the LINK token', () => { + describe('when not called through the LINK token', () => { it('reverts', async () => { await h.assertActionThrows(async () => { - await oc.oracleRequest( - '0x0000000000000000000000000000000000000000', - 0, - specId, - to, - fHash, - 1, - 1, - '0x', - { from: roles.oracleNode }, - ) + await oc + .connect(roles.oracleNode) + .oracleRequest( + '0x0000000000000000000000000000000000000000', + 0, + specId, + to, + fHash, + 1, + 1, + '0x', + ) }) }) }) @@ -274,357 +290,425 @@ contract('Oracle', () => { describe('#fulfillOracleRequest', () => { const response = 'Hi Mom!' - let mock, request + let maliciousRequester: Instance + let basicConsumer: Instance + let maliciousConsumer: Instance + let request: ReturnType - context('cooperative consumer', () => { + describe('cooperative consumer', () => { beforeEach(async () => { - mock = await BasicConsumer.new(link.address, oc.address, specId) - const paymentAmount = h.toWei(1) - await link.transfer(mock.address, paymentAmount) + basicConsumer = await basicConsumerFactory + .connect(roles.defaultAccount) + .deploy(link.address, oc.address, specId) + const paymentAmount = h.toWei('1') + await link.transfer(basicConsumer.address, paymentAmount) const currency = 'USD' - const tx = await mock.requestEthereumPrice(currency) - request = h.decodeRunRequest(tx.receipt.rawLogs[3]) + const tx = await basicConsumer.requestEthereumPrice(currency) + const receipt = await tx.wait() + request = h.decodeRunRequest(receipt.logs![3]) }) - context('when called by an unauthorized node', () => { + describe('when called by an unauthorized node', () => { beforeEach(async () => { - assert.equal(false, await oc.getAuthorizationStatus(roles.stranger)) + assert.equal( + false, + await oc.getAuthorizationStatus(roles.stranger.address), + ) }) it('raises an error', async () => { await h.assertActionThrows(async () => { - await h.fulfillOracleRequest(oc, request, response, { - from: roles.stranger, - }) + await h.fulfillOracleRequest( + oc.connect(roles.stranger), + request, + response, + ) }) }) }) - context('when called by an authorized node', () => { + describe('when called by an authorized node', () => { it('raises an error if the request ID does not exist', async () => { - request.id = '0xdeadbeef' + request.id = ethers.utils.formatBytes32String('DOESNOTEXIST') await h.assertActionThrows(async () => { - await h.fulfillOracleRequest(oc, request, response, { - from: roles.oracleNode, - }) + await h.fulfillOracleRequest( + oc.connect(roles.oracleNode), + request, + response, + ) }) }) it('sets the value on the requested contract', async () => { - await h.fulfillOracleRequest(oc, request, response, { - from: roles.oracleNode, - }) + await h.fulfillOracleRequest( + oc.connect(roles.oracleNode), + request, + response, + ) - const currentValue = await mock.currentPrice.call() - assert.equal(response, h.toUtf8(currentValue)) + const currentValue = await basicConsumer.currentPrice() + assert.equal(response, ethers.utils.parseBytes32String(currentValue)) }) it('does not allow a request to be fulfilled twice', async () => { const response2 = response + ' && Hello World!!' - await h.fulfillOracleRequest(oc, request, response, { - from: roles.oracleNode, - }) + await h.fulfillOracleRequest( + oc.connect(roles.oracleNode), + request, + response, + ) await h.assertActionThrows(async () => { - await h.fulfillOracleRequest(oc, request, response2, { - from: roles.oracleNode, - }) + await h.fulfillOracleRequest( + oc.connect(roles.oracleNode), + request, + response2, + ) }) - const currentValue = await mock.currentPrice.call() - assert.equal(response, h.toUtf8(currentValue)) + const currentValue = await basicConsumer.currentPrice() + assert.equal(response, ethers.utils.parseBytes32String(currentValue)) }) }) - context('when the oracle does not provide enough gas', () => { + describe('when the oracle does not provide enough gas', () => { // if updating this defaultGasLimit, be sure it matches with the // defaultGasLimit specified in store/tx_manager.go const defaultGasLimit = 500000 beforeEach(async () => { - assertBigNum(0, await oc.withdrawable.call()) + assertBigNum(0, await oc.withdrawable()) }) it('does not allow the oracle to withdraw the payment', async () => { await h.assertActionThrows(async () => { - await h.fulfillOracleRequest(oc, request, response, { - from: roles.oracleNode, - gas: 70000, - }) + await h.fulfillOracleRequest( + oc.connect(roles.oracleNode), + request, + response, + { + gasLimit: 70000, + }, + ) }) - assertBigNum(0, await oc.withdrawable.call()) + assertBigNum(0, await oc.withdrawable()) }) it(`${defaultGasLimit} is enough to pass the gas requirement`, async () => { - await h.fulfillOracleRequest(oc, request, response, { - from: roles.oracleNode, - gas: defaultGasLimit, - }) + await h.fulfillOracleRequest( + oc.connect(roles.oracleNode), + request, + response, + { + gasLimit: defaultGasLimit, + }, + ) - assertBigNum(request.payment, await oc.withdrawable.call()) + assertBigNum(request.payment, await oc.withdrawable()) }) }) }) - context('with a malicious requester', () => { + describe('with a malicious requester', () => { beforeEach(async () => { - const paymentAmount = h.toWei(1) - mock = await MaliciousRequester.new(link.address, oc.address) - await link.transfer(mock.address, paymentAmount) + const paymentAmount = h.toWei('1') + maliciousRequester = await maliciousRequesterFactory + .connect(roles.defaultAccount) + .deploy(link.address, oc.address) + await link.transfer(maliciousRequester.address, paymentAmount) }) it('cannot cancel before the expiration', async () => { await h.assertActionThrows(async () => { - await mock.maliciousRequestCancel( + await maliciousRequester.maliciousRequestCancel( specId, - h.toHex('doesNothing(bytes32,bytes32)'), + ethers.utils.toUtf8Bytes('doesNothing(bytes32,bytes32)'), ) }) }) it('cannot call functions on the LINK token through callbacks', async () => { await h.assertActionThrows(async () => { - await mock.request( + await maliciousRequester.request( specId, link.address, - h.toHex('transfer(address,uint256)'), + ethers.utils.toUtf8Bytes('transfer(address,uint256)'), ) }) }) - context('requester lies about amount of LINK sent', () => { + describe('requester lies about amount of LINK sent', () => { it('the oracle uses the amount of LINK actually paid', async () => { - const tx = await mock.maliciousPrice(specId) - const req = h.decodeRunRequest(tx.receipt.rawLogs[3]) + const tx = await maliciousRequester.maliciousPrice(specId) + const receipt = await tx.wait() + const req = h.decodeRunRequest(receipt.logs![3]) - assert(h.toWei(1).eq(h.bigNum(req.payment))) + assert(h.toWei('1').eq(req.payment)) }) }) }) - context('with a malicious consumer', () => { - const paymentAmount = h.toWei(1) + describe('with a malicious consumer', () => { + const paymentAmount = h.toWei('1') beforeEach(async () => { - mock = await MaliciousConsumer.new(link.address, oc.address) - await link.transfer(mock.address, paymentAmount) + maliciousConsumer = await maliciousConsumerFactory + .connect(roles.defaultAccount) + .deploy(link.address, oc.address) + await link.transfer(maliciousConsumer.address, paymentAmount) }) - context('fails during fulfillment', () => { + describe('fails during fulfillment', () => { beforeEach(async () => { - const tx = await mock.requestData( + const tx = await maliciousConsumer.requestData( specId, - h.toHex('assertFail(bytes32,bytes32)'), + ethers.utils.toUtf8Bytes('assertFail(bytes32,bytes32)'), ) - request = h.decodeRunRequest(tx.receipt.rawLogs[3]) + const receipt = await tx.wait() + request = h.decodeRunRequest(receipt.logs![3]) }) it('allows the oracle node to receive their payment', async () => { - await h.fulfillOracleRequest(oc, request, response, { - from: roles.oracleNode, - }) + await h.fulfillOracleRequest( + oc.connect(roles.oracleNode), + request, + response, + ) - const balance = await link.balanceOf.call(roles.oracleNode) - assertBigNum(balance, h.bigNum(0)) + const balance = await link.balanceOf(roles.oracleNode.address) + assertBigNum(balance, 0) - await withdraw(roles.oracleNode, paymentAmount, { - from: roles.defaultAccount, - }) - const newBalance = await link.balanceOf.call(roles.oracleNode) + await oc + .connect(roles.defaultAccount) + .withdraw(roles.oracleNode.address, paymentAmount) + + const newBalance = await link.balanceOf(roles.oracleNode.address) assertBigNum(paymentAmount, newBalance) }) it("can't fulfill the data again", async () => { const response2 = 'hack the planet 102' - await h.fulfillOracleRequest(oc, request, response, { - from: roles.oracleNode, - }) + await h.fulfillOracleRequest( + oc.connect(roles.oracleNode), + request, + response, + ) await h.assertActionThrows(async () => { - await h.fulfillOracleRequest(oc, request, response2, { - from: roles.oracleNode, - }) + await h.fulfillOracleRequest( + oc.connect(roles.oracleNode), + request, + response2, + ) }) }) }) - context('calls selfdestruct', () => { + describe('calls selfdestruct', () => { beforeEach(async () => { - const tx = await mock.requestData( + const tx = await maliciousConsumer.requestData( specId, - h.toHex('doesNothing(bytes32,bytes32)'), + ethers.utils.toUtf8Bytes('doesNothing(bytes32,bytes32)'), ) - request = h.decodeRunRequest(tx.receipt.rawLogs[3]) - await mock.remove() + const receipt = await tx.wait() + request = h.decodeRunRequest(receipt.logs![3]) + await maliciousConsumer.remove() }) it('allows the oracle node to receive their payment', async () => { - await h.fulfillOracleRequest(oc, request, response, { - from: roles.oracleNode, - }) + await h.fulfillOracleRequest( + oc.connect(roles.oracleNode), + request, + response, + ) - const balance = await link.balanceOf.call(roles.oracleNode) - assertBigNum(balance, h.bigNum(0)) + const balance = await link.balanceOf(roles.oracleNode.address) + assertBigNum(balance, 0) - await withdraw(roles.oracleNode, paymentAmount, { - from: roles.defaultAccount, - }) - const newBalance = await link.balanceOf.call(roles.oracleNode) + await oc + .connect(roles.defaultAccount) + .withdraw(roles.oracleNode.address, paymentAmount) + const newBalance = await link.balanceOf(roles.oracleNode.address) assertBigNum(paymentAmount, newBalance) }) }) - context('request is canceled during fulfillment', () => { + describe('request is canceled during fulfillment', () => { beforeEach(async () => { - const tx = await mock.requestData( + const tx = await maliciousConsumer.requestData( specId, - h.toHex('cancelRequestOnFulfill(bytes32,bytes32)'), + ethers.utils.toUtf8Bytes('cancelRequestOnFulfill(bytes32,bytes32)'), ) - request = h.decodeRunRequest(tx.receipt.rawLogs[3]) + const receipt = await tx.wait() + request = h.decodeRunRequest(receipt.logs![3]) - assertBigNum(0, await link.balanceOf.call(mock.address)) + assertBigNum(0, await link.balanceOf(maliciousConsumer.address)) }) it('allows the oracle node to receive their payment', async () => { - await h.fulfillOracleRequest(oc, request, response, { - from: roles.oracleNode, - }) + await h.fulfillOracleRequest( + oc.connect(roles.oracleNode), + request, + response, + ) - const mockBalance = await link.balanceOf.call(mock.address) - assertBigNum(mockBalance, h.bigNum(0)) + const mockBalance = await link.balanceOf(maliciousConsumer.address) + assertBigNum(mockBalance, 0) - const balance = await link.balanceOf.call(roles.oracleNode) - assertBigNum(balance, h.bigNum(0)) + const balance = await link.balanceOf(roles.oracleNode.address) + assertBigNum(balance, 0) - await withdraw(roles.oracleNode, paymentAmount, { - from: roles.defaultAccount, - }) - const newBalance = await link.balanceOf.call(roles.oracleNode) + await oc + .connect(roles.defaultAccount) + .withdraw(roles.oracleNode.address, paymentAmount) + const newBalance = await link.balanceOf(roles.oracleNode.address) assertBigNum(paymentAmount, newBalance) }) it("can't fulfill the data again", async () => { const response2 = 'hack the planet 102' - await h.fulfillOracleRequest(oc, request, response, { - from: roles.oracleNode, - }) + await h.fulfillOracleRequest( + oc.connect(roles.oracleNode), + request, + response, + ) await h.assertActionThrows(async () => { - await h.fulfillOracleRequest(oc, request, response2, { - from: roles.oracleNode, - }) + await h.fulfillOracleRequest( + oc.connect(roles.oracleNode), + request, + response2, + ) }) }) }) - context('tries to steal funds from node', () => { + describe('tries to steal funds from node', () => { it('is not successful with call', async () => { - const tx = await mock.requestData( + const tx = await maliciousConsumer.requestData( specId, - h.toHex('stealEthCall(bytes32,bytes32)'), + ethers.utils.toUtf8Bytes('stealEthCall(bytes32,bytes32)'), ) - request = h.decodeRunRequest(tx.receipt.rawLogs[3]) + const receipt = await tx.wait() + request = h.decodeRunRequest(receipt.logs![3]) - await h.fulfillOracleRequest(oc, request, response, { - from: roles.oracleNode, - }) + await h.fulfillOracleRequest( + oc.connect(roles.oracleNode), + request, + response, + ) - assertBigNum(0, await web3.eth.getBalance(mock.address)) + assertBigNum(0, await provider.getBalance(maliciousConsumer.address)) }) it('is not successful with send', async () => { - const tx = await mock.requestData( + const tx = await maliciousConsumer.requestData( specId, - h.toHex('stealEthSend(bytes32,bytes32)'), + ethers.utils.toUtf8Bytes('stealEthSend(bytes32,bytes32)'), ) - request = h.decodeRunRequest(tx.receipt.rawLogs[3]) + const receipt = await tx.wait() + request = h.decodeRunRequest(receipt.logs![3]) - await h.fulfillOracleRequest(oc, request, response, { - from: roles.oracleNode, - }) - assertBigNum(0, await web3.eth.getBalance(mock.address)) + await h.fulfillOracleRequest( + oc.connect(roles.oracleNode), + request, + response, + ) + assertBigNum(0, await provider.getBalance(maliciousConsumer.address)) }) it('is not successful with transfer', async () => { - const tx = await mock.requestData( + const tx = await maliciousConsumer.requestData( specId, - h.toHex('stealEthTransfer(bytes32,bytes32)'), + ethers.utils.toUtf8Bytes('stealEthTransfer(bytes32,bytes32)'), ) - request = h.decodeRunRequest(tx.receipt.rawLogs[3]) + const receipt = await tx.wait() + request = h.decodeRunRequest(receipt.logs![3]) - await h.fulfillOracleRequest(oc, request, response, { - from: roles.oracleNode, - }) - assertBigNum(0, await web3.eth.getBalance(mock.address)) + await h.fulfillOracleRequest( + oc.connect(roles.oracleNode), + request, + response, + ) + assertBigNum(0, await provider.getBalance(maliciousConsumer.address)) }) }) }) }) describe('#withdraw', () => { - context('without reserving funds via oracleRequest', () => { + describe('without reserving funds via oracleRequest', () => { it('does nothing', async () => { - let balance = await link.balanceOf(roles.oracleNode) - assert.equal(0, balance) + let balance = await link.balanceOf(roles.oracleNode.address) + assert.equal(0, balance.toNumber()) await h.assertActionThrows(async () => { - await withdraw(roles.oracleNode, h.toWei(1), { - from: roles.defaultAccount, - }) + await oc + .connect(roles.defaultAccount) + .withdraw(roles.oracleNode.address, h.toWei('1')) }) - balance = await link.balanceOf(roles.oracleNode) - assert.equal(0, balance) + balance = await link.balanceOf(roles.oracleNode.address) + assert.equal(0, balance.toNumber()) }) }) - context('reserving funds via oracleRequest', () => { + describe('reserving funds via oracleRequest', () => { const payment = 15 - let request + let request: ReturnType beforeEach(async () => { - const mock = await GetterSetter.new() - const args = h.requestDataBytes(specId, mock.address, fHash, 'id', '') + const mock = await getterSetterFactory + .connect(roles.defaultAccount) + .deploy() + const args = h.requestDataBytes(specId, mock.address, fHash, 0, '0x0') const tx = await h.requestDataFrom(oc, link, payment, args) - assert.equal(3, tx.receipt.rawLogs.length) - request = h.decodeRunRequest(tx.receipt.rawLogs[2]) + const receipt = await tx.wait() + assert.equal(3, receipt.logs!.length) + request = h.decodeRunRequest(receipt.logs![2]) }) - context('but not freeing funds w fulfillOracleRequest', () => { + describe('but not freeing funds w fulfillOracleRequest', () => { it('does not transfer funds', async () => { await h.assertActionThrows(async () => { - await withdraw(roles.oracleNode, payment, { - from: roles.defaultAccount, - }) + await oc + .connect(roles.defaultAccount) + .withdraw(roles.oracleNode.address, payment) }) - const balance = await link.balanceOf(roles.oracleNode) - assert.equal(0, balance) + const balance = await link.balanceOf(roles.oracleNode.address) + assert.equal(0, balance.toNumber()) }) }) - context('and freeing funds', () => { + describe('and freeing funds', () => { beforeEach(async () => { - await h.fulfillOracleRequest(oc, request, 'Hello World!', { - from: roles.oracleNode, - }) + await h.fulfillOracleRequest( + oc.connect(roles.oracleNode), + request, + 'Hello World!', + ) }) it('does not allow input greater than the balance', async () => { const originalOracleBalance = await link.balanceOf(oc.address) - const originalStrangerBalance = await link.balanceOf(roles.stranger) + const originalStrangerBalance = await link.balanceOf( + roles.stranger.address, + ) const withdrawalAmount = payment + 1 assert.isAbove(withdrawalAmount, originalOracleBalance.toNumber()) await h.assertActionThrows(async () => { - await withdraw(roles.stranger, withdrawalAmount, { - from: roles.defaultAccount, - }) + await oc + .connect(roles.defaultAccount) + .withdraw(roles.stranger.address, withdrawalAmount) }) const newOracleBalance = await link.balanceOf(oc.address) - const newStrangerBalance = await link.balanceOf(roles.stranger) + const newStrangerBalance = await link.balanceOf( + roles.stranger.address, + ) assert.equal( originalOracleBalance.toNumber(), @@ -639,129 +723,158 @@ contract('Oracle', () => { it('allows transfer of partial balance by owner to specified address', async () => { const partialAmount = 6 const difference = payment - partialAmount - await withdraw(roles.stranger, partialAmount, { - from: roles.defaultAccount, - }) - const strangerBalance = await link.balanceOf(roles.stranger) + await oc + .connect(roles.defaultAccount) + .withdraw(roles.stranger.address, partialAmount) + const strangerBalance = await link.balanceOf(roles.stranger.address) const oracleBalance = await link.balanceOf(oc.address) - assert.equal(partialAmount, strangerBalance) - assert.equal(difference, oracleBalance) + assert.equal(partialAmount, strangerBalance.toNumber()) + assert.equal(difference, oracleBalance.toNumber()) }) it('allows transfer of entire balance by owner to specified address', async () => { - await withdraw(roles.stranger, payment, { - from: roles.defaultAccount, - }) - const balance = await link.balanceOf(roles.stranger) - assert.equal(payment, balance) + await oc + .connect(roles.defaultAccount) + .withdraw(roles.stranger.address, payment) + const balance = await link.balanceOf(roles.stranger.address) + assert.equal(payment, balance.toNumber()) }) it('does not allow a transfer of funds by non-owner', async () => { await h.assertActionThrows(async () => { - await withdraw(roles.stranger, payment, { from: roles.stranger }) + await oc + .connect(roles.stranger) + .withdraw(roles.stranger.address, payment) }) - const balance = await link.balanceOf(roles.stranger) - assert.equal(0, balance) + const balance = await link.balanceOf(roles.stranger.address) + assert.isTrue(ethers.constants.Zero.eq(balance)) }) }) }) }) describe('#withdrawable', () => { - let request + let request: ReturnType beforeEach(async () => { - const amount = h.toWei(1, 'ether').toString() - const mock = await GetterSetter.new() - const args = h.requestDataBytes(specId, mock.address, fHash, 'id', '') + const amount = h.toWei('1') + const mock = await getterSetterFactory + .connect(roles.defaultAccount) + .deploy() + const args = h.requestDataBytes(specId, mock.address, fHash, 0, '0x0') const tx = await h.requestDataFrom(oc, link, amount, args) - assert.equal(3, tx.receipt.rawLogs.length) - request = h.decodeRunRequest(tx.receipt.rawLogs[2]) - await h.fulfillOracleRequest(oc, request, 'Hello World!', { - from: roles.oracleNode, - }) + const receipt = await tx.wait() + assert.equal(3, receipt.logs!.length) + request = h.decodeRunRequest(receipt.logs![2]) + await h.fulfillOracleRequest( + oc.connect(roles.oracleNode), + request, + 'Hello World!', + ) }) it('returns the correct value', async () => { - const withdrawAmount = await oc.withdrawable.call() + const withdrawAmount = await oc.withdrawable() assertBigNum(withdrawAmount, request.payment) }) }) describe('#cancelOracleRequest', () => { - context('with no pending requests', () => { + describe('with no pending requests', () => { it('fails', async () => { - const fakeRequest = { - id: h.toHex(1337), - payment: 0, - callbackFunc: h.functionSelector('requestedBytes32(bytes32,bytes32)'), - expiration: 999999999999, + const fakeRequest: h.RunRequest = { + id: ethers.utils.formatBytes32String('1337'), + payment: '0', + callbackFunc: + getterSetterFactory.interface.functions.requestedBytes32.sighash, + expiration: '999999999999', + + callbackAddr: '', + data: Buffer.from(''), + dataVersion: 0, + jobId: '', + requester: '', + topic: '', } - await h.increaseTime5Minutes() + await h.increaseTime5Minutes(provider) await h.assertActionThrows(async () => { - await h.cancelOracleRequest(oc, fakeRequest, { from: roles.stranger }) + await h.cancelOracleRequest(oc.connect(roles.stranger), fakeRequest) }) }) }) - context('with a pending request', () => { + describe('with a pending request', () => { const startingBalance = 100 - let request, tx + let request: ReturnType + let receipt: ethers.providers.TransactionReceipt beforeEach(async () => { const requestAmount = 20 - await link.transfer(roles.consumer, startingBalance) + await link.transfer(roles.consumer.address, startingBalance) - const args = h.requestDataBytes(specId, roles.consumer, fHash, 1, '') - tx = await link.transferAndCall(oc.address, requestAmount, args, { - from: roles.consumer, - }) - assert.equal(3, tx.receipt.rawLogs.length) - request = h.decodeRunRequest(tx.receipt.rawLogs[2]) + const args = h.requestDataBytes( + specId, + roles.consumer.address, + fHash, + 1, + '0x0', + ) + const tx = await link + .connect(roles.consumer) + .transferAndCall(oc.address, requestAmount, args) + receipt = await tx.wait() + + assert.equal(3, receipt.logs!.length) + request = h.decodeRunRequest(receipt.logs![2]) }) it('has correct initial balances', async () => { const oracleBalance = await link.balanceOf(oc.address) assertBigNum(request.payment, oracleBalance) - const consumerAmount = await link.balanceOf(roles.consumer) - assert.equal(startingBalance - request.payment, consumerAmount) + const consumerAmount = await link.balanceOf(roles.consumer.address) + assert.equal( + startingBalance - Number(request.payment), + consumerAmount.toNumber(), + ) }) - context('from a stranger', () => { + describe('from a stranger', () => { it('fails', async () => { await h.assertActionThrows(async () => { - await h.cancelOracleRequest(oc, request, { from: roles.consumer }) + await h.cancelOracleRequest(oc.connect(roles.consumer), request) }) }) }) - context('from the requester', () => { + describe('from the requester', () => { it('refunds the correct amount', async () => { - await h.increaseTime5Minutes() - await h.cancelOracleRequest(oc, request, { from: roles.consumer }) - const balance = await link.balanceOf(roles.consumer) - assert.equal(startingBalance, balance) // 100 + await h.increaseTime5Minutes(provider) + await h.cancelOracleRequest(oc.connect(roles.consumer), request) + const balance = await link.balanceOf(roles.consumer.address) + assert.equal(startingBalance, balance.toNumber()) // 100 }) it('triggers a cancellation event', async () => { - await h.increaseTime5Minutes() - const tx = await h.cancelOracleRequest(oc, request, { - from: roles.consumer, - }) + await h.increaseTime5Minutes(provider) + const tx = await h.cancelOracleRequest( + oc.connect(roles.consumer), + request, + ) + const receipt = await tx.wait() - assert.equal(tx.receipt.rawLogs.length, 2) - assert.equal(request.id, tx.receipt.rawLogs[0].topics[1]) + assert.equal(receipt.logs!.length, 2) + assert.equal(request.id, receipt.logs![0].topics[1]) }) it('fails when called twice', async () => { - await h.increaseTime5Minutes() - await h.cancelOracleRequest(oc, request, { from: roles.consumer }) + await h.increaseTime5Minutes(provider) + await h.cancelOracleRequest(oc.connect(roles.consumer), request) await h.assertActionThrows(async () => { - await h.cancelOracleRequest(oc, request, { from: roles.consumer }) + await h.cancelOracleRequest(oc.connect(roles.consumer), request) }) }) }) From 93c8bb580453724c81114298d1f840dc744f892b Mon Sep 17 00:00:00 2001 From: henrynguyen5 <6404866+HenryNguyen5@users.noreply.github.com> Date: Thu, 24 Oct 2019 18:07:39 -0400 Subject: [PATCH 010/199] Use ethers to encode oracleRequest ABI call --- evm/src/helpersV2.ts | 27 +++++++-------------------- 1 file changed, 7 insertions(+), 20 deletions(-) diff --git a/evm/src/helpersV2.ts b/evm/src/helpersV2.ts index ccea6ed0b35..0aab67cd3a0 100644 --- a/evm/src/helpersV2.ts +++ b/evm/src/helpersV2.ts @@ -7,6 +7,7 @@ import { LinkToken } from './generated/LinkToken' import { makeDebug } from './debug' import cbor from 'cbor' import { EmptyOracle } from './generated/EmptyOracle' +import { OracleFactory } from './generated/OracleFactory' const debug = makeDebug('helpers') @@ -340,20 +341,11 @@ export function requestDataBytes( to: string, fHash: string, nonce: number, - data: string, -): any { - const types = [ - 'address', - 'uint256', - 'bytes32', - 'address', - 'bytes4', - 'uint256', - 'uint256', - 'bytes', - ] + dataBytes: string, +): string { + const ocFactory = new OracleFactory() - const values = [ + return ocFactory.interface.functions.oracleRequest.encode([ ethers.constants.AddressZero, 0, specId, @@ -361,13 +353,8 @@ export function requestDataBytes( fHash, nonce, 1, - data, - ] - const encoded = ethers.utils.defaultAbiCoder.encode(types, values) - const funcSelector = functionSelector( - 'oracleRequest(address,uint256,bytes32,address,bytes4,uint256,uint256,bytes)', - ) - return `${funcSelector}${stripHexPrefix(encoded)}` + dataBytes, + ]) } // link param must be from linkContract(), if amount is a BN From f6164e75d9decdb39a3d160163d5e1a83624ff82 Mon Sep 17 00:00:00 2001 From: henrynguyen5 <6404866+HenryNguyen5@users.noreply.github.com> Date: Thu, 24 Oct 2019 18:09:26 -0400 Subject: [PATCH 011/199] Provide cancelOracleRequest helper function --- evm/src/helpersV2.ts | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/evm/src/helpersV2.ts b/evm/src/helpersV2.ts index 0aab67cd3a0..bdd1572b141 100644 --- a/evm/src/helpersV2.ts +++ b/evm/src/helpersV2.ts @@ -298,11 +298,13 @@ export function keccak( return utils.keccak256(...args) } +type TxOptions = Omit + export async function fulfillOracleRequest( oracleContract: Oracle | EmptyOracle, runRequest: RunRequest, response: string, - options: Omit = { + options: TxOptions = { gasLimit: 1000000, // FIXME: incorrect gas estimation }, ): ReturnType { @@ -327,13 +329,18 @@ export async function fulfillOracleRequest( ) } -/** - * The solidity function selector for the given signature - */ -export function functionSelector(signature: string): string { - const fullHash = ethers.utils.id(signature) - assert(fullHash.startsWith('0x')) - return fullHash.slice(0, 2 + 4 * 2) // '0x' + initial 4 bytes, in hex +export async function cancelOracleRequest( + oracleContract: Oracle | EmptyOracle, + request: RunRequest, + options: TxOptions = {}, +): ReturnType { + return oracleContract.cancelOracleRequest( + request.id, + request.payment, + request.callbackFunc, + request.expiration, + options, + ) } export function requestDataBytes( From 1ce88a4a3db6f43568f3028a33d6eb478adc9a5e Mon Sep 17 00:00:00 2001 From: henrynguyen5 <6404866+HenryNguyen5@users.noreply.github.com> Date: Thu, 24 Oct 2019 18:09:52 -0400 Subject: [PATCH 012/199] Make amount parameter more accurate --- evm/src/helpersV2.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/evm/src/helpersV2.ts b/evm/src/helpersV2.ts index bdd1572b141..a179a611c16 100644 --- a/evm/src/helpersV2.ts +++ b/evm/src/helpersV2.ts @@ -368,7 +368,7 @@ export function requestDataBytes( export function requestDataFrom( oc: Oracle, link: LinkToken, - amount: number, + amount: ethers.utils.BigNumberish, args: string, options: Omit = {}, ): ReturnType { From d4e6427dec76d3f1cec624ab9e14310dbc8ed332 Mon Sep 17 00:00:00 2001 From: henrynguyen5 <6404866+HenryNguyen5@users.noreply.github.com> Date: Thu, 31 Oct 2019 18:29:30 -0400 Subject: [PATCH 013/199] Fix eslint command for evm --- evm/.eslintignore | 1 + evm/package.json | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 evm/.eslintignore diff --git a/evm/.eslintignore b/evm/.eslintignore new file mode 100644 index 00000000000..80697f2272d --- /dev/null +++ b/evm/.eslintignore @@ -0,0 +1 @@ +src/generated \ No newline at end of file diff --git a/evm/package.json b/evm/package.json index 601482049e2..3fd7d1f6ab2 100644 --- a/evm/package.json +++ b/evm/package.json @@ -10,7 +10,7 @@ "postbuild": "yarn generate-typings && yarn tsc && cp -f src/generated/*.d.ts dist/src/generated", "build:windows": "truffle.cmd build", "depcheck": "echo 'chainlink' && depcheck --ignore-dirs=build/contracts,v0.5,box || true", - "eslint": "eslint --ext .ts,.js test", + "eslint": "eslint --ext .ts,.js testv2 src", "solhint": "solhint ./contracts/**/*.sol", "lint": "yarn solhint && yarn eslint", "slither": "truffle compile --quiet && slither .", From 058902d4cd7c5c4d6096d7571ccadd57601477ad Mon Sep 17 00:00:00 2001 From: henrynguyen5 <6404866+HenryNguyen5@users.noreply.github.com> Date: Thu, 31 Oct 2019 18:40:44 -0400 Subject: [PATCH 014/199] Remove empty test/ dir scripts from evm --- evm/package.json | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/evm/package.json b/evm/package.json index 3fd7d1f6ab2..cea33272bba 100644 --- a/evm/package.json +++ b/evm/package.json @@ -15,10 +15,8 @@ "lint": "yarn solhint && yarn eslint", "slither": "truffle compile --quiet && slither .", "pretest": "yarn build", - "test:v1": "tsc && truffle test ./dist/test/*.js", - "test:v2": "jest --testTimeout 20000", - "test": "yarn test:v1 && yarn test:v2", - "format": "prettier --write \"{src,test,testv2}/*/**\"", + "test": "jest --testTimeout 20000", + "format": "prettier --write \"{src,testv2}/*/**\"", "prepublishOnly": "yarn build && yarn lint && yarn test", "setup": "ts-node ./scripts/build", "truffle:migrate:cldev": "truffle migrate --network cldev" From 3aae76cc491957f466ddfc9f2cba6668777ee0ea Mon Sep 17 00:00:00 2001 From: Alex Kwiatkowski Date: Thu, 31 Oct 2019 14:27:33 -0700 Subject: [PATCH 015/199] Store log artifacts on CircleCI --- .circleci/config.yml | 15 ++++++++++++++- integration/common | 34 +++++++++++++++++----------------- integration/logs/.gitkeep | 0 integration/runlog_test | 6 +++--- tools/ci/ethereum_test | 2 +- 5 files changed, 35 insertions(+), 22 deletions(-) create mode 100644 integration/logs/.gitkeep diff --git a/.circleci/config.yml b/.circleci/config.yml index 9374ce7ac09..a5e00543c14 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -141,6 +141,8 @@ jobs: - run: ./tools/ci/ethereum_test - store_artifacts: path: ./integration/cypress/screenshots + - store_artifacts: + path: ./integration/logs parity-postgres: resource_class: xlarge docker: @@ -177,6 +179,8 @@ jobs: - run: ./tools/ci/ethereum_test parity - store_artifacts: path: ./integration/cypress/screenshots + - store_artifacts: + path: ./integration/logs truffle: docker: - image: smartcontract/builder:1.0.25 @@ -194,6 +198,8 @@ jobs: - /usr/local/share/.cache/yarn - run: pip3 install -r requirements.txt - run: ./tools/ci/truffle_test + - store_artifacts: + path: ./integration/logs json-api-client: docker: - image: smartcontract/builder:1.0.25 @@ -210,6 +216,8 @@ jobs: paths: - /usr/local/share/.cache/yarn - run: yarn workspace @chainlink/json-api-client + - store_artifacts: + path: ./integration/logs operator-ui: docker: - image: smartcontract/builder:1.0.25 @@ -227,6 +235,8 @@ jobs: - /usr/local/share/.cache/yarn - run: ./tools/ci/init_gcloud - run: ./tools/ci/operator_ui_test + - store_artifacts: + path: ./integration/logs explorer: working_directory: ~/chainlink docker: @@ -266,6 +276,8 @@ jobs: - run: name: Run E2E Tests command: yarn workspace @chainlink/explorer-client run build && yarn workspace @chainlink/explorer run test-ci:e2e:silent + - store_artifacts: + path: ./integration/logs forks: machine: image: ubuntu-1604:201903-01 @@ -277,7 +289,8 @@ jobs: name: Install Yarn command: npm install -g yarn - run: ./tools/ci/forks_test - + - store_artifacts: + path: ./integration/logs build-publish-explorer: machine: true steps: diff --git a/integration/common b/integration/common index 40338a8e7d4..7f28c10e5e5 100644 --- a/integration/common +++ b/integration/common @@ -43,7 +43,7 @@ launch_gethnet() { printf -- "[\033[31mstopped\033[0m]\n" title "Starting geth..." - $SRCROOT/tools/bin/gethnet &>$SRCROOT/integration/gethnet.log & + $SRCROOT/tools/bin/gethnet &>$SRCROOT/integration/logs/gethnet.log & waitForResponse $ETH_HTTP_URL title "Geth is running." } @@ -57,7 +57,7 @@ launch_parity() { printf -- "[\033[31mstopped\033[0m]\n" title "Starting parity..." - $SRCROOT/tools/bin/devnet &>$SRCROOT/integration/devnet.log & + $SRCROOT/tools/bin/devnet &>$SRCROOT/integration/logs/devnet.log & waitForResponse $ETH_HTTP_URL title "Parity is running." } @@ -68,7 +68,7 @@ install_chainlink() { fi title "Making chainlink..." - make install &>$SRCROOT/integration/make.log + make install &>$SRCROOT/integration/logs/make.log } launch_chainlink() { @@ -88,15 +88,15 @@ launch_chainlink() { trap "rm -rf $clroot" EXIT HUP TERM INT cp $SRCROOT/tools/clroot/{password.txt,apicredentials} $clroot/ echo running chainlink from ${clroot} - chainlink node start -d -p $clroot/password.txt -a $clroot/apicredentials &>$SRCROOT/integration/chainlink.log & + chainlink node start -d -p $clroot/password.txt -a $clroot/apicredentials &>$SRCROOT/integration/logs/chainlink.log & waitForResponse $chainlink_url title "Chainlink is running." - waitFor "grep 'Unlocked account' '$SRCROOT/integration/chainlink.log'" 10 - export CHAINLINK_NODE_ADDRESS=`cat $SRCROOT/integration/chainlink.log | grep 'Unlocked account' | awk '{print$5}'` + waitFor "grep 'Unlocked account' '$SRCROOT/integration/logs/chainlink.log'" 10 + export CHAINLINK_NODE_ADDRESS=`cat $SRCROOT/integration/logs/chainlink.log | grep 'Unlocked account' | awk '{print$5}'` - yarn workspace @chainlink/integration-scripts fund-address > $SRCROOT/integration/fund_address.log + yarn workspace @chainlink/integration-scripts fund-address > $SRCROOT/integration/logs/fund_address.log } explorer_url="http://127.0.0.1:8080" @@ -110,8 +110,8 @@ launch_explorer() { printf -- "[\033[31mstopped\033[0m]\n" title "Starting explorer..." - yarn workspace @chainlink/explorer run build &>$SRCROOT/integration/explorer-yarn.log - yarn workspace @chainlink/explorer run prod &>$SRCROOT/integration/explorer.log & waitForResponse $explorer_url + yarn workspace @chainlink/explorer run build &>$SRCROOT/integration/logs/explorer-yarn.log + yarn workspace @chainlink/explorer run prod &>$SRCROOT/integration/logs/explorer.log & waitForResponse $explorer_url title "Explorer is running." } @@ -128,7 +128,7 @@ add_clnode_to_explorer() { } setup_scripts() { - yarn --no-progress install &>$SRCROOT/integration/yarn.log + yarn --no-progress install &>$SRCROOT/integration/logs/yarn.log yarn workspace chainlinkv0.5 build yarn workspace chainlink build yarn workspace @chainlink/integration-scripts setup @@ -139,18 +139,18 @@ deploy_contracts() { pushd integration >/dev/null # run migrations - yarn workspace @chainlink/integration-scripts deploy-contracts | tee $SRCROOT/integration/deploy.log - export LINK_TOKEN_ADDRESS=`cat $SRCROOT/integration/deploy.log | grep 'Deployed LinkToken at:' | awk '{print$4}'` - export ORACLE_CONTRACT_ADDRESS=`cat $SRCROOT/integration/deploy.log | grep 'Deployed Oracle at:' | awk '{print$4}'` - export ETH_LOG_ADDRESS=`cat $SRCROOT/integration/deploy.log | grep 'Deployed EthLog at:' | awk '{print$4}'` - export RUN_LOG_ADDRESS=`cat $SRCROOT/integration/deploy.log | grep 'Deployed RunLog at:' | awk '{print$4}'` + yarn workspace @chainlink/integration-scripts deploy-contracts | tee $SRCROOT/integration/logs/deploy.log + export LINK_TOKEN_ADDRESS=`cat $SRCROOT/integration/logs/deploy.log | grep 'Deployed LinkToken at:' | awk '{print$4}'` + export ORACLE_CONTRACT_ADDRESS=`cat $SRCROOT/integration/logs/deploy.log | grep 'Deployed Oracle at:' | awk '{print$4}'` + export ETH_LOG_ADDRESS=`cat $SRCROOT/integration/logs/deploy.log | grep 'Deployed EthLog at:' | awk '{print$4}'` + export RUN_LOG_ADDRESS=`cat $SRCROOT/integration/logs/deploy.log | grep 'Deployed RunLog at:' | awk '{print$4}'` echo "RunLog address: $RUN_LOG_ADDRESS" popd >/dev/null title "Migration complete." } deploy_v05_contracts() { - log_path=$SRCROOT/integration/initiate-service-agreement.log + log_path=$SRCROOT/integration/logs/initiate-service-agreement.log yarn workspace @chainlink/integration-scripts deploy-v0.5-contracts | tee $log_path export LINK_TOKEN_ADDRESS=`cat $log_path | grep 'Deployed LinkToken at:' | awk '{print$4}'` export COORDINATOR_ADDRESS=`cat $log_path | grep 'Deployed Coordinator at:' | awk '{print$4}'` @@ -167,7 +167,7 @@ launch_echo_server() { title "Starting echo server..." pushd integration >/dev/null - yarn workspace @chainlink/integration-scripts start-echo-server "$ECHO_SERVER_PORT" &>$SRCROOT/integration/echo-server.log & + yarn workspace @chainlink/integration-scripts start-echo-server "$ECHO_SERVER_PORT" &>$SRCROOT/integration/logs/echo-server.log & waitForResponse $ECHO_SERVER_URL popd >/dev/null diff --git a/integration/logs/.gitkeep b/integration/logs/.gitkeep new file mode 100644 index 00000000000..e69de29bb2d diff --git a/integration/runlog_test b/integration/runlog_test index 87e1b339c81..b1fc0df6cba 100755 --- a/integration/runlog_test +++ b/integration/runlog_test @@ -12,7 +12,7 @@ fi expected_echo_count=$(expr $(curl -sS "$ECHO_SERVER_URL") + 1) expected_job_count=$(expr $(chainlink -j jobs list | jq length) + 1) -yarn workspace @chainlink/integration-scripts send-runlog-transaction | tee $SRCROOT/integration/send_runlog_transaction.log +yarn workspace @chainlink/integration-scripts send-runlog-transaction | tee $SRCROOT/integration/logs/send_runlog_transaction.log # Check echo count assert "Echo count" "curl -sS $ECHO_SERVER_URL" $expected_echo_count @@ -35,7 +35,7 @@ tx_receiver=$(chainlink -j runs list --jobid $jid | jq '.[].result.data.address' echo "Test sent TX to: $tx_receiver" # Check for the Fullfillment event -yarn workspace @chainlink/integration-scripts count-transaction-events | tee $SRCROOT/integration/send_runlog_transaction.log -tx_event_count=`cat $SRCROOT/integration/send_runlog_transaction.log | grep "Events from $RUN_LOG_ADDRESS in $txid:" | awk '{print$6}'` +yarn workspace @chainlink/integration-scripts count-transaction-events | tee $SRCROOT/integration/logs/send_runlog_transaction.log +tx_event_count=`cat $SRCROOT/integration/logs/send_runlog_transaction.log | grep "Events from $RUN_LOG_ADDRESS in $txid:" | awk '{print$6}'` assert "Transaction Events" "echo $tx_event_count" 2 diff --git a/tools/ci/ethereum_test b/tools/ci/ethereum_test index ce8670af771..df5235de331 100755 --- a/tools/ci/ethereum_test +++ b/tools/ci/ethereum_test @@ -60,7 +60,7 @@ title 'End to end tests.' launch_cypress_job_server set -o pipefail -yarn workspace @chainlink/integration test:cypress | tee $SRCROOT/integration/e2e.tests.log +yarn workspace @chainlink/integration test:cypress | tee $SRCROOT/integration/logs/e2e.tests.log set +o pipefail title 'All tests passed.' From e1b74e723d090abadcfb40ec5a0f95dcc91a953a Mon Sep 17 00:00:00 2001 From: John Barker Date: Thu, 10 Oct 2019 11:36:13 +0900 Subject: [PATCH 016/199] Move RunResult into its own file --- core/store/models/job_run.go | 117 --------------------- core/store/models/run_result.go | 124 ++++++++++++++++++++++ core/store/models/run_result_test.go | 152 +++++++++++++++++++++++++++ 3 files changed, 276 insertions(+), 117 deletions(-) create mode 100644 core/store/models/run_result.go create mode 100644 core/store/models/run_result_test.go diff --git a/core/store/models/job_run.go b/core/store/models/job_run.go index 06db64f34b9..bc0b6108a7e 100644 --- a/core/store/models/job_run.go +++ b/core/store/models/job_run.go @@ -2,14 +2,12 @@ package models import ( "encoding/json" - "errors" "fmt" "time" "github.com/ethereum/go-ethereum/common" clnull "github.com/smartcontractkit/chainlink/core/null" "github.com/smartcontractkit/chainlink/core/store/assets" - "github.com/tidwall/gjson" null "gopkg.in/guregu/null.v3" ) @@ -221,121 +219,6 @@ func (tr *TaskRun) MarkPendingConfirmations() { tr.Result.Status = RunStatusPendingConfirmations } -// RunResult keeps track of the outcome of a TaskRun or JobRun. It stores the -// Data and ErrorMessage, and contains a field to track the status. -type RunResult struct { - ID uint `json:"-" gorm:"primary_key;auto_increment"` - CachedJobRunID *ID `json:"jobRunId" gorm:"-"` - CachedTaskRunID *ID `json:"taskRunId" gorm:"-"` - Data JSON `json:"data" gorm:"type:text"` - Status RunStatus `json:"status"` - ErrorMessage null.String `json:"error"` -} - -func RunResultComplete(resultVal interface{}) RunResult { - var result RunResult - result.CompleteWithResult(resultVal) - return result -} - -func RunResultError(err error) RunResult { - var result RunResult - result.SetError(err) - return result -} - -// CompleteWithResult saves a value to a RunResult and marks it as completed -func (rr *RunResult) CompleteWithResult(val interface{}) { - rr.Status = RunStatusCompleted - rr.ApplyResult(val) -} - -// ApplyResult saves a value to a RunResult with the key result. -func (rr *RunResult) ApplyResult(val interface{}) { - rr.Add("result", val) -} - -// Add adds a key and result to the RunResult's JSON payload. -func (rr *RunResult) Add(key string, result interface{}) { - data, err := rr.Data.Add(key, result) - if err != nil { - rr.SetError(err) - return - } - rr.Data = data -} - -// SetError marks the result as errored and saves the specified error message -func (rr *RunResult) SetError(err error) { - rr.ErrorMessage = null.StringFrom(err.Error()) - rr.Status = RunStatusErrored -} - -// MarkPendingBridge sets the status to pending_bridge -func (rr *RunResult) MarkPendingBridge() { - rr.Status = RunStatusPendingBridge -} - -// MarkPendingConfirmations sets the status to pending_confirmations. -func (rr *RunResult) MarkPendingConfirmations() { - rr.Status = RunStatusPendingConfirmations -} - -// MarkPendingConnection sets the status to pending_connection. -func (rr *RunResult) MarkPendingConnection() { - rr.Status = RunStatusPendingConnection -} - -// Get searches for and returns the JSON at the given path. -func (rr *RunResult) Get(path string) gjson.Result { - return rr.Data.Get(path) -} - -// ResultString returns the string result of the Data JSON field. -func (rr *RunResult) ResultString() (string, error) { - val := rr.Result() - if val.Type != gjson.String { - return "", fmt.Errorf("non string result") - } - return val.String(), nil -} - -// Result returns the result as a gjson object -func (rr *RunResult) Result() gjson.Result { - return rr.Get("result") -} - -// HasError returns true if the ErrorMessage is present. -func (rr *RunResult) HasError() bool { - return rr.ErrorMessage.Valid -} - -// Error returns the string value of the ErrorMessage field. -func (rr *RunResult) Error() string { - return rr.ErrorMessage.String -} - -// GetError returns the error of a RunResult if it is present. -func (rr *RunResult) GetError() error { - if rr.HasError() { - return errors.New(rr.ErrorMessage.ValueOrZero()) - } - return nil -} - -// Merge saves the specified result's data onto the receiving RunResult. The -// input result's data takes preference over the receivers'. -func (rr *RunResult) Merge(in RunResult) error { - var err error - rr.Data, err = rr.Data.Merge(in.Data) - if err != nil { - return err - } - rr.ErrorMessage = in.ErrorMessage - rr.Status = in.Status - return nil -} - // BridgeRunResult handles the parsing of RunResults from external adapters. type BridgeRunResult struct { RunResult diff --git a/core/store/models/run_result.go b/core/store/models/run_result.go new file mode 100644 index 00000000000..d4cbc69de72 --- /dev/null +++ b/core/store/models/run_result.go @@ -0,0 +1,124 @@ +package models + +import ( + "errors" + "fmt" + + "github.com/tidwall/gjson" + null "gopkg.in/guregu/null.v3" +) + +// RunResult keeps track of the outcome of a TaskRun or JobRun. It stores the +// Data and ErrorMessage, and contains a field to track the status. +type RunResult struct { + ID uint `json:"-" gorm:"primary_key;auto_increment"` + CachedJobRunID *ID `json:"jobRunId" gorm:"-"` + CachedTaskRunID *ID `json:"taskRunId" gorm:"-"` + Data JSON `json:"data" gorm:"type:text"` + Status RunStatus `json:"status"` + ErrorMessage null.String `json:"error"` +} + +func RunResultComplete(resultVal interface{}) RunResult { + var result RunResult + result.CompleteWithResult(resultVal) + return result +} + +func RunResultError(err error) RunResult { + var result RunResult + result.SetError(err) + return result +} + +// CompleteWithResult saves a value to a RunResult and marks it as completed +func (rr *RunResult) CompleteWithResult(val interface{}) { + rr.Status = RunStatusCompleted + rr.ApplyResult(val) +} + +// ApplyResult saves a value to a RunResult with the key result. +func (rr *RunResult) ApplyResult(val interface{}) { + rr.Add("result", val) +} + +// Add adds a key and result to the RunResult's JSON payload. +func (rr *RunResult) Add(key string, result interface{}) { + data, err := rr.Data.Add(key, result) + if err != nil { + rr.SetError(err) + return + } + rr.Data = data +} + +// SetError marks the result as errored and saves the specified error message +func (rr *RunResult) SetError(err error) { + rr.ErrorMessage = null.StringFrom(err.Error()) + rr.Status = RunStatusErrored +} + +// MarkPendingBridge sets the status to pending_bridge +func (rr *RunResult) MarkPendingBridge() { + rr.Status = RunStatusPendingBridge +} + +// MarkPendingConfirmations sets the status to pending_confirmations. +func (rr *RunResult) MarkPendingConfirmations() { + rr.Status = RunStatusPendingConfirmations +} + +// MarkPendingConnection sets the status to pending_connection. +func (rr *RunResult) MarkPendingConnection() { + rr.Status = RunStatusPendingConnection +} + +// Get searches for and returns the JSON at the given path. +func (rr *RunResult) Get(path string) gjson.Result { + return rr.Data.Get(path) +} + +// ResultString returns the string result of the Data JSON field. +func (rr *RunResult) ResultString() (string, error) { + val := rr.Result() + if val.Type != gjson.String { + return "", fmt.Errorf("non string result") + } + return val.String(), nil +} + +// Result returns the result as a gjson object +func (rr *RunResult) Result() gjson.Result { + return rr.Get("result") +} + +// HasError returns true if the ErrorMessage is present. +func (rr *RunResult) HasError() bool { + return rr.ErrorMessage.Valid +} + +// Error returns the string value of the ErrorMessage field. +func (rr *RunResult) Error() string { + return rr.ErrorMessage.String +} + +// GetError returns the error of a RunResult if it is present. +func (rr *RunResult) GetError() error { + if rr.HasError() { + return errors.New(rr.ErrorMessage.ValueOrZero()) + } + return nil +} + +// Merge saves the specified result's data onto the receiving RunResult. The +// input result's data takes preference over the receivers'. +func (rr *RunResult) Merge(in RunResult) error { + var err error + rr.Data, err = rr.Data.Merge(in.Data) + if err != nil { + return err + } + rr.ErrorMessage = in.ErrorMessage + rr.Status = in.Status + return nil +} diff --git a/core/store/models/run_result_test.go b/core/store/models/run_result_test.go new file mode 100644 index 00000000000..60642da44b3 --- /dev/null +++ b/core/store/models/run_result_test.go @@ -0,0 +1,152 @@ +package models_test + +import ( + "encoding/json" + "errors" + "testing" + + "github.com/smartcontractkit/chainlink/core/internal/cltest" + "github.com/smartcontractkit/chainlink/core/store/models" + "github.com/stretchr/testify/assert" + "github.com/tidwall/gjson" + null "gopkg.in/guregu/null.v3" +) + +func TestRunResult_Value(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + json string + want string + wantErrored bool + }{ + {"string", `{"result": "100", "other": "101"}`, "100", false}, + {"integer", `{"result": 100}`, "", true}, + {"float", `{"result": 100.01}`, "", true}, + {"boolean", `{"result": true}`, "", true}, + {"null", `{"result": null}`, "", true}, + {"no key", `{"other": 100}`, "", true}, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + var data models.JSON + assert.NoError(t, json.Unmarshal([]byte(test.json), &data)) + rr := models.RunResult{Data: data} + + val, err := rr.ResultString() + assert.Equal(t, test.want, val) + assert.Equal(t, test.wantErrored, (err != nil)) + }) + } +} + +func TestRunResult_Add(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + json string + key string + value interface{} + want string + }{ + {"string", `{"a": "1"}`, "b", "2", `{"a": "1", "b": "2"}`}, + {"int", `{"a": "1"}`, "b", 2, `{"a": "1", "b": 2}`}, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + var data models.JSON + assert.NoError(t, json.Unmarshal([]byte(test.json), &data)) + rr := models.RunResult{Data: data} + + rr.Add(test.key, test.value) + + assert.JSONEq(t, test.want, rr.Data.String()) + }) + } +} + +func TestRunResult_WithError(t *testing.T) { + t.Parallel() + + rr := models.RunResult{} + + assert.Equal(t, models.RunStatusUnstarted, rr.Status) + + rr.SetError(errors.New("this blew up")) + + assert.Equal(t, models.RunStatusErrored, rr.Status) + assert.Equal(t, cltest.NullString("this blew up"), rr.ErrorMessage) +} + +func TestRunResult_Merge(t *testing.T) { + t.Parallel() + + inProgress := models.RunStatusInProgress + pending := models.RunStatusPendingBridge + errored := models.RunStatusErrored + completed := models.RunStatusCompleted + + nullString := cltest.NullString(nil) + tests := []struct { + name string + originalData string + originalError null.String + originalStatus models.RunStatus + inData string + inError null.String + inStatus models.RunStatus + wantData string + wantErrorMessage null.String + wantStatus models.RunStatus + }{ + {"merging data", + `{"result":"old&busted","unique":"1"}`, nullString, inProgress, + `{"result":"newHotness","and":"!"}`, nullString, inProgress, + `{"result":"newHotness","unique":"1","and":"!"}`, nullString, inProgress}, + {"completed result", + `{"result":"old"}`, nullString, inProgress, + `{}`, nullString, completed, + `{"result":"old"}`, nullString, completed}, + {"error override", + `{"result":"old"}`, nullString, inProgress, + `{}`, cltest.NullString("new problem"), errored, + `{"result":"old"}`, cltest.NullString("new problem"), errored}, + {"pending override", + `{"result":"old"}`, nullString, inProgress, + `{}`, nullString, pending, + `{"result":"old"}`, nullString, pending}, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + original := models.RunResult{ + Data: models.JSON{Result: gjson.Parse(test.originalData)}, + ErrorMessage: test.originalError, + Status: test.originalStatus, + } + in := models.RunResult{ + Data: cltest.JSONFromString(t, test.inData), + ErrorMessage: test.inError, + Status: test.inStatus, + } + merged := original + merged.Merge(in) + + assert.JSONEq(t, test.originalData, original.Data.String()) + assert.Equal(t, test.originalError, original.ErrorMessage) + assert.Equal(t, test.originalStatus, original.Status) + + assert.JSONEq(t, test.inData, in.Data.String()) + assert.Equal(t, test.inError, in.ErrorMessage) + assert.Equal(t, test.inStatus, in.Status) + + assert.JSONEq(t, test.wantData, merged.Data.String()) + assert.Equal(t, test.wantErrorMessage, merged.ErrorMessage) + assert.Equal(t, test.wantStatus, merged.Status) + }) + } +} From d7332bdeaadf2912c5aaf4e45c3e56f17c605c26 Mon Sep 17 00:00:00 2001 From: John Barker Date: Wed, 16 Oct 2019 19:38:35 +0800 Subject: [PATCH 017/199] Use a simplified RunOutput type for Perform return --- core/adapters/adapter.go | 2 +- core/adapters/bridge.go | 48 +++--- core/adapters/bridge_test.go | 10 +- core/adapters/compare.go | 33 ++-- core/adapters/compare_test.go | 2 - core/adapters/copy.go | 16 +- core/adapters/copy_test.go | 62 +++++-- core/adapters/eth_bool.go | 8 +- core/adapters/eth_format.go | 18 +- core/adapters/eth_tx.go | 112 ++++++------ core/adapters/eth_tx_abi_encode.go | 10 +- core/adapters/eth_tx_test.go | 31 ++-- core/adapters/http.go | 18 +- core/adapters/json_parse.go | 14 +- core/adapters/multiply.go | 6 +- core/adapters/multiply_sgx.go | 14 +- core/adapters/no_op.go | 10 +- core/adapters/random.go | 6 +- core/adapters/sleep.go | 6 +- core/adapters/wasm.go | 6 +- core/adapters/wasm_sgx.go | 14 +- core/services/job_runner.go | 16 +- core/services/job_subscriber_test.go | 2 +- core/services/runs.go | 6 +- core/services/runs_test.go | 12 +- .../synchronization/stats_pusher_test.go | 2 +- core/store/models/bridge_run_result.go | 40 +++++ core/store/models/job_run.go | 73 ++++---- core/store/models/job_run_test.go | 161 ++---------------- core/store/models/run_output.go | 131 ++++++++++++++ core/store/models/run_result.go | 5 - core/web/job_runs_controller.go | 2 +- 32 files changed, 454 insertions(+), 442 deletions(-) create mode 100644 core/store/models/bridge_run_result.go create mode 100644 core/store/models/run_output.go diff --git a/core/adapters/adapter.go b/core/adapters/adapter.go index 0f7a063ebe8..f912223c764 100644 --- a/core/adapters/adapter.go +++ b/core/adapters/adapter.go @@ -49,7 +49,7 @@ var ( // BaseAdapter is the minimum interface required to create an adapter. Only core // adapters have this minimum requirement. type BaseAdapter interface { - Perform(models.RunResult, *store.Store) models.RunResult + Perform(models.RunResult, *store.Store) models.RunOutput } // PipelineAdapter wraps a BaseAdapter with requirements for execution in the pipeline. diff --git a/core/adapters/bridge.go b/core/adapters/bridge.go index f8eaefd8c41..bd55eebd06d 100644 --- a/core/adapters/bridge.go +++ b/core/adapters/bridge.go @@ -25,27 +25,26 @@ type Bridge struct { // // If the Perform is resumed with a pending RunResult, the RunResult is marked // not pending and the RunResult is returned. -func (ba *Bridge) Perform(input models.RunResult, store *store.Store) models.RunResult { +func (ba *Bridge) Perform(input models.RunResult, store *store.Store) models.RunOutput { if input.Status.Finished() { - return input + return models.RunOutput{ + Data: input.Data, + Status: input.Status, + ErrorMessage: input.ErrorMessage, + } } else if input.Status.PendingBridge() { - return resumeBridge(input) + return models.NewRunOutputInProgress() } return ba.handleNewRun(input, store.Config.BridgeResponseURL()) } -func resumeBridge(input models.RunResult) models.RunResult { - input.Status = models.RunStatusInProgress - return input -} - -func (ba *Bridge) handleNewRun(input models.RunResult, bridgeResponseURL *url.URL) models.RunResult { +func (ba *Bridge) handleNewRun(input models.RunResult, bridgeResponseURL *url.URL) models.RunOutput { if ba.Params == nil { ba.Params = new(models.JSON) } var err error if input.Data, err = input.Data.Merge(*ba.Params); err != nil { - return models.RunResultError(baRunResultError("handling data param", err)) + return models.NewRunOutputError(baRunResultError("handling data param", err)) } responseURL := bridgeResponseURL @@ -55,33 +54,34 @@ func (ba *Bridge) handleNewRun(input models.RunResult, bridgeResponseURL *url.UR body, err := ba.postToExternalAdapter(input, responseURL) if err != nil { - return models.RunResultError(baRunResultError("post to external adapter", err)) + return models.NewRunOutputError(baRunResultError("post to external adapter", err)) } return responseToRunResult(body, input) } -func responseToRunResult(body []byte, input models.RunResult) models.RunResult { - var output models.RunResult - +func responseToRunResult(body []byte, input models.RunResult) models.RunOutput { var brr models.BridgeRunResult err := json.Unmarshal(body, &brr) if err != nil { - return models.RunResultError(baRunResultError("unmarshaling JSON", err)) - } else if brr.HasError() { - return brr.RunResult + return models.NewRunOutputError(baRunResultError("unmarshaling JSON", err)) } - if brr.RunResult.Data.Exists() && !brr.RunResult.Data.IsObject() { - output.CompleteWithResult(brr.RunResult.Data.String()) + status := brr.Status + // FIXME: This code is bizarre to me, what is the intention? It looks like it + // has a bunch of edge cases... + var data models.JSON + if brr.Data.Exists() && !brr.Data.IsObject() { + data, _ = data.Add("result", brr.Data.String()) + status = models.RunStatusCompleted } + data, _ = data.Merge(brr.Data) - err = output.Merge(brr.RunResult) - if err != nil { - output.SetError(err) + return models.RunOutput{ + Status: status, + ErrorMessage: brr.ErrorMessage, + Data: data, } - - return output } func (ba *Bridge) postToExternalAdapter(input models.RunResult, bridgeResponseURL *url.URL) ([]byte, error) { diff --git a/core/adapters/bridge_test.go b/core/adapters/bridge_test.go index 7c70b37cdc1..de22916934a 100644 --- a/core/adapters/bridge_test.go +++ b/core/adapters/bridge_test.go @@ -61,7 +61,9 @@ func TestBridge_PerformAcceptsNonJsonObjectResponses(t *testing.T) { } result := ba.Perform(input, store) assert.NoError(t, result.GetError()) - assert.Equal(t, "251990120", result.Data.Get("result").String()) + resultString, err := result.ResultString() + assert.NoError(t, err) + assert.Equal(t, "251990120", resultString) } func TestBridge_Perform_transitionsTo(t *testing.T) { @@ -98,7 +100,11 @@ func TestBridge_Perform_transitionsTo(t *testing.T) { assert.Equal(t, test.result, result.Data.String()) assert.Equal(t, test.wantStatus, result.Status) if test.wantStatus.Errored() || test.wantStatus.Completed() { - assert.Equal(t, input, result) + outputWanted := models.RunOutput{ + Data: input.Data, + Status: input.Status, + } + assert.Equal(t, outputWanted, result) } }) } diff --git a/core/adapters/compare.go b/core/adapters/compare.go index 3b4b4d8f350..2180d876679 100644 --- a/core/adapters/compare.go +++ b/core/adapters/compare.go @@ -25,52 +25,45 @@ var ( // Perform uses the Operator to check the run's result against the // specified Value. -func (c *Compare) Perform(input models.RunResult, _ *store.Store) models.RunResult { +func (c *Compare) Perform(input models.RunResult, _ *store.Store) models.RunOutput { prevResult := input.Result() if c.Value == "" { - input.SetError(ErrValueNotSpecified) - return input + return models.NewRunOutputError(ErrValueNotSpecified) } switch c.Operator { case "eq": - input.CompleteWithResult(c.Value == prevResult.String()) + return models.NewRunOutputCompleteWithResult(c.Value == prevResult.String()) case "neq": - input.CompleteWithResult(c.Value != prevResult.String()) + return models.NewRunOutputCompleteWithResult(c.Value != prevResult.String()) case "gt": value, desired, err := getValues(prevResult, c.Value) if err != nil { - input.SetError(err) - return input + return models.NewRunOutputError(err) } - input.CompleteWithResult(desired < value) + return models.NewRunOutputCompleteWithResult(desired < value) case "gte": value, desired, err := getValues(prevResult, c.Value) if err != nil { - input.SetError(err) - return input + return models.NewRunOutputError(err) } - input.CompleteWithResult(desired <= value) + return models.NewRunOutputCompleteWithResult(desired <= value) case "lt": value, desired, err := getValues(prevResult, c.Value) if err != nil { - input.SetError(err) - return input + return models.NewRunOutputError(err) } - input.CompleteWithResult(desired > value) + return models.NewRunOutputCompleteWithResult(desired > value) case "lte": value, desired, err := getValues(prevResult, c.Value) if err != nil { - input.SetError(err) - return input + return models.NewRunOutputError(err) } - input.CompleteWithResult(desired >= value) + return models.NewRunOutputCompleteWithResult(desired >= value) default: - input.SetError(ErrOperatorNotSpecified) + return models.NewRunOutputError(ErrOperatorNotSpecified) } - - return input } func getValues(result gjson.Result, d string) (float64, float64, error) { diff --git a/core/adapters/compare_test.go b/core/adapters/compare_test.go index d6eab147f52..a5fd5139b6a 100644 --- a/core/adapters/compare_test.go +++ b/core/adapters/compare_test.go @@ -882,8 +882,6 @@ func TestCompareError_Perform(t *testing.T) { input := cltest.RunResultWithResult(test.input) adapter := test.adapter result := adapter.Perform(input, nil) - _, err := result.ResultString() - assert.NoError(t, err) assert.Equal(t, test.expected, result.GetError()) }) } diff --git a/core/adapters/copy.go b/core/adapters/copy.go index 78e3e818f59..7cf27ede598 100644 --- a/core/adapters/copy.go +++ b/core/adapters/copy.go @@ -12,24 +12,14 @@ type Copy struct { } // Perform returns the copied values from the desired mapping within the `data` JSON object -func (c *Copy) Perform(input models.RunResult, store *store.Store) models.RunResult { +func (c *Copy) Perform(input models.RunResult, store *store.Store) models.RunOutput { jp := JSONParse{Path: c.CopyPath} data, err := input.Data.Add("result", input.Data.String()) if err != nil { - return models.RunResultError(err) + return models.NewRunOutputError(err) } input.Data = data - rr := jp.Perform(input, store) - if rr.HasError() { - return rr - } - - rr.Data, err = input.Data.Merge(rr.Data) - if err != nil { - return models.RunResultError(err) - } - - return rr + return jp.Perform(input, store) } diff --git a/core/adapters/copy_test.go b/core/adapters/copy_test.go index 215f30a1f71..8e60069a30b 100644 --- a/core/adapters/copy_test.go +++ b/core/adapters/copy_test.go @@ -19,23 +19,59 @@ func TestCopy_Perform(t *testing.T) { wantStatus models.RunStatus wantResultError bool }{ - {"existing path", `{"high":"11850.00","last":"11779.99"}`, []string{"last"}, - `{"high":"11850.00","last":"11779.99","result":"11779.99"}`, models.RunStatusCompleted, false}, - {"nonexistent path", `{"high":"11850.00","last":"11779.99"}`, []string{"doesnotexist"}, - `{"high":"11850.00","last":"11779.99","result":null}`, models.RunStatusCompleted, false}, - {"double nonexistent path", `{"high":"11850.00","last":"11779.99"}`, []string{"no", "really"}, - ``, models.RunStatusErrored, true}, - {"array index path", `{"data":[{"availability":"0.99991"}]}`, []string{"data", "0", "availability"}, - `{"data":[{"availability":"0.99991"}],"result":"0.99991"}`, models.RunStatusCompleted, false}, - {"float result", `{"availability":0.99991}`, []string{"availability"}, - `{"availability":0.99991,"result":0.99991}`, models.RunStatusCompleted, false}, - {"result with quotes", `{"availability":"\""}`, []string{`"`}, - `{"availability":"\"","result":null}`, models.RunStatusCompleted, false}, + { + "existing path", + `{"high":"11850.00","last":"11779.99"}`, + []string{"last"}, + `{"result":"11779.99"}`, + models.RunStatusCompleted, + false, + }, + { + "nonexistent path", + `{"high":"11850.00","last":"11779.99"}`, + []string{"doesnotexist"}, + `{"result":null}`, + models.RunStatusCompleted, + false, + }, + { + "double nonexistent path", + `{"high":"11850.00","last":"11779.99"}`, + []string{"no", "really"}, + ``, + models.RunStatusErrored, + true, + }, + { + "array index path", + `{"data":[{"availability":"0.99991"}]}`, + []string{"data", "0", "availability"}, + `{"result":"0.99991"}`, + models.RunStatusCompleted, + false, + }, + { + "float result", + `{"availability":0.99991}`, + []string{"availability"}, + `{"result":0.99991}`, + models.RunStatusCompleted, + false, + }, + { + "result with quotes", + `{"availability":"\""}`, + []string{`"`}, + `{"result":null}`, + models.RunStatusCompleted, + false, + }, { "index array of array", `{"data":[[0,1]]}`, []string{"data", "0", "0"}, - `{"data":[[0,1]],"result":0}`, + `{"result":0}`, models.RunStatusCompleted, false, }, diff --git a/core/adapters/eth_bool.go b/core/adapters/eth_bool.go index 30133e4b445..28a049a5e8d 100644 --- a/core/adapters/eth_bool.go +++ b/core/adapters/eth_bool.go @@ -17,12 +17,12 @@ type EthBool struct{} // For example, after converting the result false to hex encoded Ethereum // ABI, it would be: // "0x0000000000000000000000000000000000000000000000000000000000000000" -func (*EthBool) Perform(input models.RunResult, _ *store.Store) models.RunResult { +func (*EthBool) Perform(input models.RunResult, _ *store.Store) models.RunOutput { if boolean(input.Result().Type) { - return models.RunResultComplete(evmTrue) - } else { - return models.RunResultComplete(evmFalse) + return models.NewRunOutputCompleteWithResult(evmTrue) } + + return models.NewRunOutputCompleteWithResult(evmFalse) } func boolean(t gjson.Type) bool { diff --git a/core/adapters/eth_format.go b/core/adapters/eth_format.go index a412fd31874..eb20b3c50e6 100644 --- a/core/adapters/eth_format.go +++ b/core/adapters/eth_format.go @@ -17,7 +17,7 @@ type EthBytes32 struct{} // For example, after converting the string "16800.01" to hex encoded Ethereum // ABI, it would be: // "0x31363830302e3031000000000000000000000000000000000000000000000000" -func (*EthBytes32) Perform(input models.RunResult, _ *store.Store) models.RunResult { +func (*EthBytes32) Perform(input models.RunResult, _ *store.Store) models.RunOutput { result := input.Result() value := common.RightPadBytes([]byte(result.String()), utils.EVMWordByteLen) hex := utils.RemoveHexPrefix(hexutil.Encode(value)) @@ -26,7 +26,7 @@ func (*EthBytes32) Perform(input models.RunResult, _ *store.Store) models.RunRes hex = hex[:utils.EVMWordHexLen] } - return models.RunResultComplete(utils.AddHexPrefix(hex)) + return models.NewRunOutputCompleteWithResult(utils.AddHexPrefix(hex)) } // EthInt256 holds no fields @@ -38,14 +38,13 @@ type EthInt256 struct{} // For example, after converting the string "-123.99" to hex encoded Ethereum // ABI, it would be: // "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff85" -func (*EthInt256) Perform(input models.RunResult, _ *store.Store) models.RunResult { +func (*EthInt256) Perform(input models.RunResult, _ *store.Store) models.RunOutput { value, err := utils.EVMTranscodeInt256(input.Result()) if err != nil { - input.SetError(err) - return input + return models.NewRunOutputError(err) } - return models.RunResultComplete(hexutil.Encode(value)) + return models.NewRunOutputCompleteWithResult(hexutil.Encode(value)) } // EthUint256 holds no fields. @@ -57,12 +56,11 @@ type EthUint256 struct{} // For example, after converting the string "123.99" to hex encoded Ethereum // ABI, it would be: // "0x000000000000000000000000000000000000000000000000000000000000007b" -func (*EthUint256) Perform(input models.RunResult, _ *store.Store) models.RunResult { +func (*EthUint256) Perform(input models.RunResult, _ *store.Store) models.RunOutput { value, err := utils.EVMTranscodeUint256(input.Result()) if err != nil { - input.SetError(err) - return input + return models.NewRunOutputError(err) } - return models.RunResultComplete(hexutil.Encode(value)) + return models.NewRunOutputCompleteWithResult(hexutil.Encode(value)) } diff --git a/core/adapters/eth_tx.go b/core/adapters/eth_tx.go index 98ba71993c7..25dac5a889a 100644 --- a/core/adapters/eth_tx.go +++ b/core/adapters/eth_tx.go @@ -37,24 +37,23 @@ type EthTx struct { // Perform creates the run result for the transaction if the existing run result // is not currently pending. Then it confirms the transaction was confirmed on // the blockchain. -func (etx *EthTx) Perform(input models.RunResult, store *strpkg.Store) models.RunResult { +func (etx *EthTx) Perform(input models.RunResult, store *strpkg.Store) models.RunOutput { if !store.TxManager.Connected() { - var output models.RunResult - // output.Data = input.Data - output.MarkPendingConnection() - return output + return models.NewRunOutputPendingConnection() } - if !input.Status.PendingConfirmations() { - value, err := getTxData(etx, input) - if err != nil { - input.SetError(errors.Wrap(err, "while constructing EthTx data")) - return input - } - data := utils.ConcatBytes(etx.FunctionSelector.Bytes(), etx.DataPrefix, value) - return createTxRunResult(etx.Address, etx.GasPrice, etx.GasLimit, data, input, store) + if input.Status.PendingConfirmations() { + return ensureTxRunResult(input, store) + } + + value, err := getTxData(etx, input) + if err != nil { + err = errors.Wrap(err, "while constructing EthTx data") + return models.NewRunOutputError(err) } - return ensureTxRunResult(input, store) + + data := utils.ConcatBytes(etx.FunctionSelector.Bytes(), etx.DataPrefix, value) + return createTxRunResult(etx.Address, etx.GasPrice, etx.GasLimit, data, input, store) } // getTxData returns the data to save against the callback encoded according to @@ -83,7 +82,7 @@ func createTxRunResult( data []byte, input models.RunResult, store *strpkg.Store, -) models.RunResult { +) models.RunOutput { jobRunID := null.String{} if input.CachedJobRunID != nil { jobRunID = null.StringFrom(input.CachedJobRunID.String()) @@ -97,27 +96,22 @@ func createTxRunResult( gasLimit, ) if IsClientRetriable(err) { - var output models.RunResult - output.MarkPendingConnection() - return output + return models.NewRunOutputPendingConnection() } else if err != nil { - return models.RunResultError(err) + return models.NewRunOutputError(err) } - var output models.RunResult - output.ApplyResult(tx.Hash.String()) + var output models.JSON + output, _ = output.Add("result", tx.Hash.String()) txAttempt := tx.Attempts[0] - receipt, state, err := store.TxManager.CheckAttempt(txAttempt, tx.SentAt) if IsClientRetriable(err) { - output.MarkPendingConnection() - return output + return models.NewRunOutputPendingConnectionWithData(output) } else if IsClientEmptyError(err) { - output.MarkPendingConfirmations() - return output + return models.NewRunOutputPendingConfirmationsWithData(output) } else if err != nil { - return models.RunResultError(err) + return models.NewRunOutputError(err) } logger.Debugw( @@ -129,36 +123,26 @@ func createTxRunResult( "receiptHash", receipt.Hash.Hex(), ) - if state != strpkg.Safe { - output.MarkPendingConfirmations() - return output + if state == strpkg.Safe { + return addReceiptToResult(receipt, input, output) } - if receipt != nil { - addReceiptToResult(receipt, input, &output) - } - return output + return models.NewRunOutputPendingConfirmationsWithData(output) } -func ensureTxRunResult(input models.RunResult, str *strpkg.Store) models.RunResult { +func ensureTxRunResult(input models.RunResult, str *strpkg.Store) models.RunOutput { val, err := input.ResultString() if err != nil { - return models.RunResultError(err) + return models.NewRunOutputError(err) } hash := common.HexToHash(val) - if err != nil { - return models.RunResultError(err) - } - receipt, state, err := str.TxManager.BumpGasUntilSafe(hash) if err != nil { if IsClientEmptyError(err) { - var output models.RunResult - output.MarkPendingConfirmations() - return output + return models.NewRunOutputPendingConfirmations() } else if state == strpkg.Unknown { - return models.RunResultError(err) + return models.NewRunOutputError(err) } // We failed to get one of the TxAttempt receipts, so we won't mark this @@ -166,45 +150,45 @@ func ensureTxRunResult(input models.RunResult, str *strpkg.Store) models.RunResu logger.Warn("EthTx Adapter Perform Resuming: ", err) } - var output models.RunResult + var output models.JSON if receipt != nil && !receipt.Unconfirmed() { // If the tx has been confirmed, add its hash to the RunResult. hex := receipt.Hash.String() - output.ApplyResult(hex) - output.Add("latestOutgoingTxHash", hex) + output, _ = output.Add("result", hex) + output, _ = output.Add("latestOutgoingTxHash", hex) } else { // If the tx is still unconfirmed, just copy over the original tx hash. - output.ApplyResult(hash) + output, _ = output.Add("result", hash) } - if state != strpkg.Safe { - output.MarkPendingConfirmations() - return output + if state == strpkg.Safe { + return addReceiptToResult(receipt, input, output) } - if receipt != nil { - addReceiptToResult(receipt, input, &output) - } - return output + return models.NewRunOutputPendingConfirmationsWithData(output) } var zero = common.Hash{} -func addReceiptToResult(receipt *models.TxReceipt, input models.RunResult, output *models.RunResult) { +func addReceiptToResult( + receipt *models.TxReceipt, + input models.RunResult, + data models.JSON, +) models.RunOutput { receipts := []models.TxReceipt{} - if !output.Get("ethereumReceipts").IsArray() { - output.Add("ethereumReceipts", receipts) - } - - if err := json.Unmarshal([]byte(input.Get("ethereumReceipts").String()), &receipts); err != nil { - logger.Error(fmt.Errorf("EthTx Adapter unmarshaling ethereum Receipts: %v", err)) + ethereumReceipts := input.Get("ethereumReceipts").String() + if ethereumReceipts != "" { + if err := json.Unmarshal([]byte(ethereumReceipts), &receipts); err != nil { + logger.Errorw("Error unmarshaling ethereum Receipts", "error", err) + } } receipts = append(receipts, *receipt) - output.Add("ethereumReceipts", receipts) - output.CompleteWithResult(receipt.Hash.String()) + data, _ = data.Add("ethereumReceipts", receipts) + data, _ = data.Add("result", receipt.Hash.String()) + return models.NewRunOutputComplete(data) } // IsClientRetriable does its best effort to see if an error indicates one that diff --git a/core/adapters/eth_tx_abi_encode.go b/core/adapters/eth_tx_abi_encode.go index b09fe91c584..35e190a7adc 100644 --- a/core/adapters/eth_tx_abi_encode.go +++ b/core/adapters/eth_tx_abi_encode.go @@ -63,17 +63,15 @@ func (etx *EthTxABIEncode) UnmarshalJSON(data []byte) error { // Perform creates the run result for the transaction if the existing run result // is not currently pending. Then it confirms the transaction was confirmed on // the blockchain. -func (etx *EthTxABIEncode) Perform( - input models.RunResult, store *strpkg.Store) models.RunResult { +func (etx *EthTxABIEncode) Perform(input models.RunResult, store *strpkg.Store) models.RunOutput { if !store.TxManager.Connected() { - input.MarkPendingConnection() - return input + return models.NewRunOutputPendingConnection() } if !input.Status.PendingConfirmations() { data, err := etx.abiEncode(&input) if err != nil { - input.SetError(errors.Wrap(err, "while constructing EthTxABIEncode data")) - return input + err = errors.Wrap(err, "while constructing EthTxABIEncode data") + return models.NewRunOutputError(err) } return createTxRunResult(etx.Address, etx.GasPrice, etx.GasLimit, data, input, store) } diff --git a/core/adapters/eth_tx_test.go b/core/adapters/eth_tx_test.go index 639c76430f9..375bb42adaa 100644 --- a/core/adapters/eth_tx_test.go +++ b/core/adapters/eth_tx_test.go @@ -315,8 +315,8 @@ func TestEthTxAdapter_Perform_FromPendingConfirmations_ConfirmCompletes(t *testi receiptsJSON := output.Get("ethereumReceipts").String() var receipts []models.TxReceipt - assert.NoError(t, json.Unmarshal([]byte(receiptsJSON), &receipts)) - assert.Equal(t, 1, len(receipts)) + require.NoError(t, json.Unmarshal([]byte(receiptsJSON), &receipts)) + require.Len(t, receipts, 1) assert.Equal(t, receipt, receipts[0]) confirmedTxHex := output.Get("latestOutgoingTxHash").String() @@ -341,6 +341,9 @@ func TestEthTxAdapter_Perform_AppendingTransactionReceipts(t *testing.T) { confirmedAt := sentAt + config.MinOutgoingConfirmations() - 1 // confirmations are 0-based idx require.NoError(t, app.Store.ORM.CreateHead(cltest.Head(confirmedAt))) ethMock.Register("eth_chainId", config.ChainID()) + ethMock.Register("eth_getTransactionCount", `0x100`) + ethMock.Register("eth_getBalance", "0x100") + ethMock.Register("eth_call", "0x1") require.NoError(t, app.StartAndConnect()) @@ -359,8 +362,10 @@ func TestEthTxAdapter_Perform_AppendingTransactionReceipts(t *testing.T) { output := adapter.Perform(input, store) assert.True(t, output.Status.Completed()) + receiptsJSON := output.Get("ethereumReceipts").String() var receipts []models.TxReceipt + require.NotEqual(t, "", receiptsJSON) assert.NoError(t, json.Unmarshal([]byte(receiptsJSON), &receipts)) assert.Equal(t, []models.TxReceipt{previousReceipt, receipt}, receipts) @@ -645,6 +650,7 @@ func TestEthTxAdapter_Perform_CreateTxWithEmptyResponseErrorTreatsAsPendingConfi ctrl := gomock.NewController(t) defer ctrl.Finish() + badResponseErr := errors.New("Bad response on request: [ TransactionIndex ]. Error cause was EmptyResponse, (majority count: 94 / total: 94)") txmMock := mocks.NewMockTxManager(ctrl) store.TxManager = txmMock txmMock.EXPECT().Connected().Return(true) @@ -658,24 +664,27 @@ func TestEthTxAdapter_Perform_CreateTxWithEmptyResponseErrorTreatsAsPendingConfi txmMock.EXPECT().CheckAttempt( gomock.Any(), gomock.Any(), - ).Return(nil, strpkg.Unknown, errors.New("Bad response on request: [ TransactionIndex ]. Error cause was EmptyResponse, (majority count: 94 / total: 94)")) + ).Return(nil, strpkg.Unknown, badResponseErr) adapter := adapters.EthTx{} - input := models.RunResult{} - input = adapter.Perform(input, store) + output := adapter.Perform(models.RunResult{}, store) - assert.False(t, input.HasError()) - assert.Equal(t, models.RunStatusPendingConfirmations, input.Status) + assert.False(t, output.HasError()) + assert.Equal(t, models.RunStatusPendingConfirmations, output.Status) // Have a head come through with the same empty response txmMock.EXPECT().Connected().Return(true) txmMock.EXPECT().BumpGasUntilSafe( gomock.Any(), - ).Return(nil, strpkg.Unknown, errors.New("Bad response on request: [ TransactionIndex ]. Error cause was EmptyResponse, (majority count: 94 / total: 94)")) + ).Return(nil, strpkg.Unknown, badResponseErr) - input = adapter.Perform(input, store) - assert.False(t, input.HasError()) - assert.Equal(t, models.RunStatusPendingConfirmations, input.Status) + input := models.RunResult{ + Data: output.Data, + Status: output.Status, + } + output = adapter.Perform(input, store) + assert.False(t, output.HasError()) + assert.Equal(t, models.RunStatusPendingConfirmations, output.Status) } func TestEthTxAdapter_Perform_NoDoubleSpendOnSendTransactionFail(t *testing.T) { diff --git a/core/adapters/http.go b/core/adapters/http.go index 807e2a3adbc..8d52f61d0b2 100644 --- a/core/adapters/http.go +++ b/core/adapters/http.go @@ -28,10 +28,10 @@ type HTTPGet struct { // Perform ensures that the adapter's URL responds to a GET request without // errors and returns the response body as the "value" field of the result. -func (hga *HTTPGet) Perform(input models.RunResult, store *store.Store) models.RunResult { +func (hga *HTTPGet) Perform(input models.RunResult, store *store.Store) models.RunOutput { request, err := hga.GetRequest() if err != nil { - return models.RunResultError(err) + return models.NewRunOutputError(err) } return sendRequest(input, request, store.Config.DefaultHTTPLimit()) } @@ -68,10 +68,10 @@ type HTTPPost struct { // Perform ensures that the adapter's URL responds to a POST request without // errors and returns the response body as the "value" field of the result. -func (hpa *HTTPPost) Perform(input models.RunResult, store *store.Store) models.RunResult { +func (hpa *HTTPPost) Perform(input models.RunResult, store *store.Store) models.RunOutput { request, err := hpa.GetRequest(input.Data.String()) if err != nil { - return models.RunResultError(err) + return models.NewRunOutputError(err) } return sendRequest(input, request, store.Config.DefaultHTTPLimit()) } @@ -132,14 +132,14 @@ func setHeaders(request *http.Request, headers http.Header, contentType string) } } -func sendRequest(input models.RunResult, request *http.Request, limit int64) models.RunResult { +func sendRequest(input models.RunResult, request *http.Request, limit int64) models.RunOutput { tr := &http.Transport{ DisableCompression: true, } client := &http.Client{Transport: tr} response, err := client.Do(request) if err != nil { - return models.RunResultError(err) + return models.NewRunOutputError(err) } defer response.Body.Close() @@ -147,15 +147,15 @@ func sendRequest(input models.RunResult, request *http.Request, limit int64) mod source := newMaxBytesReader(response.Body, limit) bytes, err := ioutil.ReadAll(source) if err != nil { - return models.RunResultError(err) + return models.NewRunOutputError(err) } responseBody := string(bytes) if response.StatusCode >= 400 { - return models.RunResultError(errors.New(responseBody)) + return models.NewRunOutputError(errors.New(responseBody)) } - return models.RunResultComplete(responseBody) + return models.NewRunOutputCompleteWithResult(responseBody) } // maxBytesReader is inspired by diff --git a/core/adapters/json_parse.go b/core/adapters/json_parse.go index 3fd848fe864..eacecc743e1 100644 --- a/core/adapters/json_parse.go +++ b/core/adapters/json_parse.go @@ -30,15 +30,15 @@ type JSONParse struct { // } // // Then ["0","last"] would be the path, and "111" would be the returned value -func (jpa *JSONParse) Perform(input models.RunResult, _ *store.Store) models.RunResult { +func (jpa *JSONParse) Perform(input models.RunResult, _ *store.Store) models.RunOutput { val, err := input.ResultString() if err != nil { - return models.RunResultError(err) + return models.NewRunOutputError(err) } js, err := simplejson.NewJson([]byte(val)) if err != nil { - return models.RunResultError(err) + return models.NewRunOutputError(err) } last, err := dig(js, jpa.Path) @@ -46,7 +46,7 @@ func (jpa *JSONParse) Perform(input models.RunResult, _ *store.Store) models.Run return moldErrorOutput(js, jpa.Path, input) } - return models.RunResultComplete(last.Interface()) + return models.NewRunOutputCompleteWithResult(last.Interface()) } func dig(js *simplejson.Json, path []string) (*simplejson.Json, error) { @@ -66,11 +66,11 @@ func dig(js *simplejson.Json, path []string) (*simplejson.Json, error) { // only error if any keys prior to the last one in the path are nonexistent. // i.e. Path = ["errorIfNonExistent", "nullIfNonExistent"] -func moldErrorOutput(js *simplejson.Json, path []string, input models.RunResult) models.RunResult { +func moldErrorOutput(js *simplejson.Json, path []string, input models.RunResult) models.RunOutput { if _, err := getEarlyPath(js, path); err != nil { - return models.RunResultError(err) + return models.NewRunOutputError(err) } - return models.RunResultComplete(nil) + return models.NewRunOutputCompleteWithResult(nil) } func getEarlyPath(js *simplejson.Json, path []string) (*simplejson.Json, error) { diff --git a/core/adapters/multiply.go b/core/adapters/multiply.go index d99392de8d6..08dd65d4679 100644 --- a/core/adapters/multiply.go +++ b/core/adapters/multiply.go @@ -38,15 +38,15 @@ type Multiply struct { // // For example, if input value is "99.994" and the adapter's "times" is // set to "100", the result's value will be "9999.4". -func (ma *Multiply) Perform(input models.RunResult, _ *store.Store) models.RunResult { +func (ma *Multiply) Perform(input models.RunResult, _ *store.Store) models.RunOutput { val := input.Result() i, ok := (&big.Float{}).SetString(val.String()) if !ok { - return models.RunResultError(fmt.Errorf("cannot parse into big.Float: %v", val.String())) + return models.NewRunOutputError(fmt.Errorf("cannot parse into big.Float: %v", val.String())) } if ma.Times != nil { i.Mul(i, big.NewFloat(float64(*ma.Times))) } - return models.RunResultComplete(i.String()) + return models.NewRunOutputCompleteWithResult(i.String()) } diff --git a/core/adapters/multiply_sgx.go b/core/adapters/multiply_sgx.go index 02731967f82..f2a17992a6c 100644 --- a/core/adapters/multiply_sgx.go +++ b/core/adapters/multiply_sgx.go @@ -46,16 +46,14 @@ type Multiply struct { // // For example, if input value is "99.994" and the adapter's "times" is // set to "100", the result's value will be "9999.4". -func (ma *Multiply) Perform(input models.RunResult, _ *store.Store) models.RunResult { +func (ma *Multiply) Perform(input models.RunResult, _ *store.Store) models.RunOutput { adapterJSON, err := json.Marshal(ma) if err != nil { - input.SetError(err) - return input + return models.NewRunOutputError(err) } inputJSON, err := json.Marshal(input) if err != nil { - input.SetError(err) - return input + return models.NewRunOutputError(err) } cAdapter := C.CString(string(adapterJSON)) @@ -70,15 +68,13 @@ func (ma *Multiply) Perform(input models.RunResult, _ *store.Store) models.RunRe outputLenPtr := (*C.int)(unsafe.Pointer(&outputLen)) if _, err = C.multiply(cAdapter, cInput, output, bufferCapacity, outputLenPtr); err != nil { - input.SetError(fmt.Errorf("SGX multiply: %v", err)) - return input + return models.NewRunOutputError(fmt.Errorf("SGX multiply: %v", err)) } sgxResult := C.GoStringN(output, outputLen) var result models.RunResult if err := json.Unmarshal([]byte(sgxResult), &result); err != nil { - input.SetError(fmt.Errorf("unmarshaling SGX result: %v", err)) - return input + return models.NewRunOutputError(fmt.Errorf("unmarshaling SGX result: %v", err)) } return result diff --git a/core/adapters/no_op.go b/core/adapters/no_op.go index 662823c977c..9c150ba1f94 100644 --- a/core/adapters/no_op.go +++ b/core/adapters/no_op.go @@ -9,9 +9,9 @@ import ( type NoOp struct{} // Perform returns the empty RunResult -func (noa *NoOp) Perform(input models.RunResult, _ *store.Store) models.RunResult { +func (noa *NoOp) Perform(input models.RunResult, _ *store.Store) models.RunOutput { val := input.Result().Value() - return models.RunResultComplete(val) + return models.NewRunOutputCompleteWithResult(val) } // NoOpPend adapter type holds no fields @@ -19,8 +19,6 @@ type NoOpPend struct{} // Perform on this adapter type returns an empty RunResult with an // added field for the status to indicate the task is Pending. -func (noa *NoOpPend) Perform(input models.RunResult, _ *store.Store) models.RunResult { - var output models.RunResult - output.MarkPendingConfirmations() - return output +func (noa *NoOpPend) Perform(input models.RunResult, _ *store.Store) models.RunOutput { + return models.NewRunOutputPendingConfirmations() } diff --git a/core/adapters/random.go b/core/adapters/random.go index 748d0fd88c5..2055e1aec57 100644 --- a/core/adapters/random.go +++ b/core/adapters/random.go @@ -12,12 +12,12 @@ import ( type Random struct{} // Perform returns a random uint256 number in 0 | 2**256-1 range -func (ra *Random) Perform(input models.RunResult, _ *store.Store) models.RunResult { +func (ra *Random) Perform(input models.RunResult, _ *store.Store) models.RunOutput { b := make([]byte, 32) _, err := rand.Read(b) if err != nil { - return models.RunResultError(err) + return models.NewRunOutputError(err) } ran := new(big.Int).SetBytes(b) - return models.RunResultComplete(ran.String()) + return models.NewRunOutputCompleteWithResult(ran.String()) } diff --git a/core/adapters/sleep.go b/core/adapters/sleep.go index c79fde2ec72..1d929558d31 100644 --- a/core/adapters/sleep.go +++ b/core/adapters/sleep.go @@ -14,10 +14,8 @@ type Sleep struct { } // Perform returns the input RunResult after waiting for the specified Until parameter. -func (adapter *Sleep) Perform(input models.RunResult, str *store.Store) models.RunResult { - var output models.RunResult - output.Status = models.RunStatusPendingSleep - return output +func (adapter *Sleep) Perform(input models.RunResult, str *store.Store) models.RunOutput { + return models.NewRunOutputPendingSleep() } // Duration returns the amount of sleeping this task should be paused for. diff --git a/core/adapters/wasm.go b/core/adapters/wasm.go index 6f611f25f64..898ac9de358 100644 --- a/core/adapters/wasm.go +++ b/core/adapters/wasm.go @@ -15,7 +15,7 @@ type Wasm struct { } // Perform ships the wasm representation to the SGX enclave where it is evaluated. -func (wasm *Wasm) Perform(input models.RunResult, _ *store.Store) models.RunResult { - input.SetError(fmt.Errorf("Wasm is not supported without SGX")) - return input +func (wasm *Wasm) Perform(input models.RunResult, _ *store.Store) models.RunOutput { + err := fmt.Errorf("Wasm is not supported without SGX") + return models.NewRunOutputError(err) } diff --git a/core/adapters/wasm_sgx.go b/core/adapters/wasm_sgx.go index 5dfd17b9405..d968040967b 100644 --- a/core/adapters/wasm_sgx.go +++ b/core/adapters/wasm_sgx.go @@ -24,16 +24,14 @@ type Wasm struct { } // Perform ships the wasm representation to the SGX enclave where it is evaluated. -func (wasm *Wasm) Perform(input models.RunResult, _ *store.Store) models.RunResult { +func (wasm *Wasm) Perform(input models.RunResult, _ *store.Store) models.RunOutput { adapterJSON, err := json.Marshal(wasm) if err != nil { - input.SetError(err) - return input + return models.NewRunOutputError(err) } inputJSON, err := json.Marshal(input) if err != nil { - input.SetError(err) - return input + return models.NewRunOutputError(err) } cAdapter := C.CString(string(adapterJSON)) @@ -49,15 +47,13 @@ func (wasm *Wasm) Perform(input models.RunResult, _ *store.Store) models.RunResu _, err = C.wasm(cAdapter, cInput, output, bufferCapacity, outputLenPtr) if err != nil { - input.SetError(fmt.Errorf("SGX wasm: %v", err)) - return input + return models.NewRunOutputError(fmt.Errorf("SGX wasm: %v", err))) } sgxResult := C.GoStringN(output, outputLen) var result models.RunResult if err := json.Unmarshal([]byte(sgxResult), &result); err != nil { - input.SetError(fmt.Errorf("unmarshaling SGX result: %v", err)) - return input + return models.NewRunOutputError(fmt.Errorf("unmarshaling SGX result: %v", err)) } return result diff --git a/core/services/job_runner.go b/core/services/job_runner.go index 503b6c7c87a..1b485af1869 100644 --- a/core/services/job_runner.go +++ b/core/services/job_runner.go @@ -196,33 +196,29 @@ func prepareTaskInput(run *models.JobRun, input models.JSON) (models.JSON, error return input, nil } -func executeTask(run *models.JobRun, currentTaskRun *models.TaskRun, store *store.Store) models.RunResult { +func executeTask(run *models.JobRun, currentTaskRun *models.TaskRun, store *store.Store) models.RunOutput { taskCopy := currentTaskRun.TaskSpec // deliberately copied to keep mutations local var err error if taskCopy.Params, err = taskCopy.Params.Merge(run.Overrides); err != nil { - currentTaskRun.Result.SetError(err) - return currentTaskRun.Result + return models.NewRunOutputError(err) } adapter, err := adapters.For(taskCopy, store) if err != nil { - currentTaskRun.Result.SetError(err) - return currentTaskRun.Result + return models.NewRunOutputError(err) } logger.Infow(fmt.Sprintf("Processing task %s", taskCopy.Type), []interface{}{"task", currentTaskRun.ID.String()}...) data, err := prepareTaskInput(run, currentTaskRun.Result.Data) if err != nil { - currentTaskRun.Result.SetError(err) - return currentTaskRun.Result + return models.NewRunOutputError(err) } currentTaskRun.Result.CachedJobRunID = run.ID currentTaskRun.Result.Data = data result := adapter.Perform(currentTaskRun.Result, store) - result.ID = currentTaskRun.Result.ID logger.Infow(fmt.Sprintf("Finished processing task %s", taskCopy.Type), []interface{}{ "task", currentTaskRun.ID, @@ -247,8 +243,8 @@ func executeRun(run *models.JobRun, store *store.Store) error { result := executeTask(run, currentTaskRun, store) - currentTaskRun.ApplyResult(result) - run.ApplyResult(result) + currentTaskRun.ApplyOutput(result) + run.ApplyOutput(result) if currentTaskRun.Status.PendingSleep() { logger.Debugw("Task is sleeping", []interface{}{"run", run.ID.String()}...) diff --git a/core/services/job_subscriber_test.go b/core/services/job_subscriber_test.go index 2a7ae2e2355..ae75c5d4e1e 100644 --- a/core/services/job_subscriber_test.go +++ b/core/services/job_subscriber_test.go @@ -287,7 +287,7 @@ func TestJobSubscriber_OnNewHead_ResumePendingConfirmationsAndPendingConnections require.NoError(t, store.CreateJob(&job)) initr := job.Initiators[0] run := job.NewRun(initr) - run.ApplyResult(models.RunResult{Status: test.status}) + run.Status = test.status require.NoError(t, store.CreateJobRun(&run)) if test.archived { diff --git a/core/services/runs.go b/core/services/runs.go index 36cfeba2b75..e1c8a516b71 100644 --- a/core/services/runs.go +++ b/core/services/runs.go @@ -235,7 +235,7 @@ func ResumeConnectingTask( func ResumePendingTask( run *models.JobRun, store *store.Store, - input models.RunResult, + input models.BridgeRunResult, ) error { logger.Debugw("External adapter resuming job", []interface{}{ "run", run.ID.String(), @@ -256,8 +256,8 @@ func ResumePendingTask( run.Overrides.Merge(input.Data) - currentTaskRun.ApplyResult(input) - run.ApplyResult(input) + currentTaskRun.ApplyBridgeRunResult(input) + run.ApplyBridgeRunResult(input) return updateAndTrigger(run, store) } diff --git a/core/services/runs_test.go b/core/services/runs_test.go index bc036c279b3..f3437f47953 100644 --- a/core/services/runs_test.go +++ b/core/services/runs_test.go @@ -273,7 +273,7 @@ func TestResumePendingTask(t *testing.T) { ID: runID, JobSpecID: jobID, } - err := services.ResumePendingTask(run, store, models.RunResult{}) + err := services.ResumePendingTask(run, store, models.BridgeRunResult{}) assert.Error(t, err) // reject a run with no tasks @@ -282,12 +282,12 @@ func TestResumePendingTask(t *testing.T) { JobSpecID: jobID, Status: models.RunStatusPendingBridge, } - err = services.ResumePendingTask(run, store, models.RunResult{}) + err = services.ResumePendingTask(run, store, models.BridgeRunResult{}) assert.Error(t, err) // input with error errors run run.TaskRuns = []models.TaskRun{models.TaskRun{ID: models.NewID(), JobRunID: runID}} - err = services.ResumePendingTask(run, store, models.RunResult{CachedJobRunID: runID, Status: models.RunStatusErrored}) + err = services.ResumePendingTask(run, store, models.BridgeRunResult{Status: models.RunStatusErrored}) assert.Error(t, err) assert.True(t, run.FinishedAt.Valid) @@ -299,11 +299,10 @@ func TestResumePendingTask(t *testing.T) { TaskRuns: []models.TaskRun{models.TaskRun{ID: models.NewID(), JobRunID: runID}, models.TaskRun{ID: models.NewID(), JobRunID: runID}}, } input := models.JSON{Result: gjson.Parse(`{"address":"0xdfcfc2b9200dbb10952c2b7cce60fc7260e03c6f"}`)} - err = services.ResumePendingTask(run, store, models.RunResult{CachedJobRunID: runID, Data: input, Status: models.RunStatusCompleted}) + err = services.ResumePendingTask(run, store, models.BridgeRunResult{Data: input, Status: models.RunStatusCompleted}) assert.Error(t, err) assert.Equal(t, string(models.RunStatusInProgress), string(run.Status)) assert.Len(t, run.TaskRuns, 2) - assert.Equal(t, run.ID, run.TaskRuns[0].Result.CachedJobRunID) assert.Equal(t, string(models.RunStatusCompleted), string(run.TaskRuns[0].Result.Status)) // completed input with no remaining tasks should get marked as complete @@ -313,12 +312,11 @@ func TestResumePendingTask(t *testing.T) { Status: models.RunStatusPendingBridge, TaskRuns: []models.TaskRun{models.TaskRun{ID: models.NewID(), JobRunID: runID}}, } - err = services.ResumePendingTask(run, store, models.RunResult{CachedJobRunID: runID, Data: input, Status: models.RunStatusCompleted}) + err = services.ResumePendingTask(run, store, models.BridgeRunResult{Data: input, Status: models.RunStatusCompleted}) assert.Error(t, err) assert.Equal(t, string(models.RunStatusCompleted), string(run.Status)) assert.True(t, run.FinishedAt.Valid) assert.Len(t, run.TaskRuns, 1) - assert.Equal(t, run.ID, run.TaskRuns[0].Result.CachedJobRunID) assert.Equal(t, string(models.RunStatusCompleted), string(run.TaskRuns[0].Result.Status)) } diff --git a/core/services/synchronization/stats_pusher_test.go b/core/services/synchronization/stats_pusher_test.go index e8026dd08e3..b41e1c9c11f 100644 --- a/core/services/synchronization/stats_pusher_test.go +++ b/core/services/synchronization/stats_pusher_test.go @@ -36,7 +36,7 @@ func TestStatsPusher(t *testing.T) { }) cltest.WaitForSyncEventCount(t, store.ORM, 0) - jr.ApplyResult(models.RunResult{Status: models.RunStatusCompleted}) + jr.Status = models.RunStatusCompleted require.NoError(t, store.SaveJobRun(&jr)) assert.Equal(t, 1, lenSyncEvents(t, store.ORM)) diff --git a/core/store/models/bridge_run_result.go b/core/store/models/bridge_run_result.go new file mode 100644 index 00000000000..16eb417eab5 --- /dev/null +++ b/core/store/models/bridge_run_result.go @@ -0,0 +1,40 @@ +package models + +import ( + "encoding/json" + + null "gopkg.in/guregu/null.v3" +) + +// BridgeRunResult handles the parsing of RunResults from external adapters. +type BridgeRunResult struct { + Data JSON `json:"data"` + Status RunStatus `json:"status"` + ErrorMessage null.String `json:"error"` + ExternalPending bool `json:"pending"` + AccessToken string `json:"accessToken"` +} + +// UnmarshalJSON parses the given input and updates the BridgeRunResult in the +// external adapter format. +func (brr *BridgeRunResult) UnmarshalJSON(input []byte) error { + type biAlias BridgeRunResult + var anon biAlias + err := json.Unmarshal(input, &anon) + *brr = BridgeRunResult(anon) + + if brr.Status == RunStatusErrored || brr.ErrorMessage.Valid { + brr.Status = RunStatusErrored + } else if brr.ExternalPending || brr.Status.PendingBridge() { + brr.Status = RunStatusPendingBridge + } else { + brr.Status = RunStatusCompleted + } + + return err +} + +// HasError returns true if the status is errored or the error message is set +func (brr *BridgeRunResult) HasError() bool { + return brr.Status == RunStatusErrored || brr.ErrorMessage.Valid +} diff --git a/core/store/models/job_run.go b/core/store/models/job_run.go index bc0b6108a7e..87f8778a1d2 100644 --- a/core/store/models/job_run.go +++ b/core/store/models/job_run.go @@ -1,7 +1,6 @@ package models import ( - "encoding/json" "fmt" "time" @@ -119,21 +118,31 @@ func (jr *JobRun) SetError(err error) { jr.FinishedAt = null.TimeFrom(time.Now()) } -// ApplyResult updates the JobRun's Result and Status -func (jr *JobRun) ApplyResult(result RunResult) error { - data, err := jr.Result.Data.Merge(result.Data) - if err != nil { - return err - } - jr.Result = result - jr.Result.Data = data - jr.Status = result.Status +// ApplyOutput updates the JobRun's Result and Status +func (jr *JobRun) ApplyOutput(result RunOutput) error { + jr.Result.Status = result.Status + jr.Result.ErrorMessage = result.ErrorMessage + jr.Result.Data = result.Data + jr.setStatus(result.Status) + return nil +} + +// ApplyBridgeRunResult saves the input from a BridgeAdapter +func (jr *JobRun) ApplyBridgeRunResult(result BridgeRunResult) error { + jr.Result.Status = result.Status + jr.Result.ErrorMessage = result.ErrorMessage + jr.Result.Data = result.Data + jr.setStatus(result.Status) + return nil +} + +func (jr *JobRun) setStatus(status RunStatus) { + jr.Status = status if jr.Status.Completed() && jr.TasksRemain() { jr.Status = RunStatusInProgress } else if jr.Status.Finished() { jr.FinishedAt = null.TimeFrom(time.Now()) } - return nil } // JobRunsWithStatus filters passed job runs returning those that have @@ -207,9 +216,19 @@ func (tr *TaskRun) SetError(err error) { tr.Status = tr.Result.Status } -// ApplyResult updates the TaskRun's Result and Status -func (tr *TaskRun) ApplyResult(result RunResult) { - tr.Result = result +// ApplyBridgeRunResult updates the TaskRun's Result and Status +func (tr *TaskRun) ApplyBridgeRunResult(result BridgeRunResult) { + tr.Result.Status = result.Status + tr.Result.ErrorMessage = result.ErrorMessage + tr.Result.Data = result.Data + tr.Status = result.Status +} + +// ApplyOutput updates the TaskRun's Result and Status +func (tr *TaskRun) ApplyOutput(result RunOutput) { + tr.Result.Status = result.Status + tr.Result.ErrorMessage = result.ErrorMessage + tr.Result.Data = result.Data tr.Status = result.Status } @@ -218,29 +237,3 @@ func (tr *TaskRun) MarkPendingConfirmations() { tr.Status = RunStatusPendingConfirmations tr.Result.Status = RunStatusPendingConfirmations } - -// BridgeRunResult handles the parsing of RunResults from external adapters. -type BridgeRunResult struct { - RunResult - ExternalPending bool `json:"pending"` - AccessToken string `json:"accessToken"` -} - -// UnmarshalJSON parses the given input and updates the BridgeRunResult in the -// external adapter format. -func (brr *BridgeRunResult) UnmarshalJSON(input []byte) error { - type biAlias BridgeRunResult - var anon biAlias - err := json.Unmarshal(input, &anon) - *brr = BridgeRunResult(anon) - - if brr.Status.Errored() || brr.HasError() { - brr.Status = RunStatusErrored - } else if brr.ExternalPending || brr.Status.PendingBridge() { - brr.Status = RunStatusPendingBridge - } else { - brr.Status = RunStatusCompleted - } - - return err -} diff --git a/core/store/models/job_run_test.go b/core/store/models/job_run_test.go index c0b73850073..234b36d59c3 100644 --- a/core/store/models/job_run_test.go +++ b/core/store/models/job_run_test.go @@ -13,8 +13,6 @@ import ( "github.com/smartcontractkit/chainlink/core/store/models" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/tidwall/gjson" - null "gopkg.in/guregu/null.v3" ) func TestJobRuns_RetrievingFromDBWithError(t *testing.T) { @@ -199,180 +197,41 @@ func TestJobRun_NextTaskRun(t *testing.T) { assert.Equal(t, &run.TaskRuns[1], run.NextTaskRun()) } -func TestRunResult_Value(t *testing.T) { - t.Parallel() - - tests := []struct { - name string - json string - want string - wantErrored bool - }{ - {"string", `{"result": "100", "other": "101"}`, "100", false}, - {"integer", `{"result": 100}`, "", true}, - {"float", `{"result": 100.01}`, "", true}, - {"boolean", `{"result": true}`, "", true}, - {"null", `{"result": null}`, "", true}, - {"no key", `{"other": 100}`, "", true}, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - var data models.JSON - assert.NoError(t, json.Unmarshal([]byte(test.json), &data)) - rr := models.RunResult{Data: data} - - val, err := rr.ResultString() - assert.Equal(t, test.want, val) - assert.Equal(t, test.wantErrored, (err != nil)) - }) - } -} - -func TestRunResult_Add(t *testing.T) { - t.Parallel() - - tests := []struct { - name string - json string - key string - value interface{} - want string - }{ - {"string", `{"a": "1"}`, "b", "2", `{"a": "1", "b": "2"}`}, - {"int", `{"a": "1"}`, "b", 2, `{"a": "1", "b": 2}`}, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - var data models.JSON - assert.NoError(t, json.Unmarshal([]byte(test.json), &data)) - rr := models.RunResult{Data: data} - - rr.Add(test.key, test.value) - - assert.JSONEq(t, test.want, rr.Data.String()) - }) - } -} - -func TestRunResult_WithError(t *testing.T) { - t.Parallel() - - rr := models.RunResult{} - - assert.Equal(t, models.RunStatusUnstarted, rr.Status) - - rr.SetError(errors.New("this blew up")) - - assert.Equal(t, models.RunStatusErrored, rr.Status) - assert.Equal(t, cltest.NullString("this blew up"), rr.ErrorMessage) -} - -func TestRunResult_Merge(t *testing.T) { - t.Parallel() - - inProgress := models.RunStatusInProgress - pending := models.RunStatusPendingBridge - errored := models.RunStatusErrored - completed := models.RunStatusCompleted - - nullString := cltest.NullString(nil) - tests := []struct { - name string - originalData string - originalError null.String - originalStatus models.RunStatus - inData string - inError null.String - inStatus models.RunStatus - wantData string - wantErrorMessage null.String - wantStatus models.RunStatus - }{ - {"merging data", - `{"result":"old&busted","unique":"1"}`, nullString, inProgress, - `{"result":"newHotness","and":"!"}`, nullString, inProgress, - `{"result":"newHotness","unique":"1","and":"!"}`, nullString, inProgress}, - {"completed result", - `{"result":"old"}`, nullString, inProgress, - `{}`, nullString, completed, - `{"result":"old"}`, nullString, completed}, - {"error override", - `{"result":"old"}`, nullString, inProgress, - `{}`, cltest.NullString("new problem"), errored, - `{"result":"old"}`, cltest.NullString("new problem"), errored}, - {"pending override", - `{"result":"old"}`, nullString, inProgress, - `{}`, nullString, pending, - `{"result":"old"}`, nullString, pending}, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - original := models.RunResult{ - Data: models.JSON{Result: gjson.Parse(test.originalData)}, - ErrorMessage: test.originalError, - Status: test.originalStatus, - } - in := models.RunResult{ - Data: cltest.JSONFromString(t, test.inData), - ErrorMessage: test.inError, - Status: test.inStatus, - } - merged := original - merged.Merge(in) - - assert.JSONEq(t, test.originalData, original.Data.String()) - assert.Equal(t, test.originalError, original.ErrorMessage) - assert.Equal(t, test.originalStatus, original.Status) - - assert.JSONEq(t, test.inData, in.Data.String()) - assert.Equal(t, test.inError, in.ErrorMessage) - assert.Equal(t, test.inStatus, in.Status) - - assert.JSONEq(t, test.wantData, merged.Data.String()) - assert.Equal(t, test.wantErrorMessage, merged.ErrorMessage) - assert.Equal(t, test.wantStatus, merged.Status) - }) - } -} - -func TestJobRun_ApplyResult_CompletedWithNoTasksRemaining(t *testing.T) { +func TestJobRun_ApplyOutput_CompletedWithNoTasksRemaining(t *testing.T) { t.Parallel() job := cltest.NewJobWithWebInitiator() jobRun := job.NewRun(job.Initiators[0]) - result := models.RunResult{Status: models.RunStatusCompleted} - jobRun.TaskRuns[0].ApplyResult(result) - err := jobRun.ApplyResult(result) + result := models.NewRunOutputComplete(models.JSON{}) + jobRun.TaskRuns[0].ApplyOutput(result) + err := jobRun.ApplyOutput(result) assert.NoError(t, err) assert.True(t, jobRun.FinishedAt.Valid) } -func TestJobRun_ApplyResult_CompletedWithTasksRemaining(t *testing.T) { +func TestJobRun_ApplyOutput_CompletedWithTasksRemaining(t *testing.T) { t.Parallel() job := cltest.NewJobWithWebInitiator() jobRun := job.NewRun(job.Initiators[0]) - result := models.RunResult{Status: models.RunStatusCompleted} - err := jobRun.ApplyResult(result) + result := models.NewRunOutputComplete(models.JSON{}) + err := jobRun.ApplyOutput(result) assert.NoError(t, err) assert.False(t, jobRun.FinishedAt.Valid) assert.Equal(t, jobRun.Status, models.RunStatusInProgress) } -func TestJobRun_ApplyResult_ErrorSetsFinishedAt(t *testing.T) { +func TestJobRun_ApplyOutput_ErrorSetsFinishedAt(t *testing.T) { t.Parallel() job := cltest.NewJobWithWebInitiator() jobRun := job.NewRun(job.Initiators[0]) jobRun.Status = models.RunStatusErrored - result := models.RunResult{Status: models.RunStatusErrored} - err := jobRun.ApplyResult(result) + result := models.NewRunOutputError(errors.New("oh futz")) + err := jobRun.ApplyOutput(result) assert.NoError(t, err) assert.True(t, jobRun.FinishedAt.Valid) } diff --git a/core/store/models/run_output.go b/core/store/models/run_output.go new file mode 100644 index 00000000000..d7bebba8f41 --- /dev/null +++ b/core/store/models/run_output.go @@ -0,0 +1,131 @@ +package models + +import ( + "errors" + "fmt" + + "github.com/tidwall/gjson" + null "gopkg.in/guregu/null.v3" +) + +// RunOutput represents the result of performing a Task +type RunOutput struct { + Data JSON + Status RunStatus + ErrorMessage null.String +} + +// NewRunOutputError returns a new RunOutput with an error +func NewRunOutputError(err error) RunOutput { + return RunOutput{ + Status: RunStatusErrored, + ErrorMessage: null.StringFrom(err.Error()), + } +} + +// NewRunOutputCompleteWithResult returns a new RunOutput that is complete and +// contains a result +func NewRunOutputCompleteWithResult(resultVal interface{}) RunOutput { + var data JSON + data, _ = data.Add("result", resultVal) + return NewRunOutputComplete(data) +} + +// NewRunOutputComplete returns a new RunOutput that is complete and contains +// raw data +func NewRunOutputComplete(data JSON) RunOutput { + return RunOutput{ + Status: RunStatusCompleted, + Data: data, + } +} + +// NewRunOutputPendingSleep returns a new RunOutput that indicates the task is +// sleeping +func NewRunOutputPendingSleep() RunOutput { + return RunOutput{ + Status: RunStatusPendingSleep, + } +} + +// NewRunOutputPendingConfirmations returns a new RunOutput that indicates the +// task is pending confirmations +func NewRunOutputPendingConfirmations() RunOutput { + return RunOutput{ + Status: RunStatusPendingConfirmations, + } +} + +// NewRunOutputPendingConfirmationsWithData returns a new RunOutput that +// indicates the task is pending confirmations but also has some data that +// needs to be fed in on next invocation +func NewRunOutputPendingConfirmationsWithData(data JSON) RunOutput { + return RunOutput{ + Status: RunStatusPendingConfirmations, + Data: data, + } +} + +// NewRunOutputPendingConnection returns a new RunOutput that indicates the +// task got disconnected +func NewRunOutputPendingConnection() RunOutput { + return RunOutput{ + Status: RunStatusPendingConnection, + } +} + +// NewRunOutputPendingConfirmationsWithData returns a new RunOutput that +// indicates the task got disconnected but also has some data that needs to be +// fed in on next invocation +func NewRunOutputPendingConnectionWithData(data JSON) RunOutput { + return RunOutput{ + Status: RunStatusPendingConnection, + Data: data, + } +} + +// FIXME: This seems a bit nonsensical but is used by the BridgeAdapter? +// NewRunOutputPendingConnection returns a new RunOutput that indicates the +// task is still in progress +func NewRunOutputInProgress() RunOutput { + return RunOutput{ + Status: RunStatusInProgress, + } +} + +// HasError returns true if the status is errored or the error message is set +func (ro RunOutput) HasError() bool { + return ro.Status == RunStatusErrored || ro.ErrorMessage.Valid +} + +// Result returns the result as a gjson object +func (ro RunOutput) Result() gjson.Result { + return ro.Data.Get("result") +} + +// GetError returns the error of a RunResult if it is present. +func (ro RunOutput) GetError() error { + if ro.HasError() { + return errors.New(ro.ErrorMessage.ValueOrZero()) + } + return nil +} + +// ResultString returns the string result of the Data JSON field. +func (ro RunOutput) ResultString() (string, error) { + val := ro.Result() + if val.Type != gjson.String { + return "", fmt.Errorf("non string result") + } + return val.String(), nil +} + +// Get searches for and returns the JSON at the given path. +func (ro RunOutput) Get(path string) gjson.Result { + return ro.Data.Get(path) +} + +// Error returns the string value of the ErrorMessage field. +func (ro RunOutput) Error() string { + return ro.ErrorMessage.String +} diff --git a/core/store/models/run_result.go b/core/store/models/run_result.go index d4cbc69de72..88f70e806b5 100644 --- a/core/store/models/run_result.go +++ b/core/store/models/run_result.go @@ -34,11 +34,6 @@ func RunResultError(err error) RunResult { // CompleteWithResult saves a value to a RunResult and marks it as completed func (rr *RunResult) CompleteWithResult(val interface{}) { rr.Status = RunStatusCompleted - rr.ApplyResult(val) -} - -// ApplyResult saves a value to a RunResult with the key result. -func (rr *RunResult) ApplyResult(val interface{}) { rr.Add("result", val) } diff --git a/core/web/job_runs_controller.go b/core/web/job_runs_controller.go index d5b43dafd87..d9b3b096fb7 100644 --- a/core/web/job_runs_controller.go +++ b/core/web/job_runs_controller.go @@ -133,7 +133,7 @@ func (jrc *JobRunsController) Update(c *gin.Context) { jsonAPIError(c, http.StatusInternalServerError, err) } else if !ok { c.AbortWithStatus(http.StatusUnauthorized) - } else if err = services.ResumePendingTask(&jr, unscoped, brr.RunResult); err != nil { + } else if err = services.ResumePendingTask(&jr, unscoped, brr); err != nil { jsonAPIError(c, http.StatusInternalServerError, err) } else { jsonAPIResponse(c, jr, "job run") From c196570639d0d31b7f93f4cfdd6d1f479cc18330 Mon Sep 17 00:00:00 2001 From: John Barker Date: Thu, 17 Oct 2019 15:19:06 +0800 Subject: [PATCH 018/199] Add RunInput for Perform input --- core/adapters/adapter.go | 2 +- core/adapters/adapter_test.go | 2 +- core/adapters/bridge.go | 44 ++++++++----------- core/adapters/bridge_test.go | 18 ++++---- core/adapters/compare.go | 2 +- core/adapters/compare_test.go | 4 +- core/adapters/copy.go | 2 +- core/adapters/copy_test.go | 2 +- core/adapters/eth_bool.go | 2 +- core/adapters/eth_bool_test.go | 2 +- core/adapters/eth_format.go | 6 +-- core/adapters/eth_format_test.go | 6 +-- core/adapters/eth_tx.go | 21 ++++----- core/adapters/eth_tx_abi_encode.go | 6 +-- core/adapters/eth_tx_abi_encode_test.go | 2 +- core/adapters/eth_tx_test.go | 57 ++++++++++++------------- core/adapters/http.go | 6 +-- core/adapters/http_test.go | 8 ++-- core/adapters/json_parse.go | 4 +- core/adapters/json_parse_test.go | 2 +- core/adapters/multiply.go | 2 +- core/adapters/multiply_sgx.go | 2 +- core/adapters/multiply_test.go | 4 +- core/adapters/no_op.go | 6 +-- core/adapters/random.go | 2 +- core/adapters/random_test.go | 3 +- core/adapters/sleep.go | 4 +- core/adapters/sleep_test.go | 2 +- core/adapters/wasm.go | 2 +- core/adapters/wasm_sgx.go | 2 +- core/internal/cltest/factories.go | 21 ++------- core/services/job_runner.go | 10 +++-- core/services/runs.go | 6 +-- core/services/runs_test.go | 20 ++++----- core/services/scheduler.go | 4 +- core/services/subscription.go | 3 +- core/store/models/job_run_test.go | 6 +-- core/store/models/run_input.go | 50 ++++++++++++++++++++++ core/web/job_runs_controller.go | 2 +- go.mod | 2 +- 40 files changed, 185 insertions(+), 166 deletions(-) create mode 100644 core/store/models/run_input.go diff --git a/core/adapters/adapter.go b/core/adapters/adapter.go index f912223c764..6f8790ea038 100644 --- a/core/adapters/adapter.go +++ b/core/adapters/adapter.go @@ -49,7 +49,7 @@ var ( // BaseAdapter is the minimum interface required to create an adapter. Only core // adapters have this minimum requirement. type BaseAdapter interface { - Perform(models.RunResult, *store.Store) models.RunOutput + Perform(models.RunInput, *store.Store) models.RunOutput } // PipelineAdapter wraps a BaseAdapter with requirements for execution in the pipeline. diff --git a/core/adapters/adapter_test.go b/core/adapters/adapter_test.go index 13606a2605f..04a1118f5cf 100644 --- a/core/adapters/adapter_test.go +++ b/core/adapters/adapter_test.go @@ -18,7 +18,7 @@ func TestCreatingAdapterWithConfig(t *testing.T) { task := models.TaskSpec{Type: adapters.TaskTypeNoOp} adapter, err := adapters.For(task, store) - adapter.Perform(models.RunResult{}, nil) + adapter.Perform(models.RunInput{}, nil) assert.NoError(t, err) } diff --git a/core/adapters/bridge.go b/core/adapters/bridge.go index bd55eebd06d..616043e4c8f 100644 --- a/core/adapters/bridge.go +++ b/core/adapters/bridge.go @@ -19,13 +19,14 @@ type Bridge struct { Params *models.JSON } -// Perform sends a POST request containing the JSON of the input RunResult to -// the external adapter specified in the BridgeType. +// Perform sends a POST request containing the JSON of the input to the +// external adapter specified in the BridgeType. +// // It records the RunResult returned to it, and optionally marks the RunResult pending. // // If the Perform is resumed with a pending RunResult, the RunResult is marked // not pending and the RunResult is returned. -func (ba *Bridge) Perform(input models.RunResult, store *store.Store) models.RunOutput { +func (ba *Bridge) Perform(input models.RunInput, store *store.Store) models.RunOutput { if input.Status.Finished() { return models.RunOutput{ Data: input.Data, @@ -33,12 +34,12 @@ func (ba *Bridge) Perform(input models.RunResult, store *store.Store) models.Run ErrorMessage: input.ErrorMessage, } } else if input.Status.PendingBridge() { - return models.NewRunOutputInProgress() + return models.NewRunOutputInProgress(input.Data) } return ba.handleNewRun(input, store.Config.BridgeResponseURL()) } -func (ba *Bridge) handleNewRun(input models.RunResult, bridgeResponseURL *url.URL) models.RunOutput { +func (ba *Bridge) handleNewRun(input models.RunInput, bridgeResponseURL *url.URL) models.RunOutput { if ba.Params == nil { ba.Params = new(models.JSON) } @@ -49,7 +50,7 @@ func (ba *Bridge) handleNewRun(input models.RunResult, bridgeResponseURL *url.UR responseURL := bridgeResponseURL if *responseURL != *zeroURL { - responseURL.Path += fmt.Sprintf("/v2/runs/%s", input.CachedJobRunID) + responseURL.Path += fmt.Sprintf("/v2/runs/%s", input.JobRunID.String()) } body, err := ba.postToExternalAdapter(input, responseURL) @@ -60,7 +61,7 @@ func (ba *Bridge) handleNewRun(input models.RunResult, bridgeResponseURL *url.UR return responseToRunResult(body, input) } -func responseToRunResult(body []byte, input models.RunResult) models.RunOutput { +func responseToRunResult(body []byte, input models.RunInput) models.RunOutput { var brr models.BridgeRunResult err := json.Unmarshal(body, &brr) if err != nil { @@ -84,11 +85,12 @@ func responseToRunResult(body []byte, input models.RunResult) models.RunOutput { } } -func (ba *Bridge) postToExternalAdapter(input models.RunResult, bridgeResponseURL *url.URL) ([]byte, error) { - in, err := json.Marshal(&bridgeOutgoing{ - RunResult: input, - ResponseURL: bridgeResponseURL, - }) +func (ba *Bridge) postToExternalAdapter(input models.RunInput, bridgeResponseURL *url.URL) ([]byte, error) { + outgoing := bridgeOutgoing{JobRunID: input.JobRunID.String(), Data: input.Data} + if bridgeResponseURL != nil { + outgoing.ResponseURL = bridgeResponseURL.String() + } + in, err := json.Marshal(&outgoing) if err != nil { return nil, fmt.Errorf("marshaling request body: %v", err) } @@ -121,21 +123,9 @@ func baRunResultError(str string, err error) error { } type bridgeOutgoing struct { - models.RunResult - ResponseURL *url.URL -} - -func (bp bridgeOutgoing) MarshalJSON() ([]byte, error) { - anon := struct { - JobRunID *models.ID `json:"id"` - Data models.JSON `json:"data"` - ResponseURL string `json:"responseURL,omitempty"` - }{ - JobRunID: bp.CachedJobRunID, - Data: bp.Data, - ResponseURL: bp.ResponseURL.String(), - } - return json.Marshal(anon) + JobRunID string `json:"id"` + Data models.JSON `json:"data"` + ResponseURL string `json:"responseURL,omitempty"` } var zeroURL = new(url.URL) diff --git a/core/adapters/bridge_test.go b/core/adapters/bridge_test.go index de22916934a..a3f1285a2ce 100644 --- a/core/adapters/bridge_test.go +++ b/core/adapters/bridge_test.go @@ -31,7 +31,7 @@ func TestBridge_PerformEmbedsParamsInData(t *testing.T) { params := cltest.JSONFromString(t, `{"bodyParam": true}`) ba := &adapters.Bridge{BridgeType: bt, Params: ¶ms} - input := models.RunResult{ + input := models.RunInput{ Data: cltest.JSONFromString(t, `{"result":"100"}`), Status: models.RunStatusUnstarted, } @@ -55,7 +55,7 @@ func TestBridge_PerformAcceptsNonJsonObjectResponses(t *testing.T) { params := cltest.JSONFromString(t, `{"bodyParam": true}`) ba := &adapters.Bridge{BridgeType: bt, Params: ¶ms} - input := models.RunResult{ + input := models.RunInput{ Data: cltest.JSONFromString(t, `{"jobRunID": "jobID", "data": 251990120, "statusCode": 200}`), Status: models.RunStatusUnstarted, } @@ -90,7 +90,7 @@ func TestBridge_Perform_transitionsTo(t *testing.T) { _, bt := cltest.NewBridgeType(t, "auctionBidding", mock.URL) ba := &adapters.Bridge{BridgeType: bt} - input := models.RunResult{ + input := models.RunInput{ Data: cltest.JSONFromString(t, `{"result":"100"}`), Status: test.status, } @@ -145,8 +145,8 @@ func TestBridge_Perform_startANewRun(t *testing.T) { _, bt := cltest.NewBridgeType(t, "auctionBidding", mock.URL) eb := &adapters.Bridge{BridgeType: bt} - input := cltest.RunResultWithResult("lot 49") - input.CachedJobRunID = runID + input := cltest.RunInputWithResult("lot 49") + input.JobRunID = *runID result := eb.Perform(input, store) val := result.Result() @@ -158,8 +158,8 @@ func TestBridge_Perform_startANewRun(t *testing.T) { } func TestBridge_Perform_responseURL(t *testing.T) { - input := cltest.RunResultWithResult("lot 49") - input.CachedJobRunID = models.NewID() + input := cltest.RunInputWithResult("lot 49") + input.JobRunID = *models.NewID() t.Parallel() cases := []struct { @@ -170,12 +170,12 @@ func TestBridge_Perform_responseURL(t *testing.T) { { name: "basic URL", configuredURL: cltest.WebURL(t, "https://chain.link"), - want: fmt.Sprintf(`{"id":"%s","data":{"result":"lot 49"},"responseURL":"https://chain.link/v2/runs/%s"}`, input.CachedJobRunID, input.CachedJobRunID), + want: fmt.Sprintf(`{"id":"%s","data":{"result":"lot 49"},"responseURL":"https://chain.link/v2/runs/%s"}`, input.JobRunID.String(), input.JobRunID.String()), }, { name: "blank URL", configuredURL: cltest.WebURL(t, ""), - want: fmt.Sprintf(`{"id":"%s","data":{"result":"lot 49"}}`, input.CachedJobRunID), + want: fmt.Sprintf(`{"id":"%s","data":{"result":"lot 49"}}`, input.JobRunID.String()), }, } diff --git a/core/adapters/compare.go b/core/adapters/compare.go index 2180d876679..c87bda09f0a 100644 --- a/core/adapters/compare.go +++ b/core/adapters/compare.go @@ -25,7 +25,7 @@ var ( // Perform uses the Operator to check the run's result against the // specified Value. -func (c *Compare) Perform(input models.RunResult, _ *store.Store) models.RunOutput { +func (c *Compare) Perform(input models.RunInput, _ *store.Store) models.RunOutput { prevResult := input.Result() if c.Value == "" { diff --git a/core/adapters/compare_test.go b/core/adapters/compare_test.go index a5fd5139b6a..38fe2bf644b 100644 --- a/core/adapters/compare_test.go +++ b/core/adapters/compare_test.go @@ -803,7 +803,7 @@ func TestCompare_Perform(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { t.Parallel() - input := cltest.RunResultWithResult(test.input) + input := cltest.RunInputWithResult(test.input) adapter := test.adapter result := adapter.Perform(input, nil) val := result.Result() @@ -879,7 +879,7 @@ func TestCompareError_Perform(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { t.Parallel() - input := cltest.RunResultWithResult(test.input) + input := cltest.RunInputWithResult(test.input) adapter := test.adapter result := adapter.Perform(input, nil) assert.Equal(t, test.expected, result.GetError()) diff --git a/core/adapters/copy.go b/core/adapters/copy.go index 7cf27ede598..0872f8fff30 100644 --- a/core/adapters/copy.go +++ b/core/adapters/copy.go @@ -12,7 +12,7 @@ type Copy struct { } // Perform returns the copied values from the desired mapping within the `data` JSON object -func (c *Copy) Perform(input models.RunResult, store *store.Store) models.RunOutput { +func (c *Copy) Perform(input models.RunInput, store *store.Store) models.RunOutput { jp := JSONParse{Path: c.CopyPath} data, err := input.Data.Add("result", input.Data.String()) diff --git a/core/adapters/copy_test.go b/core/adapters/copy_test.go index 8e60069a30b..dbc32adf111 100644 --- a/core/adapters/copy_test.go +++ b/core/adapters/copy_test.go @@ -81,7 +81,7 @@ func TestCopy_Perform(t *testing.T) { test := tt t.Run(test.name, func(t *testing.T) { t.Parallel() - input := cltest.RunResultWithData(test.result) + input := cltest.RunInputWithResult(test.result) adapter := adapters.Copy{CopyPath: test.copyPath} result := adapter.Perform(input, nil) assert.Equal(t, test.wantData, result.Data.String()) diff --git a/core/adapters/eth_bool.go b/core/adapters/eth_bool.go index 28a049a5e8d..b4a37035292 100644 --- a/core/adapters/eth_bool.go +++ b/core/adapters/eth_bool.go @@ -17,7 +17,7 @@ type EthBool struct{} // For example, after converting the result false to hex encoded Ethereum // ABI, it would be: // "0x0000000000000000000000000000000000000000000000000000000000000000" -func (*EthBool) Perform(input models.RunResult, _ *store.Store) models.RunOutput { +func (*EthBool) Perform(input models.RunInput, _ *store.Store) models.RunOutput { if boolean(input.Result().Type) { return models.NewRunOutputCompleteWithResult(evmTrue) } diff --git a/core/adapters/eth_bool_test.go b/core/adapters/eth_bool_test.go index 248edb5605d..01664c274f3 100644 --- a/core/adapters/eth_bool_test.go +++ b/core/adapters/eth_bool_test.go @@ -29,7 +29,7 @@ func TestEthBool_Perform(t *testing.T) { test := tt t.Run(test.name, func(t *testing.T) { t.Parallel() - past := models.RunResult{ + past := models.RunInput{ Data: cltest.JSONFromString(t, test.json), } adapter := adapters.EthBool{} diff --git a/core/adapters/eth_format.go b/core/adapters/eth_format.go index eb20b3c50e6..b207569e4ff 100644 --- a/core/adapters/eth_format.go +++ b/core/adapters/eth_format.go @@ -17,7 +17,7 @@ type EthBytes32 struct{} // For example, after converting the string "16800.01" to hex encoded Ethereum // ABI, it would be: // "0x31363830302e3031000000000000000000000000000000000000000000000000" -func (*EthBytes32) Perform(input models.RunResult, _ *store.Store) models.RunOutput { +func (*EthBytes32) Perform(input models.RunInput, _ *store.Store) models.RunOutput { result := input.Result() value := common.RightPadBytes([]byte(result.String()), utils.EVMWordByteLen) hex := utils.RemoveHexPrefix(hexutil.Encode(value)) @@ -38,7 +38,7 @@ type EthInt256 struct{} // For example, after converting the string "-123.99" to hex encoded Ethereum // ABI, it would be: // "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff85" -func (*EthInt256) Perform(input models.RunResult, _ *store.Store) models.RunOutput { +func (*EthInt256) Perform(input models.RunInput, _ *store.Store) models.RunOutput { value, err := utils.EVMTranscodeInt256(input.Result()) if err != nil { return models.NewRunOutputError(err) @@ -56,7 +56,7 @@ type EthUint256 struct{} // For example, after converting the string "123.99" to hex encoded Ethereum // ABI, it would be: // "0x000000000000000000000000000000000000000000000000000000000000007b" -func (*EthUint256) Perform(input models.RunResult, _ *store.Store) models.RunOutput { +func (*EthUint256) Perform(input models.RunInput, _ *store.Store) models.RunOutput { value, err := utils.EVMTranscodeUint256(input.Result()) if err != nil { return models.NewRunOutputError(err) diff --git a/core/adapters/eth_format_test.go b/core/adapters/eth_format_test.go index e713512cd74..161f6db897c 100644 --- a/core/adapters/eth_format_test.go +++ b/core/adapters/eth_format_test.go @@ -34,7 +34,7 @@ func TestEthBytes32_Perform(t *testing.T) { test := tt t.Run(test.name, func(t *testing.T) { t.Parallel() - past := models.RunResult{ + past := models.RunInput{ Data: cltest.JSONFromString(t, test.json), } adapter := adapters.EthBytes32{} @@ -78,7 +78,7 @@ func TestEthInt256_Perform(t *testing.T) { adapter := adapters.EthInt256{} for _, test := range tests { t.Run(test.name, func(t *testing.T) { - input := models.RunResult{ + input := models.RunInput{ Data: cltest.JSONFromString(t, test.json), } result := adapter.Perform(input, nil) @@ -124,7 +124,7 @@ func TestEthUint256_Perform(t *testing.T) { adapter := adapters.EthUint256{} for _, test := range tests { t.Run(test.name, func(t *testing.T) { - input := models.RunResult{ + input := models.RunInput{ Data: cltest.JSONFromString(t, test.json), } result := adapter.Perform(input, nil) diff --git a/core/adapters/eth_tx.go b/core/adapters/eth_tx.go index 25dac5a889a..70f0e60feb7 100644 --- a/core/adapters/eth_tx.go +++ b/core/adapters/eth_tx.go @@ -37,7 +37,7 @@ type EthTx struct { // Perform creates the run result for the transaction if the existing run result // is not currently pending. Then it confirms the transaction was confirmed on // the blockchain. -func (etx *EthTx) Perform(input models.RunResult, store *strpkg.Store) models.RunOutput { +func (etx *EthTx) Perform(input models.RunInput, store *strpkg.Store) models.RunOutput { if !store.TxManager.Connected() { return models.NewRunOutputPendingConnection() } @@ -58,7 +58,7 @@ func (etx *EthTx) Perform(input models.RunResult, store *strpkg.Store) models.Ru // getTxData returns the data to save against the callback encoded according to // the dataFormat parameter in the job spec -func getTxData(e *EthTx, input models.RunResult) ([]byte, error) { +func getTxData(e *EthTx, input models.RunInput) ([]byte, error) { result := input.Result() if e.DataFormat == "" { return common.HexToHash(result.Str).Bytes(), nil @@ -80,16 +80,11 @@ func createTxRunResult( gasPrice *models.Big, gasLimit uint64, data []byte, - input models.RunResult, + input models.RunInput, store *strpkg.Store, ) models.RunOutput { - jobRunID := null.String{} - if input.CachedJobRunID != nil { - jobRunID = null.StringFrom(input.CachedJobRunID.String()) - } - tx, err := store.TxManager.CreateTxWithGas( - jobRunID, + null.StringFrom(input.JobRunID.String()), address, data, gasPrice.ToInt(), @@ -130,7 +125,7 @@ func createTxRunResult( return models.NewRunOutputPendingConfirmationsWithData(output) } -func ensureTxRunResult(input models.RunResult, str *strpkg.Store) models.RunOutput { +func ensureTxRunResult(input models.RunInput, str *strpkg.Store) models.RunOutput { val, err := input.ResultString() if err != nil { return models.NewRunOutputError(err) @@ -153,7 +148,7 @@ func ensureTxRunResult(input models.RunResult, str *strpkg.Store) models.RunOutp var output models.JSON if receipt != nil && !receipt.Unconfirmed() { - // If the tx has been confirmed, add its hash to the RunResult. + // If the tx has been confirmed, record the hash in the output hex := receipt.Hash.String() output, _ = output.Add("result", hex) output, _ = output.Add("latestOutgoingTxHash", hex) @@ -173,12 +168,12 @@ var zero = common.Hash{} func addReceiptToResult( receipt *models.TxReceipt, - input models.RunResult, + input models.RunInput, data models.JSON, ) models.RunOutput { receipts := []models.TxReceipt{} - ethereumReceipts := input.Get("ethereumReceipts").String() + ethereumReceipts := input.Data.Get("ethereumReceipts").String() if ethereumReceipts != "" { if err := json.Unmarshal([]byte(ethereumReceipts), &receipts); err != nil { logger.Errorw("Error unmarshaling ethereum Receipts", "error", err) diff --git a/core/adapters/eth_tx_abi_encode.go b/core/adapters/eth_tx_abi_encode.go index 35e190a7adc..4b09ee0b4a0 100644 --- a/core/adapters/eth_tx_abi_encode.go +++ b/core/adapters/eth_tx_abi_encode.go @@ -63,7 +63,7 @@ func (etx *EthTxABIEncode) UnmarshalJSON(data []byte) error { // Perform creates the run result for the transaction if the existing run result // is not currently pending. Then it confirms the transaction was confirmed on // the blockchain. -func (etx *EthTxABIEncode) Perform(input models.RunResult, store *strpkg.Store) models.RunOutput { +func (etx *EthTxABIEncode) Perform(input models.RunInput, store *strpkg.Store) models.RunOutput { if !store.TxManager.Connected() { return models.NewRunOutputPendingConnection() } @@ -80,8 +80,8 @@ func (etx *EthTxABIEncode) Perform(input models.RunResult, store *strpkg.Store) // abiEncode ABI-encodes the arguments passed in a RunResult's result field // according to etx.FunctionABI -func (etx *EthTxABIEncode) abiEncode(runResult *models.RunResult) ([]byte, error) { - args, ok := runResult.Get("result").Value().(map[string]interface{}) +func (etx *EthTxABIEncode) abiEncode(runResult *models.RunInput) ([]byte, error) { + args, ok := runResult.Data.Get("result").Value().(map[string]interface{}) if !ok { return nil, errors.Errorf("json result is not an object") } diff --git a/core/adapters/eth_tx_abi_encode_test.go b/core/adapters/eth_tx_abi_encode_test.go index b16762333b4..dd8f376d8e1 100644 --- a/core/adapters/eth_tx_abi_encode_test.go +++ b/core/adapters/eth_tx_abi_encode_test.go @@ -148,7 +148,7 @@ func TestEthTxABIEncodeAdapter_Perform_ConfirmedWithJSON(t *testing.T) { }) receipt := models.TxReceipt{Hash: hash, BlockNumber: cltest.Int(confirmed)} ethMock.Register("eth_getTransactionReceipt", receipt) - input := cltest.RunResultWithData(rawInput) + input := models.RunInput{Data: cltest.JSONFromString(t, rawInput)} responseData := adapterUnderTest.Perform(input, store) assert.False(t, responseData.HasError()) from := cltest.GetAccountAddress(t, store) diff --git a/core/adapters/eth_tx_test.go b/core/adapters/eth_tx_test.go index 375bb42adaa..d7dec3daecf 100644 --- a/core/adapters/eth_tx_test.go +++ b/core/adapters/eth_tx_test.go @@ -61,7 +61,7 @@ func TestEthTxAdapter_Perform_Confirmed(t *testing.T) { DataPrefix: dataPrefix, FunctionSelector: fHash, } - input := cltest.RunResultWithResult(inputValue) + input := cltest.RunInputWithResult(inputValue) data := adapter.Perform(input, store) assert.NoError(t, data.GetError()) @@ -118,7 +118,7 @@ func TestEthTxAdapter_Perform_ConfirmedWithBytes(t *testing.T) { FunctionSelector: fHash, DataFormat: adapters.DataFormatBytes, } - input := cltest.RunResultWithResult(inputValue) + input := cltest.RunInputWithResult(inputValue) data := adapter.Perform(input, store) assert.NoError(t, data.GetError()) @@ -173,7 +173,7 @@ func TestEthTxAdapter_Perform_SafeWithBytesAndNoDataPrefix(t *testing.T) { FunctionSelector: fHash, DataFormat: adapters.DataFormatBytes, } - input := cltest.RunResultWithResult(inputValue) + input := cltest.RunInputWithResult(inputValue) data := adapter.Perform(input, store) assert.NoError(t, data.GetError()) @@ -212,8 +212,8 @@ func TestEthTxAdapter_Perform_FromPendingConfirmations_StillPending(t *testing.T tx := cltest.CreateTx(t, store, from, sentAt) a := tx.Attempts[0] adapter := adapters.EthTx{} - sentResult := cltest.RunResultWithResult(a.Hash.String()) - sentResult.MarkPendingConfirmations() + sentResult := cltest.RunInputWithResult(a.Hash.String()) + sentResult.Status = models.RunStatusPendingConfirmations output := adapter.Perform(sentResult, store) @@ -249,8 +249,8 @@ func TestEthTxAdapter_Perform_FromPendingConfirmations_BumpGas(t *testing.T) { a := tx.Attempts[0] adapter := adapters.EthTx{} - sentResult := cltest.RunResultWithResult(a.Hash.String()) - sentResult.MarkPendingConfirmations() + sentResult := cltest.RunInputWithResult(a.Hash.String()) + sentResult.Status = models.RunStatusPendingConfirmations output := adapter.Perform(sentResult, store) assert.False(t, output.HasError()) @@ -293,8 +293,8 @@ func TestEthTxAdapter_Perform_FromPendingConfirmations_ConfirmCompletes(t *testi store.AddTxAttempt(tx, tx.EthTx(big.NewInt(2)), sentAt+1) a3, _ := store.AddTxAttempt(tx, tx.EthTx(big.NewInt(3)), sentAt+2) adapter := adapters.EthTx{} - sentResult := cltest.RunResultWithResult(a3.Hash.String()) - sentResult.MarkPendingConfirmations() + sentResult := cltest.RunInputWithResult(a3.Hash.String()) + sentResult.Status = models.RunStatusPendingConfirmations assert.False(t, tx.Confirmed) @@ -353,12 +353,12 @@ func TestEthTxAdapter_Perform_AppendingTransactionReceipts(t *testing.T) { a, err := store.AddTxAttempt(tx, tx.EthTx(big.NewInt(1)), sentAt) assert.NoError(t, err) adapter := adapters.EthTx{} - sentResult := cltest.RunResultWithResult(a.Hash.String()) + sentResult := cltest.RunInputWithResult(a.Hash.String()) input := sentResult - input.MarkPendingConfirmations() + input.Status = models.RunStatusPendingConfirmations previousReceipt := models.TxReceipt{Hash: cltest.NewHash(), BlockNumber: cltest.Int(sentAt - 10)} - input.Add("ethereumReceipts", []models.TxReceipt{previousReceipt}) + input.Data.Add("ethereumReceipts", []models.TxReceipt{previousReceipt}) output := adapter.Perform(input, store) assert.True(t, output.Status.Completed()) @@ -386,7 +386,7 @@ func TestEthTxAdapter_Perform_WithError(t *testing.T) { Address: cltest.NewAddress(), FunctionSelector: models.HexToFunctionSelector("0xb3f98adc"), } - input := cltest.RunResultWithResult("0x9786856756") + input := cltest.RunInputWithResult("0x9786856756") ethMock.RegisterError("eth_sendRawTransaction", "Cannot connect to nodes") output := adapter.Perform(input, store) @@ -410,7 +410,7 @@ func TestEthTxAdapter_Perform_WithErrorInvalidInput(t *testing.T) { Address: cltest.NewAddress(), FunctionSelector: models.HexToFunctionSelector("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF1"), } - input := cltest.RunResultWithResult("0x9786856756") + input := cltest.RunInputWithResult("0x9786856756") ethMock.RegisterError("eth_sendRawTransaction", "Cannot connect to nodes") output := adapter.Perform(input, store) @@ -436,7 +436,7 @@ func TestEthTxAdapter_Perform_PendingConfirmations_WithFatalErrorInTxManager(t * Address: cltest.NewAddress(), FunctionSelector: models.HexToFunctionSelector("0xb3f98adc"), } - input := cltest.RunResultWithResult(cltest.NewHash().String()) + input := cltest.RunInputWithResult(cltest.NewHash().String()) input.Status = models.RunStatusPendingConfirmations output := adapter.Perform(input, store) @@ -462,7 +462,7 @@ func TestEthTxAdapter_Perform_PendingConfirmations_WithRecoverableErrorInTxManag from := cltest.GetAccountAddress(t, store) tx := cltest.CreateTx(t, store, from, uint64(14372)) - input := cltest.RunResultWithResult(tx.Attempts[0].Hash.String()) + input := cltest.RunInputWithResult(tx.Attempts[0].Hash.String()) input.Status = models.RunStatusPendingConfirmations ethMock.RegisterError("eth_getTransactionReceipt", "Connection reset by peer") @@ -510,7 +510,7 @@ func TestEthTxAdapter_DeserializationBytesFormat(t *testing.T) { assert.True(t, ok) assert.Equal(t, ethtx.DataFormat, adapters.DataFormatBytes) - input := models.RunResult{ + input := models.RunInput{ Data: cltest.JSONFromString(t, `{"result": "hello world"}`), Status: models.RunStatusInProgress, } @@ -552,7 +552,7 @@ func TestEthTxAdapter_Perform_CustomGas(t *testing.T) { GasLimit: gasLimit, } - input := models.RunResult{ + input := models.RunInput{ Data: cltest.JSONFromString(t, `{"result": "hello world"}`), Status: models.RunStatusInProgress, } @@ -569,8 +569,7 @@ func TestEthTxAdapter_Perform_NotConnected(t *testing.T) { store := app.Store adapter := adapters.EthTx{} - input := models.RunResult{} - data := adapter.Perform(input, store) + data := adapter.Perform(models.RunInput{}, store) assert.NoError(t, data.GetError()) assert.Equal(t, models.RunStatusPendingConnection, data.Status) @@ -598,8 +597,7 @@ func TestEthTxAdapter_Perform_CreateTxWithGasErrorTreatsAsNotConnected(t *testin ).Return(nil, syscall.ETIMEDOUT) adapter := adapters.EthTx{} - input := models.RunResult{} - data := adapter.Perform(input, store) + data := adapter.Perform(models.RunInput{}, store) assert.NoError(t, data.GetError()) assert.Equal(t, models.RunStatusPendingConnection, data.Status) @@ -630,8 +628,7 @@ func TestEthTxAdapter_Perform_CheckAttemptErrorTreatsAsNotConnected(t *testing.T txmMock.EXPECT().CheckAttempt(gomock.Any(), gomock.Any()).Return(nil, strpkg.Unknown, syscall.EWOULDBLOCK) adapter := adapters.EthTx{} - input := models.RunResult{} - data := adapter.Perform(input, store) + data := adapter.Perform(models.RunInput{}, store) assert.NoError(t, data.GetError()) assert.Equal(t, models.RunStatusPendingConnection, data.Status) @@ -667,7 +664,7 @@ func TestEthTxAdapter_Perform_CreateTxWithEmptyResponseErrorTreatsAsPendingConfi ).Return(nil, strpkg.Unknown, badResponseErr) adapter := adapters.EthTx{} - output := adapter.Perform(models.RunResult{}, store) + output := adapter.Perform(models.RunInput{}, store) assert.False(t, output.HasError()) assert.Equal(t, models.RunStatusPendingConfirmations, output.Status) @@ -678,7 +675,7 @@ func TestEthTxAdapter_Perform_CreateTxWithEmptyResponseErrorTreatsAsPendingConfi gomock.Any(), ).Return(nil, strpkg.Unknown, badResponseErr) - input := models.RunResult{ + input := models.RunInput{ Data: output.Data, Status: output.Status, } @@ -722,8 +719,8 @@ func TestEthTxAdapter_Perform_NoDoubleSpendOnSendTransactionFail(t *testing.T) { DataPrefix: dataPrefix, FunctionSelector: fHash, } - input := cltest.RunResultWithResult(inputValue) - input.CachedJobRunID = models.NewID() + input := cltest.RunInputWithResult(inputValue) + input.JobRunID = *models.NewID() data := adapter.Perform(input, store) assert.Error(t, data.GetError()) @@ -798,8 +795,8 @@ func TestEthTxAdapter_Perform_NoDoubleSpendOnSendTransactionFailAndNonceChange(t DataPrefix: dataPrefix, FunctionSelector: fHash, } - input := cltest.RunResultWithResult(inputValue) - input.CachedJobRunID = models.NewID() + input := cltest.RunInputWithResult(inputValue) + input.JobRunID = *models.NewID() data := adapter.Perform(input, store) assert.Error(t, data.GetError()) diff --git a/core/adapters/http.go b/core/adapters/http.go index 8d52f61d0b2..d7c9de8a7da 100644 --- a/core/adapters/http.go +++ b/core/adapters/http.go @@ -28,7 +28,7 @@ type HTTPGet struct { // Perform ensures that the adapter's URL responds to a GET request without // errors and returns the response body as the "value" field of the result. -func (hga *HTTPGet) Perform(input models.RunResult, store *store.Store) models.RunOutput { +func (hga *HTTPGet) Perform(input models.RunInput, store *store.Store) models.RunOutput { request, err := hga.GetRequest() if err != nil { return models.NewRunOutputError(err) @@ -68,7 +68,7 @@ type HTTPPost struct { // Perform ensures that the adapter's URL responds to a POST request without // errors and returns the response body as the "value" field of the result. -func (hpa *HTTPPost) Perform(input models.RunResult, store *store.Store) models.RunOutput { +func (hpa *HTTPPost) Perform(input models.RunInput, store *store.Store) models.RunOutput { request, err := hpa.GetRequest(input.Data.String()) if err != nil { return models.NewRunOutputError(err) @@ -132,7 +132,7 @@ func setHeaders(request *http.Request, headers http.Header, contentType string) } } -func sendRequest(input models.RunResult, request *http.Request, limit int64) models.RunOutput { +func sendRequest(input models.RunInput, request *http.Request, limit int64) models.RunOutput { tr := &http.Transport{ DisableCompression: true, } diff --git a/core/adapters/http_test.go b/core/adapters/http_test.go index f7fae1aa76b..6999dc813bf 100644 --- a/core/adapters/http_test.go +++ b/core/adapters/http_test.go @@ -31,7 +31,7 @@ func TestHttpAdapters_NotAUrlError(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { - result := test.adapter.Perform(models.RunResult{}, store) + result := test.adapter.Perform(models.RunInput{}, store) assert.Equal(t, models.JSON{}, result.Data) assert.True(t, result.HasError()) }) @@ -70,7 +70,7 @@ func TestHTTPGet_Perform(t *testing.T) { for _, test := range cases { t.Run(test.name, func(t *testing.T) { - input := cltest.RunResultWithResult("inputValue") + input := cltest.RunInputWithResult("inputValue") mock, cleanup := cltest.NewHTTPMockServer(t, test.status, "GET", test.response, func(header http.Header, body string) { assert.Equal(t, ``, body) @@ -114,7 +114,7 @@ func TestHTTP_TooLarge(t *testing.T) { } for _, test := range tests { t.Run(test.verb, func(t *testing.T) { - input := cltest.RunResultWithResult("inputValue") + input := cltest.RunInputWithResult("inputValue") largePayload := "12" mock, cleanup := cltest.NewHTTPMockServer(t, http.StatusOK, test.verb, largePayload) defer cleanup() @@ -250,7 +250,7 @@ func TestHttpPost_Perform(t *testing.T) { for _, test := range cases { t.Run(test.name, func(t *testing.T) { - input := cltest.RunResultWithResult("inputVal") + input := cltest.RunInputWithResult("inputVal") mock, cleanup := cltest.NewHTTPMockServer(t, test.status, "POST", test.response, func(header http.Header, body string) { assert.Equal(t, test.wantBody, body) diff --git a/core/adapters/json_parse.go b/core/adapters/json_parse.go index eacecc743e1..dae17333003 100644 --- a/core/adapters/json_parse.go +++ b/core/adapters/json_parse.go @@ -30,7 +30,7 @@ type JSONParse struct { // } // // Then ["0","last"] would be the path, and "111" would be the returned value -func (jpa *JSONParse) Perform(input models.RunResult, _ *store.Store) models.RunOutput { +func (jpa *JSONParse) Perform(input models.RunInput, _ *store.Store) models.RunOutput { val, err := input.ResultString() if err != nil { return models.NewRunOutputError(err) @@ -66,7 +66,7 @@ func dig(js *simplejson.Json, path []string) (*simplejson.Json, error) { // only error if any keys prior to the last one in the path are nonexistent. // i.e. Path = ["errorIfNonExistent", "nullIfNonExistent"] -func moldErrorOutput(js *simplejson.Json, path []string, input models.RunResult) models.RunOutput { +func moldErrorOutput(js *simplejson.Json, path []string, input models.RunInput) models.RunOutput { if _, err := getEarlyPath(js, path); err != nil { return models.NewRunOutputError(err) } diff --git a/core/adapters/json_parse_test.go b/core/adapters/json_parse_test.go index 87cfa8dbfda..c893d6dc7d0 100644 --- a/core/adapters/json_parse_test.go +++ b/core/adapters/json_parse_test.go @@ -115,7 +115,7 @@ func TestJsonParse_Perform(t *testing.T) { for _, tt := range tests { test := tt t.Run(test.name, func(t *testing.T) { - input := cltest.RunResultWithResult(test.result) + input := cltest.RunInputWithResult(test.result) adapter := adapters.JSONParse{Path: test.path} result := adapter.Perform(input, nil) assert.Equal(t, test.wantData, result.Data.String()) diff --git a/core/adapters/multiply.go b/core/adapters/multiply.go index 08dd65d4679..d2e09563fb9 100644 --- a/core/adapters/multiply.go +++ b/core/adapters/multiply.go @@ -38,7 +38,7 @@ type Multiply struct { // // For example, if input value is "99.994" and the adapter's "times" is // set to "100", the result's value will be "9999.4". -func (ma *Multiply) Perform(input models.RunResult, _ *store.Store) models.RunOutput { +func (ma *Multiply) Perform(input models.RunInput, _ *store.Store) models.RunOutput { val := input.Result() i, ok := (&big.Float{}).SetString(val.String()) if !ok { diff --git a/core/adapters/multiply_sgx.go b/core/adapters/multiply_sgx.go index f2a17992a6c..9b6de02f693 100644 --- a/core/adapters/multiply_sgx.go +++ b/core/adapters/multiply_sgx.go @@ -46,7 +46,7 @@ type Multiply struct { // // For example, if input value is "99.994" and the adapter's "times" is // set to "100", the result's value will be "9999.4". -func (ma *Multiply) Perform(input models.RunResult, _ *store.Store) models.RunOutput { +func (ma *Multiply) Perform(input models.RunInput, _ *store.Store) models.RunOutput { adapterJSON, err := json.Marshal(ma) if err != nil { return models.NewRunOutputError(err) diff --git a/core/adapters/multiply_test.go b/core/adapters/multiply_test.go index 1d518eefb17..b16dde784c9 100644 --- a/core/adapters/multiply_test.go +++ b/core/adapters/multiply_test.go @@ -41,9 +41,7 @@ func TestMultiply_Perform(t *testing.T) { test := tt t.Run(test.name, func(t *testing.T) { t.Parallel() - input := models.RunResult{ - Data: cltest.JSONFromString(t, test.json), - } + input := models.RunInput{Data: cltest.JSONFromString(t, test.json)} adapter := adapters.Multiply{} jsonErr := json.Unmarshal([]byte(test.params), &adapter) result := adapter.Perform(input, nil) diff --git a/core/adapters/no_op.go b/core/adapters/no_op.go index 9c150ba1f94..4819ef71f6a 100644 --- a/core/adapters/no_op.go +++ b/core/adapters/no_op.go @@ -8,8 +8,8 @@ import ( // NoOp adapter type holds no fields type NoOp struct{} -// Perform returns the empty RunResult -func (noa *NoOp) Perform(input models.RunResult, _ *store.Store) models.RunOutput { +// Perform returns the input +func (noa *NoOp) Perform(input models.RunInput, _ *store.Store) models.RunOutput { val := input.Result().Value() return models.NewRunOutputCompleteWithResult(val) } @@ -19,6 +19,6 @@ type NoOpPend struct{} // Perform on this adapter type returns an empty RunResult with an // added field for the status to indicate the task is Pending. -func (noa *NoOpPend) Perform(input models.RunResult, _ *store.Store) models.RunOutput { +func (noa *NoOpPend) Perform(_ models.RunInput, _ *store.Store) models.RunOutput { return models.NewRunOutputPendingConfirmations() } diff --git a/core/adapters/random.go b/core/adapters/random.go index 2055e1aec57..d5c51b48a05 100644 --- a/core/adapters/random.go +++ b/core/adapters/random.go @@ -12,7 +12,7 @@ import ( type Random struct{} // Perform returns a random uint256 number in 0 | 2**256-1 range -func (ra *Random) Perform(input models.RunResult, _ *store.Store) models.RunOutput { +func (ra *Random) Perform(input models.RunInput, _ *store.Store) models.RunOutput { b := make([]byte, 32) _, err := rand.Read(b) if err != nil { diff --git a/core/adapters/random_test.go b/core/adapters/random_test.go index 0e39e7d45a6..3ab61c46c32 100644 --- a/core/adapters/random_test.go +++ b/core/adapters/random_test.go @@ -10,9 +10,8 @@ import ( ) func TestRandom_Perform(t *testing.T) { - input := models.RunResult{} adapter := adapters.Random{} - result := adapter.Perform(input, nil) + result := adapter.Perform(models.RunInput{}, nil) val, err := result.ResultString() assert.NoError(t, err) assert.NoError(t, result.GetError()) diff --git a/core/adapters/sleep.go b/core/adapters/sleep.go index 1d929558d31..af872b1b402 100644 --- a/core/adapters/sleep.go +++ b/core/adapters/sleep.go @@ -13,8 +13,8 @@ type Sleep struct { Until models.AnyTime `json:"until"` } -// Perform returns the input RunResult after waiting for the specified Until parameter. -func (adapter *Sleep) Perform(input models.RunResult, str *store.Store) models.RunOutput { +// Perform waits for the specified Until duration. +func (adapter *Sleep) Perform(input models.RunInput, str *store.Store) models.RunOutput { return models.NewRunOutputPendingSleep() } diff --git a/core/adapters/sleep_test.go b/core/adapters/sleep_test.go index a4e791189af..0b2e67d5593 100644 --- a/core/adapters/sleep_test.go +++ b/core/adapters/sleep_test.go @@ -18,6 +18,6 @@ func TestSleep_Perform(t *testing.T) { err := json.Unmarshal([]byte(`{"until": 1332151919}`), &adapter) assert.NoError(t, err) - result := adapter.Perform(models.RunResult{}, store) + result := adapter.Perform(models.RunInput{}, store) assert.Equal(t, string(models.RunStatusPendingSleep), string(result.Status)) } diff --git a/core/adapters/wasm.go b/core/adapters/wasm.go index 898ac9de358..f0179d3c05e 100644 --- a/core/adapters/wasm.go +++ b/core/adapters/wasm.go @@ -15,7 +15,7 @@ type Wasm struct { } // Perform ships the wasm representation to the SGX enclave where it is evaluated. -func (wasm *Wasm) Perform(input models.RunResult, _ *store.Store) models.RunOutput { +func (wasm *Wasm) Perform(input models.RunInput, _ *store.Store) models.RunOutput { err := fmt.Errorf("Wasm is not supported without SGX") return models.NewRunOutputError(err) } diff --git a/core/adapters/wasm_sgx.go b/core/adapters/wasm_sgx.go index d968040967b..5064e2f8a71 100644 --- a/core/adapters/wasm_sgx.go +++ b/core/adapters/wasm_sgx.go @@ -24,7 +24,7 @@ type Wasm struct { } // Perform ships the wasm representation to the SGX enclave where it is evaluated. -func (wasm *Wasm) Perform(input models.RunResult, _ *store.Store) models.RunOutput { +func (wasm *Wasm) Perform(input models.RunInput, _ *store.Store) models.RunOutput { adapterJSON, err := json.Marshal(wasm) if err != nil { return models.NewRunOutputError(err) diff --git a/core/internal/cltest/factories.go b/core/internal/cltest/factories.go index 761d60363fb..8a0c5b7ea19 100644 --- a/core/internal/cltest/factories.go +++ b/core/internal/cltest/factories.go @@ -439,24 +439,11 @@ func Int(val interface{}) *models.Big { } } -// RunResultWithResult creates a RunResult with given result -func RunResultWithResult(val interface{}) models.RunResult { +// RunInputWithResult creates a RunResult with given result +func RunInputWithResult(val interface{}) models.RunInput { data := models.JSON{} - data, err := data.Add("result", val) - if err != nil { - return RunResultWithError(err) - } - - return models.RunResult{Data: data} -} - -// RunResultWithData creates a run result with a given data JSON object -func RunResultWithData(val string) models.RunResult { - data, err := models.ParseJSON([]byte(val)) - if err != nil { - return RunResultWithError(err) - } - return models.RunResult{Data: data} + data, _ = data.Add("result", val) + return models.RunInput{Data: data} } // RunResultWithError creates a runresult with given error diff --git a/core/services/job_runner.go b/core/services/job_runner.go index 1b485af1869..0124538d3da 100644 --- a/core/services/job_runner.go +++ b/core/services/job_runner.go @@ -216,9 +216,13 @@ func executeTask(run *models.JobRun, currentTaskRun *models.TaskRun, store *stor return models.NewRunOutputError(err) } - currentTaskRun.Result.CachedJobRunID = run.ID - currentTaskRun.Result.Data = data - result := adapter.Perform(currentTaskRun.Result, store) + input := models.RunInput{ + JobRunID: *run.ID, + Data: data, + Status: currentTaskRun.Result.Status, + ErrorMessage: currentTaskRun.Result.ErrorMessage, + } + result := adapter.Perform(input, store) logger.Infow(fmt.Sprintf("Finished processing task %s", taskCopy.Type), []interface{}{ "task", currentTaskRun.ID, diff --git a/core/services/runs.go b/core/services/runs.go index e1c8a516b71..ccabd2bbb30 100644 --- a/core/services/runs.go +++ b/core/services/runs.go @@ -19,7 +19,7 @@ import ( func ExecuteJob( job models.JobSpec, initiator models.Initiator, - input models.RunResult, + input models.RunInput, creationHeight *big.Int, store *store.Store) (*models.JobRun, error) { return ExecuteJobWithRunRequest( @@ -37,7 +37,7 @@ func ExecuteJob( func ExecuteJobWithRunRequest( job models.JobSpec, initiator models.Initiator, - input models.RunResult, + input models.RunInput, creationHeight *big.Int, store *store.Store, runRequest models.RunRequest) (*models.JobRun, error) { @@ -74,7 +74,7 @@ func MeetsMinimumPayment( func NewRun( job models.JobSpec, initiator models.Initiator, - input models.RunResult, + input models.RunInput, currentHeight *big.Int, store *store.Store, payment *assets.Link) (*models.JobRun, error) { diff --git a/core/services/runs_test.go b/core/services/runs_test.go index f3437f47953..c8deda98d78 100644 --- a/core/services/runs_test.go +++ b/core/services/runs_test.go @@ -42,7 +42,7 @@ func TestNewRun(t *testing.T) { Type: models.InitiatorEthLog, }} - inputResult := models.RunResult{Data: input} + inputResult := models.RunInput{Data: input} run, err := services.NewRun(jobSpec, jobSpec.Initiators[0], inputResult, creationHeight, store, nil) assert.NoError(t, err) assert.Equal(t, string(models.RunStatusInProgress), string(run.Status)) @@ -155,8 +155,7 @@ func TestNewRun_taskSumPayment(t *testing.T) { Type: models.InitiatorEthLog, }} - inputResult := models.RunResult{Data: input} - + inputResult := models.RunInput{Data: input} run, err := services.NewRun(jobSpec, jobSpec.Initiators[0], inputResult, nil, store, test.payment) assert.NoError(t, err) assert.Equal(t, string(test.expectedStatus), string(run.Status)) @@ -168,8 +167,7 @@ func TestNewRun_minimumConfirmations(t *testing.T) { store, cleanup := cltest.NewStore(t) defer cleanup() - input := models.JSON{Result: gjson.Parse(`{"address":"0xdfcfc2b9200dbb10952c2b7cce60fc7260e03c6f"}`)} - inputResult := models.RunResult{Data: input} + inputResult := models.RunInput{Data: cltest.JSONFromString(t, `{"address":"0xdfcfc2b9200dbb10952c2b7cce60fc7260e03c6f"}`)} creationHeight := big.NewInt(1000) @@ -238,7 +236,7 @@ func TestNewRun_startAtAndEndAt(t *testing.T) { job.EndAt = test.endAt assert.Nil(t, store.CreateJob(&job)) - _, err := services.NewRun(job, job.Initiators[0], models.RunResult{}, nil, store, nil) + _, err := services.NewRun(job, job.Initiators[0], models.RunInput{}, nil, store, nil) if test.errored { assert.Error(t, err) } else { @@ -256,7 +254,7 @@ func TestNewRun_noTasksErrorsInsteadOfPanic(t *testing.T) { job.Tasks = []models.TaskSpec{} require.NoError(t, store.CreateJob(&job)) - jr, err := services.NewRun(job, job.Initiators[0], models.RunResult{}, nil, store, nil) + jr, err := services.NewRun(job, job.Initiators[0], models.RunInput{}, nil, store, nil) assert.NoError(t, err) assert.True(t, jr.Status.Errored()) assert.True(t, jr.Result.HasError()) @@ -647,7 +645,7 @@ func TestExecuteJob_DoesNotSaveToTaskSpec(t *testing.T) { jr, err := services.ExecuteJob( job, initr, - cltest.RunResultWithData(`{"random": "input"}`), + models.RunInput{Data: cltest.JSONFromString(t, `{"random": "input"}`)}, nil, store, ) @@ -683,7 +681,7 @@ func TestExecuteJobWithRunRequest(t *testing.T) { jr, err := services.ExecuteJobWithRunRequest( job, initr, - cltest.RunResultWithData(`{"random": "input"}`), + models.RunInput{Data: cltest.JSONFromString(t, `{"random": "input"}`)}, nil, store, rr, @@ -747,7 +745,7 @@ func TestExecuteJobWithRunRequest_fromRunLog_Happy(t *testing.T) { jr, err := services.ExecuteJobWithRunRequest( job, initr, - cltest.RunResultWithData(`{"random": "input"}`), + models.RunInput{Data: cltest.JSONFromString(t, `{"random": "input"}`)}, creationHeight, store, rr, @@ -811,7 +809,7 @@ func TestExecuteJobWithRunRequest_fromRunLog_ConnectToLaggingEthNode(t *testing. jr, err := services.ExecuteJobWithRunRequest( job, initr, - cltest.RunResultWithData(`{"random": "input"}`), + models.RunInput{Data: cltest.JSONFromString(t, `{"random": "input"}`)}, futureCreationHeight, store, rr, diff --git a/core/services/scheduler.go b/core/services/scheduler.go index 05e2b79817b..252458659e4 100644 --- a/core/services/scheduler.go +++ b/core/services/scheduler.go @@ -129,7 +129,7 @@ func (r *Recurring) AddJob(job models.JobSpec) { archived = true return } - _, err := ExecuteJob(job, initr, models.RunResult{}, nil, r.store) + _, err := ExecuteJob(job, initr, models.RunInput{}, nil, r.store) if err != nil && !expectedRecurringScheduleJobError(err) { logger.Errorw(err.Error()) } @@ -180,7 +180,7 @@ func (ot *OneTime) RunJobAt(initr models.Initiator, job models.JobSpec) { logger.Error(err.Error()) return } - _, err := ExecuteJob(job, initr, models.RunResult{}, nil, ot.Store) + _, err := ExecuteJob(job, initr, models.RunInput{}, nil, ot.Store) if err != nil { logger.Error(err.Error()) if err := ot.Store.MarkRan(&initr, false); err != nil { diff --git a/core/services/subscription.go b/core/services/subscription.go index a8bc31ecbc0..4c14164fc8e 100644 --- a/core/services/subscription.go +++ b/core/services/subscription.go @@ -158,11 +158,12 @@ func ReceiveLogRequest(store *strpkg.Store, le models.LogRequest) { } func runJob(store *strpkg.Store, le models.LogRequest, data models.JSON) { - input := models.RunResult{Data: data} + input := models.RunInput{Data: data} if err := le.ValidateRequester(); err != nil { input.SetError(err) logger.Errorw(err.Error(), le.ForLogger()...) } + rr, err := le.RunRequest() if err != nil { input.SetError(err) diff --git a/core/store/models/job_run_test.go b/core/store/models/job_run_test.go index 234b36d59c3..5a6f98c6dfd 100644 --- a/core/store/models/job_run_test.go +++ b/core/store/models/job_run_test.go @@ -46,7 +46,7 @@ func TestJobRuns_RetrievingFromDBWithData(t *testing.T) { jr := job.NewRun(initr) data := `{"result":"11850.00"}` - jr.Result = cltest.RunResultWithData(data) + jr.Result = models.RunResult{Data: cltest.JSONFromString(t, data)} err = store.CreateJobRun(&jr) assert.NoError(t, err) @@ -109,7 +109,7 @@ func TestJobRuns_SkipsEventSaveIfURLBlank(t *testing.T) { jr := job.NewRun(initr) data := `{"result":"921.02"}` - jr.Result = cltest.RunResultWithData(data) + jr.Result = models.RunResult{Data: cltest.JSONFromString(t, data)} err = store.CreateJobRun(&jr) assert.NoError(t, err) @@ -133,7 +133,7 @@ func TestForLogger(t *testing.T) { jr.JobSpecID = job.ID linkReward := assets.NewLink(5) - jr.Result = cltest.RunResultWithData(`{"result":"11850.00"}`) + jr.Result = models.RunResult{Data: cltest.JSONFromString(t, `{"result":"11850.00"}`)} jr.Payment = linkReward logsBeforeCompletion := jr.ForLogger() require.Len(t, logsBeforeCompletion, 6) diff --git a/core/store/models/run_input.go b/core/store/models/run_input.go new file mode 100644 index 00000000000..72de22074b1 --- /dev/null +++ b/core/store/models/run_input.go @@ -0,0 +1,50 @@ +package models + +import ( + "errors" + "fmt" + + "github.com/tidwall/gjson" + null "gopkg.in/guregu/null.v3" +) + +// RunInput represents the input for performing a Task +type RunInput struct { + JobRunID ID + Data JSON + Status RunStatus + ErrorMessage null.String +} + +// Result returns the result as a gjson object +func (ri RunInput) Result() gjson.Result { + return ri.Data.Get("result") +} + +// ResultString returns the string result of the Data JSON field. +func (ri RunInput) ResultString() (string, error) { + val := ri.Result() + if val.Type != gjson.String { + return "", fmt.Errorf("non string result") + } + return val.String(), nil +} + +// HasError returns true if the status is errored or the error message is set +func (ri RunInput) HasError() bool { + return ri.Status == RunStatusErrored || ri.ErrorMessage.Valid +} + +// GetError returns the error of a RunResult if it is present. +func (ri RunInput) GetError() error { + if ri.HasError() { + return errors.New(ri.ErrorMessage.ValueOrZero()) + } + return nil +} + +// SetError marks the result as errored and saves the specified error message +func (ri *RunInput) SetError(err error) { + ri.ErrorMessage = null.StringFrom(err.Error()) + ri.Status = RunStatusErrored +} diff --git a/core/web/job_runs_controller.go b/core/web/job_runs_controller.go index d9b3b096fb7..7f95a0e4d68 100644 --- a/core/web/job_runs_controller.go +++ b/core/web/job_runs_controller.go @@ -59,7 +59,7 @@ func (jrc *JobRunsController) Create(c *gin.Context) { jsonAPIError(c, http.StatusForbidden, err) } else if data, err := getRunData(c); err != nil { jsonAPIError(c, http.StatusInternalServerError, err) - } else if jr, err := services.ExecuteJob(j, *initiator, models.RunResult{Data: data}, nil, jrc.App.GetStore()); err != nil { + } else if jr, err := services.ExecuteJob(j, *initiator, models.RunInput{Data: data}, nil, jrc.App.GetStore()); err != nil { jsonAPIError(c, http.StatusInternalServerError, err) } else { jsonAPIResponse(c, presenters.JobRun{JobRun: *jr}, "job run") diff --git a/go.mod b/go.mod index 90ea062d4c4..19c509e040b 100644 --- a/go.mod +++ b/go.mod @@ -70,7 +70,7 @@ require ( golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2 golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 gopkg.in/gormigrate.v1 v1.6.0 - gopkg.in/guregu/null.v2 v2.1.2 // indirect + gopkg.in/guregu/null.v2 v2.1.2 gopkg.in/guregu/null.v3 v3.4.0 gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce // indirect ) From 376f202164e5ea9ef230d282ab53b97edb5343f9 Mon Sep 17 00:00:00 2001 From: John Barker Date: Thu, 17 Oct 2019 15:29:30 +0800 Subject: [PATCH 019/199] Trim now unused parts of RunResult --- core/internal/features_test.go | 4 +- core/store/models/job_run.go | 6 -- core/store/models/job_spec.go | 4 +- core/store/models/run_result.go | 73 ++------------------- core/store/models/run_result_test.go | 98 ---------------------------- core/store/orm/orm_test.go | 6 +- 6 files changed, 14 insertions(+), 177 deletions(-) diff --git a/core/internal/features_test.go b/core/internal/features_test.go index 047b72104d2..917f59e73d8 100644 --- a/core/internal/features_test.go +++ b/core/internal/features_test.go @@ -473,7 +473,7 @@ func TestIntegration_ExternalAdapter_RunLogInitiated(t *testing.T) { val, err := tr.Result.ResultString() assert.NoError(t, err) assert.Equal(t, eaValue, val) - res := tr.Result.Get("extra") + res := tr.Result.Data.Get("extra") assert.Equal(t, eaExtra, res.String()) assert.True(t, eth.AllCalled(), eth.Remaining()) @@ -529,7 +529,7 @@ func TestIntegration_ExternalAdapter_Copy(t *testing.T) { val, err := tr.Result.ResultString() assert.NoError(t, err) assert.Equal(t, eaPrice, val) - res := tr.Result.Get("quote") + res := tr.Result.Data.Get("quote") assert.Equal(t, eaQuote, res.String()) } diff --git a/core/store/models/job_run.go b/core/store/models/job_run.go index 87f8778a1d2..001cbc8022f 100644 --- a/core/store/models/job_run.go +++ b/core/store/models/job_run.go @@ -231,9 +231,3 @@ func (tr *TaskRun) ApplyOutput(result RunOutput) { tr.Result.Data = result.Data tr.Status = result.Status } - -// MarkPendingConfirmations marks the task's status as blocked. -func (tr *TaskRun) MarkPendingConfirmations() { - tr.Status = RunStatusPendingConfirmations - tr.Result.Status = RunStatusPendingConfirmations -} diff --git a/core/store/models/job_spec.go b/core/store/models/job_spec.go index 0263b8e2da2..641b763928d 100644 --- a/core/store/models/job_spec.go +++ b/core/store/models/job_spec.go @@ -135,7 +135,7 @@ func (j JobSpec) NewRun(i Initiator) JobRun { ID: trid, JobRunID: jrid, TaskSpec: task, - Result: RunResult{CachedTaskRunID: trid, CachedJobRunID: jrid}, + Result: RunResult{}, } } @@ -151,7 +151,7 @@ func (j JobSpec) NewRun(i Initiator) JobRun { Initiator: i, InitiatorID: i.ID, Status: RunStatusUnstarted, - Result: RunResult{CachedJobRunID: jrid}, + Result: RunResult{}, } } diff --git a/core/store/models/run_result.go b/core/store/models/run_result.go index 88f70e806b5..f6400e463de 100644 --- a/core/store/models/run_result.go +++ b/core/store/models/run_result.go @@ -11,40 +11,10 @@ import ( // RunResult keeps track of the outcome of a TaskRun or JobRun. It stores the // Data and ErrorMessage, and contains a field to track the status. type RunResult struct { - ID uint `json:"-" gorm:"primary_key;auto_increment"` - CachedJobRunID *ID `json:"jobRunId" gorm:"-"` - CachedTaskRunID *ID `json:"taskRunId" gorm:"-"` - Data JSON `json:"data" gorm:"type:text"` - Status RunStatus `json:"status"` - ErrorMessage null.String `json:"error"` -} - -func RunResultComplete(resultVal interface{}) RunResult { - var result RunResult - result.CompleteWithResult(resultVal) - return result -} - -func RunResultError(err error) RunResult { - var result RunResult - result.SetError(err) - return result -} - -// CompleteWithResult saves a value to a RunResult and marks it as completed -func (rr *RunResult) CompleteWithResult(val interface{}) { - rr.Status = RunStatusCompleted - rr.Add("result", val) -} - -// Add adds a key and result to the RunResult's JSON payload. -func (rr *RunResult) Add(key string, result interface{}) { - data, err := rr.Data.Add(key, result) - if err != nil { - rr.SetError(err) - return - } - rr.Data = data + ID uint `json:"-" gorm:"primary_key;auto_increment"` + Data JSON `json:"data" gorm:"type:text"` + Status RunStatus `json:"status"` + ErrorMessage null.String `json:"error"` } // SetError marks the result as errored and saves the specified error message @@ -53,26 +23,6 @@ func (rr *RunResult) SetError(err error) { rr.Status = RunStatusErrored } -// MarkPendingBridge sets the status to pending_bridge -func (rr *RunResult) MarkPendingBridge() { - rr.Status = RunStatusPendingBridge -} - -// MarkPendingConfirmations sets the status to pending_confirmations. -func (rr *RunResult) MarkPendingConfirmations() { - rr.Status = RunStatusPendingConfirmations -} - -// MarkPendingConnection sets the status to pending_connection. -func (rr *RunResult) MarkPendingConnection() { - rr.Status = RunStatusPendingConnection -} - -// Get searches for and returns the JSON at the given path. -func (rr *RunResult) Get(path string) gjson.Result { - return rr.Data.Get(path) -} - // ResultString returns the string result of the Data JSON field. func (rr *RunResult) ResultString() (string, error) { val := rr.Result() @@ -84,7 +34,7 @@ func (rr *RunResult) ResultString() (string, error) { // Result returns the result as a gjson object func (rr *RunResult) Result() gjson.Result { - return rr.Get("result") + return rr.Data.Get("result") } // HasError returns true if the ErrorMessage is present. @@ -104,16 +54,3 @@ func (rr *RunResult) GetError() error { } return nil } - -// Merge saves the specified result's data onto the receiving RunResult. The -// input result's data takes preference over the receivers'. -func (rr *RunResult) Merge(in RunResult) error { - var err error - rr.Data, err = rr.Data.Merge(in.Data) - if err != nil { - return err - } - rr.ErrorMessage = in.ErrorMessage - rr.Status = in.Status - return nil -} diff --git a/core/store/models/run_result_test.go b/core/store/models/run_result_test.go index 60642da44b3..57086880d6e 100644 --- a/core/store/models/run_result_test.go +++ b/core/store/models/run_result_test.go @@ -8,8 +8,6 @@ import ( "github.com/smartcontractkit/chainlink/core/internal/cltest" "github.com/smartcontractkit/chainlink/core/store/models" "github.com/stretchr/testify/assert" - "github.com/tidwall/gjson" - null "gopkg.in/guregu/null.v3" ) func TestRunResult_Value(t *testing.T) { @@ -42,33 +40,6 @@ func TestRunResult_Value(t *testing.T) { } } -func TestRunResult_Add(t *testing.T) { - t.Parallel() - - tests := []struct { - name string - json string - key string - value interface{} - want string - }{ - {"string", `{"a": "1"}`, "b", "2", `{"a": "1", "b": "2"}`}, - {"int", `{"a": "1"}`, "b", 2, `{"a": "1", "b": 2}`}, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - var data models.JSON - assert.NoError(t, json.Unmarshal([]byte(test.json), &data)) - rr := models.RunResult{Data: data} - - rr.Add(test.key, test.value) - - assert.JSONEq(t, test.want, rr.Data.String()) - }) - } -} - func TestRunResult_WithError(t *testing.T) { t.Parallel() @@ -81,72 +52,3 @@ func TestRunResult_WithError(t *testing.T) { assert.Equal(t, models.RunStatusErrored, rr.Status) assert.Equal(t, cltest.NullString("this blew up"), rr.ErrorMessage) } - -func TestRunResult_Merge(t *testing.T) { - t.Parallel() - - inProgress := models.RunStatusInProgress - pending := models.RunStatusPendingBridge - errored := models.RunStatusErrored - completed := models.RunStatusCompleted - - nullString := cltest.NullString(nil) - tests := []struct { - name string - originalData string - originalError null.String - originalStatus models.RunStatus - inData string - inError null.String - inStatus models.RunStatus - wantData string - wantErrorMessage null.String - wantStatus models.RunStatus - }{ - {"merging data", - `{"result":"old&busted","unique":"1"}`, nullString, inProgress, - `{"result":"newHotness","and":"!"}`, nullString, inProgress, - `{"result":"newHotness","unique":"1","and":"!"}`, nullString, inProgress}, - {"completed result", - `{"result":"old"}`, nullString, inProgress, - `{}`, nullString, completed, - `{"result":"old"}`, nullString, completed}, - {"error override", - `{"result":"old"}`, nullString, inProgress, - `{}`, cltest.NullString("new problem"), errored, - `{"result":"old"}`, cltest.NullString("new problem"), errored}, - {"pending override", - `{"result":"old"}`, nullString, inProgress, - `{}`, nullString, pending, - `{"result":"old"}`, nullString, pending}, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - original := models.RunResult{ - Data: models.JSON{Result: gjson.Parse(test.originalData)}, - ErrorMessage: test.originalError, - Status: test.originalStatus, - } - in := models.RunResult{ - Data: cltest.JSONFromString(t, test.inData), - ErrorMessage: test.inError, - Status: test.inStatus, - } - merged := original - merged.Merge(in) - - assert.JSONEq(t, test.originalData, original.Data.String()) - assert.Equal(t, test.originalError, original.ErrorMessage) - assert.Equal(t, test.originalStatus, original.Status) - - assert.JSONEq(t, test.inData, in.Data.String()) - assert.Equal(t, test.inError, in.ErrorMessage) - assert.Equal(t, test.inStatus, in.Status) - - assert.JSONEq(t, test.wantData, merged.Data.String()) - assert.Equal(t, test.wantErrorMessage, merged.ErrorMessage) - assert.Equal(t, test.wantStatus, merged.Status) - }) - } -} diff --git a/core/store/orm/orm_test.go b/core/store/orm/orm_test.go index e2291bcfcb6..c814d7012d0 100644 --- a/core/store/orm/orm_test.go +++ b/core/store/orm/orm_test.go @@ -1017,6 +1017,7 @@ func TestBulkDeleteRuns(t *testing.T) { // matches updated before but none of the statuses oldIncompleteRun := job.NewRun(initiator) + oldIncompleteRun.Result = models.RunResult{Data: cltest.JSONFromString(t, `{"result": 17}`)} oldIncompleteRun.Status = models.RunStatusInProgress err := orm.CreateJobRun(&oldIncompleteRun) require.NoError(t, err) @@ -1024,6 +1025,7 @@ func TestBulkDeleteRuns(t *testing.T) { // matches one of the statuses and the updated before oldCompletedRun := job.NewRun(initiator) + oldCompletedRun.Result = models.RunResult{Data: cltest.JSONFromString(t, `{"result": 19}`)} oldCompletedRun.Status = models.RunStatusCompleted err = orm.CreateJobRun(&oldCompletedRun) require.NoError(t, err) @@ -1031,6 +1033,7 @@ func TestBulkDeleteRuns(t *testing.T) { // matches one of the statuses but not the updated before newCompletedRun := job.NewRun(initiator) + newCompletedRun.Result = models.RunResult{Data: cltest.JSONFromString(t, `{"result": 23}`)} newCompletedRun.Status = models.RunStatusCompleted err = orm.CreateJobRun(&newCompletedRun) require.NoError(t, err) @@ -1038,6 +1041,7 @@ func TestBulkDeleteRuns(t *testing.T) { // matches nothing newIncompleteRun := job.NewRun(initiator) + newIncompleteRun.Result = models.RunResult{Data: cltest.JSONFromString(t, `{"result": 71}`)} newIncompleteRun.Status = models.RunStatusCompleted err = orm.CreateJobRun(&newIncompleteRun) require.NoError(t, err) @@ -1063,7 +1067,7 @@ func TestBulkDeleteRuns(t *testing.T) { var resultCount int err = db.Model(&models.RunResult{}).Count(&resultCount).Error assert.NoError(t, err) - assert.Equal(t, 6, resultCount) + assert.Equal(t, 3, resultCount) var requestCount int err = db.Model(&models.RunRequest{}).Count(&requestCount).Error From deb84dbfc2593d1d703cb7ca10260c0ab14f92e1 Mon Sep 17 00:00:00 2001 From: John Barker Date: Thu, 17 Oct 2019 16:00:10 +0800 Subject: [PATCH 020/199] Remove Status from RunResult, it's redundant --- core/internal/cltest/factories.go | 3 --- core/services/job_runner.go | 6 +++--- core/services/runs_test.go | 4 ++-- core/store/models/job_run.go | 10 ++-------- core/store/models/run_result.go | 2 -- core/store/models/run_result_test.go | 5 ----- core/web/job_runs_controller.go | 2 +- 7 files changed, 8 insertions(+), 24 deletions(-) diff --git a/core/internal/cltest/factories.go b/core/internal/cltest/factories.go index 8a0c5b7ea19..f88ecdd21b0 100644 --- a/core/internal/cltest/factories.go +++ b/core/internal/cltest/factories.go @@ -449,7 +449,6 @@ func RunInputWithResult(val interface{}) models.RunInput { // RunResultWithError creates a runresult with given error func RunResultWithError(err error) models.RunResult { return models.RunResult{ - Status: models.RunStatusErrored, ErrorMessage: null.StringFrom(err.Error()), } } @@ -457,9 +456,7 @@ func RunResultWithError(err error) models.RunResult { // MarkJobRunPendingBridge marks the jobrun as Pending Bridge Status func MarkJobRunPendingBridge(jr models.JobRun, i int) models.JobRun { jr.Status = models.RunStatusPendingBridge - jr.Result.Status = models.RunStatusPendingBridge jr.TaskRuns[i].Status = models.RunStatusPendingBridge - jr.TaskRuns[i].Result.Status = models.RunStatusPendingBridge return jr } diff --git a/core/services/job_runner.go b/core/services/job_runner.go index 0124538d3da..3c4a66b5be1 100644 --- a/core/services/job_runner.go +++ b/core/services/job_runner.go @@ -91,7 +91,7 @@ func (rm *jobRunner) resumeRunsSinceLastShutdown() error { var merr error err := rm.store.UnscopedJobRunsWithStatus(func(run *models.JobRun) { - if run.Result.Status == models.RunStatusPendingSleep { + if run.Status == models.RunStatusPendingSleep { if err := QueueSleepingTask(run, rm.store.Unscoped()); err != nil { logger.Errorw("Error resuming sleeping job", "error", err) } @@ -219,7 +219,7 @@ func executeTask(run *models.JobRun, currentTaskRun *models.TaskRun, store *stor input := models.RunInput{ JobRunID: *run.ID, Data: data, - Status: currentTaskRun.Result.Status, + Status: currentTaskRun.Status, ErrorMessage: currentTaskRun.Result.ErrorMessage, } result := adapter.Perform(input, store) @@ -256,7 +256,7 @@ func executeRun(run *models.JobRun, store *store.Store) error { return err } } else if !currentTaskRun.Status.Runnable() { - logger.Debugw("Task execution blocked", []interface{}{"run", run.ID.String(), "task", currentTaskRun.ID.String(), "state", currentTaskRun.Result.Status}...) + logger.Debugw("Task execution blocked", []interface{}{"run", run.ID.String(), "task", currentTaskRun.ID.String(), "state", currentTaskRun.Status}...) } else if currentTaskRun.Status.Unstarted() { return fmt.Errorf("run %s task %s cannot return a status of empty string or Unstarted", run.ID.String(), currentTaskRun.TaskSpec.Type) } else if futureTaskRun := run.NextTaskRun(); futureTaskRun != nil { diff --git a/core/services/runs_test.go b/core/services/runs_test.go index c8deda98d78..4f814284805 100644 --- a/core/services/runs_test.go +++ b/core/services/runs_test.go @@ -301,7 +301,7 @@ func TestResumePendingTask(t *testing.T) { assert.Error(t, err) assert.Equal(t, string(models.RunStatusInProgress), string(run.Status)) assert.Len(t, run.TaskRuns, 2) - assert.Equal(t, string(models.RunStatusCompleted), string(run.TaskRuns[0].Result.Status)) + assert.Equal(t, string(models.RunStatusCompleted), string(run.TaskRuns[0].Status)) // completed input with no remaining tasks should get marked as complete run = &models.JobRun{ @@ -315,7 +315,7 @@ func TestResumePendingTask(t *testing.T) { assert.Equal(t, string(models.RunStatusCompleted), string(run.Status)) assert.True(t, run.FinishedAt.Valid) assert.Len(t, run.TaskRuns, 1) - assert.Equal(t, string(models.RunStatusCompleted), string(run.TaskRuns[0].Result.Status)) + assert.Equal(t, string(models.RunStatusCompleted), string(run.TaskRuns[0].Status)) } func TestResumeConfirmingTask(t *testing.T) { diff --git a/core/store/models/job_run.go b/core/store/models/job_run.go index 001cbc8022f..213b6c552f5 100644 --- a/core/store/models/job_run.go +++ b/core/store/models/job_run.go @@ -113,14 +113,12 @@ func (jr *JobRun) TasksRemain() bool { // SetError sets this job run to failed and saves the error message func (jr *JobRun) SetError(err error) { jr.Result.ErrorMessage = null.StringFrom(err.Error()) - jr.Result.Status = RunStatusErrored - jr.Status = jr.Result.Status + jr.Status = RunStatusErrored jr.FinishedAt = null.TimeFrom(time.Now()) } // ApplyOutput updates the JobRun's Result and Status func (jr *JobRun) ApplyOutput(result RunOutput) error { - jr.Result.Status = result.Status jr.Result.ErrorMessage = result.ErrorMessage jr.Result.Data = result.Data jr.setStatus(result.Status) @@ -129,7 +127,6 @@ func (jr *JobRun) ApplyOutput(result RunOutput) error { // ApplyBridgeRunResult saves the input from a BridgeAdapter func (jr *JobRun) ApplyBridgeRunResult(result BridgeRunResult) error { - jr.Result.Status = result.Status jr.Result.ErrorMessage = result.ErrorMessage jr.Result.Data = result.Data jr.setStatus(result.Status) @@ -212,13 +209,11 @@ func (tr *TaskRun) ForLogger(kvs ...interface{}) []interface{} { // SetError sets this task run to failed and saves the error message func (tr *TaskRun) SetError(err error) { tr.Result.ErrorMessage = null.StringFrom(err.Error()) - tr.Result.Status = RunStatusErrored - tr.Status = tr.Result.Status + tr.Status = RunStatusErrored } // ApplyBridgeRunResult updates the TaskRun's Result and Status func (tr *TaskRun) ApplyBridgeRunResult(result BridgeRunResult) { - tr.Result.Status = result.Status tr.Result.ErrorMessage = result.ErrorMessage tr.Result.Data = result.Data tr.Status = result.Status @@ -226,7 +221,6 @@ func (tr *TaskRun) ApplyBridgeRunResult(result BridgeRunResult) { // ApplyOutput updates the TaskRun's Result and Status func (tr *TaskRun) ApplyOutput(result RunOutput) { - tr.Result.Status = result.Status tr.Result.ErrorMessage = result.ErrorMessage tr.Result.Data = result.Data tr.Status = result.Status diff --git a/core/store/models/run_result.go b/core/store/models/run_result.go index f6400e463de..ea5d2b305c2 100644 --- a/core/store/models/run_result.go +++ b/core/store/models/run_result.go @@ -13,14 +13,12 @@ import ( type RunResult struct { ID uint `json:"-" gorm:"primary_key;auto_increment"` Data JSON `json:"data" gorm:"type:text"` - Status RunStatus `json:"status"` ErrorMessage null.String `json:"error"` } // SetError marks the result as errored and saves the specified error message func (rr *RunResult) SetError(err error) { rr.ErrorMessage = null.StringFrom(err.Error()) - rr.Status = RunStatusErrored } // ResultString returns the string result of the Data JSON field. diff --git a/core/store/models/run_result_test.go b/core/store/models/run_result_test.go index 57086880d6e..b53cd570c27 100644 --- a/core/store/models/run_result_test.go +++ b/core/store/models/run_result_test.go @@ -44,11 +44,6 @@ func TestRunResult_WithError(t *testing.T) { t.Parallel() rr := models.RunResult{} - - assert.Equal(t, models.RunStatusUnstarted, rr.Status) - rr.SetError(errors.New("this blew up")) - - assert.Equal(t, models.RunStatusErrored, rr.Status) assert.Equal(t, cltest.NullString("this blew up"), rr.ErrorMessage) } diff --git a/core/web/job_runs_controller.go b/core/web/job_runs_controller.go index 7f95a0e4d68..d29b6cd8ddd 100644 --- a/core/web/job_runs_controller.go +++ b/core/web/job_runs_controller.go @@ -123,7 +123,7 @@ func (jrc *JobRunsController) Update(c *gin.Context) { jsonAPIError(c, http.StatusNotFound, errors.New("Job Run not found")) } else if err != nil { jsonAPIError(c, http.StatusInternalServerError, err) - } else if !jr.Result.Status.PendingBridge() { + } else if !jr.Status.PendingBridge() { jsonAPIError(c, http.StatusMethodNotAllowed, errors.New("Cannot resume a job run that isn't pending")) } else if err := c.ShouldBindJSON(&brr); err != nil { jsonAPIError(c, http.StatusInternalServerError, err) From 158b7ee705badae1642016bd3d7072955fefbb00 Mon Sep 17 00:00:00 2001 From: John Barker Date: Thu, 17 Oct 2019 16:02:25 +0800 Subject: [PATCH 021/199] RunStatusUnstarted is the default, not need to construct --- core/adapters/bridge_test.go | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/core/adapters/bridge_test.go b/core/adapters/bridge_test.go index a3f1285a2ce..06b0e58e621 100644 --- a/core/adapters/bridge_test.go +++ b/core/adapters/bridge_test.go @@ -31,10 +31,7 @@ func TestBridge_PerformEmbedsParamsInData(t *testing.T) { params := cltest.JSONFromString(t, `{"bodyParam": true}`) ba := &adapters.Bridge{BridgeType: bt, Params: ¶ms} - input := models.RunInput{ - Data: cltest.JSONFromString(t, `{"result":"100"}`), - Status: models.RunStatusUnstarted, - } + input := models.RunInput{Data: cltest.JSONFromString(t, `{"result":"100"}`)} ba.Perform(input, store) assert.Equal(t, `{"bodyParam":true,"result":"100"}`, data) @@ -56,8 +53,7 @@ func TestBridge_PerformAcceptsNonJsonObjectResponses(t *testing.T) { ba := &adapters.Bridge{BridgeType: bt, Params: ¶ms} input := models.RunInput{ - Data: cltest.JSONFromString(t, `{"jobRunID": "jobID", "data": 251990120, "statusCode": 200}`), - Status: models.RunStatusUnstarted, + Data: cltest.JSONFromString(t, `{"jobRunID": "jobID", "data": 251990120, "statusCode": 200}`), } result := ba.Perform(input, store) assert.NoError(t, result.GetError()) From 833293f34c499e10395aadedbd3a04b2c00d73ab Mon Sep 17 00:00:00 2001 From: John Barker Date: Thu, 17 Oct 2019 16:24:32 +0800 Subject: [PATCH 022/199] Preserve data when BridgeAdapter resumes with no-op --- core/store/models/run_output.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/store/models/run_output.go b/core/store/models/run_output.go index d7bebba8f41..43e5c116c88 100644 --- a/core/store/models/run_output.go +++ b/core/store/models/run_output.go @@ -84,12 +84,12 @@ func NewRunOutputPendingConnectionWithData(data JSON) RunOutput { } } -// FIXME: This seems a bit nonsensical but is used by the BridgeAdapter? // NewRunOutputPendingConnection returns a new RunOutput that indicates the // task is still in progress -func NewRunOutputInProgress() RunOutput { +func NewRunOutputInProgress(data JSON) RunOutput { return RunOutput{ Status: RunStatusInProgress, + Data: data, } } From b9c08afec90d43afcfb62e4b03e548181083d61d Mon Sep 17 00:00:00 2001 From: John Barker Date: Thu, 17 Oct 2019 17:03:25 +0800 Subject: [PATCH 023/199] Save BridgeRunResult in a clearer way --- core/adapters/bridge.go | 17 +++++++---------- core/store/models/bridge_run_result.go | 11 ++++++++++- 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/core/adapters/bridge.go b/core/adapters/bridge.go index 616043e4c8f..d3d5799e9d3 100644 --- a/core/adapters/bridge.go +++ b/core/adapters/bridge.go @@ -68,21 +68,18 @@ func responseToRunResult(body []byte, input models.RunInput) models.RunOutput { return models.NewRunOutputError(baRunResultError("unmarshaling JSON", err)) } - status := brr.Status - // FIXME: This code is bizarre to me, what is the intention? It looks like it - // has a bunch of edge cases... + if brr.HasError() { + return models.NewRunOutputError(brr.GetError()) + } + var data models.JSON if brr.Data.Exists() && !brr.Data.IsObject() { data, _ = data.Add("result", brr.Data.String()) - status = models.RunStatusCompleted + } else { + data = brr.Data } - data, _ = data.Merge(brr.Data) - return models.RunOutput{ - Status: status, - ErrorMessage: brr.ErrorMessage, - Data: data, - } + return models.NewRunOutputComplete(data) } func (ba *Bridge) postToExternalAdapter(input models.RunInput, bridgeResponseURL *url.URL) ([]byte, error) { diff --git a/core/store/models/bridge_run_result.go b/core/store/models/bridge_run_result.go index 16eb417eab5..ae6da310b62 100644 --- a/core/store/models/bridge_run_result.go +++ b/core/store/models/bridge_run_result.go @@ -2,6 +2,7 @@ package models import ( "encoding/json" + "errors" null "gopkg.in/guregu/null.v3" ) @@ -35,6 +36,14 @@ func (brr *BridgeRunResult) UnmarshalJSON(input []byte) error { } // HasError returns true if the status is errored or the error message is set -func (brr *BridgeRunResult) HasError() bool { +func (brr BridgeRunResult) HasError() bool { return brr.Status == RunStatusErrored || brr.ErrorMessage.Valid } + +// GetError returns the error of a BridgeRunResult if it is present. +func (brr BridgeRunResult) GetError() error { + if brr.HasError() { + return errors.New(brr.ErrorMessage.ValueOrZero()) + } + return nil +} From d7b090184c88d3a6689b4c279d27868de9136e18 Mon Sep 17 00:00:00 2001 From: John Barker Date: Thu, 17 Oct 2019 17:30:30 +0800 Subject: [PATCH 024/199] Clearer responseToRunResult --- core/adapters/adapter.go | 2 +- core/adapters/bridge.go | 20 ++++++++--------- core/adapters/bridge_test.go | 6 ++--- core/store/models/run_output.go | 40 ++++++++++++--------------------- 4 files changed, 27 insertions(+), 41 deletions(-) diff --git a/core/adapters/adapter.go b/core/adapters/adapter.go index 6f8790ea038..58fcd3d3ae2 100644 --- a/core/adapters/adapter.go +++ b/core/adapters/adapter.go @@ -135,7 +135,7 @@ func For(task models.TaskSpec, store *store.Store) (*PipelineAdapter, error) { if err != nil { return nil, fmt.Errorf("%s is not a supported adapter type", task.Type) } - b := Bridge{BridgeType: &bt, Params: &task.Params} + b := Bridge{BridgeType: &bt, Params: task.Params} ba = &b mic = b.Confirmations mcp = bt.MinimumContractPayment diff --git a/core/adapters/bridge.go b/core/adapters/bridge.go index d3d5799e9d3..2f7e1007c6b 100644 --- a/core/adapters/bridge.go +++ b/core/adapters/bridge.go @@ -16,7 +16,7 @@ import ( // adapters, allowing for custom computations to be executed and included in runs. type Bridge struct { *models.BridgeType - Params *models.JSON + Params models.JSON } // Perform sends a POST request containing the JSON of the input to the @@ -40,11 +40,8 @@ func (ba *Bridge) Perform(input models.RunInput, store *store.Store) models.RunO } func (ba *Bridge) handleNewRun(input models.RunInput, bridgeResponseURL *url.URL) models.RunOutput { - if ba.Params == nil { - ba.Params = new(models.JSON) - } var err error - if input.Data, err = input.Data.Merge(*ba.Params); err != nil { + if input.Data, err = input.Data.Merge(ba.Params); err != nil { return models.NewRunOutputError(baRunResultError("handling data param", err)) } @@ -72,14 +69,15 @@ func responseToRunResult(body []byte, input models.RunInput) models.RunOutput { return models.NewRunOutputError(brr.GetError()) } - var data models.JSON - if brr.Data.Exists() && !brr.Data.IsObject() { - data, _ = data.Add("result", brr.Data.String()) - } else { - data = brr.Data + if brr.ExternalPending { + return models.NewRunOutputPendingBridge() + } + + if brr.Data.IsObject() { + return models.NewRunOutputComplete(brr.Data) } - return models.NewRunOutputComplete(data) + return models.NewRunOutputCompleteWithResult(brr.Data.String()) } func (ba *Bridge) postToExternalAdapter(input models.RunInput, bridgeResponseURL *url.URL) ([]byte, error) { diff --git a/core/adapters/bridge_test.go b/core/adapters/bridge_test.go index 06b0e58e621..ea5344743fc 100644 --- a/core/adapters/bridge_test.go +++ b/core/adapters/bridge_test.go @@ -29,7 +29,7 @@ func TestBridge_PerformEmbedsParamsInData(t *testing.T) { _, bt := cltest.NewBridgeType(t, "auctionBidding", mock.URL) params := cltest.JSONFromString(t, `{"bodyParam": true}`) - ba := &adapters.Bridge{BridgeType: bt, Params: ¶ms} + ba := &adapters.Bridge{BridgeType: bt, Params: params} input := models.RunInput{Data: cltest.JSONFromString(t, `{"result":"100"}`)} ba.Perform(input, store) @@ -50,7 +50,7 @@ func TestBridge_PerformAcceptsNonJsonObjectResponses(t *testing.T) { _, bt := cltest.NewBridgeType(t, "auctionBidding", mock.URL) params := cltest.JSONFromString(t, `{"bodyParam": true}`) - ba := &adapters.Bridge{BridgeType: bt, Params: ¶ms} + ba := &adapters.Bridge{BridgeType: bt, Params: params} input := models.RunInput{ Data: cltest.JSONFromString(t, `{"jobRunID": "jobID", "data": 251990120, "statusCode": 200}`), @@ -72,7 +72,7 @@ func TestBridge_Perform_transitionsTo(t *testing.T) { }{ {"from pending bridge", models.RunStatusPendingBridge, models.RunStatusInProgress, `{"result":"100"}`}, {"from errored", models.RunStatusErrored, models.RunStatusErrored, `{"result":"100"}`}, - {"from in progress", models.RunStatusInProgress, models.RunStatusPendingBridge, `{}`}, + {"from in progress", models.RunStatusInProgress, models.RunStatusPendingBridge, ""}, {"from completed", models.RunStatusCompleted, models.RunStatusCompleted, `{"result":"100"}`}, } diff --git a/core/store/models/run_output.go b/core/store/models/run_output.go index 43e5c116c88..ea65b9a1775 100644 --- a/core/store/models/run_output.go +++ b/core/store/models/run_output.go @@ -34,63 +34,51 @@ func NewRunOutputCompleteWithResult(resultVal interface{}) RunOutput { // NewRunOutputComplete returns a new RunOutput that is complete and contains // raw data func NewRunOutputComplete(data JSON) RunOutput { - return RunOutput{ - Status: RunStatusCompleted, - Data: data, - } + return RunOutput{Status: RunStatusCompleted, Data: data} } // NewRunOutputPendingSleep returns a new RunOutput that indicates the task is // sleeping func NewRunOutputPendingSleep() RunOutput { - return RunOutput{ - Status: RunStatusPendingSleep, - } + return RunOutput{Status: RunStatusPendingSleep} } // NewRunOutputPendingConfirmations returns a new RunOutput that indicates the // task is pending confirmations func NewRunOutputPendingConfirmations() RunOutput { - return RunOutput{ - Status: RunStatusPendingConfirmations, - } + return RunOutput{Status: RunStatusPendingConfirmations} } // NewRunOutputPendingConfirmationsWithData returns a new RunOutput that // indicates the task is pending confirmations but also has some data that // needs to be fed in on next invocation func NewRunOutputPendingConfirmationsWithData(data JSON) RunOutput { - return RunOutput{ - Status: RunStatusPendingConfirmations, - Data: data, - } + return RunOutput{Status: RunStatusPendingConfirmations, Data: data} } // NewRunOutputPendingConnection returns a new RunOutput that indicates the // task got disconnected func NewRunOutputPendingConnection() RunOutput { - return RunOutput{ - Status: RunStatusPendingConnection, - } + return RunOutput{Status: RunStatusPendingConnection} } // NewRunOutputPendingConfirmationsWithData returns a new RunOutput that // indicates the task got disconnected but also has some data that needs to be // fed in on next invocation func NewRunOutputPendingConnectionWithData(data JSON) RunOutput { - return RunOutput{ - Status: RunStatusPendingConnection, - Data: data, - } + return RunOutput{Status: RunStatusPendingConnection, Data: data} } -// NewRunOutputPendingConnection returns a new RunOutput that indicates the +// NewRunOutputInProgress returns a new RunOutput that indicates the // task is still in progress func NewRunOutputInProgress(data JSON) RunOutput { - return RunOutput{ - Status: RunStatusInProgress, - Data: data, - } + return RunOutput{Status: RunStatusInProgress, Data: data} +} + +// NewRunOutputPendingBridge returns a new RunOutput that indicates the +// task is still in progress +func NewRunOutputPendingBridge() RunOutput { + return RunOutput{Status: RunStatusPendingBridge} } // HasError returns true if the status is errored or the error message is set From bd22f995755581dc77b81b60e2cda89b2a9ef575 Mon Sep 17 00:00:00 2001 From: John Barker Date: Fri, 18 Oct 2019 12:14:57 +0800 Subject: [PATCH 025/199] Encode RunInput functionally for TestEt... --- core/adapters/eth_tx_test.go | 12 ++++++++---- core/internal/cltest/factories.go | 8 +++++++- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/core/adapters/eth_tx_test.go b/core/adapters/eth_tx_test.go index d7dec3daecf..08f27bfd685 100644 --- a/core/adapters/eth_tx_test.go +++ b/core/adapters/eth_tx_test.go @@ -353,12 +353,16 @@ func TestEthTxAdapter_Perform_AppendingTransactionReceipts(t *testing.T) { a, err := store.AddTxAttempt(tx, tx.EthTx(big.NewInt(1)), sentAt) assert.NoError(t, err) adapter := adapters.EthTx{} - sentResult := cltest.RunInputWithResult(a.Hash.String()) - input := sentResult - input.Status = models.RunStatusPendingConfirmations previousReceipt := models.TxReceipt{Hash: cltest.NewHash(), BlockNumber: cltest.Int(sentAt - 10)} - input.Data.Add("ethereumReceipts", []models.TxReceipt{previousReceipt}) + data := map[string]interface{}{ + "result": a.Hash.String(), + "ethereumReceipts": []models.TxReceipt{previousReceipt}, + } + input := models.RunInput{ + Data: cltest.MustJSONMarshal(t, data), + Status: models.RunStatusPendingConfirmations, + } output := adapter.Perform(input, store) assert.True(t, output.Status.Completed()) diff --git a/core/internal/cltest/factories.go b/core/internal/cltest/factories.go index f88ecdd21b0..2dff8f6576a 100644 --- a/core/internal/cltest/factories.go +++ b/core/internal/cltest/factories.go @@ -532,6 +532,12 @@ func CreateServiceAgreementViaWeb( responseSA := models.ServiceAgreement{} err := ParseJSONAPIResponse(t, resp, &responseSA) require.NoError(t, err) +} - return FindServiceAgreement(t, app.Store, responseSA.ID) +func MustJSONMarshal(t *testing.T, input interface{}) models.JSON { + bytes, err := json.Marshal(input) + require.NoError(t, err) + json, err := models.ParseJSON(bytes) + require.NoError(t, err) + return json } From 48fc9b839d31cc539dca3c9085a07ab96f4cecfb Mon Sep 17 00:00:00 2001 From: John Barker Date: Mon, 21 Oct 2019 16:55:24 +0800 Subject: [PATCH 026/199] Don't double encode result in input to Copy --- core/adapters/copy.go | 8 +++----- core/adapters/copy_test.go | 8 ++++---- core/internal/features_test.go | 2 -- 3 files changed, 7 insertions(+), 11 deletions(-) diff --git a/core/adapters/copy.go b/core/adapters/copy.go index 0872f8fff30..3fba06a0a94 100644 --- a/core/adapters/copy.go +++ b/core/adapters/copy.go @@ -13,13 +13,11 @@ type Copy struct { // Perform returns the copied values from the desired mapping within the `data` JSON object func (c *Copy) Perform(input models.RunInput, store *store.Store) models.RunOutput { - jp := JSONParse{Path: c.CopyPath} - - data, err := input.Data.Add("result", input.Data.String()) + data, err := models.JSON{}.Add("result", input.Data.String()) if err != nil { return models.NewRunOutputError(err) } - input.Data = data - return jp.Perform(input, store) + jp := JSONParse{Path: c.CopyPath} + return jp.Perform(models.RunInput{Data: data}, store) } diff --git a/core/adapters/copy_test.go b/core/adapters/copy_test.go index dbc32adf111..25520498468 100644 --- a/core/adapters/copy_test.go +++ b/core/adapters/copy_test.go @@ -13,7 +13,7 @@ import ( func TestCopy_Perform(t *testing.T) { tests := []struct { name string - result string + input string copyPath []string wantData string wantStatus models.RunStatus @@ -81,16 +81,16 @@ func TestCopy_Perform(t *testing.T) { test := tt t.Run(test.name, func(t *testing.T) { t.Parallel() - input := cltest.RunInputWithResult(test.result) + input := models.RunInput{Data: cltest.JSONFromString(t, test.input)} adapter := adapters.Copy{CopyPath: test.copyPath} result := adapter.Perform(input, nil) assert.Equal(t, test.wantData, result.Data.String()) assert.Equal(t, test.wantStatus, result.Status) if test.wantResultError { - assert.NotNil(t, result.GetError()) + assert.Error(t, result.GetError()) } else { - assert.Nil(t, result.GetError()) + assert.NoError(t, result.GetError()) } }) } diff --git a/core/internal/features_test.go b/core/internal/features_test.go index 917f59e73d8..4d9876e3d37 100644 --- a/core/internal/features_test.go +++ b/core/internal/features_test.go @@ -529,8 +529,6 @@ func TestIntegration_ExternalAdapter_Copy(t *testing.T) { val, err := tr.Result.ResultString() assert.NoError(t, err) assert.Equal(t, eaPrice, val) - res := tr.Result.Data.Get("quote") - assert.Equal(t, eaQuote, res.String()) } // This test ensures that an bridge adapter task is resumed from pending after From b7df9e0fd3293cf2bcfbb9c1a3a8e2887e76e646 Mon Sep 17 00:00:00 2001 From: John Barker Date: Tue, 22 Oct 2019 14:48:58 +0800 Subject: [PATCH 027/199] Interrogate status on JobRun in ethlog_test --- integration/ethlog_test | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integration/ethlog_test b/integration/ethlog_test index a2e0e476eff..4a8a48dde8e 100755 --- a/integration/ethlog_test +++ b/integration/ethlog_test @@ -26,4 +26,4 @@ assert "Jobs count" "chainlink -j jobs list | jq length" $expected_job_count assert "EthLog Runs count" "chainlink -j runs list --jobid $jid | jq length" 1 # Check that the run completed -assert "Run completed" 'chainlink -j runs list --jobid $jid | jq ".[].result.status" | sed s/\"//g' completed +assert "Run completed" 'chainlink -j runs list --jobid $jid | jq ".[].status" | sed s/\"//g' completed From 3cee4f768923cd013f19b38e5ab26107f5a3777a Mon Sep 17 00:00:00 2001 From: John Barker Date: Tue, 22 Oct 2019 20:40:15 +0800 Subject: [PATCH 028/199] Encapsulate members for RunInputOutput --- core/adapters/adapter.go | 2 +- core/adapters/bridge.go | 42 ++++--- core/adapters/bridge_test.go | 59 ++++----- core/adapters/compare_test.go | 9 +- core/adapters/copy.go | 5 +- core/adapters/copy_test.go | 10 +- core/adapters/eth_bool_test.go | 10 +- core/adapters/eth_format_test.go | 31 ++--- core/adapters/eth_tx.go | 6 +- core/adapters/eth_tx_abi_encode.go | 6 +- core/adapters/eth_tx_abi_encode_test.go | 2 +- core/adapters/eth_tx_test.go | 153 +++++++++++------------- core/adapters/http.go | 2 +- core/adapters/http_test.go | 31 +++-- core/adapters/json_parse_test.go | 10 +- core/adapters/multiply_test.go | 8 +- core/adapters/random_test.go | 5 +- core/adapters/sleep_test.go | 6 +- core/cmd/renderer.go | 2 +- core/internal/cltest/cltest.go | 10 ++ core/internal/cltest/factories.go | 14 --- core/services/job_runner.go | 11 +- core/services/runs.go | 31 +++-- core/services/runs_test.go | 50 +++----- core/services/scheduler.go | 4 +- core/services/subscription.go | 27 +++-- core/store/models/job_run.go | 28 ++--- core/store/models/job_run_test.go | 20 ++-- core/store/models/run_input.go | 66 +++++++--- core/store/models/run_output.go | 68 ++++++----- core/store/models/run_result.go | 28 ++--- core/store/models/run_result_test.go | 3 +- core/web/job_runs_controller.go | 2 +- go.mod | 2 +- 34 files changed, 383 insertions(+), 380 deletions(-) diff --git a/core/adapters/adapter.go b/core/adapters/adapter.go index 58fcd3d3ae2..6f2da1646b8 100644 --- a/core/adapters/adapter.go +++ b/core/adapters/adapter.go @@ -135,7 +135,7 @@ func For(task models.TaskSpec, store *store.Store) (*PipelineAdapter, error) { if err != nil { return nil, fmt.Errorf("%s is not a supported adapter type", task.Type) } - b := Bridge{BridgeType: &bt, Params: task.Params} + b := Bridge{BridgeType: bt, Params: task.Params} ba = &b mic = b.Confirmations mcp = bt.MinimumContractPayment diff --git a/core/adapters/bridge.go b/core/adapters/bridge.go index 2f7e1007c6b..9f681ff18ac 100644 --- a/core/adapters/bridge.go +++ b/core/adapters/bridge.go @@ -8,6 +8,7 @@ import ( "net/http" "net/url" + "github.com/pkg/errors" "github.com/smartcontractkit/chainlink/core/store" "github.com/smartcontractkit/chainlink/core/store/models" ) @@ -15,7 +16,7 @@ import ( // Bridge adapter is responsible for connecting the task pipeline to external // adapters, allowing for custom computations to be executed and included in runs. type Bridge struct { - *models.BridgeType + models.BridgeType Params models.JSON } @@ -27,27 +28,25 @@ type Bridge struct { // If the Perform is resumed with a pending RunResult, the RunResult is marked // not pending and the RunResult is returned. func (ba *Bridge) Perform(input models.RunInput, store *store.Store) models.RunOutput { - if input.Status.Finished() { - return models.RunOutput{ - Data: input.Data, - Status: input.Status, - ErrorMessage: input.ErrorMessage, - } - } else if input.Status.PendingBridge() { - return models.NewRunOutputInProgress(input.Data) + if input.Status().Completed() { + return models.NewRunOutputComplete(input.Data()) + } else if input.Status().Errored() { + return models.NewRunOutputError(input.Error()) + } else if input.Status().PendingBridge() { + return models.NewRunOutputInProgress(input.Data()) } return ba.handleNewRun(input, store.Config.BridgeResponseURL()) } func (ba *Bridge) handleNewRun(input models.RunInput, bridgeResponseURL *url.URL) models.RunOutput { - var err error - if input.Data, err = input.Data.Merge(ba.Params); err != nil { + data, err := input.Data().Merge(ba.Params) + if err != nil { return models.NewRunOutputError(baRunResultError("handling data param", err)) } responseURL := bridgeResponseURL if *responseURL != *zeroURL { - responseURL.Path += fmt.Sprintf("/v2/runs/%s", input.JobRunID.String()) + responseURL.Path += fmt.Sprintf("/v2/runs/%s", input.JobRunID().String()) } body, err := ba.postToExternalAdapter(input, responseURL) @@ -55,10 +54,11 @@ func (ba *Bridge) handleNewRun(input models.RunInput, bridgeResponseURL *url.URL return models.NewRunOutputError(baRunResultError("post to external adapter", err)) } - return responseToRunResult(body, input) + input = *models.NewRunInput(input.JobRunID(), data, input.Status()) + return ba.responseToRunResult(body, input) } -func responseToRunResult(body []byte, input models.RunInput) models.RunOutput { +func (ba *Bridge) responseToRunResult(body []byte, input models.RunInput) models.RunOutput { var brr models.BridgeRunResult err := json.Unmarshal(body, &brr) if err != nil { @@ -74,14 +74,24 @@ func responseToRunResult(body []byte, input models.RunInput) models.RunOutput { } if brr.Data.IsObject() { - return models.NewRunOutputComplete(brr.Data) + data, err := ba.Params.Merge(brr.Data) + if err != nil { + return models.NewRunOutputError(baRunResultError("handling data param", err)) + } + + return models.NewRunOutputComplete(data) } return models.NewRunOutputCompleteWithResult(brr.Data.String()) } func (ba *Bridge) postToExternalAdapter(input models.RunInput, bridgeResponseURL *url.URL) ([]byte, error) { - outgoing := bridgeOutgoing{JobRunID: input.JobRunID.String(), Data: input.Data} + data, err := input.Data().Merge(ba.Params) + if err != nil { + return nil, errors.Wrap(err, "error merging bridge params with input params") + } + + outgoing := bridgeOutgoing{JobRunID: input.JobRunID().String(), Data: data} if bridgeResponseURL != nil { outgoing.ResponseURL = bridgeResponseURL.String() } diff --git a/core/adapters/bridge_test.go b/core/adapters/bridge_test.go index ea5344743fc..5a2f3ce98fd 100644 --- a/core/adapters/bridge_test.go +++ b/core/adapters/bridge_test.go @@ -9,6 +9,7 @@ import ( "github.com/smartcontractkit/chainlink/core/internal/cltest" "github.com/smartcontractkit/chainlink/core/store/models" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestBridge_PerformEmbedsParamsInData(t *testing.T) { @@ -29,11 +30,11 @@ func TestBridge_PerformEmbedsParamsInData(t *testing.T) { _, bt := cltest.NewBridgeType(t, "auctionBidding", mock.URL) params := cltest.JSONFromString(t, `{"bodyParam": true}`) - ba := &adapters.Bridge{BridgeType: bt, Params: params} - - input := models.RunInput{Data: cltest.JSONFromString(t, `{"result":"100"}`)} - ba.Perform(input, store) + ba := &adapters.Bridge{BridgeType: *bt, Params: params} + input := cltest.NewRunInputWithResult("100") + result := ba.Perform(input, store) + require.NoError(t, result.Error()) assert.Equal(t, `{"bodyParam":true,"result":"100"}`, data) assert.Equal(t, "Bearer "+bt.OutgoingToken, token) } @@ -42,21 +43,20 @@ func TestBridge_PerformAcceptsNonJsonObjectResponses(t *testing.T) { store, cleanup := cltest.NewStore(t) defer cleanup() store.Config.Set("BRIDGE_RESPONSE_URL", cltest.WebURL(t, "")) + jobRunID := models.NewID() - mock, cleanup := cltest.NewHTTPMockServer(t, http.StatusOK, "POST", fmt.Sprintf(`{"jobRunID": "%s", "data": 251990120, "statusCode": 200}`, models.NewID()), + mock, cleanup := cltest.NewHTTPMockServer(t, http.StatusOK, "POST", fmt.Sprintf(`{"jobRunID": "%s", "data": 251990120, "statusCode": 200}`, jobRunID.String()), func(h http.Header, b string) {}, ) defer cleanup() _, bt := cltest.NewBridgeType(t, "auctionBidding", mock.URL) params := cltest.JSONFromString(t, `{"bodyParam": true}`) - ba := &adapters.Bridge{BridgeType: bt, Params: params} + ba := &adapters.Bridge{BridgeType: *bt, Params: params} - input := models.RunInput{ - Data: cltest.JSONFromString(t, `{"jobRunID": "jobID", "data": 251990120, "statusCode": 200}`), - } + input := *models.NewRunInput(jobRunID, cltest.JSONFromString(t, `{"jobRunID": "jobID", "data": 251990120, "statusCode": 200}`), models.RunStatusUnstarted) result := ba.Perform(input, store) - assert.NoError(t, result.GetError()) + require.NoError(t, result.Error()) resultString, err := result.ResultString() assert.NoError(t, err) assert.Equal(t, "251990120", resultString) @@ -71,7 +71,7 @@ func TestBridge_Perform_transitionsTo(t *testing.T) { result string }{ {"from pending bridge", models.RunStatusPendingBridge, models.RunStatusInProgress, `{"result":"100"}`}, - {"from errored", models.RunStatusErrored, models.RunStatusErrored, `{"result":"100"}`}, + {"from errored", models.RunStatusErrored, models.RunStatusErrored, ""}, {"from in progress", models.RunStatusInProgress, models.RunStatusPendingBridge, ""}, {"from completed", models.RunStatusCompleted, models.RunStatusCompleted, `{"result":"100"}`}, } @@ -84,23 +84,16 @@ func TestBridge_Perform_transitionsTo(t *testing.T) { t.Run(test.name, func(t *testing.T) { mock, _ := cltest.NewHTTPMockServer(t, http.StatusOK, "POST", `{"pending": true}`) _, bt := cltest.NewBridgeType(t, "auctionBidding", mock.URL) - ba := &adapters.Bridge{BridgeType: bt} - - input := models.RunInput{ - Data: cltest.JSONFromString(t, `{"result":"100"}`), - Status: test.status, - } + ba := &adapters.Bridge{BridgeType: *bt} + input := *models.NewRunInputWithResult(models.NewID(), "100", test.status) result := ba.Perform(input, store) - assert.Equal(t, test.result, result.Data.String()) - assert.Equal(t, test.wantStatus, result.Status) - if test.wantStatus.Errored() || test.wantStatus.Completed() { - outputWanted := models.RunOutput{ - Data: input.Data, - Status: input.Status, - } - assert.Equal(t, outputWanted, result) + assert.Equal(t, test.result, result.Data().String()) + assert.Equal(t, test.wantStatus, result.Status()) + if test.wantStatus.Completed() { + assert.Equal(t, input.Data(), result.Data()) + assert.Equal(t, input.Status(), result.Status()) } }) } @@ -140,22 +133,20 @@ func TestBridge_Perform_startANewRun(t *testing.T) { defer ensureCalled() _, bt := cltest.NewBridgeType(t, "auctionBidding", mock.URL) - eb := &adapters.Bridge{BridgeType: bt} - input := cltest.RunInputWithResult("lot 49") - input.JobRunID = *runID + eb := &adapters.Bridge{BridgeType: *bt} + input := *models.NewRunInput(runID, cltest.JSONFromString(t, `{"result": "lot 49"}`), models.RunStatusUnstarted) result := eb.Perform(input, store) val := result.Result() assert.Equal(t, test.want, val.String()) assert.Equal(t, test.wantErrored, result.HasError()) - assert.Equal(t, test.wantPending, result.Status.PendingBridge()) + assert.Equal(t, test.wantPending, result.Status().PendingBridge()) }) } } func TestBridge_Perform_responseURL(t *testing.T) { - input := cltest.RunInputWithResult("lot 49") - input.JobRunID = *models.NewID() + input := cltest.NewRunInputWithResult("lot 49") t.Parallel() cases := []struct { @@ -166,12 +157,12 @@ func TestBridge_Perform_responseURL(t *testing.T) { { name: "basic URL", configuredURL: cltest.WebURL(t, "https://chain.link"), - want: fmt.Sprintf(`{"id":"%s","data":{"result":"lot 49"},"responseURL":"https://chain.link/v2/runs/%s"}`, input.JobRunID.String(), input.JobRunID.String()), + want: fmt.Sprintf(`{"id":"%s","data":{"result":"lot 49"},"responseURL":"https://chain.link/v2/runs/%s"}`, input.JobRunID().String(), input.JobRunID().String()), }, { name: "blank URL", configuredURL: cltest.WebURL(t, ""), - want: fmt.Sprintf(`{"id":"%s","data":{"result":"lot 49"}}`, input.JobRunID.String()), + want: fmt.Sprintf(`{"id":"%s","data":{"result":"lot 49"}}`, input.JobRunID().String()), }, } @@ -188,7 +179,7 @@ func TestBridge_Perform_responseURL(t *testing.T) { defer ensureCalled() _, bt := cltest.NewBridgeType(t, "auctionBidding", mock.URL) - eb := &adapters.Bridge{BridgeType: bt} + eb := &adapters.Bridge{BridgeType: *bt} eb.Perform(input, store) }) } diff --git a/core/adapters/compare_test.go b/core/adapters/compare_test.go index 38fe2bf644b..6975e0b6c4e 100644 --- a/core/adapters/compare_test.go +++ b/core/adapters/compare_test.go @@ -803,11 +803,11 @@ func TestCompare_Perform(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { t.Parallel() - input := cltest.RunInputWithResult(test.input) + input := cltest.NewRunInputWithResult(test.input) adapter := test.adapter result := adapter.Perform(input, nil) val := result.Result() - assert.NoError(t, result.GetError()) + assert.NoError(t, result.Error()) assert.Equal(t, test.wantResult, val.Bool()) }) } @@ -879,10 +879,11 @@ func TestCompareError_Perform(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { t.Parallel() - input := cltest.RunInputWithResult(test.input) + + input := cltest.NewRunInputWithResult(test.input) adapter := test.adapter result := adapter.Perform(input, nil) - assert.Equal(t, test.expected, result.GetError()) + assert.Equal(t, test.expected, result.Error()) }) } } diff --git a/core/adapters/copy.go b/core/adapters/copy.go index 3fba06a0a94..8612ecbd2c2 100644 --- a/core/adapters/copy.go +++ b/core/adapters/copy.go @@ -13,11 +13,12 @@ type Copy struct { // Perform returns the copied values from the desired mapping within the `data` JSON object func (c *Copy) Perform(input models.RunInput, store *store.Store) models.RunOutput { - data, err := models.JSON{}.Add("result", input.Data.String()) + data, err := models.JSON{}.Add("result", input.Data().String()) if err != nil { return models.NewRunOutputError(err) } jp := JSONParse{Path: c.CopyPath} - return jp.Perform(models.RunInput{Data: data}, store) + input = *models.NewRunInput(input.JobRunID(), data, input.Status()) + return jp.Perform(input, store) } diff --git a/core/adapters/copy_test.go b/core/adapters/copy_test.go index 25520498468..c733c0fc9fa 100644 --- a/core/adapters/copy_test.go +++ b/core/adapters/copy_test.go @@ -81,16 +81,16 @@ func TestCopy_Perform(t *testing.T) { test := tt t.Run(test.name, func(t *testing.T) { t.Parallel() - input := models.RunInput{Data: cltest.JSONFromString(t, test.input)} + input := cltest.NewRunInput(cltest.JSONFromString(t, test.input)) adapter := adapters.Copy{CopyPath: test.copyPath} result := adapter.Perform(input, nil) - assert.Equal(t, test.wantData, result.Data.String()) - assert.Equal(t, test.wantStatus, result.Status) + assert.Equal(t, test.wantData, result.Data().String()) + assert.Equal(t, test.wantStatus, result.Status()) if test.wantResultError { - assert.Error(t, result.GetError()) + assert.Error(t, result.Error()) } else { - assert.NoError(t, result.GetError()) + assert.NoError(t, result.Error()) } }) } diff --git a/core/adapters/eth_bool_test.go b/core/adapters/eth_bool_test.go index 01664c274f3..91ab3221e25 100644 --- a/core/adapters/eth_bool_test.go +++ b/core/adapters/eth_bool_test.go @@ -5,8 +5,8 @@ import ( "github.com/smartcontractkit/chainlink/core/adapters" "github.com/smartcontractkit/chainlink/core/internal/cltest" - "github.com/smartcontractkit/chainlink/core/store/models" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) var evmFalse = "0x0000000000000000000000000000000000000000000000000000000000000000" @@ -29,16 +29,14 @@ func TestEthBool_Perform(t *testing.T) { test := tt t.Run(test.name, func(t *testing.T) { t.Parallel() - past := models.RunInput{ - Data: cltest.JSONFromString(t, test.json), - } + past := cltest.NewRunInput(cltest.JSONFromString(t, test.json)) adapter := adapters.EthBool{} result := adapter.Perform(past, nil) val, err := result.ResultString() + require.NoError(t, err) + assert.NoError(t, result.Error()) assert.Equal(t, test.expected, val) - assert.NoError(t, err) - assert.NoError(t, result.GetError()) }) } } diff --git a/core/adapters/eth_format_test.go b/core/adapters/eth_format_test.go index 161f6db897c..a0a333fc6ac 100644 --- a/core/adapters/eth_format_test.go +++ b/core/adapters/eth_format_test.go @@ -5,8 +5,8 @@ import ( "github.com/smartcontractkit/chainlink/core/adapters" "github.com/smartcontractkit/chainlink/core/internal/cltest" - "github.com/smartcontractkit/chainlink/core/store/models" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestEthBytes32_Perform(t *testing.T) { @@ -34,15 +34,14 @@ func TestEthBytes32_Perform(t *testing.T) { test := tt t.Run(test.name, func(t *testing.T) { t.Parallel() - past := models.RunInput{ - Data: cltest.JSONFromString(t, test.json), - } + + past := cltest.NewRunInput(cltest.JSONFromString(t, test.json)) adapter := adapters.EthBytes32{} result := adapter.Perform(past, nil) + require.NoError(t, result.Error()) val, err := result.ResultString() - assert.NoError(t, err) - assert.NoError(t, result.GetError()) + require.NoError(t, err) assert.Equal(t, test.expected, val) }) } @@ -78,17 +77,15 @@ func TestEthInt256_Perform(t *testing.T) { adapter := adapters.EthInt256{} for _, test := range tests { t.Run(test.name, func(t *testing.T) { - input := models.RunInput{ - Data: cltest.JSONFromString(t, test.json), - } + input := cltest.NewRunInput(cltest.JSONFromString(t, test.json)) result := adapter.Perform(input, nil) if test.errored { - assert.Error(t, result.GetError()) + assert.Error(t, result.Error()) } else { + require.NoError(t, result.Error()) val, err := result.ResultString() - assert.NoError(t, result.GetError()) - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, test.want, val) } }) @@ -124,17 +121,15 @@ func TestEthUint256_Perform(t *testing.T) { adapter := adapters.EthUint256{} for _, test := range tests { t.Run(test.name, func(t *testing.T) { - input := models.RunInput{ - Data: cltest.JSONFromString(t, test.json), - } + input := cltest.NewRunInput(cltest.JSONFromString(t, test.json)) result := adapter.Perform(input, nil) if test.errored { - assert.Error(t, result.GetError()) + require.Error(t, result.Error()) } else { + require.NoError(t, result.Error()) val, err := result.ResultString() - assert.NoError(t, result.GetError()) - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, test.want, val) } }) diff --git a/core/adapters/eth_tx.go b/core/adapters/eth_tx.go index 70f0e60feb7..51193b27e4c 100644 --- a/core/adapters/eth_tx.go +++ b/core/adapters/eth_tx.go @@ -42,7 +42,7 @@ func (etx *EthTx) Perform(input models.RunInput, store *strpkg.Store) models.Run return models.NewRunOutputPendingConnection() } - if input.Status.PendingConfirmations() { + if input.Status().PendingConfirmations() { return ensureTxRunResult(input, store) } @@ -84,7 +84,7 @@ func createTxRunResult( store *strpkg.Store, ) models.RunOutput { tx, err := store.TxManager.CreateTxWithGas( - null.StringFrom(input.JobRunID.String()), + null.StringFrom(input.JobRunID().String()), address, data, gasPrice.ToInt(), @@ -173,7 +173,7 @@ func addReceiptToResult( ) models.RunOutput { receipts := []models.TxReceipt{} - ethereumReceipts := input.Data.Get("ethereumReceipts").String() + ethereumReceipts := input.Data().Get("ethereumReceipts").String() if ethereumReceipts != "" { if err := json.Unmarshal([]byte(ethereumReceipts), &receipts); err != nil { logger.Errorw("Error unmarshaling ethereum Receipts", "error", err) diff --git a/core/adapters/eth_tx_abi_encode.go b/core/adapters/eth_tx_abi_encode.go index 4b09ee0b4a0..6475019366b 100644 --- a/core/adapters/eth_tx_abi_encode.go +++ b/core/adapters/eth_tx_abi_encode.go @@ -67,7 +67,7 @@ func (etx *EthTxABIEncode) Perform(input models.RunInput, store *strpkg.Store) m if !store.TxManager.Connected() { return models.NewRunOutputPendingConnection() } - if !input.Status.PendingConfirmations() { + if !input.Status().PendingConfirmations() { data, err := etx.abiEncode(&input) if err != nil { err = errors.Wrap(err, "while constructing EthTxABIEncode data") @@ -80,8 +80,8 @@ func (etx *EthTxABIEncode) Perform(input models.RunInput, store *strpkg.Store) m // abiEncode ABI-encodes the arguments passed in a RunResult's result field // according to etx.FunctionABI -func (etx *EthTxABIEncode) abiEncode(runResult *models.RunInput) ([]byte, error) { - args, ok := runResult.Data.Get("result").Value().(map[string]interface{}) +func (etx *EthTxABIEncode) abiEncode(input *models.RunInput) ([]byte, error) { + args, ok := input.Data().Get("result").Value().(map[string]interface{}) if !ok { return nil, errors.Errorf("json result is not an object") } diff --git a/core/adapters/eth_tx_abi_encode_test.go b/core/adapters/eth_tx_abi_encode_test.go index dd8f376d8e1..b64570cccb4 100644 --- a/core/adapters/eth_tx_abi_encode_test.go +++ b/core/adapters/eth_tx_abi_encode_test.go @@ -148,7 +148,7 @@ func TestEthTxABIEncodeAdapter_Perform_ConfirmedWithJSON(t *testing.T) { }) receipt := models.TxReceipt{Hash: hash, BlockNumber: cltest.Int(confirmed)} ethMock.Register("eth_getTransactionReceipt", receipt) - input := models.RunInput{Data: cltest.JSONFromString(t, rawInput)} + input := cltest.NewRunInput(cltest.JSONFromString(t, rawInput)) responseData := adapterUnderTest.Perform(input, store) assert.False(t, responseData.HasError()) from := cltest.GetAccountAddress(t, store) diff --git a/core/adapters/eth_tx_test.go b/core/adapters/eth_tx_test.go index 08f27bfd685..3da9f0e8d62 100644 --- a/core/adapters/eth_tx_test.go +++ b/core/adapters/eth_tx_test.go @@ -32,7 +32,6 @@ func TestEthTxAdapter_Perform_Confirmed(t *testing.T) { fHash := models.HexToFunctionSelector("b3f98adc") dataPrefix := hexutil.Bytes( hexutil.MustDecode("0x0000000000000000000000000000000000000000000000000045746736453745")) - inputValue := "0x9786856756" ethMock, err := app.MockStartAndConnect() require.NoError(t, err) @@ -56,15 +55,14 @@ func TestEthTxAdapter_Perform_Confirmed(t *testing.T) { receipt := models.TxReceipt{Hash: hash, BlockNumber: cltest.Int(confirmed)} ethMock.Register("eth_getTransactionReceipt", receipt) + input := *models.NewRunInputWithResult(models.NewID(), "0x9786856756", models.RunStatusUnstarted) adapter := adapters.EthTx{ Address: address, DataPrefix: dataPrefix, FunctionSelector: fHash, } - input := cltest.RunInputWithResult(inputValue) - data := adapter.Perform(input, store) - - assert.NoError(t, data.GetError()) + result := adapter.Perform(input, store) + require.NoError(t, result.Error()) from := cltest.GetAccountAddress(t, store) txs, err := store.TxFrom(from) @@ -86,7 +84,6 @@ func TestEthTxAdapter_Perform_ConfirmedWithBytes(t *testing.T) { fHash := models.HexToFunctionSelector("b3f98adc") dataPrefix := hexutil.Bytes( hexutil.MustDecode("0x0000000000000000000000000000000000000000000000000045746736453745")) - inputValue := "cönfirmed" // contains diacritic acute to check bytes counted for length not chars ethMock, err := app.MockStartAndConnect() require.NoError(t, err) @@ -118,10 +115,11 @@ func TestEthTxAdapter_Perform_ConfirmedWithBytes(t *testing.T) { FunctionSelector: fHash, DataFormat: adapters.DataFormatBytes, } - input := cltest.RunInputWithResult(inputValue) - data := adapter.Perform(input, store) - assert.NoError(t, data.GetError()) + inputValue := "cönfirmed" // contains diacritic acute to check bytes counted for length not chars + input := *models.NewRunInputWithResult(models.NewID(), inputValue, models.RunStatusUnstarted) + result := adapter.Perform(input, store) + assert.NoError(t, result.Error()) from := cltest.GetAccountAddress(t, store) txs, err := store.TxFrom(from) @@ -141,8 +139,6 @@ func TestEthTxAdapter_Perform_SafeWithBytesAndNoDataPrefix(t *testing.T) { address := cltest.NewAddress() fHash := models.HexToFunctionSelector("b3f98adc") - // contains diacritic acute to check bytes counted for length not chars - inputValue := "cönfirmed" currentHeight := uint64(23456) require.NoError(t, app.Store.ORM.CreateHead(cltest.Head(currentHeight))) @@ -173,11 +169,13 @@ func TestEthTxAdapter_Perform_SafeWithBytesAndNoDataPrefix(t *testing.T) { FunctionSelector: fHash, DataFormat: adapters.DataFormatBytes, } - input := cltest.RunInputWithResult(inputValue) - data := adapter.Perform(input, store) - assert.NoError(t, data.GetError()) - assert.Equal(t, string(models.RunStatusCompleted), string(data.Status)) + // contains diacritic acute to check bytes counted for length not chars + inputValue := "cönfirmed" + input := *models.NewRunInputWithResult(models.NewID(), inputValue, models.RunStatusUnstarted) + result := adapter.Perform(input, store) + require.NoError(t, result.Error()) + assert.Equal(t, string(models.RunStatusCompleted), string(result.Status())) from := cltest.GetAccountAddress(t, store) var txs []models.Tx @@ -212,13 +210,14 @@ func TestEthTxAdapter_Perform_FromPendingConfirmations_StillPending(t *testing.T tx := cltest.CreateTx(t, store, from, sentAt) a := tx.Attempts[0] adapter := adapters.EthTx{} - sentResult := cltest.RunInputWithResult(a.Hash.String()) - sentResult.Status = models.RunStatusPendingConfirmations + sentResult := *models.NewRunInputWithResult( + models.NewID(), a.Hash.String(), models.RunStatusPendingConfirmations, + ) output := adapter.Perform(sentResult, store) - assert.False(t, output.HasError()) - assert.True(t, output.Status.PendingConfirmations()) + require.NoError(t, output.Error()) + assert.True(t, output.Status().PendingConfirmations()) tx, err := store.FindTx(tx.ID) require.NoError(t, err) assert.Len(t, tx.Attempts, 1) @@ -249,12 +248,13 @@ func TestEthTxAdapter_Perform_FromPendingConfirmations_BumpGas(t *testing.T) { a := tx.Attempts[0] adapter := adapters.EthTx{} - sentResult := cltest.RunInputWithResult(a.Hash.String()) - sentResult.Status = models.RunStatusPendingConfirmations + sentResult := *models.NewRunInputWithResult( + models.NewID(), a.Hash.String(), models.RunStatusPendingConfirmations, + ) output := adapter.Perform(sentResult, store) - assert.False(t, output.HasError()) - assert.True(t, output.Status.PendingConfirmations()) + require.NoError(t, output.Error()) + assert.True(t, output.Status().PendingConfirmations()) tx, err = store.FindTx(tx.ID) require.NoError(t, err) assert.Len(t, tx.Attempts, 2) @@ -293,17 +293,18 @@ func TestEthTxAdapter_Perform_FromPendingConfirmations_ConfirmCompletes(t *testi store.AddTxAttempt(tx, tx.EthTx(big.NewInt(2)), sentAt+1) a3, _ := store.AddTxAttempt(tx, tx.EthTx(big.NewInt(3)), sentAt+2) adapter := adapters.EthTx{} - sentResult := cltest.RunInputWithResult(a3.Hash.String()) - sentResult.Status = models.RunStatusPendingConfirmations + sentResult := *models.NewRunInputWithResult( + models.NewID(), a3.Hash.String(), models.RunStatusPendingConfirmations, + ) assert.False(t, tx.Confirmed) output := adapter.Perform(sentResult, store) - assert.True(t, output.Status.Completed()) - assert.False(t, output.HasError()) + require.NoError(t, output.Error()) + assert.True(t, output.Status().Completed()) value, err := output.ResultString() - assert.Nil(t, err) + require.Nil(t, err) assert.Equal(t, confirmedHash.String(), value) tx, err = store.FindTx(tx.ID) @@ -355,17 +356,13 @@ func TestEthTxAdapter_Perform_AppendingTransactionReceipts(t *testing.T) { adapter := adapters.EthTx{} previousReceipt := models.TxReceipt{Hash: cltest.NewHash(), BlockNumber: cltest.Int(sentAt - 10)} - data := map[string]interface{}{ - "result": a.Hash.String(), - "ethereumReceipts": []models.TxReceipt{previousReceipt}, - } - input := models.RunInput{ - Data: cltest.MustJSONMarshal(t, data), - Status: models.RunStatusPendingConfirmations, - } + data, _ := models.JSON{}.Add("result", a.Hash.String()) + data, _ = data.Add("ethereumReceipts", []models.TxReceipt{previousReceipt}) + input := *models.NewRunInput(models.NewID(), data, models.RunStatusPendingConfirmations) output := adapter.Perform(input, store) - assert.True(t, output.Status.Completed()) + require.NoError(t, output.Error()) + assert.True(t, output.Status().Completed()) receiptsJSON := output.Get("ethereumReceipts").String() var receipts []models.TxReceipt @@ -390,12 +387,12 @@ func TestEthTxAdapter_Perform_WithError(t *testing.T) { Address: cltest.NewAddress(), FunctionSelector: models.HexToFunctionSelector("0xb3f98adc"), } - input := cltest.RunInputWithResult("0x9786856756") + input := *models.NewRunInputWithResult(models.NewID(), "0x9786856756", models.RunStatusUnstarted) ethMock.RegisterError("eth_sendRawTransaction", "Cannot connect to nodes") output := adapter.Perform(input, store) - assert.True(t, output.HasError()) - assert.Contains(t, output.Error(), "Cannot connect to nodes") + require.Error(t, output.Error()) + assert.Contains(t, output.Error().Error(), "Cannot connect to nodes") } func TestEthTxAdapter_Perform_WithErrorInvalidInput(t *testing.T) { @@ -414,12 +411,12 @@ func TestEthTxAdapter_Perform_WithErrorInvalidInput(t *testing.T) { Address: cltest.NewAddress(), FunctionSelector: models.HexToFunctionSelector("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF1"), } - input := cltest.RunInputWithResult("0x9786856756") + input := *models.NewRunInputWithResult(models.NewID(), "0x9786856756", models.RunStatusUnstarted) ethMock.RegisterError("eth_sendRawTransaction", "Cannot connect to nodes") output := adapter.Perform(input, store) - assert.True(t, output.HasError()) - assert.Contains(t, output.Error(), "Cannot connect to nodes") + require.Error(t, output.Error()) + assert.Contains(t, output.Error().Error(), "Cannot connect to nodes") } func TestEthTxAdapter_Perform_PendingConfirmations_WithFatalErrorInTxManager(t *testing.T) { @@ -440,13 +437,14 @@ func TestEthTxAdapter_Perform_PendingConfirmations_WithFatalErrorInTxManager(t * Address: cltest.NewAddress(), FunctionSelector: models.HexToFunctionSelector("0xb3f98adc"), } - input := cltest.RunInputWithResult(cltest.NewHash().String()) - input.Status = models.RunStatusPendingConfirmations + input := *models.NewRunInputWithResult( + models.NewID(), cltest.NewHash().String(), models.RunStatusPendingConfirmations, + ) output := adapter.Perform(input, store) ethMock.AssertAllCalled() - assert.Equal(t, models.RunStatusErrored, output.Status) + assert.Equal(t, models.RunStatusErrored, output.Status()) assert.NotNil(t, output.Error()) } @@ -466,9 +464,9 @@ func TestEthTxAdapter_Perform_PendingConfirmations_WithRecoverableErrorInTxManag from := cltest.GetAccountAddress(t, store) tx := cltest.CreateTx(t, store, from, uint64(14372)) - input := cltest.RunInputWithResult(tx.Attempts[0].Hash.String()) - input.Status = models.RunStatusPendingConfirmations - + input := *models.NewRunInputWithResult( + models.NewID(), tx.Attempts[0].Hash.String(), models.RunStatusPendingConfirmations, + ) ethMock.RegisterError("eth_getTransactionReceipt", "Connection reset by peer") adapter := adapters.EthTx{ @@ -479,8 +477,8 @@ func TestEthTxAdapter_Perform_PendingConfirmations_WithRecoverableErrorInTxManag ethMock.AssertAllCalled() - assert.Equal(t, models.RunStatusPendingConfirmations, output.Status) - assert.NoError(t, output.GetError()) + require.NoError(t, output.Error()) + assert.Equal(t, models.RunStatusPendingConfirmations, output.Status()) } func TestEthTxAdapter_DeserializationBytesFormat(t *testing.T) { @@ -514,13 +512,9 @@ func TestEthTxAdapter_DeserializationBytesFormat(t *testing.T) { assert.True(t, ok) assert.Equal(t, ethtx.DataFormat, adapters.DataFormatBytes) - input := models.RunInput{ - Data: cltest.JSONFromString(t, `{"result": "hello world"}`), - Status: models.RunStatusInProgress, - } + input := *models.NewRunInputWithResult(models.NewID(), "hello world", models.RunStatusInProgress) result := adapter.Perform(input, store) - assert.False(t, result.HasError()) - assert.Equal(t, result.Error(), "") + assert.NoError(t, result.Error()) } func TestEthTxAdapter_Perform_CustomGas(t *testing.T) { @@ -556,13 +550,9 @@ func TestEthTxAdapter_Perform_CustomGas(t *testing.T) { GasLimit: gasLimit, } - input := models.RunInput{ - Data: cltest.JSONFromString(t, `{"result": "hello world"}`), - Status: models.RunStatusInProgress, - } - + input := *models.NewRunInputWithResult(models.NewID(), "hello world", models.RunStatusInProgress) result := adapter.Perform(input, store) - assert.False(t, result.HasError()) + assert.NoError(t, result.Error()) } func TestEthTxAdapter_Perform_NotConnected(t *testing.T) { @@ -575,8 +565,8 @@ func TestEthTxAdapter_Perform_NotConnected(t *testing.T) { adapter := adapters.EthTx{} data := adapter.Perform(models.RunInput{}, store) - assert.NoError(t, data.GetError()) - assert.Equal(t, models.RunStatusPendingConnection, data.Status) + require.NoError(t, data.Error()) + assert.Equal(t, models.RunStatusPendingConnection, data.Status()) } func TestEthTxAdapter_Perform_CreateTxWithGasErrorTreatsAsNotConnected(t *testing.T) { @@ -603,8 +593,8 @@ func TestEthTxAdapter_Perform_CreateTxWithGasErrorTreatsAsNotConnected(t *testin adapter := adapters.EthTx{} data := adapter.Perform(models.RunInput{}, store) - assert.NoError(t, data.GetError()) - assert.Equal(t, models.RunStatusPendingConnection, data.Status) + require.NoError(t, data.Error()) + assert.Equal(t, models.RunStatusPendingConnection, data.Status()) } func TestEthTxAdapter_Perform_CheckAttemptErrorTreatsAsNotConnected(t *testing.T) { @@ -634,8 +624,8 @@ func TestEthTxAdapter_Perform_CheckAttemptErrorTreatsAsNotConnected(t *testing.T adapter := adapters.EthTx{} data := adapter.Perform(models.RunInput{}, store) - assert.NoError(t, data.GetError()) - assert.Equal(t, models.RunStatusPendingConnection, data.Status) + require.NoError(t, data.Error()) + assert.Equal(t, models.RunStatusPendingConnection, data.Status()) } func TestEthTxAdapter_Perform_CreateTxWithEmptyResponseErrorTreatsAsPendingConfirmations(t *testing.T) { @@ -671,7 +661,7 @@ func TestEthTxAdapter_Perform_CreateTxWithEmptyResponseErrorTreatsAsPendingConfi output := adapter.Perform(models.RunInput{}, store) assert.False(t, output.HasError()) - assert.Equal(t, models.RunStatusPendingConfirmations, output.Status) + assert.Equal(t, models.RunStatusPendingConfirmations, output.Status()) // Have a head come through with the same empty response txmMock.EXPECT().Connected().Return(true) @@ -679,13 +669,10 @@ func TestEthTxAdapter_Perform_CreateTxWithEmptyResponseErrorTreatsAsPendingConfi gomock.Any(), ).Return(nil, strpkg.Unknown, badResponseErr) - input := models.RunInput{ - Data: output.Data, - Status: output.Status, - } + input := *models.NewRunInput(models.NewID(), output.Data(), output.Status()) output = adapter.Perform(input, store) - assert.False(t, output.HasError()) - assert.Equal(t, models.RunStatusPendingConfirmations, output.Status) + require.NoError(t, output.Error()) + assert.Equal(t, models.RunStatusPendingConfirmations, output.Status()) } func TestEthTxAdapter_Perform_NoDoubleSpendOnSendTransactionFail(t *testing.T) { @@ -723,10 +710,9 @@ func TestEthTxAdapter_Perform_NoDoubleSpendOnSendTransactionFail(t *testing.T) { DataPrefix: dataPrefix, FunctionSelector: fHash, } - input := cltest.RunInputWithResult(inputValue) - input.JobRunID = *models.NewID() + input := cltest.NewRunInputWithResult(inputValue) data := adapter.Perform(input, store) - assert.Error(t, data.GetError()) + require.Error(t, data.Error()) // Run the adapter again confirmed := sentAt + 1 @@ -740,7 +726,7 @@ func TestEthTxAdapter_Perform_NoDoubleSpendOnSendTransactionFail(t *testing.T) { ethMock.Register("eth_getTransactionReceipt", receipt) data = adapter.Perform(input, store) - assert.NoError(t, data.GetError()) + require.NoError(t, data.Error()) // The first and second transaction should have the same data assert.Equal(t, firstTxData, secondTxData) @@ -799,10 +785,9 @@ func TestEthTxAdapter_Perform_NoDoubleSpendOnSendTransactionFailAndNonceChange(t DataPrefix: dataPrefix, FunctionSelector: fHash, } - input := cltest.RunInputWithResult(inputValue) - input.JobRunID = *models.NewID() + input := cltest.NewRunInputWithResult(inputValue) data := adapter.Perform(input, store) - assert.Error(t, data.GetError()) + require.Error(t, data.Error()) // Run the adapter again var secondTxData []interface{} @@ -813,7 +798,7 @@ func TestEthTxAdapter_Perform_NoDoubleSpendOnSendTransactionFailAndNonceChange(t }) data = adapter.Perform(input, store) - assert.NoError(t, data.GetError()) + require.NoError(t, data.Error()) // Since the nonce (and from address) changed, the data should also change assert.NotEqual(t, firstTxData, secondTxData) diff --git a/core/adapters/http.go b/core/adapters/http.go index d7c9de8a7da..386f668fe91 100644 --- a/core/adapters/http.go +++ b/core/adapters/http.go @@ -69,7 +69,7 @@ type HTTPPost struct { // Perform ensures that the adapter's URL responds to a POST request without // errors and returns the response body as the "value" field of the result. func (hpa *HTTPPost) Perform(input models.RunInput, store *store.Store) models.RunOutput { - request, err := hpa.GetRequest(input.Data.String()) + request, err := hpa.GetRequest(input.Data().String()) if err != nil { return models.NewRunOutputError(err) } diff --git a/core/adapters/http_test.go b/core/adapters/http_test.go index 6999dc813bf..1ced321fa7c 100644 --- a/core/adapters/http_test.go +++ b/core/adapters/http_test.go @@ -11,6 +11,7 @@ import ( "github.com/smartcontractkit/chainlink/core/store/models" "github.com/smartcontractkit/chainlink/core/store/orm" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func leanStore() *store.Store { @@ -32,8 +33,8 @@ func TestHttpAdapters_NotAUrlError(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { result := test.adapter.Perform(models.RunInput{}, store) - assert.Equal(t, models.JSON{}, result.Data) assert.True(t, result.HasError()) + assert.Empty(t, result.Data()) }) } } @@ -70,7 +71,7 @@ func TestHTTPGet_Perform(t *testing.T) { for _, test := range cases { t.Run(test.name, func(t *testing.T) { - input := cltest.RunInputWithResult("inputValue") + input := cltest.NewRunInputWithResult("inputValue") mock, cleanup := cltest.NewHTTPMockServer(t, test.status, "GET", test.response, func(header http.Header, body string) { assert.Equal(t, ``, body) @@ -89,13 +90,15 @@ func TestHTTPGet_Perform(t *testing.T) { result := hga.Perform(input, store) - if !test.wantErrored { + if test.wantErrored { + require.Error(t, result.Error()) + } else { + require.NoError(t, result.Error()) val, err := result.ResultString() - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, test.want, val) } - assert.Equal(t, test.wantErrored, result.HasError()) - assert.Equal(t, false, result.Status.PendingBridge()) + assert.Equal(t, false, result.Status().PendingBridge()) }) } } @@ -114,7 +117,7 @@ func TestHTTP_TooLarge(t *testing.T) { } for _, test := range tests { t.Run(test.verb, func(t *testing.T) { - input := cltest.RunInputWithResult("inputValue") + input := cltest.NewRunInputWithResult("inputValue") largePayload := "12" mock, cleanup := cltest.NewHTTPMockServer(t, http.StatusOK, test.verb, largePayload) defer cleanup() @@ -122,8 +125,8 @@ func TestHTTP_TooLarge(t *testing.T) { hga := test.factory(cltest.WebURL(t, mock.URL)) result := hga.Perform(input, store) - assert.Equal(t, true, result.HasError()) - assert.Equal(t, "HTTP request too large, must be less than 1 bytes", result.Error()) + require.Error(t, result.Error()) + assert.Equal(t, "HTTP request too large, must be less than 1 bytes", result.Error().Error()) assert.Equal(t, "", result.Result().String()) }) } @@ -250,7 +253,7 @@ func TestHttpPost_Perform(t *testing.T) { for _, test := range cases { t.Run(test.name, func(t *testing.T) { - input := cltest.RunInputWithResult("inputVal") + input := cltest.NewRunInputWithResult("inputVal") mock, cleanup := cltest.NewHTTPMockServer(t, test.status, "POST", test.response, func(header http.Header, body string) { assert.Equal(t, test.wantBody, body) @@ -273,8 +276,12 @@ func TestHttpPost_Perform(t *testing.T) { val := result.Result() assert.Equal(t, test.want, val.String()) assert.NotEqual(t, test.wantErrored, val.Exists()) - assert.Equal(t, test.wantErrored, result.HasError()) - assert.Equal(t, false, result.Status.PendingBridge()) + if test.wantErrored { + require.Error(t, result.Error()) + } else { + require.NoError(t, result.Error()) + } + assert.Equal(t, false, result.Status().PendingBridge()) }) } } diff --git a/core/adapters/json_parse_test.go b/core/adapters/json_parse_test.go index c893d6dc7d0..925a5262974 100644 --- a/core/adapters/json_parse_test.go +++ b/core/adapters/json_parse_test.go @@ -115,16 +115,16 @@ func TestJsonParse_Perform(t *testing.T) { for _, tt := range tests { test := tt t.Run(test.name, func(t *testing.T) { - input := cltest.RunInputWithResult(test.result) + input := cltest.NewRunInputWithResult(test.result) adapter := adapters.JSONParse{Path: test.path} result := adapter.Perform(input, nil) - assert.Equal(t, test.wantData, result.Data.String()) - assert.Equal(t, test.wantStatus, result.Status) + assert.Equal(t, test.wantData, result.Data().String()) + assert.Equal(t, test.wantStatus, result.Status()) if test.wantResultError { - assert.Error(t, result.GetError()) + assert.Error(t, result.Error()) } else { - assert.NoError(t, result.GetError()) + assert.NoError(t, result.Error()) } }) } diff --git a/core/adapters/multiply_test.go b/core/adapters/multiply_test.go index b16dde784c9..2a302998976 100644 --- a/core/adapters/multiply_test.go +++ b/core/adapters/multiply_test.go @@ -6,8 +6,8 @@ import ( "github.com/smartcontractkit/chainlink/core/adapters" "github.com/smartcontractkit/chainlink/core/internal/cltest" - "github.com/smartcontractkit/chainlink/core/store/models" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestMultiply_Perform(t *testing.T) { @@ -41,7 +41,7 @@ func TestMultiply_Perform(t *testing.T) { test := tt t.Run(test.name, func(t *testing.T) { t.Parallel() - input := models.RunInput{Data: cltest.JSONFromString(t, test.json)} + input := cltest.NewRunInput(cltest.JSONFromString(t, test.json)) adapter := adapters.Multiply{} jsonErr := json.Unmarshal([]byte(test.params), &adapter) result := adapter.Perform(input, nil) @@ -49,13 +49,13 @@ func TestMultiply_Perform(t *testing.T) { if test.jsonError { assert.Error(t, jsonErr) } else if test.errored { - assert.Error(t, result.GetError()) + require.Error(t, result.Error()) assert.NoError(t, jsonErr) } else { + require.NoError(t, result.Error()) val, err := result.ResultString() assert.NoError(t, err) assert.Equal(t, test.want, val) - assert.NoError(t, result.GetError()) assert.NoError(t, jsonErr) } }) diff --git a/core/adapters/random_test.go b/core/adapters/random_test.go index 3ab61c46c32..9cc074cb053 100644 --- a/core/adapters/random_test.go +++ b/core/adapters/random_test.go @@ -7,14 +7,15 @@ import ( "github.com/smartcontractkit/chainlink/core/adapters" "github.com/smartcontractkit/chainlink/core/store/models" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestRandom_Perform(t *testing.T) { adapter := adapters.Random{} result := adapter.Perform(models.RunInput{}, nil) + require.NoError(t, result.Error()) val, err := result.ResultString() - assert.NoError(t, err) - assert.NoError(t, result.GetError()) + require.NoError(t, err) res := new(big.Int) res, ok := res.SetString(val, 10) assert.True(t, ok) diff --git a/core/adapters/sleep_test.go b/core/adapters/sleep_test.go index 0b2e67d5593..fbf3b92fb18 100644 --- a/core/adapters/sleep_test.go +++ b/core/adapters/sleep_test.go @@ -8,6 +8,7 @@ import ( "github.com/smartcontractkit/chainlink/core/internal/cltest" "github.com/smartcontractkit/chainlink/core/store/models" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestSleep_Perform(t *testing.T) { @@ -16,8 +17,9 @@ func TestSleep_Perform(t *testing.T) { adapter := adapters.Sleep{} err := json.Unmarshal([]byte(`{"until": 1332151919}`), &adapter) - assert.NoError(t, err) + require.NoError(t, err) result := adapter.Perform(models.RunInput{}, store) - assert.Equal(t, string(models.RunStatusPendingSleep), string(result.Status)) + require.NoError(t, result.Error()) + assert.Equal(t, string(models.RunStatusPendingSleep), string(result.Status())) } diff --git a/core/cmd/renderer.go b/core/cmd/renderer.go index 07c7cb46a13..925598d8cd8 100644 --- a/core/cmd/renderer.go +++ b/core/cmd/renderer.go @@ -267,7 +267,7 @@ func (rt RendererTable) renderJobRuns(runs []presenters.JobRun) error { utils.ISO8601UTC(jr.CreatedAt), utils.NullISO8601UTC(jr.FinishedAt), jr.Result.Data.String(), - jr.Result.ErrorMessage.String, + jr.Result.ErrorString(), }) } diff --git a/core/internal/cltest/cltest.go b/core/internal/cltest/cltest.go index 7ef6ad09b4f..09443300f09 100644 --- a/core/internal/cltest/cltest.go +++ b/core/internal/cltest/cltest.go @@ -1140,3 +1140,13 @@ func MustParseURL(input string) *url.URL { } return u } + +func NewRunInput(value models.JSON) models.RunInput { + jobRunID := models.NewID() + return *models.NewRunInput(jobRunID, value, models.RunStatusUnstarted) +} + +func NewRunInputWithResult(value interface{}) models.RunInput { + jobRunID := models.NewID() + return *models.NewRunInputWithResult(jobRunID, value, models.RunStatusUnstarted) +} diff --git a/core/internal/cltest/factories.go b/core/internal/cltest/factories.go index 2dff8f6576a..c820aeaeacc 100644 --- a/core/internal/cltest/factories.go +++ b/core/internal/cltest/factories.go @@ -439,20 +439,6 @@ func Int(val interface{}) *models.Big { } } -// RunInputWithResult creates a RunResult with given result -func RunInputWithResult(val interface{}) models.RunInput { - data := models.JSON{} - data, _ = data.Add("result", val) - return models.RunInput{Data: data} -} - -// RunResultWithError creates a runresult with given error -func RunResultWithError(err error) models.RunResult { - return models.RunResult{ - ErrorMessage: null.StringFrom(err.Error()), - } -} - // MarkJobRunPendingBridge marks the jobrun as Pending Bridge Status func MarkJobRunPendingBridge(jr models.JobRun, i int) models.JobRun { jr.Status = models.RunStatusPendingBridge diff --git a/core/services/job_runner.go b/core/services/job_runner.go index 3c4a66b5be1..06185d0d00f 100644 --- a/core/services/job_runner.go +++ b/core/services/job_runner.go @@ -216,18 +216,13 @@ func executeTask(run *models.JobRun, currentTaskRun *models.TaskRun, store *stor return models.NewRunOutputError(err) } - input := models.RunInput{ - JobRunID: *run.ID, - Data: data, - Status: currentTaskRun.Status, - ErrorMessage: currentTaskRun.Result.ErrorMessage, - } + input := *models.NewRunInput(run.ID, data, currentTaskRun.Status) result := adapter.Perform(input, store) logger.Infow(fmt.Sprintf("Finished processing task %s", taskCopy.Type), []interface{}{ "task", currentTaskRun.ID, - "result", result.Status, - "result_data", result.Data, + "result", result.Status(), + "result_data", result.Data(), }...) return result diff --git a/core/services/runs.go b/core/services/runs.go index ccabd2bbb30..44e16d0abba 100644 --- a/core/services/runs.go +++ b/core/services/runs.go @@ -14,18 +14,31 @@ import ( "github.com/smartcontractkit/chainlink/core/utils" ) +// CreateErroredJob creates a job that is in the errored state. This is a +// special case where this job cannot run but we want to create the run record +// so the error is more visible to the node operator. +func CreateErroredJob( + job models.JobSpec, + initiator models.Initiator, + err error, + store *store.Store) (*models.JobRun, error) { + run := job.NewRun(initiator) + run.SetError(err) + return &run, store.CreateJobRun(&run) +} + // ExecuteJob saves and immediately begins executing a run for a specified job // if it is ready. func ExecuteJob( job models.JobSpec, initiator models.Initiator, - input models.RunInput, + data *models.JSON, creationHeight *big.Int, store *store.Store) (*models.JobRun, error) { return ExecuteJobWithRunRequest( job, initiator, - input, + data, creationHeight, store, models.NewRunRequest(), @@ -37,18 +50,17 @@ func ExecuteJob( func ExecuteJobWithRunRequest( job models.JobSpec, initiator models.Initiator, - input models.RunInput, + data *models.JSON, creationHeight *big.Int, store *store.Store, runRequest models.RunRequest) (*models.JobRun, error) { logger.Debugw(fmt.Sprintf("New run triggered by %s", initiator.Type), "job", job.ID, - "input_status", input.Status, "creation_height", creationHeight, ) - run, err := NewRun(job, initiator, input, creationHeight, store, runRequest.Payment) + run, err := NewRun(job, initiator, data, creationHeight, store, runRequest.Payment) if err != nil { return nil, errors.Wrap(err, "NewRun failed") } @@ -74,7 +86,7 @@ func MeetsMinimumPayment( func NewRun( job models.JobSpec, initiator models.Initiator, - input models.RunInput, + data *models.JSON, currentHeight *big.Int, store *store.Store, payment *assets.Link) (*models.JobRun, error) { @@ -94,12 +106,7 @@ func NewRun( run := job.NewRun(initiator) - if input.HasError() { - run.SetError(input.GetError()) - return &run, nil - } - - run.Overrides = input.Data + run.Overrides = *data run.CreationHeight = models.NewBig(currentHeight) run.ObservedHeight = models.NewBig(currentHeight) diff --git a/core/services/runs_test.go b/core/services/runs_test.go index 4f814284805..5ea406b30ba 100644 --- a/core/services/runs_test.go +++ b/core/services/runs_test.go @@ -26,8 +26,6 @@ func TestNewRun(t *testing.T) { store, cleanup := cltest.NewStore(t) defer cleanup() - input := models.JSON{Result: gjson.Parse(`{"address":"0xdfcfc2b9200dbb10952c2b7cce60fc7260e03c6f"}`)} - _, bt := cltest.NewBridgeType(t, "timecube", "http://http://timecube.2enp.com/") bt.MinimumContractPayment = assets.NewLink(10) require.NoError(t, store.CreateBridgeType(bt)) @@ -42,12 +40,12 @@ func TestNewRun(t *testing.T) { Type: models.InitiatorEthLog, }} - inputResult := models.RunInput{Data: input} - run, err := services.NewRun(jobSpec, jobSpec.Initiators[0], inputResult, creationHeight, store, nil) + data := models.JSON{Result: gjson.Parse(`{"address":"0xdfcfc2b9200dbb10952c2b7cce60fc7260e03c6f"}`)} + run, err := services.NewRun(jobSpec, jobSpec.Initiators[0], &data, creationHeight, store, nil) assert.NoError(t, err) assert.Equal(t, string(models.RunStatusInProgress), string(run.Status)) assert.Len(t, run.TaskRuns, 1) - assert.Equal(t, input, run.Overrides) + assert.Equal(t, `{"address":"0xdfcfc2b9200dbb10952c2b7cce60fc7260e03c6f"}`, run.Overrides.String()) assert.False(t, run.TaskRuns[0].Confirmations.Valid) } @@ -78,7 +76,7 @@ func TestNewRun_jobSpecMinPayment(t *testing.T) { store, cleanup := cltest.NewStore(t) defer cleanup() - input := models.JSON{Result: gjson.Parse(`{"address":"0xdfcfc2b9200dbb10952c2b7cce60fc7260e03c6f"}`)} + data := models.JSON{Result: gjson.Parse(`{"address":"0xdfcfc2b9200dbb10952c2b7cce60fc7260e03c6f"}`)} tests := []struct { name string @@ -155,8 +153,7 @@ func TestNewRun_taskSumPayment(t *testing.T) { Type: models.InitiatorEthLog, }} - inputResult := models.RunInput{Data: input} - run, err := services.NewRun(jobSpec, jobSpec.Initiators[0], inputResult, nil, store, test.payment) + run, err := services.NewRun(jobSpec, jobSpec.Initiators[0], &data, nil, store, test.payment) assert.NoError(t, err) assert.Equal(t, string(test.expectedStatus), string(run.Status)) }) @@ -167,8 +164,7 @@ func TestNewRun_minimumConfirmations(t *testing.T) { store, cleanup := cltest.NewStore(t) defer cleanup() - inputResult := models.RunInput{Data: cltest.JSONFromString(t, `{"address":"0xdfcfc2b9200dbb10952c2b7cce60fc7260e03c6f"}`)} - + data := cltest.JSONFromString(t, `{"address":"0xdfcfc2b9200dbb10952c2b7cce60fc7260e03c6f"}`) creationHeight := big.NewInt(1000) tests := []struct { @@ -193,7 +189,7 @@ func TestNewRun_minimumConfirmations(t *testing.T) { run, err := services.NewRun( jobSpec, jobSpec.Initiators[0], - inputResult, + &data, creationHeight, store, nil) @@ -236,7 +232,7 @@ func TestNewRun_startAtAndEndAt(t *testing.T) { job.EndAt = test.endAt assert.Nil(t, store.CreateJob(&job)) - _, err := services.NewRun(job, job.Initiators[0], models.RunInput{}, nil, store, nil) + _, err := services.NewRun(job, job.Initiators[0], &models.JSON{}, nil, store, nil) if test.errored { assert.Error(t, err) } else { @@ -254,10 +250,10 @@ func TestNewRun_noTasksErrorsInsteadOfPanic(t *testing.T) { job.Tasks = []models.TaskSpec{} require.NoError(t, store.CreateJob(&job)) - jr, err := services.NewRun(job, job.Initiators[0], models.RunInput{}, nil, store, nil) + jr, err := services.NewRun(job, job.Initiators[0], &models.JSON{}, nil, store, nil) assert.NoError(t, err) assert.True(t, jr.Status.Errored()) - assert.True(t, jr.Result.HasError()) + assert.True(t, jr.Result.ErrorMessage.Valid) } func TestResumePendingTask(t *testing.T) { @@ -642,10 +638,11 @@ func TestExecuteJob_DoesNotSaveToTaskSpec(t *testing.T) { require.NoError(t, store.CreateJob(&job)) initr := job.Initiators[0] + data := cltest.JSONFromString(t, `{"random": "input"}`) jr, err := services.ExecuteJob( job, initr, - models.RunInput{Data: cltest.JSONFromString(t, `{"random": "input"}`)}, + &data, nil, store, ) @@ -675,13 +672,14 @@ func TestExecuteJobWithRunRequest(t *testing.T) { require.NoError(t, store.CreateJob(&job)) requestID := "RequestID" + data := cltest.JSONFromString(t, `{"random": "input"}`) initr := job.Initiators[0] rr := models.NewRunRequest() rr.RequestID = &requestID jr, err := services.ExecuteJobWithRunRequest( job, initr, - models.RunInput{Data: cltest.JSONFromString(t, `{"random": "input"}`)}, + &data, nil, store, rr, @@ -742,14 +740,8 @@ func TestExecuteJobWithRunRequest_fromRunLog_Happy(t *testing.T) { rr.RequestID = &requestID rr.TxHash = &initiatingTxHash rr.BlockHash = &test.logBlockHash - jr, err := services.ExecuteJobWithRunRequest( - job, - initr, - models.RunInput{Data: cltest.JSONFromString(t, `{"random": "input"}`)}, - creationHeight, - store, - rr, - ) + data := cltest.JSONFromString(t, `{"random": "input"}`) + jr, err := services.ExecuteJobWithRunRequest(job, initr, &data, creationHeight, store, rr) require.NoError(t, err) cltest.WaitForJobRunToPendConfirmations(t, app.Store, *jr) @@ -806,14 +798,8 @@ func TestExecuteJobWithRunRequest_fromRunLog_ConnectToLaggingEthNode(t *testing. futureCreationHeight := big.NewInt(9) pastCurrentHeight := big.NewInt(1) - jr, err := services.ExecuteJobWithRunRequest( - job, - initr, - models.RunInput{Data: cltest.JSONFromString(t, `{"random": "input"}`)}, - futureCreationHeight, - store, - rr, - ) + data := cltest.JSONFromString(t, `{"random": "input"}`) + jr, err := services.ExecuteJobWithRunRequest(job, initr, &data, futureCreationHeight, store, rr) require.NoError(t, err) cltest.WaitForJobRunToPendConfirmations(t, app.Store, *jr) diff --git a/core/services/scheduler.go b/core/services/scheduler.go index 252458659e4..e6998e8e544 100644 --- a/core/services/scheduler.go +++ b/core/services/scheduler.go @@ -129,7 +129,7 @@ func (r *Recurring) AddJob(job models.JobSpec) { archived = true return } - _, err := ExecuteJob(job, initr, models.RunInput{}, nil, r.store) + _, err := ExecuteJob(job, initr, &models.JSON{}, nil, r.store) if err != nil && !expectedRecurringScheduleJobError(err) { logger.Errorw(err.Error()) } @@ -180,7 +180,7 @@ func (ot *OneTime) RunJobAt(initr models.Initiator, job models.JobSpec) { logger.Error(err.Error()) return } - _, err := ExecuteJob(job, initr, models.RunInput{}, nil, ot.Store) + _, err := ExecuteJob(job, initr, &models.JSON{}, nil, ot.Store) if err != nil { logger.Error(err.Error()) if err := ot.Store.MarkRan(&initr, false); err != nil { diff --git a/core/services/subscription.go b/core/services/subscription.go index 4c14164fc8e..bf79fe6aa83 100644 --- a/core/services/subscription.go +++ b/core/services/subscription.go @@ -154,30 +154,31 @@ func ReceiveLogRequest(store *strpkg.Store, le models.LogRequest) { return } - runJob(store, le, data) + runJob(store, le, &data) } -func runJob(store *strpkg.Store, le models.LogRequest, data models.JSON) { - input := models.RunInput{Data: data} +func runJob(store *strpkg.Store, le models.LogRequest, data *models.JSON) { + jobSpec := le.GetJobSpec() + initiator := le.GetInitiator() + if err := le.ValidateRequester(); err != nil { - input.SetError(err) + if _, err := CreateErroredJob(jobSpec, initiator, err, store); err != nil { + logger.Errorw(err.Error()) + } logger.Errorw(err.Error(), le.ForLogger()...) + return } rr, err := le.RunRequest() if err != nil { - input.SetError(err) + if _, err := CreateErroredJob(jobSpec, initiator, err, store); err != nil { + logger.Errorw(err.Error()) + } logger.Errorw(err.Error(), le.ForLogger()...) + return } - _, err = ExecuteJobWithRunRequest( - le.GetJobSpec(), - le.GetInitiator(), - input, - le.BlockNumber(), - store.Unscoped(), - rr, - ) + _, err = ExecuteJobWithRunRequest(jobSpec, initiator, data, le.BlockNumber(), store, rr) if err != nil { logger.Errorw(err.Error(), le.ForLogger()...) } diff --git a/core/store/models/job_run.go b/core/store/models/job_run.go index 213b6c552f5..593aee1d0d7 100644 --- a/core/store/models/job_run.go +++ b/core/store/models/job_run.go @@ -64,8 +64,8 @@ func (jr JobRun) ForLogger(kvs ...interface{}) []interface{} { output = append(output, "observed_height", jr.ObservedHeight.ToInt()) } - if jr.Result.HasError() { - output = append(output, "job_error", jr.Result.Error()) + if jr.Result.ErrorMessage.Valid { + output = append(output, "job_error", jr.Result.ErrorString()) } if jr.Status == "completed" { @@ -112,22 +112,22 @@ func (jr *JobRun) TasksRemain() bool { // SetError sets this job run to failed and saves the error message func (jr *JobRun) SetError(err error) { - jr.Result.ErrorMessage = null.StringFrom(err.Error()) + jr.Result.SetError(err) jr.Status = RunStatusErrored jr.FinishedAt = null.TimeFrom(time.Now()) } // ApplyOutput updates the JobRun's Result and Status func (jr *JobRun) ApplyOutput(result RunOutput) error { - jr.Result.ErrorMessage = result.ErrorMessage - jr.Result.Data = result.Data - jr.setStatus(result.Status) + jr.Result.SetError(result.Error()) + jr.Result.Data = result.Data() + jr.setStatus(result.Status()) return nil } // ApplyBridgeRunResult saves the input from a BridgeAdapter func (jr *JobRun) ApplyBridgeRunResult(result BridgeRunResult) error { - jr.Result.ErrorMessage = result.ErrorMessage + jr.Result.SetError(result.GetError()) jr.Result.Data = result.Data jr.setStatus(result.Status) return nil @@ -199,8 +199,8 @@ func (tr *TaskRun) ForLogger(kvs ...interface{}) []interface{} { "status", tr.Status, } - if tr.Result.HasError() { - output = append(output, "error", tr.Result.Error()) + if tr.Result.ErrorMessage.Valid { + output = append(output, "error", tr.Result.ErrorString()) } return append(kvs, output...) @@ -208,20 +208,20 @@ func (tr *TaskRun) ForLogger(kvs ...interface{}) []interface{} { // SetError sets this task run to failed and saves the error message func (tr *TaskRun) SetError(err error) { - tr.Result.ErrorMessage = null.StringFrom(err.Error()) + tr.Result.SetError(err) tr.Status = RunStatusErrored } // ApplyBridgeRunResult updates the TaskRun's Result and Status func (tr *TaskRun) ApplyBridgeRunResult(result BridgeRunResult) { - tr.Result.ErrorMessage = result.ErrorMessage + tr.Result.SetError(result.GetError()) tr.Result.Data = result.Data tr.Status = result.Status } // ApplyOutput updates the TaskRun's Result and Status func (tr *TaskRun) ApplyOutput(result RunOutput) { - tr.Result.ErrorMessage = result.ErrorMessage - tr.Result.Data = result.Data - tr.Status = result.Status + tr.Result.SetError(result.Error()) + tr.Result.Data = result.Data() + tr.Status = result.Status() } diff --git a/core/store/models/job_run_test.go b/core/store/models/job_run_test.go index 5a6f98c6dfd..d65666e276b 100644 --- a/core/store/models/job_run_test.go +++ b/core/store/models/job_run_test.go @@ -3,7 +3,6 @@ package models_test import ( "encoding/json" "errors" - "fmt" "math/big" "testing" @@ -13,6 +12,7 @@ import ( "github.com/smartcontractkit/chainlink/core/store/models" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + null "gopkg.in/guregu/null.v3" ) func TestJobRuns_RetrievingFromDBWithError(t *testing.T) { @@ -24,14 +24,14 @@ func TestJobRuns_RetrievingFromDBWithError(t *testing.T) { require.NoError(t, store.CreateJob(&job)) jr := job.NewRun(job.Initiators[0]) jr.JobSpecID = job.ID - jr.Result = cltest.RunResultWithError(fmt.Errorf("bad idea")) + jr.Result.ErrorMessage = null.StringFrom("bad idea") err := store.CreateJobRun(&jr) require.NoError(t, err) run, err := store.FindJobRun(jr.ID) - assert.NoError(t, err) - assert.True(t, run.Result.HasError()) - assert.Equal(t, "bad idea", run.Result.Error()) + require.NoError(t, err) + assert.True(t, run.Result.ErrorMessage.Valid) + assert.Equal(t, "bad idea", run.Result.ErrorString()) } func TestJobRuns_RetrievingFromDBWithData(t *testing.T) { @@ -52,7 +52,7 @@ func TestJobRuns_RetrievingFromDBWithData(t *testing.T) { run, err := store.FindJobRun(jr.ID) assert.NoError(t, err) - assert.False(t, run.Result.HasError()) + assert.False(t, run.Result.ErrorMessage.Valid) assert.JSONEq(t, data, run.Result.Data.String()) } @@ -161,11 +161,11 @@ func TestForLogger(t *testing.T) { assert.Equal(t, logsWithBlockHeights[8], "observed_height") assert.Equal(t, logsWithBlockHeights[9], big.NewInt(10)) - jrErr := job.NewRun(job.Initiators[0]) - jrErr.Result = cltest.RunResultWithError(fmt.Errorf("bad idea")) - logsWithErr := jrErr.ForLogger() + result := job.NewRun(job.Initiators[0]) + result.Result.ErrorMessage = null.StringFrom("bad idea") + logsWithErr := result.ForLogger() assert.Equal(t, logsWithErr[6], "job_error") - assert.Equal(t, logsWithErr[7], jrErr.Result.Error()) + assert.Equal(t, logsWithErr[7], result.Result.ErrorString()) } diff --git a/core/store/models/run_input.go b/core/store/models/run_input.go index 72de22074b1..ea67130bc0c 100644 --- a/core/store/models/run_input.go +++ b/core/store/models/run_input.go @@ -1,24 +1,41 @@ package models import ( - "errors" "fmt" "github.com/tidwall/gjson" - null "gopkg.in/guregu/null.v3" ) // RunInput represents the input for performing a Task type RunInput struct { - JobRunID ID - Data JSON - Status RunStatus - ErrorMessage null.String + jobRunID ID + data JSON + status RunStatus + err error +} + +// NewRunInput creates a new RunInput with arbitrary data +func NewRunInput(jobRunID *ID, data JSON, status RunStatus) *RunInput { + return &RunInput{ + jobRunID: *jobRunID, + data: data, + status: status, + } +} + +// NewRunInputWithResult creates a new RunInput with a value in the "result" field +func NewRunInputWithResult(jobRunID *ID, value interface{}, status RunStatus) *RunInput { + data, _ := JSON{}.Add("result", value) + return &RunInput{ + jobRunID: *jobRunID, + data: data, + status: status, + } } // Result returns the result as a gjson object func (ri RunInput) Result() gjson.Result { - return ri.Data.Get("result") + return ri.data.Get("result") } // ResultString returns the string result of the Data JSON field. @@ -30,21 +47,36 @@ func (ri RunInput) ResultString() (string, error) { return val.String(), nil } -// HasError returns true if the status is errored or the error message is set -func (ri RunInput) HasError() bool { - return ri.Status == RunStatusErrored || ri.ErrorMessage.Valid +// GetError returns the error of a RunResult if it is present. +func (ri RunInput) Error() error { + return ri.err } -// GetError returns the error of a RunResult if it is present. -func (ri RunInput) GetError() error { - if ri.HasError() { - return errors.New(ri.ErrorMessage.ValueOrZero()) +// ErrorMessage returns the error as a string if present, otherwise "". +func (ri RunInput) ErrorMessage() string { + if ri.err != nil { + return ri.err.Error() } - return nil + return "" } // SetError marks the result as errored and saves the specified error message func (ri *RunInput) SetError(err error) { - ri.ErrorMessage = null.StringFrom(err.Error()) - ri.Status = RunStatusErrored + ri.status = RunStatusErrored + ri.err = err +} + +// Status returns the RunInput's status +func (ri RunInput) Status() RunStatus { + return ri.status +} + +// Data returns the RunInput's data +func (ri RunInput) Data() JSON { + return ri.data +} + +// JobRunID returns this RunInput's JobRunID +func (ri RunInput) JobRunID() *ID { + return &ri.jobRunID } diff --git a/core/store/models/run_output.go b/core/store/models/run_output.go index ea65b9a1775..a9b93ceef5d 100644 --- a/core/store/models/run_output.go +++ b/core/store/models/run_output.go @@ -1,25 +1,23 @@ package models import ( - "errors" "fmt" "github.com/tidwall/gjson" - null "gopkg.in/guregu/null.v3" ) // RunOutput represents the result of performing a Task type RunOutput struct { - Data JSON - Status RunStatus - ErrorMessage null.String + data JSON + status RunStatus + err error } // NewRunOutputError returns a new RunOutput with an error func NewRunOutputError(err error) RunOutput { return RunOutput{ - Status: RunStatusErrored, - ErrorMessage: null.StringFrom(err.Error()), + status: RunStatusErrored, + err: err, } } @@ -34,69 +32,61 @@ func NewRunOutputCompleteWithResult(resultVal interface{}) RunOutput { // NewRunOutputComplete returns a new RunOutput that is complete and contains // raw data func NewRunOutputComplete(data JSON) RunOutput { - return RunOutput{Status: RunStatusCompleted, Data: data} + return RunOutput{status: RunStatusCompleted, data: data} } // NewRunOutputPendingSleep returns a new RunOutput that indicates the task is // sleeping func NewRunOutputPendingSleep() RunOutput { - return RunOutput{Status: RunStatusPendingSleep} + return RunOutput{status: RunStatusPendingSleep} } // NewRunOutputPendingConfirmations returns a new RunOutput that indicates the // task is pending confirmations func NewRunOutputPendingConfirmations() RunOutput { - return RunOutput{Status: RunStatusPendingConfirmations} + return RunOutput{status: RunStatusPendingConfirmations} } // NewRunOutputPendingConfirmationsWithData returns a new RunOutput that // indicates the task is pending confirmations but also has some data that // needs to be fed in on next invocation func NewRunOutputPendingConfirmationsWithData(data JSON) RunOutput { - return RunOutput{Status: RunStatusPendingConfirmations, Data: data} + return RunOutput{status: RunStatusPendingConfirmations, data: data} } // NewRunOutputPendingConnection returns a new RunOutput that indicates the // task got disconnected func NewRunOutputPendingConnection() RunOutput { - return RunOutput{Status: RunStatusPendingConnection} + return RunOutput{status: RunStatusPendingConnection} } -// NewRunOutputPendingConfirmationsWithData returns a new RunOutput that +// NewRunOutputPendingConnectionWithData returns a new RunOutput that // indicates the task got disconnected but also has some data that needs to be // fed in on next invocation func NewRunOutputPendingConnectionWithData(data JSON) RunOutput { - return RunOutput{Status: RunStatusPendingConnection, Data: data} + return RunOutput{status: RunStatusPendingConnection, data: data} } // NewRunOutputInProgress returns a new RunOutput that indicates the // task is still in progress func NewRunOutputInProgress(data JSON) RunOutput { - return RunOutput{Status: RunStatusInProgress, Data: data} + return RunOutput{status: RunStatusInProgress, data: data} } // NewRunOutputPendingBridge returns a new RunOutput that indicates the // task is still in progress func NewRunOutputPendingBridge() RunOutput { - return RunOutput{Status: RunStatusPendingBridge} + return RunOutput{status: RunStatusPendingBridge} } // HasError returns true if the status is errored or the error message is set func (ro RunOutput) HasError() bool { - return ro.Status == RunStatusErrored || ro.ErrorMessage.Valid + return ro.status == RunStatusErrored } // Result returns the result as a gjson object func (ro RunOutput) Result() gjson.Result { - return ro.Data.Get("result") -} - -// GetError returns the error of a RunResult if it is present. -func (ro RunOutput) GetError() error { - if ro.HasError() { - return errors.New(ro.ErrorMessage.ValueOrZero()) - } - return nil + return ro.data.Get("result") } // ResultString returns the string result of the Data JSON field. @@ -110,10 +100,28 @@ func (ro RunOutput) ResultString() (string, error) { // Get searches for and returns the JSON at the given path. func (ro RunOutput) Get(path string) gjson.Result { - return ro.Data.Get(path) + return ro.data.Get(path) +} + +// Error returns error for this RunOutput +func (ro RunOutput) Error() error { + return ro.err +} + +// ErrorMessage returns the error as a string if present, otherwise "". +func (ro RunOutput) ErrorMessage() string { + if ro.err != nil { + return ro.err.Error() + } + return "" +} + +// Data returns the data held by this RunOutput +func (ro RunOutput) Data() JSON { + return ro.data } -// Error returns the string value of the ErrorMessage field. -func (ro RunOutput) Error() string { - return ro.ErrorMessage.String +// Status returns the status returned from a task +func (ro RunOutput) Status() RunStatus { + return ro.status } diff --git a/core/store/models/run_result.go b/core/store/models/run_result.go index ea5d2b305c2..4f1a41e12a8 100644 --- a/core/store/models/run_result.go +++ b/core/store/models/run_result.go @@ -1,7 +1,6 @@ package models import ( - "errors" "fmt" "github.com/tidwall/gjson" @@ -18,11 +17,13 @@ type RunResult struct { // SetError marks the result as errored and saves the specified error message func (rr *RunResult) SetError(err error) { - rr.ErrorMessage = null.StringFrom(err.Error()) + if err != nil { + rr.ErrorMessage = null.StringFrom(err.Error()) + } } // ResultString returns the string result of the Data JSON field. -func (rr *RunResult) ResultString() (string, error) { +func (rr RunResult) ResultString() (string, error) { val := rr.Result() if val.Type != gjson.String { return "", fmt.Errorf("non string result") @@ -31,24 +32,11 @@ func (rr *RunResult) ResultString() (string, error) { } // Result returns the result as a gjson object -func (rr *RunResult) Result() gjson.Result { +func (rr RunResult) Result() gjson.Result { return rr.Data.Get("result") } -// HasError returns true if the ErrorMessage is present. -func (rr *RunResult) HasError() bool { - return rr.ErrorMessage.Valid -} - -// Error returns the string value of the ErrorMessage field. -func (rr *RunResult) Error() string { - return rr.ErrorMessage.String -} - -// GetError returns the error of a RunResult if it is present. -func (rr *RunResult) GetError() error { - if rr.HasError() { - return errors.New(rr.ErrorMessage.ValueOrZero()) - } - return nil +// ErrorString returns the error as a string if present, otherwise "". +func (rr RunResult) ErrorString() string { + return rr.ErrorMessage.ValueOrZero() } diff --git a/core/store/models/run_result_test.go b/core/store/models/run_result_test.go index b53cd570c27..e7b1a7b8d0d 100644 --- a/core/store/models/run_result_test.go +++ b/core/store/models/run_result_test.go @@ -5,7 +5,6 @@ import ( "errors" "testing" - "github.com/smartcontractkit/chainlink/core/internal/cltest" "github.com/smartcontractkit/chainlink/core/store/models" "github.com/stretchr/testify/assert" ) @@ -45,5 +44,5 @@ func TestRunResult_WithError(t *testing.T) { rr := models.RunResult{} rr.SetError(errors.New("this blew up")) - assert.Equal(t, cltest.NullString("this blew up"), rr.ErrorMessage) + assert.True(t, rr.ErrorMessage.Valid) } diff --git a/core/web/job_runs_controller.go b/core/web/job_runs_controller.go index d29b6cd8ddd..173d8ca371c 100644 --- a/core/web/job_runs_controller.go +++ b/core/web/job_runs_controller.go @@ -59,7 +59,7 @@ func (jrc *JobRunsController) Create(c *gin.Context) { jsonAPIError(c, http.StatusForbidden, err) } else if data, err := getRunData(c); err != nil { jsonAPIError(c, http.StatusInternalServerError, err) - } else if jr, err := services.ExecuteJob(j, *initiator, models.RunInput{Data: data}, nil, jrc.App.GetStore()); err != nil { + } else if jr, err := services.ExecuteJob(j, *initiator, &data, nil, jrc.App.GetStore()); err != nil { jsonAPIError(c, http.StatusInternalServerError, err) } else { jsonAPIResponse(c, presenters.JobRun{JobRun: *jr}, "job run") diff --git a/go.mod b/go.mod index 19c509e040b..90ea062d4c4 100644 --- a/go.mod +++ b/go.mod @@ -70,7 +70,7 @@ require ( golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2 golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 gopkg.in/gormigrate.v1 v1.6.0 - gopkg.in/guregu/null.v2 v2.1.2 + gopkg.in/guregu/null.v2 v2.1.2 // indirect gopkg.in/guregu/null.v3 v3.4.0 gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce // indirect ) From ea87971b26a2ce1316998892baae091cdff0becd Mon Sep 17 00:00:00 2001 From: John Barker Date: Wed, 23 Oct 2019 12:00:18 +0800 Subject: [PATCH 029/199] Remove unused MustJSONMarshal --- core/internal/cltest/factories.go | 8 -------- 1 file changed, 8 deletions(-) diff --git a/core/internal/cltest/factories.go b/core/internal/cltest/factories.go index c820aeaeacc..77e2387a19f 100644 --- a/core/internal/cltest/factories.go +++ b/core/internal/cltest/factories.go @@ -519,11 +519,3 @@ func CreateServiceAgreementViaWeb( err := ParseJSONAPIResponse(t, resp, &responseSA) require.NoError(t, err) } - -func MustJSONMarshal(t *testing.T, input interface{}) models.JSON { - bytes, err := json.Marshal(input) - require.NoError(t, err) - json, err := models.ParseJSON(bytes) - require.NoError(t, err) - return json -} From 4c0b3abcd75ea2fa32a3e45b439e313c6a7d2894 Mon Sep 17 00:00:00 2001 From: John Barker Date: Wed, 23 Oct 2019 12:11:30 +0800 Subject: [PATCH 030/199] Remove ErrorMessage, SetError unused --- core/adapters/eth_tx.go | 2 -- core/services/runs_test.go | 6 ++---- core/store/models/run_input.go | 14 -------------- core/store/models/run_output.go | 8 -------- 4 files changed, 2 insertions(+), 28 deletions(-) diff --git a/core/adapters/eth_tx.go b/core/adapters/eth_tx.go index 51193b27e4c..e6d6eed807d 100644 --- a/core/adapters/eth_tx.go +++ b/core/adapters/eth_tx.go @@ -164,8 +164,6 @@ func ensureTxRunResult(input models.RunInput, str *strpkg.Store) models.RunOutpu return models.NewRunOutputPendingConfirmationsWithData(output) } -var zero = common.Hash{} - func addReceiptToResult( receipt *models.TxReceipt, input models.RunInput, diff --git a/core/services/runs_test.go b/core/services/runs_test.go index 5ea406b30ba..158d8424777 100644 --- a/core/services/runs_test.go +++ b/core/services/runs_test.go @@ -103,9 +103,7 @@ func TestNewRun_jobSpecMinPayment(t *testing.T) { }} jobSpec.MinPayment = test.minPayment - inputResult := models.RunResult{Data: input} - - run, err := services.NewRun(jobSpec, jobSpec.Initiators[0], inputResult, nil, store, test.payment) + run, err := services.NewRun(jobSpec, jobSpec.Initiators[0], &data, nil, store, test.payment) assert.NoError(t, err) assert.Equal(t, string(test.expectedStatus), string(run.Status)) }) @@ -126,7 +124,7 @@ func TestNewRun_taskSumPayment(t *testing.T) { store.Config.Set("MINIMUM_CONTRACT_PAYMENT", "1") - input := models.JSON{Result: gjson.Parse(`{"address":"0xdfcfc2b9200dbb10952c2b7cce60fc7260e03c6f"}`)} + data := models.JSON{Result: gjson.Parse(`{"address":"0xdfcfc2b9200dbb10952c2b7cce60fc7260e03c6f"}`)} tests := []struct { name string diff --git a/core/store/models/run_input.go b/core/store/models/run_input.go index ea67130bc0c..b14b74c7468 100644 --- a/core/store/models/run_input.go +++ b/core/store/models/run_input.go @@ -52,20 +52,6 @@ func (ri RunInput) Error() error { return ri.err } -// ErrorMessage returns the error as a string if present, otherwise "". -func (ri RunInput) ErrorMessage() string { - if ri.err != nil { - return ri.err.Error() - } - return "" -} - -// SetError marks the result as errored and saves the specified error message -func (ri *RunInput) SetError(err error) { - ri.status = RunStatusErrored - ri.err = err -} - // Status returns the RunInput's status func (ri RunInput) Status() RunStatus { return ri.status diff --git a/core/store/models/run_output.go b/core/store/models/run_output.go index a9b93ceef5d..5fd384c9857 100644 --- a/core/store/models/run_output.go +++ b/core/store/models/run_output.go @@ -108,14 +108,6 @@ func (ro RunOutput) Error() error { return ro.err } -// ErrorMessage returns the error as a string if present, otherwise "". -func (ro RunOutput) ErrorMessage() string { - if ro.err != nil { - return ro.err.Error() - } - return "" -} - // Data returns the data held by this RunOutput func (ro RunOutput) Data() JSON { return ro.data From 79f47bf7a35e7a1b593e084d8d7c903fce1a28a3 Mon Sep 17 00:00:00 2001 From: John Barker Date: Wed, 23 Oct 2019 14:01:33 +0800 Subject: [PATCH 031/199] Remove some more unused code from job_run/task_run --- core/store/models/job_run.go | 37 ----------------------------------- core/store/models/job_spec.go | 10 ---------- 2 files changed, 47 deletions(-) diff --git a/core/store/models/job_run.go b/core/store/models/job_run.go index 593aee1d0d7..be116b0fadb 100644 --- a/core/store/models/job_run.go +++ b/core/store/models/job_run.go @@ -95,15 +95,6 @@ func (jr *JobRun) NextTaskRun() *TaskRun { return nil } -// PreviousTaskRun returns the last task to be processed, if it exists -func (jr *JobRun) PreviousTaskRun() *TaskRun { - index, runnable := jr.NextTaskRunIndex() - if runnable && index > 0 { - return &jr.TaskRuns[index-1] - } - return nil -} - // TasksRemain returns true if there are unfinished tasks left for this job run func (jr *JobRun) TasksRemain() bool { _, runnable := jr.NextTaskRunIndex() @@ -142,18 +133,6 @@ func (jr *JobRun) setStatus(status RunStatus) { } } -// JobRunsWithStatus filters passed job runs returning those that have -// the desired status, entirely in memory. -func JobRunsWithStatus(runs []JobRun, status RunStatus) []JobRun { - rval := []JobRun{} - for _, r := range runs { - if r.Status == status { - rval = append(rval, r) - } - } - return rval -} - // RunRequest stores the fields used to initiate the parent job run. type RunRequest struct { ID uint `gorm:"primary_key"` @@ -190,22 +169,6 @@ func (tr TaskRun) String() string { return fmt.Sprintf("TaskRun(%v,%v,%v,%v)", tr.ID.String(), tr.TaskSpec.Type, tr.Status, tr.Result) } -// ForLogger formats the TaskRun info for a common formatting in the log. -func (tr *TaskRun) ForLogger(kvs ...interface{}) []interface{} { - output := []interface{}{ - "type", tr.TaskSpec.Type, - "params", tr.TaskSpec.Params, - "taskrun", tr.ID, - "status", tr.Status, - } - - if tr.Result.ErrorMessage.Valid { - output = append(output, "error", tr.Result.ErrorString()) - } - - return append(kvs, output...) -} - // SetError sets this task run to failed and saves the error message func (tr *TaskRun) SetError(err error) { tr.Result.SetError(err) diff --git a/core/store/models/job_spec.go b/core/store/models/job_spec.go index 641b763928d..d51b645cf5e 100644 --- a/core/store/models/job_spec.go +++ b/core/store/models/job_spec.go @@ -184,16 +184,6 @@ func (j JobSpec) InitiatorExternal(name string) *Initiator { return found } -// WebAuthorized returns true if the "web" initiator is present. -func (j JobSpec) WebAuthorized() bool { - for _, initr := range j.Initiators { - if initr.Type == InitiatorWeb { - return true - } - } - return false -} - // IsLogInitiated Returns true if any of the job's initiators are triggered by event logs. func (j JobSpec) IsLogInitiated() bool { for _, initr := range j.Initiators { From 94809e962b0453f1469fa1c3c242fb174d3c455e Mon Sep 17 00:00:00 2001 From: John Barker Date: Tue, 29 Oct 2019 16:36:49 +0100 Subject: [PATCH 032/199] Add HasError to JobRun instead of interrogating Result.ErrorMessage --- core/services/runs_test.go | 3 +-- core/store/models/job_run.go | 7 ++++++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/core/services/runs_test.go b/core/services/runs_test.go index 158d8424777..535b26ee416 100644 --- a/core/services/runs_test.go +++ b/core/services/runs_test.go @@ -250,8 +250,7 @@ func TestNewRun_noTasksErrorsInsteadOfPanic(t *testing.T) { jr, err := services.NewRun(job, job.Initiators[0], &models.JSON{}, nil, store, nil) assert.NoError(t, err) - assert.True(t, jr.Status.Errored()) - assert.True(t, jr.Result.ErrorMessage.Valid) + assert.True(t, jr.HasError()) } func TestResumePendingTask(t *testing.T) { diff --git a/core/store/models/job_run.go b/core/store/models/job_run.go index be116b0fadb..b2449adff97 100644 --- a/core/store/models/job_run.go +++ b/core/store/models/job_run.go @@ -64,7 +64,7 @@ func (jr JobRun) ForLogger(kvs ...interface{}) []interface{} { output = append(output, "observed_height", jr.ObservedHeight.ToInt()) } - if jr.Result.ErrorMessage.Valid { + if jr.HasError() { output = append(output, "job_error", jr.Result.ErrorString()) } @@ -75,6 +75,11 @@ func (jr JobRun) ForLogger(kvs ...interface{}) []interface{} { return append(kvs, output...) } +// HasError returns true if this JobRun has errored +func (jr JobRun) HasError() bool { + return jr.Status.Errored() +} + // NextTaskRunIndex returns the position of the next unfinished task func (jr *JobRun) NextTaskRunIndex() (int, bool) { for index, tr := range jr.TaskRuns { From 19fd8378d5aee53feb25d5c5b54db4417608cbdc Mon Sep 17 00:00:00 2001 From: John Barker Date: Tue, 29 Oct 2019 16:42:46 +0100 Subject: [PATCH 033/199] Straight line error test for TestCopy_Perform --- core/adapters/copy_test.go | 38 +++++++++++++++++--------------------- 1 file changed, 17 insertions(+), 21 deletions(-) diff --git a/core/adapters/copy_test.go b/core/adapters/copy_test.go index c733c0fc9fa..274f68eb254 100644 --- a/core/adapters/copy_test.go +++ b/core/adapters/copy_test.go @@ -2,6 +2,7 @@ package adapters_test import ( "encoding/json" + "errors" "testing" "github.com/smartcontractkit/chainlink/core/adapters" @@ -17,7 +18,7 @@ func TestCopy_Perform(t *testing.T) { copyPath []string wantData string wantStatus models.RunStatus - wantResultError bool + wantResultError error }{ { "existing path", @@ -25,7 +26,7 @@ func TestCopy_Perform(t *testing.T) { []string{"last"}, `{"result":"11779.99"}`, models.RunStatusCompleted, - false, + nil, }, { "nonexistent path", @@ -33,15 +34,7 @@ func TestCopy_Perform(t *testing.T) { []string{"doesnotexist"}, `{"result":null}`, models.RunStatusCompleted, - false, - }, - { - "double nonexistent path", - `{"high":"11850.00","last":"11779.99"}`, - []string{"no", "really"}, - ``, - models.RunStatusErrored, - true, + nil, }, { "array index path", @@ -49,7 +42,7 @@ func TestCopy_Perform(t *testing.T) { []string{"data", "0", "availability"}, `{"result":"0.99991"}`, models.RunStatusCompleted, - false, + nil, }, { "float result", @@ -57,7 +50,7 @@ func TestCopy_Perform(t *testing.T) { []string{"availability"}, `{"result":0.99991}`, models.RunStatusCompleted, - false, + nil, }, { "result with quotes", @@ -65,7 +58,7 @@ func TestCopy_Perform(t *testing.T) { []string{`"`}, `{"result":null}`, models.RunStatusCompleted, - false, + nil, }, { "index array of array", @@ -73,7 +66,15 @@ func TestCopy_Perform(t *testing.T) { []string{"data", "0", "0"}, `{"result":0}`, models.RunStatusCompleted, - false, + nil, + }, + { + "double nonexistent path", + `{"high":"11850.00","last":"11779.99"}`, + []string{"no", "really"}, + ``, + models.RunStatusErrored, + errors.New("No value could be found for the key 'no'"), }, } @@ -86,12 +87,7 @@ func TestCopy_Perform(t *testing.T) { result := adapter.Perform(input, nil) assert.Equal(t, test.wantData, result.Data().String()) assert.Equal(t, test.wantStatus, result.Status()) - - if test.wantResultError { - assert.Error(t, result.Error()) - } else { - assert.NoError(t, result.Error()) - } + assert.Equal(t, test.wantResultError, result.Error()) }) } } From a4531bb6e26a09e940bef17bd746c4ff69b43f6b Mon Sep 17 00:00:00 2001 From: John Barker Date: Tue, 29 Oct 2019 16:43:00 +0100 Subject: [PATCH 034/199] Simpler comment for ResultString --- core/store/models/run_output.go | 2 +- core/store/models/run_result.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/core/store/models/run_output.go b/core/store/models/run_output.go index 5fd384c9857..d2ed2e066ac 100644 --- a/core/store/models/run_output.go +++ b/core/store/models/run_output.go @@ -89,7 +89,7 @@ func (ro RunOutput) Result() gjson.Result { return ro.data.Get("result") } -// ResultString returns the string result of the Data JSON field. +// ResultString returns the "result" value as a string if possible func (ro RunOutput) ResultString() (string, error) { val := ro.Result() if val.Type != gjson.String { diff --git a/core/store/models/run_result.go b/core/store/models/run_result.go index 4f1a41e12a8..5e3a32e6841 100644 --- a/core/store/models/run_result.go +++ b/core/store/models/run_result.go @@ -22,7 +22,7 @@ func (rr *RunResult) SetError(err error) { } } -// ResultString returns the string result of the Data JSON field. +// ResultString returns the "result" value as a string if possible func (rr RunResult) ResultString() (string, error) { val := rr.Result() if val.Type != gjson.String { From 9e36322669b97441bb1d4c1743544c40500b5f14 Mon Sep 17 00:00:00 2001 From: John Barker Date: Tue, 29 Oct 2019 16:50:55 +0100 Subject: [PATCH 035/199] Remove ResultString() from RunOutput, only used in tests --- core/adapters/bridge_test.go | 4 +--- core/adapters/eth_bool_test.go | 6 +----- core/adapters/eth_format_test.go | 12 +++--------- core/adapters/eth_tx_test.go | 6 ++---- core/adapters/http_test.go | 4 +--- core/adapters/multiply_test.go | 5 +---- core/adapters/random_test.go | 4 +--- core/store/models/run_output.go | 11 ----------- 8 files changed, 10 insertions(+), 42 deletions(-) diff --git a/core/adapters/bridge_test.go b/core/adapters/bridge_test.go index 5a2f3ce98fd..09f0eed0ec1 100644 --- a/core/adapters/bridge_test.go +++ b/core/adapters/bridge_test.go @@ -57,9 +57,7 @@ func TestBridge_PerformAcceptsNonJsonObjectResponses(t *testing.T) { input := *models.NewRunInput(jobRunID, cltest.JSONFromString(t, `{"jobRunID": "jobID", "data": 251990120, "statusCode": 200}`), models.RunStatusUnstarted) result := ba.Perform(input, store) require.NoError(t, result.Error()) - resultString, err := result.ResultString() - assert.NoError(t, err) - assert.Equal(t, "251990120", resultString) + assert.Equal(t, "251990120", result.Result().String()) } func TestBridge_Perform_transitionsTo(t *testing.T) { diff --git a/core/adapters/eth_bool_test.go b/core/adapters/eth_bool_test.go index 91ab3221e25..8613106ece8 100644 --- a/core/adapters/eth_bool_test.go +++ b/core/adapters/eth_bool_test.go @@ -6,7 +6,6 @@ import ( "github.com/smartcontractkit/chainlink/core/adapters" "github.com/smartcontractkit/chainlink/core/internal/cltest" "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" ) var evmFalse = "0x0000000000000000000000000000000000000000000000000000000000000000" @@ -32,11 +31,8 @@ func TestEthBool_Perform(t *testing.T) { past := cltest.NewRunInput(cltest.JSONFromString(t, test.json)) adapter := adapters.EthBool{} result := adapter.Perform(past, nil) - - val, err := result.ResultString() - require.NoError(t, err) assert.NoError(t, result.Error()) - assert.Equal(t, test.expected, val) + assert.Equal(t, test.expected, result.Result().String()) }) } } diff --git a/core/adapters/eth_format_test.go b/core/adapters/eth_format_test.go index a0a333fc6ac..a6141a93062 100644 --- a/core/adapters/eth_format_test.go +++ b/core/adapters/eth_format_test.go @@ -40,9 +40,7 @@ func TestEthBytes32_Perform(t *testing.T) { result := adapter.Perform(past, nil) require.NoError(t, result.Error()) - val, err := result.ResultString() - require.NoError(t, err) - assert.Equal(t, test.expected, val) + assert.Equal(t, test.expected, result.Result().String()) }) } } @@ -84,9 +82,7 @@ func TestEthInt256_Perform(t *testing.T) { assert.Error(t, result.Error()) } else { require.NoError(t, result.Error()) - val, err := result.ResultString() - require.NoError(t, err) - assert.Equal(t, test.want, val) + assert.Equal(t, test.want, result.Result().String()) } }) } @@ -128,9 +124,7 @@ func TestEthUint256_Perform(t *testing.T) { require.Error(t, result.Error()) } else { require.NoError(t, result.Error()) - val, err := result.ResultString() - require.NoError(t, err) - assert.Equal(t, test.want, val) + assert.Equal(t, test.want, result.Result().String()) } }) } diff --git a/core/adapters/eth_tx_test.go b/core/adapters/eth_tx_test.go index 3da9f0e8d62..14770e461db 100644 --- a/core/adapters/eth_tx_test.go +++ b/core/adapters/eth_tx_test.go @@ -303,11 +303,9 @@ func TestEthTxAdapter_Perform_FromPendingConfirmations_ConfirmCompletes(t *testi require.NoError(t, output.Error()) assert.True(t, output.Status().Completed()) - value, err := output.ResultString() - require.Nil(t, err) - assert.Equal(t, confirmedHash.String(), value) + assert.Equal(t, confirmedHash.String(), output.Result().String()) - tx, err = store.FindTx(tx.ID) + tx, err := store.FindTx(tx.ID) require.NoError(t, err) assert.True(t, tx.Confirmed) require.Len(t, tx.Attempts, 2) diff --git a/core/adapters/http_test.go b/core/adapters/http_test.go index 1ced321fa7c..6e350fe3042 100644 --- a/core/adapters/http_test.go +++ b/core/adapters/http_test.go @@ -94,9 +94,7 @@ func TestHTTPGet_Perform(t *testing.T) { require.Error(t, result.Error()) } else { require.NoError(t, result.Error()) - val, err := result.ResultString() - require.NoError(t, err) - assert.Equal(t, test.want, val) + assert.Equal(t, test.want, result.Result().String()) } assert.Equal(t, false, result.Status().PendingBridge()) }) diff --git a/core/adapters/multiply_test.go b/core/adapters/multiply_test.go index 2a302998976..4a0960c4597 100644 --- a/core/adapters/multiply_test.go +++ b/core/adapters/multiply_test.go @@ -53,10 +53,7 @@ func TestMultiply_Perform(t *testing.T) { assert.NoError(t, jsonErr) } else { require.NoError(t, result.Error()) - val, err := result.ResultString() - assert.NoError(t, err) - assert.Equal(t, test.want, val) - assert.NoError(t, jsonErr) + assert.Equal(t, test.want, result.Result().String()) } }) } diff --git a/core/adapters/random_test.go b/core/adapters/random_test.go index 9cc074cb053..3da00b33455 100644 --- a/core/adapters/random_test.go +++ b/core/adapters/random_test.go @@ -14,9 +14,7 @@ func TestRandom_Perform(t *testing.T) { adapter := adapters.Random{} result := adapter.Perform(models.RunInput{}, nil) require.NoError(t, result.Error()) - val, err := result.ResultString() - require.NoError(t, err) res := new(big.Int) - res, ok := res.SetString(val, 10) + res, ok := res.SetString(result.Result().String(), 10) assert.True(t, ok) } diff --git a/core/store/models/run_output.go b/core/store/models/run_output.go index d2ed2e066ac..0662be508f3 100644 --- a/core/store/models/run_output.go +++ b/core/store/models/run_output.go @@ -1,8 +1,6 @@ package models import ( - "fmt" - "github.com/tidwall/gjson" ) @@ -89,15 +87,6 @@ func (ro RunOutput) Result() gjson.Result { return ro.data.Get("result") } -// ResultString returns the "result" value as a string if possible -func (ro RunOutput) ResultString() (string, error) { - val := ro.Result() - if val.Type != gjson.String { - return "", fmt.Errorf("non string result") - } - return val.String(), nil -} - // Get searches for and returns the JSON at the given path. func (ro RunOutput) Get(path string) gjson.Result { return ro.data.Get(path) From 41a807452a56db7eedb8aa55665b56052cab574f Mon Sep 17 00:00:00 2001 From: John Barker Date: Tue, 29 Oct 2019 17:21:47 +0100 Subject: [PATCH 036/199] Add clarity to comments in TestBulkDeleteRuns --- core/store/orm/orm_test.go | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/core/store/orm/orm_test.go b/core/store/orm/orm_test.go index c814d7012d0..3d3415500a2 100644 --- a/core/store/orm/orm_test.go +++ b/core/store/orm/orm_test.go @@ -1015,7 +1015,8 @@ func TestBulkDeleteRuns(t *testing.T) { require.NoError(t, store.ORM.CreateJob(&job)) initiator := job.Initiators[0] - // matches updated before but none of the statuses + // bulk delete should not delete these because they match the updated before + // but none of the statuses oldIncompleteRun := job.NewRun(initiator) oldIncompleteRun.Result = models.RunResult{Data: cltest.JSONFromString(t, `{"result": 17}`)} oldIncompleteRun.Status = models.RunStatusInProgress @@ -1023,7 +1024,8 @@ func TestBulkDeleteRuns(t *testing.T) { require.NoError(t, err) db.Model(&oldIncompleteRun).UpdateColumn("updated_at", cltest.ParseISO8601(t, "2018-01-01T00:00:00Z")) - // matches one of the statuses and the updated before + // bulk delete *SHOULD* delete these because they match one of the statuses + // and the updated before oldCompletedRun := job.NewRun(initiator) oldCompletedRun.Result = models.RunResult{Data: cltest.JSONFromString(t, `{"result": 19}`)} oldCompletedRun.Status = models.RunStatusCompleted @@ -1031,7 +1033,8 @@ func TestBulkDeleteRuns(t *testing.T) { require.NoError(t, err) db.Model(&oldCompletedRun).UpdateColumn("updated_at", cltest.ParseISO8601(t, "2018-01-01T00:00:00Z")) - // matches one of the statuses but not the updated before + // bulk delete should not delete these because they match one of the + // statuses but not the updated before newCompletedRun := job.NewRun(initiator) newCompletedRun.Result = models.RunResult{Data: cltest.JSONFromString(t, `{"result": 23}`)} newCompletedRun.Status = models.RunStatusCompleted @@ -1039,7 +1042,7 @@ func TestBulkDeleteRuns(t *testing.T) { require.NoError(t, err) db.Model(&newCompletedRun).UpdateColumn("updated_at", cltest.ParseISO8601(t, "2018-01-30T00:00:00Z")) - // matches nothing + // bulk delete should not delete these because none of their attributes match newIncompleteRun := job.NewRun(initiator) newIncompleteRun.Result = models.RunResult{Data: cltest.JSONFromString(t, `{"result": 71}`)} newIncompleteRun.Status = models.RunStatusCompleted From 8e4171760c9ab29a91b48f6061796c5f738040ad Mon Sep 17 00:00:00 2001 From: John Barker Date: Tue, 29 Oct 2019 17:34:14 +0100 Subject: [PATCH 037/199] Remove last bit of mutation from RunResult --- core/store/models/job_run.go | 22 ++++++++++++++++------ core/store/models/job_run_test.go | 9 ++++----- core/store/models/run_result.go | 7 ------- core/store/models/run_result_test.go | 9 --------- core/store/tx_manager_test.go | 1 + 5 files changed, 21 insertions(+), 27 deletions(-) diff --git a/core/store/models/job_run.go b/core/store/models/job_run.go index b2449adff97..a36208c3fa2 100644 --- a/core/store/models/job_run.go +++ b/core/store/models/job_run.go @@ -108,14 +108,17 @@ func (jr *JobRun) TasksRemain() bool { // SetError sets this job run to failed and saves the error message func (jr *JobRun) SetError(err error) { - jr.Result.SetError(err) + jr.Result.ErrorMessage = null.StringFrom(err.Error()) jr.Status = RunStatusErrored jr.FinishedAt = null.TimeFrom(time.Now()) } // ApplyOutput updates the JobRun's Result and Status func (jr *JobRun) ApplyOutput(result RunOutput) error { - jr.Result.SetError(result.Error()) + if result.HasError() { + jr.SetError(result.Error()) + return nil + } jr.Result.Data = result.Data() jr.setStatus(result.Status()) return nil @@ -123,7 +126,9 @@ func (jr *JobRun) ApplyOutput(result RunOutput) error { // ApplyBridgeRunResult saves the input from a BridgeAdapter func (jr *JobRun) ApplyBridgeRunResult(result BridgeRunResult) error { - jr.Result.SetError(result.GetError()) + if result.HasError() { + jr.SetError(result.GetError()) + } jr.Result.Data = result.Data jr.setStatus(result.Status) return nil @@ -176,20 +181,25 @@ func (tr TaskRun) String() string { // SetError sets this task run to failed and saves the error message func (tr *TaskRun) SetError(err error) { - tr.Result.SetError(err) + tr.Result.ErrorMessage = null.StringFrom(err.Error()) tr.Status = RunStatusErrored } // ApplyBridgeRunResult updates the TaskRun's Result and Status func (tr *TaskRun) ApplyBridgeRunResult(result BridgeRunResult) { - tr.Result.SetError(result.GetError()) + if result.HasError() { + tr.SetError(result.GetError()) + } tr.Result.Data = result.Data tr.Status = result.Status } // ApplyOutput updates the TaskRun's Result and Status func (tr *TaskRun) ApplyOutput(result RunOutput) { - tr.Result.SetError(result.Error()) + if result.HasError() { + tr.SetError(result.Error()) + return + } tr.Result.Data = result.Data() tr.Status = result.Status() } diff --git a/core/store/models/job_run_test.go b/core/store/models/job_run_test.go index d65666e276b..b597cc418d8 100644 --- a/core/store/models/job_run_test.go +++ b/core/store/models/job_run_test.go @@ -161,12 +161,11 @@ func TestForLogger(t *testing.T) { assert.Equal(t, logsWithBlockHeights[8], "observed_height") assert.Equal(t, logsWithBlockHeights[9], big.NewInt(10)) - result := job.NewRun(job.Initiators[0]) - result.Result.ErrorMessage = null.StringFrom("bad idea") - logsWithErr := result.ForLogger() + run := job.NewRun(job.Initiators[0]) + run.SetError(errors.New("bad idea")) + logsWithErr := run.ForLogger() assert.Equal(t, logsWithErr[6], "job_error") - assert.Equal(t, logsWithErr[7], result.Result.ErrorString()) - + assert.Equal(t, logsWithErr[7], run.Result.ErrorString()) } func TestJobRun_NextTaskRun(t *testing.T) { diff --git a/core/store/models/run_result.go b/core/store/models/run_result.go index 5e3a32e6841..2fefae2a0f3 100644 --- a/core/store/models/run_result.go +++ b/core/store/models/run_result.go @@ -15,13 +15,6 @@ type RunResult struct { ErrorMessage null.String `json:"error"` } -// SetError marks the result as errored and saves the specified error message -func (rr *RunResult) SetError(err error) { - if err != nil { - rr.ErrorMessage = null.StringFrom(err.Error()) - } -} - // ResultString returns the "result" value as a string if possible func (rr RunResult) ResultString() (string, error) { val := rr.Result() diff --git a/core/store/models/run_result_test.go b/core/store/models/run_result_test.go index e7b1a7b8d0d..5da08bb21d2 100644 --- a/core/store/models/run_result_test.go +++ b/core/store/models/run_result_test.go @@ -2,7 +2,6 @@ package models_test import ( "encoding/json" - "errors" "testing" "github.com/smartcontractkit/chainlink/core/store/models" @@ -38,11 +37,3 @@ func TestRunResult_Value(t *testing.T) { }) } } - -func TestRunResult_WithError(t *testing.T) { - t.Parallel() - - rr := models.RunResult{} - rr.SetError(errors.New("this blew up")) - assert.True(t, rr.ErrorMessage.Valid) -} diff --git a/core/store/tx_manager_test.go b/core/store/tx_manager_test.go index 8b3b57b8654..6971890f455 100644 --- a/core/store/tx_manager_test.go +++ b/core/store/tx_manager_test.go @@ -930,6 +930,7 @@ func TestTxManager_WithdrawLink_HappyPath(t *testing.T) { transactions, err := app.Store.TxFrom(from) require.NoError(t, err) + require.Len(t, transactions, 1) tx := transactions[0] assert.Equal(t, hash, tx.Hash) assert.Equal(t, nonce, tx.Nonce) From f8dc960e02a1308359d6019f11157cfaaf8dedde Mon Sep 17 00:00:00 2001 From: John Barker Date: Wed, 30 Oct 2019 09:30:06 +0100 Subject: [PATCH 038/199] Encapsulate RunResult on TaskRun and JobRun (LoD) --- core/cmd/renderer.go | 7 +++-- core/internal/features_test.go | 20 +++++++------- core/store/models/job_run.go | 31 +++++++++++++++++++++- core/store/models/job_run_test.go | 4 +-- core/store/models/run_result.go | 24 +---------------- core/store/models/run_result_test.go | 39 ---------------------------- core/web/job_runs_controller_test.go | 8 +++--- 7 files changed, 50 insertions(+), 83 deletions(-) delete mode 100644 core/store/models/run_result_test.go diff --git a/core/cmd/renderer.go b/core/cmd/renderer.go index 925598d8cd8..56e3e98d425 100644 --- a/core/cmd/renderer.go +++ b/core/cmd/renderer.go @@ -1,9 +1,9 @@ package cmd import ( - "reflect" "fmt" "io" + "reflect" "strconv" "github.com/olekukonko/tablewriter" @@ -93,7 +93,7 @@ func (rt RendererTable) renderJobs(jobs []models.JobSpec) error { func (rt RendererTable) renderConfiguration(cwl presenters.ConfigWhitelist) error { table := rt.newTable([]string{"Key", "Value"}) - + table.Append([]string{ "ACCOUNT_ADDRESS", cwl.AccountAddress, @@ -134,7 +134,6 @@ func (rt RendererTable) renderConfiguration(cwl presenters.ConfigWhitelist) erro return nil } - func render(name string, table *tablewriter.Table) { table.SetRowLine(true) table.SetColumnSeparator("║") @@ -267,7 +266,7 @@ func (rt RendererTable) renderJobRuns(runs []presenters.JobRun) error { utils.ISO8601UTC(jr.CreatedAt), utils.NullISO8601UTC(jr.FinishedAt), jr.Result.Data.String(), - jr.Result.ErrorString(), + jr.ErrorString(), }) } diff --git a/core/internal/features_test.go b/core/internal/features_test.go index 4d9876e3d37..7688c60ba93 100644 --- a/core/internal/features_test.go +++ b/core/internal/features_test.go @@ -224,16 +224,16 @@ func TestIntegration_FeeBump(t *testing.T) { eth.EventuallyAllCalled(t) jr = cltest.WaitForJobRunToComplete(t, app.Store, jr) - val, err := jr.TaskRuns[0].Result.ResultString() + val, err := jr.TaskRuns[0].ResultString() assert.NoError(t, err) assert.Equal(t, tickerResponse, val) - val, err = jr.TaskRuns[1].Result.ResultString() + val, err = jr.TaskRuns[1].ResultString() assert.Equal(t, "10583.75", val) assert.NoError(t, err) - val, err = jr.TaskRuns[3].Result.ResultString() + val, err = jr.TaskRuns[3].ResultString() assert.Equal(t, attempt1Hash.String(), val) assert.NoError(t, err) - val, err = jr.Result.ResultString() + val, err = jr.ResultString() assert.Equal(t, attempt1Hash.String(), val) assert.NoError(t, err) } @@ -470,7 +470,7 @@ func TestIntegration_ExternalAdapter_RunLogInitiated(t *testing.T) { tr := jr.TaskRuns[0] assert.Equal(t, "randomnumber", tr.TaskSpec.Type.String()) - val, err := tr.Result.ResultString() + val, err := tr.ResultString() assert.NoError(t, err) assert.Equal(t, eaValue, val) res := tr.Result.Data.Get("extra") @@ -526,7 +526,7 @@ func TestIntegration_ExternalAdapter_Copy(t *testing.T) { assert.Equal(t, "assetprice", tr.TaskSpec.Type.String()) tr = jr.TaskRuns[1] assert.Equal(t, "copy", tr.TaskSpec.Type.String()) - val, err := tr.Result.ResultString() + val, err := tr.ResultString() assert.NoError(t, err) assert.Equal(t, eaPrice, val) } @@ -572,7 +572,7 @@ func TestIntegration_ExternalAdapter_Pending(t *testing.T) { tr := jr.TaskRuns[0] assert.Equal(t, models.RunStatusPendingBridge, tr.Status) - val, err := tr.Result.ResultString() + val, err := tr.ResultString() assert.Error(t, err) assert.Equal(t, "", val) @@ -580,7 +580,7 @@ func TestIntegration_ExternalAdapter_Pending(t *testing.T) { jr = cltest.WaitForJobRunToComplete(t, app.Store, jr) tr = jr.TaskRuns[0] assert.Equal(t, models.RunStatusCompleted, tr.Status) - val, err = tr.Result.ResultString() + val, err = tr.ResultString() assert.NoError(t, err) assert.Equal(t, "100", val) } @@ -632,7 +632,7 @@ func TestIntegration_MultiplierInt256(t *testing.T) { jr := cltest.CreateJobRunViaWeb(t, app, j, `{"result":"-10221.30"}`) jr = cltest.WaitForJobRunToComplete(t, app.Store, jr) - val, err := jr.Result.ResultString() + val, err := jr.ResultString() assert.NoError(t, err) assert.Equal(t, "0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0674e", val) } @@ -648,7 +648,7 @@ func TestIntegration_MultiplierUint256(t *testing.T) { jr := cltest.CreateJobRunViaWeb(t, app, j, `{"result":"10221.30"}`) jr = cltest.WaitForJobRunToComplete(t, app.Store, jr) - val, err := jr.Result.ResultString() + val, err := jr.ResultString() assert.NoError(t, err) assert.Equal(t, "0x00000000000000000000000000000000000000000000000000000000000f98b2", val) } diff --git a/core/store/models/job_run.go b/core/store/models/job_run.go index a36208c3fa2..287e6aa4b85 100644 --- a/core/store/models/job_run.go +++ b/core/store/models/job_run.go @@ -7,6 +7,7 @@ import ( "github.com/ethereum/go-ethereum/common" clnull "github.com/smartcontractkit/chainlink/core/null" "github.com/smartcontractkit/chainlink/core/store/assets" + "github.com/tidwall/gjson" null "gopkg.in/guregu/null.v3" ) @@ -65,7 +66,7 @@ func (jr JobRun) ForLogger(kvs ...interface{}) []interface{} { } if jr.HasError() { - output = append(output, "job_error", jr.Result.ErrorString()) + output = append(output, "job_error", jr.ErrorString()) } if jr.Status == "completed" { @@ -143,6 +144,20 @@ func (jr *JobRun) setStatus(status RunStatus) { } } +// ResultString returns the "result" value as a string if possible +func (jr *JobRun) ResultString() (string, error) { + val := jr.Result.Data.Get("result") + if val.Type != gjson.String { + return "", fmt.Errorf("non string result") + } + return val.String(), nil +} + +// ErrorString returns the error as a string if present, otherwise "". +func (jr *JobRun) ErrorString() string { + return jr.Result.ErrorMessage.ValueOrZero() +} + // RunRequest stores the fields used to initiate the parent job run. type RunRequest struct { ID uint `gorm:"primary_key"` @@ -203,3 +218,17 @@ func (tr *TaskRun) ApplyOutput(result RunOutput) { tr.Result.Data = result.Data() tr.Status = result.Status() } + +// ErrorString returns the error as a string if present, otherwise "". +func (tr *TaskRun) ErrorString() string { + return tr.Result.ErrorMessage.ValueOrZero() +} + +// ResultString returns the "result" value as a string if possible +func (tr *TaskRun) ResultString() (string, error) { + val := tr.Result.Data.Get("result") + if val.Type != gjson.String { + return "", fmt.Errorf("non string result") + } + return val.String(), nil +} diff --git a/core/store/models/job_run_test.go b/core/store/models/job_run_test.go index b597cc418d8..7fb3b9bc46d 100644 --- a/core/store/models/job_run_test.go +++ b/core/store/models/job_run_test.go @@ -31,7 +31,7 @@ func TestJobRuns_RetrievingFromDBWithError(t *testing.T) { run, err := store.FindJobRun(jr.ID) require.NoError(t, err) assert.True(t, run.Result.ErrorMessage.Valid) - assert.Equal(t, "bad idea", run.Result.ErrorString()) + assert.Equal(t, "bad idea", run.ErrorString()) } func TestJobRuns_RetrievingFromDBWithData(t *testing.T) { @@ -165,7 +165,7 @@ func TestForLogger(t *testing.T) { run.SetError(errors.New("bad idea")) logsWithErr := run.ForLogger() assert.Equal(t, logsWithErr[6], "job_error") - assert.Equal(t, logsWithErr[7], run.Result.ErrorString()) + assert.Equal(t, logsWithErr[7], run.ErrorString()) } func TestJobRun_NextTaskRun(t *testing.T) { diff --git a/core/store/models/run_result.go b/core/store/models/run_result.go index 2fefae2a0f3..fce1cd17598 100644 --- a/core/store/models/run_result.go +++ b/core/store/models/run_result.go @@ -1,35 +1,13 @@ package models import ( - "fmt" - - "github.com/tidwall/gjson" null "gopkg.in/guregu/null.v3" ) // RunResult keeps track of the outcome of a TaskRun or JobRun. It stores the -// Data and ErrorMessage, and contains a field to track the status. +// Data and ErrorMessage. type RunResult struct { ID uint `json:"-" gorm:"primary_key;auto_increment"` Data JSON `json:"data" gorm:"type:text"` ErrorMessage null.String `json:"error"` } - -// ResultString returns the "result" value as a string if possible -func (rr RunResult) ResultString() (string, error) { - val := rr.Result() - if val.Type != gjson.String { - return "", fmt.Errorf("non string result") - } - return val.String(), nil -} - -// Result returns the result as a gjson object -func (rr RunResult) Result() gjson.Result { - return rr.Data.Get("result") -} - -// ErrorString returns the error as a string if present, otherwise "". -func (rr RunResult) ErrorString() string { - return rr.ErrorMessage.ValueOrZero() -} diff --git a/core/store/models/run_result_test.go b/core/store/models/run_result_test.go deleted file mode 100644 index 5da08bb21d2..00000000000 --- a/core/store/models/run_result_test.go +++ /dev/null @@ -1,39 +0,0 @@ -package models_test - -import ( - "encoding/json" - "testing" - - "github.com/smartcontractkit/chainlink/core/store/models" - "github.com/stretchr/testify/assert" -) - -func TestRunResult_Value(t *testing.T) { - t.Parallel() - - tests := []struct { - name string - json string - want string - wantErrored bool - }{ - {"string", `{"result": "100", "other": "101"}`, "100", false}, - {"integer", `{"result": 100}`, "", true}, - {"float", `{"result": 100.01}`, "", true}, - {"boolean", `{"result": true}`, "", true}, - {"null", `{"result": null}`, "", true}, - {"no key", `{"other": 100}`, "", true}, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - var data models.JSON - assert.NoError(t, json.Unmarshal([]byte(test.json), &data)) - rr := models.RunResult{Data: data} - - val, err := rr.ResultString() - assert.Equal(t, test.want, val) - assert.Equal(t, test.wantErrored, (err != nil)) - }) - } -} diff --git a/core/web/job_runs_controller_test.go b/core/web/job_runs_controller_test.go index b213dda4484..aaedea30f68 100644 --- a/core/web/job_runs_controller_test.go +++ b/core/web/job_runs_controller_test.go @@ -131,7 +131,7 @@ func TestJobRunsController_Create_Success(t *testing.T) { jr := cltest.CreateJobRunViaWeb(t, app, j, `{"result":"100"}`) jr = cltest.WaitForJobRunToComplete(t, app.Store, jr) - val, err := jr.Result.ResultString() + val, err := jr.ResultString() assert.NoError(t, err) assert.Equal(t, "100", val) } @@ -198,7 +198,7 @@ func TestJobRunsController_Create_ExternalInitiator_Success(t *testing.T) { j, *eia, `{"result":"100"}`, ) jr = cltest.WaitForJobRunToComplete(t, app.Store, jr) - val, err := jr.Result.ResultString() + val, err := jr.ResultString() assert.NoError(t, err) assert.Equal(t, "100", val) } @@ -327,7 +327,7 @@ func TestJobRunsController_Update_Success(t *testing.T) { require.Equal(t, jr.ID, respJobRun.ID) jr = cltest.WaitForJobRunToComplete(t, app.Store, jr) - val, err := jr.Result.ResultString() + val, err := jr.ResultString() assert.NoError(t, err) assert.Equal(t, "100", val) }) @@ -406,7 +406,7 @@ func TestJobRunsController_Update_WithError(t *testing.T) { assert.Equal(t, jr.ID, respJobRun.ID) jr = cltest.WaitForJobRunStatus(t, app.Store, jr, models.RunStatusErrored) - val, err := jr.Result.ResultString() + val, err := jr.ResultString() assert.NoError(t, err) assert.Equal(t, "0", val) } From 85e43bf9e94fcfb930b0cba38cc4ecf70f4276c3 Mon Sep 17 00:00:00 2001 From: John Barker Date: Wed, 30 Oct 2019 09:47:31 +0100 Subject: [PATCH 039/199] Move RunResult into job_run.go --- core/store/models/job_run.go | 8 ++++++++ core/store/models/run_result.go | 13 ------------- 2 files changed, 8 insertions(+), 13 deletions(-) delete mode 100644 core/store/models/run_result.go diff --git a/core/store/models/job_run.go b/core/store/models/job_run.go index 287e6aa4b85..9d410499213 100644 --- a/core/store/models/job_run.go +++ b/core/store/models/job_run.go @@ -232,3 +232,11 @@ func (tr *TaskRun) ResultString() (string, error) { } return val.String(), nil } + +// RunResult keeps track of the outcome of a TaskRun or JobRun. It stores the +// Data and ErrorMessage. +type RunResult struct { + ID uint `json:"-" gorm:"primary_key;auto_increment"` + Data JSON `json:"data" gorm:"type:text"` + ErrorMessage null.String `json:"error"` +} diff --git a/core/store/models/run_result.go b/core/store/models/run_result.go deleted file mode 100644 index fce1cd17598..00000000000 --- a/core/store/models/run_result.go +++ /dev/null @@ -1,13 +0,0 @@ -package models - -import ( - null "gopkg.in/guregu/null.v3" -) - -// RunResult keeps track of the outcome of a TaskRun or JobRun. It stores the -// Data and ErrorMessage. -type RunResult struct { - ID uint `json:"-" gorm:"primary_key;auto_increment"` - Data JSON `json:"data" gorm:"type:text"` - ErrorMessage null.String `json:"error"` -} From 19588ef6f1ddc700df925e226dd3063f85ff992b Mon Sep 17 00:00:00 2001 From: John Barker Date: Wed, 30 Oct 2019 14:47:53 +0100 Subject: [PATCH 040/199] ApplyOutput, ApplyBridgeRunResult don't need to return error --- core/store/models/job_run.go | 8 +++----- core/store/models/job_run_test.go | 9 +++------ 2 files changed, 6 insertions(+), 11 deletions(-) diff --git a/core/store/models/job_run.go b/core/store/models/job_run.go index 9d410499213..a3fcb98679c 100644 --- a/core/store/models/job_run.go +++ b/core/store/models/job_run.go @@ -115,24 +115,22 @@ func (jr *JobRun) SetError(err error) { } // ApplyOutput updates the JobRun's Result and Status -func (jr *JobRun) ApplyOutput(result RunOutput) error { +func (jr *JobRun) ApplyOutput(result RunOutput) { if result.HasError() { jr.SetError(result.Error()) - return nil + return } jr.Result.Data = result.Data() jr.setStatus(result.Status()) - return nil } // ApplyBridgeRunResult saves the input from a BridgeAdapter -func (jr *JobRun) ApplyBridgeRunResult(result BridgeRunResult) error { +func (jr *JobRun) ApplyBridgeRunResult(result BridgeRunResult) { if result.HasError() { jr.SetError(result.GetError()) } jr.Result.Data = result.Data jr.setStatus(result.Status) - return nil } func (jr *JobRun) setStatus(status RunStatus) { diff --git a/core/store/models/job_run_test.go b/core/store/models/job_run_test.go index 7fb3b9bc46d..9be21a956df 100644 --- a/core/store/models/job_run_test.go +++ b/core/store/models/job_run_test.go @@ -204,8 +204,7 @@ func TestJobRun_ApplyOutput_CompletedWithNoTasksRemaining(t *testing.T) { result := models.NewRunOutputComplete(models.JSON{}) jobRun.TaskRuns[0].ApplyOutput(result) - err := jobRun.ApplyOutput(result) - assert.NoError(t, err) + jobRun.ApplyOutput(result) assert.True(t, jobRun.FinishedAt.Valid) } @@ -216,8 +215,7 @@ func TestJobRun_ApplyOutput_CompletedWithTasksRemaining(t *testing.T) { jobRun := job.NewRun(job.Initiators[0]) result := models.NewRunOutputComplete(models.JSON{}) - err := jobRun.ApplyOutput(result) - assert.NoError(t, err) + jobRun.ApplyOutput(result) assert.False(t, jobRun.FinishedAt.Valid) assert.Equal(t, jobRun.Status, models.RunStatusInProgress) } @@ -230,7 +228,6 @@ func TestJobRun_ApplyOutput_ErrorSetsFinishedAt(t *testing.T) { jobRun.Status = models.RunStatusErrored result := models.NewRunOutputError(errors.New("oh futz")) - err := jobRun.ApplyOutput(result) - assert.NoError(t, err) + jobRun.ApplyOutput(result) assert.True(t, jobRun.FinishedAt.Valid) } From 2549e7434aedb8168323734be2bedeab4cb6bdd4 Mon Sep 17 00:00:00 2001 From: John Barker Date: Wed, 30 Oct 2019 14:49:35 +0100 Subject: [PATCH 041/199] Explain why biAlias is used in BridgeRunResult.UnmarshalJSON --- core/store/models/bridge_run_result.go | 1 + 1 file changed, 1 insertion(+) diff --git a/core/store/models/bridge_run_result.go b/core/store/models/bridge_run_result.go index ae6da310b62..4ff548a1724 100644 --- a/core/store/models/bridge_run_result.go +++ b/core/store/models/bridge_run_result.go @@ -19,6 +19,7 @@ type BridgeRunResult struct { // UnmarshalJSON parses the given input and updates the BridgeRunResult in the // external adapter format. func (brr *BridgeRunResult) UnmarshalJSON(input []byte) error { + // XXX: This indirection prevents an infinite regress during json.Unmarshal type biAlias BridgeRunResult var anon biAlias err := json.Unmarshal(input, &anon) From 91c9863d05dd1babab59e2612e956522677e5ef6 Mon Sep 17 00:00:00 2001 From: John Barker Date: Wed, 30 Oct 2019 14:52:17 +0100 Subject: [PATCH 042/199] require.NoError on json.Unmarshal in TestEthTxAdapter_Perform_AppendingTransactionReceipts --- core/adapters/eth_tx_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/adapters/eth_tx_test.go b/core/adapters/eth_tx_test.go index 14770e461db..edd3e92ff98 100644 --- a/core/adapters/eth_tx_test.go +++ b/core/adapters/eth_tx_test.go @@ -365,7 +365,7 @@ func TestEthTxAdapter_Perform_AppendingTransactionReceipts(t *testing.T) { receiptsJSON := output.Get("ethereumReceipts").String() var receipts []models.TxReceipt require.NotEqual(t, "", receiptsJSON) - assert.NoError(t, json.Unmarshal([]byte(receiptsJSON), &receipts)) + require.NoError(t, json.Unmarshal([]byte(receiptsJSON), &receipts)) assert.Equal(t, []models.TxReceipt{previousReceipt, receipt}, receipts) ethMock.EventuallyAllCalled(t) From c85b99d7a330f31253ff4561b8cf956dbda80920 Mon Sep 17 00:00:00 2001 From: John Barker Date: Wed, 30 Oct 2019 15:06:01 +0100 Subject: [PATCH 043/199] Suggestions around assert/require for TestEthTx,TestExternalCopy etc. --- core/adapters/eth_tx_test.go | 2 +- core/adapters/http_test.go | 6 +----- core/internal/features_test.go | 14 +++++++------- 3 files changed, 9 insertions(+), 13 deletions(-) diff --git a/core/adapters/eth_tx_test.go b/core/adapters/eth_tx_test.go index edd3e92ff98..ad2fac6377f 100644 --- a/core/adapters/eth_tx_test.go +++ b/core/adapters/eth_tx_test.go @@ -658,7 +658,7 @@ func TestEthTxAdapter_Perform_CreateTxWithEmptyResponseErrorTreatsAsPendingConfi adapter := adapters.EthTx{} output := adapter.Perform(models.RunInput{}, store) - assert.False(t, output.HasError()) + require.NoError(t, output.Error()) assert.Equal(t, models.RunStatusPendingConfirmations, output.Status()) // Have a head come through with the same empty response diff --git a/core/adapters/http_test.go b/core/adapters/http_test.go index 6e350fe3042..930523ca468 100644 --- a/core/adapters/http_test.go +++ b/core/adapters/http_test.go @@ -274,11 +274,7 @@ func TestHttpPost_Perform(t *testing.T) { val := result.Result() assert.Equal(t, test.want, val.String()) assert.NotEqual(t, test.wantErrored, val.Exists()) - if test.wantErrored { - require.Error(t, result.Error()) - } else { - require.NoError(t, result.Error()) - } + require.Equal(t, test.wantErrored, result.HasError()) assert.Equal(t, false, result.Status().PendingBridge()) }) } diff --git a/core/internal/features_test.go b/core/internal/features_test.go index 7688c60ba93..e658c00f36a 100644 --- a/core/internal/features_test.go +++ b/core/internal/features_test.go @@ -497,20 +497,20 @@ func TestIntegration_ExternalAdapter_Copy(t *testing.T) { eaResponse := fmt.Sprintf(`{"data":{"price": "%v", "quote": "%v"}}`, eaPrice, eaQuote) ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - assert.Equal(t, "POST", r.Method) - assert.Equal(t, "/", r.URL.Path) + require.Equal(t, "POST", r.Method) + require.Equal(t, "/", r.URL.Path) b, err := ioutil.ReadAll(r.Body) - assert.NoError(t, err) + require.NoError(t, err) body := cltest.JSONFromBytes(t, b) data := body.Get("data") - assert.True(t, data.Exists()) + require.True(t, data.Exists()) bodyParam := data.Get("bodyParam") - assert.True(t, bodyParam.Exists()) - assert.Equal(t, true, bodyParam.Bool()) + require.True(t, bodyParam.Exists()) + require.Equal(t, true, bodyParam.Bool()) url := body.Get("responseURL") - assert.Contains(t, url.String(), "https://test.chain.link/always/v2/runs") + require.Contains(t, url.String(), "https://test.chain.link/always/v2/runs") w.WriteHeader(http.StatusOK) io.WriteString(w, eaResponse) From fba9db63d338a5f3b77c1a9102c14291ef2006c2 Mon Sep 17 00:00:00 2001 From: John Barker Date: Wed, 30 Oct 2019 15:24:12 +0100 Subject: [PATCH 044/199] Remove redundant tests in TestBridge_Perform_transitionsTo --- core/adapters/bridge_test.go | 4 ---- core/internal/cltest/factories.go | 2 ++ 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/core/adapters/bridge_test.go b/core/adapters/bridge_test.go index 09f0eed0ec1..b3c0755e127 100644 --- a/core/adapters/bridge_test.go +++ b/core/adapters/bridge_test.go @@ -89,10 +89,6 @@ func TestBridge_Perform_transitionsTo(t *testing.T) { assert.Equal(t, test.result, result.Data().String()) assert.Equal(t, test.wantStatus, result.Status()) - if test.wantStatus.Completed() { - assert.Equal(t, input.Data(), result.Data()) - assert.Equal(t, input.Status(), result.Status()) - } }) } } diff --git a/core/internal/cltest/factories.go b/core/internal/cltest/factories.go index 77e2387a19f..160afd26276 100644 --- a/core/internal/cltest/factories.go +++ b/core/internal/cltest/factories.go @@ -518,4 +518,6 @@ func CreateServiceAgreementViaWeb( responseSA := models.ServiceAgreement{} err := ParseJSONAPIResponse(t, resp, &responseSA) require.NoError(t, err) + + return FindServiceAgreement(t, app.Store, responseSA.ID) } From 42f9276ba499a02c42d7dd8823bd178bcdc4d0b8 Mon Sep 17 00:00:00 2001 From: John Barker Date: Thu, 31 Oct 2019 08:32:06 +0100 Subject: [PATCH 045/199] Remove redundant zero RunResult{} in NewRun --- core/store/models/job_spec.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/core/store/models/job_spec.go b/core/store/models/job_spec.go index d51b645cf5e..02dabf6f398 100644 --- a/core/store/models/job_spec.go +++ b/core/store/models/job_spec.go @@ -135,7 +135,6 @@ func (j JobSpec) NewRun(i Initiator) JobRun { ID: trid, JobRunID: jrid, TaskSpec: task, - Result: RunResult{}, } } @@ -151,7 +150,6 @@ func (j JobSpec) NewRun(i Initiator) JobRun { Initiator: i, InitiatorID: i.ID, Status: RunStatusUnstarted, - Result: RunResult{}, } } From 2b31e0e19f5f933a860649a1264f24187e746f38 Mon Sep 17 00:00:00 2001 From: John Barker Date: Thu, 31 Oct 2019 08:42:00 +0100 Subject: [PATCH 046/199] Remove input error path, not actually used --- core/adapters/bridge.go | 2 -- core/adapters/bridge_test.go | 1 - core/store/models/run_input.go | 6 ------ 3 files changed, 9 deletions(-) diff --git a/core/adapters/bridge.go b/core/adapters/bridge.go index 9f681ff18ac..f30b38f695b 100644 --- a/core/adapters/bridge.go +++ b/core/adapters/bridge.go @@ -30,8 +30,6 @@ type Bridge struct { func (ba *Bridge) Perform(input models.RunInput, store *store.Store) models.RunOutput { if input.Status().Completed() { return models.NewRunOutputComplete(input.Data()) - } else if input.Status().Errored() { - return models.NewRunOutputError(input.Error()) } else if input.Status().PendingBridge() { return models.NewRunOutputInProgress(input.Data()) } diff --git a/core/adapters/bridge_test.go b/core/adapters/bridge_test.go index b3c0755e127..d7628f9ac14 100644 --- a/core/adapters/bridge_test.go +++ b/core/adapters/bridge_test.go @@ -69,7 +69,6 @@ func TestBridge_Perform_transitionsTo(t *testing.T) { result string }{ {"from pending bridge", models.RunStatusPendingBridge, models.RunStatusInProgress, `{"result":"100"}`}, - {"from errored", models.RunStatusErrored, models.RunStatusErrored, ""}, {"from in progress", models.RunStatusInProgress, models.RunStatusPendingBridge, ""}, {"from completed", models.RunStatusCompleted, models.RunStatusCompleted, `{"result":"100"}`}, } diff --git a/core/store/models/run_input.go b/core/store/models/run_input.go index b14b74c7468..b629e09d809 100644 --- a/core/store/models/run_input.go +++ b/core/store/models/run_input.go @@ -11,7 +11,6 @@ type RunInput struct { jobRunID ID data JSON status RunStatus - err error } // NewRunInput creates a new RunInput with arbitrary data @@ -47,11 +46,6 @@ func (ri RunInput) ResultString() (string, error) { return val.String(), nil } -// GetError returns the error of a RunResult if it is present. -func (ri RunInput) Error() error { - return ri.err -} - // Status returns the RunInput's status func (ri RunInput) Status() RunStatus { return ri.status From 7c87f5ba12ff55eb9b0e5a7edec5c637ac8ff224 Mon Sep 17 00:00:00 2001 From: John Barker Date: Fri, 1 Nov 2019 12:04:43 +0100 Subject: [PATCH 047/199] Don't ignore err on JSON.Add, it scares people --- core/adapters/eth_tx.go | 32 +++++++++++++++++++++++++------- core/adapters/eth_tx_test.go | 6 ++++-- core/store/models/run_input.go | 5 ++++- core/store/models/run_output.go | 8 ++++++-- 4 files changed, 39 insertions(+), 12 deletions(-) diff --git a/core/adapters/eth_tx.go b/core/adapters/eth_tx.go index e6d6eed807d..73ee8b21ac1 100644 --- a/core/adapters/eth_tx.go +++ b/core/adapters/eth_tx.go @@ -96,8 +96,10 @@ func createTxRunResult( return models.NewRunOutputError(err) } - var output models.JSON - output, _ = output.Add("result", tx.Hash.String()) + output, err := models.JSON{}.Add("result", tx.Hash.String()) + if err != nil { + return models.NewRunOutputError(err) + } txAttempt := tx.Attempts[0] receipt, state, err := store.TxManager.CheckAttempt(txAttempt, tx.SentAt) @@ -150,11 +152,20 @@ func ensureTxRunResult(input models.RunInput, str *strpkg.Store) models.RunOutpu if receipt != nil && !receipt.Unconfirmed() { // If the tx has been confirmed, record the hash in the output hex := receipt.Hash.String() - output, _ = output.Add("result", hex) - output, _ = output.Add("latestOutgoingTxHash", hex) + output, err = output.Add("result", hex) + if err != nil { + return models.NewRunOutputError(err) + } + output, err = output.Add("latestOutgoingTxHash", hex) + if err != nil { + return models.NewRunOutputError(err) + } } else { // If the tx is still unconfirmed, just copy over the original tx hash. - output, _ = output.Add("result", hash) + output, err = output.Add("result", hash) + if err != nil { + return models.NewRunOutputError(err) + } } if state == strpkg.Safe { @@ -179,8 +190,15 @@ func addReceiptToResult( } receipts = append(receipts, *receipt) - data, _ = data.Add("ethereumReceipts", receipts) - data, _ = data.Add("result", receipt.Hash.String()) + var err error + data, err = data.Add("ethereumReceipts", receipts) + if err != nil { + return models.NewRunOutputError(err) + } + data, err = data.Add("result", receipt.Hash.String()) + if err != nil { + return models.NewRunOutputError(err) + } return models.NewRunOutputComplete(data) } diff --git a/core/adapters/eth_tx_test.go b/core/adapters/eth_tx_test.go index ad2fac6377f..1bfb34f4e4e 100644 --- a/core/adapters/eth_tx_test.go +++ b/core/adapters/eth_tx_test.go @@ -354,8 +354,10 @@ func TestEthTxAdapter_Perform_AppendingTransactionReceipts(t *testing.T) { adapter := adapters.EthTx{} previousReceipt := models.TxReceipt{Hash: cltest.NewHash(), BlockNumber: cltest.Int(sentAt - 10)} - data, _ := models.JSON{}.Add("result", a.Hash.String()) - data, _ = data.Add("ethereumReceipts", []models.TxReceipt{previousReceipt}) + data, err := models.JSON{}.Add("result", a.Hash.String()) + require.NoError(t, err) + data, err = data.Add("ethereumReceipts", []models.TxReceipt{previousReceipt}) + require.NoError(t, err) input := *models.NewRunInput(models.NewID(), data, models.RunStatusPendingConfirmations) output := adapter.Perform(input, store) diff --git a/core/store/models/run_input.go b/core/store/models/run_input.go index b629e09d809..35a80efc291 100644 --- a/core/store/models/run_input.go +++ b/core/store/models/run_input.go @@ -24,7 +24,10 @@ func NewRunInput(jobRunID *ID, data JSON, status RunStatus) *RunInput { // NewRunInputWithResult creates a new RunInput with a value in the "result" field func NewRunInputWithResult(jobRunID *ID, value interface{}, status RunStatus) *RunInput { - data, _ := JSON{}.Add("result", value) + data, err := JSON{}.Add("result", value) + if err != nil { + panic(fmt.Sprintf("invariant violated, add should not fail on empty JSON %v", err)) + } return &RunInput{ jobRunID: *jobRunID, data: data, diff --git a/core/store/models/run_output.go b/core/store/models/run_output.go index 0662be508f3..d5aafb4e9c0 100644 --- a/core/store/models/run_output.go +++ b/core/store/models/run_output.go @@ -1,6 +1,8 @@ package models import ( + "fmt" + "github.com/tidwall/gjson" ) @@ -22,8 +24,10 @@ func NewRunOutputError(err error) RunOutput { // NewRunOutputCompleteWithResult returns a new RunOutput that is complete and // contains a result func NewRunOutputCompleteWithResult(resultVal interface{}) RunOutput { - var data JSON - data, _ = data.Add("result", resultVal) + data, err := JSON{}.Add("result", resultVal) + if err != nil { + panic(fmt.Sprintf("invariant violated, add should not fail on empty JSON %v", err)) + } return NewRunOutputComplete(data) } From a77dec63635a95fbac0a7f1cfa0545eb7f96ac5f Mon Sep 17 00:00:00 2001 From: John Barker Date: Fri, 1 Nov 2019 12:42:40 +0100 Subject: [PATCH 048/199] core/store/models/job_run.go:221:20: func (*TaskRun).ErrorString is unused (U1001) --- core/store/models/job_run.go | 5 ----- 1 file changed, 5 deletions(-) diff --git a/core/store/models/job_run.go b/core/store/models/job_run.go index a3fcb98679c..811f5e8aaaa 100644 --- a/core/store/models/job_run.go +++ b/core/store/models/job_run.go @@ -217,11 +217,6 @@ func (tr *TaskRun) ApplyOutput(result RunOutput) { tr.Status = result.Status() } -// ErrorString returns the error as a string if present, otherwise "". -func (tr *TaskRun) ErrorString() string { - return tr.Result.ErrorMessage.ValueOrZero() -} - // ResultString returns the "result" value as a string if possible func (tr *TaskRun) ResultString() (string, error) { val := tr.Result.Data.Get("result") From 58d1b1aee83a1afa41288108ea044641111f4925 Mon Sep 17 00:00:00 2001 From: John Barker Date: Fri, 1 Nov 2019 12:56:40 +0100 Subject: [PATCH 049/199] Remove ResultString from TaskRun and JobRun, use test helper --- core/adapters/wasm_test.go | 9 +++-- core/internal/cltest/cltest.go | 6 ++++ core/internal/features_test.go | 51 ++++++++++++---------------- core/store/models/job_run.go | 19 ----------- core/web/job_runs_controller_test.go | 20 +++++------ 5 files changed, 39 insertions(+), 66 deletions(-) diff --git a/core/adapters/wasm_test.go b/core/adapters/wasm_test.go index 011ebb22240..362c82f1436 100644 --- a/core/adapters/wasm_test.go +++ b/core/adapters/wasm_test.go @@ -85,14 +85,13 @@ func TestWasm_Perform(t *testing.T) { if test.jsonError { assert.Error(t, jsonErr) } else if test.errored { - assert.Error(t, result.GetError()) assert.NoError(t, jsonErr) + assert.Error(t, result.GetError()) } else { - val, err := result.ResultString() - assert.NoError(t, err) - assert.Equal(t, test.want, val) - assert.NoError(t, result.GetError()) assert.NoError(t, jsonErr) + value := cltest.MustResultString(t, result) + assert.Equal(t, test.want, value) + assert.NoError(t, result.GetError()) } }) } diff --git a/core/internal/cltest/cltest.go b/core/internal/cltest/cltest.go index 09443300f09..05c6fac069c 100644 --- a/core/internal/cltest/cltest.go +++ b/core/internal/cltest/cltest.go @@ -1150,3 +1150,9 @@ func NewRunInputWithResult(value interface{}) models.RunInput { jobRunID := models.NewID() return *models.NewRunInputWithResult(jobRunID, value, models.RunStatusUnstarted) } + +func MustResultString(t *testing.T, input models.RunResult) string { + result := input.Data.Get("result") + require.Equal(t, gjson.String, result.Type, fmt.Sprintf("result type %s is not string", result.Type)) + return result.String() +} diff --git a/core/internal/features_test.go b/core/internal/features_test.go index e658c00f36a..8511c83bef7 100644 --- a/core/internal/features_test.go +++ b/core/internal/features_test.go @@ -224,18 +224,15 @@ func TestIntegration_FeeBump(t *testing.T) { eth.EventuallyAllCalled(t) jr = cltest.WaitForJobRunToComplete(t, app.Store, jr) - val, err := jr.TaskRuns[0].ResultString() - assert.NoError(t, err) - assert.Equal(t, tickerResponse, val) - val, err = jr.TaskRuns[1].ResultString() - assert.Equal(t, "10583.75", val) - assert.NoError(t, err) - val, err = jr.TaskRuns[3].ResultString() - assert.Equal(t, attempt1Hash.String(), val) - assert.NoError(t, err) - val, err = jr.ResultString() - assert.Equal(t, attempt1Hash.String(), val) - assert.NoError(t, err) + require.Len(t, jr.TaskRuns, 4) + value := cltest.MustResultString(t, jr.TaskRuns[0].Result) + assert.Equal(t, tickerResponse, value) + value = cltest.MustResultString(t, jr.TaskRuns[1].Result) + assert.Equal(t, "10583.75", value) + value = cltest.MustResultString(t, jr.TaskRuns[3].Result) + assert.Equal(t, attempt1Hash.String(), value) + value = cltest.MustResultString(t, jr.Result) + assert.Equal(t, attempt1Hash.String(), value) } func TestIntegration_RunAt(t *testing.T) { @@ -470,9 +467,8 @@ func TestIntegration_ExternalAdapter_RunLogInitiated(t *testing.T) { tr := jr.TaskRuns[0] assert.Equal(t, "randomnumber", tr.TaskSpec.Type.String()) - val, err := tr.ResultString() - assert.NoError(t, err) - assert.Equal(t, eaValue, val) + value := cltest.MustResultString(t, tr.Result) + assert.Equal(t, eaValue, value) res := tr.Result.Data.Get("extra") assert.Equal(t, eaExtra, res.String()) @@ -526,9 +522,8 @@ func TestIntegration_ExternalAdapter_Copy(t *testing.T) { assert.Equal(t, "assetprice", tr.TaskSpec.Type.String()) tr = jr.TaskRuns[1] assert.Equal(t, "copy", tr.TaskSpec.Type.String()) - val, err := tr.ResultString() - assert.NoError(t, err) - assert.Equal(t, eaPrice, val) + value := cltest.MustResultString(t, tr.Result) + assert.Equal(t, eaPrice, value) } // This test ensures that an bridge adapter task is resumed from pending after @@ -572,17 +567,15 @@ func TestIntegration_ExternalAdapter_Pending(t *testing.T) { tr := jr.TaskRuns[0] assert.Equal(t, models.RunStatusPendingBridge, tr.Status) - val, err := tr.ResultString() - assert.Error(t, err) - assert.Equal(t, "", val) + assert.Equal(t, gjson.Null, tr.Result.Data.Get("result").Type) jr = cltest.UpdateJobRunViaWeb(t, app, jr, bta, `{"data":{"result":"100"}}`) jr = cltest.WaitForJobRunToComplete(t, app.Store, jr) tr = jr.TaskRuns[0] assert.Equal(t, models.RunStatusCompleted, tr.Status) - val, err = tr.ResultString() - assert.NoError(t, err) - assert.Equal(t, "100", val) + + value := cltest.MustResultString(t, tr.Result) + assert.Equal(t, "100", value) } func TestIntegration_WeiWatchers(t *testing.T) { @@ -632,9 +625,8 @@ func TestIntegration_MultiplierInt256(t *testing.T) { jr := cltest.CreateJobRunViaWeb(t, app, j, `{"result":"-10221.30"}`) jr = cltest.WaitForJobRunToComplete(t, app.Store, jr) - val, err := jr.ResultString() - assert.NoError(t, err) - assert.Equal(t, "0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0674e", val) + value := cltest.MustResultString(t, jr.Result) + assert.Equal(t, "0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0674e", value) } func TestIntegration_MultiplierUint256(t *testing.T) { @@ -648,9 +640,8 @@ func TestIntegration_MultiplierUint256(t *testing.T) { jr := cltest.CreateJobRunViaWeb(t, app, j, `{"result":"10221.30"}`) jr = cltest.WaitForJobRunToComplete(t, app.Store, jr) - val, err := jr.ResultString() - assert.NoError(t, err) - assert.Equal(t, "0x00000000000000000000000000000000000000000000000000000000000f98b2", val) + value := cltest.MustResultString(t, jr.Result) + assert.Equal(t, "0x00000000000000000000000000000000000000000000000000000000000f98b2", value) } func TestIntegration_NonceManagement_firstRunWithExistingTxs(t *testing.T) { diff --git a/core/store/models/job_run.go b/core/store/models/job_run.go index 811f5e8aaaa..0dca815b52c 100644 --- a/core/store/models/job_run.go +++ b/core/store/models/job_run.go @@ -7,7 +7,6 @@ import ( "github.com/ethereum/go-ethereum/common" clnull "github.com/smartcontractkit/chainlink/core/null" "github.com/smartcontractkit/chainlink/core/store/assets" - "github.com/tidwall/gjson" null "gopkg.in/guregu/null.v3" ) @@ -142,15 +141,6 @@ func (jr *JobRun) setStatus(status RunStatus) { } } -// ResultString returns the "result" value as a string if possible -func (jr *JobRun) ResultString() (string, error) { - val := jr.Result.Data.Get("result") - if val.Type != gjson.String { - return "", fmt.Errorf("non string result") - } - return val.String(), nil -} - // ErrorString returns the error as a string if present, otherwise "". func (jr *JobRun) ErrorString() string { return jr.Result.ErrorMessage.ValueOrZero() @@ -217,15 +207,6 @@ func (tr *TaskRun) ApplyOutput(result RunOutput) { tr.Status = result.Status() } -// ResultString returns the "result" value as a string if possible -func (tr *TaskRun) ResultString() (string, error) { - val := tr.Result.Data.Get("result") - if val.Type != gjson.String { - return "", fmt.Errorf("non string result") - } - return val.String(), nil -} - // RunResult keeps track of the outcome of a TaskRun or JobRun. It stores the // Data and ErrorMessage. type RunResult struct { diff --git a/core/web/job_runs_controller_test.go b/core/web/job_runs_controller_test.go index aaedea30f68..f21a3cdd052 100644 --- a/core/web/job_runs_controller_test.go +++ b/core/web/job_runs_controller_test.go @@ -131,9 +131,8 @@ func TestJobRunsController_Create_Success(t *testing.T) { jr := cltest.CreateJobRunViaWeb(t, app, j, `{"result":"100"}`) jr = cltest.WaitForJobRunToComplete(t, app.Store, jr) - val, err := jr.ResultString() - assert.NoError(t, err) - assert.Equal(t, "100", val) + value := cltest.MustResultString(t, jr.Result) + assert.Equal(t, "100", value) } func TestJobRunsController_Create_Wrong_ExternalInitiator(t *testing.T) { @@ -198,9 +197,8 @@ func TestJobRunsController_Create_ExternalInitiator_Success(t *testing.T) { j, *eia, `{"result":"100"}`, ) jr = cltest.WaitForJobRunToComplete(t, app.Store, jr) - val, err := jr.ResultString() - assert.NoError(t, err) - assert.Equal(t, "100", val) + value := cltest.MustResultString(t, jr.Result) + assert.Equal(t, "100", value) } func TestJobRunsController_Create_Archived(t *testing.T) { @@ -327,9 +325,8 @@ func TestJobRunsController_Update_Success(t *testing.T) { require.Equal(t, jr.ID, respJobRun.ID) jr = cltest.WaitForJobRunToComplete(t, app.Store, jr) - val, err := jr.ResultString() - assert.NoError(t, err) - assert.Equal(t, "100", val) + value := cltest.MustResultString(t, jr.Result) + assert.Equal(t, "100", value) }) } } @@ -406,9 +403,8 @@ func TestJobRunsController_Update_WithError(t *testing.T) { assert.Equal(t, jr.ID, respJobRun.ID) jr = cltest.WaitForJobRunStatus(t, app.Store, jr, models.RunStatusErrored) - val, err := jr.ResultString() - assert.NoError(t, err) - assert.Equal(t, "0", val) + value := cltest.MustResultString(t, jr.Result) + assert.Equal(t, "0", value) } func TestJobRunsController_Update_BadInput(t *testing.T) { From 1781e306cdedf5cbf122f58f8010cfe4bd0b9dbe Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Sun, 3 Nov 2019 23:05:45 +0000 Subject: [PATCH 050/199] Bump normalize-url from 4.3.0 to 4.5.0 Bumps [normalize-url](https://github.com/sindresorhus/normalize-url) from 4.3.0 to 4.5.0. - [Release notes](https://github.com/sindresorhus/normalize-url/releases) - [Commits](https://github.com/sindresorhus/normalize-url/compare/v4.3.0...v4.5.0) Signed-off-by: dependabot-preview[bot] --- operator_ui/package.json | 2 +- yarn.lock | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/operator_ui/package.json b/operator_ui/package.json index b71f325628c..597b148c366 100644 --- a/operator_ui/package.json +++ b/operator_ui/package.json @@ -38,7 +38,7 @@ "local-storage-fallback": "^4.1.1", "lodash": "^4.17.13", "moment": "^2.24.0", - "normalize-url": "^4.3.0", + "normalize-url": "^4.5.0", "numeral": "^2.0.6", "path-to-regexp": "^3.0.0", "promise.prototype.finally": "^3.1.0", diff --git a/yarn.lock b/yarn.lock index 0c7b46638d5..07883226f99 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5933,7 +5933,7 @@ buffer-xor@^1.0.3: buffer@5.4.0, buffer@^5.1.0: version "5.4.0" - resolved "https://registry.npmjs.org/buffer/-/buffer-5.4.0.tgz#33294f5c1f26e08461e528b69fa06de3c45cbd8c" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.4.0.tgz#33294f5c1f26e08461e528b69fa06de3c45cbd8c" integrity sha512-Xpgy0IwHK2N01ncykXTy6FpCWuM+CJSHoPVBLyNqyrWxsedpLvwsYUhf0ME3WRFNUhos0dMamz9cOS/xRDtU5g== dependencies: base64-js "^1.0.2" @@ -15860,10 +15860,10 @@ normalize-url@^3.0.0, normalize-url@^3.3.0: resolved "https://registry.npmjs.org/normalize-url/-/normalize-url-3.3.0.tgz#b2e1c4dc4f7c6d57743df733a4f5978d18650559" integrity sha512-U+JJi7duF1o+u2pynbp2zXDW2/PADgC30f0GsHZtRh+HOcXHnw137TrNlyxxRvWW5fjKd3bcLHPxofWuCjaeZg== -normalize-url@^4.1.0, normalize-url@^4.3.0: - version "4.3.0" - resolved "https://registry.npmjs.org/normalize-url/-/normalize-url-4.3.0.tgz#9c49e10fc1876aeb76dba88bf1b2b5d9fa57b2ee" - integrity sha512-0NLtR71o4k6GLP+mr6Ty34c5GA6CMoEsncKJxvQd8NzPxaHRJNnb5gZE8R1XF4CPIS7QPHLJ74IFszwtNVAHVQ== +normalize-url@^4.1.0, normalize-url@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-4.5.0.tgz#453354087e6ca96957bd8f5baf753f5982142129" + integrity sha512-2s47yzUxdexf1OhyRi4Em83iQk0aPvwTddtFz4hnSSw9dCEsLEGf6SwIO8ss/19S9iBb5sJaOuTvTGDeZI00BQ== now-and-later@^2.0.0: version "2.0.1" @@ -20196,7 +20196,7 @@ sha.js@^2.4.0, sha.js@^2.4.8: sha3@^1.2.2, sha3@^2.0.7: version "2.0.7" - resolved "https://registry.npmjs.org/sha3/-/sha3-2.0.7.tgz#02ed270099647cbcc681b064ba091af72f561f35" + resolved "https://registry.yarnpkg.com/sha3/-/sha3-2.0.7.tgz#02ed270099647cbcc681b064ba091af72f561f35" integrity sha512-7Qsj/0J3pxCWfmyuDbTZWoKNSKY/rg2eecNRvTYE4EAPJ11Uh6xuEObyCG295AqgrtOjzdDbfwtGLDY52h11tA== dependencies: buffer "5.4.0" From 48dfe6d28cef29a740de353ba0e3d9811569c165 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Sun, 3 Nov 2019 23:05:53 +0000 Subject: [PATCH 051/199] Bump @babel/core from 7.6.0 to 7.6.4 Bumps [@babel/core](https://github.com/babel/babel) from 7.6.0 to 7.6.4. - [Release notes](https://github.com/babel/babel/releases) - [Changelog](https://github.com/babel/babel/blob/master/CHANGELOG.md) - [Commits](https://github.com/babel/babel/compare/v7.6.0...v7.6.4) Signed-off-by: dependabot-preview[bot] --- evm/v0.5/package.json | 2 +- operator_ui/package.json | 2 +- styleguide/package.json | 2 +- yarn.lock | 153 +++++++++------------------------------ 4 files changed, 36 insertions(+), 123 deletions(-) diff --git a/evm/v0.5/package.json b/evm/v0.5/package.json index 67301cb1eff..23651bc18d5 100644 --- a/evm/v0.5/package.json +++ b/evm/v0.5/package.json @@ -22,7 +22,7 @@ "truffle-contract": "^4.0.31" }, "devDependencies": { - "@babel/core": "^7.5.5", + "@babel/core": "^7.6.4", "@babel/plugin-proposal-class-properties": "^7.3.0", "@babel/polyfill": "^7.2.5", "@babel/preset-env": "^7.6.0", diff --git a/operator_ui/package.json b/operator_ui/package.json index b71f325628c..53fda3df436 100644 --- a/operator_ui/package.json +++ b/operator_ui/package.json @@ -61,7 +61,7 @@ "uuid": "^3.3.2" }, "devDependencies": { - "@babel/core": "^7.2.2", + "@babel/core": "^7.6.4", "@babel/preset-env": "^7.6.0", "@babel/preset-react": "^7.0.0", "@babel/preset-typescript": "^7.1.0", diff --git a/styleguide/package.json b/styleguide/package.json index 40f562f50d9..d1485396ab2 100644 --- a/styleguide/package.json +++ b/styleguide/package.json @@ -28,7 +28,7 @@ "typescript": "^3.6.3" }, "devDependencies": { - "@babel/core": "^7.2.2", + "@babel/core": "^7.6.4", "@babel/preset-env": "^7.6.0", "@babel/preset-react": "^7.0.0", "@babel/preset-typescript": "^7.1.0", diff --git a/yarn.lock b/yarn.lock index 0c7b46638d5..59f0a86480e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -228,7 +228,7 @@ dependencies: "@babel/highlight" "^7.0.0" -"@babel/core@7.5.5", "@babel/core@^7.4.5": +"@babel/core@7.5.5": version "7.5.5" resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.5.5.tgz#17b2686ef0d6bc58f963dddd68ab669755582c30" integrity sha512-i4qoSr2KTtce0DmkuuQBV4AuQgGPUcPXMr9L5MyYAtk06z068lQ10a4O009fe5OB/DfNV+h+qqT7ddNV8UnRjg== @@ -248,38 +248,18 @@ semver "^5.4.1" source-map "^0.5.0" -"@babel/core@^7.0.0", "@babel/core@^7.1.0", "@babel/core@^7.2.2", "@babel/core@^7.5.5": - version "7.6.0" - resolved "https://registry.npmjs.org/@babel/core/-/core-7.6.0.tgz#9b00f73554edd67bebc86df8303ef678be3d7b48" - integrity sha512-FuRhDRtsd6IptKpHXAa+4WPZYY2ZzgowkbLBecEDDSje1X/apG7jQM33or3NdOmjXBKWGOg4JmSiRfUfuTtHXw== +"@babel/core@^7.0.0", "@babel/core@^7.0.1", "@babel/core@^7.1.0", "@babel/core@^7.2.2", "@babel/core@^7.4.5", "@babel/core@^7.6.4": + version "7.6.4" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.6.4.tgz#6ebd9fe00925f6c3e177bb726a188b5f578088ff" + integrity sha512-Rm0HGw101GY8FTzpWSyRbki/jzq+/PkNQJ+nSulrdY6gFGOsNseCqD6KHRYe2E+EdzuBdr2pxCp6s4Uk6eJ+XQ== dependencies: "@babel/code-frame" "^7.5.5" - "@babel/generator" "^7.6.0" - "@babel/helpers" "^7.6.0" - "@babel/parser" "^7.6.0" - "@babel/template" "^7.6.0" - "@babel/traverse" "^7.6.0" - "@babel/types" "^7.6.0" - convert-source-map "^1.1.0" - debug "^4.1.0" - json5 "^2.1.0" - lodash "^4.17.13" - resolve "^1.3.2" - semver "^5.4.1" - source-map "^0.5.0" - -"@babel/core@^7.0.1": - version "7.6.2" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.6.2.tgz#069a776e8d5e9eefff76236bc8845566bd31dd91" - integrity sha512-l8zto/fuoZIbncm+01p8zPSDZu/VuuJhAfA7d/AbzM09WR7iVhavvfNDYCNpo1VvLk6E6xgAoP9P+/EMJHuRkQ== - dependencies: - "@babel/code-frame" "^7.5.5" - "@babel/generator" "^7.6.2" + "@babel/generator" "^7.6.4" "@babel/helpers" "^7.6.2" - "@babel/parser" "^7.6.2" + "@babel/parser" "^7.6.4" "@babel/template" "^7.6.0" - "@babel/traverse" "^7.6.2" - "@babel/types" "^7.6.0" + "@babel/traverse" "^7.6.3" + "@babel/types" "^7.6.3" convert-source-map "^1.1.0" debug "^4.1.0" json5 "^2.1.0" @@ -288,23 +268,12 @@ semver "^5.4.1" source-map "^0.5.0" -"@babel/generator@^7.0.0", "@babel/generator@^7.4.0", "@babel/generator@^7.5.5", "@babel/generator@^7.6.0": - version "7.6.0" - resolved "https://registry.npmjs.org/@babel/generator/-/generator-7.6.0.tgz#e2c21efbfd3293ad819a2359b448f002bfdfda56" - integrity sha512-Ms8Mo7YBdMMn1BYuNtKuP/z0TgEIhbcyB8HVR6PPNYp4P61lMsABiS4A3VG1qznjXVCf3r+fVHhm4efTYVsySA== - dependencies: - "@babel/types" "^7.6.0" - jsesc "^2.5.1" - lodash "^4.17.13" - source-map "^0.5.0" - trim-right "^1.0.1" - -"@babel/generator@^7.6.2": - version "7.6.2" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.6.2.tgz#dac8a3c2df118334c2a29ff3446da1636a8f8c03" - integrity sha512-j8iHaIW4gGPnViaIHI7e9t/Hl8qLjERI6DcV9kEpAIDJsAOrcnXqRS7t+QbhL76pwbtqP+QCQLL0z1CyVmtjjQ== +"@babel/generator@^7.0.0", "@babel/generator@^7.4.0", "@babel/generator@^7.5.5", "@babel/generator@^7.6.3", "@babel/generator@^7.6.4": + version "7.6.4" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.6.4.tgz#a4f8437287bf9671b07f483b76e3bb731bc97671" + integrity sha512-jsBuXkFoZxk0yWLyGI9llT9oiQ2FeTASmRFE32U+aaDTfoE92t78eroO7PTpU/OrYq38hlcDM6vbfLDaOLy+7w== dependencies: - "@babel/types" "^7.6.0" + "@babel/types" "^7.6.3" jsesc "^2.5.1" lodash "^4.17.13" source-map "^0.5.0" @@ -484,16 +453,7 @@ "@babel/traverse" "^7.1.0" "@babel/types" "^7.2.0" -"@babel/helpers@^7.5.5", "@babel/helpers@^7.6.0": - version "7.6.0" - resolved "https://registry.npmjs.org/@babel/helpers/-/helpers-7.6.0.tgz#21961d16c6a3c3ab597325c34c465c0887d31c6e" - integrity sha512-W9kao7OBleOjfXtFGgArGRX6eCP0UEcA2ZWEWNkJdRZnHhW4eEbeswbG3EwaRsnQUAEGWYgMq1HsIXuNNNy2eQ== - dependencies: - "@babel/template" "^7.6.0" - "@babel/traverse" "^7.6.0" - "@babel/types" "^7.6.0" - -"@babel/helpers@^7.6.2": +"@babel/helpers@^7.5.5", "@babel/helpers@^7.6.2": version "7.6.2" resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.6.2.tgz#681ffe489ea4dcc55f23ce469e58e59c1c045153" integrity sha512-3/bAUL8zZxYs1cdX2ilEE0WobqbCmKWr/889lf2SS0PpDcpEIY8pb1CCyz0pEcX3pEb+MCbks1jIokz2xLtGTA== @@ -511,20 +471,10 @@ esutils "^2.0.2" js-tokens "^4.0.0" -"@babel/parser@^7.0.0", "@babel/parser@^7.1.0", "@babel/parser@^7.3.1", "@babel/parser@^7.4.3", "@babel/parser@^7.5.5": - version "7.5.5" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.5.5.tgz#02f077ac8817d3df4a832ef59de67565e71cca4b" - integrity sha512-E5BN68cqR7dhKan1SfqgPGhQ178bkVKpXTPEXnFJBrEt8/DKRZlybmy+IgYLTeN7tp1R5Ccmbm2rBk17sHYU3g== - -"@babel/parser@^7.6.0": - version "7.6.0" - resolved "https://registry.npmjs.org/@babel/parser/-/parser-7.6.0.tgz#3e05d0647432a8326cb28d0de03895ae5a57f39b" - integrity sha512-+o2q111WEx4srBs7L9eJmcwi655eD8sXniLqMB93TBK9GrNzGrxDWSjiqz2hLU0Ha8MTXFIP0yd9fNdP+m43ZQ== - -"@babel/parser@^7.6.2": - version "7.6.2" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.6.2.tgz#205e9c95e16ba3b8b96090677a67c9d6075b70a1" - integrity sha512-mdFqWrSPCmikBoaBYMuBulzTIKuXVPtEISFbRRVNwMWpCms/hmE2kRq0bblUHaNRKrjRlmVbx1sDHmjmRgD2Xg== +"@babel/parser@^7.0.0", "@babel/parser@^7.1.0", "@babel/parser@^7.3.1", "@babel/parser@^7.4.3", "@babel/parser@^7.5.5", "@babel/parser@^7.6.0", "@babel/parser@^7.6.3", "@babel/parser@^7.6.4": + version "7.6.4" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.6.4.tgz#cb9b36a7482110282d5cb6dd424ec9262b473d81" + integrity sha512-D8RHPW5qd0Vbyo3qb+YjO5nvUVRTXFLQ/FsDxJU2Nqz4uB5EnUN0ZQSEYpvTIbRuttig1XbHWU5oMeQwQSAA+A== "@babel/plugin-proposal-async-generator-functions@^7.2.0": version "7.2.0" @@ -1337,55 +1287,25 @@ "@babel/parser" "^7.6.0" "@babel/types" "^7.6.0" -"@babel/traverse@^7.0.0", "@babel/traverse@^7.1.0", "@babel/traverse@^7.4.3", "@babel/traverse@^7.4.4", "@babel/traverse@^7.5.5", "@babel/traverse@^7.6.0": - version "7.6.0" - resolved "https://registry.npmjs.org/@babel/traverse/-/traverse-7.6.0.tgz#389391d510f79be7ce2ddd6717be66d3fed4b516" - integrity sha512-93t52SaOBgml/xY74lsmt7xOR4ufYvhb5c5qiM6lu4J/dWGMAfAh6eKw4PjLes6DI6nQgearoxnFJk60YchpvQ== +"@babel/traverse@^7.0.0", "@babel/traverse@^7.1.0", "@babel/traverse@^7.2.3", "@babel/traverse@^7.4.3", "@babel/traverse@^7.4.4", "@babel/traverse@^7.5.5", "@babel/traverse@^7.6.2", "@babel/traverse@^7.6.3": + version "7.6.3" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.6.3.tgz#66d7dba146b086703c0fb10dd588b7364cec47f9" + integrity sha512-unn7P4LGsijIxaAJo/wpoU11zN+2IaClkQAxcJWBNCMS6cmVh802IyLHNkAjQ0iYnRS3nnxk5O3fuXW28IMxTw== dependencies: "@babel/code-frame" "^7.5.5" - "@babel/generator" "^7.6.0" + "@babel/generator" "^7.6.3" "@babel/helper-function-name" "^7.1.0" "@babel/helper-split-export-declaration" "^7.4.4" - "@babel/parser" "^7.6.0" - "@babel/types" "^7.6.0" + "@babel/parser" "^7.6.3" + "@babel/types" "^7.6.3" debug "^4.1.0" globals "^11.1.0" lodash "^4.17.13" -"@babel/traverse@^7.2.3": - version "7.5.5" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.5.5.tgz#f664f8f368ed32988cd648da9f72d5ca70f165bb" - integrity sha512-MqB0782whsfffYfSjH4TM+LMjrJnhCNEDMDIjeTpl+ASaUvxcjoiVCo/sM1GhS1pHOXYfWVCYneLjMckuUxDaQ== - dependencies: - "@babel/code-frame" "^7.5.5" - "@babel/generator" "^7.5.5" - "@babel/helper-function-name" "^7.1.0" - "@babel/helper-split-export-declaration" "^7.4.4" - "@babel/parser" "^7.5.5" - "@babel/types" "^7.5.5" - debug "^4.1.0" - globals "^11.1.0" - lodash "^4.17.13" - -"@babel/traverse@^7.6.2": - version "7.6.2" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.6.2.tgz#b0e2bfd401d339ce0e6c05690206d1e11502ce2c" - integrity sha512-8fRE76xNwNttVEF2TwxJDGBLWthUkHWSldmfuBzVRmEDWOtu4XdINTgN7TDWzuLg4bbeIMLvfMFD9we5YcWkRQ== - dependencies: - "@babel/code-frame" "^7.5.5" - "@babel/generator" "^7.6.2" - "@babel/helper-function-name" "^7.1.0" - "@babel/helper-split-export-declaration" "^7.4.4" - "@babel/parser" "^7.6.2" - "@babel/types" "^7.6.0" - debug "^4.1.0" - globals "^11.1.0" - lodash "^4.17.13" - -"@babel/types@^7.0.0", "@babel/types@^7.2.0", "@babel/types@^7.3.0", "@babel/types@^7.4.0", "@babel/types@^7.4.4", "@babel/types@^7.5.5", "@babel/types@^7.6.0": - version "7.6.1" - resolved "https://registry.npmjs.org/@babel/types/-/types-7.6.1.tgz#53abf3308add3ac2a2884d539151c57c4b3ac648" - integrity sha512-X7gdiuaCmA0uRjCmRtYJNAVCc/q+5xSgsfKJHqMN4iNLILX39677fJE1O40arPMh0TTtS9ItH67yre6c7k6t0g== +"@babel/types@^7.0.0", "@babel/types@^7.2.0", "@babel/types@^7.3.0", "@babel/types@^7.4.0", "@babel/types@^7.4.4", "@babel/types@^7.5.5", "@babel/types@^7.6.0", "@babel/types@^7.6.3": + version "7.6.3" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.6.3.tgz#3f07d96f854f98e2fbd45c64b0cb942d11e8ba09" + integrity sha512-CqbcpTxMcpuQTMhjI37ZHVgjBkysg5icREQIEZ0eG1yCNwg3oy+5AaLiOKmjsCj6nqOsa6Hf0ObjRVwokb7srA== dependencies: esutils "^2.0.2" lodash "^4.17.13" @@ -5933,7 +5853,7 @@ buffer-xor@^1.0.3: buffer@5.4.0, buffer@^5.1.0: version "5.4.0" - resolved "https://registry.npmjs.org/buffer/-/buffer-5.4.0.tgz#33294f5c1f26e08461e528b69fa06de3c45cbd8c" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.4.0.tgz#33294f5c1f26e08461e528b69fa06de3c45cbd8c" integrity sha512-Xpgy0IwHK2N01ncykXTy6FpCWuM+CJSHoPVBLyNqyrWxsedpLvwsYUhf0ME3WRFNUhos0dMamz9cOS/xRDtU5g== dependencies: base64-js "^1.0.2" @@ -19561,20 +19481,13 @@ resolve@1.1.7, resolve@1.1.x: resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b" integrity sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs= -resolve@1.12.0, resolve@1.x, resolve@^1.0.0, resolve@^1.1.6, resolve@^1.1.7, resolve@^1.10.0, resolve@^1.10.1, resolve@^1.11.0, resolve@^1.3.2, resolve@^1.4.0, resolve@^1.8.1: +resolve@1.12.0, resolve@1.x, resolve@^1.0.0, resolve@^1.1.6, resolve@^1.1.7, resolve@^1.10.0, resolve@^1.10.1, resolve@^1.11.0, resolve@^1.3.2, resolve@^1.4.0, resolve@^1.5.0, resolve@^1.8.1: version "1.12.0" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.12.0.tgz#3fc644a35c84a48554609ff26ec52b66fa577df6" integrity sha512-B/dOmuoAik5bKcD6s6nXDCjzUKnaDvdkRyAk6rsmsKLipWj4797iothd7jmmUhWTfinVMU+wc56rYKsit2Qy4w== dependencies: path-parse "^1.0.6" -resolve@^1.5.0: - version "1.10.0" - resolved "https://registry.npmjs.org/resolve/-/resolve-1.10.0.tgz#3bdaaeaf45cc07f375656dfd2e54ed0810b101ba" - integrity sha512-3sUr9aq5OfSg2S9pNtPA9hL1FVEAjvfOC4leW0SNf/mpnaakz2a9femSd6LqAww2RaFctwyf1lCqnTHuF1rxDg== - dependencies: - path-parse "^1.0.6" - resolve@~1.11.1: version "1.11.1" resolved "https://registry.npmjs.org/resolve/-/resolve-1.11.1.tgz#ea10d8110376982fef578df8fc30b9ac30a07a3e" @@ -20196,7 +20109,7 @@ sha.js@^2.4.0, sha.js@^2.4.8: sha3@^1.2.2, sha3@^2.0.7: version "2.0.7" - resolved "https://registry.npmjs.org/sha3/-/sha3-2.0.7.tgz#02ed270099647cbcc681b064ba091af72f561f35" + resolved "https://registry.yarnpkg.com/sha3/-/sha3-2.0.7.tgz#02ed270099647cbcc681b064ba091af72f561f35" integrity sha512-7Qsj/0J3pxCWfmyuDbTZWoKNSKY/rg2eecNRvTYE4EAPJ11Uh6xuEObyCG295AqgrtOjzdDbfwtGLDY52h11tA== dependencies: buffer "5.4.0" From 6abba45a473e50dd03609d56d79b8278293d85aa Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Sun, 3 Nov 2019 23:06:40 +0000 Subject: [PATCH 052/199] Bump @storybook/addon-links from 5.2.4 to 5.2.5 Bumps [@storybook/addon-links](https://github.com/storybookjs/storybook/tree/HEAD/addons/links) from 5.2.4 to 5.2.5. - [Release notes](https://github.com/storybookjs/storybook/releases) - [Changelog](https://github.com/storybookjs/storybook/blob/next/CHANGELOG.md) - [Commits](https://github.com/storybookjs/storybook/commits/v5.2.5/addons/links) Signed-off-by: dependabot-preview[bot] --- styleguide/package.json | 2 +- yarn.lock | 110 ++++++++++++++++++++++++++++++++++++---- 2 files changed, 100 insertions(+), 12 deletions(-) diff --git a/styleguide/package.json b/styleguide/package.json index 40f562f50d9..eaf1d864ac4 100644 --- a/styleguide/package.json +++ b/styleguide/package.json @@ -36,7 +36,7 @@ "@chainlink/prettier-config": "0.0.1", "@storybook/addon-actions": "^5.1.10", "@storybook/addon-info": "^5.2.4", - "@storybook/addon-links": "^5.2.4", + "@storybook/addon-links": "^5.2.5", "@storybook/addons": "^5.2.1", "@storybook/react": "^5.1.10", "@types/react": "^16.9.5", diff --git a/yarn.lock b/yarn.lock index 0c7b46638d5..eae7419529d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2057,14 +2057,14 @@ react-lifecycles-compat "^3.0.4" util-deprecate "^1.0.2" -"@storybook/addon-links@^5.2.4": - version "5.2.4" - resolved "https://registry.yarnpkg.com/@storybook/addon-links/-/addon-links-5.2.4.tgz#5a5d631dbd66bf5800d4be7660b79568085a210d" - integrity sha512-MG+Qne4gUWGYx2qQuLQXNcl7oOBF4PbIcR0oboZNrkZ+D+6f3nHwyb53CTtzVTc+SF45CFFYLHvFdGZvv5fcAw== - dependencies: - "@storybook/addons" "5.2.4" - "@storybook/core-events" "5.2.4" - "@storybook/router" "5.2.4" +"@storybook/addon-links@^5.2.5": + version "5.2.5" + resolved "https://registry.yarnpkg.com/@storybook/addon-links/-/addon-links-5.2.5.tgz#4e700688a5826b47a82adee5f4cb4d96130499e8" + integrity sha512-QuXOcZlDSRWEIwmHJZ9uAsjtNysVUsofX5yABX+x5Nkm4BCqT1NyAuu8Xq9IlyLF1ngiOF61dy530p4lcntmHA== + dependencies: + "@storybook/addons" "5.2.5" + "@storybook/core-events" "5.2.5" + "@storybook/router" "5.2.5" common-tags "^1.8.0" core-js "^3.0.1" global "^4.3.2" @@ -2083,7 +2083,7 @@ global "^4.3.2" util-deprecate "^1.0.2" -"@storybook/addons@5.2.4", "@storybook/addons@^5.2.1": +"@storybook/addons@5.2.4": version "5.2.4" resolved "https://registry.yarnpkg.com/@storybook/addons/-/addons-5.2.4.tgz#5c4f031e403c90a517cd6d208ec51d7e2455683a" integrity sha512-Q+bnVlBA308qnELxnh18hBDRSUgltR9KbV537285dUL/okv/NC6n51mxJwIaG+ksBW2wU+5e6tqSayaKF3uHLw== @@ -2096,6 +2096,19 @@ global "^4.3.2" util-deprecate "^1.0.2" +"@storybook/addons@5.2.5", "@storybook/addons@^5.2.1": + version "5.2.5" + resolved "https://registry.yarnpkg.com/@storybook/addons/-/addons-5.2.5.tgz#e3e23d5ea6eb221df31e1a5d125be47454e9a0e8" + integrity sha512-CvMj7Bs3go9tv5rZuAvFwuwe8p/16LDCHS7+5nVFosvcL8nuN339V3rzakw8nLy/S6XKeZ1ACu4t3vYkreRE3w== + dependencies: + "@storybook/api" "5.2.5" + "@storybook/channels" "5.2.5" + "@storybook/client-logger" "5.2.5" + "@storybook/core-events" "5.2.5" + core-js "^3.0.1" + global "^4.3.2" + util-deprecate "^1.0.2" + "@storybook/api@5.1.11": version "5.1.11" resolved "https://registry.yarnpkg.com/@storybook/api/-/api-5.1.11.tgz#71ef00285cd8602aad24cdb26c60c5d3c76631e5" @@ -2142,6 +2155,29 @@ telejson "^3.0.2" util-deprecate "^1.0.2" +"@storybook/api@5.2.5": + version "5.2.5" + resolved "https://registry.yarnpkg.com/@storybook/api/-/api-5.2.5.tgz#dcc68c873820485372a47c095a8fc5e4fb53a34c" + integrity sha512-JvLafqFVgA3dIWpLMoGNk4sRuogE5imhD6/g0d8DOwnCID9xowj5xIptSrCTKvGGGxuN3wWRGn6I2lEbY6969g== + dependencies: + "@storybook/channels" "5.2.5" + "@storybook/client-logger" "5.2.5" + "@storybook/core-events" "5.2.5" + "@storybook/router" "5.2.5" + "@storybook/theming" "5.2.5" + core-js "^3.0.1" + fast-deep-equal "^2.0.1" + global "^4.3.2" + lodash "^4.17.15" + memoizerific "^1.11.3" + prop-types "^15.6.2" + react "^16.8.3" + semver "^6.0.0" + shallow-equal "^1.1.0" + store2 "^2.7.1" + telejson "^3.0.2" + util-deprecate "^1.0.2" + "@storybook/channel-postmessage@5.1.11": version "5.1.11" resolved "https://registry.npmjs.org/@storybook/channel-postmessage/-/channel-postmessage-5.1.11.tgz#e75ab7d59ba19476eb631cdb69ee713c3b956c2b" @@ -2167,6 +2203,13 @@ dependencies: core-js "^3.0.1" +"@storybook/channels@5.2.5": + version "5.2.5" + resolved "https://registry.yarnpkg.com/@storybook/channels/-/channels-5.2.5.tgz#d6ca2b490281dacb272096563fe760ccb353c4bb" + integrity sha512-I+zB3ym5ozBcNBqyzZbvB6gRIG/ZKKkqy5k6LwKd5NMx7NU7zU74+LQUBBOcSIrigj8kCArZz7rlgb0tlSKXxQ== + dependencies: + core-js "^3.0.1" + "@storybook/client-api@5.1.11": version "5.1.11" resolved "https://registry.npmjs.org/@storybook/client-api/-/client-api-5.1.11.tgz#30d82c09c6c40aa70d932e77b1d1e65526bddc0c" @@ -2199,6 +2242,13 @@ dependencies: core-js "^3.0.1" +"@storybook/client-logger@5.2.5": + version "5.2.5" + resolved "https://registry.yarnpkg.com/@storybook/client-logger/-/client-logger-5.2.5.tgz#6f386ac6f81b4a783c57d54bb328281abbea1bab" + integrity sha512-6DyYUrMgAvF+th0foH7UNz+2JJpRdvNbpvYKtvi/+hlvRIaI6AqANgLkPUgMibaif5TLzjCr0bLdAYcjeJz03w== + dependencies: + core-js "^3.0.1" + "@storybook/components@5.1.11": version "5.1.11" resolved "https://registry.npmjs.org/@storybook/components/-/components-5.1.11.tgz#da253af0a8cb1b063c5c2e8016c4540c983f717d" @@ -2261,6 +2311,13 @@ dependencies: core-js "^3.0.1" +"@storybook/core-events@5.2.5": + version "5.2.5" + resolved "https://registry.yarnpkg.com/@storybook/core-events/-/core-events-5.2.5.tgz#62881164a4a01aa99ff0691e70eaed2dd58e229e" + integrity sha512-O5GM8XEBbYNbM6Z7a4H1bbnbO2cxQrXMhEwansC7a7YinQdkTPiuGxke3NiyK+7pLDh778kpQyjoCjXq6UfAoQ== + dependencies: + core-js "^3.0.1" + "@storybook/core@5.1.11": version "5.1.11" resolved "https://registry.npmjs.org/@storybook/core/-/core-5.1.11.tgz#d7c4b14b02f74c183ab5baffe9b3e5ec8289b320" @@ -2395,6 +2452,19 @@ memoizerific "^1.11.3" qs "^6.6.0" +"@storybook/router@5.2.5": + version "5.2.5" + resolved "https://registry.yarnpkg.com/@storybook/router/-/router-5.2.5.tgz#a005332bc6aa1e7849503187ad50c41b3f3bef92" + integrity sha512-e6ElDAWSoEW1KSnsTbVwbpzaZ8CNWYw0Ok3b5AHfY2fuSH5L4l6s6k/bP7QSYqvWUeTvkFQYux7A2rOFCriAgA== + dependencies: + "@reach/router" "^1.2.1" + "@types/reach__router" "^1.2.3" + core-js "^3.0.1" + global "^4.3.2" + lodash "^4.17.15" + memoizerific "^1.11.3" + qs "^6.6.0" + "@storybook/theming@5.1.11": version "5.1.11" resolved "https://registry.yarnpkg.com/@storybook/theming/-/theming-5.1.11.tgz#0d1af46535f2e601293c999a314905069a93ec3b" @@ -2431,6 +2501,24 @@ prop-types "^15.7.2" resolve-from "^5.0.0" +"@storybook/theming@5.2.5": + version "5.2.5" + resolved "https://registry.yarnpkg.com/@storybook/theming/-/theming-5.2.5.tgz#9579e7944f61ded637d1d79be5fb859a617620f5" + integrity sha512-PGZNYrRgAhXFJKnktFpyyKlaDXEhtTi5XPq5ASVJrsPW6l963Mk2EMKSm4TCTxIJhs0Kx4cv2MnNZFDqHf47eg== + dependencies: + "@emotion/core" "^10.0.14" + "@emotion/styled" "^10.0.14" + "@storybook/client-logger" "5.2.5" + common-tags "^1.8.0" + core-js "^3.0.1" + deep-object-diff "^1.1.0" + emotion-theming "^10.0.14" + global "^4.3.2" + memoizerific "^1.11.3" + polished "^3.3.1" + prop-types "^15.7.2" + resolve-from "^5.0.0" + "@storybook/ui@5.1.11": version "5.1.11" resolved "https://registry.npmjs.org/@storybook/ui/-/ui-5.1.11.tgz#02246f7656f644a36908430de12abbdf4e2a8a72" @@ -5933,7 +6021,7 @@ buffer-xor@^1.0.3: buffer@5.4.0, buffer@^5.1.0: version "5.4.0" - resolved "https://registry.npmjs.org/buffer/-/buffer-5.4.0.tgz#33294f5c1f26e08461e528b69fa06de3c45cbd8c" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.4.0.tgz#33294f5c1f26e08461e528b69fa06de3c45cbd8c" integrity sha512-Xpgy0IwHK2N01ncykXTy6FpCWuM+CJSHoPVBLyNqyrWxsedpLvwsYUhf0ME3WRFNUhos0dMamz9cOS/xRDtU5g== dependencies: base64-js "^1.0.2" @@ -20196,7 +20284,7 @@ sha.js@^2.4.0, sha.js@^2.4.8: sha3@^1.2.2, sha3@^2.0.7: version "2.0.7" - resolved "https://registry.npmjs.org/sha3/-/sha3-2.0.7.tgz#02ed270099647cbcc681b064ba091af72f561f35" + resolved "https://registry.yarnpkg.com/sha3/-/sha3-2.0.7.tgz#02ed270099647cbcc681b064ba091af72f561f35" integrity sha512-7Qsj/0J3pxCWfmyuDbTZWoKNSKY/rg2eecNRvTYE4EAPJ11Uh6xuEObyCG295AqgrtOjzdDbfwtGLDY52h11tA== dependencies: buffer "5.4.0" From c51e35c79d0b316e5e4dec1d11e3be92dd12541a Mon Sep 17 00:00:00 2001 From: Alex Kwiatkowski Date: Wed, 30 Oct 2019 16:10:27 -0700 Subject: [PATCH 053/199] Use cross-platform rm -rf --- explorer/client/package.json | 3 ++- styleguide/package.json | 2 +- tools/json-api-client/package.json | 3 ++- yarn.lock | 7 +++++++ 4 files changed, 12 insertions(+), 3 deletions(-) diff --git a/explorer/client/package.json b/explorer/client/package.json index a9880cfc372..04153af7cda 100644 --- a/explorer/client/package.json +++ b/explorer/client/package.json @@ -21,7 +21,7 @@ }, "scripts": { "start": "cross-env PORT=3001 craco start", - "build:clean": "rm -rf build", + "build:clean": "rimraf -rf build", "prebuild": "yarn install", "build": "craco build", "lint": "eslint --ext .js,.ts,.tsx,.jsx src", @@ -85,6 +85,7 @@ "react-scripts": "^3.1.0", "redux-devtools-extension": "^2.13.8", "redux-logger": "^3.0.6", + "rimraf": "^3.0.0", "typescript": "^3.6.3", "webpack": "4.41.1" }, diff --git a/styleguide/package.json b/styleguide/package.json index 964c90ba2f6..5c858093b88 100644 --- a/styleguide/package.json +++ b/styleguide/package.json @@ -7,7 +7,7 @@ "scripts": { "start": "start-storybook -p 9001", "build-storybook": "build-storybook", - "build": "rm -rf dist && tsc", + "build": "rimraf -rf dist && tsc", "eslint": "eslint --ext .js,.jsx,.ts,.tsx src stories @types", "lint": "tsc --noEmit && yarn eslint", "format": "prettier --write \"**/*\"", diff --git a/tools/json-api-client/package.json b/tools/json-api-client/package.json index dfc5baa8687..2ab4d4018b1 100644 --- a/tools/json-api-client/package.json +++ b/tools/json-api-client/package.json @@ -5,7 +5,7 @@ "main": "./dist/src", "types": "./dist/src", "scripts": { - "build": "rm -rf dist && tsc", + "build": "rimraf -rf dist && tsc", "lint": "eslint --ext .ts .", "format": "prettier --write \"*.ts\"", "setup": "yarn build", @@ -26,6 +26,7 @@ "@types/path-to-regexp": "^1.7.0", "fetch-mock": "^7.3.1", "jest": "^24.9.0", + "rimraf": "^3.0.0", "ts-jest": "^24.0.0" } } diff --git a/yarn.lock b/yarn.lock index 34716e97dd9..17086b839f4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -19667,6 +19667,13 @@ rimraf@2.6.3: dependencies: glob "^7.1.3" +rimraf@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/rimraf/-/rimraf-3.0.0.tgz#614176d4b3010b75e5c390eb0ee96f6dc0cebb9b" + integrity sha512-NDGVxTsjqfunkds7CqsOiEnxln4Bo7Nddl3XhS4pXg5OzwkLqJ971ZVAAnB+DDLnF76N+VnDEiBHaVV8I06SUg== + dependencies: + glob "^7.1.3" + ripemd160@^2.0.0, ripemd160@^2.0.1: version "2.0.2" resolved "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz#a1c1a6f624751577ba5d07914cbc92850585890c" From a7a47514458166353aefbb80ecd31e411294c81a Mon Sep 17 00:00:00 2001 From: Alex Kwiatkowski Date: Thu, 31 Oct 2019 15:25:02 -0700 Subject: [PATCH 054/199] Install packages for forks test --- .circleci/config.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.circleci/config.yml b/.circleci/config.yml index a5e00543c14..f57c728d0f5 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -288,6 +288,9 @@ jobs: - run: name: Install Yarn command: npm install -g yarn + - run: + name: Install New Packages + command: yarn install - run: ./tools/ci/forks_test - store_artifacts: path: ./integration/logs From e3f07a7e35c91768572e8cac3ecb21e01700aaf4 Mon Sep 17 00:00:00 2001 From: Alex Kwiatkowski Date: Mon, 4 Nov 2019 07:57:05 -0800 Subject: [PATCH 055/199] Add rimraf dependency to styleguide package.json --- styleguide/package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/styleguide/package.json b/styleguide/package.json index 5c858093b88..05117408553 100644 --- a/styleguide/package.json +++ b/styleguide/package.json @@ -48,6 +48,7 @@ "eslint": "^6.3.0", "prettier": "^1.18.2", "react-docgen-typescript-loader": "^3.1.0", + "rimraf": "^3.0.0", "webpack": "^4.41.1" }, "prettier": "@chainlink/prettier-config" From 962993352aa2744f42b91ae0177a500043cac419 Mon Sep 17 00:00:00 2001 From: Ryan Hall Date: Mon, 4 Nov 2019 11:15:03 -0500 Subject: [PATCH 056/199] Add InitiatorServiceAgreementExecutionLog case to initiatorParams() --- core/store/presenters/presenters.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/core/store/presenters/presenters.go b/core/store/presenters/presenters.go index c7afdda4b4c..29fb24dbf11 100644 --- a/core/store/presenters/presenters.go +++ b/core/store/presenters/presenters.go @@ -357,6 +357,8 @@ func initiatorParams(i Initiator) (interface{}, error) { switch i.Type { case models.InitiatorWeb: return struct{}{}, nil + case models.InitiatorServiceAgreementExecutionLog: + return struct{}{}, nil case models.InitiatorCron: return struct { Schedule models.Cron `json:"schedule"` From 4fdeb0980046e99fe6d39b5b1743d483c9d5b045 Mon Sep 17 00:00:00 2001 From: Ryan Hall Date: Tue, 29 Oct 2019 18:29:22 -0400 Subject: [PATCH 057/199] Add RPC server --- explorer/package.json | 1 + .../fixtures/JobRun.ethtx.fixture.json | 78 +++-- .../__tests__/fixtures/JobRun.fixture.json | 51 +-- .../fixtures/JobRunLegacy.ethtx.fixture.json | 37 +++ .../fixtures/JobRunLegacy.fixture.json | 25 ++ .../fixtures/JobRunUpdate.fixture.json | 51 +-- .../fixtures/JobRunUpdateLegacy.fixture.json | 25 ++ explorer/src/__tests__/realtime.test.ts | 294 +++++++++++++----- explorer/src/entity/JobRun.ts | 4 + explorer/src/handleMessage.ts | 49 +++ explorer/src/realtime.ts | 29 +- explorer/src/rpcServer.ts | 32 ++ 12 files changed, 494 insertions(+), 182 deletions(-) create mode 100644 explorer/src/__tests__/fixtures/JobRunLegacy.ethtx.fixture.json create mode 100644 explorer/src/__tests__/fixtures/JobRunLegacy.fixture.json create mode 100644 explorer/src/__tests__/fixtures/JobRunUpdateLegacy.fixture.json create mode 100644 explorer/src/handleMessage.ts create mode 100644 explorer/src/rpcServer.ts diff --git a/explorer/package.json b/explorer/package.json index cb432a7bf75..79d7d2dfa18 100644 --- a/explorer/package.json +++ b/explorer/package.json @@ -42,6 +42,7 @@ "express": "^4.16.4", "express-winston": "^3.4.0", "helmet": "^3.20.0", + "jayson": "^3.1.1", "js-sha256": "^0.9.0", "jsonapi-serializer": "^3.6.4", "local-storage-fallback": "^4.1.1", diff --git a/explorer/src/__tests__/fixtures/JobRun.ethtx.fixture.json b/explorer/src/__tests__/fixtures/JobRun.ethtx.fixture.json index d2c8117371f..a6722d019ac 100644 --- a/explorer/src/__tests__/fixtures/JobRun.ethtx.fixture.json +++ b/explorer/src/__tests__/fixtures/JobRun.ethtx.fixture.json @@ -1,37 +1,47 @@ { - "id": "fe64b0a02e944a6ab411e121a0658117", - "jobId": "0d7f0402ce8d44ef80eea4305af7755d", - "runId": "fe64b0a02e944a6ab411e121a0658117", - "status": "completed", - "error": null, - "createdAt": "2019-05-03T15:21:35Z", - "payment": null, - "finishedAt": "2019-05-03T11:21:36.01197-04:00", - "initiator": { "type": "web" }, - "tasks": [ - { - "index": 0, - "type": "httpget", - "status": "completed", - "confirmations": "3", - "minimumConfirmations": "3", - "error": null - }, - { "index": 1, "type": "jsonparse", "status": "completed", "error": null }, - { "index": 2, "type": "ethbytes32", "status": "completed", "error": null }, - { - "index": 3, - "type": "ethtx", - "status": "completed", - "error": null, - "result": { - "transactionHash": "0x1111111111111111111111111111111111111111111111111111111111111111", - "transactionStatus": "fulfilledRunLog", - "timestamp": "2018-01-08T18:12:01.103Z", - "blockHeight": "0xdeadbeef", - "blockHash": "0xbadc0de5", - "blockNumber": "0x6" + "jsonrpc": "2.0", + "method": "upsertJobRun", + "id": 1, + "params": { + "id": "fe64b0a02e944a6ab411e121a0658117", + "jobId": "0d7f0402ce8d44ef80eea4305af7755d", + "runId": "fe64b0a02e944a6ab411e121a0658117", + "status": "completed", + "error": null, + "createdAt": "2019-05-03T15:21:35Z", + "payment": null, + "finishedAt": "2019-05-03T11:21:36.01197-04:00", + "initiator": { "type": "web" }, + "tasks": [ + { + "index": 0, + "type": "httpget", + "status": "completed", + "confirmations": "3", + "minimumConfirmations": "3", + "error": null + }, + { "index": 1, "type": "jsonparse", "status": "completed", "error": null }, + { + "index": 2, + "type": "ethbytes32", + "status": "completed", + "error": null + }, + { + "index": 3, + "type": "ethtx", + "status": "completed", + "error": null, + "result": { + "transactionHash": "0x1111111111111111111111111111111111111111111111111111111111111111", + "transactionStatus": "fulfilledRunLog", + "timestamp": "2018-01-08T18:12:01.103Z", + "blockHeight": "0xdeadbeef", + "blockHash": "0xbadc0de5", + "blockNumber": "0x6" + } } - } - ] + ] + } } diff --git a/explorer/src/__tests__/fixtures/JobRun.fixture.json b/explorer/src/__tests__/fixtures/JobRun.fixture.json index 89beed84ce9..0c9870133c6 100644 --- a/explorer/src/__tests__/fixtures/JobRun.fixture.json +++ b/explorer/src/__tests__/fixtures/JobRun.fixture.json @@ -1,25 +1,30 @@ { - "jobId": "aeb2861d306645b1ba012079aeb2e53a", - "runId": "f1xtureAaaaaaaaaaaaaaaaaaaaaaaaa", - "status": "in_progress", - "error": null, - "createdAt": "2019-04-01T22:07:04Z", - "payment": null, - "finishedAt": null, - "initiator": { - "type": "runlog", - "requestId": "RequestID", - "txHash": "0x00000000000000000000000000000000000000000000000000000000deadbeef", - "requester": "0x9FBDa871d559710256a2502A2517b794B482Db40" - }, - "tasks": [ - { - "index": 0, - "type": "httpget", - "status": "", - "confirmations": "0", - "minimumConfirmations": "3", - "error": null - } - ] + "jsonrpc": "2.0", + "method": "upsertJobRun", + "id": 1, + "params": { + "jobId": "aeb2861d306645b1ba012079aeb2e53a", + "runId": "f1xtureAaaaaaaaaaaaaaaaaaaaaaaaa", + "status": "in_progress", + "error": null, + "createdAt": "2019-04-01T22:07:04Z", + "payment": null, + "finishedAt": null, + "initiator": { + "type": "runlog", + "requestId": "RequestID", + "txHash": "0x00000000000000000000000000000000000000000000000000000000deadbeef", + "requester": "0x9FBDa871d559710256a2502A2517b794B482Db40" + }, + "tasks": [ + { + "index": 0, + "type": "httpget", + "status": "", + "confirmations": "0", + "minimumConfirmations": "3", + "error": null + } + ] + } } diff --git a/explorer/src/__tests__/fixtures/JobRunLegacy.ethtx.fixture.json b/explorer/src/__tests__/fixtures/JobRunLegacy.ethtx.fixture.json new file mode 100644 index 00000000000..d2c8117371f --- /dev/null +++ b/explorer/src/__tests__/fixtures/JobRunLegacy.ethtx.fixture.json @@ -0,0 +1,37 @@ +{ + "id": "fe64b0a02e944a6ab411e121a0658117", + "jobId": "0d7f0402ce8d44ef80eea4305af7755d", + "runId": "fe64b0a02e944a6ab411e121a0658117", + "status": "completed", + "error": null, + "createdAt": "2019-05-03T15:21:35Z", + "payment": null, + "finishedAt": "2019-05-03T11:21:36.01197-04:00", + "initiator": { "type": "web" }, + "tasks": [ + { + "index": 0, + "type": "httpget", + "status": "completed", + "confirmations": "3", + "minimumConfirmations": "3", + "error": null + }, + { "index": 1, "type": "jsonparse", "status": "completed", "error": null }, + { "index": 2, "type": "ethbytes32", "status": "completed", "error": null }, + { + "index": 3, + "type": "ethtx", + "status": "completed", + "error": null, + "result": { + "transactionHash": "0x1111111111111111111111111111111111111111111111111111111111111111", + "transactionStatus": "fulfilledRunLog", + "timestamp": "2018-01-08T18:12:01.103Z", + "blockHeight": "0xdeadbeef", + "blockHash": "0xbadc0de5", + "blockNumber": "0x6" + } + } + ] +} diff --git a/explorer/src/__tests__/fixtures/JobRunLegacy.fixture.json b/explorer/src/__tests__/fixtures/JobRunLegacy.fixture.json new file mode 100644 index 00000000000..89beed84ce9 --- /dev/null +++ b/explorer/src/__tests__/fixtures/JobRunLegacy.fixture.json @@ -0,0 +1,25 @@ +{ + "jobId": "aeb2861d306645b1ba012079aeb2e53a", + "runId": "f1xtureAaaaaaaaaaaaaaaaaaaaaaaaa", + "status": "in_progress", + "error": null, + "createdAt": "2019-04-01T22:07:04Z", + "payment": null, + "finishedAt": null, + "initiator": { + "type": "runlog", + "requestId": "RequestID", + "txHash": "0x00000000000000000000000000000000000000000000000000000000deadbeef", + "requester": "0x9FBDa871d559710256a2502A2517b794B482Db40" + }, + "tasks": [ + { + "index": 0, + "type": "httpget", + "status": "", + "confirmations": "0", + "minimumConfirmations": "3", + "error": null + } + ] +} diff --git a/explorer/src/__tests__/fixtures/JobRunUpdate.fixture.json b/explorer/src/__tests__/fixtures/JobRunUpdate.fixture.json index 9550bb971b4..1924750d70f 100644 --- a/explorer/src/__tests__/fixtures/JobRunUpdate.fixture.json +++ b/explorer/src/__tests__/fixtures/JobRunUpdate.fixture.json @@ -1,25 +1,30 @@ { - "jobId": "aeb2861d306645b1ba012079aeb2e53a", - "runId": "f1xtureAaaaaaaaaaaaaaaaaaaaaaaaa", - "status": "completed", - "error": null, - "createdAt": "2019-04-01T22:07:04Z", - "payment": null, - "finishedAt": "2018-04-01T22:07:04Z", - "initiator": { - "type": "runlog", - "requestId": "RequestID", - "txHash": "0x00000000000000000000000000000000000000000000000000000000deadbeef", - "requester": "0x9FBDa871d559710256a2502A2517b794B482Db40" - }, - "tasks": [ - { - "index": 0, - "type": "httpget", - "status": "completed", - "confirmations": "3", - "minimumConfirmations": "3", - "error": null - } - ] + "jsonrpc": "2.0", + "method": "upsertJobRun", + "id": 2, + "params": { + "jobId": "aeb2861d306645b1ba012079aeb2e53a", + "runId": "f1xtureAaaaaaaaaaaaaaaaaaaaaaaaa", + "status": "completed", + "error": null, + "createdAt": "2019-04-01T22:07:04Z", + "payment": null, + "finishedAt": "2018-04-01T22:07:04Z", + "initiator": { + "type": "runlog", + "requestId": "RequestID", + "txHash": "0x00000000000000000000000000000000000000000000000000000000deadbeef", + "requester": "0x9FBDa871d559710256a2502A2517b794B482Db40" + }, + "tasks": [ + { + "index": 0, + "type": "httpget", + "status": "completed", + "confirmations": "3", + "minimumConfirmations": "3", + "error": null + } + ] + } } diff --git a/explorer/src/__tests__/fixtures/JobRunUpdateLegacy.fixture.json b/explorer/src/__tests__/fixtures/JobRunUpdateLegacy.fixture.json new file mode 100644 index 00000000000..9550bb971b4 --- /dev/null +++ b/explorer/src/__tests__/fixtures/JobRunUpdateLegacy.fixture.json @@ -0,0 +1,25 @@ +{ + "jobId": "aeb2861d306645b1ba012079aeb2e53a", + "runId": "f1xtureAaaaaaaaaaaaaaaaaaaaaaaaa", + "status": "completed", + "error": null, + "createdAt": "2019-04-01T22:07:04Z", + "payment": null, + "finishedAt": "2018-04-01T22:07:04Z", + "initiator": { + "type": "runlog", + "requestId": "RequestID", + "txHash": "0x00000000000000000000000000000000000000000000000000000000deadbeef", + "requester": "0x9FBDa871d559710256a2502A2517b794B482Db40" + }, + "tasks": [ + { + "index": 0, + "type": "httpget", + "status": "completed", + "confirmations": "3", + "minimumConfirmations": "3", + "error": null + } + ] +} diff --git a/explorer/src/__tests__/realtime.test.ts b/explorer/src/__tests__/realtime.test.ts index 34fde3062dd..ebe3ecacb82 100644 --- a/explorer/src/__tests__/realtime.test.ts +++ b/explorer/src/__tests__/realtime.test.ts @@ -6,6 +6,10 @@ import { ChainlinkNode, createChainlinkNode } from '../entity/ChainlinkNode' import { JobRun } from '../entity/JobRun' import { TaskRun } from '../entity/TaskRun' import { DEFAULT_TEST_PORT, start, stop } from '../support/server' +import { DEFAULT_TEST_PORT, start as startServer } from '../support/server' +import ethtxFixtureLegacy from './fixtures/JobRunLegacy.ethtx.fixture.json' +import createFixtureLegacy from './fixtures/JobRunLegacy.fixture.json' +import updateFixtureLegacy from './fixtures/JobRunUpdateLegacy.fixture.json' import ethtxFixture from './fixtures/JobRun.ethtx.fixture.json' import createFixture from './fixtures/JobRun.fixture.json' import updateFixture from './fixtures/JobRunUpdate.fixture.json' @@ -46,6 +50,9 @@ describe('realtime', () => { let chainlinkNode: ChainlinkNode let secret: string + const authenticatedNode = async () => + newChainlinkNode(ENDPOINT, chainlinkNode.accessKey, secret) + beforeAll(async () => { server = await start() db = await getDb() @@ -61,123 +68,254 @@ describe('realtime', () => { afterAll(done => stop(server, done)) - it('create a job run for valid JSON', async () => { - expect.assertions(3) + describe('legacy message format', () => { + it('can create a job run with valid JSON', async () => { + expect.assertions(3) - const ws = await newChainlinkNode(ENDPOINT, chainlinkNode.accessKey, secret) + const ws = await authenticatedNode() - ws.send(JSON.stringify(createFixture)) + ws.send(JSON.stringify(createFixtureLegacy)) - await new Promise(resolve => { - ws.on('message', (data: WebSocket.Data) => { - const result = JSON.parse(data as string) - expect(result.status).toEqual(201) - ws.close() - resolve() + await new Promise(resolve => { + ws.on('message', (data: WebSocket.Data) => { + const response = JSON.parse(data as string) + expect(response.status).toEqual(201) + ws.close() + resolve() + }) }) + + const jobRunCount = await db.manager.count(JobRun) + expect(jobRunCount).toEqual(1) + + const taskRunCount = await db.manager.count(TaskRun) + expect(taskRunCount).toEqual(1) }) - const jobRunCount = await db.manager.count(JobRun) - expect(jobRunCount).toEqual(1) + it('can create and update a job run and task runs', async () => { + expect.assertions(6) - const taskRunCount = await db.manager.count(TaskRun) - expect(taskRunCount).toEqual(1) - }) + const ws = await authenticatedNode() - it('can create and update a job run and task runs', async () => { - expect.assertions(6) + ws.send(JSON.stringify(createFixtureLegacy)) - const ws = await newChainlinkNode(ENDPOINT, chainlinkNode.accessKey, secret) + await new Promise(resolve => { + let responses = 0 + ws.on('message', (data: any) => { + responses += 1 + const response = JSON.parse(data) - ws.send(JSON.stringify(createFixture)) + if (responses === 1) { + expect(response.status).toEqual(201) + ws.send(JSON.stringify(updateFixtureLegacy)) + } - await new Promise(resolve => { - let responses = 0 - ws.on('message', (data: any) => { - responses += 1 - const result = JSON.parse(data) + if (responses === 2) { + expect(response.status).toEqual(201) + ws.close() + resolve() + } + }) + }) - if (responses === 1) { - expect(result.status).toEqual(201) - ws.send(JSON.stringify(updateFixture)) - } + const jobRunCount = await db.manager.count(JobRun) + expect(jobRunCount).toEqual(1) - if (responses === 2) { - expect(result.status).toEqual(201) - ws.close() + const taskRunCount = await db.manager.count(TaskRun) + expect(taskRunCount).toEqual(1) + + const jr = await db.manager.findOne(JobRun) + expect(jr.status).toEqual('completed') + + const tr = jr.taskRuns[0] + expect(tr.status).toEqual('completed') + }) + + it('can create a task run with transactionHash and status', async () => { + expect.assertions(10) + + const ws = await authenticatedNode() + + const messageReceived = new Promise(resolve => { + ws.on('message', (data: any) => { + const response = JSON.parse(data) + expect(response.status).toEqual(201) resolve() - } + }) }) + + ws.send(JSON.stringify(ethtxFixtureLegacy)) + await messageReceived + + const jobRunCount = await db.manager.count(JobRun) + expect(jobRunCount).toEqual(1) + + const taskRunCount = await db.manager.count(TaskRun) + expect(taskRunCount).toEqual(4) + + const jobRunRepository = getCustomRepository(JobRunRepository, db.name) + const jr = await jobRunRepository.getFirst() + + expect(jr.status).toEqual('completed') + + const tr = jr.taskRuns[3] + expect(tr.status).toEqual('completed') + expect(tr.transactionHash).toEqual( + '0x1111111111111111111111111111111111111111111111111111111111111111', + ) + expect(tr.timestamp).toEqual(new Date('2018-01-08T18:12:01.103Z')) + expect(tr.blockHeight).toEqual('3735928559') + expect(tr.blockHash).toEqual('0xbadc0de5') + expect(tr.transactionStatus).toEqual('fulfilledRunLog') + ws.close() }) - const jobRunCount = await db.manager.count(JobRun) - expect(jobRunCount).toEqual(1) + it('rejects malformed json events with code 422', async (done: any) => { + expect.assertions(2) - const taskRunCount = await db.manager.count(TaskRun) - expect(taskRunCount).toEqual(1) + const ws = await authenticatedNode() - const jr = await db.manager.findOne(JobRun) - expect(jr.status).toEqual('completed') + ws.send('{invalid json}') - const tr = jr.taskRuns[0] - expect(tr.status).toEqual('completed') + ws.on('message', async (data: any) => { + const response = JSON.parse(data) + expect(response.status).toEqual(422) + + const count = await db.manager.count(JobRun) + expect(count).toEqual(0) + + ws.close() + done() + }) + }) }) - it('can create a task run with transactionHash and status', async () => { - expect.assertions(10) + describe('jsonRPC message format', () => { + it('can create a job run with valid JSON', async () => { + expect.assertions(3) + + const ws = await authenticatedNode() - const ws = await newChainlinkNode(ENDPOINT, chainlinkNode.accessKey, secret) + ws.send(JSON.stringify(createFixture)) - const messageReceived = new Promise(resolve => { - ws.on('message', (data: any) => { - const result = JSON.parse(data) - expect(result.status).toEqual(201) - resolve() + await new Promise(resolve => { + ws.on('message', (data: WebSocket.Data) => { + const response = JSON.parse(data as string) + expect(response.result).toEqual('success') + ws.close() + resolve() + }) }) + + const jobRunCount = await db.manager.count(JobRun) + expect(jobRunCount).toEqual(1) + + const taskRunCount = await db.manager.count(TaskRun) + expect(taskRunCount).toEqual(1) }) - ws.send(JSON.stringify(ethtxFixture)) - await messageReceived + it('can create and update a job run and task runs', async () => { + expect.assertions(6) - const jobRunCount = await db.manager.count(JobRun) - expect(jobRunCount).toEqual(1) + const ws = await authenticatedNode() - const taskRunCount = await db.manager.count(TaskRun) - expect(taskRunCount).toEqual(4) + ws.send(JSON.stringify(createFixture)) - const jobRunRepository = getCustomRepository(JobRunRepository, db.name) - const jr = await jobRunRepository.getFirst() + await new Promise(resolve => { + ws.on('message', (data: any) => { + const response = JSON.parse(data) - expect(jr.status).toEqual('completed') + if (response.id === createFixture.id) { + expect(response.result).toEqual('success') + ws.send(JSON.stringify(updateFixture)) + } - const tr = jr.taskRuns[3] - expect(tr.status).toEqual('completed') - expect(tr.transactionHash).toEqual( - '0x1111111111111111111111111111111111111111111111111111111111111111', - ) - expect(tr.timestamp).toEqual(new Date('2018-01-08T18:12:01.103Z')) - expect(tr.blockHeight).toEqual('3735928559') - expect(tr.blockHash).toEqual('0xbadc0de5') - expect(tr.transactionStatus).toEqual('fulfilledRunLog') - ws.close() - }) + if (response.id === updateFixture.id) { + expect(response.result).toEqual('success') + ws.close() + resolve() + } + }) + }) + + const jobRunCount = await db.manager.count(JobRun) + expect(jobRunCount).toEqual(1) - it('rejects malformed json events with code 422', async (done: any) => { - expect.assertions(2) + const taskRunCount = await db.manager.count(TaskRun) + expect(taskRunCount).toEqual(1) - const ws = await newChainlinkNode(ENDPOINT, chainlinkNode.accessKey, secret) + const jr = await db.manager.findOne(JobRun) + expect(jr.status).toEqual('completed') + + const tr = jr.taskRuns[0] + expect(tr.status).toEqual('completed') + }) + + it('can create a task run with transactionHash and status', async () => { + expect.assertions(10) + + const ws = await authenticatedNode() + + const messageReceived = new Promise(resolve => { + ws.on('message', (data: any) => { + const response = JSON.parse(data) + expect(response.result).toEqual('success') + resolve() + }) + }) - ws.send('{invalid json}') + ws.send(JSON.stringify(ethtxFixture)) + await messageReceived - ws.on('message', async (data: any) => { - const result = JSON.parse(data) - expect(result.status).toEqual(422) + const jobRunCount = await db.manager.count(JobRun) + expect(jobRunCount).toEqual(1) - const count = await db.manager.count(JobRun) - expect(count).toEqual(0) + const taskRunCount = await db.manager.count(TaskRun) + expect(taskRunCount).toEqual(4) + const jobRunRepository = getCustomRepository(JobRunRepository, db.name) + const jr = await jobRunRepository.getFirst() + + expect(jr.status).toEqual('completed') + + const tr = jr.taskRuns[3] + expect(tr.status).toEqual('completed') + expect(tr.transactionHash).toEqual( + '0x1111111111111111111111111111111111111111111111111111111111111111', + ) + expect(tr.timestamp).toEqual(new Date('2018-01-08T18:12:01.103Z')) + expect(tr.blockHeight).toEqual('3735928559') + expect(tr.blockHash).toEqual('0xbadc0de5') + expect(tr.transactionStatus).toEqual('fulfilledRunLog') ws.close() - done() + }) + + it('rejects malformed json events with code -32602', async (done: any) => { + expect.assertions(2) + + const ws = await authenticatedNode() + + const invalidJSON = { + jsonrpc: '2.0', + method: 'upsertJobRun', + id: 1, + params: { + invalid: 'json', + }, + } + + ws.send(JSON.stringify(invalidJSON)) + + ws.on('message', async (data: any) => { + const response = JSON.parse(data) + expect(response.error.code).toEqual(-32602) + + const count = await db.manager.count(JobRun) + expect(count).toEqual(0) + + ws.close() + done() + }) }) }) diff --git a/explorer/src/entity/JobRun.ts b/explorer/src/entity/JobRun.ts index a433b8ad7eb..3c2ac9f646a 100644 --- a/explorer/src/entity/JobRun.ts +++ b/explorer/src/entity/JobRun.ts @@ -61,6 +61,10 @@ export class JobRun { export const fromString = (str: string): JobRun => { const json = JSON.parse(str) + return fromJSONObject(json) +} + +export const fromJSONObject = (json: any): JobRun => { const jr = new JobRun() jr.runId = json.runId jr.jobId = json.jobId diff --git a/explorer/src/handleMessage.ts b/explorer/src/handleMessage.ts new file mode 100644 index 00000000000..4c714e52417 --- /dev/null +++ b/explorer/src/handleMessage.ts @@ -0,0 +1,49 @@ +import { saveJobRunTree, fromString } from './entity/JobRun' +// import { Connection } from 'typeorm' +import { logger } from './logging' +import rpcServer from './rpcServer' +import { getDb } from './database' +import { reject } from 'q' +import jayson from 'jayson' + +// todo make jsonRPC +export const legacyErrorResponse = { status: 422 } + +export type messageContext = { + chainlinkNodeId: number +} + +const handleLegacy = async (json: string, context: messageContext) => { + try { + const db = await getDb() + const jobRun = fromString(json) + jobRun.chainlinkNodeId = context.chainlinkNodeId + await saveJobRunTree(db, jobRun) + return { status: 201 } + } catch { + return { status: 422 } + } +} + +const handleJSONRCP = async (request: string, context: messageContext) => { + return await new Promise((resolve, reject) => { + // @ts-ignore - broken typing for server.call - should be able to accept 3 arguments + // https://github.com/tedeh/jayson#server-context + // https://github.com/tedeh/jayson/pull/152 + rpcServer.call(request, context, (error: any, response: any) => { + // resolve both error and success responses + error ? resolve(error) : resolve(response) + }) + }) +} + +export const handleMessage = async ( + message: string, + context: messageContext, +) => { + if (message.includes('jsonrpc')) { + return await handleJSONRCP(message, context) + } else { + return await handleLegacy(message, context) + } +} diff --git a/explorer/src/realtime.ts b/explorer/src/realtime.ts index c2dab3a9669..829e4b6823a 100644 --- a/explorer/src/realtime.ts +++ b/explorer/src/realtime.ts @@ -1,34 +1,16 @@ import http from 'http' -import { fromString, saveJobRunTree } from './entity/JobRun' import { logger } from './logging' import WebSocket from 'ws' -import { Connection } from 'typeorm' import { getDb } from './database' import { authenticate } from './sessions' import { closeSession, Session } from './entity/Session' +import { handleMessage } from './handleMessage' import { ACCESS_KEY_HEADER, NORMAL_CLOSE, SECRET_HEADER, } from './utils/constants' -const handleMessage = async ( - message: string, - chainlinkNodeId: number, - db: Connection, -) => { - try { - const jobRun = fromString(message) - jobRun.chainlinkNodeId = chainlinkNodeId - - await saveJobRunTree(db, jobRun) - return { status: 201 } - } catch (e) { - logger.error(e) - return { status: 422 } - } -} - export const bootstrapRealtime = async (server: http.Server) => { const db = await getDb() let clnodeCount = 0 @@ -96,11 +78,10 @@ export const bootstrapRealtime = async (server: http.Server) => { return } - const result = await handleMessage( - message as string, - session.chainlinkNodeId, - db, - ) + const result = await handleMessage(message as string, { + chainlinkNodeId: session.chainlinkNodeId, + }) + ws.send(JSON.stringify(result)) }) diff --git a/explorer/src/rpcServer.ts b/explorer/src/rpcServer.ts new file mode 100644 index 00000000000..e29a3505b20 --- /dev/null +++ b/explorer/src/rpcServer.ts @@ -0,0 +1,32 @@ +import { fromJSONObject, saveJobRunTree } from './entity/JobRun' +import { logger } from './logging' +import { getDb } from './database' +import { messageContext } from './handleMessage' + +import jayson from 'jayson' + +const { INVALID_PARAMS } = jayson.Server.errors + +const methods = { + upsertJobRun: async ( + payload: any, + context: messageContext, + callback: (a: any, b?: any) => void, + ) => { + try { + const db = await getDb() + const jobRun = fromJSONObject(payload) + jobRun.chainlinkNodeId = context.chainlinkNodeId + await saveJobRunTree(db, jobRun) + callback(null, 'success') + } catch { + callback({ code: INVALID_PARAMS, message: 'invalid params' }) + } + }, +} + +const serverOptions = { + useContext: true, +} + +export default new jayson.Server(methods, serverOptions) From fe86dc2f62cd94484f3004d7c6e3e082ffdb2a53 Mon Sep 17 00:00:00 2001 From: Ryan Hall Date: Tue, 29 Oct 2019 18:36:59 -0400 Subject: [PATCH 058/199] move server files to server/directory --- explorer/src/{ => server}/handleMessage.ts | 12 +++++------- explorer/src/{server.ts => server/index.ts} | 8 ++++---- explorer/src/{ => server}/realtime.ts | 10 +++++----- explorer/src/{ => server}/rpcServer.ts | 9 +++++---- 4 files changed, 19 insertions(+), 20 deletions(-) rename explorer/src/{ => server}/handleMessage.ts (83%) rename explorer/src/{server.ts => server/index.ts} (89%) rename explorer/src/{ => server}/realtime.ts (93%) rename explorer/src/{ => server}/rpcServer.ts (78%) diff --git a/explorer/src/handleMessage.ts b/explorer/src/server/handleMessage.ts similarity index 83% rename from explorer/src/handleMessage.ts rename to explorer/src/server/handleMessage.ts index 4c714e52417..700860ea9d4 100644 --- a/explorer/src/handleMessage.ts +++ b/explorer/src/server/handleMessage.ts @@ -1,10 +1,7 @@ -import { saveJobRunTree, fromString } from './entity/JobRun' -// import { Connection } from 'typeorm' -import { logger } from './logging' +import { saveJobRunTree, fromString } from '../entity/JobRun' +import { logger } from '../logging' import rpcServer from './rpcServer' -import { getDb } from './database' -import { reject } from 'q' -import jayson from 'jayson' +import { getDb } from '../database' // todo make jsonRPC export const legacyErrorResponse = { status: 422 } @@ -20,7 +17,8 @@ const handleLegacy = async (json: string, context: messageContext) => { jobRun.chainlinkNodeId = context.chainlinkNodeId await saveJobRunTree(db, jobRun) return { status: 201 } - } catch { + } catch (e) { + logger.error(e) return { status: 422 } } } diff --git a/explorer/src/server.ts b/explorer/src/server/index.ts similarity index 89% rename from explorer/src/server.ts rename to explorer/src/server/index.ts index 5fb9d352fe5..b88cf7bc6ca 100644 --- a/explorer/src/server.ts +++ b/explorer/src/server/index.ts @@ -3,11 +3,11 @@ import helmet from 'helmet' import http from 'http' import mime from 'mime-types' import cookieSession from 'cookie-session' -import adminAuth from './middleware/adminAuth' -import * as controllers from './controllers' -import { addRequestLogging, logger } from './logging' +import adminAuth from '../middleware/adminAuth' +import * as controllers from '../controllers' +import { addRequestLogging, logger } from '../logging' import { bootstrapRealtime } from './realtime' -import seed from './seed' +import seed from '../seed' export const DEFAULT_PORT = parseInt(process.env.SERVER_PORT, 10) || 8080 export const COOKIE_EXPIRATION_MS = 86400000 // 1 day in ms diff --git a/explorer/src/realtime.ts b/explorer/src/server/realtime.ts similarity index 93% rename from explorer/src/realtime.ts rename to explorer/src/server/realtime.ts index 829e4b6823a..3c10e52a25d 100644 --- a/explorer/src/realtime.ts +++ b/explorer/src/server/realtime.ts @@ -1,15 +1,15 @@ import http from 'http' -import { logger } from './logging' +import { logger } from '../logging' import WebSocket from 'ws' -import { getDb } from './database' -import { authenticate } from './sessions' -import { closeSession, Session } from './entity/Session' +import { getDb } from '../database' +import { authenticate } from '../sessions' +import { closeSession, Session } from '../entity/Session' import { handleMessage } from './handleMessage' import { ACCESS_KEY_HEADER, NORMAL_CLOSE, SECRET_HEADER, -} from './utils/constants' +} from '../utils/constants' export const bootstrapRealtime = async (server: http.Server) => { const db = await getDb() diff --git a/explorer/src/rpcServer.ts b/explorer/src/server/rpcServer.ts similarity index 78% rename from explorer/src/rpcServer.ts rename to explorer/src/server/rpcServer.ts index e29a3505b20..95bfe97fe4a 100644 --- a/explorer/src/rpcServer.ts +++ b/explorer/src/server/rpcServer.ts @@ -1,6 +1,6 @@ -import { fromJSONObject, saveJobRunTree } from './entity/JobRun' -import { logger } from './logging' -import { getDb } from './database' +import { fromJSONObject, saveJobRunTree } from '../entity/JobRun' +import { logger } from '../logging' +import { getDb } from '../database' import { messageContext } from './handleMessage' import jayson from 'jayson' @@ -19,7 +19,8 @@ const methods = { jobRun.chainlinkNodeId = context.chainlinkNodeId await saveJobRunTree(db, jobRun) callback(null, 'success') - } catch { + } catch (e) { + logger.error(e) callback({ code: INVALID_PARAMS, message: 'invalid params' }) } }, From 2a118c3bdc8367525c72dd1a78f1f34361d896d1 Mon Sep 17 00:00:00 2001 From: Ryan Hall Date: Wed, 30 Oct 2019 08:53:00 -0400 Subject: [PATCH 059/199] separate RPCMethods from server --- explorer/src/server/handleMessage.ts | 20 +----------- explorer/src/server/rpcMethods/index.ts | 5 +++ explorer/src/server/rpcMethods/legacy.ts | 20 ++++++++++++ .../src/server/rpcMethods/upsertJobRun.ts | 24 ++++++++++++++ explorer/src/server/rpcServer.ts | 31 ++----------------- 5 files changed, 53 insertions(+), 47 deletions(-) create mode 100644 explorer/src/server/rpcMethods/index.ts create mode 100644 explorer/src/server/rpcMethods/legacy.ts create mode 100644 explorer/src/server/rpcMethods/upsertJobRun.ts diff --git a/explorer/src/server/handleMessage.ts b/explorer/src/server/handleMessage.ts index 700860ea9d4..c73a292520a 100644 --- a/explorer/src/server/handleMessage.ts +++ b/explorer/src/server/handleMessage.ts @@ -1,28 +1,10 @@ -import { saveJobRunTree, fromString } from '../entity/JobRun' -import { logger } from '../logging' import rpcServer from './rpcServer' -import { getDb } from '../database' - -// todo make jsonRPC -export const legacyErrorResponse = { status: 422 } +import handleLegacy from './rpcMethods/legacy' export type messageContext = { chainlinkNodeId: number } -const handleLegacy = async (json: string, context: messageContext) => { - try { - const db = await getDb() - const jobRun = fromString(json) - jobRun.chainlinkNodeId = context.chainlinkNodeId - await saveJobRunTree(db, jobRun) - return { status: 201 } - } catch (e) { - logger.error(e) - return { status: 422 } - } -} - const handleJSONRCP = async (request: string, context: messageContext) => { return await new Promise((resolve, reject) => { // @ts-ignore - broken typing for server.call - should be able to accept 3 arguments diff --git a/explorer/src/server/rpcMethods/index.ts b/explorer/src/server/rpcMethods/index.ts new file mode 100644 index 00000000000..4921bd0e84c --- /dev/null +++ b/explorer/src/server/rpcMethods/index.ts @@ -0,0 +1,5 @@ +import upsertJobRun from './upsertJobRun' + +export default { + upsertJobRun, +} diff --git a/explorer/src/server/rpcMethods/legacy.ts b/explorer/src/server/rpcMethods/legacy.ts new file mode 100644 index 00000000000..73ba0de4769 --- /dev/null +++ b/explorer/src/server/rpcMethods/legacy.ts @@ -0,0 +1,20 @@ +/***** THIS IS NOT AN RPC METHOD ******/ +/* THIS IS THE LEGACY SERVER FUNCTION */ + +import { fromString, saveJobRunTree } from '../../entity/JobRun' +import { logger } from '../../logging' +import { getDb } from '../../database' +import { messageContext } from './../handleMessage' + +export default async (json: string, context: messageContext) => { + try { + const db = await getDb() + const jobRun = fromString(json) + jobRun.chainlinkNodeId = context.chainlinkNodeId + await saveJobRunTree(db, jobRun) + return { status: 201 } + } catch (e) { + logger.error(e) + return { status: 422 } + } +} diff --git a/explorer/src/server/rpcMethods/upsertJobRun.ts b/explorer/src/server/rpcMethods/upsertJobRun.ts new file mode 100644 index 00000000000..0f60be5ff48 --- /dev/null +++ b/explorer/src/server/rpcMethods/upsertJobRun.ts @@ -0,0 +1,24 @@ +import { fromJSONObject, saveJobRunTree } from '../../entity/JobRun' +import { logger } from '../../logging' +import { getDb } from '../../database' +import { messageContext } from './../handleMessage' +import jayson from 'jayson' + +const { INVALID_PARAMS } = jayson.Server.errors + +export default async ( + payload: any, + context: messageContext, + callback: (a: any, b?: any) => void, +) => { + try { + const db = await getDb() + const jobRun = fromJSONObject(payload) + jobRun.chainlinkNodeId = context.chainlinkNodeId + await saveJobRunTree(db, jobRun) + callback(null, 'success') + } catch (e) { + logger.error(e) + callback({ code: INVALID_PARAMS, message: 'invalid params' }) + } +} diff --git a/explorer/src/server/rpcServer.ts b/explorer/src/server/rpcServer.ts index 95bfe97fe4a..a5a8fccfd9e 100644 --- a/explorer/src/server/rpcServer.ts +++ b/explorer/src/server/rpcServer.ts @@ -1,33 +1,8 @@ -import { fromJSONObject, saveJobRunTree } from '../entity/JobRun' -import { logger } from '../logging' -import { getDb } from '../database' -import { messageContext } from './handleMessage' - import jayson from 'jayson' - -const { INVALID_PARAMS } = jayson.Server.errors - -const methods = { - upsertJobRun: async ( - payload: any, - context: messageContext, - callback: (a: any, b?: any) => void, - ) => { - try { - const db = await getDb() - const jobRun = fromJSONObject(payload) - jobRun.chainlinkNodeId = context.chainlinkNodeId - await saveJobRunTree(db, jobRun) - callback(null, 'success') - } catch (e) { - logger.error(e) - callback({ code: INVALID_PARAMS, message: 'invalid params' }) - } - }, -} +import rpcMethods from './rpcMethods' const serverOptions = { - useContext: true, + useContext: true, // permits passing extra data object to RPC methods as 'server context' } -export default new jayson.Server(methods, serverOptions) +export default new jayson.Server(rpcMethods, serverOptions) From 511aeb4f367e3169ac26d23c025e3b088223c0e1 Mon Sep 17 00:00:00 2001 From: Ryan Hall Date: Wed, 30 Oct 2019 09:09:44 -0400 Subject: [PATCH 060/199] move handleLegacy back into handleMessage --- explorer/src/server/handleMessage.ts | 23 +++++++++++++++++++---- explorer/src/server/rpcMethods/legacy.ts | 20 -------------------- 2 files changed, 19 insertions(+), 24 deletions(-) delete mode 100644 explorer/src/server/rpcMethods/legacy.ts diff --git a/explorer/src/server/handleMessage.ts b/explorer/src/server/handleMessage.ts index c73a292520a..254fb77b4e4 100644 --- a/explorer/src/server/handleMessage.ts +++ b/explorer/src/server/handleMessage.ts @@ -1,11 +1,26 @@ import rpcServer from './rpcServer' -import handleLegacy from './rpcMethods/legacy' +import { logger } from '../logging' +import { getDb } from '../database' +import { fromString, saveJobRunTree } from '../entity/JobRun' -export type messageContext = { +export type serverContext = { chainlinkNodeId: number } -const handleJSONRCP = async (request: string, context: messageContext) => { +const handleLegacy = async (json: string, context: serverContext) => { + try { + const db = await getDb() + const jobRun = fromString(json) + jobRun.chainlinkNodeId = context.chainlinkNodeId + await saveJobRunTree(db, jobRun) + return { status: 201 } + } catch (e) { + logger.error(e) + return { status: 422 } + } +} + +const handleJSONRCP = async (request: string, context: serverContext) => { return await new Promise((resolve, reject) => { // @ts-ignore - broken typing for server.call - should be able to accept 3 arguments // https://github.com/tedeh/jayson#server-context @@ -19,7 +34,7 @@ const handleJSONRCP = async (request: string, context: messageContext) => { export const handleMessage = async ( message: string, - context: messageContext, + context: serverContext, ) => { if (message.includes('jsonrpc')) { return await handleJSONRCP(message, context) diff --git a/explorer/src/server/rpcMethods/legacy.ts b/explorer/src/server/rpcMethods/legacy.ts deleted file mode 100644 index 73ba0de4769..00000000000 --- a/explorer/src/server/rpcMethods/legacy.ts +++ /dev/null @@ -1,20 +0,0 @@ -/***** THIS IS NOT AN RPC METHOD ******/ -/* THIS IS THE LEGACY SERVER FUNCTION */ - -import { fromString, saveJobRunTree } from '../../entity/JobRun' -import { logger } from '../../logging' -import { getDb } from '../../database' -import { messageContext } from './../handleMessage' - -export default async (json: string, context: messageContext) => { - try { - const db = await getDb() - const jobRun = fromString(json) - jobRun.chainlinkNodeId = context.chainlinkNodeId - await saveJobRunTree(db, jobRun) - return { status: 201 } - } catch (e) { - logger.error(e) - return { status: 422 } - } -} From 9dd6966bea4fb1d0bc9a3350a82b5bb5be8e4cd2 Mon Sep 17 00:00:00 2001 From: Ryan Hall Date: Wed, 30 Oct 2019 09:54:16 -0400 Subject: [PATCH 061/199] add typings --- explorer/src/server/handleMessage.ts | 2 +- explorer/src/server/rpcMethods/upsertJobRun.ts | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/explorer/src/server/handleMessage.ts b/explorer/src/server/handleMessage.ts index 254fb77b4e4..d63f1c0eec4 100644 --- a/explorer/src/server/handleMessage.ts +++ b/explorer/src/server/handleMessage.ts @@ -25,7 +25,7 @@ const handleJSONRCP = async (request: string, context: serverContext) => { // @ts-ignore - broken typing for server.call - should be able to accept 3 arguments // https://github.com/tedeh/jayson#server-context // https://github.com/tedeh/jayson/pull/152 - rpcServer.call(request, context, (error: any, response: any) => { + rpcServer.call(request, context, (error, response) => { // resolve both error and success responses error ? resolve(error) : resolve(response) }) diff --git a/explorer/src/server/rpcMethods/upsertJobRun.ts b/explorer/src/server/rpcMethods/upsertJobRun.ts index 0f60be5ff48..04c7748826d 100644 --- a/explorer/src/server/rpcMethods/upsertJobRun.ts +++ b/explorer/src/server/rpcMethods/upsertJobRun.ts @@ -1,15 +1,15 @@ import { fromJSONObject, saveJobRunTree } from '../../entity/JobRun' import { logger } from '../../logging' import { getDb } from '../../database' -import { messageContext } from './../handleMessage' +import { serverContext } from './../handleMessage' import jayson from 'jayson' const { INVALID_PARAMS } = jayson.Server.errors export default async ( - payload: any, - context: messageContext, - callback: (a: any, b?: any) => void, + payload: object, + context: serverContext, + callback: jayson.JSONRPCCallbackTypePlain, ) => { try { const db = await getDb() From 4737e52ab37aede7d93d446e37e01ea45d5ef620 Mon Sep 17 00:00:00 2001 From: Ryan Hall Date: Wed, 30 Oct 2019 11:02:15 -0400 Subject: [PATCH 062/199] add more tests surrounding error handling --- explorer/src/__tests__/realtime.test.ts | 225 ++++++++++++++++-------- 1 file changed, 151 insertions(+), 74 deletions(-) diff --git a/explorer/src/__tests__/realtime.test.ts b/explorer/src/__tests__/realtime.test.ts index ebe3ecacb82..6e07f929254 100644 --- a/explorer/src/__tests__/realtime.test.ts +++ b/explorer/src/__tests__/realtime.test.ts @@ -68,7 +68,7 @@ describe('realtime', () => { afterAll(done => stop(server, done)) - describe('legacy message format', () => { + describe('when sending messages in legacy format', () => { it('can create a job run with valid JSON', async () => { expect.assertions(3) @@ -190,125 +190,202 @@ describe('realtime', () => { }) }) - describe('jsonRPC message format', () => { - it('can create a job run with valid JSON', async () => { - expect.assertions(3) + describe('when sending messages in JSON-RPC format', () => { + describe('#upsertJobRun', () => { + it('can create a job run with valid JSON', async () => { + expect.assertions(3) - const ws = await authenticatedNode() + const ws = await authenticatedNode() - ws.send(JSON.stringify(createFixture)) + ws.send(JSON.stringify(createFixture)) - await new Promise(resolve => { - ws.on('message', (data: WebSocket.Data) => { - const response = JSON.parse(data as string) - expect(response.result).toEqual('success') - ws.close() - resolve() + await new Promise(resolve => { + ws.on('message', (data: WebSocket.Data) => { + const response = JSON.parse(data as string) + expect(response.result).toEqual('success') + ws.close() + resolve() + }) }) + + const jobRunCount = await db.manager.count(JobRun) + expect(jobRunCount).toEqual(1) + + const taskRunCount = await db.manager.count(TaskRun) + expect(taskRunCount).toEqual(1) }) - const jobRunCount = await db.manager.count(JobRun) - expect(jobRunCount).toEqual(1) + it('can create and update a job run and task runs', async () => { + expect.assertions(6) - const taskRunCount = await db.manager.count(TaskRun) - expect(taskRunCount).toEqual(1) - }) + const ws = await authenticatedNode() - it('can create and update a job run and task runs', async () => { - expect.assertions(6) + ws.send(JSON.stringify(createFixture)) - const ws = await authenticatedNode() + await new Promise(resolve => { + ws.on('message', (data: any) => { + const response = JSON.parse(data) - ws.send(JSON.stringify(createFixture)) + if (response.id === createFixture.id) { + expect(response.result).toEqual('success') + ws.send(JSON.stringify(updateFixture)) + } - await new Promise(resolve => { - ws.on('message', (data: any) => { - const response = JSON.parse(data) + if (response.id === updateFixture.id) { + expect(response.result).toEqual('success') + ws.close() + resolve() + } + }) + }) - if (response.id === createFixture.id) { - expect(response.result).toEqual('success') - ws.send(JSON.stringify(updateFixture)) - } + const jobRunCount = await db.manager.count(JobRun) + expect(jobRunCount).toEqual(1) - if (response.id === updateFixture.id) { + const taskRunCount = await db.manager.count(TaskRun) + expect(taskRunCount).toEqual(1) + + const jr = await db.manager.findOne(JobRun) + expect(jr.status).toEqual('completed') + + const tr = jr.taskRuns[0] + expect(tr.status).toEqual('completed') + }) + + it('can create a task run with transactionHash and status', async () => { + expect.assertions(10) + + const ws = await authenticatedNode() + + const messageReceived = new Promise(resolve => { + ws.on('message', (data: any) => { + const response = JSON.parse(data) expect(response.result).toEqual('success') - ws.close() resolve() - } + }) }) + + ws.send(JSON.stringify(ethtxFixture)) + await messageReceived + + const jobRunCount = await db.manager.count(JobRun) + expect(jobRunCount).toEqual(1) + + const taskRunCount = await db.manager.count(TaskRun) + expect(taskRunCount).toEqual(4) + + const jobRunRepository = getCustomRepository(JobRunRepository, db.name) + const jr = await jobRunRepository.getFirst() + + expect(jr.status).toEqual('completed') + + const tr = jr.taskRuns[3] + expect(tr.status).toEqual('completed') + expect(tr.transactionHash).toEqual( + '0x1111111111111111111111111111111111111111111111111111111111111111', + ) + expect(tr.timestamp).toEqual(new Date('2018-01-08T18:12:01.103Z')) + expect(tr.blockHeight).toEqual('3735928559') + expect(tr.blockHash).toEqual('0xbadc0de5') + expect(tr.transactionStatus).toEqual('fulfilledRunLog') + ws.close() }) - const jobRunCount = await db.manager.count(JobRun) - expect(jobRunCount).toEqual(1) + it('rejects malformed json events with code -32602', async (done: any) => { + expect.assertions(2) - const taskRunCount = await db.manager.count(TaskRun) - expect(taskRunCount).toEqual(1) + const ws = await authenticatedNode() - const jr = await db.manager.findOne(JobRun) - expect(jr.status).toEqual('completed') + const invalidJSON = { + jsonrpc: '2.0', + method: 'upsertJobRun', + id: 1, + params: { + invalid: 'json', + }, + } - const tr = jr.taskRuns[0] - expect(tr.status).toEqual('completed') + ws.send(JSON.stringify(invalidJSON)) + + ws.on('message', async (data: any) => { + const response = JSON.parse(data) + expect(response.error.code).toEqual(-32602) + + const count = await db.manager.count(JobRun) + expect(count).toEqual(0) + + ws.close() + done() + }) + }) }) - it('can create a task run with transactionHash and status', async () => { - expect.assertions(10) + it('rejects non-existing methods with code -32601', async (done: any) => { + expect.assertions(2) const ws = await authenticatedNode() - const messageReceived = new Promise(resolve => { - ws.on('message', (data: any) => { - const response = JSON.parse(data) - expect(response.result).toEqual('success') - resolve() - }) + const request = { + jsonrpc: '2.0', + method: 'doesNotExist', + id: 1, + } + + ws.send(JSON.stringify(request)) + + ws.on('message', async (data: any) => { + const response = JSON.parse(data) + expect(response.error.code).toEqual(-32601) + + const count = await db.manager.count(JobRun) + expect(count).toEqual(0) + + ws.close() + done() }) + }) - ws.send(JSON.stringify(ethtxFixture)) - await messageReceived + // this test depends on the presence of "jsonrpc" in the message + // otherwise, the server will attempt to process the message as a + // legacy message and will respond with { status: 422 }. + // This test will be more appropriate once the legacy format is removed. + it('rejects malformed json with code -32700', async (done: any) => { + expect.assertions(2) - const jobRunCount = await db.manager.count(JobRun) - expect(jobRunCount).toEqual(1) + const ws = await authenticatedNode() - const taskRunCount = await db.manager.count(TaskRun) - expect(taskRunCount).toEqual(4) + const request = 'jsonrpc invalid' - const jobRunRepository = getCustomRepository(JobRunRepository, db.name) - const jr = await jobRunRepository.getFirst() + ws.send(request) - expect(jr.status).toEqual('completed') + ws.on('message', async (data: any) => { + const response = JSON.parse(data) + expect(response.error.code).toEqual(-32700) - const tr = jr.taskRuns[3] - expect(tr.status).toEqual('completed') - expect(tr.transactionHash).toEqual( - '0x1111111111111111111111111111111111111111111111111111111111111111', - ) - expect(tr.timestamp).toEqual(new Date('2018-01-08T18:12:01.103Z')) - expect(tr.blockHeight).toEqual('3735928559') - expect(tr.blockHash).toEqual('0xbadc0de5') - expect(tr.transactionStatus).toEqual('fulfilledRunLog') - ws.close() + const count = await db.manager.count(JobRun) + expect(count).toEqual(0) + + ws.close() + done() + }) }) - it('rejects malformed json events with code -32602', async (done: any) => { + it('rejects invalid rpc requests with code -32600', async (done: any) => { expect.assertions(2) const ws = await authenticatedNode() - const invalidJSON = { + const request = { jsonrpc: '2.0', - method: 'upsertJobRun', + function: 'foo', id: 1, - params: { - invalid: 'json', - }, } - ws.send(JSON.stringify(invalidJSON)) + ws.send(JSON.stringify(request)) ws.on('message', async (data: any) => { const response = JSON.parse(data) - expect(response.error.code).toEqual(-32602) + expect(response.error.code).toEqual(-32600) const count = await db.manager.count(JobRun) expect(count).toEqual(0) From 78b66fca4b3b8fc26e308541d33f17faf639e237 Mon Sep 17 00:00:00 2001 From: Ryan Hall Date: Wed, 30 Oct 2019 11:22:52 -0400 Subject: [PATCH 063/199] update error message creation to use jayson defaults --- explorer/src/__tests__/realtime.test.ts | 2 +- explorer/src/server/rpcMethods/upsertJobRun.ts | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/explorer/src/__tests__/realtime.test.ts b/explorer/src/__tests__/realtime.test.ts index 6e07f929254..697cc1944f1 100644 --- a/explorer/src/__tests__/realtime.test.ts +++ b/explorer/src/__tests__/realtime.test.ts @@ -291,7 +291,7 @@ describe('realtime', () => { ws.close() }) - it('rejects malformed json events with code -32602', async (done: any) => { + it('rejects invalid json params with code -32602', async (done: any) => { expect.assertions(2) const ws = await authenticatedNode() diff --git a/explorer/src/server/rpcMethods/upsertJobRun.ts b/explorer/src/server/rpcMethods/upsertJobRun.ts index 04c7748826d..d23c48bcddd 100644 --- a/explorer/src/server/rpcMethods/upsertJobRun.ts +++ b/explorer/src/server/rpcMethods/upsertJobRun.ts @@ -4,7 +4,9 @@ import { getDb } from '../../database' import { serverContext } from './../handleMessage' import jayson from 'jayson' +// default invalid params error const { INVALID_PARAMS } = jayson.Server.errors +const invalidParamsError = new jayson.Server().error(INVALID_PARAMS) export default async ( payload: object, @@ -19,6 +21,6 @@ export default async ( callback(null, 'success') } catch (e) { logger.error(e) - callback({ code: INVALID_PARAMS, message: 'invalid params' }) + callback(invalidParamsError) } } From 2506fd1f93ea14a6679767ee9a0a7f74a783d374 Mon Sep 17 00:00:00 2001 From: Ryan Hall Date: Wed, 30 Oct 2019 12:02:01 -0400 Subject: [PATCH 064/199] all tests passing --- .../fixtures/JobRun.ethtx.fixture.json | 78 ++++++++----------- .../__tests__/fixtures/JobRun.fixture.json | 51 ++++++------ .../fixtures/JobRunLegacy.ethtx.fixture.json | 37 --------- .../fixtures/JobRunLegacy.fixture.json | 25 ------ .../fixtures/JobRunUpdate.fixture.json | 51 ++++++------ .../fixtures/JobRunUpdateLegacy.fixture.json | 25 ------ explorer/src/__tests__/realtime.test.ts | 65 ++++++---------- 7 files changed, 104 insertions(+), 228 deletions(-) delete mode 100644 explorer/src/__tests__/fixtures/JobRunLegacy.ethtx.fixture.json delete mode 100644 explorer/src/__tests__/fixtures/JobRunLegacy.fixture.json delete mode 100644 explorer/src/__tests__/fixtures/JobRunUpdateLegacy.fixture.json diff --git a/explorer/src/__tests__/fixtures/JobRun.ethtx.fixture.json b/explorer/src/__tests__/fixtures/JobRun.ethtx.fixture.json index a6722d019ac..d2c8117371f 100644 --- a/explorer/src/__tests__/fixtures/JobRun.ethtx.fixture.json +++ b/explorer/src/__tests__/fixtures/JobRun.ethtx.fixture.json @@ -1,47 +1,37 @@ { - "jsonrpc": "2.0", - "method": "upsertJobRun", - "id": 1, - "params": { - "id": "fe64b0a02e944a6ab411e121a0658117", - "jobId": "0d7f0402ce8d44ef80eea4305af7755d", - "runId": "fe64b0a02e944a6ab411e121a0658117", - "status": "completed", - "error": null, - "createdAt": "2019-05-03T15:21:35Z", - "payment": null, - "finishedAt": "2019-05-03T11:21:36.01197-04:00", - "initiator": { "type": "web" }, - "tasks": [ - { - "index": 0, - "type": "httpget", - "status": "completed", - "confirmations": "3", - "minimumConfirmations": "3", - "error": null - }, - { "index": 1, "type": "jsonparse", "status": "completed", "error": null }, - { - "index": 2, - "type": "ethbytes32", - "status": "completed", - "error": null - }, - { - "index": 3, - "type": "ethtx", - "status": "completed", - "error": null, - "result": { - "transactionHash": "0x1111111111111111111111111111111111111111111111111111111111111111", - "transactionStatus": "fulfilledRunLog", - "timestamp": "2018-01-08T18:12:01.103Z", - "blockHeight": "0xdeadbeef", - "blockHash": "0xbadc0de5", - "blockNumber": "0x6" - } + "id": "fe64b0a02e944a6ab411e121a0658117", + "jobId": "0d7f0402ce8d44ef80eea4305af7755d", + "runId": "fe64b0a02e944a6ab411e121a0658117", + "status": "completed", + "error": null, + "createdAt": "2019-05-03T15:21:35Z", + "payment": null, + "finishedAt": "2019-05-03T11:21:36.01197-04:00", + "initiator": { "type": "web" }, + "tasks": [ + { + "index": 0, + "type": "httpget", + "status": "completed", + "confirmations": "3", + "minimumConfirmations": "3", + "error": null + }, + { "index": 1, "type": "jsonparse", "status": "completed", "error": null }, + { "index": 2, "type": "ethbytes32", "status": "completed", "error": null }, + { + "index": 3, + "type": "ethtx", + "status": "completed", + "error": null, + "result": { + "transactionHash": "0x1111111111111111111111111111111111111111111111111111111111111111", + "transactionStatus": "fulfilledRunLog", + "timestamp": "2018-01-08T18:12:01.103Z", + "blockHeight": "0xdeadbeef", + "blockHash": "0xbadc0de5", + "blockNumber": "0x6" } - ] - } + } + ] } diff --git a/explorer/src/__tests__/fixtures/JobRun.fixture.json b/explorer/src/__tests__/fixtures/JobRun.fixture.json index 0c9870133c6..89beed84ce9 100644 --- a/explorer/src/__tests__/fixtures/JobRun.fixture.json +++ b/explorer/src/__tests__/fixtures/JobRun.fixture.json @@ -1,30 +1,25 @@ { - "jsonrpc": "2.0", - "method": "upsertJobRun", - "id": 1, - "params": { - "jobId": "aeb2861d306645b1ba012079aeb2e53a", - "runId": "f1xtureAaaaaaaaaaaaaaaaaaaaaaaaa", - "status": "in_progress", - "error": null, - "createdAt": "2019-04-01T22:07:04Z", - "payment": null, - "finishedAt": null, - "initiator": { - "type": "runlog", - "requestId": "RequestID", - "txHash": "0x00000000000000000000000000000000000000000000000000000000deadbeef", - "requester": "0x9FBDa871d559710256a2502A2517b794B482Db40" - }, - "tasks": [ - { - "index": 0, - "type": "httpget", - "status": "", - "confirmations": "0", - "minimumConfirmations": "3", - "error": null - } - ] - } + "jobId": "aeb2861d306645b1ba012079aeb2e53a", + "runId": "f1xtureAaaaaaaaaaaaaaaaaaaaaaaaa", + "status": "in_progress", + "error": null, + "createdAt": "2019-04-01T22:07:04Z", + "payment": null, + "finishedAt": null, + "initiator": { + "type": "runlog", + "requestId": "RequestID", + "txHash": "0x00000000000000000000000000000000000000000000000000000000deadbeef", + "requester": "0x9FBDa871d559710256a2502A2517b794B482Db40" + }, + "tasks": [ + { + "index": 0, + "type": "httpget", + "status": "", + "confirmations": "0", + "minimumConfirmations": "3", + "error": null + } + ] } diff --git a/explorer/src/__tests__/fixtures/JobRunLegacy.ethtx.fixture.json b/explorer/src/__tests__/fixtures/JobRunLegacy.ethtx.fixture.json deleted file mode 100644 index d2c8117371f..00000000000 --- a/explorer/src/__tests__/fixtures/JobRunLegacy.ethtx.fixture.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "id": "fe64b0a02e944a6ab411e121a0658117", - "jobId": "0d7f0402ce8d44ef80eea4305af7755d", - "runId": "fe64b0a02e944a6ab411e121a0658117", - "status": "completed", - "error": null, - "createdAt": "2019-05-03T15:21:35Z", - "payment": null, - "finishedAt": "2019-05-03T11:21:36.01197-04:00", - "initiator": { "type": "web" }, - "tasks": [ - { - "index": 0, - "type": "httpget", - "status": "completed", - "confirmations": "3", - "minimumConfirmations": "3", - "error": null - }, - { "index": 1, "type": "jsonparse", "status": "completed", "error": null }, - { "index": 2, "type": "ethbytes32", "status": "completed", "error": null }, - { - "index": 3, - "type": "ethtx", - "status": "completed", - "error": null, - "result": { - "transactionHash": "0x1111111111111111111111111111111111111111111111111111111111111111", - "transactionStatus": "fulfilledRunLog", - "timestamp": "2018-01-08T18:12:01.103Z", - "blockHeight": "0xdeadbeef", - "blockHash": "0xbadc0de5", - "blockNumber": "0x6" - } - } - ] -} diff --git a/explorer/src/__tests__/fixtures/JobRunLegacy.fixture.json b/explorer/src/__tests__/fixtures/JobRunLegacy.fixture.json deleted file mode 100644 index 89beed84ce9..00000000000 --- a/explorer/src/__tests__/fixtures/JobRunLegacy.fixture.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "jobId": "aeb2861d306645b1ba012079aeb2e53a", - "runId": "f1xtureAaaaaaaaaaaaaaaaaaaaaaaaa", - "status": "in_progress", - "error": null, - "createdAt": "2019-04-01T22:07:04Z", - "payment": null, - "finishedAt": null, - "initiator": { - "type": "runlog", - "requestId": "RequestID", - "txHash": "0x00000000000000000000000000000000000000000000000000000000deadbeef", - "requester": "0x9FBDa871d559710256a2502A2517b794B482Db40" - }, - "tasks": [ - { - "index": 0, - "type": "httpget", - "status": "", - "confirmations": "0", - "minimumConfirmations": "3", - "error": null - } - ] -} diff --git a/explorer/src/__tests__/fixtures/JobRunUpdate.fixture.json b/explorer/src/__tests__/fixtures/JobRunUpdate.fixture.json index 1924750d70f..9550bb971b4 100644 --- a/explorer/src/__tests__/fixtures/JobRunUpdate.fixture.json +++ b/explorer/src/__tests__/fixtures/JobRunUpdate.fixture.json @@ -1,30 +1,25 @@ { - "jsonrpc": "2.0", - "method": "upsertJobRun", - "id": 2, - "params": { - "jobId": "aeb2861d306645b1ba012079aeb2e53a", - "runId": "f1xtureAaaaaaaaaaaaaaaaaaaaaaaaa", - "status": "completed", - "error": null, - "createdAt": "2019-04-01T22:07:04Z", - "payment": null, - "finishedAt": "2018-04-01T22:07:04Z", - "initiator": { - "type": "runlog", - "requestId": "RequestID", - "txHash": "0x00000000000000000000000000000000000000000000000000000000deadbeef", - "requester": "0x9FBDa871d559710256a2502A2517b794B482Db40" - }, - "tasks": [ - { - "index": 0, - "type": "httpget", - "status": "completed", - "confirmations": "3", - "minimumConfirmations": "3", - "error": null - } - ] - } + "jobId": "aeb2861d306645b1ba012079aeb2e53a", + "runId": "f1xtureAaaaaaaaaaaaaaaaaaaaaaaaa", + "status": "completed", + "error": null, + "createdAt": "2019-04-01T22:07:04Z", + "payment": null, + "finishedAt": "2018-04-01T22:07:04Z", + "initiator": { + "type": "runlog", + "requestId": "RequestID", + "txHash": "0x00000000000000000000000000000000000000000000000000000000deadbeef", + "requester": "0x9FBDa871d559710256a2502A2517b794B482Db40" + }, + "tasks": [ + { + "index": 0, + "type": "httpget", + "status": "completed", + "confirmations": "3", + "minimumConfirmations": "3", + "error": null + } + ] } diff --git a/explorer/src/__tests__/fixtures/JobRunUpdateLegacy.fixture.json b/explorer/src/__tests__/fixtures/JobRunUpdateLegacy.fixture.json deleted file mode 100644 index 9550bb971b4..00000000000 --- a/explorer/src/__tests__/fixtures/JobRunUpdateLegacy.fixture.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "jobId": "aeb2861d306645b1ba012079aeb2e53a", - "runId": "f1xtureAaaaaaaaaaaaaaaaaaaaaaaaa", - "status": "completed", - "error": null, - "createdAt": "2019-04-01T22:07:04Z", - "payment": null, - "finishedAt": "2018-04-01T22:07:04Z", - "initiator": { - "type": "runlog", - "requestId": "RequestID", - "txHash": "0x00000000000000000000000000000000000000000000000000000000deadbeef", - "requester": "0x9FBDa871d559710256a2502A2517b794B482Db40" - }, - "tasks": [ - { - "index": 0, - "type": "httpget", - "status": "completed", - "confirmations": "3", - "minimumConfirmations": "3", - "error": null - } - ] -} diff --git a/explorer/src/__tests__/realtime.test.ts b/explorer/src/__tests__/realtime.test.ts index 697cc1944f1..a3965801390 100644 --- a/explorer/src/__tests__/realtime.test.ts +++ b/explorer/src/__tests__/realtime.test.ts @@ -1,15 +1,13 @@ import { Server } from 'http' import { Connection, getCustomRepository } from 'typeorm' import WebSocket from 'ws' +import jayson from 'jayson' import { getDb } from '../database' import { ChainlinkNode, createChainlinkNode } from '../entity/ChainlinkNode' import { JobRun } from '../entity/JobRun' import { TaskRun } from '../entity/TaskRun' import { DEFAULT_TEST_PORT, start, stop } from '../support/server' import { DEFAULT_TEST_PORT, start as startServer } from '../support/server' -import ethtxFixtureLegacy from './fixtures/JobRunLegacy.ethtx.fixture.json' -import createFixtureLegacy from './fixtures/JobRunLegacy.fixture.json' -import updateFixtureLegacy from './fixtures/JobRunUpdateLegacy.fixture.json' import ethtxFixture from './fixtures/JobRun.ethtx.fixture.json' import createFixture from './fixtures/JobRun.fixture.json' import updateFixture from './fixtures/JobRunUpdate.fixture.json' @@ -44,6 +42,10 @@ const newChainlinkNode = ( }) } +const jsonClient = new jayson.Client(null, null) +const createRPCRequest = (method: string, params?: any) => + jsonClient.request(method, params) + describe('realtime', () => { let server: Server let db: Connection @@ -73,8 +75,7 @@ describe('realtime', () => { expect.assertions(3) const ws = await authenticatedNode() - - ws.send(JSON.stringify(createFixtureLegacy)) + ws.send(JSON.stringify(createFixture)) await new Promise(resolve => { ws.on('message', (data: WebSocket.Data) => { @@ -96,8 +97,7 @@ describe('realtime', () => { expect.assertions(6) const ws = await authenticatedNode() - - ws.send(JSON.stringify(createFixtureLegacy)) + ws.send(JSON.stringify(createFixture)) await new Promise(resolve => { let responses = 0 @@ -107,7 +107,7 @@ describe('realtime', () => { if (responses === 1) { expect(response.status).toEqual(201) - ws.send(JSON.stringify(updateFixtureLegacy)) + ws.send(JSON.stringify(updateFixture)) } if (responses === 2) { @@ -144,7 +144,7 @@ describe('realtime', () => { }) }) - ws.send(JSON.stringify(ethtxFixtureLegacy)) + ws.send(JSON.stringify(ethtxFixture)) await messageReceived const jobRunCount = await db.manager.count(JobRun) @@ -174,7 +174,6 @@ describe('realtime', () => { expect.assertions(2) const ws = await authenticatedNode() - ws.send('{invalid json}') ws.on('message', async (data: any) => { @@ -196,8 +195,8 @@ describe('realtime', () => { expect.assertions(3) const ws = await authenticatedNode() - - ws.send(JSON.stringify(createFixture)) + const request = createRPCRequest('upsertJobRun', createFixture) + ws.send(JSON.stringify(request)) await new Promise(resolve => { ws.on('message', (data: WebSocket.Data) => { @@ -219,19 +218,18 @@ describe('realtime', () => { expect.assertions(6) const ws = await authenticatedNode() - - ws.send(JSON.stringify(createFixture)) + const createRequest = createRPCRequest('upsertJobRun', createFixture) + const updateRequest = createRPCRequest('upsertJobRun', updateFixture) + ws.send(JSON.stringify(createRequest)) await new Promise(resolve => { ws.on('message', (data: any) => { const response = JSON.parse(data) - - if (response.id === createFixture.id) { + if (response.id === createRequest.id) { expect(response.result).toEqual('success') - ws.send(JSON.stringify(updateFixture)) + ws.send(JSON.stringify(updateRequest)) } - - if (response.id === updateFixture.id) { + if (response.id === updateRequest.id) { expect(response.result).toEqual('success') ws.close() resolve() @@ -265,7 +263,9 @@ describe('realtime', () => { }) }) - ws.send(JSON.stringify(ethtxFixture)) + const request = createRPCRequest('upsertJobRun', ethtxFixture) + ws.send(JSON.stringify(request)) + await messageReceived const jobRunCount = await db.manager.count(JobRun) @@ -291,21 +291,12 @@ describe('realtime', () => { ws.close() }) - it('rejects invalid json params with code -32602', async (done: any) => { + it('rejects invalid params with code -32602', async (done: any) => { expect.assertions(2) const ws = await authenticatedNode() - - const invalidJSON = { - jsonrpc: '2.0', - method: 'upsertJobRun', - id: 1, - params: { - invalid: 'json', - }, - } - - ws.send(JSON.stringify(invalidJSON)) + const request = createRPCRequest('upsertJobRun', { invalid: 'params' }) + ws.send(JSON.stringify(request)) ws.on('message', async (data: any) => { const response = JSON.parse(data) @@ -324,13 +315,7 @@ describe('realtime', () => { expect.assertions(2) const ws = await authenticatedNode() - - const request = { - jsonrpc: '2.0', - method: 'doesNotExist', - id: 1, - } - + const request = createRPCRequest('doesNotExist', { invalid: 'params' }) ws.send(JSON.stringify(request)) ws.on('message', async (data: any) => { @@ -353,9 +338,7 @@ describe('realtime', () => { expect.assertions(2) const ws = await authenticatedNode() - const request = 'jsonrpc invalid' - ws.send(request) ws.on('message', async (data: any) => { From 2e92d93617a96b8bc8505b07834fbe5ffc063655 Mon Sep 17 00:00:00 2001 From: Ryan Hall Date: Wed, 30 Oct 2019 12:27:37 -0400 Subject: [PATCH 065/199] finish rebase --- explorer/src/__tests__/realtime.test.ts | 1 - explorer/src/server/rpcMethods/index.ts | 6 +- explorer/src/server/rpcServer.ts | 2 +- yarn.lock | 911 ++---------------------- 4 files changed, 64 insertions(+), 856 deletions(-) diff --git a/explorer/src/__tests__/realtime.test.ts b/explorer/src/__tests__/realtime.test.ts index a3965801390..aea1bfc54ac 100644 --- a/explorer/src/__tests__/realtime.test.ts +++ b/explorer/src/__tests__/realtime.test.ts @@ -7,7 +7,6 @@ import { ChainlinkNode, createChainlinkNode } from '../entity/ChainlinkNode' import { JobRun } from '../entity/JobRun' import { TaskRun } from '../entity/TaskRun' import { DEFAULT_TEST_PORT, start, stop } from '../support/server' -import { DEFAULT_TEST_PORT, start as startServer } from '../support/server' import ethtxFixture from './fixtures/JobRun.ethtx.fixture.json' import createFixture from './fixtures/JobRun.fixture.json' import updateFixture from './fixtures/JobRunUpdate.fixture.json' diff --git a/explorer/src/server/rpcMethods/index.ts b/explorer/src/server/rpcMethods/index.ts index 4921bd0e84c..999ad566819 100644 --- a/explorer/src/server/rpcMethods/index.ts +++ b/explorer/src/server/rpcMethods/index.ts @@ -1,5 +1 @@ -import upsertJobRun from './upsertJobRun' - -export default { - upsertJobRun, -} +export { default as upsertJobRun } from './upsertJobRun' diff --git a/explorer/src/server/rpcServer.ts b/explorer/src/server/rpcServer.ts index a5a8fccfd9e..33db0adc3c2 100644 --- a/explorer/src/server/rpcServer.ts +++ b/explorer/src/server/rpcServer.ts @@ -1,5 +1,5 @@ import jayson from 'jayson' -import rpcMethods from './rpcMethods' +import * as rpcMethods from './rpcMethods' const serverOptions = { useContext: true, // permits passing extra data object to RPC methods as 'server context' diff --git a/yarn.lock b/yarn.lock index 34716e97dd9..ad585818a46 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,201 +2,6 @@ # yarn lockfile v1 -"@0x/assert@^2.1.6": - version "2.1.6" - resolved "https://registry.yarnpkg.com/@0x/assert/-/assert-2.1.6.tgz#61c5854b555bca1f1f0503754f2fd0169bee0ef1" - integrity sha512-Gu8eBnFdEuIAH2GubWYOSVz/BIoRccKof68AziduYDHxh4nSPM6NUH52xtfUGk4nXljiOXU1XHZJhcjTObI+8Q== - dependencies: - "@0x/json-schemas" "^4.0.2" - "@0x/typescript-typings" "^4.3.0" - "@0x/utils" "^4.5.2" - lodash "^4.17.11" - valid-url "^1.0.9" - -"@0x/dev-utils@^2.3.3": - version "2.3.3" - resolved "https://registry.yarnpkg.com/@0x/dev-utils/-/dev-utils-2.3.3.tgz#9b6df00fea357fa6da02b35ca93fc89d100e1992" - integrity sha512-Pi664W/jj1U6WU+kHEPyKpflBnmKRsclB69RaL7wpnvOOFjAPhFV2/0FvIZ25w62rMzKEpfD1/1NcZ7NjAk7OQ== - dependencies: - "@0x/subproviders" "^5.0.4" - "@0x/types" "^2.4.3" - "@0x/typescript-typings" "^4.3.0" - "@0x/utils" "^4.5.2" - "@0x/web3-wrapper" "^6.0.13" - "@types/web3-provider-engine" "^14.0.0" - chai "^4.0.1" - ethereum-types "^2.1.6" - lodash "^4.17.11" - -"@0x/json-schemas@^4.0.2": - version "4.0.2" - resolved "https://registry.yarnpkg.com/@0x/json-schemas/-/json-schemas-4.0.2.tgz#6f7c1dcde04d3acc3e8ca2f24177b9705c10e772" - integrity sha512-JHOwESZeWKAzT5Z42ZNvOvQUQ5vuRIFQWS0FNjYwV8Cv4/dRlLHd7kwxxsvlm9NxgXnOW0ddEDBbVGxhVSYNIg== - dependencies: - "@0x/typescript-typings" "^4.3.0" - "@types/node" "*" - jsonschema "^1.2.0" - lodash.values "^4.3.0" - -"@0x/sol-compiler@^3.1.15": - version "3.1.15" - resolved "https://registry.yarnpkg.com/@0x/sol-compiler/-/sol-compiler-3.1.15.tgz#aaaad55008dddd69ad1e3226aa4a2832e0dd13b3" - integrity sha512-IobhcQ/whFRL942/ykKc0fV6/YstHhvnQJ0noUZ9GabMDtaBlW6k5vAerSkXZU/YyOd8sD9nw7QSm295D6HoTA== - dependencies: - "@0x/assert" "^2.1.6" - "@0x/json-schemas" "^4.0.2" - "@0x/sol-resolver" "^2.0.11" - "@0x/types" "^2.4.3" - "@0x/typescript-typings" "^4.3.0" - "@0x/utils" "^4.5.2" - "@0x/web3-wrapper" "^6.0.13" - "@types/yargs" "^11.0.0" - chalk "^2.3.0" - chokidar "^3.0.2" - ethereum-types "^2.1.6" - ethereumjs-util "^5.1.1" - lodash "^4.17.11" - mkdirp "^0.5.1" - pluralize "^7.0.0" - require-from-string "^2.0.1" - semver "5.5.0" - solc "^0.5.5" - source-map-support "^0.5.0" - web3-eth-abi "^1.0.0-beta.24" - yargs "^10.0.3" - -"@0x/sol-resolver@^2.0.11": - version "2.0.11" - resolved "https://registry.yarnpkg.com/@0x/sol-resolver/-/sol-resolver-2.0.11.tgz#282d545a423baf0d478c498fd3adf607339d5504" - integrity sha512-TGvkuWoEMghPB4OZaPz49OJ/J0cw/I3yV7VLbE/OeN0K/J2P8uOkzhNgWLOVSi8EK3hcJ1JVc0iDSkCnzGj4xQ== - dependencies: - "@0x/types" "^2.4.3" - "@0x/typescript-typings" "^4.3.0" - lodash "^4.17.11" - -"@0x/sol-trace@^2.0.20": - version "2.0.20" - resolved "https://registry.yarnpkg.com/@0x/sol-trace/-/sol-trace-2.0.20.tgz#4c14af3f5c30ab50882e9667926ca9533446711d" - integrity sha512-lkw+8l+InqXKoyVFPCxdLA0oZnLLPvmpFm+lIJwEkPjrbwIyhvQbwSwyzPW/1XUWj0wIwWOpq6PSyofWZgt4KA== - dependencies: - "@0x/sol-tracing-utils" "^6.0.19" - "@0x/subproviders" "^5.0.4" - "@0x/typescript-typings" "^4.3.0" - chalk "^2.3.0" - ethereum-types "^2.1.6" - ethereumjs-util "^5.1.1" - lodash "^4.17.11" - loglevel "^1.6.1" - web3-provider-engine "14.0.6" - -"@0x/sol-tracing-utils@^6.0.19": - version "6.0.19" - resolved "https://registry.yarnpkg.com/@0x/sol-tracing-utils/-/sol-tracing-utils-6.0.19.tgz#3c119c7e5b6d2bd1c94663985b7641aec85ef625" - integrity sha512-5tQOEo+dUYWiclT7UDy5IRm/EPEwDzCqAY3J8l0ecIsssBk0r2YLKKf/ugWkdwASGeAxiepjYpC+mxcxOT6yDA== - dependencies: - "@0x/dev-utils" "^2.3.3" - "@0x/sol-compiler" "^3.1.15" - "@0x/sol-resolver" "^2.0.11" - "@0x/subproviders" "^5.0.4" - "@0x/typescript-typings" "^4.3.0" - "@0x/utils" "^4.5.2" - "@0x/web3-wrapper" "^6.0.13" - "@types/solidity-parser-antlr" "^0.2.3" - chalk "^2.3.0" - ethereum-types "^2.1.6" - ethereumjs-util "^5.1.1" - ethers "~4.0.4" - glob "^7.1.2" - istanbul "^0.4.5" - lodash "^4.17.11" - loglevel "^1.6.1" - mkdirp "^0.5.1" - rimraf "^2.6.2" - semaphore-async-await "^1.5.1" - solc "^0.5.5" - solidity-parser-antlr "^0.4.2" - -"@0x/subproviders@^5.0.4": - version "5.0.4" - resolved "https://registry.yarnpkg.com/@0x/subproviders/-/subproviders-5.0.4.tgz#e4b165634ef6a50c4bd41baacf0dbd2a9390c2f8" - integrity sha512-1LiGcOXkP5eUOl/0JRqkrqYtCvIL4NJj1GbbLIRq4TvkfqrRbF7zJM2SaayxPo3Z48zVsqk0ZE5+RrNAdK/Rrg== - dependencies: - "@0x/assert" "^2.1.6" - "@0x/types" "^2.4.3" - "@0x/typescript-typings" "^4.3.0" - "@0x/utils" "^4.5.2" - "@0x/web3-wrapper" "^6.0.13" - "@ledgerhq/hw-app-eth" "^4.3.0" - "@ledgerhq/hw-transport-u2f" "4.24.0" - "@types/hdkey" "^0.7.0" - "@types/web3-provider-engine" "^14.0.0" - bip39 "^2.5.0" - bn.js "^4.11.8" - ethereum-types "^2.1.6" - ethereumjs-tx "^1.3.5" - ethereumjs-util "^5.1.1" - ganache-core "^2.6.0" - hdkey "^0.7.1" - json-rpc-error "2.0.0" - lodash "^4.17.11" - semaphore-async-await "^1.5.1" - web3-provider-engine "14.0.6" - optionalDependencies: - "@ledgerhq/hw-transport-node-hid" "^4.3.0" - -"@0x/types@^2.4.3": - version "2.4.3" - resolved "https://registry.yarnpkg.com/@0x/types/-/types-2.4.3.tgz#ea014889789e9013fdf48ce97b79f2c016e10fb3" - integrity sha512-3z4ca9fb9pyTu9lJhTSll5EuEthkA3tLAayyZixCoCnwi4ok6PJ83PnMMsSxlRY2iXr7QGbrQr6nU64YWk2WjA== - dependencies: - "@types/node" "*" - bignumber.js "~8.0.2" - ethereum-types "^2.1.6" - -"@0x/typescript-typings@^4.3.0": - version "4.3.0" - resolved "https://registry.yarnpkg.com/@0x/typescript-typings/-/typescript-typings-4.3.0.tgz#4813a996ac5101841d1c22f4aa1738ab56168857" - integrity sha512-6IH2JyKyl33+40tJ5rEhaMPTS2mVuRvoNmoXlCd/F0GPYSsDHMGObIXOkx+Qsw5SyCmqNs/3CTLeeCCqiSUdaw== - dependencies: - "@types/bn.js" "^4.11.0" - "@types/react" "*" - bignumber.js "~8.0.2" - ethereum-types "^2.1.6" - popper.js "1.14.3" - -"@0x/utils@^4.5.2": - version "4.5.2" - resolved "https://registry.yarnpkg.com/@0x/utils/-/utils-4.5.2.tgz#6cc89f2d0dda341e0fb4e76049a35abfb67a4ac5" - integrity sha512-NWfNcvyiOhouk662AWxX0ZVe4ednBZJS9WZT/by3DBCY/WvN7WHMpEy9M5rBCxO+JJndLYeB5eBztDp7W+Ytkw== - dependencies: - "@0x/types" "^2.4.3" - "@0x/typescript-typings" "^4.3.0" - "@types/node" "*" - abortcontroller-polyfill "^1.1.9" - bignumber.js "~8.0.2" - chalk "^2.3.0" - detect-node "2.0.3" - ethereum-types "^2.1.6" - ethereumjs-util "^5.1.1" - ethers "~4.0.4" - isomorphic-fetch "2.2.1" - js-sha3 "^0.7.0" - lodash "^4.17.11" - -"@0x/web3-wrapper@^6.0.13": - version "6.0.13" - resolved "https://registry.yarnpkg.com/@0x/web3-wrapper/-/web3-wrapper-6.0.13.tgz#2e666221bd44ceebe02762028214d4aa41ad7247" - integrity sha512-LQjKBCCNdkJuhcJld+/sy+G0+sJu5qp9VDNNwJGLDxWIJpgoshhUpBPi7vUnZ79UY4SYuNcC4yM9yI61cs7ZiA== - dependencies: - "@0x/assert" "^2.1.6" - "@0x/json-schemas" "^4.0.2" - "@0x/typescript-typings" "^4.3.0" - "@0x/utils" "^4.5.2" - ethereum-types "^2.1.6" - ethereumjs-util "^5.1.1" - ethers "~4.0.4" - lodash "^4.17.11" - "@babel/cli@^7.1.5": version "7.6.0" resolved "https://registry.npmjs.org/@babel/cli/-/cli-7.6.0.tgz#1470a04394eaf37862989ea4912adf440fa6ff8d" @@ -1682,75 +1487,6 @@ "@types/istanbul-reports" "^1.1.1" "@types/yargs" "^13.0.0" -"@ledgerhq/devices@^4.72.2": - version "4.72.2" - resolved "https://registry.yarnpkg.com/@ledgerhq/devices/-/devices-4.72.2.tgz#9f91371c6a08cd4d93be0d6fe1c5d53a9ccd4a14" - integrity sha512-iX6ZfxFrKxlO3A7rDypjsqbu1Ta2IB+OsOk/7jnd3fzcvwlSEQwflAHb81CsjOPhdVG2aPIFU/VxzMioDFz24g== - dependencies: - "@ledgerhq/errors" "^4.72.2" - "@ledgerhq/logs" "^4.72.0" - rxjs "^6.5.3" - -"@ledgerhq/errors@^4.72.2": - version "4.72.2" - resolved "https://registry.yarnpkg.com/@ledgerhq/errors/-/errors-4.72.2.tgz#62a87a249c3c05e7295f66c3d49d78e7d46d611f" - integrity sha512-Q9SnjsoIiNhoTujUWfNgGqvYHgaPGRLzuYAC0Rx2WjEKp7swO42BMukYtSgXyyrz4yfHhMBMYerEF2sonBVzXw== - -"@ledgerhq/hw-app-eth@^4.3.0": - version "4.72.2" - resolved "https://registry.yarnpkg.com/@ledgerhq/hw-app-eth/-/hw-app-eth-4.72.2.tgz#64ec0377efa462bc8092cba88b700d11ed51c13f" - integrity sha512-pW2vOX29M5UvjqvEHVQsd3pDskJBcRPtQCSXtknLj4ZlSuCCuZtOhtD4qO3/nEiiHZL0DejTlRB9gl2Qug50lw== - dependencies: - "@ledgerhq/errors" "^4.72.2" - "@ledgerhq/hw-transport" "^4.72.2" - -"@ledgerhq/hw-transport-node-hid-noevents@^4.72.2": - version "4.72.2" - resolved "https://registry.yarnpkg.com/@ledgerhq/hw-transport-node-hid-noevents/-/hw-transport-node-hid-noevents-4.72.2.tgz#3991614bd6793a1bf66a27c9fe50ec23ba2aa5ec" - integrity sha512-yzsN5dDAeZlC3FzYQbCN8yeyWvHzNNRrYQFwtWFDs43ZD2k0p9j1xr5rozAIP9NTzVlAyAow0VCRTQEzUb9QuA== - dependencies: - "@ledgerhq/devices" "^4.72.2" - "@ledgerhq/errors" "^4.72.2" - "@ledgerhq/hw-transport" "^4.72.2" - "@ledgerhq/logs" "^4.72.0" - node-hid "^0.7.9" - -"@ledgerhq/hw-transport-node-hid@^4.3.0": - version "4.72.2" - resolved "https://registry.yarnpkg.com/@ledgerhq/hw-transport-node-hid/-/hw-transport-node-hid-4.72.2.tgz#921b6d5757f31fff6209dba5148a4b74b10c7a4a" - integrity sha512-KGFt/GqJUO8gDTA0HzXckvDcGgvGRA6jFrsRLaRBPmflaVMOjJ7UkeTL9kSLkJCyxFp1YuJ0eZaGIO25dACn0g== - dependencies: - "@ledgerhq/devices" "^4.72.2" - "@ledgerhq/errors" "^4.72.2" - "@ledgerhq/hw-transport" "^4.72.2" - "@ledgerhq/hw-transport-node-hid-noevents" "^4.72.2" - "@ledgerhq/logs" "^4.72.0" - lodash "^4.17.15" - node-hid "^0.7.9" - usb "^1.6.0" - -"@ledgerhq/hw-transport-u2f@4.24.0": - version "4.24.0" - resolved "https://registry.yarnpkg.com/@ledgerhq/hw-transport-u2f/-/hw-transport-u2f-4.24.0.tgz#d67cfc4abf6d9a900ed45f2e2df7fe06dfdff5c7" - integrity sha512-/gFjhkM0sJfZ7iUf8HoIkGufAWgPacrbb1LW0TvWnZwvsATVJ1BZJBtrr90Wo401PKsjVwYtFt3Ce4gOAUv9jQ== - dependencies: - "@ledgerhq/hw-transport" "^4.24.0" - u2f-api "0.2.7" - -"@ledgerhq/hw-transport@^4.24.0", "@ledgerhq/hw-transport@^4.72.2": - version "4.72.2" - resolved "https://registry.yarnpkg.com/@ledgerhq/hw-transport/-/hw-transport-4.72.2.tgz#b13d1797ba7530b9b94ebee71f3d0d2185153988" - integrity sha512-HYe3kTNoA9YHGCsktAzMURpXqZ1QvMUreI24GhMKfec6AIuB0d05avn2j7z8QeOpAUh5L6/OaPPWi9t4KpqG5A== - dependencies: - "@ledgerhq/devices" "^4.72.2" - "@ledgerhq/errors" "^4.72.2" - events "^3.0.0" - -"@ledgerhq/logs@^4.72.0": - version "4.72.0" - resolved "https://registry.yarnpkg.com/@ledgerhq/logs/-/logs-4.72.0.tgz#43df23af013ad1135407e5cf33ca6e4c4c7708d5" - integrity sha512-o+TYF8vBcyySRsb2kqBDv/KMeme8a2nwWoG+lAWzbDmWfb2/MrVWYCVYDYvjXdSoI/Cujqy1i0gIDrkdxa9chA== - "@machinomy/types-truffle-contract@^0.2.0": version "0.2.0" resolved "https://registry.yarnpkg.com/@machinomy/types-truffle-contract/-/types-truffle-contract-0.2.0.tgz#0f91f6f8fa92b8b1e07ba1679027ac14eba1a73f" @@ -2646,7 +2382,7 @@ resolved "https://registry.npmjs.org/@types/bcrypt/-/bcrypt-3.0.0.tgz#851489a9065a067cb7f3c9cbe4ce9bed8bba0876" integrity sha512-nohgNyv+1ViVcubKBh0+XiNJ3dO8nYu///9aJ4cgSqv70gBL+94SNy/iC2NLzKPT2Zt/QavrOkBVbZRLZmw6NQ== -"@types/bn.js@*", "@types/bn.js@^4.11.0", "@types/bn.js@^4.11.5": +"@types/bn.js@*", "@types/bn.js@^4.11.5": version "4.11.5" resolved "https://registry.npmjs.org/@types/bn.js/-/bn.js-4.11.5.tgz#40e36197433f78f807524ec623afcf0169ac81dc" integrity sha512-AEAZcIZga0JgVMHNtl1CprA/hXX7/wPt79AgR4XqaDt7jyj3QWYw6LPoOiznPtugDmlubUnAahMs2PFxGcQrng== @@ -2730,13 +2466,6 @@ resolved "https://registry.yarnpkg.com/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#1ee30d79544ca84d68d4b3cdb0af4f205663dd2d" integrity sha512-OCutwjDZ4aFS6PB1UZ988C4YgwlBHJd6wCeQqaLdmadZ/7e+w79+hbMUFC1QXDNCmdyoRfAFdm0RypzwR+Qpag== -"@types/ethereum-protocol@*": - version "1.0.0" - resolved "https://registry.yarnpkg.com/@types/ethereum-protocol/-/ethereum-protocol-1.0.0.tgz#416e3827d5fdfa4658b0045b35a008747871b271" - integrity sha512-3DiI3Zxf81CgX+VhxNNFJBv/sfr1BFBKQK2sQ85hU9FwWJJMWV5gRDV79OUNShiwj3tYYIezU94qpucsb3dThQ== - dependencies: - bignumber.js "7.2.1" - "@types/ethereumjs-abi@^0.6.3": version "0.6.3" resolved "https://registry.yarnpkg.com/@types/ethereumjs-abi/-/ethereumjs-abi-0.6.3.tgz#eb5ed09fd86b9e2b1c0eb75d1e9bc29c50715c86" @@ -2788,7 +2517,7 @@ resolved "https://registry.yarnpkg.com/@types/fetch-mock/-/fetch-mock-7.3.1.tgz#df7421e8bcb351b430bfbfa5c52bb353826ac94f" integrity sha512-2U4vZWHNbsbK7TRmizgr/pbKe0FKopcxu+hNDtIBDiM1wvrKRItybaYj7VQ6w/hZJStU/JxRiNi5ww4YDEvKbA== -"@types/glob@*", "@types/glob@^7.1.1": +"@types/glob@^7.1.1": version "7.1.1" resolved "https://registry.npmjs.org/@types/glob/-/glob-7.1.1.tgz#aa59a1c6e3fbc421e07ccd31a944c30eba521575" integrity sha512-1Bh06cbWJUHMC97acuD6UMG29nMt0Aqz1vF3guLfG+kHHJhy3AyohZFFxYk2f7Q1SQIrNwvncxAE0N/9s70F2w== @@ -2797,13 +2526,6 @@ "@types/minimatch" "*" "@types/node" "*" -"@types/hdkey@^0.7.0": - version "0.7.1" - resolved "https://registry.yarnpkg.com/@types/hdkey/-/hdkey-0.7.1.tgz#9bc63ebbe96b107b277b65ea7a95442a677d0d61" - integrity sha512-4Kkr06hq+R8a9EzVNqXGOY2x1xA7dhY6qlp6OvaZ+IJy1BCca1Cv126RD9X7CMJoXoLo8WvAizy8gQHpqW6K0Q== - dependencies: - "@types/node" "*" - "@types/helmet@0.0.43": version "0.0.43" resolved "https://registry.npmjs.org/@types/helmet/-/helmet-0.0.43.tgz#2a3f7da435088501f2901f68c3e402f79e5b2a9b" @@ -3137,19 +2859,6 @@ "@types/express-serve-static-core" "*" "@types/mime" "*" -"@types/shelljs@^0.8.5": - version "0.8.5" - resolved "https://registry.yarnpkg.com/@types/shelljs/-/shelljs-0.8.5.tgz#1e507b2f6d1f893269bd3e851ec24419ef9beeea" - integrity sha512-bZgjwIWu9gHCjirKJoOlLzGi5N0QgZ5t7EXEuoqyWCHTuSddURXo3FOBYDyRPNOWzZ6NbkLvZnVkn483Y/tvcQ== - dependencies: - "@types/glob" "*" - "@types/node" "*" - -"@types/solidity-parser-antlr@^0.2.3": - version "0.2.3" - resolved "https://registry.yarnpkg.com/@types/solidity-parser-antlr/-/solidity-parser-antlr-0.2.3.tgz#bb2d9c6511bf483afe4fc3e2714da8a924e59e3f" - integrity sha512-FoSyZT+1TTaofbEtGW1oC9wHND1YshvVeHerME/Jh6gIdHbBAWFW8A97YYqO/dpHcFjIwEPEepX0Efl2ckJgwA== - "@types/stack-utils@^1.0.1": version "1.0.1" resolved "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-1.0.1.tgz#0a851d3bd96498fa25c33ab7278ed3bd65f06c3e" @@ -3212,13 +2921,6 @@ resolved "https://registry.npmjs.org/@types/validator/-/validator-10.11.2.tgz#48b60ca2cca927081f37a1ad1de3e25d04abc9f0" integrity sha512-k/ju1RsdP5ACFUWebqsyEy0avP5uNJCs2p3pmTHzOZdd4gMSAJTq7iUEHFY3tt3emBrPTm6oGvfZ4SzcqOgLPQ== -"@types/web3-provider-engine@^14.0.0": - version "14.0.0" - resolved "https://registry.yarnpkg.com/@types/web3-provider-engine/-/web3-provider-engine-14.0.0.tgz#43adc3b39dc9812b82aef8cd2d66577665ad59b0" - integrity sha512-yHr8mX2SoX3JNyfqdLXdO1UobsGhfiwSgtekbVxKLQrzD7vtpPkKbkIVsPFOhvekvNbPsCmDyeDCLkpeI9gSmA== - dependencies: - "@types/ethereum-protocol" "*" - "@types/web3@^1.0.0", "@types/web3@^1.0.19": version "1.0.19" resolved "https://registry.npmjs.org/@types/web3/-/web3-1.0.19.tgz#46b85d91d398ded9ab7c85a5dd57cb33ac558924" @@ -3244,11 +2946,6 @@ resolved "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-13.0.0.tgz#453743c5bbf9f1bed61d959baab5b06be029b2d0" integrity sha512-wBlsw+8n21e6eTd4yVv8YD/E3xq0O6nNnJIquutAsFGE7EyMKz7W6RNT6BRu1SmdgmlCZ9tb0X+j+D6HGr8pZw== -"@types/yargs@^11.0.0": - version "11.1.3" - resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-11.1.3.tgz#33c8ebf05f78f1edeb249c1cde1a42ae57f5664e" - integrity sha512-moBUF6X8JsK5MbLZGP3vCfG/TVHZHsaePj3EimlLKp8+ESUjGjpXalxyn90a2L9fTM2ZGtW4swb6Am1DvVRNGA== - "@types/yargs@^12.0.10": version "12.0.12" resolved "https://registry.npmjs.org/@types/yargs/-/yargs-12.0.12.tgz#45dd1d0638e8c8f153e87d296907659296873916" @@ -3555,11 +3252,6 @@ abbrev@1: resolved "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q== -abbrev@1.0.x: - version "1.0.9" - resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.0.9.tgz#91b4792588a7738c25f35dd6f63752a2f8776135" - integrity sha1-kbR5JYinc4wl813W9jdSovh3YTU= - abort-controller@^3.0.0: version "3.0.0" resolved "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz#eaf54d53b62bae4138e809ca225c8439a6efb392" @@ -3567,11 +3259,6 @@ abort-controller@^3.0.0: dependencies: event-target-shim "^5.0.0" -abortcontroller-polyfill@^1.1.9: - version "1.3.0" - resolved "https://registry.yarnpkg.com/abortcontroller-polyfill/-/abortcontroller-polyfill-1.3.0.tgz#de69af32ae926c210b7efbcc29bf644ee4838b00" - integrity sha512-lbWQgf+eRvku3va8poBlDBO12FigTQr9Zb7NIjXrePrhxWVKdCP2wbDl1tLDaYa18PWTom3UEWwdH13S46I+yA== - abstract-leveldown@3.0.0: version "3.0.0" resolved "https://registry.npmjs.org/abstract-leveldown/-/abstract-leveldown-3.0.0.tgz#5cb89f958a44f526779d740d1440e743e0c30a57" @@ -3646,16 +3333,11 @@ acorn-globals@^4.3.0: acorn "^6.0.1" acorn-walk "^6.0.1" -acorn-jsx@^5.0.0: +acorn-jsx@^5.0.0, acorn-jsx@^5.0.2: version "5.0.2" resolved "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.0.2.tgz#84b68ea44b373c4f8686023a551f61a21b7c4a4f" integrity sha512-tiNTrP1MP0QrChmD2DdupCr6HWSFeKVw5d/dHTu4Y7rkAkRhU/Dt7dphAfIUyxtHpl/eBVip5uTNSpQJHylpAw== -acorn-jsx@^5.0.2: - version "5.1.0" - resolved "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.1.0.tgz#294adb71b57398b0680015f0a38c563ee1db5384" - integrity sha512-tMUqwBWfLFbJbizRmEcWSLw6HnFzfdJs2sOJEOwwtVPMoH/0Ay+E703oZz78VSXZiiDcZrQ5XKjPIUQixhmgVw== - acorn-walk@^6.0.1, acorn-walk@^6.1.1: version "6.2.0" resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-6.2.0.tgz#123cb8f3b84c2171f1f7fb252615b1c78a6b1a8c" @@ -3915,14 +3597,6 @@ anymatch@^2.0.0: micromatch "^3.1.4" normalize-path "^2.1.1" -anymatch@~3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.1.tgz#c55ecf02185e2469259399310c173ce31233b142" - integrity sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg== - dependencies: - normalize-path "^3.0.0" - picomatch "^2.0.4" - app-module-path@^2.2.0: version "2.2.0" resolved "https://registry.npmjs.org/app-module-path/-/app-module-path-2.2.0.tgz#641aa55dfb7d6a6f0a8141c4b9c0aa50b6c24dd5" @@ -4319,11 +3993,6 @@ async-settle@^1.0.0: dependencies: async-done "^1.2.2" -async@1.x, async@^1.4.2, async@^1.5.2: - version "1.5.2" - resolved "https://registry.npmjs.org/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a" - integrity sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo= - async@2.6.1: version "2.6.1" resolved "https://registry.yarnpkg.com/async/-/async-2.6.1.tgz#b245a23ca71930044ec53fa46aa00a3e87c6a610" @@ -4338,6 +4007,11 @@ async@2.6.2: dependencies: lodash "^4.17.11" +async@^1.4.2, async@^1.5.2: + version "1.5.2" + resolved "https://registry.npmjs.org/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a" + integrity sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo= + async@^2.0.1, async@^2.1.2, async@^2.1.4, async@^2.3.0, async@^2.4.0, async@^2.5.0, async@^2.6.1, async@~2.6.0: version "2.6.3" resolved "https://registry.npmjs.org/async/-/async-2.6.3.tgz#d72625e2344a3656e3a3ad4fa749fa83299d82ff" @@ -5411,7 +5085,7 @@ big.js@^5.2.2: resolved "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328" integrity sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ== -bignumber.js@7.2.1, bignumber.js@^7.2.1: +bignumber.js@^7.2.1: version "7.2.1" resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-7.2.1.tgz#80c048759d826800807c4bfd521e50edbba57a5f" integrity sha512-S4XzBk5sMB+Rcb/LNcpzXr57VRTxgAvaAEDAl1AwRx27j00hT84O6OkteE7u8UB3NuaaygCRrEpqox4uDOrbdQ== @@ -5426,22 +5100,12 @@ bignumber.js@^9.0.0: resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-9.0.0.tgz#805880f84a329b5eac6e7cb6f8274b6d82bdf075" integrity sha512-t/OYhhJ2SD+YGBQcjY8GzzDHEk9f3nerxjtfa6tlMXfe7frs/WozhvCNoGvpM0P3bNf3Gq5ZRMlGr5f3r4/N8A== -bignumber.js@~8.0.2: - version "8.0.2" - resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-8.0.2.tgz#d8c4e1874359573b1ef03011a2d861214aeef137" - integrity sha512-EiuvFrnbv0jFixEQ9f58jo7X0qI2lNGIr/MxntmVzQc5JUweDSh8y8hbTCAomFtqwUPIOWcLXP0VEOSZTG7FFw== - binary-extensions@^1.0.0: version "1.13.1" resolved "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz#598afe54755b2868a5330d2aff9d4ebb53209b65" integrity sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw== -binary-extensions@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.0.0.tgz#23c0df14f6a88077f5f986c0d167ec03c3d5537c" - integrity sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow== - -bindings@^1.2.1, bindings@^1.3.0, bindings@^1.3.1, bindings@^1.4.0, bindings@^1.5.0: +bindings@^1.2.1, bindings@^1.3.0, bindings@^1.3.1: version "1.5.0" resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.5.0.tgz#10353c9e945334bc0511a6d90b38fbc7c9c504df" integrity sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ== @@ -5472,17 +5136,6 @@ bip39@2.5.0: safe-buffer "^5.0.1" unorm "^1.3.3" -bip39@^2.5.0: - version "2.6.0" - resolved "https://registry.yarnpkg.com/bip39/-/bip39-2.6.0.tgz#9e3a720b42ec8b3fbe4038f1e445317b6a99321c" - integrity sha512-RrnQRG2EgEoqO24ea+Q/fftuPUZLmrEM3qNhhGsA3PbaXaCW791LTzPuVyx/VprXQcTbPJ3K3UeTna8ZnVl2sg== - dependencies: - create-hash "^1.1.0" - pbkdf2 "^3.0.9" - randombytes "^2.0.1" - safe-buffer "^5.0.1" - unorm "^1.3.3" - bip39@^3.0.2: version "3.0.2" resolved "https://registry.npmjs.org/bip39/-/bip39-3.0.2.tgz#2baf42ff3071fc9ddd5103de92e8f80d9257ee32" @@ -5508,13 +5161,6 @@ bl@^1.0.0: readable-stream "^2.3.5" safe-buffer "^5.1.1" -bl@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/bl/-/bl-3.0.0.tgz#3611ec00579fd18561754360b21e9f784500ff88" - integrity sha512-EUAyP5UHU5hxF8BPT0LKW8gjYLhq1DQIcneOX/pL/m2Alo+OYDQAJlHq+yseMP50Os2nHXOSic6Ss3vSQeyf4A== - dependencies: - readable-stream "^3.0.1" - blob@0.0.5: version "0.0.5" resolved "https://registry.npmjs.org/blob/-/blob-0.0.5.tgz#d680eeef25f8cd91ad533f5b01eed48e64caf683" @@ -5676,7 +5322,7 @@ braces@^2.3.1, braces@^2.3.2: split-string "^3.0.2" to-regex "^3.0.1" -braces@^3.0.1, braces@~3.0.2: +braces@^3.0.1: version "3.0.2" resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== @@ -6266,7 +5912,7 @@ chai-bn@^0.1.1: resolved "https://registry.npmjs.org/chai-bn/-/chai-bn-0.1.1.tgz#a8904b2dc878e5c094881f327c0029579ff2062b" integrity sha512-e1npVXt3cQfZ6oQET9oP38vNj/4HeJ4ojeUpuC8YzhVbTJpIDqANVt7TKi7Dq9yKlHySk2FqbmiMih35iT4DYg== -chai@^4.0.1, chai@^4.2.0: +chai@^4.2.0: version "4.2.0" resolved "https://registry.npmjs.org/chai/-/chai-4.2.0.tgz#760aa72cf20e3795e84b12877ce0e83737aa29e5" integrity sha512-XQU3bhBukrOsQCuwZndwGcCVQHyZi53fQ6Ys1Fym7E4olpIqqZZhhoFJoaKVvV17lWQoXYwgWN2nF5crA8J2jw== @@ -6290,7 +5936,7 @@ chainlink@0.6.1: chainlink@0.7.5: version "0.7.5" - resolved "https://registry.yarnpkg.com/chainlink/-/chainlink-0.7.5.tgz#82043e99b8477dac8478a1707d217d0ef716b28d" + resolved "https://registry.npmjs.org/chainlink/-/chainlink-0.7.5.tgz#82043e99b8477dac8478a1707d217d0ef716b28d" integrity sha512-UFwFuF5EdxVIBj6lSUmDsEUjnM+hOefelKTZ5vfjPEfUJCq4KAw3Ew1JJYsssmceaOYWcg4iL3A5Q40MnTpmIg== dependencies: "@ensdomains/ens" "^0.1.1" @@ -6466,21 +6112,6 @@ chokidar@^2.0.2, chokidar@^2.0.4, chokidar@^2.1.6, chokidar@^2.1.8: optionalDependencies: fsevents "^1.2.7" -chokidar@^3.0.2: - version "3.2.2" - resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.2.2.tgz#a433973350021e09f2b853a2287781022c0dc935" - integrity sha512-bw3pm7kZ2Wa6+jQWYP/c7bAZy3i4GwiIiMO2EeRjrE48l8vBqC/WvFhSF0xyM8fQiPEGvwMY/5bqDG7sSEOuhg== - dependencies: - anymatch "~3.1.1" - braces "~3.0.2" - glob-parent "~5.1.0" - is-binary-path "~2.1.0" - is-glob "~4.0.1" - normalize-path "~3.0.0" - readdirp "~3.2.0" - optionalDependencies: - fsevents "~2.1.1" - chownr@^1.0.1: version "1.1.1" resolved "https://registry.npmjs.org/chownr/-/chownr-1.1.1.tgz#54726b8b8fff4df053c42187e801fb4412df1494" @@ -7801,7 +7432,7 @@ debug@3.2.6, debug@^3.0.1, debug@^3.1.0, debug@^3.2.5, debug@^3.2.6: dependencies: ms "^2.1.1" -debug@4.1.1, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@~4.1.0: +debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@~4.1.0: version "4.1.1" resolved "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791" integrity sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw== @@ -7832,13 +7463,6 @@ decompress-response@^3.2.0, decompress-response@^3.3.0: dependencies: mimic-response "^1.0.0" -decompress-response@^4.2.0: - version "4.2.1" - resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-4.2.1.tgz#414023cc7a302da25ce2ec82d0d5238ccafd8986" - integrity sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw== - dependencies: - mimic-response "^2.0.0" - decompress-tar@^4.0.0, decompress-tar@^4.1.0, decompress-tar@^4.1.1: version "4.1.1" resolved "https://registry.npmjs.org/decompress-tar/-/decompress-tar-4.1.1.tgz#718cbd3fcb16209716e70a26b84e7ba4592e5af1" @@ -8134,11 +7758,6 @@ detect-newline@^2.1.0: resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-2.1.0.tgz#f41f1c10be4b00e87b5f13da680759f2c5bfd3e2" integrity sha1-9B8cEL5LAOh7XxPaaAdZ8sW/0+I= -detect-node@2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.0.3.tgz#a2033c09cc8e158d37748fbde7507832bd6ce127" - integrity sha1-ogM8CcyOFY03dI+951B4Mr1s4Sc= - detect-node@^2.0.4: version "2.0.4" resolved "https://registry.npmjs.org/detect-node/-/detect-node-2.0.4.tgz#014ee8f8f669c5c58023da64b8179c083a28c46c" @@ -8650,13 +8269,6 @@ end-of-stream@^1.0.0, end-of-stream@^1.1.0: dependencies: once "^1.4.0" -end-of-stream@^1.4.1: - version "1.4.4" - resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" - integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== - dependencies: - once "^1.4.0" - engine.io-client@~3.3.1: version "3.3.2" resolved "https://registry.npmjs.org/engine.io-client/-/engine.io-client-3.3.2.tgz#04e068798d75beda14375a264bb3d742d7bc33aa" @@ -8955,18 +8567,6 @@ escape-string-regexp@1.0.5, escape-string-regexp@^1.0.2, escape-string-regexp@^1 resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= -escodegen@1.8.x: - version "1.8.1" - resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.8.1.tgz#5a5b53af4693110bebb0867aa3430dd3b70a1018" - integrity sha1-WltTr0aTEQvrsIZ6o0MN07cKEBg= - dependencies: - esprima "^2.7.1" - estraverse "^1.9.1" - esutils "^2.0.2" - optionator "^0.8.1" - optionalDependencies: - source-map "~0.2.0" - escodegen@^1.11.0, escodegen@^1.6.1: version "1.12.0" resolved "https://registry.npmjs.org/escodegen/-/escodegen-1.12.0.tgz#f763daf840af172bb3a2b6dd7219c0e17f7ff541" @@ -9257,11 +8857,6 @@ esprima-fb@^15001.1.0-dev-harmony-fb: resolved "https://registry.yarnpkg.com/esprima-fb/-/esprima-fb-15001.1.0-dev-harmony-fb.tgz#30a947303c6b8d5e955bee2b99b1d233206a6901" integrity sha1-MKlHMDxrjV6VW+4rmbHSMyBqaQE= -esprima@2.7.x, esprima@^2.7.1: - version "2.7.3" - resolved "https://registry.yarnpkg.com/esprima/-/esprima-2.7.3.tgz#96e3b70d5779f6ad49cd032673d1c312767ba581" - integrity sha1-luO3DVd59q1JzQMmc9HDEnZ7pYE= - esprima@^3.1.3, esprima@~3.1.0: version "3.1.3" resolved "https://registry.yarnpkg.com/esprima/-/esprima-3.1.3.tgz#fdca51cee6133895e3c88d535ce49dbff62a4633" @@ -9286,11 +8881,6 @@ esrecurse@^4.1.0: dependencies: estraverse "^4.1.0" -estraverse@^1.9.1: - version "1.9.3" - resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-1.9.3.tgz#af67f2dc922582415950926091a4005d29c9bb44" - integrity sha1-r2fy3JIlgkFZUJJgkaQAXSnJu0Q= - estraverse@^4.0.0, estraverse@^4.2.0: version "4.2.0" resolved "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz#0dee3fed31fcd469618ce7342099fc1afa0bdb13" @@ -9455,14 +9045,6 @@ ethereum-common@^0.0.18: resolved "https://registry.npmjs.org/ethereum-common/-/ethereum-common-0.0.18.tgz#2fdc3576f232903358976eb39da783213ff9523f" integrity sha1-L9w1dvIykDNYl26znaeDIT/5Uj8= -ethereum-types@^2.1.6: - version "2.1.6" - resolved "https://registry.yarnpkg.com/ethereum-types/-/ethereum-types-2.1.6.tgz#57d9d515fad86ab987c0f6962c4203be37da8579" - integrity sha512-xaN5TxLvkdFCGjGfUQ5wV00GHzDHStozP1j+K/YdmUeQXVGiD15cogYPhBVWG3pQJM/aBjtYrpMrjywvKkNC4A== - dependencies: - "@types/node" "*" - bignumber.js "~8.0.2" - ethereumjs-abi@0.6.5: version "0.6.5" resolved "https://registry.npmjs.org/ethereumjs-abi/-/ethereumjs-abi-0.6.5.tgz#5a637ef16ab43473fa72a29ad90871405b3f5241" @@ -9578,7 +9160,7 @@ ethereumjs-testrpc@^6.0.3: dependencies: webpack "^3.0.0" -ethereumjs-tx@1.3.7, ethereumjs-tx@^1.1.1, ethereumjs-tx@^1.2.0, ethereumjs-tx@^1.2.2, ethereumjs-tx@^1.3.3, ethereumjs-tx@^1.3.5: +ethereumjs-tx@1.3.7, ethereumjs-tx@^1.1.1, ethereumjs-tx@^1.2.0, ethereumjs-tx@^1.2.2, ethereumjs-tx@^1.3.3: version "1.3.7" resolved "https://registry.npmjs.org/ethereumjs-tx/-/ethereumjs-tx-1.3.7.tgz#88323a2d875b10549b8347e09f4862b546f3d89a" integrity sha512-wvLMxzt1RPhAQ9Yi3/HKZTn0FZYpnsmQdbKYfUUpi4j1SEIcbkd9tndVjcPrufY3V7j2IebOpC00Zp2P/Ay2kA== @@ -9742,22 +9324,6 @@ ethers@^4.0.0-beta.1, ethers@^4.0.32, ethers@^4.0.36, ethers@^4.0.37: uuid "2.0.1" xmlhttprequest "1.8.0" -ethers@~4.0.4: - version "4.0.38" - resolved "https://registry.yarnpkg.com/ethers/-/ethers-4.0.38.tgz#3fabafb4f79c435205b143b66b4a1af035043e37" - integrity sha512-l7l7RIfk2/rIFgRRVLFY3H06S9dhXXPUdMlYm6SCelB6oG+ABmoRig7xSVOLcHLayBfSwssjAAYLKxf1jWhbuQ== - dependencies: - "@types/node" "^10.3.2" - aes-js "3.0.0" - bn.js "^4.4.0" - elliptic "6.3.3" - hash.js "1.1.3" - js-sha3 "0.5.7" - scrypt-js "2.0.4" - setimmediate "1.0.4" - uuid "2.0.1" - xmlhttprequest "1.8.0" - ethjs-abi@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/ethjs-abi/-/ethjs-abi-0.2.1.tgz#e0a7a93a7e81163a94477bad56ede524ab6de533" @@ -9895,22 +9461,6 @@ execa@^1.0.0: signal-exit "^3.0.0" strip-eof "^1.0.0" -execa@^3.2.0: - version "3.2.0" - resolved "https://registry.npmjs.org/execa/-/execa-3.2.0.tgz#18326b79c7ab7fbd6610fd900c1b9e95fa48f90a" - integrity sha512-kJJfVbI/lZE1PZYDI5VPxp8zXPO9rtxOkhpZ0jMKha56AI9y2gGVC6bkukStQf0ka5Rh15BA5m7cCCH4jmHqkw== - dependencies: - cross-spawn "^7.0.0" - get-stream "^5.0.0" - human-signals "^1.1.1" - is-stream "^2.0.0" - merge-stream "^2.0.0" - npm-run-path "^4.0.0" - onetime "^5.1.0" - p-finally "^2.0.0" - signal-exit "^3.0.2" - strip-final-newline "^2.0.0" - executable@4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/executable/-/executable-4.1.1.tgz#41532bff361d3e57af4d763b70582db18f5d133c" @@ -10873,11 +10423,6 @@ fsevents@^1.2.7: nan "^2.12.1" node-pre-gyp "^0.12.0" -fsevents@~2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.1.1.tgz#74c64e21df71721845d0c44fe54b7f56b82995a9" - integrity sha512-4FRPXWETxtigtJW/gxzEDsX1LVbPAM93VleB83kZB+ellqbHMkyt2aJfuzNLRvFPnGi6bcE5SvfxgbXPeKteJw== - fstream@^1.0.12, fstream@^1.0.8: version "1.0.12" resolved "https://registry.yarnpkg.com/fstream/-/fstream-1.0.12.tgz#4e8ba8ee2d48be4f7d0de505455548eae5932045" @@ -10927,7 +10472,7 @@ ganache-cli@^6.1.0: source-map-support "0.5.9" yargs "11.1.0" -ganache-core@^2.6.0, ganache-core@^2.8.0: +ganache-core@^2.8.0: version "2.8.0" resolved "https://registry.npmjs.org/ganache-core/-/ganache-core-2.8.0.tgz#eeadc7f7fc3a0c20d99f8f62021fb80b5a05490c" integrity sha512-hfXqZGJx700jJqwDHNXrU2BnPYuETn1ekm36oRHuXY3oOuJLFs5C+cFdUFaBlgUxcau1dOgZUUwKqTAy0gTA9Q== @@ -11031,7 +10576,7 @@ get-stream@^4.0.0, get-stream@^4.1.0: dependencies: pump "^3.0.0" -get-stream@^5.0.0, get-stream@^5.1.0: +get-stream@^5.1.0: version "5.1.0" resolved "https://registry.npmjs.org/get-stream/-/get-stream-5.1.0.tgz#01203cdc92597f9b909067c3e656cc1f4d3c4dc9" integrity sha512-EXr1FOzrzTfGeL0gQdeFEvOMm2mzMOglyiOXSTpPC+iAjAKftbr3jpCMWynogwYnM+eSj9sHGc6wjIcDvYiygw== @@ -11098,7 +10643,7 @@ glob-parent@^3.1.0: is-glob "^3.1.0" path-dirname "^1.0.0" -glob-parent@^5.0.0, glob-parent@~5.1.0: +glob-parent@^5.0.0: version "5.1.0" resolved "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.0.tgz#5f4c1d1e748d30cd73ad2944b3577a81b081e8c2" integrity sha512-qjtRgnIVmOfnKUE3NJAQEdk+lKrxfw8t5ke7SXtfMTHcjsBfOfWXCQfdb30zfDoZQ2IRSIiidmjtbHZPZ++Ihw== @@ -11505,17 +11050,6 @@ handle-thing@^2.0.0: resolved "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.0.tgz#0e039695ff50c93fc288557d696f3c1dc6776754" integrity sha512-d4sze1JNC454Wdo2fkuyzCr6aHcbL6PGGuFAz0Li/NcOm1tCHGnWDRmJP85dh9IhQErTc2svWFEX5xHIOo//kQ== -handlebars@^4.0.1: - version "4.4.3" - resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.4.3.tgz#180bae52c1d0e9ec0c15d7e82a4362d662762f6e" - integrity sha512-B0W4A2U1ww3q7VVthTKfh+epHx+q4mCt6iK+zEAzbMBpWQAwxCeKxEGpj/1oQTpzPXDNSOG7hmG14TsISH50yw== - dependencies: - neo-async "^2.6.0" - optimist "^0.6.1" - source-map "^0.6.1" - optionalDependencies: - uglify-js "^3.1.4" - handlebars@^4.1.2: version "4.1.2" resolved "https://registry.npmjs.org/handlebars/-/handlebars-4.1.2.tgz#b6b37c1ced0306b221e094fc7aca3ec23b131b67" @@ -11564,11 +11098,6 @@ has-cors@1.1.0: resolved "https://registry.npmjs.org/has-cors/-/has-cors-1.1.0.tgz#5e474793f7ea9843d1bb99c23eef49ff126fff39" integrity sha1-XkdHk/fqmEPRu5nCPu9J/xJv/zk= -has-flag@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-1.0.0.tgz#9d9e793165ce017a00f00418c43f942a7b1d11fa" - integrity sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo= - has-flag@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz#e8207af1cc7b30d446cc70b734b5e8be18f88d51" @@ -11678,14 +11207,6 @@ hastscript@^5.0.0: property-information "^5.0.1" space-separated-tokens "^1.0.0" -hdkey@^0.7.1: - version "0.7.1" - resolved "https://registry.yarnpkg.com/hdkey/-/hdkey-0.7.1.tgz#caee4be81aa77921e909b8d228dd0f29acaee632" - integrity sha1-yu5L6BqneSHpCbjSKN0PKayu5jI= - dependencies: - coinstring "^2.0.0" - secp256k1 "^3.0.1" - hdkey@^1.1.0: version "1.1.1" resolved "https://registry.npmjs.org/hdkey/-/hdkey-1.1.1.tgz#c2b3bfd5883ff9529b72f2f08b28be0972a9f64a" @@ -12085,11 +11606,6 @@ https-proxy-agent@^2.2.1: agent-base "^4.1.0" debug "^3.1.0" -human-signals@^1.1.1: - version "1.1.1" - resolved "https://registry.npmjs.org/human-signals/-/human-signals-1.1.1.tgz#c5b1cd14f50aeae09ab6c59fe63ba3395fe4dfa3" - integrity sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw== - humps@^2.0.1: version "2.0.1" resolved "https://registry.npmjs.org/humps/-/humps-2.0.1.tgz#dd02ea6081bd0568dc5d073184463957ba9ef9aa" @@ -12541,13 +12057,6 @@ is-binary-path@^1.0.0: dependencies: binary-extensions "^1.0.0" -is-binary-path@~2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" - integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== - dependencies: - binary-extensions "^2.0.0" - is-boolean-object@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.0.0.tgz#98f8b28030684219a95f375cfbd88ce3405dff93" @@ -12736,7 +12245,7 @@ is-glob@^3.1.0: dependencies: is-extglob "^2.1.0" -is-glob@^4.0.0, is-glob@^4.0.1, is-glob@~4.0.1: +is-glob@^4.0.0, is-glob@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.1.tgz#7567dbe9f2f5e2467bc77ab83c4a29482407a5dc" integrity sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg== @@ -12961,11 +12470,6 @@ is-stream@^1.0.0, is-stream@^1.0.1, is-stream@^1.1.0: resolved "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ= -is-stream@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz#bde9c32680d6fae04129d6ac9d921ce7815f78e3" - integrity sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw== - is-string@^1.0.4: version "1.0.4" resolved "https://registry.npmjs.org/is-string/-/is-string-1.0.4.tgz#cc3a9b69857d621e963725a24caeec873b826e64" @@ -13071,7 +12575,7 @@ isobject@^4.0.0: resolved "https://registry.npmjs.org/isobject/-/isobject-4.0.0.tgz#3f1c9155e73b192022a80819bacd0343711697b0" integrity sha512-S/2fF5wH8SJA/kmwr6HYhK/RI/OkhD84k8ntalo0iJjZikgq1XFvR5M8NPT1x5F7fBwCG3qHfnzeP/Vh/ZxCUA== -isomorphic-fetch@2.2.1, isomorphic-fetch@^2.1.1: +isomorphic-fetch@^2.1.1: version "2.2.1" resolved "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz#611ae1acf14f5e81f729507472819fe9733558a9" integrity sha1-YRrhrPFPXoH3KVB0coGf6XM1WKk= @@ -13155,26 +12659,6 @@ istanbul-reports@^2.2.6: dependencies: handlebars "^4.1.2" -istanbul@^0.4.5: - version "0.4.5" - resolved "https://registry.yarnpkg.com/istanbul/-/istanbul-0.4.5.tgz#65c7d73d4c4da84d4f3ac310b918fb0b8033733b" - integrity sha1-ZcfXPUxNqE1POsMQuRj7C4Azczs= - dependencies: - abbrev "1.0.x" - async "1.x" - escodegen "1.8.x" - esprima "2.7.x" - glob "^5.0.15" - handlebars "^4.0.1" - js-yaml "3.x" - mkdirp "0.5.x" - nopt "3.x" - once "1.x" - resolve "1.1.x" - supports-color "^3.1.0" - which "^1.1.1" - wordwrap "^1.0.0" - isurl@^1.0.0-alpha5: version "1.0.0" resolved "https://registry.npmjs.org/isurl/-/isurl-1.0.0.tgz#b27f4f49f3cdaa3ea44a0a5b7f3462e6edc39d67" @@ -13660,11 +13144,6 @@ js-sha3@^0.6.1: resolved "https://registry.npmjs.org/js-sha3/-/js-sha3-0.6.1.tgz#5b89f77a7477679877f58c4a075240934b1f95c0" integrity sha1-W4n3enR3Z5h39YxKB1JAk0sflcA= -js-sha3@^0.7.0: - version "0.7.0" - resolved "https://registry.yarnpkg.com/js-sha3/-/js-sha3-0.7.0.tgz#0a5c57b36f79882573b2d84051f8bb85dd1bd63a" - integrity sha512-Wpks3yBDm0UcL5qlVhwW9Jr9n9i4FfeWBFOOXP5puDS/SiudJGhw7DPyBqn3487qD4F0lsC0q3zxink37f7zeA== - "js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: version "4.0.0" resolved "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" @@ -13675,14 +13154,6 @@ js-tokens@^3.0.2: resolved "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b" integrity sha1-mGbfOVECEw449/mWvOtlRDIJwls= -js-yaml@3.x, js-yaml@^3.13.0, js-yaml@^3.13.1, js-yaml@^3.4.2: - version "3.13.1" - resolved "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz#aff151b30bfdfa8e49e05da22e7415e9dfa37847" - integrity sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw== - dependencies: - argparse "^1.0.7" - esprima "^4.0.0" - js-yaml@^3.12.0: version "3.13.0" resolved "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.0.tgz#38ee7178ac0eea2c97ff6d96fff4b18c7d8cf98e" @@ -13691,6 +13162,14 @@ js-yaml@^3.12.0: argparse "^1.0.7" esprima "^4.0.0" +js-yaml@^3.13.0, js-yaml@^3.13.1, js-yaml@^3.4.2: + version "3.13.1" + resolved "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz#aff151b30bfdfa8e49e05da22e7415e9dfa37847" + integrity sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw== + dependencies: + argparse "^1.0.7" + esprima "^4.0.0" + jsbi@^3.1.1: version "3.1.1" resolved "https://registry.npmjs.org/jsbi/-/jsbi-3.1.1.tgz#8ea18b3e08d102c6cc09acaa9a099921d775f4fa" @@ -13844,7 +13323,7 @@ json-rpc-engine@^3.4.0, json-rpc-engine@^3.6.0: promise-to-callback "^1.0.0" safe-event-emitter "^1.0.1" -json-rpc-error@2.0.0, json-rpc-error@^2.0.0: +json-rpc-error@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/json-rpc-error/-/json-rpc-error-2.0.0.tgz#a7af9c202838b5e905c7250e547f1aff77258a02" integrity sha1-p6+cICg4tekFxyUOVH8a/3cligI= @@ -13946,11 +13425,6 @@ jsonify@~0.0.0: resolved "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73" integrity sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM= -jsonschema@^1.2.0: - version "1.2.4" - resolved "https://registry.yarnpkg.com/jsonschema/-/jsonschema-1.2.4.tgz#a46bac5d3506a254465bc548876e267c6d0d6464" - integrity sha512-lz1nOH69GbsVHeVgEdvyavc/33oymY1AZwtePMiMj4HZPMbP5OIKK3zT9INMWjwua/V4Z4yq7wSlBbSG+g4AEw== - jsonwebtoken@^8.5.1: version "8.5.1" resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz#00e71e0b8df54c2121a1f26137df2280673bcc0d" @@ -14724,11 +14198,6 @@ lodash.uniq@^4.5.0: resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M= -lodash.values@^4.3.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/lodash.values/-/lodash.values-4.3.0.tgz#a3a6c2b0ebecc5c2cba1c17e6e620fe81b53d347" - integrity sha1-o6bCsOvsxcLLocF+bmIP6BtT00c= - lodash@4.17.14: version "4.17.14" resolved "https://registry.npmjs.org/lodash/-/lodash-4.17.14.tgz#9ce487ae66c96254fe20b599f21b6816028078ba" @@ -14777,7 +14246,7 @@ loglevel@^1.4.1: resolved "https://registry.npmjs.org/loglevel/-/loglevel-1.6.1.tgz#e0fc95133b6ef276cdc8887cdaf24aa6f156f8fa" integrity sha1-4PyVEztu8nbNyIh82vJKpvFW+Po= -loglevel@^1.6.1, loglevel@^1.6.3: +loglevel@^1.6.3: version "1.6.4" resolved "https://registry.npmjs.org/loglevel/-/loglevel-1.6.4.tgz#f408f4f006db8354d0577dcf6d33485b3cb90d56" integrity sha512-p0b6mOGKcGa+7nnmKbpzR6qloPbrgLcnio++E+14Vo/XffOGwZtRpUhr8dTH/x2oCMmEoIU0Zwm3ZauhvYD17g== @@ -15287,21 +14756,11 @@ mimic-fn@^2.0.0: resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.0.0.tgz#0913ff0b121db44ef5848242c38bbb35d44cabde" integrity sha512-jbex9Yd/3lmICXwYT6gA/j2mNQGU48wCh/VzRd+/Y/PjYQtlg1gLMdZqvu9s/xH7qKvngxRObl56XZR609IMbA== -mimic-fn@^2.1.0: - version "2.1.0" - resolved "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" - integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== - mimic-response@^1.0.0, mimic-response@^1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b" integrity sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ== -mimic-response@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-2.0.0.tgz#996a51c60adf12cb8a87d7fb8ef24c2f3d5ebb46" - integrity sha512-8ilDoEapqA4uQ3TwS0jakGONKXVJqpy+RpM+3b7pLdOjghCrEiGp9SRkFbUHAmZW9vdnrENWHjaweIoTIJExSQ== - min-document@^2.19.0: version "2.19.0" resolved "https://registry.npmjs.org/min-document/-/min-document-2.19.0.tgz#7bd282e3f5842ed295bb748cdd9f1ffa2c824685" @@ -15537,7 +14996,7 @@ mz@^2.4.0, mz@^2.6.0: object-assign "^4.0.1" thenify-all "^1.0.0" -nan@2.13.2, nan@^2.0.8, nan@^2.2.1, nan@^2.3.3: +nan@^2.0.8, nan@^2.2.1, nan@^2.3.3: version "2.13.2" resolved "https://registry.yarnpkg.com/nan/-/nan-2.13.2.tgz#f51dc7ae66ba7d5d55e1e6d4d8092e802c9aefe7" integrity sha512-TghvYc72wlMGMVMluVo9WRJc0mB8KxxF/gZ4YYFy7V2ZQX9l7rgbPg7vjS9mt6U5HXODVFVI2bOduCzwOMv/lw== @@ -15698,15 +15157,6 @@ node-gyp-build@^4.1.0: resolved "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.1.1.tgz#d7270b5d86717068d114cc57fff352f96d745feb" integrity sha512-dSq1xmcPDKPZ2EED2S6zw/b9NKsqzXRE6dVr8TVQnI3FJOTteUMuqF3Qqs6LZg+mLGYJWqQzMbIjMtJqTv87nQ== -node-hid@^0.7.9: - version "0.7.9" - resolved "https://registry.yarnpkg.com/node-hid/-/node-hid-0.7.9.tgz#cc0cdf1418a286a7667f0b63642b5eeb544ccd05" - integrity sha512-vJnonTqmq3frCyTumJqG4g2IZcny3ynkfmbfDfQ90P3ZhRzcWYS/Um1ux6HFmAxmkaQnrZqIYHcGpL7kdqY8jA== - dependencies: - bindings "^1.5.0" - nan "^2.13.2" - prebuild-install "^5.3.0" - node-int64@^0.4.0: version "0.4.0" resolved "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" @@ -15797,13 +15247,6 @@ noop-logger@^0.1.1: resolved "https://registry.npmjs.org/noop-logger/-/noop-logger-0.1.1.tgz#94a2b1633c4f1317553007d8966fd0e841b6a4c2" integrity sha1-lKKxYzxPExdVMAfYlm/Q6EG2pMI= -nopt@3.x: - version "3.0.6" - resolved "https://registry.yarnpkg.com/nopt/-/nopt-3.0.6.tgz#c6465dbf08abcd4db359317f79ac68a646b28ff9" - integrity sha1-xkZdvwirzU2zWTF/eaxopkayj/k= - dependencies: - abbrev "1" - nopt@^4.0.1, nopt@~4.0.1: version "4.0.1" resolved "https://registry.npmjs.org/nopt/-/nopt-4.0.1.tgz#d0d4685afd5415193c8c7505602d0d17cd64474d" @@ -15829,7 +15272,7 @@ normalize-path@^2.0.1, normalize-path@^2.1.1: dependencies: remove-trailing-separator "^1.0.1" -normalize-path@^3.0.0, normalize-path@~3.0.0: +normalize-path@^3.0.0: version "3.0.0" resolved "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== @@ -15908,13 +15351,6 @@ npm-run-path@^2.0.0: dependencies: path-key "^2.0.0" -npm-run-path@^4.0.0: - version "4.0.0" - resolved "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.0.tgz#d644ec1bd0569187d2a52909971023a0a58e8438" - integrity sha512-8eyAOAH+bYXFPSnNnKr3J+yoybe8O87Is5rtAQ8qRczJz1ajcsjg8l2oZqP+Ppx15Ii3S1vUTjQN2h4YO2tWWQ== - dependencies: - path-key "^3.0.0" - npmlog@^4.0.1, npmlog@^4.0.2, npmlog@^4.1.2: version "4.1.2" resolved "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b" @@ -16156,7 +15592,7 @@ on-headers@~1.0.1, on-headers@~1.0.2: resolved "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz#772b0ae6aaa525c399e489adfad90c403eb3c28f" integrity sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA== -once@1.x, once@^1.3.0, once@^1.3.1, once@^1.3.2, once@^1.4.0: +once@^1.3.0, once@^1.3.1, once@^1.3.2, once@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= @@ -16180,13 +15616,6 @@ onetime@^2.0.0: dependencies: mimic-fn "^1.0.0" -onetime@^5.1.0: - version "5.1.0" - resolved "https://registry.npmjs.org/onetime/-/onetime-5.1.0.tgz#fff0f3c91617fe62bb50189636e99ac8a6df7be5" - integrity sha512-5NcSkPHhwTVFIQN+TUqXoS5+dlElHXdpAWu9I0HP20YOtIi+aZ0Ct82jdlILDxjLEAWwvm+qj1m6aEtsDVmm6Q== - dependencies: - mimic-fn "^2.1.0" - open@^6.1.0, open@^6.3.0: version "6.4.0" resolved "https://registry.yarnpkg.com/open/-/open-6.4.0.tgz#5c13e96d0dc894686164f18965ecfe889ecfc8a9" @@ -16383,11 +15812,6 @@ p-finally@^1.0.0: resolved "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" integrity sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4= -p-finally@^2.0.0: - version "2.0.1" - resolved "https://registry.npmjs.org/p-finally/-/p-finally-2.0.1.tgz#bd6fcaa9c559a096b680806f4d657b3f0f240561" - integrity sha512-vpm09aKwq6H9phqRQzecoDpD8TmVyGw70qmWlyq5onxY7tqyTTFVvxMykxQSQKILBSFlbXpypIw2T1Ml7+DDtw== - p-is-promise@^1.1.0: version "1.1.0" resolved "https://registry.npmjs.org/p-is-promise/-/p-is-promise-1.1.0.tgz#9c9456989e9f6588017b0434d56097675c3da05e" @@ -16701,7 +16125,7 @@ path-key@^2.0.0, path-key@^2.0.1: resolved "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" integrity sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A= -path-key@^3.0.0, path-key@^3.1.0: +path-key@^3.1.0: version "3.1.0" resolved "https://registry.npmjs.org/path-key/-/path-key-3.1.0.tgz#99a10d870a803bdd5ee6f0470e58dfcd2f9a54d3" integrity sha512-8cChqz0RP6SHJkMt48FW0A7+qUOn+OsnOsVtzI59tZ8m+5bCSk7hzwET0pulwOM2YMn9J1efb07KB9l9f30SGg== @@ -16859,7 +16283,7 @@ pgpass@1.x: dependencies: split "^1.0.0" -picomatch@^2.0.4, picomatch@^2.0.5: +picomatch@^2.0.5: version "2.0.7" resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.0.7.tgz#514169d8c7cd0bdbeecc8a2609e34a7163de69f6" integrity sha512-oLHIdio3tZ0qH76NybpeneBhYVj0QFTfXEFTc/B3zKQspYfYYkWYgFsmzo+4kvId/bQRcNkVeguI3y+CD22BtA== @@ -16940,11 +16364,6 @@ please-upgrade-node@^3.1.1: dependencies: semver-compare "^1.0.0" -pluralize@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-7.0.0.tgz#298b89df8b93b0221dbf421ad2b1b1ea23fc6777" - integrity sha512-ARhBOdzS3e41FbkW/XWrTEtukqqLoK5+Z/4UeDaLuSW+39JPeFgs4gCGqsrJHVZX0fUrx//4OF0K1CUGwlIFow== - pn@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/pn/-/pn-1.1.0.tgz#e2f4cef0e219f463c179ab37463e4e1ecdccbafb" @@ -16976,11 +16395,6 @@ pop-iterate@^1.0.1: resolved "https://registry.npmjs.org/pop-iterate/-/pop-iterate-1.0.1.tgz#ceacfdab4abf353d7a0f2aaa2c1fc7b3f9413ba3" integrity sha1-zqz9q0q/NT16DyqqLB/Hs/lBO6M= -popper.js@1.14.3: - version "1.14.3" - resolved "https://registry.yarnpkg.com/popper.js/-/popper.js-1.14.3.tgz#1438f98d046acf7b4d78cd502bf418ac64d4f095" - integrity sha1-FDj5jQRqz3tNeM1QK/QYrGTU8JU= - popper.js@^1.14.1, popper.js@^1.14.4, popper.js@^1.14.7: version "1.15.0" resolved "https://registry.yarnpkg.com/popper.js/-/popper.js-1.15.0.tgz#5560b99bbad7647e9faa475c6b8056621f5a4ff2" @@ -17700,27 +17114,6 @@ postgres-interval@^1.1.0: dependencies: xtend "^4.0.0" -prebuild-install@^5.2.4: - version "5.3.2" - resolved "https://registry.yarnpkg.com/prebuild-install/-/prebuild-install-5.3.2.tgz#6392e9541ac0b879ef0f22b3d65037417eb2035e" - integrity sha512-INDfXzTPnhT+WYQemqnAXlP7SvfiFMopMozSgXCZ+RDLb279gKfIuLk4o7PgEawLp3WrMgIYGBpkxpraROHsSA== - dependencies: - detect-libc "^1.0.3" - expand-template "^2.0.3" - github-from-package "0.0.0" - minimist "^1.2.0" - mkdirp "^0.5.1" - napi-build-utils "^1.0.1" - node-abi "^2.7.0" - noop-logger "^0.1.1" - npmlog "^4.0.1" - pump "^3.0.0" - rc "^1.2.7" - simple-get "^3.0.3" - tar-fs "^2.0.0" - tunnel-agent "^0.6.0" - which-pm-runs "^1.0.0" - prebuild-install@^5.3.0: version "5.3.0" resolved "https://registry.npmjs.org/prebuild-install/-/prebuild-install-5.3.0.tgz#58b4d8344e03590990931ee088dd5401b03004c8" @@ -18985,19 +18378,19 @@ readable-stream@^1.0.33: isarray "0.0.1" string_decoder "~0.10.x" -readable-stream@^3.0.1, readable-stream@^3.1.1: - version "3.4.0" - resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-3.4.0.tgz#a51c26754658e0a3c21dbf59163bd45ba6f447fc" - integrity sha512-jItXPLmrSR8jmTRmRWJXCnGJsfy85mB3Wd/uINMXA65yrnFo0cPClFIUWzo2najVNSl+mx7/4W8ttlLWJe99pQ== +readable-stream@^3.0.6: + version "3.2.0" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.2.0.tgz#de17f229864c120a9f56945756e4f32c4045245d" + integrity sha512-RV20kLjdmpZuTF1INEb9IA3L68Nmi+Ri7ppZqo78wj//Pn62fCoJyV9zalccNzDD/OuJpMG4f+pfMl8+L6QdGw== dependencies: inherits "^2.0.3" string_decoder "^1.1.1" util-deprecate "^1.0.1" -readable-stream@^3.0.6: - version "3.2.0" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.2.0.tgz#de17f229864c120a9f56945756e4f32c4045245d" - integrity sha512-RV20kLjdmpZuTF1INEb9IA3L68Nmi+Ri7ppZqo78wj//Pn62fCoJyV9zalccNzDD/OuJpMG4f+pfMl8+L6QdGw== +readable-stream@^3.1.1: + version "3.4.0" + resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-3.4.0.tgz#a51c26754658e0a3c21dbf59163bd45ba6f447fc" + integrity sha512-jItXPLmrSR8jmTRmRWJXCnGJsfy85mB3Wd/uINMXA65yrnFo0cPClFIUWzo2najVNSl+mx7/4W8ttlLWJe99pQ== dependencies: inherits "^2.0.3" string_decoder "^1.1.1" @@ -19022,13 +18415,6 @@ readdirp@^2.2.1: micromatch "^3.1.10" readable-stream "^2.0.2" -readdirp@~3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.2.0.tgz#c30c33352b12c96dfb4b895421a49fd5a9593839" - integrity sha512-crk4Qu3pmXwgxdSgGhgA/eXiJAPQiX4GMOZZMXnqKxHX7TaoL+3gQVo/WeuAiogr07DpnfjIMpXXa+PAIvwPGQ== - dependencies: - picomatch "^2.0.4" - realpath-native@^1.1.0: version "1.1.0" resolved "https://registry.npmjs.org/realpath-native/-/realpath-native-1.1.0.tgz#2003294fea23fb0672f2476ebe22fcf498a2d65c" @@ -19423,7 +18809,7 @@ request-promise@4.2.4, request-promise@^4.2.2, request-promise@^4.2.4: stealthy-require "^1.1.1" tough-cookie "^2.3.3" -request@2.88.0, request@^2.67.0, request@^2.79.0, request@^2.83.0, request@^2.85.0, request@^2.87.0, request@^2.88.0: +request@2.88.0, request@^2.79.0, request@^2.83.0, request@^2.85.0, request@^2.87.0, request@^2.88.0: version "2.88.0" resolved "https://registry.yarnpkg.com/request/-/request-2.88.0.tgz#9c2fca4f7d35b592efe57c7f0a55e81052124fef" integrity sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg== @@ -19459,7 +18845,7 @@ require-from-string@^1.1.0: resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-1.2.1.tgz#529c9ccef27380adfec9a2f965b649bbee636418" integrity sha1-UpyczvJzgK3+yaL5ZbZJu+5jZBg= -require-from-string@^2.0.0, require-from-string@^2.0.1: +require-from-string@^2.0.0: version "2.0.2" resolved "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909" integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw== @@ -19564,7 +18950,7 @@ resolve-url@^0.2.1: resolved "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" integrity sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo= -resolve@1.1.7, resolve@1.1.x: +resolve@1.1.7: version "1.1.7" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b" integrity sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs= @@ -19734,7 +19120,7 @@ rxjs@^6.1.0: dependencies: tslib "^1.9.0" -rxjs@^6.4.0, rxjs@^6.5.3: +rxjs@^6.4.0: version "6.5.3" resolved "https://registry.npmjs.org/rxjs/-/rxjs-6.5.3.tgz#510e26317f4db91a7eb1de77d9dd9ba0a4899a3a" integrity sha512-wuYsAYYFdWTAnAaPoKGNhfpWwKZbJW+HgAJ+mImp+Epl7BG8oNWBCTyRM8gba9k4lk8BgWdoYm21Mo/RYhhbgA== @@ -19960,11 +19346,6 @@ selfsigned@^1.9.1: dependencies: node-forge "0.7.5" -semaphore-async-await@^1.5.1: - version "1.5.1" - resolved "https://registry.yarnpkg.com/semaphore-async-await/-/semaphore-async-await-1.5.1.tgz#857bef5e3644601ca4b9570b87e9df5ca12974fa" - integrity sha1-hXvvXjZEYBykuVcLh+nfXKEpdPo= - semaphore@>=1.0.1, semaphore@^1.0.3, semaphore@^1.1.0: version "1.1.0" resolved "https://registry.npmjs.org/semaphore/-/semaphore-1.1.0.tgz#aaad8b86b20fe8e9b32b16dc2ee682a8cd26a8aa" @@ -20069,20 +19450,6 @@ serve-handler@5.0.8: path-to-regexp "2.2.1" range-parser "1.2.0" -serve-handler@6.1.2: - version "6.1.2" - resolved "https://registry.yarnpkg.com/serve-handler/-/serve-handler-6.1.2.tgz#f05b0421a313fff2d257838cba00cbcc512cd2b6" - integrity sha512-RFh49wX7zJmmOVDcIjiDSJnMH+ItQEvyuYLYuDBVoA/xmQSCuj+uRmk1cmBB5QQlI3qOiWKp6p4DUGY+Z5AB2A== - dependencies: - bytes "3.0.0" - content-disposition "0.5.2" - fast-url-parser "1.1.3" - mime-types "2.1.18" - minimatch "3.0.4" - path-is-inside "1.0.2" - path-to-regexp "2.2.1" - range-parser "1.2.0" - serve-index@^1.7.2, serve-index@^1.9.1: version "1.9.1" resolved "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz#d3768d69b1e7d82e5ce050fff5b453bea12a9239" @@ -20106,7 +19473,7 @@ serve-static@1.14.1: parseurl "~1.3.3" send "0.17.1" -serve@^10.1.1: +serve@^10.0.0, serve@^10.1.1: version "10.1.2" resolved "https://registry.npmjs.org/serve/-/serve-10.1.2.tgz#805917f2692ed5d8720bbd1981ac3974d38b7d8d" integrity sha512-TVH35uwndRlCqSeX3grR3Ntrjx2aBTeu6sx+zTD2CzN2N/rHuEDTvxiBwWbrellJNyWiQFz2xZmoW+UxV+Zahg== @@ -20121,21 +19488,6 @@ serve@^10.1.1: serve-handler "5.0.8" update-check "1.5.2" -serve@^11.2.0: - version "11.2.0" - resolved "https://registry.yarnpkg.com/serve/-/serve-11.2.0.tgz#0405ce95c4e4a6abd9cd3d3a04ebaa7d94638627" - integrity sha512-THZcLzDGk3vJqjhAbLkLag43tiE3V0B7wVe98Xtl+1KyAsr+4iShg+9hke4pLZmrCJu0pUg0TrbhJmdqn/MKoA== - dependencies: - "@zeit/schemas" "2.6.0" - ajv "6.5.3" - arg "2.0.0" - boxen "1.3.0" - chalk "2.4.1" - clipboardy "1.2.3" - compression "1.7.3" - serve-handler "6.1.2" - update-check "1.5.2" - servify@^0.1.12: version "0.1.12" resolved "https://registry.npmjs.org/servify/-/servify-0.1.12.tgz#142ab7bee1f1d033b66d0707086085b17c06db95" @@ -20253,7 +19605,7 @@ shell-quote@1.6.1: shelljs@^0.8.3: version "0.8.3" - resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.8.3.tgz#a7f3319520ebf09ee81275b2368adb286659b097" + resolved "https://registry.npmjs.org/shelljs/-/shelljs-0.8.3.tgz#a7f3319520ebf09ee81275b2368adb286659b097" integrity sha512-fc0BKlAWiLpwZljmOvAOTE/gXawtCoNrP5oaY7KIaQbbyHeQVg01pSEuEGvGh3HEdBU4baCD7wQBwADmM/7f7A== dependencies: glob "^7.0.0" @@ -20299,15 +19651,6 @@ simple-get@^2.7.0: once "^1.3.1" simple-concat "^1.0.0" -simple-get@^3.0.3: - version "3.1.0" - resolved "https://registry.yarnpkg.com/simple-get/-/simple-get-3.1.0.tgz#b45be062435e50d159540b576202ceec40b9c6b3" - integrity sha512-bCR6cP+aTdScaQCnQKbPKtJOKDp/hj9EDLJo3Nw4y1QksqaovlW/bnptB6/c1e+qmNIDHRK+oXFDdEqBT8WzUA== - dependencies: - decompress-response "^4.2.0" - once "^1.3.1" - simple-concat "^1.0.0" - simple-swizzle@^0.2.2: version "0.2.2" resolved "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz#a4da6b635ffcccca33f70d17cb92592de95e557a" @@ -20527,20 +19870,6 @@ solc@^0.4.20: semver "^5.3.0" yargs "^4.7.1" -solc@^0.5.5: - version "0.5.12" - resolved "https://registry.yarnpkg.com/solc/-/solc-0.5.12.tgz#e63047dce04c82ec6f469f6e28febfbde713b808" - integrity sha512-OX/AGZT04tuUsagoVXSZBiBZYJReA02hdwZOfRkB03/eeYP9Dl3pr+M+au+1MhssgiuWBlFPN7sRXFiqwkAW2g== - dependencies: - command-exists "^1.2.8" - fs-extra "^0.30.0" - js-sha3 "0.8.0" - memorystream "^0.3.1" - require-from-string "^2.0.0" - semver "^5.5.0" - tmp "0.0.33" - yargs "^13.2.0" - solhint@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/solhint/-/solhint-2.1.0.tgz#a0f8064db5726bb54c8ed21e94b01f0bb3959b3c" @@ -20633,7 +19962,7 @@ source-map-support@^0.4.15: dependencies: source-map "^0.5.6" -source-map-support@^0.5.0, source-map-support@^0.5.12, source-map-support@^0.5.13, source-map-support@^0.5.6, source-map-support@^0.5.9, source-map-support@~0.5.12: +source-map-support@^0.5.12, source-map-support@^0.5.13, source-map-support@^0.5.6, source-map-support@^0.5.9, source-map-support@~0.5.12: version "0.5.13" resolved "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz#31b24a9c2e73c2de85066c0feb7d44767ed52932" integrity sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w== @@ -20668,13 +19997,6 @@ source-map@^0.7.3: resolved "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383" integrity sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ== -source-map@~0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.2.0.tgz#dab73fbcfc2ba819b4de03bd6f6eaa48164b3f9d" - integrity sha1-2rc/vPwrqBm03gO9b26qSBZLP50= - dependencies: - amdefine ">=0.0.4" - space-separated-tokens@^1.0.0: version "1.1.4" resolved "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-1.1.4.tgz#27910835ae00d0adfcdbd0ad7e611fb9544351fa" @@ -21074,11 +20396,6 @@ strip-eof@^1.0.0: resolved "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf" integrity sha1-u0P/VZim6wXYm1n80SnJgzE2Br8= -strip-final-newline@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" - integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== - strip-hex-prefix@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/strip-hex-prefix/-/strip-hex-prefix-1.0.0.tgz#0c5f155fef1151373377de9dbb588da05500e36f" @@ -21185,13 +20502,6 @@ supports-color@^2.0.0: resolved "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" integrity sha1-U10EXOa2Nj+kARcIRimZXp3zJMc= -supports-color@^3.1.0: - version "3.2.3" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-3.2.3.tgz#65ac0504b3954171d8a64946b2ae3cbb8a5f54f6" - integrity sha1-ZawFBLOVQXHYpklGsq48u4pfVPY= - dependencies: - has-flag "^1.0.0" - supports-color@^4.2.1, supports-color@^4.5.0: version "4.5.0" resolved "https://registry.npmjs.org/supports-color/-/supports-color-4.5.0.tgz#be7a0de484dec5c5cddf8b3d59125044912f635b" @@ -21350,7 +20660,7 @@ tapable@^1.0.0, tapable@^1.1.0, tapable@^1.1.3: resolved "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz#a1fccc06b58db61fd7a45da2da44f5f3a3e67ba2" integrity sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA== -tape@^4.4.0, tape@^4.6.3: +tape@^4.6.3: version "4.11.0" resolved "https://registry.npmjs.org/tape/-/tape-4.11.0.tgz#63d41accd95e45a23a874473051c57fdbc58edc1" integrity sha512-yixvDMX7q7JIs/omJSzSZrqulOV51EC9dK8dM0TzImTIkHWfe2/kFyL5v+d9C+SrCMaICk59ujsqFAVidDqDaA== @@ -21379,16 +20689,6 @@ tar-fs@^1.13.0, tar-fs@^1.16.3: pump "^1.0.0" tar-stream "^1.1.2" -tar-fs@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-2.0.0.tgz#677700fc0c8b337a78bee3623fdc235f21d7afad" - integrity sha512-vaY0obB6Om/fso8a8vakQBzwholQ7v5+uy+tF3Ozvxv1KNezmVQAiWtcNmMHFSFPqL3dJA8ha6gdtFbfX9mcxA== - dependencies: - chownr "^1.1.1" - mkdirp "^0.5.1" - pump "^3.0.0" - tar-stream "^2.0.0" - tar-stream@^1.1.2, tar-stream@^1.5.2: version "1.6.2" resolved "https://registry.npmjs.org/tar-stream/-/tar-stream-1.6.2.tgz#8ea55dab37972253d9a9af90fdcd559ae435c555" @@ -21402,17 +20702,6 @@ tar-stream@^1.1.2, tar-stream@^1.5.2: to-buffer "^1.1.1" xtend "^4.0.0" -tar-stream@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.1.0.tgz#d1aaa3661f05b38b5acc9b7020efdca5179a2cc3" - integrity sha512-+DAn4Nb4+gz6WZigRzKEZl1QuJVOLtAwwF+WUxy1fJ6X63CaGaUAxJRD2KEn1OMfcbCjySTYpNC6WmfQoIEOdw== - dependencies: - bl "^3.0.0" - end-of-stream "^1.4.1" - fs-constants "^1.0.0" - inherits "^2.0.3" - readable-stream "^3.1.1" - tar.gz@^1.0.5: version "1.0.7" resolved "https://registry.yarnpkg.com/tar.gz/-/tar.gz-1.0.7.tgz#577ef2c595faaa73452ef0415fed41113212257b" @@ -21946,10 +21235,10 @@ ts-jest@^24.0.0, ts-jest@^24.1.0: semver "^5.5" yargs-parser "10.x" -ts-loader@^6.2.1: - version "6.2.1" - resolved "https://registry.yarnpkg.com/ts-loader/-/ts-loader-6.2.1.tgz#67939d5772e8a8c6bdaf6277ca023a4812da02ef" - integrity sha512-Dd9FekWuABGgjE1g0TlQJ+4dFUfYGbYcs52/HQObE0ZmUNjQlmLAS7xXsSzy23AMaMwipsx5sNHvoEpT2CZq1g== +ts-loader@^6.1.0: + version "6.1.2" + resolved "https://registry.yarnpkg.com/ts-loader/-/ts-loader-6.1.2.tgz#ff6bc767334970226438949fbde2e211147a1325" + integrity sha512-dudxFKm0Ellrg/gLNlu+97/UgwvoMK0SdUVImPUSzq3IcRUVtShylZvcMX+CgvCQL1BEKb913NL0gAP1GA/OFw== dependencies: chalk "^2.3.0" enhanced-resolve "^4.0.0" @@ -22192,11 +21481,6 @@ typescript@^3.6.3: resolved "https://registry.npmjs.org/typescript/-/typescript-3.6.3.tgz#fea942fabb20f7e1ca7164ff626f1a9f3f70b4da" integrity sha512-N7bceJL1CtRQ2RiG0AQME13ksR7DiuQh/QehubYcghzv20tnh+MQnQIuJddTmsbqYj+dztchykemz0zFzlvdQw== -typescript@^3.7.0-beta: - version "3.7.0-beta" - resolved "https://registry.npmjs.org/typescript/-/typescript-3.7.0-beta.tgz#4ad556e0eee14b90ecc39261001690e16e5eeba9" - integrity sha512-4jyCX+IQamrPJxgkABPq9xf+hUN+GWHVxoj+oey1TadCPa4snQl1RKwUba+1dyzYCamwlCxKvZQ3TjyWLhMGBA== - typewise-core@^1.2, typewise-core@^1.2.0: version "1.2.0" resolved "https://registry.npmjs.org/typewise-core/-/typewise-core-1.2.0.tgz#97eb91805c7f55d2f941748fa50d315d991ef195" @@ -22224,11 +21508,6 @@ typical@^4.0.0: resolved "https://registry.yarnpkg.com/typical/-/typical-4.0.0.tgz#cbeaff3b9d7ae1e2bbfaf5a4e6f11eccfde94fc4" integrity sha512-VAH4IvQ7BDFYglMd7BPRDfLgxZZX4O4TFcRDA6EN5X7erNJJq+McIEp8np9aVtxrCJ6qx4GTYVfOWNjcqwZgRw== -u2f-api@0.2.7: - version "0.2.7" - resolved "https://registry.yarnpkg.com/u2f-api/-/u2f-api-0.2.7.tgz#17bf196b242f6bf72353d9858e6a7566cc192720" - integrity sha512-fqLNg8vpvLOD5J/z4B6wpPg4Lvowz1nJ9xdHcCzdUPKcFE/qNCceV2gNZxSJd5vhAZemHr/K/hbzVA0zxB5mkg== - ua-parser-js@^0.7.18, ua-parser-js@^0.7.9: version "0.7.20" resolved "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.20.tgz#7527178b82f6a62a0f243d1f94fd30e3e3c21098" @@ -22528,15 +21807,6 @@ url@0.11.0, url@^0.11.0: punycode "1.3.2" querystring "0.2.0" -usb@^1.6.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/usb/-/usb-1.6.0.tgz#bc5d4decf4ffca32d1136717edcf73366137a789" - integrity sha512-52DyWlCk9K+iw3LnvY95WXSnpHjxJoI++aGkV8HiMNPc4zmvDQlYvWAzrkbJ2JH3oUcx26XfU5sZcG4RAcVkMg== - dependencies: - bindings "^1.4.0" - nan "2.13.2" - prebuild-install "^5.2.4" - use-react-hooks@^1.0.7: version "1.0.7" resolved "https://registry.npmjs.org/use-react-hooks/-/use-react-hooks-1.0.7.tgz#57a3948dcddf721eaf19b80a1cc060171e00f0f1" @@ -22626,11 +21896,6 @@ v8flags@^3.0.1: dependencies: homedir-polyfill "^1.0.1" -valid-url@^1.0.9: - version "1.0.9" - resolved "https://registry.yarnpkg.com/valid-url/-/valid-url-1.0.9.tgz#1c14479b40f1397a75782f115e4086447433a200" - integrity sha1-HBRHm0DxOXp1eC8RXkCGRHQzogA= - validate-npm-package-license@^3.0.1: version "3.0.4" resolved "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz#fc91f6b9c7ba15c857f4cb2c5defeec39d4f410a" @@ -22940,7 +22205,7 @@ web3-eth-abi@1.0.0-beta.37: underscore "1.8.3" web3-utils "1.0.0-beta.37" -web3-eth-abi@1.2.1, web3-eth-abi@^1.0.0-beta.24: +web3-eth-abi@1.2.1: version "1.2.1" resolved "https://registry.npmjs.org/web3-eth-abi/-/web3-eth-abi-1.2.1.tgz#9b915b1c9ebf82f70cca631147035d5419064689" integrity sha512-jI/KhU2a/DQPZXHjo2GW0myEljzfiKOn+h1qxK1+Y9OQfTcBMxrQJyH5AP89O6l6NZ1QvNdq99ThAxBFoy5L+g== @@ -23132,33 +22397,6 @@ web3-net@1.2.1: web3-core-method "1.2.1" web3-utils "1.2.1" -web3-provider-engine@14.0.6: - version "14.0.6" - resolved "https://registry.yarnpkg.com/web3-provider-engine/-/web3-provider-engine-14.0.6.tgz#cbdd66fe20c0136a3a495cbe40d18b6c4160d5f0" - integrity sha512-tr5cGSyxfSC/JqiUpBlJtfZpwQf1yAA8L/zy1C6fDFm0ntR974pobJ4v4676atpZne4Ze5VFy3kPPahHe9gQiQ== - dependencies: - async "^2.5.0" - backoff "^2.5.0" - clone "^2.0.0" - cross-fetch "^2.1.0" - eth-block-tracker "^3.0.0" - eth-json-rpc-infura "^3.1.0" - eth-sig-util "^1.4.2" - ethereumjs-block "^1.2.2" - ethereumjs-tx "^1.2.0" - ethereumjs-util "^5.1.5" - ethereumjs-vm "^2.3.4" - json-rpc-error "^2.0.0" - json-stable-stringify "^1.0.1" - promise-to-callback "^1.0.0" - readable-stream "^2.2.9" - request "^2.67.0" - semaphore "^1.0.3" - tape "^4.4.0" - ws "^5.1.1" - xhr "^2.2.0" - xtend "^4.0.1" - web3-provider-engine@14.2.1: version "14.2.1" resolved "https://registry.npmjs.org/web3-provider-engine/-/web3-provider-engine-14.2.1.tgz#ef351578797bf170e08d529cb5b02f8751329b95" @@ -23711,7 +22949,7 @@ which-pm-runs@^1.0.0: resolved "https://registry.npmjs.org/which-pm-runs/-/which-pm-runs-1.0.0.tgz#670b3afbc552e0b55df6b7780ca74615f23ad1cb" integrity sha1-Zws6+8VS4LVd9rd4DKdGFfI60cs= -which@^1.1.1, which@^1.2.14, which@^1.2.9, which@^1.3.0, which@^1.3.1: +which@^1.2.14, which@^1.2.9, which@^1.3.0, which@^1.3.1: version "1.3.1" resolved "https://registry.npmjs.org/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== @@ -23777,16 +23015,16 @@ wordwrap@0.0.2: resolved "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz#b79669bb42ecb409f83d583cad52ca17eaa1643f" integrity sha1-t5Zpu0LstAn4PVg8rVLKF+qhZD8= -wordwrap@^1.0.0, wordwrap@~1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb" - integrity sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus= - wordwrap@~0.0.2: version "0.0.3" resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.3.tgz#a3d5da6cd5c0bc0008d37234bbaf1bed63059107" integrity sha1-o9XabNXAvAAI03I0u68b7WMFkQc= +wordwrap@~1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb" + integrity sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus= + workbox-background-sync@^4.3.1: version "4.3.1" resolved "https://registry.npmjs.org/workbox-background-sync/-/workbox-background-sync-4.3.1.tgz#26821b9bf16e9e37fd1d640289edddc08afd1950" @@ -24197,13 +23435,6 @@ yargs-parser@^7.0.0: dependencies: camelcase "^4.1.0" -yargs-parser@^8.1.0: - version "8.1.0" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-8.1.0.tgz#f1376a33b6629a5d063782944da732631e966950" - integrity sha512-yP+6QqN8BmrgW2ggLtTbdrOyBNSI7zBa4IykmiV5R1wl1JWNxQvWhMfMdmzIYtKU7oP3OOInY/tl2ov3BDjnJQ== - dependencies: - camelcase "^4.1.0" - yargs-parser@^9.0.2: version "9.0.2" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-9.0.2.tgz#9ccf6a43460fe4ed40a9bb68f48d43b8a68cc077" @@ -24282,24 +23513,6 @@ yargs@13.2.4: y18n "^4.0.0" yargs-parser "^13.1.0" -yargs@^10.0.3: - version "10.1.2" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-10.1.2.tgz#454d074c2b16a51a43e2fb7807e4f9de69ccb5c5" - integrity sha512-ivSoxqBGYOqQVruxD35+EyCFDYNEFL/Uo6FcOnz+9xZdZzK0Zzw4r4KhbrME1Oo2gOggwJod2MnsdamSG7H9ig== - dependencies: - cliui "^4.0.0" - decamelize "^1.1.1" - find-up "^2.1.0" - get-caller-file "^1.0.1" - os-locale "^2.0.0" - require-directory "^2.1.1" - require-main-filename "^1.0.1" - set-blocking "^2.0.0" - string-width "^2.0.0" - which-module "^2.0.0" - y18n "^3.2.1" - yargs-parser "^8.1.0" - yargs@^13.0.0, yargs@^13.2.0, yargs@^13.2.1, yargs@^13.2.2, yargs@^13.3.0: version "13.3.0" resolved "https://registry.npmjs.org/yargs/-/yargs-13.3.0.tgz#4c657a55e07e5f2cf947f8a366567c04a0dedc83" From 996e7dda80ba6905447c732325d541b168d7480e Mon Sep 17 00:00:00 2001 From: Ryan Hall Date: Wed, 30 Oct 2019 16:12:12 -0400 Subject: [PATCH 066/199] address linter complaints --- .../__tests__/middleware/adminAuth.test.ts | 2 +- explorer/src/entity/JobRun.ts | 10 +++---- explorer/src/server/handleMessage.ts | 28 ++++++++++--------- .../src/server/rpcMethods/upsertJobRun.ts | 4 +-- explorer/src/server/rpcServer.ts | 14 +++++++++- 5 files changed, 36 insertions(+), 22 deletions(-) diff --git a/explorer/src/__tests__/middleware/adminAuth.test.ts b/explorer/src/__tests__/middleware/adminAuth.test.ts index 9d56a3ecae4..63e6e69a87b 100644 --- a/explorer/src/__tests__/middleware/adminAuth.test.ts +++ b/explorer/src/__tests__/middleware/adminAuth.test.ts @@ -4,7 +4,7 @@ import http from 'http' import httpStatus from 'http-status-codes' import cookieSession from 'cookie-session' import { Connection } from 'typeorm' -import { closeDbConnection, getDb } from '../../database' +import { getDb } from '../../database' import { clearDb } from '../testdatabase' import { createAdmin } from '../../support/admin' import { stop } from '../../support/server' diff --git a/explorer/src/entity/JobRun.ts b/explorer/src/entity/JobRun.ts index 3c2ac9f646a..548aa8adb1a 100644 --- a/explorer/src/entity/JobRun.ts +++ b/explorer/src/entity/JobRun.ts @@ -59,11 +59,6 @@ export class JobRun { chainlinkNode: ChainlinkNode } -export const fromString = (str: string): JobRun => { - const json = JSON.parse(str) - return fromJSONObject(json) -} - export const fromJSONObject = (json: any): JobRun => { const jr = new JobRun() jr.runId = json.runId @@ -102,6 +97,11 @@ export const fromJSONObject = (json: any): JobRun => { return jr } +export const fromString = (str: string): JobRun => { + const json = JSON.parse(str) + return fromJSONObject(json) +} + export const saveJobRunTree = async (db: Connection, jobRun: JobRun) => { await db.manager.transaction(async manager => { let builder = manager.createQueryBuilder() diff --git a/explorer/src/server/handleMessage.ts b/explorer/src/server/handleMessage.ts index d63f1c0eec4..2f71498a9a7 100644 --- a/explorer/src/server/handleMessage.ts +++ b/explorer/src/server/handleMessage.ts @@ -1,13 +1,14 @@ -import rpcServer from './rpcServer' +import { callRPCServer } from './rpcServer' import { logger } from '../logging' import { getDb } from '../database' import { fromString, saveJobRunTree } from '../entity/JobRun' +import jayson from 'jayson' -export type serverContext = { +export interface ServerContext { chainlinkNodeId: number } -const handleLegacy = async (json: string, context: serverContext) => { +const handleLegacy = async (json: string, context: ServerContext) => { try { const db = await getDb() const jobRun = fromString(json) @@ -20,21 +21,22 @@ const handleLegacy = async (json: string, context: serverContext) => { } } -const handleJSONRCP = async (request: string, context: serverContext) => { - return await new Promise((resolve, reject) => { - // @ts-ignore - broken typing for server.call - should be able to accept 3 arguments - // https://github.com/tedeh/jayson#server-context - // https://github.com/tedeh/jayson/pull/152 - rpcServer.call(request, context, (error, response) => { - // resolve both error and success responses - error ? resolve(error) : resolve(response) - }) +const handleJSONRCP = async (request: string, context: ServerContext) => { + return await new Promise(resolve => { + callRPCServer( + request, + context, + (error: jayson.JSONRPCErrorLike, response: jayson.JSONRPCResultLike) => { + // resolve both error and success responses + error ? resolve(error) : resolve(response) + }, + ) }) } export const handleMessage = async ( message: string, - context: serverContext, + context: ServerContext, ) => { if (message.includes('jsonrpc')) { return await handleJSONRCP(message, context) diff --git a/explorer/src/server/rpcMethods/upsertJobRun.ts b/explorer/src/server/rpcMethods/upsertJobRun.ts index d23c48bcddd..0b3f896cbeb 100644 --- a/explorer/src/server/rpcMethods/upsertJobRun.ts +++ b/explorer/src/server/rpcMethods/upsertJobRun.ts @@ -1,7 +1,7 @@ import { fromJSONObject, saveJobRunTree } from '../../entity/JobRun' import { logger } from '../../logging' import { getDb } from '../../database' -import { serverContext } from './../handleMessage' +import { ServerContext } from './../handleMessage' import jayson from 'jayson' // default invalid params error @@ -10,7 +10,7 @@ const invalidParamsError = new jayson.Server().error(INVALID_PARAMS) export default async ( payload: object, - context: serverContext, + context: ServerContext, callback: jayson.JSONRPCCallbackTypePlain, ) => { try { diff --git a/explorer/src/server/rpcServer.ts b/explorer/src/server/rpcServer.ts index 33db0adc3c2..09e8966eb46 100644 --- a/explorer/src/server/rpcServer.ts +++ b/explorer/src/server/rpcServer.ts @@ -5,4 +5,16 @@ const serverOptions = { useContext: true, // permits passing extra data object to RPC methods as 'server context' } -export default new jayson.Server(rpcMethods, serverOptions) +export const rpcServer = new jayson.Server(rpcMethods, serverOptions) + +// broken typing for server.call - should be able to accept 3 arguments +// https://github.com/tedeh/jayson#server-context +// https://github.com/tedeh/jayson/pull/152 + +type callFunction = ( + request: jayson.JSONRPCRequestLike | Array, + context: object, + originalCallback?: jayson.JSONRPCCallbackType, +) => void + +export const callRPCServer: callFunction = rpcServer.call.bind(rpcServer) From 7fca42951bcc033b44165448138a96400dd3f097 Mon Sep 17 00:00:00 2001 From: Ryan Hall Date: Fri, 1 Nov 2019 10:48:41 -0400 Subject: [PATCH 067/199] move server file to workspace root --- explorer/src/{server/index.ts => server.ts} | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) rename explorer/src/{server/index.ts => server.ts} (86%) diff --git a/explorer/src/server/index.ts b/explorer/src/server.ts similarity index 86% rename from explorer/src/server/index.ts rename to explorer/src/server.ts index b88cf7bc6ca..57d0b274ee8 100644 --- a/explorer/src/server/index.ts +++ b/explorer/src/server.ts @@ -3,11 +3,11 @@ import helmet from 'helmet' import http from 'http' import mime from 'mime-types' import cookieSession from 'cookie-session' -import adminAuth from '../middleware/adminAuth' -import * as controllers from '../controllers' -import { addRequestLogging, logger } from '../logging' -import { bootstrapRealtime } from './realtime' -import seed from '../seed' +import adminAuth from './middleware/adminAuth' +import * as controllers from './controllers' +import { addRequestLogging, logger } from './logging' +import { bootstrapRealtime } from './server/realtime' +import seed from './seed' export const DEFAULT_PORT = parseInt(process.env.SERVER_PORT, 10) || 8080 export const COOKIE_EXPIRATION_MS = 86400000 // 1 day in ms From 73e3400e8ab28effb88d4c933c537312c0ddefa8 Mon Sep 17 00:00:00 2001 From: Ryan Hall Date: Fri, 1 Nov 2019 11:03:48 -0400 Subject: [PATCH 068/199] update yarn.lock --- yarn.lock | 1004 +++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 941 insertions(+), 63 deletions(-) diff --git a/yarn.lock b/yarn.lock index ad585818a46..e43daac99a3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,6 +2,201 @@ # yarn lockfile v1 +"@0x/assert@^2.1.6": + version "2.1.6" + resolved "https://registry.yarnpkg.com/@0x/assert/-/assert-2.1.6.tgz#61c5854b555bca1f1f0503754f2fd0169bee0ef1" + integrity sha512-Gu8eBnFdEuIAH2GubWYOSVz/BIoRccKof68AziduYDHxh4nSPM6NUH52xtfUGk4nXljiOXU1XHZJhcjTObI+8Q== + dependencies: + "@0x/json-schemas" "^4.0.2" + "@0x/typescript-typings" "^4.3.0" + "@0x/utils" "^4.5.2" + lodash "^4.17.11" + valid-url "^1.0.9" + +"@0x/dev-utils@^2.3.3": + version "2.3.3" + resolved "https://registry.yarnpkg.com/@0x/dev-utils/-/dev-utils-2.3.3.tgz#9b6df00fea357fa6da02b35ca93fc89d100e1992" + integrity sha512-Pi664W/jj1U6WU+kHEPyKpflBnmKRsclB69RaL7wpnvOOFjAPhFV2/0FvIZ25w62rMzKEpfD1/1NcZ7NjAk7OQ== + dependencies: + "@0x/subproviders" "^5.0.4" + "@0x/types" "^2.4.3" + "@0x/typescript-typings" "^4.3.0" + "@0x/utils" "^4.5.2" + "@0x/web3-wrapper" "^6.0.13" + "@types/web3-provider-engine" "^14.0.0" + chai "^4.0.1" + ethereum-types "^2.1.6" + lodash "^4.17.11" + +"@0x/json-schemas@^4.0.2": + version "4.0.2" + resolved "https://registry.yarnpkg.com/@0x/json-schemas/-/json-schemas-4.0.2.tgz#6f7c1dcde04d3acc3e8ca2f24177b9705c10e772" + integrity sha512-JHOwESZeWKAzT5Z42ZNvOvQUQ5vuRIFQWS0FNjYwV8Cv4/dRlLHd7kwxxsvlm9NxgXnOW0ddEDBbVGxhVSYNIg== + dependencies: + "@0x/typescript-typings" "^4.3.0" + "@types/node" "*" + jsonschema "^1.2.0" + lodash.values "^4.3.0" + +"@0x/sol-compiler@^3.1.15": + version "3.1.15" + resolved "https://registry.yarnpkg.com/@0x/sol-compiler/-/sol-compiler-3.1.15.tgz#aaaad55008dddd69ad1e3226aa4a2832e0dd13b3" + integrity sha512-IobhcQ/whFRL942/ykKc0fV6/YstHhvnQJ0noUZ9GabMDtaBlW6k5vAerSkXZU/YyOd8sD9nw7QSm295D6HoTA== + dependencies: + "@0x/assert" "^2.1.6" + "@0x/json-schemas" "^4.0.2" + "@0x/sol-resolver" "^2.0.11" + "@0x/types" "^2.4.3" + "@0x/typescript-typings" "^4.3.0" + "@0x/utils" "^4.5.2" + "@0x/web3-wrapper" "^6.0.13" + "@types/yargs" "^11.0.0" + chalk "^2.3.0" + chokidar "^3.0.2" + ethereum-types "^2.1.6" + ethereumjs-util "^5.1.1" + lodash "^4.17.11" + mkdirp "^0.5.1" + pluralize "^7.0.0" + require-from-string "^2.0.1" + semver "5.5.0" + solc "^0.5.5" + source-map-support "^0.5.0" + web3-eth-abi "^1.0.0-beta.24" + yargs "^10.0.3" + +"@0x/sol-resolver@^2.0.11": + version "2.0.11" + resolved "https://registry.yarnpkg.com/@0x/sol-resolver/-/sol-resolver-2.0.11.tgz#282d545a423baf0d478c498fd3adf607339d5504" + integrity sha512-TGvkuWoEMghPB4OZaPz49OJ/J0cw/I3yV7VLbE/OeN0K/J2P8uOkzhNgWLOVSi8EK3hcJ1JVc0iDSkCnzGj4xQ== + dependencies: + "@0x/types" "^2.4.3" + "@0x/typescript-typings" "^4.3.0" + lodash "^4.17.11" + +"@0x/sol-trace@^2.0.20": + version "2.0.20" + resolved "https://registry.yarnpkg.com/@0x/sol-trace/-/sol-trace-2.0.20.tgz#4c14af3f5c30ab50882e9667926ca9533446711d" + integrity sha512-lkw+8l+InqXKoyVFPCxdLA0oZnLLPvmpFm+lIJwEkPjrbwIyhvQbwSwyzPW/1XUWj0wIwWOpq6PSyofWZgt4KA== + dependencies: + "@0x/sol-tracing-utils" "^6.0.19" + "@0x/subproviders" "^5.0.4" + "@0x/typescript-typings" "^4.3.0" + chalk "^2.3.0" + ethereum-types "^2.1.6" + ethereumjs-util "^5.1.1" + lodash "^4.17.11" + loglevel "^1.6.1" + web3-provider-engine "14.0.6" + +"@0x/sol-tracing-utils@^6.0.19": + version "6.0.19" + resolved "https://registry.yarnpkg.com/@0x/sol-tracing-utils/-/sol-tracing-utils-6.0.19.tgz#3c119c7e5b6d2bd1c94663985b7641aec85ef625" + integrity sha512-5tQOEo+dUYWiclT7UDy5IRm/EPEwDzCqAY3J8l0ecIsssBk0r2YLKKf/ugWkdwASGeAxiepjYpC+mxcxOT6yDA== + dependencies: + "@0x/dev-utils" "^2.3.3" + "@0x/sol-compiler" "^3.1.15" + "@0x/sol-resolver" "^2.0.11" + "@0x/subproviders" "^5.0.4" + "@0x/typescript-typings" "^4.3.0" + "@0x/utils" "^4.5.2" + "@0x/web3-wrapper" "^6.0.13" + "@types/solidity-parser-antlr" "^0.2.3" + chalk "^2.3.0" + ethereum-types "^2.1.6" + ethereumjs-util "^5.1.1" + ethers "~4.0.4" + glob "^7.1.2" + istanbul "^0.4.5" + lodash "^4.17.11" + loglevel "^1.6.1" + mkdirp "^0.5.1" + rimraf "^2.6.2" + semaphore-async-await "^1.5.1" + solc "^0.5.5" + solidity-parser-antlr "^0.4.2" + +"@0x/subproviders@^5.0.4": + version "5.0.4" + resolved "https://registry.yarnpkg.com/@0x/subproviders/-/subproviders-5.0.4.tgz#e4b165634ef6a50c4bd41baacf0dbd2a9390c2f8" + integrity sha512-1LiGcOXkP5eUOl/0JRqkrqYtCvIL4NJj1GbbLIRq4TvkfqrRbF7zJM2SaayxPo3Z48zVsqk0ZE5+RrNAdK/Rrg== + dependencies: + "@0x/assert" "^2.1.6" + "@0x/types" "^2.4.3" + "@0x/typescript-typings" "^4.3.0" + "@0x/utils" "^4.5.2" + "@0x/web3-wrapper" "^6.0.13" + "@ledgerhq/hw-app-eth" "^4.3.0" + "@ledgerhq/hw-transport-u2f" "4.24.0" + "@types/hdkey" "^0.7.0" + "@types/web3-provider-engine" "^14.0.0" + bip39 "^2.5.0" + bn.js "^4.11.8" + ethereum-types "^2.1.6" + ethereumjs-tx "^1.3.5" + ethereumjs-util "^5.1.1" + ganache-core "^2.6.0" + hdkey "^0.7.1" + json-rpc-error "2.0.0" + lodash "^4.17.11" + semaphore-async-await "^1.5.1" + web3-provider-engine "14.0.6" + optionalDependencies: + "@ledgerhq/hw-transport-node-hid" "^4.3.0" + +"@0x/types@^2.4.3": + version "2.4.3" + resolved "https://registry.yarnpkg.com/@0x/types/-/types-2.4.3.tgz#ea014889789e9013fdf48ce97b79f2c016e10fb3" + integrity sha512-3z4ca9fb9pyTu9lJhTSll5EuEthkA3tLAayyZixCoCnwi4ok6PJ83PnMMsSxlRY2iXr7QGbrQr6nU64YWk2WjA== + dependencies: + "@types/node" "*" + bignumber.js "~8.0.2" + ethereum-types "^2.1.6" + +"@0x/typescript-typings@^4.3.0": + version "4.3.0" + resolved "https://registry.yarnpkg.com/@0x/typescript-typings/-/typescript-typings-4.3.0.tgz#4813a996ac5101841d1c22f4aa1738ab56168857" + integrity sha512-6IH2JyKyl33+40tJ5rEhaMPTS2mVuRvoNmoXlCd/F0GPYSsDHMGObIXOkx+Qsw5SyCmqNs/3CTLeeCCqiSUdaw== + dependencies: + "@types/bn.js" "^4.11.0" + "@types/react" "*" + bignumber.js "~8.0.2" + ethereum-types "^2.1.6" + popper.js "1.14.3" + +"@0x/utils@^4.5.2": + version "4.5.2" + resolved "https://registry.yarnpkg.com/@0x/utils/-/utils-4.5.2.tgz#6cc89f2d0dda341e0fb4e76049a35abfb67a4ac5" + integrity sha512-NWfNcvyiOhouk662AWxX0ZVe4ednBZJS9WZT/by3DBCY/WvN7WHMpEy9M5rBCxO+JJndLYeB5eBztDp7W+Ytkw== + dependencies: + "@0x/types" "^2.4.3" + "@0x/typescript-typings" "^4.3.0" + "@types/node" "*" + abortcontroller-polyfill "^1.1.9" + bignumber.js "~8.0.2" + chalk "^2.3.0" + detect-node "2.0.3" + ethereum-types "^2.1.6" + ethereumjs-util "^5.1.1" + ethers "~4.0.4" + isomorphic-fetch "2.2.1" + js-sha3 "^0.7.0" + lodash "^4.17.11" + +"@0x/web3-wrapper@^6.0.13": + version "6.0.13" + resolved "https://registry.yarnpkg.com/@0x/web3-wrapper/-/web3-wrapper-6.0.13.tgz#2e666221bd44ceebe02762028214d4aa41ad7247" + integrity sha512-LQjKBCCNdkJuhcJld+/sy+G0+sJu5qp9VDNNwJGLDxWIJpgoshhUpBPi7vUnZ79UY4SYuNcC4yM9yI61cs7ZiA== + dependencies: + "@0x/assert" "^2.1.6" + "@0x/json-schemas" "^4.0.2" + "@0x/typescript-typings" "^4.3.0" + "@0x/utils" "^4.5.2" + ethereum-types "^2.1.6" + ethereumjs-util "^5.1.1" + ethers "~4.0.4" + lodash "^4.17.11" + "@babel/cli@^7.1.5": version "7.6.0" resolved "https://registry.npmjs.org/@babel/cli/-/cli-7.6.0.tgz#1470a04394eaf37862989ea4912adf440fa6ff8d" @@ -1487,6 +1682,75 @@ "@types/istanbul-reports" "^1.1.1" "@types/yargs" "^13.0.0" +"@ledgerhq/devices@^4.73.4": + version "4.73.4" + resolved "https://registry.yarnpkg.com/@ledgerhq/devices/-/devices-4.73.4.tgz#0a1866fd1f10e2afc57c5f002f60d52ad5e6bb99" + integrity sha512-oEhd1UlbkdRFe5ZQCr8aXZ4Z/uFvrTsIhXv6bDlqA0sR2VYZRqQPR4C0xLdcDo3NG88fP+6Xfi6wq3wBbT5n3A== + dependencies: + "@ledgerhq/errors" "^4.73.4" + "@ledgerhq/logs" "^4.72.0" + rxjs "^6.5.3" + +"@ledgerhq/errors@^4.73.4": + version "4.73.4" + resolved "https://registry.yarnpkg.com/@ledgerhq/errors/-/errors-4.73.4.tgz#df4705d11350f252c0f0c202e88a32723f409484" + integrity sha512-5Wz48jm0NozGkipPcYRAAFcwm50xLhCWSpmP96Fs7v8BtM72E1AtqOud5SCyTKS6OP10mPuDB1459b7FGFoBcw== + +"@ledgerhq/hw-app-eth@^4.3.0": + version "4.73.4" + resolved "https://registry.yarnpkg.com/@ledgerhq/hw-app-eth/-/hw-app-eth-4.73.4.tgz#3696df17c819dea213da56c4841341db7fb571aa" + integrity sha512-MJZlzVj6s/ZRQBikNo6Z9n5N/RQLc78xF+Esxo8C9RnFj0K0mTW6J0fqwqRwyfSJrJblAQE3ucTNAoXI4vJDPg== + dependencies: + "@ledgerhq/errors" "^4.73.4" + "@ledgerhq/hw-transport" "^4.73.4" + +"@ledgerhq/hw-transport-node-hid-noevents@^4.73.4": + version "4.73.4" + resolved "https://registry.yarnpkg.com/@ledgerhq/hw-transport-node-hid-noevents/-/hw-transport-node-hid-noevents-4.73.4.tgz#964d3699a3110a48663247ba861278baeaffcf79" + integrity sha512-UQ8ntXxftN6PeASD9UpWs2rKanEY66BCwPSMeMJ6PpQC1IRO/HYJ0bdfaLrIWiAr+zJ4BjDRPui1jX5uDmMs3A== + dependencies: + "@ledgerhq/devices" "^4.73.4" + "@ledgerhq/errors" "^4.73.4" + "@ledgerhq/hw-transport" "^4.73.4" + "@ledgerhq/logs" "^4.72.0" + node-hid "^0.7.9" + +"@ledgerhq/hw-transport-node-hid@^4.3.0": + version "4.73.4" + resolved "https://registry.yarnpkg.com/@ledgerhq/hw-transport-node-hid/-/hw-transport-node-hid-4.73.4.tgz#ef9efdcfbcb08146a2087beccc47865e99966c78" + integrity sha512-rSzOjnjRzVBkH6hrbKG6Y979LR/oGeihyTE2Bt7UHeY8QepJGgkJavJkmKHq71QK6RbT5ntBfhErbNvrno5B1Q== + dependencies: + "@ledgerhq/devices" "^4.73.4" + "@ledgerhq/errors" "^4.73.4" + "@ledgerhq/hw-transport" "^4.73.4" + "@ledgerhq/hw-transport-node-hid-noevents" "^4.73.4" + "@ledgerhq/logs" "^4.72.0" + lodash "^4.17.15" + node-hid "^0.7.9" + usb "^1.6.0" + +"@ledgerhq/hw-transport-u2f@4.24.0": + version "4.24.0" + resolved "https://registry.yarnpkg.com/@ledgerhq/hw-transport-u2f/-/hw-transport-u2f-4.24.0.tgz#d67cfc4abf6d9a900ed45f2e2df7fe06dfdff5c7" + integrity sha512-/gFjhkM0sJfZ7iUf8HoIkGufAWgPacrbb1LW0TvWnZwvsATVJ1BZJBtrr90Wo401PKsjVwYtFt3Ce4gOAUv9jQ== + dependencies: + "@ledgerhq/hw-transport" "^4.24.0" + u2f-api "0.2.7" + +"@ledgerhq/hw-transport@^4.24.0", "@ledgerhq/hw-transport@^4.73.4": + version "4.73.4" + resolved "https://registry.yarnpkg.com/@ledgerhq/hw-transport/-/hw-transport-4.73.4.tgz#e3b58eebbc99b003406838660b7d17611e02ebc7" + integrity sha512-UAw7v+le8pyEz+q2sA0TVS9WGmifKqGxuwcYz+hN1quJkfTmivvcHCuSF5t7BDCuuuRNoCT36YjnIzf363Tf2w== + dependencies: + "@ledgerhq/devices" "^4.73.4" + "@ledgerhq/errors" "^4.73.4" + events "^3.0.0" + +"@ledgerhq/logs@^4.72.0": + version "4.72.0" + resolved "https://registry.yarnpkg.com/@ledgerhq/logs/-/logs-4.72.0.tgz#43df23af013ad1135407e5cf33ca6e4c4c7708d5" + integrity sha512-o+TYF8vBcyySRsb2kqBDv/KMeme8a2nwWoG+lAWzbDmWfb2/MrVWYCVYDYvjXdSoI/Cujqy1i0gIDrkdxa9chA== + "@machinomy/types-truffle-contract@^0.2.0": version "0.2.0" resolved "https://registry.yarnpkg.com/@machinomy/types-truffle-contract/-/types-truffle-contract-0.2.0.tgz#0f91f6f8fa92b8b1e07ba1679027ac14eba1a73f" @@ -2382,7 +2646,7 @@ resolved "https://registry.npmjs.org/@types/bcrypt/-/bcrypt-3.0.0.tgz#851489a9065a067cb7f3c9cbe4ce9bed8bba0876" integrity sha512-nohgNyv+1ViVcubKBh0+XiNJ3dO8nYu///9aJ4cgSqv70gBL+94SNy/iC2NLzKPT2Zt/QavrOkBVbZRLZmw6NQ== -"@types/bn.js@*", "@types/bn.js@^4.11.5": +"@types/bn.js@*", "@types/bn.js@^4.11.0", "@types/bn.js@^4.11.5": version "4.11.5" resolved "https://registry.npmjs.org/@types/bn.js/-/bn.js-4.11.5.tgz#40e36197433f78f807524ec623afcf0169ac81dc" integrity sha512-AEAZcIZga0JgVMHNtl1CprA/hXX7/wPt79AgR4XqaDt7jyj3QWYw6LPoOiznPtugDmlubUnAahMs2PFxGcQrng== @@ -2421,7 +2685,7 @@ resolved "https://registry.npmjs.org/@types/classnames/-/classnames-2.2.9.tgz#d868b6febb02666330410fe7f58f3c4b8258be7b" integrity sha512-MNl+rT5UmZeilaPxAVs6YaPC2m6aA8rofviZbhbxpPpl61uKodfdQVsBtgJGTqGizEf02oW3tsVe7FYB8kK14A== -"@types/connect@*": +"@types/connect@*", "@types/connect@^3.4.32": version "3.4.32" resolved "https://registry.npmjs.org/@types/connect/-/connect-3.4.32.tgz#aa0e9616b9435ccad02bc52b5b454ffc2c70ba28" integrity sha512-4r8qa0quOvh7lGD0pre62CAb1oni1OO6ecJLGCezTmhQ8Fz50Arx9RUszryR8KlgK6avuSXvviL6yWyViQABOg== @@ -2466,6 +2730,13 @@ resolved "https://registry.yarnpkg.com/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#1ee30d79544ca84d68d4b3cdb0af4f205663dd2d" integrity sha512-OCutwjDZ4aFS6PB1UZ988C4YgwlBHJd6wCeQqaLdmadZ/7e+w79+hbMUFC1QXDNCmdyoRfAFdm0RypzwR+Qpag== +"@types/ethereum-protocol@*": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@types/ethereum-protocol/-/ethereum-protocol-1.0.0.tgz#416e3827d5fdfa4658b0045b35a008747871b271" + integrity sha512-3DiI3Zxf81CgX+VhxNNFJBv/sfr1BFBKQK2sQ85hU9FwWJJMWV5gRDV79OUNShiwj3tYYIezU94qpucsb3dThQ== + dependencies: + bignumber.js "7.2.1" + "@types/ethereumjs-abi@^0.6.3": version "0.6.3" resolved "https://registry.yarnpkg.com/@types/ethereumjs-abi/-/ethereumjs-abi-0.6.3.tgz#eb5ed09fd86b9e2b1c0eb75d1e9bc29c50715c86" @@ -2494,6 +2765,14 @@ "@types/node" "*" "@types/range-parser" "*" +"@types/express-serve-static-core@^4.16.9": + version "4.16.10" + resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.16.10.tgz#3c1313c6e6b75594561b473a286f016a9abf2132" + integrity sha512-gM6evDj0OvTILTRKilh9T5dTaGpv1oYiFcJAfgSejuMJgGJUsD9hKEU2lB4aiTNy4WwChxRnjfYFuBQsULzsJw== + dependencies: + "@types/node" "*" + "@types/range-parser" "*" + "@types/express-winston@^3.0.1": version "3.0.4" resolved "https://registry.npmjs.org/@types/express-winston/-/express-winston-3.0.4.tgz#0818399a094374f16c39bcba24ebcea3891791c6" @@ -2517,7 +2796,7 @@ resolved "https://registry.yarnpkg.com/@types/fetch-mock/-/fetch-mock-7.3.1.tgz#df7421e8bcb351b430bfbfa5c52bb353826ac94f" integrity sha512-2U4vZWHNbsbK7TRmizgr/pbKe0FKopcxu+hNDtIBDiM1wvrKRItybaYj7VQ6w/hZJStU/JxRiNi5ww4YDEvKbA== -"@types/glob@^7.1.1": +"@types/glob@*", "@types/glob@^7.1.1": version "7.1.1" resolved "https://registry.npmjs.org/@types/glob/-/glob-7.1.1.tgz#aa59a1c6e3fbc421e07ccd31a944c30eba521575" integrity sha512-1Bh06cbWJUHMC97acuD6UMG29nMt0Aqz1vF3guLfG+kHHJhy3AyohZFFxYk2f7Q1SQIrNwvncxAE0N/9s70F2w== @@ -2526,6 +2805,13 @@ "@types/minimatch" "*" "@types/node" "*" +"@types/hdkey@^0.7.0": + version "0.7.1" + resolved "https://registry.yarnpkg.com/@types/hdkey/-/hdkey-0.7.1.tgz#9bc63ebbe96b107b277b65ea7a95442a677d0d61" + integrity sha512-4Kkr06hq+R8a9EzVNqXGOY2x1xA7dhY6qlp6OvaZ+IJy1BCca1Cv126RD9X7CMJoXoLo8WvAizy8gQHpqW6K0Q== + dependencies: + "@types/node" "*" + "@types/helmet@0.0.43": version "0.0.43" resolved "https://registry.npmjs.org/@types/helmet/-/helmet-0.0.43.tgz#2a3f7da435088501f2901f68c3e402f79e5b2a9b" @@ -2611,6 +2897,11 @@ resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.138.tgz#34f52640d7358230308344e579c15b378d91989e" integrity sha512-A4uJgHz4hakwNBdHNPdxOTkYmXNgmUAKLbXZ7PKGslgeV0Mb8P3BlbYfPovExek1qnod4pDfRbxuzcVs3dlFLg== +"@types/lodash@^4.14.139": + version "4.14.144" + resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.144.tgz#12e57fc99064bce45e5ab3c8bc4783feb75eab8e" + integrity sha512-ogI4g9W5qIQQUhXAclq6zhqgqNUr7UlFaqDHbch7WLSLeeM/7d3CRaw7GLajxvyFvhJqw4Rpcz5bhoaYtIx6Tg== + "@types/logform@*": version "1.2.0" resolved "https://registry.npmjs.org/@types/logform/-/logform-1.2.0.tgz#4ead916c7eb1ee99d726bfa849b6a2ee5ea50e3e" @@ -2683,6 +2974,11 @@ resolved "https://registry.npmjs.org/@types/node/-/node-11.13.20.tgz#da42fe93d6599f80b35ffeb5006f4c31f40d89ea" integrity sha512-JE0UpLWZTV1sGcaj0hN+Q0760OEjpgyFJ06DOMVW6qKBducKdJQaIw0TGL6ccj7VXRduIOHLWQi+tHwulZJHVQ== +"@types/node@^12.7.7": + version "12.12.3" + resolved "https://registry.yarnpkg.com/@types/node/-/node-12.12.3.tgz#ebfe83507ac506bc3486314a8aa395be66af8d23" + integrity sha512-opgSsy+cEF9N8MgaVPnWVtdJ3o4mV2aMHvDq7thkQUFt0EuOHJon4rQpJfhjmNHB+ikl0Cd6WhWIErOyQ+f7tw== + "@types/numeral@^0.0.25": version "0.0.25" resolved "https://registry.yarnpkg.com/@types/numeral/-/numeral-0.0.25.tgz#b6f55062827a4787fe4ab151cf3412a468e65271" @@ -2859,6 +3155,19 @@ "@types/express-serve-static-core" "*" "@types/mime" "*" +"@types/shelljs@^0.8.5": + version "0.8.5" + resolved "https://registry.yarnpkg.com/@types/shelljs/-/shelljs-0.8.5.tgz#1e507b2f6d1f893269bd3e851ec24419ef9beeea" + integrity sha512-bZgjwIWu9gHCjirKJoOlLzGi5N0QgZ5t7EXEuoqyWCHTuSddURXo3FOBYDyRPNOWzZ6NbkLvZnVkn483Y/tvcQ== + dependencies: + "@types/glob" "*" + "@types/node" "*" + +"@types/solidity-parser-antlr@^0.2.3": + version "0.2.3" + resolved "https://registry.yarnpkg.com/@types/solidity-parser-antlr/-/solidity-parser-antlr-0.2.3.tgz#bb2d9c6511bf483afe4fc3e2714da8a924e59e3f" + integrity sha512-FoSyZT+1TTaofbEtGW1oC9wHND1YshvVeHerME/Jh6gIdHbBAWFW8A97YYqO/dpHcFjIwEPEepX0Efl2ckJgwA== + "@types/stack-utils@^1.0.1": version "1.0.1" resolved "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-1.0.1.tgz#0a851d3bd96498fa25c33ab7278ed3bd65f06c3e" @@ -2921,6 +3230,13 @@ resolved "https://registry.npmjs.org/@types/validator/-/validator-10.11.2.tgz#48b60ca2cca927081f37a1ad1de3e25d04abc9f0" integrity sha512-k/ju1RsdP5ACFUWebqsyEy0avP5uNJCs2p3pmTHzOZdd4gMSAJTq7iUEHFY3tt3emBrPTm6oGvfZ4SzcqOgLPQ== +"@types/web3-provider-engine@^14.0.0": + version "14.0.0" + resolved "https://registry.yarnpkg.com/@types/web3-provider-engine/-/web3-provider-engine-14.0.0.tgz#43adc3b39dc9812b82aef8cd2d66577665ad59b0" + integrity sha512-yHr8mX2SoX3JNyfqdLXdO1UobsGhfiwSgtekbVxKLQrzD7vtpPkKbkIVsPFOhvekvNbPsCmDyeDCLkpeI9gSmA== + dependencies: + "@types/ethereum-protocol" "*" + "@types/web3@^1.0.0", "@types/web3@^1.0.19": version "1.0.19" resolved "https://registry.npmjs.org/@types/web3/-/web3-1.0.19.tgz#46b85d91d398ded9ab7c85a5dd57cb33ac558924" @@ -2946,6 +3262,11 @@ resolved "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-13.0.0.tgz#453743c5bbf9f1bed61d959baab5b06be029b2d0" integrity sha512-wBlsw+8n21e6eTd4yVv8YD/E3xq0O6nNnJIquutAsFGE7EyMKz7W6RNT6BRu1SmdgmlCZ9tb0X+j+D6HGr8pZw== +"@types/yargs@^11.0.0": + version "11.1.3" + resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-11.1.3.tgz#33c8ebf05f78f1edeb249c1cde1a42ae57f5664e" + integrity sha512-moBUF6X8JsK5MbLZGP3vCfG/TVHZHsaePj3EimlLKp8+ESUjGjpXalxyn90a2L9fTM2ZGtW4swb6Am1DvVRNGA== + "@types/yargs@^12.0.10": version "12.0.12" resolved "https://registry.npmjs.org/@types/yargs/-/yargs-12.0.12.tgz#45dd1d0638e8c8f153e87d296907659296873916" @@ -3237,6 +3558,14 @@ resolved "https://registry.npmjs.org/@zeit/schemas/-/schemas-2.6.0.tgz#004e8e553b4cd53d538bd38eac7bcbf58a867fe3" integrity sha512-uUrgZ8AxS+Lio0fZKAipJjAh415JyrOZowliZAzmnJSsf7piVL5w+G0+gFJ0KSu3QRhvui/7zuvpLz03YjXAhg== +JSONStream@^1.3.1: + version "1.3.5" + resolved "https://registry.yarnpkg.com/JSONStream/-/JSONStream-1.3.5.tgz#3208c1f08d3a4d99261ab64f92302bc15e111ca0" + integrity sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ== + dependencies: + jsonparse "^1.2.0" + through ">=2.2.7 <3" + abab@^1.0.3: version "1.0.4" resolved "https://registry.npmjs.org/abab/-/abab-1.0.4.tgz#5faad9c2c07f60dd76770f71cf025b62a63cfd4e" @@ -3252,6 +3581,11 @@ abbrev@1: resolved "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q== +abbrev@1.0.x: + version "1.0.9" + resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.0.9.tgz#91b4792588a7738c25f35dd6f63752a2f8776135" + integrity sha1-kbR5JYinc4wl813W9jdSovh3YTU= + abort-controller@^3.0.0: version "3.0.0" resolved "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz#eaf54d53b62bae4138e809ca225c8439a6efb392" @@ -3259,6 +3593,11 @@ abort-controller@^3.0.0: dependencies: event-target-shim "^5.0.0" +abortcontroller-polyfill@^1.1.9: + version "1.4.0" + resolved "https://registry.yarnpkg.com/abortcontroller-polyfill/-/abortcontroller-polyfill-1.4.0.tgz#0d5eb58e522a461774af8086414f68e1dda7a6c4" + integrity sha512-3ZFfCRfDzx3GFjO6RAkYx81lPGpUS20ISxux9gLxuKnqafNcFQo59+IoZqpO2WvQlyc287B62HDnDdNYRmlvWA== + abstract-leveldown@3.0.0: version "3.0.0" resolved "https://registry.npmjs.org/abstract-leveldown/-/abstract-leveldown-3.0.0.tgz#5cb89f958a44f526779d740d1440e743e0c30a57" @@ -3597,6 +3936,14 @@ anymatch@^2.0.0: micromatch "^3.1.4" normalize-path "^2.1.1" +anymatch@~3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.1.tgz#c55ecf02185e2469259399310c173ce31233b142" + integrity sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg== + dependencies: + normalize-path "^3.0.0" + picomatch "^2.0.4" + app-module-path@^2.2.0: version "2.2.0" resolved "https://registry.npmjs.org/app-module-path/-/app-module-path-2.2.0.tgz#641aa55dfb7d6a6f0a8141c4b9c0aa50b6c24dd5" @@ -3993,6 +4340,11 @@ async-settle@^1.0.0: dependencies: async-done "^1.2.2" +async@1.x, async@^1.4.2, async@^1.5.2: + version "1.5.2" + resolved "https://registry.npmjs.org/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a" + integrity sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo= + async@2.6.1: version "2.6.1" resolved "https://registry.yarnpkg.com/async/-/async-2.6.1.tgz#b245a23ca71930044ec53fa46aa00a3e87c6a610" @@ -4007,11 +4359,6 @@ async@2.6.2: dependencies: lodash "^4.17.11" -async@^1.4.2, async@^1.5.2: - version "1.5.2" - resolved "https://registry.npmjs.org/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a" - integrity sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo= - async@^2.0.1, async@^2.1.2, async@^2.1.4, async@^2.3.0, async@^2.4.0, async@^2.5.0, async@^2.6.1, async@~2.6.0: version "2.6.3" resolved "https://registry.npmjs.org/async/-/async-2.6.3.tgz#d72625e2344a3656e3a3ad4fa749fa83299d82ff" @@ -5085,7 +5432,7 @@ big.js@^5.2.2: resolved "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328" integrity sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ== -bignumber.js@^7.2.1: +bignumber.js@7.2.1, bignumber.js@^7.2.1: version "7.2.1" resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-7.2.1.tgz#80c048759d826800807c4bfd521e50edbba57a5f" integrity sha512-S4XzBk5sMB+Rcb/LNcpzXr57VRTxgAvaAEDAl1AwRx27j00hT84O6OkteE7u8UB3NuaaygCRrEpqox4uDOrbdQ== @@ -5100,12 +5447,22 @@ bignumber.js@^9.0.0: resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-9.0.0.tgz#805880f84a329b5eac6e7cb6f8274b6d82bdf075" integrity sha512-t/OYhhJ2SD+YGBQcjY8GzzDHEk9f3nerxjtfa6tlMXfe7frs/WozhvCNoGvpM0P3bNf3Gq5ZRMlGr5f3r4/N8A== +bignumber.js@~8.0.2: + version "8.0.2" + resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-8.0.2.tgz#d8c4e1874359573b1ef03011a2d861214aeef137" + integrity sha512-EiuvFrnbv0jFixEQ9f58jo7X0qI2lNGIr/MxntmVzQc5JUweDSh8y8hbTCAomFtqwUPIOWcLXP0VEOSZTG7FFw== + binary-extensions@^1.0.0: version "1.13.1" resolved "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz#598afe54755b2868a5330d2aff9d4ebb53209b65" integrity sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw== -bindings@^1.2.1, bindings@^1.3.0, bindings@^1.3.1: +binary-extensions@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.0.0.tgz#23c0df14f6a88077f5f986c0d167ec03c3d5537c" + integrity sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow== + +bindings@^1.2.1, bindings@^1.3.0, bindings@^1.3.1, bindings@^1.4.0, bindings@^1.5.0: version "1.5.0" resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.5.0.tgz#10353c9e945334bc0511a6d90b38fbc7c9c504df" integrity sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ== @@ -5136,6 +5493,17 @@ bip39@2.5.0: safe-buffer "^5.0.1" unorm "^1.3.3" +bip39@^2.5.0: + version "2.6.0" + resolved "https://registry.yarnpkg.com/bip39/-/bip39-2.6.0.tgz#9e3a720b42ec8b3fbe4038f1e445317b6a99321c" + integrity sha512-RrnQRG2EgEoqO24ea+Q/fftuPUZLmrEM3qNhhGsA3PbaXaCW791LTzPuVyx/VprXQcTbPJ3K3UeTna8ZnVl2sg== + dependencies: + create-hash "^1.1.0" + pbkdf2 "^3.0.9" + randombytes "^2.0.1" + safe-buffer "^5.0.1" + unorm "^1.3.3" + bip39@^3.0.2: version "3.0.2" resolved "https://registry.npmjs.org/bip39/-/bip39-3.0.2.tgz#2baf42ff3071fc9ddd5103de92e8f80d9257ee32" @@ -5161,6 +5529,13 @@ bl@^1.0.0: readable-stream "^2.3.5" safe-buffer "^5.1.1" +bl@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/bl/-/bl-3.0.0.tgz#3611ec00579fd18561754360b21e9f784500ff88" + integrity sha512-EUAyP5UHU5hxF8BPT0LKW8gjYLhq1DQIcneOX/pL/m2Alo+OYDQAJlHq+yseMP50Os2nHXOSic6Ss3vSQeyf4A== + dependencies: + readable-stream "^3.0.1" + blob@0.0.5: version "0.0.5" resolved "https://registry.npmjs.org/blob/-/blob-0.0.5.tgz#d680eeef25f8cd91ad533f5b01eed48e64caf683" @@ -5322,7 +5697,7 @@ braces@^2.3.1, braces@^2.3.2: split-string "^3.0.2" to-regex "^3.0.1" -braces@^3.0.1: +braces@^3.0.1, braces@~3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== @@ -5912,7 +6287,7 @@ chai-bn@^0.1.1: resolved "https://registry.npmjs.org/chai-bn/-/chai-bn-0.1.1.tgz#a8904b2dc878e5c094881f327c0029579ff2062b" integrity sha512-e1npVXt3cQfZ6oQET9oP38vNj/4HeJ4ojeUpuC8YzhVbTJpIDqANVt7TKi7Dq9yKlHySk2FqbmiMih35iT4DYg== -chai@^4.2.0: +chai@^4.0.1, chai@^4.2.0: version "4.2.0" resolved "https://registry.npmjs.org/chai/-/chai-4.2.0.tgz#760aa72cf20e3795e84b12877ce0e83737aa29e5" integrity sha512-XQU3bhBukrOsQCuwZndwGcCVQHyZi53fQ6Ys1Fym7E4olpIqqZZhhoFJoaKVvV17lWQoXYwgWN2nF5crA8J2jw== @@ -6112,6 +6487,21 @@ chokidar@^2.0.2, chokidar@^2.0.4, chokidar@^2.1.6, chokidar@^2.1.8: optionalDependencies: fsevents "^1.2.7" +chokidar@^3.0.2: + version "3.2.3" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.2.3.tgz#b9270a565d14f02f6bfdd537a6a2bbf5549b8c8c" + integrity sha512-GtrxGuRf6bzHQmXWRepvsGnXpkQkVU+D2/9a7dAe4a7v1NhrfZOZ2oKf76M3nOs46fFYL8D+Q8JYA4GYeJ8Cjw== + dependencies: + anymatch "~3.1.1" + braces "~3.0.2" + glob-parent "~5.1.0" + is-binary-path "~2.1.0" + is-glob "~4.0.1" + normalize-path "~3.0.0" + readdirp "~3.2.0" + optionalDependencies: + fsevents "~2.1.1" + chownr@^1.0.1: version "1.1.1" resolved "https://registry.npmjs.org/chownr/-/chownr-1.1.1.tgz#54726b8b8fff4df053c42187e801fb4412df1494" @@ -6514,7 +6904,7 @@ commander@^2.11.0, commander@^2.18.0, commander@^2.19.0, commander@^2.20.0, comm resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.0.tgz#d58bb2b5c1ee8f87b0d340027e9e94e222c5a422" integrity sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ== -commander@^2.5.0: +commander@^2.12.2, commander@^2.5.0: version "2.20.3" resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== @@ -7432,7 +7822,7 @@ debug@3.2.6, debug@^3.0.1, debug@^3.1.0, debug@^3.2.5, debug@^3.2.6: dependencies: ms "^2.1.1" -debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@~4.1.0: +debug@4.1.1, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@~4.1.0: version "4.1.1" resolved "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791" integrity sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw== @@ -7463,6 +7853,13 @@ decompress-response@^3.2.0, decompress-response@^3.3.0: dependencies: mimic-response "^1.0.0" +decompress-response@^4.2.0: + version "4.2.1" + resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-4.2.1.tgz#414023cc7a302da25ce2ec82d0d5238ccafd8986" + integrity sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw== + dependencies: + mimic-response "^2.0.0" + decompress-tar@^4.0.0, decompress-tar@^4.1.0, decompress-tar@^4.1.1: version "4.1.1" resolved "https://registry.npmjs.org/decompress-tar/-/decompress-tar-4.1.1.tgz#718cbd3fcb16209716e70a26b84e7ba4592e5af1" @@ -7758,6 +8155,11 @@ detect-newline@^2.1.0: resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-2.1.0.tgz#f41f1c10be4b00e87b5f13da680759f2c5bfd3e2" integrity sha1-9B8cEL5LAOh7XxPaaAdZ8sW/0+I= +detect-node@2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.0.3.tgz#a2033c09cc8e158d37748fbde7507832bd6ce127" + integrity sha1-ogM8CcyOFY03dI+951B4Mr1s4Sc= + detect-node@^2.0.4: version "2.0.4" resolved "https://registry.npmjs.org/detect-node/-/detect-node-2.0.4.tgz#014ee8f8f669c5c58023da64b8179c083a28c46c" @@ -8269,6 +8671,13 @@ end-of-stream@^1.0.0, end-of-stream@^1.1.0: dependencies: once "^1.4.0" +end-of-stream@^1.4.1: + version "1.4.4" + resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" + integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== + dependencies: + once "^1.4.0" + engine.io-client@~3.3.1: version "3.3.2" resolved "https://registry.npmjs.org/engine.io-client/-/engine.io-client-3.3.2.tgz#04e068798d75beda14375a264bb3d742d7bc33aa" @@ -8567,6 +8976,18 @@ escape-string-regexp@1.0.5, escape-string-regexp@^1.0.2, escape-string-regexp@^1 resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= +escodegen@1.8.x: + version "1.8.1" + resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.8.1.tgz#5a5b53af4693110bebb0867aa3430dd3b70a1018" + integrity sha1-WltTr0aTEQvrsIZ6o0MN07cKEBg= + dependencies: + esprima "^2.7.1" + estraverse "^1.9.1" + esutils "^2.0.2" + optionator "^0.8.1" + optionalDependencies: + source-map "~0.2.0" + escodegen@^1.11.0, escodegen@^1.6.1: version "1.12.0" resolved "https://registry.npmjs.org/escodegen/-/escodegen-1.12.0.tgz#f763daf840af172bb3a2b6dd7219c0e17f7ff541" @@ -8857,6 +9278,11 @@ esprima-fb@^15001.1.0-dev-harmony-fb: resolved "https://registry.yarnpkg.com/esprima-fb/-/esprima-fb-15001.1.0-dev-harmony-fb.tgz#30a947303c6b8d5e955bee2b99b1d233206a6901" integrity sha1-MKlHMDxrjV6VW+4rmbHSMyBqaQE= +esprima@2.7.x, esprima@^2.7.1: + version "2.7.3" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-2.7.3.tgz#96e3b70d5779f6ad49cd032673d1c312767ba581" + integrity sha1-luO3DVd59q1JzQMmc9HDEnZ7pYE= + esprima@^3.1.3, esprima@~3.1.0: version "3.1.3" resolved "https://registry.yarnpkg.com/esprima/-/esprima-3.1.3.tgz#fdca51cee6133895e3c88d535ce49dbff62a4633" @@ -8881,6 +9307,11 @@ esrecurse@^4.1.0: dependencies: estraverse "^4.1.0" +estraverse@^1.9.1: + version "1.9.3" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-1.9.3.tgz#af67f2dc922582415950926091a4005d29c9bb44" + integrity sha1-r2fy3JIlgkFZUJJgkaQAXSnJu0Q= + estraverse@^4.0.0, estraverse@^4.2.0: version "4.2.0" resolved "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz#0dee3fed31fcd469618ce7342099fc1afa0bdb13" @@ -9035,6 +9466,13 @@ ethashjs@~0.0.7: ethereumjs-util "^4.0.1" miller-rabin "^4.0.0" +ethereum-bloom-filters@^1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/ethereum-bloom-filters/-/ethereum-bloom-filters-1.0.6.tgz#9cdebb3ec20de96ec4a434c6bad6ea5a513037aa" + integrity sha512-dE9CGNzgOOsdh7msZirvv8qjHtnHpvBlKe2647kM8v+yeF71IRso55jpojemvHV+jMjr48irPWxMRaHuOWzAFA== + dependencies: + js-sha3 "^0.8.0" + ethereum-common@0.2.0: version "0.2.0" resolved "https://registry.npmjs.org/ethereum-common/-/ethereum-common-0.2.0.tgz#13bf966131cce1eeade62a1b434249bb4cb120ca" @@ -9045,6 +9483,14 @@ ethereum-common@^0.0.18: resolved "https://registry.npmjs.org/ethereum-common/-/ethereum-common-0.0.18.tgz#2fdc3576f232903358976eb39da783213ff9523f" integrity sha1-L9w1dvIykDNYl26znaeDIT/5Uj8= +ethereum-types@^2.1.6: + version "2.1.6" + resolved "https://registry.yarnpkg.com/ethereum-types/-/ethereum-types-2.1.6.tgz#57d9d515fad86ab987c0f6962c4203be37da8579" + integrity sha512-xaN5TxLvkdFCGjGfUQ5wV00GHzDHStozP1j+K/YdmUeQXVGiD15cogYPhBVWG3pQJM/aBjtYrpMrjywvKkNC4A== + dependencies: + "@types/node" "*" + bignumber.js "~8.0.2" + ethereumjs-abi@0.6.5: version "0.6.5" resolved "https://registry.npmjs.org/ethereumjs-abi/-/ethereumjs-abi-0.6.5.tgz#5a637ef16ab43473fa72a29ad90871405b3f5241" @@ -9160,7 +9606,7 @@ ethereumjs-testrpc@^6.0.3: dependencies: webpack "^3.0.0" -ethereumjs-tx@1.3.7, ethereumjs-tx@^1.1.1, ethereumjs-tx@^1.2.0, ethereumjs-tx@^1.2.2, ethereumjs-tx@^1.3.3: +ethereumjs-tx@1.3.7, ethereumjs-tx@^1.1.1, ethereumjs-tx@^1.2.0, ethereumjs-tx@^1.2.2, ethereumjs-tx@^1.3.3, ethereumjs-tx@^1.3.5: version "1.3.7" resolved "https://registry.npmjs.org/ethereumjs-tx/-/ethereumjs-tx-1.3.7.tgz#88323a2d875b10549b8347e09f4862b546f3d89a" integrity sha512-wvLMxzt1RPhAQ9Yi3/HKZTn0FZYpnsmQdbKYfUUpi4j1SEIcbkd9tndVjcPrufY3V7j2IebOpC00Zp2P/Ay2kA== @@ -9324,6 +9770,22 @@ ethers@^4.0.0-beta.1, ethers@^4.0.32, ethers@^4.0.36, ethers@^4.0.37: uuid "2.0.1" xmlhttprequest "1.8.0" +ethers@~4.0.4: + version "4.0.39" + resolved "https://registry.yarnpkg.com/ethers/-/ethers-4.0.39.tgz#5ce9dfffedb03936415743f63b37d96280886a47" + integrity sha512-QVtC8TTUgTrnlQjQvdFJ7fkSWKwp8HVTbKRmrdbVryrPzJHMTf3WSeRNvLF2enGyAFtyHJyFNnjN0fSshcEr9w== + dependencies: + "@types/node" "^10.3.2" + aes-js "3.0.0" + bn.js "^4.4.0" + elliptic "6.3.3" + hash.js "1.1.3" + js-sha3 "0.5.7" + scrypt-js "2.0.4" + setimmediate "1.0.4" + uuid "2.0.1" + xmlhttprequest "1.8.0" + ethjs-abi@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/ethjs-abi/-/ethjs-abi-0.2.1.tgz#e0a7a93a7e81163a94477bad56ede524ab6de533" @@ -9461,6 +9923,22 @@ execa@^1.0.0: signal-exit "^3.0.0" strip-eof "^1.0.0" +execa@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/execa/-/execa-3.2.0.tgz#18326b79c7ab7fbd6610fd900c1b9e95fa48f90a" + integrity sha512-kJJfVbI/lZE1PZYDI5VPxp8zXPO9rtxOkhpZ0jMKha56AI9y2gGVC6bkukStQf0ka5Rh15BA5m7cCCH4jmHqkw== + dependencies: + cross-spawn "^7.0.0" + get-stream "^5.0.0" + human-signals "^1.1.1" + is-stream "^2.0.0" + merge-stream "^2.0.0" + npm-run-path "^4.0.0" + onetime "^5.1.0" + p-finally "^2.0.0" + signal-exit "^3.0.2" + strip-final-newline "^2.0.0" + executable@4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/executable/-/executable-4.1.1.tgz#41532bff361d3e57af4d763b70582db18f5d133c" @@ -9684,6 +10162,11 @@ extsprintf@^1.2.0: resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f" integrity sha1-4mifjzVvrWLMplo6kcXfX5VRaS8= +eyes@^0.1.8: + version "0.1.8" + resolved "https://registry.yarnpkg.com/eyes/-/eyes-0.1.8.tgz#62cf120234c683785d902348a800ef3e0cc20bc0" + integrity sha1-Ys8SAjTGg3hdkCNIqADvPgzCC8A= + fake-merkle-patricia-tree@^1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/fake-merkle-patricia-tree/-/fake-merkle-patricia-tree-1.0.1.tgz#4b8c3acfb520afadf9860b1f14cd8ce3402cddd3" @@ -10423,6 +10906,11 @@ fsevents@^1.2.7: nan "^2.12.1" node-pre-gyp "^0.12.0" +fsevents@~2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.1.1.tgz#74c64e21df71721845d0c44fe54b7f56b82995a9" + integrity sha512-4FRPXWETxtigtJW/gxzEDsX1LVbPAM93VleB83kZB+ellqbHMkyt2aJfuzNLRvFPnGi6bcE5SvfxgbXPeKteJw== + fstream@^1.0.12, fstream@^1.0.8: version "1.0.12" resolved "https://registry.yarnpkg.com/fstream/-/fstream-1.0.12.tgz#4e8ba8ee2d48be4f7d0de505455548eae5932045" @@ -10472,7 +10960,7 @@ ganache-cli@^6.1.0: source-map-support "0.5.9" yargs "11.1.0" -ganache-core@^2.8.0: +ganache-core@^2.6.0, ganache-core@^2.8.0: version "2.8.0" resolved "https://registry.npmjs.org/ganache-core/-/ganache-core-2.8.0.tgz#eeadc7f7fc3a0c20d99f8f62021fb80b5a05490c" integrity sha512-hfXqZGJx700jJqwDHNXrU2BnPYuETn1ekm36oRHuXY3oOuJLFs5C+cFdUFaBlgUxcau1dOgZUUwKqTAy0gTA9Q== @@ -10576,7 +11064,7 @@ get-stream@^4.0.0, get-stream@^4.1.0: dependencies: pump "^3.0.0" -get-stream@^5.1.0: +get-stream@^5.0.0, get-stream@^5.1.0: version "5.1.0" resolved "https://registry.npmjs.org/get-stream/-/get-stream-5.1.0.tgz#01203cdc92597f9b909067c3e656cc1f4d3c4dc9" integrity sha512-EXr1FOzrzTfGeL0gQdeFEvOMm2mzMOglyiOXSTpPC+iAjAKftbr3jpCMWynogwYnM+eSj9sHGc6wjIcDvYiygw== @@ -10643,7 +11131,7 @@ glob-parent@^3.1.0: is-glob "^3.1.0" path-dirname "^1.0.0" -glob-parent@^5.0.0: +glob-parent@^5.0.0, glob-parent@~5.1.0: version "5.1.0" resolved "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.0.tgz#5f4c1d1e748d30cd73ad2944b3577a81b081e8c2" integrity sha512-qjtRgnIVmOfnKUE3NJAQEdk+lKrxfw8t5ke7SXtfMTHcjsBfOfWXCQfdb30zfDoZQ2IRSIiidmjtbHZPZ++Ihw== @@ -11050,6 +11538,17 @@ handle-thing@^2.0.0: resolved "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.0.tgz#0e039695ff50c93fc288557d696f3c1dc6776754" integrity sha512-d4sze1JNC454Wdo2fkuyzCr6aHcbL6PGGuFAz0Li/NcOm1tCHGnWDRmJP85dh9IhQErTc2svWFEX5xHIOo//kQ== +handlebars@^4.0.1: + version "4.5.1" + resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.5.1.tgz#8a01c382c180272260d07f2d1aa3ae745715c7ba" + integrity sha512-C29UoFzHe9yM61lOsIlCE5/mQVGrnIOrOq7maQl76L7tYPCgC1og0Ajt6uWnX4ZTxBPnjw+CUvawphwCfJgUnA== + dependencies: + neo-async "^2.6.0" + optimist "^0.6.1" + source-map "^0.6.1" + optionalDependencies: + uglify-js "^3.1.4" + handlebars@^4.1.2: version "4.1.2" resolved "https://registry.npmjs.org/handlebars/-/handlebars-4.1.2.tgz#b6b37c1ced0306b221e094fc7aca3ec23b131b67" @@ -11098,6 +11597,11 @@ has-cors@1.1.0: resolved "https://registry.npmjs.org/has-cors/-/has-cors-1.1.0.tgz#5e474793f7ea9843d1bb99c23eef49ff126fff39" integrity sha1-XkdHk/fqmEPRu5nCPu9J/xJv/zk= +has-flag@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-1.0.0.tgz#9d9e793165ce017a00f00418c43f942a7b1d11fa" + integrity sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo= + has-flag@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz#e8207af1cc7b30d446cc70b734b5e8be18f88d51" @@ -11207,6 +11711,14 @@ hastscript@^5.0.0: property-information "^5.0.1" space-separated-tokens "^1.0.0" +hdkey@^0.7.1: + version "0.7.1" + resolved "https://registry.yarnpkg.com/hdkey/-/hdkey-0.7.1.tgz#caee4be81aa77921e909b8d228dd0f29acaee632" + integrity sha1-yu5L6BqneSHpCbjSKN0PKayu5jI= + dependencies: + coinstring "^2.0.0" + secp256k1 "^3.0.1" + hdkey@^1.1.0: version "1.1.1" resolved "https://registry.npmjs.org/hdkey/-/hdkey-1.1.1.tgz#c2b3bfd5883ff9529b72f2f08b28be0972a9f64a" @@ -11606,6 +12118,11 @@ https-proxy-agent@^2.2.1: agent-base "^4.1.0" debug "^3.1.0" +human-signals@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-1.1.1.tgz#c5b1cd14f50aeae09ab6c59fe63ba3395fe4dfa3" + integrity sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw== + humps@^2.0.1: version "2.0.1" resolved "https://registry.npmjs.org/humps/-/humps-2.0.1.tgz#dd02ea6081bd0568dc5d073184463957ba9ef9aa" @@ -12057,6 +12574,13 @@ is-binary-path@^1.0.0: dependencies: binary-extensions "^1.0.0" +is-binary-path@~2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" + integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== + dependencies: + binary-extensions "^2.0.0" + is-boolean-object@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.0.0.tgz#98f8b28030684219a95f375cfbd88ce3405dff93" @@ -12245,7 +12769,7 @@ is-glob@^3.1.0: dependencies: is-extglob "^2.1.0" -is-glob@^4.0.0, is-glob@^4.0.1: +is-glob@^4.0.0, is-glob@^4.0.1, is-glob@~4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.1.tgz#7567dbe9f2f5e2467bc77ab83c4a29482407a5dc" integrity sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg== @@ -12470,6 +12994,11 @@ is-stream@^1.0.0, is-stream@^1.0.1, is-stream@^1.1.0: resolved "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ= +is-stream@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.0.tgz#bde9c32680d6fae04129d6ac9d921ce7815f78e3" + integrity sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw== + is-string@^1.0.4: version "1.0.4" resolved "https://registry.npmjs.org/is-string/-/is-string-1.0.4.tgz#cc3a9b69857d621e963725a24caeec873b826e64" @@ -12575,7 +13104,7 @@ isobject@^4.0.0: resolved "https://registry.npmjs.org/isobject/-/isobject-4.0.0.tgz#3f1c9155e73b192022a80819bacd0343711697b0" integrity sha512-S/2fF5wH8SJA/kmwr6HYhK/RI/OkhD84k8ntalo0iJjZikgq1XFvR5M8NPT1x5F7fBwCG3qHfnzeP/Vh/ZxCUA== -isomorphic-fetch@^2.1.1: +isomorphic-fetch@2.2.1, isomorphic-fetch@^2.1.1: version "2.2.1" resolved "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz#611ae1acf14f5e81f729507472819fe9733558a9" integrity sha1-YRrhrPFPXoH3KVB0coGf6XM1WKk= @@ -12659,6 +13188,26 @@ istanbul-reports@^2.2.6: dependencies: handlebars "^4.1.2" +istanbul@^0.4.5: + version "0.4.5" + resolved "https://registry.yarnpkg.com/istanbul/-/istanbul-0.4.5.tgz#65c7d73d4c4da84d4f3ac310b918fb0b8033733b" + integrity sha1-ZcfXPUxNqE1POsMQuRj7C4Azczs= + dependencies: + abbrev "1.0.x" + async "1.x" + escodegen "1.8.x" + esprima "2.7.x" + glob "^5.0.15" + handlebars "^4.0.1" + js-yaml "3.x" + mkdirp "0.5.x" + nopt "3.x" + once "1.x" + resolve "1.1.x" + supports-color "^3.1.0" + which "^1.1.1" + wordwrap "^1.0.0" + isurl@^1.0.0-alpha5: version "1.0.0" resolved "https://registry.npmjs.org/isurl/-/isurl-1.0.0.tgz#b27f4f49f3cdaa3ea44a0a5b7f3462e6edc39d67" @@ -12679,6 +13228,23 @@ javascript-time-ago@^2.0.1, javascript-time-ago@^2.0.2: dependencies: relative-time-format "^0.1.3" +jayson@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/jayson/-/jayson-3.1.1.tgz#2faedb6e8c8df266c7ef3d9d6a6cc2d2655e226d" + integrity sha512-w+RKdtrRNlqFlyDxcALzTrcKSwETYcU+P9/cqWn8RogXkcV9RSmdL1tUb6E8hglFh8r3I62vVP8xrXkaaKyQdQ== + dependencies: + "@types/connect" "^3.4.32" + "@types/express-serve-static-core" "^4.16.9" + "@types/lodash" "^4.14.139" + "@types/node" "^12.7.7" + JSONStream "^1.3.1" + commander "^2.12.2" + es6-promisify "^5.0.0" + eyes "^0.1.8" + json-stringify-safe "^5.0.1" + lodash "^4.17.15" + uuid "^3.2.1" + jest-changed-files@^24.9.0: version "24.9.0" resolved "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-24.9.0.tgz#08d8c15eb79a7fa3fc98269bc14b451ee82f8039" @@ -13134,7 +13700,7 @@ js-sha3@0.5.7, js-sha3@^0.5.7: resolved "https://registry.npmjs.org/js-sha3/-/js-sha3-0.5.7.tgz#0d4ffd8002d5333aabaf4a23eed2f6374c9f28e7" integrity sha1-DU/9gALVMzqrr0oj7tL2N0yfKOc= -js-sha3@0.8.0: +js-sha3@0.8.0, js-sha3@^0.8.0: version "0.8.0" resolved "https://registry.npmjs.org/js-sha3/-/js-sha3-0.8.0.tgz#b9b7a5da73afad7dedd0f8c463954cbde6818840" integrity sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q== @@ -13144,6 +13710,11 @@ js-sha3@^0.6.1: resolved "https://registry.npmjs.org/js-sha3/-/js-sha3-0.6.1.tgz#5b89f77a7477679877f58c4a075240934b1f95c0" integrity sha1-W4n3enR3Z5h39YxKB1JAk0sflcA= +js-sha3@^0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/js-sha3/-/js-sha3-0.7.0.tgz#0a5c57b36f79882573b2d84051f8bb85dd1bd63a" + integrity sha512-Wpks3yBDm0UcL5qlVhwW9Jr9n9i4FfeWBFOOXP5puDS/SiudJGhw7DPyBqn3487qD4F0lsC0q3zxink37f7zeA== + "js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: version "4.0.0" resolved "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" @@ -13154,18 +13725,18 @@ js-tokens@^3.0.2: resolved "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b" integrity sha1-mGbfOVECEw449/mWvOtlRDIJwls= -js-yaml@^3.12.0: - version "3.13.0" - resolved "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.0.tgz#38ee7178ac0eea2c97ff6d96fff4b18c7d8cf98e" - integrity sha512-pZZoSxcCYco+DIKBTimr67J6Hy+EYGZDY/HCWC+iAEA9h1ByhMXAIVUXMcMFpOCxQ/xjXmPI2MkDL5HRm5eFrQ== +js-yaml@3.x, js-yaml@^3.13.0, js-yaml@^3.13.1, js-yaml@^3.4.2: + version "3.13.1" + resolved "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz#aff151b30bfdfa8e49e05da22e7415e9dfa37847" + integrity sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw== dependencies: argparse "^1.0.7" esprima "^4.0.0" -js-yaml@^3.13.0, js-yaml@^3.13.1, js-yaml@^3.4.2: - version "3.13.1" - resolved "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz#aff151b30bfdfa8e49e05da22e7415e9dfa37847" - integrity sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw== +js-yaml@^3.12.0: + version "3.13.0" + resolved "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.0.tgz#38ee7178ac0eea2c97ff6d96fff4b18c7d8cf98e" + integrity sha512-pZZoSxcCYco+DIKBTimr67J6Hy+EYGZDY/HCWC+iAEA9h1ByhMXAIVUXMcMFpOCxQ/xjXmPI2MkDL5HRm5eFrQ== dependencies: argparse "^1.0.7" esprima "^4.0.0" @@ -13323,7 +13894,7 @@ json-rpc-engine@^3.4.0, json-rpc-engine@^3.6.0: promise-to-callback "^1.0.0" safe-event-emitter "^1.0.1" -json-rpc-error@^2.0.0: +json-rpc-error@2.0.0, json-rpc-error@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/json-rpc-error/-/json-rpc-error-2.0.0.tgz#a7af9c202838b5e905c7250e547f1aff77258a02" integrity sha1-p6+cICg4tekFxyUOVH8a/3cligI= @@ -13362,7 +13933,7 @@ json-stringify-pretty-compact@^1.0.1: resolved "https://registry.yarnpkg.com/json-stringify-pretty-compact/-/json-stringify-pretty-compact-1.2.0.tgz#0bc316b5e6831c07041fc35612487fb4e9ab98b8" integrity sha512-/11Pj1OyX814QMKO7K8l85SHPTr/KsFxHp8GE2zVa0BtJgGimDjXHfM3FhC7keQdWDea7+nXf+f1de7ATZcZkQ== -json-stringify-safe@~5.0.1: +json-stringify-safe@^5.0.1, json-stringify-safe@~5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" integrity sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus= @@ -13425,6 +13996,16 @@ jsonify@~0.0.0: resolved "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73" integrity sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM= +jsonparse@^1.2.0: + version "1.3.1" + resolved "https://registry.yarnpkg.com/jsonparse/-/jsonparse-1.3.1.tgz#3f4dae4a91fac315f71062f8521cc239f1366280" + integrity sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA= + +jsonschema@^1.2.0: + version "1.2.4" + resolved "https://registry.yarnpkg.com/jsonschema/-/jsonschema-1.2.4.tgz#a46bac5d3506a254465bc548876e267c6d0d6464" + integrity sha512-lz1nOH69GbsVHeVgEdvyavc/33oymY1AZwtePMiMj4HZPMbP5OIKK3zT9INMWjwua/V4Z4yq7wSlBbSG+g4AEw== + jsonwebtoken@^8.5.1: version "8.5.1" resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz#00e71e0b8df54c2121a1f26137df2280673bcc0d" @@ -14198,6 +14779,11 @@ lodash.uniq@^4.5.0: resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M= +lodash.values@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/lodash.values/-/lodash.values-4.3.0.tgz#a3a6c2b0ebecc5c2cba1c17e6e620fe81b53d347" + integrity sha1-o6bCsOvsxcLLocF+bmIP6BtT00c= + lodash@4.17.14: version "4.17.14" resolved "https://registry.npmjs.org/lodash/-/lodash-4.17.14.tgz#9ce487ae66c96254fe20b599f21b6816028078ba" @@ -14246,7 +14832,7 @@ loglevel@^1.4.1: resolved "https://registry.npmjs.org/loglevel/-/loglevel-1.6.1.tgz#e0fc95133b6ef276cdc8887cdaf24aa6f156f8fa" integrity sha1-4PyVEztu8nbNyIh82vJKpvFW+Po= -loglevel@^1.6.3: +loglevel@^1.6.1, loglevel@^1.6.3: version "1.6.4" resolved "https://registry.npmjs.org/loglevel/-/loglevel-1.6.4.tgz#f408f4f006db8354d0577dcf6d33485b3cb90d56" integrity sha512-p0b6mOGKcGa+7nnmKbpzR6qloPbrgLcnio++E+14Vo/XffOGwZtRpUhr8dTH/x2oCMmEoIU0Zwm3ZauhvYD17g== @@ -14756,11 +15342,21 @@ mimic-fn@^2.0.0: resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.0.0.tgz#0913ff0b121db44ef5848242c38bbb35d44cabde" integrity sha512-jbex9Yd/3lmICXwYT6gA/j2mNQGU48wCh/VzRd+/Y/PjYQtlg1gLMdZqvu9s/xH7qKvngxRObl56XZR609IMbA== +mimic-fn@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" + integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== + mimic-response@^1.0.0, mimic-response@^1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b" integrity sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ== +mimic-response@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-2.0.0.tgz#996a51c60adf12cb8a87d7fb8ef24c2f3d5ebb46" + integrity sha512-8ilDoEapqA4uQ3TwS0jakGONKXVJqpy+RpM+3b7pLdOjghCrEiGp9SRkFbUHAmZW9vdnrENWHjaweIoTIJExSQ== + min-document@^2.19.0: version "2.19.0" resolved "https://registry.npmjs.org/min-document/-/min-document-2.19.0.tgz#7bd282e3f5842ed295bb748cdd9f1ffa2c824685" @@ -14996,7 +15592,7 @@ mz@^2.4.0, mz@^2.6.0: object-assign "^4.0.1" thenify-all "^1.0.0" -nan@^2.0.8, nan@^2.2.1, nan@^2.3.3: +nan@2.13.2, nan@^2.0.8, nan@^2.2.1, nan@^2.3.3: version "2.13.2" resolved "https://registry.yarnpkg.com/nan/-/nan-2.13.2.tgz#f51dc7ae66ba7d5d55e1e6d4d8092e802c9aefe7" integrity sha512-TghvYc72wlMGMVMluVo9WRJc0mB8KxxF/gZ4YYFy7V2ZQX9l7rgbPg7vjS9mt6U5HXODVFVI2bOduCzwOMv/lw== @@ -15157,6 +15753,15 @@ node-gyp-build@^4.1.0: resolved "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.1.1.tgz#d7270b5d86717068d114cc57fff352f96d745feb" integrity sha512-dSq1xmcPDKPZ2EED2S6zw/b9NKsqzXRE6dVr8TVQnI3FJOTteUMuqF3Qqs6LZg+mLGYJWqQzMbIjMtJqTv87nQ== +node-hid@^0.7.9: + version "0.7.9" + resolved "https://registry.yarnpkg.com/node-hid/-/node-hid-0.7.9.tgz#cc0cdf1418a286a7667f0b63642b5eeb544ccd05" + integrity sha512-vJnonTqmq3frCyTumJqG4g2IZcny3ynkfmbfDfQ90P3ZhRzcWYS/Um1ux6HFmAxmkaQnrZqIYHcGpL7kdqY8jA== + dependencies: + bindings "^1.5.0" + nan "^2.13.2" + prebuild-install "^5.3.0" + node-int64@^0.4.0: version "0.4.0" resolved "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" @@ -15247,6 +15852,13 @@ noop-logger@^0.1.1: resolved "https://registry.npmjs.org/noop-logger/-/noop-logger-0.1.1.tgz#94a2b1633c4f1317553007d8966fd0e841b6a4c2" integrity sha1-lKKxYzxPExdVMAfYlm/Q6EG2pMI= +nopt@3.x: + version "3.0.6" + resolved "https://registry.yarnpkg.com/nopt/-/nopt-3.0.6.tgz#c6465dbf08abcd4db359317f79ac68a646b28ff9" + integrity sha1-xkZdvwirzU2zWTF/eaxopkayj/k= + dependencies: + abbrev "1" + nopt@^4.0.1, nopt@~4.0.1: version "4.0.1" resolved "https://registry.npmjs.org/nopt/-/nopt-4.0.1.tgz#d0d4685afd5415193c8c7505602d0d17cd64474d" @@ -15272,7 +15884,7 @@ normalize-path@^2.0.1, normalize-path@^2.1.1: dependencies: remove-trailing-separator "^1.0.1" -normalize-path@^3.0.0: +normalize-path@^3.0.0, normalize-path@~3.0.0: version "3.0.0" resolved "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== @@ -15351,6 +15963,13 @@ npm-run-path@^2.0.0: dependencies: path-key "^2.0.0" +npm-run-path@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.0.tgz#d644ec1bd0569187d2a52909971023a0a58e8438" + integrity sha512-8eyAOAH+bYXFPSnNnKr3J+yoybe8O87Is5rtAQ8qRczJz1ajcsjg8l2oZqP+Ppx15Ii3S1vUTjQN2h4YO2tWWQ== + dependencies: + path-key "^3.0.0" + npmlog@^4.0.1, npmlog@^4.0.2, npmlog@^4.1.2: version "4.1.2" resolved "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b" @@ -15592,7 +16211,7 @@ on-headers@~1.0.1, on-headers@~1.0.2: resolved "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz#772b0ae6aaa525c399e489adfad90c403eb3c28f" integrity sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA== -once@^1.3.0, once@^1.3.1, once@^1.3.2, once@^1.4.0: +once@1.x, once@^1.3.0, once@^1.3.1, once@^1.3.2, once@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= @@ -15616,6 +16235,13 @@ onetime@^2.0.0: dependencies: mimic-fn "^1.0.0" +onetime@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.0.tgz#fff0f3c91617fe62bb50189636e99ac8a6df7be5" + integrity sha512-5NcSkPHhwTVFIQN+TUqXoS5+dlElHXdpAWu9I0HP20YOtIi+aZ0Ct82jdlILDxjLEAWwvm+qj1m6aEtsDVmm6Q== + dependencies: + mimic-fn "^2.1.0" + open@^6.1.0, open@^6.3.0: version "6.4.0" resolved "https://registry.yarnpkg.com/open/-/open-6.4.0.tgz#5c13e96d0dc894686164f18965ecfe889ecfc8a9" @@ -15812,6 +16438,11 @@ p-finally@^1.0.0: resolved "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" integrity sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4= +p-finally@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-2.0.1.tgz#bd6fcaa9c559a096b680806f4d657b3f0f240561" + integrity sha512-vpm09aKwq6H9phqRQzecoDpD8TmVyGw70qmWlyq5onxY7tqyTTFVvxMykxQSQKILBSFlbXpypIw2T1Ml7+DDtw== + p-is-promise@^1.1.0: version "1.1.0" resolved "https://registry.npmjs.org/p-is-promise/-/p-is-promise-1.1.0.tgz#9c9456989e9f6588017b0434d56097675c3da05e" @@ -16125,7 +16756,7 @@ path-key@^2.0.0, path-key@^2.0.1: resolved "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" integrity sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A= -path-key@^3.1.0: +path-key@^3.0.0, path-key@^3.1.0: version "3.1.0" resolved "https://registry.npmjs.org/path-key/-/path-key-3.1.0.tgz#99a10d870a803bdd5ee6f0470e58dfcd2f9a54d3" integrity sha512-8cChqz0RP6SHJkMt48FW0A7+qUOn+OsnOsVtzI59tZ8m+5bCSk7hzwET0pulwOM2YMn9J1efb07KB9l9f30SGg== @@ -16283,6 +16914,11 @@ pgpass@1.x: dependencies: split "^1.0.0" +picomatch@^2.0.4: + version "2.1.0" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.1.0.tgz#0fd042f568d08b1ad9ff2d3ec0f0bfb3cb80e177" + integrity sha512-uhnEDzAbrcJ8R3g2fANnSuXZMBtkpSjxTTgn2LeSiQlfmq72enQJWdQllXW24MBLYnA1SBD2vfvx2o0Zw3Ielw== + picomatch@^2.0.5: version "2.0.7" resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.0.7.tgz#514169d8c7cd0bdbeecc8a2609e34a7163de69f6" @@ -16364,6 +17000,11 @@ please-upgrade-node@^3.1.1: dependencies: semver-compare "^1.0.0" +pluralize@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-7.0.0.tgz#298b89df8b93b0221dbf421ad2b1b1ea23fc6777" + integrity sha512-ARhBOdzS3e41FbkW/XWrTEtukqqLoK5+Z/4UeDaLuSW+39JPeFgs4gCGqsrJHVZX0fUrx//4OF0K1CUGwlIFow== + pn@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/pn/-/pn-1.1.0.tgz#e2f4cef0e219f463c179ab37463e4e1ecdccbafb" @@ -16395,6 +17036,11 @@ pop-iterate@^1.0.1: resolved "https://registry.npmjs.org/pop-iterate/-/pop-iterate-1.0.1.tgz#ceacfdab4abf353d7a0f2aaa2c1fc7b3f9413ba3" integrity sha1-zqz9q0q/NT16DyqqLB/Hs/lBO6M= +popper.js@1.14.3: + version "1.14.3" + resolved "https://registry.yarnpkg.com/popper.js/-/popper.js-1.14.3.tgz#1438f98d046acf7b4d78cd502bf418ac64d4f095" + integrity sha1-FDj5jQRqz3tNeM1QK/QYrGTU8JU= + popper.js@^1.14.1, popper.js@^1.14.4, popper.js@^1.14.7: version "1.15.0" resolved "https://registry.yarnpkg.com/popper.js/-/popper.js-1.15.0.tgz#5560b99bbad7647e9faa475c6b8056621f5a4ff2" @@ -17114,6 +17760,27 @@ postgres-interval@^1.1.0: dependencies: xtend "^4.0.0" +prebuild-install@^5.2.4: + version "5.3.2" + resolved "https://registry.yarnpkg.com/prebuild-install/-/prebuild-install-5.3.2.tgz#6392e9541ac0b879ef0f22b3d65037417eb2035e" + integrity sha512-INDfXzTPnhT+WYQemqnAXlP7SvfiFMopMozSgXCZ+RDLb279gKfIuLk4o7PgEawLp3WrMgIYGBpkxpraROHsSA== + dependencies: + detect-libc "^1.0.3" + expand-template "^2.0.3" + github-from-package "0.0.0" + minimist "^1.2.0" + mkdirp "^0.5.1" + napi-build-utils "^1.0.1" + node-abi "^2.7.0" + noop-logger "^0.1.1" + npmlog "^4.0.1" + pump "^3.0.0" + rc "^1.2.7" + simple-get "^3.0.3" + tar-fs "^2.0.0" + tunnel-agent "^0.6.0" + which-pm-runs "^1.0.0" + prebuild-install@^5.3.0: version "5.3.0" resolved "https://registry.npmjs.org/prebuild-install/-/prebuild-install-5.3.0.tgz#58b4d8344e03590990931ee088dd5401b03004c8" @@ -17586,7 +18253,7 @@ randomatic@^3.0.0: kind-of "^6.0.0" math-random "^1.0.1" -randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.5, randombytes@^2.0.6: +randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.5, randombytes@^2.0.6, randombytes@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ== @@ -18378,19 +19045,19 @@ readable-stream@^1.0.33: isarray "0.0.1" string_decoder "~0.10.x" -readable-stream@^3.0.6: - version "3.2.0" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.2.0.tgz#de17f229864c120a9f56945756e4f32c4045245d" - integrity sha512-RV20kLjdmpZuTF1INEb9IA3L68Nmi+Ri7ppZqo78wj//Pn62fCoJyV9zalccNzDD/OuJpMG4f+pfMl8+L6QdGw== +readable-stream@^3.0.1, readable-stream@^3.1.1: + version "3.4.0" + resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-3.4.0.tgz#a51c26754658e0a3c21dbf59163bd45ba6f447fc" + integrity sha512-jItXPLmrSR8jmTRmRWJXCnGJsfy85mB3Wd/uINMXA65yrnFo0cPClFIUWzo2najVNSl+mx7/4W8ttlLWJe99pQ== dependencies: inherits "^2.0.3" string_decoder "^1.1.1" util-deprecate "^1.0.1" -readable-stream@^3.1.1: - version "3.4.0" - resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-3.4.0.tgz#a51c26754658e0a3c21dbf59163bd45ba6f447fc" - integrity sha512-jItXPLmrSR8jmTRmRWJXCnGJsfy85mB3Wd/uINMXA65yrnFo0cPClFIUWzo2najVNSl+mx7/4W8ttlLWJe99pQ== +readable-stream@^3.0.6: + version "3.2.0" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.2.0.tgz#de17f229864c120a9f56945756e4f32c4045245d" + integrity sha512-RV20kLjdmpZuTF1INEb9IA3L68Nmi+Ri7ppZqo78wj//Pn62fCoJyV9zalccNzDD/OuJpMG4f+pfMl8+L6QdGw== dependencies: inherits "^2.0.3" string_decoder "^1.1.1" @@ -18415,6 +19082,13 @@ readdirp@^2.2.1: micromatch "^3.1.10" readable-stream "^2.0.2" +readdirp@~3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.2.0.tgz#c30c33352b12c96dfb4b895421a49fd5a9593839" + integrity sha512-crk4Qu3pmXwgxdSgGhgA/eXiJAPQiX4GMOZZMXnqKxHX7TaoL+3gQVo/WeuAiogr07DpnfjIMpXXa+PAIvwPGQ== + dependencies: + picomatch "^2.0.4" + realpath-native@^1.1.0: version "1.1.0" resolved "https://registry.npmjs.org/realpath-native/-/realpath-native-1.1.0.tgz#2003294fea23fb0672f2476ebe22fcf498a2d65c" @@ -18809,7 +19483,7 @@ request-promise@4.2.4, request-promise@^4.2.2, request-promise@^4.2.4: stealthy-require "^1.1.1" tough-cookie "^2.3.3" -request@2.88.0, request@^2.79.0, request@^2.83.0, request@^2.85.0, request@^2.87.0, request@^2.88.0: +request@2.88.0, request@^2.67.0, request@^2.79.0, request@^2.83.0, request@^2.85.0, request@^2.87.0, request@^2.88.0: version "2.88.0" resolved "https://registry.yarnpkg.com/request/-/request-2.88.0.tgz#9c2fca4f7d35b592efe57c7f0a55e81052124fef" integrity sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg== @@ -18845,7 +19519,7 @@ require-from-string@^1.1.0: resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-1.2.1.tgz#529c9ccef27380adfec9a2f965b649bbee636418" integrity sha1-UpyczvJzgK3+yaL5ZbZJu+5jZBg= -require-from-string@^2.0.0: +require-from-string@^2.0.0, require-from-string@^2.0.1: version "2.0.2" resolved "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909" integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw== @@ -18950,7 +19624,7 @@ resolve-url@^0.2.1: resolved "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" integrity sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo= -resolve@1.1.7: +resolve@1.1.7, resolve@1.1.x: version "1.1.7" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b" integrity sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs= @@ -19120,7 +19794,7 @@ rxjs@^6.1.0: dependencies: tslib "^1.9.0" -rxjs@^6.4.0: +rxjs@^6.4.0, rxjs@^6.5.3: version "6.5.3" resolved "https://registry.npmjs.org/rxjs/-/rxjs-6.5.3.tgz#510e26317f4db91a7eb1de77d9dd9ba0a4899a3a" integrity sha512-wuYsAYYFdWTAnAaPoKGNhfpWwKZbJW+HgAJ+mImp+Epl7BG8oNWBCTyRM8gba9k4lk8BgWdoYm21Mo/RYhhbgA== @@ -19346,6 +20020,11 @@ selfsigned@^1.9.1: dependencies: node-forge "0.7.5" +semaphore-async-await@^1.5.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/semaphore-async-await/-/semaphore-async-await-1.5.1.tgz#857bef5e3644601ca4b9570b87e9df5ca12974fa" + integrity sha1-hXvvXjZEYBykuVcLh+nfXKEpdPo= + semaphore@>=1.0.1, semaphore@^1.0.3, semaphore@^1.1.0: version "1.1.0" resolved "https://registry.npmjs.org/semaphore/-/semaphore-1.1.0.tgz#aaad8b86b20fe8e9b32b16dc2ee682a8cd26a8aa" @@ -19450,6 +20129,20 @@ serve-handler@5.0.8: path-to-regexp "2.2.1" range-parser "1.2.0" +serve-handler@6.1.2: + version "6.1.2" + resolved "https://registry.yarnpkg.com/serve-handler/-/serve-handler-6.1.2.tgz#f05b0421a313fff2d257838cba00cbcc512cd2b6" + integrity sha512-RFh49wX7zJmmOVDcIjiDSJnMH+ItQEvyuYLYuDBVoA/xmQSCuj+uRmk1cmBB5QQlI3qOiWKp6p4DUGY+Z5AB2A== + dependencies: + bytes "3.0.0" + content-disposition "0.5.2" + fast-url-parser "1.1.3" + mime-types "2.1.18" + minimatch "3.0.4" + path-is-inside "1.0.2" + path-to-regexp "2.2.1" + range-parser "1.2.0" + serve-index@^1.7.2, serve-index@^1.9.1: version "1.9.1" resolved "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz#d3768d69b1e7d82e5ce050fff5b453bea12a9239" @@ -19473,7 +20166,7 @@ serve-static@1.14.1: parseurl "~1.3.3" send "0.17.1" -serve@^10.0.0, serve@^10.1.1: +serve@^10.1.1: version "10.1.2" resolved "https://registry.npmjs.org/serve/-/serve-10.1.2.tgz#805917f2692ed5d8720bbd1981ac3974d38b7d8d" integrity sha512-TVH35uwndRlCqSeX3grR3Ntrjx2aBTeu6sx+zTD2CzN2N/rHuEDTvxiBwWbrellJNyWiQFz2xZmoW+UxV+Zahg== @@ -19488,6 +20181,21 @@ serve@^10.0.0, serve@^10.1.1: serve-handler "5.0.8" update-check "1.5.2" +serve@^11.2.0: + version "11.2.0" + resolved "https://registry.yarnpkg.com/serve/-/serve-11.2.0.tgz#0405ce95c4e4a6abd9cd3d3a04ebaa7d94638627" + integrity sha512-THZcLzDGk3vJqjhAbLkLag43tiE3V0B7wVe98Xtl+1KyAsr+4iShg+9hke4pLZmrCJu0pUg0TrbhJmdqn/MKoA== + dependencies: + "@zeit/schemas" "2.6.0" + ajv "6.5.3" + arg "2.0.0" + boxen "1.3.0" + chalk "2.4.1" + clipboardy "1.2.3" + compression "1.7.3" + serve-handler "6.1.2" + update-check "1.5.2" + servify@^0.1.12: version "0.1.12" resolved "https://registry.npmjs.org/servify/-/servify-0.1.12.tgz#142ab7bee1f1d033b66d0707086085b17c06db95" @@ -19651,6 +20359,15 @@ simple-get@^2.7.0: once "^1.3.1" simple-concat "^1.0.0" +simple-get@^3.0.3: + version "3.1.0" + resolved "https://registry.yarnpkg.com/simple-get/-/simple-get-3.1.0.tgz#b45be062435e50d159540b576202ceec40b9c6b3" + integrity sha512-bCR6cP+aTdScaQCnQKbPKtJOKDp/hj9EDLJo3Nw4y1QksqaovlW/bnptB6/c1e+qmNIDHRK+oXFDdEqBT8WzUA== + dependencies: + decompress-response "^4.2.0" + once "^1.3.1" + simple-concat "^1.0.0" + simple-swizzle@^0.2.2: version "0.2.2" resolved "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz#a4da6b635ffcccca33f70d17cb92592de95e557a" @@ -19870,6 +20587,20 @@ solc@^0.4.20: semver "^5.3.0" yargs "^4.7.1" +solc@^0.5.5: + version "0.5.12" + resolved "https://registry.yarnpkg.com/solc/-/solc-0.5.12.tgz#e63047dce04c82ec6f469f6e28febfbde713b808" + integrity sha512-OX/AGZT04tuUsagoVXSZBiBZYJReA02hdwZOfRkB03/eeYP9Dl3pr+M+au+1MhssgiuWBlFPN7sRXFiqwkAW2g== + dependencies: + command-exists "^1.2.8" + fs-extra "^0.30.0" + js-sha3 "0.8.0" + memorystream "^0.3.1" + require-from-string "^2.0.0" + semver "^5.5.0" + tmp "0.0.33" + yargs "^13.2.0" + solhint@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/solhint/-/solhint-2.1.0.tgz#a0f8064db5726bb54c8ed21e94b01f0bb3959b3c" @@ -19962,6 +20693,14 @@ source-map-support@^0.4.15: dependencies: source-map "^0.5.6" +source-map-support@^0.5.0: + version "0.5.16" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.16.tgz#0ae069e7fe3ba7538c64c98515e35339eac5a042" + integrity sha512-efyLRJDr68D9hBBNIPWFjhpFzURh+KJykQwvMyW5UiZzYwoF6l4YMMDIJJEyFWxWCqfyxLzz6tSfUFR+kXXsVQ== + dependencies: + buffer-from "^1.0.0" + source-map "^0.6.0" + source-map-support@^0.5.12, source-map-support@^0.5.13, source-map-support@^0.5.6, source-map-support@^0.5.9, source-map-support@~0.5.12: version "0.5.13" resolved "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz#31b24a9c2e73c2de85066c0feb7d44767ed52932" @@ -19997,6 +20736,13 @@ source-map@^0.7.3: resolved "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383" integrity sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ== +source-map@~0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.2.0.tgz#dab73fbcfc2ba819b4de03bd6f6eaa48164b3f9d" + integrity sha1-2rc/vPwrqBm03gO9b26qSBZLP50= + dependencies: + amdefine ">=0.0.4" + space-separated-tokens@^1.0.0: version "1.1.4" resolved "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-1.1.4.tgz#27910835ae00d0adfcdbd0ad7e611fb9544351fa" @@ -20396,6 +21142,11 @@ strip-eof@^1.0.0: resolved "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf" integrity sha1-u0P/VZim6wXYm1n80SnJgzE2Br8= +strip-final-newline@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" + integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== + strip-hex-prefix@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/strip-hex-prefix/-/strip-hex-prefix-1.0.0.tgz#0c5f155fef1151373377de9dbb588da05500e36f" @@ -20502,6 +21253,13 @@ supports-color@^2.0.0: resolved "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" integrity sha1-U10EXOa2Nj+kARcIRimZXp3zJMc= +supports-color@^3.1.0: + version "3.2.3" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-3.2.3.tgz#65ac0504b3954171d8a64946b2ae3cbb8a5f54f6" + integrity sha1-ZawFBLOVQXHYpklGsq48u4pfVPY= + dependencies: + has-flag "^1.0.0" + supports-color@^4.2.1, supports-color@^4.5.0: version "4.5.0" resolved "https://registry.npmjs.org/supports-color/-/supports-color-4.5.0.tgz#be7a0de484dec5c5cddf8b3d59125044912f635b" @@ -20660,7 +21418,7 @@ tapable@^1.0.0, tapable@^1.1.0, tapable@^1.1.3: resolved "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz#a1fccc06b58db61fd7a45da2da44f5f3a3e67ba2" integrity sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA== -tape@^4.6.3: +tape@^4.4.0, tape@^4.6.3: version "4.11.0" resolved "https://registry.npmjs.org/tape/-/tape-4.11.0.tgz#63d41accd95e45a23a874473051c57fdbc58edc1" integrity sha512-yixvDMX7q7JIs/omJSzSZrqulOV51EC9dK8dM0TzImTIkHWfe2/kFyL5v+d9C+SrCMaICk59ujsqFAVidDqDaA== @@ -20689,6 +21447,16 @@ tar-fs@^1.13.0, tar-fs@^1.16.3: pump "^1.0.0" tar-stream "^1.1.2" +tar-fs@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-2.0.0.tgz#677700fc0c8b337a78bee3623fdc235f21d7afad" + integrity sha512-vaY0obB6Om/fso8a8vakQBzwholQ7v5+uy+tF3Ozvxv1KNezmVQAiWtcNmMHFSFPqL3dJA8ha6gdtFbfX9mcxA== + dependencies: + chownr "^1.1.1" + mkdirp "^0.5.1" + pump "^3.0.0" + tar-stream "^2.0.0" + tar-stream@^1.1.2, tar-stream@^1.5.2: version "1.6.2" resolved "https://registry.npmjs.org/tar-stream/-/tar-stream-1.6.2.tgz#8ea55dab37972253d9a9af90fdcd559ae435c555" @@ -20702,6 +21470,17 @@ tar-stream@^1.1.2, tar-stream@^1.5.2: to-buffer "^1.1.1" xtend "^4.0.0" +tar-stream@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.1.0.tgz#d1aaa3661f05b38b5acc9b7020efdca5179a2cc3" + integrity sha512-+DAn4Nb4+gz6WZigRzKEZl1QuJVOLtAwwF+WUxy1fJ6X63CaGaUAxJRD2KEn1OMfcbCjySTYpNC6WmfQoIEOdw== + dependencies: + bl "^3.0.0" + end-of-stream "^1.4.1" + fs-constants "^1.0.0" + inherits "^2.0.3" + readable-stream "^3.1.1" + tar.gz@^1.0.5: version "1.0.7" resolved "https://registry.yarnpkg.com/tar.gz/-/tar.gz-1.0.7.tgz#577ef2c595faaa73452ef0415fed41113212257b" @@ -20898,7 +21677,7 @@ through2@^2.0.0, through2@^2.0.3, through2@~2.0.0: readable-stream "~2.3.6" xtend "~4.0.1" -through@2, through@^2.3.6, through@^2.3.8, through@~2.3.4, through@~2.3.8: +through@2, "through@>=2.2.7 <3", through@^2.3.6, through@^2.3.8, through@~2.3.4, through@~2.3.8: version "2.3.8" resolved "https://registry.npmjs.org/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU= @@ -21235,10 +22014,10 @@ ts-jest@^24.0.0, ts-jest@^24.1.0: semver "^5.5" yargs-parser "10.x" -ts-loader@^6.1.0: - version "6.1.2" - resolved "https://registry.yarnpkg.com/ts-loader/-/ts-loader-6.1.2.tgz#ff6bc767334970226438949fbde2e211147a1325" - integrity sha512-dudxFKm0Ellrg/gLNlu+97/UgwvoMK0SdUVImPUSzq3IcRUVtShylZvcMX+CgvCQL1BEKb913NL0gAP1GA/OFw== +ts-loader@^6.2.1: + version "6.2.1" + resolved "https://registry.yarnpkg.com/ts-loader/-/ts-loader-6.2.1.tgz#67939d5772e8a8c6bdaf6277ca023a4812da02ef" + integrity sha512-Dd9FekWuABGgjE1g0TlQJ+4dFUfYGbYcs52/HQObE0ZmUNjQlmLAS7xXsSzy23AMaMwipsx5sNHvoEpT2CZq1g== dependencies: chalk "^2.3.0" enhanced-resolve "^4.0.0" @@ -21481,6 +22260,11 @@ typescript@^3.6.3: resolved "https://registry.npmjs.org/typescript/-/typescript-3.6.3.tgz#fea942fabb20f7e1ca7164ff626f1a9f3f70b4da" integrity sha512-N7bceJL1CtRQ2RiG0AQME13ksR7DiuQh/QehubYcghzv20tnh+MQnQIuJddTmsbqYj+dztchykemz0zFzlvdQw== +typescript@^3.7.0-beta: + version "3.7.0-dev.20191021" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.7.0-dev.20191021.tgz#e0238e0b3eed9fc265767a1b7f5346fea8ab5edb" + integrity sha512-SSx/+QkyW7PMcaGQXzVmVkrRmmaLFsdOYXhP9sY9eYMiHrfmtZE9EL2hjtbihfnpyWfCmPup69VgbB4dTTEQgg== + typewise-core@^1.2, typewise-core@^1.2.0: version "1.2.0" resolved "https://registry.npmjs.org/typewise-core/-/typewise-core-1.2.0.tgz#97eb91805c7f55d2f941748fa50d315d991ef195" @@ -21508,6 +22292,11 @@ typical@^4.0.0: resolved "https://registry.yarnpkg.com/typical/-/typical-4.0.0.tgz#cbeaff3b9d7ae1e2bbfaf5a4e6f11eccfde94fc4" integrity sha512-VAH4IvQ7BDFYglMd7BPRDfLgxZZX4O4TFcRDA6EN5X7erNJJq+McIEp8np9aVtxrCJ6qx4GTYVfOWNjcqwZgRw== +u2f-api@0.2.7: + version "0.2.7" + resolved "https://registry.yarnpkg.com/u2f-api/-/u2f-api-0.2.7.tgz#17bf196b242f6bf72353d9858e6a7566cc192720" + integrity sha512-fqLNg8vpvLOD5J/z4B6wpPg4Lvowz1nJ9xdHcCzdUPKcFE/qNCceV2gNZxSJd5vhAZemHr/K/hbzVA0zxB5mkg== + ua-parser-js@^0.7.18, ua-parser-js@^0.7.9: version "0.7.20" resolved "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.20.tgz#7527178b82f6a62a0f243d1f94fd30e3e3c21098" @@ -21807,6 +22596,15 @@ url@0.11.0, url@^0.11.0: punycode "1.3.2" querystring "0.2.0" +usb@^1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/usb/-/usb-1.6.0.tgz#bc5d4decf4ffca32d1136717edcf73366137a789" + integrity sha512-52DyWlCk9K+iw3LnvY95WXSnpHjxJoI++aGkV8HiMNPc4zmvDQlYvWAzrkbJ2JH3oUcx26XfU5sZcG4RAcVkMg== + dependencies: + bindings "^1.4.0" + nan "2.13.2" + prebuild-install "^5.2.4" + use-react-hooks@^1.0.7: version "1.0.7" resolved "https://registry.npmjs.org/use-react-hooks/-/use-react-hooks-1.0.7.tgz#57a3948dcddf721eaf19b80a1cc060171e00f0f1" @@ -21874,7 +22672,7 @@ uuid@3.3.2, uuid@^3.0.1: resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.2.tgz#1b4af4955eb3077c501c23872fc6513811587131" integrity sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA== -uuid@^3.1.0, uuid@^3.3.2: +uuid@^3.1.0, uuid@^3.2.1, uuid@^3.3.2: version "3.3.3" resolved "https://registry.npmjs.org/uuid/-/uuid-3.3.3.tgz#4568f0216e78760ee1dbf3a4d2cf53e224112866" integrity sha512-pW0No1RGHgzlpHJO1nsVrHKpOEIxkGg1xB+v0ZmdNH5OAeAwzAVrCnI2/6Mtx+Uys6iaylxa+D3g4j63IKKjSQ== @@ -21896,6 +22694,11 @@ v8flags@^3.0.1: dependencies: homedir-polyfill "^1.0.1" +valid-url@^1.0.9: + version "1.0.9" + resolved "https://registry.yarnpkg.com/valid-url/-/valid-url-1.0.9.tgz#1c14479b40f1397a75782f115e4086447433a200" + integrity sha1-HBRHm0DxOXp1eC8RXkCGRHQzogA= + validate-npm-package-license@^3.0.1: version "3.0.4" resolved "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz#fc91f6b9c7ba15c857f4cb2c5defeec39d4f410a" @@ -22214,6 +23017,15 @@ web3-eth-abi@1.2.1: underscore "1.9.1" web3-utils "1.2.1" +web3-eth-abi@^1.0.0-beta.24: + version "1.2.2" + resolved "https://registry.yarnpkg.com/web3-eth-abi/-/web3-eth-abi-1.2.2.tgz#d5616d88a90020f894763423a9769f2da11fe37a" + integrity sha512-Yn/ZMgoOLxhTVxIYtPJ0eS6pnAnkTAaJgUJh1JhZS4ekzgswMfEYXOwpMaD5eiqPJLpuxmZFnXnBZlnQ1JMXsw== + dependencies: + ethers "4.0.0-beta.3" + underscore "1.9.1" + web3-utils "1.2.2" + web3-eth-accounts@1.0.0-beta.37: version "1.0.0-beta.37" resolved "https://registry.yarnpkg.com/web3-eth-accounts/-/web3-eth-accounts-1.0.0-beta.37.tgz#0a5a9f14a6c3bd285e001c15eb3bb38ffa4b5204" @@ -22397,6 +23209,33 @@ web3-net@1.2.1: web3-core-method "1.2.1" web3-utils "1.2.1" +web3-provider-engine@14.0.6: + version "14.0.6" + resolved "https://registry.yarnpkg.com/web3-provider-engine/-/web3-provider-engine-14.0.6.tgz#cbdd66fe20c0136a3a495cbe40d18b6c4160d5f0" + integrity sha512-tr5cGSyxfSC/JqiUpBlJtfZpwQf1yAA8L/zy1C6fDFm0ntR974pobJ4v4676atpZne4Ze5VFy3kPPahHe9gQiQ== + dependencies: + async "^2.5.0" + backoff "^2.5.0" + clone "^2.0.0" + cross-fetch "^2.1.0" + eth-block-tracker "^3.0.0" + eth-json-rpc-infura "^3.1.0" + eth-sig-util "^1.4.2" + ethereumjs-block "^1.2.2" + ethereumjs-tx "^1.2.0" + ethereumjs-util "^5.1.5" + ethereumjs-vm "^2.3.4" + json-rpc-error "^2.0.0" + json-stable-stringify "^1.0.1" + promise-to-callback "^1.0.0" + readable-stream "^2.2.9" + request "^2.67.0" + semaphore "^1.0.3" + tape "^4.4.0" + ws "^5.1.1" + xhr "^2.2.0" + xtend "^4.0.1" + web3-provider-engine@14.2.1: version "14.2.1" resolved "https://registry.npmjs.org/web3-provider-engine/-/web3-provider-engine-14.2.1.tgz#ef351578797bf170e08d529cb5b02f8751329b95" @@ -22521,6 +23360,20 @@ web3-utils@1.2.1, web3-utils@^1.0.0-beta.31, web3-utils@^1.2.0: underscore "1.9.1" utf8 "3.0.0" +web3-utils@1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/web3-utils/-/web3-utils-1.2.2.tgz#b53a08c40d2c3f31d3c4a28e7d749405df99c8c0" + integrity sha512-joF+s3243TY5cL7Z7y4h1JsJpUCf/kmFmj+eJar7Y2yNIGVcW961VyrAms75tjUysSuHaUQ3eQXjBEUJueT52A== + dependencies: + bn.js "4.11.8" + eth-lib "0.2.7" + ethereum-bloom-filters "^1.0.6" + ethjs-unit "0.1.6" + number-to-bn "1.7.0" + randombytes "^2.1.0" + underscore "1.9.1" + utf8 "3.0.0" + web3@1.0.0-beta.37: version "1.0.0-beta.37" resolved "https://registry.yarnpkg.com/web3/-/web3-1.0.0-beta.37.tgz#b42c30e67195f816cd19d048fda872f70eca7083" @@ -22949,7 +23802,7 @@ which-pm-runs@^1.0.0: resolved "https://registry.npmjs.org/which-pm-runs/-/which-pm-runs-1.0.0.tgz#670b3afbc552e0b55df6b7780ca74615f23ad1cb" integrity sha1-Zws6+8VS4LVd9rd4DKdGFfI60cs= -which@^1.2.14, which@^1.2.9, which@^1.3.0, which@^1.3.1: +which@^1.1.1, which@^1.2.14, which@^1.2.9, which@^1.3.0, which@^1.3.1: version "1.3.1" resolved "https://registry.npmjs.org/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== @@ -23015,16 +23868,16 @@ wordwrap@0.0.2: resolved "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz#b79669bb42ecb409f83d583cad52ca17eaa1643f" integrity sha1-t5Zpu0LstAn4PVg8rVLKF+qhZD8= +wordwrap@^1.0.0, wordwrap@~1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb" + integrity sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus= + wordwrap@~0.0.2: version "0.0.3" resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.3.tgz#a3d5da6cd5c0bc0008d37234bbaf1bed63059107" integrity sha1-o9XabNXAvAAI03I0u68b7WMFkQc= -wordwrap@~1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb" - integrity sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus= - workbox-background-sync@^4.3.1: version "4.3.1" resolved "https://registry.npmjs.org/workbox-background-sync/-/workbox-background-sync-4.3.1.tgz#26821b9bf16e9e37fd1d640289edddc08afd1950" @@ -23435,6 +24288,13 @@ yargs-parser@^7.0.0: dependencies: camelcase "^4.1.0" +yargs-parser@^8.1.0: + version "8.1.0" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-8.1.0.tgz#f1376a33b6629a5d063782944da732631e966950" + integrity sha512-yP+6QqN8BmrgW2ggLtTbdrOyBNSI7zBa4IykmiV5R1wl1JWNxQvWhMfMdmzIYtKU7oP3OOInY/tl2ov3BDjnJQ== + dependencies: + camelcase "^4.1.0" + yargs-parser@^9.0.2: version "9.0.2" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-9.0.2.tgz#9ccf6a43460fe4ed40a9bb68f48d43b8a68cc077" @@ -23513,6 +24373,24 @@ yargs@13.2.4: y18n "^4.0.0" yargs-parser "^13.1.0" +yargs@^10.0.3: + version "10.1.2" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-10.1.2.tgz#454d074c2b16a51a43e2fb7807e4f9de69ccb5c5" + integrity sha512-ivSoxqBGYOqQVruxD35+EyCFDYNEFL/Uo6FcOnz+9xZdZzK0Zzw4r4KhbrME1Oo2gOggwJod2MnsdamSG7H9ig== + dependencies: + cliui "^4.0.0" + decamelize "^1.1.1" + find-up "^2.1.0" + get-caller-file "^1.0.1" + os-locale "^2.0.0" + require-directory "^2.1.1" + require-main-filename "^1.0.1" + set-blocking "^2.0.0" + string-width "^2.0.0" + which-module "^2.0.0" + y18n "^3.2.1" + yargs-parser "^8.1.0" + yargs@^13.0.0, yargs@^13.2.0, yargs@^13.2.1, yargs@^13.2.2, yargs@^13.3.0: version "13.3.0" resolved "https://registry.npmjs.org/yargs/-/yargs-13.3.0.tgz#4c657a55e07e5f2cf947f8a366567c04a0dedc83" From f51f44510b86e85e56b3fa0565ae24e1c5526f61 Mon Sep 17 00:00:00 2001 From: Ryan Hall Date: Fri, 1 Nov 2019 11:15:47 -0400 Subject: [PATCH 069/199] add error logging to handleMessage for JSON-RPC responses --- explorer/src/server/handleMessage.ts | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/explorer/src/server/handleMessage.ts b/explorer/src/server/handleMessage.ts index 2f71498a9a7..18e272b152b 100644 --- a/explorer/src/server/handleMessage.ts +++ b/explorer/src/server/handleMessage.ts @@ -26,9 +26,14 @@ const handleJSONRCP = async (request: string, context: ServerContext) => { callRPCServer( request, context, - (error: jayson.JSONRPCErrorLike, response: jayson.JSONRPCResultLike) => { - // resolve both error and success responses - error ? resolve(error) : resolve(response) + (error: jayson.JSONRPCErrorLike, result: jayson.JSONRPCResultLike) => { + // resolve both errored and successful responses + if (error) { + logger.error(error.message) + resolve(error) + } else { + resolve(result) + } }, ) }) From a014095c3ba35069114ab63b127d82dcc408e896 Mon Sep 17 00:00:00 2001 From: Ryan Hall Date: Fri, 1 Nov 2019 11:20:37 -0400 Subject: [PATCH 070/199] add comment --- explorer/src/server/handleMessage.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/explorer/src/server/handleMessage.ts b/explorer/src/server/handleMessage.ts index 18e272b152b..48c2278f983 100644 --- a/explorer/src/server/handleMessage.ts +++ b/explorer/src/server/handleMessage.ts @@ -8,6 +8,7 @@ export interface ServerContext { chainlinkNodeId: number } +// legacy server response synonymous with upsertJobRun RPC method const handleLegacy = async (json: string, context: ServerContext) => { try { const db = await getDb() From ae4314302dccba935c445a01d6939230b762b13d Mon Sep 17 00:00:00 2001 From: Ryan Hall Date: Fri, 1 Nov 2019 11:32:33 -0400 Subject: [PATCH 071/199] breakup realtime.test.ts into 3 smaller tests --- explorer/src/__tests__/server/legacy.test.ts | 181 +++++++++++++++++ .../src/__tests__/server/realtime.test.ts | 98 +++++++++ .../rpcMethods/upsertJobRun.test.ts} | 187 ++---------------- 3 files changed, 292 insertions(+), 174 deletions(-) create mode 100644 explorer/src/__tests__/server/legacy.test.ts create mode 100644 explorer/src/__tests__/server/realtime.test.ts rename explorer/src/__tests__/{realtime.test.ts => server/rpcMethods/upsertJobRun.test.ts} (57%) diff --git a/explorer/src/__tests__/server/legacy.test.ts b/explorer/src/__tests__/server/legacy.test.ts new file mode 100644 index 00000000000..99e68f0fa7e --- /dev/null +++ b/explorer/src/__tests__/server/legacy.test.ts @@ -0,0 +1,181 @@ +import { Server } from 'http' +import { Connection, getCustomRepository } from 'typeorm' +import WebSocket from 'ws' +import { getDb } from '../../database' +import { ChainlinkNode, createChainlinkNode } from '../../entity/ChainlinkNode' +import { JobRun } from '../../entity/JobRun' +import { TaskRun } from '../../entity/TaskRun' +import { DEFAULT_TEST_PORT, start, stop } from '../../support/server' +import ethtxFixture from '../fixtures/JobRun.ethtx.fixture.json' +import createFixture from '../fixtures/JobRun.fixture.json' +import updateFixture from '../fixtures/JobRunUpdate.fixture.json' +import { clearDb } from '../testdatabase' +import { ACCESS_KEY_HEADER, SECRET_HEADER } from '../../utils/constants' +import { JobRunRepository } from '../../repositories/JobRunRepository' + +const ENDPOINT = `ws://localhost:${DEFAULT_TEST_PORT}` + +const newChainlinkNode = ( + url: string, + accessKey: string, + secret: string, +): Promise => { + const ws = new WebSocket(ENDPOINT, { + headers: { + [ACCESS_KEY_HEADER]: accessKey, + [SECRET_HEADER]: secret, + }, + }) + + return new Promise((resolve: (arg0: WebSocket) => void, reject) => { + ws.on('error', (error: Error) => { + reject(error) + }) + + ws.on('open', () => resolve(ws)) + }) +} + +describe('realtime', () => { + let server: Server + let db: Connection + let chainlinkNode: ChainlinkNode + let secret: string + + const authenticatedNode = async () => + newChainlinkNode(ENDPOINT, chainlinkNode.accessKey, secret) + + beforeAll(async () => { + server = await start() + db = await getDb() + }) + + beforeEach(async () => { + clearDb() + ;[chainlinkNode, secret] = await createChainlinkNode( + db, + 'explore realtime test chainlinkNode', + ) + }) + + afterAll(done => stop(server, done)) + + describe('when sending messages in legacy format', () => { + it('can create a job run with valid JSON', async () => { + expect.assertions(3) + + const ws = await authenticatedNode() + ws.send(JSON.stringify(createFixture)) + + await new Promise(resolve => { + ws.on('message', (data: WebSocket.Data) => { + const response = JSON.parse(data as string) + expect(response.status).toEqual(201) + ws.close() + resolve() + }) + }) + + const jobRunCount = await db.manager.count(JobRun) + expect(jobRunCount).toEqual(1) + + const taskRunCount = await db.manager.count(TaskRun) + expect(taskRunCount).toEqual(1) + }) + + it('can create and update a job run and task runs', async () => { + expect.assertions(6) + + const ws = await authenticatedNode() + ws.send(JSON.stringify(createFixture)) + + await new Promise(resolve => { + let responses = 0 + ws.on('message', (data: any) => { + responses += 1 + const response = JSON.parse(data) + + if (responses === 1) { + expect(response.status).toEqual(201) + ws.send(JSON.stringify(updateFixture)) + } + + if (responses === 2) { + expect(response.status).toEqual(201) + ws.close() + resolve() + } + }) + }) + + const jobRunCount = await db.manager.count(JobRun) + expect(jobRunCount).toEqual(1) + + const taskRunCount = await db.manager.count(TaskRun) + expect(taskRunCount).toEqual(1) + + const jr = await db.manager.findOne(JobRun) + expect(jr.status).toEqual('completed') + + const tr = jr.taskRuns[0] + expect(tr.status).toEqual('completed') + }) + + it('can create a task run with transactionHash and status', async () => { + expect.assertions(10) + + const ws = await authenticatedNode() + + const messageReceived = new Promise(resolve => { + ws.on('message', (data: any) => { + const response = JSON.parse(data) + expect(response.status).toEqual(201) + resolve() + }) + }) + + ws.send(JSON.stringify(ethtxFixture)) + await messageReceived + + const jobRunCount = await db.manager.count(JobRun) + expect(jobRunCount).toEqual(1) + + const taskRunCount = await db.manager.count(TaskRun) + expect(taskRunCount).toEqual(4) + + const jobRunRepository = getCustomRepository(JobRunRepository, db.name) + const jr = await jobRunRepository.getFirst() + + expect(jr.status).toEqual('completed') + + const tr = jr.taskRuns[3] + expect(tr.status).toEqual('completed') + expect(tr.transactionHash).toEqual( + '0x1111111111111111111111111111111111111111111111111111111111111111', + ) + expect(tr.timestamp).toEqual(new Date('2018-01-08T18:12:01.103Z')) + expect(tr.blockHeight).toEqual('3735928559') + expect(tr.blockHash).toEqual('0xbadc0de5') + expect(tr.transactionStatus).toEqual('fulfilledRunLog') + ws.close() + }) + + it('rejects malformed json events with code 422', async (done: any) => { + expect.assertions(2) + + const ws = await authenticatedNode() + ws.send('{invalid json}') + + ws.on('message', async (data: any) => { + const response = JSON.parse(data) + expect(response.status).toEqual(422) + + const count = await db.manager.count(JobRun) + expect(count).toEqual(0) + + ws.close() + done() + }) + }) + }) +}) diff --git a/explorer/src/__tests__/server/realtime.test.ts b/explorer/src/__tests__/server/realtime.test.ts new file mode 100644 index 00000000000..7a6cc8c9d23 --- /dev/null +++ b/explorer/src/__tests__/server/realtime.test.ts @@ -0,0 +1,98 @@ +import { Server } from 'http' +import { Connection } from 'typeorm' +import WebSocket from 'ws' +import { getDb } from '../../database' +import { ChainlinkNode, createChainlinkNode } from '../../entity/ChainlinkNode' +import { DEFAULT_TEST_PORT, start, stop } from '../../support/server' +import { clearDb } from '../testdatabase' +import { + ACCESS_KEY_HEADER, + NORMAL_CLOSE, + SECRET_HEADER, +} from '../../utils/constants' + +const ENDPOINT = `ws://localhost:${DEFAULT_TEST_PORT}` + +const newChainlinkNode = ( + url: string, + accessKey: string, + secret: string, +): Promise => { + const ws = new WebSocket(ENDPOINT, { + headers: { + [ACCESS_KEY_HEADER]: accessKey, + [SECRET_HEADER]: secret, + }, + }) + + return new Promise((resolve: (arg0: WebSocket) => void, reject) => { + ws.on('error', (error: Error) => { + reject(error) + }) + + ws.on('open', () => resolve(ws)) + }) +} + +describe('realtime', () => { + let server: Server + let db: Connection + let chainlinkNode: ChainlinkNode + let secret: string + + beforeAll(async () => { + server = await start() + db = await getDb() + }) + + beforeEach(async () => { + clearDb() + ;[chainlinkNode, secret] = await createChainlinkNode( + db, + 'explore realtime test chainlinkNode', + ) + }) + + afterAll(done => stop(server, done)) + + it('rejects invalid authentication', async (done: any) => { + expect.assertions(1) + + newChainlinkNode(ENDPOINT, chainlinkNode.accessKey, 'lol-no').catch( + error => { + expect(error).toBeDefined() + done() + }, + ) + }) + + it('rejects multiple connections from single node', async done => { + expect.assertions(8) + + // eslint-disable-next-line prefer-const + let ws1: WebSocket, ws2: WebSocket, ws3: WebSocket + + // eslint-disable-next-line prefer-const + ws1 = await newChainlinkNode(ENDPOINT, chainlinkNode.accessKey, secret) + + ws1.addEventListener('close', (event: WebSocket.CloseEvent) => { + expect(ws1.readyState).toBe(WebSocket.CLOSED) + expect(ws2.readyState).toBe(WebSocket.OPEN) + expect(event.code).toBe(NORMAL_CLOSE) + expect(event.reason).toEqual('Duplicate connection opened') + }) + + ws2 = await newChainlinkNode(ENDPOINT, chainlinkNode.accessKey, secret) + + ws2.addEventListener('close', (event: WebSocket.CloseEvent) => { + expect(ws2.readyState).toBe(WebSocket.CLOSED) + expect(ws3.readyState).toBe(WebSocket.OPEN) + expect(event.code).toBe(NORMAL_CLOSE) + expect(event.reason).toEqual('Duplicate connection opened') + ws3.close() + done() + }) + + ws3 = await newChainlinkNode(ENDPOINT, chainlinkNode.accessKey, secret) + }) +}) diff --git a/explorer/src/__tests__/realtime.test.ts b/explorer/src/__tests__/server/rpcMethods/upsertJobRun.test.ts similarity index 57% rename from explorer/src/__tests__/realtime.test.ts rename to explorer/src/__tests__/server/rpcMethods/upsertJobRun.test.ts index aea1bfc54ac..ba36e21300f 100644 --- a/explorer/src/__tests__/realtime.test.ts +++ b/explorer/src/__tests__/server/rpcMethods/upsertJobRun.test.ts @@ -2,21 +2,20 @@ import { Server } from 'http' import { Connection, getCustomRepository } from 'typeorm' import WebSocket from 'ws' import jayson from 'jayson' -import { getDb } from '../database' -import { ChainlinkNode, createChainlinkNode } from '../entity/ChainlinkNode' -import { JobRun } from '../entity/JobRun' -import { TaskRun } from '../entity/TaskRun' -import { DEFAULT_TEST_PORT, start, stop } from '../support/server' -import ethtxFixture from './fixtures/JobRun.ethtx.fixture.json' -import createFixture from './fixtures/JobRun.fixture.json' -import updateFixture from './fixtures/JobRunUpdate.fixture.json' -import { clearDb } from './testdatabase' +import { getDb } from '../../../database' import { - ACCESS_KEY_HEADER, - NORMAL_CLOSE, - SECRET_HEADER, -} from '../utils/constants' -import { JobRunRepository } from '../repositories/JobRunRepository' + ChainlinkNode, + createChainlinkNode, +} from '../../../entity/ChainlinkNode' +import { JobRun } from '../../../entity/JobRun' +import { TaskRun } from '../../../entity/TaskRun' +import { DEFAULT_TEST_PORT, start, stop } from '../../../support/server' +import ethtxFixture from '../../fixtures/JobRun.ethtx.fixture.json' +import createFixture from '../../fixtures/JobRun.fixture.json' +import updateFixture from '../../fixtures/JobRunUpdate.fixture.json' +import { clearDb } from '../../testdatabase' +import { ACCESS_KEY_HEADER, SECRET_HEADER } from '../../../utils/constants' +import { JobRunRepository } from '../../../repositories/JobRunRepository' const ENDPOINT = `ws://localhost:${DEFAULT_TEST_PORT}` @@ -69,125 +68,6 @@ describe('realtime', () => { afterAll(done => stop(server, done)) - describe('when sending messages in legacy format', () => { - it('can create a job run with valid JSON', async () => { - expect.assertions(3) - - const ws = await authenticatedNode() - ws.send(JSON.stringify(createFixture)) - - await new Promise(resolve => { - ws.on('message', (data: WebSocket.Data) => { - const response = JSON.parse(data as string) - expect(response.status).toEqual(201) - ws.close() - resolve() - }) - }) - - const jobRunCount = await db.manager.count(JobRun) - expect(jobRunCount).toEqual(1) - - const taskRunCount = await db.manager.count(TaskRun) - expect(taskRunCount).toEqual(1) - }) - - it('can create and update a job run and task runs', async () => { - expect.assertions(6) - - const ws = await authenticatedNode() - ws.send(JSON.stringify(createFixture)) - - await new Promise(resolve => { - let responses = 0 - ws.on('message', (data: any) => { - responses += 1 - const response = JSON.parse(data) - - if (responses === 1) { - expect(response.status).toEqual(201) - ws.send(JSON.stringify(updateFixture)) - } - - if (responses === 2) { - expect(response.status).toEqual(201) - ws.close() - resolve() - } - }) - }) - - const jobRunCount = await db.manager.count(JobRun) - expect(jobRunCount).toEqual(1) - - const taskRunCount = await db.manager.count(TaskRun) - expect(taskRunCount).toEqual(1) - - const jr = await db.manager.findOne(JobRun) - expect(jr.status).toEqual('completed') - - const tr = jr.taskRuns[0] - expect(tr.status).toEqual('completed') - }) - - it('can create a task run with transactionHash and status', async () => { - expect.assertions(10) - - const ws = await authenticatedNode() - - const messageReceived = new Promise(resolve => { - ws.on('message', (data: any) => { - const response = JSON.parse(data) - expect(response.status).toEqual(201) - resolve() - }) - }) - - ws.send(JSON.stringify(ethtxFixture)) - await messageReceived - - const jobRunCount = await db.manager.count(JobRun) - expect(jobRunCount).toEqual(1) - - const taskRunCount = await db.manager.count(TaskRun) - expect(taskRunCount).toEqual(4) - - const jobRunRepository = getCustomRepository(JobRunRepository, db.name) - const jr = await jobRunRepository.getFirst() - - expect(jr.status).toEqual('completed') - - const tr = jr.taskRuns[3] - expect(tr.status).toEqual('completed') - expect(tr.transactionHash).toEqual( - '0x1111111111111111111111111111111111111111111111111111111111111111', - ) - expect(tr.timestamp).toEqual(new Date('2018-01-08T18:12:01.103Z')) - expect(tr.blockHeight).toEqual('3735928559') - expect(tr.blockHash).toEqual('0xbadc0de5') - expect(tr.transactionStatus).toEqual('fulfilledRunLog') - ws.close() - }) - - it('rejects malformed json events with code 422', async (done: any) => { - expect.assertions(2) - - const ws = await authenticatedNode() - ws.send('{invalid json}') - - ws.on('message', async (data: any) => { - const response = JSON.parse(data) - expect(response.status).toEqual(422) - - const count = await db.manager.count(JobRun) - expect(count).toEqual(0) - - ws.close() - done() - }) - }) - }) - describe('when sending messages in JSON-RPC format', () => { describe('#upsertJobRun', () => { it('can create a job run with valid JSON', async () => { @@ -377,45 +257,4 @@ describe('realtime', () => { }) }) }) - - it('rejects invalid authentication', async (done: any) => { - expect.assertions(1) - - newChainlinkNode(ENDPOINT, chainlinkNode.accessKey, 'lol-no').catch( - error => { - expect(error).toBeDefined() - done() - }, - ) - }) - - it('rejects multiple connections from single node', async done => { - expect.assertions(8) - - // eslint-disable-next-line prefer-const - let ws1: WebSocket, ws2: WebSocket, ws3: WebSocket - - // eslint-disable-next-line prefer-const - ws1 = await newChainlinkNode(ENDPOINT, chainlinkNode.accessKey, secret) - - ws1.addEventListener('close', (event: WebSocket.CloseEvent) => { - expect(ws1.readyState).toBe(WebSocket.CLOSED) - expect(ws2.readyState).toBe(WebSocket.OPEN) - expect(event.code).toBe(NORMAL_CLOSE) - expect(event.reason).toEqual('Duplicate connection opened') - }) - - ws2 = await newChainlinkNode(ENDPOINT, chainlinkNode.accessKey, secret) - - ws2.addEventListener('close', (event: WebSocket.CloseEvent) => { - expect(ws2.readyState).toBe(WebSocket.CLOSED) - expect(ws3.readyState).toBe(WebSocket.OPEN) - expect(event.code).toBe(NORMAL_CLOSE) - expect(event.reason).toEqual('Duplicate connection opened') - ws3.close() - done() - }) - - ws3 = await newChainlinkNode(ENDPOINT, chainlinkNode.accessKey, secret) - }) }) From 85f73fe285d94d0ef155b8f91611dc88dcc6d579 Mon Sep 17 00:00:00 2001 From: Ryan Hall Date: Fri, 1 Nov 2019 12:21:13 -0400 Subject: [PATCH 072/199] add support/client.ts --- .../__tests__/rpcMethods/upsertJobRun.test.ts | 164 +++++++++++ explorer/src/__tests__/server/legacy.test.ts | 27 +- .../src/__tests__/server/realtime.test.ts | 107 +++++-- .../server/rpcMethods/upsertJobRun.test.ts | 260 ------------------ explorer/src/support/client.ts | 31 +++ 5 files changed, 276 insertions(+), 313 deletions(-) create mode 100644 explorer/src/__tests__/rpcMethods/upsertJobRun.test.ts delete mode 100644 explorer/src/__tests__/server/rpcMethods/upsertJobRun.test.ts create mode 100644 explorer/src/support/client.ts diff --git a/explorer/src/__tests__/rpcMethods/upsertJobRun.test.ts b/explorer/src/__tests__/rpcMethods/upsertJobRun.test.ts new file mode 100644 index 00000000000..bf7efc47b56 --- /dev/null +++ b/explorer/src/__tests__/rpcMethods/upsertJobRun.test.ts @@ -0,0 +1,164 @@ +import { Server } from 'http' +import { Connection, getCustomRepository } from 'typeorm' +import WebSocket from 'ws' +import { getDb } from '../../database' +import { ChainlinkNode, createChainlinkNode } from '../../entity/ChainlinkNode' +import { JobRun } from '../../entity/JobRun' +import { TaskRun } from '../../entity/TaskRun' +import { start, stop } from '../../support/server' +import ethtxFixture from '../fixtures/JobRun.ethtx.fixture.json' +import createFixture from '../fixtures/JobRun.fixture.json' +import updateFixture from '../fixtures/JobRunUpdate.fixture.json' +import { clearDb } from '../testdatabase' +import { JobRunRepository } from '../../repositories/JobRunRepository' +import { + ENDPOINT, + createRPCRequest, + newChainlinkNode, +} from '../../support/client' + +describe('realtime', () => { + let server: Server + let db: Connection + let chainlinkNode: ChainlinkNode + let secret: string + + const authenticatedNode = async () => + newChainlinkNode(ENDPOINT, chainlinkNode.accessKey, secret) + + beforeAll(async () => { + server = await start() + db = await getDb() + }) + + beforeEach(async () => { + clearDb() + ;[chainlinkNode, secret] = await createChainlinkNode( + db, + 'explore realtime test chainlinkNode', + ) + }) + + afterAll(done => stop(server, done)) + + describe('#upsertJobRun', () => { + it('can create a job run with valid JSON', async () => { + expect.assertions(3) + + const ws = await authenticatedNode() + const request = createRPCRequest('upsertJobRun', createFixture) + ws.send(JSON.stringify(request)) + + await new Promise(resolve => { + ws.on('message', (data: WebSocket.Data) => { + const response = JSON.parse(data as string) + expect(response.result).toEqual('success') + ws.close() + resolve() + }) + }) + + const jobRunCount = await db.manager.count(JobRun) + expect(jobRunCount).toEqual(1) + + const taskRunCount = await db.manager.count(TaskRun) + expect(taskRunCount).toEqual(1) + }) + + it('can create and update a job run and task runs', async () => { + expect.assertions(6) + + const ws = await authenticatedNode() + const createRequest = createRPCRequest('upsertJobRun', createFixture) + const updateRequest = createRPCRequest('upsertJobRun', updateFixture) + ws.send(JSON.stringify(createRequest)) + + await new Promise(resolve => { + ws.on('message', (data: any) => { + const response = JSON.parse(data) + if (response.id === createRequest.id) { + expect(response.result).toEqual('success') + ws.send(JSON.stringify(updateRequest)) + } + if (response.id === updateRequest.id) { + expect(response.result).toEqual('success') + ws.close() + resolve() + } + }) + }) + + const jobRunCount = await db.manager.count(JobRun) + expect(jobRunCount).toEqual(1) + + const taskRunCount = await db.manager.count(TaskRun) + expect(taskRunCount).toEqual(1) + + const jr = await db.manager.findOne(JobRun) + expect(jr.status).toEqual('completed') + + const tr = jr.taskRuns[0] + expect(tr.status).toEqual('completed') + }) + + it('can create a task run with transactionHash and status', async () => { + expect.assertions(10) + + const ws = await authenticatedNode() + + const messageReceived = new Promise(resolve => { + ws.on('message', (data: any) => { + const response = JSON.parse(data) + expect(response.result).toEqual('success') + resolve() + }) + }) + + const request = createRPCRequest('upsertJobRun', ethtxFixture) + ws.send(JSON.stringify(request)) + + await messageReceived + + const jobRunCount = await db.manager.count(JobRun) + expect(jobRunCount).toEqual(1) + + const taskRunCount = await db.manager.count(TaskRun) + expect(taskRunCount).toEqual(4) + + const jobRunRepository = getCustomRepository(JobRunRepository, db.name) + const jr = await jobRunRepository.getFirst() + + expect(jr.status).toEqual('completed') + + const tr = jr.taskRuns[3] + expect(tr.status).toEqual('completed') + expect(tr.transactionHash).toEqual( + '0x1111111111111111111111111111111111111111111111111111111111111111', + ) + expect(tr.timestamp).toEqual(new Date('2018-01-08T18:12:01.103Z')) + expect(tr.blockHeight).toEqual('3735928559') + expect(tr.blockHash).toEqual('0xbadc0de5') + expect(tr.transactionStatus).toEqual('fulfilledRunLog') + ws.close() + }) + + it('rejects invalid params with code -32602', async (done: any) => { + expect.assertions(2) + + const ws = await authenticatedNode() + const request = createRPCRequest('upsertJobRun', { invalid: 'params' }) + ws.send(JSON.stringify(request)) + + ws.on('message', async (data: any) => { + const response = JSON.parse(data) + expect(response.error.code).toEqual(-32602) + + const count = await db.manager.count(JobRun) + expect(count).toEqual(0) + + ws.close() + done() + }) + }) + }) +}) diff --git a/explorer/src/__tests__/server/legacy.test.ts b/explorer/src/__tests__/server/legacy.test.ts index 99e68f0fa7e..a1dc6e520ac 100644 --- a/explorer/src/__tests__/server/legacy.test.ts +++ b/explorer/src/__tests__/server/legacy.test.ts @@ -5,36 +5,13 @@ import { getDb } from '../../database' import { ChainlinkNode, createChainlinkNode } from '../../entity/ChainlinkNode' import { JobRun } from '../../entity/JobRun' import { TaskRun } from '../../entity/TaskRun' -import { DEFAULT_TEST_PORT, start, stop } from '../../support/server' +import { start, stop } from '../../support/server' import ethtxFixture from '../fixtures/JobRun.ethtx.fixture.json' import createFixture from '../fixtures/JobRun.fixture.json' import updateFixture from '../fixtures/JobRunUpdate.fixture.json' import { clearDb } from '../testdatabase' -import { ACCESS_KEY_HEADER, SECRET_HEADER } from '../../utils/constants' import { JobRunRepository } from '../../repositories/JobRunRepository' - -const ENDPOINT = `ws://localhost:${DEFAULT_TEST_PORT}` - -const newChainlinkNode = ( - url: string, - accessKey: string, - secret: string, -): Promise => { - const ws = new WebSocket(ENDPOINT, { - headers: { - [ACCESS_KEY_HEADER]: accessKey, - [SECRET_HEADER]: secret, - }, - }) - - return new Promise((resolve: (arg0: WebSocket) => void, reject) => { - ws.on('error', (error: Error) => { - reject(error) - }) - - ws.on('open', () => resolve(ws)) - }) -} +import { ENDPOINT, newChainlinkNode } from '../../support/client' describe('realtime', () => { let server: Server diff --git a/explorer/src/__tests__/server/realtime.test.ts b/explorer/src/__tests__/server/realtime.test.ts index 7a6cc8c9d23..552db1ad020 100644 --- a/explorer/src/__tests__/server/realtime.test.ts +++ b/explorer/src/__tests__/server/realtime.test.ts @@ -3,36 +3,15 @@ import { Connection } from 'typeorm' import WebSocket from 'ws' import { getDb } from '../../database' import { ChainlinkNode, createChainlinkNode } from '../../entity/ChainlinkNode' -import { DEFAULT_TEST_PORT, start, stop } from '../../support/server' +import { JobRun } from '../../entity/JobRun' +import { start, stop } from '../../support/server' import { clearDb } from '../testdatabase' +import { NORMAL_CLOSE } from '../../utils/constants' import { - ACCESS_KEY_HEADER, - NORMAL_CLOSE, - SECRET_HEADER, -} from '../../utils/constants' - -const ENDPOINT = `ws://localhost:${DEFAULT_TEST_PORT}` - -const newChainlinkNode = ( - url: string, - accessKey: string, - secret: string, -): Promise => { - const ws = new WebSocket(ENDPOINT, { - headers: { - [ACCESS_KEY_HEADER]: accessKey, - [SECRET_HEADER]: secret, - }, - }) - - return new Promise((resolve: (arg0: WebSocket) => void, reject) => { - ws.on('error', (error: Error) => { - reject(error) - }) - - ws.on('open', () => resolve(ws)) - }) -} + ENDPOINT, + createRPCRequest, + newChainlinkNode, +} from '../../support/client' describe('realtime', () => { let server: Server @@ -40,6 +19,9 @@ describe('realtime', () => { let chainlinkNode: ChainlinkNode let secret: string + const newAuthenticatedNode = async () => + newChainlinkNode(ENDPOINT, chainlinkNode.accessKey, secret) + beforeAll(async () => { server = await start() db = await getDb() @@ -55,6 +37,75 @@ describe('realtime', () => { afterAll(done => stop(server, done)) + describe('when sending messages in JSON-RPC format', () => { + it('rejects non-existing methods with code -32601', async (done: any) => { + expect.assertions(2) + + const ws = await newAuthenticatedNode() + const request = createRPCRequest('doesNotExist') + ws.send(JSON.stringify(request)) + + ws.on('message', async (data: any) => { + const response = JSON.parse(data) + expect(response.error.code).toEqual(-32601) + + const count = await db.manager.count(JobRun) + expect(count).toEqual(0) + + ws.close() + done() + }) + }) + + // this test depends on the presence of "jsonrpc" in the message + // otherwise, the server will attempt to process the message as a + // legacy message and will respond with { status: 422 }. + // This test will be more appropriate once the legacy format is removed. + it('rejects malformed json with code -32700', async (done: any) => { + expect.assertions(2) + + const ws = await newAuthenticatedNode() + const request = 'jsonrpc invalid' + ws.send(request) + + ws.on('message', async (data: any) => { + const response = JSON.parse(data) + expect(response.error.code).toEqual(-32700) + + const count = await db.manager.count(JobRun) + expect(count).toEqual(0) + + ws.close() + done() + }) + }) + + it('rejects invalid rpc requests with code -32600', async (done: any) => { + expect.assertions(2) + + const ws = await newAuthenticatedNode() + + const request = { + jsonrpc: '2.0', + function: 'foo', + id: 1, + } + + ws.send(JSON.stringify(request)) + + ws.on('message', async (data: any) => { + const response = JSON.parse(data) + expect(response.error.code).toEqual(-32600) + + const count = await db.manager.count(JobRun) + expect(count).toEqual(0) + + ws.close() + done() + }) + }) + }) + it('rejects invalid authentication', async (done: any) => { expect.assertions(1) diff --git a/explorer/src/__tests__/server/rpcMethods/upsertJobRun.test.ts b/explorer/src/__tests__/server/rpcMethods/upsertJobRun.test.ts deleted file mode 100644 index ba36e21300f..00000000000 --- a/explorer/src/__tests__/server/rpcMethods/upsertJobRun.test.ts +++ /dev/null @@ -1,260 +0,0 @@ -import { Server } from 'http' -import { Connection, getCustomRepository } from 'typeorm' -import WebSocket from 'ws' -import jayson from 'jayson' -import { getDb } from '../../../database' -import { - ChainlinkNode, - createChainlinkNode, -} from '../../../entity/ChainlinkNode' -import { JobRun } from '../../../entity/JobRun' -import { TaskRun } from '../../../entity/TaskRun' -import { DEFAULT_TEST_PORT, start, stop } from '../../../support/server' -import ethtxFixture from '../../fixtures/JobRun.ethtx.fixture.json' -import createFixture from '../../fixtures/JobRun.fixture.json' -import updateFixture from '../../fixtures/JobRunUpdate.fixture.json' -import { clearDb } from '../../testdatabase' -import { ACCESS_KEY_HEADER, SECRET_HEADER } from '../../../utils/constants' -import { JobRunRepository } from '../../../repositories/JobRunRepository' - -const ENDPOINT = `ws://localhost:${DEFAULT_TEST_PORT}` - -const newChainlinkNode = ( - url: string, - accessKey: string, - secret: string, -): Promise => { - const ws = new WebSocket(ENDPOINT, { - headers: { - [ACCESS_KEY_HEADER]: accessKey, - [SECRET_HEADER]: secret, - }, - }) - - return new Promise((resolve: (arg0: WebSocket) => void, reject) => { - ws.on('error', (error: Error) => { - reject(error) - }) - - ws.on('open', () => resolve(ws)) - }) -} - -const jsonClient = new jayson.Client(null, null) -const createRPCRequest = (method: string, params?: any) => - jsonClient.request(method, params) - -describe('realtime', () => { - let server: Server - let db: Connection - let chainlinkNode: ChainlinkNode - let secret: string - - const authenticatedNode = async () => - newChainlinkNode(ENDPOINT, chainlinkNode.accessKey, secret) - - beforeAll(async () => { - server = await start() - db = await getDb() - }) - - beforeEach(async () => { - clearDb() - ;[chainlinkNode, secret] = await createChainlinkNode( - db, - 'explore realtime test chainlinkNode', - ) - }) - - afterAll(done => stop(server, done)) - - describe('when sending messages in JSON-RPC format', () => { - describe('#upsertJobRun', () => { - it('can create a job run with valid JSON', async () => { - expect.assertions(3) - - const ws = await authenticatedNode() - const request = createRPCRequest('upsertJobRun', createFixture) - ws.send(JSON.stringify(request)) - - await new Promise(resolve => { - ws.on('message', (data: WebSocket.Data) => { - const response = JSON.parse(data as string) - expect(response.result).toEqual('success') - ws.close() - resolve() - }) - }) - - const jobRunCount = await db.manager.count(JobRun) - expect(jobRunCount).toEqual(1) - - const taskRunCount = await db.manager.count(TaskRun) - expect(taskRunCount).toEqual(1) - }) - - it('can create and update a job run and task runs', async () => { - expect.assertions(6) - - const ws = await authenticatedNode() - const createRequest = createRPCRequest('upsertJobRun', createFixture) - const updateRequest = createRPCRequest('upsertJobRun', updateFixture) - ws.send(JSON.stringify(createRequest)) - - await new Promise(resolve => { - ws.on('message', (data: any) => { - const response = JSON.parse(data) - if (response.id === createRequest.id) { - expect(response.result).toEqual('success') - ws.send(JSON.stringify(updateRequest)) - } - if (response.id === updateRequest.id) { - expect(response.result).toEqual('success') - ws.close() - resolve() - } - }) - }) - - const jobRunCount = await db.manager.count(JobRun) - expect(jobRunCount).toEqual(1) - - const taskRunCount = await db.manager.count(TaskRun) - expect(taskRunCount).toEqual(1) - - const jr = await db.manager.findOne(JobRun) - expect(jr.status).toEqual('completed') - - const tr = jr.taskRuns[0] - expect(tr.status).toEqual('completed') - }) - - it('can create a task run with transactionHash and status', async () => { - expect.assertions(10) - - const ws = await authenticatedNode() - - const messageReceived = new Promise(resolve => { - ws.on('message', (data: any) => { - const response = JSON.parse(data) - expect(response.result).toEqual('success') - resolve() - }) - }) - - const request = createRPCRequest('upsertJobRun', ethtxFixture) - ws.send(JSON.stringify(request)) - - await messageReceived - - const jobRunCount = await db.manager.count(JobRun) - expect(jobRunCount).toEqual(1) - - const taskRunCount = await db.manager.count(TaskRun) - expect(taskRunCount).toEqual(4) - - const jobRunRepository = getCustomRepository(JobRunRepository, db.name) - const jr = await jobRunRepository.getFirst() - - expect(jr.status).toEqual('completed') - - const tr = jr.taskRuns[3] - expect(tr.status).toEqual('completed') - expect(tr.transactionHash).toEqual( - '0x1111111111111111111111111111111111111111111111111111111111111111', - ) - expect(tr.timestamp).toEqual(new Date('2018-01-08T18:12:01.103Z')) - expect(tr.blockHeight).toEqual('3735928559') - expect(tr.blockHash).toEqual('0xbadc0de5') - expect(tr.transactionStatus).toEqual('fulfilledRunLog') - ws.close() - }) - - it('rejects invalid params with code -32602', async (done: any) => { - expect.assertions(2) - - const ws = await authenticatedNode() - const request = createRPCRequest('upsertJobRun', { invalid: 'params' }) - ws.send(JSON.stringify(request)) - - ws.on('message', async (data: any) => { - const response = JSON.parse(data) - expect(response.error.code).toEqual(-32602) - - const count = await db.manager.count(JobRun) - expect(count).toEqual(0) - - ws.close() - done() - }) - }) - }) - - it('rejects non-existing methods with code -32601', async (done: any) => { - expect.assertions(2) - - const ws = await authenticatedNode() - const request = createRPCRequest('doesNotExist', { invalid: 'params' }) - ws.send(JSON.stringify(request)) - - ws.on('message', async (data: any) => { - const response = JSON.parse(data) - expect(response.error.code).toEqual(-32601) - - const count = await db.manager.count(JobRun) - expect(count).toEqual(0) - - ws.close() - done() - }) - }) - - // this test depends on the presence of "jsonrpc" in the message - // otherwise, the server will attempt to process the message as a - // legacy message and will respond with { status: 422 }. - // This test will be more appropriate once the legacy format is removed. - it('rejects malformed json with code -32700', async (done: any) => { - expect.assertions(2) - - const ws = await authenticatedNode() - const request = 'jsonrpc invalid' - ws.send(request) - - ws.on('message', async (data: any) => { - const response = JSON.parse(data) - expect(response.error.code).toEqual(-32700) - - const count = await db.manager.count(JobRun) - expect(count).toEqual(0) - - ws.close() - done() - }) - }) - - it('rejects invalid rpc requests with code -32600', async (done: any) => { - expect.assertions(2) - - const ws = await authenticatedNode() - - const request = { - jsonrpc: '2.0', - function: 'foo', - id: 1, - } - - ws.send(JSON.stringify(request)) - - ws.on('message', async (data: any) => { - const response = JSON.parse(data) - expect(response.error.code).toEqual(-32600) - - const count = await db.manager.count(JobRun) - expect(count).toEqual(0) - - ws.close() - done() - }) - }) - }) -}) diff --git a/explorer/src/support/client.ts b/explorer/src/support/client.ts new file mode 100644 index 00000000000..d3622a24331 --- /dev/null +++ b/explorer/src/support/client.ts @@ -0,0 +1,31 @@ +import WebSocket from 'ws' +import jayson from 'jayson' +import { DEFAULT_TEST_PORT } from './server' +import { ACCESS_KEY_HEADER, SECRET_HEADER } from '../utils/constants' + +export const ENDPOINT = `ws://localhost:${DEFAULT_TEST_PORT}` + +export const newChainlinkNode = ( + url: string, + accessKey: string, + secret: string, +): Promise => { + const ws = new WebSocket(ENDPOINT, { + headers: { + [ACCESS_KEY_HEADER]: accessKey, + [SECRET_HEADER]: secret, + }, + }) + + return new Promise((resolve: (arg0: WebSocket) => void, reject) => { + ws.on('error', (error: Error) => { + reject(error) + }) + + ws.on('open', () => resolve(ws)) + }) +} + +const jsonClient = new jayson.Client(null, null) +export const createRPCRequest = (method: string, params?: any) => + jsonClient.request(method, params) From 9d15d960b05269b7b68ad2d490defd1def67284d Mon Sep 17 00:00:00 2001 From: Ryan Hall Date: Fri, 1 Nov 2019 14:26:01 -0400 Subject: [PATCH 073/199] simplify ws tests with support/client.ts --- .../__tests__/rpcMethods/upsertJobRun.test.ts | 63 +++++---------- explorer/src/__tests__/server/legacy.test.ts | 61 +++++--------- .../src/__tests__/server/realtime.test.ts | 80 +++++++------------ explorer/src/support/client.ts | 16 ++++ 4 files changed, 83 insertions(+), 137 deletions(-) diff --git a/explorer/src/__tests__/rpcMethods/upsertJobRun.test.ts b/explorer/src/__tests__/rpcMethods/upsertJobRun.test.ts index bf7efc47b56..4d6b6de6928 100644 --- a/explorer/src/__tests__/rpcMethods/upsertJobRun.test.ts +++ b/explorer/src/__tests__/rpcMethods/upsertJobRun.test.ts @@ -1,6 +1,7 @@ import { Server } from 'http' import { Connection, getCustomRepository } from 'typeorm' import WebSocket from 'ws' +import jayson from 'jayson' import { getDb } from '../../database' import { ChainlinkNode, createChainlinkNode } from '../../entity/ChainlinkNode' import { JobRun } from '../../entity/JobRun' @@ -15,16 +16,17 @@ import { ENDPOINT, createRPCRequest, newChainlinkNode, + sendSingleMessage, } from '../../support/client' +const { INVALID_PARAMS } = jayson.Server.errors + describe('realtime', () => { let server: Server let db: Connection let chainlinkNode: ChainlinkNode let secret: string - - const authenticatedNode = async () => - newChainlinkNode(ENDPOINT, chainlinkNode.accessKey, secret) + let ws: WebSocket beforeAll(async () => { server = await start() @@ -37,6 +39,11 @@ describe('realtime', () => { db, 'explore realtime test chainlinkNode', ) + ws = await newChainlinkNode(ENDPOINT, chainlinkNode.accessKey, secret) + }) + + afterEach(async () => { + ws.close() }) afterAll(done => stop(server, done)) @@ -45,18 +52,9 @@ describe('realtime', () => { it('can create a job run with valid JSON', async () => { expect.assertions(3) - const ws = await authenticatedNode() const request = createRPCRequest('upsertJobRun', createFixture) - ws.send(JSON.stringify(request)) - - await new Promise(resolve => { - ws.on('message', (data: WebSocket.Data) => { - const response = JSON.parse(data as string) - expect(response.result).toEqual('success') - ws.close() - resolve() - }) - }) + const response = await sendSingleMessage(ws, request) + expect(response.result).toEqual('success') const jobRunCount = await db.manager.count(JobRun) expect(jobRunCount).toEqual(1) @@ -68,7 +66,6 @@ describe('realtime', () => { it('can create and update a job run and task runs', async () => { expect.assertions(6) - const ws = await authenticatedNode() const createRequest = createRPCRequest('upsertJobRun', createFixture) const updateRequest = createRPCRequest('upsertJobRun', updateFixture) ws.send(JSON.stringify(createRequest)) @@ -104,20 +101,9 @@ describe('realtime', () => { it('can create a task run with transactionHash and status', async () => { expect.assertions(10) - const ws = await authenticatedNode() - - const messageReceived = new Promise(resolve => { - ws.on('message', (data: any) => { - const response = JSON.parse(data) - expect(response.result).toEqual('success') - resolve() - }) - }) - const request = createRPCRequest('upsertJobRun', ethtxFixture) - ws.send(JSON.stringify(request)) - - await messageReceived + const response = await sendSingleMessage(ws, request) + expect(response.result).toEqual('success') const jobRunCount = await db.manager.count(JobRun) expect(jobRunCount).toEqual(1) @@ -139,26 +125,15 @@ describe('realtime', () => { expect(tr.blockHeight).toEqual('3735928559') expect(tr.blockHash).toEqual('0xbadc0de5') expect(tr.transactionStatus).toEqual('fulfilledRunLog') - ws.close() }) - it('rejects invalid params with code -32602', async (done: any) => { + it(`rejects invalid params with code ${INVALID_PARAMS}`, async () => { expect.assertions(2) - - const ws = await authenticatedNode() const request = createRPCRequest('upsertJobRun', { invalid: 'params' }) - ws.send(JSON.stringify(request)) - - ws.on('message', async (data: any) => { - const response = JSON.parse(data) - expect(response.error.code).toEqual(-32602) - - const count = await db.manager.count(JobRun) - expect(count).toEqual(0) - - ws.close() - done() - }) + const response = await sendSingleMessage(ws, request) + expect(response.error.code).toEqual(INVALID_PARAMS) + const count = await db.manager.count(JobRun) + expect(count).toEqual(0) }) }) }) diff --git a/explorer/src/__tests__/server/legacy.test.ts b/explorer/src/__tests__/server/legacy.test.ts index a1dc6e520ac..c1ab7a5f883 100644 --- a/explorer/src/__tests__/server/legacy.test.ts +++ b/explorer/src/__tests__/server/legacy.test.ts @@ -11,13 +11,18 @@ import createFixture from '../fixtures/JobRun.fixture.json' import updateFixture from '../fixtures/JobRunUpdate.fixture.json' import { clearDb } from '../testdatabase' import { JobRunRepository } from '../../repositories/JobRunRepository' -import { ENDPOINT, newChainlinkNode } from '../../support/client' +import { + ENDPOINT, + newChainlinkNode, + sendSingleMessage, +} from '../../support/client' describe('realtime', () => { let server: Server let db: Connection let chainlinkNode: ChainlinkNode let secret: string + let ws: WebSocket const authenticatedNode = async () => newChainlinkNode(ENDPOINT, chainlinkNode.accessKey, secret) @@ -33,6 +38,11 @@ describe('realtime', () => { db, 'explore realtime test chainlinkNode', ) + ws = await newChainlinkNode(ENDPOINT, chainlinkNode.accessKey, secret) + }) + + afterEach(async () => { + ws.close() }) afterAll(done => stop(server, done)) @@ -41,17 +51,8 @@ describe('realtime', () => { it('can create a job run with valid JSON', async () => { expect.assertions(3) - const ws = await authenticatedNode() - ws.send(JSON.stringify(createFixture)) - - await new Promise(resolve => { - ws.on('message', (data: WebSocket.Data) => { - const response = JSON.parse(data as string) - expect(response.status).toEqual(201) - ws.close() - resolve() - }) - }) + const response = await sendSingleMessage(ws, createFixture) + expect(response.status).toEqual(201) const jobRunCount = await db.manager.count(JobRun) expect(jobRunCount).toEqual(1) @@ -63,7 +64,6 @@ describe('realtime', () => { it('can create and update a job run and task runs', async () => { expect.assertions(6) - const ws = await authenticatedNode() ws.send(JSON.stringify(createFixture)) await new Promise(resolve => { @@ -101,18 +101,8 @@ describe('realtime', () => { it('can create a task run with transactionHash and status', async () => { expect.assertions(10) - const ws = await authenticatedNode() - - const messageReceived = new Promise(resolve => { - ws.on('message', (data: any) => { - const response = JSON.parse(data) - expect(response.status).toEqual(201) - resolve() - }) - }) - - ws.send(JSON.stringify(ethtxFixture)) - await messageReceived + const response = await sendSingleMessage(ws, ethtxFixture) + expect(response.status).toEqual(201) const jobRunCount = await db.manager.count(JobRun) expect(jobRunCount).toEqual(1) @@ -137,22 +127,13 @@ describe('realtime', () => { ws.close() }) - it('rejects malformed json events with code 422', async (done: any) => { + it('rejects malformed json events with code 422', async () => { expect.assertions(2) - - const ws = await authenticatedNode() - ws.send('{invalid json}') - - ws.on('message', async (data: any) => { - const response = JSON.parse(data) - expect(response.status).toEqual(422) - - const count = await db.manager.count(JobRun) - expect(count).toEqual(0) - - ws.close() - done() - }) + const request = '{invalid json}' + const response = await sendSingleMessage(ws, request) + expect(response.status).toEqual(422) + const count = await db.manager.count(JobRun) + expect(count).toEqual(0) }) }) }) diff --git a/explorer/src/__tests__/server/realtime.test.ts b/explorer/src/__tests__/server/realtime.test.ts index 552db1ad020..6837a39794a 100644 --- a/explorer/src/__tests__/server/realtime.test.ts +++ b/explorer/src/__tests__/server/realtime.test.ts @@ -1,9 +1,9 @@ import { Server } from 'http' import { Connection } from 'typeorm' import WebSocket from 'ws' +import jayson from 'jayson' import { getDb } from '../../database' import { ChainlinkNode, createChainlinkNode } from '../../entity/ChainlinkNode' -import { JobRun } from '../../entity/JobRun' import { start, stop } from '../../support/server' import { clearDb } from '../testdatabase' import { NORMAL_CLOSE } from '../../utils/constants' @@ -11,8 +11,11 @@ import { ENDPOINT, createRPCRequest, newChainlinkNode, + sendSingleMessage, } from '../../support/client' +const { PARSE_ERROR, INVALID_REQUEST, METHOD_NOT_FOUND } = jayson.Server.errors + describe('realtime', () => { let server: Server let db: Connection @@ -38,77 +41,48 @@ describe('realtime', () => { afterAll(done => stop(server, done)) describe('when sending messages in JSON-RPC format', () => { - it('rejects non-existing methods with code -32601', async (done: any) => { - expect.assertions(2) - - const ws = await newAuthenticatedNode() - const request = createRPCRequest('doesNotExist') - ws.send(JSON.stringify(request)) + let ws: WebSocket - ws.on('message', async (data: any) => { - const response = JSON.parse(data) - expect(response.error.code).toEqual(-32601) + beforeEach(async () => { + ws = await newAuthenticatedNode() + }) - const count = await db.manager.count(JobRun) - expect(count).toEqual(0) + afterEach(async () => { + ws.close() + }) - ws.close() - done() - }) + it(`rejects non-existing methods with code ${METHOD_NOT_FOUND}`, async () => { + expect.assertions(1) + const request = createRPCRequest('doesNotExist') + const response = await sendSingleMessage(ws, request) + expect(response.error.code).toEqual(METHOD_NOT_FOUND) }) // this test depends on the presence of "jsonrpc" in the message // otherwise, the server will attempt to process the message as a // legacy message and will respond with { status: 422 }. // This test will be more appropriate once the legacy format is removed. - it('rejects malformed json with code -32700', async (done: any) => { - expect.assertions(2) - - const ws = await newAuthenticatedNode() + it(`rejects malformed json with code ${PARSE_ERROR}`, async () => { + expect.assertions(1) const request = 'jsonrpc invalid' - ws.send(request) - - ws.on('message', async (data: any) => { - const response = JSON.parse(data) - expect(response.error.code).toEqual(-32700) - - const count = await db.manager.count(JobRun) - expect(count).toEqual(0) - - ws.close() - done() - }) + const response = await sendSingleMessage(ws, request) + expect(response.error.code).toEqual(PARSE_ERROR) }) - it('rejects invalid rpc requests with code -32600', async (done: any) => { - expect.assertions(2) - - const ws = await newAuthenticatedNode() - + it(`rejects invalid rpc requests with code ${INVALID_REQUEST}`, async () => { + expect.assertions(1) const request = { jsonrpc: '2.0', function: 'foo', id: 1, } - - ws.send(JSON.stringify(request)) - - ws.on('message', async (data: any) => { - const response = JSON.parse(data) - expect(response.error.code).toEqual(-32600) - - const count = await db.manager.count(JobRun) - expect(count).toEqual(0) - - ws.close() - done() - }) + const response = await sendSingleMessage(ws, request) + expect(response.error.code).toEqual(INVALID_REQUEST) }) }) it('rejects invalid authentication', async (done: any) => { expect.assertions(1) - newChainlinkNode(ENDPOINT, chainlinkNode.accessKey, 'lol-no').catch( error => { expect(error).toBeDefined() @@ -124,7 +98,7 @@ describe('realtime', () => { let ws1: WebSocket, ws2: WebSocket, ws3: WebSocket // eslint-disable-next-line prefer-const - ws1 = await newChainlinkNode(ENDPOINT, chainlinkNode.accessKey, secret) + ws1 = await newAuthenticatedNode() ws1.addEventListener('close', (event: WebSocket.CloseEvent) => { expect(ws1.readyState).toBe(WebSocket.CLOSED) @@ -133,7 +107,7 @@ describe('realtime', () => { expect(event.reason).toEqual('Duplicate connection opened') }) - ws2 = await newChainlinkNode(ENDPOINT, chainlinkNode.accessKey, secret) + ws2 = await newAuthenticatedNode() ws2.addEventListener('close', (event: WebSocket.CloseEvent) => { expect(ws2.readyState).toBe(WebSocket.CLOSED) @@ -144,6 +118,6 @@ describe('realtime', () => { done() }) - ws3 = await newChainlinkNode(ENDPOINT, chainlinkNode.accessKey, secret) + ws3 = await newAuthenticatedNode() }) }) diff --git a/explorer/src/support/client.ts b/explorer/src/support/client.ts index d3622a24331..7edb4104999 100644 --- a/explorer/src/support/client.ts +++ b/explorer/src/support/client.ts @@ -29,3 +29,19 @@ export const newChainlinkNode = ( const jsonClient = new jayson.Client(null, null) export const createRPCRequest = (method: string, params?: any) => jsonClient.request(method, params) + +// helper function that sends a message and only resolves once the +// rsponse is received +export const sendSingleMessage = ( + ws: WebSocket, + request: string | object, +): Promise => + new Promise(resolve => { + const requestData: string = + typeof request === 'object' ? JSON.stringify(request) : request + ws.send(requestData) + ws.on('message', async (data: string) => { + const response = JSON.parse(data) + resolve(response) + }) + }) From 282eca8f7ab7e0d30b3a773c08694593a102d840 Mon Sep 17 00:00:00 2001 From: Ryan Hall Date: Fri, 1 Nov 2019 14:29:29 -0400 Subject: [PATCH 074/199] rename nodes in test files --- explorer/src/__tests__/rpcMethods/upsertJobRun.test.ts | 2 +- explorer/src/__tests__/server/legacy.test.ts | 2 +- explorer/src/__tests__/server/realtime.test.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/explorer/src/__tests__/rpcMethods/upsertJobRun.test.ts b/explorer/src/__tests__/rpcMethods/upsertJobRun.test.ts index 4d6b6de6928..41342e6c72a 100644 --- a/explorer/src/__tests__/rpcMethods/upsertJobRun.test.ts +++ b/explorer/src/__tests__/rpcMethods/upsertJobRun.test.ts @@ -37,7 +37,7 @@ describe('realtime', () => { clearDb() ;[chainlinkNode, secret] = await createChainlinkNode( db, - 'explore realtime test chainlinkNode', + 'upsertJobRun test chainlinkNode', ) ws = await newChainlinkNode(ENDPOINT, chainlinkNode.accessKey, secret) }) diff --git a/explorer/src/__tests__/server/legacy.test.ts b/explorer/src/__tests__/server/legacy.test.ts index c1ab7a5f883..5d2017fa492 100644 --- a/explorer/src/__tests__/server/legacy.test.ts +++ b/explorer/src/__tests__/server/legacy.test.ts @@ -36,7 +36,7 @@ describe('realtime', () => { clearDb() ;[chainlinkNode, secret] = await createChainlinkNode( db, - 'explore realtime test chainlinkNode', + 'legacy test chainlinkNode', ) ws = await newChainlinkNode(ENDPOINT, chainlinkNode.accessKey, secret) }) diff --git a/explorer/src/__tests__/server/realtime.test.ts b/explorer/src/__tests__/server/realtime.test.ts index 6837a39794a..612100c6a83 100644 --- a/explorer/src/__tests__/server/realtime.test.ts +++ b/explorer/src/__tests__/server/realtime.test.ts @@ -34,7 +34,7 @@ describe('realtime', () => { clearDb() ;[chainlinkNode, secret] = await createChainlinkNode( db, - 'explore realtime test chainlinkNode', + 'realtime test chainlinkNode', ) }) From 77fe4b9bf90b819bc4873b07eb674e0643d9dafd Mon Sep 17 00:00:00 2001 From: Ryan Hall Date: Fri, 1 Nov 2019 14:35:18 -0400 Subject: [PATCH 075/199] remove unused function --- explorer/src/__tests__/server/legacy.test.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/explorer/src/__tests__/server/legacy.test.ts b/explorer/src/__tests__/server/legacy.test.ts index 5d2017fa492..d3888b0e466 100644 --- a/explorer/src/__tests__/server/legacy.test.ts +++ b/explorer/src/__tests__/server/legacy.test.ts @@ -24,9 +24,6 @@ describe('realtime', () => { let secret: string let ws: WebSocket - const authenticatedNode = async () => - newChainlinkNode(ENDPOINT, chainlinkNode.accessKey, secret) - beforeAll(async () => { server = await start() db = await getDb() From c511b4a2780212c579c6c7eec71100557c724a14 Mon Sep 17 00:00:00 2001 From: Ryan Hall Date: Mon, 4 Nov 2019 11:53:01 -0500 Subject: [PATCH 076/199] remove async from handleJSONRCP --- explorer/src/server/handleMessage.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/explorer/src/server/handleMessage.ts b/explorer/src/server/handleMessage.ts index 48c2278f983..d3629902f7b 100644 --- a/explorer/src/server/handleMessage.ts +++ b/explorer/src/server/handleMessage.ts @@ -22,8 +22,8 @@ const handleLegacy = async (json: string, context: ServerContext) => { } } -const handleJSONRCP = async (request: string, context: ServerContext) => { - return await new Promise(resolve => { +const handleJSONRCP = (request: string, context: ServerContext) => { + return new Promise(resolve => { callRPCServer( request, context, From 642c7638c1457d7afd81b386d412232d06a2c657 Mon Sep 17 00:00:00 2001 From: Alex Kwiatkowski Date: Mon, 4 Nov 2019 10:30:08 -0800 Subject: [PATCH 077/199] Add eslint to json-api-client package --- tools/json-api-client/package.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tools/json-api-client/package.json b/tools/json-api-client/package.json index 2ab4d4018b1..e79f15f00fc 100644 --- a/tools/json-api-client/package.json +++ b/tools/json-api-client/package.json @@ -21,9 +21,11 @@ "url": "^0.11.0" }, "devDependencies": { + "@chainlink/eslint-config": "0.0.1", "@types/fetch-mock": "^7.3.1", "@types/jest": "^24.0.18", "@types/path-to-regexp": "^1.7.0", + "eslint": "^6.3.0", "fetch-mock": "^7.3.1", "jest": "^24.9.0", "rimraf": "^3.0.0", From 4d52e897b302f371475fbddf063084c00f159926 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Mon, 4 Nov 2019 22:18:27 +0000 Subject: [PATCH 078/199] Bump @types/react-router from 5.1.1 to 5.1.2 Bumps [@types/react-router](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react-router) from 5.1.1 to 5.1.2. - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/react-router) Signed-off-by: dependabot-preview[bot] --- operator_ui/package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/operator_ui/package.json b/operator_ui/package.json index f447d5f7d98..d5237cb727a 100644 --- a/operator_ui/package.json +++ b/operator_ui/package.json @@ -79,7 +79,7 @@ "@types/react-dom": "^16.7.0", "@types/react-redux": "~6.0.0", "@types/react-resize-detector": "^4.0.1", - "@types/react-router": "^5.1.1", + "@types/react-router": "^5.1.2", "@types/react-router-dom": "^4.3.4", "@types/redux-mock-store": "^1.0.1", "depcheck": "^0.8.3", diff --git a/yarn.lock b/yarn.lock index 17086b839f4..89a08d53e01 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3071,10 +3071,10 @@ "@types/react" "*" "@types/react-router" "*" -"@types/react-router@*", "@types/react-router@^5.1.1": - version "5.1.1" - resolved "https://registry.yarnpkg.com/@types/react-router/-/react-router-5.1.1.tgz#e0b827556abc70da3473d05daf074c839d6852aa" - integrity sha512-S7SlFAPb7ZKr6HHMW0kLHGcz8pyJSL0UdM+JtlWthDqKUWwr7E6oPXuHgkofDI8dKCm16slg8K8VCf5pZJquaA== +"@types/react-router@*", "@types/react-router@^5.1.2": + version "5.1.2" + resolved "https://registry.yarnpkg.com/@types/react-router/-/react-router-5.1.2.tgz#41e5e6aa333a7b9a2bfdac753c04e1ca4b3e0d21" + integrity sha512-euC3SiwDg3NcjFdNmFL8uVuAFTpZJm0WMFUw+4eXMUnxa7M9RGFEG0szt0z+/Zgk4G2k9JBFhaEnY64RBiFmuw== dependencies: "@types/history" "*" "@types/react" "*" From 244c0ab7c36f14cc33b237d0e124ce613b993047 Mon Sep 17 00:00:00 2001 From: henrynguyen5 <6404866+HenryNguyen5@users.noreply.github.com> Date: Mon, 4 Nov 2019 17:22:33 -0500 Subject: [PATCH 079/199] Cleanup integration package --- integration/package.json | 14 +--- yarn.lock | 149 +++++++++------------------------------ 2 files changed, 36 insertions(+), 127 deletions(-) diff --git a/integration/package.json b/integration/package.json index e04fc273b87..0c8d776bf01 100644 --- a/integration/package.json +++ b/integration/package.json @@ -14,30 +14,20 @@ "test:cypress": "cross-env NODE_ENV=test cypress run", "test:forks": "./forks/test" }, - "dependencies": { - "chainlink": "0.6.1" - }, + "dependencies": {}, "devDependencies": { "@chainlink/eslint-config": "0.0.1", "@chainlink/prettier-config": "0.0.1", "@cypress/webpack-preprocessor": "^4.1.0", - "@types/node": "^12.7.5", - "babel-jest": "^24.1.0", - "command-line-args": "^5.1.1", "cross-env": "^6.0.0", "cypress": "^3.4.1", "depcheck": "^0.8.3", "eslint": "^6.3.0", - "ethers": "^4.0.36", "prettier": "^1.18.2", - "request-promise": "^4.2.4", - "solc": "0.4.24", - "truffle": "^5.0.25", "ts-loader": "^6.2.1", "ts-node": "^8.4.1", "typescript": "^3.6.3", - "webpack": "^4.41.1", - "webpack-cli": "^3.3.9" + "webpack": "^4.41.1" }, "prettier": "@chainlink/prettier-config" } diff --git a/yarn.lock b/yarn.lock index 17086b839f4..7aa36cda7ae 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4064,11 +4064,6 @@ array-back@^2.0.0: dependencies: typical "^2.6.1" -array-back@^3.0.1: - version "3.1.0" - resolved "https://registry.yarnpkg.com/array-back/-/array-back-3.1.0.tgz#b8859d7a508871c9a7b2cf42f99428f65e96bfb0" - integrity sha512-TkuxA4UCOvxuDK6NZYXCalszEzj+TLszyASooky+i742l9TqsOdYCMJJupxRic61hwquNtppB3hgcuq9SVSH1Q== - array-each@^1.0.0, array-each@^1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/array-each/-/array-each-1.0.1.tgz#a794af0c05ab1752846ee753a1f211a05ba0c44f" @@ -4613,7 +4608,7 @@ babel-helpers@^6.24.1: babel-runtime "^6.22.0" babel-template "^6.24.1" -babel-jest@^24.1.0, babel-jest@^24.8.0, babel-jest@^24.9.0: +babel-jest@^24.8.0, babel-jest@^24.9.0: version "24.9.0" resolved "https://registry.npmjs.org/babel-jest/-/babel-jest-24.9.0.tgz#3fc327cb8467b89d14d7bc70e315104a783ccd54" integrity sha512-ntuddfyiN+EhMw58PTNL1ph4C9rECiQXjI4nMMBKBaNjXvqLdkXpPRcMSr4iyBrJg/+wz9brFUD6RhOAT6r4Iw== @@ -6278,16 +6273,6 @@ chai@^4.0.1, chai@^4.2.0: pathval "^1.1.0" type-detect "^4.0.5" -chainlink@0.6.1: - version "0.6.1" - resolved "https://registry.npmjs.org/chainlink/-/chainlink-0.6.1.tgz#f658b44525d788dbbbdbe40b770eefdbac6b66a1" - integrity sha512-rTfd22GpYkTbNSuwoNi7T+aE9wsE3e0Qh5NG4zXit6OPi5FzYjL/ib15/HrNsK5STd0enfHzCK/FnxhGMtH8GA== - dependencies: - "@ensdomains/ens" "^0.1.1" - link_token "^1.0.3" - openzeppelin-solidity "^1.12.0" - solidity-cborutils "^1.0.2" - chainlink@0.7.5: version "0.7.5" resolved "https://registry.yarnpkg.com/chainlink/-/chainlink-0.7.5.tgz#82043e99b8477dac8478a1707d217d0ef716b28d" @@ -6853,16 +6838,6 @@ command-line-args@^4.0.7: find-replace "^1.0.3" typical "^2.6.1" -command-line-args@^5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/command-line-args/-/command-line-args-5.1.1.tgz#88e793e5bb3ceb30754a86863f0401ac92fd369a" - integrity sha512-hL/eG8lrll1Qy1ezvkant+trihbGnaKaeEjj6Scyr3DN+RC7iQ5Rz84IeLERfAWDGo0HBSNAakczwgCilDXnWg== - dependencies: - array-back "^3.0.1" - find-replace "^3.0.0" - lodash.camelcase "^4.3.0" - typical "^4.0.0" - commander@2.15.1: version "2.15.1" resolved "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz#df46e867d0fc2aec66a34662b406a9ccafff5b0f" @@ -8697,15 +8672,6 @@ engine.io@~3.3.1: engine.io-parser "~2.1.0" ws "~6.1.0" -enhanced-resolve@4.1.0, enhanced-resolve@^4.0.0, enhanced-resolve@^4.1.0: - version "4.1.0" - resolved "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-4.1.0.tgz#41c7e0bfdfe74ac1ffe1e57ad6a5c6c9f3742a7f" - integrity sha512-F/7vkyTtyc/llOIn8oWclcB25KdRaiPBpZYDgJHgh/UHtpgT2p2eldQgtQnLtUvfMKPKxbRaQM/hHkvLHt1Vng== - dependencies: - graceful-fs "^4.1.2" - memory-fs "^0.4.0" - tapable "^1.0.0" - enhanced-resolve@^3.4.0: version "3.4.1" resolved "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-3.4.1.tgz#0421e339fd71419b3da13d129b3979040230476e" @@ -8716,6 +8682,15 @@ enhanced-resolve@^3.4.0: object-assign "^4.0.1" tapable "^0.2.7" +enhanced-resolve@^4.0.0, enhanced-resolve@^4.1.0: + version "4.1.0" + resolved "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-4.1.0.tgz#41c7e0bfdfe74ac1ffe1e57ad6a5c6c9f3742a7f" + integrity sha512-F/7vkyTtyc/llOIn8oWclcB25KdRaiPBpZYDgJHgh/UHtpgT2p2eldQgtQnLtUvfMKPKxbRaQM/hHkvLHt1Vng== + dependencies: + graceful-fs "^4.1.2" + memory-fs "^0.4.0" + tapable "^1.0.0" + enquirer@^2.3.0: version "2.3.2" resolved "https://registry.npmjs.org/enquirer/-/enquirer-2.3.2.tgz#1c30284907cadff5ed2404bd8396036dd3da070e" @@ -9726,7 +9701,7 @@ ethers@4.0.0-beta.3: uuid "2.0.1" xmlhttprequest "1.8.0" -ethers@^4.0.0-beta.1, ethers@^4.0.32, ethers@^4.0.36, ethers@^4.0.37: +ethers@^4.0.0-beta.1, ethers@^4.0.32, ethers@^4.0.37: version "4.0.37" resolved "https://registry.npmjs.org/ethers/-/ethers-4.0.37.tgz#dfa70d59498663878c5e4a977d14356660ca5b90" integrity sha512-B7bDdyQ45A5lPr6k2HOkEKMtYOuqlfy+nNf8glnRvWidkDQnToKw1bv7UyrwlbsIgY2mE03UxTVtouXcT6Vvcw== @@ -10488,13 +10463,6 @@ find-replace@^1.0.3: array-back "^1.0.4" test-value "^2.1.0" -find-replace@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/find-replace/-/find-replace-3.0.0.tgz#3e7e23d3b05167a76f770c9fbd5258b0def68c38" - integrity sha512-6Tb2myMioCAgv5kfvP5/PkZZ/ntTpVK39fHY7WkWBgvbeE+VHd/tZuZ4mrC+bxh4cfOZeYKVPaJIZtZXV7GNCQ== - dependencies: - array-back "^3.0.1" - find-root@^1.1.0: version "1.1.0" resolved "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz#abcfc8ba76f708c42a97b3d685b7e9450bfb9ce4" @@ -10530,16 +10498,6 @@ find-up@^4.0.0: locate-path "^5.0.0" path-exists "^4.0.0" -findup-sync@3.0.0, findup-sync@^3.0.0: - version "3.0.0" - resolved "https://registry.npmjs.org/findup-sync/-/findup-sync-3.0.0.tgz#17b108f9ee512dfb7a5c7f3c8b27ea9e1a9c08d1" - integrity sha512-YbffarhcicEhOrm4CtrwdKBdCuz576RLdhJDsIfvNtxUuhdRet1qZcsMjqbePtAseKdAnDyM/IyXbu7PRPRLYg== - dependencies: - detect-file "^1.0.0" - is-glob "^4.0.0" - micromatch "^3.0.4" - resolve-dir "^1.0.1" - findup-sync@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/findup-sync/-/findup-sync-2.0.0.tgz#9326b1488c22d1a6088650a86901b2d9a90a2cbc" @@ -10550,6 +10508,16 @@ findup-sync@^2.0.0: micromatch "^3.0.4" resolve-dir "^1.0.1" +findup-sync@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/findup-sync/-/findup-sync-3.0.0.tgz#17b108f9ee512dfb7a5c7f3c8b27ea9e1a9c08d1" + integrity sha512-YbffarhcicEhOrm4CtrwdKBdCuz576RLdhJDsIfvNtxUuhdRet1qZcsMjqbePtAseKdAnDyM/IyXbu7PRPRLYg== + dependencies: + detect-file "^1.0.0" + is-glob "^4.0.0" + micromatch "^3.0.4" + resolve-dir "^1.0.1" + fined@^1.0.1: version "1.2.0" resolved "https://registry.npmjs.org/fined/-/fined-1.2.0.tgz#d00beccf1aa2b475d16d423b0238b713a2c4a37b" @@ -12240,7 +12208,7 @@ import-from@^2.1.0: dependencies: resolve-from "^3.0.0" -import-local@2.0.0, import-local@^2.0.0: +import-local@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/import-local/-/import-local-2.0.0.tgz#55070be38a5993cf18ef6db7e961f5bee5c5a09d" integrity sha512-b6s04m3O+s3CGSbqDIyP4R6aAwAeYlVq9+WUWep6iHa8ETRf9yei1U48C5MmfJmV9AiLYYBKPMq/W+/WRpQmCQ== @@ -12422,7 +12390,7 @@ internal-ip@^4.3.0: default-gateway "^4.2.0" ipaddr.js "^1.9.0" -interpret@1.2.0, interpret@^1.0.0, interpret@^1.1.0, interpret@^1.2.0: +interpret@^1.0.0, interpret@^1.1.0, interpret@^1.2.0: version "1.2.0" resolved "https://registry.npmjs.org/interpret/-/interpret-1.2.0.tgz#d5061a6224be58e8083985f5014d844359576296" integrity sha512-mT34yGKMNceBQUoVn7iCDKDntA7SC6gycMAWzGx1z/CMCTV7b2AAtXlo3nRyHZ1FelRkQbQjprHSYGwzLtkVbw== @@ -14619,11 +14587,6 @@ lodash.assign@^4.0.3, lodash.assign@^4.0.6: resolved "https://registry.yarnpkg.com/lodash.assign/-/lodash.assign-4.2.0.tgz#0d99f3ccd7a6d261d19bdaeb9245005d285808e7" integrity sha1-DZnzzNem0mHRm9rrkkUAXShYCOc= -lodash.camelcase@^4.3.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6" - integrity sha1-soqmKIorn8ZRA1x3EfZathkDMaY= - lodash.debounce@^4.0.8: version "4.0.8" resolved "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af" @@ -16313,7 +16276,7 @@ os-locale@^2.0.0: lcid "^1.0.0" mem "^1.1.0" -os-locale@^3.0.0, os-locale@^3.1.0: +os-locale@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/os-locale/-/os-locale-3.1.0.tgz#a802a6ee17f24c10483ab9935719cef4ed16bf1a" integrity sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q== @@ -19413,7 +19376,7 @@ request-promise-native@^1.0.5: stealthy-require "^1.1.1" tough-cookie "^2.3.3" -request-promise@4.2.4, request-promise@^4.2.2, request-promise@^4.2.4: +request-promise@4.2.4, request-promise@^4.2.2: version "4.2.4" resolved "https://registry.npmjs.org/request-promise/-/request-promise-4.2.4.tgz#1c5ed0d71441e38ad58c7ce4ea4ea5b06d54b310" integrity sha512-8wgMrvE546PzbR5WbYxUQogUnUDfM0S7QIFZMID+J73vdFARkFy+HElj4T+MWYhpXwlLp0EQ8Zoj8xUA0he4Vg== @@ -20567,7 +20530,7 @@ solhint@^2.1.0: optionalDependencies: prettier "^1.14.3" -solidity-cborutils@^1.0.2, solidity-cborutils@^1.0.4: +solidity-cborutils@^1.0.4: version "1.0.5" resolved "https://registry.npmjs.org/solidity-cborutils/-/solidity-cborutils-1.0.5.tgz#25a2875d942c18d4b61e889c0b56604de1f41063" integrity sha512-SHwp1Un9s6Ki8rXMod18w/6VAqzuBor61SqPS6uy7s1fPDx1rUwnhcx3zWK8CfA0Pm7UY31Apy9NOBu/Vo4ZMg== @@ -21180,13 +21143,6 @@ supports-color@5.5.0, supports-color@^5.3.0: dependencies: has-flag "^3.0.0" -supports-color@6.1.0, supports-color@^6.0.0, supports-color@^6.1.0: - version "6.1.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-6.1.0.tgz#0764abc69c63d5ac842dd4867e8d025e880df8f3" - integrity sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ== - dependencies: - has-flag "^3.0.0" - supports-color@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" @@ -21206,6 +21162,13 @@ supports-color@^4.2.1, supports-color@^4.5.0: dependencies: has-flag "^2.0.0" +supports-color@^6.0.0, supports-color@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-6.1.0.tgz#0764abc69c63d5ac842dd4867e8d025e880df8f3" + integrity sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ== + dependencies: + has-flag "^3.0.0" + sver-compat@^1.5.0: version "1.5.0" resolved "https://registry.npmjs.org/sver-compat/-/sver-compat-1.5.0.tgz#3cf87dfeb4d07b4a3f14827bc186b3fd0c645cd8" @@ -22226,11 +22189,6 @@ typical@^2.6.0, typical@^2.6.1: resolved "https://registry.npmjs.org/typical/-/typical-2.6.1.tgz#5c080e5d661cbbe38259d2e70a3c7253e873881d" integrity sha1-XAgOXWYcu+OCWdLnCjxyU+hziB0= -typical@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/typical/-/typical-4.0.0.tgz#cbeaff3b9d7ae1e2bbfaf5a4e6f11eccfde94fc4" - integrity sha512-VAH4IvQ7BDFYglMd7BPRDfLgxZZX4O4TFcRDA6EN5X7erNJJq+McIEp8np9aVtxrCJ6qx4GTYVfOWNjcqwZgRw== - u2f-api@0.2.7: version "0.2.7" resolved "https://registry.yarnpkg.com/u2f-api/-/u2f-api-0.2.7.tgz#17bf196b242f6bf72353d9858e6a7566cc192720" @@ -22616,11 +22574,6 @@ uuid@^3.1.0, uuid@^3.3.2: resolved "https://registry.npmjs.org/uuid/-/uuid-3.3.3.tgz#4568f0216e78760ee1dbf3a4d2cf53e224112866" integrity sha512-pW0No1RGHgzlpHJO1nsVrHKpOEIxkGg1xB+v0ZmdNH5OAeAwzAVrCnI2/6Mtx+Uys6iaylxa+D3g4j63IKKjSQ== -v8-compile-cache@2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.0.3.tgz#00f7494d2ae2b688cfe2899df6ed2c54bef91dbe" - integrity sha512-CNmdbwQMBjwr9Gsmohvm0pbL954tJrNzf6gWL3K+QMQf00PF7ERGrEiLgjuU3mKreLC2MeGhUsNV9ybTbLgd3w== - v8-compile-cache@^2.0.3: version "2.1.0" resolved "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.1.0.tgz#e14de37b31a6d194f5690d67efc4e7f6fc6ab30e" @@ -23345,23 +23298,6 @@ webpack-bundle-analyzer@^3.0.3: opener "^1.5.1" ws "^6.0.0" -webpack-cli@^3.3.9: - version "3.3.9" - resolved "https://registry.yarnpkg.com/webpack-cli/-/webpack-cli-3.3.9.tgz#79c27e71f94b7fe324d594ab64a8e396b9daa91a" - integrity sha512-xwnSxWl8nZtBl/AFJCOn9pG7s5CYUYdZxmmukv+fAHLcBIHM36dImfpQg3WfShZXeArkWlf6QRw24Klcsv8a5A== - dependencies: - chalk "2.4.2" - cross-spawn "6.0.5" - enhanced-resolve "4.1.0" - findup-sync "3.0.0" - global-modules "2.0.0" - import-local "2.0.0" - interpret "1.2.0" - loader-utils "1.2.3" - supports-color "6.1.0" - v8-compile-cache "2.0.3" - yargs "13.2.4" - webpack-dev-middleware@^3.5.1: version "3.6.1" resolved "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-3.6.1.tgz#91f2531218a633a99189f7de36045a331a4b9cd4" @@ -24174,7 +24110,7 @@ yargs-parser@^11.1.1: camelcase "^5.0.0" decamelize "^1.2.0" -yargs-parser@^13.1.0, yargs-parser@^13.1.1: +yargs-parser@^13.1.1: version "13.1.1" resolved "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.1.tgz#d26058532aa06d365fe091f6a1fc06b2f7e5eca0" integrity sha512-oVAVsHz6uFrg3XQheFII8ESO2ssAf9luWuAd6Wexsu4F3OtIW0o8IribPXYrD4WC24LWtPrJlGy87y5udK+dxQ== @@ -24272,23 +24208,6 @@ yargs@12.0.5, yargs@^12.0.5: y18n "^3.2.1 || ^4.0.0" yargs-parser "^11.1.1" -yargs@13.2.4: - version "13.2.4" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-13.2.4.tgz#0b562b794016eb9651b98bd37acf364aa5d6dc83" - integrity sha512-HG/DWAJa1PAnHT9JAhNa8AbAv3FPaiLzioSjCcmuXXhP8MlpHO5vwls4g4j6n30Z74GVQj8Xa62dWVx1QCGklg== - dependencies: - cliui "^5.0.0" - find-up "^3.0.0" - get-caller-file "^2.0.1" - os-locale "^3.1.0" - require-directory "^2.1.1" - require-main-filename "^2.0.0" - set-blocking "^2.0.0" - string-width "^3.0.0" - which-module "^2.0.0" - y18n "^4.0.0" - yargs-parser "^13.1.0" - yargs@^10.0.3: version "10.1.2" resolved "https://registry.yarnpkg.com/yargs/-/yargs-10.1.2.tgz#454d074c2b16a51a43e2fb7807e4f9de69ccb5c5" From ac126df33486b99cb3919d4c80319753227e0ddc Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Mon, 4 Nov 2019 22:22:55 +0000 Subject: [PATCH 080/199] Bump @babel/register from 7.6.0 to 7.6.2 Bumps [@babel/register](https://github.com/babel/babel) from 7.6.0 to 7.6.2. - [Release notes](https://github.com/babel/babel/releases) - [Changelog](https://github.com/babel/babel/blob/master/CHANGELOG.md) - [Commits](https://github.com/babel/babel/compare/v7.6.0...v7.6.2) Signed-off-by: dependabot-preview[bot] --- evm/v0.5/package.json | 2 +- examples/echo_server/package.json | 2 +- yarn.lock | 8 ++++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/evm/v0.5/package.json b/evm/v0.5/package.json index 23651bc18d5..82e8f199b56 100644 --- a/evm/v0.5/package.json +++ b/evm/v0.5/package.json @@ -27,7 +27,7 @@ "@babel/polyfill": "^7.2.5", "@babel/preset-env": "^7.6.0", "@babel/preset-typescript": "^7.1.0", - "@babel/register": "^7.0.0", + "@babel/register": "^7.6.2", "@chainlink/eslint-config": "0.0.1", "@chainlink/prettier-config": "0.0.1", "@machinomy/types-truffle-contract": "^0.2.0", diff --git a/examples/echo_server/package.json b/examples/echo_server/package.json index 64e55debc8d..df4f8b4ab6b 100644 --- a/examples/echo_server/package.json +++ b/examples/echo_server/package.json @@ -15,7 +15,7 @@ }, "dependencies": { "@babel/polyfill": "^7.2.5", - "@babel/register": "^7.0.0", + "@babel/register": "^7.6.2", "body-parser": "^1.18.3", "cbor": "^4.0.0", "chainlink": "^0.6.5", diff --git a/yarn.lock b/yarn.lock index 17086b839f4..92a18e6e2ed 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1239,10 +1239,10 @@ "@babel/helper-plugin-utils" "^7.0.0" "@babel/plugin-transform-typescript" "^7.6.0" -"@babel/register@^7.0.0": - version "7.6.0" - resolved "https://registry.npmjs.org/@babel/register/-/register-7.6.0.tgz#76b6f466714680f4becafd45beeb2a7b87431abf" - integrity sha512-78BomdN8el+x/nkup9KwtjJXuptW5oXMFmP11WoM2VJBjxrKv4grC3qjpLL8RGGUYUGsm57xnjYFM2uom+jWUQ== +"@babel/register@^7.0.0", "@babel/register@^7.6.2": + version "7.6.2" + resolved "https://registry.yarnpkg.com/@babel/register/-/register-7.6.2.tgz#25765a922202cb06f8bdac5a3b1e70cd6bf3dd45" + integrity sha512-xgZk2LRZvt6i2SAUWxc7ellk4+OYRgS3Zpsnr13nMS1Qo25w21Uu8o6vTOAqNaxiqrnv30KTYzh9YWY2k21CeQ== dependencies: find-cache-dir "^2.0.0" lodash "^4.17.13" From b1c66950e140a3b2f60d12af7fc974cca4dd0e6d Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Mon, 4 Nov 2019 22:47:29 +0000 Subject: [PATCH 081/199] Bump cross-env from 6.0.0 to 6.0.3 Bumps [cross-env](https://github.com/kentcdodds/cross-env) from 6.0.0 to 6.0.3. - [Release notes](https://github.com/kentcdodds/cross-env/releases) - [Changelog](https://github.com/kentcdodds/cross-env/blob/master/CHANGELOG.md) - [Commits](https://github.com/kentcdodds/cross-env/compare/v6.0.0...v6.0.3) Signed-off-by: dependabot-preview[bot] --- evm/package.json | 2 +- evm/v0.5/package.json | 2 +- explorer/client/package.json | 2 +- explorer/package.json | 2 +- integration/package.json | 2 +- yarn.lock | 8 ++++---- 6 files changed, 9 insertions(+), 9 deletions(-) diff --git a/evm/package.json b/evm/package.json index cea33272bba..dfed6973313 100644 --- a/evm/package.json +++ b/evm/package.json @@ -46,7 +46,7 @@ "@types/web3": "^1.0.0", "bn.js": "^4.11.0", "chai": "^4.2.0", - "cross-env": "^6.0.0", + "cross-env": "^6.0.3", "debug": "^4.1.1", "depcheck": "^0.8.3", "eslint": "^6.3.0", diff --git a/evm/v0.5/package.json b/evm/v0.5/package.json index 23651bc18d5..102e9dd00d5 100644 --- a/evm/v0.5/package.json +++ b/evm/v0.5/package.json @@ -34,7 +34,7 @@ "@types/cbor": "^2.0.0", "@types/ethereumjs-abi": "^0.6.3", "chai": "^4.2.0", - "cross-env": "^6.0.0", + "cross-env": "^6.0.3", "depcheck": "^0.8.3", "eslint": "^6.3.0", "execa": "^3.2.0", diff --git a/explorer/client/package.json b/explorer/client/package.json index 04153af7cda..ef2b67ea1b2 100644 --- a/explorer/client/package.json +++ b/explorer/client/package.json @@ -72,7 +72,7 @@ "@types/redux-thunk": "^2.1.0", "brotli-webpack-plugin": "^1.1.0", "compression-webpack-plugin": "^3.0.0", - "cross-env": "^6.0.0", + "cross-env": "^6.0.3", "depcheck": "^0.8.3", "dynamic-cdn-webpack-plugin": "^4.0.0", "enzyme": "^3.10.0", diff --git a/explorer/package.json b/explorer/package.json index cb432a7bf75..e9a8774fc2d 100644 --- a/explorer/package.json +++ b/explorer/package.json @@ -75,7 +75,7 @@ "@types/ws": "^6.0.1", "@types/yargs": "^12.0.10", "concurrently": "^4.1.0", - "cross-env": "^6.0.0", + "cross-env": "^6.0.3", "depcheck": "^0.8.3", "eslint": "^6.3.0", "expect-puppeteer": "^4.1.0", diff --git a/integration/package.json b/integration/package.json index 0c8d776bf01..881ca491ffb 100644 --- a/integration/package.json +++ b/integration/package.json @@ -19,7 +19,7 @@ "@chainlink/eslint-config": "0.0.1", "@chainlink/prettier-config": "0.0.1", "@cypress/webpack-preprocessor": "^4.1.0", - "cross-env": "^6.0.0", + "cross-env": "^6.0.3", "cypress": "^3.4.1", "depcheck": "^0.8.3", "eslint": "^6.3.0", diff --git a/yarn.lock b/yarn.lock index 7aa36cda7ae..4ef6e0cf0d9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7320,10 +7320,10 @@ create-react-context@^0.3.0: gud "^1.0.0" warning "^4.0.3" -cross-env@^6.0.0: - version "6.0.0" - resolved "https://registry.npmjs.org/cross-env/-/cross-env-6.0.0.tgz#3c8e71440ea20aa6faaf5aec541235efc565dac6" - integrity sha512-G/B6gtkjgthT8AP/xN1wdj5Xe18fVyk58JepK8GxpUbqcz3hyWxegocMbvnZK+KoTslwd0ACZ3woi/DVUdVjyQ== +cross-env@^6.0.3: + version "6.0.3" + resolved "https://registry.yarnpkg.com/cross-env/-/cross-env-6.0.3.tgz#4256b71e49b3a40637a0ce70768a6ef5c72ae941" + integrity sha512-+KqxF6LCvfhWvADcDPqo64yVIB31gv/jQulX2NGzKS/g3GEVz6/pt4wjHFtFWsHMddebWD/sDthJemzM4MaAag== dependencies: cross-spawn "^7.0.0" From 061ea07af3073c09727da45fddff1e05c618ccc5 Mon Sep 17 00:00:00 2001 From: henrynguyen5 <6404866+HenryNguyen5@users.noreply.github.com> Date: Mon, 4 Nov 2019 17:36:53 -0500 Subject: [PATCH 082/199] Add prepublish ci test for chainlink(v0.5) --- .circleci/config.yml | 20 ++++++++++++++++++++ evm/package.json | 2 +- evm/v0.5/package.json | 1 + tools/ci/prepublish_npm_test | 6 ++++++ 4 files changed, 28 insertions(+), 1 deletion(-) create mode 100755 tools/ci/prepublish_npm_test diff --git a/.circleci/config.yml b/.circleci/config.yml index f57c728d0f5..62b7ddd27f9 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -332,6 +332,22 @@ jobs: - checkout - run: ./tools/ci/init_gcloud - run: ./tools/ci/report_coverage + prepublish_npm: + docker: + - image: smartcontract/builder:1.0.25 + steps: + - checkout + - run: echo $CACHE_VERSION > cache.version + - restore_cache: + name: Restore Yarn Package Cache + key: v{{ checksum "cache.version" }}-yarn-vendor-{{ checksum "yarn.lock" }} + - run: yarn install + - save_cache: + name: Save Yarn Package Cache + key: v{{ checksum "cache.version" }}-yarn-vendor-{{ checksum "yarn.lock" }} + paths: + - /usr/local/share/.cache/yarn + - run: ./tools/ci/prepublish_npm_test workflows: version: 2 @@ -381,6 +397,10 @@ workflows: filters: tags: only: /^v.*/ + - prepublish_npm: + filters: + tags: + only: /^v.*/ - build-publish-explorer: requires: - explorer diff --git a/evm/package.json b/evm/package.json index cea33272bba..9d28510f3b4 100644 --- a/evm/package.json +++ b/evm/package.json @@ -17,7 +17,7 @@ "pretest": "yarn build", "test": "jest --testTimeout 20000", "format": "prettier --write \"{src,testv2}/*/**\"", - "prepublishOnly": "yarn build && yarn lint && yarn test", + "prepublishOnly": "yarn workspace chainlinkv0.5 setup && yarn setup && yarn lint && yarn test", "setup": "ts-node ./scripts/build", "truffle:migrate:cldev": "truffle migrate --network cldev" }, diff --git a/evm/v0.5/package.json b/evm/v0.5/package.json index 23651bc18d5..42d8d069488 100644 --- a/evm/v0.5/package.json +++ b/evm/v0.5/package.json @@ -12,6 +12,7 @@ "format": "prettier --write \"{src,test}/**/*\"", "slither": "truffle compile --quiet && slither .", "setup": "ts-node ./scripts/build", + "prepublishOnly": "yarn setup && yarn lint && yarn test", "test": "truffle test" }, "dependencies": { diff --git a/tools/ci/prepublish_npm_test b/tools/ci/prepublish_npm_test new file mode 100755 index 00000000000..f1decf8cdd5 --- /dev/null +++ b/tools/ci/prepublish_npm_test @@ -0,0 +1,6 @@ +#!/bin/bash + +set -e + +yarn workspace chainlink prepublishOnly +yarn workspace chainlinkv0.5 prepublishOnly From f0dc00273c80ff453de9a81cbe054cdb0b3ebe1d Mon Sep 17 00:00:00 2001 From: henrynguyen5 <6404866+HenryNguyen5@users.noreply.github.com> Date: Mon, 4 Nov 2019 18:18:51 -0500 Subject: [PATCH 083/199] Speedup evm tests This makes use of evm_snapshot RPC command available within ganache to speed up our tests by around 40% --- evm/buidler.config.js | 3 - evm/package.json | 2 +- evm/src/helpersV2.ts | 39 ++ evm/src/index.ts | 3 +- evm/src/provider.ts | 27 -- evm/testv2/Aggregator.test.ts | 12 +- evm/testv2/AggregatorProxy.test.ts | 15 +- evm/testv2/BasicConsumer.test.ts | 13 +- evm/testv2/ConcreteChainlink.test.ts | 57 ++- evm/testv2/ConcreteChainlinked.test.ts | 30 +- evm/testv2/GetterSetter.test.ts | 10 +- evm/testv2/Oracle.test.ts | 12 +- evm/testv2/Pointer.test.ts | 13 +- evm/testv2/SignedSafeMath.test.ts | 13 +- evm/testv2/UpdatableConsumer.test.ts | 12 +- yarn.lock | 495 ++----------------------- 16 files changed, 182 insertions(+), 574 deletions(-) delete mode 100644 evm/buidler.config.js delete mode 100644 evm/src/provider.ts diff --git a/evm/buidler.config.js b/evm/buidler.config.js deleted file mode 100644 index 89b6f7b5442..00000000000 --- a/evm/buidler.config.js +++ /dev/null @@ -1,3 +0,0 @@ -module.exports = { - defaultNetwork: 'buidlerevm', -} diff --git a/evm/package.json b/evm/package.json index 2134e367aa5..d48359cb3b2 100644 --- a/evm/package.json +++ b/evm/package.json @@ -37,10 +37,10 @@ "devDependencies": { "@chainlink/eslint-config": "0.0.1", "@chainlink/prettier-config": "0.0.1", - "@nomiclabs/buidler": "^1.0.1", "@types/cbor": "^2.0.0", "@types/chai": "^4.2.4", "@types/debug": "^4.1.5", + "@types/ganache-core": "^2.7.0", "@types/jest": "^24.0.18", "@types/node": "^12.7.5", "@types/web3": "^1.0.0", diff --git a/evm/src/helpersV2.ts b/evm/src/helpersV2.ts index a179a611c16..95c5f734677 100644 --- a/evm/src/helpersV2.ts +++ b/evm/src/helpersV2.ts @@ -35,6 +35,45 @@ interface RolesAndPersonas { personas: Personas } +/** + * This helper function allows us to make use of ganache snapshots, + * which allows us to snapshot one state instance and revert back to it. + * + * This is used to memoize expensive setup calls typically found in beforeEach hooks when we + * need to setup our state with contract deployments before running assertions. + * + * @param provider The provider that's used within the tests + * @param cb The callback to execute that generates the state we want to snapshot + */ +export function useSnapshot( + provider: ethers.providers.JsonRpcProvider, + cb: () => Promise, +) { + const d = debug.extend('memoizeDeploy') + let hasDeployed = false + let snapshotId = '' + + return async () => { + if (!hasDeployed) { + d('executing deployment..') + await cb() + + d('snapshotting...') + snapshotId = await provider.send('evm_snapshot', undefined) + d('snapshot id:%s', snapshotId) + + hasDeployed = true + } else { + d('reverting to snapshot: %s', snapshotId) + await provider.send('evm_revert', snapshotId) + + d('re-creating snapshot..') + snapshotId = await provider.send('evm_snapshot', undefined) + d('recreated snapshot id:%s', snapshotId) + } + } +} + /** * Generate roles and personas for tests along with their corrolated account addresses */ diff --git a/evm/src/index.ts b/evm/src/index.ts index 7c568a46432..386492fdc5a 100644 --- a/evm/src/index.ts +++ b/evm/src/index.ts @@ -4,6 +4,5 @@ import * as debug from './debug' import LinkToken from './LinkToken.json' import * as wallet from './wallet' import * as matchers from './matchersV2' -import * as provider from './provider' -export { contract, helpers, debug, LinkToken, wallet, matchers, provider } +export { contract, helpers, debug, LinkToken, wallet, matchers } diff --git a/evm/src/provider.ts b/evm/src/provider.ts deleted file mode 100644 index ef1a02fc512..00000000000 --- a/evm/src/provider.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { IEthereumProvider } from '@nomiclabs/buidler/types' -import { JsonRpcProvider } from 'ethers/providers' - -// https://github.com/nomiclabs/buidler/blob/master/packages/buidler-ethers/src/ethers-provider-wrapper.ts -export class EthersProviderWrapper extends JsonRpcProvider { - private readonly _buidlerProvider: IEthereumProvider - constructor(buidlerProvider: IEthereumProvider) { - super() - this._buidlerProvider = buidlerProvider - } - public async send(method: string, params: any): Promise { - const result = await this._buidlerProvider.send(method, params) - // We replicate ethers' behavior. - this.emit('debug', { - action: 'send', - request: { - id: 42, - jsonrpc: '2.0', - method, - params, - }, - response: result, - provider: this, - }) - return result - } -} diff --git a/evm/testv2/Aggregator.test.ts b/evm/testv2/Aggregator.test.ts index bd0deb8f4fa..e1a0e20e860 100644 --- a/evm/testv2/Aggregator.test.ts +++ b/evm/testv2/Aggregator.test.ts @@ -1,13 +1,12 @@ import * as h from '../src/helpersV2' import { assertBigNum } from '../src/matchersV2' import { ethers } from 'ethers' -import { EthersProviderWrapper } from '../src/provider' -import env from '@nomiclabs/buidler' import { Instance } from '../src/contract' import { OracleFactory } from '../src/generated/OracleFactory' import { LinkTokenFactory } from '../src/generated/LinkTokenFactory' import { AggregatorFactory } from '../src/generated/AggregatorFactory' import { assert } from 'chai' +import ganache from 'ganache-core' const aggregatorFactory = new AggregatorFactory() const oracleFactory = new OracleFactory() @@ -15,7 +14,7 @@ const linkTokenFactory = new LinkTokenFactory() let personas: h.Personas let defaultAccount: ethers.Wallet -const provider = new EthersProviderWrapper(env.ethereum) +const provider = new ethers.providers.Web3Provider(ganache.provider() as any) beforeAll(async () => { const rolesAndPersonas = await h.initializeRolesAndPersonas(provider) @@ -43,8 +42,7 @@ describe('Aggregator', () => { let oc4: Instance let oracles: Instance[] let jobIds: string[] = [] - - beforeEach(async () => { + const deployment = h.useSnapshot(provider, async () => { link = await linkTokenFactory.connect(defaultAccount).deploy() oc1 = await oracleFactory.connect(defaultAccount).deploy(link.address) oc2 = await oracleFactory.connect(defaultAccount).deploy(link.address) @@ -53,6 +51,10 @@ describe('Aggregator', () => { oracles = [oc1, oc2, oc3] }) + beforeEach(async () => { + await deployment() + }) + it('has a limited public interface', () => { h.checkPublicABI(aggregatorFactory, [ 'authorizedRequesters', diff --git a/evm/testv2/AggregatorProxy.test.ts b/evm/testv2/AggregatorProxy.test.ts index ea39f95051f..ba0447c6a77 100644 --- a/evm/testv2/AggregatorProxy.test.ts +++ b/evm/testv2/AggregatorProxy.test.ts @@ -1,7 +1,5 @@ import * as h from '../src/helpersV2' import { assertBigNum } from '../src/matchersV2' -import env from '@nomiclabs/buidler' -import { EthersProviderWrapper } from '../src/provider' import { ethers } from 'ethers' import { Instance } from '../src/contract' import { LinkTokenFactory } from '../src/generated/LinkTokenFactory' @@ -9,11 +7,12 @@ import { AggregatorFactory } from '../src/generated/AggregatorFactory' import { OracleFactory } from '../src/generated/OracleFactory' import { AggregatorProxyFactory } from '../src/generated/AggregatorProxyFactory' import { assert } from 'chai' +import ganache from 'ganache-core' let personas: h.Personas let defaultAccount: ethers.Wallet -const provider = new EthersProviderWrapper(env.ethereum) +const provider = new ethers.providers.Web3Provider(ganache.provider() as any) const linkTokenFactory = new LinkTokenFactory() const aggregatorFactory = new AggregatorFactory() const oracleFactory = new OracleFactory() @@ -39,22 +38,22 @@ describe('AggregatorProxy', () => { let aggregator2: Instance let oc1: Instance let proxy: Instance - - beforeEach(async () => { + const deployment = h.useSnapshot(provider, async () => { link = await linkTokenFactory.connect(defaultAccount).deploy() - oc1 = await oracleFactory.connect(defaultAccount).deploy(link.address) - aggregator = await aggregatorFactory .connect(defaultAccount) .deploy(link.address, basePayment, 1, [oc1.address], [jobId1]) await link.transfer(aggregator.address, deposit) - proxy = await aggregatorProxyFactory .connect(defaultAccount) .deploy(aggregator.address) }) + beforeEach(async () => { + await deployment() + }) + it('has a limited public interface', () => { h.checkPublicABI(aggregatorProxyFactory, [ 'aggregator', diff --git a/evm/testv2/BasicConsumer.test.ts b/evm/testv2/BasicConsumer.test.ts index 085dca8286a..b1ee78ae533 100644 --- a/evm/testv2/BasicConsumer.test.ts +++ b/evm/testv2/BasicConsumer.test.ts @@ -7,15 +7,14 @@ import { LinkTokenFactory } from '../src/generated/LinkTokenFactory' import { OracleFactory } from '../src/generated/OracleFactory' import { BasicConsumerFactory } from '../src/generated/BasicConsumerFactory' import { Instance } from '../src/contract' -import env from '@nomiclabs/buidler' -import { EthersProviderWrapper } from '../src/provider' +import ganache from 'ganache-core' const basicConsumerFactory = new BasicConsumerFactory() const oracleFactory = new OracleFactory() const linkTokenFactory = new LinkTokenFactory() // create ethers provider from that web3js instance -const provider = new EthersProviderWrapper(env.ethereum) +const provider = new ethers.providers.Web3Provider(ganache.provider() as any) let roles: h.Roles @@ -27,13 +26,11 @@ beforeAll(async () => { describe('BasicConsumer', () => { const specId = '0x4c7b7ffb66b344fbaa64995af81e355a'.padEnd(66, '0') - const currency = 'USD' let link: Instance let oc: Instance let cc: Instance - - beforeEach(async () => { + const deployment = h.useSnapshot(provider, async () => { link = await linkTokenFactory.connect(roles.defaultAccount).deploy() oc = await oracleFactory.connect(roles.oracleNode).deploy(link.address) cc = await basicConsumerFactory @@ -41,6 +38,10 @@ describe('BasicConsumer', () => { .deploy(link.address, oc.address, specId) }) + beforeEach(async () => { + await deployment() + }) + it('has a predictable gas price', async () => { const rec = await provider.getTransactionReceipt(cc.deployTransaction.hash!) assert.isBelow(rec.gasUsed!.toNumber(), 1700000) diff --git a/evm/testv2/ConcreteChainlink.test.ts b/evm/testv2/ConcreteChainlink.test.ts index f4e55086515..5ed149f05b1 100644 --- a/evm/testv2/ConcreteChainlink.test.ts +++ b/evm/testv2/ConcreteChainlink.test.ts @@ -1,32 +1,31 @@ -import { - checkPublicABI, - decodeDietCBOR, - initializeRolesAndPersonas, - hexToBuf, -} from '../src/helpersV2' +import * as h from '../src/helpersV2' import { ConcreteChainlinkFactory } from '../src/generated/ConcreteChainlinkFactory' import { Instance } from '../src/contract' import { ethers } from 'ethers' -import env from '@nomiclabs/buidler' -import { EthersProviderWrapper } from '../src/provider' import { assert } from 'chai' import { makeDebug } from '../src/debug' -const provider = new EthersProviderWrapper(env.ethereum) +import ganache from 'ganache-core' + +const provider = new ethers.providers.Web3Provider(ganache.provider() as any) const concreteChainlinkFactory = new ConcreteChainlinkFactory() const debug = makeDebug('ConcreteChainlink') describe('ConcreteChainlink', () => { let ccl: Instance let defaultAccount: ethers.Wallet - beforeEach(async () => { - defaultAccount = await initializeRolesAndPersonas(provider).then( - r => r.roles.defaultAccount, - ) + const deployment = h.useSnapshot(provider, async () => { + defaultAccount = await h + .initializeRolesAndPersonas(provider) + .then(r => r.roles.defaultAccount) ccl = await concreteChainlinkFactory.connect(defaultAccount).deploy() }) + beforeEach(async () => { + await deployment() + }) + it('has a limited public interface', () => { - checkPublicABI(concreteChainlinkFactory, [ + h.checkPublicABI(concreteChainlinkFactory, [ 'add', 'addBytes', 'addInt', @@ -49,7 +48,7 @@ describe('ConcreteChainlink', () => { it('handles empty payloads', async () => { const tx = await ccl.closeEvent() const [payload] = await parseCCLEvent(tx) - const decoded = await decodeDietCBOR(payload) + const decoded = await h.decodeDietCBOR(payload) assert.deepEqual(decoded, {}) }) }) @@ -59,7 +58,7 @@ describe('ConcreteChainlink', () => { await ccl.setBuffer('0xA161616162') const tx = await ccl.closeEvent() const [payload] = await parseCCLEvent(tx) - const decoded = await decodeDietCBOR(payload) + const decoded = await h.decodeDietCBOR(payload) assert.deepEqual(decoded, { a: 'b' }) }) }) @@ -69,7 +68,7 @@ describe('ConcreteChainlink', () => { await ccl.add('first', 'word!!') const tx = await ccl.closeEvent() const [payload] = await parseCCLEvent(tx) - const decoded = await decodeDietCBOR(payload) + const decoded = await h.decodeDietCBOR(payload) assert.deepEqual(decoded, { first: 'word!!' }) }) @@ -78,7 +77,7 @@ describe('ConcreteChainlink', () => { await ccl.add('second', 'dos') const tx = await ccl.closeEvent() const [payload] = await parseCCLEvent(tx) - const decoded = await decodeDietCBOR(payload) + const decoded = await h.decodeDietCBOR(payload) assert.deepEqual(decoded, { first: 'uno', @@ -92,8 +91,8 @@ describe('ConcreteChainlink', () => { await ccl.addBytes('first', '0xaabbccddeeff') const tx = await ccl.closeEvent() const [payload] = await parseCCLEvent(tx) - const decoded = await decodeDietCBOR(payload) - const expected = hexToBuf('0xaabbccddeeff') + const decoded = await h.decodeDietCBOR(payload) + const expected = h.hexToBuf('0xaabbccddeeff') assert.deepEqual(decoded, { first: expected }) }) @@ -102,10 +101,10 @@ describe('ConcreteChainlink', () => { await ccl.addBytes('second', '0x646F73') const tx = await ccl.closeEvent() const [payload] = await parseCCLEvent(tx) - const decoded = await decodeDietCBOR(payload) + const decoded = await h.decodeDietCBOR(payload) - const expectedFirst = hexToBuf('0x756E6F') - const expectedSecond = hexToBuf('0x646F73') + const expectedFirst = h.hexToBuf('0x756E6F') + const expectedSecond = h.hexToBuf('0x646F73') assert.deepEqual(decoded, { first: expectedFirst, second: expectedSecond, @@ -116,7 +115,7 @@ describe('ConcreteChainlink', () => { await ccl.addBytes('first', ethers.utils.toUtf8Bytes('apple')) const tx = await ccl.closeEvent() const [payload] = await parseCCLEvent(tx) - const decoded = await decodeDietCBOR(payload) + const decoded = await h.decodeDietCBOR(payload) const expected = ethers.utils.toUtf8Bytes('apple') assert.deepEqual(decoded, { first: expected }) }) @@ -127,7 +126,7 @@ describe('ConcreteChainlink', () => { await ccl.addInt('first', 1) const tx = await ccl.closeEvent() const [payload] = await parseCCLEvent(tx) - const decoded = await decodeDietCBOR(payload) + const decoded = await h.decodeDietCBOR(payload) assert.deepEqual(decoded, { first: 1 }) }) @@ -136,7 +135,7 @@ describe('ConcreteChainlink', () => { await ccl.addInt('second', 2) const tx = await ccl.closeEvent() const [payload] = await parseCCLEvent(tx) - const decoded = await decodeDietCBOR(payload) + const decoded = await h.decodeDietCBOR(payload) assert.deepEqual(decoded, { first: 1, @@ -150,7 +149,7 @@ describe('ConcreteChainlink', () => { await ccl.addUint('first', 1) const tx = await ccl.closeEvent() const [payload] = await parseCCLEvent(tx) - const decoded = await decodeDietCBOR(payload) + const decoded = await h.decodeDietCBOR(payload) assert.deepEqual(decoded, { first: 1 }) }) @@ -159,7 +158,7 @@ describe('ConcreteChainlink', () => { await ccl.addUint('second', 2) const tx = await ccl.closeEvent() const [payload] = await parseCCLEvent(tx) - const decoded = await decodeDietCBOR(payload) + const decoded = await h.decodeDietCBOR(payload) assert.deepEqual(decoded, { first: 1, @@ -177,7 +176,7 @@ describe('ConcreteChainlink', () => { ]) const tx = await ccl.closeEvent() const [payload] = await parseCCLEvent(tx) - const decoded = await decodeDietCBOR(payload) + const decoded = await h.decodeDietCBOR(payload) assert.deepEqual(decoded, { word: ['seinfeld', '"4"', 'LIFE'] }) }) }) diff --git a/evm/testv2/ConcreteChainlinked.test.ts b/evm/testv2/ConcreteChainlinked.test.ts index d8ca12a37a6..0a29dc50ee1 100644 --- a/evm/testv2/ConcreteChainlinked.test.ts +++ b/evm/testv2/ConcreteChainlinked.test.ts @@ -7,8 +7,7 @@ import { assert } from 'chai' import { ethers } from 'ethers' import { LinkTokenFactory } from '../src/generated/LinkTokenFactory' import { Instance } from '../src/contract' -import env from '@nomiclabs/buidler' -import { EthersProviderWrapper } from '../src/provider' +import ganache from 'ganache-core' const concreteChainlinkedFactory = new ConcreteChainlinkedFactory() const emptyOracleFactory = new EmptyOracleFactory() @@ -16,7 +15,7 @@ const getterSetterFactory = new GetterSetterFactory() const oracleFactory = new OracleFactory() const linkTokenFactory = new LinkTokenFactory() -const provider = new EthersProviderWrapper(env.ethereum) +const provider = new ethers.providers.Web3Provider(ganache.provider() as any) let roles: h.Roles @@ -34,8 +33,7 @@ describe('ConcreteChainlinked', () => { let oc: Instance let newoc: Instance let link: Instance - - beforeEach(async () => { + const deployment = h.useSnapshot(provider, async () => { link = await linkTokenFactory.connect(roles.defaultAccount).deploy() oc = await oracleFactory.connect(roles.defaultAccount).deploy(link.address) newoc = await oracleFactory @@ -47,6 +45,10 @@ describe('ConcreteChainlinked', () => { .deploy(link.address, oc.address) }) + beforeEach(async () => { + await deployment() + }) + describe('#newRequest', () => { it('forwards the information to the oracle contract through the link token', async () => { const tx = await cc.publicNewRequest( @@ -131,16 +133,20 @@ describe('ConcreteChainlinked', () => { describe('#cancelChainlinkRequest', () => { let requestId: string + // a concrete chainlink attached to an empty oracle + let ecc: Instance beforeEach(async () => { - oc = await emptyOracleFactory.connect(roles.defaultAccount).deploy() - cc = await concreteChainlinkedFactory + const emptyOracle = await emptyOracleFactory .connect(roles.defaultAccount) - .deploy(link.address, oc.address) + .deploy() + ecc = await concreteChainlinkedFactory + .connect(roles.defaultAccount) + .deploy(link.address, emptyOracle.address) - const tx = await cc.publicRequest( + const tx = await ecc.publicRequest( specId, - cc.address, + ecc.address, ethers.utils.toUtf8Bytes('fulfillRequest(bytes32,bytes32)'), 0, ) @@ -149,7 +155,7 @@ describe('ConcreteChainlinked', () => { }) it('emits an event from the contract showing the run was cancelled', async () => { - const tx = await cc.publicCancelRequest( + const tx = await ecc.publicCancelRequest( requestId, 0, ethers.utils.hexZeroPad('0x', 4), @@ -164,7 +170,7 @@ describe('ConcreteChainlinked', () => { it('throws if given a bogus event ID', async () => { await h.assertActionThrows(async () => { - await cc.publicCancelRequest( + await ecc.publicCancelRequest( ethers.utils.formatBytes32String('bogusId'), 0, ethers.utils.hexZeroPad('0x', 4), diff --git a/evm/testv2/GetterSetter.test.ts b/evm/testv2/GetterSetter.test.ts index 156cbbc9dc5..82d114a65ab 100644 --- a/evm/testv2/GetterSetter.test.ts +++ b/evm/testv2/GetterSetter.test.ts @@ -3,11 +3,10 @@ import { ethers } from 'ethers' import { assert } from 'chai' import { GetterSetterFactory } from '../src/generated/GetterSetterFactory' import { Instance } from '../src/contract' -import env from '@nomiclabs/buidler' -import { EthersProviderWrapper } from '../src/provider' +import ganache from 'ganache-core' const GetterSetterContract = new GetterSetterFactory() -const provider = new EthersProviderWrapper(env.ethereum) +const provider = new ethers.providers.Web3Provider(ganache.provider() as any) let roles: h.Roles @@ -23,9 +22,12 @@ describe('GetterSetter', () => { const bytes32 = ethers.utils.formatBytes32String('Hi Mom!') const uint256 = ethers.utils.bigNumberify(645746535432) let gs: Instance + const deployment = h.useSnapshot(provider, async () => { + gs = await GetterSetterContract.connect(roles.defaultAccount).deploy() + }) beforeEach(async () => { - gs = await GetterSetterContract.connect(roles.defaultAccount).deploy() + await deployment() }) describe('#setBytes32Val', () => { diff --git a/evm/testv2/Oracle.test.ts b/evm/testv2/Oracle.test.ts index 40aee50dc96..c856d109c09 100644 --- a/evm/testv2/Oracle.test.ts +++ b/evm/testv2/Oracle.test.ts @@ -6,11 +6,10 @@ import { MaliciousRequesterFactory } from '../src/generated/MaliciousRequesterFa import { MaliciousConsumerFactory } from '../src/generated/MaliciousConsumerFactory' import { OracleFactory } from '../src/generated/OracleFactory' import { LinkTokenFactory } from '../src/generated/LinkTokenFactory' -import { EthersProviderWrapper } from '../src/provider' -import env from '@nomiclabs/buidler' import { Instance } from '../src/contract' import { ethers } from 'ethers' import { assert } from 'chai' +import ganache from 'ganache-core' const basicConsumerFactory = new BasicConsumerFactory() const getterSetterFactory = new GetterSetterFactory() @@ -20,7 +19,7 @@ const oracleFactory = new OracleFactory() const linkTokenFactory = new LinkTokenFactory() let roles: h.Roles -const provider = new EthersProviderWrapper(env.ethereum) +const provider = new ethers.providers.Web3Provider(ganache.provider() as any) beforeAll(async () => { const rolesAndPersonas = await h.initializeRolesAndPersonas(provider) @@ -35,13 +34,16 @@ describe('Oracle', () => { const to = '0x80e29acb842498fe6591f020bd82766dce619d43' let link: Instance let oc: Instance - - beforeEach(async () => { + const deployment = h.useSnapshot(provider, async () => { link = await linkTokenFactory.connect(roles.defaultAccount).deploy() oc = await oracleFactory.connect(roles.defaultAccount).deploy(link.address) await oc.setFulfillmentPermission(roles.oracleNode.address, true) }) + beforeEach(async () => { + await deployment() + }) + it('has a limited public interface', () => { h.checkPublicABI(oracleFactory, [ 'EXPIRY_TIME', diff --git a/evm/testv2/Pointer.test.ts b/evm/testv2/Pointer.test.ts index a6e7759f4d2..223633a54f7 100644 --- a/evm/testv2/Pointer.test.ts +++ b/evm/testv2/Pointer.test.ts @@ -3,12 +3,12 @@ import { assert } from 'chai' import { PointerFactory } from '../src/generated/PointerFactory' import { LinkTokenFactory } from '../src/generated/LinkTokenFactory' import { Instance } from '../src/contract' -import env from '@nomiclabs/buidler' -import { EthersProviderWrapper } from '../src/provider' +import ganache from 'ganache-core' +import { ethers } from 'ethers' const pointerFactory = new PointerFactory() const linkTokenFactory = new LinkTokenFactory() -const provider = new EthersProviderWrapper(env.ethereum) +const provider = new ethers.providers.Web3Provider(ganache.provider() as any) let roles: h.Roles @@ -21,14 +21,17 @@ beforeAll(async () => { describe('Pointer', () => { let contract: Instance let link: Instance - - beforeEach(async () => { + const deployment = h.useSnapshot(provider, async () => { link = await linkTokenFactory.connect(roles.defaultAccount).deploy() contract = await pointerFactory .connect(roles.defaultAccount) .deploy(link.address) }) + beforeEach(async () => { + await deployment() + }) + it('has a limited public interface', () => { h.checkPublicABI(contract, ['getAddress']) }) diff --git a/evm/testv2/SignedSafeMath.test.ts b/evm/testv2/SignedSafeMath.test.ts index 0a78bc7d652..92b40898b45 100644 --- a/evm/testv2/SignedSafeMath.test.ts +++ b/evm/testv2/SignedSafeMath.test.ts @@ -2,13 +2,12 @@ import * as h from '../src/helpersV2' import { assertBigNum } from '../src/matchersV2' import { ethers } from 'ethers' import { createFundedWallet } from '../src/wallet' -import { EthersProviderWrapper } from '../src/provider' import { ConcreteSignedSafeMathFactory } from '../src/generated/ConcreteSignedSafeMathFactory' import { Instance } from '../src/contract' -import env from '@nomiclabs/buidler' +import ganache from 'ganache-core' const concreteSignedSafeMathFactory = new ConcreteSignedSafeMathFactory() -const provider = new EthersProviderWrapper(env.ethereum) +const provider = new ethers.providers.Web3Provider(ganache.provider() as any) let defaultAccount: ethers.Wallet @@ -21,17 +20,19 @@ describe('SignedSafeMath', () => { // a version of the adder contract where we make all ABI exposed functions constant // TODO: submit upstream PR to support constant contract type generation let adder: Instance - - let response + let response: ethers.utils.BigNumber const INT256_MAX = ethers.utils.bigNumberify( '57896044618658097711785492504343953926634992332820282019728792003956564819967', ) const INT256_MIN = ethers.utils.bigNumberify( '-57896044618658097711785492504343953926634992332820282019728792003956564819968', ) + const deployment = h.useSnapshot(provider, async () => { + adder = await concreteSignedSafeMathFactory.connect(defaultAccount).deploy() + }) beforeEach(async () => { - adder = await concreteSignedSafeMathFactory.connect(defaultAccount).deploy() + await deployment() }) describe('#add', () => { diff --git a/evm/testv2/UpdatableConsumer.test.ts b/evm/testv2/UpdatableConsumer.test.ts index 8c9ab691cc4..d736d253f2b 100644 --- a/evm/testv2/UpdatableConsumer.test.ts +++ b/evm/testv2/UpdatableConsumer.test.ts @@ -8,8 +8,7 @@ import { assert } from 'chai' import { LinkTokenFactory } from '../src/generated/LinkTokenFactory' import { OracleFactory } from '../src/generated/OracleFactory' import { Instance } from '../src/contract' -import env from '@nomiclabs/buidler' -import { EthersProviderWrapper } from '../src/provider' +import ganache from 'ganache-core' const linkTokenFactory = new LinkTokenFactory() const ensRegistryFactory = new ENSRegistryFactory() @@ -17,7 +16,7 @@ const oracleFactory = new OracleFactory() const publicResolverFacotory = new PublicResolverFactory() const updatableConsumerFactory = new UpdatableConsumerFactory() -const provider = new EthersProviderWrapper(env.ethereum) +const provider = new ethers.providers.Web3Provider(ganache.provider() as any) let roles: h.Roles @@ -50,8 +49,7 @@ describe('UpdatableConsumer', () => { let link: Instance let oc: Instance let uc: Instance - - beforeEach(async () => { + const deployment = h.useSnapshot(provider, async () => { link = await linkTokenFactory.connect(roles.defaultAccount).deploy() oc = await oracleFactory.connect(roles.oracleNode).deploy(link.address) ens = await ensRegistryFactory.connect(roles.defaultAccount).deploy() @@ -102,6 +100,10 @@ describe('UpdatableConsumer', () => { .deploy(specId, ens.address, domainNode) }) + beforeEach(async () => { + await deployment() + }) + describe('constructor', () => { it('pulls the token contract address from the resolver', async () => { assert.equal(link.address, await uc.getChainlinkToken()) diff --git a/yarn.lock b/yarn.lock index d77f6255ba9..0f66c44a054 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1831,82 +1831,6 @@ resolved "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-1.1.3.tgz#2b5a3ab3f918cca48a8c754c08168e3f03eba61b" integrity sha512-shAmDyaQC4H92APFoIaVDHCx5bStIocgvbwQyxPRrbUY20V1EYTbSDchWbuwlMG3V17cprZhA6+78JfB+3DTPw== -"@nomiclabs/buidler@^1.0.1": - version "1.0.1" - resolved "https://registry.npmjs.org/@nomiclabs/buidler/-/buidler-1.0.1.tgz#4ac100fa96cf4326a551bf7099922b4c61956a50" - integrity sha512-MpjKvp7iYbTCT8gf4/RTYl5LK98owGYkibjUsWZ+B8COtlNQgpt7Eoxrh77lls06oGP8GmseWtVFF+qt9oZczw== - dependencies: - "@nomiclabs/eth-sig-util" "^2.4.4" - "@nomiclabs/ethereumjs-vm" "^4.1.0" - "@types/bn.js" "^4.11.5" - "@types/lru-cache" "^5.1.0" - abort-controller "^3.0.0" - bip32 "^2.0.3" - bip39 "^3.0.2" - chalk "^2.4.2" - ci-info "^2.0.0" - debug "^4.1.1" - deepmerge "^2.1.0" - download "^7.1.0" - enquirer "^2.3.0" - ethereumjs-abi "^0.6.8" - ethereumjs-account "^3.0.0" - ethereumjs-block "^2.2.0" - ethereumjs-common "^1.3.2" - ethereumjs-tx "^2.1.1" - ethereumjs-util "^6.1.0" - find-up "^2.1.0" - fp-ts "1.19.3" - fs-extra "^7.0.1" - glob "^7.1.3" - io-ts "1.10.4" - is-installed-globally "^0.2.0" - lodash "^4.17.11" - merkle-patricia-tree "^3.0.0" - mocha "^5.2.0" - node-fetch "^2.6.0" - qs "^6.7.0" - semver "^6.3.0" - solc "0.5.11" - solidity-parser-antlr "^0.4.2" - source-map-support "^0.5.13" - ts-essentials "^2.0.7" - tsort "0.0.1" - uuid "^3.3.2" - -"@nomiclabs/eth-sig-util@^2.4.4": - version "2.4.4" - resolved "https://registry.npmjs.org/@nomiclabs/eth-sig-util/-/eth-sig-util-2.4.4.tgz#e3518a90027a7952af24204a81713c21b500d546" - integrity sha512-t6vqztYHLq9t0CTiULheO+zrNqp1c10EjFgfKNxQGSV+A8hoztJDgU8FRKpczWN6MgQv2qFFgGMRfymadjY4pA== - dependencies: - buffer "^5.2.1" - elliptic "^6.4.0" - ethereumjs-abi "0.6.5" - ethereumjs-util "^5.1.1" - tweetnacl "^1.0.0" - tweetnacl-util "^0.15.0" - -"@nomiclabs/ethereumjs-vm@^4.1.0": - version "4.1.0" - resolved "https://registry.npmjs.org/@nomiclabs/ethereumjs-vm/-/ethereumjs-vm-4.1.0.tgz#2767e5e779c9c1db4ce50adb789d87a683622710" - integrity sha512-2eordoieZBmNEMkspyOpn3X4jqUtIzKarahGlPUd3WRVPvOSfbf68wNgKN+IbcQ9xND6nvb64fpW3kgckXdclQ== - dependencies: - async "^2.1.2" - async-eventemitter "^0.2.2" - core-js-pure "^3.0.1" - ethereumjs-account "^3.0.0" - ethereumjs-block "~2.2.0" - ethereumjs-blockchain "^4.0.1" - ethereumjs-common "^1.3.2" - ethereumjs-tx "^2.1.1" - ethereumjs-util "^6.1.0" - fake-merkle-patricia-tree "^1.0.1" - functional-red-black-tree "^1.0.1" - merkle-patricia-tree "^2.3.2" - rustbn.js "~0.2.0" - safe-buffer "^5.1.1" - util.promisify "^1.0.0" - "@phc/format@^0.5.0": version "0.5.0" resolved "https://registry.npmjs.org/@phc/format/-/format-0.5.0.tgz#a99d27a83d78b3100a191412adda04315e2e3aba" @@ -1930,11 +1854,6 @@ resolved "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz#9fb3a3cf3132328151f353de4632e01e52102bea" integrity sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ== -"@sindresorhus/is@^0.7.0": - version "0.7.0" - resolved "https://registry.npmjs.org/@sindresorhus/is/-/is-0.7.0.tgz#9a06f4f137ee84d7df0460c1fdb1135ffa6c50fd" - integrity sha512-ONhaKPIufzzrlNbqtWFFd+jlnemX6lJAgq9ZeiZtS7I1PIf/la7CW4m83rTXRnVnsMbW2k56pGYu7AUFJD9Pow== - "@storybook/addon-actions@^5.1.10": version "5.1.11" resolved "https://registry.npmjs.org/@storybook/addon-actions/-/addon-actions-5.1.11.tgz#ebc299b9dfe476b5c65eb5d148c4b064f682ca08" @@ -2646,7 +2565,7 @@ resolved "https://registry.npmjs.org/@types/bcrypt/-/bcrypt-3.0.0.tgz#851489a9065a067cb7f3c9cbe4ce9bed8bba0876" integrity sha512-nohgNyv+1ViVcubKBh0+XiNJ3dO8nYu///9aJ4cgSqv70gBL+94SNy/iC2NLzKPT2Zt/QavrOkBVbZRLZmw6NQ== -"@types/bn.js@*", "@types/bn.js@^4.11.0", "@types/bn.js@^4.11.5": +"@types/bn.js@*", "@types/bn.js@^4.11.0": version "4.11.5" resolved "https://registry.npmjs.org/@types/bn.js/-/bn.js-4.11.5.tgz#40e36197433f78f807524ec623afcf0169ac81dc" integrity sha512-AEAZcIZga0JgVMHNtl1CprA/hXX7/wPt79AgR4XqaDt7jyj3QWYw6LPoOiznPtugDmlubUnAahMs2PFxGcQrng== @@ -2788,6 +2707,13 @@ resolved "https://registry.yarnpkg.com/@types/fetch-mock/-/fetch-mock-7.3.1.tgz#df7421e8bcb351b430bfbfa5c52bb353826ac94f" integrity sha512-2U4vZWHNbsbK7TRmizgr/pbKe0FKopcxu+hNDtIBDiM1wvrKRItybaYj7VQ6w/hZJStU/JxRiNi5ww4YDEvKbA== +"@types/ganache-core@^2.7.0": + version "2.7.0" + resolved "https://registry.npmjs.org/@types/ganache-core/-/ganache-core-2.7.0.tgz#867ecc7f3f34a9b7bfac45567db8fab1d051c1ab" + integrity sha512-kNnP+XBHmyEMYGLFzA+5PDt6P1TazTjALtZoJXExVisVT9buU8Al1xY24f3V+ROWshghQCL1cqofi8YsmacIew== + dependencies: + ganache-core "*" + "@types/glob@*", "@types/glob@^7.1.1": version "7.1.1" resolved "https://registry.npmjs.org/@types/glob/-/glob-7.1.1.tgz#aa59a1c6e3fbc421e07ccd31a944c30eba521575" @@ -2894,11 +2820,6 @@ resolved "https://registry.npmjs.org/@types/logform/-/logform-1.2.0.tgz#4ead916c7eb1ee99d726bfa849b6a2ee5ea50e3e" integrity sha512-E8cLzW+PmqHI//4HLR+ZvE3cyzqVjsncYBx1O14P07oVJj3ezhmiL/azlgkXKLFyCWAeKsPQdjHNg/NEhBF5Ow== -"@types/lru-cache@^5.1.0": - version "5.1.0" - resolved "https://registry.npmjs.org/@types/lru-cache/-/lru-cache-5.1.0.tgz#57f228f2b80c046b4a1bd5cac031f81f207f4f03" - integrity sha512-RaE0B+14ToE4l6UqdarKPnXwVDuigfFv+5j9Dze/Nqr23yyuqdNvzcZi3xB+3Agvi5R4EOgAksfv3lXX4vBt9w== - "@types/material-ui@^0.21.6": version "0.21.7" resolved "https://registry.npmjs.org/@types/material-ui/-/material-ui-0.21.7.tgz#2a4ab77a56a16adef044ba607edde5214151a5d8" @@ -2941,16 +2862,6 @@ resolved "https://registry.npmjs.org/@types/node/-/node-12.7.5.tgz#e19436e7f8e9b4601005d73673b6dc4784ffcc2f" integrity sha512-9fq4jZVhPNW8r+UYKnxF1e2HkDWOWKM5bC2/7c9wPV835I0aOrVbS/Hw/pWPk2uKrNXQqg9Z959Kz+IYDd5p3w== -"@types/node@10.12.18": - version "10.12.18" - resolved "https://registry.npmjs.org/@types/node/-/node-10.12.18.tgz#1d3ca764718915584fcd9f6344621b7672665c67" - integrity sha512-fh+pAqt4xRzPfqA6eh3Z2y6fyZavRIumvjhaCL753+TVkGKGhpPeyrJG2JftD0T9q4GF00KjefsQ+PQNDdWQaQ== - -"@types/node@11.11.6": - version "11.11.6" - resolved "https://registry.npmjs.org/@types/node/-/node-11.11.6.tgz#df929d1bb2eee5afdda598a41930fe50b43eaa6a" - integrity sha512-Exw4yUWMBXM3X+8oqzJNRqZSwUAaS4+7NdvHqQuFi/d+synz++xmX3QIf+BFqneW8N31R8Ky+sikfZUXq07ggQ== - "@types/node@^10.3.2": version "10.14.4" resolved "https://registry.npmjs.org/@types/node/-/node-10.14.4.tgz#1c586b991457cbb58fef51bc4e0cfcfa347714b5" @@ -3560,13 +3471,6 @@ abbrev@1.0.x: resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.0.9.tgz#91b4792588a7738c25f35dd6f63752a2f8776135" integrity sha1-kbR5JYinc4wl813W9jdSovh3YTU= -abort-controller@^3.0.0: - version "3.0.0" - resolved "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz#eaf54d53b62bae4138e809ca225c8439a6efb392" - integrity sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg== - dependencies: - event-target-shim "^5.0.0" - abortcontroller-polyfill@^1.1.9: version "1.3.0" resolved "https://registry.yarnpkg.com/abortcontroller-polyfill/-/abortcontroller-polyfill-1.3.0.tgz#de69af32ae926c210b7efbcc29bf644ee4838b00" @@ -3838,7 +3742,7 @@ ansi-colors@^1.0.1: dependencies: ansi-wrap "^0.1.0" -ansi-colors@^3.0.0, ansi-colors@^3.2.1, ansi-colors@^3.2.3: +ansi-colors@^3.0.0, ansi-colors@^3.2.3: version "3.2.4" resolved "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.4.tgz#e3a3da4bfbae6c86a9c285625de124a234026fbf" integrity sha512-hHUXGagefjN2iRrID63xckIvotOXOojhQKWIPUZ4mNUZ9nLZW+7FMNoE1lOkEhNWYsx/7ysGIuJYCiMAA9FnrA== @@ -3955,13 +3859,6 @@ arch@2.1.1, arch@^2.1.0: resolved "https://registry.npmjs.org/arch/-/arch-2.1.1.tgz#8f5c2731aa35a30929221bb0640eed65175ec84e" integrity sha512-BLM56aPo9vLLFVa8+/+pJLnrZ7QGGTVHWsCwieAWT9o9K8UeGaQbzZbGoabWLOo2ksBCztoXdqBZBplqLDDCSg== -archive-type@^4.0.0: - version "4.0.0" - resolved "https://registry.npmjs.org/archive-type/-/archive-type-4.0.0.tgz#f92e72233056dfc6969472749c267bdb046b1d70" - integrity sha1-+S5yIzBW38aWlHJ0nCZ72wRrHXA= - dependencies: - file-type "^4.2.0" - archy@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/archy/-/archy-1.0.0.tgz#f9c8c13757cc1dd7bc379ac77b2c62a5c2868c40" @@ -5436,26 +5333,13 @@ binary-extensions@^2.0.0: resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.0.0.tgz#23c0df14f6a88077f5f986c0d167ec03c3d5537c" integrity sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow== -bindings@^1.2.1, bindings@^1.3.0, bindings@^1.3.1, bindings@^1.4.0, bindings@^1.5.0: +bindings@^1.2.1, bindings@^1.3.1, bindings@^1.4.0, bindings@^1.5.0: version "1.5.0" resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.5.0.tgz#10353c9e945334bc0511a6d90b38fbc7c9c504df" integrity sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ== dependencies: file-uri-to-path "1.0.0" -bip32@^2.0.3: - version "2.0.4" - resolved "https://registry.npmjs.org/bip32/-/bip32-2.0.4.tgz#b662bd28710d4676fb351ba8a13be45cb4d85d01" - integrity sha512-ioPytarPDIrWckWMuK4RNUtvwhvWEc2fvuhnO0WEwu732k5OLjUXv4rXi2c/KJHw9ZMNQMkYRJrBw81RujShGQ== - dependencies: - "@types/node" "10.12.18" - bs58check "^2.1.1" - create-hash "^1.2.0" - create-hmac "^1.1.7" - tiny-secp256k1 "^1.1.0" - typeforce "^1.11.5" - wif "^2.0.6" - bip39@2.5.0: version "2.5.0" resolved "https://registry.npmjs.org/bip39/-/bip39-2.5.0.tgz#51cbd5179460504a63ea3c000db3f787ca051235" @@ -5478,16 +5362,6 @@ bip39@^2.5.0: safe-buffer "^5.0.1" unorm "^1.3.3" -bip39@^3.0.2: - version "3.0.2" - resolved "https://registry.npmjs.org/bip39/-/bip39-3.0.2.tgz#2baf42ff3071fc9ddd5103de92e8f80d9257ee32" - integrity sha512-J4E1r2N0tUylTKt07ibXvhpT2c5pyAFgvuA5q1H9uDy6dEGpjV8jmymh3MTYJDLCNbIVClSB9FbND49I6N24MQ== - dependencies: - "@types/node" "11.11.6" - create-hash "^1.1.0" - pbkdf2 "^3.0.9" - randombytes "^2.0.1" - bip66@^1.1.3: version "1.1.5" resolved "https://registry.yarnpkg.com/bip66/-/bip66-1.1.5.tgz#01fa8748785ca70955d5011217d1b3139969ca22" @@ -5860,7 +5734,7 @@ bs58@^4.0.0: dependencies: base-x "^3.0.2" -bs58check@<3.0.0, bs58check@^2.1.1, bs58check@^2.1.2: +bs58check@^2.1.2: version "2.1.2" resolved "https://registry.npmjs.org/bs58check/-/bs58check-2.1.2.tgz#53b018291228d82a5aa08e7d796fdafda54aebfc" integrity sha512-0TS1jicxdU09dwJMNZtVAfzPi6Q6QeN0pM1Fkzrjn+XYHvzMKPU3pHVpva+769iNVSfIYWf7LJ6WR+BuuMf8cA== @@ -5936,7 +5810,7 @@ buffer-xor@^1.0.3: buffer@5.4.0, buffer@^5.1.0: version "5.4.0" - resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.4.0.tgz#33294f5c1f26e08461e528b69fa06de3c45cbd8c" + resolved "https://registry.npmjs.org/buffer/-/buffer-5.4.0.tgz#33294f5c1f26e08461e528b69fa06de3c45cbd8c" integrity sha512-Xpgy0IwHK2N01ncykXTy6FpCWuM+CJSHoPVBLyNqyrWxsedpLvwsYUhf0ME3WRFNUhos0dMamz9cOS/xRDtU5g== dependencies: base64-js "^1.0.2" @@ -6050,19 +5924,6 @@ cache-base@^1.0.1: union-value "^1.0.0" unset-value "^1.0.0" -cacheable-request@^2.1.1: - version "2.1.4" - resolved "https://registry.npmjs.org/cacheable-request/-/cacheable-request-2.1.4.tgz#0d808801b6342ad33c91df9d0b44dc09b91e5c3d" - integrity sha1-DYCIAbY0KtM8kd+dC0TcCbkeXD0= - dependencies: - clone-response "1.0.2" - get-stream "3.0.0" - http-cache-semantics "3.8.1" - keyv "3.0.0" - lowercase-keys "1.0.0" - normalize-url "2.0.1" - responselike "1.0.2" - cacheable-request@^6.0.0: version "6.1.0" resolved "https://registry.npmjs.org/cacheable-request/-/cacheable-request-6.1.0.tgz#20ffb8bd162ba4be11e9567d823db651052ca912" @@ -6228,7 +6089,7 @@ caseless@~0.12.0: resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw= -caw@^2.0.0, caw@^2.0.1: +caw@^2.0.0: version "2.0.1" resolved "https://registry.npmjs.org/caw/-/caw-2.0.1.tgz#6c3ca071fc194720883c2dc5da9b074bfc7e9e95" integrity sha512-Cg8/ZSBEa8ZVY9HspcGUYaK63d/bN7rqS3CYCzEGUxuYv6UlmcjzDUz2fCFFHyTvUW5Pk0I+3hkA3iXlIj6guA== @@ -6678,7 +6539,7 @@ clone-deep@^4.0.1: kind-of "^6.0.2" shallow-clone "^3.0.0" -clone-response@1.0.2, clone-response@^1.0.2: +clone-response@^1.0.2: version "1.0.2" resolved "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz#d1dc973920314df67fbeb94223b4ee350239e96b" integrity sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws= @@ -7072,7 +6933,7 @@ content-disposition@0.5.2: resolved "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz#0cf68bb9ddf5f2be7961c3a85178cb85dba78cb4" integrity sha1-DPaLud318r55YcOoUXjLhdunjLQ= -content-disposition@0.5.3, content-disposition@^0.5.2: +content-disposition@0.5.3: version "0.5.3" resolved "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz#e130caf7e7279087c5616c2007d0485698984fbd" integrity sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g== @@ -7281,7 +7142,7 @@ create-error-class@^3.0.0: dependencies: capture-stack-trace "^1.0.0" -create-hash@^1.1.0, create-hash@^1.1.1, create-hash@^1.1.2, create-hash@^1.2.0: +create-hash@^1.1.0, create-hash@^1.1.1, create-hash@^1.1.2: version "1.2.0" resolved "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz#889078af11a63756bcfb59bd221996be3a9ef196" integrity sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg== @@ -7292,7 +7153,7 @@ create-hash@^1.1.0, create-hash@^1.1.1, create-hash@^1.1.2, create-hash@^1.2.0: ripemd160 "^2.0.1" sha.js "^2.4.0" -create-hmac@^1.1.0, create-hmac@^1.1.2, create-hmac@^1.1.4, create-hmac@^1.1.7: +create-hmac@^1.1.0, create-hmac@^1.1.2, create-hmac@^1.1.4: version "1.1.7" resolved "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz#69170c78b3ab957147b2b8b04572e47ead2243ff" integrity sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg== @@ -7853,7 +7714,7 @@ decompress-unzip@^4.0.1: pify "^2.3.0" yauzl "^2.4.2" -decompress@^4.0.0, decompress@^4.2.0: +decompress@^4.0.0: version "4.2.0" resolved "https://registry.npmjs.org/decompress/-/decompress-4.2.0.tgz#7aedd85427e5a92dacfe55674a7c505e96d01f9d" integrity sha1-eu3YVCflqS2s/lVnSnxQXpbQH50= @@ -7899,7 +7760,7 @@ deep-object-diff@^1.1.0: resolved "https://registry.npmjs.org/deep-object-diff/-/deep-object-diff-1.1.0.tgz#d6fabf476c2ed1751fc94d5ca693d2ed8c18bc5a" integrity sha512-b+QLs5vHgS+IoSNcUE4n9HP2NwcHj7aqnJWsjPtuG75Rh5TOaGt0OjAYInh77d5T16V5cRDC+Pw/6ZZZiETBGw== -deepmerge@^2.1.0, deepmerge@^2.1.1: +deepmerge@^2.1.1: version "2.2.1" resolved "https://registry.npmjs.org/deepmerge/-/deepmerge-2.2.1.tgz#5d3ff22a01c00f645405a2fbc17d0778a1801170" integrity sha512-R9hc1Xa/NOBi9WRVUWg19rl1UB7Tt4kuPd+thNJgFZoxXsTz7ncaPaeIm+40oSGuP33DfMb4sZt1QIGiJzC4EA== @@ -8403,24 +8264,6 @@ download@^5.0.3: mkdirp "^0.5.1" pify "^2.3.0" -download@^7.1.0: - version "7.1.0" - resolved "https://registry.npmjs.org/download/-/download-7.1.0.tgz#9059aa9d70b503ee76a132897be6dec8e5587233" - integrity sha512-xqnBTVd/E+GxJVrX5/eUJiLYjCGPwMpdL+jGhGU57BvtcA7wwhtHVbXBeUk51kOpW3S7Jn3BQbN9Q1R1Km2qDQ== - dependencies: - archive-type "^4.0.0" - caw "^2.0.1" - content-disposition "^0.5.2" - decompress "^4.2.0" - ext-name "^5.0.0" - file-type "^8.1.0" - filenamify "^2.0.0" - get-stream "^3.0.0" - got "^8.3.1" - make-dir "^1.2.0" - p-event "^2.1.0" - pify "^3.0.0" - drbg.js@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/drbg.js/-/drbg.js-1.0.1.tgz#3e36b6c42b37043823cdbc332d58f31e2445480b" @@ -8691,13 +8534,6 @@ enhanced-resolve@^4.0.0, enhanced-resolve@^4.1.0: memory-fs "^0.4.0" tapable "^1.0.0" -enquirer@^2.3.0: - version "2.3.2" - resolved "https://registry.npmjs.org/enquirer/-/enquirer-2.3.2.tgz#1c30284907cadff5ed2404bd8396036dd3da070e" - integrity sha512-PLhTMPUXlnaIv9D3Cq3/Zr1xb7soeDDgunobyCmYLUG19n24dvC8i+ZZgm2DekGpDnx7JvFSHV7lxfM58PMtbA== - dependencies: - ansi-colors "^3.2.1" - entities@^1.1.1, entities@~1.1.1: version "1.1.2" resolved "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz#bdfa735299664dfafd34529ed4f8522a275fea56" @@ -9469,7 +9305,7 @@ ethereumjs-abi@^0.6.7, ethereumjs-abi@^0.6.8: bn.js "^4.11.8" ethereumjs-util "^6.0.0" -ethereumjs-account@3.0.0, ethereumjs-account@^3.0.0: +ethereumjs-account@3.0.0: version "3.0.0" resolved "https://registry.npmjs.org/ethereumjs-account/-/ethereumjs-account-3.0.0.tgz#728f060c8e0c6e87f1e987f751d3da25422570a9" integrity sha512-WP6BdscjiiPkQfF9PVfMcwx/rDvfZTjFKY0Uwc09zSQr9JfIVH87dYIJu0gNhBhpmovV4yq295fdllS925fnBA== @@ -9487,7 +9323,7 @@ ethereumjs-account@^2.0.3: rlp "^2.0.0" safe-buffer "^5.1.1" -ethereumjs-block@2.2.0, ethereumjs-block@^2.2.0, ethereumjs-block@~2.2.0: +ethereumjs-block@2.2.0, ethereumjs-block@~2.2.0: version "2.2.0" resolved "https://registry.npmjs.org/ethereumjs-block/-/ethereumjs-block-2.2.0.tgz#8c6c3ab4a5eff0a16d9785fbeedbe643f4dbcbef" integrity sha512-Ye+uG/L2wrp364Zihdlr/GfC3ft+zG8PdHcRtsBFNNH1CkOhxOwdB8friBU85n89uRZ9eIMAywCq0F4CwT1wAw== @@ -9525,23 +9361,7 @@ ethereumjs-blockchain@^3.4.0: safe-buffer "^5.1.2" semaphore "^1.1.0" -ethereumjs-blockchain@^4.0.1: - version "4.0.1" - resolved "https://registry.npmjs.org/ethereumjs-blockchain/-/ethereumjs-blockchain-4.0.1.tgz#db113dfed4fcc5197d223391f10adbc5a1b3536b" - integrity sha512-twf2yeyzeBXzCgclLyF9wZEyCKbCweM2KZdZkTsnlqGgaffgnSgY44+z+9BHUIVoWY+gxMj+XsTlTgVcbha8rg== - dependencies: - async "^2.6.1" - ethashjs "~0.0.7" - ethereumjs-block "~2.2.0" - ethereumjs-common "^1.1.0" - ethereumjs-util "~6.1.0" - flow-stoplight "^1.0.0" - level-mem "^3.0.1" - lru-cache "^5.1.1" - rlp "^2.2.2" - semaphore "^1.1.0" - -ethereumjs-common@^1.1.0, ethereumjs-common@^1.3.1, ethereumjs-common@^1.3.2: +ethereumjs-common@^1.1.0: version "1.3.2" resolved "https://registry.npmjs.org/ethereumjs-common/-/ethereumjs-common-1.3.2.tgz#5a20831e52199a31ff4b68ef361e34c05c976ed0" integrity sha512-GkltYRIqBLzaZLmF/K3E+g9lZ4O4FL+TtpisAlD3N+UVlR+mrtoG+TvxavqVa6PwOY4nKIEMe5pl6MrTio3Lww== @@ -9561,15 +9381,7 @@ ethereumjs-tx@1.3.7, ethereumjs-tx@^1.1.1, ethereumjs-tx@^1.2.0, ethereumjs-tx@^ ethereum-common "^0.0.18" ethereumjs-util "^5.0.0" -ethereumjs-tx@^2.1.1: - version "2.1.1" - resolved "https://registry.npmjs.org/ethereumjs-tx/-/ethereumjs-tx-2.1.1.tgz#7d204e2b319156c9bc6cec67e9529424a26e8ccc" - integrity sha512-QtVriNqowCFA19X9BCRPMgdVNJ0/gMBS91TQb1DfrhsbR748g4STwxZptFAwfqehMyrF8rDwB23w87PQwru0wA== - dependencies: - ethereumjs-common "^1.3.1" - ethereumjs-util "^6.0.0" - -ethereumjs-util@6.1.0, ethereumjs-util@^6.0.0, ethereumjs-util@^6.1.0, ethereumjs-util@~6.1.0: +ethereumjs-util@6.1.0, ethereumjs-util@^6.0.0, ethereumjs-util@^6.1.0: version "6.1.0" resolved "https://registry.yarnpkg.com/ethereumjs-util/-/ethereumjs-util-6.1.0.tgz#e9c51e5549e8ebd757a339cc00f5380507e799c8" integrity sha512-URESKMFbDeJxnAxPppnk2fN6Y3BIatn9fwn76Lm8bQlt+s52TpG8dN9M66MLPuRAiAOIqL3dfwqWJf0sd0fL0Q== @@ -9593,7 +9405,7 @@ ethereumjs-util@^4.0.1, ethereumjs-util@^4.3.0: rlp "^2.0.0" secp256k1 "^3.0.1" -ethereumjs-util@^5.0.0, ethereumjs-util@^5.0.1, ethereumjs-util@^5.1.1, ethereumjs-util@^5.1.2, ethereumjs-util@^5.1.3, ethereumjs-util@^5.1.5, ethereumjs-util@^5.2.0: +ethereumjs-util@^5.0.0, ethereumjs-util@^5.0.1, ethereumjs-util@^5.1.1, ethereumjs-util@^5.1.2, ethereumjs-util@^5.1.3, ethereumjs-util@^5.1.5: version "5.2.0" resolved "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-5.2.0.tgz#3e0c0d1741471acf1036052d048623dee54ad642" integrity sha512-CJAKdI0wgMbQFLlLRtZKGcy/L6pzVRgelIZqRqNbuVFM3K9VEnyfbcvz0ncWMRNCe4kaHWjwRYQcYMucmwsnWA== @@ -9766,11 +9578,6 @@ event-emitter@~0.3.5: d "1" es5-ext "~0.10.14" -event-target-shim@^5.0.0: - version "5.0.1" - resolved "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz#5d4d3ebdf9583d63a5333ce2deb7480ab2b05789" - integrity sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ== - eventemitter3@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-1.1.1.tgz#47786bdaa087caf7b1b75e73abc5c7d540158cd0" @@ -10013,21 +9820,6 @@ express@^4.14.0, express@^4.15.2, express@^4.16.2, express@^4.16.3, express@^4.1 utils-merge "1.0.1" vary "~1.1.2" -ext-list@^2.0.0: - version "2.2.2" - resolved "https://registry.npmjs.org/ext-list/-/ext-list-2.2.2.tgz#0b98e64ed82f5acf0f2931babf69212ef52ddd37" - integrity sha512-u+SQgsubraE6zItfVA0tBuCBhfU9ogSRnsvygI7wht9TS510oLkBRXBsqopeUG/GBOIQyKZO9wjTqIu/sf5zFA== - dependencies: - mime-db "^1.28.0" - -ext-name@^5.0.0: - version "5.0.0" - resolved "https://registry.npmjs.org/ext-name/-/ext-name-5.0.0.tgz#70781981d183ee15d13993c8822045c506c8f0a6" - integrity sha512-yblEwXAbGv1VQDmow7s38W77hzAgJAO50ztBLMcUyUBfxv1HC+LGwtiEN+Co6LtlqT/5uwVOxsD4TNIilWhwdQ== - dependencies: - ext-list "^2.0.0" - sort-keys-length "^1.0.0" - extend-shallow@^2.0.1: version "2.0.1" resolved "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz#51af7d614ad9a9f610ea1bafbb989d6b1c56890f" @@ -10326,11 +10118,6 @@ file-type@^3.8.0: resolved "https://registry.npmjs.org/file-type/-/file-type-3.9.0.tgz#257a078384d1db8087bc449d107d52a52672b9e9" integrity sha1-JXoHg4TR24CHvESdEH1SpSZyuek= -file-type@^4.2.0: - version "4.4.0" - resolved "https://registry.npmjs.org/file-type/-/file-type-4.4.0.tgz#1b600e5fca1fbdc6e80c0a70c71c8dba5f7906c5" - integrity sha1-G2AOX8ofvcboDApwxxyNul95BsU= - file-type@^5.2.0: version "5.2.0" resolved "https://registry.npmjs.org/file-type/-/file-type-5.2.0.tgz#2ddbea7c73ffe36368dfae49dc338c058c2b8ad6" @@ -10341,11 +10128,6 @@ file-type@^6.1.0: resolved "https://registry.npmjs.org/file-type/-/file-type-6.2.0.tgz#e50cd75d356ffed4e306dc4f5bcf52a79903a919" integrity sha512-YPcTBDV+2Tm0VqjybVd32MHdlEGAtuxS3VAYsumFokDSMG+ROT5wawGlnHDoz7bfMcMDt9hxuXvXwoKUx2fkOg== -file-type@^8.1.0: - version "8.1.0" - resolved "https://registry.npmjs.org/file-type/-/file-type-8.1.0.tgz#244f3b7ef641bbe0cca196c7276e4b332399f68c" - integrity sha512-qyQ0pzAy78gVoJsmYeNgl8uH8yKhr1lVhW7JbzJmnlRi0I4R2eEDEJZVKG8agpDnLpacwNbDhLNG/LMdxHD2YQ== - file-uri-to-path@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd" @@ -10683,16 +10465,6 @@ forwarded@~0.1.2: resolved "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz#98c23dab1175657b8c0573e8ceccd91b0ff18c84" integrity sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ= -fp-ts@1.19.3: - version "1.19.3" - resolved "https://registry.npmjs.org/fp-ts/-/fp-ts-1.19.3.tgz#261a60d1088fbff01f91256f91d21d0caaaaa96f" - integrity sha512-H5KQDspykdHuztLTg+ajGN0Z2qUjcEf3Ybxc6hLt0k7/zPkn29XnKnxlBPyW2XIddWrGaJBzBl4VLYOtk39yZg== - -fp-ts@^1.0.0: - version "1.19.5" - resolved "https://registry.npmjs.org/fp-ts/-/fp-ts-1.19.5.tgz#3da865e585dfa1fdfd51785417357ac50afc520a" - integrity sha512-wDNqTimnzs8QqpldiId9OavWK2NptormjXnRJTQecNjzwfyp6P/8s/zG8e4h3ja3oqkKaY72UlTjQYt/1yXf9A== - fragment-cache@^0.2.1: version "0.2.1" resolved "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz#4290fad27f13e89be7f33799c6bc5a0abfff0d19" @@ -10710,7 +10482,7 @@ fresh@0.5.2: resolved "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" integrity sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac= -from2@^2.1.0, from2@^2.1.1: +from2@^2.1.0: version "2.3.0" resolved "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz#8bfb5502bde4a4d36cfdeea007fcca21d7e382af" integrity sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8= @@ -10895,7 +10667,7 @@ ganache-cli@^6.1.0: source-map-support "0.5.9" yargs "11.1.0" -ganache-core@^2.6.0, ganache-core@^2.8.0: +ganache-core@*, ganache-core@^2.6.0, ganache-core@^2.8.0: version "2.8.0" resolved "https://registry.npmjs.org/ganache-core/-/ganache-core-2.8.0.tgz#eeadc7f7fc3a0c20d99f8f62021fb80b5a05490c" integrity sha512-hfXqZGJx700jJqwDHNXrU2BnPYuETn1ekm36oRHuXY3oOuJLFs5C+cFdUFaBlgUxcau1dOgZUUwKqTAy0gTA9Q== @@ -10979,11 +10751,6 @@ get-stdin@^6.0.0: resolved "https://registry.npmjs.org/get-stdin/-/get-stdin-6.0.0.tgz#9e09bf712b360ab9225e812048f71fde9c89657b" integrity sha512-jp4tHawyV7+fkkSKyvjuLZswblUtz+SQKzSWnBbii16BuZksJlU1wuBYXY75r+duh/llF1ur6oNwi+2ZzjKZ7g== -get-stream@3.0.0, get-stream@^3.0.0: - version "3.0.0" - resolved "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz#8e943d1358dc37555054ecbe2edb05aa174ede14" - integrity sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ= - get-stream@^2.2.0: version "2.3.1" resolved "https://registry.npmjs.org/get-stream/-/get-stream-2.3.1.tgz#5f38f93f346009666ee0150a054167f91bdd95de" @@ -10992,6 +10759,11 @@ get-stream@^2.2.0: object-assign "^4.0.1" pinkie-promise "^2.0.0" +get-stream@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz#8e943d1358dc37555054ecbe2edb05aa174ede14" + integrity sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ= + get-stream@^4.0.0, get-stream@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-4.1.0.tgz#c1b255575f3dc21d59bfc79cd3d2b46b1c3a54b5" @@ -11158,7 +10930,7 @@ glob@^7.0.3, glob@^7.1.1: once "^1.3.0" path-is-absolute "^1.0.0" -global-dirs@^0.1.0, global-dirs@^0.1.1: +global-dirs@^0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/global-dirs/-/global-dirs-0.1.1.tgz#b319c0dd4607f353f3be9cca4c72fc148c49f445" integrity sha1-sxnA3UYH81PzvpzKTHL8FIxJ9EU= @@ -11351,29 +11123,6 @@ got@^6.3.0: unzip-response "^2.0.1" url-parse-lax "^1.0.0" -got@^8.3.1: - version "8.3.2" - resolved "https://registry.npmjs.org/got/-/got-8.3.2.tgz#1d23f64390e97f776cac52e5b936e5f514d2e937" - integrity sha512-qjUJ5U/hawxosMryILofZCkm3C84PLJS/0grRIpjAwu+Lkxxj5cxeCU25BG0/3mDSpXKTyZr8oh8wIgLaH0QCw== - dependencies: - "@sindresorhus/is" "^0.7.0" - cacheable-request "^2.1.1" - decompress-response "^3.3.0" - duplexer3 "^0.1.4" - get-stream "^3.0.0" - into-stream "^3.1.0" - is-retry-allowed "^1.1.0" - isurl "^1.0.0-alpha5" - lowercase-keys "^1.0.0" - mimic-response "^1.0.0" - p-cancelable "^0.4.0" - p-timeout "^2.0.1" - pify "^3.0.0" - safe-buffer "^5.1.1" - timed-out "^4.0.1" - url-parse-lax "^3.0.0" - url-to-options "^1.0.1" - graceful-fs@^4.0.0, graceful-fs@^4.1.10, graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.1.9, graceful-fs@^4.2.0: version "4.2.2" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.2.tgz#6f0952605d0140c1cfdb138ed005775b92d67b02" @@ -11940,11 +11689,6 @@ htmlparser2@^3.3.0, htmlparser2@^3.9.1: inherits "^2.0.1" readable-stream "^3.1.1" -http-cache-semantics@3.8.1: - version "3.8.1" - resolved "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-3.8.1.tgz#39b0e16add9b605bf0a9ef3d9daaf4843b4cacd2" - integrity sha512-5ai2iksyV8ZXmnZhHH4rWPoxxistEexSi5936zIQ1bnNTW5VnA85B6P/VpXiRM017IgRvb2kKo1a//y+0wSp3w== - http-cache-semantics@^4.0.0: version "4.0.3" resolved "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.0.3.tgz#495704773277eeef6e43f9ab2c2c7d259dda25c5" @@ -12400,14 +12144,6 @@ intersection-observer@^0.5.1: resolved "https://registry.npmjs.org/intersection-observer/-/intersection-observer-0.5.1.tgz#e340fc56ce74290fe2b2394d1ce88c4353ac6dfa" integrity sha512-Zd7Plneq82kiXFixs7bX62YnuZ0BMRci9br7io88LwDyF3V43cQMI+G5IiTlTNTt+LsDUppl19J/M2Fp9UkH6g== -into-stream@^3.1.0: - version "3.1.0" - resolved "https://registry.npmjs.org/into-stream/-/into-stream-3.1.0.tgz#96fb0a936c12babd6ff1752a17d05616abd094c6" - integrity sha1-lvsKk2wSur1v8XUqF9BWFqvQlMY= - dependencies: - from2 "^2.1.1" - p-is-promise "^1.1.0" - invariant@2.2.4, invariant@^2.2.2, invariant@^2.2.3, invariant@^2.2.4: version "2.2.4" resolved "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6" @@ -12425,13 +12161,6 @@ invert-kv@^2.0.0: resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-2.0.0.tgz#7393f5afa59ec9ff5f67a27620d11c226e3eec02" integrity sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA== -io-ts@1.10.4: - version "1.10.4" - resolved "https://registry.npmjs.org/io-ts/-/io-ts-1.10.4.tgz#cd5401b138de88e4f920adbcb7026e2d1967e6e2" - integrity sha512-b23PteSnYXSONJ6JQXRAlvJhuw8KOtkqa87W4wDtvMrud/DTJd5X+NpOOI+O/zZwVq6v0VLAaJ+1EDViKEuN9g== - dependencies: - fp-ts "^1.0.0" - ip-regex@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/ip-regex/-/ip-regex-2.1.0.tgz#fa78bf5d2e6913c911ce9f819ee5146bb6d844e9" @@ -12739,14 +12468,6 @@ is-installed-globally@0.1.0: global-dirs "^0.1.0" is-path-inside "^1.0.0" -is-installed-globally@^0.2.0: - version "0.2.0" - resolved "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.2.0.tgz#8cde07ade508458b51f14bcda315ffaf4898de30" - integrity sha512-g3TzWCnR/eO4Q3abCwgFjOFw7uVOfxG4m8hMr/39Jcf2YvE5mHrFKqpyuraWV4zwx9XhjnVO4nY0ZI4llzl0Pg== - dependencies: - global-dirs "^0.1.1" - is-path-inside "^2.1.0" - is-lower-case@^1.1.0: version "1.1.3" resolved "https://registry.npmjs.org/is-lower-case/-/is-lower-case-1.1.3.tgz#7e147be4768dc466db3bfb21cc60b31e6ad69393" @@ -12909,11 +12630,6 @@ is-retry-allowed@^1.0.0: resolved "https://registry.yarnpkg.com/is-retry-allowed/-/is-retry-allowed-1.1.0.tgz#11a060568b67339444033d0125a61a20d564fb34" integrity sha1-EaBgVotnM5REAz0BJaYaINVk+zQ= -is-retry-allowed@^1.1.0: - version "1.2.0" - resolved "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-1.2.0.tgz#d778488bd0a4666a3be8a1482b9f2baafedea8b4" - integrity sha512-RUbUeKwvm3XG2VYamhJL1xFktgjvPzL0Hq8C+6yrWIswDy3BIXGqCxhxkc30N9jqK311gVU137K8Ei55/zVJRg== - is-root@2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/is-root/-/is-root-2.0.0.tgz#838d1e82318144e5a6f77819d90207645acc7019" @@ -14105,13 +13821,6 @@ keygrip@~1.0.3: resolved "https://registry.npmjs.org/keygrip/-/keygrip-1.0.3.tgz#399d709f0aed2bab0a059e0cdd3a5023a053e1dc" integrity sha512-/PpesirAIfaklxUzp4Yb7xBper9MwP6hNRA6BGGUFCgbJ+BM5CKBtsoxinNXkLHAr+GXS1/lSlF2rP7cv5Fl+g== -keyv@3.0.0: - version "3.0.0" - resolved "https://registry.npmjs.org/keyv/-/keyv-3.0.0.tgz#44923ba39e68b12a7cec7df6c3268c031f2ef373" - integrity sha512-eguHnq22OE3uVoSYG0LVWNP+4ppamWr9+zWBe1bsNcovIMy6huUJFPgy4mGwCd/rnl3vOLGW1MTlu4c57CT1xA== - dependencies: - json-buffer "3.0.0" - keyv@^3.0.0: version "3.1.0" resolved "https://registry.npmjs.org/keyv/-/keyv-3.1.0.tgz#ecc228486f69991e49e9476485a5be1e8fc5c4d9" @@ -14355,15 +14064,6 @@ level-ws@0.0.0: readable-stream "~1.0.15" xtend "~2.1.1" -level-ws@^1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/level-ws/-/level-ws-1.0.0.tgz#19a22d2d4ac57b18cc7c6ecc4bd23d899d8f603b" - integrity sha512-RXEfCmkd6WWFlArh3X8ONvQPm8jNpfA0s/36M4QzLqrLEIt1iJE9WBHLZ5vZJK6haMjJPJGJCQWfjMNnRcq/9Q== - dependencies: - inherits "^2.0.3" - readable-stream "^2.2.8" - xtend "^4.0.1" - levelup@3.1.1, levelup@^3.0.0: version "3.1.1" resolved "https://registry.npmjs.org/levelup/-/levelup-3.1.1.tgz#c2c0b3be2b4dc316647c53b42e2f559e232d2189" @@ -14795,11 +14495,6 @@ lower-case@^1.1.0, lower-case@^1.1.1, lower-case@^1.1.2: resolved "https://registry.npmjs.org/lower-case/-/lower-case-1.1.4.tgz#9a2cabd1b9e8e0ae993a4bf7d5875c39c42e8eac" integrity sha1-miyr0bno4K6ZOkv31YdcOcQujqw= -lowercase-keys@1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.0.tgz#4e3366b39e7f5457e35f1324bdf6f88d0bfc7306" - integrity sha1-TjNms55/VFfjXxMkvfb4jQv8cwY= - lowercase-keys@^1.0.0, lowercase-keys@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.1.tgz#6f9e30b47084d971a7c820ff15a6c5167b74c26f" @@ -14850,7 +14545,7 @@ ltgt@~2.1.1: resolved "https://registry.npmjs.org/ltgt/-/ltgt-2.1.3.tgz#10851a06d9964b971178441c23c9e52698eece34" integrity sha1-EIUaBtmWS5cReEQcI8nlJpjuzjQ= -make-dir@^1.0.0, make-dir@^1.2.0, make-dir@^1.3.0: +make-dir@^1.0.0, make-dir@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-1.3.0.tgz#79c1033b80515bd6d24ec9933e860ca75ee27f0c" integrity sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ== @@ -15107,19 +14802,6 @@ merkle-patricia-tree@2.3.2, merkle-patricia-tree@^2.1.2, merkle-patricia-tree@^2 rlp "^2.0.0" semaphore ">=1.0.1" -merkle-patricia-tree@^3.0.0: - version "3.0.0" - resolved "https://registry.npmjs.org/merkle-patricia-tree/-/merkle-patricia-tree-3.0.0.tgz#448d85415565df72febc33ca362b8b614f5a58f8" - integrity sha512-soRaMuNf/ILmw3KWbybaCjhx86EYeBbD8ph0edQCTed0JN/rxDt1EBN52Ajre3VyGo+91f8+/rfPIRQnnGMqmQ== - dependencies: - async "^2.6.1" - ethereumjs-util "^5.2.0" - level-mem "^3.0.1" - level-ws "^1.0.0" - readable-stream "^3.0.6" - rlp "^2.0.0" - semaphore ">=1.0.1" - methods@^1.1.1, methods@^1.1.2, methods@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" @@ -15199,11 +14881,6 @@ mime-db@1.40.0: resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.41.0.tgz#9110408e1f6aa1b34aef51f2c9df3caddf46b6a0" integrity sha512-B5gxBI+2K431XW8C2rcc/lhppbuji67nf9v39eH8pkWoZDxnAL0PxdpH32KYRScniF8qDHBDlI+ipgg5WrCUYw== -mime-db@^1.28.0: - version "1.42.0" - resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.42.0.tgz#3e252907b4c7adb906597b4b65636272cf9e7bac" - integrity sha512-UbfJCR4UAVRNgMpfImz05smAXK7+c+ZntjaA26ANtkXLlOe947Aag5zdIcKQULAiF9Cq4WxBi9jUs5zkA84bYQ== - mime-db@~1.33.0: version "1.33.0" resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz#a3492050a5cb9b63450541e39d9788d2272783db" @@ -15384,7 +15061,7 @@ mkdirp@*, mkdirp@0.5.1, mkdirp@0.5.x, mkdirp@0.x, "mkdirp@>=0.5 0", mkdirp@^0.5. dependencies: minimist "0.0.8" -mocha@5.2.0, mocha@^5.2.0: +mocha@5.2.0: version "5.2.0" resolved "https://registry.npmjs.org/mocha/-/mocha-5.2.0.tgz#6d8ae508f59167f940f2b5b3c4a612ae50c90ae6" integrity sha512-2IUgKDhc3J7Uug+FxMXuqIyYzH7gJjXECKe/w43IGgQHTSj3InJi+yAA7T24L9bQMRKiUEHxEX37G5JpVUGLcQ== @@ -15817,15 +15494,6 @@ normalize-url@1.9.1: query-string "^4.1.0" sort-keys "^1.0.0" -normalize-url@2.0.1: - version "2.0.1" - resolved "https://registry.npmjs.org/normalize-url/-/normalize-url-2.0.1.tgz#835a9da1551fa26f70e92329069a23aa6574d7e6" - integrity sha512-D6MUW4K/VzoJ4rJ01JFKxDrtY1v9wrgzCX5f2qj/lzH1m/lW6MhUZFKerVsnyjOhOsYzI9Kqqak+10l4LvLpMw== - dependencies: - prepend-http "^2.0.0" - query-string "^5.0.1" - sort-keys "^2.0.0" - normalize-url@^3.0.0, normalize-url@^3.3.0: version "3.3.0" resolved "https://registry.npmjs.org/normalize-url/-/normalize-url-3.3.0.tgz#b2e1c4dc4f7c6d57743df733a4f5978d18650559" @@ -16312,11 +15980,6 @@ p-cancelable@^0.3.0: resolved "https://registry.npmjs.org/p-cancelable/-/p-cancelable-0.3.0.tgz#b9e123800bcebb7ac13a479be195b507b98d30fa" integrity sha512-RVbZPLso8+jFeq1MfNvgXtCRED2raz/dKpacfTNxsx6pLEpEomM7gah6VeHSYV3+vo0OAi4MkArtQcWWXuQoyw== -p-cancelable@^0.4.0: - version "0.4.1" - resolved "https://registry.npmjs.org/p-cancelable/-/p-cancelable-0.4.1.tgz#35f363d67d52081c8d9585e37bcceb7e0bbcb2a0" - integrity sha512-HNa1A8LvB1kie7cERyy21VNeHb2CWJJYqyyC2o3klWFfMGlFmWv2Z7sFgZH8ZiaYL95ydToKTFVXgMV/Os0bBQ== - p-cancelable@^1.0.0: version "1.1.0" resolved "https://registry.npmjs.org/p-cancelable/-/p-cancelable-1.1.0.tgz#d078d15a3af409220c886f1d9a0ca2e441ab26cc" @@ -16334,13 +15997,6 @@ p-each-series@^1.0.0: dependencies: p-reduce "^1.0.0" -p-event@^2.1.0: - version "2.3.1" - resolved "https://registry.npmjs.org/p-event/-/p-event-2.3.1.tgz#596279ef169ab2c3e0cae88c1cfbb08079993ef6" - integrity sha512-NQCqOFhbpVTMX4qMe8PF8lbGtzZ+LCiN7pcNrb/413Na7+TRoe1xkKUzuWa/YEJdGQ0FvKtj35EEbDoVPO2kbA== - dependencies: - p-timeout "^2.0.1" - p-finally@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" @@ -16351,11 +16007,6 @@ p-finally@^2.0.0: resolved "https://registry.npmjs.org/p-finally/-/p-finally-2.0.1.tgz#bd6fcaa9c559a096b680806f4d657b3f0f240561" integrity sha512-vpm09aKwq6H9phqRQzecoDpD8TmVyGw70qmWlyq5onxY7tqyTTFVvxMykxQSQKILBSFlbXpypIw2T1Ml7+DDtw== -p-is-promise@^1.1.0: - version "1.1.0" - resolved "https://registry.npmjs.org/p-is-promise/-/p-is-promise-1.1.0.tgz#9c9456989e9f6588017b0434d56097675c3da05e" - integrity sha1-nJRWmJ6fZYgBewQ01WCXZ1w9oF4= - p-is-promise@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/p-is-promise/-/p-is-promise-2.0.0.tgz#7554e3d572109a87e1f3f53f6a7d85d1b194f4c5" @@ -16425,13 +16076,6 @@ p-timeout@^1.1.1: dependencies: p-finally "^1.0.0" -p-timeout@^2.0.1: - version "2.0.1" - resolved "https://registry.npmjs.org/p-timeout/-/p-timeout-2.0.1.tgz#d8dd1979595d2dc0139e1fe46b8b646cb3cdf038" - integrity sha512-88em58dDVB/KzPEx1X0N3LwFfYZPyDc4B6eF38M1rk9VTZMbxXXgjugz8mmwpS9Ox4BDZ+t6t3QP5+/gazweIA== - dependencies: - p-finally "^1.0.0" - p-try@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz#cbc79cdbaf8fd4228e13f621f2b1a237c1b207b3" @@ -18075,7 +17719,7 @@ qs@6.7.0: resolved "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc" integrity sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ== -qs@^6.5.1, qs@^6.6.0, qs@^6.7.0: +qs@^6.5.1, qs@^6.6.0: version "6.9.0" resolved "https://registry.npmjs.org/qs/-/qs-6.9.0.tgz#d1297e2a049c53119cb49cca366adbbacc80b409" integrity sha512-27RP4UotQORTpmNQDX8BHPukOnBP3p1uUJY5UnDhaJB+rMt9iMsok724XL+UHU23bEFOHRMQ2ZhI99qOWUMGFA== @@ -18925,7 +18569,7 @@ read-pkg@^4.0.1: parse-json "^4.0.0" pify "^3.0.0" -"readable-stream@1 || 2", readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.5, readable-stream@^2.0.6, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.2.8, readable-stream@^2.2.9, readable-stream@^2.3.0, readable-stream@^2.3.3, readable-stream@^2.3.5, readable-stream@^2.3.6, readable-stream@~2.3.6: +"readable-stream@1 || 2", readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.5, readable-stream@^2.0.6, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.2.9, readable-stream@^2.3.0, readable-stream@^2.3.3, readable-stream@^2.3.5, readable-stream@^2.3.6, readable-stream@~2.3.6: version "2.3.6" resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf" integrity sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw== @@ -19546,7 +19190,7 @@ resolve@~1.11.1: dependencies: path-parse "^1.0.6" -responselike@1.0.2, responselike@^1.0.2: +responselike@^1.0.2: version "1.0.2" resolved "https://registry.npmjs.org/responselike/-/responselike-1.0.2.tgz#918720ef3b631c5642be068f15ade5a46f4ba1e7" integrity sha1-kYcg7ztjHFZCvgaPFa3lpG9Loec= @@ -19645,7 +19289,7 @@ ripemd160@^2.0.0, ripemd160@^2.0.1: hash-base "^3.0.0" inherits "^2.0.1" -rlp@^2.0.0, rlp@^2.2.1, rlp@^2.2.2: +rlp@^2.0.0, rlp@^2.2.1: version "2.2.3" resolved "https://registry.yarnpkg.com/rlp/-/rlp-2.2.3.tgz#7f94aef86cec412df87d5ea1d8cb116a47d45f0e" integrity sha512-l6YVrI7+d2vpW6D6rS05x2Xrmq8oW7v3pieZOJKBEdjuTF4Kz/iwk55Zyh1Zaz+KOB2kC8+2jZlp2u9L4tTzCQ== @@ -20167,7 +19811,7 @@ sha.js@^2.4.0, sha.js@^2.4.8: sha3@^1.2.2, sha3@^2.0.7: version "2.0.7" - resolved "https://registry.yarnpkg.com/sha3/-/sha3-2.0.7.tgz#02ed270099647cbcc681b064ba091af72f561f35" + resolved "https://registry.npmjs.org/sha3/-/sha3-2.0.7.tgz#02ed270099647cbcc681b064ba091af72f561f35" integrity sha512-7Qsj/0J3pxCWfmyuDbTZWoKNSKY/rg2eecNRvTYE4EAPJ11Uh6xuEObyCG295AqgrtOjzdDbfwtGLDY52h11tA== dependencies: buffer "5.4.0" @@ -20472,20 +20116,6 @@ solc@0.5.0: semver "^5.5.0" yargs "^11.0.0" -solc@0.5.11: - version "0.5.11" - resolved "https://registry.npmjs.org/solc/-/solc-0.5.11.tgz#5905261191d01befd78ef52610a006820022ee8f" - integrity sha512-F8avCCVDYnJzvIm/ITsU11GFNdFI4HaNsME+zw9lK5a3ojD3LZN2Op2cIfWg7w1HeRYRiMOU1dM77saX6jUIKw== - dependencies: - command-exists "^1.2.8" - fs-extra "^0.30.0" - js-sha3 "0.8.0" - memorystream "^0.3.1" - require-from-string "^2.0.0" - semver "^5.5.0" - tmp "0.0.33" - yargs "^13.2.0" - solc@^0.4.20: version "0.4.25" resolved "https://registry.yarnpkg.com/solc/-/solc-0.4.25.tgz#06b8321f7112d95b4b903639b1138a4d292f5faa" @@ -20543,13 +20173,6 @@ solidity-parser-antlr@^0.4.2: resolved "https://registry.npmjs.org/solidity-parser-antlr/-/solidity-parser-antlr-0.4.11.tgz#af43e1f13b3b88309a875455f5d6e565b05ee5f1" integrity sha512-4jtxasNGmyC0midtjH/lTFPZYvTTUMy6agYcF+HoMnzW8+cqo3piFrINb4ZCzpPW+7tTVFCGa5ubP34zOzeuMg== -sort-keys-length@^1.0.0: - version "1.0.1" - resolved "https://registry.npmjs.org/sort-keys-length/-/sort-keys-length-1.0.1.tgz#9cb6f4f4e9e48155a6aa0671edd336ff1479a188" - integrity sha1-nLb09OnkgVWmqgZx7dM2/xR5oYg= - dependencies: - sort-keys "^1.0.0" - sort-keys@^1.0.0: version "1.1.2" resolved "https://registry.npmjs.org/sort-keys/-/sort-keys-1.1.2.tgz#441b6d4d346798f1b4e49e8920adfba0e543f9ad" @@ -20557,13 +20180,6 @@ sort-keys@^1.0.0: dependencies: is-plain-obj "^1.0.0" -sort-keys@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/sort-keys/-/sort-keys-2.0.0.tgz#658535584861ec97d730d6cf41822e1f56684128" - integrity sha1-ZYU1WEhh7JfXMNbPQYIuH1ZoQSg= - dependencies: - is-plain-obj "^1.0.0" - source-list-map@^2.0.0: version "2.0.1" resolved "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz#3993bd873bfc48479cca9ea3a547835c7c154b34" @@ -21621,17 +21237,6 @@ tiny-invariant@^1.0.2: resolved "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.0.4.tgz#346b5415fd93cb696b0c4e8a96697ff590f92463" integrity sha512-lMhRd/djQJ3MoaHEBrw8e2/uM4rs9YMNk0iOr8rHQ0QdbM7D4l0gFl3szKdeixrlyfm9Zqi4dxHCM2qVG8ND5g== -tiny-secp256k1@^1.1.0: - version "1.1.3" - resolved "https://registry.npmjs.org/tiny-secp256k1/-/tiny-secp256k1-1.1.3.tgz#e93b1e1bf62e9bd1ad3ab24af27ff6127ce0e077" - integrity sha512-ZpobrhOtHP98VYEN51IYQH1YcrbFpnxFhI6ceWa3OEbJn7eHvSd8YFjGPxbedGCy7PNYU1v/+BRsdvyr5uRd4g== - dependencies: - bindings "^1.3.0" - bn.js "^4.11.8" - create-hmac "^1.1.7" - elliptic "^6.4.0" - nan "^2.13.2" - tiny-warning@^1.0.0: version "1.0.2" resolved "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.2.tgz#1dfae771ee1a04396bdfde27a3adcebc6b648b28" @@ -21880,11 +21485,6 @@ ts-essentials@^1.0.0: resolved "https://registry.npmjs.org/ts-essentials/-/ts-essentials-1.0.4.tgz#ce3b5dade5f5d97cf69889c11bf7d2da8555b15a" integrity sha512-q3N1xS4vZpRouhYHDPwO0bDW3EZ6SK9CrrDHxi/D6BPReSjpVgWIOpLS2o0gSBZm+7q/wyKp6RVM1AeeW7uyfQ== -ts-essentials@^2.0.7: - version "2.0.12" - resolved "https://registry.npmjs.org/ts-essentials/-/ts-essentials-2.0.12.tgz#c9303f3d74f75fa7528c3d49b80e089ab09d8745" - integrity sha512-3IVX4nI6B5cc31/GFFE+i8ey/N2eA0CZDbo6n0yrz0zDX8ZJ8djmU1p+XRz7G3is0F3bB3pu2pAroFdAWQKU3w== - ts-generator@^0.0.8: version "0.0.8" resolved "https://registry.npmjs.org/ts-generator/-/ts-generator-0.0.8.tgz#7bd48ca064db026d9520bcb682b69efc20971d6a" @@ -21997,11 +21597,6 @@ tslib@^1.9.0, tslib@^1.9.3: resolved "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz#c3c19f95973fb0a62973fb09d90d961ee43e5c8a" integrity sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ== -tsort@0.0.1: - version "0.0.1" - resolved "https://registry.npmjs.org/tsort/-/tsort-0.0.1.tgz#e2280f5e817f8bf4275657fd0f9aebd44f5a2786" - integrity sha1-4igPXoF/i/QnVlf9D5rr1E9aJ4Y= - tsutils@^3.14.0, tsutils@^3.7.0: version "3.17.1" resolved "https://registry.npmjs.org/tsutils/-/tsutils-3.17.1.tgz#ed719917f11ca0dee586272b2ac49e015a2dd759" @@ -22125,11 +21720,6 @@ typedarray@^0.0.6: resolved "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= -typeforce@^1.11.5: - version "1.18.0" - resolved "https://registry.npmjs.org/typeforce/-/typeforce-1.18.0.tgz#d7416a2c5845e085034d70fcc5b6cc4a90edbfdc" - integrity sha512-7uc1O8h1M1g0rArakJdf0uLRSSgFcYexrVoKo+bzJd32gd4gDy2L/Z+8/FjPnU9ydY3pEnVPtr9FyscYY60K1g== - typeorm@^0.2.15: version "0.2.18" resolved "https://registry.npmjs.org/typeorm/-/typeorm-0.2.18.tgz#8ae1d21104117724af41ddc11035c40a705e1de8" @@ -23675,13 +23265,6 @@ widest-line@^2.0.0: dependencies: string-width "^2.1.1" -wif@^2.0.6: - version "2.0.6" - resolved "https://registry.npmjs.org/wif/-/wif-2.0.6.tgz#08d3f52056c66679299726fade0d432ae74b4704" - integrity sha1-CNP1IFbGZnkplyb63g1DKudLRwQ= - dependencies: - bs58check "<3.0.0" - window-size@0.1.0: version "0.1.0" resolved "https://registry.npmjs.org/window-size/-/window-size-0.1.0.tgz#5438cd2ea93b202efa3a19fe8887aee7c94f9c9d" From a73fa6cc742e81812441b00f36aa28a65092408a Mon Sep 17 00:00:00 2001 From: henrynguyen5 <6404866+HenryNguyen5@users.noreply.github.com> Date: Mon, 4 Nov 2019 18:39:50 -0500 Subject: [PATCH 084/199] Remove unneeded .js linting filter --- evm/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/evm/package.json b/evm/package.json index d48359cb3b2..c4fb2f64abb 100644 --- a/evm/package.json +++ b/evm/package.json @@ -10,7 +10,7 @@ "postbuild": "yarn generate-typings && yarn tsc && cp -f src/generated/*.d.ts dist/src/generated", "build:windows": "truffle.cmd build", "depcheck": "echo 'chainlink' && depcheck --ignore-dirs=build/contracts,v0.5,box || true", - "eslint": "eslint --ext .ts,.js testv2 src", + "eslint": "eslint --ext .ts, testv2 src", "solhint": "solhint ./contracts/**/*.sol", "lint": "yarn solhint && yarn eslint", "slither": "truffle compile --quiet && slither .", From 9038eecfe0d34e688c3051a58bf56b1a18b24bb3 Mon Sep 17 00:00:00 2001 From: henrynguyen5 <6404866+HenryNguyen5@users.noreply.github.com> Date: Mon, 4 Nov 2019 18:42:36 -0500 Subject: [PATCH 085/199] Disable warning for safe non-atomic updates --- evm/src/helpersV2.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/evm/src/helpersV2.ts b/evm/src/helpersV2.ts index 95c5f734677..bfde87db599 100644 --- a/evm/src/helpersV2.ts +++ b/evm/src/helpersV2.ts @@ -59,15 +59,18 @@ export function useSnapshot( await cb() d('snapshotting...') + /* eslint-disable-next-line require-atomic-updates */ snapshotId = await provider.send('evm_snapshot', undefined) d('snapshot id:%s', snapshotId) + /* eslint-disable-next-line require-atomic-updates */ hasDeployed = true } else { d('reverting to snapshot: %s', snapshotId) await provider.send('evm_revert', snapshotId) d('re-creating snapshot..') + /* eslint-disable-next-line require-atomic-updates */ snapshotId = await provider.send('evm_snapshot', undefined) d('recreated snapshot id:%s', snapshotId) } From 1e2bb0c1d8a079b836cf6387c69eb319208142db Mon Sep 17 00:00:00 2001 From: henrynguyen5 <6404866+HenryNguyen5@users.noreply.github.com> Date: Mon, 4 Nov 2019 18:24:44 -0500 Subject: [PATCH 086/199] Add comments for debugger.ts --- evm/src/debug.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/evm/src/debug.ts b/evm/src/debug.ts index a55de396a85..2acc9e42dca 100644 --- a/evm/src/debug.ts +++ b/evm/src/debug.ts @@ -1,5 +1,11 @@ import debug from 'debug' +/** + * This creates a debug logger instance to be used within our internal code. + * @see https://www.npmjs.com/package/debug to see how to use the logger at runtime + * @see wallet.ts makes extensive use of this function. + * @param name The root namespace to assign to the log messages + */ export function makeDebug(name: string): debug.Debugger { return debug(name) } From 7e61dcb73a91c6edd8dcc883127a2d43edbd3cfd Mon Sep 17 00:00:00 2001 From: henrynguyen5 <6404866+HenryNguyen5@users.noreply.github.com> Date: Mon, 4 Nov 2019 19:06:37 -0500 Subject: [PATCH 087/199] Increase test timeout time The beforeEach hook still needs to slowly run once before we can use it as a snapshot, so we need a long test timeout for CI --- evm/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/evm/package.json b/evm/package.json index c4fb2f64abb..f492175cec4 100644 --- a/evm/package.json +++ b/evm/package.json @@ -15,7 +15,7 @@ "lint": "yarn solhint && yarn eslint", "slither": "truffle compile --quiet && slither .", "pretest": "yarn build", - "test": "jest --testTimeout 20000", + "test": "jest --testTimeout 80000", "format": "prettier --write \"{src,testv2}/*/**\"", "prepublishOnly": "yarn workspace chainlinkv0.5 setup && yarn setup && yarn lint && yarn test", "setup": "ts-node ./scripts/build", From d165df35619c553ef544dd0957def566457bb11c Mon Sep 17 00:00:00 2001 From: Ryan Hall Date: Mon, 4 Nov 2019 20:38:21 -0500 Subject: [PATCH 088/199] make PR changes --- explorer/src/__tests__/server/realtime.test.ts | 2 +- explorer/src/support/client.ts | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/explorer/src/__tests__/server/realtime.test.ts b/explorer/src/__tests__/server/realtime.test.ts index 612100c6a83..457475661e1 100644 --- a/explorer/src/__tests__/server/realtime.test.ts +++ b/explorer/src/__tests__/server/realtime.test.ts @@ -81,7 +81,7 @@ describe('realtime', () => { }) }) - it('rejects invalid authentication', async (done: any) => { + it('rejects invalid authentication', async done => { expect.assertions(1) newChainlinkNode(ENDPOINT, chainlinkNode.accessKey, 'lol-no').catch( error => { diff --git a/explorer/src/support/client.ts b/explorer/src/support/client.ts index 7edb4104999..83e768bb636 100644 --- a/explorer/src/support/client.ts +++ b/explorer/src/support/client.ts @@ -27,8 +27,10 @@ export const newChainlinkNode = ( } const jsonClient = new jayson.Client(null, null) -export const createRPCRequest = (method: string, params?: any) => - jsonClient.request(method, params) +export const createRPCRequest = ( + method: string, + params?: jayson.RequestParamsLike, +) => jsonClient.request(method, params) // helper function that sends a message and only resolves once the // rsponse is received From e8024a2dcf88437f2af4f4bb15ec26fa0d3d022a Mon Sep 17 00:00:00 2001 From: John Barker Date: Mon, 4 Nov 2019 13:11:12 +0100 Subject: [PATCH 089/199] Introduce cancellable jobs --- core/adapters/adapter.go | 11 +- core/adapters/adapter_test.go | 4 +- core/adapters/compare.go | 11 +- core/adapters/copy_test.go | 3 +- core/adapters/eth_bool_test.go | 3 +- core/adapters/eth_format_test.go | 4 +- core/adapters/eth_tx_abi_encode_test.go | 2 +- core/adapters/eth_tx_test.go | 16 +- core/adapters/multiply_test.go | 2 +- core/adapters/sleep.go | 11 +- core/adapters/sleep_test.go | 31 +- core/cmd/client_test.go | 7 +- core/cmd/key_store_authenticator_test.go | 47 +- core/cmd/local_client_test.go | 91 +- core/cmd/remote_client.go | 22 +- core/cmd/remote_client_test.go | 65 +- core/cmd/renderer_test.go | 5 +- core/internal/cltest/cltest.go | 55 +- core/internal/cltest/factories.go | 46 +- core/internal/cltest/mocks.go | 89 +- core/internal/features_test.go | 49 +- core/internal/fixtures/web/start_at_job.json | 2 +- core/internal/mocks/application.go | 236 +++++ core/internal/mocks/run_executor.go | 25 + core/internal/mocks/run_manager.go | 128 +++ core/internal/mocks/run_queue.go | 49 ++ core/services/application.go | 59 +- core/services/application_test.go | 33 +- core/services/export_test.go | 35 - core/services/head_tracker.go | 2 +- core/services/job_runner.go | 267 ------ core/services/job_runner_test.go | 199 ----- core/services/job_subscriber.go | 27 +- core/services/job_subscriber_test.go | 304 ++----- core/services/run_executor.go | 116 +++ core/services/run_executor_test.go | 171 ++++ core/services/run_manager.go | 367 ++++++++ core/services/run_manager_test.go | 596 +++++++++++++ core/services/run_queue.go | 114 +++ core/services/run_queue_test.go | 122 +++ core/services/runs.go | 413 +-------- core/services/runs_test.go | 809 ------------------ core/services/scheduler.go | 92 +- core/services/scheduler_test.go | 349 ++------ core/services/subscription.go | 58 +- core/services/subscription_test.go | 224 ++++- core/services/synchronization/stats_pusher.go | 3 - core/services/validators.go | 4 +- core/store/eth_client_test.go | 6 + core/store/models/common.go | 2 + core/store/models/eth.go | 2 +- core/store/models/job_run.go | 13 +- core/store/models/job_run_test.go | 43 +- core/store/models/job_spec.go | 7 +- core/store/models/job_spec_test.go | 2 +- core/store/models/log_events.go | 24 +- core/store/models/log_events_test.go | 4 +- core/store/models/run_output.go | 6 - core/store/orm/orm.go | 28 +- core/store/orm/orm_test.go | 41 +- core/store/store.go | 65 -- core/store/store_test.go | 39 +- core/store/tx_manager_test.go | 10 +- core/web/bridge_types_controller_test.go | 21 +- core/web/config_controller_test.go | 1 + core/web/cors_test.go | 6 + .../external_initiators_controller_test.go | 5 + core/web/external_initiators_test.go | 17 +- core/web/gui_assets_test.go | 6 + core/web/job_runs_controller.go | 12 +- core/web/job_runs_controller_test.go | 2 + core/web/job_specs_controller_test.go | 42 +- core/web/ping_controller_test.go | 3 + core/web/router_test.go | 8 + .../web/service_agreements_controller_test.go | 6 + core/web/user_controller_test.go | 23 +- tools/ci/go_test | 2 +- 77 files changed, 2872 insertions(+), 2952 deletions(-) create mode 100644 core/internal/mocks/application.go create mode 100644 core/internal/mocks/run_executor.go create mode 100644 core/internal/mocks/run_manager.go create mode 100644 core/internal/mocks/run_queue.go delete mode 100644 core/services/export_test.go delete mode 100644 core/services/job_runner.go delete mode 100644 core/services/job_runner_test.go create mode 100644 core/services/run_executor.go create mode 100644 core/services/run_executor_test.go create mode 100644 core/services/run_manager.go create mode 100644 core/services/run_manager_test.go create mode 100644 core/services/run_queue.go create mode 100644 core/services/run_queue_test.go delete mode 100644 core/services/runs_test.go diff --git a/core/adapters/adapter.go b/core/adapters/adapter.go index 6f2da1646b8..2b4feb7e61e 100644 --- a/core/adapters/adapter.go +++ b/core/adapters/adapter.go @@ -7,6 +7,7 @@ import ( "github.com/smartcontractkit/chainlink/core/store" "github.com/smartcontractkit/chainlink/core/store/assets" "github.com/smartcontractkit/chainlink/core/store/models" + "github.com/smartcontractkit/chainlink/core/store/orm" ) var ( @@ -70,10 +71,10 @@ func (p PipelineAdapter) MinContractPayment() *assets.Link { } // For determines the adapter type to use for a given task. -func For(task models.TaskSpec, store *store.Store) (*PipelineAdapter, error) { +func For(task models.TaskSpec, config orm.ConfigReader, orm *orm.ORM) (*PipelineAdapter, error) { var ba BaseAdapter var err error - mic := store.Config.MinIncomingConfirmations() + mic := config.MinIncomingConfirmations() mcp := assets.NewLink(0) switch task.Type { @@ -94,11 +95,11 @@ func For(task models.TaskSpec, store *store.Store) (*PipelineAdapter, error) { err = unmarshalParams(task.Params, ba) case TaskTypeEthTx: ba = &EthTx{} - mcp = store.Config.MinimumContractPayment() + mcp = config.MinimumContractPayment() err = unmarshalParams(task.Params, ba) case TaskTypeEthTxABIEncode: ba = &EthTxABIEncode{} - mcp = store.Config.MinimumContractPayment() + mcp = config.MinimumContractPayment() err = unmarshalParams(task.Params, ba) case TaskTypeHTTPGet: ba = &HTTPGet{} @@ -131,7 +132,7 @@ func For(task models.TaskSpec, store *store.Store) (*PipelineAdapter, error) { ba = &Compare{} err = unmarshalParams(task.Params, ba) default: - bt, err := store.FindBridge(task.Type) + bt, err := orm.FindBridge(task.Type) if err != nil { return nil, fmt.Errorf("%s is not a supported adapter type", task.Type) } diff --git a/core/adapters/adapter_test.go b/core/adapters/adapter_test.go index 04a1118f5cf..576ef8b6894 100644 --- a/core/adapters/adapter_test.go +++ b/core/adapters/adapter_test.go @@ -17,7 +17,7 @@ func TestCreatingAdapterWithConfig(t *testing.T) { defer cleanup() task := models.TaskSpec{Type: adapters.TaskTypeNoOp} - adapter, err := adapters.For(task, store) + adapter, err := adapters.For(task, store.Config, store.ORM) adapter.Perform(models.RunInput{}, nil) assert.NoError(t, err) } @@ -48,7 +48,7 @@ func TestAdapterFor(t *testing.T) { for _, test := range cases { t.Run(test.wantType, func(t *testing.T) { task := models.TaskSpec{Type: models.MustNewTaskType(test.bridgeName)} - adapter, err := adapters.For(task, store) + adapter, err := adapters.For(task, store.Config, store.ORM) if test.wantErrored { assert.Error(t, err) } else { diff --git a/core/adapters/compare.go b/core/adapters/compare.go index c87bda09f0a..55750c6e80c 100644 --- a/core/adapters/compare.go +++ b/core/adapters/compare.go @@ -6,7 +6,6 @@ import ( "github.com/smartcontractkit/chainlink/core/store" "github.com/smartcontractkit/chainlink/core/store/models" - "github.com/tidwall/gjson" ) // Compare adapter type takes an Operator and a Value field to @@ -26,7 +25,7 @@ var ( // Perform uses the Operator to check the run's result against the // specified Value. func (c *Compare) Perform(input models.RunInput, _ *store.Store) models.RunOutput { - prevResult := input.Result() + prevResult := input.Result().String() if c.Value == "" { return models.NewRunOutputError(ErrValueNotSpecified) @@ -34,9 +33,9 @@ func (c *Compare) Perform(input models.RunInput, _ *store.Store) models.RunOutpu switch c.Operator { case "eq": - return models.NewRunOutputCompleteWithResult(c.Value == prevResult.String()) + return models.NewRunOutputCompleteWithResult(c.Value == prevResult) case "neq": - return models.NewRunOutputCompleteWithResult(c.Value != prevResult.String()) + return models.NewRunOutputCompleteWithResult(c.Value != prevResult) case "gt": value, desired, err := getValues(prevResult, c.Value) if err != nil { @@ -66,8 +65,8 @@ func (c *Compare) Perform(input models.RunInput, _ *store.Store) models.RunOutpu } } -func getValues(result gjson.Result, d string) (float64, float64, error) { - value, err := strconv.ParseFloat(result.String(), 64) +func getValues(result string, d string) (float64, float64, error) { + value, err := strconv.ParseFloat(result, 64) if err != nil { return 0, 0, ErrResultNotNumber } diff --git a/core/adapters/copy_test.go b/core/adapters/copy_test.go index 274f68eb254..f395b7a63ce 100644 --- a/core/adapters/copy_test.go +++ b/core/adapters/copy_test.go @@ -82,11 +82,12 @@ func TestCopy_Perform(t *testing.T) { test := tt t.Run(test.name, func(t *testing.T) { t.Parallel() - input := cltest.NewRunInput(cltest.JSONFromString(t, test.input)) + input := cltest.NewRunInputWithString(t, test.input) adapter := adapters.Copy{CopyPath: test.copyPath} result := adapter.Perform(input, nil) assert.Equal(t, test.wantData, result.Data().String()) assert.Equal(t, test.wantStatus, result.Status()) + assert.Equal(t, test.wantResultError, result.Error()) }) } diff --git a/core/adapters/eth_bool_test.go b/core/adapters/eth_bool_test.go index 8613106ece8..2e19341c6e5 100644 --- a/core/adapters/eth_bool_test.go +++ b/core/adapters/eth_bool_test.go @@ -28,9 +28,10 @@ func TestEthBool_Perform(t *testing.T) { test := tt t.Run(test.name, func(t *testing.T) { t.Parallel() - past := cltest.NewRunInput(cltest.JSONFromString(t, test.json)) + past := cltest.NewRunInputWithString(t, test.json) adapter := adapters.EthBool{} result := adapter.Perform(past, nil) + assert.NoError(t, result.Error()) assert.Equal(t, test.expected, result.Result().String()) }) diff --git a/core/adapters/eth_format_test.go b/core/adapters/eth_format_test.go index a6141a93062..d31d175b2f3 100644 --- a/core/adapters/eth_format_test.go +++ b/core/adapters/eth_format_test.go @@ -35,7 +35,7 @@ func TestEthBytes32_Perform(t *testing.T) { t.Run(test.name, func(t *testing.T) { t.Parallel() - past := cltest.NewRunInput(cltest.JSONFromString(t, test.json)) + past := cltest.NewRunInputWithString(t, test.json) adapter := adapters.EthBytes32{} result := adapter.Perform(past, nil) @@ -75,7 +75,7 @@ func TestEthInt256_Perform(t *testing.T) { adapter := adapters.EthInt256{} for _, test := range tests { t.Run(test.name, func(t *testing.T) { - input := cltest.NewRunInput(cltest.JSONFromString(t, test.json)) + input := cltest.NewRunInputWithString(t, test.json) result := adapter.Perform(input, nil) if test.errored { diff --git a/core/adapters/eth_tx_abi_encode_test.go b/core/adapters/eth_tx_abi_encode_test.go index b64570cccb4..f5d71b686ed 100644 --- a/core/adapters/eth_tx_abi_encode_test.go +++ b/core/adapters/eth_tx_abi_encode_test.go @@ -148,7 +148,7 @@ func TestEthTxABIEncodeAdapter_Perform_ConfirmedWithJSON(t *testing.T) { }) receipt := models.TxReceipt{Hash: hash, BlockNumber: cltest.Int(confirmed)} ethMock.Register("eth_getTransactionReceipt", receipt) - input := cltest.NewRunInput(cltest.JSONFromString(t, rawInput)) + input := cltest.NewRunInputWithString(t, rawInput) responseData := adapterUnderTest.Perform(input, store) assert.False(t, responseData.HasError()) from := cltest.GetAccountAddress(t, store) diff --git a/core/adapters/eth_tx_test.go b/core/adapters/eth_tx_test.go index 1bfb34f4e4e..70d1acd6b4f 100644 --- a/core/adapters/eth_tx_test.go +++ b/core/adapters/eth_tx_test.go @@ -506,7 +506,7 @@ func TestEthTxAdapter_DeserializationBytesFormat(t *testing.T) { assert.NoError(t, err) assert.Equal(t, task.Type, adapters.TaskTypeEthTx) - adapter, err := adapters.For(task, store) + adapter, err := adapters.For(task, store.Config, store.ORM) assert.NoError(t, err) ethtx, ok := adapter.BaseAdapter.(*adapters.EthTx) assert.True(t, ok) @@ -558,9 +558,8 @@ func TestEthTxAdapter_Perform_CustomGas(t *testing.T) { func TestEthTxAdapter_Perform_NotConnected(t *testing.T) { t.Parallel() - app, cleanup := cltest.NewApplicationWithKey(t) + store, cleanup := cltest.NewStore(t) defer cleanup() - store := app.Store adapter := adapters.EthTx{} data := adapter.Perform(models.RunInput{}, store) @@ -572,9 +571,8 @@ func TestEthTxAdapter_Perform_NotConnected(t *testing.T) { func TestEthTxAdapter_Perform_CreateTxWithGasErrorTreatsAsNotConnected(t *testing.T) { t.Parallel() - app, cleanup := cltest.NewApplicationWithKey(t) + store, cleanup := cltest.NewStore(t) defer cleanup() - store := app.Store ctrl := gomock.NewController(t) defer ctrl.Finish() @@ -600,9 +598,8 @@ func TestEthTxAdapter_Perform_CreateTxWithGasErrorTreatsAsNotConnected(t *testin func TestEthTxAdapter_Perform_CheckAttemptErrorTreatsAsNotConnected(t *testing.T) { t.Parallel() - app, cleanup := cltest.NewApplicationWithKey(t) + store, cleanup := cltest.NewStore(t) defer cleanup() - store := app.Store ctrl := gomock.NewController(t) defer ctrl.Finish() @@ -631,11 +628,10 @@ func TestEthTxAdapter_Perform_CheckAttemptErrorTreatsAsNotConnected(t *testing.T func TestEthTxAdapter_Perform_CreateTxWithEmptyResponseErrorTreatsAsPendingConfirmations(t *testing.T) { t.Parallel() - app, cleanup := cltest.NewApplicationWithKey(t) + store, cleanup := cltest.NewStore(t) defer cleanup() - store := app.Store - from := cltest.GetAccountAddress(t, store) + from := cltest.NewAddress() tx := cltest.CreateTx(t, store, from, 1) ctrl := gomock.NewController(t) diff --git a/core/adapters/multiply_test.go b/core/adapters/multiply_test.go index 4a0960c4597..c76bf42c27d 100644 --- a/core/adapters/multiply_test.go +++ b/core/adapters/multiply_test.go @@ -41,7 +41,7 @@ func TestMultiply_Perform(t *testing.T) { test := tt t.Run(test.name, func(t *testing.T) { t.Parallel() - input := cltest.NewRunInput(cltest.JSONFromString(t, test.json)) + input := cltest.NewRunInputWithString(t, test.json) adapter := adapters.Multiply{} jsonErr := json.Unmarshal([]byte(test.params), &adapter) result := adapter.Perform(input, nil) diff --git a/core/adapters/sleep.go b/core/adapters/sleep.go index af872b1b402..ec885162e23 100644 --- a/core/adapters/sleep.go +++ b/core/adapters/sleep.go @@ -3,6 +3,7 @@ package adapters import ( "time" + "github.com/smartcontractkit/chainlink/core/logger" "github.com/smartcontractkit/chainlink/core/store" "github.com/smartcontractkit/chainlink/core/store/models" "github.com/smartcontractkit/chainlink/core/utils" @@ -13,9 +14,15 @@ type Sleep struct { Until models.AnyTime `json:"until"` } -// Perform waits for the specified Until duration. +// Perform returns the input RunResult after waiting for the specified Until parameter. func (adapter *Sleep) Perform(input models.RunInput, str *store.Store) models.RunOutput { - return models.NewRunOutputPendingSleep() + duration := adapter.Duration() + if duration > 0 { + logger.Debugw("Task sleeping...", "duration", duration) + <-str.Clock.After(duration) + } + + return models.NewRunOutputComplete(models.JSON{}) } // Duration returns the amount of sleeping this task should be paused for. diff --git a/core/adapters/sleep_test.go b/core/adapters/sleep_test.go index fbf3b92fb18..d391a687284 100644 --- a/core/adapters/sleep_test.go +++ b/core/adapters/sleep_test.go @@ -14,6 +14,35 @@ import ( func TestSleep_Perform(t *testing.T) { store, cleanup := cltest.NewStore(t) defer cleanup() + clock := cltest.NewTriggerClock(t) + store.Clock = clock + + adapter := adapters.Sleep{} + err := json.Unmarshal([]byte(`{"until": 2147483647}`), &adapter) + assert.NoError(t, err) + + doneChan := make(chan struct{}) + go func() { + result := adapter.Perform(models.RunInput{}, store) + assert.Equal(t, string(models.RunStatusCompleted), string(result.Status())) + doneChan <- struct{}{} + }() + + select { + case <-doneChan: + t.Error("Sleep adapter did not sleep") + default: + } + + clock.Trigger() + + _, ok := <-doneChan + assert.True(t, ok) +} + +func TestSleep_Perform_AlreadyElapsed(t *testing.T) { + store, cleanup := cltest.NewStore(t) + defer cleanup() adapter := adapters.Sleep{} err := json.Unmarshal([]byte(`{"until": 1332151919}`), &adapter) @@ -21,5 +50,5 @@ func TestSleep_Perform(t *testing.T) { result := adapter.Perform(models.RunInput{}, store) require.NoError(t, result.Error()) - assert.Equal(t, string(models.RunStatusPendingSleep), string(result.Status())) + assert.Equal(t, string(models.RunStatusCompleted), string(result.Status())) } diff --git a/core/cmd/client_test.go b/core/cmd/client_test.go index 96da6363623..e3114526296 100644 --- a/core/cmd/client_test.go +++ b/core/cmd/client_test.go @@ -6,6 +6,7 @@ import ( "github.com/smartcontractkit/chainlink/core/cmd" "github.com/smartcontractkit/chainlink/core/internal/cltest" "github.com/smartcontractkit/chainlink/core/store/models" + "github.com/smartcontractkit/chainlink/core/store/orm" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -23,12 +24,11 @@ func TestTerminalCookieAuthenticator_AuthenticateWithoutSession(t *testing.T) { } for _, test := range tests { t.Run(test.name, func(t *testing.T) { - app, cleanup := cltest.NewApplication(t) - defer cleanup() + config := orm.NewConfig() sr := models.SessionRequest{Email: test.email, Password: test.pwd} store := &cmd.MemoryCookieStore{} - tca := cmd.NewSessionCookieAuthenticator(app.Config.Config, store) + tca := cmd.NewSessionCookieAuthenticator(config, store) cookie, err := tca.Authenticate(sr) assert.Error(t, err) @@ -45,6 +45,7 @@ func TestTerminalCookieAuthenticator_AuthenticateWithSession(t *testing.T) { app, cleanup := cltest.NewApplication(t) defer cleanup() + require.NoError(t, app.Start()) app.MustSeedUserSession() tests := []struct { diff --git a/core/cmd/key_store_authenticator_test.go b/core/cmd/key_store_authenticator_test.go index 0009df018ce..fcd95b1c95b 100644 --- a/core/cmd/key_store_authenticator_test.go +++ b/core/cmd/key_store_authenticator_test.go @@ -11,57 +11,64 @@ import ( func TestTerminalKeyStoreAuthenticator_WithNoAcctNoPwdCreatesAccount(t *testing.T) { t.Parallel() - app, cleanup := cltest.NewApplication(t) + store, cleanup := cltest.NewStore(t) defer cleanup() - prompt := &cltest.MockCountingPrompter{EnteredStrings: []string{ - cltest.Password, "wrongconfirmation", cltest.Password, cltest.Password, - }} + prompt := &cltest.MockCountingPrompter{ + T: t, + EnteredStrings: []string{ + cltest.Password, + "wrongconfirmation", + cltest.Password, + cltest.Password, + }, + } auth := cmd.TerminalKeyStoreAuthenticator{Prompter: prompt} - assert.False(t, app.Store.KeyStore.HasAccounts()) - _, err := auth.Authenticate(app.Store, "") + assert.False(t, store.KeyStore.HasAccounts()) + _, err := auth.Authenticate(store, "") assert.NoError(t, err) assert.Equal(t, 4, prompt.Count) - assert.Equal(t, 1, len(app.Store.KeyStore.Accounts())) + assert.Len(t, store.KeyStore.Accounts(), 1) } func TestTerminalKeyStoreAuthenticator_WithNoAcctWithInitialPwdCreatesAcct(t *testing.T) { t.Parallel() - app, cleanup := cltest.NewApplication(t) + store, cleanup := cltest.NewStore(t) defer cleanup() - auth := cmd.TerminalKeyStoreAuthenticator{Prompter: &cltest.MockCountingPrompter{}} + auth := cmd.TerminalKeyStoreAuthenticator{Prompter: &cltest.MockCountingPrompter{T: t}} - assert.Equal(t, 0, len(app.Store.KeyStore.Accounts())) - _, err := auth.Authenticate(app.Store, "somepassword") + assert.Len(t, store.KeyStore.Accounts(), 0) + _, err := auth.Authenticate(store, "somepassword") assert.NoError(t, err) - assert.True(t, app.Store.KeyStore.HasAccounts()) - assert.Equal(t, 1, len(app.Store.KeyStore.Accounts())) + assert.True(t, store.KeyStore.HasAccounts()) + assert.Len(t, store.KeyStore.Accounts(), 1) } func TestTerminalKeyStoreAuthenticator_WithAcctNoInitialPwdPromptLoop(t *testing.T) { t.Parallel() - app, cleanup := cltest.NewApplicationWithKey(t) + store, cleanup := cltest.NewStore(t) defer cleanup() // prompt loop tries all in array prompt := &cltest.MockCountingPrompter{ - EnteredStrings: []string{"wrongpassword", cltest.Password}, + T: t, + EnteredStrings: []string{"wrongpassword", cltest.Password, cltest.Password, cltest.Password}, } auth := cmd.TerminalKeyStoreAuthenticator{Prompter: prompt} - _, err := auth.Authenticate(app.Store, "") + _, err := auth.Authenticate(store, "") assert.NoError(t, err) - assert.Equal(t, 2, prompt.Count) + assert.Equal(t, 4, prompt.Count) } func TestTerminalKeyStoreAuthenticator_WithAcctAndPwd(t *testing.T) { t.Parallel() - app, cleanup := cltest.NewApplicationWithKey(t) + store, cleanup := cltest.NewStore(t) defer cleanup() tests := []struct { @@ -74,8 +81,8 @@ func TestTerminalKeyStoreAuthenticator_WithAcctAndPwd(t *testing.T) { for _, test := range tests { t.Run(test.password, func(t *testing.T) { - auth := cmd.TerminalKeyStoreAuthenticator{Prompter: &cltest.MockCountingPrompter{}} - _, err := auth.Authenticate(app.Store, test.password) + auth := cmd.TerminalKeyStoreAuthenticator{Prompter: &cltest.MockCountingPrompter{T: t}} + _, err := auth.Authenticate(store, test.password) assert.Equal(t, test.wantError, err != nil) }) } diff --git a/core/cmd/local_client_test.go b/core/cmd/local_client_test.go index 52b09d8f486..e85129bf959 100644 --- a/core/cmd/local_client_test.go +++ b/core/cmd/local_client_test.go @@ -6,30 +6,39 @@ import ( "path/filepath" "sort" "testing" + "time" "github.com/smartcontractkit/chainlink/core/cmd" "github.com/smartcontractkit/chainlink/core/internal/cltest" + "github.com/smartcontractkit/chainlink/core/internal/mocks" "github.com/smartcontractkit/chainlink/core/logger" - "github.com/smartcontractkit/chainlink/core/store" + strpkg "github.com/smartcontractkit/chainlink/core/store" + "github.com/smartcontractkit/chainlink/core/store/orm" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/urfave/cli" ) func TestClient_RunNodeShowsEnv(t *testing.T) { - config, configCleanup := cltest.NewConfig(t) - defer configCleanup() - config.Set("LINK_CONTRACT_ADDRESS", "0x514910771AF9Ca656af840dff83E8264EcF986CA") - config.Set("CHAINLINK_PORT", 6688) - - app, cleanup := cltest.NewApplicationWithConfigAndKey(t, config) + store, cleanup := cltest.NewStore(t) defer cleanup() + _, err := store.KeyStore.NewAccount(cltest.Password) + require.NoError(t, err) + require.NoError(t, store.KeyStore.Unlock(cltest.Password)) + + store.Config.Set("LINK_CONTRACT_ADDRESS", "0x514910771AF9Ca656af840dff83E8264EcF986CA") + store.Config.Set("CHAINLINK_PORT", 6688) + + app := new(mocks.Application) + app.On("GetStore").Return(store) + app.On("Start").Return(nil) + app.On("Stop").Return(nil) - auth := cltest.CallbackAuthenticator{Callback: func(*store.Store, string) (string, error) { return "", nil }} + auth := cltest.CallbackAuthenticator{Callback: func(*strpkg.Store, string) (string, error) { return "", nil }} runner := cltest.BlockedRunner{Done: make(chan struct{})} client := cmd.Client{ - Config: app.Store.Config, - AppFactory: cltest.InstanceAppFactory{App: app.ChainlinkApplication}, + Config: store.Config, + AppFactory: cltest.InstanceAppFactory{App: app}, KeyStoreAuthenticator: auth, FallbackAPIInitializer: &cltest.MockAPIInitializer{}, Runner: runner, @@ -39,23 +48,20 @@ func TestClient_RunNodeShowsEnv(t *testing.T) { set.Bool("debug", true, "") c := cli.NewContext(nil, set, nil) - eth := app.MockEthCallerSubscriber() - eth.Register("eth_getTransactionCount", `0x1`) - eth.Register("eth_chainId", config.ChainID()) - // Start RunNode in a goroutine, it will block until we resume the runner go func() { assert.NoError(t, client.RunNode(c)) }() - // Wait for RunNode to connect the app - require.NoError(t, app.WaitForConnection()) - // Unlock the runner to the client can begin shutdown - runner.Done <- struct{}{} + select { + case runner.Done <- struct{}{}: + case <-time.After(30 * time.Second): + t.Fatal("Timed out waiting for runner") + } logger.Sync() - logs, err := cltest.ReadLogs(app) + logs, err := cltest.ReadLogs(store.Config) require.NoError(t, err) assert.Contains(t, logs, "LOG_LEVEL: debug\\n") @@ -76,6 +82,8 @@ func TestClient_RunNodeShowsEnv(t *testing.T) { assert.Contains(t, logs, "ORACLE_CONTRACT_ADDRESS: \\n") assert.Contains(t, logs, "ALLOW_ORIGINS: http://localhost:3000,http://localhost:6688\\n") assert.Contains(t, logs, "BRIDGE_RESPONSE_URL: http://localhost:6688\\n") + + app.AssertExpectations(t) } func TestClient_RunNodeWithPasswords(t *testing.T) { @@ -93,13 +101,22 @@ func TestClient_RunNodeWithPasswords(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { - app, cleanup := cltest.NewApplication(t) + store, cleanup := cltest.NewStore(t) defer cleanup() - _, err := app.Store.KeyStore.NewAccount("password") // matches correct_password.txt - assert.NoError(t, err) + _, err := store.KeyStore.NewAccount(cltest.Password) + require.NoError(t, err) + require.NoError(t, store.KeyStore.Unlock(cltest.Password)) + + app := new(mocks.Application) + app.On("GetStore").Return(store) + app.On("Start").Maybe().Return(nil) + app.On("Stop").Maybe().Return(nil) + + _, err = store.KeyStore.NewAccount("password") // matches correct_password.txt + require.NoError(t, err) var unlocked bool - callback := func(store *store.Store, phrase string) (string, error) { + callback := func(store *strpkg.Store, phrase string) (string, error) { err := store.KeyStore.Unlock(phrase) unlocked = err == nil return phrase, err @@ -108,7 +125,7 @@ func TestClient_RunNodeWithPasswords(t *testing.T) { auth := cltest.CallbackAuthenticator{Callback: callback} apiPrompt := &cltest.MockAPIInitializer{} client := cmd.Client{ - Config: app.Store.Config, + Config: store.Config, AppFactory: cltest.InstanceAppFactory{App: app}, KeyStoreAuthenticator: auth, FallbackAPIInitializer: apiPrompt, @@ -119,8 +136,6 @@ func TestClient_RunNodeWithPasswords(t *testing.T) { set.String("password", test.pwdfile, "") c := cli.NewContext(nil, set, nil) - eth := app.MockEthCallerSubscriber() - eth.Register("eth_getTransactionCount", `0x1`) if test.wantUnlocked { assert.NoError(t, client.RunNode(c)) assert.True(t, unlocked) @@ -150,13 +165,24 @@ func TestClient_RunNodeWithAPICredentialsFile(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { - app, cleanup := cltest.NewApplicationWithKey(t) + config := orm.NewConfig() + + store, cleanup := cltest.NewStore(t) defer cleanup() + _, err := store.KeyStore.NewAccount(cltest.Password) + require.NoError(t, err) + require.NoError(t, store.KeyStore.Unlock(cltest.Password)) + + app := new(mocks.Application) + app.On("GetStore").Return(store) + app.On("Start").Maybe().Return(nil) + app.On("Stop").Maybe().Return(nil) - noauth := cltest.CallbackAuthenticator{Callback: func(*store.Store, string) (string, error) { return "", nil }} + callback := func(*strpkg.Store, string) (string, error) { return "", nil } + noauth := cltest.CallbackAuthenticator{Callback: callback} apiPrompt := &cltest.MockAPIInitializer{} client := cmd.Client{ - Config: app.Config.Config, + Config: config, AppFactory: cltest.InstanceAppFactory{App: app}, KeyStoreAuthenticator: noauth, FallbackAPIInitializer: apiPrompt, @@ -167,15 +193,14 @@ func TestClient_RunNodeWithAPICredentialsFile(t *testing.T) { set.String("api", test.apiFile, "") c := cli.NewContext(nil, set, nil) - eth := app.MockEthCallerSubscriber() - eth.Register("eth_getTransactionCount", `0x1`) - if test.wantError { assert.Error(t, client.RunNode(c)) } else { assert.NoError(t, client.RunNode(c)) } assert.Equal(t, test.wantPrompt, apiPrompt.Count > 0) + + app.AssertExpectations(t) }) } } @@ -185,6 +210,8 @@ func TestClient_ImportKey(t *testing.T) { app, cleanup := cltest.NewApplication(t) defer cleanup() + require.NoError(t, app.Start()) + client, _ := app.NewClientAndRenderer() set := flag.NewFlagSet("import", 0) diff --git a/core/cmd/remote_client.go b/core/cmd/remote_client.go index 5355f342f63..0bc8f7f4bf3 100644 --- a/core/cmd/remote_client.go +++ b/core/cmd/remote_client.go @@ -513,17 +513,6 @@ func (cli *Client) deserializeAPIResponse(resp *http.Response, dst interface{}, return nil } -func (cli *Client) deserializeResponse(resp *http.Response, dst interface{}) error { - b, err := cli.parseResponse(resp) - if err != nil { - return err - } - if err = json.Unmarshal(b, &dst); err != nil { - return cli.errorOut(err) - } - return nil -} - func (cli *Client) parseResponse(resp *http.Response) ([]byte, error) { b, err := parseResponse(resp) if err == errUnauthorized { @@ -560,14 +549,6 @@ func (cli *Client) printResponseBody(resp *http.Response) error { return nil } -func (cli *Client) renderResponse(resp *http.Response, dst interface{}) error { - err := cli.deserializeResponse(resp, dst) - if err != nil { - return cli.errorOut(err) - } - return cli.errorOut(cli.Render(dst)) -} - func (cli *Client) renderAPIResponse(resp *http.Response, dst interface{}) error { var links jsonapi.Links if err := cli.deserializeAPIResponse(resp, dst, &links); err != nil { @@ -639,7 +620,6 @@ func (cli *Client) SetMinimumGasPrice(c *clipkg.Context) error { return cli.errorOut(cli.Render(&patchResponse)) } - // GetConfiguration gets the nodes environment variables func (cli *Client) GetConfiguration(c *clipkg.Context) error { resp, err := cli.HTTP.Get("/v2/config") @@ -649,4 +629,4 @@ func (cli *Client) GetConfiguration(c *clipkg.Context) error { defer resp.Body.Close() cwl := presenters.ConfigWhitelist{} return cli.renderAPIResponse(resp, &cwl) -} \ No newline at end of file +} diff --git a/core/cmd/remote_client_test.go b/core/cmd/remote_client_test.go index f3efebbfce6..63fe40e421a 100644 --- a/core/cmd/remote_client_test.go +++ b/core/cmd/remote_client_test.go @@ -25,6 +25,7 @@ func TestClient_DisplayAccountBalance(t *testing.T) { app, cleanup := cltest.NewApplicationWithKey(t) defer cleanup() + require.NoError(t, app.Start()) ethMock := app.MockEthCallerSubscriber() ethMock.Register("eth_getBalance", "0x0100") @@ -44,6 +45,7 @@ func TestClient_IndexJobSpecs(t *testing.T) { app, cleanup := cltest.NewApplication(t) defer cleanup() + require.NoError(t, app.Start()) j1 := cltest.NewJob() app.Store.CreateJob(&j1) @@ -63,6 +65,7 @@ func TestClient_ShowJobRun_Exists(t *testing.T) { app, cleanup := cltest.NewApplication(t) defer cleanup() + require.NoError(t, app.Start()) j := cltest.NewJobWithWebInitiator() assert.NoError(t, app.Store.CreateJob(&j)) @@ -84,6 +87,7 @@ func TestClient_ShowJobRun_NotFound(t *testing.T) { app, cleanup := cltest.NewApplication(t) defer cleanup() + require.NoError(t, app.Start()) client, r := app.NewClientAndRenderer() @@ -99,25 +103,27 @@ func TestClient_IndexJobRuns(t *testing.T) { app, cleanup := cltest.NewApplication(t) defer cleanup() + require.NoError(t, app.Start()) j := cltest.NewJobWithWebInitiator() assert.NoError(t, app.Store.CreateJob(&j)) - jr0 := cltest.CreateJobRunViaWeb(t, app, j, `{"result":"100"}`) - jr1 := cltest.CreateJobRunViaWeb(t, app, j, `{"result":"105"}`) - jr2 := cltest.CreateJobRunViaWeb(t, app, j, `{"result":"110"}`) + jr0 := j.NewRun(j.Initiators[0]) + jr0.Result.Data = cltest.JSONFromString(t, `{"a":"b"}`) + require.NoError(t, app.Store.CreateJobRun(&jr0)) + jr1 := j.NewRun(j.Initiators[0]) + jr1.Result.Data = cltest.JSONFromString(t, `{"x":"y"}`) + require.NoError(t, app.Store.CreateJobRun(&jr1)) client, r := app.NewClientAndRenderer() require.Nil(t, client.IndexJobRuns(cltest.EmptyCLIContext())) runs := *r.Renders[0].(*[]presenters.JobRun) - require.Equal(t, 3, len(runs)) + require.Len(t, runs, 2) assert.Equal(t, jr0.ID, runs[0].ID) - assert.Equal(t, jr0.Result.ID, runs[0].Result.ID) + assert.JSONEq(t, `{"a":"b"}`, runs[0].Result.Data.String()) assert.Equal(t, jr1.ID, runs[1].ID) - assert.Equal(t, jr1.Result.ID, runs[1].Result.ID) - assert.Equal(t, jr2.ID, runs[2].ID) - assert.Equal(t, jr2.Result.ID, runs[2].Result.ID) + assert.JSONEq(t, `{"x":"y"}`, runs[1].Result.Data.String()) } func TestClient_ShowJobSpec_Exists(t *testing.T) { @@ -125,6 +131,8 @@ func TestClient_ShowJobSpec_Exists(t *testing.T) { app, cleanup := cltest.NewApplication(t) defer cleanup() + require.NoError(t, app.Start()) + job := cltest.NewJob() app.Store.CreateJob(&job) @@ -143,6 +151,7 @@ func TestClient_ShowJobSpec_NotFound(t *testing.T) { app, cleanup := cltest.NewApplication(t) defer cleanup() + require.NoError(t, app.Start()) client, r := app.NewClientAndRenderer() @@ -160,6 +169,8 @@ func TestClient_CreateServiceAgreement(t *testing.T) { app, cleanup := cltest.NewApplicationWithKey(t) defer cleanup() + require.NoError(t, app.Start()) + client, _ := app.NewClientAndRenderer() sa := string(cltest.MustReadFile(t, "testdata/hello_world_agreement.json")) @@ -219,6 +230,7 @@ func TestClient_CreateExternalInitiator(t *testing.T) { t.Run(test.name, func(t *testing.T) { app, cleanup := cltest.NewApplicationWithKey(t) defer cleanup() + require.NoError(t, app.Start()) client, _ := app.NewClientAndRenderer() @@ -256,6 +268,7 @@ func TestClient_CreateExternalInitiator_Errors(t *testing.T) { t.Run(test.name, func(t *testing.T) { app, cleanup := cltest.NewApplicationWithKey(t) defer cleanup() + require.NoError(t, app.Start()) client, _ := app.NewClientAndRenderer() @@ -277,6 +290,8 @@ func TestClient_CreateJobSpec(t *testing.T) { app, cleanup := cltest.NewApplication(t) defer cleanup() + require.NoError(t, app.Start()) + client, _ := app.NewClientAndRenderer() tests := []struct { @@ -311,6 +326,7 @@ func TestClient_ArchiveJobSpec(t *testing.T) { app, cleanup := cltest.NewApplication(t) defer cleanup() + require.NoError(t, app.Start()) job := cltest.NewJob() require.NoError(t, app.Store.CreateJob(&job)) @@ -332,6 +348,8 @@ func TestClient_CreateJobSpec_JSONAPIErrors(t *testing.T) { app, cleanup := cltest.NewApplication(t) defer cleanup() + require.NoError(t, app.Start()) + client, _ := app.NewClientAndRenderer() set := flag.NewFlagSet("create", 0) @@ -348,6 +366,8 @@ func TestClient_CreateJobRun(t *testing.T) { app, cleanup := cltest.NewApplication(t) defer cleanup() + require.NoError(t, app.Start()) + client, _ := app.NewClientAndRenderer() tests := []struct { @@ -395,6 +415,8 @@ func TestClient_CreateBridge(t *testing.T) { app, cleanup := cltest.NewApplication(t) defer cleanup() + require.NoError(t, app.Start()) + client, _ := app.NewClientAndRenderer() tests := []struct { @@ -431,6 +453,8 @@ func TestClient_IndexBridges(t *testing.T) { app, cleanup := cltest.NewApplication(t) defer cleanup() + require.NoError(t, app.Start()) + bt1 := &models.BridgeType{ Name: models.MustNewTaskType("testingbridges1"), URL: cltest.WebURL(t, "https://testing.com/bridges"), @@ -460,6 +484,8 @@ func TestClient_ShowBridge(t *testing.T) { app, cleanup := cltest.NewApplication(t) defer cleanup() + require.NoError(t, app.Start()) + bt := &models.BridgeType{ Name: models.MustNewTaskType("testingbridges1"), URL: cltest.WebURL(t, "https://testing.com/bridges"), @@ -482,6 +508,8 @@ func TestClient_RemoveBridge(t *testing.T) { app, cleanup := cltest.NewApplication(t) defer cleanup() + require.NoError(t, app.Start()) + bt := &models.BridgeType{ Name: models.MustNewTaskType("testingbridges1"), URL: cltest.WebURL(t, "https://testing.com/bridges"), @@ -505,7 +533,7 @@ func TestClient_RemoteLogin(t *testing.T) { app, cleanup := cltest.NewApplication(t) defer cleanup() - app.Start() + require.NoError(t, app.Start()) tests := []struct { name, file string @@ -543,8 +571,7 @@ func TestClient_WithdrawSuccess(t *testing.T) { app, cleanup, _ := setupWithdrawalsApplication(t) defer cleanup() - - assert.NoError(t, app.StartAndConnect()) + require.NoError(t, app.StartAndConnect()) client, _ := app.NewClientAndRenderer() set := flag.NewFlagSet("admin withdraw", 0) @@ -581,8 +608,7 @@ func TestClient_WithdrawFromSpecifiedContractAddress(t *testing.T) { app, cleanup, ethMockCheck := setupWithdrawalsApplication(t) defer cleanup() - - assert.NoError(t, app.StartAndConnect()) + require.NoError(t, app.StartAndConnect()) client, _ := app.NewClientAndRenderer() cliParserRouter := cmd.NewApp(client) @@ -658,7 +684,7 @@ func TestClient_ChangePassword(t *testing.T) { app, cleanup := cltest.NewApplication(t) defer cleanup() - app.Start() + require.NoError(t, app.Start()) enteredStrings := []string{cltest.APIEmail, cltest.Password} prompter := &cltest.MockCountingPrompter{EnteredStrings: enteredStrings} @@ -695,6 +721,7 @@ func TestClient_IndexTransactions(t *testing.T) { app, cleanup := cltest.NewApplicationWithKey(t) defer cleanup() + require.NoError(t, app.Start()) store := app.GetStore() from := cltest.GetAccountAddress(t, store) @@ -729,6 +756,7 @@ func TestClient_ShowTransaction(t *testing.T) { app, cleanup := cltest.NewApplicationWithKey(t) defer cleanup() + require.NoError(t, app.Start()) store := app.GetStore() from := cltest.GetAccountAddress(t, store) @@ -750,6 +778,7 @@ func TestClient_IndexTxAttempts(t *testing.T) { app, cleanup := cltest.NewApplicationWithKey(t) defer cleanup() + require.NoError(t, app.Start()) store := app.GetStore() from := cltest.GetAccountAddress(t, store) @@ -784,7 +813,7 @@ func TestClient_CreateExtraKey(t *testing.T) { app, cleanup := cltest.NewApplication(t) defer cleanup() - app.Start() + require.NoError(t, app.Start()) client, _ := app.NewClientAndRenderer() @@ -804,8 +833,7 @@ func TestClient_SetMinimumGasPrice(t *testing.T) { app, cleanup, _ := setupWithdrawalsApplication(t) defer cleanup() - - assert.NoError(t, app.StartAndConnect()) + require.NoError(t, app.StartAndConnect()) client, _ := app.NewClientAndRenderer() set := flag.NewFlagSet("setgasprice", 0) @@ -832,11 +860,12 @@ func TestClient_GetConfiguration(t *testing.T) { app, cleanup := cltest.NewApplicationWithKey(t) defer cleanup() + require.NoError(t, app.Start()) client, r := app.NewClientAndRenderer() assert.NoError(t, client.GetConfiguration(cltest.EmptyCLIContext())) require.Equal(t, 1, len(r.Renders)) - + cwl := *r.Renders[0].(*presenters.ConfigWhitelist) assert.Equal(t, cwl.Whitelist.BridgeResponseURL, app.Config.BridgeResponseURL().String()) assert.Equal(t, cwl.Whitelist.ChainID, app.Config.ChainID()) diff --git a/core/cmd/renderer_test.go b/core/cmd/renderer_test.go index de42ee6a1bb..12f7a5f928a 100644 --- a/core/cmd/renderer_test.go +++ b/core/cmd/renderer_test.go @@ -38,12 +38,13 @@ func TestRendererTable_RenderConfiguration(t *testing.T) { app, cleanup := cltest.NewApplicationWithKey(t) defer cleanup() + require.NoError(t, app.Start()) client := app.NewHTTPClient() - + resp, cleanup := client.Get("/v2/config") cwl := presenters.ConfigWhitelist{} require.NoError(t, cltest.ParseJSONAPIResponse(t, resp, &cwl)) - + r := cmd.RendererTable{Writer: ioutil.Discard} assert.NoError(t, r.Render(&cwl)) } diff --git a/core/internal/cltest/cltest.go b/core/internal/cltest/cltest.go index 05c6fac069c..9635c279060 100644 --- a/core/internal/cltest/cltest.go +++ b/core/internal/cltest/cltest.go @@ -140,6 +140,7 @@ type TestApplication struct { Server *httptest.Server wsServer *httptest.Server connectedChannel chan struct{} + Started bool } func newWSServer() (*httptest.Server, func()) { @@ -245,6 +246,13 @@ func (ta *TestApplication) NewBox() packr.Box { return packr.NewBox("../fixtures/operator_ui/dist") } +func (ta *TestApplication) Start() error { + ta.t.Helper() + ta.Started = true + + return ta.ChainlinkApplication.Start() +} + func (ta *TestApplication) StartAndConnect() error { ta.t.Helper() @@ -283,6 +291,12 @@ func (ta *TestApplication) MockStartAndConnect() (*EthMock, error) { // Stop will stop the test application and perform cleanup func (ta *TestApplication) Stop() error { + ta.t.Helper() + + if !ta.Started { + ta.t.Fatal("TestApplication Stop() called on an unstarted application") + } + // TODO: Here we double close, which is less than ideal. // We would prefer to invoke a method on an interface that // cleans up only in test. @@ -401,17 +415,6 @@ func cleanUpStore(t testing.TB, store *strpkg.Store) { require.NoError(t, store.Close()) } -// NewJobSubscriber creates a new JobSubscriber -func NewJobSubscriber(t testing.TB) (*strpkg.Store, services.JobSubscriber, func()) { - t.Helper() - - store, cl := NewStore(t) - nl := services.NewJobSubscriber(store) - return store, nl, func() { - cl() - } -} - func ParseJSON(t testing.TB, body io.Reader) models.JSON { t.Helper() @@ -521,8 +524,8 @@ func ParseJSONAPIResponseMetaCount(input []byte) (int, error) { } // ReadLogs returns the contents of the applications log file as a string -func ReadLogs(app *TestApplication) (string, error) { - logFile := fmt.Sprintf("%s/log.jsonl", app.Store.Config.RootDir()) +func ReadLogs(config orm.ConfigReader) (string, error) { + logFile := fmt.Sprintf("%s/log.jsonl", config.RootDir()) b, err := ioutil.ReadFile(logFile) return string(b), err } @@ -731,17 +734,6 @@ func WaitForJobRunToPendConfirmations( return WaitForJobRunStatus(t, store, jr, models.RunStatusPendingConfirmations) } -// WaitForJobRunToPendSleep waits for a JobRun to reach PendingBridge Status -func WaitForJobRunToPendSleep( - t testing.TB, - store *strpkg.Store, - jr models.JobRun, -) models.JobRun { - t.Helper() - - return WaitForJobRunStatus(t, store, jr, models.RunStatusPendingSleep) -} - // WaitForJobRunStatus waits for a JobRun to reach given status func WaitForJobRunStatus( t testing.TB, @@ -925,11 +917,6 @@ func Head(val interface{}) *models.Head { } } -// NewBlockHeader return a new BlockHeader with given number -func NewBlockHeader(number int) *models.BlockHeader { - return &models.BlockHeader{Number: BigHexInt(number)} -} - // GetAccountAddress returns Address of the account in the keystore of the passed in store func GetAccountAddress(t testing.TB, store *strpkg.Store) common.Address { t.Helper() @@ -1141,16 +1128,6 @@ func MustParseURL(input string) *url.URL { return u } -func NewRunInput(value models.JSON) models.RunInput { - jobRunID := models.NewID() - return *models.NewRunInput(jobRunID, value, models.RunStatusUnstarted) -} - -func NewRunInputWithResult(value interface{}) models.RunInput { - jobRunID := models.NewID() - return *models.NewRunInputWithResult(jobRunID, value, models.RunStatusUnstarted) -} - func MustResultString(t *testing.T, input models.RunResult) string { result := input.Data.Get("result") require.Equal(t, gjson.String, result.Type, fmt.Sprintf("result type %s is not string", result.Type)) diff --git a/core/internal/cltest/factories.go b/core/internal/cltest/factories.go index 160afd26276..d09bf57bb08 100644 --- a/core/internal/cltest/factories.go +++ b/core/internal/cltest/factories.go @@ -19,7 +19,6 @@ import ( "github.com/ethereum/go-ethereum/core/types" "github.com/smartcontractkit/chainlink/core/adapters" "github.com/smartcontractkit/chainlink/core/logger" - "github.com/smartcontractkit/chainlink/core/services" "github.com/smartcontractkit/chainlink/core/store" strpkg "github.com/smartcontractkit/chainlink/core/store" "github.com/smartcontractkit/chainlink/core/store/assets" @@ -221,30 +220,6 @@ func WebURL(t testing.TB, unparsed string) models.WebURL { return models.WebURL(*parsed) } -// NullString creates null.String from given value -func NullString(val interface{}) null.String { - switch val.(type) { - case string: - return null.StringFrom(val.(string)) - case nil: - return null.NewString("", false) - default: - panic("cannot create a null string of any type other than string or nil") - } -} - -// NullTime creates a null.Time from given value -func NullTime(t testing.TB, val interface{}) null.Time { - switch val.(type) { - case string: - return ParseNullableTime(t, val.(string)) - case nil: - return null.NewTime(time.Unix(0, 0), false) - default: - panic("cannot create a null time of any type other than string or nil") - } -} - // JSONFromString create JSON from given body and arguments func JSONFromString(t testing.TB, body string, args ...interface{}) models.JSON { return JSONFromBytes(t, []byte(fmt.Sprintf(body, args...))) @@ -446,11 +421,6 @@ func MarkJobRunPendingBridge(jr models.JobRun, i int) models.JobRun { return jr } -func NewJobRunner(s *strpkg.Store) (services.JobRunner, func()) { - rm := services.NewJobRunner(s) - return rm, func() { rm.Stop() } -} - type MockSigner struct{} func (s MockSigner) SignHash(common.Hash) (models.Signature, error) { @@ -521,3 +491,19 @@ func CreateServiceAgreementViaWeb( return FindServiceAgreement(t, app.Store, responseSA.ID) } + +func NewRunInput(value models.JSON) models.RunInput { + jobRunID := models.NewID() + return *models.NewRunInput(jobRunID, value, models.RunStatusUnstarted) +} + +func NewRunInputWithString(t testing.TB, value string) models.RunInput { + jobRunID := models.NewID() + data := JSONFromString(t, value) + return *models.NewRunInput(jobRunID, data, models.RunStatusUnstarted) +} + +func NewRunInputWithResult(value interface{}) models.RunInput { + jobRunID := models.NewID() + return *models.NewRunInputWithResult(jobRunID, value, models.RunStatusUnstarted) +} diff --git a/core/internal/cltest/mocks.go b/core/internal/cltest/mocks.go index 9077cae4650..f9b698d50bd 100644 --- a/core/internal/cltest/mocks.go +++ b/core/internal/cltest/mocks.go @@ -92,7 +92,6 @@ func (mock *EthMock) ShouldCall(setup func(mock *EthMock)) ethMockDuring { type ethMockDuring struct { mock *EthMock - t testing.TB } func (emd ethMockDuring) During(action func()) { @@ -374,47 +373,10 @@ func (ta *TestApplication) InstantClock() InstantClock { return clock } -// UseSettableClock creates a SettableClock on the store -func UseSettableClock(s *store.Store) *SettableClock { - clock := &SettableClock{} - s.Clock = clock - return clock -} - -// SettableClock a settable clock -type SettableClock struct { - mutex sync.Mutex - time time.Time -} - -// Now get the current time -func (clock *SettableClock) Now() time.Time { - clock.mutex.Lock() - defer clock.mutex.Unlock() - if clock.time.IsZero() { - return time.Now() - } - return clock.time -} - -// SetTime set the current time -func (clock *SettableClock) SetTime(t time.Time) { - clock.mutex.Lock() - defer clock.mutex.Unlock() - clock.time = t -} - -// After return channel of time -func (*SettableClock) After(_ time.Duration) <-chan time.Time { - channel := make(chan time.Time, 1) - channel <- time.Now() - return channel -} - // InstantClock an InstantClock type InstantClock struct{} -// Now current local time +// Now returns the current local time func (InstantClock) Now() time.Time { return time.Now() } @@ -451,24 +413,16 @@ func (t *TriggerClock) Trigger() { } } +// Now returns the current local time +func (t TriggerClock) Now() time.Time { + return time.Now() +} + // After waits on a manual trigger. func (t *TriggerClock) After(_ time.Duration) <-chan time.Time { return t.triggers } -// NeverClock a never clock -type NeverClock struct{} - -// After return channel of time -func (NeverClock) After(_ time.Duration) <-chan time.Time { - return make(chan time.Time) -} - -// Now returns current local time -func (NeverClock) Now() time.Time { - return time.Now() -} - // RendererMock a mock renderer type RendererMock struct { Renders []interface{} @@ -537,6 +491,7 @@ func (r EmptyRunner) Run(app services.Application) error { // MockCountingPrompter is a mock counting prompt type MockCountingPrompter struct { + T *testing.T EnteredStrings []string Count int NotTerminal bool @@ -546,6 +501,10 @@ type MockCountingPrompter struct { func (p *MockCountingPrompter) Prompt(string) string { i := p.Count p.Count++ + if len(p.EnteredStrings)-1 < i { + p.T.Errorf("Not enough passwords supplied to MockCountingPrompter, wanted %d", i) + p.T.FailNow() + } return p.EnteredStrings[i] } @@ -553,6 +512,10 @@ func (p *MockCountingPrompter) Prompt(string) string { func (p *MockCountingPrompter) PasswordPrompt(string) string { i := p.Count p.Count++ + if len(p.EnteredStrings)-1 < i { + p.T.Errorf("Not enough passwords supplied to MockCountingPrompter, wanted %d", i) + p.T.FailNow() + } return p.EnteredStrings[i] } @@ -737,28 +700,6 @@ func (m mockSecretGenerator) Generate(orm.Config) ([]byte, error) { return []byte(SessionSecret), nil } -type MockRunChannel struct { - Runs []models.RunResult - neverReturningChan chan store.RunRequest -} - -func NewMockRunChannel() *MockRunChannel { - return &MockRunChannel{ - neverReturningChan: make(chan store.RunRequest, 1), - } -} - -func (m *MockRunChannel) Send(jobRunID *models.ID) error { - m.Runs = append(m.Runs, models.RunResult{}) - return nil -} - -func (m *MockRunChannel) Receive() <-chan store.RunRequest { - return m.neverReturningChan -} - -func (m *MockRunChannel) Close() {} - // extractERC20BalanceTargetAddress returns the address whose balance is being // queried by the message in the given call to an ERC20 contract, which is // interpreted as a callArgs. diff --git a/core/internal/features_test.go b/core/internal/features_test.go index 8511c83bef7..b13ed66b4b1 100644 --- a/core/internal/features_test.go +++ b/core/internal/features_test.go @@ -1,7 +1,6 @@ package internal_test import ( - "bytes" "encoding/json" "fmt" "io" @@ -13,7 +12,6 @@ import ( "time" "github.com/ethereum/go-ethereum/common" - "github.com/onsi/gomega" "github.com/smartcontractkit/chainlink/core/internal/cltest" "github.com/smartcontractkit/chainlink/core/store/models" "github.com/smartcontractkit/chainlink/core/utils" @@ -364,35 +362,6 @@ func TestIntegration_RunLog(t *testing.T) { } } -func TestIntegration_EndAt(t *testing.T) { - t.Parallel() - - app, cleanup := cltest.NewApplication(t) - defer cleanup() - eth := app.MockEthCallerSubscriber(cltest.Strict) - eth.Register("eth_chainId", app.Store.Config.ChainID()) - clock := cltest.UseSettableClock(app.Store) - app.Start() - client := app.NewHTTPClient() - - j := cltest.FixtureCreateJobViaWeb(t, app, "fixtures/web/end_at_job.json") - endAt := cltest.ParseISO8601(t, "3000-01-01T00:00:00.000Z") - assert.Equal(t, endAt, j.EndAt.Time) - - cltest.CreateJobRunViaWeb(t, app, j) - - clock.SetTime(endAt.Add(time.Nanosecond)) - - resp, cleanup := client.Post("/v2/specs/"+j.ID.String()+"/runs", &bytes.Buffer{}) - defer cleanup() - assert.Equal(t, http.StatusInternalServerError, resp.StatusCode) - gomega.NewGomegaWithT(t).Consistently(func() []models.JobRun { - jobRuns, err := app.Store.JobRunsFor(j.ID) - assert.NoError(t, err) - return jobRuns - }).Should(gomega.HaveLen(1)) -} - func TestIntegration_StartAt(t *testing.T) { t.Parallel() @@ -400,21 +369,12 @@ func TestIntegration_StartAt(t *testing.T) { defer cleanup() eth := app.MockEthCallerSubscriber(cltest.Strict) eth.Register("eth_chainId", app.Store.Config.ChainID()) - clock := cltest.UseSettableClock(app.Store) app.Start() - client := app.NewHTTPClient() j := cltest.FixtureCreateJobViaWeb(t, app, "fixtures/web/start_at_job.json") - startAt := cltest.ParseISO8601(t, "3000-01-01T00:00:00.000Z") + startAt := cltest.ParseISO8601(t, "1970-01-01T00:00:00.000Z") assert.Equal(t, startAt, j.StartAt.Time) - resp, cleanup := client.Post("/v2/specs/"+j.ID.String()+"/runs", &bytes.Buffer{}) - defer cleanup() - assert.Equal(t, http.StatusInternalServerError, resp.StatusCode) - cltest.WaitForRuns(t, j, app.Store, 0) - - clock.SetTime(startAt) - cltest.CreateJobRunViaWeb(t, app, j) } @@ -748,10 +708,9 @@ func TestIntegration_SyncJobRuns(t *testing.T) { eth.Register("eth_chainId", config.ChainID()) app.InstantClock() - app.Store.StatsPusher.Period = 300 * time.Millisecond - app.Start() + j := cltest.FixtureCreateJobViaWeb(t, app, "fixtures/web/run_at_job.json") cltest.CallbackOrTimeout(t, "stats pusher connects", func() { @@ -784,8 +743,8 @@ func TestIntegration_SleepAdapter(t *testing.T) { runInput := fmt.Sprintf("{\"until\": \"%s\"}", time.Now().Local().Add(time.Second*time.Duration(sleepSeconds))) jr := cltest.CreateJobRunViaWeb(t, app, j, runInput) - cltest.WaitForJobRunToPendSleep(t, app.Store, jr) - cltest.JobRunStays(t, app.Store, jr, models.RunStatusPendingSleep, time.Second) + cltest.WaitForJobRunStatus(t, app.Store, jr, models.RunStatusInProgress) + cltest.JobRunStays(t, app.Store, jr, models.RunStatusInProgress, time.Second) cltest.WaitForJobRunToComplete(t, app.Store, jr) } diff --git a/core/internal/fixtures/web/start_at_job.json b/core/internal/fixtures/web/start_at_job.json index be92a87267b..85dc13084ce 100644 --- a/core/internal/fixtures/web/start_at_job.json +++ b/core/internal/fixtures/web/start_at_job.json @@ -1,5 +1,5 @@ { "initiators": [ { "type": "web" } ], "tasks": [ { "type": "NoOp" } ], - "startAt": "3000-01-01T00:00:00.000Z" + "startAt": "1970-01-01T00:00:00.000Z" } diff --git a/core/internal/mocks/application.go b/core/internal/mocks/application.go new file mode 100644 index 00000000000..66eece42137 --- /dev/null +++ b/core/internal/mocks/application.go @@ -0,0 +1,236 @@ +// Code generated by mockery v1.0.0. DO NOT EDIT. + +package mocks + +import big "math/big" +import mock "github.com/stretchr/testify/mock" +import models "github.com/smartcontractkit/chainlink/core/store/models" +import packr "github.com/gobuffalo/packr" + +import store "github.com/smartcontractkit/chainlink/core/store" + +// Application is an autogenerated mock type for the Application type +type Application struct { + mock.Mock +} + +// AddJob provides a mock function with given fields: job +func (_m *Application) AddJob(job models.JobSpec) error { + ret := _m.Called(job) + + var r0 error + if rf, ok := ret.Get(0).(func(models.JobSpec) error); ok { + r0 = rf(job) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// AddServiceAgreement provides a mock function with given fields: _a0 +func (_m *Application) AddServiceAgreement(_a0 *models.ServiceAgreement) error { + ret := _m.Called(_a0) + + var r0 error + if rf, ok := ret.Get(0).(func(*models.ServiceAgreement) error); ok { + r0 = rf(_a0) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// ArchiveJob provides a mock function with given fields: _a0 +func (_m *Application) ArchiveJob(_a0 *models.ID) error { + ret := _m.Called(_a0) + + var r0 error + if rf, ok := ret.Get(0).(func(*models.ID) error); ok { + r0 = rf(_a0) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// Cancel provides a mock function with given fields: runID +func (_m *Application) Cancel(runID *models.ID) error { + ret := _m.Called(runID) + + var r0 error + if rf, ok := ret.Get(0).(func(*models.ID) error); ok { + r0 = rf(runID) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// Create provides a mock function with given fields: jobSpecID, initiator, data, creationHeight, runRequest +func (_m *Application) Create(jobSpecID *models.ID, initiator *models.Initiator, data *models.JSON, creationHeight *big.Int, runRequest *models.RunRequest) (*models.JobRun, error) { + ret := _m.Called(jobSpecID, initiator, data, creationHeight, runRequest) + + var r0 *models.JobRun + if rf, ok := ret.Get(0).(func(*models.ID, *models.Initiator, *models.JSON, *big.Int, *models.RunRequest) *models.JobRun); ok { + r0 = rf(jobSpecID, initiator, data, creationHeight, runRequest) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*models.JobRun) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(*models.ID, *models.Initiator, *models.JSON, *big.Int, *models.RunRequest) error); ok { + r1 = rf(jobSpecID, initiator, data, creationHeight, runRequest) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// CreateErrored provides a mock function with given fields: jobSpecID, initiator, err +func (_m *Application) CreateErrored(jobSpecID *models.ID, initiator models.Initiator, err error) (*models.JobRun, error) { + ret := _m.Called(jobSpecID, initiator, err) + + var r0 *models.JobRun + if rf, ok := ret.Get(0).(func(*models.ID, models.Initiator, error) *models.JobRun); ok { + r0 = rf(jobSpecID, initiator, err) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*models.JobRun) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(*models.ID, models.Initiator, error) error); ok { + r1 = rf(jobSpecID, initiator, err) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetStore provides a mock function with given fields: +func (_m *Application) GetStore() *store.Store { + ret := _m.Called() + + var r0 *store.Store + if rf, ok := ret.Get(0).(func() *store.Store); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*store.Store) + } + } + + return r0 +} + +// NewBox provides a mock function with given fields: +func (_m *Application) NewBox() packr.Box { + ret := _m.Called() + + var r0 packr.Box + if rf, ok := ret.Get(0).(func() packr.Box); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(packr.Box) + } + + return r0 +} + +// ResumeAllConfirming provides a mock function with given fields: currentBlockHeight +func (_m *Application) ResumeAllConfirming(currentBlockHeight *big.Int) error { + ret := _m.Called(currentBlockHeight) + + var r0 error + if rf, ok := ret.Get(0).(func(*big.Int) error); ok { + r0 = rf(currentBlockHeight) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// ResumeAllConnecting provides a mock function with given fields: +func (_m *Application) ResumeAllConnecting() error { + ret := _m.Called() + + var r0 error + if rf, ok := ret.Get(0).(func() error); ok { + r0 = rf() + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// ResumeAllInProgress provides a mock function with given fields: +func (_m *Application) ResumeAllInProgress() error { + ret := _m.Called() + + var r0 error + if rf, ok := ret.Get(0).(func() error); ok { + r0 = rf() + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// ResumePending provides a mock function with given fields: runID, input +func (_m *Application) ResumePending(runID *models.ID, input models.BridgeRunResult) error { + ret := _m.Called(runID, input) + + var r0 error + if rf, ok := ret.Get(0).(func(*models.ID, models.BridgeRunResult) error); ok { + r0 = rf(runID, input) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// Start provides a mock function with given fields: +func (_m *Application) Start() error { + ret := _m.Called() + + var r0 error + if rf, ok := ret.Get(0).(func() error); ok { + r0 = rf() + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// Stop provides a mock function with given fields: +func (_m *Application) Stop() error { + ret := _m.Called() + + var r0 error + if rf, ok := ret.Get(0).(func() error); ok { + r0 = rf() + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// WakeSessionReaper provides a mock function with given fields: +func (_m *Application) WakeSessionReaper() { + _m.Called() +} diff --git a/core/internal/mocks/run_executor.go b/core/internal/mocks/run_executor.go new file mode 100644 index 00000000000..977fa6143f8 --- /dev/null +++ b/core/internal/mocks/run_executor.go @@ -0,0 +1,25 @@ +// Code generated by mockery v1.0.0. DO NOT EDIT. + +package mocks + +import mock "github.com/stretchr/testify/mock" +import models "github.com/smartcontractkit/chainlink/core/store/models" + +// RunExecutor is an autogenerated mock type for the RunExecutor type +type RunExecutor struct { + mock.Mock +} + +// Execute provides a mock function with given fields: _a0 +func (_m *RunExecutor) Execute(_a0 *models.ID) error { + ret := _m.Called(_a0) + + var r0 error + if rf, ok := ret.Get(0).(func(*models.ID) error); ok { + r0 = rf(_a0) + } else { + r0 = ret.Error(0) + } + + return r0 +} diff --git a/core/internal/mocks/run_manager.go b/core/internal/mocks/run_manager.go new file mode 100644 index 00000000000..0503bc9e9fb --- /dev/null +++ b/core/internal/mocks/run_manager.go @@ -0,0 +1,128 @@ +// Code generated by mockery v1.0.0. DO NOT EDIT. + +package mocks + +import big "math/big" +import mock "github.com/stretchr/testify/mock" +import models "github.com/smartcontractkit/chainlink/core/store/models" + +// RunManager is an autogenerated mock type for the RunManager type +type RunManager struct { + mock.Mock +} + +// Cancel provides a mock function with given fields: runID +func (_m *RunManager) Cancel(runID *models.ID) error { + ret := _m.Called(runID) + + var r0 error + if rf, ok := ret.Get(0).(func(*models.ID) error); ok { + r0 = rf(runID) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// Create provides a mock function with given fields: jobSpecID, initiator, data, creationHeight, runRequest +func (_m *RunManager) Create(jobSpecID *models.ID, initiator *models.Initiator, data *models.JSON, creationHeight *big.Int, runRequest *models.RunRequest) (*models.JobRun, error) { + ret := _m.Called(jobSpecID, initiator, data, creationHeight, runRequest) + + var r0 *models.JobRun + if rf, ok := ret.Get(0).(func(*models.ID, *models.Initiator, *models.JSON, *big.Int, *models.RunRequest) *models.JobRun); ok { + r0 = rf(jobSpecID, initiator, data, creationHeight, runRequest) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*models.JobRun) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(*models.ID, *models.Initiator, *models.JSON, *big.Int, *models.RunRequest) error); ok { + r1 = rf(jobSpecID, initiator, data, creationHeight, runRequest) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// CreateErrored provides a mock function with given fields: jobSpecID, initiator, err +func (_m *RunManager) CreateErrored(jobSpecID *models.ID, initiator models.Initiator, err error) (*models.JobRun, error) { + ret := _m.Called(jobSpecID, initiator, err) + + var r0 *models.JobRun + if rf, ok := ret.Get(0).(func(*models.ID, models.Initiator, error) *models.JobRun); ok { + r0 = rf(jobSpecID, initiator, err) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*models.JobRun) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(*models.ID, models.Initiator, error) error); ok { + r1 = rf(jobSpecID, initiator, err) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ResumeAllConfirming provides a mock function with given fields: currentBlockHeight +func (_m *RunManager) ResumeAllConfirming(currentBlockHeight *big.Int) error { + ret := _m.Called(currentBlockHeight) + + var r0 error + if rf, ok := ret.Get(0).(func(*big.Int) error); ok { + r0 = rf(currentBlockHeight) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// ResumeAllConnecting provides a mock function with given fields: +func (_m *RunManager) ResumeAllConnecting() error { + ret := _m.Called() + + var r0 error + if rf, ok := ret.Get(0).(func() error); ok { + r0 = rf() + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// ResumeAllInProgress provides a mock function with given fields: +func (_m *RunManager) ResumeAllInProgress() error { + ret := _m.Called() + + var r0 error + if rf, ok := ret.Get(0).(func() error); ok { + r0 = rf() + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// ResumePending provides a mock function with given fields: runID, input +func (_m *RunManager) ResumePending(runID *models.ID, input models.BridgeRunResult) error { + ret := _m.Called(runID, input) + + var r0 error + if rf, ok := ret.Get(0).(func(*models.ID, models.BridgeRunResult) error); ok { + r0 = rf(runID, input) + } else { + r0 = ret.Error(0) + } + + return r0 +} diff --git a/core/internal/mocks/run_queue.go b/core/internal/mocks/run_queue.go new file mode 100644 index 00000000000..a96f3f315d7 --- /dev/null +++ b/core/internal/mocks/run_queue.go @@ -0,0 +1,49 @@ +// Code generated by mockery v1.0.0. DO NOT EDIT. + +package mocks + +import mock "github.com/stretchr/testify/mock" +import models "github.com/smartcontractkit/chainlink/core/store/models" + +// RunQueue is an autogenerated mock type for the RunQueue type +type RunQueue struct { + mock.Mock +} + +// Run provides a mock function with given fields: _a0 +func (_m *RunQueue) Run(_a0 *models.JobRun) { + _m.Called(_a0) +} + +// Start provides a mock function with given fields: +func (_m *RunQueue) Start() error { + ret := _m.Called() + + var r0 error + if rf, ok := ret.Get(0).(func() error); ok { + r0 = rf() + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// Stop provides a mock function with given fields: +func (_m *RunQueue) Stop() { + _m.Called() +} + +// WorkerCount provides a mock function with given fields: +func (_m *RunQueue) WorkerCount() int { + ret := _m.Called() + + var r0 int + if rf, ok := ret.Get(0).(func() int); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(int) + } + + return r0 +} diff --git a/core/services/application.go b/core/services/application.go index 40174d6887d..71e275be45b 100644 --- a/core/services/application.go +++ b/core/services/application.go @@ -1,7 +1,6 @@ package services import ( - "errors" "os" "os/signal" "sync" @@ -16,6 +15,8 @@ import ( "go.uber.org/multierr" ) +//go:generate mockery -name Application -output ../internal/mocks/ -case=underscore + // Application implements the common functions used in the core node. type Application interface { Start() error @@ -26,15 +27,17 @@ type Application interface { ArchiveJob(*models.ID) error AddServiceAgreement(*models.ServiceAgreement) error NewBox() packr.Box + RunManager } // ChainlinkApplication contains fields for the JobSubscriber, Scheduler, // and Store. The JobSubscriber and Scheduler are also available // in the services package, but the Store has its own package. type ChainlinkApplication struct { - Exiter func(int) - HeadTracker *HeadTracker - JobRunner JobRunner + Exiter func(int) + HeadTracker *HeadTracker + RunManager + RunQueue RunQueue JobSubscriber JobSubscriber Scheduler *Scheduler Store *store.Store @@ -51,13 +54,18 @@ func NewApplication(config *orm.Config, onConnectCallbacks ...func(Application)) store := store.NewStore(config) config.SetRuntimeStore(store.ORM) - jobSubscriber := NewJobSubscriber(store) - pendingConnectionResumer := newPendingConnectionResumer(store) + runExecutor := NewRunExecutor(store) + runQueue := NewRunQueue(runExecutor) + runManager := NewRunManager(runQueue, config, store.ORM, store.TxManager, store.Clock) + jobSubscriber := NewJobSubscriber(store, runManager) + + pendingConnectionResumer := newPendingConnectionResumer(runManager) app := &ChainlinkApplication{ JobSubscriber: jobSubscriber, - JobRunner: NewJobRunner(store), - Scheduler: NewScheduler(store), + RunManager: runManager, + RunQueue: runQueue, + Scheduler: NewScheduler(store, runManager), Store: store, SessionReaper: NewStoreReaper(store), Exiter: os.Exit, @@ -96,15 +104,15 @@ func (app *ChainlinkApplication) Start() error { return multierr.Combine( app.Store.Start(), + app.RunQueue.Start(), + app.RunManager.ResumeAllInProgress(), - // Deliberately started immediately after Store, to start the RunChannel consumer - app.JobRunner.Start(), - app.JobRunner.resumeRunsSinceLastShutdown(), // Started before any other service writes RunStatus to db. - - // HeadTracker deliberately started after JobRunner#resumeRunsSinceLastShutdown - // since it Connects JobSubscriber which leads to writes of JobRuns RunStatus to the db. + // HeadTracker deliberately started after + // RunQueue#resumeRunsSinceLastShutdown since it Connects JobSubscriber + // which leads to writes of JobRuns RunStatus to the db. // https://www.pivotaltracker.com/story/show/162230780 app.HeadTracker.Start(), + app.Scheduler.Start(), app.SessionReaper.Start(), ) @@ -120,7 +128,7 @@ func (app *ChainlinkApplication) Stop() error { app.Scheduler.Stop() merr = multierr.Append(merr, app.HeadTracker.Stop()) - app.JobRunner.Stop() + app.RunQueue.Stop() merr = multierr.Append(merr, app.SessionReaper.Stop()) merr = multierr.Append(merr, app.Store.Close()) }) @@ -175,28 +183,15 @@ func (app *ChainlinkApplication) NewBox() packr.Box { } type pendingConnectionResumer struct { - store *store.Store - resumer func(*models.JobRun, *store.Store) error + runManager RunManager } -func newPendingConnectionResumer(store *store.Store) *pendingConnectionResumer { - return &pendingConnectionResumer{store: store, resumer: ResumeConnectingTask} +func newPendingConnectionResumer(runManager RunManager) *pendingConnectionResumer { + return &pendingConnectionResumer{runManager: runManager} } func (p *pendingConnectionResumer) Connect(head *models.Head) error { - var merr error - err := p.store.UnscopedJobRunsWithStatus(func(run *models.JobRun) { - err := p.resumer(run, p.store.Unscoped()) - if err != nil { - merr = multierr.Append(merr, err) - } - }, models.RunStatusPendingConnection) - - if err != nil { - return multierr.Append(errors.New("error resuming pending connections"), err) - } - - return merr + return p.runManager.ResumeAllConnecting() } func (p *pendingConnectionResumer) Disconnect() {} diff --git a/core/services/application_test.go b/core/services/application_test.go index 5bd7d15a09e..ffa12374b4f 100644 --- a/core/services/application_test.go +++ b/core/services/application_test.go @@ -9,12 +9,9 @@ import ( "github.com/golang/mock/gomock" "github.com/onsi/gomega" "github.com/smartcontractkit/chainlink/core/internal/cltest" - "github.com/smartcontractkit/chainlink/core/services" "github.com/smartcontractkit/chainlink/core/services/mock_services" - strpkg "github.com/smartcontractkit/chainlink/core/store" "github.com/smartcontractkit/chainlink/core/store/models" "github.com/smartcontractkit/chainlink/core/utils" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/tevino/abool" ) @@ -32,7 +29,7 @@ func TestChainlinkApplication_SignalShutdown(t *testing.T) { completed.Set() } - app.Start() + require.NoError(t, app.Start()) syscall.Kill(syscall.Getpid(), syscall.SIGTERM) gomega.NewGomegaWithT(t).Eventually(func() bool { @@ -43,6 +40,8 @@ func TestChainlinkApplication_SignalShutdown(t *testing.T) { func TestChainlinkApplication_AddJob(t *testing.T) { app, cleanup := cltest.NewApplication(t) defer cleanup() + require.NoError(t, app.Start()) + ctrl := gomock.NewController(t) defer ctrl.Finish() @@ -81,29 +80,3 @@ func TestChainlinkApplication_resumesPendingConnection_Archived(t *testing.T) { require.NoError(t, utils.JustError(app.MockStartAndConnect())) _ = cltest.WaitForJobRunToComplete(t, store, jr) } - -func TestPendingConnectionResumer(t *testing.T) { - store, cleanup := cltest.NewStore(t) - defer cleanup() - - resumedRuns := []*models.ID{} - resumer := func(run *models.JobRun, store *strpkg.Store) error { - resumedRuns = append(resumedRuns, run.ID) - return nil - } - pcr := services.ExportedNewPendingConnectionResumer(store, resumer) - - j := cltest.NewJobWithWebInitiator() - require.NoError(t, store.CreateJob(&j)) - - expectedRun := cltest.CreateJobRunWithStatus(t, store, j, models.RunStatusPendingConnection) - _ = cltest.CreateJobRunWithStatus(t, store, j, models.RunStatusPendingConfirmations) - _ = cltest.CreateJobRunWithStatus(t, store, j, models.RunStatusInProgress) - _ = cltest.CreateJobRunWithStatus(t, store, j, models.RunStatusUnstarted) - _ = cltest.CreateJobRunWithStatus(t, store, j, models.RunStatusPendingBridge) - _ = cltest.CreateJobRunWithStatus(t, store, j, models.RunStatusInProgress) - _ = cltest.CreateJobRunWithStatus(t, store, j, models.RunStatusCompleted) - - assert.NoError(t, pcr.Connect(cltest.Head(1))) - assert.Equal(t, []*models.ID{expectedRun.ID}, resumedRuns) -} diff --git a/core/services/export_test.go b/core/services/export_test.go deleted file mode 100644 index 4ab06fafe41..00000000000 --- a/core/services/export_test.go +++ /dev/null @@ -1,35 +0,0 @@ -package services - -import ( - "github.com/smartcontractkit/chainlink/core/store" - "github.com/smartcontractkit/chainlink/core/store/models" -) - -func ExportedExecuteRun( - run *models.JobRun, - store *store.Store, -) error { - return executeRun(run, store) -} - -func ExportedChannelForRun(jr JobRunner, runID *models.ID) chan<- struct{} { - return jr.channelForRun(runID) -} - -func ExportedResumeRunsSinceLastShutdown(jr JobRunner) error { - return jr.resumeRunsSinceLastShutdown() -} - -func ExportedWorkerCount(jr JobRunner) int { - return jr.workerCount() -} - -func ExportedNewPendingConnectionResumer( - store *store.Store, - resumer func(*models.JobRun, *store.Store) error, -) store.HeadTrackable { - return &pendingConnectionResumer{ - store: store, - resumer: resumer, - } -} diff --git a/core/services/head_tracker.go b/core/services/head_tracker.go index c22402424c6..90cac77f7d8 100644 --- a/core/services/head_tracker.go +++ b/core/services/head_tracker.go @@ -239,7 +239,7 @@ func (ht *HeadTracker) subscribeToHead() error { } if err := verifyEthereumChainID(ht); err != nil { - return errors.Wrap(err, "verifyEthereumChainID") + return errors.Wrap(err, "verifyEthereumChainID failed") } ht.headSubscription = sub diff --git a/core/services/job_runner.go b/core/services/job_runner.go deleted file mode 100644 index 06185d0d00f..00000000000 --- a/core/services/job_runner.go +++ /dev/null @@ -1,267 +0,0 @@ -package services - -import ( - "errors" - "fmt" - "sync" - - "github.com/smartcontractkit/chainlink/core/adapters" - "github.com/smartcontractkit/chainlink/core/logger" - "github.com/smartcontractkit/chainlink/core/store" - "github.com/smartcontractkit/chainlink/core/store/models" - "go.uber.org/multierr" -) - -// JobRunner safely handles coordinating job runs. -type JobRunner interface { - Start() error - Stop() - resumeRunsSinceLastShutdown() error - channelForRun(*models.ID) chan<- struct{} - workerCount() int -} - -type jobRunner struct { - started bool - done chan struct{} - bootMutex sync.Mutex - store *store.Store - workerMutex sync.RWMutex - workers map[string]chan struct{} - workersWg sync.WaitGroup - demultiplexStopperWg sync.WaitGroup -} - -// NewJobRunner initializes a JobRunner. -func NewJobRunner(str *store.Store) JobRunner { - return &jobRunner{ - // Unscoped allows the processing of runs that are soft deleted asynchronously - store: str.Unscoped(), - workers: make(map[string]chan struct{}), - } -} - -// Start reinitializes runs and starts the execution of the store's runs. -func (rm *jobRunner) Start() error { - rm.bootMutex.Lock() - defer rm.bootMutex.Unlock() - - if rm.started { - return errors.New("JobRunner already started") - } - rm.done = make(chan struct{}) - rm.started = true - - var starterWg sync.WaitGroup - starterWg.Add(1) - go rm.demultiplexRuns(&starterWg) - starterWg.Wait() - - rm.demultiplexStopperWg.Add(1) - return nil -} - -// Stop closes all open worker channels. -func (rm *jobRunner) Stop() { - rm.bootMutex.Lock() - defer rm.bootMutex.Unlock() - - if !rm.started { - return - } - close(rm.done) - rm.started = false - rm.demultiplexStopperWg.Wait() -} - -// resumeRunsSinceLastShutdown queries the db for job runs that should be resumed -// since a previous node shutdown. -// -// As a result of its reliance on the database, it must run before anything -// persists a job RunStatus to the db to ensure that it only captures pending and in progress -// jobs as a result of the last shutdown, and not as a result of what's happening now. -// -// To recap: This must run before anything else writes job run status to the db, -// ie. tries to run a job. -// https://github.com/smartcontractkit/chainlink/pull/807 -func (rm *jobRunner) resumeRunsSinceLastShutdown() error { - // Do all querying of run statuses since last shutdown before enqueuing - // runs in progress and asleep, to prevent the following race condition: - // 1. resume sleep, 2. awake from sleep, 3. in progress, 4. resume in progress (double enqueued). - var merr error - err := rm.store.UnscopedJobRunsWithStatus(func(run *models.JobRun) { - - if run.Status == models.RunStatusPendingSleep { - if err := QueueSleepingTask(run, rm.store.Unscoped()); err != nil { - logger.Errorw("Error resuming sleeping job", "error", err) - } - } else { - merr = multierr.Append(merr, rm.store.RunChannel.Send(run.ID)) - } - - }, models.RunStatusInProgress, models.RunStatusPendingSleep) - - if err != nil { - return err - } - - return merr -} - -func (rm *jobRunner) demultiplexRuns(starterWg *sync.WaitGroup) { - starterWg.Done() - defer rm.demultiplexStopperWg.Done() - for { - select { - case <-rm.done: - logger.Debug("JobRunner demultiplexing of job runs finished") - rm.workersWg.Wait() - return - case rr, ok := <-rm.store.RunChannel.Receive(): - if !ok { - logger.Panic("RunChannel closed before JobRunner, can no longer demultiplexing job runs") - return - } - rm.channelForRun(rr.ID) <- struct{}{} - } - } -} - -func (rm *jobRunner) channelForRun(runID *models.ID) chan<- struct{} { - rm.workerMutex.Lock() - defer rm.workerMutex.Unlock() - - workerChannel, present := rm.workers[runID.String()] - if !present { - workerChannel = make(chan struct{}, 1) - rm.workers[runID.String()] = workerChannel - rm.workersWg.Add(1) - - go func() { - rm.workerLoop(runID, workerChannel) - - rm.workerMutex.Lock() - delete(rm.workers, runID.String()) - rm.workersWg.Done() - rm.workerMutex.Unlock() - - logger.Debug("Worker finished for ", runID) - }() - } - return workerChannel -} - -func (rm *jobRunner) workerLoop(runID *models.ID, workerChannel chan struct{}) { - for { - select { - case <-workerChannel: - run, err := rm.store.FindJobRun(runID) - if err != nil { - logger.Errorw(fmt.Sprint("Error finding run ", runID), run.ForLogger("error", err)...) - } - - if err := executeRun(&run, rm.store); err != nil { - logger.Errorw(fmt.Sprint("Error executing run ", runID), run.ForLogger("error", err)...) - return - } - - if run.Status.Finished() { - logger.Debugw("All tasks complete for run", "run", run.ID.String()) - return - } - - case <-rm.done: - logger.Debug("JobRunner worker loop for ", runID.String(), " finished") - return - } - } -} - -func (rm *jobRunner) workerCount() int { - rm.workerMutex.RLock() - defer rm.workerMutex.RUnlock() - - return len(rm.workers) -} - -func prepareTaskInput(run *models.JobRun, input models.JSON) (models.JSON, error) { - var err error - if input, err = run.Result.Data.Merge(input); err != nil { - return models.JSON{}, err - } - - if input, err = run.Overrides.Merge(input); err != nil { - return models.JSON{}, err - } - return input, nil -} - -func executeTask(run *models.JobRun, currentTaskRun *models.TaskRun, store *store.Store) models.RunOutput { - taskCopy := currentTaskRun.TaskSpec // deliberately copied to keep mutations local - - var err error - if taskCopy.Params, err = taskCopy.Params.Merge(run.Overrides); err != nil { - return models.NewRunOutputError(err) - } - - adapter, err := adapters.For(taskCopy, store) - if err != nil { - return models.NewRunOutputError(err) - } - - logger.Infow(fmt.Sprintf("Processing task %s", taskCopy.Type), []interface{}{"task", currentTaskRun.ID.String()}...) - - data, err := prepareTaskInput(run, currentTaskRun.Result.Data) - if err != nil { - return models.NewRunOutputError(err) - } - - input := *models.NewRunInput(run.ID, data, currentTaskRun.Status) - result := adapter.Perform(input, store) - - logger.Infow(fmt.Sprintf("Finished processing task %s", taskCopy.Type), []interface{}{ - "task", currentTaskRun.ID, - "result", result.Status(), - "result_data", result.Data(), - }...) - - return result -} - -func executeRun(run *models.JobRun, store *store.Store) error { - logger.Infow("Processing run", run.ForLogger()...) - - if !run.Status.Runnable() { - return fmt.Errorf("Run triggered in non runnable state %s", run.Status) - } - - currentTaskRun := run.NextTaskRun() - if currentTaskRun == nil { - return errors.New("Run triggered with no remaining tasks") - } - - result := executeTask(run, currentTaskRun, store) - - currentTaskRun.ApplyOutput(result) - run.ApplyOutput(result) - - if currentTaskRun.Status.PendingSleep() { - logger.Debugw("Task is sleeping", []interface{}{"run", run.ID.String()}...) - if err := QueueSleepingTask(run, store); err != nil { - return err - } - } else if !currentTaskRun.Status.Runnable() { - logger.Debugw("Task execution blocked", []interface{}{"run", run.ID.String(), "task", currentTaskRun.ID.String(), "state", currentTaskRun.Status}...) - } else if currentTaskRun.Status.Unstarted() { - return fmt.Errorf("run %s task %s cannot return a status of empty string or Unstarted", run.ID.String(), currentTaskRun.TaskSpec.Type) - } else if futureTaskRun := run.NextTaskRun(); futureTaskRun != nil { - validateMinimumConfirmations(run, futureTaskRun, run.ObservedHeight, store) - } - - if err := updateAndTrigger(run, store); err != nil { - return err - } - logger.Infow("Run finished processing", run.ForLogger()...) - - return nil -} diff --git a/core/services/job_runner_test.go b/core/services/job_runner_test.go deleted file mode 100644 index 4fd5ff39f04..00000000000 --- a/core/services/job_runner_test.go +++ /dev/null @@ -1,199 +0,0 @@ -package services_test - -import ( - "fmt" - "testing" - "time" - - "github.com/smartcontractkit/chainlink/core/store/assets" - - "github.com/onsi/gomega" - "github.com/smartcontractkit/chainlink/core/internal/cltest" - "github.com/smartcontractkit/chainlink/core/services" - "github.com/smartcontractkit/chainlink/core/store/models" - "github.com/smartcontractkit/chainlink/core/utils" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestJobRunner_resumeRunsSinceLastShutdown(t *testing.T) { - store, cleanup := cltest.NewStore(t) - defer cleanup() - rm, cleanup := cltest.NewJobRunner(store) - defer cleanup() - - j := models.NewJob() - i := models.Initiator{Type: models.InitiatorWeb} - j.Initiators = []models.Initiator{i} - json := fmt.Sprintf(`{"until":"%v"}`, utils.ISO8601UTC(time.Now().Add(time.Second))) - j.Tasks = []models.TaskSpec{cltest.NewTask(t, "sleep", json)} - assert.NoError(t, store.CreateJob(&j)) - - sleepingRun := j.NewRun(i) - sleepingRun.Status = models.RunStatusPendingSleep - sleepingRun.TaskRuns[0].Status = models.RunStatusPendingSleep - assert.NoError(t, store.CreateJobRun(&sleepingRun)) - - inProgressRun := j.NewRun(i) - inProgressRun.Status = models.RunStatusInProgress - assert.NoError(t, store.CreateJobRun(&inProgressRun)) - - assert.NoError(t, services.ExportedResumeRunsSinceLastShutdown(rm)) - messages := []*models.ID{} - - rr, open := <-store.RunChannel.Receive() - assert.True(t, open) - messages = append(messages, rr.ID) - - rr, open = <-store.RunChannel.Receive() - assert.True(t, open) - messages = append(messages, rr.ID) - - expectedMessages := []*models.ID{sleepingRun.ID, inProgressRun.ID} - assert.ElementsMatch(t, expectedMessages, messages) -} - -func TestJobRunner_executeRun_correctlyPopulatesFinishedAt(t *testing.T) { - store, cleanup := cltest.NewStore(t) - defer cleanup() - - j := models.NewJob() - i := models.Initiator{Type: models.InitiatorWeb} - j.Initiators = []models.Initiator{i} - j.Tasks = []models.TaskSpec{ - cltest.NewTask(t, "noop"), - cltest.NewTask(t, "nooppend"), - } - assert.NoError(t, store.CreateJob(&j)) - - run := j.NewRun(i) - require.NoError(t, store.CreateJobRun(&run)) - - require.NoError(t, services.ExportedExecuteRun(&run, store)) - assert.False(t, run.Result.ErrorMessage.Valid) - assert.False(t, run.FinishedAt.Valid) - assert.Equal(t, models.RunStatusInProgress, run.Status) - - require.NoError(t, services.ExportedExecuteRun(&run, store)) - assert.False(t, run.Result.ErrorMessage.Valid) - assert.False(t, run.FinishedAt.Valid) - assert.Equal(t, models.RunStatusPendingConfirmations, run.Status) -} - -func TestJobRunner_executeRun_correctlyAddsLinkEarnings(t *testing.T) { - store, cleanup := cltest.NewStore(t) - defer cleanup() - - j := models.NewJob() - i := models.Initiator{Type: models.InitiatorWeb} - j.Initiators = []models.Initiator{i} - j.Tasks = []models.TaskSpec{ - cltest.NewTask(t, "noop"), - } - assert.NoError(t, store.CreateJob(&j)) - run := j.NewRun(i) - run.Payment = assets.NewLink(1) - require.NoError(t, store.CreateJobRun(&run)) - require.NoError(t, services.ExportedExecuteRun(&run, store)) - - actual, err := store.LinkEarnedFor(&j) - require.NoError(t, err) - assert.Equal(t, assets.NewLink(1), actual) -} - -func TestJobRunner_ChannelForRun_equalityBetweenRuns(t *testing.T) { - t.Parallel() - - store, cleanup := cltest.NewStore(t) - defer cleanup() - rm, cleanup := cltest.NewJobRunner(store) - defer cleanup() - - job := cltest.NewJobWithWebInitiator() - initr := job.Initiators[0] - run1 := job.NewRun(initr) - run2 := job.NewRun(initr) - - chan1a := services.ExportedChannelForRun(rm, run1.ID) - chan2 := services.ExportedChannelForRun(rm, run2.ID) - chan1b := services.ExportedChannelForRun(rm, run1.ID) - - assert.NotEqual(t, chan1a, chan2) - assert.Equal(t, chan1a, chan1b) - assert.NotEqual(t, chan2, chan1b) -} - -func TestJobRunner_ChannelForRun_sendAfterClosing(t *testing.T) { - t.Parallel() - - s, cleanup := cltest.NewStore(t) - defer cleanup() - rm, cleanup := cltest.NewJobRunner(s) - defer cleanup() - assert.NoError(t, rm.Start()) - - j := cltest.NewJobWithWebInitiator() - assert.NoError(t, s.CreateJob(&j)) - initr := j.Initiators[0] - jr := j.NewRun(initr) - assert.NoError(t, s.CreateJobRun(&jr)) - - chan1 := services.ExportedChannelForRun(rm, jr.ID) - chan1 <- struct{}{} - cltest.WaitForJobRunToComplete(t, s, jr) - - gomega.NewGomegaWithT(t).Eventually(func() chan<- struct{} { - return services.ExportedChannelForRun(rm, jr.ID) - }).Should(gomega.Not(gomega.Equal(chan1))) // eventually deletes the channel - - chan2 := services.ExportedChannelForRun(rm, jr.ID) - chan2 <- struct{}{} // does not panic -} - -func TestJobRunner_ChannelForRun_equalityWithoutClosing(t *testing.T) { - t.Parallel() - - s, cleanup := cltest.NewStore(t) - defer cleanup() - rm, cleanup := cltest.NewJobRunner(s) - defer cleanup() - assert.NoError(t, rm.Start()) - - j := cltest.NewJobWithWebInitiator() - j.Tasks = []models.TaskSpec{cltest.NewTask(t, "nooppend")} - assert.NoError(t, s.CreateJob(&j)) - initr := j.Initiators[0] - jr := j.NewRun(initr) - assert.NoError(t, s.CreateJobRun(&jr)) - - chan1 := services.ExportedChannelForRun(rm, jr.ID) - - chan1 <- struct{}{} - cltest.WaitForJobRunToPendConfirmations(t, s, jr) - - chan2 := services.ExportedChannelForRun(rm, jr.ID) - assert.Equal(t, chan1, chan2) -} - -func TestJobRunner_Stop(t *testing.T) { - t.Parallel() - - s, cleanup := cltest.NewStore(t) - defer cleanup() - rm, cleanup := cltest.NewJobRunner(s) - defer cleanup() - j := cltest.NewJobWithWebInitiator() - initr := j.Initiators[0] - jr := j.NewRun(initr) - - require.NoError(t, rm.Start()) - - services.ExportedChannelForRun(rm, jr.ID) - assert.Equal(t, 1, services.ExportedWorkerCount(rm)) - - rm.Stop() - - gomega.NewGomegaWithT(t).Eventually(func() int { - return services.ExportedWorkerCount(rm) - }).Should(gomega.Equal(0)) -} diff --git a/core/services/job_subscriber.go b/core/services/job_subscriber.go index 6a9361de32d..18e9b991329 100644 --- a/core/services/job_subscriber.go +++ b/core/services/job_subscriber.go @@ -24,12 +24,14 @@ type jobSubscriber struct { store *store.Store jobSubscriptions map[string]JobSubscription jobsMutex *sync.RWMutex + runManager RunManager } // NewJobSubscriber returns a new job subscriber. -func NewJobSubscriber(store *store.Store) JobSubscriber { +func NewJobSubscriber(store *store.Store, runManager RunManager) JobSubscriber { return &jobSubscriber{ store: store, + runManager: runManager, jobSubscriptions: map[string]JobSubscription{}, jobsMutex: &sync.RWMutex{}, } @@ -42,7 +44,7 @@ func (js *jobSubscriber) AddJob(job models.JobSpec, bn *models.Head) error { return nil } - sub, err := StartJobSubscription(job, bn, js.store) + sub, err := StartJobSubscription(job, bn, js.store, js.runManager) if err != nil { return err } @@ -56,6 +58,7 @@ func (js *jobSubscriber) RemoveJob(ID *models.ID) error { sub, ok := js.jobSubscriptions[ID.String()] delete(js.jobSubscriptions, ID.String()) js.jobsMutex.Unlock() + if !ok { return fmt.Errorf("JobSubscriber#RemoveJob: job %s not found", ID) } @@ -67,6 +70,7 @@ func (js *jobSubscriber) RemoveJob(ID *models.ID) error { func (js *jobSubscriber) Jobs() []models.JobSpec { js.jobsMutex.RLock() defer js.jobsMutex.RUnlock() + var jobs []models.JobSpec for _, sub := range js.jobSubscriptions { jobs = append(jobs, sub.Job) @@ -77,14 +81,15 @@ func (js *jobSubscriber) Jobs() []models.JobSpec { func (js *jobSubscriber) addSubscription(sub JobSubscription) { js.jobsMutex.Lock() defer js.jobsMutex.Unlock() + js.jobSubscriptions[sub.Job.ID.String()] = sub } // Connect connects the jobs to the ethereum node by creating corresponding subscriptions. func (js *jobSubscriber) Connect(bn *models.Head) error { var merr error - err := js.store.Jobs(func(j models.JobSpec) bool { - merr = multierr.Append(merr, js.AddJob(j, bn)) + err := js.store.Jobs(func(j *models.JobSpec) bool { + merr = multierr.Append(merr, js.AddJob(*j, bn)) return true }) return multierr.Append(merr, err) @@ -95,6 +100,7 @@ func (js *jobSubscriber) Connect(bn *models.Head) error { func (js *jobSubscriber) Disconnect() { js.jobsMutex.Lock() defer js.jobsMutex.Unlock() + for _, sub := range js.jobSubscriptions { sub.Unsubscribe() } @@ -103,17 +109,8 @@ func (js *jobSubscriber) Disconnect() { // OnNewHead resumes all pending job runs based on the new head activity. func (js *jobSubscriber) OnNewHead(head *models.Head) { - height := head.ToInt() - - err := js.store.UnscopedJobRunsWithStatus(func(run *models.JobRun) { - err := ResumeConfirmingTask(run, js.store.Unscoped(), height) - if err != nil { - logger.Errorf("JobSubscriber.OnNewHead: %v", err) - } - - }, models.RunStatusPendingConnection, models.RunStatusPendingConfirmations) - + err := js.runManager.ResumeAllConfirming(head.ToInt()) if err != nil { - logger.Errorf("error fetching pending job runs: %v", err) + logger.Errorw("Failed to resume confirming tasks on new head", "error", err) } } diff --git a/core/services/job_subscriber_test.go b/core/services/job_subscriber_test.go index ae75c5d4e1e..9b08dc7ce17 100644 --- a/core/services/job_subscriber_test.go +++ b/core/services/job_subscriber_test.go @@ -1,305 +1,111 @@ package services_test import ( - "fmt" "math/big" "testing" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/hexutil" - "github.com/onsi/gomega" "github.com/smartcontractkit/chainlink/core/internal/cltest" + "github.com/smartcontractkit/chainlink/core/internal/mocks" "github.com/smartcontractkit/chainlink/core/services" - strpkg "github.com/smartcontractkit/chainlink/core/store" "github.com/smartcontractkit/chainlink/core/store/models" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) -func TestJobSubscriber_Connect_WithJobs(t *testing.T) { +func TestJobSubscriber_OnNewHead(t *testing.T) { t.Parallel() - store, el, cleanup := cltest.NewJobSubscriber(t) + store, cleanup := cltest.NewStore(t) defer cleanup() - eth := cltest.MockEthOnStore(t, store) - eth.Register("eth_getLogs", []models.Log{}) - eth.Register("eth_getLogs", []models.Log{}) - j1 := cltest.NewJobWithLogInitiator() - j2 := cltest.NewJobWithLogInitiator() - assert.Nil(t, store.CreateJob(&j1)) - assert.Nil(t, store.CreateJob(&j2)) - eth.RegisterSubscription("logs") - eth.RegisterSubscription("logs") + runManager := new(mocks.RunManager) + jobSubscriber := services.NewJobSubscriber(store, runManager) - assert.Nil(t, el.Connect(cltest.Head(1))) - eth.EventuallyAllCalled(t) -} + runManager.On("ResumeAllConfirming", big.NewInt(1337)).Return(nil) + + jobSubscriber.OnNewHead(cltest.Head(1337)) -func newAddr() common.Address { - return cltest.NewAddress() + runManager.AssertExpectations(t) } -func TestJobSubscriber_reconnectLoop_Resubscribing(t *testing.T) { +func TestJobSubscriber_AddJob_RemoveJob(t *testing.T) { t.Parallel() store, cleanup := cltest.NewStore(t) defer cleanup() + cltest.MockEthOnStore(t, store) - eth := cltest.MockEthOnStore(t, store) - eth.Register("eth_getLogs", []models.Log{}) - eth.Register("eth_getLogs", []models.Log{}) - eth.Register("eth_getLogs", []models.Log{}) - eth.Register("eth_getLogs", []models.Log{}) + runManager := new(mocks.RunManager) + jobSubscriber := services.NewJobSubscriber(store, runManager) - j1 := cltest.NewJobWithLogInitiator() - j2 := cltest.NewJobWithLogInitiator() - assert.Nil(t, store.CreateJob(&j1)) - assert.Nil(t, store.CreateJob(&j2)) + jobSpec := cltest.NewJobWithLogInitiator() + err := jobSubscriber.AddJob(jobSpec, cltest.Head(321)) + require.NoError(t, err) - eth.RegisterSubscription("logs") - eth.RegisterSubscription("logs") + assert.Len(t, jobSubscriber.Jobs(), 1) - el := services.NewJobSubscriber(store) - assert.Nil(t, el.Connect(cltest.Head(1))) - assert.Equal(t, 2, len(el.Jobs())) - el.Disconnect() - assert.Equal(t, 0, len(el.Jobs())) + err = jobSubscriber.RemoveJob(jobSpec.ID) + require.NoError(t, err) + + assert.Len(t, jobSubscriber.Jobs(), 0) + + runManager.AssertExpectations(t) - eth.RegisterSubscription("logs") - eth.RegisterSubscription("logs") - assert.Nil(t, el.Connect(cltest.Head(2))) - assert.Equal(t, 2, len(el.Jobs())) - el.Disconnect() - assert.Equal(t, 0, len(el.Jobs())) - eth.EventuallyAllCalled(t) } -func TestJobSubscriber_AttachedToHeadTracker(t *testing.T) { +func TestJobSubscriber_AddJob_NotLogInitiatedError(t *testing.T) { t.Parallel() - g := gomega.NewGomegaWithT(t) - store, el, cleanup := cltest.NewJobSubscriber(t) + store, cleanup := cltest.NewStore(t) defer cleanup() - eth := cltest.MockEthOnStore(t, store) - j1 := cltest.NewJobWithLogInitiator() - j2 := cltest.NewJobWithLogInitiator() - assert.Nil(t, store.CreateJob(&j1)) - assert.Nil(t, store.CreateJob(&j2)) + runManager := new(mocks.RunManager) + jobSubscriber := services.NewJobSubscriber(store, runManager) - eth.RegisterSubscription("logs") - eth.RegisterSubscription("logs") - eth.Register("eth_chainId", store.Config.ChainID()) - - ht := services.NewHeadTracker(store, []strpkg.HeadTrackable{el}) - assert.Nil(t, ht.Start()) - g.Eventually(func() int { return len(el.Jobs()) }).Should(gomega.Equal(2)) - eth.EventuallyAllCalled(t) + job := models.JobSpec{} + err := jobSubscriber.AddJob(job, cltest.Head(1)) + require.NoError(t, err) } -func TestJobSubscriber_AddJob_Listening(t *testing.T) { +func TestJobSubscriber_RemoveJob_NotFoundError(t *testing.T) { t.Parallel() - sharedAddr := newAddr() - noAddr := common.Address{} - - tests := []struct { - name string - initType string - initrAddr common.Address - logAddr common.Address - wantCount int - topic0 common.Hash - data hexutil.Bytes - }{ - {"ethlog matching address", "ethlog", sharedAddr, sharedAddr, 1, common.Hash{}, hexutil.Bytes{}}, - {"ethlog all address", "ethlog", noAddr, newAddr(), 1, common.Hash{}, hexutil.Bytes{}}, - {"runlog v0 matching address", "runlog", sharedAddr, sharedAddr, 1, models.RunLogTopic0original, cltest.StringToVersionedLogData0(t, "id", `{"value":"100"}`)}, - {"runlog v20190123 w/o address", "runlog", noAddr, newAddr(), 1, models.RunLogTopic20190123withFullfillmentParams, cltest.StringToVersionedLogData20190123withFulfillmentParams(t, "id", `{"value":"100"}`)}, - {"runlog v20190123 matching address", "runlog", sharedAddr, sharedAddr, 1, models.RunLogTopic20190123withFullfillmentParams, cltest.StringToVersionedLogData20190123withFulfillmentParams(t, "id", `{"value":"100"}`)}, - {"runlog w non-matching topic", "runlog", sharedAddr, sharedAddr, 0, common.Hash{}, cltest.StringToVersionedLogData20190123withFulfillmentParams(t, "id", `{"value":"100"}`)}, - {"runlog v20190207 w/o address", "runlog", noAddr, newAddr(), 1, models.RunLogTopic20190207withoutIndexes, cltest.StringToVersionedLogData20190207withoutIndexes(t, "id", cltest.NewAddress(), `{"value":"100"}`)}, - {"runlog v20190207 matching address", "runlog", sharedAddr, sharedAddr, 1, models.RunLogTopic20190207withoutIndexes, cltest.StringToVersionedLogData20190207withoutIndexes(t, "id", cltest.NewAddress(), `{"value":"100"}`)}, - {"runlog w non-matching topic", "runlog", sharedAddr, sharedAddr, 0, common.Hash{}, cltest.StringToVersionedLogData20190207withoutIndexes(t, "id", cltest.NewAddress(), `{"value":"100"}`)}, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - store, el, cleanup := cltest.NewJobSubscriber(t) - defer cleanup() - - eth := cltest.MockEthOnStore(t, store) - eth.Register("eth_getLogs", []models.Log{}) - eth.Register("eth_chainId", store.Config.ChainID()) - logChan := make(chan models.Log, 1) - eth.RegisterSubscription("logs", logChan) - - job := cltest.NewJob() - initr := models.Initiator{Type: test.initType} - initr.Address = test.initrAddr - job.Initiators = []models.Initiator{initr} - require.NoError(t, store.CreateJob(&job)) - el.AddJob(job, cltest.Head(1)) - - ht := services.NewHeadTracker(store, []strpkg.HeadTrackable{el}) - require.NoError(t, ht.Start()) - - logChan <- models.Log{ - Address: test.logAddr, - Data: test.data, - Topics: []common.Hash{ - test.topic0, - models.IDToTopic(job.ID), - newAddr().Hash(), - common.BigToHash(big.NewInt(0)), - }, - } - - cltest.WaitForRuns(t, job, store, test.wantCount) - - eth.EventuallyAllCalled(t) - }) - } -} -func TestJobSubscriber_RemoveJob_RunLog(t *testing.T) { - t.Parallel() - - store, el, cleanup := cltest.NewJobSubscriber(t) + store, cleanup := cltest.NewStore(t) defer cleanup() - eth := cltest.MockEthOnStore(t, store) - eth.Register("eth_getLogs", []models.Log{}) - eth.Register("eth_chainId", store.Config.ChainID()) - logChan := make(chan models.Log, 1) - eth.RegisterSubscription("logs", logChan) - - addr := newAddr() - job := cltest.NewJob() - initr := models.Initiator{Type: "runlog"} - initr.Address = addr - job.Initiators = []models.Initiator{initr} - require.NoError(t, store.CreateJob(&job)) - el.AddJob(job, cltest.Head(1)) - require.Len(t, el.Jobs(), 1) - - require.NoError(t, el.RemoveJob(job.ID)) - require.Len(t, el.Jobs(), 0) - - ht := services.NewHeadTracker(store, []strpkg.HeadTrackable{el}) - require.NoError(t, ht.Start()) - - // asserts that JobSubscriber unsubscribed the job specific channel - require.True(t, sendingOnClosedChannel(func() { - logChan <- models.Log{} - })) - - cltest.WaitForRuns(t, job, store, 0) - eth.EventuallyAllCalled(t) + runManager := new(mocks.RunManager) + jobSubscriber := services.NewJobSubscriber(store, runManager) + + err := jobSubscriber.RemoveJob(models.NewID()) + require.Error(t, err) } -func TestJobSubscriber_RemoveJob_EthLog(t *testing.T) { +func TestJobSubscriber_Connect_Disconnect(t *testing.T) { t.Parallel() - store, el, cleanup := cltest.NewJobSubscriber(t) + store, cleanup := cltest.NewStore(t) defer cleanup() + runManager := new(mocks.RunManager) + jobSubscriber := services.NewJobSubscriber(store, runManager) + eth := cltest.MockEthOnStore(t, store) eth.Register("eth_getLogs", []models.Log{}) - eth.Register("eth_chainId", store.Config.ChainID()) - logChan := make(chan models.Log, 1) - eth.RegisterSubscription("logs", logChan) - - addr := newAddr() - job := cltest.NewJob() - initr := models.Initiator{Type: "ethlog"} - initr.Address = addr - job.Initiators = []models.Initiator{initr} - require.NoError(t, store.CreateJob(&job)) - el.AddJob(job, cltest.Head(1)) - require.Len(t, el.Jobs(), 1) - - require.NoError(t, el.RemoveJob(job.ID)) - require.Len(t, el.Jobs(), 0) - - ht := services.NewHeadTracker(store, []strpkg.HeadTrackable{el}) - require.NoError(t, ht.Start()) - - // asserts that JobSubscriber unsubscribed the job specific channel - require.True(t, sendingOnClosedChannel(func() { - logChan <- models.Log{} - })) - - cltest.WaitForRuns(t, job, store, 0) + eth.Register("eth_getLogs", []models.Log{}) + + jobSpec1 := cltest.NewJobWithLogInitiator() + jobSpec2 := cltest.NewJobWithLogInitiator() + assert.Nil(t, store.CreateJob(&jobSpec1)) + assert.Nil(t, store.CreateJob(&jobSpec2)) + eth.RegisterSubscription("logs") + eth.RegisterSubscription("logs") + + assert.Nil(t, jobSubscriber.Connect(cltest.Head(491))) eth.EventuallyAllCalled(t) -} -func sendingOnClosedChannel(callback func()) (rval bool) { - defer func() { - if r := recover(); r != nil { - rerror := r.(error) - rval = rerror.Error() == "send on closed channel" - } - }() - callback() - return false -} + assert.Len(t, jobSubscriber.Jobs(), 2) -func TestJobSubscriber_OnNewHead_ResumePendingConfirmationsAndPendingConnections(t *testing.T) { - t.Parallel() + jobSubscriber.Disconnect() - block := cltest.NewBlockHeader(10) - prettyLabel := func(archived bool, rs models.RunStatus) string { - if archived { - return fmt.Sprintf("archived:%s", string(rs)) - } - return string(rs) - } - - tests := []struct { - status models.RunStatus - archived bool - wantSend bool - }{ - {models.RunStatusPendingConnection, false, true}, - {models.RunStatusPendingConnection, true, true}, - {models.RunStatusPendingConfirmations, false, true}, - {models.RunStatusPendingConfirmations, true, true}, - {models.RunStatusInProgress, false, false}, - {models.RunStatusInProgress, true, false}, - {models.RunStatusPendingBridge, false, false}, - {models.RunStatusPendingBridge, true, false}, - {models.RunStatusPendingSleep, false, false}, - {models.RunStatusPendingSleep, true, false}, - {models.RunStatusCompleted, false, false}, - {models.RunStatusCompleted, true, false}, - } - - for _, test := range tests { - t.Run(prettyLabel(test.archived, test.status), func(t *testing.T) { - store, js, cleanup := cltest.NewJobSubscriber(t) - defer cleanup() - - mockRunChannel := cltest.NewMockRunChannel() - store.RunChannel = mockRunChannel - - job := cltest.NewJobWithWebInitiator() - require.NoError(t, store.CreateJob(&job)) - initr := job.Initiators[0] - run := job.NewRun(initr) - run.Status = test.status - require.NoError(t, store.CreateJobRun(&run)) - - if test.archived { - require.NoError(t, store.ArchiveJob(job.ID)) - } - - js.OnNewHead(block.ToHead()) - if test.wantSend { - assert.Equal(t, 1, len(mockRunChannel.Runs)) - } else { - assert.Equal(t, 0, len(mockRunChannel.Runs)) - } - }) - } + assert.Len(t, jobSubscriber.Jobs(), 0) } diff --git a/core/services/run_executor.go b/core/services/run_executor.go new file mode 100644 index 00000000000..d05e0bc6a26 --- /dev/null +++ b/core/services/run_executor.go @@ -0,0 +1,116 @@ +package services + +import ( + "fmt" + "time" + + "github.com/pkg/errors" + "github.com/smartcontractkit/chainlink/core/adapters" + "github.com/smartcontractkit/chainlink/core/logger" + "github.com/smartcontractkit/chainlink/core/store" + "github.com/smartcontractkit/chainlink/core/store/models" + "github.com/smartcontractkit/chainlink/core/store/orm" +) + +//go:generate mockery -name RunExecutor -output ../internal/mocks/ -case=underscore + +// RunExecutor handles the actual running of the job tasks +type RunExecutor interface { + Execute(*models.ID) error +} + +type runExecutor struct { + store *store.Store +} + +// NewRunExecutor initializes a RunExecutor. +func NewRunExecutor(store *store.Store) RunExecutor { + return &runExecutor{ + store: store, + } +} + +// Execute performs the work associate with a job run +func (je *runExecutor) Execute(runID *models.ID) error { + run, err := je.store.Unscoped().FindJobRun(runID) + if err != nil { + return fmt.Errorf("Error finding run %s", runID.String()) + } + + if !run.Status.Runnable() { + return fmt.Errorf("Run triggered in non runnable state %s", run.Status) + } + + for run.Status.Runnable() { + currentTaskRun := run.NextTaskRun() + if currentTaskRun == nil { + return errors.New("Run triggered with no remaining tasks") + } + + result := je.executeTask(&run, currentTaskRun) + + currentTaskRun.ApplyOutput(result) + run.ApplyOutput(result) + + if !result.Status().Runnable() { + logger.Debugw("Task execution blocked", []interface{}{"run", run.ID, "task", currentTaskRun.ID.String(), "state", currentTaskRun.Status}...) + } else if currentTaskRun.Status.Unstarted() { + return fmt.Errorf("run %s task %s cannot return a status of empty string or Unstarted", run.ID, currentTaskRun.TaskSpec.Type) + } else if futureTaskRun := run.NextTaskRun(); futureTaskRun != nil { + validateMinimumConfirmations(&run, futureTaskRun, run.ObservedHeight, je.store.TxManager) + } + + if err := je.store.ORM.SaveJobRun(&run); errors.Cause(err) == orm.OptimisticUpdateConflictError { + return nil + } else if err != nil { + return err + } + + if run.Status.Finished() { + logger.Debugw("All tasks complete for run", run.ForLogger()...) + break + } + } + + return nil +} + +func (je *runExecutor) executeTask(run *models.JobRun, currentTaskRun *models.TaskRun) models.RunOutput { + taskCopy := currentTaskRun.TaskSpec // deliberately copied to keep mutations local + + var err error + if taskCopy.Params, err = taskCopy.Params.Merge(run.Overrides); err != nil { + return models.NewRunOutputError(err) + } + + adapter, err := adapters.For(taskCopy, je.store.Config, je.store.ORM) + if err != nil { + return models.NewRunOutputError(err) + } + + previousTaskRun := run.PreviousTaskRun() + + data := models.JSON{} + if previousTaskRun != nil { + if data, err = previousTaskRun.Result.Data.Merge(currentTaskRun.Result.Data); err != nil { + return models.NewRunOutputError(err) + } + } + + if data, err = run.Overrides.Merge(data); err != nil { + return models.NewRunOutputError(err) + } + + input := *models.NewRunInput(run.ID, data, currentTaskRun.Status) + + start := time.Now() + result := adapter.Perform(input, je.store) + logger.Debugw(fmt.Sprintf("Executed task %s", taskCopy.Type), []interface{}{ + "task", currentTaskRun.ID.String(), + "result", result.Status(), + "result_data", result.Data(), + "elapsed", time.Since(start).Seconds(), + }...) + + return result +} diff --git a/core/services/run_executor_test.go b/core/services/run_executor_test.go new file mode 100644 index 00000000000..34122cdf986 --- /dev/null +++ b/core/services/run_executor_test.go @@ -0,0 +1,171 @@ +package services_test + +import ( + "testing" + "time" + + "github.com/smartcontractkit/chainlink/core/internal/cltest" + "github.com/smartcontractkit/chainlink/core/internal/mocks" + "github.com/smartcontractkit/chainlink/core/services" + "github.com/smartcontractkit/chainlink/core/store/assets" + "github.com/smartcontractkit/chainlink/core/store/models" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestRunExecutor_Execute(t *testing.T) { + t.Parallel() + + store, cleanup := cltest.NewStore(t) + defer cleanup() + + runExecutor := services.NewRunExecutor(store) + + j := models.NewJob() + i := models.Initiator{Type: models.InitiatorWeb} + j.Initiators = []models.Initiator{i} + j.Tasks = []models.TaskSpec{ + cltest.NewTask(t, "noop"), + } + assert.NoError(t, store.CreateJob(&j)) + + run := j.NewRun(i) + run.Payment = assets.NewLink(9117) + require.NoError(t, store.CreateJobRun(&run)) + + err := runExecutor.Execute(run.ID) + require.NoError(t, err) + + run, err = store.FindJobRun(run.ID) + require.NoError(t, err) + assert.Equal(t, models.RunStatusCompleted, run.Status) + require.Len(t, run.TaskRuns, 1) + assert.Equal(t, models.RunStatusCompleted, run.TaskRuns[0].Status) + + actual, err := store.LinkEarnedFor(&j) + require.NoError(t, err) + assert.Equal(t, assets.NewLink(9117), actual) +} + +func TestRunExecutor_Execute_Pending(t *testing.T) { + t.Parallel() + + store, cleanup := cltest.NewStore(t) + defer cleanup() + + runExecutor := services.NewRunExecutor(store) + + j := models.NewJob() + i := models.Initiator{Type: models.InitiatorWeb} + j.Initiators = []models.Initiator{i} + j.Tasks = []models.TaskSpec{ + cltest.NewTask(t, "noop"), + cltest.NewTask(t, "nooppend"), + } + assert.NoError(t, store.CreateJob(&j)) + + run := j.NewRun(i) + run.Payment = assets.NewLink(9117) + require.NoError(t, store.CreateJobRun(&run)) + + err := runExecutor.Execute(run.ID) + require.NoError(t, err) + + run, err = store.FindJobRun(run.ID) + require.NoError(t, err) + assert.Equal(t, models.RunStatusPendingConfirmations, run.Status) + require.Len(t, run.TaskRuns, 2) + assert.Equal(t, models.RunStatusCompleted, run.TaskRuns[0].Status) + assert.Equal(t, models.RunStatusPendingConfirmations, run.TaskRuns[1].Status) + + actual, err := store.LinkEarnedFor(&j) + require.NoError(t, err) + assert.Nil(t, actual) +} + +func TestRunExecutor_Execute_RunNotFoundError(t *testing.T) { + t.Parallel() + + store, cleanup := cltest.NewStore(t) + defer cleanup() + + runExecutor := services.NewRunExecutor(store) + + err := runExecutor.Execute(models.NewID()) + require.Error(t, err) +} + +func TestRunExecutor_Execute_RunNotRunnableError(t *testing.T) { + t.Parallel() + + store, cleanup := cltest.NewStore(t) + defer cleanup() + + runExecutor := services.NewRunExecutor(store) + + j := models.NewJob() + i := models.Initiator{Type: models.InitiatorWeb} + j.Initiators = []models.Initiator{i} + j.Tasks = []models.TaskSpec{ + cltest.NewTask(t, "noop"), + } + assert.NoError(t, store.CreateJob(&j)) + + run := j.NewRun(i) + run.Status = models.RunStatusPendingConfirmations + require.NoError(t, store.CreateJobRun(&run)) + + err := runExecutor.Execute(run.ID) + require.Error(t, err) +} + +func TestRunExecutor_Execute_CancelActivelyRunningTask(t *testing.T) { + t.Parallel() + + store, cleanup := cltest.NewStore(t) + defer cleanup() + clock := cltest.NewTriggerClock(t) + store.Clock = clock + + runExecutor := services.NewRunExecutor(store) + + j := models.NewJob() + i := models.Initiator{Type: models.InitiatorWeb} + j.Initiators = []models.Initiator{i} + j.Tasks = []models.TaskSpec{ + cltest.NewTask(t, "sleep", `{"until": 2147483647}`), + cltest.NewTask(t, "noop"), + } + assert.NoError(t, store.CreateJob(&j)) + + run := j.NewRun(i) + run.Payment = assets.NewLink(19238) + require.NoError(t, store.CreateJobRun(&run)) + + go func() { + err := runExecutor.Execute(run.ID) + require.NoError(t, err) + }() + + // FIXME: Can't think of a better way to do this + // Make sure Execute has some time to start the sleep task + time.Sleep(300 * time.Millisecond) + + runQueue := new(mocks.RunQueue) + runManager := services.NewRunManager(runQueue, store.Config, store.ORM, store.TxManager, clock) + runManager.Cancel(run.ID) + + clock.Trigger() + + run, err := store.FindJobRun(run.ID) + require.NoError(t, err) + assert.Equal(t, models.RunStatusCancelled, run.Status) + + require.Len(t, run.TaskRuns, 2) + assert.Equal(t, models.RunStatusCancelled, run.TaskRuns[0].Status) + assert.Equal(t, models.RunStatusUnstarted, run.TaskRuns[1].Status) + + actual, err := store.LinkEarnedFor(&j) + require.NoError(t, err) + assert.Nil(t, actual) +} diff --git a/core/services/run_manager.go b/core/services/run_manager.go new file mode 100644 index 00000000000..ba3cdc612f2 --- /dev/null +++ b/core/services/run_manager.go @@ -0,0 +1,367 @@ +package services + +import ( + "fmt" + "math/big" + + "github.com/pkg/errors" + "github.com/smartcontractkit/chainlink/core/adapters" + "github.com/smartcontractkit/chainlink/core/logger" + clnull "github.com/smartcontractkit/chainlink/core/null" + "github.com/smartcontractkit/chainlink/core/store" + "github.com/smartcontractkit/chainlink/core/store/assets" + "github.com/smartcontractkit/chainlink/core/store/models" + "github.com/smartcontractkit/chainlink/core/store/orm" + "github.com/smartcontractkit/chainlink/core/utils" +) + +// RecurringScheduleJobError contains the field for the error message. +type RecurringScheduleJobError struct { + msg string +} + +// Error returns the error message for the run. +func (err RecurringScheduleJobError) Error() string { + return err.msg +} + +//go:generate mockery -name RunManager -output ../internal/mocks/ -case=underscore + +// RunManager supplies methods for queueing, resuming and cancelling jobs in +// the RunQueue +type RunManager interface { + Create( + jobSpecID *models.ID, + initiator *models.Initiator, + data *models.JSON, + creationHeight *big.Int, + runRequest *models.RunRequest) (*models.JobRun, error) + CreateErrored( + jobSpecID *models.ID, + initiator models.Initiator, + err error) (*models.JobRun, error) + ResumePending( + runID *models.ID, + input models.BridgeRunResult) error + Cancel(runID *models.ID) error + + ResumeAllInProgress() error + ResumeAllConfirming(currentBlockHeight *big.Int) error + ResumeAllConnecting() error +} + +// runManager implements RunManager +type runManager struct { + orm *orm.ORM + runQueue RunQueue + txManager store.TxManager + config orm.ConfigReader + clock utils.AfterNower +} + +func newRun( + job *models.JobSpec, + initiator *models.Initiator, + data *models.JSON, + currentHeight *big.Int, + payment *assets.Link, + config orm.ConfigReader, + orm *orm.ORM, + clock utils.AfterNower, + txManager store.TxManager) (*models.JobRun, error) { + + now := clock.Now() + if !job.Started(now) { + return nil, RecurringScheduleJobError{ + msg: fmt.Sprintf("Job runner: Job %v unstarted: %v before job's start time %v", job.ID, now, job.EndAt), + } + } + + if job.Ended(now) { + return nil, RecurringScheduleJobError{ + msg: fmt.Sprintf("Job runner: Job %v ended: %v past job's end time %v", job.ID, now, job.EndAt), + } + } + + run := job.NewRun(*initiator) + run.Overrides = *data + run.CreationHeight = models.NewBig(currentHeight) + run.ObservedHeight = models.NewBig(currentHeight) + + if !MeetsMinimumPayment(job.MinPayment, payment) { + logger.Infow("Rejecting run with insufficient payment", + run.ForLogger( + "input_payment", payment, + "required_payment", job.MinPayment)...) + + err := fmt.Errorf( + "Rejecting job %s with payment %s below job-specific-minimum threshold (%s)", + job.ID, + payment, + job.MinPayment.Text(10)) + run.SetError(err) + } + + cost := &assets.Link{} + cost.Set(job.MinPayment) + for i, taskRun := range run.TaskRuns { + adapter, err := adapters.For(taskRun.TaskSpec, config, orm) + + if err != nil { + run.SetError(err) + return &run, nil + } + + if job.MinPayment.IsZero() { + mp := adapter.MinContractPayment() + if mp != nil { + cost.Add(cost, mp) + } + } + + if currentHeight != nil { + run.TaskRuns[i].MinimumConfirmations = clnull.Uint32From( + utils.MaxUint32( + config.MinIncomingConfirmations(), + taskRun.TaskSpec.Confirmations.Uint32, + adapter.MinConfs()), + ) + } + } + + // payment is only present for runs triggered by runlogs + if payment != nil { + if cost.Cmp(payment) > 0 { + logger.Debugw("Rejecting run with insufficient payment", + run.ForLogger( + "input_payment", payment, + "required_payment", cost)...) + + err := fmt.Errorf( + "Rejecting job %s with payment %s below minimum threshold (%s)", + job.ID, + payment, + config.MinimumContractPayment().Text(10)) + run.SetError(err) + } + } + + if len(run.TaskRuns) == 0 { + run.SetError(fmt.Errorf("invariant for job %s: no tasks to run in NewRun", job.ID)) + } + + if !run.Status.Runnable() { + return &run, nil + } + + initialTask := run.TaskRuns[0] + validateMinimumConfirmations(&run, &initialTask, run.CreationHeight, txManager) + return &run, nil +} + +// NewRunManager returns a new job manager +func NewRunManager( + runQueue RunQueue, + config orm.ConfigReader, + orm *orm.ORM, + txManager store.TxManager, + clock utils.AfterNower) RunManager { + return &runManager{ + orm: orm, + runQueue: runQueue, + txManager: txManager, + config: config, + clock: clock, + } +} + +// CreateErrored creates a run that is in the errored state. This is a +// special case where this job cannot run but we want to create the run record +// so the error is more visible to the node operator. +func (jm *runManager) CreateErrored( + jobSpecID *models.ID, + initiator models.Initiator, + runErr error) (*models.JobRun, error) { + job, err := jm.orm.Unscoped().FindJob(jobSpecID) + if err != nil { + return nil, errors.Wrap(err, "failed to find job spec") + } + + run := job.NewRun(initiator) + run.SetError(runErr) + return &run, jm.orm.CreateJobRun(&run) +} + +// Create immediately persists a JobRun and sends it to the RunQueue for +// execution. +func (jm *runManager) Create( + jobSpecID *models.ID, + initiator *models.Initiator, + data *models.JSON, + creationHeight *big.Int, + runRequest *models.RunRequest, +) (*models.JobRun, error) { + logger.Debugw(fmt.Sprintf("New run triggered by %s", initiator.Type), + "job", jobSpecID.String(), + "creation_height", creationHeight.String(), + ) + + job, err := jm.orm.Unscoped().FindJob(jobSpecID) + if err != nil { + return nil, errors.Wrap(err, "failed to find job spec") + } + + if job.Archived() { + return nil, RecurringScheduleJobError{ + msg: fmt.Sprintf("Trying to run archived job %s", job.ID), + } + } + + run, err := newRun(&job, initiator, data, creationHeight, runRequest.Payment, jm.config, jm.orm, jm.clock, jm.txManager) + if err != nil { + return nil, errors.Wrap(err, "newRun failed") + } + + run.RunRequest = *runRequest + + if err := jm.orm.CreateJobRun(run); err != nil { + return nil, errors.Wrap(err, "CreateJobRun failed") + } + + if run.Status == models.RunStatusInProgress { + logger.Debugw( + fmt.Sprintf("Executing run originally initiated by %s", run.Initiator.Type), + run.ForLogger()..., + ) + jm.runQueue.Run(run) + } + + return run, nil +} + +// ResumeAllConfirming wakes up all jobs that were sleeping because they were +// waiting for block confirmations. +func (jm *runManager) ResumeAllConfirming(currentBlockHeight *big.Int) error { + return jm.orm.UnscopedJobRunsWithStatus(func(run *models.JobRun) { + currentTaskRun := run.NextTaskRun() + if currentTaskRun == nil { + jm.updateWithError(run, "Attempting to resume confirming run with no remaining tasks %s", run.ID) + return + } + + run.ObservedHeight = models.NewBig(currentBlockHeight) + logger.Debugw(fmt.Sprintf("New head #%s resuming run", currentBlockHeight), run.ForLogger()...) + + validateMinimumConfirmations(run, currentTaskRun, run.ObservedHeight, jm.txManager) + + err := jm.updateAndTrigger(run) + if err != nil { + logger.Error("Error saving job run", "error", err) + } + }, models.RunStatusPendingConnection, models.RunStatusPendingConfirmations) +} + +// ResumeAllConnecting wakes up all tasks that have gone to sleep because they +// needed an ethereum client connection. +func (jm *runManager) ResumeAllConnecting() error { + return jm.orm.UnscopedJobRunsWithStatus(func(run *models.JobRun) { + logger.Debugw("New connection resuming run", run.ForLogger()...) + + currentTaskRun := run.NextTaskRun() + if currentTaskRun == nil { + jm.updateWithError(run, "Attempting to resume connecting run with no remaining tasks %s", run.ID) + return + } + + run.Status = models.RunStatusInProgress + err := jm.updateAndTrigger(run) + if err != nil { + logger.Error("Error saving job run", "error", err) + } + }, models.RunStatusPendingConnection, models.RunStatusPendingConfirmations) +} + +// ResumePendingTask wakes up a task that required a response from a bridge adapter. +func (jm *runManager) ResumePending( + runID *models.ID, + input models.BridgeRunResult, +) error { + run, err := jm.orm.Unscoped().FindJobRun(runID) + if err != nil { + return err + } + + logger.Debugw("External adapter resuming job", run.ForLogger("input_data", input.Data)...) + + if !run.Status.PendingBridge() { + return fmt.Errorf("Attempting to resume non pending run %s", run.ID) + } + + currentTaskRun := run.NextTaskRun() + if currentTaskRun == nil { + return jm.updateWithError(&run, "Attempting to resume pending run with no remaining tasks %s", run.ID) + } + + run.Overrides.Merge(input.Data) + + currentTaskRun.ApplyBridgeRunResult(input) + run.ApplyBridgeRunResult(input) + + return jm.updateAndTrigger(&run) +} + +// ResumeAllInProgress queries the db for job runs that should be resumed +// since a previous node shutdown. +// +// As a result of its reliance on the database, it must run before anything +// persists a job RunStatus to the db to ensure that it only captures pending and in progress +// jobs as a result of the last shutdown, and not as a result of what's happening now. +// +// To recap: This must run before anything else writes job run status to the db, +// ie. tries to run a job. +// https://github.com/smartcontractkit/chainlink/pull/807 +func (jm *runManager) ResumeAllInProgress() error { + return jm.orm.UnscopedJobRunsWithStatus(jm.runQueue.Run, models.RunStatusInProgress, models.RunStatusPendingSleep) +} + +// Cancel suspends a running task. +func (jm *runManager) Cancel(runID *models.ID) error { + run, err := jm.orm.FindJobRun(runID) + if err != nil { + return err + } + + logger.Debugw("Cancelling job", run.ForLogger()...) + if !run.Status.Runnable() { + return fmt.Errorf("Cannot cancel a non runnable job") + } + + currentTaskRun := run.NextTaskRun() + if currentTaskRun != nil { + currentTaskRun.Status = models.RunStatusCancelled + } + + run.Status = models.RunStatusCancelled + return jm.orm.SaveJobRun(&run) +} + +func (jm *runManager) updateWithError(run *models.JobRun, msg string, args ...interface{}) error { + run.SetError(fmt.Errorf(msg, args...)) + logger.Error(fmt.Sprintf(msg, args...)) + + if err := jm.orm.SaveJobRun(run); err != nil { + logger.Error("Error saving job run", "error", err) + return err + } + return nil +} + +func (jm *runManager) updateAndTrigger(run *models.JobRun) error { + if err := jm.orm.SaveJobRun(run); err != nil { + return err + } + if run.Status == models.RunStatusInProgress { + jm.runQueue.Run(run) + } + return nil +} diff --git a/core/services/run_manager_test.go b/core/services/run_manager_test.go new file mode 100644 index 00000000000..fa99d0d179e --- /dev/null +++ b/core/services/run_manager_test.go @@ -0,0 +1,596 @@ +package services_test + +import ( + "math/big" + "testing" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/smartcontractkit/chainlink/core/adapters" + "github.com/smartcontractkit/chainlink/core/internal/cltest" + "github.com/smartcontractkit/chainlink/core/internal/mocks" + clnull "github.com/smartcontractkit/chainlink/core/null" + "github.com/smartcontractkit/chainlink/core/services" + "github.com/smartcontractkit/chainlink/core/store/models" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" + "gopkg.in/guregu/null.v3" +) + +func TestRunManager_ResumePending(t *testing.T) { + store, cleanup := cltest.NewStore(t) + defer cleanup() + + runQueue := new(mocks.RunQueue) + runQueue.On("Run", mock.Anything).Maybe().Return(nil) + + runManager := services.NewRunManager(runQueue, store.Config, store.ORM, store.TxManager, store.Clock) + + job := cltest.NewJob() + require.NoError(t, store.CreateJob(&job)) + input := cltest.JSONFromString(t, `{"address":"0xdfcfc2b9200dbb10952c2b7cce60fc7260e03c6f"}`) + + t.Run("reject a run with an invalid state", func(t *testing.T) { + run := &models.JobRun{ID: models.NewID(), JobSpecID: job.ID} + require.NoError(t, store.CreateJobRun(run)) + err := runManager.ResumePending(run.ID, models.BridgeRunResult{}) + assert.Error(t, err) + }) + + t.Run("reject a run with no tasks", func(t *testing.T) { + run := models.JobRun{ID: models.NewID(), JobSpecID: job.ID, Status: models.RunStatusPendingBridge} + require.NoError(t, store.CreateJobRun(&run)) + err := runManager.ResumePending(run.ID, models.BridgeRunResult{}) + assert.NoError(t, err) + + run, err = store.FindJobRun(run.ID) + require.NoError(t, err) + assert.Equal(t, models.RunStatusErrored, run.Status) + }) + + t.Run("input with error errors run", func(t *testing.T) { + runID := models.NewID() + run := models.JobRun{ + ID: runID, + JobSpecID: job.ID, + Status: models.RunStatusPendingBridge, + TaskRuns: []models.TaskRun{models.TaskRun{ID: models.NewID(), JobRunID: runID}}, + } + require.NoError(t, store.CreateJobRun(&run)) + + err := runManager.ResumePending(run.ID, models.BridgeRunResult{Status: models.RunStatusErrored}) + assert.NoError(t, err) + + run, err = store.FindJobRun(run.ID) + require.NoError(t, err) + assert.Equal(t, models.RunStatusErrored, run.Status) + assert.True(t, run.FinishedAt.Valid) + assert.Len(t, run.TaskRuns, 1) + assert.Equal(t, models.RunStatusErrored, run.TaskRuns[0].Status) + }) + + t.Run("completed input with remaining tasks should put task into pending", func(t *testing.T) { + runID := models.NewID() + run := models.JobRun{ + ID: runID, + JobSpecID: job.ID, + Status: models.RunStatusPendingBridge, + TaskRuns: []models.TaskRun{models.TaskRun{ID: models.NewID(), JobRunID: runID}, models.TaskRun{ID: models.NewID(), JobRunID: runID}}, + } + require.NoError(t, store.CreateJobRun(&run)) + + err := runManager.ResumePending(run.ID, models.BridgeRunResult{Data: input, Status: models.RunStatusCompleted}) + assert.NoError(t, err) + + run, err = store.FindJobRun(run.ID) + require.NoError(t, err) + assert.NoError(t, err) + assert.Equal(t, string(models.RunStatusInProgress), string(run.Status)) + assert.Len(t, run.TaskRuns, 2) + assert.Equal(t, string(models.RunStatusCompleted), string(run.TaskRuns[0].Status)) + }) + + t.Run("completed input with no remaining tasks should get marked as complete", func(t *testing.T) { + runID := models.NewID() + run := models.JobRun{ + ID: runID, + JobSpecID: job.ID, + Status: models.RunStatusPendingBridge, + TaskRuns: []models.TaskRun{models.TaskRun{ID: models.NewID(), JobRunID: runID}}, + } + require.NoError(t, store.CreateJobRun(&run)) + + err := runManager.ResumePending(run.ID, models.BridgeRunResult{Data: input, Status: models.RunStatusCompleted}) + assert.NoError(t, err) + + run, err = store.FindJobRun(run.ID) + require.NoError(t, err) + assert.Equal(t, string(models.RunStatusCompleted), string(run.Status)) + assert.True(t, run.FinishedAt.Valid) + assert.Len(t, run.TaskRuns, 1) + assert.Equal(t, string(models.RunStatusCompleted), string(run.TaskRuns[0].Status)) + }) + + runQueue.AssertExpectations(t) +} + +func TestRunManager_ResumeAllConfirming(t *testing.T) { + store, cleanup := cltest.NewStore(t) + defer cleanup() + + runQueue := new(mocks.RunQueue) + runQueue.On("Run", mock.Anything).Maybe().Return(nil) + + runManager := services.NewRunManager(runQueue, store.Config, store.ORM, store.TxManager, store.Clock) + + job := cltest.NewJob() + require.NoError(t, store.CreateJob(&job)) + + t.Run("reject a run with no tasks", func(t *testing.T) { + run := models.JobRun{ + ID: models.NewID(), + JobSpecID: job.ID, + Status: models.RunStatusPendingConfirmations, + } + require.NoError(t, store.CreateJobRun(&run)) + + err := runManager.ResumeAllConfirming(nil) + assert.NoError(t, err) + + run, err = store.FindJobRun(run.ID) + require.NoError(t, err) + assert.Equal(t, models.RunStatusErrored, run.Status) + }) + + creationHeight := models.NewBig(big.NewInt(0)) + + t.Run("leave in pending if not enough confirmations have been met yet", func(t *testing.T) { + run := models.JobRun{ + ID: models.NewID(), + JobSpecID: job.ID, + CreationHeight: creationHeight, + Status: models.RunStatusPendingConfirmations, + TaskRuns: []models.TaskRun{models.TaskRun{ + ID: models.NewID(), + MinimumConfirmations: clnull.Uint32From(2), + TaskSpec: models.TaskSpec{ + Type: adapters.TaskTypeNoOp, + }, + }}, + } + require.NoError(t, store.CreateJobRun(&run)) + + err := runManager.ResumeAllConfirming(creationHeight.ToInt()) + require.NoError(t, err) + + run, err = store.FindJobRun(run.ID) + require.NoError(t, err) + assert.Equal(t, models.RunStatusPendingConfirmations, run.Status) + assert.Equal(t, uint32(1), run.TaskRuns[0].Confirmations.Uint32) + }) + + t.Run("input, should go from pending -> in progress and save the input", func(t *testing.T) { + run := models.JobRun{ + ID: models.NewID(), + JobSpecID: job.ID, + CreationHeight: creationHeight, + Status: models.RunStatusPendingConfirmations, + TaskRuns: []models.TaskRun{models.TaskRun{ + ID: models.NewID(), + MinimumConfirmations: clnull.Uint32From(1), + TaskSpec: models.TaskSpec{ + Type: adapters.TaskTypeNoOp, + }, + }}, + } + require.NoError(t, store.CreateJobRun(&run)) + + observedHeight := big.NewInt(1) + err := runManager.ResumeAllConfirming(observedHeight) + require.NoError(t, err) + + run, err = store.FindJobRun(run.ID) + require.NoError(t, err) + assert.Equal(t, string(models.RunStatusInProgress), string(run.Status)) + }) + + runQueue.AssertExpectations(t) +} + +func TestRunManager_ResumeAllConnecting(t *testing.T) { + store, cleanup := cltest.NewStore(t) + defer cleanup() + + runQueue := new(mocks.RunQueue) + runQueue.On("Run", mock.Anything).Maybe().Return(nil) + + runManager := services.NewRunManager(runQueue, store.Config, store.ORM, store.TxManager, store.Clock) + + job := cltest.NewJob() + require.NoError(t, store.CreateJob(&job)) + + t.Run("reject a run with no tasks", func(t *testing.T) { + run := models.JobRun{ + ID: models.NewID(), + JobSpecID: job.ID, + Status: models.RunStatusPendingConnection, + } + require.NoError(t, store.CreateJobRun(&run)) + + err := runManager.ResumeAllConnecting() + assert.NoError(t, err) + + run, err = store.FindJobRun(run.ID) + require.NoError(t, err) + assert.Equal(t, models.RunStatusErrored, run.Status) + }) + + t.Run("input, should go from pending -> in progress", func(t *testing.T) { + run := models.JobRun{ + ID: models.NewID(), + JobSpecID: job.ID, + Status: models.RunStatusPendingConnection, + TaskRuns: []models.TaskRun{models.TaskRun{ + ID: models.NewID(), + }}, + } + require.NoError(t, store.CreateJobRun(&run)) + err := runManager.ResumeAllConnecting() + assert.NoError(t, err) + + run, err = store.FindJobRun(run.ID) + require.NoError(t, err) + assert.Equal(t, models.RunStatusInProgress, run.Status) + }) +} + +func TestRunManager_Create(t *testing.T) { + t.Parallel() + app, cleanup := cltest.NewApplication(t) + defer cleanup() + + store := app.Store + eth := cltest.MockEthOnStore(t, store) + eth.Register("eth_chainId", store.Config.ChainID()) + + app.Start() + + job := cltest.NewJobWithRunLogInitiator() + job.Tasks = []models.TaskSpec{cltest.NewTask(t, "NoOp")} // empty params + require.NoError(t, store.CreateJob(&job)) + + requestID := "RequestID" + initr := job.Initiators[0] + rr := models.NewRunRequest() + rr.RequestID = &requestID + data := cltest.JSONFromString(t, `{"random": "input"}`) + jr, err := app.RunManager.Create(job.ID, &initr, &data, nil, rr) + require.NoError(t, err) + updatedJR := cltest.WaitForJobRunToComplete(t, store, *jr) + assert.Equal(t, rr.RequestID, updatedJR.RunRequest.RequestID) +} + +func TestRunManager_Create_DoesNotSaveToTaskSpec(t *testing.T) { + t.Parallel() + app, cleanup := cltest.NewApplication(t) + defer cleanup() + + store := app.Store + eth := cltest.MockEthOnStore(t, store) + eth.Register("eth_chainId", store.Config.ChainID()) + + app.Start() + + job := cltest.NewJobWithWebInitiator() + job.Tasks = []models.TaskSpec{cltest.NewTask(t, "NoOp")} // empty params + require.NoError(t, store.CreateJob(&job)) + + initr := job.Initiators[0] + data := cltest.JSONFromString(t, `{"random": "input"}`) + jr, err := app.RunManager.Create(job.ID, &initr, &data, nil, &models.RunRequest{}) + require.NoError(t, err) + cltest.WaitForJobRunToComplete(t, store, *jr) + + retrievedJob, err := store.FindJob(job.ID) + require.NoError(t, err) + require.Len(t, job.Tasks, 1) + require.Len(t, retrievedJob.Tasks, 1) + assert.Equal(t, job.Tasks[0].Params, retrievedJob.Tasks[0].Params) +} + +func TestRunManager_Create_fromRunLog_Happy(t *testing.T) { + t.Parallel() + + initiatingTxHash := cltest.NewHash() + triggeringBlockHash := cltest.NewHash() + otherBlockHash := cltest.NewHash() + + tests := []struct { + name string + logBlockHash common.Hash + receiptBlockHash common.Hash + wantStatus models.RunStatus + }{ + { + name: "main chain", + logBlockHash: triggeringBlockHash, + receiptBlockHash: triggeringBlockHash, + wantStatus: models.RunStatusCompleted, + }, + { + name: "ommered chain", + logBlockHash: triggeringBlockHash, + receiptBlockHash: otherBlockHash, + wantStatus: models.RunStatusErrored, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + config, cfgCleanup := cltest.NewConfig(t) + defer cfgCleanup() + minimumConfirmations := uint32(2) + config.Set("MIN_INCOMING_CONFIRMATIONS", minimumConfirmations) + app, cleanup := cltest.NewApplicationWithConfig(t, config) + defer cleanup() + + eth := app.MockEthCallerSubscriber() + app.Start() + + store := app.GetStore() + job := cltest.NewJobWithRunLogInitiator() + job.Tasks = []models.TaskSpec{cltest.NewTask(t, "NoOp")} + require.NoError(t, store.CreateJob(&job)) + + creationHeight := big.NewInt(1) + requestID := "RequestID" + initr := job.Initiators[0] + rr := models.NewRunRequest() + rr.RequestID = &requestID + rr.TxHash = &initiatingTxHash + rr.BlockHash = &test.logBlockHash + data := cltest.JSONFromString(t, `{"random": "input"}`) + jr, err := app.RunManager.Create(job.ID, &initr, &data, creationHeight, rr) + require.NoError(t, err) + cltest.WaitForJobRunToPendConfirmations(t, app.Store, *jr) + + confirmedReceipt := models.TxReceipt{ + Hash: initiatingTxHash, + BlockHash: &test.receiptBlockHash, + BlockNumber: cltest.Int(3), + } + eth.Context("validateOnMainChain", func(ethMock *cltest.EthMock) { + eth.Register("eth_getTransactionReceipt", confirmedReceipt) + }) + + err = app.RunManager.ResumeAllConfirming(big.NewInt(2)) + require.NoError(t, err) + updatedJR := cltest.WaitForJobRunStatus(t, store, *jr, test.wantStatus) + assert.Equal(t, rr.RequestID, updatedJR.RunRequest.RequestID) + assert.Equal(t, minimumConfirmations, updatedJR.TaskRuns[0].MinimumConfirmations.Uint32) + assert.True(t, updatedJR.TaskRuns[0].MinimumConfirmations.Valid) + assert.Equal(t, minimumConfirmations, updatedJR.TaskRuns[0].Confirmations.Uint32, "task run should track its current confirmations") + assert.True(t, updatedJR.TaskRuns[0].Confirmations.Valid) + assert.True(t, eth.AllCalled(), eth.Remaining()) + }) + } +} + +func TestRunManager_Create_fromRunLog_ConnectToLaggingEthNode(t *testing.T) { + t.Parallel() + + initiatingTxHash := cltest.NewHash() + triggeringBlockHash := cltest.NewHash() + + config, cfgCleanup := cltest.NewConfig(t) + defer cfgCleanup() + minimumConfirmations := uint32(2) + config.Set("MIN_INCOMING_CONFIRMATIONS", minimumConfirmations) + app, cleanup := cltest.NewApplicationWithConfig(t, config) + defer cleanup() + + eth := app.MockEthCallerSubscriber() + app.MockStartAndConnect() + + store := app.GetStore() + job := cltest.NewJobWithRunLogInitiator() + job.Tasks = []models.TaskSpec{cltest.NewTask(t, "NoOp")} + require.NoError(t, store.CreateJob(&job)) + + requestID := "RequestID" + initr := job.Initiators[0] + rr := models.NewRunRequest() + rr.RequestID = &requestID + rr.TxHash = &initiatingTxHash + rr.BlockHash = &triggeringBlockHash + + futureCreationHeight := big.NewInt(9) + pastCurrentHeight := big.NewInt(1) + + data := cltest.JSONFromString(t, `{"random": "input"}`) + jr, err := app.RunManager.Create(job.ID, &initr, &data, futureCreationHeight, rr) + require.NoError(t, err) + cltest.WaitForJobRunToPendConfirmations(t, app.Store, *jr) + + err = app.RunManager.ResumeAllConfirming(pastCurrentHeight) + require.NoError(t, err) + updatedJR := cltest.WaitForJobRunToPendConfirmations(t, app.Store, *jr) + assert.True(t, updatedJR.TaskRuns[0].Confirmations.Valid) + assert.Equal(t, uint32(0), updatedJR.TaskRuns[0].Confirmations.Uint32) + assert.True(t, eth.AllCalled(), eth.Remaining()) +} + +func TestRunManager_ResumeConfirmingTasks(t *testing.T) { + t.Parallel() + + tests := []struct { + status models.RunStatus + }{ + {models.RunStatusPendingConnection}, + {models.RunStatusPendingConfirmations}, + } + + for _, test := range tests { + t.Run(string(test.status), func(t *testing.T) { + store, cleanup := cltest.NewStore(t) + defer cleanup() + + job := cltest.NewJobWithWebInitiator() + require.NoError(t, store.CreateJob(&job)) + initr := job.Initiators[0] + run := job.NewRun(initr) + run.Status = test.status + require.NoError(t, store.CreateJobRun(&run)) + + runQueue := new(mocks.RunQueue) + runQueue.On("Run", mock.Anything).Return(nil) + + runManager := services.NewRunManager(runQueue, store.Config, store.ORM, store.TxManager, store.Clock) + runManager.ResumeAllConfirming(big.NewInt(3821)) + + runQueue.AssertExpectations(t) + }) + } +} + +func TestRunManager_ResumeAllInProgress(t *testing.T) { + t.Parallel() + + tests := []struct { + status models.RunStatus + }{ + {models.RunStatusInProgress}, + {models.RunStatusPendingSleep}, + } + + for _, test := range tests { + t.Run(string(test.status), func(t *testing.T) { + store, cleanup := cltest.NewStore(t) + defer cleanup() + + job := cltest.NewJobWithWebInitiator() + require.NoError(t, store.CreateJob(&job)) + initr := job.Initiators[0] + run := job.NewRun(initr) + run.Status = test.status + require.NoError(t, store.CreateJobRun(&run)) + + runQueue := new(mocks.RunQueue) + runQueue.On("Run", mock.Anything).Return(nil) + + runManager := services.NewRunManager(runQueue, store.Config, store.ORM, store.TxManager, store.Clock) + runManager.ResumeAllInProgress() + + runQueue.AssertExpectations(t) + }) + } +} + +// XXX: In progress tasks that are archived should still be run as they have been paid for +func TestRunManager_ResumeAllInProgress_Archived(t *testing.T) { + t.Parallel() + + tests := []struct { + status models.RunStatus + }{ + {models.RunStatusInProgress}, + {models.RunStatusPendingSleep}, + } + + for _, test := range tests { + t.Run(string(test.status), func(t *testing.T) { + store, cleanup := cltest.NewStore(t) + defer cleanup() + + job := cltest.NewJobWithWebInitiator() + require.NoError(t, store.CreateJob(&job)) + initr := job.Initiators[0] + run := job.NewRun(initr) + run.Status = test.status + run.DeletedAt = null.TimeFrom(time.Now()) + require.NoError(t, store.CreateJobRun(&run)) + + runQueue := new(mocks.RunQueue) + runQueue.On("Run", mock.Anything).Return(nil) + + runManager := services.NewRunManager(runQueue, store.Config, store.ORM, store.TxManager, store.Clock) + runManager.ResumeAllInProgress() + + runQueue.AssertExpectations(t) + }) + } +} + +func TestRunManager_ResumeAllInProgress_NotInProgress(t *testing.T) { + t.Parallel() + + tests := []struct { + status models.RunStatus + }{ + {models.RunStatusPendingConnection}, + {models.RunStatusPendingConfirmations}, + {models.RunStatusPendingBridge}, + {models.RunStatusCompleted}, + {models.RunStatusCancelled}, + } + + for _, test := range tests { + t.Run(string(test.status), func(t *testing.T) { + store, cleanup := cltest.NewStore(t) + defer cleanup() + + job := cltest.NewJobWithWebInitiator() + require.NoError(t, store.CreateJob(&job)) + initr := job.Initiators[0] + run := job.NewRun(initr) + run.Status = test.status + require.NoError(t, store.CreateJobRun(&run)) + + runQueue := new(mocks.RunQueue) + runQueue.On("Run", mock.Anything).Maybe().Return(nil) + + runManager := services.NewRunManager(runQueue, store.Config, store.ORM, store.TxManager, store.Clock) + runManager.ResumeAllInProgress() + + runQueue.AssertExpectations(t) + }) + } +} + +func TestRunManager_ResumeAllInProgress_NotInProgressAndArchived(t *testing.T) { + t.Parallel() + + tests := []struct { + status models.RunStatus + }{ + {models.RunStatusPendingConnection}, + {models.RunStatusPendingConfirmations}, + {models.RunStatusPendingBridge}, + {models.RunStatusCompleted}, + {models.RunStatusCancelled}, + } + + for _, test := range tests { + t.Run(string(test.status), func(t *testing.T) { + store, cleanup := cltest.NewStore(t) + defer cleanup() + + job := cltest.NewJobWithWebInitiator() + require.NoError(t, store.CreateJob(&job)) + initr := job.Initiators[0] + run := job.NewRun(initr) + run.Status = test.status + run.DeletedAt = null.TimeFrom(time.Now()) + require.NoError(t, store.CreateJobRun(&run)) + + runQueue := new(mocks.RunQueue) + runQueue.On("Run", mock.Anything).Maybe().Return(nil) + + runManager := services.NewRunManager(runQueue, store.Config, store.ORM, store.TxManager, store.Clock) + runManager.ResumeAllInProgress() + + runQueue.AssertExpectations(t) + }) + } +} diff --git a/core/services/run_queue.go b/core/services/run_queue.go new file mode 100644 index 00000000000..520c0d88c81 --- /dev/null +++ b/core/services/run_queue.go @@ -0,0 +1,114 @@ +package services + +import ( + "fmt" + "sync" + "time" + + "github.com/smartcontractkit/chainlink/core/logger" + "github.com/smartcontractkit/chainlink/core/store/models" +) + +//go:generate mockery -name RunQueue -output ../internal/mocks/ -case=underscore + +// RunQueue safely handles coordinating job runs. +type RunQueue interface { + Start() error + Stop() + Run(*models.JobRun) + + WorkerCount() int +} + +type runQueue struct { + workersMutex sync.RWMutex + workers map[string]int + workersWg sync.WaitGroup + + runExecutor RunExecutor + + runsQueued uint + runsExecuted uint + quit chan struct{} +} + +// NewRunQueue initializes a RunQueue. +func NewRunQueue(runExecutor RunExecutor) RunQueue { + return &runQueue{ + quit: make(chan struct{}), + workers: make(map[string]int), + runExecutor: runExecutor, + } +} + +// Start prepares the job runner for accepting runs to execute. +func (rq *runQueue) Start() error { + go rq.statisticsLogger() + return nil +} + +func (rq *runQueue) statisticsLogger() { + ticker := time.NewTicker(5 * time.Minute) + for { + select { + case <-ticker.C: + rq.workersMutex.RLock() + logger.Debugw("Run queue statistics", "runs_executed", rq.runsExecuted, "runs_queued", rq.runsQueued, "worker_count", len(rq.workers)) + rq.workersMutex.RUnlock() + case <-rq.quit: + ticker.Stop() + return + } + } +} + +// Stop closes all open worker channels. +func (rq *runQueue) Stop() { + rq.quit <- struct{}{} + rq.workersWg.Wait() +} + +// Run tells the job runner to start executing a job +func (rq *runQueue) Run(run *models.JobRun) { + runID := run.ID.String() + + rq.workersMutex.Lock() + if queueCount, present := rq.workers[runID]; present { + rq.runsQueued += 1 + rq.workers[runID] = queueCount + 1 + rq.workersMutex.Unlock() + return + } + rq.runsExecuted += 1 + rq.workers[runID] = 1 + rq.workersMutex.Unlock() + + rq.workersWg.Add(1) + go func() { + for { + rq.workersMutex.Lock() + queueCount := rq.workers[runID] + if queueCount <= 0 { + delete(rq.workers, runID) + rq.workersMutex.Unlock() + break + } + rq.workers[runID] = queueCount - 1 + rq.workersMutex.Unlock() + + if err := rq.runExecutor.Execute(run.ID); err != nil { + logger.Errorw(fmt.Sprint("Error executing run ", runID), "error", err) + } + } + + rq.workersWg.Done() + }() +} + +// WorkerCount returns the number of workers currently processing a job run +func (rq *runQueue) WorkerCount() int { + rq.workersMutex.RLock() + defer rq.workersMutex.RUnlock() + + return len(rq.workers) +} diff --git a/core/services/run_queue_test.go b/core/services/run_queue_test.go new file mode 100644 index 00000000000..8f2a2fca663 --- /dev/null +++ b/core/services/run_queue_test.go @@ -0,0 +1,122 @@ +package services_test + +import ( + "testing" + + "github.com/onsi/gomega" + "github.com/smartcontractkit/chainlink/core/internal/cltest" + "github.com/smartcontractkit/chainlink/core/internal/mocks" + "github.com/smartcontractkit/chainlink/core/services" + "github.com/smartcontractkit/chainlink/core/store/models" + "github.com/stretchr/testify/mock" +) + +func TestRunQueue(t *testing.T) { + t.Parallel() + g := gomega.NewGomegaWithT(t) + + runExecutor := new(mocks.RunExecutor) + runQueue := services.NewRunQueue(runExecutor) + + executeJobChannel := make(chan struct{}) + + runQueue.Start() + defer runQueue.Stop() + + runExecutor.On("Execute", mock.Anything). + Return(nil, nil). + Run(func(mock.Arguments) { + executeJobChannel <- struct{}{} + }) + + runQueue.Run(&models.JobRun{ID: models.NewID()}) + + g.Eventually(func() int { + return runQueue.WorkerCount() + }).Should(gomega.Equal(1)) + + cltest.CallbackOrTimeout(t, "Execute", func() { + <-executeJobChannel + }) + + runExecutor.AssertExpectations(t) + + g.Eventually(func() int { + return runQueue.WorkerCount() + }).Should(gomega.Equal(0)) +} + +func TestRunQueue_OneWorkerPerRun(t *testing.T) { + t.Parallel() + g := gomega.NewGomegaWithT(t) + + runExecutor := new(mocks.RunExecutor) + runQueue := services.NewRunQueue(runExecutor) + + executeJobChannel := make(chan struct{}) + + runQueue.Start() + defer runQueue.Stop() + + runExecutor.On("Execute", mock.Anything). + Return(nil, nil). + Run(func(mock.Arguments) { + executeJobChannel <- struct{}{} + }) + + runQueue.Run(&models.JobRun{ID: models.NewID()}) + runQueue.Run(&models.JobRun{ID: models.NewID()}) + + g.Eventually(func() int { + return runQueue.WorkerCount() + }).Should(gomega.Equal(2)) + + cltest.CallbackOrTimeout(t, "Execute", func() { + <-executeJobChannel + <-executeJobChannel + }) + + runExecutor.AssertExpectations(t) + + g.Eventually(func() int { + return runQueue.WorkerCount() + }).Should(gomega.Equal(0)) +} + +func TestRunQueue_OneWorkerForSameRunTriggeredMultipleTimes(t *testing.T) { + t.Parallel() + g := gomega.NewGomegaWithT(t) + + runExecutor := new(mocks.RunExecutor) + runQueue := services.NewRunQueue(runExecutor) + + executeJobChannel := make(chan struct{}) + + runQueue.Start() + defer runQueue.Stop() + + runExecutor.On("Execute", mock.Anything). + Return(nil, nil). + Run(func(mock.Arguments) { + executeJobChannel <- struct{}{} + }) + + id := models.NewID() + runQueue.Run(&models.JobRun{ID: id}) + runQueue.Run(&models.JobRun{ID: id}) + + g.Eventually(func() int { + return runQueue.WorkerCount() + }).Should(gomega.Equal(1)) + + cltest.CallbackOrTimeout(t, "Execute", func() { + <-executeJobChannel + <-executeJobChannel + }) + + runExecutor.AssertExpectations(t) + + g.Eventually(func() int { + return runQueue.WorkerCount() + }).Should(gomega.Equal(0)) +} diff --git a/core/services/runs.go b/core/services/runs.go index 44e16d0abba..72fa6f8260e 100644 --- a/core/services/runs.go +++ b/core/services/runs.go @@ -4,8 +4,6 @@ import ( "fmt" "math/big" - "github.com/pkg/errors" - "github.com/smartcontractkit/chainlink/core/adapters" "github.com/smartcontractkit/chainlink/core/logger" clnull "github.com/smartcontractkit/chainlink/core/null" "github.com/smartcontractkit/chainlink/core/store" @@ -14,61 +12,6 @@ import ( "github.com/smartcontractkit/chainlink/core/utils" ) -// CreateErroredJob creates a job that is in the errored state. This is a -// special case where this job cannot run but we want to create the run record -// so the error is more visible to the node operator. -func CreateErroredJob( - job models.JobSpec, - initiator models.Initiator, - err error, - store *store.Store) (*models.JobRun, error) { - run := job.NewRun(initiator) - run.SetError(err) - return &run, store.CreateJobRun(&run) -} - -// ExecuteJob saves and immediately begins executing a run for a specified job -// if it is ready. -func ExecuteJob( - job models.JobSpec, - initiator models.Initiator, - data *models.JSON, - creationHeight *big.Int, - store *store.Store) (*models.JobRun, error) { - return ExecuteJobWithRunRequest( - job, - initiator, - data, - creationHeight, - store, - models.NewRunRequest(), - ) -} - -// ExecuteJobWithRunRequest saves and immediately begins executing a run -// for a specified job if it is ready, assigning the passed initiator run. -func ExecuteJobWithRunRequest( - job models.JobSpec, - initiator models.Initiator, - data *models.JSON, - creationHeight *big.Int, - store *store.Store, - runRequest models.RunRequest) (*models.JobRun, error) { - - logger.Debugw(fmt.Sprintf("New run triggered by %s", initiator.Type), - "job", job.ID, - "creation_height", creationHeight, - ) - - run, err := NewRun(job, initiator, data, creationHeight, store, runRequest.Payment) - if err != nil { - return nil, errors.Wrap(err, "NewRun failed") - } - - run.RunRequest = runRequest - return run, createAndTrigger(run, store) -} - // MeetsMinimumPayment is a helper that returns true if jobrun received // sufficient payment (more than jobspec's MinimumPayment) to be considered successful func MeetsMinimumPayment( @@ -81,300 +24,38 @@ func MeetsMinimumPayment( return expectedMinJobPayment.Cmp(actualRunPayment) < 1 } -// NewRun returns a run from an input job, in an initial state ready for -// processing by the job runner system -func NewRun( - job models.JobSpec, - initiator models.Initiator, - data *models.JSON, - currentHeight *big.Int, - store *store.Store, - payment *assets.Link) (*models.JobRun, error) { - - now := store.Clock.Now() - if !job.Started(now) { - return nil, RecurringScheduleJobError{ - msg: fmt.Sprintf("Job runner: Job %s unstarted: %v before job's start time %v", job.ID.String(), now, job.EndAt), - } - } - - if job.Ended(now) { - return nil, RecurringScheduleJobError{ - msg: fmt.Sprintf("Job runner: Job %s ended: %v past job's end time %v", job.ID.String(), now, job.EndAt), - } - } - - run := job.NewRun(initiator) - - run.Overrides = *data - run.CreationHeight = models.NewBig(currentHeight) - run.ObservedHeight = models.NewBig(currentHeight) - - if !MeetsMinimumPayment(job.MinPayment, payment) { - logger.Infow("Rejecting run with insufficient payment", []interface{}{ - "run", run.ID, - "job", run.JobSpecID, - "input_payment", payment, - "required_payment", job.MinPayment, - }...) - - err := fmt.Errorf( - "Rejecting job %s with payment %s below job-specific-minimum threshold (%s)", - job.ID, - payment, - job.MinPayment.Text(10)) +func validateMinimumConfirmations(run *models.JobRun, taskRun *models.TaskRun, currentHeight *models.Big, txManager store.TxManager) { + updateTaskRunConfirmations(currentHeight, run, taskRun) + if !meetsMinimumConfirmations(run, taskRun, run.ObservedHeight) { + logger.Debugw("Pausing run pending confirmations", []interface{}{"run", run.ID.String(), "required_height", taskRun.MinimumConfirmations}...) + run.Status = models.RunStatusPendingConfirmations + } else if err := validateOnMainChain(run, taskRun, txManager); err != nil { run.SetError(err) + } else { + run.Status = models.RunStatusInProgress } - - cost := &assets.Link{} - if job.MinPayment != nil { - cost.Set(job.MinPayment) - } - for i, taskRun := range run.TaskRuns { - adapter, err := adapters.For(taskRun.TaskSpec, store) - - if err != nil { - run.SetError(err) - return &run, nil - } - - if job.MinPayment == nil || job.MinPayment.IsZero() { - mp := adapter.MinContractPayment() - if mp != nil { - cost.Add(cost, mp) - } - } - - if currentHeight != nil { - run.TaskRuns[i].MinimumConfirmations = clnull.Uint32From( - utils.MaxUint32( - store.Config.MinIncomingConfirmations(), - taskRun.TaskSpec.Confirmations.Uint32, - adapter.MinConfs()), - ) - } - } - - // payment is always present for runs triggered by ethlogs - if payment != nil { - if cost.Cmp(payment) > 0 { - logger.Debugw("Rejecting run with insufficient payment", []interface{}{ - "run", run.ID, - "job", run.JobSpecID, - "input_payment", payment, - "required_payment", cost, - }...) - - err := fmt.Errorf( - "Rejecting job %s with payment %s below minimum threshold (%s)", - job.ID, - payment, - store.Config.MinimumContractPayment().Text(10)) - run.SetError(err) - } - } - - if len(run.TaskRuns) == 0 { - run.SetError(fmt.Errorf("invariant for job %s: no tasks to run in NewRun", job.ID.String())) - } - - if !run.Status.Runnable() { - return &run, nil - } - - initialTask := run.TaskRuns[0] - validateMinimumConfirmations(&run, &initialTask, run.CreationHeight, store) - return &run, nil -} - -// ResumeConfirmingTask resumes a confirming run if the minimum confirmations have been met -func ResumeConfirmingTask( - run *models.JobRun, - store *store.Store, - currentBlockHeight *big.Int, -) error { - - logger.Debugw("New head resuming run", run.ForLogger()...) - - if !run.Status.PendingConfirmations() && !run.Status.PendingConnection() { - return fmt.Errorf("Attempt to resume non confirming task") - } - - currentTaskRun := run.NextTaskRun() - if currentTaskRun == nil { - return fmt.Errorf("Attempting to resume confirming run with no remaining tasks %s", run.ID.String()) - } - - if currentBlockHeight == nil { - return fmt.Errorf("Attempting to resume confirming run with no currentBlockHeight %s", run.ID.String()) - } - - run.ObservedHeight = models.NewBig(currentBlockHeight) - - validateMinimumConfirmations(run, currentTaskRun, run.ObservedHeight, store) - return updateAndTrigger(run, store) -} - -// ResumeConnectingTask resumes a run that was left in pending_connection. -func ResumeConnectingTask( - run *models.JobRun, - store *store.Store, -) error { - - logger.Debugw("New connection resuming run", run.ForLogger()...) - - if !run.Status.PendingConnection() { - return fmt.Errorf("Attempt to resume non connecting task") - } - - currentTaskRun := run.NextTaskRun() - if currentTaskRun == nil { - return fmt.Errorf("Attempting to resume connecting run with no remaining tasks %s", run.ID.String()) - } - - run.Status = models.RunStatusInProgress - return updateAndTrigger(run, store) -} - -// ResumePendingTask takes the body provided from an external adapter, -// saves it for the next task to process, then tells the job runner to execute -// it -func ResumePendingTask( - run *models.JobRun, - store *store.Store, - input models.BridgeRunResult, -) error { - logger.Debugw("External adapter resuming job", []interface{}{ - "run", run.ID.String(), - "job", run.JobSpecID.String(), - "status", run.Status, - "input_data", input.Data, - "input_result", input.Status, - }...) - - if !run.Status.PendingBridge() { - return fmt.Errorf("Attempting to resume non pending run %s", run.ID.String()) - } - - currentTaskRun := run.NextTaskRun() - if currentTaskRun == nil { - return fmt.Errorf("Attempting to resume pending run with no remaining tasks %s", run.ID.String()) - } - - run.Overrides.Merge(input.Data) - - currentTaskRun.ApplyBridgeRunResult(input) - run.ApplyBridgeRunResult(input) - - return updateAndTrigger(run, store) -} - -func prepareAdapter( - taskRun *models.TaskRun, - data models.JSON, - store *store.Store, -) (*adapters.PipelineAdapter, error) { - taskCopy := taskRun.TaskSpec // deliberately copied to keep mutations local - - merged, err := taskCopy.Params.Merge(data) - if err != nil { - return nil, err - } - taskCopy.Params = merged - - return adapters.For(taskCopy, store) } -// QueueSleepingTask creates a go routine which will wake up the job runner -// once the sleep's time has elapsed -func QueueSleepingTask( - run *models.JobRun, - store *store.Store, -) error { - if !run.Status.PendingSleep() { - return fmt.Errorf("Attempting to resume non sleeping run %s", run.ID.String()) - } - - currentTaskRun := run.NextTaskRun() - if currentTaskRun == nil { - return fmt.Errorf("Attempting to resume sleeping run with no remaining tasks %s", run.ID.String()) - } - - if !currentTaskRun.Status.PendingSleep() { - return fmt.Errorf("Attempting to resume sleeping run with non sleeping task %s", run.ID.String()) +func validateOnMainChain(run *models.JobRun, taskRun *models.TaskRun, txManager store.TxManager) error { + txhash := run.RunRequest.TxHash + if txhash == nil || !taskRun.MinimumConfirmations.Valid || taskRun.MinimumConfirmations.Uint32 == 0 { + return nil } - adapter, err := prepareAdapter(currentTaskRun, run.Overrides, store) + receipt, err := txManager.GetTxReceipt(*txhash) if err != nil { - currentTaskRun.SetError(err) - run.SetError(err) - return store.SaveJobRun(run) - } - - if sleepAdapter, ok := adapter.BaseAdapter.(*adapters.Sleep); ok { - return performTaskSleep(run, currentTaskRun, sleepAdapter, store) + return err } - - return fmt.Errorf("Attempting to resume non sleeping task for run %s (%s)", run.ID.String(), currentTaskRun.TaskSpec.Type) -} - -func performTaskSleep( - run *models.JobRun, - task *models.TaskRun, - adapter *adapters.Sleep, - store *store.Store) error { - - duration := adapter.Duration() - if duration <= 0 { - logger.Debugw("Sleep duration has already elapsed, completing task", run.ForLogger()...) - task.Status = models.RunStatusCompleted - run.Status = models.RunStatusInProgress - return updateAndTrigger(run, store) + if invalidRequest(run.RunRequest, receipt) { + return fmt.Errorf( + "TxHash %s initiating run %s not on main chain; presumably has been uncled", + txhash.Hex(), + run.ID.String(), + ) } - - // XXX: This is to eliminate data race that occurs because slices share their - // underlying array even in copies - runCopy := *run - runCopy.TaskRuns = make([]models.TaskRun, len(run.TaskRuns)) - copy(runCopy.TaskRuns, run.TaskRuns) - - go func(run models.JobRun) { - logger.Debugw("Task sleeping...", run.ForLogger()...) - - <-store.Clock.After(duration) - - task := run.NextTaskRun() - task.Status = models.RunStatusCompleted - run.Status = models.RunStatusInProgress - - logger.Debugw("Waking job up after sleep", run.ForLogger()...) - - if err := updateAndTrigger(&run, store); err != nil { - logger.Errorw("Error resuming sleeping job:", "error", err) - } - }(runCopy) - return nil } -func validateMinimumConfirmations( - run *models.JobRun, - taskRun *models.TaskRun, - currentHeight *models.Big, - store *store.Store) { - - updateTaskRunConfirmations(currentHeight, run, taskRun) - if !meetsMinimumConfirmations(run, taskRun, run.ObservedHeight) { - logger.Debugw("Run cannot continue because it lacks sufficient confirmations", []interface{}{"run", run.ID.String(), "required_height", taskRun.MinimumConfirmations}...) - run.Status = models.RunStatusPendingConfirmations - } else if err := validateOnMainChain(run, taskRun, store); err != nil { - run.SetError(err) - } else { - logger.Debugw("Adding next task to job run queue", []interface{}{"run", run.ID.String(), "nextTask", taskRun.TaskSpec.Type}...) - run.Status = models.RunStatusInProgress - } -} - func updateTaskRunConfirmations(currentHeight *models.Big, jr *models.JobRun, taskRun *models.TaskRun) { if !taskRun.MinimumConfirmations.Valid || jr.CreationHeight == nil || currentHeight == nil { return @@ -388,26 +69,6 @@ func updateTaskRunConfirmations(currentHeight *models.Big, jr *models.JobRun, ta taskRun.Confirmations = clnull.Uint32From(uint32(diff.Int64())) } -func validateOnMainChain(jr *models.JobRun, taskRun *models.TaskRun, store *store.Store) error { - txhash := jr.RunRequest.TxHash - if txhash == nil || !taskRun.MinimumConfirmations.Valid || taskRun.MinimumConfirmations.Uint32 == 0 { - return nil - } - - receipt, err := store.TxManager.GetTxReceipt(*txhash) - if err != nil { - return err - } - if invalidRequest(jr.RunRequest, receipt) { - return fmt.Errorf( - "TxHash %s initiating run %s not on main chain; presumably has been uncled", - txhash.Hex(), - jr.ID.String(), - ) - } - return nil -} - func invalidRequest(request models.RunRequest, receipt *models.TxReceipt) bool { return receipt.Unconfirmed() || (request.BlockHash != nil && *request.BlockHash != *receipt.BlockHash) @@ -433,37 +94,3 @@ func blockConfirmations(currentHeight, creationHeight *models.Big) *big.Int { } return confs } - -func updateAndTrigger(run *models.JobRun, store *store.Store) error { - if err := store.SaveJobRun(run); err != nil { - return err - } - return triggerIfReady(run, store) -} - -func createAndTrigger(run *models.JobRun, store *store.Store) error { - if err := store.CreateJobRun(run); err != nil { - return errors.Wrap(err, "CreateJobRun failed") - } - return triggerIfReady(run, store) -} - -func triggerIfReady(run *models.JobRun, store *store.Store) error { - if run.Status == models.RunStatusInProgress { - logger.Debugw(fmt.Sprintf("Executing run originally initiated by %s", run.Initiator.Type), run.ForLogger()...) - return store.RunChannel.Send(run.ID) - } - - logger.Debugw(fmt.Sprintf("Pausing run originally initiated by %s", run.Initiator.Type), run.ForLogger()...) - return nil -} - -// RecurringScheduleJobError contains the field for the error message. -type RecurringScheduleJobError struct { - msg string -} - -// Error returns the error message for the run. -func (err RecurringScheduleJobError) Error() string { - return err.msg -} diff --git a/core/services/runs_test.go b/core/services/runs_test.go deleted file mode 100644 index 535b26ee416..00000000000 --- a/core/services/runs_test.go +++ /dev/null @@ -1,809 +0,0 @@ -package services_test - -import ( - "bytes" - "fmt" - "math" - "math/big" - "testing" - "time" - - "github.com/ethereum/go-ethereum/common" - "github.com/smartcontractkit/chainlink/core/adapters" - "github.com/smartcontractkit/chainlink/core/internal/cltest" - clnull "github.com/smartcontractkit/chainlink/core/null" - "github.com/smartcontractkit/chainlink/core/services" - "github.com/smartcontractkit/chainlink/core/store/assets" - "github.com/smartcontractkit/chainlink/core/store/models" - "github.com/smartcontractkit/chainlink/core/utils" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "github.com/tidwall/gjson" - null "gopkg.in/guregu/null.v3" -) - -func TestNewRun(t *testing.T) { - store, cleanup := cltest.NewStore(t) - defer cleanup() - - _, bt := cltest.NewBridgeType(t, "timecube", "http://http://timecube.2enp.com/") - bt.MinimumContractPayment = assets.NewLink(10) - require.NoError(t, store.CreateBridgeType(bt)) - - creationHeight := big.NewInt(1000) - - jobSpec := models.NewJob() - jobSpec.Tasks = []models.TaskSpec{{ - Type: "timecube", - }} - jobSpec.Initiators = []models.Initiator{{ - Type: models.InitiatorEthLog, - }} - - data := models.JSON{Result: gjson.Parse(`{"address":"0xdfcfc2b9200dbb10952c2b7cce60fc7260e03c6f"}`)} - run, err := services.NewRun(jobSpec, jobSpec.Initiators[0], &data, creationHeight, store, nil) - assert.NoError(t, err) - assert.Equal(t, string(models.RunStatusInProgress), string(run.Status)) - assert.Len(t, run.TaskRuns, 1) - assert.Equal(t, `{"address":"0xdfcfc2b9200dbb10952c2b7cce60fc7260e03c6f"}`, run.Overrides.String()) - assert.False(t, run.TaskRuns[0].Confirmations.Valid) -} - -func TestNewRun_MeetsMinimumPayment(t *testing.T) { - tests := []struct { - name string - MinJobPayment *assets.Link - RunPayment *assets.Link - meetsMinPayment bool - }{ - {"insufficient payment", assets.NewLink(100), assets.NewLink(10), false}, - {"sufficient payment (strictly greater)", assets.NewLink(1), assets.NewLink(10), true}, - {"sufficient payment (equal)", assets.NewLink(10), assets.NewLink(10), true}, - {"runs that do not accept payments must return true", assets.NewLink(10), nil, true}, - {"return true when minpayment is not specified in jobspec", nil, assets.NewLink(0), true}, - } - - for _, tt := range tests { - test := tt - t.Run(test.name, func(t *testing.T) { - actual := services.MeetsMinimumPayment(test.MinJobPayment, test.RunPayment) - assert.Equal(t, test.meetsMinPayment, actual) - }) - } -} - -func TestNewRun_jobSpecMinPayment(t *testing.T) { - store, cleanup := cltest.NewStore(t) - defer cleanup() - - data := models.JSON{Result: gjson.Parse(`{"address":"0xdfcfc2b9200dbb10952c2b7cce60fc7260e03c6f"}`)} - - tests := []struct { - name string - payment *assets.Link - minPayment *assets.Link - expectedStatus models.RunStatus - }{ - {"payment < min payment", assets.NewLink(9), assets.NewLink(10), models.RunStatusErrored}, - {"payment = min payment", assets.NewLink(10), assets.NewLink(10), models.RunStatusInProgress}, - {"payment > min payment", assets.NewLink(11), assets.NewLink(10), models.RunStatusInProgress}, - {"payment is nil", nil, assets.NewLink(10), models.RunStatusInProgress}, - {"minPayment is nil", nil, nil, models.RunStatusInProgress}, - } - - for _, tt := range tests { - test := tt - t.Run(test.name, func(t *testing.T) { - jobSpec := models.NewJob() - jobSpec.Tasks = []models.TaskSpec{{ - Type: "noop", - }} - jobSpec.Initiators = []models.Initiator{{ - Type: models.InitiatorEthLog, - }} - jobSpec.MinPayment = test.minPayment - - run, err := services.NewRun(jobSpec, jobSpec.Initiators[0], &data, nil, store, test.payment) - assert.NoError(t, err) - assert.Equal(t, string(test.expectedStatus), string(run.Status)) - }) - } -} - -func TestNewRun_taskSumPayment(t *testing.T) { - store, cleanup := cltest.NewStore(t) - defer cleanup() - - _, bta := cltest.NewBridgeType(t, "timecube_a", "http://http://timecube.2enp.com/") - bta.MinimumContractPayment = assets.NewLink(8) - require.NoError(t, store.CreateBridgeType(bta)) - - _, btb := cltest.NewBridgeType(t, "timecube_b", "http://http://timecube.2enp.com/") - btb.MinimumContractPayment = assets.NewLink(7) - require.NoError(t, store.CreateBridgeType(btb)) - - store.Config.Set("MINIMUM_CONTRACT_PAYMENT", "1") - - data := models.JSON{Result: gjson.Parse(`{"address":"0xdfcfc2b9200dbb10952c2b7cce60fc7260e03c6f"}`)} - - tests := []struct { - name string - payment *assets.Link - expectedStatus models.RunStatus - }{ - {"payment < min payment", assets.NewLink(15), models.RunStatusErrored}, - {"payment = min payment", assets.NewLink(16), models.RunStatusInProgress}, - {"payment > min payment", assets.NewLink(17), models.RunStatusInProgress}, - {"payment is nil", nil, models.RunStatusInProgress}, - } - - for _, tt := range tests { - test := tt - t.Run(test.name, func(t *testing.T) { - jobSpec := models.NewJob() - jobSpec.Tasks = []models.TaskSpec{ - {Type: "timecube_a"}, - {Type: "timecube_b"}, - {Type: "ethtx"}, - {Type: "noop"}, - } - jobSpec.Initiators = []models.Initiator{{ - Type: models.InitiatorEthLog, - }} - - run, err := services.NewRun(jobSpec, jobSpec.Initiators[0], &data, nil, store, test.payment) - assert.NoError(t, err) - assert.Equal(t, string(test.expectedStatus), string(run.Status)) - }) - } -} - -func TestNewRun_minimumConfirmations(t *testing.T) { - store, cleanup := cltest.NewStore(t) - defer cleanup() - - data := cltest.JSONFromString(t, `{"address":"0xdfcfc2b9200dbb10952c2b7cce60fc7260e03c6f"}`) - creationHeight := big.NewInt(1000) - - tests := []struct { - name string - configConfirmations uint32 - taskConfirmations uint32 - expectedStatus models.RunStatus - }{ - {"creates runnable job", 0, 0, models.RunStatusInProgress}, - {"requires minimum task confirmations", 2, 0, models.RunStatusPendingConfirmations}, - {"requires minimum config confirmations", 0, 2, models.RunStatusPendingConfirmations}, - } - - for _, tt := range tests { - test := tt - t.Run(test.name, func(t *testing.T) { - store.Config.Set("MIN_INCOMING_CONFIRMATIONS", test.configConfirmations) - - jobSpec := cltest.NewJobWithLogInitiator() - jobSpec.Tasks[0].Confirmations = clnull.Uint32From(test.taskConfirmations) - - run, err := services.NewRun( - jobSpec, - jobSpec.Initiators[0], - &data, - creationHeight, - store, - nil) - assert.NoError(t, err) - assert.Equal(t, string(test.expectedStatus), string(run.Status)) - require.Len(t, run.TaskRuns, 1) - max := utils.MaxUint32(test.taskConfirmations, test.configConfirmations) - assert.Equal(t, max, run.TaskRuns[0].MinimumConfirmations.Uint32) - }) - } -} - -func TestNewRun_startAtAndEndAt(t *testing.T) { - pastTime := cltest.ParseNullableTime(t, "2000-01-01T00:00:00.000Z") - futureTime := cltest.ParseNullableTime(t, "3000-01-01T00:00:00.000Z") - nullTime := null.Time{Valid: false} - - tests := []struct { - name string - startAt null.Time - endAt null.Time - errored bool - }{ - {"job not started", futureTime, nullTime, true}, - {"job started", pastTime, futureTime, false}, - {"job with no time range", nullTime, nullTime, false}, - {"job ended", nullTime, pastTime, true}, - } - - store, cleanup := cltest.NewStore(t) - defer cleanup() - clock := cltest.UseSettableClock(store) - clock.SetTime(time.Now()) - - for _, tt := range tests { - test := tt - t.Run(test.name, func(t *testing.T) { - job := cltest.NewJobWithWebInitiator() - job.StartAt = test.startAt - job.EndAt = test.endAt - assert.Nil(t, store.CreateJob(&job)) - - _, err := services.NewRun(job, job.Initiators[0], &models.JSON{}, nil, store, nil) - if test.errored { - assert.Error(t, err) - } else { - assert.NoError(t, err) - } - }) - } -} - -func TestNewRun_noTasksErrorsInsteadOfPanic(t *testing.T) { - store, cleanup := cltest.NewStore(t) - defer cleanup() - - job := cltest.NewJobWithWebInitiator() - job.Tasks = []models.TaskSpec{} - require.NoError(t, store.CreateJob(&job)) - - jr, err := services.NewRun(job, job.Initiators[0], &models.JSON{}, nil, store, nil) - assert.NoError(t, err) - assert.True(t, jr.HasError()) -} - -func TestResumePendingTask(t *testing.T) { - store, cleanup := cltest.NewStore(t) - defer cleanup() - - // reject a run with an invalid state - jobID := models.NewID() - runID := models.NewID() - run := &models.JobRun{ - ID: runID, - JobSpecID: jobID, - } - err := services.ResumePendingTask(run, store, models.BridgeRunResult{}) - assert.Error(t, err) - - // reject a run with no tasks - run = &models.JobRun{ - ID: runID, - JobSpecID: jobID, - Status: models.RunStatusPendingBridge, - } - err = services.ResumePendingTask(run, store, models.BridgeRunResult{}) - assert.Error(t, err) - - // input with error errors run - run.TaskRuns = []models.TaskRun{models.TaskRun{ID: models.NewID(), JobRunID: runID}} - err = services.ResumePendingTask(run, store, models.BridgeRunResult{Status: models.RunStatusErrored}) - assert.Error(t, err) - assert.True(t, run.FinishedAt.Valid) - - // completed input with remaining tasks should put task into pending - run = &models.JobRun{ - ID: runID, - JobSpecID: jobID, - Status: models.RunStatusPendingBridge, - TaskRuns: []models.TaskRun{models.TaskRun{ID: models.NewID(), JobRunID: runID}, models.TaskRun{ID: models.NewID(), JobRunID: runID}}, - } - input := models.JSON{Result: gjson.Parse(`{"address":"0xdfcfc2b9200dbb10952c2b7cce60fc7260e03c6f"}`)} - err = services.ResumePendingTask(run, store, models.BridgeRunResult{Data: input, Status: models.RunStatusCompleted}) - assert.Error(t, err) - assert.Equal(t, string(models.RunStatusInProgress), string(run.Status)) - assert.Len(t, run.TaskRuns, 2) - assert.Equal(t, string(models.RunStatusCompleted), string(run.TaskRuns[0].Status)) - - // completed input with no remaining tasks should get marked as complete - run = &models.JobRun{ - ID: runID, - JobSpecID: jobID, - Status: models.RunStatusPendingBridge, - TaskRuns: []models.TaskRun{models.TaskRun{ID: models.NewID(), JobRunID: runID}}, - } - err = services.ResumePendingTask(run, store, models.BridgeRunResult{Data: input, Status: models.RunStatusCompleted}) - assert.Error(t, err) - assert.Equal(t, string(models.RunStatusCompleted), string(run.Status)) - assert.True(t, run.FinishedAt.Valid) - assert.Len(t, run.TaskRuns, 1) - assert.Equal(t, string(models.RunStatusCompleted), string(run.TaskRuns[0].Status)) -} - -func TestResumeConfirmingTask(t *testing.T) { - store, cleanup := cltest.NewStore(t) - defer cleanup() - - // reject a run with an invalid state - jobID := models.NewID() - runID := models.NewID() - run := &models.JobRun{ - ID: runID, - JobSpecID: jobID, - } - err := services.ResumeConfirmingTask(run, store, nil) - assert.Error(t, err) - - // reject a run with no tasks - run = &models.JobRun{ - ID: runID, - JobSpecID: jobID, - Status: models.RunStatusPendingConfirmations, - } - err = services.ResumeConfirmingTask(run, store, nil) - assert.Error(t, err) - - jobSpec := models.JobSpec{ID: models.NewID()} - require.NoError(t, store.ORM.CreateJob(&jobSpec)) - - // leave in pending if not enough confirmations have been met yet - creationHeight := models.NewBig(big.NewInt(0)) - run = &models.JobRun{ - ID: models.NewID(), - JobSpecID: jobSpec.ID, - CreationHeight: creationHeight, - Status: models.RunStatusPendingConfirmations, - TaskRuns: []models.TaskRun{models.TaskRun{ - ID: models.NewID(), - MinimumConfirmations: clnull.Uint32From(2), - TaskSpec: models.TaskSpec{ - JobSpecID: jobSpec.ID, - Type: adapters.TaskTypeNoOp, - }, - }}, - } - require.NoError(t, store.CreateJobRun(run)) - err = services.ResumeConfirmingTask(run, store, creationHeight.ToInt()) - assert.NoError(t, err) - assert.Equal(t, string(models.RunStatusPendingConfirmations), string(run.Status)) - assert.Equal(t, uint32(1), run.TaskRuns[0].Confirmations.Uint32) - - // input, should go from pending -> in progress and save the input - run = &models.JobRun{ - ID: models.NewID(), - JobSpecID: jobSpec.ID, - CreationHeight: creationHeight, - Status: models.RunStatusPendingConfirmations, - TaskRuns: []models.TaskRun{models.TaskRun{ - ID: models.NewID(), - MinimumConfirmations: clnull.Uint32From(1), - TaskSpec: models.TaskSpec{ - JobSpecID: jobSpec.ID, - Type: adapters.TaskTypeNoOp, - }, - }}, - } - observedHeight := big.NewInt(1) - require.NoError(t, store.CreateJobRun(run)) - err = services.ResumeConfirmingTask(run, store, observedHeight) - assert.NoError(t, err) - assert.Equal(t, string(models.RunStatusInProgress), string(run.Status)) -} - -func TestResumeConnectingTask(t *testing.T) { - store, cleanup := cltest.NewStore(t) - defer cleanup() - - // reject a run with an invalid state - jobID := models.NewID() - runID := models.NewID() - run := &models.JobRun{ - ID: runID, - JobSpecID: jobID, - } - err := services.ResumeConnectingTask(run, store) - assert.Error(t, err) - - // reject a run with no tasks - run = &models.JobRun{ - ID: runID, - JobSpecID: jobID, - Status: models.RunStatusPendingConnection, - } - err = services.ResumeConnectingTask(run, store) - assert.Error(t, err) - - jobSpec := models.JobSpec{ID: models.NewID()} - require.NoError(t, store.ORM.CreateJob(&jobSpec)) - - taskSpec := models.TaskSpec{Type: adapters.TaskTypeNoOp, JobSpecID: jobSpec.ID} - // input, should go from pending -> in progress and save the input - run = &models.JobRun{ - ID: models.NewID(), - JobSpecID: jobSpec.ID, - Status: models.RunStatusPendingConnection, - TaskRuns: []models.TaskRun{models.TaskRun{ - ID: models.NewID(), - TaskSpec: taskSpec, - }}, - } - require.NoError(t, store.CreateJobRun(run)) - err = services.ResumeConnectingTask(run, store) - assert.NoError(t, err) - assert.Equal(t, string(models.RunStatusInProgress), string(run.Status)) -} - -func sleepAdapterParams(t testing.TB, n int) models.JSON { - d := time.Duration(n) - json := []byte(fmt.Sprintf(`{"until":%v}`, time.Now().Add(d*time.Second).Unix())) - return cltest.ParseJSON(t, bytes.NewBuffer(json)) -} - -func TestQueueSleepingTask(t *testing.T) { - store, cleanup := cltest.NewStore(t) - defer cleanup() - store.Clock = cltest.NeverClock{} - - // reject a run with an invalid state - jobID := models.NewID() - runID := models.NewID() - run := &models.JobRun{ - ID: runID, - JobSpecID: jobID, - } - err := services.QueueSleepingTask(run, store) - assert.Error(t, err) - - // reject a run with no tasks - run = &models.JobRun{ - ID: runID, - JobSpecID: jobID, - Status: models.RunStatusPendingSleep, - } - err = services.QueueSleepingTask(run, store) - assert.Error(t, err) - - jobSpec := models.JobSpec{ID: models.NewID()} - require.NoError(t, store.ORM.CreateJob(&jobSpec)) - - // reject a run that is sleeping but its task is not - run = &models.JobRun{ - ID: models.NewID(), - JobSpecID: jobSpec.ID, - Status: models.RunStatusPendingSleep, - TaskRuns: []models.TaskRun{models.TaskRun{ - ID: models.NewID(), - TaskSpec: models.TaskSpec{Type: adapters.TaskTypeSleep, JobSpecID: jobSpec.ID}, - }}, - } - require.NoError(t, store.CreateJobRun(run)) - err = services.QueueSleepingTask(run, store) - assert.Error(t, err) - - // error decoding params into adapter - inputFromTheFuture := cltest.ParseJSON(t, bytes.NewBuffer([]byte(`{"until": -1}`))) - run = &models.JobRun{ - ID: models.NewID(), - JobSpecID: jobSpec.ID, - Status: models.RunStatusPendingSleep, - TaskRuns: []models.TaskRun{ - models.TaskRun{ - ID: models.NewID(), - Status: models.RunStatusPendingSleep, - TaskSpec: models.TaskSpec{ - JobSpecID: jobSpec.ID, - Type: adapters.TaskTypeSleep, - Params: inputFromTheFuture, - }, - }, - }, - } - require.NoError(t, store.CreateJobRun(run)) - err = services.QueueSleepingTask(run, store) - assert.NoError(t, err) - assert.Equal(t, string(models.RunStatusErrored), string(run.TaskRuns[0].Status)) - assert.Equal(t, string(models.RunStatusErrored), string(run.Status)) - - // mark run as pending, task as completed if duration has already elapsed - run = &models.JobRun{ - ID: models.NewID(), - JobSpecID: jobSpec.ID, - Status: models.RunStatusPendingSleep, - TaskRuns: []models.TaskRun{models.TaskRun{ - ID: models.NewID(), - Status: models.RunStatusPendingSleep, - TaskSpec: models.TaskSpec{Type: adapters.TaskTypeSleep, JobSpecID: jobSpec.ID}, - }}, - } - require.NoError(t, store.CreateJobRun(run)) - err = services.QueueSleepingTask(run, store) - assert.NoError(t, err) - assert.Equal(t, string(models.RunStatusCompleted), string(run.TaskRuns[0].Status)) - assert.Equal(t, string(models.RunStatusInProgress), string(run.Status)) - - runRequest, open := <-store.RunChannel.Receive() - assert.True(t, open) - assert.Equal(t, run.ID, runRequest.ID) - -} - -func TestQueueSleepingTaskA_CompletesSleepingTaskAfterDurationElapsed_Happy(t *testing.T) { - store, cleanup := cltest.NewStore(t) - defer cleanup() - store.Clock = cltest.NeverClock{} - - jobSpec := models.JobSpec{ID: models.NewID()} - require.NoError(t, store.ORM.CreateJob(&jobSpec)) - - // queue up next run if duration has not elapsed yet - clock := cltest.UseSettableClock(store) - store.Clock = clock - clock.SetTime(time.Time{}) - - inputFromTheFuture := sleepAdapterParams(t, 60) - run := &models.JobRun{ - ID: models.NewID(), - JobSpecID: jobSpec.ID, - Status: models.RunStatusPendingSleep, - TaskRuns: []models.TaskRun{ - models.TaskRun{ - ID: models.NewID(), - Status: models.RunStatusPendingSleep, - TaskSpec: models.TaskSpec{ - JobSpecID: jobSpec.ID, - Type: adapters.TaskTypeSleep, - Params: inputFromTheFuture, - }, - }, - }, - } - require.NoError(t, store.CreateJobRun(run)) - err := services.QueueSleepingTask(run, store) - assert.NoError(t, err) - assert.Equal(t, string(models.RunStatusPendingSleep), string(run.TaskRuns[0].Status)) - assert.Equal(t, string(models.RunStatusPendingSleep), string(run.Status)) - - // force the duration elapse - clock.SetTime((time.Time{}).Add(math.MaxInt64)) - runRequest, open := <-store.RunChannel.Receive() - assert.True(t, open) - assert.Equal(t, run.ID, runRequest.ID) - - *run, err = store.ORM.FindJobRun(run.ID) - assert.NoError(t, err) - assert.Equal(t, string(models.RunStatusCompleted), string(run.TaskRuns[0].Status)) - assert.Equal(t, string(models.RunStatusInProgress), string(run.Status)) -} - -func TestQueueSleepingTaskA_CompletesSleepingTaskAfterDurationElapsed_Archived(t *testing.T) { - store, cleanup := cltest.NewStore(t) - defer cleanup() - store.Clock = cltest.NeverClock{} - - jobSpec := models.JobSpec{ID: models.NewID()} - require.NoError(t, store.ORM.CreateJob(&jobSpec)) - - // queue up next run if duration has not elapsed yet - clock := cltest.UseSettableClock(store) - store.Clock = clock - clock.SetTime(time.Time{}) - - inputFromTheFuture := sleepAdapterParams(t, 60) - run := &models.JobRun{ - ID: models.NewID(), - JobSpecID: jobSpec.ID, - Status: models.RunStatusPendingSleep, - TaskRuns: []models.TaskRun{ - models.TaskRun{ - ID: models.NewID(), - Status: models.RunStatusPendingSleep, - TaskSpec: models.TaskSpec{ - JobSpecID: jobSpec.ID, - Type: adapters.TaskTypeSleep, - Params: inputFromTheFuture, - }, - }, - }, - } - require.NoError(t, store.CreateJobRun(run)) - require.NoError(t, store.ArchiveJob(jobSpec.ID)) - - unscoped := store.Unscoped() - err := services.QueueSleepingTask(run, unscoped) - assert.NoError(t, err) - assert.Equal(t, string(models.RunStatusPendingSleep), string(run.TaskRuns[0].Status)) - assert.Equal(t, string(models.RunStatusPendingSleep), string(run.Status)) - - // force the duration elapse - clock.SetTime((time.Time{}).Add(math.MaxInt64)) - runRequest, open := <-store.RunChannel.Receive() - assert.True(t, open) - assert.Equal(t, run.ID, runRequest.ID) - - require.Error(t, utils.JustError(store.FindJobRun(run.ID)), "archived runs should not be visible to normal store") - - *run, err = unscoped.FindJobRun(run.ID) - assert.NoError(t, err) - assert.Equal(t, string(models.RunStatusCompleted), string(run.TaskRuns[0].Status)) - assert.Equal(t, string(models.RunStatusInProgress), string(run.Status)) -} - -func TestExecuteJob_DoesNotSaveToTaskSpec(t *testing.T) { - t.Parallel() - app, cleanup := cltest.NewApplication(t) - defer cleanup() - - store := app.Store - eth := cltest.MockEthOnStore(t, store) - eth.Register("eth_chainId", store.Config.ChainID()) - - app.Start() - - job := cltest.NewJobWithWebInitiator() - job.Tasks = []models.TaskSpec{cltest.NewTask(t, "NoOp")} // empty params - require.NoError(t, store.CreateJob(&job)) - - initr := job.Initiators[0] - data := cltest.JSONFromString(t, `{"random": "input"}`) - jr, err := services.ExecuteJob( - job, - initr, - &data, - nil, - store, - ) - require.NoError(t, err) - cltest.WaitForJobRunToComplete(t, store, *jr) - - retrievedJob, err := store.FindJob(job.ID) - require.NoError(t, err) - require.Len(t, job.Tasks, 1) - require.Len(t, retrievedJob.Tasks, 1) - assert.Equal(t, job.Tasks[0].Params, retrievedJob.Tasks[0].Params) -} - -func TestExecuteJobWithRunRequest(t *testing.T) { - t.Parallel() - app, cleanup := cltest.NewApplication(t) - defer cleanup() - - store := app.Store - eth := cltest.MockEthOnStore(t, store) - eth.Register("eth_chainId", store.Config.ChainID()) - - app.Start() - - job := cltest.NewJobWithRunLogInitiator() - job.Tasks = []models.TaskSpec{cltest.NewTask(t, "NoOp")} // empty params - require.NoError(t, store.CreateJob(&job)) - - requestID := "RequestID" - data := cltest.JSONFromString(t, `{"random": "input"}`) - initr := job.Initiators[0] - rr := models.NewRunRequest() - rr.RequestID = &requestID - jr, err := services.ExecuteJobWithRunRequest( - job, - initr, - &data, - nil, - store, - rr, - ) - require.NoError(t, err) - updatedJR := cltest.WaitForJobRunToComplete(t, store, *jr) - assert.Equal(t, rr.RequestID, updatedJR.RunRequest.RequestID) -} - -func TestExecuteJobWithRunRequest_fromRunLog_Happy(t *testing.T) { - t.Parallel() - - initiatingTxHash := cltest.NewHash() - triggeringBlockHash := cltest.NewHash() - otherBlockHash := cltest.NewHash() - - tests := []struct { - name string - logBlockHash common.Hash - receiptBlockHash common.Hash - wantStatus models.RunStatus - }{ - { - name: "main chain", - logBlockHash: triggeringBlockHash, - receiptBlockHash: triggeringBlockHash, - wantStatus: models.RunStatusCompleted, - }, - { - name: "ommered chain", - logBlockHash: triggeringBlockHash, - receiptBlockHash: otherBlockHash, - wantStatus: models.RunStatusErrored, - }, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - config, cfgCleanup := cltest.NewConfig(t) - defer cfgCleanup() - minimumConfirmations := uint32(2) - config.Set("MIN_INCOMING_CONFIRMATIONS", minimumConfirmations) - app, cleanup := cltest.NewApplicationWithConfig(t, config) - defer cleanup() - - eth := app.MockEthCallerSubscriber() - app.Start() - - store := app.GetStore() - job := cltest.NewJobWithRunLogInitiator() - job.Tasks = []models.TaskSpec{cltest.NewTask(t, "NoOp")} - require.NoError(t, store.CreateJob(&job)) - - creationHeight := big.NewInt(1) - requestID := "RequestID" - initr := job.Initiators[0] - rr := models.NewRunRequest() - rr.RequestID = &requestID - rr.TxHash = &initiatingTxHash - rr.BlockHash = &test.logBlockHash - data := cltest.JSONFromString(t, `{"random": "input"}`) - jr, err := services.ExecuteJobWithRunRequest(job, initr, &data, creationHeight, store, rr) - require.NoError(t, err) - cltest.WaitForJobRunToPendConfirmations(t, app.Store, *jr) - - confirmedReceipt := models.TxReceipt{ - Hash: initiatingTxHash, - BlockHash: &test.receiptBlockHash, - BlockNumber: cltest.Int(3), - } - eth.Context("validateOnMainChain", func(ethMock *cltest.EthMock) { - eth.Register("eth_getTransactionReceipt", confirmedReceipt) - }) - - err = services.ResumeConfirmingTask(jr, store, big.NewInt(2)) - require.NoError(t, err) - updatedJR := cltest.WaitForJobRunStatus(t, store, *jr, test.wantStatus) - assert.Equal(t, rr.RequestID, updatedJR.RunRequest.RequestID) - assert.Equal(t, minimumConfirmations, updatedJR.TaskRuns[0].MinimumConfirmations.Uint32) - assert.True(t, updatedJR.TaskRuns[0].MinimumConfirmations.Valid) - assert.Equal(t, minimumConfirmations, updatedJR.TaskRuns[0].Confirmations.Uint32, "task run should track its current confirmations") - assert.True(t, updatedJR.TaskRuns[0].Confirmations.Valid) - assert.True(t, eth.AllCalled(), eth.Remaining()) - }) - } -} - -func TestExecuteJobWithRunRequest_fromRunLog_ConnectToLaggingEthNode(t *testing.T) { - t.Parallel() - - initiatingTxHash := cltest.NewHash() - triggeringBlockHash := cltest.NewHash() - - config, cfgCleanup := cltest.NewConfig(t) - defer cfgCleanup() - minimumConfirmations := uint32(2) - config.Set("MIN_INCOMING_CONFIRMATIONS", minimumConfirmations) - app, cleanup := cltest.NewApplicationWithConfig(t, config) - defer cleanup() - - eth := app.MockEthCallerSubscriber() - app.MockStartAndConnect() - - store := app.GetStore() - job := cltest.NewJobWithRunLogInitiator() - job.Tasks = []models.TaskSpec{cltest.NewTask(t, "NoOp")} - require.NoError(t, store.CreateJob(&job)) - - requestID := "RequestID" - initr := job.Initiators[0] - rr := models.NewRunRequest() - rr.RequestID = &requestID - rr.TxHash = &initiatingTxHash - rr.BlockHash = &triggeringBlockHash - - futureCreationHeight := big.NewInt(9) - pastCurrentHeight := big.NewInt(1) - - data := cltest.JSONFromString(t, `{"random": "input"}`) - jr, err := services.ExecuteJobWithRunRequest(job, initr, &data, futureCreationHeight, store, rr) - require.NoError(t, err) - cltest.WaitForJobRunToPendConfirmations(t, app.Store, *jr) - - err = services.ResumeConfirmingTask(jr, store, pastCurrentHeight) - require.NoError(t, err) - updatedJR := cltest.WaitForJobRunToPendConfirmations(t, app.Store, *jr) - assert.True(t, updatedJR.TaskRuns[0].Confirmations.Valid) - assert.Equal(t, uint32(0), updatedJR.TaskRuns[0].Confirmations.Uint32) - assert.True(t, eth.AllCalled(), eth.Remaining()) -} diff --git a/core/services/scheduler.go b/core/services/scheduler.go index e6998e8e544..309fde94c64 100644 --- a/core/services/scheduler.go +++ b/core/services/scheduler.go @@ -18,20 +18,23 @@ type Scheduler struct { Recurring *Recurring OneTime *OneTime store *store.Store + runManager RunManager startedMutex sync.RWMutex started bool } // NewScheduler initializes the Scheduler instances with both Recurring // and OneTime fields since jobs can contain tasks which utilize both. -func NewScheduler(store *store.Store) *Scheduler { +func NewScheduler(store *store.Store, runManager RunManager) *Scheduler { return &Scheduler{ - Recurring: NewRecurring(store), + Recurring: NewRecurring(runManager), OneTime: &OneTime{ - Store: store, - Clock: store.Clock, + Store: store, + Clock: store.Clock, + RunManager: runManager, }, - store: store, + store: store, + runManager: runManager, } } @@ -53,7 +56,7 @@ func (s *Scheduler) Start() error { } s.started = true - return s.store.Jobs(func(j models.JobSpec) bool { + return s.store.Jobs(func(j *models.JobSpec) bool { s.addJob(j) return true }) @@ -71,9 +74,9 @@ func (s *Scheduler) Stop() { } } -func (s *Scheduler) addJob(job models.JobSpec) { - s.Recurring.AddJob(job) - s.OneTime.AddJob(job) +func (s *Scheduler) addJob(job *models.JobSpec) { + s.Recurring.AddJob(*job) + s.OneTime.AddJob(*job) } // AddJob is the governing function for Recurring and OneTime, @@ -84,23 +87,22 @@ func (s *Scheduler) AddJob(job models.JobSpec) { if !s.started { return } - s.addJob(job) + s.addJob(&job) } // Recurring is used for runs that need to execute on a schedule, // and is configured with cron. // Instances of Recurring must be initialized using NewRecurring(). type Recurring struct { - Cron Cron - Clock utils.Nower - store *store.Store + Cron Cron + Clock utils.Nower + runManager RunManager } // NewRecurring create a new instance of Recurring, ready to use. -func NewRecurring(store *store.Store) *Recurring { +func NewRecurring(runManager RunManager) *Recurring { return &Recurring{ - store: store, - Clock: store.Clock, + runManager: runManager, } } @@ -120,29 +122,22 @@ func (r *Recurring) Stop() { // AddJob looks for "cron" initiators, adds them to cron's schedule // for execution when specified. func (r *Recurring) AddJob(job models.JobSpec) { - for _, i := range job.InitiatorsFor(models.InitiatorCron) { - initr := i - if !job.Ended(r.Clock.Now()) { - archived := false - r.Cron.AddFunc(string(initr.Schedule), func() { - if archived || r.store.Archived(job.ID) { - archived = true - return - } - _, err := ExecuteJob(job, initr, &models.JSON{}, nil, r.store) - if err != nil && !expectedRecurringScheduleJobError(err) { - logger.Errorw(err.Error()) - } - }) - } + for _, initr := range job.InitiatorsFor(models.InitiatorCron) { + r.Cron.AddFunc(string(initr.Schedule), func() { + _, err := r.runManager.Create(job.ID, &initr, &models.JSON{}, nil, &models.RunRequest{}) + if err != nil && !expectedRecurringScheduleJobError(err) { + logger.Errorw(err.Error()) + } + }) } } // OneTime represents runs that are to be executed only once. type OneTime struct { - Store *store.Store - Clock utils.Afterer - done chan struct{} + Store *store.Store + Clock utils.Afterer + RunManager RunManager + done chan struct{} } // Start allocates a channel for the "done" field with an empty struct. @@ -153,8 +148,13 @@ func (ot *OneTime) Start() error { // AddJob runs the job at the time specified for the "runat" initiator. func (ot *OneTime) AddJob(job models.JobSpec) { - for _, initr := range job.InitiatorsFor(models.InitiatorRunAt) { - go ot.RunJobAt(initr, job) + for _, initiator := range job.InitiatorsFor(models.InitiatorRunAt) { + if !initiator.Time.Valid { + logger.Errorf("RunJobAt: JobSpec %s must have initiator with valid run at time: %v", job.ID, initiator) + continue + } + + go ot.RunJobAt(initiator, job) } } @@ -165,27 +165,17 @@ func (ot *OneTime) Stop() { // RunJobAt wait until the Stop() function has been called on the run // or the specified time for the run is after the present time. -func (ot *OneTime) RunJobAt(initr models.Initiator, job models.JobSpec) { - if !initr.Time.Valid { - logger.Errorf("RunJobAt: JobSpec %s must have initiator with valid run at time: %v", job.ID.String(), initr) - return - } +func (ot *OneTime) RunJobAt(initiator models.Initiator, job models.JobSpec) { select { case <-ot.done: - case <-ot.Clock.After(utils.DurationFromNow(initr.Time.Time)): - if ot.Store.Archived(job.ID) { - return - } - if err := ot.Store.MarkRan(&initr, true); err != nil { + case <-ot.Clock.After(utils.DurationFromNow(initiator.Time.Time)): + _, err := ot.RunManager.Create(job.ID, &initiator, &models.JSON{}, nil, &models.RunRequest{}) + if err != nil { logger.Error(err.Error()) return } - _, err := ExecuteJob(job, initr, &models.JSON{}, nil, ot.Store) - if err != nil { + if err := ot.Store.MarkRan(&initiator, true); err != nil { logger.Error(err.Error()) - if err := ot.Store.MarkRan(&initr, false); err != nil { - logger.Error(err.Error()) - } } } } diff --git a/core/services/scheduler_test.go b/core/services/scheduler_test.go index 442c30c8d23..e363dfcadde 100644 --- a/core/services/scheduler_test.go +++ b/core/services/scheduler_test.go @@ -4,14 +4,11 @@ import ( "testing" "time" - "github.com/onsi/gomega" "github.com/smartcontractkit/chainlink/core/internal/cltest" + "github.com/smartcontractkit/chainlink/core/internal/mocks" "github.com/smartcontractkit/chainlink/core/services" - "github.com/smartcontractkit/chainlink/core/store/models" - "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" - "github.com/tevino/abool" - null "gopkg.in/guregu/null.v3" ) func TestScheduler_Start_LoadingRecurringJobs(t *testing.T) { @@ -25,322 +22,102 @@ func TestScheduler_Start_LoadingRecurringJobs(t *testing.T) { jobWoCron := cltest.NewJob() require.NoError(t, store.CreateJob(&jobWoCron)) - sched := services.NewScheduler(store) - require.NoError(t, sched.Start()) - defer sched.Stop() - - cltest.WaitForRunsAtLeast(t, jobWCron, store, 1) - cltest.WaitForRuns(t, jobWoCron, store, 0) -} - -func TestScheduler_AddJob_WhenStopped(t *testing.T) { - t.Parallel() - - store, cleanup := cltest.NewStore(t) - defer cleanup() - sched := services.NewScheduler(store) - defer sched.Stop() - - j := cltest.NewJobWithSchedule("* * * * *") - assert.Nil(t, store.CreateJob(&j)) - sched.AddJob(j) - - cltest.WaitForRuns(t, j, store, 0) -} - -func TestScheduler_Start_AddingUnstartedJob(t *testing.T) { - store, cleanupStore := cltest.NewStore(t) - defer cleanupStore() - clock := cltest.UseSettableClock(store) - - startAt := cltest.ParseISO8601(t, "3000-01-01T00:00:00.000Z") - j := cltest.NewJobWithSchedule("* * * * *") - j.StartAt = cltest.NullableTime(startAt) - assert.Nil(t, store.CreateJob(&j)) + executeJobChannel := make(chan struct{}) + runManager := new(mocks.RunManager) + runManager.On("Create", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything). + Return(nil, nil). + Twice(). + Run(func(mock.Arguments) { + executeJobChannel <- struct{}{} + }) - sched := services.NewScheduler(store) - defer sched.Stop() - assert.Nil(t, sched.Start()) + sched := services.NewScheduler(store, runManager) + require.NoError(t, sched.Start()) - gomega.NewGomegaWithT(t).Consistently(func() int { - runs, err := store.JobRunsFor(j.ID) - assert.NoError(t, err) - return len(runs) - }, (2 * time.Second)).Should(gomega.Equal(0)) + cltest.CallbackOrTimeout(t, "Create", func() { + <-executeJobChannel + <-executeJobChannel + }, 3*time.Second) - clock.SetTime(startAt) + sched.Stop() - cltest.WaitForRunsAtLeast(t, j, store, 2) + runManager.AssertExpectations(t) } func TestRecurring_AddJob(t *testing.T) { - nullTime := cltest.NullTime(t, nil) - pastTime := cltest.NullTime(t, "2000-01-01T00:00:00.000Z") - futureTime := cltest.NullTime(t, "3000-01-01T00:00:00.000Z") - tests := []struct { - name string - startAt null.Time - endAt null.Time - wantEntries int - wantRuns int - }{ - {"before start at", futureTime, nullTime, 1, 0}, - {"before end at", nullTime, futureTime, 1, 1}, - {"after start at", pastTime, nullTime, 1, 1}, - {"after end at", nullTime, pastTime, 0, 0}, - {"no range", nullTime, nullTime, 1, 1}, - {"start at after end at", futureTime, pastTime, 0, 0}, - } - - store, cleanup := cltest.NewStore(t) - defer cleanup() - for _, tt := range tests { - test := tt - t.Run(test.name, func(t *testing.T) { - r := services.NewRecurring(store) - cron := cltest.NewMockCron() - r.Cron = cron - defer r.Stop() - - job := cltest.NewJobWithSchedule("* * * * *") - job.StartAt = test.startAt - job.EndAt = test.endAt - - require.NoError(t, store.CreateJob(&job)) - r.AddJob(job) - - assert.Equal(t, test.wantEntries, len(cron.Entries)) - - cron.RunEntries() - jobRuns, err := store.JobRunsFor(job.ID) - assert.NoError(t, err) - assert.Equal(t, test.wantRuns, len(jobRuns)) - }) - } -} - -func TestRecurring_AddJob_Archived(t *testing.T) { - store, cleanup := cltest.NewStore(t) - defer cleanup() - r := services.NewRecurring(store) + executeJobChannel := make(chan struct{}, 1) + runManager := new(mocks.RunManager) + runManager.On("Create", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything). + Return(nil, nil). + Run(func(mock.Arguments) { + executeJobChannel <- struct{}{} + }). + Twice() + + r := services.NewRecurring(runManager) cron := cltest.NewMockCron() r.Cron = cron - defer r.Stop() job := cltest.NewJobWithSchedule("* * * * *") - require.NoError(t, store.CreateJob(&job)) r.AddJob(job) cron.RunEntries() - count, err := store.Unscoped().JobRunsCountFor(job.ID) - require.NoError(t, err) - assert.Equal(t, 1, count) - - require.NoError(t, store.ArchiveJob(job.ID)) - cron.RunEntries() - - count, err = store.Unscoped().JobRunsCountFor(job.ID) - require.NoError(t, err) - assert.Equal(t, 1, count) -} -func TestOneTime_AddJob(t *testing.T) { - nullTime := cltest.NullTime(t, nil) - pastTime := cltest.NullTime(t, "2000-01-01T00:00:00.000Z") - futureTime := cltest.NullTime(t, "3000-01-01T00:00:00.000Z") - pastRunTime := time.Now().Add(time.Hour * -1) - tests := []struct { - name string - startAt null.Time - endAt null.Time - runAt time.Time - wantCompleted bool - }{ - {"run at before start at", futureTime, nullTime, pastRunTime, false}, - {"run at before end at", nullTime, futureTime, pastRunTime, true}, - {"run at after start at", pastTime, nullTime, pastRunTime, true}, - {"run at after end at", nullTime, pastTime, pastRunTime, false}, - {"no range", nullTime, nullTime, pastRunTime, true}, - {"start at after end at", futureTime, pastTime, pastRunTime, false}, - } + cltest.CallbackOrTimeout(t, "Create", func() { + <-executeJobChannel + }, 3*time.Second) - store, cleanup := cltest.NewStore(t) - defer cleanup() - jobRunner, cleanup := cltest.NewJobRunner(store) - defer cleanup() - jobRunner.Start() - - for _, tt := range tests { - test := tt - t.Run(test.name, func(t *testing.T) { - ot := services.OneTime{ - Clock: store.Clock, - Store: store, - } - require.NoError(t, ot.Start()) - defer ot.Stop() - - j := cltest.NewJobWithRunAtInitiator(test.runAt) - require.Nil(t, store.CreateJob(&j)) - - j.StartAt = test.startAt - j.EndAt = test.endAt - - ot.AddJob(j) - - tester := func() bool { - completed := false - jobRuns, err := store.JobRunsFor(j.ID) - require.NoError(t, err) - if (len(jobRuns) > 0) && (jobRuns[0].Status == models.RunStatusCompleted) { - completed = true - } - return completed - } - - if test.wantCompleted { - gomega.NewGomegaWithT(t).Eventually(tester).Should(gomega.Equal(true)) - } else { - gomega.NewGomegaWithT(t).Consistently(tester).Should(gomega.Equal(false)) - } - - }) - } -} - -func TestOneTime_RunJobAt_StopJobBeforeExecution(t *testing.T) { - t.Parallel() - - store, cleanup := cltest.NewStore(t) - defer cleanup() - - ot := services.OneTime{ - Clock: &cltest.NeverClock{}, - Store: store, - } - ot.Start() - j := cltest.NewJobWithRunAtInitiator(time.Now().Add(time.Hour)) - assert.Nil(t, store.CreateJob(&j)) - initr := j.Initiators[0] - - finished := abool.New() - go func() { - ot.RunJobAt(initr, j) - finished.Set() - }() - - ot.Stop() - - gomega.NewGomegaWithT(t).Eventually(func() bool { - return finished.IsSet() - }).Should(gomega.Equal(true)) - jobRuns, err := store.JobRunsFor(j.ID) - assert.NoError(t, err) - assert.Equal(t, 0, len(jobRuns)) -} - -func TestOneTime_RunJobAt_ExecuteLateJob(t *testing.T) { - t.Parallel() - - store, cleanup := cltest.NewStore(t) - defer cleanup() + cron.RunEntries() - ot := services.OneTime{ - Clock: store.Clock, - Store: store, - } - j := cltest.NewJobWithRunAtInitiator(time.Now().Add(time.Hour * -1)) - assert.Nil(t, store.CreateJob(&j)) - initr := j.Initiators[0] - initr.ID = j.Initiators[0].ID - initr.JobSpecID = j.ID + cltest.CallbackOrTimeout(t, "Create", func() { + <-executeJobChannel + }, 3*time.Second) - finished := abool.New() - go func() { - ot.RunJobAt(initr, j) - finished.Set() - }() + r.Stop() - gomega.NewGomegaWithT(t).Eventually(func() bool { - return finished.IsSet() - }).Should(gomega.Equal(true)) - jobRuns, err := store.JobRunsFor(j.ID) - assert.NoError(t, err) - assert.Equal(t, 1, len(jobRuns)) + runManager.AssertExpectations(t) } -func TestOneTime_RunJobAt_RunTwice(t *testing.T) { - t.Parallel() - +func TestOneTime_AddJob(t *testing.T) { store, cleanup := cltest.NewStore(t) defer cleanup() - ot := services.OneTime{ - Clock: store.Clock, - Store: store, - } - - j := cltest.NewJobWithRunAtInitiator(time.Now()) - assert.NoError(t, store.CreateJob(&j)) - ot.RunJobAt(j.Initiators[0], j) - - j2, err := ot.Store.FindJob(j.ID) - require.NoError(t, err) - require.Len(t, j2.Initiators, 1) - ot.RunJobAt(j2.Initiators[0], j2) - - jobRuns, err := store.JobRunsFor(j.ID) - assert.NoError(t, err) - assert.Equal(t, 1, len(jobRuns)) -} + executeJobChannel := make(chan struct{}) + runManager := new(mocks.RunManager) + runManager.On("Create", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything). + Return(nil, nil). + Once(). + Run(func(mock.Arguments) { + executeJobChannel <- struct{}{} + }) -func TestOneTime_RunJobAt_UnstartedRun(t *testing.T) { - t.Parallel() - - store, cleanup := cltest.NewStore(t) - defer cleanup() + clock := cltest.NewTriggerClock(t) ot := services.OneTime{ - Clock: store.Clock, - Store: store, + Clock: clock, + Store: store, + RunManager: runManager, } + require.NoError(t, ot.Start()) j := cltest.NewJobWithRunAtInitiator(time.Now()) - j.EndAt = cltest.NullTime(t, "2000-01-01T00:10:00.000Z") - assert.NoError(t, store.CreateJob(&j)) + require.Nil(t, store.CreateJob(&j)) - ot.RunJobAt(j.Initiators[0], j) + ot.AddJob(j) - j2, err := store.FindJob(j.ID) - require.NoError(t, err) - require.Len(t, j2.Initiators, 1) - assert.Equal(t, false, j2.Initiators[0].Ran) -} + clock.Trigger() -func TestOneTime_RunJobAt_ArchivedRun(t *testing.T) { - t.Parallel() + cltest.CallbackOrTimeout(t, "ws client restarts", func() { + <-executeJobChannel + }, 3*time.Second) - store, cleanup := cltest.NewStore(t) - defer cleanup() + // This should block because if OneTime works it won't listen on the channel again + go clock.Trigger() - ot := services.OneTime{ - Clock: cltest.InstantClock{}, - Store: store, - } + // Sleep for some time to make sure another call isn't made + time.Sleep(1 * time.Second) - j := cltest.NewJobWithRunAtInitiator(time.Now()) - j.EndAt = cltest.NullTime(t, "2000-01-01T00:10:00.000Z") - require.NoError(t, store.CreateJob(&j)) - require.NoError(t, store.ArchiveJob(j.ID)) - - ot.RunJobAt(j.Initiators[0], j) + ot.Stop() - unscoped := store.Unscoped() - j2, err := unscoped.FindJob(j.ID) - require.NoError(t, err) - require.Len(t, j2.Initiators, 1) - assert.Equal(t, false, j2.Initiators[0].Ran) - count, err := unscoped.JobRunsCountFor(j.ID) - require.NoError(t, err) - assert.Equal(t, 0, count) + runManager.AssertExpectations(t) } diff --git a/core/services/subscription.go b/core/services/subscription.go index bf79fe6aa83..b2710cdcb5a 100644 --- a/core/services/subscription.go +++ b/core/services/subscription.go @@ -29,7 +29,7 @@ type JobSubscription struct { // StartJobSubscription constructs a JobSubscription which listens for and // tracks event logs corresponding to the specified job. Ignores any errors if // there is at least one successful subscription to an initiator log. -func StartJobSubscription(job models.JobSpec, head *models.Head, store *strpkg.Store) (JobSubscription, error) { +func StartJobSubscription(job models.JobSpec, head *models.Head, store *strpkg.Store, runManager RunManager) (JobSubscription, error) { var merr error var unsubscribers []Unsubscriber @@ -40,7 +40,7 @@ func StartJobSubscription(job models.JobSpec, head *models.Head, store *strpkg.S ) for _, initr := range initrs { - unsubscriber, err := NewInitiatorSubscription(initr, job, store, head, ReceiveLogRequest) + unsubscriber, err := NewInitiatorSubscription(initr, job, store, runManager, head, ReceiveLogRequest) if err == nil { unsubscribers = append(unsubscribers, unsubscriber) } else { @@ -68,10 +68,11 @@ func (js JobSubscription) Unsubscribe() { // to the callback. type InitiatorSubscription struct { *ManagedSubscription - Job models.JobSpec - Initiator models.Initiator - store *strpkg.Store - callback func(*strpkg.Store, models.LogRequest) + runManager RunManager + JobSpecID models.ID + Initiator models.Initiator + store *strpkg.Store + callback func(*strpkg.Store, RunManager, models.LogRequest) } // NewInitiatorSubscription creates a new InitiatorSubscription that feeds received @@ -80,8 +81,9 @@ func NewInitiatorSubscription( initr models.Initiator, job models.JobSpec, store *strpkg.Store, + runManager RunManager, head *models.Head, - callback func(*strpkg.Store, models.LogRequest), + callback func(*strpkg.Store, RunManager, models.LogRequest), ) (InitiatorSubscription, error) { nextHead := head.NextInt() // Exclude current block from subscription if replayFromBlock := store.Config.ReplayFromBlock(); replayFromBlock >= 0 { @@ -97,10 +99,11 @@ func NewInitiatorSubscription( } sub := InitiatorSubscription{ - Job: job, - Initiator: initr, - store: store, - callback: callback, + JobSpecID: *job.ID, + runManager: runManager, + Initiator: initr, + store: store, + callback: callback, } managedSub, err := NewManagedSubscription(store, filter, sub.dispatchLog) @@ -114,36 +117,31 @@ func NewInitiatorSubscription( } func (sub InitiatorSubscription) dispatchLog(log models.Log) { - logger.Debugw(fmt.Sprintf("Log for %v initiator for job %s", sub.Initiator.Type, sub.Job.ID.String()), - "txHash", log.TxHash.Hex(), "logIndex", log.Index, "blockNumber", log.BlockNumber, "job", sub.Job.ID.String()) + logger.Debugw(fmt.Sprintf("Log for %v initiator for job %s", sub.Initiator.Type, sub.JobSpecID.String()), + "txHash", log.TxHash.Hex(), "logIndex", log.Index, "blockNumber", log.BlockNumber, "job", sub.JobSpecID.String()) base := models.InitiatorLogEvent{ - JobSpec: sub.Job, + JobSpecID: sub.JobSpecID, Initiator: sub.Initiator, Log: log, } - sub.callback(sub.store, base.LogRequest()) + sub.callback(sub.store, sub.runManager, base.LogRequest()) } func loggerLogListening(initr models.Initiator, blockNumber *big.Int) { - msg := fmt.Sprintf( - "Listening for %v from block %v for address %v for job %s", - initr.Type, - presenters.FriendlyBigInt(blockNumber), - utils.LogListeningAddress(initr.Address), - initr.JobSpecID.String()) - logger.Infow(msg) + msg := fmt.Sprintf("Listening for %v from block %v", initr.Type, presenters.FriendlyBigInt(blockNumber)) + logger.Infow(msg, "address", utils.LogListeningAddress(initr.Address), "jobID", initr.JobSpecID.String()) } // ReceiveLogRequest parses the log and runs the job indicated by a RunLog or // ServiceAgreementExecutionLog. (Both log events have the same format.) -func ReceiveLogRequest(store *strpkg.Store, le models.LogRequest) { +func ReceiveLogRequest(store *strpkg.Store, runManager RunManager, le models.LogRequest) { if !le.Validate() { return } if le.GetLog().Removed { - logger.Debugw("Skipping run for removed log", "log", le.GetLog(), "jobId", le.GetJobSpec().ID.String()) + logger.Debugw("Skipping run for removed log", "log", le.GetLog(), "jobId", le.GetJobSpecID().String()) return } @@ -154,15 +152,15 @@ func ReceiveLogRequest(store *strpkg.Store, le models.LogRequest) { return } - runJob(store, le, &data) + runJob(store, runManager, le, data) } -func runJob(store *strpkg.Store, le models.LogRequest, data *models.JSON) { - jobSpec := le.GetJobSpec() +func runJob(store *strpkg.Store, runManager RunManager, le models.LogRequest, data models.JSON) { + jobSpecID := le.GetJobSpecID() initiator := le.GetInitiator() if err := le.ValidateRequester(); err != nil { - if _, err := CreateErroredJob(jobSpec, initiator, err, store); err != nil { + if _, err := runManager.CreateErrored(jobSpecID, initiator, err); err != nil { logger.Errorw(err.Error()) } logger.Errorw(err.Error(), le.ForLogger()...) @@ -171,14 +169,14 @@ func runJob(store *strpkg.Store, le models.LogRequest, data *models.JSON) { rr, err := le.RunRequest() if err != nil { - if _, err := CreateErroredJob(jobSpec, initiator, err, store); err != nil { + if _, err := runManager.CreateErrored(jobSpecID, initiator, err); err != nil { logger.Errorw(err.Error()) } logger.Errorw(err.Error(), le.ForLogger()...) return } - _, err = ExecuteJobWithRunRequest(jobSpec, initiator, data, le.BlockNumber(), store, rr) + _, err = runManager.Create(jobSpecID, &initiator, &data, le.BlockNumber(), &rr) if err != nil { logger.Errorw(err.Error(), le.ForLogger()...) } diff --git a/core/services/subscription_test.go b/core/services/subscription_test.go index 7e35ebdb0c7..7fd24d9f82e 100644 --- a/core/services/subscription_test.go +++ b/core/services/subscription_test.go @@ -9,6 +9,8 @@ import ( "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/common" "github.com/golang/mock/gomock" + + "github.com/ethereum/go-ethereum/common/hexutil" "github.com/onsi/gomega" "github.com/smartcontractkit/chainlink/core/internal/cltest" "github.com/smartcontractkit/chainlink/core/internal/mocks" @@ -17,6 +19,7 @@ import ( "github.com/smartcontractkit/chainlink/core/store/models" "github.com/smartcontractkit/chainlink/core/store/orm" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" ) @@ -34,9 +37,10 @@ func TestServices_NewInitiatorSubscription_BackfillLogs(t *testing.T) { eth.RegisterSubscription("logs") var count int32 - callback := func(*strpkg.Store, models.LogRequest) { atomic.AddInt32(&count, 1) } + callback := func(*strpkg.Store, services.RunManager, models.LogRequest) { atomic.AddInt32(&count, 1) } fromBlock := cltest.Head(0) - sub, err := services.NewInitiatorSubscription(initr, job, store, fromBlock, callback) + jm := new(mocks.RunManager) + sub, err := services.NewInitiatorSubscription(initr, job, store, jm, fromBlock, callback) assert.NoError(t, err) defer sub.Unsubscribe() @@ -59,8 +63,9 @@ func TestServices_NewInitiatorSubscription_BackfillLogs_WithNoHead(t *testing.T) eth.RegisterSubscription("logs") var count int32 - callback := func(*strpkg.Store, models.LogRequest) { atomic.AddInt32(&count, 1) } - sub, err := services.NewInitiatorSubscription(initr, job, store, nil, callback) + callback := func(*strpkg.Store, services.RunManager, models.LogRequest) { atomic.AddInt32(&count, 1) } + jm := new(mocks.RunManager) + sub, err := services.NewInitiatorSubscription(initr, job, store, jm, nil, callback) assert.NoError(t, err) defer sub.Unsubscribe() @@ -84,9 +89,10 @@ func TestServices_NewInitiatorSubscription_PreventsDoubleDispatch(t *testing.T) eth.RegisterSubscription("logs", logsChan) var count int32 - callback := func(*strpkg.Store, models.LogRequest) { atomic.AddInt32(&count, 1) } + callback := func(*strpkg.Store, services.RunManager, models.LogRequest) { atomic.AddInt32(&count, 1) } head := cltest.Head(0) - sub, err := services.NewInitiatorSubscription(initr, job, store, head, callback) + jm := new(mocks.RunManager) + sub, err := services.NewInitiatorSubscription(initr, job, store, jm, head, callback) assert.NoError(t, err) defer sub.Unsubscribe() @@ -111,7 +117,7 @@ func TestServices_ReceiveLogRequest_IgnoredLogWithRemovedFlag(t *testing.T) { require.NoError(t, store.CreateJob(&jobSpec)) log := models.InitiatorLogEvent{ - JobSpec: jobSpec, + JobSpecID: *jobSpec.ID, Log: models.Log{ Removed: true, }, @@ -121,14 +127,191 @@ func TestServices_ReceiveLogRequest_IgnoredLogWithRemovedFlag(t *testing.T) { err := store.ORM.DB.Model(&models.JobRun{}).Count(&originalCount).Error require.NoError(t, err) - services.ReceiveLogRequest(store, log) + jm := new(mocks.RunManager) + services.ReceiveLogRequest(store, jm, log) + jm.AssertExpectations(t) +} + +func TestServices_StartJobSubscription(t *testing.T) { + t.Parallel() + + sharedAddr := cltest.NewAddress() + noAddr := common.Address{} + + tests := []struct { + name string + initType string + initrAddr common.Address + logAddr common.Address + topic0 common.Hash + data hexutil.Bytes + }{ + { + "ethlog matching address", + "ethlog", + sharedAddr, + sharedAddr, + common.Hash{}, + hexutil.Bytes{}, + }, + { + "ethlog all address", + "ethlog", + noAddr, + cltest.NewAddress(), + common.Hash{}, + hexutil.Bytes{}, + }, + { + "runlog v0 matching address", + "runlog", + sharedAddr, + sharedAddr, + models.RunLogTopic0original, + cltest.StringToVersionedLogData0(t, + "id", + `{"value":"100"}`, + ), + }, + { + "runlog v20190123 w/o address", + "runlog", + noAddr, + cltest.NewAddress(), + models.RunLogTopic20190123withFullfillmentParams, + cltest.StringToVersionedLogData20190123withFulfillmentParams(t, "id", `{"value":"100"}`), + }, + { + "runlog v20190123 matching address", + "runlog", + sharedAddr, + sharedAddr, + models.RunLogTopic20190123withFullfillmentParams, + cltest.StringToVersionedLogData20190123withFulfillmentParams(t, "id", `{"value":"100"}`), + }, + { + "runlog v20190207 w/o address", + "runlog", + noAddr, + cltest.NewAddress(), + models.RunLogTopic20190207withoutIndexes, + cltest.StringToVersionedLogData20190207withoutIndexes(t, "id", cltest.NewAddress(), `{"value":"100"}`), + }, + { + "runlog v20190207 matching address", + "runlog", + sharedAddr, + sharedAddr, + models.RunLogTopic20190207withoutIndexes, + cltest.StringToVersionedLogData20190207withoutIndexes(t, "id", cltest.NewAddress(), `{"value":"100"}`), + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + store, cleanup := cltest.NewStore(t) + defer cleanup() + + eth := cltest.MockEthOnStore(t, store) + eth.Register("eth_getLogs", []models.Log{}) + logChan := make(chan models.Log, 1) + eth.RegisterSubscription("logs", logChan) + + job := cltest.NewJob() + initr := models.Initiator{Type: test.initType} + initr.Address = test.initrAddr + job.Initiators = []models.Initiator{initr} + require.NoError(t, store.CreateJob(&job)) + + executeJobChannel := make(chan struct{}) + + runManager := new(mocks.RunManager) + runManager.On("Create", job.ID, mock.Anything, mock.Anything, big.NewInt(0), mock.Anything). + Return(nil, nil). + Run(func(mock.Arguments) { + executeJobChannel <- struct{}{} + }) + + subscription, err := services.StartJobSubscription(job, cltest.Head(91), store, runManager) + require.NoError(t, err) + assert.NotNil(t, subscription) + + logChan <- models.Log{ + Address: test.logAddr, + Data: test.data, + Topics: []common.Hash{ + test.topic0, + models.IDToTopic(job.ID), + cltest.NewAddress().Hash(), + common.BigToHash(big.NewInt(0)), + }, + } + + cltest.CallbackOrTimeout(t, "Create", func() { + <-executeJobChannel + }) - gomega.NewGomegaWithT(t).Consistently(func() int { - count := 0 - err := store.ORM.DB.Model(&models.JobRun{}).Count(&count).Error - require.NoError(t, err) - return count - originalCount - }).Should(gomega.Equal(0)) + runManager.AssertExpectations(t) + eth.EventuallyAllCalled(t) + }) + } +} + +func TestServices_StartJobSubscription_RunlogNoTopicMatch(t *testing.T) { + t.Parallel() + + sharedAddr := cltest.NewAddress() + + tests := []struct { + name string + data hexutil.Bytes + }{ + { + "runlog w non-matching topic", + cltest.StringToVersionedLogData20190123withFulfillmentParams(t, "id", `{"value":"100"}`)}, + { + "runlog w non-matching topic", + cltest.StringToVersionedLogData20190207withoutIndexes(t, "id", cltest.NewAddress(), `{"value":"100"}`), + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + store, cleanup := cltest.NewStore(t) + defer cleanup() + + eth := cltest.MockEthOnStore(t, store) + eth.Register("eth_getLogs", []models.Log{}) + logChan := make(chan models.Log, 1) + eth.RegisterSubscription("logs", logChan) + + job := cltest.NewJob() + initr := models.Initiator{Type: "runlog"} + initr.Address = sharedAddr + job.Initiators = []models.Initiator{initr} + require.NoError(t, store.CreateJob(&job)) + + runManager := new(mocks.RunManager) + + subscription, err := services.StartJobSubscription(job, cltest.Head(91), store, runManager) + require.NoError(t, err) + assert.NotNil(t, subscription) + + logChan <- models.Log{ + Address: sharedAddr, + Data: test.data, + Topics: []common.Hash{ + common.Hash{}, + models.IDToTopic(job.ID), + cltest.NewAddress().Hash(), + common.BigToHash(big.NewInt(0)), + }, + } + + runManager.AssertExpectations(t) + eth.EventuallyAllCalled(t) + }) + } } func TestServices_NewInitiatorSubscription_ReplayFromBlock(t *testing.T) { @@ -177,11 +360,20 @@ func TestServices_NewInitiatorSubscription_ReplayFromBlock(t *testing.T) { txmMock.EXPECT().SubscribeToLogs(gomock.Any(), expectedQuery).Return(cltest.EmptyMockSubscription(), nil) txmMock.EXPECT().GetLogs(expectedQuery).Return([]models.Log{log}, nil) + executeJobChannel := make(chan struct{}) + + runManager := new(mocks.RunManager) + runManager.On("Create", job.ID, mock.Anything, mock.Anything, big.NewInt(0), mock.Anything). + Return(nil, nil). + Run(func(mock.Arguments) { + executeJobChannel <- struct{}{} + }) + var wg sync.WaitGroup wg.Add(1) - callback := func(*strpkg.Store, models.LogRequest) { wg.Done() } + callback := func(*strpkg.Store, services.RunManager, models.LogRequest) { wg.Done() } - _, err := services.NewInitiatorSubscription(initr, job, store, currentHead, callback) + _, err := services.NewInitiatorSubscription(initr, job, store, runManager, currentHead, callback) require.NoError(t, err) wg.Wait() diff --git a/core/services/synchronization/stats_pusher.go b/core/services/synchronization/stats_pusher.go index bc61b2ba46f..5dbc8cdff20 100644 --- a/core/services/synchronization/stats_pusher.go +++ b/core/services/synchronization/stats_pusher.go @@ -120,8 +120,6 @@ func (sp *StatsPusher) eventLoop(parentCtx context.Context) { } func (sp *StatsPusher) pusherLoop(parentCtx context.Context) error { - logger.Debugw("Started StatsPusher") - for { select { case <-sp.waker: @@ -135,7 +133,6 @@ func (sp *StatsPusher) pusherLoop(parentCtx context.Context) error { return err } case <-parentCtx.Done(): - logger.Debugw("StatsPusher got done signal, shutting down") return nil } } diff --git a/core/services/validators.go b/core/services/validators.go index 004aa007086..649595e271a 100644 --- a/core/services/validators.go +++ b/core/services/validators.go @@ -50,7 +50,7 @@ func ValidateBridgeType(bt *models.BridgeTypeRequest, store *store.Store) error fe.Add("Invalid URL format") } ts := models.TaskSpec{Type: bt.Name} - if a, _ := adapters.For(ts, store); a != nil { + if a, _ := adapters.For(ts, store.Config, store.ORM); a != nil { fe.Add(fmt.Sprintf("Adapter %v already exists", bt.Name)) } return fe.CoerceEmptyToNil() @@ -135,7 +135,7 @@ func validateServiceAgreementInitiator(i models.Initiator, j models.JobSpec) err } func validateTask(task models.TaskSpec, store *store.Store) error { - adapter, err := adapters.For(task, store) + adapter, err := adapters.For(task, store.Config, store.ORM) if !store.Config.Dev() { if _, ok := adapter.BaseAdapter.(*adapters.Sleep); ok { return errors.New("Sleep Adapter is not implemented yet") diff --git a/core/store/eth_client_test.go b/core/store/eth_client_test.go index c53d12ee886..09e66f5cb55 100644 --- a/core/store/eth_client_test.go +++ b/core/store/eth_client_test.go @@ -77,6 +77,8 @@ func TestEthCallerSubscriber_GetNonce(t *testing.T) { t.Parallel() app, cleanup := cltest.NewApplicationWithKey(t) defer cleanup() + require.NoError(t, app.Start()) + ethMock := app.MockEthCallerSubscriber() ethClientObject := app.Store.TxManager.(*strpkg.EthTxManager).EthClient ethMock.Register("eth_getTransactionCount", "0x0100") @@ -90,6 +92,8 @@ func TestEthCallerSubscriber_SendRawTx(t *testing.T) { t.Parallel() app, cleanup := cltest.NewApplicationWithKey(t) defer cleanup() + require.NoError(t, app.Start()) + ethMock := app.MockEthCallerSubscriber() ethClientObject := app.Store.TxManager.(*strpkg.EthTxManager).EthClient ethMock.Register("eth_sendRawTransaction", common.Hash{1}) @@ -102,6 +106,7 @@ func TestEthCallerSubscriber_GetEthBalance(t *testing.T) { t.Parallel() app, cleanup := cltest.NewApplicationWithKey(t) defer cleanup() + require.NoError(t, app.Start()) tests := []struct { name string @@ -129,6 +134,7 @@ func TestEthCallerSubscriber_GetERC20Balance(t *testing.T) { t.Parallel() app, cleanup := cltest.NewApplicationWithKey(t) defer cleanup() + require.NoError(t, app.Start()) ethMock := app.MockEthCallerSubscriber() ethClientObject := app.Store.TxManager.(*strpkg.EthTxManager).EthClient diff --git a/core/store/models/common.go b/core/store/models/common.go index fe2dd2884bb..8d00080ff0e 100644 --- a/core/store/models/common.go +++ b/core/store/models/common.go @@ -47,6 +47,8 @@ const ( RunStatusErrored = RunStatus("errored") // RunStatusCompleted is used for when a run has successfully completed execution. RunStatusCompleted = RunStatus("completed") + // RunStatusCancelled is used to indicate a run is no longer desired. + RunStatusCancelled = RunStatus("cancelled") ) // Unstarted returns true if the status is the initial state. diff --git a/core/store/models/eth.go b/core/store/models/eth.go index 57a1d100284..f824db11dd2 100755 --- a/core/store/models/eth.go +++ b/core/store/models/eth.go @@ -95,7 +95,7 @@ type Tx struct { // String implements Stringer for Tx func (tx *Tx) String() string { - return fmt.Sprintf("Tx{ID: %d, From: %s, To: %s, Hash: %s, SentAt: %d}", + return fmt.Sprintf("Tx(ID: %d, From: %s, To: %s, Hash: %s, SentAt: %d)", tx.ID, tx.From.String(), tx.To.String(), diff --git a/core/store/models/job_run.go b/core/store/models/job_run.go index 0dca815b52c..8ffabdfedc3 100644 --- a/core/store/models/job_run.go +++ b/core/store/models/job_run.go @@ -100,6 +100,15 @@ func (jr *JobRun) NextTaskRun() *TaskRun { return nil } +// PreviousTaskRun returns the last task to be processed, if it exists +func (jr *JobRun) PreviousTaskRun() *TaskRun { + index, runnable := jr.NextTaskRunIndex() + if runnable && index > 0 { + return &jr.TaskRuns[index-1] + } + return nil +} + // TasksRemain returns true if there are unfinished tasks left for this job run func (jr *JobRun) TasksRemain() bool { _, runnable := jr.NextTaskRunIndex() @@ -158,8 +167,8 @@ type RunRequest struct { } // NewRunRequest returns a new RunRequest instance. -func NewRunRequest() RunRequest { - return RunRequest{CreatedAt: time.Now()} +func NewRunRequest() *RunRequest { + return &RunRequest{CreatedAt: time.Now()} } // TaskRun stores the Task and represents the status of the diff --git a/core/store/models/job_run_test.go b/core/store/models/job_run_test.go index 9be21a956df..a0d58a05d49 100644 --- a/core/store/models/job_run_test.go +++ b/core/store/models/job_run_test.go @@ -6,7 +6,6 @@ import ( "math/big" "testing" - "github.com/smartcontractkit/chainlink/core/adapters" "github.com/smartcontractkit/chainlink/core/internal/cltest" "github.com/smartcontractkit/chainlink/core/store/assets" "github.com/smartcontractkit/chainlink/core/store/models" @@ -15,7 +14,7 @@ import ( null "gopkg.in/guregu/null.v3" ) -func TestJobRuns_RetrievingFromDBWithError(t *testing.T) { +func TestJobRun_RetrievingFromDBWithError(t *testing.T) { t.Parallel() store, cleanup := cltest.NewStore(t) defer cleanup() @@ -34,7 +33,7 @@ func TestJobRuns_RetrievingFromDBWithError(t *testing.T) { assert.Equal(t, "bad idea", run.ErrorString()) } -func TestJobRuns_RetrievingFromDBWithData(t *testing.T) { +func TestJobRun_RetrievingFromDBWithData(t *testing.T) { t.Parallel() store, cleanup := cltest.NewStore(t) defer cleanup() @@ -56,7 +55,7 @@ func TestJobRuns_RetrievingFromDBWithData(t *testing.T) { assert.JSONEq(t, data, run.Result.Data.String()) } -func TestJobRuns_SavesASyncEvent(t *testing.T) { +func TestJobRun_SavesASyncEvent(t *testing.T) { t.Parallel() config, _ := cltest.NewConfig(t) config.Set("EXPLORER_URL", "http://localhost:4201") @@ -95,7 +94,7 @@ func TestJobRuns_SavesASyncEvent(t *testing.T) { assert.Contains(t, data, "status") } -func TestJobRuns_SkipsEventSaveIfURLBlank(t *testing.T) { +func TestJobRun_SkipsEventSaveIfURLBlank(t *testing.T) { t.Parallel() config, _ := cltest.NewConfig(t) config.Set("EXPLORER_URL", "") @@ -122,7 +121,7 @@ func TestJobRuns_SkipsEventSaveIfURLBlank(t *testing.T) { require.Len(t, events, 0) } -func TestForLogger(t *testing.T) { +func TestJobRun_ForLogger(t *testing.T) { t.Parallel() store, cleanup := cltest.NewStore(t) defer cleanup() @@ -162,40 +161,14 @@ func TestForLogger(t *testing.T) { assert.Equal(t, logsWithBlockHeights[9], big.NewInt(10)) run := job.NewRun(job.Initiators[0]) - run.SetError(errors.New("bad idea")) + run.Status = models.RunStatusErrored + run.Result.ErrorMessage = null.StringFrom("bad idea") logsWithErr := run.ForLogger() + require.Len(t, logsWithErr, 8) assert.Equal(t, logsWithErr[6], "job_error") assert.Equal(t, logsWithErr[7], run.ErrorString()) } -func TestJobRun_NextTaskRun(t *testing.T) { - t.Parallel() - - store, cleanup := cltest.NewStore(t) - defer cleanup() - jobRunner, cleanup := cltest.NewJobRunner(store) - defer cleanup() - jobRunner.Start() - - job := cltest.NewJobWithWebInitiator() - job.Tasks = []models.TaskSpec{ - {Type: adapters.TaskTypeNoOp}, - {Type: adapters.TaskTypeNoOpPend}, - {Type: adapters.TaskTypeNoOp}, - } - assert.NoError(t, store.CreateJob(&job)) - run := job.NewRun(job.Initiators[0]) - assert.NoError(t, store.CreateJobRun(&run)) - assert.Equal(t, &run.TaskRuns[0], run.NextTaskRun()) - - store.RunChannel.Send(run.ID) - cltest.WaitForJobRunStatus(t, store, run, models.RunStatusPendingConfirmations) - - run, err := store.FindJobRun(run.ID) - assert.NoError(t, err) - assert.Equal(t, &run.TaskRuns[1], run.NextTaskRun()) -} - func TestJobRun_ApplyOutput_CompletedWithNoTasksRemaining(t *testing.T) { t.Parallel() diff --git a/core/store/models/job_spec.go b/core/store/models/job_spec.go index 02dabf6f398..187cba3e78a 100644 --- a/core/store/models/job_spec.go +++ b/core/store/models/job_spec.go @@ -124,6 +124,11 @@ func NewJobFromRequest(jsr JobSpecRequest) JobSpec { return jobSpec } +// Archived returns true if the job spec has been soft deleted +func (j JobSpec) Archived() bool { + return j.DeletedAt.Valid +} + // NewRun initializes the job by creating the IDs for the job // and all associated tasks, and setting the CreatedAt field. func (j JobSpec) NewRun(i Initiator) JobRun { @@ -146,7 +151,7 @@ func (j JobSpec) NewRun(i Initiator) JobRun { CreatedAt: now, UpdatedAt: now, TaskRuns: taskRuns, - RunRequest: runRequest, + RunRequest: *runRequest, Initiator: i, InitiatorID: i.ID, Status: RunStatusUnstarted, diff --git a/core/store/models/job_spec_test.go b/core/store/models/job_spec_test.go index e65db434922..fdfee087706 100644 --- a/core/store/models/job_spec_test.go +++ b/core/store/models/job_spec_test.go @@ -190,7 +190,7 @@ func TestJobSpec_NewRun(t *testing.T) { taskRun := run.TaskRuns[0] assert.Equal(t, "noop", taskRun.TaskSpec.Type.String()) - adapter, _ := adapters.For(taskRun.TaskSpec, store) + adapter, _ := adapters.For(taskRun.TaskSpec, store.Config, store.ORM) assert.NotNil(t, adapter) assert.JSONEq(t, `{"type":"NoOp","a":1}`, taskRun.TaskSpec.Params.String()) diff --git a/core/store/models/log_events.go b/core/store/models/log_events.go index 5fcbdd1b432..a8c005be7cf 100644 --- a/core/store/models/log_events.go +++ b/core/store/models/log_events.go @@ -129,7 +129,7 @@ func FilterQueryFactory(i Initiator, from *big.Int) (ethereum.FilterQuery, error // i.e. EthLogEvent, RunLogEvent, ServiceAgreementLogEvent, OracleLogEvent type LogRequest interface { GetLog() Log - GetJobSpec() JobSpec + GetJobSpecID() *ID GetInitiator() Initiator Validate() bool @@ -144,8 +144,8 @@ type LogRequest interface { // InitiatorLogEvent encapsulates all information as a result of a received log from an // InitiatorSubscription. type InitiatorLogEvent struct { + JobSpecID ID Log Log - JobSpec JobSpec Initiator Initiator } @@ -169,9 +169,9 @@ func (le InitiatorLogEvent) GetLog() Log { return le.Log } -// GetJobSpec returns the associated JobSpec -func (le InitiatorLogEvent) GetJobSpec() JobSpec { - return le.JobSpec +// GetJobSpecID returns the associated JobSpecID +func (le InitiatorLogEvent) GetJobSpecID() *ID { + return &le.JobSpecID } // GetInitiator returns the initiator. @@ -183,7 +183,7 @@ func (le InitiatorLogEvent) GetInitiator() Initiator { // formatting in logs (trace statements, not ethereum events). func (le InitiatorLogEvent) ForLogger(kvs ...interface{}) []interface{} { output := []interface{}{ - "job", le.JobSpec.ID.String(), + "job", le.JobSpecID.String(), "log", le.Log.BlockNumber, "initiator", le.Initiator, } @@ -197,8 +197,10 @@ func (le InitiatorLogEvent) ForLogger(kvs ...interface{}) []interface{} { // ToDebug prints this event via logger.Debug. func (le InitiatorLogEvent) ToDebug() { friendlyAddress := utils.LogListeningAddress(le.Initiator.Address) - msg := fmt.Sprintf("Received log from block #%v for address %v for job %v", le.Log.BlockNumber, friendlyAddress, le.JobSpec.ID.String()) - logger.Debugw(msg, le.ForLogger()...) + logger.Debugw( + fmt.Sprintf("Received log from block #%v for address %v", le.Log.BlockNumber, friendlyAddress), + le.ForLogger()..., + ) } // BlockNumber returns the block number for the given InitiatorSubscriptionLogEvent. @@ -256,11 +258,11 @@ type RunLogEvent struct { // Validate returns whether or not the contained log has a properly encoded // job id. func (le RunLogEvent) Validate() bool { - jobSpecID := le.JobSpec.ID + jobSpecID := &le.JobSpecID topic := le.Log.Topics[RequestLogTopicJobID] if IDToTopic(jobSpecID) != topic && IDToHexTopic(jobSpecID) != topic { - logger.Errorw("Run Log didn't have matching job ID", le.ForLogger("id", le.JobSpec.ID.String())...) + logger.Errorw("Run Log didn't have matching job ID", le.ForLogger("id", le.JobSpecID.String())...) return false } return true @@ -359,7 +361,7 @@ func parserFromLog(log Log) (logRequestParser, error) { } parser, ok := topicFactoryMap[topic] if !ok { - return nil, fmt.Errorf("No parser for the RunLogEvent topic %v", topic) + return nil, fmt.Errorf("No parser for the RunLogEvent topic %s", topic.String()) } return parser, nil } diff --git a/core/store/models/log_events_test.go b/core/store/models/log_events_test.go index 5fe9db897ee..fd76dbcb7b3 100644 --- a/core/store/models/log_events_test.go +++ b/core/store/models/log_events_test.go @@ -139,8 +139,8 @@ func TestRequestLogEvent_Validate(t *testing.T) { } logRequest := models.InitiatorLogEvent{ - JobSpec: job, - Log: log, + JobSpecID: *job.ID, + Log: log, Initiator: models.Initiator{ Type: models.InitiatorRunLog, InitiatorParams: models.InitiatorParams{ diff --git a/core/store/models/run_output.go b/core/store/models/run_output.go index d5aafb4e9c0..6d8e6b28951 100644 --- a/core/store/models/run_output.go +++ b/core/store/models/run_output.go @@ -37,12 +37,6 @@ func NewRunOutputComplete(data JSON) RunOutput { return RunOutput{status: RunStatusCompleted, data: data} } -// NewRunOutputPendingSleep returns a new RunOutput that indicates the task is -// sleeping -func NewRunOutputPendingSleep() RunOutput { - return RunOutput{status: RunStatusPendingSleep} -} - // NewRunOutputPendingConfirmations returns a new RunOutput that indicates the // task is pending confirmations func NewRunOutputPendingConfirmations() RunOutput { diff --git a/core/store/orm/orm.go b/core/store/orm/orm.go index 1ef42acaa88..d0104c2e481 100644 --- a/core/store/orm/orm.go +++ b/core/store/orm/orm.go @@ -281,10 +281,23 @@ func (orm *ORM) convenientTransaction(callback func(*gorm.DB) error) error { return dbtx.Commit().Error } +// OptimisticUpdateConflictError is returned when a record update failed +// because another update occurred while the model was in memory and the +// differences must be reconciled. +var OptimisticUpdateConflictError = errors.New("conflict while updating record") + // SaveJobRun updates UpdatedAt for a JobRun and saves it func (orm *ORM) SaveJobRun(run *models.JobRun) error { return orm.convenientTransaction(func(dbtx *gorm.DB) error { - return dbtx.Unscoped().Omit("deleted_at").Save(run).Error + result := dbtx.Unscoped(). + Model(run). + Where("updated_at = ?", run.UpdatedAt). + Omit("deleted_at"). + Save(run) + if result.RowsAffected == 0 { + return OptimisticUpdateConflictError + } + return result.Error }) } @@ -350,7 +363,7 @@ func (orm *ORM) FindServiceAgreement(id string) (models.ServiceAgreement, error) } // Jobs fetches all jobs. -func (orm *ORM) Jobs(cb func(models.JobSpec) bool) error { +func (orm *ORM) Jobs(cb func(*models.JobSpec) bool) error { return Batch(1000, func(offset, limit uint) (uint, error) { jobs := []models.JobSpec{} err := orm.preloadJobs(). @@ -362,7 +375,7 @@ func (orm *ORM) Jobs(cb func(models.JobSpec) bool) error { } for _, j := range jobs { - if !cb(j) { + if !cb(&j) { return 0, nil } } @@ -447,15 +460,6 @@ func (orm *ORM) createJob(tx *gorm.DB, job *models.JobSpec) error { return tx.Create(job).Error } -// Archived returns whether or not a job has been archived. -func (orm *ORM) Archived(id *models.ID) bool { - j, err := orm.Unscoped().FindJob(id) - if err != nil { - return false - } - return j.DeletedAt.Valid -} - // ArchiveJob soft deletes the job and its associated job runs. func (orm *ORM) ArchiveJob(ID *models.ID) error { j, err := orm.FindJob(ID) diff --git a/core/store/orm/orm_test.go b/core/store/orm/orm_test.go index 3d3415500a2..219e9467fbf 100644 --- a/core/store/orm/orm_test.go +++ b/core/store/orm/orm_test.go @@ -13,6 +13,7 @@ import ( "github.com/ethereum/go-ethereum/core/types" "github.com/smartcontractkit/chainlink/core/adapters" "github.com/smartcontractkit/chainlink/core/internal/cltest" + "github.com/smartcontractkit/chainlink/core/services" "github.com/smartcontractkit/chainlink/core/services/synchronization" "github.com/smartcontractkit/chainlink/core/store/assets" "github.com/smartcontractkit/chainlink/core/store/models" @@ -163,6 +164,33 @@ func TestORM_SaveJobRun_ArchivedDoesNotRevertDeletedAt(t *testing.T) { require.NoError(t, utils.JustError(store.Unscoped().FindJobRun(jr.ID))) } +func TestORM_SaveJobRun_Cancelled(t *testing.T) { + t.Parallel() + store, cleanup := cltest.NewStore(t) + defer cleanup() + store.ORM.SetLogging(true) + + job := cltest.NewJobWithWebInitiator() + require.NoError(t, store.CreateJob(&job)) + + jr := job.NewRun(job.Initiators[0]) + require.NoError(t, store.CreateJobRun(&jr)) + + jr.Status = models.RunStatusInProgress + require.NoError(t, store.SaveJobRun(&jr)) + + // Save the updated at before saving with cancelled + updatedAt := jr.UpdatedAt + + jr.Status = models.RunStatusCancelled + require.NoError(t, store.SaveJobRun(&jr)) + + // Restore the previous updated at to simulate a conflict + jr.UpdatedAt = updatedAt + jr.Status = models.RunStatusInProgress + assert.Equal(t, orm.OptimisticUpdateConflictError, store.SaveJobRun(&jr)) +} + func coercedJSON(v string) string { if v == "" { return "{}" @@ -708,9 +736,6 @@ func TestORM_PendingBridgeType_alreadyCompleted(t *testing.T) { store, cleanup := cltest.NewStore(t) defer cleanup() - jobRunner, cleanup := cltest.NewJobRunner(store) - defer cleanup() - jobRunner.Start() _, bt := cltest.NewBridgeType(t) require.NoError(t, store.CreateBridgeType(bt)) @@ -722,7 +747,9 @@ func TestORM_PendingBridgeType_alreadyCompleted(t *testing.T) { run := job.NewRun(initr) require.NoError(t, store.CreateJobRun(&run)) - store.RunChannel.Send(run.ID) + executor := services.NewRunExecutor(store) + require.NoError(t, executor.Execute(run.ID)) + cltest.WaitForJobRunStatus(t, store, run, models.RunStatusCompleted) _, err := store.PendingBridgeType(run) @@ -751,8 +778,10 @@ func TestORM_PendingBridgeType_success(t *testing.T) { func TestORM_GetLastNonce_StormNotFound(t *testing.T) { t.Parallel() + app, cleanup := cltest.NewApplicationWithKey(t) defer cleanup() + require.NoError(t, app.Start()) store := app.Store account := cltest.GetAccountAddress(t, store) @@ -1219,10 +1248,8 @@ func TestORM_DeduceDialect(t *testing.T) { } func TestORM_SyncDbKeyStoreToDisk(t *testing.T) { - app, cleanup := cltest.NewApplication(t) + store, cleanup := cltest.NewStore(t) defer cleanup() - - store := app.GetStore() orm := store.ORM seed, err := models.NewKeyFromFile("../../internal/fixtures/keys/3cb8e3fd9d27e39a5e9e6852b0e96160061fd4ea.json") diff --git a/core/store/store.go b/core/store/store.go index c6316b6de36..73094461a25 100644 --- a/core/store/store.go +++ b/core/store/store.go @@ -29,7 +29,6 @@ type Store struct { Config *orm.Config Clock utils.AfterNower KeyStore *KeyStore - RunChannel RunChannel TxManager TxManager StatsPusher *synchronization.StatsPusher } @@ -154,7 +153,6 @@ func NewStoreWithDialer(config *orm.Config, dialer Dialer) *Store { Config: config, KeyStore: keyStore, ORM: orm, - RunChannel: NewQueuedRunChannel(), TxManager: NewEthTxManager(&EthCallerSubscriber{ethrpc}, config, keyStore, orm), StatsPusher: synchronization.NewStatsPusher(orm, config.ExplorerURL(), config.ExplorerAccessKey(), config.ExplorerSecret()), } @@ -172,7 +170,6 @@ func (s *Store) Start() error { // Close shuts down all of the working parts of the store. func (s *Store) Close() error { - s.RunChannel.Close() return multierr.Combine( s.ORM.Close(), s.StatsPusher.Close(), @@ -229,65 +226,3 @@ func initializeORM(config *orm.Config) (*orm.ORM, error) { orm.SetLogging(config.LogSQLStatements()) return orm, nil } - -// RunRequest is the type that the RunChannel uses to package all the necessary -// pieces to execute a Job Run. -type RunRequest struct { - ID *models.ID -} - -// RunChannel manages and dispatches incoming runs. -type RunChannel interface { - Send(jobRunID *models.ID) error - Receive() <-chan RunRequest - Close() -} - -// QueuedRunChannel manages incoming results and blocks by enqueuing them -// in a queue per run. -type QueuedRunChannel struct { - queue chan RunRequest - closed bool - mutex sync.Mutex -} - -// NewQueuedRunChannel initializes a QueuedRunChannel. -func NewQueuedRunChannel() RunChannel { - return &QueuedRunChannel{ - queue: make(chan RunRequest, 1000), - } -} - -// Send adds another entry to the queue of runs. -func (rq *QueuedRunChannel) Send(jobRunID *models.ID) error { - rq.mutex.Lock() - defer rq.mutex.Unlock() - - if rq.closed { - return errors.New("QueuedRunChannel.Add: cannot add to a closed QueuedRunChannel") - } - - if jobRunID == nil { - return errors.New("QueuedRunChannel.Add: cannot add an empty jobRunID") - } - - rq.queue <- RunRequest{ID: jobRunID} - return nil -} - -// Receive returns a channel for listening to sent runs. -func (rq *QueuedRunChannel) Receive() <-chan RunRequest { - return rq.queue -} - -// Close closes the QueuedRunChannel so that no runs can be added to it without -// throwing an error. -func (rq *QueuedRunChannel) Close() { - rq.mutex.Lock() - defer rq.mutex.Unlock() - - if !rq.closed { - rq.closed = true - close(rq.queue) - } -} diff --git a/core/store/store_test.go b/core/store/store_test.go index 9e5598e4a14..f8b3d526501 100644 --- a/core/store/store_test.go +++ b/core/store/store_test.go @@ -9,8 +9,6 @@ import ( "github.com/golang/mock/gomock" "github.com/smartcontractkit/chainlink/core/internal/cltest" "github.com/smartcontractkit/chainlink/core/internal/mocks" - "github.com/smartcontractkit/chainlink/core/store" - "github.com/smartcontractkit/chainlink/core/store/models" "github.com/smartcontractkit/chainlink/core/utils" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -20,10 +18,9 @@ import ( func TestStore_Start(t *testing.T) { t.Parallel() - app, cleanup := cltest.NewApplicationWithKey(t) + store, cleanup := cltest.NewStore(t) defer cleanup() - store := app.Store ctrl := gomock.NewController(t) defer ctrl.Finish() @@ -39,20 +36,7 @@ func TestStore_Close(t *testing.T) { s, cleanup := cltest.NewStore(t) defer cleanup() - s.RunChannel.Send(models.NewID()) - s.RunChannel.Send(models.NewID()) - - _, open := <-s.RunChannel.Receive() - assert.True(t, open) - - _, open = <-s.RunChannel.Receive() - assert.True(t, open) - assert.NoError(t, s.Close()) - - rr, open := <-s.RunChannel.Receive() - assert.Equal(t, store.RunRequest{}, rr) - assert.False(t, open) } func TestStore_SyncDiskKeyStoreToDB_HappyPath(t *testing.T) { @@ -60,6 +44,7 @@ func TestStore_SyncDiskKeyStoreToDB_HappyPath(t *testing.T) { app, cleanup := cltest.NewApplication(t) defer cleanup() + require.NoError(t, app.Start()) store := app.GetStore() // create key on disk @@ -94,6 +79,7 @@ func TestStore_SyncDiskKeyStoreToDB_MultipleKeys(t *testing.T) { app, cleanup := cltest.NewApplicationWithKey(t) app.AddUnlockedKey() // second account defer cleanup() + require.NoError(t, app.Start()) store := app.GetStore() @@ -161,22 +147,3 @@ func TestStore_SyncDiskKeyStoreToDB_DBKeyAlreadyExists(t *testing.T) { require.Len(t, keys, 1) require.Equal(t, acc.Address.Hex(), keys[0].Address.String()) } - -func TestQueuedRunChannel_Send(t *testing.T) { - t.Parallel() - - rq := store.NewQueuedRunChannel() - - assert.NoError(t, rq.Send(models.NewID())) - rr1 := <-rq.Receive() - assert.NotNil(t, rr1) -} - -func TestQueuedRunChannel_Send_afterClose(t *testing.T) { - t.Parallel() - - rq := store.NewQueuedRunChannel() - rq.Close() - - assert.Error(t, rq.Send(models.NewID())) -} diff --git a/core/store/tx_manager_test.go b/core/store/tx_manager_test.go index 6971890f455..33bb2634a2c 100644 --- a/core/store/tx_manager_test.go +++ b/core/store/tx_manager_test.go @@ -8,6 +8,7 @@ import ( "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" "github.com/golang/mock/gomock" "github.com/smartcontractkit/chainlink/core/internal/cltest" "github.com/smartcontractkit/chainlink/core/internal/mocks" @@ -374,16 +375,15 @@ func TestTxManager_CreateTx_NonceTooLowReloadLimit(t *testing.T) { func TestTxManager_CreateTx_ErrPendingConnection(t *testing.T) { t.Parallel() - app, cleanup := cltest.NewApplicationWithKey(t) + + store, cleanup := cltest.NewStore(t) defer cleanup() - store := app.Store manager := store.TxManager to := cltest.NewAddress() - data, err := hex.DecodeString("0000abcdef") - assert.NoError(t, err) + data := hexutil.MustDecode("0x0000abcdef") - _, err = manager.CreateTx(to, data) + _, err := manager.CreateTx(to, data) assert.Contains(t, err.Error(), strpkg.ErrPendingConnection.Error()) } diff --git a/core/web/bridge_types_controller_test.go b/core/web/bridge_types_controller_test.go index 4c357350794..e4d43a52e4c 100644 --- a/core/web/bridge_types_controller_test.go +++ b/core/web/bridge_types_controller_test.go @@ -8,6 +8,7 @@ import ( "github.com/manyminds/api2go/jsonapi" "github.com/smartcontractkit/chainlink/core/internal/cltest" + "github.com/smartcontractkit/chainlink/core/store" "github.com/smartcontractkit/chainlink/core/store/assets" "github.com/smartcontractkit/chainlink/core/store/models" "github.com/smartcontractkit/chainlink/core/web" @@ -34,9 +35,10 @@ func TestBridgeTypesController_Index(t *testing.T) { app, cleanup := cltest.NewApplication(t) defer cleanup() + require.NoError(t, app.Start()) client := app.NewHTTPClient() - bt, err := setupBridgeControllerIndex(t, app) + bt, err := setupBridgeControllerIndex(t, app.Store) assert.NoError(t, err) resp, cleanup := client.Get("/v2/specs?size=x") @@ -75,14 +77,14 @@ func TestBridgeTypesController_Index(t *testing.T) { assert.Equal(t, bt[1].Confirmations, bridges[0].Confirmations, "should have the same Confirmations") } -func setupBridgeControllerIndex(t testing.TB, app *cltest.TestApplication) ([]*models.BridgeType, error) { +func setupBridgeControllerIndex(t testing.TB, store *store.Store) ([]*models.BridgeType, error) { bt1 := &models.BridgeType{ Name: models.MustNewTaskType("testingbridges1"), URL: cltest.WebURL(t, "https://testing.com/bridges"), Confirmations: 0, } - err := app.GetStore().CreateBridgeType(bt1) + err := store.CreateBridgeType(bt1) if err != nil { return nil, err } @@ -92,7 +94,7 @@ func setupBridgeControllerIndex(t testing.TB, app *cltest.TestApplication) ([]*m URL: cltest.WebURL(t, "https://testing.com/tari"), Confirmations: 0, } - err = app.GetStore().CreateBridgeType(bt2) + err = store.CreateBridgeType(bt2) return []*models.BridgeType{bt1, bt2}, err } @@ -101,6 +103,7 @@ func TestBridgeTypesController_Create_Success(t *testing.T) { app, cleanup := cltest.NewApplication(t) defer cleanup() + require.NoError(t, app.Start()) client := app.NewHTTPClient() resp, cleanup := client.Post( @@ -129,6 +132,7 @@ func TestBridgeTypesController_Update_Success(t *testing.T) { app, cleanup := cltest.NewApplication(t) defer cleanup() + require.NoError(t, app.Start()) client := app.NewHTTPClient() bt := &models.BridgeType{ @@ -152,6 +156,7 @@ func TestBridgeController_Show(t *testing.T) { app, cleanup := cltest.NewApplication(t) defer cleanup() + require.NoError(t, app.Start()) client := app.NewHTTPClient() bt := &models.BridgeType{ @@ -181,6 +186,8 @@ func TestBridgeController_Destroy(t *testing.T) { app, cleanup := cltest.NewApplication(t) defer cleanup() + require.NoError(t, app.Start()) + client := app.NewHTTPClient() resp, cleanup := client.Delete("/v2/bridge_types/testingbridges1") defer cleanup() @@ -216,6 +223,8 @@ func TestBridgeTypesController_Create_AdapterExistsError(t *testing.T) { app, cleanup := cltest.NewApplication(t) defer cleanup() + require.NoError(t, app.Start()) + client := app.NewHTTPClient() resp, cleanup := client.Post( @@ -231,6 +240,8 @@ func TestBridgeTypesController_Create_BindJSONError(t *testing.T) { app, cleanup := cltest.NewApplication(t) defer cleanup() + require.NoError(t, app.Start()) + client := app.NewHTTPClient() resp, cleanup := client.Post( @@ -246,6 +257,8 @@ func TestBridgeTypesController_Create_DatabaseError(t *testing.T) { app, cleanup := cltest.NewApplication(t) defer cleanup() + require.NoError(t, app.Start()) + client := app.NewHTTPClient() resp, cleanup := client.Post( diff --git a/core/web/config_controller_test.go b/core/web/config_controller_test.go index 3e56580945d..620301168e2 100644 --- a/core/web/config_controller_test.go +++ b/core/web/config_controller_test.go @@ -20,6 +20,7 @@ func TestConfigController_Show(t *testing.T) { app, cleanup := cltest.NewApplicationWithKey(t) defer cleanup() + require.NoError(t, app.Start()) client := app.NewHTTPClient() resp, cleanup := client.Get("/v2/config") diff --git a/core/web/cors_test.go b/core/web/cors_test.go index 47257b2f154..2603fd03c9e 100644 --- a/core/web/cors_test.go +++ b/core/web/cors_test.go @@ -5,6 +5,7 @@ import ( "testing" "github.com/smartcontractkit/chainlink/core/internal/cltest" + "github.com/stretchr/testify/require" ) func TestCors_DefaultOrigins(t *testing.T) { @@ -14,6 +15,8 @@ func TestCors_DefaultOrigins(t *testing.T) { config.Set("ALLOW_ORIGINS", "http://localhost:3000,http://localhost:6689") app, appCleanup := cltest.NewApplicationWithConfigAndKey(t, config) defer appCleanup() + require.NoError(t, app.Start()) + client := app.NewHTTPClient() tests := []struct { @@ -53,8 +56,11 @@ func TestCors_OverrideOrigins(t *testing.T) { t.Run(test.origin, func(t *testing.T) { config, _ := cltest.NewConfig(t) config.Set("ALLOW_ORIGINS", test.allow) + app, appCleanup := cltest.NewApplicationWithConfigAndKey(t, config) defer appCleanup() + require.NoError(t, app.Start()) + client := app.NewHTTPClient() headers := map[string]string{"Origin": test.origin} diff --git a/core/web/external_initiators_controller_test.go b/core/web/external_initiators_controller_test.go index 7adf30fdc18..6fad24dba03 100644 --- a/core/web/external_initiators_controller_test.go +++ b/core/web/external_initiators_controller_test.go @@ -16,6 +16,7 @@ func TestExternalInitiatorsController_Create_success(t *testing.T) { app, cleanup := cltest.NewApplicationWithKey(t) defer cleanup() + require.NoError(t, app.Start()) client := app.NewHTTPClient() @@ -41,6 +42,7 @@ func TestExternalInitiatorsController_Create_invalid(t *testing.T) { app, cleanup := cltest.NewApplicationWithKey(t) defer cleanup() + require.NoError(t, app.Start()) client := app.NewHTTPClient() @@ -56,6 +58,7 @@ func TestExternalInitiatorsController_Delete(t *testing.T) { app, cleanup := cltest.NewApplicationWithKey(t) defer cleanup() + require.NoError(t, app.Start()) client := app.NewHTTPClient() @@ -69,6 +72,8 @@ func TestExternalInitiatorsController_DeleteNotFound(t *testing.T) { app, cleanup := cltest.NewApplicationWithKey(t) defer cleanup() + require.NoError(t, app.Start()) + err := app.GetStore().CreateExternalInitiator(&models.ExternalInitiator{ AccessKey: "abracadabra", }) diff --git a/core/web/external_initiators_test.go b/core/web/external_initiators_test.go index afb57c79a64..ccd0931dc1e 100644 --- a/core/web/external_initiators_test.go +++ b/core/web/external_initiators_test.go @@ -72,8 +72,9 @@ func TestNotifyExternalInitiator_Notified(t *testing.T) { } for _, test := range tests { t.Run(test.Name, func(t *testing.T) { - app, cleanup := cltest.NewApplicationWithKey(t) + store, cleanup := cltest.NewStore(t) defer cleanup() + exInitr := struct { Header http.Header Body web.JobSpecNotice @@ -91,13 +92,13 @@ func TestNotifyExternalInitiator_Notified(t *testing.T) { eia := models.NewExternalInitiatorAuthentication() ei, err := models.NewExternalInitiator(eia, &test.ExInitr) require.NoError(t, err) - err = app.GetStore().CreateExternalInitiator(ei) + err = store.CreateExternalInitiator(ei) require.NoError(t, err) - err = app.GetStore().CreateJob(&test.JobSpec) + err = store.CreateJob(&test.JobSpec) require.NoError(t, err) - err = web.NotifyExternalInitiator(test.JobSpec, app.GetStore()) + err = web.NotifyExternalInitiator(test.JobSpec, store) require.NoError(t, err) assert.Equal(t, ei.OutgoingToken, @@ -149,7 +150,7 @@ func TestNotifyExternalInitiator_NotNotified(t *testing.T) { } for _, test := range tests { t.Run(test.Name, func(t *testing.T) { - app, cleanup := cltest.NewApplicationWithKey(t) + store, cleanup := cltest.NewStore(t) defer cleanup() var remoteNotified bool @@ -164,13 +165,13 @@ func TestNotifyExternalInitiator_NotNotified(t *testing.T) { eia := models.NewExternalInitiatorAuthentication() ei, err := models.NewExternalInitiator(eia, &test.ExInitr) require.NoError(t, err) - err = app.GetStore().CreateExternalInitiator(ei) + err = store.CreateExternalInitiator(ei) require.NoError(t, err) - err = app.GetStore().CreateJob(&test.JobSpec) + err = store.CreateJob(&test.JobSpec) require.NoError(t, err) - err = web.NotifyExternalInitiator(test.JobSpec, app.GetStore()) + err = web.NotifyExternalInitiator(test.JobSpec, store) require.NoError(t, err) require.False(t, remoteNotified) diff --git a/core/web/gui_assets_test.go b/core/web/gui_assets_test.go index 03fd135bf7c..d04dbf91104 100644 --- a/core/web/gui_assets_test.go +++ b/core/web/gui_assets_test.go @@ -13,6 +13,8 @@ func TestGuiAssets_WildcardIndexHtml(t *testing.T) { app, cleanup := cltest.NewApplication(t) defer cleanup() + require.NoError(t, app.Start()) + client := &http.Client{} resp, err := client.Get(app.Server.URL + "/") @@ -53,6 +55,8 @@ func TestGuiAssets_WildcardRouteInfo(t *testing.T) { app, cleanup := cltest.NewApplication(t) defer cleanup() + require.NoError(t, app.Start()) + client := &http.Client{} resp, err := client.Get(app.Server.URL + "/job_specs/abc123/routeInfo.json") @@ -77,6 +81,8 @@ func TestGuiAssets_Exact(t *testing.T) { app, cleanup := cltest.NewApplication(t) defer cleanup() + require.NoError(t, app.Start()) + client := &http.Client{} resp, err := client.Get(app.Server.URL + "/main.js") diff --git a/core/web/job_runs_controller.go b/core/web/job_runs_controller.go index 173d8ca371c..babde6c4e11 100644 --- a/core/web/job_runs_controller.go +++ b/core/web/job_runs_controller.go @@ -59,7 +59,9 @@ func (jrc *JobRunsController) Create(c *gin.Context) { jsonAPIError(c, http.StatusForbidden, err) } else if data, err := getRunData(c); err != nil { jsonAPIError(c, http.StatusInternalServerError, err) - } else if jr, err := services.ExecuteJob(j, *initiator, &data, nil, jrc.App.GetStore()); err != nil { + } else if jr, err := jrc.App.Create(j.ID, initiator, &data, nil, &models.RunRequest{}); errors.Cause(err) == orm.ErrorNotFound { + jsonAPIError(c, http.StatusNotFound, errors.New("Job not found")) + } else if err != nil { jsonAPIError(c, http.StatusInternalServerError, err) } else { jsonAPIResponse(c, presenters.JobRun{JobRun: *jr}, "job run") @@ -117,9 +119,9 @@ func (jrc *JobRunsController) Update(c *gin.Context) { authToken := utils.StripBearer(c.Request.Header.Get("Authorization")) unscoped := jrc.App.GetStore().Unscoped() - if id, err := models.NewIDFromString(c.Param("RunID")); err != nil { + if runID, err := models.NewIDFromString(c.Param("RunID")); err != nil { jsonAPIError(c, http.StatusUnprocessableEntity, err) - } else if jr, err := unscoped.FindJobRun(id); errors.Cause(err) == orm.ErrorNotFound { + } else if jr, err := unscoped.FindJobRun(runID); errors.Cause(err) == orm.ErrorNotFound { jsonAPIError(c, http.StatusNotFound, errors.New("Job Run not found")) } else if err != nil { jsonAPIError(c, http.StatusInternalServerError, err) @@ -133,7 +135,9 @@ func (jrc *JobRunsController) Update(c *gin.Context) { jsonAPIError(c, http.StatusInternalServerError, err) } else if !ok { c.AbortWithStatus(http.StatusUnauthorized) - } else if err = services.ResumePendingTask(&jr, unscoped, brr); err != nil { + } else if err = jrc.App.ResumePending(runID, brr); errors.Cause(err) == orm.ErrorNotFound { + jsonAPIError(c, http.StatusNotFound, errors.New("Job Run not found")) + } else if err != nil { jsonAPIError(c, http.StatusInternalServerError, err) } else { jsonAPIResponse(c, jr, "job run") diff --git a/core/web/job_runs_controller_test.go b/core/web/job_runs_controller_test.go index f21a3cdd052..10b2ee41818 100644 --- a/core/web/job_runs_controller_test.go +++ b/core/web/job_runs_controller_test.go @@ -287,6 +287,8 @@ func TestJobRunsController_Create_InvalidID(t *testing.T) { func TestJobRunsController_Update_Success(t *testing.T) { t.Parallel() app, cleanup := cltest.NewApplication(t) + eth := app.MockEthCallerSubscriber() + eth.Register("eth_chainId", app.Store.Config.ChainID()) app.Start() defer cleanup() diff --git a/core/web/job_specs_controller_test.go b/core/web/job_specs_controller_test.go index 5d25089a43a..09b856ec992 100644 --- a/core/web/job_specs_controller_test.go +++ b/core/web/job_specs_controller_test.go @@ -37,6 +37,7 @@ func TestJobSpecsController_Index_noSort(t *testing.T) { app, cleanup := cltest.NewApplication(t) defer cleanup() + require.NoError(t, app.Start()) client := app.NewHTTPClient() j1, err := setupJobSpecsControllerIndex(app) @@ -85,6 +86,8 @@ func TestJobSpecsController_Index_sortCreatedAt(t *testing.T) { app, cleanup := cltest.NewApplication(t) defer cleanup() + require.NoError(t, app.Start()) + client := app.NewHTTPClient() j2 := cltest.NewJobWithWebInitiator() @@ -159,6 +162,8 @@ func TestJobSpecsController_Create_HappyPath(t *testing.T) { app, cleanup := cltest.NewApplication(t) defer cleanup() + require.NoError(t, app.Start()) + client := app.NewHTTPClient() resp, cleanup := client.Post("/v2/specs", bytes.NewBuffer(cltest.MustReadFile(t, "testdata/hello_world_job.json"))) @@ -170,15 +175,15 @@ func TestJobSpecsController_Create_HappyPath(t *testing.T) { err := cltest.ParseJSONAPIResponse(t, resp, &j) require.NoError(t, err) - adapter1, _ := adapters.For(j.Tasks[0], app.Store) + adapter1, _ := adapters.For(j.Tasks[0], app.Store.Config, app.Store.ORM) httpGet := adapter1.BaseAdapter.(*adapters.HTTPGet) assert.Equal(t, httpGet.GetURL(), "https://bitstamp.net/api/ticker/") - adapter2, _ := adapters.For(j.Tasks[1], app.Store) + adapter2, _ := adapters.For(j.Tasks[1], app.Store.Config, app.Store.ORM) jsonParse := adapter2.BaseAdapter.(*adapters.JSONParse) assert.Equal(t, []string(jsonParse.Path), []string{"last"}) - adapter4, _ := adapters.For(j.Tasks[3], app.Store) + adapter4, _ := adapters.For(j.Tasks[3], app.Store.Config, app.Store.ORM) signTx := adapter4.BaseAdapter.(*adapters.EthTx) assert.Equal(t, "0x356a04bCe728ba4c62A30294A55E6A8600a320B3", signTx.Address.String()) assert.Equal(t, "0x609ff1bd", signTx.FunctionSelector.String()) @@ -194,7 +199,7 @@ func TestJobSpecsController_Create_HappyPath(t *testing.T) { require.Len(t, j.Initiators, 1) assert.Equal(t, models.InitiatorWeb, j.Initiators[0].Type) - adapter1, _ = adapters.For(j.Tasks[0], app.Store) + adapter1, _ = adapters.For(j.Tasks[0], app.Store.Config, app.Store.ORM) httpGet = adapter1.BaseAdapter.(*adapters.HTTPGet) assert.Equal(t, httpGet.GetURL(), "https://bitstamp.net/api/ticker/") } @@ -244,20 +249,21 @@ func TestJobSpecsController_Create_CaseInsensitiveTypes(t *testing.T) { t.Parallel() app, cleanup := cltest.NewApplication(t) defer cleanup() + require.NoError(t, app.Start()) j := cltest.FixtureCreateJobViaWeb(t, app, "testdata/caseinsensitive_hello_world_job.json") - adapter1, _ := adapters.For(j.Tasks[0], app.Store) + adapter1, _ := adapters.For(j.Tasks[0], app.Store.Config, app.Store.ORM) httpGet := adapter1.BaseAdapter.(*adapters.HTTPGet) assert.Equal(t, httpGet.GetURL(), "https://bitstamp.net/api/ticker/") - adapter2, _ := adapters.For(j.Tasks[1], app.Store) + adapter2, _ := adapters.For(j.Tasks[1], app.Store.Config, app.Store.ORM) jsonParse := adapter2.BaseAdapter.(*adapters.JSONParse) assert.Equal(t, []string(jsonParse.Path), []string{"last"}) assert.Equal(t, "ethbytes32", j.Tasks[2].Type.String()) - adapter4, _ := adapters.For(j.Tasks[3], app.Store) + adapter4, _ := adapters.For(j.Tasks[3], app.Store.Config, app.Store.ORM) signTx := adapter4.BaseAdapter.(*adapters.EthTx) assert.Equal(t, "0x356a04bCe728ba4c62A30294A55E6A8600a320B3", signTx.Address.String()) assert.Equal(t, "0x609ff1bd", signTx.FunctionSelector.String()) @@ -270,6 +276,8 @@ func TestJobSpecsController_Create_NonExistentTaskJob(t *testing.T) { t.Parallel() app, cleanup := cltest.NewApplication(t) defer cleanup() + require.NoError(t, app.Start()) + client := app.NewHTTPClient() jsonStr := cltest.MustReadFile(t, "testdata/nonexistent_task_job.json") @@ -286,6 +294,8 @@ func TestJobSpecsController_Create_InvalidJob(t *testing.T) { t.Parallel() app, cleanup := cltest.NewApplication(t) defer cleanup() + require.NoError(t, app.Start()) + client := app.NewHTTPClient() jsonStr := cltest.MustReadFile(t, "testdata/run_at_wo_time_job.json") @@ -302,6 +312,8 @@ func TestJobSpecsController_Create_InvalidCron(t *testing.T) { t.Parallel() app, cleanup := cltest.NewApplication(t) defer cleanup() + require.NoError(t, app.Start()) + client := app.NewHTTPClient() jsonStr := cltest.MustReadFile(t, "testdata/invalid_cron.json") @@ -318,6 +330,8 @@ func TestJobSpecsController_Create_Initiator_Only(t *testing.T) { t.Parallel() app, cleanup := cltest.NewApplication(t) defer cleanup() + require.NoError(t, app.Start()) + client := app.NewHTTPClient() jsonStr := cltest.MustReadFile(t, "testdata/initiator_only_job.json") @@ -334,6 +348,8 @@ func TestJobSpecsController_Create_Task_Only(t *testing.T) { t.Parallel() app, cleanup := cltest.NewApplication(t) defer cleanup() + require.NoError(t, app.Start()) + client := app.NewHTTPClient() jsonStr := cltest.MustReadFile(t, "testdata/task_only_job.json") @@ -349,6 +365,8 @@ func TestJobSpecsController_Create_Task_Only(t *testing.T) { func BenchmarkJobSpecsController_Show(b *testing.B) { app, cleanup := cltest.NewApplication(b) defer cleanup() + require.NoError(b, app.Start()) + client := app.NewHTTPClient() j := setupJobSpecsControllerShow(b, app) @@ -364,6 +382,8 @@ func TestJobSpecsController_Show(t *testing.T) { app, cleanup := cltest.NewApplication(t) defer cleanup() + require.NoError(t, app.Start()) + client := app.NewHTTPClient() j := setupJobSpecsControllerShow(t, app) @@ -399,6 +419,8 @@ func TestJobSpecsController_Show_NotFound(t *testing.T) { t.Parallel() app, cleanup := cltest.NewApplication(t) defer cleanup() + require.NoError(t, app.Start()) + client := app.NewHTTPClient() resp, cleanup := client.Get("/v2/specs/190AE4CE-40B6-4D60-A3DA-061C5ACD32D0") @@ -410,6 +432,8 @@ func TestJobSpecsController_Show_InvalidUuid(t *testing.T) { t.Parallel() app, cleanup := cltest.NewApplication(t) defer cleanup() + require.NoError(t, app.Start()) + client := app.NewHTTPClient() resp, cleanup := client.Get("/v2/specs/garbage") @@ -420,6 +444,8 @@ func TestJobSpecsController_Show_InvalidUuid(t *testing.T) { func TestJobSpecsController_Show_Unauthenticated(t *testing.T) { t.Parallel() app, cleanup := cltest.NewApplication(t) + require.NoError(t, app.Start()) + defer cleanup() resp, err := http.Get(app.Server.URL + "/v2/specs/" + "garbage") @@ -431,6 +457,8 @@ func TestJobSpecsController_Destroy(t *testing.T) { t.Parallel() app, cleanup := cltest.NewApplication(t) defer cleanup() + require.NoError(t, app.Start()) + client := app.NewHTTPClient() job := cltest.NewJobWithLogInitiator() require.NoError(t, app.Store.CreateJob(&job)) diff --git a/core/web/ping_controller_test.go b/core/web/ping_controller_test.go index 6e7101a4ddc..e76cb95ae1a 100644 --- a/core/web/ping_controller_test.go +++ b/core/web/ping_controller_test.go @@ -15,6 +15,7 @@ func TestPingController_Show_APICredentials(t *testing.T) { app, cleanup := cltest.NewApplication(t) defer cleanup() + require.NoError(t, app.Start()) client := app.NewHTTPClient() @@ -29,6 +30,7 @@ func TestPingController_Show_ExternalInitiatorCredentials(t *testing.T) { app, cleanup := cltest.NewApplication(t) defer cleanup() + require.NoError(t, app.Start()) eia := &models.ExternalInitiatorAuthentication{ AccessKey: "abracadabra", @@ -65,6 +67,7 @@ func TestPingController_Show_NoCredentials(t *testing.T) { app, cleanup := cltest.NewApplication(t) defer cleanup() + require.NoError(t, app.Start()) client := http.Client{} url := app.Config.ClientNodeURL() + "/v2/ping" diff --git a/core/web/router_test.go b/core/web/router_test.go index e87f975a04d..8c36f6c9956 100644 --- a/core/web/router_test.go +++ b/core/web/router_test.go @@ -16,6 +16,7 @@ import ( func TestTokenAuthRequired_NoCredentials(t *testing.T) { app, cleanup := cltest.NewApplicationWithKey(t) defer cleanup() + require.NoError(t, app.Start()) router := web.Router(app) ts := httptest.NewServer(router) @@ -30,6 +31,7 @@ func TestTokenAuthRequired_NoCredentials(t *testing.T) { func TestTokenAuthRequired_SessionCredentials(t *testing.T) { app, cleanup := cltest.NewApplicationWithKey(t) defer cleanup() + require.NoError(t, app.Start()) router := web.Router(app) ts := httptest.NewServer(router) @@ -45,6 +47,7 @@ func TestTokenAuthRequired_SessionCredentials(t *testing.T) { func TestTokenAuthRequired_TokenCredentials(t *testing.T) { app, cleanup := cltest.NewApplicationWithKey(t) defer cleanup() + require.NoError(t, app.Start()) router := web.Router(app) ts := httptest.NewServer(router) @@ -76,6 +79,7 @@ func TestTokenAuthRequired_TokenCredentials(t *testing.T) { func TestTokenAuthRequired_BadTokenCredentials(t *testing.T) { app, cleanup := cltest.NewApplicationWithKey(t) defer cleanup() + require.NoError(t, app.Start()) router := web.Router(app) ts := httptest.NewServer(router) @@ -107,6 +111,7 @@ func TestTokenAuthRequired_BadTokenCredentials(t *testing.T) { func TestSessions_RateLimited(t *testing.T) { app, cleanup := cltest.NewApplicationWithKey(t) defer cleanup() + require.NoError(t, app.Start()) router := web.Router(app) ts := httptest.NewServer(router) @@ -135,6 +140,7 @@ func TestSessions_RateLimited(t *testing.T) { func TestRouter_LargePOSTBody(t *testing.T) { app, cleanup := cltest.NewApplicationWithKey(t) defer cleanup() + require.NoError(t, app.Start()) router := web.Router(app) ts := httptest.NewServer(router) @@ -154,6 +160,8 @@ func TestRouter_LargePOSTBody(t *testing.T) { func TestRouter_GinHelmetHeaders(t *testing.T) { app, cleanup := cltest.NewApplicationWithKey(t) defer cleanup() + require.NoError(t, app.Start()) + router := web.Router(app) ts := httptest.NewServer(router) defer ts.Close() diff --git a/core/web/service_agreements_controller_test.go b/core/web/service_agreements_controller_test.go index b138209d35d..ba2527d93be 100644 --- a/core/web/service_agreements_controller_test.go +++ b/core/web/service_agreements_controller_test.go @@ -22,6 +22,8 @@ func TestServiceAgreementsController_Create(t *testing.T) { app, cleanup := cltest.NewApplicationWithKey(t) defer cleanup() + require.NoError(t, app.Start()) + eth := cltest.MockEthOnStore(t, app.GetStore()) eth.RegisterSubscription("logs") @@ -73,6 +75,8 @@ func TestServiceAgreementsController_Create_isIdempotent(t *testing.T) { app, cleanup := cltest.NewApplicationWithKey(t) defer cleanup() + require.NoError(t, app.Start()) + eth := cltest.MockEthOnStore(t, app.GetStore()) eth.RegisterSubscription("logs") @@ -104,6 +108,8 @@ func TestServiceAgreementsController_Show(t *testing.T) { t.Parallel() app, cleanup := cltest.NewApplication(t) defer cleanup() + require.NoError(t, app.Start()) + client := app.NewHTTPClient() input := cltest.MustReadFile(t, "testdata/hello_world_agreement.json") diff --git a/core/web/user_controller_test.go b/core/web/user_controller_test.go index 8292e81c424..9fc830d11f9 100644 --- a/core/web/user_controller_test.go +++ b/core/web/user_controller_test.go @@ -14,9 +14,10 @@ import ( func TestUserController_UpdatePassword(t *testing.T) { t.Parallel() - appWithUser, cleanup := cltest.NewApplicationWithKey(t) + app, cleanup := cltest.NewApplicationWithKey(t) defer cleanup() - client := appWithUser.NewHTTPClient() + require.NoError(t, app.Start()) + client := app.NewHTTPClient() // Invalid request resp, cleanup := client.Patch("/v2/user/password", bytes.NewBufferString("")) @@ -48,9 +49,11 @@ func TestUserController_UpdatePassword(t *testing.T) { func TestUserController_AccountBalances_NoAccounts(t *testing.T) { t.Parallel() - appWithoutAccount, cleanup := cltest.NewApplication(t) + app, cleanup := cltest.NewApplication(t) defer cleanup() - client := appWithoutAccount.NewHTTPClient() + require.NoError(t, app.Start()) + + client := app.NewHTTPClient() resp, cleanup := client.Get("/v2/user/balances") defer cleanup() @@ -66,12 +69,14 @@ func TestUserController_AccountBalances_NoAccounts(t *testing.T) { func TestUserController_AccountBalances_Success(t *testing.T) { t.Parallel() - appWithAccount, cleanup := cltest.NewApplicationWithKey(t) + app, cleanup := cltest.NewApplicationWithKey(t) defer cleanup() - appWithAccount.AddUnlockedKey() - client := appWithAccount.NewHTTPClient() + require.NoError(t, app.Start()) + + app.AddUnlockedKey() + client := app.NewHTTPClient() - ethMock := appWithAccount.MockEthCallerSubscriber() + ethMock := app.MockEthCallerSubscriber() ethMock.Context("first wallet", func(ethMock *cltest.EthMock) { ethMock.Register("eth_getBalance", "0x0100") ethMock.Register("eth_call", "0x0100") @@ -85,7 +90,7 @@ func TestUserController_AccountBalances_Success(t *testing.T) { defer cleanup() require.Equal(t, http.StatusOK, resp.StatusCode) - expectedAccounts := appWithAccount.Store.KeyStore.Accounts() + expectedAccounts := app.Store.KeyStore.Accounts() actualBalances := []presenters.AccountBalance{} err := cltest.ParseJSONAPIResponse(t, resp, &actualBalances) assert.NoError(t, err) diff --git a/tools/ci/go_test b/tools/ci/go_test index 9302fab728a..e169262729a 100755 --- a/tools/ci/go_test +++ b/tools/ci/go_test @@ -9,7 +9,7 @@ yarn workspace chainlink setup # Run golang coverage go get -u github.com/smartcontractkit/goverage -goverage -parallel 2 -coverprofile=c.out ./... +goverage -v -parallel 2 -coverprofile=c.out ./... if [ -n "$CC_TEST_REPORTER_ID" ]; then cc-test-reporter format-coverage --output "coverage/codeclimate.go.json" --prefix "github.com/smartcontractkit/chainlink" gsutil cp "coverage/codeclimate.go.json" gs://codeclimate-aggregation/$CIRCLE_WORKFLOW_ID/ From 7243e7df0e567bac511b2f121114717b5c0361c1 Mon Sep 17 00:00:00 2001 From: John Barker Date: Tue, 5 Nov 2019 07:50:12 +0100 Subject: [PATCH 090/199] Ensure RunQueue never grows beyong one worker in TRQ_One... --- core/services/run_queue_test.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/core/services/run_queue_test.go b/core/services/run_queue_test.go index 8f2a2fca663..9726f362395 100644 --- a/core/services/run_queue_test.go +++ b/core/services/run_queue_test.go @@ -109,6 +109,10 @@ func TestRunQueue_OneWorkerForSameRunTriggeredMultipleTimes(t *testing.T) { return runQueue.WorkerCount() }).Should(gomega.Equal(1)) + g.Consistently(func() int { + return runQueue.WorkerCount() + }).Should(gomega.BeNumerically("<", 2)) + cltest.CallbackOrTimeout(t, "Execute", func() { <-executeJobChannel <-executeJobChannel From 53e2b570e336433078ea9be6ffa8fc88805d2c12 Mon Sep 17 00:00:00 2001 From: John Barker Date: Tue, 5 Nov 2019 07:51:25 +0100 Subject: [PATCH 091/199] Update core/services/run_manager_test.go Co-Authored-By: Steve Ellis --- core/services/run_manager_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/services/run_manager_test.go b/core/services/run_manager_test.go index fa99d0d179e..0598e9c8c34 100644 --- a/core/services/run_manager_test.go +++ b/core/services/run_manager_test.go @@ -70,7 +70,7 @@ func TestRunManager_ResumePending(t *testing.T) { assert.Equal(t, models.RunStatusErrored, run.TaskRuns[0].Status) }) - t.Run("completed input with remaining tasks should put task into pending", func(t *testing.T) { + t.Run("completed input with remaining tasks should put task into in-progress", func(t *testing.T) { runID := models.NewID() run := models.JobRun{ ID: runID, From 4099231f934ff3d62e824d3472cbfef6e8a4c846 Mon Sep 17 00:00:00 2001 From: Alex Kwiatkowski Date: Mon, 4 Nov 2019 17:32:49 -0800 Subject: [PATCH 092/199] Use JSON-API errors when returned from the server in json-api-client package --- .../__tests__/containers/SignIn.test.js | 2 ++ tools/json-api-client/src/errors.ts | 19 ++++++---------- tools/json-api-client/src/transport/json.ts | 22 +++++++++++++++++-- 3 files changed, 29 insertions(+), 14 deletions(-) diff --git a/operator_ui/__tests__/containers/SignIn.test.js b/operator_ui/__tests__/containers/SignIn.test.js index 30a0ea4a798..62471769d05 100644 --- a/operator_ui/__tests__/containers/SignIn.test.js +++ b/operator_ui/__tests__/containers/SignIn.test.js @@ -47,6 +47,8 @@ describe('containers/SignIn', () => { await syncFetch(wrapper) const newState = store.getState() expect(newState.authentication.allowed).toEqual(true) + + await syncFetch(wrapper) expect(wrapper.text()).toContain('Behind authentication') }) diff --git a/tools/json-api-client/src/errors.ts b/tools/json-api-client/src/errors.ts index 4126f185efa..57bb053c03f 100644 --- a/tools/json-api-client/src/errors.ts +++ b/tools/json-api-client/src/errors.ts @@ -1,6 +1,6 @@ import { JsonApiResponse } from 'json-api-normalizer' -interface Error { +export interface ErrorItem { status: number detail: any } @@ -10,7 +10,7 @@ export interface DocumentWithErrors { } export class AuthenticationError extends Error { - errors: Error[] + errors: ErrorItem[] constructor(response: Response) { super(`AuthenticationError(${response.statusText})`) @@ -33,21 +33,16 @@ export class BadRequestError extends Error { } export class ServerError extends Error { - errors: Error[] + errors: ErrorItem[] - constructor(response: Response) { - super(`ServerError(${response.statusText})`) - this.errors = [ - { - status: response.status, - detail: response.statusText, - }, - ] + constructor(errors: ErrorItem[]) { + super('ServerError') + this.errors = errors } } export class UnknownResponseError extends Error { - errors: Error[] + errors: ErrorItem[] constructor(response: Response) { super(`UnknownResponseError(${response.statusText})`) diff --git a/tools/json-api-client/src/transport/json.ts b/tools/json-api-client/src/transport/json.ts index 26cbe978f9c..a646362a879 100644 --- a/tools/json-api-client/src/transport/json.ts +++ b/tools/json-api-client/src/transport/json.ts @@ -11,6 +11,7 @@ import { BadRequestError, ServerError, UnknownResponseError, + ErrorItem, } from '../errors' import * as http from './http' @@ -124,7 +125,7 @@ type ResponseType = TParams extends PaginatedRequestParams ? PaginatedApiResponse : ApiResponse -function parseResponse(response: Response): Promise { +async function parseResponse(response: Response): Promise { if (response.status === 204) { return new Promise(resolve => resolve({} as T)) } else if (response.status >= 200 && response.status < 300) { @@ -136,7 +137,24 @@ function parseResponse(response: Response): Promise { } else if (response.status === 401) { throw new AuthenticationError(response) } else if (response.status >= 500) { - throw new ServerError(response) + const json = await response.json() + let errors: ErrorItem[] + + if (json.errors) { + errors = json.errors.map((e: any) => ({ + status: response.status, + detail: e.detail, + })) + } else { + errors = [ + { + status: response.status, + detail: response.statusText, + }, + ] + } + + throw new ServerError(errors) } else { throw new UnknownResponseError(response) } From 907ae67cc044a0b300516ab9572fbca438f268ef Mon Sep 17 00:00:00 2001 From: Alex Kwiatkowski Date: Tue, 5 Nov 2019 11:17:12 -0800 Subject: [PATCH 093/199] Use ErrorsObject type --- tools/json-api-client/src/transport/json.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/json-api-client/src/transport/json.ts b/tools/json-api-client/src/transport/json.ts index a646362a879..147990e9d9d 100644 --- a/tools/json-api-client/src/transport/json.ts +++ b/tools/json-api-client/src/transport/json.ts @@ -141,7 +141,7 @@ async function parseResponse(response: Response): Promise { let errors: ErrorItem[] if (json.errors) { - errors = json.errors.map((e: any) => ({ + errors = json.errors.map((e: ErrorsObject) => ({ status: response.status, detail: e.detail, })) From 52ca8b1dd05a53575b4cb542407fb62b126a96fb Mon Sep 17 00:00:00 2001 From: Alex Kwiatkowski Date: Tue, 5 Nov 2019 11:21:11 -0800 Subject: [PATCH 094/199] Extract function to build ErrorItem's --- tools/json-api-client/src/transport/json.ts | 36 +++++++++++---------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/tools/json-api-client/src/transport/json.ts b/tools/json-api-client/src/transport/json.ts index 147990e9d9d..85623ea8b33 100644 --- a/tools/json-api-client/src/transport/json.ts +++ b/tools/json-api-client/src/transport/json.ts @@ -137,25 +137,27 @@ async function parseResponse(response: Response): Promise { } else if (response.status === 401) { throw new AuthenticationError(response) } else if (response.status >= 500) { - const json = await response.json() - let errors: ErrorItem[] - - if (json.errors) { - errors = json.errors.map((e: ErrorsObject) => ({ - status: response.status, - detail: e.detail, - })) - } else { - errors = [ - { - status: response.status, - detail: response.statusText, - }, - ] - } - + const errors = await errorItems(response) throw new ServerError(errors) } else { throw new UnknownResponseError(response) } } + +async function errorItems(response: Response): Promise { + const json = await response.json() + + if (json.errors) { + return json.errors.map((e: ErrorsObject) => ({ + status: response.status, + detail: e.detail, + })) + } + + return [ + { + status: response.status, + detail: response.statusText, + }, + ] +} From f59e9005c954ac3fe4f3c6729a56d6e625e762a5 Mon Sep 17 00:00:00 2001 From: Alex Kwiatkowski Date: Tue, 5 Nov 2019 11:27:44 -0800 Subject: [PATCH 095/199] Remove unrequired Promise wrapper --- tools/json-api-client/src/transport/json.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/json-api-client/src/transport/json.ts b/tools/json-api-client/src/transport/json.ts index 85623ea8b33..44e291d5b5d 100644 --- a/tools/json-api-client/src/transport/json.ts +++ b/tools/json-api-client/src/transport/json.ts @@ -127,7 +127,7 @@ type ResponseType = TParams extends PaginatedRequestParams async function parseResponse(response: Response): Promise { if (response.status === 204) { - return new Promise(resolve => resolve({} as T)) + return {} as T } else if (response.status >= 200 && response.status < 300) { return response.json() } else if (response.status === 400) { From e7990040fa3b8ac0cddb7e759248909f3b9b6e00 Mon Sep 17 00:00:00 2001 From: henrynguyen5 <6404866+HenryNguyen5@users.noreply.github.com> Date: Tue, 5 Nov 2019 18:20:12 -0500 Subject: [PATCH 096/199] Update evm typescript dependency to 3.7 --- evm/package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/evm/package.json b/evm/package.json index f492175cec4..d9938d79cc6 100644 --- a/evm/package.json +++ b/evm/package.json @@ -62,7 +62,7 @@ "ts-node": "^8.4.1", "typechain": "1.0.1", "typechain-target-ethers": "^1.0.0-beta.1", - "typescript": "^3.7.0-beta" + "typescript": "^3.7.0" }, "prettier": "@chainlink/prettier-config", "files": [ diff --git a/yarn.lock b/yarn.lock index 009b22b29a9..4c084c1ef64 100644 --- a/yarn.lock +++ b/yarn.lock @@ -21820,10 +21820,10 @@ typescript@^3.6.3: resolved "https://registry.npmjs.org/typescript/-/typescript-3.6.3.tgz#fea942fabb20f7e1ca7164ff626f1a9f3f70b4da" integrity sha512-N7bceJL1CtRQ2RiG0AQME13ksR7DiuQh/QehubYcghzv20tnh+MQnQIuJddTmsbqYj+dztchykemz0zFzlvdQw== -typescript@^3.7.0-beta: - version "3.7.0-dev.20191021" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.7.0-dev.20191021.tgz#e0238e0b3eed9fc265767a1b7f5346fea8ab5edb" - integrity sha512-SSx/+QkyW7PMcaGQXzVmVkrRmmaLFsdOYXhP9sY9eYMiHrfmtZE9EL2hjtbihfnpyWfCmPup69VgbB4dTTEQgg== +typescript@^3.7.0: + version "3.7.2" + resolved "https://registry.npmjs.org/typescript/-/typescript-3.7.2.tgz#27e489b95fa5909445e9fef5ee48d81697ad18fb" + integrity sha512-ml7V7JfiN2Xwvcer+XAf2csGO1bPBdRbFCkYBczNZggrBZ9c7G3riSUeJmqEU5uOtXNPMhE3n+R4FA/3YOAWOQ== typewise-core@^1.2, typewise-core@^1.2.0: version "1.2.0" From 0f36f0053d02eadb6545803e964b89ac67e8e179 Mon Sep 17 00:00:00 2001 From: henrynguyen5 <6404866+HenryNguyen5@users.noreply.github.com> Date: Mon, 21 Oct 2019 18:29:55 -0400 Subject: [PATCH 097/199] Swap out truffle compile for @0x/sol-compiler The swap out of the compiler allows us to create a much more efficient build process for developers. We gain the ability to do dockerized compilation to gain an order of magnitude of speedup for compiling solidity files; the ability to enable watch mode which leads to hot- reloadable solidity tests written in typescript; and the eventual removal of the heavyweight truffle dependency. Some bigger changes that were made along with the swap for compatibility and cleanup reasons: - Generating a index barrel file for generated contract factories via the evm/scripts/export-generated-contract-factories.ts script. This lets us export our contract factories that were generated from the typechain package. Allowing package consumers to access these generated factories, giving them typesafe interaction and deployment of our solidity contracts. - Moving typechain generated files from dist/generated to src/generated This is a change for future watch mode compilation and testing of contracts, the directory src/generated is currently ignored for now, incase we want to move it again before implementation starts. - Move compiled solidity artifacts from build/contracts to dist/artifacts This was done to aggregate our compilation outputs into dist/ since we now include javascript output, not just solidity output. --- core/store/tx_manager.go | 6 +- evm/compiler.json | 20 +++ evm/package.json | 24 +-- .../export-generated-contract-factories.ts | 122 +++++++++++++ evm/src/helpersV2.ts | 5 +- evm/src/index.ts | 3 +- evm/tsconfig.json | 2 - evm/v0.5/.gitignore | 1 + evm/v0.5/compiler.json | 20 +++ evm/v0.5/package.json | 16 +- evm/v0.5/src/index.ts | 3 + evm/v0.5/test/support/LinkToken.json | 163 +++++++++++++++++ evm/v0.5/test/support/helpers.ts | 2 +- evm/v0.5/test/support/linkToken.ts | 165 ------------------ evm/v0.5/truffle.js | 1 + evm/v0.5/tsconfig.json | 24 +++ yarn.lock | 68 ++++++-- 17 files changed, 445 insertions(+), 200 deletions(-) create mode 100644 evm/compiler.json create mode 100644 evm/scripts/export-generated-contract-factories.ts create mode 100644 evm/v0.5/compiler.json create mode 100644 evm/v0.5/src/index.ts create mode 100644 evm/v0.5/test/support/LinkToken.json delete mode 100644 evm/v0.5/test/support/linkToken.ts create mode 100644 evm/v0.5/tsconfig.json diff --git a/core/store/tx_manager.go b/core/store/tx_manager.go index 8a5ff2d25ac..74967468d3d 100644 --- a/core/store/tx_manager.go +++ b/core/store/tx_manager.go @@ -841,16 +841,16 @@ type Contract struct { ABI abi.ABI } -// GetContract loads the contract JSON file from ../evm/build/contracts +// GetContract loads the contract JSON file from ../evm/dist/artifacts // and parses the ABI JSON contents into an abi.ABI object func GetContract(name string) (*Contract, error) { - box := packr.NewBox("../../evm/build/contracts") + box := packr.NewBox("../../evm/dist/artifacts") jsonFile, err := box.Find(name + ".json") if err != nil { return nil, errors.Wrap(err, "unable to read contract JSON") } - abiBytes := gjson.GetBytes(jsonFile, "abi") + abiBytes := gjson.GetBytes(jsonFile, "abi.compilerOutput") abiParsed, err := abi.JSON(strings.NewReader(abiBytes.Raw)) if err != nil { return nil, err diff --git a/evm/compiler.json b/evm/compiler.json new file mode 100644 index 00000000000..fa28db0d00c --- /dev/null +++ b/evm/compiler.json @@ -0,0 +1,20 @@ +{ + "contractsDir": "contracts", + "artifactsDir": "dist/artifacts", + "contracts": "*", + "solcVersion": "0.4.24", + "useDockerisedSolc": false, + "compilerSettings": { + "outputSelection": { + "*": { + "*": [ + "abi", + "evm.bytecode.object", + "evm.bytecode.sourceMap", + "evm.deployedBytecode.object", + "evm.deployedBytecode.sourceMap" + ] + } + } + } +} diff --git a/evm/package.json b/evm/package.json index d9938d79cc6..8e43d56d29c 100644 --- a/evm/package.json +++ b/evm/package.json @@ -4,10 +4,9 @@ "license": "MIT", "main": "./dist/src", "scripts": { - "generate-typings": "echo \"\u001b[1;33mWARN: Please import the generated contract factory classes for Coordinator directly from the chainlinkv0.5 package when exported.\u001b[0m\" && typechain --target ethers --outDir src/generated \"{src/LinkToken*,build/contracts/*,./v0.5/build/contracts/{Coordinator,CoordinatorInterface}.json}\"", - "build": "truffle build", - "_comment": "XXX: The definition files should be copied over by the tsc framework! This will break on windows!!", - "postbuild": "yarn generate-typings && yarn tsc && cp -f src/generated/*.d.ts dist/src/generated", + "generate-typings": "typechain --target ethers --outDir src/generated \"{src/LinkToken*,dist/artifacts/*}\"", + "postgenerate-typings": "yarn export-typings src/generated dist/src/generated", + "build": "sol-compiler && yarn generate-typings && tsc", "build:windows": "truffle.cmd build", "depcheck": "echo 'chainlink' && depcheck --ignore-dirs=build/contracts,v0.5,box || true", "eslint": "eslint --ext .ts, testv2 src", @@ -19,11 +18,13 @@ "format": "prettier --write \"{src,testv2}/*/**\"", "prepublishOnly": "yarn workspace chainlinkv0.5 setup && yarn setup && yarn lint && yarn test", "setup": "ts-node ./scripts/build", - "truffle:migrate:cldev": "truffle migrate --network cldev" + "truffle:migrate:cldev": "truffle migrate --network cldev", + "export-typings": "ts-node scripts/export-generated-contract-factories" }, "dependencies": { "@types/path-to-regexp": "^1.7.0", "cbor": "^4.1.1", + "chainlinkv0.5": "0.0.1", "eth-ens-namehash": "^2.0.8", "ethereumjs-abi": "^0.6.7", "ethereumjs-util": "^6.1.0", @@ -31,10 +32,10 @@ "link_token": "^1.0.6", "openzeppelin-solidity": "^1.12.0", "solidity-cborutils": "^1.0.4", - "truffle-contract": "^4.0.31", - "web3": "^1.2.0" + "truffle-contract": "^4.0.31" }, "devDependencies": { + "@0x/sol-compiler": "^3.1.15", "@chainlink/eslint-config": "0.0.1", "@chainlink/prettier-config": "0.0.1", "@types/cbor": "^2.0.0", @@ -43,9 +44,10 @@ "@types/ganache-core": "^2.7.0", "@types/jest": "^24.0.18", "@types/node": "^12.7.5", - "@types/web3": "^1.0.0", + "@types/shelljs": "^0.8.6", "bn.js": "^4.11.0", "chai": "^4.2.0", + "chalk": "^2.4.2", "cross-env": "^6.0.3", "debug": "^4.1.1", "depcheck": "^0.8.3", @@ -55,13 +57,15 @@ "jest": "^24.9.0", "jest-circus": "^24.9.0", "prettier": "^1.18.2", + "rimraf": "^3.0.0", + "shelljs": "^0.8.3", "solc": "0.4.24", "solhint": "^2.1.0", "truffle": "^5.0.25", "ts-jest": "^24.1.0", "ts-node": "^8.4.1", - "typechain": "1.0.1", - "typechain-target-ethers": "^1.0.0-beta.1", + "typechain": "1.0.3", + "typechain-target-ethers": "^1.0.1", "typescript": "^3.7.0" }, "prettier": "@chainlink/prettier-config", diff --git a/evm/scripts/export-generated-contract-factories.ts b/evm/scripts/export-generated-contract-factories.ts new file mode 100644 index 00000000000..1b542662f54 --- /dev/null +++ b/evm/scripts/export-generated-contract-factories.ts @@ -0,0 +1,122 @@ +import { lstatSync, readdirSync, writeFileSync, readFileSync } from 'fs' +import { resolve, join, parse } from 'path' +import chalk from 'chalk' +import { cp, rm } from 'shelljs' +import { mkdir } from 'shelljs' + +const DRY_RUN = process.env.DRY_RUN + +// logging functions with colour output +const err = (...args: string[]) => console.error(chalk.red(...args)) +const warn = (...args: string[]) => console.warn(chalk.yellow(...args)) +const log = (...args: string[]) => console.log(chalk.green(...args)) +const info = (...args: string[]) => console.log(chalk.grey(...args)) + +function main() { + const [generatedPath, distPath] = [process.argv[2], process.argv[3]].map(p => + resolve(p), + ) + + console.log(process.cwd()) + exportGeneratedContractFactories(generatedPath, distPath) +} +main() + +/** + * Export all generated contract factories and their associated types + */ +export function exportGeneratedContractFactories( + generatedPath: string, + distPath: string, +) { + const dir = getGeneratedFilePaths(generatedPath) + const exportPaths = dir + .map(makeExportPath) + .filter(Boolean) + .join('\n') + info(`Export paths:\n${exportPaths}`) + + if (!DRY_RUN) { + makeBarrelFile(generatedPath, exportPaths) + copyTypings(generatedPath, distPath) + } +} + +/** + * This copies the .d.ts files from the generated phase over, + * since the typescript compiler drops any .d.ts source files during + * compilation + * @param generatedPath The path of the generated files + * @param distPath The path of the post-tsc generated files + */ +function copyTypings(generatedPath: string, distPath: string) { + mkdir('-p', distPath) + cp(`${generatedPath}/*.d.ts`, distPath) +} + +/** + * Create a barrel file which contains all of the exports. + * This will replace the existing barrel file if it already exists. + * @param data The data to write to the barrel file + */ +function makeBarrelFile(path: string, data: string) { + const exportFilePath = join(path, 'index.ts') + warn(`Writing barrel file to ${exportFilePath}`) + writeFileSync(exportFilePath, data) + mergeIndexes(path) +} + +/** + * Making a barrel file makes us end up with two index files, + * since one already is generated (albeit with a .d.ts extension). + * + * This function merges both of them into one index file, and deletes the + * .d.fs one. + * @param path The path of the generated files + */ +function mergeIndexes(path: string) { + const exportFilePath = join(path, 'index.ts') + const declarationsFilePath = join(path, 'index.d.ts') + const declarationFile = readFileSync(declarationsFilePath) + const exportsFile = readFileSync(exportFilePath) + + writeFileSync(exportFilePath, [declarationFile, exportsFile].join('\n')) + rm(declarationsFilePath) +} + +/** + * Check if the generated directory for smart contract factories exists + */ +function generatedDirExists(path: string): boolean { + log(`Checking if directory: ${path} exists...`) + const exists = lstatSync(path).isDirectory() + return exists +} + +/** + * Get all the generated file paths from the generated directory + */ +function getGeneratedFilePaths(path: string): string[] { + if (!generatedDirExists(path)) { + err(`Directory ${path} does not exist. Exiting...`) + process.exit(1) + } + log(`Directory ${path} exists, continuing...`) + return readdirSync(path) +} + +/** + * Create an es6 export of a filename, handles interface and index conflicts naively + * @param fileName The filename to export + */ +function makeExportPath(fileName: string) { + const { name } = parse(fileName) + + if (name.endsWith('.d')) { + return '' + } else if (name === 'index') { + return '' + } else { + return `export * from './${name}'` + } +} diff --git a/evm/src/helpersV2.ts b/evm/src/helpersV2.ts index bfde87db599..4626a0ef350 100644 --- a/evm/src/helpersV2.ts +++ b/evm/src/helpersV2.ts @@ -2,7 +2,7 @@ import { ethers } from 'ethers' import { createFundedWallet } from './wallet' import { assert } from 'chai' import { Oracle } from './generated/Oracle' -import { CoordinatorFactory } from './generated/CoordinatorFactory' +import { generated as chainlinkv05 } from 'chainlinkv0.5' import { LinkToken } from './generated/LinkToken' import { makeDebug } from './debug' import cbor from 'cbor' @@ -435,8 +435,9 @@ export function hexToBuf(hexstr: string): Buffer { return Buffer.from(stripHexPrefix(hexstr), 'hex') } +const { CoordinatorFactory } = chainlinkv05 type Hash = ReturnType -type Coordinator = ReturnType +type Coordinator = ReturnType type ServiceAgreement = Parameters[0] /** diff --git a/evm/src/index.ts b/evm/src/index.ts index 386492fdc5a..c0cec65e207 100644 --- a/evm/src/index.ts +++ b/evm/src/index.ts @@ -4,5 +4,6 @@ import * as debug from './debug' import LinkToken from './LinkToken.json' import * as wallet from './wallet' import * as matchers from './matchersV2' +import * as generated from './generated' -export { contract, helpers, debug, LinkToken, wallet, matchers } +export { contract, helpers, debug, LinkToken, wallet, matchers, generated } diff --git a/evm/tsconfig.json b/evm/tsconfig.json index eea5e1d9343..32ff54d4a02 100644 --- a/evm/tsconfig.json +++ b/evm/tsconfig.json @@ -3,12 +3,10 @@ "incremental": true, "target": "es2019", "module": "commonjs", - "allowJs": true, "declaration": true, "sourceMap": true, "skipLibCheck": true, "outDir": "./dist", - "noEmitOnError": false, "resolveJsonModule": true, "strict": true, "noUnusedLocals": true, diff --git a/evm/v0.5/.gitignore b/evm/v0.5/.gitignore index d6838e4effe..25a7ff8dacd 100644 --- a/evm/v0.5/.gitignore +++ b/evm/v0.5/.gitignore @@ -1,3 +1,4 @@ node_modules/ build/ !contracts/vendor +src/generated diff --git a/evm/v0.5/compiler.json b/evm/v0.5/compiler.json new file mode 100644 index 00000000000..e07fe488b0c --- /dev/null +++ b/evm/v0.5/compiler.json @@ -0,0 +1,20 @@ +{ + "contractsDir": "contracts", + "artifactsDir": "dist/artifacts", + "contracts": "*", + "solcVersion": "0.5.0", + "useDockerisedSolc": false, + "compilerSettings": { + "outputSelection": { + "*": { + "*": [ + "abi", + "evm.bytecode.object", + "evm.bytecode.sourceMap", + "evm.deployedBytecode.object", + "evm.deployedBytecode.sourceMap" + ] + } + } + } +} diff --git a/evm/v0.5/package.json b/evm/v0.5/package.json index 5261c844807..af7def9e08d 100644 --- a/evm/v0.5/package.json +++ b/evm/v0.5/package.json @@ -2,9 +2,12 @@ "name": "chainlinkv0.5", "version": "0.0.1", "license": "MIT", + "main": "./dist/src", + "typings": "./dist/src", "scripts": { - "build": "truffle build", - "build.windows": "truffle.cmd build", + "generate-typings": "typechain --target ethers --outDir src/generated \"{test/support/LinkToken*,dist/artifacts/*}\"", + "postgenerate-typings": "yarn workspace chainlink export-typings v0.5/src/generated v0.5/dist/src/generated", + "build": "sol-compiler && yarn generate-typings && (tsc || true)", "depcheck": "echo \"chainlinkv0.5\" && depcheck --ignore-dirs=build/contracts || true", "eslint": "eslint --ext .ts,.js test", "solhint": "solhint ./contracts/**/*.sol", @@ -20,10 +23,12 @@ "cbor": "^4.1.1", "ethereumjs-abi": "^0.6.8", "ethereumjs-util": "^6.1.0", - "truffle-contract": "^4.0.31" + "truffle-contract": "^4.0.31", + "typescript": "^3.7.0-beta" }, "devDependencies": { "@babel/core": "^7.6.4", + "@0x/sol-compiler": "^3.1.15", "@babel/plugin-proposal-class-properties": "^7.3.0", "@babel/polyfill": "^7.2.5", "@babel/preset-env": "^7.6.0", @@ -45,10 +50,13 @@ "solhint": "^2.1.0", "truffle": "^5.0.25", "ts-node": "^8.4.1", + "typechain": "^1.0.3", + "typechain-target-ethers": "^1.0.1", "types-bn": "^0.0.1" }, "files": [ - "contracts" + "contracts", + "dist" ], "prettier": "@chainlink/prettier-config" } diff --git a/evm/v0.5/src/index.ts b/evm/v0.5/src/index.ts new file mode 100644 index 00000000000..bda4ef3786d --- /dev/null +++ b/evm/v0.5/src/index.ts @@ -0,0 +1,3 @@ +import * as generated from './generated' + +export { generated } diff --git a/evm/v0.5/test/support/LinkToken.json b/evm/v0.5/test/support/LinkToken.json new file mode 100644 index 00000000000..45c4cb218ab --- /dev/null +++ b/evm/v0.5/test/support/LinkToken.json @@ -0,0 +1,163 @@ +{ + "bytecode": "0x6060604052341561000f57600080fd5b5b600160a060020a03331660009081526001602052604090206b033b2e3c9fd0803ce800000090555b5b610c51806100486000396000f300606060405236156100b75763ffffffff7c010000000000000000000000000000000000000000000000000000000060003504166306fdde0381146100bc578063095ea7b31461014757806318160ddd1461017d57806323b872dd146101a2578063313ce567146101de5780634000aea014610207578063661884631461028057806370a08231146102b657806395d89b41146102e7578063a9059cbb14610372578063d73dd623146103a8578063dd62ed3e146103de575b600080fd5b34156100c757600080fd5b6100cf610415565b60405160208082528190810183818151815260200191508051906020019080838360005b8381101561010c5780820151818401525b6020016100f3565b50505050905090810190601f1680156101395780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b341561015257600080fd5b610169600160a060020a036004351660243561044c565b604051901515815260200160405180910390f35b341561018857600080fd5b610190610499565b60405190815260200160405180910390f35b34156101ad57600080fd5b610169600160a060020a03600435811690602435166044356104a9565b604051901515815260200160405180910390f35b34156101e957600080fd5b6101f16104f8565b60405160ff909116815260200160405180910390f35b341561021257600080fd5b61016960048035600160a060020a03169060248035919060649060443590810190830135806020601f820181900481020160405190810160405281815292919060208401838380828437509496506104fd95505050505050565b604051901515815260200160405180910390f35b341561028b57600080fd5b610169600160a060020a036004351660243561054c565b604051901515815260200160405180910390f35b34156102c157600080fd5b610190600160a060020a0360043516610648565b60405190815260200160405180910390f35b34156102f257600080fd5b6100cf610667565b60405160208082528190810183818151815260200191508051906020019080838360005b8381101561010c5780820151818401525b6020016100f3565b50505050905090810190601f1680156101395780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b341561037d57600080fd5b610169600160a060020a036004351660243561069e565b604051901515815260200160405180910390f35b34156103b357600080fd5b610169600160a060020a03600435166024356106eb565b604051901515815260200160405180910390f35b34156103e957600080fd5b610190600160a060020a0360043581169060243516610790565b60405190815260200160405180910390f35b60408051908101604052600f81527f436861696e4c696e6b20546f6b656e0000000000000000000000000000000000602082015281565b600082600160a060020a03811615801590610479575030600160a060020a031681600160a060020a031614155b151561048457600080fd5b61048e84846107bd565b91505b5b5092915050565b6b033b2e3c9fd0803ce800000081565b600082600160a060020a038116158015906104d6575030600160a060020a031681600160a060020a031614155b15156104e157600080fd5b6104ec85858561082a565b91505b5b509392505050565b601281565b600083600160a060020a0381161580159061052a575030600160a060020a031681600160a060020a031614155b151561053557600080fd5b6104ec85858561093c565b91505b5b509392505050565b600160a060020a033381166000908152600260209081526040808320938616835292905290812054808311156105a957600160a060020a0333811660009081526002602090815260408083209388168352929052908120556105e0565b6105b9818463ffffffff610a2316565b600160a060020a033381166000908152600260209081526040808320938916835292905220555b600160a060020a0333811660008181526002602090815260408083209489168084529490915290819020547f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925915190815260200160405180910390a3600191505b5092915050565b600160a060020a0381166000908152600160205260409020545b919050565b60408051908101604052600481527f4c494e4b00000000000000000000000000000000000000000000000000000000602082015281565b600082600160a060020a038116158015906106cb575030600160a060020a031681600160a060020a031614155b15156106d657600080fd5b61048e8484610a3a565b91505b5b5092915050565b600160a060020a033381166000908152600260209081526040808320938616835292905290812054610723908363ffffffff610afa16565b600160a060020a0333811660008181526002602090815260408083209489168084529490915290819020849055919290917f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92591905190815260200160405180910390a35060015b92915050565b600160a060020a038083166000908152600260209081526040808320938516835292905220545b92915050565b600160a060020a03338116600081815260026020908152604080832094871680845294909152808220859055909291907f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b9259085905190815260200160405180910390a35060015b92915050565b600160a060020a03808416600081815260026020908152604080832033909516835293815283822054928252600190529182205461086e908463ffffffff610a2316565b600160a060020a0380871660009081526001602052604080822093909355908616815220546108a3908463ffffffff610afa16565b600160a060020a0385166000908152600160205260409020556108cc818463ffffffff610a2316565b600160a060020a03808716600081815260026020908152604080832033861684529091529081902093909355908616917fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9086905190815260200160405180910390a3600191505b509392505050565b60006109488484610a3a565b5083600160a060020a031633600160a060020a03167fe19260aff97b920c7df27010903aeb9c8d2be5d310a2c67824cf3f15396e4c16858560405182815260406020820181815290820183818151815260200191508051906020019080838360005b838110156109c35780820151818401525b6020016109aa565b50505050905090810190601f1680156109f05780820380516001836020036101000a031916815260200191505b50935050505060405180910390a3610a0784610b14565b15610a1757610a17848484610b23565b5b5060015b9392505050565b600082821115610a2f57fe5b508082035b92915050565b600160a060020a033316600090815260016020526040812054610a63908363ffffffff610a2316565b600160a060020a033381166000908152600160205260408082209390935590851681522054610a98908363ffffffff610afa16565b600160a060020a0380851660008181526001602052604090819020939093559133909116907fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9085905190815260200160405180910390a35060015b92915050565b600082820183811015610b0957fe5b8091505b5092915050565b6000813b908111905b50919050565b82600160a060020a03811663a4c0ed363385856040518463ffffffff167c01000000000000000000000000000000000000000000000000000000000281526004018084600160a060020a0316600160a060020a0316815260200183815260200180602001828103825283818151815260200191508051906020019080838360005b83811015610bbd5780820151818401525b602001610ba4565b50505050905090810190601f168015610bea5780820380516001836020036101000a031916815260200191505b50945050505050600060405180830381600087803b1515610c0a57600080fd5b6102c65a03f11515610c1b57600080fd5b5050505b505050505600a165627a7a72305820c5f438ff94e5ddaf2058efa0019e246c636c37a622e04bb67827c7374acad8d60029", + "abi": [ + { + "constant": true, + "inputs": [], + "name": "name", + "outputs": [{ "name": "", "type": "string" }], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { "name": "_spender", "type": "address" }, + { "name": "_value", "type": "uint256" } + ], + "name": "approve", + "outputs": [{ "name": "", "type": "bool" }], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "totalSupply", + "outputs": [{ "name": "", "type": "uint256" }], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { "name": "_from", "type": "address" }, + { "name": "_to", "type": "address" }, + { "name": "_value", "type": "uint256" } + ], + "name": "transferFrom", + "outputs": [{ "name": "", "type": "bool" }], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "decimals", + "outputs": [{ "name": "", "type": "uint8" }], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { "name": "_to", "type": "address" }, + { "name": "_value", "type": "uint256" }, + { "name": "_data", "type": "bytes" } + ], + "name": "transferAndCall", + "outputs": [{ "name": "success", "type": "bool" }], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { "name": "_spender", "type": "address" }, + { "name": "_subtractedValue", "type": "uint256" } + ], + "name": "decreaseApproval", + "outputs": [{ "name": "success", "type": "bool" }], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [{ "name": "_owner", "type": "address" }], + "name": "balanceOf", + "outputs": [{ "name": "balance", "type": "uint256" }], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "symbol", + "outputs": [{ "name": "", "type": "string" }], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { "name": "_to", "type": "address" }, + { "name": "_value", "type": "uint256" } + ], + "name": "transfer", + "outputs": [{ "name": "success", "type": "bool" }], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { "name": "_spender", "type": "address" }, + { "name": "_addedValue", "type": "uint256" } + ], + "name": "increaseApproval", + "outputs": [{ "name": "success", "type": "bool" }], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { "name": "_owner", "type": "address" }, + { "name": "_spender", "type": "address" } + ], + "name": "allowance", + "outputs": [{ "name": "remaining", "type": "uint256" }], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { "indexed": true, "name": "from", "type": "address" }, + { "indexed": true, "name": "to", "type": "address" }, + { "indexed": false, "name": "value", "type": "uint256" }, + { "indexed": false, "name": "data", "type": "bytes" } + ], + "name": "Transfer", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { "indexed": true, "name": "owner", "type": "address" }, + { "indexed": true, "name": "spender", "type": "address" }, + { "indexed": false, "name": "value", "type": "uint256" } + ], + "name": "Approval", + "type": "event" + } + ] +} diff --git a/evm/v0.5/test/support/helpers.ts b/evm/v0.5/test/support/helpers.ts index 79719b63a0b..208691b723c 100644 --- a/evm/v0.5/test/support/helpers.ts +++ b/evm/v0.5/test/support/helpers.ts @@ -4,7 +4,7 @@ import * as abi from 'ethereumjs-abi' import * as util from 'ethereumjs-util' import { FunctionFragment, ParamType } from 'ethers/utils/abi-coder' import TruffleContract from 'truffle-contract' -import { linkToken } from './linkToken' +import linkToken from './LinkToken.json' import { assertBigNum } from './matchers' // https://github.com/ethereum/web3.js/issues/1119#issuecomment-394217563 diff --git a/evm/v0.5/test/support/linkToken.ts b/evm/v0.5/test/support/linkToken.ts deleted file mode 100644 index 3ae3a5af6d9..00000000000 --- a/evm/v0.5/test/support/linkToken.ts +++ /dev/null @@ -1,165 +0,0 @@ -export const linkToken = { - // bytecode from verification on https://etherscan.io/address/0x514910771af9ca656af840dff83e8264ecf986ca#contracts - bytecode: - '0x6060604052341561000f57600080fd5b5b600160a060020a03331660009081526001602052604090206b033b2e3c9fd0803ce800000090555b5b610c51806100486000396000f300606060405236156100b75763ffffffff7c010000000000000000000000000000000000000000000000000000000060003504166306fdde0381146100bc578063095ea7b31461014757806318160ddd1461017d57806323b872dd146101a2578063313ce567146101de5780634000aea014610207578063661884631461028057806370a08231146102b657806395d89b41146102e7578063a9059cbb14610372578063d73dd623146103a8578063dd62ed3e146103de575b600080fd5b34156100c757600080fd5b6100cf610415565b60405160208082528190810183818151815260200191508051906020019080838360005b8381101561010c5780820151818401525b6020016100f3565b50505050905090810190601f1680156101395780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b341561015257600080fd5b610169600160a060020a036004351660243561044c565b604051901515815260200160405180910390f35b341561018857600080fd5b610190610499565b60405190815260200160405180910390f35b34156101ad57600080fd5b610169600160a060020a03600435811690602435166044356104a9565b604051901515815260200160405180910390f35b34156101e957600080fd5b6101f16104f8565b60405160ff909116815260200160405180910390f35b341561021257600080fd5b61016960048035600160a060020a03169060248035919060649060443590810190830135806020601f820181900481020160405190810160405281815292919060208401838380828437509496506104fd95505050505050565b604051901515815260200160405180910390f35b341561028b57600080fd5b610169600160a060020a036004351660243561054c565b604051901515815260200160405180910390f35b34156102c157600080fd5b610190600160a060020a0360043516610648565b60405190815260200160405180910390f35b34156102f257600080fd5b6100cf610667565b60405160208082528190810183818151815260200191508051906020019080838360005b8381101561010c5780820151818401525b6020016100f3565b50505050905090810190601f1680156101395780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b341561037d57600080fd5b610169600160a060020a036004351660243561069e565b604051901515815260200160405180910390f35b34156103b357600080fd5b610169600160a060020a03600435166024356106eb565b604051901515815260200160405180910390f35b34156103e957600080fd5b610190600160a060020a0360043581169060243516610790565b60405190815260200160405180910390f35b60408051908101604052600f81527f436861696e4c696e6b20546f6b656e0000000000000000000000000000000000602082015281565b600082600160a060020a03811615801590610479575030600160a060020a031681600160a060020a031614155b151561048457600080fd5b61048e84846107bd565b91505b5b5092915050565b6b033b2e3c9fd0803ce800000081565b600082600160a060020a038116158015906104d6575030600160a060020a031681600160a060020a031614155b15156104e157600080fd5b6104ec85858561082a565b91505b5b509392505050565b601281565b600083600160a060020a0381161580159061052a575030600160a060020a031681600160a060020a031614155b151561053557600080fd5b6104ec85858561093c565b91505b5b509392505050565b600160a060020a033381166000908152600260209081526040808320938616835292905290812054808311156105a957600160a060020a0333811660009081526002602090815260408083209388168352929052908120556105e0565b6105b9818463ffffffff610a2316565b600160a060020a033381166000908152600260209081526040808320938916835292905220555b600160a060020a0333811660008181526002602090815260408083209489168084529490915290819020547f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925915190815260200160405180910390a3600191505b5092915050565b600160a060020a0381166000908152600160205260409020545b919050565b60408051908101604052600481527f4c494e4b00000000000000000000000000000000000000000000000000000000602082015281565b600082600160a060020a038116158015906106cb575030600160a060020a031681600160a060020a031614155b15156106d657600080fd5b61048e8484610a3a565b91505b5b5092915050565b600160a060020a033381166000908152600260209081526040808320938616835292905290812054610723908363ffffffff610afa16565b600160a060020a0333811660008181526002602090815260408083209489168084529490915290819020849055919290917f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92591905190815260200160405180910390a35060015b92915050565b600160a060020a038083166000908152600260209081526040808320938516835292905220545b92915050565b600160a060020a03338116600081815260026020908152604080832094871680845294909152808220859055909291907f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b9259085905190815260200160405180910390a35060015b92915050565b600160a060020a03808416600081815260026020908152604080832033909516835293815283822054928252600190529182205461086e908463ffffffff610a2316565b600160a060020a0380871660009081526001602052604080822093909355908616815220546108a3908463ffffffff610afa16565b600160a060020a0385166000908152600160205260409020556108cc818463ffffffff610a2316565b600160a060020a03808716600081815260026020908152604080832033861684529091529081902093909355908616917fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9086905190815260200160405180910390a3600191505b509392505050565b60006109488484610a3a565b5083600160a060020a031633600160a060020a03167fe19260aff97b920c7df27010903aeb9c8d2be5d310a2c67824cf3f15396e4c16858560405182815260406020820181815290820183818151815260200191508051906020019080838360005b838110156109c35780820151818401525b6020016109aa565b50505050905090810190601f1680156109f05780820380516001836020036101000a031916815260200191505b50935050505060405180910390a3610a0784610b14565b15610a1757610a17848484610b23565b5b5060015b9392505050565b600082821115610a2f57fe5b508082035b92915050565b600160a060020a033316600090815260016020526040812054610a63908363ffffffff610a2316565b600160a060020a033381166000908152600160205260408082209390935590851681522054610a98908363ffffffff610afa16565b600160a060020a0380851660008181526001602052604090819020939093559133909116907fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9085905190815260200160405180910390a35060015b92915050565b600082820183811015610b0957fe5b8091505b5092915050565b6000813b908111905b50919050565b82600160a060020a03811663a4c0ed363385856040518463ffffffff167c01000000000000000000000000000000000000000000000000000000000281526004018084600160a060020a0316600160a060020a0316815260200183815260200180602001828103825283818151815260200191508051906020019080838360005b83811015610bbd5780820151818401525b602001610ba4565b50505050905090810190601f168015610bea5780820380516001836020036101000a031916815260200191505b50945050505050600060405180830381600087803b1515610c0a57600080fd5b6102c65a03f11515610c1b57600080fd5b5050505b505050505600a165627a7a72305820c5f438ff94e5ddaf2058efa0019e246c636c37a622e04bb67827c7374acad8d60029', - abi: [ - { - constant: true, - inputs: [], - name: 'name', - outputs: [{ name: '', type: 'string' }], - payable: false, - stateMutability: 'view', - type: 'function', - }, - { - constant: false, - inputs: [ - { name: '_spender', type: 'address' }, - { name: '_value', type: 'uint256' }, - ], - name: 'approve', - outputs: [{ name: '', type: 'bool' }], - payable: false, - stateMutability: 'nonpayable', - type: 'function', - }, - { - constant: true, - inputs: [], - name: 'totalSupply', - outputs: [{ name: '', type: 'uint256' }], - payable: false, - stateMutability: 'view', - type: 'function', - }, - { - constant: false, - inputs: [ - { name: '_from', type: 'address' }, - { name: '_to', type: 'address' }, - { name: '_value', type: 'uint256' }, - ], - name: 'transferFrom', - outputs: [{ name: '', type: 'bool' }], - payable: false, - stateMutability: 'nonpayable', - type: 'function', - }, - { - constant: true, - inputs: [], - name: 'decimals', - outputs: [{ name: '', type: 'uint8' }], - payable: false, - stateMutability: 'view', - type: 'function', - }, - { - constant: false, - inputs: [ - { name: '_to', type: 'address' }, - { name: '_value', type: 'uint256' }, - { name: '_data', type: 'bytes' }, - ], - name: 'transferAndCall', - outputs: [{ name: 'success', type: 'bool' }], - payable: false, - stateMutability: 'nonpayable', - type: 'function', - }, - { - constant: false, - inputs: [ - { name: '_spender', type: 'address' }, - { name: '_subtractedValue', type: 'uint256' }, - ], - name: 'decreaseApproval', - outputs: [{ name: 'success', type: 'bool' }], - payable: false, - stateMutability: 'nonpayable', - type: 'function', - }, - { - constant: true, - inputs: [{ name: '_owner', type: 'address' }], - name: 'balanceOf', - outputs: [{ name: 'balance', type: 'uint256' }], - payable: false, - stateMutability: 'view', - type: 'function', - }, - { - constant: true, - inputs: [], - name: 'symbol', - outputs: [{ name: '', type: 'string' }], - payable: false, - stateMutability: 'view', - type: 'function', - }, - { - constant: false, - inputs: [ - { name: '_to', type: 'address' }, - { name: '_value', type: 'uint256' }, - ], - name: 'transfer', - outputs: [{ name: 'success', type: 'bool' }], - payable: false, - stateMutability: 'nonpayable', - type: 'function', - }, - { - constant: false, - inputs: [ - { name: '_spender', type: 'address' }, - { name: '_addedValue', type: 'uint256' }, - ], - name: 'increaseApproval', - outputs: [{ name: 'success', type: 'bool' }], - payable: false, - stateMutability: 'nonpayable', - type: 'function', - }, - { - constant: true, - inputs: [ - { name: '_owner', type: 'address' }, - { name: '_spender', type: 'address' }, - ], - name: 'allowance', - outputs: [{ name: 'remaining', type: 'uint256' }], - payable: false, - stateMutability: 'view', - type: 'function', - }, - { - inputs: [], - payable: false, - stateMutability: 'nonpayable', - type: 'constructor', - }, - { - anonymous: false, - inputs: [ - { indexed: true, name: 'from', type: 'address' }, - { indexed: true, name: 'to', type: 'address' }, - { indexed: false, name: 'value', type: 'uint256' }, - { indexed: false, name: 'data', type: 'bytes' }, - ], - name: 'Transfer', - type: 'event', - }, - { - anonymous: false, - inputs: [ - { indexed: true, name: 'owner', type: 'address' }, - { indexed: true, name: 'spender', type: 'address' }, - { indexed: false, name: 'value', type: 'uint256' }, - ], - name: 'Approval', - type: 'event', - }, - ], -} diff --git a/evm/v0.5/truffle.js b/evm/v0.5/truffle.js index 11dae0e66dc..e98767d6e26 100644 --- a/evm/v0.5/truffle.js +++ b/evm/v0.5/truffle.js @@ -4,6 +4,7 @@ require('@babel/register')({ require('@babel/polyfill') module.exports = { + contracts_build_directory: 'dist/artifacts', compilers: { solc: { version: '0.5.0', diff --git a/evm/v0.5/tsconfig.json b/evm/v0.5/tsconfig.json new file mode 100644 index 00000000000..6d0ed2c8ab0 --- /dev/null +++ b/evm/v0.5/tsconfig.json @@ -0,0 +1,24 @@ +{ + "compilerOptions": { + "incremental": true, + "target": "es2019", + "module": "commonjs", + "allowJs": true, + "declaration": true, + "declarationDir": "./dist", + "sourceMap": true, + "skipLibCheck": true, + "outDir": "./dist", + "noEmitOnError": false, + "resolveJsonModule": true, + // "strict": true, + // "noUnusedLocals": true, + // "noUnusedParameters": true, + // "noImplicitReturns": true, + // "noFallthroughCasesInSwitch": true, + "moduleResolution": "node", + "esModuleInterop": true, + "typeRoots": ["src/generated"] + }, + "include": ["test", "src"] +} diff --git a/yarn.lock b/yarn.lock index 4c084c1ef64..ab4a5af267f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -40,7 +40,7 @@ "@0x/sol-compiler@^3.1.15": version "3.1.15" - resolved "https://registry.yarnpkg.com/@0x/sol-compiler/-/sol-compiler-3.1.15.tgz#aaaad55008dddd69ad1e3226aa4a2832e0dd13b3" + resolved "https://registry.npmjs.org/@0x/sol-compiler/-/sol-compiler-3.1.15.tgz#aaaad55008dddd69ad1e3226aa4a2832e0dd13b3" integrity sha512-IobhcQ/whFRL942/ykKc0fV6/YstHhvnQJ0noUZ9GabMDtaBlW6k5vAerSkXZU/YyOd8sD9nw7QSm295D6HoTA== dependencies: "@0x/assert" "^2.1.6" @@ -2870,10 +2870,10 @@ dependencies: "@types/node" "*" -"@types/node@*", "@types/node@^12.7.5": - version "12.7.5" - resolved "https://registry.npmjs.org/@types/node/-/node-12.7.5.tgz#e19436e7f8e9b4601005d73673b6dc4784ffcc2f" - integrity sha512-9fq4jZVhPNW8r+UYKnxF1e2HkDWOWKM5bC2/7c9wPV835I0aOrVbS/Hw/pWPk2uKrNXQqg9Z959Kz+IYDd5p3w== +"@types/node@*": + version "12.11.5" + resolved "https://registry.npmjs.org/@types/node/-/node-12.11.5.tgz#6c3c8dc84988aff11fd2a63d7b5fbf39eaaab7b1" + integrity sha512-LC8ALj/24PhByn39nr5jnTvpE7MujK8y7LQmV74kHYF5iQ0odCPkMH4IZNZw+cobKfSXqaC8GgegcbIsQpffdA== "@types/node@^10.3.2": version "10.14.4" @@ -2885,6 +2885,11 @@ resolved "https://registry.npmjs.org/@types/node/-/node-11.13.20.tgz#da42fe93d6599f80b35ffeb5006f4c31f40d89ea" integrity sha512-JE0UpLWZTV1sGcaj0hN+Q0760OEjpgyFJ06DOMVW6qKBducKdJQaIw0TGL6ccj7VXRduIOHLWQi+tHwulZJHVQ== +"@types/node@^12.7.5": + version "12.7.5" + resolved "https://registry.npmjs.org/@types/node/-/node-12.7.5.tgz#e19436e7f8e9b4601005d73673b6dc4784ffcc2f" + integrity sha512-9fq4jZVhPNW8r+UYKnxF1e2HkDWOWKM5bC2/7c9wPV835I0aOrVbS/Hw/pWPk2uKrNXQqg9Z959Kz+IYDd5p3w== + "@types/node@^12.7.7": version "12.12.3" resolved "https://registry.yarnpkg.com/@types/node/-/node-12.12.3.tgz#ebfe83507ac506bc3486314a8aa395be66af8d23" @@ -2903,9 +2908,9 @@ path-to-regexp "*" "@types/prettier@^1.13.2": - version "1.18.2" - resolved "https://registry.npmjs.org/@types/prettier/-/prettier-1.18.2.tgz#069e7d132024d436fd1f5771f6932426a695f230" - integrity sha512-2JBasa5Qaj81Qsp/dxX2Njy+MdKC767WytHUDsRM7TYEfQvKPxsnGpnCBlBS1i2Aiv1YwCpmKSbQ6O6v8TpiKg== + version "1.18.3" + resolved "https://registry.npmjs.org/@types/prettier/-/prettier-1.18.3.tgz#64ff53329ce16139f17c3db9d3e0487199972cd8" + integrity sha512-48rnerQdcZ26odp+HOvDGX8IcUkYOCuMc2BodWYTe956MqkHlOGAG4oFQ83cjZ0a4GAgj7mb4GUClxYd2Hlodg== "@types/prop-types@*": version "15.7.2" @@ -3074,6 +3079,14 @@ "@types/glob" "*" "@types/node" "*" +"@types/shelljs@^0.8.6": + version "0.8.6" + resolved "https://registry.npmjs.org/@types/shelljs/-/shelljs-0.8.6.tgz#45193a51df99e0f00513c39a2152832399783221" + integrity sha512-svx2eQS268awlppL/P8wgDLBrsDXdKznABHJcuqXyWpSKJgE1s2clXlBvAwbO/lehTmG06NtEWJRkAk4tAgenA== + dependencies: + "@types/glob" "*" + "@types/node" "*" + "@types/solidity-parser-antlr@^0.2.3": version "0.2.3" resolved "https://registry.yarnpkg.com/@types/solidity-parser-antlr/-/solidity-parser-antlr-0.2.3.tgz#bb2d9c6511bf483afe4fc3e2714da8a924e59e3f" @@ -3148,7 +3161,7 @@ dependencies: "@types/ethereum-protocol" "*" -"@types/web3@^1.0.0", "@types/web3@^1.0.19": +"@types/web3@^1.0.19": version "1.0.19" resolved "https://registry.npmjs.org/@types/web3/-/web3-1.0.19.tgz#46b85d91d398ded9ab7c85a5dd57cb33ac558924" integrity sha512-fhZ9DyvDYDwHZUp5/STa9XW2re0E8GxoioYJ4pEUZ13YHpApSagixj7IAdoYH5uAK+UalGq6Ml8LYzmgRA/q+A== @@ -10939,7 +10952,7 @@ glob@^5.0.15: once "^1.3.0" path-is-absolute "^1.0.0" -glob@^7.0.0, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@~7.1.4: +glob@^7.0.0, glob@^7.1.3, glob@^7.1.4, glob@~7.1.4: version "7.1.4" resolved "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz#aa608a2f6c577ad357e1ae5a5c26d9a8d1969255" integrity sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A== @@ -10963,6 +10976,18 @@ glob@^7.0.3, glob@^7.1.1: once "^1.3.0" path-is-absolute "^1.0.0" +glob@^7.1.2: + version "7.1.5" + resolved "https://registry.npmjs.org/glob/-/glob-7.1.5.tgz#6714c69bee20f3c3e64c4dd905553e532b40cdc0" + integrity sha512-J9dlskqUXK1OeTOYBEn5s8aMukWMwWfs+rPTn/jn50Ux4MNXVhubL1wu/j2t+H4NVI+cXEcCaYellqaPVGXNqQ== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + global-dirs@^0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/global-dirs/-/global-dirs-0.1.1.tgz#b319c0dd4607f353f3be9cca4c72fc148c49f445" @@ -12039,7 +12064,7 @@ inflected@^1.1.6: inflight@^1.0.4: version "1.0.6" - resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + resolved "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= dependencies: once "^1.3.0" @@ -19336,7 +19361,7 @@ rimraf@2.6.3: rimraf@^3.0.0: version "3.0.0" - resolved "https://registry.npmjs.org/rimraf/-/rimraf-3.0.0.tgz#614176d4b3010b75e5c390eb0ee96f6dc0cebb9b" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.0.tgz#614176d4b3010b75e5c390eb0ee96f6dc0cebb9b" integrity sha512-NDGVxTsjqfunkds7CqsOiEnxln4Bo7Nddl3XhS4pXg5OzwkLqJ971ZVAAnB+DDLnF76N+VnDEiBHaVV8I06SUg== dependencies: glob "^7.1.3" @@ -21760,6 +21785,13 @@ typechain-target-ethers@^1.0.0-beta.1: dependencies: lodash "^4.17.15" +typechain-target-ethers@^1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/typechain-target-ethers/-/typechain-target-ethers-1.0.1.tgz#b4a1467a115d8bc46dbc6a2531d9da278bc358e5" + integrity sha512-4rsWiKxZc20D4ldF0Pviqo2z07yrW3xIbf6+GdifEZP6zmc0Z2yXOHxEVkqhjLpBp/4z5yRgpW5G2/I32TsI/w== + dependencies: + lodash "^4.17.15" + typechain@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/typechain/-/typechain-1.0.1.tgz#e0dcf63bb0ae8feb74a56ff3153abfd706473efe" @@ -21771,6 +21803,18 @@ typechain@1.0.1: lodash "^4.17.15" ts-generator "^0.0.8" +typechain@1.0.3, typechain@^1.0.3: + version "1.0.3" + resolved "https://registry.npmjs.org/typechain/-/typechain-1.0.3.tgz#c4465cdc27526989c735774ad96fadbb908bfe01" + integrity sha512-GG/isuyXy0vtm4fwfboMqnApU3hUF+XdrpQMMVUIhlHgm1kXxxf+w8+v8shn4gfhRoiATCZGDOt9fbE+/hMhjQ== + dependencies: + command-line-args "^4.0.7" + debug "^3.0.1" + fs-extra "^7.0.0" + js-sha3 "^0.8.0" + lodash "^4.17.15" + ts-generator "^0.0.8" + typed-styles@^0.0.7: version "0.0.7" resolved "https://registry.npmjs.org/typed-styles/-/typed-styles-0.0.7.tgz#93392a008794c4595119ff62dde6809dbc40a3d9" From 5daa3cd413c865e665035d211a1571c632f9ca51 Mon Sep 17 00:00:00 2001 From: henrynguyen5 <6404866+HenryNguyen5@users.noreply.github.com> Date: Wed, 30 Oct 2019 23:38:15 -0400 Subject: [PATCH 098/199] Remove commented out tsconfig properties --- evm/v0.5/tsconfig.json | 5 ----- 1 file changed, 5 deletions(-) diff --git a/evm/v0.5/tsconfig.json b/evm/v0.5/tsconfig.json index 6d0ed2c8ab0..0a3d149eea8 100644 --- a/evm/v0.5/tsconfig.json +++ b/evm/v0.5/tsconfig.json @@ -11,11 +11,6 @@ "outDir": "./dist", "noEmitOnError": false, "resolveJsonModule": true, - // "strict": true, - // "noUnusedLocals": true, - // "noUnusedParameters": true, - // "noImplicitReturns": true, - // "noFallthroughCasesInSwitch": true, "moduleResolution": "node", "esModuleInterop": true, "typeRoots": ["src/generated"] From caf202ba931dc5ce8e8b4bf71fa668727d69d9a7 Mon Sep 17 00:00:00 2001 From: henrynguyen5 <6404866+HenryNguyen5@users.noreply.github.com> Date: Sun, 3 Nov 2019 16:45:15 -0500 Subject: [PATCH 099/199] Remove peer dependencies from eslint setup This was repeatedly giving us false warnings, so removing it for now. --- tools/eslint-config/package.json | 5 ----- 1 file changed, 5 deletions(-) diff --git a/tools/eslint-config/package.json b/tools/eslint-config/package.json index 5ed2e07c1eb..d227215f202 100644 --- a/tools/eslint-config/package.json +++ b/tools/eslint-config/package.json @@ -13,11 +13,6 @@ "eslint:config:react": "eslint -c ./react.js --print-config ./base", "setup": "echo \"No setup required for @chainlink/eslint-config\"" }, - "peerDependencies": { - "@chainlink/prettier-config": "0.0.1", - "prettier": "^1.18.2", - "typescript": "^3.6.3" - }, "dependencies": { "@typescript-eslint/eslint-plugin": "^2.1.0", "@typescript-eslint/parser": "^2.5.0", From b39b591a73c54007df9d6a37cf6f5041b1021b01 Mon Sep 17 00:00:00 2001 From: henrynguyen5 <6404866+HenryNguyen5@users.noreply.github.com> Date: Sun, 3 Nov 2019 16:46:27 -0500 Subject: [PATCH 100/199] Bump chainlink and chainlinkv0.5 versions --- evm/package.json | 4 ++-- evm/v0.5/package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/evm/package.json b/evm/package.json index 8e43d56d29c..f09b2a75425 100644 --- a/evm/package.json +++ b/evm/package.json @@ -1,6 +1,6 @@ { "name": "chainlink", - "version": "0.7.7", + "version": "0.7.8", "license": "MIT", "main": "./dist/src", "scripts": { @@ -24,7 +24,7 @@ "dependencies": { "@types/path-to-regexp": "^1.7.0", "cbor": "^4.1.1", - "chainlinkv0.5": "0.0.1", + "chainlinkv0.5": "0.0.2", "eth-ens-namehash": "^2.0.8", "ethereumjs-abi": "^0.6.7", "ethereumjs-util": "^6.1.0", diff --git a/evm/v0.5/package.json b/evm/v0.5/package.json index af7def9e08d..7f17132cba0 100644 --- a/evm/v0.5/package.json +++ b/evm/v0.5/package.json @@ -1,6 +1,6 @@ { "name": "chainlinkv0.5", - "version": "0.0.1", + "version": "0.0.2", "license": "MIT", "main": "./dist/src", "typings": "./dist/src", From bf32f6cd7fe4d244fb8fd4e312ac7bcafe5a4d79 Mon Sep 17 00:00:00 2001 From: henrynguyen5 <6404866+HenryNguyen5@users.noreply.github.com> Date: Sun, 3 Nov 2019 16:46:58 -0500 Subject: [PATCH 101/199] Refactor integration scripts to use packages Since we export the generated solidity contract factories now, we can make use of nice import paths and versioning for our integration scripts. --- integration-scripts/compiler.json | 7 ++++ integration-scripts/contracts/Oracle.sol | 3 -- integration-scripts/contracts/RunLog.sol | 4 +- integration-scripts/package.json | 39 +++++++++---------- integration-scripts/src/deployContracts.ts | 7 ++-- .../src/deployLinkTokenContract.ts | 6 +-- .../src/deployV0.5Contracts.ts | 4 +- .../src/initiateServiceAgreement.ts | 9 +++-- .../src/sendEthlogTransaction.ts | 2 +- .../src/sendRunlogTransaction.ts | 6 +-- integration-scripts/truffle.js | 17 -------- yarn.lock | 27 ------------- 12 files changed, 46 insertions(+), 85 deletions(-) create mode 100644 integration-scripts/compiler.json delete mode 100644 integration-scripts/contracts/Oracle.sol delete mode 100644 integration-scripts/truffle.js diff --git a/integration-scripts/compiler.json b/integration-scripts/compiler.json new file mode 100644 index 00000000000..d5b3579a0a1 --- /dev/null +++ b/integration-scripts/compiler.json @@ -0,0 +1,7 @@ +{ + "contractsDir": "contracts", + "artifactsDir": "dist/artifacts", + "contracts": "*", + "solcVersion": "0.4.24", + "useDockerisedSolc": false +} diff --git a/integration-scripts/contracts/Oracle.sol b/integration-scripts/contracts/Oracle.sol deleted file mode 100644 index b86c03710d4..00000000000 --- a/integration-scripts/contracts/Oracle.sol +++ /dev/null @@ -1,3 +0,0 @@ -pragma solidity 0.4.24; - -import "../../evm/contracts/Oracle.sol"; diff --git a/integration-scripts/contracts/RunLog.sol b/integration-scripts/contracts/RunLog.sol index 5d6aa0b1407..95d5ecc54e0 100644 --- a/integration-scripts/contracts/RunLog.sol +++ b/integration-scripts/contracts/RunLog.sol @@ -1,6 +1,6 @@ -pragma solidity ^0.4.23; +pragma solidity ^0.4.24; -import "../../evm/contracts/Chainlinked.sol"; +import "chainlink/contracts/Chainlinked.sol"; contract RunLog is Chainlinked { uint256 constant private ORACLE_PAYMENT = 1 * LINK; // solium-disable-line zeppelin/no-arithmetic-operations diff --git a/integration-scripts/package.json b/integration-scripts/package.json index ad356ef238d..df77e8edced 100644 --- a/integration-scripts/package.json +++ b/integration-scripts/package.json @@ -7,11 +7,9 @@ "license": "MIT", "private": true, "scripts": { - "generate-typings": "echo \"\u001b[1;33mWARN: Please import the generated contract factory classes for LinkTokenV05, Coordinator and MeanAggregator directly from the chainlinkv0.5 package when exported.\u001b[0m\" && typechain --target ethers --outDir src/generated \"{build/contracts/*,../evm/v0.5/test/support/LinkTokenV05.json,../evm/v0.5/build/contracts/Coordinator.json,../evm/v0.5/build/contracts/MeanAggregator.json}\"", - "build:contracts": "truffle compile", - "postbuild:contracts": "yarn generate-typings", - "prebuild": "yarn build:contracts", - "build": "tsc", + "generate-typings": "typechain --target ethers --outDir src/generated \"dist/artifacts/*\"", + "postgenerate-typings": "yarn workspace chainlink export-typings ../integration-scripts/src/generated ../integration-scripts/dist/src/generated", + "build": "sol-compiler && yarn generate-typings && tsc", "setup": "yarn build", "format": "prettier --write \"src/**/*\"", "lint": "eslint --ext .ts src", @@ -25,33 +23,34 @@ "initiate-service-agreement": "node ./dist/initiateServiceAgreement", "start-echo-server": "node ./dist/echoServer" }, - "devDependencies": { - "@chainlink/eslint-config": "0.0.1", - "@chainlink/prettier-config": "^0.0.1", - "@types/body-parser": "^1.17.1", - "@types/express": "^4.17.1", - "@types/shelljs": "^0.8.5", - "debug": "4.1.1", - "eslint": "6.3.0", - "typechain": "1.0.1", - "typechain-target-ethers": "^1.0.0-beta.1", - "typescript": "^3.6.3" - }, "dependencies": { "@0x/sol-compiler": "^3.1.15", "@0x/sol-trace": "^2.0.20", "@chainlink/eslint-config": "0.0.1", "@chainlink/prettier-config": "^0.0.1", "body-parser": "^1.18.3", - "chainlink": "0.7.7", + "chainlink": "0.7.8", + "chainlinkv0.5": "0.0.2", "chalk": "^2.4.2", "ethers": "^4.0.37", "express": "^4.16.4", "link_token": "^1.0.6", "request-promise": "4.2.4", "shelljs": "^0.8.3", - "source-map-support": "^0.5.13", - "truffle": "^5.0.39" + "source-map-support": "^0.5.13" + }, + "devDependencies": { + "@chainlink/eslint-config": "0.0.1", + "@chainlink/prettier-config": "^0.0.1", + "@types/body-parser": "^1.17.1", + "@types/express": "^4.17.1", + "@types/shelljs": "^0.8.5", + "debug": "4.1.1", + "eslint": "6.3.0", + "typechain": "1.0.3", + "typechain-target-ethers": "^1.0.1", + "typescript": "^3.6.3", + "@0x/sol-compiler": "^3.1.15" }, "prettier": "@chainlink/prettier-config" } diff --git a/integration-scripts/src/deployContracts.ts b/integration-scripts/src/deployContracts.ts index 98ad3ffc688..31935ddc005 100644 --- a/integration-scripts/src/deployContracts.ts +++ b/integration-scripts/src/deployContracts.ts @@ -1,4 +1,4 @@ -import { OracleFactory } from './generated/OracleFactory' +import { generated as chainlink } from 'chainlink' import { createProvider, DEVNET_ADDRESS, @@ -6,9 +6,8 @@ import { getArgs, deployContract, } from './common' -import { EthLogFactory } from './generated/EthLogFactory' -import { RunLogFactory } from './generated/RunLogFactory' import { deployLinkTokenContract } from './deployLinkTokenContract' +import { EthLogFactory, RunLogFactory } from './generated' async function main() { registerPromiseHandler() @@ -28,7 +27,7 @@ async function deployContracts({ chainlinkNodeAddress }: Args) { const linkToken = await deployLinkTokenContract() const oracle = await deployContract( - { Factory: OracleFactory, name: 'Oracle', signer }, + { Factory: chainlink.OracleFactory, name: 'Oracle', signer }, linkToken.address, ) await oracle.setFulfillmentPermission(chainlinkNodeAddress, true) diff --git a/integration-scripts/src/deployLinkTokenContract.ts b/integration-scripts/src/deployLinkTokenContract.ts index 00d07a35dc2..1aa549b35a9 100644 --- a/integration-scripts/src/deployLinkTokenContract.ts +++ b/integration-scripts/src/deployLinkTokenContract.ts @@ -1,9 +1,9 @@ import { createProvider, DEVNET_ADDRESS, deployContract } from './common' -import { LinkTokenFactory } from 'chainlink/dist/src/generated/LinkTokenFactory' -import { Instance } from 'chainlink/dist/src/contract' +import { contract, generated as chainlink } from 'chainlink' +const { LinkTokenFactory } = chainlink export async function deployLinkTokenContract(): Promise< - Instance + contract.Instance > { const provider = createProvider() const signer = provider.getSigner(DEVNET_ADDRESS) diff --git a/integration-scripts/src/deployV0.5Contracts.ts b/integration-scripts/src/deployV0.5Contracts.ts index 1b80c0dbd2d..a1e87e364e0 100644 --- a/integration-scripts/src/deployV0.5Contracts.ts +++ b/integration-scripts/src/deployV0.5Contracts.ts @@ -1,5 +1,4 @@ -import { CoordinatorFactory } from './generated/CoordinatorFactory' -import { MeanAggregatorFactory } from './generated/MeanAggregatorFactory' +import { generated as chainlink } from 'chainlinkv0.5' import { registerPromiseHandler, DEVNET_ADDRESS, @@ -7,6 +6,7 @@ import { deployContract, } from './common' import { deployLinkTokenContract } from './deployLinkTokenContract' +const { CoordinatorFactory, MeanAggregatorFactory } = chainlink async function main() { registerPromiseHandler() diff --git a/integration-scripts/src/initiateServiceAgreement.ts b/integration-scripts/src/initiateServiceAgreement.ts index 39092817c32..0d08c2c2ae7 100644 --- a/integration-scripts/src/initiateServiceAgreement.ts +++ b/integration-scripts/src/initiateServiceAgreement.ts @@ -1,3 +1,5 @@ +import { generated as chainlink } from 'chainlinkv0.5' +import { contract } from 'chainlink' import { helpers } from 'chainlink' import { getArgs, @@ -5,11 +7,12 @@ import { DEVNET_ADDRESS, createProvider, } from './common' -import { CoordinatorFactory } from './generated/CoordinatorFactory' import { ethers } from 'ethers' -import { Coordinator } from './generated/Coordinator' +const { CoordinatorFactory } = chainlink -type CoordinatorParams = Parameters +type CoordinatorParams = Parameters< + contract.Instance['initiateServiceAgreement'] +> type ServiceAgreement = CoordinatorParams[0] type OracleSignatures = CoordinatorParams[1] diff --git a/integration-scripts/src/sendEthlogTransaction.ts b/integration-scripts/src/sendEthlogTransaction.ts index 1bbf12ec4c9..41ebab60362 100755 --- a/integration-scripts/src/sendEthlogTransaction.ts +++ b/integration-scripts/src/sendEthlogTransaction.ts @@ -1,5 +1,5 @@ import url from 'url' -import { EthLogFactory } from './generated/EthLogFactory' +import { EthLogFactory } from './generated' import { createProvider, getArgs, diff --git a/integration-scripts/src/sendRunlogTransaction.ts b/integration-scripts/src/sendRunlogTransaction.ts index 085da71de85..d3e4369632d 100755 --- a/integration-scripts/src/sendRunlogTransaction.ts +++ b/integration-scripts/src/sendRunlogTransaction.ts @@ -1,5 +1,5 @@ -import { RunLogFactory } from './generated/RunLogFactory' -import { LinkTokenFactory } from 'chainlink/dist/src/generated/LinkTokenFactory' +import { RunLogFactory } from './generated' +import { generated as chainlink } from 'chainlink' import { RunLog } from './generated/RunLog' import { ethers } from 'ethers' import url from 'url' @@ -46,7 +46,7 @@ async function sendRunlogTransaction({ const signer = provider.getSigner(DEVNET_ADDRESS) const runLogFactory = new RunLogFactory(signer) - const linkTokenFactory = new LinkTokenFactory(signer) + const linkTokenFactory = new chainlink.LinkTokenFactory(signer) const runLog = runLogFactory.attach(runLogAddress) const linkToken = linkTokenFactory.attach(linkTokenAddress) diff --git a/integration-scripts/truffle.js b/integration-scripts/truffle.js deleted file mode 100644 index 43f375d8d7b..00000000000 --- a/integration-scripts/truffle.js +++ /dev/null @@ -1,17 +0,0 @@ -/* eslint-disable @typescript-eslint/camelcase */ -module.exports = { - compilers: { - solc: { - version: '0.4.24', - }, - }, - networks: { - test: { - host: '127.0.0.1', - port: process.env.ETH_HTTP_PORT || 18545, - network_id: '*', - gas: 4700000, - gasPrice: 5e9, - }, - }, -} diff --git a/yarn.lock b/yarn.lock index ab4a5af267f..3af38942023 100644 --- a/yarn.lock +++ b/yarn.lock @@ -21559,15 +21559,6 @@ truffle@^5.0.25: mocha "5.2.0" original-require "1.0.1" -truffle@^5.0.39: - version "5.0.39" - resolved "https://registry.yarnpkg.com/truffle/-/truffle-5.0.39.tgz#5710ba8f60a7184d9eb51d632308f2af0a2e8aff" - integrity sha512-2a17t4o6r0rNMpeQXBc51nXigtIaP9/sU8N2zflaazvzYgDgLMZfqh/dir2mTfyybOsrR47NL310p+6+c8u8VA== - dependencies: - app-module-path "^2.2.0" - mocha "5.2.0" - original-require "1.0.1" - tryer@^1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/tryer/-/tryer-1.0.1.tgz#f2c85406800b9b0f74c9f7465b81eaad241252f8" @@ -21778,13 +21769,6 @@ type@^1.0.1: resolved "https://registry.npmjs.org/type/-/type-1.0.3.tgz#16f5d39f27a2d28d86e48f8981859e9d3296c179" integrity sha512-51IMtNfVcee8+9GJvj0spSuFcZHe9vSib6Xtgsny1Km9ugyz2mbS08I3rsUIRYgJohFRFU1160sgRodYz378Hg== -typechain-target-ethers@^1.0.0-beta.1: - version "1.0.0-beta.1" - resolved "https://registry.npmjs.org/typechain-target-ethers/-/typechain-target-ethers-1.0.0-beta.1.tgz#3c97f28e66fedb37153e152bc8a4f1f5fe2c62a1" - integrity sha512-n8IoNCcbQSvX8hvl+Q8pjh13n9ezRzvKpk0ZYIE+WEdfR76EUYN0/6k/lzSgkpJUM33QF62dwjo7ZZsXlOV6pw== - dependencies: - lodash "^4.17.15" - typechain-target-ethers@^1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/typechain-target-ethers/-/typechain-target-ethers-1.0.1.tgz#b4a1467a115d8bc46dbc6a2531d9da278bc358e5" @@ -21792,17 +21776,6 @@ typechain-target-ethers@^1.0.1: dependencies: lodash "^4.17.15" -typechain@1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/typechain/-/typechain-1.0.1.tgz#e0dcf63bb0ae8feb74a56ff3153abfd706473efe" - integrity sha512-mB/8UbcziE+CJi3ph8hHhBRILAnFjIpAELIhkNWYs08xg2UPFIWVEfK5v0Eb+G7B/XEhOMFbd0EJwGaeWp54Xg== - dependencies: - command-line-args "^4.0.7" - debug "^3.0.1" - fs-extra "^7.0.0" - lodash "^4.17.15" - ts-generator "^0.0.8" - typechain@1.0.3, typechain@^1.0.3: version "1.0.3" resolved "https://registry.npmjs.org/typechain/-/typechain-1.0.3.tgz#c4465cdc27526989c735774ad96fadbb908bfe01" From 564c2065f73b2905828838cf87e95e8ac78115bb Mon Sep 17 00:00:00 2001 From: henrynguyen5 <6404866+HenryNguyen5@users.noreply.github.com> Date: Sun, 3 Nov 2019 17:34:02 -0500 Subject: [PATCH 102/199] Cleanup dependencies and unused files --- evm/@types/globals.d.ts | 6 - evm/@types/truffle-contract.d.ts | 1 - evm/package.json | 11 +- evm/src/helpers.ts | 741 ------------------------------- evm/src/matchers.ts | 13 - evm/tsconfig.json | 2 +- yarn.lock | 4 +- 7 files changed, 4 insertions(+), 774 deletions(-) delete mode 100644 evm/@types/globals.d.ts delete mode 100644 evm/@types/truffle-contract.d.ts delete mode 100644 evm/src/helpers.ts delete mode 100644 evm/src/matchers.ts diff --git a/evm/@types/globals.d.ts b/evm/@types/globals.d.ts deleted file mode 100644 index 0b157927e95..00000000000 --- a/evm/@types/globals.d.ts +++ /dev/null @@ -1,6 +0,0 @@ -declare const web3: import('web3') -declare const assert: Chai.Assert -declare const contract: import('mocha').MochaGlobals['describe'] -declare const artifacts: { - require: (contract: string) => any -} diff --git a/evm/@types/truffle-contract.d.ts b/evm/@types/truffle-contract.d.ts deleted file mode 100644 index 6be343e2e0f..00000000000 --- a/evm/@types/truffle-contract.d.ts +++ /dev/null @@ -1 +0,0 @@ -declare module 'truffle-contract' diff --git a/evm/package.json b/evm/package.json index f09b2a75425..3834dcfacdd 100644 --- a/evm/package.json +++ b/evm/package.json @@ -22,17 +22,11 @@ "export-typings": "ts-node scripts/export-generated-contract-factories" }, "dependencies": { - "@types/path-to-regexp": "^1.7.0", "cbor": "^4.1.1", "chainlinkv0.5": "0.0.2", - "eth-ens-namehash": "^2.0.8", - "ethereumjs-abi": "^0.6.7", - "ethereumjs-util": "^6.1.0", "ethers": "^4.0.37", "link_token": "^1.0.6", - "openzeppelin-solidity": "^1.12.0", - "solidity-cborutils": "^1.0.4", - "truffle-contract": "^4.0.31" + "openzeppelin-solidity": "^1.12.0" }, "devDependencies": { "@0x/sol-compiler": "^3.1.15", @@ -48,18 +42,15 @@ "bn.js": "^4.11.0", "chai": "^4.2.0", "chalk": "^2.4.2", - "cross-env": "^6.0.3", "debug": "^4.1.1", "depcheck": "^0.8.3", "eslint": "^6.3.0", "execa": "^3.2.0", - "ganache-core": "^2.8.0", "jest": "^24.9.0", "jest-circus": "^24.9.0", "prettier": "^1.18.2", "rimraf": "^3.0.0", "shelljs": "^0.8.3", - "solc": "0.4.24", "solhint": "^2.1.0", "truffle": "^5.0.25", "ts-jest": "^24.1.0", diff --git a/evm/src/helpers.ts b/evm/src/helpers.ts deleted file mode 100644 index 620ffbaf40b..00000000000 --- a/evm/src/helpers.ts +++ /dev/null @@ -1,741 +0,0 @@ -import cbor from 'cbor' -import TruffleContract from 'truffle-contract' -import LinkToken from './LinkToken.json' -import { assertBigNum } from './matchers' -import * as abi from 'ethereumjs-abi' -import BN from 'bn.js' -import * as util from 'ethereumjs-util' - -const HEX_BASE = 16 - -web3.providers.HttpProvider.prototype.sendAsync = - web3.providers.HttpProvider.prototype.send -export const eth = web3.eth - -export interface Roles { - defaultAccount: string - oracleNode: string - oracleNode1: string - oracleNode2: string - oracleNode3: string - stranger: string - consumer: string -} - -export interface Personas { - Default: string - Neil: string - Ned: string - Nelly: string - Carol: string - Eddy: string -} - -interface RolesAndPersonas { - roles: Roles - personas: Personas -} - -/** - * Generate roles and personas for tests along with their corrolated account addresses - */ -export async function initializeRolesAndPersonas(): Promise { - const [ - defaultAccount, - oracleNode1, - oracleNode2, - oracleNode3, - stranger, - consumer, - ] = await eth.getAccounts() - - const personas: Personas = { - Default: defaultAccount, - Neil: oracleNode1, - Ned: oracleNode2, - Nelly: oracleNode3, - Carol: consumer, - Eddy: stranger, - } - - const roles: Roles = { - defaultAccount, - oracleNode: oracleNode1, - oracleNode1, - oracleNode2, - oracleNode3, - stranger, - consumer, - } - - return { personas, roles } -} - -const bNToStringOrIdentity = (a: any): any => (BN.isBN(a) ? a.toString() : a) - -// Deal with transfer amount type truffle doesn't currently handle. (BN) -export const wrappedERC20 = (contract: any): any => ({ - ...contract, - transfer: async (address: any, amount: any) => - contract.transfer(address, bNToStringOrIdentity(amount)), - transferAndCall: async ( - address: any, - amount: any, - payload: any, - options: any, - ) => - contract.transferAndCall( - address, - bNToStringOrIdentity(amount), - payload, - options, - ), -}) - -export const linkContract = async (account: string): Promise => { - if (!account) { - throw Error('No account supplied as a parameter') - } - const receipt = await web3.eth.sendTransaction({ - data: LinkToken.bytecode, - from: account, - gas: 2000000, - }) - const contract = TruffleContract({ abi: LinkToken.abi }) - contract.setProvider(web3.currentProvider) - contract.defaults({ - from: account, - gas: 3500000, - gasPrice: 10000000000, - }) - - return wrappedERC20(await contract.at(receipt.contractAddress)) -} - -export const bigNum = (num: any) => web3.utils.toBN(num) -// TODO: dont call assertions on import -assertBigNum( - bigNum('1'), - bigNum(1), - 'Different representations should give same BNs', -) - -// toWei(n) is n * 10**18, as a BN. -export const toWei = (num: string | number): any => - bigNum(web3.utils.toWei(bigNum(num))) -// TODO: dont call assertions on import -assertBigNum( - toWei('1'), - toWei(1), - 'Different representations should give same BNs', -) - -export const toUtf8 = web3.utils.toUtf8 - -export const keccak = web3.utils.sha3 - -export const hexToInt = (str: string): any => bigNum(str).toNumber() - -export const toHexWithoutPrefix = (arg: any): string => { - if (arg instanceof Buffer || arg instanceof BN) { - return arg.toString('hex') - } else if (arg instanceof Uint8Array) { - return arg.reduce((a, v) => a + v.toString(16).padStart(2, '0'), '') - } else if (Number(arg) === arg) { - return arg.toString(16).padStart(64, '0') - } else { - return Buffer.from(arg, 'ascii').toString('hex') - } -} - -export const toHex = (value: any): string => { - return Ox(toHexWithoutPrefix(value)) -} - -export function Ox(value: any): string { - return value.slice(0, 2) !== '0x' ? `0x${value}` : value -} - -// True if h is a standard representation of a byte array, false otherwise -export const isByteRepresentation = (h: any): boolean => { - return h instanceof Buffer || h instanceof BN || h instanceof Uint8Array -} - -export const getEvents = (contract: any): Promise => - new Promise((resolve, reject) => - contract - .getPastEvents() - .then((events: any) => resolve(events)) - .catch((error: any) => reject(error)), - ) - -export const getLatestEvent = async (contract: any): Promise => { - const events = await getEvents(contract) - return events[events.length - 1] -} - -// link param must be from linkContract(), if amount is a BN -export const requestDataFrom = ( - oc: any, - link: any, - amount: any, - args: any, - options: any, -): any => { - if (!options) { - options = { value: 0 } - } - return link.transferAndCall(oc.address, amount, args, options) -} - -export const functionSelector = (signature: any): string => - '0x' + - keccak(signature) - .slice(2) - .slice(0, 8) - -export const assertActionThrows = (action: any) => - Promise.resolve() - .then(action) - .catch(error => { - assert(error, 'Expected an error to be raised') - assert(error.message, 'Expected an error to be raised') - return error.message - }) - .then(errorMessage => { - assert(errorMessage, 'Expected an error to be raised') - const invalidOpcode = errorMessage.includes('invalid opcode') - const reverted = errorMessage.includes( - 'VM Exception while processing transaction: revert', - ) - assert( - invalidOpcode || reverted, - 'expected following error message to include "invalid JUMP" or ' + - `"revert": "${errorMessage}"`, - ) - // see https://github.com/ethereumjs/testrpc/issues/39 - // for why the "invalid JUMP" is the throw related error when using TestRPC - }) - -export const checkPublicABI = (contract: any, expectedPublic: any) => { - const actualPublic = [] - // eslint-disable-next-line @typescript-eslint/no-unused-vars - for (const method of contract.abi) { - if (method.type === 'function') { - actualPublic.push(method.name) - } - } - - // eslint-disable-next-line @typescript-eslint/no-unused-vars - for (const method of actualPublic) { - const index = expectedPublic.indexOf(method) - assert.isAtLeast(index, 0, `#${method} is NOT expected to be public`) - } - - // eslint-disable-next-line @typescript-eslint/no-unused-vars - for (const method of expectedPublic) { - const index = actualPublic.indexOf(method) - assert.isAtLeast(index, 0, `#${method} is expected to be public`) - } -} - -export const decodeRunABI = (log: any): any => { - const runABI = util.toBuffer(log.data) - const types = ['bytes32', 'address', 'bytes4', 'bytes'] - return abi.rawDecode(types, runABI) -} - -const startMapBuffer = Buffer.from([0xbf]) -const endMapBuffer = Buffer.from([0xff]) - -export const decodeRunRequest = (log: any): any => { - const runABI = util.toBuffer(log.data) - const types = [ - 'address', - 'bytes32', - 'uint256', - 'address', - 'bytes4', - 'uint256', - 'uint256', - 'bytes', - ] - const [ - requester, - requestId, - payment, - callbackAddress, - callbackFunc, - expiration, - version, - data, - ] = abi.rawDecode(types, runABI) - - return { - callbackAddr: Ox(callbackAddress), - callbackFunc: toHex(callbackFunc), - data: autoAddMapDelimiters(data), - dataVersion: version, - expiration: toHex(expiration), - id: toHex(requestId), - jobId: log.topics[1], - payment: toHex(payment), - requester: Ox(requester), - topic: log.topics[0], - } -} - -function autoAddMapDelimiters(data: any): Buffer { - let buffer = data - - if (buffer[0] >> 5 !== 5) { - buffer = Buffer.concat( - [startMapBuffer, buffer, endMapBuffer], - buffer.length + 2, - ) - } - - return buffer -} - -export const decodeDietCBOR = (data: any): any => { - return cbor.decodeFirstSync(autoAddMapDelimiters(data)) -} - -export const runRequestId = (log: any): any => { - const { requestId } = decodeRunRequest(log) - return requestId -} - -export const requestDataBytes = ( - specId: any, - to: any, - fHash: any, - nonce: any, - data: any, -): any => { - const types = [ - 'address', - 'uint256', - 'bytes32', - 'address', - 'bytes4', - 'uint256', - 'uint256', - 'bytes', - ] - const values = [0, 0, specId, to, fHash, nonce, 1, data] - const encoded = abiEncode(types, values) - const funcSelector = functionSelector( - 'oracleRequest(address,uint256,bytes32,address,bytes4,uint256,uint256,bytes)', - ) - return funcSelector + encoded -} - -export function abiEncode(types: any, values: any): string { - return abi.rawEncode(types, values).toString('hex') -} - -export const newUint8ArrayFromStr = (str: string): Uint8Array => { - const codePoints = [...str].map(c => c.charCodeAt(0)) - return Uint8Array.from(codePoints) -} - -// newUint8Array returns a uint8array of count bytes from either a hex or -// decimal string, hex strings must begin with 0x -export const newUint8Array = (str: string, count: number): any => { - let result = new Uint8Array(count) - - if (str.startsWith('0x') || str.startsWith('0X')) { - const hexStr = str.slice(2).padStart(count * 2, '0') - for (let i = result.length; i >= 0; i--) { - const offset = i * 2 - result[i] = parseInt(hexStr[offset] + hexStr[offset + 1], HEX_BASE) - } - } else { - const num = bigNum(str) - result = newHash('0x' + num.toString(HEX_BASE)) - } - - return result -} - -// newSignature returns a signature object with v, r, and s broken up -export const newSignature = (str: string): any => { - const oracleSignature = newUint8Array(str, 65) - let v = oracleSignature[64] - if (v < 27) { - v += 27 - } - return { - full: oracleSignature, - r: oracleSignature.slice(0, 32), - s: oracleSignature.slice(32, 64), - v, - } -} - -// newHash returns a 65 byte Uint8Array for representing a hash -export function newHash(str: string): Uint8Array { - return newUint8Array(str, 32) -} - -// newAddress returns a 20 byte Uint8Array for representing an address -export const newAddress = (str: string): Uint8Array => { - return newUint8Array(str, 20) -} - -// lengthTypedArrays sums the length of all specified TypedArrays -export const lengthTypedArrays = ( - ...arrays: Array> -): number => { - return arrays.reduce((a, v) => a + v.length, 0) -} - -export const toBuffer = (uint8a: Uint8Array): Buffer => { - return Buffer.from(uint8a) -} - -// concatTypedArrays recursively concatenates TypedArrays into one big -// TypedArray -// TODO: Does not work recursively -export const concatTypedArrays = ( - ...arrays: Array> -): ArrayLike => { - const size = lengthTypedArrays(...arrays) - const arrayCtor: any = arrays[0].constructor - const result = new arrayCtor(size) - let offset = 0 - arrays.forEach(a => { - result.set(a, offset) - offset += a.length - }) - return result -} - -export const increaseTime5Minutes = async () => { - await web3.currentProvider.send( - { - id: 0, - jsonrpc: '2.0', - method: 'evm_increaseTime', - params: [300], - }, - (error: any) => { - if (error) { - console.log(`Error during helpers.increaseTime5Minutes! ${error}`) - throw error - } - }, - ) -} - -export const sendToEvm = async (evmMethod: string, ...params: any) => { - await web3.currentProvider.send( - { - id: 0, - jsonrpc: '2.0', - method: evmMethod, - params: [...params], - }, - (error: any) => { - if (error) { - console.log(`Error during ${evmMethod}! ${error}`) - throw error - } - }, - ) -} - -export const mineBlocks = async (blocks: number) => { - for (let i = 0; i < blocks; i++) { - await sendToEvm('evm_mine') - } -} - -export const createTxData = ( - selector: string, - types: any, - values: any, -): any => { - const funcSelector = functionSelector(selector) - const encoded = abiEncode([...types], [...values]) - return funcSelector + encoded -} - -export const generateSAID = ({ - payment, - expiration, - endAt, - oracles, - requestDigest, -}: any): Uint8Array => { - const serviceAgreementIDInput = concatTypedArrays( - newHash(payment.toString()), - newHash(expiration.toString()), - newHash(endAt.toString()), - concatTypedArrays( - ...oracles - .map(newAddress) - .map(toHex) - .map(newHash), - ), - requestDigest, - ) - const serviceAgreementIDInputDigest = util.keccak( - toHex(serviceAgreementIDInput), - ) - return newHash(toHex(serviceAgreementIDInputDigest)) -} - -export const recoverPersonalSignature = ( - message: Uint8Array, - signature: any, -): any => { - const personalSignPrefix = newUint8ArrayFromStr( - '\x19Ethereum Signed Message:\n', - ) - const personalSignMessage = Uint8Array.from( - concatTypedArrays( - personalSignPrefix, - newUint8ArrayFromStr(message.length.toString()), - message, - ), - ) - const digest = util.keccak(toBuffer(personalSignMessage)) - const requestDigestPubKey = util.ecrecover( - digest, - signature.v, - toBuffer(signature.r), - toBuffer(signature.s), - ) - return util.pubToAddress(requestDigestPubKey) -} - -export const personalSign = async ( - account: any, - message: any, -): Promise => { - if (!isByteRepresentation(message)) { - throw new Error(`Message ${message} is not a recognized representation of a byte array. - (Can be Buffer, BigNumber, Uint8Array, 0x-prepended hexadecimal string.)`) - } - return newSignature(await web3.eth.sign(toHex(message), account)) -} - -export const executeServiceAgreementBytes = ( - sAID: any, - to: any, - fHash: any, - nonce: any, - data: any, -): any => { - const types = [ - 'address', - 'uint256', - 'bytes32', - 'address', - 'bytes4', - 'uint256', - 'uint256', - 'bytes', - ] - const values = [0, 0, sAID, to, fHash, nonce, 1, data] - const encoded = abiEncode(types, values) - const funcSelector = functionSelector( - 'oracleRequest(address,uint256,bytes32,address,bytes4,uint256,uint256,bytes)', - ) - return funcSelector + encoded -} - -// Convenience functions for constructing hexadecimal representations of -// binary serializations. -export const strip0x = (s: string): string => - s.startsWith('0x') ? s.slice(2) : s -export const padHexTo256Bit = (s: string): string => - strip0x(s).padStart(64, '0') -export const padNumTo256Bit = (n: number): string => - padHexTo256Bit(n.toString(16)) - -export const constructStructArgs = ( - fieldNames: string[], - values: any[], -): any => { - assert.equal(fieldNames.length, values.length) - const args: Record = {} - for (let i = 0; i < fieldNames.length; i++) { - args[i] = values[i] - args[fieldNames[i]] = values[i] - } - return args -} - -export const initiateServiceAgreementArgs = ({ - payment, - expiration, - endAt, - oracles, - oracleSignatures, - requestDigest, -}: any): any[] => { - return [ - constructStructArgs( - ['payment', 'expiration', 'endAt', 'oracles', 'requestDigest'], - [ - toHex(newHash(payment.toString())), - toHex(newHash(expiration.toString())), - toHex(newHash(endAt.toString())), - oracles.map(newAddress).map(toHex), - toHex(requestDigest), - ], - ), - constructStructArgs( - ['vs', 'rs', 'ss'], - [ - oracleSignatures.map((os: any) => os.v), - oracleSignatures.map((os: any) => toHex(os.r)), - oracleSignatures.map((os: any) => toHex(os.s)), - ], - ), - ] -} - -// Call coordinator contract to initiate the specified service agreement, and -// get the return value -export const initiateServiceAgreementCall = async ( - coordinator: any, - args: any, -): Promise => - coordinator.initiateServiceAgreement.call( - ...initiateServiceAgreementArgs(args), - ) - -/** Call coordinator contract to initiate the specified service agreement. */ -export const initiateServiceAgreement = async ( - coordinator: any, - args: any, -): Promise => - coordinator.initiateServiceAgreement(...initiateServiceAgreementArgs(args)) - -/** Check that the given service agreement was stored at the correct location */ -export const checkServiceAgreementPresent = async ( - coordinator: any, - { payment, expiration, endAt, requestDigest, id }: any, -): Promise => { - const sa = await coordinator.serviceAgreements.call(id) - assertBigNum(sa[0], bigNum(payment)) - assertBigNum(sa[1], bigNum(expiration)) - assertBigNum(sa[2], bigNum(endAt)) - assert.equal(sa[3], toHex(requestDigest)) - - /// / TODO: - - /// / Web3.js doesn't support generating an artifact for arrays - /// within a struct. / This means that we aren't returned the - /// list of oracles and / can't assert on their values. - /// / - - /// / However, we can pass them into the function to generate the - /// ID / & solidity won't compile unless we pass the correct - /// number and / type of params when initializing the - /// ServiceAgreement struct, / so we have some indirect test - /// coverage. - /// / - /// / https://github.com/ethereum/web3.js/issues/1241 - /// / assert.equal( - /// / sa[2], - /// / ['0x70AEc4B9CFFA7b55C0711b82DD719049d615E21d', - /// / '0xd26114cd6EE289AccF82350c8d8487fedB8A0C07'] - /// / ) -} - -// Check that all values for the struct at this SAID have default values. I.e. -// nothing was changed due to invalid request -export const checkServiceAgreementAbsent = async ( - coordinator: any, - serviceAgreementID: any, -) => { - const sa = await coordinator.serviceAgreements.call( - toHex(serviceAgreementID).slice(0, 66), - ) - assertBigNum(sa[0], bigNum(0)) - assertBigNum(sa[1], bigNum(0)) - assertBigNum(sa[2], bigNum(0)) - assert.equal( - sa[3], - '0x0000000000000000000000000000000000000000000000000000000000000000', - ) -} - -export const newServiceAgreement = async (params: any): Promise => { - const agreement: any = {} - params = params || {} - agreement.payment = params.payment || '1000000000000000000' - agreement.expiration = params.expiration || 300 - agreement.endAt = params.endAt || sixMonthsFromNow() - if (!params.oracles) { - throw Error('No Oracle node address provided') - } - agreement.oracles = params.oracles - agreement.oracleSignatures = [] - agreement.requestDigest = - params.requestDigest || - newHash( - '0xbadc0de5badc0de5badc0de5badc0de5badc0de5badc0de5badc0de5badc0de5', - ) - - const sAID = generateSAID(agreement) - agreement.id = toHex(sAID) - - for (let i = 0; i < agreement.oracles.length; i++) { - const oracle = agreement.oracles[i] - const oracleSignature = await personalSign(oracle, sAID) - const requestDigestAddr = recoverPersonalSignature(sAID, oracleSignature) - assert.equal(oracle.toLowerCase(), toHex(requestDigestAddr)) - agreement.oracleSignatures[i] = oracleSignature - } - return agreement -} - -export function sixMonthsFromNow(): number { - return Math.round(Date.now() / 1000.0) + 6 * 30 * 24 * 60 * 60 -} - -export const fulfillOracleRequest = async ( - oracle: any, - request: any, - response: any, - options: any, -): Promise => { - if (!options) { - options = { value: 0 } - } - - return oracle.fulfillOracleRequest( - request.id, - request.payment, - request.callbackAddr, - request.callbackFunc, - request.expiration, - toHex(response), - options, - ) -} - -export const cancelOracleRequest = async ( - oracle: any, - request: any, - options: any, -): Promise => { - if (!options) { - options = { value: 0 } - } - - return oracle.cancelOracleRequest( - request.id, - request.payment, - request.callbackFunc, - request.expiration, - options, - ) -} diff --git a/evm/src/matchers.ts b/evm/src/matchers.ts deleted file mode 100644 index 8ff1ffde603..00000000000 --- a/evm/src/matchers.ts +++ /dev/null @@ -1,13 +0,0 @@ -import web3 from 'web3' -import { assert } from 'chai' -import BigNumber from 'bn.js' -import BN from 'bn.js' - -const bigNum = (num: number | BN): BigNumber => web3.utils.toBN(num) - -// Throws if a and b are not equal, as BN's -export const assertBigNum = (a: BN, b: BN, failureMessage?: string) => - assert( - bigNum(a).eq(bigNum(b)), - `BigNum ${a} is not ${b}` + (failureMessage ? ': ' + failureMessage : ''), - ) diff --git a/evm/tsconfig.json b/evm/tsconfig.json index 32ff54d4a02..76e5591d014 100644 --- a/evm/tsconfig.json +++ b/evm/tsconfig.json @@ -16,5 +16,5 @@ "moduleResolution": "node", "esModuleInterop": true }, - "include": ["src", "@types", "test", "testv2"] + "include": ["src", "testv2"] } diff --git a/yarn.lock b/yarn.lock index 3af38942023..f43d7ad9777 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2589,7 +2589,7 @@ "@types/chai@^4.2.4": version "4.2.4" - resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.2.4.tgz#8936cffad3c96ec470a2dc26a38c3ba8b9b6f619" + resolved "https://registry.npmjs.org/@types/chai/-/chai-4.2.4.tgz#8936cffad3c96ec470a2dc26a38c3ba8b9b6f619" integrity sha512-7qvf9F9tMTzo0akeswHPGqgUx/gIaJqrOEET/FCD8CFRkSUHlygQiM5yB6OvjrtdxBVLSyw7COJubsFYs0683g== "@types/cheerio@*": @@ -10713,7 +10713,7 @@ ganache-cli@^6.1.0: source-map-support "0.5.9" yargs "11.1.0" -ganache-core@*, ganache-core@^2.6.0, ganache-core@^2.8.0: +ganache-core@*, ganache-core@^2.6.0: version "2.8.0" resolved "https://registry.npmjs.org/ganache-core/-/ganache-core-2.8.0.tgz#eeadc7f7fc3a0c20d99f8f62021fb80b5a05490c" integrity sha512-hfXqZGJx700jJqwDHNXrU2BnPYuETn1ekm36oRHuXY3oOuJLFs5C+cFdUFaBlgUxcau1dOgZUUwKqTAy0gTA9Q== From 8af9a739a3a0c251552b1d66a2df00e04780e66c Mon Sep 17 00:00:00 2001 From: henrynguyen5 <6404866+HenryNguyen5@users.noreply.github.com> Date: Sun, 3 Nov 2019 17:34:40 -0500 Subject: [PATCH 103/199] Fix comment for type helper --- evm/src/contract.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/evm/src/contract.ts b/evm/src/contract.ts index a90c96f0495..6f0718457dd 100644 --- a/evm/src/contract.ts +++ b/evm/src/contract.ts @@ -1,5 +1,5 @@ /** - * The type of any function + * The type of any function that is deployable */ type Deployable = { deploy: (...deployArgs: any[]) => Promise From b83d84d44f4be654a68b95bc11c6dfb68f413844 Mon Sep 17 00:00:00 2001 From: henrynguyen5 <6404866+HenryNguyen5@users.noreply.github.com> Date: Sun, 3 Nov 2019 17:52:14 -0500 Subject: [PATCH 104/199] Add chainlinkv0.5 build dependency for test --- tools/ci/truffle_test | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tools/ci/truffle_test b/tools/ci/truffle_test index a42d0f4ae9c..5c00ec22195 100755 --- a/tools/ci/truffle_test +++ b/tools/ci/truffle_test @@ -8,6 +8,8 @@ yarn workspaces run lint yarn workspace chainlink run slither yarn workspace chainlinkv0.5 run slither +yarn workspace chainlinkv0.5 build + # These should be merged into a global test command yarn workspace chainlink test yarn workspace chainlinkv0.5 test From a4f0d9e2e46bcd991e9af46f689f52f090bf82c7 Mon Sep 17 00:00:00 2001 From: henrynguyen5 <6404866+HenryNguyen5@users.noreply.github.com> Date: Mon, 4 Nov 2019 14:08:05 -0500 Subject: [PATCH 105/199] Cleanup export-generated-contract-factories script --- .../export-generated-contract-factories.ts | 32 +++++++++++-------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/evm/scripts/export-generated-contract-factories.ts b/evm/scripts/export-generated-contract-factories.ts index 1b542662f54..a4e4069ac5b 100644 --- a/evm/scripts/export-generated-contract-factories.ts +++ b/evm/scripts/export-generated-contract-factories.ts @@ -1,9 +1,9 @@ -import { lstatSync, readdirSync, writeFileSync, readFileSync } from 'fs' import { resolve, join, parse } from 'path' import chalk from 'chalk' -import { cp, rm } from 'shelljs' -import { mkdir } from 'shelljs' +import { cp, rm, ls, test, mkdir, cat } from 'shelljs' +import { writeFileSync } from 'fs' +// when this is non-empty, no files will be written const DRY_RUN = process.env.DRY_RUN // logging functions with colour output @@ -17,18 +17,19 @@ function main() { resolve(p), ) - console.log(process.cwd()) exportGeneratedContractFactories(generatedPath, distPath) } main() /** * Export all generated contract factories and their associated types + * @param generatedPath The path of the generated files + * @param distPath The path of the post-tsc generated files */ export function exportGeneratedContractFactories( generatedPath: string, distPath: string, -) { +): void { const dir = getGeneratedFilePaths(generatedPath) const exportPaths = dir .map(makeExportPath) @@ -49,7 +50,7 @@ export function exportGeneratedContractFactories( * @param generatedPath The path of the generated files * @param distPath The path of the post-tsc generated files */ -function copyTypings(generatedPath: string, distPath: string) { +function copyTypings(generatedPath: string, distPath: string): void { mkdir('-p', distPath) cp(`${generatedPath}/*.d.ts`, distPath) } @@ -57,9 +58,10 @@ function copyTypings(generatedPath: string, distPath: string) { /** * Create a barrel file which contains all of the exports. * This will replace the existing barrel file if it already exists. + * @path the path to create the barrel file * @param data The data to write to the barrel file */ -function makeBarrelFile(path: string, data: string) { +function makeBarrelFile(path: string, data: string): void { const exportFilePath = join(path, 'index.ts') warn(`Writing barrel file to ${exportFilePath}`) writeFileSync(exportFilePath, data) @@ -74,11 +76,11 @@ function makeBarrelFile(path: string, data: string) { * .d.fs one. * @param path The path of the generated files */ -function mergeIndexes(path: string) { +function mergeIndexes(path: string): void { const exportFilePath = join(path, 'index.ts') const declarationsFilePath = join(path, 'index.d.ts') - const declarationFile = readFileSync(declarationsFilePath) - const exportsFile = readFileSync(exportFilePath) + const declarationFile = cat(declarationsFilePath) + const exportsFile = cat(exportFilePath) writeFileSync(exportFilePath, [declarationFile, exportsFile].join('\n')) rm(declarationsFilePath) @@ -86,15 +88,17 @@ function mergeIndexes(path: string) { /** * Check if the generated directory for smart contract factories exists + * @param path The path of the generated files */ function generatedDirExists(path: string): boolean { log(`Checking if directory: ${path} exists...`) - const exists = lstatSync(path).isDirectory() - return exists + + return test('-d', path) } /** * Get all the generated file paths from the generated directory + * @param path The path of the generated files */ function getGeneratedFilePaths(path: string): string[] { if (!generatedDirExists(path)) { @@ -102,14 +106,14 @@ function getGeneratedFilePaths(path: string): string[] { process.exit(1) } log(`Directory ${path} exists, continuing...`) - return readdirSync(path) + return ls(path) } /** * Create an es6 export of a filename, handles interface and index conflicts naively * @param fileName The filename to export */ -function makeExportPath(fileName: string) { +function makeExportPath(fileName: string): string { const { name } = parse(fileName) if (name.endsWith('.d')) { From 7f7d71c633f081ec2d4762d29a3a1f748fa72fff Mon Sep 17 00:00:00 2001 From: henrynguyen5 <6404866+HenryNguyen5@users.noreply.github.com> Date: Mon, 4 Nov 2019 15:39:40 -0500 Subject: [PATCH 106/199] Update appveyor.yml --- tools/ci/appveyor.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tools/ci/appveyor.yml b/tools/ci/appveyor.yml index a4392c1963b..cb92cf78c0d 100644 --- a/tools/ci/appveyor.yml +++ b/tools/ci/appveyor.yml @@ -26,7 +26,8 @@ install: - ps: Install-Product node $env:nodejs_version # Generate solidity contract artifacts - yarn install - - yarn workspace chainlink run setup + - yarn workspace chainlinkv0.5 setup + - yarn workspace chainlink setup # Install the specific Go version. - rmdir c:\go /s /q From 282b73fb70565f02e60a322a19da7431f1cf8b6b Mon Sep 17 00:00:00 2001 From: henrynguyen5 <6404866+HenryNguyen5@users.noreply.github.com> Date: Tue, 5 Nov 2019 17:40:07 -0500 Subject: [PATCH 107/199] Use truffle compile for Windows --- evm/v0.5/package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/evm/v0.5/package.json b/evm/v0.5/package.json index 7f17132cba0..51c82758d7f 100644 --- a/evm/v0.5/package.json +++ b/evm/v0.5/package.json @@ -7,6 +7,7 @@ "scripts": { "generate-typings": "typechain --target ethers --outDir src/generated \"{test/support/LinkToken*,dist/artifacts/*}\"", "postgenerate-typings": "yarn workspace chainlink export-typings v0.5/src/generated v0.5/dist/src/generated", + "build:windows": "truffle.cmd build && yarn generate-typings && (tsc || true)", "build": "sol-compiler && yarn generate-typings && (tsc || true)", "depcheck": "echo \"chainlinkv0.5\" && depcheck --ignore-dirs=build/contracts || true", "eslint": "eslint --ext .ts,.js test", From 1eac65c96f2d51a994f1bcf951800e175dafd920 Mon Sep 17 00:00:00 2001 From: henrynguyen5 <6404866+HenryNguyen5@users.noreply.github.com> Date: Tue, 5 Nov 2019 20:59:10 -0500 Subject: [PATCH 108/199] Configure truffle output directory --- core/store/tx_manager.go | 2 +- evm/package.json | 2 +- evm/scripts/build.ts | 24 +++++++++++++++++++++++- evm/slither.config.json | 1 + evm/truffle.js | 1 + evm/v0.5/package.json | 2 +- evm/v0.5/slither.config.json | 1 + yarn.lock | 2 +- 8 files changed, 30 insertions(+), 5 deletions(-) diff --git a/core/store/tx_manager.go b/core/store/tx_manager.go index 74967468d3d..fc97c4f78f5 100644 --- a/core/store/tx_manager.go +++ b/core/store/tx_manager.go @@ -850,7 +850,7 @@ func GetContract(name string) (*Contract, error) { return nil, errors.Wrap(err, "unable to read contract JSON") } - abiBytes := gjson.GetBytes(jsonFile, "abi.compilerOutput") + abiBytes := gjson.GetBytes(jsonFile, "compilerOutput.abi") abiParsed, err := abi.JSON(strings.NewReader(abiBytes.Raw)) if err != nil { return nil, err diff --git a/evm/package.json b/evm/package.json index 3834dcfacdd..46f4693302e 100644 --- a/evm/package.json +++ b/evm/package.json @@ -12,7 +12,7 @@ "eslint": "eslint --ext .ts, testv2 src", "solhint": "solhint ./contracts/**/*.sol", "lint": "yarn solhint && yarn eslint", - "slither": "truffle compile --quiet && slither .", + "slither": "truffle compile --quiet && slither . && rimraf dist/artifacts", "pretest": "yarn build", "test": "jest --testTimeout 80000", "format": "prettier --write \"{src,testv2}/*/**\"", diff --git a/evm/scripts/build.ts b/evm/scripts/build.ts index cc253887ce1..2680fbbca8e 100755 --- a/evm/scripts/build.ts +++ b/evm/scripts/build.ts @@ -1,9 +1,31 @@ #!/usr/bin/env node import execa from 'execa' +import { ls } from 'shelljs' +import { writeFileSync } from 'fs' +import { join, resolve } from 'path' -const task = /^win/i.test(process.platform) ? 'build:windows' : 'build' +const isWindows = /^win/i.test(process.platform) +const task = isWindows ? 'build:windows' : 'build' execa.commandSync(`yarn -s workspace chainlink run ${task}`, { stdio: 'inherit', }) +if (isWindows) { + remapAbi() +} +/** + * Temporary interop for truffle compile on Windows + * to flatten the compiler output into the JSON file + */ +function remapAbi() { + const artifacts = join('dist', 'artifacts') + const jsons = ls(artifacts) + jsons.forEach(j => { + const jsonPath = resolve(join(artifacts, j)) + const json = require(jsonPath) + const { abi } = json + const newJson = { compilerOutput: { abi }, ...json } + writeFileSync(jsonPath, JSON.stringify(newJson)) + }) +} diff --git a/evm/slither.config.json b/evm/slither.config.json index a12e70741ed..d3a75e57930 100644 --- a/evm/slither.config.json +++ b/evm/slither.config.json @@ -1,5 +1,6 @@ { "detectors_to_exclude": "naming-convention,pragma,solc-version", + "truffle_build_directory": "dist/artifacts", "filter_paths": "tests,vendor", "truffle_ignore_compile": true, "truffle_version": "truffle" diff --git a/evm/truffle.js b/evm/truffle.js index 4aa236aad09..d138e780a9f 100644 --- a/evm/truffle.js +++ b/evm/truffle.js @@ -1,4 +1,5 @@ module.exports = { + contracts_build_directory: 'dist/artifacts', compilers: { solc: { version: '0.4.24', diff --git a/evm/v0.5/package.json b/evm/v0.5/package.json index 51c82758d7f..0bde0527639 100644 --- a/evm/v0.5/package.json +++ b/evm/v0.5/package.json @@ -14,7 +14,7 @@ "solhint": "solhint ./contracts/**/*.sol", "lint": "yarn eslint && yarn solhint", "format": "prettier --write \"{src,test}/**/*\"", - "slither": "truffle compile --quiet && slither .", + "slither": "truffle compile --quiet && slither . && rimraf dist/artifacts", "setup": "ts-node ./scripts/build", "prepublishOnly": "yarn setup && yarn lint && yarn test", "test": "truffle test" diff --git a/evm/v0.5/slither.config.json b/evm/v0.5/slither.config.json index e24750c1d07..3cc46fdd845 100644 --- a/evm/v0.5/slither.config.json +++ b/evm/v0.5/slither.config.json @@ -1,5 +1,6 @@ { "detectors_to_exclude": "naming-convention,pragma,solc-version", + "truffle_build_directory": "dist/artifacts", "filter_paths": "dev,tests,vendor", "truffle_ignore_compile": true, "truffle_version": "truffle" diff --git a/yarn.lock b/yarn.lock index f43d7ad9777..c11dfe1f82b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -21837,7 +21837,7 @@ typescript@^3.6.3: resolved "https://registry.npmjs.org/typescript/-/typescript-3.6.3.tgz#fea942fabb20f7e1ca7164ff626f1a9f3f70b4da" integrity sha512-N7bceJL1CtRQ2RiG0AQME13ksR7DiuQh/QehubYcghzv20tnh+MQnQIuJddTmsbqYj+dztchykemz0zFzlvdQw== -typescript@^3.7.0: +typescript@^3.7.0, typescript@^3.7.0-beta: version "3.7.2" resolved "https://registry.npmjs.org/typescript/-/typescript-3.7.2.tgz#27e489b95fa5909445e9fef5ee48d81697ad18fb" integrity sha512-ml7V7JfiN2Xwvcer+XAf2csGO1bPBdRbFCkYBczNZggrBZ9c7G3riSUeJmqEU5uOtXNPMhE3n+R4FA/3YOAWOQ== From 1a65fe7b3fecb85a811453a200d6ba50dd2b1997 Mon Sep 17 00:00:00 2001 From: John Barker Date: Wed, 6 Nov 2019 11:36:44 -0700 Subject: [PATCH 109/199] Use consistent label for eth and link balances --- core/cmd/local_client.go | 8 ++------ core/store/presenters/presenters.go | 14 +++++++------- 2 files changed, 9 insertions(+), 13 deletions(-) diff --git a/core/cmd/local_client.go b/core/cmd/local_client.go index 81a4c30b4b1..b82360a67b3 100644 --- a/core/cmd/local_client.go +++ b/core/cmd/local_client.go @@ -117,20 +117,16 @@ func logNodeBalance(store *strpkg.Store) { accounts, err := presenters.ShowEthBalance(store) logger.WarnIf(err) for _, a := range accounts { - logAccountBalance(a) + logger.Infow(a["message"], "address", a["address"], "ethBalance", a["balance"]) } accounts, err = presenters.ShowLinkBalance(store) logger.WarnIf(err) for _, a := range accounts { - logAccountBalance(a) + logger.Infow(a["message"], "address", a["address"], "linkBalance", a["balance"]) } } -func logAccountBalance(kv map[string]interface{}) { - logger.Infow(fmt.Sprint(kv["message"]), "address", kv["address"], "balance", kv["balance"]) -} - func logConfigVariables(store *strpkg.Store) error { wlc, err := presenters.NewConfigWhitelist(store) if err != nil { diff --git a/core/store/presenters/presenters.go b/core/store/presenters/presenters.go index 29fb24dbf11..a33e7d518e6 100644 --- a/core/store/presenters/presenters.go +++ b/core/store/presenters/presenters.go @@ -35,22 +35,22 @@ const ( ) // ShowEthBalance returns the current Eth Balance for current Account -func ShowEthBalance(store *store.Store) ([]map[string]interface{}, error) { +func ShowEthBalance(store *store.Store) ([]map[string]string, error) { return showBalanceFor(store, ethRequest) } // ShowLinkBalance returns the current Link Balance for current Account -func ShowLinkBalance(store *store.Store) ([]map[string]interface{}, error) { +func ShowLinkBalance(store *store.Store) ([]map[string]string, error) { return showBalanceFor(store, linkRequest) } -func showBalanceFor(store *store.Store, balanceType requestType) ([]map[string]interface{}, error) { +func showBalanceFor(store *store.Store, balanceType requestType) ([]map[string]string, error) { if !store.KeyStore.HasAccounts() { logger.Panic("KeyStore must have an account in order to show balance") } var merr error - info := []map[string]interface{}{} + info := []map[string]string{} for _, account := range store.KeyStore.Accounts() { b, err := showBalanceForAccount(store, account, balanceType) merr = multierr.Append(merr, err) @@ -62,16 +62,16 @@ func showBalanceFor(store *store.Store, balanceType requestType) ([]map[string]i } // ShowEthBalance returns the current Eth Balance for current Account -func showBalanceForAccount(store *store.Store, account accounts.Account, balanceType requestType) (map[string]interface{}, error) { +func showBalanceForAccount(store *store.Store, account accounts.Account, balanceType requestType) (map[string]string, error) { balance, err := getBalance(store, account, balanceType) if err != nil { return nil, err } address := account.Address - keysAndValues := make(map[string]interface{}) + keysAndValues := make(map[string]string) keysAndValues["message"] = fmt.Sprintf("%v Balance for %v: %v", balance.Symbol(), address.Hex(), balance.String()) keysAndValues["balance"] = balance.String() - keysAndValues["address"] = address + keysAndValues["address"] = address.String() if balance.IsZero() && balanceType == ethRequest { return nil, errors.New("0 ETH Balance. Chainlink node not fully functional, please deposit ETH into your address: " + address.Hex()) } From 9439ed48a4db45d2ed89078d7c694bebcb25ea92 Mon Sep 17 00:00:00 2001 From: John Barker Date: Fri, 1 Nov 2019 16:01:35 +0100 Subject: [PATCH 110/199] Change TestMigrate_Migrations to go to the final table --- core/store/migrations/migrate_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/store/migrations/migrate_test.go b/core/store/migrations/migrate_test.go index 3649347cfc9..650daa58423 100644 --- a/core/store/migrations/migrate_test.go +++ b/core/store/migrations/migrate_test.go @@ -45,7 +45,7 @@ func TestMigrate_Migrations(t *testing.T) { db := orm.DB - require.NoError(t, migrations.MigrateTo(db, "1559081901")) + require.NoError(t, migrations.Migrate(db)) assert.True(t, db.HasTable("bridge_types")) assert.True(t, db.HasTable("encumbrances")) From fa4c779e5afa2b5c47d173906f644a0bb9586c22 Mon Sep 17 00:00:00 2001 From: John Barker Date: Wed, 6 Nov 2019 15:24:12 -0700 Subject: [PATCH 111/199] Don't stop an unstarted app in LocalClient --- core/cmd/local_client.go | 1 - 1 file changed, 1 deletion(-) diff --git a/core/cmd/local_client.go b/core/cmd/local_client.go index b82360a67b3..bbcae9ed457 100644 --- a/core/cmd/local_client.go +++ b/core/cmd/local_client.go @@ -154,7 +154,6 @@ func (cli *Client) DeleteUser(c *clipkg.Context) error { func (cli *Client) ImportKey(c *clipkg.Context) error { logger.SetLogger(cli.Config.CreateProductionLogger()) app := cli.AppFactory.NewApplication(cli.Config) - defer app.Stop() if !c.Args().Present() { return cli.errorOut(errors.New("Must pass in filepath to key")) From d4d0066b6a4c08449a5bd79d7d92f65a4dfb352c Mon Sep 17 00:00:00 2001 From: Alex Kwiatkowski Date: Wed, 6 Nov 2019 15:04:58 -0800 Subject: [PATCH 112/199] Copy json-api-client package.json in Dockerfile [Fixes #169612542] --- Dockerfile | 1 + 1 file changed, 1 insertion(+) diff --git a/Dockerfile b/Dockerfile index b9d2f0a9420..1a03fd81a2c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -23,6 +23,7 @@ COPY operator_ui/package.json ./operator_ui/ COPY styleguide/package.json ./styleguide/ COPY tools/prettier-config/package.json ./tools/prettier-config/ COPY tools/eslint-config/package.json ./tools/eslint-config/ +COPY tools/json-api-client/package.json ./tools/json-api-client/ RUN make yarndep # Install chainlink From 39493dfd47768eea280beaf6295ec6b662c08c3b Mon Sep 17 00:00:00 2001 From: John Barker Date: Wed, 6 Nov 2019 14:18:20 -0700 Subject: [PATCH 113/199] Add UI for cancellation of jobs --- core/cmd/app.go | 9 +++++++-- core/cmd/client.go | 6 ++++++ core/cmd/remote_client.go | 17 ++++++++++++++++ core/cmd/remote_client_test.go | 31 ++++++++++++++++++++++++++++++ core/internal/mocks/application.go | 19 +++++++++++++----- core/internal/mocks/run_manager.go | 19 +++++++++++++----- core/services/run_executor.go | 1 + core/services/run_manager.go | 29 ++++++++++++---------------- core/store/models/common.go | 7 ++++++- core/store/models/job_run.go | 14 +++++++++++--- core/store/orm/orm.go | 2 +- core/store/orm/orm_test.go | 9 +++++++++ core/web/job_runs_controller.go | 16 +++++++++++++++ core/web/router.go | 1 + 14 files changed, 146 insertions(+), 34 deletions(-) diff --git a/core/cmd/app.go b/core/cmd/app.go index e0f3337b40c..2af66e9d662 100644 --- a/core/cmd/app.go +++ b/core/cmd/app.go @@ -104,8 +104,8 @@ func NewApp(client *Client) *cli.App { Usage: "Commands for the node's configuration", Subcommands: []cli.Command{ { - Name: "list", - Usage: "Show the node's environment variables", + Name: "list", + Usage: "Show the node's environment variables", Action: client.GetConfiguration, }, { @@ -233,6 +233,11 @@ func NewApp(client *Client) *cli.App { Usage: "Show a Run for a specific ID", Action: client.ShowJobRun, }, + { + Name: "cancel", + Usage: "Cancel a Run with a specified ID", + Action: client.CancelJobRun, + }, }, }, diff --git a/core/cmd/client.go b/core/cmd/client.go index 4ef157dc029..9ab13e8655e 100644 --- a/core/cmd/client.go +++ b/core/cmd/client.go @@ -137,6 +137,7 @@ func createServer(handler *gin.Engine, port uint16) *http.Server { type HTTPClient interface { Get(string, ...map[string]string) (*http.Response, error) Post(string, io.Reader) (*http.Response, error) + Put(string, io.Reader) (*http.Response, error) Patch(string, io.Reader, ...map[string]string) (*http.Response, error) Delete(string) (*http.Response, error) } @@ -167,6 +168,11 @@ func (h *authenticatedHTTPClient) Post(path string, body io.Reader) (*http.Respo return h.doRequest("POST", path, body) } +// Put performs an HTTP Put using the authenticated HTTP client's cookie. +func (h *authenticatedHTTPClient) Put(path string, body io.Reader) (*http.Response, error) { + return h.doRequest("PUT", path, body) +} + // Patch performs an HTTP Patch using the authenticated HTTP client's cookie. func (h *authenticatedHTTPClient) Patch(path string, body io.Reader, headers ...map[string]string) (*http.Response, error) { return h.doRequest("PATCH", path, body, headers...) diff --git a/core/cmd/remote_client.go b/core/cmd/remote_client.go index 0bc8f7f4bf3..755068ac8bf 100644 --- a/core/cmd/remote_client.go +++ b/core/cmd/remote_client.go @@ -630,3 +630,20 @@ func (cli *Client) GetConfiguration(c *clipkg.Context) error { cwl := presenters.ConfigWhitelist{} return cli.renderAPIResponse(resp, &cwl) } + +// CancelJob cancels a running job +func (cli *Client) CancelJobRun(c *clipkg.Context) error { + if !c.Args().Present() { + return cli.errorOut(errors.New("Must pass the run id to be cancelled")) + } + + response, err := cli.HTTP.Put(fmt.Sprintf("/v2/runs/%s/cancellation", c.Args().First()), nil) + if err != nil { + return cli.errorOut(errors.Wrap(err, "HTTP.Put")) + } + _, err = cli.parseResponse(response) + if err != nil { + return cli.errorOut(errors.Wrap(err, "cli.parseResponse")) + } + return nil +} diff --git a/core/cmd/remote_client_test.go b/core/cmd/remote_client_test.go index 63fe40e421a..75ad3b18d59 100644 --- a/core/cmd/remote_client_test.go +++ b/core/cmd/remote_client_test.go @@ -879,3 +879,34 @@ func TestClient_GetConfiguration(t *testing.T) { assert.Equal(t, cwl.Whitelist.RootDir, app.Config.RootDir()) assert.Equal(t, cwl.Whitelist.SessionTimeout, app.Config.SessionTimeout()) } + +func TestClient_CancelJobRun(t *testing.T) { + t.Parallel() + + app, cleanup := cltest.NewApplication(t) + defer cleanup() + ethMock := app.MockEthCallerSubscriber() + ethMock.Context("app.Start()", func(ethMock *cltest.EthMock) { + ethMock.Register("eth_getTransactionCount", 0) + ethMock.Register("eth_chainId", app.Config.ChainID()) + }) + require.NoError(t, app.Start()) + + job := cltest.NewJobWithWebInitiator() + require.NoError(t, app.Store.CreateJob(&job)) + run := job.NewRun(job.Initiators[0]) + require.NoError(t, app.Store.CreateJobRun(&run)) + + client, _ := app.NewClientAndRenderer() + + set := flag.NewFlagSet("cancel", 0) + set.Parse([]string{run.ID.String()}) + c := cli.NewContext(nil, set, nil) + + require.NoError(t, client.CancelJobRun(c)) + + runs := cltest.MustAllJobsWithStatus(t, app.Store, models.RunStatusCancelled) + require.Len(t, runs, 1) + assert.Equal(t, models.RunStatusCancelled, runs[0].Status) + assert.NotNil(t, runs[0].FinishedAt) +} diff --git a/core/internal/mocks/application.go b/core/internal/mocks/application.go index 66eece42137..b3990ce5880 100644 --- a/core/internal/mocks/application.go +++ b/core/internal/mocks/application.go @@ -57,17 +57,26 @@ func (_m *Application) ArchiveJob(_a0 *models.ID) error { } // Cancel provides a mock function with given fields: runID -func (_m *Application) Cancel(runID *models.ID) error { +func (_m *Application) Cancel(runID *models.ID) (*models.JobRun, error) { ret := _m.Called(runID) - var r0 error - if rf, ok := ret.Get(0).(func(*models.ID) error); ok { + var r0 *models.JobRun + if rf, ok := ret.Get(0).(func(*models.ID) *models.JobRun); ok { r0 = rf(runID) } else { - r0 = ret.Error(0) + if ret.Get(0) != nil { + r0 = ret.Get(0).(*models.JobRun) + } } - return r0 + var r1 error + if rf, ok := ret.Get(1).(func(*models.ID) error); ok { + r1 = rf(runID) + } else { + r1 = ret.Error(1) + } + + return r0, r1 } // Create provides a mock function with given fields: jobSpecID, initiator, data, creationHeight, runRequest diff --git a/core/internal/mocks/run_manager.go b/core/internal/mocks/run_manager.go index 0503bc9e9fb..5775b380a81 100644 --- a/core/internal/mocks/run_manager.go +++ b/core/internal/mocks/run_manager.go @@ -12,17 +12,26 @@ type RunManager struct { } // Cancel provides a mock function with given fields: runID -func (_m *RunManager) Cancel(runID *models.ID) error { +func (_m *RunManager) Cancel(runID *models.ID) (*models.JobRun, error) { ret := _m.Called(runID) - var r0 error - if rf, ok := ret.Get(0).(func(*models.ID) error); ok { + var r0 *models.JobRun + if rf, ok := ret.Get(0).(func(*models.ID) *models.JobRun); ok { r0 = rf(runID) } else { - r0 = ret.Error(0) + if ret.Get(0) != nil { + r0 = ret.Get(0).(*models.JobRun) + } } - return r0 + var r1 error + if rf, ok := ret.Get(1).(func(*models.ID) error); ok { + r1 = rf(runID) + } else { + r1 = ret.Error(1) + } + + return r0, r1 } // Create provides a mock function with given fields: jobSpecID, initiator, data, creationHeight, runRequest diff --git a/core/services/run_executor.go b/core/services/run_executor.go index d05e0bc6a26..a4119e1273f 100644 --- a/core/services/run_executor.go +++ b/core/services/run_executor.go @@ -61,6 +61,7 @@ func (je *runExecutor) Execute(runID *models.ID) error { } if err := je.store.ORM.SaveJobRun(&run); errors.Cause(err) == orm.OptimisticUpdateConflictError { + logger.Debugw("Optimistic update conflict while updating run", run.ForLogger()...) return nil } else if err != nil { return err diff --git a/core/services/run_manager.go b/core/services/run_manager.go index ba3cdc612f2..7b3cac7f647 100644 --- a/core/services/run_manager.go +++ b/core/services/run_manager.go @@ -43,7 +43,7 @@ type RunManager interface { ResumePending( runID *models.ID, input models.BridgeRunResult) error - Cancel(runID *models.ID) error + Cancel(runID *models.ID) (*models.JobRun, error) ResumeAllInProgress() error ResumeAllConfirming(currentBlockHeight *big.Int) error @@ -256,7 +256,7 @@ func (jm *runManager) ResumeAllConfirming(currentBlockHeight *big.Int) error { err := jm.updateAndTrigger(run) if err != nil { - logger.Error("Error saving job run", "error", err) + logger.Error("Error saving run", "error", err) } }, models.RunStatusPendingConnection, models.RunStatusPendingConfirmations) } @@ -276,7 +276,7 @@ func (jm *runManager) ResumeAllConnecting() error { run.Status = models.RunStatusInProgress err := jm.updateAndTrigger(run) if err != nil { - logger.Error("Error saving job run", "error", err) + logger.Error("Error saving run", "error", err) } }, models.RunStatusPendingConnection, models.RunStatusPendingConfirmations) } @@ -291,7 +291,7 @@ func (jm *runManager) ResumePending( return err } - logger.Debugw("External adapter resuming job", run.ForLogger("input_data", input.Data)...) + logger.Debugw("External adapter resuming run", run.ForLogger("input_data", input.Data)...) if !run.Status.PendingBridge() { return fmt.Errorf("Attempting to resume non pending run %s", run.ID) @@ -325,24 +325,19 @@ func (jm *runManager) ResumeAllInProgress() error { } // Cancel suspends a running task. -func (jm *runManager) Cancel(runID *models.ID) error { +func (jm *runManager) Cancel(runID *models.ID) (*models.JobRun, error) { run, err := jm.orm.FindJobRun(runID) if err != nil { - return err + return nil, err } - logger.Debugw("Cancelling job", run.ForLogger()...) - if !run.Status.Runnable() { - return fmt.Errorf("Cannot cancel a non runnable job") - } - - currentTaskRun := run.NextTaskRun() - if currentTaskRun != nil { - currentTaskRun.Status = models.RunStatusCancelled + logger.Debugw("Cancelling run", run.ForLogger()...) + if run.Status.Finished() { + return nil, fmt.Errorf("Cannot cancel a run that has already finished") } - run.Status = models.RunStatusCancelled - return jm.orm.SaveJobRun(&run) + run.Cancel() + return &run, jm.orm.SaveJobRun(&run) } func (jm *runManager) updateWithError(run *models.JobRun, msg string, args ...interface{}) error { @@ -350,7 +345,7 @@ func (jm *runManager) updateWithError(run *models.JobRun, msg string, args ...in logger.Error(fmt.Sprintf(msg, args...)) if err := jm.orm.SaveJobRun(run); err != nil { - logger.Error("Error saving job run", "error", err) + logger.Error("Error saving run", "error", err) return err } return nil diff --git a/core/store/models/common.go b/core/store/models/common.go index 8d00080ff0e..a930e9702ec 100644 --- a/core/store/models/common.go +++ b/core/store/models/common.go @@ -81,6 +81,11 @@ func (s RunStatus) Completed() bool { return s == RunStatusCompleted } +// Cancelled returns true if the status is RunStatusCancelled. +func (s RunStatus) Cancelled() bool { + return s == RunStatusCancelled +} + // Errored returns true if the status is RunStatusErrored. func (s RunStatus) Errored() bool { return s == RunStatusErrored @@ -93,7 +98,7 @@ func (s RunStatus) Pending() bool { // Finished returns true if the status is final and can't be changed. func (s RunStatus) Finished() bool { - return s.Completed() || s.Errored() + return s.Completed() || s.Errored() || s.Cancelled() } // Runnable returns true if the status is ready to be run. diff --git a/core/store/models/job_run.go b/core/store/models/job_run.go index 8ffabdfedc3..9771cdeb2ab 100644 --- a/core/store/models/job_run.go +++ b/core/store/models/job_run.go @@ -68,7 +68,7 @@ func (jr JobRun) ForLogger(kvs ...interface{}) []interface{} { output = append(output, "job_error", jr.ErrorString()) } - if jr.Status == "completed" { + if jr.Status.Completed() { output = append(output, "link_earned", jr.Payment) } @@ -118,8 +118,16 @@ func (jr *JobRun) TasksRemain() bool { // SetError sets this job run to failed and saves the error message func (jr *JobRun) SetError(err error) { jr.Result.ErrorMessage = null.StringFrom(err.Error()) - jr.Status = RunStatusErrored - jr.FinishedAt = null.TimeFrom(time.Now()) + jr.setStatus(RunStatusErrored) +} + +// Cancel sets this run as cancelled, it should no longer be processed. +func (jr *JobRun) Cancel() { + currentTaskRun := jr.NextTaskRun() + if currentTaskRun != nil { + currentTaskRun.Status = RunStatusCancelled + } + jr.setStatus(RunStatusCancelled) } // ApplyOutput updates the JobRun's Result and Status diff --git a/core/store/orm/orm.go b/core/store/orm/orm.go index d0104c2e481..c45e5e32f2a 100644 --- a/core/store/orm/orm.go +++ b/core/store/orm/orm.go @@ -311,7 +311,7 @@ func (orm *ORM) LinkEarnedFor(spec *models.JobSpec) (*assets.Link, error) { var earned *assets.Link query := orm.DB.Table("job_runs"). Joins("JOIN job_specs ON job_runs.job_spec_id = job_specs.id"). - Where("job_specs.id = ? AND job_runs.finished_at IS NOT NULL", spec.ID) + Where("job_specs.id = ? AND job_runs.status = ? AND job_runs.finished_at IS NOT NULL", spec.ID, models.RunStatusCompleted) if dbutil.IsPostgres(orm.DB) { query = query.Select("SUM(payment)") diff --git a/core/store/orm/orm_test.go b/core/store/orm/orm_test.go index 219e9467fbf..ecedc4b6d3e 100644 --- a/core/store/orm/orm_test.go +++ b/core/store/orm/orm_test.go @@ -244,20 +244,29 @@ func TestORM_LinkEarnedFor(t *testing.T) { initr := job.Initiators[0] jr1 := job.NewRun(initr) + jr1.Status = models.RunStatusCompleted jr1.Payment = assets.NewLink(2) jr1.FinishedAt = null.TimeFrom(time.Now()) require.NoError(t, store.CreateJobRun(&jr1)) jr2 := job.NewRun(initr) + jr2.Status = models.RunStatusCompleted jr2.Payment = assets.NewLink(3) jr2.FinishedAt = null.TimeFrom(time.Now()) require.NoError(t, store.CreateJobRun(&jr2)) jr3 := job.NewRun(initr) + jr3.Status = models.RunStatusCompleted jr3.Payment = assets.NewLink(5) jr3.FinishedAt = null.TimeFrom(time.Now()) require.NoError(t, store.CreateJobRun(&jr3)) jr4 := job.NewRun(initr) + jr4.Status = models.RunStatusCompleted jr4.Payment = assets.NewLink(5) require.NoError(t, store.CreateJobRun(&jr4)) + jr5 := job.NewRun(initr) + jr5.Status = models.RunStatusCancelled + jr5.Payment = assets.NewLink(5) + jr5.FinishedAt = null.TimeFrom(time.Now()) + require.NoError(t, store.CreateJobRun(&jr5)) totalEarned, err := store.LinkEarnedFor(&job) require.NoError(t, err) diff --git a/core/web/job_runs_controller.go b/core/web/job_runs_controller.go index babde6c4e11..32c28442871 100644 --- a/core/web/job_runs_controller.go +++ b/core/web/job_runs_controller.go @@ -143,3 +143,19 @@ func (jrc *JobRunsController) Update(c *gin.Context) { jsonAPIResponse(c, jr, "job run") } } + +// Cancel stops a Run from continuing. +// Example: +// "/runs/:RunID/cancellation" +func (jrc *JobRunsController) Cancel(c *gin.Context) { + var jr *models.JobRun + if id, err := models.NewIDFromString(c.Param("RunID")); err != nil { + jsonAPIError(c, http.StatusUnprocessableEntity, err) + } else if jr, err = jrc.App.Cancel(id); errors.Cause(err) == orm.ErrorNotFound { + jsonAPIError(c, http.StatusNotFound, errors.New("Job run not found")) + } else if err != nil { + jsonAPIError(c, http.StatusInternalServerError, err) + } else { + jsonAPIResponse(c, presenters.JobRun{JobRun: *jr}, "job run") + } +} diff --git a/core/web/router.go b/core/web/router.go index 4e7db934fde..ff51b53f9e0 100644 --- a/core/web/router.go +++ b/core/web/router.go @@ -297,6 +297,7 @@ func v2Routes(app services.Application, r *gin.RouterGroup) { authv2.GET("/runs", paginatedRequest(jr.Index)) authv2.GET("/runs/:RunID", jr.Show) + authv2.PUT("/runs/:RunID/cancellation", jr.Cancel) authv2.GET("/service_agreements/:SAID", sa.Show) From 9030e0a6d35124e2d14a48e848a62cf093af82ae Mon Sep 17 00:00:00 2001 From: John Barker Date: Wed, 6 Nov 2019 17:06:43 -0700 Subject: [PATCH 114/199] Add TestJobRunsController_Cancel --- core/internal/cltest/cltest.go | 5 +++++ core/web/job_runs_controller_test.go | 32 ++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/core/internal/cltest/cltest.go b/core/internal/cltest/cltest.go index 9635c279060..85bf435e240 100644 --- a/core/internal/cltest/cltest.go +++ b/core/internal/cltest/cltest.go @@ -457,6 +457,11 @@ func (r *HTTPClientCleaner) Post(path string, body io.Reader) (*http.Response, f return bodyCleaner(r.t, resp, err) } +func (r *HTTPClientCleaner) Put(path string, body io.Reader) (*http.Response, func()) { + resp, err := r.HTTPClient.Put(path, body) + return bodyCleaner(r.t, resp, err) +} + func (r *HTTPClientCleaner) Patch(path string, body io.Reader, headers ...map[string]string) (*http.Response, func()) { resp, err := r.HTTPClient.Patch(path, body, headers...) return bodyCleaner(r.t, resp, err) diff --git a/core/web/job_runs_controller_test.go b/core/web/job_runs_controller_test.go index 10b2ee41818..35878fdf719 100644 --- a/core/web/job_runs_controller_test.go +++ b/core/web/job_runs_controller_test.go @@ -515,3 +515,35 @@ func TestJobRunsController_Show_Unauthenticated(t *testing.T) { assert.NoError(t, err) assert.Equal(t, http.StatusUnauthorized, resp.StatusCode, "Response should be forbidden") } + +func TestJobRunsController_Cancel(t *testing.T) { + t.Parallel() + app, cleanup := cltest.NewApplication(t) + app.Start() + defer cleanup() + + client := app.NewHTTPClient() + + t.Run("invalid run id", func(t *testing.T) { + response, cleanup := client.Put("/v2/runs/xxx/cancellation", nil) + defer cleanup() + cltest.AssertServerResponse(t, response, http.StatusUnprocessableEntity) + }) + + t.Run("missing run", func(t *testing.T) { + resp, cleanup := client.Put("/v2/runs/29023583-0D39-4844-9696-451102590936/cancellation", nil) + defer cleanup() + cltest.AssertServerResponse(t, resp, http.StatusNotFound) + }) + + job := cltest.NewJobWithWebInitiator() + require.NoError(t, app.Store.CreateJob(&job)) + run := job.NewRun(job.Initiators[0]) + require.NoError(t, app.Store.CreateJobRun(&run)) + + t.Run("valid run", func(t *testing.T) { + resp, cleanup := client.Put(fmt.Sprintf("/v2/runs/%s/cancellation", run.ID), nil) + defer cleanup() + cltest.AssertServerResponse(t, resp, http.StatusOK) + }) +} From 58ec295f78cdc33651ed614230cb7db8044f3868 Mon Sep 17 00:00:00 2001 From: Alex Kwiatkowski Date: Wed, 6 Nov 2019 16:20:25 -0800 Subject: [PATCH 115/199] Fix webpack dev server in OperatorUI [Fixes #169613448] --- operator_ui/static.config.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/operator_ui/static.config.js b/operator_ui/static.config.js index 1b943b3e342..1089958527a 100644 --- a/operator_ui/static.config.js +++ b/operator_ui/static.config.js @@ -10,6 +10,10 @@ const generateClassName = createGenerateClassName() export default { maxThreads: MAX_EXPORT_HTML_THREADS || CORES, + devServer: { + port: 3000, + host: '127.0.0.1', + }, getSiteData: () => ({ title: 'Chainlink', }), From 8ade52c2746903e5853354ef4dc8115fcd5bad38 Mon Sep 17 00:00:00 2001 From: Ryan Hall Date: Wed, 6 Nov 2019 19:20:46 -0500 Subject: [PATCH 116/199] Add better error message and abort response on error in user_controller#getAccountBalanceFor --- core/web/user_controller.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/core/web/user_controller.go b/core/web/user_controller.go index 9d36f633b01..579f7320578 100644 --- a/core/web/user_controller.go +++ b/core/web/user_controller.go @@ -86,9 +86,13 @@ func (c *UserController) updateUserPassword(ctx *gin.Context, user *models.User, func getAccountBalanceFor(ctx *gin.Context, store *store.Store, account accounts.Account) presenters.AccountBalance { txm := store.TxManager if ethBalance, err := txm.GetEthBalance(account.Address); err != nil { + err = fmt.Errorf("Error calling getEthBalance on Ethereum node: %v", err) jsonAPIError(ctx, http.StatusInternalServerError, err) + ctx.Abort() } else if linkBalance, err := txm.GetLINKBalance(account.Address); err != nil { + err = fmt.Errorf("Error calling getLINKBalance on Ethereum node: %v", err) jsonAPIError(ctx, http.StatusInternalServerError, err) + ctx.Abort() } else { return presenters.AccountBalance{ Address: account.Address.Hex(), From 64bcf7fe7eac10a45956971646af4d9450ca51fb Mon Sep 17 00:00:00 2001 From: John Barker Date: Wed, 6 Nov 2019 17:25:57 -0700 Subject: [PATCH 117/199] Check job is cancelled on TestJobRunsController_Cancel --- core/web/job_runs_controller_test.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/core/web/job_runs_controller_test.go b/core/web/job_runs_controller_test.go index 35878fdf719..6952a0caf44 100644 --- a/core/web/job_runs_controller_test.go +++ b/core/web/job_runs_controller_test.go @@ -545,5 +545,9 @@ func TestJobRunsController_Cancel(t *testing.T) { resp, cleanup := client.Put(fmt.Sprintf("/v2/runs/%s/cancellation", run.ID), nil) defer cleanup() cltest.AssertServerResponse(t, resp, http.StatusOK) + + run, err := app.Store.FindJobRun(run.ID) + assert.NoError(t, err) + assert.Equal(t, models.RunStatusCancelled, run.Status) }) } From 4359c0bed89398aff85a3c6f14060635e9130c94 Mon Sep 17 00:00:00 2001 From: John Barker Date: Wed, 6 Nov 2019 20:18:18 -0700 Subject: [PATCH 118/199] Always build images on non release branches --- .circleci/config.yml | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/.circleci/config.yml b/.circleci/config.yml index 62b7ddd27f9..a06e118fb59 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -309,6 +309,17 @@ jobs: name: Docker push, if applicable command: | tools/ci/push_explorer "${CIRCLE_BRANCH}" "${CIRCLE_TAG}" + build-explorer: + machine: true + steps: + - checkout + - run: + name: Docker login + command: | + echo "$DOCKERHUB_PASS" | docker login -u "$DOCKERHUB_USER" --password-stdin + - run: + name: Docker build + command: docker build -f explorer/Dockerfile -t smartcontract/explorer:circleci . build-publish-chainlink: machine: true steps: @@ -325,6 +336,18 @@ jobs: name: Docker push, if applicable command: | tools/ci/push_chainlink "${CIRCLE_BRANCH}" "${CIRCLE_TAG}" + build-chainlink: + machine: true + steps: + - checkout + - run: + name: Docker login + command: | + echo "$DOCKERHUB_PASS" | docker login -u "$DOCKERHUB_USER" --password-stdin + - run: + name: Docker build + command: | + DOCKER_TAG=circleci make docker reportcoverage: docker: - image: smartcontract/builder:1.0.25 @@ -397,6 +420,14 @@ workflows: filters: tags: only: /^v.*/ + - build-chainlink: + filters: + tags: + only: /^v.*/ + - build-explorer: + filters: + tags: + only: /^v.*/ - prepublish_npm: filters: tags: From d7e34d7f47997b070f562f1142420f015e6b9c34 Mon Sep 17 00:00:00 2001 From: henrynguyen5 <6404866+HenryNguyen5@users.noreply.github.com> Date: Wed, 6 Nov 2019 22:19:05 -0500 Subject: [PATCH 119/199] Add support for truffle interop with helpers This adds support for truffle tests by exposing a "create" function within the src/helpersV2.ts file. --- evm/package.json | 2 +- evm/src/helpersV2.ts | 24 ++++++++++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/evm/package.json b/evm/package.json index 46f4693302e..453eb9594de 100644 --- a/evm/package.json +++ b/evm/package.json @@ -1,6 +1,6 @@ { "name": "chainlink", - "version": "0.7.8", + "version": "0.7.9", "license": "MIT", "main": "./dist/src", "scripts": { diff --git a/evm/src/helpersV2.ts b/evm/src/helpersV2.ts index 4626a0ef350..308fb06a217 100644 --- a/evm/src/helpersV2.ts +++ b/evm/src/helpersV2.ts @@ -77,6 +77,30 @@ export function useSnapshot( } } +/** + * A wrapper function to make generated contracts compatible with truffle test suites. + * + * Note that the returned contract is an instance of ethers.Contract, not a @truffle/contract, so there are slight + * api differences, though largely the same. + * + * @see https://docs.ethers.io/ethers.js/html/api-contract.html + * @param contractFactory The ethers based contract factory to interop with + * @param address The address to supply as the signer + */ +export function create any>( + contractFactory: T, + address: string, +): InstanceType { + const web3Instance = (global as any).web3 + const provider = new ethers.providers.Web3Provider( + web3Instance.currentProvider, + ) + const signer = provider.getSigner(address) + const factory = new contractFactory(signer) + + return factory +} + /** * Generate roles and personas for tests along with their corrolated account addresses */ From 834beb386a3b08bae5d5fbf1fda5388fe3211a54 Mon Sep 17 00:00:00 2001 From: Steve Ellis Date: Wed, 6 Nov 2019 23:14:12 -0500 Subject: [PATCH 120/199] add logging on disconnect of head tracker --- core/services/head_tracker.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core/services/head_tracker.go b/core/services/head_tracker.go index 90cac77f7d8..3b5a590d85b 100644 --- a/core/services/head_tracker.go +++ b/core/services/head_tracker.go @@ -90,6 +90,7 @@ func (ht *HeadTracker) Stop() error { ht.connected = false ht.disconnect() } + logger.Info(fmt.Sprintf("Head tracker disconnecting from %v", ht.store.Config.EthereumURL())) close(ht.done) close(ht.subscriptionSucceeded) ht.started = false @@ -179,7 +180,7 @@ func (ht *HeadTracker) subscribe() bool { ht.sleeper.Reset() for { ht.unsubscribeFromHead() - logger.Info("Connecting to node ", ht.store.Config.EthereumURL(), " in ", ht.sleeper.Duration()) + logger.Info("Connecting to ethereum node ", ht.store.Config.EthereumURL(), " in ", ht.sleeper.Duration()) select { case <-ht.done: return false From 32c40e2ff4242d877602fde96ffabc882ceda86c Mon Sep 17 00:00:00 2001 From: henrynguyen5 <6404866+HenryNguyen5@users.noreply.github.com> Date: Wed, 6 Nov 2019 23:24:54 -0500 Subject: [PATCH 121/199] Bump resource classes of flakey tests --- .circleci/config.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.circleci/config.yml b/.circleci/config.yml index 62b7ddd27f9..c71fff13f7c 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -182,6 +182,7 @@ jobs: - store_artifacts: path: ./integration/logs truffle: + resource_class: large docker: - image: smartcontract/builder:1.0.25 steps: @@ -333,6 +334,7 @@ jobs: - run: ./tools/ci/init_gcloud - run: ./tools/ci/report_coverage prepublish_npm: + resource_class: large docker: - image: smartcontract/builder:1.0.25 steps: From 68b41b21e681453dbb7857823c9a19e8328ceb67 Mon Sep 17 00:00:00 2001 From: "Tabbakha, Ahmad" Date: Tue, 29 Oct 2019 08:21:47 -0500 Subject: [PATCH 122/199] remove govalidator url check that prevented valid urls --- core/services/validators.go | 4 ++- core/services/validators_test.go | 6 ++-- core/web/bridge_types_controller_test.go | 29 +++++++++++++++++++ .../testdata/bridge_with_hostname_url.json | 6 ++++ 4 files changed, 41 insertions(+), 4 deletions(-) create mode 100644 core/web/testdata/bridge_with_hostname_url.json diff --git a/core/services/validators.go b/core/services/validators.go index 649595e271a..4f84ab79232 100644 --- a/core/services/validators.go +++ b/core/services/validators.go @@ -2,6 +2,7 @@ package services import ( "fmt" + "net/url" "strings" "time" @@ -46,8 +47,9 @@ func ValidateBridgeType(bt *models.BridgeTypeRequest, store *store.Store) error if _, err := models.NewTaskType(bt.Name.String()); err != nil { fe.Merge(err) } - if isURL := govalidator.IsURL(bt.URL.String()); !isURL { + if _, err := url.Parse(bt.URL.String()); err != nil { fe.Add("Invalid URL format") + fe.Merge(err) } ts := models.TaskSpec{Type: bt.Name} if a, _ := adapters.For(ts, store.Config, store.ORM); a != nil { diff --git a/core/services/validators_test.go b/core/services/validators_test.go index 7bc1e167f82..74eac2df1a7 100644 --- a/core/services/validators_test.go +++ b/core/services/validators_test.go @@ -125,10 +125,10 @@ func TestValidateAdapter(t *testing.T) { models.NewJSONAPIErrorsWith("Task Type validation: name invalid/adapter contains invalid characters"), }, { - "invalid adapter url", - "adapterwithinvalidurl", + "valid url", + "adapterwithvalidurl", cltest.WebURL(t, "//denergy"), - models.NewJSONAPIErrorsWith("Invalid URL format"), + nil, }, {"new external adapter", "gdaxprice", bt.URL, nil}, } diff --git a/core/web/bridge_types_controller_test.go b/core/web/bridge_types_controller_test.go index e4d43a52e4c..3fea350a823 100644 --- a/core/web/bridge_types_controller_test.go +++ b/core/web/bridge_types_controller_test.go @@ -127,6 +127,35 @@ func TestBridgeTypesController_Create_Success(t *testing.T) { assert.NotEmpty(t, bt.OutgoingToken) } +func TestBridgeTypesController_Create_Host_URL(t *testing.T) { + t.Parallel() + + app, cleanup := cltest.NewApplication(t) + defer cleanup() + require.NoError(t, app.Start()) + client := app.NewHTTPClient() + + resp, cleanup := client.Post( + "/v2/bridge_types", + bytes.NewBuffer(cltest.MustReadFile(t, "testdata/bridge_with_hostname_url.json")), + ) + defer cleanup() + cltest.AssertServerResponse(t, resp, http.StatusOK) + respJSON := cltest.ParseJSON(t, resp.Body) + btName := respJSON.Get("data.attributes.name").String() + + assert.NotEmpty(t, respJSON.Get("data.attributes.incomingToken").String()) + assert.NotEmpty(t, respJSON.Get("data.attributes.outgoingToken").String()) + + bt, err := app.Store.FindBridge(models.MustNewTaskType(btName)) + assert.NoError(t, err) + assert.Equal(t, "coinmarketcap", bt.Name.String()) + assert.Equal(t, uint32(10), bt.Confirmations) + assert.Equal(t, "http://chainlink_cmc-adapter_1:8080", bt.URL.String()) + assert.Equal(t, assets.NewLink(350), bt.MinimumContractPayment) + assert.NotEmpty(t, bt.OutgoingToken) +} + func TestBridgeTypesController_Update_Success(t *testing.T) { t.Parallel() diff --git a/core/web/testdata/bridge_with_hostname_url.json b/core/web/testdata/bridge_with_hostname_url.json new file mode 100644 index 00000000000..fb47a0ccde8 --- /dev/null +++ b/core/web/testdata/bridge_with_hostname_url.json @@ -0,0 +1,6 @@ +{ + "name": "coinmarketcap", + "url": "http://chainlink_cmc-adapter_1:8080", + "confirmations": 10, + "minimumContractPayment": "350" +} From d7e950cb87dc1da82600e9b1cfcb18ee6e3c3b7b Mon Sep 17 00:00:00 2001 From: "Tabbakha, Ahmad" Date: Wed, 6 Nov 2019 16:32:03 -0600 Subject: [PATCH 123/199] update import paths with chainlink as new module name --- core/adapters/adapter.go | 8 ++-- core/adapters/adapter_test.go | 9 ++-- core/adapters/bridge.go | 5 ++- core/adapters/bridge_test.go | 7 +-- core/adapters/compare.go | 4 +- core/adapters/compare_test.go | 5 ++- core/adapters/copy.go | 4 +- core/adapters/copy_test.go | 7 +-- core/adapters/eth_bool.go | 5 ++- core/adapters/eth_bool_test.go | 5 ++- core/adapters/eth_format.go | 7 +-- core/adapters/eth_format_test.go | 5 ++- core/adapters/eth_tx.go | 11 ++--- core/adapters/eth_tx_abi_encode.go | 6 +-- core/adapters/eth_tx_abi_encode_test.go | 8 ++-- core/adapters/eth_tx_test.go | 13 +++--- core/adapters/http.go | 6 +-- core/adapters/http_test.go | 11 ++--- core/adapters/json_parse.go | 7 +-- core/adapters/json_parse_test.go | 7 +-- core/adapters/multiply.go | 6 +-- core/adapters/multiply_sgx.go | 6 +-- core/adapters/multiply_test.go | 5 ++- core/adapters/no_op.go | 4 +- core/adapters/random.go | 4 +- core/adapters/random_test.go | 5 ++- core/adapters/sleep.go | 8 ++-- core/adapters/sleep_test.go | 7 +-- core/adapters/wasm.go | 4 +- core/adapters/wasm_sgx.go | 4 +- core/adapters/wasm_test.go | 7 +-- core/cmd/app.go | 7 +-- core/cmd/client.go | 13 +++--- core/cmd/client_test.go | 9 ++-- core/cmd/enclave.go | 2 +- core/cmd/enclave_sgx.go | 2 +- core/cmd/key_store_authenticator.go | 2 +- core/cmd/key_store_authenticator_test.go | 5 ++- core/cmd/local_client.go | 15 ++++--- core/cmd/local_client_test.go | 13 +++--- core/cmd/prompter.go | 3 +- core/cmd/remote_client.go | 11 ++--- core/cmd/remote_client_test.go | 11 ++--- core/cmd/renderer.go | 13 +++--- core/cmd/renderer_test.go | 11 ++--- core/internal/cltest/cltest.go | 21 ++++----- core/internal/cltest/factories.go | 15 ++++--- core/internal/cltest/fixtures.go | 3 +- core/internal/cltest/mocks.go | 13 +++--- core/internal/cltest/postgres.go | 6 +-- core/internal/features_test.go | 9 ++-- core/internal/mocks/application.go | 4 +- core/internal/mocks/eth_client_mocks.go | 11 ++--- core/internal/mocks/run_executor.go | 2 +- core/internal/mocks/run_manager.go | 2 +- core/internal/mocks/run_queue.go | 2 +- core/internal/mocks/tx_manager_mocks.go | 13 +++--- core/logger/prettyconsole.go | 3 +- core/main.go | 6 +-- core/main_test.go | 4 +- core/services/application.go | 25 ++++++----- core/services/application_test.go | 9 ++-- core/services/head_trackable_callback.go | 2 +- core/services/head_tracker.go | 11 ++--- core/services/head_tracker_test.go | 11 ++--- core/services/job_subscriber.go | 7 +-- core/services/job_subscriber_test.go | 9 ++-- core/services/mock_services/job_subscriber.go | 5 ++- core/services/reaper.go | 6 +-- core/services/reaper_test.go | 7 +-- core/services/run_executor.go | 11 ++--- core/services/run_executor_test.go | 11 ++--- core/services/run_manager.go | 19 ++++---- core/services/run_manager_test.go | 13 +++--- core/services/run_queue.go | 4 +- core/services/run_queue_test.go | 9 ++-- core/services/runs.go | 12 ++--- core/services/scheduler.go | 9 ++-- core/services/scheduler_test.go | 7 +-- core/services/signatures/ethdss/ethdss.go | 5 ++- .../services/signatures/ethdss/ethdss_test.go | 6 +-- .../signatures/ethschnorr/ethschnorr.go | 2 +- .../signatures/ethschnorr/ethschnorr_test.go | 4 +- .../signatures/secp256k1/curve_test.go | 3 +- .../signatures/secp256k1/field_test.go | 2 +- .../signatures/secp256k1/point_test.go | 2 +- .../signatures/secp256k1/scalar_test.go | 2 +- core/services/subscription.go | 13 +++--- core/services/subscription_test.go | 13 +++--- core/services/synchronization/presenters.go | 9 ++-- .../synchronization/presenters_test.go | 7 +-- core/services/synchronization/stats_pusher.go | 9 ++-- .../synchronization/stats_pusher_test.go | 9 ++-- .../synchronization/web_socket_client.go | 5 ++- .../synchronization/web_socket_client_test.go | 5 ++- core/services/validators.go | 9 ++-- core/services/validators_test.go | 11 ++--- core/services/vrf/vrf.go | 5 ++- core/services/vrf/vrf_test.go | 4 +- core/store/assets/currencies_test.go | 3 +- core/store/eth_client.go | 9 ++-- core/store/eth_client_test.go | 7 +-- core/store/key_store.go | 7 +-- core/store/key_store_test.go | 3 +- core/store/migrations/migrate.go | 45 ++++++++++--------- core/store/migrations/migrate_test.go | 17 +++---- core/store/migrations/migration0/migrate.go | 8 ++-- .../migrations/migration1559081901/migrate.go | 3 +- .../migrations/migration1560791143/migrate.go | 3 +- .../migrations/migration1560881855/migrate.go | 5 ++- .../migrations/migration1560886530/migrate.go | 9 ++-- .../migrations/migration1564007745/migrate.go | 3 +- .../migrations/migration1565210496/migrate.go | 3 +- .../migrations/migration1565291711/migrate.go | 3 +- .../migrations/migration1565877314/migrate.go | 7 +-- .../migrations/migration1566498796/migrate.go | 3 +- .../migrations/migration1567029116/migrate.go | 3 +- .../migrations/migration1568280052/migrate.go | 5 ++- .../migrations/migration1568833756/migrate.go | 3 +- .../migrations/migration1570087128/migrate.go | 3 +- core/store/models/address.go | 3 +- core/store/models/big.go | 3 +- core/store/models/bridge_type.go | 4 +- core/store/models/bridge_type_test.go | 5 ++- core/store/models/bulk_test.go | 3 +- core/store/models/cbor.go | 3 +- core/store/models/common.go | 3 +- core/store/models/common_test.go | 7 +-- core/store/models/eth.go | 3 +- core/store/models/eth_test.go | 9 ++-- core/store/models/external_initiator.go | 3 +- core/store/models/external_initiator_test.go | 5 ++- core/store/models/id.go | 3 +- core/store/models/job_run.go | 5 ++- core/store/models/job_run_test.go | 7 +-- core/store/models/job_spec.go | 5 ++- core/store/models/job_spec_test.go | 9 ++-- core/store/models/log_events.go | 11 ++--- core/store/models/log_events_test.go | 7 +-- core/store/models/service_agreement.go | 5 ++- core/store/models/service_agreement_test.go | 9 ++-- core/store/models/signature.go | 3 +- core/store/models/topics_test.go | 3 +- core/store/models/user.go | 2 +- core/store/models/user_test.go | 5 ++- core/store/orm/config.go | 7 +-- core/store/orm/config_reader.go | 3 +- core/store/orm/config_test.go | 5 ++- core/store/orm/locking_strategies_test.go | 5 ++- core/store/orm/log_wrapper.go | 3 +- core/store/orm/orm.go | 11 ++--- core/store/orm/orm_test.go | 17 +++---- core/store/orm/schema.go | 3 +- core/store/presenters/presenters.go | 31 ++++++------- core/store/presenters/presenters_test.go | 3 +- core/store/store.go | 13 +++--- core/store/store_test.go | 7 +-- core/store/tx_manager.go | 15 ++++--- core/store/tx_manager_internal_test.go | 3 +- core/store/tx_manager_test.go | 17 +++---- core/store/types.go | 2 +- core/utils/json_normalization_test.go | 5 ++- core/utils/utils_test.go | 5 ++- core/web/box_test.go | 3 +- core/web/bridge_types_controller.go | 7 +-- core/web/bridge_types_controller_test.go | 11 ++--- core/web/bulk_deletes_controller.go | 5 ++- core/web/config_controller.go | 7 +-- core/web/config_controller_test.go | 9 ++-- core/web/cors_test.go | 3 +- core/web/external_initiators.go | 5 ++- core/web/external_initiators_controller.go | 7 +-- .../external_initiators_controller_test.go | 7 +-- core/web/external_initiators_test.go | 7 +-- core/web/gui_assets_test.go | 3 +- core/web/helpers.go | 5 ++- core/web/helpers_test.go | 5 ++- core/web/job_runs_controller.go | 11 ++--- core/web/job_runs_controller_test.go | 9 ++-- core/web/job_specs_controller.go | 9 ++-- core/web/job_specs_controller_test.go | 13 +++--- core/web/keys_controller.go | 7 +-- core/web/keys_controller_test.go | 5 ++- core/web/ping_controller.go | 3 +- core/web/ping_controller_test.go | 7 +-- core/web/router.go | 11 ++--- core/web/router_test.go | 7 +-- core/web/service_agreements_controller.go | 9 ++-- .../web/service_agreements_controller_test.go | 7 +-- core/web/sessions_controller.go | 5 ++- core/web/sessions_controller_test.go | 5 ++- core/web/transactions_controller.go | 7 +-- core/web/transactions_controller_test.go | 9 ++-- core/web/transfer_controller.go | 11 ++--- core/web/transfer_controller_test.go | 7 +-- core/web/tx_attempts_controller.go | 3 +- core/web/tx_attempts_controller_test.go | 7 +-- core/web/user_controller.go | 11 ++--- core/web/user_controller_test.go | 5 ++- core/web/withdrawals_controller.go | 11 ++--- core/web/withdrawals_controller_test.go | 9 ++-- go.mod | 2 +- tools/ci/go_test | 2 +- 203 files changed, 808 insertions(+), 643 deletions(-) diff --git a/core/adapters/adapter.go b/core/adapters/adapter.go index 2b4feb7e61e..4886acd5e37 100644 --- a/core/adapters/adapter.go +++ b/core/adapters/adapter.go @@ -4,10 +4,10 @@ import ( "encoding/json" "fmt" - "github.com/smartcontractkit/chainlink/core/store" - "github.com/smartcontractkit/chainlink/core/store/assets" - "github.com/smartcontractkit/chainlink/core/store/models" - "github.com/smartcontractkit/chainlink/core/store/orm" + "chainlink/core/store" + "chainlink/core/store/assets" + "chainlink/core/store/models" + "chainlink/core/store/orm" ) var ( diff --git a/core/adapters/adapter_test.go b/core/adapters/adapter_test.go index 576ef8b6894..5a54a929bad 100644 --- a/core/adapters/adapter_test.go +++ b/core/adapters/adapter_test.go @@ -4,10 +4,11 @@ import ( "reflect" "testing" - "github.com/smartcontractkit/chainlink/core/adapters" - "github.com/smartcontractkit/chainlink/core/internal/cltest" - "github.com/smartcontractkit/chainlink/core/store/assets" - "github.com/smartcontractkit/chainlink/core/store/models" + "chainlink/core/adapters" + "chainlink/core/internal/cltest" + "chainlink/core/store/assets" + "chainlink/core/store/models" + "github.com/stretchr/testify/assert" ) diff --git a/core/adapters/bridge.go b/core/adapters/bridge.go index f30b38f695b..adeab5602e4 100644 --- a/core/adapters/bridge.go +++ b/core/adapters/bridge.go @@ -8,9 +8,10 @@ import ( "net/http" "net/url" + "chainlink/core/store" + "chainlink/core/store/models" + "github.com/pkg/errors" - "github.com/smartcontractkit/chainlink/core/store" - "github.com/smartcontractkit/chainlink/core/store/models" ) // Bridge adapter is responsible for connecting the task pipeline to external diff --git a/core/adapters/bridge_test.go b/core/adapters/bridge_test.go index d7628f9ac14..da00e076276 100644 --- a/core/adapters/bridge_test.go +++ b/core/adapters/bridge_test.go @@ -5,9 +5,10 @@ import ( "net/http" "testing" - "github.com/smartcontractkit/chainlink/core/adapters" - "github.com/smartcontractkit/chainlink/core/internal/cltest" - "github.com/smartcontractkit/chainlink/core/store/models" + "chainlink/core/adapters" + "chainlink/core/internal/cltest" + "chainlink/core/store/models" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/core/adapters/compare.go b/core/adapters/compare.go index 55750c6e80c..e18925bba50 100644 --- a/core/adapters/compare.go +++ b/core/adapters/compare.go @@ -4,8 +4,8 @@ import ( "errors" "strconv" - "github.com/smartcontractkit/chainlink/core/store" - "github.com/smartcontractkit/chainlink/core/store/models" + "chainlink/core/store" + "chainlink/core/store/models" ) // Compare adapter type takes an Operator and a Value field to diff --git a/core/adapters/compare_test.go b/core/adapters/compare_test.go index 6975e0b6c4e..2e8886970fc 100644 --- a/core/adapters/compare_test.go +++ b/core/adapters/compare_test.go @@ -3,8 +3,9 @@ package adapters_test import ( "testing" - "github.com/smartcontractkit/chainlink/core/adapters" - "github.com/smartcontractkit/chainlink/core/internal/cltest" + "chainlink/core/adapters" + "chainlink/core/internal/cltest" + "github.com/stretchr/testify/assert" ) diff --git a/core/adapters/copy.go b/core/adapters/copy.go index 8612ecbd2c2..99de8ea0e3a 100644 --- a/core/adapters/copy.go +++ b/core/adapters/copy.go @@ -1,8 +1,8 @@ package adapters import ( - "github.com/smartcontractkit/chainlink/core/store" - "github.com/smartcontractkit/chainlink/core/store/models" + "chainlink/core/store" + "chainlink/core/store/models" ) // Copy obj keys refers to which value to copy inside `data`, diff --git a/core/adapters/copy_test.go b/core/adapters/copy_test.go index f395b7a63ce..9e1826fabef 100644 --- a/core/adapters/copy_test.go +++ b/core/adapters/copy_test.go @@ -5,9 +5,10 @@ import ( "errors" "testing" - "github.com/smartcontractkit/chainlink/core/adapters" - "github.com/smartcontractkit/chainlink/core/internal/cltest" - "github.com/smartcontractkit/chainlink/core/store/models" + "chainlink/core/adapters" + "chainlink/core/internal/cltest" + "chainlink/core/store/models" + "github.com/stretchr/testify/assert" ) diff --git a/core/adapters/eth_bool.go b/core/adapters/eth_bool.go index b4a37035292..fc337b39c73 100644 --- a/core/adapters/eth_bool.go +++ b/core/adapters/eth_bool.go @@ -1,8 +1,9 @@ package adapters import ( - "github.com/smartcontractkit/chainlink/core/store" - "github.com/smartcontractkit/chainlink/core/store/models" + "chainlink/core/store" + "chainlink/core/store/models" + "github.com/tidwall/gjson" ) diff --git a/core/adapters/eth_bool_test.go b/core/adapters/eth_bool_test.go index 2e19341c6e5..b8ad7846880 100644 --- a/core/adapters/eth_bool_test.go +++ b/core/adapters/eth_bool_test.go @@ -3,8 +3,9 @@ package adapters_test import ( "testing" - "github.com/smartcontractkit/chainlink/core/adapters" - "github.com/smartcontractkit/chainlink/core/internal/cltest" + "chainlink/core/adapters" + "chainlink/core/internal/cltest" + "github.com/stretchr/testify/assert" ) diff --git a/core/adapters/eth_format.go b/core/adapters/eth_format.go index b207569e4ff..22a107b9636 100644 --- a/core/adapters/eth_format.go +++ b/core/adapters/eth_format.go @@ -1,11 +1,12 @@ package adapters import ( + "chainlink/core/store" + "chainlink/core/store/models" + "chainlink/core/utils" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" - "github.com/smartcontractkit/chainlink/core/store" - "github.com/smartcontractkit/chainlink/core/store/models" - "github.com/smartcontractkit/chainlink/core/utils" ) // EthBytes32 holds no fields. diff --git a/core/adapters/eth_format_test.go b/core/adapters/eth_format_test.go index d31d175b2f3..b65308a95c2 100644 --- a/core/adapters/eth_format_test.go +++ b/core/adapters/eth_format_test.go @@ -3,8 +3,9 @@ package adapters_test import ( "testing" - "github.com/smartcontractkit/chainlink/core/adapters" - "github.com/smartcontractkit/chainlink/core/internal/cltest" + "chainlink/core/adapters" + "chainlink/core/internal/cltest" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/core/adapters/eth_tx.go b/core/adapters/eth_tx.go index 73ee8b21ac1..964d8abf42c 100644 --- a/core/adapters/eth_tx.go +++ b/core/adapters/eth_tx.go @@ -6,14 +6,15 @@ import ( "net" "regexp" + "chainlink/core/logger" + "chainlink/core/store" + strpkg "chainlink/core/store" + "chainlink/core/store/models" + "chainlink/core/utils" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/pkg/errors" - "github.com/smartcontractkit/chainlink/core/logger" - "github.com/smartcontractkit/chainlink/core/store" - strpkg "github.com/smartcontractkit/chainlink/core/store" - "github.com/smartcontractkit/chainlink/core/store/models" - "github.com/smartcontractkit/chainlink/core/utils" "gopkg.in/guregu/null.v3" ) diff --git a/core/adapters/eth_tx_abi_encode.go b/core/adapters/eth_tx_abi_encode.go index 6475019366b..7f9a8dae193 100644 --- a/core/adapters/eth_tx_abi_encode.go +++ b/core/adapters/eth_tx_abi_encode.go @@ -13,9 +13,9 @@ import ( "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" - strpkg "github.com/smartcontractkit/chainlink/core/store" - "github.com/smartcontractkit/chainlink/core/store/models" - "github.com/smartcontractkit/chainlink/core/utils" + strpkg "chainlink/core/store" + "chainlink/core/store/models" + "chainlink/core/utils" ) const evmWordSize = 32 diff --git a/core/adapters/eth_tx_abi_encode_test.go b/core/adapters/eth_tx_abi_encode_test.go index f5d71b686ed..ab0f7c68333 100644 --- a/core/adapters/eth_tx_abi_encode_test.go +++ b/core/adapters/eth_tx_abi_encode_test.go @@ -15,10 +15,10 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" - "github.com/smartcontractkit/chainlink/core/adapters" - "github.com/smartcontractkit/chainlink/core/internal/cltest" - "github.com/smartcontractkit/chainlink/core/store/models" - "github.com/smartcontractkit/chainlink/core/utils" + "chainlink/core/adapters" + "chainlink/core/internal/cltest" + "chainlink/core/store/models" + "chainlink/core/utils" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" diff --git a/core/adapters/eth_tx_test.go b/core/adapters/eth_tx_test.go index 70d1acd6b4f..df76157986c 100644 --- a/core/adapters/eth_tx_test.go +++ b/core/adapters/eth_tx_test.go @@ -7,16 +7,17 @@ import ( "syscall" "testing" + "chainlink/core/adapters" + "chainlink/core/internal/cltest" + "chainlink/core/internal/mocks" + strpkg "chainlink/core/store" + "chainlink/core/store/models" + "chainlink/core/utils" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/golang/mock/gomock" "github.com/onsi/gomega" - "github.com/smartcontractkit/chainlink/core/adapters" - "github.com/smartcontractkit/chainlink/core/internal/cltest" - "github.com/smartcontractkit/chainlink/core/internal/mocks" - strpkg "github.com/smartcontractkit/chainlink/core/store" - "github.com/smartcontractkit/chainlink/core/store/models" - "github.com/smartcontractkit/chainlink/core/utils" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/core/adapters/http.go b/core/adapters/http.go index 386f668fe91..abedf4cbfef 100644 --- a/core/adapters/http.go +++ b/core/adapters/http.go @@ -12,9 +12,9 @@ import ( "path" "strings" - "github.com/smartcontractkit/chainlink/core/store" - "github.com/smartcontractkit/chainlink/core/store/models" - "github.com/smartcontractkit/chainlink/core/utils" + "chainlink/core/store" + "chainlink/core/store/models" + "chainlink/core/utils" ) // HTTPGet requires a URL which is used for a GET request when the adapter is called. diff --git a/core/adapters/http_test.go b/core/adapters/http_test.go index 930523ca468..8c74ccefb3f 100644 --- a/core/adapters/http_test.go +++ b/core/adapters/http_test.go @@ -5,11 +5,12 @@ import ( "net/http" "testing" - "github.com/smartcontractkit/chainlink/core/adapters" - "github.com/smartcontractkit/chainlink/core/internal/cltest" - "github.com/smartcontractkit/chainlink/core/store" - "github.com/smartcontractkit/chainlink/core/store/models" - "github.com/smartcontractkit/chainlink/core/store/orm" + "chainlink/core/adapters" + "chainlink/core/internal/cltest" + "chainlink/core/store" + "chainlink/core/store/models" + "chainlink/core/store/orm" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/core/adapters/json_parse.go b/core/adapters/json_parse.go index dae17333003..658ada66396 100644 --- a/core/adapters/json_parse.go +++ b/core/adapters/json_parse.go @@ -6,10 +6,11 @@ import ( "strconv" "strings" + "chainlink/core/store" + "chainlink/core/store/models" + "chainlink/core/utils" + simplejson "github.com/bitly/go-simplejson" - "github.com/smartcontractkit/chainlink/core/store" - "github.com/smartcontractkit/chainlink/core/store/models" - "github.com/smartcontractkit/chainlink/core/utils" ) // JSONParse holds a path to the desired field in a JSON object, diff --git a/core/adapters/json_parse_test.go b/core/adapters/json_parse_test.go index 925a5262974..123f6e25629 100644 --- a/core/adapters/json_parse_test.go +++ b/core/adapters/json_parse_test.go @@ -4,9 +4,10 @@ import ( "encoding/json" "testing" - "github.com/smartcontractkit/chainlink/core/adapters" - "github.com/smartcontractkit/chainlink/core/internal/cltest" - "github.com/smartcontractkit/chainlink/core/store/models" + "chainlink/core/adapters" + "chainlink/core/internal/cltest" + "chainlink/core/store/models" + "github.com/stretchr/testify/assert" ) diff --git a/core/adapters/multiply.go b/core/adapters/multiply.go index d2e09563fb9..5ac0950e377 100644 --- a/core/adapters/multiply.go +++ b/core/adapters/multiply.go @@ -7,9 +7,9 @@ import ( "math/big" "strconv" - "github.com/smartcontractkit/chainlink/core/store" - "github.com/smartcontractkit/chainlink/core/store/models" - "github.com/smartcontractkit/chainlink/core/utils" + "chainlink/core/store" + "chainlink/core/store/models" + "chainlink/core/utils" ) // Multiplier represents the number to multiply by in Multiply adapter. diff --git a/core/adapters/multiply_sgx.go b/core/adapters/multiply_sgx.go index 9b6de02f693..a0120543c1e 100644 --- a/core/adapters/multiply_sgx.go +++ b/core/adapters/multiply_sgx.go @@ -15,9 +15,9 @@ import ( "strconv" "unsafe" - "github.com/smartcontractkit/chainlink/core/store" - "github.com/smartcontractkit/chainlink/core/store/models" - "github.com/smartcontractkit/chainlink/core/utils" + "chainlink/core/store" + "chainlink/core/store/models" + "chainlink/core/utils" ) // Multiplier represents the number to multiply by in Multiply adapter. diff --git a/core/adapters/multiply_test.go b/core/adapters/multiply_test.go index c76bf42c27d..b4e0decb4ae 100644 --- a/core/adapters/multiply_test.go +++ b/core/adapters/multiply_test.go @@ -4,8 +4,9 @@ import ( "encoding/json" "testing" - "github.com/smartcontractkit/chainlink/core/adapters" - "github.com/smartcontractkit/chainlink/core/internal/cltest" + "chainlink/core/adapters" + "chainlink/core/internal/cltest" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/core/adapters/no_op.go b/core/adapters/no_op.go index 4819ef71f6a..eeb5df37df1 100644 --- a/core/adapters/no_op.go +++ b/core/adapters/no_op.go @@ -1,8 +1,8 @@ package adapters import ( - "github.com/smartcontractkit/chainlink/core/store" - "github.com/smartcontractkit/chainlink/core/store/models" + "chainlink/core/store" + "chainlink/core/store/models" ) // NoOp adapter type holds no fields diff --git a/core/adapters/random.go b/core/adapters/random.go index d5c51b48a05..a7e5a4168d4 100644 --- a/core/adapters/random.go +++ b/core/adapters/random.go @@ -4,8 +4,8 @@ import ( "crypto/rand" "math/big" - "github.com/smartcontractkit/chainlink/core/store" - "github.com/smartcontractkit/chainlink/core/store/models" + "chainlink/core/store" + "chainlink/core/store/models" ) // Random adapter type holds no fields diff --git a/core/adapters/random_test.go b/core/adapters/random_test.go index 3da00b33455..cdcc11b4aa0 100644 --- a/core/adapters/random_test.go +++ b/core/adapters/random_test.go @@ -4,8 +4,9 @@ import ( "math/big" "testing" - "github.com/smartcontractkit/chainlink/core/adapters" - "github.com/smartcontractkit/chainlink/core/store/models" + "chainlink/core/adapters" + "chainlink/core/store/models" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/core/adapters/sleep.go b/core/adapters/sleep.go index ec885162e23..2746c40b220 100644 --- a/core/adapters/sleep.go +++ b/core/adapters/sleep.go @@ -3,10 +3,10 @@ package adapters import ( "time" - "github.com/smartcontractkit/chainlink/core/logger" - "github.com/smartcontractkit/chainlink/core/store" - "github.com/smartcontractkit/chainlink/core/store/models" - "github.com/smartcontractkit/chainlink/core/utils" + "chainlink/core/logger" + "chainlink/core/store" + "chainlink/core/store/models" + "chainlink/core/utils" ) // Sleep adapter allows a job to do nothing for some amount of wall time. diff --git a/core/adapters/sleep_test.go b/core/adapters/sleep_test.go index d391a687284..d8a48268309 100644 --- a/core/adapters/sleep_test.go +++ b/core/adapters/sleep_test.go @@ -4,9 +4,10 @@ import ( "encoding/json" "testing" - "github.com/smartcontractkit/chainlink/core/adapters" - "github.com/smartcontractkit/chainlink/core/internal/cltest" - "github.com/smartcontractkit/chainlink/core/store/models" + "chainlink/core/adapters" + "chainlink/core/internal/cltest" + "chainlink/core/store/models" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/core/adapters/wasm.go b/core/adapters/wasm.go index f0179d3c05e..bce4e33dc3f 100644 --- a/core/adapters/wasm.go +++ b/core/adapters/wasm.go @@ -5,8 +5,8 @@ package adapters import ( "fmt" - "github.com/smartcontractkit/chainlink/core/store" - "github.com/smartcontractkit/chainlink/core/store/models" + "chainlink/core/store" + "chainlink/core/store/models" ) // Wasm represents a wasm binary encoded as base64 or wasm encoded as text (a lisp like language). diff --git a/core/adapters/wasm_sgx.go b/core/adapters/wasm_sgx.go index 5064e2f8a71..9915826fd3e 100644 --- a/core/adapters/wasm_sgx.go +++ b/core/adapters/wasm_sgx.go @@ -14,8 +14,8 @@ import ( "fmt" "unsafe" - "github.com/smartcontractkit/chainlink/core/store" - "github.com/smartcontractkit/chainlink/core/store/models" + "chainlink/core/store" + "chainlink/core/store/models" ) // Wasm represents a wasm binary encoded as base64 or wasm encoded as text (a lisp like language). diff --git a/core/adapters/wasm_test.go b/core/adapters/wasm_test.go index 362c82f1436..18be079ff60 100644 --- a/core/adapters/wasm_test.go +++ b/core/adapters/wasm_test.go @@ -7,9 +7,10 @@ import ( "fmt" "testing" - "github.com/smartcontractkit/chainlink/core/adapters" - "github.com/smartcontractkit/chainlink/core/internal/cltest" - "github.com/smartcontractkit/chainlink/core/store/models" + "chainlink/core/adapters" + "chainlink/core/internal/cltest" + "chainlink/core/store/models" + "github.com/stretchr/testify/assert" ) diff --git a/core/cmd/app.go b/core/cmd/app.go index e0f3337b40c..ce703b7bb8a 100644 --- a/core/cmd/app.go +++ b/core/cmd/app.go @@ -4,7 +4,8 @@ import ( "fmt" "os" - "github.com/smartcontractkit/chainlink/core/store" + "chainlink/core/store" + "github.com/urfave/cli" ) @@ -104,8 +105,8 @@ func NewApp(client *Client) *cli.App { Usage: "Commands for the node's configuration", Subcommands: []cli.Command{ { - Name: "list", - Usage: "Show the node's environment variables", + Name: "list", + Usage: "Show the node's environment variables", Action: client.GetConfiguration, }, { diff --git a/core/cmd/client.go b/core/cmd/client.go index 4ef157dc029..987bc414790 100644 --- a/core/cmd/client.go +++ b/core/cmd/client.go @@ -13,13 +13,14 @@ import ( "strings" "time" + "chainlink/core/logger" + "chainlink/core/services" + "chainlink/core/store" + "chainlink/core/store/models" + "chainlink/core/store/orm" + "chainlink/core/web" + "github.com/gin-gonic/gin" - "github.com/smartcontractkit/chainlink/core/logger" - "github.com/smartcontractkit/chainlink/core/services" - "github.com/smartcontractkit/chainlink/core/store" - "github.com/smartcontractkit/chainlink/core/store/models" - "github.com/smartcontractkit/chainlink/core/store/orm" - "github.com/smartcontractkit/chainlink/core/web" clipkg "github.com/urfave/cli" "go.uber.org/multierr" "golang.org/x/sync/errgroup" diff --git a/core/cmd/client_test.go b/core/cmd/client_test.go index e3114526296..04f2d6ef8ef 100644 --- a/core/cmd/client_test.go +++ b/core/cmd/client_test.go @@ -3,10 +3,11 @@ package cmd_test import ( "testing" - "github.com/smartcontractkit/chainlink/core/cmd" - "github.com/smartcontractkit/chainlink/core/internal/cltest" - "github.com/smartcontractkit/chainlink/core/store/models" - "github.com/smartcontractkit/chainlink/core/store/orm" + "chainlink/core/cmd" + "chainlink/core/internal/cltest" + "chainlink/core/store/models" + "chainlink/core/store/orm" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/core/cmd/enclave.go b/core/cmd/enclave.go index 1a4f952cdc9..56e27fbd1ac 100644 --- a/core/cmd/enclave.go +++ b/core/cmd/enclave.go @@ -3,7 +3,7 @@ package cmd import ( - "github.com/smartcontractkit/chainlink/core/logger" + "chainlink/core/logger" ) // InitEnclave is a stub in non SGX enabled builds. diff --git a/core/cmd/enclave_sgx.go b/core/cmd/enclave_sgx.go index bc40c277624..0d9b0444b1a 100644 --- a/core/cmd/enclave_sgx.go +++ b/core/cmd/enclave_sgx.go @@ -2,7 +2,7 @@ package cmd -import "github.com/smartcontractkit/chainlink/core/logger" +import "chainlink/core/logger" // InitEnclave initialized the SGX enclave for use by adapters func InitEnclave() error { diff --git a/core/cmd/key_store_authenticator.go b/core/cmd/key_store_authenticator.go index 3de97c32f5c..00b16b67e2d 100644 --- a/core/cmd/key_store_authenticator.go +++ b/core/cmd/key_store_authenticator.go @@ -4,7 +4,7 @@ import ( "errors" "fmt" - "github.com/smartcontractkit/chainlink/core/store" + "chainlink/core/store" ) // KeyStoreAuthenticator implements the Authenticate method for the store and diff --git a/core/cmd/key_store_authenticator_test.go b/core/cmd/key_store_authenticator_test.go index fcd95b1c95b..f3dfc7b55ed 100644 --- a/core/cmd/key_store_authenticator_test.go +++ b/core/cmd/key_store_authenticator_test.go @@ -3,8 +3,9 @@ package cmd_test import ( "testing" - "github.com/smartcontractkit/chainlink/core/cmd" - "github.com/smartcontractkit/chainlink/core/internal/cltest" + "chainlink/core/cmd" + "chainlink/core/internal/cltest" + "github.com/stretchr/testify/assert" ) diff --git a/core/cmd/local_client.go b/core/cmd/local_client.go index b82360a67b3..cef1ee5aa93 100644 --- a/core/cmd/local_client.go +++ b/core/cmd/local_client.go @@ -8,13 +8,14 @@ import ( "os" "strings" - "github.com/smartcontractkit/chainlink/core/logger" - "github.com/smartcontractkit/chainlink/core/services" - strpkg "github.com/smartcontractkit/chainlink/core/store" - "github.com/smartcontractkit/chainlink/core/store/models" - "github.com/smartcontractkit/chainlink/core/store/orm" - "github.com/smartcontractkit/chainlink/core/store/presenters" - "github.com/smartcontractkit/chainlink/core/utils" + "chainlink/core/logger" + "chainlink/core/services" + strpkg "chainlink/core/store" + "chainlink/core/store/models" + "chainlink/core/store/orm" + "chainlink/core/store/presenters" + "chainlink/core/utils" + clipkg "github.com/urfave/cli" "go.uber.org/zap/zapcore" ) diff --git a/core/cmd/local_client_test.go b/core/cmd/local_client_test.go index e85129bf959..71639170637 100644 --- a/core/cmd/local_client_test.go +++ b/core/cmd/local_client_test.go @@ -8,12 +8,13 @@ import ( "testing" "time" - "github.com/smartcontractkit/chainlink/core/cmd" - "github.com/smartcontractkit/chainlink/core/internal/cltest" - "github.com/smartcontractkit/chainlink/core/internal/mocks" - "github.com/smartcontractkit/chainlink/core/logger" - strpkg "github.com/smartcontractkit/chainlink/core/store" - "github.com/smartcontractkit/chainlink/core/store/orm" + "chainlink/core/cmd" + "chainlink/core/internal/cltest" + "chainlink/core/internal/mocks" + "chainlink/core/logger" + strpkg "chainlink/core/store" + "chainlink/core/store/orm" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/urfave/cli" diff --git a/core/cmd/prompter.go b/core/cmd/prompter.go index e2d5fefcf9a..defcd04a201 100644 --- a/core/cmd/prompter.go +++ b/core/cmd/prompter.go @@ -8,7 +8,8 @@ import ( "strings" "syscall" - "github.com/smartcontractkit/chainlink/core/logger" + "chainlink/core/logger" + "golang.org/x/crypto/ssh/terminal" ) diff --git a/core/cmd/remote_client.go b/core/cmd/remote_client.go index 0bc8f7f4bf3..25a6f37dc1b 100644 --- a/core/cmd/remote_client.go +++ b/core/cmd/remote_client.go @@ -11,15 +11,16 @@ import ( "os" "strconv" + "chainlink/core/store/assets" + "chainlink/core/store/models" + "chainlink/core/store/presenters" + "chainlink/core/utils" + "chainlink/core/web" + "github.com/ethereum/go-ethereum/common" "github.com/manyminds/api2go/jsonapi" homedir "github.com/mitchellh/go-homedir" "github.com/pkg/errors" - "github.com/smartcontractkit/chainlink/core/store/assets" - "github.com/smartcontractkit/chainlink/core/store/models" - "github.com/smartcontractkit/chainlink/core/store/presenters" - "github.com/smartcontractkit/chainlink/core/utils" - "github.com/smartcontractkit/chainlink/core/web" "github.com/tidwall/gjson" clipkg "github.com/urfave/cli" "go.uber.org/multierr" diff --git a/core/cmd/remote_client_test.go b/core/cmd/remote_client_test.go index 63fe40e421a..4bb93550f3e 100644 --- a/core/cmd/remote_client_test.go +++ b/core/cmd/remote_client_test.go @@ -9,12 +9,13 @@ import ( "testing" "time" + "chainlink/core/cmd" + "chainlink/core/internal/cltest" + "chainlink/core/store/models" + "chainlink/core/store/presenters" + "chainlink/core/utils" + "github.com/ethereum/go-ethereum/common" - "github.com/smartcontractkit/chainlink/core/cmd" - "github.com/smartcontractkit/chainlink/core/internal/cltest" - "github.com/smartcontractkit/chainlink/core/store/models" - "github.com/smartcontractkit/chainlink/core/store/presenters" - "github.com/smartcontractkit/chainlink/core/utils" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/urfave/cli" diff --git a/core/cmd/renderer.go b/core/cmd/renderer.go index 56e3e98d425..53a2f331b5c 100644 --- a/core/cmd/renderer.go +++ b/core/cmd/renderer.go @@ -6,13 +6,14 @@ import ( "reflect" "strconv" + "chainlink/core/logger" + "chainlink/core/store/models" + "chainlink/core/store/orm" + "chainlink/core/store/presenters" + "chainlink/core/utils" + "chainlink/core/web" + "github.com/olekukonko/tablewriter" - "github.com/smartcontractkit/chainlink/core/logger" - "github.com/smartcontractkit/chainlink/core/store/models" - "github.com/smartcontractkit/chainlink/core/store/orm" - "github.com/smartcontractkit/chainlink/core/store/presenters" - "github.com/smartcontractkit/chainlink/core/utils" - "github.com/smartcontractkit/chainlink/core/web" ) // Renderer implements the Render method. diff --git a/core/cmd/renderer_test.go b/core/cmd/renderer_test.go index 12f7a5f928a..5eacd1e45c3 100644 --- a/core/cmd/renderer_test.go +++ b/core/cmd/renderer_test.go @@ -8,11 +8,12 @@ import ( "regexp" "testing" - "github.com/smartcontractkit/chainlink/core/cmd" - "github.com/smartcontractkit/chainlink/core/internal/cltest" - "github.com/smartcontractkit/chainlink/core/store/models" - "github.com/smartcontractkit/chainlink/core/store/presenters" - "github.com/smartcontractkit/chainlink/core/web" + "chainlink/core/cmd" + "chainlink/core/internal/cltest" + "chainlink/core/store/models" + "chainlink/core/store/presenters" + "chainlink/core/web" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/core/internal/cltest/cltest.go b/core/internal/cltest/cltest.go index 9635c279060..43a099550fc 100644 --- a/core/internal/cltest/cltest.go +++ b/core/internal/cltest/cltest.go @@ -17,6 +17,17 @@ import ( "testing" "time" + "chainlink/core/cmd" + "chainlink/core/logger" + "chainlink/core/services" + strpkg "chainlink/core/store" + "chainlink/core/store/assets" + "chainlink/core/store/models" + "chainlink/core/store/orm" + "chainlink/core/store/presenters" + "chainlink/core/utils" + "chainlink/core/web" + "github.com/ethereum/go-ethereum/common" "github.com/gin-gonic/gin" "github.com/gobuffalo/packr" @@ -25,16 +36,6 @@ import ( "github.com/gorilla/websocket" "github.com/manyminds/api2go/jsonapi" "github.com/onsi/gomega" - "github.com/smartcontractkit/chainlink/core/cmd" - "github.com/smartcontractkit/chainlink/core/logger" - "github.com/smartcontractkit/chainlink/core/services" - strpkg "github.com/smartcontractkit/chainlink/core/store" - "github.com/smartcontractkit/chainlink/core/store/assets" - "github.com/smartcontractkit/chainlink/core/store/models" - "github.com/smartcontractkit/chainlink/core/store/orm" - "github.com/smartcontractkit/chainlink/core/store/presenters" - "github.com/smartcontractkit/chainlink/core/utils" - "github.com/smartcontractkit/chainlink/core/web" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/tidwall/gjson" diff --git a/core/internal/cltest/factories.go b/core/internal/cltest/factories.go index d09bf57bb08..8f2d49c2561 100644 --- a/core/internal/cltest/factories.go +++ b/core/internal/cltest/factories.go @@ -14,16 +14,17 @@ import ( "testing" "time" + "chainlink/core/adapters" + "chainlink/core/logger" + "chainlink/core/store" + strpkg "chainlink/core/store" + "chainlink/core/store/assets" + "chainlink/core/store/models" + "chainlink/core/utils" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/core/types" - "github.com/smartcontractkit/chainlink/core/adapters" - "github.com/smartcontractkit/chainlink/core/logger" - "github.com/smartcontractkit/chainlink/core/store" - strpkg "github.com/smartcontractkit/chainlink/core/store" - "github.com/smartcontractkit/chainlink/core/store/assets" - "github.com/smartcontractkit/chainlink/core/store/models" - "github.com/smartcontractkit/chainlink/core/utils" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/tidwall/sjson" diff --git a/core/internal/cltest/fixtures.go b/core/internal/cltest/fixtures.go index 78bad62bfb2..ad7a1e63212 100644 --- a/core/internal/cltest/fixtures.go +++ b/core/internal/cltest/fixtures.go @@ -4,7 +4,8 @@ import ( "encoding/json" "testing" - "github.com/smartcontractkit/chainlink/core/store/models" + "chainlink/core/store/models" + "github.com/stretchr/testify/require" "github.com/tidwall/gjson" ) diff --git a/core/internal/cltest/mocks.go b/core/internal/cltest/mocks.go index f9b698d50bd..85d6fdddd2f 100644 --- a/core/internal/cltest/mocks.go +++ b/core/internal/cltest/mocks.go @@ -18,14 +18,15 @@ import ( "testing" "time" + "chainlink/core/cmd" + "chainlink/core/logger" + "chainlink/core/services" + "chainlink/core/store" + "chainlink/core/store/models" + "chainlink/core/store/orm" + "github.com/ethereum/go-ethereum/common" "github.com/onsi/gomega" - "github.com/smartcontractkit/chainlink/core/cmd" - "github.com/smartcontractkit/chainlink/core/logger" - "github.com/smartcontractkit/chainlink/core/services" - "github.com/smartcontractkit/chainlink/core/store" - "github.com/smartcontractkit/chainlink/core/store/models" - "github.com/smartcontractkit/chainlink/core/store/orm" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/core/internal/cltest/postgres.go b/core/internal/cltest/postgres.go index 698e2d75069..979bb4821ed 100644 --- a/core/internal/cltest/postgres.go +++ b/core/internal/cltest/postgres.go @@ -6,9 +6,9 @@ import ( "net/url" "testing" - "github.com/smartcontractkit/chainlink/core/store/dbutil" - "github.com/smartcontractkit/chainlink/core/store/models" - "github.com/smartcontractkit/chainlink/core/store/orm" + "chainlink/core/store/dbutil" + "chainlink/core/store/models" + "chainlink/core/store/orm" ) // PrepareTestDB prepares the database to run tests, functionality varies diff --git a/core/internal/features_test.go b/core/internal/features_test.go index b13ed66b4b1..4f2d9af136e 100644 --- a/core/internal/features_test.go +++ b/core/internal/features_test.go @@ -11,11 +11,12 @@ import ( "testing" "time" + "chainlink/core/internal/cltest" + "chainlink/core/store/models" + "chainlink/core/utils" + "chainlink/core/web" + "github.com/ethereum/go-ethereum/common" - "github.com/smartcontractkit/chainlink/core/internal/cltest" - "github.com/smartcontractkit/chainlink/core/store/models" - "github.com/smartcontractkit/chainlink/core/utils" - "github.com/smartcontractkit/chainlink/core/web" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/tidwall/gjson" diff --git a/core/internal/mocks/application.go b/core/internal/mocks/application.go index 66eece42137..2ec096b259e 100644 --- a/core/internal/mocks/application.go +++ b/core/internal/mocks/application.go @@ -4,10 +4,10 @@ package mocks import big "math/big" import mock "github.com/stretchr/testify/mock" -import models "github.com/smartcontractkit/chainlink/core/store/models" +import models "chainlink/core/store/models" import packr "github.com/gobuffalo/packr" -import store "github.com/smartcontractkit/chainlink/core/store" +import store "chainlink/core/store" // Application is an autogenerated mock type for the Application type type Application struct { diff --git a/core/internal/mocks/eth_client_mocks.go b/core/internal/mocks/eth_client_mocks.go index fed8d3d5eca..d37d17ec20f 100644 --- a/core/internal/mocks/eth_client_mocks.go +++ b/core/internal/mocks/eth_client_mocks.go @@ -1,17 +1,18 @@ // Code generated by MockGen. DO NOT EDIT. -// Source: github.com/smartcontractkit/chainlink/core/store (interfaces: EthClient) +// Source: chainlink/core/store (interfaces: EthClient) // Package mocks is a generated GoMock package. package mocks import ( + assets "chainlink/core/store/assets" + models "chainlink/core/store/models" + big "math/big" + reflect "reflect" + go_ethereum "github.com/ethereum/go-ethereum" common "github.com/ethereum/go-ethereum/common" gomock "github.com/golang/mock/gomock" - assets "github.com/smartcontractkit/chainlink/core/store/assets" - models "github.com/smartcontractkit/chainlink/core/store/models" - big "math/big" - reflect "reflect" ) // MockEthClient is a mock of EthClient interface diff --git a/core/internal/mocks/run_executor.go b/core/internal/mocks/run_executor.go index 977fa6143f8..e4d145ae5fd 100644 --- a/core/internal/mocks/run_executor.go +++ b/core/internal/mocks/run_executor.go @@ -3,7 +3,7 @@ package mocks import mock "github.com/stretchr/testify/mock" -import models "github.com/smartcontractkit/chainlink/core/store/models" +import models "chainlink/core/store/models" // RunExecutor is an autogenerated mock type for the RunExecutor type type RunExecutor struct { diff --git a/core/internal/mocks/run_manager.go b/core/internal/mocks/run_manager.go index 0503bc9e9fb..15acd87565f 100644 --- a/core/internal/mocks/run_manager.go +++ b/core/internal/mocks/run_manager.go @@ -4,7 +4,7 @@ package mocks import big "math/big" import mock "github.com/stretchr/testify/mock" -import models "github.com/smartcontractkit/chainlink/core/store/models" +import models "chainlink/core/store/models" // RunManager is an autogenerated mock type for the RunManager type type RunManager struct { diff --git a/core/internal/mocks/run_queue.go b/core/internal/mocks/run_queue.go index a96f3f315d7..c03dd79a462 100644 --- a/core/internal/mocks/run_queue.go +++ b/core/internal/mocks/run_queue.go @@ -3,7 +3,7 @@ package mocks import mock "github.com/stretchr/testify/mock" -import models "github.com/smartcontractkit/chainlink/core/store/models" +import models "chainlink/core/store/models" // RunQueue is an autogenerated mock type for the RunQueue type type RunQueue struct { diff --git a/core/internal/mocks/tx_manager_mocks.go b/core/internal/mocks/tx_manager_mocks.go index f8f00e07753..984b1c34ba0 100644 --- a/core/internal/mocks/tx_manager_mocks.go +++ b/core/internal/mocks/tx_manager_mocks.go @@ -1,20 +1,21 @@ // Code generated by MockGen. DO NOT EDIT. -// Source: github.com/smartcontractkit/chainlink/core/store (interfaces: TxManager) +// Source: chainlink/core/store (interfaces: TxManager) // Package mocks is a generated GoMock package. package mocks import ( + store "chainlink/core/store" + assets "chainlink/core/store/assets" + models "chainlink/core/store/models" + big "math/big" + reflect "reflect" + go_ethereum "github.com/ethereum/go-ethereum" accounts "github.com/ethereum/go-ethereum/accounts" common "github.com/ethereum/go-ethereum/common" gomock "github.com/golang/mock/gomock" - store "github.com/smartcontractkit/chainlink/core/store" - assets "github.com/smartcontractkit/chainlink/core/store/assets" - models "github.com/smartcontractkit/chainlink/core/store/models" null_v3 "gopkg.in/guregu/null.v3" - big "math/big" - reflect "reflect" ) // MockTxManager is a mock of TxManager interface diff --git a/core/logger/prettyconsole.go b/core/logger/prettyconsole.go index ed58f237a75..d6413f4ffd6 100644 --- a/core/logger/prettyconsole.go +++ b/core/logger/prettyconsole.go @@ -7,8 +7,9 @@ import ( "strings" "time" + "chainlink/core/utils" + "github.com/fatih/color" - "github.com/smartcontractkit/chainlink/core/utils" "github.com/tidwall/gjson" "go.uber.org/zap" ) diff --git a/core/main.go b/core/main.go index 730287c7d7a..dfcfa756470 100644 --- a/core/main.go +++ b/core/main.go @@ -4,9 +4,9 @@ import ( "os" "time" - "github.com/smartcontractkit/chainlink/core/cmd" - "github.com/smartcontractkit/chainlink/core/logger" - "github.com/smartcontractkit/chainlink/core/store/orm" + "chainlink/core/cmd" + "chainlink/core/logger" + "chainlink/core/store/orm" ) func init() { diff --git a/core/main_test.go b/core/main_test.go index 270433983b7..d84f1d4afe4 100644 --- a/core/main_test.go +++ b/core/main_test.go @@ -6,8 +6,8 @@ import ( "io/ioutil" "testing" - "github.com/smartcontractkit/chainlink/core/cmd" - "github.com/smartcontractkit/chainlink/core/internal/cltest" + "chainlink/core/cmd" + "chainlink/core/internal/cltest" ) func ExampleRun() { diff --git a/core/services/application.go b/core/services/application.go index 71e275be45b..f58a61cfe90 100644 --- a/core/services/application.go +++ b/core/services/application.go @@ -6,12 +6,13 @@ import ( "sync" "syscall" + "chainlink/core/logger" + "chainlink/core/store" + strpkg "chainlink/core/store" + "chainlink/core/store/models" + "chainlink/core/store/orm" + "github.com/gobuffalo/packr" - "github.com/smartcontractkit/chainlink/core/logger" - "github.com/smartcontractkit/chainlink/core/store" - strpkg "github.com/smartcontractkit/chainlink/core/store" - "github.com/smartcontractkit/chainlink/core/store/models" - "github.com/smartcontractkit/chainlink/core/store/orm" "go.uber.org/multierr" ) @@ -62,13 +63,13 @@ func NewApplication(config *orm.Config, onConnectCallbacks ...func(Application)) pendingConnectionResumer := newPendingConnectionResumer(runManager) app := &ChainlinkApplication{ - JobSubscriber: jobSubscriber, - RunManager: runManager, - RunQueue: runQueue, - Scheduler: NewScheduler(store, runManager), - Store: store, - SessionReaper: NewStoreReaper(store), - Exiter: os.Exit, + JobSubscriber: jobSubscriber, + RunManager: runManager, + RunQueue: runQueue, + Scheduler: NewScheduler(store, runManager), + Store: store, + SessionReaper: NewStoreReaper(store), + Exiter: os.Exit, pendingConnectionResumer: pendingConnectionResumer, } diff --git a/core/services/application_test.go b/core/services/application_test.go index ffa12374b4f..197cf0fb97d 100644 --- a/core/services/application_test.go +++ b/core/services/application_test.go @@ -6,12 +6,13 @@ import ( "syscall" "testing" + "chainlink/core/internal/cltest" + "chainlink/core/services/mock_services" + "chainlink/core/store/models" + "chainlink/core/utils" + "github.com/golang/mock/gomock" "github.com/onsi/gomega" - "github.com/smartcontractkit/chainlink/core/internal/cltest" - "github.com/smartcontractkit/chainlink/core/services/mock_services" - "github.com/smartcontractkit/chainlink/core/store/models" - "github.com/smartcontractkit/chainlink/core/utils" "github.com/stretchr/testify/require" "github.com/tevino/abool" ) diff --git a/core/services/head_trackable_callback.go b/core/services/head_trackable_callback.go index d99b4688e8f..4b6d6b379ea 100644 --- a/core/services/head_trackable_callback.go +++ b/core/services/head_trackable_callback.go @@ -1,6 +1,6 @@ package services -import "github.com/smartcontractkit/chainlink/core/store/models" +import "chainlink/core/store/models" // headTrackableCallback is a simple wrapper around an On Connect callback type headTrackableCallback struct { diff --git a/core/services/head_tracker.go b/core/services/head_tracker.go index 90cac77f7d8..f7e17e5619e 100644 --- a/core/services/head_tracker.go +++ b/core/services/head_tracker.go @@ -5,12 +5,13 @@ import ( "sync" "time" + "chainlink/core/logger" + strpkg "chainlink/core/store" + "chainlink/core/store/models" + "chainlink/core/store/presenters" + "chainlink/core/utils" + "github.com/pkg/errors" - "github.com/smartcontractkit/chainlink/core/logger" - strpkg "github.com/smartcontractkit/chainlink/core/store" - "github.com/smartcontractkit/chainlink/core/store/models" - "github.com/smartcontractkit/chainlink/core/store/presenters" - "github.com/smartcontractkit/chainlink/core/utils" ) // HeadTracker holds and stores the latest block number experienced by this particular node diff --git a/core/services/head_tracker_test.go b/core/services/head_tracker_test.go index f0475d09f5d..eaa1d3a6b4a 100644 --- a/core/services/head_tracker_test.go +++ b/core/services/head_tracker_test.go @@ -6,14 +6,15 @@ import ( "sync/atomic" "testing" + "chainlink/core/internal/cltest" + "chainlink/core/internal/mocks" + "chainlink/core/services" + strpkg "chainlink/core/store" + "chainlink/core/store/models" + "github.com/ethereum/go-ethereum/common/hexutil" "github.com/golang/mock/gomock" "github.com/onsi/gomega" - "github.com/smartcontractkit/chainlink/core/internal/cltest" - "github.com/smartcontractkit/chainlink/core/internal/mocks" - "github.com/smartcontractkit/chainlink/core/services" - strpkg "github.com/smartcontractkit/chainlink/core/store" - "github.com/smartcontractkit/chainlink/core/store/models" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/core/services/job_subscriber.go b/core/services/job_subscriber.go index 18e9b991329..b9cf2375114 100644 --- a/core/services/job_subscriber.go +++ b/core/services/job_subscriber.go @@ -4,9 +4,10 @@ import ( "fmt" "sync" - "github.com/smartcontractkit/chainlink/core/logger" - "github.com/smartcontractkit/chainlink/core/store" - "github.com/smartcontractkit/chainlink/core/store/models" + "chainlink/core/logger" + "chainlink/core/store" + "chainlink/core/store/models" + "go.uber.org/multierr" ) diff --git a/core/services/job_subscriber_test.go b/core/services/job_subscriber_test.go index 9b08dc7ce17..02f074c694a 100644 --- a/core/services/job_subscriber_test.go +++ b/core/services/job_subscriber_test.go @@ -4,10 +4,11 @@ import ( "math/big" "testing" - "github.com/smartcontractkit/chainlink/core/internal/cltest" - "github.com/smartcontractkit/chainlink/core/internal/mocks" - "github.com/smartcontractkit/chainlink/core/services" - "github.com/smartcontractkit/chainlink/core/store/models" + "chainlink/core/internal/cltest" + "chainlink/core/internal/mocks" + "chainlink/core/services" + "chainlink/core/store/models" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/core/services/mock_services/job_subscriber.go b/core/services/mock_services/job_subscriber.go index cb7de255f38..5a2617bf580 100644 --- a/core/services/mock_services/job_subscriber.go +++ b/core/services/mock_services/job_subscriber.go @@ -1,5 +1,5 @@ // Code generated by MockGen. DO NOT EDIT. -// Source: github.com/smartcontractkit/chainlink/core/services (interfaces: JobSubscriber) +// Source: chainlink/core/services (interfaces: JobSubscriber) // Package mock_services is a generated GoMock package. package mock_services @@ -7,8 +7,9 @@ package mock_services import ( reflect "reflect" + models "chainlink/core/store/models" + gomock "github.com/golang/mock/gomock" - models "github.com/smartcontractkit/chainlink/core/store/models" ) // MockJobSubscriber is a mock of JobSubscriber interface diff --git a/core/services/reaper.go b/core/services/reaper.go index 711461fb89c..409710eb982 100644 --- a/core/services/reaper.go +++ b/core/services/reaper.go @@ -3,9 +3,9 @@ package services import ( "time" - "github.com/smartcontractkit/chainlink/core/logger" - "github.com/smartcontractkit/chainlink/core/store" - "github.com/smartcontractkit/chainlink/core/store/orm" + "chainlink/core/logger" + "chainlink/core/store" + "chainlink/core/store/orm" ) type storeReaper struct { diff --git a/core/services/reaper_test.go b/core/services/reaper_test.go index 1329823c283..5a2a1caa7bf 100644 --- a/core/services/reaper_test.go +++ b/core/services/reaper_test.go @@ -4,10 +4,11 @@ import ( "testing" "time" + "chainlink/core/internal/cltest" + "chainlink/core/services" + "chainlink/core/store/models" + "github.com/onsi/gomega" - "github.com/smartcontractkit/chainlink/core/internal/cltest" - "github.com/smartcontractkit/chainlink/core/services" - "github.com/smartcontractkit/chainlink/core/store/models" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/core/services/run_executor.go b/core/services/run_executor.go index d05e0bc6a26..270816d4b34 100644 --- a/core/services/run_executor.go +++ b/core/services/run_executor.go @@ -4,12 +4,13 @@ import ( "fmt" "time" + "chainlink/core/adapters" + "chainlink/core/logger" + "chainlink/core/store" + "chainlink/core/store/models" + "chainlink/core/store/orm" + "github.com/pkg/errors" - "github.com/smartcontractkit/chainlink/core/adapters" - "github.com/smartcontractkit/chainlink/core/logger" - "github.com/smartcontractkit/chainlink/core/store" - "github.com/smartcontractkit/chainlink/core/store/models" - "github.com/smartcontractkit/chainlink/core/store/orm" ) //go:generate mockery -name RunExecutor -output ../internal/mocks/ -case=underscore diff --git a/core/services/run_executor_test.go b/core/services/run_executor_test.go index 34122cdf986..f6a9492ca88 100644 --- a/core/services/run_executor_test.go +++ b/core/services/run_executor_test.go @@ -4,11 +4,12 @@ import ( "testing" "time" - "github.com/smartcontractkit/chainlink/core/internal/cltest" - "github.com/smartcontractkit/chainlink/core/internal/mocks" - "github.com/smartcontractkit/chainlink/core/services" - "github.com/smartcontractkit/chainlink/core/store/assets" - "github.com/smartcontractkit/chainlink/core/store/models" + "chainlink/core/internal/cltest" + "chainlink/core/internal/mocks" + "chainlink/core/services" + "chainlink/core/store/assets" + "chainlink/core/store/models" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/core/services/run_manager.go b/core/services/run_manager.go index ba3cdc612f2..65907e4a096 100644 --- a/core/services/run_manager.go +++ b/core/services/run_manager.go @@ -4,15 +4,16 @@ import ( "fmt" "math/big" + "chainlink/core/adapters" + "chainlink/core/logger" + clnull "chainlink/core/null" + "chainlink/core/store" + "chainlink/core/store/assets" + "chainlink/core/store/models" + "chainlink/core/store/orm" + "chainlink/core/utils" + "github.com/pkg/errors" - "github.com/smartcontractkit/chainlink/core/adapters" - "github.com/smartcontractkit/chainlink/core/logger" - clnull "github.com/smartcontractkit/chainlink/core/null" - "github.com/smartcontractkit/chainlink/core/store" - "github.com/smartcontractkit/chainlink/core/store/assets" - "github.com/smartcontractkit/chainlink/core/store/models" - "github.com/smartcontractkit/chainlink/core/store/orm" - "github.com/smartcontractkit/chainlink/core/utils" ) // RecurringScheduleJobError contains the field for the error message. @@ -319,7 +320,7 @@ func (jm *runManager) ResumePending( // // To recap: This must run before anything else writes job run status to the db, // ie. tries to run a job. -// https://github.com/smartcontractkit/chainlink/pull/807 +// https://chainlink/pull/807 func (jm *runManager) ResumeAllInProgress() error { return jm.orm.UnscopedJobRunsWithStatus(jm.runQueue.Run, models.RunStatusInProgress, models.RunStatusPendingSleep) } diff --git a/core/services/run_manager_test.go b/core/services/run_manager_test.go index 0598e9c8c34..538179c9bfe 100644 --- a/core/services/run_manager_test.go +++ b/core/services/run_manager_test.go @@ -5,13 +5,14 @@ import ( "testing" "time" + "chainlink/core/adapters" + "chainlink/core/internal/cltest" + "chainlink/core/internal/mocks" + clnull "chainlink/core/null" + "chainlink/core/services" + "chainlink/core/store/models" + "github.com/ethereum/go-ethereum/common" - "github.com/smartcontractkit/chainlink/core/adapters" - "github.com/smartcontractkit/chainlink/core/internal/cltest" - "github.com/smartcontractkit/chainlink/core/internal/mocks" - clnull "github.com/smartcontractkit/chainlink/core/null" - "github.com/smartcontractkit/chainlink/core/services" - "github.com/smartcontractkit/chainlink/core/store/models" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" diff --git a/core/services/run_queue.go b/core/services/run_queue.go index 520c0d88c81..c8542169357 100644 --- a/core/services/run_queue.go +++ b/core/services/run_queue.go @@ -5,8 +5,8 @@ import ( "sync" "time" - "github.com/smartcontractkit/chainlink/core/logger" - "github.com/smartcontractkit/chainlink/core/store/models" + "chainlink/core/logger" + "chainlink/core/store/models" ) //go:generate mockery -name RunQueue -output ../internal/mocks/ -case=underscore diff --git a/core/services/run_queue_test.go b/core/services/run_queue_test.go index 9726f362395..0312c1c9371 100644 --- a/core/services/run_queue_test.go +++ b/core/services/run_queue_test.go @@ -3,11 +3,12 @@ package services_test import ( "testing" + "chainlink/core/internal/cltest" + "chainlink/core/internal/mocks" + "chainlink/core/services" + "chainlink/core/store/models" + "github.com/onsi/gomega" - "github.com/smartcontractkit/chainlink/core/internal/cltest" - "github.com/smartcontractkit/chainlink/core/internal/mocks" - "github.com/smartcontractkit/chainlink/core/services" - "github.com/smartcontractkit/chainlink/core/store/models" "github.com/stretchr/testify/mock" ) diff --git a/core/services/runs.go b/core/services/runs.go index 72fa6f8260e..628c5c9777b 100644 --- a/core/services/runs.go +++ b/core/services/runs.go @@ -4,12 +4,12 @@ import ( "fmt" "math/big" - "github.com/smartcontractkit/chainlink/core/logger" - clnull "github.com/smartcontractkit/chainlink/core/null" - "github.com/smartcontractkit/chainlink/core/store" - "github.com/smartcontractkit/chainlink/core/store/assets" - "github.com/smartcontractkit/chainlink/core/store/models" - "github.com/smartcontractkit/chainlink/core/utils" + "chainlink/core/logger" + clnull "chainlink/core/null" + "chainlink/core/store" + "chainlink/core/store/assets" + "chainlink/core/store/models" + "chainlink/core/utils" ) // MeetsMinimumPayment is a helper that returns true if jobrun received diff --git a/core/services/scheduler.go b/core/services/scheduler.go index 309fde94c64..2a7f3b9778a 100644 --- a/core/services/scheduler.go +++ b/core/services/scheduler.go @@ -4,11 +4,12 @@ import ( "errors" "sync" + "chainlink/core/logger" + "chainlink/core/store" + "chainlink/core/store/models" + "chainlink/core/utils" + "github.com/mrwonko/cron" - "github.com/smartcontractkit/chainlink/core/logger" - "github.com/smartcontractkit/chainlink/core/store" - "github.com/smartcontractkit/chainlink/core/store/models" - "github.com/smartcontractkit/chainlink/core/utils" ) // Scheduler contains fields for Recurring and OneTime for occurrences, diff --git a/core/services/scheduler_test.go b/core/services/scheduler_test.go index e363dfcadde..fbbd2062035 100644 --- a/core/services/scheduler_test.go +++ b/core/services/scheduler_test.go @@ -4,9 +4,10 @@ import ( "testing" "time" - "github.com/smartcontractkit/chainlink/core/internal/cltest" - "github.com/smartcontractkit/chainlink/core/internal/mocks" - "github.com/smartcontractkit/chainlink/core/services" + "chainlink/core/internal/cltest" + "chainlink/core/internal/mocks" + "chainlink/core/services" + "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" ) diff --git a/core/services/signatures/ethdss/ethdss.go b/core/services/signatures/ethdss/ethdss.go index 4764c2837dd..52bc2bcbf62 100644 --- a/core/services/signatures/ethdss/ethdss.go +++ b/core/services/signatures/ethdss/ethdss.go @@ -23,8 +23,9 @@ import ( "errors" "math/big" - "github.com/smartcontractkit/chainlink/core/services/signatures/ethschnorr" - "github.com/smartcontractkit/chainlink/core/services/signatures/secp256k1" + "chainlink/core/services/signatures/ethschnorr" + "chainlink/core/services/signatures/secp256k1" + "go.dedis.ch/kyber/v3" "go.dedis.ch/kyber/v3/share" ) diff --git a/core/services/signatures/ethdss/ethdss_test.go b/core/services/signatures/ethdss/ethdss_test.go index ec344053669..6e51759708f 100644 --- a/core/services/signatures/ethdss/ethdss_test.go +++ b/core/services/signatures/ethdss/ethdss_test.go @@ -9,9 +9,9 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/smartcontractkit/chainlink/core/services/signatures/cryptotest" - "github.com/smartcontractkit/chainlink/core/services/signatures/ethschnorr" - "github.com/smartcontractkit/chainlink/core/services/signatures/secp256k1" + "chainlink/core/services/signatures/cryptotest" + "chainlink/core/services/signatures/ethschnorr" + "chainlink/core/services/signatures/secp256k1" "go.dedis.ch/kyber/v3" dkg "go.dedis.ch/kyber/v3/share/dkg/rabin" diff --git a/core/services/signatures/ethschnorr/ethschnorr.go b/core/services/signatures/ethschnorr/ethschnorr.go index 30ce38efc9f..3bd1e29a560 100644 --- a/core/services/signatures/ethschnorr/ethschnorr.go +++ b/core/services/signatures/ethschnorr/ethschnorr.go @@ -19,7 +19,7 @@ import ( "fmt" "math/big" - "github.com/smartcontractkit/chainlink/core/services/signatures/secp256k1" + "chainlink/core/services/signatures/secp256k1" "go.dedis.ch/kyber/v3" ) diff --git a/core/services/signatures/ethschnorr/ethschnorr_test.go b/core/services/signatures/ethschnorr/ethschnorr_test.go index 1004ecbc044..e2cf156c204 100644 --- a/core/services/signatures/ethschnorr/ethschnorr_test.go +++ b/core/services/signatures/ethschnorr/ethschnorr_test.go @@ -15,8 +15,8 @@ import ( "go.dedis.ch/kyber/v3" "go.dedis.ch/kyber/v3/group/curve25519" - "github.com/smartcontractkit/chainlink/core/services/signatures/cryptotest" - "github.com/smartcontractkit/chainlink/core/services/signatures/secp256k1" + "chainlink/core/services/signatures/cryptotest" + "chainlink/core/services/signatures/secp256k1" ) var numSignatures = 5 diff --git a/core/services/signatures/secp256k1/curve_test.go b/core/services/signatures/secp256k1/curve_test.go index 3c6b21257ed..e4802ea3d0e 100644 --- a/core/services/signatures/secp256k1/curve_test.go +++ b/core/services/signatures/secp256k1/curve_test.go @@ -1,8 +1,9 @@ package secp256k1 import ( - "github.com/stretchr/testify/require" "testing" + + "github.com/stretchr/testify/require" ) var group = &Secp256k1{} diff --git a/core/services/signatures/secp256k1/field_test.go b/core/services/signatures/secp256k1/field_test.go index 2839a3dbaa7..0a5cf4bcf9c 100644 --- a/core/services/signatures/secp256k1/field_test.go +++ b/core/services/signatures/secp256k1/field_test.go @@ -8,7 +8,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/smartcontractkit/chainlink/core/services/signatures/cryptotest" + "chainlink/core/services/signatures/cryptotest" ) var numFieldSamples = 10 diff --git a/core/services/signatures/secp256k1/point_test.go b/core/services/signatures/secp256k1/point_test.go index 020ff8a2c22..0dcaf7e8f6f 100644 --- a/core/services/signatures/secp256k1/point_test.go +++ b/core/services/signatures/secp256k1/point_test.go @@ -12,7 +12,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/smartcontractkit/chainlink/core/services/signatures/cryptotest" + "chainlink/core/services/signatures/cryptotest" ) var numPointSamples = 10 diff --git a/core/services/signatures/secp256k1/scalar_test.go b/core/services/signatures/secp256k1/scalar_test.go index b08dc01ad3f..dfa815cd4d9 100644 --- a/core/services/signatures/secp256k1/scalar_test.go +++ b/core/services/signatures/secp256k1/scalar_test.go @@ -12,7 +12,7 @@ import ( "go.dedis.ch/kyber/v3" "go.dedis.ch/kyber/v3/group/curve25519" - "github.com/smartcontractkit/chainlink/core/services/signatures/cryptotest" + "chainlink/core/services/signatures/cryptotest" ) var numScalarSamples = 10 diff --git a/core/services/subscription.go b/core/services/subscription.go index b2710cdcb5a..eec82cc6e89 100644 --- a/core/services/subscription.go +++ b/core/services/subscription.go @@ -5,13 +5,14 @@ import ( "math/big" "time" + "chainlink/core/logger" + strpkg "chainlink/core/store" + "chainlink/core/store/models" + "chainlink/core/store/presenters" + "chainlink/core/utils" + ethereum "github.com/ethereum/go-ethereum" "github.com/pkg/errors" - "github.com/smartcontractkit/chainlink/core/logger" - strpkg "github.com/smartcontractkit/chainlink/core/store" - "github.com/smartcontractkit/chainlink/core/store/models" - "github.com/smartcontractkit/chainlink/core/store/presenters" - "github.com/smartcontractkit/chainlink/core/utils" "go.uber.org/multierr" ) @@ -225,7 +226,7 @@ func (sub ManagedSubscription) Unsubscribe() { // timedUnsubscribe attempts to unsubscribe but aborts abruptly after a time delay // unblocking the application. This is an effort to mitigate the occasional // indefinite block described here from go-ethereum: -// https://github.com/smartcontractkit/chainlink/pull/600#issuecomment-426320971 +// https://chainlink/pull/600#issuecomment-426320971 func timedUnsubscribe(unsubscriber Unsubscriber) { unsubscribed := make(chan struct{}) go func() { diff --git a/core/services/subscription_test.go b/core/services/subscription_test.go index 7fd24d9f82e..0b8a0a2b523 100644 --- a/core/services/subscription_test.go +++ b/core/services/subscription_test.go @@ -10,14 +10,15 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/golang/mock/gomock" + "chainlink/core/internal/cltest" + "chainlink/core/internal/mocks" + "chainlink/core/services" + strpkg "chainlink/core/store" + "chainlink/core/store/models" + "chainlink/core/store/orm" + "github.com/ethereum/go-ethereum/common/hexutil" "github.com/onsi/gomega" - "github.com/smartcontractkit/chainlink/core/internal/cltest" - "github.com/smartcontractkit/chainlink/core/internal/mocks" - "github.com/smartcontractkit/chainlink/core/services" - strpkg "github.com/smartcontractkit/chainlink/core/store" - "github.com/smartcontractkit/chainlink/core/store/models" - "github.com/smartcontractkit/chainlink/core/store/orm" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" diff --git a/core/services/synchronization/presenters.go b/core/services/synchronization/presenters.go index afaff673bba..b088a7ce931 100644 --- a/core/services/synchronization/presenters.go +++ b/core/services/synchronization/presenters.go @@ -3,11 +3,12 @@ package synchronization import ( "encoding/json" + clnull "chainlink/core/null" + "chainlink/core/store/assets" + "chainlink/core/store/models" + "chainlink/core/utils" + "github.com/ethereum/go-ethereum/common" - clnull "github.com/smartcontractkit/chainlink/core/null" - "github.com/smartcontractkit/chainlink/core/store/assets" - "github.com/smartcontractkit/chainlink/core/store/models" - "github.com/smartcontractkit/chainlink/core/utils" null "gopkg.in/guregu/null.v3" ) diff --git a/core/services/synchronization/presenters_test.go b/core/services/synchronization/presenters_test.go index 41ca6e0da89..3ba55ee6182 100644 --- a/core/services/synchronization/presenters_test.go +++ b/core/services/synchronization/presenters_test.go @@ -5,10 +5,11 @@ import ( "io/ioutil" "testing" + clnull "chainlink/core/null" + "chainlink/core/store/assets" + "chainlink/core/store/models" + "github.com/ethereum/go-ethereum/common" - clnull "github.com/smartcontractkit/chainlink/core/null" - "github.com/smartcontractkit/chainlink/core/store/assets" - "github.com/smartcontractkit/chainlink/core/store/models" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/tidwall/gjson" diff --git a/core/services/synchronization/stats_pusher.go b/core/services/synchronization/stats_pusher.go index 5dbc8cdff20..b96eb952abb 100644 --- a/core/services/synchronization/stats_pusher.go +++ b/core/services/synchronization/stats_pusher.go @@ -7,13 +7,14 @@ import ( "sync" "time" + "chainlink/core/logger" + "chainlink/core/store/models" + "chainlink/core/store/orm" + "chainlink/core/utils" + "github.com/jinzhu/gorm" "github.com/jpillora/backoff" "github.com/pkg/errors" - "github.com/smartcontractkit/chainlink/core/logger" - "github.com/smartcontractkit/chainlink/core/store/models" - "github.com/smartcontractkit/chainlink/core/store/orm" - "github.com/smartcontractkit/chainlink/core/utils" ) // StatsPusher polls for events and pushes them via a WebSocketClient diff --git a/core/services/synchronization/stats_pusher_test.go b/core/services/synchronization/stats_pusher_test.go index b41e1c9c11f..10e67e14779 100644 --- a/core/services/synchronization/stats_pusher_test.go +++ b/core/services/synchronization/stats_pusher_test.go @@ -3,10 +3,11 @@ package synchronization_test import ( "testing" - "github.com/smartcontractkit/chainlink/core/internal/cltest" - "github.com/smartcontractkit/chainlink/core/services/synchronization" - "github.com/smartcontractkit/chainlink/core/store/models" - "github.com/smartcontractkit/chainlink/core/store/orm" + "chainlink/core/internal/cltest" + "chainlink/core/services/synchronization" + "chainlink/core/store/models" + "chainlink/core/store/orm" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/core/services/synchronization/web_socket_client.go b/core/services/synchronization/web_socket_client.go index 45b3c0f5d0c..ca9337a5ce8 100644 --- a/core/services/synchronization/web_socket_client.go +++ b/core/services/synchronization/web_socket_client.go @@ -9,9 +9,10 @@ import ( "sync" "time" + "chainlink/core/logger" + "chainlink/core/utils" + "github.com/gorilla/websocket" - "github.com/smartcontractkit/chainlink/core/logger" - "github.com/smartcontractkit/chainlink/core/utils" ) var ( diff --git a/core/services/synchronization/web_socket_client_test.go b/core/services/synchronization/web_socket_client_test.go index e31300fe39a..7a0005a40aa 100644 --- a/core/services/synchronization/web_socket_client_test.go +++ b/core/services/synchronization/web_socket_client_test.go @@ -6,8 +6,9 @@ import ( "testing" "time" - "github.com/smartcontractkit/chainlink/core/internal/cltest" - "github.com/smartcontractkit/chainlink/core/services/synchronization" + "chainlink/core/internal/cltest" + "chainlink/core/services/synchronization" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/core/services/validators.go b/core/services/validators.go index 649595e271a..81a5b6207f0 100644 --- a/core/services/validators.go +++ b/core/services/validators.go @@ -5,12 +5,13 @@ import ( "strings" "time" + "chainlink/core/adapters" + "chainlink/core/store" + "chainlink/core/store/models" + "chainlink/core/store/orm" + "github.com/asaskevich/govalidator" "github.com/pkg/errors" - "github.com/smartcontractkit/chainlink/core/adapters" - "github.com/smartcontractkit/chainlink/core/store" - "github.com/smartcontractkit/chainlink/core/store/models" - "github.com/smartcontractkit/chainlink/core/store/orm" ) // ValidateJob checks the job and its associated Initiators and Tasks for any diff --git a/core/services/validators_test.go b/core/services/validators_test.go index 7bc1e167f82..e79f5c89641 100644 --- a/core/services/validators_test.go +++ b/core/services/validators_test.go @@ -6,11 +6,12 @@ import ( "testing" "time" - "github.com/smartcontractkit/chainlink/core/adapters" - "github.com/smartcontractkit/chainlink/core/internal/cltest" - "github.com/smartcontractkit/chainlink/core/services" - "github.com/smartcontractkit/chainlink/core/store/models" - "github.com/smartcontractkit/chainlink/core/utils" + "chainlink/core/adapters" + "chainlink/core/internal/cltest" + "chainlink/core/services" + "chainlink/core/store/models" + "chainlink/core/utils" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/core/services/vrf/vrf.go b/core/services/vrf/vrf.go index 669e9977b9d..58e81b36089 100644 --- a/core/services/vrf/vrf.go +++ b/core/services/vrf/vrf.go @@ -32,8 +32,9 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/pkg/errors" - "github.com/smartcontractkit/chainlink/core/services/signatures/secp256k1" - "github.com/smartcontractkit/chainlink/core/utils" + "chainlink/core/services/signatures/secp256k1" + "chainlink/core/utils" + "go.dedis.ch/kyber/v3" ) diff --git a/core/services/vrf/vrf_test.go b/core/services/vrf/vrf_test.go index 3b7e4e8ec56..138d6f9175f 100644 --- a/core/services/vrf/vrf_test.go +++ b/core/services/vrf/vrf_test.go @@ -8,8 +8,8 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/smartcontractkit/chainlink/core/services/signatures/secp256k1" - "github.com/smartcontractkit/chainlink/core/utils" + "chainlink/core/services/signatures/secp256k1" + "chainlink/core/utils" ) func TestVRF_IsSquare(t *testing.T) { diff --git a/core/store/assets/currencies_test.go b/core/store/assets/currencies_test.go index 6c1a87c7688..9b396785c7b 100644 --- a/core/store/assets/currencies_test.go +++ b/core/store/assets/currencies_test.go @@ -4,7 +4,8 @@ import ( "encoding/json" "testing" - "github.com/smartcontractkit/chainlink/core/store/assets" + "chainlink/core/store/assets" + "github.com/stretchr/testify/assert" ) diff --git a/core/store/eth_client.go b/core/store/eth_client.go index 54d6b6c8138..1c6891e7f9d 100644 --- a/core/store/eth_client.go +++ b/core/store/eth_client.go @@ -4,15 +4,16 @@ import ( "context" "math/big" + "chainlink/core/store/assets" + "chainlink/core/store/models" + "chainlink/core/utils" + ethereum "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" - "github.com/smartcontractkit/chainlink/core/store/assets" - "github.com/smartcontractkit/chainlink/core/store/models" - "github.com/smartcontractkit/chainlink/core/utils" ) -//go:generate mockgen -package=mocks -destination=../internal/mocks/eth_client_mocks.go github.com/smartcontractkit/chainlink/core/store EthClient +//go:generate mockgen -package=mocks -destination=../internal/mocks/eth_client_mocks.go chainlink/core/store EthClient // EthClient is the interface supplied by EthCallerSubscriber type EthClient interface { diff --git a/core/store/eth_client_test.go b/core/store/eth_client_test.go index 09e66f5cb55..12ffb9db52f 100644 --- a/core/store/eth_client_test.go +++ b/core/store/eth_client_test.go @@ -6,10 +6,11 @@ import ( "math/big" + "chainlink/core/internal/cltest" + strpkg "chainlink/core/store" + "chainlink/core/store/models" + "github.com/ethereum/go-ethereum/common" - "github.com/smartcontractkit/chainlink/core/internal/cltest" - strpkg "github.com/smartcontractkit/chainlink/core/store" - "github.com/smartcontractkit/chainlink/core/store/models" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/core/store/key_store.go b/core/store/key_store.go index 98fd0a7e828..adffb5ded42 100644 --- a/core/store/key_store.go +++ b/core/store/key_store.go @@ -5,13 +5,14 @@ import ( "fmt" "math/big" + "chainlink/core/logger" + "chainlink/core/store/models" + "chainlink/core/utils" + "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/accounts/keystore" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" - "github.com/smartcontractkit/chainlink/core/logger" - "github.com/smartcontractkit/chainlink/core/store/models" - "github.com/smartcontractkit/chainlink/core/utils" "go.uber.org/multierr" ) diff --git a/core/store/key_store_test.go b/core/store/key_store_test.go index 6c01cf1c732..a4e3aa01dac 100644 --- a/core/store/key_store_test.go +++ b/core/store/key_store_test.go @@ -4,7 +4,8 @@ import ( "io/ioutil" "testing" - "github.com/smartcontractkit/chainlink/core/internal/cltest" + "chainlink/core/internal/cltest" + "github.com/stretchr/testify/assert" ) diff --git a/core/store/migrations/migrate.go b/core/store/migrations/migrate.go index 009759dd908..265f81d58ca 100644 --- a/core/store/migrations/migrate.go +++ b/core/store/migrations/migrate.go @@ -3,30 +3,31 @@ package migrations import ( "regexp" + "chainlink/core/store/migrations/migration0" + "chainlink/core/store/migrations/migration1559081901" + "chainlink/core/store/migrations/migration1559767166" + "chainlink/core/store/migrations/migration1560433987" + "chainlink/core/store/migrations/migration1560791143" + "chainlink/core/store/migrations/migration1560881846" + "chainlink/core/store/migrations/migration1560881855" + "chainlink/core/store/migrations/migration1560886530" + "chainlink/core/store/migrations/migration1560924400" + "chainlink/core/store/migrations/migration1564007745" + "chainlink/core/store/migrations/migration1565139192" + "chainlink/core/store/migrations/migration1565210496" + "chainlink/core/store/migrations/migration1565291711" + "chainlink/core/store/migrations/migration1565877314" + "chainlink/core/store/migrations/migration1566498796" + "chainlink/core/store/migrations/migration1566915476" + "chainlink/core/store/migrations/migration1567029116" + "chainlink/core/store/migrations/migration1568280052" + "chainlink/core/store/migrations/migration1568390387" + "chainlink/core/store/migrations/migration1568833756" + "chainlink/core/store/migrations/migration1570087128" + "chainlink/core/store/migrations/migration1570675883" + "github.com/jinzhu/gorm" "github.com/pkg/errors" - "github.com/smartcontractkit/chainlink/core/store/migrations/migration0" - "github.com/smartcontractkit/chainlink/core/store/migrations/migration1559081901" - "github.com/smartcontractkit/chainlink/core/store/migrations/migration1559767166" - "github.com/smartcontractkit/chainlink/core/store/migrations/migration1560433987" - "github.com/smartcontractkit/chainlink/core/store/migrations/migration1560791143" - "github.com/smartcontractkit/chainlink/core/store/migrations/migration1560881846" - "github.com/smartcontractkit/chainlink/core/store/migrations/migration1560881855" - "github.com/smartcontractkit/chainlink/core/store/migrations/migration1560886530" - "github.com/smartcontractkit/chainlink/core/store/migrations/migration1560924400" - "github.com/smartcontractkit/chainlink/core/store/migrations/migration1564007745" - "github.com/smartcontractkit/chainlink/core/store/migrations/migration1565139192" - "github.com/smartcontractkit/chainlink/core/store/migrations/migration1565210496" - "github.com/smartcontractkit/chainlink/core/store/migrations/migration1565291711" - "github.com/smartcontractkit/chainlink/core/store/migrations/migration1565877314" - "github.com/smartcontractkit/chainlink/core/store/migrations/migration1566498796" - "github.com/smartcontractkit/chainlink/core/store/migrations/migration1566915476" - "github.com/smartcontractkit/chainlink/core/store/migrations/migration1567029116" - "github.com/smartcontractkit/chainlink/core/store/migrations/migration1568280052" - "github.com/smartcontractkit/chainlink/core/store/migrations/migration1568390387" - "github.com/smartcontractkit/chainlink/core/store/migrations/migration1568833756" - "github.com/smartcontractkit/chainlink/core/store/migrations/migration1570087128" - "github.com/smartcontractkit/chainlink/core/store/migrations/migration1570675883" gormigrate "gopkg.in/gormigrate.v1" ) diff --git a/core/store/migrations/migrate_test.go b/core/store/migrations/migrate_test.go index 650daa58423..90a19485ee8 100644 --- a/core/store/migrations/migrate_test.go +++ b/core/store/migrations/migrate_test.go @@ -7,15 +7,16 @@ import ( "testing" "time" + "chainlink/core/internal/cltest" + "chainlink/core/store/assets" + "chainlink/core/store/migrations" + "chainlink/core/store/migrations/migration0" + "chainlink/core/store/migrations/migration1560881855" + "chainlink/core/store/models" + "chainlink/core/store/orm" + "chainlink/core/utils" + "github.com/gofrs/uuid" - "github.com/smartcontractkit/chainlink/core/internal/cltest" - "github.com/smartcontractkit/chainlink/core/store/assets" - "github.com/smartcontractkit/chainlink/core/store/migrations" - "github.com/smartcontractkit/chainlink/core/store/migrations/migration0" - "github.com/smartcontractkit/chainlink/core/store/migrations/migration1560881855" - "github.com/smartcontractkit/chainlink/core/store/models" - "github.com/smartcontractkit/chainlink/core/store/orm" - "github.com/smartcontractkit/chainlink/core/utils" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" gormigrate "gopkg.in/gormigrate.v1" diff --git a/core/store/migrations/migration0/migrate.go b/core/store/migrations/migration0/migrate.go index 44d0ed9c8a7..d519365a110 100644 --- a/core/store/migrations/migration0/migrate.go +++ b/core/store/migrations/migration0/migrate.go @@ -3,13 +3,15 @@ package migration0 import ( "time" + clnull "chainlink/core/null" + "chainlink/core/store/assets" + "chainlink/core/store/models" + "github.com/ethereum/go-ethereum/common" "github.com/jinzhu/gorm" "github.com/pkg/errors" - clnull "github.com/smartcontractkit/chainlink/core/null" - "github.com/smartcontractkit/chainlink/core/store/assets" - "github.com/smartcontractkit/chainlink/core/store/models" "gopkg.in/guregu/null.v3" + ) // Migrate runs the initial migration diff --git a/core/store/migrations/migration1559081901/migrate.go b/core/store/migrations/migration1559081901/migrate.go index 7129dde564f..a874df65cec 100644 --- a/core/store/migrations/migration1559081901/migrate.go +++ b/core/store/migrations/migration1559081901/migrate.go @@ -1,9 +1,10 @@ package migration1559081901 import ( + "chainlink/core/store/models" + "github.com/jinzhu/gorm" "github.com/pkg/errors" - "github.com/smartcontractkit/chainlink/core/store/models" ) func Migrate(tx *gorm.DB) error { diff --git a/core/store/migrations/migration1560791143/migrate.go b/core/store/migrations/migration1560791143/migrate.go index 07b3fcd27bf..00f398cbb49 100644 --- a/core/store/migrations/migration1560791143/migrate.go +++ b/core/store/migrations/migration1560791143/migrate.go @@ -1,9 +1,10 @@ package migration1560791143 import ( + "chainlink/core/store/dbutil" + "github.com/jinzhu/gorm" "github.com/pkg/errors" - "github.com/smartcontractkit/chainlink/core/store/dbutil" ) func Migrate(tx *gorm.DB) error { diff --git a/core/store/migrations/migration1560881855/migrate.go b/core/store/migrations/migration1560881855/migrate.go index e2249abc5c0..00435c686a7 100644 --- a/core/store/migrations/migration1560881855/migrate.go +++ b/core/store/migrations/migration1560881855/migrate.go @@ -3,10 +3,11 @@ package migration1560881855 import ( "time" + "chainlink/core/store/assets" + "chainlink/core/store/dbutil" + "github.com/jinzhu/gorm" "github.com/pkg/errors" - "github.com/smartcontractkit/chainlink/core/store/assets" - "github.com/smartcontractkit/chainlink/core/store/dbutil" ) func Migrate(tx *gorm.DB) error { diff --git a/core/store/migrations/migration1560886530/migrate.go b/core/store/migrations/migration1560886530/migrate.go index 045dea7f3c3..e5a6d662506 100644 --- a/core/store/migrations/migration1560886530/migrate.go +++ b/core/store/migrations/migration1560886530/migrate.go @@ -1,13 +1,14 @@ package migration1560886530 import ( + "chainlink/core/store/dbutil" + "chainlink/core/store/migrations/migration0" + "chainlink/core/store/models" + "chainlink/core/store/orm" + "github.com/ethereum/go-ethereum/common" "github.com/jinzhu/gorm" "github.com/pkg/errors" - "github.com/smartcontractkit/chainlink/core/store/dbutil" - "github.com/smartcontractkit/chainlink/core/store/migrations/migration0" - "github.com/smartcontractkit/chainlink/core/store/models" - "github.com/smartcontractkit/chainlink/core/store/orm" ) // Migrate converts the heads table to use a surrogate ID and binary hash diff --git a/core/store/migrations/migration1564007745/migrate.go b/core/store/migrations/migration1564007745/migrate.go index d8b71ebd380..48dad61bbb2 100644 --- a/core/store/migrations/migration1564007745/migrate.go +++ b/core/store/migrations/migration1564007745/migrate.go @@ -1,9 +1,10 @@ package migration1564007745 import ( + "chainlink/core/store/models" + "github.com/jinzhu/gorm" "github.com/pkg/errors" - "github.com/smartcontractkit/chainlink/core/store/models" ) func Migrate(tx *gorm.DB) error { diff --git a/core/store/migrations/migration1565210496/migrate.go b/core/store/migrations/migration1565210496/migrate.go index 171d573158d..4b3c1a5be9e 100644 --- a/core/store/migrations/migration1565210496/migrate.go +++ b/core/store/migrations/migration1565210496/migrate.go @@ -1,9 +1,10 @@ package migration1565210496 import ( + "chainlink/core/store/dbutil" + "github.com/jinzhu/gorm" "github.com/pkg/errors" - "github.com/smartcontractkit/chainlink/core/store/dbutil" ) // Migrate optimizes the JobRuns table for reduced on disk footprint diff --git a/core/store/migrations/migration1565291711/migrate.go b/core/store/migrations/migration1565291711/migrate.go index 0538241cc14..d5b4bb98319 100644 --- a/core/store/migrations/migration1565291711/migrate.go +++ b/core/store/migrations/migration1565291711/migrate.go @@ -1,9 +1,10 @@ package migration1565291711 import ( + "chainlink/core/store/dbutil" + "github.com/jinzhu/gorm" "github.com/pkg/errors" - "github.com/smartcontractkit/chainlink/core/store/dbutil" ) // Migrate optimizes the JobRuns table to reduce the cost of IDs diff --git a/core/store/migrations/migration1565877314/migrate.go b/core/store/migrations/migration1565877314/migrate.go index e6645c9cd6e..c8cfc20c1ec 100644 --- a/core/store/migrations/migration1565877314/migrate.go +++ b/core/store/migrations/migration1565877314/migrate.go @@ -3,11 +3,12 @@ package migration1565877314 import ( "net/url" + "chainlink/core/store/migrations/migration0" + "chainlink/core/store/models" + "chainlink/core/utils" + "github.com/jinzhu/gorm" "github.com/pkg/errors" - "github.com/smartcontractkit/chainlink/core/store/migrations/migration0" - "github.com/smartcontractkit/chainlink/core/store/models" - "github.com/smartcontractkit/chainlink/core/utils" ) type ExternalInitiator struct { diff --git a/core/store/migrations/migration1566498796/migrate.go b/core/store/migrations/migration1566498796/migrate.go index b59c9551863..44f21137f73 100644 --- a/core/store/migrations/migration1566498796/migrate.go +++ b/core/store/migrations/migration1566498796/migrate.go @@ -1,8 +1,9 @@ package migration1566498796 import ( + "chainlink/core/store/dbutil" + "github.com/jinzhu/gorm" - "github.com/smartcontractkit/chainlink/core/store/dbutil" ) // Migrate optimizes the JobRuns table to reduce the cost of IDs diff --git a/core/store/migrations/migration1567029116/migrate.go b/core/store/migrations/migration1567029116/migrate.go index 4104fd863a4..2a0c8252639 100644 --- a/core/store/migrations/migration1567029116/migrate.go +++ b/core/store/migrations/migration1567029116/migrate.go @@ -1,8 +1,9 @@ package migration1567029116 import ( + "chainlink/core/store/dbutil" + "github.com/jinzhu/gorm" - "github.com/smartcontractkit/chainlink/core/store/dbutil" ) // Migrate optimizes the JobRuns table to reduce the cost of IDs diff --git a/core/store/migrations/migration1568280052/migrate.go b/core/store/migrations/migration1568280052/migrate.go index 08922f12d9f..91f1614c757 100644 --- a/core/store/migrations/migration1568280052/migrate.go +++ b/core/store/migrations/migration1568280052/migrate.go @@ -1,10 +1,11 @@ package migration1568280052 import ( + "chainlink/core/store/migrations/migration1565877314" + "chainlink/core/store/models" + "github.com/jinzhu/gorm" "github.com/pkg/errors" - "github.com/smartcontractkit/chainlink/core/store/migrations/migration1565877314" - "github.com/smartcontractkit/chainlink/core/store/models" ) type ExternalInitiator struct { diff --git a/core/store/migrations/migration1568833756/migrate.go b/core/store/migrations/migration1568833756/migrate.go index a42d8f5095d..c22696ba5b1 100644 --- a/core/store/migrations/migration1568833756/migrate.go +++ b/core/store/migrations/migration1568833756/migrate.go @@ -3,10 +3,11 @@ package migration1568833756 import ( "time" + "chainlink/core/store/models" + "github.com/ethereum/go-ethereum/common" "github.com/jinzhu/gorm" "github.com/pkg/errors" - "github.com/smartcontractkit/chainlink/core/store/models" "gopkg.in/guregu/null.v3" ) diff --git a/core/store/migrations/migration1570087128/migrate.go b/core/store/migrations/migration1570087128/migrate.go index 4205d1e5c16..3e35090eae5 100644 --- a/core/store/migrations/migration1570087128/migrate.go +++ b/core/store/migrations/migration1570087128/migrate.go @@ -1,8 +1,9 @@ package migration1570087128 import ( + "chainlink/core/store/dbutil" + "github.com/jinzhu/gorm" - "github.com/smartcontractkit/chainlink/core/store/dbutil" ) func Migrate(tx *gorm.DB) error { diff --git a/core/store/models/address.go b/core/store/models/address.go index 3b42227cb30..a0068fa3dd0 100644 --- a/core/store/models/address.go +++ b/core/store/models/address.go @@ -6,8 +6,9 @@ import ( "math/big" "strings" + "chainlink/core/utils" + "github.com/ethereum/go-ethereum/common" - "github.com/smartcontractkit/chainlink/core/utils" ) // EIP55Address is a newtype for string which persists an ethereum address in diff --git a/core/store/models/big.go b/core/store/models/big.go index c359541d4a3..a27cbe3c8a9 100644 --- a/core/store/models/big.go +++ b/core/store/models/big.go @@ -5,8 +5,9 @@ import ( "fmt" "math/big" + "chainlink/core/utils" + "github.com/ethereum/go-ethereum/common/hexutil" - "github.com/smartcontractkit/chainlink/core/utils" ) // Big stores large integers and can deserialize a variety of inputs. diff --git a/core/store/models/bridge_type.go b/core/store/models/bridge_type.go index 7eefe3151b7..315850591b7 100644 --- a/core/store/models/bridge_type.go +++ b/core/store/models/bridge_type.go @@ -4,8 +4,8 @@ import ( "crypto/subtle" "fmt" - "github.com/smartcontractkit/chainlink/core/store/assets" - "github.com/smartcontractkit/chainlink/core/utils" + "chainlink/core/store/assets" + "chainlink/core/utils" ) // BridgeTypeRequest is the incoming record used to create a BridgeType diff --git a/core/store/models/bridge_type_test.go b/core/store/models/bridge_type_test.go index b03769c2aff..d03598fc224 100644 --- a/core/store/models/bridge_type_test.go +++ b/core/store/models/bridge_type_test.go @@ -3,8 +3,9 @@ package models_test import ( "testing" - "github.com/smartcontractkit/chainlink/core/internal/cltest" - "github.com/smartcontractkit/chainlink/core/store/models" + "chainlink/core/internal/cltest" + "chainlink/core/store/models" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/core/store/models/bulk_test.go b/core/store/models/bulk_test.go index 02ae691d19f..db9ad4cb21a 100644 --- a/core/store/models/bulk_test.go +++ b/core/store/models/bulk_test.go @@ -3,7 +3,8 @@ package models_test import ( "testing" - "github.com/smartcontractkit/chainlink/core/store/models" + "chainlink/core/store/models" + "github.com/stretchr/testify/assert" ) diff --git a/core/store/models/cbor.go b/core/store/models/cbor.go index bb41e7c6202..94fd8e136b3 100644 --- a/core/store/models/cbor.go +++ b/core/store/models/cbor.go @@ -4,7 +4,8 @@ import ( "bytes" "encoding/json" - "github.com/smartcontractkit/chainlink/core/utils" + "chainlink/core/utils" + "github.com/ugorji/go/codec" ) diff --git a/core/store/models/common.go b/core/store/models/common.go index 8d00080ff0e..40d4e563ac4 100644 --- a/core/store/models/common.go +++ b/core/store/models/common.go @@ -9,9 +9,10 @@ import ( "strings" "time" + "chainlink/core/store/assets" + "github.com/araddon/dateparse" "github.com/jinzhu/gorm" - "github.com/smartcontractkit/chainlink/core/store/assets" "github.com/ethereum/go-ethereum/common" "github.com/mrwonko/cron" diff --git a/core/store/models/common_test.go b/core/store/models/common_test.go index bf763d0d3c4..23dc293c39f 100644 --- a/core/store/models/common_test.go +++ b/core/store/models/common_test.go @@ -7,9 +7,10 @@ import ( "testing" "time" - "github.com/smartcontractkit/chainlink/core/internal/cltest" - "github.com/smartcontractkit/chainlink/core/store/models" - "github.com/smartcontractkit/chainlink/core/utils" + "chainlink/core/internal/cltest" + "chainlink/core/store/models" + "chainlink/core/utils" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/ugorji/go/codec" diff --git a/core/store/models/eth.go b/core/store/models/eth.go index f824db11dd2..66bb58e2254 100755 --- a/core/store/models/eth.go +++ b/core/store/models/eth.go @@ -10,11 +10,12 @@ import ( "regexp" "time" + "chainlink/core/utils" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/core/types" "github.com/jinzhu/gorm" - "github.com/smartcontractkit/chainlink/core/utils" "github.com/tidwall/gjson" "go.uber.org/multierr" null "gopkg.in/guregu/null.v3" diff --git a/core/store/models/eth_test.go b/core/store/models/eth_test.go index d46d628c69f..38020c8c5a1 100644 --- a/core/store/models/eth_test.go +++ b/core/store/models/eth_test.go @@ -8,11 +8,12 @@ import ( "sort" "testing" + "chainlink/core/internal/cltest" + "chainlink/core/store/models" + "chainlink/core/store/presenters" + "chainlink/core/utils" + "github.com/ethereum/go-ethereum/common/hexutil" - "github.com/smartcontractkit/chainlink/core/internal/cltest" - "github.com/smartcontractkit/chainlink/core/store/models" - "github.com/smartcontractkit/chainlink/core/store/presenters" - "github.com/smartcontractkit/chainlink/core/utils" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/tidwall/gjson" diff --git a/core/store/models/external_initiator.go b/core/store/models/external_initiator.go index 45f33845518..f2a226db8e8 100644 --- a/core/store/models/external_initiator.go +++ b/core/store/models/external_initiator.go @@ -6,9 +6,10 @@ import ( "fmt" "strings" + "chainlink/core/utils" + "github.com/jinzhu/gorm" "github.com/pkg/errors" - "github.com/smartcontractkit/chainlink/core/utils" "golang.org/x/crypto/sha3" ) diff --git a/core/store/models/external_initiator_test.go b/core/store/models/external_initiator_test.go index 52c9c49e491..e5263fccbae 100644 --- a/core/store/models/external_initiator_test.go +++ b/core/store/models/external_initiator_test.go @@ -3,8 +3,9 @@ package models_test import ( "testing" - "github.com/smartcontractkit/chainlink/core/internal/cltest" - "github.com/smartcontractkit/chainlink/core/store/models" + "chainlink/core/internal/cltest" + "chainlink/core/store/models" + "github.com/stretchr/testify/assert" ) diff --git a/core/store/models/id.go b/core/store/models/id.go index c7a806fcc15..166b547d36c 100644 --- a/core/store/models/id.go +++ b/core/store/models/id.go @@ -5,8 +5,9 @@ import ( "fmt" "strings" + "chainlink/core/utils" + uuid "github.com/satori/go.uuid" - "github.com/smartcontractkit/chainlink/core/utils" ) // ID is a UUID that has a custom display format diff --git a/core/store/models/job_run.go b/core/store/models/job_run.go index 8ffabdfedc3..86d2f2673cd 100644 --- a/core/store/models/job_run.go +++ b/core/store/models/job_run.go @@ -4,9 +4,10 @@ import ( "fmt" "time" + clnull "chainlink/core/null" + "chainlink/core/store/assets" + "github.com/ethereum/go-ethereum/common" - clnull "github.com/smartcontractkit/chainlink/core/null" - "github.com/smartcontractkit/chainlink/core/store/assets" null "gopkg.in/guregu/null.v3" ) diff --git a/core/store/models/job_run_test.go b/core/store/models/job_run_test.go index a0d58a05d49..538d3385c90 100644 --- a/core/store/models/job_run_test.go +++ b/core/store/models/job_run_test.go @@ -6,9 +6,10 @@ import ( "math/big" "testing" - "github.com/smartcontractkit/chainlink/core/internal/cltest" - "github.com/smartcontractkit/chainlink/core/store/assets" - "github.com/smartcontractkit/chainlink/core/store/models" + "chainlink/core/internal/cltest" + "chainlink/core/store/assets" + "chainlink/core/store/models" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" null "gopkg.in/guregu/null.v3" diff --git a/core/store/models/job_spec.go b/core/store/models/job_spec.go index 187cba3e78a..50cb431f40b 100644 --- a/core/store/models/job_spec.go +++ b/core/store/models/job_spec.go @@ -8,11 +8,12 @@ import ( "strings" "time" + clnull "chainlink/core/null" + "chainlink/core/store/assets" + "github.com/ethereum/go-ethereum/common" "github.com/jinzhu/gorm" "github.com/pkg/errors" - clnull "github.com/smartcontractkit/chainlink/core/null" - "github.com/smartcontractkit/chainlink/core/store/assets" null "gopkg.in/guregu/null.v3" ) diff --git a/core/store/models/job_spec_test.go b/core/store/models/job_spec_test.go index fdfee087706..4d37ab1d954 100644 --- a/core/store/models/job_spec_test.go +++ b/core/store/models/job_spec_test.go @@ -5,10 +5,11 @@ import ( "testing" "time" - "github.com/smartcontractkit/chainlink/core/adapters" - "github.com/smartcontractkit/chainlink/core/internal/cltest" - "github.com/smartcontractkit/chainlink/core/store/assets" - "github.com/smartcontractkit/chainlink/core/store/models" + "chainlink/core/adapters" + "chainlink/core/internal/cltest" + "chainlink/core/store/assets" + "chainlink/core/store/models" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" null "gopkg.in/guregu/null.v3" diff --git a/core/store/models/log_events.go b/core/store/models/log_events.go index a8c005be7cf..e134dbe63d6 100644 --- a/core/store/models/log_events.go +++ b/core/store/models/log_events.go @@ -6,12 +6,13 @@ import ( "fmt" "math/big" + "chainlink/core/logger" + "chainlink/core/store/assets" + "chainlink/core/utils" + ethereum "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/common" "github.com/pkg/errors" - "github.com/smartcontractkit/chainlink/core/logger" - "github.com/smartcontractkit/chainlink/core/store/assets" - "github.com/smartcontractkit/chainlink/core/utils" ) // Descriptive indices of a RunLog's Topic array @@ -46,11 +47,11 @@ var ( RunLogTopic20190207withoutIndexes = utils.MustHash("OracleRequest(bytes32,address,bytes32,uint256,address,bytes4,uint256,uint256,bytes)") // ServiceAgreementExecutionLogTopic is the signature for the // Coordinator.RunRequest(...) events which Chainlink nodes watch for. See - // https://github.com/smartcontractkit/chainlink/blob/master/evm/contracts/Coordinator.sol#RunRequest + // https://chainlink/blob/master/evm/contracts/Coordinator.sol#RunRequest ServiceAgreementExecutionLogTopic = utils.MustHash("ServiceAgreementExecution(bytes32,address,uint256,uint256,uint256,bytes)") // ChainlinkFulfilledTopic is the signature for the event emitted after calling // ChainlinkClient.validateChainlinkCallback(requestId). - // https://github.com/smartcontractkit/chainlink/blob/master/evm/contracts/ChainlinkClient.sol + // https://chainlink/blob/master/evm/contracts/ChainlinkClient.sol ChainlinkFulfilledTopic = utils.MustHash("ChainlinkFulfilled(bytes32)") // OracleFullfillmentFunctionID0original is the original function selector for fulfilling Ethereum requests. OracleFullfillmentFunctionID0original = utils.MustHash("fulfillData(uint256,bytes32)").Hex()[:10] diff --git a/core/store/models/log_events_test.go b/core/store/models/log_events_test.go index fd76dbcb7b3..dbdcdbc7892 100644 --- a/core/store/models/log_events_test.go +++ b/core/store/models/log_events_test.go @@ -5,12 +5,13 @@ import ( "strings" "testing" + "chainlink/core/internal/cltest" + "chainlink/core/store/assets" + "chainlink/core/store/models" + ethereum "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/common" "github.com/onsi/gomega" - "github.com/smartcontractkit/chainlink/core/internal/cltest" - "github.com/smartcontractkit/chainlink/core/store/assets" - "github.com/smartcontractkit/chainlink/core/store/models" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/core/store/models/service_agreement.go b/core/store/models/service_agreement.go index 305995b2e7a..1d6281e6f8c 100644 --- a/core/store/models/service_agreement.go +++ b/core/store/models/service_agreement.go @@ -8,10 +8,11 @@ import ( "math/big" "time" + "chainlink/core/store/assets" + "chainlink/core/utils" + "github.com/ethereum/go-ethereum/common" "github.com/pkg/errors" - "github.com/smartcontractkit/chainlink/core/store/assets" - "github.com/smartcontractkit/chainlink/core/utils" null "gopkg.in/guregu/null.v3" ) diff --git a/core/store/models/service_agreement_test.go b/core/store/models/service_agreement_test.go index 7b533805c66..4b071f8d683 100644 --- a/core/store/models/service_agreement_test.go +++ b/core/store/models/service_agreement_test.go @@ -5,11 +5,12 @@ import ( "strings" "testing" + "chainlink/core/internal/cltest" + "chainlink/core/store/assets" + "chainlink/core/store/models" + "chainlink/core/utils" + "github.com/ethereum/go-ethereum/common/hexutil" - "github.com/smartcontractkit/chainlink/core/internal/cltest" - "github.com/smartcontractkit/chainlink/core/store/assets" - "github.com/smartcontractkit/chainlink/core/store/models" - "github.com/smartcontractkit/chainlink/core/utils" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/core/store/models/signature.go b/core/store/models/signature.go index f820e6f031d..fb51b058ce1 100644 --- a/core/store/models/signature.go +++ b/core/store/models/signature.go @@ -6,9 +6,10 @@ import ( "fmt" "math/big" + "chainlink/core/utils" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" - "github.com/smartcontractkit/chainlink/core/utils" ) const ( diff --git a/core/store/models/topics_test.go b/core/store/models/topics_test.go index 0b8d702cc09..9e71a9e2588 100644 --- a/core/store/models/topics_test.go +++ b/core/store/models/topics_test.go @@ -3,8 +3,9 @@ package models_test import ( "testing" + "chainlink/core/store/models" + "github.com/ethereum/go-ethereum/common" - "github.com/smartcontractkit/chainlink/core/store/models" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/core/store/models/user.go b/core/store/models/user.go index d9dffdcb2cd..3e8d93d65fc 100644 --- a/core/store/models/user.go +++ b/core/store/models/user.go @@ -5,7 +5,7 @@ import ( "regexp" "time" - "github.com/smartcontractkit/chainlink/core/utils" + "chainlink/core/utils" ) // User holds the credentials for API user. diff --git a/core/store/models/user_test.go b/core/store/models/user_test.go index 5e271e8bbd2..85f4247d647 100644 --- a/core/store/models/user_test.go +++ b/core/store/models/user_test.go @@ -3,8 +3,9 @@ package models_test import ( "testing" - "github.com/smartcontractkit/chainlink/core/store/models" - "github.com/smartcontractkit/chainlink/core/utils" + "chainlink/core/store/models" + "chainlink/core/utils" + "github.com/stretchr/testify/assert" ) diff --git a/core/store/orm/config.go b/core/store/orm/config.go index d77439f059e..55566946cbd 100644 --- a/core/store/orm/config.go +++ b/core/store/orm/config.go @@ -13,15 +13,16 @@ import ( "strconv" "time" + "chainlink/core/logger" + "chainlink/core/store/assets" + "chainlink/core/utils" + "github.com/ethereum/go-ethereum/common" "github.com/gin-gonic/contrib/sessions" "github.com/gin-gonic/gin" "github.com/gorilla/securecookie" homedir "github.com/mitchellh/go-homedir" "github.com/pkg/errors" - "github.com/smartcontractkit/chainlink/core/logger" - "github.com/smartcontractkit/chainlink/core/store/assets" - "github.com/smartcontractkit/chainlink/core/utils" "github.com/spf13/viper" "go.uber.org/zap" "go.uber.org/zap/zapcore" diff --git a/core/store/orm/config_reader.go b/core/store/orm/config_reader.go index b277b4d62aa..acdfad5e196 100644 --- a/core/store/orm/config_reader.go +++ b/core/store/orm/config_reader.go @@ -5,9 +5,10 @@ import ( "net/url" "time" + "chainlink/core/store/assets" + "github.com/ethereum/go-ethereum/common" "github.com/gin-gonic/contrib/sessions" - "github.com/smartcontractkit/chainlink/core/store/assets" "go.uber.org/zap" ) diff --git a/core/store/orm/config_test.go b/core/store/orm/config_test.go index 553895d38a3..3a41736b36c 100644 --- a/core/store/orm/config_test.go +++ b/core/store/orm/config_test.go @@ -8,9 +8,10 @@ import ( "testing" "time" + "chainlink/core/store/assets" + "chainlink/core/store/migrations/migration1564007745" + "github.com/ethereum/go-ethereum/common" - "github.com/smartcontractkit/chainlink/core/store/assets" - "github.com/smartcontractkit/chainlink/core/store/migrations/migration1564007745" "github.com/spf13/viper" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" diff --git a/core/store/orm/locking_strategies_test.go b/core/store/orm/locking_strategies_test.go index 60df8d1fd23..6fff817f55a 100644 --- a/core/store/orm/locking_strategies_test.go +++ b/core/store/orm/locking_strategies_test.go @@ -7,8 +7,9 @@ import ( "testing" "time" - "github.com/smartcontractkit/chainlink/core/internal/cltest" - "github.com/smartcontractkit/chainlink/core/store/orm" + "chainlink/core/internal/cltest" + "chainlink/core/store/orm" + "github.com/stretchr/testify/require" ) diff --git a/core/store/orm/log_wrapper.go b/core/store/orm/log_wrapper.go index 441f91fa6bc..94d554e7cd8 100644 --- a/core/store/orm/log_wrapper.go +++ b/core/store/orm/log_wrapper.go @@ -1,7 +1,8 @@ package orm import ( - "github.com/smartcontractkit/chainlink/core/logger" + "chainlink/core/logger" + "go.uber.org/zap" ) diff --git a/core/store/orm/orm.go b/core/store/orm/orm.go index d0104c2e481..e62eca704ca 100644 --- a/core/store/orm/orm.go +++ b/core/store/orm/orm.go @@ -10,17 +10,18 @@ import ( "strings" "time" + "chainlink/core/logger" + "chainlink/core/store/assets" + "chainlink/core/store/dbutil" + "chainlink/core/store/models" + "chainlink/core/utils" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" "github.com/jinzhu/gorm" _ "github.com/jinzhu/gorm/dialects/postgres" // http://doc.gorm.io/database.html#connecting-to-a-database _ "github.com/jinzhu/gorm/dialects/sqlite" // http://doc.gorm.io/database.html#connecting-to-a-database "github.com/pkg/errors" - "github.com/smartcontractkit/chainlink/core/logger" - "github.com/smartcontractkit/chainlink/core/store/assets" - "github.com/smartcontractkit/chainlink/core/store/dbutil" - "github.com/smartcontractkit/chainlink/core/store/models" - "github.com/smartcontractkit/chainlink/core/utils" "go.uber.org/multierr" "gopkg.in/guregu/null.v3" ) diff --git a/core/store/orm/orm_test.go b/core/store/orm/orm_test.go index 219e9467fbf..d74dbc767a3 100644 --- a/core/store/orm/orm_test.go +++ b/core/store/orm/orm_test.go @@ -9,16 +9,17 @@ import ( "testing" "time" + "chainlink/core/adapters" + "chainlink/core/internal/cltest" + "chainlink/core/services" + "chainlink/core/services/synchronization" + "chainlink/core/store/assets" + "chainlink/core/store/models" + "chainlink/core/store/orm" + "chainlink/core/utils" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" - "github.com/smartcontractkit/chainlink/core/adapters" - "github.com/smartcontractkit/chainlink/core/internal/cltest" - "github.com/smartcontractkit/chainlink/core/services" - "github.com/smartcontractkit/chainlink/core/services/synchronization" - "github.com/smartcontractkit/chainlink/core/store/assets" - "github.com/smartcontractkit/chainlink/core/store/models" - "github.com/smartcontractkit/chainlink/core/store/orm" - "github.com/smartcontractkit/chainlink/core/utils" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "gopkg.in/guregu/null.v3" diff --git a/core/store/orm/schema.go b/core/store/orm/schema.go index 078633464f1..fdd248393d9 100644 --- a/core/store/orm/schema.go +++ b/core/store/orm/schema.go @@ -7,8 +7,9 @@ import ( "reflect" "time" + "chainlink/core/store/assets" + "github.com/ethereum/go-ethereum/common" - "github.com/smartcontractkit/chainlink/core/store/assets" ) // ConfigSchema records the schema of configuration at the type level diff --git a/core/store/presenters/presenters.go b/core/store/presenters/presenters.go index a33e7d518e6..98cdf21db3b 100644 --- a/core/store/presenters/presenters.go +++ b/core/store/presenters/presenters.go @@ -13,16 +13,17 @@ import ( "strings" "time" + "chainlink/core/logger" + "chainlink/core/store" + "chainlink/core/store/assets" + "chainlink/core/store/models" + "chainlink/core/store/orm" + "chainlink/core/utils" + "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/pkg/errors" - "github.com/smartcontractkit/chainlink/core/logger" - "github.com/smartcontractkit/chainlink/core/store" - "github.com/smartcontractkit/chainlink/core/store/assets" - "github.com/smartcontractkit/chainlink/core/store/models" - "github.com/smartcontractkit/chainlink/core/store/orm" - "github.com/smartcontractkit/chainlink/core/utils" "github.com/tidwall/gjson" "go.uber.org/multierr" ) @@ -195,15 +196,15 @@ func NewConfigWhitelist(store *store.Store) (ConfigWhitelist, error) { MinIncomingConfirmations: config.MinIncomingConfirmations(), MinOutgoingConfirmations: config.MinOutgoingConfirmations(), OracleContractAddress: config.OracleContractAddress(), - Port: config.Port(), - ReaperExpiration: config.ReaperExpiration(), - ReplayFromBlock: config.ReplayFromBlock(), - RootDir: config.RootDir(), - SessionTimeout: config.SessionTimeout(), - TLSHost: config.TLSHost(), - TLSPort: config.TLSPort(), - TLSRedirect: config.TLSRedirect(), - TxAttemptLimit: config.TxAttemptLimit(), + Port: config.Port(), + ReaperExpiration: config.ReaperExpiration(), + ReplayFromBlock: config.ReplayFromBlock(), + RootDir: config.RootDir(), + SessionTimeout: config.SessionTimeout(), + TLSHost: config.TLSHost(), + TLSPort: config.TLSPort(), + TLSRedirect: config.TLSRedirect(), + TxAttemptLimit: config.TxAttemptLimit(), }, }, nil } diff --git a/core/store/presenters/presenters_test.go b/core/store/presenters/presenters_test.go index 70b82f511b5..55ef5b38587 100644 --- a/core/store/presenters/presenters_test.go +++ b/core/store/presenters/presenters_test.go @@ -5,8 +5,9 @@ import ( "fmt" "testing" + "chainlink/core/store/models" + "github.com/ethereum/go-ethereum/common" - "github.com/smartcontractkit/chainlink/core/store/models" "github.com/stretchr/testify/assert" ) diff --git a/core/store/store.go b/core/store/store.go index 73094461a25..a414a99d7c8 100644 --- a/core/store/store.go +++ b/core/store/store.go @@ -9,14 +9,15 @@ import ( "sync" "time" + "chainlink/core/logger" + "chainlink/core/services/synchronization" + "chainlink/core/store/migrations" + "chainlink/core/store/models" + "chainlink/core/store/orm" + "chainlink/core/utils" + "github.com/ethereum/go-ethereum/rpc" "github.com/pkg/errors" - "github.com/smartcontractkit/chainlink/core/logger" - "github.com/smartcontractkit/chainlink/core/services/synchronization" - "github.com/smartcontractkit/chainlink/core/store/migrations" - "github.com/smartcontractkit/chainlink/core/store/models" - "github.com/smartcontractkit/chainlink/core/store/orm" - "github.com/smartcontractkit/chainlink/core/utils" "github.com/tevino/abool" "go.uber.org/multierr" "golang.org/x/time/rate" diff --git a/core/store/store_test.go b/core/store/store_test.go index f8b3d526501..4b9233b314c 100644 --- a/core/store/store_test.go +++ b/core/store/store_test.go @@ -6,10 +6,11 @@ import ( "strings" "testing" + "chainlink/core/internal/cltest" + "chainlink/core/internal/mocks" + "chainlink/core/utils" + "github.com/golang/mock/gomock" - "github.com/smartcontractkit/chainlink/core/internal/cltest" - "github.com/smartcontractkit/chainlink/core/internal/mocks" - "github.com/smartcontractkit/chainlink/core/utils" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/tidwall/gjson" diff --git a/core/store/tx_manager.go b/core/store/tx_manager.go index 8a5ff2d25ac..ac815981a92 100644 --- a/core/store/tx_manager.go +++ b/core/store/tx_manager.go @@ -11,19 +11,20 @@ import ( "github.com/gobuffalo/packr" "github.com/pkg/errors" "github.com/tidwall/gjson" - "gopkg.in/guregu/null.v3" + + "chainlink/core/logger" + "chainlink/core/store/assets" + "chainlink/core/store/models" + "chainlink/core/store/orm" + "chainlink/core/utils" ethereum "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" - "github.com/smartcontractkit/chainlink/core/logger" - "github.com/smartcontractkit/chainlink/core/store/assets" - "github.com/smartcontractkit/chainlink/core/store/models" - "github.com/smartcontractkit/chainlink/core/store/orm" - "github.com/smartcontractkit/chainlink/core/utils" "github.com/tevino/abool" "go.uber.org/multierr" + "gopkg.in/guregu/null.v3" ) // DefaultGasLimit sets the default gas limit for outgoing transactions. @@ -62,7 +63,7 @@ type TxManager interface { GetChainID() (*big.Int, error) } -//go:generate mockgen -package=mocks -destination=../internal/mocks/tx_manager_mocks.go github.com/smartcontractkit/chainlink/core/store TxManager +//go:generate mockgen -package=mocks -destination=../internal/mocks/tx_manager_mocks.go chainlink/core/store TxManager // EthTxManager contains fields for the Ethereum client, the KeyStore, // the local Config for the application, and the database. diff --git a/core/store/tx_manager_internal_test.go b/core/store/tx_manager_internal_test.go index e5261078296..63529dc3f44 100644 --- a/core/store/tx_manager_internal_test.go +++ b/core/store/tx_manager_internal_test.go @@ -3,9 +3,10 @@ package store import ( "testing" + "chainlink/core/store/models" + "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/common" - "github.com/smartcontractkit/chainlink/core/store/models" "github.com/stretchr/testify/assert" ) diff --git a/core/store/tx_manager_test.go b/core/store/tx_manager_test.go index 33bb2634a2c..6a03f128ca7 100644 --- a/core/store/tx_manager_test.go +++ b/core/store/tx_manager_test.go @@ -6,18 +6,19 @@ import ( "math/big" "testing" + "chainlink/core/internal/cltest" + "chainlink/core/internal/mocks" + "chainlink/core/store" + strpkg "chainlink/core/store" + "chainlink/core/store/assets" + "chainlink/core/store/models" + "chainlink/core/store/orm" + "chainlink/core/utils" + "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/golang/mock/gomock" - "github.com/smartcontractkit/chainlink/core/internal/cltest" - "github.com/smartcontractkit/chainlink/core/internal/mocks" - "github.com/smartcontractkit/chainlink/core/store" - strpkg "github.com/smartcontractkit/chainlink/core/store" - "github.com/smartcontractkit/chainlink/core/store/assets" - "github.com/smartcontractkit/chainlink/core/store/models" - "github.com/smartcontractkit/chainlink/core/store/orm" - "github.com/smartcontractkit/chainlink/core/utils" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "gopkg.in/guregu/null.v3" diff --git a/core/store/types.go b/core/store/types.go index 13390437959..b2a0c498374 100644 --- a/core/store/types.go +++ b/core/store/types.go @@ -1,7 +1,7 @@ package store import ( - "github.com/smartcontractkit/chainlink/core/store/models" + "chainlink/core/store/models" ) // HeadTrackable represents any object that wishes to respond to ethereum events, diff --git a/core/utils/json_normalization_test.go b/core/utils/json_normalization_test.go index 6b9c83881ec..2669372387e 100644 --- a/core/utils/json_normalization_test.go +++ b/core/utils/json_normalization_test.go @@ -4,8 +4,9 @@ import ( "encoding/json" "testing" - "github.com/smartcontractkit/chainlink/core/internal/cltest" - "github.com/smartcontractkit/chainlink/core/utils" + "chainlink/core/internal/cltest" + "chainlink/core/utils" + "github.com/stretchr/testify/assert" ) diff --git a/core/utils/utils_test.go b/core/utils/utils_test.go index 33e26831543..a81a6ac6fc1 100644 --- a/core/utils/utils_test.go +++ b/core/utils/utils_test.go @@ -8,10 +8,11 @@ import ( "testing" "time" + "chainlink/core/internal/cltest" + "chainlink/core/utils" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" - "github.com/smartcontractkit/chainlink/core/internal/cltest" - "github.com/smartcontractkit/chainlink/core/utils" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.uber.org/multierr" diff --git a/core/web/box_test.go b/core/web/box_test.go index de57e325f43..f14eeea42ba 100644 --- a/core/web/box_test.go +++ b/core/web/box_test.go @@ -5,7 +5,8 @@ import ( "strings" "testing" - "github.com/smartcontractkit/chainlink/core/web" + "chainlink/core/web" + "github.com/stretchr/testify/assert" ) diff --git a/core/web/bridge_types_controller.go b/core/web/bridge_types_controller.go index 6cfa5f1e5c8..31ead1cb23b 100644 --- a/core/web/bridge_types_controller.go +++ b/core/web/bridge_types_controller.go @@ -4,11 +4,12 @@ import ( "fmt" "net/http" + "chainlink/core/services" + "chainlink/core/store/models" + "chainlink/core/store/orm" + "github.com/gin-gonic/gin" "github.com/pkg/errors" - "github.com/smartcontractkit/chainlink/core/services" - "github.com/smartcontractkit/chainlink/core/store/models" - "github.com/smartcontractkit/chainlink/core/store/orm" ) // BridgeTypesController manages BridgeType requests in the node. diff --git a/core/web/bridge_types_controller_test.go b/core/web/bridge_types_controller_test.go index e4d43a52e4c..3997936d437 100644 --- a/core/web/bridge_types_controller_test.go +++ b/core/web/bridge_types_controller_test.go @@ -6,12 +6,13 @@ import ( "net/http" "testing" + "chainlink/core/internal/cltest" + "chainlink/core/store" + "chainlink/core/store/assets" + "chainlink/core/store/models" + "chainlink/core/web" + "github.com/manyminds/api2go/jsonapi" - "github.com/smartcontractkit/chainlink/core/internal/cltest" - "github.com/smartcontractkit/chainlink/core/store" - "github.com/smartcontractkit/chainlink/core/store/assets" - "github.com/smartcontractkit/chainlink/core/store/models" - "github.com/smartcontractkit/chainlink/core/web" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/core/web/bulk_deletes_controller.go b/core/web/bulk_deletes_controller.go index 61f3ae877ed..67011403eeb 100644 --- a/core/web/bulk_deletes_controller.go +++ b/core/web/bulk_deletes_controller.go @@ -3,9 +3,10 @@ package web import ( "net/http" + "chainlink/core/services" + "chainlink/core/store/models" + "github.com/gin-gonic/gin" - "github.com/smartcontractkit/chainlink/core/services" - "github.com/smartcontractkit/chainlink/core/store/models" ) // BulkDeletesController manages background tasks that delete resources given a query diff --git a/core/web/config_controller.go b/core/web/config_controller.go index c3f8e03c787..f76c481ee31 100644 --- a/core/web/config_controller.go +++ b/core/web/config_controller.go @@ -4,10 +4,11 @@ import ( "fmt" "net/http" + "chainlink/core/services" + "chainlink/core/store/models" + "chainlink/core/store/presenters" + "github.com/gin-gonic/gin" - "github.com/smartcontractkit/chainlink/core/services" - "github.com/smartcontractkit/chainlink/core/store/models" - "github.com/smartcontractkit/chainlink/core/store/presenters" ) // ConfigController manages config variables diff --git a/core/web/config_controller_test.go b/core/web/config_controller_test.go index 620301168e2..5a7cadb36bc 100644 --- a/core/web/config_controller_test.go +++ b/core/web/config_controller_test.go @@ -6,11 +6,12 @@ import ( "testing" "time" + "chainlink/core/internal/cltest" + "chainlink/core/store/assets" + "chainlink/core/store/orm" + "chainlink/core/store/presenters" + "github.com/ethereum/go-ethereum/common" - "github.com/smartcontractkit/chainlink/core/internal/cltest" - "github.com/smartcontractkit/chainlink/core/store/assets" - "github.com/smartcontractkit/chainlink/core/store/orm" - "github.com/smartcontractkit/chainlink/core/store/presenters" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/core/web/cors_test.go b/core/web/cors_test.go index 2603fd03c9e..2853f62d0dc 100644 --- a/core/web/cors_test.go +++ b/core/web/cors_test.go @@ -4,7 +4,8 @@ import ( "net/http" "testing" - "github.com/smartcontractkit/chainlink/core/internal/cltest" + "chainlink/core/internal/cltest" + "github.com/stretchr/testify/require" ) diff --git a/core/web/external_initiators.go b/core/web/external_initiators.go index 45228d8051a..90fa82f4e14 100644 --- a/core/web/external_initiators.go +++ b/core/web/external_initiators.go @@ -6,9 +6,10 @@ import ( "fmt" "net/http" + "chainlink/core/store" + "chainlink/core/store/models" + "github.com/pkg/errors" - "github.com/smartcontractkit/chainlink/core/store" - "github.com/smartcontractkit/chainlink/core/store/models" ) // JobSpecNotice is sent to the External Initiator when JobSpecs are created. diff --git a/core/web/external_initiators_controller.go b/core/web/external_initiators_controller.go index 6a93b1af19f..383d956fba6 100644 --- a/core/web/external_initiators_controller.go +++ b/core/web/external_initiators_controller.go @@ -4,10 +4,11 @@ import ( "errors" "net/http" + "chainlink/core/services" + "chainlink/core/store/models" + "chainlink/core/store/presenters" + "github.com/gin-gonic/gin" - "github.com/smartcontractkit/chainlink/core/services" - "github.com/smartcontractkit/chainlink/core/store/models" - "github.com/smartcontractkit/chainlink/core/store/presenters" ) // ExternalInitiatorsController manages external initiators diff --git a/core/web/external_initiators_controller_test.go b/core/web/external_initiators_controller_test.go index 6fad24dba03..05602596f36 100644 --- a/core/web/external_initiators_controller_test.go +++ b/core/web/external_initiators_controller_test.go @@ -4,9 +4,10 @@ import ( "bytes" "testing" - "github.com/smartcontractkit/chainlink/core/internal/cltest" - "github.com/smartcontractkit/chainlink/core/store/models" - "github.com/smartcontractkit/chainlink/core/store/presenters" + "chainlink/core/internal/cltest" + "chainlink/core/store/models" + "chainlink/core/store/presenters" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/core/web/external_initiators_test.go b/core/web/external_initiators_test.go index ccd0931dc1e..d03abe1b115 100644 --- a/core/web/external_initiators_test.go +++ b/core/web/external_initiators_test.go @@ -5,9 +5,10 @@ import ( "net/http" "testing" - "github.com/smartcontractkit/chainlink/core/internal/cltest" - "github.com/smartcontractkit/chainlink/core/store/models" - "github.com/smartcontractkit/chainlink/core/web" + "chainlink/core/internal/cltest" + "chainlink/core/store/models" + "chainlink/core/web" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/core/web/gui_assets_test.go b/core/web/gui_assets_test.go index d04dbf91104..37c6041b293 100644 --- a/core/web/gui_assets_test.go +++ b/core/web/gui_assets_test.go @@ -4,7 +4,8 @@ import ( "net/http" "testing" - "github.com/smartcontractkit/chainlink/core/internal/cltest" + "chainlink/core/internal/cltest" + "github.com/stretchr/testify/require" ) diff --git a/core/web/helpers.go b/core/web/helpers.go index 5f31a035fd2..6c6b62a67d0 100644 --- a/core/web/helpers.go +++ b/core/web/helpers.go @@ -4,11 +4,12 @@ import ( "fmt" "net/http" + "chainlink/core/store/models" + "chainlink/core/store/orm" + "github.com/gin-gonic/gin" "github.com/manyminds/api2go/jsonapi" "github.com/pkg/errors" - "github.com/smartcontractkit/chainlink/core/store/models" - "github.com/smartcontractkit/chainlink/core/store/orm" ) // StatusCodeForError returns an http status code for an error type. diff --git a/core/web/helpers_test.go b/core/web/helpers_test.go index 14b8a83c53d..71e8ab36cf4 100644 --- a/core/web/helpers_test.go +++ b/core/web/helpers_test.go @@ -4,8 +4,9 @@ import ( "errors" "testing" - "github.com/smartcontractkit/chainlink/core/store/models" - "github.com/smartcontractkit/chainlink/core/web" + "chainlink/core/store/models" + "chainlink/core/web" + "github.com/stretchr/testify/assert" ) diff --git a/core/web/job_runs_controller.go b/core/web/job_runs_controller.go index babde6c4e11..9564e5e2e80 100644 --- a/core/web/job_runs_controller.go +++ b/core/web/job_runs_controller.go @@ -5,13 +5,14 @@ import ( "io/ioutil" "net/http" + "chainlink/core/services" + "chainlink/core/store/models" + "chainlink/core/store/orm" + "chainlink/core/store/presenters" + "chainlink/core/utils" + "github.com/gin-gonic/gin" "github.com/pkg/errors" - "github.com/smartcontractkit/chainlink/core/services" - "github.com/smartcontractkit/chainlink/core/store/models" - "github.com/smartcontractkit/chainlink/core/store/orm" - "github.com/smartcontractkit/chainlink/core/store/presenters" - "github.com/smartcontractkit/chainlink/core/utils" ) // JobRunsController manages JobRun requests in the node. diff --git a/core/web/job_runs_controller_test.go b/core/web/job_runs_controller_test.go index 10b2ee41818..06ba3009b0a 100644 --- a/core/web/job_runs_controller_test.go +++ b/core/web/job_runs_controller_test.go @@ -7,11 +7,12 @@ import ( "testing" "time" + "chainlink/core/internal/cltest" + "chainlink/core/store/models" + "chainlink/core/store/presenters" + "chainlink/core/web" + "github.com/manyminds/api2go/jsonapi" - "github.com/smartcontractkit/chainlink/core/internal/cltest" - "github.com/smartcontractkit/chainlink/core/store/models" - "github.com/smartcontractkit/chainlink/core/store/presenters" - "github.com/smartcontractkit/chainlink/core/web" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/core/web/job_specs_controller.go b/core/web/job_specs_controller.go index 622d57bbb47..8fba4fbe9ba 100644 --- a/core/web/job_specs_controller.go +++ b/core/web/job_specs_controller.go @@ -3,12 +3,13 @@ package web import ( "net/http" + "chainlink/core/services" + "chainlink/core/store/models" + "chainlink/core/store/orm" + "chainlink/core/store/presenters" + "github.com/gin-gonic/gin" "github.com/pkg/errors" - "github.com/smartcontractkit/chainlink/core/services" - "github.com/smartcontractkit/chainlink/core/store/models" - "github.com/smartcontractkit/chainlink/core/store/orm" - "github.com/smartcontractkit/chainlink/core/store/presenters" ) // JobSpecsController manages JobSpec requests. diff --git a/core/web/job_specs_controller_test.go b/core/web/job_specs_controller_test.go index 09b856ec992..f967c13e3b5 100644 --- a/core/web/job_specs_controller_test.go +++ b/core/web/job_specs_controller_test.go @@ -7,13 +7,14 @@ import ( "testing" "time" + "chainlink/core/adapters" + "chainlink/core/internal/cltest" + "chainlink/core/store/models" + "chainlink/core/store/presenters" + "chainlink/core/utils" + "chainlink/core/web" + "github.com/manyminds/api2go/jsonapi" - "github.com/smartcontractkit/chainlink/core/adapters" - "github.com/smartcontractkit/chainlink/core/internal/cltest" - "github.com/smartcontractkit/chainlink/core/store/models" - "github.com/smartcontractkit/chainlink/core/store/presenters" - "github.com/smartcontractkit/chainlink/core/utils" - "github.com/smartcontractkit/chainlink/core/web" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/core/web/keys_controller.go b/core/web/keys_controller.go index cc6fa8db3eb..41947e1f5ee 100644 --- a/core/web/keys_controller.go +++ b/core/web/keys_controller.go @@ -3,10 +3,11 @@ package web import ( "net/http" + "chainlink/core/services" + "chainlink/core/store/models" + "chainlink/core/store/presenters" + "github.com/gin-gonic/gin" - "github.com/smartcontractkit/chainlink/core/services" - "github.com/smartcontractkit/chainlink/core/store/models" - "github.com/smartcontractkit/chainlink/core/store/presenters" ) // KeysController manages account keys diff --git a/core/web/keys_controller_test.go b/core/web/keys_controller_test.go index a488cb27ed5..8d124ea39e4 100644 --- a/core/web/keys_controller_test.go +++ b/core/web/keys_controller_test.go @@ -5,8 +5,9 @@ import ( "encoding/json" "testing" - "github.com/smartcontractkit/chainlink/core/internal/cltest" - "github.com/smartcontractkit/chainlink/core/store/models" + "chainlink/core/internal/cltest" + "chainlink/core/store/models" + "github.com/stretchr/testify/assert" ) diff --git a/core/web/ping_controller.go b/core/web/ping_controller.go index 8dc80a6825d..195f8edac7d 100644 --- a/core/web/ping_controller.go +++ b/core/web/ping_controller.go @@ -3,8 +3,9 @@ package web import ( "net/http" + "chainlink/core/services" + "github.com/gin-gonic/gin" - "github.com/smartcontractkit/chainlink/core/services" ) // PingController has the ping endpoint. diff --git a/core/web/ping_controller_test.go b/core/web/ping_controller_test.go index e76cb95ae1a..61b9a7170eb 100644 --- a/core/web/ping_controller_test.go +++ b/core/web/ping_controller_test.go @@ -4,9 +4,10 @@ import ( "net/http" "testing" - "github.com/smartcontractkit/chainlink/core/internal/cltest" - "github.com/smartcontractkit/chainlink/core/store/models" - "github.com/smartcontractkit/chainlink/core/web" + "chainlink/core/internal/cltest" + "chainlink/core/store/models" + "chainlink/core/web" + "github.com/stretchr/testify/require" ) diff --git a/core/web/router.go b/core/web/router.go index 4e7db934fde..91d88a1aeba 100644 --- a/core/web/router.go +++ b/core/web/router.go @@ -15,6 +15,12 @@ import ( "strings" "time" + "chainlink/core/logger" + "chainlink/core/services" + "chainlink/core/store" + "chainlink/core/store/models" + "chainlink/core/store/orm" + helmet "github.com/danielkov/gin-helmet" "github.com/gin-contrib/cors" "github.com/gin-contrib/expvar" @@ -23,11 +29,6 @@ import ( "github.com/gin-gonic/gin" "github.com/gobuffalo/packr" "github.com/pkg/errors" - "github.com/smartcontractkit/chainlink/core/logger" - "github.com/smartcontractkit/chainlink/core/services" - "github.com/smartcontractkit/chainlink/core/store" - "github.com/smartcontractkit/chainlink/core/store/models" - "github.com/smartcontractkit/chainlink/core/store/orm" "github.com/ulule/limiter" mgin "github.com/ulule/limiter/drivers/middleware/gin" "github.com/ulule/limiter/drivers/store/memory" diff --git a/core/web/router_test.go b/core/web/router_test.go index 8c36f6c9956..0d53d48c300 100644 --- a/core/web/router_test.go +++ b/core/web/router_test.go @@ -6,9 +6,10 @@ import ( "net/http/httptest" "testing" - "github.com/smartcontractkit/chainlink/core/internal/cltest" - "github.com/smartcontractkit/chainlink/core/store/models" - "github.com/smartcontractkit/chainlink/core/web" + "chainlink/core/internal/cltest" + "chainlink/core/store/models" + "chainlink/core/web" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/core/web/service_agreements_controller.go b/core/web/service_agreements_controller.go index fafe88ab792..a92136cc7cc 100644 --- a/core/web/service_agreements_controller.go +++ b/core/web/service_agreements_controller.go @@ -3,13 +3,14 @@ package web import ( "net/http" + "chainlink/core/services" + "chainlink/core/store/models" + "chainlink/core/store/orm" + "chainlink/core/store/presenters" + "github.com/ethereum/go-ethereum/common" "github.com/gin-gonic/gin" "github.com/pkg/errors" - "github.com/smartcontractkit/chainlink/core/services" - "github.com/smartcontractkit/chainlink/core/store/models" - "github.com/smartcontractkit/chainlink/core/store/orm" - "github.com/smartcontractkit/chainlink/core/store/presenters" ) // ServiceAgreementsController manages service agreements. diff --git a/core/web/service_agreements_controller_test.go b/core/web/service_agreements_controller_test.go index ba2527d93be..ea864464413 100644 --- a/core/web/service_agreements_controller_test.go +++ b/core/web/service_agreements_controller_test.go @@ -7,9 +7,10 @@ import ( "testing" "time" - "github.com/smartcontractkit/chainlink/core/internal/cltest" - "github.com/smartcontractkit/chainlink/core/store/models" - "github.com/smartcontractkit/chainlink/core/store/presenters" + "chainlink/core/internal/cltest" + "chainlink/core/store/models" + "chainlink/core/store/presenters" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/core/web/sessions_controller.go b/core/web/sessions_controller.go index 90480444d12..fb21c0d0f66 100644 --- a/core/web/sessions_controller.go +++ b/core/web/sessions_controller.go @@ -5,10 +5,11 @@ import ( "fmt" "net/http" + "chainlink/core/services" + "chainlink/core/store/models" + "github.com/gin-gonic/contrib/sessions" "github.com/gin-gonic/gin" - "github.com/smartcontractkit/chainlink/core/services" - "github.com/smartcontractkit/chainlink/core/store/models" "go.uber.org/multierr" ) diff --git a/core/web/sessions_controller_test.go b/core/web/sessions_controller_test.go index baf9081ad87..5c7ede82767 100644 --- a/core/web/sessions_controller_test.go +++ b/core/web/sessions_controller_test.go @@ -8,9 +8,10 @@ import ( "testing" "time" + "chainlink/core/internal/cltest" + "chainlink/core/store/models" + "github.com/onsi/gomega" - "github.com/smartcontractkit/chainlink/core/internal/cltest" - "github.com/smartcontractkit/chainlink/core/store/models" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/core/web/transactions_controller.go b/core/web/transactions_controller.go index 9f7ba66e07e..b3702bc1e2d 100644 --- a/core/web/transactions_controller.go +++ b/core/web/transactions_controller.go @@ -3,12 +3,13 @@ package web import ( "net/http" + "chainlink/core/services" + "chainlink/core/store/orm" + "chainlink/core/store/presenters" + "github.com/ethereum/go-ethereum/common" "github.com/gin-gonic/gin" "github.com/pkg/errors" - "github.com/smartcontractkit/chainlink/core/services" - "github.com/smartcontractkit/chainlink/core/store/orm" - "github.com/smartcontractkit/chainlink/core/store/presenters" ) // TransactionsController displays Ethereum transactions requests. diff --git a/core/web/transactions_controller_test.go b/core/web/transactions_controller_test.go index a587965619b..70ceebdd6fe 100644 --- a/core/web/transactions_controller_test.go +++ b/core/web/transactions_controller_test.go @@ -5,11 +5,12 @@ import ( "net/http" "testing" + "chainlink/core/internal/cltest" + "chainlink/core/store/models" + "chainlink/core/store/presenters" + "chainlink/core/web" + "github.com/manyminds/api2go/jsonapi" - "github.com/smartcontractkit/chainlink/core/internal/cltest" - "github.com/smartcontractkit/chainlink/core/store/models" - "github.com/smartcontractkit/chainlink/core/store/presenters" - "github.com/smartcontractkit/chainlink/core/web" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/core/web/transfer_controller.go b/core/web/transfer_controller.go index b5f85b79fd4..ebaa80d6a3f 100644 --- a/core/web/transfer_controller.go +++ b/core/web/transfer_controller.go @@ -5,13 +5,14 @@ import ( "fmt" "net/http" + "chainlink/core/services" + "chainlink/core/store" + "chainlink/core/store/models" + "chainlink/core/store/presenters" + "chainlink/core/utils" + "github.com/ethereum/go-ethereum/common" "github.com/gin-gonic/gin" - "github.com/smartcontractkit/chainlink/core/services" - "github.com/smartcontractkit/chainlink/core/store" - "github.com/smartcontractkit/chainlink/core/store/models" - "github.com/smartcontractkit/chainlink/core/store/presenters" - "github.com/smartcontractkit/chainlink/core/utils" ) // TransfersController can send LINK tokens to another address diff --git a/core/web/transfer_controller_test.go b/core/web/transfer_controller_test.go index 82d6b1b9c34..787bd9ac1cc 100644 --- a/core/web/transfer_controller_test.go +++ b/core/web/transfer_controller_test.go @@ -6,10 +6,11 @@ import ( "net/http" "testing" + "chainlink/core/internal/cltest" + "chainlink/core/store/assets" + "chainlink/core/store/models" + "github.com/ethereum/go-ethereum/common" - "github.com/smartcontractkit/chainlink/core/internal/cltest" - "github.com/smartcontractkit/chainlink/core/store/assets" - "github.com/smartcontractkit/chainlink/core/store/models" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/core/web/tx_attempts_controller.go b/core/web/tx_attempts_controller.go index c3ae50ecf8b..700c3c7d3a5 100644 --- a/core/web/tx_attempts_controller.go +++ b/core/web/tx_attempts_controller.go @@ -1,8 +1,9 @@ package web import ( + "chainlink/core/services" + "github.com/gin-gonic/gin" - "github.com/smartcontractkit/chainlink/core/services" ) // TxAttemptsController lists TxAttempts requests. diff --git a/core/web/tx_attempts_controller_test.go b/core/web/tx_attempts_controller_test.go index f16535a5b8e..17d7f1a0dd2 100644 --- a/core/web/tx_attempts_controller_test.go +++ b/core/web/tx_attempts_controller_test.go @@ -5,10 +5,11 @@ import ( "net/http" "testing" + "chainlink/core/internal/cltest" + "chainlink/core/store/models" + "chainlink/core/web" + "github.com/manyminds/api2go/jsonapi" - "github.com/smartcontractkit/chainlink/core/internal/cltest" - "github.com/smartcontractkit/chainlink/core/store/models" - "github.com/smartcontractkit/chainlink/core/web" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/core/web/user_controller.go b/core/web/user_controller.go index 9d36f633b01..7ff1de14f81 100644 --- a/core/web/user_controller.go +++ b/core/web/user_controller.go @@ -5,14 +5,15 @@ import ( "fmt" "net/http" + "chainlink/core/services" + "chainlink/core/store" + "chainlink/core/store/models" + "chainlink/core/store/presenters" + "chainlink/core/utils" + "github.com/ethereum/go-ethereum/accounts" "github.com/gin-gonic/contrib/sessions" "github.com/gin-gonic/gin" - "github.com/smartcontractkit/chainlink/core/services" - "github.com/smartcontractkit/chainlink/core/store" - "github.com/smartcontractkit/chainlink/core/store/models" - "github.com/smartcontractkit/chainlink/core/store/presenters" - "github.com/smartcontractkit/chainlink/core/utils" ) // UserController manages the current Session's User User. diff --git a/core/web/user_controller_test.go b/core/web/user_controller_test.go index 9fc830d11f9..eb0126a2fd8 100644 --- a/core/web/user_controller_test.go +++ b/core/web/user_controller_test.go @@ -5,8 +5,9 @@ import ( "net/http" "testing" - "github.com/smartcontractkit/chainlink/core/internal/cltest" - "github.com/smartcontractkit/chainlink/core/store/presenters" + "chainlink/core/internal/cltest" + "chainlink/core/store/presenters" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/core/web/withdrawals_controller.go b/core/web/withdrawals_controller.go index 689be55f334..337481f57e4 100644 --- a/core/web/withdrawals_controller.go +++ b/core/web/withdrawals_controller.go @@ -5,12 +5,13 @@ import ( "fmt" "net/http" + "chainlink/core/services" + "chainlink/core/store/assets" + "chainlink/core/store/models" + "chainlink/core/store/presenters" + "chainlink/core/utils" + "github.com/gin-gonic/gin" - "github.com/smartcontractkit/chainlink/core/services" - "github.com/smartcontractkit/chainlink/core/store/assets" - "github.com/smartcontractkit/chainlink/core/store/models" - "github.com/smartcontractkit/chainlink/core/store/presenters" - "github.com/smartcontractkit/chainlink/core/utils" ) // WithdrawalsController can send LINK tokens to another address diff --git a/core/web/withdrawals_controller_test.go b/core/web/withdrawals_controller_test.go index 30e6b3df27b..80031192a66 100644 --- a/core/web/withdrawals_controller_test.go +++ b/core/web/withdrawals_controller_test.go @@ -7,12 +7,13 @@ import ( "net/http" "testing" + "chainlink/core/internal/cltest" + "chainlink/core/internal/mocks" + "chainlink/core/store/assets" + "chainlink/core/store/models" + "github.com/ethereum/go-ethereum/common" "github.com/golang/mock/gomock" - "github.com/smartcontractkit/chainlink/core/internal/cltest" - "github.com/smartcontractkit/chainlink/core/internal/mocks" - "github.com/smartcontractkit/chainlink/core/store/assets" - "github.com/smartcontractkit/chainlink/core/store/models" "github.com/stretchr/testify/assert" ) diff --git a/go.mod b/go.mod index 90ea062d4c4..d636df110ec 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module github.com/smartcontractkit/chainlink +module chainlink go 1.12 diff --git a/tools/ci/go_test b/tools/ci/go_test index e169262729a..3073c63f02b 100755 --- a/tools/ci/go_test +++ b/tools/ci/go_test @@ -11,6 +11,6 @@ yarn workspace chainlink setup go get -u github.com/smartcontractkit/goverage goverage -v -parallel 2 -coverprofile=c.out ./... if [ -n "$CC_TEST_REPORTER_ID" ]; then - cc-test-reporter format-coverage --output "coverage/codeclimate.go.json" --prefix "github.com/smartcontractkit/chainlink" + cc-test-reporter format-coverage --output "coverage/codeclimate.go.json" --prefix "chainlink" gsutil cp "coverage/codeclimate.go.json" gs://codeclimate-aggregation/$CIRCLE_WORKFLOW_ID/ fi From 63b69103f505a1494d11b9d2c9a287c2dafc187b Mon Sep 17 00:00:00 2001 From: "Tabbakha, Ahmad" Date: Thu, 7 Nov 2019 07:44:15 -0600 Subject: [PATCH 124/199] move docker url test to validators_test --- core/services/validators_test.go | 6 ++++ core/web/bridge_types_controller_test.go | 29 ------------------- .../testdata/bridge_with_hostname_url.json | 6 ---- 3 files changed, 6 insertions(+), 35 deletions(-) delete mode 100644 core/web/testdata/bridge_with_hostname_url.json diff --git a/core/services/validators_test.go b/core/services/validators_test.go index 74eac2df1a7..20ed1d5ddd2 100644 --- a/core/services/validators_test.go +++ b/core/services/validators_test.go @@ -130,6 +130,12 @@ func TestValidateAdapter(t *testing.T) { cltest.WebURL(t, "//denergy"), nil, }, + { + "valid docker url", + "adapterwithdockerurl", + cltest.WebURL(t, "http://chainlink_cmc-adapter_1:8080"), + nil, + }, {"new external adapter", "gdaxprice", bt.URL, nil}, } diff --git a/core/web/bridge_types_controller_test.go b/core/web/bridge_types_controller_test.go index 3fea350a823..e4d43a52e4c 100644 --- a/core/web/bridge_types_controller_test.go +++ b/core/web/bridge_types_controller_test.go @@ -127,35 +127,6 @@ func TestBridgeTypesController_Create_Success(t *testing.T) { assert.NotEmpty(t, bt.OutgoingToken) } -func TestBridgeTypesController_Create_Host_URL(t *testing.T) { - t.Parallel() - - app, cleanup := cltest.NewApplication(t) - defer cleanup() - require.NoError(t, app.Start()) - client := app.NewHTTPClient() - - resp, cleanup := client.Post( - "/v2/bridge_types", - bytes.NewBuffer(cltest.MustReadFile(t, "testdata/bridge_with_hostname_url.json")), - ) - defer cleanup() - cltest.AssertServerResponse(t, resp, http.StatusOK) - respJSON := cltest.ParseJSON(t, resp.Body) - btName := respJSON.Get("data.attributes.name").String() - - assert.NotEmpty(t, respJSON.Get("data.attributes.incomingToken").String()) - assert.NotEmpty(t, respJSON.Get("data.attributes.outgoingToken").String()) - - bt, err := app.Store.FindBridge(models.MustNewTaskType(btName)) - assert.NoError(t, err) - assert.Equal(t, "coinmarketcap", bt.Name.String()) - assert.Equal(t, uint32(10), bt.Confirmations) - assert.Equal(t, "http://chainlink_cmc-adapter_1:8080", bt.URL.String()) - assert.Equal(t, assets.NewLink(350), bt.MinimumContractPayment) - assert.NotEmpty(t, bt.OutgoingToken) -} - func TestBridgeTypesController_Update_Success(t *testing.T) { t.Parallel() diff --git a/core/web/testdata/bridge_with_hostname_url.json b/core/web/testdata/bridge_with_hostname_url.json deleted file mode 100644 index fb47a0ccde8..00000000000 --- a/core/web/testdata/bridge_with_hostname_url.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "name": "coinmarketcap", - "url": "http://chainlink_cmc-adapter_1:8080", - "confirmations": 10, - "minimumContractPayment": "350" -} From d314230b1acb5d2104c4cdd1d431f5a31135687d Mon Sep 17 00:00:00 2001 From: "Tabbakha, Ahmad" Date: Thu, 7 Nov 2019 08:06:35 -0600 Subject: [PATCH 125/199] update gorace test to use new module path --- go.sum | 1 + tools/ci/gorace_test | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/go.sum b/go.sum index 1058ebc32d7..ec760356de3 100644 --- a/go.sum +++ b/go.sum @@ -290,6 +290,7 @@ github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeV github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/smartcontractkit/chainlink v0.6.9 h1:HpDYgKaZXFslJI1nqA6QzY4u3Wj+gQhyRmKDk1gk3vE= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI= diff --git a/tools/ci/gorace_test b/tools/ci/gorace_test index 69e5d5940d9..6e7df9b1a17 100755 --- a/tools/ci/gorace_test +++ b/tools/ci/gorace_test @@ -1,4 +1,4 @@ #!/bin/bash set -e -GORACE="halt_on_error=1" go test -v -race -parallel 2 -p 1 github.com/smartcontractkit/chainlink/core/internal github.com/smartcontractkit/chainlink/core/services +GORACE="halt_on_error=1" go test -v -race -parallel 2 -p 1 chainlink/core/internal chainlink/core/services From a1becbeee545cb118e4ee04757e0ceb57f5f5af5 Mon Sep 17 00:00:00 2001 From: Ryan Hall Date: Thu, 7 Nov 2019 10:29:56 -0500 Subject: [PATCH 126/199] update jayson to version 3.1.2 --- explorer/package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/explorer/package.json b/explorer/package.json index 544ee9a2e0a..0e67fbf4bd4 100644 --- a/explorer/package.json +++ b/explorer/package.json @@ -42,7 +42,7 @@ "express": "^4.16.4", "express-winston": "^3.4.0", "helmet": "^3.20.0", - "jayson": "^3.1.1", + "jayson": "^3.1.2", "js-sha256": "^0.9.0", "jsonapi-serializer": "^3.6.4", "local-storage-fallback": "^4.1.1", diff --git a/yarn.lock b/yarn.lock index c11dfe1f82b..38af5f03340 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12937,10 +12937,10 @@ javascript-time-ago@^2.0.1, javascript-time-ago@^2.0.2: dependencies: relative-time-format "^0.1.3" -jayson@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/jayson/-/jayson-3.1.1.tgz#2faedb6e8c8df266c7ef3d9d6a6cc2d2655e226d" - integrity sha512-w+RKdtrRNlqFlyDxcALzTrcKSwETYcU+P9/cqWn8RogXkcV9RSmdL1tUb6E8hglFh8r3I62vVP8xrXkaaKyQdQ== +jayson@^3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/jayson/-/jayson-3.1.2.tgz#1497ef3ebba53bbdb4737ed7a2456079024a846f" + integrity sha512-EYGNAuffmuwSsoL+/wqaILH+2hhUJCdH/XIc+STshrIsS8yjFqYpBCn8qPHogtGl2Hq9/tJ4VoQ8V/tdZdaLDw== dependencies: "@types/connect" "^3.4.32" "@types/express-serve-static-core" "^4.16.9" From 19a25af069e395375cd801d20c0f8283c13d0bb4 Mon Sep 17 00:00:00 2001 From: Ryan Hall Date: Thu, 7 Nov 2019 10:33:33 -0500 Subject: [PATCH 127/199] remove rpcServer call function type patch --- explorer/src/server/handleMessage.ts | 4 ++-- explorer/src/server/rpcServer.ts | 14 +------------- 2 files changed, 3 insertions(+), 15 deletions(-) diff --git a/explorer/src/server/handleMessage.ts b/explorer/src/server/handleMessage.ts index d3629902f7b..1aa6a6529b8 100644 --- a/explorer/src/server/handleMessage.ts +++ b/explorer/src/server/handleMessage.ts @@ -1,4 +1,4 @@ -import { callRPCServer } from './rpcServer' +import rpcServer from './rpcServer' import { logger } from '../logging' import { getDb } from '../database' import { fromString, saveJobRunTree } from '../entity/JobRun' @@ -24,7 +24,7 @@ const handleLegacy = async (json: string, context: ServerContext) => { const handleJSONRCP = (request: string, context: ServerContext) => { return new Promise(resolve => { - callRPCServer( + rpcServer.call( request, context, (error: jayson.JSONRPCErrorLike, result: jayson.JSONRPCResultLike) => { diff --git a/explorer/src/server/rpcServer.ts b/explorer/src/server/rpcServer.ts index 09e8966eb46..33db0adc3c2 100644 --- a/explorer/src/server/rpcServer.ts +++ b/explorer/src/server/rpcServer.ts @@ -5,16 +5,4 @@ const serverOptions = { useContext: true, // permits passing extra data object to RPC methods as 'server context' } -export const rpcServer = new jayson.Server(rpcMethods, serverOptions) - -// broken typing for server.call - should be able to accept 3 arguments -// https://github.com/tedeh/jayson#server-context -// https://github.com/tedeh/jayson/pull/152 - -type callFunction = ( - request: jayson.JSONRPCRequestLike | Array, - context: object, - originalCallback?: jayson.JSONRPCCallbackType, -) => void - -export const callRPCServer: callFunction = rpcServer.call.bind(rpcServer) +export default new jayson.Server(rpcMethods, serverOptions) From 91c1ac6bcca43378e052398c300487f99f5edae6 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Thu, 7 Nov 2019 17:00:21 +0000 Subject: [PATCH 128/199] Bump ws from 7.1.2 to 7.2.0 Bumps [ws](https://github.com/websockets/ws) from 7.1.2 to 7.2.0. - [Release notes](https://github.com/websockets/ws/releases) - [Commits](https://github.com/websockets/ws/compare/7.1.2...7.2.0) Signed-off-by: dependabot-preview[bot] --- explorer/package.json | 2 +- yarn.lock | 23 +++++++++++++++++------ 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/explorer/package.json b/explorer/package.json index 544ee9a2e0a..e9e72bd8f2d 100644 --- a/explorer/package.json +++ b/explorer/package.json @@ -52,7 +52,7 @@ "ts-node": "^8.0.3", "typeorm": "^0.2.15", "winston": "^3.2.1", - "ws": "^7.1.0", + "ws": "^7.2.0", "yargs": "^13.2.2" }, "devDependencies": { diff --git a/yarn.lock b/yarn.lock index c11dfe1f82b..20f51f1c678 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5844,7 +5844,7 @@ buffer-xor@^1.0.3: buffer@5.4.0, buffer@^5.1.0: version "5.4.0" - resolved "https://registry.npmjs.org/buffer/-/buffer-5.4.0.tgz#33294f5c1f26e08461e528b69fa06de3c45cbd8c" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.4.0.tgz#33294f5c1f26e08461e528b69fa06de3c45cbd8c" integrity sha512-Xpgy0IwHK2N01ncykXTy6FpCWuM+CJSHoPVBLyNqyrWxsedpLvwsYUhf0ME3WRFNUhos0dMamz9cOS/xRDtU5g== dependencies: base64-js "^1.0.2" @@ -6186,6 +6186,17 @@ chainlink@0.7.5: truffle-contract "^4.0.31" web3 "^1.2.0" +chainlink@0.7.8: + version "0.7.8" + resolved "https://registry.yarnpkg.com/chainlink/-/chainlink-0.7.8.tgz#4b7f93d629bb35d7201155e30bb587b56579f848" + integrity sha512-S8oCmasY95irJouUKBA02RhX4bSaE4hJ81HMeiF7Cg/GYAkoUg1L2umRc1WLc+3pDg7udMt2pRDmEmx3tpCIpQ== + dependencies: + cbor "^4.1.1" + chainlinkv0.5 "0.0.2" + ethers "^4.0.37" + link_token "^1.0.6" + openzeppelin-solidity "^1.12.0" + chainlink@^0.6.5: version "0.6.5" resolved "https://registry.npmjs.org/chainlink/-/chainlink-0.6.5.tgz#35c8fef8529f4c48a996b6a1d2546cfcdb38af59" @@ -19896,7 +19907,7 @@ sha.js@^2.4.0, sha.js@^2.4.8: sha3@^1.2.2, sha3@^2.0.7: version "2.0.7" - resolved "https://registry.npmjs.org/sha3/-/sha3-2.0.7.tgz#02ed270099647cbcc681b064ba091af72f561f35" + resolved "https://registry.yarnpkg.com/sha3/-/sha3-2.0.7.tgz#02ed270099647cbcc681b064ba091af72f561f35" integrity sha512-7Qsj/0J3pxCWfmyuDbTZWoKNSKY/rg2eecNRvTYE4EAPJ11Uh6xuEObyCG295AqgrtOjzdDbfwtGLDY52h11tA== dependencies: buffer "5.4.0" @@ -23631,10 +23642,10 @@ ws@^6.0.0, ws@^6.1.0, ws@^6.1.2, ws@^6.2.1: dependencies: async-limiter "~1.0.0" -ws@^7.1.0: - version "7.1.2" - resolved "https://registry.npmjs.org/ws/-/ws-7.1.2.tgz#c672d1629de8bb27a9699eb599be47aeeedd8f73" - integrity sha512-gftXq3XI81cJCgkUiAVixA0raD9IVmXqsylCrjRygw4+UOOGzPoxnQ6r/CnVL9i+mDncJo94tSkyrtuuQVBmrg== +ws@^7.2.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/ws/-/ws-7.2.0.tgz#422eda8c02a4b5dba7744ba66eebbd84bcef0ec7" + integrity sha512-+SqNqFbwTm/0DC18KYzIsMTnEWpLwJsiasW/O17la4iDRRIO9uaHbvKiAS3AHgTiuuWerK/brj4O6MYZkei9xg== dependencies: async-limiter "^1.0.0" From c958b29c9dff8e4a3147f3eebcf3fc9339b19127 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Thu, 7 Nov 2019 17:01:59 +0000 Subject: [PATCH 129/199] Bump concurrently from 4.1.2 to 5.0.0 Bumps [concurrently](https://github.com/kimmobrunfeldt/concurrently) from 4.1.2 to 5.0.0. - [Release notes](https://github.com/kimmobrunfeldt/concurrently/releases) - [Commits](https://github.com/kimmobrunfeldt/concurrently/compare/v4.1.2...v5.0.0) Signed-off-by: dependabot-preview[bot] --- explorer/package.json | 2 +- yarn.lock | 48 ++++++++++++++++++++++--------------------- 2 files changed, 26 insertions(+), 24 deletions(-) diff --git a/explorer/package.json b/explorer/package.json index 544ee9a2e0a..16a5fda9871 100644 --- a/explorer/package.json +++ b/explorer/package.json @@ -75,7 +75,7 @@ "@types/uuid": "^3.4.4", "@types/ws": "^6.0.1", "@types/yargs": "^12.0.10", - "concurrently": "^4.1.0", + "concurrently": "^5.0.0", "cross-env": "^6.0.3", "depcheck": "^0.8.3", "eslint": "^6.3.0", diff --git a/yarn.lock b/yarn.lock index c11dfe1f82b..438453417e6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5844,7 +5844,7 @@ buffer-xor@^1.0.3: buffer@5.4.0, buffer@^5.1.0: version "5.4.0" - resolved "https://registry.npmjs.org/buffer/-/buffer-5.4.0.tgz#33294f5c1f26e08461e528b69fa06de3c45cbd8c" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.4.0.tgz#33294f5c1f26e08461e528b69fa06de3c45cbd8c" integrity sha512-Xpgy0IwHK2N01ncykXTy6FpCWuM+CJSHoPVBLyNqyrWxsedpLvwsYUhf0ME3WRFNUhos0dMamz9cOS/xRDtU5g== dependencies: base64-js "^1.0.2" @@ -6186,6 +6186,17 @@ chainlink@0.7.5: truffle-contract "^4.0.31" web3 "^1.2.0" +chainlink@0.7.8: + version "0.7.8" + resolved "https://registry.yarnpkg.com/chainlink/-/chainlink-0.7.8.tgz#4b7f93d629bb35d7201155e30bb587b56579f848" + integrity sha512-S8oCmasY95irJouUKBA02RhX4bSaE4hJ81HMeiF7Cg/GYAkoUg1L2umRc1WLc+3pDg7udMt2pRDmEmx3tpCIpQ== + dependencies: + cbor "^4.1.1" + chainlinkv0.5 "0.0.2" + ethers "^4.0.37" + link_token "^1.0.6" + openzeppelin-solidity "^1.12.0" + chainlink@^0.6.5: version "0.6.5" resolved "https://registry.npmjs.org/chainlink/-/chainlink-0.6.5.tgz#35c8fef8529f4c48a996b6a1d2546cfcdb38af59" @@ -6899,13 +6910,13 @@ concat-stream@1.6.2, concat-stream@^1.5.0, concat-stream@^1.5.1, concat-stream@^ readable-stream "^2.2.2" typedarray "^0.0.6" -concurrently@^4.1.0: - version "4.1.2" - resolved "https://registry.npmjs.org/concurrently/-/concurrently-4.1.2.tgz#1a683b2b5c41e9ed324c9002b9f6e4c6e1f3b6d7" - integrity sha512-Kim9SFrNr2jd8/0yNYqDTFALzUX1tvimmwFWxmp/D4mRI+kbqIIwE2RkBDrxS2ic25O1UgQMI5AtBqdtX3ynYg== +concurrently@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/concurrently/-/concurrently-5.0.0.tgz#99c7567d009411fbdc98299d553c4b99a978612c" + integrity sha512-1yDvK8mduTIdxIxV9C60KoiOySUl/lfekpdbI+U5GXaPrgdffEavFa9QZB3vh68oWOpbCC+TuvxXV9YRPMvUrA== dependencies: chalk "^2.4.2" - date-fns "^1.30.1" + date-fns "^2.0.1" lodash "^4.17.15" read-pkg "^4.0.1" rxjs "^6.5.2" @@ -7622,11 +7633,16 @@ data-urls@^1.0.0, data-urls@^1.1.0: whatwg-mimetype "^2.2.0" whatwg-url "^7.0.0" -date-fns@^1.27.2, date-fns@^1.30.1: +date-fns@^1.27.2: version "1.30.1" resolved "https://registry.npmjs.org/date-fns/-/date-fns-1.30.1.tgz#2e71bf0b119153dbb4cc4e88d9ea5acfb50dc05c" integrity sha512-hBSVCvSmWC+QypYObzwGOd9wqdDpOt+0wl0KbU+R+uuZBS1jN8VsD1ss3irQDknRj5NvxiTF6oj/nDRnN/UQNw== +date-fns@^2.0.1: + version "2.7.0" + resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.7.0.tgz#8271d943cc4636a1f27698f1b8d6a9f1ceb74026" + integrity sha512-wxYp2PGoUDN5ZEACc61aOtYFvSsJUylIvCjpjDOqM1UDaKIIuMJ9fAnMYFHV3TQaDpfTVxhwNK/GiCaHKuemTA== + date-now@^0.1.4: version "0.1.4" resolved "https://registry.npmjs.org/date-now/-/date-now-0.1.4.tgz#eaf439fd4d4848ad74e5cc7dbef200672b9e345b" @@ -19426,27 +19442,13 @@ rxjs@^5.0.0-beta.11: dependencies: symbol-observable "1.0.1" -rxjs@^6.1.0: - version "6.4.0" - resolved "https://registry.npmjs.org/rxjs/-/rxjs-6.4.0.tgz#f3bb0fe7bda7fb69deac0c16f17b50b0b8790504" - integrity sha512-Z9Yfa11F6B9Sg/BK9MnqnQ+aQYicPLtilXBp2yUtDt2JRCE0h26d33EnfO3ZxoNxG0T92OUucP3Ct7cpfkdFfw== - dependencies: - tslib "^1.9.0" - -rxjs@^6.4.0, rxjs@^6.5.3: +rxjs@^6.1.0, rxjs@^6.4.0, rxjs@^6.5.2, rxjs@^6.5.3: version "6.5.3" resolved "https://registry.npmjs.org/rxjs/-/rxjs-6.5.3.tgz#510e26317f4db91a7eb1de77d9dd9ba0a4899a3a" integrity sha512-wuYsAYYFdWTAnAaPoKGNhfpWwKZbJW+HgAJ+mImp+Epl7BG8oNWBCTyRM8gba9k4lk8BgWdoYm21Mo/RYhhbgA== dependencies: tslib "^1.9.0" -rxjs@^6.5.2: - version "6.5.2" - resolved "https://registry.npmjs.org/rxjs/-/rxjs-6.5.2.tgz#2e35ce815cd46d84d02a209fb4e5921e051dbec7" - integrity sha512-HUb7j3kvb7p7eCUHE3FqjoDsC1xfZQ4AHFWfTKSpZ+sAhhz5X1WX0ZuUqWbzB2QhSLp3DoLUG+hMdEDKqWo2Zg== - dependencies: - tslib "^1.9.0" - safe-buffer@5.1.1: version "5.1.1" resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz#893312af69b2123def71f57889001671eeb2c853" @@ -19896,7 +19898,7 @@ sha.js@^2.4.0, sha.js@^2.4.8: sha3@^1.2.2, sha3@^2.0.7: version "2.0.7" - resolved "https://registry.npmjs.org/sha3/-/sha3-2.0.7.tgz#02ed270099647cbcc681b064ba091af72f561f35" + resolved "https://registry.yarnpkg.com/sha3/-/sha3-2.0.7.tgz#02ed270099647cbcc681b064ba091af72f561f35" integrity sha512-7Qsj/0J3pxCWfmyuDbTZWoKNSKY/rg2eecNRvTYE4EAPJ11Uh6xuEObyCG295AqgrtOjzdDbfwtGLDY52h11tA== dependencies: buffer "5.4.0" From 677002b813745445aaac422a8eebe98ddaac7384 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Thu, 7 Nov 2019 17:02:10 +0000 Subject: [PATCH 130/199] Bump class-validator from 0.10.0 to 0.11.0 Bumps [class-validator](https://github.com/typestack/class-validator) from 0.10.0 to 0.11.0. - [Release notes](https://github.com/typestack/class-validator/releases) - [Changelog](https://github.com/typestack/class-validator/blob/master/CHANGELOG.md) - [Commits](https://github.com/typestack/class-validator/compare/0.10.0...v0.11.0) Signed-off-by: dependabot-preview[bot] --- explorer/package.json | 2 +- yarn.lock | 43 +++++++++++++++++++++++++++---------------- 2 files changed, 28 insertions(+), 17 deletions(-) diff --git a/explorer/package.json b/explorer/package.json index 544ee9a2e0a..56940160652 100644 --- a/explorer/package.json +++ b/explorer/package.json @@ -37,7 +37,7 @@ "main": "src/index.ts", "dependencies": { "argon2": "^0.24.1", - "class-validator": "^0.10.0", + "class-validator": "^0.11.0", "cookie-session": "^1.3.3", "express": "^4.16.4", "express-winston": "^3.4.0", diff --git a/yarn.lock b/yarn.lock index c11dfe1f82b..e7e5f42cf29 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3149,10 +3149,10 @@ dependencies: "@types/node" "*" -"@types/validator@10.11.2": - version "10.11.2" - resolved "https://registry.npmjs.org/@types/validator/-/validator-10.11.2.tgz#48b60ca2cca927081f37a1ad1de3e25d04abc9f0" - integrity sha512-k/ju1RsdP5ACFUWebqsyEy0avP5uNJCs2p3pmTHzOZdd4gMSAJTq7iUEHFY3tt3emBrPTm6oGvfZ4SzcqOgLPQ== +"@types/validator@10.11.3": + version "10.11.3" + resolved "https://registry.yarnpkg.com/@types/validator/-/validator-10.11.3.tgz#945799bef24a953c5bc02011ca8ad79331a3ef25" + integrity sha512-GKF2VnEkMmEeEGvoo03ocrP9ySMuX1ypKazIYMlsjfslfBMhOAtC5dmEWKdJioW4lJN7MZRS88kalTsVClyQ9w== "@types/web3-provider-engine@^14.0.0": version "14.0.0" @@ -5844,7 +5844,7 @@ buffer-xor@^1.0.3: buffer@5.4.0, buffer@^5.1.0: version "5.4.0" - resolved "https://registry.npmjs.org/buffer/-/buffer-5.4.0.tgz#33294f5c1f26e08461e528b69fa06de3c45cbd8c" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.4.0.tgz#33294f5c1f26e08461e528b69fa06de3c45cbd8c" integrity sha512-Xpgy0IwHK2N01ncykXTy6FpCWuM+CJSHoPVBLyNqyrWxsedpLvwsYUhf0ME3WRFNUhos0dMamz9cOS/xRDtU5g== dependencies: base64-js "^1.0.2" @@ -6186,6 +6186,17 @@ chainlink@0.7.5: truffle-contract "^4.0.31" web3 "^1.2.0" +chainlink@0.7.8: + version "0.7.8" + resolved "https://registry.yarnpkg.com/chainlink/-/chainlink-0.7.8.tgz#4b7f93d629bb35d7201155e30bb587b56579f848" + integrity sha512-S8oCmasY95irJouUKBA02RhX4bSaE4hJ81HMeiF7Cg/GYAkoUg1L2umRc1WLc+3pDg7udMt2pRDmEmx3tpCIpQ== + dependencies: + cbor "^4.1.1" + chainlinkv0.5 "0.0.2" + ethers "^4.0.37" + link_token "^1.0.6" + openzeppelin-solidity "^1.12.0" + chainlink@^0.6.5: version "0.6.5" resolved "https://registry.npmjs.org/chainlink/-/chainlink-0.6.5.tgz#35c8fef8529f4c48a996b6a1d2546cfcdb38af59" @@ -6411,14 +6422,14 @@ class-utils@^0.3.5: isobject "^3.0.0" static-extend "^0.1.1" -class-validator@^0.10.0: - version "0.10.0" - resolved "https://registry.npmjs.org/class-validator/-/class-validator-0.10.0.tgz#5e5f8108fa200d5ce1906cd5537af102c072dbcd" - integrity sha512-RvjxRlvoCvM/ojUq11j78ISpReGdBoMErdmDk1e27aQZK6ppSXq751UE6jB9JI7ayEnL6Nnmllzn/HXVSu3dmg== +class-validator@^0.11.0: + version "0.11.0" + resolved "https://registry.yarnpkg.com/class-validator/-/class-validator-0.11.0.tgz#5fca8b8a957c738a6749391e03ee81fad375dc4a" + integrity sha512-niAmmSPFku9xsnpYYrddy8NZRrCX3yyoZ/rgPKOilE5BG0Ma1eVCIxpR4X0LasL/6BzbYzsutG+mSbAXlh4zNw== dependencies: - "@types/validator" "10.11.2" + "@types/validator" "10.11.3" google-libphonenumber "^3.1.6" - validator "11.1.0" + validator "12.0.0" classnames@^2.2.5, classnames@^2.2.6: version "2.2.6" @@ -19896,7 +19907,7 @@ sha.js@^2.4.0, sha.js@^2.4.8: sha3@^1.2.2, sha3@^2.0.7: version "2.0.7" - resolved "https://registry.npmjs.org/sha3/-/sha3-2.0.7.tgz#02ed270099647cbcc681b064ba091af72f561f35" + resolved "https://registry.yarnpkg.com/sha3/-/sha3-2.0.7.tgz#02ed270099647cbcc681b064ba091af72f561f35" integrity sha512-7Qsj/0J3pxCWfmyuDbTZWoKNSKY/rg2eecNRvTYE4EAPJ11Uh6xuEObyCG295AqgrtOjzdDbfwtGLDY52h11tA== dependencies: buffer "5.4.0" @@ -22274,10 +22285,10 @@ validate-npm-package-license@^3.0.1: spdx-correct "^3.0.0" spdx-expression-parse "^3.0.0" -validator@11.1.0: - version "11.1.0" - resolved "https://registry.npmjs.org/validator/-/validator-11.1.0.tgz#ac18cac42e0aa5902b603d7a5d9b7827e2346ac4" - integrity sha512-qiQ5ktdO7CD6C/5/mYV4jku/7qnqzjrxb3C/Q5wR3vGGinHTgJZN/TdFT3ZX4vXhX2R1PXx42fB1cn5W+uJ4lg== +validator@12.0.0: + version "12.0.0" + resolved "https://registry.yarnpkg.com/validator/-/validator-12.0.0.tgz#fb33221f5320abe2422cda2f517dc3838064e813" + integrity sha512-r5zA1cQBEOgYlesRmSEwc9LkbfNLTtji+vWyaHzRZUxCTHdsX3bd+sdHfs5tGZ2W6ILGGsxWxCNwT/h3IY/3ng== value-equal@^0.4.0: version "0.4.0" From fc8dcf5ef632eb1b840b3673cf27d9a184b6ccfa Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Thu, 7 Nov 2019 18:53:19 +0000 Subject: [PATCH 131/199] Bump core-js from 3.2.1 to 3.4.0 Bumps [core-js](https://github.com/zloirock/core-js) from 3.2.1 to 3.4.0. - [Release notes](https://github.com/zloirock/core-js/releases) - [Changelog](https://github.com/zloirock/core-js/blob/master/CHANGELOG.md) - [Commits](https://github.com/zloirock/core-js/compare/v3.2.1...v3.4.0) Signed-off-by: dependabot-preview[bot] --- operator_ui/package.json | 2 +- styleguide/package.json | 2 +- yarn.lock | 8 ++++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/operator_ui/package.json b/operator_ui/package.json index d5237cb727a..c3b839a2585 100644 --- a/operator_ui/package.json +++ b/operator_ui/package.json @@ -28,7 +28,7 @@ "bignumber.js": "^9.0.0", "change-case": "^3.0.2", "classnames": "^2.2.6", - "core-js": "^3.2.1", + "core-js": "^3.4.0", "formik": "^1.0.3", "humps": "^2.0.1", "isomorphic-unfetch": "^3.0.0", diff --git a/styleguide/package.json b/styleguide/package.json index 05117408553..7f9a8e9d253 100644 --- a/styleguide/package.json +++ b/styleguide/package.json @@ -17,7 +17,7 @@ "dependencies": { "@material-ui/core": "^3.9.2", "change-case": "^3.0.2", - "core-js": "^3.2.1", + "core-js": "^3.4.0", "javascript-time-ago": "^2.0.1", "moment": "^2.24.0", "prop-types": "^15.6.2", diff --git a/yarn.lock b/yarn.lock index fa627e6a305..a02261baece 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7126,10 +7126,10 @@ core-js@^2.4.0, core-js@^2.5.0, core-js@^2.6.5, core-js@^2.6.9: resolved "https://registry.npmjs.org/core-js/-/core-js-2.6.9.tgz#6b4b214620c834152e179323727fc19741b084f2" integrity sha512-HOpZf6eXmnl7la+cUdMnLvUxKNqLUzJvgIziQ0DiF3JwSImNphIqdGqzj6hIKyX04MmV0poclQ7+wjWvxQyR2A== -core-js@^3.0.1, core-js@^3.0.4, core-js@^3.2.1: - version "3.2.1" - resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.2.1.tgz#cd41f38534da6cc59f7db050fe67307de9868b09" - integrity sha512-Qa5XSVefSVPRxy2XfUC13WbvqkxhkwB3ve+pgCQveNgYzbM/UxZeu1dcOX/xr4UmfUd+muuvsaxilQzCyUurMw== +core-js@^3.0.1, core-js@^3.0.4, core-js@^3.4.0: + version "3.4.0" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.4.0.tgz#29ea478601789c72f2978e9bb98f43546f89d3aa" + integrity sha512-lQxb4HScV71YugF/X28LtePZj9AB7WqOpcB+YztYxusvhrgZiQXPmCYfPC5LHsw/+ScEtDbXU3xbqH3CjBRmYA== core-util-is@1.0.2, core-util-is@~1.0.0: version "1.0.2" From 775101d237212584a64ac29417bcd57c2e2dbc8c Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Thu, 7 Nov 2019 18:54:42 +0000 Subject: [PATCH 132/199] Bump @types/yargs from 12.0.12 to 13.0.3 Bumps [@types/yargs](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/yargs) from 12.0.12 to 13.0.3. - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/yargs) Signed-off-by: dependabot-preview[bot] --- explorer/package.json | 2 +- yarn.lock | 13 ++++--------- 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/explorer/package.json b/explorer/package.json index 66899a2772d..5415f21c95b 100644 --- a/explorer/package.json +++ b/explorer/package.json @@ -74,7 +74,7 @@ "@types/supertest": "^2.0.7", "@types/uuid": "^3.4.4", "@types/ws": "^6.0.1", - "@types/yargs": "^12.0.10", + "@types/yargs": "^13.0.3", "concurrently": "^5.0.0", "cross-env": "^6.0.3", "depcheck": "^0.8.3", diff --git a/yarn.lock b/yarn.lock index fa627e6a305..b107ce1f9e8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3191,15 +3191,10 @@ resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-11.1.3.tgz#33c8ebf05f78f1edeb249c1cde1a42ae57f5664e" integrity sha512-moBUF6X8JsK5MbLZGP3vCfG/TVHZHsaePj3EimlLKp8+ESUjGjpXalxyn90a2L9fTM2ZGtW4swb6Am1DvVRNGA== -"@types/yargs@^12.0.10": - version "12.0.12" - resolved "https://registry.npmjs.org/@types/yargs/-/yargs-12.0.12.tgz#45dd1d0638e8c8f153e87d296907659296873916" - integrity sha512-SOhuU4wNBxhhTHxYaiG5NY4HBhDIDnJF60GU+2LqHAdKKer86//e4yg69aENCtQ04n0ovz+tq2YPME5t5yp4pw== - -"@types/yargs@^13.0.0": - version "13.0.2" - resolved "https://registry.npmjs.org/@types/yargs/-/yargs-13.0.2.tgz#a64674fc0149574ecd90ba746e932b5a5f7b3653" - integrity sha512-lwwgizwk/bIIU+3ELORkyuOgDjCh7zuWDFqRtPPhhVgq9N1F7CvLNKg1TX4f2duwtKQ0p044Au9r1PLIXHrIzQ== +"@types/yargs@^13.0.0", "@types/yargs@^13.0.3": + version "13.0.3" + resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-13.0.3.tgz#76482af3981d4412d65371a318f992d33464a380" + integrity sha512-K8/LfZq2duW33XW/tFwEAfnZlqIfVsoyRB3kfXdPXYhl0nfM8mmh7GS0jg7WrX2Dgq/0Ha/pR1PaR+BvmWwjiQ== dependencies: "@types/yargs-parser" "*" From 14379dde1be1a33841d846adc4a3979a6dbab010 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Thu, 7 Nov 2019 18:56:05 +0000 Subject: [PATCH 133/199] Bump axios from 0.18.1 to 0.19.0 Bumps [axios](https://github.com/axios/axios) from 0.18.1 to 0.19.0. - [Release notes](https://github.com/axios/axios/releases) - [Changelog](https://github.com/axios/axios/blob/master/CHANGELOG.md) - [Commits](https://github.com/axios/axios/compare/v0.18.1...v0.19.0) Signed-off-by: dependabot-preview[bot] --- tools/package.json | 2 +- yarn.lock | 10 +++++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/tools/package.json b/tools/package.json index 692fb8efee6..f036f043b07 100644 --- a/tools/package.json +++ b/tools/package.json @@ -9,7 +9,7 @@ "setup": "echo \"No setup required for @chainlink/tools\"" }, "dependencies": { - "axios": "^0.18.1" + "axios": "^0.19.0" }, "devDependencies": { "@chainlink/eslint-config": "0.0.1", diff --git a/yarn.lock b/yarn.lock index fa627e6a305..21bcd52f898 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4304,7 +4304,7 @@ aws4@^1.8.0: resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.8.0.tgz#f0e003d9ca9e7f59c7a508945d7b2ef9a04a542f" integrity sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ== -axios@^0.18.0, axios@^0.18.1: +axios@^0.18.0: version "0.18.1" resolved "https://registry.yarnpkg.com/axios/-/axios-0.18.1.tgz#ff3f0de2e7b5d180e757ad98000f1081b87bcea3" integrity sha512-0BfJq4NSfQXd+SkFdrvFbG7addhYSBA2mQwISr46pD6E5iqkWg02RAs8vyTT/j0RTnoYmeXauBuSv1qKwR179g== @@ -4312,6 +4312,14 @@ axios@^0.18.0, axios@^0.18.1: follow-redirects "1.5.10" is-buffer "^2.0.2" +axios@^0.19.0: + version "0.19.0" + resolved "https://registry.yarnpkg.com/axios/-/axios-0.19.0.tgz#8e09bff3d9122e133f7b8101c8fbdd00ed3d2ab8" + integrity sha512-1uvKqKQta3KBxIz14F2v06AEHZ/dIoeKfbTRkK1E5oqjDnuEerLmYTgJB5AiQZHJcljpg1TuRzdjDR06qNk0DQ== + dependencies: + follow-redirects "1.5.10" + is-buffer "^2.0.2" + axobject-query@^2.0.2: version "2.0.2" resolved "https://registry.npmjs.org/axobject-query/-/axobject-query-2.0.2.tgz#ea187abe5b9002b377f925d8bf7d1c561adf38f9" From 7ee8855439b135702ab127fa5fd01d860d8e6007 Mon Sep 17 00:00:00 2001 From: Bartosz Wojciechowski Date: Thu, 7 Nov 2019 22:33:36 +0100 Subject: [PATCH 134/199] [Fixes #169608434] Fix user logout in OperatorUI --- operator_ui/src/components/AvatarMenu.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/operator_ui/src/components/AvatarMenu.js b/operator_ui/src/components/AvatarMenu.js index b36444c49db..276828519f0 100644 --- a/operator_ui/src/components/AvatarMenu.js +++ b/operator_ui/src/components/AvatarMenu.js @@ -55,7 +55,10 @@ const AvatarMenu = useHooks(({ classes, submitSignOut }) => { if (anchorEl.current.contains(event.target)) { return } + setOpenState(false) + } + const handleLogOut = () => { submitSignOut() setOpenState(false) } @@ -89,7 +92,10 @@ const AvatarMenu = useHooks(({ classes, submitSignOut }) => { - + Log out From 5b1fb07fd14ab196a50c67fe03b59f1bc3643328 Mon Sep 17 00:00:00 2001 From: Steve Ellis Date: Fri, 8 Nov 2019 15:53:36 -0500 Subject: [PATCH 135/199] remove Appveyor CI builds --- tools/ci/appveyor.yml | 44 ------------------------------------------- 1 file changed, 44 deletions(-) delete mode 100644 tools/ci/appveyor.yml diff --git a/tools/ci/appveyor.yml b/tools/ci/appveyor.yml deleted file mode 100644 index cb92cf78c0d..00000000000 --- a/tools/ci/appveyor.yml +++ /dev/null @@ -1,44 +0,0 @@ -build: false - -clone_folder: c:\chainlink - -deploy: false - -environment: - CC: gcc.exe - CGO_ENABLED: 1 - GOPATH: c:\gopath - GOVERSION: 1.12.6 - MSYS2_ARCH: x86_64 - MSYS2_BITS: 64 - MSYSTEM: MINGW64 - PATH: C:\msys64\mingw64\bin\;C:\Program Files (x86)\NSIS\;%PATH% - nodejs_version: "10" - PYTHON: "3.7" - -stack: python %PYTHON% - -init: - - git config --global core.autocrlf input - -install: - # Install the configured nodejs version - - ps: Install-Product node $env:nodejs_version - # Generate solidity contract artifacts - - yarn install - - yarn workspace chainlinkv0.5 setup - - yarn workspace chainlink setup - - # Install the specific Go version. - - rmdir c:\go /s /q - - appveyor DownloadFile https://storage.googleapis.com/golang/go%GOVERSION%.windows-amd64.msi - - msiexec /i go%GOVERSION%.windows-amd64.msi /q - - set Path=c:\go\bin;c:\gopath\bin;C:\msys64\mingw64\bin\;%Path% - # Check tools exist. - - gcc --version - - go version - - go env - - go mod download - -test_script: - - go test -parallel 2 -p 1 ./... From 24c04be177e510f2f5097e28969e87cd841db0a6 Mon Sep 17 00:00:00 2001 From: Steve Ellis Date: Fri, 8 Nov 2019 15:58:47 -0500 Subject: [PATCH 136/199] remove forks build --- .circleci/config.yml | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index fc977d0889f..1d762f3a87c 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -279,22 +279,6 @@ jobs: command: yarn workspace @chainlink/explorer-client run build && yarn workspace @chainlink/explorer run test-ci:e2e:silent - store_artifacts: path: ./integration/logs - forks: - machine: - image: ubuntu-1604:201903-01 - docker_layer_caching: true - working_directory: ~/chainlink - steps: - - checkout - - run: - name: Install Yarn - command: npm install -g yarn - - run: - name: Install New Packages - command: yarn install - - run: ./tools/ci/forks_test - - store_artifacts: - path: ./integration/logs build-publish-explorer: machine: true steps: @@ -418,10 +402,6 @@ workflows: filters: # all branches, and /^explorer-v../ tags for build-publish... tags: only: /^explorer-v.*/ - - forks: - filters: - tags: - only: /^v.*/ - build-chainlink: filters: tags: From 8f76d33a1b26f1903a678eeace2b937034f41fe8 Mon Sep 17 00:00:00 2001 From: Bartosz Wojciechowski Date: Fri, 8 Nov 2019 23:14:29 +0100 Subject: [PATCH 137/199] [Fixes #169659642] Fix Operator UI tests --- operator_ui/__tests__/actions.test.ts | 5 +++-- .../__tests__/containers/Bridges/Index.test.js | 9 +++++---- .../__tests__/containers/Bridges/Show.test.js | 3 ++- .../__tests__/containers/Configuration.test.js | 3 ++- .../containers/Dashboards/Index.test.js | 7 ++++--- .../__tests__/containers/JobRuns/Index.test.js | 16 +++++++++------- .../containers/JobRuns/Show/Overview.test.js | 3 ++- .../__tests__/containers/Jobs/Index.test.js | 9 +++++---- .../__tests__/containers/Jobs/Show.test.js | 9 +++++---- operator_ui/__tests__/containers/SignIn.test.js | 5 +++-- operator_ui/support/test-helpers/globPath.js | 3 +++ 11 files changed, 43 insertions(+), 29 deletions(-) create mode 100644 operator_ui/support/test-helpers/globPath.js diff --git a/operator_ui/__tests__/actions.test.ts b/operator_ui/__tests__/actions.test.ts index 49abf51042c..046a1a3afdd 100644 --- a/operator_ui/__tests__/actions.test.ts +++ b/operator_ui/__tests__/actions.test.ts @@ -4,6 +4,7 @@ import jsonApiJobSpecRunFactory from 'factories/jsonApiJobSpecRun' import configureStore from 'redux-mock-store' import thunk from 'redux-thunk' import isoDate, { MINUTE_MS } from 'test-helpers/isoDate' +import globPath from 'test-helpers/globPath' const middlewares = [thunk] const mockStore = configureStore(middlewares) @@ -28,7 +29,7 @@ describe('fetchJob', () => { tasks: [expectedTask], }) - global.fetch.getOnce(`/v2/specs/${jobSpecId}`, jobSpecResponse) + global.fetch.getOnce(globPath(`/v2/specs/${jobSpecId}`), jobSpecResponse) const store = mockStore({}) return store.dispatch(actions.fetchJob(jobSpecId)).then(() => { @@ -60,7 +61,7 @@ describe('fetchJobRun', () => { }) const id = runResponse.data.id - global.fetch.getOnce(`/v2/runs/${id}`, runResponse) + global.fetch.getOnce(globPath(`/v2/runs/${id}`), runResponse) const store = mockStore({}) return store.dispatch(actions.fetchJobRun(id)).then(() => { diff --git a/operator_ui/__tests__/containers/Bridges/Index.test.js b/operator_ui/__tests__/containers/Bridges/Index.test.js index 1514b5418fb..8cb66a2c4a7 100644 --- a/operator_ui/__tests__/containers/Bridges/Index.test.js +++ b/operator_ui/__tests__/containers/Bridges/Index.test.js @@ -9,6 +9,7 @@ import { MemoryRouter } from 'react-router-dom' import clickNextPage from 'test-helpers/clickNextPage' import clickPreviousPage from 'test-helpers/clickPreviousPage' import syncFetch from 'test-helpers/syncFetch' +import globPath from 'test-helpers/globPath' const classes = {} const mountIndex = (opts = {}) => @@ -30,7 +31,7 @@ describe('containers/Bridges/Index', () => { url: 'butbobistho.com', }, ]) - global.fetch.getOnce('begin:/v2/bridge_types', bridgesResponse) + global.fetch.getOnce(globPath('/v2/bridge_types'), bridgesResponse) const wrapper = mountIndex() @@ -46,7 +47,7 @@ describe('containers/Bridges/Index', () => { [{ name: 'ID-ON-FIRST-PAGE', url: 'bridge.com' }], 2, ) - global.fetch.getOnce('begin:/v2/bridge_types', pageOneResponse) + global.fetch.getOnce(globPath('/v2/bridge_types'), pageOneResponse) const wrapper = mountIndex({ pageSize: 1 }) @@ -58,14 +59,14 @@ describe('containers/Bridges/Index', () => { [{ name: 'ID-ON-SECOND-PAGE', url: 'bridge.com' }], 2, ) - global.fetch.getOnce('begin:/v2/bridge_types', pageTwoResponse) + global.fetch.getOnce(globPath('/v2/bridge_types'), pageTwoResponse) clickNextPage(wrapper) await syncFetch(wrapper) expect(wrapper.text()).not.toContain('ID-ON-FIRST-PAGE') expect(wrapper.text()).toContain('ID-ON-SECOND-PAGE') - global.fetch.getOnce('begin:/v2/bridge_types', pageOneResponse) + global.fetch.getOnce(globPath('/v2/bridge_types'), pageOneResponse) clickPreviousPage(wrapper) await syncFetch(wrapper) diff --git a/operator_ui/__tests__/containers/Bridges/Show.test.js b/operator_ui/__tests__/containers/Bridges/Show.test.js index c5ed30d8280..26173f0ba07 100644 --- a/operator_ui/__tests__/containers/Bridges/Show.test.js +++ b/operator_ui/__tests__/containers/Bridges/Show.test.js @@ -5,6 +5,7 @@ import { mount } from 'enzyme' import { Provider } from 'react-redux' import { MemoryRouter } from 'react-router-dom' import { ConnectedShow as Show } from 'containers/Bridges/Show' +import globPath from 'test-helpers/globPath' const classes = {} const mountShow = props => @@ -32,7 +33,7 @@ describe('containers/Bridges/Show', () => { }, } - global.fetch.getOnce(`/v2/bridge_types/tallbridge`, response) + global.fetch.getOnce(globPath(`/v2/bridge_types/tallbridge`), response) const props = { match: { params: { bridgeId: 'tallbridge' } } } const wrapper = mountShow(props) diff --git a/operator_ui/__tests__/containers/Configuration.test.js b/operator_ui/__tests__/containers/Configuration.test.js index 8ecdc77bbd5..05f2eb806cc 100644 --- a/operator_ui/__tests__/containers/Configuration.test.js +++ b/operator_ui/__tests__/containers/Configuration.test.js @@ -4,6 +4,7 @@ import configurationFactory from 'factories/configuration' import React from 'react' import mountWithinStoreAndRouter from 'test-helpers/mountWithinStoreAndRouter' import syncFetch from 'test-helpers/syncFetch' +import globPath from 'test-helpers/globPath' const classes = {} const mount = props => { @@ -20,7 +21,7 @@ describe('containers/Configuration', () => { band: 'Major Lazer', singer: 'Bob Marley', }) - global.fetch.getOnce('/v2/config', configurationResponse) + global.fetch.getOnce(globPath('/v2/config'), configurationResponse) const wrapper = mount() diff --git a/operator_ui/__tests__/containers/Dashboards/Index.test.js b/operator_ui/__tests__/containers/Dashboards/Index.test.js index 85fced5c5a0..71fb0bda2ea 100644 --- a/operator_ui/__tests__/containers/Dashboards/Index.test.js +++ b/operator_ui/__tests__/containers/Dashboards/Index.test.js @@ -4,6 +4,7 @@ import accountBalanceFactory from 'factories/accountBalance' import React from 'react' import mountWithTheme from 'test-helpers/mountWithTheme' import syncFetch from 'test-helpers/syncFetch' +import globPath from 'test-helpers/globPath' const classes = {} const mountIndex = () => mountWithTheme() @@ -35,7 +36,7 @@ describe('containers/Dashboards/Index', () => { ], meta: { count: 2 }, } - global.fetch.getOnce('begin:/v2/runs', recentJobRuns) + global.fetch.getOnce(globPath('/v2/runs'), recentJobRuns) const recentlyCreatedJobsResponse = { data: [ @@ -57,13 +58,13 @@ describe('containers/Dashboards/Index', () => { }, ], } - global.fetch.getOnce('begin:/v2/specs', recentlyCreatedJobsResponse) + global.fetch.getOnce(globPath('/v2/specs'), recentlyCreatedJobsResponse) const accountBalanceResponse = accountBalanceFactory( '10123456000000000000000', '7467870000000000000000', ) - global.fetch.getOnce('/v2/user/balances', accountBalanceResponse) + global.fetch.getOnce(globPath('/v2/user/balances'), accountBalanceResponse) const wrapper = mountIndex() diff --git a/operator_ui/__tests__/containers/JobRuns/Index.test.js b/operator_ui/__tests__/containers/JobRuns/Index.test.js index 9d66f0b97fa..6db92a5b7fa 100644 --- a/operator_ui/__tests__/containers/JobRuns/Index.test.js +++ b/operator_ui/__tests__/containers/JobRuns/Index.test.js @@ -10,6 +10,7 @@ import clickNextPage from 'test-helpers/clickNextPage' import clickPreviousPage from 'test-helpers/clickPreviousPage' import mountWithTheme from 'test-helpers/mountWithTheme' import syncFetch from 'test-helpers/syncFetch' +import globPath from 'test-helpers/globPath' const classes = {} const mountIndex = props => @@ -32,7 +33,7 @@ describe('containers/JobRuns/Index', () => { expect.assertions(2) const runsResponse = jsonApiJobSpecRunFactory([{ jobId: jobSpecId }]) - global.fetch.getOnce(`begin:/v2/runs`, runsResponse) + global.fetch.getOnce(globPath('/v2/runs'), runsResponse) const props = { match: { params: { jobSpecId: jobSpecId } } } const wrapper = mountIndex(props) @@ -49,7 +50,7 @@ describe('containers/JobRuns/Index', () => { [{ id: 'ID-ON-FIRST-PAGE', jobId: jobSpecId }], 3, ) - global.fetch.getOnce(`begin:/v2/runs`, pageOneResponse) + global.fetch.getOnce(globPath('/v2/runs'), pageOneResponse) const props = { match: { params: { jobSpecId: jobSpecId } }, pageSize: 1 } const wrapper = mountIndex(props) @@ -62,14 +63,14 @@ describe('containers/JobRuns/Index', () => { [{ id: 'ID-ON-SECOND-PAGE', jobId: jobSpecId }], 3, ) - global.fetch.getOnce(`begin:/v2/runs`, pageTwoResponse) + global.fetch.getOnce(globPath('/v2/runs'), pageTwoResponse) clickNextPage(wrapper) await syncFetch(wrapper) expect(wrapper.text()).not.toContain('ID-ON-FIRST-PAGE') expect(wrapper.text()).toContain('ID-ON-SECOND-PAGE') - global.fetch.getOnce(`begin:/v2/runs`, pageOneResponse) + global.fetch.getOnce(globPath('/v2/runs'), pageOneResponse) clickPreviousPage(wrapper) await syncFetch(wrapper) @@ -80,7 +81,7 @@ describe('containers/JobRuns/Index', () => { [{ id: 'ID-ON-THIRD-PAGE', jobId: jobSpecId }], 3, ) - global.fetch.getOnce(`begin:/v2/runs`, pageThreeResponse) + global.fetch.getOnce(globPath('/v2/runs'), pageThreeResponse) clickLastPage(wrapper) await syncFetch(wrapper) @@ -88,7 +89,7 @@ describe('containers/JobRuns/Index', () => { expect(wrapper.text()).not.toContain('ID-ON-FIRST-PAGE') expect(wrapper.text()).not.toContain('ID-ON-SECOND-PAGE') - global.fetch.getOnce(`begin:/v2/runs`, pageOneResponse) + global.fetch.getOnce(globPath('/v2/runs'), pageOneResponse) clickFirstPage(wrapper) await syncFetch(wrapper) @@ -101,10 +102,11 @@ describe('containers/JobRuns/Index', () => { expect.assertions(1) const runsResponse = jsonApiJobSpecRunFactory([]) - global.fetch.getOnce(`begin:/v2/runs`, runsResponse) + await global.fetch.getOnce(`glob:*/v2/runs*`, runsResponse) const props = { match: { params: { jobSpecId: jobSpecId } } } const wrapper = mountIndex(props) + console.log('yeah', wrapper) await syncFetch(wrapper) expect(wrapper.text()).toContain('No jobs have been run yet') diff --git a/operator_ui/__tests__/containers/JobRuns/Show/Overview.test.js b/operator_ui/__tests__/containers/JobRuns/Show/Overview.test.js index 5b663e09af8..786343305b5 100644 --- a/operator_ui/__tests__/containers/JobRuns/Show/Overview.test.js +++ b/operator_ui/__tests__/containers/JobRuns/Show/Overview.test.js @@ -7,6 +7,7 @@ import { MemoryRouter } from 'react-router-dom' import { ConnectedShow as Show } from 'containers/JobRuns/Show/Overview' import isoDate, { MINUTE_MS } from 'test-helpers/isoDate' import mountWithTheme from 'test-helpers/mountWithTheme' +import globPath from 'test-helpers/globPath' const classes = {} const mountShow = props => @@ -48,7 +49,7 @@ describe('containers/JobRuns/Show/Overview', () => { }, }, }) - global.fetch.getOnce(`/v2/runs/${jobRunId}`, jobRunResponse) + global.fetch.getOnce(globPath(`/v2/runs/${jobRunId}`), jobRunResponse) const props = { match: { params: { jobSpecId: jobSpecId, jobRunId: jobRunId } }, diff --git a/operator_ui/__tests__/containers/Jobs/Index.test.js b/operator_ui/__tests__/containers/Jobs/Index.test.js index 5962cac2f2e..0bf6cb391c2 100644 --- a/operator_ui/__tests__/containers/Jobs/Index.test.js +++ b/operator_ui/__tests__/containers/Jobs/Index.test.js @@ -9,6 +9,7 @@ import { MemoryRouter } from 'react-router-dom' import clickNextPage from 'test-helpers/clickNextPage' import clickPreviousPage from 'test-helpers/clickPreviousPage' import syncFetch from 'test-helpers/syncFetch' +import globPath from 'test-helpers/globPath' const classes = {} const mountIndex = (opts = {}) => @@ -31,7 +32,7 @@ describe('containers/Jobs/Index', () => { createdAt: new Date().toISOString(), }, ]) - global.fetch.getOnce(`begin:/v2/specs`, jobSpecsResponse) + global.fetch.getOnce(globPath('/v2/specs'), jobSpecsResponse) const wrapper = mountIndex() @@ -48,7 +49,7 @@ describe('containers/Jobs/Index', () => { [{ id: 'ID-ON-FIRST-PAGE' }], 2, ) - global.fetch.getOnce(`begin:/v2/specs`, pageOneResponse) + global.fetch.getOnce(globPath('/v2/specs'), pageOneResponse) const wrapper = mountIndex({ pageSize: 1 }) @@ -60,14 +61,14 @@ describe('containers/Jobs/Index', () => { [{ id: 'ID-ON-SECOND-PAGE' }], 2, ) - global.fetch.getOnce(`begin:/v2/specs`, pageTwoResponse) + global.fetch.getOnce(globPath('/v2/specs'), pageTwoResponse) clickNextPage(wrapper) await syncFetch(wrapper) expect(wrapper.text()).not.toContain('ID-ON-FIRST-PAGE') expect(wrapper.text()).toContain('ID-ON-SECOND-PAGE') - global.fetch.getOnce(`begin:/v2/specs`, pageOneResponse) + global.fetch.getOnce(globPath('/v2/specs'), pageOneResponse) clickPreviousPage(wrapper) await syncFetch(wrapper) diff --git a/operator_ui/__tests__/containers/Jobs/Show.test.js b/operator_ui/__tests__/containers/Jobs/Show.test.js index af2c22022bc..5a1790fa9b1 100644 --- a/operator_ui/__tests__/containers/Jobs/Show.test.js +++ b/operator_ui/__tests__/containers/Jobs/Show.test.js @@ -8,6 +8,7 @@ import { MemoryRouter } from 'react-router-dom' import isoDate, { MINUTE_MS } from 'test-helpers/isoDate' import mountWithTheme from 'test-helpers/mountWithTheme' import syncFetch from 'test-helpers/syncFetch' +import globPath from 'test-helpers/globPath' import { GWEI_PER_TOKEN, WEI_PER_TOKEN } from '../../../src/utils/constants' const mountShow = props => @@ -33,7 +34,7 @@ describe('containers/Jobs/Show', () => { earnings: GWEI_PER_TOKEN, minPayment: 100 * WEI_PER_TOKEN, }) - global.fetch.getOnce(`/v2/specs/${jobSpecId}`, jobSpecResponse) + global.fetch.getOnce(globPath(`/v2/specs/${jobSpecId}`), jobSpecResponse) const jobRunResponse = jsonApiJobSpecRunsFactory([ { @@ -42,7 +43,7 @@ describe('containers/Jobs/Show', () => { status: 'pending', }, ]) - global.fetch.getOnce(`begin:/v2/runs`, jobRunResponse) + global.fetch.getOnce(globPath('/v2/runs'), jobRunResponse) const props = { match: { params: { jobSpecId: jobSpecId } } } const wrapper = mountShow(props) @@ -71,8 +72,8 @@ describe('containers/Jobs/Show', () => { }) const jobRunsResponse = jsonApiJobSpecRunsFactory(runs) - global.fetch.getOnce(`begin:/v2/specs/${jobSpecId}`, jobSpecResponse) - global.fetch.getOnce(`begin:/v2/runs`, jobRunsResponse) + global.fetch.getOnce(globPath(`/v2/specs/${jobSpecId}`), jobSpecResponse) + global.fetch.getOnce(globPath('/v2/runs'), jobRunsResponse) const props = { match: { params: { jobSpecId: jobSpecId } } } const wrapper = mountShow(props) diff --git a/operator_ui/__tests__/containers/SignIn.test.js b/operator_ui/__tests__/containers/SignIn.test.js index 62471769d05..0958cf1b59f 100644 --- a/operator_ui/__tests__/containers/SignIn.test.js +++ b/operator_ui/__tests__/containers/SignIn.test.js @@ -7,6 +7,7 @@ import { Switch, Route } from 'react-router-dom' import { MemoryRouter } from 'react-router' import SignIn from 'containers/SignIn' import fillIn from 'test-helpers/fillIn' +import globPath from 'test-helpers/globPath' const RedirectApp = () =>
Behind authentication
const mountSignIn = store => @@ -39,7 +40,7 @@ const AUTHENTICATED_RESPONSE = { describe('containers/SignIn', () => { it('unauthenticated user can input credentials and sign in', async () => { const store = createStore() - global.fetch.postOnce(`/sessions`, AUTHENTICATED_RESPONSE) + global.fetch.postOnce(globPath('/sessions'), AUTHENTICATED_RESPONSE) const wrapper = mountSignIn(store) submitForm(wrapper) @@ -55,7 +56,7 @@ describe('containers/SignIn', () => { it('unauthenticated user inputs wrong credentials', async () => { const store = createStore() global.fetch.postOnce( - '/sessions', + globPath('/sessions'), { authenticated: false, errors: [{ detail: 'Invalid email' }] }, { response: { status: 401 } }, ) diff --git a/operator_ui/support/test-helpers/globPath.js b/operator_ui/support/test-helpers/globPath.js new file mode 100644 index 00000000000..67adcb76e42 --- /dev/null +++ b/operator_ui/support/test-helpers/globPath.js @@ -0,0 +1,3 @@ +export default path => { + return `glob:${process.env.CHAINLINK_PORT || ''}${path}*` +} From 31138af41aaadeef86ef941693ea8b7924cdc578 Mon Sep 17 00:00:00 2001 From: John Barker Date: Thu, 7 Nov 2019 13:05:41 -0700 Subject: [PATCH 138/199] Silence chainlink warning about not having a TLS cert on startup --- integration/forks/docker-compose.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/integration/forks/docker-compose.yaml b/integration/forks/docker-compose.yaml index c8c7a201010..d46cb8db312 100644 --- a/integration/forks/docker-compose.yaml +++ b/integration/forks/docker-compose.yaml @@ -41,6 +41,7 @@ services: - MIN_OUTGOING_CONFIRMATIONS=2 - MINIMUM_CONTRACT_PAYMENT=1000000000000 - CHAINLINK_DEV=true + - CHAINLINK_TLS_PORT=0 networks: gethnet: ipv4_address: 172.16.1.102 From 9c08cbca625f2797ee16c4b02329112a90852c83 Mon Sep 17 00:00:00 2001 From: John Barker Date: Thu, 7 Nov 2019 13:29:11 -0700 Subject: [PATCH 139/199] Ensure the chainlink version running is the same as the current commit --- integration/forks/test_helpers | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/integration/forks/test_helpers b/integration/forks/test_helpers index e656d06cb2a..51914b219d9 100644 --- a/integration/forks/test_helpers +++ b/integration/forks/test_helpers @@ -42,6 +42,7 @@ initial_setup() { build_images start_network waitForResponse "172.16.1.102:6688" + assert_testing_correct_artifact create_job } @@ -56,6 +57,12 @@ assert_not_in_chainlink_logs() { fi; } +assert_testing_correct_artifact() { + version="`cat ../../VERSION`" + sha="`git rev-parse HEAD`" + search_chainlink_logs "Starting Chainlink Node ${version} at commit ${sha}" +} + search_chainlink_logs() { echo "searching for \"$1\" ... " check_count=0; From 2a377819618929d380d6ca21b490e9d3f9786243 Mon Sep 17 00:00:00 2001 From: John Barker Date: Thu, 7 Nov 2019 16:29:11 -0700 Subject: [PATCH 140/199] Do curl via docker image with same network as fork test --- integration/forks/test_helpers | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/integration/forks/test_helpers b/integration/forks/test_helpers index 51914b219d9..fe5e3a0e88f 100644 --- a/integration/forks/test_helpers +++ b/integration/forks/test_helpers @@ -30,7 +30,7 @@ create_job() { create_contract() { CONTRACT_DATA=`cat fixtures/create_contract.json` - curl \ + docker run --network forks_gethnet --ip 172.16.1.199 --rm pstauffer/curl:latest curl \ -X POST \ -H "Content-Type: application/json" \ --data "$CONTRACT_DATA" \ @@ -41,12 +41,11 @@ initial_setup() { tear_down build_images start_network - waitForResponse "172.16.1.102:6688" + waitFor "docker run --network forks_gethnet --ip 172.16.1.199 --rm pstauffer/curl:latest curl -s 172.16.1.102:6688 2>/dev/null" assert_testing_correct_artifact create_job } - assert_not_in_chainlink_logs() { echo "asserting \"$1\" not present ..." num_found=`docker-compose logs chainlink | grep -c "$1"` From c7ef0724d602011001fb754bbced84adf5fe26d0 Mon Sep 17 00:00:00 2001 From: Ryan Hall Date: Thu, 7 Nov 2019 11:23:10 -0500 Subject: [PATCH 141/199] save logs on test failure --- integration/forks/test | 76 ++++++++++++++++++++-------------- integration/forks/test_helpers | 6 +++ 2 files changed, 50 insertions(+), 32 deletions(-) diff --git a/integration/forks/test b/integration/forks/test index a858d89c58b..9a3a68669ba 100755 --- a/integration/forks/test +++ b/integration/forks/test @@ -9,39 +9,51 @@ source ./test_helpers initial_setup mkdir -p logs -# Runs the first chain, where the ethlog contract is deployed -printf "\nSTARTING CHAIN 1\n" -# make sure chainlink has actually started receiving blocks from geth -search_chainlink_logs 'Received new head' -# broadcast contract creation transaction -create_contract -# wait for chainlink to get notified about transaction -search_chainlink_logs 'New run triggered by ethlog' -search_chainlink_logs 'Run cannot continue because it lacks sufficient confirmations' -# save log -docker-compose logs chainlink > logs/chain_1.log -docker-compose logs geth > logs/geth.log -# tear down network before sufficient confirmations can be reached -docker-compose down -# assert that nothing has been uncled yet -assert_not_in_chainlink_logs 'presumably has been uncled' +# run the first chain, where the ethlog contract is deployed +{ + printf "\nSTARTING CHAIN 1\n" + # make sure chainlink has actually started receiving blocks from geth + search_chainlink_logs 'Received new head' + # broadcast contract creation transaction + create_contract + # wait for chainlink to get notified about transaction + search_chainlink_logs 'New run triggered by ethlog' + search_chainlink_logs 'Run cannot continue because it lacks sufficient confirmations' + # stop mining before sufficient confirmations can be reached + docker-compose stop + # assert that nothing has been uncled yet + assert_not_in_chainlink_logs 'presumably has been uncled' + # tear down + save_logs 1 + docker-compose down +} || { + # if error encountered, save logs and exit + save_logs 1 + exit 1 +} # create 2nd chain that is longer than first chain. Job should be uncled, not run -printf "\nSTARTING CHAIN 2\n" -start_network -# 2nd chain should be younger than first, and so chainlink won't immediately save new heads -search_chainlink_logs 'Cannot save new head confirmation' -# when 2nd chain gets longer, chainlink resumes saving heads -search_chainlink_logs 'New head resuming run' -# will wait for head # to be 10 more than block # with contract creation -search_chainlink_logs 'Run cannot continue because it lacks sufficient confirmations services' -# should eventually abort running running job -search_chainlink_logs 'presumably has been uncled' -# save log -docker-compose logs chainlink > logs/chain_2.log -docker-compose logs geth >> logs/geth.log -# tear down -docker-compose down -assert_not_in_chainlink_logs 'All tasks complete for run' +{ + printf "\nSTARTING CHAIN 2\n" + start_network + # 2nd chain should be younger than first, and so chainlink won't immediately save new heads + search_chainlink_logs 'Cannot save new head confirmation' + # when 2nd chain gets longer, chainlink resumes saving heads + search_chainlink_logs 'New head resuming run' + # will wait for head # to be 10 more than block # with contract creation + search_chainlink_logs 'Run cannot continue because it lacks sufficient confirmations services' + # should eventually abort running running job + search_chainlink_logs 'presumably has been uncled' + # assert job was never run + docker-compose stop + assert_not_in_chainlink_logs 'All tasks complete for run' + # tear down + save_logs 2 + docker-compose down +} || { + # if error encountered, save logs and exit + save_logs 2 + exit 1 +} echo "test passed!" diff --git a/integration/forks/test_helpers b/integration/forks/test_helpers index fe5e3a0e88f..a3b0a231b75 100644 --- a/integration/forks/test_helpers +++ b/integration/forks/test_helpers @@ -76,3 +76,9 @@ search_chainlink_logs() { sleep 1; done } + +save_logs() { + echo "saving logs for chain $1" + docker-compose logs chainlink > "logs/chain_$1.log" + docker-compose logs geth >> "logs/geth_$1.log" +} From 143b2e391614e463e995ec1aca8d62a8518e40c7 Mon Sep 17 00:00:00 2001 From: Ryan Hall Date: Thu, 7 Nov 2019 12:13:25 -0500 Subject: [PATCH 142/199] update test to reflect current verbage --- integration/forks/test | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integration/forks/test b/integration/forks/test index 9a3a68669ba..501ccd1854a 100755 --- a/integration/forks/test +++ b/integration/forks/test @@ -18,7 +18,7 @@ mkdir -p logs create_contract # wait for chainlink to get notified about transaction search_chainlink_logs 'New run triggered by ethlog' - search_chainlink_logs 'Run cannot continue because it lacks sufficient confirmations' + search_chainlink_logs 'Pausing run pending confirmations' # stop mining before sufficient confirmations can be reached docker-compose stop # assert that nothing has been uncled yet From f4448e248d2b01182dcfaf03127ae807780b5104 Mon Sep 17 00:00:00 2001 From: John Barker Date: Thu, 7 Nov 2019 18:01:19 -0700 Subject: [PATCH 143/199] Implenent forks test against RunExecutor --- core/internal/mocks/tx_manager.go | 432 +++++++++++++++++++++++++++++ core/services/run_executor_test.go | 35 +++ core/services/run_manager_test.go | 8 +- core/store/tx_manager.go | 1 + 4 files changed, 472 insertions(+), 4 deletions(-) create mode 100644 core/internal/mocks/tx_manager.go diff --git a/core/internal/mocks/tx_manager.go b/core/internal/mocks/tx_manager.go new file mode 100644 index 00000000000..32ba9ceaafa --- /dev/null +++ b/core/internal/mocks/tx_manager.go @@ -0,0 +1,432 @@ +// Code generated by mockery v1.0.0. DO NOT EDIT. + +package mocks + +import accounts "github.com/ethereum/go-ethereum/accounts" +import assets "github.com/smartcontractkit/chainlink/core/store/assets" +import big "math/big" +import common "github.com/ethereum/go-ethereum/common" +import ethereum "github.com/ethereum/go-ethereum" +import mock "github.com/stretchr/testify/mock" +import models "github.com/smartcontractkit/chainlink/core/store/models" +import null "gopkg.in/guregu/null.v3" +import store "github.com/smartcontractkit/chainlink/core/store" + +// TxManager is an autogenerated mock type for the TxManager type +type TxManager struct { + mock.Mock +} + +// BumpGasUntilSafe provides a mock function with given fields: hash +func (_m *TxManager) BumpGasUntilSafe(hash common.Hash) (*models.TxReceipt, store.AttemptState, error) { + ret := _m.Called(hash) + + var r0 *models.TxReceipt + if rf, ok := ret.Get(0).(func(common.Hash) *models.TxReceipt); ok { + r0 = rf(hash) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*models.TxReceipt) + } + } + + var r1 store.AttemptState + if rf, ok := ret.Get(1).(func(common.Hash) store.AttemptState); ok { + r1 = rf(hash) + } else { + r1 = ret.Get(1).(store.AttemptState) + } + + var r2 error + if rf, ok := ret.Get(2).(func(common.Hash) error); ok { + r2 = rf(hash) + } else { + r2 = ret.Error(2) + } + + return r0, r1, r2 +} + +// CheckAttempt provides a mock function with given fields: txAttempt, blockHeight +func (_m *TxManager) CheckAttempt(txAttempt *models.TxAttempt, blockHeight uint64) (*models.TxReceipt, store.AttemptState, error) { + ret := _m.Called(txAttempt, blockHeight) + + var r0 *models.TxReceipt + if rf, ok := ret.Get(0).(func(*models.TxAttempt, uint64) *models.TxReceipt); ok { + r0 = rf(txAttempt, blockHeight) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*models.TxReceipt) + } + } + + var r1 store.AttemptState + if rf, ok := ret.Get(1).(func(*models.TxAttempt, uint64) store.AttemptState); ok { + r1 = rf(txAttempt, blockHeight) + } else { + r1 = ret.Get(1).(store.AttemptState) + } + + var r2 error + if rf, ok := ret.Get(2).(func(*models.TxAttempt, uint64) error); ok { + r2 = rf(txAttempt, blockHeight) + } else { + r2 = ret.Error(2) + } + + return r0, r1, r2 +} + +// Connect provides a mock function with given fields: _a0 +func (_m *TxManager) Connect(_a0 *models.Head) error { + ret := _m.Called(_a0) + + var r0 error + if rf, ok := ret.Get(0).(func(*models.Head) error); ok { + r0 = rf(_a0) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// Connected provides a mock function with given fields: +func (_m *TxManager) Connected() bool { + ret := _m.Called() + + var r0 bool + if rf, ok := ret.Get(0).(func() bool); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(bool) + } + + return r0 +} + +// ContractLINKBalance provides a mock function with given fields: wr +func (_m *TxManager) ContractLINKBalance(wr models.WithdrawalRequest) (assets.Link, error) { + ret := _m.Called(wr) + + var r0 assets.Link + if rf, ok := ret.Get(0).(func(models.WithdrawalRequest) assets.Link); ok { + r0 = rf(wr) + } else { + r0 = ret.Get(0).(assets.Link) + } + + var r1 error + if rf, ok := ret.Get(1).(func(models.WithdrawalRequest) error); ok { + r1 = rf(wr) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// CreateTx provides a mock function with given fields: to, data +func (_m *TxManager) CreateTx(to common.Address, data []byte) (*models.Tx, error) { + ret := _m.Called(to, data) + + var r0 *models.Tx + if rf, ok := ret.Get(0).(func(common.Address, []byte) *models.Tx); ok { + r0 = rf(to, data) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*models.Tx) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(common.Address, []byte) error); ok { + r1 = rf(to, data) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// CreateTxWithEth provides a mock function with given fields: from, to, value +func (_m *TxManager) CreateTxWithEth(from common.Address, to common.Address, value *assets.Eth) (*models.Tx, error) { + ret := _m.Called(from, to, value) + + var r0 *models.Tx + if rf, ok := ret.Get(0).(func(common.Address, common.Address, *assets.Eth) *models.Tx); ok { + r0 = rf(from, to, value) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*models.Tx) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(common.Address, common.Address, *assets.Eth) error); ok { + r1 = rf(from, to, value) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// CreateTxWithGas provides a mock function with given fields: surrogateID, to, data, gasPriceWei, gasLimit +func (_m *TxManager) CreateTxWithGas(surrogateID null.String, to common.Address, data []byte, gasPriceWei *big.Int, gasLimit uint64) (*models.Tx, error) { + ret := _m.Called(surrogateID, to, data, gasPriceWei, gasLimit) + + var r0 *models.Tx + if rf, ok := ret.Get(0).(func(null.String, common.Address, []byte, *big.Int, uint64) *models.Tx); ok { + r0 = rf(surrogateID, to, data, gasPriceWei, gasLimit) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*models.Tx) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(null.String, common.Address, []byte, *big.Int, uint64) error); ok { + r1 = rf(surrogateID, to, data, gasPriceWei, gasLimit) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Disconnect provides a mock function with given fields: +func (_m *TxManager) Disconnect() { + _m.Called() +} + +// GetBlockByNumber provides a mock function with given fields: hex +func (_m *TxManager) GetBlockByNumber(hex string) (models.BlockHeader, error) { + ret := _m.Called(hex) + + var r0 models.BlockHeader + if rf, ok := ret.Get(0).(func(string) models.BlockHeader); ok { + r0 = rf(hex) + } else { + r0 = ret.Get(0).(models.BlockHeader) + } + + var r1 error + if rf, ok := ret.Get(1).(func(string) error); ok { + r1 = rf(hex) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetChainID provides a mock function with given fields: +func (_m *TxManager) GetChainID() (*big.Int, error) { + ret := _m.Called() + + var r0 *big.Int + if rf, ok := ret.Get(0).(func() *big.Int); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*big.Int) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func() error); ok { + r1 = rf() + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetEthBalance provides a mock function with given fields: address +func (_m *TxManager) GetEthBalance(address common.Address) (*assets.Eth, error) { + ret := _m.Called(address) + + var r0 *assets.Eth + if rf, ok := ret.Get(0).(func(common.Address) *assets.Eth); ok { + r0 = rf(address) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*assets.Eth) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(common.Address) error); ok { + r1 = rf(address) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetLINKBalance provides a mock function with given fields: address +func (_m *TxManager) GetLINKBalance(address common.Address) (*assets.Link, error) { + ret := _m.Called(address) + + var r0 *assets.Link + if rf, ok := ret.Get(0).(func(common.Address) *assets.Link); ok { + r0 = rf(address) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*assets.Link) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(common.Address) error); ok { + r1 = rf(address) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetLogs provides a mock function with given fields: q +func (_m *TxManager) GetLogs(q ethereum.FilterQuery) ([]models.Log, error) { + ret := _m.Called(q) + + var r0 []models.Log + if rf, ok := ret.Get(0).(func(ethereum.FilterQuery) []models.Log); ok { + r0 = rf(q) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]models.Log) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(ethereum.FilterQuery) error); ok { + r1 = rf(q) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetTxReceipt provides a mock function with given fields: _a0 +func (_m *TxManager) GetTxReceipt(_a0 common.Hash) (*models.TxReceipt, error) { + ret := _m.Called(_a0) + + var r0 *models.TxReceipt + if rf, ok := ret.Get(0).(func(common.Hash) *models.TxReceipt); ok { + r0 = rf(_a0) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*models.TxReceipt) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(common.Hash) error); ok { + r1 = rf(_a0) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// NextActiveAccount provides a mock function with given fields: +func (_m *TxManager) NextActiveAccount() *store.ManagedAccount { + ret := _m.Called() + + var r0 *store.ManagedAccount + if rf, ok := ret.Get(0).(func() *store.ManagedAccount); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*store.ManagedAccount) + } + } + + return r0 +} + +// OnNewHead provides a mock function with given fields: _a0 +func (_m *TxManager) OnNewHead(_a0 *models.Head) { + _m.Called(_a0) +} + +// Register provides a mock function with given fields: _a0 +func (_m *TxManager) Register(_a0 []accounts.Account) { + _m.Called(_a0) +} + +// SubscribeToLogs provides a mock function with given fields: channel, q +func (_m *TxManager) SubscribeToLogs(channel chan<- models.Log, q ethereum.FilterQuery) (models.EthSubscription, error) { + ret := _m.Called(channel, q) + + var r0 models.EthSubscription + if rf, ok := ret.Get(0).(func(chan<- models.Log, ethereum.FilterQuery) models.EthSubscription); ok { + r0 = rf(channel, q) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(models.EthSubscription) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(chan<- models.Log, ethereum.FilterQuery) error); ok { + r1 = rf(channel, q) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// SubscribeToNewHeads provides a mock function with given fields: channel +func (_m *TxManager) SubscribeToNewHeads(channel chan<- models.BlockHeader) (models.EthSubscription, error) { + ret := _m.Called(channel) + + var r0 models.EthSubscription + if rf, ok := ret.Get(0).(func(chan<- models.BlockHeader) models.EthSubscription); ok { + r0 = rf(channel) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(models.EthSubscription) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(chan<- models.BlockHeader) error); ok { + r1 = rf(channel) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// WithdrawLINK provides a mock function with given fields: wr +func (_m *TxManager) WithdrawLINK(wr models.WithdrawalRequest) (common.Hash, error) { + ret := _m.Called(wr) + + var r0 common.Hash + if rf, ok := ret.Get(0).(func(models.WithdrawalRequest) common.Hash); ok { + r0 = rf(wr) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(common.Hash) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(models.WithdrawalRequest) error); ok { + r1 = rf(wr) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} diff --git a/core/services/run_executor_test.go b/core/services/run_executor_test.go index f6a9492ca88..a2f6b001ba3 100644 --- a/core/services/run_executor_test.go +++ b/core/services/run_executor_test.go @@ -1,6 +1,7 @@ package services_test import ( + "math/big" "testing" "time" @@ -10,6 +11,7 @@ import ( "chainlink/core/store/assets" "chainlink/core/store/models" + "github.com/smartcontractkit/chainlink/core/null" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -170,3 +172,36 @@ func TestRunExecutor_Execute_CancelActivelyRunningTask(t *testing.T) { require.NoError(t, err) assert.Nil(t, actual) } + +func TestRunExecutor_UncleForkDoesNotCompleteJob(t *testing.T) { + t.Parallel() + + store, cleanup := cltest.NewStore(t) + defer cleanup() + txManager := new(mocks.TxManager) + store.TxManager = txManager + + runExecutor := services.NewRunExecutor(store) + + j := cltest.NewJobWithWebInitiator() + j.Tasks = []models.TaskSpec{cltest.NewTask(t, "noop"), cltest.NewTask(t, "nooppend")} + assert.NoError(t, store.CreateJob(&j)) + + run := j.NewRun(j.Initiators[0]) + txHash := cltest.NewHash() + run.RunRequest.TxHash = &txHash + run.TaskRuns[0].MinimumConfirmations = null.Uint32From(10) + run.ObservedHeight = models.NewBig(big.NewInt(0)) + require.NoError(t, store.CreateJobRun(&run)) + txManager.On("GetTxReceipt", txHash).Return(&models.TxReceipt{}, nil) + require.NoError(t, runExecutor.Execute(run.ID)) + + run, err := store.FindJobRun(run.ID) + require.NoError(t, err) + assert.Equal(t, models.RunStatusErrored, run.Status) + require.Len(t, run.TaskRuns, 2) + assert.Equal(t, models.RunStatusErrored, run.TaskRuns[0].Status) + assert.Equal(t, models.RunStatusUnstarted, run.TaskRuns[1].Status) + + txManager.AssertExpectations(t) +} diff --git a/core/services/run_manager_test.go b/core/services/run_manager_test.go index 538179c9bfe..5cf1c26faa1 100644 --- a/core/services/run_manager_test.go +++ b/core/services/run_manager_test.go @@ -336,10 +336,12 @@ func TestRunManager_Create_fromRunLog_Happy(t *testing.T) { app, cleanup := cltest.NewApplicationWithConfig(t, config) defer cleanup() + store := app.GetStore() + eth := app.MockEthCallerSubscriber() + eth.Register("eth_chainId", store.Config.ChainID()) app.Start() - store := app.GetStore() job := cltest.NewJobWithRunLogInitiator() job.Tasks = []models.TaskSpec{cltest.NewTask(t, "NoOp")} require.NoError(t, store.CreateJob(&job)) @@ -361,9 +363,7 @@ func TestRunManager_Create_fromRunLog_Happy(t *testing.T) { BlockHash: &test.receiptBlockHash, BlockNumber: cltest.Int(3), } - eth.Context("validateOnMainChain", func(ethMock *cltest.EthMock) { - eth.Register("eth_getTransactionReceipt", confirmedReceipt) - }) + eth.Register("eth_getTransactionReceipt", confirmedReceipt) err = app.RunManager.ResumeAllConfirming(big.NewInt(2)) require.NoError(t, err) diff --git a/core/store/tx_manager.go b/core/store/tx_manager.go index 55398f3af9f..38e1db154d6 100644 --- a/core/store/tx_manager.go +++ b/core/store/tx_manager.go @@ -64,6 +64,7 @@ type TxManager interface { } //go:generate mockgen -package=mocks -destination=../internal/mocks/tx_manager_mocks.go chainlink/core/store TxManager +//go:generate mockery -name TxManager -output ../internal/mocks/ -case=underscore // EthTxManager contains fields for the Ethereum client, the KeyStore, // the local Config for the application, and the database. From 38a0645409887a47a112d7f69ce1bc94ec4a41b7 Mon Sep 17 00:00:00 2001 From: John Barker Date: Fri, 8 Nov 2019 09:49:57 -0700 Subject: [PATCH 144/199] RunExecutor should never get unrunnable runs --- core/services/run_executor_test.go | 24 ------------------------ 1 file changed, 24 deletions(-) diff --git a/core/services/run_executor_test.go b/core/services/run_executor_test.go index a2f6b001ba3..692e3930b77 100644 --- a/core/services/run_executor_test.go +++ b/core/services/run_executor_test.go @@ -98,30 +98,6 @@ func TestRunExecutor_Execute_RunNotFoundError(t *testing.T) { require.Error(t, err) } -func TestRunExecutor_Execute_RunNotRunnableError(t *testing.T) { - t.Parallel() - - store, cleanup := cltest.NewStore(t) - defer cleanup() - - runExecutor := services.NewRunExecutor(store) - - j := models.NewJob() - i := models.Initiator{Type: models.InitiatorWeb} - j.Initiators = []models.Initiator{i} - j.Tasks = []models.TaskSpec{ - cltest.NewTask(t, "noop"), - } - assert.NoError(t, store.CreateJob(&j)) - - run := j.NewRun(i) - run.Status = models.RunStatusPendingConfirmations - require.NoError(t, store.CreateJobRun(&run)) - - err := runExecutor.Execute(run.ID) - require.Error(t, err) -} - func TestRunExecutor_Execute_CancelActivelyRunningTask(t *testing.T) { t.Parallel() From 903aaba9535e3406c44e6be18f3845c8c3eb42f5 Mon Sep 17 00:00:00 2001 From: John Barker Date: Thu, 7 Nov 2019 21:27:10 -0700 Subject: [PATCH 145/199] Push validateMinimumConfirmations into Execute loop validateMinimumConfirmations checks if the run is on an uncled chain, and is called once per task during a run. We only need to do this once per run. This should reduce overall ethereum calls. This also makes RunManager.Create a little bit simpler. TestIntegration_HttpRequestWithHeaders needs to have the run's ObservedHeight updated to satisfy the confirmations being checked before running a task. --- core/internal/features_test.go | 3 ++ core/internal/mocks/eth_client_mocks.go | 5 +-- core/internal/mocks/tx_manager.go | 6 +-- core/internal/mocks/tx_manager_mocks.go | 5 +-- core/services/run_executor.go | 54 +++++++++++++------------ core/services/run_executor_test.go | 21 ++++------ core/services/run_manager.go | 19 +++++---- core/services/run_manager_test.go | 32 +++++++++------ core/services/runs.go | 14 ++++++- go.sum | 1 - 10 files changed, 89 insertions(+), 71 deletions(-) diff --git a/core/internal/features_test.go b/core/internal/features_test.go index 4f2d9af136e..85f04ca46b2 100644 --- a/core/internal/features_test.go +++ b/core/internal/features_test.go @@ -87,6 +87,9 @@ func TestIntegration_HttpRequestWithHeaders(t *testing.T) { eth.EventuallyAllCalled(t) cltest.WaitForTxAttemptCount(t, app.Store, 1) + jr.ObservedHeight = confirmedReceipt.BlockNumber + require.NoError(t, app.Store.SaveJobRun(&jr)) + eth.Context("ethTx.Perform()#4 at block 23465", func(eth *cltest.EthMock) { eth.Register("eth_getTransactionReceipt", confirmedReceipt) // confirmed for gas bumped txat eth.Register("eth_getBalance", "0x0100") diff --git a/core/internal/mocks/eth_client_mocks.go b/core/internal/mocks/eth_client_mocks.go index d37d17ec20f..e07996aa9b8 100644 --- a/core/internal/mocks/eth_client_mocks.go +++ b/core/internal/mocks/eth_client_mocks.go @@ -7,12 +7,11 @@ package mocks import ( assets "chainlink/core/store/assets" models "chainlink/core/store/models" - big "math/big" - reflect "reflect" - go_ethereum "github.com/ethereum/go-ethereum" common "github.com/ethereum/go-ethereum/common" gomock "github.com/golang/mock/gomock" + big "math/big" + reflect "reflect" ) // MockEthClient is a mock of EthClient interface diff --git a/core/internal/mocks/tx_manager.go b/core/internal/mocks/tx_manager.go index 32ba9ceaafa..026f9855161 100644 --- a/core/internal/mocks/tx_manager.go +++ b/core/internal/mocks/tx_manager.go @@ -3,14 +3,14 @@ package mocks import accounts "github.com/ethereum/go-ethereum/accounts" -import assets "github.com/smartcontractkit/chainlink/core/store/assets" +import assets "chainlink/core/store/assets" import big "math/big" import common "github.com/ethereum/go-ethereum/common" import ethereum "github.com/ethereum/go-ethereum" import mock "github.com/stretchr/testify/mock" -import models "github.com/smartcontractkit/chainlink/core/store/models" +import models "chainlink/core/store/models" import null "gopkg.in/guregu/null.v3" -import store "github.com/smartcontractkit/chainlink/core/store" +import store "chainlink/core/store" // TxManager is an autogenerated mock type for the TxManager type type TxManager struct { diff --git a/core/internal/mocks/tx_manager_mocks.go b/core/internal/mocks/tx_manager_mocks.go index 984b1c34ba0..110900c5ea8 100644 --- a/core/internal/mocks/tx_manager_mocks.go +++ b/core/internal/mocks/tx_manager_mocks.go @@ -8,14 +8,13 @@ import ( store "chainlink/core/store" assets "chainlink/core/store/assets" models "chainlink/core/store/models" - big "math/big" - reflect "reflect" - go_ethereum "github.com/ethereum/go-ethereum" accounts "github.com/ethereum/go-ethereum/accounts" common "github.com/ethereum/go-ethereum/common" gomock "github.com/golang/mock/gomock" null_v3 "gopkg.in/guregu/null.v3" + big "math/big" + reflect "reflect" ) // MockTxManager is a mock of TxManager interface diff --git a/core/services/run_executor.go b/core/services/run_executor.go index ca563ca4379..d2d133869e4 100644 --- a/core/services/run_executor.go +++ b/core/services/run_executor.go @@ -35,30 +35,32 @@ func NewRunExecutor(store *store.Store) RunExecutor { func (je *runExecutor) Execute(runID *models.ID) error { run, err := je.store.Unscoped().FindJobRun(runID) if err != nil { - return fmt.Errorf("Error finding run %s", runID.String()) + return errors.Wrapf(err, "error finding run %s", runID) } - if !run.Status.Runnable() { - return fmt.Errorf("Run triggered in non runnable state %s", run.Status) - } - - for run.Status.Runnable() { - currentTaskRun := run.NextTaskRun() - if currentTaskRun == nil { - return errors.New("Run triggered with no remaining tasks") + for taskIndex := range run.TaskRuns { + taskRun := &run.TaskRuns[taskIndex] + if taskRun.Status.Completed() { + continue } - result := je.executeTask(&run, currentTaskRun) + if meetsMinimumConfirmations(&run, taskRun, run.ObservedHeight) { + result := je.executeTask(&run, taskRun) + + taskRun.ApplyOutput(result) + run.ApplyOutput(result) - currentTaskRun.ApplyOutput(result) - run.ApplyOutput(result) + if !result.Status().Runnable() { + logger.Debugw("Task execution blocked", run.ForLogger("task", taskRun.ID.String())...) + } + + } else { + logger.Debugw("Pausing run pending confirmations", + run.ForLogger("required_height", taskRun.MinimumConfirmations)..., + ) + taskRun.Status = models.RunStatusPendingConfirmations + run.Status = models.RunStatusPendingConfirmations - if !result.Status().Runnable() { - logger.Debugw("Task execution blocked", []interface{}{"run", run.ID, "task", currentTaskRun.ID.String(), "state", currentTaskRun.Status}...) - } else if currentTaskRun.Status.Unstarted() { - return fmt.Errorf("run %s task %s cannot return a status of empty string or Unstarted", run.ID, currentTaskRun.TaskSpec.Type) - } else if futureTaskRun := run.NextTaskRun(); futureTaskRun != nil { - validateMinimumConfirmations(&run, futureTaskRun, run.ObservedHeight, je.store.TxManager) } if err := je.store.ORM.SaveJobRun(&run); errors.Cause(err) == orm.OptimisticUpdateConflictError { @@ -68,17 +70,19 @@ func (je *runExecutor) Execute(runID *models.ID) error { return err } - if run.Status.Finished() { - logger.Debugw("All tasks complete for run", run.ForLogger()...) + if !run.Status.Runnable() { break } } + if run.Status.Finished() { + logger.Debugw("All tasks complete for run", run.ForLogger()...) + } return nil } -func (je *runExecutor) executeTask(run *models.JobRun, currentTaskRun *models.TaskRun) models.RunOutput { - taskCopy := currentTaskRun.TaskSpec // deliberately copied to keep mutations local +func (je *runExecutor) executeTask(run *models.JobRun, taskRun *models.TaskRun) models.RunOutput { + taskCopy := taskRun.TaskSpec // deliberately copied to keep mutations local var err error if taskCopy.Params, err = taskCopy.Params.Merge(run.Overrides); err != nil { @@ -94,7 +98,7 @@ func (je *runExecutor) executeTask(run *models.JobRun, currentTaskRun *models.Ta data := models.JSON{} if previousTaskRun != nil { - if data, err = previousTaskRun.Result.Data.Merge(currentTaskRun.Result.Data); err != nil { + if data, err = previousTaskRun.Result.Data.Merge(taskRun.Result.Data); err != nil { return models.NewRunOutputError(err) } } @@ -103,12 +107,12 @@ func (je *runExecutor) executeTask(run *models.JobRun, currentTaskRun *models.Ta return models.NewRunOutputError(err) } - input := *models.NewRunInput(run.ID, data, currentTaskRun.Status) + input := *models.NewRunInput(run.ID, data, taskRun.Status) start := time.Now() result := adapter.Perform(input, je.store) logger.Debugw(fmt.Sprintf("Executed task %s", taskCopy.Type), []interface{}{ - "task", currentTaskRun.ID.String(), + "task", taskRun.ID.String(), "result", result.Status(), "result_data", result.Data(), "elapsed", time.Since(start).Seconds(), diff --git a/core/services/run_executor_test.go b/core/services/run_executor_test.go index 692e3930b77..c59843174db 100644 --- a/core/services/run_executor_test.go +++ b/core/services/run_executor_test.go @@ -7,11 +7,11 @@ import ( "chainlink/core/internal/cltest" "chainlink/core/internal/mocks" + "chainlink/core/null" "chainlink/core/services" "chainlink/core/store/assets" "chainlink/core/store/models" - "github.com/smartcontractkit/chainlink/core/null" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -149,35 +149,30 @@ func TestRunExecutor_Execute_CancelActivelyRunningTask(t *testing.T) { assert.Nil(t, actual) } -func TestRunExecutor_UncleForkDoesNotCompleteJob(t *testing.T) { +func TestRunExecutor_InitialTaskLacksConfirmations(t *testing.T) { t.Parallel() store, cleanup := cltest.NewStore(t) defer cleanup() - txManager := new(mocks.TxManager) - store.TxManager = txManager runExecutor := services.NewRunExecutor(store) j := cltest.NewJobWithWebInitiator() - j.Tasks = []models.TaskSpec{cltest.NewTask(t, "noop"), cltest.NewTask(t, "nooppend")} + j.Tasks = []models.TaskSpec{cltest.NewTask(t, "noop")} assert.NoError(t, store.CreateJob(&j)) run := j.NewRun(j.Initiators[0]) txHash := cltest.NewHash() run.RunRequest.TxHash = &txHash run.TaskRuns[0].MinimumConfirmations = null.Uint32From(10) - run.ObservedHeight = models.NewBig(big.NewInt(0)) + run.CreationHeight = models.NewBig(big.NewInt(0)) + run.ObservedHeight = run.CreationHeight require.NoError(t, store.CreateJobRun(&run)) - txManager.On("GetTxReceipt", txHash).Return(&models.TxReceipt{}, nil) require.NoError(t, runExecutor.Execute(run.ID)) run, err := store.FindJobRun(run.ID) require.NoError(t, err) - assert.Equal(t, models.RunStatusErrored, run.Status) - require.Len(t, run.TaskRuns, 2) - assert.Equal(t, models.RunStatusErrored, run.TaskRuns[0].Status) - assert.Equal(t, models.RunStatusUnstarted, run.TaskRuns[1].Status) - - txManager.AssertExpectations(t) + assert.Equal(t, models.RunStatusPendingConfirmations, run.Status) + require.Len(t, run.TaskRuns, 1) + assert.Equal(t, models.RunStatusPendingConfirmations, run.TaskRuns[0].Status) } diff --git a/core/services/run_manager.go b/core/services/run_manager.go index 26b5f15af8a..84880da5c35 100644 --- a/core/services/run_manager.go +++ b/core/services/run_manager.go @@ -155,8 +155,6 @@ func newRun( return &run, nil } - initialTask := run.TaskRuns[0] - validateMinimumConfirmations(&run, &initialTask, run.CreationHeight, txManager) return &run, nil } @@ -224,19 +222,19 @@ func (jm *runManager) Create( } run.RunRequest = *runRequest + currentTaskRun := run.TaskRuns[0] + currentTaskRun.Status = models.RunStatusInProgress + run.Status = models.RunStatusInProgress if err := jm.orm.CreateJobRun(run); err != nil { return nil, errors.Wrap(err, "CreateJobRun failed") } - if run.Status == models.RunStatusInProgress { - logger.Debugw( - fmt.Sprintf("Executing run originally initiated by %s", run.Initiator.Type), - run.ForLogger()..., - ) - jm.runQueue.Run(run) - } - + logger.Debugw( + fmt.Sprintf("Executing run originally initiated by %s", run.Initiator.Type), + run.ForLogger()..., + ) + jm.runQueue.Run(run) return run, nil } @@ -274,6 +272,7 @@ func (jm *runManager) ResumeAllConnecting() error { return } + currentTaskRun.Status = models.RunStatusInProgress run.Status = models.RunStatusInProgress err := jm.updateAndTrigger(run) if err != nil { diff --git a/core/services/run_manager_test.go b/core/services/run_manager_test.go index 5cf1c26faa1..7618b158f55 100644 --- a/core/services/run_manager_test.go +++ b/core/services/run_manager_test.go @@ -327,7 +327,8 @@ func TestRunManager_Create_fromRunLog_Happy(t *testing.T) { }, } - for _, test := range tests { + for _, tt := range tests { + test := tt t.Run(test.name, func(t *testing.T) { config, cfgCleanup := cltest.NewConfig(t) defer cfgCleanup() @@ -336,10 +337,11 @@ func TestRunManager_Create_fromRunLog_Happy(t *testing.T) { app, cleanup := cltest.NewApplicationWithConfig(t, config) defer cleanup() - store := app.GetStore() - eth := app.MockEthCallerSubscriber() - eth.Register("eth_chainId", store.Config.ChainID()) + store := app.GetStore() + eth.Context("app.Start()", func(eth *cltest.EthMock) { + eth.Register("eth_chainId", store.Config.ChainID()) + }) app.Start() job := cltest.NewJobWithRunLogInitiator() @@ -356,23 +358,29 @@ func TestRunManager_Create_fromRunLog_Happy(t *testing.T) { data := cltest.JSONFromString(t, `{"random": "input"}`) jr, err := app.RunManager.Create(job.ID, &initr, &data, creationHeight, rr) require.NoError(t, err) - cltest.WaitForJobRunToPendConfirmations(t, app.Store, *jr) + + run := cltest.WaitForJobRunToPendConfirmations(t, app.Store, *jr) + assert.Equal(t, models.RunStatusPendingConfirmations, run.TaskRuns[0].Status) + assert.Equal(t, models.RunStatusPendingConfirmations, run.Status) confirmedReceipt := models.TxReceipt{ Hash: initiatingTxHash, BlockHash: &test.receiptBlockHash, BlockNumber: cltest.Int(3), } - eth.Register("eth_getTransactionReceipt", confirmedReceipt) + eth.Context("validateOnMainChain", func(eth *cltest.EthMock) { + eth.Register("eth_getTransactionReceipt", confirmedReceipt) + }) err = app.RunManager.ResumeAllConfirming(big.NewInt(2)) require.NoError(t, err) - updatedJR := cltest.WaitForJobRunStatus(t, store, *jr, test.wantStatus) - assert.Equal(t, rr.RequestID, updatedJR.RunRequest.RequestID) - assert.Equal(t, minimumConfirmations, updatedJR.TaskRuns[0].MinimumConfirmations.Uint32) - assert.True(t, updatedJR.TaskRuns[0].MinimumConfirmations.Valid) - assert.Equal(t, minimumConfirmations, updatedJR.TaskRuns[0].Confirmations.Uint32, "task run should track its current confirmations") - assert.True(t, updatedJR.TaskRuns[0].Confirmations.Valid) + run = cltest.WaitForJobRunStatus(t, store, *jr, test.wantStatus) + assert.Equal(t, rr.RequestID, run.RunRequest.RequestID) + assert.Equal(t, minimumConfirmations, run.TaskRuns[0].MinimumConfirmations.Uint32) + assert.True(t, run.TaskRuns[0].MinimumConfirmations.Valid) + assert.Equal(t, minimumConfirmations, run.TaskRuns[0].Confirmations.Uint32, "task run should track its current confirmations") + assert.True(t, run.TaskRuns[0].Confirmations.Valid) + assert.True(t, eth.AllCalled(), eth.Remaining()) }) } diff --git a/core/services/runs.go b/core/services/runs.go index 628c5c9777b..b7692935a89 100644 --- a/core/services/runs.go +++ b/core/services/runs.go @@ -26,11 +26,23 @@ func MeetsMinimumPayment( func validateMinimumConfirmations(run *models.JobRun, taskRun *models.TaskRun, currentHeight *models.Big, txManager store.TxManager) { updateTaskRunConfirmations(currentHeight, run, taskRun) + if !meetsMinimumConfirmations(run, taskRun, run.ObservedHeight) { - logger.Debugw("Pausing run pending confirmations", []interface{}{"run", run.ID.String(), "required_height", taskRun.MinimumConfirmations}...) + logger.Debugw("Pausing run pending confirmations", + run.ForLogger("required_height", taskRun.MinimumConfirmations)..., + ) + + taskRun.Status = models.RunStatusPendingConfirmations run.Status = models.RunStatusPendingConfirmations + } else if err := validateOnMainChain(run, taskRun, txManager); err != nil { + logger.Warnw("Failure while trying to validate chain", + run.ForLogger("error", err)..., + ) + + taskRun.SetError(err) run.SetError(err) + } else { run.Status = models.RunStatusInProgress } diff --git a/go.sum b/go.sum index ec760356de3..1058ebc32d7 100644 --- a/go.sum +++ b/go.sum @@ -290,7 +290,6 @@ github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeV github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= -github.com/smartcontractkit/chainlink v0.6.9 h1:HpDYgKaZXFslJI1nqA6QzY4u3Wj+gQhyRmKDk1gk3vE= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI= From bf26280f0e0933bb94108fab929eaa970c23ff91 Mon Sep 17 00:00:00 2001 From: John Barker Date: Fri, 8 Nov 2019 13:05:35 -0700 Subject: [PATCH 146/199] Use shorter module name in ldflags --- tools/bin/ldflags | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tools/bin/ldflags b/tools/bin/ldflags index 041b9610938..c9b45b9f75d 100755 --- a/tools/bin/ldflags +++ b/tools/bin/ldflags @@ -5,5 +5,4 @@ cd "$(dirname "$0")" COMMIT_SHA=${COMMIT_SHA:-$(git rev-parse HEAD)} VERSION=${VERSION:-$(cat "../../VERSION")} -echo "-X github.com/smartcontractkit/chainlink/core/store.Version=$VERSION" \ - "-X github.com/smartcontractkit/chainlink/core/store.Sha=$COMMIT_SHA" +echo "-X chainlink/core/store.Version=$VERSION -X chainlink/core/store.Sha=$COMMIT_SHA" From d3eeb2d4f3eba553929680447f93c9c8c86cb65e Mon Sep 17 00:00:00 2001 From: John Barker Date: Fri, 8 Nov 2019 14:47:30 -0700 Subject: [PATCH 147/199] Add test for ResumeAllConnecting skipping confirmations check --- core/services/run_manager.go | 6 +++--- core/services/run_manager_test.go | 29 +++++++++++++++++++++++++++++ 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/core/services/run_manager.go b/core/services/run_manager.go index 84880da5c35..ed1cfbc47f1 100644 --- a/core/services/run_manager.go +++ b/core/services/run_manager.go @@ -255,7 +255,7 @@ func (jm *runManager) ResumeAllConfirming(currentBlockHeight *big.Int) error { err := jm.updateAndTrigger(run) if err != nil { - logger.Error("Error saving run", "error", err) + logger.Errorw("Error saving run", run.ForLogger("error", err)...) } }, models.RunStatusPendingConnection, models.RunStatusPendingConfirmations) } @@ -276,7 +276,7 @@ func (jm *runManager) ResumeAllConnecting() error { run.Status = models.RunStatusInProgress err := jm.updateAndTrigger(run) if err != nil { - logger.Error("Error saving run", "error", err) + logger.Errorw("Error saving run", run.ForLogger("error", err)...) } }, models.RunStatusPendingConnection, models.RunStatusPendingConfirmations) } @@ -345,7 +345,7 @@ func (jm *runManager) updateWithError(run *models.JobRun, msg string, args ...in logger.Error(fmt.Sprintf(msg, args...)) if err := jm.orm.SaveJobRun(run); err != nil { - logger.Error("Error saving run", "error", err) + logger.Errorw("Error saving run", run.ForLogger("error", err)...) return err } return nil diff --git a/core/services/run_manager_test.go b/core/services/run_manager_test.go index 7618b158f55..d9806cca347 100644 --- a/core/services/run_manager_test.go +++ b/core/services/run_manager_test.go @@ -246,6 +246,35 @@ func TestRunManager_ResumeAllConnecting(t *testing.T) { }) } +func TestRunManager_ResumeAllConnecting_NotEnoughConfirmations(t *testing.T) { + t.Parallel() + app, cleanup := cltest.NewApplication(t) + defer cleanup() + + store := app.Store + eth := cltest.MockEthOnStore(t, store) + eth.Register("eth_chainId", store.Config.ChainID()) + + app.Start() + + job := cltest.NewJobWithRunLogInitiator() + job.Tasks = []models.TaskSpec{cltest.NewTask(t, "NoOp")} + require.NoError(t, store.CreateJob(&job)) + + initiator := job.Initiators[0] + run := job.NewRun(initiator) + run.Status = models.RunStatusPendingConnection + run.CreationHeight = models.NewBig(big.NewInt(0)) + run.ObservedHeight = run.CreationHeight + run.TaskRuns[0].MinimumConfirmations = clnull.Uint32From(807) + run.TaskRuns[0].Status = models.RunStatusPendingConnection + require.NoError(t, store.CreateJobRun(&run)) + + app.RunManager.ResumeAllConnecting() + + cltest.WaitForJobRunToPendConfirmations(t, store, run) +} + func TestRunManager_Create(t *testing.T) { t.Parallel() app, cleanup := cltest.NewApplication(t) From 9b76927c86ae13f9f88d0e59291ab1afb896c868 Mon Sep 17 00:00:00 2001 From: John Barker Date: Fri, 8 Nov 2019 16:15:22 -0700 Subject: [PATCH 148/199] Remove redundant set of task status to RunStatusInProgress --- core/services/run_manager.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/core/services/run_manager.go b/core/services/run_manager.go index ed1cfbc47f1..855e15089d7 100644 --- a/core/services/run_manager.go +++ b/core/services/run_manager.go @@ -222,8 +222,6 @@ func (jm *runManager) Create( } run.RunRequest = *runRequest - currentTaskRun := run.TaskRuns[0] - currentTaskRun.Status = models.RunStatusInProgress run.Status = models.RunStatusInProgress if err := jm.orm.CreateJobRun(run); err != nil { From 9816c4555661fc796234c62bc5dcbaf72101c7b0 Mon Sep 17 00:00:00 2001 From: John Barker Date: Fri, 8 Nov 2019 16:20:48 -0700 Subject: [PATCH 149/199] Do run=Runnable status check at head of loop in RunManager --- core/services/run_executor.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/core/services/run_executor.go b/core/services/run_executor.go index d2d133869e4..dd930bf07bd 100644 --- a/core/services/run_executor.go +++ b/core/services/run_executor.go @@ -39,6 +39,10 @@ func (je *runExecutor) Execute(runID *models.ID) error { } for taskIndex := range run.TaskRuns { + if !run.Status.Runnable() { + break + } + taskRun := &run.TaskRuns[taskIndex] if taskRun.Status.Completed() { continue @@ -69,10 +73,6 @@ func (je *runExecutor) Execute(runID *models.ID) error { } else if err != nil { return err } - - if !run.Status.Runnable() { - break - } } if run.Status.Finished() { From af2366f320c1dd20fc41d106e9c50690721f67e1 Mon Sep 17 00:00:00 2001 From: Bartosz Wojciechowski Date: Sat, 9 Nov 2019 00:30:18 +0100 Subject: [PATCH 150/199] [Fixes #169659642] Clean the code --- operator_ui/__tests__/containers/JobRuns/Index.test.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/operator_ui/__tests__/containers/JobRuns/Index.test.js b/operator_ui/__tests__/containers/JobRuns/Index.test.js index 6db92a5b7fa..a7ea2713ab4 100644 --- a/operator_ui/__tests__/containers/JobRuns/Index.test.js +++ b/operator_ui/__tests__/containers/JobRuns/Index.test.js @@ -102,11 +102,10 @@ describe('containers/JobRuns/Index', () => { expect.assertions(1) const runsResponse = jsonApiJobSpecRunFactory([]) - await global.fetch.getOnce(`glob:*/v2/runs*`, runsResponse) + await global.fetch.getOnce(globPath('/v2/runs'), runsResponse) const props = { match: { params: { jobSpecId: jobSpecId } } } const wrapper = mountIndex(props) - console.log('yeah', wrapper) await syncFetch(wrapper) expect(wrapper.text()).toContain('No jobs have been run yet') From eeedb9321e4dd72525b2278518dc3407973e5f70 Mon Sep 17 00:00:00 2001 From: Ryan Hall Date: Thu, 7 Nov 2019 10:49:37 -0500 Subject: [PATCH 151/199] add artifacts to circleCI --- .circleci/config.yml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/.circleci/config.yml b/.circleci/config.yml index 1d762f3a87c..0c62b52643d 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -279,6 +279,22 @@ jobs: command: yarn workspace @chainlink/explorer-client run build && yarn workspace @chainlink/explorer run test-ci:e2e:silent - store_artifacts: path: ./integration/logs + forks: + machine: + image: ubuntu-1604:201903-01 + docker_layer_caching: true + working_directory: ~/chainlink + steps: + - checkout + - run: + name: Install Yarn + command: npm install -g yarn + - run: + name: Install New Packages + command: yarn install + - run: ./tools/ci/forks_test + - store_artifacts: + path: ./integration/forks/logs build-publish-explorer: machine: true steps: From dd4a9e32d956774740ae06db32006984c68e690f Mon Sep 17 00:00:00 2001 From: Ryan Hall Date: Thu, 7 Nov 2019 12:12:46 -0500 Subject: [PATCH 152/199] update test to reflect current verbage --- integration/forks/test | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integration/forks/test b/integration/forks/test index 501ccd1854a..4d4fc36642d 100755 --- a/integration/forks/test +++ b/integration/forks/test @@ -41,7 +41,7 @@ mkdir -p logs # when 2nd chain gets longer, chainlink resumes saving heads search_chainlink_logs 'New head resuming run' # will wait for head # to be 10 more than block # with contract creation - search_chainlink_logs 'Run cannot continue because it lacks sufficient confirmations services' + search_chainlink_logs 'Pausing run pending confirmations' # should eventually abort running running job search_chainlink_logs 'presumably has been uncled' # assert job was never run From 46b075fd4d71a383386be568ad3039ea0ce9bcd6 Mon Sep 17 00:00:00 2001 From: Ryan Hall Date: Thu, 7 Nov 2019 13:32:30 -0500 Subject: [PATCH 153/199] Update docker-compose with key-import --- integration/forks/docker-compose.yaml | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/integration/forks/docker-compose.yaml b/integration/forks/docker-compose.yaml index d46cb8db312..c406762298a 100644 --- a/integration/forks/docker-compose.yaml +++ b/integration/forks/docker-compose.yaml @@ -3,7 +3,7 @@ # If you modify this, make sure to make corresponding modifications to the scripts in forks/scripts. # Geth is bad about letting you know when it's getting inconsistent inputs, so be careful. -version: '3.7' +version: "3.7" services: geth: @@ -26,6 +26,17 @@ services: - password - "0x9CA9d2D5E04012C9Ed24C0e513C9bfAa4A2dD77f" + # imports the 0x9CA9d2D5E04012C9Ed24C0e513C9bfAa4A2dD77f key into the CL db + chainlink_key_import: + image: smartcontract/chainlink + command: local import /run/secrets/0x9CA9d2D5E04012C9Ed24C0e513C9bfAa4A2dD77f + volumes: + - ./tmp/clroot/:/clroot/ + environment: + - ROOT=/clroot + secrets: + - "0x9CA9d2D5E04012C9Ed24C0e513C9bfAa4A2dD77f" + chainlink: image: smartcontract/chainlink container_name: forks_chainlink @@ -45,6 +56,8 @@ services: networks: gethnet: ipv4_address: 172.16.1.102 + depends_on: + - chainlink_key_import expose: - 6688 secrets: From 521cccd8e3442cdc4491afa4e3706e555d11b719 Mon Sep 17 00:00:00 2001 From: Ryan Hall Date: Thu, 7 Nov 2019 13:56:01 -0500 Subject: [PATCH 154/199] Update test running in CI to bypass yarn --- tools/ci/forks_test | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/ci/forks_test b/tools/ci/forks_test index c625e8364fb..c1c06569aa7 100755 --- a/tools/ci/forks_test +++ b/tools/ci/forks_test @@ -6,7 +6,7 @@ source ./integration/common heading 'Forks Test' -yarn workspace @chainlink/integration test:forks +./integration/forks/test title 'All tests passed.' From 49dda1c2c81a6271b0c331cd1d190a59b73a1116 Mon Sep 17 00:00:00 2001 From: Ryan Hall Date: Thu, 7 Nov 2019 13:56:51 -0500 Subject: [PATCH 155/199] Update test to always write logs --- integration/forks/test | 82 ++++++++++++++++++++---------------------- 1 file changed, 39 insertions(+), 43 deletions(-) diff --git a/integration/forks/test b/integration/forks/test index 4d4fc36642d..ece6532cf0b 100755 --- a/integration/forks/test +++ b/integration/forks/test @@ -1,5 +1,12 @@ #!/bin/bash +# save logs if script errors and exits early +chain_number=1 +function on_exit { + save_logs $chain_number +} +trap on_exit EXIT + # set this directory as working directory cd "$(dirname "$0")" # import test helpers @@ -10,50 +17,39 @@ initial_setup mkdir -p logs # run the first chain, where the ethlog contract is deployed -{ - printf "\nSTARTING CHAIN 1\n" - # make sure chainlink has actually started receiving blocks from geth - search_chainlink_logs 'Received new head' - # broadcast contract creation transaction - create_contract - # wait for chainlink to get notified about transaction - search_chainlink_logs 'New run triggered by ethlog' - search_chainlink_logs 'Pausing run pending confirmations' - # stop mining before sufficient confirmations can be reached - docker-compose stop - # assert that nothing has been uncled yet - assert_not_in_chainlink_logs 'presumably has been uncled' - # tear down - save_logs 1 - docker-compose down -} || { - # if error encountered, save logs and exit - save_logs 1 - exit 1 -} +printf "\nSTARTING CHAIN 1\n" +# make sure chainlink has actually started receiving blocks from geth +search_chainlink_logs 'Received new head' +# broadcast contract creation transaction +create_contract +# wait for chainlink to get notified about transaction +search_chainlink_logs 'New run triggered by ethlog' +search_chainlink_logs 'Pausing run pending confirmations' +# stop mining before sufficient confirmations can be reached +docker-compose stop +# assert that nothing has been uncled yet +assert_not_in_chainlink_logs 'presumably has been uncled' +# tear down +save_logs 1 +docker-compose down # create 2nd chain that is longer than first chain. Job should be uncled, not run -{ - printf "\nSTARTING CHAIN 2\n" - start_network - # 2nd chain should be younger than first, and so chainlink won't immediately save new heads - search_chainlink_logs 'Cannot save new head confirmation' - # when 2nd chain gets longer, chainlink resumes saving heads - search_chainlink_logs 'New head resuming run' - # will wait for head # to be 10 more than block # with contract creation - search_chainlink_logs 'Pausing run pending confirmations' - # should eventually abort running running job - search_chainlink_logs 'presumably has been uncled' - # assert job was never run - docker-compose stop - assert_not_in_chainlink_logs 'All tasks complete for run' - # tear down - save_logs 2 - docker-compose down -} || { - # if error encountered, save logs and exit - save_logs 2 - exit 1 -} +printf "\nSTARTING CHAIN 2\n" +chain_number=2 +start_network +# 2nd chain should be younger than first, and so chainlink won't immediately save new heads +search_chainlink_logs 'Cannot save new head confirmation' +# when 2nd chain gets longer, chainlink resumes saving heads +search_chainlink_logs 'New connection resuming run' +# will wait for head # to be 10 more than block # with contract creation +search_chainlink_logs 'Cannot save new head confirmation' +# should eventually abort running running job +search_chainlink_logs 'presumably has been uncled' +# assert job was never run +docker-compose stop +assert_not_in_chainlink_logs 'All tasks complete for run' +# tear down +save_logs 2 +docker-compose down echo "test passed!" From 19e6d718129ee43fe4c9ac888928bc909610a12c Mon Sep 17 00:00:00 2001 From: Ryan Hall Date: Fri, 8 Nov 2019 17:47:25 -0500 Subject: [PATCH 156/199] add forks test back into circle --- .circleci/config.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.circleci/config.yml b/.circleci/config.yml index 0c62b52643d..76d96817526 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -418,6 +418,10 @@ workflows: filters: # all branches, and /^explorer-v../ tags for build-publish... tags: only: /^explorer-v.*/ + - forks: + filters: + tags: + only: /^v.*/ - build-chainlink: filters: tags: From 4421e0e64e95a7e4d0db010edb731f27ffcfc70b Mon Sep 17 00:00:00 2001 From: Alex Coventry Date: Sat, 9 Nov 2019 14:11:30 -0500 Subject: [PATCH 157/199] Be explicit about dependence between logMarshaling and gen_log_json --- core/store/models/eth.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/core/store/models/eth.go b/core/store/models/eth.go index 66bb58e2254..0e713c89e59 100755 --- a/core/store/models/eth.go +++ b/core/store/models/eth.go @@ -60,6 +60,13 @@ func (log Log) getTopic(idx uint) (common.Hash, error) { return log.Topics[idx], nil } +// logMarshaling represents an ethereum event log. +// +// NOTE: If this is changed, gen_log_json.go must be changed accordingly. It was +// generated by the above "//go:generate gencodec" command, which is currently +// broken. (It seems as though the problem might be that gencodec doesn't work +// with modules-based packages, in which case it could probably be run outside +// chainlink. https://github.com/fjl/gencodec/issues/10) type logMarshaling struct { Data hexutil.Bytes BlockNumber hexutil.Uint64 From c90fed2946815e41d28f09e3e606a0be0fba6511 Mon Sep 17 00:00:00 2001 From: spooktheducks Date: Wed, 6 Nov 2019 19:07:38 -0600 Subject: [PATCH 158/199] Make MeanAggregator use linearly-decaying payout structure --- evm/v0.5/contracts/tests/MeanAggregator.sol | 42 +++++++++++++-------- evm/v0.5/test/Coordinator_test.js | 10 ++++- evm/v0.5/test/support/helpers.ts | 5 ++- 3 files changed, 38 insertions(+), 19 deletions(-) diff --git a/evm/v0.5/contracts/tests/MeanAggregator.sol b/evm/v0.5/contracts/tests/MeanAggregator.sol index 203f7f6a452..9ecc722fc68 100644 --- a/evm/v0.5/contracts/tests/MeanAggregator.sol +++ b/evm/v0.5/contracts/tests/MeanAggregator.sol @@ -9,11 +9,10 @@ contract MeanAggregator { // Relies on Coordinator's authorization of the oracles (no need to track // oracle authorization in this contract.) - mapping(bytes32 /* service agreement ID */ => uint256) numOracles; - mapping(bytes32 /* request ID */ => mapping(address /* oracle */ => bool)) - reported; mapping(bytes32 /* service agreement ID */ => uint256) payment; + mapping(bytes32 /* service agreement ID */ => address[]) oracles; mapping(bytes32 /* request ID */ => uint256) numberReported; + mapping(bytes32 /* request ID */ => mapping(address => uint256)) reportingOrder; // Current total for given request, divided by number of oracles reporting mapping(bytes32 /* request ID */ => uint256) average; @@ -23,13 +22,13 @@ contract MeanAggregator { function initiateJob( bytes32 _sAId, CoordinatorInterface.ServiceAgreement memory _sa) public returns (bool success, bytes memory message) { - if (numOracles[_sAId] != 0) { + if (oracles[_sAId].length != 0) { return (false, bytes("job already initiated")); } if (_sa.oracles.length == 0) { return (false, bytes("must depend on at least one oracle")); } - numOracles[_sAId] = _sa.oracles.length; + oracles[_sAId] = _sa.oracles; payment[_sAId] = _sa.payment; success = true; } @@ -38,26 +37,37 @@ contract MeanAggregator { bytes32 _value) public returns (bool success, bool complete, bytes memory response, int256[] memory paymentAmounts) { - if (reported[_requestId][_oracle]) { + if (reportingOrder[_requestId][_oracle] != 0 || + numberReported[_requestId] == oracles[_sAId].length) { return (false, false, "oracle already reported", paymentAmounts); } - uint256 oDividend = uint256(_value) / numOracles[_sAId]; - uint256 oRemainder = uint256(_value) % numOracles[_sAId]; + uint256 oDividend = uint256(_value) / oracles[_sAId].length; + uint256 oRemainder = uint256(_value) % oracles[_sAId].length; uint256 newRemainder = remainder[_requestId] + oRemainder; - uint256 newAverage = average[_requestId] + oDividend + (newRemainder / numOracles[_sAId]); + uint256 newAverage = average[_requestId] + oDividend + (newRemainder / oracles[_sAId].length); assert(newAverage >= average[_requestId]); // No overflow average[_requestId] = newAverage; - remainder[_requestId] = newRemainder % numOracles[_sAId]; + remainder[_requestId] = newRemainder % oracles[_sAId].length; numberReported[_requestId] += 1; + reportingOrder[_requestId][_oracle] = numberReported[_requestId]; success = true; - reported[_requestId][_oracle] = true; - complete = (numberReported[_requestId] == numOracles[_sAId]); + complete = (numberReported[_requestId] == oracles[_sAId].length); if (complete) { response = abi.encode(average[_requestId]); - paymentAmounts = new int256[](numOracles[_sAId]); - for (uint256 oIdx = 0; oIdx < numOracles[_sAId]; oIdx++) { - paymentAmounts[oIdx] = int256(payment[_sAId] / numOracles[_sAId]); - } + paymentAmounts = calculatePayments(_sAId, _requestId); } } + + function calculatePayments(bytes32 _sAId, bytes32 _requestId) private returns (int256[] memory paymentAmounts) { + paymentAmounts = new int256[](oracles[_sAId].length); + uint256 numOracles = oracles[_sAId].length; + uint256 totalPayment = payment[_sAId]; + for (uint256 oIdx = 0; oIdx < oracles[_sAId].length; oIdx++) { + // Linearly-decaying payout determined by each oracle's reportingIdx + uint256 reportingIdx = reportingOrder[_requestId][oracles[_sAId][oIdx]] - 1; + paymentAmounts[oIdx] = int256(2*(totalPayment/numOracles) - ( + (totalPayment * ((2*reportingIdx) + 1)) / (numOracles**2))); + delete reportingOrder[_requestId][oracles[_sAId][oIdx]]; + } + } } diff --git a/evm/v0.5/test/Coordinator_test.js b/evm/v0.5/test/Coordinator_test.js index e272c0d6942..f85034855e8 100644 --- a/evm/v0.5/test/Coordinator_test.js +++ b/evm/v0.5/test/Coordinator_test.js @@ -714,9 +714,15 @@ contract('Coordinator', () => { it('oracle balances are updated', async () => { // Given the 3 oracles from the SA, each should have the following balance after fulfillment - const expected = h.bigNum('333333333333333333') + const expected1 = h.bigNum('555555555555555555') + const expected2 = h.bigNum('333333333333333333') + const expected3 = h.bigNum('111111111111111111') const balance1 = await coordinator.withdrawableTokens.call(oracle1) - assertBigNum(expected, balance1) + const balance2 = await coordinator.withdrawableTokens.call(oracle2) + const balance3 = await coordinator.withdrawableTokens.call(oracle3) + assertBigNum(expected1, balance1) + assertBigNum(expected2, balance2) + assertBigNum(expected3, balance3) }) }) diff --git a/evm/v0.5/test/support/helpers.ts b/evm/v0.5/test/support/helpers.ts index 208691b723c..7d0d6bb55a9 100644 --- a/evm/v0.5/test/support/helpers.ts +++ b/evm/v0.5/test/support/helpers.ts @@ -209,7 +209,10 @@ export const assertActionThrows = (action: any, messageContains?: RegExp) => return error.message }) .then(errorMessage => { - assert(errorMessage, 'Expected an error to be raised') + assert( + errorMessage && errorMessage.includes, + 'Expected an error to be raised', + ) const invalidOpcode = errorMessage.includes('invalid opcode') const reverted = errorMessage.includes( 'VM Exception while processing transaction: revert', From ff651a2a4a7b966a1caa8004df98435f6abb1b18 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Mon, 11 Nov 2019 10:18:27 +0000 Subject: [PATCH 159/199] Bump github.com/tidwall/gjson from 1.3.2 to 1.3.4 Bumps [github.com/tidwall/gjson](https://github.com/tidwall/gjson) from 1.3.2 to 1.3.4. - [Release notes](https://github.com/tidwall/gjson/releases) - [Commits](https://github.com/tidwall/gjson/compare/v1.3.2...v1.3.4) Signed-off-by: dependabot-preview[bot] --- go.mod | 2 +- go.sum | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index d636df110ec..cf9d0231cb4 100644 --- a/go.mod +++ b/go.mod @@ -54,7 +54,7 @@ require ( github.com/stretchr/testify v1.3.1-0.20190712000136-221dbe5ed467 github.com/syndtr/goleveldb v0.0.0-20190203031304-2f17a3356c66 // indirect github.com/tevino/abool v0.0.0-20170917061928-9b9efcf221b5 - github.com/tidwall/gjson v1.3.2 + github.com/tidwall/gjson v1.3.4 github.com/tidwall/sjson v1.0.4 github.com/ugorji/go/codec v1.1.7 github.com/ulule/limiter v0.0.0-20190417201358-7873d115fc4e diff --git a/go.sum b/go.sum index 1058ebc32d7..841413916cf 100644 --- a/go.sum +++ b/go.sum @@ -325,6 +325,8 @@ github.com/tevino/abool v0.0.0-20170917061928-9b9efcf221b5 h1:hNna6Fi0eP1f2sMBe/ github.com/tevino/abool v0.0.0-20170917061928-9b9efcf221b5/go.mod h1:f1SCnEOt6sc3fOJfPQDRDzHOtSXuTtnz0ImG9kPRDV0= github.com/tidwall/gjson v1.3.2 h1:+7p3qQFaH3fOMXAJSrdZwGKcOO/lYdGS0HqGhPqDdTI= github.com/tidwall/gjson v1.3.2/go.mod h1:P256ACg0Mn+j1RXIDXoss50DeIABTYK1PULOJHhxOls= +github.com/tidwall/gjson v1.3.4 h1:On5waDnyKKk3SWE4EthbjjirAWXp43xx5cKCUZY1eZw= +github.com/tidwall/gjson v1.3.4/go.mod h1:P256ACg0Mn+j1RXIDXoss50DeIABTYK1PULOJHhxOls= github.com/tidwall/match v1.0.1 h1:PnKP62LPNxHKTwvHHZZzdOAOCtsJTjo6dZLCwpKm5xc= github.com/tidwall/match v1.0.1/go.mod h1:LujAq0jyVjBy028G1WhWfIzbpQfMO8bBZ6Tyb0+pL9E= github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4= From 731b89242034f58fbb8bac6ed13370e93d94a1cf Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Mon, 11 Nov 2019 10:19:13 +0000 Subject: [PATCH 160/199] Bump github.com/olekukonko/tablewriter from 0.0.1 to 0.0.2 Bumps [github.com/olekukonko/tablewriter](https://github.com/olekukonko/tablewriter) from 0.0.1 to 0.0.2. - [Release notes](https://github.com/olekukonko/tablewriter/releases) - [Commits](https://github.com/olekukonko/tablewriter/compare/v0.0.1...v0.0.2) Signed-off-by: dependabot-preview[bot] --- go.mod | 2 +- go.sum | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index d636df110ec..47e9e87edba 100644 --- a/go.mod +++ b/go.mod @@ -40,7 +40,7 @@ require ( github.com/mattn/go-runewidth v0.0.4 // indirect github.com/mitchellh/go-homedir v1.1.0 github.com/mrwonko/cron v0.0.0-20180828170130-e0ddd0f7e7db - github.com/olekukonko/tablewriter v0.0.1 + github.com/olekukonko/tablewriter v0.0.2 github.com/onsi/gomega v1.7.0 github.com/pborman/uuid v0.0.0-20180906182336-adf5a7427709 // indirect github.com/pkg/errors v0.8.1 diff --git a/go.sum b/go.sum index 1058ebc32d7..d40f608407e 100644 --- a/go.sum +++ b/go.sum @@ -239,6 +239,8 @@ github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRW github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/olekukonko/tablewriter v0.0.1 h1:b3iUnf1v+ppJiOfNX4yxxqfWKMQPZR5yoh8urCTFX88= github.com/olekukonko/tablewriter v0.0.1/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= +github.com/olekukonko/tablewriter v0.0.2 h1:sq53g+DWf0J6/ceFUHpQ0nAEb6WgM++fq16MZ91cS6o= +github.com/olekukonko/tablewriter v0.0.2/go.mod h1:rSAaSIOAGT9odnlyGlUfAJaoc5w2fSBUmeGDbRWPxyQ= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= From 694de2a7cb751dc90f279b1095d4831fb8c20f54 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Mon, 11 Nov 2019 10:19:55 +0000 Subject: [PATCH 161/199] Bump github.com/spf13/viper from 1.4.0 to 1.5.0 Bumps [github.com/spf13/viper](https://github.com/spf13/viper) from 1.4.0 to 1.5.0. - [Release notes](https://github.com/spf13/viper/releases) - [Commits](https://github.com/spf13/viper/compare/v1.4.0...v1.5.0) Signed-off-by: dependabot-preview[bot] --- go.mod | 2 +- go.sum | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index d636df110ec..024f1ba9849 100644 --- a/go.mod +++ b/go.mod @@ -48,7 +48,7 @@ require ( github.com/rs/cors v1.6.0 // indirect github.com/satori/go.uuid v1.2.0 github.com/spf13/afero v1.2.1 // indirect - github.com/spf13/viper v1.4.0 + github.com/spf13/viper v1.5.0 github.com/steakknife/bloomfilter v0.0.0-20180922174646-6819c0d2a570 // indirect github.com/steakknife/hamming v0.0.0-20180906055917-c99c65617cd3 // indirect github.com/stretchr/testify v1.3.1-0.20190712000136-221dbe5ed467 diff --git a/go.sum b/go.sum index 1058ebc32d7..625a01907ac 100644 --- a/go.sum +++ b/go.sum @@ -211,6 +211,8 @@ github.com/lib/pq v1.1.1 h1:sJZmqHoEaY7f+NPP8pgLB/WxulyR3fewgCM2qaSlBb4= github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4= +github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/manyminds/api2go v0.0.0-20171030193247-e7b693844a6f h1:tVvGiZQFjOXP+9YyGqSA6jE55x1XVxmoPYudncxrZ8U= github.com/manyminds/api2go v0.0.0-20171030193247-e7b693844a6f/go.mod h1:Z60vy0EZVSu0bOugCHdcN5ZxFMKSpjRgsnh0XKPFqqk= github.com/mattn/go-colorable v0.0.9 h1:UVL0vNpWh04HeJXV0KLcaT7r06gOH2l4OW6ddYRUIY4= @@ -306,6 +308,8 @@ github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnIn github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= github.com/spf13/viper v1.4.0 h1:yXHLWeravcrgGyFSyCgdYpXQ9dR9c/WED3pg1RhxqEU= github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= +github.com/spf13/viper v1.5.0 h1:GpsTwfsQ27oS/Aha/6d1oD7tpKIqWnOA6tgOX9HHkt4= +github.com/spf13/viper v1.5.0/go.mod h1:AkYRkVJF8TkSG/xet6PzXX+l39KhhXa2pdqVSxnTcn4= github.com/steakknife/bloomfilter v0.0.0-20180922174646-6819c0d2a570 h1:gIlAHnH1vJb5vwEjIp5kBj/eu99p/bl0Ay2goiPe5xE= github.com/steakknife/bloomfilter v0.0.0-20180922174646-6819c0d2a570/go.mod h1:8OR4w3TdeIHIh1g6EMY5p0gVNOovcWC+1vpc7naMuAw= github.com/steakknife/hamming v0.0.0-20180906055917-c99c65617cd3 h1:njlZPzLwU639dk2kqnCPPv+wNjq7Xb6EfUxe/oX0/NM= @@ -319,6 +323,8 @@ github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.3.1-0.20190712000136-221dbe5ed467 h1:/pva5wyh0PKqe0bnHBbndEzbqsilMKFNXI0GPbO+L8c= github.com/stretchr/testify v1.3.1-0.20190712000136-221dbe5ed467/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= +github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/syndtr/goleveldb v0.0.0-20190203031304-2f17a3356c66 h1:AwmkkZT+TucFotNCL+aNJ/0KCMsRtlXN9fs8uoOMSRk= github.com/syndtr/goleveldb v0.0.0-20190203031304-2f17a3356c66/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ= github.com/tevino/abool v0.0.0-20170917061928-9b9efcf221b5 h1:hNna6Fi0eP1f2sMBe/rJicDmaHmoXGe1Ta84FPYHLuE= @@ -485,6 +491,8 @@ gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bl gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= From fc861bb502eda5ff807ad51fb3bd487dd0dedd7a Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Mon, 11 Nov 2019 10:20:53 +0000 Subject: [PATCH 162/199] Bump go.dedis.ch/kyber/v3 from 3.0.7 to 3.0.9 Bumps [go.dedis.ch/kyber/v3](https://github.com/dedis/kyber) from 3.0.7 to 3.0.9. - [Release notes](https://github.com/dedis/kyber/releases) - [Commits](https://github.com/dedis/kyber/compare/v3.0.7...v3.0.9) Signed-off-by: dependabot-preview[bot] --- go.mod | 2 +- go.sum | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index d636df110ec..240e6627a6d 100644 --- a/go.mod +++ b/go.mod @@ -62,7 +62,7 @@ require ( github.com/urfave/cli v1.22.1 github.com/willf/pad v0.0.0-20190207183901-eccfe5d84172 go.dedis.ch/fixbuf v1.0.3 - go.dedis.ch/kyber/v3 v3.0.7 + go.dedis.ch/kyber/v3 v3.0.9 go.uber.org/multierr v1.2.0 go.uber.org/zap v1.11.0 golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 diff --git a/go.sum b/go.sum index 1058ebc32d7..5f3a2d7a8f7 100644 --- a/go.sum +++ b/go.sum @@ -357,6 +357,8 @@ go.dedis.ch/kyber/v3 v3.0.4 h1:FDuC/S3STkvwxZ0ooo3gcp56QkUKsN7Jy7cpzBxL+vQ= go.dedis.ch/kyber/v3 v3.0.4/go.mod h1:OzvaEnPvKlyrWyp3kGXlFdp7ap1VC6RkZDTaPikqhsQ= go.dedis.ch/kyber/v3 v3.0.7 h1:yBaDckJWBkkK2xChU+vXotLqQareeRkfwY++GSwLkOI= go.dedis.ch/kyber/v3 v3.0.7/go.mod h1:V1z0JihG9+dUEUCKLI9j9tjnlIflBw3wx8UOg0g3Pnk= +go.dedis.ch/kyber/v3 v3.0.9 h1:i0ZbOQocHUjfFasBiUql5zVeC7u/vahFd96DFA8UOWk= +go.dedis.ch/kyber/v3 v3.0.9/go.mod h1:rhNjUUg6ahf8HEg5HUvVBYoWY4boAafX8tYxX+PS+qg= go.dedis.ch/protobuf v1.0.5 h1:EbF1czEKICxf5KY8Tm7wMF28hcOQbB6yk4IybIFWTYE= go.dedis.ch/protobuf v1.0.5/go.mod h1:eIV4wicvi6JK0q/QnfIEGeSFNG0ZeB24kzut5+HaRLo= go.dedis.ch/protobuf v1.0.7 h1:wRUEiq3u0/vBhLjcw9CmAVrol+BnDyq2M0XLukdphyI= From b77f4143591e3ec4123685379f49c9fc6a7b4624 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Mon, 11 Nov 2019 11:07:23 +0000 Subject: [PATCH 163/199] Bump cbor from 4.1.5 to 5.0.1 Bumps [cbor](https://github.com/hildjj/node-cbor) from 4.1.5 to 5.0.1. - [Release notes](https://github.com/hildjj/node-cbor/releases) - [Commits](https://github.com/hildjj/node-cbor/compare/v4.1.5...v5.0.1) Signed-off-by: dependabot-preview[bot] --- evm/package.json | 2 +- evm/v0.5/package.json | 2 +- examples/echo_server/package.json | 2 +- examples/uptime_sla/package.json | 2 +- yarn.lock | 25 ++++++++++++++----------- 5 files changed, 18 insertions(+), 15 deletions(-) diff --git a/evm/package.json b/evm/package.json index 453eb9594de..2837c5039d7 100644 --- a/evm/package.json +++ b/evm/package.json @@ -22,7 +22,7 @@ "export-typings": "ts-node scripts/export-generated-contract-factories" }, "dependencies": { - "cbor": "^4.1.1", + "cbor": "^5.0.1", "chainlinkv0.5": "0.0.2", "ethers": "^4.0.37", "link_token": "^1.0.6", diff --git a/evm/v0.5/package.json b/evm/v0.5/package.json index 0bde0527639..749cb6aa428 100644 --- a/evm/v0.5/package.json +++ b/evm/v0.5/package.json @@ -21,7 +21,7 @@ }, "dependencies": { "bn.js": "^4.11.0", - "cbor": "^4.1.1", + "cbor": "^5.0.1", "ethereumjs-abi": "^0.6.8", "ethereumjs-util": "^6.1.0", "truffle-contract": "^4.0.31", diff --git a/examples/echo_server/package.json b/examples/echo_server/package.json index df4f8b4ab6b..94feb92eea3 100644 --- a/examples/echo_server/package.json +++ b/examples/echo_server/package.json @@ -17,7 +17,7 @@ "@babel/polyfill": "^7.2.5", "@babel/register": "^7.6.2", "body-parser": "^1.18.3", - "cbor": "^4.0.0", + "cbor": "^5.0.1", "chainlink": "^0.6.5", "express": "^4.16.2", "link_token": "^1.0.6", diff --git a/examples/uptime_sla/package.json b/examples/uptime_sla/package.json index 9b09a95b278..c05233771c7 100644 --- a/examples/uptime_sla/package.json +++ b/examples/uptime_sla/package.json @@ -17,7 +17,7 @@ "@chainlink/prettier-config": "0.0.1", "bignumber.js": "^9.0.0", "bn.js": "^4.11.8", - "cbor": "^4.0.0", + "cbor": "^5.0.1", "chainlink": "^0.6.5", "depcheck": "^0.8.3", "eslint": "^6.3.0", diff --git a/yarn.lock b/yarn.lock index b2d4d28c43d..f7bc7628268 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6136,7 +6136,15 @@ caw@^2.0.0: tunnel-agent "^0.6.0" url-to-options "^1.0.1" -cbor@>=4.0.0, cbor@^4.0.0, cbor@^4.1.1: +cbor@>=4.0.0, cbor@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/cbor/-/cbor-5.0.1.tgz#243eea46b19c6e54ffb18fb07fa52c1c627a6f05" + integrity sha512-l4ghwqioCyuAaD3LvY4ONwv8NMuERz62xjbMHGdWBqERJPygVmoFER1b4+VS6iW0rXwoVGuKZPPPTofwWOg3YQ== + dependencies: + bignumber.js "^9.0.0" + nofilter "^1.0.3" + +cbor@^4.1.1: version "4.1.5" resolved "https://registry.yarnpkg.com/cbor/-/cbor-4.1.5.tgz#c8ef3702b2f8710bde1425903c0c16a50419d369" integrity sha512-WqpISHl7/kk1u1uoaqctGyGiET3+wGN4A6pPsFCzEBztJJlCVxnMB4JwcuuNSrMiV4V6UBlxMkhNzJrUxDWO1A== @@ -6762,12 +6770,7 @@ commander@2.18.0: resolved "https://registry.yarnpkg.com/commander/-/commander-2.18.0.tgz#2bf063ddee7c7891176981a2cc798e5754bc6970" integrity sha512-6CYPa+JP2ftfRU2qkDK+UTVeQYosOg/2GbcjIcKPHfinyOLPVGXu/ovN86RP49Re5ndJK1N0kuiidFFuepc4ZQ== -commander@^2.11.0, commander@^2.18.0, commander@^2.19.0, commander@^2.20.0, commander@^2.8.1, commander@~2.20.0: - version "2.20.0" - resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.0.tgz#d58bb2b5c1ee8f87b0d340027e9e94e222c5a422" - integrity sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ== - -commander@^2.12.2, commander@^2.5.0: +commander@^2.11.0, commander@^2.12.2, commander@^2.18.0, commander@^2.19.0, commander@^2.20.0, commander@^2.5.0, commander@^2.8.1, commander@~2.20.0: version "2.20.3" resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== @@ -15526,10 +15529,10 @@ node-sass-tilde-importer@^1.0.2: dependencies: find-parent-dir "^0.3.0" -nofilter@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/nofilter/-/nofilter-1.0.2.tgz#d2887b9e76531fa251f20038c3843b993f1b3e26" - integrity sha512-d38SORxm9UNoDsnPXajV9nBEebKX4/paXAlyRGnSjZuFbLLZDFUO4objr+tbybqsbqGXDWllb6gQoKUDc9q3Cg== +nofilter@^1.0.1, nofilter@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/nofilter/-/nofilter-1.0.3.tgz#34e54b4cc9757de0cad38cc0d19462489b1b7f5d" + integrity sha512-FlUlqwRK6reQCaFLAhMcF+6VkVG2caYjKQY3YsRDTl4/SEch595Qb3oLjJRDr8dkHAAOVj2pOx3VknfnSgkE5g== noop-logger@^0.1.1: version "0.1.1" From 04ff6610853b27b8d95aabb02f148d32842b3a20 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Mon, 11 Nov 2019 11:08:37 +0000 Subject: [PATCH 164/199] Bump react-hot-loader from 4.12.15 to 4.12.16 Bumps [react-hot-loader](https://github.com/gaearon/react-hot-loader) from 4.12.15 to 4.12.16. - [Release notes](https://github.com/gaearon/react-hot-loader/releases) - [Changelog](https://github.com/gaearon/react-hot-loader/blob/master/CHANGELOG.md) - [Commits](https://github.com/gaearon/react-hot-loader/compare/v4.12.15...v4.12.16) Signed-off-by: dependabot-preview[bot] --- explorer/client/package.json | 2 +- operator_ui/package.json | 2 +- yarn.lock | 17 +++++++++-------- 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/explorer/client/package.json b/explorer/client/package.json index ef2b67ea1b2..602ba88a479 100644 --- a/explorer/client/package.json +++ b/explorer/client/package.json @@ -81,7 +81,7 @@ "html-webpack-plugin": "4.0.0-beta.5", "module-to-cdn": "^3.1.2", "prettier": "^1.18.2", - "react-hot-loader": "^4.8.0", + "react-hot-loader": "^4.12.16", "react-scripts": "^3.1.0", "redux-devtools-extension": "^2.13.8", "redux-logger": "^3.0.6", diff --git a/operator_ui/package.json b/operator_ui/package.json index c3b839a2585..31936b6d43a 100644 --- a/operator_ui/package.json +++ b/operator_ui/package.json @@ -91,7 +91,7 @@ "jest-silent-reporter": "^0.1.2", "mock-local-storage": "^1.0.5", "prettier": "^1.18.2", - "react-hot-loader": "^4.6.3", + "react-hot-loader": "^4.12.16", "react-static": "^6.3.8", "react-static-plugin-jss": "^6.3.0", "react-static-plugin-react-router": "^6.3.4", diff --git a/yarn.lock b/yarn.lock index b2d4d28c43d..30e15540f0b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3027,10 +3027,10 @@ dependencies: "@types/react" "*" -"@types/react@*", "@types/react@^16.9.5": - version "16.9.5" - resolved "https://registry.yarnpkg.com/@types/react/-/react-16.9.5.tgz#079dabd918b19b32118c25fd00a786bb6d0d5e51" - integrity sha512-jQ12VMiFOWYlp+j66dghOWcmDDwhca0bnlcTxS4Qz/fh5gi6wpaZDthPEu/Gc/YlAuO87vbiUXL8qKstFvuOaA== +"@types/react@*", "@types/react@^15.0.0 || ^16.0.0", "@types/react@^16.9.5": + version "16.9.11" + resolved "https://registry.yarnpkg.com/@types/react/-/react-16.9.11.tgz#70e0b7ad79058a7842f25ccf2999807076ada120" + integrity sha512-UBT4GZ3PokTXSWmdgC/GeCGEJXE5ofWyibCcecRLUVN2ZBpXQGVgQGtG2foS7CrTKFKlQVVswLvf7Js6XA/CVQ== dependencies: "@types/prop-types" "*" csstype "^2.2.0" @@ -18197,11 +18197,12 @@ react-helmet@^5.2.0: react-fast-compare "^2.0.2" react-side-effect "^1.1.0" -react-hot-loader@^4.3.6, react-hot-loader@^4.6.1, react-hot-loader@^4.6.3, react-hot-loader@^4.8.0: - version "4.12.15" - resolved "https://registry.yarnpkg.com/react-hot-loader/-/react-hot-loader-4.12.15.tgz#6bf3984e52edbdf02ea8952777f53da1b3c68c95" - integrity sha512-sgkN6g+tgPE6xZzD0Ysqll7KUFYJbMX0DrczT5OxD6S7hZlSnmqSC3ceudwCkiDd65ZTtm+Ayk4Y9k5xxCvpOw== +react-hot-loader@^4.12.16, react-hot-loader@^4.3.6, react-hot-loader@^4.6.1: + version "4.12.16" + resolved "https://registry.yarnpkg.com/react-hot-loader/-/react-hot-loader-4.12.16.tgz#353bd07fbb08f791b5720535f86b0a8f9b651317" + integrity sha512-KC33uTBacgdunMtfpZFP2pgPpyvKIcCwuh0XmWESbeFrkWLqUtCFN91zyaTdU5OiAM982+E8xh1gjE5EINumaw== dependencies: + "@types/react" "^15.0.0 || ^16.0.0" fast-levenshtein "^2.0.6" global "^4.3.0" hoist-non-react-statics "^3.3.0" From 82a2fdfad963fc1140df6cc4b9711a26e374ceae Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Mon, 11 Nov 2019 11:09:30 +0000 Subject: [PATCH 165/199] Bump @typescript-eslint/parser from 2.5.0 to 2.6.1 Bumps [@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser) from 2.5.0 to 2.6.1. - [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases) - [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/parser/CHANGELOG.md) - [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v2.6.1/packages/parser) Signed-off-by: dependabot-preview[bot] --- tools/eslint-config/package.json | 2 +- yarn.lock | 33 ++++++++++++++++---------------- 2 files changed, 18 insertions(+), 17 deletions(-) diff --git a/tools/eslint-config/package.json b/tools/eslint-config/package.json index d227215f202..b846541abc0 100644 --- a/tools/eslint-config/package.json +++ b/tools/eslint-config/package.json @@ -15,7 +15,7 @@ }, "dependencies": { "@typescript-eslint/eslint-plugin": "^2.1.0", - "@typescript-eslint/parser": "^2.5.0", + "@typescript-eslint/parser": "^2.6.1", "eslint-config-prettier": "^6.2.0", "eslint-plugin-prettier": "^3.1.0", "eslint-plugin-react": "^7.14.3", diff --git a/yarn.lock b/yarn.lock index b2d4d28c43d..68d326ad6dc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3238,13 +3238,13 @@ "@typescript-eslint/typescript-estree" "2.1.0" eslint-scope "^4.0.0" -"@typescript-eslint/experimental-utils@2.5.0": - version "2.5.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-2.5.0.tgz#383a97ded9a7940e5053449f6d73995e782b8fb1" - integrity sha512-UgcQGE0GKJVChyRuN1CWqDW8Pnu7+mVst0aWrhiyuUD1J9c+h8woBdT4XddCvhcXDodTDVIfE3DzGHVjp7tUeQ== +"@typescript-eslint/experimental-utils@2.6.1": + version "2.6.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-2.6.1.tgz#eddaca17a399ebf93a8628923233b4f93793acfd" + integrity sha512-EVrrUhl5yBt7fC7c62lWmriq4MIc49zpN3JmrKqfiFXPXCM5ErfEcZYfKOhZXkW6MBjFcJ5kGZqu1b+lyyExUw== dependencies: "@types/json-schema" "^7.0.3" - "@typescript-eslint/typescript-estree" "2.5.0" + "@typescript-eslint/typescript-estree" "2.6.1" eslint-scope "^5.0.0" "@typescript-eslint/parser@1.13.0": @@ -3257,14 +3257,14 @@ "@typescript-eslint/typescript-estree" "1.13.0" eslint-visitor-keys "^1.0.0" -"@typescript-eslint/parser@^2.5.0": - version "2.5.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-2.5.0.tgz#858030ddd808fbbe88e03f42e5971efaccb8218a" - integrity sha512-9UBMiAwIDWSl79UyogaBdj3hidzv6exjKUx60OuZuFnJf56tq/UMpdPcX09YmGqE8f4AnAueYtBxV8IcAT3jdQ== +"@typescript-eslint/parser@^2.6.1": + version "2.6.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-2.6.1.tgz#3c00116baa0d696bc334ca18ac5286b34793993c" + integrity sha512-PDPkUkZ4c7yA+FWqigjwf3ngPUgoLaGjMlFh6TRtbjhqxFBnkElDfckSjm98q9cMr4xRzZ15VrS/xKm6QHYf0w== dependencies: "@types/eslint-visitor-keys" "^1.0.0" - "@typescript-eslint/experimental-utils" "2.5.0" - "@typescript-eslint/typescript-estree" "2.5.0" + "@typescript-eslint/experimental-utils" "2.6.1" + "@typescript-eslint/typescript-estree" "2.6.1" eslint-visitor-keys "^1.1.0" "@typescript-eslint/typescript-estree@1.13.0": @@ -3285,16 +3285,17 @@ lodash.unescape "4.0.1" semver "^6.2.0" -"@typescript-eslint/typescript-estree@2.5.0": - version "2.5.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-2.5.0.tgz#40ada624d6217ef092a3a79ed30d947ad4f212ce" - integrity sha512-AXURyF8NcA3IsnbjNX1v9qbwa0dDoY9YPcKYR2utvMHoUcu3636zrz0gRWtVAyxbPCkhyKuGg6WZIyi2Fc79CA== +"@typescript-eslint/typescript-estree@2.6.1": + version "2.6.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-2.6.1.tgz#fb363dd4ca23384745c5ea4b7f4c867432b00d31" + integrity sha512-+sTnssW6bcbDZKE8Ce7VV6LdzkQz2Bxk7jzk1J8H1rovoTxnm6iXvYIyncvNsaB/kBCOM63j/LNJfm27bNdUoA== dependencies: debug "^4.1.1" glob "^7.1.4" is-glob "^4.0.1" lodash.unescape "4.0.1" semver "^6.3.0" + tsutils "^3.17.1" "@usulpro/color-picker@^1.1.3": version "1.1.3" @@ -21686,7 +21687,7 @@ tslib@^1.9.0, tslib@^1.9.3: resolved "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz#c3c19f95973fb0a62973fb09d90d961ee43e5c8a" integrity sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ== -tsutils@^3.14.0, tsutils@^3.7.0: +tsutils@^3.14.0, tsutils@^3.17.1, tsutils@^3.7.0: version "3.17.1" resolved "https://registry.npmjs.org/tsutils/-/tsutils-3.17.1.tgz#ed719917f11ca0dee586272b2ac49e015a2dd759" integrity sha512-kzeQ5B8H3w60nFY2g8cJIuH7JDpsALXySGtwGJ0p2LSjLgay3NdIpqq5SoOBe46bKDW2iq25irHCr8wjomUS2g== From 5d6f642c03259b71515f9252ec7904feaab275e5 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Mon, 11 Nov 2019 11:09:43 +0000 Subject: [PATCH 166/199] Bump @types/react-router-dom from 4.3.5 to 5.1.2 Bumps [@types/react-router-dom](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react-router-dom) from 4.3.5 to 5.1.2. - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/react-router-dom) Signed-off-by: dependabot-preview[bot] --- operator_ui/package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/operator_ui/package.json b/operator_ui/package.json index c3b839a2585..c755d58c838 100644 --- a/operator_ui/package.json +++ b/operator_ui/package.json @@ -80,7 +80,7 @@ "@types/react-redux": "~6.0.0", "@types/react-resize-detector": "^4.0.1", "@types/react-router": "^5.1.2", - "@types/react-router-dom": "^4.3.4", + "@types/react-router-dom": "^5.1.2", "@types/redux-mock-store": "^1.0.1", "depcheck": "^0.8.3", "enzyme": "^3.10.0", diff --git a/yarn.lock b/yarn.lock index b2d4d28c43d..d50360545f9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2996,10 +2996,10 @@ dependencies: "@types/react" "*" -"@types/react-router-dom@^4.3.4": - version "4.3.5" - resolved "https://registry.npmjs.org/@types/react-router-dom/-/react-router-dom-4.3.5.tgz#72f229967690c890d00f96e6b85e9ee5780db31f" - integrity sha512-eFajSUASYbPHg2BDM1G8Btx+YqGgvROPIg6sBhl3O4kbDdYXdFdfrgQFf/pcBuQVObjfT9AL/dd15jilR5DIEA== +"@types/react-router-dom@^5.1.2": + version "5.1.2" + resolved "https://registry.yarnpkg.com/@types/react-router-dom/-/react-router-dom-5.1.2.tgz#853f229f1f297513c0be84f7c914a08b778cfdf5" + integrity sha512-kRx8hoBflE4Dp7uus+j/0uMHR5uGTAvQtc4A3vOTWKS+epe0leCuxEx7HNT7XGUd1lH53/moWM51MV2YUyhzAg== dependencies: "@types/history" "*" "@types/react" "*" From 1b77eed04dd1b87385729cc7ce49df44b1165b0a Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Mon, 11 Nov 2019 13:44:31 +0000 Subject: [PATCH 167/199] Bump go.uber.org/multierr from 1.2.0 to 1.4.0 Bumps [go.uber.org/multierr](https://github.com/uber-go/multierr) from 1.2.0 to 1.4.0. - [Release notes](https://github.com/uber-go/multierr/releases) - [Changelog](https://github.com/uber-go/multierr/blob/master/CHANGELOG.md) - [Commits](https://github.com/uber-go/multierr/compare/v1.2.0...v1.4.0) Signed-off-by: dependabot-preview[bot] --- go.mod | 2 +- go.sum | 19 +++++++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 3bbf8d7cc92..3c4c9826c32 100644 --- a/go.mod +++ b/go.mod @@ -63,7 +63,7 @@ require ( github.com/willf/pad v0.0.0-20190207183901-eccfe5d84172 go.dedis.ch/fixbuf v1.0.3 go.dedis.ch/kyber/v3 v3.0.9 - go.uber.org/multierr v1.2.0 + go.uber.org/multierr v1.4.0 go.uber.org/zap v1.11.0 golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 golang.org/x/sync v0.0.0-20190423024810-112230192c58 diff --git a/go.sum b/go.sum index e722adbb1fc..c95ba6e9e4f 100644 --- a/go.sum +++ b/go.sum @@ -145,6 +145,7 @@ github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.1.0 h1:Jf4mxPC/ziBnoPIdpQdPJ9OeiomAUHLvxmPRSPH9m4s= github.com/google/uuid v1.1.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= @@ -371,10 +372,16 @@ go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= go.uber.org/atomic v1.4.0 h1:cxzIVoETapQEqDhQu3QfnvXAV4AlzcvUCxkVUFw3+EU= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.5.0 h1:OI5t8sDa1Or+q8AeE+yKeB/SDYioSHAgcVljj9JIETY= +go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/multierr v1.1.0 h1:HoEmRHQPVSqub6w2z2d2EOVs2fjyFRGyofhKuyDq0QI= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/multierr v1.2.0 h1:6I+W7f5VwC5SV9dNrZ3qXrDB9mD0dyGOi/ZJmYw03T4= go.uber.org/multierr v1.2.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/multierr v1.4.0 h1:f3WCSC2KzAcBXGATIxAB1E2XuCpNU255wNKZ505qi3E= +go.uber.org/multierr v1.4.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= +go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee h1:0mgffUl7nfd+FpvXMVz4IDEaUSmT1ysygQC7qYo7sG4= +go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= go.uber.org/zap v1.10.0 h1:ORx85nbTijNz8ljznvCMR1ZBIPKFn3jQrag10X2AsuM= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.11.0 h1:gSmpCfs+R47a4yQPAI4xJ0IPDLTRGXskm6UelqNXpqE= @@ -386,6 +393,7 @@ golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnf golang.org/x/crypto v0.0.0-20190123085648-057139ce5d2b/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 h1:HuIa8hRrWRSrqYzx1qI49NNxhdi2PrY7gxVSq1JjLDc= golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= @@ -394,6 +402,9 @@ golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTk golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/net v0.0.0-20170324220409-6c2325251549/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -410,6 +421,7 @@ golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c h1:uOCk1iQW6Vc18bnC13MfzScl+ golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190522155817-f3200d17e092 h1:4QSRKanuywn15aTZvI/mIDEgPQpswuFndXpOj3rKEco= golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -451,7 +463,12 @@ golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3 golang.org/x/tools v0.0.0-20190312170243-e65039ee4138 h1:H3uGjxCR/6Ds0Mjgyp7LMK81+LvmbvWWEnJhzk1Pi9E= golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190624180213-70d37148ca0c/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5 h1:hKsoRgsbwY1NafxrwTs+k64bikrLBkAgPir1TNCj3Zs= +golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -494,3 +511,5 @@ gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= From 574ba9035f9111ba4571cd898a1ab6722e0be389 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Mon, 11 Nov 2019 14:14:08 +0000 Subject: [PATCH 168/199] Bump github.com/onsi/gomega from 1.7.0 to 1.7.1 Bumps [github.com/onsi/gomega](https://github.com/onsi/gomega) from 1.7.0 to 1.7.1. - [Release notes](https://github.com/onsi/gomega/releases) - [Changelog](https://github.com/onsi/gomega/blob/master/CHANGELOG.md) - [Commits](https://github.com/onsi/gomega/compare/v1.7.0...v1.7.1) Signed-off-by: dependabot-preview[bot] --- go.mod | 2 +- go.sum | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 3c4c9826c32..deb65b186a4 100644 --- a/go.mod +++ b/go.mod @@ -41,7 +41,7 @@ require ( github.com/mitchellh/go-homedir v1.1.0 github.com/mrwonko/cron v0.0.0-20180828170130-e0ddd0f7e7db github.com/olekukonko/tablewriter v0.0.2 - github.com/onsi/gomega v1.7.0 + github.com/onsi/gomega v1.7.1 github.com/pborman/uuid v0.0.0-20180906182336-adf5a7427709 // indirect github.com/pkg/errors v0.8.1 github.com/rjeczalik/notify v0.9.2 // indirect diff --git a/go.sum b/go.sum index c95ba6e9e4f..999a72cf20b 100644 --- a/go.sum +++ b/go.sum @@ -248,6 +248,8 @@ github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+W github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.0 h1:XPnZz8VVBHjVsy1vzJmRwIcSwiUO+JFfrv/xGiigmME= github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.7.1 h1:K0jcRCwNQM3vFGh1ppMtDh/+7ApJrjldlX8fA0jDTLQ= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw= github.com/pborman/uuid v0.0.0-20180906182336-adf5a7427709 h1:zNBQb37RGLmJybyMcs983HfUfpkw9OTFD9tbBfAViHE= github.com/pborman/uuid v0.0.0-20180906182336-adf5a7427709/go.mod h1:VyrYX9gd7irzKovcSS6BIIEwPRkP2Wm2m9ufcdFSJ34= @@ -421,6 +423,7 @@ golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c h1:uOCk1iQW6Vc18bnC13MfzScl+ golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190522155817-f3200d17e092 h1:4QSRKanuywn15aTZvI/mIDEgPQpswuFndXpOj3rKEco= golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -508,6 +511,8 @@ gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bl gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= From c7ad5e24913bee33e07f024bcdcc8402247fb776 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Mon, 11 Nov 2019 14:30:02 +0000 Subject: [PATCH 169/199] Bump go.uber.org/zap from 1.11.0 to 1.12.0 Bumps [go.uber.org/zap](https://github.com/uber-go/zap) from 1.11.0 to 1.12.0. - [Release notes](https://github.com/uber-go/zap/releases) - [Changelog](https://github.com/uber-go/zap/blob/master/CHANGELOG.md) - [Commits](https://github.com/uber-go/zap/compare/v1.11.0...v1.12.0) Signed-off-by: dependabot-preview[bot] --- go.mod | 2 +- go.sum | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 57cc8e3dd47..62297901bc6 100644 --- a/go.mod +++ b/go.mod @@ -64,7 +64,7 @@ require ( go.dedis.ch/fixbuf v1.0.3 go.dedis.ch/kyber/v3 v3.0.9 go.uber.org/multierr v1.4.0 - go.uber.org/zap v1.11.0 + go.uber.org/zap v1.12.0 golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 golang.org/x/sync v0.0.0-20190423024810-112230192c58 golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2 diff --git a/go.sum b/go.sum index 1be8e514314..a4b1ec2b11a 100644 --- a/go.sum +++ b/go.sum @@ -326,6 +326,7 @@ github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.3.1-0.20190712000136-221dbe5ed467 h1:/pva5wyh0PKqe0bnHBbndEzbqsilMKFNXI0GPbO+L8c= github.com/stretchr/testify v1.3.1-0.20190712000136-221dbe5ed467/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/syndtr/goleveldb v0.0.0-20190203031304-2f17a3356c66 h1:AwmkkZT+TucFotNCL+aNJ/0KCMsRtlXN9fs8uoOMSRk= @@ -384,6 +385,7 @@ go.uber.org/multierr v1.1.0 h1:HoEmRHQPVSqub6w2z2d2EOVs2fjyFRGyofhKuyDq0QI= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/multierr v1.2.0 h1:6I+W7f5VwC5SV9dNrZ3qXrDB9mD0dyGOi/ZJmYw03T4= go.uber.org/multierr v1.2.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= go.uber.org/multierr v1.4.0 h1:f3WCSC2KzAcBXGATIxAB1E2XuCpNU255wNKZ505qi3E= go.uber.org/multierr v1.4.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee h1:0mgffUl7nfd+FpvXMVz4IDEaUSmT1ysygQC7qYo7sG4= @@ -392,6 +394,8 @@ go.uber.org/zap v1.10.0 h1:ORx85nbTijNz8ljznvCMR1ZBIPKFn3jQrag10X2AsuM= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.11.0 h1:gSmpCfs+R47a4yQPAI4xJ0IPDLTRGXskm6UelqNXpqE= go.uber.org/zap v1.11.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.12.0 h1:dySoUQPFBGj6xwjmBzageVL8jGi8uxc6bEmJQjA06bw= +go.uber.org/zap v1.12.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181112202954-3d3f9f413869/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= From 6d312e1a35180593f13128be4274649c49d6b1bf Mon Sep 17 00:00:00 2001 From: John Barker Date: Fri, 8 Nov 2019 16:49:47 -0700 Subject: [PATCH 170/199] Remove MockGen mock for TxManager --- core/adapters/eth_tx_test.go | 122 ++++----- core/internal/mocks/tx_manager_mocks.go | 346 ------------------------ core/services/head_tracker_test.go | 20 +- core/services/subscription_test.go | 13 +- core/store/store_test.go | 13 +- core/store/tx_manager.go | 1 - core/web/withdrawals_controller_test.go | 22 +- 7 files changed, 86 insertions(+), 451 deletions(-) delete mode 100644 core/internal/mocks/tx_manager_mocks.go diff --git a/core/adapters/eth_tx_test.go b/core/adapters/eth_tx_test.go index df76157986c..fdc9d284baa 100644 --- a/core/adapters/eth_tx_test.go +++ b/core/adapters/eth_tx_test.go @@ -16,9 +16,9 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" - "github.com/golang/mock/gomock" "github.com/onsi/gomega" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" ) @@ -485,22 +485,19 @@ func TestEthTxAdapter_Perform_PendingConfirmations_WithRecoverableErrorInTxManag func TestEthTxAdapter_DeserializationBytesFormat(t *testing.T) { store, cleanup := cltest.NewStore(t) defer cleanup() - ctrl := gomock.NewController(t) - defer ctrl.Finish() - - txmMock := mocks.NewMockTxManager(ctrl) - store.TxManager = txmMock + txManager := new(mocks.TxManager) txAttempt := &models.TxAttempt{} tx := &models.Tx{Attempts: []*models.TxAttempt{txAttempt}} - txmMock.EXPECT().Connected().Return(true).AnyTimes() - txmMock.EXPECT().CreateTxWithGas(gomock.Any(), gomock.Any(), hexutil.MustDecode( + txManager.On("Connected").Maybe().Return(true) + txManager.On("CreateTxWithGas", mock.Anything, mock.Anything, hexutil.MustDecode( "0x00000000"+ "0000000000000000000000000000000000000000000000000000000000000020"+ "000000000000000000000000000000000000000000000000000000000000000b"+ "68656c6c6f20776f726c64000000000000000000000000000000000000000000"), - gomock.Any(), gomock.Any()).Return(tx, nil) - txmMock.EXPECT().CheckAttempt(txAttempt, uint64(0)).Return(&models.TxReceipt{}, strpkg.Unconfirmed, nil) + mock.Anything, mock.Anything).Return(tx, nil) + txManager.On("CheckAttempt", txAttempt, uint64(0)).Return(&models.TxReceipt{}, strpkg.Unconfirmed, nil) + store.TxManager = txManager task := models.TaskSpec{} err := json.Unmarshal([]byte(`{"type": "EthTx", "params": {"format": "bytes"}}`), &task) @@ -516,6 +513,8 @@ func TestEthTxAdapter_DeserializationBytesFormat(t *testing.T) { input := *models.NewRunInputWithResult(models.NewID(), "hello world", models.RunStatusInProgress) result := adapter.Perform(input, store) assert.NoError(t, result.Error()) + + txManager.AssertExpectations(t) } func TestEthTxAdapter_Perform_CustomGas(t *testing.T) { @@ -527,22 +526,19 @@ func TestEthTxAdapter_Perform_CustomGas(t *testing.T) { gasPrice := big.NewInt(187) gasLimit := uint64(911) - ctrl := gomock.NewController(t) - defer ctrl.Finish() - - txmMock := mocks.NewMockTxManager(ctrl) - store.TxManager = txmMock - txmMock.EXPECT().Connected().Return(true) - txmMock.EXPECT().CreateTxWithGas( - gomock.Any(), - gomock.Any(), - gomock.Any(), + txManager := new(mocks.TxManager) + txManager.On("Connected").Return(true) + txManager.On("CreateTxWithGas", + mock.Anything, + mock.Anything, + mock.Anything, gasPrice, gasLimit, ).Return(&models.Tx{ Attempts: []*models.TxAttempt{&models.TxAttempt{}}, }, nil) - txmMock.EXPECT().CheckAttempt(gomock.Any(), gomock.Any()).Return(&models.TxReceipt{}, strpkg.Unconfirmed, nil) + txManager.On("CheckAttempt", mock.Anything, mock.Anything).Return(&models.TxReceipt{}, strpkg.Unconfirmed, nil) + store.TxManager = txManager adapter := adapters.EthTx{ Address: cltest.NewAddress(), @@ -554,6 +550,8 @@ func TestEthTxAdapter_Perform_CustomGas(t *testing.T) { input := *models.NewRunInputWithResult(models.NewID(), "hello world", models.RunStatusInProgress) result := adapter.Perform(input, store) assert.NoError(t, result.Error()) + + txManager.AssertExpectations(t) } func TestEthTxAdapter_Perform_NotConnected(t *testing.T) { @@ -575,25 +573,24 @@ func TestEthTxAdapter_Perform_CreateTxWithGasErrorTreatsAsNotConnected(t *testin store, cleanup := cltest.NewStore(t) defer cleanup() - ctrl := gomock.NewController(t) - defer ctrl.Finish() - - txmMock := mocks.NewMockTxManager(ctrl) - store.TxManager = txmMock - txmMock.EXPECT().Connected().Return(true) - txmMock.EXPECT().CreateTxWithGas( - gomock.Any(), - gomock.Any(), - gomock.Any(), - gomock.Any(), - gomock.Any(), + txManager := new(mocks.TxManager) + txManager.On("Connected").Return(true) + txManager.On("CreateTxWithGas", + mock.Anything, + mock.Anything, + mock.Anything, + mock.Anything, + mock.Anything, ).Return(nil, syscall.ETIMEDOUT) + store.TxManager = txManager adapter := adapters.EthTx{} data := adapter.Perform(models.RunInput{}, store) require.NoError(t, data.Error()) assert.Equal(t, models.RunStatusPendingConnection, data.Status()) + + txManager.AssertExpectations(t) } func TestEthTxAdapter_Perform_CheckAttemptErrorTreatsAsNotConnected(t *testing.T) { @@ -602,28 +599,27 @@ func TestEthTxAdapter_Perform_CheckAttemptErrorTreatsAsNotConnected(t *testing.T store, cleanup := cltest.NewStore(t) defer cleanup() - ctrl := gomock.NewController(t) - defer ctrl.Finish() - - txmMock := mocks.NewMockTxManager(ctrl) - store.TxManager = txmMock - txmMock.EXPECT().Connected().Return(true) - txmMock.EXPECT().CreateTxWithGas( - gomock.Any(), - gomock.Any(), - gomock.Any(), - gomock.Any(), - gomock.Any(), + txManager := new(mocks.TxManager) + txManager.On("Connected").Return(true) + txManager.On("CreateTxWithGas", + mock.Anything, + mock.Anything, + mock.Anything, + mock.Anything, + mock.Anything, ).Return(&models.Tx{ Attempts: []*models.TxAttempt{&models.TxAttempt{}}, }, nil) - txmMock.EXPECT().CheckAttempt(gomock.Any(), gomock.Any()).Return(nil, strpkg.Unknown, syscall.EWOULDBLOCK) + txManager.On("CheckAttempt", mock.Anything, mock.Anything).Return(nil, strpkg.Unknown, syscall.EWOULDBLOCK) + store.TxManager = txManager adapter := adapters.EthTx{} data := adapter.Perform(models.RunInput{}, store) require.NoError(t, data.Error()) assert.Equal(t, models.RunStatusPendingConnection, data.Status()) + + txManager.AssertExpectations(t) } func TestEthTxAdapter_Perform_CreateTxWithEmptyResponseErrorTreatsAsPendingConfirmations(t *testing.T) { @@ -635,24 +631,18 @@ func TestEthTxAdapter_Perform_CreateTxWithEmptyResponseErrorTreatsAsPendingConfi from := cltest.NewAddress() tx := cltest.CreateTx(t, store, from, 1) - ctrl := gomock.NewController(t) - defer ctrl.Finish() - badResponseErr := errors.New("Bad response on request: [ TransactionIndex ]. Error cause was EmptyResponse, (majority count: 94 / total: 94)") - txmMock := mocks.NewMockTxManager(ctrl) - store.TxManager = txmMock - txmMock.EXPECT().Connected().Return(true) - txmMock.EXPECT().CreateTxWithGas( - gomock.Any(), - gomock.Any(), - gomock.Any(), - gomock.Any(), - gomock.Any(), + txManager := new(mocks.TxManager) + txManager.On("Connected").Return(true) + txManager.On("CreateTxWithGas", + mock.Anything, + mock.Anything, + mock.Anything, + mock.Anything, + mock.Anything, ).Return(tx, nil) - txmMock.EXPECT().CheckAttempt( - gomock.Any(), - gomock.Any(), - ).Return(nil, strpkg.Unknown, badResponseErr) + txManager.On("CheckAttempt", mock.Anything, mock.Anything).Return(nil, strpkg.Unknown, badResponseErr) + store.TxManager = txManager adapter := adapters.EthTx{} output := adapter.Perform(models.RunInput{}, store) @@ -661,15 +651,15 @@ func TestEthTxAdapter_Perform_CreateTxWithEmptyResponseErrorTreatsAsPendingConfi assert.Equal(t, models.RunStatusPendingConfirmations, output.Status()) // Have a head come through with the same empty response - txmMock.EXPECT().Connected().Return(true) - txmMock.EXPECT().BumpGasUntilSafe( - gomock.Any(), - ).Return(nil, strpkg.Unknown, badResponseErr) + txManager.On("Connected").Return(true) + txManager.On("BumpGasUntilSafe", mock.Anything).Return(nil, strpkg.Unknown, badResponseErr) input := *models.NewRunInput(models.NewID(), output.Data(), output.Status()) output = adapter.Perform(input, store) require.NoError(t, output.Error()) assert.Equal(t, models.RunStatusPendingConfirmations, output.Status()) + + txManager.AssertExpectations(t) } func TestEthTxAdapter_Perform_NoDoubleSpendOnSendTransactionFail(t *testing.T) { diff --git a/core/internal/mocks/tx_manager_mocks.go b/core/internal/mocks/tx_manager_mocks.go deleted file mode 100644 index 110900c5ea8..00000000000 --- a/core/internal/mocks/tx_manager_mocks.go +++ /dev/null @@ -1,346 +0,0 @@ -// Code generated by MockGen. DO NOT EDIT. -// Source: chainlink/core/store (interfaces: TxManager) - -// Package mocks is a generated GoMock package. -package mocks - -import ( - store "chainlink/core/store" - assets "chainlink/core/store/assets" - models "chainlink/core/store/models" - go_ethereum "github.com/ethereum/go-ethereum" - accounts "github.com/ethereum/go-ethereum/accounts" - common "github.com/ethereum/go-ethereum/common" - gomock "github.com/golang/mock/gomock" - null_v3 "gopkg.in/guregu/null.v3" - big "math/big" - reflect "reflect" -) - -// MockTxManager is a mock of TxManager interface -type MockTxManager struct { - ctrl *gomock.Controller - recorder *MockTxManagerMockRecorder -} - -// MockTxManagerMockRecorder is the mock recorder for MockTxManager -type MockTxManagerMockRecorder struct { - mock *MockTxManager -} - -// NewMockTxManager creates a new mock instance -func NewMockTxManager(ctrl *gomock.Controller) *MockTxManager { - mock := &MockTxManager{ctrl: ctrl} - mock.recorder = &MockTxManagerMockRecorder{mock} - return mock -} - -// EXPECT returns an object that allows the caller to indicate expected use -func (m *MockTxManager) EXPECT() *MockTxManagerMockRecorder { - return m.recorder -} - -// BumpGasUntilSafe mocks base method -func (m *MockTxManager) BumpGasUntilSafe(arg0 common.Hash) (*models.TxReceipt, store.AttemptState, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "BumpGasUntilSafe", arg0) - ret0, _ := ret[0].(*models.TxReceipt) - ret1, _ := ret[1].(store.AttemptState) - ret2, _ := ret[2].(error) - return ret0, ret1, ret2 -} - -// BumpGasUntilSafe indicates an expected call of BumpGasUntilSafe -func (mr *MockTxManagerMockRecorder) BumpGasUntilSafe(arg0 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BumpGasUntilSafe", reflect.TypeOf((*MockTxManager)(nil).BumpGasUntilSafe), arg0) -} - -// CheckAttempt mocks base method -func (m *MockTxManager) CheckAttempt(arg0 *models.TxAttempt, arg1 uint64) (*models.TxReceipt, store.AttemptState, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "CheckAttempt", arg0, arg1) - ret0, _ := ret[0].(*models.TxReceipt) - ret1, _ := ret[1].(store.AttemptState) - ret2, _ := ret[2].(error) - return ret0, ret1, ret2 -} - -// CheckAttempt indicates an expected call of CheckAttempt -func (mr *MockTxManagerMockRecorder) CheckAttempt(arg0, arg1 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CheckAttempt", reflect.TypeOf((*MockTxManager)(nil).CheckAttempt), arg0, arg1) -} - -// Connect mocks base method -func (m *MockTxManager) Connect(arg0 *models.Head) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Connect", arg0) - ret0, _ := ret[0].(error) - return ret0 -} - -// Connect indicates an expected call of Connect -func (mr *MockTxManagerMockRecorder) Connect(arg0 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Connect", reflect.TypeOf((*MockTxManager)(nil).Connect), arg0) -} - -// Connected mocks base method -func (m *MockTxManager) Connected() bool { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Connected") - ret0, _ := ret[0].(bool) - return ret0 -} - -// Connected indicates an expected call of Connected -func (mr *MockTxManagerMockRecorder) Connected() *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Connected", reflect.TypeOf((*MockTxManager)(nil).Connected)) -} - -// ContractLINKBalance mocks base method -func (m *MockTxManager) ContractLINKBalance(arg0 models.WithdrawalRequest) (assets.Link, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ContractLINKBalance", arg0) - ret0, _ := ret[0].(assets.Link) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// ContractLINKBalance indicates an expected call of ContractLINKBalance -func (mr *MockTxManagerMockRecorder) ContractLINKBalance(arg0 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ContractLINKBalance", reflect.TypeOf((*MockTxManager)(nil).ContractLINKBalance), arg0) -} - -// CreateTx mocks base method -func (m *MockTxManager) CreateTx(arg0 common.Address, arg1 []byte) (*models.Tx, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "CreateTx", arg0, arg1) - ret0, _ := ret[0].(*models.Tx) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// CreateTx indicates an expected call of CreateTx -func (mr *MockTxManagerMockRecorder) CreateTx(arg0, arg1 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateTx", reflect.TypeOf((*MockTxManager)(nil).CreateTx), arg0, arg1) -} - -// CreateTxWithEth mocks base method -func (m *MockTxManager) CreateTxWithEth(arg0, arg1 common.Address, arg2 *assets.Eth) (*models.Tx, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "CreateTxWithEth", arg0, arg1, arg2) - ret0, _ := ret[0].(*models.Tx) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// CreateTxWithEth indicates an expected call of CreateTxWithEth -func (mr *MockTxManagerMockRecorder) CreateTxWithEth(arg0, arg1, arg2 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateTxWithEth", reflect.TypeOf((*MockTxManager)(nil).CreateTxWithEth), arg0, arg1, arg2) -} - -// CreateTxWithGas mocks base method -func (m *MockTxManager) CreateTxWithGas(arg0 null_v3.String, arg1 common.Address, arg2 []byte, arg3 *big.Int, arg4 uint64) (*models.Tx, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "CreateTxWithGas", arg0, arg1, arg2, arg3, arg4) - ret0, _ := ret[0].(*models.Tx) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// CreateTxWithGas indicates an expected call of CreateTxWithGas -func (mr *MockTxManagerMockRecorder) CreateTxWithGas(arg0, arg1, arg2, arg3, arg4 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateTxWithGas", reflect.TypeOf((*MockTxManager)(nil).CreateTxWithGas), arg0, arg1, arg2, arg3, arg4) -} - -// Disconnect mocks base method -func (m *MockTxManager) Disconnect() { - m.ctrl.T.Helper() - m.ctrl.Call(m, "Disconnect") -} - -// Disconnect indicates an expected call of Disconnect -func (mr *MockTxManagerMockRecorder) Disconnect() *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Disconnect", reflect.TypeOf((*MockTxManager)(nil).Disconnect)) -} - -// GetBlockByNumber mocks base method -func (m *MockTxManager) GetBlockByNumber(arg0 string) (models.BlockHeader, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetBlockByNumber", arg0) - ret0, _ := ret[0].(models.BlockHeader) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// GetBlockByNumber indicates an expected call of GetBlockByNumber -func (mr *MockTxManagerMockRecorder) GetBlockByNumber(arg0 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetBlockByNumber", reflect.TypeOf((*MockTxManager)(nil).GetBlockByNumber), arg0) -} - -// GetChainID mocks base method -func (m *MockTxManager) GetChainID() (*big.Int, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetChainID") - ret0, _ := ret[0].(*big.Int) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// GetChainID indicates an expected call of GetChainID -func (mr *MockTxManagerMockRecorder) GetChainID() *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetChainID", reflect.TypeOf((*MockTxManager)(nil).GetChainID)) -} - -// GetEthBalance mocks base method -func (m *MockTxManager) GetEthBalance(arg0 common.Address) (*assets.Eth, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetEthBalance", arg0) - ret0, _ := ret[0].(*assets.Eth) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// GetEthBalance indicates an expected call of GetEthBalance -func (mr *MockTxManagerMockRecorder) GetEthBalance(arg0 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetEthBalance", reflect.TypeOf((*MockTxManager)(nil).GetEthBalance), arg0) -} - -// GetLINKBalance mocks base method -func (m *MockTxManager) GetLINKBalance(arg0 common.Address) (*assets.Link, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetLINKBalance", arg0) - ret0, _ := ret[0].(*assets.Link) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// GetLINKBalance indicates an expected call of GetLINKBalance -func (mr *MockTxManagerMockRecorder) GetLINKBalance(arg0 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetLINKBalance", reflect.TypeOf((*MockTxManager)(nil).GetLINKBalance), arg0) -} - -// GetLogs mocks base method -func (m *MockTxManager) GetLogs(arg0 go_ethereum.FilterQuery) ([]models.Log, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetLogs", arg0) - ret0, _ := ret[0].([]models.Log) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// GetLogs indicates an expected call of GetLogs -func (mr *MockTxManagerMockRecorder) GetLogs(arg0 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetLogs", reflect.TypeOf((*MockTxManager)(nil).GetLogs), arg0) -} - -// GetTxReceipt mocks base method -func (m *MockTxManager) GetTxReceipt(arg0 common.Hash) (*models.TxReceipt, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetTxReceipt", arg0) - ret0, _ := ret[0].(*models.TxReceipt) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// GetTxReceipt indicates an expected call of GetTxReceipt -func (mr *MockTxManagerMockRecorder) GetTxReceipt(arg0 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetTxReceipt", reflect.TypeOf((*MockTxManager)(nil).GetTxReceipt), arg0) -} - -// NextActiveAccount mocks base method -func (m *MockTxManager) NextActiveAccount() *store.ManagedAccount { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "NextActiveAccount") - ret0, _ := ret[0].(*store.ManagedAccount) - return ret0 -} - -// NextActiveAccount indicates an expected call of NextActiveAccount -func (mr *MockTxManagerMockRecorder) NextActiveAccount() *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NextActiveAccount", reflect.TypeOf((*MockTxManager)(nil).NextActiveAccount)) -} - -// OnNewHead mocks base method -func (m *MockTxManager) OnNewHead(arg0 *models.Head) { - m.ctrl.T.Helper() - m.ctrl.Call(m, "OnNewHead", arg0) -} - -// OnNewHead indicates an expected call of OnNewHead -func (mr *MockTxManagerMockRecorder) OnNewHead(arg0 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "OnNewHead", reflect.TypeOf((*MockTxManager)(nil).OnNewHead), arg0) -} - -// Register mocks base method -func (m *MockTxManager) Register(arg0 []accounts.Account) { - m.ctrl.T.Helper() - m.ctrl.Call(m, "Register", arg0) -} - -// Register indicates an expected call of Register -func (mr *MockTxManagerMockRecorder) Register(arg0 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Register", reflect.TypeOf((*MockTxManager)(nil).Register), arg0) -} - -// SubscribeToLogs mocks base method -func (m *MockTxManager) SubscribeToLogs(arg0 chan<- models.Log, arg1 go_ethereum.FilterQuery) (models.EthSubscription, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "SubscribeToLogs", arg0, arg1) - ret0, _ := ret[0].(models.EthSubscription) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// SubscribeToLogs indicates an expected call of SubscribeToLogs -func (mr *MockTxManagerMockRecorder) SubscribeToLogs(arg0, arg1 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SubscribeToLogs", reflect.TypeOf((*MockTxManager)(nil).SubscribeToLogs), arg0, arg1) -} - -// SubscribeToNewHeads mocks base method -func (m *MockTxManager) SubscribeToNewHeads(arg0 chan<- models.BlockHeader) (models.EthSubscription, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "SubscribeToNewHeads", arg0) - ret0, _ := ret[0].(models.EthSubscription) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// SubscribeToNewHeads indicates an expected call of SubscribeToNewHeads -func (mr *MockTxManagerMockRecorder) SubscribeToNewHeads(arg0 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SubscribeToNewHeads", reflect.TypeOf((*MockTxManager)(nil).SubscribeToNewHeads), arg0) -} - -// WithdrawLINK mocks base method -func (m *MockTxManager) WithdrawLINK(arg0 models.WithdrawalRequest) (common.Hash, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "WithdrawLINK", arg0) - ret0, _ := ret[0].(common.Hash) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// WithdrawLINK indicates an expected call of WithdrawLINK -func (mr *MockTxManagerMockRecorder) WithdrawLINK(arg0 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WithdrawLINK", reflect.TypeOf((*MockTxManager)(nil).WithdrawLINK), arg0) -} diff --git a/core/services/head_tracker_test.go b/core/services/head_tracker_test.go index eaa1d3a6b4a..891596512cb 100644 --- a/core/services/head_tracker_test.go +++ b/core/services/head_tracker_test.go @@ -13,9 +13,9 @@ import ( "chainlink/core/store/models" "github.com/ethereum/go-ethereum/common/hexutil" - "github.com/golang/mock/gomock" "github.com/onsi/gomega" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" ) @@ -158,21 +158,17 @@ func TestHeadTracker_ReconnectOnError(t *testing.T) { store, cleanup := cltest.NewStore(t) defer cleanup() - ctrl := gomock.NewController(t) - defer ctrl.Finish() - - txmMock := mocks.NewMockTxManager(ctrl) - store.TxManager = txmMock + txManager := new(mocks.TxManager) + subscription := cltest.EmptyMockSubscription() + txManager.On("GetChainID").Maybe().Return(store.Config.ChainID(), nil) + txManager.On("SubscribeToNewHeads", mock.Anything).Return(subscription, nil) + txManager.On("SubscribeToNewHeads", mock.Anything).Return(nil, errors.New("cannot reconnect")) + txManager.On("SubscribeToNewHeads", mock.Anything).Return(subscription, nil) + store.TxManager = txManager checker := &cltest.MockHeadTrackable{} ht := services.NewHeadTracker(store, []strpkg.HeadTrackable{checker}, cltest.NeverSleeper{}) - subscription := cltest.EmptyMockSubscription() - txmMock.EXPECT().GetChainID().Return(store.Config.ChainID(), nil).AnyTimes() - txmMock.EXPECT().SubscribeToNewHeads(gomock.Any()).Return(subscription, nil) - txmMock.EXPECT().SubscribeToNewHeads(gomock.Any()).Return(nil, errors.New("cannot reconnect")) - txmMock.EXPECT().SubscribeToNewHeads(gomock.Any()).Return(subscription, nil) - // connect assert.Nil(t, ht.Start()) g.Eventually(func() int32 { return checker.ConnectedCount() }).Should(gomega.Equal(int32(1))) diff --git a/core/services/subscription_test.go b/core/services/subscription_test.go index 0b8a0a2b523..edc91da5512 100644 --- a/core/services/subscription_test.go +++ b/core/services/subscription_test.go @@ -8,7 +8,6 @@ import ( "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/common" - "github.com/golang/mock/gomock" "chainlink/core/internal/cltest" "chainlink/core/internal/mocks" @@ -321,10 +320,8 @@ func TestServices_NewInitiatorSubscription_ReplayFromBlock(t *testing.T) { store, cleanup := cltest.NewStore(t) defer cleanup() - ctrl := gomock.NewController(t) - defer ctrl.Finish() - txmMock := mocks.NewMockTxManager(ctrl) - store.TxManager = txmMock + txManager := new(mocks.TxManager) + store.TxManager = txManager cases := []struct { name string @@ -358,8 +355,8 @@ func TestServices_NewInitiatorSubscription_ReplayFromBlock(t *testing.T) { log := cltest.LogFromFixture(t, "testdata/subscription_logs.json") - txmMock.EXPECT().SubscribeToLogs(gomock.Any(), expectedQuery).Return(cltest.EmptyMockSubscription(), nil) - txmMock.EXPECT().GetLogs(expectedQuery).Return([]models.Log{log}, nil) + txManager.On("SubscribeToLogs", mock.Anything, expectedQuery).Return(cltest.EmptyMockSubscription(), nil) + txManager.On("GetLogs", expectedQuery).Return([]models.Log{log}, nil) executeJobChannel := make(chan struct{}) @@ -378,6 +375,8 @@ func TestServices_NewInitiatorSubscription_ReplayFromBlock(t *testing.T) { require.NoError(t, err) wg.Wait() + + txManager.AssertExpectations(t) }) } } diff --git a/core/store/store_test.go b/core/store/store_test.go index 4b9233b314c..97bb67b636c 100644 --- a/core/store/store_test.go +++ b/core/store/store_test.go @@ -1,6 +1,7 @@ package store_test import ( + "math/big" "path/filepath" "sort" "strings" @@ -10,8 +11,8 @@ import ( "chainlink/core/internal/mocks" "chainlink/core/utils" - "github.com/golang/mock/gomock" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" "github.com/tidwall/gjson" ) @@ -22,13 +23,13 @@ func TestStore_Start(t *testing.T) { store, cleanup := cltest.NewStore(t) defer cleanup() - ctrl := gomock.NewController(t) - defer ctrl.Finish() + txManager := new(mocks.TxManager) + txManager.On("Register", mock.Anything).Return(big.NewInt(3), nil) + store.TxManager = txManager - txmMock := mocks.NewMockTxManager(ctrl) - store.TxManager = txmMock - txmMock.EXPECT().Register(gomock.Any()) assert.NoError(t, store.Start()) + + txManager.AssertExpectations(t) } func TestStore_Close(t *testing.T) { diff --git a/core/store/tx_manager.go b/core/store/tx_manager.go index 38e1db154d6..8bcdf587772 100644 --- a/core/store/tx_manager.go +++ b/core/store/tx_manager.go @@ -63,7 +63,6 @@ type TxManager interface { GetChainID() (*big.Int, error) } -//go:generate mockgen -package=mocks -destination=../internal/mocks/tx_manager_mocks.go chainlink/core/store TxManager //go:generate mockery -name TxManager -output ../internal/mocks/ -case=underscore // EthTxManager contains fields for the Ethereum client, the KeyStore, diff --git a/core/web/withdrawals_controller_test.go b/core/web/withdrawals_controller_test.go index 80031192a66..8a53ab4f58e 100644 --- a/core/web/withdrawals_controller_test.go +++ b/core/web/withdrawals_controller_test.go @@ -13,8 +13,8 @@ import ( "chainlink/core/store/models" "github.com/ethereum/go-ethereum/common" - "github.com/golang/mock/gomock" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" ) // verifyLinkBalanceCheck(t) is used to check that the address checked in a @@ -36,24 +36,19 @@ func TestWithdrawalsController_CreateSuccess(t *testing.T) { defer cleanup() client := app.NewHTTPClient() - ctrl := gomock.NewController(t) - defer ctrl.Finish() - wr := models.WithdrawalRequest{ DestinationAddress: common.HexToAddress("0xDEADEAFDEADEAFDEADEAFDEADEAFDEAD00000000"), Amount: assets.NewLink(1000000000000000000), } subscription := cltest.EmptyMockSubscription() - - txmMock := mocks.NewMockTxManager(ctrl) - txmMock.EXPECT().SubscribeToNewHeads(gomock.Any()).Return(subscription, nil).AnyTimes() - txmMock.EXPECT().GetChainID().Return(big.NewInt(3), nil).AnyTimes() - txmMock.EXPECT().Register(gomock.Any()) - - txmMock.EXPECT().ContractLINKBalance(wr).Return(*wr.Amount, nil) - txmMock.EXPECT().WithdrawLINK(wr).Return(cltest.NewHash(), nil) - app.Store.TxManager = txmMock + txManager := new(mocks.TxManager) + txManager.On("SubscribeToNewHeads", mock.Anything).Maybe().Return(subscription, nil) + txManager.On("GetChainID").Maybe().Return(big.NewInt(3), nil) + txManager.On("Register", mock.Anything).Return(big.NewInt(3), nil) + txManager.On("ContractLINKBalance", wr).Return(*wr.Amount, nil) + txManager.On("WithdrawLINK", wr).Return(cltest.NewHash(), nil) + app.Store.TxManager = txManager oca := common.HexToAddress("0xDEADB3333333F") config.Set("ORACLE_CONTRACT_ADDRESS", &oca) @@ -67,6 +62,7 @@ func TestWithdrawalsController_CreateSuccess(t *testing.T) { defer cleanup() cltest.AssertServerResponse(t, resp, http.StatusOK) + txManager.AssertExpectations(t) } func TestWithdrawalsController_BalanceTooLow(t *testing.T) { From 95728e4b045ba4eb2f613596520509bb02d53e68 Mon Sep 17 00:00:00 2001 From: John Barker Date: Fri, 8 Nov 2019 17:19:29 -0700 Subject: [PATCH 171/199] Remove mockgen mock for EthClient, add mockery mock --- core/internal/mocks/eth_client.go | 241 ++++++++++++++++++++++++ core/internal/mocks/eth_client_mocks.go | 188 ------------------ core/store/eth_client.go | 2 +- core/store/tx_manager_test.go | 100 +++++----- 4 files changed, 289 insertions(+), 242 deletions(-) create mode 100644 core/internal/mocks/eth_client.go delete mode 100644 core/internal/mocks/eth_client_mocks.go diff --git a/core/internal/mocks/eth_client.go b/core/internal/mocks/eth_client.go new file mode 100644 index 00000000000..13f0a94cf4d --- /dev/null +++ b/core/internal/mocks/eth_client.go @@ -0,0 +1,241 @@ +// Code generated by mockery v1.0.0. DO NOT EDIT. + +package mocks + +import assets "chainlink/core/store/assets" +import big "math/big" +import common "github.com/ethereum/go-ethereum/common" +import ethereum "github.com/ethereum/go-ethereum" +import mock "github.com/stretchr/testify/mock" +import models "chainlink/core/store/models" + +// EthClient is an autogenerated mock type for the EthClient type +type EthClient struct { + mock.Mock +} + +// GetBlockByNumber provides a mock function with given fields: hex +func (_m *EthClient) GetBlockByNumber(hex string) (models.BlockHeader, error) { + ret := _m.Called(hex) + + var r0 models.BlockHeader + if rf, ok := ret.Get(0).(func(string) models.BlockHeader); ok { + r0 = rf(hex) + } else { + r0 = ret.Get(0).(models.BlockHeader) + } + + var r1 error + if rf, ok := ret.Get(1).(func(string) error); ok { + r1 = rf(hex) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetChainID provides a mock function with given fields: +func (_m *EthClient) GetChainID() (*big.Int, error) { + ret := _m.Called() + + var r0 *big.Int + if rf, ok := ret.Get(0).(func() *big.Int); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*big.Int) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func() error); ok { + r1 = rf() + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetERC20Balance provides a mock function with given fields: address, contractAddress +func (_m *EthClient) GetERC20Balance(address common.Address, contractAddress common.Address) (*big.Int, error) { + ret := _m.Called(address, contractAddress) + + var r0 *big.Int + if rf, ok := ret.Get(0).(func(common.Address, common.Address) *big.Int); ok { + r0 = rf(address, contractAddress) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*big.Int) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(common.Address, common.Address) error); ok { + r1 = rf(address, contractAddress) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetEthBalance provides a mock function with given fields: address +func (_m *EthClient) GetEthBalance(address common.Address) (*assets.Eth, error) { + ret := _m.Called(address) + + var r0 *assets.Eth + if rf, ok := ret.Get(0).(func(common.Address) *assets.Eth); ok { + r0 = rf(address) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*assets.Eth) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(common.Address) error); ok { + r1 = rf(address) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetLogs provides a mock function with given fields: q +func (_m *EthClient) GetLogs(q ethereum.FilterQuery) ([]models.Log, error) { + ret := _m.Called(q) + + var r0 []models.Log + if rf, ok := ret.Get(0).(func(ethereum.FilterQuery) []models.Log); ok { + r0 = rf(q) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]models.Log) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(ethereum.FilterQuery) error); ok { + r1 = rf(q) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetNonce provides a mock function with given fields: address +func (_m *EthClient) GetNonce(address common.Address) (uint64, error) { + ret := _m.Called(address) + + var r0 uint64 + if rf, ok := ret.Get(0).(func(common.Address) uint64); ok { + r0 = rf(address) + } else { + r0 = ret.Get(0).(uint64) + } + + var r1 error + if rf, ok := ret.Get(1).(func(common.Address) error); ok { + r1 = rf(address) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetTxReceipt provides a mock function with given fields: hash +func (_m *EthClient) GetTxReceipt(hash common.Hash) (*models.TxReceipt, error) { + ret := _m.Called(hash) + + var r0 *models.TxReceipt + if rf, ok := ret.Get(0).(func(common.Hash) *models.TxReceipt); ok { + r0 = rf(hash) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*models.TxReceipt) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(common.Hash) error); ok { + r1 = rf(hash) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// SendRawTx provides a mock function with given fields: hex +func (_m *EthClient) SendRawTx(hex string) (common.Hash, error) { + ret := _m.Called(hex) + + var r0 common.Hash + if rf, ok := ret.Get(0).(func(string) common.Hash); ok { + r0 = rf(hex) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(common.Hash) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(string) error); ok { + r1 = rf(hex) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// SubscribeToLogs provides a mock function with given fields: channel, q +func (_m *EthClient) SubscribeToLogs(channel chan<- models.Log, q ethereum.FilterQuery) (models.EthSubscription, error) { + ret := _m.Called(channel, q) + + var r0 models.EthSubscription + if rf, ok := ret.Get(0).(func(chan<- models.Log, ethereum.FilterQuery) models.EthSubscription); ok { + r0 = rf(channel, q) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(models.EthSubscription) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(chan<- models.Log, ethereum.FilterQuery) error); ok { + r1 = rf(channel, q) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// SubscribeToNewHeads provides a mock function with given fields: channel +func (_m *EthClient) SubscribeToNewHeads(channel chan<- models.BlockHeader) (models.EthSubscription, error) { + ret := _m.Called(channel) + + var r0 models.EthSubscription + if rf, ok := ret.Get(0).(func(chan<- models.BlockHeader) models.EthSubscription); ok { + r0 = rf(channel) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(models.EthSubscription) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(chan<- models.BlockHeader) error); ok { + r1 = rf(channel) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} diff --git a/core/internal/mocks/eth_client_mocks.go b/core/internal/mocks/eth_client_mocks.go deleted file mode 100644 index e07996aa9b8..00000000000 --- a/core/internal/mocks/eth_client_mocks.go +++ /dev/null @@ -1,188 +0,0 @@ -// Code generated by MockGen. DO NOT EDIT. -// Source: chainlink/core/store (interfaces: EthClient) - -// Package mocks is a generated GoMock package. -package mocks - -import ( - assets "chainlink/core/store/assets" - models "chainlink/core/store/models" - go_ethereum "github.com/ethereum/go-ethereum" - common "github.com/ethereum/go-ethereum/common" - gomock "github.com/golang/mock/gomock" - big "math/big" - reflect "reflect" -) - -// MockEthClient is a mock of EthClient interface -type MockEthClient struct { - ctrl *gomock.Controller - recorder *MockEthClientMockRecorder -} - -// MockEthClientMockRecorder is the mock recorder for MockEthClient -type MockEthClientMockRecorder struct { - mock *MockEthClient -} - -// NewMockEthClient creates a new mock instance -func NewMockEthClient(ctrl *gomock.Controller) *MockEthClient { - mock := &MockEthClient{ctrl: ctrl} - mock.recorder = &MockEthClientMockRecorder{mock} - return mock -} - -// EXPECT returns an object that allows the caller to indicate expected use -func (m *MockEthClient) EXPECT() *MockEthClientMockRecorder { - return m.recorder -} - -// GetBlockByNumber mocks base method -func (m *MockEthClient) GetBlockByNumber(arg0 string) (models.BlockHeader, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetBlockByNumber", arg0) - ret0, _ := ret[0].(models.BlockHeader) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// GetBlockByNumber indicates an expected call of GetBlockByNumber -func (mr *MockEthClientMockRecorder) GetBlockByNumber(arg0 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetBlockByNumber", reflect.TypeOf((*MockEthClient)(nil).GetBlockByNumber), arg0) -} - -// GetChainID mocks base method -func (m *MockEthClient) GetChainID() (*big.Int, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetChainID") - ret0, _ := ret[0].(*big.Int) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// GetChainID indicates an expected call of GetChainID -func (mr *MockEthClientMockRecorder) GetChainID() *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetChainID", reflect.TypeOf((*MockEthClient)(nil).GetChainID)) -} - -// GetERC20Balance mocks base method -func (m *MockEthClient) GetERC20Balance(arg0, arg1 common.Address) (*big.Int, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetERC20Balance", arg0, arg1) - ret0, _ := ret[0].(*big.Int) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// GetERC20Balance indicates an expected call of GetERC20Balance -func (mr *MockEthClientMockRecorder) GetERC20Balance(arg0, arg1 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetERC20Balance", reflect.TypeOf((*MockEthClient)(nil).GetERC20Balance), arg0, arg1) -} - -// GetEthBalance mocks base method -func (m *MockEthClient) GetEthBalance(arg0 common.Address) (*assets.Eth, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetEthBalance", arg0) - ret0, _ := ret[0].(*assets.Eth) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// GetEthBalance indicates an expected call of GetEthBalance -func (mr *MockEthClientMockRecorder) GetEthBalance(arg0 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetEthBalance", reflect.TypeOf((*MockEthClient)(nil).GetEthBalance), arg0) -} - -// GetLogs mocks base method -func (m *MockEthClient) GetLogs(arg0 go_ethereum.FilterQuery) ([]models.Log, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetLogs", arg0) - ret0, _ := ret[0].([]models.Log) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// GetLogs indicates an expected call of GetLogs -func (mr *MockEthClientMockRecorder) GetLogs(arg0 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetLogs", reflect.TypeOf((*MockEthClient)(nil).GetLogs), arg0) -} - -// GetNonce mocks base method -func (m *MockEthClient) GetNonce(arg0 common.Address) (uint64, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetNonce", arg0) - ret0, _ := ret[0].(uint64) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// GetNonce indicates an expected call of GetNonce -func (mr *MockEthClientMockRecorder) GetNonce(arg0 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetNonce", reflect.TypeOf((*MockEthClient)(nil).GetNonce), arg0) -} - -// GetTxReceipt mocks base method -func (m *MockEthClient) GetTxReceipt(arg0 common.Hash) (*models.TxReceipt, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetTxReceipt", arg0) - ret0, _ := ret[0].(*models.TxReceipt) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// GetTxReceipt indicates an expected call of GetTxReceipt -func (mr *MockEthClientMockRecorder) GetTxReceipt(arg0 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetTxReceipt", reflect.TypeOf((*MockEthClient)(nil).GetTxReceipt), arg0) -} - -// SendRawTx mocks base method -func (m *MockEthClient) SendRawTx(arg0 string) (common.Hash, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "SendRawTx", arg0) - ret0, _ := ret[0].(common.Hash) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// SendRawTx indicates an expected call of SendRawTx -func (mr *MockEthClientMockRecorder) SendRawTx(arg0 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendRawTx", reflect.TypeOf((*MockEthClient)(nil).SendRawTx), arg0) -} - -// SubscribeToLogs mocks base method -func (m *MockEthClient) SubscribeToLogs(arg0 chan<- models.Log, arg1 go_ethereum.FilterQuery) (models.EthSubscription, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "SubscribeToLogs", arg0, arg1) - ret0, _ := ret[0].(models.EthSubscription) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// SubscribeToLogs indicates an expected call of SubscribeToLogs -func (mr *MockEthClientMockRecorder) SubscribeToLogs(arg0, arg1 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SubscribeToLogs", reflect.TypeOf((*MockEthClient)(nil).SubscribeToLogs), arg0, arg1) -} - -// SubscribeToNewHeads mocks base method -func (m *MockEthClient) SubscribeToNewHeads(arg0 chan<- models.BlockHeader) (models.EthSubscription, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "SubscribeToNewHeads", arg0) - ret0, _ := ret[0].(models.EthSubscription) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// SubscribeToNewHeads indicates an expected call of SubscribeToNewHeads -func (mr *MockEthClientMockRecorder) SubscribeToNewHeads(arg0 interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SubscribeToNewHeads", reflect.TypeOf((*MockEthClient)(nil).SubscribeToNewHeads), arg0) -} diff --git a/core/store/eth_client.go b/core/store/eth_client.go index 1c6891e7f9d..0621c22f2cf 100644 --- a/core/store/eth_client.go +++ b/core/store/eth_client.go @@ -13,7 +13,7 @@ import ( "github.com/ethereum/go-ethereum/common/hexutil" ) -//go:generate mockgen -package=mocks -destination=../internal/mocks/eth_client_mocks.go chainlink/core/store EthClient +//go:generate mockery -name EthClient -output ../internal/mocks/ -case=underscore // EthClient is the interface supplied by EthCallerSubscriber type EthClient interface { diff --git a/core/store/tx_manager_test.go b/core/store/tx_manager_test.go index 6a03f128ca7..1e9c4a1f193 100644 --- a/core/store/tx_manager_test.go +++ b/core/store/tx_manager_test.go @@ -18,8 +18,8 @@ import ( "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" - "github.com/golang/mock/gomock" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" "gopkg.in/guregu/null.v3" ) @@ -30,17 +30,14 @@ func TestTxManager_CreateTx_Success(t *testing.T) { store, cleanup := cltest.NewStore(t) defer cleanup() - ctrl := gomock.NewController(t) - defer ctrl.Finish() - - eth := mocks.NewMockEthClient(ctrl) + ethClient := new(mocks.EthClient) config := cltest.NewTestConfig(t) keyStore := strpkg.NewKeyStore(config.KeysDir()) account, err := keyStore.NewAccount(cltest.Password) require.NoError(t, err) require.NoError(t, keyStore.Unlock(cltest.Password)) - manager := strpkg.NewEthTxManager(eth, config, keyStore, store.ORM) + manager := strpkg.NewEthTxManager(ethClient, config, keyStore, store.ORM) from := account.Address to := cltest.NewAddress() @@ -50,12 +47,12 @@ func TestTxManager_CreateTx_Success(t *testing.T) { manager.Register(keyStore.Accounts()) require.NoError(t, err) - eth.EXPECT().GetNonce(from).Return(nonce, nil) + ethClient.On("GetNonce", from).Return(nonce, nil) err = manager.Connect(cltest.Head(nonce)) require.NoError(t, err) - eth.EXPECT().SendRawTx(gomock.Any()) + ethClient.On("SendRawTx", mock.Anything).Return(cltest.NewHash(), nil) tx, err := manager.CreateTx(to, data) require.NoError(t, err) @@ -68,6 +65,8 @@ func TestTxManager_CreateTx_Success(t *testing.T) { assert.Equal(t, from, ntx.From) assert.Equal(t, nonce, ntx.Nonce) assert.Len(t, ntx.Attempts, 1) + + ethClient.AssertExpectations(t) } func TestTxManager_CreateTx_RoundRobinSuccess(t *testing.T) { @@ -76,10 +75,7 @@ func TestTxManager_CreateTx_RoundRobinSuccess(t *testing.T) { store, cleanup := cltest.NewStore(t) defer cleanup() - ctrl := gomock.NewController(t) - defer ctrl.Finish() - - eth := mocks.NewMockEthClient(ctrl) + ethClient := new(mocks.EthClient) config := cltest.NewTestConfig(t) keyStore := strpkg.NewKeyStore(config.KeysDir()) @@ -91,7 +87,7 @@ func TestTxManager_CreateTx_RoundRobinSuccess(t *testing.T) { require.NoError(t, err) require.NoError(t, keyStore.Unlock(cltest.Password)) - manager := strpkg.NewEthTxManager(eth, config, keyStore, store.ORM) + manager := strpkg.NewEthTxManager(ethClient, config, keyStore, store.ORM) to := cltest.NewAddress() data, err := hex.DecodeString("0000abcdef") @@ -102,12 +98,12 @@ func TestTxManager_CreateTx_RoundRobinSuccess(t *testing.T) { manager.Register(keyStore.Accounts()) require.NoError(t, err) - eth.EXPECT().GetNonce(gomock.Any()).Return(nonce, nil).Times(2) + ethClient.On("GetNonce", mock.Anything).Return(nonce, nil).Times(2) err = manager.Connect(cltest.Head(sentAt)) require.NoError(t, err) - eth.EXPECT().SendRawTx(gomock.Any()).Return(cltest.NewHash(), nil) + ethClient.On("SendRawTx", mock.Anything).Return(cltest.NewHash(), nil) createdTx1, err := manager.CreateTx(to, data) require.NoError(t, err) @@ -117,8 +113,8 @@ func TestTxManager_CreateTx_RoundRobinSuccess(t *testing.T) { assert.Len(t, ntx.Attempts, 1) manager.OnNewHead(cltest.Head(bumpAt)) - eth.EXPECT().GetTxReceipt(createdTx1.Attempts[0].Hash).Return(&models.TxReceipt{}, nil) - eth.EXPECT().SendRawTx(gomock.Any()).Return(cltest.NewHash(), nil) + ethClient.On("GetTxReceipt", createdTx1.Attempts[0].Hash).Return(&models.TxReceipt{}, nil) + ethClient.On("SendRawTx", mock.Anything).Return(cltest.NewHash(), nil) // bump gas receipt, state, err := manager.BumpGasUntilSafe(createdTx1.Attempts[0].Hash) @@ -134,11 +130,13 @@ func TestTxManager_CreateTx_RoundRobinSuccess(t *testing.T) { assert.Equal(t, createdTx1.From, ntx.From) // ensure second tx uses the first account again - eth.EXPECT().SendRawTx(gomock.Any()).Return(cltest.NewHash(), nil) + ethClient.On("SendRawTx", mock.Anything).Return(cltest.NewHash(), nil) createdTx2, err := manager.CreateTx(to, data) assert.NoError(t, err) require.NotEqual(t, createdTx1.From.Hex(), createdTx2.From.Hex(), "should come from a different account") + + ethClient.AssertExpectations(t) } func TestTxManager_CreateTx_BreakTxAttemptLimit(t *testing.T) { @@ -147,10 +145,7 @@ func TestTxManager_CreateTx_BreakTxAttemptLimit(t *testing.T) { store, cleanup := cltest.NewStore(t) defer cleanup() - ctrl := gomock.NewController(t) - defer ctrl.Finish() - - eth := mocks.NewMockEthClient(ctrl) + ethClient := new(mocks.EthClient) config := cltest.NewTestConfig(t) config.Set("CHAINLINK_TX_ATTEMPT_LIMIT", 1) @@ -158,7 +153,7 @@ func TestTxManager_CreateTx_BreakTxAttemptLimit(t *testing.T) { account, err := keyStore.NewAccount(cltest.Password) require.NoError(t, err) require.NoError(t, keyStore.Unlock(cltest.Password)) - manager := strpkg.NewEthTxManager(eth, config, keyStore, store.ORM) + manager := strpkg.NewEthTxManager(ethClient, config, keyStore, store.ORM) from := account.Address to := cltest.NewAddress() @@ -171,12 +166,12 @@ func TestTxManager_CreateTx_BreakTxAttemptLimit(t *testing.T) { manager.Register(keyStore.Accounts()) require.NoError(t, err) - eth.EXPECT().GetNonce(from).Return(nonce, nil) + ethClient.On("GetNonce", from).Return(nonce, nil) err = manager.Connect(cltest.Head(sentAt)) require.NoError(t, err) - eth.EXPECT().SendRawTx(gomock.Any()) + ethClient.On("SendRawTx", mock.Anything).Return(cltest.NewHash(), nil) tx, err := manager.CreateTx(to, data) require.NoError(t, err) @@ -191,8 +186,8 @@ func TestTxManager_CreateTx_BreakTxAttemptLimit(t *testing.T) { assert.Len(t, ntx.Attempts, 1) manager.OnNewHead(cltest.Head(bumpAt)) - eth.EXPECT().GetTxReceipt(gomock.Any()).Return(&models.TxReceipt{}, nil) - eth.EXPECT().SendRawTx(gomock.Any()).Return(tx.Attempts[0].Hash, nil) + ethClient.On("GetTxReceipt", mock.Anything).Return(&models.TxReceipt{}, nil) + ethClient.On("SendRawTx", mock.Anything).Return(tx.Attempts[0].Hash, nil) receipt, state, err := manager.BumpGasUntilSafe(tx.Attempts[0].Hash) require.NoError(t, err) @@ -200,13 +195,15 @@ func TestTxManager_CreateTx_BreakTxAttemptLimit(t *testing.T) { assert.Equal(t, strpkg.Unconfirmed, state) manager.OnNewHead(cltest.Head(bumpAgainAt)) - eth.EXPECT().GetTxReceipt(gomock.Any()).Return(&models.TxReceipt{}, nil) - eth.EXPECT().GetTxReceipt(gomock.Any()).Return(&models.TxReceipt{}, nil) + ethClient.On("GetTxReceipt", mock.Anything).Return(&models.TxReceipt{}, nil) + ethClient.On("GetTxReceipt", mock.Anything).Return(&models.TxReceipt{}, nil) receipt, state, err = manager.BumpGasUntilSafe(tx.Attempts[0].Hash) require.NoError(t, err) assert.Nil(t, receipt) assert.Equal(t, strpkg.Unconfirmed, state) + + ethClient.AssertExpectations(t) } func TestTxManager_CreateTx_AttemptErrorDoesNotIncrementNonce(t *testing.T) { @@ -868,11 +865,9 @@ func TestTxManager_NextActiveAccount_RoundRobin(t *testing.T) { func TestTxManager_ReloadNonce(t *testing.T) { t.Parallel() - ctrl := gomock.NewController(t) - defer ctrl.Finish() - eth := mocks.NewMockEthClient(ctrl) + ethClient := new(mocks.EthClient) txm := store.NewEthTxManager( - eth, + ethClient, orm.NewConfig(), nil, nil, @@ -883,13 +878,15 @@ func TestTxManager_ReloadNonce(t *testing.T) { ma := store.NewManagedAccount(account, 0) nonce := uint64(234) - eth.EXPECT().GetNonce(from).Return(nonce, nil) + ethClient.On("GetNonce", from).Return(nonce, nil) err := ma.ReloadNonce(txm) assert.NoError(t, err) assert.Equal(t, account.Address, ma.Address) assert.Equal(t, nonce, ma.Nonce()) + + ethClient.AssertExpectations(t) } func TestTxManager_WithdrawLink_HappyPath(t *testing.T) { @@ -999,17 +996,14 @@ func TestTxManager_LogsETHAndLINKBalancesAfterSuccessfulTx(t *testing.T) { store, cleanup := cltest.NewStore(t) defer cleanup() - ctrl := gomock.NewController(t) - defer ctrl.Finish() - - eth := mocks.NewMockEthClient(ctrl) + ethClient := new(mocks.EthClient) config := cltest.NewTestConfig(t) keyStore := strpkg.NewKeyStore(config.KeysDir()) account, err := keyStore.NewAccount(cltest.Password) require.NoError(t, err) require.NoError(t, keyStore.Unlock(cltest.Password)) - manager := strpkg.NewEthTxManager(eth, config, keyStore, store.ORM) + manager := strpkg.NewEthTxManager(ethClient, config, keyStore, store.ORM) from := account.Address to := cltest.NewAddress() @@ -1021,12 +1015,12 @@ func TestTxManager_LogsETHAndLINKBalancesAfterSuccessfulTx(t *testing.T) { manager.Register(keyStore.Accounts()) require.NoError(t, err) - eth.EXPECT().GetNonce(from).Return(nonce, nil) + ethClient.On("GetNonce", from).Return(nonce, nil) err = manager.Connect(cltest.Head(sentAt)) require.NoError(t, err) - eth.EXPECT().SendRawTx(gomock.Any()) + ethClient.On("SendRawTx", mock.Anything).Return(cltest.NewHash(), nil) tx, err := manager.CreateTx(to, data) require.NoError(t, err) @@ -1045,14 +1039,16 @@ func TestTxManager_LogsETHAndLINKBalancesAfterSuccessfulTx(t *testing.T) { BlockNumber: cltest.Int(sentAt), } manager.OnNewHead(cltest.Head(confirmedAt)) - eth.EXPECT().GetTxReceipt(tx.Attempts[0].Hash).Return(&confirmedReceipt, nil) - eth.EXPECT().GetERC20Balance(from, gomock.Any()) - eth.EXPECT().GetEthBalance(from) + ethClient.On("GetTxReceipt", tx.Attempts[0].Hash).Return(&confirmedReceipt, nil) + ethClient.On("GetERC20Balance", from, mock.Anything).Return(nil, nil) + ethClient.On("GetEthBalance", from).Return(nil, nil) receipt, state, err := manager.BumpGasUntilSafe(tx.Attempts[0].Hash) require.NoError(t, err) assert.NotNil(t, receipt) assert.Equal(t, strpkg.Safe, state) + + ethClient.AssertExpectations(t) } func TestTxManager_CreateTxWithGas(t *testing.T) { @@ -1191,10 +1187,7 @@ func TestTxManager_RebroadcastUnconfirmedTxsOnReconnect(t *testing.T) { store, cleanup := cltest.NewStore(t) defer cleanup() - ctrl := gomock.NewController(t) - defer ctrl.Finish() - - eth := mocks.NewMockEthClient(ctrl) + ethClient := new(mocks.EthClient) config := cltest.NewTestConfig(t) config.Set("CHAINLINK_TX_ATTEMPT_LIMIT", 1) @@ -1202,7 +1195,7 @@ func TestTxManager_RebroadcastUnconfirmedTxsOnReconnect(t *testing.T) { _, err := keyStore.NewAccount(cltest.Password) require.NoError(t, err) require.NoError(t, keyStore.Unlock(cltest.Password)) - manager := strpkg.NewEthTxManager(eth, config, keyStore, store.ORM) + manager := strpkg.NewEthTxManager(ethClient, config, keyStore, store.ORM) to := cltest.NewAddress() data, err := hex.DecodeString("0000abcdef") @@ -1211,21 +1204,22 @@ func TestTxManager_RebroadcastUnconfirmedTxsOnReconnect(t *testing.T) { manager.Register(keyStore.Accounts()) require.NoError(t, err) - eth.EXPECT().GetNonce(gomock.Any()) - eth.EXPECT().GetNonce(gomock.Any()) + ethClient.On("GetNonce", mock.Anything).Times(2).Return(uint64(0), nil) err = manager.Connect(cltest.Head(sentAt)) require.NoError(t, err) hash := cltest.NewHash() - eth.EXPECT().SendRawTx(gomock.Any()).Return(hash, nil) + ethClient.On("SendRawTx", mock.Anything).Return(hash, nil) _, err = manager.CreateTx(to, data) require.NoError(t, err) manager.Disconnect() - eth.EXPECT().SendRawTx(gomock.Any()).Return(hash, nil) + ethClient.On("SendRawTx", mock.Anything).Return(hash, nil) err = manager.Connect(cltest.Head(sentAt)) require.NoError(t, err) + + ethClient.AssertExpectations(t) } From ef849e428723d120bd6e1abac866f128fa2073fd Mon Sep 17 00:00:00 2001 From: John Barker Date: Fri, 8 Nov 2019 17:37:34 -0700 Subject: [PATCH 172/199] Remove mockgen mock for JobSubscriber, add mockery mock and go:generate --- core/internal/mocks/job_subscriber.go | 79 +++++++++++++ core/services/application_test.go | 14 +-- core/services/job_subscriber.go | 2 + core/services/mock_services/job_subscriber.go | 104 ------------------ 4 files changed, 88 insertions(+), 111 deletions(-) create mode 100644 core/internal/mocks/job_subscriber.go delete mode 100644 core/services/mock_services/job_subscriber.go diff --git a/core/internal/mocks/job_subscriber.go b/core/internal/mocks/job_subscriber.go new file mode 100644 index 00000000000..4b3819b77f1 --- /dev/null +++ b/core/internal/mocks/job_subscriber.go @@ -0,0 +1,79 @@ +// Code generated by mockery v1.0.0. DO NOT EDIT. + +package mocks + +import mock "github.com/stretchr/testify/mock" +import models "chainlink/core/store/models" + +// JobSubscriber is an autogenerated mock type for the JobSubscriber type +type JobSubscriber struct { + mock.Mock +} + +// AddJob provides a mock function with given fields: job, bn +func (_m *JobSubscriber) AddJob(job models.JobSpec, bn *models.Head) error { + ret := _m.Called(job, bn) + + var r0 error + if rf, ok := ret.Get(0).(func(models.JobSpec, *models.Head) error); ok { + r0 = rf(job, bn) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// Connect provides a mock function with given fields: _a0 +func (_m *JobSubscriber) Connect(_a0 *models.Head) error { + ret := _m.Called(_a0) + + var r0 error + if rf, ok := ret.Get(0).(func(*models.Head) error); ok { + r0 = rf(_a0) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// Disconnect provides a mock function with given fields: +func (_m *JobSubscriber) Disconnect() { + _m.Called() +} + +// Jobs provides a mock function with given fields: +func (_m *JobSubscriber) Jobs() []models.JobSpec { + ret := _m.Called() + + var r0 []models.JobSpec + if rf, ok := ret.Get(0).(func() []models.JobSpec); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]models.JobSpec) + } + } + + return r0 +} + +// OnNewHead provides a mock function with given fields: _a0 +func (_m *JobSubscriber) OnNewHead(_a0 *models.Head) { + _m.Called(_a0) +} + +// RemoveJob provides a mock function with given fields: ID +func (_m *JobSubscriber) RemoveJob(ID *models.ID) error { + ret := _m.Called(ID) + + var r0 error + if rf, ok := ret.Get(0).(func(*models.ID) error); ok { + r0 = rf(ID) + } else { + r0 = ret.Error(0) + } + + return r0 +} diff --git a/core/services/application_test.go b/core/services/application_test.go index 197cf0fb97d..210ff2d04de 100644 --- a/core/services/application_test.go +++ b/core/services/application_test.go @@ -7,12 +7,12 @@ import ( "testing" "chainlink/core/internal/cltest" - "chainlink/core/services/mock_services" + "chainlink/core/internal/mocks" "chainlink/core/store/models" "chainlink/core/utils" - "github.com/golang/mock/gomock" "github.com/onsi/gomega" + "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" "github.com/tevino/abool" ) @@ -43,13 +43,13 @@ func TestChainlinkApplication_AddJob(t *testing.T) { defer cleanup() require.NoError(t, app.Start()) - ctrl := gomock.NewController(t) - defer ctrl.Finish() + jobSubscriber := new(mocks.JobSubscriber) + jobSubscriber.On("AddJob", mock.Anything, (*models.Head)(nil)).Return(nil, nil) + app.ChainlinkApplication.JobSubscriber = jobSubscriber - jobSubscriberMock := mock_services.NewMockJobSubscriber(ctrl) - app.ChainlinkApplication.JobSubscriber = jobSubscriberMock - jobSubscriberMock.EXPECT().AddJob(gomock.Any(), nil) // nil to represent "latest" block app.AddJob(cltest.NewJob()) + + jobSubscriber.AssertExpectations(t) } func TestChainlinkApplication_resumesPendingConnection_Happy(t *testing.T) { diff --git a/core/services/job_subscriber.go b/core/services/job_subscriber.go index b9cf2375114..cb083ed5488 100644 --- a/core/services/job_subscriber.go +++ b/core/services/job_subscriber.go @@ -11,6 +11,8 @@ import ( "go.uber.org/multierr" ) +//go:generate mockery -name JobSubscriber -output ../internal/mocks/ -case=underscore + // JobSubscriber listens for push notifications of event logs from the ethereum // node's websocket for specific jobs by subscribing to ethLogs. type JobSubscriber interface { diff --git a/core/services/mock_services/job_subscriber.go b/core/services/mock_services/job_subscriber.go deleted file mode 100644 index 5a2617bf580..00000000000 --- a/core/services/mock_services/job_subscriber.go +++ /dev/null @@ -1,104 +0,0 @@ -// Code generated by MockGen. DO NOT EDIT. -// Source: chainlink/core/services (interfaces: JobSubscriber) - -// Package mock_services is a generated GoMock package. -package mock_services - -import ( - reflect "reflect" - - models "chainlink/core/store/models" - - gomock "github.com/golang/mock/gomock" -) - -// MockJobSubscriber is a mock of JobSubscriber interface -type MockJobSubscriber struct { - ctrl *gomock.Controller - recorder *MockJobSubscriberMockRecorder -} - -// MockJobSubscriberMockRecorder is the mock recorder for MockJobSubscriber -type MockJobSubscriberMockRecorder struct { - mock *MockJobSubscriber -} - -// NewMockJobSubscriber creates a new mock instance -func NewMockJobSubscriber(ctrl *gomock.Controller) *MockJobSubscriber { - mock := &MockJobSubscriber{ctrl: ctrl} - mock.recorder = &MockJobSubscriberMockRecorder{mock} - return mock -} - -// EXPECT returns an object that allows the caller to indicate expected use -func (m *MockJobSubscriber) EXPECT() *MockJobSubscriberMockRecorder { - return m.recorder -} - -// AddJob mocks base method -func (m *MockJobSubscriber) AddJob(arg0 models.JobSpec, arg1 *models.Head) error { - ret := m.ctrl.Call(m, "AddJob", arg0, arg1) - ret0, _ := ret[0].(error) - return ret0 -} - -// AddJob indicates an expected call of AddJob -func (mr *MockJobSubscriberMockRecorder) AddJob(arg0, arg1 interface{}) *gomock.Call { - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddJob", reflect.TypeOf((*MockJobSubscriber)(nil).AddJob), arg0, arg1) -} - -// Connect mocks base method -func (m *MockJobSubscriber) Connect(arg0 *models.Head) error { - ret := m.ctrl.Call(m, "Connect", arg0) - ret0, _ := ret[0].(error) - return ret0 -} - -// Connect indicates an expected call of Connect -func (mr *MockJobSubscriberMockRecorder) Connect(arg0 interface{}) *gomock.Call { - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Connect", reflect.TypeOf((*MockJobSubscriber)(nil).Connect), arg0) -} - -// Disconnect mocks base method -func (m *MockJobSubscriber) Disconnect() { - m.ctrl.Call(m, "Disconnect") -} - -// Disconnect indicates an expected call of Disconnect -func (mr *MockJobSubscriberMockRecorder) Disconnect() *gomock.Call { - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Disconnect", reflect.TypeOf((*MockJobSubscriber)(nil).Disconnect)) -} - -// Jobs mocks base method -func (m *MockJobSubscriber) Jobs() []models.JobSpec { - ret := m.ctrl.Call(m, "Jobs") - ret0, _ := ret[0].([]models.JobSpec) - return ret0 -} - -// Jobs indicates an expected call of Jobs -func (mr *MockJobSubscriberMockRecorder) Jobs() *gomock.Call { - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Jobs", reflect.TypeOf((*MockJobSubscriber)(nil).Jobs)) -} - -// OnNewHead mocks base method -func (m *MockJobSubscriber) OnNewHead(arg0 *models.Head) { - m.ctrl.Call(m, "OnNewHead", arg0) -} - -// OnNewHead indicates an expected call of OnNewHead -func (mr *MockJobSubscriberMockRecorder) OnNewHead(arg0 interface{}) *gomock.Call { - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "OnNewHead", reflect.TypeOf((*MockJobSubscriber)(nil).OnNewHead), arg0) -} - -// RemoveJob mocks base method -func (m *MockJobSubscriber) RemoveJob(arg0 *models.ID) error { - ret := m.ctrl.Call(m, "RemoveJob", arg0) - ret0, _ := ret[0].(error) - return ret0 -} - -// RemoveJob indicates an expected call of RemoveJob -func (mr *MockJobSubscriberMockRecorder) RemoveJob(arg0 interface{}) *gomock.Call { - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RemoveJob", reflect.TypeOf((*MockJobSubscriber)(nil).RemoveJob), arg0) -} From 716a425b5062dc430afaf0a0c15c57eaea7c9fef Mon Sep 17 00:00:00 2001 From: John Barker Date: Fri, 8 Nov 2019 17:38:43 -0700 Subject: [PATCH 173/199] Remove mock dependencies --- go.mod | 4 +--- go.sum | 18 +----------------- 2 files changed, 2 insertions(+), 20 deletions(-) diff --git a/go.mod b/go.mod index 33b5218ad9c..66cdda3b01a 100644 --- a/go.mod +++ b/go.mod @@ -28,7 +28,6 @@ require ( github.com/gobuffalo/packr v1.30.1 github.com/gofrs/flock v0.7.1 github.com/gofrs/uuid v3.2.0+incompatible - github.com/golang/mock v1.3.1 github.com/google/uuid v1.1.0 // indirect github.com/gorilla/securecookie v1.1.1 github.com/gorilla/sessions v1.2.0 @@ -37,7 +36,6 @@ require ( github.com/jpillora/backoff v0.0.0-20170918002102-8eab2debe79d github.com/manyminds/api2go v0.0.0-20171030193247-e7b693844a6f github.com/mattn/go-colorable v0.0.9 // indirect - github.com/mattn/go-runewidth v0.0.4 // indirect github.com/mitchellh/go-homedir v1.1.0 github.com/mrwonko/cron v0.0.0-20180828170130-e0ddd0f7e7db github.com/olekukonko/tablewriter v0.0.2 @@ -51,7 +49,7 @@ require ( github.com/spf13/viper v1.5.0 github.com/steakknife/bloomfilter v0.0.0-20180922174646-6819c0d2a570 // indirect github.com/steakknife/hamming v0.0.0-20180906055917-c99c65617cd3 // indirect - github.com/stretchr/testify v1.3.1-0.20190712000136-221dbe5ed467 + github.com/stretchr/testify v1.4.0 github.com/syndtr/goleveldb v0.0.0-20190203031304-2f17a3356c66 // indirect github.com/tevino/abool v0.0.0-20170917061928-9b9efcf221b5 github.com/tidwall/gjson v1.3.4 diff --git a/go.sum b/go.sum index 85c419d460b..2c924fc9816 100644 --- a/go.sum +++ b/go.sum @@ -130,8 +130,6 @@ github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4er github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0 h1:28o5sBqPkBsMGnC6b4MvE2TzSr5/AT4c/1fLqVGIwlk= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.3.1 h1:qGJ6qTW+x6xX/my+8YUVl4WNpX9B7+/l2tRsHGZ7f2s= -github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= github.com/golang/protobuf v0.0.0-20170307001533-c9c7427a2a70/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -248,8 +246,6 @@ github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+W github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= -github.com/onsi/gomega v1.7.0 h1:XPnZz8VVBHjVsy1vzJmRwIcSwiUO+JFfrv/xGiigmME= -github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.1 h1:K0jcRCwNQM3vFGh1ppMtDh/+7ApJrjldlX8fA0jDTLQ= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw= @@ -311,8 +307,6 @@ github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb6 github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= -github.com/spf13/viper v1.4.0 h1:yXHLWeravcrgGyFSyCgdYpXQ9dR9c/WED3pg1RhxqEU= -github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= github.com/spf13/viper v1.5.0 h1:GpsTwfsQ27oS/Aha/6d1oD7tpKIqWnOA6tgOX9HHkt4= github.com/spf13/viper v1.5.0/go.mod h1:AkYRkVJF8TkSG/xet6PzXX+l39KhhXa2pdqVSxnTcn4= github.com/steakknife/bloomfilter v0.0.0-20180922174646-6819c0d2a570 h1:gIlAHnH1vJb5vwEjIp5kBj/eu99p/bl0Ay2goiPe5xE= @@ -326,8 +320,7 @@ github.com/stretchr/testify v0.0.0-20170130113145-4d4bfba8f1d1/go.mod h1:a8OnRci github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.3.1-0.20190712000136-221dbe5ed467 h1:/pva5wyh0PKqe0bnHBbndEzbqsilMKFNXI0GPbO+L8c= -github.com/stretchr/testify v1.3.1-0.20190712000136-221dbe5ed467/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= @@ -335,8 +328,6 @@ github.com/syndtr/goleveldb v0.0.0-20190203031304-2f17a3356c66 h1:AwmkkZT+TucFot github.com/syndtr/goleveldb v0.0.0-20190203031304-2f17a3356c66/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ= github.com/tevino/abool v0.0.0-20170917061928-9b9efcf221b5 h1:hNna6Fi0eP1f2sMBe/rJicDmaHmoXGe1Ta84FPYHLuE= github.com/tevino/abool v0.0.0-20170917061928-9b9efcf221b5/go.mod h1:f1SCnEOt6sc3fOJfPQDRDzHOtSXuTtnz0ImG9kPRDV0= -github.com/tidwall/gjson v1.3.2 h1:+7p3qQFaH3fOMXAJSrdZwGKcOO/lYdGS0HqGhPqDdTI= -github.com/tidwall/gjson v1.3.2/go.mod h1:P256ACg0Mn+j1RXIDXoss50DeIABTYK1PULOJHhxOls= github.com/tidwall/gjson v1.3.4 h1:On5waDnyKKk3SWE4EthbjjirAWXp43xx5cKCUZY1eZw= github.com/tidwall/gjson v1.3.4/go.mod h1:P256ACg0Mn+j1RXIDXoss50DeIABTYK1PULOJHhxOls= github.com/tidwall/match v1.0.1 h1:PnKP62LPNxHKTwvHHZZzdOAOCtsJTjo6dZLCwpKm5xc= @@ -369,8 +360,6 @@ go.dedis.ch/fixbuf v1.0.3 h1:hGcV9Cd/znUxlusJ64eAlExS+5cJDIyTyEG+otu5wQs= go.dedis.ch/fixbuf v1.0.3/go.mod h1:yzJMt34Wa5xD37V5RTdmp38cz3QhMagdGoem9anUalw= go.dedis.ch/kyber/v3 v3.0.4 h1:FDuC/S3STkvwxZ0ooo3gcp56QkUKsN7Jy7cpzBxL+vQ= go.dedis.ch/kyber/v3 v3.0.4/go.mod h1:OzvaEnPvKlyrWyp3kGXlFdp7ap1VC6RkZDTaPikqhsQ= -go.dedis.ch/kyber/v3 v3.0.7 h1:yBaDckJWBkkK2xChU+vXotLqQareeRkfwY++GSwLkOI= -go.dedis.ch/kyber/v3 v3.0.7/go.mod h1:V1z0JihG9+dUEUCKLI9j9tjnlIflBw3wx8UOg0g3Pnk= go.dedis.ch/kyber/v3 v3.0.9 h1:i0ZbOQocHUjfFasBiUql5zVeC7u/vahFd96DFA8UOWk= go.dedis.ch/kyber/v3 v3.0.9/go.mod h1:rhNjUUg6ahf8HEg5HUvVBYoWY4boAafX8tYxX+PS+qg= go.dedis.ch/protobuf v1.0.5 h1:EbF1czEKICxf5KY8Tm7wMF28hcOQbB6yk4IybIFWTYE= @@ -385,8 +374,6 @@ go.uber.org/atomic v1.5.0 h1:OI5t8sDa1Or+q8AeE+yKeB/SDYioSHAgcVljj9JIETY= go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/multierr v1.1.0 h1:HoEmRHQPVSqub6w2z2d2EOVs2fjyFRGyofhKuyDq0QI= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= -go.uber.org/multierr v1.2.0 h1:6I+W7f5VwC5SV9dNrZ3qXrDB9mD0dyGOi/ZJmYw03T4= -go.uber.org/multierr v1.2.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= go.uber.org/multierr v1.4.0 h1:f3WCSC2KzAcBXGATIxAB1E2XuCpNU255wNKZ505qi3E= go.uber.org/multierr v1.4.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= @@ -394,8 +381,6 @@ go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee h1:0mgffUl7nfd+FpvXMVz4IDEa go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= go.uber.org/zap v1.10.0 h1:ORx85nbTijNz8ljznvCMR1ZBIPKFn3jQrag10X2AsuM= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= -go.uber.org/zap v1.11.0 h1:gSmpCfs+R47a4yQPAI4xJ0IPDLTRGXskm6UelqNXpqE= -go.uber.org/zap v1.11.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.12.0 h1:dySoUQPFBGj6xwjmBzageVL8jGi8uxc6bEmJQjA06bw= go.uber.org/zap v1.12.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= @@ -475,7 +460,6 @@ golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3 golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312170243-e65039ee4138 h1:H3uGjxCR/6Ds0Mjgyp7LMK81+LvmbvWWEnJhzk1Pi9E= golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190624180213-70d37148ca0c/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= From 7f9959fcea3df632896d8fc11aeff42c5792bf7f Mon Sep 17 00:00:00 2001 From: Ryan Hall Date: Mon, 11 Nov 2019 13:19:16 -0500 Subject: [PATCH 174/199] make ChainlinkNodeID column non-nullable --- explorer/src/entity/JobRun.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/explorer/src/entity/JobRun.ts b/explorer/src/entity/JobRun.ts index 548aa8adb1a..906a1e12df5 100644 --- a/explorer/src/entity/JobRun.ts +++ b/explorer/src/entity/JobRun.ts @@ -14,7 +14,7 @@ export class JobRun { @PrimaryGeneratedColumn('uuid') id: string - @Column({ nullable: true }) + @Column() chainlinkNodeId: number @Column() From 0b87c61c5d8d67414d22e7d731309244d423cb64 Mon Sep 17 00:00:00 2001 From: John Barker Date: Wed, 6 Nov 2019 16:56:21 -0700 Subject: [PATCH 175/199] Use a Merge function, to make merging of >2 inputs clearer --- core/adapters/bridge.go | 6 ++-- core/internal/cltest/cltest.go | 5 +-- core/services/run_executor.go | 14 ++++---- core/services/run_manager.go | 6 +++- core/store/models/common.go | 56 ++++++++++++++++---------------- core/store/models/common_test.go | 4 +-- 6 files changed, 48 insertions(+), 43 deletions(-) diff --git a/core/adapters/bridge.go b/core/adapters/bridge.go index adeab5602e4..eb54cba93e5 100644 --- a/core/adapters/bridge.go +++ b/core/adapters/bridge.go @@ -38,7 +38,7 @@ func (ba *Bridge) Perform(input models.RunInput, store *store.Store) models.RunO } func (ba *Bridge) handleNewRun(input models.RunInput, bridgeResponseURL *url.URL) models.RunOutput { - data, err := input.Data().Merge(ba.Params) + data, err := models.Merge(input.Data(), ba.Params) if err != nil { return models.NewRunOutputError(baRunResultError("handling data param", err)) } @@ -73,7 +73,7 @@ func (ba *Bridge) responseToRunResult(body []byte, input models.RunInput) models } if brr.Data.IsObject() { - data, err := ba.Params.Merge(brr.Data) + data, err := models.Merge(ba.Params, brr.Data) if err != nil { return models.NewRunOutputError(baRunResultError("handling data param", err)) } @@ -85,7 +85,7 @@ func (ba *Bridge) responseToRunResult(body []byte, input models.RunInput) models } func (ba *Bridge) postToExternalAdapter(input models.RunInput, bridgeResponseURL *url.URL) ([]byte, error) { - data, err := input.Data().Merge(ba.Params) + data, err := models.Merge(input.Data(), ba.Params) if err != nil { return nil, errors.Wrap(err, "error merging bridge params with input params") } diff --git a/core/internal/cltest/cltest.go b/core/internal/cltest/cltest.go index 829c221e90b..8b525ba7f1b 100644 --- a/core/internal/cltest/cltest.go +++ b/core/internal/cltest/cltest.go @@ -635,8 +635,9 @@ func CreateHelloWorldJobViaWeb(t testing.TB, app *TestApplication, url string) m err := json.Unmarshal(buffer, &job) require.NoError(t, err) - job.Tasks[0].Params, err = job.Tasks[0].Params.Merge(JSONFromString(t, `{"get":"%v"}`, url)) - assert.NoError(t, err) + data, err := models.Merge(job.Tasks[0].Params, JSONFromString(t, `{"get":"%v"}`, url)) + require.NoError(t, err) + job.Tasks[0].Params = data return CreateJobSpecViaWeb(t, app, job) } diff --git a/core/services/run_executor.go b/core/services/run_executor.go index dd930bf07bd..fc5f162ad96 100644 --- a/core/services/run_executor.go +++ b/core/services/run_executor.go @@ -84,10 +84,11 @@ func (je *runExecutor) Execute(runID *models.ID) error { func (je *runExecutor) executeTask(run *models.JobRun, taskRun *models.TaskRun) models.RunOutput { taskCopy := taskRun.TaskSpec // deliberately copied to keep mutations local - var err error - if taskCopy.Params, err = taskCopy.Params.Merge(run.Overrides); err != nil { + params, err := models.Merge(taskCopy.Params, run.Overrides) + if err != nil { return models.NewRunOutputError(err) } + taskCopy.Params = params adapter, err := adapters.For(taskCopy, je.store.Config, je.store.ORM) if err != nil { @@ -96,14 +97,13 @@ func (je *runExecutor) executeTask(run *models.JobRun, taskRun *models.TaskRun) previousTaskRun := run.PreviousTaskRun() - data := models.JSON{} + previousTaskInput := models.JSON{} if previousTaskRun != nil { - if data, err = previousTaskRun.Result.Data.Merge(taskRun.Result.Data); err != nil { - return models.NewRunOutputError(err) - } + previousTaskInput = previousTaskRun.Result.Data } - if data, err = run.Overrides.Merge(data); err != nil { + data, err := models.Merge(previousTaskInput, currentTaskRun.Result.Data, run.Overrides) + if err != nil { return models.NewRunOutputError(err) } diff --git a/core/services/run_manager.go b/core/services/run_manager.go index 855e15089d7..9bcfa426fa3 100644 --- a/core/services/run_manager.go +++ b/core/services/run_manager.go @@ -300,7 +300,11 @@ func (jm *runManager) ResumePending( return jm.updateWithError(&run, "Attempting to resume pending run with no remaining tasks %s", run.ID) } - run.Overrides.Merge(input.Data) + data, err := models.Merge(run.Overrides, input.Data) + if err != nil { + return jm.updateWithError(&run, "Error while merging onto overrides for run %s", run.ID) + } + run.Overrides = data currentTaskRun.ApplyBridgeRunResult(input) run.ApplyBridgeRunResult(input) diff --git a/core/store/models/common.go b/core/store/models/common.go index bc7f18742a5..9190ca29eef 100644 --- a/core/store/models/common.go +++ b/core/store/models/common.go @@ -184,31 +184,6 @@ func (j JSON) MarshalJSON() ([]byte, error) { return []byte("{}"), nil } -// Merge combines the given JSON with the existing JSON. -func (j JSON) Merge(j2 JSON) (JSON, error) { - body := j.Map() - if body == nil || (j.Type != gjson.JSON && j.Type != gjson.Null) { - return JSON{}, ErrorCannotMergeNonObject - } - - for key, value := range j2.Map() { - body[key] = value - } - - cleaned := map[string]interface{}{} - for k, v := range body { - cleaned[k] = v.Value() - } - - b, err := json.Marshal(cleaned) - if err != nil { - return JSON{}, err - } - - var rval JSON - return rval, gjson.Unmarshal(b, &rval) -} - // Bytes returns the raw JSON. func (j JSON) Bytes() []byte { return []byte(j.String()) @@ -216,16 +191,17 @@ func (j JSON) Bytes() []byte { // Add returns a new instance of JSON with the new value added. func (j JSON) Add(key string, val interface{}) (JSON, error) { - var j2 JSON b, err := json.Marshal(val) if err != nil { - return j2, err + return JSON{}, err } + + var j2 JSON str := fmt.Sprintf(`{"%v":%v}`, key, string(b)) if err = json.Unmarshal([]byte(str), &j2); err != nil { return j2, err } - return j.Merge(j2) + return Merge(j, j2) } // Delete returns a new instance of JSON with the specified key removed. @@ -480,3 +456,27 @@ type Configuration struct { Name string `gorm:"not null;unique;index"` Value string `gorm:"not null"` } + +// Merge returns a new map with all keys merged from right to left +func Merge(inputs ...JSON) (JSON, error) { + output := make(map[string]interface{}) + + for _, input := range inputs { + switch v := input.Result.Value().(type) { + case map[string]interface{}: + for key, value := range v { + output[key] = value + } + case nil: + default: + return JSON{}, errors.New("can only merge JSON objects") + } + } + + bytes, err := json.Marshal(output) + if err != nil { + return JSON{}, err + } + + return JSON{Result: gjson.ParseBytes(bytes)}, nil +} diff --git a/core/store/models/common_test.go b/core/store/models/common_test.go index 23dc293c39f..dfd2a669317 100644 --- a/core/store/models/common_test.go +++ b/core/store/models/common_test.go @@ -82,7 +82,7 @@ func TestJSON_Merge(t *testing.T) { j1 := cltest.JSONFromString(t, test.original) j2 := cltest.JSONFromString(t, test.input) - merged, err := j1.Merge(j2) + merged, err := models.Merge(j1, j2) if test.wantError { require.Error(t, err) } else { @@ -95,7 +95,7 @@ func TestJSON_Merge(t *testing.T) { } func TestJSON_MergeNull(t *testing.T) { - merged, err := models.JSON{}.Merge(models.JSON{}) + merged, err := models.Merge(models.JSON{}, models.JSON{}) require.NoError(t, err) assert.Equal(t, `{}`, merged.String()) } From a57f7f2835845d3ae0cbd912bb2e2da75493641b Mon Sep 17 00:00:00 2001 From: John Barker Date: Wed, 6 Nov 2019 17:24:44 -0700 Subject: [PATCH 176/199] Rewrite Add to create an output directly instead of using merge --- core/services/run_executor.go | 2 +- core/store/models/common.go | 27 +++++++++++++++++++-------- core/store/orm/orm_test.go | 26 -------------------------- 3 files changed, 20 insertions(+), 35 deletions(-) diff --git a/core/services/run_executor.go b/core/services/run_executor.go index fc5f162ad96..5d3873a6611 100644 --- a/core/services/run_executor.go +++ b/core/services/run_executor.go @@ -102,7 +102,7 @@ func (je *runExecutor) executeTask(run *models.JobRun, taskRun *models.TaskRun) previousTaskInput = previousTaskRun.Result.Data } - data, err := models.Merge(previousTaskInput, currentTaskRun.Result.Data, run.Overrides) + data, err := models.Merge(run.Overrides, previousTaskInput, taskRun.Result.Data) if err != nil { return models.NewRunOutputError(err) } diff --git a/core/store/models/common.go b/core/store/models/common.go index 9190ca29eef..b7587df6c3a 100644 --- a/core/store/models/common.go +++ b/core/store/models/common.go @@ -190,18 +190,29 @@ func (j JSON) Bytes() []byte { } // Add returns a new instance of JSON with the new value added. -func (j JSON) Add(key string, val interface{}) (JSON, error) { - b, err := json.Marshal(val) +func (j JSON) Add(insertKey string, insertValue interface{}) (JSON, error) { + output := make(map[string]interface{}) + + switch v := j.Result.Value().(type) { + case map[string]interface{}: + for key, value := range v { + if key != insertKey { + output[key] = value + } + } + output[insertKey] = insertValue + case nil: + output[insertKey] = insertValue + default: + return JSON{}, errors.New("can only add to JSON objects or null") + } + + bytes, err := json.Marshal(output) if err != nil { return JSON{}, err } - var j2 JSON - str := fmt.Sprintf(`{"%v":%v}`, key, string(b)) - if err = json.Unmarshal([]byte(str), &j2); err != nil { - return j2, err - } - return Merge(j, j2) + return JSON{Result: gjson.ParseBytes(bytes)}, nil } // Delete returns a new instance of JSON with the specified key removed. diff --git a/core/store/orm/orm_test.go b/core/store/orm/orm_test.go index a562e62cbd4..46515dd79f0 100644 --- a/core/store/orm/orm_test.go +++ b/core/store/orm/orm_test.go @@ -119,32 +119,6 @@ func TestORM_CreateJobRun_CreatesRunRequest(t *testing.T) { assert.Equal(t, 1, requestCount) } -func TestORM_SaveJobRun_DoesNotSaveTaskSpec(t *testing.T) { - t.Parallel() - store, cleanup := cltest.NewStore(t) - defer cleanup() - - job := cltest.NewJobWithSchedule("* * * * *") - require.NoError(t, store.CreateJob(&job)) - - jr := job.NewRun(job.Initiators[0]) - require.NoError(t, store.CreateJobRun(&jr)) - - var err error - jr.TaskRuns[0].TaskSpec.Params, err = jr.TaskRuns[0].TaskSpec.Params.Merge(cltest.JSONFromString(t, `{"random": "input"}`)) - require.NoError(t, err) - require.NoError(t, store.SaveJobRun(&jr)) - - retrievedJob, err := store.FindJob(job.ID) - require.NoError(t, err) - require.Len(t, job.Tasks, 1) - require.Len(t, retrievedJob.Tasks, 1) - assert.JSONEq( - t, - coercedJSON(job.Tasks[0].Params.String()), - retrievedJob.Tasks[0].Params.String()) -} - func TestORM_SaveJobRun_ArchivedDoesNotRevertDeletedAt(t *testing.T) { t.Parallel() store, cleanup := cltest.NewStore(t) From d7cbceadccbf0e6687eaf2eb783291c045b9a5de Mon Sep 17 00:00:00 2001 From: John Barker Date: Mon, 11 Nov 2019 13:17:34 -0700 Subject: [PATCH 177/199] Remove unused coercedJSON --- core/store/orm/orm_test.go | 7 ------- 1 file changed, 7 deletions(-) diff --git a/core/store/orm/orm_test.go b/core/store/orm/orm_test.go index 46515dd79f0..6d1021db36f 100644 --- a/core/store/orm/orm_test.go +++ b/core/store/orm/orm_test.go @@ -166,13 +166,6 @@ func TestORM_SaveJobRun_Cancelled(t *testing.T) { assert.Equal(t, orm.OptimisticUpdateConflictError, store.SaveJobRun(&jr)) } -func coercedJSON(v string) string { - if v == "" { - return "{}" - } - return v -} - func TestORM_JobRunsFor(t *testing.T) { t.Parallel() From 737cc6a529db04def663b70ae67fc6bf18715e94 Mon Sep 17 00:00:00 2001 From: John Barker Date: Mon, 11 Nov 2019 13:18:12 -0700 Subject: [PATCH 178/199] Remove unused ErrorCannotMergeNonObject --- core/store/models/common.go | 6 ------ 1 file changed, 6 deletions(-) diff --git a/core/store/models/common.go b/core/store/models/common.go index b7587df6c3a..e9afe5f0a42 100644 --- a/core/store/models/common.go +++ b/core/store/models/common.go @@ -21,12 +21,6 @@ import ( "github.com/ugorji/go/codec" ) -var ( - // ErrorCannotMergeNonObject is returned if a Merge was attempted on a string - // or array JSON value - ErrorCannotMergeNonObject = errors.New("Cannot merge, expected object '{}'") -) - // RunStatus is a string that represents the run status type RunStatus string From fdb64ba0e2f58334d0ebcb159f08273243051d89 Mon Sep 17 00:00:00 2001 From: Ryan Hall Date: Mon, 11 Nov 2019 16:46:22 -0500 Subject: [PATCH 179/199] remove puppeteer dependency --- explorer/package.json | 4 --- yarn.lock | 60 +++---------------------------------------- 2 files changed, 3 insertions(+), 61 deletions(-) diff --git a/explorer/package.json b/explorer/package.json index 0034c179e50..c1f763e7f24 100644 --- a/explorer/package.json +++ b/explorer/package.json @@ -60,7 +60,6 @@ "@chainlink/prettier-config": "0.0.1", "@types/bcrypt": "^3.0.0", "@types/cookie-session": "^2.0.37", - "@types/expect-puppeteer": "^3.3.1", "@types/express": "^4.16.1", "@types/express-winston": "^3.0.1", "@types/helmet": "0.0.43", @@ -70,7 +69,6 @@ "@types/mime-types": "^2.1.0", "@types/node": "^11.11.3", "@types/node-fetch": "^2.5.0", - "@types/puppeteer": "^1.12.0", "@types/supertest": "^2.0.7", "@types/uuid": "^3.4.4", "@types/ws": "^6.0.1", @@ -79,12 +77,10 @@ "cross-env": "^6.0.3", "depcheck": "^0.8.3", "eslint": "^6.3.0", - "expect-puppeteer": "^4.1.0", "http-status-codes": "^1.3.2", "jest": "^24.7.0", "node-fetch": "^2.6.0", "prettier": "^1.18.2", - "puppeteer": "^1.20.0", "supertest": "^4.0.2", "ts-jest": "^24.0.0", "ts-node-dev": "^1.0.0-pre.40", diff --git a/yarn.lock b/yarn.lock index 3e776ebb09b..29bb9d8ecfd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2668,14 +2668,6 @@ resolved "https://registry.npmjs.org/@types/events/-/events-3.0.0.tgz#2862f3f58a9a7f7c3e78d79f130dd4d71c25c2a7" integrity sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g== -"@types/expect-puppeteer@^3.3.1": - version "3.3.1" - resolved "https://registry.npmjs.org/@types/expect-puppeteer/-/expect-puppeteer-3.3.1.tgz#46e5944bf425b86ea13a563c7c8b86901414988d" - integrity sha512-3raSnf28NelDtv0ksvQPZs410taJZ4d70vA8sVzmbRPV04fpmQm9/BOxUCloETD/ZI1EXRpv0pzOQKhPTbm4jg== - dependencies: - "@types/jest" "*" - "@types/puppeteer" "*" - "@types/express-serve-static-core@*": version "4.16.9" resolved "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.16.9.tgz#69e00643b0819b024bdede95ced3ff239bb54558" @@ -2788,7 +2780,7 @@ resolved "https://registry.npmjs.org/@types/jest-diff/-/jest-diff-20.0.1.tgz#35cc15b9c4f30a18ef21852e255fdb02f6d59b89" integrity sha512-yALhelO3i0hqZwhjtcr6dYyaLoCHbAMshwtj6cGxTvHZAKXHsYGdff6E8EPw3xLKY0ELUTQ69Q1rQiJENnccMA== -"@types/jest@*", "@types/jest@^24.0.0", "@types/jest@^24.0.18": +"@types/jest@^24.0.0", "@types/jest@^24.0.18": version "24.0.18" resolved "https://registry.npmjs.org/@types/jest/-/jest-24.0.18.tgz#9c7858d450c59e2164a8a9df0905fc5091944498" integrity sha512-jcDDXdjTcrQzdN06+TSVsPPqxvsZA/5QkYfIZlq1JMw7FdP5AZylbOc+6B/cuDurctRe+MziUMtQ3xQdrbjqyQ== @@ -2917,13 +2909,6 @@ resolved "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.2.tgz#0e58ae66773d7fd7c372a493aff740878ec9ceaa" integrity sha512-f8JzJNWVhKtc9dg/dyDNfliTKNOJSLa7Oht/ElZdF/UbMUmAH3rLmAk3ODNjw0mZajDEgatA03tRjB4+Dp/tzA== -"@types/puppeteer@*", "@types/puppeteer@^1.12.0": - version "1.20.0" - resolved "https://registry.yarnpkg.com/@types/puppeteer/-/puppeteer-1.20.0.tgz#8a09062a744ca1e2f0607468d810b149ab7cf97f" - integrity sha512-V3nWu/ENNW7LzHumrgf1D+HnIEQHE+nHThq9MTPmGjQC75SVlWsalCp1OaZFaDeuUgWOmQPIBhSLz0cz+Hlz8w== - dependencies: - "@types/node" "*" - "@types/q@^1.5.1": version "1.5.2" resolved "https://registry.npmjs.org/@types/q/-/q-1.5.2.tgz#690a1475b84f2a884fd07cd797c00f5f31356ea8" @@ -3656,13 +3641,6 @@ after@0.8.2: resolved "https://registry.npmjs.org/after/-/after-0.8.2.tgz#fedb394f9f0e02aa9768e702bda23b505fae7e1f" integrity sha1-/ts5T58OAqqXaOcCvaI7UF+ufh8= -agent-base@^4.1.0: - version "4.2.1" - resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.2.1.tgz#d89e5999f797875674c07d87f260fc41e83e8ca9" - integrity sha512-JVwXMr9nHYTUXsBFKUqhJwvlcYU/blreOEUkhNR2eXZIvwd+c+o5V4MgDPKWnMS/56awN3TRzIP+KoPn+roQtg== - dependencies: - es6-promisify "^5.0.0" - "airbnb-js-shims@^1 || ^2": version "2.2.0" resolved "https://registry.npmjs.org/airbnb-js-shims/-/airbnb-js-shims-2.2.0.tgz#46e1d9d9516f704ef736de76a3b6d484df9a96d8" @@ -9823,11 +9801,6 @@ expect-ct@0.2.0: resolved "https://registry.npmjs.org/expect-ct/-/expect-ct-0.2.0.tgz#3a54741b6ed34cc7a93305c605f63cd268a54a62" integrity sha512-6SK3MG/Bbhm8MsgyJAylg+ucIOU71/FzyFalcfu5nY19dH8y/z0tBJU0wrNBXD4B27EoQtqPF/9wqH0iYAd04g== -expect-puppeteer@^4.1.0: - version "4.3.0" - resolved "https://registry.npmjs.org/expect-puppeteer/-/expect-puppeteer-4.3.0.tgz#732a3c94ab44af0c7d947040ad3e3637a0359bf3" - integrity sha512-p8N/KSVPG9PAOJlftK5f1n3JrULJ6Qq1EQ8r/n9xzkX2NmXbK8PcnJnkSAEzEHrMycELKGnlJV7M5nkgm+wEWA== - expect@^24.9.0: version "24.9.0" resolved "https://registry.npmjs.org/expect/-/expect-24.9.0.tgz#b75165b4817074fa4a157794f46fe9f1ba15b6ca" @@ -9945,7 +9918,7 @@ extract-css-chunks-webpack-plugin@^3.2.1: schema-utils "^1.0.0" webpack-sources "^1.1.0" -extract-zip@1.6.7, extract-zip@^1.6.6: +extract-zip@1.6.7: version "1.6.7" resolved "https://registry.npmjs.org/extract-zip/-/extract-zip-1.6.7.tgz#a840b4b8af6403264c8db57f4f1a74333ef81fe9" integrity sha1-qEC0uK9kAyZMjbV/Txp0Mz74H+k= @@ -11870,14 +11843,6 @@ https-browserify@^1.0.0: resolved "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73" integrity sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM= -https-proxy-agent@^2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-2.2.1.tgz#51552970fa04d723e04c56d04178c3f92592bbc0" - integrity sha512-HPCTS1LW51bcyMYbxUIOO4HEOlQ1/1qRaFWcyxvwaqUS9TY88aoEuHUY33kuAh1YhVVaDQhLZsnPd+XNARWZlQ== - dependencies: - agent-base "^4.1.0" - debug "^3.1.0" - human-signals@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-1.1.1.tgz#c5b1cd14f50aeae09ab6c59fe63ba3395fe4dfa3" @@ -17651,11 +17616,6 @@ proxy-addr@~2.0.5: forwarded "~0.1.2" ipaddr.js "1.9.0" -proxy-from-env@^1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.0.0.tgz#33c50398f70ea7eb96d21f7b817630a55791c7ee" - integrity sha1-M8UDmPcOp+uW0h97gXYwpVeRx+4= - prr@~1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz#d3fc114ba06995a45ec6893f484ceb1d78f5f476" @@ -17784,20 +17744,6 @@ punycode@^2.1.0, punycode@^2.1.1: resolved "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== -puppeteer@^1.20.0: - version "1.20.0" - resolved "https://registry.yarnpkg.com/puppeteer/-/puppeteer-1.20.0.tgz#e3d267786f74e1d87cf2d15acc59177f471bbe38" - integrity sha512-bt48RDBy2eIwZPrkgbcwHtb51mj2nKvHOPMaSH2IsWiv7lOG9k9zhaRzpDZafrk05ajMc3cu+lSQYYOfH2DkVQ== - dependencies: - debug "^4.1.0" - extract-zip "^1.6.6" - https-proxy-agent "^2.2.1" - mime "^2.0.3" - progress "^2.0.1" - proxy-from-env "^1.0.0" - rimraf "^2.6.1" - ws "^6.1.0" - q@2.0.x: version "2.0.3" resolved "https://registry.npmjs.org/q/-/q-2.0.3.tgz#75b8db0255a1a5af82f58c3f3aaa1efec7d0d134" @@ -23634,7 +23580,7 @@ ws@^5.1.1, ws@^5.2.0: dependencies: async-limiter "~1.0.0" -ws@^6.0.0, ws@^6.1.0, ws@^6.1.2, ws@^6.2.1: +ws@^6.0.0, ws@^6.1.2, ws@^6.2.1: version "6.2.1" resolved "https://registry.npmjs.org/ws/-/ws-6.2.1.tgz#442fdf0a47ed64f59b6a5d8ff130f4748ed524fb" integrity sha512-GIyAXC2cB7LjvpgMt9EKS2ldqr0MTrORaleiOno6TweZ6r3TKtoFQWay/2PceJ3RuBasOHzXNn5Lrw1X0bEjqA== From eb32d5b835da4c3e8746a733d8f41ba6f637e62e Mon Sep 17 00:00:00 2001 From: Ryan Hall Date: Mon, 11 Nov 2019 16:55:36 -0500 Subject: [PATCH 180/199] add jobRunId detection to integration test --- integration/cypress/integration/createAndRunJob.spec.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/integration/cypress/integration/createAndRunJob.spec.ts b/integration/cypress/integration/createAndRunJob.spec.ts index 906ab7480d7..92cb7eb7a77 100644 --- a/integration/cypress/integration/createAndRunJob.spec.ts +++ b/integration/cypress/integration/createAndRunJob.spec.ts @@ -40,5 +40,8 @@ context('End to end', function() { cy.clickLink(runId) }) cy.contains('h5', 'Complete').should('exist') + cy.get('@runId').then(runId => { + cy.contains(runId).should('exist') + }) }) }) From 1841c82037a02f7f4723d153220278b2cb5e2dca Mon Sep 17 00:00:00 2001 From: Ryan Hall Date: Mon, 11 Nov 2019 16:56:57 -0500 Subject: [PATCH 181/199] remove puppeteer!! --- explorer/e2e/showJobRun.test.ts | 43 ------------------- .../integration/createAndRunJob.spec.ts | 4 +- 2 files changed, 1 insertion(+), 46 deletions(-) delete mode 100644 explorer/e2e/showJobRun.test.ts diff --git a/explorer/e2e/showJobRun.test.ts b/explorer/e2e/showJobRun.test.ts deleted file mode 100644 index 2ae165a4e63..00000000000 --- a/explorer/e2e/showJobRun.test.ts +++ /dev/null @@ -1,43 +0,0 @@ -import expect from 'expect-puppeteer' -import { Server } from 'http' -import { Browser, launch, Page } from 'puppeteer' -import { getDb } from '../src/database' -import { createChainlinkNode } from '../src/entity/ChainlinkNode' -import { DEFAULT_TEST_PORT, start, stop } from '../src/support/server' -import { createJobRun } from '../src/factories' - -describe('End to end', () => { - let browser: Browser - let page: Page - let server: Server - - beforeAll(async () => { - browser = await launch({ - args: ['--no-sandbox'], - devtools: false, - headless: true, - }) - - page = await browser.newPage() - server = await start() - - page.on('console', msg => console.log('PAGE LOG:', msg.text())) - }) - - afterAll(async done => { - browser.close() - stop(server, done) - }) - - it('can search for job run', async () => { - const db = await getDb() - const [node] = await createChainlinkNode(db, 'endToEndChainlinkNode') - const jobRun = await createJobRun(db, node) - - await page.goto(`http://localhost:${DEFAULT_TEST_PORT}`) - await expect(page).toFill('form input[name=search]', jobRun.runId) - await expect(page).toClick('form button') - await page.waitForNavigation() - await expect(page).toMatch(jobRun.runId) - }) -}) diff --git a/integration/cypress/integration/createAndRunJob.spec.ts b/integration/cypress/integration/createAndRunJob.spec.ts index 92cb7eb7a77..46c01fef92b 100644 --- a/integration/cypress/integration/createAndRunJob.spec.ts +++ b/integration/cypress/integration/createAndRunJob.spec.ts @@ -38,10 +38,8 @@ context('End to end', function() { cy.clickButton('Search') cy.get('@runId').then(runId => { cy.clickLink(runId) - }) - cy.contains('h5', 'Complete').should('exist') - cy.get('@runId').then(runId => { cy.contains(runId).should('exist') }) + cy.contains('h5', 'Complete').should('exist') }) }) From a6e9ad66a27aff48476d19df5129e82e74241557 Mon Sep 17 00:00:00 2001 From: John Barker Date: Mon, 11 Nov 2019 14:59:18 -0700 Subject: [PATCH 182/199] Trim extra Runnable() check from run executor --- core/services/run_executor.go | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/core/services/run_executor.go b/core/services/run_executor.go index 5d3873a6611..98479889c58 100644 --- a/core/services/run_executor.go +++ b/core/services/run_executor.go @@ -39,11 +39,12 @@ func (je *runExecutor) Execute(runID *models.ID) error { } for taskIndex := range run.TaskRuns { + taskRun := &run.TaskRuns[taskIndex] if !run.Status.Runnable() { + logger.Debugw("Task execution blocked", run.ForLogger("task", taskRun.ID.String())...) break } - taskRun := &run.TaskRuns[taskIndex] if taskRun.Status.Completed() { continue } @@ -54,10 +55,6 @@ func (je *runExecutor) Execute(runID *models.ID) error { taskRun.ApplyOutput(result) run.ApplyOutput(result) - if !result.Status().Runnable() { - logger.Debugw("Task execution blocked", run.ForLogger("task", taskRun.ID.String())...) - } - } else { logger.Debugw("Pausing run pending confirmations", run.ForLogger("required_height", taskRun.MinimumConfirmations)..., From ce64df85cebadf4a7a6e4a633cd44918fe8030af Mon Sep 17 00:00:00 2001 From: Ryan Hall Date: Mon, 11 Nov 2019 17:06:43 -0500 Subject: [PATCH 183/199] remove e2e linting and test scripts --- .circleci/config.yml | 3 --- explorer/tsconfig.json | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 76d96817526..24899be65f7 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -274,9 +274,6 @@ jobs: - run: name: Run Client Tests command: yarn workspace @chainlink/explorer-client run test-ci:silent - - run: - name: Run E2E Tests - command: yarn workspace @chainlink/explorer-client run build && yarn workspace @chainlink/explorer run test-ci:e2e:silent - store_artifacts: path: ./integration/logs forks: diff --git a/explorer/tsconfig.json b/explorer/tsconfig.json index 407e608672f..826c0de4d09 100644 --- a/explorer/tsconfig.json +++ b/explorer/tsconfig.json @@ -15,5 +15,5 @@ "baseUrl": ".", "typeRoots": ["node_modules/@types", "../node_modules/@types", "@types"] }, - "include": ["src/**/*", "e2e/**/*"] + "include": ["src/**/*"] } From 03ab0cddc0daa5c72f3ff1515d64c2fecb48aeaa Mon Sep 17 00:00:00 2001 From: Ryan Hall Date: Mon, 11 Nov 2019 17:13:06 -0500 Subject: [PATCH 184/199] remove e2e linting and test scripts --- explorer/package.json | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/explorer/package.json b/explorer/package.json index c1f763e7f24..7c9390524b7 100644 --- a/explorer/package.json +++ b/explorer/package.json @@ -20,11 +20,8 @@ "pretest": "NODE_ENV=test yarn automigrate", "test": "yarn jest --runInBand --detectOpenHandles", "test-ci": "yarn jest src/__tests__ --runInBand --detectOpenHandles", - "test-ci:e2e": "yarn build && yarn test-ci:e2e:no-build", - "test-ci:e2e:no-build": "yarn jest e2e/ --runInBand --detectOpenHandles", "test-ci:silent": "yarn test-ci --silent", - "test-ci:e2e:silent": "yarn test-ci:e2e --silent", - "lint": "eslint --ext .js,.ts,.tsx,.jsx src e2e", + "lint": "eslint --ext .js,.ts,.tsx,.jsx src", "lint:fix": "yarn lint --fix", "format": "prettier --write \"**/*\"", "migration:run": "cross-env ts-node ./src/bin/migrator.ts migrate", From 5494d6e8a23129fdf9ba1e660c19fd11e998c914 Mon Sep 17 00:00:00 2001 From: Alex Kwiatkowski Date: Mon, 11 Nov 2019 15:35:18 -0800 Subject: [PATCH 185/199] Render JSON-API errors on 422 unprocessable entity --- tools/json-api-client/src/errors.ts | 9 +++++++++ tools/json-api-client/src/transport/json.ts | 4 ++++ 2 files changed, 13 insertions(+) diff --git a/tools/json-api-client/src/errors.ts b/tools/json-api-client/src/errors.ts index 57bb053c03f..fe77950f4d9 100644 --- a/tools/json-api-client/src/errors.ts +++ b/tools/json-api-client/src/errors.ts @@ -32,6 +32,15 @@ export class BadRequestError extends Error { } } +export class UnprocessableEntityError extends Error { + errors: ErrorItem[] + + constructor(errors: ErrorItem[]) { + super('UnprocessableEntityError') + this.errors = errors + } +} + export class ServerError extends Error { errors: ErrorItem[] diff --git a/tools/json-api-client/src/transport/json.ts b/tools/json-api-client/src/transport/json.ts index 44e291d5b5d..fbea3bc2f14 100644 --- a/tools/json-api-client/src/transport/json.ts +++ b/tools/json-api-client/src/transport/json.ts @@ -9,6 +9,7 @@ import fetchWithTimeout from '../fetchWithTimeout' import { AuthenticationError, BadRequestError, + UnprocessableEntityError, ServerError, UnknownResponseError, ErrorItem, @@ -136,6 +137,9 @@ async function parseResponse(response: Response): Promise { }) } else if (response.status === 401) { throw new AuthenticationError(response) + } else if (response.status === 422) { + const errors = await errorItems(response) + throw new UnprocessableEntityError(errors) } else if (response.status >= 500) { const errors = await errorItems(response) throw new ServerError(errors) From 76b31820fc694d195f7dcdd50900bae722bd722c Mon Sep 17 00:00:00 2001 From: henrynguyen5 <6404866+HenryNguyen5@users.noreply.github.com> Date: Mon, 11 Nov 2019 18:19:05 -0500 Subject: [PATCH 186/199] Remove unused examples --- .solhintignore | 3 - README.md | 38 ++-- examples/echo_server/.eslintignore | 2 - examples/echo_server/.eslintrc.js | 7 - examples/echo_server/.prettierignore | 6 - examples/echo_server/README.md | 45 ---- examples/echo_server/broadcast_logs_job.json | 7 - examples/echo_server/clmigration.js | 19 -- examples/echo_server/contracts/EthLog.sol | 9 - examples/echo_server/contracts/LinkToken.sol | 3 - examples/echo_server/contracts/Migrations.sol | 23 -- examples/echo_server/contracts/Oracle.sol | 3 - examples/echo_server/contracts/RunLog.sol | 27 --- examples/echo_server/create_ethlog_job | 16 -- examples/echo_server/echo.js | 28 --- .../migrations/1_initial_migration.js | 5 - examples/echo_server/migrations/2_eth_log.js | 5 - .../echo_server/migrations/3_link_token.js | 5 - .../migrations/4_chainlink_oracle.js | 6 - examples/echo_server/migrations/5_run_log.js | 28 --- .../echo_server/migrations/6_transfer_link.js | 20 -- examples/echo_server/package.json | 38 ---- examples/echo_server/screenshot.jpg | Bin 174588 -> 0 bytes .../echo_server/send_ethlog_transaction.js | 29 --- .../echo_server/send_runlog_transaction.js | 29 --- examples/echo_server/test/RunLog_test.js | 21 -- examples/echo_server/truffle.js | 19 -- examples/twilio_sms/.eslintignore | 2 - examples/twilio_sms/.eslintrc.js | 7 - examples/twilio_sms/.prettierignore | 5 - examples/twilio_sms/HitMeOnMyBeeper.jpg | Bin 25645 -> 0 bytes examples/twilio_sms/README.md | 34 --- examples/twilio_sms/contracts/GetMoney.sol | 11 - examples/twilio_sms/contracts/Migrations.sol | 23 -- examples/twilio_sms/create_twilio_job_for | 27 --- .../migrations/1_initial_migration.js | 5 - examples/twilio_sms/migrations/2_get_money.js | 5 - examples/twilio_sms/package.json | 30 --- examples/twilio_sms/send_money_to | 22 -- examples/twilio_sms/truffle.js | 12 -- examples/twilio_sms/twilio.js | 44 ---- examples/uptime_sla/.babelrc | 8 - examples/uptime_sla/.eslintignore | 2 - examples/uptime_sla/.eslintrc.js | 7 - .../.node-xmlhttprequest-sync-78767 | 0 examples/uptime_sla/.prettierignore | 6 - examples/uptime_sla/README.md | 61 ------ examples/uptime_sla/clmigration.js | 19 -- examples/uptime_sla/contracts/LinkToken.sol | 3 - examples/uptime_sla/contracts/Migrations.sol | 23 -- examples/uptime_sla/contracts/Oracle.sol | 3 - examples/uptime_sla/contracts/UptimeSLA.sol | 54 ----- examples/uptime_sla/deploy | 3 - examples/uptime_sla/get_uptime.js | 20 -- .../migrations/1_initial_migration.js | 5 - .../uptime_sla/migrations/2_link_token.js | 5 - examples/uptime_sla/migrations/3_oracle.js | 6 - .../uptime_sla/migrations/4_uptime_sla.js | 45 ---- examples/uptime_sla/package.json | 37 ---- examples/uptime_sla/send_sla_transaction.js | 21 -- examples/uptime_sla/test/UptimeSLA_test.js | 151 ------------- examples/uptime_sla/test/support/helpers.js | 198 ------------------ examples/uptime_sla/truffle.js | 16 -- tools/bin/echo_server | 3 - tools/ci/truffle_test | 2 - yarn.lock | 154 +------------- 66 files changed, 29 insertions(+), 1491 deletions(-) delete mode 100644 examples/echo_server/.eslintignore delete mode 100644 examples/echo_server/.eslintrc.js delete mode 100644 examples/echo_server/.prettierignore delete mode 100644 examples/echo_server/README.md delete mode 100644 examples/echo_server/broadcast_logs_job.json delete mode 100644 examples/echo_server/clmigration.js delete mode 100644 examples/echo_server/contracts/EthLog.sol delete mode 100644 examples/echo_server/contracts/LinkToken.sol delete mode 100644 examples/echo_server/contracts/Migrations.sol delete mode 100644 examples/echo_server/contracts/Oracle.sol delete mode 100644 examples/echo_server/contracts/RunLog.sol delete mode 100755 examples/echo_server/create_ethlog_job delete mode 100755 examples/echo_server/echo.js delete mode 100644 examples/echo_server/migrations/1_initial_migration.js delete mode 100644 examples/echo_server/migrations/2_eth_log.js delete mode 100644 examples/echo_server/migrations/3_link_token.js delete mode 100644 examples/echo_server/migrations/4_chainlink_oracle.js delete mode 100644 examples/echo_server/migrations/5_run_log.js delete mode 100644 examples/echo_server/migrations/6_transfer_link.js delete mode 100644 examples/echo_server/package.json delete mode 100644 examples/echo_server/screenshot.jpg delete mode 100755 examples/echo_server/send_ethlog_transaction.js delete mode 100755 examples/echo_server/send_runlog_transaction.js delete mode 100644 examples/echo_server/test/RunLog_test.js delete mode 100644 examples/echo_server/truffle.js delete mode 100644 examples/twilio_sms/.eslintignore delete mode 100644 examples/twilio_sms/.eslintrc.js delete mode 100644 examples/twilio_sms/.prettierignore delete mode 100644 examples/twilio_sms/HitMeOnMyBeeper.jpg delete mode 100644 examples/twilio_sms/README.md delete mode 100644 examples/twilio_sms/contracts/GetMoney.sol delete mode 100644 examples/twilio_sms/contracts/Migrations.sol delete mode 100755 examples/twilio_sms/create_twilio_job_for delete mode 100644 examples/twilio_sms/migrations/1_initial_migration.js delete mode 100644 examples/twilio_sms/migrations/2_get_money.js delete mode 100644 examples/twilio_sms/package.json delete mode 100755 examples/twilio_sms/send_money_to delete mode 100644 examples/twilio_sms/truffle.js delete mode 100755 examples/twilio_sms/twilio.js delete mode 100644 examples/uptime_sla/.babelrc delete mode 100644 examples/uptime_sla/.eslintignore delete mode 100644 examples/uptime_sla/.eslintrc.js delete mode 100644 examples/uptime_sla/.node-xmlhttprequest-sync-78767 delete mode 100644 examples/uptime_sla/.prettierignore delete mode 100644 examples/uptime_sla/README.md delete mode 100644 examples/uptime_sla/clmigration.js delete mode 100644 examples/uptime_sla/contracts/LinkToken.sol delete mode 100644 examples/uptime_sla/contracts/Migrations.sol delete mode 100644 examples/uptime_sla/contracts/Oracle.sol delete mode 100644 examples/uptime_sla/contracts/UptimeSLA.sol delete mode 100755 examples/uptime_sla/deploy delete mode 100755 examples/uptime_sla/get_uptime.js delete mode 100644 examples/uptime_sla/migrations/1_initial_migration.js delete mode 100644 examples/uptime_sla/migrations/2_link_token.js delete mode 100644 examples/uptime_sla/migrations/3_oracle.js delete mode 100644 examples/uptime_sla/migrations/4_uptime_sla.js delete mode 100644 examples/uptime_sla/package.json delete mode 100755 examples/uptime_sla/send_sla_transaction.js delete mode 100644 examples/uptime_sla/test/UptimeSLA_test.js delete mode 100644 examples/uptime_sla/test/support/helpers.js delete mode 100644 examples/uptime_sla/truffle.js delete mode 100755 tools/bin/echo_server diff --git a/.solhintignore b/.solhintignore index a8c1b38254b..14325ce6534 100644 --- a/.solhintignore +++ b/.solhintignore @@ -1,7 +1,4 @@ node_modules -examples/echo_server/node_modules -examples/uptime_sla/node_modules -examples/twilio_sms/node_modules examples/testnet/node_modules examples/testnet/contracts/TestnetConsumer.sol examples/testnet/contracts/Oracle.sol diff --git a/README.md b/README.md index c53069602ab..c7a03969e81 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,7 @@ and will go on to form the basis for Chainlink's [decentralized oracle network]( Further development of the Chainlink Node and Chainlink Network will happen here, if you are interested in contributing please see our [contribution guidelines](./docs/CONTRIBUTING.md). The current node supports: + - easy connectivity of on-chain contracts to any off-chain computation or API - multiple methods for scheduling both on-chain and off-chain computation for a user's smart contract - automatic gas price bumping to prevent stuck transactions, assuring your data is delivered in a timely manner @@ -46,16 +47,21 @@ Ethereum node versions currently tested and supported: **NOTE**: By default, chainlink will run in TLS mode. For local development you can either disable this by setting CHAINLINK_DEV to true, or generate self signed certificates using `tools/bin/self-signed-certs` or [manually](https://github.com/smartcontractkit/chainlink/wiki/Creating-Self-Signed-Certificates). To start your Chainlink node, simply run: + ```bash $ chainlink local node ``` + By default this will start on port 6688, where it exposes a [REST API](https://github.com/smartcontractkit/chainlink/wiki/REST-API). Once your node has started, you can view your current jobs with: + ```bash $ chainlink jobspecs -```` +``` + View details of a specific job with: + ```bash $ chainlink show $JOB_ID ``` @@ -72,20 +78,16 @@ You can configure your node's behavior by setting environment variables which ca This project contains several sub-projects, some with their own documentation. - - [evm](/evm) - smart contract-related resources - - [box](/evm/box) - [Chainlink Truffle box](https://www.trufflesuite.com/blog/using-truffle-to-interact-with-chainlink-smart-contracts) - - [v0.5](/evm/v0.5) - Chainlink using Solidity v0.5.0 - - [examples](/examples) - collection of example Chainlink integrations - - [echo_server](/examples/echo_server) - demonstrates using Chainlink to monitor incoming Ethereum logs and report them as JSON - - [testnet](/examples/testnet) - guide to creating, deploying and using Chainlinked smart contracts - - [twilio_sms](/examples/twilio_sms) - send a text message when a smart contract receives payment - - [uptime_sla](/examples/uptime_sla) - example SLA that uses ChainLink to determine the release of payment - - [explorer](/explorer) - [Chainlink Explorer](https://explorer.chain.link/) - - [integration/forks](/integration/forks) - integration test for [ommers](https://ethereum.stackexchange.com/a/46/19503) and [re-orgs](https://en.bitcoin.it/wiki/Chain_Reorganization) - - [sgx](/sgx) - experimental, optional module that can be loaded into Chainlink to do processing within an [SGX](https://software.intel.com/en-us/sgx) enclave - - [styleguide](/styleguide) - Chainlink style guide - - [tools](/tools) - Chainlink tools - +- [evm](/evm) - smart contract-related resources + - [box](/evm/box) - [Chainlink Truffle box](https://www.trufflesuite.com/blog/using-truffle-to-interact-with-chainlink-smart-contracts) + - [v0.5](/evm/v0.5) - Chainlink using Solidity v0.5.0 +- [examples](/examples) - collection of example Chainlink integrations + - [testnet](/examples/testnet) - guide to creating, deploying and using Chainlinked smart contracts +- [explorer](/explorer) - [Chainlink Explorer](https://explorer.chain.link/) +- [integration/forks](/integration/forks) - integration test for [ommers](https://ethereum.stackexchange.com/a/46/19503) and [re-orgs](https://en.bitcoin.it/wiki/Chain_Reorganization) +- [sgx](/sgx) - experimental, optional module that can be loaded into Chainlink to do processing within an [SGX](https://software.intel.com/en-us/sgx) enclave +- [styleguide](/styleguide) - Chainlink style guide +- [tools](/tools) - Chainlink tools ## External Adapters @@ -94,7 +96,6 @@ A Chainlink node communicates with external adapters via a simple REST API. For more information on creating and using external adapters, please see our [external adapters page](https://github.com/smartcontractkit/chainlink/wiki/External-Adapters). - ## Development Setup For the latest information on setting up a development environment, see the [guide here](https://github.com/smartcontractkit/chainlink/wiki/Development-Setup-Guide). @@ -106,6 +107,7 @@ $ go build -o chainlink ./core/ ``` - Run the binary: + ```bash $ ./chainlink ``` @@ -120,14 +122,18 @@ $ go test ./... 1. [Install Yarn](https://yarnpkg.com/lang/en/docs/install) 2. Install the dependencies: + ```bash $ cd evm $ yarn install ``` + 3. Run tests: + ```bash $ yarn run test-sol ``` + ### Development Tips For more tips on how to build and test Chainlink, see our [development tips page](https://github.com/smartcontractkit/chainlink/wiki/Development-Tips). diff --git a/examples/echo_server/.eslintignore b/examples/echo_server/.eslintignore deleted file mode 100644 index 2dc187a55fb..00000000000 --- a/examples/echo_server/.eslintignore +++ /dev/null @@ -1,2 +0,0 @@ -contracts -node_modules \ No newline at end of file diff --git a/examples/echo_server/.eslintrc.js b/examples/echo_server/.eslintrc.js deleted file mode 100644 index d8c6c7ba920..00000000000 --- a/examples/echo_server/.eslintrc.js +++ /dev/null @@ -1,7 +0,0 @@ -module.exports = { - root: true, - extends: [ - '@chainlink/eslint-config/node', - '@chainlink/eslint-config/truffle', - ], -} diff --git a/examples/echo_server/.prettierignore b/examples/echo_server/.prettierignore deleted file mode 100644 index ff685c12273..00000000000 --- a/examples/echo_server/.prettierignore +++ /dev/null @@ -1,6 +0,0 @@ -.prettierignore -.gitignore -.eslintignore -**/*.sol -screenshot.jpg -create_ethlog_job \ No newline at end of file diff --git a/examples/echo_server/README.md b/examples/echo_server/README.md deleted file mode 100644 index 229003ed0d3..00000000000 --- a/examples/echo_server/README.md +++ /dev/null @@ -1,45 +0,0 @@ -# Echo Server - -Using Chainlink (CL), this application simply echos incoming ethereum logs -as JSON, listened to by a CL job. It is intended to demonstrate the the first -step to bridging on chain to off chain activity. - -![Log Echo Server](screenshot.jpg?raw=true 'Log Echo Server') - -## Configure and run [Chainlink development environment](../README.md) - -## Run EthLog (Raw Ethereum Logs) - -Uses an `ethlog` initiator to echo all log events. An `ethlog` initiator starts -a job anytime a log event occurs. It can optionally be filtered by an `address`. - -1. Complete the [Run Chainlink Development Environment](../README.md#run-chainlink-development-environment) steps. -2. `./create_ethlog_job` to create the Chainlink (CL) job -3. `yarn install` -4. `node echo.js` -5. `yarn truffle migrate` in another window -6. `node send_ethlog_transaction.js` -7. Wait for log to show up in echo server - -## Run RunLog (Chainlink Specific Ethereum Logs) - -Uses a `runlog` initiator to echo Chainlink log events with the matching job id. - -1. Complete the [Run Chainlink Development Environment](../README.md#run-chainlink-development-environment) steps. -2. `yarn install` -3. `node echo.js` -4. `yarn truffle migrate` in another window -5. `node send_runlog_transaction.js` -6. Wait for log to show up in echo server -7. Investigate migrations/5_run_log.js for insight - -## Further Reading - -Please see the other examples in the repo, and take care to -identify the difference between an `HttpPost` task and a bridge to an external -adapter. The latter allows an asynchronous response from a service to -then potentially write back to the chain. - -## Development - -To run the tests, call `./node_modules/.bin/truffle --network=test test` diff --git a/examples/echo_server/broadcast_logs_job.json b/examples/echo_server/broadcast_logs_job.json deleted file mode 100644 index 2a15e3e50cc..00000000000 --- a/examples/echo_server/broadcast_logs_job.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "_comment": "An ethlog with no address listens to all addresses.", - "initiators": [{ "type": "ethlog" }], - "tasks": [ - { "type": "HttpPost", "params": { "post": "http://localhost:6690" } } - ] -} diff --git a/examples/echo_server/clmigration.js b/examples/echo_server/clmigration.js deleted file mode 100644 index f96d66b5703..00000000000 --- a/examples/echo_server/clmigration.js +++ /dev/null @@ -1,19 +0,0 @@ -// clmigration provides two key helpers for Chainlink development: -// 1. wraps migrations that to be skipped in the test environment, since we -// recreate every contract beforeEach test, and hit other APIs in our migration process. -// 2. Prepare plumbing for correct async/await behavior, -// in spite of https://github.com/trufflesuite/truffle/issues/501 - -module.exports = function(callback) { - return function(deployer, network) { - if (network === 'test') { - console.log('===== SKIPPING MIGRATIONS IN TEST ENVIRONMENT =====') - } else { - deployer - .then(async () => { - return callback(deployer, network) - }) - .catch(console.log) - } - } -} diff --git a/examples/echo_server/contracts/EthLog.sol b/examples/echo_server/contracts/EthLog.sol deleted file mode 100644 index 929383b58f8..00000000000 --- a/examples/echo_server/contracts/EthLog.sol +++ /dev/null @@ -1,9 +0,0 @@ -pragma solidity 0.4.24; - -contract EthLog { - event LogEvent(bytes32 indexed jobId); - - function logEvent() public { - emit LogEvent("hello_chainlink"); - } -} diff --git a/examples/echo_server/contracts/LinkToken.sol b/examples/echo_server/contracts/LinkToken.sol deleted file mode 100644 index e67ad4037c4..00000000000 --- a/examples/echo_server/contracts/LinkToken.sol +++ /dev/null @@ -1,3 +0,0 @@ -pragma solidity 0.4.24; - -import "link_token/contracts/LinkToken.sol"; diff --git a/examples/echo_server/contracts/Migrations.sol b/examples/echo_server/contracts/Migrations.sol deleted file mode 100644 index cffe8b95161..00000000000 --- a/examples/echo_server/contracts/Migrations.sol +++ /dev/null @@ -1,23 +0,0 @@ -pragma solidity 0.4.24; - -contract Migrations { - address public owner; - uint public last_completed_migration; - - modifier restricted() { - if (msg.sender == owner) _; - } - - constructor() public { - owner = msg.sender; - } - - function setCompleted(uint completed) public restricted { - last_completed_migration = completed; - } - - function upgrade(address new_address) public restricted { - Migrations upgraded = Migrations(new_address); - upgraded.setCompleted(last_completed_migration); - } -} diff --git a/examples/echo_server/contracts/Oracle.sol b/examples/echo_server/contracts/Oracle.sol deleted file mode 100644 index 8b00e54e486..00000000000 --- a/examples/echo_server/contracts/Oracle.sol +++ /dev/null @@ -1,3 +0,0 @@ -pragma solidity 0.4.24; - -import "chainlink/contracts/Oracle.sol"; \ No newline at end of file diff --git a/examples/echo_server/contracts/RunLog.sol b/examples/echo_server/contracts/RunLog.sol deleted file mode 100644 index 7799dfda75c..00000000000 --- a/examples/echo_server/contracts/RunLog.sol +++ /dev/null @@ -1,27 +0,0 @@ -pragma solidity 0.4.24; - -import "chainlink/contracts/Chainlinked.sol"; - -contract RunLog is Chainlinked { - uint256 constant private ORACLE_PAYMENT = 1 * LINK; - - bytes32 private jobId; - - constructor(address _link, address _oracle, bytes32 _jobId) public { - setLinkToken(_link); - setOracle(_oracle); - jobId = _jobId; - } - - function request() public { - Chainlink.Request memory req = newRequest(jobId, this, this.fulfill.selector); - req.add("msg", "hello_chainlink"); - chainlinkRequest(req, ORACLE_PAYMENT); - } - - function fulfill(bytes32 _externalId, bytes32 _data) - public - recordChainlinkFulfillment(_externalId) - {} // solhint-disable-line no-empty-blocks - -} diff --git a/examples/echo_server/create_ethlog_job b/examples/echo_server/create_ethlog_job deleted file mode 100755 index b6279c3bb3f..00000000000 --- a/examples/echo_server/create_ethlog_job +++ /dev/null @@ -1,16 +0,0 @@ -#!/bin/bash - -mkdir tmp - -set -e - -curl -c tmp/cookiefile \ - -d '{"email":"notreal@fakeemail.ch", "password":"twochains"}' \ - -X POST -H 'Content-Type: application/json' \ - http://localhost:6688/sessions \ - >/dev/null - -curl -sS -X POST -H 'Content-Type: application/json' \ - -b tmp/cookiefile \ - -d @./broadcast_logs_job.json http://localhost:6688/v2/specs - diff --git a/examples/echo_server/echo.js b/examples/echo_server/echo.js deleted file mode 100755 index de1fd9bddd0..00000000000 --- a/examples/echo_server/echo.js +++ /dev/null @@ -1,28 +0,0 @@ -#!/usr/bin/env node -/* eslint-disable @typescript-eslint/no-var-requires */ - -let echoes = 0 -const express = require('express') -const bodyParser = require('body-parser') -const app = express() -let PORT = process.argv[2] -app.use(bodyParser.json()) - -app.get('/count', function(req, res) { - res.json(echoes) -}) - -app.all('*', function(req, res) { - echoes += 1 - console.log({ headers: req.headers, body: req.body }) - res.json({ headers: req.headers, body: req.body }) -}) - -if (isNaN(parseFloat(PORT))) { - console.log('defaulting to 6690') - PORT = 6690 -} - -app.listen(PORT, function() { - console.log('listening on port ' + PORT) -}) diff --git a/examples/echo_server/migrations/1_initial_migration.js b/examples/echo_server/migrations/1_initial_migration.js deleted file mode 100644 index a7a4fdbc824..00000000000 --- a/examples/echo_server/migrations/1_initial_migration.js +++ /dev/null @@ -1,5 +0,0 @@ -const Migrations = artifacts.require('Migrations') - -module.exports = function(deployer) { - deployer.deploy(Migrations) -} diff --git a/examples/echo_server/migrations/2_eth_log.js b/examples/echo_server/migrations/2_eth_log.js deleted file mode 100644 index dbe0c8e3938..00000000000 --- a/examples/echo_server/migrations/2_eth_log.js +++ /dev/null @@ -1,5 +0,0 @@ -const EthLog = artifacts.require('EthLog') - -module.exports = function(deployer) { - deployer.deploy(EthLog) -} diff --git a/examples/echo_server/migrations/3_link_token.js b/examples/echo_server/migrations/3_link_token.js deleted file mode 100644 index 703e5033e02..00000000000 --- a/examples/echo_server/migrations/3_link_token.js +++ /dev/null @@ -1,5 +0,0 @@ -const LinkToken = artifacts.require('LinkToken') - -module.exports = function(deployer) { - deployer.deploy(LinkToken) -} diff --git a/examples/echo_server/migrations/4_chainlink_oracle.js b/examples/echo_server/migrations/4_chainlink_oracle.js deleted file mode 100644 index 63f53fc82d6..00000000000 --- a/examples/echo_server/migrations/4_chainlink_oracle.js +++ /dev/null @@ -1,6 +0,0 @@ -const LinkToken = artifacts.require('LinkToken') -const Oracle = artifacts.require('Oracle') - -module.exports = function(deployer) { - deployer.deploy(Oracle, LinkToken.address) -} diff --git a/examples/echo_server/migrations/5_run_log.js b/examples/echo_server/migrations/5_run_log.js deleted file mode 100644 index 7b4ad89b7ca..00000000000 --- a/examples/echo_server/migrations/5_run_log.js +++ /dev/null @@ -1,28 +0,0 @@ -/* eslint-disable @typescript-eslint/no-var-requires */ - -const clmigration = require('../clmigration.js') -const request = require('request-promise').defaults({ jar: true }) -const LinkToken = artifacts.require('LinkToken') -const Oracle = artifacts.require('Oracle') -const RunLog = artifacts.require('RunLog') - -const sessionsUrl = 'http://localhost:6688/sessions' -const specsUrl = 'http://localhost:6688/v2/specs' -const credentials = { email: 'notreal@fakeemail.ch', password: 'twochains' } -const job = { - _comment: - 'A runlog has a jobid baked into the contract so chainlink knows which job to run.', - initiators: [{ type: 'runlog' }], - tasks: [{ type: 'HttpPost', params: { url: 'http://localhost:6690' } }], -} - -module.exports = clmigration(async function(truffleDeployer) { - await request.post(sessionsUrl, { json: credentials }) - const body = await request.post(specsUrl, { json: job }) - console.log(`Deploying Consumer Contract with JobID ${body.data.id}`) - let jobid = body.data.id - if (jobid && jobid.slice(0, 2) != '0x') { - jobid = `0x${jobid}` // hack to prefix 0x to satisfy bytes32 requirement - } - await truffleDeployer.deploy(RunLog, LinkToken.address, Oracle.address, jobid) -}) diff --git a/examples/echo_server/migrations/6_transfer_link.js b/examples/echo_server/migrations/6_transfer_link.js deleted file mode 100644 index 046af0335b3..00000000000 --- a/examples/echo_server/migrations/6_transfer_link.js +++ /dev/null @@ -1,20 +0,0 @@ -/* eslint-disable @typescript-eslint/no-var-requires */ - -const clmigration = require('../clmigration.js') -const LinkToken = artifacts.require('LinkToken') -const RunLog = artifacts.require('RunLog') -const devnetAddress = '0x9CA9d2D5E04012C9Ed24C0e513C9bfAa4A2dD77f' - -module.exports = clmigration(function() { - LinkToken.deployed().then(async function(linkInstance) { - await RunLog.deployed() - .then(async function(runLogInstance) { - await linkInstance.transfer( - runLogInstance.address, - web3.utils.toWei('1000'), - ) - await linkInstance.transfer(devnetAddress, web3.utils.toWei('1000')) - }) - .catch(console.log) - }) -}) diff --git a/examples/echo_server/package.json b/examples/echo_server/package.json deleted file mode 100644 index 94feb92eea3..00000000000 --- a/examples/echo_server/package.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "name": "@chainlink/example-echo-server", - "version": "0.6.0", - "license": "MIT", - "private": true, - "scripts": { - "start": "node echo.js", - "depcheck": "echo \"@chainlink/example-echo-server\" && depcheck --ignore-dirs=contracts || true", - "eslint": "eslint --ext .js,.ts .", - "solhint": "solhint ./contracts/**/*.sol", - "lint": "yarn eslint && yarn solhint", - "format": "prettier --write \"**/*\"", - "setup": "echo \"No setup required for @chainlink/example-echo-server\"", - "test": "truffle test" - }, - "dependencies": { - "@babel/polyfill": "^7.2.5", - "@babel/register": "^7.6.2", - "body-parser": "^1.18.3", - "cbor": "^5.0.1", - "chainlink": "^0.6.5", - "express": "^4.16.2", - "link_token": "^1.0.6", - "request-promise": "^4.2.2", - "truffle": "^5.0.25", - "web3": "^1.0.0-beta.55" - }, - "devDependencies": { - "@chainlink/eslint-config": "0.0.1", - "@chainlink/prettier-config": "0.0.1", - "depcheck": "^0.8.3", - "eslint": "^6.3.0", - "prettier": "^1.18.2", - "solc": "0.4.24", - "solhint": "^2.1.0" - }, - "prettier": "@chainlink/prettier-config" -} diff --git a/examples/echo_server/screenshot.jpg b/examples/echo_server/screenshot.jpg deleted file mode 100644 index 7232ff3a5cf66ff06effe2b5754df2de4f848c36..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 174588 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�_r+H3uhcj%q+ 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 diff --git a/examples/echo_server/send_ethlog_transaction.js b/examples/echo_server/send_ethlog_transaction.js deleted file mode 100755 index 1cdb5371873..00000000000 --- a/examples/echo_server/send_ethlog_transaction.js +++ /dev/null @@ -1,29 +0,0 @@ -#!/usr/bin/env node -/* eslint-disable @typescript-eslint/no-var-requires */ - -const Web3 = require('web3') -const path = require('path') -const EthLogJSON = require(path.join(__dirname, 'build/contracts/EthLog.json')) - -const provider = new Web3.providers.HttpProvider('http://localhost:18545') -const devnetAddress = '0x9CA9d2D5E04012C9Ed24C0e513C9bfAa4A2dD77f' - -const web3 = new Web3(provider) -const networkId = Object.keys(EthLogJSON.networks)[0] -const wc3 = new web3.eth.Contract( - EthLogJSON.abi, - EthLogJSON.networks[networkId].address, -) - -wc3.methods.logEvent().send( - { - from: devnetAddress, - }, - (error, transactionHash) => { - if (error) { - console.error('encountered error: ', error, transactionHash) - } else { - console.log('sent tx: ', transactionHash) - } - }, -) diff --git a/examples/echo_server/send_runlog_transaction.js b/examples/echo_server/send_runlog_transaction.js deleted file mode 100755 index 0daa6435ada..00000000000 --- a/examples/echo_server/send_runlog_transaction.js +++ /dev/null @@ -1,29 +0,0 @@ -#!/usr/bin/env node -/* eslint-disable @typescript-eslint/no-var-requires */ - -const Web3 = require('web3') -const path = require('path') -const RunLogJSON = require(path.join(__dirname, 'build/contracts/RunLog.json')) - -const provider = new Web3.providers.HttpProvider('http://localhost:18545') -const devnetAddress = '0x9CA9d2D5E04012C9Ed24C0e513C9bfAa4A2dD77f' -const web3 = new Web3(provider) -const networkId = Object.keys(RunLogJSON.networks)[0] -const wc3 = new web3.eth.Contract( - RunLogJSON.abi, - RunLogJSON.networks[networkId].address, -) - -wc3.methods.request().send( - { - from: devnetAddress, - gas: 200000, - }, - (error, transactionHash) => { - if (error) { - console.error('encountered error: ', error, transactionHash) - } else { - console.log('sent tx: ', transactionHash) - } - }, -) diff --git a/examples/echo_server/test/RunLog_test.js b/examples/echo_server/test/RunLog_test.js deleted file mode 100644 index 0588eb0a966..00000000000 --- a/examples/echo_server/test/RunLog_test.js +++ /dev/null @@ -1,21 +0,0 @@ -const LinkToken = artifacts.require('LinkToken') -const Oracle = artifacts.require('Oracle') -const RunLog = artifacts.require('RunLog') - -contract('RunLog', () => { - const arbitraryJobID = - '0x0000000000000000000000000000000000000000000000000000000000000001' - let link, logger, oc - - beforeEach(async () => { - link = await LinkToken.new() - oc = await Oracle.new(link.address) - logger = await RunLog.new(link.address, oc.address, arbitraryJobID) - await link.transfer(logger.address, web3.utils.toWei('1')) - }) - - it('has a limited public interface', async () => { - const tx = await logger.request() - assert.equal(4, tx.receipt.rawLogs.length) - }) -}) diff --git a/examples/echo_server/truffle.js b/examples/echo_server/truffle.js deleted file mode 100644 index 96f3f191a6a..00000000000 --- a/examples/echo_server/truffle.js +++ /dev/null @@ -1,19 +0,0 @@ -/* eslint-disable @typescript-eslint/camelcase */ -require('@babel/register') -require('@babel/polyfill') - -module.exports = { - networks: { - cldev: { - host: '127.0.0.1', - port: 18545, - network_id: '*', - gas: 4700000, - }, - }, - compilers: { - solc: { - version: '0.4.24', - }, - }, -} diff --git a/examples/twilio_sms/.eslintignore b/examples/twilio_sms/.eslintignore deleted file mode 100644 index c6bf72cf137..00000000000 --- a/examples/twilio_sms/.eslintignore +++ /dev/null @@ -1,2 +0,0 @@ -node_modules -contracts \ No newline at end of file diff --git a/examples/twilio_sms/.eslintrc.js b/examples/twilio_sms/.eslintrc.js deleted file mode 100644 index d8c6c7ba920..00000000000 --- a/examples/twilio_sms/.eslintrc.js +++ /dev/null @@ -1,7 +0,0 @@ -module.exports = { - root: true, - extends: [ - '@chainlink/eslint-config/node', - '@chainlink/eslint-config/truffle', - ], -} diff --git a/examples/twilio_sms/.prettierignore b/examples/twilio_sms/.prettierignore deleted file mode 100644 index 6d316116044..00000000000 --- a/examples/twilio_sms/.prettierignore +++ /dev/null @@ -1,5 +0,0 @@ -.prettierignore -.gitignore -**/*.sol -HitMeOnMyBeeper.jpg -.eslintignore diff --git a/examples/twilio_sms/HitMeOnMyBeeper.jpg b/examples/twilio_sms/HitMeOnMyBeeper.jpg deleted file mode 100644 index 876df977a666b0c9ad7002b66e93cdaeafff06ae..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 25645 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 R{1ft@zWk5 ` - - i.e. `./twilio.js AC97fade171cxxxxxxxxxxxxxxxxxxxxxx ffab4f5ecc65acxxxxxxefe99xxxxx "+1 786-555-5555" 3055555555`. - -## Create Chainlink Job and Smart Contract (where you will send the money) - -- `truffle migrate`, remember the contract address (0x...) of the migration above. -- `./create_twilio_job_for 0x...` pass contract address as argument - -## Send Money - -- `./send_money_to 0x...` pass contract address as argument - -## Celebrate - -[![Hit Me On My Beeper](HitMeOnMyBeeper.jpg?raw=true 'Hit Me On My Beeper')](https://www.youtube.com/watch?v=-4Wu-zSndlw&t=13s) diff --git a/examples/twilio_sms/contracts/GetMoney.sol b/examples/twilio_sms/contracts/GetMoney.sol deleted file mode 100644 index 6bde57e710d..00000000000 --- a/examples/twilio_sms/contracts/GetMoney.sol +++ /dev/null @@ -1,11 +0,0 @@ -pragma solidity 0.4.24; - -contract GetMoney { - address[] public payees; - event LogMoney(uint256 indexed amount); - - function receive() public payable { - payees.push(msg.sender); - emit LogMoney(msg.value); - } -} diff --git a/examples/twilio_sms/contracts/Migrations.sol b/examples/twilio_sms/contracts/Migrations.sol deleted file mode 100644 index cffe8b95161..00000000000 --- a/examples/twilio_sms/contracts/Migrations.sol +++ /dev/null @@ -1,23 +0,0 @@ -pragma solidity 0.4.24; - -contract Migrations { - address public owner; - uint public last_completed_migration; - - modifier restricted() { - if (msg.sender == owner) _; - } - - constructor() public { - owner = msg.sender; - } - - function setCompleted(uint completed) public restricted { - last_completed_migration = completed; - } - - function upgrade(address new_address) public restricted { - Migrations upgraded = Migrations(new_address); - upgraded.setCompleted(last_completed_migration); - } -} diff --git a/examples/twilio_sms/create_twilio_job_for b/examples/twilio_sms/create_twilio_job_for deleted file mode 100755 index 1910413a40b..00000000000 --- a/examples/twilio_sms/create_twilio_job_for +++ /dev/null @@ -1,27 +0,0 @@ -#!/usr/bin/env node - -if (process.argv.length <= 2) { - console.log('Usage: ' + __filename + ' ') - process.exit(-1) -} - -const job = { - _comment: 'An ethlog with a specific address only listens to that address.', - initiators: [{ type: 'ethlog', address: process.argv[2] }], - tasks: [{ type: 'HttpPost', params: { post: 'http://localhost:6691' } }], -} - -const request = require('request').defaults({ jar: true }) -let sessionsUrl = 'http://localhost:6688/sessions' -let credentials = { email: 'notreal@fakeemail.ch', password: 'twochains' } -request.post(sessionsUrl, { json: credentials }) -request.post( - { - url: 'http://localhost:6688/v2/specs', - body: job, - json: true, - }, - function(err, response, body) { - console.log(body) - }, -) diff --git a/examples/twilio_sms/migrations/1_initial_migration.js b/examples/twilio_sms/migrations/1_initial_migration.js deleted file mode 100644 index a01c6d6fbbd..00000000000 --- a/examples/twilio_sms/migrations/1_initial_migration.js +++ /dev/null @@ -1,5 +0,0 @@ -const Migrations = artifacts.require('./Migrations.sol') - -module.exports = function(deployer) { - deployer.deploy(Migrations) -} diff --git a/examples/twilio_sms/migrations/2_get_money.js b/examples/twilio_sms/migrations/2_get_money.js deleted file mode 100644 index 463efeee99a..00000000000 --- a/examples/twilio_sms/migrations/2_get_money.js +++ /dev/null @@ -1,5 +0,0 @@ -const GetMoney = artifacts.require('./GetMoney.sol') - -module.exports = function(deployer) { - deployer.deploy(GetMoney) -} diff --git a/examples/twilio_sms/package.json b/examples/twilio_sms/package.json deleted file mode 100644 index 23175cb02d5..00000000000 --- a/examples/twilio_sms/package.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "private": true, - "name": "@chainlink/example-twilio-sms", - "version": "0.6.0", - "license": "MIT", - "scripts": { - "depcheck": "echo \"@chainlink/example-twilio-sms\" && depcheck --ignore-dirs=contracts || true", - "eslint": "eslint --ext .js,.ts .", - "solhint": "solhint ./contracts/**/*.sol", - "lint": "yarn eslint && yarn solhint", - "format": "prettier --write \"**/*\"", - "setup": "echo \"No setup required for @chainlink/example-twillio-sms\"", - "test": "truffle test" - }, - "devDependencies": { - "@chainlink/eslint-config": "0.0.1", - "@chainlink/prettier-config": "0.0.1", - "body-parser": "^1.18.3", - "depcheck": "^0.8.3", - "eslint": "^6.3.0", - "express": "^4.16.2", - "prettier": "^1.18.2", - "request": "^2.83.0", - "solhint": "^2.1.0", - "truffle": "^5.0.25", - "truffle-contract": "^4.0.31", - "twilio": "^3.34.0" - }, - "prettier": "@chainlink/prettier-config" -} diff --git a/examples/twilio_sms/send_money_to b/examples/twilio_sms/send_money_to deleted file mode 100755 index f9bb29081b8..00000000000 --- a/examples/twilio_sms/send_money_to +++ /dev/null @@ -1,22 +0,0 @@ -#!/usr/bin/env node - -if (process.argv.length <= 2) { - console.log('Usage: ' + __filename + ' ') - process.exit(-1) -} - -const web3 = require('web3'), - contract = require('truffle-contract'), - path = require('path') -GetMoneyJSON = require(path.join(__dirname, 'build/contracts/GetMoney.json')) - -var provider = new web3.providers.HttpProvider('http://localhost:18545') -var GetMoney = contract(GetMoneyJSON) -GetMoney.setProvider(provider) -var devnetAddress = '0x9CA9d2D5E04012C9Ed24C0e513C9bfAa4A2dD77f' - -GetMoney.deployed() - .then(function(instance) { - return instance.receive({ value: 305, from: devnetAddress }) - }) - .then(console.log, console.log) diff --git a/examples/twilio_sms/truffle.js b/examples/twilio_sms/truffle.js deleted file mode 100644 index 0d28cffea8d..00000000000 --- a/examples/twilio_sms/truffle.js +++ /dev/null @@ -1,12 +0,0 @@ -/* eslint-disable @typescript-eslint/camelcase */ - -module.exports = { - networks: { - development: { - host: '127.0.0.1', - port: 18545, - network_id: '*', - gas: 4700000, - }, - }, -} diff --git a/examples/twilio_sms/twilio.js b/examples/twilio_sms/twilio.js deleted file mode 100755 index 999a8b121ca..00000000000 --- a/examples/twilio_sms/twilio.js +++ /dev/null @@ -1,44 +0,0 @@ -#!/usr/bin/env node -/* eslint-disable @typescript-eslint/no-var-requires */ - -if (process.argv.length <= 3) { - console.log( - `Usage: ${__filename} `, - ) - process.exit(-1) -} - -const express = require('express') -const bodyParser = require('body-parser') -const app = express() -const PORT = 6691 - -const [accountSid, authToken, from, to] = process.argv.slice(2) -const client = require('twilio')(accountSid, authToken) - -app.use(bodyParser.json()) -app.all('*', function(req, res) { - const log = req.body - const amount = parseInt(log.topics[1], 16) - const message = - 'Hello Chainlink! You just sent received ' + - amount + - ' wei at ' + - log.address - console.log( - 'Sending text from ' + from + ' to ' + to + ' with message: ' + message, - ) - client.messages - .create({ - from: from, - to: to, - body: message, - }) - .then(null, console.log) - - res.json({ body: { status: 'ok' } }) -}) - -app.listen(PORT, function() { - console.log('listening on port ' + PORT + ' for incoming ether') -}) diff --git a/examples/uptime_sla/.babelrc b/examples/uptime_sla/.babelrc deleted file mode 100644 index ae031e09f66..00000000000 --- a/examples/uptime_sla/.babelrc +++ /dev/null @@ -1,8 +0,0 @@ -{ - "presets": ["@babel/preset-env"], - "plugins": [ - "@babel/plugin-proposal-export-namespace-from", - "@babel/plugin-proposal-throw-expressions", - "@babel/plugin-proposal-class-properties" - ] -} diff --git a/examples/uptime_sla/.eslintignore b/examples/uptime_sla/.eslintignore deleted file mode 100644 index c6bf72cf137..00000000000 --- a/examples/uptime_sla/.eslintignore +++ /dev/null @@ -1,2 +0,0 @@ -node_modules -contracts \ No newline at end of file diff --git a/examples/uptime_sla/.eslintrc.js b/examples/uptime_sla/.eslintrc.js deleted file mode 100644 index d8c6c7ba920..00000000000 --- a/examples/uptime_sla/.eslintrc.js +++ /dev/null @@ -1,7 +0,0 @@ -module.exports = { - root: true, - extends: [ - '@chainlink/eslint-config/node', - '@chainlink/eslint-config/truffle', - ], -} diff --git a/examples/uptime_sla/.node-xmlhttprequest-sync-78767 b/examples/uptime_sla/.node-xmlhttprequest-sync-78767 deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/examples/uptime_sla/.prettierignore b/examples/uptime_sla/.prettierignore deleted file mode 100644 index 2179226c66f..00000000000 --- a/examples/uptime_sla/.prettierignore +++ /dev/null @@ -1,6 +0,0 @@ -.node-xmlhttprequest-sync-78767 -**/*.sol -.prettierignore -.gitignore -deploy -.eslintignore diff --git a/examples/uptime_sla/README.md b/examples/uptime_sla/README.md deleted file mode 100644 index b3249a2d602..00000000000 --- a/examples/uptime_sla/README.md +++ /dev/null @@ -1,61 +0,0 @@ -# Uptime Service Level Agreement - -An example SLA that uses ChainLink to determine the release of payment. - -When the contract is deployed a client, service provider, and start time are specified. Additionally a deposit is made. The end of the contract is set to 30 days after the start time. - -After the contract is created anyone can request updates from the oracle for the contract. If the oracle reports that the uptime is below 99.99% then the deposit is released to the client. If the rate is still above 99.99% after the contract ends, and the deposit has not been released, the deposit is sent to the service provider. - -```solidity -function report(uint256 _requestId, uint256 _rate) - public - recordChainlinkFulfillment(_requestId) - { - if (_rate < uptimeThreshold) { - client.send(this.balance); - } else if (block.timestamp >= endAt) { - serviceProvider.send(this.balance); - } - } -``` - -# ChainLink - -Initiator: `runLog` - -Job Pipeline: `httpGet` => `jsonParse` => `multiply` => `ethUint256` => `ethTx` - -This contract displays ChainLinks ability to pull in data from outside data feeds and format it to be used by Ethereum contracts. - -A float value is pulled out of a nested JSON object and multiplied to a precision level that is useful for the contract. - -The ChainLink Job is configured to not take any specific URL or JSON path, so that this oracle and job can be reused for other APIs. Both `url` and `path` are passed into the oracle by the SLA contract, specifically which data point to use is passed into the contract: - -```solidity -function updateUptime(string _when) public { - Chainlink.Request memory req = newRequest(jobId, this, "report(uint256,uint256)"); - req.add("get", "https://status.heroku.com/api/ui/availabilities"); - string[] memory path = new string[](4); - path[0] = "data"; - path[1] = _when; //pick which data point in the array you want to examine - path[2] = "attributes"; - path[3] = "calculation"; - req.add("path", path); - chainlinkRequest(req, LINK(1)); -} -``` - -The API returns the percentage as a float, for example the current value is `0.999999178716033`. The `multiply` adapter takes that result and multiplies it by 1000, which [a parameter specified in the `times` field](https://github.com/smartcontractkit/hello_chainlink/blob/4b42f127ddeca6541ac2aba1803f458d0a3bf460/uptime_sla/http_json_x10000_job.json). The result is `9999`, allowing the contract to check for "four nines" of uptime. - -## Configure and run [Chainlink development environment](../README.md#run-chainlink-development-environment) - -## Run and update the Uptime SLA contract. - -1. `yarn install` -2. `./deploy` in another window -3. `./send_sla_transaction.js` to trigger an update to the SLA -4. `./get_uptime.js` get the latest uptime - -## Development - -To run the tests, call `./node_modules/.bin/truffle --network=test test` diff --git a/examples/uptime_sla/clmigration.js b/examples/uptime_sla/clmigration.js deleted file mode 100644 index f96d66b5703..00000000000 --- a/examples/uptime_sla/clmigration.js +++ /dev/null @@ -1,19 +0,0 @@ -// clmigration provides two key helpers for Chainlink development: -// 1. wraps migrations that to be skipped in the test environment, since we -// recreate every contract beforeEach test, and hit other APIs in our migration process. -// 2. Prepare plumbing for correct async/await behavior, -// in spite of https://github.com/trufflesuite/truffle/issues/501 - -module.exports = function(callback) { - return function(deployer, network) { - if (network === 'test') { - console.log('===== SKIPPING MIGRATIONS IN TEST ENVIRONMENT =====') - } else { - deployer - .then(async () => { - return callback(deployer, network) - }) - .catch(console.log) - } - } -} diff --git a/examples/uptime_sla/contracts/LinkToken.sol b/examples/uptime_sla/contracts/LinkToken.sol deleted file mode 100644 index e67ad4037c4..00000000000 --- a/examples/uptime_sla/contracts/LinkToken.sol +++ /dev/null @@ -1,3 +0,0 @@ -pragma solidity 0.4.24; - -import "link_token/contracts/LinkToken.sol"; diff --git a/examples/uptime_sla/contracts/Migrations.sol b/examples/uptime_sla/contracts/Migrations.sol deleted file mode 100644 index cffe8b95161..00000000000 --- a/examples/uptime_sla/contracts/Migrations.sol +++ /dev/null @@ -1,23 +0,0 @@ -pragma solidity 0.4.24; - -contract Migrations { - address public owner; - uint public last_completed_migration; - - modifier restricted() { - if (msg.sender == owner) _; - } - - constructor() public { - owner = msg.sender; - } - - function setCompleted(uint completed) public restricted { - last_completed_migration = completed; - } - - function upgrade(address new_address) public restricted { - Migrations upgraded = Migrations(new_address); - upgraded.setCompleted(last_completed_migration); - } -} diff --git a/examples/uptime_sla/contracts/Oracle.sol b/examples/uptime_sla/contracts/Oracle.sol deleted file mode 100644 index 8b00e54e486..00000000000 --- a/examples/uptime_sla/contracts/Oracle.sol +++ /dev/null @@ -1,3 +0,0 @@ -pragma solidity 0.4.24; - -import "chainlink/contracts/Oracle.sol"; \ No newline at end of file diff --git a/examples/uptime_sla/contracts/UptimeSLA.sol b/examples/uptime_sla/contracts/UptimeSLA.sol deleted file mode 100644 index e6c23f65194..00000000000 --- a/examples/uptime_sla/contracts/UptimeSLA.sol +++ /dev/null @@ -1,54 +0,0 @@ -pragma solidity 0.4.24; - -import "chainlink/contracts/Chainlinked.sol"; - -contract UptimeSLA is Chainlinked { - uint256 constant private ORACLE_PAYMENT = 1 * LINK; - uint256 constant public uptimeThreshold = 9999; // solhint-disable-line const-name-snakecase - bytes32 private jobId; - uint256 private endAt; - address private client; - address private serviceProvider; - uint256 public uptime; - - constructor( - address _client, - address _serviceProvider, - address _link, - address _oracle, - bytes32 _jobId - ) public payable { - client = _client; - serviceProvider = _serviceProvider; - endAt = block.timestamp.add(30 days); // solhint-disable-line not-rely-on-time - jobId = _jobId; - - setLinkToken(_link); - setOracle(_oracle); - } - - function updateUptime(string _when) public { - Chainlink.Request memory req = newRequest(jobId, this, this.report.selector); - req.add("get", "https://status.heroku.com/api/ui/availabilities"); - string[] memory path = new string[](4); - path[0] = "data"; - path[1] = _when; - path[2] = "attributes"; - path[3] = "calculation"; - req.addStringArray("path", path); - chainlinkRequest(req, ORACLE_PAYMENT); - } - - function report(bytes32 _externalId, uint256 _uptime) - public - recordChainlinkFulfillment(_externalId) - { - uptime = _uptime; - if (_uptime < uptimeThreshold) { - client.transfer(address(this).balance); - } else if (block.timestamp >= endAt) { // solhint-disable-line not-rely-on-time - serviceProvider.transfer(address(this).balance); - } - } - -} diff --git a/examples/uptime_sla/deploy b/examples/uptime_sla/deploy deleted file mode 100755 index dd3d792569c..00000000000 --- a/examples/uptime_sla/deploy +++ /dev/null @@ -1,3 +0,0 @@ -#!/usr/bin/env bash - -./node_modules/.bin/truffle migrate --compile-all --reset diff --git a/examples/uptime_sla/get_uptime.js b/examples/uptime_sla/get_uptime.js deleted file mode 100755 index 5509a483e0e..00000000000 --- a/examples/uptime_sla/get_uptime.js +++ /dev/null @@ -1,20 +0,0 @@ -#!/usr/bin/env node -/* eslint-disable @typescript-eslint/no-var-requires */ - -const Web3 = require('web3') -const contract = require('truffle-contract') -const path = require('path') -const UptimeSLAJSON = require(path.join( - __dirname, - 'build/contracts/UptimeSLA.json', -)) - -const provider = new Web3.providers.HttpProvider('http://localhost:18545') -const UptimeSLA = contract(UptimeSLAJSON) -UptimeSLA.setProvider(provider) - -UptimeSLA.deployed() - .then(function(instance) { - return instance.uptime.call() - }) - .then(console.log, console.log) diff --git a/examples/uptime_sla/migrations/1_initial_migration.js b/examples/uptime_sla/migrations/1_initial_migration.js deleted file mode 100644 index a7a4fdbc824..00000000000 --- a/examples/uptime_sla/migrations/1_initial_migration.js +++ /dev/null @@ -1,5 +0,0 @@ -const Migrations = artifacts.require('Migrations') - -module.exports = function(deployer) { - deployer.deploy(Migrations) -} diff --git a/examples/uptime_sla/migrations/2_link_token.js b/examples/uptime_sla/migrations/2_link_token.js deleted file mode 100644 index d36a9916a92..00000000000 --- a/examples/uptime_sla/migrations/2_link_token.js +++ /dev/null @@ -1,5 +0,0 @@ -const LINK = artifacts.require('LinkToken') - -module.exports = function(deployer) { - deployer.deploy(LINK) -} diff --git a/examples/uptime_sla/migrations/3_oracle.js b/examples/uptime_sla/migrations/3_oracle.js deleted file mode 100644 index ed309bc6aa1..00000000000 --- a/examples/uptime_sla/migrations/3_oracle.js +++ /dev/null @@ -1,6 +0,0 @@ -const LINK = artifacts.require('LinkToken') -const Oracle = artifacts.require('Oracle') - -module.exports = function(deployer) { - deployer.deploy(Oracle, LINK.address) -} diff --git a/examples/uptime_sla/migrations/4_uptime_sla.js b/examples/uptime_sla/migrations/4_uptime_sla.js deleted file mode 100644 index a229710fa8b..00000000000 --- a/examples/uptime_sla/migrations/4_uptime_sla.js +++ /dev/null @@ -1,45 +0,0 @@ -/* eslint-disable @typescript-eslint/no-var-requires */ - -const clmigration = require('../clmigration.js') -const request = require('request-promise').defaults({ jar: true }) -const UptimeSLA = artifacts.require('UptimeSLA') -const Oracle = artifacts.require('Oracle') -const LINK = artifacts.require('LinkToken') - -const sessionsUrl = 'http://localhost:6688/sessions' -const specsUrl = 'http://localhost:6688/v2/specs' -const credentials = { email: 'notreal@fakeemail.ch', password: 'twochains' } -const job = { - _comment: - 'GETs a number from JSON, multiplies by 10,000, and reports uint256', - initiators: [{ type: 'runlog' }], - tasks: [ - { type: 'httpGet' }, - { type: 'jsonParse' }, - { type: 'multiply', params: { times: 10000 } }, - { type: 'ethuint256' }, - { type: 'ethtx' }, - ], -} - -module.exports = clmigration(async function(truffleDeployer) { - const client = '0x542B68aE7029b7212A5223ec2867c6a94703BeE3' - const serviceProvider = '0xB16E8460cCd76aEC437ca74891D3D358EA7d1d88' - - await request.post(sessionsUrl, { json: credentials }) - const body = await request.post(specsUrl, { json: job }) - console.log(`Deploying UptimeSLA:`) - console.log(`\tjob: ${body.data.id}`) - console.log(`\tclient: ${client}`) - console.log(`\tservice provider: ${serviceProvider}`) - - await truffleDeployer.deploy( - UptimeSLA, - client, - serviceProvider, - LINK.address, - Oracle.address, - body.id, - { value: 1000000000 }, - ) -}) diff --git a/examples/uptime_sla/package.json b/examples/uptime_sla/package.json deleted file mode 100644 index c05233771c7..00000000000 --- a/examples/uptime_sla/package.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "name": "@chainlink/example-uptime-sla", - "version": "0.6.0", - "license": "MIT", - "private": true, - "scripts": { - "depcheck": "echo \"@chainlink/example-uptime-sla\" && depcheck --ignore-dirs=contracts || true", - "eslint": "eslint --ext .js,.ts .", - "solhint": "solhint ./contracts/**/*.sol", - "lint": "yarn eslint && yarn solhint", - "format": "prettier --write \"**/*\"", - "setup": "echo \"No setup required for @chainlink/example-uptime-sla\"", - "test": "truffle test" - }, - "devDependencies": { - "@chainlink/eslint-config": "0.0.1", - "@chainlink/prettier-config": "0.0.1", - "bignumber.js": "^9.0.0", - "bn.js": "^4.11.8", - "cbor": "^5.0.1", - "chainlink": "^0.6.5", - "depcheck": "^0.8.3", - "eslint": "^6.3.0", - "ethereumjs-abi": "^0.6.5", - "ethereumjs-util": "^5.1.5", - "fs": "^0.0.1-security", - "link_token": "^1.0.6", - "prettier": "^1.18.2", - "request-promise": "^4.2.2", - "solc": "0.4.24", - "solhint": "^2.1.0", - "truffle": "^5.0.25", - "truffle-contract": "^4.0.31", - "web3": "^1.0.0-beta.35" - }, - "prettier": "@chainlink/prettier-config" -} diff --git a/examples/uptime_sla/send_sla_transaction.js b/examples/uptime_sla/send_sla_transaction.js deleted file mode 100755 index 7d3682f10a2..00000000000 --- a/examples/uptime_sla/send_sla_transaction.js +++ /dev/null @@ -1,21 +0,0 @@ -#!/usr/bin/env node -/* eslint-disable @typescript-eslint/no-var-requires */ - -const Web3 = require('web3') -const contract = require('truffle-contract') -const path = require('path') -const UptimeSLAJSON = require(path.join( - __dirname, - 'build/contracts/UptimeSLA.json', -)) - -const provider = new Web3.providers.HttpProvider('http://localhost:18545') -const UptimeSLA = contract(UptimeSLAJSON) -UptimeSLA.setProvider(provider) -const devnetAddress = '0x9CA9d2D5E04012C9Ed24C0e513C9bfAa4A2dD77f' - -UptimeSLA.deployed() - .then(function(instance) { - return instance.updateUptime('0', { from: devnetAddress }) - }) - .then(console.log, console.log) diff --git a/examples/uptime_sla/test/UptimeSLA_test.js b/examples/uptime_sla/test/UptimeSLA_test.js deleted file mode 100644 index 249bc22c6af..00000000000 --- a/examples/uptime_sla/test/UptimeSLA_test.js +++ /dev/null @@ -1,151 +0,0 @@ -/* eslint-disable @typescript-eslint/no-var-requires */ - -const h = require('./support/helpers') -const LinkToken = artifacts.require('LinkToken') -const Oracle = artifacts.require('Oracle') -const SLA = artifacts.require('UptimeSLA') - -contract('UptimeSLA', accounts => { - const specId = '0x4c7b7ffb66b344fbaa64995af81e355a' - const deposit = 1000000000 - const oracleNode = accounts[1] - let link, oc, sla, client, serviceProvider, startAt - - beforeEach(async () => { - client = '0xf000000000000000000000000000000000000001' - serviceProvider = '0xf000000000000000000000000000000000000002' - link = await LinkToken.new() - oc = await Oracle.new(link.address, { from: oracleNode }) - sla = await SLA.new( - client, - serviceProvider, - link.address, - oc.address, - specId, - { - value: deposit, - }, - ) - link.transfer(sla.address, web3.utils.toWei('1', 'ether')) - startAt = await h.getLatestTimestamp() - }) - - describe('before updates', () => { - it('does not release money to anyone', async () => { - assert.equal(await web3.eth.getBalance(sla.address), deposit) - assert.equal(await web3.eth.getBalance(client), 0) - assert.equal(await web3.eth.getBalance(serviceProvider), 0) - }) - }) - - describe('#updateUptime', () => { - it('triggers a log event in the Oracle contract', async () => { - await sla.updateUptime('0') - - const events = await oc.getPastEvents('allEvents') - assert.equal(1, events.length) - assert.equal( - events[0].args.specId, - specId + '00000000000000000000000000000000', - ) - - const decoded = await h.decodeDietCBOR(events[0].args.data) - assert.deepEqual(decoded, { - get: 'https://status.heroku.com/api/ui/availabilities', - path: ['data', '0', 'attributes', 'calculation'], - }) - }) - }) - - describe('#fulfillOracleRequest', () => { - const response = - '0x00000000000000000000000000000000000000000000000000000000000f8c4c' - let request - - beforeEach(async () => { - const tx = await sla.updateUptime('0') - request = h.decodeRunRequest(tx.receipt.rawLogs[3]) - }) - - context('when the value is below 9999', async () => { - const response = - '0x000000000000000000000000000000000000000000000000000000000000270e' - - it('sends the deposit to the client', async () => { - await h.fulfillOracleRequest(oc, request, response, { - from: oracleNode, - }) - - assert.equal(await web3.eth.getBalance(sla.address), 0) - assert.equal(await web3.eth.getBalance(client), deposit) - assert.equal(await web3.eth.getBalance(serviceProvider), 0) - }) - }) - - context('when the value is 9999 or above', () => { - const response = - '0x000000000000000000000000000000000000000000000000000000000000270f' - let originalClientBalance - - beforeEach(async () => { - originalClientBalance = await web3.eth.getBalance(client) - }) - - it('does not move the money', async () => { - await h.fulfillOracleRequest(oc, request, response, { - from: oracleNode, - }) - - h.assertBigNum(await web3.eth.getBalance(sla.address), deposit) - h.assertBigNum(await web3.eth.getBalance(client), originalClientBalance) - h.assertBigNum(await web3.eth.getBalance(serviceProvider), 0) - }) - - context('and a month has passed', () => { - beforeEach(async () => { - await h.fastForwardTo(startAt + h.days(30)) - }) - - it('gives the money back to the service provider', async () => { - await h.fulfillOracleRequest(oc, request, response, { - from: oracleNode, - }) - - h.assertBigNum(await web3.eth.getBalance(sla.address), 0) - h.assertBigNum( - await web3.eth.getBalance(client), - originalClientBalance, - ) - h.assertBigNum(await web3.eth.getBalance(serviceProvider), deposit) - }) - }) - }) - - context('when the consumer does not recognize the request ID', () => { - beforeEach(async () => { - const fid = h.functionSelector('report(uint256,bytes32)') - const args = h.requestDataBytes(specId, sla.address, fid, 'xid', 'foo') - const tx = await link.transferAndCall(oc.address, 0, args) - request = h.decodeRunRequest(tx.receipt.rawLogs[2]) - }) - - it('does not accept the data provided', async () => { - const originalUptime = await sla.uptime() - await h.fulfillOracleRequest(oc, request, response, { - from: oracleNode, - }) - const newUptime = await sla.uptime() - - h.assertBigNum(originalUptime, newUptime) - }) - }) - - context('when called by anyone other than the oracle contract', () => { - it('does not accept the data provided', async () => { - await h.assertActionThrows(async () => { - await sla.report(request.id, response, { from: oracleNode }) - }) - }) - }) - }) -}) diff --git a/examples/uptime_sla/test/support/helpers.js b/examples/uptime_sla/test/support/helpers.js deleted file mode 100644 index 41085a277e6..00000000000 --- a/examples/uptime_sla/test/support/helpers.js +++ /dev/null @@ -1,198 +0,0 @@ -/* eslint-disable @typescript-eslint/no-var-requires */ - -const bigNum = number => web3.utils.toBN(number) -const cbor = require('cbor') -const abi = require('ethereumjs-abi') -const util = require('ethereumjs-util') -const BN = require('bn.js') -const ethJSUtils = require('ethereumjs-util') - -web3.providers.HttpProvider.prototype.sendAsync = - web3.providers.HttpProvider.prototype.send - -const exps = {} - -const sendEth = (method, params) => - new Promise((resolve, reject) => { - web3.currentProvider.sendAsync( - { - jsonrpc: '2.0', - method: method, - params: params || [], - id: new Date().getTime(), - }, - (error, response) => (error ? reject(error) : resolve(response.result)), - () => {}, - () => {}, - ) - }) - -exps.assertBigNum = (a, b, failureMessage) => - assert( - bigNum(a).eq(bigNum(b)), - `BigNum ${a} is not ${b}` + (failureMessage ? ': ' + failureMessage : ''), - ) - -exps.getLatestTimestamp = async () => { - const latestBlock = await web3.eth.getBlock('latest', false) - return web3.utils.toDecimal(latestBlock.timestamp) -} - -exps.fastForwardTo = async target => { - const now = await exps.getLatestTimestamp() - assert.isAbove(target, now, 'Cannot fast forward to the past') - const difference = target - now - await sendEth('evm_increaseTime', [difference]) - await sendEth('evm_mine') -} - -const minutes = number => number * 60 -const hours = number => number * minutes(60) - -exps.days = number => number * hours(24) - -const abiEncode = (types, values) => { - return abi.rawEncode(types, values).toString('hex') -} - -const startMapBuffer = Buffer.from([0xbf]) -const endMapBuffer = Buffer.from([0xff]) - -const autoAddMapDelimiters = data => { - let buffer = data - - if (buffer[0] >> 5 !== 5) { - buffer = Buffer.concat( - [startMapBuffer, buffer, endMapBuffer], - buffer.length + 2, - ) - } - - return buffer -} - -const zeroX = value => (value.slice(0, 2) !== '0x' ? `0x${value}` : value) - -const toHexWithoutPrefix = arg => { - if (arg instanceof Buffer || arg instanceof BN) { - return arg.toString('hex') - } else if (arg instanceof Uint8Array) { - return Array.prototype.reduce.call( - arg, - (a, v) => a + v.toString('16').padStart(2, '0'), - '', - ) - } else { - return Buffer.from(arg, 'ascii').toString('hex') - } -} - -const toHex = value => { - return zeroX(toHexWithoutPrefix(value)) -} - -exps.assertActionThrows = action => - Promise.resolve() - .then(action) - .catch(error => { - assert(error, 'Expected an error to be raised') - assert(error.message, 'Expected an error to be raised') - return error.message - }) - .then(errorMessage => { - assert(errorMessage, 'Expected an error to be raised') - const invalidOpcode = errorMessage.includes('invalid opcode') - const reverted = errorMessage.includes( - 'VM Exception while processing transaction: revert', - ) - assert( - invalidOpcode || reverted, - 'expected following error message to include "invalid JUMP" or ' + - `"revert": "${errorMessage}"`, - ) - // see https://github.com/ethereumjs/testrpc/issues/39 - // for why the "invalid JUMP" is the throw related error when using TestRPC - }) - -exps.decodeDietCBOR = data => { - return cbor.decodeFirst(autoAddMapDelimiters(ethJSUtils.toBuffer(data))) -} - -exps.decodeRunRequest = log => { - const runABI = util.toBuffer(log.data) - const types = [ - 'address', - 'bytes32', - 'uint256', - 'address', - 'bytes4', - 'uint256', - 'uint256', - 'bytes', - ] - const [ - requester, - requestId, - payment, - callbackAddress, - callbackFunc, - expiration, - version, - data, - ] = abi.rawDecode(types, runABI) - - return { - topic: log.topics[0], - jobId: log.topics[1], - requester: zeroX(requester), - id: toHex(requestId), - payment: toHex(payment), - callbackAddr: zeroX(callbackAddress), - callbackFunc: toHex(callbackFunc), - expiration: toHex(expiration), - dataVersion: version, - data: autoAddMapDelimiters(data), - } -} - -exps.functionSelector = signature => - '0x' + - web3.utils - .sha3(signature) - .slice(2) - .slice(0, 8) - -exps.fulfillOracleRequest = async (oracle, request, response, options) => { - if (!options) options = {} - - return oracle.fulfillOracleRequest( - request.id, - request.payment, - request.callbackAddr, - request.callbackFunc, - request.expiration, - response, - options, - ) -} - -exps.requestDataBytes = (specId, to, fHash, nonce, data) => { - const types = [ - 'address', - 'uint256', - 'bytes32', - 'address', - 'bytes4', - 'uint256', - 'uint256', - 'bytes', - ] - const values = [0, 0, specId, to, fHash, nonce, 1, data] - const encoded = abiEncode(types, values) - const funcSelector = exps.functionSelector( - 'oracleRequest(address,uint256,bytes32,address,bytes4,uint256,uint256,bytes)', - ) - return funcSelector + encoded -} - -module.exports = exps diff --git a/examples/uptime_sla/truffle.js b/examples/uptime_sla/truffle.js deleted file mode 100644 index 6c476df1834..00000000000 --- a/examples/uptime_sla/truffle.js +++ /dev/null @@ -1,16 +0,0 @@ -/* eslint-disable @typescript-eslint/camelcase */ -module.exports = { - compilers: { - solc: { - version: '0.4.24', - }, - }, - networks: { - cldev: { - host: '127.0.0.1', - port: 18545, - network_id: '*', - gas: 4700000, - }, - }, -} diff --git a/tools/bin/echo_server b/tools/bin/echo_server deleted file mode 100755 index 3b2be113e4d..00000000000 --- a/tools/bin/echo_server +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/bash - -yarn workspace @chainlink/example-echo-server run start diff --git a/tools/ci/truffle_test b/tools/ci/truffle_test index 5c00ec22195..3f48dfda4af 100755 --- a/tools/ci/truffle_test +++ b/tools/ci/truffle_test @@ -13,6 +13,4 @@ yarn workspace chainlinkv0.5 build # These should be merged into a global test command yarn workspace chainlink test yarn workspace chainlinkv0.5 test -yarn workspace @chainlink/example-uptime-sla test -yarn workspace @chainlink/example-echo-server test yarn workspace @chainlink/box test \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index e1c72c31fdf..a8a1c4402ac 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4109,7 +4109,7 @@ arrify@^1.0.1: resolved "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d" integrity sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0= -asap@^2.0.0, asap@~2.0.3, asap@~2.0.6: +asap@~2.0.3, asap@~2.0.6: version "2.0.6" resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" integrity sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY= @@ -5784,11 +5784,6 @@ buffer-crc32@~0.2.3: resolved "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" integrity sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI= -buffer-equal-constant-time@1.0.1: - version "1.0.1" - resolved "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819" - integrity sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk= - buffer-equal@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/buffer-equal/-/buffer-equal-1.0.0.tgz#59616b498304d556abd466966b22eeda3eca5fbe" @@ -6187,16 +6182,6 @@ chainlink@0.7.8: link_token "^1.0.6" openzeppelin-solidity "^1.12.0" -chainlink@^0.6.5: - version "0.6.5" - resolved "https://registry.npmjs.org/chainlink/-/chainlink-0.6.5.tgz#35c8fef8529f4c48a996b6a1d2546cfcdb38af59" - integrity sha512-V6Z1Z4BYEwpicc5jVw66oyQeVe+WT90ZlDKTsbO04eASSJup2eCUxSp9nEDywA2gva/1D1F1q+AHDkMDWHpoPA== - dependencies: - "@ensdomains/ens" "^0.1.1" - link_token "^1.0.3" - openzeppelin-solidity "^1.12.0" - solidity-cborutils "^1.0.4" - chalk@2.4.1: version "2.4.1" resolved "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz#18c49ab16a037b6eb0152cc83e3471338215b66e" @@ -7955,11 +7940,6 @@ depd@~1.1.2: resolved "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" integrity sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak= -deprecate@1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/deprecate/-/deprecate-1.0.0.tgz#661490ed2428916a6c8883d8834e5646f4e4a4a8" - integrity sha1-ZhSQ7SQokWpsiIPYg05WRvTkpKg= - deprecate@^1.0.0: version "1.1.1" resolved "https://registry.npmjs.org/deprecate/-/deprecate-1.1.1.tgz#4632e981fc815eeaf00be945a40359c0f8bf9913" @@ -8360,13 +8340,6 @@ ecc-jsbn@~0.1.1: jsbn "~0.1.0" safer-buffer "^2.1.0" -ecdsa-sig-formatter@1.0.11: - version "1.0.11" - resolved "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz#ae0f0fa2d85045ef14a817daa3ce9acd0489e5bf" - integrity sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ== - dependencies: - safe-buffer "^5.0.1" - editorconfig@^0.15.3: version "0.15.3" resolved "https://registry.npmjs.org/editorconfig/-/editorconfig-0.15.3.tgz#bef84c4e75fb8dcb0ce5cee8efd51c15999befc5" @@ -9324,7 +9297,7 @@ ethereumjs-abi@0.6.5: bn.js "^4.10.0" ethereumjs-util "^4.3.0" -ethereumjs-abi@0.6.7, ethereumjs-abi@^0.6.5: +ethereumjs-abi@0.6.7: version "0.6.7" resolved "https://registry.yarnpkg.com/ethereumjs-abi/-/ethereumjs-abi-0.6.7.tgz#d1d1c5cdb8d910a7d97645ba9e93be5d153bba2e" integrity sha512-EMLOA8ICO5yAaXDhjVEfYjsJIXYutY8ufTE93eEKwsVtp2usQreKwsDTJ9zvam3omYqNuffr8IONIqb2uUslGQ== @@ -10637,11 +10610,6 @@ fs.realpath@^1.0.0: resolved "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= -fs@^0.0.1-security: - version "0.0.1-security" - resolved "https://registry.npmjs.org/fs/-/fs-0.0.1-security.tgz#8a7bd37186b6dddf3813f23858b57ecaaf5e41d4" - integrity sha1-invTcYa23d84E/I4WLV+yq9eQdQ= - fsevents@2.0.7: version "2.0.7" resolved "https://registry.npmjs.org/fsevents/-/fsevents-2.0.7.tgz#382c9b443c6cbac4c57187cdda23aa3bf1ccfc2a" @@ -13703,22 +13671,6 @@ jsonschema@^1.2.0: resolved "https://registry.yarnpkg.com/jsonschema/-/jsonschema-1.2.4.tgz#a46bac5d3506a254465bc548876e267c6d0d6464" integrity sha512-lz1nOH69GbsVHeVgEdvyavc/33oymY1AZwtePMiMj4HZPMbP5OIKK3zT9INMWjwua/V4Z4yq7wSlBbSG+g4AEw== -jsonwebtoken@^8.5.1: - version "8.5.1" - resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz#00e71e0b8df54c2121a1f26137df2280673bcc0d" - integrity sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w== - dependencies: - jws "^3.2.2" - lodash.includes "^4.3.0" - lodash.isboolean "^3.0.3" - lodash.isinteger "^4.0.4" - lodash.isnumber "^3.0.3" - lodash.isplainobject "^4.0.6" - lodash.isstring "^4.0.1" - lodash.once "^4.0.0" - ms "^2.1.1" - semver "^5.6.0" - jsprim@^1.2.2: version "1.4.1" resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2" @@ -13849,23 +13801,6 @@ just-debounce@^1.0.0: resolved "https://registry.npmjs.org/just-debounce/-/just-debounce-1.0.0.tgz#87fccfaeffc0b68cd19d55f6722943f929ea35ea" integrity sha1-h/zPrv/AtozRnVX2cilD+SnqNeo= -jwa@^1.4.1: - version "1.4.1" - resolved "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz#743c32985cb9e98655530d53641b66c8645b039a" - integrity sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA== - dependencies: - buffer-equal-constant-time "1.0.1" - ecdsa-sig-formatter "1.0.11" - safe-buffer "^5.0.1" - -jws@^3.2.2: - version "3.2.2" - resolved "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz#001099f3639468c9414000e99995fa52fb478304" - integrity sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA== - dependencies: - jwa "^1.4.1" - safe-buffer "^5.0.1" - keccak@^1.0.2: version "1.4.0" resolved "https://registry.yarnpkg.com/keccak/-/keccak-1.4.0.tgz#572f8a6dbee8e7b3aa421550f9e6408ca2186f80" @@ -14182,7 +14117,7 @@ liftoff@^3.1.0: rechoir "^0.6.2" resolve "^1.1.7" -link_token@^1.0.3, link_token@^1.0.6: +link_token@^1.0.6: version "1.0.6" resolved "https://registry.yarnpkg.com/link_token/-/link_token-1.0.6.tgz#98c5a1f53d4e22d7b0d5f80c5051702e7dc3b436" integrity sha512-WI6n2Ri9kWQFsFYPTnLvU+Y3DT87he55uqLLX/sAwCVkGTXI/wionGEHAoWU33y8RepWlh46y+RD+DAz5Wmejg== @@ -14370,41 +14305,16 @@ lodash.flattendeep@^4.4.0: resolved "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz#fb030917f86a3134e5bc9bec0d69e0013ddfedb2" integrity sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI= -lodash.includes@^4.3.0: - version "4.3.0" - resolved "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz#60bb98a87cb923c68ca1e51325483314849f553f" - integrity sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8= - -lodash.isboolean@^3.0.3: - version "3.0.3" - resolved "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz#6c2e171db2a257cd96802fd43b01b20d5f5870f6" - integrity sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY= - lodash.isequal@^4.5.0: version "4.5.0" resolved "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0" integrity sha1-QVxEePK8wwEgwizhDtMib30+GOA= -lodash.isinteger@^4.0.4: - version "4.0.4" - resolved "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz#619c0af3d03f8b04c31f5882840b77b11cd68343" - integrity sha1-YZwK89A/iwTDH1iChAt3sRzWg0M= - -lodash.isnumber@^3.0.3: - version "3.0.3" - resolved "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz#3ce76810c5928d03352301ac287317f11c0b1ffc" - integrity sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w= - lodash.isplainobject@^4.0.6: version "4.0.6" resolved "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb" integrity sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs= -lodash.isstring@^4.0.1: - version "4.0.1" - resolved "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451" - integrity sha1-1SfftUVuynzJu5XV2ur4i6VKVFE= - lodash.memoize@4.x, lodash.memoize@^4.1.2: version "4.1.2" resolved "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" @@ -14415,7 +14325,7 @@ lodash.mergewith@^4.6.2: resolved "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.2.tgz#617121f89ac55f59047c7aec1ccd6654c6590f55" integrity sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ== -lodash.once@^4.0.0, lodash.once@^4.1.1: +lodash.once@^4.1.1: version "4.1.1" resolved "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac" integrity sha1-DdOXEhPHxW34gJd9UEyI+0cal6w= @@ -16651,11 +16561,6 @@ polished@^3.3.1: dependencies: "@babel/runtime" "^7.4.5" -pop-iterate@^1.0.1: - version "1.0.1" - resolved "https://registry.npmjs.org/pop-iterate/-/pop-iterate-1.0.1.tgz#ceacfdab4abf353d7a0f2aaa2c1fc7b3f9413ba3" - integrity sha1-zqz9q0q/NT16DyqqLB/Hs/lBO6M= - popper.js@1.14.3: version "1.14.3" resolved "https://registry.yarnpkg.com/popper.js/-/popper.js-1.14.3.tgz#1438f98d046acf7b4d78cd502bf418ac64d4f095" @@ -17744,15 +17649,6 @@ punycode@^2.1.0, punycode@^2.1.1: resolved "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== -q@2.0.x: - version "2.0.3" - resolved "https://registry.npmjs.org/q/-/q-2.0.3.tgz#75b8db0255a1a5af82f58c3f3aaa1efec7d0d134" - integrity sha1-dbjbAlWhpa+C9Yw/Oqoe/sfQ0TQ= - dependencies: - asap "^2.0.0" - pop-iterate "^1.0.1" - weak-map "^1.0.5" - q@^1.1.2: version "1.5.1" resolved "https://registry.npmjs.org/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7" @@ -19075,7 +18971,7 @@ request-promise-native@^1.0.5: stealthy-require "^1.1.1" tough-cookie "^2.3.3" -request-promise@4.2.4, request-promise@^4.2.2: +request-promise@4.2.4: version "4.2.4" resolved "https://registry.npmjs.org/request-promise/-/request-promise-4.2.4.tgz#1c5ed0d71441e38ad58c7ce4ea4ea5b06d54b310" integrity sha512-8wgMrvE546PzbR5WbYxUQogUnUDfM0S7QIFZMID+J73vdFARkFy+HElj4T+MWYhpXwlLp0EQ8Zoj8xUA0he4Vg== @@ -19085,7 +18981,7 @@ request-promise@4.2.4, request-promise@^4.2.2: stealthy-require "^1.1.1" tough-cookie "^2.3.3" -request@2.88.0, request@^2.67.0, request@^2.79.0, request@^2.83.0, request@^2.85.0, request@^2.87.0, request@^2.88.0: +request@2.88.0, request@^2.67.0, request@^2.79.0, request@^2.85.0, request@^2.87.0, request@^2.88.0: version "2.88.0" resolved "https://registry.yarnpkg.com/request/-/request-2.88.0.tgz#9c2fca4f7d35b592efe57c7f0a55e81052124fef" integrity sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg== @@ -19352,11 +19248,6 @@ rlp@^2.0.0, rlp@^2.2.1: bn.js "^4.11.1" safe-buffer "^5.1.1" -rootpath@0.1.2: - version "0.1.2" - resolved "https://registry.npmjs.org/rootpath/-/rootpath-0.1.2.tgz#5b379a87dca906e9b91d690a599439bef267ea6b" - integrity sha1-Wzeah9ypBum5HWkKWZQ5vvJn6ms= - rst-selector-parser@^2.2.3: version "2.2.3" resolved "https://registry.npmjs.org/rst-selector-parser/-/rst-selector-parser-2.2.3.tgz#81b230ea2fcc6066c89e3472de794285d9b03d91" @@ -19507,11 +19398,6 @@ schema-utils@^2.0.0, schema-utils@^2.0.1: ajv "^6.10.2" ajv-keywords "^3.4.1" -scmp@2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/scmp/-/scmp-2.0.0.tgz#247110ef22ccf897b13a3f0abddb52782393cd6a" - integrity sha1-JHEQ7yLM+JexOj8KvdtSeCOTzWo= - scroll-into-view-if-needed@^2.2.16: version "2.2.20" resolved "https://registry.npmjs.org/scroll-into-view-if-needed/-/scroll-into-view-if-needed-2.2.20.tgz#3a46847a72233a3af9770e55df450f2a7f2e2a0e" @@ -21671,22 +21557,6 @@ tweetnacl@^1.0.0: resolved "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.1.tgz#2594d42da73cd036bd0d2a54683dd35a6b55ca17" integrity sha512-kcoMoKTPYnoeS50tzoqjPY3Uv9axeuuFAZY9M/9zFnhoVvRfxz9K29IMPD7jGmt2c8SW7i3gT9WqDl2+nV7p4A== -twilio@^3.34.0: - version "3.34.0" - resolved "https://registry.yarnpkg.com/twilio/-/twilio-3.34.0.tgz#3d3721c6019395f9b0ef6f922c1a457e03a987d2" - integrity sha512-LBOOyT2NZJXe3gnC2kGmtqopKRiKDjGQjDK93IJ0+5ePUIh77huulaWHkdwaoD1ejL3gKs+K+P3MVIkFHeAqUQ== - dependencies: - "@types/express" "^4.17.1" - deprecate "1.0.0" - jsonwebtoken "^8.5.1" - lodash "^4.17.15" - moment "^2.24.0" - q "2.0.x" - request "^2.88.0" - rootpath "0.1.2" - scmp "2.0.0" - xmlbuilder "9.0.1" - type-check@~0.3.2: version "0.3.2" resolved "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz#5884cab512cf1d355e3fb784f30804b2b520db72" @@ -22383,11 +22253,6 @@ wbuf@^1.1.0, wbuf@^1.7.3: dependencies: minimalistic-assert "^1.0.0" -weak-map@^1.0.5: - version "1.0.5" - resolved "https://registry.npmjs.org/weak-map/-/weak-map-1.0.5.tgz#79691584d98607f5070bd3b70a40e6bb22e401eb" - integrity sha1-eWkVhNmGB/UHC9O3CkDmuyLkAes= - web3-bzz@1.0.0-beta.37: version "1.0.0-beta.37" resolved "https://registry.yarnpkg.com/web3-bzz/-/web3-bzz-1.0.0-beta.37.tgz#59e3e4f5a9d732731008fe9165c3ec8bf85d502f" @@ -22910,7 +22775,7 @@ web3@1.0.0-beta.37: web3-shh "1.0.0-beta.37" web3-utils "1.0.0-beta.37" -web3@1.2.1, web3@^1.0.0-beta.35, web3@^1.0.0-beta.55, web3@^1.2.0: +web3@1.2.1, web3@^1.2.0: version "1.2.1" resolved "https://registry.npmjs.org/web3/-/web3-1.2.1.tgz#5d8158bcca47838ab8c2b784a2dee4c3ceb4179b" integrity sha512-nNMzeCK0agb5i/oTWNdQ1aGtwYfXzHottFP2Dz0oGIzavPMGSKyVlr8ibVb1yK5sJBjrWVnTdGaOC2zKDFuFRw== @@ -23661,11 +23526,6 @@ xml2js@^0.4.17: sax ">=0.6.0" xmlbuilder "~9.0.1" -xmlbuilder@9.0.1: - version "9.0.1" - resolved "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.1.tgz#91cd70897755363eba57c12ddeeab4a341a61f65" - integrity sha1-kc1wiXdVNj66V8Et3uq0o0GmH2U= - xmlbuilder@~9.0.1: version "9.0.7" resolved "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz#132ee63d2ec5565c557e20f4c22df9aca686b10d" From 00aab15745d0bf3171913105a4a60b059c7ebb47 Mon Sep 17 00:00:00 2001 From: Alex Kwiatkowski Date: Wed, 6 Nov 2019 15:47:26 -0800 Subject: [PATCH 187/199] Handle null from localStorage persistURL [Fixes #169601756] --- Dockerfile | 1 + operator_ui/__tests__/utils/storage.test.js | 32 ------------ operator_ui/package.json | 1 + operator_ui/src/App.tsx | 8 +-- operator_ui/src/components/Bridges/Form.tsx | 11 ++-- operator_ui/src/components/Jobs/Form.js | 13 +++-- .../redux/reducers/notifications.js | 15 ++++-- operator_ui/src/containers/SignIn.js | 9 ++-- .../src/utils/authenticationStorage.ts | 15 ++++-- operator_ui/src/utils/storage.js | 20 -------- operator_ui/src/utils/urlStorage.ts | 11 ++++ operator_ui/tsconfig.json | 3 +- package.json | 1 + tools/local-storage/.eslintignore | 3 ++ tools/local-storage/.eslintrc.js | 4 ++ tools/local-storage/.gitignore | 1 + tools/local-storage/.prettierignore | 2 + tools/local-storage/@types/.gitkeep | 0 tools/local-storage/@types/globals.d.ts | 9 ++++ tools/local-storage/__tests__/.gitkeep | 0 tools/local-storage/__tests__/storage.test.ts | 51 +++++++++++++++++++ tools/local-storage/jest.config.js | 6 +++ tools/local-storage/jest.setup.js | 0 tools/local-storage/package.json | 28 ++++++++++ tools/local-storage/src/index.ts | 1 + tools/local-storage/src/storage.ts | 29 +++++++++++ tools/local-storage/tsconfig.json | 20 ++++++++ 27 files changed, 215 insertions(+), 79 deletions(-) delete mode 100644 operator_ui/__tests__/utils/storage.test.js delete mode 100644 operator_ui/src/utils/storage.js create mode 100644 operator_ui/src/utils/urlStorage.ts create mode 100644 tools/local-storage/.eslintignore create mode 100644 tools/local-storage/.eslintrc.js create mode 100644 tools/local-storage/.gitignore create mode 100644 tools/local-storage/.prettierignore create mode 100644 tools/local-storage/@types/.gitkeep create mode 100644 tools/local-storage/@types/globals.d.ts create mode 100644 tools/local-storage/__tests__/.gitkeep create mode 100644 tools/local-storage/__tests__/storage.test.ts create mode 100644 tools/local-storage/jest.config.js create mode 100644 tools/local-storage/jest.setup.js create mode 100644 tools/local-storage/package.json create mode 100644 tools/local-storage/src/index.ts create mode 100644 tools/local-storage/src/storage.ts create mode 100644 tools/local-storage/tsconfig.json diff --git a/Dockerfile b/Dockerfile index 1a03fd81a2c..a251980af48 100644 --- a/Dockerfile +++ b/Dockerfile @@ -24,6 +24,7 @@ COPY styleguide/package.json ./styleguide/ COPY tools/prettier-config/package.json ./tools/prettier-config/ COPY tools/eslint-config/package.json ./tools/eslint-config/ COPY tools/json-api-client/package.json ./tools/json-api-client/ +COPY tools/local-storage/package.json ./tools/local-storage/ RUN make yarndep # Install chainlink diff --git a/operator_ui/__tests__/utils/storage.test.js b/operator_ui/__tests__/utils/storage.test.js deleted file mode 100644 index 4fb3242323d..00000000000 --- a/operator_ui/__tests__/utils/storage.test.js +++ /dev/null @@ -1,32 +0,0 @@ -import { get, set } from 'utils/storage' - -describe('utils/storage', () => { - beforeEach(() => { - global.localStorage.clear() - }) - - describe('get', () => { - it('returns a JS object for JSON keyed under "chainlink." in localStorage', () => { - global.localStorage.setItem('chainlink.foo', '{"foo":"FOO"}') - expect(get('foo')).toEqual({ foo: 'FOO' }) - }) - - it('returns an empty JS object when not valid JSON', () => { - global.localStorage.setItem('chainlink.foo', '{"foo"}') - expect(get('foo')).toEqual({}) - }) - - it('returns an empty JS object when the key does not exist', () => { - expect(get('foo')).toEqual({}) - }) - }) - - describe('set', () => { - it('saves the JS object as JSON keyed under "chainlink." in localStorage', () => { - set('foo', { foo: 'FOO' }) - expect(global.localStorage.getItem('chainlink.foo')).toEqual( - '{"foo":"FOO"}', - ) - }) - }) -}) diff --git a/operator_ui/package.json b/operator_ui/package.json index 0bb7238f8b1..31e5beb5aad 100644 --- a/operator_ui/package.json +++ b/operator_ui/package.json @@ -22,6 +22,7 @@ }, "dependencies": { "@babel/polyfill": "^7.2.5", + "@chainlink/local-storage": "0.0.1", "@chainlink/styleguide": "0.0.1", "@material-ui/core": "^3.9.2", "@material-ui/icons": "^3.0.1", diff --git a/operator_ui/src/App.tsx b/operator_ui/src/App.tsx index 4f6c00a7362..87773c54417 100644 --- a/operator_ui/src/App.tsx +++ b/operator_ui/src/App.tsx @@ -3,14 +3,16 @@ import { Provider } from 'react-redux' import createStore from './connectors/redux' import './index.css' import Layout from './Layout' -import { set } from './utils/storage' +import { setPersistUrl } from './utils/urlStorage' + +const SIGNIN_PATH = '/signin' const store = createStore() store.subscribe(() => { const prevURL = store.getState().notifications.currentUrl - if (prevURL !== '/signin') { - set('persistURL', prevURL) + if (prevURL && prevURL !== SIGNIN_PATH) { + setPersistUrl(prevURL) } }) diff --git a/operator_ui/src/components/Bridges/Form.tsx b/operator_ui/src/components/Bridges/Form.tsx index 1e8376d1737..b01e721cb3d 100644 --- a/operator_ui/src/components/Bridges/Form.tsx +++ b/operator_ui/src/components/Bridges/Form.tsx @@ -10,7 +10,7 @@ import { withFormik, FormikProps, Form as FormikForm } from 'formik' import normalizeUrl from 'normalize-url' import React from 'react' import { Prompt } from 'react-router-dom' -import { get, set } from 'utils/storage' +import * as storage from '@chainlink/local-storage' const styles = (theme: Theme) => createStyles({ @@ -139,9 +139,10 @@ Form.defaultProps = { const WithFormikForm = withFormik({ mapPropsToValues({ name, url, minimumContractPayment, confirmations }) { - const shouldPersist = Object.keys(get('persistBridge')).length !== 0 - const persistedJSON = shouldPersist && get('persistBridge') - if (shouldPersist) set('persistBridge', {}) + const shouldPersist = + Object.keys(storage.getJson('persistBridge')).length !== 0 + const persistedJSON = shouldPersist && storage.getJson('persistBridge') + if (shouldPersist) storage.setJson('persistBridge', {}) const json: FormValues = { name: name || '', url: url || '', @@ -158,7 +159,7 @@ const WithFormikForm = withFormik({ values.url = '' } props.onSubmit(values, props.onSuccess, props.onError) - set('persistBridge', values) + storage.setJson('persistBridge', values) setTimeout(() => { setSubmitting(false) }, 1000) diff --git a/operator_ui/src/components/Jobs/Form.js b/operator_ui/src/components/Jobs/Form.js index 8df1abcad38..9c30d687963 100644 --- a/operator_ui/src/components/Jobs/Form.js +++ b/operator_ui/src/components/Jobs/Form.js @@ -5,7 +5,7 @@ import { withStyles } from '@material-ui/core/styles' import Button from 'components/Button' import { TextField, Grid } from '@material-ui/core' import { Prompt } from 'react-router-dom' -import { set, get } from 'utils/storage' +import * as storage from '@chainlink/local-storage' const styles = theme => ({ card: { @@ -81,9 +81,12 @@ Form.propTypes = { const formikOpts = { mapPropsToValues({ definition }) { - const shouldPersist = Object.keys(get('persistSpec')).length !== 0 - const persistedJSON = shouldPersist && get('persistSpec') - if (shouldPersist) set('persistSpec', {}) + const shouldPersist = + Object.keys(storage.getJson('persistSpec')).length !== 0 + const persistedJSON = shouldPersist && storage.getJson('persistSpec') + if (shouldPersist) { + storage.setJson('persistSpec', {}) + } const json = JSON.stringify(definition, null, '\t') || (shouldPersist && persistedJSON) || @@ -105,7 +108,7 @@ const formikOpts = { handleSubmit(values, { props, setSubmitting }) { const definition = JSON.parse(values.json) - set('persistSpec', values.json) + storage.setJson('persistSpec', values.json) props.onSubmit(definition, props.onSuccess, props.onError) setTimeout(() => { setSubmitting(false) diff --git a/operator_ui/src/connectors/redux/reducers/notifications.js b/operator_ui/src/connectors/redux/reducers/notifications.js index 8e6aafd079f..4b311c7a998 100644 --- a/operator_ui/src/connectors/redux/reducers/notifications.js +++ b/operator_ui/src/connectors/redux/reducers/notifications.js @@ -4,7 +4,7 @@ import { NOTIFY_SUCCESS, NOTIFY_ERROR, } from 'actions' -import { set } from 'utils/storage' +import * as storage from '@chainlink/local-storage' import { BadRequestError } from '@chainlink/json-api-client' const initialState = { @@ -38,9 +38,12 @@ export default (state = initialState, action = {}) => { component: action.component, props: action.props, } - if (success.props.data && success.props.data.type === 'specs') - set('persistSpec', {}) - else if (typeof success.props.url === 'string') set('persistBridge', {}) + if (success.props.data && success.props.data.type === 'specs') { + storage.setJson('persistSpec', {}) + } else if (typeof success.props.url === 'string') { + storage.setJson('persistBridge', {}) + } + return Object.assign({}, state, { successes: [success], errors: [], @@ -65,7 +68,9 @@ export default (state = initialState, action = {}) => { } else { errorNotifications = [error] } - if (error instanceof BadRequestError) set('persistBridge', {}) + if (error instanceof BadRequestError) { + storage.setJson('persistBridge', {}) + } return Object.assign({}, state, { successes: [], diff --git a/operator_ui/src/containers/SignIn.js b/operator_ui/src/containers/SignIn.js index be2d67cfe22..8d2c4eeeda8 100644 --- a/operator_ui/src/containers/SignIn.js +++ b/operator_ui/src/containers/SignIn.js @@ -13,7 +13,7 @@ import { hot } from 'react-hot-loader' import { submitSignIn } from 'actions' import HexagonLogo from 'components/Logos/Hexagon' import matchRouteAndMapDispatchToProps from 'utils/matchRouteAndMapDispatchToProps' -import { get } from 'utils/storage' +import { getPersistUrl } from '../utils/urlStorage' const styles = theme => ({ container: { @@ -53,9 +53,10 @@ export const SignIn = useHooks(props => { } const { classes, fetching, authenticated, errors } = props - const hasPrevState = Object.keys(get('persistURL')).length !== 0 - if (authenticated) - return + if (authenticated) { + return + } + return ( storage.get('authentication') +interface Auth { + allowed?: boolean +} -// CHECK ME -export const set = (obj: any) => storage.set('authentication', obj) +export function get(): Auth { + return storage.getJson('authentication') +} + +export function set(auth: Auth): void { + storage.setJson('authentication', auth) +} diff --git a/operator_ui/src/utils/storage.js b/operator_ui/src/utils/storage.js deleted file mode 100644 index 856cf449bc3..00000000000 --- a/operator_ui/src/utils/storage.js +++ /dev/null @@ -1,20 +0,0 @@ -import storage from 'local-storage-fallback' - -export const get = key => { - const localStorageItem = storage.getItem(`chainlink.${key}`) - const obj = {} - - if (localStorageItem) { - try { - return JSON.parse(localStorageItem) - } catch (e) { - // continue regardless of error - } - } - - return obj -} - -export const set = (key, obj) => { - storage.setItem(`chainlink.${key}`, JSON.stringify(obj)) -} diff --git a/operator_ui/src/utils/urlStorage.ts b/operator_ui/src/utils/urlStorage.ts new file mode 100644 index 00000000000..b5743630eb0 --- /dev/null +++ b/operator_ui/src/utils/urlStorage.ts @@ -0,0 +1,11 @@ +import * as storage from '@chainlink/local-storage' + +const PERSIST_URL = 'persistURL' + +export function getPersistUrl(): string { + return storage.get(PERSIST_URL) || '' +} + +export function setPersistUrl(url: string): void { + storage.set(PERSIST_URL, url) +} diff --git a/operator_ui/tsconfig.json b/operator_ui/tsconfig.json index 077e5340640..f84dc48e7c5 100644 --- a/operator_ui/tsconfig.json +++ b/operator_ui/tsconfig.json @@ -24,6 +24,7 @@ "references": [ { "path": "../styleguide" }, - { "path": "../tools/json-api-client" } + { "path": "../tools/json-api-client" }, + { "path": "../tools/local-storage" } ] } diff --git a/package.json b/package.json index 85bf2a988df..1b5380e1af7 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,7 @@ "evm/v0.5", "tools", "tools/eslint-config", + "tools/local-storage", "tools/json-api-client", "tools/prettier-config", "operator_ui", diff --git a/tools/local-storage/.eslintignore b/tools/local-storage/.eslintignore new file mode 100644 index 00000000000..6ca845cd079 --- /dev/null +++ b/tools/local-storage/.eslintignore @@ -0,0 +1,3 @@ +public/ +node_modules/ +dist/ diff --git a/tools/local-storage/.eslintrc.js b/tools/local-storage/.eslintrc.js new file mode 100644 index 00000000000..e81b54ce712 --- /dev/null +++ b/tools/local-storage/.eslintrc.js @@ -0,0 +1,4 @@ +module.exports = { + root: true, + extends: ['@chainlink/eslint-config/react'], +} diff --git a/tools/local-storage/.gitignore b/tools/local-storage/.gitignore new file mode 100644 index 00000000000..1521c8b7652 --- /dev/null +++ b/tools/local-storage/.gitignore @@ -0,0 +1 @@ +dist diff --git a/tools/local-storage/.prettierignore b/tools/local-storage/.prettierignore new file mode 100644 index 00000000000..7cb6210d43f --- /dev/null +++ b/tools/local-storage/.prettierignore @@ -0,0 +1,2 @@ +.prettierignore +.gitignore diff --git a/tools/local-storage/@types/.gitkeep b/tools/local-storage/@types/.gitkeep new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tools/local-storage/@types/globals.d.ts b/tools/local-storage/@types/globals.d.ts new file mode 100644 index 00000000000..122956575a0 --- /dev/null +++ b/tools/local-storage/@types/globals.d.ts @@ -0,0 +1,9 @@ +declare namespace NodeJS { + interface Global { + localStorage: { + clear: Function + getItem: Function + setItem: Function + } + } +} diff --git a/tools/local-storage/__tests__/.gitkeep b/tools/local-storage/__tests__/.gitkeep new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tools/local-storage/__tests__/storage.test.ts b/tools/local-storage/__tests__/storage.test.ts new file mode 100644 index 00000000000..3b28ad9ba25 --- /dev/null +++ b/tools/local-storage/__tests__/storage.test.ts @@ -0,0 +1,51 @@ +import storage from 'local-storage-fallback' +import { get, set, getJson, setJson } from '../src/storage' + +beforeEach(() => { + storage.clear() +}) + +describe('get', () => { + it('returns a string keyed under "chainlink." in localStorage', () => { + storage.setItem('chainlink.foo', 'FOO') + expect(get('foo')).toEqual('FOO') + }) + + it('returns null when the key does not exist', () => { + expect(get('foo')).toEqual(null) + }) +}) + +describe('getJson', () => { + it('returns a JS object for JSON keyed under "chainlink." in localStorage', () => { + storage.setItem('chainlink.foo', '{"foo":"FOO"}') + expect(getJson('foo')).toEqual({ foo: 'FOO' }) + }) + + it('returns an empty JS object when not valid JSON', () => { + storage.setItem('chainlink.foo', '{"foo"}') + expect(getJson('foo')).toEqual({}) + }) + + it('returns an empty JS object when the key does not exist', () => { + expect(getJson('foo')).toEqual({}) + }) +}) + +describe('set', () => { + it('saves the string keyed under "chainlink." in localStorage', () => { + set('foo', 'FOO') + + const stored = storage.getItem('chainlink.foo') + expect(stored).toEqual('FOO') + }) +}) + +describe('setJson', () => { + it('saves the JS object as JSON keyed under "chainlink." in localStorage', () => { + setJson('foo', { foo: 'FOO' }) + + const stored = storage.getItem('chainlink.foo') + expect(stored).toEqual('{"foo":"FOO"}') + }) +}) diff --git a/tools/local-storage/jest.config.js b/tools/local-storage/jest.config.js new file mode 100644 index 00000000000..957032f3d22 --- /dev/null +++ b/tools/local-storage/jest.config.js @@ -0,0 +1,6 @@ +module.exports = { + preset: 'ts-jest', + testEnvironment: 'node', + setupFilesAfterEnv: ['/jest.setup.js'], + testPathIgnorePatterns: ['/node_modules/'], +} diff --git a/tools/local-storage/jest.setup.js b/tools/local-storage/jest.setup.js new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tools/local-storage/package.json b/tools/local-storage/package.json new file mode 100644 index 00000000000..93613d6e9ce --- /dev/null +++ b/tools/local-storage/package.json @@ -0,0 +1,28 @@ +{ + "name": "@chainlink/local-storage", + "private": true, + "version": "0.0.1", + "main": "./dist/src", + "types": "./dist/src", + "scripts": { + "build": "rimraf -rf dist && tsc", + "lint": "eslint --ext .ts .", + "format": "prettier --write \"*.ts\"", + "setup": "yarn build", + "test": "jest", + "test:ci": "yarn test --coverage --reporters jest-silent-reporter --maxWorkers=50%" + }, + "peerDependencies": {}, + "dependencies": { + "local-storage-fallback": "^4.1.1", + "typescript": "^3.6.3" + }, + "devDependencies": { + "@chainlink/eslint-config": "0.0.1", + "@types/jest": "^24.0.18", + "eslint": "^6.3.0", + "jest": "^24.9.0", + "rimraf": "^3.0.0", + "ts-jest": "^24.0.0" + } +} diff --git a/tools/local-storage/src/index.ts b/tools/local-storage/src/index.ts new file mode 100644 index 00000000000..69b61ecc471 --- /dev/null +++ b/tools/local-storage/src/index.ts @@ -0,0 +1 @@ +export * from './storage' diff --git a/tools/local-storage/src/storage.ts b/tools/local-storage/src/storage.ts new file mode 100644 index 00000000000..a97eabd2253 --- /dev/null +++ b/tools/local-storage/src/storage.ts @@ -0,0 +1,29 @@ +import storage from 'local-storage-fallback' + +export function get(key: string): string | null { + return storage.getItem(`chainlink.${key}`) +} + +export function set(key: string, val: string): void { + storage.setItem(`chainlink.${key}`, val) +} + +export function getJson(key: string): any { + const stored = get(key) + const obj = {} + + if (stored) { + try { + return JSON.parse(stored) + } catch (e) { + // continue regardless of error + } + } + + return obj +} + +export function setJson(key: string, obj: any): void { + const json = JSON.stringify(obj) + set(key, json) +} diff --git a/tools/local-storage/tsconfig.json b/tools/local-storage/tsconfig.json new file mode 100644 index 00000000000..38b42d2702d --- /dev/null +++ b/tools/local-storage/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "composite": true, + "declaration": true, + "declarationMap": true, + "strict": true, + "noEmitOnError": true, + "allowSyntheticDefaultImports": true, + "moduleResolution": "node", + "target": "es2017", + "module": "es2015", + "lib": ["es2018"], + "outDir": "./dist", + "jsx": "preserve", + "sourceMap": true, + "removeComments": false + }, + + "include": ["src", "@types"] +} From 2c2906af9739177e1f5534a0819a261de975003a Mon Sep 17 00:00:00 2001 From: Alex Kwiatkowski Date: Mon, 11 Nov 2019 08:49:31 -0800 Subject: [PATCH 188/199] Improve grammar for local storage test name --- tools/local-storage/__tests__/storage.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/local-storage/__tests__/storage.test.ts b/tools/local-storage/__tests__/storage.test.ts index 3b28ad9ba25..2b56f51fe88 100644 --- a/tools/local-storage/__tests__/storage.test.ts +++ b/tools/local-storage/__tests__/storage.test.ts @@ -22,7 +22,7 @@ describe('getJson', () => { expect(getJson('foo')).toEqual({ foo: 'FOO' }) }) - it('returns an empty JS object when not valid JSON', () => { + it('returns an empty JS object when it retrieves invalid JSON from storage', () => { storage.setItem('chainlink.foo', '{"foo"}') expect(getJson('foo')).toEqual({}) }) From 12d01dd1d3d0285349323c8be3e45c186d0a8e5f Mon Sep 17 00:00:00 2001 From: Alex Kwiatkowski Date: Mon, 11 Nov 2019 15:15:44 -0800 Subject: [PATCH 189/199] Combine url + authentication storage --- .../reducers/authentication.test.js | 12 ++++----- .../utils/authenticationStorage.test.js | 23 ---------------- operator_ui/__tests__/utils/storage.test.ts | 26 +++++++++++++++++++ operator_ui/src/App.tsx | 2 +- .../redux/reducers/authentication.ts | 13 ++++++---- operator_ui/src/containers/SignIn.js | 2 +- .../src/utils/authenticationStorage.ts | 13 ---------- .../src/utils/{urlStorage.ts => storage.ts} | 12 +++++++++ 8 files changed, 54 insertions(+), 49 deletions(-) delete mode 100644 operator_ui/__tests__/utils/authenticationStorage.test.js create mode 100644 operator_ui/__tests__/utils/storage.test.ts delete mode 100644 operator_ui/src/utils/authenticationStorage.ts rename operator_ui/src/utils/{urlStorage.ts => storage.ts} (52%) diff --git a/operator_ui/__tests__/connectors/reducers/authentication.test.js b/operator_ui/__tests__/connectors/reducers/authentication.test.js index 43fb6ff6a53..86f1880effb 100644 --- a/operator_ui/__tests__/connectors/reducers/authentication.test.js +++ b/operator_ui/__tests__/connectors/reducers/authentication.test.js @@ -1,5 +1,5 @@ import reducer from 'connectors/redux/reducers' -import { get as getAuthenticationStorage } from 'utils/authenticationStorage' +import { getAuthentication } from 'utils/storage' import { REQUEST_SIGNIN, RECEIVE_SIGNIN_SUCCESS, @@ -46,7 +46,7 @@ describe('connectors/reducers/authentication', () => { const action = { type: RECEIVE_SIGNIN_SUCCESS, authenticated: true } reducer(undefined, action) - expect(getAuthenticationStorage()).toEqual({ allowed: true }) + expect(getAuthentication()).toEqual({ allowed: true }) }) }) @@ -69,7 +69,7 @@ describe('connectors/reducers/authentication', () => { const action = { type: RECEIVE_SIGNIN_FAIL } reducer(undefined, action) - expect(getAuthenticationStorage()).toEqual({ allowed: false }) + expect(getAuthentication()).toEqual({ allowed: false }) }) }) @@ -92,7 +92,7 @@ describe('connectors/reducers/authentication', () => { const action = { type: RECEIVE_SIGNIN_ERROR } reducer(undefined, action) - expect(getAuthenticationStorage()).toEqual({ allowed: false }) + expect(getAuthentication()).toEqual({ allowed: false }) }) }) @@ -122,7 +122,7 @@ describe('connectors/reducers/authentication', () => { const action = { type: RECEIVE_SIGNOUT_SUCCESS, authenticated: false } reducer(undefined, action) - expect(getAuthenticationStorage()).toEqual({ allowed: false }) + expect(getAuthentication()).toEqual({ allowed: false }) }) }) @@ -145,7 +145,7 @@ describe('connectors/reducers/authentication', () => { const action = { type: RECEIVE_SIGNOUT_ERROR } reducer(undefined, action) - expect(getAuthenticationStorage()).toEqual({ allowed: false }) + expect(getAuthentication()).toEqual({ allowed: false }) }) }) }) diff --git a/operator_ui/__tests__/utils/authenticationStorage.test.js b/operator_ui/__tests__/utils/authenticationStorage.test.js deleted file mode 100644 index 285e081d928..00000000000 --- a/operator_ui/__tests__/utils/authenticationStorage.test.js +++ /dev/null @@ -1,23 +0,0 @@ -import { get, set } from 'utils/authenticationStorage' - -describe('utils/authenticationStorage', () => { - beforeEach(() => { - global.localStorage.clear() - }) - - describe('get', () => { - it('returns a JS object for JSON stored as "chainlink.authentication" in localStorage', () => { - global.localStorage.setItem('chainlink.authentication', '{"foo":"FOO"}') - expect(get()).toEqual({ foo: 'FOO' }) - }) - }) - - describe('set', () => { - it('saves the JS object as JSON under the key "chainlink.authentication" in localStorage', () => { - set({ foo: 'FOO' }) - expect(global.localStorage.getItem('chainlink.authentication')).toEqual( - '{"foo":"FOO"}', - ) - }) - }) -}) diff --git a/operator_ui/__tests__/utils/storage.test.ts b/operator_ui/__tests__/utils/storage.test.ts new file mode 100644 index 00000000000..748b38fc333 --- /dev/null +++ b/operator_ui/__tests__/utils/storage.test.ts @@ -0,0 +1,26 @@ +import { getAuthentication, setAuthentication } from '../../src/utils/storage' + +describe('utils/storage', () => { + beforeEach(() => { + global.localStorage.clear() + }) + + describe('getAuthentication', () => { + it('returns a JS object for JSON stored as "chainlink.authentication" in localStorage', () => { + global.localStorage.setItem( + 'chainlink.authentication', + '{"allowed":true}', + ) + expect(getAuthentication()).toEqual({ allowed: true }) + }) + }) + + describe('setAuthentication', () => { + it('saves the JS object as JSON under the key "chainlink.authentication" in localStorage', () => { + setAuthentication({ allowed: true }) + expect(global.localStorage.getItem('chainlink.authentication')).toEqual( + '{"allowed":true}', + ) + }) + }) +}) diff --git a/operator_ui/src/App.tsx b/operator_ui/src/App.tsx index 87773c54417..a39e56e6ffa 100644 --- a/operator_ui/src/App.tsx +++ b/operator_ui/src/App.tsx @@ -3,7 +3,7 @@ import { Provider } from 'react-redux' import createStore from './connectors/redux' import './index.css' import Layout from './Layout' -import { setPersistUrl } from './utils/urlStorage' +import { setPersistUrl } from './utils/storage' const SIGNIN_PATH = '/signin' diff --git a/operator_ui/src/connectors/redux/reducers/authentication.ts b/operator_ui/src/connectors/redux/reducers/authentication.ts index 8b0e2bd4aac..27762e6f31d 100644 --- a/operator_ui/src/connectors/redux/reducers/authentication.ts +++ b/operator_ui/src/connectors/redux/reducers/authentication.ts @@ -1,4 +1,4 @@ -import * as authenticationStorage from 'utils/authenticationStorage' +import * as storage from 'utils/storage' const defaultState = { allowed: false, @@ -9,7 +9,7 @@ const defaultState = { const initialState = Object.assign( {}, defaultState, - authenticationStorage.get(), + storage.getAuthentication(), ) export type AuthenticationAction = @@ -53,7 +53,8 @@ export default (state = initialState, action: AuthenticationAction) => { case AuthenticationActionType.RECEIVE_SIGNOUT_SUCCESS: case AuthenticationActionType.RECEIVE_SIGNIN_SUCCESS: { const allowed = { allowed: action.authenticated } - authenticationStorage.set(allowed) + storage.setAuthentication(allowed) + return Object.assign({}, state, allowed, { errors: [], networkError: false, @@ -61,13 +62,15 @@ export default (state = initialState, action: AuthenticationAction) => { } case AuthenticationActionType.RECEIVE_SIGNIN_FAIL: { const allowed = { allowed: false } - authenticationStorage.set(allowed) + storage.setAuthentication(allowed) + return Object.assign({}, state, allowed, { errors: [] }) } case AuthenticationActionType.RECEIVE_SIGNIN_ERROR: case AuthenticationActionType.RECEIVE_SIGNOUT_ERROR: { const allowed = { allowed: false } - authenticationStorage.set(allowed) + storage.setAuthentication(allowed) + return Object.assign({}, state, allowed, { errors: action.errors || [], networkError: action.networkError, diff --git a/operator_ui/src/containers/SignIn.js b/operator_ui/src/containers/SignIn.js index 8d2c4eeeda8..8b1e4e51be8 100644 --- a/operator_ui/src/containers/SignIn.js +++ b/operator_ui/src/containers/SignIn.js @@ -13,7 +13,7 @@ import { hot } from 'react-hot-loader' import { submitSignIn } from 'actions' import HexagonLogo from 'components/Logos/Hexagon' import matchRouteAndMapDispatchToProps from 'utils/matchRouteAndMapDispatchToProps' -import { getPersistUrl } from '../utils/urlStorage' +import { getPersistUrl } from '../utils/storage' const styles = theme => ({ container: { diff --git a/operator_ui/src/utils/authenticationStorage.ts b/operator_ui/src/utils/authenticationStorage.ts deleted file mode 100644 index 1540bc1127a..00000000000 --- a/operator_ui/src/utils/authenticationStorage.ts +++ /dev/null @@ -1,13 +0,0 @@ -import * as storage from '@chainlink/local-storage' - -interface Auth { - allowed?: boolean -} - -export function get(): Auth { - return storage.getJson('authentication') -} - -export function set(auth: Auth): void { - storage.setJson('authentication', auth) -} diff --git a/operator_ui/src/utils/urlStorage.ts b/operator_ui/src/utils/storage.ts similarity index 52% rename from operator_ui/src/utils/urlStorage.ts rename to operator_ui/src/utils/storage.ts index b5743630eb0..a7616e954f6 100644 --- a/operator_ui/src/utils/urlStorage.ts +++ b/operator_ui/src/utils/storage.ts @@ -9,3 +9,15 @@ export function getPersistUrl(): string { export function setPersistUrl(url: string): void { storage.set(PERSIST_URL, url) } + +export interface Auth { + allowed?: boolean +} + +export function getAuthentication(): Auth { + return storage.getJson('authentication') +} + +export function setAuthentication(auth: Auth): void { + storage.setJson('authentication', auth) +} From 93d15ba63dd2dcaaacc9560cb3cfe91871eea971 Mon Sep 17 00:00:00 2001 From: Alex Kwiatkowski Date: Mon, 11 Nov 2019 23:08:14 -0800 Subject: [PATCH 190/199] Configure TS dom lib for local-storage --- tools/local-storage/@types/.gitkeep | 0 tools/local-storage/@types/globals.d.ts | 9 --------- tools/local-storage/tsconfig.json | 5 ++++- 3 files changed, 4 insertions(+), 10 deletions(-) delete mode 100644 tools/local-storage/@types/.gitkeep delete mode 100644 tools/local-storage/@types/globals.d.ts diff --git a/tools/local-storage/@types/.gitkeep b/tools/local-storage/@types/.gitkeep deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/tools/local-storage/@types/globals.d.ts b/tools/local-storage/@types/globals.d.ts deleted file mode 100644 index 122956575a0..00000000000 --- a/tools/local-storage/@types/globals.d.ts +++ /dev/null @@ -1,9 +0,0 @@ -declare namespace NodeJS { - interface Global { - localStorage: { - clear: Function - getItem: Function - setItem: Function - } - } -} diff --git a/tools/local-storage/tsconfig.json b/tools/local-storage/tsconfig.json index 38b42d2702d..eafa53e4f03 100644 --- a/tools/local-storage/tsconfig.json +++ b/tools/local-storage/tsconfig.json @@ -9,7 +9,10 @@ "moduleResolution": "node", "target": "es2017", "module": "es2015", - "lib": ["es2018"], + "lib": [ + "es2018", + "dom" + ], "outDir": "./dist", "jsx": "preserve", "sourceMap": true, From c94a2610b364efe3339038bbb6ec6505d2ae5c87 Mon Sep 17 00:00:00 2001 From: Alex Kwiatkowski Date: Mon, 11 Nov 2019 23:09:40 -0800 Subject: [PATCH 191/199] Add @chainlink/prettier-config to json-api-client & local-storage --- tools/json-api-client/package.json | 1 + tools/local-storage/package.json | 1 + 2 files changed, 2 insertions(+) diff --git a/tools/json-api-client/package.json b/tools/json-api-client/package.json index e79f15f00fc..1d9ac3d898f 100644 --- a/tools/json-api-client/package.json +++ b/tools/json-api-client/package.json @@ -22,6 +22,7 @@ }, "devDependencies": { "@chainlink/eslint-config": "0.0.1", + "@chainlink/prettier-config": "0.0.1", "@types/fetch-mock": "^7.3.1", "@types/jest": "^24.0.18", "@types/path-to-regexp": "^1.7.0", diff --git a/tools/local-storage/package.json b/tools/local-storage/package.json index 93613d6e9ce..3047aac36f3 100644 --- a/tools/local-storage/package.json +++ b/tools/local-storage/package.json @@ -19,6 +19,7 @@ }, "devDependencies": { "@chainlink/eslint-config": "0.0.1", + "@chainlink/prettier-config": "0.0.1", "@types/jest": "^24.0.18", "eslint": "^6.3.0", "jest": "^24.9.0", From 9ddd4514f4653452af14a7e7e86d97d669371e0f Mon Sep 17 00:00:00 2001 From: Alex Kwiatkowski Date: Mon, 11 Nov 2019 23:17:25 -0800 Subject: [PATCH 192/199] Remove unrequired prevURL check --- operator_ui/src/App.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/operator_ui/src/App.tsx b/operator_ui/src/App.tsx index a39e56e6ffa..9647fe77cf1 100644 --- a/operator_ui/src/App.tsx +++ b/operator_ui/src/App.tsx @@ -11,7 +11,7 @@ const store = createStore() store.subscribe(() => { const prevURL = store.getState().notifications.currentUrl - if (prevURL && prevURL !== SIGNIN_PATH) { + if (prevURL !== SIGNIN_PATH) { setPersistUrl(prevURL) } }) From 4d96f326f9c6569688277d6014b0160dff35e4dc Mon Sep 17 00:00:00 2001 From: Alex Kwiatkowski Date: Mon, 11 Nov 2019 17:12:49 -0800 Subject: [PATCH 193/199] Improve bridge form persistence - Save form input on error - Clear saved input on success - Show changed form nav warning when different to initial or persisted --- operator_ui/src/components/Bridges/Form.tsx | 97 ++++++++++++------- .../components/Notifications/DefaultError.js | 1 - .../components/Notifications/DefaultError.tsx | 9 ++ tools/local-storage/__tests__/storage.test.ts | 30 ++++-- tools/local-storage/src/storage.ts | 4 + 5 files changed, 95 insertions(+), 46 deletions(-) delete mode 100644 operator_ui/src/components/Notifications/DefaultError.js create mode 100644 operator_ui/src/components/Notifications/DefaultError.tsx diff --git a/operator_ui/src/components/Bridges/Form.tsx b/operator_ui/src/components/Bridges/Form.tsx index b01e721cb3d..80e60effe8b 100644 --- a/operator_ui/src/components/Bridges/Form.tsx +++ b/operator_ui/src/components/Bridges/Form.tsx @@ -5,12 +5,14 @@ import { withStyles, WithStyles, } from '@material-ui/core/styles' -import Button from 'components/Button' +import * as storage from '@chainlink/local-storage' import { withFormik, FormikProps, Form as FormikForm } from 'formik' import normalizeUrl from 'normalize-url' import React from 'react' import { Prompt } from 'react-router-dom' -import * as storage from '@chainlink/local-storage' +import isEmpty from 'lodash/isEmpty' +import isEqual from 'lodash/isEqual' +import Button from 'components/Button' const styles = (theme: Theme) => createStyles({ @@ -30,25 +32,9 @@ const styles = (theme: Theme) => }, }) -const isDirty = ({ values, submitCount }: Props) => { - return ( - (values.name !== '' || - values.url !== '' || - (values.minimumContractPayment !== '0' && values.confirmations !== 0)) && - submitCount === 0 - ) -} +const SUBMITTING_TIMEOUT_MS = 1000 +const UNSAVED_BRIDGE = 'persistBridge' -// CHECKME -interface OwnProps extends Partial, WithStyles { - actionText: string - nameDisabled?: boolean - onSubmit: any - onSuccess: any - onError: any -} - -// CHECKME interface FormValues { name: string minimumContractPayment: string @@ -56,8 +42,56 @@ interface FormValues { url: string } +interface OwnProps extends Partial, WithStyles { + actionText: string + nameDisabled?: boolean + onSubmit: ( + values: FormValues, + onSuccesss: Function, + onError: Function, + ) => void + onSuccess: Function + onError: Function +} + type Props = FormikProps & OwnProps +function submitSuccess(callback: Function) { + return (response: object) => { + storage.remove(UNSAVED_BRIDGE) + return callback(response) + } +} + +function submitError(callback: Function, values: FormValues) { + return (error: object) => { + storage.setJson(UNSAVED_BRIDGE, values) + return callback(error) + } +} + +function initialValues({ + name, + url, + minimumContractPayment, + confirmations, +}: OwnProps): FormValues { + const unsavedBridge = storage.getJson(UNSAVED_BRIDGE) + const propValues = { + name: name || '', + url: url || '', + minimumContractPayment: minimumContractPayment || '0', + confirmations: confirmations || 0, + } + + return isEmpty(unsavedBridge) ? propValues : unsavedBridge +} + +function isDirty(props: Props): boolean { + const initial = initialValues(props) + return !isEqual(props.values, initial) && props.submitCount === 0 +} + const Form: React.SFC = props => ( <> ({ - mapPropsToValues({ name, url, minimumContractPayment, confirmations }) { - const shouldPersist = - Object.keys(storage.getJson('persistBridge')).length !== 0 - const persistedJSON = shouldPersist && storage.getJson('persistBridge') - if (shouldPersist) storage.setJson('persistBridge', {}) - const json: FormValues = { - name: name || '', - url: url || '', - minimumContractPayment: minimumContractPayment || '0', - confirmations: confirmations || 0, - } - return (shouldPersist && persistedJSON) || json + mapPropsToValues(ownProps) { + return initialValues(ownProps) }, handleSubmit(values, { props, setSubmitting }) { @@ -158,11 +182,14 @@ const WithFormikForm = withFormik({ } catch { values.url = '' } - props.onSubmit(values, props.onSuccess, props.onError) - storage.setJson('persistBridge', values) + props.onSubmit( + values, + submitSuccess(props.onSuccess), + submitError(props.onError, values), + ) setTimeout(() => { setSubmitting(false) - }, 1000) + }, SUBMITTING_TIMEOUT_MS) }, })(Form) diff --git a/operator_ui/src/components/Notifications/DefaultError.js b/operator_ui/src/components/Notifications/DefaultError.js deleted file mode 100644 index 33eb04abafe..00000000000 --- a/operator_ui/src/components/Notifications/DefaultError.js +++ /dev/null @@ -1 +0,0 @@ -export default ({ msg }) => msg diff --git a/operator_ui/src/components/Notifications/DefaultError.tsx b/operator_ui/src/components/Notifications/DefaultError.tsx new file mode 100644 index 00000000000..17cdda22eda --- /dev/null +++ b/operator_ui/src/components/Notifications/DefaultError.tsx @@ -0,0 +1,9 @@ +import React from 'react' + +interface Props { + msg: string +} + +export default function DefaultError({ msg }: Props) { + return msg +} diff --git a/tools/local-storage/__tests__/storage.test.ts b/tools/local-storage/__tests__/storage.test.ts index 2b56f51fe88..74b82b825a3 100644 --- a/tools/local-storage/__tests__/storage.test.ts +++ b/tools/local-storage/__tests__/storage.test.ts @@ -1,5 +1,5 @@ import storage from 'local-storage-fallback' -import { get, set, getJson, setJson } from '../src/storage' +import { get, set, remove, getJson, setJson } from '../src/storage' beforeEach(() => { storage.clear() @@ -16,6 +16,25 @@ describe('get', () => { }) }) +describe('set', () => { + it('saves the string keyed under "chainlink." in localStorage', () => { + set('foo', 'FOO') + + const stored = storage.getItem('chainlink.foo') + expect(stored).toEqual('FOO') + }) +}) + +describe('remove', () => { + it('deletes the chainlink key', () => { + storage.setItem('chainlink.foo', 'FOO') + expect(storage.getItem('chainlink.foo')).toEqual('FOO') + + remove('foo') + expect(storage.getItem('chainlink.foo')).toEqual(null) + }) +}) + describe('getJson', () => { it('returns a JS object for JSON keyed under "chainlink." in localStorage', () => { storage.setItem('chainlink.foo', '{"foo":"FOO"}') @@ -32,15 +51,6 @@ describe('getJson', () => { }) }) -describe('set', () => { - it('saves the string keyed under "chainlink." in localStorage', () => { - set('foo', 'FOO') - - const stored = storage.getItem('chainlink.foo') - expect(stored).toEqual('FOO') - }) -}) - describe('setJson', () => { it('saves the JS object as JSON keyed under "chainlink." in localStorage', () => { setJson('foo', { foo: 'FOO' }) diff --git a/tools/local-storage/src/storage.ts b/tools/local-storage/src/storage.ts index a97eabd2253..577efbd7895 100644 --- a/tools/local-storage/src/storage.ts +++ b/tools/local-storage/src/storage.ts @@ -8,6 +8,10 @@ export function set(key: string, val: string): void { storage.setItem(`chainlink.${key}`, val) } +export function remove(key: string): void { + storage.removeItem(`chainlink.${key}`) +} + export function getJson(key: string): any { const stored = get(key) const obj = {} From e51b342f7ee3a492196c2f11e212e4be83d1af93 Mon Sep 17 00:00:00 2001 From: Alex Kwiatkowski Date: Mon, 11 Nov 2019 22:55:57 -0800 Subject: [PATCH 194/199] Improve job spec form persistence - Save form input on error - Clear saved input on success - Show changed form nav warning when different to initial or persisted --- operator_ui/src/components/Jobs/Form.js | 121 ------------------ operator_ui/src/components/Jobs/Form.tsx | 153 +++++++++++++++++++++++ 2 files changed, 153 insertions(+), 121 deletions(-) delete mode 100644 operator_ui/src/components/Jobs/Form.js create mode 100644 operator_ui/src/components/Jobs/Form.tsx diff --git a/operator_ui/src/components/Jobs/Form.js b/operator_ui/src/components/Jobs/Form.js deleted file mode 100644 index 9c30d687963..00000000000 --- a/operator_ui/src/components/Jobs/Form.js +++ /dev/null @@ -1,121 +0,0 @@ -import React from 'react' -import PropTypes from 'prop-types' -import * as formik from 'formik' -import { withStyles } from '@material-ui/core/styles' -import Button from 'components/Button' -import { TextField, Grid } from '@material-ui/core' -import { Prompt } from 'react-router-dom' -import * as storage from '@chainlink/local-storage' - -const styles = theme => ({ - card: { - paddingBottom: theme.spacing.unit * 2, - }, - flash: { - textAlign: 'center', - paddingTop: theme.spacing.unit, - paddingBottom: theme.spacing.unit, - }, - button: { - marginTop: theme.spacing.unit * 2, - }, -}) - -const Form = ({ - actionText, - isSubmitting, - classes, - handleChange, - values, - touched, - errors, - submitCount, -}) => { - return ( - - - - - - - - - - - - - - ) -} - -Form.propTypes = { - actionText: PropTypes.string.isRequired, - onSubmit: PropTypes.func.isRequired, -} - -const formikOpts = { - mapPropsToValues({ definition }) { - const shouldPersist = - Object.keys(storage.getJson('persistSpec')).length !== 0 - const persistedJSON = shouldPersist && storage.getJson('persistSpec') - if (shouldPersist) { - storage.setJson('persistSpec', {}) - } - const json = - JSON.stringify(definition, null, '\t') || - (shouldPersist && persistedJSON) || - '' - return { json } - }, - - validate(values) { - const errors = {} - - try { - JSON.parse(values.json, null, '\t') - } catch (e) { - errors.json = 'Invalid JSON' - } - - return errors - }, - - handleSubmit(values, { props, setSubmitting }) { - const definition = JSON.parse(values.json) - storage.setJson('persistSpec', values.json) - props.onSubmit(definition, props.onSuccess, props.onError) - setTimeout(() => { - setSubmitting(false) - }, 1000) - }, -} - -const FormikForm = formik.withFormik(formikOpts)(Form) - -export default withStyles(styles)(FormikForm) diff --git a/operator_ui/src/components/Jobs/Form.tsx b/operator_ui/src/components/Jobs/Form.tsx new file mode 100644 index 00000000000..f0e2b28dcbf --- /dev/null +++ b/operator_ui/src/components/Jobs/Form.tsx @@ -0,0 +1,153 @@ +import React from 'react' +import { + createStyles, + Theme, + withStyles, + WithStyles, +} from '@material-ui/core/styles' +import { TextField, Grid } from '@material-ui/core' +import { withFormik, FormikProps, Form as FormikForm } from 'formik' +import * as storage from '@chainlink/local-storage' +import { Prompt } from 'react-router-dom' +import isEqual from 'lodash/isEqual' +import Button from 'components/Button' + +const styles = (theme: Theme) => + createStyles({ + card: { + paddingBottom: theme.spacing.unit * 2, + }, + flash: { + textAlign: 'center', + paddingTop: theme.spacing.unit, + paddingBottom: theme.spacing.unit, + }, + button: { + marginTop: theme.spacing.unit * 2, + }, + }) + +const SUBMITTING_TIMEOUT_MS = 1000 +const UNSAVED_JOB_SPEC = 'persistSpec' + +interface FormValues { + json: string +} + +interface OwnProps extends Partial, WithStyles { + definition: string + actionText: string + isSubmitting: boolean + handleChange: Function + errors: any + onSubmit: ( + values: FormValues, + onSuccesss: Function, + onError: Function, + ) => void + onSuccess: Function + onError: Function +} + +type Props = FormikProps & OwnProps + +function submitSuccess(callback: Function) { + return (response: object) => { + storage.remove(UNSAVED_JOB_SPEC) + return callback(response) + } +} + +function submitError(callback: Function, values: FormValues) { + return (error: object) => { + storage.set(UNSAVED_JOB_SPEC, values.json) + return callback(error) + } +} + +function initialValues({ json }: OwnProps): FormValues { + const unsavedJobSpec = storage.get(UNSAVED_JOB_SPEC) + return unsavedJobSpec ? { json: unsavedJobSpec } : { json: json || '' } +} + +function isDirty(props: Props): boolean { + const initial = initialValues(props) + return !isEqual(props.values, initial) && props.submitCount === 0 +} + +const Form: React.FC = props => { + return ( + <> + + + + + + + + + + + + + ) +} + +const WithFormikForm = withFormik({ + mapPropsToValues({ definition }) { + const json = + JSON.stringify(definition, null, '\t') || + storage.get(UNSAVED_JOB_SPEC) || + '' + return { json } + }, + + validate(values) { + try { + JSON.parse(values.json) + return {} + } catch { + return { json: 'Invalid JSON' } + } + }, + + handleSubmit(values, { props, setSubmitting }) { + const definition = JSON.parse(values.json) + props.onSubmit( + definition, + submitSuccess(props.onSuccess), + submitError(props.onError, values), + ) + setTimeout(() => { + setSubmitting(false) + }, SUBMITTING_TIMEOUT_MS) + }, +})(Form) + +export default withStyles(styles)(WithFormikForm) From 612e94db524e9e581098d6274db3d4628edefbdb Mon Sep 17 00:00:00 2001 From: John Barker Date: Tue, 12 Nov 2019 15:10:04 -0700 Subject: [PATCH 195/199] Bump version --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index 1a5ac0d409c..faef31a4357 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.6.9 +0.7.0 From 90a603cd1fad877ee7ceca436d436d401ae50c69 Mon Sep 17 00:00:00 2001 From: John Barker Date: Tue, 12 Nov 2019 17:14:17 -0700 Subject: [PATCH 196/199] MinPayment null is not usefully distinct from nil --- core/internal/features_test.go | 2 +- core/services/run_manager.go | 5 ++- core/services/runs.go | 2 +- core/services/validators.go | 2 +- core/store/assets/currencies.go | 38 +++++++++++++++++++++ core/store/assets/currencies_test.go | 12 +++---- core/store/migrations/migrate_test.go | 6 ++-- core/store/models/job_spec.go | 24 ++++++------- core/store/models/job_spec_test.go | 6 ++-- core/store/models/service_agreement.go | 6 ++-- core/store/models/service_agreement_test.go | 26 +++++++------- core/store/presenters/presenters.go | 21 +++++------- 12 files changed, 91 insertions(+), 59 deletions(-) diff --git a/core/internal/features_test.go b/core/internal/features_test.go index 85f04ca46b2..a92cd77ecd5 100644 --- a/core/internal/features_test.go +++ b/core/internal/features_test.go @@ -677,7 +677,7 @@ func TestIntegration_CreateServiceAgreement(t *testing.T) { assert.NotEqual(t, "", sa.ID) j := cltest.FindJob(t, app.Store, sa.JobSpecID) - assert.Equal(t, cltest.NewLink(t, "1000000000000000000"), sa.Encumbrance.Payment) + assert.Equal(t, *cltest.NewLink(t, "1000000000000000000"), sa.Encumbrance.Payment) assert.Equal(t, uint64(300), sa.Encumbrance.Expiration) assert.Equal(t, endAt, sa.Encumbrance.EndAt.Time) diff --git a/core/services/run_manager.go b/core/services/run_manager.go index 9bcfa426fa3..3bd15ddf6a1 100644 --- a/core/services/run_manager.go +++ b/core/services/run_manager.go @@ -89,7 +89,7 @@ func newRun( run.CreationHeight = models.NewBig(currentHeight) run.ObservedHeight = models.NewBig(currentHeight) - if !MeetsMinimumPayment(job.MinPayment, payment) { + if !MeetsMinimumPayment(&job.MinPayment, payment) { logger.Infow("Rejecting run with insufficient payment", run.ForLogger( "input_payment", payment, @@ -104,7 +104,7 @@ func newRun( } cost := &assets.Link{} - cost.Set(job.MinPayment) + cost.Set(&job.MinPayment) for i, taskRun := range run.TaskRuns { adapter, err := adapters.For(taskRun.TaskSpec, config, orm) @@ -321,7 +321,6 @@ func (jm *runManager) ResumePending( // // To recap: This must run before anything else writes job run status to the db, // ie. tries to run a job. -// https://chainlink/pull/807 func (jm *runManager) ResumeAllInProgress() error { return jm.orm.UnscopedJobRunsWithStatus(jm.runQueue.Run, models.RunStatusInProgress, models.RunStatusPendingSleep) } diff --git a/core/services/runs.go b/core/services/runs.go index b7692935a89..c0315425dad 100644 --- a/core/services/runs.go +++ b/core/services/runs.go @@ -18,7 +18,7 @@ func MeetsMinimumPayment( expectedMinJobPayment *assets.Link, actualRunPayment *assets.Link) bool { // input.Payment is always present for runs triggered by ethlogs - if actualRunPayment == nil || expectedMinJobPayment == nil || expectedMinJobPayment.IsZero() { + if actualRunPayment == nil || expectedMinJobPayment.IsZero() { return true } return expectedMinJobPayment.Cmp(actualRunPayment) < 1 diff --git a/core/services/validators.go b/core/services/validators.go index 1172b2da4bf..967433f4769 100644 --- a/core/services/validators.go +++ b/core/services/validators.go @@ -155,7 +155,7 @@ func ValidateServiceAgreement(sa models.ServiceAgreement, store *store.Store) er fe := models.NewJSONAPIErrors() config := store.Config - if sa.Encumbrance.Payment == nil { + if sa.Encumbrance.Payment.IsZero() { fe.Add("Service agreement encumbrance error: No payment amount set") } else if sa.Encumbrance.Payment.Cmp(config.MinimumContractPayment()) == -1 { fe.Add(fmt.Sprintf("Service agreement encumbrance error: Payment amount is below minimum %v", config.MinimumContractPayment().String())) diff --git a/core/store/assets/currencies.go b/core/store/assets/currencies.go index 51891ac1181..79da64b6dd3 100644 --- a/core/store/assets/currencies.go +++ b/core/store/assets/currencies.go @@ -1,7 +1,9 @@ package assets import ( + "chainlink/core/utils" "database/sql/driver" + "errors" "fmt" "math/big" @@ -9,6 +11,8 @@ import ( "github.com/willf/pad" ) +var ErrNoQuotesForCurrency = errors.New("cannot unmarshal json.Number into currency") + func format(i *big.Int, precision int) string { v := "1" + pad.Right("", precision, "0") d := &big.Int{} @@ -83,6 +87,23 @@ func (l *Link) MarshalText() ([]byte, error) { return (*big.Int)(l).MarshalText() } +// MarshalJSON implements the json.Marshaler interface. +func (l Link) MarshalJSON() ([]byte, error) { + value, err := l.MarshalText() + if err != nil { + return nil, err + } + return []byte(fmt.Sprintf(`"%s"`, value)), nil +} + +// UnmarshalJSON implements the json.Unmarshaler interface. +func (l *Link) UnmarshalJSON(data []byte) error { + if utils.IsQuoted(data) { + return l.UnmarshalText(utils.RemoveQuotes(data)) + } + return ErrNoQuotesForCurrency +} + // UnmarshalText implements the encoding.TextUnmarshaler interface. func (l *Link) UnmarshalText(text []byte) error { if _, ok := l.SetString(string(text), 10); !ok { @@ -161,11 +182,28 @@ func (e *Eth) SetString(s string, base int) (*Eth, bool) { return (*Eth)(w), ok } +// MarshalJSON implements the json.Marshaler interface. +func (e Eth) MarshalJSON() ([]byte, error) { + value, err := e.MarshalText() + if err != nil { + return nil, err + } + return []byte(fmt.Sprintf(`"%s"`, value)), nil +} + // MarshalText implements the encoding.TextMarshaler interface. func (e *Eth) MarshalText() ([]byte, error) { return e.ToInt().MarshalText() } +// UnmarshalJSON implements the json.Unmarshaler interface. +func (e *Eth) UnmarshalJSON(data []byte) error { + if utils.IsQuoted(data) { + return e.UnmarshalText(utils.RemoveQuotes(data)) + } + return ErrNoQuotesForCurrency +} + // UnmarshalText implements the encoding.TextUnmarshaler interface. func (e *Eth) UnmarshalText(text []byte) error { if _, ok := e.SetString(string(text), 10); !ok { diff --git a/core/store/assets/currencies_test.go b/core/store/assets/currencies_test.go index 9b396785c7b..079f30ddcbd 100644 --- a/core/store/assets/currencies_test.go +++ b/core/store/assets/currencies_test.go @@ -57,11 +57,11 @@ func TestAssets_Link_UnmarshalJsonError(t *testing.T) { link := assets.Link{} - err := json.Unmarshal([]byte(`"a"`), &link) - assert.EqualError(t, err, "assets: cannot unmarshal \"a\" into a *assets.Link") + err := json.Unmarshal([]byte(`"x"`), &link) + assert.EqualError(t, err, "assets: cannot unmarshal \"x\" into a *assets.Link") err = json.Unmarshal([]byte(`1`), &link) - assert.EqualError(t, err, "json: cannot unmarshal number into Go value of type *assets.Link") + assert.Equal(t, assets.ErrNoQuotesForCurrency, err) } func TestAssets_NewEthAndString(t *testing.T) { @@ -119,9 +119,9 @@ func TestAssets_Eth_UnmarshalJsonError(t *testing.T) { eth := assets.Eth{} - err := json.Unmarshal([]byte(`"a"`), ð) - assert.EqualError(t, err, "assets: cannot unmarshal \"a\" into a *assets.Eth") + err := json.Unmarshal([]byte(`"x"`), ð) + assert.EqualError(t, err, "assets: cannot unmarshal \"x\" into a *assets.Eth") err = json.Unmarshal([]byte(`1`), ð) - assert.EqualError(t, err, "json: cannot unmarshal number into Go value of type *assets.Eth") + assert.Equal(t, assets.ErrNoQuotesForCurrency, err) } diff --git a/core/store/migrations/migrate_test.go b/core/store/migrations/migrate_test.go index 90a19485ee8..6afff5bf880 100644 --- a/core/store/migrations/migrate_test.go +++ b/core/store/migrations/migrate_test.go @@ -165,7 +165,7 @@ func TestMigrate_Migration1565139192(t *testing.T) { specNoPayment := models.NewJobFromRequest(models.JobSpecRequest{}) specWithPayment := models.NewJobFromRequest(models.JobSpecRequest{ - MinPayment: assets.NewLink(5), + MinPayment: *assets.NewLink(5), }) specOneFound := models.JobSpec{} specTwoFound := models.JobSpec{} @@ -173,9 +173,9 @@ func TestMigrate_Migration1565139192(t *testing.T) { require.NoError(t, db.Create(&specWithPayment).Error) require.NoError(t, db.Create(&specNoPayment).Error) require.NoError(t, db.Where("id = ?", specNoPayment.ID).Find(&specOneFound).Error) - require.Equal(t, assets.NewLink(0), specNoPayment.MinPayment) + require.Equal(t, *assets.NewLink(0), specNoPayment.MinPayment) require.NoError(t, db.Where("id = ?", specWithPayment.ID).Find(&specTwoFound).Error) - require.Equal(t, assets.NewLink(5), specWithPayment.MinPayment) + require.Equal(t, *assets.NewLink(5), specWithPayment.MinPayment) } func TestMigrate_Migration1565210496(t *testing.T) { diff --git a/core/store/models/job_spec.go b/core/store/models/job_spec.go index 50cb431f40b..295e4134fc1 100644 --- a/core/store/models/job_spec.go +++ b/core/store/models/job_spec.go @@ -23,7 +23,7 @@ type JobSpecRequest struct { Tasks []TaskSpecRequest `json:"tasks"` StartAt null.Time `json:"startAt"` EndAt null.Time `json:"endAt"` - MinPayment *assets.Link `json:"minPayment"` + MinPayment assets.Link `json:"minPayment"` } // InitiatorRequest represents a schema for incoming initiator requests as used by the API. @@ -65,14 +65,14 @@ type TaskSpecRequest struct { // for a given contract. It contains the Initiators, Tasks (which are the // individual steps to be carried out), StartAt, EndAt, and CreatedAt fields. type JobSpec struct { - ID *ID `json:"id,omitempty" gorm:"primary_key;not null"` - CreatedAt time.Time `json:"createdAt" gorm:"index"` - Initiators []Initiator `json:"initiators"` - MinPayment *assets.Link `json:"minPayment" gorm:"type:varchar(255)"` - Tasks []TaskSpec `json:"tasks"` - StartAt null.Time `json:"startAt" gorm:"index"` - EndAt null.Time `json:"endAt" gorm:"index"` - DeletedAt null.Time `json:"-" gorm:"index"` + ID *ID `json:"id,omitempty" gorm:"primary_key;not null"` + CreatedAt time.Time `json:"createdAt" gorm:"index"` + Initiators []Initiator `json:"initiators"` + MinPayment assets.Link `json:"minPayment" gorm:"type:varchar(255)"` + Tasks []TaskSpec `json:"tasks"` + StartAt null.Time `json:"startAt" gorm:"index"` + EndAt null.Time `json:"endAt" gorm:"index"` + DeletedAt null.Time `json:"-" gorm:"index"` } // GetID returns the ID of this structure for jsonapi serialization. @@ -96,7 +96,7 @@ func NewJob() JobSpec { return JobSpec{ ID: NewID(), CreatedAt: time.Now(), - MinPayment: assets.NewLink(0), + MinPayment: *assets.NewLink(0), } } @@ -119,9 +119,7 @@ func NewJobFromRequest(jsr JobSpecRequest) JobSpec { jobSpec.EndAt = jsr.EndAt jobSpec.StartAt = jsr.StartAt - if jsr.MinPayment != nil { - jobSpec.MinPayment = jsr.MinPayment - } + jobSpec.MinPayment = jsr.MinPayment return jobSpec } diff --git a/core/store/models/job_spec_test.go b/core/store/models/job_spec_test.go index 4d37ab1d954..d892b4e229d 100644 --- a/core/store/models/job_spec_test.go +++ b/core/store/models/job_spec_test.go @@ -132,7 +132,7 @@ func TestNewJobFromRequest(t *testing.T) { Tasks: cltest.BuildTaskRequests(t, j1.Tasks), StartAt: j1.StartAt, EndAt: j1.EndAt, - MinPayment: assets.NewLink(5), + MinPayment: *assets.NewLink(5), } j2 := models.NewJobFromRequest(jsr) @@ -142,13 +142,13 @@ func TestNewJobFromRequest(t *testing.T) { assert.NoError(t, err) assert.Len(t, fetched1.Initiators, 1) assert.Len(t, fetched1.Tasks, 1) - assert.Equal(t, fetched1.MinPayment, assets.NewLink(0)) + assert.Equal(t, fetched1.MinPayment, *assets.NewLink(0)) fetched2, err := store.FindJob(j2.ID) assert.NoError(t, err) assert.Len(t, fetched2.Initiators, 1) assert.Len(t, fetched2.Tasks, 1) - assert.Equal(t, fetched2.MinPayment, assets.NewLink(5)) + assert.Equal(t, fetched2.MinPayment, *assets.NewLink(5)) } func TestJobSpec_Save(t *testing.T) { diff --git a/core/store/models/service_agreement.go b/core/store/models/service_agreement.go index 1d6281e6f8c..0f159bc354e 100644 --- a/core/store/models/service_agreement.go +++ b/core/store/models/service_agreement.go @@ -21,7 +21,7 @@ type Encumbrance struct { // Corresponds to requestDigest in solidity ServiceAgreement struct ID uint `json:"-" gorm:"primary_key;auto_increment"` // Price to request a report based on this agreement - Payment *assets.Link `json:"payment" gorm:"type:varchar(255)"` + Payment assets.Link `json:"payment" gorm:"type:varchar(255)"` // Expiration is the amount of time an oracle has to answer a request Expiration uint64 `json:"expiration"` // Agreement is valid until this time @@ -60,7 +60,7 @@ type ServiceAgreement struct { type ServiceAgreementRequest struct { Initiators []InitiatorRequest `json:"initiators"` Tasks []TaskSpecRequest `json:"tasks"` - Payment *assets.Link `json:"payment"` + Payment assets.Link `json:"payment"` Expiration uint64 `json:"expiration"` EndAt AnyTime `json:"endAt"` Oracles EIP55AddressCollection `json:"oracles"` @@ -186,7 +186,7 @@ func generateSAID(e Encumbrance, digest common.Hash) (common.Hash, error) { func (e Encumbrance) ABI(digest common.Hash) ([]byte, error) { buffer := bytes.Buffer{} var paymentHash common.Hash - if e.Payment != nil { + if !e.Payment.IsZero() { paymentHash = e.Payment.ToHash() } _, err := buffer.Write(paymentHash.Bytes()) diff --git a/core/store/models/service_agreement_test.go b/core/store/models/service_agreement_test.go index 4b071f8d683..f235a605b11 100644 --- a/core/store/models/service_agreement_test.go +++ b/core/store/models/service_agreement_test.go @@ -21,7 +21,7 @@ func TestNewUnsignedServiceAgreementFromRequest(t *testing.T) { name string input string wantDigest string - wantPayment *assets.Link + wantPayment assets.Link }{ { "basic", @@ -35,7 +35,7 @@ func TestNewUnsignedServiceAgreementFromRequest(t *testing.T) { `"aggFulfillSelector":"0x87654321"` + `}`, "0xad12826461f2259eac07e762d9f1d32dd6af2e4ed0797b08cb3d8a8c3c4dd61d", - assets.NewLink(1), + *assets.NewLink(1), }, } for _, test := range tests { @@ -58,7 +58,7 @@ func TestBuildServiceAgreement(t *testing.T) { name string input string wantDigest string - wantPayment *assets.Link + wantPayment assets.Link }{ { "basic", @@ -72,7 +72,7 @@ func TestBuildServiceAgreement(t *testing.T) { `"aggFulfillSelector":"0x87654321"` + `}`, "0xad12826461f2259eac07e762d9f1d32dd6af2e4ed0797b08cb3d8a8c3c4dd61d", - assets.NewLink(1), + *assets.NewLink(1), }, } for _, test := range tests { @@ -102,7 +102,7 @@ func TestEncumbrance_ABI(t *testing.T) { tests := []struct { name string - payment *assets.Link + payment assets.Link expiration int endAt models.AnyTime oracles []models.EIP55Address @@ -111,7 +111,7 @@ func TestEncumbrance_ABI(t *testing.T) { aggFulfillSelector string want string }{ - {"basic", assets.NewLink(1), 2, models.AnyTime{}, nil, + {"basic", *assets.NewLink(1), 2, models.AnyTime{}, nil, "0x0000000000000000000000000000000000000000000000000000000000000000", "0x00000000", "0x00000000", "0x" + "0000000000000000000000000000000000000000000000000000000000000001" + // Payment @@ -121,7 +121,7 @@ func TestEncumbrance_ABI(t *testing.T) { "0000000000000000000000000000000000000000" + // Aggregator address "00000000" + "00000000", // Function selectors }, - {"basic dead beef payment", assets.NewLink(3735928559), 2, models.AnyTime{}, nil, + {"basic dead beef payment", *assets.NewLink(3735928559), 2, models.AnyTime{}, nil, "0x0000000000000000000000000000000000000000000000000000000000000000", "0x00000000", "0x00000000", "0x" + "00000000000000000000000000000000000000000000000000000000deadbeef" + // Payment @@ -131,7 +131,7 @@ func TestEncumbrance_ABI(t *testing.T) { "0000000000000000000000000000000000000000" + // Aggregator address "00000000" + "00000000", // Function selectors }, - {"empty", nil, 0, models.AnyTime{}, nil, + {"empty", *assets.NewLink(0), 0, models.AnyTime{}, nil, "0x0000000000000000000000000000000000000000000000000000000000000000", "0x00000000", "0x00000000", "0x" + "0000000000000000000000000000000000000000000000000000000000000000" + // Payment @@ -141,7 +141,7 @@ func TestEncumbrance_ABI(t *testing.T) { "0000000000000000000000000000000000000000" + // Aggregator address "00000000" + "00000000", // Function selectors }, - {"oracle address", nil, 0, models.AnyTime{}, + {"oracle address", *assets.NewLink(0), 0, models.AnyTime{}, []models.EIP55Address{models.EIP55Address("0xa0788FC17B1dEe36f057c42B6F373A34B014687e")}, "0x0000000000000000000000000000000000000000000000000000000000000000", "0x00000000", "0x00000000", "0x" + @@ -153,7 +153,7 @@ func TestEncumbrance_ABI(t *testing.T) { "0000000000000000000000000000000000000000" + // Aggregator address "00000000" + "00000000", // Function selectors }, - {"different endAt", nil, 0, models.NewAnyTime(endAt), + {"different endAt", *assets.NewLink(0), 0, models.NewAnyTime(endAt), []models.EIP55Address{models.EIP55Address("0xa0788FC17B1dEe36f057c42B6F373A34B014687e")}, "0x0000000000000000000000000000000000000000000000000000000000000000", "0x00000000", "0x00000000", "0x" + @@ -165,7 +165,7 @@ func TestEncumbrance_ABI(t *testing.T) { "0000000000000000000000000000000000000000" + // Aggregator address "00000000" + "00000000", // Function selectors }, - {name: "aggregator info", payment: nil, expiration: 0, endAt: models.NewAnyTime(endAt), + {name: "aggregator info", expiration: 0, endAt: models.NewAnyTime(endAt), oracles: []models.EIP55Address{ models.EIP55Address("0xa0788FC17B1dEe36f057c42B6F373A34B014687e"), }, @@ -210,7 +210,7 @@ func TestServiceAgreementRequest_UnmarshalJSON(t *testing.T) { name string input string wantDigest string - wantPayment *assets.Link + wantPayment assets.Link }{ { "basic", @@ -225,7 +225,7 @@ func TestServiceAgreementRequest_UnmarshalJSON(t *testing.T) { `"endAt":"2018-06-19T22:17:19Z"}` + `}`, "0x57bf5be3447b9a3f8491b6538b01f828bcfcaf2d685ea90375ed4ec2943f4865", - assets.NewLink(1), + *assets.NewLink(1), }, } for _, test := range tests { diff --git a/core/store/presenters/presenters.go b/core/store/presenters/presenters.go index 98cdf21db3b..646d83f861a 100644 --- a/core/store/presenters/presenters.go +++ b/core/store/presenters/presenters.go @@ -196,15 +196,15 @@ func NewConfigWhitelist(store *store.Store) (ConfigWhitelist, error) { MinIncomingConfirmations: config.MinIncomingConfirmations(), MinOutgoingConfirmations: config.MinOutgoingConfirmations(), OracleContractAddress: config.OracleContractAddress(), - Port: config.Port(), - ReaperExpiration: config.ReaperExpiration(), - ReplayFromBlock: config.ReplayFromBlock(), - RootDir: config.RootDir(), - SessionTimeout: config.SessionTimeout(), - TLSHost: config.TLSHost(), - TLSPort: config.TLSPort(), - TLSRedirect: config.TLSRedirect(), - TxAttemptLimit: config.TxAttemptLimit(), + Port: config.Port(), + ReaperExpiration: config.ReaperExpiration(), + ReplayFromBlock: config.ReplayFromBlock(), + RootDir: config.RootDir(), + SessionTimeout: config.SessionTimeout(), + TLSHost: config.TLSHost(), + TLSPort: config.TLSPort(), + TLSRedirect: config.TLSRedirect(), + TxAttemptLimit: config.TxAttemptLimit(), }, }, nil } @@ -308,9 +308,6 @@ func (job JobSpec) FriendlyEndAt() string { // FriendlyMinPayment returns a formatted string of the Job's // Minimum Link Payment threshold func (job JobSpec) FriendlyMinPayment() string { - if job.MinPayment == nil { - return assets.NewLink(0).Text(10) - } return job.MinPayment.Text(10) } From 22ce5fe657687ca47c2950d9622e2cd566090535 Mon Sep 17 00:00:00 2001 From: John Barker Date: Wed, 13 Nov 2019 16:27:56 -0700 Subject: [PATCH 197/199] Don't log result_data when task is completed, it's often too big --- core/services/run_executor.go | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/core/services/run_executor.go b/core/services/run_executor.go index 98479889c58..07ebbafb449 100644 --- a/core/services/run_executor.go +++ b/core/services/run_executor.go @@ -41,7 +41,7 @@ func (je *runExecutor) Execute(runID *models.ID) error { for taskIndex := range run.TaskRuns { taskRun := &run.TaskRuns[taskIndex] if !run.Status.Runnable() { - logger.Debugw("Task execution blocked", run.ForLogger("task", taskRun.ID.String())...) + logger.Debugw("Run execution blocked", run.ForLogger("task", taskRun.ID.String())...) break } @@ -50,11 +50,17 @@ func (je *runExecutor) Execute(runID *models.ID) error { } if meetsMinimumConfirmations(&run, taskRun, run.ObservedHeight) { + start := time.Now() + result := je.executeTask(&run, taskRun) taskRun.ApplyOutput(result) run.ApplyOutput(result) + elapsed := time.Since(start).Seconds() + + logger.Debugw(fmt.Sprintf("Executed task %s", taskRun.TaskSpec.Type), run.ForLogger("task", taskRun.ID.String(), "elapsed", elapsed)...) + } else { logger.Debugw("Pausing run pending confirmations", run.ForLogger("required_height", taskRun.MinimumConfirmations)..., @@ -105,15 +111,6 @@ func (je *runExecutor) executeTask(run *models.JobRun, taskRun *models.TaskRun) } input := *models.NewRunInput(run.ID, data, taskRun.Status) - - start := time.Now() result := adapter.Perform(input, je.store) - logger.Debugw(fmt.Sprintf("Executed task %s", taskCopy.Type), []interface{}{ - "task", taskRun.ID.String(), - "result", result.Status(), - "result_data", result.Data(), - "elapsed", time.Since(start).Seconds(), - }...) - return result } From 3386791b8e5f72ccdc161a5b778638db826b40ff Mon Sep 17 00:00:00 2001 From: John Barker Date: Wed, 13 Nov 2019 16:29:32 -0700 Subject: [PATCH 198/199] Only one log line per safe transaction receipt --- core/store/tx_manager.go | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/core/store/tx_manager.go b/core/store/tx_manager.go index 8bcdf587772..7413c7fda4a 100644 --- a/core/store/tx_manager.go +++ b/core/store/tx_manager.go @@ -578,15 +578,6 @@ func (txm *EthTxManager) processAttempt( switch state { case Safe: - logger.Debugw( - fmt.Sprintf("Tx #%d is %s", attemptIndex, state), - "txHash", txAttempt.Hash.String(), - "txID", txAttempt.TxID, - "receiptBlockNumber", receipt.BlockNumber.ToInt(), - "currentBlockNumber", blockHeight, - "receiptHash", receipt.Hash.Hex(), - ) - txm.updateLastSafeNonce(tx) return receipt, state, txm.handleSafe(tx, attemptIndex) @@ -686,7 +677,8 @@ func (txm *EthTxManager) handleSafe( ethBalance, linkBalance, balanceErr := txm.GetETHAndLINKBalances(tx.From) logger.Infow( - fmt.Sprintf("Tx #%d got minimum confirmations (%d)", attemptIndex, minimumConfirmations), + fmt.Sprintf("Tx #%d is safe", attemptIndex), + "minimumConfirmations", minimumConfirmations, "txHash", txAttempt.Hash.String(), "txID", txAttempt.TxID, "ethBalance", ethBalance, From 5fe7c38bbc65c1c32f0966f516b60161024d2104 Mon Sep 17 00:00:00 2001 From: John Barker Date: Wed, 13 Nov 2019 16:32:27 -0700 Subject: [PATCH 199/199] Correct import for migration1568390387/migrate.go --- core/store/migrations/migration1568390387/migrate.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core/store/migrations/migration1568390387/migrate.go b/core/store/migrations/migration1568390387/migrate.go index 6b614ced69d..959db1cc207 100644 --- a/core/store/migrations/migration1568390387/migrate.go +++ b/core/store/migrations/migration1568390387/migrate.go @@ -3,9 +3,10 @@ package migration1568390387 import ( "time" + "chainlink/core/store/assets" + "github.com/jinzhu/gorm" "github.com/pkg/errors" - "github.com/smartcontractkit/chainlink/core/store/assets" ) type Encumbrance struct {