From f921870772b408b6b3f5688d7d416e5b6f0c0e84 Mon Sep 17 00:00:00 2001 From: avatar-lavventura Date: Sat, 3 Feb 2024 13:24:11 +0000 Subject: [PATCH 1/8] Update AutonomousSoftwareOrg address --- broker/eblocbroker_scripts/contract.yaml | 93 ++++++++++++------------ 1 file changed, 47 insertions(+), 46 deletions(-) diff --git a/broker/eblocbroker_scripts/contract.yaml b/broker/eblocbroker_scripts/contract.yaml index 59ee5670..e74ff86e 100644 --- a/broker/eblocbroker_scripts/contract.yaml +++ b/broker/eblocbroker_scripts/contract.yaml @@ -1,48 +1,49 @@ networks: - active_network: bloxberg # sepolia # bloxberg change me at cfg.py file as well - bloxberg: - project_dir: ~/ebloc-broker/contract - eBlocBroker: - address: '0x75a7357764379F26d2CF42E27e274D795B0fC977' - tx_hash: '0xcfc50ce3c89b54db1dda88329674c25c6fdcbf9d8e559021c1251bcea8ba6fc1' - USDTmy: - address: '0xbCc68aEa77cf128A7ae9eB7588f363Efc76349a7' - tx_hash: '0x99004818f181cac56c49d502f871db1a7ecb03a6a6f714a7072ee79f8ad025a6' - ResearchCertificate: - address: '0xD6397bf1A42f01C753dcD638C6eD3486963A09eD' - tx_hash: '0x8cf7bab8993c192c0de1b56aa8f9f0b706cbd341b35b425d500803509026c653' - AutonomousSoftwareOrg: - address: '0x06EE74fa579C771f182624a3f71845697270A9fF' - tx_hash: '0xe76315a96d47584b7da739a3a4d96e1bf64fbe913f35ed85ac4a1e7c4ca7c14e' - sepolia: - project_dir: ~/ebloc-broker/contract - name: sepolia - eBlocBroker: - address: '0x00A7413ACb69D7F9a03ab92B77c49628bD340274' - tx_hash: '0x7a32155daaef5d9884cabb91136966c672746847157fa06ed2f0236f101ad7f2' - USDTmy: - address: '0x7449585EaB2106B3c2c7f625d3Ad26640Fc14cec' - tx_hash: '0x62ef4bb70b6e4a8e9ab3c976974e5978244f0790784d6176ab4b650d73ee337c' - ResearchCertificate: - address: '0x17e85EF468e5e085659d0443e29856a9054f0E7A' - tx_hash: '0x5b7eb179c6a54cf86ccee595de71a9f2d31c55b0e4bb7f57a46a5a19f5e66a07' - AutonomousSoftwareOrg: - address: '0xC26ad052140a483f0342DD6e58c3D3A49DBfc106' - tx_hash: '0x596adaa251729551525850d913424946f3096821fc8a12e6bb5de0c6094cd29a' - bloxberg-docker: - project_dir: /workspace/ebloc-broker/contract - # eBlocBroker: - # address: '0x35F8ecb17a9EB63cB12cc0D15404A41946371F00' - # tx_hash: '0xa3b58df370bce393762ed92ec2d7dcd6d54e53381d42f76c6a18f9d4d1b2662c' - # USDTmy: - # address: '0x499331123D9861A8A812ccFB81552a4A00786ee2' - # tx_hash: '0x88780cb86701fab9df24c3d1808f21bc2395d70338a527b767942d353fe9299e' + active_network: bloxberg # sepolia # bloxberg change me at cfg.py file as well + bloxberg: + project_dir: ~/ebloc-broker/contract + eBlocBroker: + address: "0x75a7357764379F26d2CF42E27e274D795B0fC977" + tx_hash: "0xcfc50ce3c89b54db1dda88329674c25c6fdcbf9d8e559021c1251bcea8ba6fc1" + USDTmy: + address: "0xbCc68aEa77cf128A7ae9eB7588f363Efc76349a7" + tx_hash: "0x99004818f181cac56c49d502f871db1a7ecb03a6a6f714a7072ee79f8ad025a6" + ResearchCertificate: + address: "0xD6397bf1A42f01C753dcD638C6eD3486963A09eD" + tx_hash: "0x8cf7bab8993c192c0de1b56aa8f9f0b706cbd341b35b425d500803509026c653" + AutonomousSoftwareOrg: + address: "0x7FCCc5e2029a1A4818CB02D7824FEf6ceB2028c1" + tx_hash: "0xa9d9a5129b9d408f5248e32d0f2134df2009172bf741e12d83f7644e6e83b3a0" + sepolia: + project_dir: ~/ebloc-broker/contract + name: sepolia + eBlocBroker: + address: "0x00A7413ACb69D7F9a03ab92B77c49628bD340274" + tx_hash: "0x7a32155daaef5d9884cabb91136966c672746847157fa06ed2f0236f101ad7f2" + USDTmy: + address: "0x7449585EaB2106B3c2c7f625d3Ad26640Fc14cec" + tx_hash: "0x62ef4bb70b6e4a8e9ab3c976974e5978244f0790784d6176ab4b650d73ee337c" + ResearchCertificate: + address: "0x17e85EF468e5e085659d0443e29856a9054f0E7A" + tx_hash: "0x5b7eb179c6a54cf86ccee595de71a9f2d31c55b0e4bb7f57a46a5a19f5e66a07" + AutonomousSoftwareOrg: + address: "0xC26ad052140a483f0342DD6e58c3D3A49DBfc106" + tx_hash: "0x596adaa251729551525850d913424946f3096821fc8a12e6bb5de0c6094cd29a" + bloxberg-docker: + project_dir: + /workspace/ebloc-broker/contract + # eBlocBroker: + # address: '0x35F8ecb17a9EB63cB12cc0D15404A41946371F00' + # tx_hash: '0xa3b58df370bce393762ed92ec2d7dcd6d54e53381d42f76c6a18f9d4d1b2662c' + # USDTmy: + # address: '0x499331123D9861A8A812ccFB81552a4A00786ee2' + # tx_hash: '0x88780cb86701fab9df24c3d1808f21bc2395d70338a527b767942d353fe9299e' networks_old: - bloxberg: - project_dir: ~/ebloc-broker/contract - eBlocBroker: - address: '0x81aF3aeACbE068e7513443572d998bC2A20A83C1' - tx_hash: '0x39b3fb95d0258e42f71c5da835a32e7a197cab4eee2cea9373d627d2f5593d9d' - USDTmy: # dummy - address: '0x56B59aFC0Df1fcc1B42cf6135f94604cf7F7eb77' - tx_hash: '0x3a47a7971f80bf1a320a8409951a7830bf86bffb5005a65df4973c8943ec45f5' + bloxberg: + project_dir: ~/ebloc-broker/contract + eBlocBroker: + address: "0x81aF3aeACbE068e7513443572d998bC2A20A83C1" + tx_hash: "0x39b3fb95d0258e42f71c5da835a32e7a197cab4eee2cea9373d627d2f5593d9d" + USDTmy: # dummy + address: "0x56B59aFC0Df1fcc1B42cf6135f94604cf7F7eb77" + tx_hash: "0x3a47a7971f80bf1a320a8409951a7830bf86bffb5005a65df4973c8943ec45f5" From 69c307bcbcce6f132395417366c5bd9e3549de7b Mon Sep 17 00:00:00 2001 From: avatar-lavventura Date: Mon, 12 Feb 2024 14:36:15 +0000 Subject: [PATCH 2/8] Update graph items based on global index --- broker/cfg.py | 5 +- broker/eblocbroker_scripts/contract.yaml | 81 +++++++++--------- broker/roc/commit.py | 104 ++++++++++++----------- 3 files changed, 97 insertions(+), 93 deletions(-) diff --git a/broker/cfg.py b/broker/cfg.py index b0c9fd90..0d021ecf 100644 --- a/broker/cfg.py +++ b/broker/cfg.py @@ -17,8 +17,9 @@ IS_FULL_TEST = False # check whether the full-long test is applied IS_FIRST_CYCLE = True BERG_CMPE_IP = "79.123.176.66" # "berg-cmpe-boun.duckdns.org" // may be down once in a while -NETWORK_ID = "bloxberg_core" # "bloxberg" -# NETWORK_ID = "bloxberg" # "bloxberg" +NETWORK_ID = "bloxberg" +# NETWORK_ID = "bloxberg_boun" +# NETWORK_ID = "bloxberg_core" # NETWORK_ID = "sepolia" TEST_JOB_COUNTER = 0 diff --git a/broker/eblocbroker_scripts/contract.yaml b/broker/eblocbroker_scripts/contract.yaml index e74ff86e..71f5d8ad 100644 --- a/broker/eblocbroker_scripts/contract.yaml +++ b/broker/eblocbroker_scripts/contract.yaml @@ -1,37 +1,36 @@ networks: - active_network: bloxberg # sepolia # bloxberg change me at cfg.py file as well - bloxberg: - project_dir: ~/ebloc-broker/contract - eBlocBroker: - address: "0x75a7357764379F26d2CF42E27e274D795B0fC977" - tx_hash: "0xcfc50ce3c89b54db1dda88329674c25c6fdcbf9d8e559021c1251bcea8ba6fc1" - USDTmy: - address: "0xbCc68aEa77cf128A7ae9eB7588f363Efc76349a7" - tx_hash: "0x99004818f181cac56c49d502f871db1a7ecb03a6a6f714a7072ee79f8ad025a6" - ResearchCertificate: - address: "0xD6397bf1A42f01C753dcD638C6eD3486963A09eD" - tx_hash: "0x8cf7bab8993c192c0de1b56aa8f9f0b706cbd341b35b425d500803509026c653" - AutonomousSoftwareOrg: - address: "0x7FCCc5e2029a1A4818CB02D7824FEf6ceB2028c1" - tx_hash: "0xa9d9a5129b9d408f5248e32d0f2134df2009172bf741e12d83f7644e6e83b3a0" - sepolia: - project_dir: ~/ebloc-broker/contract - name: sepolia - eBlocBroker: - address: "0x00A7413ACb69D7F9a03ab92B77c49628bD340274" - tx_hash: "0x7a32155daaef5d9884cabb91136966c672746847157fa06ed2f0236f101ad7f2" - USDTmy: - address: "0x7449585EaB2106B3c2c7f625d3Ad26640Fc14cec" - tx_hash: "0x62ef4bb70b6e4a8e9ab3c976974e5978244f0790784d6176ab4b650d73ee337c" - ResearchCertificate: - address: "0x17e85EF468e5e085659d0443e29856a9054f0E7A" - tx_hash: "0x5b7eb179c6a54cf86ccee595de71a9f2d31c55b0e4bb7f57a46a5a19f5e66a07" - AutonomousSoftwareOrg: - address: "0xC26ad052140a483f0342DD6e58c3D3A49DBfc106" - tx_hash: "0x596adaa251729551525850d913424946f3096821fc8a12e6bb5de0c6094cd29a" - bloxberg-docker: - project_dir: - /workspace/ebloc-broker/contract + active_network: bloxberg # sepolia | change me at cfg.py file as well + bloxberg: + project_dir: ~/ebloc-broker/contract + eBlocBroker: + address: '0x75a7357764379F26d2CF42E27e274D795B0fC977' + tx_hash: '0xcfc50ce3c89b54db1dda88329674c25c6fdcbf9d8e559021c1251bcea8ba6fc1' + USDTmy: + address: '0xbCc68aEa77cf128A7ae9eB7588f363Efc76349a7' + tx_hash: '0x99004818f181cac56c49d502f871db1a7ecb03a6a6f714a7072ee79f8ad025a6' + ResearchCertificate: + address: '0xD6397bf1A42f01C753dcD638C6eD3486963A09eD' + tx_hash: '0x8cf7bab8993c192c0de1b56aa8f9f0b706cbd341b35b425d500803509026c653' + AutonomousSoftwareOrg: + address: '0xf08fd6412B2dC386b5DfA11ADC8bCAf740B9B010' + tx_hash: '0x3f561b36bb00e7229b77374fdc722f392d8b4a023dd8ce5bacd4a3006b06bf48' + sepolia: + project_dir: ~/ebloc-broker/contract + name: sepolia + eBlocBroker: + address: '0x00A7413ACb69D7F9a03ab92B77c49628bD340274' + tx_hash: '0x7a32155daaef5d9884cabb91136966c672746847157fa06ed2f0236f101ad7f2' + USDTmy: + address: '0x7449585EaB2106B3c2c7f625d3Ad26640Fc14cec' + tx_hash: '0x62ef4bb70b6e4a8e9ab3c976974e5978244f0790784d6176ab4b650d73ee337c' + ResearchCertificate: + address: '0x17e85EF468e5e085659d0443e29856a9054f0E7A' + tx_hash: '0x5b7eb179c6a54cf86ccee595de71a9f2d31c55b0e4bb7f57a46a5a19f5e66a07' + AutonomousSoftwareOrg: + address: '0xC26ad052140a483f0342DD6e58c3D3A49DBfc106' + tx_hash: '0x596adaa251729551525850d913424946f3096821fc8a12e6bb5de0c6094cd29a' + bloxberg-docker: + project_dir: /workspace/ebloc-broker/contract # eBlocBroker: # address: '0x35F8ecb17a9EB63cB12cc0D15404A41946371F00' # tx_hash: '0xa3b58df370bce393762ed92ec2d7dcd6d54e53381d42f76c6a18f9d4d1b2662c' @@ -39,11 +38,11 @@ networks: # address: '0x499331123D9861A8A812ccFB81552a4A00786ee2' # tx_hash: '0x88780cb86701fab9df24c3d1808f21bc2395d70338a527b767942d353fe9299e' networks_old: - bloxberg: - project_dir: ~/ebloc-broker/contract - eBlocBroker: - address: "0x81aF3aeACbE068e7513443572d998bC2A20A83C1" - tx_hash: "0x39b3fb95d0258e42f71c5da835a32e7a197cab4eee2cea9373d627d2f5593d9d" - USDTmy: # dummy - address: "0x56B59aFC0Df1fcc1B42cf6135f94604cf7F7eb77" - tx_hash: "0x3a47a7971f80bf1a320a8409951a7830bf86bffb5005a65df4973c8943ec45f5" + bloxberg: + project_dir: ~/ebloc-broker/contract + eBlocBroker: + address: '0x81aF3aeACbE068e7513443572d998bC2A20A83C1' + tx_hash: '0x39b3fb95d0258e42f71c5da835a32e7a197cab4eee2cea9373d627d2f5593d9d' + USDTmy: # dummy + address: '0x56B59aFC0Df1fcc1B42cf6135f94604cf7F7eb77' + tx_hash: '0x3a47a7971f80bf1a320a8409951a7830bf86bffb5005a65df4973c8943ec45f5' diff --git a/broker/roc/commit.py b/broker/roc/commit.py index 5837c7ad..7a48154c 100755 --- a/broker/roc/commit.py +++ b/broker/roc/commit.py @@ -13,32 +13,29 @@ fn = "/home/alper/git/AutonomousSoftwareOrg/graph/original.gv" G = nx.drawing.nx_pydot.read_dot(fn) -""" -Ebb = cfg.Ebb -Ebb.get_block_number() -""" data_map = { "17.2": "9ea971a966ec0f612b268d7e089b1f9e", - "10.0": "110e00c9266bf7cb964cd68f5e0a6b96", - "7.0": "3930b695da4c9f46aa0ef0153d8ca288", - "7.1": "8d7506d81cf589cc7603093272b68bef", - "7.2": "b04e7dd0ef6ecd43eb3973b0d6952733", - "7.3": "7080d03c288977b88812b865b761bffa", - "7.4": "84f310b307a08955e01d9c2347adf77e", - "17.1": "0f2290337f9cc909e62e4375a0353d7d", - "17.4": "059dc553c65d19c917e94291016b6714", - "14.0": "468e6561b82c811bb3c2324e2ca0865f", - "14.1": "0e975e19d841b2d5e7facc93eaf3db2e", - "4.0": "c69dc79f17e20e4ba5014f56492cddaf", - "4.1": "c2fee597bc585140bb2f214c6f19d89e", - "17.3": "42742ab7bfc995a7fc3490e663705590", - "17.0": "5a259600f0c0a7984f380cd00b89d1fe", - "22.3": "feb2c4b74f898c2064707843e0585fbe", - "19.0": "be293b48fa60443023e308bea4ec4df8", - "22.0": "a7e8fe2dcfeb2f4ddce864682133b60e", - "22.1": "a715e0a320e144335bb0974acdbe3847", - "22.2": "be038e12f93a275eb013192ab896c8a2", + "10.1": "110e00c9266bf7cb964cd68f5e0a6b96", + "7.16": "3930b695da4c9f46aa0ef0153d8ca288", + "7.9": "8d7506d81cf589cc7603093272b68bef", + "7.3": "b04e7dd0ef6ecd43eb3973b0d6952733", + "7.4": "7080d03c288977b88812b865b761bffa", + "7.5": "84f310b307a08955e01d9c2347adf77e", + "17.17": "0f2290337f9cc909e62e4375a0353d7d", + "17.14": "059dc553c65d19c917e94291016b6714", + "14.6": "468e6561b82c811bb3c2324e2ca0865f", + "14.10": "0e975e19d841b2d5e7facc93eaf3db2e", + "4.7": "c69dc79f17e20e4ba5014f56492cddaf", + "4.8": "c2fee597bc585140bb2f214c6f19d89e", + "17.12": "42742ab7bfc995a7fc3490e663705590", + "17.11": "5a259600f0c0a7984f380cd00b89d1fe", + "22.15": "feb2c4b74f898c2064707843e0585fbe", + "19.13": "be293b48fa60443023e308bea4ec4df8", + "22.18": "a7e8fe2dcfeb2f4ddce864682133b60e", + "22.19": "a715e0a320e144335bb0974acdbe3847", + "22.20": "be038e12f93a275eb013192ab896c8a2", + # "1": "7dca945940426af6fb934a8ffb78cc8d", "2": "44f8657617b4d88473d19c3265b8aaa3", "3": "6e006040b06789a03549d4d383ad7d12", @@ -95,7 +92,10 @@ completed_sw = [] -def add_software_exec_record(sw, index, input_hashes, output_hashes): +def add_software_exec_record(sw, input_hashes, output_hashes): + Ebb = cfg.Ebb + Ebb.get_block_number() + fn = env.PROVIDER_ID.lower().replace("0x", "") + ".json" Ebb.brownie_load_account(fn) @@ -103,9 +103,12 @@ def add_software_exec_record(sw, index, input_hashes, output_hashes): log(f"=> sw({sw}) is already created") else: log(f"=> [blue]sw({sw}) to be registered") - config.auto.addSoftwareExecRecord( - sw, index, input_hashes, output_hashes, {"from": env.PROVIDER_ID, "gas": 9900000, "allow_revert": True} - ) + args = {"from": env.PROVIDER_ID, "gas": 9900000, "allow_revert": True} + tx = config.auto.setNextCounter(sw, args) + index = tx.return_value + log(f"global_index={index}") + config.auto.addSoftwareExecRecord(sw, index, input_hashes, output_hashes, args) + breakpoint() # DEBUG def commit_software(): @@ -129,8 +132,8 @@ def commit_software(): completed_sw.append(sw_node) - #: save to blockchain - add_software_exec_record(data_map[sw_node], int(sw_node.split(".")[1]), input_hashes, output_hashes) + #: save to the blockchain + add_software_exec_record(data_map[sw_node], input_hashes, output_hashes) sw_nodes = [] @@ -138,7 +141,7 @@ def commit_software(): if "." in node: sw_nodes.append(node) -# commit_software() +commit_software() """ order = {} for sw_node in sw_nodes: @@ -149,27 +152,28 @@ def commit_software(): log(sorted_order) """ +#: this will be change in new smart contract sorted_order = { - "10.0": 13, + "10.1": 13, "17.2": 14, - "7.2": 19, - "7.3": 21, - "7.4": 23, - "14.0": 26, - "4.0": 29, - "4.1": 33, - "7.1": 35, - "14.1": 38, - "17.0": 42, - "17.3": 45, - "19.0": 46, - "17.4": 48, - "22.3": 50, - "7.0": 53, - "17.1": 57, - "22.0": 60, - "22.1": 64, - "22.2": 66, + "7.3": 19, + "7.4": 21, + "7.5": 23, + "14.6": 26, + "4.7": 29, + "4.8": 33, + "7.9": 35, + "14.10": 38, + "17.11": 42, + "17.12": 45, + "19.13": 46, + "17.14": 48, + "22.15": 50, + "7.16": 53, + "17.17": 57, + "22.18": 60, + "22.19": 64, + "22.20": 66, } hit_data = {} @@ -183,7 +187,7 @@ def commit_software(): hit_data[succ] = True log(f"{node} => {owned_data}") - # breakpoint() # DEBUG + breakpoint() # DEBUG # set(G.predecessors("17.2")) From af83e5fa80ef165698b5e68a3fecab5918f8b4f1 Mon Sep 17 00:00:00 2001 From: avatar-lavventura Date: Sun, 25 Feb 2024 12:50:36 +0000 Subject: [PATCH 3/8] Reading logs from Auto --- broker/cfg.py | 4 +- broker/eblocbroker_scripts/contract.yaml | 8 +- .../original_from_bloxberg.gv | 144 ++++++++++++++++++ broker/eblocbroker_scripts/roc.py | 51 ++++++- broker/roc/commit.py | 12 +- research_certificate/deploy.sh | 4 +- research_certificate/deploy_output.txt | 10 +- 7 files changed, 212 insertions(+), 21 deletions(-) create mode 100644 broker/eblocbroker_scripts/original_from_bloxberg.gv diff --git a/broker/cfg.py b/broker/cfg.py index 0d021ecf..959495db 100644 --- a/broker/cfg.py +++ b/broker/cfg.py @@ -17,9 +17,9 @@ IS_FULL_TEST = False # check whether the full-long test is applied IS_FIRST_CYCLE = True BERG_CMPE_IP = "79.123.176.66" # "berg-cmpe-boun.duckdns.org" // may be down once in a while -NETWORK_ID = "bloxberg" +# NETWORK_ID = "bloxberg" # NETWORK_ID = "bloxberg_boun" -# NETWORK_ID = "bloxberg_core" +NETWORK_ID = "bloxberg_core" # NETWORK_ID = "sepolia" TEST_JOB_COUNTER = 0 diff --git a/broker/eblocbroker_scripts/contract.yaml b/broker/eblocbroker_scripts/contract.yaml index 71f5d8ad..c0f52872 100644 --- a/broker/eblocbroker_scripts/contract.yaml +++ b/broker/eblocbroker_scripts/contract.yaml @@ -9,11 +9,11 @@ networks: address: '0xbCc68aEa77cf128A7ae9eB7588f363Efc76349a7' tx_hash: '0x99004818f181cac56c49d502f871db1a7ecb03a6a6f714a7072ee79f8ad025a6' ResearchCertificate: - address: '0xD6397bf1A42f01C753dcD638C6eD3486963A09eD' - tx_hash: '0x8cf7bab8993c192c0de1b56aa8f9f0b706cbd341b35b425d500803509026c653' + address: '0xB17dE8C64DC6BB678043457F1A7d992F583A37A0' + tx_hash: '0x22dc1725d46c0844e6fc198e37c3c72d55e151aa192adad959c05848a86bb760' AutonomousSoftwareOrg: - address: '0xf08fd6412B2dC386b5DfA11ADC8bCAf740B9B010' - tx_hash: '0x3f561b36bb00e7229b77374fdc722f392d8b4a023dd8ce5bacd4a3006b06bf48' + address: '0x450eb7EC90418E9D768f85Ffa1b29187AD57251a' + tx_hash: '0xc07dc41d0e435728a5e405a8697470c57db19db26a16aac6c97bdb1e81110359' sepolia: project_dir: ~/ebloc-broker/contract name: sepolia diff --git a/broker/eblocbroker_scripts/original_from_bloxberg.gv b/broker/eblocbroker_scripts/original_from_bloxberg.gv new file mode 100644 index 00000000..12eb2714 --- /dev/null +++ b/broker/eblocbroker_scripts/original_from_bloxberg.gv @@ -0,0 +1,144 @@ +strict graph { +2; +"1.1"; +3; +4; +6; +"5.2"; +8; +"7.3"; +9; +11; +"10.4"; +12; +13; +14; +"15.5"; +16; +17; +"18.6"; +19; +"20.7"; +21; +"22.8"; +23; +24; +"25.9"; +26; +27; +"28.10"; +29; +30; +31; +"32.11"; +33; +34; +"35.12"; +"36.13"; +37; +"38.14"; +39; +"40.15"; +41; +42; +"43.16"; +44; +45; +46; +"47.17"; +48; +49; +"50.18"; +51; +52; +53; +"54.19"; +55; +"56.20"; +57; +2 -- "1.1"; +"1.1" -- 3; +"1.1" -- 4; +3 -- "15.5"; +4 -- "5.2"; +4 -- "15.5"; +4 -- "28.10"; +6 -- "5.2"; +8 -- "7.3"; +8 -- "18.6"; +8 -- "28.10"; +"7.3" -- 9; +9 -- "18.6"; +11 -- "10.4"; +"10.4" -- 12; +"10.4" -- 13; +"10.4" -- 14; +13 -- "20.7"; +13 -- "22.8"; +14 -- "22.8"; +"15.5" -- 16; +"15.5" -- 17; +16 -- "36.13"; +16 -- "50.18"; +17 -- "28.10"; +17 -- "36.13"; +"18.6" -- 19; +19 -- "20.7"; +19 -- "25.9"; +19 -- "32.11"; +"20.7" -- 21; +21 -- "25.9"; +21 -- "32.11"; +21 -- "35.12"; +"22.8" -- 23; +"22.8" -- 24; +23 -- "38.14"; +24 -- "38.14"; +"25.9" -- 26; +"25.9" -- 27; +26 -- "32.11"; +27 -- "32.11"; +27 -- "36.13"; +"28.10" -- 29; +"28.10" -- 30; +"28.10" -- 31; +29 -- "43.16"; +30 -- "43.16"; +31 -- "43.16"; +"32.11" -- 33; +"32.11" -- 34; +33 -- "38.14"; +33 -- "40.15"; +34 -- "35.12"; +34 -- "36.13"; +34 -- "40.15"; +34 -- "50.18"; +34 -- "56.20"; +"36.13" -- 37; +37 -- "50.18"; +"38.14" -- 39; +39 -- "40.15"; +"40.15" -- 41; +"40.15" -- 42; +42 -- "54.19"; +42 -- "56.20"; +"43.16" -- 44; +"43.16" -- 45; +"43.16" -- 46; +44 -- "47.17"; +45 -- "47.17"; +46 -- "47.17"; +"47.17" -- 48; +"47.17" -- 49; +49 -- "50.18"; +49 -- "54.19"; +"50.18" -- 51; +"50.18" -- 52; +"50.18" -- 53; +51 -- "54.19"; +51 -- "56.20"; +52 -- "54.19"; +53 -- "54.19"; +"54.19" -- 55; +"56.20" -- 57; +} diff --git a/broker/eblocbroker_scripts/roc.py b/broker/eblocbroker_scripts/roc.py index baa27913..06396e77 100755 --- a/broker/eblocbroker_scripts/roc.py +++ b/broker/eblocbroker_scripts/roc.py @@ -1,13 +1,61 @@ #!/usr/bin/env python3 +import networkx as nx from broker._utils._log import log from broker import cfg, config from broker.config import env, setup_logger from broker.utils import print_tb + Ebb = cfg.Ebb logging = setup_logger() +G = nx.Graph() + + +# G.add_edge("2", "1.1") + +""" +""" + + +def roc_log(): + output = Ebb.get_block_number() + event_filter = config.auto.events.LogSoftwareExecRecord.createFilter( + # argument_filters={"to": str(provider)}, + fromBlock=23066710, + toBlock="latest", + ) + for logged_receipt in event_filter.get_all_entries(): + log(logged_receipt.args) + sw = logged_receipt.args.sourceCodeHash + sw = sw.hex()[32:64] + token_index = config.roc.getTokenIndex(sw) + index = logged_receipt.args.index + sw_str = f"{token_index}.{index}" + # + input_hash_bytes = logged_receipt.args.inputHash + output_hash_bytes = logged_receipt.args.outputHash + + input_hash_token_index = [] + for inp in input_hash_bytes: + _hash = inp.hex()[32:64] + input_hash_token_index.append(config.roc.getTokenIndex(_hash)) + + output_hash_token_index = [] + for out in output_hash_bytes: + _hash = out.hex()[32:64] + output_hash_token_index.append(config.roc.getTokenIndex(_hash)) + + for inp in input_hash_token_index: + G.add_edge(inp, sw_str) + + for out in output_hash_token_index: + G.add_edge(sw_str, out) + + nx.nx_pydot.write_dot(G, "original_from_bloxberg.gv") + breakpoint() # DEBUG + def roc(): output = Ebb.get_block_number() @@ -25,7 +73,8 @@ def roc(): def main(): - roc() + # roc() + roc_log() if __name__ == "__main__": diff --git a/broker/roc/commit.py b/broker/roc/commit.py index 7a48154c..99cc703a 100755 --- a/broker/roc/commit.py +++ b/broker/roc/commit.py @@ -10,7 +10,7 @@ import networkx as nx from typing import List -fn = "/home/alper/git/AutonomousSoftwareOrg/graph/original.gv" +fn = "/home/alper/git/AutonomousSoftwareOrg/graph-tools/original.gv" G = nx.drawing.nx_pydot.read_dot(fn) @@ -26,7 +26,7 @@ "17.14": "059dc553c65d19c917e94291016b6714", "14.6": "468e6561b82c811bb3c2324e2ca0865f", "14.10": "0e975e19d841b2d5e7facc93eaf3db2e", - "4.7": "c69dc79f17e20e4ba5014f56492cddaf", + "4.7": "b69dc79f17e20e4ba5014f56492cddaf", # c "4.8": "c2fee597bc585140bb2f214c6f19d89e", "17.12": "42742ab7bfc995a7fc3490e663705590", "17.11": "5a259600f0c0a7984f380cd00b89d1fe", @@ -104,11 +104,8 @@ def add_software_exec_record(sw, input_hashes, output_hashes): else: log(f"=> [blue]sw({sw}) to be registered") args = {"from": env.PROVIDER_ID, "gas": 9900000, "allow_revert": True} - tx = config.auto.setNextCounter(sw, args) - index = tx.return_value - log(f"global_index={index}") - config.auto.addSoftwareExecRecord(sw, index, input_hashes, output_hashes, args) - breakpoint() # DEBUG + # tx = config.auto.setNextCounter(sw, args) + tx = config.auto.addSoftwareExecRecord(sw, 0, input_hashes, output_hashes, args) def commit_software(): @@ -200,6 +197,7 @@ def md5_hash(): def commit_hash(_hash): + Ebb = cfg.Ebb roc_num = config.roc.getTokenIndex(_hash) if roc_num == 0: fn = env.PROVIDER_ID.lower().replace("0x", "") + ".json" diff --git a/research_certificate/deploy.sh b/research_certificate/deploy.sh index cc3915d9..ea1f9acd 100755 --- a/research_certificate/deploy.sh +++ b/research_certificate/deploy.sh @@ -1,8 +1,8 @@ #!/bin/bash main () { - network="sepolia" - # network="bloxberg_core" + # network="sepolia" + network="bloxberg_core" printf "## network=$network\n" rm -rf build/ brownie compile diff --git a/research_certificate/deploy_output.txt b/research_certificate/deploy_output.txt index 4ab29d4d..13bee6a3 100644 --- a/research_certificate/deploy_output.txt +++ b/research_certificate/deploy_output.txt @@ -1,4 +1,4 @@ -## network=sepolia +## network=bloxberg_core Brownie v1.19.3 - Python development framework for Ethereum New compatible solc version available: 0.8.22 @@ -31,9 +31,9 @@ ResearchCertificateProject is the active project. Running 'scripts/ResearchCertificate.py::main'... from=0xD118b6EF83ccF11b34331F1E7285542dDf70Bc49 - Transaction sent: 0x5b7eb179c6a54cf86ccee595de71a9f2d31c55b0e4bb7f57a46a5a19f5e66a07 - Gas price: 1.138964003 gwei Gas limit: 1232479 Nonce: 8 - Waiting for confirmation... - Waiting for confirmation... \ Waiting for confirmation... | Waiting for confirmation... / Waiting for confirmation... - Waiting for confirmation... \ ResearchCertificate.constructor confirmed Block: 4936991 Gas used: 1120436 (90.91%) - ResearchCertificate deployed at: 0x17e85EF468e5e085659d0443e29856a9054f0E7A + Transaction sent: 0x22dc1725d46c0844e6fc198e37c3c72d55e151aa192adad959c05848a86bb760 + Gas price: 0.002 gwei Gas limit: 1229258 Nonce: 4042 + Waiting for confirmation... - Waiting for confirmation... \ Waiting for confirmation... | Waiting for confirmation... / Waiting for confirmation... - Waiting for confirmation... \ Waiting for confirmation... | Waiting for confirmation... / Waiting for confirmation... - ResearchCertificate.constructor confirmed Block: 24136402 Gas used: 1117508 (90.91%) + ResearchCertificate deployed at: 0xB17dE8C64DC6BB678043457F1A7d992F583A37A0 ## setting abi... done From d6b22387a2a6aae4bd5b64d6887cc253d162f83e Mon Sep 17 00:00:00 2001 From: avatar-lavventura Date: Tue, 5 Mar 2024 08:51:02 +0000 Subject: [PATCH 4/8] Fix some bugs, minor improvements, and more --- broker/cfg.py | 4 +-- broker/eblocbroker_scripts/Contract.py | 2 +- broker/eblocbroker_scripts/roc.py | 3 --- broker/flask/README.org | 10 +++++++ broker/flask/hello.py | 16 +++++++++++ broker/flask/run_app.sh | 1 + broker/ipfs/job_workflow.yaml | 14 +++++----- broker/test_setup_w/README.org | 6 +++-- broker/test_setup_w/generate_w.py | 6 ++--- broker/test_setup_w/job_workflow.yaml | 37 +++++++++++++------------- broker/test_setup_w/my_scheduler.py | 4 ++- 11 files changed, 66 insertions(+), 37 deletions(-) create mode 100644 broker/flask/hello.py diff --git a/broker/cfg.py b/broker/cfg.py index 959495db..0d021ecf 100644 --- a/broker/cfg.py +++ b/broker/cfg.py @@ -17,9 +17,9 @@ IS_FULL_TEST = False # check whether the full-long test is applied IS_FIRST_CYCLE = True BERG_CMPE_IP = "79.123.176.66" # "berg-cmpe-boun.duckdns.org" // may be down once in a while -# NETWORK_ID = "bloxberg" +NETWORK_ID = "bloxberg" # NETWORK_ID = "bloxberg_boun" -NETWORK_ID = "bloxberg_core" +# NETWORK_ID = "bloxberg_core" # NETWORK_ID = "sepolia" TEST_JOB_COUNTER = 0 diff --git a/broker/eblocbroker_scripts/Contract.py b/broker/eblocbroker_scripts/Contract.py index 9183b999..1a526eb1 100644 --- a/broker/eblocbroker_scripts/Contract.py +++ b/broker/eblocbroker_scripts/Contract.py @@ -28,7 +28,7 @@ if cfg.NETWORK_ID == "sepolia": GAS_PRICE = 5 else: #: for bloxberg - GAS_PRICE = 0.25 # was 1.21 Gwei + GAS_PRICE = 0.004 # was 1.21 Gwei EXIT_AFTER = 1000 # seconds diff --git a/broker/eblocbroker_scripts/roc.py b/broker/eblocbroker_scripts/roc.py index 06396e77..3c986431 100755 --- a/broker/eblocbroker_scripts/roc.py +++ b/broker/eblocbroker_scripts/roc.py @@ -54,7 +54,6 @@ def roc_log(): G.add_edge(sw_str, out) nx.nx_pydot.write_dot(G, "original_from_bloxberg.gv") - breakpoint() # DEBUG def roc(): @@ -69,8 +68,6 @@ def roc(): log(logged_receipt.args) token_id = logged_receipt.args["tokenId"] - breakpoint() # DEBUG - def main(): # roc() diff --git a/broker/flask/README.org b/broker/flask/README.org index a81bebca..1dcda3ab 100644 --- a/broker/flask/README.org +++ b/broker/flask/README.org @@ -14,3 +14,13 @@ hypercorn app_ebb:app -b 127.0.0.1:8000 --reload curl -v 127.0.0.1:8000 curl -X POST http://127.0.0.1:8000/webhook -d "0x29e613b04125c16db3f3613563bfdd0ba24cb629 0000-0001-7642-0552" #+end_src + +--------------------------------------------- + +#+begin_src bash +export FLASK_APP=hello +export FLASK_ENV=development +flask run --host=0.0.0.0 + +curl http://192.168.1.117:5000/ +#+end_src diff --git a/broker/flask/hello.py b/broker/flask/hello.py new file mode 100644 index 00000000..ca94fea5 --- /dev/null +++ b/broker/flask/hello.py @@ -0,0 +1,16 @@ +from flask import Flask +from broker.utils import run + +app = Flask(__name__) + + +@app.route("/") +def hello(): + core_info = run(["sinfo", "-h", "-o%C"]).split("/") + + # allocated_cores = int(core_info[0]) + idle_cores = int(core_info[1]) + # other_cores = int(core_info[2]) + # total_cores = int(core_info[3]) + + return str(idle_cores) diff --git a/broker/flask/run_app.sh b/broker/flask/run_app.sh index 36b164cf..0d605feb 100755 --- a/broker/flask/run_app.sh +++ b/broker/flask/run_app.sh @@ -11,6 +11,7 @@ countdown () { num=$(ps axuww | grep -E "[h]ypercorn app_ebb:app" | \ grep -v -e "grep" -e "emacsclient" -e "flycheck_" | wc -l) + if [ $num -ge 1 ]; then echo "warning: app_ebb is already running" exit diff --git a/broker/ipfs/job_workflow.yaml b/broker/ipfs/job_workflow.yaml index b4726d43..8692e9a6 100644 --- a/broker/ipfs/job_workflow.yaml +++ b/broker/ipfs/job_workflow.yaml @@ -4,17 +4,17 @@ config: search_cheapest_provider: false source_code: cache_type: public - path: /home/alper/test_eblocbroker/workflow/256_448 + path: /home/alper/test_eblocbroker/workflow/6_10 storage_hours: 0 storage_id: ipfs - dt_in: 372 ## - data_transfer_out: 250 # sum of output weights + dt_in: 200 ## + data_transfer_out: 176 # sum of output weights jobs: job1: cores: 1 - run_time: 4 + run_time: 2 costs: - '0x29e613B04125c16db3f3613563bFdd0BA24Cb629': 88154 - '0x4934a70Ba8c1C3aCFA72E809118BDd9048563A24': 84600 - '0xe2e146d6B456760150d78819af7d276a1223A6d4': 93120 + '0x29e613B04125c16db3f3613563bFdd0BA24Cb629': 105490 + '0x4934a70Ba8c1C3aCFA72E809118BDd9048563A24': 101000 + '0xe2e146d6B456760150d78819af7d276a1223A6d4': 110700 path: {} diff --git a/broker/test_setup_w/README.org b/broker/test_setup_w/README.org index 2bf0a3ef..7cacfa66 100644 --- a/broker/test_setup_w/README.org +++ b/broker/test_setup_w/README.org @@ -1,11 +1,13 @@ * Random Workflow Generator To run: #+begin_src bash -./generate_w.py +./generate_w.py 6 10 ~/ebloc-broker/broker/ipfs/submit_w.py + +./my_layer_heft.py 6 10 #+end_src -On provider +** On the provider #+begin_src bash while ~/.ebloc-broker/start.sh; do sleep 15; done # diff --git a/broker/test_setup_w/generate_w.py b/broker/test_setup_w/generate_w.py index d3d4aebb..821c82e8 100755 --- a/broker/test_setup_w/generate_w.py +++ b/broker/test_setup_w/generate_w.py @@ -49,7 +49,7 @@ def main(): except: pass - print(f"Generate random DAG for {n} {edges}...") + print(f"Generating random DAG for (node, edge) {n} {edges} ...") wf = Workflow() while True: #: to be sure nodes are generated with the exact given node number @@ -66,7 +66,7 @@ def main(): nx.draw_spring(wf.G, with_labels=True) plt.savefig(BASE / "job.png") - base_size = 200 + base_dt_in_size = 200 base_dt_out_size = 250 for i in range(1, job_num + 1): sleep_dur = random.randint(2, 5) # 2 <= x <= 5 @@ -75,7 +75,7 @@ def main(): _job = yaml["config"]["jobs"][f"job{i}"] _job["run_time"] = sleep_dur _job["cores"] = 1 - _job["dt_in"] = base_size + _job["dt_in"] = base_dt_in_size _job["dt_out"] = 0 dt_out = 0 for edge in wf.out_edges(i): diff --git a/broker/test_setup_w/job_workflow.yaml b/broker/test_setup_w/job_workflow.yaml index 0924ea40..e5016704 100644 --- a/broker/test_setup_w/job_workflow.yaml +++ b/broker/test_setup_w/job_workflow.yaml @@ -1,19 +1,20 @@ config: - # requester_address: "0x0636278CBD420368b1238ab204b1073df9cC1c5c" - provider_address: '0x29e613B04125c16db3f3613563bFdd0BA24Cb629' - search_cheapest_provider: false - source_code: - cache_type: public - path: /home/alper/test_eblocbroker/workflow/256_448 - storage_hours: 0 - storage_id: ipfs - dt_in: 94 ## - data_transfer_out: 250 # sum of output weights - jobs: - job154: - cores: 1 - run_time: 2 - job179: - cores: 1 - run_time: 4 - dt_out: {} + # requester_address: "0x0636278CBD420368b1238ab204b1073df9cC1c5c" + provider_address: "0x29e613B04125c16db3f3613563bFdd0BA24Cb629" + search_cheapest_provider: false + source_code: + cache_type: public + path: /home/alper/test_eblocbroker/test_data/base/source_code_wf_random + # path: /home/alper/test_eblocbroker/workflow/256_448 + storage_hours: 0 + storage_id: ipfs + dt_in: 94 ## + data_transfer_out: 250 # sum of output weights + jobs: + job154: + cores: 1 + run_time: 2 + job179: + cores: 1 + run_time: 4 + dt_out: {} diff --git a/broker/test_setup_w/my_scheduler.py b/broker/test_setup_w/my_scheduler.py index c19ebbf9..253eb134 100755 --- a/broker/test_setup_w/my_scheduler.py +++ b/broker/test_setup_w/my_scheduler.py @@ -1,6 +1,5 @@ #!/usr/bin/env python3 -# >10 running jobs should be carried to FAILED import datetime import networkx as nx import pickle @@ -24,6 +23,8 @@ from broker.lib import state from broker.workflow.Workflow import Workflow +#: >10 running jobs should be carried to FAILED + wf = Workflow() _log.ll.LOG_FILENAME = Path.home() / ".ebloc-broker" / "test.log" @@ -226,6 +227,7 @@ def batch_submit(self): print(f"dt_in={yaml_cfg['config']['dt_in']}") while True: try: + #: TODO: check load of the providers submit_ipfs(job) # submits the job break except: From e4aa460bb7c037fadd9a2123ac65593f99e56015 Mon Sep 17 00:00:00 2001 From: avatar-lavventura Date: Thu, 7 Mar 2024 13:52:55 +0000 Subject: [PATCH 5/8] Fix some bugs, minor improvements, and more --- broker/Driver.py | 2 +- broker/_utils/tools.py | 9 + broker/eblocbroker_scripts/Contract.py | 7 +- broker/flask/run.sh | 5 + broker/imports.py | 3 + broker/ipfs/job_workflow.yaml | 13 +- broker/ipfs/submit.py | 11 +- broker/test_setup_w/README.org | 4 +- broker/test_setup_w/job_workflow.yaml | 43 ++-- broker/test_setup_w/my_layer_heft.py | 28 ++- broker/test_setup_w/my_scheduler.py | 27 ++- broker/test_setup_w/read_txs.py | 2 +- broker/test_setup_w/read_txs_final_test.py | 216 +++++++++++++++++++++ 13 files changed, 335 insertions(+), 35 deletions(-) create mode 100755 broker/flask/run.sh create mode 100755 broker/test_setup_w/read_txs_final_test.py diff --git a/broker/Driver.py b/broker/Driver.py index 0e7051a1..79c916f1 100644 --- a/broker/Driver.py +++ b/broker/Driver.py @@ -138,7 +138,7 @@ def tools(bn): check_output, gdrive_gmail = gdrive.check_gdrive_about(gmail) if not check_output: log( - f"E: provider's registered gmail=[m]{gmail}[/m] does not match with the set gdrive's gmail=[m]{gdrive_gmail}[/m]", + f"E: provider's registered gmail=[m]{gmail}[/m] does not match with the already set gdrive's gmail=[m]{gdrive_gmail}[/m]", is_code=True, h=False, ) diff --git a/broker/_utils/tools.py b/broker/_utils/tools.py index 1dd3daad..48ed155a 100644 --- a/broker/_utils/tools.py +++ b/broker/_utils/tools.py @@ -696,3 +696,12 @@ def gdrive_about_user() -> str: return ret[1] raise Exception() + + +def get_online_idle_core(ip) -> int: + while True: + with suppress(Exception): + return int(run(["curl", "-s", f"http://{ip}:5000"])) + + log("|", end="") + time.sleep(2) diff --git a/broker/eblocbroker_scripts/Contract.py b/broker/eblocbroker_scripts/Contract.py index 1a526eb1..9a240d2a 100644 --- a/broker/eblocbroker_scripts/Contract.py +++ b/broker/eblocbroker_scripts/Contract.py @@ -26,7 +26,7 @@ from brownie.network.transaction import TransactionReceipt if cfg.NETWORK_ID == "sepolia": - GAS_PRICE = 5 + GAS_PRICE = 5.0 else: #: for bloxberg GAS_PRICE = 0.004 # was 1.21 Gwei @@ -168,6 +168,8 @@ def _wait_for_transaction_receipt(self, tx_hash, compact=False, is_verbose=False tx_receipt = cfg.w3.eth.get_transaction_receipt(tx_hash) except TransactionNotFound as e: log(f"warning: TransactionNotFound tx={tx_hash} {e}") + if attempt == 5: + raise Exception("TransactionNotFound") except Exception as e: print_tb(e) tx_receipt = None @@ -439,7 +441,6 @@ def timeout_wrapper(self, method, contract, *args): log(f"warning: Tx: {e}", is_code=True) else: log(f"E: Tx: {e}") - breakpoint() # DEBUG if ("Try increasing the gas price" in str(e)) or ( "Transaction with the same hash was already imported." in str(e) @@ -512,6 +513,7 @@ def _submit_job(self, required_confs, requester, job_price, *args) -> "Transacti except Exception as e: raise Exception(f"Approve transaction is failed, {e}") + time.sleep(2) self.gas_price = GAS_PRICE method_name = "submitJob" idx = 0 @@ -566,6 +568,7 @@ def _submit_job(self, required_confs, requester, job_price, *args) -> "Transacti continue + time.sleep(2) idx += 1 raise Exception("No valid Tx receipt for 'submitJob' is generated") diff --git a/broker/flask/run.sh b/broker/flask/run.sh new file mode 100755 index 00000000..6be90ecc --- /dev/null +++ b/broker/flask/run.sh @@ -0,0 +1,5 @@ +#!/bin/bash + +export FLASK_APP=hello +export FLASK_ENV=development +flask run --host=0.0.0.0 diff --git a/broker/imports.py b/broker/imports.py index 05412c67..a60c0589 100644 --- a/broker/imports.py +++ b/broker/imports.py @@ -133,6 +133,7 @@ def connect_to_eblocbroker() -> None: env.ROC_CONTRACT_ADDRESS, abi=read_abi_file(env.EBB_SCRIPTS / "abi_ResearchCertificate.json") ) # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + """ from brownie import project as pro_auto _project = pro_auto.load("/home/alper/git/AutonomousSoftwareOrg") # TODO: add as sub-module @@ -141,6 +142,8 @@ def connect_to_eblocbroker() -> None: config._auto = cfg.w3.eth.contract( env.AUTO_CONTRACT_ADDRESS, abi=read_abi_file(env.EBB_SCRIPTS / "abi_AutonomousSoftwareOrg.json") ) + """ + """ depreciated from brownie import project as pro diff --git a/broker/ipfs/job_workflow.yaml b/broker/ipfs/job_workflow.yaml index 8692e9a6..c2853bc0 100644 --- a/broker/ipfs/job_workflow.yaml +++ b/broker/ipfs/job_workflow.yaml @@ -1,18 +1,21 @@ config: requester_address: '0x72c1a89ff3606aa29686ba8d29e28dccff06430a' - provider_address: '0xe2e146d6B456760150d78819af7d276a1223A6d4' + provider_address: '0x29e613B04125c16db3f3613563bFdd0BA24Cb629' search_cheapest_provider: false source_code: cache_type: public - path: /home/alper/test_eblocbroker/workflow/6_10 + path: /home/alper/test_eblocbroker/workflow/32_56 storage_hours: 0 storage_id: ipfs - dt_in: 200 ## - data_transfer_out: 176 # sum of output weights + dt_in: 0 ## + data_transfer_out: 124 # sum of output weights jobs: job1: cores: 1 - run_time: 2 + run_time: 5 + job2: + cores: 1 + run_time: 5 costs: '0x29e613B04125c16db3f3613563bFdd0BA24Cb629': 105490 '0x4934a70Ba8c1C3aCFA72E809118BDd9048563A24': 101000 diff --git a/broker/ipfs/submit.py b/broker/ipfs/submit.py index cf0c4f13..cb911ab0 100755 --- a/broker/ipfs/submit.py +++ b/broker/ipfs/submit.py @@ -4,7 +4,7 @@ from pathlib import Path from sys import platform from web3.logs import DISCARD - +import time from broker import cfg from broker._utils.tools import _remove, log from broker._utils.web3_tools import get_tx_status @@ -97,7 +97,14 @@ def _ipfs_add(job, target, idx, is_verbose=False): def _submit(provider_addr, job, requester, targets, required_confs): tx_hash = Ebb.submit_job(provider_addr, job.key, job, requester, required_confs) if required_confs >= 1: - tx_receipt = get_tx_status(tx_hash) + while True: + try: + tx_receipt = get_tx_status(tx_hash) + break + except: + time.sleep(2) + tx_hash = Ebb.submit_job(provider_addr, job.key, job, requester, required_confs) + if tx_receipt["status"] == 1: processed_logs = Ebb._eblocbroker.events.LogJob().processReceipt(tx_receipt, errors=DISCARD) try: diff --git a/broker/test_setup_w/README.org b/broker/test_setup_w/README.org index 7cacfa66..eae58500 100644 --- a/broker/test_setup_w/README.org +++ b/broker/test_setup_w/README.org @@ -9,7 +9,7 @@ To run: ** On the provider #+begin_src bash -while ~/.ebloc-broker/start.sh; do sleep 15; done +~/.ebloc-broker/broker/start.sh # -while ~/.ebloc-broker/end.sh; do sleep 15; done +~/.ebloc-broker/broker/end.sh #+end_src diff --git a/broker/test_setup_w/job_workflow.yaml b/broker/test_setup_w/job_workflow.yaml index e5016704..7aa096aa 100644 --- a/broker/test_setup_w/job_workflow.yaml +++ b/broker/test_setup_w/job_workflow.yaml @@ -1,20 +1,29 @@ config: # requester_address: "0x0636278CBD420368b1238ab204b1073df9cC1c5c" - provider_address: "0x29e613B04125c16db3f3613563bFdd0BA24Cb629" - search_cheapest_provider: false - source_code: - cache_type: public - path: /home/alper/test_eblocbroker/test_data/base/source_code_wf_random + provider_address: '0x4934a70Ba8c1C3aCFA72E809118BDd9048563A24' + search_cheapest_provider: false + source_code: + cache_type: public + path: /home/alper/test_eblocbroker/workflow/128_224 # path: /home/alper/test_eblocbroker/workflow/256_448 - storage_hours: 0 - storage_id: ipfs - dt_in: 94 ## - data_transfer_out: 250 # sum of output weights - jobs: - job154: - cores: 1 - run_time: 2 - job179: - cores: 1 - run_time: 4 - dt_out: {} + storage_hours: 0 + storage_id: ipfs + dt_in: 456 ## + data_transfer_out: 250 # sum of output weights + jobs: + job128: + cores: 1 + run_time: 4 + job95: + cores: 1 + run_time: 3 + job127: + cores: 1 + run_time: 3 + job101: + cores: 1 + run_time: 3 + job48: + cores: 1 + run_time: 3 + dt_out: {} diff --git a/broker/test_setup_w/my_layer_heft.py b/broker/test_setup_w/my_layer_heft.py index b2f25e0b..3c1e0a54 100755 --- a/broker/test_setup_w/my_layer_heft.py +++ b/broker/test_setup_w/my_layer_heft.py @@ -13,7 +13,7 @@ from broker import cfg from broker._utils import _log from broker._utils._log import log -from broker._utils.tools import print_tb +from broker._utils.tools import print_tb, get_online_idle_core from broker._utils.yaml import Yaml from broker.eblocbroker_scripts import Contract from broker.eblocbroker_scripts.job import Job @@ -35,6 +35,13 @@ provider_id["a"] = "0x29e613B04125c16db3f3613563bFdd0BA24Cb629" provider_id["b"] = "0x4934a70Ba8c1C3aCFA72E809118BDd9048563A24" provider_id["c"] = "0xe2e146d6B456760150d78819af7d276a1223A6d4" + +provider_ip = {} +provider_ip["a"] = "192.168.1.117" +provider_ip["b"] = "192.168.1.21" +provider_ip["c"] = "192.168.1.104" + + if len(sys.argv) == 3: n = int(sys.argv[1]) edges = int(sys.argv[2]) @@ -109,6 +116,7 @@ def update_job_stats(self): self.refunded.append(key) + #: save operation is done with open(BASE / "layer_submitted_dict.pkl", "wb") as f: pickle.dump(self.submitted_node_dict, f) @@ -376,7 +384,23 @@ def submit_layering(): continue_flag = True if not continue_flag: - yaml_original["config"]["provider_address"] = provider_id[provider_char] + # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + idle_code = get_online_idle_core(provider_ip[provider_char]) + log(f"provider {provider_char} idle cores: {idle_code}") + if idle_code > 0: + yaml_original["config"]["provider_address"] = provider_id[provider_char] + else: + yaml_original["config"]["provider_address"] = provider_id[provider_char] + for pr in provider_ip: + idle_code = get_online_idle_core(provider_ip[pr]) + if idle_code > 0: + print( + f"#: Load changed to provider={pr} #########################################################################" + ) + yaml_original["config"]["provider_address"] = provider_id[pr] + break + + # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- yaml_original["config"]["source_code"]["path"] = str(BASE) log( f"* w{_idx} => {sorted(partial_layer)} | provider to submit => [bold cyan]{provider_char}[/bold cyan] " diff --git a/broker/test_setup_w/my_scheduler.py b/broker/test_setup_w/my_scheduler.py index 253eb134..1e385d5c 100755 --- a/broker/test_setup_w/my_scheduler.py +++ b/broker/test_setup_w/my_scheduler.py @@ -14,7 +14,7 @@ from broker import cfg from broker._utils import _log from broker._utils._log import log -from broker._utils.tools import print_tb +from broker._utils.tools import print_tb, get_online_idle_core from broker._utils.yaml import Yaml from broker.eblocbroker_scripts import Contract from broker.eblocbroker_scripts.job import Job @@ -51,6 +51,11 @@ provider_id["b"] = "0x4934a70Ba8c1C3aCFA72E809118BDd9048563A24" provider_id["c"] = "0xe2e146d6B456760150d78819af7d276a1223A6d4" +provider_ip = {} +provider_ip["a"] = "192.168.1.117" +provider_ip["b"] = "192.168.1.21" +provider_ip["c"] = "192.168.1.104" + try: with open(BASE / "heft_submitted_dict.pkl", "rb") as f: loaded_dict = pickle.load(f) @@ -152,7 +157,23 @@ def batch_submit(self): G_copy = wf.G.copy() yaml_cfg = Yaml(yaml_fn_wf) yaml_cfg["config"]["source_code"]["path"] = str(BASE) - yaml_cfg["config"]["provider_address"] = provider_id[batch_key] + # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + idle_code = get_online_idle_core(provider_ip[batch_key]) + log(f"provider {batch_key} idle cores: {idle_code}") + if idle_code > 0: + yaml_cfg["config"]["provider_address"] = provider_id[batch_key] + else: + yaml_cfg["config"]["provider_address"] = provider_id[batch_key] + for pr in provider_ip: + idle_code = get_online_idle_core(provider_ip[pr]) + if idle_code > 0: + print( + f"#: Load changed to provider={pr} #########################################################################" + ) + yaml_cfg["config"]["provider_address"] = provider_id[pr] + break + + # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- g_list = [] for node in list(wf.G.nodes): if node != "\\n" and int(node) not in self.batch_to_submit[batch_key]: @@ -227,7 +248,6 @@ def batch_submit(self): print(f"dt_in={yaml_cfg['config']['dt_in']}") while True: try: - #: TODO: check load of the providers submit_ipfs(job) # submits the job break except: @@ -241,6 +261,7 @@ def batch_submit(self): except: self.submitted_dict[key] = [int(node)] + #: save operation is done with open(BASE / "heft_submitted_dict.pkl", "wb") as f: pickle.dump(self.submitted_dict, f) diff --git a/broker/test_setup_w/read_txs.py b/broker/test_setup_w/read_txs.py index ac00d1be..4f6bd14b 100755 --- a/broker/test_setup_w/read_txs.py +++ b/broker/test_setup_w/read_txs.py @@ -181,7 +181,7 @@ def read_txs(n, edges, fn): def main(): test = [(16, 28), (32, 56), (64, 112), (128, 224), (256, 448)] - # test = [(16, 28)] + test = [(16, 28)] # test = [(32, 56)] # test = [(64, 112)] # test = [(128, 224)] diff --git a/broker/test_setup_w/read_txs_final_test.py b/broker/test_setup_w/read_txs_final_test.py new file mode 100755 index 00000000..916f5fca --- /dev/null +++ b/broker/test_setup_w/read_txs_final_test.py @@ -0,0 +1,216 @@ +#!/usr/bin/env python3 + +from broker.eblocbroker_scripts import Contract +from broker._utils._log import log +from broker._utils.tools import print_tb +from broker.errors import QuietExit +import pickle +from pathlib import Path +from broker import cfg +from broker.eblocbroker_scripts.utils import Cent + +Ebb: "Contract.Contract" = cfg.Ebb + +is_excel = False +if is_excel: + log( + "type,provider_label,id,sourceCodeHash,index,job_id,received_bn,w_type,elapsed_time,processPayment_tx_hash," + "processPayment_gasUsed,submitJob_tx_hash,submitJob_gas_used,data_transfer_in_to_download,data_transfer_out," + "job_price" + ) + +provider_id = {} +provider_id["0x29e613B04125c16db3f3613563bFdd0BA24Cb629".lower()] = "a" +provider_id["0x4934a70Ba8c1C3aCFA72E809118BDd9048563A24".lower()] = "b" +provider_id["0xe2e146d6B456760150d78819af7d276a1223A6d4".lower()] = "c" + + +def read_txs_layer(n, edges, fn): + BASE = Path.home() / "test_eblocbroker" / "workflow" / f"{n}_{edges}" + try: + with open(BASE / fn, "rb") as f: + loaded_dict = pickle.load(f) + except Exception as e: + print_tb(e) + + name = fn.replace(".pkl", "").replace("submitted_dict_", "") + job_price_sum = 0 + sum_received = 0 + sum_refunded = 0 + total_processpayment_gas = 0 + idx = 0 + total_submitjob_gas = 0 + total_processpayment_gas = 0 + total_refunded = 0 + # for k, v in loaded_dict.items(): + # log(f"{v} => {k}") + + for k, v in loaded_dict.items(): + keys = v.split("_") + provider = keys[0] + job_key = keys[1] + index = keys[2] + received_bn = keys[3] + job_id = keys[4] + event_filter = Ebb._eblocbroker.events.LogProcessPayment.createFilter( + argument_filters={"provider": str(provider)}, + fromBlock=int(received_bn), + toBlock="latest", + ) + if int(job_id) == 0: + output_g = Ebb.get_job_info(keys[0], keys[1], keys[2], 0, keys[3], is_print=False) + _job_price = output_g["submitJob_received_job_price"] + jp = float(Cent(_job_price)._to()) + job_price_sum += jp + total_submitjob_gas += output_g["submitJob_gas_used"] + total_refunded += sum_refunded # output_g["refunded_cent"] + + for logged_receipt in event_filter.get_all_entries(): + if ( + logged_receipt.args["jobKey"] == job_key + and logged_receipt.args["index"] == int(index) + and logged_receipt.args["jobID"] == int(job_id) + ): + output = Ebb.get_job_info(keys[0], keys[1], keys[2], job_id, keys[3], is_print=False) + idx += 1 + recv = logged_receipt.args["receivedCent"] + ref = logged_receipt.args["refundedCent"] + sum_received += float(Cent(recv)._to()) + sum_refunded += float(Cent(ref)._to()) + tx_receipt = Ebb.get_transaction_receipt(logged_receipt["transactionHash"].hex()) + total_processpayment_gas += int(tx_receipt["gasUsed"]) + if is_excel: + if int(job_id) == 0: + log( + f"{name},{provider_id[provider.lower()]},j{idx},{job_key},{index},{job_id},{received_bn},{n}_{edges},{output['run_time'][int(job_id)]}," + f"{tx_receipt['transactionHash'].hex()},{tx_receipt['gasUsed']},{output_g['submitJob_tx_hash']},{output_g['submitJob_gas_used']}," + f"{output_g['data_transfer_in_to_download']},{output_g['data_transfer_out']},{jp}" + ) + else: + log( + f"{name},{provider_id[provider.lower()]},j{idx},{job_key},{index},{job_id},{received_bn},{n}_{edges},{output['run_time'][int(job_id)]}," + f"{tx_receipt['transactionHash'].hex()},{tx_receipt['gasUsed']}" + ) + + if not is_excel: + log(f"LAYER {n} {edges}") + log(f"total_submitjob_gas={total_submitjob_gas}") + log(f"total_processpayment_gas={total_processpayment_gas} idx={idx}") + log(f"total_received={sum_received} [pink]USDmy") + log(f"total_refunded={Cent(total_refunded)._to()} [pink]USDmy") + log(f"job_price_sum={job_price_sum}") + log("--------------------------------------------------------") + + +def read_txs(n, edges, fn): + BASE = Path.home() / "test_eblocbroker" / "workflow" / f"{n}_{edges}" + try: + with open(BASE / fn, "rb") as f: + loaded_dict = pickle.load(f) + except Exception as e: + print_tb(e) + + name = fn.replace(".pkl", "").replace("submitted_dict_", "") + job_price_sum = 0 + total_submitjob_gas = 0 + total_refunded = 0 + sum_received = 0 + sum_refunded = 0 + total_processpayment_gas = 0 + idx = 0 + for k, v in loaded_dict.items(): + # log(f"{k} => {v}") + keys = k.split("_") + provider = keys[0] + job_key = keys[1] + index = keys[2] + received_bn = keys[3] + event_filter = Ebb._eblocbroker.events.LogProcessPayment.createFilter( + argument_filters={"provider": str(provider)}, + fromBlock=int(received_bn), + toBlock="latest", + ) + output_g = Ebb.get_job_info(keys[0], keys[1], keys[2], 0, keys[3], is_print=False) + _job_price = output_g["submitJob_received_job_price"] + jp = float(Cent(_job_price)._to()) + job_price_sum += jp + total_submitjob_gas += output_g["submitJob_gas_used"] + total_refunded += sum_refunded # output_g["refunded_cent"] + # log(f"{sum_received} {_job_price}") + # log(f"{k} => ", end="") + # log(f"{v}") + for logged_receipt in event_filter.get_all_entries(): + if logged_receipt.args["jobKey"] == job_key and logged_receipt.args["index"] == int(index): + idx += 1 + job_id = logged_receipt.args["jobID"] + output = Ebb.get_job_info(keys[0], keys[1], keys[2], job_id, keys[3], is_print=False) + recv = logged_receipt.args["receivedCent"] + # log(f"{Cent(recv)._to()} [pink]USDmy") + sum_received += float(Cent(recv)._to()) + sum_refunded += logged_receipt.args["refundedCent"] + tx_receipt = Ebb.get_transaction_receipt(logged_receipt["transactionHash"].hex()) + total_processpayment_gas += int(tx_receipt["gasUsed"]) + if is_excel: + if job_id == 0: + log( + f"{name},{provider_id[provider.lower()]},j{idx},{job_key},{index},{job_id},{received_bn},{n}_{edges},{output['run_time'][job_id]}," + f"{tx_receipt['transactionHash'].hex()},{tx_receipt['gasUsed']},{output_g['submitJob_tx_hash']},{output_g['submitJob_gas_used']}," + f"{output_g['data_transfer_in_to_download']},{output_g['data_transfer_out']},{jp}" + ) + else: + log( + f"{name},{provider_id[provider.lower()]},j{idx},{job_key},{index},{job_id},{received_bn},{n}_{edges},{output['run_time'][job_id]}," + f"{tx_receipt['transactionHash'].hex()},{tx_receipt['gasUsed']}" + ) + + # print(logged_receipt.args["receivedCent"]) + # print(int(tx_receipt["gasUsed"])) + # log(logged_receipt.args) + # log() + + if not is_excel: + log() + log(f"* HEFT {n} {edges}") + log(f"total_submitjob_gas={total_submitjob_gas}") + log(f"total_processpayment_gas={total_processpayment_gas} idx={idx}") + log(f"total_received={sum_received} [pink]USDmy") + log(f"total_refunded={Cent(total_refunded)._to()} [pink]USDmy") + log(f"job_price_sum={job_price_sum}") + log("--------------------------------------------------------") + + +def main(): + test = [(16, 28), (32, 56), (64, 112), (128, 224), (256, 448)] + # test = [(16, 28)] + # test = [(32, 56)] + # test = [(64, 112)] + test = [(128, 224)] + # test = [(256, 448)] + test = dict(test) + for n, edges in test.items(): + read_txs(n, edges, "heft_submitted_dict.pkl") + """ + log("\n\n\n") + read_txs(n, edges, "heft_submitted_dict_2.pkl") + log("\n\n\n") + read_txs(n, edges, "heft_submitted_dict_3.pkl") + # ----------------------------------------------------- + read_txs_layer(n, edges, "layer_submitted_dict_1.pkl") + log("\n\n\n") + read_txs_layer(n, edges, "layer_submitted_dict_2.pkl") + log("\n\n\n") + read_txs_layer(n, edges, "layer_submitted_dict_3.pkl") + if not is_excel: + log("-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-", "yellow") + """ + + +if __name__ == "__main__": + try: + main() + except KeyboardInterrupt: + pass + except QuietExit as e: + print(f"#> {e}") + except Exception as e: + print_tb(str(e)) From 49123014408ee6f6f4eb5642e88284a20ea9ef2f Mon Sep 17 00:00:00 2001 From: avatar-lavventura Date: Tue, 12 Mar 2024 21:44:45 +0300 Subject: [PATCH 6/8] Workflow test results updated --- broker/eblocbroker_scripts/g.png | Bin 0 -> 262368 bytes .../original_from_bloxberg.gv | 291 +++++++++--------- ...ocbroker_wf_test-results_October_2023.xlsx | 4 +- 3 files changed, 150 insertions(+), 145 deletions(-) create mode 100644 broker/eblocbroker_scripts/g.png diff --git a/broker/eblocbroker_scripts/g.png b/broker/eblocbroker_scripts/g.png new file mode 100644 index 0000000000000000000000000000000000000000..55146798c02bd889221d46017c7b06e962dd649d GIT binary patch literal 262368 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