├── requirements.txt ├── .gitignore ├── run.sh ├── screenshot.png ├── animated-screenshot.gif ├── style.css ├── deploy.sh ├── .gcloudignore ├── index.html ├── LICENSE ├── sketch.js ├── gol.sol ├── README.md └── main.py /requirements.txt: -------------------------------------------------------------------------------- 1 | Flask===1.0.2 2 | web3==5.17.0 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .env 2 | env.yaml 3 | __pycache__/* 4 | -------------------------------------------------------------------------------- /run.sh: -------------------------------------------------------------------------------- 1 | set -x 2 | FLASK_ENV=development FLASK_APP=main flask run -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nialloc/GameOfLife/HEAD/screenshot.png -------------------------------------------------------------------------------- /animated-screenshot.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nialloc/GameOfLife/HEAD/animated-screenshot.gif -------------------------------------------------------------------------------- /style.css: -------------------------------------------------------------------------------- 1 | html, body { 2 | margin: 0; 3 | padding: 0; 4 | } 5 | canvas { 6 | display: block; 7 | } 8 | 9 | -------------------------------------------------------------------------------- /deploy.sh: -------------------------------------------------------------------------------- 1 | gcloud functions deploy gameoflife \ 2 | --runtime python38 \ 3 | --trigger-http \ 4 | --allow-unauthenticated \ 5 | --env-vars-file env.yaml -------------------------------------------------------------------------------- /.gcloudignore: -------------------------------------------------------------------------------- 1 | run.sh 2 | deploy.sh 3 | .env 4 | gol.sol 5 | index.html 6 | LICENSE 7 | README.md 8 | run.sh 9 | screenshot.png 10 | sketch.js 11 | style.css 12 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 nialloc 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 | -------------------------------------------------------------------------------- /sketch.js: -------------------------------------------------------------------------------- 1 | let w 2 | let columns 3 | let rows 4 | let board 5 | let button 6 | let timer = 3 7 | 8 | 9 | let gdata 10 | 11 | // using ngrok for testing purposes 12 | //let server_name = 'https://76a64b6ad96e.ngrok.io' 13 | server_name = 'https://us-central1-crypto-174821.cloudfunctions.net/gameoflife' 14 | 15 | function setup() { 16 | createCanvas(800, 640); 17 | w = 12; // width of cells in pixels 18 | columns = 32 19 | rows = 32 20 | 21 | board = new Array(columns); 22 | for (let i = 0; i < columns; i++) { 23 | board[i] = new Array(rows); 24 | } 25 | for (let i = 0; i < rows; i++) { 26 | for (let j = 0; j < columns; j++) { 27 | board[i][j] = 0 28 | } 29 | } 30 | 31 | button_start = createButton('start new pattern'); 32 | button_start.position(400, 0); 33 | button_start.mousePressed(function() { 34 | // send cell pattern to server 35 | send_cells() 36 | }); 37 | 38 | 39 | button_getdata = createButton('get data') 40 | button_getdata.position(400,30) 41 | button_getdata.mousePressed(function(){ 42 | request_data() 43 | }) 44 | 45 | 46 | 47 | button_random = createButton('Random'); 48 | button_random.position(400, 120); 49 | button_random.mousePressed(function() { 50 | for (let i = 0; i < rows; i++) { 51 | for (let j = 0; j < columns; j++) { 52 | board[i][j] = random([0, 1]) 53 | } 54 | } 55 | 56 | 57 | }); 58 | 59 | button_clear = createButton('Clear'); 60 | button_clear.position(400, 150); 61 | button_clear.mousePressed(function() { 62 | console.log('button_clear') 63 | clear_board() 64 | }) 65 | 66 | button_gliders = createButton('Gliders') 67 | button_gliders.position(400, 180) 68 | button_gliders.mousePressed(function() { 69 | 70 | clear_board() 71 | for (let i = 5; i < 20; i += 4) { 72 | board[i + 1][i + 1] = 1 73 | board[i + 2][i + 2] = 1 74 | board[i + 3][i + 2] = 1 75 | board[i + 1][i + 3] = 1 76 | board[i + 2][i + 3] = 1 77 | } 78 | 79 | }) 80 | 81 | 82 | setInterval(timerGetData, 30 * 1000); 83 | setInterval(timerStep, 60 * 1000); 84 | 85 | loadJSON(server_name + '/data', cbData, 'json') 86 | 87 | } 88 | 89 | function timerGetData() { 90 | loadJSON(server_name + '/data', cbData, 'json'); 91 | } 92 | function timerStep() { 93 | loadJSON(server_name + '/step', cbStep, 'json'); 94 | } 95 | 96 | function request_data(){ 97 | loadJSON(server_name + '/data', cbData, 'json') 98 | } 99 | 100 | function send_cells() { 101 | let cells = [] 102 | for (let i = 0; i < rows; i++) { 103 | for (let j = 0; j < columns; j++) { 104 | cells.push(board[i][j]) 105 | } 106 | } 107 | 108 | httpPost(server_name + '/setcells', 109 | "json", { 110 | 'cells': cells 111 | },cbData) 112 | 113 | } 114 | 115 | 116 | function clear_board() { 117 | for (let i = 0; i < rows; i++) { 118 | for (let j = 0; j < columns; j++) { 119 | board[i][j] = 0 120 | } 121 | } 122 | } 123 | 124 | 125 | 126 | function cbStep(data){ 127 | console.log('cbStep',data) 128 | loadJSON(server_name + '/data', cbData, 'json'); 129 | } 130 | 131 | function cbSetCellsError(response) { 132 | console.log("cbSetCellsError", response) 133 | } 134 | 135 | function draw() { 136 | background(255); 137 | 138 | for (let i = 0; i < rows; i++) { 139 | for (let j = 0; j < columns; j++) { 140 | if ((board[i][j] == 1)) { 141 | fill(0); 142 | } else { 143 | fill(255); 144 | } 145 | stroke(0); 146 | rect(i * w, j * w, w - 1, w - 1); 147 | } 148 | } 149 | 150 | 151 | fill(color(0, 0, 255)); 152 | textSize(16) 153 | let posx = 400 154 | let posy = 220 155 | if (gdata) { 156 | text(`Network: ${gdata.network}`,posx,posy) 157 | 158 | s = `Sender Address: ${gdata.caller_address} 159 | Balance ${gdata.caller_balance} eth 160 | Latest Block ${gdata.block} 161 | Last Transaction Block ${gdata.myblock} 162 | Next Step won't be until at least Block ${gdata.target}` 163 | text(s,posx,posy+20) 164 | 165 | } 166 | 167 | text(`status: ${status}`,10,400) 168 | 169 | } 170 | 171 | // allow user to select their own cells.. 172 | function mousePressed(event) { 173 | 174 | let i = int(mouseX / w) 175 | let j = int(mouseY / w) 176 | if (i >= 0 && i < rows && j >= 0 && j < columns) { 177 | if (board[i][j]) { 178 | board[i][j] = 0 179 | } else { 180 | board[i][j] = 1 181 | } 182 | } 183 | 184 | } 185 | 186 | function cbData(data) { 187 | 188 | console.log(data) 189 | 190 | if (data.cells) { 191 | let cells = data.cells 192 | 193 | let index = 0 194 | for (let i = 0; i < rows; i++) { 195 | for (let j = 0; j < columns; j++) { 196 | board[i][j] = cells[index++] 197 | 198 | } 199 | } 200 | } 201 | 202 | // store this is a global variable 203 | gdata = data 204 | 205 | if (data.status) { 206 | status = data.status 207 | } 208 | 209 | } -------------------------------------------------------------------------------- /gol.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.7.4; 3 | 4 | contract GameOfLife { 5 | int16 constant rows = 32; 6 | int16 constant cols = 32; 7 | uint256[] public cells; 8 | uint256[] newcells; 9 | int public iterations; 10 | uint public myblock; 11 | 12 | event log(string text); 13 | event Cells(uint256[] cells); 14 | 15 | constructor () { 16 | // a bit for each cell - stored in an array of 256 bit numbers 17 | // for default of 32 x 32 this is 4 x 256 bit numbers 18 | cells = new uint256[](uint16(rows * cols) / 256); 19 | 20 | newcells = cells; 21 | 22 | // set up a few 'sliders' 23 | for (int16 i=0; i<20; i +=6) { 24 | int16 pos; 25 | pos = ((i+1) + (i+1)*cols) ; 26 | set(pos,1); 27 | pos = ((i+2) + (i+2)*cols) ; 28 | set(pos,1); 29 | pos = ((i+3) + (i+2)*cols) ; 30 | set(pos,1); 31 | pos = ((i+1) + (i+3)*cols) ; 32 | set(pos,1); 33 | pos = ((i+2) + (i+3)*cols) ; 34 | set(pos,1); 35 | } 36 | 37 | cells = newcells; 38 | } 39 | 40 | 41 | // get for a particular row/col return the cell state (1 alive, 0 dead) 42 | function get(int32 pos) view internal returns (int) { 43 | 44 | if (pos<0) { 45 | pos += rows*cols; 46 | } 47 | if (pos >= rows*cols) { 48 | pos -= rows*cols; 49 | } 50 | // make sure pos always is a valid value 51 | pos = pos % int32(rows*cols); 52 | // the linear array is held in 4 x 256 unsigned integers 53 | uint32 i = uint32(pos) / 256; 54 | uint32 j = uint32(pos) % 256; 55 | // if the j-th bit set? 56 | if ((cells[i] >> j) & 0x01 == 1 ) { 57 | return 1; 58 | } else { 59 | return 0; 60 | } 61 | } 62 | function set(int16 pos, int8 value) internal { 63 | // the linear array is held in 4 x 256 unsigned integers 64 | uint32 i = uint32(pos) / 256; 65 | uint32 j = uint32(pos) % 256; 66 | // if value is 1 then set the j-th bit 67 | if (value>0){ 68 | newcells[i] |= (1 << j); // set this bit 69 | } else { 70 | newcells[i] &= ~(1 << j); // turn off this bit 71 | } 72 | 73 | } 74 | 75 | // Overpopulation: if a living cell is surrounded by more than three living cells, it dies. 76 | // Stasis: if a living cell is surrounded by two or three living cells, it survives. 77 | // Underpopulation: if a living cell is surrounded by fewer than two living cells, it dies. 78 | // Reproduction: if a dead cell is surrounded by exactly three cells, it becomes a live cell. 79 | 80 | // step - perform a single step of the Conway's game of life 81 | function step() public { 82 | 83 | newcells = new uint256[](cells.length); 84 | // skip the edge row/col to avoid checking for out of bounds 85 | // skip some more to reduce the amount of gas needed. 86 | for (int16 row=4; row 3) { 107 | set(pos,0); 108 | } else if (count == 2 || count == 3) { 109 | set(pos,1); 110 | } else if (count < 2) { 111 | set(pos,0); 112 | } 113 | } else { // dead cell 114 | if (count == 3) { 115 | set(pos,1); 116 | } 117 | } 118 | } 119 | 120 | } 121 | cells = newcells; 122 | 123 | myblock = block.number; 124 | //emit Cells(cells); 125 | } 126 | 127 | function getCells() public view returns (uint256[] memory) { 128 | return cells; 129 | } 130 | 131 | function getMyBlock() public view returns (uint256) { 132 | return myblock; 133 | } 134 | 135 | function setCells(uint256 c0, uint256 c1, uint256 c2, uint256 c3) public { 136 | cells[0] = c0; 137 | cells[1] = c1; 138 | cells[2] = c2; 139 | cells[3] = c3; 140 | 141 | myblock = block.number; 142 | } 143 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Ethereum based Conway's Game Of Life 2 | The world's most expensive implementation of [Conway's Game of Life](https://en.wikipedia.org/wiki/Conway%27s_Game_of_Life) ever - over $2,000 per step! (Probably the slowest too!) 3 | 4 | Conway's Game of Life running as a smart contract on the Ethereum Blockchain. 5 | 6 | 7 | 8 | This application would be able to run 'forever' - so long as there was some funds in an Ethereum account that could be used to run each 'step'. 9 | 10 | However, the cost of Ethereum (and therefore 'gas') used to run smart contracts is so high that it would cost (in March 2021) over $80 just to register the smart contract, and to run a single 'step' of the game would cost over $2,000! No doubt that the code could be made more efficient and consume less resources, but hey that's just too much work for a concept app, so I have simply registered the [contract](https://kovan.etherscan.io/address/0x51B92cef4C0847EF552e4129a28d817c26a4A053) on the Kovan test network instead, and use some 'fake' Ethereum to run the system. 11 | The app is the same, but it just points to the 'Kovan' test network instead of the Ethereum mainnet. 12 | 13 | You can see it in action [here](https://nialloc.github.io/GameOfLife/) 14 | 15 | The application consists of three parts: 16 | * Front end Javascript application using the [p5js](https://p5js.org/) library which runs in your browser 17 | * A Python Flask app implemented as a google cloud function 18 | * An Ethereum smart contract written in Solidity which runs on the (Kovan) Ethereum blockchain 19 | 20 | ### p5js - client application 21 | There are just three files in the app: a very simple `index.html` to host the javascript application in [sketch.js](https://nialloc.github.io/GameOfLife/sketch.js) along with a simple `style.css` stylesheet. When started, the app requests the 32x32 grid from the blockchain (via the flask app). Every minute or so the app will trigger a 'step' in the smart contract. There are a couple of other buttons that will create a random selection on the screen, clear the screen, and add a few [gliders](https://en.wikipedia.org/wiki/Glider_(Conway%27s_Life)). You can also use the mouse to select/deselect individual cells. Press the `start new pattern` button to send your pattern to the smart contract. 22 | 23 | ### Python Flask google cloud function 24 | This started as a bog standard [Flask](https://flask.palletsprojects.com/en/1.1.x/) application, but I converted it into a google cloud function to avoid the hassle of having to host it somewhere. 25 | Most of the functionality here could have also been included in the browser-based javascript application, but because of my greater familiarity with python and my uncertainty about how to secure the private key needed to sign the solidity transactions I left it running on the server side. 26 | The ```main.py``` needs some environment variables for it to work. These are configured as part of the deployment script when pushing the flask app to the google cloud service: 27 | ``` 28 | gcloud functions deploy gameoflife \ 29 | --runtime python38 \ 30 | --trigger-http \ 31 | --allow-unauthenticated \ 32 | --env-vars-file env.yaml 33 | ``` 34 | ``` 35 | env.yaml: 36 | --- 37 | network_name: Kovan 38 | HTTPProvider: 'https://kovan.infura.io/v3/PUT-YOUR-INFURA-KEY-HERE' 39 | contract_address: '0x51B92cef4C0847EF552e4129a28d817c26a4A053' 40 | private_key: 'PUT-THE-PRIVATE-KEY-OF-YOUR-ACCOUNT-HERE' 41 | chain_id: '42' 42 | ``` 43 | ### Ethereum smart contract 44 | The smart contract was written in Solidity. I used VS Code with a [solidity extension](https://marketplace.visualstudio.com/items?itemName=JuanBlanco.solidity) that highlighted any syntax errors. 45 | The testing of the contract was done with the [Truffle/Ganache](https://www.trufflesuite.com/ganache) suite of applications, and to get it onto the blockchain I simply used the [remix](http://remix.ethereum.org) online tool with the [metamask](https://metamask.io/) browser extension. 46 | 47 | 48 | I decided that a 32 x 32 cell structure would be big enough the showcase how the game works. In order to reduce the size requirements of data to be stored on the block chain, I used an array of 4 x 256 bit unsigned integers and used this as a bit field. There are three entry points in the contract (apart from the constructor): setCells(), getCells() and step() 49 | 50 | 51 | #### Step 52 | The step function mainly consists of loop iterating through the cells, creating/removing cells according to the rules of the game. 53 | I didn't make much effort to reduce the amount of work done in order to run a single cell, so I did run foul of one specific issue - the amount of gas consumed. At times it would exceed the limits of even the test networks, and so I ignored some of the cells on the edge of the whole cell universe. 54 | It would then take about 11M gas to process it, which is just below the 12M limit for the Kovan network. 55 | 56 | ``` 57 | for (int16 row=4; row 3) { 78 | set(pos,0); 79 | } else if (count == 2 || count == 3) { 80 | set(pos,1); 81 | } else if (count < 2) { 82 | set(pos,0); 83 | } 84 | } else { // dead cell 85 | if (count == 3) { 86 | set(pos,1); 87 | } 88 | } 89 | } 90 | } 91 | ``` 92 | and the _get_ and _set_ functions are implemented as bit twiddlers. 93 | ```solidity 94 | function set(int16 pos, int8 value) internal { 95 | // the linear array is held in 4 x 256 unsigned integers 96 | uint32 i = uint32(pos) / 256; 97 | uint32 j = uint32(pos) % 256; 98 | // if value is 1 then set the j-th bit 99 | if (value>0){ 100 | newcells[i] |= (1 << j); // set this bit 101 | } else { 102 | newcells[i] &= ~(1 << j); // turn off this bit 103 | } 104 | } 105 | function get(int32 pos) view internal returns (int) { 106 | 107 | if (pos<0) { 108 | pos += rows*cols; 109 | } 110 | if (pos >= rows*cols) { 111 | pos -= rows*cols; 112 | } 113 | // make sure pos always is a valid value 114 | pos = pos % int32(rows*cols); 115 | // the linear array is held in 4 x 256 unsigned integers 116 | uint32 i = uint32(pos) / 256; 117 | uint32 j = uint32(pos) % 256; 118 | // if the j-th bit set? 119 | if ((cells[i] >> j) & 0x01 == 1 ) { 120 | return 1; 121 | } else { 122 | return 0; 123 | } 124 | } 125 | ``` 126 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | import json 2 | from flask import Flask, jsonify, request 3 | from web3 import Web3, HTTPProvider 4 | from web3.eth import Account 5 | import os 6 | 7 | # not sure if dotenv will work in google cloud (can't pip install it) 8 | try : 9 | from dotenv import load_dotenv 10 | load_dotenv() 11 | except: 12 | pass 13 | 14 | app = Flask(__name__) 15 | 16 | print('starting..') 17 | web3 = Web3(HTTPProvider(os.getenv('HTTPProvider'))) 18 | 19 | # do this a a test of connectivity 20 | print('block', web3.eth.blockNumber) 21 | 22 | GAP = int(60/4) # Kovan updates every 4 seconds, let's wait for a minute (60) 23 | 24 | compiler_contract_path = '../build/contracts/GameOfLife.json' 25 | compiler_contract_path = 'GameOfLife.json' 26 | 27 | deployed_contract_address = os.getenv('contract_address') 28 | 29 | contract_json = json.load(open(compiler_contract_path)) 30 | contract_abi = contract_json['abi'] 31 | 32 | deployed_contract_address=Web3.toChecksumAddress(deployed_contract_address) 33 | print('deployed contract address',deployed_contract_address) 34 | contract = web3.eth.contract( 35 | address=deployed_contract_address, 36 | abi=contract_abi, 37 | ) 38 | 39 | rows = 32 40 | cols = 32 41 | 42 | cells = [0 for x in range(rows*cols)] 43 | blocks = 0 44 | 45 | 46 | # use to this to print the network name to the web app 47 | network_name = os.getenv('network_name') 48 | print('network name',network_name) 49 | 50 | 51 | @app.route('/', methods=['GET','POST','OPTIONS']) 52 | def gameoflife(cmd): 53 | 54 | # just extract the full path as the decorator's dont work in a google cloud environment 55 | cmd = request.path 56 | print("cmd",cmd) 57 | if cmd =='/data': 58 | return get_data() 59 | elif cmd in ['/step','/setcells']: 60 | return do_command(cmd) 61 | else: 62 | return jsonify({'unknown cmd':cmd}) 63 | 64 | 65 | def do_command(cmd): 66 | 67 | # not sure if this particular bit is needed, but it referenced in the google cloud function docs 68 | if request.method == 'OPTIONS': 69 | # Allows GET requests from any origin with the Content-Type 70 | # header and caches preflight response for an 3600s 71 | headers = { 72 | 'Access-Control-Allow-Origin': '*', 73 | 'Access-Control-Allow-Methods': 'GET', 74 | 'Access-Control-Allow-Headers': 'Content-Type', 75 | 'Access-Control-Max-Age': '3600' 76 | } 77 | 78 | return jsonify({'status':'ok'}), 204, headers 79 | 80 | # Set CORS headers for the main request 81 | headers = { 82 | 'Access-Control-Allow-Origin': '*' 83 | } 84 | 85 | if cmd in ['/step','/setcells']: 86 | pass 87 | else : 88 | return jsonify({"unknown cmd":cmd}) 89 | 90 | 91 | private_key = os.getenv('private_key') 92 | print('private_key',private_key) 93 | 94 | acct = Account.from_key(private_key) 95 | caller_address = acct.address 96 | 97 | print('caller_address',caller_address) 98 | 99 | nonce = web3.eth.getTransactionCount(caller_address) 100 | chainId = int(os.getenv('chain_id')) 101 | 102 | gwei = 1_000_000_000 103 | gasPrice = 1 * gwei 104 | 105 | caller_balance0 = web3.eth.get_balance(caller_address) 106 | 107 | gasPrice = Web3.toWei(1, 'gwei') 108 | 109 | myblock = contract.functions.getMyBlock().call() 110 | 111 | print('myblock',myblock) 112 | 113 | if 'setcells' in cmd: 114 | print("setcells") 115 | 116 | data = request.data 117 | 118 | print('type',type(data)) 119 | print('data',data) 120 | # convert from text to dict 121 | try : 122 | cells = json.loads(data) 123 | print("cells",cells) 124 | except Exception as e: 125 | return jsonify({'status':str(e)}) 126 | 127 | # convert array into 4 x 256 bit elements 128 | try: 129 | cell4 = generate_cell4(cells['cells']) 130 | print("cell4",cell4) 131 | except Exception as e: 132 | return jsonify({'code':str(e)}) 133 | 134 | 135 | txn = contract.functions.setCells(cell4[0],cell4[1],cell4[2],cell4[3]).buildTransaction( 136 | { 137 | 'chainId' : chainId, 138 | 'nonce': nonce, 139 | 'gasPrice': gasPrice, 140 | } 141 | ) 142 | else : 143 | print("starting step..") 144 | 145 | # check when the last step was done 146 | # compare to current block 147 | # only execute the step if the time is greater than one minute 148 | myblock = contract.functions.getMyBlock().call() 149 | current = web3.eth.blockNumber 150 | 151 | target = myblock + GAP 152 | print(f"current {current} target {target}") 153 | 154 | if current < target : 155 | return jsonify({'code':f'current block is {current}, skipping until block {target}'}) 156 | 157 | txn = contract.functions.step().buildTransaction( 158 | { 159 | 'chainId' : chainId, 160 | 'nonce': nonce, 161 | 'gasPrice': gasPrice, 162 | } 163 | ) 164 | print('txn',txn) 165 | 166 | 167 | signed_txn = web3.eth.account.sign_transaction( 168 | txn, 169 | private_key=private_key 170 | ) 171 | 172 | try: 173 | result = web3.eth.sendRawTransaction(signed_txn.rawTransaction) 174 | print('result',result) 175 | txn['status'] = 'ok' 176 | except Exception as e: 177 | print(type(e)) 178 | print(e) 179 | txn = {'status':str(e)} 180 | 181 | response = txn 182 | 183 | caller_balance1 = web3.eth.get_balance(caller_address) 184 | 185 | 186 | response['block'] = web3.eth.blockNumber 187 | response['balance_before'] = caller_balance0 188 | response['balance_after'] = caller_balance1 189 | response['cost'] = caller_balance0 - caller_balance1 190 | response['myblock'] = contract.functions.getMyBlock().call() 191 | 192 | return jsonify(response), 200 , headers 193 | 194 | 195 | 196 | # take an array of 4 x uint256 and convert into a linear array of 1s and 0s. 197 | def generate_cells(cell4): 198 | cells = [0 for x in range(rows*cols)] 199 | index = 0 200 | for row in range(rows): 201 | for col in range(cols): 202 | pos = (col + row*cols) % (rows*cols) 203 | i = int(pos / 256) 204 | j = pos % 256 205 | #print(row,col,pos,i,j,cells[i]) 206 | if (cell4[i] >> j) & 0x01: 207 | cells[index] = 1 208 | else: 209 | cells[index] = 0 210 | 211 | index += 1 212 | return cells 213 | 214 | # take an array of 1s and 0s and create an 4 x uint256 array 215 | def generate_cell4(cells): 216 | cell4 = [0,0,0,0] 217 | 218 | for row in range(rows): 219 | for col in range(cols): 220 | pos = (col + row*cols) % (rows*cols) 221 | i = int(pos / 256) 222 | j = pos % 256 223 | if cells[pos] == 1: 224 | cell4[i] |= ( 1 << j) 225 | 226 | return cell4 227 | 228 | def get_data(): 229 | 230 | 231 | # not sure if this particular bit is needed, but it referenced in the google cloud function docs 232 | if request.method == 'OPTIONS': 233 | # Allows GET requests from any origin with the Content-Type 234 | # header and caches preflight response for an 3600s 235 | headers = { 236 | 'Access-Control-Allow-Origin': '*', 237 | 'Access-Control-Allow-Methods': 'GET', 238 | 'Access-Control-Allow-Headers': 'Content-Type', 239 | 'Access-Control-Max-Age': '3600' 240 | } 241 | 242 | return ('', 204, headers) 243 | 244 | # Set CORS headers for the main request 245 | headers = { 246 | 'Access-Control-Allow-Origin': '*' 247 | } 248 | 249 | cell4 = contract.functions.getCells().call() 250 | print("cells ", cell4) 251 | 252 | cells = generate_cells(cell4) 253 | 254 | private_key = os.getenv('private_key') 255 | print('private_key',private_key) 256 | 257 | acct = Account.from_key(private_key) 258 | caller_address = acct.address 259 | caller_balance = web3.eth.get_balance(caller_address) 260 | 261 | # convert balance into ether.. 262 | caller_balance = str(web3.fromWei(caller_balance,'ether')) 263 | 264 | block = web3.eth.blockNumber 265 | myblock = contract.functions.getMyBlock().call() 266 | 267 | response = { 268 | 'status' : 'ok', 269 | 'network' : network_name, 270 | 'block' : block, 271 | 'myblock' : myblock, 272 | 'caller_address' : caller_address, 273 | 'caller_balance' : caller_balance, 274 | 'contract_address': deployed_contract_address, 275 | 'contract_balance' : web3.eth.get_balance(deployed_contract_address), 276 | 'target' : myblock + GAP, 277 | 'rows' : rows, 278 | 'cols' : cols, 279 | 'cells' : cells 280 | 281 | } 282 | return jsonify(response), 200 , headers 283 | --------------------------------------------------------------------------------