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 81b48d5
Show file tree
Hide file tree
Showing 6 changed files with 19 additions and 154 deletions.
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.reject_reason.create!(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
44 changes: 7 additions & 37 deletions test/workers/pool_transaction_check_worker_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,47 +2,17 @@

class PoolTransactionCheckWorkerTest < ActiveSupport::TestCase
setup do
CkbSync::Api.any_instance.stubs(:generate_json_rpc_id).returns(1)
Sidekiq::Testing.inline!
@rejected_tx_id = "0xed2049c21ffccfcd26281d60f8f77ff117adb9df9d3f8cbe5fe86e893c66d359"
@tx = create(:pending_transaction,
tx_hash: @rejected_tx_id)
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
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
PoolTransactionCheckWorker.perform_async @rejected_tx_id
pending_transaction = CkbTransaction.find_by tx_hash: @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
Expand Down
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 81b48d5

Please sign in to comment.