Usage docs Reference Examples

src/tracking/blocks/blocksSaga.js

import {
  call, fork, put, select, takeEvery, takeLatest
} from 'redux-saga/effects';
import poller from '../../util/poller';
import subber from '../../util/subber';
import * as blocksAT from './AT';

/**
 * @param web3 Web3 handle.
 * @param action The action to handle.
 * @param action.blockHandle The hash, number,
 *  of special name ("genesis", "latest", "pending") of the block to get.
 * @returns {ReduxSaga} Saga generator
 */
function* getSpecificBlock(web3, {blockHandle}) {
  try {
    // Get the block, and put it into the store.
    const block = yield call(web3.eth.getBlock, blockHandle);
    yield put({
      type: blocksAT.BLOCK_RECEIVED,
      // isolate block data from the action, i.e. add as payload named 'block'
      block
    });
  } catch (err) {
    yield put({
      type: blocksAT.BLOCK_RETRIEVAL_ERROR,
      blockHandle,
      err: err.message
    });
  }
}

function* getLatestBlock(web3) {
  // Simply use the special 'latest' handle to get the latest block.
  yield call(getSpecificBlock, web3, {blockHandle: 'latest'});
}

function* handleNewBlock(web3, getBlocksState, blockDepth, {block: {parentHash, number}}) {
  // continue looking for the ancestor blocks
  //  until we find a known older block, or got $blockDepth blocks in the store.
  const latestBlock = yield select(state => getBlocksState(state).latest.number);
  const parentBlock = yield select(state => getBlocksState(state).blocks[parentHash]);
  // If we don't know the parent block, and the current block number is within the
  //  block depth range to track, then try to get the parent block.
  // TODO: double check range
  if (!parentBlock && (latestBlock - blockDepth) < number) {
    yield put({type: blocksAT.GET_BLOCK, blockHandle: parentHash});
  }
}

function* blocksPollWorker(web3, getBlocksState) {
  const reportedNumber = yield call(web3.eth.getBlockNumber);
  const stateNumber = yield select(state => getBlocksState(state).latest.number);
  // Retrieve the data of the block if we know the block will be higher than we already have.
  if (reportedNumber > stateNumber) {
    // We know the exact block number, but try get the latest anyway.
    // Maybe we are lagging behind badly,
    //  and then it's better to just skip and back-fill only the necessary depth.
    // Also, duplicate GET_LATEST_BLOCK actions are reduced to only the latest.
    yield put({type: blocksAT.GET_LATEST_BLOCK});
  }
}

function* blocksPollError(err) {
  yield put({type: blocksAT.BLOCKS_POLL_ERROR, err: err.message});
}

function* blocksSubWorker(web3, getBlocksState, {number, hash}) {
  // if it has no hash, than we got a pending block. Ignore it.
  if (!hash) return;

  // "number": the block-number from the block-header received from the subscription.
  const stateNumber = yield select(state => getBlocksState(state).latest.number);
  // Retrieve the data of the block if we know the block will be higher than we already have.
  if (number > stateNumber) {
    yield call(getLatestBlock, web3);
  }
}

/**
 * Handles ReDApp block background processing.
 * @param web3 The web3js 1.0 instance to use.
 * @param {ReduxStateSelector} getBlocksState Gets blocks state
 *  (obj. incl. both `blocks` and `latest`)
 * @return {ReduxSaga} Blocks saga.
 */
function* blocksSaga(web3, getBlocksState) {
  // Get block depth from the store.
  const blockDepth = (yield select(state => getBlocksState(state).maxBlockDepth)) || 24;

  // Polling system, only intended for non-stream based RPC, e.g. metamask (over http).
  yield fork(poller(
    blocksAT.BLOCKS_START_POLLING,
    blocksAT.BLOCKS_STOP_POLLING,
    blocksPollWorker,
    blocksPollError,
    web3, getBlocksState
  ));

  yield fork(subber(
    blocksAT.BLOCKS_START_LISTENING,
    blocksAT.BLOCKS_STOP_LISTENING,
    // Get the full block, the header has slightly less data (Compared to getBlock without TXs)
    blocksAT.BLOCKS_SUB_NEW_BLOCK_CHECK,
    // "changed" events are never fired by block-headers subscription, bad generalization of web3js
    null,
    blocksAT.BLOCKS_LISTEN_ERROR,
    () => web3.eth.subscribe('newBlockHeaders')
  ));
  yield takeEvery(blocksAT.BLOCK_RECEIVED, handleNewBlock, web3, getBlocksState, blockDepth);
  yield takeLatest(blocksAT.GET_LATEST_BLOCK, getLatestBlock, web3);
  yield takeEvery(blocksAT.GET_BLOCK, getSpecificBlock, web3);
  yield takeEvery(blocksAT.BLOCKS_SUB_NEW_BLOCK_CHECK, blocksSubWorker, web3, getBlocksState);
}

export default blocksSaga;