Skip to content

Instantly share code, notes, and snippets.

@dainmiller
Created April 14, 2020 08:59
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dainmiller/3deb66d474e2f6560b0e2772f06765d5 to your computer and use it in GitHub Desktop.
Save dainmiller/3deb66d474e2f6560b0e2772f06765d5 to your computer and use it in GitHub Desktop.
Simplest possible Blockchain implementation in ruby
require 'minitest/autorun'
require 'digest'
require 'pki'
class Block
NUM_ZEROES = 4
attr_reader :own_hash, :prev, :transaction
def self.create_genesis_block(pub, priv)
transaction = Transaction.new(nil, pub, 500_000, priv)
new(nil, genesis)
end
def initialize(prev, transaction)
raise TypeError unless transaction.is_a? Transaction
@prev = prev.own_hash if prev
@transaction = transaction
mine!
end
def mine!
@nonce = calc_nonce
@own_hash = hash full_block @nonce
end
def valid?
is_valid_nonce? @nonce and @transaction.is_valid_signature?
end
private
def calc_nonce
nonce = ''
count = 0
until is_valid_nonce? nonce
nonce = nonce.next
count += 1
end
nonce
end
def is_valid_nonce? nonce
hash(full_block(nonce)).start_with? '0' * NUM_ZEROES
end
def full_block nonce
[@prev, @transaction, nonce].compact.join
end
def hash contents
Digest::SHA256.hexdigest contents
end
end
class Transaction
attr_reader :from, :to, :amt
def initialize from, to, amount, priv
@from = from
@to = to
@amt = amount
@sig = PKI.sign message, priv
end
def is_valid_signature?
return true if genesis?
PKI.valid_sig? message, @sig, from
end
def genesis?
from.nil?
end
def message
Digest::SHA256.hexdigest [@from, @to, @amt].join
end
def to_s
message
end
end
class BlockChain
attr_reader :blocks
def initialize pub, priv
@blocks = []
@blocks << Block.create_genesis_block pub, priv
end
def length
@blocks.length
end
def add transaction
@blocks << Block.new(@blocks.last, transaction)
end
def valid?
@blocks.all? do |block|
block.is_a?(Block) and
@blocks.map?(&:valid?) and
all_balances_valid?
end
end
def all_balances_valid?
compute_balances do |balances, from, to|
false if balances.values_at(from, to).any? { |bal| bal < 0 }
end
end
def compute_balances
genesis = @blocks.first.transaction
balances = {}
balances[genesis.to] = genesis.amount
@blocks.drop(1).each do |block|
balances[from] -= block.transaction.amount
balances[to] += block.transaction.amount
yield balances, block.transaction.from, block.transaction.to if block_given?
end
end
def to_s
@blocks.map(&:to_s).join('\n')
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment