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" } } -
Returns the transaction related to dao contract by given transaction hash
Returns the transaction related to dao contract by given transaction hash
Returns transactions under the address by given address hash
Returns transactions under the address by given address hash