├── .gitignore ├── README.md ├── app ├── contract.py ├── files │ ├── Python1.txt │ ├── Python2.txt │ └── Solidity.txt ├── server.py ├── storage.sol └── templates │ ├── check.html │ ├── info.html │ ├── upload.html │ └── uploaded.html ├── blockchain ├── CustomGenesis.json ├── init.sh └── start.sh └── example └── deploy_contract_and_test.py /.gitignore: -------------------------------------------------------------------------------- 1 | .python-version 2 | chain-data/ 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # File on blockchain 2 | 3 | |Index |Upload result|File info| 4 | |:-------:|:-----------:|:-------:| 5 | |![upload]|![result] |![info] | 6 | 7 | 8 | |Check |Exist |None | 9 | |:------:|:-----:|:------:| 10 | |![check]|![true]|![false]| 11 | 12 | 13 | 14 | ## Initialize blockchain environment 15 | 1. `git clone https://github.com/JungWinter/file-on-blockchain` 16 | 2. `cd file-on-blockchain/blockchain` 17 | 3. `./init.sh` 18 | 4. `./start.sh` 19 | 20 | ## Create account for deploying 21 | 1. Open another terminal and move to `file-on-blockchain/blockchain` 22 | 2. `geth attach ./chain-data/geth.ipc` 23 | 3. In console, `personal.newAccount()` and enter password 24 | 4. `miner.start()` and `miner.stop()` 25 | 26 | ## Deploy simple contract 27 | 1. `cd file-on-blockchain/example` 28 | 2. `python deploy_contract_and_test.py` 29 | 3. :tada: 30 | 31 | ## Run server 32 | 1. `cd file-on-blockchain/app` 33 | 2. `python server.py` 34 | 3. :star: 35 | 36 | 37 | [upload]: https://raw.githubusercontent.com/JungWinter/JungWinter.github.io/master/images/20171203/02_upload_file.png 38 | [result]: https://raw.githubusercontent.com/JungWinter/JungWinter.github.io/master/images/20171203/03_upload_result.png 39 | [info]: https://raw.githubusercontent.com/JungWinter/JungWinter.github.io/master/images/20171203/04_info.png 40 | [check]:https://raw.githubusercontent.com/JungWinter/JungWinter.github.io/master/images/20171203/05_check.png 41 | [true]:https://raw.githubusercontent.com/JungWinter/JungWinter.github.io/master/images/20171203/06_true.png 42 | [false]: https://raw.githubusercontent.com/JungWinter/JungWinter.github.io/master/images/20171203/08_false.png 43 | -------------------------------------------------------------------------------- /app/contract.py: -------------------------------------------------------------------------------- 1 | import time 2 | from logzero import logger 3 | from web3 import Web3, HTTPProvider 4 | from solc import compile_files 5 | 6 | # Web3 setting 7 | rpc_url = "http://localhost:8545" 8 | w3 = Web3(HTTPProvider(rpc_url)) 9 | # w3 = Web3(IPCProvider("./chain-data/geth.ipc")) 10 | w3.personal.unlockAccount(w3.eth.accounts[0], "test", 0) 11 | 12 | 13 | def deploy(contract_file_name, contract_name): 14 | 15 | compiled_sol = compile_files([contract_file_name]) 16 | interface = compiled_sol['{}:{}'.format(contract_file_name, 17 | contract_name)] 18 | 19 | contract = w3.eth.contract(abi=interface['abi'], 20 | bytecode=interface['bin'], 21 | bytecode_runtime=interface['bin-runtime']) 22 | 23 | # Deploy 24 | tx_hash = contract.deploy(transaction={'from': w3.eth.accounts[0]}) 25 | 26 | logger.info("tx_hash: {}".format(tx_hash)) 27 | 28 | # Mining 29 | w3.miner.start(2) 30 | time.sleep(5) 31 | w3.miner.stop() 32 | 33 | # Contract address 34 | tx_receipt = w3.eth.getTransactionReceipt(tx_hash) 35 | contract_address = tx_receipt['contractAddress'] 36 | logger.info("contract_address: {}".format(contract_address)) 37 | # Use contract 38 | contract_instance = contract(contract_address) 39 | return contract_instance 40 | 41 | 42 | def upload(ins, filehash, filename, filesize, owner): 43 | logger.info("Call upload") 44 | logger.info("{}, {}, {}, {}".format(owner, filehash, filename, filesize)) 45 | transaction = ins.transact({"from": w3.eth.accounts[0]}) 46 | tx_hash = transaction.upload(owner, 47 | filehash, 48 | filename, 49 | filesize) 50 | 51 | logger.info("Wait Uploading: {}".format(tx_hash)) 52 | 53 | w3.miner.start(2) 54 | time.sleep(5) 55 | w3.miner.stop() 56 | 57 | logger.info("Finish Uploading") 58 | 59 | 60 | def get_file_info(ins, filehash): 61 | logger.info("Call get_file_info") 62 | logger.debug(filehash) 63 | file_info = ins.call().getFileInfo(filehash) 64 | logger.debug(file_info) 65 | return file_info 66 | 67 | 68 | def check_file_exist(ins, filehash): 69 | logger.info("Call check_file_exist") 70 | logger.debug(filehash) 71 | is_exist = ins.call().checkExist(filehash) 72 | logger.debug(is_exist) 73 | return is_exist 74 | -------------------------------------------------------------------------------- /app/files/Python1.txt: -------------------------------------------------------------------------------- 1 | Python 2 | -------------------------------------------------------------------------------- /app/files/Python2.txt: -------------------------------------------------------------------------------- 1 | Python 2 | -------------------------------------------------------------------------------- /app/files/Solidity.txt: -------------------------------------------------------------------------------- 1 | Solidity 2 | -------------------------------------------------------------------------------- /app/server.py: -------------------------------------------------------------------------------- 1 | import os 2 | import hashlib 3 | import contract 4 | from flask import (Flask, request, redirect, url_for, send_from_directory, 5 | render_template) 6 | from werkzeug import secure_filename 7 | from datetime import datetime 8 | 9 | UPLOAD_FOLDER = './files/' 10 | ALLOWED_EXTENSIONS = set(['txt', 'pdf', 'png', 'jpg', 'jpeg', 'gif']) 11 | INS = None 12 | 13 | app = Flask(__name__) 14 | app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER 15 | app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024 16 | 17 | 18 | def allowed_file(filename): 19 | return '.' in filename and \ 20 | filename.rsplit('.', 1)[1] in ALLOWED_EXTENSIONS 21 | 22 | 23 | @app.route('/', methods=['GET', 'POST']) 24 | def upload_file(): 25 | if request.method == 'POST': 26 | file = request.files['file'] 27 | if file and allowed_file(file.filename): 28 | filename = secure_filename(file.filename) 29 | path = os.path.join(app.config['UPLOAD_FOLDER'], filename) 30 | file.save(path) 31 | 32 | owner = request.form["owner"] 33 | filehash = sha256_checksum(path) 34 | filesize = os.path.getsize(path) 35 | upload_on_blockchain(filehash=filehash, 36 | filename=filename, 37 | filesize=filesize, 38 | owner=owner) 39 | 40 | return redirect(url_for('uploaded_file', 41 | filename=filename, 42 | filehash=filehash)) 43 | return render_template("upload.html") 44 | 45 | 46 | @app.route('/uploader') 47 | def uploaded_file(): 48 | filename = request.args['filename'] 49 | filehash = request.args['filehash'] 50 | return render_template("uploaded.html", 51 | filename=filename, 52 | filehash=filehash) 53 | 54 | 55 | @app.route('/uploads/') 56 | def get_file(filename): 57 | return send_from_directory(app.config['UPLOAD_FOLDER'], filename) 58 | 59 | 60 | @app.route('/info/') 61 | def get_file_info(filehash): 62 | file_info = contract.get_file_info(INS, filehash) 63 | info = { 64 | "file_name": file_info[0], 65 | "upload_date": datetime.fromtimestamp(file_info[1]), 66 | "file_size": file_info[2], 67 | } 68 | return render_template("info.html", 69 | filehash=filehash, 70 | info=info) 71 | 72 | 73 | @app.route('/check', methods=['POST']) 74 | def check_file(): 75 | file = request.files['file'] 76 | if file and allowed_file(file.filename): 77 | filename = secure_filename(file.filename) 78 | path = os.path.join(app.config['UPLOAD_FOLDER'], filename) 79 | file.save(path) 80 | filehash = sha256_checksum(path) 81 | is_exist = contract.check_file_exist(INS, filehash) 82 | return render_template('check.html', 83 | is_exist=is_exist, 84 | filehash=filehash, 85 | filename=filename) 86 | 87 | 88 | # TODO: Use celery 89 | def upload_on_blockchain(**kwargs): 90 | contract.upload(INS, **kwargs) 91 | 92 | 93 | def sha256_checksum(filename, block_size=65536): 94 | sha256 = hashlib.sha256() 95 | with open(filename, 'rb') as f: 96 | for block in iter(lambda: f.read(block_size), b''): 97 | sha256.update(block) 98 | return sha256.hexdigest() 99 | 100 | 101 | if __name__ == "__main__": 102 | INS = contract.deploy("storage.sol", "FileHashStorage") 103 | app.run(debug=False) 104 | -------------------------------------------------------------------------------- /app/storage.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.16; 2 | 3 | contract Owned { 4 | address public owner; 5 | 6 | function Owned() { 7 | owner = msg.sender; 8 | } 9 | 10 | modifier onlyOwner { 11 | require(msg.sender == owner); 12 | _; 13 | } 14 | 15 | function transferOwnership(address newOwner) public onlyOwner { 16 | owner = newOwner; 17 | } 18 | 19 | function getOwner() public constant returns (address) { 20 | return owner; 21 | } 22 | } 23 | 24 | contract FileHashStorage is Owned { 25 | struct File { 26 | string name; 27 | uint uploadDate; 28 | uint size; 29 | } 30 | mapping(string => File) private files; 31 | mapping(string => string[]) private fileOwners; 32 | string[] public owners; 33 | uint public ownerID = 0; 34 | 35 | /* 36 | owners = ["Jung", "Park", ...] 37 | fileOwners["Jung"] = ["0xABCD1234", "0xDEAD4321"] // Hashed file 38 | files["0xABCD1234"] = { 39 | name: "test_file.pdf", 40 | registerDate: 17203124, // Unix timestamp 41 | size: 154000 // Bytes 42 | } 43 | */ 44 | 45 | event Upload(string personName, string fileHash, File file); 46 | 47 | 48 | function upload(string personName, string fileHash, string fileName, uint fileSize) onlyOwner public { 49 | ownerID++; 50 | owners.push(personName); 51 | File memory f = File(fileName, now, fileSize); 52 | files[fileHash] = f; 53 | Upload(personName, fileHash, f); 54 | } 55 | 56 | function checkExist(string fileHash) onlyOwner public view returns (bool) { 57 | if (files[fileHash].size > 0) { 58 | return true; 59 | } 60 | return false; 61 | } 62 | 63 | function getOwnerName(uint id) onlyOwner public view returns (string) { 64 | return owners[id]; 65 | } 66 | 67 | function getFileInfo(string fileHash) onlyOwner public view returns (string, uint, uint) { 68 | return (files[fileHash].name, files[fileHash].uploadDate, files[fileHash].size); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /app/templates/check.html: -------------------------------------------------------------------------------- 1 | 2 | Check File 3 | 4 | 5 |

Result

6 |

7 | {% if is_exist %} 8 |

{{ filename }} exist in blockchain!.

9 | Show file info
10 | Get file
11 | {% else %} 12 |

{{ filename }} doesn't exist in blockchain!.

13 | {% endif %} 14 |

15 | 16 | -------------------------------------------------------------------------------- /app/templates/info.html: -------------------------------------------------------------------------------- 1 | 2 | File info 3 | 4 | 5 |

File Info

6 |

7 | {{ filehash }} is {{ info.file_name }}.
8 | GET FILE 9 |

10 |

Upload Date: {{ info.upload_date }}

11 |

File Size: {{ info.file_size }} bytes

12 | 13 | -------------------------------------------------------------------------------- /app/templates/upload.html: -------------------------------------------------------------------------------- 1 | 2 | Upload new File 3 | 4 | 5 |

6 |

Upload new File

7 |
8 |

9 | 10 | 11 |

12 |

13 | 14 | 15 |

16 |
17 |

18 |

19 |

Check File

20 |
21 |

22 | 23 | 24 |

25 |
26 |

27 | 28 | -------------------------------------------------------------------------------- /app/templates/uploaded.html: -------------------------------------------------------------------------------- 1 | 2 | Uploaded 3 | 4 | 5 |

Uploaded successfully

