Skip to content

Commit 865ec2f

Browse files
outSHpetermetz
authored andcommitted
feat(corda4): implement monitoring of state changes
Add new endpoints to corda kotlin server used to start and stop monitoring, and get/clean state changes from its internal buffer. Monitoring session is started separately for each client, each client can monitor many corda states. Clients that are not active for specified amount of time are removed. Implementation uses transaction queues that are polled periodically by the client, instead of socketio-based solution because we want to minimize probability of losing a transaction from corda. This can happen when WS connection is disconnected, for instance. With transaction queue on the connector, and explicit get/remove calls from the client, we have greater control over what transactions are delivered to the client code. Also, setting up socketio on spring boot seems overly complicated and would obscurificate the implementation. Add reactive watchBlocksV1 that polls kotlin server and reports new transactions asynchronously. Add CordaApiClient support to VerifierClient. Add functional test for both monitoring interfaces. Update corda setup in corda-all-in-one to newer version. Closes: #1610 Signed-off-by: Michal Bajer <[email protected]>
1 parent dda3f00 commit 865ec2f

File tree

28 files changed

+2706
-58
lines changed

28 files changed

+2706
-58
lines changed

packages/cactus-core-api/src/main/typescript/plugin/ledger-connector/i-socket-api-client.ts

+4
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,8 @@ export interface ISocketApiClient<BlockType> {
2222
watchBlocksV1?(
2323
monitorOptions?: Record<string, unknown>,
2424
): Observable<BlockType>;
25+
26+
watchBlocksAsyncV1?(
27+
monitorOptions?: Record<string, unknown>,
28+
): Promise<Observable<BlockType>>;
2529
}

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

+31
Original file line numberDiff line numberDiff line change
@@ -293,6 +293,37 @@ const res = await apiClient.invokeContractV1({
293293
});
294294
```
295295

296+
### Transaction Monitoring
297+
- There are two interfaces to monitor changes of vault states - reactive `watchBlocksV1` method, and low-level HTTP API calls.
298+
- Note: The monitoring APIs are implemented only on kotlin-server connector (`main-server`), not typescript connector!
299+
- For usage examples review the functional test file: `packages/cactus-plugin-ledger-connector-corda/src/test/typescript/integration/monitor-transactions-v4.8.test.ts`
300+
- Because transactions read from corda are stored on the connector, they will be lost if connector is closed/killed before transaction were read by the clients.
301+
- Each client has own set of state monitors that are managed independently. After starting the monitoring, each new transaction is queued on the connector until read and explicitly cleared by `watchBlocksV1` or direct HTTP API call.
302+
- Client monitors can be periodically removed by the connector, if there was no action from the client for specified amount of time.
303+
- Client expiration delay can be configured with `cactus.sessionExpireMinutes` option. It default to 30 minutes.
304+
- Each transaction has own index assigned by the corda connector. Index is unique for each client monitoring session. For instance:
305+
- Stopping monitoring for given state will reset the transaction index counter for given client. After restart, it will report first transaction with index 0.
306+
- Each client can see tha same transaction with different index.
307+
- Index can be used to determine the transaction order for given client session.
308+
309+
#### watchBlocksV1
310+
- `watchBlocksV1(options: watchBlocksV1Options): Observable<CordaBlock>`
311+
- Reactive (RxJS) interface to observe state changes.
312+
- Internally, it uses polling of low-level HTTP APIs.
313+
- Watching block should return each block at least once, no blocks should be missed after startMonitor has started. The only case when transaction is lost is when connector we were connected to died.
314+
- Transactions can be duplicated in case internal `ClearMonitorTransactionsV1` call was not successful (for instance, because of connection problems).
315+
- Options:
316+
- `stateFullClassName: string`: state to monitor.
317+
- `pollRate?: number`: how often poll the kotlin server for changes (default 5 seconds).
318+
319+
#### Low-level HTTP API
320+
- These should not be used when watchBlocks API is sufficient.
321+
- Consists of the following methods:
322+
- `startMonitorV1`: Start monitoring for specified state changes. All changes after calling this function will be stored in internal kotlin-server buffer, ready to be read by calls to `GetMonitorTransactionsV1`. Transactions occuring before the call to startMonitorV1 will not be reported.
323+
- `GetMonitorTransactionsV1`: Read all transactions for given state name still remaining in internal buffer.
324+
- `ClearMonitorTransactionsV1`: Remove transaction for given state name with specified index number from internal buffer. Should be used to acknowledge receiving specified transactions in user code, so that transactions are not reported multiple times.
325+
- `stopMonitorV1`: Don't watch for transactions changes anymore, remove any transactions that were not read until now.
326+
296327
### Custom Configuration via Env Variables
297328

298329
```json

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

+1
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@
6363
"joi": "17.4.2",
6464
"node-ssh": "12.0.0",
6565
"prom-client": "13.2.0",
66+
"rxjs": "7.3.0",
6667
"temp": "0.9.4",
6768
"typescript-optional": "2.0.1"
6869
},

packages/cactus-plugin-ledger-connector-corda/src/main-server/kotlin/gen/kotlin-spring/.openapi-generator/FILES

+9
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ settings.gradle
44
src/main/kotlin/org/hyperledger/cactus/plugin/ledger/connector/corda/server/api/ApiPluginLedgerConnectorCorda.kt
55
src/main/kotlin/org/hyperledger/cactus/plugin/ledger/connector/corda/server/api/ApiPluginLedgerConnectorCordaService.kt
66
src/main/kotlin/org/hyperledger/cactus/plugin/ledger/connector/corda/server/api/ApiUtil.kt
7+
src/main/kotlin/org/hyperledger/cactus/plugin/ledger/connector/corda/server/model/ClearMonitorTransactionsV1Request.kt
8+
src/main/kotlin/org/hyperledger/cactus/plugin/ledger/connector/corda/server/model/ClearMonitorTransactionsV1Response.kt
79
src/main/kotlin/org/hyperledger/cactus/plugin/ledger/connector/corda/server/model/CordaNodeSshCredentials.kt
810
src/main/kotlin/org/hyperledger/cactus/plugin/ledger/connector/corda/server/model/CordaRpcCredentials.kt
911
src/main/kotlin/org/hyperledger/cactus/plugin/ledger/connector/corda/server/model/CordaX500Name.kt
@@ -15,6 +17,9 @@ src/main/kotlin/org/hyperledger/cactus/plugin/ledger/connector/corda/server/mode
1517
src/main/kotlin/org/hyperledger/cactus/plugin/ledger/connector/corda/server/model/DiagnoseNodeV1Request.kt
1618
src/main/kotlin/org/hyperledger/cactus/plugin/ledger/connector/corda/server/model/DiagnoseNodeV1Response.kt
1719
src/main/kotlin/org/hyperledger/cactus/plugin/ledger/connector/corda/server/model/FlowInvocationType.kt
20+
src/main/kotlin/org/hyperledger/cactus/plugin/ledger/connector/corda/server/model/GetMonitorTransactionsV1Request.kt
21+
src/main/kotlin/org/hyperledger/cactus/plugin/ledger/connector/corda/server/model/GetMonitorTransactionsV1Response.kt
22+
src/main/kotlin/org/hyperledger/cactus/plugin/ledger/connector/corda/server/model/GetMonitorTransactionsV1ResponseTx.kt
1823
src/main/kotlin/org/hyperledger/cactus/plugin/ledger/connector/corda/server/model/InvokeContractV1Request.kt
1924
src/main/kotlin/org/hyperledger/cactus/plugin/ledger/connector/corda/server/model/InvokeContractV1Response.kt
2025
src/main/kotlin/org/hyperledger/cactus/plugin/ledger/connector/corda/server/model/JarFile.kt
@@ -29,5 +34,9 @@ src/main/kotlin/org/hyperledger/cactus/plugin/ledger/connector/corda/server/mode
2934
src/main/kotlin/org/hyperledger/cactus/plugin/ledger/connector/corda/server/model/Party.kt
3035
src/main/kotlin/org/hyperledger/cactus/plugin/ledger/connector/corda/server/model/PublicKey.kt
3136
src/main/kotlin/org/hyperledger/cactus/plugin/ledger/connector/corda/server/model/SHA256.kt
37+
src/main/kotlin/org/hyperledger/cactus/plugin/ledger/connector/corda/server/model/StartMonitorV1Request.kt
38+
src/main/kotlin/org/hyperledger/cactus/plugin/ledger/connector/corda/server/model/StartMonitorV1Response.kt
39+
src/main/kotlin/org/hyperledger/cactus/plugin/ledger/connector/corda/server/model/StopMonitorV1Request.kt
40+
src/main/kotlin/org/hyperledger/cactus/plugin/ledger/connector/corda/server/model/StopMonitorV1Response.kt
3241
src/main/kotlin/org/hyperledger/cactus/plugin/ledger/connector/corda/server/model/X500Principal.kt
3342
src/main/resources/application.yaml

