84: Validate 'jup' and the configured signer when constructing SolanaWalletImpl
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
package com.r35157.cryptowallet.solana.impl.ref;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.r35157.cryptowallet.solana.SolanaWallet;
|
||||
import com.r35157.cryptowallet.solana.MissingRecipientTokenAccountPolicy;
|
||||
@@ -28,9 +29,202 @@ public class SolanaWalletImpl implements SolanaWallet {
|
||||
String signerKeyName,
|
||||
SolanaBlockChain solanaBlockChain
|
||||
) {
|
||||
if (address == null || address.isBlank()) {
|
||||
throw new IllegalArgumentException(
|
||||
"Solana wallet address must not be blank"
|
||||
);
|
||||
}
|
||||
if (signerKeyName == null || signerKeyName.isBlank()) {
|
||||
throw new IllegalArgumentException(
|
||||
"Solana wallet signer key name must not be blank"
|
||||
);
|
||||
}
|
||||
|
||||
this.address = address;
|
||||
this.signerKeyName = signerKeyName;
|
||||
this.solanaBlockChain = solanaBlockChain;
|
||||
this.solanaBlockChain = Objects.requireNonNull(
|
||||
solanaBlockChain,
|
||||
"Solana blockchain must not be null"
|
||||
);
|
||||
|
||||
validateJupConfiguration();
|
||||
}
|
||||
|
||||
private void validateJupConfiguration() {
|
||||
JupCommandResult versionResult = runJupCommand(
|
||||
"jup --version",
|
||||
"--version"
|
||||
);
|
||||
String actualVersion = versionResult.standardOutput().trim();
|
||||
|
||||
if (!actualVersion.matches("\\d+\\.\\d+\\.\\d+")) {
|
||||
throw new IllegalStateException(
|
||||
"Invalid jup version while validating Solana wallet "
|
||||
+ "signer '"
|
||||
+ signerKeyName
|
||||
+ "': expected version "
|
||||
+ EXPECTED_JUP_VERSION
|
||||
+ " but jup --version returned '"
|
||||
+ actualVersion
|
||||
+ "'"
|
||||
);
|
||||
}
|
||||
|
||||
// An exact match is intentional: the CLI arguments and JSON response
|
||||
// structure used by this class are verified only for this version.
|
||||
if (!EXPECTED_JUP_VERSION.equals(actualVersion)) {
|
||||
throw new IllegalStateException(
|
||||
"Unsupported jup version while validating Solana wallet "
|
||||
+ "signer '"
|
||||
+ signerKeyName
|
||||
+ "': expected "
|
||||
+ EXPECTED_JUP_VERSION
|
||||
+ " but found "
|
||||
+ actualVersion
|
||||
);
|
||||
}
|
||||
|
||||
JupCommandResult keysResult = runJupCommand(
|
||||
"jup -f json keys list",
|
||||
"-f",
|
||||
"json",
|
||||
"keys",
|
||||
"list"
|
||||
);
|
||||
JsonNode keys;
|
||||
|
||||
try {
|
||||
keys = OBJECT_MAPPER.readTree(keysResult.standardOutput());
|
||||
} catch (JsonProcessingException e) {
|
||||
throw new IllegalStateException(
|
||||
"Unable to decode the jup key list while validating "
|
||||
+ "Solana wallet signer '"
|
||||
+ signerKeyName
|
||||
+ "'",
|
||||
e
|
||||
);
|
||||
}
|
||||
|
||||
if (keys == null || !keys.isArray()) {
|
||||
throw new IllegalStateException(
|
||||
"jup returned an invalid key list while validating "
|
||||
+ "Solana wallet signer '"
|
||||
+ signerKeyName
|
||||
+ "': expected a JSON array"
|
||||
);
|
||||
}
|
||||
|
||||
JsonNode signerKey = null;
|
||||
for (JsonNode key : keys) {
|
||||
if (signerKeyName.equals(key.path("name").textValue())) {
|
||||
signerKey = key;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (signerKey == null) {
|
||||
throw new IllegalStateException(
|
||||
"Solana wallet signer key '"
|
||||
+ signerKeyName
|
||||
+ "' is not available to jup"
|
||||
);
|
||||
}
|
||||
|
||||
ΩSolanaWalletIdΩ signerAddress = signerKey
|
||||
.path("address")
|
||||
.textValue();
|
||||
if (signerAddress == null || signerAddress.isBlank()) {
|
||||
throw new IllegalStateException(
|
||||
"jup signer key '"
|
||||
+ signerKeyName
|
||||
+ "' does not contain a valid address; expected "
|
||||
+ address
|
||||
);
|
||||
}
|
||||
if (!address.equals(signerAddress)) {
|
||||
throw new IllegalStateException(
|
||||
"jup signer key '"
|
||||
+ signerKeyName
|
||||
+ "' has an unexpected address: expected "
|
||||
+ address
|
||||
+ " but found "
|
||||
+ signerAddress
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private JupCommandResult runJupCommand(
|
||||
String commandDescription,
|
||||
String... arguments
|
||||
) {
|
||||
String[] command = new String[arguments.length + 1];
|
||||
command[0] = "jup";
|
||||
System.arraycopy(arguments, 0, command, 1, arguments.length);
|
||||
|
||||
Process process;
|
||||
try {
|
||||
process = new ProcessBuilder(command).start();
|
||||
} catch (IOException e) {
|
||||
throw new IllegalStateException(
|
||||
"Unable to start required jup CLI command '"
|
||||
+ commandDescription
|
||||
+ "' while validating Solana wallet signer '"
|
||||
+ signerKeyName
|
||||
+ "'. Ensure jup version "
|
||||
+ EXPECTED_JUP_VERSION
|
||||
+ " is installed and available on PATH",
|
||||
e
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
String standardOutput = new String(
|
||||
process.getInputStream().readAllBytes(),
|
||||
StandardCharsets.UTF_8
|
||||
);
|
||||
String standardError = new String(
|
||||
process.getErrorStream().readAllBytes(),
|
||||
StandardCharsets.UTF_8
|
||||
);
|
||||
int exitCode = process.waitFor();
|
||||
|
||||
if (exitCode != 0) {
|
||||
throw new IllegalStateException(
|
||||
"Required jup CLI command '"
|
||||
+ commandDescription
|
||||
+ "' failed while validating Solana wallet "
|
||||
+ "signer '"
|
||||
+ signerKeyName
|
||||
+ "' with exit code "
|
||||
+ exitCode
|
||||
+ ": "
|
||||
+ standardError.trim()
|
||||
);
|
||||
}
|
||||
|
||||
return new JupCommandResult(standardOutput);
|
||||
} catch (IOException e) {
|
||||
process.destroyForcibly();
|
||||
throw new IllegalStateException(
|
||||
"Unable to read the result of required jup CLI command '"
|
||||
+ commandDescription
|
||||
+ "' while validating Solana wallet signer '"
|
||||
+ signerKeyName
|
||||
+ "'",
|
||||
e
|
||||
);
|
||||
} catch (InterruptedException e) {
|
||||
process.destroyForcibly();
|
||||
Thread.currentThread().interrupt();
|
||||
throw new IllegalStateException(
|
||||
"Interrupted while running required jup CLI command '"
|
||||
+ commandDescription
|
||||
+ "' for Solana wallet signer '"
|
||||
+ signerKeyName
|
||||
+ "'",
|
||||
e
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -475,6 +669,10 @@ public class SolanaWalletImpl implements SolanaWallet {
|
||||
) {
|
||||
}
|
||||
|
||||
private record JupCommandResult(String standardOutput) {
|
||||
}
|
||||
|
||||
private static final String EXPECTED_JUP_VERSION = "0.10.1";
|
||||
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
|
||||
|
||||
private final ΩSolanaWalletIdΩ address;
|
||||
|
||||
Reference in New Issue
Block a user