Search code examples
hyperledgerhyperledger-composer

Hyperledger Composer returns error No ledger context for GetState


I am currently developing a transaction in Hyperledger composer. This transaction is going to create 2 different Assets : Asset A and Asset B. Asset A has a field with the type Asset B. So I first need to create Asset B and then create Asset A with a link to Asset B. I have managed to implement the logic, after a few struggles. Now the peer returns the following error :

2018-10-10T14:06:14.022Z [5ad2a944] DEBUG :NodeDataCollection :add() > assetAIdValue, {"$class": "mynamespace.assetA","assetAId": "assetAIdValue","assetAFieldX": "assetAFieldXValue","assetB": ["resource:mynamespace.assetB#assetBIdValue1", "resource:mynamespace.assetB#assetBIdValue2"]},"$registryType":"Asset","$registryId":"mynamespace.assetA"}, false

(node:17) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 2): Error: Error when creating AssetA with id assetAIdValue

Error: [5ad2a944]No ledger context for GetState. Sending ERROR

Here is my model

namespace mynamespace

/**
 * Definition of createAssetA transaction
 */
transaction createAssetA{
  o String assetAId
  o String assetAFieldX
  o AssetB[] assetB
}

/**
 * Definition of AssetA asset
 */
asset AssetA identified by assetAId{
  o String assetAId
  o String assetAFieldX
  --> AssetB[] assetB
}

/**
 * Definition of assetB asset
 */
asset AssetB identified by AssetBId{
  o String assetBId
  o String assetBFieldX
  o String assetBFieldY
}

Here is my logic.js

/**
* Creates the assets assetB and assetA
* @param {mynamespace.createAssetA} createAssetA - the AssetA to create
* @transaction
*/
function createAssetA(createAssetA) {
    return getAssetRegistry('mynamespace.AssetB')
        .then(function (assetBRegistry) {
            // Add all the AssetBs to the AssetB registry
            assetBRegistry.addAll(createAssetA.assetB)
                .then(function(){
                    addAssetA(createAssetA);
                }, function(error) {
                    throw new Error ('Error while creating AssetBs' + '\n' + error);
                } );
        });
}

/**
 * Function to add assetA asset to registry
 * @param {*} createAssetA - The createAssetA Transaction containing the assetA info
 */
function addAssetA(createAssetA){
    return getAssetRegistry('mynamespace.AssetA')
        .then(function (assetARegistry) {
            var newAssetA = castcreateAssetATxToAsset(createAssetA);
            return assetARegistry.add(newAssetA).catch(function (error) {
                throw new Error ('Error when creating AssetA with id ' + createAssetA.id + '\n' + error);
            });
        });
}

/**
 * Casts the createAssetA transaction as a AssetA Asset
 * @param {*} createAssetA - The transaction createAssetA to cast
 * @returns {mynamespace.AssetA} the AssetA Asset with the info from the createAssetA param
 */
function castcreateAssetATxToAsset(createAssetA){
    var factory = getFactory();
    // Create a new instance of AssetA class from the mynamespace namespace with the id createAssetA.id
    var newAssetA = factory.newResource('mynamespace', 'AssetA', createAssetA.id);
    newAssetA.assetAFieldX = createAssetA.assetAFieldX;
    newAssetA.assetB = [];
    for (var i = 0; i < createAssetA.assetB.length; i++) {
        var assetB = factory.newRelationship('mynamespace', 'assetB', createAssetA.assetB[i].id);
        newAssetA.assetB[i] = assetB;
    }
    return newAssetA;
}

And here is the content of my request

{
 "$class": "mynamespace.createAssetA",
 "assetAId": "assetAIdValue",
 "assetAFieldX": "assetAFieldXValue",
 "assetB": [
  {
   "$class": "mynamespace.assetB",
   "assetBId": "assetBIdValue1",
   "assetBFieldX": "assetBFieldXValue",
   "assetBFieldY": "assetBFieldYValue"
  },
  {
   "$class": "mynamespace.assetB",
   "assetBId": "assetBIdValue2",
   "assetBFieldX": "assetBFieldXValue",
   "assetBFieldY": "assetBFieldYValue"
  }
 ]
}

If anyone new the cause for such an error he would save my day !

Another question : This error is only visible when I look at the chaincode containers. Anybody knows how I can make them visible from the Composer REST Server?


Solution

  • It looks like you are missing a view return statements from your code in the promise chains, but TP functions support async/await which I would highly recommend you use instead of promise chains as it makes the code so much easier to read. For example (note, not tested)

    /**
     * Creates the assets assetB and assetA
     * @param {mynamespace.createAssetA} createAssetA - the AssetA to create
     * @transaction
     */
    async function createAssetA(createAssetA) {
        try {
            const assetBRegistry = await getAssetRegistry('mynamespace.AssetB');
            await assetBRegistry.addAll(createAssetA.assetB);
            await addAssetA(createAssetA);
        } catch(error) {
            throw new Error ('Error while creating AssetBs' + '\n' + error);
        }
    }
    
    /**
     * Function to add assetA asset to registry
     * @param {*} createAssetA - The createAssetA Transaction containing the assetA info
     */
    async function addAssetA(createAssetA){
        try {
            const assetARegistry = await getAssetRegistry('mynamespace.AssetA');
            const newAssetA = castcreateAssetATxToAsset(createAssetA);
            await assetARegistry.add(newAssetA);
        } catch(error) {
            throw new Error ('Error when creating AssetA with id ' + createAssetA.id + '\n' + error);
        }
    }
    
    /**
     * Casts the createAssetA transaction as a AssetA Asset
     * @param {*} createAssetA - The transaction createAssetA to cast
     * @returns {mynamespace.AssetA} the AssetA Asset with the info from the createAssetA param
     */
    function castcreateAssetATxToAsset(createAssetA){
        const factory = getFactory();
        // Create a new instance of AssetA class from the mynamespace namespace with the id createAssetA.id
        let newAssetA = factory.newResource('mynamespace', 'AssetA', createAssetA.id);
        newAssetA.assetAFieldX = createAssetA.assetAFieldX;
        newAssetA.assetB = [];
        for (let i = 0; i