Welcome to Part 2 of the Jackblockchain, where I write some code to introduce the ability for different to nodes Te.
Initially my goal is to write about nodes syncing up and talking with each other, along with mining and broadcasting thei R winning blocks to the other nodes. In the "End", I realized that the amount of code and explanation to accomplish all of this is way too big for one post. Because of this, I decided to make part 2 is about nodes beginning the process of talking for the future.
By reading this you'll get a sense of what I did and I did it. But you won ' t is seeing all the code. There ' So very much code involved which if you ' re looking for me total implementation your should look at the entire code on the Part-2 branch on Github.
Like all programming, I didn ' t write the following code in order. I had different ideas, tried different tactics, deleted some code, wrote some more, deleted "Code", and then ended up W ith the following.
This is totally fine! I wanted to mention me process so people reading this don ' t always do you someone who writes about programming it In the sequence they write about it. If it were easy todo, I ' d really like to write about different things I tried, bugs I had that weren ' t simple to fix, par TS where I was stuck and didn ' t easily know the how to move forward.
It's difficult to explain "full process" and I assume most people reading this aren ' t looking to know how people , they want to the Code and implementation. Just Keep in mind this programming is very rarely in a sequence.
Twitter, contacts, and feel free to use comments below to yell at me, tell me what I did wrong, or tell me how helpful this Was. Big fan of feedback. Other Posts in- Series part 1-creating, storing, Syncing, displaying, Mining, and proving Work Part 3-nodes This Mine part 4.1-bitcoin Proof to Work difficulty explained part 4.2-ethereum Proof of Work Y explained Tl;dr
If you ' re looking to learn about how blockchain mining works, you ' re not going to learn it. For now, the read Part 1 where I talk about it initially, and the wait for the more parts of this project where I go into the more advance D Mining.
At the end, I'll show the way to create nodes which, when running, would ask other nodes what the blockchain ' re using, and Be able to store it locally to is used when they start mining. That ' s it. Why is this post so long? Because there are so very involved in building up the "code to" make it easier to work the Future.
That's being said, the syncing here isn ' t super advanced. I go over improving the classes involved the chain, testing the new features, creating other nodes simply, and finally A way for nodes to sync when they start running.
For this sections, I talk about the code and then paste the code, so get ready. expanding block and adding Chain class
This project is a great example of the benefits of Object oriented programming. In here, I ' m going to start talking about the changes to the "block class," and then go on to the creation of the Ch Ain class.
The big keys for Blocks are:
Throwing in a way to convert the keys into the block's header from input values to the types we ' re looking for. This is makes it easy to dump in a JSON dict from a file and convert the string value of the index, Nonce, and second Timesta MP into INTs. This could to the future for new variables. For example if I want to convert the datetime into a actual DateTime object, this method is good to have. Validating the checking whether the hash begins with the required number of zeros. The ability for the blocks to save itself in the Chaindata folder. Allowing this rather than requiring a utility function to take care of. overriding some of the operator functions. __repr__ for making it easier to get quick information about the "block" when debugging. It overrides __str__ as if you ' re printing. __eq__ that allows the compare two blocks to the if they ' re equal with ==. Otherwise Python would compare with the "address" block has in memory. We ' re looking foR data Comparison __ne__, opposite of __eq__ of course in the future, we'll probably want to have some greater than (__gt_ _) operator so we are able to simply use > to, which chain is better. But right now, we don ' t have a good way.
########### #config. py chaindata_dir = ' chaindata/' broadcasted_block_dir = chaindata_dir + ' bblocs/' NUM_ZEROS = 5 #diff
Iculty, currently.
Block_var_conversions = {' index ': int, ' nonce ': int, ' hash ': str, ' Prev_hash ': str, ' timestamp ': int} ########## #block. py From config Import * class block (object): Def __init__ (self, *args, **kwargs): "" We re looking for index, Timestamp, data, Prev_hash, nonce ' for key, value in Dictionary.items (): If key in Block_var_conversions : SetAttr (self, Key, Block_var_conversions[key] (value) else:setattr (self, key, value) if not Hasattr (self, ' hash '): #in creating the "a", needs to is removed in future Self.hash = Self.create_self_hash () if not hasattr (self, ' nonce '): #we ' re throwin the ' for generation self.nonce = ' None ' ... def self _save (self): ' Want the ability to easily save ' index_string = str (self.index). Zfill (6) #front of Z Eros So they stay into numerical order filename = '%s%s.json '% (Chaindata_dir, index_string) with open (filename, ' W ') a S Block_file:json.dump (Self.to_dict (), block_file) def is_valid (self): ' "' current validity are only th At the hash begins with at least Num_zeros ' self.update_self_hash () if STR (self.hash[0:num_zeros)) = = ' 0 ' * Num_zeros:return True Else:return False def __repr__ (self): #used for debugging without print, __rep r__ > __str__ return "Block<index:%s>,
Again, these operations can easily change in the future.
Chain time. Initialize a Chain by passing in a list of blocks. Chain ([Block_zero, Block_one]) or Chain ([]) If you are creating an empty Chain. Validity is determined from index incrementing by one, Prev_hash are actually the hash of the previous block, and hashes have Valid zeros. The ability to save the blocks to chaindata the ability to find a specific blocks in the chain by index or by hash the Lengt H of the chain, which can be called by Len (chain_obj) is the length of self.blocks equality of chains requires of Blo Cks of equal lengths that are all equal. Greater than, less than are determined only by the length of the chain ability to add a blocks to the chain, currently by T Hrowing it on the "end of" variable, not checking validitiy returning a list of dictionary to all objects Cks in the chain.
From blocks import Block class Chain (object): Def __init__ (self, blocks): Self.blocks = Blocks def is_valid (self ): ' is a valid blockchain if 1 ', each blocks is indexed one following the other 2-each blocks ' s prev Hash is th e Hash of the Prev Block 3) The block's hash is valid for the number of zeros ' for index, cur_block in Enumer Ate (self.blocks[1:]): Prev_block = Self.blocks[index] if prev_block.index+1!= Cur_block.index:return False if not cur_block.is_valid (): #checks the hash return False if Prev_block.hash!= Cur_block.prev _hash:return False return True def self_save (self): "" We want to save this in the file system
As we do. ' For B in Self.blocks:b.self_save () return True def find_block_by_index (self, index): If Len (self <= Index:return Self.blocks[index] Else:return False def find_block_by_hash (self, hash): for B in self. blocks:if B.hash = = Hash:return B return False def __len__ (self): return len (self.blocks) d EF __eq__ (Self, Other): If Len (self)!= len ("other"): Return False to Self_block, other_block in Zip (self.blo
CKS, Other.blocks): If Self_block!= other_block:return False return True def __gt__ (self, Other): Return len (self.blocks) > Len (other.blocks) ... def __ge__ (self, Other): Return self.__eq__ (other) or self . __gt__ (Other) def max_index (self): ' We ' re assuming a valid chain. Might change later "return Self.blocks[-1].index def add_block (self, New_block):" Put the new B
Lock into the index which is asking. The "is", if the index is by one that currently exists, the new block would take it's place.
Then we want to the if is valid.
If it isn ' t, then we ditch the new block and return False. "Self.blocks.append" (New_block) reTurn True def block_list_dict (self): return [B.to_dict () to B in Self.blocks]
Woof. Ok Fine, I listed a lot of the functions of the Chain class here. In the future, this functions are probably going to change, most notably the __gt__ and __lt__ comparisons. Right now, this is handles a bunch of abilities we ' re looking for. Testing
I ' m going to throw me note about testing the classes here. I have the ability to mine new blocks which use the classes. One way to test was to change the classes, run the mining, observe the errors, try to fix, and then run the mining. That ' s quite a waste of the the Code would take forever, especially when.
There are a bunch of testing libraries out There, but they do involve a bunch of formatting. I don ' t want to take, and right now, so has a test.py definitely works.
In order to get the blocks dicts listed at the start, I simply ran the mining algorithm a few times to get a valid chai N of the Block dicts. From there, I write sections that test the different parts of the classes. If I Change or add something to the classes, it barely takes no time to write the test for it. When I Run python test.py, the file runs incredibly quickly and tells me exactly-line the error is from.
From block import blocks from chain import chain Block_zero_dir = {"Nonce": "631412", "index": "0", "hash": "000002f9c703d c80340c08462a0d6acdac9d0e10eb4190f6e57af6bb0850d03c "," timestamp ":" 1508895381 "," Prev_hash ":" "," Data ":" The Block Data "} Block_one_dir = {" Nonce ":" 1225518 "," Index ":" 1 "," Hash ":" 00000c575050241e0a4df1acd7e6fb90cc1f599e2cc2908ec8225e10915006cc "," timestamp ":" 1508895386 "," Prev_hash ":" 000002f9c703dc80340c08462a0d6acdac9d0e10eb4190f6e57af6bb0850d03c "," Data ":" I block #1 "} Block_two_dir = {" Nonce ":" 1315081 "," Index ":" 2 "," Hash ":" 000003cf81f6b17e60ef1e3d8d24793450aecaf65cbe95086a29c1e48a5043b1 "," timestamp ":"
1508895393 "," Prev_hash ":" 00000c575050241e0a4df1acd7e6fb90cc1f599e2cc2908ec8225e10915006cc "," Data ":" I block #2 "} Block_three_dir = {"Nonce": "24959", "Index": "3", "Hash": " 00000221653e89d7b04704d4690abcf83fdb144106bb0453683c8183253fabad "," timestamp ":" 1508895777 "," Prev_hash ":" 000003cf81f6b17e60ef1e3d8d24793450aecaf65cbe95086a29c1e48a5043b1 "," DaTa ":" I block #3} block_three_later_in_time_dir = {"Nonce": "46053", "Index": "3", "Hash": "000000257df186344486c2c3c1eb AA159E812CA1C5C29947651672E2588EFE1E "," timestamp ":" 1508961173 "," Prev_hash ":" 000003cf81f6b17e60ef1e3d8d24793450aecaf65cbe95086a29c1e48a5043b1 "," Data ":" I block #3 "} ########################## # # block Time # ########################### Block_zero = blocks (block_zero_dir) Another_block_zero = Block (block_zero_d
IR) assert Block_zero.is_valid () assert Block_zero = = Another_block_zero assert not Block_zero!= another_block_zero .... ##################################### # # Bringing chains into play # ##################################### blockchain = Chain ([Block_zero, Block_one, Block_two]) assert Blockchain.is_valid () assert len (blockchain) = = 3 Empty_chain = Chain ([ ] Assert len (empty_chain) = = 0 Empty_chain.add_block (Block_zero) assert len (empty_chain) = = 1 Empty_chain = Chain ([]) ass ert len (empty_chain) = = 0 ... assert blockchain = = Another_blockchain ASsert not blockchain!= Another_blockchain assert blockchain <= another_blockchain assert blockchain >= Another_bloc Kchain assert not blockchain > Another_blockchain assert not Another_blockchain < blockchain Blockchain.add_block (b Lock_three) assert Blockchain.is_valid () assert len (blockchain) = 4 assert not blockchain = = Another_blockchain assert bl Ockchain!= Another_blockchain assert not blockchain <= Another_blockchain assert blockchain >= a Ssert Blockchain > Another_blockchain assert another_blockchain < blockchain
Future additions to test.py include using one of the fancy, testing libraries which'll run all tests separately. This would let you know all of the tests that could fail rather than dealing with them one at a time. Another would is to put the test block dicts in files instead of the script. For example, adding a chaindata dir in the ' Test dir so I can test the creation and saving of blocks. peers, and Hard Links
The whole point of the "JBC" is "to" able to create different nodes that can run their own mining, own nodes O n different ports, store their own chains to their own folder, and chaindata the have to ability other give BL Ockchain.
To does this, I want to share the files quickly between the folders so any change to one would be represented in the other BL Ockchain nodes. Enter hard links.
At I tried rsync, where I would run the bash script and have it copy the main files to different local folder. The problem with the "is" every the change to a file and restart of the node would require me to ship the files over. I don ' t want to have to does all of the time.
Hard links, on the other hand, 'll make the OS point the files in the different folder exactly to the files in my main JB C folder. Any change and save for the main files would be represented in the other nodes.
Here's the bash script I created that'll link the folders.
#!/bin/bash
port=$1
If [-Z "$port"] #if port isn ' t assigned
then
echo Need to specify port number
EX It 1
fi
files= (block.py chain.py config.py mine.py node.py sync.py utils.py) mkdir jbc$port for
file in ${files[@]} "
do
echo Syncing $file
ln jbc/$file jbc$port/$file
done
Echo synced new JBC folder For Port $port
exit 1
To run, $./linknodes 5001 to create a folder called jbc5001 with the correct files.
Since Flask runs initially on port 5000, I ' m gong to-use 5001, 5002, and 5003 as my initial peer. Define them in config.py and use them when config is imported. As with many parts of the code, this'll definitely the change to make sure peers aren ' t hardcoded. We want to is able to ask for new ones.
#config. PY
#possible peers
to start with peers = [
' http://localhost:5000/',
' http://localhost:5001/ ',
' http://localhost:5002/',
' http://localhost:5003/',
]
#node. py
If __name__ = ' __main__ ':
If Len (SYS.ARGV) >= 2:
port = sys.argv[1]
else:
port = 5000
node.run (host= ' 127.0.0.1 '), Port=port)
Cool. In part 1, the Flask node already has a endpoint for sharing it ' s blockchain so we ' re good on that front, but we still NE Ed to write the "code to ask" Our peer friends about what th