Giter Club home page Giter Club logo

gnarly's Introduction

🛠 Status: Alpha, Postponed Development

gnarly is currently in 'hella alpha'. If you'd like to play around with it, check out the usage instructions below.

🤙 Gnarly

standard-readme compliant Open Source Love Build Status Coverage Status

Gnarly reduces blockchain events into a steady state with confidence.

And that’s fuckin’ gnarly.

💬 Join #gnarly in https://xlnt.chat if you're interested in chatting in real-time about the project.

Background

For a 15 minute talk about why projects like gnarly need to exist, watch this recording from BuildETH:

Matt at BuildETH

Reading state from a blockchain is unecessarily hard because data is never indexed in the format your client expects, often requiring n+1 queries to get the information you want, like token balances.

Gnarly takes all the data you care about, transforms it, and puts it somewhere else, in real-time. It also handles short-lived forks and helps you understand when, and how, and why your data was changed.

This means read-only operations are fast and efficient and can leverage the existing web developer tooling we've developed over the last 30 years—requesting all of a user's token balances takes milliseconds, not entire seconds due to individual requests to every token contract.

This model allows us to also tackle the "severe asychronicity" of Proof of Work networks: state changes take seconds or minutes to resolve before they can confidently be displayed to the user, so users are stuck with a terrible experience of laggy frontends, infinite spinners, and zero context into what's happening behind the scenes. Until a state change completes, users don't have confidence that they can move onto the next thing they were doing.

The Downsides and Tradeoffs when Using Gnarly

Gnarly is a centralized state store, so you immediately trade away decentralization for user experience. We have a plan (decentralized snapshots) to support client-side gnarly indexes, but this is still a ways away.

Syncing a gnarly indexer is _slow_; it's about 15x faster than real-time. So if we want to index all of the CryptoKitties, which have been around for 6 months, it'll take around 12 days.

Obviously, it can easily keep up with the 15 second block times offered by the Ethereum main and test networks, so if you run a gnarly indexer as you launch your product, you won't experience this issue. We're naturally working hard on maximizing gnarly's processing speed.

Description

