From 24adc71bd45d15a40dbab38f143b70bafd2f4b75 Mon Sep 17 00:00:00 2001 From: Matthew DeGarmo Date: Mon, 12 Sep 2022 22:57:19 -0500 Subject: [PATCH 01/53] Trying to run in container, can't background process. Adding docker arg to unsuppress logs. Fixing entry point. fixing paths fixing paths --- Dockerfile | 24 ++++++++++++++++-------- start.sh | 35 ++++++++++++++++++++++++----------- 2 files changed, 40 insertions(+), 19 deletions(-) diff --git a/Dockerfile b/Dockerfile index b06f7659..7fb25163 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,19 +1,21 @@ FROM maven:3-openjdk-11 as builder + + WORKDIR /work -COPY ./ /work/ +COPY . . RUN mvn clean package ### FROM openjdk:11 RUN useradd -r -u 1000 -g users qortal && \ - mkdir /usr/local/qortal /qortal && \ - chown 1000:100 /qortal - -COPY --from=builder /work/log4j2.properties /usr/local/qortal/ -COPY --from=builder /work/target/qortal*.jar /usr/local/qortal/qortal.jar + mkdir /qortal && \ + chown 1000:100 /qortal && \ +COPY --from=builder /work/log4j2.properties /qortal/ +COPY --from=builder /work/target/qortal*.jar /qortal/qortal.jar +COPY --from=builder /work/start.sh /qortal/ USER 1000:100 EXPOSE 12391 12392 @@ -22,5 +24,11 @@ HEALTHCHECK --start-period=5m CMD curl -sf http://127.0.0.1:12391/admin/info || WORKDIR /qortal VOLUME /qortal -ENTRYPOINT ["java"] -CMD ["-Djava.net.preferIPv4Stack=false", "-jar", "/usr/local/qortal/qortal.jar"] +## ENTRYPOINT ["java"] +## CMD ["-Djava.net.preferIPv4Stack=false", "-jar", "/usr/local/qortal/qortal.jar"] + +ENTRYPOINT ["/qortal/start.sh"] +CMD ["docker"] + + + diff --git a/start.sh b/start.sh index b3db54fe..3cee1178 100755 --- a/start.sh +++ b/start.sh @@ -2,10 +2,18 @@ # There's no need to run as root, so don't allow it, for security reasons if [ "$USER" = "root" ]; then - echo "Please su to non-root user before running" - exit + echo "Please su to non-root user before running" + exit fi +# If docker is passed, then we want to change the behavior of the script +# to change the log suppression +if [ "$1" = "docker" ]; then + DOCKER=true +fi + + + # Validate Java is installed and the minimum version is available MIN_JAVA_VER='11' @@ -26,8 +34,8 @@ fi # No qortal.jar but we have a Maven built one? # Be helpful and copy across to correct location if [ ! -e qortal.jar -a -f target/qortal*.jar ]; then - echo "Copying Maven-built Qortal JAR to correct pathname" - cp target/qortal*.jar qortal.jar + echo "Copying Maven-built Qortal JAR to correct pathname" + cp target/qortal*.jar qortal.jar fi # Limits Java JVM stack size and maximum heap usage. @@ -39,12 +47,17 @@ fi # by default in Java 11, on some platforms (e.g. FreeBSD 12), # it is overridden to be true by default. Hence we explicitly # set it to false to obtain desired behaviour. -nohup nice -n 20 java \ - -Djava.net.preferIPv4Stack=false \ - ${JVM_MEMORY_ARGS} \ - -jar qortal.jar \ - 1>run.log 2>&1 & + +if [ "$DOCKER" = true ]; then + java -Djava.net.preferIPv4Stack=false $JVM_MEMORY_ARGS -jar qortal.jar +else + nohup nice -n 20 java \ + -Djava.net.preferIPv4Stack=false \ + ${JVM_MEMORY_ARGS} \ + -jar qortal.jar +fi + # Save backgrounded process's PID -echo $! > run.pid -echo qortal running as pid $! +# echo $! > run.pid +# echo qortal running as pid $! From 423514b749df8ab3a804aa85bc52cac2215d9eac Mon Sep 17 00:00:00 2001 From: Matthew DeGarmo Date: Wed, 21 Sep 2022 21:14:53 -0500 Subject: [PATCH 02/53] fixing line continuation. --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 7fb25163..d0097053 100644 --- a/Dockerfile +++ b/Dockerfile @@ -11,7 +11,7 @@ FROM openjdk:11 RUN useradd -r -u 1000 -g users qortal && \ mkdir /qortal && \ - chown 1000:100 /qortal && \ + chown 1000:100 /qortal COPY --from=builder /work/log4j2.properties /qortal/ COPY --from=builder /work/target/qortal*.jar /qortal/qortal.jar From f6d5f1becd7c87ff63ac391c04b1080d6c855f24 Mon Sep 17 00:00:00 2001 From: Matthew DeGarmo Date: Wed, 21 Sep 2022 21:34:08 -0500 Subject: [PATCH 03/53] adding settings --- Dockerfile | 1 + 1 file changed, 1 insertion(+) diff --git a/Dockerfile b/Dockerfile index d0097053..4fb77167 100644 --- a/Dockerfile +++ b/Dockerfile @@ -16,6 +16,7 @@ RUN useradd -r -u 1000 -g users qortal && \ COPY --from=builder /work/log4j2.properties /qortal/ COPY --from=builder /work/target/qortal*.jar /qortal/qortal.jar COPY --from=builder /work/start.sh /qortal/ +COPY --from=builder /work/settings.json /qortal/ USER 1000:100 EXPOSE 12391 12392 From afef11125c724392746fb1087ae811f9afb9b416 Mon Sep 17 00:00:00 2001 From: CalDescent Date: Fri, 16 Sep 2022 11:19:10 +0100 Subject: [PATCH 04/53] Allow BTC trades in redeemAll / refundAll, since most will now be using ACCTv3. --- .../org/qortal/api/resource/CrossChainHtlcResource.java | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/main/java/org/qortal/api/resource/CrossChainHtlcResource.java b/src/main/java/org/qortal/api/resource/CrossChainHtlcResource.java index cf098f53..9f10f781 100644 --- a/src/main/java/org/qortal/api/resource/CrossChainHtlcResource.java +++ b/src/main/java/org/qortal/api/resource/CrossChainHtlcResource.java @@ -373,10 +373,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); @@ -599,11 +595,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 From 23a4e5e7e48bd382a0e314c8ee23e79b78d99c8a Mon Sep 17 00:00:00 2001 From: CalDescent Date: Sat, 17 Sep 2022 10:30:10 +0100 Subject: [PATCH 05/53] Added support for ARRR refunds via /crosschain/htlc/refund/{ataddress} and /crosschain/htlc/refundAll This could probably be refactored into multiple classes to make the code cleaner, but it is functional for now. --- .../api/resource/CrossChainHtlcResource.java | 74 ++++++++++++++----- .../tradebot/PirateChainACCTv3TradeBot.java | 8 +- .../org/qortal/crosschain/PirateChain.java | 12 +++ .../qortal/crosschain/PirateChainHTLC.java | 10 +-- .../test/crosschain/PirateChainTests.java | 8 +- 5 files changed, 75 insertions(+), 37 deletions(-) diff --git a/src/main/java/org/qortal/api/resource/CrossChainHtlcResource.java b/src/main/java/org/qortal/api/resource/CrossChainHtlcResource.java index 9f10f781..664b013a 100644 --- a/src/main/java/org/qortal/api/resource/CrossChainHtlcResource.java +++ b/src/main/java/org/qortal/api/resource/CrossChainHtlcResource.java @@ -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; @@ -538,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); @@ -585,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); } @@ -606,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: @@ -631,18 +638,45 @@ public class CrossChainHtlcResource { case FUNDED:{ Coin refundAmount = Coin.valueOf(crossChainTradeData.expectedForeignAmount); - ECKey refundKey = ECKey.fromPrivate(tradeBotData.getTradePrivateKey()); - List 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 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; } } diff --git a/src/main/java/org/qortal/controller/tradebot/PirateChainACCTv3TradeBot.java b/src/main/java/org/qortal/controller/tradebot/PirateChainACCTv3TradeBot.java index 8f413093..9834df20 100644 --- a/src/main/java/org/qortal/controller/tradebot/PirateChainACCTv3TradeBot.java +++ b/src/main/java/org/qortal/controller/tradebot/PirateChainACCTv3TradeBot.java @@ -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: diff --git a/src/main/java/org/qortal/crosschain/PirateChain.java b/src/main/java/org/qortal/crosschain/PirateChain.java index 97aa07fe..09b37481 100644 --- a/src/main/java/org/qortal/crosschain/PirateChain.java +++ b/src/main/java/org/qortal/crosschain/PirateChain.java @@ -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); diff --git a/src/main/java/org/qortal/crosschain/PirateChainHTLC.java b/src/main/java/org/qortal/crosschain/PirateChainHTLC.java index f28897dc..17f7ad74 100644 --- a/src/main/java/org/qortal/crosschain/PirateChainHTLC.java +++ b/src/main/java/org/qortal/crosschain/PirateChainHTLC.java @@ -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; diff --git a/src/test/java/org/qortal/test/crosschain/PirateChainTests.java b/src/test/java/org/qortal/test/crosschain/PirateChainTests.java index d203cf5e..9502e45a 100644 --- a/src/test/java/org/qortal/test/crosschain/PirateChainTests.java +++ b/src/test/java/org/qortal/test/crosschain/PirateChainTests.java @@ -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); } From 2b3b38b79f5bdbd09ff4fa9a515a9a0b874023d7 Mon Sep 17 00:00:00 2001 From: CalDescent Date: Sat, 17 Sep 2022 10:36:25 +0100 Subject: [PATCH 06/53] Added support for Pirate Chain wallets on FreeBSD. --- .../org/qortal/controller/PirateChainWalletController.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/qortal/controller/PirateChainWalletController.java b/src/main/java/org/qortal/controller/PirateChainWalletController.java index 931850db..1eac4b3a 100644 --- a/src/main/java/org/qortal/controller/PirateChainWalletController.java +++ b/src/main/java/org/qortal/controller/PirateChainWalletController.java @@ -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")) { From dc23195eba8ca9dff07c0ef59b102211e26b7fad Mon Sep 17 00:00:00 2001 From: CalDescent Date: Sat, 17 Sep 2022 12:21:56 +0100 Subject: [PATCH 07/53] ChatTransaction MAX_DATA_SIZE increased from 256 to 1024 bytes, to allow for new UI features. --- src/main/java/org/qortal/transaction/ChatTransaction.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/qortal/transaction/ChatTransaction.java b/src/main/java/org/qortal/transaction/ChatTransaction.java index 2671c209..9cccd42a 100644 --- a/src/main/java/org/qortal/transaction/ChatTransaction.java +++ b/src/main/java/org/qortal/transaction/ChatTransaction.java @@ -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 From 63160c1aa536d7720ef6ee3ce03f6fd636d16d17 Mon Sep 17 00:00:00 2001 From: CalDescent Date: Sat, 17 Sep 2022 13:28:32 +0100 Subject: [PATCH 08/53] Added GET /chat/message/{signature} endpoint. This will ease the transition to a Q-Chat protocol, where chat messages will no longer be regular transactions. --- .../org/qortal/api/resource/ChatResource.java | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/src/main/java/org/qortal/api/resource/ChatResource.java b/src/main/java/org/qortal/api/resource/ChatResource.java index be8bd7d7..79e479b1 100644 --- a/src/main/java/org/qortal/api/resource/ChatResource.java +++ b/src/main/java/org/qortal/api/resource/ChatResource.java @@ -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(@QueryParam("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( From 5e61fe3bff5310e3816138bf217e35589404bd77 Mon Sep 17 00:00:00 2001 From: CalDescent Date: Sat, 17 Sep 2022 13:50:04 +0100 Subject: [PATCH 09/53] Use path parameter instead of query string. --- src/main/java/org/qortal/api/resource/ChatResource.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/qortal/api/resource/ChatResource.java b/src/main/java/org/qortal/api/resource/ChatResource.java index 79e479b1..0bbd1951 100644 --- a/src/main/java/org/qortal/api/resource/ChatResource.java +++ b/src/main/java/org/qortal/api/resource/ChatResource.java @@ -115,7 +115,7 @@ public class ChatResource { } ) @ApiErrors({ApiError.INVALID_CRITERIA, ApiError.INVALID_ADDRESS, ApiError.REPOSITORY_ISSUE}) - public ChatMessage getMessageBySignature(@QueryParam("signature") String signature58) { + public ChatMessage getMessageBySignature(@PathParam("signature") String signature58) { byte[] signature = Base58.decode(signature58); try (final Repository repository = RepositoryManager.getRepository()) { From b8895c43234877d248e95b4fcc6925b98f4c772a Mon Sep 17 00:00:00 2001 From: CalDescent Date: Mon, 19 Sep 2022 16:36:39 +0100 Subject: [PATCH 10/53] OnlineAccountsV3Message.MIN_PEER_VERSION set to 3.6.0 --- .../org/qortal/network/message/OnlineAccountsV3Message.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/qortal/network/message/OnlineAccountsV3Message.java b/src/main/java/org/qortal/network/message/OnlineAccountsV3Message.java index 0c5f6730..d554d96c 100644 --- a/src/main/java/org/qortal/network/message/OnlineAccountsV3Message.java +++ b/src/main/java/org/qortal/network/message/OnlineAccountsV3Message.java @@ -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 onlineAccounts; From 04e47676826b6b108dfa9c56706e3cd6c4174bf8 Mon Sep 17 00:00:00 2001 From: CalDescent Date: Mon, 19 Sep 2022 17:27:07 +0100 Subject: [PATCH 11/53] QORA / block reward adjustments set to activate at height 1010000 --- src/main/resources/blockchain.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/resources/blockchain.json b/src/main/resources/blockchain.json index 8d1600ed..fad81ab5 100644 --- a/src/main/resources/blockchain.json +++ b/src/main/resources/blockchain.json @@ -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, From 216a21bc49794fac6412f4ff1c33430404858f5f Mon Sep 17 00:00:00 2001 From: CalDescent Date: Mon, 19 Sep 2022 17:29:26 +0100 Subject: [PATCH 12/53] Bump version to 3.6.0 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 22017136..e045e0f4 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ 4.0.0 org.qortal qortal - 3.5.0 + 3.6.0 jar true From 97221771947ec63452a5f015ce1bd09d224bc94f Mon Sep 17 00:00:00 2001 From: CalDescent Date: Tue, 20 Sep 2022 08:50:37 +0100 Subject: [PATCH 13/53] Reordered code in Block.mint() to fix potential issue after mempow activates. --- src/main/java/org/qortal/block/Block.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/qortal/block/Block.java b/src/main/java/org/qortal/block/Block.java index e0581e7d..bdae83c2 100644 --- a/src/main/java/org/qortal/block/Block.java +++ b/src/main/java/org/qortal/block/Block.java @@ -368,16 +368,17 @@ public class Block { // Fetch our list of online accounts List 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. From 2ee1829faded19bf56b6dcaef86f33dd79ec253c Mon Sep 17 00:00:00 2001 From: CalDescent Date: Tue, 20 Sep 2022 22:26:30 +0100 Subject: [PATCH 14/53] Fixed bug causing error 500 in some cases. --- .../org/qortal/data/arbitrary/ArbitraryResourceMetadata.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/qortal/data/arbitrary/ArbitraryResourceMetadata.java b/src/main/java/org/qortal/data/arbitrary/ArbitraryResourceMetadata.java index 75b5a4d8..e2bcaf56 100644 --- a/src/main/java/org/qortal/data/arbitrary/ArbitraryResourceMetadata.java +++ b/src/main/java/org/qortal/data/arbitrary/ArbitraryResourceMetadata.java @@ -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) { From bc4fa0010f482153afa3f5b6a47309f353a977dd Mon Sep 17 00:00:00 2001 From: CalDescent Date: Fri, 23 Sep 2022 15:25:44 +0100 Subject: [PATCH 15/53] Removed online accounts V2 and V1 messaging, as the V3 format will soon be required due to the nonce values. --- .../org/qortal/controller/Controller.java | 11 +- .../controller/OnlineAccountsManager.java | 157 +----------------- 2 files changed, 6 insertions(+), 162 deletions(-) diff --git a/src/main/java/org/qortal/controller/Controller.java b/src/main/java/org/qortal/controller/Controller.java index 4ff08e15..f6711991 100644 --- a/src/main/java/org/qortal/controller/Controller.java +++ b/src/main/java/org/qortal/controller/Controller.java @@ -1232,19 +1232,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: diff --git a/src/main/java/org/qortal/controller/OnlineAccountsManager.java b/src/main/java/org/qortal/controller/OnlineAccountsManager.java index 254d6168..b4bfab12 100644 --- a/src/main/java/org/qortal/controller/OnlineAccountsManager.java +++ b/src/main/java/org/qortal/controller/OnlineAccountsManager.java @@ -55,12 +55,8 @@ 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_V2_PEER_VERSION = 0x0300020000L; // v3.2.0 - private static final long ONLINE_ACCOUNTS_V3_PEER_VERSION = 0x0300040000L; // v3.4.0 - // MemoryPoW public final int POW_BUFFER_SIZE = 1 * 1024 * 1024; // bytes public int POW_DIFFICULTY = 18; // leading zero bits @@ -125,9 +121,7 @@ public class OnlineAccountsManager { // 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+) + // Request online accounts from peers executor.scheduleAtFixedRate(this::requestRemoteOnlineAccounts, ONLINE_ACCOUNTS_BROADCAST_INTERVAL, ONLINE_ACCOUNTS_BROADCAST_INTERVAL, TimeUnit.MILLISECONDS); // Process import queue @@ -399,30 +393,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 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(); @@ -435,11 +406,7 @@ public class OnlineAccountsManager { Message messageV3 = new GetOnlineAccountsV3Message(currentOnlineAccountsHashes); - Network.getInstance().broadcast(peer -> - peer.getPeersVersion() >= ONLINE_ACCOUNTS_V3_PEER_VERSION - ? messageV3 - : null - ); + Network.getInstance().broadcast(peer -> messageV3); } /** @@ -579,17 +546,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); @@ -767,106 +724,6 @@ public class OnlineAccountsManager { // Network handlers - public void onNetworkGetOnlineAccountsMessage(Peer peer, Message message) { - GetOnlineAccountsMessage getOnlineAccountsMessage = (GetOnlineAccountsMessage) message; - - List excludeAccounts = getOnlineAccountsMessage.getOnlineAccounts(); - - // Send online accounts info, excluding entries with matching timestamp & public key from excludeAccounts - List accountsToSend = Set.copyOf(this.currentOnlineAccounts.values()).stream().flatMap(Set::stream).collect(Collectors.toList()); - int prefilterSize = accountsToSend.size(); - - Iterator 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 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 excludeAccounts = getOnlineAccountsMessage.getOnlineAccounts(); - - // Send online accounts info, excluding entries with matching timestamp & public key from excludeAccounts - List accountsToSend = Set.copyOf(this.currentOnlineAccounts.values()).stream().flatMap(Set::stream).collect(Collectors.toList()); - int prefilterSize = accountsToSend.size(); - - Iterator 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 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; @@ -920,11 +777,7 @@ 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); } From 1eea3e97fd4caae20fc21c86caa8310a6c6a51a2 Mon Sep 17 00:00:00 2001 From: CalDescent Date: Fri, 23 Sep 2022 18:02:46 +0100 Subject: [PATCH 16/53] Don't add online accounts to the import queue if they are already validated --- .../java/org/qortal/controller/OnlineAccountsManager.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/main/java/org/qortal/controller/OnlineAccountsManager.java b/src/main/java/org/qortal/controller/OnlineAccountsManager.java index b4bfab12..eaf12db3 100644 --- a/src/main/java/org/qortal/controller/OnlineAccountsManager.java +++ b/src/main/java/org/qortal/controller/OnlineAccountsManager.java @@ -792,6 +792,12 @@ public class OnlineAccountsManager { // Add any online accounts to the queue that aren't already present for (OnlineAccountData onlineAccountData : peersOnlineAccounts) { + + Set onlineAccounts = this.currentOnlineAccounts.computeIfAbsent(onlineAccountData.getTimestamp(), k -> ConcurrentHashMap.newKeySet()); + if (onlineAccounts.contains(onlineAccountData)) + // We have already validated this online account + continue; + boolean isNewEntry = onlineAccountsImportQueue.add(onlineAccountData); if (isNewEntry) From f1314779ed211cfdfb830a26e84f0e1b4ea562a2 Mon Sep 17 00:00:00 2001 From: CalDescent Date: Fri, 23 Sep 2022 18:28:41 +0100 Subject: [PATCH 17/53] Wait 30 seconds after the node starts before computing our online accounts. This allows some time for initial online account lists to be retrieved, and reduces the chances of the same nonce being computed twice. --- .../controller/OnlineAccountsManager.java | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/qortal/controller/OnlineAccountsManager.java b/src/main/java/org/qortal/controller/OnlineAccountsManager.java index eaf12db3..39ce8a85 100644 --- a/src/main/java/org/qortal/controller/OnlineAccountsManager.java +++ b/src/main/java/org/qortal/controller/OnlineAccountsManager.java @@ -57,6 +57,8 @@ public class OnlineAccountsManager { private static final long ONLINE_ACCOUNTS_TASKS_INTERVAL = 10 * 1000L; // ms private static final long ONLINE_ACCOUNTS_BROADCAST_INTERVAL = 5 * 1000L; // ms + private static final long INITIAL_SLEEP_INTERVAL = 30 * 1000L; + // MemoryPoW public final int POW_BUFFER_SIZE = 1 * 1024 * 1024; // bytes public int POW_DIFFICULTY = 18; // leading zero bits @@ -118,14 +120,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 executor.scheduleAtFixedRate(this::requestRemoteOnlineAccounts, ONLINE_ACCOUNTS_BROADCAST_INTERVAL, ONLINE_ACCOUNTS_BROADCAST_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_BROADCAST_INTERVAL, ONLINE_ACCOUNTS_BROADCAST_INTERVAL, TimeUnit.MILLISECONDS); } public void shutdown() { From 74f4457f2b2fc8d47bf57c68a2bd30cbfa4d8bcc Mon Sep 17 00:00:00 2001 From: CalDescent Date: Fri, 23 Sep 2022 18:46:01 +0100 Subject: [PATCH 18/53] Allow duplicate variations of each OnlineAccountData in the import queue, but don't allow two entries that match exactly. --- .../controller/OnlineAccountsManager.java | 24 +++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/qortal/controller/OnlineAccountsManager.java b/src/main/java/org/qortal/controller/OnlineAccountsManager.java index 39ce8a85..f770bc3a 100644 --- a/src/main/java/org/qortal/controller/OnlineAccountsManager.java +++ b/src/main/java/org/qortal/controller/OnlineAccountsManager.java @@ -66,7 +66,7 @@ public class OnlineAccountsManager { private final ScheduledExecutorService executor = Executors.newScheduledThreadPool(4, new NamedThreadFactory("OnlineAccounts")); private volatile boolean isStopping = false; - private final Set onlineAccountsImportQueue = ConcurrentHashMap.newKeySet(); + private final List onlineAccountsImportQueue = Collections.synchronizedList(new ArrayList<>()); /** * Cache of 'current' online accounts, keyed by timestamp @@ -184,9 +184,12 @@ 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 onlineAccountsImportQueueCopy = new ArrayList<>(this.onlineAccountsImportQueue); + Set onlineAccountsToAdd = new HashSet<>(); try (final Repository repository = RepositoryManager.getRepository()) { - for (OnlineAccountData onlineAccountData : this.onlineAccountsImportQueue) { + for (OnlineAccountData onlineAccountData : onlineAccountsImportQueueCopy) { if (isStopping) return; @@ -207,6 +210,19 @@ public class OnlineAccountsManager { } } + 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()) + ); + } + } + /** * Check if supplied onlineAccountData is superior (i.e. has a nonce value) than existing record. * Two entries are considered equal even if the nonce differs, to prevent multiple variations @@ -809,6 +825,10 @@ public class OnlineAccountsManager { // 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) From 741c5c4ae4ec61d14ff67d971c3a357498a7ff90 Mon Sep 17 00:00:00 2001 From: CalDescent Date: Fri, 23 Sep 2022 19:45:59 +0100 Subject: [PATCH 19/53] When validating online accounts, enforce mempow if the online account's timestamp is after the feature trigger. --- .../java/org/qortal/controller/OnlineAccountsManager.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/qortal/controller/OnlineAccountsManager.java b/src/main/java/org/qortal/controller/OnlineAccountsManager.java index f770bc3a..4d1ab561 100644 --- a/src/main/java/org/qortal/controller/OnlineAccountsManager.java +++ b/src/main/java/org/qortal/controller/OnlineAccountsManager.java @@ -325,8 +325,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; @@ -628,7 +629,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; } From 8099a5776d361d77122fc6eabf43308412f007f7 Mon Sep 17 00:00:00 2001 From: CalDescent Date: Sat, 24 Sep 2022 10:23:55 +0100 Subject: [PATCH 20/53] Set hasOurOnlineAccounts to true if one of our accounts is found before signing. --- src/main/java/org/qortal/controller/OnlineAccountsManager.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/org/qortal/controller/OnlineAccountsManager.java b/src/main/java/org/qortal/controller/OnlineAccountsManager.java index 4d1ab561..32d0a47a 100644 --- a/src/main/java/org/qortal/controller/OnlineAccountsManager.java +++ b/src/main/java/org/qortal/controller/OnlineAccountsManager.java @@ -517,6 +517,8 @@ public class OnlineAccountsManager { Set 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; From 09a577d7e198410761f4323be364a58cd425e4d8 Mon Sep 17 00:00:00 2001 From: CalDescent Date: Sat, 24 Sep 2022 10:56:52 +0100 Subject: [PATCH 21/53] Add accounts from the import queue individually, and then skip future duplicates before unnecessarily validating them again. This closes a gap where accounts would be moved from onlineAccountsImportQueue to onlineAccountsToAdd, but not yet imported. During this time, there was nothing to stop them from being added to the import queue again, causing duplicate validations. --- .../controller/OnlineAccountsManager.java | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/main/java/org/qortal/controller/OnlineAccountsManager.java b/src/main/java/org/qortal/controller/OnlineAccountsManager.java index 32d0a47a..de8cfb12 100644 --- a/src/main/java/org/qortal/controller/OnlineAccountsManager.java +++ b/src/main/java/org/qortal/controller/OnlineAccountsManager.java @@ -193,9 +193,17 @@ public class OnlineAccountsManager { if (isStopping) return; + // Skip this account if it's already validated + Set 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); @@ -203,11 +211,6 @@ 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) { @@ -381,7 +384,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; } From 24def5abdcee4cbe5802c4e0d22f40ada6f491de Mon Sep 17 00:00:00 2001 From: CalDescent Date: Sat, 24 Sep 2022 13:02:27 +0100 Subject: [PATCH 22/53] Modified online accounts request interval, and introduced bursting. It will now request online accounts every 1 minute instead of every 5 seconds, except for the first 5 minutes following a new online accounts timestamp, in which it will request every 5 seconds (referred to as the "burst" interval). It will also use the burst interval for the first 5 minutes after the node starts. This is based on the idea that most online accounts arrive soon after a new timestamp begins, and so there is no need to request accounts so frequently after that. This should reduce data usage by a significant amount. Once mempow is fully rolled out, the "burst" feature can be reduced or removed, since online accounts will be sent ahead of time, generally 15-30 mins prior to the new online accounts timestamp becoming active. --- .../org/qortal/controller/Controller.java | 4 +++ .../controller/OnlineAccountsManager.java | 31 ++++++++++++++++--- 2 files changed, 31 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/qortal/controller/Controller.java b/src/main/java/org/qortal/controller/Controller.java index f6711991..8e1dfd8a 100644 --- a/src/main/java/org/qortal/controller/Controller.java +++ b/src/main/java/org/qortal/controller/Controller.java @@ -317,6 +317,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) { diff --git a/src/main/java/org/qortal/controller/OnlineAccountsManager.java b/src/main/java/org/qortal/controller/OnlineAccountsManager.java index de8cfb12..a0f4db68 100644 --- a/src/main/java/org/qortal/controller/OnlineAccountsManager.java +++ b/src/main/java/org/qortal/controller/OnlineAccountsManager.java @@ -55,7 +55,12 @@ 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_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 INITIAL_SLEEP_INTERVAL = 30 * 1000L; @@ -83,6 +88,8 @@ public class OnlineAccountsManager { */ private final SortedMap> latestBlocksOnlineAccounts = new ConcurrentSkipListMap<>(); + private long lastOnlineAccountsRequest = 0; + private boolean hasOurOnlineAccounts = false; public static long getOnlineTimestampModulus() { @@ -121,7 +128,7 @@ public class OnlineAccountsManager { executor.scheduleAtFixedRate(this::expireOldOnlineAccounts, ONLINE_ACCOUNTS_TASKS_INTERVAL, ONLINE_ACCOUNTS_TASKS_INTERVAL, TimeUnit.MILLISECONDS); // Request online accounts from peers - executor.scheduleAtFixedRate(this::requestRemoteOnlineAccounts, ONLINE_ACCOUNTS_BROADCAST_INTERVAL, ONLINE_ACCOUNTS_BROADCAST_INTERVAL, TimeUnit.MILLISECONDS); + 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); @@ -136,7 +143,7 @@ public class OnlineAccountsManager { } // Send our online accounts - executor.scheduleAtFixedRate(this::sendOurOnlineAccountsInfo, ONLINE_ACCOUNTS_BROADCAST_INTERVAL, ONLINE_ACCOUNTS_BROADCAST_INTERVAL, TimeUnit.MILLISECONDS); + executor.scheduleAtFixedRate(this::sendOurOnlineAccountsInfo, ONLINE_ACCOUNTS_COMPUTE_INTERVAL, ONLINE_ACCOUNTS_COMPUTE_INTERVAL, TimeUnit.MILLISECONDS); } public void shutdown() { @@ -435,8 +442,24 @@ 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 + 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.info("Requesting online accounts via broadcast..."); + + lastOnlineAccountsRequest = now; + Message messageV3 = new GetOnlineAccountsV3Message(currentOnlineAccountsHashes); Network.getInstance().broadcast(peer -> messageV3); } From 58bdddd3d62bab99d174dde9e0acec7cd9bc1636 Mon Sep 17 00:00:00 2001 From: CalDescent Date: Sat, 24 Sep 2022 13:11:28 +0100 Subject: [PATCH 23/53] Moved various online accounts logs to TRACE level, to make it easier to monitor the queue processing when in DEBUG. --- .../org/qortal/controller/OnlineAccountsManager.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/main/java/org/qortal/controller/OnlineAccountsManager.java b/src/main/java/org/qortal/controller/OnlineAccountsManager.java index a0f4db68..40192876 100644 --- a/src/main/java/org/qortal/controller/OnlineAccountsManager.java +++ b/src/main/java/org/qortal/controller/OnlineAccountsManager.java @@ -365,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(", ")) ) @@ -456,7 +456,7 @@ public class OnlineAccountsManager { } } - LOGGER.info("Requesting online accounts via broadcast..."); + LOGGER.debug("Requesting online accounts via broadcast..."); lastOnlineAccountsRequest = now; Message messageV3 = new GetOnlineAccountsV3Message(currentOnlineAccountsHashes); @@ -801,7 +801,7 @@ public class OnlineAccountsManager { Set 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 outgoingLeadingBytes = new HashSet<>(); @@ -825,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(", ")) @@ -836,14 +836,14 @@ public class OnlineAccountsManager { 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 peersOnlineAccounts = onlineAccountsMessage.getOnlineAccounts(); - LOGGER.debug("Received {} online accounts from {}", peersOnlineAccounts.size(), peer); + LOGGER.trace("Received {} online accounts from {}", peersOnlineAccounts.size(), peer); int importCount = 0; From a69078988897fadedce8bb230f72d8e869703fa4 Mon Sep 17 00:00:00 2001 From: catbref Date: Tue, 31 May 2022 21:06:34 +0100 Subject: [PATCH 24/53] Initial work on BLOCK_SUMMARIES_V2, part of a bigger arc to improve synchronization. Touches quite a few files because: * Deprecate HEIGHT_V2 because it doesn't contain enough info to be fully useful during sync. Newer peers will re-use BLOCK_SUMMARIES_V2. * For newer peers, instead of sending / broadcasting HEIGHT_V2, send top N block summaries instead, to avoid requests for minor reorgs. * When responding to GET_BLOCK, and we don't actually have the requested block, we currently send an empty BLOCK_SUMMARIES message instead of not responding, which would cause a slow timeout in Synchronizer. This pattern has spread to other network message response code, so now we introduce a generic 'unknown' message type for all these cases. * Remove PeerChainTipData class entirely and re-use BlockSummaryData instead. * Each Peer instance used to hold PeerChainTipData - essentially single latest block summary - but now holds a List of latest block summaries. * PeerChainTipData getter/setter methods modified for compatibility at this point in time. * Repository methods that return BlockSummaryData (or lists of) now try to fully populate them, including newly added block reference field. * Re-worked Peer.canUseCommonBlockData() to be more readable * Cherry-picked patch to Message.fromByteBuffer() to pass an empty, read-only ByteBuffer to subclass fromByteBuffer() methods, instead of null. This allows natural use of BufferUnderflowException if a subclass tries to use read(), or hasRemaining(), etc. from an empty data-payload message. Previously this could have caused an NPE. --- .../org/qortal/api/model/ConnectedPeer.java | 10 +- .../org/qortal/controller/BlockMinter.java | 16 +-- .../org/qortal/controller/Controller.java | 102 +++++++++++------ .../org/qortal/controller/Synchronizer.java | 44 ++++---- .../arbitrary/ArbitraryDataFileManager.java | 7 +- .../qortal/data/block/BlockSummaryData.java | 24 +++- .../qortal/data/block/CommonBlockData.java | 8 +- .../qortal/data/network/PeerChainTipData.java | 37 ------- src/main/java/org/qortal/network/Network.java | 62 +++++++++-- src/main/java/org/qortal/network/Peer.java | 66 ++++++----- .../message/BlockSummariesV2Message.java | 104 ++++++++++++++++++ .../message/GenericUnknownMessage.java | 23 ++++ .../qortal/network/message/MessageType.java | 2 + .../hsqldb/HSQLDBBlockArchiveRepository.java | 8 +- .../hsqldb/HSQLDBBlockRepository.java | 13 ++- 15 files changed, 367 insertions(+), 159 deletions(-) delete mode 100644 src/main/java/org/qortal/data/network/PeerChainTipData.java create mode 100644 src/main/java/org/qortal/network/message/BlockSummariesV2Message.java create mode 100644 src/main/java/org/qortal/network/message/GenericUnknownMessage.java diff --git a/src/main/java/org/qortal/api/model/ConnectedPeer.java b/src/main/java/org/qortal/api/model/ConnectedPeer.java index 21bfc1f9..3d383321 100644 --- a/src/main/java/org/qortal/api/model/ConnectedPeer.java +++ b/src/main/java/org/qortal/api/model/ConnectedPeer.java @@ -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(); } } diff --git a/src/main/java/org/qortal/controller/BlockMinter.java b/src/main/java/org/qortal/controller/BlockMinter.java index 343ab4af..a07d37fe 100644 --- a/src/main/java/org/qortal/controller/BlockMinter.java +++ b/src/main/java/org/qortal/controller/BlockMinter.java @@ -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; @@ -431,16 +434,9 @@ public class BlockMinter extends Thread { blockchainLock.unlock(); } - if (newBlockMinted) { - // Broadcast our new chain to network - BlockData newBlockData = newBlock.getBlockData(); - - Network network = Network.getInstance(); - network.broadcast(broadcastPeer -> network.buildHeightMessage(broadcastPeer, newBlockData)); - } - } catch (InterruptedException e) { - // We've been interrupted - time to exit - return; + if (newBlockMinted) { + // Broadcast our new chain to network + Network.getInstance().broadcastOurChain(); } } } catch (DataException e) { diff --git a/src/main/java/org/qortal/controller/Controller.java b/src/main/java/org/qortal/controller/Controller.java index 8e1dfd8a..ce994757 100644 --- a/src/main/java/org/qortal/controller/Controller.java +++ b/src/main/java/org/qortal/controller/Controller.java @@ -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; @@ -731,25 +730,25 @@ public class Controller extends Thread { public static final Predicate 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 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 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 hasInferiorChainTip = peer -> { - final PeerChainTipData peerChainTipData = peer.getChainTipData(); + final BlockSummaryData peerChainTipData = peer.getChainTipData(); final List 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 hasOldVersion = peer -> { @@ -1011,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 @@ -1219,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; @@ -1373,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"); @@ -1423,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"); @@ -1483,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"); @@ -1558,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(); @@ -1585,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"); @@ -1621,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"); @@ -1665,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"); @@ -1702,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"); @@ -1737,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"); @@ -1786,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; } diff --git a/src/main/java/org/qortal/controller/Synchronizer.java b/src/main/java/org/qortal/controller/Synchronizer.java index 74a4a785..a6fbfe71 100644 --- a/src/main/java/org/qortal/controller/Synchronizer.java +++ b/src/main/java/org/qortal/controller/Synchronizer.java @@ -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 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; diff --git a/src/main/java/org/qortal/controller/arbitrary/ArbitraryDataFileManager.java b/src/main/java/org/qortal/controller/arbitrary/ArbitraryDataFileManager.java index 22cf4144..30b0fcca 100644 --- a/src/main/java/org/qortal/controller/arbitrary/ArbitraryDataFileManager.java +++ b/src/main/java/org/qortal/controller/arbitrary/ArbitraryDataFileManager.java @@ -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"); diff --git a/src/main/java/org/qortal/data/block/BlockSummaryData.java b/src/main/java/org/qortal/data/block/BlockSummaryData.java index 2167f0f0..57e29d0d 100644 --- a/src/main/java/org/qortal/data/block/BlockSummaryData.java +++ b/src/main/java/org/qortal/data/block/BlockSummaryData.java @@ -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; } diff --git a/src/main/java/org/qortal/data/block/CommonBlockData.java b/src/main/java/org/qortal/data/block/CommonBlockData.java index dd502df7..37e9649b 100644 --- a/src/main/java/org/qortal/data/block/CommonBlockData.java +++ b/src/main/java/org/qortal/data/block/CommonBlockData.java @@ -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 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; } diff --git a/src/main/java/org/qortal/data/network/PeerChainTipData.java b/src/main/java/org/qortal/data/network/PeerChainTipData.java deleted file mode 100644 index d8dbbad4..00000000 --- a/src/main/java/org/qortal/data/network/PeerChainTipData.java +++ /dev/null @@ -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; - } - -} diff --git a/src/main/java/org/qortal/network/Network.java b/src/main/java/org/qortal/network/Network.java index 57073e99..8aac68f0 100644 --- a/src/main/java/org/qortal/network/Network.java +++ b/src/main/java/org/qortal/network/Network.java @@ -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 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 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) { diff --git a/src/main/java/org/qortal/network/Peer.java b/src/main/java/org/qortal/network/Peer.java index cac0ccc9..a187d29b 100644 --- a/src/main/java/org/qortal/network/Peer.java +++ b/src/main/java/org/qortal/network/Peer.java @@ -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 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 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 getChainTipSummaries() { + return this.peersChainTipData; + } + + public void setChainTipSummaries(List 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; } diff --git a/src/main/java/org/qortal/network/message/BlockSummariesV2Message.java b/src/main/java/org/qortal/network/message/BlockSummariesV2Message.java new file mode 100644 index 00000000..96c661a4 --- /dev/null +++ b/src/main/java/org/qortal/network/message/BlockSummariesV2Message.java @@ -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 = 0x03000400cbL; + + 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 blockSummaries; + + public BlockSummariesV2Message(List 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 blockSummaries) { + super(id, MessageType.BLOCK_SUMMARIES_V2); + + this.blockSummaries = blockSummaries; + } + + public List 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 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); + } + +} diff --git a/src/main/java/org/qortal/network/message/GenericUnknownMessage.java b/src/main/java/org/qortal/network/message/GenericUnknownMessage.java new file mode 100644 index 00000000..15faaa1b --- /dev/null +++ b/src/main/java/org/qortal/network/message/GenericUnknownMessage.java @@ -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); + } + +} diff --git a/src/main/java/org/qortal/network/message/MessageType.java b/src/main/java/org/qortal/network/message/MessageType.java index 087e7fbf..4dd4a3c8 100644 --- a/src/main/java/org/qortal/network/message/MessageType.java +++ b/src/main/java/org/qortal/network/message/MessageType.java @@ -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), diff --git a/src/main/java/org/qortal/repository/hsqldb/HSQLDBBlockArchiveRepository.java b/src/main/java/org/qortal/repository/hsqldb/HSQLDBBlockArchiveRepository.java index cc7e1611..c3c5638a 100644 --- a/src/main/java/org/qortal/repository/hsqldb/HSQLDBBlockArchiveRepository.java +++ b/src/main/java/org/qortal/repository/hsqldb/HSQLDBBlockArchiveRepository.java @@ -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()); diff --git a/src/main/java/org/qortal/repository/hsqldb/HSQLDBBlockRepository.java b/src/main/java/org/qortal/repository/hsqldb/HSQLDBBlockRepository.java index b8238085..f38d549c 100644 --- a/src/main/java/org/qortal/repository/hsqldb/HSQLDBBlockRepository.java +++ b/src/main/java/org/qortal/repository/hsqldb/HSQLDBBlockRepository.java @@ -297,7 +297,7 @@ public class HSQLDBBlockRepository implements BlockRepository { @Override public List 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 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 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()); From 5510418138dfb8974c59a91d1f69a3712c4c77be Mon Sep 17 00:00:00 2001 From: CalDescent Date: Sat, 24 Sep 2022 13:53:27 +0100 Subject: [PATCH 25/53] BlockSummariesV2Message.MINIMUM_PEER_VERSION set to 3.6.1 --- .../org/qortal/network/message/BlockSummariesV2Message.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/qortal/network/message/BlockSummariesV2Message.java b/src/main/java/org/qortal/network/message/BlockSummariesV2Message.java index 96c661a4..6ed6c8aa 100644 --- a/src/main/java/org/qortal/network/message/BlockSummariesV2Message.java +++ b/src/main/java/org/qortal/network/message/BlockSummariesV2Message.java @@ -15,7 +15,7 @@ import java.util.List; public class BlockSummariesV2Message extends Message { - public static final long MINIMUM_PEER_VERSION = 0x03000400cbL; + 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 */ From 77a4a87541cd5e18f465fe7dd6385b024db8f663 Mon Sep 17 00:00:00 2001 From: CalDescent Date: Sat, 24 Sep 2022 14:35:02 +0100 Subject: [PATCH 26/53] Fixed error in rebase. --- src/main/java/org/qortal/controller/BlockMinter.java | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/qortal/controller/BlockMinter.java b/src/main/java/org/qortal/controller/BlockMinter.java index a07d37fe..100e74db 100644 --- a/src/main/java/org/qortal/controller/BlockMinter.java +++ b/src/main/java/org/qortal/controller/BlockMinter.java @@ -434,9 +434,14 @@ public class BlockMinter extends Thread { blockchainLock.unlock(); } - if (newBlockMinted) { - // Broadcast our new chain to network - Network.getInstance().broadcastOurChain(); + if (newBlockMinted) { + // Broadcast our new chain to network + Network.getInstance().broadcastOurChain(); + } + + } catch (InterruptedException e) { + // We've been interrupted - time to exit + return; } } } catch (DataException e) { From 6d91cbfe58d59466496188386e80591ad833d7b3 Mon Sep 17 00:00:00 2001 From: CalDescent Date: Sat, 24 Sep 2022 14:36:49 +0100 Subject: [PATCH 27/53] Fixed Synchronizer.getBlockSummaries() which was expecting BLOCK_SUMMARIES, but updated peers send BLOCK_SUMMARIES_V2 --- .../java/org/qortal/controller/Synchronizer.java | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/qortal/controller/Synchronizer.java b/src/main/java/org/qortal/controller/Synchronizer.java index a6fbfe71..a8d91f52 100644 --- a/src/main/java/org/qortal/controller/Synchronizer.java +++ b/src/main/java/org/qortal/controller/Synchronizer.java @@ -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 getBlockSignatures(Peer peer, byte[] parentSignature, int numberRequested) throws InterruptedException { From 41438e1b48f7cfa068660b5b3f2b0009193b3de7 Mon Sep 17 00:00:00 2001 From: CalDescent Date: Sat, 24 Sep 2022 15:21:01 +0100 Subject: [PATCH 28/53] Moved error to debug, as we now get a burst of these soon after startup, due to commit 99858f3. This also shows that commit 99858f3 now prevents a block candidate with a very small number of online accounts being built immediately after startup. --- src/main/java/org/qortal/block/Block.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/qortal/block/Block.java b/src/main/java/org/qortal/block/Block.java index bdae83c2..07c7db6f 100644 --- a/src/main/java/org/qortal/block/Block.java +++ b/src/main/java/org/qortal/block/Block.java @@ -375,7 +375,7 @@ public class Block { } if (onlineAccounts.isEmpty()) { - LOGGER.error("No online accounts - not even our own?"); + LOGGER.debug("No online accounts - not even our own?"); return null; } From e2342e23278e241677874e181173419684f69f9a Mon Sep 17 00:00:00 2001 From: CalDescent Date: Sat, 24 Sep 2022 15:48:45 +0100 Subject: [PATCH 29/53] Fixed bug which required a node to hold local trade presences before it would request any. This caused large gaps with no presence data. They are removed when they expire, causing the local count to drop to zero, and the node would only start requesting them again once a peer had pushed one or more entries proactively. --- src/main/java/org/qortal/controller/tradebot/TradeBot.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/main/java/org/qortal/controller/tradebot/TradeBot.java b/src/main/java/org/qortal/controller/tradebot/TradeBot.java index c7ae1db3..85e594fa 100644 --- a/src/main/java/org/qortal/controller/tradebot/TradeBot.java +++ b/src/main/java/org/qortal/controller/tradebot/TradeBot.java @@ -468,9 +468,6 @@ public class TradeBot implements Listener { List safeTradePresences = List.copyOf(this.safeAllTradePresencesByPubkey.values()); - if (safeTradePresences.isEmpty()) - return; - LOGGER.debug("Broadcasting all {} known trade presences. Next broadcast timestamp: {}", safeTradePresences.size(), nextTradePresenceBroadcastTimestamp ); From eac2407238a8fbb72bc331bc83206588edb7f11f Mon Sep 17 00:00:00 2001 From: CalDescent Date: Sat, 24 Sep 2022 15:49:29 +0100 Subject: [PATCH 30/53] Include total count in debug trade presence logging --- src/main/java/org/qortal/controller/tradebot/TradeBot.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/qortal/controller/tradebot/TradeBot.java b/src/main/java/org/qortal/controller/tradebot/TradeBot.java index 85e594fa..5880f561 100644 --- a/src/main/java/org/qortal/controller/tradebot/TradeBot.java +++ b/src/main/java/org/qortal/controller/tradebot/TradeBot.java @@ -634,7 +634,7 @@ public class TradeBot implements Listener { } if (newCount > 0) { - LOGGER.debug("New trade presences: {}", newCount); + LOGGER.debug("New trade presences: {}, all trade presences: {}", newCount, allTradePresencesByPubkey.size()); rebuildSafeAllTradePresences(); } } From 2f1dc18910cad598fcb9204acd15134e7b55cc88 Mon Sep 17 00:00:00 2001 From: CalDescent Date: Sun, 25 Sep 2022 11:37:07 +0100 Subject: [PATCH 31/53] Added optional filtering by reference in GET /chat/messages --- src/main/java/org/qortal/api/resource/ChatResource.java | 6 ++++++ .../org/qortal/api/websocket/ChatMessagesWebSocket.java | 2 ++ src/main/java/org/qortal/repository/ChatRepository.java | 2 +- .../org/qortal/repository/hsqldb/HSQLDBChatRepository.java | 7 ++++++- 4 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/qortal/api/resource/ChatResource.java b/src/main/java/org/qortal/api/resource/ChatResource.java index 0bbd1951..ee2a8599 100644 --- a/src/main/java/org/qortal/api/resource/ChatResource.java +++ b/src/main/java/org/qortal/api/resource/ChatResource.java @@ -69,6 +69,7 @@ public class ChatResource { public List searchChat(@QueryParam("before") Long before, @QueryParam("after") Long after, @QueryParam("txGroupId") Integer txGroupId, @QueryParam("involving") List involvingAddresses, + @QueryParam("reference") String reference, @Parameter(ref = "limit") @QueryParam("limit") Integer limit, @Parameter(ref = "offset") @QueryParam("offset") Integer offset, @Parameter(ref = "reverse") @QueryParam("reverse") Boolean reverse) { @@ -87,11 +88,16 @@ public class ChatResource { if (after != null && after < 1500000000000L) throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_CRITERIA); + byte[] referenceBytes = null; + if (reference != null) + referenceBytes = Base58.decode(reference); + try (final Repository repository = RepositoryManager.getRepository()) { return repository.getChatRepository().getMessagesMatchingCriteria( before, after, txGroupId, + referenceBytes, involvingAddresses, limit, offset, reverse); } catch (DataException e) { diff --git a/src/main/java/org/qortal/api/websocket/ChatMessagesWebSocket.java b/src/main/java/org/qortal/api/websocket/ChatMessagesWebSocket.java index 3dc2d494..9760b7f0 100644 --- a/src/main/java/org/qortal/api/websocket/ChatMessagesWebSocket.java +++ b/src/main/java/org/qortal/api/websocket/ChatMessagesWebSocket.java @@ -46,6 +46,7 @@ public class ChatMessagesWebSocket extends ApiWebSocket { null, txGroupId, null, + null, null, null, null); sendMessages(session, chatMessages); @@ -72,6 +73,7 @@ public class ChatMessagesWebSocket extends ApiWebSocket { null, null, null, + null, involvingAddresses, null, null, null); diff --git a/src/main/java/org/qortal/repository/ChatRepository.java b/src/main/java/org/qortal/repository/ChatRepository.java index cd4b9a8f..2ecd8a34 100644 --- a/src/main/java/org/qortal/repository/ChatRepository.java +++ b/src/main/java/org/qortal/repository/ChatRepository.java @@ -14,7 +14,7 @@ public interface ChatRepository { * Expects EITHER non-null txGroupID OR non-null sender and recipient addresses. */ public List getMessagesMatchingCriteria(Long before, Long after, - Integer txGroupId, List involving, + Integer txGroupId, byte[] reference, List involving, Integer limit, Integer offset, Boolean reverse) throws DataException; public ChatMessage toChatMessage(ChatTransactionData chatTransactionData) throws DataException; diff --git a/src/main/java/org/qortal/repository/hsqldb/HSQLDBChatRepository.java b/src/main/java/org/qortal/repository/hsqldb/HSQLDBChatRepository.java index 2972e9f2..2f570686 100644 --- a/src/main/java/org/qortal/repository/hsqldb/HSQLDBChatRepository.java +++ b/src/main/java/org/qortal/repository/hsqldb/HSQLDBChatRepository.java @@ -23,7 +23,7 @@ public class HSQLDBChatRepository implements ChatRepository { } @Override - public List getMessagesMatchingCriteria(Long before, Long after, Integer txGroupId, + public List getMessagesMatchingCriteria(Long before, Long after, Integer txGroupId, byte[] referenceBytes, List involving, Integer limit, Integer offset, Boolean reverse) throws DataException { // Check args meet expectations @@ -57,6 +57,11 @@ public class HSQLDBChatRepository implements ChatRepository { bindParams.add(after); } + if (referenceBytes != null) { + whereClauses.add("reference = ?"); + bindParams.add(referenceBytes); + } + if (txGroupId != null) { whereClauses.add("tx_group_id = " + txGroupId); // int safe to use literally whereClauses.add("recipient IS NULL"); From 2b3f627de5ad58707bd9a3e6a3104bb220df75c0 Mon Sep 17 00:00:00 2001 From: CalDescent Date: Sun, 25 Sep 2022 12:06:14 +0100 Subject: [PATCH 32/53] Revert "Allow duplicate variations of each OnlineAccountData in the import queue, but don't allow two entries that match exactly." This reverts commit 6d9e6e8d4c89582ffad23c1094b6a8e3aee91116. --- .../controller/OnlineAccountsManager.java | 24 ++----------------- 1 file changed, 2 insertions(+), 22 deletions(-) diff --git a/src/main/java/org/qortal/controller/OnlineAccountsManager.java b/src/main/java/org/qortal/controller/OnlineAccountsManager.java index 40192876..47d8cf1b 100644 --- a/src/main/java/org/qortal/controller/OnlineAccountsManager.java +++ b/src/main/java/org/qortal/controller/OnlineAccountsManager.java @@ -71,7 +71,7 @@ public class OnlineAccountsManager { private final ScheduledExecutorService executor = Executors.newScheduledThreadPool(4, new NamedThreadFactory("OnlineAccounts")); private volatile boolean isStopping = false; - private final List onlineAccountsImportQueue = Collections.synchronizedList(new ArrayList<>()); + private final Set onlineAccountsImportQueue = ConcurrentHashMap.newKeySet(); /** * Cache of 'current' online accounts, keyed by timestamp @@ -191,12 +191,9 @@ 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 onlineAccountsImportQueueCopy = new ArrayList<>(this.onlineAccountsImportQueue); - Set onlineAccountsToAdd = new HashSet<>(); try (final Repository repository = RepositoryManager.getRepository()) { - for (OnlineAccountData onlineAccountData : onlineAccountsImportQueueCopy) { + for (OnlineAccountData onlineAccountData : this.onlineAccountsImportQueue) { if (isStopping) return; @@ -220,19 +217,6 @@ public class OnlineAccountsManager { } } - 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()) - ); - } - } - /** * Check if supplied onlineAccountData is superior (i.e. has a nonce value) than existing record. * Two entries are considered equal even if the nonce differs, to prevent multiple variations @@ -855,10 +839,6 @@ public class OnlineAccountsManager { // 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) From 238c88ca5b1b16cff85e35fec68a69ee75b9986a Mon Sep 17 00:00:00 2001 From: CalDescent Date: Sun, 25 Sep 2022 12:26:00 +0100 Subject: [PATCH 33/53] Yet another attempt to optimize the online accounts import queue processing. The main difference here is that we now remove items from the onlineAccountsImportQueue in a batch, _after_ they have been imported. This prevents duplicates from being added to the queue in the previous time gap between them being removed and imported. --- .../qortal/controller/OnlineAccountsManager.java | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/qortal/controller/OnlineAccountsManager.java b/src/main/java/org/qortal/controller/OnlineAccountsManager.java index 47d8cf1b..6fa69a89 100644 --- a/src/main/java/org/qortal/controller/OnlineAccountsManager.java +++ b/src/main/java/org/qortal/controller/OnlineAccountsManager.java @@ -207,13 +207,20 @@ public class OnlineAccountsManager { boolean isValid = this.isValidCurrentAccount(repository, onlineAccountData); if (isValid) - addAccounts(Arrays.asList(onlineAccountData)); + onlineAccountsToAdd.add(onlineAccountData); - // Remove from queue - onlineAccountsImportQueue.remove(onlineAccountData); + // Don't remove from the queue yet - we'll do this at the end of the process + // This prevents duplicates being added to the queue whilst it's being processed } } catch (DataException e) { LOGGER.error("Repository issue while verifying online accounts", e); + + } finally { + if (!onlineAccountsToAdd.isEmpty()) { + LOGGER.debug("Merging {} validated online accounts from import queue", onlineAccountsToAdd.size()); + addAccounts(onlineAccountsToAdd); + onlineAccountsImportQueue.removeAll(onlineAccountsToAdd); + } } } From b52cea03a2142937b1373a683b4d6d38fc80f873 Mon Sep 17 00:00:00 2001 From: CalDescent Date: Sun, 25 Sep 2022 12:36:00 +0100 Subject: [PATCH 34/53] Fixed bug in last commit. We need to track items to remove separately from items to add, otherwise invalid accounts remain in the queue. --- .../java/org/qortal/controller/OnlineAccountsManager.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/qortal/controller/OnlineAccountsManager.java b/src/main/java/org/qortal/controller/OnlineAccountsManager.java index 6fa69a89..686ef514 100644 --- a/src/main/java/org/qortal/controller/OnlineAccountsManager.java +++ b/src/main/java/org/qortal/controller/OnlineAccountsManager.java @@ -192,6 +192,7 @@ public class OnlineAccountsManager { LOGGER.debug("Processing online accounts import queue (size: {})", this.onlineAccountsImportQueue.size()); Set onlineAccountsToAdd = new HashSet<>(); + Set onlineAccountsToRemove = new HashSet<>(); try (final Repository repository = RepositoryManager.getRepository()) { for (OnlineAccountData onlineAccountData : this.onlineAccountsImportQueue) { if (isStopping) @@ -211,6 +212,7 @@ public class OnlineAccountsManager { // Don't remove from the queue yet - we'll do this at the end of the process // This prevents duplicates being added to the queue whilst it's being processed + onlineAccountsToRemove.add(onlineAccountData); } } catch (DataException e) { LOGGER.error("Repository issue while verifying online accounts", e); @@ -219,7 +221,7 @@ public class OnlineAccountsManager { if (!onlineAccountsToAdd.isEmpty()) { LOGGER.debug("Merging {} validated online accounts from import queue", onlineAccountsToAdd.size()); addAccounts(onlineAccountsToAdd); - onlineAccountsImportQueue.removeAll(onlineAccountsToAdd); + onlineAccountsImportQueue.removeAll(onlineAccountsToRemove); } } } From 5fed38bb654f44edd6c79dfa23ef294477261bc0 Mon Sep 17 00:00:00 2001 From: CalDescent Date: Sun, 25 Sep 2022 18:39:56 +0100 Subject: [PATCH 35/53] Fixed issue causing startup of various components to be delayed by 30 seconds. --- .../org/qortal/controller/OnlineAccountsManager.java | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/src/main/java/org/qortal/controller/OnlineAccountsManager.java b/src/main/java/org/qortal/controller/OnlineAccountsManager.java index 686ef514..2644fa66 100644 --- a/src/main/java/org/qortal/controller/OnlineAccountsManager.java +++ b/src/main/java/org/qortal/controller/OnlineAccountsManager.java @@ -133,17 +133,10 @@ public class OnlineAccountsManager { // Process import queue executor.scheduleWithFixedDelay(this::processOnlineAccountsImportQueue, ONLINE_ACCOUNTS_QUEUE_INTERVAL, ONLINE_ACCOUNTS_QUEUE_INTERVAL, TimeUnit.MILLISECONDS); - // Sleep for some time before scheduling sendOurOnlineAccountsInfo() + // Send our online accounts (using increased initial delay) // 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); + executor.scheduleAtFixedRate(this::sendOurOnlineAccountsInfo, INITIAL_SLEEP_INTERVAL, ONLINE_ACCOUNTS_COMPUTE_INTERVAL, TimeUnit.MILLISECONDS); } public void shutdown() { From 25886d143852f76bf48f476276e3ebb62e3bf139 Mon Sep 17 00:00:00 2001 From: CalDescent Date: Sun, 25 Sep 2022 18:46:33 +0100 Subject: [PATCH 36/53] Renamed constant for consistency --- .../java/org/qortal/controller/OnlineAccountsManager.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/qortal/controller/OnlineAccountsManager.java b/src/main/java/org/qortal/controller/OnlineAccountsManager.java index 2644fa66..ff20a8d0 100644 --- a/src/main/java/org/qortal/controller/OnlineAccountsManager.java +++ b/src/main/java/org/qortal/controller/OnlineAccountsManager.java @@ -53,7 +53,7 @@ public class OnlineAccountsManager { */ private static final int MAX_BLOCKS_CACHED_ONLINE_ACCOUNTS = 3; - private static final long ONLINE_ACCOUNTS_QUEUE_INTERVAL = 100L; //ms + 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_COMPUTE_INTERVAL = 5 * 1000L; // ms private static final long ONLINE_ACCOUNTS_BROADCAST_INTERVAL = 60 * 1000L; // ms @@ -62,7 +62,7 @@ public class OnlineAccountsManager { 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 INITIAL_SLEEP_INTERVAL = 30 * 1000L; + private static final long ONLINE_ACCOUNTS_COMPUTE_INITIAL_SLEEP_INTERVAL = 30 * 1000L; // ms // MemoryPoW public final int POW_BUFFER_SIZE = 1 * 1024 * 1024; // bytes @@ -136,7 +136,7 @@ public class OnlineAccountsManager { // Send our online accounts (using increased initial delay) // This allows some time for initial online account lists to be retrieved, and // reduces the chances of the same nonce being computed twice - executor.scheduleAtFixedRate(this::sendOurOnlineAccountsInfo, INITIAL_SLEEP_INTERVAL, ONLINE_ACCOUNTS_COMPUTE_INTERVAL, TimeUnit.MILLISECONDS); + executor.scheduleAtFixedRate(this::sendOurOnlineAccountsInfo, ONLINE_ACCOUNTS_COMPUTE_INITIAL_SLEEP_INTERVAL, ONLINE_ACCOUNTS_COMPUTE_INTERVAL, TimeUnit.MILLISECONDS); } public void shutdown() { From 27104a6b76551a3a119632aa56be2108c4cf2317 Mon Sep 17 00:00:00 2001 From: CalDescent Date: Sun, 25 Sep 2022 19:43:56 +0100 Subject: [PATCH 37/53] Reintroduced initial sleep period in block archiver. --- .../java/org/qortal/controller/repository/BlockArchiver.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/qortal/controller/repository/BlockArchiver.java b/src/main/java/org/qortal/controller/repository/BlockArchiver.java index 8757bf32..63d61ef8 100644 --- a/src/main/java/org/qortal/controller/repository/BlockArchiver.java +++ b/src/main/java/org/qortal/controller/repository/BlockArchiver.java @@ -16,7 +16,7 @@ public class BlockArchiver implements Runnable { private static final Logger LOGGER = LogManager.getLogger(BlockArchiver.class); - private static final long INITIAL_SLEEP_PERIOD = 0L; // TODO: 5 * 60 * 1000L + 1234L; // ms + private static final long INITIAL_SLEEP_PERIOD = 5 * 60 * 1000L + 1234L; // ms public void run() { Thread.currentThread().setName("Block archiver"); From d7d0848e9001946fbc3c16d93206ec7d58e93457 Mon Sep 17 00:00:00 2001 From: CalDescent Date: Mon, 3 Oct 2022 10:58:47 +0100 Subject: [PATCH 38/53] Return empty levels in GET /addresses/online/levels --- src/main/java/org/qortal/api/resource/AddressesResource.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main/java/org/qortal/api/resource/AddressesResource.java b/src/main/java/org/qortal/api/resource/AddressesResource.java index 4de8d908..468b90a8 100644 --- a/src/main/java/org/qortal/api/resource/AddressesResource.java +++ b/src/main/java/org/qortal/api/resource/AddressesResource.java @@ -205,6 +205,10 @@ public class AddressesResource { try (final Repository repository = RepositoryManager.getRepository()) { List onlineAccountLevels = new ArrayList<>(); + // Prepopulate all levels + for (int i=0; i<=10; i++) + onlineAccountLevels.add(new OnlineAccountLevel(i, 0)); + for (OnlineAccountData onlineAccountData : onlineAccounts) { try { final int minterLevel = Account.getRewardShareEffectiveMintingLevelIncludingLevelZero(repository, onlineAccountData.getPublicKey()); From 6fee2c3e9e62b83f05cd06a250253287027f6da0 Mon Sep 17 00:00:00 2001 From: CalDescent Date: Tue, 4 Oct 2022 20:08:30 +0100 Subject: [PATCH 39/53] Bump version to 3.6.1 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index e045e0f4..3be7fff3 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ 4.0.0 org.qortal qortal - 3.6.0 + 3.6.1 jar true From 05e5733e415aa6fdcfe20b8e56e11bbecf98fb03 Mon Sep 17 00:00:00 2001 From: CalDescent Date: Wed, 5 Oct 2022 15:29:29 +0100 Subject: [PATCH 40/53] Catch JSON exceptions in PirateChainWalletController. This could prevent additional wallets from being initialized if connection was lost while syncing an existing one. --- .../PirateChainWalletController.java | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/main/java/org/qortal/controller/PirateChainWalletController.java b/src/main/java/org/qortal/controller/PirateChainWalletController.java index 1eac4b3a..333c2cda 100644 --- a/src/main/java/org/qortal/controller/PirateChainWalletController.java +++ b/src/main/java/org/qortal/controller/PirateChainWalletController.java @@ -4,6 +4,7 @@ import com.rust.litewalletjni.LiteWalletJni; import org.apache.commons.io.FileUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.json.JSONException; import org.json.JSONObject; import org.qortal.arbitrary.ArbitraryDataFile; import org.qortal.arbitrary.ArbitraryDataReader; @@ -99,14 +100,19 @@ public class PirateChainWalletController extends Thread { LOGGER.debug("Syncing Pirate Chain wallet..."); String response = LiteWalletJni.execute("sync", ""); LOGGER.debug("sync response: {}", response); - JSONObject json = new JSONObject(response); - if (json.has("result")) { - String result = json.getString("result"); - // We may have to set wallet to ready if this is the first ever successful sync - if (Objects.equals(result, "success")) { - this.currentWallet.setReady(true); + try { + JSONObject json = new JSONObject(response); + if (json.has("result")) { + String result = json.getString("result"); + + // We may have to set wallet to ready if this is the first ever successful sync + if (Objects.equals(result, "success")) { + this.currentWallet.setReady(true); + } } + } catch (JSONException e) { + LOGGER.info("Unable to interpret JSON", e); } // Rate limit sync attempts From 04a30a9edfbc1fc7052dfc1161c3df37bc332fb1 Mon Sep 17 00:00:00 2001 From: CalDescent Date: Fri, 7 Oct 2022 11:05:24 +0100 Subject: [PATCH 41/53] Limit to 240 blocks in syncToPeerChain(). Should fix OutOfMemoryException often seen when syncing from 1000+ blocks behind the chain tip. --- src/main/java/org/qortal/controller/Synchronizer.java | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/qortal/controller/Synchronizer.java b/src/main/java/org/qortal/controller/Synchronizer.java index a8d91f52..0fe9a56b 100644 --- a/src/main/java/org/qortal/controller/Synchronizer.java +++ b/src/main/java/org/qortal/controller/Synchronizer.java @@ -1246,7 +1246,14 @@ public class Synchronizer extends Thread { int numberSignaturesRequired = additionalPeerBlocksAfterCommonBlock - peerBlockSignatures.size(); int retryCount = 0; - while (height < peerHeight) { + + // Keep fetching blocks from peer until we reach their tip, or reach a count of MAXIMUM_COMMON_DELTA blocks. + // We need to limit the total number, otherwise too much can be loaded into memory, causing an + // OutOfMemoryException. This is common when syncing from 1000+ blocks behind the chain tip, after starting + // from a small fork that didn't become part of the main chain. This causes the entire sync process to + // use syncToPeerChain(), resulting in potentially thousands of blocks being held in memory if the limit + // below isn't applied. + while (height < peerHeight && peerBlocks.size() <= MAXIMUM_COMMON_DELTA) { if (Controller.isStopping()) return SynchronizationResult.SHUTTING_DOWN; From c61d1eef1d7d3a8a5daf0cf48d7c359497bec4a4 Mon Sep 17 00:00:00 2001 From: CalDescent Date: Fri, 7 Oct 2022 14:46:09 +0100 Subject: [PATCH 42/53] Skip GET_BLOCK_SUMMARIES requests if it can already be fulfilled entirely from the peer's chain tip block summaries cache. Loading from the cache should speed up sync decisions, particularly when choose which peer to sync from. The greater the number of connected peers, the more significant this optimization will be. It should also reduce wasted network requests and data usage. Adding this check prior to making a network request is a simple way to introduce the new cached summaries from BLOCK_SUMMARIES_V2 without having to rewrite a lot of the complex sync / peer comparison logic. Longer term we may want to rewrite that logic to read from the cache directly, but it doesn't make sense to introduce that level of risk at this point time, especially as the Synchronizer may be rewritten soon to prefer longer chains. Even so, this is still quite a high risk commit so lots of testing will be needed. --- .../org/qortal/controller/Synchronizer.java | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/src/main/java/org/qortal/controller/Synchronizer.java b/src/main/java/org/qortal/controller/Synchronizer.java index 0fe9a56b..dc70db2a 100644 --- a/src/main/java/org/qortal/controller/Synchronizer.java +++ b/src/main/java/org/qortal/controller/Synchronizer.java @@ -8,6 +8,7 @@ import java.util.*; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.ReentrantLock; import java.util.stream.Collectors; +import java.util.stream.IntStream; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -1556,7 +1557,41 @@ public class Synchronizer extends Thread { return SynchronizationResult.OK; } + private List getBlockSummariesFromCache(Peer peer, byte[] parentSignature, int numberRequested) { + List peerSummaries = peer.getChainTipSummaries(); + if (peerSummaries == null) + return null; + + // Check if the requested parent block exists in peer's summaries cache + int parentIndex = IntStream.range(0, peerSummaries.size()).filter(i -> Arrays.equals(peerSummaries.get(i).getSignature(), parentSignature)).findFirst().orElse(-1); + if (parentIndex < 0) + return null; + + // Peer's summaries contains the requested parent, so return summaries after that + // Make sure we have at least one block after the parent block + int summariesAvailable = peerSummaries.size() - parentIndex - 1; + if (summariesAvailable <= 0) + return null; + + // Don't try and return more summaries than we have, or more than were requested + int summariesToReturn = Math.min(numberRequested, summariesAvailable); + int startIndex = parentIndex + 1; + int endIndex = startIndex + summariesToReturn - 1; + if (endIndex > peerSummaries.size() - 1) + return null; + + LOGGER.trace("Serving {} block summaries from cache", summariesToReturn); + return peerSummaries.subList(startIndex, endIndex); + } + private List getBlockSummaries(Peer peer, byte[] parentSignature, int numberRequested) throws InterruptedException { + // We might be able to shortcut the response if we already have the summaries in the peer's chain tip data + List cachedSummaries = this.getBlockSummariesFromCache(peer, parentSignature, numberRequested); + if (cachedSummaries != null && !cachedSummaries.isEmpty()) + return cachedSummaries; + + LOGGER.trace("Requesting {} block summaries from peer {}", numberRequested, peer); + Message getBlockSummariesMessage = new GetBlockSummariesMessage(parentSignature, numberRequested); Message message = peer.getResponse(getBlockSummariesMessage); From 6d18708e8da0d34571b8a0a278a781c328f7eb8f Mon Sep 17 00:00:00 2001 From: CalDescent Date: Fri, 7 Oct 2022 14:47:46 +0100 Subject: [PATCH 43/53] Reduce INITIAL_BLOCK_STEP from 8 to 7. This allows the first pass to always be served from the peer's cache of 8 summaries. This allows a maximum of 7 to be returned, because the 8th spot is needed for the parent block's signature. --- src/main/java/org/qortal/controller/Synchronizer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/qortal/controller/Synchronizer.java b/src/main/java/org/qortal/controller/Synchronizer.java index dc70db2a..ccb3dfdd 100644 --- a/src/main/java/org/qortal/controller/Synchronizer.java +++ b/src/main/java/org/qortal/controller/Synchronizer.java @@ -44,7 +44,7 @@ public class Synchronizer extends Thread { private static final int SYNC_BATCH_SIZE = 1000; // XXX move to Settings? /** Initial jump back of block height when searching for common block with peer */ - private static final int INITIAL_BLOCK_STEP = 8; + private static final int INITIAL_BLOCK_STEP = 7; /** Maximum jump back of block height when searching for common block with peer */ private static final int MAXIMUM_BLOCK_STEP = 128; From 8619cb4d67acce947c5bd6e4ee3f51fcfba680b4 Mon Sep 17 00:00:00 2001 From: Nuc1eoN <2538022+Nuc1eoN@users.noreply.github.com> Date: Fri, 7 Oct 2022 23:35:35 +0200 Subject: [PATCH 44/53] Mark start/stop scripts as executables The `start.sh` & `stop.sh` scripts have already been marked as executables in the source folder... But since we have only piped their contents, we need to set correct file permissions again. --- tools/build-zip.sh | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tools/build-zip.sh b/tools/build-zip.sh index b52b5da7..f423bca1 100755 --- a/tools/build-zip.sh +++ b/tools/build-zip.sh @@ -58,6 +58,9 @@ git show HEAD:log4j2.properties > ${build_dir}/log4j2.properties git show HEAD:start.sh > ${build_dir}/start.sh git show HEAD:stop.sh > ${build_dir}/stop.sh +chmod +x ${build_dir}/start.sh +chmod +x ${build_dir}/stop.sh + printf "{\n}\n" > ${build_dir}/settings.json gtouch -d ${commit_ts%%+??:??} ${build_dir} ${build_dir}/* From 8f551fdee4b3708d05d3b0eb76de4ed601e3fab3 Mon Sep 17 00:00:00 2001 From: CalDescent Date: Sun, 9 Oct 2022 14:11:28 +0100 Subject: [PATCH 45/53] Revert "Skip GET_BLOCK_SUMMARIES requests if it can already be fulfilled entirely from the peer's chain tip block summaries cache." This reverts commit 8cedf618f45a5d1ab24633e9b32972e663c3dcdd. --- .../org/qortal/controller/Synchronizer.java | 35 ------------------- 1 file changed, 35 deletions(-) diff --git a/src/main/java/org/qortal/controller/Synchronizer.java b/src/main/java/org/qortal/controller/Synchronizer.java index ccb3dfdd..e4419249 100644 --- a/src/main/java/org/qortal/controller/Synchronizer.java +++ b/src/main/java/org/qortal/controller/Synchronizer.java @@ -8,7 +8,6 @@ import java.util.*; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.ReentrantLock; import java.util.stream.Collectors; -import java.util.stream.IntStream; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -1557,41 +1556,7 @@ public class Synchronizer extends Thread { return SynchronizationResult.OK; } - private List getBlockSummariesFromCache(Peer peer, byte[] parentSignature, int numberRequested) { - List peerSummaries = peer.getChainTipSummaries(); - if (peerSummaries == null) - return null; - - // Check if the requested parent block exists in peer's summaries cache - int parentIndex = IntStream.range(0, peerSummaries.size()).filter(i -> Arrays.equals(peerSummaries.get(i).getSignature(), parentSignature)).findFirst().orElse(-1); - if (parentIndex < 0) - return null; - - // Peer's summaries contains the requested parent, so return summaries after that - // Make sure we have at least one block after the parent block - int summariesAvailable = peerSummaries.size() - parentIndex - 1; - if (summariesAvailable <= 0) - return null; - - // Don't try and return more summaries than we have, or more than were requested - int summariesToReturn = Math.min(numberRequested, summariesAvailable); - int startIndex = parentIndex + 1; - int endIndex = startIndex + summariesToReturn - 1; - if (endIndex > peerSummaries.size() - 1) - return null; - - LOGGER.trace("Serving {} block summaries from cache", summariesToReturn); - return peerSummaries.subList(startIndex, endIndex); - } - private List getBlockSummaries(Peer peer, byte[] parentSignature, int numberRequested) throws InterruptedException { - // We might be able to shortcut the response if we already have the summaries in the peer's chain tip data - List cachedSummaries = this.getBlockSummariesFromCache(peer, parentSignature, numberRequested); - if (cachedSummaries != null && !cachedSummaries.isEmpty()) - return cachedSummaries; - - LOGGER.trace("Requesting {} block summaries from peer {}", numberRequested, peer); - Message getBlockSummariesMessage = new GetBlockSummariesMessage(parentSignature, numberRequested); Message message = peer.getResponse(getBlockSummariesMessage); From d0a637ed2f2d9df28d7db91f92f684a5e777207d Mon Sep 17 00:00:00 2001 From: CalDescent Date: Sun, 9 Oct 2022 19:11:20 +0100 Subject: [PATCH 46/53] Revert "Reduce INITIAL_BLOCK_STEP from 8 to 7." This reverts commit 0088ba8485a73c723a9ea4555e0435d42df20a3f. --- src/main/java/org/qortal/controller/Synchronizer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/qortal/controller/Synchronizer.java b/src/main/java/org/qortal/controller/Synchronizer.java index e4419249..0fe9a56b 100644 --- a/src/main/java/org/qortal/controller/Synchronizer.java +++ b/src/main/java/org/qortal/controller/Synchronizer.java @@ -43,7 +43,7 @@ public class Synchronizer extends Thread { private static final int SYNC_BATCH_SIZE = 1000; // XXX move to Settings? /** Initial jump back of block height when searching for common block with peer */ - private static final int INITIAL_BLOCK_STEP = 7; + private static final int INITIAL_BLOCK_STEP = 8; /** Maximum jump back of block height when searching for common block with peer */ private static final int MAXIMUM_BLOCK_STEP = 128; From c3d225fc6907c2a0f4b4551b655bde7665e62180 Mon Sep 17 00:00:00 2001 From: CalDescent Date: Sun, 9 Oct 2022 20:11:01 +0100 Subject: [PATCH 47/53] Always use BlockSummariesMessage V1 (instead of V2) when responding to GetBlockSummaries requests. This should hopefully fix a potential issue where peer's chain tip data becomes contaminated with other summary data, causing incorrect sync decisions. --- src/main/java/org/qortal/controller/Controller.java | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/main/java/org/qortal/controller/Controller.java b/src/main/java/org/qortal/controller/Controller.java index ce994757..1e028ebc 100644 --- a/src/main/java/org/qortal/controller/Controller.java +++ b/src/main/java/org/qortal/controller/Controller.java @@ -1430,9 +1430,7 @@ public class Controller extends Thread { // 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 = peer.getPeersVersion() >= BlockSummariesV2Message.MINIMUM_PEER_VERSION - ? new BlockSummariesV2Message(Collections.emptyList()) - : new BlockSummariesMessage(Collections.emptyList()); + Message blockSummariesMessage = new BlockSummariesMessage(Collections.emptyList()); blockSummariesMessage.setId(message.getId()); @@ -1491,9 +1489,7 @@ public class Controller extends Thread { this.stats.getBlockSummariesStats.fullyFromCache.incrementAndGet(); } - Message blockSummariesMessage = peer.getPeersVersion() >= BlockSummariesV2Message.MINIMUM_PEER_VERSION - ? new BlockSummariesV2Message(blockSummaries) - : new BlockSummariesMessage(blockSummaries); + Message blockSummariesMessage = new BlockSummariesMessage(blockSummaries); blockSummariesMessage.setId(message.getId()); if (!peer.sendMessage(blockSummariesMessage)) peer.disconnect("failed to send block summaries"); From cfa55afdfac5f3bce9d2e54c2923f2bb9e06ad10 Mon Sep 17 00:00:00 2001 From: CalDescent Date: Sun, 9 Oct 2022 20:37:39 +0100 Subject: [PATCH 48/53] GenericUnknownMessage.MINIMUM_PEER_VERSION set to 3.6.1. This should ideally have been set in the 3.6.1 release, but not setting it is unlikely to have caused any problems. --- .../java/org/qortal/network/message/GenericUnknownMessage.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/qortal/network/message/GenericUnknownMessage.java b/src/main/java/org/qortal/network/message/GenericUnknownMessage.java index 15faaa1b..dea9f2b8 100644 --- a/src/main/java/org/qortal/network/message/GenericUnknownMessage.java +++ b/src/main/java/org/qortal/network/message/GenericUnknownMessage.java @@ -4,7 +4,7 @@ import java.nio.ByteBuffer; public class GenericUnknownMessage extends Message { - public static final long MINIMUM_PEER_VERSION = 0x03000400cbL; + public static final long MINIMUM_PEER_VERSION = 0x0300060001L; public GenericUnknownMessage() { super(MessageType.GENERIC_UNKNOWN); From a7f0bac211cc3f8022427412ed85e049160edc0a Mon Sep 17 00:00:00 2001 From: CalDescent Date: Mon, 10 Oct 2022 10:28:36 +0100 Subject: [PATCH 49/53] Discard BLOCK_SUMMARIES_V2 messages with an ID (thanks to @catbref for the code) This is a better fix for the "contaminated chain tip summaries" issue. Need to reduce the logging level to debug before release. --- src/main/java/org/qortal/controller/Controller.java | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/main/java/org/qortal/controller/Controller.java b/src/main/java/org/qortal/controller/Controller.java index 1e028ebc..2146c86b 100644 --- a/src/main/java/org/qortal/controller/Controller.java +++ b/src/main/java/org/qortal/controller/Controller.java @@ -1599,6 +1599,17 @@ public class Controller extends Thread { } } + if (message.hasId()) { + /* + * Experimental proof-of-concept: discard messages with ID + * These are 'late' reply messages received after timeout has expired, + * having been passed upwards from Peer to Network to Controller. + * Hence, these are NOT simple "here's my chain tip" broadcasts from other peers. + */ + LOGGER.info("Discarding late {} message with ID {} from {}", message.getType().name(), message.getId(), peer); + return; + } + // Update peer chain tip data peer.setChainTipSummaries(blockSummariesV2Message.getBlockSummaries()); From 38d52b951c843f86a199badae5b452d8394ddaaa Mon Sep 17 00:00:00 2001 From: CalDescent Date: Mon, 10 Oct 2022 10:28:44 +0100 Subject: [PATCH 50/53] Revert "Always use BlockSummariesMessage V1 (instead of V2) when responding to GetBlockSummaries requests." This reverts commit 2d58118d7cfa717a4a6521b9d2fa2bd325c7e5ea. --- src/main/java/org/qortal/controller/Controller.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/qortal/controller/Controller.java b/src/main/java/org/qortal/controller/Controller.java index 2146c86b..93cbae92 100644 --- a/src/main/java/org/qortal/controller/Controller.java +++ b/src/main/java/org/qortal/controller/Controller.java @@ -1430,7 +1430,9 @@ public class Controller extends Thread { // 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()); @@ -1489,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"); From 20e20f45d134dddd1399a2f6dabf534f7314a172 Mon Sep 17 00:00:00 2001 From: CalDescent Date: Mon, 10 Oct 2022 19:06:08 +0100 Subject: [PATCH 51/53] Bump version to 3.6.2 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 3be7fff3..591801e9 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ 4.0.0 org.qortal qortal - 3.6.1 + 3.6.2 jar true From d1db2fc2389ae59517f95204a3ea297a9099916c Mon Sep 17 00:00:00 2001 From: CalDescent Date: Wed, 12 Oct 2022 08:52:58 +0100 Subject: [PATCH 52/53] Fix for issue in BLOCK_SUMMARIES_V2 when sending an empty array of summaries. The BLOCK_SUMMARIES message type would differentiate between an empty response and a missing/invalid response. However, in V2, a response with empty summaries would throw a BufferUnderflowException and be treated by the caller as a null message. This caused problems when trying to find a common block with peers that have diverged by more than 8 blocks. With V1 the caller would know to search back further (e.g. 16 blocks) but in V2 it was treated as "no response" and so the caller would give up instead of increasing the look-back threshold. This fix will identify BLOCK_SUMMARIES_V2 messages with no content, and return an empty array of block summaries instead of a null message. Should be enough to recover any stuck nodes, as long as they haven't diverged more than 240 blocks from the main chain. --- .../qortal/network/message/BlockSummariesV2Message.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/qortal/network/message/BlockSummariesV2Message.java b/src/main/java/org/qortal/network/message/BlockSummariesV2Message.java index 6ed6c8aa..62428cc0 100644 --- a/src/main/java/org/qortal/network/message/BlockSummariesV2Message.java +++ b/src/main/java/org/qortal/network/message/BlockSummariesV2Message.java @@ -68,13 +68,18 @@ public class BlockSummariesV2Message extends Message { } public static Message fromByteBuffer(int id, ByteBuffer bytes) { + List blockSummaries = new ArrayList<>(); + + // If there are no bytes remaining then we can treat this as an empty array of summaries + if (bytes.remaining() == 0) + return new BlockSummariesV2Message(id, blockSummaries); + 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 blockSummaries = new ArrayList<>(); while (bytes.hasRemaining()) { byte[] signature = new byte[BlockTransformer.BLOCK_SIGNATURE_LENGTH]; bytes.get(signature); From 09480a3625e0b367463b15680f5ce330af41086a Mon Sep 17 00:00:00 2001 From: CalDescent Date: Wed, 12 Oct 2022 08:54:27 +0100 Subject: [PATCH 53/53] Bump version to 3.6.3 finishing log supression for docker instances. --- Dockerfile | 1 - pom.xml | 2 +- start.sh | 6 +++--- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/Dockerfile b/Dockerfile index 4fb77167..728ad25b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,6 @@ FROM maven:3-openjdk-11 as builder - WORKDIR /work COPY . . RUN mvn clean package diff --git a/pom.xml b/pom.xml index 591801e9..5f439cad 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ 4.0.0 org.qortal qortal - 3.6.2 + 3.6.3 jar true diff --git a/start.sh b/start.sh index 3cee1178..598ed533 100755 --- a/start.sh +++ b/start.sh @@ -55,9 +55,9 @@ else -Djava.net.preferIPv4Stack=false \ ${JVM_MEMORY_ARGS} \ -jar qortal.jar + # Save backgrounded process's PID + echo $! > run.pid + echo qortal running as pid $! fi -# Save backgrounded process's PID -# echo $! > run.pid -# echo qortal running as pid $!