-
Notifications
You must be signed in to change notification settings - Fork 6
/
web3py.html
161 lines (157 loc) · 11.9 KB
/
web3py.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" lang="" xml:lang="">
<head>
<meta charset="utf-8" />
<meta name="generator" content="pandoc" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes" />
<title>Web3.py Introduction</title>
<style>
code{white-space: pre-wrap;}
span.smallcaps{font-variant: small-caps;}
div.columns{display: flex; gap: min(4vw, 1.5em);}
div.column{flex: auto; overflow-x: auto;}
div.hanging-indent{margin-left: 1.5em; text-indent: -1.5em;}
/* The extra [class] is a hack that increases specificity enough to
override a similar rule in reveal.js */
ul.task-list[class]{list-style: none;}
ul.task-list li input[type="checkbox"] {
font-size: inherit;
width: 0.8em;
margin: 0 0.8em 0.2em -1.6em;
vertical-align: middle;
}
.display.math{display: block; text-align: center; margin: 0.5rem auto;}
</style>
<link rel="stylesheet" href="../markdown.css" />
<script>
function openTab(evt, tabName) {
// Declare all variables
var i, tabcontent, tablinks;
// Get all elements with class="tabcontent" and hide them
tabcontent = document.getElementsByClassName("tabcontent");
for (i = 0; i < tabcontent.length; i++) {
tabcontent[i].style.display = "none";
}
// Get all elements with class="tablinks" and remove the class "active"
tablinks = document.getElementsByClassName("tablinks");
for (i = 0; i < tablinks.length; i++) {
tablinks[i].className = tablinks[i].className.replace(" active", "");
}
// Show the current tab, and add an "active" class to the button that opened the tab
document.getElementById(tabName).style.display = "block";
evt.currentTarget.className += " active";
}
function insertCopyLink(text) {
document.write("<span class=\"copylink copy_img\" onclick=\"navigator.clipboard.writeText('" + text.replace(/\"/g,"\\'") + "')\"></span>");
}
</script>
</head>
<body>
<h1 id="web3.py-introduction">Web3.py Introduction</h1>
<p><a href="index.html">Go up to the main CCC docs page</a> (<a href="index.md">md</a>)</p>
<p>This document is an introduction to using web3 in Python.</p>
<h4 id="version">Version</h4>
<p>The functions herein are for web3.py version 6.0.0 and above. Specifically, all the function names changed from version 5.31.0 and 6.0.0. Run <code>pip freeze</code> to see what version of web3 you have installed. If it’s not 6.0.0 or above, this tutorial is not going to work out well for you.</p>
<h4 id="starting-python">Starting Python</h4>
<p>Run Python. You can put these commands into a script file, or type these commands directly into a Python interpreter.</p>
<p>You will need to install two packages via <code>pip</code> or <code>pip3</code>: the packages are <code>web3</code> and <code>hexbytes</code> (note that the former may install the latter on your system).</p>
<p>You should always start with the following import lines:</p>
<pre><code>from web3 import Web3
from hexbytes import HexBytes</code></pre>
<p>All the code below assumes those two import lines are present.</p>
<h4 id="connect-to-the-blockchain">Connect to the blockchain</h4>
<p>In Python, you can connect to the blockchain in a few different ways. The difference is if you are going to connect via the geth.ipc file or through the course server endpoint.</p>
<p>If you are using the geth.ipc file:</p>
<pre><code>w3 = Web3(Web3.IPCProvider('/path/to/geth.ipc'))</code></pre>
<p>This assumes you have started up a node as per the <a href="../ethprivate/index.html">Private Ethereum Blockchain assignment</a> (<a href="(../ethprivate/index.md)">md</a>).</p>
<p>In Windows, according to <a href="https://ethereum.stackexchange.com/questions/76036/how-do-i-connect-geth-to-web3-py-using-ipc-on-windows">this post</a>, you do not pass anything in to the function, as shown here: <code>w3 = Web3(Web3.IPCProvider()</code>.</p>
<p>To connect via the course server:</p>
<pre><code>w3 = Web3(Web3.WebsocketProvider('wss://<your-provider-url>'))</code></pre>
<p>The full URL for the course server is on the Collab landing page.</p>
<p>If you are using a proof-of-authority blockchain, you have to execute the following two commands after you initialize the <code>w3</code> variable:</p>
<pre><code>from web3.middleware import geth_poa_middleware
w3.middleware_onion.inject(geth_poa_middleware, layer=0)</code></pre>
<p>To see if you are connected, you can try:</p>
<ul>
<li><code>w3.is_connected()</code>, which should return <code>True</code></li>
<li><code>w3.eth.get_block('latest')</code>, which should return the latest block</li>
</ul>
<h4 id="calling-a-view-or-pure-function-on-a-smart-contract">Calling a <code>view</code> or <code>pure</code> function on a smart contract</h4>
<p>Calling a <code>view</code> or <code>pure</code> function is much like with geth – we define an address and an ABI variable, then create the interface and then the contract. For this example, we’ll call a method on our TokenDEX contract.</p>
<p>You’ll need to determine the address of your TokenDEX – you can use the one you submitted to the dex.php listing. Define the following variables:</p>
<pre><code>address='0x0123456789abcdef0123456789abcdef01234567'
abi='[...]'</code></pre>
<p>For the <code>abi</code> variable, you can copy it from the Collab landing page. Note that this ABI value is in single quotes, which is unlike how we do it via the geth Javascript terminal.</p>
<p>We can then create the contract instance in one command:</p>
<pre><code>contract = w3.eth.contract(address=address, abi=abi)</code></pre>
<p>From there, we can call a function on it:</p>
<pre><code>contract.functions.k().call()</code></pre>
<p>Notice that we have to put parenthesis after both the method name of <code>k</code> and after the <code>call</code>. Parameters, if there were any, would go in the parenthesis after the method name, not in the <code>call()</code> parentheses.</p>
<h4 id="transactions">Transactions</h4>
<p>In geth, we would unlock our account with our password, and then call <code>send_transaction()</code>. To do this in Python is a bit more complicated.</p>
<p>First, we need the private key in decrypted form. This was done in the <a href="ethprivate/index.html">Private Ethereum Blockchain assignment</a> (<a href="ethprivate/index.md">md</a>) in part 4 – if you don’t have that decrypted private key saved, or if you changed accounts, then re-do that section. Your private key will be of the form <code>b'0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef'</code>. Save it via:</p>
<pre><code>private_key = hexbytes.HexBytes('0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef')</code></pre>
<p>You will note that this is a slightly different form than what you got when you decrypted your key – the hex byte data is the same, it just is in a <code>hexbytes.HexBytes()</code> constructor. In particular, the leading ‘b’ was removed from the string value (the one before the single quote).</p>
<p>We also need to define our account address that we have the private key for:</p>
<pre><code>my_address='0x0123456789abcdef0123456789abcdef01234567';</code></pre>
<p>This address has to be in correct check-summed form – you can run it through <a href="https://ethsum.netlify.app/">ethsum.netlify.app</a> to get the check-summed version. You can also use the Web3.py library to checksum and address: <code>Web3.to_checksum_address(addr)</code>.</p>
<p>Once we have the private key, we have to take three steps: create the transaction, sign it, and then transmit it to the blockchain.</p>
<p>Let’s first create the transaction:</p>
<pre><code>transaction = contract.functions.getTokenCCAbbreviation().build_transaction({
'gas': 70000,
'gasPrice': w3.to_wei('10', 'gwei'),
'from': my_address,
'nonce': w3.eth.get_transaction_count(my_address),
'chainId': 12345678,
})</code></pre>
<p>Parameters, if there were any, would go in the parenthesis after the method name, not in the <code>build_transaction()</code> parentheses.</p>
<p>Other fields could be added as well – if we wanted to send some wei in with the transaction, such as to a <code>payable</code> function, then we would add a <code>value</code> key with the (integer) wei amount as the value.</p>
<p>If all we wanted to do was to just pay ETH, and not call a function, we would just create a dictionary:</p>
<pre><code>transaction = {
'nonce': w3.eth.get_transaction_count(my_address),
'to': '0x0123456789abcdef0123456789abcdef01234567',
'value': w3.to_wei(1, 'ether'),
'gas': 21000,
'gasPrice': web3.to_wei('10', 'gwei'),
'chainId': 12345678,
}</code></pre>
<p>We then sign that transaction:</p>
<pre><code>signed_txn = w3.eth.account.sign_transaction(transaction, private_key=private_key)</code></pre>
<p>Lastly, we send it to the network</p>
<pre><code>ret = w3.eth.send_raw_transaction(signed_txn.rawTransaction)</code></pre>
<p>That’s it! The transaction was sent to the blockchain.</p>
<h4 id="transaction-details">Transaction details</h4>
<p>The return value of <code>send_raw_transaction()</code> was saved into the <code>ret</code> variable. We can then get that transaction information:</p>
<pre><code>w3.eth.wait_for_transaction_receipt(ret)</code></pre>
<p>This is the transaction <em>receipt</em>, which has slightly different information than the transaction itself. In particular, it will wait (block) until the transaction is mined into a block. If the <code>status</code> field is 0, then the transaction was not successful for some reason. Those reasons can include: a reversion (such as a failed <code>require()</code>, insufficient funds, insufficient gas, etc.</p>
<p>We can also get the raw transaction information itself:</p>
<pre><code>w3.eth.get_transaction(ret)</code></pre>
<p>This is the same information that you can find in the blockchain explorer.</p>
<h4 id="gas-estimation">Gas estimation</h4>
<p>Web3 can estimate how much gas your transaction will use. Once you have created your transaction object, you can just call: <code>gas = w3.eth.estimate_gas(transaction)</code>. Note that this is an <em>estimate</em>, not a fully accurate count. In particular, if there is an if/else path, then it can’t always know which path it will take. Although an estimate, it is sufficient for our purposes. Lastly, note that this is the amount of gas, and once you supply it with a amount of wei per gas, you can convert that into a actual price in ether.</p>
<h4 id="return-values-and-reverts">Return values and reverts</h4>
<p>It turns out it is often (but not always!) possible to get the return value for a transaction or the reason for a reversion. Note that calls do not need any special code to get the return value, only transactions. The code below will attempt to do just that (code adapted from <a href="https://snakecharmers.ethereum.org/web3py-revert-reason-parsing/">here</a>).</p>
<pre><code>def getTXNResult(w3,txhash):
try:
tx = w3.eth.get_transaction(txhash)
except Exception as e:
print("Error getting transaction:",e)
return None
replay_tx = {
'to': tx['to'],
'from': tx['from'],
'value': tx['value'],
'data': tx['input'],
'gas': tx['gas'],
}
# replay the transaction locally:
try:
ret = w3.eth.call(replay_tx, tx.blockNumber - 1)
return (True,ret)
except Exception as e:
return (False,str(e))</code></pre>
<p>This function is provided in the code given with the homework.</p>
<h4 id="read-more">Read more</h4>
<p>The functionality of web3.py is similar to what we know of from geth, but the formatting of the function calls is different. You can see full API <a href="https://web3py.readthedocs.io/en/latest/index.html">here</a>.</p>
</body>
</html>