To recap, the features of gnarly are that it:

  • allows your client to use a reactive data source for reading blockchain state,
  • produces this reactive data source in real-time as blocks are produced,
  • the state is shared, allowing for superior frontend user experiences (like removing an exchange listing once it's been purchased),
  • handles short-lived-forks, reorganizations, etc all behind the scenes,
  • if gnarly crashes, it can resume exactly where it left off by replaying patches to arrive at the current state (kinda like git! (or a blockchain!))
  • produces an append-only event log that informs the developer and the user about when and why a state change was made (use this for very nice user-facing notifications!)
  • (WIP) supports optimistic transactions for highly real-time, reactive clients

The simple description of gnarly is that it's a single-process stream-processor (aka a real-time extra-transform-load) tool for atomic events, following the solid-state-interpreter pattern, poplarized by Urbit.

Gnarly ingests blocks (either histoical blocks or in real-time) transforms your data, and then loads that data into something else (like postgres, redshift, or elasticsearch).

The way you tell gnarly how to produce the data you care about is via a reducer. For example, we have already made a few reducers like

You can then integrate these indexes (which are just normal postgres tables!) into your application. For example, see XLNT/paperboy for a resilient event websocket powered by gnarly.

Setup

# clone this project
git clone [email protected]:XLNT/gnarly.git

# cd into it
cd gnarly

# install yarn if you haven't already
# $ npm i -g yarn

# install workspace dependencies, which includes lerna
yarn install

# boostrap the packages within this project (install deps, linking, etc)
lerna bootstrap

# now this command should pass:
yarn run build-ts

Now you should be able to run the tests with

yarn run test

Running

If you're a developer that would like to use gnarly, you can use the gnarly-bin project. The gnarly-bin project is a configuration-friendly approach for using gnarly. By telling it which reducers you care about, it produces a linux- and macos-friendly docker container that you can get started with immediately.

Note: Right now, gnarly-bin doesn't actually do any of the configuration stuff; it's just some code. See here for how it works. Curently gnarly-bin is just configured with the above reducers to monitor CryptoKitty events and block metadata.

Building a Gnarly Binary

To build the project in gnarly-bin, do the following:

# build the typescript files
yarn run build-ts

# build a linux- and macos- binary
yarn run pkg

# build a docker container to run that binary
yarn run docker-build

# push that docker container
yarn run docker-push

# (or just do it all at once)
# $ yarn run deploy

Developer Installation / Setup

Here's what I do when I'm manually testing gnarly:

# //packages/gnarly-bin/.env
DEBUG=*
# ^ which logs do you want to see? * means all of them. See node-debug for info
NODE_ENDPOINT=http://localhost:8545
# ^ point it at an Ethereum node like ganache, Infura, or a personal node
DB_CONNECTION_STRING=postgresql://postgres@localhost:5432/default
# ^ point it at an output store (like postgres)
GNARLY_RESET=false
# ^ if GNARLY_RESET=true, gnarly-bin will nuke the output store before running
LATEST_BLOCK_HASH=
# ^ set this to a block hash if you want gnarly to run from a specific block

# note that you can remove logs by using the -prefix:* syntax
# like: "DEBUG=*,-sequelize:*"
# in one terminal window from //
yarn run watch-ts

# in one terminal window from //packages/gnarly-bin
yarn run ts-start

And then your gnarly-bin project will be running with local code changes.

Developer Scripts

Want to watch all of the files and recompile the typescript?

yarn run watch-ts

Want to build all of the typescript projects once?

yarn run build-ts

Writing a Reducer

If the first-party reducers don't cover your needs, you can easily write your own reducer and plug it into your gnarly instance.

‼ This section will almost definitely be out of date during the alpha! Use the source code as the source of truth for documentation until the internal API becomes stable.

Look at gnarly-reducer-erc721 or gnarly-reducer-events or gnarly-reducer-block-meta for inspiration and up-to-date examples, but here we go!

A reducer is a way to tell gnarly how to change the state you manage. You also include a TypeStore which tells gnarly how to store the state you're producing.

Here's an example of a reducer to track events:

import {
  addABI,
  appendTo,
  because,
  Block,
  emit,
  forEach,
  getLogs,
  IABIItemInput,
  ILog,
  IReducer,
  ReducerType,
  toHex,
} from '@xlnt/gnarly-core'
import flatten = require('arr-flatten')

const makeReducer = (
  key: string,
  config: { [_: string]: IABIItemInput[] } = {},
): IReducer => {
  const addrs = Object.keys(config)

  // add the abis to the global registry
  // this is how we determine if this event is one we care about or not
  for (const addr of addrs) {
    addABI(addr, config[addr])
  }

  // given a state, build a set of actions that operate over that state
  //   in this case, we don't have any mutable state! so `state` isn't
  //    actually used here
  // see gnarly-reducer-erc721 for an example of using mutable state
  const makeActions = (state: object) => ({
    // define an `emit` action
    emit: (log: ILog) => {
      // this emit action uses gnarly.emit to produce an immutable
      //   append operation to the events domain within the reducer's key
      //   this operation includes all of the information your TypeStore needs
      emit(appendTo('events', {
        address: log.address,
        event: log.event,
        eventName: log.eventName,
        signature: log.signature,
        args: log.args,
      }))
    },
  })

  // we give gnarly a ReducerConfig, which tells it how this reducer
  //   operates and should be run
  return {
    config: {
      // this reducer is an Atomic reducer
      //   (i.e., it doesn't care about _when_ it is run and doesn't
      //    operate on past information)
      type: ReducerType.Atomic,
      // it has a key of `key`, necessary to scope its operations
      //   in the database
      key,
    },
    state: {},
    // the reduction function! accept the previous state and the block
    // and produce changes to the state
    reduce: async (state: object, block: Block): Promise<void> => {
      // let's build our actions from above
      const actions = makeActions(state)

      // let's pull the logs for every address we care about this block
      const logs = await forEach(addrs, async (addr) => getLogs({
        fromBlock: toHex(block.number),
        toBlock: toHex(block.number),
        address: addr,
      }))

      // then we'll look through those logs for ones that we recognize
      flatten(logs).forEach((log) => {
        const recognized = log.parse()
        if (recognized) {
          // and then emit them!
          because('EVENT_EMITTED', {}, () => {
            actions.emit(log)
          })
        }
      })
    },
  }
}

export default makeReducer

That's how easy it is to make a reducer to track events on Ethereum. This reducer automatically stays up to date with the latest block and all those other fun features from above. Neat!

Look in the gnarly-reducer-events folder for the rest of the example files.

gnarly's People

Contributors

d-xo avatar mykelp avatar nieldlr avatar nionis avatar shrugs avatar stevenjnpearce avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

gnarly's Issues

document developer-facing API

i.e. everything a developer interacts with when writing a reducer; all of the imports operation, because, emit, etc.

Error running yarn run build-ts

yarn run v1.3.2
$ lerna exec --parallel -- yarn run build-ts
lerna info version 2.11.0
lerna info exec in 5 package(s): yarn run build-ts
@xlnt/gnarly-bin: $ tsc
@xlnt/gnarly-reducer-erc721: $ tsc
@xlnt/gnarly-reducer-events: $ tsc
@xlnt/gnarly-core: $ tsc
@xlnt/gnarly-reducer-block-meta: $ tsc
@xlnt/gnarly-core: src/Ourbit.ts(10,8): error TS2307: Cannot find module '@xlnt/fast-json-patch'.
@xlnt/gnarly-core: src/utils.ts(1,27): error TS2307: Cannot find module '@xlnt/fast-json-patch'.
@xlnt/gnarly-core: info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.
@xlnt/gnarly-core: error Command failed with exit code 2.
lerna ERR! execute callback with error
lerna ERR! Error: Command failed: yarn run build-ts
lerna ERR! error Command failed with exit code 2.
lerna ERR! 
lerna ERR! $ tsc
lerna ERR! src/Ourbit.ts(10,8): error TS2307: Cannot find module '@xlnt/fast-json-patch'.
lerna ERR! src/utils.ts(1,27): error TS2307: Cannot find module '@xlnt/fast-json-patch'.
lerna ERR! info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.
lerna ERR! 
lerna ERR!     at Promise.all.then.arr (/Users/aklempner/Repos/ETHBA/gnarly/node_modules/lerna/node_modules/execa/index.js:236:11)
lerna ERR!     at <anonymous>
{ Error: Command failed: yarn run build-ts
error Command failed with exit code 2.

$ tsc
src/Ourbit.ts(10,8): error TS2307: Cannot find module '@xlnt/fast-json-patch'.
src/utils.ts(1,27): error TS2307: Cannot find module '@xlnt/fast-json-patch'.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.

    at Promise.all.then.arr (/Users/aklempner/Repos/ETHBA/gnarly/node_modules/lerna/node_modules/execa/index.js:236:11)
    at <anonymous>
    at process._tickCallback (internal/process/next_tick.js:188:7)
  code: 1,
  killed: false,
  stdout: '$ tsc\nsrc/Ourbit.ts(10,8): error TS2307: Cannot find module \'@xlnt/fast-json-patch\'.\nsrc/utils.ts(1,27): error TS2307: Cannot find module \'@xlnt/fast-json-patch\'.\ninfo Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.\n',
  stderr: 'error Command failed with exit code 2.\n',
  failed: true,
  signal: null,
  cmd: 'yarn run build-ts',
  timedOut: false,
  exitCode: 1 }
error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.

Implement "future" block propagation

Ourbit should natively support this already, but it should be possible to propose a new, future block that hasn't been detected by the blockstream.

This feature is necessary for supporting things like mempool monitoring and optimistic state.

This may require changes to ethereumjs-blockstream, in which case we'll create a fork on this organization.

CONTRIBUTING.md?

Hey I read the Medium post, and I'm very interested in helping out with this project!

Currently, I'm building two dApps:

Creating great UX with optimistic UIs and sane error handling for transactions is high on my priority list. I want to avoid wasting users' time/ETH at all costs. Plus, I'm already using react + apollo-client, so this seems like a really ideal fit 👍

I have a lot of experience building SPA's with react/redux/apollo, so it'd be super cool to learn how I could get involved.

Key containing "/"

Issue:

State is not updated in the DB

Why:

There is unexpected behaviour when a key in state contains "/", due to the way JSON patches work "/" determines the path in a JSON patch.

Example:

// reducer function
updateState = (id, value) => {
  console.log(id)
  // "eth/usd" (contains "/")

  state[id].value = value
  // state will not update
}

Solution:

Don't use "/" in a key

Create reducer testing environment

This would be some sort of package of testing utilities that can easily bootstrap some text fixtures and allow reducer writers to easy put their reducer logic through its paces.

Something like

import {
  reducer,
  
} from 'gnarly-test-utils'

reducer('gnarly-reducer-erc20', function () {


})

what this interface actually looks like is TBD and probably only understandable via manually testing a reducer and then abstracting that to support any reducer thrown at it.

Error: Invalid JSON Response

Error: 
gnarly_1                           |         Invalid JSON response: {
gnarly_1                           |   "jsonrpc": "2.0",
gnarly_1                           |   "id": 1,
gnarly_1                           |   "result": null
gnarly_1                           | }

This happens because infura is either dropping the ball on your request or is rate limiting you without actually telling us 😢

For Now: use your own node

Real Solution: implement retried requests and better error descriptions

erc20 reducer patches failing

Issue

Sometimes when existing instance (ctrl+c) and we restart, gnarly will fail to apply ERC20 reducer patches.

I suspect this is due to 2 extra operations within the ERC20 reducer, my tests show that if I have only one extra operation it will not have this issue.

Recreate issue

I recreate it by randomly stopping the server and rebooting (with resuming on), once I recreate the issue, then every boot will fail.

ctrl-c that caused the bug:

equelize:pool connection acquired +0ms
  sequelize:sql:pg executing(default) : UPDATE "erc20_balances" SET "balanceStr"='3149499999999999999',"patchId"='78525b54-177e-4373-9494-16c037a19559',"updatedAt"='2018-08-09 10:38:06.083 +00:00' WHERE "id" = '0xe41d2489571d322189246dafa5ebde1f4699f498-0x4969358e80cdC3D74477D7447BFfA3B2e2aCbe92' +1ms
  sequelize:sql:pg executing(default) : UPDATE "erc20_balances" SET "balance"='3149499999999999999',"patchId"='78525b54-177e-4373-9494-16c037a19559',"updatedAt"='2018-08-09 10:38:06.085 +00:00' WHERE "id" = '0xe41d2489571d322189246dafa5ebde1f4699f498-0x4969358e80cdC3D74477D7447BFfA3B2e2aCbe92' +1ms
  sequelize:sql:pg executed(default) : UPDATE "erc20_balances" SET "balance"='174199399999999999999',"patchId"='a4821b9b-81f7-4c48-97a2-c526281845a8',"updatedAt"='2018-08-09 10:38:06.068 +00:00' WHERE "id" = '0xe41d2489571d322189246dafa5ebde1f4699f498-0x4969358e80cdC3D74477D7447BFfA3B2e2aCbe92' +0ms
  sequelize:pool connection released +1ms
  sequelize:sql:pg executed(default) : UPDATE "erc20_balances" SET "balance"='1482159000000000000000',"patchId"='78525b54-177e-4373-9494-16c037a19559',"updatedAt"='2018-08-09 10:38:06.073 +00:00' WHERE "id" = '0xe41d2489571d322189246dafa5ebde1f4699f498-0x1Fc73344279F4D402Bb6eB3f41FD212013b7810c' +1ms
  sequelize:sql:pg executed(default) : UPDATE "erc20_balances" SET "balance"='-171759999999999999999',"patchId"='a4821b9b-81f7-4c48-97a2-c526281845a8',"updatedAt"='2018-08-09 10:38:06.071 +00:00' WHERE "id" = '0xe41d2489571d322189246dafa5ebde1f4699f498-0x0bcd6ed54Df75d2BD2876C2900dfCcB2CA6F848e' +0ms
  sequelize:pool connection released +1ms
  sequelize:pool connection released +0ms
  sequelize:sql:pg executed(default) : UPDATE "erc20_balances" SET "balanceStr"='3149499999999999999',"patchId"='78525b54-177e-4373-9494-16c037a19559',"updatedAt"='2018-08-09 10:38:06.083 +00:00' WHERE "id" = '0xe41d2489571d322189246dafa5ebde1f4699f498-0x4969358e80cdC3D74477D7447BFfA3B2e2aCbe92' +1ms
  sequelize:pool connection released +1ms
  sequelize:sql:pg executed(default) : UPDATE "erc20_balances" SET "balance"='3149499999999999999',"patchId"='78525b54-177e-4373-9494-16c037a19559',"updatedAt"='2018-08-09 10:38:06.085 +00:00' WHERE "id" = '0xe41d2489571d322189246dafa5ebde1f4699f498-0x4969358e80cdC3D74477D7447BFfA3B2e2aCbe92' +2ms
  sequelize:pool connection released +2ms
  sequelize:pool connection acquired +116ms
  sequelize:sql:pg executing(default) : INSERT INTO "transactions" ("id","blockHash","createdAt","updatedAt","reducerId") VALUES ('6e8c01d0-599e-478c-b850-800c3d91f90a','0xb2a7ca9d945d4b2b00eac3ba9ae14088d61615aa6430d2226e4518c3e58e5da7','2018-08-09 10:38:06.219 +00:00','2018-08-09 10:38:06.219 +00:00','events') RETURNING *; +116ms
  sequelize:sql:pg executed(default) : INSERT INTO "transactions" ("id","blockHash","createdAt","updatedAt","reducerId") VALUES ('6e8c01d0-599e-478c-b850-800c3d91f90a','0xb2a7ca9d945d4b2b00eac3ba9ae14088d61615aa6430d2226e4518c3e58e5da7','2018-08-09 10:38:06.219 +00:00','2018-08-09 10:38:06.219 +00:00','events') RETURNING *; +3ms
  sequelize:pool connection released +4ms
  gnarly-core:ourbit:events:notifyPatches txId: 6e8c01d0-599e-478c-b850-800c3d91f90a, patches: [] +3s
  gnarly-core:api [getBlockByNumber] 5547564 0x54a62c +1s
  gnarly-core:api [getBlockByNumber] 5547565 0x54a62d +186ms
  gnarly-core:api [getBlockByNumber] 5547565 0x54a62d +45ms
^C  gnarly Gracefully exiting. Send the signal again to force exit. +0ms
  gnarly-core Gracefully decomposing reducers... +47s

  gnarly-core:blockstream Pending Transactions: 0 +44s
  gnarly-core:blockstream Pending Transactions: 0 +0ms
  gnarly-core:blockstream Pending Transactions: 0 +0ms
  gnarly-core:blockstream Pending Transactions: 0 +0ms
  gnarly-core:blockstream Done! Exiting... +1ms
  gnarly-core:blockstream Done! Exiting... +0ms
  gnarly-core:blockstream Done! Exiting... +0ms
  gnarly-core:blockstream Done! Exiting... +0ms

reboot logs:

  gnarly-core:ourbit:ZRX [applyPatch] d9553a35-7a05-4808-91a0-752d4c4a08f3 3 +219ms
  gnarly-core:ourbit:ZRX [applyPatch] b2c2474a-5e9a-4f96-8d05-429fb018004f 3 +2ms
  gnarly-core:ourbit:ZRX [applyPatch] 4003ad5f-3bbb-42ac-be1c-132259dcfc0b 8 +0ms
  gnarly-core:ourbit:ZRX [applyPatch] 8dcc662a-ed7f-4728-9e4b-d76bd4a442ba 5 +0ms
  gnarly-core:ourbit:ZRX [applyPatch] 8fc40e6e-9fde-4bfd-b15a-8a61e3c3a2f0 7 +1ms
  gnarly-core:ourbit:ZRX [applyPatch] 61b586ac-a32f-457a-b21b-9e4bfc32bafa 10 +0ms
  gnarly-core:ourbit:ZRX [applyPatch] b77b908b-8617-4697-8565-11cc608669d1 2 +0ms
  gnarly-core:ourbit:ZRX [applyPatch] 869b63f4-12f6-4917-a1d4-4fecf9f70773 10 +0ms
  gnarly-core:ourbit:ZRX [applyPatch] 142ddcfe-3d3d-4f67-9a2f-e5c7087a83ea 4 +0ms
  gnarly-core:ourbit:ZRX [applyPatch] 8b082b63-0994-4761-86e6-5b07d54f6d59 16 +1ms
  gnarly-core:ourbit:ZRX [applyPatch] 9069f397-686d-4d47-b0c5-585ca0e31a5a 10 +0ms
  gnarly-core:ourbit:ZRX [applyPatch] f4beaf96-1899-4ed3-81e7-570d0d443385 8 +0ms
  gnarly-core:ourbit:ZRX [applyPatch] f8070bc2-b28f-4a59-952c-24c88f653c2d 3 +0ms
  gnarly-core:ourbit:ZRX [applyPatch] d13f14ee-3c50-4111-bec2-0c34d444cdf1 9 +0ms
  gnarly-core:ourbit:ZRX [applyPatch] 6ac8173a-c3a2-4044-9026-5c73ebf47409 6 +1ms
  gnarly-core:ourbit:ZRX [applyPatch] c00e4b60-ff03-4034-866a-2efb1c09c236 4 +0ms
  gnarly-core:ourbit:ZRX [applyPatch] d7512215-f278-418f-89b9-7e3d55d45581 3 +0ms
  gnarly-core:ourbit:ZRX [applyPatch] 8c767448-b1ff-4f04-8b39-ad3c11a8130a 10 +1ms
  gnarly-core:ourbit:ZRX [applyPatch] b8fd1dfc-fb58-4140-acda-cbec020de84e 4 +0ms
  gnarly-core:ourbit:ZRX [applyPatch] 447b86ad-5c98-4122-9dee-072b623bce63 6 +0ms
  gnarly-core:ourbit:ZRX [applyPatch] c55ad150-2048-468e-8572-edad0f528836 17 +0ms
  gnarly-core:ourbit:ZRX [applyPatch] 1540c607-5b18-4177-9575-d47736b41072 7 +0ms
  gnarly-core:ourbit:ZRX [applyPatch] 1de4d82a-a3a4-4966-bf7c-5e1a5dac743a 8 +1ms
  gnarly-core:ourbit:ZRX [applyPatch] 356a7d8e-9aa3-4049-9365-e140db5f2ea5 3 +0ms
  gnarly-core:ourbit:ZRX [applyPatch] 85b53d22-d9a0-48a7-b081-ae417359eaf5 4 +0ms
  gnarly-core:ourbit:ZRX [applyPatch] f4ff0faf-fd44-4e88-97e7-58433c969857 2 +0ms
  gnarly-core:ourbit:ZRX [applyPatch] 071e4b57-1690-426c-8c54-ba04f3b2f6b2 3 +0ms
  gnarly-core:ourbit:ZRX [applyPatch] 3c0e187a-9b63-4425-ab30-84eb08cc9a6e 2 +1ms
  gnarly-core:ourbit:ZRX [applyPatch] ac9c7b82-8ec6-4098-a04a-e4f9a4bef5f6 2 +0ms
  gnarly-core:ourbit:ZRX [applyPatch] bd6a052e-6033-4a38-8915-305dee4be699 5 +0ms
  gnarly-core:ourbit:ZRX [applyPatch] 0b7a24d8-1e49-45f1-8322-72c9d74c00a5 3 +0ms
  gnarly-core:ourbit:ZRX [applyPatch] d7807b8f-567e-4e8a-8359-f60ccd78bcb3 1 +0ms
  gnarly-core:ourbit:ZRX [applyPatch] 71fd50ea-1368-46f2-b63c-0488b8f73634 2 +0ms
  gnarly-core:ourbit:ZRX [applyPatch] d8021f5f-c2c1-4007-a918-5449403b58fe 2 +1ms
  gnarly-core:ourbit:ZRX [applyPatch] c352951e-1b7d-466e-94f6-090edc93823e 4 +0ms
  gnarly-core:ourbit:ZRX [applyPatch] 823831d2-ad93-406a-922d-9d0363026414 3 +0ms
  gnarly-core:ourbit:ZRX [applyPatch] b338389e-1d6b-4494-9da6-6e50c503ee44 4 +0ms
  gnarly-core:ourbit:ZRX [applyPatch] 4045a3a2-6437-49ef-8304-94bbb556c12c 2 +0ms
  gnarly-core:ourbit:ZRX [applyPatch] 84040fd3-0485-47de-bd38-4a4b0934f107 4 +0ms
  gnarly-core:ourbit:ZRX [applyPatch] 6bbf952a-0cb2-4266-bd15-fe501baa6f5c 3 +1ms
  gnarly-core:ourbit:ZRX [applyPatch] f35ced55-9772-4140-adde-aabc8e0c2b00 2 +0ms
  gnarly-core:ourbit:ZRX [applyPatch] d19061f7-272a-4c98-93a2-f98dc501fce4 3 +0ms
  gnarly-core:ourbit:ZRX [applyPatch] 5dc13587-e2f9-4b3f-b14a-96cd34ec96de 2 +0ms
  gnarly-core:ourbit:ZRX [applyPatch] 99d9180a-1ed2-4157-9b65-704a6e3d3e93 6 +0ms
  gnarly-core:ourbit:ZRX [applyPatch] dca97bbb-1683-44af-be54-b59c78562372 7 +0ms
  gnarly-core:ourbit:ZRX [applyPatch] ca96583c-1a69-46ac-bf9c-48bf8cfb7554 5 +1ms
  gnarly-core:ourbit:ZRX [applyPatch] 150e0d0a-86a1-475e-8180-b21f97755e90 9 +0ms
  gnarly-core:ourbit:ZRX [applyPatch] 65fc6903-0ea7-45dd-9e96-c667c7c8f1c5 2 +0ms
  gnarly-core:ourbit:ZRX [applyPatch] 4c3459b9-bf1c-4c24-8f85-c5f75603f454 2 +0ms
  gnarly-core:ourbit:ZRX [applyPatch] f425bf48-4433-4956-baea-892a23c13fef 4 +0ms
  gnarly-core:ourbit:ZRX [applyPatch] d526793c-b7a0-46bf-bf32-e42d7dfbb750 2 +1ms
  gnarly-core:ourbit:ZRX [applyPatch] 30b81dd5-85ea-4ec3-a927-afc4afcd43bc 2 +0ms
  gnarly-core:ourbit:ZRX [applyPatch] ee299e06-2b8f-4817-a03e-425fc7bfd6e8 2 +0ms
  gnarly-core:ourbit:ZRX [applyPatch] 670a9d5c-0cc3-4eca-ab77-4f03654e2cbb 2 +0ms
  gnarly-core:ourbit:ZRX [applyPatch] 4da7ef97-e9b5-4c1e-be68-c01e3ef0c90a 5 +0ms
  gnarly-core:ourbit:ZRX [applyPatch] ac8de6b8-88c0-4d79-90ae-c11c6e2ec979 4 +0ms
  gnarly-core:ourbit:ZRX [applyPatch] 74e8fcc0-d9aa-452e-81e9-37e93273d8a8 4 +1ms
  gnarly-core:ourbit:ZRX [applyPatch] 29b1972f-fef0-4456-a473-68a8318b6497 4 +0ms
  gnarly-core:ourbit:ZRX [applyPatch] 7e2036cc-de23-45d3-885a-47c05da6c1fd 2 +0ms
  gnarly-core:ourbit:ZRX [applyPatch] ae18fd1e-86e0-4eea-9c58-e33d851e7d27 2 +0ms
  gnarly-core:ourbit:ZRX [applyPatch] a18a87ec-61c7-4ca0-b352-c04f44cfa33e 1 +0ms
  gnarly-core:ourbit:ZRX [applyPatch] 01e63019-e8de-4fa2-9e6f-611a5511dc94 1 +0ms
  gnarly-core:ourbit:ZRX [applyPatch] 6e99b932-9b6c-4e32-9343-a698c1b6c1c0 3 +1ms
  gnarly-core:ourbit:ZRX [applyPatch] 42cb637a-a6d1-4adf-9a20-7f0b5e7f6eaa 2 +0ms
  gnarly-core:ourbit:ZRX [applyPatch] d788e123-f730-452d-aaff-2e0b997803c3 3 +0ms
  gnarly-core:ourbit:ZRX [applyPatch] 61316421-7f78-46ce-81ce-8153a144456e 2 +1ms
  gnarly-core:ourbit:ZRX [applyPatch] e607b94d-4cc0-4d98-a8dd-81b3babc66d5 1 +0ms
  gnarly-core:ourbit:ZRX [applyPatch] e1c0ce0f-bd5f-43ae-9544-f1ac1f5281b1 3 +0ms
  gnarly-core:ourbit:ZRX [applyPatch] 2c82061a-6be1-4d5d-a870-088f42823901 1 +0ms
  gnarly-core:ourbit:ZRX [applyPatch] 3520a38a-3ec7-442b-b1a4-b7cfdba6b8c0 4 +1ms
  gnarly-core:ourbit:ZRX [applyPatch] f4df5c2d-7d12-4d1a-9a48-8214e457613b 5 +0ms
  gnarly-core:runner:ZRX TypeError: Cannot read property 'balanceStr' of undefined
  gnarly-core:runner:ZRX     at Object.replace (/mnt/c/Users/stive/Documents/work/projects/gnarly/node_modules/@xlnt/fast-json-patch/lib/core.js:27:26)
  gnarly-core:runner:ZRX     at applyOperation (/mnt/c/Users/stive/Documents/work/projects/gnarly/node_modules/@xlnt/fast-json-patch/lib/core.js:231:60)
  gnarly-core:runner:ZRX     at Object.applyPatch (/mnt/c/Users/stive/Documents/work/projects/gnarly/node_modules/@xlnt/fast-json-patch/lib/core.js:268:22)
  gnarly-core:runner:ZRX     at txBatch.forEach (/mnt/c/Users/stive/Documents/work/projects/gnarly/packages/gnarly-core/src/ourbit/Ourbit.ts:131:9)
  gnarly-core:runner:ZRX     at Array.forEach (<anonymous>)
  gnarly-core:runner:ZRX     at Ourbit.<anonymous> (/mnt/c/Users/stive/Documents/work/projects/gnarly/packages/gnarly-core/src/ourbit/Ourbit.ts:127:15)
  gnarly-core:runner:ZRX     at Generator.next (<anonymous>)
  gnarly-core:runner:ZRX     at fulfilled (/mnt/c/Users/stive/Documents/work/projects/gnarly/packages/gnarly-core/lib/ourbit/Ourbit.js:4:58)
  gnarly-core:runner:ZRX     at process._tickCallback (internal/process/next_tick.js:68:7) +236ms
error Command failed with exit code 1.

Add PouchDB Support

PouchDB is a cross-platform database. It's possible to build a database, dump it, and then load it into another PouchDB instance, which is how we'd like to support client-side snapshots (it also supports real-time sync, which is nice for reactive frontends!)

The logic for resuming from a snapshot is:

  1. Drop local databases (reset)
  2. Download the database dump
  3. Load this database dump.
  4. Find the blockHash of the most recent transaction recorded
  5. Restart all reducers from that latestBlockHash

or, when syncing

db.replicate.from(remote)

In this way, a client can download snapshots from trusted third parties (eventually we'll use a decentralized computation network to produce these indexes) and then resume following the index in real-time, locally, block-by-block talking directly to an Ethereum node.

The first step to supporting PouchDB snapshots is to add support to Gnarly's IPersistInterface for talking to PouchDB. By default PouchDB will use an indexeddb adaptor in browser and a leveldb adaptor on node, which is probably fine. If there are any inconsistencies, though, we can use the websql adaptor in browser and the pouchdb-adapter-node-websql adaptor in node (which ends up writing to disk via sqlite).

Use pouchdb-server to persist data serverside.

Run the default gnarly reducer for a few days and report on usage statistics

The current version of gnarly-bin (commit 999b9e3) is configured to index CryptoKitties NFT transfers, Transfer events, and block metadata.

Build yourself a docker container, push it to your own username in the docker hub, and then run this container from the beginning on cryptokitties. We want to see how it performs in your experience!

Requirements

  • you must own your own ethereum node. infura is too unreliable at the moment, when using gnarly (perhaps due to rate-limiting!). Our tests are against a fully-synced parity archive node, but theoretically a fast/warp-synced node will work, but slower, since it requests information on-demand from the network
  • a VPS or server instance. gnarly should be running in a relatively production-level environment, so knowing. no minimum requirements, but make sure you don't run out of space for the blockchain you're downloading (1TB+ for parity archive). Run gnarly on that same machine or a co-located machine to cut down on round-trip time.

Extra Usage Details

For running the gnarly instance docker container, you can use a command like:

sudo docker run --name gnarly --net=host -d \
  -e "DEBUG=*,-sequelize*,-gnarly-core:store:*,-gnarly-core:api,-gnarly-core:blockstream:fast-forward,-gnarly-core:ourbit:notifyPatches" \
  -e "GNARLY_RESET=true" \
  -e "LATEST_BLOCK_HASH=0x62f9bc4786f6f8101dc9510aba5744a4ca5ed94d0e47d9b625d5f09afd7e842a" \
  -e "NODE_ENDPOINT=http://127.0.0.1:8545" \
  -e "CONNECTION_STRING=postgres:////" \
  shrugs/gnarly-test:latest

# and then save your logs with something like
sudo docker logs gnarly -f &> gnarly.log &

(remembering to configure the postgres connection string correctly, as well as, if gnarly happens to crash (report that bug please ❤️), removing the GNARLY_RESET=true env var. That var tells gnarly that it should nuke your data store to nothingness, so avoid using it if there's something in your postgres database that you want to keep!)

That hash is the blockhash of the block before cryptokitties was deployed, so we'll be indexing every cryptokitty since the beginning.

Report on:

  • what does your network topology look like?
    • what instance size(s) are you using? resources (mem, cpu) available?
    • what specs does your database have? for
  • any specific failure cases or bug reports noted
  • the results of the following sql query, ideally over time, whenever you remember to record them
SELECT EXTRACT(EPOCH FROM (f."createdAt" - s."createdAt")) as duration, bc.block_count, bc."block_count" / EXTRACT(EPOCH FROM (f."createdAt" - s."createdAt")) as blocks_per_second FROM (
    select "createdAt" from "transactions" limit 1
) as s JOIN (
    select "createdAt" from "transactions" order by "createdAt" DESC limit 1
) as f ON 1=1 JOIN (
    select count(*) as block_count from "transactions"
) as bc ON 1=1;

SELECT ckt."tokenId" as latest_token_id, b."timestamp" as block_time, b."number" as block_number
FROM (select max("tokenId"::integer)::text as "tokenId" from "cryptoKitties_tokens") as lt
JOIN "cryptoKitties_tokens" ckt ON lt."tokenId" = ckt."tokenId"
JOIN "patches" p ON p."id" = ckt."patchId"
JOIN "transactions" t ON t."id" = p."transactionId"
JOIN "blocks" b ON b.hash = t."blockHash"
WHERE ckt."tokenId" = lt."tokenId";

Output should be like

duration,block_count,blocks_per_second
297313.879,109786,0.369259586431887

latest_token_id,block_time,block_number
250419,2017-12-11 20:16:46+00,4715906

(which shows that I've been running this instance over ~110k blocks for 3.4 days and indexed 250419 kitties up to block 4715906 with an average blocks per second of 0.36)

Notes

If there are any questions, mention me in this thread and I'll be around to help!

Resuming from persisted state is broken in feat/only-json-patch

This is probably an off-by-one error somehow, but here's the logs:

...
[applyPatch] 0xbcc76e0137d7da8477142bf407e62a442c3470a75a332b446f043fec9dcbdf09 24
[applyPatch] 0x2fd113b6652b5e7d79090b1f74836763af086fbfc341241fa7faf66dd46a6054 18
[applyPatch] 0x93ebcc2b5a4bcc4dae8635d168e3d41b1cc44458c15ef7b0dd6739180814c86d 6
[applyPatch] 0xaafc3a054d0b9748dc6e703bc9fa75f92cd395e2738a6909237f7f164146b942 30
[applyPatch] 0x13c96b78eb8ac3bad8d83e5f646cfaeff711717806ec36b8fa333cde7bfa102f 16
[applyPatch] 0xb6401524d42fe5f0a603710e874afe4b0f7abb2cbe5330822acbaf5ba09e979c 48
[fast-forward] Starting from 4676308 to 5641672
[fast-forward] block 0x475ad4 (0xd9b3b3ab9f3a3ec043edf4ec7cf24728d017e81b586fa6989985189365109f1b)
[onBlockAdd] 0x475ad4 (0xd9b3b3ab9f3a3ec043edf4ec7cf24728d017e81b586fa6989985189365109f1b)
[op] transferring token 69155 to 0xdb595932F2d278E80eA7Bad9bD50ec22B5E84844
[op] transferring token 57421 to 0x42cEC1Efe742be293CDf8df2a89F48814Cf084ff
[op] transferring token 46335 to 0x00Ce0cEa39FfeB12a89824266689dF9A96b28870
[op] transferring token 68867 to 0xC7af99Fe5513eB6710e6D5f44F9989dA40F27F26
[op] transferring token 69010 to 0x48bf0f9c84d3E449c7b1cc09C95FcbA1F8bb948c
[op] transferring token 50757 to 0x94482Bf9d292c84ad190D5FBEc45D325130005a7
[op] transferring token 69134 to 0xC7af99Fe5513eB6710e6D5f44F9989dA40F27F26
[op] transferring token 50719 to 0xb1690C08E213a35Ed9bAb7B318DE14420FB57d8C
[op] transferring token 66930 to 0x4B43041E418b16BB7dcf94A09F721cC73574FC04
[op] transferring token 41250 to 0xb1690C08E213a35Ed9bAb7B318DE14420FB57d8C
[op] transferring token 68445 to 0xa1C975e2Fb60F50c669c21B2e32274faCB1e807c
[op] transferring token 69122 to 0xb1690C08E213a35Ed9bAb7B318DE14420FB57d8C
[op] transferring token 53752 to 0x04141983Eb7EE3Bb81aEDdED4c4CDc8cEF06817F
[op] transferring token 42222 to 0xb1690C08E213a35Ed9bAb7B318DE14420FB57d8C
[op] transferring token 56063 to 0xC7af99Fe5513eB6710e6D5f44F9989dA40F27F26
[op] transferring token 50759 to 0x08209fF57752Cd1931c420245ADcEB29bf991E21
[op] transferring token 68394 to 0xb1690C08E213a35Ed9bAb7B318DE14420FB57d8C
[op] transferring token 53350 to 0xC7af99Fe5513eB6710e6D5f44F9989dA40F27F26
[op] transferring token 33839 to 0xb1690C08E213a35Ed9bAb7B318DE14420FB57d8C
[op] transferring token 69303 to 0x1D7384D546De28D682eC9A73c2aBA056C72364Cb
{ SequelizeUniqueConstraintError: Validation error
    at Query.formatError (/snapshot/gnarly/node_modules/sequelize/lib/dialects/postgres/query.js:325:18)
    at AbstractQuery.run.query.catch.err (/snapshot/gnarly/node_modules/sequelize/lib/dialects/postgres/query.js:86:18)
    at tryCatcher (/snapshot/gnarly/node_modules/bluebird/js/release/util.js:16:23)
    at Promise.module.exports.Promise._settlePromiseFromHandler (/snapshot/gnarly/node_modules/bluebird/js/release/promise.js:512:31)
    at Promise.module.exports.Promise._settlePromise (/snapshot/gnarly/node_modules/bluebird/js/release/promise.js:569:18)
    at Promise.module.exports.Promise._settlePromise0 (/snapshot/gnarly/node_modules/bluebird/js/release/promise.js:614:10)
    at Promise.module.exports.Promise._settlePromises (/snapshot/gnarly/node_modules/bluebird/js/release/promise.js:689:18)
    at Async._drainQueue (/snapshot/gnarly/node_modules/bluebird/js/release/async.js:133:16)
    at Async._drainQueues (/snapshot/gnarly/node_modules/bluebird/js/release/async.js:143:10)
    at Immediate.__dirname.Async.drainQueues [as _onImmediate] (/snapshot/gnarly/node_modules/bluebird/js/release/async.js:17:14)
    at runCallback (timers.js:800:20)
    at tryOnImmediate (timers.js:762:5)
    at processImmediate [as _immediateCallback] (timers.js:733:5)
  name: 'SequelizeUniqueConstraintError',
  errors:
   [ ValidationErrorItem {
       message: 'tokenId must be unique',
       type: 'unique violation',
       path: 'tokenId',
       value: '69155',
       origin: 'DB',
       instance: [cryptoKitties_tokens],
       validatorKey: 'not_unique',
       validatorName: null,
       validatorArgs: [] } ],
  fields: { tokenId: '69155' },
  parent:
   { error: duplicate key value violates unique constraint "cryptoKitties_tokens_pkey"
    at Connection.parseE (/snapshot/gnarly/node_modules/pg/lib/connection.js:553:11)
    at Connection.parseMessage (/snapshot/gnarly/node_modules/pg/lib/connection.js:378:19)
    at Socket.<anonymous> (/snapshot/gnarly/node_modules/pg/lib/connection.js:119:22)

aka we resumed from state but then ingested a duplicate block and violated a unique key constraint.

Redshift Support

Given that redshift and postgres are designed to be relatively compatible, it should be easy to have a typeStore that points to redshift.

  1. Check to see if the existing Sequelize typeStore works with redshift
  2. If it does not, identify the problems and either modify the sequelize typeStore to support redshift or implement a redshift-specific typestore.

Cannot use namespace 'IPatch' as a type

src/index.ts(33,42): error TS2709: Cannot use namespace 'IPatch' as a type.

How do you use interfaces exported from typescript? Need to provide a types file somehow

`build-ts` cannot find module

When using yarn within lerna, with useWorkspaces: true, yarn will attempt to hoist the packages, this resulted on my end issues where typescript would complain about the paths (since they are hoisted at root node_modules), and also build failing

By disabling workspaces and using lerna bootstrap --nohoist it was working fine,
I am not sure if I should be experiencing this issue since you will have noticed it by now, any clues?

Each Reducer should be Independent

Description

The primary problem with gnarly right now is that it cannot be re-configured at runtime. That is, once you've started a gnarly instance, all reducers track the blockchain at the same rate, because the progress of indexing a chain is stored as part of the system as a whole. If a new NFT contract is created, but your gnarly isn't configured to track it, it will never know, and you'd currently have to spin up a totally separate instance of gnarly in order to track that additional NFT. Scale that to the number of NFTs to be created from now, and we have a real problem.

This is also an issue for things like tracking historical token transfers; tracking all of this information is compute/storage heavy, and if you're running an analytics website for users that tracks their balances over time, you want to say "only track the balances of my users, and not everyone in the ethereum blockchain". In this case, you must be able to tell gnarly about new users of your service, so it can start tracking them.


Currently, the sequelize-specific typestores are highly dependent on each other via foreign keys and such, which I'm now realizing isn't a great idea; each reducer needs to be relatively independent of the others in the system so that they can be run in independently (i.e., in parallel in real-time or, when loading from history, at different rates).

Implementation

Right now, the state of the system is implicitly tracked as part of the (gnarly) transactions log. Each transaction is an atomic step of the pipeline and is currently representing a single block in the ethereum chain.

To accomplish runtime-configuration, we need each reducer to keep track of its own progress along the chain, independent of gnarly's transaction-tracing. So now each transaction shouldn't be connected to a block at all; it's just a generic method of storing diffs against a persistent store.

Then, each reducer can keep track of its own progress via some other mechanism (ReducerTransaction?). These reducer transactions are 1:1 with a block in the ethereum blockchain (an atomic event) and also has_one Gnarly transaction. That way we can time-travel between each reducer independently.

Discussion

  • Is the ability to have dependencies between reducers (i.e., this cryptokitty reducer requires that, in order to store information about a kitty produced in block 123, the block-metadata reducer must have indexed at least up to block 123) really valuable? Or could we get away with an implicit dependency that will fail at query-time, (but only if the programmer doesn't realize that the gnarly store isn't ready to be queried)?
    • my thought: probably not, right now.
  • Making each reducer independent means we need to fetch the same block multiple times for each reducer. A caching layer like #14 is necessary, since we're giving up the guaranteed fetch-once behavior when the system stepped forward in time as a single entity.

Replace fromBlockHash behavior with blockRange

It will be necessary/ideal in the future to be able to stop a reducer at one block and begin another at a future block, to handle reactors of on-chain contracts, etc.

A reducer should track up until the blockRange is over, but only stopping after some confidence interval; the switch-over between reducers has to be deterministic as well.

MongoDB Gnarly State Store

Gnarly's state would actually be a good use-case for storing things in MongoDB; implement a store //src/stores/mongo.ts that conforms to IPersistInterface

blockstream state persist

Problem

If we are following the chain but then stop processing during a short-lived fork, once we restart the process the blockstream queue will be empty so it will blindly trust that the next block it sees is the main chain because it can't do any resolution since it doesn't know the blocks before that to calculate longest chain.

Solution

When resuming from a block number n, actually import all blocks from n-10 to n and then start triggering any listeners that want info from blocks n+

Fix transaction lookup bug

logs graciously provided by pk on xlnt.chat

https://gist.githubusercontent.com/pkrasam/4e25487f53188bbc5e7bdc6677b68bb8/raw/50f42187baa1b584ef4d466743c4ee76aeeef1d0/180704_gnarly-demo_logs_v2

�[36mgnarly_1    |�[0m Wed, 04 Jul 2018 04:38:44 GMT sequelize deprecated String based operators are now deprecated. Please use Symbol based operators for better security, read more at http://docs.sequelizejs.com/manual/tutorial/querying.html#operators at ../snapshot/gnarly/node_modules/sequelize/lib/sequelize.js:242:13
�[36mgnarly_1    |�[0m 2018-07-04T04:38:44.621Z gnarly-core Resetting reducer stores: %s...
�[36mgnarly_1    |�[0m 2018-07-04T04:38:44.667Z gnarly-core Done with resetting reducers.
�[36mgnarly_1    |�[0m 2018-07-04T04:38:44.667Z gnarly-core Surfs up!
�[36mgnarly_1    |�[0m 2018-07-04T04:38:44.674Z gnarly-core:runner:cryptoKitties Explicitely starting from HEAD
�[36mgnarly_1    |�[0m 2018-07-04T04:38:44.690Z gnarly-core:runner:blocks Explicitely starting from HEAD
�[36mgnarly_1    |�[0m 2018-07-04T04:38:44.691Z gnarly-core:runner:events Explicitely starting from HEAD
�[36mgnarly_1    |�[0m 2018-07-04T04:38:46.327Z gnarly-core:blockstream Beginning from block 5902205
�[36mgnarly_1    |�[0m 2018-07-04T04:38:46.355Z gnarly-core:blockstream Beginning from block 5902205
�[36mgnarly_1    |�[0m 2018-07-04T04:38:46.358Z gnarly-core:blockstream Beginning from block 5902205
�[36mgnarly_1    |�[0m 2018-07-04T04:38:54.709Z gnarly-core:blockstream:onBlockAdd block 0x5a0f7d (0xe3d9a9a6572f146a66d1c0ca05f6e434ff658ba51409869a9ae52850b5377e2f)
�[36mgnarly_1    |�[0m 2018-07-04T04:38:55.769Z gnarly-core:blockstream:onBlockAdd block 0x5a0f7d (0xe3d9a9a6572f146a66d1c0ca05f6e434ff658ba51409869a9ae52850b5377e2f)
�[36mgnarly_1    |�[0m 2018-07-04T04:38:55.783Z gnarly-core:blockstream:onBlockAdd block 0x5a0f7d (0xe3d9a9a6572f146a66d1c0ca05f6e434ff658ba51409869a9ae52850b5377e2f)
�[36mgnarly_1    |�[0m 2018-07-04T04:39:01.942Z gnarly-core:blockstream:onBlockInvalidated block 0x5a0f7d (0xe3d9a9a6572f146a66d1c0ca05f6e434ff658ba51409869a9ae52850b5377e2f)
�[36mgnarly_1    |�[0m 2018-07-04T04:39:01.954Z gnarly-core:blockstream:onBlockAdd block 0x5a0f7c (0x6dbf67176d921de46d318b508522fd4f9d627637b2e9848a098c5cad563d3b1d)
�[36mgnarly_1    |�[0m unhandledRejection: Error: Could not find transaction 0xe3d9a9a6572f146a66d1c0ca05f6e434ff658ba51409869a9ae52850b5377e2f SequelizeEagerLoadingError: reducer is not associated to patch!
�[36mgnarly_1    |�[0m     at Function._getIncludedAssociation (/snapshot/gnarly/node_modules/sequelize/lib/model.js:583:13)
�[36mgnarly_1    |�[0m     at Function._validateIncludedElement (/snapshot/gnarly/node_modules/sequelize/lib/model.js:499:53)
�[36mgnarly_1    |�[0m     at __dirname._validateIncludedElements.options.include.options.include.map.include (/snapshot/gnarly/node_modules/sequelize/lib/model.js:395:37)
�[36mgnarly_1    |�[0m     at Array.map (<anonymous>)
�[36mgnarly_1    |�[0m     at Function._validateIncludedElements (/snapshot/gnarly/node_modules/sequelize/lib/model.js:390:39)
�[36mgnarly_1    |�[0m     at Function._validateIncludedElement (/snapshot/gnarly/node_modules/sequelize/lib/model.js:573:38)
�[36mgnarly_1    |�[0m     at __dirname._validateIncludedElements.options.include.options.include.map.include (/snapshot/gnarly/node_modules/sequelize/lib/model.js:395:37)
�[36mgnarly_1    |�[0m     at Array.map (<anonymous>)
�[36mgnarly_1    |�[0m     at Function._validateIncludedElements (/snapshot/gnarly/node_modules/sequelize/lib/model.js:390:39)
�[36mgnarly_1    |�[0m     at __dirname.findAll.Promise.try.then.then (/snapshot/gnarly/node_modules/sequelize/lib/model.js:1576:14)
�[36mgnarly_1    |�[0m     at tryCatcher (/snapshot/gnarly/node_modules/bluebird/js/release/util.js:16:23)
�[36mgnarly_1    |�[0m     at Promise.module.exports.Promise._settlePromiseFromHandler (/snapshot/gnarly/node_modules/bluebird/js/release/promise.js:512:31)
�[36mgnarly_1    |�[0m     at Promise.module.exports.Promise._settlePromise (/snapshot/gnarly/node_modules/bluebird/js/release/promise.js:569:18)
�[36mgnarly_1    |�[0m     at Promise.module.exports.Promise._settlePromise0 (/snapshot/gnarly/node_modules/bluebird/js/release/promise.js:614:10)
�[36mgnarly_1    |�[0m     at Promise.module.exports.Promise._settlePromises (/snapshot/gnarly/node_modules/bluebird/js/release/promise.js:693:18)
�[36mgnarly_1    |�[0m     at Async._drainQueue (/snapshot/gnarly/node_modules/bluebird/js/release/async.js:133:16)
�[36mgnarly_1    |�[0m     at Async._drainQueues (/snapshot/gnarly/node_modules/bluebird/js/release/async.js:143:10)
�[36mgnarly_1    |�[0m     at Immediate.__dirname.Async.drainQueues [as _onImmediate] (/snapshot/gnarly/node_modules/bluebird/js/release/async.js:17:14)
�[36mgnarly_1    |�[0m     at runCallback (timers.js:800:20)
�[36mgnarly_1    |�[0m     at tryOnImmediate (timers.js:762:5)
�[36mgnarly_1    |�[0m     at processImmediate [as _immediateCallback] (timers.js:733:5)
�[36mgnarly_1    |�[0m     at SequelizePersistInterface.<anonymous> (/snapshot/gnarly/node_modules/@xlnt/gnarly-core/lib/stores/sequelize.js:205:23)
�[36mgnarly_1    |�[0m     at Generator.throw (<anonymous>)
�[36mgnarly_1    |�[0m     at rejected (/snapshot/gnarly/node_modules/@xlnt/gnarly-core/lib/stores/sequelize.js:5:65)
�[36mgnarly_1    |�[0m     at <anonymous> Error: Could not find transaction 0xe3d9a9a6572f146a66d1c0ca05f6e434ff658ba51409869a9ae52850b5377e2f SequelizeEagerLoadingError: reducer is not associated to patch!
�[36mgnarly_1    |�[0m     at Function._getIncludedAssociation (/snapshot/gnarly/node_modules/sequelize/lib/model.js:583:13)
�[36mgnarly_1    |�[0m     at Function._validateIncludedElement (/snapshot/gnarly/node_modules/sequelize/lib/model.js:499:53)
�[36mgnarly_1    |�[0m     at __dirname._validateIncludedElements.options.include.options.include.map.include (/snapshot/gnarly/node_modules/sequelize/lib/model.js:395:37)
�[36mgnarly_1    |�[0m     at Array.map (<anonymous>)
�[36mgnarly_1    |�[0m     at Function._validateIncludedElements (/snapshot/gnarly/node_modules/sequelize/lib/model.js:390:39)
�[36mgnarly_1    |�[0m     at Function._validateIncludedElement (/snapshot/gnarly/node_modules/sequelize/lib/model.js:573:38)
�[36mgnarly_1    |�[0m     at __dirname._validateIncludedElements.options.include.options.include.map.include (/snapshot/gnarly/node_modules/sequelize/lib/model.js:395:37)
�[36mgnarly_1    |�[0m     at Array.map (<anonymous>)
�[36mgnarly_1    |�[0m     at Function._validateIncludedElements (/snapshot/gnarly/node_modules/sequelize/lib/model.js:390:39)
�[36mgnarly_1    |�[0m     at __dirname.findAll.Promise.try.then.then (/snapshot/gnarly/node_modules/sequelize/lib/model.js:1576:14)
�[36mgnarly_1    |�[0m     at tryCatcher (/snapshot/gnarly/node_modules/bluebird/js/release/util.js:16:23)
�[36mgnarly_1    |�[0m     at Promise.module.exports.Promise._settlePromiseFromHandler (/snapshot/gnarly/node_modules/bluebird/js/release/promise.js:512:31)
�[36mgnarly_1    |�[0m     at Promise.module.exports.Promise._settlePromise (/snapshot/gnarly/node_modules/bluebird/js/release/promise.js:569:18)
�[36mgnarly_1    |�[0m     at Promise.module.exports.Promise._settlePromise0 (/snapshot/gnarly/node_modules/bluebird/js/release/promise.js:614:10)
�[36mgnarly_1    |�[0m     at Promise.module.exports.Promise._settlePromises (/snapshot/gnarly/node_modules/bluebird/js/release/promise.js:693:18)
�[36mgnarly_1    |�[0m     at Async._drainQueue (/snapshot/gnarly/node_modules/bluebird/js/release/async.js:133:16)
�[36mgnarly_1    |�[0m     at Async._drainQueues (/snapshot/gnarly/node_modules/bluebird/js/release/async.js:143:10)
�[36mgnarly_1    |�[0m     at Immediate.__dirname.Async.drainQueues [as _onImmediate] (/snapshot/gnarly/node_modules/bluebird/js/release/async.js:17:14)
�[36mgnarly_1    |�[0m     at runCallback (timers.js:800:20)
�[36mgnarly_1    |�[0m     at tryOnImmediate (timers.js:762:5)
�[36mgnarly_1    |�[0m     at processImmediate [as _immediateCallback] (timers.js:733:5)
�[36mgnarly_1    |�[0m     at SequelizePersistInterface.<anonymous> (/snapshot/gnarly/node_modules/@xlnt/gnarly-core/lib/stores/sequelize.js:205:23)
�[36mgnarly_1    |�[0m     at Generator.throw (<anonymous>)
�[36mgnarly_1    |�[0m     at rejected (/snapshot/gnarly/node_modules/@xlnt/gnarly-core/lib/stores/sequelize.js:5:65)
�[36mgnarly_1    |�[0m     at <anonymous>
�[36mgnarly_1    |�[0m Wed, 04 Jul 2018 04:39:04 GMT sequelize deprecated String based operators are now deprecated. Please use Symbol based operators for better security, read more at http://docs.sequelizejs.com/manual/tutorial/querying.html#operators at ../snapshot/gnarly/node_modules/sequelize/lib/sequelize.js:242:13

which occurs when there's an invalidation in one reducer that causes some logic to search for a transaction that has an improper sequelize construction

Gas Price Oracle Reducer

Implement a reducer that calculates ethgasstation's gas price oracle and stores the relevant variables using a typeStore.

ETHGasStation's backend uses the following logic, in python: https://github.com/ethgasstation/ethgasstation-backend/blob/master/egs/main.py

ETHGasStation uses GNU GPL v3, which would normally be an issue, but algorithms are usually not subject to copyright law (IANAL). So it should be reasonable to use the same algorithm with our our implementation, and give recognition where do.

Bonus points for showing how an API could read from these variables and construct a gas price prediction.

Block/TX caching via redis

Pulling blocks and transactions from the ethereum node is one of the largest bottlenecks in the gnarly pipeline, so caching the hell out of that is a good optimization.

Implement the (optional) ability to cache all NodeApi requests using a get/set cache provider, with an initial redis implementation.

race condition for tx lookup upon previous tx invalidation

https://gist.github.com/pkrasam/003fc5223ce82f5fe6c9e24287296ab3

important lines:


[36mgnarly_1    |�[0m 2018-07-04T08:04:41.930Z gnarly-core Resetting reducer stores: %s...
�[36mgnarly_1    |�[0m 2018-07-04T08:04:41.980Z gnarly-core Done with resetting reducers.
�[36mgnarly_1    |�[0m 2018-07-04T08:04:41.981Z gnarly-core Surfs up!
�[36mgnarly_1    |�[0m 2018-07-04T08:04:41.989Z gnarly-core:runner:events Explicitely starting from HEAD
�[36mgnarly_1    |�[0m 2018-07-04T08:04:42.002Z gnarly-core:runner:blocks Explicitely starting from HEAD
�[36mgnarly_1    |�[0m 2018-07-04T08:04:42.003Z gnarly-core:runner:cryptoKitties Explicitely starting from HEAD
�[36mgnarly_1    |�[0m 2018-07-04T08:04:43.830Z gnarly-core:blockstream Beginning from block 5903039
�[36mgnarly_1    |�[0m 2018-07-04T08:04:43.835Z gnarly-core:blockstream Beginning from block 5903039
�[36mgnarly_1    |�[0m 2018-07-04T08:04:43.838Z gnarly-core:blockstream Beginning from block 5903039
�[36mgnarly_1    |�[0m 2018-07-04T08:04:52.091Z gnarly-core:blockstream:onBlockAdd block 0x5a12c0 (0xe911bdc9ab499964c3a3512a68aa3b0f439bfe589154b25ef889aa179ce1220f)
�[36mgnarly_1    |�[0m 2018-07-04T08:04:52.097Z gnarly-core:blockstream:onBlockAdd block 0x5a12c0 (0xe911bdc9ab499964c3a3512a68aa3b0f439bfe589154b25ef889aa179ce1220f)
�[36mgnarly_1    |�[0m 2018-07-04T08:04:52.103Z gnarly-core:blockstream:onBlockAdd block 0x5a12c0 (0xe911bdc9ab499964c3a3512a68aa3b0f439bfe589154b25ef889aa179ce1220f)
�[36mgnarly_1    |�[0m 2018-07-04T08:05:07.165Z gnarly-core:blockstream:onBlockInvalidated block 0x5a12c0 (0xe911bdc9ab499964c3a3512a68aa3b0f439bfe589154b25ef889aa179ce1220f)
�[36mgnarly_1    |�[0m 2018-07-04T08:05:07.169Z gnarly-core:blockstream:onBlockInvalidated block 0x5a12c0 (0xe911bdc9ab499964c3a3512a68aa3b0f439bfe589154b25ef889aa179ce1220f)
�[36mgnarly_1    |�[0m 2018-07-04T08:05:07.189Z gnarly-core:blockstream:onBlockInvalidated block 0x5a12c0 (0xe911bdc9ab499964c3a3512a68aa3b0f439bfe589154b25ef889aa179ce1220f)
�[36mgnarly_1    |�[0m 2018-07-04T08:05:07.193Z gnarly-core:blockstream:onBlockAdd block 0x5a12be (0xb6279c18906ab5169f68004655e6fb777542cbca329fb4526fffa66c316a3949)
�[36mgnarly_1    |�[0m 2018-07-04T08:05:07.196Z gnarly-core:blockstream:onBlockAdd block 0x5a12be (0xb6279c18906ab5169f68004655e6fb777542cbca329fb4526fffa66c316a3949)
�[36mgnarly_1    |�[0m unhandledRejection: Error: Could not find transaction 0xe911bdc9ab499964c3a3512a68aa3b0f439bfe589154b25ef889aa179ce1220f SequelizeEmptyResultError
�[36mgnarly_1    |�[0m     at __dirname.findAll.Promise.try.then.then.then.tap.then.results (/snapshot/gnarly/node_modules/sequelize/lib/model.js:1618:17)
�[36mgnarly_1    |�[0m     at tryCatcher (/snapshot/gnarly/node_modules/bluebird/js/release/util.js:16:23)
�[36mgnarly_1    |�[0m     at Promise.module.exports.Promise._settlePromiseFromHandler (/snapshot/gnarly/node_modules/bluebird/js/release/promise.js:512:31)
�[36mgnarly_1    |�[0m     at Promise.module.exports.Promise._settlePromise (/snapshot/gnarly/node_modules/bluebird/js/release/promise.js:569:18)
�[36mgnarly_1    |�[0m     at Promise.module.exports.Promise._settlePromise0 (/snapshot/gnarly/node_modules/bluebird/js/release/promise.js:614:10)
�[36mgnarly_1    |�[0m     at Promise.module.exports.Promise._settlePromises (/snapshot/gnarly/node_modules/bluebird/js/release/promise.js:693:18)
�[36mgnarly_1    |�[0m     at Async._drainQueue (/snapshot/gnarly/node_modules/bluebird/js/release/async.js:133:16)
�[36mgnarly_1    |�[0m     at Async._drainQueues (/snapshot/gnarly/node_modules/bluebird/js/release/async.js:143:10)
�[36mgnarly_1    |�[0m     at Immediate.__dirname.Async.drainQueues [as _onImmediate] (/snapshot/gnarly/node_modules/bluebird/js/release/async.js:17:14)
�[36mgnarly_1    |�[0m     at runCallback (timers.js:800:20)
�[36mgnarly_1    |�[0m     at tryOnImmediate (timers.js:762:5)
�[36mgnarly_1    |�[0m     at processImmediate [as _immediateCallback] (timers.js:733:5)
�[36mgnarly_1    |�[0m     at SequelizePersistInterface.<anonymous> (/snapshot/gnarly/node_modules/@xlnt/gnarly-core/lib/stores/sequelize.js:205:23)
�[36mgnarly_1    |�[0m     at Generator.throw (<anonymous>)
�[36mgnarly_1    |�[0m     at rejected (/snapshot/gnarly/node_modules/@xlnt/gnarly-core/lib/stores/sequelize.js:5:65)
�[36mgnarly_1    |�[0m     at <anonymous> Error: Could not find transaction 0xe911bdc9ab499964c3a3512a68aa3b0f439bfe589154b25ef889aa179ce1220f SequelizeEmptyResultError
�[36mgnarly_1    |�[0m     at __dirname.findAll.Promise.try.then.then.then.tap.then.results (/snapshot/gnarly/node_modules/sequelize/lib/model.js:1618:17)
�[36mgnarly_1    |�[0m     at tryCatcher (/snapshot/gnarly/node_modules/bluebird/js/release/util.js:16:23)
�[36mgnarly_1    |�[0m     at Promise.module.exports.Promise._settlePromiseFromHandler (/snapshot/gnarly/node_modules/bluebird/js/release/promise.js:512:31)
�[36mgnarly_1    |�[0m     at Promise.module.exports.Promise._settlePromise (/snapshot/gnarly/node_modules/bluebird/js/release/promise.js:569:18)
�[36mgnarly_1    |�[0m     at Promise.module.exports.Promise._settlePromise0 (/snapshot/gnarly/node_modules/bluebird/js/release/promise.js:614:10)
�[36mgnarly_1    |�[0m     at Promise.module.exports.Promise._settlePromises (/snapshot/gnarly/node_modules/bluebird/js/release/promise.js:693:18)
�[36mgnarly_1    |�[0m     at Async._drainQueue (/snapshot/gnarly/node_modules/bluebird/js/release/async.js:133:16)
�[36mgnarly_1    |�[0m     at Async._drainQueues (/snapshot/gnarly/node_modules/bluebird/js/release/async.js:143:10)
�[36mgnarly_1    |�[0m     at Immediate.__dirname.Async.drainQueues [as _onImmediate] (/snapshot/gnarly/node_modules/bluebird/js/release/async.js:17:14)
�[36mgnarly_1    |�[0m     at runCallback (timers.js:800:20)
�[36mgnarly_1    |�[0m     at tryOnImmediate (timers.js:762:5)
�[36mgnarly_1    |�[0m     at processImmediate [as _immediateCallback] (timers.js:733:5)
�[36mgnarly_1    |�[0m     at SequelizePersistInterface.<anonymous> (/snapshot/gnarly/node_modules/@xlnt/gnarly-core/lib/stores/sequelize.js:205:23)
�[36mgnarly_1    |�[0m     at Generator.throw (<anonymous>)
�[36mgnarly_1    |�[0m     at rejected (/snapshot/gnarly/node_modules/@xlnt/gnarly-core/lib/stores/sequelize.js:5:65)
�[36mgnarly_1    |�[0m     at <anonymous>

Update to Typescript 3.0

The new typescript has some cool features like project references that make monorepoa better. We should upgrade!

Implement EIP-234's get logs by blockHash

Issue

Currently we use eth_getLogs to request logs per block, but this endpoint only responds to block numbers, which means it's possible to get the wrong logs during a fork 😨

Solution

So, once parity and geth ship the ability to get logs by block hash, we can support that as the default behavior in gnarly, and update the various reducers that import getLogs from gnarly-core.

(edited by @shrugs)
(original, unedited issue:)


EIP-234

This will help with state consistency, you can get more details here

yarn run docker-build fails

Hi 👋

Thanks so much for putting this together, it looks super helpful.

I'm trying to build a docker image using the instructions in the README and I'm running into an error on macOS.

I'm using node 8.11.4, yarn 1.9.4, and docker 18.06.0-ce.

Command output is below, let me know if I'm doing something wrong / you need more information.

❯ node --version
v8.11.4

❯ npx lerna bootstrap
lerna info version 3.0.3
lerna info bootstrap root only
yarn install v1.9.4
warning package-lock.json found. Your project contains lock files generated by tools other than Yarn. It is advised not to mix package managers in order to avoid resolution inconsistencies caused by unsynchronized lock files. To clear this warning, remove package-lock.json.
[1/4] 🔍  Resolving packages...
[2/4] 🚚  Fetching packages...
[3/4] 🔗  Linking dependencies...
warning " > [email protected]" has unmet peer dependency "typescript@>=2.1.0 || >=2.1.0-dev || >=2.2.0-dev || >=2.3.0-dev || >=2.4.0-dev || >=2.5.0-dev || >=2.6.0-dev || >=2.7.0-dev || >=2.8.0-dev || >=2.9.0-dev".
warning "tslint > [email protected]" has unmet peer dependency "typescript@>=2.1.0 || >=2.1.0-dev || >=2.2.0-dev || >=2.3.0-dev || >=2.4.0-dev || >=2.5.0-dev || >=2.6.0-dev || >=2.7.0-dev || >=2.8.0-dev || >=2.9.0-dev || >= 3.0.0-dev || >= 3.1.0-dev".
[4/4] 📃  Building fresh packages...
✨  Done in 10.74s.

❯ yarn build-ts
yarn run v1.9.4
$ lerna run build-ts
lerna info version 3.0.3
$ tsc
$ tsc
$ tsc
$ tsc
$ tsc
$ tsc
lerna success run Ran npm script 'build-ts' in 6 packages:
lerna success - @xlnt/gnarly-bin
lerna success - @xlnt/gnarly-core
lerna success - @xlnt/gnarly-reducer-block-meta
lerna success - @xlnt/gnarly-reducer-erc20
lerna success - @xlnt/gnarly-reducer-erc721
lerna success - @xlnt/gnarly-reducer-events
✨  Done in 18.73s.

❯ yarn pkg
yarn run v1.9.4
$ lerna run pkg --scope=@xlnt/gnarly-bin
lerna info version 3.0.3
lerna info filter [ '@xlnt/gnarly-bin' ]
$ pkg --targets node9-linux-x64,node9-macos-x64 --out-path ./pkg .
> [email protected]
> Warning Cannot include addon %1 into executable.
  The addon must be distributed with executable as %2.
  /Users/david/code/circles/gnarly/node_modules/sha3/build/Release/sha3.node
  path-to-executable/sha3.node
lerna success run Ran npm script 'pkg' in 1 package:
lerna success - @xlnt/gnarly-bin
✨  Done in 9.75s.

❯ yarn docker-build
yarn run v1.9.4
$ docker build -t shrugs/gnarly-test:demo .
Sending build context to Docker daemon  273.8MB
Step 1/9 : FROM ubuntu:18.04
 ---> 735f80812f90
Step 2/9 : WORKDIR /app
 ---> Using cache
 ---> 65ef499923e3
Step 3/9 : COPY ./packages/gnarly-bin/pkg/ .
 ---> 4bb56740195b
Step 4/9 : RUN chmod +x -R .
 ---> Running in acac0b3112f9
Removing intermediate container acac0b3112f9
 ---> 99781fd44aed
Step 5/9 : COPY ./node_modules/sha3/build/Release/sha3.node .
 ---> 14c201529478
Step 6/9 : COPY ./node_modules/scrypt/build/Release/scrypt.node .
COPY failed: stat /var/lib/docker/tmp/docker-builder036702492/node_modules/scrypt/build/Release/scrypt.node: no such file or directory
error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.