diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ed096f644..237d746ef 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -19,28 +19,9 @@ jobs: build: runs-on: ubuntu-latest outputs: - image-name: '${{ steps.docker_build.outputs.imageFullName }}' - image-tag: '${{ steps.docker_build.outputs.tags }}' + image-name: "${{ steps.docker_build.outputs.imageFullName }}" + image-tag: "${{ steps.docker_build.outputs.tags }}" steps: - - name: Update Package List and Remove Dotnet - run: | - sudo apt-get update - sudo apt-get remove -y '^dotnet-.*' - - name: Free Disk Space (Ubuntu) - uses: jlumbroso/free-disk-space@main - with: - # this might remove tools that are actually needed, - # if set to "true" but frees about 6 GB - tool-cache: false - - # all of these default to true, but feel free to set to - # "false" if necessary for your workflow - android: true - dotnet: true - haskell: true - large-packages: true - docker-images: true - swap-storage: true - uses: actions/checkout@v3 with: fetch-depth: 0 @@ -56,3 +37,7 @@ jobs: githubOrg: magickbase # optional username: ${{ secrets.GHCR_USERNAME }} password: ${{ secrets.GHCR_TOKEN }} + - name: DeleteUselessImages + run: docker images && docker images| grep "ckb-explorer-api" | grep -v "IMAGE ID" | awk 'NR>1 {print $3}' | xargs -I {} docker rmi {} + - name: CheckImage + run: docker images && df -hT && df -ih \ No newline at end of file diff --git a/.github/workflows/ruby.yml b/.github/workflows/ruby.yml index 980050669..89e0ebfd9 100644 --- a/.github/workflows/ruby.yml +++ b/.github/workflows/ruby.yml @@ -18,7 +18,7 @@ jobs: - 5432:5432 redis: image: redis - ports: ['6379:6379'] + ports: ["6379:6379"] options: --entrypoint redis-server steps: - uses: actions/checkout@v3 @@ -32,3 +32,9 @@ jobs: bundle exec rails db:setup RAILS_ENV=test cp .env.test.local.travis .env.test.local bundle exec rails test RAILS_ENV=test + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v4 + with: + file: coverage/coverage.xml + fail_ci_if_error: true + token: ${{ secrets.CODECOV_TOKEN }} diff --git a/Gemfile b/Gemfile index 1aa6d32b1..4a074b038 100644 --- a/Gemfile +++ b/Gemfile @@ -87,7 +87,6 @@ group :development, :test do end group :test do - gem "codecov", require: false gem "database_cleaner" gem "database_cleaner-active_record" gem "minitest-reporters" @@ -95,6 +94,7 @@ group :test do gem "shoulda-context" gem "shoulda-matchers" gem "simplecov", require: false + gem "simplecov-cobertura" gem "vcr" gem "webmock" end diff --git a/Gemfile.lock b/Gemfile.lock index 86ce13983..462cbd5bf 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -139,8 +139,6 @@ GEM msgpack (~> 1.2) builder (3.2.4) byebug (11.1.3) - codecov (0.6.0) - simplecov (>= 0.15, < 0.22) coderay (1.1.3) concurrent-ruby (1.2.2) config (4.1.0) @@ -455,6 +453,9 @@ GEM docile (~> 1.1) simplecov-html (~> 0.11) simplecov_json_formatter (~> 0.1) + simplecov-cobertura (2.1.0) + rexml + simplecov (~> 0.19) simplecov-html (0.12.3) simplecov_json_formatter (0.1.4) siphash (0.0.1) @@ -518,7 +519,6 @@ DEPENDENCIES bitcoinrb bootsnap ckb-sdk-ruby! - codecov config dalli database_cleaner @@ -569,6 +569,7 @@ DEPENDENCIES shoulda-matchers sidekiq simplecov + simplecov-cobertura solargraph spring spring-watcher-listen diff --git a/app/controllers/api/v1/address_dao_transactions_controller.rb b/app/controllers/api/v1/address_dao_transactions_controller.rb index 4231efae1..215d6a506 100644 --- a/app/controllers/api/v1/address_dao_transactions_controller.rb +++ b/app/controllers/api/v1/address_dao_transactions_controller.rb @@ -8,15 +8,16 @@ def show address = Address.find_address!(params[:id]) raise Api::V1::Exceptions::AddressNotFoundError if address.is_a?(NullAddress) - ckb_dao_transactions = address.ckb_dao_transactions.select(:id, :tx_hash, :block_id, :block_number, :block_timestamp, :is_cellbase, :updated_at, :created_at).recent.page(@page).per(@page_size).fast_page + ckb_dao_transactions = address.ckb_dao_transactions.select(:id, :tx_hash, :block_id, :block_number, :tags, :block_timestamp, :is_cellbase, :updated_at, :created_at). + recent.page(@page).per(@page_size).fast_page json = Rails.cache.realize(ckb_dao_transactions.cache_key, version: ckb_dao_transactions.cache_version) do records_counter = RecordCounters::AddressDaoTransactions.new(address) - options = FastJsonapi::PaginationMetaGenerator.new(request: request, records: ckb_dao_transactions, page: @page, page_size: @page_size, records_counter: records_counter).call + options = FastJsonapi::PaginationMetaGenerator.new(request:, records: ckb_dao_transactions, page: @page, page_size: @page_size, records_counter:).call CkbTransactionsSerializer.new(ckb_dao_transactions, options.merge(params: { previews: true })).serialized_json end - render json: json + render json: end private @@ -28,7 +29,7 @@ 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 diff --git a/app/controllers/api/v1/address_udt_transactions_controller.rb b/app/controllers/api/v1/address_udt_transactions_controller.rb index c0d81f545..b4aec1fcb 100644 --- a/app/controllers/api/v1/address_udt_transactions_controller.rb +++ b/app/controllers/api/v1/address_udt_transactions_controller.rb @@ -13,7 +13,7 @@ def show raise Api::V1::Exceptions::UdtNotFoundError if udt.blank? ckb_dao_transactions = address.ckb_udt_transactions(udt.id). - select(:id, :tx_hash, :block_id, :block_number, :block_timestamp, :is_cellbase, :updated_at, :created_at). + select(:id, :tx_hash, :block_id, :block_number, :block_timestamp, :is_cellbase, :updated_at, :created_at, :tags). recent.page(@page).per(@page_size).fast_page json = Rails.cache.realize(ckb_dao_transactions.cache_key, version: ckb_dao_transactions.cache_version) do diff --git a/app/controllers/api/v1/block_transactions_controller.rb b/app/controllers/api/v1/block_transactions_controller.rb index 7ee14019a..bd71f0888 100644 --- a/app/controllers/api/v1/block_transactions_controller.rb +++ b/app/controllers/api/v1/block_transactions_controller.rb @@ -6,7 +6,7 @@ class BlockTransactionsController < ApplicationController def show block = Block.find_by!(block_hash: params[:id]) ckb_transactions = block.ckb_transactions. - select(:id, :tx_hash, :block_id, :block_number, :block_timestamp, :is_cellbase, :updated_at, :created_at). + select(:id, :tx_hash, :block_id, :block_number, :block_timestamp, :is_cellbase, :updated_at, :created_at, :tags). order(is_cellbase: :desc, id: :asc) if params[:tx_hash].present? @@ -24,16 +24,16 @@ def show records_counter = RecordCounters::BlockTransactions.new(ckb_transactions) ckb_transactions = ckb_transactions.page(@page).per(@page_size).fast_page options = FastJsonapi::PaginationMetaGenerator.new( - request: request, + request:, records: ckb_transactions, page: @page, page_size: @page_size, - records_counter: records_counter + records_counter:, ).call json = CkbTransactionsSerializer.new(ckb_transactions, options.merge(params: { previews: true })).serialized_json - render json: json + render json: end rescue ActiveRecord::RecordNotFound raise Api::V1::Exceptions::BlockTransactionsNotFoundError @@ -48,7 +48,7 @@ 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 diff --git a/app/controllers/api/v1/ckb_transactions_controller.rb b/app/controllers/api/v1/ckb_transactions_controller.rb index 82e64b4b8..dfcb070f9 100644 --- a/app/controllers/api/v1/ckb_transactions_controller.rb +++ b/app/controllers/api/v1/ckb_transactions_controller.rb @@ -2,13 +2,13 @@ module Api module V1 class CkbTransactionsController < ApplicationController before_action :validate_query_params, only: %i[show] - before_action :find_transaction,only: %i[show] + before_action :find_transaction, only: %i[show] before_action :validate_pagination_params, :pagination_params, only: %i[index] def index if from_home_page? ckb_transactions = CkbTransaction.tx_committed.recent.normal.select( - :id, :tx_hash, :block_number, :block_timestamp, :live_cell_changes, :capacity_involved, :updated_at, :created_at + :id, :tx_hash, :block_number, :block_timestamp, :live_cell_changes, :capacity_involved, :updated_at, :created_at, :tags ).limit((Settings.homepage_transactions_records_count || 15).to_i) json = Rails.cache.realize(ckb_transactions.cache_key, @@ -18,7 +18,7 @@ def index 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 + :id, :tx_hash, :block_number, :block_timestamp, :live_cell_changes, :capacity_involved, :updated_at, :created_at, :tags ) params[:sort] ||= "id.desc" @@ -83,7 +83,7 @@ def query records_counter = RecordCounters::Transactions.new CkbTransaction.recent.normal.page(@page).per(@page_size).fast_page end - ckb_transactions = ckb_transactions.select(:id, :tx_hash, :block_id, + ckb_transactions = ckb_transactions.select(:id, :tx_hash, :block_id, :tags, :block_number, :block_timestamp, :is_cellbase, :updated_at, :created_at) json = Rails.cache.realize(ckb_transactions.cache_key, @@ -107,8 +107,8 @@ def show expires_in 10.seconds, public: true, must_revalidate: true render json: CkbTransactionSerializer.new(@ckb_transaction, { - params: { display_cells: params.fetch(:display_cells, true) - }}) + params: { display_cells: params.fetch(:display_cells, true) }, + }) end private diff --git a/app/controllers/api/v1/contract_transactions_controller.rb b/app/controllers/api/v1/contract_transactions_controller.rb index de2f093ea..3a2e5b91a 100644 --- a/app/controllers/api/v1/contract_transactions_controller.rb +++ b/app/controllers/api/v1/contract_transactions_controller.rb @@ -12,7 +12,7 @@ def show expires_in 10.seconds, public: true, must_revalidate: true, stale_while_revalidate: 5.seconds ckb_transactions = dao_contract.ckb_transactions.includes(:cell_inputs, :cell_outputs).tx_committed.select( - :id, :tx_hash, :block_id, :block_number, :block_timestamp, :is_cellbase, :updated_at, :created_at + :id, :tx_hash, :block_id, :block_number, :block_timestamp, :is_cellbase, :updated_at, :created_at, :tags ).order("ckb_transactions.block_timestamp desc nulls last, ckb_transactions.id desc") if params[:tx_hash].present? @@ -28,12 +28,12 @@ def show end ckb_transactions = ckb_transactions.page(@page).per(@page_size).fast_page - options = FastJsonapi::PaginationMetaGenerator.new(request: request, records: ckb_transactions, + options = FastJsonapi::PaginationMetaGenerator.new(request:, records: ckb_transactions, page: @page, page_size: @page_size).call json = CkbTransactionsSerializer.new(ckb_transactions, options.merge(params: { previews: true })).serialized_json - render json: json + render json: end end diff --git a/app/controllers/api/v1/suggest_queries_controller.rb b/app/controllers/api/v1/suggest_queries_controller.rb index 17a2260b2..318154e9f 100644 --- a/app/controllers/api/v1/suggest_queries_controller.rb +++ b/app/controllers/api/v1/suggest_queries_controller.rb @@ -1,28 +1,13 @@ module Api module V1 class SuggestQueriesController < ApplicationController - before_action :validate_query_params - def index - json_response = SuggestQuery.new(params[:q]).find! + json_response = SuggestQuery.new(params[:q], params[:filter_by]).find! render json: json_response rescue ActiveRecord::RecordNotFound raise Api::V1::Exceptions::SuggestQueryResultNotFoundError end - - private - - def validate_query_params - validator = Validations::SuggestQuery.new(params) - - if validator.invalid? - errors = validator.error_object[:errors] - status = validator.error_object[:status] - - render json: errors, status: - end - end end end end diff --git a/app/controllers/api/v1/xudts_controller.rb b/app/controllers/api/v1/xudts_controller.rb index 2065208f9..de78d42d4 100644 --- a/app/controllers/api/v1/xudts_controller.rb +++ b/app/controllers/api/v1/xudts_controller.rb @@ -5,12 +5,21 @@ class XudtsController < ApplicationController before_action :validate_pagination_params, :pagination_params, only: :index def index - udts = Udt.xudt + scope = Udt.xudt.includes(:xudt_tag) - if stale?(udts) + if params[:symbol].present? + scope = scope.where("LOWER(symbol) = ?", params[:symbol].downcase) + end + + if params[:tags].present? + tags = parse_tags + scope = scope.joins(:xudt_tag).where("xudt_tags.tags @> array[?]::varchar[]", tags).select("udts.*") unless tags.empty? + end + + if stale?(scope) expires_in 30.minutes, public: true, stale_while_revalidate: 10.minutes, stale_if_error: 10.minutes - udts = sort_udts(udts).page(@page).per(@page_size).fast_page + udts = sort_udts(scope).page(@page).per(@page_size).fast_page options = FastJsonapi::PaginationMetaGenerator.new( request:, records: udts, @@ -73,6 +82,11 @@ def sort_udts(records) records.order("#{sort} #{order}") end + + def parse_tags + tags = params[:tags].split(",") + tags & XudtTag::VALID_TAGS + end end end end diff --git a/app/controllers/api/v2/bitcoin_statistics_controller.rb b/app/controllers/api/v2/bitcoin_statistics_controller.rb new file mode 100644 index 000000000..5eaad1dd6 --- /dev/null +++ b/app/controllers/api/v2/bitcoin_statistics_controller.rb @@ -0,0 +1,11 @@ +module Api + module V2 + class BitcoinStatisticsController < BaseController + def index + expires_in 15.minutes, public: true, stale_while_revalidate: 5.minutes, stale_if_error: 5.minutes + + render json: { data: BitcoinStatistic.all } + end + end + end +end diff --git a/app/controllers/api/v2/nft/holders_controller.rb b/app/controllers/api/v2/nft/holders_controller.rb index b924febbb..045d7434d 100644 --- a/app/controllers/api/v2/nft/holders_controller.rb +++ b/app/controllers/api/v2/nft/holders_controller.rb @@ -3,7 +3,7 @@ module V2 module NFT class HoldersController < BaseController def index - token_items = find_collection.items.joins(:owner) + token_items = find_collection.items.normal.joins(:owner) if params[:address_hash].present? owner = Address.find_address!(params[:address_hash]) diff --git a/app/controllers/api/v2/nft/items_controller.rb b/app/controllers/api/v2/nft/items_controller.rb index a44cbc223..13ceb8aef 100644 --- a/app/controllers/api/v2/nft/items_controller.rb +++ b/app/controllers/api/v2/nft/items_controller.rb @@ -3,7 +3,7 @@ module V2 class NFT::ItemsController < BaseController before_action :find_collection def index - scope = TokenItem.includes(:collection) + scope = TokenItem.normal.includes(:collection) if params[:owner] @owner = Address.find_address!(params[:owner]) scope = scope.where(owner_id: @owner.id) diff --git a/app/controllers/api/v2/rgb_transactions_controller.rb b/app/controllers/api/v2/rgb_transactions_controller.rb new file mode 100644 index 000000000..3c8e22a6c --- /dev/null +++ b/app/controllers/api/v2/rgb_transactions_controller.rb @@ -0,0 +1,15 @@ +module Api + module V2 + class RgbTransactionsController < BaseController + def index + @ckb_transactions = RgbTransactions::Index.run!(transaction_params) + end + + private + + def transaction_params + params.permit(:sort, :leap_direction, :page, :page_size) + end + end + end +end diff --git a/app/interactions/addresses/live_cells.rb b/app/interactions/addresses/live_cells.rb index 5e6815eb6..5c0131b1a 100644 --- a/app/interactions/addresses/live_cells.rb +++ b/app/interactions/addresses/live_cells.rb @@ -13,9 +13,15 @@ def execute raise AddressNotFoundError if address.is_a?(NullAddress) order_by, asc_or_desc = live_cells_ordering - records = CellOutput.live.where(address_id: address.map(&:id)). - order(order_by => asc_or_desc). - page(page).per(page_size).fast_page + records = + if order_by == "block_timestamp" + ids = CellOutput.live.where(address_id: address.map(&:id)).pluck(:id) + CellOutput.where(id: ids).order(order_by => asc_or_desc).page(page).per(page_size).fast_page + else + CellOutput.live.where(address_id: address.map(&:id)). + order(order_by => asc_or_desc). + page(page).per(page_size).fast_page + end options = FastJsonapi::PaginationMetaGenerator.new( request:, records:, page:, page_size:, ).call diff --git a/app/interactions/addresses/pending_transactions.rb b/app/interactions/addresses/pending_transactions.rb index 49aad2b40..191887f67 100644 --- a/app/interactions/addresses/pending_transactions.rb +++ b/app/interactions/addresses/pending_transactions.rb @@ -54,7 +54,7 @@ def transactions_ordering end def select_fields - %i[id tx_hash block_id block_number block_timestamp + %i[id tx_hash block_id block_number block_timestamp tags is_cellbase updated_at capacity_involved created_at] end diff --git a/app/interactions/rgb_transactions/index.rb b/app/interactions/rgb_transactions/index.rb new file mode 100644 index 000000000..882394976 --- /dev/null +++ b/app/interactions/rgb_transactions/index.rb @@ -0,0 +1,49 @@ +module RgbTransactions + class Index < ActiveInteraction::Base + string :sort, default: "number.desc" + string :leap_direction, default: nil + integer :page, default: 1 + integer :page_size, default: CkbTransaction.default_per_page + + def execute + order_by, asc_or_desc = transaction_ordering + transactions = CkbTransaction.where("tags @> array[?]::varchar[]", ["rgbpp"]). + order(order_by => asc_or_desc) + + if leap_direction.present? + transactions = transactions.where( + "CASE + WHEN (SELECT COUNT(*) FROM bitcoin_vins WHERE ckb_transaction_id = ckb_transactions.id) < + (SELECT COUNT(*) FROM bitcoin_vouts WHERE ckb_transaction_id = ckb_transactions.id AND op_return = false) + THEN 'in' + WHEN (SELECT COUNT(*) FROM bitcoin_vins WHERE ckb_transaction_id = ckb_transactions.id) > + (SELECT COUNT(*) FROM bitcoin_vouts WHERE ckb_transaction_id = ckb_transactions.id AND op_return = false) + THEN 'out' + ELSE 'equal' + END = ?", leap_direction + ) + end + + transactions.page(page).per(page_size) + end + + private + + def transaction_ordering + sort_by, sort_order = sort.split(".", 2) + sort_by = + case sort_by + when "confirmation", "number" + "block_number" + when "time" + "block_timestamp" + end + + if sort_order.nil? || !sort_order.match?(/^(asc|desc)$/i) + sort_order = "asc" + end + + [sort_by, sort_order] + end + end +end diff --git a/app/interactions/udts/ckb_transactions.rb b/app/interactions/udts/ckb_transactions.rb index 6c6d1dad3..09186717e 100644 --- a/app/interactions/udts/ckb_transactions.rb +++ b/app/interactions/udts/ckb_transactions.rb @@ -26,10 +26,11 @@ def execute address = Addresses::Explore.run!(key: address_hash) raise AddressNotFoundError if address.is_a?(NullAddress) - ckb_transactions = ckb_transactions.joins(:account_books).where(account_books: { address_id: address.map(&:id) }) + ckb_transactions = ckb_transactions.joins(:account_books). + where(account_books: { address_id: address.map(&:id) }).distinct end - records = ckb_transactions.page(page).per(page_size).fast_page + records = ckb_transactions.page(page).per(page_size) options = FastJsonapi::PaginationMetaGenerator.new( request:, records:, page:, page_size:, ).call @@ -53,7 +54,7 @@ def validate_tx_hash! def select_fields %i[id tx_hash block_id block_number block_timestamp - is_cellbase updated_at created_at] + is_cellbase updated_at created_at tags] end end end diff --git a/app/jobs/import_bitcoin_utxo_job.rb b/app/jobs/import_bitcoin_utxo_job.rb index 6c3c618c0..7c647b903 100644 --- a/app/jobs/import_bitcoin_utxo_job.rb +++ b/app/jobs/import_bitcoin_utxo_job.rb @@ -15,7 +15,7 @@ def perform(cell_id) Rails.logger.info("Importing bitcoin utxo #{txid} out_index #{out_index}") vout_attributes = [] # build bitcoin transaction - raw_tx = fetch_raw_transaction(txid) + raw_tx = rpc.getrawtransaction(txid, 2) tx = build_transaction!(raw_tx) # build op_returns op_returns = build_op_returns!(raw_tx, tx, cell_output.ckb_transaction, vout_attributes) @@ -36,14 +36,16 @@ def build_transaction!(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"]) + # raw transactions may not include the block hash + if raw_tx["blockhash"].present? + block_header = rpc.getblockheader(raw_tx["blockhash"]) + end 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"], + block_height: block_header&.dig("height") || 0, ) end @@ -103,14 +105,6 @@ def build_address!(address_hash, cell_output) bitcoin_address end - def fetch_raw_transaction(txid) - Rails.cache.fetch("bitcoin_transactions/#{txid}", expires_in: 1.hour) do - rpc.getrawtransaction(txid, 2) - rescue StandardError => e - raise ArgumentError, "get bitcoin raw transaction #{txid} failed: #{e}" - end - end - def rpc @rpc ||= Bitcoin::Rpc.instance end diff --git a/app/models/address.rb b/app/models/address.rb index d207e27b8..b4028c36a 100644 --- a/app/models/address.rb +++ b/app/models/address.rb @@ -101,7 +101,6 @@ def self.find_or_create_by_lock(lock_script) # @return [Address] def self.find_or_create_address(lock_script, block_timestamp, lock_script_id = nil) lock_hash = lock_script.compute_hash - address_hash = CkbUtils.generate_address(lock_script, CKB::Address::Version::CKB2019) address_hash_2021 = CkbUtils.generate_address(lock_script, CKB::Address::Version::CKB2021) address = Address.find_by(lock_hash:) diff --git a/app/models/bitcoin_statistic.rb b/app/models/bitcoin_statistic.rb new file mode 100644 index 000000000..49bae6460 --- /dev/null +++ b/app/models/bitcoin_statistic.rb @@ -0,0 +1,38 @@ +class BitcoinStatistic < ApplicationRecord + default_scope { order(timestamp: :asc) } + + def self.refresh + transaction do + current_time = Time.current + end_time = Time.zone.local(current_time.year, current_time.month, current_time.day, current_time.hour, current_time.min) + start_time = end_time - 30.minutes + + Rails.logger.info "current_time: #{current_time}, start_time: #{start_time}, end_time: #{end_time}" + + # Count the number of newly generated addresses within half an hour before the current time point + addresses_count = BitcoinAddress.where(created_at: start_time..end_time).count + # Count the number of newly generated transactions within half an hour before the current time point + transactions_count = BitcoinTransaction.where(created_at: start_time..end_time).count + Rails.logger.info "update bitcoin_statistics addresses_count(#{addresses_count}) transactions_count(#{transactions_count})" + + statistic = BitcoinStatistic.find_or_initialize_by(timestamp: end_time.utc.to_i * 1000) + statistic.addresses_count = addresses_count + statistic.transactions_count = transactions_count + statistic.save! + end + end +end + +# == Schema Information +# +# Table name: bitcoin_statistics +# +# id :bigint not null, primary key +# timestamp :bigint +# transactions_count :integer default(0) +# addresses_count :integer default(0) +# +# Indexes +# +# index_bitcoin_statistics_on_timestamp (timestamp) UNIQUE +# diff --git a/app/models/bitcoin_transaction.rb b/app/models/bitcoin_transaction.rb index 25e2c0fc6..fa829f577 100644 --- a/app/models/bitcoin_transaction.rb +++ b/app/models/bitcoin_transaction.rb @@ -12,13 +12,25 @@ def confirmations nil end - tip_block_height ? tip_block_height - block_height : 0 + return 0 unless tip_block_height + + refresh_block_height! if block_hash.blank? + block_height == 0 ? 0 : tip_block_height - block_height end def ckb_transaction_hash ckb_transaction = bitcoin_vouts&.take&.ckb_transaction return ckb_transaction.tx_hash if ckb_transaction end + + def refresh_block_height! + rpc = Bitcoin::Rpc.instance + raw_transaction = rpc.getrawtransaction(txid, 2) + block_header = rpc.getblockheader(raw_transaction["blockhash"]) + update(block_hash: raw_transaction["blockhash"], block_height: block_header["height"]) + rescue StandardError => e + Rails.logger.error "refresh block height error: #{e.message}" + end end # == Schema Information diff --git a/app/models/bitcoin_vout.rb b/app/models/bitcoin_vout.rb index 6cd74fce2..a8cec6957 100644 --- a/app/models/bitcoin_vout.rb +++ b/app/models/bitcoin_vout.rb @@ -3,7 +3,9 @@ class BitcoinVout < ApplicationRecord belongs_to :bitcoin_address, optional: true belongs_to :ckb_transaction, optional: true belongs_to :cell_output, optional: true - belongs_to :ckb_address, class_name: "Address", optional: true + belongs_to :ckb_address, class_name: "Address", foreign_key: "address_id", optional: true + + scope :without_op_return, -> { where(op_return: false) } def commitment return unless op_return? diff --git a/app/models/ckb_sync/api.rb b/app/models/ckb_sync/api.rb index b7b3e2f7e..cf57e4cf2 100644 --- a/app/models/ckb_sync/api.rb +++ b/app/models/ckb_sync/api.rb @@ -75,7 +75,7 @@ def spore_cluster_code_hashes if mode == CKB::MODE::MAINNET [Settings.spore_cluster1_code_hash] else - [Settings.spore_cluster1_code_hash, Settings.spore_cluster2_code_hash] + [Settings.spore_cluster1_code_hash, Settings.spore_cluster2_code_hash, Settings.spore_cluster3_code_hash] end end @@ -83,7 +83,7 @@ def spore_cell_code_hashes if mode == CKB::MODE::MAINNET [Settings.spore_cell1_code_hash] else - [Settings.spore_cell1_code_hash, Settings.spore_cell2_code_hash] + [Settings.spore_cell1_code_hash, Settings.spore_cell2_code_hash, Settings.spore_cell3_code_hash] end end diff --git a/app/models/ckb_sync/new_node_data_processor.rb b/app/models/ckb_sync/new_node_data_processor.rb index 4b1a557e8..e29782dfa 100644 --- a/app/models/ckb_sync/new_node_data_processor.rb +++ b/app/models/ckb_sync/new_node_data_processor.rb @@ -666,6 +666,7 @@ def build_udts!(local_block, outputs, outputs_data) unless Udt.where(type_hash:).exists? nft_token_attr = { full_name: nil, icon_file: nil, published: false, symbol: nil, decimal: nil, nrc_factory_cell_id: nil } + issuer_address = CkbUtils.generate_address(output.lock, CKB::Address::Version::CKB2021) if cell_type == "m_nft_token" m_nft_class_type = TypeScript.where(code_hash: CkbSync::Api.instance.token_class_script_code_hash, args: output.type.args[0..49]).first @@ -722,6 +723,7 @@ def build_udts!(local_block, outputs, outputs_data) nft_token_attr[:published] = true end if cell_type == "xudt" + issuer_address = Address.find_by(lock_hash: output.type.args[0..65])&.address_hash items.each_with_index do |output, index| if output.type&.code_hash == CkbSync::Api.instance.unique_cell_code_hash info = CkbUtils.parse_unique_cell(outputs_data[tx_index][index]) @@ -732,10 +734,9 @@ def build_udts!(local_block, outputs, outputs_data) end end end - # fill issuer_address after publish the token udts_attributes << { type_hash:, udt_type: parsed_udt_type, block_timestamp: local_block.timestamp, args: output.type.args, - code_hash: output.type.code_hash, hash_type: output.type.hash_type + code_hash: output.type.code_hash, hash_type: output.type.hash_type, issuer_address: }.merge(nft_token_attr) end end diff --git a/app/models/concerns/ckb_transactions/bitcoin.rb b/app/models/concerns/ckb_transactions/bitcoin.rb index 7895dafda..f050e7542 100644 --- a/app/models/concerns/ckb_transactions/bitcoin.rb +++ b/app/models/concerns/ckb_transactions/bitcoin.rb @@ -8,7 +8,7 @@ module Bitcoin has_many :bitcoin_vins def rgb_transaction? - bitcoin_vins.exists? || bitcoin_vouts.exists? + !!tags&.include?("rgbpp") end def rgb_commitment @@ -22,9 +22,28 @@ def rgb_commitment def rgb_txid return unless rgb_transaction? - bitcoin_transaction = BitcoinTransaction.includes(:bitcoin_vouts).find_by(bitcoin_vouts: { ckb_transaction_id: id }) bitcoin_transaction&.txid end + + def leap_direction + return unless rgb_transaction? + + return "in" if bitcoin_vins.count < bitcoin_vouts.without_op_return.count + return "out" if bitcoin_vins.count > bitcoin_vouts.without_op_return.count + + nil + end + + def rgb_cell_changes + return 0 unless rgb_transaction? + + bitcoin_vouts.without_op_return.count - bitcoin_vins.count + end + + def bitcoin_transaction + BitcoinTransaction.includes(:bitcoin_vouts). + find_by(bitcoin_vouts: { ckb_transaction_id: id }) + end end end end diff --git a/app/models/suggest_query.rb b/app/models/suggest_query.rb index a749a69b4..6957a2e7a 100644 --- a/app/models/suggest_query.rb +++ b/app/models/suggest_query.rb @@ -1,22 +1,26 @@ class SuggestQuery - def initialize(query_key) + attr_reader :query_key, :filter_by + + def initialize(query_key, filter_by = nil) @query_key = query_key + @filter_by = filter_by end def find! - find_record_by_query_key! + if filter_by.present? && filter_by.to_i.zero? + aggregate_query! + else + single_query! + end end - private - - attr_reader :query_key - - def find_record_by_query_key! + def single_query! result = if QueryKeyUtils.integer_string?(query_key) find_cached_block elsif QueryKeyUtils.valid_hex?(query_key) - find_by_hex + res = query_methods.map(&:call).compact + return res.first if res.any? elsif QueryKeyUtils.valid_address?(query_key) find_cached_address end @@ -26,11 +30,51 @@ def find_record_by_query_key! result end - def find_cached_block - block = Block.cached_find(query_key) - raise Api::V1::Exceptions::BlockNotFoundError if block.blank? + def aggregate_query! + results = Hash.new { |h| h[:data] = Array.new } + + # If query_key is all numbers, search block + if QueryKeyUtils.integer_string?(query_key) && (block = find_cached_block).present? + results[:data] << block.serializable_hash[:data] + return results + end + + # If the string length is less than 2, the query result will be empty + raise ActiveRecord::RecordNotFound if query_key.length < 2 + + if QueryKeyUtils.valid_hex?(query_key) + query_methods.each { results[:data] << _1.call.serializable_hash[:data] if _1.call.present? } + end + if QueryKeyUtils.valid_address?(query_key) && (address = find_cached_address).present? + results[:data] << address.serializable_hash[:data] + end + if (udts = find_udts_by_name_or_symbol).present? + results[:data].concat(udts.serializable_hash[:data]) + end + if (collections = find_nft_collections_by_name).present? + results[:data].concat(collections.serializable_hash[:data]) + end + + raise ActiveRecord::RecordNotFound if results.blank? + + results + end + + def query_methods + [ + method(:find_cached_block), + method(:find_ckb_transaction_by_hash), + method(:find_address_by_lock_hash), + method(:find_udt_by_type_hash), + method(:find_type_script_by_type_id), + method(:find_type_script_by_code_hash), + method(:find_lock_script_by_code_hash), + method(:find_bitcoin_transaction_by_txid), + ] + end - block + def find_cached_block + Block.cached_find(query_key) end def find_ckb_transaction_by_hash @@ -45,9 +89,7 @@ def find_address_by_lock_hash def find_cached_address address = Address.cached_find(query_key) - raise Api::V1::Exceptions::AddressNotFoundError if address.blank? - - AddressSerializer.new(address) + AddressSerializer.new(address) if address.present? end def find_udt_by_type_hash @@ -70,20 +112,20 @@ def find_type_script_by_code_hash TypeScriptSerializer.new(type_script) if type_script.present? end - def find_by_hex - Block.cached_find(query_key) || - find_ckb_transaction_by_hash || - find_address_by_lock_hash || - find_udt_by_type_hash || - find_type_script_by_type_id || - find_type_script_by_code_hash || - find_lock_script_by_code_hash || - find_bitcoin_transaction_by_txid - end - def find_bitcoin_transaction_by_txid txid = query_key.delete_prefix(Settings.default_hash_prefix) bitcoin_transaction = BitcoinTransaction.find_by(txid:) BitcoinTransactionSerializer.new(bitcoin_transaction) if bitcoin_transaction end + + def find_udts_by_name_or_symbol + udts = Udt.where(udt_type: %i[sudt xudt omiga_inscription], published: true). + where("full_name LIKE :query_key OR symbol LIKE :query_key", query_key: "%#{query_key}%") + UdtSerializer.new(udts) if udts.present? + end + + def find_nft_collections_by_name + token_collections = TokenCollection.where("name LIKE :query_key", query_key: "%#{query_key}%") + TokenCollectionSerializer.new(token_collections) if token_collections.present? + end end diff --git a/app/models/tx_display_info.rb b/app/models/tx_display_info.rb deleted file mode 100644 index aca50509d..000000000 --- a/app/models/tx_display_info.rb +++ /dev/null @@ -1,14 +0,0 @@ -class TxDisplayInfo < ApplicationRecord -end - -# == Schema Information -# -# Table name: tx_display_infos -# -# ckb_transaction_id :bigint not null, primary key -# inputs :jsonb -# outputs :jsonb -# created_at :datetime not null -# updated_at :datetime not null -# income :jsonb -# diff --git a/app/models/udt.rb b/app/models/udt.rb index dfa7818c5..2d573cb31 100644 --- a/app/models/udt.rb +++ b/app/models/udt.rb @@ -4,6 +4,7 @@ class Udt < ApplicationRecord belongs_to :nrc_factory_cell, optional: true has_one :udt_verification has_one :omiga_inscription_info + has_one :xudt_tag enum udt_type: { sudt: 0, m_nft_token: 1, nrc_721_token: 2, spore_cell: 3, omiga_inscription: 4, xudt: 5 } @@ -18,6 +19,7 @@ class Udt < ApplicationRecord scope :query_by_name_or_symbl, ->(search) { where("lower(full_name) LIKE ? or lower(symbol) LIKE ?", "%#{search}%", "%#{search}%") } + scope :published_xudt, -> { where(udt_type: :xudt, published: true) } attribute :code_hash, :ckb_hash diff --git a/app/models/xudt_tag.rb b/app/models/xudt_tag.rb new file mode 100644 index 000000000..df13fd6d3 --- /dev/null +++ b/app/models/xudt_tag.rb @@ -0,0 +1,21 @@ +class XudtTag < ApplicationRecord + belongs_to :udt + + VALID_TAGS = ["invalid", "suspicious", "out-of-length-range", "rgbpp-compatible", "layer-1-asset", "supply-limited", "duplicate", "layer-2-asset", "supply-unlimited"] +end + +# == Schema Information +# +# Table name: xudt_tags +# +# id :bigint not null, primary key +# udt_id :integer +# udt_type_hash :string +# tags :string default([]), is an Array +# created_at :datetime not null +# updated_at :datetime not null +# +# Indexes +# +# index_xudt_tags_on_udt_id (udt_id) UNIQUE +# diff --git a/app/serializers/token_collection_serializer.rb b/app/serializers/token_collection_serializer.rb new file mode 100644 index 000000000..f9cd4ba68 --- /dev/null +++ b/app/serializers/token_collection_serializer.rb @@ -0,0 +1,5 @@ +class TokenCollectionSerializer + include FastJsonapi::ObjectSerializer + + attributes :standard, :name, :description, :icon_url, :symbol +end diff --git a/app/serializers/udt_serializer.rb b/app/serializers/udt_serializer.rb index e5ce6b0b8..1b80662ea 100644 --- a/app/serializers/udt_serializer.rb +++ b/app/serializers/udt_serializer.rb @@ -67,4 +67,10 @@ class UdtSerializer } do |object| object.omiga_inscription_info.is_repeated_symbol end + + attribute :xudt_tags, if: Proc.new { |record, _params| + record.udt_type == "xudt" && record.published + } do |object| + object.xudt_tag&.tags + end end diff --git a/app/views/api/v2/rgb_transactions/index.json.jbuilder b/app/views/api/v2/rgb_transactions/index.json.jbuilder new file mode 100644 index 000000000..22d68cbab --- /dev/null +++ b/app/views/api/v2/rgb_transactions/index.json.jbuilder @@ -0,0 +1,16 @@ +json.data do + json.ckb_transactions @ckb_transactions do |tx| + json.id tx.id + json.tx_hash tx.tx_hash + json.block_id tx.block_id + json.block_number tx.block_number + json.block_timestamp tx.block_timestamp + json.leap_direction tx.leap_direction + json.rgb_cell_changes tx.rgb_cell_changes + json.rgb_txid tx.rgb_txid + end +end +json.meta do + json.total @ckb_transactions.total_count + json.page_size @ckb_transactions.current_per_page +end diff --git a/app/workers/bitcoin_transaction_detect_worker.rb b/app/workers/bitcoin_transaction_detect_worker.rb index 8bdd04a11..d1b155aaf 100644 --- a/app/workers/bitcoin_transaction_detect_worker.rb +++ b/app/workers/bitcoin_transaction_detect_worker.rb @@ -40,6 +40,9 @@ def perform(block_id) import_utxo!(lock_script.args, cell.id) end + + # update transaction rgbpp tags + update_rgbpp_tags!(transaction) end end end @@ -54,4 +57,10 @@ def import_utxo!(args, cell_id) ImportBitcoinUtxoJob.perform_now(cell_id) end end + + def update_rgbpp_tags!(transaction) + if transaction.bitcoin_vins.exists? || transaction.bitcoin_vouts.exists? + transaction.update!(tags: transaction.tags.to_a + ["rgbpp"]) + end + end end diff --git a/app/workers/xudt_tag_worker.rb b/app/workers/xudt_tag_worker.rb new file mode 100644 index 000000000..0a08aa256 --- /dev/null +++ b/app/workers/xudt_tag_worker.rb @@ -0,0 +1,57 @@ +class XudtTagWorker + include Sidekiq::Job + + def perform + udts = Udt.published_xudt.left_joins(:xudt_tag).where(xudt_tag: { id: nil }).limit(100) + attrs = + udts.map do |udt| + tags = mark_tags(udt) + { udt_id: udt.id, udt_type_hash: udt.type_hash, tags: } + end + + XudtTag.upsert_all(attrs, unique_by: :udt_id, on_duplicate: :update, update_only: :tags) + end + + def mark_tags(udt) + if invalid_char?(udt.symbol) + ["invalid"] + elsif invisible_char?(udt.symbol) + ["suspicious"] + elsif out_of_length?(udt.symbol) + ["out-of-length-range"] + elsif first_xudt?(udt.symbol, udt.block_timestamp) + if rgbpp_lock?(udt.issuer_address) + ["rgbpp-compatible", "layer-1-asset", "supply-limited"] + else + ["rgbpp-compatible", "layer-2-asset", "supply-unlimited"] + end + elsif rgbpp_lock?(udt.issuer_address) + ["duplicate", "layer-1-asset", "supply-limited"] + else + ["duplicate", "layer-2-asset", "supply-unlimited"] + end + end + + def invalid_char?(symbol) + !symbol.ascii_only? + end + + def invisible_char?(symbol) + (symbol =~ /^[\x21-\x7E]+$/).nil? + end + + def out_of_length?(symbol) + symbol.length > 5 || symbol.length < 4 + end + + def first_xudt?(symbol, block_timestamp) + !Udt.xudt.where("LOWER(symbol) = ?", symbol.downcase).where("block_timestamp < ?", block_timestamp).exists? + end + + def rgbpp_lock?(issuer_address) + CkbUtils.parse_address(issuer_address).script.code_hash == Settings.rgbpp_code_hash + end + + ## TODO: current no this condition + def omni_lock_with_supply_mode?(issuer_address); end +end diff --git a/config/routes/v2.rb b/config/routes/v2.rb index 93ecf8171..85c2264c0 100644 --- a/config/routes/v2.rb +++ b/config/routes/v2.rb @@ -91,5 +91,8 @@ end end end + + resources :rgb_transactions, only: :index + resources :bitcoin_statistics, only: :index end end diff --git a/config/settings.mainnet.yml b/config/settings.mainnet.yml index 4df630b09..8e70c0c15 100644 --- a/config/settings.mainnet.yml +++ b/config/settings.mainnet.yml @@ -31,7 +31,7 @@ omiga_inscription_code_hash: "0x7490970e6af9b9fe63fc19fc523a12b2ec69027e6ae484ed # xudt xudt_code_hash: "0x50bd8d6680b8b9cf98b73f3c08faf8b2a21914311954118ad6609be6e78a1b95" -unique_cell_code_hash: "0xcc2518c2c1384f2473c96f63c4e74074984296f358512ee7f54c848d4c135040" +unique_cell_code_hash: "0x2c8c11c985da60b0a330c61a85507416d6382c130ba67f0c47ab071e00aec628" # hash length of an attribute(especially which comes from bytea column), # e.g. Block.uncle_block_hashes: "0x587f354162afd133b4a4f7a4b621d11e043c3c08b0af2801f1686b5403b14953", which has a length of 66 ( 2 + 64) diff --git a/config/settings.testnet.yml b/config/settings.testnet.yml index 7578ca359..71b0ea890 100644 --- a/config/settings.testnet.yml +++ b/config/settings.testnet.yml @@ -23,8 +23,10 @@ nrc_721_token_output_data_header: "0x0ddeff3e8ee03cbf6a2c6920d05c381e" # spore nft code hash spore_cluster1_code_hash: "0x7366a61534fa7c7e6225ecc0d828ea3b5366adec2b58206f2ee84995fe030075" # tag: v2 preview spore_cluster2_code_hash: "0x598d793defef36e2eeba54a9b45130e4ca92822e1d193671f490950c3b856080" # tag: v1 latest +spore_cluster3_code_hash: "0x0bbe768b519d8ea7b96d58f1182eb7e6ef96c541fbd9526975077ee09f049058" # tag: 0.2.2-beta.2 spore_cell1_code_hash: "0x5e063b4c0e7abeaa6a428df3b693521a3050934cf3b0ae97a800d1bc31449398" # tag: v2 preview spore_cell2_code_hash: "0xbbad126377d45f90a8ee120da988a2d7332c78ba8fd679aab478a19d6c133494" # tag: v1 latest +spore_cell3_code_hash: "0x685a60219309029d01310311dba953d67029170ca4848a4ff638e57002130a0d" # tag: 0.2.2-beta.2 # omiga inscription info omiga_inscription_info_code_hash: "0x50fdea2d0030a8d0b3d69f883b471cab2a29cae6f01923f19cecac0f27fdaaa6" diff --git a/db/migrate/20240407100517_create_bitcoin_statistics.rb b/db/migrate/20240407100517_create_bitcoin_statistics.rb new file mode 100644 index 000000000..fcc3a9a67 --- /dev/null +++ b/db/migrate/20240407100517_create_bitcoin_statistics.rb @@ -0,0 +1,9 @@ +class CreateBitcoinStatistics < ActiveRecord::Migration[7.0] + def change + create_table :bitcoin_statistics do |t| + t.bigint :timestamp + t.integer :transactions_count, default: 0 + t.integer :addresses_count, default: 0 + end + end +end diff --git a/db/migrate/20240408024145_add_index_on_bitcoin_statistics.rb b/db/migrate/20240408024145_add_index_on_bitcoin_statistics.rb new file mode 100644 index 000000000..e013cec74 --- /dev/null +++ b/db/migrate/20240408024145_add_index_on_bitcoin_statistics.rb @@ -0,0 +1,5 @@ +class AddIndexOnBitcoinStatistics < ActiveRecord::Migration[7.0] + def change + add_index :bitcoin_statistics, :timestamp, unique: true + end +end diff --git a/db/migrate/20240408065818_drop_unused_tables.rb b/db/migrate/20240408065818_drop_unused_tables.rb new file mode 100644 index 000000000..6694b10ee --- /dev/null +++ b/db/migrate/20240408065818_drop_unused_tables.rb @@ -0,0 +1,8 @@ +class DropUnusedTables < ActiveRecord::Migration[7.0] + def change + drop_table :pool_transaction_entries, if_exists: true + drop_table :old_ckb_transactions, if_exists: true + drop_table :temp_view, if_exists: true + drop_table :tx_display_infos, if_exists: true + end +end diff --git a/db/migrate/20240408075718_rebind_cell_inputs_id_sequence.rb b/db/migrate/20240408075718_rebind_cell_inputs_id_sequence.rb new file mode 100644 index 000000000..97eca5f31 --- /dev/null +++ b/db/migrate/20240408075718_rebind_cell_inputs_id_sequence.rb @@ -0,0 +1,7 @@ +class RebindCellInputsIdSequence < ActiveRecord::Migration[7.0] + def up + execute <<-SQL + ALTER SEQUENCE cell_inputs_id_seq OWNED BY cell_inputs.id; + SQL + end +end diff --git a/db/migrate/20240408082159_drop_cell_inputs_old_tables.rb b/db/migrate/20240408082159_drop_cell_inputs_old_tables.rb new file mode 100644 index 000000000..7e6bff848 --- /dev/null +++ b/db/migrate/20240408082159_drop_cell_inputs_old_tables.rb @@ -0,0 +1,5 @@ +class DropCellInputsOldTables < ActiveRecord::Migration[7.0] + def change + drop_table :cell_inputs_old, if_exists: true + end +end diff --git a/db/migrate/20240415080556_create_xudt_tags.rb b/db/migrate/20240415080556_create_xudt_tags.rb new file mode 100644 index 000000000..34d872f97 --- /dev/null +++ b/db/migrate/20240415080556_create_xudt_tags.rb @@ -0,0 +1,13 @@ +class CreateXudtTags < ActiveRecord::Migration[7.0] + def change + create_table :xudt_tags do |t| + t.integer :udt_id + t.string :udt_type_hash + t.string "tags", default: [], array: true + + t.timestamps + end + + add_index :xudt_tags, :udt_id, unique: true + end +end diff --git a/db/structure.sql b/db/structure.sql index e0ba88f97..7d66491ad 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -593,6 +593,37 @@ CREATE SEQUENCE public.bitcoin_addresses_id_seq ALTER SEQUENCE public.bitcoin_addresses_id_seq OWNED BY public.bitcoin_addresses.id; +-- +-- Name: bitcoin_statistics; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.bitcoin_statistics ( + id bigint NOT NULL, + "timestamp" bigint, + transactions_count integer DEFAULT 0, + addresses_count integer DEFAULT 0 +); + + +-- +-- Name: bitcoin_statistics_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public.bitcoin_statistics_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: bitcoin_statistics_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - +-- + +ALTER SEQUENCE public.bitcoin_statistics_id_seq OWNED BY public.bitcoin_statistics.id; + + -- -- Name: bitcoin_transactions; Type: TABLE; Schema: public; Owner: - -- @@ -949,17 +980,17 @@ ALTER SEQUENCE public.cell_dependencies_id_seq OWNED BY public.cell_dependencies -- --- Name: cell_inputs_old; Type: TABLE; Schema: public; Owner: - +-- Name: cell_inputs; Type: TABLE; Schema: public; Owner: - -- -CREATE TABLE public.cell_inputs_old ( +CREATE TABLE public.cell_inputs ( id bigint NOT NULL, ckb_transaction_id bigint, created_at timestamp without time zone NOT NULL, updated_at timestamp without time zone NOT NULL, previous_cell_output_id bigint, from_cell_base boolean DEFAULT false, - block_id numeric(30,0), + block_id bigint, since numeric(30,0) DEFAULT 0.0, cell_type integer DEFAULT 0, index integer, @@ -984,27 +1015,7 @@ CREATE SEQUENCE public.cell_inputs_id_seq -- Name: cell_inputs_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - -- -ALTER SEQUENCE public.cell_inputs_id_seq OWNED BY public.cell_inputs_old.id; - - --- --- Name: cell_inputs; Type: TABLE; Schema: public; Owner: - --- - -CREATE TABLE public.cell_inputs ( - id bigint DEFAULT nextval('public.cell_inputs_id_seq'::regclass) NOT NULL, - ckb_transaction_id bigint, - created_at timestamp without time zone NOT NULL, - updated_at timestamp without time zone NOT NULL, - previous_cell_output_id bigint, - from_cell_base boolean DEFAULT false, - block_id bigint, - since numeric(30,0) DEFAULT 0.0, - cell_type integer DEFAULT 0, - index integer, - previous_tx_hash bytea, - previous_index integer -); +ALTER SEQUENCE public.cell_inputs_id_seq OWNED BY public.cell_inputs.id; -- @@ -1764,64 +1775,6 @@ CREATE SEQUENCE public.nrc_factory_cells_id_seq ALTER SEQUENCE public.nrc_factory_cells_id_seq OWNED BY public.nrc_factory_cells.id; --- --- Name: old_ckb_transactions; Type: TABLE; Schema: public; Owner: - --- - -CREATE TABLE public.old_ckb_transactions ( - id bigint NOT NULL, - tx_hash bytea, - block_id bigint, - block_number numeric(30,0), - block_timestamp numeric(30,0), - transaction_fee numeric(30,0), - version integer, - created_at timestamp without time zone NOT NULL, - updated_at timestamp without time zone NOT NULL, - is_cellbase boolean DEFAULT false, - header_deps bytea, - cell_deps jsonb, - witnesses jsonb, - live_cell_changes integer, - capacity_involved numeric(30,0), - contained_address_ids bigint[] DEFAULT '{}'::bigint[], - tags character varying[] DEFAULT '{}'::character varying[], - contained_udt_ids bigint[] DEFAULT '{}'::bigint[], - dao_address_ids bigint[] DEFAULT '{}'::bigint[], - udt_address_ids bigint[] DEFAULT '{}'::bigint[], - bytes integer DEFAULT 0, - cycles integer, - confirmation_time integer, - tx_status integer DEFAULT 2 NOT NULL -); - - --- --- Name: COLUMN old_ckb_transactions.confirmation_time; Type: COMMENT; Schema: public; Owner: - --- - -COMMENT ON COLUMN public.old_ckb_transactions.confirmation_time IS 'it cost how many seconds to confirm this transaction'; - - --- --- Name: old_ckb_transactions_id_seq; Type: SEQUENCE; Schema: public; Owner: - --- - -CREATE SEQUENCE public.old_ckb_transactions_id_seq - START WITH 1 - INCREMENT BY 1 - NO MINVALUE - NO MAXVALUE - CACHE 1; - - --- --- Name: old_ckb_transactions_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - --- - -ALTER SEQUENCE public.old_ckb_transactions_id_seq OWNED BY public.old_ckb_transactions.id; - - -- -- Name: omiga_inscription_infos; Type: TABLE; Schema: public; Owner: - -- @@ -1866,54 +1819,6 @@ CREATE SEQUENCE public.omiga_inscription_infos_id_seq ALTER SEQUENCE public.omiga_inscription_infos_id_seq OWNED BY public.omiga_inscription_infos.id; --- --- Name: pool_transaction_entries; Type: TABLE; Schema: public; Owner: - --- - -CREATE TABLE public.pool_transaction_entries ( - id bigint NOT NULL, - cell_deps jsonb, - tx_hash bytea, - header_deps jsonb, - inputs jsonb, - outputs jsonb, - outputs_data jsonb, - version integer, - witnesses jsonb, - transaction_fee numeric(30,0), - block_number numeric(30,0), - block_timestamp numeric(30,0), - cycles numeric(30,0), - tx_size numeric(30,0), - display_inputs jsonb, - display_outputs jsonb, - tx_status integer DEFAULT 0, - created_at timestamp(6) without time zone NOT NULL, - updated_at timestamp(6) without time zone NOT NULL, - detailed_message text, - bytes integer DEFAULT 0 -); - - --- --- Name: pool_transaction_entries_id_seq; Type: SEQUENCE; Schema: public; Owner: - --- - -CREATE SEQUENCE public.pool_transaction_entries_id_seq - START WITH 1 - INCREMENT BY 1 - NO MINVALUE - NO MAXVALUE - CACHE 1; - - --- --- Name: pool_transaction_entries_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - --- - -ALTER SEQUENCE public.pool_transaction_entries_id_seq OWNED BY public.pool_transaction_entries.id; - - -- -- Name: portfolios; Type: TABLE; Schema: public; Owner: - -- @@ -2345,20 +2250,6 @@ CREATE SEQUENCE public.transaction_propagation_delays_id_seq ALTER SEQUENCE public.transaction_propagation_delays_id_seq OWNED BY public.transaction_propagation_delays.id; --- --- Name: tx_display_infos; Type: TABLE; Schema: public; Owner: - --- - -CREATE TABLE public.tx_display_infos ( - ckb_transaction_id bigint NOT NULL, - inputs jsonb, - outputs jsonb, - created_at timestamp(6) without time zone NOT NULL, - updated_at timestamp(6) without time zone NOT NULL, - income jsonb -); - - -- -- Name: type_scripts; Type: TABLE; Schema: public; Owner: - -- @@ -2642,6 +2533,39 @@ CREATE SEQUENCE public.witnesses_id_seq ALTER SEQUENCE public.witnesses_id_seq OWNED BY public.witnesses.id; +-- +-- Name: xudt_tags; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.xudt_tags ( + id bigint NOT NULL, + udt_id integer, + udt_type_hash character varying, + tags character varying[] DEFAULT '{}'::character varying[], + created_at timestamp(6) without time zone NOT NULL, + updated_at timestamp(6) without time zone NOT NULL +); + + +-- +-- Name: xudt_tags_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public.xudt_tags_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: xudt_tags_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - +-- + +ALTER SEQUENCE public.xudt_tags_id_seq OWNED BY public.xudt_tags.id; + + -- -- Name: ckb_transactions_committed; Type: TABLE ATTACH; Schema: public; Owner: - -- @@ -2705,6 +2629,13 @@ ALTER TABLE ONLY public.bitcoin_address_mappings ALTER COLUMN id SET DEFAULT nex ALTER TABLE ONLY public.bitcoin_addresses ALTER COLUMN id SET DEFAULT nextval('public.bitcoin_addresses_id_seq'::regclass); +-- +-- Name: bitcoin_statistics id; Type: DEFAULT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.bitcoin_statistics ALTER COLUMN id SET DEFAULT nextval('public.bitcoin_statistics_id_seq'::regclass); + + -- -- Name: bitcoin_transactions id; Type: DEFAULT; Schema: public; Owner: - -- @@ -2776,10 +2707,10 @@ ALTER TABLE ONLY public.cell_dependencies ALTER COLUMN id SET DEFAULT nextval('p -- --- Name: cell_inputs_old id; Type: DEFAULT; Schema: public; Owner: - +-- Name: cell_inputs id; Type: DEFAULT; Schema: public; Owner: - -- -ALTER TABLE ONLY public.cell_inputs_old ALTER COLUMN id SET DEFAULT nextval('public.cell_inputs_id_seq'::regclass); +ALTER TABLE ONLY public.cell_inputs ALTER COLUMN id SET DEFAULT nextval('public.cell_inputs_id_seq'::regclass); -- @@ -2887,13 +2818,6 @@ ALTER TABLE ONLY public.mining_infos ALTER COLUMN id SET DEFAULT nextval('public ALTER TABLE ONLY public.nrc_factory_cells ALTER COLUMN id SET DEFAULT nextval('public.nrc_factory_cells_id_seq'::regclass); --- --- Name: old_ckb_transactions id; Type: DEFAULT; Schema: public; Owner: - --- - -ALTER TABLE ONLY public.old_ckb_transactions ALTER COLUMN id SET DEFAULT nextval('public.old_ckb_transactions_id_seq'::regclass); - - -- -- Name: omiga_inscription_infos id; Type: DEFAULT; Schema: public; Owner: - -- @@ -2901,13 +2825,6 @@ ALTER TABLE ONLY public.old_ckb_transactions ALTER COLUMN id SET DEFAULT nextval ALTER TABLE ONLY public.omiga_inscription_infos ALTER COLUMN id SET DEFAULT nextval('public.omiga_inscription_infos_id_seq'::regclass); --- --- Name: pool_transaction_entries id; Type: DEFAULT; Schema: public; Owner: - --- - -ALTER TABLE ONLY public.pool_transaction_entries ALTER COLUMN id SET DEFAULT nextval('public.pool_transaction_entries_id_seq'::regclass); - - -- -- Name: portfolios id; Type: DEFAULT; Schema: public; Owner: - -- @@ -3041,6 +2958,13 @@ ALTER TABLE ONLY public.users ALTER COLUMN id SET DEFAULT nextval('public.users_ ALTER TABLE ONLY public.witnesses ALTER COLUMN id SET DEFAULT nextval('public.witnesses_id_seq'::regclass); +-- +-- Name: xudt_tags id; Type: DEFAULT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.xudt_tags ALTER COLUMN id SET DEFAULT nextval('public.xudt_tags_id_seq'::regclass); + + -- -- Name: account_books account_books_pkey; Type: CONSTRAINT; Schema: public; Owner: - -- @@ -3089,6 +3013,14 @@ ALTER TABLE ONLY public.bitcoin_addresses ADD CONSTRAINT bitcoin_addresses_pkey PRIMARY KEY (id); +-- +-- Name: bitcoin_statistics bitcoin_statistics_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.bitcoin_statistics + ADD CONSTRAINT bitcoin_statistics_pkey PRIMARY KEY (id); + + -- -- Name: bitcoin_transactions bitcoin_transactions_pkey; Type: CONSTRAINT; Schema: public; Owner: - -- @@ -3169,14 +3101,6 @@ ALTER TABLE ONLY public.cell_dependencies ADD CONSTRAINT cell_dependencies_pkey PRIMARY KEY (id); --- --- Name: cell_inputs_old cell_inputs_pkey; Type: CONSTRAINT; Schema: public; Owner: - --- - -ALTER TABLE ONLY public.cell_inputs_old - ADD CONSTRAINT cell_inputs_pkey PRIMARY KEY (id); - - -- -- Name: cell_inputs cell_inputs_pkey_new; Type: CONSTRAINT; Schema: public; Owner: - -- @@ -3377,14 +3301,6 @@ ALTER TABLE ONLY public.nrc_factory_cells ADD CONSTRAINT nrc_factory_cells_pkey PRIMARY KEY (id); --- --- Name: old_ckb_transactions old_ckb_transactions_pkey; Type: CONSTRAINT; Schema: public; Owner: - --- - -ALTER TABLE ONLY public.old_ckb_transactions - ADD CONSTRAINT old_ckb_transactions_pkey PRIMARY KEY (id); - - -- -- Name: omiga_inscription_infos omiga_inscription_infos_pkey; Type: CONSTRAINT; Schema: public; Owner: - -- @@ -3393,14 +3309,6 @@ ALTER TABLE ONLY public.omiga_inscription_infos ADD CONSTRAINT omiga_inscription_infos_pkey PRIMARY KEY (id); --- --- Name: pool_transaction_entries pool_transaction_entries_pkey; Type: CONSTRAINT; Schema: public; Owner: - --- - -ALTER TABLE ONLY public.pool_transaction_entries - ADD CONSTRAINT pool_transaction_entries_pkey PRIMARY KEY (id); - - -- -- Name: portfolios portfolios_pkey; Type: CONSTRAINT; Schema: public; Owner: - -- @@ -3505,14 +3413,6 @@ ALTER TABLE ONLY public.transaction_propagation_delays ADD CONSTRAINT transaction_propagation_delays_pkey PRIMARY KEY (id); --- --- Name: tx_display_infos tx_display_infos_pkey; Type: CONSTRAINT; Schema: public; Owner: - --- - -ALTER TABLE ONLY public.tx_display_infos - ADD CONSTRAINT tx_display_infos_pkey PRIMARY KEY (ckb_transaction_id); - - -- -- Name: type_scripts type_scripts_pkey; Type: CONSTRAINT; Schema: public; Owner: - -- @@ -3569,14 +3469,6 @@ ALTER TABLE ONLY public.token_collections ADD CONSTRAINT unique_sn UNIQUE (sn); --- --- Name: pool_transaction_entries unique_tx_hash; Type: CONSTRAINT; Schema: public; Owner: - --- - -ALTER TABLE ONLY public.pool_transaction_entries - ADD CONSTRAINT unique_tx_hash UNIQUE (tx_hash); - - -- -- Name: udts unique_type_hash; Type: CONSTRAINT; Schema: public; Owner: - -- @@ -3601,6 +3493,14 @@ ALTER TABLE ONLY public.witnesses ADD CONSTRAINT witnesses_pkey PRIMARY KEY (id); +-- +-- Name: xudt_tags xudt_tags_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.xudt_tags + ADD CONSTRAINT xudt_tags_pkey PRIMARY KEY (id); + + -- -- Name: address_dao_tx_alt_pk; Type: INDEX; Schema: public; Owner: - -- @@ -3881,6 +3781,13 @@ CREATE INDEX index_addresses_on_lock_hash ON public.addresses USING hash (lock_h CREATE UNIQUE INDEX index_average_block_time_by_hour_on_hour ON public.average_block_time_by_hour USING btree (hour); +-- +-- Name: index_bitcoin_statistics_on_timestamp; Type: INDEX; Schema: public; Owner: - +-- + +CREATE UNIQUE INDEX index_bitcoin_statistics_on_timestamp ON public.bitcoin_statistics USING btree ("timestamp"); + + -- -- Name: index_bitcoin_transactions_on_txid; Type: INDEX; Schema: public; Owner: - -- @@ -4014,20 +3921,6 @@ CREATE INDEX index_cell_dependencies_on_contract_id ON public.cell_dependencies CREATE INDEX index_cell_dependencies_on_script_id ON public.cell_dependencies USING btree (script_id); --- --- Name: index_cell_inputs_on_block_id; Type: INDEX; Schema: public; Owner: - --- - -CREATE INDEX index_cell_inputs_on_block_id ON public.cell_inputs_old USING btree (block_id); - - --- --- Name: index_cell_inputs_on_ckb_transaction_id; Type: INDEX; Schema: public; Owner: - --- - -CREATE INDEX index_cell_inputs_on_ckb_transaction_id ON public.cell_inputs_old USING btree (ckb_transaction_id); - - -- -- Name: index_cell_inputs_on_ckb_transaction_id_and_index; Type: INDEX; Schema: public; Owner: - -- @@ -4035,20 +3928,6 @@ CREATE INDEX index_cell_inputs_on_ckb_transaction_id ON public.cell_inputs_old U CREATE UNIQUE INDEX index_cell_inputs_on_ckb_transaction_id_and_index ON public.cell_inputs USING btree (ckb_transaction_id, index); --- --- Name: index_cell_inputs_on_previous_cell_output_id; Type: INDEX; Schema: public; Owner: - --- - -CREATE INDEX index_cell_inputs_on_previous_cell_output_id ON public.cell_inputs_old USING btree (previous_cell_output_id); - - --- --- Name: index_cell_inputs_on_previous_tx_hash_and_previous_index; Type: INDEX; Schema: public; Owner: - --- - -CREATE INDEX index_cell_inputs_on_previous_tx_hash_and_previous_index ON public.cell_inputs_old USING btree (previous_tx_hash, previous_index); - - -- -- Name: index_cell_outputs_on_address_id_and_status; Type: INDEX; Schema: public; Owner: - -- @@ -4322,69 +4201,6 @@ CREATE INDEX index_mining_infos_on_block_number ON public.mining_infos USING btr CREATE UNIQUE INDEX index_nrc_factory_cells_on_code_hash_and_hash_type_and_args ON public.nrc_factory_cells USING btree (code_hash, hash_type, args); --- --- Name: index_old_ckb_transactions_on_block_id_and_block_timestamp; Type: INDEX; Schema: public; Owner: - --- - -CREATE INDEX index_old_ckb_transactions_on_block_id_and_block_timestamp ON public.old_ckb_transactions USING btree (block_id, block_timestamp); - - --- --- Name: index_old_ckb_transactions_on_block_timestamp_and_id; Type: INDEX; Schema: public; Owner: - --- - -CREATE INDEX index_old_ckb_transactions_on_block_timestamp_and_id ON public.old_ckb_transactions USING btree (block_timestamp DESC NULLS LAST, id DESC); - - --- --- Name: index_old_ckb_transactions_on_contained_address_ids_and_id; Type: INDEX; Schema: public; Owner: - --- - -CREATE INDEX index_old_ckb_transactions_on_contained_address_ids_and_id ON public.old_ckb_transactions USING gin (contained_address_ids, id); - - --- --- Name: index_old_ckb_transactions_on_contained_udt_ids; Type: INDEX; Schema: public; Owner: - --- - -CREATE INDEX index_old_ckb_transactions_on_contained_udt_ids ON public.old_ckb_transactions USING gin (contained_udt_ids); - - --- --- Name: index_old_ckb_transactions_on_dao_address_ids; Type: INDEX; Schema: public; Owner: - --- - -CREATE INDEX index_old_ckb_transactions_on_dao_address_ids ON public.old_ckb_transactions USING gin (dao_address_ids); - - --- --- Name: index_old_ckb_transactions_on_is_cellbase; Type: INDEX; Schema: public; Owner: - --- - -CREATE INDEX index_old_ckb_transactions_on_is_cellbase ON public.old_ckb_transactions USING btree (is_cellbase); - - --- --- Name: index_old_ckb_transactions_on_tags; Type: INDEX; Schema: public; Owner: - --- - -CREATE INDEX index_old_ckb_transactions_on_tags ON public.old_ckb_transactions USING gin (tags); - - --- --- Name: index_old_ckb_transactions_on_tx_hash_and_block_id; Type: INDEX; Schema: public; Owner: - --- - -CREATE UNIQUE INDEX index_old_ckb_transactions_on_tx_hash_and_block_id ON public.old_ckb_transactions USING btree (tx_hash, block_id); - - --- --- Name: index_old_ckb_transactions_on_udt_address_ids; Type: INDEX; Schema: public; Owner: - --- - -CREATE INDEX index_old_ckb_transactions_on_udt_address_ids ON public.old_ckb_transactions USING gin (udt_address_ids); - - -- -- Name: index_omiga_inscription_infos_on_udt_hash; Type: INDEX; Schema: public; Owner: - -- @@ -4392,27 +4208,6 @@ CREATE INDEX index_old_ckb_transactions_on_udt_address_ids ON public.old_ckb_tra CREATE UNIQUE INDEX index_omiga_inscription_infos_on_udt_hash ON public.omiga_inscription_infos USING btree (udt_hash); --- --- Name: index_pool_transaction_entries_on_id_and_tx_status; Type: INDEX; Schema: public; Owner: - --- - -CREATE INDEX index_pool_transaction_entries_on_id_and_tx_status ON public.pool_transaction_entries USING btree (id, tx_status); - - --- --- Name: index_pool_transaction_entries_on_tx_hash; Type: INDEX; Schema: public; Owner: - --- - -CREATE INDEX index_pool_transaction_entries_on_tx_hash ON public.pool_transaction_entries USING hash (tx_hash); - - --- --- Name: index_pool_transaction_entries_on_tx_status; Type: INDEX; Schema: public; Owner: - --- - -CREATE INDEX index_pool_transaction_entries_on_tx_status ON public.pool_transaction_entries USING btree (tx_status); - - -- -- Name: index_portfolios_on_user_id_and_address_id; Type: INDEX; Schema: public; Owner: - -- @@ -4707,6 +4502,13 @@ CREATE INDEX index_witnesses_on_ckb_transaction_id ON public.witnesses USING btr CREATE UNIQUE INDEX index_witnesses_on_ckb_transaction_id_and_index ON public.witnesses USING btree (ckb_transaction_id, index); +-- +-- Name: index_xudt_tags_on_udt_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE UNIQUE INDEX index_xudt_tags_on_udt_id ON public.xudt_tags USING btree (udt_id); + + -- -- Name: pk; Type: INDEX; Schema: public; Owner: - -- @@ -4896,13 +4698,6 @@ ALTER INDEX public.ckb_tx_uni_tx_hash ATTACH PARTITION public.ckb_transactions_r CREATE TRIGGER after_delete_update_ckb_transactions_count AFTER DELETE ON public.ckb_transactions FOR EACH ROW EXECUTE FUNCTION public.decrease_ckb_transactions_count(); --- --- Name: old_ckb_transactions after_delete_update_ckb_transactions_count; Type: TRIGGER; Schema: public; Owner: - --- - -CREATE TRIGGER after_delete_update_ckb_transactions_count AFTER DELETE ON public.old_ckb_transactions FOR EACH ROW EXECUTE FUNCTION public.decrease_ckb_transactions_count(); - - -- -- Name: ckb_transactions after_insert_update_ckb_transactions_count; Type: TRIGGER; Schema: public; Owner: - -- @@ -4910,13 +4705,6 @@ CREATE TRIGGER after_delete_update_ckb_transactions_count AFTER DELETE ON public CREATE TRIGGER after_insert_update_ckb_transactions_count AFTER INSERT ON public.ckb_transactions FOR EACH ROW EXECUTE FUNCTION public.increase_ckb_transactions_count(); --- --- Name: old_ckb_transactions after_insert_update_ckb_transactions_count; Type: TRIGGER; Schema: public; Owner: - --- - -CREATE TRIGGER after_insert_update_ckb_transactions_count AFTER INSERT ON public.old_ckb_transactions FOR EACH ROW EXECUTE FUNCTION public.increase_ckb_transactions_count(); - - -- -- Name: ckb_transactions after_update_ckb_transactions_count; Type: TRIGGER; Schema: public; Owner: - -- @@ -4924,20 +4712,6 @@ CREATE TRIGGER after_insert_update_ckb_transactions_count AFTER INSERT ON public CREATE TRIGGER after_update_ckb_transactions_count AFTER UPDATE ON public.ckb_transactions FOR EACH ROW EXECUTE FUNCTION public.update_ckb_transactions_count(); --- --- Name: pool_transaction_entries insert_ckb_transactions; Type: TRIGGER; Schema: public; Owner: - --- - -CREATE TRIGGER insert_ckb_transactions AFTER INSERT ON public.pool_transaction_entries FOR EACH ROW EXECUTE FUNCTION public.insert_into_ckb_transactions(); - - --- --- Name: old_ckb_transactions sync_to_account_book; Type: TRIGGER; Schema: public; Owner: - --- - -CREATE TRIGGER sync_to_account_book AFTER INSERT OR UPDATE ON public.old_ckb_transactions FOR EACH ROW EXECUTE FUNCTION public.synx_tx_to_account_book(); - - -- -- Name: block_transactions fk_rails_a0eeb26f19; Type: FK CONSTRAINT; Schema: public; Owner: - -- @@ -5250,6 +5024,12 @@ INSERT INTO "schema_migrations" (version) VALUES ('20240312050057'), ('20240313075641'), ('20240315015432'), -('20240330023445'); +('20240330023445'), +('20240407100517'), +('20240408024145'), +('20240408065818'), +('20240408075718'), +('20240408082159'), +('20240415080556'); diff --git a/lib/scheduler.rb b/lib/scheduler.rb index 19ce8e37a..149bd6571 100644 --- a/lib/scheduler.rb +++ b/lib/scheduler.rb @@ -121,4 +121,12 @@ def call_worker(clz) call_worker ContractStatisticWorker end +s.cron "0,30 * * * *" do + BitcoinStatistic.refresh +end + +s.every "2m", overlap: false do + call_worker XudtTagWorker +end + s.join diff --git a/test/controllers/api/v1/suggest_queries_controller_test.rb b/test/controllers/api/v1/suggest_queries_controller_test.rb index 95126a02a..739aea5a6 100644 --- a/test/controllers/api/v1/suggest_queries_controller_test.rb +++ b/test/controllers/api/v1/suggest_queries_controller_test.rb @@ -46,42 +46,6 @@ class SuggestQueriesControllerTest < ActionDispatch::IntegrationTest assert_equal response_json, response.body end - test "should response with error object when query key is neither integer nor hex or address" do - error_object = Api::V1::Exceptions::SuggestQueryKeyInvalidError.new - response_json = RequestErrorSerializer.new([error_object], message: error_object.title).serialized_json - - valid_get api_v1_suggest_queries_url, params: { q: "0x3b238b3326d10ec000417b6&^&bc715f17e86293d6cdbcb3fd8a628ad4a0b756f6" } - - assert_equal response_json, response.body - end - - test "should response with error object when query key is not a hex start with 0x and not a address" do - error_object = Api::V1::Exceptions::SuggestQueryKeyInvalidError.new - response_json = RequestErrorSerializer.new([error_object], message: error_object.title).serialized_json - - valid_get api_v1_suggest_queries_url, params: { q: "3b238b3326d10ec000417b68bc715f17e86293d6cdbcb3fd8a628ad4a0b756f6" } - - assert_equal response_json, response.body - end - - test "should return error object when query key is a hex start with 0x but the length is wrong" do - error_object = Api::V1::Exceptions::SuggestQueryKeyInvalidError.new - response_json = RequestErrorSerializer.new([error_object], message: error_object.title).serialized_json - - valid_get api_v1_suggest_queries_url, params: { q: "0x3b238b3326d10ec0004" } - - assert_equal response_json, response.body - end - - test "should return error object when query key is not a address" do - error_object = Api::V1::Exceptions::SuggestQueryKeyInvalidError.new - response_json = RequestErrorSerializer.new([error_object], message: error_object.title).serialized_json - - valid_get api_v1_suggest_queries_url, params: { q: "ckc2q9gry5zgwayze0rtl8g0m8lgtx0cj35hmajzz2r9e6rtnt" } - - assert_equal response_json, response.body - end - test "should return a block when query key is a exist block height" do Block.delete_all block = create(:block) @@ -121,7 +85,7 @@ class SuggestQueriesControllerTest < ActionDispatch::IntegrationTest end test "should return error object when no records found by a integer query key" do - error_object = Api::V1::Exceptions::BlockNotFoundError.new + error_object = Api::V1::Exceptions::SuggestQueryResultNotFoundError.new response_json = RequestErrorSerializer.new([error_object], message: error_object.title).serialized_json valid_get api_v1_suggest_queries_url, params: { q: 1 } diff --git a/test/controllers/api/v1/xudts_controller_test.rb b/test/controllers/api/v1/xudts_controller_test.rb index 01891ab69..7aea9c8fe 100644 --- a/test/controllers/api/v1/xudts_controller_test.rb +++ b/test/controllers/api/v1/xudts_controller_test.rb @@ -18,6 +18,28 @@ class XudtsControllerTest < ActionDispatch::IntegrationTest assert_response :success end + + test "filter xudt by symbol and tags" do + udt = create(:udt, :xudt, symbol: "CKBB") + create(:xudt_tag, udt:, tags: ["duplicate", "layer-1-asset", "supply-limited"]) + udt2 = create(:udt, :xudt, symbol: "RPGG") + create(:xudt_tag, udt: udt2, tags: ["duplicate", "layer-2-asset", "supply-limited"]) + valid_get api_v1_xudts_url, params: { symbol: "CKBB", "tags": "layer-1-asset,supply-limited,NOT EXIST" } + assert_response :success + assert_equal "CKBB", json["data"].first["attributes"]["symbol"] + assert_equal ["duplicate", "layer-1-asset", "supply-limited"], json["data"].first["attributes"]["xudt_tags"] + end + + test "list xudt and xudt_tags" do + udt = create(:udt, :xudt, symbol: "CKBB") + create(:xudt_tag, udt:, tags: ["duplicate", "layer-1-asset", "supply-limited"]) + udt2 = create(:udt, :xudt, symbol: "RPGG") + create(:xudt_tag, udt: udt2, tags: ["duplicate", "layer-2-asset", "supply-limited"]) + valid_get api_v1_xudts_url + + assert_equal "RPGG", json["data"].first["attributes"]["symbol"] + assert_equal ["duplicate", "layer-1-asset", "supply-limited"], json["data"].last["attributes"]["xudt_tags"] + end end end end diff --git a/test/controllers/api/v2/nft/holders_controller_test.rb b/test/controllers/api/v2/nft/holders_controller_test.rb index c7ac752f5..d377e295f 100644 --- a/test/controllers/api/v2/nft/holders_controller_test.rb +++ b/test/controllers/api/v2/nft/holders_controller_test.rb @@ -49,17 +49,29 @@ class HoldersControllerTest < ActionDispatch::IntegrationTest assert_equal item.owner.address_hash, json["data"].keys[0] end + test "should not return mint items" do + collection = create(:token_collection, :with_items) + item = collection.items.sample + create(:token_item, collection_id: collection.id, status: :burnt, owner_id: item.owner.id) + + get api_v2_nft_collection_holders_url(collection_id: collection.id), + params: { address_hash: item.owner.address_hash } + + assert_equal 1, json["data"].size + assert_equal item.owner.address_hash, json["data"].keys[0] + end + test "should sorted by quantity asc when sort param is quantity" do collection = create(:token_collection, items_count: 6, holders_count: 2) owner = create(:address) 4.times do |i| - create(:token_item, token_id: i, collection: collection, owner: owner) + create(:token_item, token_id: i, collection:, owner:) end owner = create(:address) 3.times do |i| - create(:token_item, token_id: i + 4, collection: collection, owner: owner) + create(:token_item, token_id: i + 4, collection:, owner:) end get api_v2_nft_collection_holders_url(collection_id: collection.id), @@ -67,7 +79,8 @@ class HoldersControllerTest < ActionDispatch::IntegrationTest response_json = { data: collection.items.joins(:owner). - order("count_all asc").group(:address_hash).count }.as_json + order("count_all asc").group(:address_hash).count, + }.as_json assert_equal response_json, json end diff --git a/test/controllers/api/v2/nft/items_controller_test.rb b/test/controllers/api/v2/nft/items_controller_test.rb index 7d517d2de..93dfd9612 100644 --- a/test/controllers/api/v2/nft/items_controller_test.rb +++ b/test/controllers/api/v2/nft/items_controller_test.rb @@ -7,12 +7,13 @@ def setup super end - test "should get index with collection id" do + test "should get index with collection id without burnt item" do token_collection = create :token_collection, name: "token1" address = create :address, is_depositor: true create :token_item, name: "item1", collection_id: token_collection.id, owner_id: address.id create :token_item, name: "item2", collection_id: token_collection.id, owner_id: address.id + create :token_item, name: "item3", collection_id: token_collection.id, owner_id: address.id, status: :burnt get api_v2_nft_collection_items_url(collection_id: token_collection.id) assert_response :success diff --git a/test/controllers/api/v2/portfolio/sessions_controller_test.rb b/test/controllers/api/v2/portfolio/sessions_controller_test.rb index e42b58b2d..215fb9818 100644 --- a/test/controllers/api/v2/portfolio/sessions_controller_test.rb +++ b/test/controllers/api/v2/portfolio/sessions_controller_test.rb @@ -59,6 +59,7 @@ class SessionsControllerTest < ActionDispatch::IntegrationTest jwt = PortfolioUtils.generate_jwt(payload) assert_equal jwt, json["jwt"] + ENV["CKB_NET_MODE"] = "mainnet" end end end diff --git a/test/controllers/rgb_transactions_controller_test.rb b/test/controllers/rgb_transactions_controller_test.rb new file mode 100644 index 000000000..763f738ea --- /dev/null +++ b/test/controllers/rgb_transactions_controller_test.rb @@ -0,0 +1,7 @@ +require "test_helper" + +class RgbTransactionsControllerTest < ActionDispatch::IntegrationTest + # test "the truth" do + # assert true + # end +end diff --git a/test/factories/bitcoin_statistics.rb b/test/factories/bitcoin_statistics.rb new file mode 100644 index 000000000..e693aff31 --- /dev/null +++ b/test/factories/bitcoin_statistics.rb @@ -0,0 +1,5 @@ +FactoryBot.define do + factory :bitcoin_statistic do + + end +end diff --git a/test/factories/udt.rb b/test/factories/udt.rb index 8d5dc1023..350a65486 100644 --- a/test/factories/udt.rb +++ b/test/factories/udt.rb @@ -58,7 +58,9 @@ trait :xudt do udt_type { "xudt" } - symbol { "Unique BBQ" } + published { true } + full_name { "UniqueBBQ" } + symbol { "BBQ" } decimal { 8 } end end diff --git a/test/factories/xudt_tag.rb b/test/factories/xudt_tag.rb new file mode 100644 index 000000000..b6df559ad --- /dev/null +++ b/test/factories/xudt_tag.rb @@ -0,0 +1,7 @@ +FactoryBot.define do + factory :xudt_tag do + udt + udt_type_hash { "0x#{SecureRandom.hex(32)}" } + tags { ["invalid"] } + end +end diff --git a/test/models/bitcoin_statistic_test.rb b/test/models/bitcoin_statistic_test.rb new file mode 100644 index 000000000..debdf32bb --- /dev/null +++ b/test/models/bitcoin_statistic_test.rb @@ -0,0 +1,7 @@ +require "test_helper" + +class BitcoinStatisticTest < ActiveSupport::TestCase + # test "the truth" do + # assert true + # end +end diff --git a/test/models/ckb_sync/node_data_processor_test.rb b/test/models/ckb_sync/node_data_processor_test.rb index 79fa1dc77..5fdd6241b 100644 --- a/test/models/ckb_sync/node_data_processor_test.rb +++ b/test/models/ckb_sync/node_data_processor_test.rb @@ -2651,6 +2651,7 @@ class NodeDataProcessorTest < ActiveSupport::TestCase args: "0x#{SecureRandom.hex(20)}") udt_script = CKB::Types::Script.new(code_hash: Settings.sudt_cell_type_hash, hash_type: "type", args: "0x#{SecureRandom.hex(32)}") + create(:udt, type_hash: udt_script.compute_hash) Address.create(lock_hash: udt_script.args, address_hash: "0x#{SecureRandom.hex(32)}") outputs = [ @@ -4154,6 +4155,7 @@ class NodeDataProcessorTest < ActiveSupport::TestCase test "create xudt with unique cell" do CkbSync::Api.any_instance.stubs(:mode).returns("testnet") + ENV["CKB_NET_MODE"] = "testnet" CkbSync::Api.any_instance.stubs(:xudt_code_hash).returns("0x25c29dc317811a6f6f3985a7a9ebc4838bd388d19d0feeecf0bcd60f6c0975bb") CkbSync::Api.any_instance.stubs(:unique_cell_code_hash).returns("0x8e341bcfec6393dcd41e635733ff2dca00a6af546949f70c57a706c0f344df8b") @@ -4176,12 +4178,18 @@ class NodeDataProcessorTest < ActiveSupport::TestCase cell_type: "normal", lock_script_id: address1_lock.id, type_script_id: nil) + owner_lock_script = create(:lock_script, code_hash: "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8", hash_type: "type", + args: "0x1df67837213ba46b6cba2f2a88a3c031dbbe0083", script_hash: "0x30d3fbec9ceba691770d57c6d06bdb98cf0f82bef0ca6e87687a118d6ce1e7b7") + create(:address, address_hash: "ckt1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqga7eurwgfm534kew3092y28sp3mwlqpqcptqy24", lock_hash: owner_lock_script.script_hash, + lock_script_id: owner_lock_script.id) node_data_processor.process_block(node_block) xudt = Udt.first assert_equal 1, Udt.count assert_equal "Unique BBQ", xudt.full_name assert_equal 8, xudt.decimal assert_equal "xudt", xudt.udt_type + assert_equal "ckt1qzda0cr08m85hc8jlnfp3zer7xulejywt49kt2rr0vthywaa50xwsqga7eurwgfm534kew3092y28sp3mwlqpqcptqy24", xudt.issuer_address + ENV["CKB_NET_MODE"] = "mainnet" end end diff --git a/test/models/suggest_query_test.rb b/test/models/suggest_query_test.rb index 0495b48db..dbfa3ef87 100644 --- a/test/models/suggest_query_test.rb +++ b/test/models/suggest_query_test.rb @@ -35,9 +35,9 @@ class SuggestQueryTest < ActiveSupport::TestCase SuggestQuery.new(address.address_hash).find!.serialized_json end - test "should raise BlockNotFoundError when query key is a block number that doesn't exist" do + test "should raise RecordNotFound when query key is a block number that doesn't exist" do create(:block, number: 12) - assert_raises Api::V1::Exceptions::BlockNotFoundError do + assert_raises ActiveRecord::RecordNotFound do SuggestQuery.new("11").find! end end diff --git a/test/services/charts/daily_statistic_generator_test.rb b/test/services/charts/daily_statistic_generator_test.rb index cb3e5549a..35eb9dede 100644 --- a/test/services/charts/daily_statistic_generator_test.rb +++ b/test/services/charts/daily_statistic_generator_test.rb @@ -248,6 +248,7 @@ class DailyStatisticGeneratorTest < ActiveSupport::TestCase # create :daily_statistic, created_at_unixtimestamp: @datetime.yesterday.yesterday.to_i, dead_cells_count: 888 + assert_equal "888", ::DailyStatistic.last.dead_cells_count assert_equal (3 + 888).to_s, Charts::DailyStatisticGenerator.new(@datetime).call.dead_cells_count end diff --git a/test/test_helper.rb b/test/test_helper.rb index 50390fb42..c220d04c2 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -1,4 +1,8 @@ require "simplecov" +require "simplecov-cobertura" +if ENV["CI"] + SimpleCov.formatter = SimpleCov::Formatter::CoberturaFormatter +end SimpleCov.start "rails" do add_filter "/app/channels/" add_filter "/app/jobs/" @@ -41,11 +45,6 @@ end end -if ENV["CI"] == "true" - require "codecov" - SimpleCov.formatter = SimpleCov::Formatter::Codecov -end - def prepare_node_data(node_tip_block_number = 30) Sidekiq::Testing.inline! GenerateStatisticsDataWorker.any_instance.stubs(:perform).returns(true) diff --git a/test/workers/xudt_tag_worker_test.rb b/test/workers/xudt_tag_worker_test.rb new file mode 100644 index 000000000..a0705e9f0 --- /dev/null +++ b/test/workers/xudt_tag_worker_test.rb @@ -0,0 +1,51 @@ +require "test_helper" + +class XudtTagWorkerTest < ActiveJob::TestCase + setup do + @address = create(:address, address_hash: "ckb1qz7xc452rgxs5z0ks3xun46dmdp58sepg0ljtae8ck0d7nah945nvqgqqqqqqx3l3v4") + end + + test "insert to xudt_tags successfully" do + udt = create(:udt, :xudt) + create(:xudt_tag, udt_id: udt.id, udt_type_hash: udt.type_hash, tags: ["out-of-length-range"]) + create(:udt, :xudt, symbol: "CKBB", issuer_address: @address.address_hash) + assert_changes -> { XudtTag.count }, from: 1, to: 2 do + XudtTagWorker.new.perform + end + assert_equal ["rgbpp-compatible", "layer-1-asset", "supply-limited"], XudtTag.last.tags + end + + test "insert invalid tag" do + create(:udt, :xudt, symbol: "ΓΌ") + assert_changes -> { XudtTag.count }, from: 0, to: 1 do + XudtTagWorker.new.perform + end + assert_equal ["invalid"], XudtTag.last.tags + end + + test "insert suspicious tag" do + create(:udt, :xudt, symbol: "CK BB") + assert_changes -> { XudtTag.count }, from: 0, to: 1 do + XudtTagWorker.new.perform + end + assert_equal ["suspicious"], XudtTag.last.tags + end + + test "insert out-of-length-range tag" do + create(:udt, :xudt, symbol: "CKBBBB") + assert_changes -> { XudtTag.count }, from: 0, to: 1 do + XudtTagWorker.new.perform + end + assert_equal ["out-of-length-range"], XudtTag.last.tags + end + + test "insert duplicate tag" do + udt = create(:udt, :xudt, symbol: "CKBBB", block_timestamp: 1.day.ago.to_i * 1000) + create(:xudt_tag, udt_id: udt.id, udt_type_hash: udt.type_hash, tags: ["rgbpp-compatible", "layer-1-asset", "supply-limited"]) + create(:udt, :xudt, symbol: "ckbbb", block_timestamp: Time.now.to_i * 1000, issuer_address: @address.address_hash) + assert_changes -> { XudtTag.count }, from: 1, to: 2 do + XudtTagWorker.new.perform + end + assert_equal ["duplicate", "layer-1-asset", "supply-limited"], XudtTag.last.tags + end +end