Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

queryByChaincode が offlineでも使えるか調べる #12

Closed
shohu opened this issue Jun 19, 2019 · 6 comments
Closed

queryByChaincode が offlineでも使えるか調べる #12

shohu opened this issue Jun 19, 2019 · 6 comments

Comments

@shohu
Copy link
Owner

shohu commented Jun 19, 2019

#11 (comment)
でも書いたが、ledgerからデータだけ取得したい場合のofflineの方法がわからない。

  const response = await channel.sendSignedTransaction({
    signedProposal: signedCommitProposal,
    request: commitReq
  })

を使った際のresponseが

{ status: 'SUCCESS', info: '' }

とだけで、データが取得できない。

queryByChaincode は channelからtransaction発行するわけではなさそうなので、こちらを調べてみる

@shohu
Copy link
Owner Author

shohu commented Jun 19, 2019

https://github.com/loucho/hyperledger-fabric/blob/master/fabcar/javascript-low-level/query.js#L23-L28
この辺りにhintが。

以下で実現できてるっぽい

var firstnetwork_path = path.resolve('..', '..', 'first-network');
var org1tlscacert_path = path.resolve(firstnetwork_path, 'crypto-config', 'peerOrganizations', 'org1.example.com', 'tlsca', 'tlsca.org1.example.com-cert.pem');
var org1tlscacert = fs.readFileSync(org1tlscacert_path, 'utf8');
: 
// setup the fabric network
var channel = fabric_client.newChannel('mychannel');
var peer = fabric_client.newPeer('grpcs://localhost:7051', {
	'ssl-target-name-override': 'peer0.org1.example.com',
	pem: org1tlscacert
});
channel.addPeer(peer);

// queryCar chaincode function - requires 1 argument, ex: args: ['CAR4'],
// queryAllCars chaincode function - requires no arguments , ex: args: [''],
const request = {
  //targets : --- letting this default to the peers assigned to the channel
  chaincodeId: 'fabcar',
  fcn: 'queryAllCars',
  args: ['']
};

// send the query proposal to the peer
return channel.queryByChaincode(request);

ただ、以下のように実装しても

    var request = {
        fcn: 'find',
        args: [
            'apart',
            '000001',
        ],
        chaincodeId: 'estate'
    };
    let response_payloads = await channel.queryByChaincode(request);

以下エラー

Error: No identity has been assigned to this client

@shohu
Copy link
Owner Author

shohu commented Jun 19, 2019

以下部分で "No identity has been assigned to this client" を出している。

_getSigningIdentity(admin) {
  logger.debug('_getSigningIdentity - admin parameter is %s :%s', (typeof admin), admin);
  if (admin && this._adminSigningIdentity) {
    return this._adminSigningIdentity;
  } else {
    if (this._userContext) {
      return this._userContext.getSigningIdentity();
    } else {
      throw new Error('No identity has been assigned to this client');
    }
  }
}

user.js

/**
 * Get the {@link SigningIdentity} object for this User instance, used to generate signatures
 * @returns {SigningIdentity} the identity object that encapsulates the user's private key for signing
 */
getSigningIdentity() {
  return this._signingIdentity;
}

queryByChaincode

/**
 * Sends a proposal to one or more endorsing peers that will be handled by the chaincode.
 * There is no difference in how the endorsing peers process a request
 * to invoke a chaincode for transaction vs. to invoke a chaincode for query. All requests
 * will be presented to the target chaincode's 'Invoke' method which must be implemented to
 * understand from the arguments that this is a query request. The chaincode must also return
 * results in the byte array format and the caller will have to be able to decode
 * these results.
 *
 * If the request contains a <code>txId</code> property, that transaction ID will be used, and its administrative
 * privileges will apply. In this case the <code>useAdmin</code> parameter to this function will be ignored.
 *
 * @param {ChaincodeQueryRequest} request
 * @param {boolean} useAdmin - Optional. Indicates that the admin credentials should be used in making
 *                  this call. Ignored if the <code>request</code> contains a <code>txId</code> property.
 * @returns {Promise} A Promise for an array of byte array results returned from the chaincode
 *                    on all Endorsing Peers
 * @example
 * <caption>Get the list of query results returned by the chaincode</caption>
 * const responsePayloads = await channel.queryByChaincode(request);
 * for (let i = 0; i < responsePayloads.length; i++) {
 *     console.log(util.format('Query result from peer [%s]: %s', i, responsePayloads[i].toString('utf8')));
 * }
 */
