forked from Qortal/qortal
Compare commits
24 Commits
online-acc
...
BLOCK_SUMM
Author | SHA1 | Date | |
---|---|---|---|
|
d2ebb215e6 | ||
|
7a60f713ea | ||
|
e80dd31fb4 | ||
|
94cdc10151 | ||
|
863a5eff97 | ||
|
5b81b30974 | ||
|
174a779e4c | ||
|
c7cf33ef78 | ||
|
ea4f4d949b | ||
|
6d9e6e8d4c | ||
|
99858f3781 | ||
|
84a16157d1 | ||
|
49d83650f4 | ||
|
951c85faf1 | ||
|
84d42b93e1 | ||
|
b99b1f5d57 | ||
|
952c51ab25 | ||
|
64ef8ab863 | ||
|
5017072f6c | ||
|
02ac6dd8c1 | ||
|
858269f6cb | ||
|
791a9b78ec | ||
|
aff49e6bdf | ||
|
2d29fdca00 |
2
pom.xml
2
pom.xml
@@ -3,7 +3,7 @@
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>org.qortal</groupId>
|
||||
<artifactId>qortal</artifactId>
|
||||
<version>3.5.0</version>
|
||||
<version>3.6.0</version>
|
||||
<packaging>jar</packaging>
|
||||
<properties>
|
||||
<skipTests>true</skipTests>
|
||||
|
@@ -1,7 +1,7 @@
|
||||
package org.qortal.api.model;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import org.qortal.data.network.PeerChainTipData;
|
||||
import org.qortal.data.block.BlockSummaryData;
|
||||
import org.qortal.data.network.PeerData;
|
||||
import org.qortal.network.Handshake;
|
||||
import org.qortal.network.Peer;
|
||||
@@ -63,11 +63,11 @@ public class ConnectedPeer {
|
||||
this.age = "connecting...";
|
||||
}
|
||||
|
||||
PeerChainTipData peerChainTipData = peer.getChainTipData();
|
||||
BlockSummaryData peerChainTipData = peer.getChainTipData();
|
||||
if (peerChainTipData != null) {
|
||||
this.lastHeight = peerChainTipData.getLastHeight();
|
||||
this.lastBlockSignature = peerChainTipData.getLastBlockSignature();
|
||||
this.lastBlockTimestamp = peerChainTipData.getLastBlockTimestamp();
|
||||
this.lastHeight = peerChainTipData.getHeight();
|
||||
this.lastBlockSignature = peerChainTipData.getSignature();
|
||||
this.lastBlockTimestamp = peerChainTipData.getTimestamp();
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -99,6 +99,38 @@ public class ChatResource {
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/message/{signature}")
|
||||
@Operation(
|
||||
summary = "Find chat message by signature",
|
||||
responses = {
|
||||
@ApiResponse(
|
||||
description = "CHAT message",
|
||||
content = @Content(
|
||||
schema = @Schema(
|
||||
implementation = ChatMessage.class
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
)
|
||||
@ApiErrors({ApiError.INVALID_CRITERIA, ApiError.INVALID_ADDRESS, ApiError.REPOSITORY_ISSUE})
|
||||
public ChatMessage getMessageBySignature(@PathParam("signature") String signature58) {
|
||||
byte[] signature = Base58.decode(signature58);
|
||||
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
|
||||
ChatTransactionData chatTransactionData = (ChatTransactionData) repository.getTransactionRepository().fromSignature(signature);
|
||||
if (chatTransactionData == null) {
|
||||
throw ApiExceptionFactory.INSTANCE.createCustomException(request, ApiError.INVALID_CRITERIA, "Message not found");
|
||||
}
|
||||
|
||||
return repository.getChatRepository().toChatMessage(chatTransactionData);
|
||||
} catch (DataException e) {
|
||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE, e);
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/active/{address}")
|
||||
@Operation(
|
||||
|
@@ -1,5 +1,6 @@
|
||||
package org.qortal.api.resource;
|
||||
|
||||
import com.google.common.hash.HashCode;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.media.Content;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
@@ -373,10 +374,6 @@ public class CrossChainHtlcResource {
|
||||
// Use secret-A to redeem P2SH-A
|
||||
|
||||
Bitcoiny bitcoiny = (Bitcoiny) acct.getBlockchain();
|
||||
if (bitcoiny.getClass() == Bitcoin.class) {
|
||||
LOGGER.info("Redeeming a Bitcoin HTLC is not yet supported");
|
||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_CRITERIA);
|
||||
}
|
||||
|
||||
int lockTime = crossChainTradeData.lockTimeA;
|
||||
byte[] redeemScriptA = BitcoinyHTLC.buildScript(crossChainTradeData.partnerForeignPKH, lockTime, crossChainTradeData.creatorForeignPKH, crossChainTradeData.hashOfSecretA);
|
||||
@@ -542,11 +539,6 @@ public class CrossChainHtlcResource {
|
||||
try {
|
||||
// Determine foreign blockchain receive address for refund
|
||||
Bitcoiny bitcoiny = (Bitcoiny) acct.getBlockchain();
|
||||
if (Objects.equals(bitcoiny.getCurrencyCode(), "ARRR")) {
|
||||
LOGGER.info("Skipping AT {} because ARRR is currently unsupported", atAddress);
|
||||
continue;
|
||||
}
|
||||
|
||||
String receivingAddress = bitcoiny.getUnusedReceiveAddress(tradeBotData.getForeignKey());
|
||||
|
||||
LOGGER.info("Attempting to refund P2SH balance associated with AT {}...", atAddress);
|
||||
@@ -589,7 +581,7 @@ public class CrossChainHtlcResource {
|
||||
// If the AT is "finished" then it will have a zero balance
|
||||
// In these cases we should avoid HTLC refunds if tbe QORT haven't been returned to the seller
|
||||
if (atData.getIsFinished() && crossChainTradeData.mode != AcctMode.REFUNDED && crossChainTradeData.mode != AcctMode.CANCELLED) {
|
||||
LOGGER.info(String.format("Skipping AT %s because the QORT has already been redemed", atAddress));
|
||||
LOGGER.info(String.format("Skipping AT %s because the QORT has already been redeemed by the buyer", atAddress));
|
||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_CRITERIA);
|
||||
}
|
||||
|
||||
@@ -599,11 +591,6 @@ public class CrossChainHtlcResource {
|
||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_CRITERIA);
|
||||
|
||||
Bitcoiny bitcoiny = (Bitcoiny) acct.getBlockchain();
|
||||
if (bitcoiny.getClass() == Bitcoin.class) {
|
||||
LOGGER.info("Refunding a Bitcoin HTLC is not yet supported");
|
||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_CRITERIA);
|
||||
}
|
||||
|
||||
int lockTime = tradeBotData.getLockTimeA();
|
||||
|
||||
// We can't refund P2SH-A until lockTime-A has passed
|
||||
@@ -615,15 +602,26 @@ public class CrossChainHtlcResource {
|
||||
if (medianBlockTime <= lockTime)
|
||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.FOREIGN_BLOCKCHAIN_TOO_SOON);
|
||||
|
||||
byte[] redeemScriptA = BitcoinyHTLC.buildScript(tradeBotData.getTradeForeignPublicKeyHash(), lockTime, crossChainTradeData.creatorForeignPKH, tradeBotData.getHashOfSecret());
|
||||
String p2shAddressA = bitcoiny.deriveP2shAddress(redeemScriptA);
|
||||
LOGGER.info(String.format("Refunding P2SH address: %s", p2shAddressA));
|
||||
|
||||
// Fee for redeem/refund is subtracted from P2SH-A balance.
|
||||
long feeTimestamp = calcFeeTimestamp(lockTime, crossChainTradeData.tradeTimeout);
|
||||
long p2shFee = bitcoiny.getP2shFee(feeTimestamp);
|
||||
long minimumAmountA = crossChainTradeData.expectedForeignAmount + p2shFee;
|
||||
BitcoinyHTLC.Status htlcStatusA = BitcoinyHTLC.determineHtlcStatus(bitcoiny.getBlockchainProvider(), p2shAddressA, minimumAmountA);
|
||||
|
||||
// Create redeem script based on destination chain
|
||||
byte[] redeemScriptA;
|
||||
String p2shAddressA;
|
||||
BitcoinyHTLC.Status htlcStatusA;
|
||||
if (Objects.equals(bitcoiny.getCurrencyCode(), "ARRR")) {
|
||||
redeemScriptA = PirateChainHTLC.buildScript(tradeBotData.getTradeForeignPublicKey(), lockTime, crossChainTradeData.creatorForeignPKH, tradeBotData.getHashOfSecret());
|
||||
p2shAddressA = PirateChain.getInstance().deriveP2shAddressBPrefix(redeemScriptA);
|
||||
htlcStatusA = PirateChainHTLC.determineHtlcStatus(bitcoiny.getBlockchainProvider(), p2shAddressA, minimumAmountA);
|
||||
}
|
||||
else {
|
||||
redeemScriptA = BitcoinyHTLC.buildScript(tradeBotData.getTradeForeignPublicKeyHash(), lockTime, crossChainTradeData.creatorForeignPKH, tradeBotData.getHashOfSecret());
|
||||
p2shAddressA = bitcoiny.deriveP2shAddress(redeemScriptA);
|
||||
htlcStatusA = BitcoinyHTLC.determineHtlcStatus(bitcoiny.getBlockchainProvider(), p2shAddressA, minimumAmountA);
|
||||
}
|
||||
LOGGER.info(String.format("Refunding P2SH address: %s", p2shAddressA));
|
||||
|
||||
switch (htlcStatusA) {
|
||||
case UNFUNDED:
|
||||
@@ -640,18 +638,45 @@ public class CrossChainHtlcResource {
|
||||
|
||||
case FUNDED:{
|
||||
Coin refundAmount = Coin.valueOf(crossChainTradeData.expectedForeignAmount);
|
||||
ECKey refundKey = ECKey.fromPrivate(tradeBotData.getTradePrivateKey());
|
||||
List<TransactionOutput> fundingOutputs = bitcoiny.getUnspentOutputs(p2shAddressA);
|
||||
|
||||
// Validate the destination foreign blockchain address
|
||||
Address receiving = Address.fromString(bitcoiny.getNetworkParameters(), receiveAddress);
|
||||
if (receiving.getOutputScriptType() != Script.ScriptType.P2PKH)
|
||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_CRITERIA);
|
||||
if (Objects.equals(bitcoiny.getCurrencyCode(), "ARRR")) {
|
||||
// Pirate Chain custom integration
|
||||
|
||||
Transaction p2shRefundTransaction = BitcoinyHTLC.buildRefundTransaction(bitcoiny.getNetworkParameters(), refundAmount, refundKey,
|
||||
fundingOutputs, redeemScriptA, lockTime, receiving.getHash());
|
||||
PirateChain pirateChain = PirateChain.getInstance();
|
||||
String p2shAddressT3 = pirateChain.deriveP2shAddress(redeemScriptA);
|
||||
|
||||
// Get funding txid
|
||||
String fundingTxidHex = PirateChainHTLC.getUnspentFundingTxid(pirateChain.getBlockchainProvider(), p2shAddressA, minimumAmountA);
|
||||
if (fundingTxidHex == null) {
|
||||
throw new ForeignBlockchainException("Missing funding txid when refunding P2SH");
|
||||
}
|
||||
String fundingTxid58 = Base58.encode(HashCode.fromString(fundingTxidHex).asBytes());
|
||||
|
||||
byte[] privateKey = tradeBotData.getTradePrivateKey();
|
||||
String privateKey58 = Base58.encode(privateKey);
|
||||
String redeemScript58 = Base58.encode(redeemScriptA);
|
||||
|
||||
String txid = PirateChain.getInstance().refundP2sh(p2shAddressT3,
|
||||
receiveAddress, refundAmount.value, redeemScript58, fundingTxid58, lockTime, privateKey58);
|
||||
LOGGER.info("Refund txid: {}", txid);
|
||||
}
|
||||
else {
|
||||
// ElectrumX coins
|
||||
|
||||
ECKey refundKey = ECKey.fromPrivate(tradeBotData.getTradePrivateKey());
|
||||
List<TransactionOutput> fundingOutputs = bitcoiny.getUnspentOutputs(p2shAddressA);
|
||||
|
||||
// Validate the destination foreign blockchain address
|
||||
Address receiving = Address.fromString(bitcoiny.getNetworkParameters(), receiveAddress);
|
||||
if (receiving.getOutputScriptType() != Script.ScriptType.P2PKH)
|
||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_CRITERIA);
|
||||
|
||||
Transaction p2shRefundTransaction = BitcoinyHTLC.buildRefundTransaction(bitcoiny.getNetworkParameters(), refundAmount, refundKey,
|
||||
fundingOutputs, redeemScriptA, lockTime, receiving.getHash());
|
||||
|
||||
bitcoiny.broadcastTransaction(p2shRefundTransaction);
|
||||
}
|
||||
|
||||
bitcoiny.broadcastTransaction(p2shRefundTransaction);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@@ -368,16 +368,17 @@ public class Block {
|
||||
|
||||
// Fetch our list of online accounts
|
||||
List<OnlineAccountData> onlineAccounts = OnlineAccountsManager.getInstance().getOnlineAccounts(onlineAccountsTimestamp);
|
||||
if (onlineAccounts.isEmpty()) {
|
||||
LOGGER.error("No online accounts - not even our own?");
|
||||
return null;
|
||||
}
|
||||
|
||||
// If mempow is active, remove any legacy accounts that are missing a nonce
|
||||
if (timestamp >= BlockChain.getInstance().getOnlineAccountsMemoryPoWTimestamp()) {
|
||||
onlineAccounts.removeIf(a -> a.getNonce() == null || a.getNonce() < 0);
|
||||
}
|
||||
|
||||
if (onlineAccounts.isEmpty()) {
|
||||
LOGGER.error("No online accounts - not even our own?");
|
||||
return null;
|
||||
}
|
||||
|
||||
// Load sorted list of reward share public keys into memory, so that the indexes can be obtained.
|
||||
// This is up to 100x faster than querying each index separately. For 4150 reward share keys, it
|
||||
// was taking around 5000ms to query individually, vs 50ms using this approach.
|
||||
|
@@ -26,6 +26,9 @@ import org.qortal.data.block.CommonBlockData;
|
||||
import org.qortal.data.transaction.TransactionData;
|
||||
import org.qortal.network.Network;
|
||||
import org.qortal.network.Peer;
|
||||
import org.qortal.network.message.BlockSummariesV2Message;
|
||||
import org.qortal.network.message.HeightV2Message;
|
||||
import org.qortal.network.message.Message;
|
||||
import org.qortal.repository.BlockRepository;
|
||||
import org.qortal.repository.DataException;
|
||||
import org.qortal.repository.Repository;
|
||||
@@ -433,11 +436,9 @@ public class BlockMinter extends Thread {
|
||||
|
||||
if (newBlockMinted) {
|
||||
// Broadcast our new chain to network
|
||||
BlockData newBlockData = newBlock.getBlockData();
|
||||
|
||||
Network network = Network.getInstance();
|
||||
network.broadcast(broadcastPeer -> network.buildHeightMessage(broadcastPeer, newBlockData));
|
||||
Network.getInstance().broadcastOurChain();
|
||||
}
|
||||
|
||||
} catch (InterruptedException e) {
|
||||
// We've been interrupted - time to exit
|
||||
return;
|
||||
|
@@ -45,7 +45,6 @@ import org.qortal.data.account.AccountData;
|
||||
import org.qortal.data.block.BlockData;
|
||||
import org.qortal.data.block.BlockSummaryData;
|
||||
import org.qortal.data.naming.NameData;
|
||||
import org.qortal.data.network.PeerChainTipData;
|
||||
import org.qortal.data.network.PeerData;
|
||||
import org.qortal.data.transaction.ChatTransactionData;
|
||||
import org.qortal.data.transaction.TransactionData;
|
||||
@@ -317,6 +316,10 @@ public class Controller extends Thread {
|
||||
}
|
||||
}
|
||||
|
||||
public static long uptime() {
|
||||
return System.currentTimeMillis() - Controller.startTime;
|
||||
}
|
||||
|
||||
/** Returns highest block, or null if it's not available. */
|
||||
public BlockData getChainTip() {
|
||||
synchronized (this.latestBlocks) {
|
||||
@@ -727,25 +730,25 @@ public class Controller extends Thread {
|
||||
|
||||
public static final Predicate<Peer> hasNoRecentBlock = peer -> {
|
||||
final Long minLatestBlockTimestamp = getMinimumLatestBlockTimestamp();
|
||||
final PeerChainTipData peerChainTipData = peer.getChainTipData();
|
||||
return peerChainTipData == null || peerChainTipData.getLastBlockTimestamp() == null || peerChainTipData.getLastBlockTimestamp() < minLatestBlockTimestamp;
|
||||
final BlockSummaryData peerChainTipData = peer.getChainTipData();
|
||||
return peerChainTipData == null || peerChainTipData.getTimestamp() == null || peerChainTipData.getTimestamp() < minLatestBlockTimestamp;
|
||||
};
|
||||
|
||||
public static final Predicate<Peer> hasNoOrSameBlock = peer -> {
|
||||
final BlockData latestBlockData = getInstance().getChainTip();
|
||||
final PeerChainTipData peerChainTipData = peer.getChainTipData();
|
||||
return peerChainTipData == null || peerChainTipData.getLastBlockSignature() == null || Arrays.equals(latestBlockData.getSignature(), peerChainTipData.getLastBlockSignature());
|
||||
final BlockSummaryData peerChainTipData = peer.getChainTipData();
|
||||
return peerChainTipData == null || peerChainTipData.getSignature() == null || Arrays.equals(latestBlockData.getSignature(), peerChainTipData.getSignature());
|
||||
};
|
||||
|
||||
public static final Predicate<Peer> hasOnlyGenesisBlock = peer -> {
|
||||
final PeerChainTipData peerChainTipData = peer.getChainTipData();
|
||||
return peerChainTipData == null || peerChainTipData.getLastHeight() == null || peerChainTipData.getLastHeight() == 1;
|
||||
final BlockSummaryData peerChainTipData = peer.getChainTipData();
|
||||
return peerChainTipData == null || peerChainTipData.getHeight() == 1;
|
||||
};
|
||||
|
||||
public static final Predicate<Peer> hasInferiorChainTip = peer -> {
|
||||
final PeerChainTipData peerChainTipData = peer.getChainTipData();
|
||||
final BlockSummaryData peerChainTipData = peer.getChainTipData();
|
||||
final List<ByteArray> inferiorChainTips = Synchronizer.getInstance().inferiorChainSignatures;
|
||||
return peerChainTipData == null || peerChainTipData.getLastBlockSignature() == null || inferiorChainTips.contains(ByteArray.wrap(peerChainTipData.getLastBlockSignature()));
|
||||
return peerChainTipData == null || peerChainTipData.getSignature() == null || inferiorChainTips.contains(ByteArray.wrap(peerChainTipData.getSignature()));
|
||||
};
|
||||
|
||||
public static final Predicate<Peer> hasOldVersion = peer -> {
|
||||
@@ -1007,8 +1010,7 @@ public class Controller extends Thread {
|
||||
network.broadcast(peer -> peer.isOutbound() ? network.buildPeersMessage(peer) : new GetPeersMessage());
|
||||
|
||||
// Send our current height
|
||||
BlockData latestBlockData = getChainTip();
|
||||
network.broadcast(peer -> network.buildHeightMessage(peer, latestBlockData));
|
||||
network.broadcastOurChain();
|
||||
|
||||
// Request unconfirmed transaction signatures, but only if we're up-to-date.
|
||||
// If we're NOT up-to-date then priority is synchronizing first
|
||||
@@ -1215,6 +1217,10 @@ public class Controller extends Thread {
|
||||
onNetworkHeightV2Message(peer, message);
|
||||
break;
|
||||
|
||||
case BLOCK_SUMMARIES_V2:
|
||||
onNetworkBlockSummariesV2Message(peer, message);
|
||||
break;
|
||||
|
||||
case GET_TRANSACTION:
|
||||
TransactionImporter.getInstance().onNetworkGetTransactionMessage(peer, message);
|
||||
break;
|
||||
@@ -1232,19 +1238,10 @@ public class Controller extends Thread {
|
||||
break;
|
||||
|
||||
case GET_ONLINE_ACCOUNTS:
|
||||
OnlineAccountsManager.getInstance().onNetworkGetOnlineAccountsMessage(peer, message);
|
||||
break;
|
||||
|
||||
case ONLINE_ACCOUNTS:
|
||||
OnlineAccountsManager.getInstance().onNetworkOnlineAccountsMessage(peer, message);
|
||||
break;
|
||||
|
||||
case GET_ONLINE_ACCOUNTS_V2:
|
||||
OnlineAccountsManager.getInstance().onNetworkGetOnlineAccountsV2Message(peer, message);
|
||||
break;
|
||||
|
||||
case ONLINE_ACCOUNTS_V2:
|
||||
OnlineAccountsManager.getInstance().onNetworkOnlineAccountsV2Message(peer, message);
|
||||
// No longer supported - to be eventually removed
|
||||
break;
|
||||
|
||||
case GET_ONLINE_ACCOUNTS_V3:
|
||||
@@ -1378,8 +1375,10 @@ public class Controller extends Thread {
|
||||
// Send valid, yet unexpected message type in response, so peer's synchronizer doesn't have to wait for timeout
|
||||
LOGGER.debug(() -> String.format("Sending 'block unknown' response to peer %s for GET_BLOCK request for unknown block %s", peer, Base58.encode(signature)));
|
||||
|
||||
// We'll send empty block summaries message as it's very short
|
||||
Message blockUnknownMessage = new BlockSummariesMessage(Collections.emptyList());
|
||||
// Send generic 'unknown' message as it's very short
|
||||
Message blockUnknownMessage = peer.getPeersVersion() >= GenericUnknownMessage.MINIMUM_PEER_VERSION
|
||||
? new GenericUnknownMessage()
|
||||
: new BlockSummariesMessage(Collections.emptyList());
|
||||
blockUnknownMessage.setId(message.getId());
|
||||
if (!peer.sendMessage(blockUnknownMessage))
|
||||
peer.disconnect("failed to send block-unknown response");
|
||||
@@ -1428,11 +1427,15 @@ public class Controller extends Thread {
|
||||
this.stats.getBlockSummariesStats.requests.incrementAndGet();
|
||||
|
||||
// If peer's parent signature matches our latest block signature
|
||||
// then we can short-circuit with an empty response
|
||||
// then we have no blocks after that and can short-circuit with an empty response
|
||||
BlockData chainTip = getChainTip();
|
||||
if (chainTip != null && Arrays.equals(parentSignature, chainTip.getSignature())) {
|
||||
Message blockSummariesMessage = new BlockSummariesMessage(Collections.emptyList());
|
||||
Message blockSummariesMessage = peer.getPeersVersion() >= BlockSummariesV2Message.MINIMUM_PEER_VERSION
|
||||
? new BlockSummariesV2Message(Collections.emptyList())
|
||||
: new BlockSummariesMessage(Collections.emptyList());
|
||||
|
||||
blockSummariesMessage.setId(message.getId());
|
||||
|
||||
if (!peer.sendMessage(blockSummariesMessage))
|
||||
peer.disconnect("failed to send block summaries");
|
||||
|
||||
@@ -1488,7 +1491,9 @@ public class Controller extends Thread {
|
||||
this.stats.getBlockSummariesStats.fullyFromCache.incrementAndGet();
|
||||
}
|
||||
|
||||
Message blockSummariesMessage = new BlockSummariesMessage(blockSummaries);
|
||||
Message blockSummariesMessage = peer.getPeersVersion() >= BlockSummariesV2Message.MINIMUM_PEER_VERSION
|
||||
? new BlockSummariesV2Message(blockSummaries)
|
||||
: new BlockSummariesMessage(blockSummaries);
|
||||
blockSummariesMessage.setId(message.getId());
|
||||
if (!peer.sendMessage(blockSummariesMessage))
|
||||
peer.disconnect("failed to send block summaries");
|
||||
@@ -1563,18 +1568,48 @@ public class Controller extends Thread {
|
||||
// If peer is inbound and we've not updated their height
|
||||
// then this is probably their initial HEIGHT_V2 message
|
||||
// so they need a corresponding HEIGHT_V2 message from us
|
||||
if (!peer.isOutbound() && (peer.getChainTipData() == null || peer.getChainTipData().getLastHeight() == null))
|
||||
peer.sendMessage(Network.getInstance().buildHeightMessage(peer, getChainTip()));
|
||||
if (!peer.isOutbound() && peer.getChainTipData() == null) {
|
||||
Message responseMessage = Network.getInstance().buildHeightOrChainTipInfo(peer);
|
||||
|
||||
if (responseMessage == null || !peer.sendMessage(responseMessage)) {
|
||||
peer.disconnect("failed to send our chain tip info");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update peer chain tip data
|
||||
PeerChainTipData newChainTipData = new PeerChainTipData(heightV2Message.getHeight(), heightV2Message.getSignature(), heightV2Message.getTimestamp(), heightV2Message.getMinterPublicKey());
|
||||
BlockSummaryData newChainTipData = new BlockSummaryData(heightV2Message.getHeight(), heightV2Message.getSignature(), heightV2Message.getMinterPublicKey(), heightV2Message.getTimestamp());
|
||||
peer.setChainTipData(newChainTipData);
|
||||
|
||||
// Potentially synchronize
|
||||
Synchronizer.getInstance().requestSync();
|
||||
}
|
||||
|
||||
private void onNetworkBlockSummariesV2Message(Peer peer, Message message) {
|
||||
BlockSummariesV2Message blockSummariesV2Message = (BlockSummariesV2Message) message;
|
||||
|
||||
if (!Settings.getInstance().isLite()) {
|
||||
// If peer is inbound and we've not updated their height
|
||||
// then this is probably their initial BLOCK_SUMMARIES_V2 message
|
||||
// so they need a corresponding BLOCK_SUMMARIES_V2 message from us
|
||||
if (!peer.isOutbound() && peer.getChainTipData() == null) {
|
||||
Message responseMessage = Network.getInstance().buildHeightOrChainTipInfo(peer);
|
||||
|
||||
if (responseMessage == null || !peer.sendMessage(responseMessage)) {
|
||||
peer.disconnect("failed to send our chain tip info");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update peer chain tip data
|
||||
peer.setChainTipSummaries(blockSummariesV2Message.getBlockSummaries());
|
||||
|
||||
// Potentially synchronize
|
||||
Synchronizer.getInstance().requestSync();
|
||||
}
|
||||
|
||||
private void onNetworkGetAccountMessage(Peer peer, Message message) {
|
||||
GetAccountMessage getAccountMessage = (GetAccountMessage) message;
|
||||
String address = getAccountMessage.getAddress();
|
||||
@@ -1590,8 +1625,8 @@ public class Controller extends Thread {
|
||||
// Send valid, yet unexpected message type in response, so peer doesn't have to wait for timeout
|
||||
LOGGER.debug(() -> String.format("Sending 'account unknown' response to peer %s for GET_ACCOUNT request for unknown account %s", peer, address));
|
||||
|
||||
// We'll send empty block summaries message as it's very short
|
||||
Message accountUnknownMessage = new BlockSummariesMessage(Collections.emptyList());
|
||||
// Send generic 'unknown' message as it's very short
|
||||
Message accountUnknownMessage = new GenericUnknownMessage();
|
||||
accountUnknownMessage.setId(message.getId());
|
||||
if (!peer.sendMessage(accountUnknownMessage))
|
||||
peer.disconnect("failed to send account-unknown response");
|
||||
@@ -1626,8 +1661,8 @@ public class Controller extends Thread {
|
||||
// Send valid, yet unexpected message type in response, so peer doesn't have to wait for timeout
|
||||
LOGGER.debug(() -> String.format("Sending 'account unknown' response to peer %s for GET_ACCOUNT_BALANCE request for unknown account %s and asset ID %d", peer, address, assetId));
|
||||
|
||||
// We'll send empty block summaries message as it's very short
|
||||
Message accountUnknownMessage = new BlockSummariesMessage(Collections.emptyList());
|
||||
// Send generic 'unknown' message as it's very short
|
||||
Message accountUnknownMessage = new GenericUnknownMessage();
|
||||
accountUnknownMessage.setId(message.getId());
|
||||
if (!peer.sendMessage(accountUnknownMessage))
|
||||
peer.disconnect("failed to send account-unknown response");
|
||||
@@ -1670,8 +1705,8 @@ public class Controller extends Thread {
|
||||
// Send valid, yet unexpected message type in response, so peer doesn't have to wait for timeout
|
||||
LOGGER.debug(() -> String.format("Sending 'account unknown' response to peer %s for GET_ACCOUNT_TRANSACTIONS request for unknown account %s", peer, address));
|
||||
|
||||
// We'll send empty block summaries message as it's very short
|
||||
Message accountUnknownMessage = new BlockSummariesMessage(Collections.emptyList());
|
||||
// Send generic 'unknown' message as it's very short
|
||||
Message accountUnknownMessage = new GenericUnknownMessage();
|
||||
accountUnknownMessage.setId(message.getId());
|
||||
if (!peer.sendMessage(accountUnknownMessage))
|
||||
peer.disconnect("failed to send account-unknown response");
|
||||
@@ -1707,8 +1742,8 @@ public class Controller extends Thread {
|
||||
// Send valid, yet unexpected message type in response, so peer doesn't have to wait for timeout
|
||||
LOGGER.debug(() -> String.format("Sending 'account unknown' response to peer %s for GET_ACCOUNT_NAMES request for unknown account %s", peer, address));
|
||||
|
||||
// We'll send empty block summaries message as it's very short
|
||||
Message accountUnknownMessage = new BlockSummariesMessage(Collections.emptyList());
|
||||
// Send generic 'unknown' message as it's very short
|
||||
Message accountUnknownMessage = new GenericUnknownMessage();
|
||||
accountUnknownMessage.setId(message.getId());
|
||||
if (!peer.sendMessage(accountUnknownMessage))
|
||||
peer.disconnect("failed to send account-unknown response");
|
||||
@@ -1742,8 +1777,8 @@ public class Controller extends Thread {
|
||||
// Send valid, yet unexpected message type in response, so peer doesn't have to wait for timeout
|
||||
LOGGER.debug(() -> String.format("Sending 'name unknown' response to peer %s for GET_NAME request for unknown name %s", peer, name));
|
||||
|
||||
// We'll send empty block summaries message as it's very short
|
||||
Message nameUnknownMessage = new BlockSummariesMessage(Collections.emptyList());
|
||||
// Send generic 'unknown' message as it's very short
|
||||
Message nameUnknownMessage = new GenericUnknownMessage();
|
||||
nameUnknownMessage.setId(message.getId());
|
||||
if (!peer.sendMessage(nameUnknownMessage))
|
||||
peer.disconnect("failed to send name-unknown response");
|
||||
@@ -1791,14 +1826,14 @@ public class Controller extends Thread {
|
||||
continue;
|
||||
}
|
||||
|
||||
final PeerChainTipData peerChainTipData = peer.getChainTipData();
|
||||
BlockSummaryData peerChainTipData = peer.getChainTipData();
|
||||
if (peerChainTipData == null) {
|
||||
iterator.remove();
|
||||
continue;
|
||||
}
|
||||
|
||||
// Disregard peers that don't have a recent block
|
||||
if (peerChainTipData.getLastBlockTimestamp() == null || peerChainTipData.getLastBlockTimestamp() < minLatestBlockTimestamp) {
|
||||
if (peerChainTipData.getTimestamp() == null || peerChainTipData.getTimestamp() < minLatestBlockTimestamp) {
|
||||
iterator.remove();
|
||||
continue;
|
||||
}
|
||||
|
@@ -55,11 +55,14 @@ public class OnlineAccountsManager {
|
||||
|
||||
private static final long ONLINE_ACCOUNTS_QUEUE_INTERVAL = 100L; //ms
|
||||
private static final long ONLINE_ACCOUNTS_TASKS_INTERVAL = 10 * 1000L; // ms
|
||||
private static final long ONLINE_ACCOUNTS_LEGACY_BROADCAST_INTERVAL = 60 * 1000L; // ms
|
||||
private static final long ONLINE_ACCOUNTS_BROADCAST_INTERVAL = 5 * 1000L; // ms
|
||||
private static final long ONLINE_ACCOUNTS_COMPUTE_INTERVAL = 5 * 1000L; // ms
|
||||
private static final long ONLINE_ACCOUNTS_BROADCAST_INTERVAL = 60 * 1000L; // ms
|
||||
// After switching to a new online timestamp, we "burst" the online accounts requests
|
||||
// at an increased interval for a specified amount of time
|
||||
private static final long ONLINE_ACCOUNTS_BROADCAST_BURST_INTERVAL = 5 * 1000L; // ms
|
||||
private static final long ONLINE_ACCOUNTS_BROADCAST_BURST_LENGTH = 5 * 60 * 1000L; // ms
|
||||
|
||||
private static final long ONLINE_ACCOUNTS_V2_PEER_VERSION = 0x0300020000L; // v3.2.0
|
||||
private static final long ONLINE_ACCOUNTS_V3_PEER_VERSION = 0x0300040000L; // v3.4.0
|
||||
private static final long INITIAL_SLEEP_INTERVAL = 30 * 1000L;
|
||||
|
||||
// MemoryPoW
|
||||
public final int POW_BUFFER_SIZE = 1 * 1024 * 1024; // bytes
|
||||
@@ -68,7 +71,7 @@ public class OnlineAccountsManager {
|
||||
private final ScheduledExecutorService executor = Executors.newScheduledThreadPool(4, new NamedThreadFactory("OnlineAccounts"));
|
||||
private volatile boolean isStopping = false;
|
||||
|
||||
private final Set<OnlineAccountData> onlineAccountsImportQueue = ConcurrentHashMap.newKeySet();
|
||||
private final List<OnlineAccountData> onlineAccountsImportQueue = Collections.synchronizedList(new ArrayList<>());
|
||||
|
||||
/**
|
||||
* Cache of 'current' online accounts, keyed by timestamp
|
||||
@@ -85,6 +88,8 @@ public class OnlineAccountsManager {
|
||||
*/
|
||||
private final SortedMap<Long, Set<OnlineAccountData>> latestBlocksOnlineAccounts = new ConcurrentSkipListMap<>();
|
||||
|
||||
private long lastOnlineAccountsRequest = 0;
|
||||
|
||||
private boolean hasOurOnlineAccounts = false;
|
||||
|
||||
public static long getOnlineTimestampModulus() {
|
||||
@@ -122,16 +127,23 @@ public class OnlineAccountsManager {
|
||||
// Expire old online accounts signatures
|
||||
executor.scheduleAtFixedRate(this::expireOldOnlineAccounts, ONLINE_ACCOUNTS_TASKS_INTERVAL, ONLINE_ACCOUNTS_TASKS_INTERVAL, TimeUnit.MILLISECONDS);
|
||||
|
||||
// Send our online accounts
|
||||
executor.scheduleAtFixedRate(this::sendOurOnlineAccountsInfo, ONLINE_ACCOUNTS_BROADCAST_INTERVAL, ONLINE_ACCOUNTS_BROADCAST_INTERVAL, TimeUnit.MILLISECONDS);
|
||||
|
||||
// Request online accounts from peers (legacy)
|
||||
executor.scheduleAtFixedRate(this::requestLegacyRemoteOnlineAccounts, ONLINE_ACCOUNTS_LEGACY_BROADCAST_INTERVAL, ONLINE_ACCOUNTS_LEGACY_BROADCAST_INTERVAL, TimeUnit.MILLISECONDS);
|
||||
// Request online accounts from peers (V3+)
|
||||
executor.scheduleAtFixedRate(this::requestRemoteOnlineAccounts, ONLINE_ACCOUNTS_BROADCAST_INTERVAL, ONLINE_ACCOUNTS_BROADCAST_INTERVAL, TimeUnit.MILLISECONDS);
|
||||
// Request online accounts from peers
|
||||
executor.scheduleAtFixedRate(this::requestRemoteOnlineAccounts, ONLINE_ACCOUNTS_BROADCAST_BURST_INTERVAL, ONLINE_ACCOUNTS_BROADCAST_BURST_INTERVAL, TimeUnit.MILLISECONDS);
|
||||
|
||||
// Process import queue
|
||||
executor.scheduleWithFixedDelay(this::processOnlineAccountsImportQueue, ONLINE_ACCOUNTS_QUEUE_INTERVAL, ONLINE_ACCOUNTS_QUEUE_INTERVAL, TimeUnit.MILLISECONDS);
|
||||
|
||||
// Sleep for some time before scheduling sendOurOnlineAccountsInfo()
|
||||
// This allows some time for initial online account lists to be retrieved, and
|
||||
// reduces the chances of the same nonce being computed twice
|
||||
try {
|
||||
Thread.sleep(INITIAL_SLEEP_INTERVAL);
|
||||
} catch (InterruptedException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
// Send our online accounts
|
||||
executor.scheduleAtFixedRate(this::sendOurOnlineAccountsInfo, ONLINE_ACCOUNTS_COMPUTE_INTERVAL, ONLINE_ACCOUNTS_COMPUTE_INTERVAL, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
public void shutdown() {
|
||||
@@ -179,15 +191,26 @@ public class OnlineAccountsManager {
|
||||
|
||||
LOGGER.debug("Processing online accounts import queue (size: {})", this.onlineAccountsImportQueue.size());
|
||||
|
||||
// Take a copy of onlineAccountsImportQueue so we can safely remove whilst iterating
|
||||
List<OnlineAccountData> onlineAccountsImportQueueCopy = new ArrayList<>(this.onlineAccountsImportQueue);
|
||||
|
||||
Set<OnlineAccountData> onlineAccountsToAdd = new HashSet<>();
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
for (OnlineAccountData onlineAccountData : this.onlineAccountsImportQueue) {
|
||||
for (OnlineAccountData onlineAccountData : onlineAccountsImportQueueCopy) {
|
||||
if (isStopping)
|
||||
return;
|
||||
|
||||
// Skip this account if it's already validated
|
||||
Set<OnlineAccountData> onlineAccounts = this.currentOnlineAccounts.computeIfAbsent(onlineAccountData.getTimestamp(), k -> ConcurrentHashMap.newKeySet());
|
||||
if (onlineAccounts.contains(onlineAccountData)) {
|
||||
// We have already validated this online account
|
||||
onlineAccountsImportQueue.remove(onlineAccountData);
|
||||
continue;
|
||||
}
|
||||
|
||||
boolean isValid = this.isValidCurrentAccount(repository, onlineAccountData);
|
||||
if (isValid)
|
||||
onlineAccountsToAdd.add(onlineAccountData);
|
||||
addAccounts(Arrays.asList(onlineAccountData));
|
||||
|
||||
// Remove from queue
|
||||
onlineAccountsImportQueue.remove(onlineAccountData);
|
||||
@@ -195,10 +218,18 @@ public class OnlineAccountsManager {
|
||||
} catch (DataException e) {
|
||||
LOGGER.error("Repository issue while verifying online accounts", e);
|
||||
}
|
||||
}
|
||||
|
||||
if (!onlineAccountsToAdd.isEmpty()) {
|
||||
LOGGER.debug("Merging {} validated online accounts from import queue", onlineAccountsToAdd.size());
|
||||
addAccounts(onlineAccountsToAdd);
|
||||
private boolean importQueueContainsExactMatch(OnlineAccountData acc) {
|
||||
// Check if an item exists where all properties match exactly
|
||||
// This is needed because signature and nonce are not compared in OnlineAccountData.equals()
|
||||
synchronized (onlineAccountsImportQueue) {
|
||||
return onlineAccountsImportQueue.stream().anyMatch(otherAcc ->
|
||||
acc.getTimestamp() == otherAcc.getTimestamp() &&
|
||||
Arrays.equals(acc.getPublicKey(), otherAcc.getPublicKey()) &&
|
||||
acc.getNonce() == otherAcc.getNonce() &&
|
||||
Arrays.equals(acc.getSignature(), otherAcc.getSignature())
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -304,8 +335,9 @@ public class OnlineAccountsManager {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Validate mempow if feature trigger is active
|
||||
if (now >= BlockChain.getInstance().getOnlineAccountsMemoryPoWTimestamp()) {
|
||||
// Validate mempow if feature trigger is active (or if online account's timestamp is past the trigger timestamp)
|
||||
long memoryPoWStartTimestamp = BlockChain.getInstance().getOnlineAccountsMemoryPoWTimestamp();
|
||||
if (now >= memoryPoWStartTimestamp || onlineAccountTimestamp >= memoryPoWStartTimestamp) {
|
||||
if (!getInstance().verifyMemoryPoW(onlineAccountData, now)) {
|
||||
LOGGER.trace(() -> String.format("Rejecting online reward-share for account %s due to invalid PoW nonce", mintingAccount.getAddress()));
|
||||
return false;
|
||||
@@ -333,7 +365,7 @@ public class OnlineAccountsManager {
|
||||
for (var entry : hashesToRebuild.entrySet()) {
|
||||
Long timestamp = entry.getKey();
|
||||
|
||||
LOGGER.debug(() -> String.format("Rehashing for timestamp %d and leading bytes %s",
|
||||
LOGGER.trace(() -> String.format("Rehashing for timestamp %d and leading bytes %s",
|
||||
timestamp,
|
||||
entry.getValue().stream().sorted(Byte::compareUnsigned).map(leadingByte -> String.format("%02x", leadingByte)).collect(Collectors.joining(", "))
|
||||
)
|
||||
@@ -359,7 +391,7 @@ public class OnlineAccountsManager {
|
||||
}
|
||||
}
|
||||
|
||||
LOGGER.debug(String.format("we have online accounts for timestamps: %s", String.join(", ", this.currentOnlineAccounts.keySet().stream().map(l -> Long.toString(l)).collect(Collectors.joining(", ")))));
|
||||
LOGGER.trace(String.format("we have online accounts for timestamps: %s", String.join(", ", this.currentOnlineAccounts.keySet().stream().map(l -> Long.toString(l)).collect(Collectors.joining(", ")))));
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -399,30 +431,7 @@ public class OnlineAccountsManager {
|
||||
}
|
||||
|
||||
/**
|
||||
* Request data from other peers. (Pre-V3)
|
||||
*/
|
||||
private void requestLegacyRemoteOnlineAccounts() {
|
||||
final Long now = NTP.getTime();
|
||||
if (now == null)
|
||||
return;
|
||||
|
||||
// Don't bother if we're not up to date
|
||||
if (!Controller.getInstance().isUpToDate())
|
||||
return;
|
||||
|
||||
List<OnlineAccountData> mergedOnlineAccounts = Set.copyOf(this.currentOnlineAccounts.values()).stream().flatMap(Set::stream).collect(Collectors.toList());
|
||||
|
||||
Message messageV2 = new GetOnlineAccountsV2Message(mergedOnlineAccounts);
|
||||
|
||||
Network.getInstance().broadcast(peer ->
|
||||
peer.getPeersVersion() < ONLINE_ACCOUNTS_V3_PEER_VERSION
|
||||
? messageV2
|
||||
: null
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Request data from other peers. V3+
|
||||
* Request data from other peers
|
||||
*/
|
||||
private void requestRemoteOnlineAccounts() {
|
||||
final Long now = NTP.getTime();
|
||||
@@ -433,13 +442,25 @@ public class OnlineAccountsManager {
|
||||
if (!Controller.getInstance().isUpToDate())
|
||||
return;
|
||||
|
||||
Message messageV3 = new GetOnlineAccountsV3Message(currentOnlineAccountsHashes);
|
||||
long onlineAccountsTimestamp = getCurrentOnlineAccountTimestamp();
|
||||
if (now - onlineAccountsTimestamp >= ONLINE_ACCOUNTS_BROADCAST_BURST_LENGTH) {
|
||||
// New online timestamp started more than 5 mins ago - we probably don't need to request so frequently
|
||||
|
||||
Network.getInstance().broadcast(peer ->
|
||||
peer.getPeersVersion() >= ONLINE_ACCOUNTS_V3_PEER_VERSION
|
||||
? messageV3
|
||||
: null
|
||||
);
|
||||
if (Controller.uptime() < ONLINE_ACCOUNTS_BROADCAST_BURST_LENGTH) {
|
||||
// The node recently started up, so we should request at the burst interval
|
||||
// This could allow accounts to move around the network more easily when an auto update is occurring
|
||||
}
|
||||
else if (now - lastOnlineAccountsRequest < ONLINE_ACCOUNTS_BROADCAST_INTERVAL) {
|
||||
// We already requested online accounts in the last minute, so no need to request again
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
LOGGER.debug("Requesting online accounts via broadcast...");
|
||||
|
||||
lastOnlineAccountsRequest = now;
|
||||
Message messageV3 = new GetOnlineAccountsV3Message(currentOnlineAccountsHashes);
|
||||
Network.getInstance().broadcast(peer -> messageV3);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -522,6 +543,8 @@ public class OnlineAccountsManager {
|
||||
Set<OnlineAccountData> onlineAccounts = this.currentOnlineAccounts.computeIfAbsent(onlineAccountsTimestamp, k -> ConcurrentHashMap.newKeySet());
|
||||
boolean alreadyExists = onlineAccounts.stream().anyMatch(a -> Arrays.equals(a.getPublicKey(), publicKey));
|
||||
if (alreadyExists) {
|
||||
this.hasOurOnlineAccounts = true;
|
||||
|
||||
if (remaining > 0) {
|
||||
// Move on to next account
|
||||
continue;
|
||||
@@ -579,17 +602,7 @@ public class OnlineAccountsManager {
|
||||
if (!hasInfoChanged)
|
||||
return false;
|
||||
|
||||
Message messageV1 = new OnlineAccountsMessage(ourOnlineAccounts);
|
||||
Message messageV2 = new OnlineAccountsV2Message(ourOnlineAccounts);
|
||||
Message messageV3 = new OnlineAccountsV3Message(ourOnlineAccounts);
|
||||
|
||||
Network.getInstance().broadcast(peer ->
|
||||
peer.getPeersVersion() >= OnlineAccountsV3Message.MIN_PEER_VERSION
|
||||
? messageV3
|
||||
: peer.getPeersVersion() >= ONLINE_ACCOUNTS_V2_PEER_VERSION
|
||||
? messageV2
|
||||
: messageV1
|
||||
);
|
||||
Network.getInstance().broadcast(peer -> new OnlineAccountsV3Message(ourOnlineAccounts));
|
||||
|
||||
LOGGER.debug("Broadcasted {} online account{} with timestamp {}", ourOnlineAccounts.size(), (ourOnlineAccounts.size() != 1 ? "s" : ""), onlineAccountsTimestamp);
|
||||
|
||||
@@ -644,7 +657,8 @@ public class OnlineAccountsManager {
|
||||
}
|
||||
|
||||
public boolean verifyMemoryPoW(OnlineAccountData onlineAccountData, Long timestamp) {
|
||||
if (!isMemoryPoWActive(timestamp)) {
|
||||
long memoryPoWStartTimestamp = BlockChain.getInstance().getOnlineAccountsMemoryPoWTimestamp();
|
||||
if (timestamp < memoryPoWStartTimestamp && onlineAccountData.getTimestamp() < memoryPoWStartTimestamp) {
|
||||
// Not active yet, so treat it as valid
|
||||
return true;
|
||||
}
|
||||
@@ -767,106 +781,6 @@ public class OnlineAccountsManager {
|
||||
|
||||
// Network handlers
|
||||
|
||||
public void onNetworkGetOnlineAccountsMessage(Peer peer, Message message) {
|
||||
GetOnlineAccountsMessage getOnlineAccountsMessage = (GetOnlineAccountsMessage) message;
|
||||
|
||||
List<OnlineAccountData> excludeAccounts = getOnlineAccountsMessage.getOnlineAccounts();
|
||||
|
||||
// Send online accounts info, excluding entries with matching timestamp & public key from excludeAccounts
|
||||
List<OnlineAccountData> accountsToSend = Set.copyOf(this.currentOnlineAccounts.values()).stream().flatMap(Set::stream).collect(Collectors.toList());
|
||||
int prefilterSize = accountsToSend.size();
|
||||
|
||||
Iterator<OnlineAccountData> iterator = accountsToSend.iterator();
|
||||
while (iterator.hasNext()) {
|
||||
OnlineAccountData onlineAccountData = iterator.next();
|
||||
|
||||
for (OnlineAccountData excludeAccountData : excludeAccounts) {
|
||||
if (onlineAccountData.getTimestamp() == excludeAccountData.getTimestamp() && Arrays.equals(onlineAccountData.getPublicKey(), excludeAccountData.getPublicKey())) {
|
||||
iterator.remove();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (accountsToSend.isEmpty())
|
||||
return;
|
||||
|
||||
Message onlineAccountsMessage = new OnlineAccountsMessage(accountsToSend);
|
||||
peer.sendMessage(onlineAccountsMessage);
|
||||
|
||||
LOGGER.debug("Sent {} of our {} online accounts to {}", accountsToSend.size(), prefilterSize, peer);
|
||||
}
|
||||
|
||||
public void onNetworkOnlineAccountsMessage(Peer peer, Message message) {
|
||||
OnlineAccountsMessage onlineAccountsMessage = (OnlineAccountsMessage) message;
|
||||
|
||||
List<OnlineAccountData> peersOnlineAccounts = onlineAccountsMessage.getOnlineAccounts();
|
||||
LOGGER.debug("Received {} online accounts from {}", peersOnlineAccounts.size(), peer);
|
||||
|
||||
int importCount = 0;
|
||||
|
||||
// Add any online accounts to the queue that aren't already present
|
||||
for (OnlineAccountData onlineAccountData : peersOnlineAccounts) {
|
||||
boolean isNewEntry = onlineAccountsImportQueue.add(onlineAccountData);
|
||||
|
||||
if (isNewEntry)
|
||||
importCount++;
|
||||
}
|
||||
|
||||
if (importCount > 0)
|
||||
LOGGER.debug("Added {} online accounts to queue", importCount);
|
||||
}
|
||||
|
||||
public void onNetworkGetOnlineAccountsV2Message(Peer peer, Message message) {
|
||||
GetOnlineAccountsV2Message getOnlineAccountsMessage = (GetOnlineAccountsV2Message) message;
|
||||
|
||||
List<OnlineAccountData> excludeAccounts = getOnlineAccountsMessage.getOnlineAccounts();
|
||||
|
||||
// Send online accounts info, excluding entries with matching timestamp & public key from excludeAccounts
|
||||
List<OnlineAccountData> accountsToSend = Set.copyOf(this.currentOnlineAccounts.values()).stream().flatMap(Set::stream).collect(Collectors.toList());
|
||||
int prefilterSize = accountsToSend.size();
|
||||
|
||||
Iterator<OnlineAccountData> iterator = accountsToSend.iterator();
|
||||
while (iterator.hasNext()) {
|
||||
OnlineAccountData onlineAccountData = iterator.next();
|
||||
|
||||
for (OnlineAccountData excludeAccountData : excludeAccounts) {
|
||||
if (onlineAccountData.getTimestamp() == excludeAccountData.getTimestamp() && Arrays.equals(onlineAccountData.getPublicKey(), excludeAccountData.getPublicKey())) {
|
||||
iterator.remove();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (accountsToSend.isEmpty())
|
||||
return;
|
||||
|
||||
Message onlineAccountsMessage = new OnlineAccountsV2Message(accountsToSend);
|
||||
peer.sendMessage(onlineAccountsMessage);
|
||||
|
||||
LOGGER.debug("Sent {} of our {} online accounts to {}", accountsToSend.size(), prefilterSize, peer);
|
||||
}
|
||||
|
||||
public void onNetworkOnlineAccountsV2Message(Peer peer, Message message) {
|
||||
OnlineAccountsV2Message onlineAccountsMessage = (OnlineAccountsV2Message) message;
|
||||
|
||||
List<OnlineAccountData> peersOnlineAccounts = onlineAccountsMessage.getOnlineAccounts();
|
||||
LOGGER.debug("Received {} online accounts from {}", peersOnlineAccounts.size(), peer);
|
||||
|
||||
int importCount = 0;
|
||||
|
||||
// Add any online accounts to the queue that aren't already present
|
||||
for (OnlineAccountData onlineAccountData : peersOnlineAccounts) {
|
||||
boolean isNewEntry = onlineAccountsImportQueue.add(onlineAccountData);
|
||||
|
||||
if (isNewEntry)
|
||||
importCount++;
|
||||
}
|
||||
|
||||
if (importCount > 0)
|
||||
LOGGER.debug("Added {} online accounts to queue", importCount);
|
||||
}
|
||||
|
||||
public void onNetworkGetOnlineAccountsV3Message(Peer peer, Message message) {
|
||||
GetOnlineAccountsV3Message getOnlineAccountsMessage = (GetOnlineAccountsV3Message) message;
|
||||
|
||||
@@ -887,7 +801,7 @@ public class OnlineAccountsManager {
|
||||
Set<OnlineAccountData> timestampsOnlineAccounts = this.currentOnlineAccounts.getOrDefault(timestamp, Collections.emptySet());
|
||||
outgoingOnlineAccounts.addAll(timestampsOnlineAccounts);
|
||||
|
||||
LOGGER.debug(() -> String.format("Going to send all %d online accounts for timestamp %d", timestampsOnlineAccounts.size(), timestamp));
|
||||
LOGGER.trace(() -> String.format("Going to send all %d online accounts for timestamp %d", timestampsOnlineAccounts.size(), timestamp));
|
||||
} else {
|
||||
// Quick cache of which leading bytes to send so we only have to filter once
|
||||
Set<Byte> outgoingLeadingBytes = new HashSet<>();
|
||||
@@ -911,7 +825,7 @@ public class OnlineAccountsManager {
|
||||
.forEach(outgoingOnlineAccounts::add);
|
||||
|
||||
if (outgoingOnlineAccounts.size() > beforeAddSize)
|
||||
LOGGER.debug(String.format("Going to send %d online accounts for timestamp %d and leading bytes %s",
|
||||
LOGGER.trace(String.format("Going to send %d online accounts for timestamp %d and leading bytes %s",
|
||||
outgoingOnlineAccounts.size() - beforeAddSize,
|
||||
timestamp,
|
||||
outgoingLeadingBytes.stream().sorted(Byte::compareUnsigned).map(leadingByte -> String.format("%02x", leadingByte)).collect(Collectors.joining(", "))
|
||||
@@ -920,25 +834,31 @@ public class OnlineAccountsManager {
|
||||
}
|
||||
}
|
||||
|
||||
peer.sendMessage(
|
||||
peer.getPeersVersion() >= OnlineAccountsV3Message.MIN_PEER_VERSION ?
|
||||
new OnlineAccountsV3Message(outgoingOnlineAccounts) :
|
||||
new OnlineAccountsV2Message(outgoingOnlineAccounts)
|
||||
);
|
||||
peer.sendMessage(new OnlineAccountsV3Message(outgoingOnlineAccounts));
|
||||
|
||||
LOGGER.debug("Sent {} online accounts to {}", outgoingOnlineAccounts.size(), peer);
|
||||
LOGGER.trace("Sent {} online accounts to {}", outgoingOnlineAccounts.size(), peer);
|
||||
}
|
||||
|
||||
public void onNetworkOnlineAccountsV3Message(Peer peer, Message message) {
|
||||
OnlineAccountsV3Message onlineAccountsMessage = (OnlineAccountsV3Message) message;
|
||||
|
||||
List<OnlineAccountData> peersOnlineAccounts = onlineAccountsMessage.getOnlineAccounts();
|
||||
LOGGER.debug("Received {} online accounts from {}", peersOnlineAccounts.size(), peer);
|
||||
LOGGER.trace("Received {} online accounts from {}", peersOnlineAccounts.size(), peer);
|
||||
|
||||
int importCount = 0;
|
||||
|
||||
// Add any online accounts to the queue that aren't already present
|
||||
for (OnlineAccountData onlineAccountData : peersOnlineAccounts) {
|
||||
|
||||
Set<OnlineAccountData> onlineAccounts = this.currentOnlineAccounts.computeIfAbsent(onlineAccountData.getTimestamp(), k -> ConcurrentHashMap.newKeySet());
|
||||
if (onlineAccounts.contains(onlineAccountData))
|
||||
// We have already validated this online account
|
||||
continue;
|
||||
|
||||
if (this.importQueueContainsExactMatch(onlineAccountData))
|
||||
// Identical online account data already present in queue
|
||||
continue;
|
||||
|
||||
boolean isNewEntry = onlineAccountsImportQueue.add(onlineAccountData);
|
||||
|
||||
if (isNewEntry)
|
||||
|
@@ -238,10 +238,10 @@ public class PirateChainWalletController extends Thread {
|
||||
if (osName.equals("Mac OS X") && osArchitecture.equals("x86_64")) {
|
||||
return "librust-macos-x86_64.dylib";
|
||||
}
|
||||
else if (osName.equals("Linux") && osArchitecture.equals("aarch64")) {
|
||||
else if ((osName.equals("Linux") || osName.equals("FreeBSD")) && osArchitecture.equals("aarch64")) {
|
||||
return "librust-linux-aarch64.so";
|
||||
}
|
||||
else if (osName.equals("Linux") && osArchitecture.equals("amd64")) {
|
||||
else if ((osName.equals("Linux") || osName.equals("FreeBSD")) && osArchitecture.equals("amd64")) {
|
||||
return "librust-linux-x86_64.so";
|
||||
}
|
||||
else if (osName.contains("Windows") && osArchitecture.equals("amd64")) {
|
||||
|
@@ -19,7 +19,6 @@ import org.qortal.block.BlockChain;
|
||||
import org.qortal.data.block.BlockData;
|
||||
import org.qortal.data.block.BlockSummaryData;
|
||||
import org.qortal.data.block.CommonBlockData;
|
||||
import org.qortal.data.network.PeerChainTipData;
|
||||
import org.qortal.data.transaction.RewardShareTransactionData;
|
||||
import org.qortal.data.transaction.TransactionData;
|
||||
import org.qortal.event.Event;
|
||||
@@ -282,7 +281,7 @@ public class Synchronizer extends Thread {
|
||||
BlockData priorChainTip = Controller.getInstance().getChainTip();
|
||||
|
||||
synchronized (this.syncLock) {
|
||||
this.syncPercent = (priorChainTip.getHeight() * 100) / peer.getChainTipData().getLastHeight();
|
||||
this.syncPercent = (priorChainTip.getHeight() * 100) / peer.getChainTipData().getHeight();
|
||||
|
||||
// Only update SysTray if we're potentially changing height
|
||||
if (this.syncPercent < 100) {
|
||||
@@ -312,7 +311,7 @@ public class Synchronizer extends Thread {
|
||||
|
||||
case INFERIOR_CHAIN: {
|
||||
// Update our list of inferior chain tips
|
||||
ByteArray inferiorChainSignature = ByteArray.wrap(peer.getChainTipData().getLastBlockSignature());
|
||||
ByteArray inferiorChainSignature = ByteArray.wrap(peer.getChainTipData().getSignature());
|
||||
if (!inferiorChainSignatures.contains(inferiorChainSignature))
|
||||
inferiorChainSignatures.add(inferiorChainSignature);
|
||||
|
||||
@@ -320,7 +319,8 @@ public class Synchronizer extends Thread {
|
||||
LOGGER.debug(() -> String.format("Refused to synchronize with peer %s (%s)", peer, syncResult.name()));
|
||||
|
||||
// Notify peer of our superior chain
|
||||
if (!peer.sendMessage(Network.getInstance().buildHeightMessage(peer, priorChainTip)))
|
||||
Message message = Network.getInstance().buildHeightOrChainTipInfo(peer);
|
||||
if (message == null || !peer.sendMessage(message))
|
||||
peer.disconnect("failed to notify peer of our superior chain");
|
||||
break;
|
||||
}
|
||||
@@ -341,7 +341,7 @@ public class Synchronizer extends Thread {
|
||||
// fall-through...
|
||||
case NOTHING_TO_DO: {
|
||||
// Update our list of inferior chain tips
|
||||
ByteArray inferiorChainSignature = ByteArray.wrap(peer.getChainTipData().getLastBlockSignature());
|
||||
ByteArray inferiorChainSignature = ByteArray.wrap(peer.getChainTipData().getSignature());
|
||||
if (!inferiorChainSignatures.contains(inferiorChainSignature))
|
||||
inferiorChainSignatures.add(inferiorChainSignature);
|
||||
|
||||
@@ -369,8 +369,7 @@ public class Synchronizer extends Thread {
|
||||
// Reset our cache of inferior chains
|
||||
inferiorChainSignatures.clear();
|
||||
|
||||
Network network = Network.getInstance();
|
||||
network.broadcast(broadcastPeer -> network.buildHeightMessage(broadcastPeer, newChainTip));
|
||||
Network.getInstance().broadcastOurChain();
|
||||
|
||||
EventBus.INSTANCE.notify(new NewChainTipEvent(priorChainTip, newChainTip));
|
||||
}
|
||||
@@ -513,13 +512,13 @@ public class Synchronizer extends Thread {
|
||||
final BlockData ourLatestBlockData = repository.getBlockRepository().getLastBlock();
|
||||
final int ourInitialHeight = ourLatestBlockData.getHeight();
|
||||
|
||||
PeerChainTipData peerChainTipData = peer.getChainTipData();
|
||||
int peerHeight = peerChainTipData.getLastHeight();
|
||||
byte[] peersLastBlockSignature = peerChainTipData.getLastBlockSignature();
|
||||
BlockSummaryData peerChainTipData = peer.getChainTipData();
|
||||
int peerHeight = peerChainTipData.getHeight();
|
||||
byte[] peersLastBlockSignature = peerChainTipData.getSignature();
|
||||
|
||||
byte[] ourLastBlockSignature = ourLatestBlockData.getSignature();
|
||||
LOGGER.debug(String.format("Fetching summaries from peer %s at height %d, sig %.8s, ts %d; our height %d, sig %.8s, ts %d", peer,
|
||||
peerHeight, Base58.encode(peersLastBlockSignature), peer.getChainTipData().getLastBlockTimestamp(),
|
||||
peerHeight, Base58.encode(peersLastBlockSignature), peerChainTipData.getTimestamp(),
|
||||
ourInitialHeight, Base58.encode(ourLastBlockSignature), ourLatestBlockData.getTimestamp()));
|
||||
|
||||
List<BlockSummaryData> peerBlockSummaries = new ArrayList<>();
|
||||
@@ -637,9 +636,9 @@ public class Synchronizer extends Thread {
|
||||
return peers;
|
||||
|
||||
// Count the number of blocks this peer has beyond our common block
|
||||
final PeerChainTipData peerChainTipData = peer.getChainTipData();
|
||||
final int peerHeight = peerChainTipData.getLastHeight();
|
||||
final byte[] peerLastBlockSignature = peerChainTipData.getLastBlockSignature();
|
||||
final BlockSummaryData peerChainTipData = peer.getChainTipData();
|
||||
final int peerHeight = peerChainTipData.getHeight();
|
||||
final byte[] peerLastBlockSignature = peerChainTipData.getSignature();
|
||||
final int peerAdditionalBlocksAfterCommonBlock = peerHeight - commonBlockSummary.getHeight();
|
||||
// Limit the number of blocks we are comparing. FUTURE: we could request more in batches, but there may not be a case when this is needed
|
||||
int summariesRequired = Math.min(peerAdditionalBlocksAfterCommonBlock, MAXIMUM_REQUEST_SIZE);
|
||||
@@ -727,8 +726,9 @@ public class Synchronizer extends Thread {
|
||||
|
||||
LOGGER.debug(String.format("Listing peers with common block %.8s...", Base58.encode(commonBlockSummary.getSignature())));
|
||||
for (Peer peer : peersSharingCommonBlock) {
|
||||
final int peerHeight = peer.getChainTipData().getLastHeight();
|
||||
final Long peerLastBlockTimestamp = peer.getChainTipData().getLastBlockTimestamp();
|
||||
BlockSummaryData peerChainTipData = peer.getChainTipData();
|
||||
final int peerHeight = peerChainTipData.getHeight();
|
||||
final Long peerLastBlockTimestamp = peerChainTipData.getTimestamp();
|
||||
final int peerAdditionalBlocksAfterCommonBlock = peerHeight - commonBlockSummary.getHeight();
|
||||
final CommonBlockData peerCommonBlockData = peer.getCommonBlockData();
|
||||
|
||||
@@ -825,7 +825,7 @@ public class Synchronizer extends Thread {
|
||||
// Calculate the length of the shortest peer chain sharing this common block
|
||||
int minChainLength = 0;
|
||||
for (Peer peer : peersSharingCommonBlock) {
|
||||
final int peerHeight = peer.getChainTipData().getLastHeight();
|
||||
final int peerHeight = peer.getChainTipData().getHeight();
|
||||
final int peerAdditionalBlocksAfterCommonBlock = peerHeight - commonBlockSummary.getHeight();
|
||||
|
||||
if (peerAdditionalBlocksAfterCommonBlock < minChainLength || minChainLength == 0)
|
||||
@@ -933,13 +933,13 @@ public class Synchronizer extends Thread {
|
||||
final BlockData ourLatestBlockData = repository.getBlockRepository().getLastBlock();
|
||||
final int ourInitialHeight = ourLatestBlockData.getHeight();
|
||||
|
||||
PeerChainTipData peerChainTipData = peer.getChainTipData();
|
||||
int peerHeight = peerChainTipData.getLastHeight();
|
||||
byte[] peersLastBlockSignature = peerChainTipData.getLastBlockSignature();
|
||||
BlockSummaryData peerChainTipData = peer.getChainTipData();
|
||||
int peerHeight = peerChainTipData.getHeight();
|
||||
byte[] peersLastBlockSignature = peerChainTipData.getSignature();
|
||||
|
||||
byte[] ourLastBlockSignature = ourLatestBlockData.getSignature();
|
||||
String syncString = String.format("Synchronizing with peer %s at height %d, sig %.8s, ts %d; our height %d, sig %.8s, ts %d", peer,
|
||||
peerHeight, Base58.encode(peersLastBlockSignature), peer.getChainTipData().getLastBlockTimestamp(),
|
||||
peerHeight, Base58.encode(peersLastBlockSignature), peerChainTipData.getTimestamp(),
|
||||
ourInitialHeight, Base58.encode(ourLastBlockSignature), ourLatestBlockData.getTimestamp());
|
||||
LOGGER.info(syncString);
|
||||
|
||||
@@ -1313,7 +1313,7 @@ public class Synchronizer extends Thread {
|
||||
// Final check to make sure the peer isn't out of date (except for when we're in recovery mode)
|
||||
if (!recoveryMode && peer.getChainTipData() != null) {
|
||||
final Long minLatestBlockTimestamp = Controller.getMinimumLatestBlockTimestamp();
|
||||
final Long peerLastBlockTimestamp = peer.getChainTipData().getLastBlockTimestamp();
|
||||
final Long peerLastBlockTimestamp = peer.getChainTipData().getTimestamp();
|
||||
if (peerLastBlockTimestamp == null || peerLastBlockTimestamp < minLatestBlockTimestamp) {
|
||||
LOGGER.info(String.format("Peer %s is out of date, so abandoning sync attempt", peer));
|
||||
return SynchronizationResult.CHAIN_TIP_TOO_OLD;
|
||||
@@ -1553,12 +1553,19 @@ public class Synchronizer extends Thread {
|
||||
Message getBlockSummariesMessage = new GetBlockSummariesMessage(parentSignature, numberRequested);
|
||||
|
||||
Message message = peer.getResponse(getBlockSummariesMessage);
|
||||
if (message == null || message.getType() != MessageType.BLOCK_SUMMARIES)
|
||||
if (message == null)
|
||||
return null;
|
||||
|
||||
BlockSummariesMessage blockSummariesMessage = (BlockSummariesMessage) message;
|
||||
if (message.getType() == MessageType.BLOCK_SUMMARIES) {
|
||||
BlockSummariesMessage blockSummariesMessage = (BlockSummariesMessage) message;
|
||||
return blockSummariesMessage.getBlockSummaries();
|
||||
}
|
||||
else if (message.getType() == MessageType.BLOCK_SUMMARIES_V2) {
|
||||
BlockSummariesV2Message blockSummariesMessage = (BlockSummariesV2Message) message;
|
||||
return blockSummariesMessage.getBlockSummaries();
|
||||
}
|
||||
|
||||
return blockSummariesMessage.getBlockSummaries();
|
||||
return null;
|
||||
}
|
||||
|
||||
private List<byte[]> getBlockSignatures(Peer peer, byte[] parentSignature, int numberRequested) throws InterruptedException {
|
||||
|
@@ -595,9 +595,10 @@ public class ArbitraryDataFileManager extends Thread {
|
||||
// Send valid, yet unexpected message type in response, so peer's synchronizer doesn't have to wait for timeout
|
||||
LOGGER.debug(String.format("Sending 'file unknown' response to peer %s for GET_FILE request for unknown file %s", peer, arbitraryDataFile));
|
||||
|
||||
// We'll send empty block summaries message as it's very short
|
||||
// TODO: use a different message type here
|
||||
Message fileUnknownMessage = new BlockSummariesMessage(Collections.emptyList());
|
||||
// Send generic 'unknown' message as it's very short
|
||||
Message fileUnknownMessage = peer.getPeersVersion() >= GenericUnknownMessage.MINIMUM_PEER_VERSION
|
||||
? new GenericUnknownMessage()
|
||||
: new BlockSummariesMessage(Collections.emptyList());
|
||||
fileUnknownMessage.setId(message.getId());
|
||||
if (!peer.sendMessage(fileUnknownMessage)) {
|
||||
LOGGER.debug("Couldn't sent file-unknown response");
|
||||
|
@@ -523,7 +523,7 @@ public class PirateChainACCTv3TradeBot implements AcctTradeBot {
|
||||
long p2shFee = PirateChain.getInstance().getP2shFee(feeTimestamp);
|
||||
final long minimumAmountA = tradeBotData.getForeignAmount() + p2shFee;
|
||||
|
||||
PirateChainHTLC.Status htlcStatusA = PirateChainHTLC.determineHtlcStatus(pirateChain.getBlockchainProvider(), p2shAddress, minimumAmountA);
|
||||
BitcoinyHTLC.Status htlcStatusA = PirateChainHTLC.determineHtlcStatus(pirateChain.getBlockchainProvider(), p2shAddress, minimumAmountA);
|
||||
|
||||
switch (htlcStatusA) {
|
||||
case UNFUNDED:
|
||||
@@ -613,7 +613,7 @@ public class PirateChainACCTv3TradeBot implements AcctTradeBot {
|
||||
long p2shFee = PirateChain.getInstance().getP2shFee(feeTimestamp);
|
||||
long minimumAmountA = crossChainTradeData.expectedForeignAmount + p2shFee;
|
||||
|
||||
PirateChainHTLC.Status htlcStatusA = PirateChainHTLC.determineHtlcStatus(pirateChain.getBlockchainProvider(), p2shAddress, minimumAmountA);
|
||||
BitcoinyHTLC.Status htlcStatusA = PirateChainHTLC.determineHtlcStatus(pirateChain.getBlockchainProvider(), p2shAddress, minimumAmountA);
|
||||
|
||||
switch (htlcStatusA) {
|
||||
case UNFUNDED:
|
||||
@@ -751,7 +751,7 @@ public class PirateChainACCTv3TradeBot implements AcctTradeBot {
|
||||
long minimumAmountA = crossChainTradeData.expectedForeignAmount + p2shFee;
|
||||
String receivingAddress = Bech32.encode("zs", receivingAccountInfo);
|
||||
|
||||
PirateChainHTLC.Status htlcStatusA = PirateChainHTLC.determineHtlcStatus(pirateChain.getBlockchainProvider(), p2shAddress, minimumAmountA);
|
||||
BitcoinyHTLC.Status htlcStatusA = PirateChainHTLC.determineHtlcStatus(pirateChain.getBlockchainProvider(), p2shAddress, minimumAmountA);
|
||||
|
||||
switch (htlcStatusA) {
|
||||
case UNFUNDED:
|
||||
@@ -822,7 +822,7 @@ public class PirateChainACCTv3TradeBot implements AcctTradeBot {
|
||||
long feeTimestamp = calcFeeTimestamp(lockTimeA, crossChainTradeData.tradeTimeout);
|
||||
long p2shFee = PirateChain.getInstance().getP2shFee(feeTimestamp);
|
||||
long minimumAmountA = crossChainTradeData.expectedForeignAmount + p2shFee;
|
||||
PirateChainHTLC.Status htlcStatusA = PirateChainHTLC.determineHtlcStatus(pirateChain.getBlockchainProvider(), p2shAddress, minimumAmountA);
|
||||
BitcoinyHTLC.Status htlcStatusA = PirateChainHTLC.determineHtlcStatus(pirateChain.getBlockchainProvider(), p2shAddress, minimumAmountA);
|
||||
|
||||
switch (htlcStatusA) {
|
||||
case UNFUNDED:
|
||||
|
@@ -4,6 +4,12 @@ import cash.z.wallet.sdk.rpc.CompactFormats;
|
||||
import com.google.common.hash.HashCode;
|
||||
import com.rust.litewalletjni.LiteWalletJni;
|
||||
import org.bitcoinj.core.*;
|
||||
import org.bitcoinj.crypto.ChildNumber;
|
||||
import org.bitcoinj.crypto.DeterministicKey;
|
||||
import org.bitcoinj.script.Script;
|
||||
import org.bitcoinj.script.ScriptBuilder;
|
||||
import org.bitcoinj.wallet.DeterministicKeyChain;
|
||||
import org.bitcoinj.wallet.Wallet;
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
@@ -352,6 +358,12 @@ public class PirateChain extends Bitcoiny {
|
||||
}
|
||||
}
|
||||
|
||||
public String getUnusedReceiveAddress(String key58) throws ForeignBlockchainException {
|
||||
// For now, return the main wallet address
|
||||
// FUTURE: generate an unused one
|
||||
return this.getWalletAddress(key58);
|
||||
}
|
||||
|
||||
public String sendCoins(PirateChainSendRequest pirateChainSendRequest) throws ForeignBlockchainException {
|
||||
PirateChainWalletController walletController = PirateChainWalletController.getInstance();
|
||||
walletController.initWithEntropy58(pirateChainSendRequest.entropy58);
|
||||
|
@@ -3,25 +3,17 @@ package org.qortal.crosschain;
|
||||
import com.google.common.hash.HashCode;
|
||||
import com.google.common.primitives.Bytes;
|
||||
import org.bitcoinj.core.*;
|
||||
import org.bitcoinj.core.Transaction.SigHash;
|
||||
import org.bitcoinj.crypto.TransactionSignature;
|
||||
import org.bitcoinj.script.Script;
|
||||
import org.bitcoinj.script.ScriptBuilder;
|
||||
import org.bitcoinj.script.ScriptChunk;
|
||||
import org.bitcoinj.script.ScriptOpCodes;
|
||||
import org.qortal.crypto.Crypto;
|
||||
import org.qortal.utils.Base58;
|
||||
import org.qortal.utils.BitTwiddling;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.function.Function;
|
||||
import static org.qortal.crosschain.BitcoinyHTLC.Status;
|
||||
|
||||
public class PirateChainHTLC {
|
||||
|
||||
public enum Status {
|
||||
UNFUNDED, FUNDING_IN_PROGRESS, FUNDED, REDEEM_IN_PROGRESS, REDEEMED, REFUND_IN_PROGRESS, REFUNDED
|
||||
}
|
||||
|
||||
public static final int SECRET_LENGTH = 32;
|
||||
public static final int MIN_LOCKTIME = 1500000000;
|
||||
|
||||
|
@@ -24,7 +24,10 @@ public class ArbitraryResourceMetadata {
|
||||
this.description = description;
|
||||
this.tags = tags;
|
||||
this.category = category;
|
||||
this.categoryName = category.getName();
|
||||
|
||||
if (category != null) {
|
||||
this.categoryName = category.getName();
|
||||
}
|
||||
}
|
||||
|
||||
public static ArbitraryResourceMetadata fromTransactionMetadata(ArbitraryDataTransactionMetadata transactionMetadata) {
|
||||
|
@@ -11,11 +11,12 @@ public class BlockSummaryData {
|
||||
private int height;
|
||||
private byte[] signature;
|
||||
private byte[] minterPublicKey;
|
||||
private int onlineAccountsCount;
|
||||
|
||||
// Optional, set during construction
|
||||
private Integer onlineAccountsCount;
|
||||
private Long timestamp;
|
||||
private Integer transactionCount;
|
||||
private byte[] reference;
|
||||
|
||||
// Optional, set after construction
|
||||
private Integer minterLevel;
|
||||
@@ -25,6 +26,15 @@ public class BlockSummaryData {
|
||||
protected BlockSummaryData() {
|
||||
}
|
||||
|
||||
/** Constructor typically populated with fields from HeightV2Message */
|
||||
public BlockSummaryData(int height, byte[] signature, byte[] minterPublicKey, long timestamp) {
|
||||
this.height = height;
|
||||
this.signature = signature;
|
||||
this.minterPublicKey = minterPublicKey;
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
|
||||
/** Constructor typically populated with fields from BlockSummariesMessage */
|
||||
public BlockSummaryData(int height, byte[] signature, byte[] minterPublicKey, int onlineAccountsCount) {
|
||||
this.height = height;
|
||||
this.signature = signature;
|
||||
@@ -32,13 +42,16 @@ public class BlockSummaryData {
|
||||
this.onlineAccountsCount = onlineAccountsCount;
|
||||
}
|
||||
|
||||
public BlockSummaryData(int height, byte[] signature, byte[] minterPublicKey, int onlineAccountsCount, long timestamp, int transactionCount) {
|
||||
/** Constructor typically populated with fields from BlockSummariesV2Message */
|
||||
public BlockSummaryData(int height, byte[] signature, byte[] minterPublicKey, Integer onlineAccountsCount,
|
||||
Long timestamp, Integer transactionCount, byte[] reference) {
|
||||
this.height = height;
|
||||
this.signature = signature;
|
||||
this.minterPublicKey = minterPublicKey;
|
||||
this.onlineAccountsCount = onlineAccountsCount;
|
||||
this.timestamp = timestamp;
|
||||
this.transactionCount = transactionCount;
|
||||
this.reference = reference;
|
||||
}
|
||||
|
||||
public BlockSummaryData(BlockData blockData) {
|
||||
@@ -49,6 +62,7 @@ public class BlockSummaryData {
|
||||
|
||||
this.timestamp = blockData.getTimestamp();
|
||||
this.transactionCount = blockData.getTransactionCount();
|
||||
this.reference = blockData.getReference();
|
||||
}
|
||||
|
||||
// Getters / setters
|
||||
@@ -65,7 +79,7 @@ public class BlockSummaryData {
|
||||
return this.minterPublicKey;
|
||||
}
|
||||
|
||||
public int getOnlineAccountsCount() {
|
||||
public Integer getOnlineAccountsCount() {
|
||||
return this.onlineAccountsCount;
|
||||
}
|
||||
|
||||
@@ -77,6 +91,10 @@ public class BlockSummaryData {
|
||||
return this.transactionCount;
|
||||
}
|
||||
|
||||
public byte[] getReference() {
|
||||
return this.reference;
|
||||
}
|
||||
|
||||
public Integer getMinterLevel() {
|
||||
return this.minterLevel;
|
||||
}
|
||||
|
@@ -1,7 +1,5 @@
|
||||
package org.qortal.data.block;
|
||||
|
||||
import org.qortal.data.network.PeerChainTipData;
|
||||
|
||||
import javax.xml.bind.annotation.XmlAccessType;
|
||||
import javax.xml.bind.annotation.XmlAccessorType;
|
||||
import java.math.BigInteger;
|
||||
@@ -14,14 +12,14 @@ public class CommonBlockData {
|
||||
private BlockSummaryData commonBlockSummary = null;
|
||||
private List<BlockSummaryData> blockSummariesAfterCommonBlock = null;
|
||||
private BigInteger chainWeight = null;
|
||||
private PeerChainTipData chainTipData = null;
|
||||
private BlockSummaryData chainTipData = null;
|
||||
|
||||
// Constructors
|
||||
|
||||
protected CommonBlockData() {
|
||||
}
|
||||
|
||||
public CommonBlockData(BlockSummaryData commonBlockSummary, PeerChainTipData chainTipData) {
|
||||
public CommonBlockData(BlockSummaryData commonBlockSummary, BlockSummaryData chainTipData) {
|
||||
this.commonBlockSummary = commonBlockSummary;
|
||||
this.chainTipData = chainTipData;
|
||||
}
|
||||
@@ -49,7 +47,7 @@ public class CommonBlockData {
|
||||
this.chainWeight = chainWeight;
|
||||
}
|
||||
|
||||
public PeerChainTipData getChainTipData() {
|
||||
public BlockSummaryData getChainTipData() {
|
||||
return this.chainTipData;
|
||||
}
|
||||
|
||||
|
@@ -1,37 +0,0 @@
|
||||
package org.qortal.data.network;
|
||||
|
||||
public class PeerChainTipData {
|
||||
|
||||
/** Latest block height as reported by peer. */
|
||||
private Integer lastHeight;
|
||||
/** Latest block signature as reported by peer. */
|
||||
private byte[] lastBlockSignature;
|
||||
/** Latest block timestamp as reported by peer. */
|
||||
private Long lastBlockTimestamp;
|
||||
/** Latest block minter public key as reported by peer. */
|
||||
private byte[] lastBlockMinter;
|
||||
|
||||
public PeerChainTipData(Integer lastHeight, byte[] lastBlockSignature, Long lastBlockTimestamp, byte[] lastBlockMinter) {
|
||||
this.lastHeight = lastHeight;
|
||||
this.lastBlockSignature = lastBlockSignature;
|
||||
this.lastBlockTimestamp = lastBlockTimestamp;
|
||||
this.lastBlockMinter = lastBlockMinter;
|
||||
}
|
||||
|
||||
public Integer getLastHeight() {
|
||||
return this.lastHeight;
|
||||
}
|
||||
|
||||
public byte[] getLastBlockSignature() {
|
||||
return this.lastBlockSignature;
|
||||
}
|
||||
|
||||
public Long getLastBlockTimestamp() {
|
||||
return this.lastBlockTimestamp;
|
||||
}
|
||||
|
||||
public byte[] getLastBlockMinter() {
|
||||
return this.lastBlockMinter;
|
||||
}
|
||||
|
||||
}
|
@@ -11,6 +11,7 @@ import org.qortal.controller.arbitrary.ArbitraryDataFileListManager;
|
||||
import org.qortal.controller.arbitrary.ArbitraryDataManager;
|
||||
import org.qortal.crypto.Crypto;
|
||||
import org.qortal.data.block.BlockData;
|
||||
import org.qortal.data.block.BlockSummaryData;
|
||||
import org.qortal.data.network.PeerData;
|
||||
import org.qortal.data.transaction.TransactionData;
|
||||
import org.qortal.network.message.*;
|
||||
@@ -90,6 +91,8 @@ public class Network {
|
||||
|
||||
private static final long DISCONNECTION_CHECK_INTERVAL = 10 * 1000L; // milliseconds
|
||||
|
||||
private static final int BROADCAST_CHAIN_TIP_DEPTH = 7; // Just enough to fill a SINGLE TCP packet (~1440 bytes)
|
||||
|
||||
// Generate our node keys / ID
|
||||
private final Ed25519PrivateKeyParameters edPrivateKeyParams = new Ed25519PrivateKeyParameters(new SecureRandom());
|
||||
private final Ed25519PublicKeyParameters edPublicKeyParams = edPrivateKeyParams.generatePublicKey();
|
||||
@@ -1087,10 +1090,16 @@ public class Network {
|
||||
|
||||
if (peer.isOutbound()) {
|
||||
if (!Settings.getInstance().isLite()) {
|
||||
// Send our height
|
||||
Message heightMessage = buildHeightMessage(peer, Controller.getInstance().getChainTip());
|
||||
if (!peer.sendMessage(heightMessage)) {
|
||||
peer.disconnect("failed to send height/info");
|
||||
// Send our height / chain tip info
|
||||
Message message = this.buildHeightOrChainTipInfo(peer);
|
||||
|
||||
if (message == null) {
|
||||
peer.disconnect("Couldn't build our chain tip info");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!peer.sendMessage(message)) {
|
||||
peer.disconnect("failed to send height / chain tip info");
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -1164,10 +1173,47 @@ public class Network {
|
||||
return new PeersV2Message(peerAddresses);
|
||||
}
|
||||
|
||||
public Message buildHeightMessage(Peer peer, BlockData blockData) {
|
||||
// HEIGHT_V2 contains way more useful info
|
||||
return new HeightV2Message(blockData.getHeight(), blockData.getSignature(),
|
||||
blockData.getTimestamp(), blockData.getMinterPublicKey());
|
||||
/** Builds either (legacy) HeightV2Message or (newer) BlockSummariesV2Message, depending on peer version.
|
||||
*
|
||||
* @return Message, or null if DataException was thrown.
|
||||
*/
|
||||
public Message buildHeightOrChainTipInfo(Peer peer) {
|
||||
if (peer.getPeersVersion() >= BlockSummariesV2Message.MINIMUM_PEER_VERSION) {
|
||||
int latestHeight = Controller.getInstance().getChainHeight();
|
||||
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
List<BlockSummaryData> latestBlockSummaries = repository.getBlockRepository().getBlockSummaries(latestHeight - BROADCAST_CHAIN_TIP_DEPTH, latestHeight);
|
||||
return new BlockSummariesV2Message(latestBlockSummaries);
|
||||
} catch (DataException e) {
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
// For older peers
|
||||
BlockData latestBlockData = Controller.getInstance().getChainTip();
|
||||
return new HeightV2Message(latestBlockData.getHeight(), latestBlockData.getSignature(),
|
||||
latestBlockData.getTimestamp(), latestBlockData.getMinterPublicKey());
|
||||
}
|
||||
}
|
||||
|
||||
public void broadcastOurChain() {
|
||||
BlockData latestBlockData = Controller.getInstance().getChainTip();
|
||||
int latestHeight = latestBlockData.getHeight();
|
||||
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
List<BlockSummaryData> latestBlockSummaries = repository.getBlockRepository().getBlockSummaries(latestHeight - BROADCAST_CHAIN_TIP_DEPTH, latestHeight);
|
||||
Message latestBlockSummariesMessage = new BlockSummariesV2Message(latestBlockSummaries);
|
||||
|
||||
// For older peers
|
||||
Message heightMessage = new HeightV2Message(latestBlockData.getHeight(), latestBlockData.getSignature(),
|
||||
latestBlockData.getTimestamp(), latestBlockData.getMinterPublicKey());
|
||||
|
||||
Network.getInstance().broadcast(broadcastPeer -> broadcastPeer.getPeersVersion() >= BlockSummariesV2Message.MINIMUM_PEER_VERSION
|
||||
? latestBlockSummariesMessage
|
||||
: heightMessage
|
||||
);
|
||||
} catch (DataException e) {
|
||||
LOGGER.warn("Couldn't broadcast our chain tip info", e);
|
||||
}
|
||||
}
|
||||
|
||||
public Message buildNewTransactionMessage(Peer peer, TransactionData transactionData) {
|
||||
|
@@ -6,8 +6,8 @@ import com.google.common.net.InetAddresses;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.qortal.controller.Controller;
|
||||
import org.qortal.data.block.BlockSummaryData;
|
||||
import org.qortal.data.block.CommonBlockData;
|
||||
import org.qortal.data.network.PeerChainTipData;
|
||||
import org.qortal.data.network.PeerData;
|
||||
import org.qortal.network.message.ChallengeMessage;
|
||||
import org.qortal.network.message.Message;
|
||||
@@ -148,7 +148,7 @@ public class Peer {
|
||||
/**
|
||||
* Latest block info as reported by peer.
|
||||
*/
|
||||
private PeerChainTipData peersChainTipData;
|
||||
private List<BlockSummaryData> peersChainTipData = Collections.emptyList();
|
||||
|
||||
/**
|
||||
* Our common block with this peer
|
||||
@@ -353,28 +353,34 @@ public class Peer {
|
||||
}
|
||||
}
|
||||
|
||||
public PeerChainTipData getChainTipData() {
|
||||
synchronized (this.peerInfoLock) {
|
||||
return this.peersChainTipData;
|
||||
}
|
||||
public BlockSummaryData getChainTipData() {
|
||||
List<BlockSummaryData> chainTipSummaries = this.peersChainTipData;
|
||||
|
||||
if (chainTipSummaries.isEmpty())
|
||||
return null;
|
||||
|
||||
// Return last entry, which should have greatest height
|
||||
return chainTipSummaries.get(chainTipSummaries.size() - 1);
|
||||
}
|
||||
|
||||
public void setChainTipData(PeerChainTipData chainTipData) {
|
||||
synchronized (this.peerInfoLock) {
|
||||
this.peersChainTipData = chainTipData;
|
||||
}
|
||||
public void setChainTipData(BlockSummaryData chainTipData) {
|
||||
this.peersChainTipData = Collections.singletonList(chainTipData);
|
||||
}
|
||||
|
||||
public List<BlockSummaryData> getChainTipSummaries() {
|
||||
return this.peersChainTipData;
|
||||
}
|
||||
|
||||
public void setChainTipSummaries(List<BlockSummaryData> chainTipSummaries) {
|
||||
this.peersChainTipData = List.copyOf(chainTipSummaries);
|
||||
}
|
||||
|
||||
public CommonBlockData getCommonBlockData() {
|
||||
synchronized (this.peerInfoLock) {
|
||||
return this.commonBlockData;
|
||||
}
|
||||
return this.commonBlockData;
|
||||
}
|
||||
|
||||
public void setCommonBlockData(CommonBlockData commonBlockData) {
|
||||
synchronized (this.peerInfoLock) {
|
||||
this.commonBlockData = commonBlockData;
|
||||
}
|
||||
this.commonBlockData = commonBlockData;
|
||||
}
|
||||
|
||||
public boolean isSyncInProgress() {
|
||||
@@ -904,20 +910,22 @@ public class Peer {
|
||||
// Common block data
|
||||
|
||||
public boolean canUseCachedCommonBlockData() {
|
||||
PeerChainTipData peerChainTipData = this.getChainTipData();
|
||||
CommonBlockData commonBlockData = this.getCommonBlockData();
|
||||
BlockSummaryData peerChainTipData = this.getChainTipData();
|
||||
if (peerChainTipData == null || peerChainTipData.getSignature() == null)
|
||||
return false;
|
||||
|
||||
if (peerChainTipData != null && commonBlockData != null) {
|
||||
PeerChainTipData commonBlockChainTipData = commonBlockData.getChainTipData();
|
||||
if (peerChainTipData.getLastBlockSignature() != null && commonBlockChainTipData != null
|
||||
&& commonBlockChainTipData.getLastBlockSignature() != null) {
|
||||
if (Arrays.equals(peerChainTipData.getLastBlockSignature(),
|
||||
commonBlockChainTipData.getLastBlockSignature())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
CommonBlockData commonBlockData = this.getCommonBlockData();
|
||||
if (commonBlockData == null)
|
||||
return false;
|
||||
|
||||
BlockSummaryData commonBlockChainTipData = commonBlockData.getChainTipData();
|
||||
if (commonBlockChainTipData == null || commonBlockChainTipData.getSignature() == null)
|
||||
return false;
|
||||
|
||||
if (!Arrays.equals(peerChainTipData.getSignature(), commonBlockChainTipData.getSignature()))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
@@ -0,0 +1,104 @@
|
||||
package org.qortal.network.message;
|
||||
|
||||
import com.google.common.primitives.Ints;
|
||||
import com.google.common.primitives.Longs;
|
||||
import org.qortal.data.block.BlockSummaryData;
|
||||
import org.qortal.transform.Transformer;
|
||||
import org.qortal.transform.block.BlockTransformer;
|
||||
|
||||
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 BlockSummariesV2Message extends Message {
|
||||
|
||||
public static final long MINIMUM_PEER_VERSION = 0x0300060001L;
|
||||
|
||||
private static final int BLOCK_SUMMARY_V2_LENGTH = BlockTransformer.BLOCK_SIGNATURE_LENGTH /* block signature */
|
||||
+ Transformer.PUBLIC_KEY_LENGTH /* minter public key */
|
||||
+ Transformer.INT_LENGTH /* online accounts count */
|
||||
+ Transformer.LONG_LENGTH /* block timestamp */
|
||||
+ Transformer.INT_LENGTH /* transactions count */
|
||||
+ BlockTransformer.BLOCK_SIGNATURE_LENGTH; /* block reference */
|
||||
|
||||
private List<BlockSummaryData> blockSummaries;
|
||||
|
||||
public BlockSummariesV2Message(List<BlockSummaryData> blockSummaries) {
|
||||
super(MessageType.BLOCK_SUMMARIES_V2);
|
||||
|
||||
// Shortcut for when there are no summaries
|
||||
if (blockSummaries.isEmpty()) {
|
||||
this.dataBytes = Message.EMPTY_DATA_BYTES;
|
||||
return;
|
||||
}
|
||||
|
||||
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
|
||||
|
||||
try {
|
||||
// First summary's height
|
||||
bytes.write(Ints.toByteArray(blockSummaries.get(0).getHeight()));
|
||||
|
||||
for (BlockSummaryData blockSummary : blockSummaries) {
|
||||
bytes.write(blockSummary.getSignature());
|
||||
bytes.write(blockSummary.getMinterPublicKey());
|
||||
bytes.write(Ints.toByteArray(blockSummary.getOnlineAccountsCount()));
|
||||
bytes.write(Longs.toByteArray(blockSummary.getTimestamp()));
|
||||
bytes.write(Ints.toByteArray(blockSummary.getTransactionCount()));
|
||||
bytes.write(blockSummary.getReference());
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new AssertionError("IOException shouldn't occur with ByteArrayOutputStream");
|
||||
}
|
||||
|
||||
this.dataBytes = bytes.toByteArray();
|
||||
this.checksumBytes = Message.generateChecksum(this.dataBytes);
|
||||
}
|
||||
|
||||
private BlockSummariesV2Message(int id, List<BlockSummaryData> blockSummaries) {
|
||||
super(id, MessageType.BLOCK_SUMMARIES_V2);
|
||||
|
||||
this.blockSummaries = blockSummaries;
|
||||
}
|
||||
|
||||
public List<BlockSummaryData> getBlockSummaries() {
|
||||
return this.blockSummaries;
|
||||
}
|
||||
|
||||
public static Message fromByteBuffer(int id, ByteBuffer bytes) {
|
||||
int height = bytes.getInt();
|
||||
|
||||
// Expecting bytes remaining to be exact multiples of BLOCK_SUMMARY_V2_LENGTH
|
||||
if (bytes.remaining() % BLOCK_SUMMARY_V2_LENGTH != 0)
|
||||
throw new BufferUnderflowException();
|
||||
|
||||
List<BlockSummaryData> blockSummaries = new ArrayList<>();
|
||||
while (bytes.hasRemaining()) {
|
||||
byte[] signature = new byte[BlockTransformer.BLOCK_SIGNATURE_LENGTH];
|
||||
bytes.get(signature);
|
||||
|
||||
byte[] minterPublicKey = new byte[Transformer.PUBLIC_KEY_LENGTH];
|
||||
bytes.get(minterPublicKey);
|
||||
|
||||
int onlineAccountsCount = bytes.getInt();
|
||||
|
||||
long timestamp = bytes.getLong();
|
||||
|
||||
int transactionsCount = bytes.getInt();
|
||||
|
||||
byte[] reference = new byte[BlockTransformer.BLOCK_SIGNATURE_LENGTH];
|
||||
bytes.get(reference);
|
||||
|
||||
BlockSummaryData blockSummary = new BlockSummaryData(height, signature, minterPublicKey,
|
||||
onlineAccountsCount, timestamp, transactionsCount, reference);
|
||||
blockSummaries.add(blockSummary);
|
||||
|
||||
height++;
|
||||
}
|
||||
|
||||
return new BlockSummariesV2Message(id, blockSummaries);
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,23 @@
|
||||
package org.qortal.network.message;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
public class GenericUnknownMessage extends Message {
|
||||
|
||||
public static final long MINIMUM_PEER_VERSION = 0x03000400cbL;
|
||||
|
||||
public GenericUnknownMessage() {
|
||||
super(MessageType.GENERIC_UNKNOWN);
|
||||
|
||||
this.dataBytes = EMPTY_DATA_BYTES;
|
||||
}
|
||||
|
||||
private GenericUnknownMessage(int id) {
|
||||
super(id, MessageType.GENERIC_UNKNOWN);
|
||||
}
|
||||
|
||||
public static Message fromByteBuffer(int id, ByteBuffer bytes) {
|
||||
return new GenericUnknownMessage(id);
|
||||
}
|
||||
|
||||
}
|
@@ -21,6 +21,7 @@ public enum MessageType {
|
||||
HEIGHT_V2(10, HeightV2Message::fromByteBuffer),
|
||||
PING(11, PingMessage::fromByteBuffer),
|
||||
PONG(12, PongMessage::fromByteBuffer),
|
||||
GENERIC_UNKNOWN(13, GenericUnknownMessage::fromByteBuffer),
|
||||
|
||||
// Requesting data
|
||||
PEERS_V2(20, PeersV2Message::fromByteBuffer),
|
||||
@@ -41,6 +42,7 @@ public enum MessageType {
|
||||
|
||||
BLOCK_SUMMARIES(70, BlockSummariesMessage::fromByteBuffer),
|
||||
GET_BLOCK_SUMMARIES(71, GetBlockSummariesMessage::fromByteBuffer),
|
||||
BLOCK_SUMMARIES_V2(72, BlockSummariesV2Message::fromByteBuffer),
|
||||
|
||||
ONLINE_ACCOUNTS(80, OnlineAccountsMessage::fromByteBuffer),
|
||||
GET_ONLINE_ACCOUNTS(81, GetOnlineAccountsMessage::fromByteBuffer),
|
||||
|
@@ -20,7 +20,7 @@ import java.util.Map;
|
||||
*/
|
||||
public class OnlineAccountsV3Message extends Message {
|
||||
|
||||
public static final long MIN_PEER_VERSION = 0x300050001L; // 3.5.1
|
||||
public static final long MIN_PEER_VERSION = 0x300060000L; // 3.6.0
|
||||
|
||||
private List<OnlineAccountData> onlineAccounts;
|
||||
|
||||
|
@@ -143,13 +143,17 @@ public class HSQLDBBlockArchiveRepository implements BlockArchiveRepository {
|
||||
byte[] blockMinterPublicKey = resultSet.getBytes(3);
|
||||
|
||||
// Fetch additional info from the archive itself
|
||||
int onlineAccountsCount = 0;
|
||||
Integer onlineAccountsCount = null;
|
||||
Long timestamp = null;
|
||||
Integer transactionCount = null;
|
||||
byte[] reference = null;
|
||||
|
||||
BlockData blockData = this.fromSignature(signature);
|
||||
if (blockData != null) {
|
||||
onlineAccountsCount = blockData.getOnlineAccountsCount();
|
||||
}
|
||||
|
||||
BlockSummaryData blockSummary = new BlockSummaryData(height, signature, blockMinterPublicKey, onlineAccountsCount);
|
||||
BlockSummaryData blockSummary = new BlockSummaryData(height, signature, blockMinterPublicKey, onlineAccountsCount, timestamp, transactionCount, reference);
|
||||
blockSummaries.add(blockSummary);
|
||||
} while (resultSet.next());
|
||||
|
||||
|
@@ -297,7 +297,7 @@ public class HSQLDBBlockRepository implements BlockRepository {
|
||||
@Override
|
||||
public List<BlockSummaryData> getBlockSummariesBySigner(byte[] signerPublicKey, Integer limit, Integer offset, Boolean reverse) throws DataException {
|
||||
StringBuilder sql = new StringBuilder(512);
|
||||
sql.append("SELECT signature, height, Blocks.minter, online_accounts_count FROM ");
|
||||
sql.append("SELECT signature, height, Blocks.minter, online_accounts_count, minted_when, transaction_count, Blocks.reference FROM ");
|
||||
|
||||
// List of minter account's public key and reward-share public keys with minter's public key
|
||||
sql.append("(SELECT * FROM (VALUES (CAST(? AS QortalPublicKey))) UNION (SELECT reward_share_public_key FROM RewardShares WHERE minter_public_key = ?)) AS PublicKeys (public_key) ");
|
||||
@@ -322,8 +322,12 @@ public class HSQLDBBlockRepository implements BlockRepository {
|
||||
int height = resultSet.getInt(2);
|
||||
byte[] blockMinterPublicKey = resultSet.getBytes(3);
|
||||
int onlineAccountsCount = resultSet.getInt(4);
|
||||
long timestamp = resultSet.getLong(5);
|
||||
int transactionCount = resultSet.getInt(6);
|
||||
byte[] reference = resultSet.getBytes(7);
|
||||
|
||||
BlockSummaryData blockSummary = new BlockSummaryData(height, signature, blockMinterPublicKey, onlineAccountsCount);
|
||||
BlockSummaryData blockSummary = new BlockSummaryData(height, signature, blockMinterPublicKey, onlineAccountsCount,
|
||||
timestamp, transactionCount, reference);
|
||||
blockSummaries.add(blockSummary);
|
||||
} while (resultSet.next());
|
||||
|
||||
@@ -355,7 +359,7 @@ public class HSQLDBBlockRepository implements BlockRepository {
|
||||
|
||||
@Override
|
||||
public List<BlockSummaryData> getBlockSummaries(int firstBlockHeight, int lastBlockHeight) throws DataException {
|
||||
String sql = "SELECT signature, height, minter, online_accounts_count, minted_when, transaction_count "
|
||||
String sql = "SELECT signature, height, minter, online_accounts_count, minted_when, transaction_count, reference "
|
||||
+ "FROM Blocks WHERE height BETWEEN ? AND ?";
|
||||
|
||||
List<BlockSummaryData> blockSummaries = new ArrayList<>();
|
||||
@@ -371,9 +375,10 @@ public class HSQLDBBlockRepository implements BlockRepository {
|
||||
int onlineAccountsCount = resultSet.getInt(4);
|
||||
long timestamp = resultSet.getLong(5);
|
||||
int transactionCount = resultSet.getInt(6);
|
||||
byte[] reference = resultSet.getBytes(7);
|
||||
|
||||
BlockSummaryData blockSummary = new BlockSummaryData(height, signature, minterPublicKey, onlineAccountsCount,
|
||||
timestamp, transactionCount);
|
||||
timestamp, transactionCount, reference);
|
||||
blockSummaries.add(blockSummary);
|
||||
} while (resultSet.next());
|
||||
|
||||
|
@@ -26,7 +26,7 @@ public class ChatTransaction extends Transaction {
|
||||
private ChatTransactionData chatTransactionData;
|
||||
|
||||
// Other useful constants
|
||||
public static final int MAX_DATA_SIZE = 256;
|
||||
public static final int MAX_DATA_SIZE = 1024;
|
||||
public static final int POW_BUFFER_SIZE = 8 * 1024 * 1024; // bytes
|
||||
public static final int POW_DIFFICULTY_WITH_QORT = 8; // leading zero bits
|
||||
public static final int POW_DIFFICULTY_NO_QORT = 12; // leading zero bits
|
||||
|
@@ -56,7 +56,7 @@
|
||||
],
|
||||
"qoraHoldersShareByHeight": [
|
||||
{ "height": 1, "share": 0.20 },
|
||||
{ "height": 9999999, "share": 0.01 }
|
||||
{ "height": 1010000, "share": 0.01 }
|
||||
],
|
||||
"qoraPerQortReward": 250,
|
||||
"minAccountsToActivateShareBin": 30,
|
||||
@@ -75,7 +75,7 @@
|
||||
"atFindNextTransactionFix": 275000,
|
||||
"newBlockSigHeight": 320000,
|
||||
"shareBinFix": 399000,
|
||||
"sharesByLevelV2Height": 9999999,
|
||||
"sharesByLevelV2Height": 1010000,
|
||||
"rewardShareLimitTimestamp": 1657382400000,
|
||||
"calcChainWeightTimestamp": 1620579600000,
|
||||
"transactionV5Timestamp": 1642176000000,
|
||||
|
@@ -20,7 +20,7 @@ import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
import static org.qortal.crosschain.PirateChainHTLC.Status.*;
|
||||
import static org.qortal.crosschain.BitcoinyHTLC.Status.*;
|
||||
|
||||
public class PirateChainTests extends Common {
|
||||
|
||||
@@ -121,7 +121,7 @@ public class PirateChainTests extends Common {
|
||||
String p2shAddress = "ba6Q5HWrWtmfU2WZqQbrFdRYsafA45cUAt";
|
||||
long p2shFee = 10000;
|
||||
final long minimumAmount = 10000 + p2shFee;
|
||||
PirateChainHTLC.Status htlcStatus = PirateChainHTLC.determineHtlcStatus(pirateChain.getBlockchainProvider(), p2shAddress, minimumAmount);
|
||||
BitcoinyHTLC.Status htlcStatus = PirateChainHTLC.determineHtlcStatus(pirateChain.getBlockchainProvider(), p2shAddress, minimumAmount);
|
||||
assertEquals(FUNDED, htlcStatus);
|
||||
}
|
||||
|
||||
@@ -130,7 +130,7 @@ public class PirateChainTests extends Common {
|
||||
String p2shAddress = "bYZrzSSgGp8aEGvihukoMGU8sXYrx19Wka";
|
||||
long p2shFee = 10000;
|
||||
final long minimumAmount = 10000 + p2shFee;
|
||||
PirateChainHTLC.Status htlcStatus = PirateChainHTLC.determineHtlcStatus(pirateChain.getBlockchainProvider(), p2shAddress, minimumAmount);
|
||||
BitcoinyHTLC.Status htlcStatus = PirateChainHTLC.determineHtlcStatus(pirateChain.getBlockchainProvider(), p2shAddress, minimumAmount);
|
||||
assertEquals(REDEEMED, htlcStatus);
|
||||
}
|
||||
|
||||
@@ -139,7 +139,7 @@ public class PirateChainTests extends Common {
|
||||
String p2shAddress = "bE49izfVxz8odhu8c2BcUaVFUnt7NLFRgv";
|
||||
long p2shFee = 10000;
|
||||
final long minimumAmount = 10000 + p2shFee;
|
||||
PirateChainHTLC.Status htlcStatus = PirateChainHTLC.determineHtlcStatus(pirateChain.getBlockchainProvider(), p2shAddress, minimumAmount);
|
||||
BitcoinyHTLC.Status htlcStatus = PirateChainHTLC.determineHtlcStatus(pirateChain.getBlockchainProvider(), p2shAddress, minimumAmount);
|
||||
assertEquals(REFUNDED, htlcStatus);
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user