mirror of
https://github.com/Qortal/qortal.git
synced 2025-07-21 20:01:23 +00:00
foreign fees manager implementation, feeCeiling -> feeRequired name change, thread-safety measures for fee values, fee backup file implementation, unsigned fees socket implementation
This commit is contained in:
parent
bcf3538d18
commit
144d6cc5c7
@ -197,6 +197,7 @@ public class ApiService {
|
||||
context.addServlet(DataMonitorSocket.class, "/websockets/datamonitor");
|
||||
context.addServlet(ActiveChatsWebSocket.class, "/websockets/chat/active/*");
|
||||
context.addServlet(ChatMessagesWebSocket.class, "/websockets/chat/messages");
|
||||
context.addServlet(UnsignedFeesSocket.class, "/websockets/crosschain/unsignedfees");
|
||||
context.addServlet(TradeOffersWebSocket.class, "/websockets/crosschain/tradeoffers");
|
||||
context.addServlet(TradeBotWebSocket.class, "/websockets/crosschain/tradebot");
|
||||
context.addServlet(TradePresenceWebSocket.class, "/websockets/crosschain/tradepresence");
|
||||
|
@ -304,11 +304,11 @@ public class BitcoinyTBDRequest {
|
||||
private String networkName;
|
||||
|
||||
/**
|
||||
* Fee Ceiling
|
||||
* Fee Required
|
||||
*
|
||||
* web search, LTC fee ceiling = 1000L
|
||||
* web search, LTC fee required = 1000L
|
||||
*/
|
||||
private long feeCeiling;
|
||||
private long feeRequired;
|
||||
|
||||
/**
|
||||
* Extended Public Key
|
||||
@ -570,8 +570,8 @@ public class BitcoinyTBDRequest {
|
||||
return this.networkName;
|
||||
}
|
||||
|
||||
public long getFeeCeiling() {
|
||||
return this.feeCeiling;
|
||||
public long getFeeRequired() {
|
||||
return this.feeRequired;
|
||||
}
|
||||
|
||||
public String getExtendedPublicKey() {
|
||||
@ -671,7 +671,7 @@ public class BitcoinyTBDRequest {
|
||||
", minimumOrderAmount=" + minimumOrderAmount +
|
||||
", feePerKb=" + feePerKb +
|
||||
", networkName='" + networkName + '\'' +
|
||||
", feeCeiling=" + feeCeiling +
|
||||
", feeRequired=" + feeRequired +
|
||||
", extendedPublicKey='" + extendedPublicKey + '\'' +
|
||||
", sendAmount=" + sendAmount +
|
||||
", sendingFeePerByte=" + sendingFeePerByte +
|
||||
|
@ -502,10 +502,10 @@ public class CrossChainBitcoinResource {
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/feeceiling")
|
||||
@Path("/feerequired")
|
||||
@Operation(
|
||||
summary = "Returns Bitcoin fee per Kb.",
|
||||
description = "Returns Bitcoin fee per Kb.",
|
||||
summary = "The total fee required for unlocking BTC to the trade offer creator.",
|
||||
description = "This is in sats for a transaction that is approximately 300 kB in size.",
|
||||
responses = {
|
||||
@ApiResponse(
|
||||
content = @Content(
|
||||
@ -516,17 +516,17 @@ public class CrossChainBitcoinResource {
|
||||
)
|
||||
}
|
||||
)
|
||||
public String getBitcoinFeeCeiling() {
|
||||
public String getBitcoinFeeRequired() {
|
||||
Bitcoin bitcoin = Bitcoin.getInstance();
|
||||
|
||||
return String.valueOf(bitcoin.getFeeCeiling());
|
||||
return String.valueOf(bitcoin.getFeeRequired());
|
||||
}
|
||||
|
||||
@POST
|
||||
@Path("/updatefeeceiling")
|
||||
@Path("/updatefeerequired")
|
||||
@Operation(
|
||||
summary = "Sets Bitcoin fee ceiling.",
|
||||
description = "Sets Bitcoin fee ceiling.",
|
||||
summary = "The total fee required for unlocking BTC to the trade offer creator.",
|
||||
description = "This is in sats for a transaction that is approximately 300 kB in size.",
|
||||
requestBody = @RequestBody(
|
||||
required = true,
|
||||
content = @Content(
|
||||
@ -545,13 +545,13 @@ public class CrossChainBitcoinResource {
|
||||
}
|
||||
)
|
||||
@ApiErrors({ApiError.INVALID_PRIVATE_KEY, ApiError.INVALID_CRITERIA})
|
||||
public String setBitcoinFeeCeiling(@HeaderParam(Security.API_KEY_HEADER) String apiKey, String fee) {
|
||||
public String setBitcoinFeeRequired(@HeaderParam(Security.API_KEY_HEADER) String apiKey, String fee) {
|
||||
Security.checkApiCallAllowed(request);
|
||||
|
||||
Bitcoin bitcoin = Bitcoin.getInstance();
|
||||
|
||||
try {
|
||||
return CrossChainUtils.setFeeCeiling(bitcoin, fee);
|
||||
return CrossChainUtils.setFeeRequired(bitcoin, fee);
|
||||
}
|
||||
catch (IllegalArgumentException e) {
|
||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_CRITERIA);
|
||||
|
@ -502,10 +502,10 @@ public class CrossChainDigibyteResource {
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/feeceiling")
|
||||
@Path("/feerequired")
|
||||
@Operation(
|
||||
summary = "Returns Digibyte fee per Kb.",
|
||||
description = "Returns Digibyte fee per Kb.",
|
||||
summary = "The total fee required for unlocking DGB to the trade offer creator.",
|
||||
description = "This is in sats for a transaction that is approximately 300 kB in size.",
|
||||
responses = {
|
||||
@ApiResponse(
|
||||
content = @Content(
|
||||
@ -516,17 +516,17 @@ public class CrossChainDigibyteResource {
|
||||
)
|
||||
}
|
||||
)
|
||||
public String getDigibyteFeeCeiling() {
|
||||
public String getDigibyteFeeRequired() {
|
||||
Digibyte digibyte = Digibyte.getInstance();
|
||||
|
||||
return String.valueOf(digibyte.getFeeCeiling());
|
||||
return String.valueOf(digibyte.getFeeRequired());
|
||||
}
|
||||
|
||||
@POST
|
||||
@Path("/updatefeeceiling")
|
||||
@Path("/updatefeerequired")
|
||||
@Operation(
|
||||
summary = "Sets Digibyte fee ceiling.",
|
||||
description = "Sets Digibyte fee ceiling.",
|
||||
summary = "The total fee required for unlocking DGB to the trade offer creator.",
|
||||
description = "This is in sats for a transaction that is approximately 300 kB in size.",
|
||||
requestBody = @RequestBody(
|
||||
required = true,
|
||||
content = @Content(
|
||||
@ -545,13 +545,13 @@ public class CrossChainDigibyteResource {
|
||||
}
|
||||
)
|
||||
@ApiErrors({ApiError.INVALID_PRIVATE_KEY, ApiError.INVALID_CRITERIA})
|
||||
public String setDigibyteFeeCeiling(@HeaderParam(Security.API_KEY_HEADER) String apiKey, String fee) {
|
||||
public String setDigibyteFeeRequired(@HeaderParam(Security.API_KEY_HEADER) String apiKey, String fee) {
|
||||
Security.checkApiCallAllowed(request);
|
||||
|
||||
Digibyte digibyte = Digibyte.getInstance();
|
||||
|
||||
try {
|
||||
return CrossChainUtils.setFeeCeiling(digibyte, fee);
|
||||
return CrossChainUtils.setFeeRequired(digibyte, fee);
|
||||
}
|
||||
catch (IllegalArgumentException e) {
|
||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_CRITERIA);
|
||||
|
@ -502,10 +502,10 @@ public class CrossChainDogecoinResource {
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/feeceiling")
|
||||
@Path("/feerequired")
|
||||
@Operation(
|
||||
summary = "Returns Dogecoin fee per Kb.",
|
||||
description = "Returns Dogecoin fee per Kb.",
|
||||
summary = "The total fee required for unlocking DOGE to the trade offer creator.",
|
||||
description = "This is in sats for a transaction that is approximately 300 kB in size.",
|
||||
responses = {
|
||||
@ApiResponse(
|
||||
content = @Content(
|
||||
@ -516,17 +516,17 @@ public class CrossChainDogecoinResource {
|
||||
)
|
||||
}
|
||||
)
|
||||
public String getDogecoinFeeCeiling() {
|
||||
public String getDogecoinFeeRequired() {
|
||||
Dogecoin dogecoin = Dogecoin.getInstance();
|
||||
|
||||
return String.valueOf(dogecoin.getFeeCeiling());
|
||||
return String.valueOf(dogecoin.getFeeRequired());
|
||||
}
|
||||
|
||||
@POST
|
||||
@Path("/updatefeeceiling")
|
||||
@Path("/updatefeerequired")
|
||||
@Operation(
|
||||
summary = "Sets Dogecoin fee ceiling.",
|
||||
description = "Sets Dogecoin fee ceiling.",
|
||||
summary = "The total fee required for unlocking DOGE to the trade offer creator.",
|
||||
description = "This is in sats for a transaction that is approximately 300 kB in size.",
|
||||
requestBody = @RequestBody(
|
||||
required = true,
|
||||
content = @Content(
|
||||
@ -545,13 +545,13 @@ public class CrossChainDogecoinResource {
|
||||
}
|
||||
)
|
||||
@ApiErrors({ApiError.INVALID_PRIVATE_KEY, ApiError.INVALID_CRITERIA})
|
||||
public String setDogecoinFeeCeiling(@HeaderParam(Security.API_KEY_HEADER) String apiKey, String fee) {
|
||||
public String setDogecoinFeeRequired(@HeaderParam(Security.API_KEY_HEADER) String apiKey, String fee) {
|
||||
Security.checkApiCallAllowed(request);
|
||||
|
||||
Dogecoin dogecoin = Dogecoin.getInstance();
|
||||
|
||||
try {
|
||||
return CrossChainUtils.setFeeCeiling(dogecoin, fee);
|
||||
return CrossChainUtils.setFeeRequired(dogecoin, fee);
|
||||
}
|
||||
catch (IllegalArgumentException e) {
|
||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_CRITERIA);
|
||||
|
@ -540,10 +540,10 @@ public class CrossChainLitecoinResource {
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/feeceiling")
|
||||
@Path("/feerequired")
|
||||
@Operation(
|
||||
summary = "Returns Litecoin fee per Kb.",
|
||||
description = "Returns Litecoin fee per Kb.",
|
||||
summary = "The total fee required for unlocking LTC to the trade offer creator.",
|
||||
description = "This is in sats for a transaction that is approximately 300 kB in size.",
|
||||
responses = {
|
||||
@ApiResponse(
|
||||
content = @Content(
|
||||
@ -554,17 +554,17 @@ public class CrossChainLitecoinResource {
|
||||
)
|
||||
}
|
||||
)
|
||||
public String getLitecoinFeeCeiling() {
|
||||
public String getLitecoinFeeRequired() {
|
||||
Litecoin litecoin = Litecoin.getInstance();
|
||||
|
||||
return String.valueOf(litecoin.getFeeCeiling());
|
||||
return String.valueOf(litecoin.getFeeRequired());
|
||||
}
|
||||
|
||||
@POST
|
||||
@Path("/updatefeeceiling")
|
||||
@Path("/updatefeerequired")
|
||||
@Operation(
|
||||
summary = "Sets Litecoin fee ceiling.",
|
||||
description = "Sets Litecoin fee ceiling.",
|
||||
summary = "The total fee required for unlocking LTC to the trade offer creator.",
|
||||
description = "This is in sats for a transaction that is approximately 300 kB in size.",
|
||||
requestBody = @RequestBody(
|
||||
required = true,
|
||||
content = @Content(
|
||||
@ -583,13 +583,13 @@ public class CrossChainLitecoinResource {
|
||||
}
|
||||
)
|
||||
@ApiErrors({ApiError.INVALID_PRIVATE_KEY, ApiError.INVALID_CRITERIA})
|
||||
public String setLitecoinFeeCeiling(@HeaderParam(Security.API_KEY_HEADER) String apiKey, String fee) {
|
||||
public String setLitecoinFeeRequired(@HeaderParam(Security.API_KEY_HEADER) String apiKey, String fee) {
|
||||
Security.checkApiCallAllowed(request);
|
||||
|
||||
Litecoin litecoin = Litecoin.getInstance();
|
||||
|
||||
try {
|
||||
return CrossChainUtils.setFeeCeiling(litecoin, fee);
|
||||
return CrossChainUtils.setFeeRequired(litecoin, fee);
|
||||
}
|
||||
catch (IllegalArgumentException e) {
|
||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_CRITERIA);
|
||||
|
@ -587,10 +587,10 @@ public class CrossChainPirateChainResource {
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/feeceiling")
|
||||
@Path("/feerequired")
|
||||
@Operation(
|
||||
summary = "Returns PirateChain fee per Kb.",
|
||||
description = "Returns PirateChain fee per Kb.",
|
||||
summary = "The total fee required for unlocking ARRR to the trade offer creator.",
|
||||
description = "The total fee required for unlocking ARRR to the trade offer creator.",
|
||||
responses = {
|
||||
@ApiResponse(
|
||||
content = @Content(
|
||||
@ -601,17 +601,17 @@ public class CrossChainPirateChainResource {
|
||||
)
|
||||
}
|
||||
)
|
||||
public String getPirateChainFeeCeiling() {
|
||||
public String getPirateChainFeeRequired() {
|
||||
PirateChain pirateChain = PirateChain.getInstance();
|
||||
|
||||
return String.valueOf(pirateChain.getFeeCeiling());
|
||||
return String.valueOf(pirateChain.getFeeRequired());
|
||||
}
|
||||
|
||||
@POST
|
||||
@Path("/updatefeeceiling")
|
||||
@Path("/updatefeerequired")
|
||||
@Operation(
|
||||
summary = "Sets PirateChain fee ceiling.",
|
||||
description = "Sets PirateChain fee ceiling.",
|
||||
summary = "The total fee required for unlocking ARRR to the trade offer creator.",
|
||||
description = "This is in sats for a transaction that is approximately 300 kB in size.",
|
||||
requestBody = @RequestBody(
|
||||
required = true,
|
||||
content = @Content(
|
||||
@ -630,13 +630,13 @@ public class CrossChainPirateChainResource {
|
||||
}
|
||||
)
|
||||
@ApiErrors({ApiError.INVALID_PRIVATE_KEY, ApiError.INVALID_CRITERIA})
|
||||
public String setPirateChainFeeCeiling(@HeaderParam(Security.API_KEY_HEADER) String apiKey, String fee) {
|
||||
public String setPirateChainFeeRequired(@HeaderParam(Security.API_KEY_HEADER) String apiKey, String fee) {
|
||||
Security.checkApiCallAllowed(request);
|
||||
|
||||
PirateChain pirateChain = PirateChain.getInstance();
|
||||
|
||||
try {
|
||||
return CrossChainUtils.setFeeCeiling(pirateChain, fee);
|
||||
return CrossChainUtils.setFeeRequired(pirateChain, fee);
|
||||
}
|
||||
catch (IllegalArgumentException e) {
|
||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_CRITERIA);
|
||||
|
@ -502,10 +502,10 @@ public class CrossChainRavencoinResource {
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/feeceiling")
|
||||
@Path("/feerequired")
|
||||
@Operation(
|
||||
summary = "Returns Ravencoin fee per Kb.",
|
||||
description = "Returns Ravencoin fee per Kb.",
|
||||
summary = "The total fee required for unlocking RVN to the trade offer creator.",
|
||||
description = "This is in sats for a transaction that is approximately 300 kB in size.",
|
||||
responses = {
|
||||
@ApiResponse(
|
||||
content = @Content(
|
||||
@ -516,17 +516,17 @@ public class CrossChainRavencoinResource {
|
||||
)
|
||||
}
|
||||
)
|
||||
public String getRavencoinFeeCeiling() {
|
||||
public String getRavencoinFeeRequired() {
|
||||
Ravencoin ravencoin = Ravencoin.getInstance();
|
||||
|
||||
return String.valueOf(ravencoin.getFeeCeiling());
|
||||
return String.valueOf(ravencoin.getFeeRequired());
|
||||
}
|
||||
|
||||
@POST
|
||||
@Path("/updatefeeceiling")
|
||||
@Path("/updatefeerequired")
|
||||
@Operation(
|
||||
summary = "Sets Ravencoin fee ceiling.",
|
||||
description = "Sets Ravencoin fee ceiling.",
|
||||
summary = "The total fee required for unlocking RVN to the trade offer creator.",
|
||||
description = "This is in sats for a transaction that is approximately 300 kB in size.",
|
||||
requestBody = @RequestBody(
|
||||
required = true,
|
||||
content = @Content(
|
||||
@ -545,13 +545,13 @@ public class CrossChainRavencoinResource {
|
||||
}
|
||||
)
|
||||
@ApiErrors({ApiError.INVALID_PRIVATE_KEY, ApiError.INVALID_CRITERIA})
|
||||
public String setRavencoinFeeCeiling(@HeaderParam(Security.API_KEY_HEADER) String apiKey, String fee) {
|
||||
public String setRavencoinFeeRequired(@HeaderParam(Security.API_KEY_HEADER) String apiKey, String fee) {
|
||||
Security.checkApiCallAllowed(request);
|
||||
|
||||
Ravencoin ravencoin = Ravencoin.getInstance();
|
||||
|
||||
try {
|
||||
return CrossChainUtils.setFeeCeiling(ravencoin, fee);
|
||||
return CrossChainUtils.setFeeRequired(ravencoin, fee);
|
||||
}
|
||||
catch (IllegalArgumentException e) {
|
||||
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_CRITERIA);
|
||||
|
@ -10,6 +10,8 @@ import io.swagger.v3.oas.annotations.parameters.RequestBody;
|
||||
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
||||
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.glassfish.jersey.media.multipart.ContentDisposition;
|
||||
import org.qortal.api.ApiError;
|
||||
import org.qortal.api.ApiErrors;
|
||||
@ -18,6 +20,7 @@ import org.qortal.api.Security;
|
||||
import org.qortal.api.model.CrossChainCancelRequest;
|
||||
import org.qortal.api.model.CrossChainTradeLedgerEntry;
|
||||
import org.qortal.api.model.CrossChainTradeSummary;
|
||||
import org.qortal.controller.ForeignFeesManager;
|
||||
import org.qortal.controller.tradebot.TradeBot;
|
||||
import org.qortal.crosschain.ACCT;
|
||||
import org.qortal.crosschain.AcctMode;
|
||||
@ -29,6 +32,8 @@ import org.qortal.data.at.ATData;
|
||||
import org.qortal.data.at.ATStateData;
|
||||
import org.qortal.data.crosschain.CrossChainTradeData;
|
||||
import org.qortal.data.crosschain.TransactionSummary;
|
||||
import org.qortal.data.crosschain.ForeignFeeDecodedData;
|
||||
import org.qortal.data.crosschain.ForeignFeeEncodedData;
|
||||
import org.qortal.data.transaction.BaseTransactionData;
|
||||
import org.qortal.data.transaction.MessageTransactionData;
|
||||
import org.qortal.data.transaction.TransactionData;
|
||||
@ -64,6 +69,8 @@ import java.util.stream.Collectors;
|
||||
@Tag(name = "Cross-Chain")
|
||||
public class CrossChainResource {
|
||||
|
||||
private static final Logger LOGGER = LogManager.getLogger(CrossChainResource.class);
|
||||
|
||||
@Context
|
||||
HttpServletRequest request;
|
||||
|
||||
@ -360,6 +367,97 @@ public class CrossChainResource {
|
||||
}
|
||||
}
|
||||
|
||||
@POST
|
||||
@Path("/signedfees")
|
||||
@Operation(
|
||||
summary = "",
|
||||
description = "",
|
||||
requestBody = @RequestBody(
|
||||
required = true,
|
||||
content = @Content(
|
||||
mediaType = MediaType.APPLICATION_JSON,
|
||||
array = @ArraySchema(
|
||||
schema = @Schema(
|
||||
implementation = ForeignFeeEncodedData.class
|
||||
)
|
||||
)
|
||||
)
|
||||
),
|
||||
responses = {
|
||||
@ApiResponse(
|
||||
description = "true on success",
|
||||
content = @Content(
|
||||
mediaType = MediaType.TEXT_PLAIN,
|
||||
schema = @Schema(
|
||||
type = "boolean"
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
)
|
||||
public String postSignedForeignFees(List<ForeignFeeEncodedData> signedFees) {
|
||||
|
||||
LOGGER.info("signedFees = " + signedFees);
|
||||
|
||||
try {
|
||||
ForeignFeesManager.getInstance().addSignedFees(signedFees);
|
||||
|
||||
return "true";
|
||||
}
|
||||
catch( Exception e ) {
|
||||
|
||||
LOGGER.error(e.getMessage(), e);
|
||||
|
||||
return "false";
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/unsignedfees/{address}")
|
||||
@Operation(
|
||||
summary = "",
|
||||
description = "",
|
||||
responses = {
|
||||
@ApiResponse(
|
||||
content = @Content(
|
||||
array = @ArraySchema(
|
||||
schema = @Schema(
|
||||
implementation = ForeignFeeEncodedData.class
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
)
|
||||
@ApiErrors({ApiError.INVALID_CRITERIA, ApiError.REPOSITORY_ISSUE})
|
||||
public List<ForeignFeeEncodedData> getUnsignedFees(@PathParam("address") String address) {
|
||||
|
||||
return ForeignFeesManager.getInstance().getUnsignedFeesForAddress(address);
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/signedfees")
|
||||
@Operation(
|
||||
summary = "",
|
||||
description = "",
|
||||
responses = {
|
||||
@ApiResponse(
|
||||
content = @Content(
|
||||
array = @ArraySchema(
|
||||
schema = @Schema(
|
||||
implementation = ForeignFeeDecodedData.class
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
)
|
||||
@ApiErrors({ApiError.INVALID_CRITERIA, ApiError.REPOSITORY_ISSUE})
|
||||
public List<ForeignFeeDecodedData> getSignedFees() {
|
||||
|
||||
return ForeignFeesManager.getInstance().getSignedFees();
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode Public Key
|
||||
*
|
||||
|
@ -16,6 +16,9 @@ import org.qortal.crosschain.*;
|
||||
import org.qortal.data.at.ATData;
|
||||
import org.qortal.data.at.ATStateData;
|
||||
import org.qortal.data.crosschain.*;
|
||||
import org.qortal.event.EventBus;
|
||||
import org.qortal.event.LockingFeeUpdateEvent;
|
||||
import org.qortal.event.RequiredFeeUpdateEvent;
|
||||
import org.qortal.repository.DataException;
|
||||
import org.qortal.repository.Repository;
|
||||
import org.qortal.utils.Amounts;
|
||||
@ -23,14 +26,9 @@ import org.qortal.utils.BitTwiddling;
|
||||
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.io.PrintWriter;
|
||||
import java.io.Writer;
|
||||
import java.text.DateFormat;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.time.Instant;
|
||||
import java.time.ZoneId;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@ -103,11 +101,13 @@ public class CrossChainUtils {
|
||||
|
||||
bitcoiny.setFeePerKb(Coin.valueOf(satoshis) );
|
||||
|
||||
EventBus.INSTANCE.notify(new LockingFeeUpdateEvent());
|
||||
|
||||
return String.valueOf(bitcoiny.getFeePerKb().value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set Fee Ceiling
|
||||
* Set Fee Required
|
||||
*
|
||||
* @param bitcoiny the blockchain support
|
||||
* @param fee the fee in satoshis
|
||||
@ -116,14 +116,16 @@ public class CrossChainUtils {
|
||||
*
|
||||
* @throws IllegalArgumentException if invalid
|
||||
*/
|
||||
public static String setFeeCeiling(Bitcoiny bitcoiny, String fee) throws IllegalArgumentException{
|
||||
public static String setFeeRequired(Bitcoiny bitcoiny, String fee) throws IllegalArgumentException{
|
||||
|
||||
long satoshis = Long.parseLong(fee);
|
||||
if( satoshis < 0 ) throw new IllegalArgumentException("can't set fee to negative number");
|
||||
|
||||
bitcoiny.setFeeCeiling( Long.parseLong(fee));
|
||||
bitcoiny.setFeeRequired( Long.parseLong(fee));
|
||||
|
||||
return String.valueOf(bitcoiny.getFeeCeiling());
|
||||
EventBus.INSTANCE.notify(new RequiredFeeUpdateEvent(bitcoiny));
|
||||
|
||||
return String.valueOf(bitcoiny.getFeeRequired());
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -0,0 +1,81 @@
|
||||
package org.qortal.api.websocket;
|
||||
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.eclipse.jetty.websocket.api.Session;
|
||||
import org.eclipse.jetty.websocket.api.WebSocketException;
|
||||
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketClose;
|
||||
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect;
|
||||
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketError;
|
||||
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage;
|
||||
import org.eclipse.jetty.websocket.api.annotations.WebSocket;
|
||||
import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory;
|
||||
import org.qortal.data.crosschain.UnsignedFeeEvent;
|
||||
import org.qortal.event.Event;
|
||||
import org.qortal.event.EventBus;
|
||||
import org.qortal.event.FeeWaitingEvent;
|
||||
import org.qortal.event.Listener;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.StringWriter;
|
||||
|
||||
@WebSocket
|
||||
@SuppressWarnings("serial")
|
||||
public class UnsignedFeesSocket extends ApiWebSocket implements Listener {
|
||||
|
||||
private static final Logger LOGGER = LogManager.getLogger(UnsignedFeesSocket.class);
|
||||
|
||||
@Override
|
||||
public void configure(WebSocketServletFactory factory) {
|
||||
LOGGER.info("configure");
|
||||
|
||||
factory.register(UnsignedFeesSocket.class);
|
||||
|
||||
EventBus.INSTANCE.addListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void listen(Event event) {
|
||||
if (!(event instanceof FeeWaitingEvent))
|
||||
return;
|
||||
|
||||
for (Session session : getSessions())
|
||||
sendUnsignedFeeEvent(session, new UnsignedFeeEvent());
|
||||
}
|
||||
|
||||
|
||||
@OnWebSocketConnect
|
||||
@Override
|
||||
public void onWebSocketConnect(Session session) {
|
||||
super.onWebSocketConnect(session);
|
||||
}
|
||||
|
||||
@OnWebSocketClose
|
||||
@Override
|
||||
public void onWebSocketClose(Session session, int statusCode, String reason) {
|
||||
super.onWebSocketClose(session, statusCode, reason);
|
||||
}
|
||||
|
||||
@OnWebSocketError
|
||||
public void onWebSocketError(Session session, Throwable throwable) {
|
||||
/* We ignore errors for now, but method here to silence log spam */
|
||||
}
|
||||
|
||||
@OnWebSocketMessage
|
||||
public void onWebSocketMessage(Session session, String message) {
|
||||
LOGGER.info("onWebSocketMessage: message = " + message);
|
||||
}
|
||||
|
||||
private void sendUnsignedFeeEvent(Session session, UnsignedFeeEvent unsignedFeeEvent) {
|
||||
StringWriter stringWriter = new StringWriter();
|
||||
|
||||
try {
|
||||
marshall(stringWriter, unsignedFeeEvent);
|
||||
|
||||
session.getRemote().sendStringByFuture(stringWriter.toString());
|
||||
} catch (IOException | WebSocketException e) {
|
||||
// No output this time
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -560,6 +560,9 @@ public class Controller extends Thread {
|
||||
LOGGER.info("Starting online accounts manager");
|
||||
OnlineAccountsManager.getInstance().start();
|
||||
|
||||
LOGGER.info("Starting foreign fees manager");
|
||||
ForeignFeesManager.getInstance().start();
|
||||
|
||||
LOGGER.info("Starting transaction importer");
|
||||
TransactionImporter.getInstance().start();
|
||||
|
||||
@ -1130,6 +1133,9 @@ public class Controller extends Thread {
|
||||
LOGGER.info("Shutting down online accounts manager");
|
||||
OnlineAccountsManager.getInstance().shutdown();
|
||||
|
||||
LOGGER.info("Shutting down foreign fees manager");
|
||||
ForeignFeesManager.getInstance().shutdown();
|
||||
|
||||
LOGGER.info("Shutting down transaction importer");
|
||||
TransactionImporter.getInstance().shutdown();
|
||||
|
||||
@ -1474,6 +1480,14 @@ public class Controller extends Thread {
|
||||
OnlineAccountsManager.getInstance().onNetworkOnlineAccountsV3Message(peer, message);
|
||||
break;
|
||||
|
||||
case GET_FOREIGN_FEES:
|
||||
ForeignFeesManager.getInstance().onNetworkGetForeignFeesMessage(peer, message);
|
||||
break;
|
||||
|
||||
case FOREIGN_FEES:
|
||||
ForeignFeesManager.getInstance().onNetworkForeignFeesMessage(peer, message);
|
||||
break;
|
||||
|
||||
case GET_ARBITRARY_DATA:
|
||||
// Not currently supported
|
||||
break;
|
||||
|
1182
src/main/java/org/qortal/controller/ForeignFeesManager.java
Normal file
1182
src/main/java/org/qortal/controller/ForeignFeesManager.java
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,5 +1,6 @@
|
||||
package org.qortal.crosschain;
|
||||
|
||||
import org.bitcoinj.core.Coin;
|
||||
import org.bitcoinj.core.Context;
|
||||
import org.bitcoinj.core.NetworkParameters;
|
||||
import org.bitcoinj.core.Transaction;
|
||||
@ -14,15 +15,21 @@ import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.EnumMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
public class Bitcoin extends Bitcoiny {
|
||||
|
||||
public static final String CURRENCY_CODE = "BTC";
|
||||
|
||||
private static final long MINIMUM_ORDER_AMOUNT = 100000; // 0.001 BTC minimum order, due to high fees
|
||||
// Locking fee to lock in a QORT for BTC. This is the default value that the user should reset to
|
||||
// a value inline with the BTC fee market. This is 5 sats per kB.
|
||||
private static final Coin DEFAULT_FEE_PER_KB = Coin.valueOf(5_000); // 0.00005 BTC per 1000 bytes
|
||||
|
||||
// Temporary values until a dynamic fee system is written.
|
||||
private static final long NEW_FEE_AMOUNT = 6_000L;
|
||||
private static final long MINIMUM_ORDER_AMOUNT = 100_000; // 0.001 BTC minimum order, due to high fees
|
||||
|
||||
// Default value until user resets fee to compete with the current market. This is a total value for a
|
||||
// p2sh transaction, size 300 kB, 5 sats per kB
|
||||
private static final long NEW_FEE_AMOUNT = 1_500L;
|
||||
|
||||
private static final long NON_MAINNET_FEE = 1000L; // enough for TESTNET3 and should be OK for REGTEST
|
||||
|
||||
@ -111,7 +118,7 @@ public class Bitcoin extends Bitcoiny {
|
||||
|
||||
@Override
|
||||
public long getP2shFee(Long timestamp) {
|
||||
return this.getFeeCeiling();
|
||||
return this.getFeeRequired();
|
||||
}
|
||||
},
|
||||
TEST3 {
|
||||
@ -173,14 +180,14 @@ public class Bitcoin extends Bitcoiny {
|
||||
}
|
||||
};
|
||||
|
||||
private long feeCeiling = NEW_FEE_AMOUNT;
|
||||
private AtomicLong feeRequired = new AtomicLong(NEW_FEE_AMOUNT);
|
||||
|
||||
public long getFeeCeiling() {
|
||||
return feeCeiling;
|
||||
public long getFeeRequired() {
|
||||
return feeRequired.get();
|
||||
}
|
||||
|
||||
public void setFeeCeiling(long feeCeiling) {
|
||||
this.feeCeiling = feeCeiling;
|
||||
public void setFeeRequired(long feeRequired) {
|
||||
this.feeRequired.set(feeRequired);
|
||||
}
|
||||
|
||||
public abstract NetworkParameters getParams();
|
||||
@ -196,7 +203,7 @@ public class Bitcoin extends Bitcoiny {
|
||||
// Constructors and instance
|
||||
|
||||
private Bitcoin(BitcoinNet bitcoinNet, BitcoinyBlockchainProvider blockchain, Context bitcoinjContext, String currencyCode) {
|
||||
super(blockchain, bitcoinjContext, currencyCode, bitcoinjContext.getFeePerKb());
|
||||
super(blockchain, bitcoinjContext, currencyCode, DEFAULT_FEE_PER_KB);
|
||||
this.bitcoinNet = bitcoinNet;
|
||||
|
||||
LOGGER.info(() -> String.format("Starting Bitcoin support using %s", this.bitcoinNet.name()));
|
||||
@ -242,14 +249,14 @@ public class Bitcoin extends Bitcoiny {
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getFeeCeiling() {
|
||||
return this.bitcoinNet.getFeeCeiling();
|
||||
public long getFeeRequired() {
|
||||
return this.bitcoinNet.getFeeRequired();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setFeeCeiling(long fee) {
|
||||
public void setFeeRequired(long fee) {
|
||||
|
||||
this.bitcoinNet.setFeeCeiling( fee );
|
||||
this.bitcoinNet.setFeeRequired( fee );
|
||||
}
|
||||
/**
|
||||
* Returns bitcoinj transaction sending <tt>amount</tt> to <tt>recipient</tt> using 20 sat/byte fee.
|
||||
|
@ -840,9 +840,9 @@ public abstract class Bitcoiny implements ForeignBlockchain {
|
||||
} while (true);
|
||||
}
|
||||
|
||||
public abstract long getFeeCeiling();
|
||||
public abstract long getFeeRequired();
|
||||
|
||||
public abstract void setFeeCeiling(long fee);
|
||||
public abstract void setFeeRequired(long fee);
|
||||
|
||||
// UTXOProvider support
|
||||
|
||||
|
@ -89,7 +89,7 @@ public class BitcoinyTBD extends Bitcoiny {
|
||||
NetTBD netTBD
|
||||
= new NetTBD(
|
||||
bitcoinyTBDRequest.getNetworkName(),
|
||||
bitcoinyTBDRequest.getFeeCeiling(),
|
||||
bitcoinyTBDRequest.getFeeRequired(),
|
||||
networkParams,
|
||||
Collections.emptyList(),
|
||||
bitcoinyTBDRequest.getExpectedGenesisHash()
|
||||
@ -134,18 +134,18 @@ public class BitcoinyTBD extends Bitcoiny {
|
||||
@Override
|
||||
public long getP2shFee(Long timestamp) throws ForeignBlockchainException {
|
||||
|
||||
return this.netTBD.getFeeCeiling();
|
||||
return this.netTBD.getFeeRequired();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getFeeCeiling() {
|
||||
public long getFeeRequired() {
|
||||
|
||||
return this.netTBD.getFeeCeiling();
|
||||
return this.netTBD.getFeeRequired();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setFeeCeiling(long fee) {
|
||||
public void setFeeRequired(long fee) {
|
||||
|
||||
this.netTBD.setFeeCeiling( fee );
|
||||
this.netTBD.setFeeRequired( fee );
|
||||
}
|
||||
}
|
@ -14,6 +14,7 @@ import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.EnumMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
public class Digibyte extends Bitcoiny {
|
||||
|
||||
@ -59,7 +60,7 @@ public class Digibyte extends Bitcoiny {
|
||||
|
||||
@Override
|
||||
public long getP2shFee(Long timestamp) {
|
||||
return this.getFeeCeiling();
|
||||
return this.getFeeRequired();
|
||||
}
|
||||
},
|
||||
TEST3 {
|
||||
@ -109,14 +110,14 @@ public class Digibyte extends Bitcoiny {
|
||||
}
|
||||
};
|
||||
|
||||
private long feeCeiling = MAINNET_FEE;
|
||||
private AtomicLong feeRequired = new AtomicLong(MAINNET_FEE);
|
||||
|
||||
public long getFeeCeiling() {
|
||||
return feeCeiling;
|
||||
public long getFeeRequired() {
|
||||
return feeRequired.get();
|
||||
}
|
||||
|
||||
public void setFeeCeiling(long feeCeiling) {
|
||||
this.feeCeiling = feeCeiling;
|
||||
public void setFeeRequired(long feeRequired) {
|
||||
this.feeRequired.set(feeRequired);
|
||||
}
|
||||
|
||||
public abstract NetworkParameters getParams();
|
||||
@ -178,13 +179,13 @@ public class Digibyte extends Bitcoiny {
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getFeeCeiling() {
|
||||
return this.digibyteNet.getFeeCeiling();
|
||||
public long getFeeRequired() {
|
||||
return this.digibyteNet.getFeeRequired();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setFeeCeiling(long fee) {
|
||||
public void setFeeRequired(long fee) {
|
||||
|
||||
this.digibyteNet.setFeeCeiling( fee );
|
||||
this.digibyteNet.setFeeRequired( fee );
|
||||
}
|
||||
}
|
||||
|
@ -13,6 +13,7 @@ import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.EnumMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
public class Dogecoin extends Bitcoiny {
|
||||
|
||||
@ -60,7 +61,7 @@ public class Dogecoin extends Bitcoiny {
|
||||
|
||||
@Override
|
||||
public long getP2shFee(Long timestamp) {
|
||||
return this.getFeeCeiling();
|
||||
return this.getFeeRequired();
|
||||
}
|
||||
},
|
||||
TEST3 {
|
||||
@ -110,14 +111,14 @@ public class Dogecoin extends Bitcoiny {
|
||||
}
|
||||
};
|
||||
|
||||
private long feeCeiling = MAINNET_FEE;
|
||||
private AtomicLong feeRequired = new AtomicLong(MAINNET_FEE);
|
||||
|
||||
public long getFeeCeiling() {
|
||||
return feeCeiling;
|
||||
public long getFeeRequired() {
|
||||
return feeRequired.get();
|
||||
}
|
||||
|
||||
public void setFeeCeiling(long feeCeiling) {
|
||||
this.feeCeiling = feeCeiling;
|
||||
public void setFeeRequired(long feeRequired) {
|
||||
this.feeRequired.set(feeRequired);
|
||||
}
|
||||
|
||||
public abstract NetworkParameters getParams();
|
||||
@ -179,13 +180,13 @@ public class Dogecoin extends Bitcoiny {
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getFeeCeiling() {
|
||||
return this.dogecoinNet.getFeeCeiling();
|
||||
public long getFeeRequired() {
|
||||
return this.dogecoinNet.getFeeRequired();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setFeeCeiling(long fee) {
|
||||
public void setFeeRequired(long fee) {
|
||||
|
||||
this.dogecoinNet.setFeeCeiling( fee );
|
||||
this.dogecoinNet.setFeeRequired( fee );
|
||||
}
|
||||
}
|
||||
|
@ -14,6 +14,7 @@ import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.EnumMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
public class Litecoin extends Bitcoiny {
|
||||
|
||||
@ -63,7 +64,7 @@ public class Litecoin extends Bitcoiny {
|
||||
|
||||
@Override
|
||||
public long getP2shFee(Long timestamp) {
|
||||
return this.getFeeCeiling();
|
||||
return this.getFeeRequired();
|
||||
}
|
||||
},
|
||||
TEST3 {
|
||||
@ -116,14 +117,14 @@ public class Litecoin extends Bitcoiny {
|
||||
}
|
||||
};
|
||||
|
||||
private long feeCeiling = MAINNET_FEE;
|
||||
private AtomicLong feeRequired = new AtomicLong(MAINNET_FEE);
|
||||
|
||||
public long getFeeCeiling() {
|
||||
return feeCeiling;
|
||||
public long getFeeRequired() {
|
||||
return feeRequired.get();
|
||||
}
|
||||
|
||||
public void setFeeCeiling(long feeCeiling) {
|
||||
this.feeCeiling = feeCeiling;
|
||||
public void setFeeRequired(long feeRequired) {
|
||||
this.feeRequired.set(feeRequired);
|
||||
}
|
||||
|
||||
public abstract NetworkParameters getParams();
|
||||
@ -185,13 +186,13 @@ public class Litecoin extends Bitcoiny {
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getFeeCeiling() {
|
||||
return this.litecoinNet.getFeeCeiling();
|
||||
public long getFeeRequired() {
|
||||
return this.litecoinNet.getFeeRequired();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setFeeCeiling(long fee) {
|
||||
public void setFeeRequired(long fee) {
|
||||
|
||||
this.litecoinNet.setFeeCeiling( fee );
|
||||
this.litecoinNet.setFeeRequired( fee );
|
||||
}
|
||||
}
|
||||
|
@ -3,18 +3,19 @@ package org.qortal.crosschain;
|
||||
import org.bitcoinj.core.NetworkParameters;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
public class NetTBD {
|
||||
|
||||
private String name;
|
||||
private long feeCeiling;
|
||||
private AtomicLong feeRequired;
|
||||
private NetworkParameters params;
|
||||
private Collection<ElectrumX.Server> servers;
|
||||
private String genesisHash;
|
||||
|
||||
public NetTBD(String name, long feeCeiling, NetworkParameters params, Collection<ElectrumX.Server> servers, String genesisHash) {
|
||||
public NetTBD(String name, long feeRequired, NetworkParameters params, Collection<ElectrumX.Server> servers, String genesisHash) {
|
||||
this.name = name;
|
||||
this.feeCeiling = feeCeiling;
|
||||
this.feeRequired = new AtomicLong(feeRequired);
|
||||
this.params = params;
|
||||
this.servers = servers;
|
||||
this.genesisHash = genesisHash;
|
||||
@ -25,14 +26,14 @@ public class NetTBD {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
public long getFeeCeiling() {
|
||||
public long getFeeRequired() {
|
||||
|
||||
return feeCeiling;
|
||||
return feeRequired.get();
|
||||
}
|
||||
|
||||
public void setFeeCeiling(long feeCeiling) {
|
||||
public void setFeeRequired(long feeRequired) {
|
||||
|
||||
this.feeCeiling = feeCeiling;
|
||||
this.feeRequired.set(feeRequired);
|
||||
}
|
||||
|
||||
public NetworkParameters getParams() {
|
||||
|
@ -21,6 +21,7 @@ import org.qortal.utils.BitTwiddling;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
public class PirateChain extends Bitcoiny {
|
||||
|
||||
@ -67,7 +68,7 @@ public class PirateChain extends Bitcoiny {
|
||||
|
||||
@Override
|
||||
public long getP2shFee(Long timestamp) {
|
||||
return this.getFeeCeiling();
|
||||
return this.getFeeRequired();
|
||||
}
|
||||
},
|
||||
TEST3 {
|
||||
@ -117,14 +118,14 @@ public class PirateChain extends Bitcoiny {
|
||||
}
|
||||
};
|
||||
|
||||
private long feeCeiling = MAINNET_FEE;
|
||||
private AtomicLong feeRequired = new AtomicLong(MAINNET_FEE);
|
||||
|
||||
public long getFeeCeiling() {
|
||||
return feeCeiling;
|
||||
public long getFeeRequired() {
|
||||
return feeRequired.get();
|
||||
}
|
||||
|
||||
public void setFeeCeiling(long feeCeiling) {
|
||||
this.feeCeiling = feeCeiling;
|
||||
public void setFeeRequired(long feeRequired) {
|
||||
this.feeRequired.set(feeRequired);
|
||||
}
|
||||
|
||||
public abstract NetworkParameters getParams();
|
||||
@ -186,14 +187,14 @@ public class PirateChain extends Bitcoiny {
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getFeeCeiling() {
|
||||
return this.pirateChainNet.getFeeCeiling();
|
||||
public long getFeeRequired() {
|
||||
return this.pirateChainNet.getFeeRequired();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setFeeCeiling(long fee) {
|
||||
public void setFeeRequired(long fee) {
|
||||
|
||||
this.pirateChainNet.setFeeCeiling( fee );
|
||||
this.pirateChainNet.setFeeRequired( fee );
|
||||
}
|
||||
/**
|
||||
* Returns confirmed balance, based on passed payment script.
|
||||
|
@ -14,6 +14,7 @@ import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.EnumMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
public class Ravencoin extends Bitcoiny {
|
||||
|
||||
@ -61,7 +62,7 @@ public class Ravencoin extends Bitcoiny {
|
||||
|
||||
@Override
|
||||
public long getP2shFee(Long timestamp) {
|
||||
return this.getFeeCeiling();
|
||||
return this.getFeeRequired();
|
||||
}
|
||||
},
|
||||
TEST3 {
|
||||
@ -111,14 +112,14 @@ public class Ravencoin extends Bitcoiny {
|
||||
}
|
||||
};
|
||||
|
||||
private long feeCeiling = MAINNET_FEE;
|
||||
private AtomicLong feeRequired = new AtomicLong( MAINNET_FEE );
|
||||
|
||||
public long getFeeCeiling() {
|
||||
return feeCeiling;
|
||||
public long getFeeRequired() {
|
||||
return feeRequired.get();
|
||||
}
|
||||
|
||||
public void setFeeCeiling(long feeCeiling) {
|
||||
this.feeCeiling = feeCeiling;
|
||||
public void setFeeRequired(long feeRequired) {
|
||||
this.feeRequired.set(feeRequired);
|
||||
}
|
||||
|
||||
public abstract NetworkParameters getParams();
|
||||
@ -180,13 +181,13 @@ public class Ravencoin extends Bitcoiny {
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getFeeCeiling() {
|
||||
return this.ravencoinNet.getFeeCeiling();
|
||||
public long getFeeRequired() {
|
||||
return this.ravencoinNet.getFeeRequired();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setFeeCeiling(long fee) {
|
||||
public void setFeeRequired(long fee) {
|
||||
|
||||
this.ravencoinNet.setFeeCeiling( fee );
|
||||
this.ravencoinNet.setFeeRequired( fee );
|
||||
}
|
||||
}
|
||||
|
57
src/main/java/org/qortal/data/crosschain/ForeignFeeData.java
Normal file
57
src/main/java/org/qortal/data/crosschain/ForeignFeeData.java
Normal file
@ -0,0 +1,57 @@
|
||||
package org.qortal.data.crosschain;
|
||||
|
||||
import org.json.JSONObject;
|
||||
|
||||
import javax.xml.bind.annotation.XmlAccessType;
|
||||
import javax.xml.bind.annotation.XmlAccessorType;
|
||||
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
|
||||
|
||||
// All properties to be converted to JSON via JAXB
|
||||
@XmlAccessorType(XmlAccessType.FIELD)
|
||||
public class ForeignFeeData {
|
||||
|
||||
private String blockchain;
|
||||
|
||||
@XmlJavaTypeAdapter(value = org.qortal.api.AmountTypeAdapter.class)
|
||||
private long fee;
|
||||
|
||||
protected ForeignFeeData() {
|
||||
/* JAXB */
|
||||
}
|
||||
|
||||
public ForeignFeeData(String blockchain,
|
||||
long fee) {
|
||||
this.blockchain = blockchain;
|
||||
this.fee = fee;
|
||||
}
|
||||
|
||||
public String getBlockchain() {
|
||||
return this.blockchain;
|
||||
}
|
||||
|
||||
public long getFee() {
|
||||
return this.fee;
|
||||
}
|
||||
|
||||
public JSONObject toJson() {
|
||||
JSONObject jsonObject = new JSONObject();
|
||||
jsonObject.put("blockchain", this.getBlockchain());
|
||||
jsonObject.put("fee", this.getFee());
|
||||
return jsonObject;
|
||||
}
|
||||
|
||||
public static ForeignFeeData fromJson(JSONObject json) {
|
||||
return new ForeignFeeData(
|
||||
json.isNull("blockchain") ? null : json.getString("blockchain"),
|
||||
json.isNull("fee") ? null : json.getLong("fee")
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ForeignFeeData{" +
|
||||
"blockchain='" + blockchain + '\'' +
|
||||
", fee=" + fee +
|
||||
'}';
|
||||
}
|
||||
}
|
@ -0,0 +1,90 @@
|
||||
package org.qortal.data.crosschain;
|
||||
|
||||
import org.json.JSONObject;
|
||||
import org.qortal.data.account.MintingAccountData;
|
||||
import org.qortal.utils.Base58;
|
||||
|
||||
import javax.xml.bind.annotation.XmlAccessType;
|
||||
import javax.xml.bind.annotation.XmlAccessorType;
|
||||
import java.util.Objects;
|
||||
|
||||
// All properties to be converted to JSON via JAXB
|
||||
@XmlAccessorType(XmlAccessType.FIELD)
|
||||
public class ForeignFeeDecodedData {
|
||||
|
||||
protected long timestamp;
|
||||
protected byte[] data;
|
||||
protected String atAddress;
|
||||
protected Integer fee;
|
||||
|
||||
// Constructors
|
||||
|
||||
// necessary for JAXB serialization
|
||||
protected ForeignFeeDecodedData() {
|
||||
}
|
||||
|
||||
public ForeignFeeDecodedData(long timestamp, byte[] data, String atAddress, Integer fee) {
|
||||
this.timestamp = timestamp;
|
||||
this.data = data;
|
||||
this.atAddress = atAddress;
|
||||
this.fee = fee;
|
||||
}
|
||||
|
||||
public long getTimestamp() {
|
||||
return this.timestamp;
|
||||
}
|
||||
|
||||
public byte[] getData() {
|
||||
return this.data;
|
||||
}
|
||||
|
||||
public String getAtAddress() {
|
||||
return atAddress;
|
||||
}
|
||||
|
||||
public Integer getFee() {
|
||||
return this.fee;
|
||||
}
|
||||
|
||||
// Comparison
|
||||
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
ForeignFeeDecodedData that = (ForeignFeeDecodedData) o;
|
||||
return timestamp == that.timestamp && Objects.equals(atAddress, that.atAddress) && Objects.equals(fee, that.fee);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(timestamp, atAddress, fee);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ForeignFeeDecodedData{" +
|
||||
"timestamp=" + timestamp +
|
||||
", atAddress='" + atAddress + '\'' +
|
||||
", fee=" + fee +
|
||||
'}';
|
||||
}
|
||||
|
||||
public JSONObject toJson() {
|
||||
JSONObject jsonObject = new JSONObject();
|
||||
jsonObject.put("data", Base58.encode(this.data));
|
||||
jsonObject.put("atAddress", this.atAddress);
|
||||
jsonObject.put("timestamp", this.timestamp);
|
||||
jsonObject.put("fee", this.fee);
|
||||
return jsonObject;
|
||||
}
|
||||
|
||||
public static ForeignFeeDecodedData fromJson(JSONObject json) {
|
||||
return new ForeignFeeDecodedData(
|
||||
json.isNull("timestamp") ? null : json.getLong("timestamp"),
|
||||
json.isNull("data") ? null : Base58.decode(json.getString("data")),
|
||||
json.isNull("atAddress") ? null : json.getString("atAddress"),
|
||||
json.isNull("fee") ? null : json.getInt("fee"));
|
||||
}
|
||||
}
|
@ -0,0 +1,69 @@
|
||||
package org.qortal.data.crosschain;
|
||||
|
||||
import javax.xml.bind.annotation.XmlAccessType;
|
||||
import javax.xml.bind.annotation.XmlAccessorType;
|
||||
import java.util.Objects;
|
||||
|
||||
// All properties to be converted to JSON via JAXB
|
||||
@XmlAccessorType(XmlAccessType.FIELD)
|
||||
public class ForeignFeeEncodedData {
|
||||
|
||||
protected long timestamp;
|
||||
protected String data;
|
||||
protected String atAddress;
|
||||
protected Integer fee;
|
||||
|
||||
// Constructors
|
||||
|
||||
// necessary for JAXB serialization
|
||||
protected ForeignFeeEncodedData() {
|
||||
}
|
||||
|
||||
public ForeignFeeEncodedData(long timestamp, String data, String atAddress, Integer fee) {
|
||||
this.timestamp = timestamp;
|
||||
this.data = data;
|
||||
this.atAddress = atAddress;
|
||||
this.fee = fee;
|
||||
}
|
||||
|
||||
public long getTimestamp() {
|
||||
return this.timestamp;
|
||||
}
|
||||
|
||||
public String getData() {
|
||||
return this.data;
|
||||
}
|
||||
|
||||
public String getAtAddress() {
|
||||
return atAddress;
|
||||
}
|
||||
|
||||
public Integer getFee() {
|
||||
return this.fee;
|
||||
}
|
||||
|
||||
// Comparison
|
||||
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
ForeignFeeEncodedData that = (ForeignFeeEncodedData) o;
|
||||
return timestamp == that.timestamp && Objects.equals(atAddress, that.atAddress) && Objects.equals(fee, that.fee);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(timestamp, atAddress, fee);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ForeignFeeDecodedData{" +
|
||||
"timestamp=" + timestamp +
|
||||
", atAddress='" + atAddress + '\'' +
|
||||
", fee=" + fee +
|
||||
'}';
|
||||
}
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
package org.qortal.data.crosschain;
|
||||
|
||||
import javax.xml.bind.annotation.XmlAccessType;
|
||||
import javax.xml.bind.annotation.XmlAccessorType;
|
||||
|
||||
@XmlAccessorType(XmlAccessType.FIELD)
|
||||
public class UnsignedFeeEvent {
|
||||
}
|
26
src/main/java/org/qortal/event/FeeWaitingEvent.java
Normal file
26
src/main/java/org/qortal/event/FeeWaitingEvent.java
Normal file
@ -0,0 +1,26 @@
|
||||
package org.qortal.event;
|
||||
|
||||
import javax.xml.bind.annotation.XmlAccessType;
|
||||
import javax.xml.bind.annotation.XmlAccessorType;
|
||||
|
||||
@XmlAccessorType(XmlAccessType.FIELD)
|
||||
public class FeeWaitingEvent implements Event{
|
||||
private long timestamp;
|
||||
private String address;
|
||||
|
||||
public FeeWaitingEvent() {
|
||||
}
|
||||
|
||||
public FeeWaitingEvent(long timestamp, String address) {
|
||||
this.timestamp = timestamp;
|
||||
this.address = address;
|
||||
}
|
||||
|
||||
public long getTimestamp() {
|
||||
return timestamp;
|
||||
}
|
||||
|
||||
public String getAddress() {
|
||||
return address;
|
||||
}
|
||||
}
|
@ -0,0 +1,4 @@
|
||||
package org.qortal.event;
|
||||
|
||||
public class LockingFeeUpdateEvent implements Event{
|
||||
}
|
15
src/main/java/org/qortal/event/RequiredFeeUpdateEvent.java
Normal file
15
src/main/java/org/qortal/event/RequiredFeeUpdateEvent.java
Normal file
@ -0,0 +1,15 @@
|
||||
package org.qortal.event;
|
||||
|
||||
import org.qortal.crosschain.Bitcoiny;
|
||||
|
||||
public class RequiredFeeUpdateEvent implements Event{
|
||||
private final Bitcoiny bitcoiny;
|
||||
|
||||
public RequiredFeeUpdateEvent(Bitcoiny bitcoiny) {
|
||||
this.bitcoiny = bitcoiny;
|
||||
}
|
||||
|
||||
public Bitcoiny getBitcoiny() {
|
||||
return bitcoiny;
|
||||
}
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
package org.qortal.network.message;
|
||||
|
||||
import org.qortal.data.crosschain.ForeignFeeDecodedData;
|
||||
import org.qortal.utils.ForeignFeesMessageUtils;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* For sending online accounts info to remote peer.
|
||||
*
|
||||
* Same format as V2, but with added support for a mempow nonce.
|
||||
*/
|
||||
public class ForeignFeesMessage extends Message {
|
||||
|
||||
public static final long MIN_PEER_VERSION = 0x300060000L; // 3.6.0
|
||||
|
||||
private List<ForeignFeeDecodedData> foreignFees;
|
||||
|
||||
public ForeignFeesMessage(List<ForeignFeeDecodedData> foreignFeeDecodedData) {
|
||||
super(MessageType.FOREIGN_FEES);
|
||||
|
||||
this.dataBytes = ForeignFeesMessageUtils.fromDataToSendBytes(foreignFeeDecodedData);
|
||||
this.checksumBytes = Message.generateChecksum(this.dataBytes);
|
||||
}
|
||||
|
||||
private ForeignFeesMessage(int id, List<ForeignFeeDecodedData> foreignFees) {
|
||||
super(id, MessageType.ONLINE_ACCOUNTS_V3);
|
||||
|
||||
this.foreignFees = foreignFees;
|
||||
}
|
||||
|
||||
public List<ForeignFeeDecodedData> getForeignFees() {
|
||||
return this.foreignFees;
|
||||
}
|
||||
|
||||
public static Message fromByteBuffer(int id, ByteBuffer bytes) throws MessageException {
|
||||
List<ForeignFeeDecodedData> foreignFeeDecodedData = ForeignFeesMessageUtils.fromSendBytesToData(bytes);
|
||||
|
||||
return new ForeignFeesMessage(id, foreignFeeDecodedData);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,46 @@
|
||||
package org.qortal.network.message;
|
||||
|
||||
import org.qortal.data.crosschain.ForeignFeeDecodedData;
|
||||
import org.qortal.utils.ForeignFeesMessageUtils;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class GetForeignFeesMessage extends Message {
|
||||
|
||||
private static final Map<Long, Map<Byte, byte[]>> EMPTY_ONLINE_ACCOUNTS = Collections.emptyMap();
|
||||
private final List<ForeignFeeDecodedData> foreignFeeDecodedData;
|
||||
|
||||
public GetForeignFeesMessage(List<ForeignFeeDecodedData> foreignFeeDecodedData) {
|
||||
super(MessageType.GET_ONLINE_ACCOUNTS_V3);
|
||||
|
||||
this.foreignFeeDecodedData = foreignFeeDecodedData;
|
||||
|
||||
// If we don't have ANY online accounts then it's an easier construction...
|
||||
if (foreignFeeDecodedData.isEmpty()) {
|
||||
this.dataBytes = EMPTY_DATA_BYTES;
|
||||
return;
|
||||
}
|
||||
|
||||
this.dataBytes = ForeignFeesMessageUtils.fromDataToGetBytes(foreignFeeDecodedData);
|
||||
this.checksumBytes = Message.generateChecksum(this.dataBytes);
|
||||
}
|
||||
|
||||
private GetForeignFeesMessage(int id, List<ForeignFeeDecodedData> foreignFeeDecodedData) {
|
||||
super(id, MessageType.GET_FOREIGN_FEES);
|
||||
|
||||
this.foreignFeeDecodedData = foreignFeeDecodedData;
|
||||
}
|
||||
|
||||
public List<ForeignFeeDecodedData> getForeignFeeData() {
|
||||
return foreignFeeDecodedData;
|
||||
}
|
||||
|
||||
public static Message fromByteBuffer(int id, ByteBuffer bytes) {
|
||||
|
||||
return new GetForeignFeesMessage(id, ForeignFeesMessageUtils.fromGetBytesToData(bytes));
|
||||
}
|
||||
|
||||
}
|
@ -79,7 +79,10 @@ public enum MessageType {
|
||||
GET_NAME(182, GetNameMessage::fromByteBuffer),
|
||||
|
||||
TRANSACTIONS(190, TransactionsMessage::fromByteBuffer),
|
||||
GET_ACCOUNT_TRANSACTIONS(191, GetAccountTransactionsMessage::fromByteBuffer);
|
||||
GET_ACCOUNT_TRANSACTIONS(191, GetAccountTransactionsMessage::fromByteBuffer),
|
||||
|
||||
FOREIGN_FEES( 200, ForeignFeesMessage::fromByteBuffer),
|
||||
GET_FOREIGN_FEES( 201, GetForeignFeesMessage::fromByteBuffer);
|
||||
|
||||
public final int value;
|
||||
public final MessageProducer fromByteBufferMethod;
|
||||
|
187
src/main/java/org/qortal/utils/ForeignFeesMessageUtils.java
Normal file
187
src/main/java/org/qortal/utils/ForeignFeesMessageUtils.java
Normal file
@ -0,0 +1,187 @@
|
||||
package org.qortal.utils;
|
||||
|
||||
import com.google.common.primitives.Ints;
|
||||
import com.google.common.primitives.Longs;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.qortal.data.crosschain.ForeignFeeDecodedData;
|
||||
import org.qortal.transform.Transformer;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static org.qortal.transform.Transformer.ADDRESS_LENGTH;
|
||||
|
||||
/**
|
||||
* Class ForeignFeesMessageUtils
|
||||
*/
|
||||
public class ForeignFeesMessageUtils {
|
||||
|
||||
private static final Logger LOGGER = LogManager.getLogger(ForeignFeesMessageUtils.class);
|
||||
|
||||
/**
|
||||
* From Data To Send Bytes
|
||||
*
|
||||
* Convert foreign fee data into bytes for send messages.
|
||||
*
|
||||
* @param foreignFees the data
|
||||
*
|
||||
* @return the bytes
|
||||
*/
|
||||
public static byte[] fromDataToSendBytes(List<ForeignFeeDecodedData> foreignFees) {
|
||||
|
||||
return fromDataToBytes(foreignFees, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* From Data To Bytes
|
||||
*
|
||||
* @param foreignFees
|
||||
* @param includeSignature
|
||||
* @return
|
||||
*/
|
||||
private static byte[] fromDataToBytes(List<ForeignFeeDecodedData> foreignFees, boolean includeSignature) {
|
||||
try {
|
||||
if (foreignFees.isEmpty()) {
|
||||
return new byte[0];
|
||||
}
|
||||
else {
|
||||
// allocate size for each data item for timestamp, AT address, fee and signature
|
||||
int byteSize
|
||||
= foreignFees.size()
|
||||
*
|
||||
(Transformer.TIMESTAMP_LENGTH + Transformer.ADDRESS_LENGTH + Transformer.INT_LENGTH + Transformer.SIGNATURE_LENGTH);
|
||||
|
||||
if( includeSignature ) byteSize += foreignFees.size() * Transformer.SIGNATURE_LENGTH;
|
||||
|
||||
ByteArrayOutputStream bytes = new ByteArrayOutputStream(byteSize);
|
||||
|
||||
// for each foreign fee data item, convert to bytes and fill the array
|
||||
for( ForeignFeeDecodedData feeData : foreignFees) {
|
||||
bytes.write(Longs.toByteArray(feeData.getTimestamp()));
|
||||
bytes.write(Base58.decode(feeData.getAtAddress()));
|
||||
bytes.write(Ints.toByteArray(feeData.getFee()));
|
||||
if( includeSignature ) bytes.write(feeData.getData());
|
||||
}
|
||||
return bytes.toByteArray();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOGGER.warn(e.getMessage());
|
||||
|
||||
return new byte[0];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* From Send Bytes to Data
|
||||
*
|
||||
* @param bytes the bytes to convert to data
|
||||
*
|
||||
* @return the data
|
||||
*/
|
||||
public static List<ForeignFeeDecodedData> fromSendBytesToData(ByteBuffer bytes) {
|
||||
|
||||
return fromBytesToData(bytes, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* From Bytes To Data
|
||||
*
|
||||
* @param bytes the bytes
|
||||
* @param includeSignature true if the bytes include signatures
|
||||
*
|
||||
* @return the foreign fee data with signatures (data member)
|
||||
*/
|
||||
private static List<ForeignFeeDecodedData> fromBytesToData(ByteBuffer bytes, boolean includeSignature) {
|
||||
if( !bytes.hasRemaining() ) return new ArrayList<>(0);
|
||||
|
||||
List<ForeignFeeDecodedData> foreignFees = new ArrayList<>();
|
||||
|
||||
try {
|
||||
while (bytes.hasRemaining()) {
|
||||
// read in the timestamp as a long
|
||||
long timestamp = bytes.getLong();
|
||||
|
||||
// read in the address as a byte array with a predetermined length
|
||||
byte[] atAddressBytes = new byte[ADDRESS_LENGTH];
|
||||
bytes.get(atAddressBytes);
|
||||
String atAddress = Base58.encode(atAddressBytes);
|
||||
|
||||
// rwad in the fee as an integer
|
||||
int fee = bytes.getInt();
|
||||
|
||||
byte[] signature;
|
||||
|
||||
if( includeSignature ) {
|
||||
signature = new byte[Transformer.SIGNATURE_LENGTH];
|
||||
bytes.get(signature);
|
||||
}
|
||||
else {
|
||||
signature = null;
|
||||
}
|
||||
|
||||
foreignFees.add(new ForeignFeeDecodedData(timestamp, signature, atAddress, fee));
|
||||
|
||||
}
|
||||
}
|
||||
// if there are any exception, log the error as a warning and clear the list before returning it
|
||||
catch (Exception e) {
|
||||
LOGGER.warn(e.getMessage());
|
||||
foreignFees.clear();
|
||||
}
|
||||
|
||||
return foreignFees;
|
||||
}
|
||||
|
||||
/**
|
||||
* From Data To Get Bytes
|
||||
*
|
||||
* Convert foreign fees data objects into get foreign fees messages. Get messages do not include signatures.
|
||||
*
|
||||
* @param foreignFees the foreign fees objects
|
||||
*
|
||||
* @return the messages
|
||||
*/
|
||||
public static byte[] fromDataToGetBytes(List<ForeignFeeDecodedData> foreignFees) {
|
||||
return fromDataToBytes(foreignFees, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* From Get Bytes to Data
|
||||
*
|
||||
* Convert bytes from get foreign fees messages to foreign fees objects. Get messages do not include signatures.
|
||||
*
|
||||
* @param bytes the bytes to convert
|
||||
*
|
||||
* @return the foreign fees data objects
|
||||
*/
|
||||
public static List<ForeignFeeDecodedData> fromGetBytesToData(ByteBuffer bytes) {
|
||||
return fromBytesToData(bytes, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build Foreign Fees Data Message
|
||||
*
|
||||
* Build the unsigned message for the foreign fees data objects.
|
||||
*
|
||||
* @param timestamp the timestamp in milliseconds
|
||||
* @param atAddress the AT address
|
||||
* @param fee the fee
|
||||
* @return
|
||||
* @throws IOException
|
||||
*/
|
||||
public static byte[] buildForeignFeesDataMessage(Long timestamp, String atAddress, int fee) throws IOException {
|
||||
int byteSize = Transformer.TIMESTAMP_LENGTH + Transformer.ADDRESS_LENGTH + Transformer.INT_LENGTH;
|
||||
|
||||
ByteArrayOutputStream bytes = new ByteArrayOutputStream(byteSize);
|
||||
|
||||
bytes.write(Longs.toByteArray(timestamp));
|
||||
bytes.write(Base58.decode(atAddress));
|
||||
bytes.write(Ints.toByteArray(fee));
|
||||
|
||||
return bytes.toByteArray();
|
||||
}
|
||||
}
|
@ -0,0 +1,334 @@
|
||||
package org.qortal.test.network.message;
|
||||
|
||||
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import org.qortal.crypto.Crypto;
|
||||
import org.qortal.data.crosschain.ForeignFeeDecodedData;
|
||||
import org.qortal.test.utils.TestUtils;
|
||||
import org.qortal.transform.Transformer;
|
||||
import org.qortal.utils.Base58;
|
||||
import org.qortal.utils.ForeignFeesMessageUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.security.KeyPair;
|
||||
import java.security.Security;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
/**
|
||||
* Class ForeignFeesMessageTests
|
||||
*/
|
||||
public class ForeignFeesMessageTests {
|
||||
|
||||
/**
|
||||
* Random
|
||||
*
|
||||
* Random input generator for seeding keys/addresses.
|
||||
*/
|
||||
private static final Random RANDOM = new Random();
|
||||
|
||||
static {
|
||||
// add the Bouncy Castle provider for keys/addresses
|
||||
Security.addProvider(new BouncyCastleProvider());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDataToSendBytesToDataEmpty() {
|
||||
|
||||
byte[] bytes = ForeignFeesMessageUtils.fromDataToSendBytes(new ArrayList<>(0));
|
||||
|
||||
List<ForeignFeeDecodedData> list = ForeignFeesMessageUtils.fromSendBytesToData(ByteBuffer.wrap(bytes));
|
||||
|
||||
Assert.assertNotNull(list);
|
||||
|
||||
Assert.assertEquals(0, list.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDataToGetBytesToDataEmpty() {
|
||||
|
||||
byte[] bytes = ForeignFeesMessageUtils.fromDataToGetBytes(new ArrayList<>(0));
|
||||
|
||||
List<ForeignFeeDecodedData> list = ForeignFeesMessageUtils.fromGetBytesToData(ByteBuffer.wrap(bytes));
|
||||
|
||||
Assert.assertNotNull(list);
|
||||
|
||||
Assert.assertEquals(0, list.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSignature() {
|
||||
|
||||
boolean exceptionThrown = false;
|
||||
|
||||
try {
|
||||
KeyPair keyPair = TestUtils.generateKeyPair();
|
||||
|
||||
long timestamp = 1_000_000L;
|
||||
String atAddress = generateAtAddress();
|
||||
int fee = 1;
|
||||
|
||||
assertSignature(keyPair, timestamp, atAddress, fee);
|
||||
|
||||
} catch (Exception e) {
|
||||
exceptionThrown = true;
|
||||
}
|
||||
|
||||
Assert.assertFalse(exceptionThrown);
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert Signature
|
||||
*
|
||||
* @param keyPair the key pair that is signing
|
||||
* @param timestamp the timestamp for the data
|
||||
* @param atAddress the AT address
|
||||
* @param fee the fee
|
||||
*
|
||||
* @return the signature bytes
|
||||
*
|
||||
* @throws IOException
|
||||
*/
|
||||
private static byte[] assertSignature(KeyPair keyPair, long timestamp, String atAddress, int fee) throws IOException {
|
||||
|
||||
// build the message and sign it
|
||||
byte[] message = ForeignFeesMessageUtils.buildForeignFeesDataMessage(timestamp, atAddress, fee);
|
||||
byte[] signature = Crypto.sign( keyPair.getPrivate().getEncoded(), message );
|
||||
|
||||
// assert signaute length
|
||||
Assert.assertEquals(Transformer.SIGNATURE_LENGTH, signature.length);
|
||||
|
||||
// assert verification
|
||||
boolean verified = Crypto.verify(Crypto.toPublicKey(keyPair.getPrivate().getEncoded()), signature, message);
|
||||
Assert.assertTrue(verified);
|
||||
|
||||
return signature;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDataToSendBytesToDataSingle() {
|
||||
|
||||
Long timestamp = 1_000_000L;
|
||||
String atAddress = generateAtAddress();
|
||||
int fee = 1;
|
||||
|
||||
boolean exceptionThrown = false;
|
||||
|
||||
try {
|
||||
// random key generation for signing data
|
||||
KeyPair keyPair = TestUtils.generateKeyPair();
|
||||
|
||||
// data to send, a list of 1 foreign fee data
|
||||
List<ForeignFeeDecodedData> sendData
|
||||
= List.of(
|
||||
new ForeignFeeDecodedData(timestamp, assertSignature(keyPair,timestamp,atAddress, fee), atAddress, fee)
|
||||
);
|
||||
|
||||
// from data to bytes
|
||||
byte[] sendBytes = ForeignFeesMessageUtils.fromDataToSendBytes(sendData);
|
||||
|
||||
// from bytes to data
|
||||
List<ForeignFeeDecodedData> returnData = ForeignFeesMessageUtils.fromSendBytesToData(ByteBuffer.wrap(sendBytes));
|
||||
|
||||
|
||||
assertListedForeignFees(sendData, returnData, true);
|
||||
|
||||
} catch (Exception e) {
|
||||
exceptionThrown = true;
|
||||
}
|
||||
|
||||
Assert.assertFalse(exceptionThrown);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDataToGetBytesToDataSingle() {
|
||||
|
||||
Long timestamp = 1_000_000L;
|
||||
String atAddress = generateAtAddress();
|
||||
int fee = 1;
|
||||
|
||||
boolean exceptionThrown = false;
|
||||
|
||||
try {
|
||||
// random key generation for signing data
|
||||
KeyPair keyPair = TestUtils.generateKeyPair();
|
||||
|
||||
// data to send, a list of 1 foreign fee data
|
||||
List<ForeignFeeDecodedData> sendData
|
||||
= List.of(
|
||||
new ForeignFeeDecodedData(timestamp, assertSignature(keyPair,timestamp,atAddress, fee), atAddress, fee)
|
||||
);
|
||||
|
||||
// from data to bytes
|
||||
byte[] sendBytes = ForeignFeesMessageUtils.fromDataToGetBytes(sendData);
|
||||
|
||||
// from bytes to data
|
||||
List<ForeignFeeDecodedData> returnData = ForeignFeesMessageUtils.fromGetBytesToData(ByteBuffer.wrap(sendBytes));
|
||||
|
||||
|
||||
assertListedForeignFees(sendData, returnData, false);
|
||||
|
||||
} catch (Exception e) {
|
||||
exceptionThrown = true;
|
||||
}
|
||||
|
||||
Assert.assertFalse(exceptionThrown);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDataToSendBytesToDataTriple() {
|
||||
|
||||
Long timestamp1 = 1_000_000L;
|
||||
String atAddress1 = generateAtAddress();
|
||||
int fee1 = 1;
|
||||
|
||||
Long timestamp2 = 2_000_000L;
|
||||
String atAddress2 = generateAtAddress();
|
||||
int fee2 = 2;
|
||||
|
||||
Long timestamp3 = 5_000_000L;
|
||||
String atAddress3 = generateAtAddress();
|
||||
int fee3 = 3;
|
||||
|
||||
boolean exceptionThrown = false;
|
||||
|
||||
try {
|
||||
// random key generation for signing data
|
||||
KeyPair keyPair1 = TestUtils.generateKeyPair();
|
||||
KeyPair keyPair2 = TestUtils.generateKeyPair();
|
||||
|
||||
// data to send, a list of 3 foreign fee data
|
||||
List<ForeignFeeDecodedData> sendData
|
||||
= List.of(
|
||||
new ForeignFeeDecodedData(timestamp1, assertSignature(keyPair1,timestamp1,atAddress1, fee1), atAddress1, fee1),
|
||||
new ForeignFeeDecodedData(timestamp2, assertSignature(keyPair1,timestamp2,atAddress2, fee2), atAddress2, fee2),
|
||||
new ForeignFeeDecodedData(timestamp3, assertSignature(keyPair2,timestamp3,atAddress3, fee3), atAddress3, fee3)
|
||||
);
|
||||
|
||||
// from data to bytes
|
||||
byte[] sendBytes = ForeignFeesMessageUtils.fromDataToSendBytes(sendData);
|
||||
|
||||
// from bytes to data
|
||||
List<ForeignFeeDecodedData> returnData = ForeignFeesMessageUtils.fromSendBytesToData(ByteBuffer.wrap(sendBytes));
|
||||
|
||||
|
||||
assertListedForeignFees(sendData, returnData, true);
|
||||
|
||||
} catch (Exception e) {
|
||||
exceptionThrown = true;
|
||||
}
|
||||
|
||||
Assert.assertFalse(exceptionThrown);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDataToGetBytesToDataTriple() {
|
||||
|
||||
Long timestamp1 = 1_000_000L;
|
||||
String atAddress1 = generateAtAddress();
|
||||
int fee1 = 1;
|
||||
|
||||
Long timestamp2 = 2_000_000L;
|
||||
String atAddress2 = generateAtAddress();
|
||||
int fee2 = 2;
|
||||
|
||||
Long timestamp3 = 5_000_000L;
|
||||
String atAddress3 = generateAtAddress();
|
||||
int fee3 = 3;
|
||||
|
||||
boolean exceptionThrown = false;
|
||||
|
||||
try {
|
||||
// random key generation for signing data
|
||||
KeyPair keyPair1 = TestUtils.generateKeyPair();
|
||||
KeyPair keyPair2 = TestUtils.generateKeyPair();
|
||||
|
||||
// data to send, a list of 3 foreign fee data
|
||||
List<ForeignFeeDecodedData> sendData
|
||||
= List.of(
|
||||
new ForeignFeeDecodedData(timestamp1, assertSignature(keyPair1,timestamp1,atAddress1, fee1), atAddress1, fee1),
|
||||
new ForeignFeeDecodedData(timestamp2, assertSignature(keyPair1,timestamp2,atAddress2, fee2), atAddress2, fee2),
|
||||
new ForeignFeeDecodedData(timestamp3, assertSignature(keyPair2,timestamp3,atAddress3, fee3), atAddress3, fee3)
|
||||
);
|
||||
|
||||
// from data to bytes
|
||||
byte[] sendBytes = ForeignFeesMessageUtils.fromDataToGetBytes(sendData);
|
||||
|
||||
// from bytes to data
|
||||
List<ForeignFeeDecodedData> returnData = ForeignFeesMessageUtils.fromGetBytesToData(ByteBuffer.wrap(sendBytes));
|
||||
|
||||
|
||||
assertListedForeignFees(sendData, returnData, false);
|
||||
|
||||
} catch (Exception e) {
|
||||
exceptionThrown = true;
|
||||
}
|
||||
|
||||
Assert.assertFalse(exceptionThrown);
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert Listed Foreign Fees
|
||||
*
|
||||
* @param expectedList
|
||||
* @param actualList
|
||||
* @param includeSignature
|
||||
*/
|
||||
private static void assertListedForeignFees(List<ForeignFeeDecodedData> expectedList, List<ForeignFeeDecodedData> actualList, boolean includeSignature) {
|
||||
|
||||
int expectedSize = expectedList.size();
|
||||
|
||||
// basic assertions on return data
|
||||
Assert.assertNotNull(actualList);
|
||||
Assert.assertEquals(expectedSize, actualList.size());
|
||||
|
||||
for( int index = 0; index < expectedSize; index++ ) {
|
||||
// expected and actual fee data
|
||||
ForeignFeeDecodedData expected = expectedList.get(index);
|
||||
ForeignFeeDecodedData actual = actualList.get(index);
|
||||
|
||||
assertForeignFeeEquality(expected, actual, includeSignature);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert Foreign Fee Equality
|
||||
*
|
||||
* @param expected the expected data, for comparison
|
||||
* @param actual the actual data, the response to evaluate
|
||||
* @param includeSignature
|
||||
*/
|
||||
private static void assertForeignFeeEquality(ForeignFeeDecodedData expected, ForeignFeeDecodedData actual, boolean includeSignature) {
|
||||
// assert
|
||||
Assert.assertEquals(expected, actual);
|
||||
|
||||
if( includeSignature ) {
|
||||
// get the data members of each, since the data members are not part of the object comparison above
|
||||
byte[] expectedData = expected.getData();
|
||||
byte[] actualData = actual.getData();
|
||||
|
||||
// assert data members, must encode them to strings for comparisons
|
||||
Assert.assertNotNull(actualData);
|
||||
Assert.assertEquals(Base58.encode(expectedData), Base58.encode(actualData));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate AT Address
|
||||
*
|
||||
* Generate AT address using a random inpute seed.
|
||||
*
|
||||
* @return the AT address
|
||||
*/
|
||||
private static String generateAtAddress() {
|
||||
|
||||
byte[] signature = new byte[64];
|
||||
RANDOM.nextBytes(signature);
|
||||
String atAddress = Crypto.toATAddress(signature);
|
||||
|
||||
return atAddress;
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user