packages/cactus-plugin-ledger-connector-corda/src/main-server/kotlin/gen/kotlin-spring/src/main/kotlin/org/hyperledger/cactus/plugin/ledger/connector/corda/server/Application.kt

+2
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,12 @@ import org.springframework.context.annotation.ComponentScan
1010
import org.springframework.boot.autoconfigure.SpringBootApplication
1111
import org.springframework.context.annotation.Bean
1212
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter
13+
import org.springframework.scheduling.annotation.EnableScheduling
1314

1415

1516
@SpringBootApplication
1617
@ComponentScan(basePackages = ["org.hyperledger.cactus.plugin.ledger.connector.corda.server", "org.hyperledger.cactus.plugin.ledger.connector.corda.server.api", "org.hyperledger.cactus.plugin.ledger.connector.corda.server.model"])
18+
@EnableScheduling
1719
class Application {
1820
/**
1921
* Spring Bean that binds a Corda Jackson object-mapper to HTTP message types used in Spring.

packages/cactus-plugin-ledger-connector-corda/src/main-server/kotlin/gen/kotlin-spring/src/main/kotlin/org/hyperledger/cactus/plugin/ledger/connector/corda/server/api/ApiPluginLedgerConnectorCorda.kt

+52
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,23 @@
11
package org.hyperledger.cactus.plugin.ledger.connector.corda.server.api
22

3+
import org.hyperledger.cactus.plugin.ledger.connector.corda.server.model.ClearMonitorTransactionsV1Request
4+
import org.hyperledger.cactus.plugin.ledger.connector.corda.server.model.ClearMonitorTransactionsV1Response
35
import org.hyperledger.cactus.plugin.ledger.connector.corda.server.model.DeployContractJarsBadRequestV1Response
46
import org.hyperledger.cactus.plugin.ledger.connector.corda.server.model.DeployContractJarsSuccessV1Response
57
import org.hyperledger.cactus.plugin.ledger.connector.corda.server.model.DeployContractJarsV1Request
68
import org.hyperledger.cactus.plugin.ledger.connector.corda.server.model.DiagnoseNodeV1Request
79
import org.hyperledger.cactus.plugin.ledger.connector.corda.server.model.DiagnoseNodeV1Response
10+
import org.hyperledger.cactus.plugin.ledger.connector.corda.server.model.GetMonitorTransactionsV1Request
11+
import org.hyperledger.cactus.plugin.ledger.connector.corda.server.model.GetMonitorTransactionsV1Response
812
import org.hyperledger.cactus.plugin.ledger.connector.corda.server.model.InvokeContractV1Request
913
import org.hyperledger.cactus.plugin.ledger.connector.corda.server.model.InvokeContractV1Response
1014
import org.hyperledger.cactus.plugin.ledger.connector.corda.server.model.ListFlowsV1Request
1115
import org.hyperledger.cactus.plugin.ledger.connector.corda.server.model.ListFlowsV1Response
1216
import org.hyperledger.cactus.plugin.ledger.connector.corda.server.model.NodeInfo
17+
import org.hyperledger.cactus.plugin.ledger.connector.corda.server.model.StartMonitorV1Request
18+
import org.hyperledger.cactus.plugin.ledger.connector.corda.server.model.StartMonitorV1Response
19+
import org.hyperledger.cactus.plugin.ledger.connector.corda.server.model.StopMonitorV1Request
20+
import org.hyperledger.cactus.plugin.ledger.connector.corda.server.model.StopMonitorV1Response
1321
import org.springframework.http.HttpStatus
1422
import org.springframework.http.MediaType
1523
import org.springframework.http.ResponseEntity
@@ -37,6 +45,17 @@ import kotlin.collections.Map
3745
class ApiPluginLedgerConnectorCordaController(@Autowired(required = true) val service: ApiPluginLedgerConnectorCordaService) {
3846

3947

48+
@DeleteMapping(
49+
value = ["/api/v1/plugins/@hyperledger/cactus-plugin-ledger-connector-corda/clear-monitor-transactions"],
50+
produces = ["application/json"],
51+
consumes = ["application/json"]
52+
)
53+
fun clearMonitorTransactionsV1( @Valid @RequestBody(required = false) clearMonitorTransactionsV1Request: ClearMonitorTransactionsV1Request?
54+
): ResponseEntity<ClearMonitorTransactionsV1Response> {
55+
return ResponseEntity(service.clearMonitorTransactionsV1(clearMonitorTransactionsV1Request), HttpStatus.valueOf(200))
56+
}
57+
58+
4059
@PostMapping(
4160
value = ["/api/v1/plugins/@hyperledger/cactus-plugin-ledger-connector-corda/deploy-contract-jars"],
4261
produces = ["application/json"],
@@ -59,6 +78,17 @@ class ApiPluginLedgerConnectorCordaController(@Autowired(required = true) val se
5978
}
6079

6180

81+
@GetMapping(
82+
value = ["/api/v1/plugins/@hyperledger/cactus-plugin-ledger-connector-corda/get-monitor-transactions"],
83+
produces = ["application/json"],
84+
consumes = ["application/json"]
85+
)
86+
fun getMonitorTransactionsV1( @Valid @RequestBody(required = false) getMonitorTransactionsV1Request: GetMonitorTransactionsV1Request?
87+
): ResponseEntity<GetMonitorTransactionsV1Response> {
88+
return ResponseEntity(service.getMonitorTransactionsV1(getMonitorTransactionsV1Request), HttpStatus.valueOf(200))
89+
}
90+
91+
6292
@GetMapping(
6393
value = ["/api/v1/plugins/@hyperledger/cactus-plugin-ledger-connector-corda/get-prometheus-exporter-metrics"],
6494
produces = ["text/plain"]
@@ -99,4 +129,26 @@ class ApiPluginLedgerConnectorCordaController(@Autowired(required = true) val se
99129
): ResponseEntity<List<NodeInfo>> {
100130
return ResponseEntity(service.networkMapV1(body), HttpStatus.valueOf(200))
101131
}
132+
133+
134+
@PostMapping(
135+
value = ["/api/v1/plugins/@hyperledger/cactus-plugin-ledger-connector-corda/start-monitor"],
136+
produces = ["application/json"],
137+
consumes = ["application/json"]
138+
)
139+
fun startMonitorV1( @Valid @RequestBody(required = false) startMonitorV1Request: StartMonitorV1Request?
140+
): ResponseEntity<StartMonitorV1Response> {
141+
return ResponseEntity(service.startMonitorV1(startMonitorV1Request), HttpStatus.valueOf(200))
142+
}
143+
144+
145+
@DeleteMapping(
146+
value = ["/api/v1/plugins/@hyperledger/cactus-plugin-ledger-connector-corda/stop-monitor"],
147+
produces = ["application/json"],
148+
consumes = ["application/json"]
149+
)
150+
fun stopMonitorV1( @Valid @RequestBody(required = false) stopMonitorV1Request: StopMonitorV1Request?
151+
): ResponseEntity<StopMonitorV1Response> {
152+
return ResponseEntity(service.stopMonitorV1(stopMonitorV1Request), HttpStatus.valueOf(200))
153+
}
102154
}
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,43 @@
11
package org.hyperledger.cactus.plugin.ledger.connector.corda.server.api
22

3+
import org.hyperledger.cactus.plugin.ledger.connector.corda.server.model.ClearMonitorTransactionsV1Request
4+
import org.hyperledger.cactus.plugin.ledger.connector.corda.server.model.ClearMonitorTransactionsV1Response
35
import org.hyperledger.cactus.plugin.ledger.connector.corda.server.model.DeployContractJarsBadRequestV1Response
46
import org.hyperledger.cactus.plugin.ledger.connector.corda.server.model.DeployContractJarsSuccessV1Response
57
import org.hyperledger.cactus.plugin.ledger.connector.corda.server.model.DeployContractJarsV1Request
68
import org.hyperledger.cactus.plugin.ledger.connector.corda.server.model.DiagnoseNodeV1Request
79
import org.hyperledger.cactus.plugin.ledger.connector.corda.server.model.DiagnoseNodeV1Response
10+
import org.hyperledger.cactus.plugin.ledger.connector.corda.server.model.GetMonitorTransactionsV1Request
11+
import org.hyperledger.cactus.plugin.ledger.connector.corda.server.model.GetMonitorTransactionsV1Response
812
import org.hyperledger.cactus.plugin.ledger.connector.corda.server.model.InvokeContractV1Request
913
import org.hyperledger.cactus.plugin.ledger.connector.corda.server.model.InvokeContractV1Response
1014
import org.hyperledger.cactus.plugin.ledger.connector.corda.server.model.ListFlowsV1Request
1115
import org.hyperledger.cactus.plugin.ledger.connector.corda.server.model.ListFlowsV1Response
1216
import org.hyperledger.cactus.plugin.ledger.connector.corda.server.model.NodeInfo
17+
import org.hyperledger.cactus.plugin.ledger.connector.corda.server.model.StartMonitorV1Request
18+
import org.hyperledger.cactus.plugin.ledger.connector.corda.server.model.StartMonitorV1Response
19+
import org.hyperledger.cactus.plugin.ledger.connector.corda.server.model.StopMonitorV1Request
20+
import org.hyperledger.cactus.plugin.ledger.connector.corda.server.model.StopMonitorV1Response
1321

1422
interface ApiPluginLedgerConnectorCordaService {
1523

24+
fun clearMonitorTransactionsV1(clearMonitorTransactionsV1Request: ClearMonitorTransactionsV1Request?): ClearMonitorTransactionsV1Response
25+
1626
fun deployContractJarsV1(deployContractJarsV1Request: DeployContractJarsV1Request?): DeployContractJarsSuccessV1Response
1727

1828
fun diagnoseNodeV1(diagnoseNodeV1Request: DiagnoseNodeV1Request?): DiagnoseNodeV1Response
1929

30+
fun getMonitorTransactionsV1(getMonitorTransactionsV1Request: GetMonitorTransactionsV1Request?): GetMonitorTransactionsV1Response
31+
2032
fun getPrometheusMetricsV1(): kotlin.String
2133

2234
fun invokeContractV1(invokeContractV1Request: InvokeContractV1Request?): InvokeContractV1Response
2335

2436
fun listFlowsV1(listFlowsV1Request: ListFlowsV1Request?): ListFlowsV1Response
2537

2638
fun networkMapV1(body: kotlin.Any?): List<NodeInfo>
39+
40+
fun startMonitorV1(startMonitorV1Request: StartMonitorV1Request?): StartMonitorV1Response
41+
42+
fun stopMonitorV1(stopMonitorV1Request: StopMonitorV1Request?): StopMonitorV1Response
2743
}

0 commit comments

Comments
 (0)