web3 | Ethereum Blockchain | TestRPC | Python 3
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 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 |
# Author............: Noelle Milton Vega # Creation Date.....: 01/04/2016 (Monday) # Description.......: Foundations for Blockchain and Smart-Contracts via Ethereum Crypto/Network. # A programmatic perspective. import web3 import random, string, pprint, sys # ============================================================================ # Ethereum is a specification of RPC methods that client-libraries can implement. # testrpc(8) is one such library, and it has nodejs and Python implementations, # respectively, testrpc and testrpc-py. We are using testrpc-py here. # It is started and running in the background like this: # nmvega@fedora$ testrpc-py | ganache-*.AppImage # ============================================================================ #endpoint = 'http://localhost:8545' # Testrpc or Ganache-cli #endpoint = 'https://ropsten.infura.io/v3/myHash' # Infuria TestNet (One of three offered). endpoint = 'http://localhost:7545' # Ganache UI: https://truffleframework.com/ganache w3 = web3.Web3(web3.providers.rpc.HTTPProvider(endpoint)) # ============================================================================ # ============================================================================ # Various helper functions used later on ... # ============================================================================ # ---------------------------------------------------------------------------- # From the list() of 10-accounts returned by testrpc(8), provide a public # key for the desired account to get its current ETH balance. # ---------------------------------------------------------------------------- accnt_ETH_balance = lambda account: w3.fromWei(w3.eth.getBalance(account), 'ether') # ---------------------------------------------------------------------------- # Print accounts and their balances in ETH ... # ---------------------------------------------------------------------------- def get_balances(): for accnt_pubKey in w3.eth.accounts: print('%s ETH balance: %s' % (accnt_pubKey, accnt_ETH_balance(accnt_pubKey))) # ---------------------------------------------------------------------------- # A function to build a transaction JSON/dict() data structure from a transaction # attributes-tuple that specifies the following (noting that 'metaData' is # optional): (srcAccnt, dstAccnt, ethValue, gas, gasPrice, metaData) # ---------------------------------------------------------------------------- build_txn = lambda txn_attribs_tuple : { "to" : txn_attribs_tuple[1], "value" : txn_attribs_tuple[2], "gas" : txn_attribs_tuple[3], "gasPrice" : txn_attribs_tuple[4], "nonce" : w3.eth.getTransactionCount(txn_attribs_tuple[0]), "data" : b'' if len(txn_attribs_tuple) < 6 else txn_attribs_tuple[5] } # ---------------------------------------------------------------------------- # Creates a "signable" equivalent of transaction JSON/dict() by converting # VALUES for all KEYS (except "from" and "to" keys) into Hexadecimal equivalent # values. This is the RAW representation, and is required for sending raw # transactions (useful when pre-signing them). Note: We never use this function # because higher level abstractions in the Web3 library do this under the hood. # It is included here just for knowledge and completeness. =:) # ---------------------------------------------------------------------------- txn_raw = lambda txn: { k : v if k in ("from", "to") else w3.toHex(v) for (k,v) in txn.items() } # ============================================================================ # ============================================================================ # Using Web3 to generate a PUBLIC/PRIVATE blockchain keypair. DEMO ONLY! # ============================================================================ # Note: Please read BITCOIN IMPROVEMENT PROGRAM (BIP) and ETHEREUM IMPROVEMENT # PROGRAM (EIP) for best-practices on key pair generation. # ============================================================================ # Note that w3.eth.account.create() generates a different account object each # time it's run; even if a 'seed' were omitted (which we added to help randomness). # ============================================================================ seed = ''.join(random.choices(string.printable, k=128)) pub_priv_address_pair = w3.eth.account.create(seed) # Not idempotent. Good! public_key = pub_priv_address_pair.address[2:].lower() # 40-hex-chars (0x prefix removed) private_key = pub_priv_address_pair.privateKey.hex()[2:].lower() # 64-hex-chars (0x prefix removed) # ============================================================================ # Ex: public_key = 46FC666ea7a328.............. (length 40) # private_key = 8107d1756216623a0f2611bf.... (length 64) # Check if public-key has ever been used on blockchain: https://etherscan.io # Not being present on a Blockchain doesn't mean it hasn't been assigned! # ============================================================================ # ============================================================================ # ETH, WEI and GAS mathematics ... # Charging ETH/WEI for operations (ops) on Ethereum has two perposes: # ============================================================================ # 1) Prevents DDoS attacks. Per "Touring's Halting Problem", there's no way # to determine if a combination PROGRAM + INPUTS will ever terminate. # So running out of GAS will halt it. # # 2) Compensate miners for transactions that do not involve Cryptocurrency # exchange. Example: If a miner is mining the insertion of a medical # record onto the blockchain, there's no fraction of cryptocurrency that # he can take. Eth/Wei solves this. # ============================================================================ # https://is.gd/evm_op_codes # OP-Code definitions. # https://is.gd/evm_gas_costs # GAS cost for each OP-Code. # ============================================================================ # Example: A "Generate Transaction" op (op-code 'GTX') costs 21,000 GAS. So # if a transaction, say, transfers currency (or whatever item of value) from # one account to another, it always costs 21,000 GAS. The unit-cost of GAS in # Eth/Wei is what varies over time. To see the current price of Gas in Wei # visit: https://ethstats.net # ============================================================================ GTX_op_gasCost = 21000 # 21,000 Gas for an Ethereum "Transaction". gwei_per_unit_gas = 1.11 # Update this from: https://ethstats.net wei_per_unit_gas = w3.toWei(gwei_per_unit_gas, 'gwei') wei_per_GTX_op = wei_per_unit_gas * GTX_op_gasCost eth_per_GTX_op = w3.fromWei(wei_per_GTX_op, 'ether') #print(eth_per_GTX_op) # 0.00002331 Eth. Now go to web and convert this to U.S. dollars. # ============================================================================ # ============================================================================ # Send transactions to blockchain: AUTO-SIGNED and MANUAL-SIGNED variants ... # ============================================================================ # Ethereun Transaction (op-code GTX): Send 1.0 Eth from account1 to account2. # Note: Get account1_privKey from testrpc/Ganache UI. Not possible to get programmatically! # ============================================================================ account1_privKey = '..... 64-character Hexadecimal string .....' account1_pubKey = w3.eth.accounts[0] # Public-key of 1st testrpc account (src). account2_pubKey = w3.eth.accounts[1] # Public-key of 2nd testrpc account (dest). value = w3.toWei(1, 'ether') # We'll sent 1.0 Ether from account1 to account2 gas = 21000 # Published GTX op-cost is 21,000 gas gasPrice = 1 # When working off-chain, this can be arbitrarily set. data = b'' # SEE: https://web3py.readthedocs.io/en/stable/web3.eth.html?highlight=sendTransaction txn_attribs_tuple = (account1_pubKey, account2_pubKey, value, gas, gasPrice, data) # ============================================================================ # ----------------------------------------------------------- # AUTOMATIC SIGNING way to do it (say, by client or wallet) ... # ----------------------------------------------------------- print("=" * 80) autoSigned_txn = build_txn(txn_attribs_tuple) # Build base transaction JSON. autoSigned_txn["from"] = txn_attribs_tuple[0] # Add "from" field b/c unsigned (i.e. auto-signed) txns require it. print('Transmitting autoSigned transaction:'); pprint.pprint(autoSigned_txn) autoSigned_txn_receipt = w3.eth.sendTransaction(autoSigned_txn) # 64-hex-char HexBytes object/type. print('\nautoSigned_txn_receipt: %s' % autoSigned_txn_receipt.hex()) print('\nautoSigned transaction BlockChain record:\n\t%s' % w3.eth.getTransaction(autoSigned_txn_receipt)) print("=" * 80, '\n') # ----------------------------------------------------------- # ----------------------------------------------------------- # MANUAL PRE-SIGNING way to do it (by, say, us offline) ... # ----------------------------------------------------------- print("=" * 80) selfSigned_txn = build_txn(txn_attribs_tuple) # Build base transaction JSON. selfSigned_txn = w3.eth.account.signTransaction(selfSigned_txn, account1_privKey) # ----------------------------------------------------------- # SITENOTE: Notice above that we SIGNED our transaction offline, and the resulting # object attribute, selfSigned_txn.rawTransaction, contains all of the data # required to submit it to the Blockchain. While we submit it programmatically # next, we could easily store it on a USB stick, pass it around, and submit it # later on elsewhere; for example here: https://etherscan.io/pushTx # ----------------------------------------------------------- # ----------------------------------------------------------- print('Transmitting selfSigned Raw Transaction: %s\n' % selfSigned_txn.rawTransaction.hex()[2:]) selfSigned_txn_receipt = w3.eth.sendRawTransaction(selfSigned_txn.rawTransaction) print('selfSigned_txn_receipt: %s\n' % selfSigned_txn_receipt.hex()) print('selfSigned transaction BlockChain record:\n\t%s' % w3.eth.getTransaction(selfSigned_txn_receipt)) print("=" * 80, '\n') # ============================================================================ # ============================================================================ print("=" * 80) print('New account1 ETH balance: %s' % accnt_ETH_balance(account1_pubKey)) # e.g.: 98.9999922 print('New account2 ETH balance: %s' % accnt_ETH_balance(account2_pubKey)) # e.g.: 101 print("=" * 80) # ============================================================================ # ============================================================================ # w3.eth.getTransaction(receipt) looks like this ... # ============================================================================ # AttributeDict({ # 'hash': HexBytes('0x7e51e692accd3d80dc3d1120c6d80b2c33b39f1ef4e5873dd989c6b3d9020404'), # 'nonce': 23, # 'blockHash': HexBytes('0x4038ace16e914bb35ee97eccda18d88c5cec6cc857e10b946a731f4d0b569fdf'), # 'blockNumber': 24, # 'transactionIndex': 0, # 'from': '0xd046443e436C21631Cdb23b344305e51C457D7Ca', # 'to': '0xc3D981622b48535509374BB64e8AAE758C04e3ab', # 'value': 1000000000000000000, # 'gas': 21000, # 'gasPrice': 1, # 'input': '0x0', # # The next three k/v pairs are only found in Signed Transactions! # 'v': 28, # 'r': HexBytes('0x5de8a34e536b78447ee863b11f19b04bcd009b58d919413a0915c188096baa2e'), # 's': HexBytes('0x08145aabb144f7e17ecb76d7ca347818cf0ffb8a2aa0ce2093eeb741cae9b9a7')}) # ============================================================================ # Notes on attributes in the output: # ============================================================================ # 'nonce'.............: Same as: w3.eth.getTransactionCount(account1) before sending. # 'hash'..............: Same value as tx_receipt that we received on transaction submission. # 'blockHash'.........: A hash of the BLOCK the transaction was mind in, and it will be # included in the NEXT BLOCK mined. # 'blockNumber'.......: The blockchain block-number that this transaction iside of. We have # a tiny personal blockchain. =:) # 'transactionIndex...: Blockchain blocks include multiple transactions in them, each with a # a transaction index number. Since this is our personal blockchain, # each mined block has only one transaction, so this number is always # zero (qty. 0) for us. =:) # 'input'.............: Our tx dict() could have included an 'input' key/value # pair where we could include arbitrary input data. Default: 0x0 # ============================================================================ # ============================================================================ # ============================================================================ # A note about transaction NONCES from an account. Above, we could specify a # nonce key/value pair in the 'tx' dict(). If we don't, the next one is automatically # generated for us when the transaction is submitted. However, whether manually # specified or automatically generated, the nonce must NOT already exist on # the blockchain for that account (i.e. it cannot have been previously used). # Nonces are monotically increasing values, and depending on the node server # (e.g. testrpc, geth, infurium) it may be possible to specify a nonce # that is much larger than the current one for an account (i.e. a "future" one # instead of the next sequential nonce). For use-cases for this, see this # explanation on StackOverflow: # https://ethereum.stackexchange.com/questions/ + # 2808/what-happens-when-a-transaction-nonce-is-too-high # ============================================================================ # Nonces is the means by which "replay attacks" (i.e. sending the exact # same HTTP or Blockchain transaction again and again) is prevented. For # explanation, see: https://en.wikipedia.org/wiki/Cryptographic_nonce # Let's see this in action. # ============================================================================ |