Search code examples
hyperledger-sawtooth

No response when transaction is submitted to sawtooth intkey TP


I am trying to set up a transaction processor with hyperledger sawtooth. I tested my TP with sawtooth 1.0 and it worked fine. But when I used sawtooth 1.1 network, my transactions are not processed. It seems like the request does not reach the TP. I then tried the intkey TP from the sdk and that also has the same problem. I matched the transaction submit process from the documentation but for no good.

Sawtooth network docker file

version: "2.1"

services:

  settings-tp:
    image: hyperledger/sawtooth-settings-tp:1.1
    container_name: sawtooth-settings-tp-default
    depends_on:
      - validator
    entrypoint: settings-tp -vv -C tcp://validator:4004

  validator:
    image: hyperledger/sawtooth-validator:1.1
    container_name: sawtooth-validator-default
    expose:
      - 4004
    ports:
      - "4004:4004"
    # start the validator with an empty genesis batch
    entrypoint: "bash -c \"\
        sawadm keygen && \
        sawtooth keygen my_key && \
        sawset genesis -k /root/.sawtooth/keys/my_key.priv && \
        sawadm genesis config-genesis.batch && \
        sawtooth-validator -vv \
          --endpoint tcp://validator:8800 \
          --bind component:tcp://eth0:4004 \
          --bind network:tcp://eth0:8800 \
        \""

  rest-api:
    image: hyperledger/sawtooth-rest-api:1.1
    container_name: sawtooth-rest-api-default
    ports:
      - "8008:8008"
    depends_on:
      - validator
    entrypoint: sawtooth-rest-api -C tcp://validator:4004 --bind rest-api:8008

Transaction processor

/**
 * Copyright 2016 Intel Corporation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * ------------------------------------------------------------------------------
 */

'use strict'

const { TransactionHandler } = require('sawtooth-sdk/processor/handler')
const {
  InvalidTransaction,
  InternalError
} = require('sawtooth-sdk/processor/exceptions')

const crypto = require('crypto')
const cbor = require('cbor')

// Constants defined in intkey specification
const MIN_VALUE = 0
const MAX_VALUE = 4294967295
const MAX_NAME_LENGTH = 20

const _hash = (x) =>
  crypto.createHash('sha512').update(x).digest('hex').toLowerCase()

const INT_KEY_FAMILY = 'intkey'
const INT_KEY_NAMESPACE = _hash(INT_KEY_FAMILY).substring(0, 6)

const _decodeCbor = (buffer) =>
  new Promise((resolve, reject) =>
    cbor.decodeFirst(buffer, (err, obj) => (err ? reject(err) : resolve(obj)))
  )

const _toInternalError = (err) => {
  let message = (err.message) ? err.message : err
  throw new InternalError(message)
}

const _setEntry = (context, address, stateValue) => {
  let entries = {
    [address]: cbor.encode(stateValue)
  }
  return context.setState(entries)
}

const _applySet = (context, address, name, value) => (possibleAddressValues) => {
  let stateValueRep = possibleAddressValues[address]

  let stateValue
  if (stateValueRep && stateValueRep.length > 0) {
    stateValue = cbor.decodeFirstSync(stateValueRep)
    let stateName = stateValue[name]
    if (stateName) {
      throw new InvalidTransaction(
        `Verb is "set" but Name already in state, Name: ${name} Value: ${stateName}`
      )
    }
  }

  // 'set' passes checks so store it in the state
  if (!stateValue) {
    stateValue = {}
  }

  stateValue[name] = value

  return _setEntry(context, address, stateValue)
}

const _applyOperator = (verb, op) => (context, address, name, value) => (possibleAddressValues) => {
  let stateValueRep = possibleAddressValues[address]
  if (!stateValueRep || stateValueRep.length === 0) {
    throw new InvalidTransaction(`Verb is ${verb} but Name is not in state`)
  }

  let stateValue = cbor.decodeFirstSync(stateValueRep)
  if (stateValue[name] === null || stateValue[name] === undefined) {
    throw new InvalidTransaction(`Verb is ${verb} but Name is not in state`)
  }

  const result = op(stateValue[name], value)

  if (result < MIN_VALUE) {
    throw new InvalidTransaction(
      `Verb is ${verb}, but result would be less than ${MIN_VALUE}`
    )
  }

  if (result > MAX_VALUE) {
    throw new InvalidTransaction(
      `Verb is ${verb}, but result would be greater than ${MAX_VALUE}`
    )
  }

  // Increment the value in state by value
  // stateValue[name] = op(stateValue[name], value)
  stateValue[name] = result
  return _setEntry(context, address, stateValue)
}

const _applyInc = _applyOperator('inc', (x, y) => x + y)
const _applyDec = _applyOperator('dec', (x, y) => x - y)

class IntegerKeyHandler extends TransactionHandler {
  constructor () {
    super(INT_KEY_FAMILY, ['1.0'], [INT_KEY_NAMESPACE])
  }

