Skip to content

Commit

Permalink
refactor: transaction details
Browse files Browse the repository at this point in the history
  • Loading branch information
rabbitz committed Jan 15, 2024
1 parent 1d4d524 commit c5bcfe8
Show file tree
Hide file tree
Showing 4 changed files with 159 additions and 85 deletions.
4 changes: 2 additions & 2 deletions .rubocop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -449,9 +449,9 @@ Layout/InitialIndentation:
Enabled: false

Layout/LineLength:
Description: 'Limit lines to 80 characters.'
Description: 'Limit lines to 160 characters.'
StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#80-character-limits'
Max: 80
Max: 160

# Lint

Expand Down
89 changes: 6 additions & 83 deletions app/controllers/api/v2/ckb_transactions_controller.rb
Original file line number Diff line number Diff line change
@@ -1,25 +1,24 @@
module Api
module V2
class CkbTransactionsController < BaseController
include CellDataComparator

before_action :set_page_and_page_size, only: %i[display_inputs display_outputs]

def details
ckb_transaction = CkbTransaction.where(tx_hash: params[:id]).order(tx_status: :desc).first
ckb_transaction = CkbTransaction.find_by(tx_hash: params[:id])
head :not_found and return if ckb_transaction.blank?

expires_in 10.seconds, public: true, must_revalidate: true

input_capacities = build_cell_capacities(ckb_transaction.display_inputs)
output_capacities = build_cell_capacities(ckb_transaction.display_outputs)
transfers = build_transfers(input_capacities, output_capacities)
transfers = compare_cells(ckb_transaction)

render json: { data: transfers }
end

def display_inputs
expires_in 1.hour, public: true, must_revalidate: true

ckb_transaction = CkbTransaction.where(tx_hash: params[:id]).order(tx_status: :desc).first
ckb_transaction = CkbTransaction.find_by(tx_hash: params[:id])
head :not_found and return if ckb_transaction.blank?

if ckb_transaction.is_cellbase
Expand All @@ -44,7 +43,7 @@ def display_inputs
def display_outputs
expires_in 1.hour, public: true, must_revalidate: true

ckb_transaction = CkbTransaction.where(tx_hash: params[:id]).order(tx_status: :desc).first
ckb_transaction = CkbTransaction.find_by(tx_hash: params[:id])
head :not_found and return if ckb_transaction.blank?

if ckb_transaction.is_cellbase
Expand All @@ -67,82 +66,6 @@ def display_outputs

private

def build_cell_capacities(outputs)
cell_capacities = Hash.new { |hash, key| hash[key] = {} }
outputs.each do |output|
parsed_output = JSON.parse(output.to_json, object_class: OpenStruct)
next if parsed_output.from_cellbase

unit = token_unit(parsed_output)
address = parsed_output.address_hash
udt_info = parsed_output.udt_info

if (cell_capacity = cell_capacities[[address, unit]]).blank?
cell_capacity = {
capacity: parsed_output.capacity.to_f,
cell_type: parsed_output.cell_type,
udt_info: {
symbol: udt_info&.symbol,
amount: udt_info&.amount.to_f,
decimal: udt_info&.decimal,
type_hash: udt_info&.type_hash,
published: !!udt_info&.published,
display_name: udt_info&.display_name,
uan: udt_info&.uan
},
m_nft_info: parsed_output.m_nft_info.to_h
}
else
cell_capacity[:capacity] += parsed_output.capacity.to_f
cell_capacity[:udt_info][:amount] += udt_info.amount.to_f unless unit == "CKB"
end

cell_capacities[[address, unit]] = cell_capacity
end

cell_capacities
end

def build_transfers(input_capacities, output_capacities)
capacities = Hash.new { |hash, key| hash[key] = [] }
keys = input_capacities.keys | output_capacities.keys
keys.each do |key|
address_hash, unit = key
input = input_capacities[key]
output = output_capacities[key]

# There may be keys in both input_capacities and output_capacities that do not exist
cell_type = output[:cell_type] || input[:cell_type]
capacity_change = output[:capacity].to_f - input[:capacity].to_f
m_nft_info = output[:m_nft_info] || input[:m_nft_info]

transfer = { capacity: capacity_change, cell_type: cell_type }
transfer[:m_nft_info] = m_nft_info if m_nft_info.present?

if unit != "CKB"
output_amount = output[:udt_info] ? output[:udt_info][:amount] : 0.0
input_amount = input[:udt_info] ? input[:udt_info][:amount] : 0.0
amount_change = output_amount - input_amount
transfer[:udt_info] = output[:udt_info] || input[:udt_info]
transfer[:udt_info][:amount] = amount_change
end

capacities[address_hash] << CkbUtils.hash_value_to_s(transfer)
end

capacities.map do |address, value|
{ address: address, transfers: value }
end
end

def token_unit(cell)
if (udt_info = cell.udt_info).present?
udt_info.type_hash
else
"CKB"
end
end

def set_page_and_page_size
@page = params.fetch(:page, 1)
@page_size = params.fetch(:page_size, CkbTransaction.default_per_page)
Expand Down
Empty file removed app/controllers/concerns/.keep
Empty file.
151 changes: 151 additions & 0 deletions app/controllers/concerns/cell_data_comparator.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
module CellDataComparator
extend ActiveSupport::Concern

private

