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
37 changes: 35 additions & 2 deletions main.rb
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,15 @@
block_data = parse_json_body
chain_id = params[:id]
blockchain = find_block_chain(chain_id)
block = blockchain.add_block(block_data['data'])
difficulty = validate_difficulty(block_data['difficulty'])
block = blockchain.add_block(block_data['data'], difficulty: difficulty)

{
chain_id: chain_id,
block_id: block.id.to_s,
block_hash: block._hash
block_hash: block._hash,
nonce: block.nonce,
difficulty: block.difficulty
}.to_json
end

Expand All @@ -54,6 +57,29 @@
}.to_json
end

get '/chain/:id/block/:block_id' do
chain_id = params[:id]
block_id = params[:block_id]
blockchain = find_block_chain(chain_id)
block = blockchain.blocks.find(block_id)
raise 'Block not found' unless block

{
chain_id: chain_id,
block: {
id: block.id.to_s,
index: block.index,
data: block.data,
hash: block._hash,
previous_hash: block.previous_hash,
nonce: block.nonce,
difficulty: block.difficulty,
timestamp: block.created_at.to_i,
valid_hash: block.valid_hash?
}
}.to_json
end

helpers do
def parse_json_body
JSON.parse(request.body.read)
Expand All @@ -65,4 +91,11 @@ def find_block_chain(chain_id)

blockchain
end

def validate_difficulty(difficulty)
difficulty = difficulty.nil? ? 2 : difficulty.to_i
halt 422, { error: 'Difficulty must be a positive integer' }.to_json if difficulty <= 0
halt 422, { error: 'Difficulty must be between 1 and 10' }.to_json if difficulty > 10
difficulty
end
end
39 changes: 35 additions & 4 deletions src/block.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,16 @@ class Block
# @return [Object] The information that is stored inside the `Block`.
# @!attribute [r] previous_hash
# @return [String] The `hash` of the previous `Block` in the `Blockchain`.
# @!attribute [r] nonce
# @return [Integer] The number used once for Proof of Work mining.
# @!attribute [r] difficulty
# @return [Integer] The number of leading zeros required in the hash.
field :index, type: Integer
field :data, type: String
field :previous_hash, type: String
field :_hash, type: String, as: :hash
field :nonce, type: Integer, default: 0
field :difficulty, type: Integer, default: 2

belongs_to :blockchain

Expand All @@ -30,18 +36,43 @@ class Block

# Calculates the SHA256 hash of the block.
#
# The `hash` is generated from the `Block`'s `index`, `timestamp`, transaction `data`
# and the `hash` of the previous block.
# The `hash` is generated from the `Block`'s `index`, `timestamp`, transaction `data`,
# the `hash` of the previous block, and the `nonce`.
#
# @return [String] The `hash` of the block
def calculate_hash
set_created_at
self._hash = Digest::SHA256.hexdigest("#{index}#{created_at.to_i}#{data}#{previous_hash}")
self._hash = Digest::SHA256.hexdigest("#{index}#{created_at.to_i}#{data}#{previous_hash}#{nonce}")
end

# Validates the integrity of the `Block`'s data.
# @return [Boolean] `true` if the `Block`'s data is valid, `false` otherwise.
def valid_data?(data)
Digest::SHA256.hexdigest("#{index}#{created_at.to_i}#{data}#{previous_hash}") == _hash
Digest::SHA256.hexdigest("#{index}#{created_at.to_i}#{data}#{previous_hash}#{nonce}") == _hash
end

# Mines the block using Proof of Work algorithm.
#
# Increments the nonce until a hash is found that starts with the required number
# of leading zeros specified by the difficulty level.
#
# @return [String] The mined hash with required difficulty
def mine_block
target = '0' * difficulty
loop do
calculate_hash
break if _hash.start_with?(target)

self.nonce += 1
end
_hash
end

# Validates that the block's hash meets the difficulty requirement.
#
# @return [Boolean] `true` if the hash has the required leading zeros, `false` otherwise.
def valid_hash?
target = '0' * difficulty
_hash.start_with?(target)
end
end
17 changes: 14 additions & 3 deletions src/blockchain.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,20 @@ class Blockchain
# Add a new Block to this Blockchain
#
# @param data [Object] the data that needs to be added to the new Block
def add_block(data)
# @param difficulty [Integer] the mining difficulty (number of leading zeros)
# @return [Block] the newly created and mined Block
def add_block(data, difficulty: 2)
integrity_valid? or raise 'Blockchain is not valid'
last_block = blocks.last
blocks.create(index: last_block.index + 1, data:, previous_hash: last_block._hash)
block = blocks.build(
index: last_block.index + 1,
data:,
previous_hash: last_block._hash,
difficulty:
)
block.mine_block
block.save!
block
end

# Get the last block of this Blockchain
Expand All @@ -37,7 +47,8 @@ def last_block
def integrity_valid?
blocks.each_cons(2).all? do |previous_block, current_block|
previous_block._hash == current_block.previous_hash &&
current_block._hash == current_block.calculate_hash
current_block._hash == current_block.calculate_hash &&
current_block.valid_hash?
end
end

Expand Down