diff --git a/.gitignore b/.gitignore index b235d79c5..bef4b1372 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ .DS_Store .vscode .idea/ +TAGS ckb-explorer.iml .generators .rakeTasks diff --git a/CHANGELOG.md b/CHANGELOG.md index b1ee16ce1..a2d799493 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,18 @@ +# [0.9.2](https://github.com/shaojunda/ckb-explorer/compare/v0.9.1...v0.9.2) (2020-01-31) + + +### Features + +* add address average deposit time generator ([0618138](https://github.com/shaojunda/ckb-explorer/commit/0618138)) +* add average deposit time to address ([81469df](https://github.com/shaojunda/ckb-explorer/commit/81469df)) +* add claimed and unclaimed compensation ([268858d](https://github.com/shaojunda/ckb-explorer/commit/268858d)) +* add compensation and lock period ([e42b072](https://github.com/shaojunda/ckb-explorer/commit/e42b072)) +* add more elements to daily chart ([0e239b1](https://github.com/shaojunda/ckb-explorer/commit/0e239b1)) +* add new columns to daily statistic ([0a013c4](https://github.com/shaojunda/ckb-explorer/commit/0a013c4)) +* add unlaimed compenstaion generator worker ([ba0e105](https://github.com/shaojunda/ckb-explorer/commit/ba0e105)) + + + # [0.9.1](https://github.com/shaojunda/ckb-explorer/compare/v0.8.4...v0.9.0) (2020-01-13) diff --git a/app/controllers/api/v1/dao_depositors_controller.rb b/app/controllers/api/v1/dao_depositors_controller.rb index e4b377f2f..3e5fcc2b2 100644 --- a/app/controllers/api/v1/dao_depositors_controller.rb +++ b/app/controllers/api/v1/dao_depositors_controller.rb @@ -2,7 +2,7 @@ module Api module V1 class DaoDepositorsController < ApplicationController def index - addresses = Address.where("dao_deposit > 0").order(dao_deposit: :desc).limit(100) + addresses = Address.select(:id, :address_hash, :dao_deposit, :average_deposit_time).where("dao_deposit > 0").order(dao_deposit: :desc).limit(100) render json: DaoDepositorSerializer.new(addresses) end diff --git a/app/controllers/api/v1/epoch_statistics_controller.rb b/app/controllers/api/v1/epoch_statistics_controller.rb index 4073c5251..720353bc2 100644 --- a/app/controllers/api/v1/epoch_statistics_controller.rb +++ b/app/controllers/api/v1/epoch_statistics_controller.rb @@ -4,7 +4,7 @@ class EpochStatisticsController < ApplicationController before_action :validate_query_params def show - epoch_statistics = EpochStatistic.order(id: :desc).reverse + epoch_statistics = EpochStatistic.order(epoch_number: :desc).limit(90).reverse render json: EpochStatisticSerializer.new(epoch_statistics, { params: { indicator: params[:id] } }) end diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 6b93e5ece..e3e09ea43 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -20,8 +20,8 @@ def api_error(error) end def check_header_info - raise Api::V1::Exceptions::WrongContentTypeError if request.headers["Content-Type"] != "application/vnd.api+json" - raise Api::V1::Exceptions::WrongAcceptError if request.headers["Accept"] != "application/vnd.api+json" + raise Api::V1::Exceptions::InvalidContentTypeError if request.headers["Content-Type"] != "application/vnd.api+json" + raise Api::V1::Exceptions::InvalidAcceptError if request.headers["Accept"] != "application/vnd.api+json" end def validate_pagination_params diff --git a/app/controllers/validations/daily_statistic.rb b/app/controllers/validations/daily_statistic.rb index dc98c6d19..c0e7f1c7c 100644 --- a/app/controllers/validations/daily_statistic.rb +++ b/app/controllers/validations/daily_statistic.rb @@ -25,7 +25,7 @@ def error_object attr_accessor :query_key def query_key_format_must_be_correct - if query_key.blank? || !query_key.in?(::DailyStatistic::VALID_INDICATORS) + if query_key.blank? || !(query_key.split("-") - ::DailyStatistic::VALID_INDICATORS).empty? errors.add(:query_key, "indicator name is invalid") end end diff --git a/app/lib/api/v1/exceptions.rb b/app/lib/api/v1/exceptions.rb index 934958057..9a8b9295f 100644 --- a/app/lib/api/v1/exceptions.rb +++ b/app/lib/api/v1/exceptions.rb @@ -13,13 +13,13 @@ def initialize(code:, status:, title:, detail:, href:) end end - class WrongContentTypeError < Error + class InvalidContentTypeError < Error def initialize super code: 1001, status: 415, title: "Unsupported Media Type", detail: "Content Type must be application/vnd.api+json", href: "https://nervosnetwork.github.io/ckb-explorer/public/api_doc.html" end end - class WrongAcceptError < Error + class InvalidAcceptError < Error def initialize super code: 1002, status: 406, title: "Not Acceptable", detail: "Accept must be application/vnd.api+json", href: "https://nervosnetwork.github.io/ckb-explorer/public/api_doc.html" end diff --git a/app/models/address.rb b/app/models/address.rb index 7d2baa0b4..ac8d023fa 100644 --- a/app/models/address.rb +++ b/app/models/address.rb @@ -80,6 +80,7 @@ def special? # visible :boolean default(TRUE) # live_cells_count :decimal(30, ) default(0) # mined_blocks_count :integer default(0) +# average_deposit_time :decimal(, ) # # Indexes # diff --git a/app/models/block.rb b/app/models/block.rb index 1d6367d8a..78bf6b940 100644 --- a/app/models/block.rb +++ b/app/models/block.rb @@ -57,7 +57,7 @@ def difficulty end def block_index_in_epoch - number - start_number + number - start_number end def fraction_epoch diff --git a/app/models/cell_output.rb b/app/models/cell_output.rb index 4245cda15..18907ba8c 100644 --- a/app/models/cell_output.rb +++ b/app/models/cell_output.rb @@ -16,6 +16,7 @@ class CellOutput < ApplicationRecord attribute :tx_hash, :ckb_hash + scope :consumed_after, -> (block_timestamp) { where("consumed_block_timestamp >= ?", block_timestamp) } scope :consumed_before, -> (block_timestamp) { where("consumed_block_timestamp <= ?", block_timestamp) } scope :unconsumed_at, -> (block_timestamp) { where("consumed_block_timestamp > ? or consumed_block_timestamp is null", block_timestamp) } scope :generated_after, -> (block_timestamp) { where("block_timestamp >= ?", block_timestamp) } diff --git a/app/models/ckb_sync/node_data_processor.rb b/app/models/ckb_sync/node_data_processor.rb index a8711144b..b94a13918 100644 --- a/app/models/ckb_sync/node_data_processor.rb +++ b/app/models/ckb_sync/node_data_processor.rb @@ -78,7 +78,7 @@ def process_take_away_all_deposit(dao_contract, dao_events) def process_issue_interest(dao_contract, dao_events) issue_interest_dao_events = dao_events.where(event_type: "issue_interest") issue_interest_dao_events.each do |event| - dao_contract.increment!(:interest_granted, event.value) + dao_contract.increment!(:claimed_compensation, event.value) address = event.address address.increment!(:interest, event.value) event.processed! @@ -190,7 +190,7 @@ def revert_take_away_all_deposit(dao_contract, dao_events) def revert_issue_interest(dao_contract, dao_events) issue_interest_dao_events = dao_events.where(event_type: "issue_interest") issue_interest_dao_events.each do |event| - dao_contract.decrement!(:interest_granted, event.value) + dao_contract.decrement!(:claimed_compensation, event.value) address = event.address address.decrement!(:interest, event.value) event.reverted! diff --git a/app/models/ckb_transaction.rb b/app/models/ckb_transaction.rb index bdbdf1834..f3041c06b 100644 --- a/app/models/ckb_transaction.rb +++ b/app/models/ckb_transaction.rb @@ -66,7 +66,6 @@ def normal_tx_display_outputs(previews) 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, address_hash: output.address_hash, status: output.status, consumed_tx_hash: consumed_tx_hash, cell_type: output.cell_type } - display_output[:dao_type_hash] = ENV["DAO_TYPE_HASH"] unless output.normal? CkbUtils.hash_value_to_s(display_output) end @@ -89,21 +88,26 @@ def normal_tx_display_inputs(previews) previous_cell_output = cell_input.previous_cell_output display_input = { id: previous_cell_output.id, from_cellbase: false, capacity: previous_cell_output.capacity, address_hash: previous_cell_output.address_hash, generated_tx_hash: previous_cell_output.generated_by.tx_hash, cell_type: previous_cell_output.cell_type } display_input.merge!(attributes_for_dao_input(previous_cell_output)) if previous_cell_output.nervos_dao_withdrawing? - display_input.merge!(attributes_for_dao_input(cell_outputs[index])) if previous_cell_output.nervos_dao_deposit? + display_input.merge!(attributes_for_dao_input(cell_outputs[index], false)) if previous_cell_output.nervos_dao_deposit? CkbUtils.hash_value_to_s(display_input) end end end - def attributes_for_dao_input(nervos_dao_withdrawing_cell) + def attributes_for_dao_input(nervos_dao_withdrawing_cell, is_phase2 = true) nervos_dao_withdrawing_cell_generated_tx = nervos_dao_withdrawing_cell.generated_by nervos_dao_deposit_cell = nervos_dao_withdrawing_cell_generated_tx.cell_inputs.order(:id)[nervos_dao_withdrawing_cell.cell_index].previous_cell_output - started_block_number = Block.find(nervos_dao_deposit_cell.block.id).number - ended_block_number = Block.find(block_id).number + compensation_started_block = Block.select(:number, :timestamp).find(nervos_dao_deposit_cell.block.id) + 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_ended_block_number: compensation_ended_block.number, compensation_started_timestamp: compensation_started_block.timestamp, compensation_ended_timestamp: compensation_ended_block.timestamp, interest: interest } + if is_phase2 + locked_until_block = Block.select(:number, :timestamp).find(block_id) + attributes.merge!({ locked_until_block_number: locked_until_block.number, locked_until_block_timestamp: locked_until_block.timestamp }) + end - CkbUtils.hash_value_to_s({ started_block_number: started_block_number, ended_block_number: ended_block_number, interest: interest, dao_type_hash: ENV["DAO_TYPE_HASH"] }) + CkbUtils.hash_value_to_s(attributes) end def cellbase_display_inputs diff --git a/app/models/daily_statistic.rb b/app/models/daily_statistic.rb index d097220c3..b189e9177 100644 --- a/app/models/daily_statistic.rb +++ b/app/models/daily_statistic.rb @@ -1,5 +1,5 @@ class DailyStatistic < ApplicationRecord - VALID_INDICATORS = %w(transactions_count addresses_count total_dao_deposit).freeze + VALID_INDICATORS = %w(transactions_count addresses_count total_dao_deposit live_cells_count dead_cells_count avg_hash_rate avg_difficulty uncle_rate total_depositors_count).freeze end # == Schema Information @@ -22,4 +22,10 @@ class DailyStatistic < ApplicationRecord # mining_reward :string default("0") # deposit_compensation :string default("0") # treasury_amount :string default("0") +# live_cells_count :string default("0") +# dead_cells_count :string default("0") +# avg_hash_rate :string default("0") +# avg_difficulty :string default("0") +# uncle_rate :string default("0") +# total_depositors_count :string default("0") # diff --git a/app/models/dao_contract.rb b/app/models/dao_contract.rb index a23aa2bdc..cdfa513f6 100644 --- a/app/models/dao_contract.rb +++ b/app/models/dao_contract.rb @@ -1,5 +1,5 @@ class DaoContract < ApplicationRecord - validates :total_deposit, :interest_granted, :deposit_transactions_count, :withdraw_transactions_count, :depositors_count, :total_depositors_count, presence: true, numericality: { greater_than_or_equal_to: 0 } + validates :total_deposit, :claimed_compensation, :deposit_transactions_count, :withdraw_transactions_count, :depositors_count, :total_depositors_count, presence: true, numericality: { greater_than_or_equal_to: 0 } CONTRACT_NAME = "nervos_dao".freeze GENESIS_ISSUANCE = 336 * 10**8 ANNUAL_PRIMARY_ISSUANCE_BASE = GENESIS_ISSUANCE / 8 @@ -54,19 +54,11 @@ def depositor_changes end def unclaimed_compensation_changes - latest_daily_statistic.unclaimed_compensation.to_d - penultimate_daily_statistic.unclaimed_compensation.to_d + unclaimed_compensation.to_d - latest_daily_statistic.unclaimed_compensation.to_d end def claimed_compensation_changes - latest_daily_statistic.claimed_compensation.to_d - penultimate_daily_statistic.claimed_compensation.to_d - end - - def unclaimed_compensation - latest_daily_statistic.unclaimed_compensation - end - - def claimed_compensation - latest_daily_statistic.claimed_compensation + claimed_compensation - latest_daily_statistic.claimed_compensation.to_d end def average_deposit_time @@ -95,10 +87,6 @@ def latest_daily_statistic @latest_daily_statistic ||= DailyStatistic.order(created_at_unixtimestamp: :desc).first || OpenStruct.new(total_dao_deposit: 0, dao_depositors_count: 0, unclaimed_compensation: 0, claimed_compensation: 0, average_deposit_time: 0, mining_reward: 0, deposit_compensation: 0, treasury_amount: 0) end - def penultimate_daily_statistic - @penultimate_daily_statistic ||= DailyStatistic.order(created_at_unixtimestamp: :desc).second || OpenStruct.new(total_dao_deposit: 0, dao_depositors_count: 0, unclaimed_compensation: 0, claimed_compensation: 0, average_deposit_time: 0, mining_reward: 0, deposit_compensation: 0, treasury_amount: 0) - end - def alpha(start_epoch_number) i = ((start_epoch_number + 1) / EPOCHS_IN_PERIOD).floor p = PRIMARY_ISSUANCE_PER_YEAR_BASE / 2 ** i / 2190 @@ -134,11 +122,12 @@ def secondary_issuance(start_epoch) # # id :bigint not null, primary key # total_deposit :decimal(30, ) default(0) -# interest_granted :decimal(30, ) default(0) +# claimed_compensation :decimal(30, ) default(0) # deposit_transactions_count :bigint default(0) # withdraw_transactions_count :bigint default(0) # depositors_count :integer default(0) # total_depositors_count :bigint default(0) # created_at :datetime not null # updated_at :datetime not null +# unclaimed_compensation :decimal(30, ) # diff --git a/app/models/dao_event.rb b/app/models/dao_event.rb index e26949a39..ed28753af 100644 --- a/app/models/dao_event.rb +++ b/app/models/dao_event.rb @@ -7,6 +7,7 @@ class DaoEvent < ApplicationRecord belongs_to :ckb_transaction belongs_to :address + scope :created_after, ->(block_timestamp) { where("block_timestamp >= ?", block_timestamp) } scope :created_before, ->(block_timestamp) { where("block_timestamp <= ?", block_timestamp) } end diff --git a/app/models/null_address.rb b/app/models/null_address.rb index 8be15966e..a02dfabb8 100644 --- a/app/models/null_address.rb +++ b/app/models/null_address.rb @@ -53,4 +53,8 @@ def lock_script script = parsed_address.script LockScript.new(code_hash: script.code_hash, args: script.args, hash_type: script.hash_type) end + + def average_deposit_time + 0 + end end diff --git a/app/presenters/address_presenter.rb b/app/presenters/address_presenter.rb index e82228348..d998a9d22 100644 --- a/app/presenters/address_presenter.rb +++ b/app/presenters/address_presenter.rb @@ -58,6 +58,10 @@ def lock_info object.first.lock_script.lock_info end + def average_deposit_time + object.first.average_deposit_time + end + private attr_reader :object diff --git a/app/serializers/address_serializer.rb b/app/serializers/address_serializer.rb index 6df531110..e49e0ae93 100644 --- a/app/serializers/address_serializer.rb +++ b/app/serializers/address_serializer.rb @@ -26,4 +26,7 @@ class AddressSerializer attribute :mined_blocks_count do |object| object.mined_blocks_count.to_s end + attribute :average_deposit_time do |object| + object.average_deposit_time.to_s + end end diff --git a/app/serializers/daily_statistic_serializer.rb b/app/serializers/daily_statistic_serializer.rb index 9fece7ac4..d3aa9b91f 100644 --- a/app/serializers/daily_statistic_serializer.rb +++ b/app/serializers/daily_statistic_serializer.rb @@ -6,14 +6,38 @@ class DailyStatisticSerializer end attribute :transactions_count, if: Proc.new { |_record, params| - params && params[:indicator] == "transactions_count" + params && params[:indicator].include?("transactions_count") } attribute :addresses_count, if: Proc.new { |_record, params| - params && params[:indicator] == "addresses_count" + params && params[:indicator].include?("addresses_count") } attribute :total_dao_deposit, if: Proc.new { |_record, params| - params && params[:indicator] == "total_dao_deposit" + params && params[:indicator].include?("total_dao_deposit") + } + + attribute :live_cells_count, if: Proc.new { |_record, params| + params && params[:indicator].include?("live_cells_count") + } + + attribute :dead_cells_count, if: Proc.new { |_record, params| + params && params[:indicator].include?("dead_cells_count") + } + + attribute :avg_hash_rate, if: Proc.new { |_record, params| + params && params[:indicator].include?("avg_hash_rate") + } + + attribute :avg_difficulty, if: Proc.new { |_record, params| + params && params[:indicator].include?("avg_difficulty") + } + + attribute :uncle_rate, if: Proc.new { |_record, params| + params && params[:indicator].include?("uncle_rate") + } + + attribute :total_depositors_count, if: Proc.new { |_record, params| + params && params[:indicator].include?("total_depositors_count") } end diff --git a/app/serializers/dao_contract_serializer.rb b/app/serializers/dao_contract_serializer.rb index ef7080e21..bd449c532 100644 --- a/app/serializers/dao_contract_serializer.rb +++ b/app/serializers/dao_contract_serializer.rb @@ -40,4 +40,7 @@ class DaoContractSerializer attribute :estimated_apc do |object| object.estimated_apc.to_s end + attribute :claimed_compensation do |object| + object.claimed_compensation.to_s + end end diff --git a/app/serializers/dao_depositor_serializer.rb b/app/serializers/dao_depositor_serializer.rb index eb0fb697e..df52fc3c7 100644 --- a/app/serializers/dao_depositor_serializer.rb +++ b/app/serializers/dao_depositor_serializer.rb @@ -4,4 +4,7 @@ class DaoDepositorSerializer attribute :dao_deposit do |object| object.dao_deposit.to_s end + attribute :average_deposit_time do |object| + object.average_deposit_time.to_s + end end diff --git a/app/services/charts/daily_statistic_generator.rb b/app/services/charts/daily_statistic_generator.rb index 1400d49ba..d612d321c 100644 --- a/app/services/charts/daily_statistic_generator.rb +++ b/app/services/charts/daily_statistic_generator.rb @@ -11,30 +11,86 @@ def initialize(datetime = nil, from_scratch = false) def call daily_ckb_transactions_count = CkbTransaction.created_after(started_at).created_before(ended_at).count - cell_outputs = CellOutput.where.not(cell_type: "normal") + return if daily_ckb_transactions_count.zero? + mining_reward = Block.where("timestamp <= ?", ended_at).sum(:secondary_reward) - deposit_compensation = unclaimed_compensation(cell_outputs, current_tip_block) + claimed_compensation(cell_outputs) + deposit_compensation = unclaimed_compensation + claimed_compensation estimated_apc = DaoContract.default_contract.estimated_apc(current_tip_block.fraction_epoch) block_timestamp = Block.created_after(started_at).created_before(ended_at).recent.pick(:timestamp) addresses_count = processed_addresses_count daily_statistic = ::DailyStatistic.find_or_create_by!(created_at_unixtimestamp: to_be_counted_date.to_i) daily_statistic.update(block_timestamp: block_timestamp, transactions_count: daily_ckb_transactions_count, addresses_count: addresses_count, total_dao_deposit: total_dao_deposit, - dao_depositors_count: dao_depositors_count, unclaimed_compensation: unclaimed_compensation(cell_outputs, current_tip_block), - claimed_compensation: claimed_compensation(cell_outputs), average_deposit_time: average_deposit_time(cell_outputs), - mining_reward: mining_reward, deposit_compensation: deposit_compensation, treasury_amount: treasury_amount(cell_outputs, current_tip_block), - estimated_apc: estimated_apc) + dao_depositors_count: dao_depositors_count, unclaimed_compensation: unclaimed_compensation, + claimed_compensation: claimed_compensation, average_deposit_time: average_deposit_time, + mining_reward: mining_reward, deposit_compensation: deposit_compensation, treasury_amount: treasury_amount, + estimated_apc: estimated_apc, live_cells_count: live_cells_count, dead_cells_count: dead_cells_count, avg_hash_rate: avg_hash_rate, + avg_difficulty: avg_difficulty, uncle_rate: uncle_rate, total_depositors_count: total_depositors_count) end private attr_reader :datetime, :from_scratch + def live_cells_count + if from_scratch + CellOutput.generated_before(ended_at).unconsumed_at(ended_at).count + else + CellOutput.generated_after(started_at).generated_before(ended_at).count + yesterday_daily_statistic.live_cells_count.to_i - dead_cells_count_today + end + end + + def dead_cells_count_today + @dead_cells_count_today ||= CellOutput.consumed_after(started_at).consumed_before(ended_at).count + end + + def dead_cells_count + if from_scratch + CellOutput.generated_before(ended_at).consumed_before(ended_at).count + else + dead_cells_count_today + yesterday_daily_statistic.dead_cells_count.to_i + end + end + + def total_blocks_count + @total_blocks_count ||= Block.created_after(started_at).created_before(ended_at).count + end + + def epoch_numbers_for_the_day + Block.created_after(started_at).created_before(ended_at).distinct(:epoch).pluck(:epoch) + end + + def avg_hash_rate + first_block_for_the_day = Block.created_after(started_at).created_before(ended_at).recent.last + last_block_for_the_day = Block.created_after(started_at).created_before(ended_at).recent.first + total_block_time = last_block_for_the_day.timestamp - first_block_for_the_day.timestamp + + BigDecimal(total_difficulties_for_the_day) / total_block_time + end + + def avg_difficulty + BigDecimal(total_difficulties_for_the_day) / total_blocks_count + end + + def total_difficulties_for_the_day + @total_difficulties ||= + epoch_numbers_for_the_day.reduce(0) do |memo, epoch_number| + first_block_of_the_epoch = Block.created_after(started_at).created_before(ended_at).where(epoch: epoch_number).recent.last + last_block_of_the_epoch = Block.created_after(started_at).created_before(ended_at).where(epoch: epoch_number).recent.first + memo + first_block_of_the_epoch.difficulty * (last_block_of_the_epoch.number - first_block_of_the_epoch.number + 1) + end + end + + def uncle_rate + uncles_count = Block.created_after(started_at).created_before(ended_at).sum(:uncles_count) + BigDecimal(uncles_count) / total_blocks_count + end + def processed_addresses_count if from_scratch Address.created_before(ended_at).count else - Address.created_after(started_at).created_before(ended_at).count + latest_daily_statistic.addresses_count.to_i + Address.created_after(started_at).created_before(ended_at).count + yesterday_daily_statistic.addresses_count.to_i end end @@ -50,13 +106,37 @@ def current_tip_block end def total_dao_deposit - deposit_amount = DaoEvent.processed.deposit_to_dao.created_before(ended_at).sum(:value) - withdraw_amount = DaoEvent.processed.withdraw_from_dao.created_before(ended_at).sum(:value) - deposit_amount - withdraw_amount + if from_scratch + deposit_amount = DaoEvent.processed.deposit_to_dao.created_before(ended_at).sum(:value) + withdraw_amount = DaoEvent.processed.withdraw_from_dao.created_before(ended_at).sum(:value) + deposit_amount - withdraw_amount + else + deposit_amount_today = DaoEvent.processed.deposit_to_dao.created_after(started_at).created_before(ended_at).sum(:value) + withdraw_amount_today = DaoEvent.processed.withdraw_from_dao.created_after(started_at).created_before(ended_at).sum(:value) + deposit_amount_today - withdraw_amount_today + yesterday_daily_statistic.total_dao_deposit.to_i + end end def dao_depositors_count - DaoEvent.processed.new_dao_depositor.created_before(ended_at).count - DaoEvent.processed.take_away_all_deposit.created_before(ended_at).count + if from_scratch + total_depositors_count - DaoEvent.processed.take_away_all_deposit.created_before(ended_at).count + else + withdrawals_today = DaoEvent.processed.take_away_all_deposit.created_after(started_at).created_before(ended_at).count + new_depositors_today = DaoEvent.processed.new_dao_depositor.created_after(started_at).created_before(ended_at).count + new_depositors_today - withdrawals_today + yesterday_daily_statistic.dao_depositors_count.to_i + end + end + + def total_depositors_count + @total_depositors_count ||= + begin + if from_scratch + DaoEvent.processed.new_dao_depositor.created_before(ended_at).count + else + new_depositors_count_today = DaoEvent.processed.new_dao_depositor.created_after(started_at).created_before(ended_at).count + new_depositors_count_today + yesterday_daily_statistic.total_depositors_count.to_i + end + end end def to_be_counted_date @@ -68,35 +148,44 @@ def started_at end def ended_at - @ended_at ||= time_in_milliseconds(to_be_counted_date.end_of_day) + @ended_at ||= time_in_milliseconds(to_be_counted_date.end_of_day) - 1 end - def unclaimed_compensation(cell_outputs, current_tip_block) + def unclaimed_compensation @unclaimed_compensation ||= begin - phase1_dao_interests(cell_outputs) + unmade_dao_interests(cell_outputs, current_tip_block) + phase1_dao_interests + unmade_dao_interests end end - def claimed_compensation(cell_outputs) + def claimed_compensation @claimed_compensation ||= begin - cell_outputs.nervos_dao_withdrawing.consumed_before(ended_at).reduce(0) do |memo, nervos_dao_withdrawing_cell| - memo + CkbUtils.dao_interest(nervos_dao_withdrawing_cell) + if from_scratch + CellOutput.nervos_dao_withdrawing.consumed_before(ended_at).reduce(0) do |memo, nervos_dao_withdrawing_cell| + memo + CkbUtils.dao_interest(nervos_dao_withdrawing_cell) + end + else + claimed_compensation_today = + CellOutput.nervos_dao_withdrawing.consumed_after(started_at).consumed_before(ended_at).reduce(0) do |memo, nervos_dao_withdrawing_cell| + memo + CkbUtils.dao_interest(nervos_dao_withdrawing_cell) + end + + claimed_compensation_today + yesterday_daily_statistic.claimed_compensation.to_i end end end - def phase1_dao_interests(cell_outputs) - cell_outputs.nervos_dao_withdrawing.generated_before(ended_at).unconsumed_at(ended_at).reduce(0) do |memo, nervos_dao_withdrawing_cell| + def phase1_dao_interests + CellOutput.nervos_dao_withdrawing.generated_before(ended_at).unconsumed_at(ended_at).reduce(0) do |memo, nervos_dao_withdrawing_cell| memo + CkbUtils.dao_interest(nervos_dao_withdrawing_cell) end end - def unmade_dao_interests(cell_outputs, current_tip_block) + def unmade_dao_interests @unmade_dao_interests ||= begin - cell_outputs.nervos_dao_deposit.generated_before(ended_at).unconsumed_at(ended_at).reduce(0) do |memo, cell_output| + CellOutput.nervos_dao_deposit.generated_before(ended_at).unconsumed_at(ended_at).reduce(0) do |memo, cell_output| dao = cell_output.block.dao tip_dao = current_tip_block.dao parse_dao = CkbUtils.parse_dao(dao) @@ -106,16 +195,16 @@ def unmade_dao_interests(cell_outputs, current_tip_block) end end - def average_deposit_time(cell_outputs) + def average_deposit_time interest_bearing_deposits = 0 uninterest_bearing_deposits = 0 - sum_interest_bearing = cell_outputs.nervos_dao_withdrawing.generated_before(ended_at).unconsumed_at(ended_at).reduce(0) do |memo, nervos_dao_withdrawing_cell| + sum_interest_bearing = CellOutput.nervos_dao_withdrawing.generated_before(ended_at).unconsumed_at(ended_at).reduce(0) do |memo, nervos_dao_withdrawing_cell| nervos_dao_withdrawing_cell_generated_tx = nervos_dao_withdrawing_cell.generated_by nervos_dao_deposit_cell = nervos_dao_withdrawing_cell_generated_tx.cell_inputs.order(:id)[nervos_dao_withdrawing_cell.cell_index].previous_cell_output interest_bearing_deposits += nervos_dao_deposit_cell.capacity memo + nervos_dao_deposit_cell.capacity * (nervos_dao_withdrawing_cell.block_timestamp - nervos_dao_deposit_cell.block_timestamp) / MILLISECONDS_IN_DAY end - sum_uninterest_bearing = cell_outputs.nervos_dao_deposit.generated_before(ended_at).unconsumed_at(ended_at).reduce(0) do |memo, nervos_dao_deposit_cell| + sum_uninterest_bearing = CellOutput.nervos_dao_deposit.generated_before(ended_at).unconsumed_at(ended_at).reduce(0) do |memo, nervos_dao_deposit_cell| uninterest_bearing_deposits += nervos_dao_deposit_cell.capacity memo + nervos_dao_deposit_cell.capacity * (ended_at - nervos_dao_deposit_cell.block_timestamp) / MILLISECONDS_IN_DAY @@ -124,17 +213,17 @@ def average_deposit_time(cell_outputs) (sum_interest_bearing + sum_uninterest_bearing) / (interest_bearing_deposits + uninterest_bearing_deposits) end - def treasury_amount(cell_outputs, current_tip_block) + def treasury_amount parse_dao = CkbUtils.parse_dao(current_tip_block.dao) - parse_dao.s_i - unmade_dao_interests(cell_outputs, current_tip_block) + parse_dao.s_i - unmade_dao_interests end def time_in_milliseconds(time) (time.to_f * 1000).floor end - def latest_daily_statistic - ::DailyStatistic.order(created_at_unixtimestamp: :desc).first || OpenStruct.new(addresses_count: 0, total_dao_deposit: 0, dao_depositors_count: 0, unclaimed_compensation: 0, claimed_compensation: 0, average_deposit_time: 0, mining_reward: 0, deposit_compensation: 0, treasury_amount: 0) + def yesterday_daily_statistic + @yesterday_daily_statistic ||= ::DailyStatistic.find_by(created_at_unixtimestamp: to_be_counted_date.yesterday.beginning_of_day.to_i) || OpenStruct.new(addresses_count: 0, total_dao_deposit: 0, dao_depositors_count: 0, unclaimed_compensation: 0, claimed_compensation: 0, average_deposit_time: 0, mining_reward: 0, deposit_compensation: 0, treasury_amount: 0, total_depositors_count: 0, live_cells_count: 0, dead_cells_count: 0) end end end diff --git a/app/workers/address_average_deposit_time_generator.rb b/app/workers/address_average_deposit_time_generator.rb new file mode 100644 index 000000000..a277e5ca4 --- /dev/null +++ b/app/workers/address_average_deposit_time_generator.rb @@ -0,0 +1,40 @@ +class AddressAverageDepositTimeGenerator + include Sidekiq::Worker + + def perform + addresses = Address.where("dao_deposit > 0") + values = + addresses.map do |address| + [address.id, cal_average_deposit_time(address)] + end + columns = [:id, :average_deposit_time] + + Address.import! columns, values, validate: false, on_duplicate_key_update: [:average_deposit_time] + end + + private + + def cal_average_deposit_time(address) + interest_bearing_deposits = 0 + uninterest_bearing_deposits = 0 + milliseconds_in_day = BigDecimal(24 * 60 * 60 * 1000) + ended_at = time_in_milliseconds(Time.current) + sum_interest_bearing = address.cell_outputs.nervos_dao_withdrawing.unconsumed_at(ended_at).reduce(0) do |memo, nervos_dao_withdrawing_cell| + nervos_dao_withdrawing_cell_generated_tx = nervos_dao_withdrawing_cell.generated_by + nervos_dao_deposit_cell = nervos_dao_withdrawing_cell_generated_tx.cell_inputs.order(:id)[nervos_dao_withdrawing_cell.cell_index].previous_cell_output + interest_bearing_deposits += nervos_dao_deposit_cell.capacity + memo + nervos_dao_deposit_cell.capacity * (nervos_dao_withdrawing_cell.block_timestamp - nervos_dao_deposit_cell.block_timestamp) / milliseconds_in_day + end + sum_uninterest_bearing = address.cell_outputs.nervos_dao_deposit.unconsumed_at(ended_at).reduce(0) do |memo, nervos_dao_deposit_cell| + uninterest_bearing_deposits += nervos_dao_deposit_cell.capacity + + memo + nervos_dao_deposit_cell.capacity * (ended_at - nervos_dao_deposit_cell.block_timestamp) / milliseconds_in_day + end + + (sum_interest_bearing + sum_uninterest_bearing) / (interest_bearing_deposits + uninterest_bearing_deposits) + end + + def time_in_milliseconds(time) + (time.to_f * 1000).floor + end +end diff --git a/app/workers/dao_contract_unclaimed_compensation_generator.rb b/app/workers/dao_contract_unclaimed_compensation_generator.rb new file mode 100644 index 000000000..b80af481e --- /dev/null +++ b/app/workers/dao_contract_unclaimed_compensation_generator.rb @@ -0,0 +1,41 @@ +class DaoContractUnclaimedCompensationGenerator + include Sidekiq::Worker + + def perform + DaoContract.default_contract.update(unclaimed_compensation: cal_unclaimed_compensation) + end + + private + + def cal_unclaimed_compensation + phase1_dao_interests + unmade_dao_interests + end + + def phase1_dao_interests + CellOutput.nervos_dao_withdrawing.generated_before(ended_at).unconsumed_at(ended_at).reduce(0) do |memo, nervos_dao_withdrawing_cell| + memo + CkbUtils.dao_interest(nervos_dao_withdrawing_cell) + end + end + + def unmade_dao_interests + CellOutput.nervos_dao_deposit.generated_before(ended_at).unconsumed_at(ended_at).reduce(0) do |memo, cell_output| + dao = cell_output.block.dao + tip_dao = current_tip_block.dao + parse_dao = CkbUtils.parse_dao(dao) + tip_parse_dao = CkbUtils.parse_dao(tip_dao) + memo + (cell_output.capacity * tip_parse_dao.ar_i / parse_dao.ar_i) - cell_output.capacity + end + end + + def ended_at + @ended_at ||= time_in_milliseconds(Time.current) + end + + def time_in_milliseconds(time) + (time.to_f * 1000).floor + end + + def current_tip_block + Block.recent.first + end +end diff --git a/config/schedule.yml b/config/schedule.yml index 074dee849..c51d5c0f0 100644 --- a/config/schedule.yml +++ b/config/schedule.yml @@ -3,7 +3,7 @@ daily_statistic: class: "Charts::DailyStatistic" queue: critical -blockstatistic: +block_statistic: cron: "*/10 * * * *" class: "Charts::BlockStatistic" queue: critical @@ -16,4 +16,12 @@ epoch_statistic: chart_forked_event_processor: cron: "30 0 * * *" class: "Charts::ForkedEventProcessor" - queue: critical \ No newline at end of file + queue: critical + +cal_unclaimed_compensation: + cron: "0 */1 * * *" + class: "DaoContractUnclaimedCompensationGenerator" + +cal_address_average_deposit_time: + cron: "0 */6 * * *" + class: "AddressAverageDepositTimeGenerator" diff --git a/db/migrate/20200115020206_add_new_columns_to_daily_statistic.rb b/db/migrate/20200115020206_add_new_columns_to_daily_statistic.rb new file mode 100644 index 000000000..2e709f5be --- /dev/null +++ b/db/migrate/20200115020206_add_new_columns_to_daily_statistic.rb @@ -0,0 +1,10 @@ +class AddNewColumnsToDailyStatistic < ActiveRecord::Migration[6.0] + def change + add_column :daily_statistics, :live_cells_count, :string, default: "0" + add_column :daily_statistics, :dead_cells_count, :string, default: "0" + add_column :daily_statistics, :avg_hash_rate, :string, default: "0" + add_column :daily_statistics, :avg_difficulty, :string, default: "0" + add_column :daily_statistics, :uncle_rate, :string, default: "0" + add_column :daily_statistics, :total_depositors_count, :string, default: "0" + end +end diff --git a/db/migrate/20200121083529_add_claimed_and_unclaimed_compensation_to_dao_contract.rb b/db/migrate/20200121083529_add_claimed_and_unclaimed_compensation_to_dao_contract.rb new file mode 100644 index 000000000..d7eef62ce --- /dev/null +++ b/db/migrate/20200121083529_add_claimed_and_unclaimed_compensation_to_dao_contract.rb @@ -0,0 +1,6 @@ +class AddClaimedAndUnclaimedCompensationToDaoContract < ActiveRecord::Migration[6.0] + def change + rename_column :dao_contracts, :interest_granted, :claimed_compensation + add_column :dao_contracts, :unclaimed_compensation, :decimal, precision: 30, scale: 0 + end +end diff --git a/db/migrate/20200122060907_add_average_deposit_time_to_address.rb b/db/migrate/20200122060907_add_average_deposit_time_to_address.rb new file mode 100644 index 000000000..ab12e192f --- /dev/null +++ b/db/migrate/20200122060907_add_average_deposit_time_to_address.rb @@ -0,0 +1,5 @@ +class AddAverageDepositTimeToAddress < ActiveRecord::Migration[6.0] + def change + add_column :addresses, :average_deposit_time, :decimal + end +end diff --git a/db/schema.rb b/db/schema.rb index bcb30a30f..c4b54f25a 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2020_01_10_123617) do +ActiveRecord::Schema.define(version: 2020_01_22_060907) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -38,6 +38,7 @@ t.boolean "visible", default: true t.decimal "live_cells_count", precision: 30, default: "0" t.integer "mined_blocks_count", default: 0 + t.decimal "average_deposit_time" t.index ["address_hash"], name: "index_addresses_on_address_hash" t.index ["lock_hash"], name: "index_addresses_on_lock_hash", unique: true end @@ -172,17 +173,24 @@ t.string "mining_reward", default: "0" t.string "deposit_compensation", default: "0" t.string "treasury_amount", default: "0" + t.string "live_cells_count", default: "0" + t.string "dead_cells_count", default: "0" + t.string "avg_hash_rate", default: "0" + t.string "avg_difficulty", default: "0" + t.string "uncle_rate", default: "0" + t.string "total_depositors_count", default: "0" end create_table "dao_contracts", force: :cascade do |t| t.decimal "total_deposit", precision: 30, default: "0" - t.decimal "interest_granted", precision: 30, default: "0" + t.decimal "claimed_compensation", precision: 30, default: "0" t.bigint "deposit_transactions_count", default: 0 t.bigint "withdraw_transactions_count", default: 0 t.integer "depositors_count", default: 0 t.bigint "total_depositors_count", default: 0 t.datetime "created_at", precision: 6, null: false t.datetime "updated_at", precision: 6, null: false + t.decimal "unclaimed_compensation", precision: 30 end create_table "dao_events", force: :cascade do |t| diff --git a/doc/api.raml b/doc/api.raml index 56882ecb1..668d9d967 100644 --- a/doc/api.raml +++ b/doc/api.raml @@ -127,9 +127,12 @@ types: }, "pending_reward_blocks_count": { "type": integer - } + }, "lock_info": { "type": lock_info_attributes + }, + "average_deposit_time": { + "type": string } } } @@ -222,12 +225,28 @@ types: "cell_type": { type: "string" }, - "started_block_number": { - type: integer, + "compensation_started_block_number": { + type: string, required: false }, - "ended_block_number": { - type: integer, + "compensation_ended_block_number": { + type: string, + required: false + }, + "compensation_started_timestamp": { + type: string, + required: false + }, + "compensation_ended_timestamp": { + type: string, + required: false + }, + "locked_until_block_number": { + type: string, + required: false + }, + "locked_until_block_timestamp": { + type: string, required: false }, "interest": { @@ -1259,8 +1278,12 @@ types: "address_hash": "0xddbd21e6e51674bb961bb4c5f3cee9faa5da30e64be10628dc1cef292cbae323", "capacity": 100, "generated_tx_hash": "0x3abd21e6e51674bb961bb4c5f3cee9faa5da30e64be10628dc1cef292cbae321", - "started_block_number": 10, - "ended_block_number": 15, + "compensation_started_block_number": "10", + "compensation_ended_block_number": "15", + "compensation_started_timestamp": "1579592269056" + "compensation_ended_timestamp": "1579592269056", + "locked_until_block_number": "608395", + "locked_until_block_timestamp": "1579665264926", "interest": 100000, "cell_type": "dao", "dao_type_hash": "0xa20df8e80518e9b2eabc1a0efb0ebe1de83f8df9c867edf99d0c5895654fcde1", @@ -2022,7 +2045,8 @@ types: "epoch_number": "1000", "epoch_index": "789", "estimated_unlock_time": "1588259390000" - } + }, + "average_deposit_time": "35.780193591686053852850446361429702611876162589037114569656468227898621305950821369" } } } @@ -2502,7 +2526,8 @@ types: "code_hash": "0x0000000000000000000000000000000000000000000000000000000000000001" }, "dao_deposit": "10000", - "interest": "100" + "interest": "100", + "average_deposit_time": "35.780193591686053852850446361429702611876162589037114569656468227898621305950821369" } } } @@ -3320,4 +3345,4 @@ types: example: | { "25374730228.31050194" - } \ No newline at end of file + } diff --git a/lib/tasks/migration/fill_average_deposit_time_to_address.rake b/lib/tasks/migration/fill_average_deposit_time_to_address.rake new file mode 100644 index 000000000..5a7ee28d2 --- /dev/null +++ b/lib/tasks/migration/fill_average_deposit_time_to_address.rake @@ -0,0 +1,21 @@ +namespace :migration do + task fill_average_deposit_time_to_address: :environment do + addresses = Address.where("dao_deposit > 0").where(average_deposit_time: nil) + progress_bar = ProgressBar.create({ + total: addresses.count, + format: "%e %B %p%% %c/%C" + }) + + values = + addresses.map do |address| + progress_bar.increment + generator = AddressAverageDepositTimeGenerator.new + [address.id, generator.send(:cal_average_deposit_time, address)] + end + + columns = [:id, :average_deposit_time] + Address.import columns, values, on_duplicate_key_update: [:average_deposit_time] + + puts "done" + end +end diff --git a/public/api_doc.html b/public/api_doc.html index 56fb953c2..3e41a5f5a 100644 --- a/public/api_doc.html +++ b/public/api_doc.html @@ -371,7 +371,7 @@ "last": "https://explorer.nervos.org/api/v1/transactions?page=28909&page_size=15" } } -

get

get

Returns a transaction by transaction hash

get

get

Returns a transaction by transaction hash

/address_transactions

get

Returns transactions related to an address

/address_transactions

get

Returns transactions related to an address

/block_transactions

get

Returns transactions in the block

/block_transactions

get

Returns transactions in the block

/addresses

get

Returns address info

/addresses

get

Returns address info

/contract_transactions

get

Returns transactions under the contract

/contract_transactions

get

Returns transactions under the contract

/dao_contract_transactions

get

Returns the transaction related to dao contract by given transaction hash

/dao_contract_transactions

get

Returns the transaction related to dao contract by given transaction hash

/address_dao_transactions

get

Returns transactions under the address by given address hash

/address_dao_transactions

get

Returns transactions under the address by given address hash