diff --git a/README.md b/README.md index f1b05a573..590a8c3f3 100644 --- a/README.md +++ b/README.md @@ -14,22 +14,21 @@ to fetch the data from the node. - [x] Integration test setup - API calls - Data API - - [x] /network/list - - [x] /network/status - - [x] /network/options - - [x] /block/* - - [ ] /mempool/* - - [x] /account/* + - [x] /network/list + - [x] /network/status + - [x] /network/options + - [x] /block/* + - [ ] /mempool/* + - [x] /account/* - Construction API - - [x] /construction/derive - - [ ] /construction/preprocess - - [ ] /construction/metadata - - [ ] /construction/payloads - - [ ] /construction/combine - - [ ] /construction/parse - - [ ] /construction/hash - - [ ] /construction/submit - + - [x] /construction/derive + - [ ] /construction/preprocess + - [ ] /construction/metadata + - [ ] /construction/payloads + - [ ] /construction/combine + - [ ] /construction/parse + - [x] /construction/hash + - [ ] /construction/submit ## Getting Started diff --git a/api/src/main/java/org/cardanofoundation/rosetta/api/service/CardanoService.java b/api/src/main/java/org/cardanofoundation/rosetta/api/service/CardanoService.java index ce2ff0f8d..61cc621ae 100644 --- a/api/src/main/java/org/cardanofoundation/rosetta/api/service/CardanoService.java +++ b/api/src/main/java/org/cardanofoundation/rosetta/api/service/CardanoService.java @@ -1,6 +1,9 @@ package org.cardanofoundation.rosetta.api.service; -public interface CardanoService { +import co.nstant.in.cbor.model.Array; +public interface CardanoService { + String getHashOfSignedTransaction(String signedTransaction); + Array decodeExtraData(String encoded); } diff --git a/api/src/main/java/org/cardanofoundation/rosetta/api/service/impl/CardanoServiceImpl.java b/api/src/main/java/org/cardanofoundation/rosetta/api/service/impl/CardanoServiceImpl.java index d883bab8e..ced0a7248 100644 --- a/api/src/main/java/org/cardanofoundation/rosetta/api/service/impl/CardanoServiceImpl.java +++ b/api/src/main/java/org/cardanofoundation/rosetta/api/service/impl/CardanoServiceImpl.java @@ -1,7 +1,16 @@ package org.cardanofoundation.rosetta.api.service.impl; +import co.nstant.in.cbor.model.Array; +import co.nstant.in.cbor.model.DataItem; +import com.bloxbean.cardano.client.common.cbor.CborSerializationUtil; +import com.bloxbean.cardano.client.crypto.Blake2bUtil; +import com.bloxbean.cardano.client.transaction.spec.Transaction; +import com.bloxbean.cardano.client.transaction.spec.TransactionBody; +import com.bloxbean.cardano.client.util.HexUtil; import lombok.extern.slf4j.Slf4j; +import org.cardanofoundation.rosetta.api.exception.ExceptionFactory; import org.cardanofoundation.rosetta.api.service.CardanoService; +import org.cardanofoundation.rosetta.api.util.CardanoAddressUtils; import org.springframework.stereotype.Service; @@ -10,5 +19,43 @@ @Service public class CardanoServiceImpl implements CardanoService { + @Override + public String getHashOfSignedTransaction(String signedTransaction) { + try { + log.info("[getHashOfSignedTransaction] About to hash signed transaction {}", + signedTransaction); + byte[] signedTransactionBytes = HexUtil.decodeHexString(signedTransaction); + log.info( + "[getHashOfSignedTransaction] About to parse transaction from signed transaction bytes"); + Transaction parsed = Transaction.deserialize(signedTransactionBytes); + log.info("[getHashOfSignedTransaction] Returning transaction hash"); + TransactionBody body = parsed.getBody(); + byte[] hashBuffer; + if (body == null || + CborSerializationUtil.serialize(body.serialize()) + == null) { + hashBuffer = null; + } else { + hashBuffer = Blake2bUtil.blake2bHash256( + com.bloxbean.cardano.client.common.cbor.CborSerializationUtil.serialize( + body.serialize())); + } + return CardanoAddressUtils.hexFormatter(hashBuffer); + } catch (Exception error) { + log.error(error.getMessage() + + "[getHashOfSignedTransaction] There was an error parsing signed transaction"); + throw ExceptionFactory.parseSignedTransactionError(); + } + } + @Override + public Array decodeExtraData(String encoded) { + try { + DataItem dataItem = com.bloxbean.cardano.client.common.cbor.CborSerializationUtil.deserialize( + HexUtil.decodeHexString(encoded)); + return (Array) dataItem; + } catch (Exception e) { + throw ExceptionFactory.cantBuildSignedTransaction(); + } + } } diff --git a/api/src/main/java/org/cardanofoundation/rosetta/api/service/impl/ConstructionApiServiceImpl.java b/api/src/main/java/org/cardanofoundation/rosetta/api/service/impl/ConstructionApiServiceImpl.java index c216b627c..0d36208a2 100644 --- a/api/src/main/java/org/cardanofoundation/rosetta/api/service/impl/ConstructionApiServiceImpl.java +++ b/api/src/main/java/org/cardanofoundation/rosetta/api/service/impl/ConstructionApiServiceImpl.java @@ -1,6 +1,8 @@ package org.cardanofoundation.rosetta.api.service.impl; import co.nstant.in.cbor.CborException; +import co.nstant.in.cbor.model.Array; +import co.nstant.in.cbor.model.UnicodeString; import com.bloxbean.cardano.client.exception.AddressExcepion; import com.bloxbean.cardano.client.exception.CborDeserializationException; import com.bloxbean.cardano.client.exception.CborSerializationException; @@ -12,6 +14,7 @@ import org.cardanofoundation.rosetta.api.model.enumeration.NetworkEnum; import org.cardanofoundation.rosetta.api.service.CardanoAddressService; +import org.cardanofoundation.rosetta.api.service.CardanoService; import org.cardanofoundation.rosetta.api.service.ConstructionApiService; import org.openapitools.client.model.*; import org.springframework.stereotype.Service; @@ -25,6 +28,7 @@ public class ConstructionApiServiceImpl implements ConstructionApiService { private final CardanoAddressService cardanoAddressService; + private final CardanoService cardanoService; @Override public ConstructionDeriveResponse constructionDeriveService(ConstructionDeriveRequest constructionDeriveRequest) throws IllegalAccessException, CborSerializationException { @@ -76,7 +80,12 @@ public ConstructionCombineResponse constructionCombineService(ConstructionCombin @Override public TransactionIdentifierResponse constructionHashService(ConstructionHashRequest constructionHashRequest) { - return null; + Array array = cardanoService.decodeExtraData(constructionHashRequest.getSignedTransaction()); + log.info("[constructionHash] About to get hash of signed transaction"); + String transactionHash = cardanoService.getHashOfSignedTransaction( + ((UnicodeString) array.getDataItems().get(0)).getString()); + log.info("[constructionHash] About to return hash of signed transaction"); + return new TransactionIdentifierResponse(new TransactionIdentifier(transactionHash), null); } @Override diff --git a/postmanTests/Rosetta-java-env.postman_environment.json b/postmanTests/Rosetta-java-env.postman_environment.json index 8f3c2d37e..d041bca1c 100644 --- a/postmanTests/Rosetta-java-env.postman_environment.json +++ b/postmanTests/Rosetta-java-env.postman_environment.json @@ -85,9 +85,20 @@ "value": "Base", "type": "default", "enabled": true + }, + { + "key": "signedTransaction", + "value": "827901c43834613430303831383235383230326632336664386363613833356166323166336163333735626163363031663937656164373566326537393134336264663731666532633462653034336538663031303138323832353831643631626234306631613634376263383863316264366237333864623865623636333537643932363437346561356666643662616137366339666231393237313038323538316436316262343066316136343762633838633162643662373338646238656236363335376439323634373465613566666436626161373663396662313939633430303231393963343030333139303365386131303038313832353832303162343030643630616166333465616636646362616239626261343630303161323334393738383663663131303636663738343639333364333065356164336635383430366339323530383133356362303630313837613237303661646538313534373832383637623135323665393631356430363734326265356335366630333761623835383934633039386332616230373937313133336330343737626165653932616466333532376164376363383136663133653165346333363130343132303666356636a16a6f7065726174696f6e7381a6746f7065726174696f6e5f6964656e746966696572a265696e646578006d6e6574776f726b5f696e64657800647479706565696e707574667374617475736773756363657373676163636f756e74a16761646472657373783a616464723176786135707564786737376733736461646465636d773874766336686d796e79776e34396c6c747434666d766e3763706e6b63707866616d6f756e74a26576616c7565662d39303030306863757272656e6379a26673796d626f6c6341444168646563696d616c73066b636f696e5f6368616e6765a26f636f696e5f6964656e746966696572a16a6964656e7469666965727842326632336664386363613833356166323166336163333735626163363031663937656164373566326537393134336264663731666532633462653034336538663a316b636f696e5f616374696f6e6a636f696e5f7370656e74", + "enabled": true + }, + { + "key": "hashedSignedTransaction", + "value": "333a6ccaaa639f7b451ce93764f54f654ef499fdb7b8b24374ee9d99eab9d795", + "type": "default", + "enabled": true } ], "_postman_variable_scope": "environment", - "_postman_exported_at": "2024-03-06T12:46:41.009Z", - "_postman_exported_using": "Postman/10.23.8" + "_postman_exported_at": "2024-03-07T11:54:40.373Z", + "_postman_exported_using": "Postman/10.23.10" } \ No newline at end of file diff --git a/postmanTests/rosetta-java.postman_collection.json b/postmanTests/rosetta-java.postman_collection.json index b23778632..9a255cd90 100644 --- a/postmanTests/rosetta-java.postman_collection.json +++ b/postmanTests/rosetta-java.postman_collection.json @@ -341,13 +341,13 @@ "response": [] }, { - "name": "/mempool", + "name": "/mempool/transaction", "request": { "method": "POST", "header": [], "body": { "mode": "raw", - "raw": "{\n \"network_identifier\": {\n \"blockchain\": \"cardano\",\n \"network\": \"{{networkId}}\"\n },\n \"metadata\": {\n }\n}", + "raw": "{\n \"network_identifier\": {\n \"blockchain\": \"cardano\",\n \"network\": \"{{networkId}}\"\n },\n \"transaction_identifier\": {\n \"hash\": {{mempoolTransaction}}\n },\n \"metadata\": {}\n}", "options": { "raw": { "language": "json" @@ -355,25 +355,52 @@ } }, "url": { - "raw": "{{URL}}/mempool", + "raw": "{{URL}}/mempool/transaction", "host": [ "{{URL}}" ], "path": [ - "mempool" + "mempool", + "transaction" ] } }, "response": [] }, { - "name": "/mempool/transaction", + "name": "/construction/derive", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test('Status code is 200', function () {", + " pm.response.to.have.status(200);", + "})", + "", + "pm.test('Contains Json Body', function () {", + " pm.response.to.have.jsonBody('address');", + "})", + "", + "pm.test(\"Correct Address\", function () {", + " const testAccountBaseAddress = pm.environment.get('TestAccountBaseAddress');", + " var responseData = pm.response.json();", + " pm.expect(responseData).to.be.an('object');", + " pm.expect(responseData.address).to.eql(testAccountBaseAddress);", + "});", + "", + "" + ], + "type": "text/javascript" + } + } + ], "request": { "method": "POST", "header": [], "body": { "mode": "raw", - "raw": "{\n \"network_identifier\": {\n \"blockchain\": \"cardano\",\n \"network\": \"{{networkId}}\"\n },\n \"transaction_identifier\": {\n \"hash\": {{mempoolTransaction}}\n },\n \"metadata\": {}\n}", + "raw": "{\n \"network_identifier\": {\n \"blockchain\": \"cardano\",\n \"network\": \"{{networkId}}\"\n },\n \"public_key\": {\n \"hex_bytes\": \"{{TestAccountPubKeyHexBytes}}\",\n \"curve_type\": \"{{curveType}}\"\n },\n \"metadata\": {\n \"address_type\": \"{{address_type}}\",\n \"staking_credential\": {\n \"hex_bytes\": \"{{TestAccountStakePubKeyHex}}\",\n \"curve_type\": \"{{curveType}}\"\n }\n }\n}", "options": { "raw": { "language": "json" @@ -381,20 +408,20 @@ } }, "url": { - "raw": "{{URL}}/mempool/transaction", + "raw": "{{URL}}/construction/derive", "host": [ "{{URL}}" ], "path": [ - "mempool", - "transaction" + "construction", + "derive" ] } }, "response": [] }, { - "name": "/construction/derive", + "name": "/construction/hash", "event": [ { "listen": "test", @@ -405,17 +432,16 @@ "})", "", "pm.test('Contains Json Body', function () {", - " pm.response.to.have.jsonBody('address');", + " pm.response.to.have.jsonBody('transaction_identifier');", "})", "", "pm.test(\"Correct Address\", function () {", - " const testAccountBaseAddress = pm.environment.get('TestAccountBaseAddress');", + " const hashedSignedTransaction = pm.environment.get('hashedSignedTransaction');", " var responseData = pm.response.json();", " pm.expect(responseData).to.be.an('object');", - " pm.expect(responseData.address).to.eql(testAccountBaseAddress);", - "});", - "", - "" + " pm.expect(responseData.transaction_identifier).to.be.an('object');", + " pm.expect(responseData.transaction_identifier.hash).to.eql(hashedSignedTransaction);", + "});" ], "type": "text/javascript" } @@ -426,7 +452,7 @@ "header": [], "body": { "mode": "raw", - "raw": "{\n \"network_identifier\": {\n \"blockchain\": \"cardano\",\n \"network\": \"{{networkId}}\"\n },\n \"public_key\": {\n \"hex_bytes\": \"{{TestAccountPubKeyHexBytes}}\",\n \"curve_type\": \"{{curveType}}\"\n },\n \"metadata\": {\n \"address_type\": \"{{address_type}}\",\n \"staking_credential\": {\n \"hex_bytes\": \"{{TestAccountStakePubKeyHex}}\",\n \"curve_type\": \"{{curveType}}\"\n }\n }\n}", + "raw": "{\n \"network_identifier\": {\n \"blockchain\": \"cardano\",\n \"network\": \"devkit\"\n },\n \"signed_transaction\": \"{{signedTransaction}}\"\n}", "options": { "raw": { "language": "json" @@ -434,13 +460,13 @@ } }, "url": { - "raw": "{{URL}}/construction/derive", + "raw": "{{URL}}/construction/hash", "host": [ "{{URL}}" ], "path": [ "construction", - "derive" + "hash" ] } }, @@ -472,6 +498,32 @@ } }, "response": [] + }, + { + "name": "/mempool", + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"network_identifier\": {\n \"blockchain\": \"cardano\",\n \"network\": \"{{networkId}}\"\n },\n \"metadata\": { }\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{URL}}/mempool", + "host": [ + "{{URL}}" + ], + "path": [ + "mempool" + ] + } + }, + "response": [] } ], "variable": [