Module: Solana::Ruby::Kit::InstructionPlans

Extended by:
T::Sig
Defined in:
lib/solana/ruby/kit/instruction_plans/instruction_plan.rb,
lib/solana/ruby/kit/instruction_plans/transaction_plan.rb,
lib/solana/ruby/kit/instruction_plans/transaction_planner.rb,
lib/solana/ruby/kit/instruction_plans/transaction_plan_result.rb,
lib/solana/ruby/kit/instruction_plans/transaction_plan_executor.rb

Defined Under Namespace

Classes: CanceledStatus, FailedStatus, MessagePacker, MessagePackerInstructionPlan, ParallelInstructionPlan, ParallelTransactionPlan, ParallelTransactionPlanResult, SequentialInstructionPlan, SequentialTransactionPlan, SequentialTransactionPlanResult, SingleInstructionPlan, SingleTransactionPlan, SingleTransactionPlanResult, SuccessfulStatus

Class Method Summary collapse

Class Method Details

.canceled_single_transaction_plan_result(message) ⇒ Object



110
111
112
113
114
115
# File 'lib/solana/ruby/kit/instruction_plans/transaction_plan_result.rb', line 110

def canceled_single_transaction_plan_result(message)
  SingleTransactionPlanResult.new(
    message: message,
    status:  CanceledStatus.new
  )
end

.create_transaction_plan_executor(execute_transaction_message:) ⇒ Object



27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
# File 'lib/solana/ruby/kit/instruction_plans/transaction_plan_executor.rb', line 27

def create_transaction_plan_executor(execute_transaction_message:)
  ->(transaction_plan) {
    state  = { canceled: false }
    result = executor_traverse(transaction_plan, execute_transaction_message, state)

    if state[:canceled]
      cause = executor_find_error(result)
      err   = SolanaError.new(
        SolanaError::INSTRUCTION_PLANS__FAILED_TO_EXECUTE_TRANSACTION_PLAN,
        { cause: cause }
      )
      # Store the full result tree as a non-enumerable attribute for recovery.
      err.instance_variable_set(:@transaction_plan_result, result)
      err.define_singleton_method(:transaction_plan_result) { @transaction_plan_result }
      Kernel.raise err
    end

    result
  }
end

.create_transaction_planner(create_transaction_message:, on_transaction_message_updated: ->(msg) { msg }) ⇒ Object



36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
# File 'lib/solana/ruby/kit/instruction_plans/transaction_planner.rb', line 36

def create_transaction_planner(
  create_transaction_message:,
  on_transaction_message_updated: ->(msg) { msg }
)
  ctx = {
    create_transaction_message:     create_transaction_message,
    on_transaction_message_updated: on_transaction_message_updated
  }

  ->(instruction_plan) {
    mutable = planner_traverse(instruction_plan, ctx.merge(parent: nil, parent_candidates: []))
    Kernel.raise SolanaError.new(SolanaError::INSTRUCTION_PLANS__EMPTY_INSTRUCTION_PLAN) unless mutable
    planner_freeze(mutable)
  }
end

.failed_single_transaction_plan_result(message, error) ⇒ Object



99
100
101
102
103
104
# File 'lib/solana/ruby/kit/instruction_plans/transaction_plan_result.rb', line 99

def failed_single_transaction_plan_result(message, error)
  SingleTransactionPlanResult.new(
    message: message,
    status:  FailedStatus.new(error: error)
  )
end

.flatten_instruction_plan(plan) ⇒ Object



227
228
229
230
231
232
233
234
235
236
237
238
# File 'lib/solana/ruby/kit/instruction_plans/instruction_plan.rb', line 227

def flatten_instruction_plan(plan)
  case plan
  when SingleInstructionPlan
    [plan.instruction]
  when SequentialInstructionPlan, ParallelInstructionPlan
    plan.plans.flat_map { |p| flatten_instruction_plan(p) }
  when MessagePackerInstructionPlan
    Kernel.raise ArgumentError, 'Cannot flatten a MessagePackerInstructionPlan (instructions are dynamically generated)'
  else
    Kernel.raise ArgumentError, "Unknown InstructionPlan type: #{plan.class}"
  end
