solana-ruby-kit 7.1.1.1 → 8.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +24 -0
- data/lib/solana/ruby/kit/errors.rb +8 -0
- data/lib/solana/ruby/kit/functional.rb +1 -1
- data/lib/solana/ruby/kit/instruction_plans/instruction_plan.rb +13 -1
- data/lib/solana/ruby/kit/instruction_plans/transaction_plan.rb +9 -0
- data/lib/solana/ruby/kit/instruction_plans/transaction_plan_executor.rb +117 -2
- data/lib/solana/ruby/kit/instruction_plans/transaction_plan_result.rb +18 -0
- data/lib/solana/ruby/kit/instruction_plans/transaction_planner.rb +24 -2
- data/lib/solana/ruby/kit/rpc/api/get_account_info.rb +5 -0
- data/lib/solana/ruby/kit/rpc/api/get_ag_genesis_cert.rb +72 -0
- data/lib/solana/ruby/kit/rpc/api/get_balance.rb +4 -0
- data/lib/solana/ruby/kit/rpc/api/get_block_height.rb +5 -0
- data/lib/solana/ruby/kit/rpc/api/get_block_time.rb +5 -0
- data/lib/solana/ruby/kit/rpc/api/get_epoch_info.rb +5 -0
- data/lib/solana/ruby/kit/rpc/api/get_epoch_schedule.rb +5 -0
- data/lib/solana/ruby/kit/rpc/api/get_inflation_reward.rb +6 -1
- data/lib/solana/ruby/kit/rpc/api/get_latest_blockhash.rb +4 -0
- data/lib/solana/ruby/kit/rpc/api/get_minimum_balance_for_rent_exemption.rb +5 -0
- data/lib/solana/ruby/kit/rpc/api/get_multiple_accounts.rb +5 -0
- data/lib/solana/ruby/kit/rpc/api/get_program_accounts.rb +5 -0
- data/lib/solana/ruby/kit/rpc/api/get_signature_statuses.rb +4 -0
- data/lib/solana/ruby/kit/rpc/api/get_signatures_for_address.rb +5 -0
- data/lib/solana/ruby/kit/rpc/api/get_slot.rb +5 -0
- data/lib/solana/ruby/kit/rpc/api/get_token_account_balance.rb +5 -0
- data/lib/solana/ruby/kit/rpc/api/get_token_accounts_by_owner.rb +5 -0
- data/lib/solana/ruby/kit/rpc/api/get_token_supply.rb +43 -0
- data/lib/solana/ruby/kit/rpc/api/get_transaction.rb +5 -0
- data/lib/solana/ruby/kit/rpc/api/get_transactions_for_address.rb +5 -0
- data/lib/solana/ruby/kit/rpc/api/get_vote_accounts.rb +5 -0
- data/lib/solana/ruby/kit/rpc/api/has_transport.rb +25 -0
- data/lib/solana/ruby/kit/rpc/api/is_blockhash_valid.rb +4 -0
- data/lib/solana/ruby/kit/rpc/api/request_airdrop.rb +4 -0
- data/lib/solana/ruby/kit/rpc/api/send_transaction.rb +5 -0
- data/lib/solana/ruby/kit/rpc/api/simulate_transaction.rb +5 -0
- data/lib/solana/ruby/kit/rpc/client.rb +8 -1
- data/lib/solana/ruby/kit/rpc_subscriptions/api/account_notifications.rb +5 -0
- data/lib/solana/ruby/kit/rpc_subscriptions/api/has_transport.rb +35 -0
- data/lib/solana/ruby/kit/rpc_subscriptions/api/logs_notifications.rb +5 -0
- data/lib/solana/ruby/kit/rpc_subscriptions/api/program_notifications.rb +5 -0
- data/lib/solana/ruby/kit/rpc_subscriptions/api/root_notifications.rb +5 -0
- data/lib/solana/ruby/kit/rpc_subscriptions/api/signature_notifications.rb +5 -0
- data/lib/solana/ruby/kit/rpc_subscriptions/api/slot_notifications.rb +5 -0
- data/lib/solana/ruby/kit/rpc_subscriptions/client.rb +5 -2
- data/lib/solana/ruby/kit/rpc_types/sol.rb +8 -6
- data/lib/solana/ruby/kit/subscribable/reactive_action_store.rb +6 -6
- data/lib/solana/ruby/kit/subscribable/reactive_stream_store.rb +4 -4
- data/lib/solana/ruby/kit/transaction_messages/compute_budget.rb +6 -4
- data/lib/solana/ruby/kit/transaction_messages/resource_limit_validation.rb +70 -0
- data/lib/solana/ruby/kit/transaction_messages.rb +1 -0
- data/lib/solana/ruby/kit/version.rb +1 -1
- data/lib/solana/ruby/kit/wallet_standard.rb +4 -4
- metadata +6 -3
- data/lib/core_extensions/tapioca/name_patch.rb +0 -32
- data/lib/core_extensions/tapioca/required_ancestors.rb +0 -13
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ba7d2659d83b01af6565c7af483364d61e61fcc9761cbca9d63d8d41f6b4d73a
|
|
4
|
+
data.tar.gz: bd8402f845e9d8db5845be0e358676d00e308b2a0777afc1973e8814175e1c1c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 589ade790ebb62b97f9905e8fe98113efc93deb5a6fad37239d377ab427915838eaa3d461644d213944ee8b80796134b28d5a6da43529a3920bb5037940a20a1
|
|
7
|
+
data.tar.gz: 3c03649c3b4314b93e7321f74b77150cd19bd775535b8431c14b678fdf35d3e7d4703fac43f341c3480ec5031b0c78bd0c0e3b7e385bb01f1822fc57fa5afaec
|
data/README.md
CHANGED
|
@@ -189,9 +189,17 @@ msg = TxMsg.append_instructions(msg, [instruction])
|
|
|
189
189
|
msg = TxMsg.prepend_instructions(msg, [priority_fee_ix])
|
|
190
190
|
|
|
191
191
|
# Compute unit limit (SetComputeUnitLimit instruction from the Compute Budget program)
|
|
192
|
+
# The limit is validated: it must be an integer in [0, MAX_COMPUTE_UNIT_LIMIT] (1,400,000).
|
|
193
|
+
# The runtime silently clamps a larger request rather than failing, so asking for more than
|
|
194
|
+
# the maximum would run on a budget you did not ask for — this raises instead.
|
|
192
195
|
msg = TxMsg.set_transaction_message_compute_unit_limit(200_000, msg)
|
|
193
196
|
TxMsg.get_transaction_message_compute_unit_limit(msg) # => 200_000
|
|
194
197
|
|
|
198
|
+
# The check is also available on its own, alongside one for heap frame sizes — a whole
|
|
199
|
+
# number of KiB in [MIN_HEAP_SIZE, MAX_HEAP_SIZE] (32 KiB to 256 KiB).
|
|
200
|
+
TxMsg.assert_is_valid_compute_unit_limit(200_000)
|
|
201
|
+
TxMsg.assert_is_valid_heap_size(64 * 1024)
|
|
202
|
+
|
|
195
203
|
# Loaded accounts data size limit
|
|
196
204
|
msg = TxMsg.set_transaction_message_loaded_accounts_data_size_limit(64_000, msg)
|
|
197
205
|
TxMsg.get_transaction_message_loaded_accounts_data_size_limit(msg) # => 64_000
|
|
@@ -274,6 +282,7 @@ rpc.get_block_time(slot)
|
|
|
274
282
|
rpc.get_inflation_reward([address])
|
|
275
283
|
rpc.get_signatures_for_address(address)
|
|
276
284
|
rpc.get_vote_accounts
|
|
285
|
+
rpc.get_ag_genesis_cert # Alpenglow genesis cert, or nil
|
|
277
286
|
rpc.simulate_transaction(encoded_tx)
|
|
278
287
|
rpc.send_transaction(encoded_tx)
|
|
279
288
|
rpc.request_airdrop(address, lamports) # devnet / testnet only
|
|
@@ -640,6 +649,21 @@ result = executor.call(transaction_plan)
|
|
|
640
649
|
# A one-argument lambda returning { transaction:, context: } — the shape this method
|
|
641
650
|
# required before v7.1.0 — is still accepted and adapted onto the flow above.
|
|
642
651
|
|
|
652
|
+
# ── 3b. Executing every leaf concurrently ────────────────────────────────────
|
|
653
|
+
# For work that has no execution dependencies — signing or serializing, say —
|
|
654
|
+
# create_transaction_plan_executor_with_concurrent_leaves takes the same callback
|
|
655
|
+
# contract but starts every leaf immediately, each on its own thread. It preserves
|
|
656
|
+
# the plan's nesting, order and divisibility in the result, supports non-divisible
|
|
657
|
+
# sequential plans, and cancels nothing: a leaf that raises does not stop the others,
|
|
658
|
+
# so every leaf runs to completion before the failure is reported. Because the leaves
|
|
659
|
+
# run at the same time, the callback must be safe to call concurrently with itself.
|
|
660
|
+
concurrent = Plans.create_transaction_plan_executor_with_concurrent_leaves(
|
|
661
|
+
execute_transaction_message: ->(_context, message) {
|
|
662
|
+
{ transaction: Kit::Transactions.compile_transaction_message(message) }
|
|
663
|
+
}
|
|
664
|
+
)
|
|
665
|
+
result = concurrent.call(transaction_plan)
|
|
666
|
+
|
|
643
667
|
# ── 4. Plan types ─────────────────────────────────────────────────────────────
|
|
644
668
|
Plans.single_instruction_plan(ix) # wrap one instruction
|
|
645
669
|
Plans.sequential_instruction_plan([ix1, ix2]) # ordered, divisible
|
|
@@ -60,6 +60,10 @@ module Solana::Ruby::Kit
|
|
|
60
60
|
TRANSACTION__FAILED_TO_DECOMPILE_INSTRUCTION_ACCOUNT_INDEX_OUT_OF_RANGE = :SOLANA_ERROR__TRANSACTION__FAILED_TO_DECOMPILE_INSTRUCTION_ACCOUNT_INDEX_OUT_OF_RANGE
|
|
61
61
|
# context: { unsupported_version: }
|
|
62
62
|
TRANSACTION__VERSION_NUMBER_NOT_SUPPORTED = :SOLANA_ERROR__TRANSACTION__VERSION_NUMBER_NOT_SUPPORTED
|
|
63
|
+
# context: { compute_unit_limit:, max_compute_unit_limit: }
|
|
64
|
+
TRANSACTION__COMPUTE_UNIT_LIMIT_OUT_OF_RANGE = :SOLANA_ERROR__TRANSACTION__COMPUTE_UNIT_LIMIT_OUT_OF_RANGE
|
|
65
|
+
# context: { heap_size:, max_heap_size:, min_heap_size:, multiple_of: }
|
|
66
|
+
TRANSACTION__INVALID_HEAP_SIZE = :SOLANA_ERROR__TRANSACTION__INVALID_HEAP_SIZE
|
|
63
67
|
TRANSACTIONS__SEND_TRANSACTION_PREFLIGHT_FAILURE = :SOLANA_ERROR__TRANSACTIONS__SEND_TRANSACTION_PREFLIGHT_FAILURE
|
|
64
68
|
TRANSACTIONS__BLOCKHASH_NOT_FOUND = :SOLANA_ERROR__TRANSACTIONS__BLOCKHASH_NOT_FOUND
|
|
65
69
|
TRANSACTIONS__FAILED_TRANSACTION_PLAN = :SOLANA_ERROR__TRANSACTIONS__FAILED_TRANSACTION_PLAN
|
|
@@ -147,6 +151,7 @@ module Solana::Ruby::Kit
|
|
|
147
151
|
|
|
148
152
|
# ── Fixed-points ──────────────────────────────────────────────────────────
|
|
149
153
|
FIXED_POINTS__STRICT_MODE_PRECISION_LOSS = :SOLANA_ERROR__FIXED_POINTS__STRICT_MODE_PRECISION_LOSS
|
|
154
|
+
LAMPORTS_OUT_OF_RANGE = :SOLANA_ERROR__LAMPORTS_OUT_OF_RANGE
|
|
150
155
|
|
|
151
156
|
# ── Invariant violations (internal) ──────────────────────────────────────
|
|
152
157
|
INVARIANT_VIOLATION__SUBSCRIPTION_ITERATOR_STATE_MISSING = :SOLANA_ERROR__INVARIANT_VIOLATION__SUBSCRIPTION_ITERATOR_STATE_MISSING
|
|
@@ -206,6 +211,8 @@ module Solana::Ruby::Kit
|
|
|
206
211
|
TRANSACTIONS__FAILED_TO_DECOMPILE_INSTRUCTION_PROGRAM_ADDRESS_NOT_FOUND => 'Failed to decompile instruction: program address not found at index %{index}',
|
|
207
212
|
TRANSACTION__FAILED_TO_DECOMPILE_INSTRUCTION_ACCOUNT_INDEX_OUT_OF_RANGE => 'Failed to decompile instruction: account index %{index} is out of range',
|
|
208
213
|
TRANSACTION__VERSION_NUMBER_NOT_SUPPORTED => 'Transaction version %{unsupported_version} is not supported',
|
|
214
|
+
TRANSACTION__COMPUTE_UNIT_LIMIT_OUT_OF_RANGE => 'Transaction compute unit limit must be an integer in the range [0, %{max_compute_unit_limit}]. `%{compute_unit_limit}` given',
|
|
215
|
+
TRANSACTION__INVALID_HEAP_SIZE => 'Transaction heap size must be an integer multiple of %{multiple_of} bytes in the range [%{min_heap_size}, %{max_heap_size}]. `%{heap_size}` given',
|
|
209
216
|
TRANSACTIONS__SEND_TRANSACTION_PREFLIGHT_FAILURE => 'Transaction simulation failed: %{message}',
|
|
210
217
|
TRANSACTIONS__BLOCKHASH_NOT_FOUND => 'Blockhash not found',
|
|
211
218
|
TRANSACTIONS__FAILED_TRANSACTION_PLAN => 'Failed to execute transaction plan',
|
|
@@ -286,6 +293,7 @@ module Solana::Ruby::Kit
|
|
|
286
293
|
|
|
287
294
|
# Fixed-points
|
|
288
295
|
FIXED_POINTS__STRICT_MODE_PRECISION_LOSS => 'Value has more than 9 fractional digits and cannot be represented exactly as a Sol amount',
|
|
296
|
+
LAMPORTS_OUT_OF_RANGE => 'Lamports value out of range; must fit in an unsigned 64-bit integer',
|
|
289
297
|
|
|
290
298
|
# Wallet Standard
|
|
291
299
|
WALLET_STANDARD__INVALID_WIRE_FORMAT => 'Invalid transaction wire format: %{reason}',
|
|
@@ -21,7 +21,7 @@ module Solana::Ruby::Kit
|
|
|
21
21
|
# ->(tx) { Solana::Ruby::Kit::TransactionMessages.set_fee_payer(fee_payer_address, tx) },
|
|
22
22
|
# ->(tx) { Solana::Ruby::Kit::TransactionMessages.set_blockhash_lifetime(blockhash, tx) },
|
|
23
23
|
# )
|
|
24
|
-
sig { params(value: T.untyped, fns: T
|
|
24
|
+
sig { params(value: T.untyped, fns: T.proc.params(arg0: T.untyped).returns(T.untyped)).returns(T.untyped) }
|
|
25
25
|
def pipe(value, *fns)
|
|
26
26
|
fns.reduce(value) { |acc, fn| T.unsafe(fn).call(acc) }
|
|
27
27
|
end
|
|
@@ -23,7 +23,10 @@ module Solana::Ruby::Kit
|
|
|
23
23
|
|
|
24
24
|
# A plan that wraps a single instruction.
|
|
25
25
|
class SingleInstructionPlan < T::Struct
|
|
26
|
+
extend T::Sig
|
|
27
|
+
|
|
26
28
|
const :instruction, Instructions::Instruction
|
|
29
|
+
sig { returns(Symbol) }
|
|
27
30
|
def kind = :single
|
|
28
31
|
end
|
|
29
32
|
|
|
@@ -31,21 +34,30 @@ module Solana::Ruby::Kit
|
|
|
31
34
|
# +divisible: true+ allows the planner to split children across transactions.
|
|
32
35
|
# +divisible: false+ means all children should be atomic (one tx or a bundle).
|
|
33
36
|
class SequentialInstructionPlan < T::Struct
|
|
37
|
+
extend T::Sig
|
|
38
|
+
|
|
34
39
|
const :plans, T::Array[T.untyped] # Array[InstructionPlan]
|
|
35
40
|
const :divisible, T::Boolean
|
|
41
|
+
sig { returns(Symbol) }
|
|
36
42
|
def kind = :sequential
|
|
37
43
|
end
|
|
38
44
|
|
|
39
45
|
# A plan whose children may execute concurrently or be packed in any order.
|
|
40
46
|
class ParallelInstructionPlan < T::Struct
|
|
47
|
+
extend T::Sig
|
|
48
|
+
|
|
41
49
|
const :plans, T::Array[T.untyped] # Array[InstructionPlan]
|
|
50
|
+
sig { returns(Symbol) }
|
|
42
51
|
def kind = :parallel
|
|
43
52
|
end
|
|
44
53
|
|
|
45
54
|
# Provides a MessagePacker that dynamically packs instructions into messages.
|
|
46
55
|
# The +get_message_packer+ proc returns a fresh MessagePacker each call.
|
|
47
56
|
class MessagePackerInstructionPlan < T::Struct
|
|
57
|
+
extend T::Sig
|
|
58
|
+
|
|
48
59
|
const :get_message_packer, T.untyped # Proc -> MessagePacker
|
|
60
|
+
sig { returns(Symbol) }
|
|
49
61
|
def kind = :message_packer
|
|
50
62
|
end
|
|
51
63
|
|
|
@@ -199,7 +211,7 @@ module Solana::Ruby::Kit
|
|
|
199
211
|
return packed
|
|
200
212
|
end
|
|
201
213
|
|
|
202
|
-
next_packed = TransactionMessages.append_instructions(packed, [instructions[i]])
|
|
214
|
+
next_packed = TransactionMessages.append_instructions(packed, [T.must(instructions[i])])
|
|
203
215
|
size = Transactions.get_transaction_message_size(next_packed)
|
|
204
216
|
|
|
205
217
|
if size > Transactions::TRANSACTION_SIZE_LIMIT
|
|
@@ -17,18 +17,27 @@ module Solana::Ruby::Kit
|
|
|
17
17
|
# | ParallelTransactionPlan
|
|
18
18
|
|
|
19
19
|
class SingleTransactionPlan < T::Struct
|
|
20
|
+
extend T::Sig
|
|
21
|
+
|
|
20
22
|
const :message, TransactionMessages::TransactionMessage
|
|
23
|
+
sig { returns(Symbol) }
|
|
21
24
|
def kind = :single
|
|
22
25
|
end
|
|
23
26
|
|
|
24
27
|
class SequentialTransactionPlan < T::Struct
|
|
28
|
+
extend T::Sig
|
|
29
|
+
|
|
25
30
|
const :plans, T::Array[T.untyped] # Array[TransactionPlan]
|
|
26
31
|
const :divisible, T::Boolean
|
|
32
|
+
sig { returns(Symbol) }
|
|
27
33
|
def kind = :sequential
|
|
28
34
|
end
|
|
29
35
|
|
|
30
36
|
class ParallelTransactionPlan < T::Struct
|
|
37
|
+
extend T::Sig
|
|
38
|
+
|
|
31
39
|
const :plans, T::Array[T.untyped] # Array[TransactionPlan]
|
|
40
|
+
sig { returns(Symbol) }
|
|
32
41
|
def kind = :parallel
|
|
33
42
|
end
|
|
34
43
|
|
|
@@ -9,6 +9,8 @@ module Solana::Ruby::Kit
|
|
|
9
9
|
module InstructionPlans
|
|
10
10
|
extend T::Sig
|
|
11
11
|
|
|
12
|
+
module_function
|
|
13
|
+
|
|
12
14
|
# Creates a TransactionPlanExecutor — a callable that traverses a TransactionPlan,
|
|
13
15
|
# executing each single transaction message and collecting results.
|
|
14
16
|
#
|
|
@@ -48,7 +50,7 @@ module Solana::Ruby::Kit
|
|
|
48
50
|
)
|
|
49
51
|
# Store the full result tree as a non-enumerable attribute for recovery.
|
|
50
52
|
err.instance_variable_set(:@transaction_plan_result, result)
|
|
51
|
-
err.define_singleton_method(:transaction_plan_result) {
|
|
53
|
+
err.define_singleton_method(:transaction_plan_result) { result }
|
|
52
54
|
Kernel.raise err
|
|
53
55
|
end
|
|
54
56
|
|
|
@@ -56,12 +58,63 @@ module Solana::Ruby::Kit
|
|
|
56
58
|
}
|
|
57
59
|
end
|
|
58
60
|
|
|
59
|
-
|
|
61
|
+
# Creates a TransactionPlanExecutor that processes every leaf concurrently.
|
|
62
|
+
#
|
|
63
|
+
# It takes the same configuration as +create_transaction_plan_executor+ and its
|
|
64
|
+
# +execute_transaction_message+ callback follows the same contract: it receives a fresh
|
|
65
|
+
# mutable +context+ Hash for every leaf and returns the Hash a successful result should
|
|
66
|
+
# carry. On success the two are merged with the returned value winning; when the callback
|
|
67
|
+
# raises, the context accumulated so far is preserved on the failed result. The legacy
|
|
68
|
+
# one-argument callable shape is accepted here too.
|
|
69
|
+
#
|
|
70
|
+
# The difference is the traversal. This executor preserves the input plan's nesting, order
|
|
71
|
+
# and divisibility in the returned result, but it does not enforce the execution
|
|
72
|
+
# dependencies expressed by sequential plans: every leaf is started immediately, each on
|
|
73
|
+
# its own thread, with no concurrency limit. That makes it suitable for work that can be
|
|
74
|
+
# performed independently, such as signing or serializing transactions. For the same
|
|
75
|
+
# reason a callback that raises does not cancel the other leaves — each one runs to
|
|
76
|
+
# completion — and non-divisible sequential plans are supported rather than rejected.
|
|
77
|
+
#
|
|
78
|
+
# Once every leaf has settled, a result tree containing any failure raises
|
|
79
|
+
# INSTRUCTION_PLANS__FAILED_TO_EXECUTE_TRANSACTION_PLAN carrying the complete tree on its
|
|
80
|
+
# +transaction_plan_result+ reader.
|
|
81
|
+
#
|
|
82
|
+
# Because each leaf runs on its own thread, the callback must be safe to run concurrently
|
|
83
|
+
# with itself. Each leaf gets its own context Hash, so the contexts do not need guarding,
|
|
84
|
+
# but anything the callback shares between invocations does.
|
|
85
|
+
#
|
|
86
|
+
# Mirrors `createTransactionPlanExecutorWithConcurrentLeaves(config)` from
|
|
87
|
+
# @solana/instruction-plans.
|
|
88
|
+
# NOTE: Ruby translation has no abort signal; leaves are never canceled, only failed.
|
|
89
|
+
sig { params(execute_transaction_message: T.untyped).returns(T.untyped) }
|
|
90
|
+
def create_transaction_plan_executor_with_concurrent_leaves(execute_transaction_message:)
|
|
91
|
+
->(transaction_plan) {
|
|
92
|
+
# Traversal spawns every leaf's thread on the way down and returns a tree of thunks;
|
|
93
|
+
# resolving them afterwards is what joins the threads, so all leaves are already in
|
|
94
|
+
# flight before the first one is waited on.
|
|
95
|
+
result = concurrent_executor_traverse(transaction_plan, execute_transaction_message).call
|
|
96
|
+
|
|
97
|
+
cause = executor_find_error(result)
|
|
98
|
+
if cause
|
|
99
|
+
err = SolanaError.new(
|
|
100
|
+
SolanaError::INSTRUCTION_PLANS__FAILED_TO_EXECUTE_TRANSACTION_PLAN,
|
|
101
|
+
{ cause: cause }
|
|
102
|
+
)
|
|
103
|
+
err.instance_variable_set(:@transaction_plan_result, result)
|
|
104
|
+
err.define_singleton_method(:transaction_plan_result) { result }
|
|
105
|
+
Kernel.raise err
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
result
|
|
109
|
+
}
|
|
110
|
+
end
|
|
60
111
|
|
|
61
112
|
# ── Private helpers ────────────────────────────────────────────────────────
|
|
62
113
|
|
|
63
114
|
module_function
|
|
64
115
|
|
|
116
|
+
sig { params(plan: T.untyped, execute_fn: T.untyped, state: T.untyped).returns(T.untyped) }
|
|
117
|
+
|
|
65
118
|
def executor_traverse(plan, execute_fn, state)
|
|
66
119
|
case plan.kind
|
|
67
120
|
when :sequential then executor_traverse_sequential(plan, execute_fn, state)
|
|
@@ -75,6 +128,8 @@ module Solana::Ruby::Kit
|
|
|
75
128
|
end
|
|
76
129
|
end
|
|
77
130
|
|
|
131
|
+
sig { params(plan: T.untyped, execute_fn: T.untyped, state: T.untyped).returns(T.untyped) }
|
|
132
|
+
|
|
78
133
|
def executor_traverse_sequential(plan, execute_fn, state)
|
|
79
134
|
results = plan.plans.map { |sub| executor_traverse(sub, execute_fn, state) }
|
|
80
135
|
plan.divisible \
|
|
@@ -82,11 +137,15 @@ module Solana::Ruby::Kit
|
|
|
82
137
|
: non_divisible_sequential_transaction_plan_result(results)
|
|
83
138
|
end
|
|
84
139
|
|
|
140
|
+
sig { params(plan: T.untyped, execute_fn: T.untyped, state: T.untyped).returns(T.untyped) }
|
|
141
|
+
|
|
85
142
|
def executor_traverse_parallel(plan, execute_fn, state)
|
|
86
143
|
results = plan.plans.map { |sub| executor_traverse(sub, execute_fn, state) }
|
|
87
144
|
parallel_transaction_plan_result(results)
|
|
88
145
|
end
|
|
89
146
|
|
|
147
|
+
sig { params(plan: T.untyped, execute_fn: T.untyped, state: T.untyped).returns(T.untyped) }
|
|
148
|
+
|
|
90
149
|
def executor_traverse_single(plan, execute_fn, state)
|
|
91
150
|
# A fresh context per single transaction plan. Nothing is populated yet — filling
|
|
92
151
|
# it in is the callback's job, which is what lets a partial context survive an error.
|
|
@@ -137,6 +196,60 @@ module Solana::Ruby::Kit
|
|
|
137
196
|
returned
|
|
138
197
|
end
|
|
139
198
|
|
|
199
|
+
# Returns a thunk that resolves to this plan's result. Leaf threads are started as the
|
|
200
|
+
# tree is walked, so the whole plan is in flight by the time the outermost thunk is called.
|
|
201
|
+
sig { params(plan: T.untyped, execute_fn: T.untyped).returns(T.untyped) }
|
|
202
|
+
|
|
203
|
+
def concurrent_executor_traverse(plan, execute_fn)
|
|
204
|
+
case plan.kind
|
|
205
|
+
when :single
|
|
206
|
+
thread = Thread.new { concurrent_executor_execute_single(plan, execute_fn) }
|
|
207
|
+
-> { thread.value }
|
|
208
|
+
when :sequential, :parallel
|
|
209
|
+
thunks = plan.plans.map { |sub| concurrent_executor_traverse(sub, execute_fn) }
|
|
210
|
+
Kernel.lambda do
|
|
211
|
+
results = thunks.map(&:call)
|
|
212
|
+
if plan.kind == :parallel
|
|
213
|
+
parallel_transaction_plan_result(results)
|
|
214
|
+
elsif plan.divisible
|
|
215
|
+
sequential_transaction_plan_result(results)
|
|
216
|
+
else
|
|
217
|
+
# Unlike the sequential executor, non-divisible plans are supported: nothing
|
|
218
|
+
# about running every leaf independently depends on the plan being divisible.
|
|
219
|
+
non_divisible_sequential_transaction_plan_result(results)
|
|
220
|
+
end
|
|
221
|
+
end
|
|
222
|
+
else
|
|
223
|
+
Kernel.raise SolanaError.new(
|
|
224
|
+
SolanaError::INVARIANT_VIOLATION__INVALID_TRANSACTION_PLAN_KIND,
|
|
225
|
+
{ kind: plan.kind }
|
|
226
|
+
)
|
|
227
|
+
end
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
# Runs one leaf. Identical to +executor_traverse_single+ except that a failure is recorded
|
|
231
|
+
# and nothing else — there is no shared cancellation state for it to trip.
|
|
232
|
+
sig { params(plan: T.untyped, execute_fn: T.untyped).returns(T.untyped) }
|
|
233
|
+
|
|
234
|
+
def concurrent_executor_execute_single(plan, execute_fn)
|
|
235
|
+
context = {}
|
|
236
|
+
|
|
237
|
+
begin
|
|
238
|
+
returned = executor_invoke(execute_fn, context, plan.message)
|
|
239
|
+
successful_single_transaction_plan_result(plan.message, nil, context.merge(returned))
|
|
240
|
+
rescue SolanaError => e
|
|
241
|
+
failed_single_transaction_plan_result(plan.message, e, context)
|
|
242
|
+
rescue => e
|
|
243
|
+
wrapped = SolanaError.new(
|
|
244
|
+
SolanaError::INSTRUCTION_PLANS__FAILED_TO_EXECUTE_TRANSACTION_PLAN,
|
|
245
|
+
{ cause: e }
|
|
246
|
+
)
|
|
247
|
+
failed_single_transaction_plan_result(plan.message, wrapped, context)
|
|
248
|
+
end
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
sig { params(result: T.untyped).returns(T.untyped) }
|
|
252
|
+
|
|
140
253
|
def executor_find_error(result)
|
|
141
254
|
return result.status.error if result.kind == :single && result.status.kind == :failed
|
|
142
255
|
return nil if result.kind == :single
|
|
@@ -154,5 +267,7 @@ module Solana::Ruby::Kit
|
|
|
154
267
|
private_class_method :executor_traverse_single
|
|
155
268
|
private_class_method :executor_invoke
|
|
156
269
|
private_class_method :executor_find_error
|
|
270
|
+
private_class_method :concurrent_executor_traverse
|
|
271
|
+
private_class_method :concurrent_executor_execute_single
|
|
157
272
|
end
|
|
158
273
|
end
|
|
@@ -21,38 +21,56 @@ module Solana::Ruby::Kit
|
|
|
21
21
|
# the result. Mirrors TypeScript's TransactionPlanResultStatus union.
|
|
22
22
|
|
|
23
23
|
class SuccessfulStatus < T::Struct
|
|
24
|
+
extend T::Sig
|
|
25
|
+
|
|
24
26
|
const :transaction, T.nilable(Transactions::Transaction)
|
|
25
27
|
const :context, T::Hash[T.untyped, T.untyped]
|
|
28
|
+
sig { returns(Symbol) }
|
|
26
29
|
def kind = :successful
|
|
27
30
|
end
|
|
28
31
|
|
|
29
32
|
class FailedStatus < T::Struct
|
|
33
|
+
extend T::Sig
|
|
34
|
+
|
|
30
35
|
const :error, SolanaError
|
|
31
36
|
const :context, T::Hash[T.untyped, T.untyped], default: {}.freeze
|
|
37
|
+
sig { returns(Symbol) }
|
|
32
38
|
def kind = :failed
|
|
33
39
|
end
|
|
34
40
|
|
|
35
41
|
class CanceledStatus < T::Struct
|
|
42
|
+
extend T::Sig
|
|
43
|
+
|
|
36
44
|
const :context, T::Hash[T.untyped, T.untyped], default: {}.freeze
|
|
45
|
+
sig { returns(Symbol) }
|
|
37
46
|
def kind = :canceled
|
|
38
47
|
end
|
|
39
48
|
|
|
40
49
|
# ── Result plan types ──────────────────────────────────────────────────────
|
|
41
50
|
|
|
42
51
|
class SingleTransactionPlanResult < T::Struct
|
|
52
|
+
extend T::Sig
|
|
53
|
+
|
|
43
54
|
const :message, TransactionMessages::TransactionMessage
|
|
44
55
|
const :status, T.untyped # SuccessfulStatus | FailedStatus | CanceledStatus
|
|
56
|
+
sig { returns(Symbol) }
|
|
45
57
|
def kind = :single
|
|
46
58
|
end
|
|
47
59
|
|
|
48
60
|
class SequentialTransactionPlanResult < T::Struct
|
|
61
|
+
extend T::Sig
|
|
62
|
+
|
|
49
63
|
const :plans, T::Array[T.untyped] # Array[TransactionPlanResult]
|
|
50
64
|
const :divisible, T::Boolean
|
|
65
|
+
sig { returns(Symbol) }
|
|
51
66
|
def kind = :sequential
|
|
52
67
|
end
|
|
53
68
|
|
|
54
69
|
class ParallelTransactionPlanResult < T::Struct
|
|
70
|
+
extend T::Sig
|
|
71
|
+
|
|
55
72
|
const :plans, T::Array[T.untyped] # Array[TransactionPlanResult]
|
|
73
|
+
sig { returns(Symbol) }
|
|
56
74
|
def kind = :parallel
|
|
57
75
|
end
|
|
58
76
|
|
|
@@ -11,6 +11,8 @@ module Solana::Ruby::Kit
|
|
|
11
11
|
module InstructionPlans
|
|
12
12
|
extend T::Sig
|
|
13
13
|
|
|
14
|
+
module_function
|
|
15
|
+
|
|
14
16
|
# Creates a TransactionPlanner — a callable that converts an InstructionPlan
|
|
15
17
|
# into a TransactionPlan by packing instructions into transaction messages.
|
|
16
18
|
#
|
|
@@ -67,12 +69,12 @@ module Solana::Ruby::Kit
|
|
|
67
69
|
}
|
|
68
70
|
end
|
|
69
71
|
|
|
70
|
-
module_function :create_transaction_planner
|
|
71
|
-
|
|
72
72
|
# ── Private helpers ────────────────────────────────────────────────────────
|
|
73
73
|
|
|
74
74
|
module_function
|
|
75
75
|
|
|
76
|
+
sig { params(plan: T.untyped, ctx: T.untyped).returns(T.untyped) }
|
|
77
|
+
|
|
76
78
|
def planner_traverse(plan, ctx)
|
|
77
79
|
case plan.kind
|
|
78
80
|
when :sequential then planner_traverse_sequential(plan, ctx)
|
|
@@ -87,6 +89,8 @@ module Solana::Ruby::Kit
|
|
|
87
89
|
end
|
|
88
90
|
end
|
|
89
91
|
|
|
92
|
+
sig { params(plan: T.untyped, ctx: T.untyped).returns(T.untyped) }
|
|
93
|
+
|
|
90
94
|
def planner_traverse_sequential(plan, ctx)
|
|
91
95
|
must_fit_in_parent = ctx[:parent] && (ctx[:parent].kind == :parallel || !plan.divisible)
|
|
92
96
|
|
|
@@ -118,6 +122,8 @@ module Solana::Ruby::Kit
|
|
|
118
122
|
{ kind: :sequential, divisible: plan.divisible, plans: tx_plans }
|
|
119
123
|
end
|
|
120
124
|
|
|
125
|
+
sig { params(plan: T.untyped, ctx: T.untyped).returns(T.untyped) }
|
|
126
|
+
|
|
121
127
|
def planner_traverse_parallel(plan, ctx)
|
|
122
128
|
candidates = ctx[:parent_candidates].dup
|
|
123
129
|
tx_plans = []
|
|
@@ -139,6 +145,8 @@ module Solana::Ruby::Kit
|
|
|
139
145
|
{ kind: :parallel, plans: tx_plans }
|
|
140
146
|
end
|
|
141
147
|
|
|
148
|
+
sig { params(plan: T.untyped, ctx: T.untyped).returns(T.untyped) }
|
|
149
|
+
|
|
142
150
|
def planner_traverse_single(plan, ctx)
|
|
143
151
|
predicate = ->(msg) { TransactionMessages.append_instructions(msg, [plan.instruction]) }
|
|
144
152
|
candidate = planner_select_and_mutate_candidate(ctx, ctx[:parent_candidates], &predicate)
|
|
@@ -148,6 +156,8 @@ module Solana::Ruby::Kit
|
|
|
148
156
|
{ kind: :single, message: msg }
|
|
149
157
|
end
|
|
150
158
|
|
|
159
|
+
sig { params(plan: T.untyped, ctx: T.untyped).returns(T.untyped) }
|
|
160
|
+
|
|
151
161
|
def planner_traverse_message_packer(plan, ctx)
|
|
152
162
|
packer = plan.get_message_packer.call
|
|
153
163
|
tx_plans = []
|
|
@@ -175,6 +185,8 @@ module Solana::Ruby::Kit
|
|
|
175
185
|
end
|
|
176
186
|
end
|
|
177
187
|
|
|
188
|
+
sig { params(ctx: T.untyped, candidates: T.untyped, predicate: T.proc.params(arg0: T.untyped).returns(T.untyped)).returns(T.untyped) }
|
|
189
|
+
|
|
178
190
|
def planner_select_and_mutate_candidate(ctx, candidates, &predicate)
|
|
179
191
|
candidates.each do |candidate|
|
|
180
192
|
begin
|
|
@@ -198,6 +210,8 @@ module Solana::Ruby::Kit
|
|
|
198
210
|
nil
|
|
199
211
|
end
|
|
200
212
|
|
|
213
|
+
sig { params(ctx: T.untyped, predicate: T.proc.params(arg0: T.untyped).returns(T.untyped)).returns(T.untyped) }
|
|
214
|
+
|
|
201
215
|
def planner_create_new_message(ctx, &predicate)
|
|
202
216
|
new_msg = ctx[:create_transaction_message].call
|
|
203
217
|
updated_msg = ctx[:on_transaction_message_updated].call(predicate.call(new_msg))
|
|
@@ -220,6 +234,8 @@ module Solana::Ruby::Kit
|
|
|
220
234
|
updated_msg
|
|
221
235
|
end
|
|
222
236
|
|
|
237
|
+
sig { params(plan: T.untyped, message: T.untyped, ctx: T.untyped).returns(T.untyped) }
|
|
238
|
+
|
|
223
239
|
def planner_fit_entire_plan(plan, message, ctx)
|
|
224
240
|
case plan.kind
|
|
225
241
|
when :sequential, :parallel
|
|
@@ -259,17 +275,23 @@ module Solana::Ruby::Kit
|
|
|
259
275
|
end
|
|
260
276
|
end
|
|
261
277
|
|
|
278
|
+
sig { params(plan: T.untyped).returns(T.untyped) }
|
|
279
|
+
|
|
262
280
|
def planner_get_sequential_candidate(plan)
|
|
263
281
|
return plan if plan[:kind] == :single
|
|
264
282
|
return nil unless plan[:kind] == :sequential && plan[:plans]&.any?
|
|
265
283
|
planner_get_sequential_candidate(plan[:plans].last)
|
|
266
284
|
end
|
|
267
285
|
|
|
286
|
+
sig { params(plan: T.untyped).returns(T.untyped) }
|
|
287
|
+
|
|
268
288
|
def planner_get_parallel_candidates(plan)
|
|
269
289
|
return [plan] if plan[:kind] == :single
|
|
270
290
|
(plan[:plans] || []).flat_map { |p| planner_get_parallel_candidates(p) }
|
|
271
291
|
end
|
|
272
292
|
|
|
293
|
+
sig { params(plan: T.untyped).returns(T.untyped) }
|
|
294
|
+
|
|
273
295
|
def planner_freeze(plan)
|
|
274
296
|
case plan[:kind]
|
|
275
297
|
when :single
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# typed: strict
|
|
2
2
|
# frozen_string_literal: true
|
|
3
3
|
|
|
4
|
+
require_relative 'has_transport'
|
|
5
|
+
|
|
4
6
|
require 'base64'
|
|
5
7
|
require_relative '../../rpc_types/account_info'
|
|
6
8
|
|
|
@@ -16,6 +18,9 @@ module Solana::Ruby::Kit
|
|
|
16
18
|
# (nil when the account does not exist)
|
|
17
19
|
module GetAccountInfo
|
|
18
20
|
extend T::Sig
|
|
21
|
+
extend T::Helpers
|
|
22
|
+
|
|
23
|
+
requires_ancestor { HasTransport }
|
|
19
24
|
|
|
20
25
|
SUPPORTED_ENCODINGS = T.let(%w[base64 jsonParsed base64+zstd].freeze, T::Array[String])
|
|
21
26
|
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require_relative 'has_transport'
|
|
5
|
+
|
|
6
|
+
module Solana::Ruby::Kit
|
|
7
|
+
module Rpc
|
|
8
|
+
module Api
|
|
9
|
+
# The block an Alpenglow genesis certificate certifies.
|
|
10
|
+
AgGenesisCertBlock = T.let(
|
|
11
|
+
Struct.new(
|
|
12
|
+
:block_id, # Array<Integer> — block id of the certified block, as a 32-byte array
|
|
13
|
+
:slot, # Integer — slot of the certified block
|
|
14
|
+
keyword_init: true
|
|
15
|
+
),
|
|
16
|
+
T.untyped
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
# The aggregate signature over an Alpenglow genesis certificate.
|
|
20
|
+
AgGenesisCertSignature = T.let(
|
|
21
|
+
Struct.new(
|
|
22
|
+
:bitmap, # Array<Integer> — ranks of the validators in the aggregate signature
|
|
23
|
+
:signature, # Array<Integer> — the 192-byte aggregate BLS signature
|
|
24
|
+
keyword_init: true
|
|
25
|
+
),
|
|
26
|
+
T.untyped
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
# The Alpenglow genesis certificate, as gossiped between validators.
|
|
30
|
+
# Mirrors the `WireBlockCertMessage` type in the Agave validator.
|
|
31
|
+
AgGenesisCert = T.let(
|
|
32
|
+
Struct.new(
|
|
33
|
+
:block, # AgGenesisCertBlock
|
|
34
|
+
:signature, # AgGenesisCertSignature
|
|
35
|
+
keyword_init: true
|
|
36
|
+
),
|
|
37
|
+
T.untyped
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
# Returns the Alpenglow genesis certificate — the certificate over the block at which the
|
|
41
|
+
# Alpenglow consensus protocol was activated — or nil if the node does not have one.
|
|
42
|
+
# Mirrors TypeScript's GetAgGenesisCertApi.getAgGenesisCert.
|
|
43
|
+
# @see https://solana.com/docs/rpc/http/getaggenesiscert
|
|
44
|
+
module GetAgGenesisCert
|
|
45
|
+
extend T::Sig
|
|
46
|
+
extend T::Helpers
|
|
47
|
+
|
|
48
|
+
requires_ancestor { HasTransport }
|
|
49
|
+
|
|
50
|
+
sig { returns(T.untyped) }
|
|
51
|
+
def get_ag_genesis_cert
|
|
52
|
+
raw = transport.request('getAgGenesisCert', [])
|
|
53
|
+
return nil if raw.nil?
|
|
54
|
+
|
|
55
|
+
block = raw['block'] || {}
|
|
56
|
+
signature = raw['signature'] || {}
|
|
57
|
+
|
|
58
|
+
AgGenesisCert.new(
|
|
59
|
+
block: AgGenesisCertBlock.new(
|
|
60
|
+
block_id: Kernel.Array(block['blockId']).map { |b| Kernel.Integer(b) },
|
|
61
|
+
slot: Kernel.Integer(block['slot'])
|
|
62
|
+
),
|
|
63
|
+
signature: AgGenesisCertSignature.new(
|
|
64
|
+
bitmap: Kernel.Array(signature['bitmap']).map { |b| Kernel.Integer(b) },
|
|
65
|
+
signature: Kernel.Array(signature['signature']).map { |b| Kernel.Integer(b) }
|
|
66
|
+
)
|
|
67
|
+
)
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# typed: strict
|
|
2
2
|
# frozen_string_literal: true
|
|
3
3
|
|
|
4
|
+
require_relative 'has_transport'
|
|
4
5
|
require_relative '../../rpc_types/account_info'
|
|
5
6
|
|
|
6
7
|
module Solana::Ruby::Kit
|
|
@@ -14,6 +15,9 @@ module Solana::Ruby::Kit
|
|
|
14
15
|
# .value — Integer (lamports)
|
|
15
16
|
module GetBalance
|
|
16
17
|
extend T::Sig
|
|
18
|
+
extend T::Helpers
|
|
19
|
+
|
|
20
|
+
requires_ancestor { HasTransport }
|
|
17
21
|
|
|
18
22
|
sig do
|
|
19
23
|
params(
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# typed: strict
|
|
2
2
|
# frozen_string_literal: true
|
|
3
3
|
|
|
4
|
+
require_relative 'has_transport'
|
|
5
|
+
|
|
4
6
|
module Solana::Ruby::Kit
|
|
5
7
|
module Rpc
|
|
6
8
|
module Api
|
|
@@ -8,6 +10,9 @@ module Solana::Ruby::Kit
|
|
|
8
10
|
# Mirrors TypeScript's `GetBlockHeightApi.getBlockHeight(config?)`.
|
|
9
11
|
module GetBlockHeight
|
|
10
12
|
extend T::Sig
|
|
13
|
+
extend T::Helpers
|
|
14
|
+
|
|
15
|
+
requires_ancestor { HasTransport }
|
|
11
16
|
|
|
12
17
|
sig do
|
|
13
18
|
params(
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# typed: strict
|
|
2
2
|
# frozen_string_literal: true
|
|
3
3
|
|
|
4
|
+
require_relative 'has_transport'
|
|
5
|
+
|
|
4
6
|
module Solana::Ruby::Kit
|
|
5
7
|
module Rpc
|
|
6
8
|
module Api
|
|
@@ -8,6 +10,9 @@ module Solana::Ruby::Kit
|
|
|
8
10
|
# Mirrors TypeScript's GetBlockTimeApi.getBlockTime.
|
|
9
11
|
module GetBlockTime
|
|
10
12
|
extend T::Sig
|
|
13
|
+
extend T::Helpers
|
|
14
|
+
|
|
15
|
+
requires_ancestor { HasTransport }
|
|
11
16
|
|
|
12
17
|
sig { params(slot: Integer).returns(T.nilable(Integer)) }
|
|
13
18
|
def get_block_time(slot)
|