diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6c8c47f --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +*.swp +build/ +package_delivery_contract.js diff --git a/LICENSE b/LICENSE index 261eeb9..7a9b3cb 100644 --- a/LICENSE +++ b/LICENSE @@ -186,7 +186,7 @@ same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright [yyyy] [name of copyright owner] + Copyright 2018 Ondrej Mular Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..dc2e404 --- /dev/null +++ b/Makefile @@ -0,0 +1,60 @@ +DOCKER=docker +NETWORK_NAME=eth-testname +DOCKER_RUN_ARGS=--detach --tty --interactive --network $(NETWORK_NAME) +BOOTNODE_IMG=eth-bootnode +NODE_IMG=eth-node +NODES_NUM=3 +BUILD_DIR=./build +CONTRACT_SOL=package_delivery.sol + +all: run + +run: run_bootnode run_nodes + +run_bootnode: setup build_bootnode clean_bootnode + $(DOCKER) run $(DOCKER_RUN_ARGS) --name eth-bootnode $(BOOTNODE_IMG) + +run_nodes: setup build_node clean_nodes + for i in $$(seq 1 $(NODES_NUM)); do \ + $(DOCKER) run $(DOCKER_RUN_ARGS) --name eth-node$$i --env "eth_acc=acc$$i" $(NODE_IMG);\ + done + +setup: setup_network build_contract + +setup_network: + -$(DOCKER) network create --internal $(NETWORK_NAME) + +build: build_bootnode build_node build_contract + +build_contract: $(CONTRACT_SOL) + mkdir -p $(BUILD_DIR) + docker run -v $$(pwd):/_input:ro -v $$(cd $(BUILD_DIR); pwd):/_output:rw -i ethereum/solc:0.4.23 --combined-json abi,bin --overwrite -o /_output /_input/$(CONTRACT_SOL) + ./create_crontract_script.py + +build_base: baseimg.docker + $(DOCKER) build -f baseimg.docker -t eth-base . + +build_bootnode: build_base bootnode.docker + $(DOCKER) build -f bootnode.docker -t $(BOOTNODE_IMG) . + +build_node: build_base node.docker + $(DOCKER) build -f node.docker -t $(NODE_IMG) . + +clean: clean_contract clean_containers clean_images clean_network + +clean_contract: + -rm -rf package_delivery_contract.js $(BUILD_DIR) + +clean_images: + -$(DOCKER) rmi eth-base $(BOOTNODE_IMG) $(NODE_IMG) + +clean_network: + -$(DOCKER) network rm $(NETWORK_NAME) + +clean_containers: clean_bootnode clean_nodes + +clean_bootnode: + -$(DOCKER) rm -f eth-bootnode + +clean_nodes: + -$(DOCKER) rm -f $$(for i in $$(seq 1 $(NODES_NUM)); do echo "eth-node$$i"; done) diff --git a/baseimg.docker b/baseimg.docker new file mode 100644 index 0000000..4d77895 --- /dev/null +++ b/baseimg.docker @@ -0,0 +1,8 @@ +FROM fedora:27 + +ENV geth_pkg geth-alltools-linux-amd64-1.8.6-12683fec +ENV geth_home /root/ +ENV bootnode_name eth-bootnode + +RUN dnf install -y wget; dnf clean all +RUN wget -nv https://gethstore.blob.core.windows.net/builds/${geth_pkg}.tar.gz && tar -xvzf ${geth_pkg}.tar.gz && rm -f ${geth_pkg}.tar.gz && mv ${geth_pkg} ${geth_home}/geth diff --git a/boot.key b/boot.key new file mode 100644 index 0000000..a114fd8 --- /dev/null +++ b/boot.key @@ -0,0 +1 @@ +e9aa0425ad62b9eecd8c0f0861a17f547ba597bb7c7622d24e75ec053d4ec100 \ No newline at end of file diff --git a/bootnode.docker b/bootnode.docker new file mode 100644 index 0000000..bf10d3b --- /dev/null +++ b/bootnode.docker @@ -0,0 +1,7 @@ +FROM eth-base + +# geth/bootnode -genkey boot.key +COPY boot.key ${geth_home}/boot.key +COPY start_bootnode.sh /bin/start_bootnode + +CMD ["/bin/start_bootnode"] diff --git a/bootnode_id b/bootnode_id new file mode 100644 index 0000000..8e47188 --- /dev/null +++ b/bootnode_id @@ -0,0 +1 @@ +91884f0f60ea2347e16abcc158409271cc011b4d933142dd1ffb2edd920373de9a2cc95ea21944dd4d89b347bb8579b3f6a22cd8ebdad8f9ccd66135b762a567 diff --git a/create_crontract_script.py b/create_crontract_script.py new file mode 100755 index 0000000..2da03bc --- /dev/null +++ b/create_crontract_script.py @@ -0,0 +1,14 @@ +#!/usr/bin/env python + +import json + +contract = json.load( + open("./build/combined.json") +)["contracts"]["/_input/package_delivery.sol:PackageDelivery"] +with open("package_delivery_contract.js", "w+") as f: + f.write("""\ +var packageDeliveryAbi = {abi}; +var packageDeliveryCode = '0x{code}'; + +""".format(abi=contract["abi"], code=contract["bin"]) + ) diff --git a/genesis.json b/genesis.json new file mode 100644 index 0000000..43e8d24 --- /dev/null +++ b/genesis.json @@ -0,0 +1,35 @@ +{ + "config": { + "chainId": 12345, + "homesteadBlock": 1, + "eip150Block": 2, + "eip150Hash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "eip155Block": 3, + "eip158Block": 3, + "byzantiumBlock": 4, + "clique": { + "period": 5, + "epoch": 30000 + } + }, + "nonce": "0x0", + "extraData": "0x0000000000000000000000000000000000000000000000000000000000000000830de593b42d3a007e19553c68f0f4c95e0e6e8f92f4f00f2f5355f5ec1c55512118f1fa537b1864f27292834a6b7129e5ad00e264062c978c48d7050000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "gasLimit": "0x47b760000", + "difficulty": "0x1", + "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "coinbase": "0x0000000000000000000000000000000000000000", + "alloc": { + "830de593b42d3a007e19553c68f0f4c95e0e6e8f": { + "balance": "0x200000000000000000000000000000000000000000000000000000000000000" + }, + "92f4f00f2f5355f5ec1c55512118f1fa537b1864": { + "balance": "0x200000000000000000000000000000000000000000000000000000000000000" + }, + "f27292834a6b7129e5ad00e264062c978c48d705": { + "balance": "0x200000000000000000000000000000000000000000000000000000000000000" + } + }, + "number": "0x0", + "gasUsed": "0x0", + "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000" +} diff --git a/keystore/830de593b42d3a007e19553c68f0f4c95e0e6e8f b/keystore/830de593b42d3a007e19553c68f0f4c95e0e6e8f new file mode 100644 index 0000000..f9e07b9 --- /dev/null +++ b/keystore/830de593b42d3a007e19553c68f0f4c95e0e6e8f @@ -0,0 +1 @@ +{"address":"830de593b42d3a007e19553c68f0f4c95e0e6e8f","crypto":{"cipher":"aes-128-ctr","ciphertext":"ccccee5012a1c4d60776d2847bb2c99a7b6aa5b89bb27813c13a02b31c85077b","cipherparams":{"iv":"d97fd2beacb3c86a2753bfa7fb87679c"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":262144,"p":1,"r":8,"salt":"5eae42bba2927111d395b2354e9f3e44f589538487e7d2d7bad5d6ff4c02938a"},"mac":"710541e740afb4d61b902296a39c3e58e76a5bfd761b9428b47bc8fb057f191d"},"id":"49a3b284-0715-4f24-9171-a019e16ad015","version":3} \ No newline at end of file diff --git a/keystore/92f4f00f2f5355f5ec1c55512118f1fa537b1864 b/keystore/92f4f00f2f5355f5ec1c55512118f1fa537b1864 new file mode 100644 index 0000000..d5cc315 --- /dev/null +++ b/keystore/92f4f00f2f5355f5ec1c55512118f1fa537b1864 @@ -0,0 +1 @@ +{"address":"92f4f00f2f5355f5ec1c55512118f1fa537b1864","crypto":{"cipher":"aes-128-ctr","ciphertext":"490a953a0775cc7219ce7a2387f1ae228ca99acc9bc2f9d69d6e7948ee628675","cipherparams":{"iv":"958f9bacb98e83f7b5e4291b5c029039"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":262144,"p":1,"r":8,"salt":"eca9045f389ef34c2f25add6f4c3bd1bbeb1bdb2381cabbef11b6be9c6c2d126"},"mac":"69cc13232e6c5830ac5b4135b4207916f48c6bf3e88f55e2b6f0fa57520825e6"},"id":"01adc11d-3151-46a4-9a6e-ee2e008d810b","version":3} \ No newline at end of file diff --git a/keystore/f27292834a6b7129e5ad00e264062c978c48d705 b/keystore/f27292834a6b7129e5ad00e264062c978c48d705 new file mode 100644 index 0000000..bd75d86 --- /dev/null +++ b/keystore/f27292834a6b7129e5ad00e264062c978c48d705 @@ -0,0 +1 @@ +{"address":"f27292834a6b7129e5ad00e264062c978c48d705","crypto":{"cipher":"aes-128-ctr","ciphertext":"19e549e155b54db7367ddb8ae85f4299ccbe5b8da5331b069c78fe0cc57ede0e","cipherparams":{"iv":"db8a4b6de73f9d6a52ba4014ebe405b1"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":262144,"p":1,"r":8,"salt":"3697350ba2d855a397e470d131c8ba32dfcc997fb7e2453504970e2f7bfb10d4"},"mac":"adfc8eafdb5941b0aabcb29edf1154d4eefa20a34c6b17afbce90b0e01f2c32f"},"id":"f54fd5c0-7a76-4d63-b7c1-4db7b680d0a4","version":3} \ No newline at end of file diff --git a/node.docker b/node.docker new file mode 100644 index 0000000..0781210 --- /dev/null +++ b/node.docker @@ -0,0 +1,13 @@ +FROM eth-base + +RUN mkdir -p ${geth_home}/chain_data + +COPY start_node.sh /bin/start_node +COPY genesis.json ${geth_home}/genesis.json +COPY keystore ${geth_home}/keystore +COPY bootnode_id ${geth_home}/bootnode_id +COPY password ${geth_home}/password +COPY package_delivery_contract.js ${geth_home}/package_delivery_contract.js +COPY package_delivery.js ${geth_home}/package_delivery.js + +CMD ["/bin/start_node"] diff --git a/package_delivery.js b/package_delivery.js new file mode 100644 index 0000000..fbdc63c --- /dev/null +++ b/package_delivery.js @@ -0,0 +1,38 @@ +// requires defined packageDeliveryAbi and packageDeliveryCode variables + +var acc1 = "0x92f4f00f2f5355f5ec1c55512118f1fa537b1864"; +var acc2 = "0xf27292834a6b7129e5ad00e264062c978c48d705"; +var acc3 = "0x830de593b42d3a007e19553c68f0f4c95e0e6e8f"; + +function new_pkg_delivery_contract(carrier_addr, recipient_addr) { + return web3.eth.contract(packageDeliveryAbi).new( + carrier_addr, + recipient_addr, + { + from: web3.eth.accounts[0], + data: packageDeliveryCode, + gas: '4700000' + }, + function (e, contract){ + console.log(e, contract); + if (typeof contract.address !== 'undefined') { + console.log( + 'Contract mined! address: ' + contract.address + ' transactionHash: ' + contract.transactionHash + ); + } + } + ) +} + +function get_existing_pkg_delivery_contract(addr) { + return web3.eth.contract(packageDeliveryAbi).at(addr); +} + +function send_tx(arg) { + return arg.sendTransaction({from: eth.accounts[0]}) +} + +function show_status(obj) { + console.log("Process status: " + obj.get_process_state()); + console.log("Package delivery status: " + obj.get_status()); +} diff --git a/package_delivery.sol b/package_delivery.sol new file mode 100644 index 0000000..91e3f6a --- /dev/null +++ b/package_delivery.sol @@ -0,0 +1,158 @@ +pragma solidity ^0.4.23; + +contract Process { + enum State { + Initiated, // 0 + InProgress, // 1 + Ended // 2 + } + State public state; + + modifier is_in_progress() { + require(state == State.InProgress, "Process is not in progress"); + _; + } + + constructor() public { + state = State.Initiated; + } + + function start() internal { + require(state == State.Initiated, "Process has been already started"); + state = State.InProgress; + } + + function end() internal is_in_progress() { + state = State.Ended; + } + + function get_process_state() public view returns(State) { + return state; + } +} + +contract PackageDelivery is Process { + address public sender; + address public carrier; + address public recipient; + uint failed_deliveries; + uint delivered_confirmation_until; + // set to lower timeout for testing + uint delivered_confirmation_timeout = 60; // in seconds + + enum PackageDeliveryState { + Preparing, // 0 + HandoverToCarrier, // 1 + InDelivery, // 2 + HandoverToRecipient, // 3 + Delivered, // 4 + ReturningToSender, // 5 + HandoverToSender, // 6 + Returned // 7 + } + + PackageDeliveryState package_state; + + modifier allowed_actor(address _actor) { + require(msg.sender == _actor, "Not authorized"); + _; + } + + modifier package_in_state(PackageDeliveryState _state) { + require(_state == package_state, "Invalid action"); + _; + } + + constructor(address _carrier, address _recipient) public { + failed_deliveries = 0; + sender = msg.sender; + carrier = _carrier; + recipient = _recipient; + start(); + package_state = PackageDeliveryState.Preparing; + } + + function get_status() public view returns (PackageDeliveryState) { + return package_state; + } + + function hand_over_to_carrier() + public + is_in_progress() + allowed_actor(sender) + package_in_state(PackageDeliveryState.Preparing) + { + package_state = PackageDeliveryState.HandoverToCarrier; + } + + function accept_package_for_delivery() + public + is_in_progress() + allowed_actor(carrier) + package_in_state(PackageDeliveryState.HandoverToCarrier) + { + package_state = PackageDeliveryState.InDelivery; + } + + function hand_over_to_recipient() + public + is_in_progress() + allowed_actor(carrier) + package_in_state(PackageDeliveryState.InDelivery) + { + package_state = PackageDeliveryState.HandoverToRecipient; + delivered_confirmation_until = now + delivered_confirmation_timeout; + } + + function accept_package() + public + is_in_progress() + allowed_actor(recipient) + package_in_state(PackageDeliveryState.HandoverToRecipient) + { + require( + now <= delivered_confirmation_until, + "Delivery confirmation timed out." + ); + package_state = PackageDeliveryState.Delivered; + end(); + } + + function unable_to_deliver() + public + is_in_progress() + allowed_actor(carrier) + package_in_state(PackageDeliveryState.HandoverToRecipient) + { + require( + now > delivered_confirmation_until, + "Delivery confirmation didn't time out yet." + ); + failed_deliveries++; + if (failed_deliveries >= 3) { + package_state = PackageDeliveryState.ReturningToSender; + } else { + package_state = PackageDeliveryState.InDelivery; + } + } + + function return_to_sender() + public + is_in_progress() + allowed_actor(carrier) + package_in_state(PackageDeliveryState.ReturningToSender) + { + package_state = PackageDeliveryState.HandoverToSender; + } + + function confirm_pkg_return() + public + is_in_progress() + allowed_actor(sender) + package_in_state(PackageDeliveryState.HandoverToSender) + { + package_state = PackageDeliveryState.Returned; + end(); + } +} + diff --git a/password b/password new file mode 100644 index 0000000..81c545e --- /dev/null +++ b/password @@ -0,0 +1 @@ +1234 diff --git a/start_bootnode.sh b/start_bootnode.sh new file mode 100755 index 0000000..955b730 --- /dev/null +++ b/start_bootnode.sh @@ -0,0 +1,12 @@ +#!/bin/sh + +BOOTNODE_IP=$(getent hosts ${bootnode_name} | awk '{print $1;}') + +echo "running: ${geth_home}/geth/bootnode \ + -nodekey ${geth_home}/boot.key \ + -addr ${BOOTNODE_IP}:30301" +${geth_home}/geth/bootnode \ + -nodekey ${geth_home}/boot.key \ + -addr ${BOOTNODE_IP}:30301 + + diff --git a/start_node.sh b/start_node.sh new file mode 100755 index 0000000..df4d4e4 --- /dev/null +++ b/start_node.sh @@ -0,0 +1,58 @@ +#!/bin/sh + +BOOTNODE_IP=$(getent hosts ${bootnode_name} | awk '{print $1;}') +CHAIN_DATA_DIR=$geth_home/chain_dir/ +GETH_BIN=${geth_home}/geth/geth +CHAIN_KEYSTORE=$CHAIN_DATA_DIR/keystore/ +NODE_IP=$(awk 'END{print $1}' /etc/hosts) + +mkdir -p $CHAIN_KEYSTORE + +case $eth_acc in + acc1) + ACC=92f4f00f2f5355f5ec1c55512118f1fa537b1864 + ;; + acc2) + ACC=f27292834a6b7129e5ad00e264062c978c48d705 + ;; + acc3) + ACC=830de593b42d3a007e19553c68f0f4c95e0e6e8f + ;; +esac + +if [[ ! -z $ACC ]]; then + echo "Using account $ACC" + cp $geth_home/keystore/$ACC $CHAIN_KEYSTORE/ + ACC_UNLOCK="--unlock 0x$ACC --password $geth_home/password" +fi + +echo "running: $GETH_BIN init $geth_home/genesis.json --datadir $CHAIN_DATA_DIR" +$GETH_BIN init $geth_home/genesis.json --datadir $CHAIN_DATA_DIR + +echo "$GETH_BIN + --datadir $CHAIN_DATA_DIR + --networkid 12345 + --bootnodes enode://$(cat $geth_home/bootnode_id)@$BOOTNODE_IP:30301 + --syncmode full + --rpc + --rpcaddr ${NODE_IP} + $ACC_UNLOCK + --mine + --etherbase 0x$ACC + --preload $geth_home/package_delivery_contract.js,$geth_home/package_delivery.js + console +" +$GETH_BIN \ + --datadir $CHAIN_DATA_DIR \ + --networkid 12345 \ + --bootnodes enode://$(cat $geth_home/bootnode_id)@$BOOTNODE_IP:30301 \ + --syncmode full \ + --rpc \ + --rpcaddr ${NODE_IP} \ + $ACC_UNLOCK \ + --mine \ + --etherbase 0x$ACC \ + --preload $geth_home/package_delivery_contract.js,$geth_home/package_delivery.js \ + console + + # --ethash.dagdir $geth_home/.ethash \