Skip to content

Commit

Permalink
ix filter methods for Skeleton.
Browse files Browse the repository at this point in the history
  • Loading branch information
jpe7s committed Oct 9, 2024
1 parent 7d682cc commit 44288db
Show file tree
Hide file tree
Showing 7 changed files with 203 additions and 98 deletions.
23 changes: 19 additions & 4 deletions core/src/main/java/software/sava/core/tx/TransactionSkeleton.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import software.sava.core.accounts.PublicKey;
import software.sava.core.accounts.lookup.AddressLookupTable;
import software.sava.core.accounts.meta.AccountMeta;
import software.sava.core.programs.Discriminator;

import java.util.Arrays;
import java.util.Map;
Expand Down Expand Up @@ -166,6 +167,8 @@ default AccountMeta[] parseAccounts(final Stream<AddressLookupTable> lookupTable

AccountMeta[] parseAccounts(final AddressLookupTable lookupTable);

PublicKey[] parseProgramAccounts();

int serializedInstructionsLength();

Instruction[] parseInstructions(final AccountMeta[] accounts);
Expand All @@ -174,11 +177,23 @@ default Instruction[] parseLegacyInstructions() {
return parseInstructions(parseAccounts());
}

PublicKey[] parseProgramAccounts();

/**
* Program accounts will be included for each instruction.
* Instruction accounts will not.
*/
Instruction[] parseInstructionsWithoutAccounts();

Instruction[] parseInstructionsWithAccounts();

/**
* If this is a versioned transaction accounts which are indexed into a lookup table will be null.
* Signing accounts and program accounts will always be included.
*/
Instruction[] parseInstructionsWithoutTableAccounts();

Instruction[] filterInstructions(final AccountMeta[] accounts, final Discriminator discriminator);

default Instruction[] filterInstructionsWithoutTableAccounts(final Discriminator discriminator) {
return filterInstructions(parseAccounts(), discriminator);
}

Instruction[] filterInstructionsWithoutAccounts(final Discriminator discriminator);
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import software.sava.core.accounts.lookup.AddressLookupTable;
import software.sava.core.accounts.meta.AccountMeta;
import software.sava.core.encoding.Base58;
import software.sava.core.programs.Discriminator;

import java.util.Arrays;
import java.util.List;
Expand Down Expand Up @@ -207,7 +208,7 @@ public Instruction[] parseInstructions(final AccountMeta[] accounts) {
o += getByteLen(data, o);
for (int a = 0; a < numIxAccounts; ++a) {
accountIndex = data[o++] & 0xFF;
ixAccounts[a] = accounts[accountIndex];
ixAccounts[a] = accountIndex < accounts.length ? accounts[accountIndex] : null;
}

final int len = decode(data, o);
Expand All @@ -218,13 +219,21 @@ public Instruction[] parseInstructions(final AccountMeta[] accounts) {
return instructions;
}

private int accountOffset(final int accountIndex) {
return accountsOffset + (accountIndex * PUBLIC_KEY_LENGTH);
}

private PublicKey getAccount(final int accountIndex) {
return PublicKey.readPubKey(data, accountOffset(accountIndex));
}

@Override
public PublicKey[] parseProgramAccounts() {
final var programs = new PublicKey[numInstructions];
for (int i = 0, o = instructionsOffset, programAccountIndex, numIxAccounts, len; i < numInstructions; ++i) {
programAccountIndex = decode(data, o);
o += getByteLen(data, o);
programs[i] = PublicKey.readPubKey(data, accountsOffset + (programAccountIndex * PUBLIC_KEY_LENGTH));
programs[i] = getAccount(programAccountIndex);

numIxAccounts = decode(data, o);
o += getByteLen(data, o);
Expand All @@ -245,7 +254,7 @@ public Instruction[] parseInstructionsWithoutAccounts() {
for (int i = 0, o = instructionsOffset, numIxAccounts, len; i < numInstructions; ++i) {
final int programAccountIndex = decode(data, o);
o += getByteLen(data, o);
final var programAccount = PublicKey.readPubKey(data, accountsOffset + (programAccountIndex * PUBLIC_KEY_LENGTH));
final var programAccount = getAccount(programAccountIndex);

numIxAccounts = decode(data, o);
o += getByteLen(data, o);
Expand All @@ -260,9 +269,59 @@ public Instruction[] parseInstructionsWithoutAccounts() {
}

@Override
public Instruction[] parseInstructionsWithAccounts() {
final var accounts = parseAccounts();
return parseInstructions(accounts);
public Instruction[] filterInstructions(final AccountMeta[] accounts, final Discriminator discriminator) {
final var instructions = new Instruction[numInstructions];
int d = 0;
for (int i = 0, o = instructionsOffset, numIxAccounts, len; i < numInstructions; ++i) {
final int programAccountIndex = decode(data, o);
o += getByteLen(data, o);

numIxAccounts = decode(data, o);
o += getByteLen(data, o);
int accountsOffset = o;
o += numIxAccounts;

len = decode(data, o);
o += getByteLen(data, o);

if (discriminator.equals(data, o)) {
final var ixAccounts = new AccountMeta[numIxAccounts];
for (int a = 0; a < numIxAccounts; ++a) {
final int accountIndex = data[accountsOffset++] & 0xFF;
ixAccounts[a] = accountIndex < accounts.length ? accounts[accountIndex] : null;
}
instructions[d++] = createInstruction(getAccount(programAccountIndex), Arrays.asList(ixAccounts), data, o, len);
}
o += len;
}
return d == numInstructions
? instructions
: Arrays.copyOfRange(instructions, 0, d);
}

@Override
public Instruction[] filterInstructionsWithoutAccounts(final Discriminator discriminator) {
final var instructions = new Instruction[numInstructions];
int d = 0;
for (int i = 0, o = instructionsOffset, numIxAccounts, len; i < numInstructions; ++i) {
final int programAccountIndex = decode(data, o);
o += getByteLen(data, o);

numIxAccounts = decode(data, o);
o += getByteLen(data, o);
o += numIxAccounts;

len = decode(data, o);
o += getByteLen(data, o);

if (discriminator.equals(data, o)) {
instructions[d++] = createInstruction(getAccount(programAccountIndex), NO_ACCOUNTS, data, o, len);
}
o += len;
}
return d == numInstructions
? instructions
: Arrays.copyOfRange(instructions, 0, d);
}

private AccountMeta[] parseIncludedAccounts() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import static org.junit.jupiter.api.Assertions.assertTrue;
import static software.sava.core.accounts.PublicKey.fromBase58Encoded;
import static software.sava.core.accounts.meta.AccountMeta.*;
import static software.sava.core.programs.Discriminator.toDiscriminator;

final class TransactionSerializationTests {

Expand All @@ -22,11 +23,12 @@ void testVariableLengthIxLength() {
final byte[] data = Base64.getDecoder().decode("""
ATgc2Iye/GlwnpSeIytu+tYkb2A+5VJhc1yui59+7/PMQSuywEqpb3k8wHCKnupEuC5fDTUjvGhASTEH5c90UACAAQAFEU4rs4al2vatnKR6MtsLLzl+Q24T1Y5kkYBmPhrq9O/VzcyvadLTPMXTLHJ2IKteqvqoQAgRH4dVHOW+cw1EkNOJB31VpbsTMHY+t2f1XsB3tBoNB1994dc/uso8Y9VUcRCcPGXQaDMBtOvEnG0Lyr4Lf68erOMMjG6weDn4HuIS6tSjkUAFDNLqypEZqieck8DZMKBobFJb3fYlMJjWpjHvHv25qj1olz/ZenFlAVmw6stGZYC5aF5nQ9ZqQr8vxXTXpuq5/UeOzPqvqL7sJuBwFgO//vEZG9uw6edrxAd2vInnwNHlA4uvk7TwFNJd9xWnndlfBJ5f9fX36m+JwJ9fAlkt3jAFytFpv8wnPC/6I0tpd+F+Bw3UOdTTA8X8HR7XL/DvxwiqYIadWSBAIms1hbo9KoaOYES91ZtNIF/jeSWG+N64PtIqGyqU3OdPOEd0TTjj79CJx+HICgFkwRrNq0B12gG6uYd+a79dybCsJPRSedzSl8R6nwJYXSLJGQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAjJclj04kifG7PRApFI4NgwtaE5na/xCEBI572Nvp+FkDBkZv5SEXMv/srbpyw5vnvIzlu8X3EmssQ5s6QAAAAJWBt/6PKcF0R86zH0ytUdAc7K5LbdbpdsVcWwvkMUGK86EED9glv4WMHdJchvP2ZZvDPQq6PniaYqUsrsmZ94ETjVRi2k6UWSxzbmoMl/cGeYhlpwGEqRE3YD3BBue3swYOAAUCgE8SAA4ABQEAAAIADgAJAyKiAAAAAAAADwYdDAABFg0IllUsHJUO0hoPHQACAwwREhMUARUWFx0eGBkEHxogGxwFBgcICQoLigEyEHMzqHo5LQIAAAACAAAAAAECAAAAAgEDAAAABBIAAAAEBQQGBwIICQoLBAAMDA0ODxABCQAAABEMEhMUFQgCAAQSAAAABBYEFxgCCAoJGQQADAwNGhscCAAAABAnTB2IE8QJ6ANkAAoAAQC0ZeZpAAAAAFDDAAAAAAAAAAAAAAAAAAAAAAAAAAAQAB9Qb3dlcmVkIGJ5IGJsb1hyb3V0ZSBUcmFkZXIgQXBpAonsVzlUh3H7+XOmaklk0KWZm+wt34ECwzGrxcB6Sb7VCQRLSE8FSUpRTgQMAgsHmoTIVF9hkZWFmpxCqc8zcavo9Yu6S8ysQBz59/7iw1ADVllVAA==""");
final var skeleton = TransactionSkeleton.deserializeSkeleton(data);
final var instructions = skeleton.parseInstructionsWithoutTableAccounts();
var instructions = skeleton.parseInstructionsWithoutTableAccounts();
assertEquals(6, instructions.length);
var ix = instructions[4];
assertEquals("B4cUJzpPVNKdnjnN7x7MhhXzJT5Z9SpteFp3JK3Y7bow", ix.programId().publicKey().toBase58());
assertEquals(138, ix.len());

ix = instructions[5];
assertEquals("HQ2UUt18uJqKaQFJhgV9zaTdQxUZjNrsKFgoEDquBkcx", ix.programId().publicKey().toBase58());
assertEquals(31, ix.len());
Expand All @@ -37,6 +39,40 @@ void testVariableLengthIxLength() {
assertEquals("AHPsnG22z2BwdeURWWPPQzLYRb6jT9PPDXzQ3hMF7hNc", tableAccount.toBase58());
tableAccount = lookupTables[1];
assertEquals("BQBDhjQ32VYzNzXW8vViqpuLiz2TAYcW1cVZDAL4Hf67", tableAccount.toBase58());

final var accounts = skeleton.parseAccounts();
for (int i = 0; i < 3; ++i) {
ix = instructions[i];
final var discriminator = toDiscriminator(ix.discriminator(1));
var instructionArray = skeleton.filterInstructions(accounts, discriminator);
assertEquals(1, instructionArray.length);
assertEquals(ix, instructionArray[0]);
}

for (int i = 3; i < instructions.length; ++i) {
ix = instructions[i];
final var discriminator = toDiscriminator(ix.discriminator(8));
var instructionArray = skeleton.filterInstructions(accounts, discriminator);
assertEquals(1, instructionArray.length);
assertEquals(ix, instructionArray[0]);
}

instructions = skeleton.parseInstructionsWithoutAccounts();
for (int i = 0; i < 3; ++i) {
ix = instructions[i];
final var discriminator = toDiscriminator(ix.discriminator(1));
var instructionArray = skeleton.filterInstructionsWithoutAccounts(discriminator);
assertEquals(1, instructionArray.length);
assertEquals(ix, instructionArray[0]);
}

for (int i = 3; i < instructions.length; ++i) {
ix = instructions[i];
final var discriminator = toDiscriminator(ix.discriminator(8));
var instructionArray = skeleton.filterInstructionsWithoutAccounts(discriminator);
assertEquals(1, instructionArray.length);
assertEquals(ix, instructionArray[0]);
}
}

private void testMultipleLookupTables(final TransactionSkeleton skeleton,
Expand Down
6 changes: 3 additions & 3 deletions rpc/src/main/java/software/sava/rpc/json/http/ws/Channel.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package software.sava.rpc.json.http.ws;

public enum Channel {
enum Channel {

account,
logs,
Expand All @@ -17,11 +17,11 @@ public enum Channel {
this.unSubscribe = name() + "Unsubscribe";
}

public String subscribe() {
String subscribe() {
return subscribe;
}

public String unSubscribe() {
String unSubscribe() {
return unSubscribe;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@
import software.sava.core.accounts.token.TokenAccount;
import software.sava.core.rpc.Filter;
import software.sava.rpc.json.http.request.Commitment;
import software.sava.rpc.json.http.request.RpcEncoding;
import software.sava.rpc.json.http.request.TransactionDetails;
import software.sava.rpc.json.http.response.*;
import systems.comodal.jsoniter.CharBufferFunction;
import systems.comodal.jsoniter.JsonIterator;

import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.WebSocket;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
Expand All @@ -24,11 +24,12 @@
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Collectors;

import static java.lang.System.Logger.Level.*;
import static java.nio.charset.StandardCharsets.ISO_8859_1;
import static java.util.concurrent.TimeUnit.HOURS;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static java.util.stream.Collectors.joining;
import static software.sava.rpc.json.http.response.AccountInfo.BYTES_IDENTITY;
import static systems.comodal.jsoniter.JsonIterator.fieldEquals;

Expand Down Expand Up @@ -389,7 +390,7 @@ public boolean programSubscribe(final Commitment commitment,
if (sub == null || !sub.containsKey(commitment)) {
final var filtersJson = filters.isEmpty() ? "" : filters.stream()
.map(Filter::toJson)
.collect(Collectors.joining(",", ",\"filters\":[", "]"));
.collect(joining(",", ",\"filters\":[", "]"));

final var params = String.format("""
"%s",{"commitment":"%s","encoding":"base64"%s}""",
Expand All @@ -410,42 +411,42 @@ public boolean programUnsubscribe(final Commitment commitment, final PublicKey p
return queueUnsubscribe(program.toBase58(), Channel.program, commitment, this.programSubs);
}

@Override
public boolean transactionSubscribe(final Commitment commitment,
final boolean vote,
final boolean failed,
final String signature,
final Collection<PublicKey> accountInclude,
final Collection<PublicKey> accountExclude,
final Collection<PublicKey> accountRequired,
final RpcEncoding encoding,
final TransactionDetails transactionDetails,
final boolean showRewards,
final Consumer<TxResult> consumer) {
final var signatureField = signature == null || signature.isBlank()
? ""
: String.format("""
",signature":"%s\"""", signature);

/// Only supported by Helius on a Business or Professional plan
private boolean transactionSubscribe(final Commitment commitment,
final boolean vote,
final boolean failed,
final Collection<PublicKey> accountInclude,
final Collection<PublicKey> accountExclude,
final Collection<PublicKey> accountRequired,
final TransactionDetails transactionDetails,
final boolean showRewards,
final Consumer<TxResult> consumer) {
final var includes = accountInclude == null || accountInclude.isEmpty()
? ""
: accountInclude.stream().map(PublicKey::toBase58).collect(Collectors.joining("\",\"", ",\"accountInclude\":[\"", "\"]"));
: accountInclude.stream()
.map(PublicKey::toBase58)
.collect(joining("\",\"", ",\"accountInclude\":[\"", "\"]"));

final var excludes = accountExclude == null || accountExclude.isEmpty()
? ""
: accountExclude.stream().map(PublicKey::toBase58).collect(Collectors.joining("\",\"", ",\"accountExclude\":[\"", "\"]"));
: accountExclude.stream()
.map(PublicKey::toBase58).
collect(joining("\",\"", ",\"accountExclude\":[\"", "\"]"));

final var required = accountRequired == null || accountRequired.isEmpty()
? ""
: accountRequired.stream().map(PublicKey::toBase58).collect(Collectors.joining("\",\"", ",\"accountRequired\":[\"", "\"]"));
: accountRequired.stream()
.map(PublicKey::toBase58)
.collect(joining("\",\"", ",\"accountRequired\":[\"", "\"]"));

final var params = String.format("""
{"vote":%b,"failed":%b%s%s%s%s},{"commitment":"%s","encoding":"%s","transactionDetails":"%s","showRewards":%b,"maxSupportedTransactionVersion":0}""",
{"vote":%b,"failed":%b%s%s%s},{"commitment":"%s","encoding":"base64","transactionDetails":"%s","showRewards":%b,"maxSupportedTransactionVersion":0}""",
vote,
failed,
signatureField,
includes,
excludes,
required,
commitment.getValue(),
encoding,
transactionDetails,
showRewards
);
Expand Down Expand Up @@ -561,7 +562,7 @@ private void onWholeMessage(final char[] msg,
final int tail,
final JsonIterator ji,
final WebSocket webSocket) {
// System.out.format("<- %s%n", new String(msg, offset, tail - offset));
System.out.format("<- %s%n", new String(msg, offset, tail - offset));
try {
if (ji.skipUntil("method") == null) {
if (ji.reset(offset).skipUntil("error") != null) {
Expand Down Expand Up @@ -606,6 +607,8 @@ private void onWholeMessage(final char[] msg,
ji.skipUntil("result");
if (channel == Channel.transaction) {
ji.skipUntil("transaction");


} else {
final int resultMark = ji.mark();
ji.skipUntil("context");
Expand Down Expand Up @@ -827,4 +830,26 @@ public void close() {
this.programSubs.clear();
this.slotSub.set(null);
}


public static void main(final String[] args) throws InterruptedException {
final var meteoraProgramId = PublicKey.fromBase58Encoded("LBUZKhRxPF3XUpBCjp4YzTKgLccjZhTSDM9YuVaPwxo");
try (final var executorService = Executors.newVirtualThreadPerTaskExecutor()) {
try (final var httpClient = HttpClient.newBuilder().executor(executorService).build()) {
final var websocketBuilder = SolanaRpcWebsocket.build();
websocketBuilder.uri(URI.create("wss://atlas-mainnet.helius-rpc.com?api-key="));
websocketBuilder.webSocketBuilder(httpClient);
websocketBuilder.commitment(Commitment.CONFIRMED);
final var webSocket = websocketBuilder.create();
// webSocket.transactionSubscribe(
// Commitment.CONFIRMED,
// List.of(), List.of(),
// List.of(meteoraProgramId),
// result -> System.out.println(result)
// );
webSocket.connect();
HOURS.sleep(1);
}
}
}
}
Loading

0 comments on commit 44288db

Please sign in to comment.