end

.get_all_single_transaction_plans(plan) ⇒ Object



70
71
72
73
74
75
76
77
78
79
# File 'lib/solana/ruby/kit/instruction_plans/transaction_plan.rb', line 70

def get_all_single_transaction_plans(plan)
  case plan
  when SingleTransactionPlan
    [plan]
  when SequentialTransactionPlan, ParallelTransactionPlan
    plan.plans.flat_map { |p| get_all_single_transaction_plans(p) }
  else
    Kernel.raise ArgumentError, "Unknown TransactionPlan type: #{plan.class}"
  end
end

.get_linear_message_packer_instruction_plan(total_length:, get_instruction:) ⇒ Object



119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
# File 'lib/solana/ruby/kit/instruction_plans/instruction_plan.rb', line 119

def get_linear_message_packer_instruction_plan(total_length:, get_instruction:)
  MessagePackerInstructionPlan.new(
    get_message_packer: -> {
      offset = T.let(0, Integer)
      MessagePacker.new(
        done_proc: -> { offset >= total_length },
        pack_proc:  ->(message) {
          if offset >= total_length
            Kernel.raise SolanaError.new(SolanaError::INSTRUCTION_PLANS__MESSAGE_PACKER_ALREADY_COMPLETE)
          end

          base_ix    = get_instruction.call(offset, 0)
          with_base  = TransactionMessages.append_instructions(message, [base_ix])
          base_size  = Transactions.get_transaction_message_size(with_base)
          # -1 leeway for compact-u16 headers
          free_space = Transactions::TRANSACTION_SIZE_LIMIT - base_size - 1

          if free_space <= 0
            msg_size = Transactions.get_transaction_message_size(message)
            Kernel.raise SolanaError.new(
              SolanaError::INSTRUCTION_PLANS__MESSAGE_CANNOT_ACCOMMODATE_PLAN,
              {
                num_bytes_required: base_size - msg_size + 1,
                num_free_bytes:     Transactions::TRANSACTION_SIZE_LIMIT - msg_size - 1
              }
            )
          end

          length = [total_length - offset, free_space].min
          ix     = get_instruction.call(offset, length)
          offset += length
          TransactionMessages.append_instructions(message, [ix])
        }
      )
    }
  )
end

.get_message_packer_instruction_plan_from_instructions(instructions) ⇒ Object



161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
# File 'lib/solana/ruby/kit/instruction_plans/instruction_plan.rb', line 161

def get_message_packer_instruction_plan_from_instructions(instructions)
  MessagePackerInstructionPlan.new(
    get_message_packer: -> {
      idx = T.let(0, Integer)
      MessagePacker.new(
        done_proc: -> { idx >= instructions.length },
        pack_proc:  ->(message) {
          if idx >= instructions.length
            Kernel.raise SolanaError.new(SolanaError::INSTRUCTION_PLANS__MESSAGE_PACKER_ALREADY_COMPLETE)
          end

          original_size = Transactions.get_transaction_message_size(message)
          packed        = T.let(message, TransactionMessages::TransactionMessage)
          start_idx     = idx

          (idx...instructions.length).each do |i|
            packed = TransactionMessages.append_instructions(packed, [instructions[i]])
            size   = Transactions.get_transaction_message_size(packed)

            if size > Transactions::TRANSACTION_SIZE_LIMIT
              if i == start_idx
                Kernel.raise SolanaError.new(
                  SolanaError::INSTRUCTION_PLANS__MESSAGE_CANNOT_ACCOMMODATE_PLAN,
                  {
                    num_bytes_required: size - original_size,
                    num_free_bytes:     Transactions::TRANSACTION_SIZE_LIMIT - original_size
                  }
                )
              end
              idx = i
              return packed
            end
          end

          idx = instructions.length
          packed
        }
      )
    }
  )
end

.get_realloc_message_packer_instruction_plan(total_size:, get_instruction:) ⇒ Object



212
213
214
215
216
217
218
219
220
221
222
223
# File 'lib/solana/ruby/kit/instruction_plans/instruction_plan.rb', line 212

