Class: Blockchain

Inherits:
Object
  • Object
show all
Defined in:
lib/rubocoin/blockchain/blockchain.rb

Overview

This class defines what our blockchain will look like as well as contain functoins for interacting with the chain.

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeBlockchain

Initialize the chain and other variables as well as create the ‘Genesis’ block



26
27
28
29
30
31
32
33
34
35
# File 'lib/rubocoin/blockchain/blockchain.rb', line 26

def initialize
  @current_transactions = []
  @chain = []
  @nodes = Set.new
  @difficulty = 4
  @difficulty_increment_counter = 0  # When this reaches 10000, increment difficulty by 1

  # Generate the genesis block
  new_block(100, 1)
end

Instance Attribute Details

#chainArray

The current chain being used

Returns:

  • (Array)

    A list of blocks



13
14
15
# File 'lib/rubocoin/blockchain/blockchain.rb', line 13

def chain
  @chain
end

#difficultyObject (readonly)

The difficulty of this node. It’s base value is 4 (meaning ‘0’*4) Each 10,000 blocks mined, the difficulty will increment by 1 (meaning ‘0’*5)



22
23
24
# File 'lib/rubocoin/blockchain/blockchain.rb', line 22

def difficulty
  @difficulty
end

#nodesSet

We use a Chain instead of an Array here because we don’t want any duplicate blocks in our chain. A Set takes care of this for us

Returns:

  • (Set)

    A duplicate-free list of other nodes currently registered with this node



18
19
20
# File 'lib/rubocoin/blockchain/blockchain.rb', line 18

def nodes
  @nodes
end

Instance Method Details

#calculate_reward_amountObject

The reward halfs each time the difficulty increases



38
39
40
41
42
43
44
# File 'lib/rubocoin/blockchain/blockchain.rb', line 38

def calculate_reward_amount
  initial_reward = 100
  (@difficulty - 3).times do
    initial_reward /= 2
  end
  return initial_reward
end

#hash(last_block) ⇒ Object



177
178
179
180
# File 'lib/rubocoin/blockchain/blockchain.rb', line 177

def hash(last_block)
  block_string = last_block.sort.to_h.to_json
  return Digest::SHA256.hexdigest(block_string)
end

#last_blockObject

Return the most recent block in our chain



55
56
57
# File 'lib/rubocoin/blockchain/blockchain.rb', line 55

def last_block
  @chain[-1]
end

#new_block(proof, previous_hash = nil) ⇒ Object



116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
# File 'lib/rubocoin/blockchain/blockchain.rb', line 116

def new_block(proof, previous_hash=nil)
  # Define the block structure
  block =
    {
      :index => @chain.size + 1,
      :timestamp => Time.now,
      :transactions => @current_transactions,
      :proof => proof,
      :previous_hash => previous_hash || hash(last_block)
    }

    # Clear transactions
    @current_transactions = []
    info_with_timestamp('Transaction list cleared due to new block formation')

    # Append the block to the chain
    @chain << block

    print_new("New block added:")
    puts '{'
    puts "\t index: #{block[:index]}"
    puts "\t timestamp: #{block[:timestamp]}"
    puts "\t transactions: #{block[:transactions]}"
    puts "\t proof: #{block[:proof]}"
    puts "\t previous_hash: #{block[:previous_hash]}"
    puts '}'

    @difficulty_increment_counter += 1

    info("Difficulty will increase after #{1000-@difficulty_increment_counter} more blocks")

    if @difficulty_increment_counter >= 0x2710
      debug_with_timestamp("Difficulty increased to #{@difficulty + 1}!")
      @difficulty += 1
      @difficulty_increment_counter = 0 # Reset the counter
    end

    block
end

#new_transaction(sender, recipient, amount, others = nil) ⇒ Object



156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
# File 'lib/rubocoin/blockchain/blockchain.rb', line 156

def new_transaction(sender, recipient, amount, others=nil)
  unless others.nil?
    @current_transactions <<
      {
        :sender => sender,
        :recipient => recipient,
        :amount => amount,
        :others => others
      }
  else
    @current_transactions <<
      {
        :sender => sender,
        :recipient => recipient,
        :amount => amount
      }
  end

  last_block[:index] + 1
end

#proof_of_work(last_proof) ⇒ Object



182
183
184
185
186
187
# File 'lib/rubocoin/blockchain/blockchain.rb', line 182

def proof_of_work(last_proof)
  proof = -1
  proof += 1 until valid_proof(last_proof, proof)
  info("Valid proof found! #{proof}")
  return proof
end

#register_node(addr) ⇒ Object

Register a new node



49
50
51
52
# File 'lib/rubocoin/blockchain/blockchain.rb', line 49

def register_node(addr)
  url = URI.parse(addr)
  @nodes.add("#{url.host}:#{url.port}")
end

#resolve_conflictsObject



83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
# File 'lib/rubocoin/blockchain/blockchain.rb', line 83

def resolve_conflicts
  neighbour_nodes = @nodes
  new_chain = nil

  # Only chains longer than ours
  max_len = @chain.size

  # Fetch and confirm each chain from the network
  neighbour_nodes.each do |node|
    response = JSON.parse(open("http://#{node}/chain").read)
    response.deep_symbolize_keys!

    if response[:chain]
      length = response[:length]
      chain = response[:chain]

      # Check if the length is longer and the chain is valid
      if length > max_len || valid_chain(chain)
        max_len = length
        new_chain = chain
      end
    end
  end

  # Replace our chain if we discovered a new, valid chain longer than ours
  if new_chain
    @chain = new_chain
    return true
  end

  return false
end

#valid_chain(chain) ⇒ Object

Check whether a chain is valid



60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
# File 'lib/rubocoin/blockchain/blockchain.rb', line 60

def valid_chain(chain)
  last_block = chain[0]
  index = 1

  while index < chain.size do
    block = chain[index]

    # Check previous block hash is correct
    if block[:previous_hash] != hash(last_block)
      false
    end

    # Check Proof of Work
    unless valid_proof(last_block[:proof], block[:proof])
      false
    end

    last_block = block
    index += 1
  end
  true  # If we got here, all blocks are valid; therefore the chain is valid
end

#valid_proof(last_proof, proof) ⇒ Object



189
190
191
192
193
# File 'lib/rubocoin/blockchain/blockchain.rb', line 189

def valid_proof(last_proof, proof)
  guess = "#{last_proof}#{proof}"
  guess_hash = Digest::SHA256.hexdigest(guess)
  return guess_hash[0..(@difficulty - 1)] == ('0' * @difficulty)
end