├── LICENSE ├── README.md ├── main.py ├── requirements.txt ├── templates └── index.html └── voting.sol /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Adam Yala 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Your_First_Decentralized_Application_Python 2 | 3 | This code borrows heavily from [llSourcell's turtorial](https://github.com/llSourcell/Your_First_Decentralized_Application) which in turn borrows heavily from [maheshmurthy's tutorial.](https://github.com/maheshmurthy/ethereum_voting_dapp) 4 | 5 | Please head over to each and toss a star on the repositories. Both of them created a wonderful tutorials to learn from. 6 | 7 | ## Overview 8 | 9 | We will be building a decentralized voting application! 10 | 11 | 12 | 13 | The functionality of this repo is nearly identical to llSourcell's but the entire implementation is done in python! 14 | 15 | ## Setup 16 | 17 | ### Requirements 18 | 19 | * Python 3.6+ 20 | * Solidity 4.23+ 21 | * Node.js 9.8+ 22 | 23 | ### Steps 24 | 25 | 1. Create and activate a virtual environment 26 | 1. Install dependencies with `pip install -r requirements.txt` 27 | 1. Install the [ganache-cli](https://github.com/trufflesuite/ganache-cli) command line tool with `npm install -g ganache-cli` 28 | 1. **What does this cli do?** It runs an ethereum node locally. Normally we'd have to download a lot of blockchain transactions and run a test ethereum node locally. This tool lets us run a small local node for easy peasey development. This tool used to be called the `testrpc`. 29 | 2. **Uh... This tool isn't python...** True, but I have found the JavaScript tooling for testrpc to be fantastic and easy to use. If you don't want to bother with `npm` or just want to try out a full python stack, try out [eth-testrpc](https://github.com/pipermerriam/eth-testrpc). It's pip installable but not as maintained as `ganache-cli`. 30 | 31 | ## Usage 32 | 33 | Open up two tabs. In the first tab run `ganache-cli`. This will start a block chain locally that we can play with. 34 | 35 | In the second tab activate your virtual environment and run `main.py`. This will start our little flask app in debug mode, deploying our contract in the process. 36 | 37 | After the python file runs you should see something like: 38 | ``` 39 | Transaction: 0xd3d96eb1d0b8ca8b327d0eca60ff405d0000c5cd249d06712877effbcf73095f 40 | Contract created: 0x9e4fab9629b8768730d107ae909567974c4c8e35 41 | Gas usage: 352112 42 | Block Number: 1 43 | Block Time: Sat Dec 23 2017 22:31:13 GMT+0200 (SAST) 44 | ``` 45 | in the first tab. This is your contract being deployed to the chain on your local node! 46 | 47 | `main.py` is where the bulk of our logic happens. It deploys our smart contract to our test ethereum node and starts serving our flask app. `main.py` and `voting.sol` are heavily commented so please give those a read to understand what each is doing. 48 | 49 | Next open http://127.0.0.1:5000/ in your browser of choice. The web application will connect to our deployed contract and use it as the backend. 50 | 51 | Congrats! You setup your first decentralized application with python! -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | # flask is a python web framework. it allows us to send and receive user requests 2 | # with a minimal number of lines of non-web3py code. flask is beyond the scope of 3 | # this tutorial so the flask code won't be commented. that way we can focus on 4 | # how we're working with our smart contract 5 | from flask import Flask, request, render_template 6 | 7 | # solc is needed to compile our Solidity code 8 | from solc import compile_source 9 | 10 | # web3 is needed to interact with eth contracts 11 | from web3 import Web3, HTTPProvider 12 | 13 | # we'll use ConciseContract to interact with our specific instance of the contract 14 | from web3.contract import ConciseContract 15 | 16 | # initialize our flask app 17 | app = Flask(__name__) 18 | 19 | # declare the candidates we're allowing people to vote for. 20 | # note that each name is in bytes because our contract variable 21 | # candidateList is type bytes32[] 22 | VOTING_CANDIDATES = [b'Rama', b'Nick', b'Jose'] 23 | 24 | # open a connection to the local ethereum node 25 | http_provider = HTTPProvider('http://localhost:8545') 26 | eth_provider = Web3(http_provider).eth 27 | 28 | # we'll use one of our default accounts to deploy from. every write to the chain requires a 29 | # payment of ethereum called "gas". if we were running an actual test ethereum node locally, 30 | # then we'd have to go on the test net and get some free ethereum to play with. that is beyond 31 | # the scope of this tutorial so we're using a mini local node that has unlimited ethereum and 32 | # the only chain we're using is our own local one 33 | default_account = eth_provider.accounts[0] 34 | # every time we write to the chain it's considered a "transaction". every time a transaction 35 | # is made we need to send with it at a minimum the info of the account that is paying for the gas 36 | transaction_details = { 37 | 'from': default_account, 38 | } 39 | 40 | # load our Solidity code into an object 41 | with open('voting.sol') as file: 42 | source_code = file.readlines() 43 | 44 | # compile the contract 45 | compiled_code = compile_source(''.join(source_code)) 46 | 47 | # store contract_name so we keep our code DRY 48 | contract_name = 'Voting' 49 | 50 | # lets make the code a bit more readable by storing these values in variables 51 | contract_bytecode = compiled_code[f':{contract_name}']['bin'] 52 | contract_abi = compiled_code[f':{contract_name}']['abi'] 53 | # the contract abi is important. it's a json representation of our smart contract. this 54 | # allows other APIs like JavaScript to understand how to interact with our contract without 55 | # reverse engineering our compiled code 56 | 57 | # create a contract factory. the contract factory contains the information about the 58 | # contract that we probably will not change later in the deployment script. 59 | contract_factory = eth_provider.contract( 60 | abi=contract_abi, 61 | bytecode=contract_bytecode, 62 | ) 63 | 64 | # here we pass in a list of smart contract constructor arguments. our contract constructor 65 | # takes only one argument, a list of candidate names. the contract constructor contains 66 | # information that we might want to change. below we pass in our list of voting candidates. 67 | # the factory -> constructor design pattern gives us some flexibility when deploying contracts. 68 | # if we wanted to deploy two contracts, each with different candidates, we could call the 69 | # constructor() function twice, each time with different candidates. 70 | contract_constructor = contract_factory.constructor(VOTING_CANDIDATES) 71 | 72 | # here we deploy the smart contract. the bare minimum info we give about the deployment is which 73 | # ethereum account is paying the gas to put the contract on the chain. the transact() function 74 | # returns a transaction hash. this is like the id of the transaction on the chain 75 | transaction_hash = contract_constructor.transact(transaction_details) 76 | 77 | # if we want our frontend to use our deployed contract as it's backend, the frontend 78 | # needs to know the address where the contract is located. we use the id of the transaction 79 | # to get the full transaction details, then we get the contract address from there 80 | transaction_receipt = eth_provider.getTransactionReceipt(transaction_hash) 81 | contract_address = transaction_receipt['contractAddress'] 82 | 83 | contract_instance = eth_provider.contract( 84 | abi=contract_abi, 85 | address=contract_address, 86 | # when a contract instance is converted to python, we call the native solidity 87 | # functions like: contract_instance.call().someFunctionHere() 88 | # the .call() notation becomes repetitive so we can pass in ConciseContract as our 89 | # parent class, allowing us to make calls like: contract_instance.someFunctionHere() 90 | ContractFactoryClass=ConciseContract, 91 | ) 92 | 93 | 94 | @app.route('/', methods=['GET', 'POST']) 95 | def index(): 96 | alert = '' 97 | candidate_name = request.form.get('candidate') 98 | if request.method == 'POST' and candidate_name: 99 | # if we want to pass a candidate name to our contract then we have to convert it to bytes 100 | candidate_name_bytes = candidate_name.encode() 101 | try: 102 | # the typical behavior of a solidity function is to validate inputs before 103 | # executing the function. remember that work on the chain is permanent so 104 | # we really want to be sure we're running it when appropriate. 105 | # 106 | # in the case of voteForCandidate, we check to see that the passed in name 107 | # is actually one of the candidates we specified on deployment. if it's not, 108 | # the contract will throw a ValueError which we want to catch 109 | contract_instance.voteForCandidate(candidate_name_bytes, transact=transaction_details) 110 | except ValueError: 111 | alert = f'{candidate_name} is not a voting option!' 112 | 113 | # the web3py wrapper will take the bytes32[] type returned by getCandidateList() 114 | # and convert it to a list of strings 115 | candidate_names = contract_instance.getCandidateList() 116 | # solidity doesn't yet understand how to return dict/mapping/hash like objects 117 | # so we have to loop through our names and fetch the current vote total for each one. 118 | candidates = {} 119 | for candidate_name in candidate_names: 120 | votes_for_candidate = contract_instance.totalVotesFor(candidate_name) 121 | # we have to convert the candidate_name back into a string. we get it back as bytes32 122 | # we also want to strip the tailing \x00 empty bytes if our names were shorter than 32 bytes 123 | # if we don't strip the bytes then our page will say "Rama\x00\x00\x00\x00\x00\x00\x00\x00" 124 | candidate_name_string = candidate_name.decode().rstrip('\x00') 125 | candidates[candidate_name_string] = votes_for_candidate 126 | 127 | return render_template('index.html', candidates=candidates, alert=alert) 128 | 129 | 130 | if __name__ == '__main__': 131 | # set debug=True for easy development and experimentation 132 | # set use_reloader=False. when this is set to True it initializes the flask app twice. usually 133 | # this isn't a problem, but since we deploy our contract during initialization it ends up getting 134 | # deployed twice. when use_reloader is set to False it deploys only once but reloading is disabled 135 | app.run(debug=True, use_reloader=False) 136 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | attrdict==2.0.0 2 | certifi==2018.4.16 3 | chardet==3.0.4 4 | click==6.7 5 | cytoolz==0.9.0.1 6 | eth-abi==1.1.1 7 | eth-account==0.2.2 8 | eth-hash==0.1.3 9 | eth-keyfile==0.5.1 10 | eth-keys==0.2.0b3 11 | eth-rlp==0.1.2 12 | eth-utils==1.0.3 13 | Flask==1.0.2 14 | hexbytes==0.1.0 15 | idna==2.6 16 | itsdangerous==0.24 17 | Jinja2==2.10 18 | lru-dict==1.1.6 19 | MarkupSafe==1.0 20 | parsimonious==0.8.0 21 | py-solc==3.0.0 22 | pycryptodome==3.6.1 23 | requests==2.18.4 24 | rlp==1.0.1 25 | semantic-version==2.6.0 26 | six==1.11.0 27 | toolz==0.9.0 28 | urllib3==1.22 29 | web3==4.3.0 30 | websockets==4.0.1 31 | Werkzeug==0.14.1 32 | -------------------------------------------------------------------------------- /templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | Hello World DApp 12 | 13 | 14 | 15 |
16 |
17 |

A Simple Hello World Voting Application

18 | {% if alert %} 19 |
20 | {{ alert }} 21 |
22 | {% endif %} 23 |
24 |
25 | 26 |
27 |
28 |
29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | {% for candidate, votes in candidates.items() %} 38 | 39 | 40 | 41 | 42 | {% endfor %} 43 | 44 |
CandidateVotes
{{ candidate }}{{ votes }}
45 |
46 |
47 |
48 | 49 |
50 |
51 |
52 |
53 | 54 |
55 | 56 |
57 |

58 |
59 | 60 |
61 |
62 |
63 |
64 |
65 | 66 | -------------------------------------------------------------------------------- /voting.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.24; 2 | // We have to specify what version of compiler this code will compile with 3 | 4 | contract Voting { 5 | /* mapping field below is equivalent to an associative array or hash. 6 | The key of the mapping is the candidate name stored as type bytes32 and value is 7 | an unsigned integer to store the vote count 8 | */ 9 | 10 | mapping (bytes32 => uint8) public votesReceived; 11 | 12 | /* Solidity doesn't let you pass in an array of strings in the constructor (yet). 13 | We will use an array of bytes32 instead to store the list of candidates 14 | */ 15 | 16 | bytes32[] public candidateList; 17 | 18 | /* This is the constructor which will be called once when you 19 | deploy the contract to the blockchain. When we deploy the contract, 20 | we will pass an array of candidates who will be contesting in the election 21 | */ 22 | constructor(bytes32[] candidateNames) public { 23 | candidateList = candidateNames; 24 | } 25 | 26 | /* Accessing class attributes directly with web3py or web3js sometimes leads to 27 | unpredictable behavior. To be safe we create a getter method that returns our 28 | array of candidate names. 29 | */ 30 | function getCandidateList() public view returns (bytes32[]) { 31 | return candidateList; 32 | } 33 | 34 | // This function returns the total votes a candidate has received so far 35 | function totalVotesFor(bytes32 candidate) public view returns (uint8) { 36 | require(validCandidate(candidate) == true); 37 | return votesReceived[candidate]; 38 | } 39 | 40 | // This function increments the vote count for the specified candidate. This 41 | // is equivalent to casting a vote 42 | function voteForCandidate(bytes32 candidate) public { 43 | require(validCandidate(candidate) == true); 44 | votesReceived[candidate] += 1; 45 | } 46 | 47 | function validCandidate(bytes32 candidate) public view returns (bool) { 48 | for(uint i = 0; i < candidateList.length; i++) { 49 | if (candidateList[i] == candidate) { 50 | return true; 51 | } 52 | } 53 | return false; 54 | } 55 | } --------------------------------------------------------------------------------