def compare_cells(transaction)
inputs = transaction.input_cells
outputs = transaction.cell_outputs
normal_transfers = diff_normal_cells(inputs, outputs)
udt_transfers = diff_udt_cells(inputs, outputs)
cota_nft_transfers = diff_cota_nft_cells(transaction, inputs, outputs)
normal_nft_transfers = diff_normal_nft_cells(inputs, outputs)
nft_class_transfers = diff_nft_capacities(inputs, outputs)

merged_transfers =
[normal_transfers, udt_transfers, cota_nft_transfers, normal_nft_transfers, nft_class_transfers].reduce do |acc, h|
acc.merge(h) { |_, ov, nv| ov + nv }
end

merged_transfers.map do |address_id, transfers|
address = Address.find_by(id: address_id)
{ address: address.address_hash, transfers: }
end
end

def diff_normal_cells(inputs, outputs)
transfers = Hash.new { |h, k| h[k] = Array.new }
inputs = inputs.normal.group(:address_id).sum(:capacity)
outputs = outputs.normal.group(:address_id).sum(:capacity)

(inputs.keys | outputs.keys).each do |k|
capacity = outputs[k].to_f - inputs[k].to_f
transfers[k] << CkbUtils.hash_value_to_s({ capacity:, cell_type: "normal" })
end

transfers
end

def diff_udt_cells(inputs, outputs)
transfers = Hash.new { |h, k| h[k] = Array.new }
udt_infos = Hash.new { |h, k| h[k] = nil }

process_udt = ->(c, h) {
info = Udt.find_by(type_hash: c.type_hash, published: true)
unless udt_infos[c.type_hash]
udt_infos[c.type_hash] = {
symbol: info&.symbol,
decimal: info&.decimal,
display_name: info&.display_name,
uan: info&.uan,
}
end

k = [c.address_id, c.type_hash]
h[k] ||= { capacity: 0.0, amount: 0.0 }
h[k][:capacity] += c.capacity
h[k][:amount] += c.udt_amount
}
inputs = inputs.udt.each_with_object({}) { |c, h| process_udt.call(c, h) }
outputs = outputs.udt.each_with_object({}) { |c, h| process_udt.call(c, h) }

(inputs.keys | outputs.keys).each do |k|
input = inputs[k]
output = outputs[k]

amount = output&.dig(:amount).to_f - input&.dig(:amount).to_f
capacity = output&.dig(:capacity).to_f - input&.dig(:capacity).to_f
udt_info = udt_infos[k[1]].merge(amount:)
transfers[k[0]] << CkbUtils.hash_value_to_s({ capacity:, cell_type: "udt", udt_info: })
end

transfers
end

def diff_cota_nft_cells(transaction, inputs, outputs)
transfers = Hash.new { |h, k| h[k] = Array.new }
inputs = inputs.cota_regular.group(:address_id).sum(:capacity)
outputs = outputs.cota_regular.group(:address_id).sum(:capacity)

(inputs.keys | outputs.keys).each do |k|
capacity = outputs[k].to_f - inputs[k].to_f
transfers[k] << { capacity: capacity.to_s, cell_type: "cota_regular", cota_info: cota_info(transaction, k) }
end

transfers
end

def diff_normal_nft_cells(inputs, outputs)
transfers = Hash.new { |h, k| h[k] = Array.new }
cell_types = %w(m_nft_token nrc_721_token spore_cell)

process_nft = ->(c, h) {
k = [c.address_id, c.cell_type, c.type_hash]
h[k] ||= { capacity: 0.0, count: 0 }
h[k][:capacity] += c.capacity
h[k][:count] -= 1
}
inputs = inputs.where(cell_type: cell_types).each_with_object({}) { |c, h| process_nft.call(c, h) }
outputs = outputs.where(cell_type: cell_types).each_with_object({}) { |c, h| process_nft.call(c, h) }

(inputs.keys | outputs.keys).each do |k|
address_id, cell_type, type_hash = k
token_id, collection_name = token_info(type_hash)
input = inputs[k]
output = outputs[k]
capacity = output&.dig(:capacity).to_f - input&.dig(:capacity).to_f
count = output&.dig(:count).to_i + input&.dig(:count).to_i
transfers[address_id] << CkbUtils.hash_value_to_s({ capacity:, cell_type:, token_id:, collection_name:, count: })
end

transfers
end

def diff_nft_capacities(inputs, outputs)
transfers = Hash.new { |h, k| h[k] = Array.new }
cell_types = %w(m_nft_issuer m_nft_class nrc_721_factory cota_registry spore_cluster)
inputs = inputs.where(cell_type: cell_types).group(:address_id, :cell_type).sum(:capacity)
outputs = outputs.where(cell_type: cell_types).group(:address_id, :cell_type).sum(:capacity)

(inputs.keys | outputs.keys).each do |k|
capacity = outputs[k].to_f - inputs[k].to_f
transfers[k[0]] << CkbUtils.hash_value_to_s({ capacity:, cell_type: k[1] })
end

transfers
end

def token_info(script_hash)
item = TokenItem.joins(:type_script).where(type_script: { script_hash: }).take
[item&.token_id, item&.collection&.name]
end

def cota_info(transaction, address_id)
info = Array.new
process_transfer = ->(item, count) {
collection = item.collection
info << CkbUtils.hash_value_to_s({
collection_name: collection.name,
count:,
token_id: item.token_id,
})
}

transaction.token_transfers.each do |t|
process_transfer.call(t.item, -1) if t.from_id == address_id
process_transfer.call(t.item, 1) if t.to_id == address_id
end

info
end
end

0 comments on commit c5bcfe8

Please sign in to comment.