From ce3c5fb5cb2a791dc2864c51b163e1bb6f498162 Mon Sep 17 00:00:00 2001 From: NanZhang Date: Mon, 13 Nov 2023 10:58:31 +0800 Subject: [PATCH] feat: paginate display cells (#1496) --- .../api/v1/ckb_transactions_controller.rb | 51 ++++- app/models/ckb_transaction.rb | 191 +---------------- app/models/concerns/.keep | 0 app/models/concerns/display_cells.rb | 200 ++++++++++++++++++ app/serializers/script_serializer.rb | 35 --- config/routes.rb | 2 + 6 files changed, 246 insertions(+), 233 deletions(-) delete mode 100644 app/models/concerns/.keep create mode 100644 app/models/concerns/display_cells.rb delete mode 100644 app/serializers/script_serializer.rb diff --git a/app/controllers/api/v1/ckb_transactions_controller.rb b/app/controllers/api/v1/ckb_transactions_controller.rb index 77f4d6ee4..58d5d7e00 100644 --- a/app/controllers/api/v1/ckb_transactions_controller.rb +++ b/app/controllers/api/v1/ckb_transactions_controller.rb @@ -1,9 +1,9 @@ module Api module V1 class CkbTransactionsController < ApplicationController - before_action :validate_query_params, only: :show - before_action :validate_pagination_params, :pagination_params, - only: :index + 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] def index if from_home_page? @@ -103,17 +103,41 @@ def query end def show - ckb_transaction = CkbTransaction.where(tx_hash: params[:id]).order(tx_status: :desc).first + expires_in 10.seconds, public: true, must_revalidate: true + + render json: CkbTransactionSerializer.new(@ckb_transaction) + end - raise Api::V1::Exceptions::CkbTransactionNotFoundError if ckb_transaction.blank? + def display_inputs + expires_in 1.hour, public: true, must_revalidate: true - if ckb_transaction.tx_status.to_s == "rejected" && ckb_transaction.detailed_message.blank? - PoolTransactionUpdateRejectReasonWorker.perform_async(ckb_transaction.tx_hash) + if @ckb_transaction.is_cellbase + cell_inputs = @ckb_transaction.cellbase_display_inputs + total_count = cell_inputs.count + else + cell_inputs = @ckb_transaction.cell_inputs.order(id: :asc). + page(@page).per(@page_size).fast_page + total_count = cell_inputs.total_count + cell_inputs = @ckb_transaction.normal_tx_display_inputs(cell_inputs) end - expires_in 10.seconds, public: true, must_revalidate: true + render json: { data: cell_inputs, meta: { total: total_count, page_size: @page_size.to_i } } + end + + def display_outputs + expires_in 1.hour, public: true, must_revalidate: true - render json: CkbTransactionSerializer.new(ckb_transaction) + if @ckb_transaction.is_cellbase + cell_outputs = @ckb_transaction.cellbase_display_outputs + total_count = cell_outputs.count + else + cell_outputs = @ckb_transaction.outputs.order(id: :asc). + page(@page).per(@page_size).fast_page + total_count = cell_outputs.total_count + 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 } } end private @@ -140,6 +164,15 @@ def validate_query_params render json: errors, status: 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 end diff --git a/app/models/ckb_transaction.rb b/app/models/ckb_transaction.rb index 0d0ba7222..044deb220 100644 --- a/app/models/ckb_transaction.rb +++ b/app/models/ckb_transaction.rb @@ -1,6 +1,8 @@ # A transaction in CKB is composed by several inputs and several outputs # the inputs are the previous generated outputs class CkbTransaction < ApplicationRecord + include DisplayCells + self.primary_key = :id MAX_PAGINATES_PER = 100 DEFAULT_PAGINATES_PER = 10 @@ -194,22 +196,6 @@ def cell_deps cell_dependencies.explicit.includes(:cell_output).to_a.map(&:to_raw) end - def display_inputs(previews: false) - if is_cellbase - cellbase_display_inputs - else - normal_tx_display_inputs(previews) - end - end - - def display_outputs(previews: false) - if is_cellbase - cellbase_display_outputs - else - normal_tx_display_outputs(previews) - end - end - def income(address) outputs.where(address: address).sum(:capacity) - inputs.where(address: address).sum(:capacity) end @@ -224,10 +210,6 @@ def dao_transaction? )).exists? end - def cell_info - nil - end - def detailed_message reject_reason&.message end @@ -263,175 +245,6 @@ def self.last_n_days_transaction_fee_rates(last_n_day) private - def normal_tx_display_outputs(previews) - cell_outputs_for_display = outputs.sort_by(&:id) - if previews - cell_outputs_for_display = cell_outputs_for_display[0, 10] - end - cell_outputs_for_display.map do |output| - consumed_tx_hash = output.live? ? nil : output.consumed_by&.tx_hash - display_output = { - id: output.id, - capacity: output.capacity, - occupied_capacity: output.occupied_capacity, - address_hash: output.address_hash, - status: output.status, - consumed_tx_hash: consumed_tx_hash, - cell_type: output.cell_type - } - display_output.merge!(attributes_for_udt_cell(output)) if output.udt? - display_output.merge!(attributes_for_cota_registry_cell(output)) if output.cota_registry? - display_output.merge!(attributes_for_cota_regular_cell(output)) if output.cota_regular? - - display_output.merge!(attributes_for_m_nft_cell(output)) if output.cell_type.in?(%w( - m_nft_issuer m_nft_class - m_nft_token - )) - display_output.merge!(attributes_for_nrc_721_cell(output)) if output.cell_type.in?(%w( - nrc_721_token - nrc_721_factory - )) - - CkbUtils.hash_value_to_s(display_output) - end - end - - def cellbase_display_outputs - cell_outputs_for_display = outputs.to_a.sort_by(&:id) - cellbase = Cellbase.new(block) - cell_outputs_for_display.map do |output| - consumed_tx_hash = output.live? ? nil : output.consumed_by.tx_hash - CkbUtils.hash_value_to_s(id: output.id, capacity: output.capacity, occupied_capacity: output.occupied_capacity, address_hash: output.address_hash, - target_block_number: cellbase.target_block_number, base_reward: cellbase.base_reward, commit_reward: cellbase.commit_reward, proposal_reward: cellbase.proposal_reward, secondary_reward: cellbase.secondary_reward, status: output.status, consumed_tx_hash: consumed_tx_hash) - end - end - - def normal_tx_display_inputs(previews) - cell_inputs_for_display = cell_inputs.to_a.sort_by(&:id) - if previews - cell_inputs_for_display = cell_inputs_for_display[0, 10] - end - cell_inputs_for_display.each_with_index.map do |cell_input, index| - previous_cell_output = cell_input.previous_cell_output - unless previous_cell_output - next({ - from_cellbase: false, - capacity: "", - occupied_capacity: "", - address_hash: "", - generated_tx_hash: cell_input.previous_tx_hash, - cell_index: cell_input.previous_index, - since: { - raw: hex_since(cell_input.since.to_i), - median_timestamp: cell_input.block&.median_timestamp.to_i - } - }) - end - - display_input = { - id: previous_cell_output.id, - from_cellbase: false, - capacity: previous_cell_output.capacity, - occupied_capacity: previous_cell_output.occupied_capacity, - address_hash: previous_cell_output.address_hash, - generated_tx_hash: previous_cell_output.ckb_transaction.tx_hash, - cell_index: previous_cell_output.cell_index, - cell_type: previous_cell_output.cell_type, - since: { - raw: hex_since(cell_input.since.to_i), - median_timestamp: cell_input.block&.median_timestamp.to_i - } - } - display_input.merge!(attributes_for_dao_input(previous_cell_output)) if previous_cell_output.nervos_dao_withdrawing? - if previous_cell_output.nervos_dao_deposit? - display_input.merge!(attributes_for_dao_input(cell_outputs[index], - false)) - end - display_input.merge!(attributes_for_udt_cell(previous_cell_output)) if previous_cell_output.udt? - display_input.merge!(attributes_for_m_nft_cell(previous_cell_output)) if previous_cell_output.cell_type.in?(%w( - m_nft_issuer m_nft_class m_nft_token - )) - display_input.merge!(attributes_for_nrc_721_cell(previous_cell_output)) if previous_cell_output.cell_type.in?(%w( - nrc_721_token nrc_721_factory - )) - - CkbUtils.hash_value_to_s(display_input) - end - end - - def hex_since(int_since_value) - return "0x#{int_since_value.to_s(16).rjust(16, '0')}" - end - - def attributes_for_udt_cell(udt_cell) - info = CkbUtils.hash_value_to_s(udt_cell.udt_info) - { - udt_info: info, - extra_info: info - } - end - - def attributes_for_m_nft_cell(m_nft_cell) - info = m_nft_cell.m_nft_info - { m_nft_info: info, extra_info: info } - end - - def attributes_for_cota_registry_cell(cota_cell) - info = cota_cell.cota_registry_info - { cota_registry_info: info, extra_info: info } - end - - def attributes_for_cota_regular_cell(cota_cell) - info = cota_cell.cota_regular_info - { cota_regular_info: info, extra_info: info } - end - - def attributes_for_nrc_721_cell(nrc_721_cell) - info = nrc_721_cell.nrc_721_nft_info - { nrc_721_token_info: info, extra_info: info } - end - - def attributes_for_dao_input(nervos_dao_withdrawing_cell, is_phase2 = true) - nervos_dao_withdrawing_cell_generated_tx = nervos_dao_withdrawing_cell.ckb_transaction - nervos_dao_deposit_cell = nervos_dao_withdrawing_cell_generated_tx.cell_inputs.order(:id)[nervos_dao_withdrawing_cell.cell_index].previous_cell_output - # start block: the block contains the trasaction which generated the deposit cell output - compensation_started_block = Block.select(:number, :timestamp).find(nervos_dao_deposit_cell.block.id) - # end block: the block contains the transaction which generated the withdrawing cell - compensation_ended_block = Block.select(:number, :timestamp).find(nervos_dao_withdrawing_cell_generated_tx.block_id) - interest = CkbUtils.dao_interest(nervos_dao_withdrawing_cell) - - attributes = { - compensation_started_block_number: compensation_started_block.number, - compensation_started_timestamp: compensation_started_block.timestamp, - compensation_ended_block_number: compensation_ended_block.number, - compensation_ended_timestamp: compensation_ended_block.timestamp, - interest: interest - } - - if is_phase2 - number, timestamp = Block.where(id: block_id).pick(:number, :timestamp) # locked_until_block - attributes[:locked_until_block_number] = number - attributes[:locked_until_block_timestamp] = timestamp - end - - CkbUtils.hash_value_to_s(attributes) - end - - def cellbase_display_inputs - cellbase = Cellbase.new(block) - [ - CkbUtils.hash_value_to_s( - id: nil, - from_cellbase: true, - capacity: nil, - occupied_capacity: nil, - address_hash: nil, - target_block_number: cellbase.target_block_number, - generated_tx_hash: tx_hash - ) - ] - end - def recover_dead_cell inputs.update_all(status: "live", consumed_by_id: nil, consumed_block_timestamp: nil) end diff --git a/app/models/concerns/.keep b/app/models/concerns/.keep deleted file mode 100644 index e69de29bb..000000000 diff --git a/app/models/concerns/display_cells.rb b/app/models/concerns/display_cells.rb new file mode 100644 index 000000000..1b95a91d5 --- /dev/null +++ b/app/models/concerns/display_cells.rb @@ -0,0 +1,200 @@ +# frozen_string_literal: true + +module DisplayCells + extend ActiveSupport::Concern + + included do + def display_inputs(previews: false) + if is_cellbase + cellbase_display_inputs + else + cell_inputs_for_display = cell_inputs.order(id: :asc) + cell_inputs_for_display = cell_inputs_for_display.limit(10) if previews + normal_tx_display_inputs(cell_inputs_for_display) + end + end + + def display_outputs(previews: false) + if is_cellbase + cellbase_display_outputs + else + cell_outputs_for_display = outputs.order(id: :asc) + cell_outputs_for_display = cell_outputs_for_display.limit(10) if previews + normal_tx_display_outputs(cell_outputs_for_display) + end + end + + def cellbase_display_inputs + cellbase = Cellbase.new(block) + [ + CkbUtils.hash_value_to_s( + id: nil, + from_cellbase: true, + capacity: nil, + occupied_capacity: nil, + address_hash: nil, + target_block_number: cellbase.target_block_number, + generated_tx_hash: tx_hash + ) + ] + end + + def cellbase_display_outputs + cell_outputs_for_display = outputs.order(id: :asc) + cellbase = Cellbase.new(block) + cell_outputs_for_display.map do |output| + consumed_tx_hash = output.live? ? nil : output.consumed_by.tx_hash + CkbUtils.hash_value_to_s( + id: output.id, + capacity: output.capacity, + occupied_capacity: output.occupied_capacity, + address_hash: output.address_hash, + target_block_number: cellbase.target_block_number, + base_reward: cellbase.base_reward, + commit_reward: cellbase.commit_reward, + proposal_reward: cellbase.proposal_reward, + secondary_reward: cellbase.secondary_reward, + status: output.status, + consumed_tx_hash: consumed_tx_hash + ) + end + end + + def normal_tx_display_inputs(cell_inputs_for_display) + cell_inputs_for_display.map do |cell_input| + previous_cell_output = cell_input.previous_cell_output + unless previous_cell_output + next({ + from_cellbase: false, + capacity: "", + occupied_capacity: "", + address_hash: "", + generated_tx_hash: cell_input.previous_tx_hash, + cell_index: cell_input.previous_index, + since: { + raw: hex_since(cell_input.since.to_i), + median_timestamp: cell_input.block&.median_timestamp.to_i + } + }) + end + + display_input = { + id: previous_cell_output.id, + from_cellbase: false, + capacity: previous_cell_output.capacity, + occupied_capacity: previous_cell_output.occupied_capacity, + address_hash: previous_cell_output.address_hash, + generated_tx_hash: previous_cell_output.ckb_transaction.tx_hash, + cell_index: previous_cell_output.cell_index, + cell_type: previous_cell_output.cell_type, + since: { + raw: hex_since(cell_input.since.to_i), + median_timestamp: cell_input.block&.median_timestamp.to_i + } + } + + if previous_cell_output.nervos_dao_withdrawing? + display_input.merge!(attributes_for_dao_input(previous_cell_output)) + end + if previous_cell_output.nervos_dao_deposit? + display_input.merge!(attributes_for_dao_input(cell_outputs[cell_input.index], false)) + end + if previous_cell_output.udt? + display_input.merge!(attributes_for_udt_cell(previous_cell_output)) + end + if previous_cell_output.cell_type.in?(%w(m_nft_issuer m_nft_class m_nft_token)) + display_input.merge!(attributes_for_m_nft_cell(previous_cell_output)) + end + if previous_cell_output.cell_type.in?(%w(nrc_721_token nrc_721_factory)) + display_input.merge!(attributes_for_nrc_721_cell(previous_cell_output)) + end + + CkbUtils.hash_value_to_s(display_input) + end + end + + def normal_tx_display_outputs(cell_outputs_for_display) + cell_outputs_for_display.map do |output| + consumed_tx_hash = output.live? ? nil : output.consumed_by&.tx_hash + display_output = { + id: output.id, + capacity: output.capacity, + occupied_capacity: output.occupied_capacity, + address_hash: output.address_hash, + status: output.status, + consumed_tx_hash: consumed_tx_hash, + cell_type: output.cell_type + } + + display_output.merge!(attributes_for_udt_cell(output)) if output.udt? + display_output.merge!(attributes_for_cota_registry_cell(output)) if output.cota_registry? + display_output.merge!(attributes_for_cota_regular_cell(output)) if output.cota_regular? + if output.cell_type.in?(%w(m_nft_issuer m_nft_class m_nft_token)) + display_output.merge!(attributes_for_m_nft_cell(output)) + end + if output.cell_type.in?(%w(nrc_721_token nrc_721_factory)) + display_output.merge!(attributes_for_nrc_721_cell(output)) + end + + CkbUtils.hash_value_to_s(display_output) + end + end + + def attributes_for_udt_cell(udt_cell) + info = CkbUtils.hash_value_to_s(udt_cell.udt_info) + { udt_info: info, extra_info: info } + end + + def attributes_for_cota_registry_cell(cota_cell) + info = cota_cell.cota_registry_info + { cota_registry_info: info, extra_info: info } + end + + def attributes_for_cota_regular_cell(cota_cell) + info = cota_cell.cota_regular_info + { cota_regular_info: info, extra_info: info } + end + + def attributes_for_m_nft_cell(m_nft_cell) + info = m_nft_cell.m_nft_info + { m_nft_info: info, extra_info: info } + end + + def attributes_for_nrc_721_cell(nrc_721_cell) + info = nrc_721_cell.nrc_721_nft_info + { nrc_721_token_info: info, extra_info: info } + end + + def attributes_for_dao_input(nervos_dao_withdrawing_cell, is_phase2 = true) + nervos_dao_withdrawing_cell_generated_tx = nervos_dao_withdrawing_cell.ckb_transaction + nervos_dao_deposit_cell = nervos_dao_withdrawing_cell_generated_tx. + cell_inputs.order(:id)[nervos_dao_withdrawing_cell.cell_index].previous_cell_output + # start block: the block contains the transaction which generated the deposit cell output + compensation_started_block = Block.select(:number, :timestamp).find(nervos_dao_deposit_cell.block.id) + # end block: the block contains the transaction which generated the withdrawing cell + compensation_ended_block = Block.select(:number, :timestamp). + find(nervos_dao_withdrawing_cell_generated_tx.block_id) + interest = CkbUtils.dao_interest(nervos_dao_withdrawing_cell) + + attributes = { + compensation_started_block_number: compensation_started_block.number, + compensation_started_timestamp: compensation_started_block.timestamp, + compensation_ended_block_number: compensation_ended_block.number, + compensation_ended_timestamp: compensation_ended_block.timestamp, + interest: interest + } + + if is_phase2 + number, timestamp = Block.where(id: block_id).pick(:number, :timestamp) # locked_until_block + attributes[:locked_until_block_number] = number + attributes[:locked_until_block_timestamp] = timestamp + end + + CkbUtils.hash_value_to_s(attributes) + end + + def hex_since(int_since_value) + return "0x#{int_since_value.to_s(16).rjust(16, '0')}" + end + end +end diff --git a/app/serializers/script_serializer.rb b/app/serializers/script_serializer.rb deleted file mode 100644 index 28922724d..000000000 --- a/app/serializers/script_serializer.rb +++ /dev/null @@ -1,35 +0,0 @@ -class ScriptSerializer - include FastJsonapi::ObjectSerializer - - attributes :code_hash, :hash_type - - attribute :capacity_of_deployed_cells do |object, params| - params[:deployed_cells].sum(:capacity) - end - - attribute :capacity_of_referring_cells do |object, params| - - # it's not a rails query result, but a regular array, - # so let's get the sum via "inject" function - params[:referring_cells].inject(0){ |sum, x| sum + x.capacity } - end - - attribute :deployed_cells do |object, params| - params[:deployed_cells].map do |output_cell| - CellOutputDataSerializer.new(output_cell).serializable_hash - end - end - - attribute :referring_cells do |object, params| - params[:referring_cells].map do |output_cell| - CellOutputDataSerializer.new(output_cell).serializable_hash - end - end - - attribute :transactions do |object, params| - ckb_transactions = params[:ckb_transactions] - ckb_transactions.map do |ckb_transaction| - CkbTransactionSerializer.new(ckb_transaction).serializable_hash - end - end -end diff --git a/config/routes.rb b/config/routes.rb index 8b2f4784e..c8dba6a47 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -25,6 +25,8 @@ resources :block_transactions, only: :show resources :addresses, only: :show get "/transactions/:id", to: "ckb_transactions#show", as: "ckb_transaction" + get "/transactions/:id/display_inputs", to: "ckb_transactions#display_inputs" + get "/transactions/:id/display_outputs", to: "ckb_transactions#display_outputs" get "/transactions", to: "ckb_transactions#index", as: "ckb_transactions" post "/transactions/query", to: "ckb_transactions#query", as: "query_ckb_transactions" resources :cell_input_lock_scripts, only: :show