Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 8 additions & 6 deletions app/models/validator_score_v1.rb
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,10 @@ def assign_consensus_mods_score
end

def assign_vote_latency_score
return 0 if vote_latency_history.blank?
if vote_latency_history.blank?
self.vote_latency_score = 0
return
end

self.vote_latency_score = case vote_latency_history.last
when 0...2 then 2
Expand Down Expand Up @@ -348,11 +351,10 @@ def skipped_after_moving_average_history_push(val)
end

def vote_latency_history_push(val)
self.vote_latency_history = [] if vote_latency_history.nil?
vote_latency_history << val
if vote_latency_history.length > MAX_HISTORY
self.vote_latency_history = vote_latency_history[-MAX_HISTORY..-1]
end
arr = vote_latency_history || []
arr << val
arr = arr[-MAX_HISTORY..-1] if arr.length > MAX_HISTORY
self.vote_latency_history = arr
end

def to_builder(with_history: false)
Expand Down
59 changes: 53 additions & 6 deletions app/services/blockchain/get_block_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ def call
@block = solana_client_request(
@config_urls,
:get_block,
params: [@slot_number, {}]
params: [@slot_number, { encoding: "jsonParsed", maxSupportedTransactionVersion: 0 }]
)
if @block[:error]
if @block[:error].include?("429")
Expand Down Expand Up @@ -70,15 +70,20 @@ def destroy_block
def process_transactions
if @block["transactions"]
vote_txs = @block["transactions"].select do |tx|
tx["transaction"]["message"]["accountKeys"].include?("Vote111111111111111111111111111111111111111")
tx.dig("transaction", "message", "accountKeys")&.any? { |key|
key.is_a?(Hash) ? key["pubkey"] == "Vote111111111111111111111111111111111111111" : key == "Vote111111111111111111111111111111111111111"
Comment thread
kbiala marked this conversation as resolved.
}
end
vote_txs.in_groups_of(1000) do |group|
batch = group.compact.map do |tx|
message = tx["transaction"]["message"]
account_keys = extract_account_keys(message)
voted_slot = extract_voted_slot(message)
{
account_key_1: tx["transaction"]["message"]["accountKeys"][0],
account_key_2: tx["transaction"]["message"]["accountKeys"][1],
account_key_3: tx["transaction"]["message"]["accountKeys"][2],
recent_blockhash: tx["transaction"]["message"]["recentBlockhash"],
account_key_1: account_keys[0],
account_key_2: account_keys[1],
account_key_3: account_keys[2],
recent_blockhash: voted_slot.to_s,
fee: tx["meta"]["fee"],
post_balances: tx["meta"]["postBalances"],
pre_balances: tx["meta"]["preBalances"],
Expand Down Expand Up @@ -112,5 +117,47 @@ def solana_client_request(clusters, method, params:)
return { error: e.message }
end
end

# Extract account keys from message (handles both legacy and jsonParsed formats)
def extract_account_keys(message)
account_keys = message["accountKeys"]
if account_keys.first.is_a?(Hash)
account_keys.map { |key| key["pubkey"] }
else
account_keys
end
end

# Extract voted slot from vote instruction
# Vote instructions contain the slot(s) being voted on
# Supports both "vote" and "towersync" instruction types
def extract_voted_slot(message)
instructions = message["instructions"]
return nil unless instructions

instructions.each do |instruction|
if instruction.is_a?(Hash) && instruction["parsed"]
parsed = instruction["parsed"]

if parsed["type"] == "vote" && parsed["info"]
votes = parsed["info"]["votes"]
return votes.last["slot"] if votes&.any?
end

if parsed["type"] == "towersync" && parsed["info"]
tower_sync_data = parsed["info"]["towerSync"]
if tower_sync_data.is_a?(Hash) && tower_sync_data["lockouts"]
lockouts = tower_sync_data["lockouts"]
if lockouts.is_a?(Array) && lockouts.any?
voted_slot = lockouts.last["slot"]
return voted_slot
end
end
end
end
end

nil
end
end
end
23 changes: 7 additions & 16 deletions app/services/blockchain/set_vote_latency_score.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@
module Blockchain
class SetVoteLatencyScore

class NoBlocksError < StandardError; end

def initialize(network)
@network = network
@blocks_slot_numbers = {}
Expand All @@ -29,31 +27,24 @@ def fill_validators_latencies
@blocks_slot_numbers[block.blockhash] = block.slot_number
block.transactions.each do |transaction|
val = transaction.account_key_1
block_distance = get_block_distance(transaction.recent_blockhash, block.slot_number)

next if transaction.recent_blockhash.blank?

voted_slot = transaction.recent_blockhash.to_i
next if voted_slot == 0 && transaction.recent_blockhash != "0"

block_distance = block.slot_number - voted_slot

if @validators_latencies[val].present?
@validators_latencies[val].push(block_distance)
else
@validators_latencies[val] = [block_distance]
end
rescue NoBlocksError
next
end
block.update(processed: true)
end
end

def get_block_distance(blockhash, current_slot_number)
if @blocks_slot_numbers[blockhash].present?
current_slot_number - @blocks_slot_numbers[blockhash]
else
block = Blockchain::Block.network(@network).find_by(blockhash: blockhash)
raise NoBlocksError if block.blank?
@blocks_slot_numbers[block.blockhash] = block.slot_number
current_slot_number - block.slot_number
end
end

def set_average_latencies_for_validators
@validators_latencies.each do |validator, latencies|
avg_latency = (latencies.sum.to_f / latencies.size).round(2)
Expand Down
43 changes: 27 additions & 16 deletions test/services/blockchain/get_block_service_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -37,20 +37,30 @@ class GetBlockServiceTest < ActiveSupport::TestCase
"transaction"=>{
"message"=>{
"accountKeys"=>[
"CoreZU3NjoVQKcx7wTPty13BmQhrJUjhvS6Hdjq6nbYx",
"6DVFiYKvmPRvYjins4LtRSiEm81CswtP5yHTpgPdtrgK",
"Vote111111111111111111111111111111111111111"
{"pubkey" => "CoreZU3NjoVQKcx7wTPty13BmQhrJUjhvS6Hdjq6nbYx", "signer" => true, "writable" => true},
{"pubkey" => "6DVFiYKvmPRvYjins4LtRSiEm81CswtP5yHTpgPdtrgK", "signer" => false, "writable" => true},
{"pubkey" => "Vote111111111111111111111111111111111111111", "signer" => false, "writable" => false}
],
"header"=>{
"numReadonlySignedAccounts"=>0,
"numReadonlyUnsignedAccounts"=>1,
"numRequiredSignatures"=>1
},
"instructions"=>[{
"accounts"=>[1, 0],
"data"=>"Fk63Pwf7bG7e5VsmGZkX4EdTXaMZT4wt2EUK1ud5gddWehs5wk93y6ziWydGnqXJq2kdBiRDgk1g5G1ofZQMuBn4mQ8fCQYuuJzgt3BR76hRG2zLDZHJazv7gjhMNxoUY2vnxbzL1bTjFxgZ6JxMjSs9odrwts",
"programIdIndex"=>2,
"stackHeight"=>nil
"parsed" => {
"type" => "vote",
"info" => {
"voteAccount" => "6DVFiYKvmPRvYjins4LtRSiEm81CswtP5yHTpgPdtrgK",
"voteAuthority" => "CoreZU3NjoVQKcx7wTPty13BmQhrJUjhvS6Hdjq6nbYx",
"votes" => [
{"slot" => 12340, "confirmationCount" => 5},
{"slot" => 12341, "confirmationCount" => 4},
{"slot" => 12342, "confirmationCount" => 3}
]
}
},
"program" => "vote",
"programId" => "Vote111111111111111111111111111111111111111"
}],
"recentBlockhash"=>"4W2iKcjsuTfxEzovgt3LfALUPGNxscFrhtjN4mVurw6L"
},
Expand Down Expand Up @@ -108,18 +118,19 @@ class GetBlockServiceTest < ActiveSupport::TestCase
end

assert_equal 1, Blockchain::Transaction.count
assert_equal "CoreZU3NjoVQKcx7wTPty13BmQhrJUjhvS6Hdjq6nbYx", Blockchain::Transaction.network(@network).last.account_key_1
assert_equal "6DVFiYKvmPRvYjins4LtRSiEm81CswtP5yHTpgPdtrgK", Blockchain::Transaction.network(@network).last.account_key_2
assert_equal "Vote111111111111111111111111111111111111111", Blockchain::Transaction.network(@network).last.account_key_3
assert_equal 5000, Blockchain::Transaction.network(@network).last.fee
assert_equal [2254765266, 27074400, 1], Blockchain::Transaction.network(@network).last.pre_balances
assert_equal [2254760266, 27074400, 1], Blockchain::Transaction.network(@network).last.post_balances
assert_equal 5000, Blockchain::Transaction.network(@network).last.fee
assert_equal Blockchain::Block.network(@network).last.id, Blockchain::Transaction.network(@network).last.block_id
transaction = Blockchain::Transaction.network(@network).last
assert_equal "CoreZU3NjoVQKcx7wTPty13BmQhrJUjhvS6Hdjq6nbYx", transaction.account_key_1
assert_equal "6DVFiYKvmPRvYjins4LtRSiEm81CswtP5yHTpgPdtrgK", transaction.account_key_2
assert_equal "Vote111111111111111111111111111111111111111", transaction.account_key_3
assert_equal 5000, transaction.fee
assert_equal [2254765266, 27074400, 1], transaction.pre_balances
assert_equal [2254760266, 27074400, 1], transaction.post_balances
assert_equal "12342", transaction.recent_blockhash # voted_slot stored as string
assert_equal Blockchain::Block.network(@network).last.id, transaction.block_id
end

test "#process_transactions does not create new blockchain::transaction when no vote transactions" do
@block["transactions"][0]["transaction"]["message"]["accountKeys"][2] = "Vote111111111111111111111111111111111111112"
@block["transactions"][0]["transaction"]["message"]["accountKeys"][2]["pubkey"] = "Vote111111111111111111111111111111111111112"
block_service = Blockchain::GetBlockService.new(@network, @slot_number, @config_urls)
block_service.stub(:solana_client_request, @block) do
block_service.call
Expand Down
8 changes: 4 additions & 4 deletions test/services/blockchain/set_vote_latency_score_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ class SetVoteLatencyScoreTest < ActiveSupport::TestCase
end

test "#call sets vote latency score for validators" do
create(:mainnet_transaction, account_key_1: @account, recent_blockhash: "blockhash_0", block: Blockchain::MainnetBlock.last)
create(:mainnet_transaction, account_key_1: @account, recent_blockhash: "0", block: Blockchain::MainnetBlock.last)
Blockchain::SetVoteLatencyScore.new(@network).call

assert_equal 0, @validator.score.reload.vote_latency_score
Expand All @@ -25,10 +25,10 @@ class SetVoteLatencyScoreTest < ActiveSupport::TestCase

test "#call sets correct score while there are multiple transactions" do
5.times do
create(:mainnet_transaction, account_key_1: @account, recent_blockhash: "blockhash_2", block: Blockchain::MainnetBlock.last)
create(:mainnet_transaction, account_key_1: @account, recent_blockhash: "2", block: Blockchain::MainnetBlock.last)
end
5.times do
create(:mainnet_transaction, account_key_1: @account, recent_blockhash: "blockhash_3", block: Blockchain::MainnetBlock.last)
create(:mainnet_transaction, account_key_1: @account, recent_blockhash: "3", block: Blockchain::MainnetBlock.last)
end
Blockchain::SetVoteLatencyScore.new(@network).call

Expand All @@ -38,7 +38,7 @@ class SetVoteLatencyScoreTest < ActiveSupport::TestCase
end

test "#call pushes to vote_latency_history up to maximum" do
create(:mainnet_transaction, account_key_1: @account, recent_blockhash: "blockhash_0", block: Blockchain::MainnetBlock.last)
create(:mainnet_transaction, account_key_1: @account, recent_blockhash: "0", block: Blockchain::MainnetBlock.last)
@validator.score.update(vote_latency_history: (0..ValidatorScoreV1::MAX_HISTORY).to_a)
Blockchain::SetVoteLatencyScore.new(@network).call

Expand Down