From b95daab7e59d84e7452631babd3c4f9582460cae Mon Sep 17 00:00:00 2001 From: Tamir Sen Date: Tue, 21 Apr 2020 12:38:06 +0200 Subject: [PATCH 1/2] Update Horizon Transaction response --- .../responses/TransactionDeserializer.java | 15 +-- .../sdk/responses/TransactionResponse.java | 95 ++++++++++++++++++ .../TransactionDeserializerTest.java | 96 ++++++++++++++++++- .../TransactionPageDeserializerTest.java | 10 +- 4 files changed, 202 insertions(+), 14 deletions(-) diff --git a/src/main/java/org/stellar/sdk/responses/TransactionDeserializer.java b/src/main/java/org/stellar/sdk/responses/TransactionDeserializer.java index 428ef7e93..c2736f1f7 100644 --- a/src/main/java/org/stellar/sdk/responses/TransactionDeserializer.java +++ b/src/main/java/org/stellar/sdk/responses/TransactionDeserializer.java @@ -35,19 +35,10 @@ public TransactionResponse deserialize(JsonElement json, Type typeOfT, JsonDeser // representation of a transaction. That's why we need to handle a special case // here. if (memoType.equals("text")) { - // we obtain the memo text from the xdr because the bytes may not be valid utf8 - String envelopeXdr = json.getAsJsonObject().get("envelope_xdr").getAsString(); + // we obtain the memo text from the "memo_bytes" field because the original byte sequence may not be valid utf8 + String memoBase64 = json.getAsJsonObject().get("memo_bytes").getAsString(); BaseEncoding base64Encoding = BaseEncoding.base64(); - byte[] bytes = base64Encoding.decode(envelopeXdr); - TransactionEnvelope transactionEnvelope = null; - try { - transactionEnvelope = TransactionEnvelope.decode(new XdrDataInputStream(new ByteArrayInputStream(bytes))); - } catch (IOException e) { - // JsonDeserializer cannot throw IOExceptions - // so we must throw it as a runtime exception - throw new RuntimeException(e); - } - memo = Memo.text(transactionEnvelope.getTx().getMemo().getText().getBytes()); + memo = Memo.text(base64Encoding.decode(memoBase64)); } else { String memoValue = json.getAsJsonObject().get("memo").getAsString(); BaseEncoding base64Encoding = BaseEncoding.base64(); diff --git a/src/main/java/org/stellar/sdk/responses/TransactionResponse.java b/src/main/java/org/stellar/sdk/responses/TransactionResponse.java index 1a7de478d..4d5c9c08d 100644 --- a/src/main/java/org/stellar/sdk/responses/TransactionResponse.java +++ b/src/main/java/org/stellar/sdk/responses/TransactionResponse.java @@ -1,9 +1,12 @@ package org.stellar.sdk.responses; +import com.google.common.base.Optional; import com.google.gson.annotations.SerializedName; import org.stellar.sdk.Memo; +import java.util.List; + import static com.google.common.base.Preconditions.checkNotNull; /** @@ -21,6 +24,8 @@ public class TransactionResponse extends Response implements Pageable { private final String createdAt; @SerializedName("source_account") private final String sourceAccount; + @SerializedName("fee_account") + private final String feeAccount; @SerializedName("successful") private final Boolean successful; @SerializedName("paging_token") @@ -39,6 +44,12 @@ public class TransactionResponse extends Response implements Pageable { private final String resultXdr; @SerializedName("result_meta_xdr") private final String resultMetaXdr; + @SerializedName("signatures") + private final List signatures; + @SerializedName("fee_bump_transaction") + private final FeeBumpTransaction feeBumpTransaction; + @SerializedName("inner_transaction") + private final InnerTransaction innerTransaction; @SerializedName("_links") private final Links links; @@ -51,6 +62,7 @@ public class TransactionResponse extends Response implements Pageable { Long ledger, String createdAt, String sourceAccount, + String feeAccount, Boolean successful, String pagingToken, Long sourceAccountSequence, @@ -61,12 +73,16 @@ public class TransactionResponse extends Response implements Pageable { String resultXdr, String resultMetaXdr, Memo memo, + List signatures, + FeeBumpTransaction feeBumpTransaction, + InnerTransaction innerTransaction, Links links ) { this.hash = hash; this.ledger = ledger; this.createdAt = createdAt; this.sourceAccount = sourceAccount; + this.feeAccount = feeAccount; this.successful = successful; this.pagingToken = pagingToken; this.sourceAccountSequence = sourceAccountSequence; @@ -77,6 +93,9 @@ public class TransactionResponse extends Response implements Pageable { this.resultXdr = resultXdr; this.resultMetaXdr = resultMetaXdr; this.memo = memo; + this.signatures = signatures; + this.feeBumpTransaction = feeBumpTransaction; + this.innerTransaction = innerTransaction; this.links = links; } @@ -96,6 +115,22 @@ public String getSourceAccount() { return sourceAccount; } + public String getFeeAccount() { + return feeAccount; + } + + public List getSignatures() { + return signatures; + } + + public Optional getFeeBump() { + return Optional.fromNullable(this.feeBumpTransaction); + } + + public Optional getInner() { + return Optional.fromNullable(this.innerTransaction); + } + public String getPagingToken() { return pagingToken; } @@ -148,6 +183,66 @@ public Links getLinks() { return links; } + /** + * FeeBumpTransaction is only present in a TransactionResponse if the transaction is a fee bump transaction or is + * wrapped by a fee bump transaction. The object has two fields: the hash of the fee bump transaction and the + * signatures present in the fee bump transaction envelope. + */ + public static class FeeBumpTransaction { + @SerializedName("hash") + private final String hash; + @SerializedName("signatures") + private final List signatures; + + FeeBumpTransaction(String hash, List signatures) { + this.hash = hash; + this.signatures = signatures; + } + + public String getHash() { + return hash; + } + + public List getSignatures() { + return signatures; + } + } + + /** + * InnerTransaction is only present in a TransactionResponse if the transaction is a fee bump transaction or is + * wrapped by a fee bump transaction. The object has three fields: the hash of the inner transaction wrapped by the + * fee bump transaction, the max fee set in the inner transaction, and the signatures present in the inner + * transaction envelope. + */ + public static class InnerTransaction { + @SerializedName("hash") + private final String hash; + @SerializedName("signatures") + private final List signatures; + @SerializedName("max_fee") + private final Long maxFee; + + + InnerTransaction(String hash, List signatures, Long maxFee) { + this.hash = hash; + this.signatures = signatures; + this.maxFee = maxFee; + } + + public String getHash() { + return hash; + } + + public List getSignatures() { + return signatures; + } + + public Long getMaxFee() { + return maxFee; + } + + } + /** * Links connected to transaction. */ diff --git a/src/test/java/org/stellar/sdk/responses/TransactionDeserializerTest.java b/src/test/java/org/stellar/sdk/responses/TransactionDeserializerTest.java index 38a74e27e..ff61680ef 100644 --- a/src/test/java/org/stellar/sdk/responses/TransactionDeserializerTest.java +++ b/src/test/java/org/stellar/sdk/responses/TransactionDeserializerTest.java @@ -1,5 +1,7 @@ package org.stellar.sdk.responses; +import com.google.common.base.Optional; +import com.google.common.collect.ImmutableList; import junit.framework.TestCase; import org.junit.Test; @@ -7,6 +9,30 @@ import org.stellar.sdk.MemoNone; public class TransactionDeserializerTest extends TestCase { + @Test + public void testDeserializeFeeBump() { + TransactionResponse transaction = GsonSingleton.getInstance().fromJson(jsonFeeBump, TransactionResponse.class); + assertEquals(transaction.getHash(), "3dfef7d7226995b504f2827cc63d45ad41e9687bb0a8abcf08ba755fedca0352"); + assertEquals(transaction.getLedger(), Long.valueOf(123)); + assertEquals(transaction.isSuccessful(), Boolean.TRUE); + assertEquals(transaction.getSourceAccount(), "GABQGAYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB2MX"); + assertEquals(transaction.getFeeAccount(), "GABAEAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABGKJ"); + assertEquals(transaction.getSourceAccountSequence(), Long.valueOf(97)); + assertEquals(transaction.getMaxFee(), Long.valueOf(776)); + assertEquals(transaction.getFeeCharged(), Long.valueOf(123)); + assertEquals(transaction.getOperationCount(), Integer.valueOf(1)); + assertEquals(transaction.getSignatures(), ImmutableList.of("Hh4e")); + + TransactionResponse.FeeBumpTransaction feeBumpTransaction = transaction.getFeeBump().get(); + assertEquals(feeBumpTransaction.getHash(), "3dfef7d7226995b504f2827cc63d45ad41e9687bb0a8abcf08ba755fedca0352"); + assertEquals(feeBumpTransaction.getSignatures(), ImmutableList.of("Hh4e")); + + TransactionResponse.InnerTransaction innerTransaction = transaction.getInner().get(); + assertEquals(innerTransaction.getHash(), "e98869bba8bce08c10b78406202127f3888c25454cd37b02600862452751f526"); + assertEquals(innerTransaction.getMaxFee(), Long.valueOf(99)); + assertEquals(innerTransaction.getSignatures(), ImmutableList.of("FBQU")); + } + @Test public void testDeserialize() { TransactionResponse transaction = GsonSingleton.getInstance().fromJson(json, TransactionResponse.class); @@ -23,7 +49,9 @@ public void testDeserialize() { assertEquals(transaction.getEnvelopeXdr(), "AAAAAKgfpXwD1fWpPmZL+GkzWcBmhRQH7ouPsoTN3RoaGCfrAAAAZAAIbkcAAB9WAAAAAAAAAANRBBZE6D1qyGjISUGLY5Ldvp31PwAAAAAAAAAAAAAAAAAAAAEAAAABAAAAAP1qe44j+i4uIT+arbD4QDQBt8ryEeJd7a0jskQ3nwDeAAAAAAAAAADA7RnarSzCwj3OT+M2btCMFpVBdqxJS+Sr00qBjtFv7gAAAABLCs/QAAAAAAAAAAEaGCfrAAAAQG/56Cj2J8W/KCZr+oC4sWND1CTGWfaccHNtuibQH8kZIb+qBSDY94g7hiaAXrlIeg9b7oz/XuP3x9MWYw2jtwM="); assertEquals(transaction.getResultXdr(), "AAAAAAAAAGQAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAA="); assertEquals(transaction.getResultMetaXdr(), "AAAAAAAAAAEAAAACAAAAAAAN+SAAAAAAAAAAAMDtGdqtLMLCPc5P4zZu0IwWlUF2rElL5KvTSoGO0W/uAAAAAEsKz9AADfkgAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAQAN+SAAAAAAAAAAAP1qe44j+i4uIT+arbD4QDQBt8ryEeJd7a0jskQ3nwDeAAHp6WMr55YACD1BAAAAHgAAAAoAAAAAAAAAAAAAAAABAAAAAAAACgAAAAARC07BokpLTOF+/vVKBwiAlop7hHGJTNeGGlY4MoPykwAAAAEAAAAAK+Lzfd3yDD+Ov0GbYu1g7SaIBrKZeBUxoCunkLuI7aoAAAABAAAAAERmsKL73CyLV/HvjyQCERDXXpWE70Xhyb6MR5qPO3yQAAAAAQAAAABSORGwAdyuanN3sNOHqNSpACyYdkUM3L8VafUu69EvEgAAAAEAAAAAeCzqJNkMM/jLvyuMIfyFHljBlLCtDyj17RMycPuNtRMAAAABAAAAAIEi4R7juq15ymL00DNlAddunyFT4FyUD4muC4t3bobdAAAAAQAAAACaNpLL5YMfjOTdXVEqrAh99LM12sN6He6pHgCRAa1f1QAAAAEAAAAAqB+lfAPV9ak+Zkv4aTNZwGaFFAfui4+yhM3dGhoYJ+sAAAABAAAAAMNJrEvdMg6M+M+n4BDIdzsVSj/ZI9SvAp7mOOsvAD/WAAAAAQAAAADbHA6xiKB1+G79mVqpsHMOleOqKa5mxDpP5KEp/Xdz9wAAAAEAAAAAAAAAAA=="); - + assertEquals(transaction.getSignatures(), ImmutableList.of("b/noKPYnxb8oJmv6gLixY0PUJMZZ9pxwc226JtAfyRkhv6oFINj3iDuGJoBeuUh6D1vujP9e4/fH0xZjDaO3Aw==")); + assertEquals(transaction.getFeeBump(), Optional.absent()); + assertEquals(transaction.getInner(), Optional.absent()); assertTrue(transaction.getMemo() instanceof MemoHash); MemoHash memo = (MemoHash) transaction.getMemo(); assertEquals("51041644e83d6ac868c849418b6392ddbe9df53f000000000000000000000000", memo.getHexValue()); @@ -136,4 +164,70 @@ public void testDeserializeWithoutMemo() { " \"b/noKPYnxb8oJmv6gLixY0PUJMZZ9pxwc226JtAfyRkhv6oFINj3iDuGJoBeuUh6D1vujP9e4/fH0xZjDaO3Aw==\"\n" + " ]\n" + "}"; + + String jsonFeeBump = "{\n" + + " \"_links\": {\n" + + " \"self\": {\n" + + " \"href\": \"http://localhost/transactions/3dfef7d7226995b504f2827cc63d45ad41e9687bb0a8abcf08ba755fedca0352\"\n" + + " },\n" + + " \"account\": {\n" + + " \"href\": \"http://localhost/accounts/GABQGAYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB2MX\"\n" + + " },\n" + + " \"ledger\": {\n" + + " \"href\": \"http://localhost/ledgers/123\"\n" + + " },\n" + + " \"operations\": {\n" + + " \"href\": \"http://localhost/transactions/3dfef7d7226995b504f2827cc63d45ad41e9687bb0a8abcf08ba755fedca0352/operations{?cursor,limit,order}\",\n" + + " \"templated\": true\n" + + " },\n" + + " \"effects\": {\n" + + " \"href\": \"http://localhost/transactions/3dfef7d7226995b504f2827cc63d45ad41e9687bb0a8abcf08ba755fedca0352/effects{?cursor,limit,order}\",\n" + + " \"templated\": true\n" + + " },\n" + + " \"precedes\": {\n" + + " \"href\": \"http://localhost/transactions?order=asc\\u0026cursor=528280981504\"\n" + + " },\n" + + " \"succeeds\": {\n" + + " \"href\": \"http://localhost/transactions?order=desc\\u0026cursor=528280981504\"\n" + + " },\n" + + " \"transaction\": {\n" + + " \"href\": \"http://localhost/transactions/3dfef7d7226995b504f2827cc63d45ad41e9687bb0a8abcf08ba755fedca0352\"\n" + + " }\n" + + " },\n" + + " \"id\": \"3dfef7d7226995b504f2827cc63d45ad41e9687bb0a8abcf08ba755fedca0352\",\n" + + " \"paging_token\": \"528280981504\",\n" + + " \"successful\": true,\n" + + " \"hash\": \"3dfef7d7226995b504f2827cc63d45ad41e9687bb0a8abcf08ba755fedca0352\",\n" + + " \"ledger\": 123,\n" + + " \"created_at\": \"2020-04-21T10:21:26Z\",\n" + + " \"source_account\": \"GABQGAYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB2MX\",\n" + + " \"source_account_sequence\": \"97\",\n" + + " \"fee_account\": \"GABAEAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABGKJ\",\n" + + " \"fee_charged\": 123,\n" + + " \"max_fee\": 776,\n" + + " \"operation_count\": 1,\n" + + " \"envelope_xdr\": \"AAAABQAAAAACAgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMIAAAAAgAAAAADAwMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGMAAAAAAAAAYQAAAAEAAAAAAAAAAgAAAAAAAAAEAAAAAAAAAAEAAAAAAAAACwAAAAAAAABiAAAAAAAAAAECAgICAAAAAxQUFAAAAAAAAAAAAQMDAwMAAAADHh4eAA==\",\n" + + " \"result_xdr\": \"AAAAAAAAAHsAAAAB6Yhpu6i84IwQt4QGICEn84iMJUVM03sCYAhiRSdR9SYAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAsAAAAAAAAAAAAAAAA=\",\n" + + " \"result_meta_xdr\": \"AAAAAQAAAAAAAAAA\",\n" + + " \"fee_meta_xdr\": \"AAAAAA==\",\n" + + " \"memo_type\": \"none\",\n" + + " \"signatures\": [\n" + + " \"Hh4e\"\n" + + " ],\n" + + " \"valid_after\": \"1970-01-01T00:00:02Z\",\n" + + " \"valid_before\": \"1970-01-01T00:00:04Z\",\n" + + " \"fee_bump_transaction\": {\n" + + " \"hash\": \"3dfef7d7226995b504f2827cc63d45ad41e9687bb0a8abcf08ba755fedca0352\",\n" + + " \"signatures\": [\n" + + " \"Hh4e\"\n" + + " ]\n" + + " },\n" + + " \"inner_transaction\": {\n" + + " \"hash\": \"e98869bba8bce08c10b78406202127f3888c25454cd37b02600862452751f526\",\n" + + " \"signatures\": [\n" + + " \"FBQU\"\n" + + " ],\n" + + " \"max_fee\": \"99\"\n" + + " }\n" + + "}"; } diff --git a/src/test/java/org/stellar/sdk/responses/TransactionPageDeserializerTest.java b/src/test/java/org/stellar/sdk/responses/TransactionPageDeserializerTest.java index 1d8db01a1..4255fae66 100644 --- a/src/test/java/org/stellar/sdk/responses/TransactionPageDeserializerTest.java +++ b/src/test/java/org/stellar/sdk/responses/TransactionPageDeserializerTest.java @@ -73,6 +73,7 @@ public void testDeserialize() { " \"result_meta_xdr\": \"AAAAAAAAAAMAAAACAAAAAAAAAAMAAAAAAAAAABbxCy3mLg3hiTqX4VUEEp60pFOrJNxYM1JtxXTwXhY2AAAAAAvrwgAAAAADAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAMAAAAAAAAAAAGUcmKO5465JxTSLQOQljwk2SfqAJmZSG6JH6wtqpwhDeC2s5t4PNQAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAEAAAADAAAAAAAAAAABlHJijueOuScU0i0DkJY8JNkn6gCZmUhuiR+sLaqcIQAAAAAL68IAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAEAAAADAAAAAAAAAAAW8Qst5i4N4Yk6l+FVBBKetKRTqyTcWDNSbcV08F4WNg3gtrObeDzUAAAAAwAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAEAAAABAAAAAwAAAAAAAAAAAZRyYo7njrknFNItA5CWPCTZJ+oAmZlIbokfrC2qnCEAAAAAC+vCAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\",\n" + " \"memo_type\": \"text\",\n" + " \"memo\": \"hello world\",\n" + + " \"memo_bytes\": \"aGVsbG8gd29ybGQ=\",\n" + " \"signatures\": [\n" + " \"SsKlst02jsaBp35vXlI300Qbz39+HbwwNDX/gfM+MwaG/dFF7bgqpkKsDAsh/qJDiN3NOW695IZB9Mj+JAsHBA==\"\n" + " ]\n" + @@ -117,6 +118,7 @@ public void testDeserialize() { " \"result_meta_xdr\": \"AAAAAAAAAAMAAAACAAAAAAAAHqEAAAAAAAAAAB+lHtRjj4+h2/0Tj8iBQiaUDzLo4oRCLyUnytFHzAyIAAAAAAvrwgAAAB6hAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAQAAHqEAAAAAAAAAABbxCy3mLg3hiTqX4VUEEp60pFOrJNxYM1JtxXTwXhY2DeC2s4+MeHwAAAADAAAAAgAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAAB6hAAAAAAAAAACzMOD+8iU8qo+qbTYewT8lxKE/s1cE3FOCVWxsqJ74GwAAAAAL68IAAAAeoQAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAEAAB6hAAAAAAAAAAAW8Qst5i4N4Yk6l+FVBBKetKRTqyTcWDNSbcV08F4WNg3gtrODoLZ8AAAAAwAAAAIAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAeoQAAAAAAAAAASZcLtOTqf+cdbsq8HmLMkeqU06LN94UTWXuSBem5Z88AAAAAC+vCAAAAHqEAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAABAAAeoQAAAAAAAAAAFvELLeYuDeGJOpfhVQQSnrSkU6sk3FgzUm3FdPBeFjYN4Lazd7T0fAAAAAMAAAACAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAA=\",\n" + " \"memo_type\": \"text\",\n" + " \"memo\": \"testpool,faucet,sdf\",\n" + + " \"memo_bytes\": \"dGVzdHBvb2wsZmF1Y2V0LHNkZg==\",\n" + " \"signatures\": [\n" + " \"NIEHl40QqRYihBtnVhBpsm4TmZb8AqGsdPHD0feb+xqL3RhxpmWwB7e5472MTGNJkXbz1lxHi9zTAXDWn9bIBw==\"\n" + " ]\n" + @@ -160,6 +162,7 @@ public void testDeserialize() { " \"result_xdr\": \"AAAAAAAAAGQAAAAAAAAAAQAAAAAAAAAFAAAAAAAAAAA=\",\n" + " \"result_meta_xdr\": \"AAAAAAAAAAEAAAABAAAAAQAAHq8AAAAAAAAAABbxCy3mLg3hiTqX4VUEEp60pFOrJNxYM1JtxXTwXhY2DeC2s3e09BgAAAADAAAAAwAAAAAAAAABAAAAAB+lHtRjj4+h2/0Tj8iBQiaUDzLo4oRCLyUnytFHzAyIAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAA\",\n" + " \"memo_type\": \"text\",\n" + + " \"memo_bytes\": \"\",\n" + " \"signatures\": [\n" + " \"1sNxaxHezfPxDOpq1x1z+TYvgtdYkwDp5dpzx1teQB1zimkfbmKwYLk7C8bTpXC9zcJ7f+vathwE/e/GHHXwDw==\"\n" + " ]\n" + @@ -203,6 +206,7 @@ public void testDeserialize() { " \"result_xdr\": \"AAAAAAAAAGQAAAAAAAAAAQAAAAAAAAAJAAAAAAAAAAEAAAAAH6Ue1GOPj6Hb/ROPyIFCJpQPMujihEIvJSfK0UfMDIgAAK11sXJ6SgAAAAA=\",\n" + " \"result_meta_xdr\": \"AAAAAAAAAAEAAAABAAAAAQAAHrcAAAAAAAAAAB+lHtRjj4+h2/0Tj8iBQiaUDzLo4oRCLyUnytFHzAyIAACtdb1ePEoAAB6hAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAA\",\n" + " \"memo_type\": \"text\",\n" + + " \"memo_bytes\": \"\",\n" + " \"signatures\": [\n" + " \"UQc3yHhlEA5DbWt8xu8R9EqujHH2uSvHeErZdfWPqY3uooA0qbPA8yVxFxvnLOyQ+qhYKy6pf8yRRgt8Nl7tBg==\"\n" + " ]\n" + @@ -246,6 +250,7 @@ public void testDeserialize() { " \"result_xdr\": \"AAAAAAAAAGQAAAAAAAAAAQAAAAAAAAAJAAAAAAAAAAEAAAAAH6Ue1GOPj6Hb/ROPyIFCJpQPMujihEIvJSfK0UfMDIgAAK11sXTKRwAAAAA=\",\n" + " \"result_meta_xdr\": \"AAAAAAAAAAEAAAABAAAAAQAAHr8AAAAAAAAAAB+lHtRjj4+h2/0Tj8iBQiaUDzLo4oRCLyUnytFHzAyIAAFa627TBpEAAB6hAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAA\",\n" + " \"memo_type\": \"text\",\n" + + " \"memo_bytes\": \"\",\n" + " \"signatures\": [\n" + " \"YLL32xpSbpqVchHKnQ02HY4VILJunUkY8M3alv/4XDTv9fvLiU8Sn8HpYhXXfRr7Z5j7lPe6NuIprpMhxRo3BA==\"\n" + " ]\n" + @@ -289,6 +294,7 @@ public void testDeserialize() { " \"result_xdr\": \"AAAAAAAAAGQAAAAAAAAAAQAAAAAAAAAJAAAAAAAAAAEAAAAAH6Ue1GOPj6Hb/ROPyIFCJpQPMujihEIvJSfK0UfMDIgAAK1+KLf6bgAAAAA=\",\n" + " \"result_meta_xdr\": \"AAAAAAAAAAEAAAABAAAAAQAAHsIAAAAAAAAAAB+lHtRjj4+h2/0Tj8iBQiaUDzLo4oRCLyUnytFHzAyIAAIIaZeLAP8AAB6hAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAA\",\n" + " \"memo_type\": \"text\",\n" + + " \"memo_bytes\": \"\",\n" + " \"signatures\": [\n" + " \"I8CjxDCxl70UDSzP914x2GW7r/OuTtKCQEbbYCm+2kRMGGcaRfi2tIUPPyxWUqq/B6oZRWC+NTIzQBwfHJdlDA==\"\n" + " ]\n" + @@ -332,6 +338,7 @@ public void testDeserialize() { " \"result_xdr\": \"AAAAAAAAAGQAAAAAAAAAAQAAAAAAAAABAAAAAAAAAAA=\",\n" + " \"result_meta_xdr\": \"AAAAAAAAAAEAAAACAAAAAQAAHzYAAAAAAAAAABbxCy3mLg3hiTqX4VUEEp60pFOrJNxYM1JtxXTwXhY2AAAABJwsBgAAAAADAAAABwAAAAAAAAABAAAAAB+lHtRjj4+h2/0Tj8iBQiaUDzLo4oRCLyUnytFHzAyIAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAQAAHzYAAAAAAAAAAEmXC7Tk6n/nHW7KvB5izJHqlNOizfeFE1l7kgXpuWfPDeC2rud0rogAAB6hAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAA\",\n" + " \"memo_type\": \"text\",\n" + + " \"memo_bytes\": \"\",\n" + " \"signatures\": [\n" + " \"NkV853/bo6msX7/TKL1m1+4de4vhO7fp4Ci4ZwBLuIerqX5ozLbPf0il3/JEBBQd1HtpyS5ZMHhvbqEQTLXHAA==\"\n" + " ]\n" + @@ -375,7 +382,8 @@ public void testDeserialize() { " \"result_xdr\": \"AAAAAAAAAMgAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAA=\",\n" + " \"result_meta_xdr\": \"AAAAAAAAAAIAAAACAAAAAAAAIeoAAAAAAAAAAAjUE6sKUXWxKAJ/rXUUeDwr/IY9Lxv0qxZKUEJG1mmJAAAAAAvrwgAAACHqAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAQAAIeoAAAAAAAAAAB+lHtRjj4+h2/0Tj8iBQiaUDzLo4oRCLyUnytFHzAyIAAIIaYufPjcAAB6hAAAAAQAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAEAACHqAAAAAAAAAAAI1BOrClF1sSgCf611FHg8K/yGPS8b9KsWSlBCRtZpiQACCGT7Xvr/AAAh6gAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAEAACHqAAAAAAAAAAAfpR7UY4+Podv9E4/IgUImlA8y6OKEQi8lJ8rRR8wMiAAAAAScLAU4AAAeoQAAAAEAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAA==\",\n" + " \"memo_type\": \"text\",\n" + - " \"memo\": \"helpsdf\",\n" + + " \"memo\": \"aGVscHNkZg==\",\n" + + " \"memo_bytes\": \"dGVzdHBvb2wsZmF1Y2V0LHNkZg==\",\n" + " \"signatures\": [\n" + " \"zkTeVJnyYHOmkdKGzlXLkH4yxRUJc2DmppUwLsbJVMk5gGFwDI2UXhfTeucZl11HeLp9R8UvKbvsuvKHjWkBCA==\"\n" + " ]\n" + From 08901592c539c76f2534eef34e01650e4a09c8ed Mon Sep 17 00:00:00 2001 From: Tamir Sen Date: Tue, 21 Apr 2020 17:22:44 +0200 Subject: [PATCH 2/2] Fallback to parsing xdr if memo_bytes is not available --- .../responses/TransactionDeserializer.java | 31 +++++++++++++------ .../TransactionPageDeserializerTest.java | 9 ++++-- 2 files changed, 29 insertions(+), 11 deletions(-) diff --git a/src/main/java/org/stellar/sdk/responses/TransactionDeserializer.java b/src/main/java/org/stellar/sdk/responses/TransactionDeserializer.java index c2736f1f7..9bf14bf9d 100644 --- a/src/main/java/org/stellar/sdk/responses/TransactionDeserializer.java +++ b/src/main/java/org/stellar/sdk/responses/TransactionDeserializer.java @@ -1,12 +1,7 @@ package org.stellar.sdk.responses; import com.google.common.io.BaseEncoding; -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; -import com.google.gson.JsonDeserializationContext; -import com.google.gson.JsonDeserializer; -import com.google.gson.JsonElement; -import com.google.gson.JsonParseException; +import com.google.gson.*; import org.stellar.sdk.Memo; import org.stellar.sdk.xdr.TransactionEnvelope; @@ -35,10 +30,28 @@ public TransactionResponse deserialize(JsonElement json, Type typeOfT, JsonDeser // representation of a transaction. That's why we need to handle a special case // here. if (memoType.equals("text")) { - // we obtain the memo text from the "memo_bytes" field because the original byte sequence may not be valid utf8 - String memoBase64 = json.getAsJsonObject().get("memo_bytes").getAsString(); BaseEncoding base64Encoding = BaseEncoding.base64(); - memo = Memo.text(base64Encoding.decode(memoBase64)); + JsonObject jsonResponse = json.getAsJsonObject(); + + if (jsonResponse.has("memo_bytes")) { + // we obtain the memo text from the "memo_bytes" field because the original byte sequence may not be valid utf8 + String memoBase64 = json.getAsJsonObject().get("memo_bytes").getAsString(); + memo = Memo.text(base64Encoding.decode(memoBase64)); + } else { + // memo_bytes is not available because horizon is running a version older than 1.2.0 + // so we will recover the bytes from the xdr + String envelopeXdr = json.getAsJsonObject().get("envelope_xdr").getAsString(); + byte[] bytes = base64Encoding.decode(envelopeXdr); + TransactionEnvelope transactionEnvelope = null; + try { + transactionEnvelope = TransactionEnvelope.decode(new XdrDataInputStream(new ByteArrayInputStream(bytes))); + } catch (IOException e) { + // JsonDeserializer cannot throw IOExceptions + // so we must throw it as a runtime exception + throw new RuntimeException(e); + } + memo = Memo.text(transactionEnvelope.getTx().getMemo().getText().getBytes()); + } } else { String memoValue = json.getAsJsonObject().get("memo").getAsString(); BaseEncoding base64Encoding = BaseEncoding.base64(); diff --git a/src/test/java/org/stellar/sdk/responses/TransactionPageDeserializerTest.java b/src/test/java/org/stellar/sdk/responses/TransactionPageDeserializerTest.java index 4255fae66..4f5a5cd0e 100644 --- a/src/test/java/org/stellar/sdk/responses/TransactionPageDeserializerTest.java +++ b/src/test/java/org/stellar/sdk/responses/TransactionPageDeserializerTest.java @@ -20,6 +20,12 @@ public void testDeserialize() { assertEquals(transactionsPage.getRecords().get(0).getLinks().getAccount().getHref(), "/accounts/GAAZI4TCR3TY5OJHCTJC2A4QSY6CJWJH5IAJTGKIN2ER7LBNVKOCCWN7"); assertEquals(transactionsPage.getRecords().get(9).getSourceAccount(), "GAENIE5LBJIXLMJIAJ7225IUPA6CX7EGHUXRX5FLCZFFAQSG2ZUYSWFK"); + // Transaction without memo_bytes field + assertTrue(transactionsPage.getRecords().get(7).getMemo() instanceof MemoText); + memoText = (MemoText) transactionsPage.getRecords().get(7).getMemo(); + assertEquals(memoText.getText(), "helpsdf"); + + // Empty memo_text assertTrue(transactionsPage.getRecords().get(2).getMemo() instanceof MemoText); memoText = (MemoText) transactionsPage.getRecords().get(2).getMemo(); @@ -382,8 +388,7 @@ public void testDeserialize() { " \"result_xdr\": \"AAAAAAAAAMgAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAA=\",\n" + " \"result_meta_xdr\": \"AAAAAAAAAAIAAAACAAAAAAAAIeoAAAAAAAAAAAjUE6sKUXWxKAJ/rXUUeDwr/IY9Lxv0qxZKUEJG1mmJAAAAAAvrwgAAACHqAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAQAAIeoAAAAAAAAAAB+lHtRjj4+h2/0Tj8iBQiaUDzLo4oRCLyUnytFHzAyIAAIIaYufPjcAAB6hAAAAAQAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAEAACHqAAAAAAAAAAAI1BOrClF1sSgCf611FHg8K/yGPS8b9KsWSlBCRtZpiQACCGT7Xvr/AAAh6gAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAEAACHqAAAAAAAAAAAfpR7UY4+Podv9E4/IgUImlA8y6OKEQi8lJ8rRR8wMiAAAAAScLAU4AAAeoQAAAAEAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAA==\",\n" + " \"memo_type\": \"text\",\n" + - " \"memo\": \"aGVscHNkZg==\",\n" + - " \"memo_bytes\": \"dGVzdHBvb2wsZmF1Y2V0LHNkZg==\",\n" + + " \"memo\": \"helpsdf\",\n" + " \"signatures\": [\n" + " \"zkTeVJnyYHOmkdKGzlXLkH4yxRUJc2DmppUwLsbJVMk5gGFwDI2UXhfTeucZl11HeLp9R8UvKbvsuvKHjWkBCA==\"\n" + " ]\n" +