I am trying to issue a new identity to a participant, create a composer card and import it.
My base.cto file is
namespace com.algorythmix.base
participant Department identified by departmentId {
o String departmentId
}
My function to issue an identity
const initIdentities = () => {
return new Promise(async function(resolve, reject) {
try {
const businessNetworkConnection = new BusinessNetworkConnection();
await businessNetworkConnection.connect(adminCardName);
let departmentRegistry = await businessNetworkConnection.getParticipantRegistry(`${BASE_NS}.Department`);
let departmentOne = await departmentRegistry.get('departmentOne');
let deptOne = await businessNetworkConnection.issueIdentity(`${BASE_NS}.Department#${departmentOne.departmentId}`, 'departmentOne');
console.log(`userID = ${deptOne.userID}`);
console.log(`userSecret = ${deptOne.userSecret}`);
let departmentTwo = await departmentRegistry.get('departmentTwo');
let deptTwo = await businessNetworkConnection.issueIdentity(`${BASE_NS}.Department#${departmentTwo.departmentId}`, 'departmentTwo');
console.log(`userID = ${deptTwo.userID}`);
console.log(`userSecret = ${deptTwo.userSecret}`);
const adminConnection = new AdminConnection(); // { cardStore: $SOME_PATH_VARIABLE } to change def2ault card storage path
await adminConnection.connect(adminCardName); // Confirm this
console.log('connected');
const cardOne = new IdCard({
userName: 'departmentOne',
version: 1,
enrollmentSecret: deptOne.userSecret,
businessNetwork: 'chips'
}, connectionProfile);
const cardTwo = new IdCard({
userName: 'departmentTwo',
version: 1,
enrollmentSecret: deptTwo.userSecret,
businessNetwork: 'chips'
}, connectionProfile);
console.log('importing card one');
await adminConnection.importCard('departmentOne', cardOne);
await adminConnection.importCard('departmentTwo', cardTwo);
console.log('imported card two');
await businessNetworkConnection.disconnect();
await adminConnection.disconnect();
resolve();
} catch (e) {
reject(e);
};
});
};
Where adminCardName is the one generated when using composer network start command as per the basic tutorial provided here https://hyperledger.github.io/composer/latest/tutorials/deploy-to-fabric-single-org
And connectionProfile is taken from the above page as well. I have double checked the connection profile used by the admin#chips card and the one I have used is exactly the same.
Once I run the function, in composer card list, a card called departmentOne and departmentTwo is listed with the Business network shown as chips (as expected).
Now when I run composer network ping -c departmentOne, I get the error
Error: 2 UNKNOWN: error executing chaincode: transaction returned with failure: AccessException: Participant 'com.algorythmix.base.Department#departmentOne' does not have 'READ' access to resource 'org.hyperledger.composer.system.Network#chips#0.2.0'
Command failed
I have
1) Deleted permissions.acl which as per the documentation results in everyone getting full access
2) used following permissions.acl file
rule Default {
description: "Allow all participants access to all resources"
participant: "com.algorythmix.base.Department"
operation: ALL
resource: "org.hyperledger.composer.system.Network"
action: ALLOW
}
rule NetworkAdminUser {
description: "Grant business network administrators full access to user resources"
participant: "org.hyperledger.composer.system.NetworkAdmin"
operation: ALL
resource: "**"
action: ALLOW
}
rule NetworkAdminSystem {
description: "Grant business network administrators full access to system resources"
participant: "org.hyperledger.composer.system.NetworkAdmin"
operation: ALL
resource: "org.hyperledger.composer.system.**"
action: ALLOW
}
To specifically give the participant access to the network. I have also uploaded the .bna to composer-playground and it works over there as expected.
Can someone please guide me, as to what I am doing wrong?
Info:
Ubuntu - 16.0.4
Fabric - 1.1
Composer - 0.19.11
Node - 8.9.1
the error 'org.hyperledger.composer.system.Network#chips#0.2.0' suggests the underlying participant does not have the minimal READ access to the actual business network.
I would suggest a rule (rule 2) something like this:
rule ReadNetwork {
description: "Allow all participants to read network"
participant: "org.hyperledger.composer.system.Participant"
operation: READ
resource: "org.hyperledger.composer.system.Network"
action: ALLOW
}
I solved the same issue by changing the version in package.json file.
Related
I am building a Teams app that uses a notification bot to send an Adaptive Card to each member of a meeting. The code is essentially unchanged from the example code created by the Teams Toolkit:
for (const target of await bot.notification.installations()) {
if (target.type === "Group") {
const members = await target.members()
for (const member of members) {
await member.sendAdaptiveCard(<<adaptive card details>>)
}
}
}
It has no issue sending the card to the meeting creator, but for any other members, it throws the error message:
The bot encountered an unhandled error: Invalid user identity in provided tenant
I've tried logging the member objects, and there doesn't seem to be any missing or broken information. The other member definitely has their own tenantId. At this point, I'm at a loss for what the issue could be, since this is basically just using the provided code.
The solution ended up being that the bot can only send Adaptive Cards to users within the host's tenant/organization. Adding this if statement prevents the error:
for (const target of await bot.notification.installations()) {
if (target.type === "Group") {
const members = await target.members()
for (const member of members) {
if (member.account.tenantId === target.conversationReference.conversation.tenantId) {
await member.sendAdaptiveCard(<<adaptive card details>>)
}
}
}
}
and it does successfully send the card to members within the host tenant.
Summary: An external app can submit a transaction that writes to a single implicit private collection but fails when writing to 2 implicit collections. Everything works OK when using the vscode blockchain extension instead of the external app.
Details:
I am using the vscode blockchain extension (v2.0.8) When used, it installs microfab version 0.0.11.
I am using a 2-org network created in the vscode extension using the 2-org template.
I have a Smart Contract that writes data to 2 implicit private collections (for Org1 and Org2).
Here is the relevant portion of the smart contract (typescript):
#Transaction()
public async createMyPrivateAsset(ctx: Context, myPrivateAssetId: string): Promise<void> {
const exists: boolean = await this.myPrivateAssetExists(ctx, myPrivateAssetId);
if (exists) {
throw new Error(`The asset my private asset ${myPrivateAssetId} already exists`);
}
const privateAsset: MyPrivateAsset = new MyPrivateAsset();
const transientData: Map<string, Uint8Array> = ctx.stub.getTransient();
if (transientData.size === 0 || !transientData.has('privateValue')) {
throw new Error('The privateValue key was not specified in transient data. Please try again.');
}
privateAsset.privateValue = transientData.get('privateValue').toString();
const collectionName: string = await getCollectionName(ctx, ctx.clientIdentity.getMSPID());
await ctx.stub.putPrivateData(collectionName, myPrivateAssetId, Buffer.from(JSON.stringify(privateAsset)));
}
#Transaction()
public async createMyPrivateAssetMultiple(ctx: Context, myPrivateAssetId: string): Promise<void> {
const exists: boolean = await this.myPrivateAssetExists(ctx, myPrivateAssetId);
if (exists) {
throw new Error(`The asset my private asset ${myPrivateAssetId} already exists`);
}
const privateAsset: MyPrivateAsset = new MyPrivateAsset();
const transientData: Map<string, Uint8Array> = ctx.stub.getTransient();
if (transientData.size === 0 || !transientData.has('privateValue')) {
throw new Error('The privateValue key was not specified in transient data. Please try again.');
}
privateAsset.privateValue = transientData.get('privateValue').toString();
for (const mspid of ['Org1MSP', 'Org2MSP']) {
var collectionName: string = await getCollectionName(ctx, mspid);
await ctx.stub.putPrivateData(collectionName, myPrivateAssetId, Buffer.from(JSON.stringify(privateAsset)));
}
}
#Transaction(false)
#Returns('MyPrivateAsset')
public async readMyPrivateAsset(ctx: Context, myPrivateAssetId: string): Promise<string> {
const exists: boolean = await this.myPrivateAssetExists(ctx, myPrivateAssetId);
if (!exists) {
throw new Error(`The asset my private asset ${myPrivateAssetId} does not exist`);
}
let privateDataString: string;
const collectionName: string = await getCollectionName(ctx, ctx.clientIdentity.getMSPID());
const privateData: Uint8Array = await ctx.stub.getPrivateData(collectionName, myPrivateAssetId);
privateDataString = JSON.parse(privateData.toString());
return privateDataString;
}
createMyPrivateAsset writes to a single implicit collection: everything OK.
createMyPrivateAssetMultiple writes to 2 implicit collections: fails in external app.
Both transactions work perfectly when I use the vscode Transaction View to submit transactions.
For createMyPrivateAssetMultiple, I submit using the Org1 gateway and then call readMyPrivateAsset using the Org1 gateway and also using the Org2 gateway and the private data is returned correctly.
Now, when I use an external app, the transaction createMyPrivateAsset works but createMyPrivateAssetMultiple does not.
Here is the relevant portion of the app (typescript):
// connection
const gateway: Gateway = new Gateway();
const connectionProfilePath: string = path.resolve(__dirname, '..', connectionFile);
const connectionProfile = JSON.parse(fs.readFileSync(connectionProfilePath, 'utf8'));
const connectionOptions: GatewayOptions = { wallet, identity: identity, discovery: { enabled: true, asLocalhost: true } };
await gateway.connect(connectionProfile, connectionOptions);
// Get the network (channel) our contract is deployed to.
const network = await gateway.getNetwork('mychannel');
// Get the contract from the network.
const contract = network.getContract('private-contract');
Here is the transaction submission for createMyPrivateAsset
let transientData = {
'privateValue': Buffer.from(`Private value for asset ${assetId}`)
};
const trans:Transaction = contract.createTransaction('createMyPrivateAsset');
const buffer: Buffer = await trans.setTransient(transientData).submit(assetId);
This works fine in the app.
Here is the code for createMyPrivateAssetMultiple
let transientData = {
'privateValue': Buffer.from(`Private value for asset ${assetId}`)
};
const trans:Transaction = contract.createTransaction('createMyPrivateAssetMultiple');
const buffer: Buffer = await trans.setTransient(transientData).submit(assetId);
For this transaction, the app throws this (using Org1 gateway):
2022-06-07T13:21:50.727Z - warn: [TransactionEventHandler]: strategyFail: commit failure for transaction "4e9921b590a361ae01bba673e1d3d204d106522780c820055cec0345e1e67e6f": TransactionError: Commit of transaction 4e9921b590a361ae01bba673e1d3d204d106522780c820055cec0345e1e67e6f failed on peer org1peer-api.127-0-0-1.nip.io:8084 with status ENDORSEMENT_POLICY_FAILURE
The microfab docker container log includes this:
> WARN 0ff Failed fetching private data from remote peers for dig2src:[map[{b16526e4cd2ac3f431103cda23a6f64adc12acab0550eff18c1f25f1cc0d8bc1 private-contract _implicit_org_Org2MSP 6 0}:[]]], err: Empty membership channel=mychannel
...
[ org2peer] 2022-06-07 13:22:50.794 UTC [gossip.privdata] RetrievePvtdata -> WARN 220 Could not fetch all 1 eligible collection private write sets for block [20] (0 from local cache, 0 from transient store, 0 from other peers). Will commit block with missing private write sets:[txID: 4e9921b590a361ae01bba673e1d3d204d106522780c820055cec0345e1e67e6f, seq: 0, namespace: private-contract, collection: _implicit_org_Org2MSP, hash: 3e0f263d2edcfaf29df346504a40fdbadce0807938f204fe3e6bf753b751d9a3
Also, package.json includes this:
"dependencies": {
"fabric-network": "~2.1.0"
},
Can anyone shed light on this problem?
You should definitely be using fabric-network#2.2.x, not 2.1.x.
I suspect what is happening is the VS Code client is not using service discovery and sending proposals for endorsement to all network peers, whereas the standalone client application is using service discovery by default and only sending proposals to orgs required by the chaincode endorsement policy. To get it to consider the collection endorsement policy you would need to add a chaincode interest to the Contract object before submitting transactions:
https://hyperledger.github.io/fabric-sdk-node/release-2.2/module-fabric-network.Contract.html#addDiscoveryInterest
There is a working example of this in the Fabric samples:
https://github.com/hyperledger/fabric-samples/blob/8ca50df4ffec311e59451c2a7ebe210d9e6f0004/asset-transfer-private-data/application-javascript/app.js#L166-L178
Alternatively you could either:
Disable service discovery in the Gateway connection options.
Explicitly set the endorsing orgs for a given transaction invocation:
https://hyperledger.github.io/fabric-sdk-node/release-2.2/module-fabric-network.Transaction.html#setEndorsingOrganizations
In general it's much better to be using service discovery so I would not recommend option 1.
The best approach would actually be to use Fabric v2.4+ and the Fabric Gateway client API:
https://hyperledger.github.io/fabric-gateway/
With this API the client (generally) does not need to worry about the organizations required for endorsement when using private data collections or state-/key-based endorsement policies. Things just work automagically.
I'm trying to issue some Fabtokens to users and then use them in various scenarios such as transferring, redeeming, etc. I follow the Node SDK documentation here: https://fabric-sdk-node.github.io/master/tutorial-fabtoken.html
This is how they do the Fabtoken operations:
// create a TokenClient instance from client
const tokenclient = client.newTokenClient(mychannel);
// create a transaction ID for "issuer"
const txId = client.newTransactionID();
// create two parameters for issue, one for user1 and one for user2
const param1 = {
owner: user1.getIdentity().serialize(),
type: 'USD',
quantity: '500',
};
const param2 = {
owner: user2.getIdentity().serialize(),
type: 'EURO',
quantity: '300',
};
// create the token request for issue
const issueRequest = {
params: [param1, param2],
txId: txId,
};
// issuer calls issue method to issue tokens to user1 and user2
const result = await tokenClient.issue(issueRequest);
And then use a different tokenClient to list the tokens of user 1:
const user1Tokenclient = client1.newTokenClient(mychannel);
// user1 lists tokens
const mytokens = await user1TokenClient.list();
// iterate the tokens to get token id, type, and quantity for each token
for (const token of tokens) {
// get token.id, token.type, and token.quantity
// token.id will be used for transfer and redeem
}
It's mentioned on the Node SDK's Client class page here: https://fabric-sdk-node.github.io/master/Client.html that switching userContexts with the same client instance is an anti-pattern and not recommended since client instances are stateful.
As they suggest, I create my client instances with different user contexts. This is how I create my clients, set their user context and create my tokenClient instances:
const adminClient = new Fabric_Client();
const admin = await adminClient.createUser(user_opts);
adminClient.setUserContext(admin, true);
let adminConfig = {
admin: admin,
adminClient: adminClient,
adminTokenClient: adminClient.newTokenClient(channel)
}
const server = await serverClient.createUser(server_opts);
serverClient.setUserContext(server, true);
let serverConfig = {
server: server,
serverClient: serverClient,
serverTokenClient: serverClient.newTokenClient(channel)
}
Later on, I'm using these config objects to issue some tokens to different users. How I issue tokens to my server account from my issuer (admin) account:
const txId = adminConfig.adminClient.newTransactionID();
let issueQuery = {
tokenClient: adminConfig.adminTokenClient,
txId: txId,
channel: channel,
params: []
}
for(let i=0; i < 3; ++i) {
let param = {
owner: serverConfig.server.getIdentity().serialize(),
type: 'test',
quantity: '1'
}
issueQuery.params.push(param);
}
let issueTx = await waitForIssue(issueQuery);
This successfully issue three tokens to the server as expected. The problem is that when I try to access to the tokens of my server like the example they provide using a similar code:
let server_tokens = await serverConfig.serverTokenClient.list();
for (let server_token of server_tokens) {
console.log(server_token.id);
}
Result is just empty and I don't get any error messages. However, when I check the transaction using queryTransaction(txId) for the token issue transaction I generate, I can see that owner of the issued tokens in that transaction is the server and that's how I can be sure that I can successfully issue the tokens to the server. Is there any other way to check the tokens of my server? Or shouldn't I use a different client and user context per each user as they suggest? Because, previously I was able to see the tokens of the server when I used a single client and single user context to issue and list tokens. But this approach caused me problems when I was trying to transfer my tokens asynchronously.
As far as I know FabTokens are removed from the Master branch of Fabric 2.0 now as described in these links:
https://gerrit.hyperledger.org/r/c/fabric/+/32979
https://lists.hyperledger.org/g/fabric/topic/fabtoken/34150195?p=,,,20,0,0,0::recentpostdate%2Fsticky,,,20,2,0,34150195
I would expect the tutorial and the information in the Fabric docs to be removed in due course.
I am working in a proof of concept with a Node.js application and 'composer-client' npm module.
I have tried different commands such as adding a participant, adding an asset and performing a transaction and everything seems to work correctly.
However, when I try to issue a new identity I do not get the results that I expect. I execute my Node.js application with the following code:
var businessNetwork = new BusinessNetworkConnection();
return businessNetwork.connect('admin#tutorial-network')
.then(() => {
return businessNetwork.issueIdentity('org.acme.biznet.Trader#Trader_001', 'usr001')
})
.then((result) => {
console.log(`userID = ${result.userID}`);
console.log(`userSecret = ${result.userSecret}`);
})
.catch((error) => {
console.error(error);
});
Then, UserId and UserSecret are displayed at console log. After that, I try to do a ping to Business Network:
var businessNetwork = new BusinessNetworkConnection();
return businessNetwork.connect('usr001#tutorial-network')
.then(() => {
return businessNetwork.ping();
})
.then((result) => {
console.log(`participant = ${result.participant ? result.participant : '<no participant found>'}`);
})
.catch((error) => {
console.error(error);
});
However, I get the following error message:
{ Error: Card not found: usr001#tutorial-network
at IdCard.fromDirectory.catch.cause (/home/user.name/git_repositories/nodejs/first.blockchain.test/node_modules/composer-common/lib/cardstore/filesystemcardstore.js:73:27)
at <anonymous>
cause:
{ Error: Unable to read card directory: /home/user.name/.composer/cards/user001#tutorial-network
If I execute the command composer identity list -c admin#tutorial-network, I get the following output:
$class: org.hyperledger.composer.system.Identity
identityId: 9b49f67c262c0ae23e1e0c4a8dc61c4a12b5119df2b6a49fa2e02fa56b8818c3
name: usr001
issuer: 27c582d674ddf0f230854814b7cfd04553f3d0eac55e37d915386c614a5a1de9
certificate:
state: ISSUED
participant: resource:org.acme.biznet.Trader#Trader_001
But, I am not able to find the business card.
It works for me. I'm using composer 0.15.1.
var businessNetwork = new BusinessNetworkConnection();
return businessNetwork.connect('admin#carauction-network')
.then(() => {
return businessNetwork.ping();
})
.then((result) => {
console.log(`participant = ${result.participant ? result.participant : '<no participant found>'}`);
})
.catch((error) => {
console.error(error);
});
Output is like this
linux1#fabric:~/eventclient$ node event.js
participant = org.hyperledger.composer.system.NetworkAdmin#admin
You may need to import ID card to wallet ?
composer card import --file networkadmin.card
I had a similar issue late last week. Part of the upgrade instructions from V0.14 to V0.15 states that we have to delete the (if existing) ~/.composer, ~/.composer-connection-profiles and ~/.composer-credentials. I skipped that step on my first upgrade to v01.5 and encountered the error you are seeing. Went back and deleted those three folders, reinstalled the binaries and rechecked docker image status. Error went away and has not reappeared.
You named the new card user001, not user001##tutorial-network. Try connecting with just user001 as your connection card name.
I used the following command to create a card for the participant using the enrollment secret obtained from the javascript.
composer card create -u usr001 -s <enrollment_secret> -f usr001.card -n tutorial-network -p connection.json
You probably created the connection.json needed in some step before in the tutorial you are following. If this file is not available explicitly, you may get it from the composer wallet. In the current version, it can be located in /home/your_user/.composer/cards/. If you are only following the tutorial, any connection.json in this directory will do.
After that, you must add the credential created to the wallet using:
composer card import -f usr001.card
Your code for testing the issued identity is correct.
I am trying to use BusinessNetworkConnection for NodeJS, but how do I get the credentials from a deployed BNA on playground and save it in my server directory?
I am getting
2017-11-03T13:07:32.83-0400 [APP/PROC/WEB/0] ERR (node:62)
UnhandledPromiseRejectionWarning: Unhandled promise rejection
(rejection id: 1): Error: Error trying login and get user Context.
Error: Error trying to enroll user or load channel configuration.
Error: Enrollment failed with errors
[[{"code":400,"message":"Authorization failure"}]]
when I try using the code
const BusinessNetworkConnection = require("composer-client").BusinessNetworkConnection;
this.businessNetworkConnection = new BusinessNetworkConnection();
this.CONNECTION_PROFILE_NAME = "ibm-bc-org1";
this.businessNetworkIdentifier = "giveback";
this.businessNetworkConnection
.connect(
this.CONNECTION_PROFILE_NAME,
this.businessNetworkIdentifier,
"admin",
"adminpw"
)
.then(result => {
this.businessNetworkDefinition = result;
console.log("BusinessNetworkConnection: ", result);
})
I have a directory /home/vcap/app/.composer-connection-profiles/ibm-bc-org1 with a connection.json file that references /home/vcap/app/.composer-credentials/ibm-bc-org1 for my credentials. The code worked for composer#0.13.2 but now I moved over to composer#0.14.3. I removed the previous credentials and created a new playground etc, everything is fresh and clean.
There is a work around when something like this happens. Because I am trying to connect with the credentials that Composer created the with admin || adminpw. You can just define a new participant in your model file and then give that participant system permissions to query and listen for events that the Historian emits.
For example in your model.cto:
participant ServerAdmin identified by serverhash {
// ServerAdmin is a type of participant
o String serverhash
}
And in your permissions.acl add :
rule ServerAdminUser {
description: "Grant business network administrators full access to
user resources"
participant: "org.acme.sample.ServerAdmin"
operation: ALL
resource: "**"
action: ALLOW
}
rule ServerAdminSystem {
description: "Grant business network administrators full access to system resources"
participant: "org.acme.sample.ServerAdmin"
operation: ALL
resource: "org.hyperledger.composer.system.**"
action: ALLOW
}
Then after the network has been deployed by Composer on playground, you can add this participant VIA the REST API, and issue it a new identity. This will give you a User ID and User Secret with which you can use in BusinessNetworkConnection.connect()