-
-
Save aunyks/47d157f8bc7d1829a729c2a6a919c173 to your computer and use it in GitHub Desktop.
from flask import Flask | |
from flask import request | |
import json | |
import requests | |
import hashlib as hasher | |
import datetime as date | |
node = Flask(__name__) | |
# Define what a Snakecoin block is | |
class Block: | |
def __init__(self, index, timestamp, data, previous_hash): | |
self.index = index | |
self.timestamp = timestamp | |
self.data = data | |
self.previous_hash = previous_hash | |
self.hash = self.hash_block() | |
def hash_block(self): | |
sha = hasher.sha256() | |
sha.update(str(self.index) + str(self.timestamp) + str(self.data) + str(self.previous_hash)) | |
return sha.hexdigest() | |
# Generate genesis block | |
def create_genesis_block(): | |
# Manually construct a block with | |
# index zero and arbitrary previous hash | |
return Block(0, date.datetime.now(), { | |
"proof-of-work": 9, | |
"transactions": None | |
}, "0") | |
# A completely random address of the owner of this node | |
miner_address = "q3nf394hjg-random-miner-address-34nf3i4nflkn3oi" | |
# This node's blockchain copy | |
blockchain = [] | |
blockchain.append(create_genesis_block()) | |
# Store the transactions that | |
# this node has in a list | |
this_nodes_transactions = [] | |
# Store the url data of every | |
# other node in the network | |
# so that we can communicate | |
# with them | |
peer_nodes = [] | |
# A variable to deciding if we're mining or not | |
mining = True | |
@node.route('/txion', methods=['POST']) | |
def transaction(): | |
# On each new POST request, | |
# we extract the transaction data | |
new_txion = request.get_json() | |
# Then we add the transaction to our list | |
this_nodes_transactions.append(new_txion) | |
# Because the transaction was successfully | |
# submitted, we log it to our console | |
print "New transaction" | |
print "FROM: {}".format(new_txion['from'].encode('ascii','replace')) | |
print "TO: {}".format(new_txion['to'].encode('ascii','replace')) | |
print "AMOUNT: {}\n".format(new_txion['amount']) | |
# Then we let the client know it worked out | |
return "Transaction submission successful\n" | |
@node.route('/blocks', methods=['GET']) | |
def get_blocks(): | |
chain_to_send = blockchain | |
# Convert our blocks into dictionaries | |
# so we can send them as json objects later | |
for i in range(len(chain_to_send)): | |
block = chain_to_send[i] | |
block_index = str(block.index) | |
block_timestamp = str(block.timestamp) | |
block_data = str(block.data) | |
block_hash = block.hash | |
chain_to_send[i] = { | |
"index": block_index, | |
"timestamp": block_timestamp, | |
"data": block_data, | |
"hash": block_hash | |
} | |
chain_to_send = json.dumps(chain_to_send) | |
return chain_to_send | |
def find_new_chains(): | |
# Get the blockchains of every | |
# other node | |
other_chains = [] | |
for node_url in peer_nodes: | |
# Get their chains using a GET request | |
block = requests.get(node_url + "/blocks").content | |
# Convert the JSON object to a Python dictionary | |
block = json.loads(block) | |
# Add it to our list | |
other_chains.append(block) | |
return other_chains | |
def consensus(): | |
# Get the blocks from other nodes | |
other_chains = find_new_chains() | |
# If our chain isn't longest, | |
# then we store the longest chain | |
longest_chain = blockchain | |
for chain in other_chains: | |
if len(longest_chain) < len(chain): | |
longest_chain = chain | |
# If the longest chain isn't ours, | |
# then we stop mining and set | |
# our chain to the longest one | |
blockchain = longest_chain | |
def proof_of_work(last_proof): | |
# Create a variable that we will use to find | |
# our next proof of work | |
incrementor = last_proof + 1 | |
# Keep incrementing the incrementor until | |
# it's equal to a number divisible by 9 | |
# and the proof of work of the previous | |
# block in the chain | |
while not (incrementor % 9 == 0 and incrementor % last_proof == 0): | |
incrementor += 1 | |
# Once that number is found, | |
# we can return it as a proof | |
# of our work | |
return incrementor | |
@node.route('/mine', methods = ['GET']) | |
def mine(): | |
# Get the last proof of work | |
last_block = blockchain[len(blockchain) - 1] | |
last_proof = last_block.data['proof-of-work'] | |
# Find the proof of work for | |
# the current block being mined | |
# Note: The program will hang here until a new | |
# proof of work is found | |
proof = proof_of_work(last_proof) | |
# Once we find a valid proof of work, | |
# we know we can mine a block so | |
# we reward the miner by adding a transaction | |
this_nodes_transactions.append( | |
{ "from": "network", "to": miner_address, "amount": 1 } | |
) | |
# Now we can gather the data needed | |
# to create the new block | |
new_block_data = { | |
"proof-of-work": proof, | |
"transactions": list(this_nodes_transactions) | |
} | |
new_block_index = last_block.index + 1 | |
new_block_timestamp = this_timestamp = date.datetime.now() | |
last_block_hash = last_block.hash | |
# Empty transaction list | |
this_nodes_transactions[:] = [] | |
# Now create the | |
# new block! | |
mined_block = Block( | |
new_block_index, | |
new_block_timestamp, | |
new_block_data, | |
last_block_hash | |
) | |
blockchain.append(mined_block) | |
# Let the client know we mined a block | |
return json.dumps({ | |
"index": new_block_index, | |
"timestamp": str(new_block_timestamp), | |
"data": new_block_data, | |
"hash": last_block_hash | |
}) + "\n" | |
node.run() | |
i'm working on a reimplementation of aunyks's great work. feel free to check out my repo simple_blockchain
. it's far from perfect right now... would love some criticism or help!
i plan to implement some of the features core to the original blockchain system, outlined in the bitcoin whitepaper.
so far, i've included the nonce calculation and hashing, with a variable for difficulty. i still need to work on transaction broadcasting to other nodes, transaction verification, etc.
In consensus()
, it seems to me that it should verify the other chains' proof of work before accepting them. This seems to work as a verifier:
def verify_proof_of_work(proof, last_proof):
return (proof > last_proof
and proof % 9 == 0
and proof % last_proof == 0)
However, it seems you can avoid doing the work by just multiplying the last proof by 9.
Edit: I see @drewrice2 uses a different proof function, based on leading 0's in a sha256 hash value, as mentioned in the whitepaper: https://github.com/drewrice2/simple_blockchain/blob/master/code/node.py
Using my Windows machine, it took me a bit to figure out the curl vs. Invoke-Web stuff. If you're trying to run the program and send a transaction but it's giving you grief about content types, this might help:
Invoke-RestMethod "http://localh ost:5000/txion" -ContentType 'application/json' -Method Post -Body '{"from": "asd fa", "to":"asdfas", "amount": 3}'
so how is the peer_nodes populated and how do we make use of the consensus function?
Is there like a central authority that takes care of peer_nodes?
@iandesj:
here's some information on how bitcoin bootstraps to find peers:
https://bitcoin.stackexchange.com/questions/2027/how-does-the-bitcoin-client-make-the-initial-connection-to-the-bitcoin-network
that seems like overkill for a small POC like this.
I am currently work on a port of this to Groovy using the Spark web server. Most of the code is ported and should be fully functional shortly.
Groovy Version:
https://github.com/benrhine/GroovySnakeCoinExpanded
Hey @aunyks don't leave use wondering how this fantastic tutorial continues and how wallets work. Thanks again and keep it up!
The consensus function synchronizes the blockchain of the current node to the longest blockchain available across the network of peers. It should be called in '/blocks' before populating the list of blocks to be returned, thus the node makes sure its blockchain is the same as all the other peers.
About how to populate the list of peers, it would be possible to define a new REST command '/add_peer' that adds a peer address running in a host:port. It would be necessary as well to be able to pass a port number when running the server, so two or more instances in the same host don't collide. I implemented those changes in a new version of the script. Check it out here: https://gist.github.com/dpino/07a0e90d559a959eb73fecf58a3fea92
Hi everyone,
Few basic questions.
If I host the above source on my machine and you host it on your machine, will this still be like we are using the same blockchain to mine SnakeCoin?
Or is there a way that nodes have to be connected before you start mining? How do we ensure that there is a limit on number of coins that can be mined? How can we increase the level of difficulty of mining as the number of coins mined gets increased?
@saint1729 this implementation doesn't cover transaction broadcasting, which would allow other nodes to mine the same transactions.
additionally, a coin issuance hard cap isn't covered here.
increasing the difficulty of the mining just means increasing the difficulty of the proof of work algorithm. this implementation doesn't include a viable proof of work algo.
@drewrice2 I see. I found a good visual explanation here below.
https://github.com/anders94/blockchain-demo
Are there any similar full fledged basic .java or .py implementations?
I can't fix this error in:
line 20, in hash_block
sha.update(str(self.index) + str(self.timestamp) + str(self.data) + str(self.previous_hash))
TypeError: Unicode-objects must be encoded before hashing. ?
@drewrice2 is there a possible solution to perform transactions between two computers with the above code?
If not how can we make transactions over a network? Please let me know. I am able to run the localhost:5000/blocks and locakhost:5000/mine but the curl command doesn't work - I have everything set up on a windows machine.
C:\curl "localhost:5000/txion" -H "Contect-Type: application/json" -d '{"from": "akjflw", "to":"fjlakdj", "amount": 3}'
<title>404 Not Found</title>Not Found
The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.
curl: (6) Could not resolve host: akjflw, curl: (3) Port number ended with 'f' curl: (6) Could not resolve host: amount curl: (3) [globbing] unmatched close brace/bracket in column 2@djalmabright try sha.update(( str(self.index) + str(self.timestamp) + str(self.data) + str(self.previous_hash)).encode('utf-8') )
def hash_block(self):
"""
:return:
"""
sha = hasher.sha256()
sha.update(self.attributes())
return sha.hexdigest()
def attributes(self):
return '%s%s%s%s' % (self.index, self.timestamp, self.data, self.previous_hash)
Any thoughts on this error?
C:\Users\HI>curl "localhost:5000/txion" \
<title>405 Method Not Allowed</title>Method Not Allowed
The method is not allowed for the requested URL.
curl: (6) Could not resolve host: \@paulvijaykumarm, @hamidabuisa
Hi,
When I used the command : curl "localhost:5000/txion" -H "Contect-Type: application/json" -d '{"from": "akjflw", "to":"fjlakdj", "amount": 3}'
The error appeared.
Where is the next_block function mentioned in part 1? is it replaced with mining?
When you run the command "curl localhost:5000/mine", the new block will be added. In this code, the function "mine" in charge the adding new block.
@DarkNTNT84 thanks, I will actually not have to implement mining, which is why I was looking for the next block function. Thank you!
This code doesn't broadcast transactions to all nodes as mentioned in the bitcoin whitepaper, so each block would only contain the transactions for the user and miner of that node, instead of combining transactions from multiple nodes. /transaction should have a GET method which would supply other nodes will all the transactions this node has, which would then be added to this_node_transactions.
To fix Internal Server Error aka "dict" object has no attribute "index", simply change chain_to_send = blockchain
to chain_to_send = blockchain[:]
. This will copy the blockchain array. So you wouldn't change the actual blockchain to JSON.
I keep getting this error when I'm using /txion
Ms-MacBook-Pro:mvb m$ curl localhost:5000/transaction
<title>405 Method Not Allowed</title>Method Not Allowed
The method is not allowed for the requested URL.
What am I doing wrong ?
I have tried doing what DarkNTNT84 mentions, but it still won't work
"your consensus function was not used" + 1