diff --git a/src/Solnet.Examples/Solnet.Examples.csproj b/src/Solnet.Examples/Solnet.Examples.csproj index 7a58ac4b..1922a935 100644 --- a/src/Solnet.Examples/Solnet.Examples.csproj +++ b/src/Solnet.Examples/Solnet.Examples.csproj @@ -2,7 +2,7 @@ Exe - net6.0 + net8.0 Exe false diff --git a/src/Solnet.Extensions/Solnet.Extensions.csproj b/src/Solnet.Extensions/Solnet.Extensions.csproj index 8e638a67..6bc661b6 100644 --- a/src/Solnet.Extensions/Solnet.Extensions.csproj +++ b/src/Solnet.Extensions/Solnet.Extensions.csproj @@ -1,7 +1,7 @@  - net6.0 + net8.0 diff --git a/src/Solnet.KeyStore/Solnet.KeyStore.csproj b/src/Solnet.KeyStore/Solnet.KeyStore.csproj index 77779b8f..88a0a426 100644 --- a/src/Solnet.KeyStore/Solnet.KeyStore.csproj +++ b/src/Solnet.KeyStore/Solnet.KeyStore.csproj @@ -1,7 +1,7 @@  - net6.0 + net8.0 true diff --git a/src/Solnet.Programs/ComputeBudgetProgram.cs b/src/Solnet.Programs/ComputeBudgetProgram.cs new file mode 100644 index 00000000..2b73eb9d --- /dev/null +++ b/src/Solnet.Programs/ComputeBudgetProgram.cs @@ -0,0 +1,97 @@ +using Solnet.Programs.Utilities; +using Solnet.Rpc.Models; +using Solnet.Wallet; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Solnet.Programs +{ + /// + /// Implements the ComputeBudget Program methods. + /// + /// For more information see: https://spl.solana.com/memo + /// + /// + + public class ComputeBudgetProgram + { + + /// + /// The public key of the ComputeBudget Program. + /// + public static readonly PublicKey ProgramIdKey = new("ComputeBudget111111111111111111111111111111"); + + + /// + /// The program's name. + /// + private const string ProgramName = "Compute Budget Program"; + + + + /// + /// Request HeapFrame Instruction related to Priority Fees + /// + /// + /// + public static TransactionInstruction RequestHeapFrame(uint bytes) + { + List keys = new(); + + byte[] instructionBytes = new byte[17]; + instructionBytes.WriteU8(1, 0); + instructionBytes.WriteU32(bytes, 1); + + return new TransactionInstruction + { + ProgramId = ProgramIdKey.KeyBytes, + Keys = keys, + Data = instructionBytes + }; + } + /// + /// Set Compute Unit Limit Instruction for Priority Fees + /// + /// + /// + public static TransactionInstruction SetComputeUnitLimit(uint units) + { + List keys = new(); + + byte[] instructionBytes = new byte[9]; + instructionBytes.WriteU8(2, 0); + instructionBytes.WriteU64(units, 1); + + return new TransactionInstruction + { + ProgramId = ProgramIdKey.KeyBytes, + Keys = keys, + Data = instructionBytes + }; + } + /// + /// Set Compute Unit Price Instruction for Priority Fees + /// + /// + /// + public static TransactionInstruction SetComputeUnitPrice(ulong priority_rate) + { + List keys = new(); + + byte[] instructionBytes = new byte[9]; + instructionBytes.WriteU8(3, 0); + instructionBytes.WriteU64(priority_rate, 1); + + return new TransactionInstruction + { + ProgramId = ProgramIdKey.KeyBytes, + Keys = keys, + Data = instructionBytes + }; + } + + } +} diff --git a/src/Solnet.Programs/Solnet.Programs.csproj b/src/Solnet.Programs/Solnet.Programs.csproj index c0f92eb4..03e2b51f 100644 --- a/src/Solnet.Programs/Solnet.Programs.csproj +++ b/src/Solnet.Programs/Solnet.Programs.csproj @@ -1,7 +1,7 @@  - net6.0 + net8.0 true diff --git a/src/Solnet.Programs/TokenProgramData.cs b/src/Solnet.Programs/TokenProgramData.cs index 64ff2c66..24a220e4 100644 --- a/src/Solnet.Programs/TokenProgramData.cs +++ b/src/Solnet.Programs/TokenProgramData.cs @@ -302,7 +302,10 @@ internal static void DecodeSetAuthorityData(DecodedInstruction decodedInstructio decodedInstruction.Values.Add("Current Authority", keys[keyIndices[1]]); decodedInstruction.Values.Add("Authority Type", Enum.Parse(typeof(AuthorityType), data.GetU8(1).ToString())); decodedInstruction.Values.Add("New Authority Option", data.GetU8(2)); - decodedInstruction.Values.Add("New Authority", data.GetPubKey(3)); + if (data.Length >= 34) + { + decodedInstruction.Values.Add("New Authority", data.GetPubKey(3)); + } for (int i = 2; i < keyIndices.Length; i++) { decodedInstruction.Values.Add($"Signer {i - 1}", keys[keyIndices[i]]); diff --git a/src/Solnet.Rpc/Builders/MessageBuilder.cs b/src/Solnet.Rpc/Builders/MessageBuilder.cs index 289e25c4..2da82039 100644 --- a/src/Solnet.Rpc/Builders/MessageBuilder.cs +++ b/src/Solnet.Rpc/Builders/MessageBuilder.cs @@ -18,22 +18,22 @@ public class MessageBuilder /// /// The length of the block hash. /// - private const int BlockHashLength = 32; + protected const int BlockHashLength = 32; /// /// The message header. /// - private MessageHeader _messageHeader; + protected MessageHeader _messageHeader; /// /// The account keys list. /// - private readonly AccountKeysList _accountKeysList; + protected readonly AccountKeysList _accountKeysList; /// /// The list of instructions contained within this transaction. /// - internal List Instructions { get; private set; } + internal List Instructions { get; private protected set; } /// /// The hash of a recent block. @@ -76,7 +76,7 @@ internal MessageBuilder AddInstruction(TransactionInstruction instruction) /// Builds the message into the wire format. /// /// The encoded message. - internal byte[] Build() + internal virtual byte[] Build() { if (RecentBlockHash == null && NonceInformation == null) throw new Exception("recent block hash or nonce information is required"); @@ -111,8 +111,7 @@ internal byte[] Build() { keyIndices[i] = FindAccountIndex(keysList, instruction.Keys[i].PublicKey); } - - CompiledInstruction compiledInstruction = new CompiledInstruction + CompiledInstruction compiledInstruction = new() { ProgramIdIndex = FindAccountIndex(keysList, instruction.ProgramId), KeyIndicesCount = ShortVectorEncoding.EncodeLength(keyCount), @@ -176,7 +175,7 @@ internal byte[] Build() /// Gets the keys for the accounts present in the message. /// /// The list of . - private List GetAccountKeys() + protected List GetAccountKeys() { List newList = new(); var keysList = _accountKeysList.AccountList; @@ -203,7 +202,7 @@ private List GetAccountKeys() /// The . /// The public key. /// The index of the - private static byte FindAccountIndex(IList accountMetas, byte[] publicKey) + protected static byte FindAccountIndex(IList accountMetas, byte[] publicKey) { string encodedKey = Encoders.Base58.EncodeData(publicKey); return FindAccountIndex(accountMetas, encodedKey); @@ -215,7 +214,7 @@ private static byte FindAccountIndex(IList accountMetas, byte[] pub /// The . /// The public key. /// The index of the - private static byte FindAccountIndex(IList accountMetas, string publicKey) + protected static byte FindAccountIndex(IList accountMetas, string publicKey) { for (byte index = 0; index < accountMetas.Count; index++) { diff --git a/src/Solnet.Rpc/Builders/VersionedMessageBuilder.cs b/src/Solnet.Rpc/Builders/VersionedMessageBuilder.cs new file mode 100644 index 00000000..dcaa93de --- /dev/null +++ b/src/Solnet.Rpc/Builders/VersionedMessageBuilder.cs @@ -0,0 +1,137 @@ +using Solnet.Rpc.Models; +using Solnet.Rpc.Utilities; +using Solnet.Wallet; +using Solnet.Wallet.Utilities; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using static Solnet.Rpc.Models.Message; + +namespace Solnet.Rpc.Builders +{ + /// + /// A compiled instruction within the message. + /// + public class VersionedMessageBuilder : MessageBuilder + { + + /// + /// Address Table Lookups + /// + public List AddressTableLookups { get; set; } + public IList AccountKeys { get; internal set; } + + /// + /// Builds the message into the wire format. + /// + /// The encoded message. + internal override byte[] Build() + { + if (RecentBlockHash == null && NonceInformation == null) + throw new Exception("recent block hash or nonce information is required"); + if (Instructions == null) + throw new Exception("no instructions provided in the transaction"); + + // In case the user specified nonce information, we'll use it. + if (NonceInformation != null) + { + RecentBlockHash = NonceInformation.Nonce; + _accountKeysList.Add(NonceInformation.Instruction.Keys); + _accountKeysList.Add(AccountMeta.ReadOnly(new PublicKey(NonceInformation.Instruction.ProgramId), + false)); + List newInstructions = new() { NonceInformation.Instruction }; + newInstructions.AddRange(Instructions); + Instructions = newInstructions; + } + + _messageHeader = new MessageHeader(); + + List keysList = GetAccountKeys(); + byte[] accountAddressesLength = ShortVectorEncoding.EncodeLength(keysList.Count); + int compiledInstructionsLength = 0; + List compiledInstructions = new(); + + foreach (TransactionInstruction instruction in Instructions) + { + int keyCount = instruction.Keys.Count; + byte[] keyIndices = new byte[keyCount]; + + if (instruction.GetType() == typeof(VersionedTransactionInstruction)) + { + keyIndices = ((VersionedTransactionInstruction)instruction).KeyIndices; + } + else + { + for (int i = 0; i < keyCount; i++) + { + keyIndices[i] = FindAccountIndex(keysList, instruction.Keys[i].PublicKey); + } + } + + CompiledInstruction compiledInstruction = new() + { + ProgramIdIndex = FindAccountIndex(keysList, instruction.ProgramId), + KeyIndicesCount = ShortVectorEncoding.EncodeLength(keyIndices.Length), + KeyIndices = keyIndices, + DataLength = ShortVectorEncoding.EncodeLength(instruction.Data.Length), + Data = instruction.Data + }; + compiledInstructions.Add(compiledInstruction); + compiledInstructionsLength += compiledInstruction.Length(); + } + + int accountKeysBufferSize = _accountKeysList.AccountList.Count * 32; + MemoryStream accountKeysBuffer = new MemoryStream(accountKeysBufferSize); + byte[] instructionsLength = ShortVectorEncoding.EncodeLength(compiledInstructions.Count); + + foreach (AccountMeta accountMeta in keysList) + { + accountKeysBuffer.Write(accountMeta.PublicKeyBytes, 0, accountMeta.PublicKeyBytes.Length); + if (accountMeta.IsSigner) + { + _messageHeader.RequiredSignatures += 1; + if (!accountMeta.IsWritable) + _messageHeader.ReadOnlySignedAccounts += 1; + } + else + { + if (!accountMeta.IsWritable) + _messageHeader.ReadOnlyUnsignedAccounts += 1; + } + } + + #region Build Message Body + + int messageBufferSize = MessageHeader.Layout.HeaderLength + BlockHashLength + + accountAddressesLength.Length + + +instructionsLength.Length + compiledInstructionsLength + accountKeysBufferSize; + MemoryStream buffer = new MemoryStream(messageBufferSize); + byte[] messageHeaderBytes = _messageHeader.ToBytes(); + + buffer.Write(new byte[] { 128 }, 0, 1); + buffer.Write(messageHeaderBytes, 0, messageHeaderBytes.Length); + buffer.Write(accountAddressesLength, 0, accountAddressesLength.Length); + buffer.Write(accountKeysBuffer.ToArray(), 0, accountKeysBuffer.ToArray().Length); + var encodedRecentBlockHash = Encoders.Base58.DecodeData(RecentBlockHash); + buffer.Write(encodedRecentBlockHash, 0, encodedRecentBlockHash.Length); + buffer.Write(instructionsLength, 0, instructionsLength.Length); + + foreach (CompiledInstruction compiledInstruction in compiledInstructions) + { + buffer.WriteByte(compiledInstruction.ProgramIdIndex); + buffer.Write(compiledInstruction.KeyIndicesCount, 0, compiledInstruction.KeyIndicesCount.Length); + buffer.Write(compiledInstruction.KeyIndices, 0, compiledInstruction.KeyIndices.Length); + buffer.Write(compiledInstruction.DataLength, 0, compiledInstruction.DataLength.Length); + buffer.Write(compiledInstruction.Data, 0, compiledInstruction.Data.Length); + } + + #endregion + + var serializeAddressTableLookups = AddressTableLookupUtils.SerializeAddressTableLookups(AddressTableLookups); + buffer.Write(serializeAddressTableLookups, 0, serializeAddressTableLookups.Length); + + return buffer.ToArray(); + } + } +} \ No newline at end of file diff --git a/src/Solnet.Rpc/IRpcClient.cs b/src/Solnet.Rpc/IRpcClient.cs index e3f7d0ad..391b0c1c 100644 --- a/src/Solnet.Rpc/IRpcClient.cs +++ b/src/Solnet.Rpc/IRpcClient.cs @@ -133,7 +133,7 @@ RequestResult> GetAccountInfo(string pubKey, Commitme /// Whether to populate the rewards array, the default includes rewards. /// Returns a task that holds the asynchronous operation result and state. Task> GetBlockAsync(ulong slot, Commitment commitment = Commitment.Finalized, - TransactionDetailsFilterType transactionDetails = TransactionDetailsFilterType.Full, bool blockRewards = false); + TransactionDetailsFilterType transactionDetails = TransactionDetailsFilterType.Full, bool blockRewards = false, int maxSupportedTransactionVersion = 0); /// /// Returns identity and transaction information about a confirmed block in the ledger. @@ -154,10 +154,11 @@ Task> GetBlockAsync(ulong slot, Commitment commitment = /// The state commitment to consider when querying the ledger state. /// The level of transaction detail to return, see . /// Whether to populate the rewards array, the default includes rewards. + /// Max supported transaction version either LEGACY or 1 /// Returns a task that holds the asynchronous operation result and state. [Obsolete("Please use GetBlockAsync whenever possible instead. This method is expected to be removed in solana-core v1.8.")] Task> GetConfirmedBlockAsync(ulong slot, Commitment commitment = Commitment.Finalized, - TransactionDetailsFilterType transactionDetails = TransactionDetailsFilterType.Full, bool blockRewards = false); + TransactionDetailsFilterType transactionDetails = TransactionDetailsFilterType.Full, bool blockRewards = false, int maxSupportedTransactionVersion = 0); /// /// Returns identity and transaction information about a block in the ledger. @@ -178,9 +179,10 @@ Task> GetConfirmedBlockAsync(ulong slot, Commitment com /// The state commitment to consider when querying the ledger state. /// The level of transaction detail to return, see . /// Whether to populate the rewards array, the default includes rewards. + /// Max supported transaction version either LEGACY or 1 /// Returns an object that wraps the result along with possible errors with the request. RequestResult GetBlock(ulong slot, Commitment commitment = Commitment.Finalized, - TransactionDetailsFilterType transactionDetails = TransactionDetailsFilterType.Full, bool blockRewards = false); + TransactionDetailsFilterType transactionDetails = TransactionDetailsFilterType.Full, bool blockRewards = false, int maxSupportedTransactionVersion = 0); /// /// Returns identity and transaction information about a confirmed block in the ledger. @@ -201,10 +203,11 @@ RequestResult GetBlock(ulong slot, Commitment commitment = Commitment /// The state commitment to consider when querying the ledger state. /// The level of transaction detail to return, see . /// Whether to populate the rewards array, the default includes rewards. + /// Max supported transaction version either LEGACY or 1 /// Returns an object that wraps the result along with possible errors with the request. [Obsolete("Please use GetBlock whenever possible instead. This method is expected to be removed in solana-core v1.8.")] RequestResult GetConfirmedBlock(ulong slot, Commitment commitment = Commitment.Finalized, - TransactionDetailsFilterType transactionDetails = TransactionDetailsFilterType.Full, bool blockRewards = false); + TransactionDetailsFilterType transactionDetails = TransactionDetailsFilterType.Full, bool blockRewards = false, int maxSupportedTransactionVersion = 0); /// /// Gets the block commitment of a certain block, identified by slot. @@ -1075,9 +1078,10 @@ Task>> GetTokenSupplyAsync(string toke /// /// Transaction signature as base-58 encoded string. /// The state commitment to consider when querying the ledger state. + /// Max supported transaction version either LEGACY or 1 /// Returns a task that holds the asynchronous operation result and state. Task> GetTransactionAsync(string signature, - Commitment commitment = Commitment.Finalized); + Commitment commitment = Commitment.Finalized, int maxSupportedTransactionVersion = 0); /// /// Returns transaction details for a confirmed transaction. @@ -1090,9 +1094,10 @@ Task> GetTransactionAsync(string signatur /// /// Transaction signature as base-58 encoded string. /// The state commitment to consider when querying the ledger state. + /// Max supported transaction version either LEGACY or 1 /// Returns an object that wraps the result along with possible errors with the request. [Obsolete("Please use GetTransactionAsync whenever possible instead. This method is expected to be removed in solana-core v1.8.")] - Task> GetConfirmedTransactionAsync(string signature, Commitment commitment = Commitment.Finalized); + Task> GetConfirmedTransactionAsync(string signature, Commitment commitment = Commitment.Finalized, int maxSupportedTransactionVersion = 0); /// /// Returns transaction details for a confirmed transaction. @@ -1105,8 +1110,9 @@ Task> GetTransactionAsync(string signatur /// /// Transaction signature as base-58 encoded string. /// The state commitment to consider when querying the ledger state. + /// Max supported transaction version either LEGACY or 1 /// Returns an object that wraps the result along with possible errors with the request. - RequestResult GetTransaction(string signature, Commitment commitment = Commitment.Finalized); + RequestResult GetTransaction(string signature, Commitment commitment = Commitment.Finalized, int maxSupportedTransactionVersion = 0); /// /// Returns transaction details for a confirmed transaction. @@ -1119,9 +1125,10 @@ Task> GetTransactionAsync(string signatur /// /// Transaction signature as base-58 encoded string. /// The state commitment to consider when querying the ledger state. + /// Max supported transaction version either LEGACY or 1 /// Returns an object that wraps the result along with possible errors with the request. [Obsolete("Please use GetTransaction whenever possible instead. This method is expected to be removed in solana-core v1.8.")] - RequestResult GetConfirmedTransaction(string signature, Commitment commitment = Commitment.Finalized); + RequestResult GetConfirmedTransaction(string signature, Commitment commitment = Commitment.Finalized, int maxSupportedTransactionVersion = 0); /// /// Gets the total transaction count of the ledger. diff --git a/src/Solnet.Rpc/Models/AccountKeysList.cs b/src/Solnet.Rpc/Models/AccountKeysList.cs index b567f64a..b779423e 100644 --- a/src/Solnet.Rpc/Models/AccountKeysList.cs +++ b/src/Solnet.Rpc/Models/AccountKeysList.cs @@ -7,7 +7,7 @@ namespace Solnet.Rpc.Models /// A wrapper around a list of s that takes care of deduplication and ordering according to /// the wire format specification. /// - internal class AccountKeysList + public class AccountKeysList { /// /// The account metas list. diff --git a/src/Solnet.Rpc/Models/Block.cs b/src/Solnet.Rpc/Models/Block.cs index d7df487e..bcb49137 100644 --- a/src/Solnet.Rpc/Models/Block.cs +++ b/src/Solnet.Rpc/Models/Block.cs @@ -39,6 +39,11 @@ public class BlockInfo /// public long? BlockHeight { get; set; } + /// + /// Max transaction version allowed + /// + public int maxSupportedTransactionVersion { get; set; } + /// /// The rewards for this given block. /// diff --git a/src/Solnet.Rpc/Models/Message.cs b/src/Solnet.Rpc/Models/Message.cs index 49b4b113..f4a2cd35 100644 --- a/src/Solnet.Rpc/Models/Message.cs +++ b/src/Solnet.Rpc/Models/Message.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.Linq; namespace Solnet.Rpc.Models { @@ -153,6 +154,14 @@ public byte[] Serialize() /// The Message object instance. public static Message Deserialize(ReadOnlySpan data) { + + // Check that the message is not a VersionedMessage + byte prefix = data[0]; + byte maskedPrefix = (byte)(prefix & VersionedMessage.VersionPrefixMask); + if (prefix != maskedPrefix) + throw new NotSupportedException("The message is a VersionedMessage, use VersionedMessage." + + "Deserialize instead."); + // Read message header byte numRequiredSignatures = data[MessageHeader.Layout.RequiredSignaturesOffset]; byte numReadOnlySignedAccounts = data[MessageHeader.Layout.ReadOnlySignedAccountsOffset]; @@ -241,5 +250,244 @@ public static Message Deserialize(string data) return Deserialize(decodedBytes); } + + public class VersionedMessage : Message + { + /// + /// Version prefix Mask. + /// + public const byte VersionPrefixMask = 0x7F; + + /// + /// Address table lookup + /// + public List AddressTableLookups { get; set; } + + + /// + /// Deserialize a compiled message into a Message object. + /// + /// The data to deserialize into the Message object. + /// The Message object instance. + public static new VersionedMessage Deserialize(ReadOnlySpan data) + { + byte prefix = data[0]; + byte maskedPrefix = (byte)(prefix & VersionPrefixMask); + + if (prefix == maskedPrefix) + throw new NotSupportedException("Expected versioned message but received legacy message"); + + byte version = maskedPrefix; + + if (version != 0) + throw new NotSupportedException($"Expected versioned message with version 0 but found version {version}"); + + data = data.Slice(1, data.Length - 1); // Remove the processed prefix byte + + // Read message header + byte numRequiredSignatures = data[MessageHeader.Layout.RequiredSignaturesOffset]; + byte numReadOnlySignedAccounts = data[MessageHeader.Layout.ReadOnlySignedAccountsOffset]; + byte numReadOnlyUnsignedAccounts = data[MessageHeader.Layout.ReadOnlyUnsignedAccountsOffset]; + + // Read account keys + (int accountAddressLength, int accountAddressLengthEncodedLength) = + ShortVectorEncoding.DecodeLength(data.Slice(MessageHeader.Layout.HeaderLength, + ShortVectorEncoding.SpanLength)); + List accountKeys = new(accountAddressLength); + for (int i = 0; i < accountAddressLength; i++) + { + ReadOnlySpan keyBytes = data.Slice( + MessageHeader.Layout.HeaderLength + accountAddressLengthEncodedLength + + i * PublicKey.PublicKeyLength, + PublicKey.PublicKeyLength); + accountKeys.Add(new PublicKey(keyBytes)); + } + + // Read block hash + string blockHash = + Encoders.Base58.EncodeData(data.Slice( + MessageHeader.Layout.HeaderLength + accountAddressLengthEncodedLength + + accountAddressLength * PublicKey.PublicKeyLength, + PublicKey.PublicKeyLength).ToArray()); + + // Read the number of instructions in the message + (int instructionsLength, int instructionsLengthEncodedLength) = + ShortVectorEncoding.DecodeLength( + data.Slice( + MessageHeader.Layout.HeaderLength + accountAddressLengthEncodedLength + + (accountAddressLength * PublicKey.PublicKeyLength) + PublicKey.PublicKeyLength, + ShortVectorEncoding.SpanLength)); + + List instructions = new(instructionsLength); + int instructionsOffset = + MessageHeader.Layout.HeaderLength + accountAddressLengthEncodedLength + + (accountAddressLength * PublicKey.PublicKeyLength) + PublicKey.PublicKeyLength + + instructionsLengthEncodedLength; + ReadOnlySpan instructionsData = data[instructionsOffset..]; + + int instructionsDataLength = 0; + + // Read the instructions in the message + for (int i = 0; i < instructionsLength; i++) + { + (CompiledInstruction compiledInstruction, int instructionLength) = + CompiledInstruction.Deserialize(instructionsData); + instructions.Add(compiledInstruction); + instructionsData = instructionsData[instructionLength..]; + instructionsDataLength += instructionLength; + } + + // Read the address table lookups + int tableLookupOffset = + MessageHeader.Layout.HeaderLength + accountAddressLengthEncodedLength + + (accountAddressLength * PublicKey.PublicKeyLength) + PublicKey.PublicKeyLength + + instructionsLengthEncodedLength + instructionsDataLength; + + ReadOnlySpan tableLookupData = data[tableLookupOffset..]; + + (int addressTableLookupsCount, int addressTableLookupsEncodedCount) = ShortVectorEncoding.DecodeLength(tableLookupData); + List addressTableLookups = new(); + tableLookupData = tableLookupData[addressTableLookupsEncodedCount..]; + + for (int i = 0; i < addressTableLookupsCount; i++) + { + byte[] accountKeyBytes = tableLookupData.Slice(0, PublicKey.PublicKeyLength).ToArray(); + PublicKey accountKey = new(accountKeyBytes); + tableLookupData = tableLookupData.Slice(PublicKey.PublicKeyLength); + + (int writableIndexesLength, int writableIndexesEncodedLength) = ShortVectorEncoding.DecodeLength(tableLookupData); + List writableIndexes = tableLookupData.Slice(writableIndexesEncodedLength, writableIndexesLength).ToArray().ToList(); + tableLookupData = tableLookupData.Slice(writableIndexesEncodedLength + writableIndexesLength); + + (int readonlyIndexesLength, int readonlyIndexesEncodedLength) = ShortVectorEncoding.DecodeLength(tableLookupData); + List readonlyIndexes = tableLookupData.Slice(readonlyIndexesEncodedLength, readonlyIndexesLength).ToArray().ToList(); + tableLookupData = tableLookupData.Slice(readonlyIndexesEncodedLength + readonlyIndexesLength); + + addressTableLookups.Add(new MessageAddressTableLookup + { + AccountKey = accountKey, + WritableIndexes = writableIndexes.ToArray(), + ReadonlyIndexes = readonlyIndexes.ToArray() + }); + } + + return new VersionedMessage() + { + Header = new MessageHeader() + { + RequiredSignatures = numRequiredSignatures, + ReadOnlySignedAccounts = numReadOnlySignedAccounts, + ReadOnlyUnsignedAccounts = numReadOnlyUnsignedAccounts + }, + RecentBlockhash = blockHash, + AccountKeys = accountKeys, + Instructions = instructions, + AddressTableLookups = addressTableLookups + }; + } + + /// + /// Deserialize a compiled message encoded as base-64 into a Message object. + /// + /// The data to deserialize into the Message object. + /// The Transaction object. + /// Thrown when the given string is null. + public static new VersionedMessage Deserialize(string data) + { + if (data == null) + throw new ArgumentNullException(nameof(data)); + + byte[] decodedBytes; + + try + { + decodedBytes = Convert.FromBase64String(data); + } + catch (Exception ex) + { + throw new Exception("could not decode message data from base64", ex); + } + + return Deserialize(decodedBytes); + } + + /// + /// Deserialize the message version + /// + /// + /// + public static string DeserializeMessageVersion(byte[] serializedMessage) + { + byte prefix = serializedMessage[0]; + byte maskedPrefix = (byte)(prefix & VersionPrefixMask); + + // If the highest bit of the prefix is not set, the message is not versioned + if (maskedPrefix == prefix) + { + return "legacy"; + } + + // The lower 7 bits of the prefix indicate the message version + return maskedPrefix.ToString(); + } + } + + /// + /// Message Address Lookup table + /// + public class MessageAddressTableLookup + { + /// + /// Account Key + /// + public PublicKey AccountKey { get; set; } + + /// + /// Writable indexes + /// + public byte[] WritableIndexes { get; set; } + + /// + /// Read only indexes + /// + public byte[] ReadonlyIndexes { get; set; } + } + + /// + /// Message Address Lookup table + /// + public static class AddressTableLookupUtils + { + /// + /// Serialize the address table lookups + /// + /// + /// + public static byte[] SerializeAddressTableLookups(List addressTableLookups) + { + MemoryStream buffer = new(); + + var encodedAddressTableLookupsLength = ShortVectorEncoding.EncodeLength(addressTableLookups.Count); + buffer.Write(encodedAddressTableLookupsLength, 0, encodedAddressTableLookupsLength.Length); + + foreach (var lookup in addressTableLookups) + { + // Write the Account Key + buffer.Write(lookup.AccountKey, 0, PublicKey.PublicKeyLength); + + // Write the Writable Indexes + var encodedWritableIndexesLength = ShortVectorEncoding.EncodeLength(lookup.WritableIndexes.Length); + buffer.Write(encodedWritableIndexesLength, 0, encodedWritableIndexesLength.Length); + buffer.Write(lookup.WritableIndexes, 0, lookup.WritableIndexes.Length); + + // Write the Readonly Indexes + var encodedReadonlyIndexesLength = ShortVectorEncoding.EncodeLength(lookup.ReadonlyIndexes.Length); + buffer.Write(encodedReadonlyIndexesLength, 0, encodedReadonlyIndexesLength.Length); + buffer.Write(lookup.ReadonlyIndexes, 0, lookup.ReadonlyIndexes.Length); + } + + return buffer.ToArray(); + } + } } -} \ No newline at end of file +} diff --git a/src/Solnet.Rpc/Models/Transaction.cs b/src/Solnet.Rpc/Models/Transaction.cs index a433c53e..4e3a3fcd 100644 --- a/src/Solnet.Rpc/Models/Transaction.cs +++ b/src/Solnet.Rpc/Models/Transaction.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using static Solnet.Rpc.Models.Message; namespace Solnet.Rpc.Models { @@ -61,6 +62,8 @@ public class Transaction /// public string RecentBlockHash { get; set; } + + internal IList _accountKeys; /// /// The nonce information of the transaction. /// @@ -81,7 +84,7 @@ public class Transaction /// /// Compile the transaction data. /// - public byte[] CompileMessage() + public virtual byte[] CompileMessage() { MessageBuilder messageBuilder = new() { FeePayer = FeePayer }; @@ -374,6 +377,15 @@ public static Transaction Deserialize(ReadOnlySpan data) signatures.Add(signature.ToArray()); } + + byte prefix = data[encodedLength + (signaturesLength * TransactionBuilder.SignatureLength)]; + byte maskedPrefix = (byte)(prefix & VersionedMessage.VersionPrefixMask); + + // If the transaction is a VersionedTransaction, use VersionedTransaction.Deserialize instead. + if (prefix != maskedPrefix) + return VersionedTransaction.Deserialize(data); + + return Populate( Message.Deserialize(data[ (encodedLength + (signaturesLength * TransactionBuilder.SignatureLength))..]), diff --git a/src/Solnet.Rpc/Models/TransactionInstruction.cs b/src/Solnet.Rpc/Models/TransactionInstruction.cs index 480565d6..e1e52210 100644 --- a/src/Solnet.Rpc/Models/TransactionInstruction.cs +++ b/src/Solnet.Rpc/Models/TransactionInstruction.cs @@ -4,6 +4,16 @@ namespace Solnet.Rpc.Models { + /// + /// Represents a versioned transaction instruction before being compiled into the transaction's message. + /// + public class VersionedTransactionInstruction : TransactionInstruction + { + /// + /// The keys associated with the instruction. + /// + public byte[] KeyIndices { get; init; } + } /// /// Represents a transaction instruction before being compiled into the transaction's message. /// diff --git a/src/Solnet.Rpc/Models/VersionedTransaction.cs b/src/Solnet.Rpc/Models/VersionedTransaction.cs new file mode 100644 index 00000000..0b7424c9 --- /dev/null +++ b/src/Solnet.Rpc/Models/VersionedTransaction.cs @@ -0,0 +1,169 @@ +using Solnet.Rpc.Builders; +using Solnet.Rpc.Utilities; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using static Solnet.Rpc.Models.Message; + +namespace Solnet.Rpc.Models +{ + /// + /// Represents a Transaction in Solana. + /// + public class VersionedTransaction : Transaction + { + + /// + /// Address Table Lookups + /// + public List AddressTableLookups { get; set; } + + + /// + /// Compile the transaction data. + /// + public override byte[] CompileMessage() + { + VersionedMessageBuilder messageBuilder = new() { FeePayer = FeePayer, AccountKeys = _accountKeys }; + + if (RecentBlockHash != null) messageBuilder.RecentBlockHash = RecentBlockHash; + if (NonceInformation != null) messageBuilder.NonceInformation = NonceInformation; + + foreach (TransactionInstruction instruction in Instructions) + { + messageBuilder.AddInstruction(instruction); + } + + messageBuilder.AddressTableLookups = AddressTableLookups; + + return messageBuilder.Build(); + } + + /// + /// Populate the Transaction from the given message and signatures. + /// + /// The object. + /// The list of signatures. + /// The Transaction object. + public static VersionedTransaction Populate(VersionedMessage message, IList signatures = null) + { + VersionedTransaction tx = new() + { + RecentBlockHash = message.RecentBlockhash, + Signatures = new List(), + Instructions = new List(), + AddressTableLookups = message.AddressTableLookups, + _accountKeys = message.AccountKeys + }; + + if (message.Header.RequiredSignatures > 0) + { + tx.FeePayer = message.AccountKeys[0]; + } + + if (signatures != null) + { + for (int i = 0; i < signatures.Count; i++) + { + tx.Signatures.Add(new SignaturePubKeyPair + { + PublicKey = message.AccountKeys[i], + Signature = signatures[i] + }); + } + } + + for (int i = 0; i < message.Instructions.Count; i++) + { + CompiledInstruction compiledInstruction = message.Instructions[i]; + (int accountLength, _) = ShortVectorEncoding.DecodeLength(compiledInstruction.KeyIndicesCount); + + List accounts = new(accountLength); + for (int j = 0; j < accountLength; j++) + { + int k = compiledInstruction.KeyIndices[j]; + if (k >= message.AccountKeys.Count) continue; + accounts.Add(new AccountMeta(message.AccountKeys[k], message.IsAccountWritable(k), + tx.Signatures.Any(pair => pair.PublicKey.Key == message.AccountKeys[k].Key) || message.IsAccountSigner(k))); + } + + VersionedTransactionInstruction instruction = new() + { + Keys = accounts, + KeyIndices = compiledInstruction.KeyIndices, + ProgramId = message.AccountKeys[compiledInstruction.ProgramIdIndex], + Data = compiledInstruction.Data + }; + if (i == 0 && accounts.Any(a => a.PublicKey == "SysvarRecentB1ockHashes11111111111111111111")) + { + tx.NonceInformation = new NonceInformation { Instruction = instruction, Nonce = tx.RecentBlockHash }; + continue; + } + tx.Instructions.Add(instruction); + } + + return tx; + } + + /// + /// Populate the Transaction from the given compiled message and signatures. + /// + /// The compiled message, as base-64 encoded string. + /// The list of signatures. + /// The Transaction object. + public static new VersionedTransaction Populate(string message, IList signatures = null) + => Populate(VersionedMessage.Deserialize(message), signatures); + + /// + /// Deserialize a wire format transaction into a Transaction object. + /// + /// The data to deserialize into the Transaction object. + /// The Transaction object. + public static new VersionedTransaction Deserialize(ReadOnlySpan data) + { + // Read number of signatures + (int signaturesLength, int encodedLength) = + ShortVectorEncoding.DecodeLength(data[..ShortVectorEncoding.SpanLength]); + List signatures = new(signaturesLength); + + for (int i = 0; i < signaturesLength; i++) + { + ReadOnlySpan signature = + data.Slice(encodedLength + (i * TransactionBuilder.SignatureLength), + TransactionBuilder.SignatureLength); + signatures.Add(signature.ToArray()); + } + + var message = VersionedMessage.Deserialize(data[(encodedLength + (signaturesLength * TransactionBuilder.SignatureLength))..]); + return Populate(message, signatures); + } + + /// + /// Deserialize a transaction encoded as base-64 into a Transaction object. + /// + /// The data to deserialize into the Transaction object. + /// The Transaction object. + /// Thrown when the given string is null. + public static new VersionedTransaction Deserialize(string data) + { + if (data == null) + throw new ArgumentNullException(nameof(data)); + + byte[] decodedBytes; + + try + { + decodedBytes = Convert.FromBase64String(data); + } + catch (Exception ex) + { + throw new Exception("could not decode transaction data from base64", ex); + } + + return Deserialize(decodedBytes); + } + } +} + diff --git a/src/Solnet.Rpc/SolanaRpcClient.cs b/src/Solnet.Rpc/SolanaRpcClient.cs index b24a1235..af53de30 100644 --- a/src/Solnet.Rpc/SolanaRpcClient.cs +++ b/src/Solnet.Rpc/SolanaRpcClient.cs @@ -211,7 +211,7 @@ public RequestResult> GetBalance(string pubKey, public async Task> GetBlockAsync(ulong slot, Commitment commitment = Commitment.Finalized, TransactionDetailsFilterType transactionDetails = TransactionDetailsFilterType.Full, - bool blockRewards = false) + bool blockRewards = false, int maxSupportedTransactionVersion = 0) { if (commitment == Commitment.Processed) { @@ -221,6 +221,7 @@ public async Task> GetBlockAsync(ulong slot, return await SendRequestAsync("getBlock", Parameters.Create(slot, ConfigObject.Create( KeyValue.Create("encoding", "json"), + KeyValue.Create("maxSupportedTransactionVersion", maxSupportedTransactionVersion), HandleTransactionDetails(transactionDetails), KeyValue.Create("rewards", blockRewards ? blockRewards : null), HandleCommitment(commitment)))); @@ -229,8 +230,8 @@ public async Task> GetBlockAsync(ulong slot, /// public RequestResult GetBlock(ulong slot, Commitment commitment = Commitment.Finalized, TransactionDetailsFilterType transactionDetails = TransactionDetailsFilterType.Full, - bool blockRewards = false) - => GetBlockAsync(slot, commitment, transactionDetails, blockRewards).Result; + bool blockRewards = false, int maxSupportedTransactionVersion = 0) + => GetBlockAsync(slot, commitment, transactionDetails, blockRewards, maxSupportedTransactionVersion).Result; /// @@ -251,7 +252,7 @@ public async Task>> GetBlocksAsync(ulong startSlot, ul public async Task> GetConfirmedBlockAsync(ulong slot, Commitment commitment = Commitment.Finalized, TransactionDetailsFilterType transactionDetails = TransactionDetailsFilterType.Full, - bool blockRewards = false) + bool blockRewards = false, int maxSupportedTransactionVersion = 0) { if (commitment == Commitment.Processed) { @@ -261,6 +262,7 @@ public async Task> GetConfirmedBlockAsync(ulong slot, return await SendRequestAsync("getConfirmedBlock", Parameters.Create(slot, ConfigObject.Create( KeyValue.Create("encoding", "json"), + KeyValue.Create("maxSupportedTransactionVersion", maxSupportedTransactionVersion), HandleTransactionDetails(transactionDetails), KeyValue.Create("rewards", blockRewards ? blockRewards : null), HandleCommitment(commitment)))); @@ -269,7 +271,7 @@ public async Task> GetConfirmedBlockAsync(ulong slot, /// public RequestResult GetConfirmedBlock(ulong slot, Commitment commitment = Commitment.Finalized, TransactionDetailsFilterType transactionDetails = TransactionDetailsFilterType.Full, - bool blockRewards = false) + bool blockRewards = false, int maxSupportedTransactionVersion = 0) => GetConfirmedBlockAsync(slot, commitment, transactionDetails, blockRewards).Result; @@ -425,31 +427,31 @@ public async Task>>> GetLeaderSched /// public async Task> GetTransactionAsync(string signature, - Commitment commitment = Commitment.Finalized) + Commitment commitment = Commitment.Finalized, int maxSupportedTransactionVersion = 0) { return await SendRequestAsync("getTransaction", Parameters.Create(signature, - ConfigObject.Create(KeyValue.Create("encoding", "json"), HandleCommitment(commitment)))); + ConfigObject.Create(KeyValue.Create("encoding", "json"), HandleCommitment(commitment), KeyValue.Create("maxSupportedTransactionVersion", maxSupportedTransactionVersion)))); } - /// + /// public async Task> GetConfirmedTransactionAsync(string signature, - Commitment commitment = Commitment.Finalized) + Commitment commitment = Commitment.Finalized, int maxSupportedTransactionVersion = 0) { return await SendRequestAsync("getConfirmedTransaction", Parameters.Create(signature, - ConfigObject.Create(KeyValue.Create("encoding", "json"), HandleCommitment(commitment)))); + ConfigObject.Create(KeyValue.Create("encoding", "json"), HandleCommitment(commitment), KeyValue.Create("maxSupportedTransactionVersion", maxSupportedTransactionVersion)))); } /// public RequestResult GetTransaction(string signature, - Commitment commitment = Commitment.Finalized) - => GetTransactionAsync(signature, commitment).Result; + Commitment commitment = Commitment.Finalized, int maxSupportedTransactionVersion = 0) + => GetTransactionAsync(signature, commitment, maxSupportedTransactionVersion).Result; - /// + /// public RequestResult GetConfirmedTransaction(string signature, - Commitment commitment = Commitment.Finalized) => - GetConfirmedTransactionAsync(signature, commitment).Result; + Commitment commitment = Commitment.Finalized, int maxSupportedTransactionVersion = 0) => + GetConfirmedTransactionAsync(signature, commitment, maxSupportedTransactionVersion).Result; /// public async Task> GetBlockHeightAsync(Commitment commitment = Commitment.Finalized) diff --git a/src/Solnet.Rpc/Solnet.Rpc.csproj b/src/Solnet.Rpc/Solnet.Rpc.csproj index 0136dedc..0780042a 100644 --- a/src/Solnet.Rpc/Solnet.Rpc.csproj +++ b/src/Solnet.Rpc/Solnet.Rpc.csproj @@ -1,7 +1,7 @@  - net6.0 + net8.0 Solnet.Rpc Solnet.Rpc diff --git a/src/Solnet.Wallet/Account.cs b/src/Solnet.Wallet/Account.cs index ddadfb2e..4e8ffde2 100644 --- a/src/Solnet.Wallet/Account.cs +++ b/src/Solnet.Wallet/Account.cs @@ -1,5 +1,6 @@ using Chaos.NaCl; using Solnet.Wallet.Utilities; +using System; using System.Diagnostics; namespace Solnet.Wallet @@ -50,7 +51,23 @@ public Account(byte[] privateKey, byte[] publicKey) PrivateKey = new PrivateKey(privateKey); PublicKey = new PublicKey(publicKey); } + /// + /// Initialize an account with the passed secret key + /// + /// The private key. + public static Account FromSecretKey(string secretKey) + { + var B58 = new Base58Encoder(); + byte[] skeyBytes = B58.DecodeData(secretKey); + if (skeyBytes.Length != 64) + { + throw new ArgumentException("Not a secret key"); + } + Account acc = new Account(skeyBytes, skeyBytes.Slice(32, 64)); + + return acc; + } /// /// Verify the signed message. /// diff --git a/test/Solnet.Extensions.Test/Solnet.Extensions.Test.csproj b/test/Solnet.Extensions.Test/Solnet.Extensions.Test.csproj index ff579957..5a17f6d0 100644 --- a/test/Solnet.Extensions.Test/Solnet.Extensions.Test.csproj +++ b/test/Solnet.Extensions.Test/Solnet.Extensions.Test.csproj @@ -1,7 +1,7 @@  - net6.0 + net8.0 false diff --git a/test/Solnet.KeyStore.Test/Solnet.KeyStore.Test.csproj b/test/Solnet.KeyStore.Test/Solnet.KeyStore.Test.csproj index ac340158..7b6212e5 100644 --- a/test/Solnet.KeyStore.Test/Solnet.KeyStore.Test.csproj +++ b/test/Solnet.KeyStore.Test/Solnet.KeyStore.Test.csproj @@ -1,7 +1,7 @@ - net6.0 + net8.0 false diff --git a/test/Solnet.Programs.Test/Solnet.Programs.Test.csproj b/test/Solnet.Programs.Test/Solnet.Programs.Test.csproj index 1a6ae3c6..e26cddd6 100644 --- a/test/Solnet.Programs.Test/Solnet.Programs.Test.csproj +++ b/test/Solnet.Programs.Test/Solnet.Programs.Test.csproj @@ -1,7 +1,7 @@  - net6.0 + net8.0 false diff --git a/test/Solnet.Rpc.Test/Resources/Http/Blocks/GetBlockConfirmedRequest.json b/test/Solnet.Rpc.Test/Resources/Http/Blocks/GetBlockConfirmedRequest.json index a7f4a162..9ba5920a 100644 --- a/test/Solnet.Rpc.Test/Resources/Http/Blocks/GetBlockConfirmedRequest.json +++ b/test/Solnet.Rpc.Test/Resources/Http/Blocks/GetBlockConfirmedRequest.json @@ -1 +1 @@ -{"method":"getBlock","params":[79662905,{"encoding":"json","commitment":"confirmed"}],"jsonrpc":"2.0","id":0} \ No newline at end of file +{"method":"getBlock","params":[79662905,{"encoding":"json","maxSupportedTransactionVersion":0,"commitment":"confirmed"}],"jsonrpc":"2.0","id":0} \ No newline at end of file diff --git a/test/Solnet.Rpc.Test/Resources/Http/Blocks/GetBlockRequest.json b/test/Solnet.Rpc.Test/Resources/Http/Blocks/GetBlockRequest.json index 938aad92..a96d097e 100644 --- a/test/Solnet.Rpc.Test/Resources/Http/Blocks/GetBlockRequest.json +++ b/test/Solnet.Rpc.Test/Resources/Http/Blocks/GetBlockRequest.json @@ -1 +1 @@ -{"method":"getBlock","params":[79662905,{"encoding":"json"}],"jsonrpc":"2.0","id":0} \ No newline at end of file +{"method":"getBlock","params":[79662905,{"encoding":"json","maxSupportedTransactionVersion":0}],"jsonrpc":"2.0","id":0} \ No newline at end of file diff --git a/test/Solnet.Rpc.Test/Resources/Http/Blocks/GetConfirmedBlockConfirmedRequest.json b/test/Solnet.Rpc.Test/Resources/Http/Blocks/GetConfirmedBlockConfirmedRequest.json index aa11cfe0..37835143 100644 --- a/test/Solnet.Rpc.Test/Resources/Http/Blocks/GetConfirmedBlockConfirmedRequest.json +++ b/test/Solnet.Rpc.Test/Resources/Http/Blocks/GetConfirmedBlockConfirmedRequest.json @@ -1 +1 @@ -{"method":"getConfirmedBlock","params":[79662905,{"encoding":"json","commitment":"confirmed"}],"jsonrpc":"2.0","id":0} \ No newline at end of file +{"method":"getConfirmedBlock","params":[79662905,{"encoding":"json","maxSupportedTransactionVersion":0,"commitment":"confirmed"}],"jsonrpc":"2.0","id":0} \ No newline at end of file diff --git a/test/Solnet.Rpc.Test/Resources/Http/Blocks/GetConfirmedBlockRequest.json b/test/Solnet.Rpc.Test/Resources/Http/Blocks/GetConfirmedBlockRequest.json index cc090029..d7d6d32c 100644 --- a/test/Solnet.Rpc.Test/Resources/Http/Blocks/GetConfirmedBlockRequest.json +++ b/test/Solnet.Rpc.Test/Resources/Http/Blocks/GetConfirmedBlockRequest.json @@ -1 +1 @@ -{"method":"getConfirmedBlock","params":[79662905,{"encoding":"json"}],"jsonrpc":"2.0","id":0} \ No newline at end of file +{"method":"getConfirmedBlock","params":[79662905,{"encoding":"json","maxSupportedTransactionVersion":0}],"jsonrpc":"2.0","id":0} \ No newline at end of file diff --git a/test/Solnet.Rpc.Test/Resources/Http/Transaction/GetConfirmedTransactionProcessedRequest.json b/test/Solnet.Rpc.Test/Resources/Http/Transaction/GetConfirmedTransactionProcessedRequest.json index 62c95d8c..66cf3975 100644 --- a/test/Solnet.Rpc.Test/Resources/Http/Transaction/GetConfirmedTransactionProcessedRequest.json +++ b/test/Solnet.Rpc.Test/Resources/Http/Transaction/GetConfirmedTransactionProcessedRequest.json @@ -1 +1 @@ -{"method":"getConfirmedTransaction","params":["5as3w4KMpY23MP5T1nkPVksjXjN7hnjHKqiDxRMxUNcw5XsCGtStayZib1kQdyR2D9w8dR11Ha9Xk38KP3kbAwM1",{"encoding":"json","commitment":"processed"}],"jsonrpc":"2.0","id":0} \ No newline at end of file +{"method":"getConfirmedTransaction","params":["5as3w4KMpY23MP5T1nkPVksjXjN7hnjHKqiDxRMxUNcw5XsCGtStayZib1kQdyR2D9w8dR11Ha9Xk38KP3kbAwM1",{"encoding":"json","commitment":"processed","maxSupportedTransactionVersion":0}],"jsonrpc":"2.0","id":0} \ No newline at end of file diff --git a/test/Solnet.Rpc.Test/Resources/Http/Transaction/GetConfirmedTransactionRequest.json b/test/Solnet.Rpc.Test/Resources/Http/Transaction/GetConfirmedTransactionRequest.json index b5b11244..8d531c08 100644 --- a/test/Solnet.Rpc.Test/Resources/Http/Transaction/GetConfirmedTransactionRequest.json +++ b/test/Solnet.Rpc.Test/Resources/Http/Transaction/GetConfirmedTransactionRequest.json @@ -1 +1 @@ -{"method":"getConfirmedTransaction","params":["5as3w4KMpY23MP5T1nkPVksjXjN7hnjHKqiDxRMxUNcw5XsCGtStayZib1kQdyR2D9w8dR11Ha9Xk38KP3kbAwM1",{"encoding":"json","commitment":"confirmed"}],"jsonrpc":"2.0","id":0} \ No newline at end of file +{"method":"getConfirmedTransaction","params":["5as3w4KMpY23MP5T1nkPVksjXjN7hnjHKqiDxRMxUNcw5XsCGtStayZib1kQdyR2D9w8dR11Ha9Xk38KP3kbAwM1",{"encoding":"json","commitment":"confirmed","maxSupportedTransactionVersion":0}],"jsonrpc":"2.0","id":0} \ No newline at end of file diff --git a/test/Solnet.Rpc.Test/Resources/Http/Transaction/GetTransactionProcessedRequest.json b/test/Solnet.Rpc.Test/Resources/Http/Transaction/GetTransactionProcessedRequest.json index 872a41f3..0a81d834 100644 --- a/test/Solnet.Rpc.Test/Resources/Http/Transaction/GetTransactionProcessedRequest.json +++ b/test/Solnet.Rpc.Test/Resources/Http/Transaction/GetTransactionProcessedRequest.json @@ -1 +1 @@ -{"method":"getTransaction","params":["5as3w4KMpY23MP5T1nkPVksjXjN7hnjHKqiDxRMxUNcw5XsCGtStayZib1kQdyR2D9w8dR11Ha9Xk38KP3kbAwM1",{"encoding":"json","commitment":"processed"}],"jsonrpc":"2.0","id":0} \ No newline at end of file +{"method":"getTransaction","params":["5as3w4KMpY23MP5T1nkPVksjXjN7hnjHKqiDxRMxUNcw5XsCGtStayZib1kQdyR2D9w8dR11Ha9Xk38KP3kbAwM1",{"encoding":"json","commitment":"processed","maxSupportedTransactionVersion":0}],"jsonrpc":"2.0","id":0} \ No newline at end of file diff --git a/test/Solnet.Rpc.Test/Resources/Http/Transaction/GetTransactionRequest.json b/test/Solnet.Rpc.Test/Resources/Http/Transaction/GetTransactionRequest.json index 230a7558..76400a00 100644 --- a/test/Solnet.Rpc.Test/Resources/Http/Transaction/GetTransactionRequest.json +++ b/test/Solnet.Rpc.Test/Resources/Http/Transaction/GetTransactionRequest.json @@ -1 +1 @@ -{"method":"getTransaction","params":["5as3w4KMpY23MP5T1nkPVksjXjN7hnjHKqiDxRMxUNcw5XsCGtStayZib1kQdyR2D9w8dR11Ha9Xk38KP3kbAwM1",{"encoding":"json"}],"jsonrpc":"2.0","id":0} \ No newline at end of file +{"method":"getTransaction","params":["5as3w4KMpY23MP5T1nkPVksjXjN7hnjHKqiDxRMxUNcw5XsCGtStayZib1kQdyR2D9w8dR11Ha9Xk38KP3kbAwM1",{"encoding":"json","maxSupportedTransactionVersion":0}],"jsonrpc":"2.0","id":0} \ No newline at end of file diff --git a/test/Solnet.Rpc.Test/Resources/Http/Transaction/GetTransactionRequest2.json b/test/Solnet.Rpc.Test/Resources/Http/Transaction/GetTransactionRequest2.json index 43b9b7a7..cd04da7b 100644 --- a/test/Solnet.Rpc.Test/Resources/Http/Transaction/GetTransactionRequest2.json +++ b/test/Solnet.Rpc.Test/Resources/Http/Transaction/GetTransactionRequest2.json @@ -1 +1 @@ -{"method":"getTransaction","params":["3Q9mu4ePvtbtQzY1kpGmaViJKyBev6hgUppyXDF9hKgWHHnecwGLE2pSoFvNUF3h7acKyFwWd65bkwr9A1jN2CdT",{"encoding":"json"}],"jsonrpc":"2.0","id":0} \ No newline at end of file +{"method":"getTransaction","params":["3Q9mu4ePvtbtQzY1kpGmaViJKyBev6hgUppyXDF9hKgWHHnecwGLE2pSoFvNUF3h7acKyFwWd65bkwr9A1jN2CdT",{"encoding":"json","maxSupportedTransactionVersion":0}],"jsonrpc":"2.0","id":0} \ No newline at end of file diff --git a/test/Solnet.Rpc.Test/SolanaRpcClientBlockTests.cs b/test/Solnet.Rpc.Test/SolanaRpcClientBlockTests.cs index f69f2bc2..76c766a4 100644 --- a/test/Solnet.Rpc.Test/SolanaRpcClientBlockTests.cs +++ b/test/Solnet.Rpc.Test/SolanaRpcClientBlockTests.cs @@ -113,7 +113,7 @@ public void TestGetConfirmedBlock() Assert.AreEqual(79662904UL, res.Result.ParentSlot); Assert.AreEqual("5wLhsKAH9SCPbRZc4qWf3GBiod9CD8sCEZfMiU25qW8", res.Result.Blockhash); Assert.AreEqual("CjJ97j84mUq3o67CEqzEkTifXpHLBCD8GvmfBYLz4Zdg", res.Result.PreviousBlockhash); - + Assert.AreEqual(0, res.Result.maxSupportedTransactionVersion); Assert.AreEqual(1, res.Result.Rewards.Length); var rewards = res.Result.Rewards[0]; diff --git a/test/Solnet.Rpc.Test/Solnet.Rpc.Test.csproj b/test/Solnet.Rpc.Test/Solnet.Rpc.Test.csproj index 97c76f60..d9cd4ef9 100644 --- a/test/Solnet.Rpc.Test/Solnet.Rpc.Test.csproj +++ b/test/Solnet.Rpc.Test/Solnet.Rpc.Test.csproj @@ -1,7 +1,7 @@  - net6.0 + net8.0 false diff --git a/test/Solnet.Wallet.Test/Solnet.Wallet.Test.csproj b/test/Solnet.Wallet.Test/Solnet.Wallet.Test.csproj index e74856d9..aee271d8 100644 --- a/test/Solnet.Wallet.Test/Solnet.Wallet.Test.csproj +++ b/test/Solnet.Wallet.Test/Solnet.Wallet.Test.csproj @@ -1,7 +1,7 @@ - net6.0 + net8.0 false