diff --git a/Dockerfile b/Dockerfile index e1a8d8b..a72ca94 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,8 +1,5 @@ FROM ruby:3.1.2 -# throw errors if Gemfile has been modified since Gemfile.lock -RUN bundle config --global frozen 1 - # Get Rust RUN curl https://sh.rustup.rs -sSf | bash -s -- -y ENV PATH="/root/.cargo/bin:${PATH}" diff --git a/Gemfile b/Gemfile index fcaa28b..2a9914f 100644 --- a/Gemfile +++ b/Gemfile @@ -1,7 +1,7 @@ source 'https://rubygems.org' -gem 'scale_rb', '0.1.9' -# gem "scale_rb", path: "/Users/wuminzhe/Projects/wuminzhe/scale_rb" +gem 'scale_rb', '0.1.10' +# gem 'scale_rb', path: '/workspaces/scale_rb' gem 'sinatra' gem 'async', '~> 2.5' diff --git a/Gemfile.lock b/Gemfile.lock index 6d67d3b..9534314 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -86,7 +86,7 @@ GEM rubyzip (~> 2.3) ruby2_keywords (0.0.5) rubyzip (2.3.2) - scale_rb (0.1.9) + scale_rb (0.1.10) base58 blake2b_rs (~> 0.1.4) xxhash @@ -119,7 +119,7 @@ DEPENDENCIES lightly mongo (~> 2) puma - scale_rb (= 0.1.9) + scale_rb (= 0.1.10) sinatra BUNDLED WITH diff --git a/Rakefile b/Rakefile index 52cb740..643603f 100644 --- a/Rakefile +++ b/Rakefile @@ -1,24 +1,41 @@ -require "scale_rb" -require "./src/utils" -require "./src/supplies" -require "./src/metadata" -require "./src/track-goerli" -require "./src/track-pangolin" -require "./config/config.rb" +require 'scale_rb' +require './src/utils' +require './src/supplies' +require './src/metadata' +require './src/track-goerli' +require './src/track-pangolin' +require './src/storage' +require './src/account' +require './config/config' config = get_config task default: %w[gen_supplies_data_loop] task :update_metadata_loop do loop_do do - Rake::Task["update_darwinia_metadata"].invoke - Rake::Task["update_crab_metadata"].invoke - Rake::Task["update_pangolin_metadata"].invoke + Rake::Task['update_darwinia_metadata'].reenable + Rake::Task['update_darwinia_metadata'].invoke + Rake::Task['update_crab_metadata'].reenable + Rake::Task['update_crab_metadata'].invoke + Rake::Task['update_pangolin_metadata'].reenable + Rake::Task['update_pangolin_metadata'].invoke end end task :gen_supplies_data_loop do - loop_do { Rake::Task["gen_darwinia_supplies_data"].invoke } + loop_do do + Rake::Task['gen_darwinia_supplies_data'].reenable + Rake::Task['gen_darwinia_supplies_data'].invoke + end +end + +task :update_nominees_loop do + loop_do(60 * 2) do + Rake::Task['update_nominees'].reenable + Rake::Task['update_nominees'].invoke('darwinia') + Rake::Task['update_nominees'].reenable + Rake::Task['update_nominees'].invoke('crab') + end end ########################################## @@ -28,13 +45,13 @@ task :gen_darwinia_supplies_data do ethereum_rpc = config[:ethereum_rpc] darwinia_rpc = config[:darwinia_rpc] darwinia_metadata = JSON.parse(File.read(config[:metadata][:darwinia])) - generate_supplies("darwinia", ethereum_rpc, darwinia_rpc, darwinia_metadata) + generate_supplies('darwinia', ethereum_rpc, darwinia_rpc, darwinia_metadata) end task :update_darwinia_metadata do darwinia_rpc = config[:darwinia_rpc] darwinia_metadata_path = config[:metadata][:darwinia] - update_metadata("darwinia", darwinia_rpc, darwinia_metadata_path) + update_metadata('darwinia', darwinia_rpc, darwinia_metadata_path) end ########################################## @@ -43,7 +60,7 @@ end task :update_crab_metadata do crab_rpc = config[:crab_rpc] crab_metadata_path = config[:metadata][:crab] - update_metadata("crab", crab_rpc, crab_metadata_path) + update_metadata('crab', crab_rpc, crab_metadata_path) end ########################################## @@ -52,7 +69,7 @@ end task :update_pangolin_metadata do pangolin_rpc = config[:pangolin_rpc] pangolin_metadata_path = config[:metadata][:pangolin] - update_metadata("pangolin", pangolin_rpc, pangolin_metadata_path) + update_metadata('pangolin', pangolin_rpc, pangolin_metadata_path) end ########################################## @@ -65,3 +82,75 @@ end task :update_pangolin_goerli_messages do track_pangolin end + +require 'logger' +task :update_nominees, [:network_name] do |_t, args| + logger = Logger.new($stdout) + logger.level = Logger::DEBUG + + network_name = args[:network_name] + logger.debug "updating #{network_name} nominees..." + + rpc = config["#{network_name}_rpc".to_sym] + metadata = JSON.parse(File.read(config[:metadata][network_name.to_sym])) + + ring_pool = get_storage(rpc, metadata, 'darwinia_staking', 'ring_pool', nil, nil) + kton_pool = get_storage(rpc, metadata, 'darwinia_staking', 'kton_pool', nil, nil) + + # 1. Get all nominators with their nominees + # --------------------------------------- + # { nominator: nominee } + nominators = + get_storage( + rpc, + metadata, + 'darwinia_staking', + 'nominators', + nil, + nil + ) + + nominator_nominee_mapping = + nominators.map do |item| + nominator_address = "0x#{item[:storage_key][-40..]}" + nominee_address = item[:storage].to_hex + [nominator_address, nominee_address] + end.to_h + + # 2. Get all nominators' staking info + # --------------------------------------- + # { + # "nominator" => {:staked_ring=>10000000000000000000, :staked_kton=>0}, + # } + nominator_addresses = nominator_nominee_mapping.keys + nominator_staking_infos = get_accounts_staking_info(rpc, metadata, nominator_addresses) + + # 3. Sum up nominators' staking info of each nominee + # --------------------------------------- + # { + # "nominee" => {:staked_ring=>total, :staked_kton=>total}, + # } + result = + nominator_addresses.each_with_object({}) do |nominator_address, acc| + nominator_staking_info = nominator_staking_infos[nominator_address] + nominee_address = nominator_nominee_mapping[nominator_address] + acc[nominee_address] = + { + staked_ring: (acc[nominee_address]&.[](:staked_ring) || 0) + nominator_staking_info[:staked_ring], + staked_kton: (acc[nominee_address]&.[](:staked_kton) || 0) + nominator_staking_info[:staked_kton] + } + end + + # 4. Calculate power of each nominee + # --------------------------------------- + nominee_powers = result.map do |nominee_address, staking_info| + power = calc_power(staking_info[:staked_ring], staking_info[:staked_kton], ring_pool, kton_pool) + [nominee_address, power] + end.to_h + logger.debug JSON.pretty_generate(nominee_powers) + + # 5. write to file + # --------------------------------------- + logger.debug "writing #{network_name} nominees to file..." + write_data_to_file(nominee_powers, "#{network_name}-nominees.json") +end diff --git a/docker-compose.yml b/docker-compose.yml index 0d725ad..755f7ab 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -12,16 +12,13 @@ services: command: rake gen_supplies_data_loop volumes: - ./data:/usr/src/app/data - update_goerli_pangolin_messages: + update_nominees: build: . env_file: - .env - command: rake update_goerli_pangolin_messages - update_pangolin_goerli_messages: - build: . - env_file: - - .env - command: rake update_pangolin_goerli_messages + command: rake update_nominees_loop + volumes: + - ./data:/usr/src/app/data server: build: . env_file: diff --git a/src/account.rb b/src/account.rb index b92a276..1f2597c 100644 --- a/src/account.rb +++ b/src/account.rb @@ -1,12 +1,13 @@ -require "scale_rb" -require_relative "./utils" +require 'scale_rb' +require_relative './utils' +require_relative './storage' def get_account_info(rpc, metadata, address) param = [c(address)] # RING info = - ScaleRb::HttpClient.get_storage2(rpc, "System", "Account", param, metadata) + ScaleRb::HttpClient.get_storage2(rpc, 'System', 'Account', param, metadata) free = info[:data][:free].to_f / 10**18 reserved = info[:data][:reserved].to_f / 10**18 @@ -14,59 +15,70 @@ def get_account_info(rpc, metadata, address) kton_info = ScaleRb::HttpClient.get_storage2( rpc, - "Assets", - "Account", + 'Assets', + 'Account', [1026] + param, - metadata, + metadata ) - kton_free = kton_info ? kton_info[ - :balance - ].to_f / 10**18 : 0 + kton_free = if kton_info + kton_info[ + :balance + ].to_f / 10**18 + else + 0 + end # LOCKS locks = - ScaleRb::HttpClient.get_storage2(rpc, "Balances", "Locks", param, metadata) + ScaleRb::HttpClient.get_storage2(rpc, 'Balances', 'Locks', param, metadata) locked = locks.reduce(0) { |sum, lock| lock[:amount] + sum }.to_f / 10**18 # DEPOSITS deposits = ScaleRb::HttpClient.get_storage2( rpc, - "Deposit", - "Deposits", + 'Deposit', + 'Deposits', param, - metadata, + metadata ) - deposits = deposits ? - deposits.map do |deposit| - deposit[:value] = deposit[:value].to_f / 10**18 - deposit[:start_time] = Time.at(deposit[:start_time] / 1000) - deposit[:expired_time] = Time.at(deposit[:expired_time] / 1000) - deposit - end : [] + deposits = if deposits + deposits.map do |deposit| + deposit[:value] = deposit[:value].to_f / 10**18 + deposit[:start_time] = Time.at(deposit[:start_time] / 1000) + deposit[:expired_time] = Time.at(deposit[:expired_time] / 1000) + deposit + end + else + [] + end # STAKING LEDGER ledger = ScaleRb::HttpClient.get_storage2( rpc, - "DarwiniaStaking", - "Ledgers", + 'DarwiniaStaking', + 'Ledgers', param, - metadata, + metadata ) staked_ring = ledger ? ledger[:staked_ring].to_f / 10**18 : 0 - unstaking_ring = ledger ? - (ledger[:unstaking_ring].map do |item| - [item[0].to_f / 10**18, item[1]] - end.reduce(0) { |sum, item| sum + item[0] } ) - : 0 + unstaking_ring = if ledger + (ledger[:unstaking_ring].map do |item| + [item[0].to_f / 10**18, item[1]] + end.reduce(0) { |sum, item| sum + item[0] }) + else + 0 + end staked_kton = ledger ? ledger[:staked_kton].to_f / 10**18 : 0 - unstaking_kton = ledger ? - (ledger[:unstaking_kton].map do |item| - [item[0].to_f / 10**18, item[1]] - end.reduce(0) { |sum, item| sum + item[0] } ) - : 0 + unstaking_kton = if ledger + (ledger[:unstaking_kton].map do |item| + [item[0].to_f / 10**18, item[1]] + end.reduce(0) { |sum, item| sum + item[0] }) + else + 0 + end { ring: @@ -75,21 +87,116 @@ def get_account_info(rpc, metadata, address) kton_free + staked_kton + unstaking_kton, items: { transferable: free - locked, - reserved: reserved, - locked: locked, - deposits: deposits, + reserved:, + locked:, + deposits:, staking: ledger } } end -# require_relative "../config/config.rb" +def staked_ring_in_deposits(ledger, deposits) + if deposits.nil? + 0 + else + # id => deposit + all_deposits = deposits.select { |deposit| deposit[:in_use] == true }.map { |deposit| [deposit[:id], deposit] }.to_h + + deposit_ids_in_ledger = ledger[:staked_deposits] # + ledger[:unstaking_deposits] + deposit_ids_in_ledger.reduce(0) do |sum, deposit_id| + all_deposits.key?(deposit_id) ? sum + all_deposits[deposit_id][:value] : sum + end + end +end + +def get_all_deposits(rpc, metadata) + # DEPOSITS + storages = get_storage(rpc, metadata, 'deposit', 'deposits', nil, nil) + storages.each_with_object({}) do |storage, acc| + acc["0x#{storage[:storage_key][-40..]}"] = storage[:storage] + end +end + +def get_all_ledgers(rpc, metadata) + # STAKING LEDGER + storages = get_storage(rpc, metadata, 'darwinia_staking', 'ledgers', nil, nil) + storages.each_with_object({}) do |storage, acc| + acc["0x#{storage[:storage_key][-40..]}"] = storage[:storage] + end +end + +def get_accounts_staking_info(rpc, metadata, addresses) + all_deposits = get_all_deposits(rpc, metadata) + all_ledgers = get_all_ledgers(rpc, metadata) + + addresses.map(&:downcase).reduce({}) do |acc, address| + ledger = all_ledgers[address] + deposits = all_deposits[address] + + # staked ring in ledger + staked ring in deposits + staked_ring = + if ledger + ledger[:staked_ring] + + # ledger[:unstaking_ring].reduce(0) { |sum, item| sum + item[0] } + + # 这个account的ledger 和 这个account的所有deposits + staked_ring_in_deposits(ledger, deposits) + else + 0 + end + + # staked kton in ledger + staked_kton = ledger ? ledger[:staked_kton] : 0 + + acc.merge( + { + address => { + staked_ring:, + staked_kton: + } + } + ) + end +end + +def get_nominee_staking_info(rpc, metadata, nominee_address) + result = get_storage(rpc, metadata, 'darwinia_staking', 'exposures', nominee_address, nil) + + nominators = result[:nominators].map do |nominator| + nominator[:who].to_hex + end + get_accounts_staking_info(rpc, metadata, nominators) +end + +def get_nominee_power(rpc, metadata, address) + staking_info = get_nominee_staking_info(rpc, metadata, address.downcase) + total = staking_info.values.each_with_object({ staked_ring: 0, staked_kton: 0 }) do |info, acc| + acc[:staked_ring] += info[:staked_ring] + acc[:staked_kton] += info[:staked_kton] + end + + ring_pool = get_storage(rpc, metadata, 'darwinia_staking', 'ring_pool', nil, nil) + kton_pool = get_storage(rpc, metadata, 'darwinia_staking', 'kton_pool', nil, nil) + + calc_power(total[:staked_ring], total[:staked_kton], ring_pool, kton_pool) +end + +# require_relative '../config/config' # config = get_config # crab_metadata = JSON.parse(File.read(config[:metadata][:crab])) # crab_rpc = config[:crab_rpc] -# p get_account_info( -# crab_rpc, -# crab_metadata, -# "0x20FF2599f29876D5a5345a5cD6592BEd749CECEa", -# ) +# result = get_account_staking_info( +# crab_rpc, +# crab_metadata, +# '0x17863f9473ce423ba00C9A3B13Be3af48B93f531' +# ) +# puts JSON.pretty_generate(result) + +# puts get_all_deposits(crab_rpc, crab_metadata) +# all_ledgers = get_all_ledgers(crab_rpc, crab_metadata) +# puts all_ledgers['0x82373ccc6ed8ced89293307efdcf31b73b8c49c8'] +# puts get_accounts_staking_info(crab_rpc, crab_metadata, +# %w[0x17863f9473ce423ba00C9A3B13Be3af48B93f531 0x82373ccc6ed8ced89293307efdcf31b73b8c49c8]) + +# puts get_nominee_power(crab_rpc, crab_metadata, '0x0a1287977578F888bdc1c7627781AF1cc000e6ab'.downcase) +# puts get_nominee_staking_info(crab_rpc, crab_metadata, '0x0a1287977578F888bdc1c7627781AF1cc000e6ab') diff --git a/src/server.rb b/src/server.rb index 99dd9be..7d18fa8 100644 --- a/src/server.rb +++ b/src/server.rb @@ -240,6 +240,20 @@ ############################################################################## # General ############################################################################## +get '/:network/nominees' do + network = params[:network].downcase + unless %w[darwinia crab pangolin].include?(network) + raise_with 404, + "network #{network} not found, only `darwinia`, `crab` and `pangolin` are supported" + end + + result = File.read("./data/#{network}-nominees.json") + result = JSON.parse(result) + + content_type :json + { code: 0, data: result }.to_json +end + get '/:network/metadata' do network = params[:network].downcase unless %w[darwinia crab pangolin].include?(network) @@ -253,7 +267,7 @@ end get '/:network/accounts/:address' do - network = network = params[:network].downcase + network = params[:network].downcase unless %w[darwinia crab pangolin].include?(network) raise_with 404, "network #{network} not found, only `darwinia`, `crab` and `pangolin` are supported" diff --git a/src/utils.rb b/src/utils.rb index f60743e..8380245 100644 --- a/src/utils.rb +++ b/src/utils.rb @@ -55,7 +55,24 @@ def matches_time_span_pattern?(input_string) !!(input_string =~ regex) end -puts matches_time_span_pattern?('12') +# https://github.com/darwinia-network/darwinia2.0-staking-ui/blob/8f9d88b4c874c36cedf140f0a288250507d3b293/src/utils/misc.ts#L5 +def calc_power(staked_ring, staked_kton, ring_pool, kton_pool) + raise 'ring_pool is zero' if ring_pool.zero? + + d = kton_pool.zero? ? 0 : ring_pool.to_f / kton_pool + 1_000_000_000 * (staked_ring + staked_kton * d) / (ring_pool * 2) +end + +def write_data_to_file(data, filename) + data_dir = './data' + FileUtils.mkdir_p(data_dir) unless File.directory?(data_dir) + File.write( + File.join(data_dir, filename), + data.to_json + ) +end + +# puts matches_time_span_pattern?('12') # require "scale_rb" # require "eth" @@ -71,4 +88,3 @@ def matches_time_span_pattern?(input_string) # ] # ) # ) -