6 |

7 | {{ filename }} is uploaded successfully.
8 | GET FILE 9 |

10 |

file's hash is {{ filehash }}

11 | 12 | -------------------------------------------------------------------------------- /blockchain/CustomGenesis.json: -------------------------------------------------------------------------------- 1 | { 2 | "config": { 3 | "chainId": 42, 4 | "homesteadBlock": 0, 5 | "eip155Block": 0, 6 | "eip158Block": 0 7 | }, 8 | "alloc": {}, 9 | "coinbase" : "0x0000000000000000000000000000000000000000", 10 | "difficulty" : "0x20000", 11 | "extraData" : "", 12 | "gasLimit" : "0xffefd8", 13 | "nonce" : "0x0000000032627504", 14 | "mixhash" : "0x0000000000000000000000000000000000000000000000000000000000000000", 15 | "parentHash" : "0x0000000000000000000000000000000000000000000000000000000000000000", 16 | "timestamp" : "0x00" 17 | } 18 | -------------------------------------------------------------------------------- /blockchain/init.sh: -------------------------------------------------------------------------------- 1 | rm -rf chain-data/* 2 | geth init CustomGenesis.json --datadir chain-data 3 | -------------------------------------------------------------------------------- /blockchain/start.sh: -------------------------------------------------------------------------------- 1 | geth --rpc --rpccorsdomain "*" --nodiscover --datadir "chain-data" --port "30303" --rpcapi "db,eth,net,web3,personal,admin,miner,debug,txpool" --networkid 1001012 2 | -------------------------------------------------------------------------------- /example/deploy_contract_and_test.py: -------------------------------------------------------------------------------- 1 | import time 2 | from web3 import Web3, HTTPProvider 3 | from solc import compile_source 4 | 5 | # Code 6 | contract_source_code = ''' 7 | contract Greeter { 8 | string public greeting; 9 | 10 | function Greeter() { 11 | greeting = 'Hello'; 12 | } 13 | 14 | function setGreeting(string _greeting) public { 15 | greeting = _greeting; 16 | } 17 | 18 | function greet() constant returns (string) { 19 | return greeting; 20 | } 21 | } 22 | ''' 23 | 24 | # Web3 setting 25 | rpc_url = "http://localhost:8545" 26 | w3 = Web3(HTTPProvider(rpc_url)) 27 | # w3 = Web3(IPCProvider("./chain-data/geth.ipc")) 28 | w3.personal.unlockAccount(w3.eth.accounts[0], "test", 0) 29 | 30 | 31 | # Compile 32 | compiled_sol = compile_source(contract_source_code) 33 | contract_interface = compiled_sol[':Greeter'] 34 | 35 | contract = w3.eth.contract(abi=contract_interface['abi'], 36 | bytecode=contract_interface['bin'], 37 | bytecode_runtime=contract_interface['bin-runtime']) 38 | 39 | # Deploy 40 | tx_hash = contract.deploy(transaction={'from': w3.eth.accounts[0]}) 41 | 42 | print("tx_hash: {}".format(tx_hash)) 43 | print("Finish deploying") 44 | 45 | # Mining 46 | w3.miner.start(2) 47 | time.sleep(5) 48 | w3.miner.stop() 49 | 50 | # Contract address 51 | tx_receipt = w3.eth.getTransactionReceipt(tx_hash) 52 | contract_address = tx_receipt['contractAddress'] 53 | 54 | # Use contract 55 | contract_instance = contract(contract_address) 56 | # Get 57 | print('Contract value: {}'.format(contract_instance.call().greet())) 58 | # Set 59 | contract_instance.transact({"from": w3.eth.accounts[0]}).setGreeting("WinterJ") 60 | print('Setting value to: WinterJ') 61 | 62 | # Mining 63 | w3.miner.start(2) 64 | time.sleep(5) 65 | w3.miner.stop() 66 | 67 | # Get 68 | print('Contract value: {}'.format(contract_instance.call().greet())) 69 | --------------------------------------------------------------------------------