class Lapidar::Chain

Attributes

block_stacks[R]

Public Class Methods

new() click to toggle source
# File lib/lapidar/chain.rb, line 5
def initialize
  @block_stacks = []
  @unconsolidated_blocks = []
end

Public Instance Methods

add(block) click to toggle source

TODO: Check for duplicates and dont add them to the chains

# File lib/lapidar/chain.rb, line 11
def add(block)
  if valid?(block)
    connect(block)
    consolidate_successors(block)
  else
    queue_unconsolidated(block)
  end
end
blocks() click to toggle source

For each position in the chain the candidate positioned first is considered the valid one

# File lib/lapidar/chain.rb, line 21
def blocks
  @block_stacks.map { |candidates| candidates&.first }
end
to_colorful_string(depth = 0) click to toggle source
# File lib/lapidar/chain.rb, line 25
def to_colorful_string(depth = 0)
  [*0..depth].map { |level|
    @block_stacks.map { |block_stack|
      if block_stack[level]
        number_display = block_stack[level].number.to_s
        if defined? Paint
          number_display = Paint[number_display, block_stack[level].hash[-6..-1]] # use last hash digits as color
          number_display = Paint[number_display, :bright, :underline] if level == 0 # emphasize preferred chain
          number_display
        end
        number_display
      else
        " " * block_stack[0].number.to_s.length # padding by digit count
      end
    }.join(" ")
  }.join("\n")
end

Private Instance Methods

connect(block) click to toggle source
# File lib/lapidar/chain.rb, line 45
def connect(block)
  @block_stacks[block.number] ||= []
  @block_stacks[block.number].push(block) unless @block_stacks[block.number].map(&:hash).include?(block.hash)

  # Rebalance if second to last block has more than one candidate
  rebalance if !contested?(block.number) && contested?(block.number - 1)
end
consolidate_successors(head_block) click to toggle source

Check if queued up unconsolidated blocks can be added to the chain after the currently added block

# File lib/lapidar/chain.rb, line 93
def consolidate_successors(head_block)
  @unconsolidated_blocks
    .select { |unconsolidated_block| unconsolidated_block.number > head_block.number }
    .each do |possible_successor|
      if valid?(possible_successor)
        connect(possible_successor)
        @unconsolidated_blocks.delete(possible_successor)
      end
    end
end
contested?(block_number) click to toggle source

Contested evaluates to true if there blocks are competing for the same position in the blockchain

# File lib/lapidar/chain.rb, line 82
def contested?(block_number)
  @block_stacks[block_number].count > 1
end
queue_unconsolidated(block) click to toggle source
# File lib/lapidar/chain.rb, line 86
def queue_unconsolidated(block)
  return if @unconsolidated_blocks.include?(block)
  @unconsolidated_blocks.push(block)
  @unconsolidated_blocks.sort_by!(&:number)
end
rebalance() click to toggle source

If a new last block comes in, we realign the first blocks to build the longest chain

# File lib/lapidar/chain.rb, line 66
def rebalance
  winning_block = @block_stacks.last.first
  parent_position = @block_stacks.count - 2

  while contested?(parent_position)
    # TODO: Is there's a smarter way to persistently select a winner than sorting the competition
    @block_stacks[parent_position].sort_by! do |previous_block|
      Assessment.valid_link?(previous_block, winning_block) ? 0 : 1
    end

    winning_block = @block_stacks[parent_position].first
    parent_position -= 1
  end
end
valid?(block) click to toggle source
# File lib/lapidar/chain.rb, line 53
def valid?(block)
  return true if Assessment.genesis?(block) # early valid if genesis
  return false if Assessment.first?(block) # early invalid if fake genesis
  return false unless Assessment.meets_difficulty?(block) # early invalid if difficulty not met
  return false if block.number > @block_stacks.count # early invalid if future block

  # Check if there's an existing parent
  @block_stacks[block.number - 1].any? do |previous_block|
    Assessment.valid_link?(previous_block, block)
  end
end