forked from Qortal/qortal
Adding in foreign blockchain server configuration add and remove capabilities. Also adding in connection recording and connection setting capabilities.
This commit is contained in:
parent
d54f840265
commit
498f409346
@ -18,7 +18,11 @@ import org.qortal.api.model.crosschain.AddressRequest;
|
|||||||
import org.qortal.api.model.crosschain.BitcoinSendRequest;
|
import org.qortal.api.model.crosschain.BitcoinSendRequest;
|
||||||
import org.qortal.crosschain.AddressInfo;
|
import org.qortal.crosschain.AddressInfo;
|
||||||
import org.qortal.crosschain.Bitcoin;
|
import org.qortal.crosschain.Bitcoin;
|
||||||
|
import org.qortal.crosschain.ChainableServer;
|
||||||
|
import org.qortal.crosschain.ElectrumX;
|
||||||
import org.qortal.crosschain.ForeignBlockchainException;
|
import org.qortal.crosschain.ForeignBlockchainException;
|
||||||
|
import org.qortal.crosschain.ServerConnectionInfo;
|
||||||
|
import org.qortal.crosschain.ServerInfo;
|
||||||
import org.qortal.crosschain.SimpleTransaction;
|
import org.qortal.crosschain.SimpleTransaction;
|
||||||
import org.qortal.crosschain.ServerConfigurationInfo;
|
import org.qortal.crosschain.ServerConfigurationInfo;
|
||||||
|
|
||||||
@ -267,6 +271,181 @@ public class CrossChainBitcoinResource {
|
|||||||
return CrossChainUtils.buildServerConfigurationInfo(Bitcoin.getInstance());
|
return CrossChainUtils.buildServerConfigurationInfo(Bitcoin.getInstance());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GET
|
||||||
|
@Path("/serverconnectionhistory")
|
||||||
|
@Operation(
|
||||||
|
summary = "Returns Bitcoin server connection history",
|
||||||
|
description = "Returns Bitcoin server connection history",
|
||||||
|
responses = {
|
||||||
|
@ApiResponse(
|
||||||
|
content = @Content(array = @ArraySchema( schema = @Schema( implementation = ServerConnectionInfo.class ) ) )
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
public List<ServerConnectionInfo> getServerConnectionHistory() {
|
||||||
|
|
||||||
|
return CrossChainUtils.buildServerConnectionHistory(Bitcoin.getInstance());
|
||||||
|
}
|
||||||
|
|
||||||
|
@POST
|
||||||
|
@Path("/addserver")
|
||||||
|
@Operation(
|
||||||
|
summary = "Add server to list of Bitcoin servers",
|
||||||
|
description = "Add server to list of Bitcoin servers",
|
||||||
|
requestBody = @RequestBody(
|
||||||
|
required = true,
|
||||||
|
content = @Content(
|
||||||
|
mediaType = MediaType.APPLICATION_JSON,
|
||||||
|
schema = @Schema(
|
||||||
|
implementation = ServerInfo.class
|
||||||
|
)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
responses = {
|
||||||
|
@ApiResponse(
|
||||||
|
description = "true if removed, false if not found",
|
||||||
|
content = @Content(
|
||||||
|
schema = @Schema(
|
||||||
|
type = "string"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
)
|
||||||
|
@ApiErrors({ApiError.INVALID_DATA})
|
||||||
|
@SecurityRequirement(name = "apiKey")
|
||||||
|
public String addServer(@HeaderParam(Security.API_KEY_HEADER) String apiKey, ServerInfo serverInfo) {
|
||||||
|
Security.checkApiCallAllowed(request);
|
||||||
|
|
||||||
|
try {
|
||||||
|
ElectrumX.Server server = new ElectrumX.Server(
|
||||||
|
serverInfo.getHostName(),
|
||||||
|
ChainableServer.ConnectionType.valueOf(serverInfo.getConnectionType()),
|
||||||
|
serverInfo.getPort()
|
||||||
|
);
|
||||||
|
|
||||||
|
if( CrossChainUtils.addServer( Bitcoin.getInstance(), server )) {
|
||||||
|
return "true";
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return "false";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (IllegalArgumentException | NullPointerException e) {
|
||||||
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_DATA);
|
||||||
|
}
|
||||||
|
catch (Exception e) {
|
||||||
|
return "false";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@POST
|
||||||
|
@Path("/removeserver")
|
||||||
|
@Operation(
|
||||||
|
summary = "Remove server from list of Bitcoin servers",
|
||||||
|
description = "Remove server from list of Bitcoin servers",
|
||||||
|
requestBody = @RequestBody(
|
||||||
|
required = true,
|
||||||
|
content = @Content(
|
||||||
|
mediaType = MediaType.APPLICATION_JSON,
|
||||||
|
schema = @Schema(
|
||||||
|
implementation = ServerInfo.class
|
||||||
|
)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
responses = {
|
||||||
|
@ApiResponse(
|
||||||
|
description = "true if removed, otherwise",
|
||||||
|
content = @Content(
|
||||||
|
schema = @Schema(
|
||||||
|
type = "string"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
)
|
||||||
|
@ApiErrors({ApiError.INVALID_DATA})
|
||||||
|
@SecurityRequirement(name = "apiKey")
|
||||||
|
public String removeServer(@HeaderParam(Security.API_KEY_HEADER) String apiKey, ServerInfo serverInfo) {
|
||||||
|
Security.checkApiCallAllowed(request);
|
||||||
|
|
||||||
|
try {
|
||||||
|
ElectrumX.Server server = new ElectrumX.Server(
|
||||||
|
serverInfo.getHostName(),
|
||||||
|
ChainableServer.ConnectionType.valueOf(serverInfo.getConnectionType()),
|
||||||
|
serverInfo.getPort()
|
||||||
|
);
|
||||||
|
|
||||||
|
if( CrossChainUtils.removeServer( Bitcoin.getInstance(), server ) ) {
|
||||||
|
|
||||||
|
return "true";
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return "false";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (IllegalArgumentException | NullPointerException e) {
|
||||||
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_DATA);
|
||||||
|
}
|
||||||
|
catch (Exception e) {
|
||||||
|
return "false";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@POST
|
||||||
|
@Path("/setcurrentserver")
|
||||||
|
@Operation(
|
||||||
|
summary = "Set current Bitcoin server",
|
||||||
|
description = "Set current Bitcoin server",
|
||||||
|
requestBody = @RequestBody(
|
||||||
|
required = true,
|
||||||
|
content = @Content(
|
||||||
|
mediaType = MediaType.APPLICATION_JSON,
|
||||||
|
schema = @Schema(
|
||||||
|
implementation = ServerInfo.class
|
||||||
|
)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
responses = {
|
||||||
|
@ApiResponse(
|
||||||
|
description = "connection info",
|
||||||
|
content = @Content(
|
||||||
|
mediaType = MediaType.APPLICATION_JSON,
|
||||||
|
schema = @Schema(
|
||||||
|
implementation = ServerConnectionInfo.class
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
)
|
||||||
|
@ApiErrors({ApiError.INVALID_DATA})
|
||||||
|
@SecurityRequirement(name = "apiKey")
|
||||||
|
public ServerConnectionInfo setCurrentServer(@HeaderParam(Security.API_KEY_HEADER) String apiKey, ServerInfo serverInfo) {
|
||||||
|
Security.checkApiCallAllowed(request);
|
||||||
|
|
||||||
|
if( serverInfo.getConnectionType() == null ||
|
||||||
|
serverInfo.getHostName() == null) throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_DATA);
|
||||||
|
try {
|
||||||
|
return CrossChainUtils.setCurrentServer( Bitcoin.getInstance(), serverInfo );
|
||||||
|
}
|
||||||
|
catch (IllegalArgumentException e) {
|
||||||
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_DATA);
|
||||||
|
}
|
||||||
|
catch (Exception e) {
|
||||||
|
return new ServerConnectionInfo(
|
||||||
|
serverInfo,
|
||||||
|
CrossChainUtils.CORE_API_CALL,
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
System.currentTimeMillis(),
|
||||||
|
CrossChainUtils.getNotes(e));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@GET
|
@GET
|
||||||
@Path("/feekb")
|
@Path("/feekb")
|
||||||
@Operation(
|
@Operation(
|
||||||
|
@ -16,8 +16,12 @@ import org.qortal.api.Security;
|
|||||||
import org.qortal.api.model.crosschain.AddressRequest;
|
import org.qortal.api.model.crosschain.AddressRequest;
|
||||||
import org.qortal.api.model.crosschain.DigibyteSendRequest;
|
import org.qortal.api.model.crosschain.DigibyteSendRequest;
|
||||||
import org.qortal.crosschain.AddressInfo;
|
import org.qortal.crosschain.AddressInfo;
|
||||||
import org.qortal.crosschain.Digibyte;
|
import org.qortal.crosschain.ChainableServer;
|
||||||
|
import org.qortal.crosschain.ElectrumX;
|
||||||
import org.qortal.crosschain.ForeignBlockchainException;
|
import org.qortal.crosschain.ForeignBlockchainException;
|
||||||
|
import org.qortal.crosschain.Digibyte;
|
||||||
|
import org.qortal.crosschain.ServerConnectionInfo;
|
||||||
|
import org.qortal.crosschain.ServerInfo;
|
||||||
import org.qortal.crosschain.SimpleTransaction;
|
import org.qortal.crosschain.SimpleTransaction;
|
||||||
import org.qortal.crosschain.ServerConfigurationInfo;
|
import org.qortal.crosschain.ServerConfigurationInfo;
|
||||||
|
|
||||||
@ -266,6 +270,181 @@ public class CrossChainDigibyteResource {
|
|||||||
return CrossChainUtils.buildServerConfigurationInfo(Digibyte.getInstance());
|
return CrossChainUtils.buildServerConfigurationInfo(Digibyte.getInstance());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GET
|
||||||
|
@Path("/serverconnectionhistory")
|
||||||
|
@Operation(
|
||||||
|
summary = "Returns Digibyte server connection history",
|
||||||
|
description = "Returns Digibyte server connection history",
|
||||||
|
responses = {
|
||||||
|
@ApiResponse(
|
||||||
|
content = @Content(array = @ArraySchema( schema = @Schema( implementation = ServerConnectionInfo.class ) ) )
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
public List<ServerConnectionInfo> getServerConnectionHistory() {
|
||||||
|
|
||||||
|
return CrossChainUtils.buildServerConnectionHistory(Digibyte.getInstance());
|
||||||
|
}
|
||||||
|
|
||||||
|
@POST
|
||||||
|
@Path("/addserver")
|
||||||
|
@Operation(
|
||||||
|
summary = "Add server to list of Digibyte servers",
|
||||||
|
description = "Add server to list of Digibyte servers",
|
||||||
|
requestBody = @RequestBody(
|
||||||
|
required = true,
|
||||||
|
content = @Content(
|
||||||
|
mediaType = MediaType.APPLICATION_JSON,
|
||||||
|
schema = @Schema(
|
||||||
|
implementation = ServerInfo.class
|
||||||
|
)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
responses = {
|
||||||
|
@ApiResponse(
|
||||||
|
description = "true if removed, false if not found",
|
||||||
|
content = @Content(
|
||||||
|
schema = @Schema(
|
||||||
|
type = "string"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
)
|
||||||
|
@ApiErrors({ApiError.INVALID_DATA})
|
||||||
|
@SecurityRequirement(name = "apiKey")
|
||||||
|
public String addServer(@HeaderParam(Security.API_KEY_HEADER) String apiKey, ServerInfo serverInfo) {
|
||||||
|
Security.checkApiCallAllowed(request);
|
||||||
|
|
||||||
|
try {
|
||||||
|
ElectrumX.Server server = new ElectrumX.Server(
|
||||||
|
serverInfo.getHostName(),
|
||||||
|
ChainableServer.ConnectionType.valueOf(serverInfo.getConnectionType()),
|
||||||
|
serverInfo.getPort()
|
||||||
|
);
|
||||||
|
|
||||||
|
if( CrossChainUtils.addServer( Digibyte.getInstance(), server )) {
|
||||||
|
return "true";
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return "false";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (IllegalArgumentException | NullPointerException e) {
|
||||||
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_DATA);
|
||||||
|
}
|
||||||
|
catch (Exception e) {
|
||||||
|
return "false";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@POST
|
||||||
|
@Path("/removeserver")
|
||||||
|
@Operation(
|
||||||
|
summary = "Remove server from list of Digibyte servers",
|
||||||
|
description = "Remove server from list of Digibyte servers",
|
||||||
|
requestBody = @RequestBody(
|
||||||
|
required = true,
|
||||||
|
content = @Content(
|
||||||
|
mediaType = MediaType.APPLICATION_JSON,
|
||||||
|
schema = @Schema(
|
||||||
|
implementation = ServerInfo.class
|
||||||
|
)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
responses = {
|
||||||
|
@ApiResponse(
|
||||||
|
description = "true if removed, otherwise",
|
||||||
|
content = @Content(
|
||||||
|
schema = @Schema(
|
||||||
|
type = "string"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
)
|
||||||
|
@ApiErrors({ApiError.INVALID_DATA})
|
||||||
|
@SecurityRequirement(name = "apiKey")
|
||||||
|
public String removeServer(@HeaderParam(Security.API_KEY_HEADER) String apiKey, ServerInfo serverInfo) {
|
||||||
|
Security.checkApiCallAllowed(request);
|
||||||
|
|
||||||
|
try {
|
||||||
|
ElectrumX.Server server = new ElectrumX.Server(
|
||||||
|
serverInfo.getHostName(),
|
||||||
|
ChainableServer.ConnectionType.valueOf(serverInfo.getConnectionType()),
|
||||||
|
serverInfo.getPort()
|
||||||
|
);
|
||||||
|
|
||||||
|
if( CrossChainUtils.removeServer( Digibyte.getInstance(), server ) ) {
|
||||||
|
|
||||||
|
return "true";
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return "false";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (IllegalArgumentException | NullPointerException e) {
|
||||||
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_DATA);
|
||||||
|
}
|
||||||
|
catch (Exception e) {
|
||||||
|
return "false";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@POST
|
||||||
|
@Path("/setcurrentserver")
|
||||||
|
@Operation(
|
||||||
|
summary = "Set current Digibyte server",
|
||||||
|
description = "Set current Digibyte server",
|
||||||
|
requestBody = @RequestBody(
|
||||||
|
required = true,
|
||||||
|
content = @Content(
|
||||||
|
mediaType = MediaType.APPLICATION_JSON,
|
||||||
|
schema = @Schema(
|
||||||
|
implementation = ServerInfo.class
|
||||||
|
)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
responses = {
|
||||||
|
@ApiResponse(
|
||||||
|
description = "connection info",
|
||||||
|
content = @Content(
|
||||||
|
mediaType = MediaType.APPLICATION_JSON,
|
||||||
|
schema = @Schema(
|
||||||
|
implementation = ServerConnectionInfo.class
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
)
|
||||||
|
@ApiErrors({ApiError.INVALID_DATA})
|
||||||
|
@SecurityRequirement(name = "apiKey")
|
||||||
|
public ServerConnectionInfo setCurrentServer(@HeaderParam(Security.API_KEY_HEADER) String apiKey, ServerInfo serverInfo) {
|
||||||
|
Security.checkApiCallAllowed(request);
|
||||||
|
|
||||||
|
if( serverInfo.getConnectionType() == null ||
|
||||||
|
serverInfo.getHostName() == null) throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_DATA);
|
||||||
|
try {
|
||||||
|
return CrossChainUtils.setCurrentServer( Digibyte.getInstance(), serverInfo );
|
||||||
|
}
|
||||||
|
catch (IllegalArgumentException e) {
|
||||||
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_DATA);
|
||||||
|
}
|
||||||
|
catch (Exception e) {
|
||||||
|
return new ServerConnectionInfo(
|
||||||
|
serverInfo,
|
||||||
|
CrossChainUtils.CORE_API_CALL,
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
System.currentTimeMillis(),
|
||||||
|
CrossChainUtils.getNotes(e));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@GET
|
@GET
|
||||||
@Path("/feekb")
|
@Path("/feekb")
|
||||||
@Operation(
|
@Operation(
|
||||||
|
@ -16,8 +16,12 @@ import org.qortal.api.Security;
|
|||||||
import org.qortal.api.model.crosschain.AddressRequest;
|
import org.qortal.api.model.crosschain.AddressRequest;
|
||||||
import org.qortal.api.model.crosschain.DogecoinSendRequest;
|
import org.qortal.api.model.crosschain.DogecoinSendRequest;
|
||||||
import org.qortal.crosschain.AddressInfo;
|
import org.qortal.crosschain.AddressInfo;
|
||||||
import org.qortal.crosschain.Dogecoin;
|
import org.qortal.crosschain.ChainableServer;
|
||||||
|
import org.qortal.crosschain.ElectrumX;
|
||||||
import org.qortal.crosschain.ForeignBlockchainException;
|
import org.qortal.crosschain.ForeignBlockchainException;
|
||||||
|
import org.qortal.crosschain.Dogecoin;
|
||||||
|
import org.qortal.crosschain.ServerConnectionInfo;
|
||||||
|
import org.qortal.crosschain.ServerInfo;
|
||||||
import org.qortal.crosschain.SimpleTransaction;
|
import org.qortal.crosschain.SimpleTransaction;
|
||||||
import org.qortal.crosschain.ServerConfigurationInfo;
|
import org.qortal.crosschain.ServerConfigurationInfo;
|
||||||
|
|
||||||
@ -266,6 +270,181 @@ public class CrossChainDogecoinResource {
|
|||||||
return CrossChainUtils.buildServerConfigurationInfo(Dogecoin.getInstance());
|
return CrossChainUtils.buildServerConfigurationInfo(Dogecoin.getInstance());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GET
|
||||||
|
@Path("/serverconnectionhistory")
|
||||||
|
@Operation(
|
||||||
|
summary = "Returns Dogecoin server connection history",
|
||||||
|
description = "Returns Dogecoin server connection history",
|
||||||
|
responses = {
|
||||||
|
@ApiResponse(
|
||||||
|
content = @Content(array = @ArraySchema( schema = @Schema( implementation = ServerConnectionInfo.class ) ) )
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
public List<ServerConnectionInfo> getServerConnectionHistory() {
|
||||||
|
|
||||||
|
return CrossChainUtils.buildServerConnectionHistory(Dogecoin.getInstance());
|
||||||
|
}
|
||||||
|
|
||||||
|
@POST
|
||||||
|
@Path("/addserver")
|
||||||
|
@Operation(
|
||||||
|
summary = "Add server to list of Dogecoin servers",
|
||||||
|
description = "Add server to list of Dogecoin servers",
|
||||||
|
requestBody = @RequestBody(
|
||||||
|
required = true,
|
||||||
|
content = @Content(
|
||||||
|
mediaType = MediaType.APPLICATION_JSON,
|
||||||
|
schema = @Schema(
|
||||||
|
implementation = ServerInfo.class
|
||||||
|
)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
responses = {
|
||||||
|
@ApiResponse(
|
||||||
|
description = "true if removed, false if not found",
|
||||||
|
content = @Content(
|
||||||
|
schema = @Schema(
|
||||||
|
type = "string"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
)
|
||||||
|
@ApiErrors({ApiError.INVALID_DATA})
|
||||||
|
@SecurityRequirement(name = "apiKey")
|
||||||
|
public String addServer(@HeaderParam(Security.API_KEY_HEADER) String apiKey, ServerInfo serverInfo) {
|
||||||
|
Security.checkApiCallAllowed(request);
|
||||||
|
|
||||||
|
try {
|
||||||
|
ElectrumX.Server server = new ElectrumX.Server(
|
||||||
|
serverInfo.getHostName(),
|
||||||
|
ChainableServer.ConnectionType.valueOf(serverInfo.getConnectionType()),
|
||||||
|
serverInfo.getPort()
|
||||||
|
);
|
||||||
|
|
||||||
|
if( CrossChainUtils.addServer( Dogecoin.getInstance(), server )) {
|
||||||
|
return "true";
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return "false";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (IllegalArgumentException | NullPointerException e) {
|
||||||
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_DATA);
|
||||||
|
}
|
||||||
|
catch (Exception e) {
|
||||||
|
return "false";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@POST
|
||||||
|
@Path("/removeserver")
|
||||||
|
@Operation(
|
||||||
|
summary = "Remove server from list of Dogecoin servers",
|
||||||
|
description = "Remove server from list of Dogecoin servers",
|
||||||
|
requestBody = @RequestBody(
|
||||||
|
required = true,
|
||||||
|
content = @Content(
|
||||||
|
mediaType = MediaType.APPLICATION_JSON,
|
||||||
|
schema = @Schema(
|
||||||
|
implementation = ServerInfo.class
|
||||||
|
)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
responses = {
|
||||||
|
@ApiResponse(
|
||||||
|
description = "true if removed, otherwise",
|
||||||
|
content = @Content(
|
||||||
|
schema = @Schema(
|
||||||
|
type = "string"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
)
|
||||||
|
@ApiErrors({ApiError.INVALID_DATA})
|
||||||
|
@SecurityRequirement(name = "apiKey")
|
||||||
|
public String removeServer(@HeaderParam(Security.API_KEY_HEADER) String apiKey, ServerInfo serverInfo) {
|
||||||
|
Security.checkApiCallAllowed(request);
|
||||||
|
|
||||||
|
try {
|
||||||
|
ElectrumX.Server server = new ElectrumX.Server(
|
||||||
|
serverInfo.getHostName(),
|
||||||
|
ChainableServer.ConnectionType.valueOf(serverInfo.getConnectionType()),
|
||||||
|
serverInfo.getPort()
|
||||||
|
);
|
||||||
|
|
||||||
|
if( CrossChainUtils.removeServer( Dogecoin.getInstance(), server ) ) {
|
||||||
|
|
||||||
|
return "true";
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return "false";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (IllegalArgumentException | NullPointerException e) {
|
||||||
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_DATA);
|
||||||
|
}
|
||||||
|
catch (Exception e) {
|
||||||
|
return "false";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@POST
|
||||||
|
@Path("/setcurrentserver")
|
||||||
|
@Operation(
|
||||||
|
summary = "Set current Dogecoin server",
|
||||||
|
description = "Set current Dogecoin server",
|
||||||
|
requestBody = @RequestBody(
|
||||||
|
required = true,
|
||||||
|
content = @Content(
|
||||||
|
mediaType = MediaType.APPLICATION_JSON,
|
||||||
|
schema = @Schema(
|
||||||
|
implementation = ServerInfo.class
|
||||||
|
)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
responses = {
|
||||||
|
@ApiResponse(
|
||||||
|
description = "connection info",
|
||||||
|
content = @Content(
|
||||||
|
mediaType = MediaType.APPLICATION_JSON,
|
||||||
|
schema = @Schema(
|
||||||
|
implementation = ServerConnectionInfo.class
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
)
|
||||||
|
@ApiErrors({ApiError.INVALID_DATA})
|
||||||
|
@SecurityRequirement(name = "apiKey")
|
||||||
|
public ServerConnectionInfo setCurrentServer(@HeaderParam(Security.API_KEY_HEADER) String apiKey, ServerInfo serverInfo) {
|
||||||
|
Security.checkApiCallAllowed(request);
|
||||||
|
|
||||||
|
if( serverInfo.getConnectionType() == null ||
|
||||||
|
serverInfo.getHostName() == null) throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_DATA);
|
||||||
|
try {
|
||||||
|
return CrossChainUtils.setCurrentServer( Dogecoin.getInstance(), serverInfo );
|
||||||
|
}
|
||||||
|
catch (IllegalArgumentException e) {
|
||||||
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_DATA);
|
||||||
|
}
|
||||||
|
catch (Exception e) {
|
||||||
|
return new ServerConnectionInfo(
|
||||||
|
serverInfo,
|
||||||
|
CrossChainUtils.CORE_API_CALL,
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
System.currentTimeMillis(),
|
||||||
|
CrossChainUtils.getNotes(e));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@GET
|
@GET
|
||||||
@Path("/feekb")
|
@Path("/feekb")
|
||||||
@Operation(
|
@Operation(
|
||||||
|
@ -16,8 +16,12 @@ import org.qortal.api.Security;
|
|||||||
import org.qortal.api.model.crosschain.AddressRequest;
|
import org.qortal.api.model.crosschain.AddressRequest;
|
||||||
import org.qortal.api.model.crosschain.LitecoinSendRequest;
|
import org.qortal.api.model.crosschain.LitecoinSendRequest;
|
||||||
import org.qortal.crosschain.AddressInfo;
|
import org.qortal.crosschain.AddressInfo;
|
||||||
|
import org.qortal.crosschain.ChainableServer;
|
||||||
|
import org.qortal.crosschain.ElectrumX;
|
||||||
import org.qortal.crosschain.ForeignBlockchainException;
|
import org.qortal.crosschain.ForeignBlockchainException;
|
||||||
import org.qortal.crosschain.Litecoin;
|
import org.qortal.crosschain.Litecoin;
|
||||||
|
import org.qortal.crosschain.ServerConnectionInfo;
|
||||||
|
import org.qortal.crosschain.ServerInfo;
|
||||||
import org.qortal.crosschain.SimpleTransaction;
|
import org.qortal.crosschain.SimpleTransaction;
|
||||||
import org.qortal.crosschain.ServerConfigurationInfo;
|
import org.qortal.crosschain.ServerConfigurationInfo;
|
||||||
|
|
||||||
@ -266,6 +270,180 @@ public class CrossChainLitecoinResource {
|
|||||||
return CrossChainUtils.buildServerConfigurationInfo(Litecoin.getInstance());
|
return CrossChainUtils.buildServerConfigurationInfo(Litecoin.getInstance());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GET
|
||||||
|
@Path("/serverconnectionhistory")
|
||||||
|
@Operation(
|
||||||
|
summary = "Returns Litecoin server connection history",
|
||||||
|
description = "Returns Litecoin server connection history",
|
||||||
|
responses = {
|
||||||
|
@ApiResponse(
|
||||||
|
content = @Content(array = @ArraySchema( schema = @Schema( implementation = ServerConnectionInfo.class ) ) )
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
public List<ServerConnectionInfo> getServerConnectionHistory() {
|
||||||
|
|
||||||
|
return CrossChainUtils.buildServerConnectionHistory(Litecoin.getInstance());
|
||||||
|
}
|
||||||
|
|
||||||
|
@POST
|
||||||
|
@Path("/addserver")
|
||||||
|
@Operation(
|
||||||
|
summary = "Add server to list of Litecoin servers",
|
||||||
|
description = "Add server to list of Litecoin servers",
|
||||||
|
requestBody = @RequestBody(
|
||||||
|
required = true,
|
||||||
|
content = @Content(
|
||||||
|
mediaType = MediaType.APPLICATION_JSON,
|
||||||
|
schema = @Schema(
|
||||||
|
implementation = ServerInfo.class
|
||||||
|
)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
responses = {
|
||||||
|
@ApiResponse(
|
||||||
|
description = "true if removed, false if not found",
|
||||||
|
content = @Content(
|
||||||
|
schema = @Schema(
|
||||||
|
type = "string"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
)
|
||||||
|
@ApiErrors({ApiError.INVALID_DATA})
|
||||||
|
@SecurityRequirement(name = "apiKey")
|
||||||
|
public String addServer(@HeaderParam(Security.API_KEY_HEADER) String apiKey, ServerInfo serverInfo) {
|
||||||
|
Security.checkApiCallAllowed(request);
|
||||||
|
|
||||||
|
try {
|
||||||
|
ElectrumX.Server server = new ElectrumX.Server(
|
||||||
|
serverInfo.getHostName(),
|
||||||
|
ChainableServer.ConnectionType.valueOf(serverInfo.getConnectionType()),
|
||||||
|
serverInfo.getPort()
|
||||||
|
);
|
||||||
|
|
||||||
|
if( CrossChainUtils.addServer( Litecoin.getInstance(), server )) {
|
||||||
|
return "true";
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return "false";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (IllegalArgumentException | NullPointerException e) {
|
||||||
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_DATA);
|
||||||
|
}
|
||||||
|
catch (Exception e) {
|
||||||
|
return "false";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@POST
|
||||||
|
@Path("/removeserver")
|
||||||
|
@Operation(
|
||||||
|
summary = "Remove server from list of Litecoin servers",
|
||||||
|
description = "Remove server from list of Litecoin servers",
|
||||||
|
requestBody = @RequestBody(
|
||||||
|
required = true,
|
||||||
|
content = @Content(
|
||||||
|
mediaType = MediaType.APPLICATION_JSON,
|
||||||
|
schema = @Schema(
|
||||||
|
implementation = ServerInfo.class
|
||||||
|
)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
responses = {
|
||||||
|
@ApiResponse(
|
||||||
|
description = "true if removed, otherwise",
|
||||||
|
content = @Content(
|
||||||
|
schema = @Schema(
|
||||||
|
type = "string"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
)
|
||||||
|
@ApiErrors({ApiError.INVALID_DATA})
|
||||||
|
@SecurityRequirement(name = "apiKey")
|
||||||
|
public String removeServer(@HeaderParam(Security.API_KEY_HEADER) String apiKey, ServerInfo serverInfo) {
|
||||||
|
Security.checkApiCallAllowed(request);
|
||||||
|
|
||||||
|
try {
|
||||||
|
ElectrumX.Server server = new ElectrumX.Server(
|
||||||
|
serverInfo.getHostName(),
|
||||||
|
ChainableServer.ConnectionType.valueOf(serverInfo.getConnectionType()),
|
||||||
|
serverInfo.getPort()
|
||||||
|
);
|
||||||
|
|
||||||
|
if( CrossChainUtils.removeServer( Litecoin.getInstance(), server ) ) {
|
||||||
|
|
||||||
|
return "true";
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return "false";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (IllegalArgumentException | NullPointerException e) {
|
||||||
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_DATA);
|
||||||
|
}
|
||||||
|
catch (Exception e) {
|
||||||
|
return "false";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@POST
|
||||||
|
@Path("/setcurrentserver")
|
||||||
|
@Operation(
|
||||||
|
summary = "Set current Litecoin server",
|
||||||
|
description = "Set current Litecoin server",
|
||||||
|
requestBody = @RequestBody(
|
||||||
|
required = true,
|
||||||
|
content = @Content(
|
||||||
|
mediaType = MediaType.APPLICATION_JSON,
|
||||||
|
schema = @Schema(
|
||||||
|
implementation = ServerInfo.class
|
||||||
|
)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
responses = {
|
||||||
|
@ApiResponse(
|
||||||
|
description = "connection info",
|
||||||
|
content = @Content(
|
||||||
|
mediaType = MediaType.APPLICATION_JSON,
|
||||||
|
schema = @Schema(
|
||||||
|
implementation = ServerConnectionInfo.class
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
)
|
||||||
|
@ApiErrors({ApiError.INVALID_DATA})
|
||||||
|
@SecurityRequirement(name = "apiKey")
|
||||||
|
public ServerConnectionInfo setCurrentServer(@HeaderParam(Security.API_KEY_HEADER) String apiKey, ServerInfo serverInfo) {
|
||||||
|
Security.checkApiCallAllowed(request);
|
||||||
|
|
||||||
|
if( serverInfo.getConnectionType() == null ||
|
||||||
|
serverInfo.getHostName() == null) throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_DATA);
|
||||||
|
try {
|
||||||
|
return CrossChainUtils.setCurrentServer( Litecoin.getInstance(), serverInfo );
|
||||||
|
}
|
||||||
|
catch (IllegalArgumentException e) {
|
||||||
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_DATA);
|
||||||
|
}
|
||||||
|
catch (Exception e) {
|
||||||
|
return new ServerConnectionInfo(
|
||||||
|
serverInfo,
|
||||||
|
CrossChainUtils.CORE_API_CALL,
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
System.currentTimeMillis(),
|
||||||
|
CrossChainUtils.getNotes(e));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@POST
|
@POST
|
||||||
@Path("/repair")
|
@Path("/repair")
|
||||||
@Operation(
|
@Operation(
|
||||||
|
@ -13,8 +13,12 @@ import org.qortal.api.ApiErrors;
|
|||||||
import org.qortal.api.ApiExceptionFactory;
|
import org.qortal.api.ApiExceptionFactory;
|
||||||
import org.qortal.api.Security;
|
import org.qortal.api.Security;
|
||||||
import org.qortal.api.model.crosschain.PirateChainSendRequest;
|
import org.qortal.api.model.crosschain.PirateChainSendRequest;
|
||||||
|
import org.qortal.crosschain.ChainableServer;
|
||||||
import org.qortal.crosschain.ForeignBlockchainException;
|
import org.qortal.crosschain.ForeignBlockchainException;
|
||||||
import org.qortal.crosschain.PirateChain;
|
import org.qortal.crosschain.PirateChain;
|
||||||
|
import org.qortal.crosschain.PirateLightClient;
|
||||||
|
import org.qortal.crosschain.ServerConnectionInfo;
|
||||||
|
import org.qortal.crosschain.ServerInfo;
|
||||||
import org.qortal.crosschain.SimpleTransaction;
|
import org.qortal.crosschain.SimpleTransaction;
|
||||||
import org.qortal.crosschain.ServerConfigurationInfo;
|
import org.qortal.crosschain.ServerConfigurationInfo;
|
||||||
|
|
||||||
@ -352,6 +356,180 @@ public class CrossChainPirateChainResource {
|
|||||||
return CrossChainUtils.buildServerConfigurationInfo(PirateChain.getInstance());
|
return CrossChainUtils.buildServerConfigurationInfo(PirateChain.getInstance());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GET
|
||||||
|
@Path("/serverconnectionhistory")
|
||||||
|
@Operation(
|
||||||
|
summary = "Returns Pirate Chain server connection history",
|
||||||
|
description = "Returns Pirate Chain server connection history",
|
||||||
|
responses = {
|
||||||
|
@ApiResponse(
|
||||||
|
content = @Content(array = @ArraySchema( schema = @Schema( implementation = ServerConnectionInfo.class ) ) )
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
public List<ServerConnectionInfo> getServerConnectionHistory() {
|
||||||
|
|
||||||
|
return CrossChainUtils.buildServerConnectionHistory(PirateChain.getInstance());
|
||||||
|
}
|
||||||
|
|
||||||
|
@POST
|
||||||
|
@Path("/addserver")
|
||||||
|
@Operation(
|
||||||
|
summary = "Add server to list of Pirate Chain servers",
|
||||||
|
description = "Add server to list of Pirate Chain servers",
|
||||||
|
requestBody = @RequestBody(
|
||||||
|
required = true,
|
||||||
|
content = @Content(
|
||||||
|
mediaType = MediaType.APPLICATION_JSON,
|
||||||
|
schema = @Schema(
|
||||||
|
implementation = ServerInfo.class
|
||||||
|
)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
responses = {
|
||||||
|
@ApiResponse(
|
||||||
|
description = "true if removed, false if not found",
|
||||||
|
content = @Content(
|
||||||
|
schema = @Schema(
|
||||||
|
type = "string"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
)
|
||||||
|
@ApiErrors({ApiError.INVALID_DATA})
|
||||||
|
@SecurityRequirement(name = "apiKey")
|
||||||
|
public String addServerInfo(@HeaderParam(Security.API_KEY_HEADER) String apiKey, ServerInfo serverInfo) {
|
||||||
|
Security.checkApiCallAllowed(request);
|
||||||
|
|
||||||
|
try {
|
||||||
|
PirateLightClient.Server server = new PirateLightClient.Server(
|
||||||
|
serverInfo.getHostName(),
|
||||||
|
ChainableServer.ConnectionType.valueOf(serverInfo.getConnectionType()),
|
||||||
|
serverInfo.getPort()
|
||||||
|
);
|
||||||
|
|
||||||
|
if( CrossChainUtils.addServer( PirateChain.getInstance(), server )) {
|
||||||
|
return "true";
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return "false";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (IllegalArgumentException | NullPointerException e) {
|
||||||
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_DATA);
|
||||||
|
}
|
||||||
|
catch (Exception e) {
|
||||||
|
return "false";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@POST
|
||||||
|
@Path("/removeserver")
|
||||||
|
@Operation(
|
||||||
|
summary = "Remove server from list of Pirate Chain servers",
|
||||||
|
description = "Remove server from list of Pirate Chain servers",
|
||||||
|
requestBody = @RequestBody(
|
||||||
|
required = true,
|
||||||
|
content = @Content(
|
||||||
|
mediaType = MediaType.APPLICATION_JSON,
|
||||||
|
schema = @Schema(
|
||||||
|
implementation = ServerInfo.class
|
||||||
|
)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
responses = {
|
||||||
|
@ApiResponse(
|
||||||
|
description = "true if removed, otherwise",
|
||||||
|
content = @Content(
|
||||||
|
schema = @Schema(
|
||||||
|
type = "string"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
)
|
||||||
|
@ApiErrors({ApiError.INVALID_DATA})
|
||||||
|
@SecurityRequirement(name = "apiKey")
|
||||||
|
public String removeServerInfo(@HeaderParam(Security.API_KEY_HEADER) String apiKey, ServerInfo serverInfo) {
|
||||||
|
Security.checkApiCallAllowed(request);
|
||||||
|
|
||||||
|
try {
|
||||||
|
PirateLightClient.Server server = new PirateLightClient.Server(
|
||||||
|
serverInfo.getHostName(),
|
||||||
|
ChainableServer.ConnectionType.valueOf(serverInfo.getConnectionType()),
|
||||||
|
serverInfo.getPort()
|
||||||
|
);
|
||||||
|
|
||||||
|
if( CrossChainUtils.removeServer( PirateChain.getInstance(), server ) ) {
|
||||||
|
|
||||||
|
return "true";
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return "false";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (IllegalArgumentException | NullPointerException e) {
|
||||||
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_DATA);
|
||||||
|
}
|
||||||
|
catch (Exception e) {
|
||||||
|
return "false";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@POST
|
||||||
|
@Path("/setcurrentserver")
|
||||||
|
@Operation(
|
||||||
|
summary = "Set current Pirate Chain server",
|
||||||
|
description = "Set current Pirate Chain server",
|
||||||
|
requestBody = @RequestBody(
|
||||||
|
required = true,
|
||||||
|
content = @Content(
|
||||||
|
mediaType = MediaType.APPLICATION_JSON,
|
||||||
|
schema = @Schema(
|
||||||
|
implementation = ServerInfo.class
|
||||||
|
)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
responses = {
|
||||||
|
@ApiResponse(
|
||||||
|
description = "connection info",
|
||||||
|
content = @Content(
|
||||||
|
mediaType = MediaType.APPLICATION_JSON,
|
||||||
|
schema = @Schema(
|
||||||
|
implementation = ServerConnectionInfo.class
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
)
|
||||||
|
@ApiErrors({ApiError.INVALID_DATA})
|
||||||
|
@SecurityRequirement(name = "apiKey")
|
||||||
|
public ServerConnectionInfo setCurrentServerInfo(@HeaderParam(Security.API_KEY_HEADER) String apiKey, ServerInfo serverInfo) {
|
||||||
|
Security.checkApiCallAllowed(request);
|
||||||
|
|
||||||
|
if( serverInfo.getConnectionType() == null ||
|
||||||
|
serverInfo.getHostName() == null) throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_DATA);
|
||||||
|
try {
|
||||||
|
return CrossChainUtils.setCurrentServer( PirateChain.getInstance(), serverInfo );
|
||||||
|
}
|
||||||
|
catch (IllegalArgumentException e) {
|
||||||
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_DATA);
|
||||||
|
}
|
||||||
|
catch (Exception e) {
|
||||||
|
return new ServerConnectionInfo(
|
||||||
|
serverInfo,
|
||||||
|
CrossChainUtils.CORE_API_CALL,
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
System.currentTimeMillis(),
|
||||||
|
CrossChainUtils.getNotes(e));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@GET
|
@GET
|
||||||
@Path("/feekb")
|
@Path("/feekb")
|
||||||
@Operation(
|
@Operation(
|
||||||
|
@ -16,8 +16,12 @@ import org.qortal.api.Security;
|
|||||||
import org.qortal.api.model.crosschain.AddressRequest;
|
import org.qortal.api.model.crosschain.AddressRequest;
|
||||||
import org.qortal.api.model.crosschain.RavencoinSendRequest;
|
import org.qortal.api.model.crosschain.RavencoinSendRequest;
|
||||||
import org.qortal.crosschain.AddressInfo;
|
import org.qortal.crosschain.AddressInfo;
|
||||||
|
import org.qortal.crosschain.ChainableServer;
|
||||||
|
import org.qortal.crosschain.ElectrumX;
|
||||||
import org.qortal.crosschain.ForeignBlockchainException;
|
import org.qortal.crosschain.ForeignBlockchainException;
|
||||||
import org.qortal.crosschain.Ravencoin;
|
import org.qortal.crosschain.Ravencoin;
|
||||||
|
import org.qortal.crosschain.ServerConnectionInfo;
|
||||||
|
import org.qortal.crosschain.ServerInfo;
|
||||||
import org.qortal.crosschain.SimpleTransaction;
|
import org.qortal.crosschain.SimpleTransaction;
|
||||||
import org.qortal.crosschain.ServerConfigurationInfo;
|
import org.qortal.crosschain.ServerConfigurationInfo;
|
||||||
|
|
||||||
@ -266,6 +270,181 @@ public class CrossChainRavencoinResource {
|
|||||||
return CrossChainUtils.buildServerConfigurationInfo(Ravencoin.getInstance());
|
return CrossChainUtils.buildServerConfigurationInfo(Ravencoin.getInstance());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GET
|
||||||
|
@Path("/serverconnectionhistory")
|
||||||
|
@Operation(
|
||||||
|
summary = "Returns Ravencoin server connection history",
|
||||||
|
description = "Returns Ravencoin server connection history",
|
||||||
|
responses = {
|
||||||
|
@ApiResponse(
|
||||||
|
content = @Content(array = @ArraySchema( schema = @Schema( implementation = ServerConnectionInfo.class ) ) )
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
public List<ServerConnectionInfo> getServerConnectionHistory() {
|
||||||
|
|
||||||
|
return CrossChainUtils.buildServerConnectionHistory(Ravencoin.getInstance());
|
||||||
|
}
|
||||||
|
|
||||||
|
@POST
|
||||||
|
@Path("/addserver")
|
||||||
|
@Operation(
|
||||||
|
summary = "Add server to list of Ravencoin servers",
|
||||||
|
description = "Add server to list of Ravencoin servers",
|
||||||
|
requestBody = @RequestBody(
|
||||||
|
required = true,
|
||||||
|
content = @Content(
|
||||||
|
mediaType = MediaType.APPLICATION_JSON,
|
||||||
|
schema = @Schema(
|
||||||
|
implementation = ServerInfo.class
|
||||||
|
)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
responses = {
|
||||||
|
@ApiResponse(
|
||||||
|
description = "true if removed, false if not found",
|
||||||
|
content = @Content(
|
||||||
|
schema = @Schema(
|
||||||
|
type = "string"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
)
|
||||||
|
@ApiErrors({ApiError.INVALID_DATA})
|
||||||
|
@SecurityRequirement(name = "apiKey")
|
||||||
|
public String addServer(@HeaderParam(Security.API_KEY_HEADER) String apiKey, ServerInfo serverInfo) {
|
||||||
|
Security.checkApiCallAllowed(request);
|
||||||
|
|
||||||
|
try {
|
||||||
|
ElectrumX.Server server = new ElectrumX.Server(
|
||||||
|
serverInfo.getHostName(),
|
||||||
|
ChainableServer.ConnectionType.valueOf(serverInfo.getConnectionType()),
|
||||||
|
serverInfo.getPort()
|
||||||
|
);
|
||||||
|
|
||||||
|
if( CrossChainUtils.addServer( Ravencoin.getInstance(), server )) {
|
||||||
|
return "true";
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return "false";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (IllegalArgumentException | NullPointerException e) {
|
||||||
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_DATA);
|
||||||
|
}
|
||||||
|
catch (Exception e) {
|
||||||
|
return "false";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@POST
|
||||||
|
@Path("/removeserver")
|
||||||
|
@Operation(
|
||||||
|
summary = "Remove server from list of Ravencoin servers",
|
||||||
|
description = "Remove server from list of Ravencoin servers",
|
||||||
|
requestBody = @RequestBody(
|
||||||
|
required = true,
|
||||||
|
content = @Content(
|
||||||
|
mediaType = MediaType.APPLICATION_JSON,
|
||||||
|
schema = @Schema(
|
||||||
|
implementation = ServerInfo.class
|
||||||
|
)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
responses = {
|
||||||
|
@ApiResponse(
|
||||||
|
description = "true if removed, otherwise",
|
||||||
|
content = @Content(
|
||||||
|
schema = @Schema(
|
||||||
|
type = "string"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
)
|
||||||
|
@ApiErrors({ApiError.INVALID_DATA})
|
||||||
|
@SecurityRequirement(name = "apiKey")
|
||||||
|
public String removeServer(@HeaderParam(Security.API_KEY_HEADER) String apiKey, ServerInfo serverInfo) {
|
||||||
|
Security.checkApiCallAllowed(request);
|
||||||
|
|
||||||
|
try {
|
||||||
|
ElectrumX.Server server = new ElectrumX.Server(
|
||||||
|
serverInfo.getHostName(),
|
||||||
|
ChainableServer.ConnectionType.valueOf(serverInfo.getConnectionType()),
|
||||||
|
serverInfo.getPort()
|
||||||
|
);
|
||||||
|
|
||||||
|
if( CrossChainUtils.removeServer( Ravencoin.getInstance(), server ) ) {
|
||||||
|
|
||||||
|
return "true";
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return "false";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (IllegalArgumentException | NullPointerException e) {
|
||||||
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_DATA);
|
||||||
|
}
|
||||||
|
catch (Exception e) {
|
||||||
|
return "false";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@POST
|
||||||
|
@Path("/setcurrentserver")
|
||||||
|
@Operation(
|
||||||
|
summary = "Set current Ravencoin server",
|
||||||
|
description = "Set current Ravencoin server",
|
||||||
|
requestBody = @RequestBody(
|
||||||
|
required = true,
|
||||||
|
content = @Content(
|
||||||
|
mediaType = MediaType.APPLICATION_JSON,
|
||||||
|
schema = @Schema(
|
||||||
|
implementation = ServerInfo.class
|
||||||
|
)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
responses = {
|
||||||
|
@ApiResponse(
|
||||||
|
description = "connection info",
|
||||||
|
content = @Content(
|
||||||
|
mediaType = MediaType.APPLICATION_JSON,
|
||||||
|
schema = @Schema(
|
||||||
|
implementation = ServerConnectionInfo.class
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
)
|
||||||
|
@ApiErrors({ApiError.INVALID_DATA})
|
||||||
|
@SecurityRequirement(name = "apiKey")
|
||||||
|
public ServerConnectionInfo setCurrentServer(@HeaderParam(Security.API_KEY_HEADER) String apiKey, ServerInfo serverInfo) {
|
||||||
|
Security.checkApiCallAllowed(request);
|
||||||
|
|
||||||
|
if( serverInfo.getConnectionType() == null ||
|
||||||
|
serverInfo.getHostName() == null) throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_DATA);
|
||||||
|
try {
|
||||||
|
return CrossChainUtils.setCurrentServer( Ravencoin.getInstance(), serverInfo );
|
||||||
|
}
|
||||||
|
catch (IllegalArgumentException e) {
|
||||||
|
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_DATA);
|
||||||
|
}
|
||||||
|
catch (Exception e) {
|
||||||
|
return new ServerConnectionInfo(
|
||||||
|
serverInfo,
|
||||||
|
CrossChainUtils.CORE_API_CALL,
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
System.currentTimeMillis(),
|
||||||
|
CrossChainUtils.getNotes(e));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@GET
|
@GET
|
||||||
@Path("/feekb")
|
@Path("/feekb")
|
||||||
@Operation(
|
@Operation(
|
||||||
|
@ -9,10 +9,7 @@ import org.bitcoinj.script.ScriptBuilder;
|
|||||||
|
|
||||||
import org.qortal.crosschain.*;
|
import org.qortal.crosschain.*;
|
||||||
import org.qortal.data.at.ATData;
|
import org.qortal.data.at.ATData;
|
||||||
import org.qortal.data.crosschain.AtomicTransactionData;
|
import org.qortal.data.crosschain.*;
|
||||||
import org.qortal.data.crosschain.CrossChainTradeData;
|
|
||||||
import org.qortal.data.crosschain.TradeBotData;
|
|
||||||
import org.qortal.data.crosschain.TransactionSummary;
|
|
||||||
import org.qortal.repository.DataException;
|
import org.qortal.repository.DataException;
|
||||||
import org.qortal.repository.Repository;
|
import org.qortal.repository.Repository;
|
||||||
|
|
||||||
@ -22,6 +19,7 @@ import java.util.stream.Collectors;
|
|||||||
|
|
||||||
public class CrossChainUtils {
|
public class CrossChainUtils {
|
||||||
private static final Logger LOGGER = LogManager.getLogger(CrossChainUtils.class);
|
private static final Logger LOGGER = LogManager.getLogger(CrossChainUtils.class);
|
||||||
|
public static final String CORE_API_CALL = "Core API Call";
|
||||||
|
|
||||||
public static ServerConfigurationInfo buildServerConfigurationInfo(Bitcoiny blockchain) {
|
public static ServerConfigurationInfo buildServerConfigurationInfo(Bitcoiny blockchain) {
|
||||||
|
|
||||||
@ -184,6 +182,74 @@ public class CrossChainUtils {
|
|||||||
return summaries;
|
return summaries;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add Server
|
||||||
|
*
|
||||||
|
* Add foreign blockchain server to list of candidates.
|
||||||
|
*
|
||||||
|
* @param bitcoiny the foreign blockchain
|
||||||
|
* @param server the server
|
||||||
|
*
|
||||||
|
* @return true if the add was successful, otherwise false
|
||||||
|
*/
|
||||||
|
public static boolean addServer(Bitcoiny bitcoiny, ChainableServer server) {
|
||||||
|
|
||||||
|
return bitcoiny.getBlockchainProvider().addServer(server);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove Server
|
||||||
|
*
|
||||||
|
* Remove foreign blockchain server from list of candidates.
|
||||||
|
*
|
||||||
|
* @param bitcoiny the foreign blockchain
|
||||||
|
* @param server the server
|
||||||
|
*
|
||||||
|
* @return true if the removal was successful, otherwise false
|
||||||
|
*/
|
||||||
|
public static boolean removeServer(Bitcoiny bitcoiny, ChainableServer server){
|
||||||
|
|
||||||
|
return bitcoiny.getBlockchainProvider().removeServer(server);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set Current Server
|
||||||
|
*
|
||||||
|
* Set the server to use the intended foreign blockchain.
|
||||||
|
*
|
||||||
|
* @param bitcoiny the foreign blockchain
|
||||||
|
* @param serverInfo the server configuration information
|
||||||
|
*
|
||||||
|
* @return the server connection information
|
||||||
|
*/
|
||||||
|
public static ServerConnectionInfo setCurrentServer(Bitcoiny bitcoiny, ServerInfo serverInfo) throws ForeignBlockchainException {
|
||||||
|
|
||||||
|
final BitcoinyBlockchainProvider blockchainProvider = bitcoiny.getBlockchainProvider();
|
||||||
|
|
||||||
|
ChainableServer server = blockchainProvider.getServer(
|
||||||
|
serverInfo.getHostName(),
|
||||||
|
ChainableServer.ConnectionType.valueOf(serverInfo.getConnectionType()),
|
||||||
|
serverInfo.getPort()
|
||||||
|
);
|
||||||
|
|
||||||
|
ChainableServerConnection connection = blockchainProvider.setCurrentServer(server, CORE_API_CALL).get();
|
||||||
|
|
||||||
|
return new ServerConnectionInfo(
|
||||||
|
new ServerInfo(
|
||||||
|
0,
|
||||||
|
serverInfo.getHostName(),
|
||||||
|
serverInfo.getPort(),
|
||||||
|
serverInfo.getConnectionType(),
|
||||||
|
connection.isSuccess()
|
||||||
|
),
|
||||||
|
CORE_API_CALL,
|
||||||
|
true,
|
||||||
|
connection.isSuccess() ,
|
||||||
|
System.currentTimeMillis(),
|
||||||
|
connection.getNotes()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get P2Sh From Trade Bot
|
* Get P2Sh From Trade Bot
|
||||||
*
|
*
|
||||||
@ -423,4 +489,60 @@ public class CrossChainUtils {
|
|||||||
}
|
}
|
||||||
return totalInputOut;
|
return totalInputOut;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Notes
|
||||||
|
*
|
||||||
|
* Build notes from an exception thrown.
|
||||||
|
*
|
||||||
|
* @param e the exception
|
||||||
|
*
|
||||||
|
* @return the exception message or the exception class name
|
||||||
|
*/
|
||||||
|
public static String getNotes(Exception e) {
|
||||||
|
return e.getMessage() + " (" + e.getClass().getSimpleName() + ")";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build Server Connection History
|
||||||
|
*
|
||||||
|
* @param bitcoiny the foreign blockchain
|
||||||
|
*
|
||||||
|
* @return the history of connections from latest to first
|
||||||
|
*/
|
||||||
|
public static List<ServerConnectionInfo> buildServerConnectionHistory(Bitcoiny bitcoiny) {
|
||||||
|
|
||||||
|
return bitcoiny.getBlockchainProvider().getServerConnections().stream()
|
||||||
|
.sorted(Comparator.comparing(ChainableServerConnection::getCurrentTimeMillis).reversed())
|
||||||
|
.map(
|
||||||
|
connection -> new ServerConnectionInfo(
|
||||||
|
serverToServerInfo( connection.getServer()),
|
||||||
|
connection.getRequestedBy(),
|
||||||
|
connection.isOpen(),
|
||||||
|
connection.isSuccess(),
|
||||||
|
connection.getCurrentTimeMillis(),
|
||||||
|
connection.getNotes()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Server To Server Info
|
||||||
|
*
|
||||||
|
* Make a server info object from a server object.
|
||||||
|
*
|
||||||
|
* @param server the server
|
||||||
|
*
|
||||||
|
* @return the server info
|
||||||
|
*/
|
||||||
|
private static ServerInfo serverToServerInfo(ChainableServer server) {
|
||||||
|
|
||||||
|
return new ServerInfo(
|
||||||
|
0,
|
||||||
|
server.getHostName(),
|
||||||
|
server.getPort(),
|
||||||
|
server.getConnectionType().toString(),
|
||||||
|
false);
|
||||||
|
}
|
||||||
}
|
}
|
@ -3,12 +3,14 @@ package org.qortal.crosschain;
|
|||||||
import cash.z.wallet.sdk.rpc.CompactFormats.CompactBlock;
|
import cash.z.wallet.sdk.rpc.CompactFormats.CompactBlock;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
public abstract class BitcoinyBlockchainProvider {
|
public abstract class BitcoinyBlockchainProvider {
|
||||||
|
|
||||||
public static final boolean INCLUDE_UNCONFIRMED = true;
|
public static final boolean INCLUDE_UNCONFIRMED = true;
|
||||||
public static final boolean EXCLUDE_UNCONFIRMED = false;
|
public static final boolean EXCLUDE_UNCONFIRMED = false;
|
||||||
|
public static final String EMPTY = "";
|
||||||
|
|
||||||
/** Sets the blockchain using this provider instance */
|
/** Sets the blockchain using this provider instance */
|
||||||
public abstract void setBlockchain(Bitcoiny blockchain);
|
public abstract void setBlockchain(Bitcoiny blockchain);
|
||||||
@ -67,4 +69,62 @@ public abstract class BitcoinyBlockchainProvider {
|
|||||||
public abstract Set<ChainableServer> getUselessServers();
|
public abstract Set<ChainableServer> getUselessServers();
|
||||||
|
|
||||||
public abstract ChainableServer getCurrentServer();
|
public abstract ChainableServer getCurrentServer();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add Server
|
||||||
|
*
|
||||||
|
* Add server to list of candidate servers.
|
||||||
|
*
|
||||||
|
* @param server the server
|
||||||
|
*
|
||||||
|
* @return true if added, otherwise false
|
||||||
|
*/
|
||||||
|
public abstract boolean addServer( ChainableServer server );
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove Server
|
||||||
|
*
|
||||||
|
* Remove server from list of candidate servers.
|
||||||
|
*
|
||||||
|
* @param server the server
|
||||||
|
*
|
||||||
|
* @return true if removed, otherwise false
|
||||||
|
*/
|
||||||
|
public abstract boolean removeServer( ChainableServer server );
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set Current Server
|
||||||
|
*
|
||||||
|
* Set server to be used for this foreign blockchain.
|
||||||
|
*
|
||||||
|
* @param server the server
|
||||||
|
* @param requestedBy who requested this setting
|
||||||
|
*
|
||||||
|
* @return the connection that was made
|
||||||
|
*
|
||||||
|
* @throws ForeignBlockchainException
|
||||||
|
*/
|
||||||
|
public abstract Optional<ChainableServerConnection> setCurrentServer(ChainableServer server, String requestedBy) throws ForeignBlockchainException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Server Connections
|
||||||
|
*
|
||||||
|
* Get the server connections made to this foreign blockchain,
|
||||||
|
*
|
||||||
|
* @return the server connections
|
||||||
|
*/
|
||||||
|
public abstract List<ChainableServerConnection> getServerConnections();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Server
|
||||||
|
*
|
||||||
|
* Get a server for this foreign blockchain.
|
||||||
|
*
|
||||||
|
* @param hostName the host URL
|
||||||
|
* @param type the type of connection (TCP, SSL)
|
||||||
|
* @param port the port
|
||||||
|
*
|
||||||
|
* @return the server
|
||||||
|
*/
|
||||||
|
public abstract ChainableServer getServer(String hostName, ChainableServer.ConnectionType type, int port);
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,71 @@
|
|||||||
|
package org.qortal.crosschain;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
public class ChainableServerConnection {
|
||||||
|
|
||||||
|
private ChainableServer server;
|
||||||
|
private String requestedBy;
|
||||||
|
private boolean open;
|
||||||
|
private boolean success;
|
||||||
|
private long currentTimeMillis;
|
||||||
|
private String notes;
|
||||||
|
|
||||||
|
public ChainableServerConnection(ChainableServer server, String requestedBy, boolean open, boolean success, long currentTimeMillis, String notes) {
|
||||||
|
this.server = server;
|
||||||
|
this.requestedBy = requestedBy;
|
||||||
|
this.open = open;
|
||||||
|
this.success = success;
|
||||||
|
this.currentTimeMillis = currentTimeMillis;
|
||||||
|
this.notes = notes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ChainableServer getServer() {
|
||||||
|
return server;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getRequestedBy() {
|
||||||
|
return requestedBy;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isOpen() {
|
||||||
|
return open;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isSuccess() {
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getCurrentTimeMillis() {
|
||||||
|
return currentTimeMillis;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getNotes() {
|
||||||
|
return notes;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
|
ChainableServerConnection that = (ChainableServerConnection) o;
|
||||||
|
return currentTimeMillis == that.currentTimeMillis && Objects.equals(server, that.server);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(server, currentTimeMillis);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "ChainableServerConnection{" +
|
||||||
|
"server=" + server +
|
||||||
|
", requestedBy='" + requestedBy + '\'' +
|
||||||
|
", open=" + open +
|
||||||
|
", success=" + success +
|
||||||
|
", currentTimeMillis=" + currentTimeMillis +
|
||||||
|
", notes='" + notes + '\'' +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,45 @@
|
|||||||
|
package org.qortal.crosschain;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class ChainableServerConnectionRecorder {
|
||||||
|
|
||||||
|
private List<ChainableServerConnection> connections;
|
||||||
|
private int limit;
|
||||||
|
|
||||||
|
public ChainableServerConnectionRecorder(int limit) {
|
||||||
|
this.connections = new ArrayList<>(limit);
|
||||||
|
this.limit = limit;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ChainableServerConnection recordConnection(
|
||||||
|
ChainableServer server, String requestedBy, boolean open, boolean success, String notes) {
|
||||||
|
|
||||||
|
ChainableServerConnection connection
|
||||||
|
= new ChainableServerConnection(server, requestedBy, open, success, System.currentTimeMillis(), notes);
|
||||||
|
|
||||||
|
connections.add(connection);
|
||||||
|
|
||||||
|
if( connections.size() > limit) {
|
||||||
|
ChainableServerConnection firstConnection
|
||||||
|
= connections.stream().sorted(Comparator.comparing(ChainableServerConnection::getCurrentTimeMillis))
|
||||||
|
.findFirst().get();
|
||||||
|
connections.remove(firstConnection);
|
||||||
|
}
|
||||||
|
return connection;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getLimit() {
|
||||||
|
return limit;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLimit(int limit) {
|
||||||
|
this.limit = limit;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<ChainableServerConnection> getConnections() {
|
||||||
|
return this.connections;
|
||||||
|
}
|
||||||
|
}
|
@ -8,6 +8,7 @@ import org.apache.logging.log4j.Logger;
|
|||||||
import org.json.simple.JSONArray;
|
import org.json.simple.JSONArray;
|
||||||
import org.json.simple.JSONObject;
|
import org.json.simple.JSONObject;
|
||||||
import org.json.simple.JSONValue;
|
import org.json.simple.JSONValue;
|
||||||
|
import org.qortal.api.resource.CrossChainUtils;
|
||||||
import org.qortal.crypto.Crypto;
|
import org.qortal.crypto.Crypto;
|
||||||
import org.qortal.crypto.TrustlessSSLSocketFactory;
|
import org.qortal.crypto.TrustlessSSLSocketFactory;
|
||||||
import org.qortal.utils.BitTwiddling;
|
import org.qortal.utils.BitTwiddling;
|
||||||
@ -26,6 +27,7 @@ import java.util.regex.Pattern;
|
|||||||
/** ElectrumX network support for querying Bitcoiny-related info like block headers, transaction outputs, etc. */
|
/** ElectrumX network support for querying Bitcoiny-related info like block headers, transaction outputs, etc. */
|
||||||
public class ElectrumX extends BitcoinyBlockchainProvider {
|
public class ElectrumX extends BitcoinyBlockchainProvider {
|
||||||
|
|
||||||
|
public static final String NULL_RESPONSE_FROM_ELECTRUM_X_SERVER = "Null response from ElectrumX server";
|
||||||
private static final Logger LOGGER = LogManager.getLogger(ElectrumX.class);
|
private static final Logger LOGGER = LogManager.getLogger(ElectrumX.class);
|
||||||
private static final Random RANDOM = new Random();
|
private static final Random RANDOM = new Random();
|
||||||
|
|
||||||
@ -44,6 +46,10 @@ public class ElectrumX extends BitcoinyBlockchainProvider {
|
|||||||
|
|
||||||
private static final int RESPONSE_TIME_READINGS = 5;
|
private static final int RESPONSE_TIME_READINGS = 5;
|
||||||
private static final long MAX_AVG_RESPONSE_TIME = 2000L; // ms
|
private static final long MAX_AVG_RESPONSE_TIME = 2000L; // ms
|
||||||
|
public static final String MINIMUM_VERSION_ERROR = "MINIMUM VERSION ERROR";
|
||||||
|
public static final String EXPECTED_GENESIS_ERROR = "EXPECTED GENESIS ERROR";
|
||||||
|
|
||||||
|
private ChainableServerConnectionRecorder recorder = new ChainableServerConnectionRecorder(100);
|
||||||
|
|
||||||
public static class Server implements ChainableServer {
|
public static class Server implements ChainableServer {
|
||||||
String hostname;
|
String hostname;
|
||||||
@ -421,7 +427,7 @@ public class ElectrumX extends BitcoinyBlockchainProvider {
|
|||||||
Server uselessServer = (Server) e.getServer();
|
Server uselessServer = (Server) e.getServer();
|
||||||
LOGGER.trace(() -> String.format("Server %s doesn't support verbose transactions - barring use of that server", uselessServer));
|
LOGGER.trace(() -> String.format("Server %s doesn't support verbose transactions - barring use of that server", uselessServer));
|
||||||
this.uselessServers.add(uselessServer);
|
this.uselessServers.add(uselessServer);
|
||||||
this.closeServer(uselessServer);
|
this.closeServer(uselessServer, this.getClass().getSimpleName(), CrossChainUtils.getNotes(e));
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -495,12 +501,13 @@ public class ElectrumX extends BitcoinyBlockchainProvider {
|
|||||||
// Update: it turns out that they were just using a different key - "address" instead of "addresses"
|
// Update: it turns out that they were just using a different key - "address" instead of "addresses"
|
||||||
// The code below can remain in place, just in case a peer returns a missing address in the future
|
// The code below can remain in place, just in case a peer returns a missing address in the future
|
||||||
if (addresses == null || addresses.isEmpty()) {
|
if (addresses == null || addresses.isEmpty()) {
|
||||||
|
final String message = String.format("No output addresses returned for transaction %s", txHash);
|
||||||
if (this.currentServer != null) {
|
if (this.currentServer != null) {
|
||||||
this.uselessServers.add(this.currentServer);
|
this.uselessServers.add(this.currentServer);
|
||||||
this.closeServer(this.currentServer);
|
this.closeServer(this.currentServer, this.getClass().getSimpleName(), message);
|
||||||
}
|
}
|
||||||
LOGGER.info("No output addresses returned for transaction {}", txHash);
|
LOGGER.info("No output addresses returned for transaction {}", txHash);
|
||||||
throw new ForeignBlockchainException(String.format("No output addresses returned for transaction %s", txHash));
|
throw new ForeignBlockchainException(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
outputs.add(new BitcoinyTransaction.Output(scriptPubKey, value, addresses));
|
outputs.add(new BitcoinyTransaction.Output(scriptPubKey, value, addresses));
|
||||||
@ -654,8 +661,9 @@ public class ElectrumX extends BitcoinyBlockchainProvider {
|
|||||||
if (!this.remainingServers.isEmpty()) {
|
if (!this.remainingServers.isEmpty()) {
|
||||||
long averageResponseTime = this.currentServer.averageResponseTime();
|
long averageResponseTime = this.currentServer.averageResponseTime();
|
||||||
if (averageResponseTime > MAX_AVG_RESPONSE_TIME) {
|
if (averageResponseTime > MAX_AVG_RESPONSE_TIME) {
|
||||||
LOGGER.info("Slow average response time {}ms from {} - trying another server...", averageResponseTime, this.currentServer.getHostName());
|
String message = String.format("Slow average response time %dms from %s - trying another server...", averageResponseTime, this.currentServer.getHostName());
|
||||||
this.closeServer();
|
LOGGER.info(message);
|
||||||
|
this.closeServer(this.getClass().getSimpleName(), message);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -663,8 +671,9 @@ public class ElectrumX extends BitcoinyBlockchainProvider {
|
|||||||
if (response != null)
|
if (response != null)
|
||||||
return response;
|
return response;
|
||||||
|
|
||||||
|
LOGGER.info(NULL_RESPONSE_FROM_ELECTRUM_X_SERVER);
|
||||||
// Didn't work, try another server...
|
// Didn't work, try another server...
|
||||||
this.closeServer();
|
this.closeServer(this.getClass().getSimpleName(), NULL_RESPONSE_FROM_ELECTRUM_X_SERVER);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Failed to perform RPC - maybe lack of servers?
|
// Failed to perform RPC - maybe lack of servers?
|
||||||
@ -680,56 +689,61 @@ public class ElectrumX extends BitcoinyBlockchainProvider {
|
|||||||
|
|
||||||
while (!this.remainingServers.isEmpty()) {
|
while (!this.remainingServers.isEmpty()) {
|
||||||
ChainableServer server = this.remainingServers.remove(RANDOM.nextInt(this.remainingServers.size()));
|
ChainableServer server = this.remainingServers.remove(RANDOM.nextInt(this.remainingServers.size()));
|
||||||
LOGGER.trace(() -> String.format("Connecting to %s", server));
|
Optional<ChainableServerConnection> chainableServerConnection = makeConnection(server, this.getClass().getSimpleName());
|
||||||
|
if(chainableServerConnection.isPresent() && chainableServerConnection.get().isSuccess() ) return true;
|
||||||
try {
|
|
||||||
SocketAddress endpoint = new InetSocketAddress(server.getHostName(), server.getPort());
|
|
||||||
int timeout = 5000; // ms
|
|
||||||
|
|
||||||
this.socket = new Socket();
|
|
||||||
this.socket.connect(endpoint, timeout);
|
|
||||||
this.socket.setTcpNoDelay(true);
|
|
||||||
|
|
||||||
if (server.getConnectionType() == Server.ConnectionType.SSL) {
|
|
||||||
SSLSocketFactory factory = TrustlessSSLSocketFactory.getSocketFactory();
|
|
||||||
this.socket = factory.createSocket(this.socket, server.getHostName(), server.getPort(), true);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.scanner = new Scanner(this.socket.getInputStream());
|
|
||||||
this.scanner.useDelimiter("\n");
|
|
||||||
|
|
||||||
// All connections need to start with a version negotiation
|
|
||||||
this.connectedRpc("server.version");
|
|
||||||
|
|
||||||
// Check connection is suitable by asking for server features, including genesis block hash
|
|
||||||
JSONObject featuresJson = (JSONObject) this.connectedRpc("server.features");
|
|
||||||
|
|
||||||
if (featuresJson == null || Double.valueOf((String) featuresJson.get("protocol_min")) < MIN_PROTOCOL_VERSION)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (this.expectedGenesisHash != null && !((String) featuresJson.get("genesis_hash")).equals(this.expectedGenesisHash))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
// Ask for more servers
|
|
||||||
Set<Server> moreServers = serverPeersSubscribe();
|
|
||||||
// Discard duplicate servers we already know
|
|
||||||
moreServers.removeAll(this.servers);
|
|
||||||
// Add to both lists
|
|
||||||
this.remainingServers.addAll(moreServers);
|
|
||||||
this.servers.addAll(moreServers);
|
|
||||||
|
|
||||||
LOGGER.debug(() -> String.format("Connected to %s", server));
|
|
||||||
this.currentServer = server;
|
|
||||||
return true;
|
|
||||||
} catch (IOException | ForeignBlockchainException | ClassCastException | NullPointerException e) {
|
|
||||||
// Didn't work, try another server...
|
|
||||||
closeServer();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Optional<ChainableServerConnection> makeConnection(ChainableServer server, String requestedBy) {
|
||||||
|
LOGGER.info(() -> String.format("Connecting to %s", server));
|
||||||
|
|
||||||
|
try {
|
||||||
|
SocketAddress endpoint = new InetSocketAddress(server.getHostName(), server.getPort());
|
||||||
|
int timeout = 5000; // ms
|
||||||
|
|
||||||
|
this.socket = new Socket();
|
||||||
|
this.socket.connect(endpoint, timeout);
|
||||||
|
this.socket.setTcpNoDelay(true);
|
||||||
|
|
||||||
|
if (server.getConnectionType() == Server.ConnectionType.SSL) {
|
||||||
|
SSLSocketFactory factory = TrustlessSSLSocketFactory.getSocketFactory();
|
||||||
|
this.socket = factory.createSocket(this.socket, server.getHostName(), server.getPort(), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.scanner = new Scanner(this.socket.getInputStream());
|
||||||
|
this.scanner.useDelimiter("\n");
|
||||||
|
|
||||||
|
// All connections need to start with a version negotiation
|
||||||
|
this.connectedRpc("server.version");
|
||||||
|
|
||||||
|
// Check connection is suitable by asking for server features, including genesis block hash
|
||||||
|
JSONObject featuresJson = (JSONObject) this.connectedRpc("server.features");
|
||||||
|
|
||||||
|
if (featuresJson == null || Double.valueOf((String) featuresJson.get("protocol_min")) < MIN_PROTOCOL_VERSION)
|
||||||
|
return Optional.of( recorder.recordConnection(server, requestedBy, true, false, MINIMUM_VERSION_ERROR) );
|
||||||
|
|
||||||
|
if (this.expectedGenesisHash != null && !((String) featuresJson.get("genesis_hash")).equals(this.expectedGenesisHash))
|
||||||
|
return Optional.of( recorder.recordConnection(server, requestedBy, true, false, EXPECTED_GENESIS_ERROR) );
|
||||||
|
|
||||||
|
// Ask for more servers
|
||||||
|
Set<Server> moreServers = serverPeersSubscribe();
|
||||||
|
// Discard duplicate servers we already know
|
||||||
|
moreServers.removeAll(this.servers);
|
||||||
|
// Add to both lists
|
||||||
|
this.remainingServers.addAll(moreServers);
|
||||||
|
this.servers.addAll(moreServers);
|
||||||
|
|
||||||
|
LOGGER.info(() -> String.format("Connected to %s", server));
|
||||||
|
this.currentServer = server;
|
||||||
|
return Optional.of( this.recorder.recordConnection( server, requestedBy, true, true, EMPTY) );
|
||||||
|
} catch (IOException | ForeignBlockchainException | ClassCastException | NullPointerException e) {
|
||||||
|
// Didn't work, try another server...
|
||||||
|
return Optional.of( this.recorder.recordConnection( server, requestedBy, true, false, CrossChainUtils.getNotes(e)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Perform RPC using currently connected server.
|
* Perform RPC using currently connected server.
|
||||||
* <p>
|
* <p>
|
||||||
@ -846,12 +860,19 @@ public class ElectrumX extends BitcoinyBlockchainProvider {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Closes connection to <tt>server</tt> if it is currently connected server.
|
* Closes connection to <tt>server</tt> if it is currently connected server.
|
||||||
|
*
|
||||||
* @param server
|
* @param server
|
||||||
|
* @param notes
|
||||||
*/
|
*/
|
||||||
private void closeServer(ChainableServer server) {
|
private Optional<ChainableServerConnection> closeServer(ChainableServer server, String requestedBy, String notes) {
|
||||||
|
|
||||||
|
ChainableServerConnection chainableServerConnection;
|
||||||
|
|
||||||
synchronized (this.serverLock) {
|
synchronized (this.serverLock) {
|
||||||
if (this.currentServer == null || !this.currentServer.equals(server))
|
if (this.currentServer == null || !this.currentServer.equals(server))
|
||||||
return;
|
return Optional.empty();
|
||||||
|
|
||||||
|
chainableServerConnection = this.recorder.recordConnection(server, requestedBy, false, true, notes);
|
||||||
|
|
||||||
if (this.socket != null && !this.socket.isClosed())
|
if (this.socket != null && !this.socket.isClosed())
|
||||||
try {
|
try {
|
||||||
@ -864,12 +885,14 @@ public class ElectrumX extends BitcoinyBlockchainProvider {
|
|||||||
this.scanner = null;
|
this.scanner = null;
|
||||||
this.currentServer = null;
|
this.currentServer = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return Optional.of( chainableServerConnection );
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Closes connection to currently connected server (if any). */
|
/** Closes connection to currently connected server (if any). */
|
||||||
private void closeServer() {
|
private Optional<ChainableServerConnection> closeServer(String requestedBy, String notes) {
|
||||||
synchronized (this.serverLock) {
|
synchronized (this.serverLock) {
|
||||||
this.closeServer(this.currentServer);
|
return this.closeServer(this.currentServer, requestedBy, notes);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -893,4 +916,32 @@ public class ElectrumX extends BitcoinyBlockchainProvider {
|
|||||||
public ChainableServer getCurrentServer() {
|
public ChainableServer getCurrentServer() {
|
||||||
return currentServer;
|
return currentServer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean addServer(ChainableServer server) {
|
||||||
|
return this.servers.add(server);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean removeServer(ChainableServer server) {
|
||||||
|
boolean removedServer = this.servers.remove(server);
|
||||||
|
boolean removedRemaining = this.remainingServers.remove(server);
|
||||||
|
|
||||||
|
return removedServer || removedRemaining;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Optional<ChainableServerConnection> setCurrentServer(ChainableServer server, String requestedBy) {
|
||||||
|
return this.makeConnection(server, requestedBy);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<ChainableServerConnection> getServerConnections() {
|
||||||
|
return this.recorder.getConnections();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChainableServer getServer(String hostName, ChainableServer.ConnectionType type, int port) {
|
||||||
|
return new ElectrumX.Server(hostName, type, port);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,6 +14,7 @@ import org.json.simple.JSONArray;
|
|||||||
import org.json.simple.JSONObject;
|
import org.json.simple.JSONObject;
|
||||||
import org.json.simple.parser.JSONParser;
|
import org.json.simple.parser.JSONParser;
|
||||||
import org.json.simple.parser.ParseException;
|
import org.json.simple.parser.ParseException;
|
||||||
|
import org.qortal.api.resource.CrossChainUtils;
|
||||||
import org.qortal.settings.Settings;
|
import org.qortal.settings.Settings;
|
||||||
import org.qortal.transform.TransformationException;
|
import org.qortal.transform.TransformationException;
|
||||||
|
|
||||||
@ -127,6 +128,8 @@ public class PirateLightClient extends BitcoinyBlockchainProvider {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
private ChainableServerConnectionRecorder recorder = new ChainableServerConnectionRecorder(100);
|
||||||
|
|
||||||
// Constructors
|
// Constructors
|
||||||
|
|
||||||
public PirateLightClient(String netId, String genesisHash, Collection<Server> initialServerList, Map<Server.ConnectionType, Integer> defaultPorts) {
|
public PirateLightClient(String netId, String genesisHash, Collection<Server> initialServerList, Map<Server.ConnectionType, Integer> defaultPorts) {
|
||||||
@ -443,12 +446,13 @@ public class PirateLightClient extends BitcoinyBlockchainProvider {
|
|||||||
// Update: it turns out that they were just using a different key - "address" instead of "addresses"
|
// Update: it turns out that they were just using a different key - "address" instead of "addresses"
|
||||||
// The code below can remain in place, just in case a peer returns a missing address in the future
|
// The code below can remain in place, just in case a peer returns a missing address in the future
|
||||||
if (addresses == null || addresses.isEmpty()) {
|
if (addresses == null || addresses.isEmpty()) {
|
||||||
|
final String message = String.format("No output addresses returned for transaction %s", txHash);
|
||||||
if (this.currentServer != null) {
|
if (this.currentServer != null) {
|
||||||
this.uselessServers.add(this.currentServer);
|
this.uselessServers.add(this.currentServer);
|
||||||
this.closeServer(this.currentServer);
|
this.closeServer(this.currentServer, message, this.getClass().getSimpleName());
|
||||||
}
|
}
|
||||||
LOGGER.info("No output addresses returned for transaction {}", txHash);
|
LOGGER.info(message);
|
||||||
throw new ForeignBlockchainException(String.format("No output addresses returned for transaction %s", txHash));
|
throw new ForeignBlockchainException(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
outputs.add(new BitcoinyTransaction.Output(scriptPubKey, value, addresses));
|
outputs.add(new BitcoinyTransaction.Output(scriptPubKey, value, addresses));
|
||||||
@ -557,6 +561,42 @@ public class PirateLightClient extends BitcoinyBlockchainProvider {
|
|||||||
@Override
|
@Override
|
||||||
public ChainableServer getCurrentServer() { return this.currentServer; }
|
public ChainableServer getCurrentServer() { return this.currentServer; }
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean addServer(ChainableServer server) {
|
||||||
|
return this.servers.add(server);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean removeServer(ChainableServer server) {
|
||||||
|
boolean removedServer = this.servers.remove(server);
|
||||||
|
boolean removedRemaining = this.remainingServers.remove(server);
|
||||||
|
|
||||||
|
return removedServer || removedRemaining;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Optional<ChainableServerConnection> setCurrentServer(ChainableServer server, String requestedBy) throws ForeignBlockchainException {
|
||||||
|
|
||||||
|
closeServer( requestedBy, "Connecting to different server by request." );
|
||||||
|
Optional<ChainableServerConnection> connection = makeConnection(server, requestedBy);
|
||||||
|
|
||||||
|
if( !connection.isPresent() || !connection.get().isSuccess() ) {
|
||||||
|
haveConnection();
|
||||||
|
}
|
||||||
|
|
||||||
|
return connection;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<ChainableServerConnection> getServerConnections() {
|
||||||
|
return this.recorder.getConnections();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChainableServer getServer(String hostName, ChainableServer.ConnectionType type, int port) {
|
||||||
|
return new PirateLightClient.Server(hostName, type, port);
|
||||||
|
}
|
||||||
|
|
||||||
// Class-private utility methods
|
// Class-private utility methods
|
||||||
|
|
||||||
|
|
||||||
@ -576,8 +616,9 @@ public class PirateLightClient extends BitcoinyBlockchainProvider {
|
|||||||
if (!this.remainingServers.isEmpty()) {
|
if (!this.remainingServers.isEmpty()) {
|
||||||
long averageResponseTime = this.currentServer.averageResponseTime();
|
long averageResponseTime = this.currentServer.averageResponseTime();
|
||||||
if (averageResponseTime > MAX_AVG_RESPONSE_TIME) {
|
if (averageResponseTime > MAX_AVG_RESPONSE_TIME) {
|
||||||
LOGGER.info("Slow average response time {}ms from {} - trying another server...", averageResponseTime, this.currentServer.getHostName());
|
String message = String.format("Slow average response time %dms from %s - trying another server...", averageResponseTime, this.currentServer.getHostName());
|
||||||
this.closeServer();
|
LOGGER.info(message);
|
||||||
|
this.closeServer(this.getClass().getSimpleName(), message);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -601,18 +642,27 @@ public class PirateLightClient extends BitcoinyBlockchainProvider {
|
|||||||
|
|
||||||
while (!this.remainingServers.isEmpty()) {
|
while (!this.remainingServers.isEmpty()) {
|
||||||
ChainableServer server = this.remainingServers.remove(RANDOM.nextInt(this.remainingServers.size()));
|
ChainableServer server = this.remainingServers.remove(RANDOM.nextInt(this.remainingServers.size()));
|
||||||
LOGGER.trace(() -> String.format("Connecting to %s", server));
|
|
||||||
|
|
||||||
try {
|
Optional<ChainableServerConnection> chainableServerConnection = makeConnection(server, this.getClass().getSimpleName());
|
||||||
this.channel = ManagedChannelBuilder.forAddress(server.getHostName(), server.getPort()).build();
|
if( chainableServerConnection.isPresent() && chainableServerConnection.get().isSuccess() ) return true;
|
||||||
|
}
|
||||||
|
|
||||||
CompactTxStreamerGrpc.CompactTxStreamerBlockingStub stub = CompactTxStreamerGrpc.newBlockingStub(this.channel);
|
return false;
|
||||||
LightdInfo lightdInfo = stub.getLightdInfo(Empty.newBuilder().build());
|
}
|
||||||
|
|
||||||
if (lightdInfo == null || lightdInfo.getBlockHeight() <= 0)
|
private Optional<ChainableServerConnection> makeConnection(ChainableServer server, String requestedBy) {
|
||||||
continue;
|
LOGGER.info(() -> String.format("Connecting to %s", server));
|
||||||
|
|
||||||
// TODO: find a way to verify that the server is using the expected chain
|
try {
|
||||||
|
this.channel = ManagedChannelBuilder.forAddress(server.getHostName(), server.getPort()).build();
|
||||||
|
|
||||||
|
CompactTxStreamerGrpc.CompactTxStreamerBlockingStub stub = CompactTxStreamerGrpc.newBlockingStub(this.channel);
|
||||||
|
LightdInfo lightdInfo = stub.getLightdInfo(Empty.newBuilder().build());
|
||||||
|
|
||||||
|
if (lightdInfo == null || lightdInfo.getBlockHeight() <= 0)
|
||||||
|
return Optional.of( this.recorder.recordConnection(server, requestedBy,true, false, "lightd info issues") );
|
||||||
|
|
||||||
|
// TODO: find a way to verify that the server is using the expected chain
|
||||||
|
|
||||||
// if (featuresJson == null || Double.valueOf((String) featuresJson.get("protocol_min")) < MIN_PROTOCOL_VERSION)
|
// if (featuresJson == null || Double.valueOf((String) featuresJson.get("protocol_min")) < MIN_PROTOCOL_VERSION)
|
||||||
// continue;
|
// continue;
|
||||||
@ -620,28 +670,31 @@ public class PirateLightClient extends BitcoinyBlockchainProvider {
|
|||||||
// if (this.expectedGenesisHash != null && !((String) featuresJson.get("genesis_hash")).equals(this.expectedGenesisHash))
|
// if (this.expectedGenesisHash != null && !((String) featuresJson.get("genesis_hash")).equals(this.expectedGenesisHash))
|
||||||
// continue;
|
// continue;
|
||||||
|
|
||||||
LOGGER.debug(() -> String.format("Connected to %s", server));
|
LOGGER.info(() -> String.format("Connected to %s", server));
|
||||||
this.currentServer = server;
|
this.currentServer = server;
|
||||||
return true;
|
return Optional.of( this.recorder.recordConnection(server, requestedBy,true, true, EMPTY) );
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
// Didn't work, try another server...
|
// Didn't work, try another server...
|
||||||
closeServer();
|
return Optional.of( this.recorder.recordConnection( server, requestedBy, true, false, CrossChainUtils.getNotes(e)));
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Closes connection to <tt>server</tt> if it is currently connected server.
|
* Closes connection to <tt>server</tt> if it is currently connected server.
|
||||||
|
*
|
||||||
* @param server
|
* @param server
|
||||||
|
* @param requestedBy
|
||||||
*/
|
*/
|
||||||
private void closeServer(ChainableServer server) {
|
private Optional<ChainableServerConnection> closeServer(ChainableServer server, String notes, String requestedBy) {
|
||||||
|
|
||||||
|
final ChainableServerConnection connection;
|
||||||
|
|
||||||
synchronized (this.serverLock) {
|
synchronized (this.serverLock) {
|
||||||
if (this.currentServer == null || !this.currentServer.equals(server) || this.channel == null) {
|
if (this.currentServer == null || !this.currentServer.equals(server) || this.channel == null) {
|
||||||
return;
|
return Optional.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
connection = this.recorder.recordConnection(server, requestedBy, false, true, notes);
|
||||||
|
|
||||||
// Close the gRPC managed-channel if not shut down already.
|
// Close the gRPC managed-channel if not shut down already.
|
||||||
if (!this.channel.isShutdown()) {
|
if (!this.channel.isShutdown()) {
|
||||||
try {
|
try {
|
||||||
@ -669,12 +722,14 @@ public class PirateLightClient extends BitcoinyBlockchainProvider {
|
|||||||
this.channel = null;
|
this.channel = null;
|
||||||
this.currentServer = null;
|
this.currentServer = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return Optional.of( connection );
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Closes connection to currently connected server (if any). */
|
/** Closes connection to currently connected server (if any). */
|
||||||
private void closeServer() {
|
private Optional<ChainableServerConnection> closeServer(String requestedBy, String notes) {
|
||||||
synchronized (this.serverLock) {
|
synchronized (this.serverLock) {
|
||||||
this.closeServer(this.currentServer);
|
return this.closeServer(this.currentServer, notes, requestedBy);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,82 @@
|
|||||||
|
package org.qortal.crosschain;
|
||||||
|
|
||||||
|
import javax.xml.bind.annotation.XmlAccessType;
|
||||||
|
import javax.xml.bind.annotation.XmlAccessorType;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
@XmlAccessorType(XmlAccessType.FIELD)
|
||||||
|
public class ServerConnectionInfo {
|
||||||
|
|
||||||
|
private ServerInfo serverInfo;
|
||||||
|
|
||||||
|
private String requestedBy;
|
||||||
|
|
||||||
|
private boolean open;
|
||||||
|
|
||||||
|
private boolean success;
|
||||||
|
|
||||||
|
private long timeInMillis;
|
||||||
|
|
||||||
|
private String notes;
|
||||||
|
|
||||||
|
public ServerConnectionInfo() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public ServerConnectionInfo(ServerInfo serverInfo, String requestedBy, boolean open, boolean success, long timeInMillis, String notes) {
|
||||||
|
this.serverInfo = serverInfo;
|
||||||
|
this.requestedBy = requestedBy;
|
||||||
|
this.open = open;
|
||||||
|
this.success = success;
|
||||||
|
this.timeInMillis = timeInMillis;
|
||||||
|
this.notes = notes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ServerInfo getServerInfo() {
|
||||||
|
return serverInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getRequestedBy() {
|
||||||
|
return requestedBy;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isOpen() {
|
||||||
|
return open;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isSuccess() {
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getTimeInMillis() {
|
||||||
|
return timeInMillis;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getNotes() {
|
||||||
|
return notes;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
|
ServerConnectionInfo that = (ServerConnectionInfo) o;
|
||||||
|
return timeInMillis == that.timeInMillis && Objects.equals(serverInfo, that.serverInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(serverInfo, timeInMillis);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "ServerConnectionInfo{" +
|
||||||
|
"serverInfo=" + serverInfo +
|
||||||
|
", requestedBy='" + requestedBy + '\'' +
|
||||||
|
", open=" + open +
|
||||||
|
", success=" + success +
|
||||||
|
", timeInMillis=" + timeInMillis +
|
||||||
|
", notes='" + notes + '\'' +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user