From 4894432262f789460190b21df6531147f205b8b0 Mon Sep 17 00:00:00 2001 From: lpthong90 Date: Mon, 4 Mar 2024 21:38:53 +0700 Subject: [PATCH] refactor --- app/controllers/blocks_controller.rb | 4 +- app/controllers/transactions_controller.rb | 2 +- app/models/address.rb | 13 +-- app/models/application_model.rb | 11 ++- app/models/block.rb | 92 ++++++------------- .../concerns/my_normalization_concern.rb | 44 --------- app/models/concerns/normalization_concern.rb | 83 ----------------- app/models/concerns/string_concern.rb | 9 -- app/models/transaction.rb | 85 ++++++----------- app/services/event_handler_service.rb | 6 +- app/types/hex_integer.rb | 24 +++++ config/initializers/custom_types.rb | 3 + 12 files changed, 98 insertions(+), 278 deletions(-) delete mode 100644 app/models/concerns/my_normalization_concern.rb delete mode 100644 app/models/concerns/normalization_concern.rb delete mode 100644 app/models/concerns/string_concern.rb create mode 100644 app/types/hex_integer.rb create mode 100644 config/initializers/custom_types.rb diff --git a/app/controllers/blocks_controller.rb b/app/controllers/blocks_controller.rb index 66c92c5..60f5ad0 100644 --- a/app/controllers/blocks_controller.rb +++ b/app/controllers/blocks_controller.rb @@ -22,8 +22,8 @@ def load_block web3.get_block_by_number(params[:block_id].to_i, include_txs: true) end Block.from_json(data) - # rescue - # nil + rescue + nil end end diff --git a/app/controllers/transactions_controller.rb b/app/controllers/transactions_controller.rb index 8847192..ef105f0 100644 --- a/app/controllers/transactions_controller.rb +++ b/app/controllers/transactions_controller.rb @@ -17,7 +17,7 @@ def load_transactions def load_transaction @transaction = begin data = web3.get_transaction_by_hash(params[:hash]) - Transaction.new.from_json(data.to_json) + Transaction.from_json(data) rescue nil end diff --git a/app/models/address.rb b/app/models/address.rb index a3ca5e9..45d00ed 100644 --- a/app/models/address.rb +++ b/app/models/address.rb @@ -1,19 +1,12 @@ -class Address - include ActiveModel::API - include ActiveModel::Validations - include ActiveModel::Serializers::JSON - - attr_accessor :address, :balance +class Address < ApplicationModel + attribute :address, :string + attribute :balance, :hex_integer FORMAT_REGEX = /0x[a-fA-F0-9]{40}/ validates :address, presence: true validates_format_of :address, with: FORMAT_REGEX - def balance=(value) - @balance = value.to_i(16) rescue 0 - end - def eth_balance balance.to_d * 1e-18 end diff --git a/app/models/application_model.rb b/app/models/application_model.rb index 4c6ac8a..9444876 100644 --- a/app/models/application_model.rb +++ b/app/models/application_model.rb @@ -7,12 +7,8 @@ class ApplicationModel include Turbo::Broadcastable - # include NormalizationConcern - include MyNormalizationConcern - include StringConcern - def attributes - instance_values + instance_values["attributes"] end def attributes=(hash) @@ -21,4 +17,9 @@ def attributes=(hash) send("#{new_key}=", value) end end + + def self.from_json(data) + return unless data + self.new(**data) + end end diff --git a/app/models/block.rb b/app/models/block.rb index c9f9b14..735544f 100644 --- a/app/models/block.rb +++ b/app/models/block.rb @@ -1,77 +1,39 @@ -class Block - include ActiveModel::API - include ActiveModel::Validations - include ActiveModel::Serializers::JSON - - include Turbo::Broadcastable - - include StringConcern - - attr_accessor :baseFeePerGas, :difficulty, :extraData, :gasLimit, :gasUsed, :hash, - :logsBloom, :miner, :mixHash, :nonce, :number, :parentHash, :receiptsRoot, - :sha3Uncles, :size, :stateRoot, :timestamp, :transactionsRoot, :withdrawals, - :withdrawalsRoot, :transactions, :totalDifficulty, :uncles +class Block < ApplicationModel + attribute :baseFeePerGas, :hex_integer + attribute :difficulty, :hex_integer + attribute :extraData, :string + attribute :gasLimit, :hex_integer + attribute :gasUsed, :hex_integer + attribute :hash, :string + attribute :logsBloom, :string + attribute :miner, :string + attribute :mixHash, :string + attribute :nonce, :hex_integer + attribute :number, :hex_integer + attribute :parentHash, :string + attribute :receiptsRoot, :string + attribute :sha3Uncles, :string + attribute :size, :hex_integer + attribute :stateRoot, :string + attribute :timestamp, :hex_integer + attribute :transactionsRoot, :string + attribute :withdrawalsRoot, :string + attribute :totalDifficulty, :hex_integer + + attr_accessor :withdrawals, :transactions, :uncles NUMBER_FORMAT_REGEX = /-?\d+/ validates :hash, presence: true - def attributes - { - hash: hash, - number: number, - timestamp: timestamp, - miner: miner, - transactions: transactions, - withdrawals: withdrawals, - baseFeePerGas: baseFeePerGas, - totalDifficulty: totalDifficulty, - size: size - } - end - - def totalDifficulty=(value) - @totalDifficulty = value.class == Integer ? value : value.to_i(16) rescue nil - end - - def size=(value) - @size = value.class == Integer ? value : value.to_i(16) - end - - def number=(value) - @number = value.class == Integer ? value : value.to_i(16) - end - - def baseFeePerGas=(value) - @baseFeePerGas = value.class == Integer ? value : value.to_i(16) - end - - def datetime - Time.at(self.timestamp) - end - - def timestamp=(value) - @timestamp = value.class == Integer ? value : value.to_i(16) - end - def transactions=(value) @transactions = value ? value : [] end - def broadcast_to_channel - ActionCable.server.broadcast('block_channel', { block: self }) - end - - def self.from_event(event) - from_json(event&.dig("params", "result")) - end - - def self.from_json(data) - return unless data - Block.new(**data) + def datetime + Time.at(self.timestamp) end - def seconds_ago current_timestamp - self.timestamp end @@ -84,6 +46,10 @@ def gwei_baseFeePerGas self.baseFeePerGas.to_d * 1e-9 end + def broadcast_to_channel + ActionCable.server.broadcast('block_channel', { block: self }) + end + private def current_timestamp Time.now.to_i diff --git a/app/models/concerns/my_normalization_concern.rb b/app/models/concerns/my_normalization_concern.rb deleted file mode 100644 index 5c8d8a9..0000000 --- a/app/models/concerns/my_normalization_concern.rb +++ /dev/null @@ -1,44 +0,0 @@ -module MyNormalizationConcern - extend ActiveSupport::Concern - - class NormalizedType < DelegateClass(ActiveModel::Type::Value) - private attr_reader :normalizer, :allow_nil - - def initialize(subtype, normalizer, allow_nil: true) - @normalizer = normalizer - @allow_nil = allow_nil - super(subtype) - end - - def cast(value) - super(value).then do |casted| - next if casted.nil? && allow_nil - - normalizer.call(casted) - end - end - - def serialize(value) - return super(value) if value.nil? && allow_nil - - super(normalizer.call(value)) - end - - def deserialize(value) - super(value).then do |casted| - next if casted.nil? && allow_nil - - normalizer.call(casted) - end - end - end - - module ClassMethods - def normalize(attr_name, proc, **options) - puts "=> normalize I'm here" - attribute(attr_name) do - NormalizedType.new(_1, proc, **options) - end - end - end -end \ No newline at end of file diff --git a/app/models/concerns/normalization_concern.rb b/app/models/concerns/normalization_concern.rb deleted file mode 100644 index 53aa8c6..0000000 --- a/app/models/concerns/normalization_concern.rb +++ /dev/null @@ -1,83 +0,0 @@ -module NormalizationConcern - extend ActiveSupport::Concern - - included do - class_attribute :normalized_attributes, default: Set.new - - before_validation :normalize_changed_in_place_attributes - end - - def normalize_attribute(name) - self[name] = self[name] - end - - module ClassMethods - def normalizes(*names, with:, apply_to_nil: false) - names.each do |name| - attribute(name) do |cast_type| - NormalizedValueType.new(cast_type: cast_type, normalizer: with, normalize_nil: apply_to_nil) - end - end - - self.normalized_attributes += names.map(&:to_sym) - end - - def normalize(name, value) - type_for_attribute(name).cast(value) - end - end - - private - def normalize_changed_in_place_attributes - self.class.normalized_attributes.each do |name| - normalize_attribute(name) if attribute_changed_in_place?(name) - end - end - - class NormalizedValueType < DelegateClass(ActiveModel::Type::Value) - include ActiveModel::Type::SerializeCastValue - - attr_reader :cast_type, :normalizer, :normalize_nil - alias :normalize_nil? :normalize_nil - - def initialize(cast_type:, normalizer:, normalize_nil:) - @cast_type = cast_type - @normalizer = normalizer - @normalize_nil = normalize_nil - super(cast_type) - end - - def cast(value) - normalize(super(value)) - end - - def serialize(value) - serialize_cast_value(cast(value)) - end - - def serialize_cast_value(value) - ActiveModel::Type::SerializeCastValue.serialize(cast_type, value) - end - - def ==(other) - self.class == other.class && - normalize_nil? == other.normalize_nil? && - normalizer == other.normalizer && - cast_type == other.cast_type - end - alias eql? == - - def hash - [self.class, cast_type, normalizer, normalize_nil?].hash - end - - def inspect - Kernel.instance_method(:inspect).bind_call(self) - end - - private - def normalize(value) - normalizer.call(value) unless value.nil? && !normalize_nil? - end - end -end diff --git a/app/models/concerns/string_concern.rb b/app/models/concerns/string_concern.rb deleted file mode 100644 index 7c891c8..0000000 --- a/app/models/concerns/string_concern.rb +++ /dev/null @@ -1,9 +0,0 @@ -module StringConcern - extend ActiveSupport::Concern - - def camel_to_snake(string) - string.gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2') - .gsub(/([a-z\d])([A-Z])/, '\1_\2') - .downcase - end -end \ No newline at end of file diff --git a/app/models/transaction.rb b/app/models/transaction.rb index be6761c..073e430 100644 --- a/app/models/transaction.rb +++ b/app/models/transaction.rb @@ -1,44 +1,29 @@ -class Transaction - include ActiveModel::API - include ActiveModel::Validations - include ActiveModel::Serializers::JSON - - include Turbo::Broadcastable - - attr_accessor :hash, :from, :to, :blockHash, :blockNumber, :gas, :gasPrice, - :input, :nonce, :r, :s, :transactionIndex, :type, :v, :value, - :accessList, :chainId, :maxFeePerGas, :maxPriorityFeePerGas, :yParity +class Transaction < ApplicationModel + attribute :hash, :string + attribute :from, :string + attribute :to, :string + attribute :blockHash, :string + attribute :blockNumber, :hex_integer + attribute :gas, :hex_integer + attribute :gasPrice, :hex_integer + attribute :input, :hex_integer + attribute :nonce, :hex_integer + attribute :r, :string + attribute :s, :string + attribute :transactionIndex, :hex_integer + attribute :type, :hex_integer + attribute :v, :hex_integer + attribute :value, :hex_integer + attribute :chainId, :hex_integer + attribute :maxFeePerGas, :hex_integer + attribute :maxPriorityFeePerGas, :hex_integer + attribute :yParity, :hex_integer + + attr_accessor :accessList HASH_FORMAT_REGEX = /0x[a-fA-F0-9]{64}/ - def attributes - {hash: hash, from: from, to: to, value: value, blockNumber: blockNumber, gas: gas, gasPrice: gasPrice} - # instance_values - end - - validates :hash, :from, :to, presence: true - # :blockHash, :blockNumber, :hash, :from, :gas, :gasPrice, :input, :nonce, - # :r, :s, :to, :transactionIndex, :type, :v, :value, presence: true - validates_format_of :hash, with: HASH_FORMAT_REGEX - # validates_format_of :from, :to, with: /0x[a-fA-F0-9]{40}/ - # validates_format_of :blockHash, with: /0x[a-fA-F0-9]{64}/ - - def value=(new_value) - @value = new_value.class == Integer ? new_value : new_value.to_i(16) - end - - def gas=(new_value) - @gas = new_value.class == Integer ? new_value : new_value.to_i(16) - end - - def gasPrice=(new_value) - @gasPrice = new_value.class == Integer ? new_value : new_value.to_i(16) - end - - def blockNumber=(value) - @blockNumber = value.class == Integer ? value : value.to_i(16) - end def eth_value self.value.to_d * 1e-18 @@ -48,13 +33,12 @@ def wei_value self.value end - def self.from_event(event) - from_json(event&.dig("params", "result", "transaction")) + def fee + self.gas * self.gasPrice end - def self.from_json(data) - return unless data - Transaction.new(**data) + def eth_fee + fee.to_d * 1e-18 end def broadcast_to_channel @@ -64,21 +48,4 @@ def broadcast_to_channel def broadcast_to_stream broadcast_append_to "transactions", target: "transactions", partial: "transactions/transaction", locals: { transaction: self } end - - def seconds_ago - current_timestamp - self.timestamp - end - - def fee - self.gas * self.gasPrice - end - - def eth_fee - fee.to_d * 1e-18 - end - - private - def current_timestamp - Time.now.to_i - end end diff --git a/app/services/event_handler_service.rb b/app/services/event_handler_service.rb index 9133608..c0ef7a0 100644 --- a/app/services/event_handler_service.rb +++ b/app/services/event_handler_service.rb @@ -23,7 +23,8 @@ def handle def handle_mined_transaction_event # ActionCable.server.broadcast('transaction_channel', msg.data) - transaction = Transaction.from_event(event) + data = event&.dig("params", "result", "transaction") + transaction = Transaction.from_json(data) if transaction.valid? EventCache.add_transaction(transaction) puts "=> cached tx: #{transaction.hash}" @@ -33,7 +34,8 @@ def handle_mined_transaction_event end def handle_new_block_event - block = Block.from_event(event) + data = event&.dig("params", "result") + block = Block.from_json(data) if block.valid? EventCache.add_block(block) puts "=> cached block: #{block.hash}" diff --git a/app/types/hex_integer.rb b/app/types/hex_integer.rb new file mode 100644 index 0000000..7c7c0fe --- /dev/null +++ b/app/types/hex_integer.rb @@ -0,0 +1,24 @@ +module Types + class HexInteger < ActiveModel::Type::Integer + def type + :hex_integer + end + + def deserialize(value) + return if value.blank? + value.to_i(16) + end + + def serialize(value) + value = ensure_in_range(super) + "0x#{value.to_s(16)}" + end + + private + + def cast_value(value) + return value if value.class == Integer + value.to_i(16) rescue nil + end + end +end \ No newline at end of file diff --git a/config/initializers/custom_types.rb b/config/initializers/custom_types.rb new file mode 100644 index 0000000..6559c78 --- /dev/null +++ b/config/initializers/custom_types.rb @@ -0,0 +1,3 @@ +Dir['./app/types/*.rb'].each { require _1 } + +ActiveModel::Type.register(:hex_integer, Types::HexInteger)