Skip to content

Commit 7425946

Browse files
committed
feat(cactus-plugin-ledger-connector-fabric): support delegated (offline) signatures
- Add new `RunDelegatedSignTransactionEndpointV1` endpoint for delegated / offline signing. Takes `signerCertificate` and `signerMspID`, uses `signCallback` on connector to sign messages. Sign must be implemented by a user, can contain any logic (contacting 3'rd party services, reading from secure sources, etc…). Interface is similar to transact. Supports private transactions. - Refactor transact endpoint: Use common logic for handling response format. with delegated transact - Refactor logic of choosing ednorsers in transact endpoint. Previously both `endorsingPeers` and `endorsingParties` were selecting organizations in sligly different way under different circumstances. Now `endorsingPeers` selectes peers and `endorsingOrgs` selects orgs for all cases (query, send, privatesend) in both transact and transact with delegated sign. This is more consistent and predictable. - Add new socketio endpoint `SubscribeDelegatedSign` for monitoring new blocks with delegated sign. - Use common error handling in getblock, transact and transact delgated endpoints. - Add functional tests for delegated signing feature. Depends on: #2598 Signed-off-by: Michal Bajer <[email protected]>
1 parent 14fe9ca commit 7425946

File tree

40 files changed

+2788
-277
lines changed

40 files changed

+2788
-277
lines changed

.cspell.json

+4
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121
"cafile",
2222
"caio",
2323
"cccs",
24+
"ccep",
25+
"cccg",
2426
"cbdc",
2527
"Cbdc",
2628
"ccid",
@@ -64,6 +66,7 @@
6466
"HTLC",
6567
"Hursley",
6668
"HyperLedger",
69+
"immalleable",
6770
"ipaddress",
6871
"ipfs",
6972
"Iroha",
@@ -86,6 +89,7 @@
8689
"miekg",
8790
"mitchellh",
8891
"MSPCONFIGPATH",
92+
"Mspids",
8993
"MSPID",
9094
"MSPIDSCOPEALLFORTX",
9195
"MSPIDSCOPEANYFORTX",

.github/workflows/ci.yaml

+1
Original file line numberDiff line numberDiff line change
@@ -831,6 +831,7 @@ jobs:
831831
CACTI_NPM_PACKAGE_NAME: "@hyperledger/cactus-plugin-ledger-connector-fabric"
832832
HFC_LOGGING: '{"debug":"console","info":"console","warn": "console","error":"console"}'
833833
FULL_BUILD_DISABLED: true
834+
FREE_UP_GITHUB_RUNNER_DISK_SPACE_DISABLED: false
834835
JEST_TEST_PATTERN: packages/cactus-plugin-ledger-connector-fabric/src/test/typescript/(unit|integration|benchmark)/.*/*.test.ts
835836
JEST_TEST_RUNNER_DISABLED: false
836837
TAPE_TEST_PATTERN: ""

packages/cactus-plugin-ledger-connector-fabric/README.md

+43
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
- [1.5 Monitoring new blocks (WatchBlocks)](#15-monitoring-new-blocks-watchblocks)
1414
- [1.5.1 Example](#151-example)
1515
- [1.5.2 Listener Type](#152-listener-type)
16+
- [1.6 Delegated Signature](#16-delegated-signature)
17+
- [1.6.1 Example](#161-example)
1618
- [2. Architecture](#2-architecture)
1719
- [2.1. run-transaction-endpoint](#21-run-transaction-endpoint)
1820
- [3. Containerization](#3-containerization)
@@ -329,6 +331,47 @@ Corresponds directly to `BlockType` from `fabric-common`:
329331
- `WatchBlocksListenerTypeV1.Full`,
330332
- `WatchBlocksListenerTypeV1.Private`,
331333

334+
### 1.6 Delegated Signature
335+
- Custom signature callback can be used when increased security is needed or currently available options are not sufficient.
336+
- Signature callback is used whenever fabric request must be signed.
337+
- To use delegate signature instead of identity supplied directly / through keychain use `transactDelegatedSign` (for transact) or `watchBlocksDelegatedSignV1` for block monitoring.
338+
- `uniqueTransactionData` can be passed to each delegate sign method on connector. This data is passed to signCallback to identify and verify the request. It can be used to pass signing tokens or any other data needed for performing the signing (e.g. user, scopes, etc...).
339+
- `signProposal` method from this package can be used to sign the requests in offline location.
340+
- For more complex examples see tests: `delegate-signing-methods.test` and `fabric-watch-blocks-delegated-sign-v1-endpoint.test`.
341+
342+
#### 1.6.1 Example
343+
```typescript
344+
// Setup - supply callback when instantiating the connector plugin
345+
fabricConnectorPlugin = new PluginLedgerConnectorFabric({
346+
instanceId: uuidv4(),
347+
// ...
348+
signCallback: async (payload, txData) => {
349+
log.debug("signCallback called with txData (token):", txData);
350+
return signProposal(adminIdentity.credentials.privateKey, payload);
351+
},
352+
});
353+
354+
// Run transactions
355+
await apiClient.runDelegatedSignTransactionV1({
356+
signerCertificate: adminIdentity.credentials.certificate,
357+
signerMspID: adminIdentity.mspId,
358+
channelName: ledgerChannelName,
359+
contractName: assetTradeContractName,
360+
invocationType: FabricContractInvocationType.Call,
361+
methodName: "ReadAsset",
362+
params: ["asset1"],
363+
uniqueTransactionData: myJwtToken,
364+
});
365+
366+
// Monitor for transactions:
367+
apiClient.watchBlocksDelegatedSignV1({
368+
type: WatchBlocksListenerTypeV1.CactusTransactions,
369+
signerCertificate: adminIdentity.credentials.certificate,
370+
signerMspID: adminIdentity.mspId,
371+
channelName: ledgerChannelName,
372+
})
373+
```
374+
332375
##### Cactus (custom)
333376
Parses the data and returns custom formatted block.
334377
- `WatchBlocksListenerTypeV1.CactusTransactions`: Returns transactions summary. Compatible with legacy `fabric-socketio` monitoring operation.

packages/cactus-plugin-ledger-connector-fabric/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@
7777
"node-vault": "0.9.22",
7878
"openapi-types": "9.1.0",
7979
"prom-client": "13.2.0",
80+
"run-time-error": "1.4.0",
8081
"rxjs": "7.8.1",
8182
"sanitize-filename": "1.6.3",
8283
"sanitize-html": "2.7.0",

packages/cactus-plugin-ledger-connector-fabric/src/main/json/openapi.json

+201-17
Original file line numberDiff line numberDiff line change
@@ -373,6 +373,15 @@
373373
}
374374
}
375375
},
376+
"RunTransactionResponseType": {
377+
"type": "string",
378+
"description": "Response format from transaction / query execution",
379+
"enum": [
380+
"org.hyperledger.cacti.api.hlfabric.RunTransactionResponseType.JSON",
381+
"org.hyperledger.cacti.api.hlfabric.RunTransactionResponseType.UTF8"
382+
],
383+
"x-enum-varnames": ["JSON", "UTF8"]
384+
},
376385
"RunTransactionRequest": {
377386
"type": "object",
378387
"required": [
@@ -386,7 +395,17 @@
386395
"additionalProperties": false,
387396
"properties": {
388397
"endorsingPeers": {
389-
"description": "An array of MSP IDs to set as the list of endorsing peers for the transaction.",
398+
"description": "An array of endorsing peers (name or url) for the transaction.",
399+
"type": "array",
400+
"items": {
401+
"type": "string",
402+
"minLength": 1,
403+
"maxLength": 4096,
404+
"nullable": false
405+
}
406+
},
407+
"endorsingOrgs": {
408+
"description": "An array of endorsing organizations (by mspID or issuer org name on certificate) for the transaction.",
390409
"type": "array",
391410
"items": {
392411
"type": "string",
@@ -439,35 +458,108 @@
439458
"nullable": true
440459
}
441460
},
442-
"endorsingParties": {
443-
"type": "array",
444-
"nullable": false,
445-
"default": [],
446-
"items": {
447-
"type": "string",
448-
"nullable": true
449-
}
450-
},
451461
"responseType": {
452-
"type": "string"
462+
"$ref": "#/components/schemas/RunTransactionResponseType"
453463
}
454464
}
455465
},
456466
"RunTransactionResponse": {
457467
"type": "object",
458-
"required": ["functionOutput", "success", "transactionId"],
468+
"required": ["functionOutput", "transactionId"],
459469
"properties": {
460470
"functionOutput": {
461471
"type": "string",
462472
"nullable": false
463473
},
464-
"success": {
465-
"type": "boolean",
474+
"transactionId": {
475+
"type": "string",
476+
"nullable": false
477+
}
478+
}
479+
},
480+
"RunDelegatedSignTransactionRequest": {
481+
"type": "object",
482+
"required": [
483+
"signerCertificate",
484+
"signerMspID",
485+
"channelName",
486+
"contractName",
487+
"invocationType",
488+
"methodName",
489+
"params"
490+
],
491+
"additionalProperties": false,
492+
"properties": {
493+
"endorsingPeers": {
494+
"description": "An array of endorsing peers (name or url) for the transaction.",
495+
"type": "array",
496+
"items": {
497+
"type": "string",
498+
"minLength": 1,
499+
"maxLength": 4096,
500+
"nullable": false
501+
}
502+
},
503+
"endorsingOrgs": {
504+
"description": "An array of endorsing organizations (by mspID or issuer org name on certificate) for the transaction.",
505+
"type": "array",
506+
"items": {
507+
"type": "string",
508+
"minLength": 1,
509+
"maxLength": 4096,
510+
"nullable": false
511+
}
512+
},
513+
"transientData": {
514+
"type": "object",
515+
"nullable": true
516+
},
517+
"signerCertificate": {
518+
"type": "string",
466519
"nullable": false
467520
},
468-
"transactionId": {
521+
"signerMspID": {
469522
"type": "string",
470523
"nullable": false
524+
},
525+
"uniqueTransactionData": {
526+
"description": "Can be used to uniquely identify and authorize signing request",
527+
"nullable": false
528+
},
529+
"channelName": {
530+
"type": "string",
531+
"minLength": 1,
532+
"maxLength": 100,
533+
"nullable": false
534+
},
535+
"contractName": {
536+
"type": "string",
537+
"minLength": 1,
538+
"maxLength": 100,
539+
"nullable": false
540+
},
541+
"invocationType": {
542+
"$ref": "#/components/schemas/FabricContractInvocationType",
543+
"nullable": false,
544+
"description": "Indicates if it is a CALL or a SEND type of invocation where only SEND ends up creating an actual transaction on the ledger."
545+
},
546+
"methodName": {
547+
"type": "string",
548+
"minLength": 1,
549+
"maxLength": 100,
550+
"nullable": false
551+
},
552+
"params": {
553+
"type": "array",
554+
"nullable": false,
555+
"default": [],
556+
"items": {
557+
"type": "string",
558+
"nullable": true
559+
}
560+
},
561+
"responseType": {
562+
"$ref": "#/components/schemas/RunTransactionResponseType"
471563
}
472564
}
473565
},
@@ -1030,13 +1122,15 @@
10301122
"description": "Websocket requests for monitoring new blocks.",
10311123
"enum": [
10321124
"org.hyperledger.cactus.api.async.hlfabric.WatchBlocksV1.Subscribe",
1125+
"org.hyperledger.cactus.api.async.hlfabric.WatchBlocksV1.SubscribeDelegatedSign",
10331126
"org.hyperledger.cactus.api.async.hlfabric.WatchBlocksV1.Next",
10341127
"org.hyperledger.cactus.api.async.hlfabric.WatchBlocksV1.Unsubscribe",
10351128
"org.hyperledger.cactus.api.async.hlfabric.WatchBlocksV1.Error",
10361129
"org.hyperledger.cactus.api.async.hlfabric.WatchBlocksV1.Complete"
10371130
],
10381131
"x-enum-varnames": [
10391132
"Subscribe",
1133+
"SubscribeDelegatedSign",
10401134
"Next",
10411135
"Unsubscribe",
10421136
"Error",
@@ -1080,6 +1174,43 @@
10801174
}
10811175
}
10821176
},
1177+
"WatchBlocksDelegatedSignOptionsV1": {
1178+
"type": "object",
1179+
"description": "Options passed when subscribing to block monitoring with delegated signing.",
1180+
"required": ["type", "channelName", "signerCertificate", "signerMspID"],
1181+
"properties": {
1182+
"type": {
1183+
"$ref": "#/components/schemas/WatchBlocksListenerTypeV1",
1184+
"description": "Type of response block to return.",
1185+
"nullable": false
1186+
},
1187+
"startBlock": {
1188+
"type": "string",
1189+
"description": "From which block start monitoring. Defaults to latest.",
1190+
"minLength": 1,
1191+
"maxLength": 100,
1192+
"nullable": false
1193+
},
1194+
"channelName": {
1195+
"type": "string",
1196+
"minLength": 1,
1197+
"maxLength": 100,
1198+
"nullable": false
1199+
},
1200+
"signerCertificate": {
1201+
"type": "string",
1202+
"nullable": false
1203+
},
1204+
"signerMspID": {
1205+
"type": "string",
1206+
"nullable": false
1207+
},
1208+
"uniqueTransactionData": {
1209+
"description": "Can be used to uniquely identify and authorize signing request",
1210+
"nullable": false
1211+
}
1212+
}
1213+
},
10831214
"WatchBlocksCactusTransactionsEventV1": {
10841215
"type": "object",
10851216
"description": "Transaction summary from commited block.",
@@ -1240,8 +1371,61 @@
12401371
}
12411372
}
12421373
},
1243-
"404": {
1244-
"description": ""
1374+
"500": {
1375+
"description": "Internal Server Error",
1376+
"content": {
1377+
"application/json": {
1378+
"schema": {
1379+
"$ref": "#/components/schemas/ErrorExceptionResponseV1"
1380+
}
1381+
}
1382+
}
1383+
}
1384+
}
1385+
}
1386+
},
1387+
"/api/v1/plugins/@hyperledger/cactus-plugin-ledger-connector-fabric/run-delegated-sign-transaction": {
1388+
"post": {
1389+
"x-hyperledger-cactus": {
1390+
"http": {
1391+
"verbLowerCase": "post",
1392+
"path": "/api/v1/plugins/@hyperledger/cactus-plugin-ledger-connector-fabric/run-delegated-sign-transaction"
1393+
}
1394+
},
1395+
"operationId": "runDelegatedSignTransactionV1",
1396+
"summary": "Runs a transaction on a Fabric ledger using user-provided signing callback.",
1397+
"description": "",
1398+
"parameters": [],
1399+
"requestBody": {
1400+
"required": true,
1401+
"content": {
1402+
"application/json": {
1403+
"schema": {
1404+
"$ref": "#/components/schemas/RunDelegatedSignTransactionRequest"
1405+
}
1406+
}
1407+
}
1408+
},
1409+
"responses": {
1410+
"200": {
1411+
"description": "OK",
1412+
"content": {
1413+
"application/json": {
1414+
"schema": {
1415+
"$ref": "#/components/schemas/RunTransactionResponse"
1416+
}
1417+
}
1418+
}
1419+
},
1420+
"500": {
1421+
"description": "Internal Server Error",
1422+
"content": {
1423+
"application/json": {
1424+
"schema": {
1425+
"$ref": "#/components/schemas/ErrorExceptionResponseV1"
1426+
}
1427+
}
1428+
}
12451429
}
12461430
}
12471431
}

packages/cactus-plugin-ledger-connector-fabric/src/main/kotlin/generated/openapi/kotlin-client/.openapi-generator/FILES

+3
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,10 @@ src/main/kotlin/org/openapitools/client/models/GetBlockResponseDecodedV1.kt
5151
src/main/kotlin/org/openapitools/client/models/GetBlockResponseEncodedV1.kt
5252
src/main/kotlin/org/openapitools/client/models/GetBlockResponseV1.kt
5353
src/main/kotlin/org/openapitools/client/models/GetTransactionReceiptResponse.kt
54+
src/main/kotlin/org/openapitools/client/models/RunDelegatedSignTransactionRequest.kt
5455
src/main/kotlin/org/openapitools/client/models/RunTransactionRequest.kt
5556
src/main/kotlin/org/openapitools/client/models/RunTransactionResponse.kt
57+
src/main/kotlin/org/openapitools/client/models/RunTransactionResponseType.kt
5658
src/main/kotlin/org/openapitools/client/models/SSHExecCommandResponse.kt
5759
src/main/kotlin/org/openapitools/client/models/TransactReceiptBlockMetaData.kt
5860
src/main/kotlin/org/openapitools/client/models/TransactReceiptTransactionCreator.kt
@@ -61,6 +63,7 @@ src/main/kotlin/org/openapitools/client/models/VaultTransitKey.kt
6163
src/main/kotlin/org/openapitools/client/models/WatchBlocksCactusErrorResponseV1.kt
6264
src/main/kotlin/org/openapitools/client/models/WatchBlocksCactusTransactionsEventV1.kt
6365
src/main/kotlin/org/openapitools/client/models/WatchBlocksCactusTransactionsResponseV1.kt
66+
src/main/kotlin/org/openapitools/client/models/WatchBlocksDelegatedSignOptionsV1.kt
6467
src/main/kotlin/org/openapitools/client/models/WatchBlocksFilteredResponseV1.kt
6568
src/main/kotlin/org/openapitools/client/models/WatchBlocksFullResponseV1.kt
6669
src/main/kotlin/org/openapitools/client/models/WatchBlocksListenerTypeV1.kt

0 commit comments

Comments
 (0)