From 5a60f49a474f4d8b6fb7c03e6894b7ee3f76335e Mon Sep 17 00:00:00 2001 From: jc0803kevin Date: Fri, 27 Dec 2024 11:43:56 +0800 Subject: [PATCH 1/3] feat: message deserialize --- .../org/p2p/solanaj/core/AccountMeta.java | 10 ++ .../java/org/p2p/solanaj/core/Message.java | 160 ++++++++++++++++-- .../org/p2p/solanaj/core/Transaction.java | 88 +++++++++- .../java/org/p2p/solanaj/utils/ByteUtils.java | 20 +++ .../p2p/solanaj/utils/GuardedArrayUtils.java | 42 +++++ .../p2p/solanaj/utils/ShortvecEncoding.java | 43 ++++- .../org/p2p/solanaj/core/MessageTest.java | 88 +++++++++- .../org/p2p/solanaj/core/TransactionTest.java | 15 ++ .../solanaj/utils/GuardedArrayUtilsTest.java | 15 ++ .../solanaj/utils/ShortvecEncodingTest.java | 31 ++++ 10 files changed, 488 insertions(+), 24 deletions(-) create mode 100644 src/main/java/org/p2p/solanaj/utils/GuardedArrayUtils.java create mode 100644 src/test/java/org/p2p/solanaj/utils/GuardedArrayUtilsTest.java diff --git a/src/main/java/org/p2p/solanaj/core/AccountMeta.java b/src/main/java/org/p2p/solanaj/core/AccountMeta.java index bfd6e525..2944eec2 100644 --- a/src/main/java/org/p2p/solanaj/core/AccountMeta.java +++ b/src/main/java/org/p2p/solanaj/core/AccountMeta.java @@ -13,4 +13,14 @@ public class AccountMeta { private boolean isWritable; + /** + * Sorting based on isSigner and isWritable cannot fully meet the requirements. This value can be used for custom sorting, because if the order is incorrect during serialization, it may lead to failed method calls. + */ + private int sort = Integer.MAX_VALUE; + + public AccountMeta(PublicKey publicKey, boolean isSigner, boolean isWritable) { + this.publicKey = publicKey; + this.isSigner = isSigner; + this.isWritable = isWritable; + } } \ No newline at end of file diff --git a/src/main/java/org/p2p/solanaj/core/Message.java b/src/main/java/org/p2p/solanaj/core/Message.java index b9ca7687..d09fc44b 100644 --- a/src/main/java/org/p2p/solanaj/core/Message.java +++ b/src/main/java/org/p2p/solanaj/core/Message.java @@ -5,24 +5,37 @@ import java.nio.ByteBuffer; import java.util.ArrayList; -import java.util.Comparator; +import java.util.Arrays; import java.util.List; +import java.util.Comparator; import java.util.stream.Collectors; +import lombok.Getter; + +import org.p2p.solanaj.utils.GuardedArrayUtils; + public class Message { - private class MessageHeader { + public static class MessageHeader { static final int HEADER_LENGTH = 3; byte numRequiredSignatures = 0; byte numReadonlySignedAccounts = 0; byte numReadonlyUnsignedAccounts = 0; + public MessageHeader(){} + + MessageHeader(byte[] byteArray) { + numRequiredSignatures = byteArray[0]; + numReadonlySignedAccounts = byteArray[1]; + numReadonlyUnsignedAccounts = byteArray[2]; + } + byte[] toByteArray() { return new byte[] { numRequiredSignatures, numReadonlySignedAccounts, numReadonlyUnsignedAccounts }; } } - private class CompiledInstruction { + private static class CompiledInstruction { byte programIdIndex; byte[] keyIndicesCount; byte[] keyIndices; @@ -39,13 +52,23 @@ int getLength() { private MessageHeader messageHeader; private String recentBlockhash; - private AccountKeysList accountKeys; - private List instructions; - private Account feePayer; + private final AccountKeysList accountKeys; + private final List instructions; + private PublicKey feePayer; public Message() { this.accountKeys = new AccountKeysList(); - this.instructions = new ArrayList(); + this.instructions = new ArrayList<>(); + } + + public Message(MessageHeader messageHeader, String recentBlockhash, AccountKeysList accountKeys, + List compiledInstructions) { + this.messageHeader = messageHeader; + this.recentBlockhash = recentBlockhash; + this.accountKeys = accountKeys; + this.instructions = compiledInstructions; + + this.feePayer = accountKeys.getList().get(0).getPublicKey(); } public Message addInstruction(TransactionInstruction instruction) { @@ -53,6 +76,21 @@ public Message addInstruction(TransactionInstruction instruction) { accountKeys.add(new AccountMeta(instruction.getProgramId(), false, false)); instructions.add(instruction); +// List keysList = getAccountKeys(); +// int keysSize = instruction.getKeys().size(); +// +// CompiledInstruction compiledInstruction = new CompiledInstruction(); +// compiledInstruction.programIdIndex = (byte) findAccountIndex(keysList, instruction.getProgramId()); +// compiledInstruction.keyIndicesCount = ShortvecEncoding.encodeLength(keysSize); +// byte[] keyIndices = new byte[keysSize]; +// for (int i = 0; i < instruction.getKeys().size(); i++) { +// keyIndices[i] = (byte) findAccountIndex(keysList, instruction.getKeys().get(i).getPublicKey()); +// } +// compiledInstruction.keyIndices = keyIndices; +// compiledInstruction.dataLength = ShortvecEncoding.encodeLength(instruction.getData().length); +// compiledInstruction.data = instruction.getData(); +// instructions.add(compiledInstruction); + return this; } @@ -60,13 +98,25 @@ public void setRecentBlockHash(String recentBlockhash) { this.recentBlockhash = recentBlockhash; } + public String getRecentBlockhash() { + return recentBlockhash; + } + + public MessageHeader getMessageHeader() { + return messageHeader; + } + + public List getInstructions() { + return instructions; + } + public byte[] serialize() { if (recentBlockhash == null) { throw new IllegalArgumentException("recentBlockhash required"); } - if (instructions.size() == 0) { + if (instructions.isEmpty()) { throw new IllegalArgumentException("No instructions provided"); } @@ -78,7 +128,7 @@ public byte[] serialize() { byte[] accountAddressesLength = ShortvecEncoding.encodeLength(accountKeysSize); int compiledInstructionsLength = 0; - List compiledInstructions = new ArrayList(); + List compiledInstructions = new ArrayList<>(); for (TransactionInstruction instruction : instructions) { int keysSize = instruction.getKeys().size(); @@ -143,18 +193,37 @@ public byte[] serialize() { return out.array(); } - protected void setFeePayer(Account feePayer) { + protected void setFeePayer(PublicKey feePayer) { this.feePayer = feePayer; } + public PublicKey getFeePayer() { + return feePayer; + } + public List getAccountKeys() { - AccountKeysList accounts = new AccountKeysList(); - accounts.add(new AccountMeta(feePayer.getPublicKey(), true, true)); - accounts.addAll(accountKeys); - return accounts.getList(); + List keysList = accountKeys.getList(); + + // Check whether custom sorting is needed. The `getAccountKeys()` method returns a reversed list of accounts, with signable and mutable accounts at the end, but the fee is placed first. When a transaction involves multiple accounts that need signing, an incorrect order can cause bugs. Change to custom sorting based on the contract order. + boolean needSort = keysList.stream().anyMatch(accountMeta -> accountMeta.getSort() < Integer.MAX_VALUE); + if (needSort) { + // Sort in ascending order based on the `sort` field. + return keysList.stream() + .sorted(Comparator.comparingInt(AccountMeta::getSort)) + .collect(Collectors.toList()); + } + + int feePayerIndex = findAccountIndex(keysList, feePayer); + List newList = new ArrayList(); + AccountMeta feePayerMeta = keysList.get(feePayerIndex); + newList.add(new AccountMeta(feePayerMeta.getPublicKey(), true, true)); + keysList.remove(feePayerIndex); + newList.addAll(keysList); + + return newList; } - private int findAccountIndex(List accountMetaList, PublicKey key) { + public int findAccountIndex(List accountMetaList, PublicKey key) { for (int i = 0; i < accountMetaList.size(); i++) { if (accountMetaList.get(i).getPublicKey().equals(key)) { return i; @@ -163,4 +232,65 @@ private int findAccountIndex(List accountMetaList, PublicKey key) { throw new RuntimeException("unable to find account index"); } + + public static Message deserialize(List serializedMessageList) { + // Remove the byte as it is used to indicate legacy Transaction. +// GuardedArrayUtils.guardedShift(serializedMessageList); + + // Remove three bytes for header + byte[] messageHeaderBytes = GuardedArrayUtils.guardedSplice(serializedMessageList, 0, MessageHeader.HEADER_LENGTH); + byte numRequiredSignatures = messageHeaderBytes[0]; + byte numReadonlySignedAccounts = messageHeaderBytes[1]; + byte numReadonlyUnsignedAccounts = messageHeaderBytes[2]; + MessageHeader messageHeader = new MessageHeader(messageHeaderBytes); + + // Total static account keys + int accountKeysSize = ShortvecEncoding.decodeLength(serializedMessageList); +// byte[] accountAddressesLength = ShortvecEncoding.encodeLength(accountKeysSize); +// GuardedArrayUtils.guardedSplice(serializedMessageList, 0, accountAddressesLength.length); + + + + List accountKeys = new ArrayList<>(accountKeysSize); + for (int i = 0; i < accountKeysSize; i++) { + byte[] accountMetaPublicKeyByteArray = GuardedArrayUtils.guardedSplice(serializedMessageList, 0, + PublicKey.PUBLIC_KEY_LENGTH); + PublicKey publicKey = new PublicKey(accountMetaPublicKeyByteArray); + accountKeys.add(new AccountMeta(publicKey, false, false)); + } + AccountKeysList accountKeysList = new AccountKeysList(); + accountKeysList.addAll(accountKeys); + + // recent_blockhash + String recentBlockHash = Base58.encode(GuardedArrayUtils.guardedSplice(serializedMessageList, 0, + PublicKey.PUBLIC_KEY_LENGTH)); + + // Deserialize instructions + int instructionsLength = ShortvecEncoding.decodeLength(serializedMessageList); + List instructions = new ArrayList<>(); + List compiledInstructions = new ArrayList<>(instructionsLength); + for (int i = 0; i < instructionsLength; i++) { + CompiledInstruction compiledInstruction = new CompiledInstruction(); + compiledInstruction.programIdIndex = GuardedArrayUtils.guardedShift(serializedMessageList); + int keysSize = ShortvecEncoding.decodeLength(serializedMessageList); // keysSize + compiledInstruction.keyIndicesCount = ShortvecEncoding.encodeLength(keysSize); + compiledInstruction.keyIndices = GuardedArrayUtils.guardedSplice(serializedMessageList, 0, keysSize); + var dataLength = ShortvecEncoding.decodeLength(serializedMessageList); + compiledInstruction.dataLength = ShortvecEncoding.encodeLength(dataLength); + compiledInstruction.data = GuardedArrayUtils.guardedSplice(serializedMessageList, 0, dataLength); + + compiledInstructions.add(compiledInstruction); + + PublicKey programId = accountKeys.get(compiledInstruction.programIdIndex).getPublicKey(); + List keys = new ArrayList<>(); + for (int i1 = 0; i1 < compiledInstruction.keyIndices.length; i1++) { + keys.add(accountKeys.get(compiledInstruction.keyIndices[i1])); + } + instructions.add(new TransactionInstruction(programId, keys, compiledInstruction.data)); + } + + return new Message(messageHeader, recentBlockHash, accountKeysList, instructions); +// return new Message(); + } + } diff --git a/src/main/java/org/p2p/solanaj/core/Transaction.java b/src/main/java/org/p2p/solanaj/core/Transaction.java index bab10710..f1cd01bd 100644 --- a/src/main/java/org/p2p/solanaj/core/Transaction.java +++ b/src/main/java/org/p2p/solanaj/core/Transaction.java @@ -7,6 +7,8 @@ import java.util.Objects; import org.bitcoinj.core.Base58; +import org.p2p.solanaj.utils.ByteUtils; +import org.p2p.solanaj.utils.GuardedArrayUtils; import org.p2p.solanaj.utils.ShortvecEncoding; import org.p2p.solanaj.utils.TweetNaclFast; @@ -30,6 +32,11 @@ public Transaction() { this.signatures = new ArrayList<>(); // Use diamond operator } + public Transaction(Message message, List signatures) { + this.message = message; + this.signatures = signatures; + } + /** * Adds an instruction to the transaction. * @@ -61,7 +68,7 @@ public void setRecentBlockHash(String recentBlockhash) { * @throws NullPointerException if the signer is null */ public void sign(Account signer) { - sign(Arrays.asList(Objects.requireNonNull(signer, "Signer cannot be null"))); // Add input validation + sign(List.of(Objects.requireNonNull(signer, "Signer cannot be null"))); // Add input validation } /** @@ -76,7 +83,7 @@ public void sign(List signers) { } Account feePayer = signers.get(0); - message.setFeePayer(feePayer); + message.setFeePayer(feePayer.getPublicKey()); serializedMessage = message.serialize(); @@ -91,6 +98,13 @@ public void sign(List signers) { } } + public String getTxHash(){ + if (signatures == null || signatures.isEmpty()){ + return null; + } + return signatures.get(0); + } + /** * Serializes the transaction into a byte array. * @@ -115,4 +129,74 @@ public byte[] serialize() { return out.array(); } + + +// public static Transaction deserialize(byte[] serializeTx){ +// int signaturesSize = ShortvecEncoding.decodeLength(serializeTx); +// List signatures = new ArrayList<>(signaturesSize); +// +// List serializedTransactionList = ByteUtils.toByteList(serializeTx); +// +// for (int i = 0; i < signaturesSize; i++) { +// +// byte[] signatureBytes = GuardedArrayUtils.guardedSplice(serializedTransactionList, 0, SIGNATURE_LENGTH); +// signatures.add(Base58.encode(signatureBytes)); +// } +// +//// Message message = Message.deserialize(serializedTransactionList); +// return new Transaction(null, signatures); +// } + + +// public static Transaction deserialize(byte[] serializedTransaction) { +// List serializedTransactionList = ByteUtils.toByteList(serializedTransaction); +// +// int signaturesSize = ShortvecEncoding.decodeLength(serializedTransaction); +// List signatures = new ArrayList<>(signaturesSize); +// +// byte[] byteArray = serializedTransaction; +// +// for (int i = 0; i < signaturesSize; i++) { +// +// byte[] signatureBytes = GuardedArrayUtils.guardedSplice(serializedTransactionList, 0, SIGNATURE_LENGTH); +// signatures.add(Base58.encode(signatureBytes)); +// +//// System.out.println("byteArray--1->" + Arrays.toString(byteArray)); +//// byte[] signature = slice(byteArray, 0, Transaction.SIGNATURE_LENGTH); +//// System.out.println("signature-- ->" + Arrays.toString(signature)); +//// signatures.add(Base58.encode(signature)); +//// +//// System.out.println("byteArray--2->" + Arrays.toString(byteArray)); +//// byteArray = Arrays.copyOfRange(byteArray, Transaction.SIGNATURE_LENGTH, byteArray.length); +//// +//// System.out.println("byteArray--3->" + Arrays.toString(byteArray)); +//// signatures.add(Base58.encode(signature)); +// } +// +// // Message message = Message.deserialize(serializedTransactionList); +// // todo kevin +// return new Transaction(null, signatures); +// } + + public static Transaction deserialize(byte[] serializedTransaction) { + List serializedTransactionList = ByteUtils.toByteList(serializedTransaction); + + int signaturesSize = ShortvecEncoding.decodeLength(serializedTransactionList); + List signatures = new ArrayList<>(signaturesSize); + + for (int i = 0; i < signaturesSize; i++) { + + byte[] signatureBytes = GuardedArrayUtils.guardedSplice(serializedTransactionList, 0, SIGNATURE_LENGTH); + signatures.add(Base58.encode(signatureBytes)); + } + + Message message = Message.deserialize(serializedTransactionList); + return new Transaction(message, signatures); + } + + public static byte[] slice(byte[] data, int start, int length) { + byte[] slice = new byte[length]; + System.arraycopy(data, start, slice, 0, length); + return slice; + } } diff --git a/src/main/java/org/p2p/solanaj/utils/ByteUtils.java b/src/main/java/org/p2p/solanaj/utils/ByteUtils.java index 60ff25d6..9c6a3cb0 100644 --- a/src/main/java/org/p2p/solanaj/utils/ByteUtils.java +++ b/src/main/java/org/p2p/solanaj/utils/ByteUtils.java @@ -1,11 +1,16 @@ package org.p2p.solanaj.utils; +import com.google.common.primitives.Bytes; + import static org.bitcoinj.core.Utils.*; import java.io.IOException; import java.io.OutputStream; import java.math.BigInteger; import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.IntStream; public class ByteUtils { public static final int UINT_32_LENGTH = 4; @@ -72,4 +77,19 @@ public static int getBit(byte[] data, int pos) { return valInt; } + public static byte[] toByteArray(List byteList) { + return Bytes.toArray(byteList); + } + + public static List toByteList(byte[] bytes) { + return IntStream.range(0, bytes.length) + .mapToObj(i -> bytes[i]) + .collect(Collectors.toList()); + } + + public static byte[] emptyByteArray() { + return new byte[]{}; + } + + } diff --git a/src/main/java/org/p2p/solanaj/utils/GuardedArrayUtils.java b/src/main/java/org/p2p/solanaj/utils/GuardedArrayUtils.java new file mode 100644 index 00000000..c2fe99b7 --- /dev/null +++ b/src/main/java/org/p2p/solanaj/utils/GuardedArrayUtils.java @@ -0,0 +1,42 @@ +package org.p2p.solanaj.utils; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class GuardedArrayUtils { + + public static Byte guardedShift(List byteArray) { + if (byteArray.isEmpty()) { + throw new IllegalArgumentException("Byte array length is 0"); + } + return byteArray.remove(0); + } + + public static byte[] guardedSplice( + List byteList, + Integer start, + Integer deleteCount, + byte... items) { + List removedItems; + if (deleteCount != null) { + if (start + deleteCount > byteList.size()) { + throw new Error("Reached end of bytes"); + } + removedItems = new ArrayList<>(byteList.subList(start, start + deleteCount)); + byteList.subList(start, start + deleteCount).clear(); + } else { + if (start > byteList.size()) { + throw new Error("Reached end of bytes"); + } + removedItems = Collections.emptyList(); + } + List itemsToAdd = ByteUtils.toByteList(items); + byteList.addAll(start, itemsToAdd); + + if (!removedItems.isEmpty()) { + return ByteUtils.toByteArray(removedItems); + } + return ByteUtils.emptyByteArray(); + } +} diff --git a/src/main/java/org/p2p/solanaj/utils/ShortvecEncoding.java b/src/main/java/org/p2p/solanaj/utils/ShortvecEncoding.java index 4a1d9080..77e967e3 100644 --- a/src/main/java/org/p2p/solanaj/utils/ShortvecEncoding.java +++ b/src/main/java/org/p2p/solanaj/utils/ShortvecEncoding.java @@ -1,6 +1,9 @@ package org.p2p.solanaj.utils; -import static org.bitcoinj.core.Utils.*; +import java.util.Arrays; +import java.util.List; + +import static org.bitcoinj.core.Utils.uint16ToByteArrayLE; public class ShortvecEncoding { @@ -27,4 +30,42 @@ public static byte[] encodeLength(int len) { return bytes; } + +// public static int decodeLength(byte[] bytes){ +// int len = 0; +// int size = 0; +// +// byte[] out = bytes; +// +// for (;;){ +// //从数组中删除第一个元素,并返回该元素的值。此方法更改数组的长度。 +// int elem = Byte.toUnsignedInt(out[0]) ; +// out = Arrays.copyOfRange(out, 1, out.length); +// +// len |= (elem & 0x7f) << (size * 7); +// size += 1; +// if((elem & 0x80) == 0){ +// break; +// } +// +// } +// return len; +// } + + + public static int decodeLength(List dataBytesList) { + int len = 0; + int size = 0; + + for (;;) { + int elem = (int) dataBytesList.remove(0); + len |= (elem & 0x7f) << (size * 7); + size += 1; + if ((elem & 0x80) == 0) { + break; + } + } + return len; + } + } diff --git a/src/test/java/org/p2p/solanaj/core/MessageTest.java b/src/test/java/org/p2p/solanaj/core/MessageTest.java index 05db0fda..d3b37094 100644 --- a/src/test/java/org/p2p/solanaj/core/MessageTest.java +++ b/src/test/java/org/p2p/solanaj/core/MessageTest.java @@ -3,23 +3,35 @@ import org.bitcoinj.core.Base58; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; + import org.p2p.solanaj.programs.SystemProgram; +import org.p2p.solanaj.utils.ByteUtils; + +import java.util.Arrays; +import java.util.List; public class MessageTest { + Account signer = new Account(Base58 + .decode("4Z7cXSyeFR8wNGMVXUE1TwtKn5D5Vu7FzEv69dokLv7KrQk7h6pu4LF8ZRR9yQBhc7uSM6RTTZtU1fmaxiNrxXrs")); - @Test - public void serializeMessage() { + + + private TransactionInstruction transfer(){ PublicKey fromPublicKey = new PublicKey("QqCCvshxtqMAL2CVALqiJB7uEeE5mjSPsseQdDzsRUo"); PublicKey toPublickKey = new PublicKey("GrDMoeqMLFjeXQ24H56S1RLgT4R76jsuWCd6SvXyGPQ5"); int lamports = 3000; - Account signer = new Account(Base58 - .decode("4Z7cXSyeFR8wNGMVXUE1TwtKn5D5Vu7FzEv69dokLv7KrQk7h6pu4LF8ZRR9yQBhc7uSM6RTTZtU1fmaxiNrxXrs")); + return SystemProgram.transfer(fromPublicKey, toPublickKey, lamports); + } + + @Test + public void serializeMessage() { Message message = new Message(); - message.addInstruction(SystemProgram.transfer(fromPublicKey, toPublickKey, lamports)); + message.setFeePayer(signer.getPublicKey()); + message.addInstruction(transfer()); message.setRecentBlockHash("Eit7RCyhUixAe2hGBS8oqnw59QK3kgMMjfLME5bm9wRn"); - message.setFeePayer(signer); assertArrayEquals(new int[] { 1, 0, 1, 3, 6, 26, 217, 208, 83, 135, 21, 72, 83, 126, 222, 62, 38, 24, 73, 163, 223, 183, 253, 2, 250, 188, 117, 178, 35, 200, 228, 106, 219, 133, 61, 12, 235, 122, 188, 208, 216, 117, @@ -30,6 +42,61 @@ public void serializeMessage() { 0, 0, 0, 0, 0 }, toUnsignedByteArray(message.serialize())); } + @Test + public void deserialize(){ + int[] serialize = new int[] { 1, 0, 1, 3, 6, 26, 217, 208, 83, 135, 21, 72, 83, 126, 222, 62, 38, 24, 73, 163, + 223, 183, 253, 2, 250, 188, 117, 178, 35, 200, 228, 106, 219, 133, 61, 12, 235, 122, 188, 208, 216, 117, + 235, 194, 109, 161, 177, 129, 163, 51, 155, 62, 242, 163, 22, 149, 187, 122, 189, 188, 103, 130, 115, + 188, 173, 205, 229, 170, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 203, 226, 136, 193, 153, 148, 240, 50, 230, 98, 9, 79, 221, 179, 243, 174, 90, 67, + 104, 169, 6, 187, 165, 72, 36, 156, 19, 57, 132, 38, 69, 245, 1, 2, 2, 0, 1, 12, 2, 0, 0, 0, 184, 11, 0, + 0, 0, 0, 0, 0 }; + List bytes = ByteUtils.toByteList(intToByteArray(serialize)); + Message message = Message.deserialize(bytes); + assertEquals("Eit7RCyhUixAe2hGBS8oqnw59QK3kgMMjfLME5bm9wRn", message.getRecentBlockhash()); + assertArrayEquals(new int[]{1, 0, 1}, toUnsignedByteArray(message.getMessageHeader().toByteArray())); + assertEquals(1, message.getInstructions().size()); + + TransactionInstruction transfer = transfer(); +// TransactionInstruction transferNew = message.getInstructions().get(0); +// assertEquals(transfer.getProgramId(), transferNew.getProgramId()); +// assertArrayEquals(transfer.getData(), transferNew.getData()); + + + +// assertEquals(transfer(), message.getInstructions().get(0)); + + + + int[] serializeNew = toUnsignedByteArray(message.serialize()); + System.out.println(Arrays.toString(serializeNew)); + + + assertArrayEquals(serialize, serializeNew); + + } + + + @Test + public void int_byte_convert(){ + int[] serialize = new int[] { 1, 0, 1, 3, 6, 26, 217, 208, 83, 135, 21, 72, 83, 126, 222, 62, 38, 24, 73, 163, + 223, 183, 253, 2, 250, 188, 117, 178, 35, 200, 228, 106, 219, 133, 61, 12, 235, 122, 188, 208, 216, 117, + 235, 194, 109, 161, 177, 129, 163, 51, 155, 62, 242, 163, 22, 149, 187, 122, 189, 188, 103, 130, 115, + 188, 173, 205, 229, 170, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 203, 226, 136, 193, 153, 148, 240, 50, 230, 98, 9, 79, 221, 179, 243, 174, 90, 67, + 104, 169, 6, 187, 165, 72, 36, 156, 19, 57, 132, 38, 69, 245, 1, 2, 2, 0, 1, 12, 2, 0, 0, 0, 184, 11, 0, + 0, 0, 0, 0, 0 }; + + byte[] bytes = intToByteArray(serialize); + + int[] serializeNew = toUnsignedByteArray(bytes); + + byte[] bytesNew = intToByteArray(serializeNew); + + assertArrayEquals(serialize, serializeNew); + assertArrayEquals(bytes, bytesNew); + } + int[] toUnsignedByteArray(byte[] in) { int[] out = new int[in.length]; @@ -39,4 +106,13 @@ int[] toUnsignedByteArray(byte[] in) { return out; } + + public static byte[] intToByteArray(int[] in) { + byte[] out = new byte[in.length]; + for (int i = 0; i < in.length; i++) { + out[i] = (byte) (in[i] & 0xff); + } + return out; + } + } diff --git a/src/test/java/org/p2p/solanaj/core/TransactionTest.java b/src/test/java/org/p2p/solanaj/core/TransactionTest.java index eae14086..9d2a0b50 100644 --- a/src/test/java/org/p2p/solanaj/core/TransactionTest.java +++ b/src/test/java/org/p2p/solanaj/core/TransactionTest.java @@ -5,6 +5,7 @@ import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertEquals; import java.util.Base64; import java.util.List; @@ -28,11 +29,25 @@ public void signAndSerialize() { transaction.sign(signer); byte[] serializedTransaction = transaction.serialize(); + assertEquals("nXkZvmiP3kzZbR7u95NSoK78Y3YqgSSthseuba99uBGsEBnR4RXugEhrAFmqhvWiN8k9aZNTZTE22NH6nBX3B7T", transaction.getTxHash()); + assertEquals( "ASdDdWBaKXVRA+6flVFiZokic9gK0+r1JWgwGg/GJAkLSreYrGF4rbTCXNJvyut6K6hupJtm72GztLbWNmRF1Q4BAAEDBhrZ0FOHFUhTft4+JhhJo9+3/QL6vHWyI8jkatuFPQzrerzQ2HXrwm2hsYGjM5s+8qMWlbt6vbxngnO8rc3lqgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAy+KIwZmU8DLmYglP3bPzrlpDaKkGu6VIJJwTOYQmRfUBAgIAAQwCAAAAuAsAAAAAAAA=", Base64.getEncoder().encodeToString(serializedTransaction)); } + + @Test + public void deserialize() { + String serializedTxBase64 = "ASdDdWBaKXVRA+6flVFiZokic9gK0+r1JWgwGg/GJAkLSreYrGF4rbTCXNJvyut6K6hupJtm72GztLbWNmRF1Q4BAAEDBhrZ0FOHFUhTft4+JhhJo9+3/QL6vHWyI8jkatuFPQzrerzQ2HXrwm2hsYGjM5s+8qMWlbt6vbxngnO8rc3lqgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAy+KIwZmU8DLmYglP3bPzrlpDaKkGu6VIJJwTOYQmRfUBAgIAAQwCAAAAuAsAAAAAAAA="; + byte[] serializedTransaction = Base64.getDecoder().decode(serializedTxBase64); + Transaction transaction = Transaction.deserialize(serializedTransaction); + + assertEquals("nXkZvmiP3kzZbR7u95NSoK78Y3YqgSSthseuba99uBGsEBnR4RXugEhrAFmqhvWiN8k9aZNTZTE22NH6nBX3B7T", transaction.getTxHash()); + + + } + @Test public void transactionBuilderTest() { final String memo = "Test memo"; diff --git a/src/test/java/org/p2p/solanaj/utils/GuardedArrayUtilsTest.java b/src/test/java/org/p2p/solanaj/utils/GuardedArrayUtilsTest.java new file mode 100644 index 00000000..50cb872f --- /dev/null +++ b/src/test/java/org/p2p/solanaj/utils/GuardedArrayUtilsTest.java @@ -0,0 +1,15 @@ +package org.p2p.solanaj.utils; + +import org.junit.jupiter.api.Test; + +public class GuardedArrayUtilsTest { + + @Test + public void a(){ + + +// GuardedArrayUtils.guardedSplice(); + } + + +} diff --git a/src/test/java/org/p2p/solanaj/utils/ShortvecEncodingTest.java b/src/test/java/org/p2p/solanaj/utils/ShortvecEncodingTest.java index 4d6ee62b..91724b9f 100644 --- a/src/test/java/org/p2p/solanaj/utils/ShortvecEncodingTest.java +++ b/src/test/java/org/p2p/solanaj/utils/ShortvecEncodingTest.java @@ -14,8 +14,39 @@ public void encodeLength() { assertArrayEquals(new byte[] { -128, 1 }/* [0x80, 0x01] */, ShortvecEncoding.encodeLength(128)); // 0x80 assertArrayEquals(new byte[] { -1, 1 } /* [0xff, 0x01] */, ShortvecEncoding.encodeLength(255)); // 0xff assertArrayEquals(new byte[] { -128, 2 } /* [0x80, 0x02] */, ShortvecEncoding.encodeLength(256)); // 0x100 + assertArrayEquals(new byte[]{ (byte) 0b10101100, (byte) 0b00000010}, ShortvecEncoding.encodeLength(300)); // assertArrayEquals(new byte[] { -1, -1, 1 } /* [0xff, 0xff, 0x01] */, ShortvecEncoding.encodeLength(32767)); // 0x7fff assertArrayEquals(new byte[] { -128, -128, -128, 1 } /* [0x80, 0x80, 0x80, 0x01] */, ShortvecEncoding.encodeLength(2097152)); // 0x200000 } + + @Test + public void decodeLength(){ + +// assertEquals(0, ShortvecEncoding.decodeLength(new byte[]{0})); +// assertEquals(1, ShortvecEncoding.decodeLength(new byte[]{1})); +// assertEquals(5, ShortvecEncoding.decodeLength(new byte[]{5})); +// assertEquals(127, ShortvecEncoding.decodeLength(new byte[]{127})); +// assertEquals(128, ShortvecEncoding.decodeLength(new byte[]{-128, 1})); +// assertEquals(255, ShortvecEncoding.decodeLength(new byte[]{-1, 1})); +// assertEquals(256, ShortvecEncoding.decodeLength(new byte[]{-128, 2})); +// assertEquals(32767, ShortvecEncoding.decodeLength(new byte[]{-1, -1, 1})); +// assertEquals(2097152, ShortvecEncoding.decodeLength(new byte[]{-128, -128, -128, 1})); + + } + @Test + public void decodeLength2(){ + + assertEquals(0, ShortvecEncoding.decodeLength(ByteUtils.toByteList(new byte[]{0}))); + assertEquals(1, ShortvecEncoding.decodeLength(ByteUtils.toByteList(new byte[]{1}))); + assertEquals(5, ShortvecEncoding.decodeLength(ByteUtils.toByteList(new byte[]{5}))); + assertEquals(127, ShortvecEncoding.decodeLength(ByteUtils.toByteList(new byte[]{127}))); + assertEquals(128, ShortvecEncoding.decodeLength(ByteUtils.toByteList(new byte[]{-128, 1}))); + assertEquals(255, ShortvecEncoding.decodeLength(ByteUtils.toByteList(new byte[]{-1, 1}))); + assertEquals(256, ShortvecEncoding.decodeLength(ByteUtils.toByteList(new byte[]{-128, 2}))); + assertEquals(300, ShortvecEncoding.decodeLength(ByteUtils.toByteList(new byte[]{ (byte) 0b10101100, (byte) 0b00000010}))); + assertEquals(32767, ShortvecEncoding.decodeLength(ByteUtils.toByteList(new byte[]{-1, -1, 1}))); + assertEquals(2097152, ShortvecEncoding.decodeLength(ByteUtils.toByteList(new byte[]{-128, -128, -128, 1}))); + + } } From d9a1f50461058a0d8937a6ef7b8bc1f68562e277 Mon Sep 17 00:00:00 2001 From: jc0803kevin Date: Thu, 2 Jan 2025 15:29:27 +0800 Subject: [PATCH 2/3] feat: message deserialize --- .../org/p2p/solanaj/core/AccountMeta.java | 8 ++ .../java/org/p2p/solanaj/core/Message.java | 97 +++++++++++++------ .../org/p2p/solanaj/core/MessageTest.java | 22 ++--- 3 files changed, 80 insertions(+), 47 deletions(-) diff --git a/src/main/java/org/p2p/solanaj/core/AccountMeta.java b/src/main/java/org/p2p/solanaj/core/AccountMeta.java index 2944eec2..0faa7aca 100644 --- a/src/main/java/org/p2p/solanaj/core/AccountMeta.java +++ b/src/main/java/org/p2p/solanaj/core/AccountMeta.java @@ -23,4 +23,12 @@ public AccountMeta(PublicKey publicKey, boolean isSigner, boolean isWritable) { this.isSigner = isSigner; this.isWritable = isWritable; } + + public void setSigner(boolean signer) { + isSigner = signer; + } + + public void setWritable(boolean writable) { + isWritable = writable; + } } \ No newline at end of file diff --git a/src/main/java/org/p2p/solanaj/core/Message.java b/src/main/java/org/p2p/solanaj/core/Message.java index d09fc44b..eaeb35a8 100644 --- a/src/main/java/org/p2p/solanaj/core/Message.java +++ b/src/main/java/org/p2p/solanaj/core/Message.java @@ -1,21 +1,18 @@ package org.p2p.solanaj.core; import org.bitcoinj.core.Base58; +import org.p2p.solanaj.utils.GuardedArrayUtils; import org.p2p.solanaj.utils.ShortvecEncoding; import java.nio.ByteBuffer; import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; +import java.util.Collections; import java.util.Comparator; +import java.util.List; import java.util.stream.Collectors; -import lombok.Getter; - -import org.p2p.solanaj.utils.GuardedArrayUtils; - public class Message { - public static class MessageHeader { + private static class MessageHeader { static final int HEADER_LENGTH = 3; byte numRequiredSignatures = 0; @@ -75,22 +72,6 @@ public Message addInstruction(TransactionInstruction instruction) { accountKeys.addAll(instruction.getKeys()); accountKeys.add(new AccountMeta(instruction.getProgramId(), false, false)); instructions.add(instruction); - -// List keysList = getAccountKeys(); -// int keysSize = instruction.getKeys().size(); -// -// CompiledInstruction compiledInstruction = new CompiledInstruction(); -// compiledInstruction.programIdIndex = (byte) findAccountIndex(keysList, instruction.getProgramId()); -// compiledInstruction.keyIndicesCount = ShortvecEncoding.encodeLength(keysSize); -// byte[] keyIndices = new byte[keysSize]; -// for (int i = 0; i < instruction.getKeys().size(); i++) { -// keyIndices[i] = (byte) findAccountIndex(keysList, instruction.getKeys().get(i).getPublicKey()); -// } -// compiledInstruction.keyIndices = keyIndices; -// compiledInstruction.dataLength = ShortvecEncoding.encodeLength(instruction.getData().length); -// compiledInstruction.data = instruction.getData(); -// instructions.add(compiledInstruction); - return this; } @@ -123,6 +104,24 @@ public byte[] serialize() { messageHeader = new MessageHeader(); List keysList = getAccountKeys(); + /** + * ################################################# + * ########## Here's the change. sort ############## + * ################################################# + * + */ + Collections.sort(keysList, new Comparator() { + @Override + public int compare(AccountMeta o1, AccountMeta o2) { + if(o2.isSigner()){ + return 1; + }else if(o1.isSigner()){ + return -1; + }else{ + return 0; + } + } + }); int accountKeysSize = keysList.size(); byte[] accountAddressesLength = ShortvecEncoding.encodeLength(accountKeysSize); @@ -239,18 +238,10 @@ public static Message deserialize(List serializedMessageList) { // Remove three bytes for header byte[] messageHeaderBytes = GuardedArrayUtils.guardedSplice(serializedMessageList, 0, MessageHeader.HEADER_LENGTH); - byte numRequiredSignatures = messageHeaderBytes[0]; - byte numReadonlySignedAccounts = messageHeaderBytes[1]; - byte numReadonlyUnsignedAccounts = messageHeaderBytes[2]; MessageHeader messageHeader = new MessageHeader(messageHeaderBytes); // Total static account keys int accountKeysSize = ShortvecEncoding.decodeLength(serializedMessageList); -// byte[] accountAddressesLength = ShortvecEncoding.encodeLength(accountKeysSize); -// GuardedArrayUtils.guardedSplice(serializedMessageList, 0, accountAddressesLength.length); - - - List accountKeys = new ArrayList<>(accountKeysSize); for (int i = 0; i < accountKeysSize; i++) { byte[] accountMetaPublicKeyByteArray = GuardedArrayUtils.guardedSplice(serializedMessageList, 0, @@ -258,6 +249,16 @@ public static Message deserialize(List serializedMessageList) { PublicKey publicKey = new PublicKey(accountMetaPublicKeyByteArray); accountKeys.add(new AccountMeta(publicKey, false, false)); } + + // setSigner VS setWritable + for (AccountMeta accountKey : accountKeys) { + PublicKey publicKey = accountKey.getPublicKey(); + boolean isSigner = isSigner(accountKeys, publicKey, messageHeader); + boolean isWriter = isWriter(accountKeys, publicKey, messageHeader); + accountKey.setSigner(isSigner); + accountKey.setWritable(isWriter); + } + AccountKeysList accountKeysList = new AccountKeysList(); accountKeysList.addAll(accountKeys); @@ -290,7 +291,39 @@ public static Message deserialize(List serializedMessageList) { } return new Message(messageHeader, recentBlockHash, accountKeysList, instructions); -// return new Message(); + } + + private static boolean isWriter(List accountKeys, PublicKey account, MessageHeader messageHeader){ + + int index = indexOf(accountKeys, account); + if(index == -1){ + return false; + } + boolean isSignerWriter= index < messageHeader.numRequiredSignatures - messageHeader.numReadonlySignedAccounts; + boolean isNonSigner = index >= messageHeader.numRequiredSignatures; + boolean isNonSignerReadonly = index >= (accountKeys.size() - messageHeader.numReadonlyUnsignedAccounts); + boolean isNonSignerWriter = isNonSigner && !isNonSignerReadonly; + return isSignerWriter || isNonSignerWriter; + } + + private static boolean isSigner(List accountKeys, PublicKey account, MessageHeader messageHeader) { + int index = indexOf(accountKeys, account); + + if (index == -1) { + return false; + } + + return index < messageHeader.numRequiredSignatures; + } + + private static int indexOf(List accountKeys, PublicKey account){ + for (int i = 0; i < accountKeys.size(); i++) { + if(account.toBase58().equals(accountKeys.get(i).getPublicKey().toBase58())){ + return i; + } + } + + return -1; } } diff --git a/src/test/java/org/p2p/solanaj/core/MessageTest.java b/src/test/java/org/p2p/solanaj/core/MessageTest.java index d3b37094..17af814b 100644 --- a/src/test/java/org/p2p/solanaj/core/MessageTest.java +++ b/src/test/java/org/p2p/solanaj/core/MessageTest.java @@ -15,8 +15,6 @@ public class MessageTest { Account signer = new Account(Base58 .decode("4Z7cXSyeFR8wNGMVXUE1TwtKn5D5Vu7FzEv69dokLv7KrQk7h6pu4LF8ZRR9yQBhc7uSM6RTTZtU1fmaxiNrxXrs")); - - private TransactionInstruction transfer(){ PublicKey fromPublicKey = new PublicKey("QqCCvshxtqMAL2CVALqiJB7uEeE5mjSPsseQdDzsRUo"); PublicKey toPublickKey = new PublicKey("GrDMoeqMLFjeXQ24H56S1RLgT4R76jsuWCd6SvXyGPQ5"); @@ -25,7 +23,6 @@ private TransactionInstruction transfer(){ return SystemProgram.transfer(fromPublicKey, toPublickKey, lamports); } - @Test public void serializeMessage() { Message message = new Message(); @@ -54,26 +51,21 @@ public void deserialize(){ List bytes = ByteUtils.toByteList(intToByteArray(serialize)); Message message = Message.deserialize(bytes); assertEquals("Eit7RCyhUixAe2hGBS8oqnw59QK3kgMMjfLME5bm9wRn", message.getRecentBlockhash()); - assertArrayEquals(new int[]{1, 0, 1}, toUnsignedByteArray(message.getMessageHeader().toByteArray())); +// assertArrayEquals(new int[]{1, 0, 1}, toUnsignedByteArray(message.getMessageHeader().toByteArray())); assertEquals(1, message.getInstructions().size()); TransactionInstruction transfer = transfer(); -// TransactionInstruction transferNew = message.getInstructions().get(0); -// assertEquals(transfer.getProgramId(), transferNew.getProgramId()); -// assertArrayEquals(transfer.getData(), transferNew.getData()); - - - -// assertEquals(transfer(), message.getInstructions().get(0)); - - + TransactionInstruction transferNew = message.getInstructions().get(0); + assertEquals(transfer.getProgramId(), transferNew.getProgramId()); + assertArrayEquals(transfer.getData(), transferNew.getData()); + for (int i = 0; i < transfer.getKeys().size(); i++) { + assertEquals(transfer.getKeys().get(i).getPublicKey().toBase58(), transferNew.getKeys().get(i).getPublicKey().toBase58()); + } int[] serializeNew = toUnsignedByteArray(message.serialize()); System.out.println(Arrays.toString(serializeNew)); - assertArrayEquals(serialize, serializeNew); - } From 0458ffa8a70644666e6d9df3d37ba193c49da996 Mon Sep 17 00:00:00 2001 From: jc0803kevin Date: Thu, 2 Jan 2025 15:57:14 +0800 Subject: [PATCH 3/3] feat: transaction deserialize --- .../java/org/p2p/solanaj/core/Message.java | 20 ++++-- .../org/p2p/solanaj/core/Transaction.java | 68 +++---------------- ...GuardedArrayUtils.java => ArrayUtils.java} | 9 ++- .../java/org/p2p/solanaj/utils/ByteUtils.java | 8 +++ .../p2p/solanaj/utils/ShortvecEncoding.java | 23 ------- .../org/p2p/solanaj/core/MessageTest.java | 14 +--- .../org/p2p/solanaj/core/TransactionTest.java | 10 +-- .../org/p2p/solanaj/utils/ArrayUtilsTest.java | 33 +++++++++ .../solanaj/utils/GuardedArrayUtilsTest.java | 15 ---- 9 files changed, 80 insertions(+), 120 deletions(-) rename src/main/java/org/p2p/solanaj/utils/{GuardedArrayUtils.java => ArrayUtils.java} (87%) create mode 100644 src/test/java/org/p2p/solanaj/utils/ArrayUtilsTest.java delete mode 100644 src/test/java/org/p2p/solanaj/utils/GuardedArrayUtilsTest.java diff --git a/src/main/java/org/p2p/solanaj/core/Message.java b/src/main/java/org/p2p/solanaj/core/Message.java index eaeb35a8..71904be2 100644 --- a/src/main/java/org/p2p/solanaj/core/Message.java +++ b/src/main/java/org/p2p/solanaj/core/Message.java @@ -1,7 +1,7 @@ package org.p2p.solanaj.core; import org.bitcoinj.core.Base58; -import org.p2p.solanaj.utils.GuardedArrayUtils; +import org.p2p.solanaj.utils.ArrayUtils; import org.p2p.solanaj.utils.ShortvecEncoding; import java.nio.ByteBuffer; @@ -232,19 +232,25 @@ public int findAccountIndex(List accountMetaList, PublicKey key) { throw new RuntimeException("unable to find account index"); } + /** + * deserialize Message + * @param serializedMessageList message serialize byte array + * @return Message + * @author jc0803kevin + */ public static Message deserialize(List serializedMessageList) { // Remove the byte as it is used to indicate legacy Transaction. // GuardedArrayUtils.guardedShift(serializedMessageList); // Remove three bytes for header - byte[] messageHeaderBytes = GuardedArrayUtils.guardedSplice(serializedMessageList, 0, MessageHeader.HEADER_LENGTH); + byte[] messageHeaderBytes = ArrayUtils.guardedSplice(serializedMessageList, 0, MessageHeader.HEADER_LENGTH); MessageHeader messageHeader = new MessageHeader(messageHeaderBytes); // Total static account keys int accountKeysSize = ShortvecEncoding.decodeLength(serializedMessageList); List accountKeys = new ArrayList<>(accountKeysSize); for (int i = 0; i < accountKeysSize; i++) { - byte[] accountMetaPublicKeyByteArray = GuardedArrayUtils.guardedSplice(serializedMessageList, 0, + byte[] accountMetaPublicKeyByteArray = ArrayUtils.guardedSplice(serializedMessageList, 0, PublicKey.PUBLIC_KEY_LENGTH); PublicKey publicKey = new PublicKey(accountMetaPublicKeyByteArray); accountKeys.add(new AccountMeta(publicKey, false, false)); @@ -263,7 +269,7 @@ public static Message deserialize(List serializedMessageList) { accountKeysList.addAll(accountKeys); // recent_blockhash - String recentBlockHash = Base58.encode(GuardedArrayUtils.guardedSplice(serializedMessageList, 0, + String recentBlockHash = Base58.encode(ArrayUtils.guardedSplice(serializedMessageList, 0, PublicKey.PUBLIC_KEY_LENGTH)); // Deserialize instructions @@ -272,13 +278,13 @@ public static Message deserialize(List serializedMessageList) { List compiledInstructions = new ArrayList<>(instructionsLength); for (int i = 0; i < instructionsLength; i++) { CompiledInstruction compiledInstruction = new CompiledInstruction(); - compiledInstruction.programIdIndex = GuardedArrayUtils.guardedShift(serializedMessageList); + compiledInstruction.programIdIndex = ArrayUtils.guardedShift(serializedMessageList); int keysSize = ShortvecEncoding.decodeLength(serializedMessageList); // keysSize compiledInstruction.keyIndicesCount = ShortvecEncoding.encodeLength(keysSize); - compiledInstruction.keyIndices = GuardedArrayUtils.guardedSplice(serializedMessageList, 0, keysSize); + compiledInstruction.keyIndices = ArrayUtils.guardedSplice(serializedMessageList, 0, keysSize); var dataLength = ShortvecEncoding.decodeLength(serializedMessageList); compiledInstruction.dataLength = ShortvecEncoding.encodeLength(dataLength); - compiledInstruction.data = GuardedArrayUtils.guardedSplice(serializedMessageList, 0, dataLength); + compiledInstruction.data = ArrayUtils.guardedSplice(serializedMessageList, 0, dataLength); compiledInstructions.add(compiledInstruction); diff --git a/src/main/java/org/p2p/solanaj/core/Transaction.java b/src/main/java/org/p2p/solanaj/core/Transaction.java index f1cd01bd..55ff6de8 100644 --- a/src/main/java/org/p2p/solanaj/core/Transaction.java +++ b/src/main/java/org/p2p/solanaj/core/Transaction.java @@ -2,13 +2,12 @@ import java.nio.ByteBuffer; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; import java.util.Objects; import org.bitcoinj.core.Base58; import org.p2p.solanaj.utils.ByteUtils; -import org.p2p.solanaj.utils.GuardedArrayUtils; +import org.p2p.solanaj.utils.ArrayUtils; import org.p2p.solanaj.utils.ShortvecEncoding; import org.p2p.solanaj.utils.TweetNaclFast; @@ -35,6 +34,7 @@ public Transaction() { public Transaction(Message message, List signatures) { this.message = message; this.signatures = signatures; + this.serializedMessage = message.serialize(); } /** @@ -130,54 +130,12 @@ public byte[] serialize() { return out.array(); } - -// public static Transaction deserialize(byte[] serializeTx){ -// int signaturesSize = ShortvecEncoding.decodeLength(serializeTx); -// List signatures = new ArrayList<>(signaturesSize); -// -// List serializedTransactionList = ByteUtils.toByteList(serializeTx); -// -// for (int i = 0; i < signaturesSize; i++) { -// -// byte[] signatureBytes = GuardedArrayUtils.guardedSplice(serializedTransactionList, 0, SIGNATURE_LENGTH); -// signatures.add(Base58.encode(signatureBytes)); -// } -// -//// Message message = Message.deserialize(serializedTransactionList); -// return new Transaction(null, signatures); -// } - - -// public static Transaction deserialize(byte[] serializedTransaction) { -// List serializedTransactionList = ByteUtils.toByteList(serializedTransaction); -// -// int signaturesSize = ShortvecEncoding.decodeLength(serializedTransaction); -// List signatures = new ArrayList<>(signaturesSize); -// -// byte[] byteArray = serializedTransaction; -// -// for (int i = 0; i < signaturesSize; i++) { -// -// byte[] signatureBytes = GuardedArrayUtils.guardedSplice(serializedTransactionList, 0, SIGNATURE_LENGTH); -// signatures.add(Base58.encode(signatureBytes)); -// -//// System.out.println("byteArray--1->" + Arrays.toString(byteArray)); -//// byte[] signature = slice(byteArray, 0, Transaction.SIGNATURE_LENGTH); -//// System.out.println("signature-- ->" + Arrays.toString(signature)); -//// signatures.add(Base58.encode(signature)); -//// -//// System.out.println("byteArray--2->" + Arrays.toString(byteArray)); -//// byteArray = Arrays.copyOfRange(byteArray, Transaction.SIGNATURE_LENGTH, byteArray.length); -//// -//// System.out.println("byteArray--3->" + Arrays.toString(byteArray)); -//// signatures.add(Base58.encode(signature)); -// } -// -// // Message message = Message.deserialize(serializedTransactionList); -// // todo kevin -// return new Transaction(null, signatures); -// } - + /** + * deserialize Transaction + * @param serializedTransaction transaction serialize byte array + * @return + * @author jc080kevin + */ public static Transaction deserialize(byte[] serializedTransaction) { List serializedTransactionList = ByteUtils.toByteList(serializedTransaction); @@ -186,17 +144,11 @@ public static Transaction deserialize(byte[] serializedTransaction) { for (int i = 0; i < signaturesSize; i++) { - byte[] signatureBytes = GuardedArrayUtils.guardedSplice(serializedTransactionList, 0, SIGNATURE_LENGTH); + byte[] signatureBytes = ArrayUtils.guardedSplice(serializedTransactionList, 0, SIGNATURE_LENGTH); signatures.add(Base58.encode(signatureBytes)); } - Message message = Message.deserialize(serializedTransactionList); + Message message = Message.deserialize(serializedTransactionList); return new Transaction(message, signatures); } - - public static byte[] slice(byte[] data, int start, int length) { - byte[] slice = new byte[length]; - System.arraycopy(data, start, slice, 0, length); - return slice; - } } diff --git a/src/main/java/org/p2p/solanaj/utils/GuardedArrayUtils.java b/src/main/java/org/p2p/solanaj/utils/ArrayUtils.java similarity index 87% rename from src/main/java/org/p2p/solanaj/utils/GuardedArrayUtils.java rename to src/main/java/org/p2p/solanaj/utils/ArrayUtils.java index c2fe99b7..8544550d 100644 --- a/src/main/java/org/p2p/solanaj/utils/GuardedArrayUtils.java +++ b/src/main/java/org/p2p/solanaj/utils/ArrayUtils.java @@ -4,7 +4,7 @@ import java.util.Collections; import java.util.List; -public class GuardedArrayUtils { +public class ArrayUtils { public static Byte guardedShift(List byteArray) { if (byteArray.isEmpty()) { @@ -13,6 +13,13 @@ public static Byte guardedShift(List byteArray) { return byteArray.remove(0); } + /** + * @param byteList array source + * @param start array start index + * @param deleteCount fetch count + * @param items + * @return + */ public static byte[] guardedSplice( List byteList, Integer start, diff --git a/src/main/java/org/p2p/solanaj/utils/ByteUtils.java b/src/main/java/org/p2p/solanaj/utils/ByteUtils.java index 9c6a3cb0..9c6426ac 100644 --- a/src/main/java/org/p2p/solanaj/utils/ByteUtils.java +++ b/src/main/java/org/p2p/solanaj/utils/ByteUtils.java @@ -91,5 +91,13 @@ public static byte[] emptyByteArray() { return new byte[]{}; } + public static byte[] intToByteArray(int[] in) { + byte[] out = new byte[in.length]; + for (int i = 0; i < in.length; i++) { + out[i] = (byte) (in[i] & 0xff); + } + return out; + } + } diff --git a/src/main/java/org/p2p/solanaj/utils/ShortvecEncoding.java b/src/main/java/org/p2p/solanaj/utils/ShortvecEncoding.java index 77e967e3..5e7d1d36 100644 --- a/src/main/java/org/p2p/solanaj/utils/ShortvecEncoding.java +++ b/src/main/java/org/p2p/solanaj/utils/ShortvecEncoding.java @@ -1,6 +1,5 @@ package org.p2p.solanaj.utils; -import java.util.Arrays; import java.util.List; import static org.bitcoinj.core.Utils.uint16ToByteArrayLE; @@ -31,28 +30,6 @@ public static byte[] encodeLength(int len) { return bytes; } -// public static int decodeLength(byte[] bytes){ -// int len = 0; -// int size = 0; -// -// byte[] out = bytes; -// -// for (;;){ -// //从数组中删除第一个元素,并返回该元素的值。此方法更改数组的长度。 -// int elem = Byte.toUnsignedInt(out[0]) ; -// out = Arrays.copyOfRange(out, 1, out.length); -// -// len |= (elem & 0x7f) << (size * 7); -// size += 1; -// if((elem & 0x80) == 0){ -// break; -// } -// -// } -// return len; -// } - - public static int decodeLength(List dataBytesList) { int len = 0; int size = 0; diff --git a/src/test/java/org/p2p/solanaj/core/MessageTest.java b/src/test/java/org/p2p/solanaj/core/MessageTest.java index 17af814b..0b35a568 100644 --- a/src/test/java/org/p2p/solanaj/core/MessageTest.java +++ b/src/test/java/org/p2p/solanaj/core/MessageTest.java @@ -48,7 +48,7 @@ public void deserialize(){ 0, 0, 0, 0, 0, 0, 203, 226, 136, 193, 153, 148, 240, 50, 230, 98, 9, 79, 221, 179, 243, 174, 90, 67, 104, 169, 6, 187, 165, 72, 36, 156, 19, 57, 132, 38, 69, 245, 1, 2, 2, 0, 1, 12, 2, 0, 0, 0, 184, 11, 0, 0, 0, 0, 0, 0 }; - List bytes = ByteUtils.toByteList(intToByteArray(serialize)); + List bytes = ByteUtils.toByteList(ByteUtils.intToByteArray(serialize)); Message message = Message.deserialize(bytes); assertEquals("Eit7RCyhUixAe2hGBS8oqnw59QK3kgMMjfLME5bm9wRn", message.getRecentBlockhash()); // assertArrayEquals(new int[]{1, 0, 1}, toUnsignedByteArray(message.getMessageHeader().toByteArray())); @@ -79,11 +79,11 @@ public void int_byte_convert(){ 104, 169, 6, 187, 165, 72, 36, 156, 19, 57, 132, 38, 69, 245, 1, 2, 2, 0, 1, 12, 2, 0, 0, 0, 184, 11, 0, 0, 0, 0, 0, 0 }; - byte[] bytes = intToByteArray(serialize); + byte[] bytes = ByteUtils.intToByteArray(serialize); int[] serializeNew = toUnsignedByteArray(bytes); - byte[] bytesNew = intToByteArray(serializeNew); + byte[] bytesNew = ByteUtils.intToByteArray(serializeNew); assertArrayEquals(serialize, serializeNew); assertArrayEquals(bytes, bytesNew); @@ -99,12 +99,4 @@ int[] toUnsignedByteArray(byte[] in) { return out; } - public static byte[] intToByteArray(int[] in) { - byte[] out = new byte[in.length]; - for (int i = 0; i < in.length; i++) { - out[i] = (byte) (in[i] & 0xff); - } - return out; - } - } diff --git a/src/test/java/org/p2p/solanaj/core/TransactionTest.java b/src/test/java/org/p2p/solanaj/core/TransactionTest.java index 9d2a0b50..e2253050 100644 --- a/src/test/java/org/p2p/solanaj/core/TransactionTest.java +++ b/src/test/java/org/p2p/solanaj/core/TransactionTest.java @@ -1,16 +1,14 @@ package org.p2p.solanaj.core; +import org.bitcoinj.core.Base58; +import org.junit.jupiter.api.Test; import org.p2p.solanaj.programs.MemoProgram; import org.p2p.solanaj.programs.SystemProgram; -import org.junit.jupiter.api.Test; -import static org.junit.jupiter.api.Assertions.*; -import static org.junit.jupiter.api.Assertions.assertEquals; - import java.util.Base64; import java.util.List; -import org.bitcoinj.core.Base58; +import static org.junit.jupiter.api.Assertions.assertEquals; public class TransactionTest { @@ -45,7 +43,9 @@ public void deserialize() { assertEquals("nXkZvmiP3kzZbR7u95NSoK78Y3YqgSSthseuba99uBGsEBnR4RXugEhrAFmqhvWiN8k9aZNTZTE22NH6nBX3B7T", transaction.getTxHash()); + byte[] serializedNew = transaction.serialize(); + assertEquals(Base64.getEncoder().encodeToString(serializedNew), serializedTxBase64); } @Test diff --git a/src/test/java/org/p2p/solanaj/utils/ArrayUtilsTest.java b/src/test/java/org/p2p/solanaj/utils/ArrayUtilsTest.java new file mode 100644 index 00000000..dbb1f8b2 --- /dev/null +++ b/src/test/java/org/p2p/solanaj/utils/ArrayUtilsTest.java @@ -0,0 +1,33 @@ +package org.p2p.solanaj.utils; + +import org.junit.jupiter.api.Test; + +import java.util.Arrays; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class ArrayUtilsTest { + + @Test + public void guardedSplice(){ + int[] serialize = new int[] { 1, 0, 1, 3, 6, 26, 217, 208, 83, 135, 21, 72, 83, 126, 222, 62, 38, 24, 73, 163, + 223, 183, 253, 2, 250, 188, 117, 178, 35, 200, 228, 106, 219, 133, 61, 12, 235, 122, 188, 208, 216, 117, + 235, 194, 109, 161, 177, 129, 163, 51, 155, 62, 242, 163, 22, 149, 187, 122, 189, 188, 103, 130, 115, + 188, 173, 205, 229, 170, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 203, 226, 136, 193, 153, 148, 240, 50, 230, 98, 9, 79, 221, 179, 243, 174, 90, 67, + 104, 169, 6, 187, 165, 72, 36, 156, 19, 57, 132, 38, 69, 245, 1, 2, 2, 0, 1, 12, 2, 0, 0, 0, 184, 11, 0, + 0, 0, 0, 0, 0 }; + List bytes = ByteUtils.toByteList(ByteUtils.intToByteArray(serialize)); + + byte[] splice = ArrayUtils.guardedSplice(bytes, 1, 5); + System.out.println(Arrays.toString(splice)); + + assertEquals(bytes.size(), serialize.length - 5); + assertArrayEquals(splice, new byte[]{0, 1, 3, 6, 26}); + + } + + +} diff --git a/src/test/java/org/p2p/solanaj/utils/GuardedArrayUtilsTest.java b/src/test/java/org/p2p/solanaj/utils/GuardedArrayUtilsTest.java deleted file mode 100644 index 50cb872f..00000000 --- a/src/test/java/org/p2p/solanaj/utils/GuardedArrayUtilsTest.java +++ /dev/null @@ -1,15 +0,0 @@ -package org.p2p.solanaj.utils; - -import org.junit.jupiter.api.Test; - -public class GuardedArrayUtilsTest { - - @Test - public void a(){ - - -// GuardedArrayUtils.guardedSplice(); - } - - -}