mirror of https://github.com/qortal/qortal
CalDescent
2 years ago
64 changed files with 1957 additions and 110 deletions
@ -0,0 +1,189 @@
|
||||
package org.qortal.controller; |
||||
|
||||
import org.apache.logging.log4j.LogManager; |
||||
import org.apache.logging.log4j.Logger; |
||||
import org.qortal.data.account.AccountBalanceData; |
||||
import org.qortal.data.account.AccountData; |
||||
import org.qortal.data.naming.NameData; |
||||
import org.qortal.data.transaction.TransactionData; |
||||
import org.qortal.network.Network; |
||||
import org.qortal.network.Peer; |
||||
import org.qortal.network.message.*; |
||||
|
||||
import java.security.SecureRandom; |
||||
import java.util.*; |
||||
|
||||
import static org.qortal.network.message.MessageType.*; |
||||
|
||||
public class LiteNode { |
||||
|
||||
private static final Logger LOGGER = LogManager.getLogger(LiteNode.class); |
||||
|
||||
private static LiteNode instance; |
||||
|
||||
|
||||
public Map<Integer, Long> pendingRequests = Collections.synchronizedMap(new HashMap<>()); |
||||
|
||||
public int MAX_TRANSACTIONS_PER_MESSAGE = 100; |
||||
|
||||
|
||||
public LiteNode() { |
||||
|
||||
} |
||||
|
||||
public static synchronized LiteNode getInstance() { |
||||
if (instance == null) { |
||||
instance = new LiteNode(); |
||||
} |
||||
|
||||
return instance; |
||||
} |
||||
|
||||
|
||||
/** |
||||
* Fetch account data from peers for given QORT address |
||||
* @param address - the QORT address to query |
||||
* @return accountData - the account data for this address, or null if not retrieved |
||||
*/ |
||||
public AccountData fetchAccountData(String address) { |
||||
GetAccountMessage getAccountMessage = new GetAccountMessage(address); |
||||
AccountMessage accountMessage = (AccountMessage) this.sendMessage(getAccountMessage, ACCOUNT); |
||||
if (accountMessage == null) { |
||||
return null; |
||||
} |
||||
return accountMessage.getAccountData(); |
||||
} |
||||
|
||||
/** |
||||
* Fetch account balance data from peers for given QORT address and asset ID |
||||
* @param address - the QORT address to query |
||||
* @return balance - the balance for this address and assetId, or null if not retrieved |
||||
*/ |
||||
public AccountBalanceData fetchAccountBalance(String address, long assetId) { |
||||
GetAccountBalanceMessage getAccountMessage = new GetAccountBalanceMessage(address, assetId); |
||||
AccountBalanceMessage accountMessage = (AccountBalanceMessage) this.sendMessage(getAccountMessage, ACCOUNT_BALANCE); |
||||
if (accountMessage == null) { |
||||
return null; |
||||
} |
||||
return accountMessage.getAccountBalanceData(); |
||||
} |
||||
|
||||
/** |
||||
* Fetch list of transactions for given QORT address |
||||
* @param address - the QORT address to query |
||||
* @param limit - the maximum number of results to return |
||||
* @param offset - the starting index |
||||
* @return a list of TransactionData objects, or null if not retrieved |
||||
*/ |
||||
public List<TransactionData> fetchAccountTransactions(String address, int limit, int offset) { |
||||
List<TransactionData> allTransactions = new ArrayList<>(); |
||||
if (limit == 0) { |
||||
limit = Integer.MAX_VALUE; |
||||
} |
||||
int batchSize = Math.min(limit, MAX_TRANSACTIONS_PER_MESSAGE); |
||||
|
||||
while (allTransactions.size() < limit) { |
||||
GetAccountTransactionsMessage getAccountTransactionsMessage = new GetAccountTransactionsMessage(address, batchSize, offset); |
||||
TransactionsMessage transactionsMessage = (TransactionsMessage) this.sendMessage(getAccountTransactionsMessage, TRANSACTIONS); |
||||
if (transactionsMessage == null) { |
||||
// An error occurred, so give up instead of returning partial results
|
||||
return null; |
||||
} |
||||
allTransactions.addAll(transactionsMessage.getTransactions()); |
||||
if (transactionsMessage.getTransactions().size() < batchSize) { |
||||
// No more transactions to fetch
|
||||
break; |
||||
} |
||||
offset += batchSize; |
||||
} |
||||
return allTransactions; |
||||
} |
||||
|
||||
/** |
||||
* Fetch list of names for given QORT address |
||||
* @param address - the QORT address to query |
||||
* @return a list of NameData objects, or null if not retrieved |
||||
*/ |
||||
public List<NameData> fetchAccountNames(String address) { |
||||
GetAccountNamesMessage getAccountNamesMessage = new GetAccountNamesMessage(address); |
||||
NamesMessage namesMessage = (NamesMessage) this.sendMessage(getAccountNamesMessage, NAMES); |
||||
if (namesMessage == null) { |
||||
return null; |
||||
} |
||||
return namesMessage.getNameDataList(); |
||||
} |
||||
|
||||
/** |
||||
* Fetch info about a registered name |
||||
* @param name - the name to query |
||||
* @return a NameData object, or null if not retrieved |
||||
*/ |
||||
public NameData fetchNameData(String name) { |
||||
GetNameMessage getNameMessage = new GetNameMessage(name); |
||||
NamesMessage namesMessage = (NamesMessage) this.sendMessage(getNameMessage, NAMES); |
||||
if (namesMessage == null) { |
||||
return null; |
||||
} |
||||
List<NameData> nameDataList = namesMessage.getNameDataList(); |
||||
if (nameDataList == null || nameDataList.size() != 1) { |
||||
return null; |
||||
} |
||||
// We are only expecting a single item in the list
|
||||
return nameDataList.get(0); |
||||
} |
||||
|
||||
|
||||
private Message sendMessage(Message message, MessageType expectedResponseMessageType) { |
||||
// This asks a random peer for the data
|
||||
// TODO: ask multiple peers, and disregard everything if there are any significant differences in the responses
|
||||
|
||||
// Needs a mutable copy of the unmodifiableList
|
||||
List<Peer> peers = new ArrayList<>(Network.getInstance().getImmutableHandshakedPeers()); |
||||
|
||||
// Disregard peers that have "misbehaved" recently
|
||||
peers.removeIf(Controller.hasMisbehaved); |
||||
|
||||
// Disregard peers that only have genesis block
|
||||
// TODO: peers.removeIf(Controller.hasOnlyGenesisBlock);
|
||||
|
||||
// Disregard peers that are on an old version
|
||||
peers.removeIf(Controller.hasOldVersion); |
||||
|
||||
// Disregard peers that are on a known inferior chain tip
|
||||
// TODO: peers.removeIf(Controller.hasInferiorChainTip);
|
||||
|
||||
if (peers.isEmpty()) { |
||||
LOGGER.info("No peers available to send {} message to", message.getType()); |
||||
return null; |
||||
} |
||||
|
||||
// Pick random peer
|
||||
int index = new SecureRandom().nextInt(peers.size()); |
||||
Peer peer = peers.get(index); |
||||
|
||||
LOGGER.info("Sending {} message to peer {}...", message.getType(), peer); |
||||
|
||||
Message responseMessage; |
||||
|
||||
try { |
||||
responseMessage = peer.getResponse(message); |
||||
|
||||
} catch (InterruptedException e) { |
||||
return null; |
||||
} |
||||
|
||||
if (responseMessage == null) { |
||||
LOGGER.info("Peer didn't respond to {} message", peer, message.getType()); |
||||
return null; |
||||
} |
||||
else if (responseMessage.getType() != expectedResponseMessageType) { |
||||
LOGGER.info("Peer responded with unexpected message type {} (should be {})", peer, responseMessage.getType(), expectedResponseMessageType); |
||||
return null; |
||||
} |
||||
|
||||
LOGGER.info("Peer {} responded with {} message", peer, responseMessage.getType()); |
||||
|
||||
return responseMessage; |
||||
} |
||||
|
||||
} |
@ -0,0 +1,70 @@
|
||||
package org.qortal.network.message; |
||||
|
||||
import com.google.common.primitives.Longs; |
||||
import org.qortal.data.account.AccountBalanceData; |
||||
import org.qortal.transform.Transformer; |
||||
import org.qortal.utils.Base58; |
||||
|
||||
import java.io.ByteArrayOutputStream; |
||||
import java.io.IOException; |
||||
import java.nio.ByteBuffer; |
||||
|
||||
public class AccountBalanceMessage extends Message { |
||||
|
||||
private static final int ADDRESS_LENGTH = Transformer.ADDRESS_LENGTH; |
||||
|
||||
private AccountBalanceData accountBalanceData; |
||||
|
||||
public AccountBalanceMessage(AccountBalanceData accountBalanceData) { |
||||
super(MessageType.ACCOUNT_BALANCE); |
||||
|
||||
ByteArrayOutputStream bytes = new ByteArrayOutputStream(); |
||||
|
||||
try { |
||||
// Send raw address instead of base58 encoded
|
||||
byte[] address = Base58.decode(accountBalanceData.getAddress()); |
||||
bytes.write(address); |
||||
|
||||
bytes.write(Longs.toByteArray(accountBalanceData.getAssetId())); |
||||
|
||||
bytes.write(Longs.toByteArray(accountBalanceData.getBalance())); |
||||
|
||||
} catch (IOException e) { |
||||
throw new AssertionError("IOException shouldn't occur with ByteArrayOutputStream"); |
||||
} |
||||
|
||||
this.dataBytes = bytes.toByteArray(); |
||||
this.checksumBytes = Message.generateChecksum(this.dataBytes); |
||||
} |
||||
|
||||
public AccountBalanceMessage(int id, AccountBalanceData accountBalanceData) { |
||||
super(id, MessageType.ACCOUNT_BALANCE); |
||||
|
||||
this.accountBalanceData = accountBalanceData; |
||||
} |
||||
|
||||
public AccountBalanceData getAccountBalanceData() { |
||||
return this.accountBalanceData; |
||||
} |
||||
|
||||
|
||||
public static Message fromByteBuffer(int id, ByteBuffer byteBuffer) { |
||||
byte[] addressBytes = new byte[ADDRESS_LENGTH]; |
||||
byteBuffer.get(addressBytes); |
||||
String address = Base58.encode(addressBytes); |
||||
|
||||
long assetId = byteBuffer.getLong(); |
||||
|
||||
long balance = byteBuffer.getLong(); |
||||
|
||||
AccountBalanceData accountBalanceData = new AccountBalanceData(address, assetId, balance); |
||||
return new AccountBalanceMessage(id, accountBalanceData); |
||||
} |
||||
|
||||
public AccountBalanceMessage cloneWithNewId(int newId) { |
||||
AccountBalanceMessage clone = new AccountBalanceMessage(this.accountBalanceData); |
||||
clone.setId(newId); |
||||
return clone; |
||||
} |
||||
|
||||
} |
@ -0,0 +1,93 @@
|
||||
package org.qortal.network.message; |
||||
|
||||
import com.google.common.primitives.Ints; |
||||
import org.qortal.data.account.AccountData; |
||||
import org.qortal.transform.Transformer; |
||||
import org.qortal.utils.Base58; |
||||
|
||||
import java.io.ByteArrayOutputStream; |
||||
import java.io.IOException; |
||||
import java.nio.ByteBuffer; |
||||
|
||||
public class AccountMessage extends Message { |
||||
|
||||
private static final int ADDRESS_LENGTH = Transformer.ADDRESS_LENGTH; |
||||
private static final int REFERENCE_LENGTH = Transformer.SIGNATURE_LENGTH; |
||||
private static final int PUBLIC_KEY_LENGTH = Transformer.PUBLIC_KEY_LENGTH; |
||||
|
||||
private AccountData accountData; |
||||
|
||||
public AccountMessage(AccountData accountData) { |
||||
super(MessageType.ACCOUNT); |
||||
|
||||
ByteArrayOutputStream bytes = new ByteArrayOutputStream(); |
||||
|
||||
try { |
||||
// Send raw address instead of base58 encoded
|
||||
byte[] address = Base58.decode(accountData.getAddress()); |
||||
bytes.write(address); |
||||
|
||||
bytes.write(accountData.getReference()); |
||||
|
||||
bytes.write(accountData.getPublicKey()); |
||||
|
||||
bytes.write(Ints.toByteArray(accountData.getDefaultGroupId())); |
||||
|
||||
bytes.write(Ints.toByteArray(accountData.getFlags())); |
||||
|
||||
bytes.write(Ints.toByteArray(accountData.getLevel())); |
||||
|
||||
bytes.write(Ints.toByteArray(accountData.getBlocksMinted())); |
||||
|
||||
bytes.write(Ints.toByteArray(accountData.getBlocksMintedAdjustment())); |
||||
|
||||
} catch (IOException e) { |
||||
throw new AssertionError("IOException shouldn't occur with ByteArrayOutputStream"); |
||||
} |
||||
|
||||
this.dataBytes = bytes.toByteArray(); |
||||
this.checksumBytes = Message.generateChecksum(this.dataBytes); |
||||
} |
||||
|
||||
public AccountMessage(int id, AccountData accountData) { |
||||
super(id, MessageType.ACCOUNT); |
||||
|
||||
this.accountData = accountData; |
||||
} |
||||
|
||||
public AccountData getAccountData() { |
||||
return this.accountData; |
||||
} |
||||
|
||||
public static Message fromByteBuffer(int id, ByteBuffer byteBuffer) { |
||||
byte[] addressBytes = new byte[ADDRESS_LENGTH]; |
||||
byteBuffer.get(addressBytes); |
||||
String address = Base58.encode(addressBytes); |
||||
|
||||
byte[] reference = new byte[REFERENCE_LENGTH]; |
||||
byteBuffer.get(reference); |
||||
|
||||
byte[] publicKey = new byte[PUBLIC_KEY_LENGTH]; |
||||
byteBuffer.get(publicKey); |
||||
|
||||
int defaultGroupId = byteBuffer.getInt(); |
||||
|
||||
int flags = byteBuffer.getInt(); |
||||
|
||||
int level = byteBuffer.getInt(); |
||||
|
||||
int blocksMinted = byteBuffer.getInt(); |
||||
|
||||
int blocksMintedAdjustment = byteBuffer.getInt(); |
||||
|
||||
AccountData accountData = new AccountData(address, reference, publicKey, defaultGroupId, flags, level, blocksMinted, blocksMintedAdjustment); |
||||
return new AccountMessage(id, accountData); |
||||
} |
||||
|
||||
public AccountMessage cloneWithNewId(int newId) { |
||||
AccountMessage clone = new AccountMessage(this.accountData); |
||||
clone.setId(newId); |
||||
return clone; |
||||
} |
||||
|
||||
} |
@ -0,0 +1,63 @@
|
||||
package org.qortal.network.message; |
||||
|
||||
import com.google.common.primitives.Longs; |
||||
import org.qortal.transform.Transformer; |
||||
import org.qortal.utils.Base58; |
||||
|
||||
import java.io.ByteArrayOutputStream; |
||||
import java.io.IOException; |
||||
import java.nio.ByteBuffer; |
||||
|
||||
public class GetAccountBalanceMessage extends Message { |
||||
|
||||
private static final int ADDRESS_LENGTH = Transformer.ADDRESS_LENGTH; |
||||
|
||||
private String address; |
||||
private long assetId; |
||||
|
||||
public GetAccountBalanceMessage(String address, long assetId) { |
||||
super(MessageType.GET_ACCOUNT_BALANCE); |
||||
|
||||
ByteArrayOutputStream bytes = new ByteArrayOutputStream(); |
||||
|
||||
try { |
||||
// Send raw address instead of base58 encoded
|
||||
byte[] addressBytes = Base58.decode(address); |
||||
bytes.write(addressBytes); |
||||
|
||||
bytes.write(Longs.toByteArray(assetId)); |
||||
|
||||
} catch (IOException e) { |
||||
throw new AssertionError("IOException shouldn't occur with ByteArrayOutputStream"); |
||||
} |
||||
|
||||
this.dataBytes = bytes.toByteArray(); |
||||
this.checksumBytes = Message.generateChecksum(this.dataBytes); |
||||
} |
||||
|
||||
private GetAccountBalanceMessage(int id, String address, long assetId) { |
||||
super(id, MessageType.GET_ACCOUNT_BALANCE); |
||||
|
||||
this.address = address; |
||||
this.assetId = assetId; |
||||
} |
||||
|
||||
public String getAddress() { |
||||
return this.address; |
||||
} |
||||
|
||||
public long getAssetId() { |
||||
return this.assetId; |
||||
} |
||||
|
||||
public static Message fromByteBuffer(int id, ByteBuffer bytes) { |
||||
byte[] addressBytes = new byte[ADDRESS_LENGTH]; |
||||
bytes.get(addressBytes); |
||||
String address = Base58.encode(addressBytes); |
||||
|
||||
long assetId = bytes.getLong(); |
||||
|
||||
return new GetAccountBalanceMessage(id, address, assetId); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,56 @@
|
||||
package org.qortal.network.message; |
||||
|
||||
import org.qortal.transform.Transformer; |
||||
import org.qortal.utils.Base58; |
||||
|
||||
import java.io.ByteArrayOutputStream; |
||||
import java.io.IOException; |
||||
import java.nio.BufferUnderflowException; |
||||
import java.nio.ByteBuffer; |
||||
|
||||
public class GetAccountMessage extends Message { |
||||
|
||||
private static final int ADDRESS_LENGTH = Transformer.ADDRESS_LENGTH; |
||||
|
||||
private String address; |
||||
|
||||
public GetAccountMessage(String address) { |
||||
super(MessageType.GET_ACCOUNT); |
||||
|
||||
ByteArrayOutputStream bytes = new ByteArrayOutputStream(); |
||||
|
||||
try { |
||||
// Send raw address instead of base58 encoded
|
||||
byte[] addressBytes = Base58.decode(address); |
||||
bytes.write(addressBytes); |
||||
|
||||
} catch (IOException e) { |
||||
throw new AssertionError("IOException shouldn't occur with ByteArrayOutputStream"); |
||||
} |
||||
|
||||
this.dataBytes = bytes.toByteArray(); |
||||
this.checksumBytes = Message.generateChecksum(this.dataBytes); |
||||
} |
||||
|
||||
private GetAccountMessage(int id, String address) { |
||||
super(id, MessageType.GET_ACCOUNT); |
||||
|
||||
this.address = address; |
||||
} |
||||
|
||||
public String getAddress() { |
||||
return this.address; |
||||
} |
||||
|
||||
public static Message fromByteBuffer(int id, ByteBuffer bytes) { |
||||
if (bytes.remaining() != ADDRESS_LENGTH) |
||||
throw new BufferUnderflowException(); |
||||
|
||||
byte[] addressBytes = new byte[ADDRESS_LENGTH]; |
||||
bytes.get(addressBytes); |
||||
String address = Base58.encode(addressBytes); |
||||
|
||||
return new GetAccountMessage(id, address); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,53 @@
|
||||
package org.qortal.network.message; |
||||
|
||||
import org.qortal.transform.Transformer; |
||||
import org.qortal.utils.Base58; |
||||
|
||||
import java.io.ByteArrayOutputStream; |
||||
import java.io.IOException; |
||||
import java.nio.ByteBuffer; |
||||
|
||||
public class GetAccountNamesMessage extends Message { |
||||
|
||||
private static final int ADDRESS_LENGTH = Transformer.ADDRESS_LENGTH; |
||||
|
||||
private String address; |
||||
|
||||
public GetAccountNamesMessage(String address) { |
||||
super(MessageType.GET_ACCOUNT_NAMES); |
||||
|
||||
ByteArrayOutputStream bytes = new ByteArrayOutputStream(); |
||||
|
||||
try { |
||||
// Send raw address instead of base58 encoded
|
||||
byte[] addressBytes = Base58.decode(address); |
||||
bytes.write(addressBytes); |
||||
|
||||
} catch (IOException e) { |
||||
throw new AssertionError("IOException shouldn't occur with ByteArrayOutputStream"); |
||||
} |
||||
|
||||
this.dataBytes = bytes.toByteArray(); |
||||
this.checksumBytes = Message.generateChecksum(this.dataBytes); |
||||
} |
||||
|
||||
private GetAccountNamesMessage(int id, String address) { |
||||
super(id, MessageType.GET_ACCOUNT_NAMES); |
||||
|
||||
this.address = address; |
||||
} |
||||
|
||||
public String getAddress() { |
||||
return this.address; |
||||
} |
||||
|
||||
|
||||
public static Message fromByteBuffer(int id, ByteBuffer bytes) { |
||||
byte[] addressBytes = new byte[ADDRESS_LENGTH]; |
||||
bytes.get(addressBytes); |
||||
String address = Base58.encode(addressBytes); |
||||
|
||||
return new GetAccountNamesMessage(id, address); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,69 @@
|
||||
package org.qortal.network.message; |
||||
|
||||
import com.google.common.primitives.Ints; |
||||
import org.qortal.transform.Transformer; |
||||
import org.qortal.utils.Base58; |
||||
|
||||
import java.io.ByteArrayOutputStream; |
||||
import java.io.IOException; |
||||
import java.nio.ByteBuffer; |
||||
|
||||
public class GetAccountTransactionsMessage extends Message { |
||||
|
||||
private static final int ADDRESS_LENGTH = Transformer.ADDRESS_LENGTH; |
||||
|
||||
private String address; |
||||
private int limit; |
||||
private int offset; |
||||
|
||||
public GetAccountTransactionsMessage(String address, int limit, int offset) { |
||||
super(MessageType.GET_ACCOUNT_TRANSACTIONS); |
||||
|
||||
ByteArrayOutputStream bytes = new ByteArrayOutputStream(); |
||||
|
||||
try { |
||||
// Send raw address instead of base58 encoded
|
||||
byte[] addressBytes = Base58.decode(address); |
||||
bytes.write(addressBytes); |
||||
|
||||
bytes.write(Ints.toByteArray(limit)); |
||||
|
||||
bytes.write(Ints.toByteArray(offset)); |
||||
|
||||
} catch (IOException e) { |
||||
throw new AssertionError("IOException shouldn't occur with ByteArrayOutputStream"); |
||||
} |
||||
|
||||
this.dataBytes = bytes.toByteArray(); |
||||
this.checksumBytes = Message.generateChecksum(this.dataBytes); |
||||
} |
||||
|
||||
private GetAccountTransactionsMessage(int id, String address, int limit, int offset) { |
||||
super(id, MessageType.GET_ACCOUNT_TRANSACTIONS); |
||||
|
||||
this.address = address; |
||||
this.limit = limit; |
||||
this.offset = offset; |
||||
} |
||||
|
||||
public String getAddress() { |
||||
return this.address; |
||||
} |
||||
|
||||
public int getLimit() { return this.limit; } |
||||
|
||||
public int getOffset() { return this.offset; } |
||||
|
||||
public static Message fromByteBuffer(int id, ByteBuffer bytes) { |
||||
byte[] addressBytes = new byte[ADDRESS_LENGTH]; |
||||
bytes.get(addressBytes); |
||||
String address = Base58.encode(addressBytes); |
||||
|
||||
int limit = bytes.getInt(); |
||||
|
||||
int offset = bytes.getInt(); |
||||
|
||||
return new GetAccountTransactionsMessage(id, address, limit, offset); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,53 @@
|
||||
package org.qortal.network.message; |
||||
|
||||
import org.qortal.naming.Name; |
||||
import org.qortal.transform.TransformationException; |
||||
import org.qortal.utils.Serialization; |
||||
|
||||
import java.io.ByteArrayOutputStream; |
||||
import java.io.IOException; |
||||
import java.nio.ByteBuffer; |
||||
|
||||
public class GetNameMessage extends Message { |
||||
|
||||
private String name; |
||||
|
||||
public GetNameMessage(String address) { |
||||
super(MessageType.GET_NAME); |
||||
|
||||
ByteArrayOutputStream bytes = new ByteArrayOutputStream(); |
||||
|
||||
try { |
||||
Serialization.serializeSizedStringV2(bytes, name); |
||||
|
||||
} catch (IOException e) { |
||||
throw new AssertionError("IOException shouldn't occur with ByteArrayOutputStream"); |
||||
} |
||||
|
||||
this.dataBytes = bytes.toByteArray(); |
||||
this.checksumBytes = Message.generateChecksum(this.dataBytes); |
||||
} |
||||
|
||||
private GetNameMessage(int id, String name) { |
||||
super(id, MessageType.GET_NAME); |
||||
|
||||
this.name = name; |
||||
} |
||||
|
||||
public String getName() { |
||||
return this.name; |
||||
} |
||||
|
||||
|
||||
public static Message fromByteBuffer(int id, ByteBuffer bytes) throws MessageException { |
||||
try { |
||||
String name = Serialization.deserializeSizedStringV2(bytes, Name.MAX_NAME_SIZE); |
||||
|
||||
return new GetNameMessage(id, name); |
||||
|
||||
} catch (TransformationException e) { |
||||
throw new MessageException(e.getMessage(), e); |
||||
} |
||||
} |
||||
|
||||
} |
@ -0,0 +1,142 @@
|
||||
package org.qortal.network.message; |
||||
|
||||
import com.google.common.primitives.Ints; |
||||
import com.google.common.primitives.Longs; |
||||
import org.qortal.data.naming.NameData; |
||||
import org.qortal.naming.Name; |
||||
import org.qortal.transform.TransformationException; |
||||
import org.qortal.transform.Transformer; |
||||
import org.qortal.utils.Serialization; |
||||
|
||||
import java.io.ByteArrayOutputStream; |
||||
import java.io.IOException; |
||||
import java.nio.BufferUnderflowException; |
||||
import java.nio.ByteBuffer; |
||||
import java.util.ArrayList; |
||||
import java.util.List; |
||||
|
||||
public class NamesMessage extends Message { |
||||
|
||||
private static final int SIGNATURE_LENGTH = Transformer.SIGNATURE_LENGTH; |
||||
|
||||
private List<NameData> nameDataList; |
||||
|
||||
public NamesMessage(List<NameData> nameDataList) { |
||||
super(MessageType.NAMES); |
||||
|
||||
ByteArrayOutputStream bytes = new ByteArrayOutputStream(); |
||||
|
||||
try { |
||||
bytes.write(Ints.toByteArray(nameDataList.size())); |
||||
|
||||
for (int i = 0; i < nameDataList.size(); ++i) { |
||||
NameData nameData = nameDataList.get(i); |
||||
|
||||
Serialization.serializeSizedStringV2(bytes, nameData.getName()); |
||||
|
||||
Serialization.serializeSizedStringV2(bytes, nameData.getReducedName()); |
||||
|
||||
Serialization.serializeAddress(bytes, nameData.getOwner()); |
||||
|
||||
Serialization.serializeSizedStringV2(bytes, nameData.getData()); |
||||
|
||||
bytes.write(Longs.toByteArray(nameData.getRegistered())); |
||||
|
||||
Long updated = nameData.getUpdated(); |
||||
int wasUpdated = (updated != null) ? 1 : 0; |
||||
bytes.write(Ints.toByteArray(wasUpdated)); |
||||
|
||||
if (updated != null) { |
||||
bytes.write(Longs.toByteArray(nameData.getUpdated())); |
||||
} |
||||
|
||||
int isForSale = nameData.isForSale() ? 1 : 0; |
||||
bytes.write(Ints.toByteArray(isForSale)); |
||||
|
||||
if (nameData.isForSale()) { |
||||
bytes.write(Longs.toByteArray(nameData.getSalePrice())); |
||||
} |
||||
|
||||
bytes.write(nameData.getReference()); |
||||
|
||||
bytes.write(Ints.toByteArray(nameData.getCreationGroupId())); |
||||
} |
||||
|
||||
} catch (IOException e) { |
||||
throw new AssertionError("IOException shouldn't occur with ByteArrayOutputStream"); |
||||
} |
||||
|
||||
this.dataBytes = bytes.toByteArray(); |
||||
this.checksumBytes = Message.generateChecksum(this.dataBytes); |
||||
} |
||||
|
||||
public NamesMessage(int id, List<NameData> nameDataList) { |
||||
super(id, MessageType.NAMES); |
||||
|
||||
this.nameDataList = nameDataList; |
||||
} |
||||
|
||||
public List<NameData> getNameDataList() { |
||||
return this.nameDataList; |
||||
} |
||||
|
||||
|
||||
public static Message fromByteBuffer(int id, ByteBuffer bytes) throws MessageException { |
||||
try { |
||||
final int nameCount = bytes.getInt(); |
||||
|
||||
List<NameData> nameDataList = new ArrayList<>(nameCount); |
||||
|
||||
for (int i = 0; i < nameCount; ++i) { |
||||
String name = Serialization.deserializeSizedStringV2(bytes, Name.MAX_NAME_SIZE); |
||||
|
||||
String reducedName = Serialization.deserializeSizedStringV2(bytes, Name.MAX_NAME_SIZE); |
||||
|
||||
String owner = Serialization.deserializeAddress(bytes); |
||||
|
||||
String data = Serialization.deserializeSizedStringV2(bytes, Name.MAX_DATA_SIZE); |
||||
|
||||
long registered = bytes.getLong(); |
||||
|
||||
int wasUpdated = bytes.getInt(); |
||||
|
||||
Long updated = null; |
||||
if (wasUpdated == 1) { |
||||
updated = bytes.getLong(); |
||||
} |
||||
|
||||
boolean isForSale = (bytes.getInt() == 1); |
||||
|
||||
Long salePrice = null; |
||||
if (isForSale) { |
||||
salePrice = bytes.getLong(); |
||||
} |
||||
|
||||
byte[] reference = new byte[SIGNATURE_LENGTH]; |
||||
bytes.get(reference); |
||||
|
||||
int creationGroupId = bytes.getInt(); |
||||
|
||||
NameData nameData = new NameData(name, reducedName, owner, data, registered, updated, |
||||
isForSale, salePrice, reference, creationGroupId); |
||||
nameDataList.add(nameData); |
||||
} |
||||
|
||||
if (bytes.hasRemaining()) { |
||||
throw new BufferUnderflowException(); |
||||
} |
||||
|
||||
return new NamesMessage(id, nameDataList); |
||||
|
||||
} catch (TransformationException e) { |
||||
throw new MessageException(e.getMessage(), e); |
||||
} |
||||
} |
||||
|
||||
public NamesMessage cloneWithNewId(int newId) { |
||||
NamesMessage clone = new NamesMessage(this.nameDataList); |
||||
clone.setId(newId); |
||||
return clone; |
||||
} |
||||
|
||||
} |
@ -0,0 +1,76 @@
|
||||
package org.qortal.network.message; |
||||
|
||||
import com.google.common.primitives.Ints; |
||||
import org.qortal.data.transaction.TransactionData; |
||||
import org.qortal.transform.TransformationException; |
||||
import org.qortal.transform.transaction.TransactionTransformer; |
||||
|
||||
import java.io.ByteArrayOutputStream; |
||||
import java.io.IOException; |
||||
import java.nio.BufferUnderflowException; |
||||
import java.nio.ByteBuffer; |
||||
import java.util.ArrayList; |
||||
import java.util.List; |
||||
|
||||
public class TransactionsMessage extends Message { |
||||
|
||||
private List<TransactionData> transactions; |
||||
|
||||
public TransactionsMessage(List<TransactionData> transactions) throws MessageException { |
||||
super(MessageType.TRANSACTIONS); |
||||
|
||||
ByteArrayOutputStream bytes = new ByteArrayOutputStream(); |
||||
|
||||
try { |
||||
bytes.write(Ints.toByteArray(transactions.size())); |
||||
|
||||
for (int i = 0; i < transactions.size(); ++i) { |
||||
TransactionData transactionData = transactions.get(i); |
||||
|
||||
byte[] serializedTransactionData = TransactionTransformer.toBytes(transactionData); |
||||
bytes.write(serializedTransactionData); |
||||
} |
||||
|
||||
} catch (IOException e) { |
||||
throw new AssertionError("IOException shouldn't occur with ByteArrayOutputStream"); |
||||
} catch (TransformationException e) { |
||||
throw new MessageException(e.getMessage(), e); |
||||
} |
||||
|
||||
this.dataBytes = bytes.toByteArray(); |
||||
this.checksumBytes = Message.generateChecksum(this.dataBytes); |
||||
} |
||||
|
||||
private TransactionsMessage(int id, List<TransactionData> transactions) { |
||||
super(id, MessageType.TRANSACTIONS); |
||||
|
||||
this.transactions = transactions; |
||||
} |
||||
|
||||
public List<TransactionData> getTransactions() { |
||||
return this.transactions; |
||||
} |
||||
|
||||
public static Message fromByteBuffer(int id, ByteBuffer byteBuffer) throws MessageException { |
||||
try { |
||||
final int transactionCount = byteBuffer.getInt(); |
||||
|
||||
List<TransactionData> transactions = new ArrayList<>(); |
||||
|
||||
for (int i = 0; i < transactionCount; ++i) { |
||||
TransactionData transactionData = TransactionTransformer.fromByteBuffer(byteBuffer); |
||||
transactions.add(transactionData); |
||||
} |
||||
|
||||
if (byteBuffer.hasRemaining()) { |
||||
throw new BufferUnderflowException(); |
||||
} |
||||
|
||||
return new TransactionsMessage(id, transactions); |
||||
|
||||
} catch (TransformationException e) { |
||||
throw new MessageException(e.getMessage(), e); |
||||
} |
||||
} |
||||
|
||||
} |
@ -0,0 +1,96 @@
|
||||
package org.qortal.test.at; |
||||
|
||||
import com.google.common.hash.HashCode; |
||||
import org.junit.After; |
||||
import org.junit.Before; |
||||
import org.junit.Test; |
||||
import org.qortal.account.PrivateKeyAccount; |
||||
import org.qortal.data.transaction.ATTransactionData; |
||||
import org.qortal.data.transaction.TransactionData; |
||||
import org.qortal.repository.DataException; |
||||
import org.qortal.repository.Repository; |
||||
import org.qortal.repository.RepositoryManager; |
||||
import org.qortal.test.common.Common; |
||||
import org.qortal.test.common.transaction.AtTestTransaction; |
||||
import org.qortal.transaction.Transaction; |
||||
import org.qortal.transform.TransformationException; |
||||
import org.qortal.transform.transaction.TransactionTransformer; |
||||
import org.qortal.utils.Base58; |
||||
|
||||
import static org.junit.Assert.assertEquals; |
||||
|
||||
public class AtSerializationTests extends Common { |
||||
|
||||
@Before |
||||
public void beforeTest() throws DataException { |
||||
Common.useDefaultSettings(); |
||||
} |
||||
|
||||
@After |
||||
public void afterTest() throws DataException { |
||||
Common.orphanCheck(); |
||||
} |
||||
|
||||
|
||||
@Test |
||||
public void testPaymentTypeAtSerialization() throws DataException, TransformationException { |
||||
try (final Repository repository = RepositoryManager.getRepository()) { |
||||
|
||||
// Build PAYMENT-type AT transaction
|
||||
PrivateKeyAccount signingAccount = Common.getTestAccount(repository, "alice"); |
||||
ATTransactionData transactionData = (ATTransactionData) AtTestTransaction.paymentType(repository, signingAccount, true); |
||||
Transaction transaction = Transaction.fromData(repository, transactionData); |
||||
transaction.sign(signingAccount); |
||||
|
||||
final int claimedLength = TransactionTransformer.getDataLength(transactionData); |
||||
byte[] serializedTransaction = TransactionTransformer.toBytes(transactionData); |
||||
assertEquals("Serialized PAYMENT-type AT transaction length differs from declared length", claimedLength, serializedTransaction.length); |
||||
|
||||
TransactionData deserializedTransactionData = TransactionTransformer.fromBytes(serializedTransaction); |
||||
// Re-sign
|
||||
Transaction deserializedTransaction = Transaction.fromData(repository, deserializedTransactionData); |
||||
deserializedTransaction.sign(signingAccount); |
||||
assertEquals("Deserialized PAYMENT-type AT transaction signature differs", Base58.encode(transactionData.getSignature()), Base58.encode(deserializedTransactionData.getSignature())); |
||||
|
||||
// Re-serialize to check new length and bytes
|
||||
final int reclaimedLength = TransactionTransformer.getDataLength(deserializedTransactionData); |
||||
assertEquals("Reserialized PAYMENT-type AT transaction declared length differs", claimedLength, reclaimedLength); |
||||
|
||||
byte[] reserializedTransaction = TransactionTransformer.toBytes(deserializedTransactionData); |
||||
assertEquals("Reserialized PAYMENT-type AT transaction bytes differ", HashCode.fromBytes(serializedTransaction).toString(), HashCode.fromBytes(reserializedTransaction).toString()); |
||||
} |
||||
} |
||||
|
||||
@Test |
||||
public void testMessageTypeAtSerialization() throws DataException, TransformationException { |
||||
try (final Repository repository = RepositoryManager.getRepository()) { |
||||
|
||||
// Build MESSAGE-type AT transaction
|
||||
PrivateKeyAccount signingAccount = Common.getTestAccount(repository, "alice"); |
||||
ATTransactionData transactionData = (ATTransactionData) AtTestTransaction.messageType(repository, signingAccount, true); |
||||
Transaction transaction = Transaction.fromData(repository, transactionData); |
||||
transaction.sign(signingAccount); |
||||
|
||||
// MESSAGE-type AT transactions are only fully supported since transaction V6
|
||||
assertEquals(6, Transaction.getVersionByTimestamp(transactionData.getTimestamp())); |
||||
|
||||
final int claimedLength = TransactionTransformer.getDataLength(transactionData); |
||||
byte[] serializedTransaction = TransactionTransformer.toBytes(transactionData); |
||||
assertEquals("Serialized MESSAGE-type AT transaction length differs from declared length", claimedLength, serializedTransaction.length); |
||||
|
||||
TransactionData deserializedTransactionData = TransactionTransformer.fromBytes(serializedTransaction); |
||||
// Re-sign
|
||||
Transaction deserializedTransaction = Transaction.fromData(repository, deserializedTransactionData); |
||||
deserializedTransaction.sign(signingAccount); |
||||
assertEquals("Deserialized MESSAGE-type AT transaction signature differs", Base58.encode(transactionData.getSignature()), Base58.encode(deserializedTransactionData.getSignature())); |
||||
|
||||
// Re-serialize to check new length and bytes
|
||||
final int reclaimedLength = TransactionTransformer.getDataLength(deserializedTransactionData); |
||||
assertEquals("Reserialized MESSAGE-type AT transaction declared length differs", claimedLength, reclaimedLength); |
||||
|
||||
byte[] reserializedTransaction = TransactionTransformer.toBytes(deserializedTransactionData); |
||||
assertEquals("Reserialized MESSAGE-type AT transaction bytes differ", HashCode.fromBytes(serializedTransaction).toString(), HashCode.fromBytes(reserializedTransaction).toString()); |
||||
} |
||||
} |
||||
|
||||
} |
Loading…
Reference in new issue