  apply (transactionProcessRequest, context) {
    return _decodeCbor(transactionProcessRequest.payload)
      .catch(_toInternalError)
      .then((update) => {
        //
        // Validate the update
        let name = update.Name
        if (!name) {
          throw new InvalidTransaction('Name is required')
        }

        if (name.length > MAX_NAME_LENGTH) {
          throw new InvalidTransaction(
            `Name must be a string of no more than ${MAX_NAME_LENGTH} characters`
          )
        }

        let verb = update.Verb
        if (!verb) {
          throw new InvalidTransaction('Verb is required')
        }

        let value = update.Value
        if (value === null || value === undefined) {
          throw new InvalidTransaction('Value is required')
        }

        let parsed = parseInt(value)
        if (parsed !== value || parsed < MIN_VALUE || parsed > MAX_VALUE) {
          throw new InvalidTransaction(
            `Value must be an integer ` +
            `no less than ${MIN_VALUE} and ` +
            `no greater than ${MAX_VALUE}`)
        }

        value = parsed

        // Determine the action to apply based on the verb
        let actionFn
        if (verb === 'set') {
          actionFn = _applySet
        } else if (verb === 'dec') {
          actionFn = _applyDec
        } else if (verb === 'inc') {
          actionFn = _applyInc
        } else {
          throw new InvalidTransaction(`Verb must be set, inc, dec not ${verb}`)
        }

        let address = INT_KEY_NAMESPACE + _hash(name).slice(-64)

        // Get the current state, for the key's address:
        let getPromise = context.getState([address])

        // Apply the action to the promise's result:
        let actionPromise = getPromise.then(
          actionFn(context, address, name, value)
        )

        // Validate that the action promise results in the correctly set address:
        return actionPromise.then(addresses => {
          if (addresses.length === 0) {
            throw new InternalError('State Error!')
          }
          console.log(`Verb: ${verb} Name: ${name} Value: ${value}`)
        })
      })
  }
}

module.exports = IntegerKeyHandler

SendTransaction

const {createContext, CryptoFactory} = require('sawtooth-sdk/signing')
const cbor = require('cbor')
const {createHash} = require('crypto')
const {protobuf} = require('sawtooth-sdk')
const crypto = require('crypto')

// Creating a Private Key and Signer
const context = createContext('secp256k1')
const privateKey = context.newRandomPrivateKey()
const signer = new CryptoFactory(context).newSigner(privateKey)
const _hash = (x) => crypto.createHash('sha512').update(x).digest('hex').toLowerCase()


// Encoding Your Payload
const payload = {
    Verb: 'get',
    Name: 'foo',
    Value: null
}
const payloadBytes = cbor.encode(payload)

let familyAddr = _hash('intkey').substring(0, 6);
let nameAddr = _hash(payload.Name).slice(-64);
let addr = familyAddr + nameAddr;
console.log(addr);


// Create the Transaction Header
const transactionHeaderBytes = protobuf.TransactionHeader.encode({
    familyName: 'intkey',
    familyVersion: '1.0',
    inputs: [addr],
    outputs: [addr],
    signerPublicKey: signer.getPublicKey().asHex(),
    batcherPublicKey: signer.getPublicKey().asHex(),
    dependencies: [],
    payloadSha512: createHash('sha512').update(payloadBytes).digest('hex')
}).finish()

// Create the Transaction
const signature = signer.sign(transactionHeaderBytes)
const transaction = protobuf.Transaction.create({
    header: transactionHeaderBytes,
    headerSignature: signature,
    payload: payloadBytes
})

//  Create the BatchHeader
const transactions = [transaction]
const batchHeaderBytes = protobuf.BatchHeader.encode({
    signerPublicKey: signer.getPublicKey().asHex(),
    transactionIds: transactions.map((txn) => txn.headerSignature),
}).finish()

// Create the Batch
const headerSignature = signer.sign(batchHeaderBytes)
const batch = protobuf.Batch.create({
    header: batchHeaderBytes,
    headerSignature: headerSignature,
    transactions: transactions
})

// Encode the Batch(es) in a BatchList
const batchListBytes = protobuf.BatchList.encode({
    batches: [batch]
}).finish()

// Submitting Batches to the Validator
const request = require('request')
request.post({
    url: 'http://localhost:8008/batches',
    body: batchListBytes,
    headers: {'Content-Type': 'application/octet-stream'}
}, (err, response) => {
    if (err) return console.log(err)
    console.log(response.body)
})

enter image description here


Solution

  • There are architectural differences in the Hyperledger Sawtooth when moving from 1.0 to 1.1. One major difference is that the consensus engine is moved outside the validator service. In your docker-compose file, there is no consensus engine component also the validator service is not listening on a port for the consensus engine.

    The consensus engine drives the block creation. For example, a timer expiry event in the PoET will cause the validator to create a block, validate, broadcast to other members in the network. Also, a confirmation from the consensus engine will make the validator service to commit the block to the blockchain.

    Please find an example docker-compose file with the PoET consensus engine here https://github.com/hyperledger/sawtooth-core/blob/1-1/docker/compose/sawtooth-default-poet.yaml . Additionally you may try out https://github.com/hyperledger/sawtooth-core/blob/1-1/docker/compose/sawtooth-default.yaml for local dev test.

    There is an explanation in the Hyperledger Sawtooth FAQ for upgrading from 1.0 version to the version 1.1 https://sawtooth.hyperledger.org/faq/upgrade/#id1 . Please feel free to ask more questions or suggestions to update these documentation.

    You can also refer to the official documentation to learn in detail for different versions here https://sawtooth.hyperledger.org/docs/.