def get_realloc_message_packer_instruction_plan(total_size:, get_instruction:)
  realloc_limit      = 10_240
  num_instructions   = (total_size.to_f / realloc_limit).ceil
  last_size          = total_size % realloc_limit

  instructions = num_instructions.times.map do |i|
    chunk = (i == num_instructions - 1) ? last_size : realloc_limit
    get_instruction.call(chunk)
  end

  get_message_packer_instruction_plan_from_instructions(instructions)
end

.non_divisible_sequential_instruction_plan(plans) ⇒ Object



99
100
101
# File 'lib/solana/ruby/kit/instruction_plans/instruction_plan.rb', line 99

def non_divisible_sequential_instruction_plan(plans)
  SequentialInstructionPlan.new(plans: parse_single_instruction_plans(plans), divisible: false)
end

.non_divisible_sequential_transaction_plan(plans) ⇒ Object



55
56
57
# File 'lib/solana/ruby/kit/instruction_plans/transaction_plan.rb', line 55

def non_divisible_sequential_transaction_plan(plans)
  SequentialTransactionPlan.new(plans: parse_single_transaction_plans(plans), divisible: false)
end

.non_divisible_sequential_transaction_plan_result(plans) ⇒ Object



67
68
69
# File 'lib/solana/ruby/kit/instruction_plans/transaction_plan_result.rb', line 67

def non_divisible_sequential_transaction_plan_result(plans)
  SequentialTransactionPlanResult.new(plans: plans, divisible: false)
end

.parallel_instruction_plan(plans) ⇒ Object



106
107
108
# File 'lib/solana/ruby/kit/instruction_plans/instruction_plan.rb', line 106

def parallel_instruction_plan(plans)
  ParallelInstructionPlan.new(plans: parse_single_instruction_plans(plans))
end

.parallel_transaction_plan(plans) ⇒ Object



63
64
65
# File 'lib/solana/ruby/kit/instruction_plans/transaction_plan.rb', line 63

def parallel_transaction_plan(plans)
  ParallelTransactionPlan.new(plans: parse_single_transaction_plans(plans))
end

.parallel_transaction_plan_result(plans) ⇒ Object



73
74
75
# File 'lib/solana/ruby/kit/instruction_plans/transaction_plan_result.rb', line 73

def parallel_transaction_plan_result(plans)
  ParallelTransactionPlanResult.new(plans: plans)
end

.sequential_instruction_plan(plans) ⇒ Object



92
93
94
# File 'lib/solana/ruby/kit/instruction_plans/instruction_plan.rb', line 92

def sequential_instruction_plan(plans)
  SequentialInstructionPlan.new(plans: parse_single_instruction_plans(plans), divisible: true)
end

.sequential_transaction_plan(plans) ⇒ Object



48
49
50
# File 'lib/solana/ruby/kit/instruction_plans/transaction_plan.rb', line 48

def sequential_transaction_plan(plans)
  SequentialTransactionPlan.new(plans: parse_single_transaction_plans(plans), divisible: true)
end

.sequential_transaction_plan_result(plans) ⇒ Object



61
62
63
# File 'lib/solana/ruby/kit/instruction_plans/transaction_plan_result.rb', line 61

def sequential_transaction_plan_result(plans)
  SequentialTransactionPlanResult.new(plans: plans, divisible: true)
end

.single_instruction_plan(instruction) ⇒ Object



84
85
86
# File 'lib/solana/ruby/kit/instruction_plans/instruction_plan.rb', line 84

def single_instruction_plan(instruction)
  SingleInstructionPlan.new(instruction: instruction)
end

.single_transaction_plan(message) ⇒ Object



40
41
42
# File 'lib/solana/ruby/kit/instruction_plans/transaction_plan.rb', line 40

def single_transaction_plan(message)
  SingleTransactionPlan.new(message: message)
end

.successful_single_transaction_plan_result(message, transaction, context = {}) ⇒ Object



85
86
87
88
89
90
# File 'lib/solana/ruby/kit/instruction_plans/transaction_plan_result.rb', line 85

def successful_single_transaction_plan_result(message, transaction, context = {})
  SingleTransactionPlanResult.new(
    message: message,
    status:  SuccessfulStatus.new(transaction: transaction, context: context)
  )
end