async queryByChaincode(request, useAdmin) {
  logger.debug('queryByChaincode - start');
  if (!request) {
    throw new Error('Missing request object for this queryByChaincode call.');
  }

  if (request.txId) {
    useAdmin = request.txId.isAdmin();
  }

  const targets = this._getTargets(request.targets, Constants.NetworkConfig.CHAINCODE_QUERY_ROLE);
  const signer = this._clientContext._getSigningIdentity(useAdmin);
  const txId = request.txId || new TransactionID(signer, useAdmin);

const proposalResults = await Channel.sendTransactionProposal(query_request, this._name, this._clientContext, request.request_timeout);
  const responses = proposalResults[0];
  logger.debug('queryByChaincode - results received');

  if (!responses || !Array.isArray(responses)) {
    throw new Error('Payload results are missing from the chaincode query');
  }

  const results = [];
  responses.forEach((response) => {
    if (response instanceof Error) {
      results.push(response);
    } else if (response.response && response.response.payload) {
      if (response.response.status === 200) {
        results.push(response.response.payload);
      } else {
        if (response.response.message) {
          results.push(new Error(response.response.message));
        } else {
          results.push(new Error(response));
        }
      }
    } else {
      logger.error('queryByChaincode - unknown or missing results in query ::' + results);
      results.push(new Error(response));
    }
  });

  return results;
/*
 * Internal static method to allow transaction proposals to be called without
 * creating a new channel
 */
static async sendTransactionProposal(request, channelId, client_context, timeout) {
  const method = 'sendTransactionProposal(static)';
  logger.debug('%s - start', method);

  let errorMsg = client_utils.checkProposalRequest(request, true);

  if (errorMsg) {
    // do nothing so we skip the rest of the checks
  } else if (!request.args) {
    // args is not optional because we need for transaction to execute
    errorMsg = 'Missing "args" in Transaction proposal request';
  } else if (!request.targets || request.targets.length < 1) {
    errorMsg = 'Missing peer objects in Transaction proposal';
  }

  if (errorMsg) {
    logger.error('%s error %s', method, errorMsg);
    throw new Error(errorMsg);
  }

  const proposal = Channel._buildSignedProposal(request, channelId, client_context);

  const responses = await client_utils.sendPeersProposal(request.targets, proposal.signed, timeout);
  return [responses, proposal.source];
}

adminで実行してしまうとまずいので、_userContext があればいいと。
_userContext が設定されている箇所は以下

/**
 * Sets an instance of the {@link User} class as the security context of this client instance. This user’s
 * signing identity (the private key and its corresponding certificate), will be used to sign all requests
 * with the fabric backend.
 * <br><br>
 * Upon setting the user context, the SDK saves the object in a persistence cache if the “state store”
 * has been set on the Client instance. If no state store has been set, this cache will not be established
 * and the application is responsible for setting the user context again if the application crashes and is recovered.
 *
 * @param {User | UserNamePasswordObject} user - An instance of the User class encapsulating the authenticated
 *                      user’s signing materials (private key and enrollment certificate).
 *                      The parameter may also be a {@link UserNamePasswordObject} that contains the username
 *                      and optionally the password and caName. A common connection profile must has been loaded to use the
 *                      {@link UserNamePasswordObject} which will also create the user context and set it on
 *                      this client instance. The created user context will be based on the current network
 *                      configuration( i.e. the current organization's CA, current persistence stores).
 * @param {boolean} skipPersistence - Whether to skip saving the user object into persistence. Default is false and the
 *                                    method will attempt to save the user object to the state store. When using a
 *                                    common connection profile and {@link UserNamePasswordObject}, the user object will
 *                                    always be stored to the persistence store.
 * @returns {Promise} Promise of the 'user' object upon successful persistence of the user to the state store
 */
async setUserContext(user, skipPersistence) {
  logger.debug(`setUserContext - user: ${user}, skipPersistence: ${skipPersistence}`);
  if (!user) {
    logger.debug('setUserContext, Cannot save null userContext.');
    throw new Error('Cannot save null userContext.');
  }

  if (user && user.constructor && user.constructor.name === 'User') {
    this._userContext = user;
    if (!skipPersistence) {
      logger.debug('setUserContext - begin promise to saveUserToStateStore');
      return this.saveUserToStateStore();
    }
    logger.debug('setUserContext - resolved user');
    return user;
  }
  // must be they have passed in an object
  logger.debug('setUserContext - will try to use common connection profile to set the user');
  return this._setUserFromConfig(user);
}

@shohu
Copy link
Owner Author

shohu commented Jun 19, 2019

以下作ってみたが相変わらず "Error: No identity has been assigned to this client"

    const testclient = new Client();
    var testchannel = testclient.newChannel('mychannel');
    const peerTLSCertPath = path.resolve(
        __dirname,
        '../../first-network/crypto-config/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/msp/tlscacerts/tlsca.org1.example.com-cert.pem'
    )
    const peerPEMCert = fs.readFileSync(peerTLSCertPath, 'utf8')
    const peer = testclient.newPeer('grpcs://localhost:7051', {
        pem: peerPEMCert,
        'ssl-target-name-override': 'peer0.org1.example.com'
    })
    testchannel.addPeer(peer);

    const request = {
        fcn: 'find',
        args: [
            'apart',
            '000001',
        ],
        chaincodeId: 'estate'
    };
    let response = await testchannel.queryByChaincode(request);
    console.log('evaluteTest ====== ', response);

やはり、setUserContext を作る必要がある。
ただ、setUserContext には以下のように password 渡すことになるから、サーバー側にpassword渡ってしまう?

client.setUserContext({username:'user1', password:secret});

@shohu
Copy link
Owner Author

shohu commented Jun 19, 2019

fabcarでqueryByChaincodeを試してみる

動かす

$ ./startFabric.sh javascript
$ cd javascript-low-level/
$ npm install
$ node enrollAdmin.js
 Store path:/Users/shohu33/sc/estate-sample/fabcar/javascript-low-level/hfc-key-store
Successfully enrolled admin user "admin"
Assigned the admin user to the fabric client ::{"name":"admin","mspid":"Org1MSP","roles":null,"affiliation":"","enrollmentSecret":"","enrollment":{"signingIdentity":"7d25ef56083b2cb85cef53ae1352283b613ca19006953a81a17f9e8f3c3e0707","identity":{"certificate":"-----BEGIN CERTIFICATE-----\nMIICAjCCAaigAwIBAgIUGx1pRPdexcGhZ5GBUxsZzA6AKDcwCgYIKoZIzj0EAwIw\nczELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFjAUBgNVBAcTDVNh\nbiBGcmFuY2lzY28xGTAXBgNVBAoTEG9yZzEuZXhhbXBsZS5jb20xHDAaBgNVBAMT\nE2NhLm9yZzEuZXhhbXBsZS5jb20wHhcNMTkwNjE5MDE1NzAwWhcNMjAwNjE4MDIw\nMjAwWjAhMQ8wDQYDVQQLEwZjbGllbnQxDjAMBgNVBAMTBWFkbWluMFkwEwYHKoZI\nzj0CAQYIKoZIzj0DAQcDQgAE5wyP3hVtaFTYg0fx3IC8d33IDVpUEHw4WwWQO1le\nWrR9O/KxOn2kf5BtVKNPdGAYF2CfmK7+BvBSh8quhzbDjaNsMGowDgYDVR0PAQH/\nBAQDAgeAMAwGA1UdEwEB/wQCMAAwHQYDVR0OBBYEFPPjMBDvuOOQe0Vl+GXxfLez\nTEBSMCsGA1UdIwQkMCKAIBe2+SNa4mfmXrND5Psf5M3vNpBSTZhMQXHBoyyJ8LMJ\nMAoGCCqGSM49BAMCA0gAMEUCIQCKHgLSe2uuBwsXjetoljQjbADSVAcrMOUlxUME\nJLwKPAIgYBKosJojR4awlXj7iAwOUpCQ2e1hl4gnq36Gf376Zqg=\n-----END CERTIFICATE-----\n"}}}
$ node registerUser.js
 Store path:/Users/shohu33/sc/estate-sample/fabcar/javascript-low-level/hfc-key-store
Successfully loaded admin from persistence
Successfully registered user1 - secret:NmrTRXobGVVP
Successfully enrolled member user "user1"
User1 was successfully registered and enrolled and is ready to interact with the fabric network
$ node invoke.js
 --- invoke.js - start
Setting up client side network objects
Created client side object to represent the channel
Created client side object to represent the peer
Setting up the user store at path:/Users/shohu33/sc/estate-sample/fabcar/javascript-low-level/hfc-key-store
Successfully loaded "user1" from user store
Successfully setup client side

Start invoke processing
Used service discovery to initialize the channel

Created a transaction ID: 93b98c97519fcdaa77ad20ddc270d0d6cdc80686abf8fd52d4c55868f1f94903
Successfully sent Proposal and received response: Status - 200
Registered transaction listener with the peer event service for transaction ID:93b98c97519fcdaa77ad20ddc270d0d6cdc80686abf8fd52d4c55868f1f94903
Sending endorsed transaction to the orderer
The transaction has been committed on peer localhost:7051
Successfully sent transaction to the orderer
Successfully committed the change to the ledger by the peer
 - try running "node query.js" to see the results
 --- invoke.js - end

同じ書き方で、以下のようにデータ取得できるんだよな、、、なぞ

$ node query.js
Store path:/Users/shohu33/sc/estate-sample/fabcar/javascript-low-level/hfc-key-store
Successfully loaded user1 from persistence
Query has completed, checking results
Response is  "[{\"Key\":\"CAR0\",\"Record\":{\"color\":\"blue\",\"docType\":\"car\",\"make\":\"Toyota\",\"model\":\"Prius\",\"owner\":\"Tomoko\"}},{\"Key\":\"CAR1\",\"Record\":{\"color\":\"red\",\"docType\":\"car\",\"make\":\"Ford\",\"model\":\"Mustang\",\"owner\":\"Brad\"}},{\"Key\":\"CAR12\",\"Record\":{\"color\":\"Black\",\"docType\":\"car\",\"make\":\"Honda\",\"model\":\"Accord\",\"owner\":\"Tom\"}},{\"Key\":\"CAR2\",\"Record\":{\"color\":\"green\",\"docType\":\"car\",\"make\":\"Hyundai\",\"model\":\"Tucson\",\"owner\":\"Jin Soo\"}},{\"Key\":\"CAR3\",\"Record\":{\"color\":\"yellow\",\"docType\":\"car\",\"make\":\"Volkswagen\",\"model\":\"Passat\",\"owner\":\"Max\"}},{\"Key\":\"CAR4\",\"Record\":{\"color\":\"black\",\"docType\":\"car\",\"make\":\"Tesla\",\"model\":\"S\",\"owner\":\"Adriana\"}},{\"Key\":\"CAR5\",\"Record\":{\"color\":\"purple\",\"docType\":\"car\",\"make\":\"Peugeot\",\"model\":\"205\",\"owner\":\"Michel\"}},{\"Key\":\"CAR6\",\"Record\":{\"color\":\"white\",\"docType\":\"car\",\"make\":\"Chery\",\"model\":\"S22L\",\"owner\":\"Aarav\"}},{\"Key\":\"CAR7\",\"Record\":{\"color\":\"violet\",\"docType\":\"car\",\"make\":\"Fiat\",\"model\":\"Punto\",\"owner\":\"Pari\"}},{\"Key\":\"CAR8\",\"Record\":{\"color\":\"indigo\",\"docType\":\"car\",\"make\":\"Tata\",\"model\":\"Nano\",\"owner\":\"Valeria\"}},{\"Key\":\"CAR9\",\"Record\":{\"color\":\"brown\",\"docType\":\"car\",\"make\":\"Holden\",\"model\":\"Barina\",\"owner\":\"Shotaro\"}}]"

わかった

newDefaultKeyValueStoreとかで前処理やってるのか。これだ。

// create the key value store as defined in the fabric-client/config/default.json 'key-value-store' setting
Fabric_Client.newDefaultKeyValueStore({ path: store_path
}).then((state_store) => {
	// assign the store to the fabric client
	fabric_client.setStateStore(state_store);
	var crypto_suite = Fabric_Client.newCryptoSuite();
	// use the same location for the state store (where the users' certificate are kept)
	// and the crypto store (where the users' keys are kept)
	var crypto_store = Fabric_Client.newCryptoKeyStore({path: store_path});
	crypto_suite.setCryptoKeyStore(crypto_store);
	fabric_client.setCryptoSuite(crypto_suite);

	// get the enrolled user from persistence, this user will sign all requests
	return fabric_client.getUserContext('user1', true);
}).then((user_from_store) => {
	if (user_from_store && user_from_store.isEnrolled()) {
		console.log('Successfully loaded user1 from persistence');
	} else {
		throw new Error('Failed to get user1.... run registerUser.js');
	}

	// queryCar chaincode function - requires 1 argument, ex: args: ['CAR4'],
	// queryAllCars chaincode function - requires no arguments , ex: args: [''],
	const request = {
		//targets : --- letting this default to the peers assigned to the channel
		chaincodeId: 'fabcar',
		fcn: 'queryAllCars',
		args: ['']
	};

	// send the query proposal to the peer
	return channel.queryByChaincode(request);

@shohu
Copy link
Owner Author

shohu commented Jun 19, 2019

queryByChaincode は client. setUserContext つかわなければ access できなさそうで、これは secret をサーバーに通信しなければいけなさそうだから、offlineじゃないっぽい?

@shohu
Copy link
Owner Author

shohu commented Jun 19, 2019

#13 で解決

@shohu shohu closed this as completed Jun 19, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant