Skip to content

Commit

Permalink
feat: check pending tx was rejected or not
Browse files Browse the repository at this point in the history
Signed-off-by: Miles Zhang <[email protected]>
  • Loading branch information
zmcNotafraid committed Dec 25, 2023
1 parent dfd4a68 commit 314de09
Show file tree
Hide file tree
Showing 8 changed files with 50 additions and 194 deletions.
58 changes: 31 additions & 27 deletions app/controllers/api/v1/ckb_transactions_controller.rb
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
module Api
module V1
class CkbTransactionsController < ApplicationController
before_action :validate_query_params, only: [:show, :display_inputs, :display_outputs]
before_action :find_transaction, only: [:show, :display_inputs, :display_outputs]
before_action :validate_pagination_params, :pagination_params, only: [:index, :display_inputs, :display_outputs]
before_action :validate_query_params,
only: %i[show display_inputs display_outputs]
before_action :find_transaction,
only: %i[show display_inputs display_outputs]
before_action :validate_pagination_params, :pagination_params,
only: %i[index display_inputs display_outputs]

def index
if from_home_page?
Expand All @@ -15,7 +18,7 @@ def index
version: ckb_transactions.cache_version, race_condition_ttl: 3.seconds) do
CkbTransactionListSerializer.new(ckb_transactions).serialized_json
end
render json: json
render json:
else
ckb_transactions = CkbTransaction.tx_committed.normal.select(
:id, :tx_hash, :block_number, :block_timestamp, :live_cell_changes, :capacity_involved, :updated_at, :created_at
Expand All @@ -26,12 +29,12 @@ def index
order_by, asc_or_desc = params[:sort].split(".", 2)
order_by =
case order_by
when "height"
"block_number"
when "capacity"
"capacity_involved"
else
order_by
when "height"
"block_number"
when "capacity"
"capacity_involved"
else
order_by
end

head :not_found and return unless order_by.in? %w[
Expand All @@ -47,16 +50,16 @@ def index
version: ckb_transactions.cache_version, race_condition_ttl: 3.seconds) do
records_counter = RecordCounters::Transactions.new
options = FastJsonapi::PaginationMetaGenerator.new(
request: request,
request:,
records: ckb_transactions,
page: @page,
page_size: @page_size,
records_counter: records_counter
records_counter:,
).call
CkbTransactionListSerializer.new(ckb_transactions,
options).serialized_json
end
render json: json
render json:
end
end

Expand All @@ -72,11 +75,11 @@ def query
if @address
records_counter = @tx_ids =
AccountBook.where(
address_id: @address.id
address_id: @address.id,
).order(
"ckb_transaction_id" => :desc
"ckb_transaction_id" => :desc,
).select(
"ckb_transaction_id"
"ckb_transaction_id",
).page(@page).per(@page_size).fast_page
CkbTransaction.where(id: @tx_ids.map(&:ckb_transaction_id)).order(id: :desc)
else
Expand All @@ -89,17 +92,18 @@ def query
Rails.cache.realize(ckb_transactions.cache_key,
version: ckb_transactions.cache_version, race_condition_ttl: 1.minute) do
options = FastJsonapi::PaginationMetaGenerator.new(
request: request,
request:,
records: ckb_transactions,
page: @page,
page_size: @page_size,
records_counter: records_counter
records_counter:,
).call
CkbTransactionsSerializer.new(ckb_transactions,
options.merge(params: {
previews: true, address: @address })).serialized_json
previews: true, address: @address
})).serialized_json
end
render json: json
render json:
end

def show
Expand All @@ -121,7 +125,9 @@ def display_inputs
cell_inputs = @ckb_transaction.normal_tx_display_inputs(cell_inputs)
end

render json: { data: cell_inputs, meta: { total: total_count, page_size: @page_size.to_i } }
render json: { data: cell_inputs,
meta: { total: total_count,
page_size: @page_size.to_i } }
end

def display_outputs
Expand All @@ -137,7 +143,9 @@ def display_outputs
cell_outputs = @ckb_transaction.normal_tx_display_outputs(cell_outputs)
end

render json: { data: cell_outputs, meta: { total: total_count, page_size: @page_size.to_i } }
render json: { data: cell_outputs,
meta: { total: total_count,
page_size: @page_size.to_i } }
end

private
Expand All @@ -161,17 +169,13 @@ def validate_query_params
errors = validator.error_object[:errors]
status = validator.error_object[:status]

render json: errors, status: status
render json: errors, status:
end
end

def find_transaction
@ckb_transaction = CkbTransaction.where(tx_hash: params[:id]).order(tx_status: :desc).first
raise Api::V1::Exceptions::CkbTransactionNotFoundError if @ckb_transaction.blank?

if @ckb_transaction.tx_status.to_s == "rejected" && @ckb_transaction.detailed_message.blank?
PoolTransactionUpdateRejectReasonWorker.perform_async(@ckb_transaction.tx_hash)
end
end
end
end
Expand Down
55 changes: 9 additions & 46 deletions app/workers/pool_transaction_check_worker.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,55 +4,18 @@ class PoolTransactionCheckWorker
sidekiq_options retry: 0

def perform
# Because iterating over all pool transaction entry record and get tx detail from CKB Node one by one
# will make heavy load to CKB node, slowing block processing, sometimes will lead to HTTP timeout
# So here we directly check the inputs and dependencies of the transaction locally in database
# If any of the input or dependency cells is used, the transaction will never be valid.
# Thus we can directly mark this transaction rejected without requesting to CKB Node.
# Only request the CKB Node for reject reason after we find the transaction is rejected.
pending_transactions = CkbTransaction.tx_pending.
includes(:cell_dependencies, cell_inputs: :previous_cell_output).
order(id: :asc).limit(150)
pending_transactions = CkbTransaction.tx_pending.where("created_at < ?",
2.minutes.ago)
pending_transactions.each do |tx|
is_rejected = false
rejected_transaction = nil
# check if any input is used by other transactions
tx.cell_inputs.each do |input|
if input.previous_cell_output && input.previous_cell_output.dead?
rejected_transaction = {
id: tx.id,
tx_status: "rejected",
created_at: tx.created_at,
updated_at: Time.current
}
is_rejected = true
break
response_string = CkbSync::Api.instance.directly_single_call_rpc method: "get_transaction",
params: [tx.tx_hash]
reason = response_string["result"]["tx_status"]
if reason["status"] == "rejected"
ApplicationRecord.transaction do
tx.update! tx_status: "rejected"
tx.create_reject_reason!(message: reason["reason"])
end
end

unless is_rejected
# check if any dependency cell(contract) is consumed by other transactions
tx.cell_dependencies.each do |dep|
if dep.cell_output && dep.cell_output.dead?
rejected_transaction = {
id: tx.id,
tx_status: "rejected",
created_at: tx.created_at,
updated_at: Time.current
}
is_rejected = true
break
end
end
end

if is_rejected
AfterCommitEverywhere.after_commit do
# fetch the reason from node
PoolTransactionUpdateRejectReasonWorker.perform_async tx.tx_hash
end
CkbTransaction.where(tx_hash: tx.tx_hash).update_all tx_status: :rejected # , detailed_message: reason
end
end
end
end
14 changes: 0 additions & 14 deletions app/workers/pool_transaction_update_reject_reason_worker.rb

This file was deleted.

6 changes: 3 additions & 3 deletions lib/scheduler.rb
Original file line number Diff line number Diff line change
Expand Up @@ -67,9 +67,9 @@ def call_worker(clz)
call_worker AddressUnclaimedCompensationGenerator
end

# s.every "5m", overlap: false do
# call_worker PoolTransactionCheckWorker
# end
s.every "2m", overlap: false do
call_worker PoolTransactionCheckWorker
end

s.every "1h", overlap: false do
call_worker CleanUpWorker
Expand Down
11 changes: 0 additions & 11 deletions test/models/pending_transaction_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,4 @@ class PendingTransactionTest < ActiveSupport::TestCase
assert_equal %w(hash header_deps cell_deps inputs outputs outputs_data version witnesses).sort,
json.keys.map(&:to_s).sort
end

test "should update_detailed_message_for_rejected_transaction when detailed_message is nil" do
rejected_tx_id = "0xed2049c21ffccfcd26281d60f8f77ff117adb9df9d3f8cbe5fe86e893c66d359"
tx = create :pending_transaction, tx_status: :rejected, tx_hash: rejected_tx_id

VCR.use_cassette("get_rejected_transaction") do
PoolTransactionUpdateRejectReasonWorker.new.perform(rejected_tx_id)
end
tx.reload
assert tx.detailed_message.include?("Resolve failed Dead")
end
end
46 changes: 7 additions & 39 deletions test/workers/pool_transaction_check_worker_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,49 +2,17 @@

class PoolTransactionCheckWorkerTest < ActiveSupport::TestCase
setup do
CkbSync::Api.any_instance.stubs(:generate_json_rpc_id).returns(1)
Sidekiq::Testing.inline!
end
test "should detect and mark failed tx from pending tx, for inputs" do
rejected_tx_id = "0xed2049c21ffccfcd26281d60f8f77ff117adb9df9d3f8cbe5fe86e893c66d359"

block = create(:block)

cell_output = create(:cell_output,
:with_full_transaction,
block: block,
ckb_transaction_id: rejected_tx_id)
cell_output.update tx_hash: rejected_tx_id, cell_index: 0, status: "dead"
tx = create :pending_transaction, tx_hash: rejected_tx_id
tx.cell_inputs.create previous_tx_hash: rejected_tx_id, previous_index: 0

VCR.use_cassette("get_rejected_transaction") do
PoolTransactionCheckWorker.perform_inline
PoolTransactionUpdateRejectReasonWorker.perform_async rejected_tx_id
pending_transaction = CkbTransaction.find_by(tx_hash: rejected_tx_id)

assert_equal "rejected", pending_transaction.tx_status
assert pending_transaction.detailed_message.include?("Resolve failed Dead")
end
@pending_tx = create(:pending_transaction,
tx_hash: rejected_tx_id, created_at: 10.minutes.ago)
end

test "should detect and mark failed tx from pending tx, for cell_deps" do
rejected_tx_id = "0xed2049c21ffccfcd26281d60f8f77ff117adb9df9d3f8cbe5fe86e893c66d359"
script = create :script
block = create(:block)
cell_output = create(:cell_output, :with_full_transaction, block: block,
ckb_transaction_id: rejected_tx_id)
cell_output.update tx_hash: rejected_tx_id, cell_index: 0, status: "dead"

tx = create :pending_transaction, tx_hash: rejected_tx_id
tx.cell_dependencies.create(dep_type: :code, cell_output: cell_output, script: script)

test "should detect and mark failed tx from pending tx, for inputs" do
Sidekiq::Testing.inline!
VCR.use_cassette("get_rejected_transaction") do
PoolTransactionUpdateRejectReasonWorker.perform_inline rejected_tx_id

pending_transaction = CkbTransaction.find_by(tx_hash: rejected_tx_id)
assert_equal "rejected", pending_transaction.tx_status
assert pending_transaction.detailed_message.include?("Resolve failed Dead")
PoolTransactionCheckWorker.perform_async
assert_equal "rejected", @pending_tx.reload.tx_status
assert @pending_tx.detailed_message.include?("Resolve failed Dead")
end
end
end
19 changes: 0 additions & 19 deletions test/workers/pool_transaction_update_reject_reason_worker_test.rb

This file was deleted.

35 changes: 0 additions & 35 deletions vcr_fixtures/vcr_cassettes/get_rejected_transaction.yml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 314de09

Please sign in to comment.