-
Notifications
You must be signed in to change notification settings - Fork 42
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
refactor: bitcoin transaction detect worker
- Loading branch information
Showing
6 changed files
with
175 additions
and
94 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
class ImportBitcoinUtxoJob < ApplicationJob | ||
queue_as :bitcoin | ||
|
||
def perform(txid, out_index, cell_id) | ||
ApplicationRecord.transaction do | ||
cell_output = CellOutput.find_by(id: cell_id) | ||
unless cell_output | ||
raise ArgumentError, "Missing cell_output(#{cell_id}), txid(#{txid}), out_index(#{out_index})" | ||
end | ||
|
||
vout_attributes = [] | ||
|
||
# build bitcoin transaction | ||
raw_tx = fetch_raw_transaction(txid) | ||
tx = build_tranaction!(raw_tx) | ||
|
||
# build op_returns | ||
op_returns = build_op_returns!(raw_tx, tx, cell_output.ckb_transaction, vout_attributes) | ||
vout_attributes.concat(op_returns) if op_returns.present? | ||
|
||
# build vout | ||
vout_attributes << build_vout!(raw_tx, tx, out_index, cell_output) | ||
|
||
BitcoinVout.upsert_all(vout_attributes, unique_by: %i[bitcoin_transaction_id index]) if vout_attributes.present? | ||
end | ||
end | ||
|
||
def build_tranaction!(raw_tx) | ||
tx = BitcoinTransaction.find_by(txid: raw_tx["txid"]) | ||
return tx if tx | ||
|
||
# avoid making multiple RPC requests | ||
block_header = rpc.getblockheader(raw_tx["blockhash"]) | ||
BitcoinTransaction.create!( | ||
txid: raw_tx["txid"], | ||
tx_hash: raw_tx["hash"], | ||
time: raw_tx["time"], | ||
block_hash: raw_tx["blockhash"], | ||
block_height: block_header["height"], | ||
) | ||
end | ||
|
||
def build_op_returns!(raw_tx, tx, ckb_tx, v_attributes) | ||
op_returns = [] | ||
|
||
raw_tx["vout"].each do |vout| | ||
data = vout.dig("scriptPubKey", "hex") | ||
script_pubkey = Bitcoin::Script.parse_from_payload(data.htb) | ||
next unless script_pubkey.op_return? | ||
|
||
op_return = { | ||
bitcoin_transaction_id: tx.id, | ||
bitcoin_address_id: nil, | ||
data:, | ||
index: vout.dig("n"), | ||
asm: vout.dig("scriptPubKey", "asm"), | ||
op_return: true, | ||
ckb_transaction_id: ckb_tx.id, | ||
cell_output_id: nil, | ||
address_id: nil, | ||
} | ||
|
||
op_returns << op_return if v_attributes.exclude?(op_return) | ||
end | ||
|
||
op_returns | ||
end | ||
|
||
def build_vout!(raw_tx, tx, out_index, cell_output) | ||
vout = raw_tx["vout"].find { _1["n"] == out_index } | ||
raise ArgumentError, "Missing vout txid: #{raw_tx['txid']} index: #{out_index}" unless vout | ||
|
||
address_hash = vout.dig("scriptPubKey", "address") | ||
raise ArgumentError, "Missing vout address: #{raw_tx['txid']} index: #{out_index}" unless address_hash | ||
|
||
address = build_address!(address_hash, cell_output) | ||
{ | ||
bitcoin_transaction_id: tx.id, | ||
bitcoin_address_id: address.id, | ||
data: vout.dig("scriptPubKey", "hex"), | ||
index: vout.dig("n"), | ||
asm: vout.dig("scriptPubKey", "asm"), | ||
op_return: false, | ||
ckb_transaction_id: cell_output.ckb_transaction_id, | ||
cell_output_id: cell_output.id, | ||
address_id: cell_output.address_id, | ||
} | ||
end | ||
|
||
def build_address!(address_hash, cell_output) | ||
bitcoin_address = BitcoinAddress.find_or_create_by!(address_hash:) | ||
BitcoinAddressMapping. | ||
create_with(bitcoin_address_id: bitcoin_address.id). | ||
find_or_create_by!(ckb_address_id: cell_output.address_id) | ||
|
||
bitcoin_address | ||
end | ||
|
||
def fetch_raw_transaction(txid) | ||
tx_json = Kredis.json txid, expires_in: 1.hour | ||
if tx_json.value.blank? | ||
tx_json.value = rpc.getrawtransaction(txid, 2) | ||
end | ||
tx_json.value | ||
end | ||
|
||
def rpc | ||
@rpc ||= Bitcoin::Rpc.instance | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,106 +1,57 @@ | ||
class BitcoinTransactionDetectWorker | ||
class BitcoinUtxoDetectWorker | ||
include Sidekiq::Worker | ||
sidekiq_options queue: "bitcoin" | ||
|
||
INIT_BITCOIN_BLOCK_HEIGHT = 100_000 | ||
|
||
attr_accessor :block | ||
|
||
def perform(block_id) | ||
@block = Block.find_by(id: block_id) | ||
return unless @block | ||
|
||
ApplicationRecord.transaction do | ||
vout_attributes = [] | ||
|
||
transacitons = @block.ckb_transactions.limit(min_transactions_count) | ||
transacitons.each_with_index do |transaction, index| | ||
next if transaction.bitcoin_vouts.exists? | ||
|
||
txid = bitcoin_txids[index] | ||
raw_transaction = rpc.getrawtransaction(txid, 2) | ||
bitcoin_transaction = build_tranaction!(raw_transaction) | ||
|
||
cell_output = transaction.cell_outputs.first | ||
vout_attribute = build_vout_attributes!(raw_transaction, bitcoin_transaction, cell_output) | ||
next unless vout_attribute | ||
|
||
vout_attributes << vout_attribute | ||
@block.ckb_transactions.each do |transaction| | ||
vin_attributes = [] | ||
|
||
# import cell_inputs utxo | ||
transaction.cell_inputs.each do |cell| | ||
previous_cell_output = cell.previous_cell_output | ||
next unless previous_cell_output | ||
|
||
lock_script = previous_cell_output.lock_script | ||
next unless CkbUtils2.is_rgbpp_lock_cell?(lock_script) | ||
|
||
# import previous bitcoin transaction if prev vout is missing | ||
import_utxo!(lock_script.args, previous_cell_output.id, transaction.id) | ||
|
||
previous_vout = BitcoinVout.find_by(cell_output_id: previous_cell_output.id) | ||
vin_attributes << { | ||
previous_bitcoin_vout_id: previous_vout.id, | ||
ckb_transaction_id: transaction.id, | ||
cell_input_id: cell.id, | ||
} | ||
end | ||
|
||
if vin_attributes.present? | ||
BitcoinVin.upsert_all(vin_attributes, unique_by: %i[ckb_transaction_id cell_input_id]) | ||
end | ||
|
||
# import cell_outputs utxo | ||
transaction.cell_outputs.each do |cell| | ||
lock_script = cell.lock_script | ||
next unless CkbUtils2.is_rgbpp_lock_cell?(lock_script) | ||
|
||
import_utxo!(lock_script.args, cell.id, transaction.id) | ||
end | ||
end | ||
|
||
return if vout_attributes.blank? | ||
|
||
BitcoinVout.upsert_all(vout_attributes, unique_by: %i[bitcoin_transaction_id index]) | ||
end | ||
end | ||
|
||
private | ||
|
||
def bitcoin_txids | ||
return @txids if @txids | ||
|
||
block_hash = rpc.getblockhash(bitcoin_block_height) | ||
# verbose set to 1 for JSON object | ||
block = rpc.getblock(block_hash, 1) | ||
@txids = block["tx"] | ||
end | ||
def import_utxo!(args, cell_id, _tx_id) | ||
txid, out_index = CkbUtils2.parse_rgbpp_args(args) | ||
|
||
def bitcoin_block_height | ||
transaction = BitcoinTransaction.last | ||
transaction ? transaction.block_height + 1 : 100_000 | ||
end | ||
|
||
def min_transactions_count | ||
[block.ckb_transactions_count, bitcoin_txids.count].min | ||
end | ||
|
||
def build_tranaction!(raw_tx) | ||
tx = BitcoinTransaction.find_by(txid: raw_tx["txid"]) | ||
return tx if tx | ||
|
||
# avoid making multiple RPC requests | ||
block_header = rpc.getblockheader(raw_tx["blockhash"]) | ||
BitcoinTransaction.create!( | ||
txid: raw_tx["txid"], | ||
tx_hash: raw_tx["hash"], | ||
time: raw_tx["time"], | ||
block_hash: raw_tx["blockhash"], | ||
block_height: block_header["height"], | ||
) | ||
end | ||
|
||
def build_vout_attributes!(raw_tx, tx, cell_output) | ||
vout = raw_tx["vout"].find { _1["n"] == cell_output.cell_index } | ||
vout ||= raw_tx["vout"][0] | ||
|
||
address_hash = vout.dig("scriptPubKey", "address") | ||
return unless address_hash | ||
|
||
bitcoin_address = build_address!(address_hash, cell_output) | ||
|
||
{ | ||
bitcoin_transaction_id: tx.id, | ||
bitcoin_address_id: bitcoin_address.id, | ||
data: "6a24aa21a9ed5e53af6963d02d7fcf87695798a0715951bd03fb05f524015d88324636141f42", | ||
index: vout.dig("scriptPubKey", "n"), | ||
asm: "OP_RETURN aa21a9ed5e53af6963d02d7fcf87695798a0715951bd03fb05f524015d88324636141f42", | ||
op_return: true, | ||
ckb_transaction_id: cell_output.ckb_transaction_id, | ||
cell_output_id: cell_output.id, | ||
address_id: cell_output.address_id, | ||
} | ||
end | ||
|
||
def build_address!(address_hash, cell_output) | ||
bitcoin_address = BitcoinAddress.find_or_create_by(address_hash:) | ||
BitcoinAddressMapping. | ||
create_with(bitcoin_address_id: bitcoin_address.id). | ||
find_or_create_by!(ckb_address_id: cell_output.address_id) | ||
|
||
bitcoin_address | ||
end | ||
|
||
def rpc | ||
@rpc ||= Bitcoin::Rpc.instance | ||
unless BitcoinTransaction.includes(:bitcoin_vouts).where( | ||
bitcoin_transactions: { txid: }, | ||
bitcoin_vouts: { index: out_index, cell_output_id: cell_id }, | ||
).exists? | ||
ImportBitcoinUtxoJob.perform_now(txid, out_index, cell_id) | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters