├── 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 |
Candidate
33 |
Votes
34 |
35 |
36 |
37 | {% for candidate, votes in candidates.items() %}
38 |
39 |
{{ candidate }}
40 |
{{ votes }}
41 |
42 | {% endfor %}
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
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 | }
--------------------------------------------------------------------------------