Skip to content

Commit 61da528

Browse files
AndreAugusto11petermetz
authored andcommitted
feat(odap-plugin): backup gateway implementation
Signed-off-by: André Augusto <[email protected]>
1 parent 008345b commit 61da528

23 files changed

+1386
-421
lines changed

packages/cactus-plugin-odap-hermes/README.md

+12-30
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ Firstly let us identify the different entities involved in the protocol and what
5252

5353
The sequence diagram of ODAP is pictured below.
5454

55-
![odap-sequence-diagram](https://imgur.com/a/NN62LyL)
55+
![odap-sequence-diagram](https://i.imgur.com/AFNvL9v.png)
5656

5757
### API Endpoints
5858
This plugin uses OpenAPI to generate the API paths.
@@ -83,45 +83,27 @@ Alice and Bob, in blockchains A and B, respectively, want to make a transfer of
8383

8484
## Running the tests
8585

86-
For developers that want to test separate steps/phases of the ODAP protocol, please refer to the following test files (client and server side along with the recovery procedure):
86+
[A test of the entire protocol with manual calls to the methods, i.e. without ledger connectors and Open API.](https://github.com/hyperledger/cactus/blob/2e94ef8d3b34449c7b4d48e37d81245851477a3e/packages/cactus-plugin-odap-hermes/src/test/typescript/integration/odap.test.ts)
8787

88-
https://github.com/hyperledger/cactus/tree/main/packages/cactus-plugin-odap-hermes/src/test/typescript/unit/
88+
[A test of the entire protocol using Open API but with no ledger connectors.](https://github.com/hyperledger/cactus/blob/2e94ef8d3b34449c7b4d48e37d81245851477a3e/packages/cactus-plugin-odap-hermes/src/test/typescript/integration/odap-api-call.test.ts)
8989

90-
A test of the entire protocol with manual calls to the methods, i.e. without ledger connectors and Open API:
90+
[A test of the entire protocol with ledger connectors (Fabric and Besu) and Open API.](https://github.com/hyperledger/cactus/blob/2e94ef8d3b34449c7b4d48e37d81245851477a3e/packages/cactus-plugin-odap-hermes/src/test/typescript/integration/odap-api-call-with-ledger-connector.test.ts)
9191

92-
https://github.com/hyperledger/cactus/tree/main/packages/cactus-plugin-odap-hermes/src/test/typescript/integration/odap-test.ts
92+
[A test with a simulated crash of the client gateway after the transfer initiation flow.](https://github.com/hyperledger/cactus/blob/2e94ef8d3b34449c7b4d48e37d81245851477a3e/packages/cactus-plugin-odap-hermes/src/test/typescript/integration/client-crash-after-transfer-initiation.test.ts)
9393

94-
A test of the entire protocol using Open API but with no ledger connectors:
94+
[A test with a simulated crash of the client gateway after the lock of the asset in the source blockchain (Fabric).](https://github.com/hyperledger/cactus/blob/2e94ef8d3b34449c7b4d48e37d81245851477a3e/packages/cactus-plugin-odap-hermes/src/test/typescript/integration/client-crash-after-lock-asset.test.ts)
9595

96-
https://github.com/hyperledger/cactus/tree/main/packages/cactus-plugin-odap-hermes/src/test/typescript/integration/odap-api-call.test.ts
96+
[A test with a simulated crash of the client gateway after the deletion of the asset in the source blockchain (Fabric).](https://github.com/hyperledger/cactus/blob/2e94ef8d3b34449c7b4d48e37d81245851477a3e/packages/cactus-plugin-odap-hermes/src/test/typescript/integration/client-crash-after-delete-asset.test.ts)
9797

98-
A test of the entire protocol with ledger connectors (Fabric and Besu) and Open API:
98+
[A test with a simulated crash of the server gateway after the creation of the the asset in the recipient blockchain (Besu).](https://github.com/hyperledger/cactus/blob/2e94ef8d3b34449c7b4d48e37d81245851477a3e/packages/cactus-plugin-odap-hermes/src/test/typescript/integration/server-crash-after-create-asset.test.ts)
9999

100-
https://github.com/hyperledger/cactus/blob/main/packages/cactus-plugin-odap-hermes/src/test/typescript/integration/odap/odap-api-call-with-ledger-connector.test.ts
100+
[A test with a simulated crash of the server gateway after the transfer initiation flow.](https://github.com/hyperledger/cactus/blob/2e94ef8d3b34449c7b4d48e37d81245851477a3e/packages/cactus-plugin-odap-hermes/src/test/typescript/integration/server-crash-after-transfer-initiation.test.ts)
101101

102-
A test with a simulated crash of the client gateway after the transfer initiation flow:
102+
[A test with a rollback after a timeout (client crashed).](https://github.com/hyperledger/cactus/blob/2e94ef8d3b34449c7b4d48e37d81245851477a3e/packages/cactus-plugin-odap-hermes/src/test/typescript/integration/odap-rollback.test.ts)
103103

104-
https://github.com/hyperledger/cactus/blob/main/packages/cactus-plugin-odap-hermes/src/test/typescript/integration/odap/client-crash-after-transfer-initiation.test.ts
104+
[A test with a backup gateway resuming the protocol after the client gateway crashed.](https://github.com/hyperledger/cactus/tree/main/packages/cactus-plugin-odap-hermes/src/test/typescript/integration/backup-gateway-after-client-crash.test.ts)
105105

106-
A test with a simulated crash of the client gateway after the lock of the asset in the source blockchain (Fabric):
107-
108-
https://github.com/hyperledger/cactus/blob/main/packages/cactus-plugin-odap-hermes/src/test/typescript/integration/odap/client-crash-after-lock-asset.test.ts
109-
110-
A test with a simulated crash of the client gateway after the deletion of the asset in the source blockchain (Fabric):
111-
112-
https://github.com/hyperledger/cactus/blob/main/packages/cactus-plugin-odap-hermes/src/test/typescript/integration/odap/client-crash-after-delete-asset.test.ts
113-
114-
A test with a simulated crash of the server gateway after the creation of the the asset in the recipient blockchain (Besu):
115-
116-
https://github.com/hyperledger/cactus/blob/main/packages/cactus-plugin-odap-hermes/src/test/typescript/integration/odap/server-crash-after-create-asset.test.ts
117-
118-
A test with a simulated crash of the server gateway after the transfer initiation flow:
119-
120-
https://github.com/hyperledger/cactus/blob/main/packages/cactus-plugin-odap-hermes/src/test/typescript/integration/odap/server-crash-after-transfer-initiation.test.ts
121-
122-
A test with a rollback after a timeout (client crashed):
123-
124-
https://github.com/hyperledger/cactus/blob/main/packages/cactus-plugin-odap-hermes/src/test/typescript/integration/odap/odap-rollback.test.ts
106+
For developers that want to test separate steps/phases of the ODAP protocol, please refer to [these](https://github.com/hyperledger/cactus/blob/2e94ef8d3b34449c7b4d48e37d81245851477a3e/packages/cactus-plugin-odap-hermes/src/test/typescript/unit/) test files (client and server side along with the recovery procedure).
125107

126108
## Usage
127109

packages/cactus-plugin-odap-hermes/src/main/json/openapi.json

+39-2
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,18 @@
195195
"assetProfile": {
196196
"$ref":"#/components/schemas/AssetProfile"
197197
},
198+
"allowedSourceBackupGateways": {
199+
"type": "array",
200+
"items": {
201+
"type": "string"
202+
}
203+
},
204+
"allowedRecipientBackupGateways": {
205+
"type": "array",
206+
"items": {
207+
"type": "string"
208+
}
209+
},
198210
"sourceBasePath": {
199211
"type":"string"
200212
},
@@ -460,6 +472,12 @@
460472
},
461473
"maxTimeout": {
462474
"type": "number"
475+
},
476+
"backupGatewaysAllowed": {
477+
"type":"array",
478+
"items":{
479+
"type":"string"
480+
}
463481
}
464482
},
465483
"required": [
@@ -478,7 +496,8 @@
478496
"sourceBasePath",
479497
"recipientBasePath",
480498
"maxRetries",
481-
"maxTimeout"
499+
"maxTimeout",
500+
"backupGatewaysAllowed"
482501
]
483502
},
484503
"TransferInitializationV1Response":{
@@ -518,6 +537,12 @@
518537
},
519538
"signature":{
520539
"type":"string"
540+
},
541+
"backupGatewaysAllowed": {
542+
"type":"array",
543+
"items":{
544+
"type":"string"
545+
}
521546
}
522547
},
523548
"required": [
@@ -528,7 +553,8 @@
528553
"sessionID",
529554
"serverIdentityPubkey",
530555
"sequenceNumber",
531-
"signature"
556+
"signature",
557+
"backupGatewaysAllowed"
532558
]
533559
},
534560
"TransferCommenceV1Request":{
@@ -1057,6 +1083,15 @@
10571083
"lastLogEntryTimestamp": {
10581084
"type": "string"
10591085
},
1086+
"isBackup": {
1087+
"type": "boolean"
1088+
},
1089+
"newBasePath": {
1090+
"type": "string"
1091+
},
1092+
"newGatewayPubKey": {
1093+
"type": "string"
1094+
},
10601095
"signature": {
10611096
"type": "string"
10621097
}
@@ -1066,6 +1101,8 @@
10661101
"odapPhase",
10671102
"sequenceNumber",
10681103
"lastLogEntryHash",
1104+
"isBackup",
1105+
"newBasePath",
10691106
"signature"
10701107
]
10711108
},

packages/cactus-plugin-odap-hermes/src/main/typescript/gateway/client/transfer-initialization.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,8 @@ export async function sendTransferInitializationRequest(
3535
sessionData.lastSequenceNumber == undefined ||
3636
sessionData.sourceGatewayDltSystem == undefined ||
3737
sessionData.recipientGatewayPubkey == undefined ||
38-
sessionData.recipientGatewayDltSystem == undefined
38+
sessionData.recipientGatewayDltSystem == undefined ||
39+
sessionData.allowedSourceBackupGateways == undefined
3940
) {
4041
throw new Error(`${fnTag}, session data is not correctly initialized`);
4142
}
@@ -71,6 +72,7 @@ export async function sendTransferInitializationRequest(
7172
// permissions
7273
maxRetries: sessionData.maxRetries,
7374
maxTimeout: sessionData.maxTimeout,
75+
backupGatewaysAllowed: sessionData.allowedSourceBackupGateways,
7476
};
7577

7678
const messageSignature = PluginOdapGateway.bufArray2HexStr(
@@ -184,6 +186,7 @@ function storeSessionData(
184186

185187
sessionData.serverSignatureInitializationResponseMessage = response.signature;
186188

189+
sessionData.allowedRecipientBackupGateways = response.backupGatewaysAllowed;
187190
sessionData.fabricAssetSize = "1";
188191

189192
sessionData.step = 3;

packages/cactus-plugin-odap-hermes/src/main/typescript/gateway/plugin-odap-gateway.ts

+21-1
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,7 @@ export interface IPluginOdapGatewayConstructorOptions {
168168
dltIDs: string[];
169169
instanceId: string;
170170
keyPair?: IOdapGatewayKeyPairs;
171+
backupGatewaysAllowed?: string[];
171172

172173
ipfsPath?: string;
173174
fabricPath?: string;
@@ -216,6 +217,8 @@ export class PluginOdapGateway implements ICactusPlugin, IPluginWebService {
216217
//map[]object, object refer to a state
217218
//of a specific comminications
218219
public supportedDltIDs: string[];
220+
public backupGatewaysAllowed: string[];
221+
219222
private odapSigner: JsObjectSigner;
220223
public fabricAssetLocked: boolean;
221224
public fabricAssetDeleted: boolean;
@@ -245,6 +248,8 @@ export class PluginOdapGateway implements ICactusPlugin, IPluginWebService {
245248
this.name = options.name;
246249
this.supportedDltIDs = options.dltIDs;
247250
this.sessions = new Map();
251+
252+
this.backupGatewaysAllowed = options.backupGatewaysAllowed || [];
248253
const keyPairs = options.keyPair
249254
? options.keyPair
250255
: Secp256k1Keys.generateKeyPairsBuffer();
@@ -564,8 +569,19 @@ export class PluginOdapGateway implements ICactusPlugin, IPluginWebService {
564569
const sessionData: SessionData = JSON.parse(log.data);
565570

566571
sessionData.lastLogEntryTimestamp = log.timestamp;
572+
573+
let amIBackup = false;
574+
if (
575+
this.pubKey != sessionData.sourceGatewayPubkey &&
576+
this.pubKey != sessionData.recipientGatewayPubkey
577+
) {
578+
// this is a backup gateway -> for now we assume backup gateways only on the client side
579+
sessionData.sourceGatewayPubkey = this.pubKey;
580+
amIBackup = true;
581+
}
582+
567583
this.sessions.set(sessionID, sessionData);
568-
if (remote) await sendRecoverMessage(sessionID, this, true);
584+
if (remote) await sendRecoverMessage(sessionID, this, amIBackup, true);
569585
}
570586
}
571587

@@ -1117,9 +1133,13 @@ export class PluginOdapGateway implements ICactusPlugin, IPluginWebService {
11171133
sessionData.step = 1;
11181134
sessionData.version = request.version;
11191135
sessionData.lastSequenceNumber = randomInt(4294967295);
1136+
11201137
sessionData.sourceBasePath = request.clientGatewayConfiguration.apiHost;
11211138
sessionData.recipientBasePath = request.serverGatewayConfiguration.apiHost;
11221139

1140+
sessionData.allowedSourceBackupGateways = this.backupGatewaysAllowed;
1141+
sessionData.allowedRecipientBackupGateways = [];
1142+
11231143
sessionData.payloadProfile = request.payloadProfile;
11241144
sessionData.loggingProfile = request.loggingProfile;
11251145
sessionData.accessControlProfile = request.accessControlProfile;

packages/cactus-plugin-odap-hermes/src/main/typescript/gateway/recovery/recover-update.ts

+11-1
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,17 @@ export async function checkValidRecoverUpdateMessage(
127127

128128
if (parseInt(recLog.timestamp) > parseInt(maxTimestamp)) {
129129
maxTimestamp = recLog.timestamp;
130-
odap.sessions.set(sessionID, JSON.parse(recLog.data));
130+
131+
const data = JSON.parse(recLog.data);
132+
133+
// don't override new gateway public keys in case of being a backup gateway
134+
if (odap.isClientGateway(sessionID)) {
135+
data.sourceGatewayPubkey = odap.pubKey;
136+
} else {
137+
data.recipientGatewayPubkey = odap.pubKey;
138+
}
139+
140+
odap.sessions.set(sessionID, data);
131141
}
132142
}
133143

packages/cactus-plugin-odap-hermes/src/main/typescript/gateway/recovery/recover.ts

+45-3
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ const log = LoggerProvider.getOrCreate({
1010
export async function sendRecoverMessage(
1111
sessionID: string,
1212
odap: PluginOdapGateway,
13+
backup: boolean,
1314
remote: boolean,
1415
): Promise<void | RecoverV1Message> {
1516
const fnTag = `${odap.className}#sendRecoverMessage()`;
@@ -33,6 +34,9 @@ export async function sendRecoverMessage(
3334
odapPhase: "sessionData.odapPhase",
3435
sequenceNumber: sessionData.lastSequenceNumber,
3536
lastLogEntryTimestamp: sessionData.lastLogEntryTimestamp,
37+
isBackup: backup,
38+
newBasePath: "",
39+
newGatewayPubKey: sessionData.sourceGatewayPubkey,
3640
signature: "",
3741
};
3842

@@ -71,9 +75,45 @@ export async function checkValidRecoverMessage(
7175
throw new Error(`${fnTag}, session data is undefined`);
7276
}
7377

74-
const pubKey = odap.isClientGateway(response.sessionID)
75-
? sessionData.recipientGatewayPubkey
76-
: sessionData.sourceGatewayPubkey;
78+
let pubKey = undefined;
79+
80+
if (odap.isClientGateway(response.sessionID)) {
81+
if (
82+
response.isBackup &&
83+
sessionData.recipientGatewayPubkey != response.newGatewayPubKey
84+
) {
85+
// this is a backup gateway
86+
sessionData.recipientGatewayPubkey = response.newGatewayPubKey;
87+
88+
if (
89+
!sessionData.recipientGatewayPubkey ||
90+
!sessionData.allowedSourceBackupGateways?.includes(
91+
sessionData.recipientGatewayPubkey,
92+
)
93+
) {
94+
throw new Error(`${fnTag}, backup gateway not allowed`);
95+
}
96+
}
97+
pubKey = sessionData.recipientGatewayPubkey;
98+
} else {
99+
if (
100+
response.isBackup &&
101+
sessionData.sourceGatewayPubkey != response.newGatewayPubKey
102+
) {
103+
// this is a backup gateway
104+
sessionData.sourceGatewayPubkey = response.newGatewayPubKey;
105+
106+
if (
107+
!sessionData.sourceGatewayPubkey ||
108+
!sessionData.allowedSourceBackupGateways?.includes(
109+
sessionData.sourceGatewayPubkey,
110+
)
111+
) {
112+
throw new Error(`${fnTag}, backup gateway not allowed`);
113+
}
114+
}
115+
pubKey = sessionData.sourceGatewayPubkey;
116+
}
77117

78118
if (pubKey == undefined) {
79119
throw new Error(`${fnTag}, session data is undefined`);
@@ -95,5 +135,7 @@ export async function checkValidRecoverMessage(
95135

96136
sessionData.lastLogEntryTimestamp = response.lastLogEntryTimestamp;
97137

138+
odap.sessions.set(sessionID, sessionData);
139+
98140
log.info(`RecoverMessage passed all checks.`);
99141
}

packages/cactus-plugin-odap-hermes/src/main/typescript/gateway/server/transfer-initialization.ts

+5
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ export async function sendTransferInitializationResponse(
4444
serverIdentityPubkey: odap.pubKey,
4545
sequenceNumber: sessionData.lastSequenceNumber,
4646
signature: "",
47+
backupGatewaysAllowed: odap.backupGatewaysAllowed,
4748
};
4849

4950
transferInitializationResponse.signature = PluginOdapGateway.bufArray2HexStr(
@@ -158,6 +159,10 @@ async function storeSessionData(
158159
sessionData.version = request.version;
159160
sessionData.maxRetries = request.maxRetries;
160161
sessionData.maxTimeout = request.maxTimeout;
162+
163+
sessionData.allowedSourceBackupGateways = request.backupGatewaysAllowed;
164+
sessionData.allowedRecipientBackupGateways = odap.backupGatewaysAllowed;
165+
161166
sessionData.sourceBasePath = request.sourceGatewayPath;
162167
sessionData.recipientBasePath = request.recipientBasePath;
163168
sessionData.lastSequenceNumber = request.sequenceNumber;

0 commit comments

Comments
 (0)