From 5fc76eacdb1bb2335b05627d26f5af5aec2dbac9 Mon Sep 17 00:00:00 2001 From: asmataamallah25 Date: Fri, 6 Dec 2024 14:02:23 +0100 Subject: [PATCH 01/19] docs: Add Canis Major workflow test commands --- validator/Tests.md | 114 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 114 insertions(+) create mode 100644 validator/Tests.md diff --git a/validator/Tests.md b/validator/Tests.md new file mode 100644 index 0000000..0624291 --- /dev/null +++ b/validator/Tests.md @@ -0,0 +1,114 @@ +# Canis Major test + +Canis Major serves as a blockchain adaptor within the FIWARE ecosystem, providing secure data persistence across blockchain networks and the Context Broker. The workflow is straightforward: clients submit transactions containing payload data and wallet credentials, which undergo authentication through Wilma PEP Proxy and KeyRock Identity Management. Once validated, the data is stored in the Orion Context Broker and simultaneously processed into a Merkle-Tree structure for blockchain integration. The system then signs the transaction using the provided wallet credentials and submits it to an Oketh-compatible blockchain. Let's explore the practical implementation through a series of test commands. + +After r unning the integartion tests, create an entity by sending this POST request to Canis Major: + +```bash +curl --location 'http://46.17.108.147:4000/ngsi-ld/v1/entities/' \ +--header 'Link: ; rel="http://www.w3.org/ns/json-ld#context"; type="application/ld+json"' \ +--header 'Wallet-Type: vault' \ +--header 'Wallet-Token: vault-plaintext-root-token' \ +--header 'Wallet-Address: http://vault:8200/v1/ethereum/accounts/mira' \ +--header 'Content-Type: application/json' \ +--header 'Accept: application/json' \ +--header 'NGSILD-TENANT: orion' \ +--data '{ + "id": "urn:ngsi-ld:Building:farm002", + "type": "Building", + "category": { + "type": "Property", + "value": ["farm"] + }, + "address": { + "type": "Property", + "value": { + "streetAddress": "Großer Stern 1", + "addressRegion": "Berlin", + "addressLocality": "Tiergarten", + "postalCode": "10557" + } + } +}' +``` + +To retrieve the available entity types in the context broker run the following command: +```bash +curl -L 'http://46.17.108.147:1026/ngsi-ld/v1/types' \ +-H 'Link: ; rel="http://www.w3.org/ns/json-ld#context"; type="application/ld+json"' \ +-H 'NGSILD-Tenant: orion' +``` +The command returns the following response, demonstrating the available entity types in the Context Broker: +```bash +{ + "@context":"https://raw.githubusercontent.com/smart-data-models/dataModel.DistributedLedgerTech/master/context.jsonld","id":"urn:ngsi-ld:EntityTypeList:d77ccfa0-b3cd-11ef-ae1b-0242ac120005","type":"EntityTypeList","typeList":["DLTtxReceipt"] +}% +``` +The @context is using a definition on smart data models for a DLTtxReceipt and the NGSILD-Tenant is defined in the start-up of the tests. +To verify which entities are created in the context broker use the following command: + +To verify that the created entities is correctly saved in the context broker having the type DLTtxReceipt, use the following command: + +```bash +curl -L 'http://46.17.108.147:1026/ngsi-ld/v1/entities/?type=DLTtxReceipt' \ +-H 'Link: ; rel="http://www.w3.org/ns/json-ld#context"; type="application/ld+json"' \ +-H 'NGSILD-Tenant: orion' +``` + +To ask Canis Major about receipts for a specific building use the following command: + +```bash +curl -L 'http://46.17.108.147:4000/entity/urn:ngsi-ld:Building:farm002' \ +-H 'Accept: application/json' +``` + +The output will be similar to following response: + +```bash +{ + "entityId":"urn:ngsi-ld:Building:farm002","txDetails":[ + { + "blockHash":"0x610f07a4b5a87ca71273791fe5d12952fca393a6e3664489f45329bebdb396c3","blockNumber":186,"blockNumberRaw":"0xba","cumulativeGasUsed":23866,"cumulativeGasUsedRaw":"0x5d3a","from":"0x34e5b3f990e55d0651b35c817bafb89d2877cb95","gasUsed":23866,"gasUsedRaw":"0x5d3a","logs":[ + { + "address":"0x476059cd57800db8eb88f67c2aa38a6fcf8251e0","blockHash":"0x610f07a4b5a87ca71273791fe5d12952fca393a6e3664489f45329bebdb396c3","blockNumber":186,"blockNumberRaw":"0xba","data":"0x","logIndex":0,"logIndexRaw":"0x0","removed":false,"topics":["0xa3865c00e01495fc2b86502cae36a4edb139f748682e7d80725a3d6571a482fa","0x9aeb7ab140e1449806e11e52ca85dacb6a028ae5feb8d431e7d927e7971e4d2d","0xb1a82faaa61a7bfbc90c42129b2049d408f20b8f43ae99e8b6aa6cc7e7b0b944"],"transactionHash":"0x8456f94030cc841bc667d76ce563b47e4920a27884bdb9b8fc5e4e9cc2df1913","transactionIndex":0,"transactionIndexRaw":"0x0","type":"mined"}],"logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000008000200000000000000000000010000000000004000000000000000000000000000110000000000000004000000000000000000000000000000000000000000000020000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000020000000000000000000000000000000000000000000000000000000000000000000000000","status":"0x1","statusOK":true,"to":"0x476059cd57800db8eb88f67c2aa38a6fcf8251e0","transactionHash":"0x8456f94030cc841bc667d76ce563b47e4920a27884bdb9b8fc5e4e9cc2df1913","transactionIndexRaw":"0x0"},{ + "blockHash":"0x86082426228ee23ea8927607c7f731194ebc1dc7ca69a6321ff46e91b0fdbe2a","blockNumber":190,"blockNumberRaw":"0xbe","cumulativeGasUsed":23866,"cumulativeGasUsedRaw":"0x5d3a","from":"0x34e5b3f990e55d0651b35c817bafb89d2877cb95","gasUsed":23866,"gasUsedRaw":"0x5d3a","logs":[ + {"address":"0x476059cd57800db8eb88f67c2aa38a6fcf8251e0","blockHash":"0x86082426228ee23ea8927607c7f731194ebc1dc7ca69a6321ff46e91b0fdbe2a","blockNumber":190,"blockNumberRaw":"0xbe","data":"0x","logIndex":0,"logIndexRaw":"0x0","removed":false,"topics":["0xa3865c00e01495fc2b86502cae36a4edb139f748682e7d80725a3d6571a482fa","0x9aeb7ab140e1449806e11e52ca85dacb6a028ae5feb8d431e7d927e7971e4d2d","0xb1a82faaa61a7bfbc90c42129b2049d408f20b8f43ae99e8b6aa6cc7e7b0b944"],"transactionHash":"0x18b8c942735e869a42fff06393ebb12824ec44ed030e9e6a579f8f839c59a5de","transactionIndex":0,"transactionIndexRaw":"0x0","type":"mined"}],"logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000008000200000000000000000000010000000000004000000000000000000000000000110000000000000004000000000000000000000000000000000000000000000020000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000020000000000000000000000000000000000000000000000000000000000000000000000000","status":"0x1","statusOK":true,"to":"0x476059cd57800db8eb88f67c2aa38a6fcf8251e0","transactionHash":"0x18b8c942735e869a42fff06393ebb12824ec44ed030e9e6a579f8f839c59a5de","transactionIndexRaw":"0x0"}] +}% +``` +The JSON structure shows: +- One Entity ID: "urn:ngsi-ld:Building:farm002" +- Two transactions recorded in different blocks: + - First transaction in block 186 (0xba) + - Second transaction in block 190 (0xbe) + +Each transaction contains detailed blockchain information including block hashes, gas usage, logs, and transaction hashes, but they all relate to the same building entity. + +To ask the context brker Orion-LD about the receipt of a specific building use the following command: +```bash +curl -L 'http://46.17.108.147:1026/ngsi-ld/v1/entities/?type=DLTtxReceipt&q=refEntity%3D%3D%22urn%3Angsi-ld%3ABuilding%3Afarm002%22&attrs=TxReceipts' \ +-H 'Link: ; rel="http://www.w3.org/ns/json-ld#context"; type="application/ld+json"' \ +-H 'NGSILD-Tenant: orion' +``` +The outpot will be similar to the following response: +```bash +["@context":"https://raw.githubusercontent.com/smart-data-models/dataModel.DistributedLedgerTech/master/context.jsonld","id":"urn:ngsi-ld:dlttxreceipt:0x8456f94030cc841bc667d76ce563b47e4920a27884bdb9b8fc5e4e9cc2df1913","type":"DLTtxReceipt","TxReceipts":{"type":"Property","value":{"blockHash":"0x610f07a4b5a87ca71273791fe5d12952fca393a6e3664489f45329bebdb396c3","blockNumber":186,"blockNumberRaw":"0xba","cumulativeGasUsed":23866,"cumulativeGasUsedRaw":"0x5d3a","from":"0x34e5b3f990e55d0651b35c817bafb89d2877cb95","gasUsed":23866,"gasUsedRaw":"0x5d3a","logs":[{"address":"0x476059cd57800db8eb88f67c2aa38a6fcf8251e0","blockHash":"0x610f07a4b5a87ca71273791fe5d12952fca393a6e3664489f45329bebdb396c3","blockNumber":186,"blockNumberRaw":"0xba","data":"0x","logIndex":0,"logIndexRaw":"0x0","removed":false,"topics":["0xa3865c00e01495fc2b86502cae36a4edb139f748682e7d80725a3d6571a482fa","0x9aeb7ab140e1449806e11e52ca85dacb6a028ae5feb8d431e7d927e7971e4d2d","0xb1a82faaa61a7bfbc90c42129b2049d408f20b8f43ae99e8b6aa6cc7e7b0b944"],"transactionHash":"0x8456f94030cc841bc667d76ce563b47e4920a27884bdb9b8fc5e4e9cc2df1913","transactionIndex":0,"transactionIndexRaw":"0x0","type":"mined"}],"logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000008000200000000000000000000010000000000004000000000000000000000000000110000000000000004000000000000000000000000000000000000000000000020000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000020000000000000000000000000000000000000000000000000000000000000000000000000","status":"0x1","statusOK":true,"to":"0x476059cd57800db8eb88f67c2aa38a6fcf8251e0","transactionHash":"0x8456f94030cc841bc667d76ce563b47e4920a27884bdb9b8fc5e4e9cc2df1913","transactionIndex":0,"transactionIndexRaw":"0x0"}}},{"@context":"https://raw.githubusercontent.com/smart-data-models/dataModel.DistributedLedgerTech/master/context.jsonld","id":"urn:ngsi-ld:dlttxreceipt:0x18b8c942735e869a42fff06393ebb12824ec44ed030e9e6a579f8f839c59a5de","type":"DLTtxReceipt","TxReceipts":{"type":"Property","value":{"blockHash":"0x86082426228ee23ea8927607c7f731194ebc1dc7ca69a6321ff46e91b0fdbe2a","blockNumber":190,"blockNumberRaw":"0xbe","cumulativeGasUsed":23866,"cumulativeGasUsedRaw":"0x5d3a","from":"0x34e5b3f990e55d0651b35c817bafb89d2877cb95","gasUsed":23866,"gasUsedRaw":"0x5d3a","logs":[{"address":"0x476059cd57800db8eb88f67c2aa38a6fcf8251e0","blockHash":"0x86082426228ee23ea8927607c7f731194ebc1dc7ca69a6321ff46e91b0fdbe2a","blockNumber":190,"blockNumberRaw":"0xbe","data":"0x","logIndex":0,"logIndexRaw":"0x0","removed":false,"topics":["0xa3865c00e01495fc2b86502cae36a4edb139f748682e7d80725a3d6571a482fa","0x9aeb7ab140e1449806e11e52ca85dacb6a028ae5feb8d431e7d927e7971e4d2d","0xb1a82faaa61a7bfbc90c42129b2049d408f20b8f43ae99e8b6aa6cc7e7b0b944"],"transactionHash":"0x18b8c942735e869a42fff06393ebb12824ec44ed030e9e6a579f8f839c59a5de","transactionIndex":0,"transactionIndexRaw":"0x0","type":"mined"}],"logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000008000200000000000000000000010000000000004000000000000000000000000000110000000000000004000000000000000000000000000000000000000000000020000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000020000000000000000000000000000000000000000000000000000000000000000000000000","status":"0x1","statusOK":true,"to":"0x476059cd57800db8eb88f67c2aa38a6fcf8251e0","transactionHash":"0x18b8c942735e869a42fff06393ebb12824ec44ed030e9e6a579f8f839c59a5de","transactionIndex":0,"transactionIndexRaw":"0x0"}}}]% +``` +This JSON response shows two DLTtxReceipt (Distributed Ledger Technology Transaction Receipt) entities in NGSI-LD format: + +##### First Transaction Receipt +- ID: urn:ngsi-ld:dlttxreceipt:0x8456f94030cc841bc667d76ce563b47e4920a27884bdb9b8fc5e4e9cc2df1913 +- Block Number: 186 (0xba) +- Gas Used: 23,866 +- From Address: 0x34e5b3f990e55d0651b35c817bafb89d2877cb95 +- To Address: 0x476059cd57800db8eb88f67c2aa38a6fcf8251e0 + +##### Second Transaction Receipt +- ID: urn:ngsi-ld:dlttxreceipt:0x18b8c942735e869a42fff06393ebb12824ec44ed030e9e6a579f8f839c59a5de +- Block Number: 190 (0xbe) +- Gas Used: 23,866 +- From Address: 0x34e5b3f990e55d0651b35c817bafb89d2877cb95 +- To Address: 0x476059cd57800db8eb88f67c2aa38a6fcf8251e0 + +Both transactions were successful (statusOK: true). Each receipt contains detailed blockchain information including logs, bloom filters, and raw transaction data. + + From 481b3827b8e5ae6fca9fa03a487802a617be677f Mon Sep 17 00:00:00 2001 From: asmataamallah25 Date: Fri, 6 Dec 2024 14:23:43 +0100 Subject: [PATCH 02/19] Updates Canis Major workflow test commands --- validator/Tests.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/validator/Tests.md b/validator/Tests.md index 0624291..f489740 100644 --- a/validator/Tests.md +++ b/validator/Tests.md @@ -5,7 +5,7 @@ Canis Major serves as a blockchain adaptor within the FIWARE ecosystem, providin After r unning the integartion tests, create an entity by sending this POST request to Canis Major: ```bash -curl --location 'http://46.17.108.147:4000/ngsi-ld/v1/entities/' \ +curl --location 'http://:4000/ngsi-ld/v1/entities/' \ --header 'Link: ; rel="http://www.w3.org/ns/json-ld#context"; type="application/ld+json"' \ --header 'Wallet-Type: vault' \ --header 'Wallet-Token: vault-plaintext-root-token' \ @@ -34,7 +34,7 @@ curl --location 'http://46.17.108.147:4000/ngsi-ld/v1/entities/' \ To retrieve the available entity types in the context broker run the following command: ```bash -curl -L 'http://46.17.108.147:1026/ngsi-ld/v1/types' \ +curl -L 'http://:1026/ngsi-ld/v1/types' \ -H 'Link: ; rel="http://www.w3.org/ns/json-ld#context"; type="application/ld+json"' \ -H 'NGSILD-Tenant: orion' ``` @@ -50,7 +50,7 @@ To verify which entities are created in the context broker use the following com To verify that the created entities is correctly saved in the context broker having the type DLTtxReceipt, use the following command: ```bash -curl -L 'http://46.17.108.147:1026/ngsi-ld/v1/entities/?type=DLTtxReceipt' \ +curl -L 'http://:1026/ngsi-ld/v1/entities/?type=DLTtxReceipt' \ -H 'Link: ; rel="http://www.w3.org/ns/json-ld#context"; type="application/ld+json"' \ -H 'NGSILD-Tenant: orion' ``` @@ -58,7 +58,7 @@ curl -L 'http://46.17.108.147:1026/ngsi-ld/v1/entities/?type=DLTtxReceipt' \ To ask Canis Major about receipts for a specific building use the following command: ```bash -curl -L 'http://46.17.108.147:4000/entity/urn:ngsi-ld:Building:farm002' \ +curl -L 'http://:4000/entity/urn:ngsi-ld:Building:farm002' \ -H 'Accept: application/json' ``` @@ -85,7 +85,7 @@ Each transaction contains detailed blockchain information including block hashes To ask the context brker Orion-LD about the receipt of a specific building use the following command: ```bash -curl -L 'http://46.17.108.147:1026/ngsi-ld/v1/entities/?type=DLTtxReceipt&q=refEntity%3D%3D%22urn%3Angsi-ld%3ABuilding%3Afarm002%22&attrs=TxReceipts' \ +curl -L 'http://:1026/ngsi-ld/v1/entities/?type=DLTtxReceipt&q=refEntity%3D%3D%22urn%3Angsi-ld%3ABuilding%3Afarm002%22&attrs=TxReceipts' \ -H 'Link: ; rel="http://www.w3.org/ns/json-ld#context"; type="application/ld+json"' \ -H 'NGSILD-Tenant: orion' ``` From 4e5dfc331a97987546cdd70053159489ad2cf0e8 Mon Sep 17 00:00:00 2001 From: asmataamallah25 Date: Mon, 9 Dec 2024 15:36:52 +0100 Subject: [PATCH 03/19] Validation service: first iteration --- it/docker-compose/clean.sh | 12 ++ src/main/java/validator/Tests.md | 131 ++++++++++++++++++ .../java/validator/ValidationService.java | 66 +++++++++ .../client/ValidationHttpClient.java | 55 ++++++++ .../controller/ValidationController.java | 29 ++++ .../java/validator/model/ValidationInfo.java | 13 ++ .../validator/model/ValidationResult.java | 9 ++ 7 files changed, 315 insertions(+) create mode 100755 it/docker-compose/clean.sh create mode 100644 src/main/java/validator/Tests.md create mode 100644 src/main/java/validator/ValidationService.java create mode 100644 src/main/java/validator/client/ValidationHttpClient.java create mode 100644 src/main/java/validator/controller/ValidationController.java create mode 100644 src/main/java/validator/model/ValidationInfo.java create mode 100644 src/main/java/validator/model/ValidationResult.java diff --git a/it/docker-compose/clean.sh b/it/docker-compose/clean.sh new file mode 100755 index 0000000..5365fad --- /dev/null +++ b/it/docker-compose/clean.sh @@ -0,0 +1,12 @@ +#Stop and remove all Docker containers: +docker stop $(sudo docker ps -aq) +docker rm $(sudo docker ps -aq) +docker container prune + +#Remove all created volumes: +docker volume rm $(sudo docker volume ls -q) +docker volume prune + +#Remove all Docker custom networks: +docker network prune -f + diff --git a/src/main/java/validator/Tests.md b/src/main/java/validator/Tests.md new file mode 100644 index 0000000..ff614da --- /dev/null +++ b/src/main/java/validator/Tests.md @@ -0,0 +1,131 @@ +# Canis Major test + +Canis Major serves as a blockchain adaptor within the FIWARE ecosystem, providing secure data persistence across blockchain networks and the Context Broker. The workflow is straightforward: + - clients submit transactions containing payload data and wallet credentials. + - These data are authenticated through a PEP Proxy or KeyRock Identity Management. + - Once validated, the data is stored in an ETSI Broker (e.g., Orion-LD Broker) and simultaneously processed into a Merkle tree structure for blockchain integration. + - The system then signs the transaction using the provided wallet credentials and submits it to an Oketh-compatible blockchain. + + Let's explore the practical implementation through a series of test commands. First, it is needed the execution of the integration tests executing the following commands: + + ```shell + cd it + docker-compose -f docker-compose/docker-compose-env.yaml -f docker-compose/docker-compose-java.yaml up + NGSI_ADDRESS=localhost:4000 mvn clean test + ``` + + +After running the integartion tests, create an entity by sending this POST request to Canis Major: + +```console +curl --location 'http://:4000/ngsi-ld/v1/entities/' \ +--header 'Link: ; rel="http://www.w3.org/ns/json-ld#context"; type="application/ld+json"' \ +--header 'Wallet-Type: vault' \ +--header 'Wallet-Token: vault-plaintext-root-token' \ +--header 'Wallet-Address: http://vault:8200/v1/ethereum/accounts/mira' \ +--header 'Content-Type: application/json' \ +--header 'Accept: application/json' \ +--header 'NGSILD-TENANT: orion' \ +--data '{ + "id": "urn:ngsi-ld:Building:farm002", + "type": "Building", + "category": { + "type": "Property", + "value": ["farm"] + }, + "address": { + "type": "Property", + "value": { + "streetAddress": "Großer Stern 1", + "addressRegion": "Berlin", + "addressLocality": "Tiergarten", + + "postalCode": "10557" + } + } +}' +``` + +To retrieve the available entity types in the context broker run the following command: +```bash +curl -L 'http://:1026/ngsi-ld/v1/types' \ +-H 'Link: ; rel="http://www.w3.org/ns/json-ld#context"; type="application/ld+json"' \ +-H 'NGSILD-Tenant: orion' +``` +The command returns the following response, demonstrating the available entity types in the Context Broker: +```bash +{ + "@context":"https://raw.githubusercontent.com/smart-data-models/dataModel.DistributedLedgerTech/master/context.jsonld","id":"urn:ngsi-ld:EntityTypeList:d77ccfa0-b3cd-11ef-ae1b-0242ac120005","type":"EntityTypeList","typeList":["DLTtxReceipt"] +}% +``` +The @context is using a definition on smart data models for a DLTtxReceipt and the NGSILD-Tenant is defined in the start-up of the tests. +To verify which entities are created in the context broker use the following command: + +To verify that the created entities is correctly saved in the context broker having the type DLTtxReceipt, use the following command: + +```json +curl -L 'http://:1026/ngsi-ld/v1/entities/?type=DLTtxReceipt' \ +-H 'Link: ; rel="http://www.w3.org/ns/json-ld#context"; type="application/ld+json"' \ +-H 'NGSILD-Tenant: orion' +``` + +To ask Canis Major about receipts for a specific building use the following command: + +```bash + +curl -L 'http://:4000/entity/urn:ngsi-ld:Building:farm002' \ +-H 'Accept: application/json' + +``` + +The output will be similar to following response: + +```bash +{ + "entityId":"urn:ngsi-ld:Building:farm002","txDetails":[ + { + "blockHash":"0x610f07a4b5a87ca71273791fe5d12952fca393a6e3664489f45329bebdb396c3","blockNumber":186,"blockNumberRaw":"0xba","cumulativeGasUsed":23866,"cumulativeGasUsedRaw":"0x5d3a","from":"0x34e5b3f990e55d0651b35c817bafb89d2877cb95","gasUsed":23866,"gasUsedRaw":"0x5d3a","logs":[ + { + + "address":"0x476059cd57800db8eb88f67c2aa38a6fcf8251e0","blockHash":"0x610f07a4b5a87ca71273791fe5d12952fca393a6e3664489f45329bebdb396c3","blockNumber":186,"blockNumberRaw":"0xba","data":"0x","logIndex":0,"logIndexRaw":"0x0","removed":false,"topics":["0xa3865c00e01495fc2b86502cae36a4edb139f748682e7d80725a3d6571a482fa","0x9aeb7ab140e1449806e11e52ca85dacb6a028ae5feb8d431e7d927e7971e4d2d","0xb1a82faaa61a7bfbc90c42129b2049d408f20b8f43ae99e8b6aa6cc7e7b0b944"],"transactionHash":"0x8456f94030cc841bc667d76ce563b47e4920a27884bdb9b8fc5e4e9cc2df1913","transactionIndex":0,"transactionIndexRaw":"0x0","type":"mined"}],"logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000008000200000000000000000000010000000000004000000000000000000000000000110000000000000004000000000000000000000000000000000000000000000020000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000020000000000000000000000000000000000000000000000000000000000000000000000000","status":"0x1","statusOK":true,"to":"0x476059cd57800db8eb88f67c2aa38a6fcf8251e0","transactionHash":"0x8456f94030cc841bc667d76ce563b47e4920a27884bdb9b8fc5e4e9cc2df1913","transactionIndexRaw":"0x0"},{ + "blockHash":"0x86082426228ee23ea8927607c7f731194ebc1dc7ca69a6321ff46e91b0fdbe2a","blockNumber":190,"blockNumberRaw":"0xbe","cumulativeGasUsed":23866,"cumulativeGasUsedRaw":"0x5d3a","from":"0x34e5b3f990e55d0651b35c817bafb89d2877cb95","gasUsed":23866,"gasUsedRaw":"0x5d3a","logs":[ + {"address":"0x476059cd57800db8eb88f67c2aa38a6fcf8251e0","blockHash":"0x86082426228ee23ea8927607c7f731194ebc1dc7ca69a6321ff46e91b0fdbe2a","blockNumber":190,"blockNumberRaw":"0xbe","data":"0x","logIndex":0,"logIndexRaw":"0x0","removed":false,"topics":["0xa3865c00e01495fc2b86502cae36a4edb139f748682e7d80725a3d6571a482fa","0x9aeb7ab140e1449806e11e52ca85dacb6a028ae5feb8d431e7d927e7971e4d2d","0xb1a82faaa61a7bfbc90c42129b2049d408f20b8f43ae99e8b6aa6cc7e7b0b944"],"transactionHash":"0x18b8c942735e869a42fff06393ebb12824ec44ed030e9e6a579f8f839c59a5de","transactionIndex":0,"transactionIndexRaw":"0x0","type":"mined"}],"logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000008000200000000000000000000010000000000004000000000000000000000000000110000000000000004000000000000000000000000000000000000000000000020000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000020000000000000000000000000000000000000000000000000000000000000000000000000","status":"0x1","statusOK":true,"to":"0x476059cd57800db8eb88f67c2aa38a6fcf8251e0","transactionHash":"0x18b8c942735e869a42fff06393ebb12824ec44ed030e9e6a579f8f839c59a5de","transactionIndexRaw":"0x0"}] +}% +``` +The JSON structure shows: +- One Entity ID: "urn:ngsi-ld:Building:farm002" +- Two transactions recorded in different blocks: + - First transaction in block 186 (0xba) + - Second transaction in block 190 (0xbe) + +Each transaction contains detailed blockchain information including block hashes, gas usage, logs, and transaction hashes, but they all relate to the same building entity. + +To ask the context brker Orion-LD about the receipt of a specific building use the following command: +```bash +curl -L 'http://:1026/ngsi-ld/v1/entities/?type=DLTtxReceipt&q=refEntity%3D%3D%22urn%3Angsi-ld%3ABuilding%3Afarm002%22&attrs=TxReceipts' \ +-H 'Link: ; rel="http://www.w3.org/ns/json-ld#context"; type="application/ld+json"' \ +-H 'NGSILD-Tenant: orion' +``` +The outpot will be similar to the following response: +```bash +["@context":"https://raw.githubusercontent.com/smart-data-models/dataModel.DistributedLedgerTech/master/context.jsonld","id":"urn:ngsi-ld:dlttxreceipt:0x8456f94030cc841bc667d76ce563b47e4920a27884bdb9b8fc5e4e9cc2df1913","type":"DLTtxReceipt","TxReceipts":{"type":"Property","value":{"blockHash":"0x610f07a4b5a87ca71273791fe5d12952fca393a6e3664489f45329bebdb396c3","blockNumber":186,"blockNumberRaw":"0xba","cumulativeGasUsed":23866,"cumulativeGasUsedRaw":"0x5d3a","from":"0x34e5b3f990e55d0651b35c817bafb89d2877cb95","gasUsed":23866,"gasUsedRaw":"0x5d3a","logs":[{"address":"0x476059cd57800db8eb88f67c2aa38a6fcf8251e0","blockHash":"0x610f07a4b5a87ca71273791fe5d12952fca393a6e3664489f45329bebdb396c3","blockNumber":186,"blockNumberRaw":"0xba","data":"0x","logIndex":0,"logIndexRaw":"0x0","removed":false,"topics":["0xa3865c00e01495fc2b86502cae36a4edb139f748682e7d80725a3d6571a482fa","0x9aeb7ab140e1449806e11e52ca85dacb6a028ae5feb8d431e7d927e7971e4d2d","0xb1a82faaa61a7bfbc90c42129b2049d408f20b8f43ae99e8b6aa6cc7e7b0b944"],"transactionHash":"0x8456f94030cc841bc667d76ce563b47e4920a27884bdb9b8fc5e4e9cc2df1913","transactionIndex":0,"transactionIndexRaw":"0x0","type":"mined"}],"logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000008000200000000000000000000010000000000004000000000000000000000000000110000000000000004000000000000000000000000000000000000000000000020000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000020000000000000000000000000000000000000000000000000000000000000000000000000","status":"0x1","statusOK":true,"to":"0x476059cd57800db8eb88f67c2aa38a6fcf8251e0","transactionHash":"0x8456f94030cc841bc667d76ce563b47e4920a27884bdb9b8fc5e4e9cc2df1913","transactionIndex":0,"transactionIndexRaw":"0x0"}}},{"@context":"https://raw.githubusercontent.com/smart-data-models/dataModel.DistributedLedgerTech/master/context.jsonld","id":"urn:ngsi-ld:dlttxreceipt:0x18b8c942735e869a42fff06393ebb12824ec44ed030e9e6a579f8f839c59a5de","type":"DLTtxReceipt","TxReceipts":{"type":"Property","value":{"blockHash":"0x86082426228ee23ea8927607c7f731194ebc1dc7ca69a6321ff46e91b0fdbe2a","blockNumber":190,"blockNumberRaw":"0xbe","cumulativeGasUsed":23866,"cumulativeGasUsedRaw":"0x5d3a","from":"0x34e5b3f990e55d0651b35c817bafb89d2877cb95","gasUsed":23866,"gasUsedRaw":"0x5d3a","logs":[{"address":"0x476059cd57800db8eb88f67c2aa38a6fcf8251e0","blockHash":"0x86082426228ee23ea8927607c7f731194ebc1dc7ca69a6321ff46e91b0fdbe2a","blockNumber":190,"blockNumberRaw":"0xbe","data":"0x","logIndex":0,"logIndexRaw":"0x0","removed":false,"topics":["0xa3865c00e01495fc2b86502cae36a4edb139f748682e7d80725a3d6571a482fa","0x9aeb7ab140e1449806e11e52ca85dacb6a028ae5feb8d431e7d927e7971e4d2d","0xb1a82faaa61a7bfbc90c42129b2049d408f20b8f43ae99e8b6aa6cc7e7b0b944"],"transactionHash":"0x18b8c942735e869a42fff06393ebb12824ec44ed030e9e6a579f8f839c59a5de","transactionIndex":0,"transactionIndexRaw":"0x0","type":"mined"}],"logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000008000200000000000000000000010000000000004000000000000000000000000000110000000000000004000000000000000000000000000000000000000000000020000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000020000000000000000000000000000000000000000000000000000000000000000000000000","status":"0x1","statusOK":true,"to":"0x476059cd57800db8eb88f67c2aa38a6fcf8251e0","transactionHash":"0x18b8c942735e869a42fff06393ebb12824ec44ed030e9e6a579f8f839c59a5de","transactionIndex":0,"transactionIndexRaw":"0x0"}}}]% +``` +This JSON response shows two DLTtxReceipt (Distributed Ledger Technology Transaction Receipt) entities in NGSI-LD format: + +##### First Transaction Receipt +- ID: urn:ngsi-ld:dlttxreceipt:0x8456f94030cc841bc667d76ce563b47e4920a27884bdb9b8fc5e4e9cc2df1913 +- Block Number: 186 (0xba) +- Gas Used: 23,866 +- From Address: 0x34e5b3f990e55d0651b35c817bafb89d2877cb95 +- To Address: 0x476059cd57800db8eb88f67c2aa38a6fcf8251e0 + +##### Second Transaction Receipt +- ID: urn:ngsi-ld:dlttxreceipt:0x18b8c942735e869a42fff06393ebb12824ec44ed030e9e6a579f8f839c59a5de +- Block Number: 190 (0xbe) +- Gas Used: 23,866 +- From Address: 0x34e5b3f990e55d0651b35c817bafb89d2877cb95 +- To Address: 0x476059cd57800db8eb88f67c2aa38a6fcf8251e0 + +Both transactions were successful (statusOK: true). Each receipt contains detailed blockchain information including logs, bloom filters, and raw transaction data. + + diff --git a/src/main/java/validator/ValidationService.java b/src/main/java/validator/ValidationService.java new file mode 100644 index 0000000..f649c21 --- /dev/null +++ b/src/main/java/validator/ValidationService.java @@ -0,0 +1,66 @@ +// Declares the package name for organizing related classes +package validator; + +// Main service class for handling validation operations +public class ValidationService { + + // Method to validate entities by comparing responses from Orion and Canis Major + // Takes two parameters: orionResponse and blockchainResponse as generic Objects + public ValidationResult validateEntityFromResponses(Object orionResponse, Object blockchainResponse) { + // Null check: if either response is null, return failed validation result + if (orionResponse == null || CanisMajorResponse == null) { + return new ValidationResult(false, "Missing response data"); + } + + // Try-catch block to handle potential exceptions during validation + try { + // Compare the responses and store the result + boolean isValid = compareResponses(orionResponse, CanisMajorResponse ); + // Create success/failure message based on comparison result + String message = isValid ? "Validation successful" : "Validation failed"; + // Return the validation result with status and message + return new ValidationResult(isValid, message); + } catch (Exception e) { + // If any error occurs, return failed validation with error message + return new ValidationResult(false, "Validation error: " + e.getMessage()); + } + } + + // Private helper method to compare responses (currently returns false as placeholder) + private boolean compareResponses(Object orionResponse, Object CanisMajorResponse) { + return false; + } + + // Method to get validation information for a specific entity + // Uses builder pattern to construct ValidationInfo object + public ValidationInfo getValidationInfo(String entityId) { + return ValidationInfo.builder() + .status("PENDING") // Sets initial status + .transactionHash(generateTransactionHash()) // Generates transaction hash + .blockHash(generateBlockHash()) // Generates block hash + .blockNumber(getCurrentBlockNumber()) // Gets current block number + .fromAddress(getFromAddress()) // Gets sender address + .entityId(entityId) // Sets entity ID + .build(); // Builds the final object + } + + // Helper method to generate transaction hash (placeholder implementation) + private String generateTransactionHash() { + return "0x..."; + } + + // Helper method to generate block hash (placeholder implementation) + private String generateBlockHash() { + return "0x..."; + } + + // Helper method to get current block number (placeholder implementation) + private long getCurrentBlockNumber() { + return 0L; + } + + // Helper method to get sender address (placeholder implementation) + private String getFromAddress() { + return "0x..."; + } +} \ No newline at end of file diff --git a/src/main/java/validator/client/ValidationHttpClient.java b/src/main/java/validator/client/ValidationHttpClient.java new file mode 100644 index 0000000..c490060 --- /dev/null +++ b/src/main/java/validator/client/ValidationHttpClient.java @@ -0,0 +1,55 @@ +// Declares the package name for this class +package validator.client; + +// Import statements: +import jakarta.inject.Singleton; // For dependency injection, marks class as singleton +import java.net.URI; // For handling URIs +import java.net.http.HttpClient; // Core class for making HTTP requests +import java.net.http.HttpRequest; // For building HTTP requests +import java.net.http.HttpResponse; // For handling HTTP responses + + +// Marks this class as a singleton - only one instance will exist in the application +@Singleton +public class ValidationHttpClient { + // Creates a single HttpClient instance to be reused for all requests + private final HttpClient client = HttpClient.newHttpClient(); + + // Method to fetch data from Orion Context Broker + public String fetchOrionData(String entityId) throws Exception { + // Builds an HTTP request: + HttpRequest request = HttpRequest.newBuilder() + // Sets the URI for the request, querying for DLTtxReceipt entities with matching refEntity + .uri(new URI("http://localhost:1026/ngsi-ld/v1/entities/?type=DLTtxReceipt&q=refEntity==" + entityId)) + // Adds Link header for JSON-LD context + .header("Link", "; rel=\"http://www.w3.org/ns/json-ld#context\"; type=\"application/ld+json\"") + // Sets the NGSILD-Tenant header to "orion" + .header("NGSILD-Tenant", "orion") + // Added Accept header for JSON format + .header("Accept", "application/json") + // Specifies this is a GET request + .GET() + // Finalizes the request building + .build(); + + // Sends the request and returns the response body as a String + return client.send(request, HttpResponse.BodyHandlers.ofString()).body(); + } + + // Method to fetch data from a blockchain service + public String fetchBlockchainData(String entityId) throws Exception { + // Builds an HTTP request: + HttpRequest request = HttpRequest.newBuilder() + // Sets the URI for the blockchain service endpoint + .uri(new URI("http://localhost:4000/entity/" + entityId)) + // Sets Accept header to receive JSONresponse + .header("Accept", "application/json") + // Specifies this is a GET request + .GET() + // Finalizes the request building + .build(); + + // Sends the request and returns the response body as a String + return client.send(request, HttpResponse.BodyHandlers.ofString()).body(); + } +} \ No newline at end of file diff --git a/src/main/java/validator/controller/ValidationController.java b/src/main/java/validator/controller/ValidationController.java new file mode 100644 index 0000000..680b60a --- /dev/null +++ b/src/main/java/validator/controller/ValidationController.java @@ -0,0 +1,29 @@ +package validator.controller; + +import org.springframework.web.bind.annotation.*; +import validator.service.ValidationService; +import validator.model.ValidationResult; +import validator.model.ValidationInfo; + +@RestController +@RequestMapping("/validation service") +public class ValidationController { + private final ValidationService validationService; + + public ValidationController(ValidationService validationService) { + this.validationService = validationService; + } + + @GetMapping("/{entityId}") + public ValidationInfo getValidationInfo(@PathVariable String entityId) { + return validationService.getValidationInfo(entityId); + } + + @PostMapping("/compare") + public ValidationResult compareResponses(@RequestBody ValidationRequest request) { + return validationService.validateEntityFromResponses( + request.orionResponse(), + request.blockchainResponse() + ); + } +} \ No newline at end of file diff --git a/src/main/java/validator/model/ValidationInfo.java b/src/main/java/validator/model/ValidationInfo.java new file mode 100644 index 0000000..27e1fd3 --- /dev/null +++ b/src/main/java/validator/model/ValidationInfo.java @@ -0,0 +1,13 @@ +package validator.model; + +public record ValidationInfo( + String entityId, + String status, + String transactionHash, + String blockHash, + Long blockNumber, + String fromAddress +) { + // Records automatically generate getters, so these methods are unnecessary + // and should be removed unless you need custom implementations +} \ No newline at end of file diff --git a/src/main/java/validator/model/ValidationResult.java b/src/main/java/validator/model/ValidationResult.java new file mode 100644 index 0000000..1aa4b8f --- /dev/null +++ b/src/main/java/validator/model/ValidationResult.java @@ -0,0 +1,9 @@ +package validator.model; + +import java.util.List; + +public record ValidationResult(boolean isValid, List errors) { + public String getErrorMessage() { + return String.join(", ", errors); + } +} \ No newline at end of file From 95b565b2ab8e9241f75fc5bcb103834d1c06372a Mon Sep 17 00:00:00 2001 From: asmataamallah25 Date: Tue, 10 Dec 2024 14:57:03 +0100 Subject: [PATCH 04/19] Updated test commands documentation and changed code location --- src/main/java/validator/Tests.md | 131 ------------------ .../ValidationService.java | 0 .../client/ValidationHttpClient.java | 0 .../controller/ValidationController.java | 0 .../model/ValidationInfo.java | 0 .../model/ValidationResult.java | 0 6 files changed, 131 deletions(-) delete mode 100644 src/main/java/validator/Tests.md rename {src/main/java/validator => validator}/ValidationService.java (100%) rename {src/main/java/validator => validator}/client/ValidationHttpClient.java (100%) rename {src/main/java/validator => validator}/controller/ValidationController.java (100%) rename {src/main/java/validator => validator}/model/ValidationInfo.java (100%) rename {src/main/java/validator => validator}/model/ValidationResult.java (100%) diff --git a/src/main/java/validator/Tests.md b/src/main/java/validator/Tests.md deleted file mode 100644 index ff614da..0000000 --- a/src/main/java/validator/Tests.md +++ /dev/null @@ -1,131 +0,0 @@ -# Canis Major test - -Canis Major serves as a blockchain adaptor within the FIWARE ecosystem, providing secure data persistence across blockchain networks and the Context Broker. The workflow is straightforward: - - clients submit transactions containing payload data and wallet credentials. - - These data are authenticated through a PEP Proxy or KeyRock Identity Management. - - Once validated, the data is stored in an ETSI Broker (e.g., Orion-LD Broker) and simultaneously processed into a Merkle tree structure for blockchain integration. - - The system then signs the transaction using the provided wallet credentials and submits it to an Oketh-compatible blockchain. - - Let's explore the practical implementation through a series of test commands. First, it is needed the execution of the integration tests executing the following commands: - - ```shell - cd it - docker-compose -f docker-compose/docker-compose-env.yaml -f docker-compose/docker-compose-java.yaml up - NGSI_ADDRESS=localhost:4000 mvn clean test - ``` - - -After running the integartion tests, create an entity by sending this POST request to Canis Major: - -```console -curl --location 'http://:4000/ngsi-ld/v1/entities/' \ ---header 'Link: ; rel="http://www.w3.org/ns/json-ld#context"; type="application/ld+json"' \ ---header 'Wallet-Type: vault' \ ---header 'Wallet-Token: vault-plaintext-root-token' \ ---header 'Wallet-Address: http://vault:8200/v1/ethereum/accounts/mira' \ ---header 'Content-Type: application/json' \ ---header 'Accept: application/json' \ ---header 'NGSILD-TENANT: orion' \ ---data '{ - "id": "urn:ngsi-ld:Building:farm002", - "type": "Building", - "category": { - "type": "Property", - "value": ["farm"] - }, - "address": { - "type": "Property", - "value": { - "streetAddress": "Großer Stern 1", - "addressRegion": "Berlin", - "addressLocality": "Tiergarten", - - "postalCode": "10557" - } - } -}' -``` - -To retrieve the available entity types in the context broker run the following command: -```bash -curl -L 'http://:1026/ngsi-ld/v1/types' \ --H 'Link: ; rel="http://www.w3.org/ns/json-ld#context"; type="application/ld+json"' \ --H 'NGSILD-Tenant: orion' -``` -The command returns the following response, demonstrating the available entity types in the Context Broker: -```bash -{ - "@context":"https://raw.githubusercontent.com/smart-data-models/dataModel.DistributedLedgerTech/master/context.jsonld","id":"urn:ngsi-ld:EntityTypeList:d77ccfa0-b3cd-11ef-ae1b-0242ac120005","type":"EntityTypeList","typeList":["DLTtxReceipt"] -}% -``` -The @context is using a definition on smart data models for a DLTtxReceipt and the NGSILD-Tenant is defined in the start-up of the tests. -To verify which entities are created in the context broker use the following command: - -To verify that the created entities is correctly saved in the context broker having the type DLTtxReceipt, use the following command: - -```json -curl -L 'http://:1026/ngsi-ld/v1/entities/?type=DLTtxReceipt' \ --H 'Link: ; rel="http://www.w3.org/ns/json-ld#context"; type="application/ld+json"' \ --H 'NGSILD-Tenant: orion' -``` - -To ask Canis Major about receipts for a specific building use the following command: - -```bash - -curl -L 'http://:4000/entity/urn:ngsi-ld:Building:farm002' \ --H 'Accept: application/json' - -``` - -The output will be similar to following response: - -```bash -{ - "entityId":"urn:ngsi-ld:Building:farm002","txDetails":[ - { - "blockHash":"0x610f07a4b5a87ca71273791fe5d12952fca393a6e3664489f45329bebdb396c3","blockNumber":186,"blockNumberRaw":"0xba","cumulativeGasUsed":23866,"cumulativeGasUsedRaw":"0x5d3a","from":"0x34e5b3f990e55d0651b35c817bafb89d2877cb95","gasUsed":23866,"gasUsedRaw":"0x5d3a","logs":[ - { - - "address":"0x476059cd57800db8eb88f67c2aa38a6fcf8251e0","blockHash":"0x610f07a4b5a87ca71273791fe5d12952fca393a6e3664489f45329bebdb396c3","blockNumber":186,"blockNumberRaw":"0xba","data":"0x","logIndex":0,"logIndexRaw":"0x0","removed":false,"topics":["0xa3865c00e01495fc2b86502cae36a4edb139f748682e7d80725a3d6571a482fa","0x9aeb7ab140e1449806e11e52ca85dacb6a028ae5feb8d431e7d927e7971e4d2d","0xb1a82faaa61a7bfbc90c42129b2049d408f20b8f43ae99e8b6aa6cc7e7b0b944"],"transactionHash":"0x8456f94030cc841bc667d76ce563b47e4920a27884bdb9b8fc5e4e9cc2df1913","transactionIndex":0,"transactionIndexRaw":"0x0","type":"mined"}],"logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000008000200000000000000000000010000000000004000000000000000000000000000110000000000000004000000000000000000000000000000000000000000000020000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000020000000000000000000000000000000000000000000000000000000000000000000000000","status":"0x1","statusOK":true,"to":"0x476059cd57800db8eb88f67c2aa38a6fcf8251e0","transactionHash":"0x8456f94030cc841bc667d76ce563b47e4920a27884bdb9b8fc5e4e9cc2df1913","transactionIndexRaw":"0x0"},{ - "blockHash":"0x86082426228ee23ea8927607c7f731194ebc1dc7ca69a6321ff46e91b0fdbe2a","blockNumber":190,"blockNumberRaw":"0xbe","cumulativeGasUsed":23866,"cumulativeGasUsedRaw":"0x5d3a","from":"0x34e5b3f990e55d0651b35c817bafb89d2877cb95","gasUsed":23866,"gasUsedRaw":"0x5d3a","logs":[ - {"address":"0x476059cd57800db8eb88f67c2aa38a6fcf8251e0","blockHash":"0x86082426228ee23ea8927607c7f731194ebc1dc7ca69a6321ff46e91b0fdbe2a","blockNumber":190,"blockNumberRaw":"0xbe","data":"0x","logIndex":0,"logIndexRaw":"0x0","removed":false,"topics":["0xa3865c00e01495fc2b86502cae36a4edb139f748682e7d80725a3d6571a482fa","0x9aeb7ab140e1449806e11e52ca85dacb6a028ae5feb8d431e7d927e7971e4d2d","0xb1a82faaa61a7bfbc90c42129b2049d408f20b8f43ae99e8b6aa6cc7e7b0b944"],"transactionHash":"0x18b8c942735e869a42fff06393ebb12824ec44ed030e9e6a579f8f839c59a5de","transactionIndex":0,"transactionIndexRaw":"0x0","type":"mined"}],"logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000008000200000000000000000000010000000000004000000000000000000000000000110000000000000004000000000000000000000000000000000000000000000020000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000020000000000000000000000000000000000000000000000000000000000000000000000000","status":"0x1","statusOK":true,"to":"0x476059cd57800db8eb88f67c2aa38a6fcf8251e0","transactionHash":"0x18b8c942735e869a42fff06393ebb12824ec44ed030e9e6a579f8f839c59a5de","transactionIndexRaw":"0x0"}] -}% -``` -The JSON structure shows: -- One Entity ID: "urn:ngsi-ld:Building:farm002" -- Two transactions recorded in different blocks: - - First transaction in block 186 (0xba) - - Second transaction in block 190 (0xbe) - -Each transaction contains detailed blockchain information including block hashes, gas usage, logs, and transaction hashes, but they all relate to the same building entity. - -To ask the context brker Orion-LD about the receipt of a specific building use the following command: -```bash -curl -L 'http://:1026/ngsi-ld/v1/entities/?type=DLTtxReceipt&q=refEntity%3D%3D%22urn%3Angsi-ld%3ABuilding%3Afarm002%22&attrs=TxReceipts' \ --H 'Link: ; rel="http://www.w3.org/ns/json-ld#context"; type="application/ld+json"' \ --H 'NGSILD-Tenant: orion' -``` -The outpot will be similar to the following response: -```bash -["@context":"https://raw.githubusercontent.com/smart-data-models/dataModel.DistributedLedgerTech/master/context.jsonld","id":"urn:ngsi-ld:dlttxreceipt:0x8456f94030cc841bc667d76ce563b47e4920a27884bdb9b8fc5e4e9cc2df1913","type":"DLTtxReceipt","TxReceipts":{"type":"Property","value":{"blockHash":"0x610f07a4b5a87ca71273791fe5d12952fca393a6e3664489f45329bebdb396c3","blockNumber":186,"blockNumberRaw":"0xba","cumulativeGasUsed":23866,"cumulativeGasUsedRaw":"0x5d3a","from":"0x34e5b3f990e55d0651b35c817bafb89d2877cb95","gasUsed":23866,"gasUsedRaw":"0x5d3a","logs":[{"address":"0x476059cd57800db8eb88f67c2aa38a6fcf8251e0","blockHash":"0x610f07a4b5a87ca71273791fe5d12952fca393a6e3664489f45329bebdb396c3","blockNumber":186,"blockNumberRaw":"0xba","data":"0x","logIndex":0,"logIndexRaw":"0x0","removed":false,"topics":["0xa3865c00e01495fc2b86502cae36a4edb139f748682e7d80725a3d6571a482fa","0x9aeb7ab140e1449806e11e52ca85dacb6a028ae5feb8d431e7d927e7971e4d2d","0xb1a82faaa61a7bfbc90c42129b2049d408f20b8f43ae99e8b6aa6cc7e7b0b944"],"transactionHash":"0x8456f94030cc841bc667d76ce563b47e4920a27884bdb9b8fc5e4e9cc2df1913","transactionIndex":0,"transactionIndexRaw":"0x0","type":"mined"}],"logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000008000200000000000000000000010000000000004000000000000000000000000000110000000000000004000000000000000000000000000000000000000000000020000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000020000000000000000000000000000000000000000000000000000000000000000000000000","status":"0x1","statusOK":true,"to":"0x476059cd57800db8eb88f67c2aa38a6fcf8251e0","transactionHash":"0x8456f94030cc841bc667d76ce563b47e4920a27884bdb9b8fc5e4e9cc2df1913","transactionIndex":0,"transactionIndexRaw":"0x0"}}},{"@context":"https://raw.githubusercontent.com/smart-data-models/dataModel.DistributedLedgerTech/master/context.jsonld","id":"urn:ngsi-ld:dlttxreceipt:0x18b8c942735e869a42fff06393ebb12824ec44ed030e9e6a579f8f839c59a5de","type":"DLTtxReceipt","TxReceipts":{"type":"Property","value":{"blockHash":"0x86082426228ee23ea8927607c7f731194ebc1dc7ca69a6321ff46e91b0fdbe2a","blockNumber":190,"blockNumberRaw":"0xbe","cumulativeGasUsed":23866,"cumulativeGasUsedRaw":"0x5d3a","from":"0x34e5b3f990e55d0651b35c817bafb89d2877cb95","gasUsed":23866,"gasUsedRaw":"0x5d3a","logs":[{"address":"0x476059cd57800db8eb88f67c2aa38a6fcf8251e0","blockHash":"0x86082426228ee23ea8927607c7f731194ebc1dc7ca69a6321ff46e91b0fdbe2a","blockNumber":190,"blockNumberRaw":"0xbe","data":"0x","logIndex":0,"logIndexRaw":"0x0","removed":false,"topics":["0xa3865c00e01495fc2b86502cae36a4edb139f748682e7d80725a3d6571a482fa","0x9aeb7ab140e1449806e11e52ca85dacb6a028ae5feb8d431e7d927e7971e4d2d","0xb1a82faaa61a7bfbc90c42129b2049d408f20b8f43ae99e8b6aa6cc7e7b0b944"],"transactionHash":"0x18b8c942735e869a42fff06393ebb12824ec44ed030e9e6a579f8f839c59a5de","transactionIndex":0,"transactionIndexRaw":"0x0","type":"mined"}],"logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000008000200000000000000000000010000000000004000000000000000000000000000110000000000000004000000000000000000000000000000000000000000000020000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000020000000000000000000000000000000000000000000000000000000000000000000000000","status":"0x1","statusOK":true,"to":"0x476059cd57800db8eb88f67c2aa38a6fcf8251e0","transactionHash":"0x18b8c942735e869a42fff06393ebb12824ec44ed030e9e6a579f8f839c59a5de","transactionIndex":0,"transactionIndexRaw":"0x0"}}}]% -``` -This JSON response shows two DLTtxReceipt (Distributed Ledger Technology Transaction Receipt) entities in NGSI-LD format: - -##### First Transaction Receipt -- ID: urn:ngsi-ld:dlttxreceipt:0x8456f94030cc841bc667d76ce563b47e4920a27884bdb9b8fc5e4e9cc2df1913 -- Block Number: 186 (0xba) -- Gas Used: 23,866 -- From Address: 0x34e5b3f990e55d0651b35c817bafb89d2877cb95 -- To Address: 0x476059cd57800db8eb88f67c2aa38a6fcf8251e0 - -##### Second Transaction Receipt -- ID: urn:ngsi-ld:dlttxreceipt:0x18b8c942735e869a42fff06393ebb12824ec44ed030e9e6a579f8f839c59a5de -- Block Number: 190 (0xbe) -- Gas Used: 23,866 -- From Address: 0x34e5b3f990e55d0651b35c817bafb89d2877cb95 -- To Address: 0x476059cd57800db8eb88f67c2aa38a6fcf8251e0 - -Both transactions were successful (statusOK: true). Each receipt contains detailed blockchain information including logs, bloom filters, and raw transaction data. - - diff --git a/src/main/java/validator/ValidationService.java b/validator/ValidationService.java similarity index 100% rename from src/main/java/validator/ValidationService.java rename to validator/ValidationService.java diff --git a/src/main/java/validator/client/ValidationHttpClient.java b/validator/client/ValidationHttpClient.java similarity index 100% rename from src/main/java/validator/client/ValidationHttpClient.java rename to validator/client/ValidationHttpClient.java diff --git a/src/main/java/validator/controller/ValidationController.java b/validator/controller/ValidationController.java similarity index 100% rename from src/main/java/validator/controller/ValidationController.java rename to validator/controller/ValidationController.java diff --git a/src/main/java/validator/model/ValidationInfo.java b/validator/model/ValidationInfo.java similarity index 100% rename from src/main/java/validator/model/ValidationInfo.java rename to validator/model/ValidationInfo.java diff --git a/src/main/java/validator/model/ValidationResult.java b/validator/model/ValidationResult.java similarity index 100% rename from src/main/java/validator/model/ValidationResult.java rename to validator/model/ValidationResult.java From 9cc270d5fd25711e9323d8d1f49bc955141b0130 Mon Sep 17 00:00:00 2001 From: asmataamallah25 <168739895+asmataamallah25@users.noreply.github.com> Date: Tue, 10 Dec 2024 15:26:14 +0100 Subject: [PATCH 05/19] Delete validator/Tests.md - --- validator/Tests.md | 114 --------------------------------------------- 1 file changed, 114 deletions(-) delete mode 100644 validator/Tests.md diff --git a/validator/Tests.md b/validator/Tests.md deleted file mode 100644 index f489740..0000000 --- a/validator/Tests.md +++ /dev/null @@ -1,114 +0,0 @@ -# Canis Major test - -Canis Major serves as a blockchain adaptor within the FIWARE ecosystem, providing secure data persistence across blockchain networks and the Context Broker. The workflow is straightforward: clients submit transactions containing payload data and wallet credentials, which undergo authentication through Wilma PEP Proxy and KeyRock Identity Management. Once validated, the data is stored in the Orion Context Broker and simultaneously processed into a Merkle-Tree structure for blockchain integration. The system then signs the transaction using the provided wallet credentials and submits it to an Oketh-compatible blockchain. Let's explore the practical implementation through a series of test commands. - -After r unning the integartion tests, create an entity by sending this POST request to Canis Major: - -```bash -curl --location 'http://:4000/ngsi-ld/v1/entities/' \ ---header 'Link: ; rel="http://www.w3.org/ns/json-ld#context"; type="application/ld+json"' \ ---header 'Wallet-Type: vault' \ ---header 'Wallet-Token: vault-plaintext-root-token' \ ---header 'Wallet-Address: http://vault:8200/v1/ethereum/accounts/mira' \ ---header 'Content-Type: application/json' \ ---header 'Accept: application/json' \ ---header 'NGSILD-TENANT: orion' \ ---data '{ - "id": "urn:ngsi-ld:Building:farm002", - "type": "Building", - "category": { - "type": "Property", - "value": ["farm"] - }, - "address": { - "type": "Property", - "value": { - "streetAddress": "Großer Stern 1", - "addressRegion": "Berlin", - "addressLocality": "Tiergarten", - "postalCode": "10557" - } - } -}' -``` - -To retrieve the available entity types in the context broker run the following command: -```bash -curl -L 'http://:1026/ngsi-ld/v1/types' \ --H 'Link: ; rel="http://www.w3.org/ns/json-ld#context"; type="application/ld+json"' \ --H 'NGSILD-Tenant: orion' -``` -The command returns the following response, demonstrating the available entity types in the Context Broker: -```bash -{ - "@context":"https://raw.githubusercontent.com/smart-data-models/dataModel.DistributedLedgerTech/master/context.jsonld","id":"urn:ngsi-ld:EntityTypeList:d77ccfa0-b3cd-11ef-ae1b-0242ac120005","type":"EntityTypeList","typeList":["DLTtxReceipt"] -}% -``` -The @context is using a definition on smart data models for a DLTtxReceipt and the NGSILD-Tenant is defined in the start-up of the tests. -To verify which entities are created in the context broker use the following command: - -To verify that the created entities is correctly saved in the context broker having the type DLTtxReceipt, use the following command: - -```bash -curl -L 'http://:1026/ngsi-ld/v1/entities/?type=DLTtxReceipt' \ --H 'Link: ; rel="http://www.w3.org/ns/json-ld#context"; type="application/ld+json"' \ --H 'NGSILD-Tenant: orion' -``` - -To ask Canis Major about receipts for a specific building use the following command: - -```bash -curl -L 'http://:4000/entity/urn:ngsi-ld:Building:farm002' \ --H 'Accept: application/json' -``` - -The output will be similar to following response: - -```bash -{ - "entityId":"urn:ngsi-ld:Building:farm002","txDetails":[ - { - "blockHash":"0x610f07a4b5a87ca71273791fe5d12952fca393a6e3664489f45329bebdb396c3","blockNumber":186,"blockNumberRaw":"0xba","cumulativeGasUsed":23866,"cumulativeGasUsedRaw":"0x5d3a","from":"0x34e5b3f990e55d0651b35c817bafb89d2877cb95","gasUsed":23866,"gasUsedRaw":"0x5d3a","logs":[ - { - "address":"0x476059cd57800db8eb88f67c2aa38a6fcf8251e0","blockHash":"0x610f07a4b5a87ca71273791fe5d12952fca393a6e3664489f45329bebdb396c3","blockNumber":186,"blockNumberRaw":"0xba","data":"0x","logIndex":0,"logIndexRaw":"0x0","removed":false,"topics":["0xa3865c00e01495fc2b86502cae36a4edb139f748682e7d80725a3d6571a482fa","0x9aeb7ab140e1449806e11e52ca85dacb6a028ae5feb8d431e7d927e7971e4d2d","0xb1a82faaa61a7bfbc90c42129b2049d408f20b8f43ae99e8b6aa6cc7e7b0b944"],"transactionHash":"0x8456f94030cc841bc667d76ce563b47e4920a27884bdb9b8fc5e4e9cc2df1913","transactionIndex":0,"transactionIndexRaw":"0x0","type":"mined"}],"logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000008000200000000000000000000010000000000004000000000000000000000000000110000000000000004000000000000000000000000000000000000000000000020000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000020000000000000000000000000000000000000000000000000000000000000000000000000","status":"0x1","statusOK":true,"to":"0x476059cd57800db8eb88f67c2aa38a6fcf8251e0","transactionHash":"0x8456f94030cc841bc667d76ce563b47e4920a27884bdb9b8fc5e4e9cc2df1913","transactionIndexRaw":"0x0"},{ - "blockHash":"0x86082426228ee23ea8927607c7f731194ebc1dc7ca69a6321ff46e91b0fdbe2a","blockNumber":190,"blockNumberRaw":"0xbe","cumulativeGasUsed":23866,"cumulativeGasUsedRaw":"0x5d3a","from":"0x34e5b3f990e55d0651b35c817bafb89d2877cb95","gasUsed":23866,"gasUsedRaw":"0x5d3a","logs":[ - {"address":"0x476059cd57800db8eb88f67c2aa38a6fcf8251e0","blockHash":"0x86082426228ee23ea8927607c7f731194ebc1dc7ca69a6321ff46e91b0fdbe2a","blockNumber":190,"blockNumberRaw":"0xbe","data":"0x","logIndex":0,"logIndexRaw":"0x0","removed":false,"topics":["0xa3865c00e01495fc2b86502cae36a4edb139f748682e7d80725a3d6571a482fa","0x9aeb7ab140e1449806e11e52ca85dacb6a028ae5feb8d431e7d927e7971e4d2d","0xb1a82faaa61a7bfbc90c42129b2049d408f20b8f43ae99e8b6aa6cc7e7b0b944"],"transactionHash":"0x18b8c942735e869a42fff06393ebb12824ec44ed030e9e6a579f8f839c59a5de","transactionIndex":0,"transactionIndexRaw":"0x0","type":"mined"}],"logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000008000200000000000000000000010000000000004000000000000000000000000000110000000000000004000000000000000000000000000000000000000000000020000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000020000000000000000000000000000000000000000000000000000000000000000000000000","status":"0x1","statusOK":true,"to":"0x476059cd57800db8eb88f67c2aa38a6fcf8251e0","transactionHash":"0x18b8c942735e869a42fff06393ebb12824ec44ed030e9e6a579f8f839c59a5de","transactionIndexRaw":"0x0"}] -}% -``` -The JSON structure shows: -- One Entity ID: "urn:ngsi-ld:Building:farm002" -- Two transactions recorded in different blocks: - - First transaction in block 186 (0xba) - - Second transaction in block 190 (0xbe) - -Each transaction contains detailed blockchain information including block hashes, gas usage, logs, and transaction hashes, but they all relate to the same building entity. - -To ask the context brker Orion-LD about the receipt of a specific building use the following command: -```bash -curl -L 'http://:1026/ngsi-ld/v1/entities/?type=DLTtxReceipt&q=refEntity%3D%3D%22urn%3Angsi-ld%3ABuilding%3Afarm002%22&attrs=TxReceipts' \ --H 'Link: ; rel="http://www.w3.org/ns/json-ld#context"; type="application/ld+json"' \ --H 'NGSILD-Tenant: orion' -``` -The outpot will be similar to the following response: -```bash -["@context":"https://raw.githubusercontent.com/smart-data-models/dataModel.DistributedLedgerTech/master/context.jsonld","id":"urn:ngsi-ld:dlttxreceipt:0x8456f94030cc841bc667d76ce563b47e4920a27884bdb9b8fc5e4e9cc2df1913","type":"DLTtxReceipt","TxReceipts":{"type":"Property","value":{"blockHash":"0x610f07a4b5a87ca71273791fe5d12952fca393a6e3664489f45329bebdb396c3","blockNumber":186,"blockNumberRaw":"0xba","cumulativeGasUsed":23866,"cumulativeGasUsedRaw":"0x5d3a","from":"0x34e5b3f990e55d0651b35c817bafb89d2877cb95","gasUsed":23866,"gasUsedRaw":"0x5d3a","logs":[{"address":"0x476059cd57800db8eb88f67c2aa38a6fcf8251e0","blockHash":"0x610f07a4b5a87ca71273791fe5d12952fca393a6e3664489f45329bebdb396c3","blockNumber":186,"blockNumberRaw":"0xba","data":"0x","logIndex":0,"logIndexRaw":"0x0","removed":false,"topics":["0xa3865c00e01495fc2b86502cae36a4edb139f748682e7d80725a3d6571a482fa","0x9aeb7ab140e1449806e11e52ca85dacb6a028ae5feb8d431e7d927e7971e4d2d","0xb1a82faaa61a7bfbc90c42129b2049d408f20b8f43ae99e8b6aa6cc7e7b0b944"],"transactionHash":"0x8456f94030cc841bc667d76ce563b47e4920a27884bdb9b8fc5e4e9cc2df1913","transactionIndex":0,"transactionIndexRaw":"0x0","type":"mined"}],"logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000008000200000000000000000000010000000000004000000000000000000000000000110000000000000004000000000000000000000000000000000000000000000020000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000020000000000000000000000000000000000000000000000000000000000000000000000000","status":"0x1","statusOK":true,"to":"0x476059cd57800db8eb88f67c2aa38a6fcf8251e0","transactionHash":"0x8456f94030cc841bc667d76ce563b47e4920a27884bdb9b8fc5e4e9cc2df1913","transactionIndex":0,"transactionIndexRaw":"0x0"}}},{"@context":"https://raw.githubusercontent.com/smart-data-models/dataModel.DistributedLedgerTech/master/context.jsonld","id":"urn:ngsi-ld:dlttxreceipt:0x18b8c942735e869a42fff06393ebb12824ec44ed030e9e6a579f8f839c59a5de","type":"DLTtxReceipt","TxReceipts":{"type":"Property","value":{"blockHash":"0x86082426228ee23ea8927607c7f731194ebc1dc7ca69a6321ff46e91b0fdbe2a","blockNumber":190,"blockNumberRaw":"0xbe","cumulativeGasUsed":23866,"cumulativeGasUsedRaw":"0x5d3a","from":"0x34e5b3f990e55d0651b35c817bafb89d2877cb95","gasUsed":23866,"gasUsedRaw":"0x5d3a","logs":[{"address":"0x476059cd57800db8eb88f67c2aa38a6fcf8251e0","blockHash":"0x86082426228ee23ea8927607c7f731194ebc1dc7ca69a6321ff46e91b0fdbe2a","blockNumber":190,"blockNumberRaw":"0xbe","data":"0x","logIndex":0,"logIndexRaw":"0x0","removed":false,"topics":["0xa3865c00e01495fc2b86502cae36a4edb139f748682e7d80725a3d6571a482fa","0x9aeb7ab140e1449806e11e52ca85dacb6a028ae5feb8d431e7d927e7971e4d2d","0xb1a82faaa61a7bfbc90c42129b2049d408f20b8f43ae99e8b6aa6cc7e7b0b944"],"transactionHash":"0x18b8c942735e869a42fff06393ebb12824ec44ed030e9e6a579f8f839c59a5de","transactionIndex":0,"transactionIndexRaw":"0x0","type":"mined"}],"logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000008000200000000000000000000010000000000004000000000000000000000000000110000000000000004000000000000000000000000000000000000000000000020000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000020000000000000000000000000000000000000000000000000000000000000000000000000","status":"0x1","statusOK":true,"to":"0x476059cd57800db8eb88f67c2aa38a6fcf8251e0","transactionHash":"0x18b8c942735e869a42fff06393ebb12824ec44ed030e9e6a579f8f839c59a5de","transactionIndex":0,"transactionIndexRaw":"0x0"}}}]% -``` -This JSON response shows two DLTtxReceipt (Distributed Ledger Technology Transaction Receipt) entities in NGSI-LD format: - -##### First Transaction Receipt -- ID: urn:ngsi-ld:dlttxreceipt:0x8456f94030cc841bc667d76ce563b47e4920a27884bdb9b8fc5e4e9cc2df1913 -- Block Number: 186 (0xba) -- Gas Used: 23,866 -- From Address: 0x34e5b3f990e55d0651b35c817bafb89d2877cb95 -- To Address: 0x476059cd57800db8eb88f67c2aa38a6fcf8251e0 - -##### Second Transaction Receipt -- ID: urn:ngsi-ld:dlttxreceipt:0x18b8c942735e869a42fff06393ebb12824ec44ed030e9e6a579f8f839c59a5de -- Block Number: 190 (0xbe) -- Gas Used: 23,866 -- From Address: 0x34e5b3f990e55d0651b35c817bafb89d2877cb95 -- To Address: 0x476059cd57800db8eb88f67c2aa38a6fcf8251e0 - -Both transactions were successful (statusOK: true). Each receipt contains detailed blockchain information including logs, bloom filters, and raw transaction data. - - From e0551ca3ac1fe83df4fa6a48d79d1eeeba3be573 Mon Sep 17 00:00:00 2001 From: asmataamallah25 <168739895+asmataamallah25@users.noreply.github.com> Date: Tue, 10 Dec 2024 15:27:07 +0100 Subject: [PATCH 06/19] Update Tests.md --- validator/Tests.md | 114 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 114 insertions(+) create mode 100644 validator/Tests.md diff --git a/validator/Tests.md b/validator/Tests.md new file mode 100644 index 0000000..f489740 --- /dev/null +++ b/validator/Tests.md @@ -0,0 +1,114 @@ +# Canis Major test + +Canis Major serves as a blockchain adaptor within the FIWARE ecosystem, providing secure data persistence across blockchain networks and the Context Broker. The workflow is straightforward: clients submit transactions containing payload data and wallet credentials, which undergo authentication through Wilma PEP Proxy and KeyRock Identity Management. Once validated, the data is stored in the Orion Context Broker and simultaneously processed into a Merkle-Tree structure for blockchain integration. The system then signs the transaction using the provided wallet credentials and submits it to an Oketh-compatible blockchain. Let's explore the practical implementation through a series of test commands. + +After r unning the integartion tests, create an entity by sending this POST request to Canis Major: + +```bash +curl --location 'http://:4000/ngsi-ld/v1/entities/' \ +--header 'Link: ; rel="http://www.w3.org/ns/json-ld#context"; type="application/ld+json"' \ +--header 'Wallet-Type: vault' \ +--header 'Wallet-Token: vault-plaintext-root-token' \ +--header 'Wallet-Address: http://vault:8200/v1/ethereum/accounts/mira' \ +--header 'Content-Type: application/json' \ +--header 'Accept: application/json' \ +--header 'NGSILD-TENANT: orion' \ +--data '{ + "id": "urn:ngsi-ld:Building:farm002", + "type": "Building", + "category": { + "type": "Property", + "value": ["farm"] + }, + "address": { + "type": "Property", + "value": { + "streetAddress": "Großer Stern 1", + "addressRegion": "Berlin", + "addressLocality": "Tiergarten", + "postalCode": "10557" + } + } +}' +``` + +To retrieve the available entity types in the context broker run the following command: +```bash +curl -L 'http://:1026/ngsi-ld/v1/types' \ +-H 'Link: ; rel="http://www.w3.org/ns/json-ld#context"; type="application/ld+json"' \ +-H 'NGSILD-Tenant: orion' +``` +The command returns the following response, demonstrating the available entity types in the Context Broker: +```bash +{ + "@context":"https://raw.githubusercontent.com/smart-data-models/dataModel.DistributedLedgerTech/master/context.jsonld","id":"urn:ngsi-ld:EntityTypeList:d77ccfa0-b3cd-11ef-ae1b-0242ac120005","type":"EntityTypeList","typeList":["DLTtxReceipt"] +}% +``` +The @context is using a definition on smart data models for a DLTtxReceipt and the NGSILD-Tenant is defined in the start-up of the tests. +To verify which entities are created in the context broker use the following command: + +To verify that the created entities is correctly saved in the context broker having the type DLTtxReceipt, use the following command: + +```bash +curl -L 'http://:1026/ngsi-ld/v1/entities/?type=DLTtxReceipt' \ +-H 'Link: ; rel="http://www.w3.org/ns/json-ld#context"; type="application/ld+json"' \ +-H 'NGSILD-Tenant: orion' +``` + +To ask Canis Major about receipts for a specific building use the following command: + +```bash +curl -L 'http://:4000/entity/urn:ngsi-ld:Building:farm002' \ +-H 'Accept: application/json' +``` + +The output will be similar to following response: + +```bash +{ + "entityId":"urn:ngsi-ld:Building:farm002","txDetails":[ + { + "blockHash":"0x610f07a4b5a87ca71273791fe5d12952fca393a6e3664489f45329bebdb396c3","blockNumber":186,"blockNumberRaw":"0xba","cumulativeGasUsed":23866,"cumulativeGasUsedRaw":"0x5d3a","from":"0x34e5b3f990e55d0651b35c817bafb89d2877cb95","gasUsed":23866,"gasUsedRaw":"0x5d3a","logs":[ + { + "address":"0x476059cd57800db8eb88f67c2aa38a6fcf8251e0","blockHash":"0x610f07a4b5a87ca71273791fe5d12952fca393a6e3664489f45329bebdb396c3","blockNumber":186,"blockNumberRaw":"0xba","data":"0x","logIndex":0,"logIndexRaw":"0x0","removed":false,"topics":["0xa3865c00e01495fc2b86502cae36a4edb139f748682e7d80725a3d6571a482fa","0x9aeb7ab140e1449806e11e52ca85dacb6a028ae5feb8d431e7d927e7971e4d2d","0xb1a82faaa61a7bfbc90c42129b2049d408f20b8f43ae99e8b6aa6cc7e7b0b944"],"transactionHash":"0x8456f94030cc841bc667d76ce563b47e4920a27884bdb9b8fc5e4e9cc2df1913","transactionIndex":0,"transactionIndexRaw":"0x0","type":"mined"}],"logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000008000200000000000000000000010000000000004000000000000000000000000000110000000000000004000000000000000000000000000000000000000000000020000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000020000000000000000000000000000000000000000000000000000000000000000000000000","status":"0x1","statusOK":true,"to":"0x476059cd57800db8eb88f67c2aa38a6fcf8251e0","transactionHash":"0x8456f94030cc841bc667d76ce563b47e4920a27884bdb9b8fc5e4e9cc2df1913","transactionIndexRaw":"0x0"},{ + "blockHash":"0x86082426228ee23ea8927607c7f731194ebc1dc7ca69a6321ff46e91b0fdbe2a","blockNumber":190,"blockNumberRaw":"0xbe","cumulativeGasUsed":23866,"cumulativeGasUsedRaw":"0x5d3a","from":"0x34e5b3f990e55d0651b35c817bafb89d2877cb95","gasUsed":23866,"gasUsedRaw":"0x5d3a","logs":[ + {"address":"0x476059cd57800db8eb88f67c2aa38a6fcf8251e0","blockHash":"0x86082426228ee23ea8927607c7f731194ebc1dc7ca69a6321ff46e91b0fdbe2a","blockNumber":190,"blockNumberRaw":"0xbe","data":"0x","logIndex":0,"logIndexRaw":"0x0","removed":false,"topics":["0xa3865c00e01495fc2b86502cae36a4edb139f748682e7d80725a3d6571a482fa","0x9aeb7ab140e1449806e11e52ca85dacb6a028ae5feb8d431e7d927e7971e4d2d","0xb1a82faaa61a7bfbc90c42129b2049d408f20b8f43ae99e8b6aa6cc7e7b0b944"],"transactionHash":"0x18b8c942735e869a42fff06393ebb12824ec44ed030e9e6a579f8f839c59a5de","transactionIndex":0,"transactionIndexRaw":"0x0","type":"mined"}],"logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000008000200000000000000000000010000000000004000000000000000000000000000110000000000000004000000000000000000000000000000000000000000000020000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000020000000000000000000000000000000000000000000000000000000000000000000000000","status":"0x1","statusOK":true,"to":"0x476059cd57800db8eb88f67c2aa38a6fcf8251e0","transactionHash":"0x18b8c942735e869a42fff06393ebb12824ec44ed030e9e6a579f8f839c59a5de","transactionIndexRaw":"0x0"}] +}% +``` +The JSON structure shows: +- One Entity ID: "urn:ngsi-ld:Building:farm002" +- Two transactions recorded in different blocks: + - First transaction in block 186 (0xba) + - Second transaction in block 190 (0xbe) + +Each transaction contains detailed blockchain information including block hashes, gas usage, logs, and transaction hashes, but they all relate to the same building entity. + +To ask the context brker Orion-LD about the receipt of a specific building use the following command: +```bash +curl -L 'http://:1026/ngsi-ld/v1/entities/?type=DLTtxReceipt&q=refEntity%3D%3D%22urn%3Angsi-ld%3ABuilding%3Afarm002%22&attrs=TxReceipts' \ +-H 'Link: ; rel="http://www.w3.org/ns/json-ld#context"; type="application/ld+json"' \ +-H 'NGSILD-Tenant: orion' +``` +The outpot will be similar to the following response: +```bash +["@context":"https://raw.githubusercontent.com/smart-data-models/dataModel.DistributedLedgerTech/master/context.jsonld","id":"urn:ngsi-ld:dlttxreceipt:0x8456f94030cc841bc667d76ce563b47e4920a27884bdb9b8fc5e4e9cc2df1913","type":"DLTtxReceipt","TxReceipts":{"type":"Property","value":{"blockHash":"0x610f07a4b5a87ca71273791fe5d12952fca393a6e3664489f45329bebdb396c3","blockNumber":186,"blockNumberRaw":"0xba","cumulativeGasUsed":23866,"cumulativeGasUsedRaw":"0x5d3a","from":"0x34e5b3f990e55d0651b35c817bafb89d2877cb95","gasUsed":23866,"gasUsedRaw":"0x5d3a","logs":[{"address":"0x476059cd57800db8eb88f67c2aa38a6fcf8251e0","blockHash":"0x610f07a4b5a87ca71273791fe5d12952fca393a6e3664489f45329bebdb396c3","blockNumber":186,"blockNumberRaw":"0xba","data":"0x","logIndex":0,"logIndexRaw":"0x0","removed":false,"topics":["0xa3865c00e01495fc2b86502cae36a4edb139f748682e7d80725a3d6571a482fa","0x9aeb7ab140e1449806e11e52ca85dacb6a028ae5feb8d431e7d927e7971e4d2d","0xb1a82faaa61a7bfbc90c42129b2049d408f20b8f43ae99e8b6aa6cc7e7b0b944"],"transactionHash":"0x8456f94030cc841bc667d76ce563b47e4920a27884bdb9b8fc5e4e9cc2df1913","transactionIndex":0,"transactionIndexRaw":"0x0","type":"mined"}],"logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000008000200000000000000000000010000000000004000000000000000000000000000110000000000000004000000000000000000000000000000000000000000000020000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000020000000000000000000000000000000000000000000000000000000000000000000000000","status":"0x1","statusOK":true,"to":"0x476059cd57800db8eb88f67c2aa38a6fcf8251e0","transactionHash":"0x8456f94030cc841bc667d76ce563b47e4920a27884bdb9b8fc5e4e9cc2df1913","transactionIndex":0,"transactionIndexRaw":"0x0"}}},{"@context":"https://raw.githubusercontent.com/smart-data-models/dataModel.DistributedLedgerTech/master/context.jsonld","id":"urn:ngsi-ld:dlttxreceipt:0x18b8c942735e869a42fff06393ebb12824ec44ed030e9e6a579f8f839c59a5de","type":"DLTtxReceipt","TxReceipts":{"type":"Property","value":{"blockHash":"0x86082426228ee23ea8927607c7f731194ebc1dc7ca69a6321ff46e91b0fdbe2a","blockNumber":190,"blockNumberRaw":"0xbe","cumulativeGasUsed":23866,"cumulativeGasUsedRaw":"0x5d3a","from":"0x34e5b3f990e55d0651b35c817bafb89d2877cb95","gasUsed":23866,"gasUsedRaw":"0x5d3a","logs":[{"address":"0x476059cd57800db8eb88f67c2aa38a6fcf8251e0","blockHash":"0x86082426228ee23ea8927607c7f731194ebc1dc7ca69a6321ff46e91b0fdbe2a","blockNumber":190,"blockNumberRaw":"0xbe","data":"0x","logIndex":0,"logIndexRaw":"0x0","removed":false,"topics":["0xa3865c00e01495fc2b86502cae36a4edb139f748682e7d80725a3d6571a482fa","0x9aeb7ab140e1449806e11e52ca85dacb6a028ae5feb8d431e7d927e7971e4d2d","0xb1a82faaa61a7bfbc90c42129b2049d408f20b8f43ae99e8b6aa6cc7e7b0b944"],"transactionHash":"0x18b8c942735e869a42fff06393ebb12824ec44ed030e9e6a579f8f839c59a5de","transactionIndex":0,"transactionIndexRaw":"0x0","type":"mined"}],"logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000008000200000000000000000000010000000000004000000000000000000000000000110000000000000004000000000000000000000000000000000000000000000020000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000020000000000000000000000000000000000000000000000000000000000000000000000000","status":"0x1","statusOK":true,"to":"0x476059cd57800db8eb88f67c2aa38a6fcf8251e0","transactionHash":"0x18b8c942735e869a42fff06393ebb12824ec44ed030e9e6a579f8f839c59a5de","transactionIndex":0,"transactionIndexRaw":"0x0"}}}]% +``` +This JSON response shows two DLTtxReceipt (Distributed Ledger Technology Transaction Receipt) entities in NGSI-LD format: + +##### First Transaction Receipt +- ID: urn:ngsi-ld:dlttxreceipt:0x8456f94030cc841bc667d76ce563b47e4920a27884bdb9b8fc5e4e9cc2df1913 +- Block Number: 186 (0xba) +- Gas Used: 23,866 +- From Address: 0x34e5b3f990e55d0651b35c817bafb89d2877cb95 +- To Address: 0x476059cd57800db8eb88f67c2aa38a6fcf8251e0 + +##### Second Transaction Receipt +- ID: urn:ngsi-ld:dlttxreceipt:0x18b8c942735e869a42fff06393ebb12824ec44ed030e9e6a579f8f839c59a5de +- Block Number: 190 (0xbe) +- Gas Used: 23,866 +- From Address: 0x34e5b3f990e55d0651b35c817bafb89d2877cb95 +- To Address: 0x476059cd57800db8eb88f67c2aa38a6fcf8251e0 + +Both transactions were successful (statusOK: true). Each receipt contains detailed blockchain information including logs, bloom filters, and raw transaction data. + + From 8942860e8eba779407086d4265771a03483bede7 Mon Sep 17 00:00:00 2001 From: asmataamallah25 <168739895+asmataamallah25@users.noreply.github.com> Date: Tue, 10 Dec 2024 15:27:49 +0100 Subject: [PATCH 07/19] Delete validator/Tests.md --- validator/Tests.md | 114 --------------------------------------------- 1 file changed, 114 deletions(-) delete mode 100644 validator/Tests.md diff --git a/validator/Tests.md b/validator/Tests.md deleted file mode 100644 index f489740..0000000 --- a/validator/Tests.md +++ /dev/null @@ -1,114 +0,0 @@ -# Canis Major test - -Canis Major serves as a blockchain adaptor within the FIWARE ecosystem, providing secure data persistence across blockchain networks and the Context Broker. The workflow is straightforward: clients submit transactions containing payload data and wallet credentials, which undergo authentication through Wilma PEP Proxy and KeyRock Identity Management. Once validated, the data is stored in the Orion Context Broker and simultaneously processed into a Merkle-Tree structure for blockchain integration. The system then signs the transaction using the provided wallet credentials and submits it to an Oketh-compatible blockchain. Let's explore the practical implementation through a series of test commands. - -After r unning the integartion tests, create an entity by sending this POST request to Canis Major: - -```bash -curl --location 'http://:4000/ngsi-ld/v1/entities/' \ ---header 'Link: ; rel="http://www.w3.org/ns/json-ld#context"; type="application/ld+json"' \ ---header 'Wallet-Type: vault' \ ---header 'Wallet-Token: vault-plaintext-root-token' \ ---header 'Wallet-Address: http://vault:8200/v1/ethereum/accounts/mira' \ ---header 'Content-Type: application/json' \ ---header 'Accept: application/json' \ ---header 'NGSILD-TENANT: orion' \ ---data '{ - "id": "urn:ngsi-ld:Building:farm002", - "type": "Building", - "category": { - "type": "Property", - "value": ["farm"] - }, - "address": { - "type": "Property", - "value": { - "streetAddress": "Großer Stern 1", - "addressRegion": "Berlin", - "addressLocality": "Tiergarten", - "postalCode": "10557" - } - } -}' -``` - -To retrieve the available entity types in the context broker run the following command: -```bash -curl -L 'http://:1026/ngsi-ld/v1/types' \ --H 'Link: ; rel="http://www.w3.org/ns/json-ld#context"; type="application/ld+json"' \ --H 'NGSILD-Tenant: orion' -``` -The command returns the following response, demonstrating the available entity types in the Context Broker: -```bash -{ - "@context":"https://raw.githubusercontent.com/smart-data-models/dataModel.DistributedLedgerTech/master/context.jsonld","id":"urn:ngsi-ld:EntityTypeList:d77ccfa0-b3cd-11ef-ae1b-0242ac120005","type":"EntityTypeList","typeList":["DLTtxReceipt"] -}% -``` -The @context is using a definition on smart data models for a DLTtxReceipt and the NGSILD-Tenant is defined in the start-up of the tests. -To verify which entities are created in the context broker use the following command: - -To verify that the created entities is correctly saved in the context broker having the type DLTtxReceipt, use the following command: - -```bash -curl -L 'http://:1026/ngsi-ld/v1/entities/?type=DLTtxReceipt' \ --H 'Link: ; rel="http://www.w3.org/ns/json-ld#context"; type="application/ld+json"' \ --H 'NGSILD-Tenant: orion' -``` - -To ask Canis Major about receipts for a specific building use the following command: - -```bash -curl -L 'http://:4000/entity/urn:ngsi-ld:Building:farm002' \ --H 'Accept: application/json' -``` - -The output will be similar to following response: - -```bash -{ - "entityId":"urn:ngsi-ld:Building:farm002","txDetails":[ - { - "blockHash":"0x610f07a4b5a87ca71273791fe5d12952fca393a6e3664489f45329bebdb396c3","blockNumber":186,"blockNumberRaw":"0xba","cumulativeGasUsed":23866,"cumulativeGasUsedRaw":"0x5d3a","from":"0x34e5b3f990e55d0651b35c817bafb89d2877cb95","gasUsed":23866,"gasUsedRaw":"0x5d3a","logs":[ - { - "address":"0x476059cd57800db8eb88f67c2aa38a6fcf8251e0","blockHash":"0x610f07a4b5a87ca71273791fe5d12952fca393a6e3664489f45329bebdb396c3","blockNumber":186,"blockNumberRaw":"0xba","data":"0x","logIndex":0,"logIndexRaw":"0x0","removed":false,"topics":["0xa3865c00e01495fc2b86502cae36a4edb139f748682e7d80725a3d6571a482fa","0x9aeb7ab140e1449806e11e52ca85dacb6a028ae5feb8d431e7d927e7971e4d2d","0xb1a82faaa61a7bfbc90c42129b2049d408f20b8f43ae99e8b6aa6cc7e7b0b944"],"transactionHash":"0x8456f94030cc841bc667d76ce563b47e4920a27884bdb9b8fc5e4e9cc2df1913","transactionIndex":0,"transactionIndexRaw":"0x0","type":"mined"}],"logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000008000200000000000000000000010000000000004000000000000000000000000000110000000000000004000000000000000000000000000000000000000000000020000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000020000000000000000000000000000000000000000000000000000000000000000000000000","status":"0x1","statusOK":true,"to":"0x476059cd57800db8eb88f67c2aa38a6fcf8251e0","transactionHash":"0x8456f94030cc841bc667d76ce563b47e4920a27884bdb9b8fc5e4e9cc2df1913","transactionIndexRaw":"0x0"},{ - "blockHash":"0x86082426228ee23ea8927607c7f731194ebc1dc7ca69a6321ff46e91b0fdbe2a","blockNumber":190,"blockNumberRaw":"0xbe","cumulativeGasUsed":23866,"cumulativeGasUsedRaw":"0x5d3a","from":"0x34e5b3f990e55d0651b35c817bafb89d2877cb95","gasUsed":23866,"gasUsedRaw":"0x5d3a","logs":[ - {"address":"0x476059cd57800db8eb88f67c2aa38a6fcf8251e0","blockHash":"0x86082426228ee23ea8927607c7f731194ebc1dc7ca69a6321ff46e91b0fdbe2a","blockNumber":190,"blockNumberRaw":"0xbe","data":"0x","logIndex":0,"logIndexRaw":"0x0","removed":false,"topics":["0xa3865c00e01495fc2b86502cae36a4edb139f748682e7d80725a3d6571a482fa","0x9aeb7ab140e1449806e11e52ca85dacb6a028ae5feb8d431e7d927e7971e4d2d","0xb1a82faaa61a7bfbc90c42129b2049d408f20b8f43ae99e8b6aa6cc7e7b0b944"],"transactionHash":"0x18b8c942735e869a42fff06393ebb12824ec44ed030e9e6a579f8f839c59a5de","transactionIndex":0,"transactionIndexRaw":"0x0","type":"mined"}],"logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000008000200000000000000000000010000000000004000000000000000000000000000110000000000000004000000000000000000000000000000000000000000000020000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000020000000000000000000000000000000000000000000000000000000000000000000000000","status":"0x1","statusOK":true,"to":"0x476059cd57800db8eb88f67c2aa38a6fcf8251e0","transactionHash":"0x18b8c942735e869a42fff06393ebb12824ec44ed030e9e6a579f8f839c59a5de","transactionIndexRaw":"0x0"}] -}% -``` -The JSON structure shows: -- One Entity ID: "urn:ngsi-ld:Building:farm002" -- Two transactions recorded in different blocks: - - First transaction in block 186 (0xba) - - Second transaction in block 190 (0xbe) - -Each transaction contains detailed blockchain information including block hashes, gas usage, logs, and transaction hashes, but they all relate to the same building entity. - -To ask the context brker Orion-LD about the receipt of a specific building use the following command: -```bash -curl -L 'http://:1026/ngsi-ld/v1/entities/?type=DLTtxReceipt&q=refEntity%3D%3D%22urn%3Angsi-ld%3ABuilding%3Afarm002%22&attrs=TxReceipts' \ --H 'Link: ; rel="http://www.w3.org/ns/json-ld#context"; type="application/ld+json"' \ --H 'NGSILD-Tenant: orion' -``` -The outpot will be similar to the following response: -```bash -["@context":"https://raw.githubusercontent.com/smart-data-models/dataModel.DistributedLedgerTech/master/context.jsonld","id":"urn:ngsi-ld:dlttxreceipt:0x8456f94030cc841bc667d76ce563b47e4920a27884bdb9b8fc5e4e9cc2df1913","type":"DLTtxReceipt","TxReceipts":{"type":"Property","value":{"blockHash":"0x610f07a4b5a87ca71273791fe5d12952fca393a6e3664489f45329bebdb396c3","blockNumber":186,"blockNumberRaw":"0xba","cumulativeGasUsed":23866,"cumulativeGasUsedRaw":"0x5d3a","from":"0x34e5b3f990e55d0651b35c817bafb89d2877cb95","gasUsed":23866,"gasUsedRaw":"0x5d3a","logs":[{"address":"0x476059cd57800db8eb88f67c2aa38a6fcf8251e0","blockHash":"0x610f07a4b5a87ca71273791fe5d12952fca393a6e3664489f45329bebdb396c3","blockNumber":186,"blockNumberRaw":"0xba","data":"0x","logIndex":0,"logIndexRaw":"0x0","removed":false,"topics":["0xa3865c00e01495fc2b86502cae36a4edb139f748682e7d80725a3d6571a482fa","0x9aeb7ab140e1449806e11e52ca85dacb6a028ae5feb8d431e7d927e7971e4d2d","0xb1a82faaa61a7bfbc90c42129b2049d408f20b8f43ae99e8b6aa6cc7e7b0b944"],"transactionHash":"0x8456f94030cc841bc667d76ce563b47e4920a27884bdb9b8fc5e4e9cc2df1913","transactionIndex":0,"transactionIndexRaw":"0x0","type":"mined"}],"logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000008000200000000000000000000010000000000004000000000000000000000000000110000000000000004000000000000000000000000000000000000000000000020000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000020000000000000000000000000000000000000000000000000000000000000000000000000","status":"0x1","statusOK":true,"to":"0x476059cd57800db8eb88f67c2aa38a6fcf8251e0","transactionHash":"0x8456f94030cc841bc667d76ce563b47e4920a27884bdb9b8fc5e4e9cc2df1913","transactionIndex":0,"transactionIndexRaw":"0x0"}}},{"@context":"https://raw.githubusercontent.com/smart-data-models/dataModel.DistributedLedgerTech/master/context.jsonld","id":"urn:ngsi-ld:dlttxreceipt:0x18b8c942735e869a42fff06393ebb12824ec44ed030e9e6a579f8f839c59a5de","type":"DLTtxReceipt","TxReceipts":{"type":"Property","value":{"blockHash":"0x86082426228ee23ea8927607c7f731194ebc1dc7ca69a6321ff46e91b0fdbe2a","blockNumber":190,"blockNumberRaw":"0xbe","cumulativeGasUsed":23866,"cumulativeGasUsedRaw":"0x5d3a","from":"0x34e5b3f990e55d0651b35c817bafb89d2877cb95","gasUsed":23866,"gasUsedRaw":"0x5d3a","logs":[{"address":"0x476059cd57800db8eb88f67c2aa38a6fcf8251e0","blockHash":"0x86082426228ee23ea8927607c7f731194ebc1dc7ca69a6321ff46e91b0fdbe2a","blockNumber":190,"blockNumberRaw":"0xbe","data":"0x","logIndex":0,"logIndexRaw":"0x0","removed":false,"topics":["0xa3865c00e01495fc2b86502cae36a4edb139f748682e7d80725a3d6571a482fa","0x9aeb7ab140e1449806e11e52ca85dacb6a028ae5feb8d431e7d927e7971e4d2d","0xb1a82faaa61a7bfbc90c42129b2049d408f20b8f43ae99e8b6aa6cc7e7b0b944"],"transactionHash":"0x18b8c942735e869a42fff06393ebb12824ec44ed030e9e6a579f8f839c59a5de","transactionIndex":0,"transactionIndexRaw":"0x0","type":"mined"}],"logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000008000200000000000000000000010000000000004000000000000000000000000000110000000000000004000000000000000000000000000000000000000000000020000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000020000000000000000000000000000000000000000000000000000000000000000000000000","status":"0x1","statusOK":true,"to":"0x476059cd57800db8eb88f67c2aa38a6fcf8251e0","transactionHash":"0x18b8c942735e869a42fff06393ebb12824ec44ed030e9e6a579f8f839c59a5de","transactionIndex":0,"transactionIndexRaw":"0x0"}}}]% -``` -This JSON response shows two DLTtxReceipt (Distributed Ledger Technology Transaction Receipt) entities in NGSI-LD format: - -##### First Transaction Receipt -- ID: urn:ngsi-ld:dlttxreceipt:0x8456f94030cc841bc667d76ce563b47e4920a27884bdb9b8fc5e4e9cc2df1913 -- Block Number: 186 (0xba) -- Gas Used: 23,866 -- From Address: 0x34e5b3f990e55d0651b35c817bafb89d2877cb95 -- To Address: 0x476059cd57800db8eb88f67c2aa38a6fcf8251e0 - -##### Second Transaction Receipt -- ID: urn:ngsi-ld:dlttxreceipt:0x18b8c942735e869a42fff06393ebb12824ec44ed030e9e6a579f8f839c59a5de -- Block Number: 190 (0xbe) -- Gas Used: 23,866 -- From Address: 0x34e5b3f990e55d0651b35c817bafb89d2877cb95 -- To Address: 0x476059cd57800db8eb88f67c2aa38a6fcf8251e0 - -Both transactions were successful (statusOK: true). Each receipt contains detailed blockchain information including logs, bloom filters, and raw transaction data. - - From fec88d06d9c3a998673694671ddaec04ab61913e Mon Sep 17 00:00:00 2001 From: asmataamallah25 <168739895+asmataamallah25@users.noreply.github.com> Date: Tue, 10 Dec 2024 15:29:55 +0100 Subject: [PATCH 08/19] Upload the most recent version of Tests.md --- validator/Tests.md | 208 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 208 insertions(+) create mode 100644 validator/Tests.md diff --git a/validator/Tests.md b/validator/Tests.md new file mode 100644 index 0000000..e2ac1dd --- /dev/null +++ b/validator/Tests.md @@ -0,0 +1,208 @@ +# Canis Major test + +Canis Major serves as a blockchain adaptor within the FIWARE ecosystem, providing secure data persistence across blockchain networks and the Context Broker. The workflow is straightforward: + - clients submit transactions containing payload data and wallet credentials. + - These data are authenticated through a PEP Proxy and KeyRock Identity Management. + - Once validated, the data is stored in an ETSI Broker (e.g., Orion-LD Broker) and simultaneously processed into a Merkle tree structure for blockchain integration. + - The system then signs the transaction using the provided wallet credentials and submits it to an Oketh-compatible blockchain. + + ### Integradtion tests + Let's explore the practical implementation through a series of test commands. First, it is needed to execute the integration tests, by running the following commands: + + ```shell + cd it + docker-compose -f docker-compose/docker-compose-env.yaml -f docker-compose/docker-compose-java.yaml up + NGSI_ADDRESS=localhost:4000 mvn clean test + ``` + +### Entity creation +After running the integartion tests, create an entity by sending this POST request to Canis Major: + +```shell +curl --location 'http://localhost:4000/ngsi-ld/v1/entities/' \ +--header 'Link: ; rel="http://www.w3.org/ns/json-ld#context"; type="application/ld+json"' \ +--header 'Wallet-Type: vault' \ +--header 'Wallet-Token: vault-plaintext-root-token' \ +--header 'Wallet-Address: http://vault:8200/v1/ethereum/accounts/mira' \ +--header 'Content-Type: application/json' \ +--header 'Accept: application/json' \ +--header 'NGSILD-TENANT: orion' \ +--data '{ + "id": "urn:ngsi-ld:Building:warehouse001", + "type": "Building", + "category": { + "type": "Property", + "value": ["warehouse"] + }, + "address": { + "type": "Property", + "value": { + "streetAddress": "Alexanderplatz 2", + "addressRegion": "Berlin", + "addressLocality": "Mitte", + "postalCode": "10178" + } + } +}' +``` + +The headers included in the HTTP request are: +##### Wallet Headers +- Wallet-Type: Specifies the wallet service type as "vault" +- Wallet-Token: Provides authentication token for the vault service +- Wallet-Address: Points to the Ethereum accoun + +##### Content Headers +- Content-Type: Declares the request body format as JSON +- Accept: Indicates the expected response format as JSON + +### Retrieve the entity types in the Context broker +To retrieve the available entity types in the context broker run the following command: + +```shell +curl -L 'http://localhost:1026/ngsi-ld/v1/types' \ +-H 'Link: ; rel="http://www.w3.org/ns/json-ld#context"; type="application/ld+json"' \ +-H 'NGSILD-Tenant: orion' +``` +The command returns the following response, demonstrating the available entity types in the Context Broker: +```json +{ + "@context":"https://raw.githubusercontent.com/smart-data-models/dataModel.DistributedLedgerTech/master/context.jsonld","id":"urn:ngsi-ld:EntityTypeList:d77ccfa0-b3cd-11ef-ae1b-0242ac120005","type":"EntityTypeList","typeList":["DLTtxReceipt"] +} +``` +The @context is using a definition on smart data models for a DLTtxReceipt and the NGSILD-Tenant is defined in the start-up of the tests. + +### Retrieve data from Canis Major +To retrieve detailed receipt information for a specific building from the Canis Major, use the following HTTP request: + +```shell +curl -L 'http://localhost:4000/ngsi-ld/v1/entities/urn:ngsi-ld:Building:warehouse001' \ +-H 'Link: ; rel="http://www.w3.org/ns/json-ld#context"; type="application/ld+json"' \ +-H 'Accept: application/json' | jq '.' +``` + +The output will be similar to following response: + +```json +{ + "blockHash": "0x619433204be327dda0d4722482e44310d2f3807e1a23b1fc097ca809f7afb421", + "blockNumber": 193, + "blockNumberRaw": "0xc1", + "cumulativeGasUsed": 23866, + "cumulativeGasUsedRaw": "0x5d3a", + "from": "0xd9fe663797b75d0b3897d55d35e0b4e72307a63f", + "gasUsed": 23866, + "gasUsedRaw": "0x5d3a", + "logs": [ + { + "address": "0x476059cd57800db8eb88f67c2aa38a6fcf8251e0", + "blockHash": "0x619433204be327dda0d4722482e44310d2f3807e1a23b1fc097ca809f7afb421", + "blockNumber": 193, + "blockNumberRaw": "0xc1", + "data": "0x", + "logIndex": 0, + "logIndexRaw": "0x0", + "removed": false, + "topics": [ + "0xa3865c00e01495fc2b86502cae36a4edb139f748682e7d80725a3d6571a482fa", + "0xf85c55023889d6ec0b723c8220471171435040e275059f8e222dfa57a50f0dd5", + "0x33868c71f7186ea974685b553d8eee61eb1e95e0a2c70ed54fcd13db67920f74" + ], + "transactionHash": "0x83f05282a30f77dcfe52ca029b10ba80aac5dad5cc46f5dc437b79e860e4dd65", + "transactionIndex": 0, + "transactionIndexRaw": "0x0", + "type": "mined" + } + ], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000020000000000000008000200000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000004000000000000000000020000000000000000000000000000000000000000000000000010000100000000000000000000000000000000000000000000000000008000000000000000000000420000000000000000000000000000000000000000000000000000000000000000000000000", + "status": "0x1", + "statusOK": true, + "to": "0x476059cd57800db8eb88f67c2aa38a6fcf8251e0", + "transactionHash": "0x83f05282a30f77dcfe52ca029b10ba80aac5dad5cc46f5dc437b79e860e4dd65", + "transactionIndexRaw": "0x0" +} +``` + +The JSON structure shows a blockchain transaction receipt with the following details: +##### Transaction Information +- Block Number: 193 (0xc1) +- Transaction Status: Successful (statusOK: true) +- Transaction Hash: 0x83f05282a30f77dcfe52ca029b10ba80aac5dad5cc46f5dc437b79e860e4dd65 +##### Transaction Participants +- From Address: 0xd9fe663797b75d0b3897d55d35e0b4e72307a63f +- To Address: 0x476059cd57800db8eb88f67c2aa38a6fcf8251e0 + +### Retrieve data from the context broker +To retrieve specific DLT transaction receipts from the the context broker, we'll use an NGSI-LD query that filters entities by type and property values. The query targets entities of type DLTtxReceipt and filters them based on the refEntity property matching the Building entity "urn:ngsi-ld:Building:warehouse001". The attrs=TxReceipts parameter in the NGSI-LD query acts as a data filter to limit the response to only include the TxReceipts property, excluding all other properties of the entity and reducing response payload size. + +```shell +curl -L 'http://localhost:1026/ngsi-ld/v1/entities/?type=DLTtxReceipt&q=refEntity%3D%3D%22urn%3Angsi-ld%3ABuilding%3Awarehouse001%22&attrs=TxReceipts' \ +-H 'Link: ; rel="http://www.w3.org/ns/json-ld#context"; type="application/ld+json"' \ +-H 'NGSILD-Tenant: orion' +``` + +The outpot will be similar to the following json response: + +```json +{ + "@context": "https://raw.githubusercontent.com/smart-data-models/dataModel.DistributedLedgerTech/master/context.jsonld", + "id": "urn:ngsi-ld:dlttxreceipt:0xa46d2e3b190d36fbb8af5d0a1c212d8036cf007e6ec4d1309904e052d25e5499", + "type": "DLTtxReceipt", + "TxReceipts": { + "type": "Property", + "value": { + "blockHash": "0x2022f801a1c094bd3a893ac6f3087c17bb9c673a6b5663f353eded030e1e7161", + "blockNumber": 192, + "blockNumberRaw": "0xc0", + "cumulativeGasUsed": 23866, + "cumulativeGasUsedRaw": "0x5d3a", + "from": "0x34e5b3f990e55d0651b35c817bafb89d2877cb95", + "gasUsed": 23866, + "gasUsedRaw": "0x5d3a", + "logs": [ + { + "address": "0x476059cd57800db8eb88f67c2aa38a6fcf8251e0", + "blockHash": "0x2022f801a1c094bd3a893ac6f3087c17bb9c673a6b5663f353eded030e1e7161", + "blockNumber": 192, + "blockNumberRaw": "0xc0", + "data": "0x", + "logIndex": 0, + "logIndexRaw": "0x0", + "removed": false, + "topics": [ + "0xa3865c00e01495fc2b86502cae36a4edb139f748682e7d80725a3d6571a482fa", + "0xf85c55023889d6ec0b723c8220471171435040e275059f8e222dfa57a50f0dd5", + "0xefc7a4d4c2393e9b62ac4b93b7d199c71e0bb103fdb00fc1b37d7949ad886ddd" + ], + "transactionHash": "0xa46d2e3b190d36fbb8af5d0a1c212d8036cf007e6ec4d1309904e052d25e5499", + "transactionIndex": 0, + "transactionIndexRaw": "0x0", + "type": "mined" + } + ], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000020000000000000008000200000000000000000000000000000000000000000000000100000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000008000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000420000000000000000000000000000000000000000200000000000000000000000000000000", + "status": "0x1", + "statusOK": true, + "to": "0x476059cd57800db8eb88f67c2aa38a6fcf8251e0", + "transactionHash": "0xa46d2e3b190d36fbb8af5d0a1c212d8036cf007e6ec4d1309904e052d25e5499", + "transactionIndex": 0, + "transactionIndexRaw": "0x0" + } + } +} +``` + +This JSON response shows a DLTtxReceipt (Distributed Ledger Technology Transaction Receipt) entity in NGSI-LD format: + +##### Transaction Details + +- Entity ID: `urn:ngsi-ld:dlttxreceipt:0xa46d2e3b190d36fbb8af5d0a1c212d8036cf007e6ec4d1309904e052d25e5499` +- Block Number: 192 (0xc0) +- Transaction Status: Successful (statusOK: true) +- Block Hash: `0x2022f801a1c094bd3a893ac6f3087c17bb9c673a6b5663f353eded030e1e7161` +- Transaction Hash: `0xa46d2e3b190d36fbb8af5d0a1c212d8036cf007e6ec4d1309904e052d25e5499` + +##### Transaction Participants +- From Address: `0x34e5b3f990e55d0651b35c817bafb89d2877cb95` +- To Address: `0x476059cd57800db8eb88f67c2aa38a6fcf8251e0` + From ee5749ec87a3c1e0efaac18e4a607381249bcdf3 Mon Sep 17 00:00:00 2001 From: asmataamallah25 <168739895+asmataamallah25@users.noreply.github.com> Date: Tue, 10 Dec 2024 15:36:26 +0100 Subject: [PATCH 09/19] Delete validator/Tests.md --- validator/Tests.md | 208 --------------------------------------------- 1 file changed, 208 deletions(-) delete mode 100644 validator/Tests.md diff --git a/validator/Tests.md b/validator/Tests.md deleted file mode 100644 index e2ac1dd..0000000 --- a/validator/Tests.md +++ /dev/null @@ -1,208 +0,0 @@ -# Canis Major test - -Canis Major serves as a blockchain adaptor within the FIWARE ecosystem, providing secure data persistence across blockchain networks and the Context Broker. The workflow is straightforward: - - clients submit transactions containing payload data and wallet credentials. - - These data are authenticated through a PEP Proxy and KeyRock Identity Management. - - Once validated, the data is stored in an ETSI Broker (e.g., Orion-LD Broker) and simultaneously processed into a Merkle tree structure for blockchain integration. - - The system then signs the transaction using the provided wallet credentials and submits it to an Oketh-compatible blockchain. - - ### Integradtion tests - Let's explore the practical implementation through a series of test commands. First, it is needed to execute the integration tests, by running the following commands: - - ```shell - cd it - docker-compose -f docker-compose/docker-compose-env.yaml -f docker-compose/docker-compose-java.yaml up - NGSI_ADDRESS=localhost:4000 mvn clean test - ``` - -### Entity creation -After running the integartion tests, create an entity by sending this POST request to Canis Major: - -```shell -curl --location 'http://localhost:4000/ngsi-ld/v1/entities/' \ ---header 'Link: ; rel="http://www.w3.org/ns/json-ld#context"; type="application/ld+json"' \ ---header 'Wallet-Type: vault' \ ---header 'Wallet-Token: vault-plaintext-root-token' \ ---header 'Wallet-Address: http://vault:8200/v1/ethereum/accounts/mira' \ ---header 'Content-Type: application/json' \ ---header 'Accept: application/json' \ ---header 'NGSILD-TENANT: orion' \ ---data '{ - "id": "urn:ngsi-ld:Building:warehouse001", - "type": "Building", - "category": { - "type": "Property", - "value": ["warehouse"] - }, - "address": { - "type": "Property", - "value": { - "streetAddress": "Alexanderplatz 2", - "addressRegion": "Berlin", - "addressLocality": "Mitte", - "postalCode": "10178" - } - } -}' -``` - -The headers included in the HTTP request are: -##### Wallet Headers -- Wallet-Type: Specifies the wallet service type as "vault" -- Wallet-Token: Provides authentication token for the vault service -- Wallet-Address: Points to the Ethereum accoun - -##### Content Headers -- Content-Type: Declares the request body format as JSON -- Accept: Indicates the expected response format as JSON - -### Retrieve the entity types in the Context broker -To retrieve the available entity types in the context broker run the following command: - -```shell -curl -L 'http://localhost:1026/ngsi-ld/v1/types' \ --H 'Link: ; rel="http://www.w3.org/ns/json-ld#context"; type="application/ld+json"' \ --H 'NGSILD-Tenant: orion' -``` -The command returns the following response, demonstrating the available entity types in the Context Broker: -```json -{ - "@context":"https://raw.githubusercontent.com/smart-data-models/dataModel.DistributedLedgerTech/master/context.jsonld","id":"urn:ngsi-ld:EntityTypeList:d77ccfa0-b3cd-11ef-ae1b-0242ac120005","type":"EntityTypeList","typeList":["DLTtxReceipt"] -} -``` -The @context is using a definition on smart data models for a DLTtxReceipt and the NGSILD-Tenant is defined in the start-up of the tests. - -### Retrieve data from Canis Major -To retrieve detailed receipt information for a specific building from the Canis Major, use the following HTTP request: - -```shell -curl -L 'http://localhost:4000/ngsi-ld/v1/entities/urn:ngsi-ld:Building:warehouse001' \ --H 'Link: ; rel="http://www.w3.org/ns/json-ld#context"; type="application/ld+json"' \ --H 'Accept: application/json' | jq '.' -``` - -The output will be similar to following response: - -```json -{ - "blockHash": "0x619433204be327dda0d4722482e44310d2f3807e1a23b1fc097ca809f7afb421", - "blockNumber": 193, - "blockNumberRaw": "0xc1", - "cumulativeGasUsed": 23866, - "cumulativeGasUsedRaw": "0x5d3a", - "from": "0xd9fe663797b75d0b3897d55d35e0b4e72307a63f", - "gasUsed": 23866, - "gasUsedRaw": "0x5d3a", - "logs": [ - { - "address": "0x476059cd57800db8eb88f67c2aa38a6fcf8251e0", - "blockHash": "0x619433204be327dda0d4722482e44310d2f3807e1a23b1fc097ca809f7afb421", - "blockNumber": 193, - "blockNumberRaw": "0xc1", - "data": "0x", - "logIndex": 0, - "logIndexRaw": "0x0", - "removed": false, - "topics": [ - "0xa3865c00e01495fc2b86502cae36a4edb139f748682e7d80725a3d6571a482fa", - "0xf85c55023889d6ec0b723c8220471171435040e275059f8e222dfa57a50f0dd5", - "0x33868c71f7186ea974685b553d8eee61eb1e95e0a2c70ed54fcd13db67920f74" - ], - "transactionHash": "0x83f05282a30f77dcfe52ca029b10ba80aac5dad5cc46f5dc437b79e860e4dd65", - "transactionIndex": 0, - "transactionIndexRaw": "0x0", - "type": "mined" - } - ], - "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000020000000000000008000200000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000004000000000000000000020000000000000000000000000000000000000000000000000010000100000000000000000000000000000000000000000000000000008000000000000000000000420000000000000000000000000000000000000000000000000000000000000000000000000", - "status": "0x1", - "statusOK": true, - "to": "0x476059cd57800db8eb88f67c2aa38a6fcf8251e0", - "transactionHash": "0x83f05282a30f77dcfe52ca029b10ba80aac5dad5cc46f5dc437b79e860e4dd65", - "transactionIndexRaw": "0x0" -} -``` - -The JSON structure shows a blockchain transaction receipt with the following details: -##### Transaction Information -- Block Number: 193 (0xc1) -- Transaction Status: Successful (statusOK: true) -- Transaction Hash: 0x83f05282a30f77dcfe52ca029b10ba80aac5dad5cc46f5dc437b79e860e4dd65 -##### Transaction Participants -- From Address: 0xd9fe663797b75d0b3897d55d35e0b4e72307a63f -- To Address: 0x476059cd57800db8eb88f67c2aa38a6fcf8251e0 - -### Retrieve data from the context broker -To retrieve specific DLT transaction receipts from the the context broker, we'll use an NGSI-LD query that filters entities by type and property values. The query targets entities of type DLTtxReceipt and filters them based on the refEntity property matching the Building entity "urn:ngsi-ld:Building:warehouse001". The attrs=TxReceipts parameter in the NGSI-LD query acts as a data filter to limit the response to only include the TxReceipts property, excluding all other properties of the entity and reducing response payload size. - -```shell -curl -L 'http://localhost:1026/ngsi-ld/v1/entities/?type=DLTtxReceipt&q=refEntity%3D%3D%22urn%3Angsi-ld%3ABuilding%3Awarehouse001%22&attrs=TxReceipts' \ --H 'Link: ; rel="http://www.w3.org/ns/json-ld#context"; type="application/ld+json"' \ --H 'NGSILD-Tenant: orion' -``` - -The outpot will be similar to the following json response: - -```json -{ - "@context": "https://raw.githubusercontent.com/smart-data-models/dataModel.DistributedLedgerTech/master/context.jsonld", - "id": "urn:ngsi-ld:dlttxreceipt:0xa46d2e3b190d36fbb8af5d0a1c212d8036cf007e6ec4d1309904e052d25e5499", - "type": "DLTtxReceipt", - "TxReceipts": { - "type": "Property", - "value": { - "blockHash": "0x2022f801a1c094bd3a893ac6f3087c17bb9c673a6b5663f353eded030e1e7161", - "blockNumber": 192, - "blockNumberRaw": "0xc0", - "cumulativeGasUsed": 23866, - "cumulativeGasUsedRaw": "0x5d3a", - "from": "0x34e5b3f990e55d0651b35c817bafb89d2877cb95", - "gasUsed": 23866, - "gasUsedRaw": "0x5d3a", - "logs": [ - { - "address": "0x476059cd57800db8eb88f67c2aa38a6fcf8251e0", - "blockHash": "0x2022f801a1c094bd3a893ac6f3087c17bb9c673a6b5663f353eded030e1e7161", - "blockNumber": 192, - "blockNumberRaw": "0xc0", - "data": "0x", - "logIndex": 0, - "logIndexRaw": "0x0", - "removed": false, - "topics": [ - "0xa3865c00e01495fc2b86502cae36a4edb139f748682e7d80725a3d6571a482fa", - "0xf85c55023889d6ec0b723c8220471171435040e275059f8e222dfa57a50f0dd5", - "0xefc7a4d4c2393e9b62ac4b93b7d199c71e0bb103fdb00fc1b37d7949ad886ddd" - ], - "transactionHash": "0xa46d2e3b190d36fbb8af5d0a1c212d8036cf007e6ec4d1309904e052d25e5499", - "transactionIndex": 0, - "transactionIndexRaw": "0x0", - "type": "mined" - } - ], - "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000020000000000000008000200000000000000000000000000000000000000000000000100000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000008000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000420000000000000000000000000000000000000000200000000000000000000000000000000", - "status": "0x1", - "statusOK": true, - "to": "0x476059cd57800db8eb88f67c2aa38a6fcf8251e0", - "transactionHash": "0xa46d2e3b190d36fbb8af5d0a1c212d8036cf007e6ec4d1309904e052d25e5499", - "transactionIndex": 0, - "transactionIndexRaw": "0x0" - } - } -} -``` - -This JSON response shows a DLTtxReceipt (Distributed Ledger Technology Transaction Receipt) entity in NGSI-LD format: - -##### Transaction Details - -- Entity ID: `urn:ngsi-ld:dlttxreceipt:0xa46d2e3b190d36fbb8af5d0a1c212d8036cf007e6ec4d1309904e052d25e5499` -- Block Number: 192 (0xc0) -- Transaction Status: Successful (statusOK: true) -- Block Hash: `0x2022f801a1c094bd3a893ac6f3087c17bb9c673a6b5663f353eded030e1e7161` -- Transaction Hash: `0xa46d2e3b190d36fbb8af5d0a1c212d8036cf007e6ec4d1309904e052d25e5499` - -##### Transaction Participants -- From Address: `0x34e5b3f990e55d0651b35c817bafb89d2877cb95` -- To Address: `0x476059cd57800db8eb88f67c2aa38a6fcf8251e0` - From 4ba7cb35a91ae9d0ca7a1a6f20b366e7e8ec1370 Mon Sep 17 00:00:00 2001 From: asmataamallah25 <168739895+asmataamallah25@users.noreply.github.com> Date: Tue, 10 Dec 2024 15:37:25 +0100 Subject: [PATCH 10/19] Rename the file as README.md --- validator/README.md | 208 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 208 insertions(+) create mode 100644 validator/README.md diff --git a/validator/README.md b/validator/README.md new file mode 100644 index 0000000..e2ac1dd --- /dev/null +++ b/validator/README.md @@ -0,0 +1,208 @@ +# Canis Major test + +Canis Major serves as a blockchain adaptor within the FIWARE ecosystem, providing secure data persistence across blockchain networks and the Context Broker. The workflow is straightforward: + - clients submit transactions containing payload data and wallet credentials. + - These data are authenticated through a PEP Proxy and KeyRock Identity Management. + - Once validated, the data is stored in an ETSI Broker (e.g., Orion-LD Broker) and simultaneously processed into a Merkle tree structure for blockchain integration. + - The system then signs the transaction using the provided wallet credentials and submits it to an Oketh-compatible blockchain. + + ### Integradtion tests + Let's explore the practical implementation through a series of test commands. First, it is needed to execute the integration tests, by running the following commands: + + ```shell + cd it + docker-compose -f docker-compose/docker-compose-env.yaml -f docker-compose/docker-compose-java.yaml up + NGSI_ADDRESS=localhost:4000 mvn clean test + ``` + +### Entity creation +After running the integartion tests, create an entity by sending this POST request to Canis Major: + +```shell +curl --location 'http://localhost:4000/ngsi-ld/v1/entities/' \ +--header 'Link: ; rel="http://www.w3.org/ns/json-ld#context"; type="application/ld+json"' \ +--header 'Wallet-Type: vault' \ +--header 'Wallet-Token: vault-plaintext-root-token' \ +--header 'Wallet-Address: http://vault:8200/v1/ethereum/accounts/mira' \ +--header 'Content-Type: application/json' \ +--header 'Accept: application/json' \ +--header 'NGSILD-TENANT: orion' \ +--data '{ + "id": "urn:ngsi-ld:Building:warehouse001", + "type": "Building", + "category": { + "type": "Property", + "value": ["warehouse"] + }, + "address": { + "type": "Property", + "value": { + "streetAddress": "Alexanderplatz 2", + "addressRegion": "Berlin", + "addressLocality": "Mitte", + "postalCode": "10178" + } + } +}' +``` + +The headers included in the HTTP request are: +##### Wallet Headers +- Wallet-Type: Specifies the wallet service type as "vault" +- Wallet-Token: Provides authentication token for the vault service +- Wallet-Address: Points to the Ethereum accoun + +##### Content Headers +- Content-Type: Declares the request body format as JSON +- Accept: Indicates the expected response format as JSON + +### Retrieve the entity types in the Context broker +To retrieve the available entity types in the context broker run the following command: + +```shell +curl -L 'http://localhost:1026/ngsi-ld/v1/types' \ +-H 'Link: ; rel="http://www.w3.org/ns/json-ld#context"; type="application/ld+json"' \ +-H 'NGSILD-Tenant: orion' +``` +The command returns the following response, demonstrating the available entity types in the Context Broker: +```json +{ + "@context":"https://raw.githubusercontent.com/smart-data-models/dataModel.DistributedLedgerTech/master/context.jsonld","id":"urn:ngsi-ld:EntityTypeList:d77ccfa0-b3cd-11ef-ae1b-0242ac120005","type":"EntityTypeList","typeList":["DLTtxReceipt"] +} +``` +The @context is using a definition on smart data models for a DLTtxReceipt and the NGSILD-Tenant is defined in the start-up of the tests. + +### Retrieve data from Canis Major +To retrieve detailed receipt information for a specific building from the Canis Major, use the following HTTP request: + +```shell +curl -L 'http://localhost:4000/ngsi-ld/v1/entities/urn:ngsi-ld:Building:warehouse001' \ +-H 'Link: ; rel="http://www.w3.org/ns/json-ld#context"; type="application/ld+json"' \ +-H 'Accept: application/json' | jq '.' +``` + +The output will be similar to following response: + +```json +{ + "blockHash": "0x619433204be327dda0d4722482e44310d2f3807e1a23b1fc097ca809f7afb421", + "blockNumber": 193, + "blockNumberRaw": "0xc1", + "cumulativeGasUsed": 23866, + "cumulativeGasUsedRaw": "0x5d3a", + "from": "0xd9fe663797b75d0b3897d55d35e0b4e72307a63f", + "gasUsed": 23866, + "gasUsedRaw": "0x5d3a", + "logs": [ + { + "address": "0x476059cd57800db8eb88f67c2aa38a6fcf8251e0", + "blockHash": "0x619433204be327dda0d4722482e44310d2f3807e1a23b1fc097ca809f7afb421", + "blockNumber": 193, + "blockNumberRaw": "0xc1", + "data": "0x", + "logIndex": 0, + "logIndexRaw": "0x0", + "removed": false, + "topics": [ + "0xa3865c00e01495fc2b86502cae36a4edb139f748682e7d80725a3d6571a482fa", + "0xf85c55023889d6ec0b723c8220471171435040e275059f8e222dfa57a50f0dd5", + "0x33868c71f7186ea974685b553d8eee61eb1e95e0a2c70ed54fcd13db67920f74" + ], + "transactionHash": "0x83f05282a30f77dcfe52ca029b10ba80aac5dad5cc46f5dc437b79e860e4dd65", + "transactionIndex": 0, + "transactionIndexRaw": "0x0", + "type": "mined" + } + ], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000020000000000000008000200000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000004000000000000000000020000000000000000000000000000000000000000000000000010000100000000000000000000000000000000000000000000000000008000000000000000000000420000000000000000000000000000000000000000000000000000000000000000000000000", + "status": "0x1", + "statusOK": true, + "to": "0x476059cd57800db8eb88f67c2aa38a6fcf8251e0", + "transactionHash": "0x83f05282a30f77dcfe52ca029b10ba80aac5dad5cc46f5dc437b79e860e4dd65", + "transactionIndexRaw": "0x0" +} +``` + +The JSON structure shows a blockchain transaction receipt with the following details: +##### Transaction Information +- Block Number: 193 (0xc1) +- Transaction Status: Successful (statusOK: true) +- Transaction Hash: 0x83f05282a30f77dcfe52ca029b10ba80aac5dad5cc46f5dc437b79e860e4dd65 +##### Transaction Participants +- From Address: 0xd9fe663797b75d0b3897d55d35e0b4e72307a63f +- To Address: 0x476059cd57800db8eb88f67c2aa38a6fcf8251e0 + +### Retrieve data from the context broker +To retrieve specific DLT transaction receipts from the the context broker, we'll use an NGSI-LD query that filters entities by type and property values. The query targets entities of type DLTtxReceipt and filters them based on the refEntity property matching the Building entity "urn:ngsi-ld:Building:warehouse001". The attrs=TxReceipts parameter in the NGSI-LD query acts as a data filter to limit the response to only include the TxReceipts property, excluding all other properties of the entity and reducing response payload size. + +```shell +curl -L 'http://localhost:1026/ngsi-ld/v1/entities/?type=DLTtxReceipt&q=refEntity%3D%3D%22urn%3Angsi-ld%3ABuilding%3Awarehouse001%22&attrs=TxReceipts' \ +-H 'Link: ; rel="http://www.w3.org/ns/json-ld#context"; type="application/ld+json"' \ +-H 'NGSILD-Tenant: orion' +``` + +The outpot will be similar to the following json response: + +```json +{ + "@context": "https://raw.githubusercontent.com/smart-data-models/dataModel.DistributedLedgerTech/master/context.jsonld", + "id": "urn:ngsi-ld:dlttxreceipt:0xa46d2e3b190d36fbb8af5d0a1c212d8036cf007e6ec4d1309904e052d25e5499", + "type": "DLTtxReceipt", + "TxReceipts": { + "type": "Property", + "value": { + "blockHash": "0x2022f801a1c094bd3a893ac6f3087c17bb9c673a6b5663f353eded030e1e7161", + "blockNumber": 192, + "blockNumberRaw": "0xc0", + "cumulativeGasUsed": 23866, + "cumulativeGasUsedRaw": "0x5d3a", + "from": "0x34e5b3f990e55d0651b35c817bafb89d2877cb95", + "gasUsed": 23866, + "gasUsedRaw": "0x5d3a", + "logs": [ + { + "address": "0x476059cd57800db8eb88f67c2aa38a6fcf8251e0", + "blockHash": "0x2022f801a1c094bd3a893ac6f3087c17bb9c673a6b5663f353eded030e1e7161", + "blockNumber": 192, + "blockNumberRaw": "0xc0", + "data": "0x", + "logIndex": 0, + "logIndexRaw": "0x0", + "removed": false, + "topics": [ + "0xa3865c00e01495fc2b86502cae36a4edb139f748682e7d80725a3d6571a482fa", + "0xf85c55023889d6ec0b723c8220471171435040e275059f8e222dfa57a50f0dd5", + "0xefc7a4d4c2393e9b62ac4b93b7d199c71e0bb103fdb00fc1b37d7949ad886ddd" + ], + "transactionHash": "0xa46d2e3b190d36fbb8af5d0a1c212d8036cf007e6ec4d1309904e052d25e5499", + "transactionIndex": 0, + "transactionIndexRaw": "0x0", + "type": "mined" + } + ], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000020000000000000008000200000000000000000000000000000000000000000000000100000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000008000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000420000000000000000000000000000000000000000200000000000000000000000000000000", + "status": "0x1", + "statusOK": true, + "to": "0x476059cd57800db8eb88f67c2aa38a6fcf8251e0", + "transactionHash": "0xa46d2e3b190d36fbb8af5d0a1c212d8036cf007e6ec4d1309904e052d25e5499", + "transactionIndex": 0, + "transactionIndexRaw": "0x0" + } + } +} +``` + +This JSON response shows a DLTtxReceipt (Distributed Ledger Technology Transaction Receipt) entity in NGSI-LD format: + +##### Transaction Details + +- Entity ID: `urn:ngsi-ld:dlttxreceipt:0xa46d2e3b190d36fbb8af5d0a1c212d8036cf007e6ec4d1309904e052d25e5499` +- Block Number: 192 (0xc0) +- Transaction Status: Successful (statusOK: true) +- Block Hash: `0x2022f801a1c094bd3a893ac6f3087c17bb9c673a6b5663f353eded030e1e7161` +- Transaction Hash: `0xa46d2e3b190d36fbb8af5d0a1c212d8036cf007e6ec4d1309904e052d25e5499` + +##### Transaction Participants +- From Address: `0x34e5b3f990e55d0651b35c817bafb89d2877cb95` +- To Address: `0x476059cd57800db8eb88f67c2aa38a6fcf8251e0` + From 87ec990d6ba605edd334adac4f12d438c6b60c4b Mon Sep 17 00:00:00 2001 From: asmataamallah25 <168739895+asmataamallah25@users.noreply.github.com> Date: Tue, 14 Jan 2025 14:02:35 +0100 Subject: [PATCH 11/19] Update README.md --- validator/README.md | 207 -------------------------------------------- 1 file changed, 207 deletions(-) diff --git a/validator/README.md b/validator/README.md index e2ac1dd..8b13789 100644 --- a/validator/README.md +++ b/validator/README.md @@ -1,208 +1 @@ -# Canis Major test - -Canis Major serves as a blockchain adaptor within the FIWARE ecosystem, providing secure data persistence across blockchain networks and the Context Broker. The workflow is straightforward: - - clients submit transactions containing payload data and wallet credentials. - - These data are authenticated through a PEP Proxy and KeyRock Identity Management. - - Once validated, the data is stored in an ETSI Broker (e.g., Orion-LD Broker) and simultaneously processed into a Merkle tree structure for blockchain integration. - - The system then signs the transaction using the provided wallet credentials and submits it to an Oketh-compatible blockchain. - - ### Integradtion tests - Let's explore the practical implementation through a series of test commands. First, it is needed to execute the integration tests, by running the following commands: - - ```shell - cd it - docker-compose -f docker-compose/docker-compose-env.yaml -f docker-compose/docker-compose-java.yaml up - NGSI_ADDRESS=localhost:4000 mvn clean test - ``` - -### Entity creation -After running the integartion tests, create an entity by sending this POST request to Canis Major: - -```shell -curl --location 'http://localhost:4000/ngsi-ld/v1/entities/' \ ---header 'Link: ; rel="http://www.w3.org/ns/json-ld#context"; type="application/ld+json"' \ ---header 'Wallet-Type: vault' \ ---header 'Wallet-Token: vault-plaintext-root-token' \ ---header 'Wallet-Address: http://vault:8200/v1/ethereum/accounts/mira' \ ---header 'Content-Type: application/json' \ ---header 'Accept: application/json' \ ---header 'NGSILD-TENANT: orion' \ ---data '{ - "id": "urn:ngsi-ld:Building:warehouse001", - "type": "Building", - "category": { - "type": "Property", - "value": ["warehouse"] - }, - "address": { - "type": "Property", - "value": { - "streetAddress": "Alexanderplatz 2", - "addressRegion": "Berlin", - "addressLocality": "Mitte", - "postalCode": "10178" - } - } -}' -``` - -The headers included in the HTTP request are: -##### Wallet Headers -- Wallet-Type: Specifies the wallet service type as "vault" -- Wallet-Token: Provides authentication token for the vault service -- Wallet-Address: Points to the Ethereum accoun - -##### Content Headers -- Content-Type: Declares the request body format as JSON -- Accept: Indicates the expected response format as JSON - -### Retrieve the entity types in the Context broker -To retrieve the available entity types in the context broker run the following command: - -```shell -curl -L 'http://localhost:1026/ngsi-ld/v1/types' \ --H 'Link: ; rel="http://www.w3.org/ns/json-ld#context"; type="application/ld+json"' \ --H 'NGSILD-Tenant: orion' -``` -The command returns the following response, demonstrating the available entity types in the Context Broker: -```json -{ - "@context":"https://raw.githubusercontent.com/smart-data-models/dataModel.DistributedLedgerTech/master/context.jsonld","id":"urn:ngsi-ld:EntityTypeList:d77ccfa0-b3cd-11ef-ae1b-0242ac120005","type":"EntityTypeList","typeList":["DLTtxReceipt"] -} -``` -The @context is using a definition on smart data models for a DLTtxReceipt and the NGSILD-Tenant is defined in the start-up of the tests. - -### Retrieve data from Canis Major -To retrieve detailed receipt information for a specific building from the Canis Major, use the following HTTP request: - -```shell -curl -L 'http://localhost:4000/ngsi-ld/v1/entities/urn:ngsi-ld:Building:warehouse001' \ --H 'Link: ; rel="http://www.w3.org/ns/json-ld#context"; type="application/ld+json"' \ --H 'Accept: application/json' | jq '.' -``` - -The output will be similar to following response: - -```json -{ - "blockHash": "0x619433204be327dda0d4722482e44310d2f3807e1a23b1fc097ca809f7afb421", - "blockNumber": 193, - "blockNumberRaw": "0xc1", - "cumulativeGasUsed": 23866, - "cumulativeGasUsedRaw": "0x5d3a", - "from": "0xd9fe663797b75d0b3897d55d35e0b4e72307a63f", - "gasUsed": 23866, - "gasUsedRaw": "0x5d3a", - "logs": [ - { - "address": "0x476059cd57800db8eb88f67c2aa38a6fcf8251e0", - "blockHash": "0x619433204be327dda0d4722482e44310d2f3807e1a23b1fc097ca809f7afb421", - "blockNumber": 193, - "blockNumberRaw": "0xc1", - "data": "0x", - "logIndex": 0, - "logIndexRaw": "0x0", - "removed": false, - "topics": [ - "0xa3865c00e01495fc2b86502cae36a4edb139f748682e7d80725a3d6571a482fa", - "0xf85c55023889d6ec0b723c8220471171435040e275059f8e222dfa57a50f0dd5", - "0x33868c71f7186ea974685b553d8eee61eb1e95e0a2c70ed54fcd13db67920f74" - ], - "transactionHash": "0x83f05282a30f77dcfe52ca029b10ba80aac5dad5cc46f5dc437b79e860e4dd65", - "transactionIndex": 0, - "transactionIndexRaw": "0x0", - "type": "mined" - } - ], - "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000020000000000000008000200000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000004000000000000000000020000000000000000000000000000000000000000000000000010000100000000000000000000000000000000000000000000000000008000000000000000000000420000000000000000000000000000000000000000000000000000000000000000000000000", - "status": "0x1", - "statusOK": true, - "to": "0x476059cd57800db8eb88f67c2aa38a6fcf8251e0", - "transactionHash": "0x83f05282a30f77dcfe52ca029b10ba80aac5dad5cc46f5dc437b79e860e4dd65", - "transactionIndexRaw": "0x0" -} -``` - -The JSON structure shows a blockchain transaction receipt with the following details: -##### Transaction Information -- Block Number: 193 (0xc1) -- Transaction Status: Successful (statusOK: true) -- Transaction Hash: 0x83f05282a30f77dcfe52ca029b10ba80aac5dad5cc46f5dc437b79e860e4dd65 -##### Transaction Participants -- From Address: 0xd9fe663797b75d0b3897d55d35e0b4e72307a63f -- To Address: 0x476059cd57800db8eb88f67c2aa38a6fcf8251e0 - -### Retrieve data from the context broker -To retrieve specific DLT transaction receipts from the the context broker, we'll use an NGSI-LD query that filters entities by type and property values. The query targets entities of type DLTtxReceipt and filters them based on the refEntity property matching the Building entity "urn:ngsi-ld:Building:warehouse001". The attrs=TxReceipts parameter in the NGSI-LD query acts as a data filter to limit the response to only include the TxReceipts property, excluding all other properties of the entity and reducing response payload size. - -```shell -curl -L 'http://localhost:1026/ngsi-ld/v1/entities/?type=DLTtxReceipt&q=refEntity%3D%3D%22urn%3Angsi-ld%3ABuilding%3Awarehouse001%22&attrs=TxReceipts' \ --H 'Link: ; rel="http://www.w3.org/ns/json-ld#context"; type="application/ld+json"' \ --H 'NGSILD-Tenant: orion' -``` - -The outpot will be similar to the following json response: - -```json -{ - "@context": "https://raw.githubusercontent.com/smart-data-models/dataModel.DistributedLedgerTech/master/context.jsonld", - "id": "urn:ngsi-ld:dlttxreceipt:0xa46d2e3b190d36fbb8af5d0a1c212d8036cf007e6ec4d1309904e052d25e5499", - "type": "DLTtxReceipt", - "TxReceipts": { - "type": "Property", - "value": { - "blockHash": "0x2022f801a1c094bd3a893ac6f3087c17bb9c673a6b5663f353eded030e1e7161", - "blockNumber": 192, - "blockNumberRaw": "0xc0", - "cumulativeGasUsed": 23866, - "cumulativeGasUsedRaw": "0x5d3a", - "from": "0x34e5b3f990e55d0651b35c817bafb89d2877cb95", - "gasUsed": 23866, - "gasUsedRaw": "0x5d3a", - "logs": [ - { - "address": "0x476059cd57800db8eb88f67c2aa38a6fcf8251e0", - "blockHash": "0x2022f801a1c094bd3a893ac6f3087c17bb9c673a6b5663f353eded030e1e7161", - "blockNumber": 192, - "blockNumberRaw": "0xc0", - "data": "0x", - "logIndex": 0, - "logIndexRaw": "0x0", - "removed": false, - "topics": [ - "0xa3865c00e01495fc2b86502cae36a4edb139f748682e7d80725a3d6571a482fa", - "0xf85c55023889d6ec0b723c8220471171435040e275059f8e222dfa57a50f0dd5", - "0xefc7a4d4c2393e9b62ac4b93b7d199c71e0bb103fdb00fc1b37d7949ad886ddd" - ], - "transactionHash": "0xa46d2e3b190d36fbb8af5d0a1c212d8036cf007e6ec4d1309904e052d25e5499", - "transactionIndex": 0, - "transactionIndexRaw": "0x0", - "type": "mined" - } - ], - "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000020000000000000008000200000000000000000000000000000000000000000000000100000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000008000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000420000000000000000000000000000000000000000200000000000000000000000000000000", - "status": "0x1", - "statusOK": true, - "to": "0x476059cd57800db8eb88f67c2aa38a6fcf8251e0", - "transactionHash": "0xa46d2e3b190d36fbb8af5d0a1c212d8036cf007e6ec4d1309904e052d25e5499", - "transactionIndex": 0, - "transactionIndexRaw": "0x0" - } - } -} -``` - -This JSON response shows a DLTtxReceipt (Distributed Ledger Technology Transaction Receipt) entity in NGSI-LD format: - -##### Transaction Details - -- Entity ID: `urn:ngsi-ld:dlttxreceipt:0xa46d2e3b190d36fbb8af5d0a1c212d8036cf007e6ec4d1309904e052d25e5499` -- Block Number: 192 (0xc0) -- Transaction Status: Successful (statusOK: true) -- Block Hash: `0x2022f801a1c094bd3a893ac6f3087c17bb9c673a6b5663f353eded030e1e7161` -- Transaction Hash: `0xa46d2e3b190d36fbb8af5d0a1c212d8036cf007e6ec4d1309904e052d25e5499` - -##### Transaction Participants -- From Address: `0x34e5b3f990e55d0651b35c817bafb89d2877cb95` -- To Address: `0x476059cd57800db8eb88f67c2aa38a6fcf8251e0` From f3e0665553f7b17f39de37fbcae474b6b4f7ea43 Mon Sep 17 00:00:00 2001 From: asmataamallah25 Date: Thu, 16 Jan 2025 09:10:39 +0100 Subject: [PATCH 12/19] Update validation service --- Validation_service/README.md | 22 ++++ Validation_service/pom.xml | 65 ++++++++++ .../client/ValidationHttpClient.java | 1 - .../controller/ValidationController.java | 36 ++++++ .../validator/model/ValidationResult.java | 30 +++++ .../validator/service/ValidationService.java | 93 ++++++++++++++ Validation_service/validation_service.png | Bin 0 -> 130008 bytes it/src/test/java/it/README.MD | 0 validator/Tests.md | 114 ------------------ validator/ValidationService.java | 66 ---------- .../controller/ValidationController.java | 29 ----- validator/model/ValidationInfo.java | 13 -- validator/model/ValidationResult.java | 9 -- 13 files changed, 246 insertions(+), 232 deletions(-) create mode 100644 Validation_service/README.md create mode 100644 Validation_service/pom.xml rename {validator => Validation_service/src/main/java/validator}/client/ValidationHttpClient.java (98%) create mode 100644 Validation_service/src/main/java/validator/controller/ValidationController.java create mode 100644 Validation_service/src/main/java/validator/model/ValidationResult.java create mode 100644 Validation_service/src/main/java/validator/service/ValidationService.java create mode 100644 Validation_service/validation_service.png create mode 100644 it/src/test/java/it/README.MD delete mode 100644 validator/Tests.md delete mode 100644 validator/ValidationService.java delete mode 100644 validator/controller/ValidationController.java delete mode 100644 validator/model/ValidationInfo.java delete mode 100644 validator/model/ValidationResult.java diff --git a/Validation_service/README.md b/Validation_service/README.md new file mode 100644 index 0000000..3370f2b --- /dev/null +++ b/Validation_service/README.md @@ -0,0 +1,22 @@ +# Validation service + +The validation service for Canis Major performs security checks to verify transaction integrity between the context broker and Canis Major blockchain transactions. + +![Validation service](validation_service.png) + + +## Service Setup +The validation service implements a three-step verification protocol. + +### Initial Validation Request +The client initiates the verification process by submitting a POST request to the Canis Major Validator service. + +### Dual-Source Data Retrieval +The Canis Major Validator executes two GET requests, based on the `entityId`, to: +- Canis Major +- The Context Broker + +Both requests retrieve the entity information using the specified entity identifier as the query parameter. + +### Data Integrity Analysis +The validator performs a systematic comparison of the entity representations obtained from both sources. diff --git a/Validation_service/pom.xml b/Validation_service/pom.xml new file mode 100644 index 0000000..b8662bd --- /dev/null +++ b/Validation_service/pom.xml @@ -0,0 +1,65 @@ + + + 4.0.0 + + validator + validation-service + 1.0-SNAPSHOT + jar + + + 17 + 2.0.0 + 2.13.0 + 1.7.32 + + + + + jakarta.inject + jakarta.inject-api + ${jakarta.version} + + + com.fasterxml.jackson.core + jackson-databind + ${jackson.version} + + + org.slf4j + slf4j-api + ${slf4j.version} + + + org.slf4j + slf4j-simple + ${slf4j.version} + + + io.micronaut + micronaut-http-client + 3.5.0 + + + io.micronaut + micronaut-runtime + 3.5.0 + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.1 + + ${java.version} + ${java.version} + + + + + \ No newline at end of file diff --git a/validator/client/ValidationHttpClient.java b/Validation_service/src/main/java/validator/client/ValidationHttpClient.java similarity index 98% rename from validator/client/ValidationHttpClient.java rename to Validation_service/src/main/java/validator/client/ValidationHttpClient.java index c490060..dce0f1b 100644 --- a/validator/client/ValidationHttpClient.java +++ b/Validation_service/src/main/java/validator/client/ValidationHttpClient.java @@ -1,4 +1,3 @@ -// Declares the package name for this class package validator.client; // Import statements: diff --git a/Validation_service/src/main/java/validator/controller/ValidationController.java b/Validation_service/src/main/java/validator/controller/ValidationController.java new file mode 100644 index 0000000..ade87aa --- /dev/null +++ b/Validation_service/src/main/java/validator/controller/ValidationController.java @@ -0,0 +1,36 @@ +package validator.controller; + +import io.micronaut.http.annotation.*; +import io.micronaut.http.HttpResponse; +import validator.service.ValidationService; +import validator.model.ValidationResult; + +@Controller("/service/v1/validation") +public class ValidationController { + private final ValidationService validationService; + + public ValidationController(ValidationService validationService) { + this.validationService = validationService; + } + + @Post("/compare") + public HttpResponse compareResponses(@Body CompareRequest request) { + // Call the validation service to perform the validation + ValidationResult result = validationService.validateEntity(request.getEntityId()); + return HttpResponse.ok(result); + } + + // Inner class to represent the request body + public static class CompareRequest { + private String entityId; + + // Getter and Setter + public String getEntityId() { + return entityId; + } + + public void setEntityId(String entityId) { + this.entityId = entityId; + } + } +} diff --git a/Validation_service/src/main/java/validator/model/ValidationResult.java b/Validation_service/src/main/java/validator/model/ValidationResult.java new file mode 100644 index 0000000..2235c3a --- /dev/null +++ b/Validation_service/src/main/java/validator/model/ValidationResult.java @@ -0,0 +1,30 @@ +package validator.model; + +import java.util.List; + +//import java.util.List; + +/*public record ValidationResult(boolean isValid, List errors) { + public String getErrorMessage() { + return String.join(", ", errors); + } +} */ + +public class ValidationResult { + private final boolean valid; + private final List messages; + + + public ValidationResult(boolean valid, List messages){ + this.valid = valid; + this.messages = messages; + } + + public boolean isValid() { //getter + return valid; + } + + public List getMessages() { + return messages; + } +} \ No newline at end of file diff --git a/Validation_service/src/main/java/validator/service/ValidationService.java b/Validation_service/src/main/java/validator/service/ValidationService.java new file mode 100644 index 0000000..036ca11 --- /dev/null +++ b/Validation_service/src/main/java/validator/service/ValidationService.java @@ -0,0 +1,93 @@ +/* + This implementation of the validation service: +- Compares all relevant fields between Orion and blockchain responses +- Provides detailed validation errors for each mismatched field +- Handles JSON parsing and null checks +- Uses logging for error tracking +- Returns structured validation results + */ + + package validator.service; + + import com.fasterxml.jackson.databind.JsonNode; + import com.fasterxml.jackson.databind.ObjectMapper; + import com.fasterxml.jackson.core.JsonProcessingException; + import validator.model.ValidationResult; + import jakarta.inject.Singleton; + import validator.client.ValidationHttpClient; + import org.slf4j.Logger; + import org.slf4j.LoggerFactory; + import java.util.ArrayList; + import java.util.List; + + @Singleton + public class ValidationService { + private static final Logger logger = LoggerFactory.getLogger(ValidationService.class); + private final ValidationHttpClient validationHttpClient; + private final ObjectMapper mapper = new ObjectMapper(); + + public ValidationService(ValidationHttpClient validationHttpClient) { + this.validationHttpClient = validationHttpClient; + } + + public ValidationResult validateEntity(String entityId) { + try { + String orionData = validationHttpClient.fetchOrionData(entityId); + String blockchainData = validationHttpClient.fetchBlockchainData(entityId); + + if (orionData == null || orionData.isEmpty()) { + logger.error("No data found from Orion for entityId: {}", entityId); + return new ValidationResult(false, List.of("No data found from Orion")); + } + + if (blockchainData == null || blockchainData.isEmpty()) { + logger.error("No data found from Canis Major for entityId: {}", entityId); + return new ValidationResult(false, List.of("No data found from Canis Major")); + } + + List validationErrors = compareData(orionData, blockchainData); + return new ValidationResult(validationErrors.isEmpty(), validationErrors); + } catch (Exception e) { + logger.error("Error validating entity: {}", entityId, e); + return new ValidationResult(false, List.of("Validation error: " + e.getMessage())); + } + } + + private List compareData(String orionData, String blockchainData) { + List errors = new ArrayList<>(); + try { + JsonNode orionNode = mapper.readTree(orionData); + JsonNode blockchainNode = mapper.readTree(blockchainData); + + JsonNode txReceipts = orionNode.get("TxReceipts").get("value"); + + validateField(txReceipts, blockchainNode, "status", "Status", errors); + validateField(txReceipts, blockchainNode, "blockNumber", "Block Number", errors); + validateField(txReceipts, blockchainNode, "transactionHash", "Transaction Hash", errors); + validateField(txReceipts, blockchainNode, "blockHash", "Block Hash", errors); + validateField(txReceipts, blockchainNode, "from", "From Address", errors); + validateField(txReceipts, blockchainNode, "to", "To Address", errors); + + return errors; + } catch (JsonProcessingException e) { + logger.error("Error parsing JSON data", e); + errors.add("Error parsing JSON data: " + e.getMessage()); + return errors; + } + } + + private void validateField(JsonNode orion, JsonNode blockchain, String fieldName, String displayName, List errors) { + JsonNode orionValue = orion.get(fieldName); + JsonNode blockchainValue = blockchain.get(fieldName); + + if (orionValue == null || blockchainValue == null) { + errors.add(displayName + " field missing in one or both responses"); + return; + } + + if (!orionValue.equals(blockchainValue)) { + errors.add(displayName + " mismatch: Orion=" + orionValue + ", Blockchain=" + blockchainValue); + } + } + } + \ No newline at end of file diff --git a/Validation_service/validation_service.png b/Validation_service/validation_service.png new file mode 100644 index 0000000000000000000000000000000000000000..ad2363e0573bff9f3d715da28012192a454cfeb9 GIT binary patch literal 130008 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 F|38XtFTnr+ literal 0 HcmV?d00001 diff --git a/it/src/test/java/it/README.MD b/it/src/test/java/it/README.MD new file mode 100644 index 0000000..e69de29 diff --git a/validator/Tests.md b/validator/Tests.md deleted file mode 100644 index f489740..0000000 --- a/validator/Tests.md +++ /dev/null @@ -1,114 +0,0 @@ -# Canis Major test - -Canis Major serves as a blockchain adaptor within the FIWARE ecosystem, providing secure data persistence across blockchain networks and the Context Broker. The workflow is straightforward: clients submit transactions containing payload data and wallet credentials, which undergo authentication through Wilma PEP Proxy and KeyRock Identity Management. Once validated, the data is stored in the Orion Context Broker and simultaneously processed into a Merkle-Tree structure for blockchain integration. The system then signs the transaction using the provided wallet credentials and submits it to an Oketh-compatible blockchain. Let's explore the practical implementation through a series of test commands. - -After r unning the integartion tests, create an entity by sending this POST request to Canis Major: - -```bash -curl --location 'http://:4000/ngsi-ld/v1/entities/' \ ---header 'Link: ; rel="http://www.w3.org/ns/json-ld#context"; type="application/ld+json"' \ ---header 'Wallet-Type: vault' \ ---header 'Wallet-Token: vault-plaintext-root-token' \ ---header 'Wallet-Address: http://vault:8200/v1/ethereum/accounts/mira' \ ---header 'Content-Type: application/json' \ ---header 'Accept: application/json' \ ---header 'NGSILD-TENANT: orion' \ ---data '{ - "id": "urn:ngsi-ld:Building:farm002", - "type": "Building", - "category": { - "type": "Property", - "value": ["farm"] - }, - "address": { - "type": "Property", - "value": { - "streetAddress": "Großer Stern 1", - "addressRegion": "Berlin", - "addressLocality": "Tiergarten", - "postalCode": "10557" - } - } -}' -``` - -To retrieve the available entity types in the context broker run the following command: -```bash -curl -L 'http://:1026/ngsi-ld/v1/types' \ --H 'Link: ; rel="http://www.w3.org/ns/json-ld#context"; type="application/ld+json"' \ --H 'NGSILD-Tenant: orion' -``` -The command returns the following response, demonstrating the available entity types in the Context Broker: -```bash -{ - "@context":"https://raw.githubusercontent.com/smart-data-models/dataModel.DistributedLedgerTech/master/context.jsonld","id":"urn:ngsi-ld:EntityTypeList:d77ccfa0-b3cd-11ef-ae1b-0242ac120005","type":"EntityTypeList","typeList":["DLTtxReceipt"] -}% -``` -The @context is using a definition on smart data models for a DLTtxReceipt and the NGSILD-Tenant is defined in the start-up of the tests. -To verify which entities are created in the context broker use the following command: - -To verify that the created entities is correctly saved in the context broker having the type DLTtxReceipt, use the following command: - -```bash -curl -L 'http://:1026/ngsi-ld/v1/entities/?type=DLTtxReceipt' \ --H 'Link: ; rel="http://www.w3.org/ns/json-ld#context"; type="application/ld+json"' \ --H 'NGSILD-Tenant: orion' -``` - -To ask Canis Major about receipts for a specific building use the following command: - -```bash -curl -L 'http://:4000/entity/urn:ngsi-ld:Building:farm002' \ --H 'Accept: application/json' -``` - -The output will be similar to following response: - -```bash -{ - "entityId":"urn:ngsi-ld:Building:farm002","txDetails":[ - { - "blockHash":"0x610f07a4b5a87ca71273791fe5d12952fca393a6e3664489f45329bebdb396c3","blockNumber":186,"blockNumberRaw":"0xba","cumulativeGasUsed":23866,"cumulativeGasUsedRaw":"0x5d3a","from":"0x34e5b3f990e55d0651b35c817bafb89d2877cb95","gasUsed":23866,"gasUsedRaw":"0x5d3a","logs":[ - { - "address":"0x476059cd57800db8eb88f67c2aa38a6fcf8251e0","blockHash":"0x610f07a4b5a87ca71273791fe5d12952fca393a6e3664489f45329bebdb396c3","blockNumber":186,"blockNumberRaw":"0xba","data":"0x","logIndex":0,"logIndexRaw":"0x0","removed":false,"topics":["0xa3865c00e01495fc2b86502cae36a4edb139f748682e7d80725a3d6571a482fa","0x9aeb7ab140e1449806e11e52ca85dacb6a028ae5feb8d431e7d927e7971e4d2d","0xb1a82faaa61a7bfbc90c42129b2049d408f20b8f43ae99e8b6aa6cc7e7b0b944"],"transactionHash":"0x8456f94030cc841bc667d76ce563b47e4920a27884bdb9b8fc5e4e9cc2df1913","transactionIndex":0,"transactionIndexRaw":"0x0","type":"mined"}],"logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000008000200000000000000000000010000000000004000000000000000000000000000110000000000000004000000000000000000000000000000000000000000000020000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000020000000000000000000000000000000000000000000000000000000000000000000000000","status":"0x1","statusOK":true,"to":"0x476059cd57800db8eb88f67c2aa38a6fcf8251e0","transactionHash":"0x8456f94030cc841bc667d76ce563b47e4920a27884bdb9b8fc5e4e9cc2df1913","transactionIndexRaw":"0x0"},{ - "blockHash":"0x86082426228ee23ea8927607c7f731194ebc1dc7ca69a6321ff46e91b0fdbe2a","blockNumber":190,"blockNumberRaw":"0xbe","cumulativeGasUsed":23866,"cumulativeGasUsedRaw":"0x5d3a","from":"0x34e5b3f990e55d0651b35c817bafb89d2877cb95","gasUsed":23866,"gasUsedRaw":"0x5d3a","logs":[ - {"address":"0x476059cd57800db8eb88f67c2aa38a6fcf8251e0","blockHash":"0x86082426228ee23ea8927607c7f731194ebc1dc7ca69a6321ff46e91b0fdbe2a","blockNumber":190,"blockNumberRaw":"0xbe","data":"0x","logIndex":0,"logIndexRaw":"0x0","removed":false,"topics":["0xa3865c00e01495fc2b86502cae36a4edb139f748682e7d80725a3d6571a482fa","0x9aeb7ab140e1449806e11e52ca85dacb6a028ae5feb8d431e7d927e7971e4d2d","0xb1a82faaa61a7bfbc90c42129b2049d408f20b8f43ae99e8b6aa6cc7e7b0b944"],"transactionHash":"0x18b8c942735e869a42fff06393ebb12824ec44ed030e9e6a579f8f839c59a5de","transactionIndex":0,"transactionIndexRaw":"0x0","type":"mined"}],"logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000008000200000000000000000000010000000000004000000000000000000000000000110000000000000004000000000000000000000000000000000000000000000020000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000020000000000000000000000000000000000000000000000000000000000000000000000000","status":"0x1","statusOK":true,"to":"0x476059cd57800db8eb88f67c2aa38a6fcf8251e0","transactionHash":"0x18b8c942735e869a42fff06393ebb12824ec44ed030e9e6a579f8f839c59a5de","transactionIndexRaw":"0x0"}] -}% -``` -The JSON structure shows: -- One Entity ID: "urn:ngsi-ld:Building:farm002" -- Two transactions recorded in different blocks: - - First transaction in block 186 (0xba) - - Second transaction in block 190 (0xbe) - -Each transaction contains detailed blockchain information including block hashes, gas usage, logs, and transaction hashes, but they all relate to the same building entity. - -To ask the context brker Orion-LD about the receipt of a specific building use the following command: -```bash -curl -L 'http://:1026/ngsi-ld/v1/entities/?type=DLTtxReceipt&q=refEntity%3D%3D%22urn%3Angsi-ld%3ABuilding%3Afarm002%22&attrs=TxReceipts' \ --H 'Link: ; rel="http://www.w3.org/ns/json-ld#context"; type="application/ld+json"' \ --H 'NGSILD-Tenant: orion' -``` -The outpot will be similar to the following response: -```bash -["@context":"https://raw.githubusercontent.com/smart-data-models/dataModel.DistributedLedgerTech/master/context.jsonld","id":"urn:ngsi-ld:dlttxreceipt:0x8456f94030cc841bc667d76ce563b47e4920a27884bdb9b8fc5e4e9cc2df1913","type":"DLTtxReceipt","TxReceipts":{"type":"Property","value":{"blockHash":"0x610f07a4b5a87ca71273791fe5d12952fca393a6e3664489f45329bebdb396c3","blockNumber":186,"blockNumberRaw":"0xba","cumulativeGasUsed":23866,"cumulativeGasUsedRaw":"0x5d3a","from":"0x34e5b3f990e55d0651b35c817bafb89d2877cb95","gasUsed":23866,"gasUsedRaw":"0x5d3a","logs":[{"address":"0x476059cd57800db8eb88f67c2aa38a6fcf8251e0","blockHash":"0x610f07a4b5a87ca71273791fe5d12952fca393a6e3664489f45329bebdb396c3","blockNumber":186,"blockNumberRaw":"0xba","data":"0x","logIndex":0,"logIndexRaw":"0x0","removed":false,"topics":["0xa3865c00e01495fc2b86502cae36a4edb139f748682e7d80725a3d6571a482fa","0x9aeb7ab140e1449806e11e52ca85dacb6a028ae5feb8d431e7d927e7971e4d2d","0xb1a82faaa61a7bfbc90c42129b2049d408f20b8f43ae99e8b6aa6cc7e7b0b944"],"transactionHash":"0x8456f94030cc841bc667d76ce563b47e4920a27884bdb9b8fc5e4e9cc2df1913","transactionIndex":0,"transactionIndexRaw":"0x0","type":"mined"}],"logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000008000200000000000000000000010000000000004000000000000000000000000000110000000000000004000000000000000000000000000000000000000000000020000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000020000000000000000000000000000000000000000000000000000000000000000000000000","status":"0x1","statusOK":true,"to":"0x476059cd57800db8eb88f67c2aa38a6fcf8251e0","transactionHash":"0x8456f94030cc841bc667d76ce563b47e4920a27884bdb9b8fc5e4e9cc2df1913","transactionIndex":0,"transactionIndexRaw":"0x0"}}},{"@context":"https://raw.githubusercontent.com/smart-data-models/dataModel.DistributedLedgerTech/master/context.jsonld","id":"urn:ngsi-ld:dlttxreceipt:0x18b8c942735e869a42fff06393ebb12824ec44ed030e9e6a579f8f839c59a5de","type":"DLTtxReceipt","TxReceipts":{"type":"Property","value":{"blockHash":"0x86082426228ee23ea8927607c7f731194ebc1dc7ca69a6321ff46e91b0fdbe2a","blockNumber":190,"blockNumberRaw":"0xbe","cumulativeGasUsed":23866,"cumulativeGasUsedRaw":"0x5d3a","from":"0x34e5b3f990e55d0651b35c817bafb89d2877cb95","gasUsed":23866,"gasUsedRaw":"0x5d3a","logs":[{"address":"0x476059cd57800db8eb88f67c2aa38a6fcf8251e0","blockHash":"0x86082426228ee23ea8927607c7f731194ebc1dc7ca69a6321ff46e91b0fdbe2a","blockNumber":190,"blockNumberRaw":"0xbe","data":"0x","logIndex":0,"logIndexRaw":"0x0","removed":false,"topics":["0xa3865c00e01495fc2b86502cae36a4edb139f748682e7d80725a3d6571a482fa","0x9aeb7ab140e1449806e11e52ca85dacb6a028ae5feb8d431e7d927e7971e4d2d","0xb1a82faaa61a7bfbc90c42129b2049d408f20b8f43ae99e8b6aa6cc7e7b0b944"],"transactionHash":"0x18b8c942735e869a42fff06393ebb12824ec44ed030e9e6a579f8f839c59a5de","transactionIndex":0,"transactionIndexRaw":"0x0","type":"mined"}],"logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000008000200000000000000000000010000000000004000000000000000000000000000110000000000000004000000000000000000000000000000000000000000000020000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000020000000000000000000000000000000000000000000000000000000000000000000000000","status":"0x1","statusOK":true,"to":"0x476059cd57800db8eb88f67c2aa38a6fcf8251e0","transactionHash":"0x18b8c942735e869a42fff06393ebb12824ec44ed030e9e6a579f8f839c59a5de","transactionIndex":0,"transactionIndexRaw":"0x0"}}}]% -``` -This JSON response shows two DLTtxReceipt (Distributed Ledger Technology Transaction Receipt) entities in NGSI-LD format: - -##### First Transaction Receipt -- ID: urn:ngsi-ld:dlttxreceipt:0x8456f94030cc841bc667d76ce563b47e4920a27884bdb9b8fc5e4e9cc2df1913 -- Block Number: 186 (0xba) -- Gas Used: 23,866 -- From Address: 0x34e5b3f990e55d0651b35c817bafb89d2877cb95 -- To Address: 0x476059cd57800db8eb88f67c2aa38a6fcf8251e0 - -##### Second Transaction Receipt -- ID: urn:ngsi-ld:dlttxreceipt:0x18b8c942735e869a42fff06393ebb12824ec44ed030e9e6a579f8f839c59a5de -- Block Number: 190 (0xbe) -- Gas Used: 23,866 -- From Address: 0x34e5b3f990e55d0651b35c817bafb89d2877cb95 -- To Address: 0x476059cd57800db8eb88f67c2aa38a6fcf8251e0 - -Both transactions were successful (statusOK: true). Each receipt contains detailed blockchain information including logs, bloom filters, and raw transaction data. - - diff --git a/validator/ValidationService.java b/validator/ValidationService.java deleted file mode 100644 index f649c21..0000000 --- a/validator/ValidationService.java +++ /dev/null @@ -1,66 +0,0 @@ -// Declares the package name for organizing related classes -package validator; - -// Main service class for handling validation operations -public class ValidationService { - - // Method to validate entities by comparing responses from Orion and Canis Major - // Takes two parameters: orionResponse and blockchainResponse as generic Objects - public ValidationResult validateEntityFromResponses(Object orionResponse, Object blockchainResponse) { - // Null check: if either response is null, return failed validation result - if (orionResponse == null || CanisMajorResponse == null) { - return new ValidationResult(false, "Missing response data"); - } - - // Try-catch block to handle potential exceptions during validation - try { - // Compare the responses and store the result - boolean isValid = compareResponses(orionResponse, CanisMajorResponse ); - // Create success/failure message based on comparison result - String message = isValid ? "Validation successful" : "Validation failed"; - // Return the validation result with status and message - return new ValidationResult(isValid, message); - } catch (Exception e) { - // If any error occurs, return failed validation with error message - return new ValidationResult(false, "Validation error: " + e.getMessage()); - } - } - - // Private helper method to compare responses (currently returns false as placeholder) - private boolean compareResponses(Object orionResponse, Object CanisMajorResponse) { - return false; - } - - // Method to get validation information for a specific entity - // Uses builder pattern to construct ValidationInfo object - public ValidationInfo getValidationInfo(String entityId) { - return ValidationInfo.builder() - .status("PENDING") // Sets initial status - .transactionHash(generateTransactionHash()) // Generates transaction hash - .blockHash(generateBlockHash()) // Generates block hash - .blockNumber(getCurrentBlockNumber()) // Gets current block number - .fromAddress(getFromAddress()) // Gets sender address - .entityId(entityId) // Sets entity ID - .build(); // Builds the final object - } - - // Helper method to generate transaction hash (placeholder implementation) - private String generateTransactionHash() { - return "0x..."; - } - - // Helper method to generate block hash (placeholder implementation) - private String generateBlockHash() { - return "0x..."; - } - - // Helper method to get current block number (placeholder implementation) - private long getCurrentBlockNumber() { - return 0L; - } - - // Helper method to get sender address (placeholder implementation) - private String getFromAddress() { - return "0x..."; - } -} \ No newline at end of file diff --git a/validator/controller/ValidationController.java b/validator/controller/ValidationController.java deleted file mode 100644 index 680b60a..0000000 --- a/validator/controller/ValidationController.java +++ /dev/null @@ -1,29 +0,0 @@ -package validator.controller; - -import org.springframework.web.bind.annotation.*; -import validator.service.ValidationService; -import validator.model.ValidationResult; -import validator.model.ValidationInfo; - -@RestController -@RequestMapping("/validation service") -public class ValidationController { - private final ValidationService validationService; - - public ValidationController(ValidationService validationService) { - this.validationService = validationService; - } - - @GetMapping("/{entityId}") - public ValidationInfo getValidationInfo(@PathVariable String entityId) { - return validationService.getValidationInfo(entityId); - } - - @PostMapping("/compare") - public ValidationResult compareResponses(@RequestBody ValidationRequest request) { - return validationService.validateEntityFromResponses( - request.orionResponse(), - request.blockchainResponse() - ); - } -} \ No newline at end of file diff --git a/validator/model/ValidationInfo.java b/validator/model/ValidationInfo.java deleted file mode 100644 index 27e1fd3..0000000 --- a/validator/model/ValidationInfo.java +++ /dev/null @@ -1,13 +0,0 @@ -package validator.model; - -public record ValidationInfo( - String entityId, - String status, - String transactionHash, - String blockHash, - Long blockNumber, - String fromAddress -) { - // Records automatically generate getters, so these methods are unnecessary - // and should be removed unless you need custom implementations -} \ No newline at end of file diff --git a/validator/model/ValidationResult.java b/validator/model/ValidationResult.java deleted file mode 100644 index 1aa4b8f..0000000 --- a/validator/model/ValidationResult.java +++ /dev/null @@ -1,9 +0,0 @@ -package validator.model; - -import java.util.List; - -public record ValidationResult(boolean isValid, List errors) { - public String getErrorMessage() { - return String.join(", ", errors); - } -} \ No newline at end of file From 42d298b32c190aa164eea6c1d98db21c6f67ecf3 Mon Sep 17 00:00:00 2001 From: asmataamallah25 <168739895+asmataamallah25@users.noreply.github.com> Date: Thu, 16 Jan 2025 09:14:08 +0100 Subject: [PATCH 13/19] Delete validator directory --- validator/README.md | 1 - 1 file changed, 1 deletion(-) delete mode 100644 validator/README.md diff --git a/validator/README.md b/validator/README.md deleted file mode 100644 index 8b13789..0000000 --- a/validator/README.md +++ /dev/null @@ -1 +0,0 @@ - From f41e17254e2c209443a024fdeb7b980252c2169c Mon Sep 17 00:00:00 2001 From: asmataamallah25 Date: Tue, 21 Jan 2025 14:21:53 +0100 Subject: [PATCH 14/19] Update validation service --- Validation_service/pom.xml | 2 +- .../src/main/java/validator/client/ValidationHttpClient.java | 2 +- .../src/main/java/validator/model/ValidationResult.java | 2 +- .../src/main/java/validator/service/ValidationService.java | 1 + 4 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Validation_service/pom.xml b/Validation_service/pom.xml index b8662bd..b0fa18e 100644 --- a/Validation_service/pom.xml +++ b/Validation_service/pom.xml @@ -62,4 +62,4 @@ - \ No newline at end of file + diff --git a/Validation_service/src/main/java/validator/client/ValidationHttpClient.java b/Validation_service/src/main/java/validator/client/ValidationHttpClient.java index dce0f1b..7f5d1f3 100644 --- a/Validation_service/src/main/java/validator/client/ValidationHttpClient.java +++ b/Validation_service/src/main/java/validator/client/ValidationHttpClient.java @@ -51,4 +51,4 @@ public String fetchBlockchainData(String entityId) throws Exception { // Sends the request and returns the response body as a String return client.send(request, HttpResponse.BodyHandlers.ofString()).body(); } -} \ No newline at end of file +} diff --git a/Validation_service/src/main/java/validator/model/ValidationResult.java b/Validation_service/src/main/java/validator/model/ValidationResult.java index 2235c3a..f68ecbc 100644 --- a/Validation_service/src/main/java/validator/model/ValidationResult.java +++ b/Validation_service/src/main/java/validator/model/ValidationResult.java @@ -27,4 +27,4 @@ public boolean isValid() { //getter public List getMessages() { return messages; } -} \ No newline at end of file +} diff --git a/Validation_service/src/main/java/validator/service/ValidationService.java b/Validation_service/src/main/java/validator/service/ValidationService.java index 036ca11..41ef4a8 100644 --- a/Validation_service/src/main/java/validator/service/ValidationService.java +++ b/Validation_service/src/main/java/validator/service/ValidationService.java @@ -90,4 +90,5 @@ private void validateField(JsonNode orion, JsonNode blockchain, String fieldName } } } + \ No newline at end of file From aef7f8569279b0a0fd27acfeda0851a17d4cef91 Mon Sep 17 00:00:00 2001 From: asmataamallah25 Date: Tue, 21 Jan 2025 14:26:33 +0100 Subject: [PATCH 15/19] Update Validation Service --- .../README.md | 2 +- {Validation_service => Validation Service}/pom.xml | 0 .../java/validator/client/ValidationHttpClient.java | 0 .../validator/controller/ValidationController.java | 0 .../main/java/validator/model/ValidationResult.java | 0 .../java/validator/service/ValidationService.java | 1 - .../validation_service.png | Bin validator/README.md | 1 - 8 files changed, 1 insertion(+), 3 deletions(-) rename {Validation_service => Validation Service}/README.md (94%) rename {Validation_service => Validation Service}/pom.xml (100%) rename {Validation_service => Validation Service}/src/main/java/validator/client/ValidationHttpClient.java (100%) rename {Validation_service => Validation Service}/src/main/java/validator/controller/ValidationController.java (100%) rename {Validation_service => Validation Service}/src/main/java/validator/model/ValidationResult.java (100%) rename {Validation_service => Validation Service}/src/main/java/validator/service/ValidationService.java (99%) rename {Validation_service => Validation Service}/validation_service.png (100%) delete mode 100644 validator/README.md diff --git a/Validation_service/README.md b/Validation Service/README.md similarity index 94% rename from Validation_service/README.md rename to Validation Service/README.md index 3370f2b..5cd0975 100644 --- a/Validation_service/README.md +++ b/Validation Service/README.md @@ -9,7 +9,7 @@ The validation service for Canis Major performs security checks to verify transa The validation service implements a three-step verification protocol. ### Initial Validation Request -The client initiates the verification process by submitting a POST request to the Canis Major Validator service. +The client initiates the verification process by submitting a POST request to the Canis Major Validation service. ### Dual-Source Data Retrieval The Canis Major Validator executes two GET requests, based on the `entityId`, to: diff --git a/Validation_service/pom.xml b/Validation Service/pom.xml similarity index 100% rename from Validation_service/pom.xml rename to Validation Service/pom.xml diff --git a/Validation_service/src/main/java/validator/client/ValidationHttpClient.java b/Validation Service/src/main/java/validator/client/ValidationHttpClient.java similarity index 100% rename from Validation_service/src/main/java/validator/client/ValidationHttpClient.java rename to Validation Service/src/main/java/validator/client/ValidationHttpClient.java diff --git a/Validation_service/src/main/java/validator/controller/ValidationController.java b/Validation Service/src/main/java/validator/controller/ValidationController.java similarity index 100% rename from Validation_service/src/main/java/validator/controller/ValidationController.java rename to Validation Service/src/main/java/validator/controller/ValidationController.java diff --git a/Validation_service/src/main/java/validator/model/ValidationResult.java b/Validation Service/src/main/java/validator/model/ValidationResult.java similarity index 100% rename from Validation_service/src/main/java/validator/model/ValidationResult.java rename to Validation Service/src/main/java/validator/model/ValidationResult.java diff --git a/Validation_service/src/main/java/validator/service/ValidationService.java b/Validation Service/src/main/java/validator/service/ValidationService.java similarity index 99% rename from Validation_service/src/main/java/validator/service/ValidationService.java rename to Validation Service/src/main/java/validator/service/ValidationService.java index 41ef4a8..036ca11 100644 --- a/Validation_service/src/main/java/validator/service/ValidationService.java +++ b/Validation Service/src/main/java/validator/service/ValidationService.java @@ -90,5 +90,4 @@ private void validateField(JsonNode orion, JsonNode blockchain, String fieldName } } } - \ No newline at end of file diff --git a/Validation_service/validation_service.png b/Validation Service/validation_service.png similarity index 100% rename from Validation_service/validation_service.png rename to Validation Service/validation_service.png diff --git a/validator/README.md b/validator/README.md deleted file mode 100644 index 8b13789..0000000 --- a/validator/README.md +++ /dev/null @@ -1 +0,0 @@ - From cfb3e40cbd7cc2dfd27c16360ef6c5a6a8e9ed63 Mon Sep 17 00:00:00 2001 From: asmataamallah25 Date: Wed, 5 Feb 2025 14:47:21 +0100 Subject: [PATCH 16/19] Update validation service code --- Validation Service/pom.xml | 65 ------ .../client/ValidationHttpClient.java | 54 ----- .../validator/service/ValidationService.java | 93 -------- it/docker-compose/clean.sh | 44 +++- pom.xml | 5 + src/main/java/validator/README.md | 213 ++++++++++++++++++ .../README.md | 0 validation-service/pom.xml | 91 ++++++++ .../client/ValidationHttpClient.java | 65 ++++++ .../controller/ValidationController.java | 19 +- .../model/ValidationResult.java | 0 .../service/ValidationService.java | 92 ++++++++ validation-service/src/main/resources/api.yml | 104 +++++++++ .../src/main/resources/application.yml | 4 + .../validation_service.png | Bin 15 files changed, 624 insertions(+), 225 deletions(-) delete mode 100644 Validation Service/pom.xml delete mode 100644 Validation Service/src/main/java/validator/client/ValidationHttpClient.java delete mode 100644 Validation Service/src/main/java/validator/service/ValidationService.java create mode 100644 src/main/java/validator/README.md rename {Validation Service => validation-service}/README.md (100%) create mode 100644 validation-service/pom.xml create mode 100644 validation-service/src/main/java/org/fiware/validation_service/client/ValidationHttpClient.java rename {Validation Service/src/main/java/validator => validation-service/src/main/java/org/fiware/validation_service}/controller/ValidationController.java (55%) rename {Validation Service/src/main/java/validator => validation-service/src/main/java/org/fiware/validation_service}/model/ValidationResult.java (100%) create mode 100644 validation-service/src/main/java/org/fiware/validation_service/service/ValidationService.java create mode 100644 validation-service/src/main/resources/api.yml create mode 100644 validation-service/src/main/resources/application.yml rename {Validation Service => validation-service}/validation_service.png (100%) diff --git a/Validation Service/pom.xml b/Validation Service/pom.xml deleted file mode 100644 index b0fa18e..0000000 --- a/Validation Service/pom.xml +++ /dev/null @@ -1,65 +0,0 @@ - - - 4.0.0 - - validator - validation-service - 1.0-SNAPSHOT - jar - - - 17 - 2.0.0 - 2.13.0 - 1.7.32 - - - - - jakarta.inject - jakarta.inject-api - ${jakarta.version} - - - com.fasterxml.jackson.core - jackson-databind - ${jackson.version} - - - org.slf4j - slf4j-api - ${slf4j.version} - - - org.slf4j - slf4j-simple - ${slf4j.version} - - - io.micronaut - micronaut-http-client - 3.5.0 - - - io.micronaut - micronaut-runtime - 3.5.0 - - - - - - - org.apache.maven.plugins - maven-compiler-plugin - 3.8.1 - - ${java.version} - ${java.version} - - - - - diff --git a/Validation Service/src/main/java/validator/client/ValidationHttpClient.java b/Validation Service/src/main/java/validator/client/ValidationHttpClient.java deleted file mode 100644 index 7f5d1f3..0000000 --- a/Validation Service/src/main/java/validator/client/ValidationHttpClient.java +++ /dev/null @@ -1,54 +0,0 @@ -package validator.client; - -// Import statements: -import jakarta.inject.Singleton; // For dependency injection, marks class as singleton -import java.net.URI; // For handling URIs -import java.net.http.HttpClient; // Core class for making HTTP requests -import java.net.http.HttpRequest; // For building HTTP requests -import java.net.http.HttpResponse; // For handling HTTP responses - - -// Marks this class as a singleton - only one instance will exist in the application -@Singleton -public class ValidationHttpClient { - // Creates a single HttpClient instance to be reused for all requests - private final HttpClient client = HttpClient.newHttpClient(); - - // Method to fetch data from Orion Context Broker - public String fetchOrionData(String entityId) throws Exception { - // Builds an HTTP request: - HttpRequest request = HttpRequest.newBuilder() - // Sets the URI for the request, querying for DLTtxReceipt entities with matching refEntity - .uri(new URI("http://localhost:1026/ngsi-ld/v1/entities/?type=DLTtxReceipt&q=refEntity==" + entityId)) - // Adds Link header for JSON-LD context - .header("Link", "; rel=\"http://www.w3.org/ns/json-ld#context\"; type=\"application/ld+json\"") - // Sets the NGSILD-Tenant header to "orion" - .header("NGSILD-Tenant", "orion") - // Added Accept header for JSON format - .header("Accept", "application/json") - // Specifies this is a GET request - .GET() - // Finalizes the request building - .build(); - - // Sends the request and returns the response body as a String - return client.send(request, HttpResponse.BodyHandlers.ofString()).body(); - } - - // Method to fetch data from a blockchain service - public String fetchBlockchainData(String entityId) throws Exception { - // Builds an HTTP request: - HttpRequest request = HttpRequest.newBuilder() - // Sets the URI for the blockchain service endpoint - .uri(new URI("http://localhost:4000/entity/" + entityId)) - // Sets Accept header to receive JSONresponse - .header("Accept", "application/json") - // Specifies this is a GET request - .GET() - // Finalizes the request building - .build(); - - // Sends the request and returns the response body as a String - return client.send(request, HttpResponse.BodyHandlers.ofString()).body(); - } -} diff --git a/Validation Service/src/main/java/validator/service/ValidationService.java b/Validation Service/src/main/java/validator/service/ValidationService.java deleted file mode 100644 index 036ca11..0000000 --- a/Validation Service/src/main/java/validator/service/ValidationService.java +++ /dev/null @@ -1,93 +0,0 @@ -/* - This implementation of the validation service: -- Compares all relevant fields between Orion and blockchain responses -- Provides detailed validation errors for each mismatched field -- Handles JSON parsing and null checks -- Uses logging for error tracking -- Returns structured validation results - */ - - package validator.service; - - import com.fasterxml.jackson.databind.JsonNode; - import com.fasterxml.jackson.databind.ObjectMapper; - import com.fasterxml.jackson.core.JsonProcessingException; - import validator.model.ValidationResult; - import jakarta.inject.Singleton; - import validator.client.ValidationHttpClient; - import org.slf4j.Logger; - import org.slf4j.LoggerFactory; - import java.util.ArrayList; - import java.util.List; - - @Singleton - public class ValidationService { - private static final Logger logger = LoggerFactory.getLogger(ValidationService.class); - private final ValidationHttpClient validationHttpClient; - private final ObjectMapper mapper = new ObjectMapper(); - - public ValidationService(ValidationHttpClient validationHttpClient) { - this.validationHttpClient = validationHttpClient; - } - - public ValidationResult validateEntity(String entityId) { - try { - String orionData = validationHttpClient.fetchOrionData(entityId); - String blockchainData = validationHttpClient.fetchBlockchainData(entityId); - - if (orionData == null || orionData.isEmpty()) { - logger.error("No data found from Orion for entityId: {}", entityId); - return new ValidationResult(false, List.of("No data found from Orion")); - } - - if (blockchainData == null || blockchainData.isEmpty()) { - logger.error("No data found from Canis Major for entityId: {}", entityId); - return new ValidationResult(false, List.of("No data found from Canis Major")); - } - - List validationErrors = compareData(orionData, blockchainData); - return new ValidationResult(validationErrors.isEmpty(), validationErrors); - } catch (Exception e) { - logger.error("Error validating entity: {}", entityId, e); - return new ValidationResult(false, List.of("Validation error: " + e.getMessage())); - } - } - - private List compareData(String orionData, String blockchainData) { - List errors = new ArrayList<>(); - try { - JsonNode orionNode = mapper.readTree(orionData); - JsonNode blockchainNode = mapper.readTree(blockchainData); - - JsonNode txReceipts = orionNode.get("TxReceipts").get("value"); - - validateField(txReceipts, blockchainNode, "status", "Status", errors); - validateField(txReceipts, blockchainNode, "blockNumber", "Block Number", errors); - validateField(txReceipts, blockchainNode, "transactionHash", "Transaction Hash", errors); - validateField(txReceipts, blockchainNode, "blockHash", "Block Hash", errors); - validateField(txReceipts, blockchainNode, "from", "From Address", errors); - validateField(txReceipts, blockchainNode, "to", "To Address", errors); - - return errors; - } catch (JsonProcessingException e) { - logger.error("Error parsing JSON data", e); - errors.add("Error parsing JSON data: " + e.getMessage()); - return errors; - } - } - - private void validateField(JsonNode orion, JsonNode blockchain, String fieldName, String displayName, List errors) { - JsonNode orionValue = orion.get(fieldName); - JsonNode blockchainValue = blockchain.get(fieldName); - - if (orionValue == null || blockchainValue == null) { - errors.add(displayName + " field missing in one or both responses"); - return; - } - - if (!orionValue.equals(blockchainValue)) { - errors.add(displayName + " mismatch: Orion=" + orionValue + ", Blockchain=" + blockchainValue); - } - } - } - \ No newline at end of file diff --git a/it/docker-compose/clean.sh b/it/docker-compose/clean.sh index 5365fad..a3a4d6b 100755 --- a/it/docker-compose/clean.sh +++ b/it/docker-compose/clean.sh @@ -1,12 +1,38 @@ -#Stop and remove all Docker containers: -docker stop $(sudo docker ps -aq) -docker rm $(sudo docker ps -aq) -docker container prune +#!/bin/bash -#Remove all created volumes: -docker volume rm $(sudo docker volume ls -q) -docker volume prune +# Function to clean delete Docker Compose resources +clean_the_docker_compose() { + echo "Cleaning up Docker Compose resources..." + sudo docker compose -f docker-compose-env.yaml -f docker-compose-java.yaml down -v + echo "Docker Compose resources cleaned up." +} -#Remove all Docker custom networks: -docker network prune -f +# Function to delete all Docker resources +delete_all() { + echo "Removing all docker resources from the machine..." + sudo docker stop $(docker ps -aq) # Stop all running containers + sudo docker rm $(docker ps -aq) # Remove all containers + sudo docker volume rm $(docker volume ls -q) # Remove all volumes + sudo docker network prune -f # Remove all networks + echo "All Docker resources deleted." +} + +# Prompt the user for their choice +echo "Select an option:" +echo "1) Clean the docker compose" +echo "2) Remove all docker resources from the machine" +read -p "Enter your choice (1 or 2): " choice + +# Execute the corresponding function based on user input +case $choice in + 1) + clean_the_docker_compose + ;; + 2) + delete_all + ;; + *) + echo "Invalid choice. Please enter 1 or 2." + ;; +esac diff --git a/pom.xml b/pom.xml index 75a3c36..d30edb7 100644 --- a/pom.xml +++ b/pom.xml @@ -5,6 +5,7 @@ org.fiware canis-major 1.0.0 + pom io.micronaut @@ -705,4 +706,8 @@ + + canis-major + validation-service + diff --git a/src/main/java/validator/README.md b/src/main/java/validator/README.md new file mode 100644 index 0000000..93f3672 --- /dev/null +++ b/src/main/java/validator/README.md @@ -0,0 +1,213 @@ +# Canis Major test + +Canis Major serves as a blockchain adaptor within the FIWARE ecosystem, providing secure data persistence across blockchain networks and the Context Broker. The workflow is straightforward: + - clients submit transactions containing payload data and wallet credentials. + - Once validated, the data is stored in an ETSI Broker (e.g., Orion-LD Broker) and simultaneously processed into a Merkle tree structure for blockchain integration. + - The system then signs the transaction using the provided wallet credentials and submits it to an Oketh-compatible blockchain. + + ## Integration tests + Let's explore the practical implementation through a series of test commands. First, it is needed to execute the integration tests, by running the following commands: + + ```shell + cd it + docker-compose -f docker-compose/docker-compose-env.yaml -f docker-compose/docker-compose-java.yaml up + NGSI_ADDRESS=localhost:4000 mvn clean test + ``` + +## Entity creation +After running the integartion tests, create an entity by sending this POST request to Canis Major: + +```shell +curl --location 'http://localhost:4000/ngsi-ld/v1/entities/' \ +--header 'Link: ; rel="http://www.w3.org/ns/json-ld#context"; type="application/ld+json"' \ +--header 'Wallet-Type: vault' \ +--header 'Wallet-Token: vault-plaintext-root-token' \ +--header 'Wallet-Address: http://vault:8200/v1/ethereum/accounts/mira' \ +--header 'Content-Type: application/json' \ +--header 'Accept: application/json' \ +--header 'NGSILD-TENANT: orion' \ +--data '{ + "id": "urn:ngsi-ld:Building:warehouse001", + "type": "Building", + "category": { + "type": "Property", + "value": ["warehouse"] + }, + "address": { + "type": "Property", + "value": { + "streetAddress": "Alexanderplatz 2", + "addressRegion": "Berlin", + "addressLocality": "Mitte", + "postalCode": "10178" + } + } +}' +``` + +### Wallet HTTP Headers +The headers included in the HTTP request are crucial for interacting with the blockchain. Here's a breakdown of each component and its significance: + +#### Wallet-Type +This header specifies the type of wallet service being used, in this case, "vault". By specifying the wallet type, the system can ensure that it uses the appropriate methods and protocols for that specific wallet service. + +#### Wallet-Token +This header provides an authentication token for accessing the vault service. This token ensures that only authorized users can access the wallet's functionalities, such as retrieving private keys or signing transactions. Without proper authentication, the system would be vulnerable to unauthorized access. + +#### Wallet-Address +This header points to the specific Ethereum account associated with the wallet. The wallet address is a unique identifier for an Ethereum account. It is used to send and receive transactions on the Ethereum blockchain. By specifying the wallet address, the system knows which account to interact with for operations such as sending tokens, signing transactions, or querying account balances. + +### Content Headers +- Content-Type: Declares the request body format as JSON +- Accept: Indicates the expected response format as JSON + +## Retrieve the entity types in the Context broker +To retrieve the available entity types in the context broker run the following command: + +```shell +curl -L 'http://localhost:1026/ngsi-ld/v1/types' \ +-H 'Link: ; rel="http://www.w3.org/ns/json-ld#context"; type="application/ld+json"' \ +-H 'NGSILD-Tenant: orion' +``` +The command returns the following response, demonstrating the available entity types in the Context Broker: +```json +{ + "@context":"https://raw.githubusercontent.com/smart-data-models/dataModel.DistributedLedgerTech/master/context.jsonld","id":"urn:ngsi-ld:EntityTypeList:d77ccfa0-b3cd-11ef-ae1b-0242ac120005","type":"EntityTypeList","typeList":["DLTtxReceipt"] +} +``` +The @context is using a definition on smart data models for a DLTtxReceipt and the NGSILD-Tenant is defined in the start-up of the tests. + +## Retrieve data from Canis Major +To retrieve detailed receipt information for a specific building entity from the Canis Major, use the following HTTP request: + +```shell +curl -L 'http://localhost:4000/ngsi-ld/v1/entities/urn:ngsi-ld:Building:warehouse001' \ +-H 'Link: ; rel="http://www.w3.org/ns/json-ld#context"; type="application/ld+json"' \ +-H 'Accept: application/json' | jq '.' +``` + +The output will be similar to following response: + +```json +{ + "blockHash": "0x619433204be327dda0d4722482e44310d2f3807e1a23b1fc097ca809f7afb421", + "blockNumber": 193, + "blockNumberRaw": "0xc1", + "cumulativeGasUsed": 23866, + "cumulativeGasUsedRaw": "0x5d3a", + "from": "0xd9fe663797b75d0b3897d55d35e0b4e72307a63f", + "gasUsed": 23866, + "gasUsedRaw": "0x5d3a", + "logs": [ + { + "address": "0x476059cd57800db8eb88f67c2aa38a6fcf8251e0", + "blockHash": "0x619433204be327dda0d4722482e44310d2f3807e1a23b1fc097ca809f7afb421", + "blockNumber": 193, + "blockNumberRaw": "0xc1", + "data": "0x", + "logIndex": 0, + "logIndexRaw": "0x0", + "removed": false, + "topics": [ + "0xa3865c00e01495fc2b86502cae36a4edb139f748682e7d80725a3d6571a482fa", + "0xf85c55023889d6ec0b723c8220471171435040e275059f8e222dfa57a50f0dd5", + "0x33868c71f7186ea974685b553d8eee61eb1e95e0a2c70ed54fcd13db67920f74" + ], + "transactionHash": "0x83f05282a30f77dcfe52ca029b10ba80aac5dad5cc46f5dc437b79e860e4dd65", + "transactionIndex": 0, + "transactionIndexRaw": "0x0", + "type": "mined" + } + ], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000020000000000000008000200000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000004000000000000000000020000000000000000000000000000000000000000000000000010000100000000000000000000000000000000000000000000000000008000000000000000000000420000000000000000000000000000000000000000000000000000000000000000000000000", + "status": "0x1", + "statusOK": true, + "to": "0x476059cd57800db8eb88f67c2aa38a6fcf8251e0", + "transactionHash": "0x83f05282a30f77dcfe52ca029b10ba80aac5dad5cc46f5dc437b79e860e4dd65", + "transactionIndexRaw": "0x0" +} +``` + +The JSON structure shows a blockchain transaction receipt with the following details: +#### Transaction Information +- Block Number: 193 (0xc1) +- Transaction Status: Successful (statusOK: true) +- Transaction Hash: 0x83f05282a30f77dcfe52ca029b10ba80aac5dad5cc46f5dc437b79e860e4dd65 +#### Transaction Participants +- From Address: 0xd9fe663797b75d0b3897d55d35e0b4e72307a63f +- To Address: 0x476059cd57800db8eb88f67c2aa38a6fcf8251e0 + +## Retrieve data from the context broker +To retrieve specific DLT transaction receipts from the the context broker, we'll use an NGSI-LD query that filters entities by type and property values. The query targets entities of type DLTtxReceipt and filters them based on the refEntity property matching the Building entity "urn:ngsi-ld:Building:warehouse001". The attrs=TxReceipts parameter in the NGSI-LD query acts as a data filter to limit the response to only include the TxReceipts property, excluding all other properties of the entity and reducing response payload size. + +```shell +curl -L 'http://localhost:1026/ngsi-ld/v1/entities/?type=DLTtxReceipt&q=refEntity%3D%3D%22urn%3Angsi-ld%3ABuilding%3Awarehouse001%22&attrs=TxReceipts' \ +-H 'Link: ; rel="http://www.w3.org/ns/json-ld#context"; type="application/ld+json"' \ +-H 'NGSILD-Tenant: orion' +``` + +The outpot will be similar to the following json response: + +```json +{ + "@context": "https://raw.githubusercontent.com/smart-data-models/dataModel.DistributedLedgerTech/master/context.jsonld", + "id": "urn:ngsi-ld:dlttxreceipt:0xa46d2e3b190d36fbb8af5d0a1c212d8036cf007e6ec4d1309904e052d25e5499", + "type": "DLTtxReceipt", + "TxReceipts": { + "type": "Property", + "value": { + "blockHash": "0x2022f801a1c094bd3a893ac6f3087c17bb9c673a6b5663f353eded030e1e7161", + "blockNumber": 192, + "blockNumberRaw": "0xc0", + "cumulativeGasUsed": 23866, + "cumulativeGasUsedRaw": "0x5d3a", + "from": "0x34e5b3f990e55d0651b35c817bafb89d2877cb95", + "gasUsed": 23866, + "gasUsedRaw": "0x5d3a", + "logs": [ + { + "address": "0x476059cd57800db8eb88f67c2aa38a6fcf8251e0", + "blockHash": "0x2022f801a1c094bd3a893ac6f3087c17bb9c673a6b5663f353eded030e1e7161", + "blockNumber": 192, + "blockNumberRaw": "0xc0", + "data": "0x", + "logIndex": 0, + "logIndexRaw": "0x0", + "removed": false, + "topics": [ + "0xa3865c00e01495fc2b86502cae36a4edb139f748682e7d80725a3d6571a482fa", + "0xf85c55023889d6ec0b723c8220471171435040e275059f8e222dfa57a50f0dd5", + "0xefc7a4d4c2393e9b62ac4b93b7d199c71e0bb103fdb00fc1b37d7949ad886ddd" + ], + "transactionHash": "0xa46d2e3b190d36fbb8af5d0a1c212d8036cf007e6ec4d1309904e052d25e5499", + "transactionIndex": 0, + "transactionIndexRaw": "0x0", + "type": "mined" + } + ], + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000000000020000000000000008000200000000000000000000000000000000000000000000000100000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000008000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000420000000000000000000000000000000000000000200000000000000000000000000000000", + "status": "0x1", + "statusOK": true, + "to": "0x476059cd57800db8eb88f67c2aa38a6fcf8251e0", + "transactionHash": "0xa46d2e3b190d36fbb8af5d0a1c212d8036cf007e6ec4d1309904e052d25e5499", + "transactionIndex": 0, + "transactionIndexRaw": "0x0" + } + } +} +``` + +This JSON response shows a DLTtxReceipt (Distributed Ledger Technology Transaction Receipt) entity in NGSI-LD format: + +### Transaction Details + +- Entity ID: `urn:ngsi-ld:dlttxreceipt:0xa46d2e3b190d36fbb8af5d0a1c212d8036cf007e6ec4d1309904e052d25e5499` +- Block Number: 192 (0xc0) +- Transaction Status: Successful (statusOK: true) +- Block Hash: `0x2022f801a1c094bd3a893ac6f3087c17bb9c673a6b5663f353eded030e1e7161` +- Transaction Hash: `0xa46d2e3b190d36fbb8af5d0a1c212d8036cf007e6ec4d1309904e052d25e5499` + +### Transaction Participants +- From Address: `0x34e5b3f990e55d0651b35c817bafb89d2877cb95` +- To Address: `0x476059cd57800db8eb88f67c2aa38a6fcf8251e0` + diff --git a/Validation Service/README.md b/validation-service/README.md similarity index 100% rename from Validation Service/README.md rename to validation-service/README.md diff --git a/validation-service/pom.xml b/validation-service/pom.xml new file mode 100644 index 0000000..f07f739 --- /dev/null +++ b/validation-service/pom.xml @@ -0,0 +1,91 @@ + + + 4.0.0 + + + + org.fiware + canis-major + 1.0.0 + + + validation-service + jar + + + 17 + 1.0.0 + + + + + + org.fiware.ngsi-ld + ngsi-ld-java-mapping + 1.0.0 + + + + + io.micronaut + micronaut-inject + + + io.micronaut + micronaut-http-client + + + io.micronaut + micronaut-runtime + + + io.micronaut + micronaut-validation + + + + + org.projectlombok + lombok + 1.18.32 + provided + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.1 + + ${jdk.version} + ${jdk.version} + + + + + org.openapitools + openapi-generator-maven-plugin + 5.3.0 + + + + generate + + + ${project.basedir}/src/main/resources/api.yml + java + ${project.build.directory}/generated-sources/openapi + org.fiware.validation_service.api + org.fiware.validation_service.model + org.fiware.validation_service.invoker + + + + + + + diff --git a/validation-service/src/main/java/org/fiware/validation_service/client/ValidationHttpClient.java b/validation-service/src/main/java/org/fiware/validation_service/client/ValidationHttpClient.java new file mode 100644 index 0000000..f8f37d0 --- /dev/null +++ b/validation-service/src/main/java/org/fiware/validation_service/client/ValidationHttpClient.java @@ -0,0 +1,65 @@ +package org.fiware.validation_service.config; + +import io.micronaut.context.annotation.ConfigurationProperties; + + +// Marks this class as a singleton - only one instance will exist in the application +@Singleton +public class ValidationHttpClient { + private final HttpClient client; + private final String tenant; // Field for tenant configuration + private final String contextBrokerUrl; // Field for broker URL configuration + private final String canisMajorUrl; // Field for Canis Major URL configuration + + // Updated constructor to accept tenant, broker URL, and Canis Major URL as parameters + public ValidationHttpClient(String NGSILD_TENANT, String contextBrokerUrl, String canisMajorUrl) { + this.client = HttpClient.newHttpClient(); + this.tenant = NGSILD_TENANT; // Set the tenant + this.brokerUrl = contextBrokerUrl; // Set the broker URL + this.canisMajorUrl = canisMajorUrl; // Set the Canis Major URL + } + + // Method to fetch data from the broker + public CompletableFuture fetchBrokerData(String entityId) { + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create(brokerUrl + "/ngsi-ld/v1/entities/?type=DLTtxReceipt&q=refEntity==" + entityId)) // Use the broker URL + .header("Link", "; rel=\"http://www.w3.org/ns/json-ld#context\"; type=\"application/ld+json\"") + .header("NGSILD-Tenant", tenant) // Use the tenant + .header("Accept", "application/json") + .GET() + .build(); + + return client.sendAsync(request, HttpResponse.BodyHandlers.ofString()) + .thenApply(HttpResponse::body); + } + + // Method to fetch data from a blockchain service + public String fetchBlockchainData(String entityId) throws Exception { + // Builds an HTTP request: + HttpRequest request = HttpRequest.newBuilder() + // Sets the URI for the blockchain service endpoint + .uri(new URI("http://localhost:4000/entity/" + entityId)) + // Sets Accept header to receive JSONresponse + .header("Accept", "application/json") + // Specifies this is a GET request + .GET() + // Finalizes the request building + .build(); + + // Sends the request and returns the response body as a String + return client.send(request, HttpResponse.BodyHandlers.ofString()).body(); + } + + // Method to fetch data from Canis Major + public CompletableFuture fetchCanisMajorData(String entityId) { + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create(canisMajorUrl + "/ngsi-ld/v1/entities/" + entityId)) // Use the Canis Major URL + .header("Accept", "application/json") + .header("NGSILD-Tenant", tenant) // Use the tenant + .GET() + .build(); + + return client.sendAsync(request, HttpResponse.BodyHandlers.ofString()) + .thenApply(HttpResponse::body); + } +} diff --git a/Validation Service/src/main/java/validator/controller/ValidationController.java b/validation-service/src/main/java/org/fiware/validation_service/controller/ValidationController.java similarity index 55% rename from Validation Service/src/main/java/validator/controller/ValidationController.java rename to validation-service/src/main/java/org/fiware/validation_service/controller/ValidationController.java index ade87aa..0b2ec13 100644 --- a/Validation Service/src/main/java/validator/controller/ValidationController.java +++ b/validation-service/src/main/java/org/fiware/validation_service/controller/ValidationController.java @@ -2,10 +2,16 @@ import io.micronaut.http.annotation.*; import io.micronaut.http.HttpResponse; +import io.micronaut.http.HttpStatus; +//import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import validator.service.ValidationService; import validator.model.ValidationResult; -@Controller("/service/v1/validation") + + +@Slf4j // for handling logging through Lombok +@Controller("/service/v1/validation") //Marks this class as a Micronaut web controller public class ValidationController { private final ValidationService validationService; @@ -15,9 +21,14 @@ public ValidationController(ValidationService validationService) { @Post("/compare") public HttpResponse compareResponses(@Body CompareRequest request) { - // Call the validation service to perform the validation - ValidationResult result = validationService.validateEntity(request.getEntityId()); - return HttpResponse.ok(result); + try { + // Call the validation service to perform the validation + ValidationResult result = validationService.validateEntity(request.getEntityId()); + return HttpResponse.ok(result); + } catch (ValidationException e) { + log.error("Error fetching data from broker: {}", e.getMessage()); + return HttpResponse.status(HttpStatus.BAD_GATEWAY); // Return HTTP 502 Bad Gateway on error + } } // Inner class to represent the request body diff --git a/Validation Service/src/main/java/validator/model/ValidationResult.java b/validation-service/src/main/java/org/fiware/validation_service/model/ValidationResult.java similarity index 100% rename from Validation Service/src/main/java/validator/model/ValidationResult.java rename to validation-service/src/main/java/org/fiware/validation_service/model/ValidationResult.java diff --git a/validation-service/src/main/java/org/fiware/validation_service/service/ValidationService.java b/validation-service/src/main/java/org/fiware/validation_service/service/ValidationService.java new file mode 100644 index 0000000..cfc99c3 --- /dev/null +++ b/validation-service/src/main/java/org/fiware/validation_service/service/ValidationService.java @@ -0,0 +1,92 @@ +/* + This implementation of the validation service: +- Compares all relevant fields between Orion and blockchain responses +- Provides detailed validation errors for each mismatched field +- Handles JSON parsing and null checks +- Uses logging for error tracking +- Returns structured validation results + */ + +package validator.service; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.core.JsonProcessingException; +import validator.model.ValidationResult; +import jakarta.inject.Singleton; +import validator.client.ValidationHttpClient; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import java.util.ArrayList; +import java.util.List; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@Slf4j +@RequiredArgsConstructor +public class ValidationService { + private static final Logger logger = LoggerFactory.getLogger(ValidationService.class); + private final ValidationHttpClient validationHttpClient; + private final ObjectMapper mapper = new ObjectMapper(); + + public ValidationResult validateEntity(String entityId) { + try { + String orionData = validationHttpClient.fetchOrionData(entityId); + String blockchainData = validationHttpClient.fetchBlockchainData(entityId); + + if (orionData == null || orionData.isEmpty()) { + logger.error("No data found from Orion for entityId: {}", entityId); + return new ValidationResult(false, List.of("No data found from Orion")); + } + + if (blockchainData == null || blockchainData.isEmpty()) { + logger.error("No data found from Canis Major for entityId: {}", entityId); + return new ValidationResult(false, List.of("No data found from Canis Major")); + } + + List validationErrors = compareData(orionData, blockchainData); + return new ValidationResult(validationErrors.isEmpty(), validationErrors); + } catch (Exception e) { + logger.error("Error validating entity: {}", entityId, e); + return new ValidationResult(false, List.of("Validation error: " + e.getMessage())); + } + } + + private List compareData(String orionData, String blockchainData) { + List errors = new ArrayList<>(); + try { + JsonNode orionNode = mapper.readTree(orionData); + JsonNode blockchainNode = mapper.readTree(blockchainData); + + JsonNode txReceipts = orionNode.get("TxReceipts").get("value"); + + validateField(txReceipts, blockchainNode, "status", "Status", errors); + validateField(txReceipts, blockchainNode, "blockNumber", "Block Number", errors); + validateField(txReceipts, blockchainNode, "transactionHash", "Transaction Hash", errors); + validateField(txReceipts, blockchainNode, "blockHash", "Block Hash", errors); + validateField(txReceipts, blockchainNode, "from", "From Address", errors); + validateField(txReceipts, blockchainNode, "to", "To Address", errors); + + return errors; + } catch (JsonProcessingException e) { + logger.error("Error parsing JSON data", e); + errors.add("Error parsing JSON data: " + e.getMessage()); + return errors; + } + } + + private void validateField(JsonNode orion, JsonNode blockchain, String fieldName, String displayName, List errors) { + JsonNode orionValue = orion.get(fieldName); + JsonNode blockchainValue = blockchain.get(fieldName); + + if (orionValue == null || blockchainValue == null) { + errors.add(displayName + " field missing in one or both responses"); + return; + } + + if (!orionValue.equals(blockchainValue)) { + errors.add(displayName + " mismatch: Orion=" + orionValue + ", Blockchain=" + blockchainValue); + } + } +} + \ No newline at end of file diff --git a/validation-service/src/main/resources/api.yml b/validation-service/src/main/resources/api.yml new file mode 100644 index 0000000..35ff993 --- /dev/null +++ b/validation-service/src/main/resources/api.yml @@ -0,0 +1,104 @@ +openapi: 3.0.0 +info: + title: Canis Major Validation Service + version: 1.0.0 + description: API for validating entities between Orion and Blockchain responses. + contact: + email:asma.taamallah@fiware.org +paths: + /validation/entity/{entityId}/transactions + get: + summary: Retrieve entity transactions + operationId: getEntityTransactions + tags: + - Validation + parameters: + - name: entityId + in: path + required: true + description: The ID of the entity to retrieve transactions for + schema: + type: string + responses: + '200': + description: Successful retrieval of entity transactions + content: + application/json: + schema: + $ref: '#/components/schemas/EntityTransactionList' + '404': + description: Entity not found + '500': + description: Internal server error + /validation/entity/{entityId}/validate: + post: + summary: Validate an entity + operationId: validateEntity + tags: + - Validation + parameters: + - name: entityId + in: path + required: true + description: The ID of the entity to validate + schema: + type: string + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ValidationRequest' + responses: + '200': + description: Validation successful + content: + application/json: + schema: + $ref: '#/components/schemas/ValidationResult' + '400': + description: Bad request + '404': + description: Entity not found + '500': + description: Internal server error +components: + schemas: + EntityTransactionList: + type: object + properties: + transactions: + type: array + items: + $ref: '#/components/schemas/EntityTransaction' + EntityTransaction: + type: object + properties: + id: + type: string + format: uri + status: + type: string + blockNumber: + type: integer + transactionHash: + type: string + from: + type: string + to: + type: string + ValidationRequest: + type: object + properties: + additionalData: + type: string + ValidationResult: + type: object + properties: + valid: + type: boolean + errors: + type: array + items: + type: string + diff --git a/validation-service/src/main/resources/application.yml b/validation-service/src/main/resources/application.yml new file mode 100644 index 0000000..adb7c4a --- /dev/null +++ b/validation-service/src/main/resources/application.yml @@ -0,0 +1,4 @@ +ngsi: + contextBrokerUrl: "127.0.0.1:1026" # Replace with your context broker URL + canisMajorUrl: "127.0.0.1:4000" # Replace with your Canis Major URL + NGSILD_TENANT: "orion" # Replace with your tenant ID \ No newline at end of file diff --git a/Validation Service/validation_service.png b/validation-service/validation_service.png similarity index 100% rename from Validation Service/validation_service.png rename to validation-service/validation_service.png From 8d673d678dc17cb4da3ea26282c4c31a81791cec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fernando=20L=C3=B3pez=20Aguilar?= Date: Thu, 13 Feb 2025 10:13:32 +0100 Subject: [PATCH 17/19] Update clean.sh --- it/docker-compose/clean.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/it/docker-compose/clean.sh b/it/docker-compose/clean.sh index a3a4d6b..f7e4efd 100755 --- a/it/docker-compose/clean.sh +++ b/it/docker-compose/clean.sh @@ -1,6 +1,6 @@ #!/bin/bash -# Function to clean delete Docker Compose resources +# Function to delete Docker Compose resources (containers, volumes, and networks) clean_the_docker_compose() { echo "Cleaning up Docker Compose resources..." sudo docker compose -f docker-compose-env.yaml -f docker-compose-java.yaml down -v @@ -19,7 +19,7 @@ delete_all() { # Prompt the user for their choice echo "Select an option:" -echo "1) Clean the docker compose" +echo "1) Clean the docker compose" echo "2) Remove all docker resources from the machine" read -p "Enter your choice (1 or 2): " choice From 850a0483d54ab0ddbfa3faef82ac12589a5cc34b Mon Sep 17 00:00:00 2001 From: asmataamallah25 Date: Thu, 13 Feb 2025 12:00:20 +0100 Subject: [PATCH 18/19] Update Validation Service documentation --- validation-service/README.md | 2 +- validation-service/Validation_process.png | Bin 0 -> 53643 bytes 2 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 validation-service/Validation_process.png diff --git a/validation-service/README.md b/validation-service/README.md index 5cd0975..4502eea 100644 --- a/validation-service/README.md +++ b/validation-service/README.md @@ -2,7 +2,7 @@ The validation service for Canis Major performs security checks to verify transaction integrity between the context broker and Canis Major blockchain transactions. -![Validation service](validation_service.png) +![Validation service](Validation_process.png) ## Service Setup diff --git a/validation-service/Validation_process.png b/validation-service/Validation_process.png new file mode 100644 index 0000000000000000000000000000000000000000..22086854889654c0d8d470af912cee12b45e60b8 GIT binary patch literal 53643 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 NgQu&X%Q~loCIDQA%Uu8f literal 0 HcmV?d00001 From 1f43986c234baa181c5535ebd49b47a04d814ea4 Mon Sep 17 00:00:00 2001 From: asmataamallah25 Date: Tue, 4 Mar 2025 15:56:03 +0100 Subject: [PATCH 19/19] Update validation source code and documentation --- validation-service/README.md | 47 +++- validation-service/pom.xml | 148 +++++++---- .../client/ValidationHttpClient.java | 103 ++++---- .../client/config/NgsiConfig.java | 30 +++ .../controller/ValidationController.java | 54 ++-- .../model/ValidationResult.java | 30 --- .../service/ValidationService.java | 160 +++++++----- validation-service/src/main/resources/api.yml | 104 -------- .../src/main/resources/application.yml | 6 +- .../src/main/resources/ngsi-ld-api.yaml | 234 ++++++++++++++++++ .../src/main/resources/validation-api.yaml | 64 +++++ .../src/test/java/IntegrationTest.java | 114 +++++++++ validation-service/validation_service.png | Bin 130008 -> 0 bytes 13 files changed, 747 insertions(+), 347 deletions(-) create mode 100644 validation-service/src/main/java/org/fiware/validation_service/client/config/NgsiConfig.java delete mode 100644 validation-service/src/main/java/org/fiware/validation_service/model/ValidationResult.java delete mode 100644 validation-service/src/main/resources/api.yml create mode 100644 validation-service/src/main/resources/ngsi-ld-api.yaml create mode 100644 validation-service/src/main/resources/validation-api.yaml create mode 100644 validation-service/src/test/java/IntegrationTest.java delete mode 100644 validation-service/validation_service.png diff --git a/validation-service/README.md b/validation-service/README.md index 4502eea..14c1dbf 100644 --- a/validation-service/README.md +++ b/validation-service/README.md @@ -1,22 +1,45 @@ # Validation service -The validation service for Canis Major performs security checks to verify transaction integrity between the context broker and Canis Major blockchain transactions. +## Overview +The Validation Service is a microservice that verifies transaction data integrity between Orion-LD Context Broker and Canis Major DLT Adapter. It provides a REST API for validating that blockchain transaction data maintains its integrity across both systems, performing security checks to ensure the authenticity and consistency of transaction records and that the transaction information has not been tampered with or corrupted when stored in the DLT. + +## Validation Process +The validation service architecture ![Validation service](Validation_process.png) +The diagram shows the flow of transaction validation between the Client Domain and Canis Major Domain: + +- The process begins when a client initiates validation by sending a request with an Entity ID to the Canis Major Validator + +- The validator retrieves transaction details from both Canis Major and the Context Broker + +- The validator performs integrity checks on the transaction data + +- The validation results are returned to the client + +## Validation Checks +The Canis Major Validator performs a comprehensive field-by-field comparison of transaction data obtained from both the Context Broker and Canis Major. + +The service executes the following data integrity verifications: + +1. **Status Check** + - Verifies if the transaction status matches between both systems. -## Service Setup -The validation service implements a three-step verification protocol. +2. **Block Information** + - Confirms block number consistency. + - Validates block hash matches. -### Initial Validation Request -The client initiates the verification process by submitting a POST request to the Canis Major Validation service. +3. **Transaction Details** + - Ensures transaction hash is identical. The hash functions as a digital fingerprint that uniquely identifies a transaction. Any alteration to the transaction data, no matter how small, would result in a completely different hash. + - Verifies sender ("from") address matches. + - Confirms recipient ("to") address is consistent. -### Dual-Source Data Retrieval -The Canis Major Validator executes two GET requests, based on the `entityId`, to: -- Canis Major -- The Context Broker +> [!NOTE] +> +> Each validation failure generates a specific discrepancy record containing: +> - The field name where the mismatch occurred +> - The value reported by Orion-LD +> - The value reported by Canis Major -Both requests retrieve the entity information using the specified entity identifier as the query parameter. -### Data Integrity Analysis -The validator performs a systematic comparison of the entity representations obtained from both sources. diff --git a/validation-service/pom.xml b/validation-service/pom.xml index f07f739..91a4001 100644 --- a/validation-service/pom.xml +++ b/validation-service/pom.xml @@ -3,89 +3,145 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 - + - org.fiware canis-major - 1.0.0 + 1.0.0 validation-service - jar + + - 17 - 1.0.0 + 3.2.7 + 5.1.0 + 2.2.1 + + + + jitpack.io + https://jitpack.io + + + - org.fiware.ngsi-ld + com.github.wistefan ngsi-ld-java-mapping - 1.0.0 + main-SNAPSHOT - - - io.micronaut - micronaut-inject - + io.micronaut - micronaut-http-client + micronaut-validation + compile io.micronaut - micronaut-runtime + micronaut-http-server-netty + compile io.micronaut - micronaut-validation + micronaut-http-client - + org.projectlombok lombok - 1.18.32 provided + + + + org.junit.jupiter + junit-jupiter-api + test + + + + io.swagger + swagger-annotations + 1.6.5 + + + org.openapitools + jackson-databind-nullable + 0.2.0 + + - org.apache.maven.plugins - maven-compiler-plugin - 3.8.1 - - ${jdk.version} - ${jdk.version} - + org.openapitools + openapi-generator-maven-plugin + 6.2.1 + + + canis-major-api + none + + + openapi-ngsi-json + none + + + + generate.ngsi-ld-client + + generate + + + ${project.basedir}/src/main/resources/ngsi-ld-api.yaml + java + native + org.fiware.validation_service.client.api + org.fiware.validation_service.client.model + org.fiware.validation_service.client.invoker + false + false + true + + src/gen/java/main + true + true + + + + + generate-validation-api + + generate + + + ${project.basedir}/src/main/resources/validation-api.yaml + java + native + org.fiware.validation_service.api + org.fiware.validation_service.model + org.fiware.validation_service.invoker + false + false + true + + true + src/gen/java/main + java8 + true + + + + - - - org.openapitools - openapi-generator-maven-plugin - 5.3.0 - - - - generate - - - ${project.basedir}/src/main/resources/api.yml - java - ${project.build.directory}/generated-sources/openapi - org.fiware.validation_service.api - org.fiware.validation_service.model - org.fiware.validation_service.invoker - - - - diff --git a/validation-service/src/main/java/org/fiware/validation_service/client/ValidationHttpClient.java b/validation-service/src/main/java/org/fiware/validation_service/client/ValidationHttpClient.java index f8f37d0..9412b18 100644 --- a/validation-service/src/main/java/org/fiware/validation_service/client/ValidationHttpClient.java +++ b/validation-service/src/main/java/org/fiware/validation_service/client/ValidationHttpClient.java @@ -1,65 +1,62 @@ -package org.fiware.validation_service.config; +package org.fiware.validation_service.client; -import io.micronaut.context.annotation.ConfigurationProperties; +import jakarta.inject.Inject; +import jakarta.inject.Singleton; +import org.fiware.validation_service.client.api.EntitiesApi; +import org.fiware.validation_service.client.invoker.ApiClient; +import org.fiware.validation_service.client.invoker.ApiException; +import org.fiware.validation_service.client.model.Entity; +import org.fiware.validation_service.config.NgsiConfig; +import java.util.concurrent.CompletableFuture; -// Marks this class as a singleton - only one instance will exist in the application -@Singleton +@Singleton public class ValidationHttpClient { - private final HttpClient client; - private final String tenant; // Field for tenant configuration - private final String contextBrokerUrl; // Field for broker URL configuration - private final String canisMajorUrl; // Field for Canis Major URL configuration - // Updated constructor to accept tenant, broker URL, and Canis Major URL as parameters - public ValidationHttpClient(String NGSILD_TENANT, String contextBrokerUrl, String canisMajorUrl) { - this.client = HttpClient.newHttpClient(); - this.tenant = NGSILD_TENANT; // Set the tenant - this.brokerUrl = contextBrokerUrl; // Set the broker URL - this.canisMajorUrl = canisMajorUrl; // Set the Canis Major URL + private final EntitiesApi orionLdClient; + private final EntitiesApi canisMajorClient; + private final String contextUrl; + private final String tenant; + + @Inject + public ValidationHttpClient(NgsiConfig ngsiConfig) { + // Initialize Orion-LD client + ApiClient orionApiClient = new ApiClient(); + orionApiClient.setBasePath("http://" + ngsiConfig.getContextBrokerUrl()); + this.orionLdClient = new EntitiesApi(orionApiClient); + + // Initialize Canis Major client + ApiClient canisMajorApiClient = new ApiClient(); + canisMajorApiClient.setBasePath("http://" + ngsiConfig.getCanisMajorUrl()); + this.canisMajorClient = new EntitiesApi(canisMajorApiClient); + + this.contextUrl = ngsiConfig.getContextUrl(); + this.tenant = ngsiConfig.getNgsildTenant(); } - // Method to fetch data from the broker - public CompletableFuture fetchBrokerData(String entityId) { - HttpRequest request = HttpRequest.newBuilder() - .uri(URI.create(brokerUrl + "/ngsi-ld/v1/entities/?type=DLTtxReceipt&q=refEntity==" + entityId)) // Use the broker URL - .header("Link", "; rel=\"http://www.w3.org/ns/json-ld#context\"; type=\"application/ld+json\"") - .header("NGSILD-Tenant", tenant) // Use the tenant - .header("Accept", "application/json") - .GET() - .build(); - - return client.sendAsync(request, HttpResponse.BodyHandlers.ofString()) - .thenApply(HttpResponse::body); - } - - // Method to fetch data from a blockchain service - public String fetchBlockchainData(String entityId) throws Exception { - // Builds an HTTP request: - HttpRequest request = HttpRequest.newBuilder() - // Sets the URI for the blockchain service endpoint - .uri(new URI("http://localhost:4000/entity/" + entityId)) - // Sets Accept header to receive JSONresponse - .header("Accept", "application/json") - // Specifies this is a GET request - .GET() - // Finalizes the request building - .build(); - - // Sends the request and returns the response body as a String - return client.send(request, HttpResponse.BodyHandlers.ofString()).body(); + // Method to fetch an entity by its ID from Orion-LD + public CompletableFuture fetchEntityById(String entityId) { + return CompletableFuture.supplyAsync(() -> { + try { + String linkHeader = "<" + contextUrl + ">; rel=\"http://www.w3.org/ns/json-ld#context\"; type=\"application/ld+json\""; + return (Entity) orionLdClient.retrieveEntityById(entityId, null, linkHeader, tenant); + } catch (ApiException e) { + throw new RuntimeException("Failed to fetch entity: " + e.getMessage(), e); + } + }); } // Method to fetch data from Canis Major - public CompletableFuture fetchCanisMajorData(String entityId) { - HttpRequest request = HttpRequest.newBuilder() - .uri(URI.create(canisMajorUrl + "/ngsi-ld/v1/entities/" + entityId)) // Use the Canis Major URL - .header("Accept", "application/json") - .header("NGSILD-Tenant", tenant) // Use the tenant - .GET() - .build(); - - return client.sendAsync(request, HttpResponse.BodyHandlers.ofString()) - .thenApply(HttpResponse::body); + public CompletableFuture fetchCanisMajorData(String entityId) { + return CompletableFuture.supplyAsync(() -> { + try { + String linkHeader = "<" + contextUrl + ">; rel=\"http://www.w3.org/ns/json-ld#context\"; type=\"application/ld+json\""; + return (Entity) canisMajorClient.retrieveEntityById(entityId, null, linkHeader, tenant); + } catch (ApiException e) { + throw new RuntimeException("Failed to fetch data from Canis Major: " + e.getMessage(), e); + } + }); } + + } diff --git a/validation-service/src/main/java/org/fiware/validation_service/client/config/NgsiConfig.java b/validation-service/src/main/java/org/fiware/validation_service/client/config/NgsiConfig.java new file mode 100644 index 0000000..e26597b --- /dev/null +++ b/validation-service/src/main/java/org/fiware/validation_service/client/config/NgsiConfig.java @@ -0,0 +1,30 @@ +package org.fiware.validation_service.config; + +import io.micronaut.context.annotation.ConfigurationProperties; +import lombok.Data; + +@Data // Lombok annotation to generate getters and setters +@ConfigurationProperties("general") // Binds to the 'general' prefix in application.yml +public class NgsiConfig { + private String contextBrokerUrl; // Field for the context broker URL + private String canisMajorUrl; // Field for the Canis Major URL + private String ngsildTenant; // Field for the tenant configuration + private String contextUrl; // Field for the context URL + + // Explicit getters to ensure they're available + public String getContextBrokerUrl() { + return contextBrokerUrl; + } + + public String getCanisMajorUrl() { + return canisMajorUrl; + } + + public String getNgsildTenant() { + return ngsildTenant; + } + + public String getContextUrl() { + return contextUrl; + } +} diff --git a/validation-service/src/main/java/org/fiware/validation_service/controller/ValidationController.java b/validation-service/src/main/java/org/fiware/validation_service/controller/ValidationController.java index 0b2ec13..b772f74 100644 --- a/validation-service/src/main/java/org/fiware/validation_service/controller/ValidationController.java +++ b/validation-service/src/main/java/org/fiware/validation_service/controller/ValidationController.java @@ -1,47 +1,35 @@ -package validator.controller; +package org.fiware.validation_service.controller; -import io.micronaut.http.annotation.*; import io.micronaut.http.HttpResponse; import io.micronaut.http.HttpStatus; -//import lombok.RequiredArgsConstructor; +import io.micronaut.http.annotation.Controller; +import io.micronaut.http.annotation.Post; +import jakarta.inject.Inject; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import validator.service.ValidationService; -import validator.model.ValidationResult; +import org.fiware.validation_service.model.ValidationResult; +import org.fiware.validation_service.service.ValidationService; +import java.util.NoSuchElementException; - -@Slf4j // for handling logging through Lombok -@Controller("/service/v1/validation") //Marks this class as a Micronaut web controller +@Slf4j +@Controller("/validation") +@RequiredArgsConstructor public class ValidationController { - private final ValidationService validationService; - public ValidationController(ValidationService validationService) { - this.validationService = validationService; - } + private final ValidationService validationService; - @Post("/compare") - public HttpResponse compareResponses(@Body CompareRequest request) { + @Post("/transaction/{entityId}") + public HttpResponse validateTransaction(String entityId) { try { - // Call the validation service to perform the validation - ValidationResult result = validationService.validateEntity(request.getEntityId()); + ValidationResult result = validationService.validateTransaction(entityId); return HttpResponse.ok(result); - } catch (ValidationException e) { - log.error("Error fetching data from broker: {}", e.getMessage()); - return HttpResponse.status(HttpStatus.BAD_GATEWAY); // Return HTTP 502 Bad Gateway on error - } - } - - // Inner class to represent the request body - public static class CompareRequest { - private String entityId; - - // Getter and Setter - public String getEntityId() { - return entityId; - } - - public void setEntityId(String entityId) { - this.entityId = entityId; + } catch (NoSuchElementException e) { + log.error("Transaction not found: {}", e.getMessage()); + return HttpResponse.status(HttpStatus.NOT_FOUND); + } catch (Exception e) { + log.error("Error validating transaction: {}", e.getMessage()); + return HttpResponse.status(HttpStatus.INTERNAL_SERVER_ERROR); } } } diff --git a/validation-service/src/main/java/org/fiware/validation_service/model/ValidationResult.java b/validation-service/src/main/java/org/fiware/validation_service/model/ValidationResult.java deleted file mode 100644 index f68ecbc..0000000 --- a/validation-service/src/main/java/org/fiware/validation_service/model/ValidationResult.java +++ /dev/null @@ -1,30 +0,0 @@ -package validator.model; - -import java.util.List; - -//import java.util.List; - -/*public record ValidationResult(boolean isValid, List errors) { - public String getErrorMessage() { - return String.join(", ", errors); - } -} */ - -public class ValidationResult { - private final boolean valid; - private final List messages; - - - public ValidationResult(boolean valid, List messages){ - this.valid = valid; - this.messages = messages; - } - - public boolean isValid() { //getter - return valid; - } - - public List getMessages() { - return messages; - } -} diff --git a/validation-service/src/main/java/org/fiware/validation_service/service/ValidationService.java b/validation-service/src/main/java/org/fiware/validation_service/service/ValidationService.java index cfc99c3..e73d4bd 100644 --- a/validation-service/src/main/java/org/fiware/validation_service/service/ValidationService.java +++ b/validation-service/src/main/java/org/fiware/validation_service/service/ValidationService.java @@ -1,92 +1,118 @@ -/* - This implementation of the validation service: -- Compares all relevant fields between Orion and blockchain responses -- Provides detailed validation errors for each mismatched field -- Handles JSON parsing and null checks -- Uses logging for error tracking -- Returns structured validation results - */ +package org.fiware.validation_service.service; -package validator.service; - -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.core.JsonProcessingException; -import validator.model.ValidationResult; import jakarta.inject.Singleton; -import validator.client.ValidationHttpClient; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.fiware.validation_service.client.ValidationHttpClient; +import org.fiware.validation_service.client.model.Entity; +import org.fiware.validation_service.model.Discrepancy; +import org.fiware.validation_service.model.ValidationResult; + +import java.time.OffsetDateTime; import java.util.ArrayList; import java.util.List; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; @Slf4j +@Singleton @RequiredArgsConstructor public class ValidationService { - private static final Logger logger = LoggerFactory.getLogger(ValidationService.class); - private final ValidationHttpClient validationHttpClient; - private final ObjectMapper mapper = new ObjectMapper(); - - public ValidationResult validateEntity(String entityId) { - try { - String orionData = validationHttpClient.fetchOrionData(entityId); - String blockchainData = validationHttpClient.fetchBlockchainData(entityId); - - if (orionData == null || orionData.isEmpty()) { - logger.error("No data found from Orion for entityId: {}", entityId); - return new ValidationResult(false, List.of("No data found from Orion")); - } - if (blockchainData == null || blockchainData.isEmpty()) { - logger.error("No data found from Canis Major for entityId: {}", entityId); - return new ValidationResult(false, List.of("No data found from Canis Major")); - } - - List validationErrors = compareData(orionData, blockchainData); - return new ValidationResult(validationErrors.isEmpty(), validationErrors); - } catch (Exception e) { - logger.error("Error validating entity: {}", entityId, e); - return new ValidationResult(false, List.of("Validation error: " + e.getMessage())); - } - } + private final ValidationHttpClient validationHttpClient; - private List compareData(String orionData, String blockchainData) { - List errors = new ArrayList<>(); + public ValidationResult validateTransaction(String entityId) { try { - JsonNode orionNode = mapper.readTree(orionData); - JsonNode blockchainNode = mapper.readTree(blockchainData); + // Fetch data from both sources asynchronously + CompletableFuture orionEntityFuture = validationHttpClient.fetchEntityById(entityId); + CompletableFuture canisMajorEntityFuture = validationHttpClient.fetchCanisMajorData(entityId); + + // Wait for both futures to complete + CompletableFuture.allOf(orionEntityFuture, canisMajorEntityFuture).join(); - JsonNode txReceipts = orionNode.get("TxReceipts").get("value"); + // Get the results + Entity orionEntity = orionEntityFuture.get(); + Entity canisMajorEntity = canisMajorEntityFuture.get(); - validateField(txReceipts, blockchainNode, "status", "Status", errors); - validateField(txReceipts, blockchainNode, "blockNumber", "Block Number", errors); - validateField(txReceipts, blockchainNode, "transactionHash", "Transaction Hash", errors); - validateField(txReceipts, blockchainNode, "blockHash", "Block Hash", errors); - validateField(txReceipts, blockchainNode, "from", "From Address", errors); - validateField(txReceipts, blockchainNode, "to", "To Address", errors); + // Compare the entities and create a validation result + List discrepancies = compareEntities(orionEntity, canisMajorEntity); - return errors; - } catch (JsonProcessingException e) { - logger.error("Error parsing JSON data", e); - errors.add("Error parsing JSON data: " + e.getMessage()); - return errors; + // Create and return the validation result + ValidationResult result = new ValidationResult(); + result.setValid(discrepancies.isEmpty()); + result.setEntityId(entityId); + result.setTimestamp(OffsetDateTime.now()); + result.setDiscrepancies(discrepancies); + + return result; + } catch (InterruptedException | ExecutionException e) { + log.error("Error validating transaction: {}", entityId, e); + throw new RuntimeException("Failed to validate transaction", e); } } - - private void validateField(JsonNode orion, JsonNode blockchain, String fieldName, String displayName, List errors) { - JsonNode orionValue = orion.get(fieldName); - JsonNode blockchainValue = blockchain.get(fieldName); + + private List compareEntities(Entity orionEntity, Entity canisMajorEntity) { + List discrepancies = new ArrayList<>(); + + // Compare entity type + if (!orionEntity.getType().equals(canisMajorEntity.getType())) { + Discrepancy discrepancy = new Discrepancy(); + discrepancy.setField("type"); + discrepancy.setOrionValue(orionEntity.getType()); + discrepancy.setCanisMajorValue(canisMajorEntity.getType()); + discrepancies.add(discrepancy); + } + + // Extract TxReceipts from both entities + Map orionProperties = (Map) orionEntity.getAdditionalProperties().get("TxReceipts"); + Map canisMajorProperties = (Map) canisMajorEntity.getAdditionalProperties().get("TxReceipts"); + + if (orionProperties != null && canisMajorProperties != null) { + Map orionValue = (Map) orionProperties.get("value"); + Map canisMajorValue = (Map) canisMajorProperties.get("value"); + + // Compare key transaction fields + compareField(discrepancies, "status", orionValue, canisMajorValue); + compareField(discrepancies, "blockNumber", orionValue, canisMajorValue); + compareField(discrepancies, "transactionHash", orionValue, canisMajorValue); + compareField(discrepancies, "blockHash", orionValue, canisMajorValue); + compareField(discrepancies, "from", orionValue, canisMajorValue); + compareField(discrepancies, "to", orionValue, canisMajorValue); + } else { + // If TxReceipts is missing in either entity + Discrepancy discrepancy = new Discrepancy(); + discrepancy.setField("TxReceipts"); + discrepancy.setOrionValue(orionProperties != null ? "present" : "missing"); + discrepancy.setCanisMajorValue(canisMajorProperties != null ? "present" : "missing"); + discrepancies.add(discrepancy); + } + + return discrepancies; + } + + private void compareField(List discrepancies, String fieldName, + Map orionValue, Map canisMajorValue) { + Object orionField = orionValue.get(fieldName); + Object canisMajorField = canisMajorValue.get(fieldName); - if (orionValue == null || blockchainValue == null) { - errors.add(displayName + " field missing in one or both responses"); + // Check if field exists in both + if (orionField == null || canisMajorField == null) { + Discrepancy discrepancy = new Discrepancy(); + discrepancy.setField(fieldName); + discrepancy.setOrionValue(orionField != null ? orionField.toString() : "missing"); + discrepancy.setCanisMajorValue(canisMajorField != null ? canisMajorField.toString() : "missing"); + discrepancies.add(discrepancy); return; } - if (!orionValue.equals(blockchainValue)) { - errors.add(displayName + " mismatch: Orion=" + orionValue + ", Blockchain=" + blockchainValue); + // Check if values are equal + if (!orionField.equals(canisMajorField)) { + Discrepancy discrepancy = new Discrepancy(); + discrepancy.setField(fieldName); + discrepancy.setOrionValue(orionField.toString()); + discrepancy.setCanisMajorValue(canisMajorField.toString()); + discrepancies.add(discrepancy); } } } - \ No newline at end of file diff --git a/validation-service/src/main/resources/api.yml b/validation-service/src/main/resources/api.yml deleted file mode 100644 index 35ff993..0000000 --- a/validation-service/src/main/resources/api.yml +++ /dev/null @@ -1,104 +0,0 @@ -openapi: 3.0.0 -info: - title: Canis Major Validation Service - version: 1.0.0 - description: API for validating entities between Orion and Blockchain responses. - contact: - email:asma.taamallah@fiware.org -paths: - /validation/entity/{entityId}/transactions - get: - summary: Retrieve entity transactions - operationId: getEntityTransactions - tags: - - Validation - parameters: - - name: entityId - in: path - required: true - description: The ID of the entity to retrieve transactions for - schema: - type: string - responses: - '200': - description: Successful retrieval of entity transactions - content: - application/json: - schema: - $ref: '#/components/schemas/EntityTransactionList' - '404': - description: Entity not found - '500': - description: Internal server error - /validation/entity/{entityId}/validate: - post: - summary: Validate an entity - operationId: validateEntity - tags: - - Validation - parameters: - - name: entityId - in: path - required: true - description: The ID of the entity to validate - schema: - type: string - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/ValidationRequest' - responses: - '200': - description: Validation successful - content: - application/json: - schema: - $ref: '#/components/schemas/ValidationResult' - '400': - description: Bad request - '404': - description: Entity not found - '500': - description: Internal server error -components: - schemas: - EntityTransactionList: - type: object - properties: - transactions: - type: array - items: - $ref: '#/components/schemas/EntityTransaction' - EntityTransaction: - type: object - properties: - id: - type: string - format: uri - status: - type: string - blockNumber: - type: integer - transactionHash: - type: string - from: - type: string - to: - type: string - ValidationRequest: - type: object - properties: - additionalData: - type: string - ValidationResult: - type: object - properties: - valid: - type: boolean - errors: - type: array - items: - type: string - diff --git a/validation-service/src/main/resources/application.yml b/validation-service/src/main/resources/application.yml index adb7c4a..acef394 100644 --- a/validation-service/src/main/resources/application.yml +++ b/validation-service/src/main/resources/application.yml @@ -1,4 +1,6 @@ -ngsi: +general: contextBrokerUrl: "127.0.0.1:1026" # Replace with your context broker URL canisMajorUrl: "127.0.0.1:4000" # Replace with your Canis Major URL - NGSILD_TENANT: "orion" # Replace with your tenant ID \ No newline at end of file + NGSILD_TENANT: "orion" # Replace with your tenant ID + contextUrl: "https://raw.githubusercontent.com/smart-data-models/dataModel.DistributedLedgerTech/master/context.jsonld" # NGSI-LD context URL + diff --git a/validation-service/src/main/resources/ngsi-ld-api.yaml b/validation-service/src/main/resources/ngsi-ld-api.yaml new file mode 100644 index 0000000..40294ca --- /dev/null +++ b/validation-service/src/main/resources/ngsi-ld-api.yaml @@ -0,0 +1,234 @@ +openapi: 3.0.0 +info: + title: NGSI-LD API + version: 1.0.0 + description: API for interacting with NGSI-LD Context Brokers and Canis Major +servers: + - url: 'http://{serverUrl}' + description: NGSI-LD Context Broker + variables: + serverUrl: + default: "127.0.0.1:1026" + description: Context Broker URL + - url: 'http://{serverUrl}' + description: Canis Major DLT Adapter + variables: + serverUrl: + default: "127.0.0.1:4000" + description: Canis Major URL + +paths: + /ngsi-ld/v1/entities: + get: + summary: Query entities + description: Retrieve a set of entities which matches a specific query from an NGSI-LD system + operationId: queryEntities + tags: + - Context Information + - Entities + parameters: + - $ref: '#/components/parameters/type' + - $ref: '#/components/parameters/attrs' + - $ref: '#/components/parameters/q' + - $ref: '#/components/parameters/LinkHeader' + - $ref: '#/components/parameters/TenantHeader' + responses: + '200': + description: OK + content: + application/ld+json: + schema: + $ref: '#/components/schemas/EntityList' + '400': + description: Bad request + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + + /ngsi-ld/v1/entities/{entityId}: + get: + summary: Retrieve entity by ID + description: Retrieve an entity by its ID + operationId: retrieveEntityById + tags: + - Context Information + - Entities + parameters: + - name: entityId + in: path + required: true + description: Entity ID + schema: + type: string + - $ref: '#/components/parameters/attrs' + - $ref: '#/components/parameters/LinkHeader' + - $ref: '#/components/parameters/TenantHeader' + responses: + '200': + description: OK + content: + application/ld+json: + schema: + $ref: '#/components/schemas/Entity' + '404': + description: Entity not found + content: + application/problem+json: + schema: + $ref: '#/components/schemas/ProblemDetails' + +components: + parameters: + type: + name: type + in: query + description: Entity type + schema: + type: string + attrs: + name: attrs + in: query + description: Comma-separated list of attribute names to be retrieved + schema: + type: string + q: + name: q + in: query + description: Query string for filtering entities + schema: + type: string + LinkHeader: + name: Link + in: header + description: NGSI-LD @context + schema: + type: string + TenantHeader: + name: NGSILD-Tenant + in: header + description: NGSI-LD tenant + schema: + type: string + default: "orion" + + schemas: + Entity: + type: object + properties: + id: + type: string + description: Entity identifier + type: + type: string + description: Entity type + additionalProperties: true + + EntityList: + type: array + items: + $ref: '#/components/schemas/Entity' + + ProblemDetails: + type: object + properties: + type: + type: string + title: + type: string + status: + type: integer + detail: + type: string + instance: + type: string + + DLTtxReceipt: + type: object + properties: + id: + type: string + description: Entity identifier + type: + type: string + description: Entity type (DLTtxReceipt) + TxReceipts: + type: object + properties: + type: + type: string + enum: [Property] + value: + type: object + properties: + blockHash: + type: string + blockNumber: + type: integer + blockNumberRaw: + type: string + cumulativeGasUsed: + type: integer + cumulativeGasUsedRaw: + type: string + from: + type: string + gasUsed: + type: integer + gasUsedRaw: + type: string + logs: + type: array + items: + type: object + properties: + address: + type: string + blockHash: + type: string + blockNumber: + type: integer + blockNumberRaw: + type: string + data: + type: string + logIndex: + type: integer + logIndexRaw: + type: string + removed: + type: boolean + topics: + type: array + items: + type: string + transactionHash: + type: string + transactionIndex: + type: integer + transactionIndexRaw: + type: string + type: + type: string + logsBloom: + type: string + status: + type: string + statusOK: + type: boolean + to: + type: string + transactionHash: + type: string + transactionIndex: + type: integer + transactionIndexRaw: + type: string + refEntity: + type: object + properties: + type: + type: string + enum: [Relationship] + object: + type: string diff --git a/validation-service/src/main/resources/validation-api.yaml b/validation-service/src/main/resources/validation-api.yaml new file mode 100644 index 0000000..819d8c4 --- /dev/null +++ b/validation-service/src/main/resources/validation-api.yaml @@ -0,0 +1,64 @@ +openapi: 3.0.0 +info: + title: Transaction Validation Service + version: 1.0.0 + description: API for validating transaction data consistency between Orion-LD and Canis Major + contact: + email: asma.taamallah@fiware.org +paths: + /validation/transaction/{entityId}: + post: + summary: Validate transaction consistency + operationId: validateTransaction + tags: + - Validation + parameters: + - name: entityId + in: path + required: true + description: The ID of the transaction entity to validate + schema: + type: string + responses: + '200': + description: Validation result + content: + application/json: + schema: + $ref: '#/components/schemas/ValidationResult' + '404': + description: Transaction not found in one or both systems + '500': + description: Internal server error +components: + schemas: + ValidationResult: + type: object + properties: + valid: + type: boolean + description: Whether the transaction data is consistent between systems + entityId: + type: string + description: The ID of the validated transaction entity + timestamp: + type: string + format: date-time + description: When the validation was performed + discrepancies: + type: array + items: + $ref: '#/components/schemas/Discrepancy' + description: List of discrepancies found (empty if valid is true) + Discrepancy: + type: object + properties: + field: + type: string + description: The field that has a discrepancy + orionValue: + type: string + description: The value from Orion-LD + canisMajorValue: + type: string + description: The value from Canis Major diff --git a/validation-service/src/test/java/IntegrationTest.java b/validation-service/src/test/java/IntegrationTest.java new file mode 100644 index 0000000..d556a4f --- /dev/null +++ b/validation-service/src/test/java/IntegrationTest.java @@ -0,0 +1,114 @@ +package org.fiware.validation_service.integration; + +import io.cucumber.java.Before; +import io.cucumber.java.en.Given; +import io.cucumber.java.en.Then; +import io.cucumber.java.en.When; +import org.fiware.validation_service.model.Discrepancy; +import org.fiware.validation_service.model.ValidationResult; +import org.awaitility.Awaitility; + +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.time.Duration; +import java.time.temporal.ChronoUnit; +import java.util.List; + +import com.fasterxml.jackson.databind.ObjectMapper; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.assertFalse; + +public class ValidationServiceStepDefinitions { + + private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + private static final String NGSILD_TENANT = "orion"; + private static final String CONTEXT_BROKER_URL = "127.0.0.1:1026"; + private static final String CANIS_MAJOR_URL = "127.0.0.1:4000"; + private static final String VALIDATION_SERVICE_URL = "127.0.0.1:8080"; + + private String testEntityId; + private ValidationResult validationResult; + private HttpClient httpClient; + + @Before + public void setup() { + httpClient = HttpClient.newHttpClient(); + testEntityId = "urn:ngsi-ld:dlttxreceipt:" + System.currentTimeMillis(); + } + + @Given("Orion-LD and Canis Major are running") + public void orion_and_canis_major_are_running() { + Awaitility.await("Wait for Orion-LD") + .atMost(Duration.of(30, ChronoUnit.SECONDS)) + .until(() -> isServiceRunning(CONTEXT_BROKER_URL, "/version")); + + Awaitility.await("Wait for Canis Major") + .atMost(Duration.of(30, ChronoUnit.SECONDS)) + .until(() -> isServiceRunning(CANIS_MAJOR_URL, "/health")); + } + + @Given("A transaction entity exists in both systems") + public void transaction_entity_exists_in_both_systems() throws Exception { + // This would typically create or ensure a transaction entity exists + // For testing purposes, we could use a known entity ID + testEntityId = "urn:ngsi-ld:dlttxreceipt:0xa46d2e3b190d36fbb8af5d0a1c212d8036cf007e6ec4d1309904e052d25e5499"; + } + + @When("I validate the transaction entity") + public void validate_transaction_entity() throws Exception { + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create(String.format("http://%s/validation/transaction/%s", + VALIDATION_SERVICE_URL, testEntityId))) + .header("Content-Type", "application/json") + .POST(HttpRequest.BodyPublishers.noBody()) + .build(); + + HttpResponse response = httpClient.send(request, + HttpResponse.BodyHandlers.ofString()); + + assertEquals(200, response.statusCode(), "Validation request should succeed"); + validationResult = OBJECT_MAPPER.readValue(response.body(), ValidationResult.class); + } + + @Then("The validation result should indicate consistency") + public void validation_result_should_indicate_consistency() { + assertTrue(validationResult.isValid(), "Transaction data should be consistent"); + assertEquals(testEntityId, validationResult.getEntityId(), "Entity ID should match"); + assertTrue(validationResult.getDiscrepancies().isEmpty(), "No discrepancies should be found"); + } + + @Then("The validation result should show discrepancies") + public void validation_result_should_show_discrepancies() { + assertFalse(validationResult.isValid(), "Transaction data should be inconsistent"); + assertEquals(testEntityId, validationResult.getEntityId(), "Entity ID should match"); + assertFalse(validationResult.getDiscrepancies().isEmpty(), "Discrepancies should be found"); + + // Optionally check specific discrepancies + List discrepancies = validationResult.getDiscrepancies(); + discrepancies.forEach(discrepancy -> { + System.out.println("Field: " + discrepancy.getField()); + System.out.println("Orion value: " + discrepancy.getOrionValue()); + System.out.println("Canis Major value: " + discrepancy.getCanisMajorValue()); + }); + } + + private boolean isServiceRunning(String host, String path) { + try { + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create(String.format("http://%s%s", host, path))) + .GET() + .build(); + + HttpResponse response = httpClient.send(request, + HttpResponse.BodyHandlers.ofString()); + + return response.statusCode() >= 200 && response.statusCode() < 300; + } catch (Exception e) { + return false; + } + } +} diff --git a/validation-service/validation_service.png b/validation-service/validation_service.png deleted file mode 100644 index ad2363e0573bff9f3d715da28012192a454cfeb9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 130008 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 F|38XtFTnr+