diff --git a/.gitignore b/.gitignore index 1a937174a7..82f5ee3cbe 100644 --- a/.gitignore +++ b/.gitignore @@ -111,4 +111,3 @@ venv.bak/ data/* temp_private_key.pem .idea/ - diff --git a/HISTORY.rst b/HISTORY.rst index 923ce5b2ea..f38d4abda1 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -99,3 +99,14 @@ Release History - Fixes some examples and docs - Refactors crypto modules and adds additional tests - Multiple additional minor fixes and changes + +0.1.13 (2019-11-08) +------------------- + +- Adds envelope serializer +- Adds support for programmatically initializing an AEA +- Adds some tests for the gui and other components +- Exposes connection status to skills +- Updates oef connection to re-establish dropped connections +- Updates the car park agent +- Multiple additional minor fixes and changes diff --git a/Pipfile b/Pipfile index 3815b5eb49..92254b76a3 100644 --- a/Pipfile +++ b/Pipfile @@ -28,7 +28,7 @@ base58 = "*" docker = "*" click = "*" pyyaml = ">=4.2b1" -oef = {index = "test-pypi",version = "==0.6.10"} +oef = "==0.8.1" colorlog = "*" jsonschema = "*" protobuf = "*" @@ -36,9 +36,9 @@ flask = "*" connexion = ">=2.4.0" watchdog = "*" python-dotenv = "*" -fetchai-ledger-api = "*" -web3 = "*" -eth-account = "*" +fetchai-ledger-api = "==0.8.1" +web3 = "==5.2.2" +eth-account = "==0.4.0" [requires] python_version = "3.7" diff --git a/Pipfile.lock b/Pipfile.lock index 00ec23c1cd..1f9e858add 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "538f6a0573e14e00ff12b0989c0499023dfd326a3ce9c48ddb00ed3fc6d2f916" + "sha256": "8ab3d8accc84f8a69a75f0264cb346c03fb35358281f88999a1bca9fe570420a" }, "pipfile-spec": 6, "requires": { @@ -60,40 +60,40 @@ }, "cffi": { "hashes": [ - "sha256:08f99e8b38d5134d504aa7e486af8e4fde66a2f388bbecc270cdd1e00fa09ff8", - "sha256:1112d2fc92a867a6103bce6740a549e74b1d320cf28875609f6e93857eee4f2d", - "sha256:1b9ab50c74e075bd2ae489853c5f7f592160b379df53b7f72befcbe145475a36", - "sha256:24eff2997436b6156c2f30bed215c782b1d8fd8c6a704206053c79af95962e45", - "sha256:2eff642fbc9877a6449026ad66bf37c73bf4232505fb557168ba5c502f95999b", - "sha256:362e896cea1249ed5c2a81cf6477fabd9e1a5088aa7ea08358a4c6b0998294d2", - "sha256:40eddb3589f382cb950f2dcf1c39c9b8d7bd5af20665ce273815b0d24635008b", - "sha256:5ed40760976f6b8613d4a0db5e423673ca162d4ed6c9ed92d1f4e58a47ee01b5", - "sha256:632c6112c1e914c486f06cfe3f0cc507f44aa1e00ebf732cedb5719e6aa0466a", - "sha256:64d84f0145e181f4e6cc942088603c8db3ae23485c37eeda71cb3900b5e67cb4", - "sha256:6cb4edcf87d0e7f5bdc7e5c1a0756fbb37081b2181293c5fdf203347df1cd2a2", - "sha256:6f19c9df4785305669335b934c852133faed913c0faa63056248168966f7a7d5", - "sha256:719537b4c5cd5218f0f47826dd705fb7a21d83824920088c4214794457113f3f", - "sha256:7b0e337a70e58f1a36fb483fd63880c9e74f1db5c532b4082bceac83df1523fa", - "sha256:853376efeeb8a4ae49a737d5d30f5db8cdf01d9319695719c4af126488df5a6a", - "sha256:85bbf77ffd12985d76a69d2feb449e35ecdcb4fc54a5f087d2bd54158ae5bb0c", - "sha256:8978115c6f0b0ce5880bc21c967c65058be8a15f1b81aa5fdbdcbea0e03952d1", - "sha256:8f7eec920bc83692231d7306b3e311586c2e340db2dc734c43c37fbf9c981d24", - "sha256:8fe230f612c18af1df6f348d02d682fe2c28ca0a6c3856c99599cdacae7cf226", - "sha256:92068ebc494b5f9826b822cec6569f1f47b9a446a3fef477e1d11d7fac9ea895", - "sha256:b57e1c8bcdd7340e9c9d09613b5e7fdd0c600be142f04e2cc1cc8cb7c0b43529", - "sha256:ba956c9b44646bc1852db715b4a252e52a8f5a4009b57f1dac48ba3203a7bde1", - "sha256:ca42034c11eb447497ea0e7b855d87ccc2aebc1e253c22e7d276b8599c112a27", - "sha256:dc9b2003e9a62bbe0c84a04c61b0329e86fccd85134a78d7aca373bbbf788165", - "sha256:dd308802beb4b2961af8f037becbdf01a1e85009fdfc14088614c1b3c383fae5", - "sha256:e77cd105b19b8cd721d101687fcf665fd1553eb7b57556a1ef0d453b6fc42faa", - "sha256:f56dff1bd81022f1c980754ec721fb8da56192b026f17f0f99b965da5ab4fbd2", - "sha256:fa4cc13c03ea1d0d37ce8528e0ecc988d2365e8ac64d8d86cafab4038cb4ce89", - "sha256:fa8cf1cb974a9f5911d2a0303f6adc40625c05578d8e7ff5d313e1e27850bd59", - "sha256:fb003019f06d5fc0aa4738492ad8df1fa343b8a37cbcf634018ad78575d185df", - "sha256:fd409b7778167c3bcc836484a8f49c0e0b93d3e745d975749f83aa5d18a5822f", - "sha256:fe5d65a3ee38122003245a82303d11ac05ff36531a8f5ce4bc7d4bbc012797e1" - ], - "version": "==1.13.0" + "sha256:0b49274afc941c626b605fb59b59c3485c17dc776dc3cc7cc14aca74cc19cc42", + "sha256:0e3ea92942cb1168e38c05c1d56b0527ce31f1a370f6117f1d490b8dcd6b3a04", + "sha256:135f69aecbf4517d5b3d6429207b2dff49c876be724ac0c8bf8e1ea99df3d7e5", + "sha256:19db0cdd6e516f13329cba4903368bff9bb5a9331d3410b1b448daaadc495e54", + "sha256:2781e9ad0e9d47173c0093321bb5435a9dfae0ed6a762aabafa13108f5f7b2ba", + "sha256:291f7c42e21d72144bb1c1b2e825ec60f46d0a7468f5346841860454c7aa8f57", + "sha256:2c5e309ec482556397cb21ede0350c5e82f0eb2621de04b2633588d118da4396", + "sha256:2e9c80a8c3344a92cb04661115898a9129c074f7ab82011ef4b612f645939f12", + "sha256:32a262e2b90ffcfdd97c7a5e24a6012a43c61f1f5a57789ad80af1d26c6acd97", + "sha256:3c9fff570f13480b201e9ab69453108f6d98244a7f495e91b6c654a47486ba43", + "sha256:415bdc7ca8c1c634a6d7163d43fb0ea885a07e9618a64bda407e04b04333b7db", + "sha256:4424e42199e86b21fc4db83bd76909a6fc2a2aefb352cb5414833c030f6ed71b", + "sha256:4a43c91840bda5f55249413037b7a9b79c90b1184ed504883b72c4df70778579", + "sha256:599a1e8ff057ac530c9ad1778293c665cb81a791421f46922d80a86473c13346", + "sha256:5c4fae4e9cdd18c82ba3a134be256e98dc0596af1e7285a3d2602c97dcfa5159", + "sha256:5ecfa867dea6fabe2a58f03ac9186ea64da1386af2159196da51c4904e11d652", + "sha256:62f2578358d3a92e4ab2d830cd1c2049c9c0d0e6d3c58322993cc341bdeac22e", + "sha256:6471a82d5abea994e38d2c2abc77164b4f7fbaaf80261cb98394d5793f11b12a", + "sha256:6d4f18483d040e18546108eb13b1dfa1000a089bcf8529e30346116ea6240506", + "sha256:71a608532ab3bd26223c8d841dde43f3516aa5d2bf37b50ac410bb5e99053e8f", + "sha256:74a1d8c85fb6ff0b30fbfa8ad0ac23cd601a138f7509dc617ebc65ef305bb98d", + "sha256:7b93a885bb13073afb0aa73ad82059a4c41f4b7d8eb8368980448b52d4c7dc2c", + "sha256:7d4751da932caaec419d514eaa4215eaf14b612cff66398dd51129ac22680b20", + "sha256:7f627141a26b551bdebbc4855c1157feeef18241b4b8366ed22a5c7d672ef858", + "sha256:8169cf44dd8f9071b2b9248c35fc35e8677451c52f795daa2bb4643f32a540bc", + "sha256:aa00d66c0fab27373ae44ae26a66a9e43ff2a678bf63a9c7c1a9a4d61172827a", + "sha256:ccb032fda0873254380aa2bfad2582aedc2959186cce61e3a17abc1a55ff89c3", + "sha256:d754f39e0d1603b5b24a7f8484b22d2904fa551fe865fd0d4c3332f078d20d4e", + "sha256:d75c461e20e29afc0aee7172a0950157c704ff0dd51613506bd7d82b718e7410", + "sha256:dcd65317dd15bc0451f3e01c80da2216a31916bdcffd6221ca1202d96584aa25", + "sha256:e570d3ab32e2c2861c4ebe6ffcad6a8abf9347432a37608fe1fbd157b3f0036b", + "sha256:fd43a88e045cf992ed09fa724b5315b790525f2676883a6ea64e3263bae6549d" + ], + "version": "==1.13.2" }, "chardet": { "hashes": [ @@ -162,10 +162,10 @@ }, "cytoolz": { "hashes": [ - "sha256:ed9f6a07c2bac70d6c597df360d0666d11d2adc90141d54c5c2db08b380a4fac" + "sha256:82f5bba81d73a5a6b06f2a3553ff9003d865952fcb32e1df192378dd944d8a5c" ], "markers": "implementation_name == 'cpython'", - "version": "==0.10.0" + "version": "==0.10.1" }, "docker": { "hashes": [ @@ -230,17 +230,17 @@ }, "eth-typing": { "hashes": [ - "sha256:164d5fb164636b62a5729557953edfadc91e4f1b055cd591cace24913a917764", - "sha256:901fcda2ab7cb4d92314484ce4730b6e02f1fccd97d3e20f72a9bdb531624c45" + "sha256:abcfd279ad708a6fe9daf2af2bebb47d3da0faa0bbcb3765294506025fe9dd8b", + "sha256:dab078dceb2b8687c9b94209e7a90da0fba5db075d132db997d9da9e2ead3465" ], - "version": "==2.1.0" + "version": "==2.2.0" }, "eth-utils": { "hashes": [ - "sha256:8f01c563fef5400c91465f33d584558e5384306d3b72b5a4fa2fcb2f6d7154e8", - "sha256:e874aa50ceeceb9e46d8a9cb566d2c010702b35eecde1c6b48c91c29e9d155d1" + "sha256:88a10ea8824042c589bbcec1516f363e96a1fbf52d0a8c8c761893a41cab8656", + "sha256:9ce4658025b8d063ddb5d781e53639725faf1f3f96a9861095f2e874e4a9fbda" ], - "version": "==1.7.0" + "version": "==1.8.0" }, "fetchai-ledger-api": { "hashes": [ @@ -409,11 +409,13 @@ }, "oef": { "hashes": [ - "sha256:bbe0d85b1fa104868f5971c3c56deefe1f5104c647fa795a84e68a8034e01480", - "sha256:c3c2ffef83b961febbf7deaf163601536e4332f0b96ff2518f2f4f7ec4fdafee" + "sha256:33ee394f10e22e46206a119fe79f349eed1738143df1acea39bba7ad84e8bf22", + "sha256:91a2983e5f1ee73306ea79fb5f23664b00dd9f9c36429cd1985a3c4e66f592e5", + "sha256:95a236f3672cc2dbf610a74e0932014fd1c2fc1dd539ae7b63e3d20febdc0ab1", + "sha256:a0d96210c687d365a037300556e105035da31636ba53edf2946543ca60b7bfc7" ], - "index": "test-pypi", - "version": "==0.6.10" + "index": "pypi", + "version": "==0.8.1" }, "openapi-spec-validator": { "hashes": [ @@ -467,42 +469,46 @@ }, "pycryptodome": { "hashes": [ - "sha256:023c294367d7189ae224fb61bc8d49a2347704087c1c78dbd5ab114dd5b97761", - "sha256:0f29e1238ad3b6b6e2acd7ea1d8e8b382978a56503f2c48b67d5dc144d143cb0", - "sha256:18f376698e3ddcb1d3b312512ca78c9eed132e68ac6d0bf2e72452dfe213e96f", - "sha256:1de815b847982f909dc2e5e2ca641b85cde80d95cc7e6a359c03d4b42cd21568", - "sha256:1ff619b8e4050799ca5ca0ffdf8eb0dbccba6997997866755f37e6aa7dde23fe", - "sha256:233a04bb7bdd4b07e14d61d5166150942d872802daa4f049d49a453fe0659e94", - "sha256:33c07e1e36ec84524b49f99f11804d5e4d2188c643e84d914cb1e0a277ed3c79", - "sha256:3701822a085dbebf678bfbdfbd6ebd92ffa80d5a544c9979984bf16a67c9790b", - "sha256:3f8e6851c0a45429f9b86c1597d3b831b0cff140b3e170a891fce55ef8dac2bb", - "sha256:4f6cdddf1fe72e7f173e9734aa19b94cbd046b61a8559d650ff222e36021d5c1", - "sha256:52d20b22c5b1fc952b4c686b99a6c55c3b0b0a673bec30570f156a72198f66ff", - "sha256:5452b534fecf8bf57cf9106d00877f5f4ab7264e7a5e1f5ea8d15b04517d1255", - "sha256:5a7a9a4a7f8f0990fa97fee71c7f7e0c412925c515cfc6d4996961e92c9be8e5", - "sha256:600bf9dd5fbed0feee83950e2a8baacaa1f38b56c237fff270d31e47f8da9e52", - "sha256:6840c9881e528224ebf72b3f73b3d11baf399e265106c9f4d9bae4f09615a93a", - "sha256:71b041d43fe13004abc36ca720ac64ea489ee8a3407a25116481d0faf9d62494", - "sha256:7252498b427c421e306473ed344e58235eedd95c15fec2e1b33d333aefa1ea10", - "sha256:8d2135c941d38f241e0e62dbdfc1ca5d9240527e61316126797f50b6f3e49825", - "sha256:a0962aea03933b99cf391c3e10dfef32f77915d5553464264cfbc6711f31d254", - "sha256:a117047a220b3911d425affcd1cbc97a1af7ea7eb5d985d9964d42b4f0558489", - "sha256:a35a5c588248ba00eb976a8554211e584a55de286783bc69b12bdd7954052b4a", - "sha256:c1a4f3f651471b9bf60b0d98fa8a994b8a73ff8ab4edc691e23243c853aaff9f", - "sha256:c419943306756ddd1a1997120bb073733bc223365909c68185106d5521cbc0ef", - "sha256:c453ad968b67d66448543420ec39770c30bd16d986058255f058ab87c4f6cc1f", - "sha256:d2d78644655629c7d1b9bf28e479d29facc0949d9ff095103ca9c2314b329ee0", - "sha256:d7be60dc2126ee350ac7191549f5ab05c2dd76a5d5a3022249f395a401c6ea37", - "sha256:dbeb08ad850056747aa7d5f33273b7ce0b9a77910604a1be7b7a6f2ef076213f", - "sha256:f02382dc1bf91fb7123f2a3851fb1b526c871fa9359f387f2bcc847efc74ae52" - ], - "version": "==3.9.0" + "sha256:0aa49f3fa110f8dc090bad1671a768cc17d3d3bd01566641ffc0d10d0fec8d49", + "sha256:0fafd3c4fb76c6992f34bf2d074f582f388e3b8062b8ba5d65b020634cc221e6", + "sha256:17eb9bd5d30a71b0c8a832e3e9cd2b7723f99907c38dc5dd23e59e8c368a70e2", + "sha256:2776255d5c748782f095ec422d42da2eadd8392ac9de7da23db4aed4231272bd", + "sha256:3500826dc3b9a8fdb762bebe551106081a6bdecd4181a3d1bd0206e48bba8974", + "sha256:3aa0d30326dcdef24c632d5c03b8e4d379c6ae0645082b27dd69ea816bb97ecb", + "sha256:3c7769bdadcc4809508e71997008912cc6d94fd7b5b1f3ef121683ebcac71d81", + "sha256:3e8c97a38dac6dafd180b4696a522b1581dd1a8e0ea60763458be547bac97361", + "sha256:5aca5125a46e458b308b5571ce8fe36d2229f161aa7db27b3ecacded70c6aa8b", + "sha256:62beb75f0688f406946312bfef8923d8ab23f5b8013acded931413625299d317", + "sha256:7725643de3c884a9945a086670787dce637037f32c5c2df7fd602bd5967f3486", + "sha256:872191a02a0c2a3b98dc75c62b32912b220a8ae5ff6ac9e39868f903f55dd6a4", + "sha256:8c501e80960d12328d49e1d409daf426f29364a37c602f257c99509999654650", + "sha256:9512638bfef8ffc94c62751965a4733c3792104dc84771ba54ce0f80f49134df", + "sha256:962043051afa7a5ab071b0d8996dc00e564327a18566d3e574a39cb6e097b462", + "sha256:9db72b18b30902a83fa57b0d7dae4ce24f85186695e3bea0d423f1ec7c5b3fbe", + "sha256:9ffd4f0bfb5949dfa0e5cedef836364f18da0deb2fba04671607fb3b59b29112", + "sha256:a26819f693cf5fc0a2373a3e4b91c38e359cad9f00020a885b667c77f28738d5", + "sha256:a3efc575a53511c48361d933e12e07c2eb940db1afda0995285176c372ab7352", + "sha256:ababd6685b9d94729a851a0615482156afdacbeaabeea60f67961db0e975b1af", + "sha256:b0e9c8c270cd3f8c73b53139f0708f257189a00bbc898be6d3f03995e5f7edc2", + "sha256:b74173b13c221ee96b608212b9adc2c459a73d3632f04490df42e4f07e7041e6", + "sha256:bed297f75ba19cefe2d10beb4959f4f8cb62c2560a3998ad87479485098ee939", + "sha256:c639f09e8ce8ad5af9884233f952ade4b73a11b7d41d3b9bb7d4e64d9e1df164", + "sha256:c7bc308be67288af1cd44668d59e36356f0ce518337899079ddb0235bd55db79", + "sha256:cca152dcebc318833ba70499190ce17ee81b525404e2a7548c77f52b439306a7", + "sha256:d5261d22bc3a54db26f11dabcda14bbaab72080977e083d795b4b1d1b510c774", + "sha256:d81111e3da7fc9eee825ba7d8a68b3c1464f41110ef98a7280e0c7fb82c91e73", + "sha256:d95fafa899abb9f82e55ff43f423e100784312b43932514f2c05d41cbb20323e", + "sha256:de411a64d4105d4424441833bd25943208e58c846abf981bba5bbeeba88a49c3", + "sha256:e02c7b3d05b88ff1a236e49a252b2bf8444d3a1d04a056784af766c0909eba36", + "sha256:fbafe9b01b717e0bfbc83cd740ff5bf5cdd3f208815be470ea203942b899bbdf" + ], + "version": "==3.9.1" }, "pyrsistent": { "hashes": [ - "sha256:34b47fa169d6006b32e99d4b3c4031f155e6e68ebcc107d6454852e8e0ee6533" + "sha256:eb6545dbeb1aa69ab1fb4809bfbf5a8705e44d92ef8fc7c2361682a47c46c778" ], - "version": "==0.15.4" + "version": "==0.15.5" }, "python-dotenv": { "hashes": [ @@ -547,10 +553,10 @@ }, "six": { "hashes": [ - "sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c", - "sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73" + "sha256:1f1b7d42e254082a9db6279deae68afb421ceba6158efa6131de7b3003ee93fd", + "sha256:30f610279e8b2578cab6db20741130331735c781b56053c59c4076da27f06b66" ], - "version": "==1.12.0" + "version": "==1.13.0" }, "toolz": { "hashes": [ @@ -580,11 +586,11 @@ }, "web3": { "hashes": [ - "sha256:a6bbf8090fd880be1a736872bf42163b44a56691d97b7e139d7f16247c1d7f8d", - "sha256:c0e070670a7d8c6ed4b8585347fec79a924c01520362ba41b411a5d0a9c8e6f9" + "sha256:1bcc729005c84b98dda696a7d8660964546bd8da2a0c4c4993c696e65ae473c8", + "sha256:639bbd428e565fe9bd3091029d84a9228f5d9e48e043efed43d616eb236d9182" ], "index": "pypi", - "version": "==5.2.1" + "version": "==5.2.2" }, "websocket-client": { "hashes": [ @@ -718,11 +724,11 @@ }, "flake8": { "hashes": [ - "sha256:19241c1cbc971b9962473e4438a2ca19749a7dd002dd1a946eaba171b4114548", - "sha256:8e9dfa3cecb2400b3738a42c54c3043e821682b9c840b0448c0503f781130696" + "sha256:45681a117ecc81e870cbf1262835ae4af5e7a8b08e40b944a8a6e6b895914cfb", + "sha256:49356e766643ad15072a789a20915d3c91dc89fd313ccd71802303fd67e4deca" ], "index": "pypi", - "version": "==3.7.8" + "version": "==3.7.9" }, "flake8-docstrings": { "hashes": [ @@ -948,18 +954,18 @@ }, "pyparsing": { "hashes": [ - "sha256:6f98a7b9397e206d78cc01df10131398f1c8b8510a2f4d97d9abd82e1aacdd80", - "sha256:d9338df12903bbf5d65a0e4e87c2161968b10d2e489652bb47001d82a9b028b4" + "sha256:4acadc9a2b96c19fe00932a38ca63e601180c39a189a696abce1eaab641447e1", + "sha256:61b5ed888beab19ddccab3478910e2076a6b5a0295dffc43021890e136edf764" ], - "version": "==2.4.2" + "version": "==2.4.4" }, "pytest": { "hashes": [ - "sha256:7e4800063ccfc306a53c461442526c5571e1462f61583506ce97e4da6a1d88c8", - "sha256:ca563435f4941d0cb34767301c27bc65c510cb82e90b9ecf9cb52dc2c63caaa0" + "sha256:27abc3fef618a01bebb1f0d6d303d2816a99aa87a5968ebc32fe971be91eb1e6", + "sha256:58cee9e09242937e136dbb3dab466116ba20d6b7828c7620f23947f37eb4dae4" ], "index": "pypi", - "version": "==5.2.1" + "version": "==5.2.2" }, "pytest-cov": { "hashes": [ @@ -990,10 +996,10 @@ }, "six": { "hashes": [ - "sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c", - "sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73" + "sha256:1f1b7d42e254082a9db6279deae68afb421ceba6158efa6131de7b3003ee93fd", + "sha256:30f610279e8b2578cab6db20741130331735c781b56053c59c4076da27f06b66" ], - "version": "==1.12.0" + "version": "==1.13.0" }, "snowballstemmer": { "hashes": [ @@ -1066,18 +1072,18 @@ }, "typing-extensions": { "hashes": [ - "sha256:2ed632b30bb54fc3941c382decfd0ee4148f5c591651c9272473fea2c6397d95", - "sha256:b1edbbf0652660e32ae780ac9433f4231e7339c7f9a8057d0f042fcbcea49b87", - "sha256:d8179012ec2c620d3791ca6fe2bf7979d979acdbef1fca0bc56b37411db682ed" + "sha256:091ecc894d5e908ac75209f10d5b4f118fbdb2eb1ede6a63544054bb1edb41f2", + "sha256:910f4656f54de5993ad9304959ce9bb903f90aadc7c67a0bef07e678014e892d", + "sha256:cf8b63fedea4d89bab840ecbb93e75578af28f76f66c35889bd7065f5af88575" ], - "version": "==3.7.4" + "version": "==3.7.4.1" }, "virtualenv": { "hashes": [ - "sha256:3e3597e89c73df9313f5566e8fc582bd7037938d15b05329c232ec57a11a7ad5", - "sha256:5d370508bf32e522d79096e8cbea3499d47e624ac7e11e9089f9397a0b3318df" + "sha256:11cb4608930d5fd3afb545ecf8db83fa50e1f96fc4fca80c94b07d2c83146589", + "sha256:d257bb3773e48cac60e475a19b608996c73f4d333b3ba2e4e57d5ac6134e0136" ], - "version": "==16.7.6" + "version": "==16.7.7" }, "virtualenv-clone": { "hashes": [ diff --git a/README.md b/README.md index f66852947d..c625049abe 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ A framework for autonomous economic agent (AEA) development This repository contains submodules. Clone with recursive strategy: - git clone git@github.com:fetchai/agents-aea.git --recursive && cd agents-aea + git clone https://github.com/fetchai/agents-aea.git --recursive && cd agents-aea ### Dependencies diff --git a/aea/__version__.py b/aea/__version__.py index d081cf74d9..c3a21af00a 100644 --- a/aea/__version__.py +++ b/aea/__version__.py @@ -23,7 +23,7 @@ __title__ = 'aea' __description__ = 'Autonomous Economic Agent framework' __url__ = 'https://github.com/fetchai/agents-aea.git' -__version__ = '0.1.12' +__version__ = '0.1.13' __author__ = 'Fetch.AI Limited' __license__ = 'Apache 2.0' __copyright__ = '2019 Fetch.AI Limited' diff --git a/aea/aea.py b/aea/aea.py index bdc8e0bba9..e970d2cf21 100644 --- a/aea/aea.py +++ b/aea/aea.py @@ -19,7 +19,6 @@ """This module contains the implementation of an Autonomous Economic Agent.""" import logging -from pathlib import Path from typing import Optional, cast from aea.agent import Agent @@ -31,7 +30,6 @@ from aea.registries.base import Filter, Resources from aea.skills.error.handlers import ErrorHandler - logger = logging.getLogger(__name__) @@ -42,10 +40,10 @@ def __init__(self, name: str, mailbox: MailBox, wallet: Wallet, ledger_apis: LedgerApis, + resources: Resources, timeout: float = 0.0, debug: bool = False, - max_reactions: int = 20, - directory: str = '') -> None: + max_reactions: int = 20) -> None: """ Instantiate the agent. @@ -53,18 +51,16 @@ def __init__(self, name: str, :param mailbox: the mailbox of the agent. :param wallet: the wallet of the agent. :param ledger_apis: the ledger apis of the agent. + :param resources: the resources of the agent. :param timeout: the time in (fractions of) seconds to time out an agent between act and react :param debug: if True, run the agent in debug mode. :param max_reactions: the processing rate of messages per iteration. - :param directory: the path to the agent's resource directory. - | If None, we assume the directory is in the working directory of the interpreter. :return: None """ super().__init__(name=name, wallet=wallet, timeout=timeout, debug=debug) self.max_reactions = max_reactions - self._directory = directory if directory else str(Path(".").absolute()) self.mailbox = mailbox self._decision_maker = DecisionMaker(self.name, @@ -76,13 +72,14 @@ def __init__(self, name: str, self.wallet.public_keys, self.wallet.addresses, ledger_apis, + self.mailbox.connection_status, self.outbox, self.decision_maker.message_in_queue, self.decision_maker.ownership_state, self.decision_maker.preferences, self.decision_maker.is_ready_to_pursuit_goals) - self._resources = None # type: Optional[Resources] - self._filter = None # type: Optional[Filter] + self._resources = resources + self._filter = Filter(self.resources, self.decision_maker.message_out_queue) @property def decision_maker(self) -> DecisionMaker: @@ -97,13 +94,16 @@ def context(self) -> AgentContext: @property def resources(self) -> Resources: """Get resources.""" - assert self._resources is not None, "No resources initialized. Call setup." return self._resources + @resources.setter + def resources(self, resources: 'Resources'): + """Set resources.""" + self._resources = resources + @property def filter(self) -> Filter: """Get filter.""" - assert self._filter is not None, "No filter initialized. Call setup." return self._filter def setup(self) -> None: @@ -112,10 +112,8 @@ def setup(self) -> None: :return: None """ - self._resources = Resources.from_resource_dir(self._directory, self.context) - assert self._resources is not None, "No resources initialized. Error in setup." - self._resources.setup() - self._filter = Filter(self.resources, self.decision_maker.message_out_queue) + self.resources.load(self.context) + self.resources.setup() def act(self) -> None: """ diff --git a/aea/cli/run.py b/aea/cli/run.py index cd19680744..634f774603 100644 --- a/aea/cli/run.py +++ b/aea/cli/run.py @@ -42,6 +42,7 @@ from aea.crypto.ledger_apis import LedgerApis, _try_to_instantiate_fetchai_ledger_api, _try_to_instantiate_ethereum_ledger_api, SUPPORTED_LEDGER_APIS from aea.crypto.wallet import Wallet, DEFAULT, SUPPORTED_CRYPTOS from aea.mail.base import MailBox +from aea.registries.base import Resources def _verify_or_create_private_keys(ctx: Context) -> None: @@ -197,7 +198,7 @@ def run(click_context, connection_name: str, env_file: str, install_deps: bool): connection_name = ctx.agent_config.default_connection if connection_name is None else connection_name _try_to_load_protocols(ctx) try: - connection = _setup_connection(connection_name, wallet.public_keys[DEFAULT], ctx) + connection = _setup_connection(connection_name, wallet.public_keys[FETCHAI], ctx) except AEAConfigException as e: logger.error(str(e)) sys.exit(1) @@ -209,7 +210,7 @@ def run(click_context, connection_name: str, env_file: str, install_deps: bool): click_context.invoke(install) mailbox = MailBox(connection) - agent = AEA(agent_name, mailbox, wallet, ledger_apis, directory=str(Path("."))) + agent = AEA(agent_name, mailbox, wallet, ledger_apis, resources=Resources(str(Path(".")))) try: agent.start() except KeyboardInterrupt: diff --git a/aea/cli_gui/__init__.py b/aea/cli_gui/__init__.py index 2fd2b2a791..ff4816ebfd 100644 --- a/aea/cli_gui/__init__.py +++ b/aea/cli_gui/__init__.py @@ -264,7 +264,7 @@ def stop_oef_node(): return "All fine", 200 # 200 (OK) -def start_agent(agent_id): +def start_agent(agent_id, connection_id): """Start a local agent running.""" # Test if it is already running in some form if agent_id in flask.app.agent_processes: @@ -279,7 +279,20 @@ def start_agent(agent_id): return {"detail": "Agent {} is already running".format(agent_id)}, 400 # 400 Bad request agent_dir = os.path.join(flask.app.agents_dir, agent_id) - agent_process = _call_aea_async(["aea", "run"], agent_dir) + + if connection_id is not None and connection_id != "": + connections = get_local_items(agent_id, "connection") + has_named_connection = False + for element in connections: + if element["id"] == connection_id: + has_named_connection = True + if has_named_connection: + agent_process = _call_aea_async(["aea", "run", "--connection", connection_id], agent_dir) + else: + return {"detail": "Trying to run agent {} with non-existant connection: {}".format(agent_id, connection_id)}, 400 # 400 Bad request + else: + agent_process = _call_aea_async(["aea", "run"], agent_dir) + if agent_process is None: return {"detail": "Failed to run agent {}".format(agent_id)}, 400 # 400 Bad request else: @@ -379,9 +392,8 @@ def _kill_running_oef_nodes(): subprocess.call(['docker', 'kill', oef_node_name]) -def run(): +def create_app(): """Run the flask server.""" - _kill_running_oef_nodes() CUR_DIR = os.path.abspath(os.path.dirname(__file__)) app = connexion.FlaskApp(__name__, specification_dir=CUR_DIR) flask.app.oef_process = None @@ -410,8 +422,23 @@ def favicon(): return flask.send_from_directory( os.path.join(app.root_path, 'static'), 'favicon.ico', mimetype='image/vnd.microsoft.icon') + return app + + +def run(): + """Run the GUI.""" + _kill_running_oef_nodes() + app = create_app() app.run(host='127.0.0.1', port=8080, debug=False) + return app + + +def run_test(): + """Run the gui in the form where we can run tests against it.""" + app = create_app() + return app.app.test_client() + # If we're running in stand alone mode, run the application if __name__ == '__main__': diff --git a/aea/cli_gui/aea_cli_rest.yaml b/aea/cli_gui/aea_cli_rest.yaml index 0493cc06e3..308105bd77 100644 --- a/aea/cli_gui/aea_cli_rest.yaml +++ b/aea/cli_gui/aea_cli_rest.yaml @@ -292,17 +292,21 @@ paths: description: id of agent to run type: string required: True + - name: connection_id + in: body + description: id f the connection to activate when running + schema: + type: string + required: True responses: 201: description: Start the OEF Nodoe schema: type: string - 400: description: Cannot start agent schema: type: string - get: operationId: aea.cli_gui.get_agent_status tags: diff --git a/aea/cli_gui/static/css/home.css b/aea/cli_gui/static/css/home.css index 91facdc8e1..ea32d223e4 100644 --- a/aea/cli_gui/static/css/home.css +++ b/aea/cli_gui/static/css/home.css @@ -155,5 +155,5 @@ td { background-color: black; color: red; font-family: "Courier New", Courier, monospace; - -} \ No newline at end of file +} +own:hover .dropbtn {background-color: #3e8e41;}w {display:block;} \ No newline at end of file diff --git a/aea/cli_gui/templates/home.html b/aea/cli_gui/templates/home.html index 7efcf6f887..c604fdb9e7 100644 --- a/aea/cli_gui/templates/home.html +++ b/aea/cli_gui/templates/home.html @@ -88,14 +88,17 @@

Selected {{htmlElements[i][1]}}:

Running "NONE" Agent

+ + +

Agent Status: NONE

-
-
+

-
-
+

@@ -129,8 +132,10 @@

Selected {{htmlElements[i][1]}}:
{% endif %} {% endfor %} +

OEF Node

+

OEF Node Status: NONE

diff --git a/aea/cli_gui/templates/home.js b/aea/cli_gui/templates/home.js index 93c4242347..cc949c13c3 100644 --- a/aea/cli_gui/templates/home.js +++ b/aea/cli_gui/templates/home.js @@ -220,12 +220,14 @@ class Model{ self.$event_pump.trigger('model_error', [xhr, textStatus, errorThrown]); }) } - startAgent(agentId){ + startAgent(agentId, runConnectionId){ var ajax_options = { type: 'POST', url: 'api/agent/' + agentId + '/run', accepts: 'application/json', - contentType: 'plain/text' + contentType: 'application/json', + dataType: 'json', + data: JSON.stringify(runConnectionId) }; var self = this; $.ajax(ajax_options) @@ -525,8 +527,9 @@ class Controller{ $('#startAgent').click({el: element}, function(e) { e.preventDefault(); var agentId = $('#localAgentsSelectionId').html() + var connectionId = $('#runConnectionId').val() if (self.validateId(agentId)){ - self.model.startAgent(agentId) + self.model.startAgent(agentId, connectionId) } else{ alert('Error: Attempting to start agent with ID: ' + agentId); diff --git a/aea/connections/base.py b/aea/connections/base.py index fdabb359f0..60d866462d 100644 --- a/aea/connections/base.py +++ b/aea/connections/base.py @@ -57,6 +57,31 @@ def send(self, envelope: 'Envelope') -> None: :return: None. """ + @abstractmethod + def receive(self) -> None: + """ + Receives an envelope. + + :return: None. + """ + + +class ConnectionStatus(object): + """The connection status class.""" + + def __init__(self): + """Initialize the connection status.""" + self._is_connected = False + + @property + def is_connected(self) -> bool: + """Check if the connection is established.""" + return self._is_connected + + @is_connected.setter + def is_connected(self, is_connected: bool) -> None: + self._is_connected = is_connected + class Connection(ABC): """Abstract definition of a connection.""" @@ -67,6 +92,12 @@ def __init__(self): """Initialize the connection.""" self.in_queue = Queue() self.out_queue = Queue() + self._connection_status = ConnectionStatus() + + @property + def connection_status(self) -> ConnectionStatus: + """Get the connection status.""" + return self._connection_status @abstractmethod def connect(self): @@ -76,11 +107,6 @@ def connect(self): def disconnect(self): """Tear down the connection.""" - @property - @abstractmethod - def is_established(self) -> bool: - """Check if the connection is established.""" - @abstractmethod def send(self, envelope: 'Envelope'): """Send a message.""" diff --git a/aea/connections/local/connection.py b/aea/connections/local/connection.py index 43df237139..ae945b23ef 100644 --- a/aea/connections/local/connection.py +++ b/aea/connections/local/connection.py @@ -305,6 +305,14 @@ def send(self, envelope: Envelope) -> None: """ return self.local_node.send(envelope) + def receive(self) -> None: + """ + Receives an envelope. + + :return: None. + """ + pass + class OEFLocalConnection(Connection): """ @@ -353,16 +361,12 @@ def _receive_loop(self): except queue.Empty: pass - @property - def is_established(self) -> bool: - """Return True if the connection has been established, False otherwise.""" - return self._connection is not None - def connect(self): """Connect to the local OEF Node.""" if self._stopped: self._stopped = False self._connection = self.channel.connect() + self.connection_status.is_connected = True self.in_thread = Thread(target=self._receive_loop) self.out_thread = Thread(target=self._fetch) self.in_thread.start() @@ -376,15 +380,24 @@ def disconnect(self): self.out_thread.join() self.in_thread = None self.out_thread = None + self.connection_status.is_connected = False self.channel.disconnect() self.stop() def send(self, envelope: Envelope): """Send a message.""" - if not self.is_established: + if not self.connection_status.is_connected: raise ConnectionError("Connection not established yet. Please use 'connect()'.") self.channel.send(envelope) + def receive(self) -> None: + """ + Receives an envelope. + + :return: None. + """ + pass + def stop(self): """Tear down the connection.""" self._connection = None diff --git a/aea/connections/oef/connection.py b/aea/connections/oef/connection.py index 6c5079d454..844a74df60 100644 --- a/aea/connections/oef/connection.py +++ b/aea/connections/oef/connection.py @@ -24,6 +24,7 @@ import pickle from queue import Empty, Queue from threading import Thread +import time from typing import List, Dict, Optional, cast import oef @@ -492,6 +493,14 @@ def send_oef_message(self, envelope: Envelope) -> None: else: raise ValueError("OEF request not recognized.") + def receive(self) -> None: + """ + Receives an envelope. + + :return: None. + """ + pass + class OEFConnection(Connection): """The OEFConnection connects the to the mailbox.""" @@ -510,13 +519,8 @@ def __init__(self, public_key: str, oef_addr: str, oef_port: int = 10000): self.channel = OEFChannel(public_key, oef_addr, oef_port, core=core, in_queue=self.in_queue) self._stopped = True - self._connected = False self.out_thread = None # type: Optional[Thread] - - @property - def is_established(self) -> bool: - """Get the connection status.""" - return self._connected + self._connection_check_thread = None # type: Optional[Thread] def _fetch(self) -> None: """ @@ -524,7 +528,7 @@ def _fetch(self) -> None: :return: None """ - while self._connected: + while self.connection_status.is_connected: try: msg = self.out_queue.get(block=True, timeout=1.0) self.send(msg) @@ -536,20 +540,56 @@ def connect(self) -> None: Connect to the channel. :return: None - :raises ConnectionError if the connection to the OEF fails. + :raises Exception if the connection to the OEF fails. """ - if self._stopped and not self._connected: + if self._connection_check_thread is not None: + self._connection_check_thread.join() + self._connection_check_thread = None + if self._stopped and not self.connection_status.is_connected: self._stopped = False self._core.run_threaded() - try: - if not self.channel.connect(): - raise ConnectionError("Cannot connect to OEFChannel.") - self._connected = True - self.out_thread = Thread(target=self._fetch) - self.out_thread.start() - except ConnectionError as e: - self._core.stop() - raise e + self._try_connect() + self._connection_check_thread = Thread(target=self._connection_check) + self._connection_check_thread.start() + + def _try_connect(self) -> None: + """ + Try connect to the channel. + + :return: None + :raises Exception if the connection to the OEF fails. + """ + try: + while not self.connection_status.is_connected and not self._stopped: + if self.channel.connect(): + self.connection_status.is_connected = True + self.out_thread = Thread(target=self._fetch) + self.out_thread.start() + else: + logger.warning("Cannot connect to OEFChannel. Retrying in 5 seconds ...") + time.sleep(5.0) + except Exception as e: + self._core.stop() + raise e + + def _connection_check(self) -> None: + """ + Check for connection to the channel. + + Try to reconnect if connection is dropped. + + :return: None + """ + while self.connection_status.is_connected: + assert self.out_thread is not None, "Call connect before _connection_check." + time.sleep(2.0) + if not self.channel.get_state() == "connected": # type: ignore + logger.warning("Lost connection to OEFChannel. Retrying to connect soon ...") + self.connection_status.is_connected = False + self.out_thread.join() + self.out_thread = None + self._try_connect() + logger.warning("Successfully re-established connection to OEFChannel.") def disconnect(self) -> None: """ @@ -558,8 +598,11 @@ def disconnect(self) -> None: :return: None """ assert self.out_thread is not None, "Call connect before disconnect." - if not self._stopped and self._connected: - self._connected = False + assert self._connection_check_thread is not None, "Call connect before disconnect." + if not self._stopped and self.connection_status.is_connected: + self.connection_status.is_connected = False + self._connection_check_thread.join() + self._connection_check_thread = None self.out_thread.join() self.out_thread = None self.channel.disconnect() @@ -572,7 +615,7 @@ def send(self, envelope: Envelope): :return: None """ - if self._connected: + if self.connection_status.is_connected: self.channel.send(envelope) @classmethod diff --git a/aea/connections/oef/connection.yaml b/aea/connections/oef/connection.yaml index 6a69ed33d1..82ade8f157 100644 --- a/aea/connections/oef/connection.yaml +++ b/aea/connections/oef/connection.yaml @@ -15,4 +15,4 @@ config: port: ${OEF_PORT:10000} dependencies: - colorlog - - oef + - oef==0.8.1 diff --git a/aea/connections/scaffold/connection.py b/aea/connections/scaffold/connection.py index 0200cb9bf7..de4b156bf3 100644 --- a/aea/connections/scaffold/connection.py +++ b/aea/connections/scaffold/connection.py @@ -59,6 +59,14 @@ def send(self, envelope: Envelope) -> None: """ raise NotImplementedError # pragma: no cover + def receive(self) -> None: + """ + Process the envelopes. + + :return: None + """ + raise NotImplementedError # pragma: no cover + def disconnect(self) -> None: """ Disconnect. @@ -81,11 +89,6 @@ def __init__(self, public_key: str): self.public_key = public_key self.channel = MyScaffoldChannel(public_key) - @property - def is_established(self) -> bool: - """Return True if the connection has been established, False otherwise.""" - raise NotImplementedError # pragma: no cover - def connect(self) -> None: """ Connect to the gym. diff --git a/aea/connections/stub/connection.py b/aea/connections/stub/connection.py index e47d65bd43..0f73a6b2bd 100644 --- a/aea/connections/stub/connection.py +++ b/aea/connections/stub/connection.py @@ -121,7 +121,6 @@ def __init__(self, input_file_path: Union[str, Path], output_file_path: Union[st self.input_file = open(input_file_path, "rb+", buffering=1) self.output_file = open(output_file_path, "wb+", buffering=1) - self._stopped = True self._observer = Observer() self._fetch_thread = Thread(target=self._fetch) @@ -129,11 +128,6 @@ def __init__(self, input_file_path: Union[str, Path], output_file_path: Union[st self._event_handler = _ConnectionFileSystemEventHandler(self, input_file_path) self._observer.schedule(self._event_handler, dir) - @property - def is_established(self) -> bool: - """Get the connection status.""" - return not self._stopped - def receive(self) -> None: """Receive new messages, if any.""" line = self.input_file.readline() @@ -159,13 +153,13 @@ def connect(self) -> None: In this type of connection there's no channel to connect. """ - if self._stopped: - self._stopped = False + if not self.connection_status.is_connected: + self.connection_status.is_connected = True try: self._observer.start() self._fetch_thread.start() except Exception as e: # pragma: no cover - self._stopped = True + self.connection_status.is_connected = False raise e self.receive() @@ -176,13 +170,13 @@ def disconnect(self) -> None: In this type of connection there's no channel to disconnect. """ - if not self._stopped: - self._stopped = True + if self.connection_status.is_connected: + self.connection_status.is_connected = False self._fetch_thread.join() try: self._observer.stop() except Exception as e: # pragma: no cover - self._stopped = False + self.connection_status.is_connected = True raise e def _fetch(self) -> None: @@ -191,7 +185,7 @@ def _fetch(self) -> None: :return: None """ - while not self._stopped: + while self.connection_status.is_connected: try: msg = self.out_queue.get(block=True, timeout=1.0) self.send(msg) diff --git a/aea/connections/tcp/base.py b/aea/connections/tcp/base.py index 61ed7c63e6..f8f37e772b 100644 --- a/aea/connections/tcp/base.py +++ b/aea/connections/tcp/base.py @@ -55,7 +55,6 @@ def __init__(self, self._lock = threading.Lock() self._stopped = True - self._connected = False self._thread_loop = None # type: Optional[Thread] self._recv_task = None # type: Optional[Future] self._fetch_task = None # type: Optional[Future] @@ -102,11 +101,6 @@ def select_writer_from_envelope(self, envelope: Envelope) -> Optional[StreamWrit :return: the stream writer to communicate with the recipient. None if it cannot be determined. """ - @property - def is_established(self): - """Check if the connection is established.""" - return not self._stopped and self._connected - def connect(self): """ Set up the connection. @@ -116,7 +110,7 @@ def connect(self): """ with self._lock: try: - if self.is_established: + if self.connection_status.is_connected: logger.warning("Connection already set up.") return @@ -126,12 +120,12 @@ def connect(self): self.setup() - self._connected = True + self.connection_status.is_connected = True except Exception as e: logger.error(str(e)) if not self._is_threaded: self._stop_loop() - self._connected = False + self.connection_status.is_connected = False self._stopped = True def disconnect(self) -> None: @@ -141,11 +135,11 @@ def disconnect(self) -> None: :return: None. """ with self._lock: - if not self.is_established: + if not self.connection_status.is_connected: logger.warning("Connection is not set up.") return - self._connected = False + self.connection_status.is_connected = False self.teardown() if not self._is_threaded: self._stop_loop() @@ -155,7 +149,7 @@ async def _recv(self, reader: StreamReader) -> Optional[bytes]: """Receive bytes.""" try: data = await reader.read(len(struct.pack("I", 0))) - if not self._connected: + if not self.connection_status.is_connected: return None nbytes = struct.unpack("I", data)[0] nbytes_read = 0 diff --git a/aea/context/base.py b/aea/context/base.py index 8310e9e1d2..d6ec4d01e3 100644 --- a/aea/context/base.py +++ b/aea/context/base.py @@ -22,8 +22,11 @@ from queue import Queue from typing import Dict +from aea.connections.base import ConnectionStatus from aea.decision_maker.base import OwnershipState, Preferences from aea.mail.base import OutBox +from aea.crypto.default import DEFAULT +from aea.crypto.fetchai import FETCHAI from aea.crypto.ledger_apis import LedgerApis @@ -34,6 +37,7 @@ def __init__(self, agent_name: str, public_keys: Dict[str, str], addresses: Dict[str, str], ledger_apis: LedgerApis, + connection_status: ConnectionStatus, outbox: OutBox, decision_maker_message_queue: Queue, ownership_state: OwnershipState, @@ -45,6 +49,7 @@ def __init__(self, agent_name: str, :param agent_name: the agent's name :param public_keys: the public keys of the agent :param ledger_apis: the ledger apis + :param connection_status: the connection status :param outbox: the outbox :param decision_maker_message_queue: the (in) queue of the decision maker :param ownership_state: the ownership state of the agent @@ -55,6 +60,7 @@ def __init__(self, agent_name: str, self._public_keys = public_keys self._addresses = addresses self._ledger_apis = ledger_apis + self._connection_status = connection_status self._outbox = outbox self._decision_maker_message_queue = decision_maker_message_queue self._ownership_state = ownership_state @@ -79,12 +85,17 @@ def addresses(self) -> Dict[str, str]: @property def address(self) -> str: """Get the defualt address.""" - return self._addresses['default'] + return self._addresses[FETCHAI] if FETCHAI in self._addresses.keys() else self._addresses[DEFAULT] @property def public_key(self) -> str: """Get the default public key.""" - return self._public_keys['default'] + return self._public_keys[FETCHAI] if FETCHAI in self._public_keys.keys() else self._public_keys[DEFAULT] + + @property + def connection_status(self) -> ConnectionStatus: + """Get connection status.""" + return self._connection_status @property def outbox(self) -> OutBox: diff --git a/aea/crypto/ledger_apis.py b/aea/crypto/ledger_apis.py index e3e10d94b7..9d3c818a6d 100644 --- a/aea/crypto/ledger_apis.py +++ b/aea/crypto/ledger_apis.py @@ -28,6 +28,7 @@ import web3 import web3.exceptions from fetchai.ledger.api import LedgerApi as FetchLedgerApi +# from fetchai.ledger.api.tx import TxStatus from web3 import Web3, HTTPProvider from aea.crypto.base import Crypto @@ -42,6 +43,9 @@ GAS_PRICE = '50' GAS_ID = 'gwei' +UNKNOWN = "UNKNOWN" +OK = "OK" +ERROR = "ERROR" class LedgerApis(object): @@ -55,7 +59,9 @@ def __init__(self, ledger_api_configs: Dict[str, Tuple[str, int]]): """ apis = {} # type: Dict[str, Any] configs = {} # type: Dict[str, Tuple[str, int]] + self._last_tx_statuses = {} # type: Dict[str, str] for identifier, config in ledger_api_configs.items(): + self._last_tx_statuses[identifier] = UNKNOWN if identifier == FETCHAI: api = FetchLedgerApi(config[0], config[1]) apis[identifier] = api @@ -81,15 +87,20 @@ def apis(self) -> Dict[str, Any]: return self._apis @property - def has_fetchai(self): + def has_fetchai(self) -> bool: """Check if it has the fetchai API.""" return FETCHAI in self.apis.keys() @property - def has_ethereum(self): + def has_ethereum(self) -> bool: """Check if it has the ethereum API.""" return ETHEREUM in self.apis.keys() + @property + def last_tx_statuses(self) -> Dict[str, str]: + """Get the statuses for the last transaction.""" + return self._last_tx_statuses + def token_balance(self, identifier: str, address: str) -> int: """ Get the token balance. @@ -103,17 +114,21 @@ def token_balance(self, identifier: str, address: str) -> int: if identifier == FETCHAI: try: balance = api.tokens.balance(address) + self._last_tx_statuses[identifier] = OK except Exception: logger.warning("An error occurred while attempting to get the current balance.") balance = 0 + self._last_tx_statuses[identifier] = ERROR elif identifier == ETHEREUM: try: balance = api.eth.getBalance(address) + self._last_tx_statuses[identifier] = OK except Exception: logger.warning("An error occurred while attempting to get the current balance.") balance = 0 - else: # pragma: no cover - balance = 0 + self._last_tx_statuses[identifier] = ERROR + else: # pragma: no cover + raise Exception("Ledger id is not known") return balance def transfer(self, identifier: str, crypto_object: Crypto, destination_address: str, amount: int, tx_fee: int) -> Optional[str]: @@ -136,39 +151,45 @@ def transfer(self, identifier: str, crypto_object: Crypto, destination_address: tx_digest = api.tokens.transfer(crypto_object.entity, destination_address, amount, tx_fee) api.sync(tx_digest) logger.info("Transaction validated ...") + self._last_tx_statuses[identifier] = OK except Exception: logger.warning("An error occurred while attempting the transfer.") tx_digest = None + self._last_tx_statuses[identifier] = ERROR elif identifier == ETHEREUM: - - nonce = api.eth.getTransactionCount(api.toChecksumAddress(crypto_object.address)) - # TODO : handle misconfiguration - chain_id = self.configs.get(identifier)[1] # type: ignore - transaction = { - 'nonce': nonce, - 'chainId': chain_id, - 'to': destination_address, - 'value': amount, - 'gas': tx_fee, - 'gasPrice': api.toWei(GAS_PRICE, GAS_ID) - } - signed = api.eth.account.signTransaction(transaction, crypto_object.entity.privateKey) - hex_value = api.eth.sendRawTransaction(signed.rawTransaction) - print("TX Hash: ", hex_value.hex()) - print("connect_to https://ropsten.etherscan.io/tx/{}".format(hex_value.hex())) - while True: - try: - api.eth.getTransactionReceipt(hex_value) - logger.info("transaction validated - exiting") - tx_digest = hex_value.hex() - break - except web3.exceptions.TransactionNotFound: - logger.info("transaction not found - sleeping for 3.0 seconds") - time.sleep(3.0) - - return tx_digest - else: # pragma: no cover - tx_digest = None + try: + nonce = api.eth.getTransactionCount(api.toChecksumAddress(crypto_object.address)) + # TODO : handle misconfiguration + chain_id = self.configs.get(identifier)[1] # type: ignore + transaction = { + 'nonce': nonce, + 'chainId': chain_id, + 'to': destination_address, + 'value': amount, + 'gas': tx_fee, + 'gasPrice': api.toWei(GAS_PRICE, GAS_ID) + } + signed = api.eth.account.signTransaction(transaction, crypto_object.entity.privateKey) + hex_value = api.eth.sendRawTransaction(signed.rawTransaction) + logger.info("TX Hash: {}".format(str(hex_value.hex()))) + while True: + try: + api.eth.getTransactionReceipt(hex_value) + logger.info("transaction validated - exiting") + tx_digest = hex_value.hex() + self._last_tx_statuses[identifier] = OK + break + except web3.exceptions.TransactionNotFound: # pragma: no cover + logger.info("transaction not found - sleeping for 3.0 seconds") + self._last_tx_statuses[identifier] = ERROR + time.sleep(3.0) + return tx_digest + except Exception: + logger.warning("An error occurred while attempting the transfer.") + tx_digest = None + self._last_tx_statuses[identifier] = ERROR + else: # pragma: no cover + raise Exception("Ledger id is not known") return tx_digest def is_tx_settled(self, identifier: str, tx_digest: str, amount: int) -> bool: @@ -187,12 +208,17 @@ def is_tx_settled(self, identifier: str, tx_digest: str, amount: int) -> bool: logger.info("Checking the transaction ...") # tx_status = cast(TxStatus, api.tx.status(tx_digest)) tx_status = cast(str, api.tx.status(tx_digest)) + # if tx_status.successful: if tx_status in SUCCESSFUL_TERMINAL_STATES: + # tx_contents = cast(TxContents, api.tx.contents(tx_digest)) + # tx_contents.transfers_to() # TODO: check the amount of the transaction is correct is_successful = True logger.info("Transaction validated ...") + self._last_tx_statuses[identifier] = OK except Exception: logger.warning("An error occurred while attempting to check the transaction.") + self._last_tx_statuses[identifier] = ERROR elif identifier == ETHEREUM: try: logger.info("Checking the transaction ...") @@ -200,8 +226,10 @@ def is_tx_settled(self, identifier: str, tx_digest: str, amount: int) -> bool: if tx_status is not None: is_successful = True logger.info("Transaction validated ...") + self._last_tx_statuses[identifier] = OK except Exception: logger.warning("An error occured while attempting to check the transaction!") + self._last_tx_statuses[identifier] = ERROR return is_successful diff --git a/aea/mail/base.py b/aea/mail/base.py index 53a28b09b4..1c357903de 100644 --- a/aea/mail/base.py +++ b/aea/mail/base.py @@ -24,6 +24,7 @@ from queue import Queue from typing import Optional, TYPE_CHECKING +from aea.connections.base import ConnectionStatus from aea.configurations.base import Address, ProtocolId from aea.mail import base_pb2 if TYPE_CHECKING: @@ -283,7 +284,12 @@ def __init__(self, connection: 'Connection'): @property def is_connected(self) -> bool: """Check whether the mailbox is processing messages.""" - return self._connection.is_established + return self._connection.connection_status.is_connected + + @property + def connection_status(self) -> ConnectionStatus: + """Get the connection status.""" + return self._connection.connection_status def connect(self) -> None: """Connect.""" diff --git a/aea/registries/base.py b/aea/registries/base.py index 16b6ee859e..8b3d28cca8 100644 --- a/aea/registries/base.py +++ b/aea/registries/base.py @@ -28,7 +28,7 @@ from abc import ABC, abstractmethod from queue import Queue from pathlib import Path -from typing import Optional, List, Dict, Any, Tuple, cast +from typing import Optional, List, Dict, Any, Tuple, cast, Union from aea.configurations.base import ProtocolId, SkillId, ProtocolConfig, DEFAULT_PROTOCOL_CONFIG_FILE from aea.configurations.loader import ConfigLoader @@ -458,8 +458,9 @@ def teardown(self) -> None: class Resources(object): """This class implements the resources of an AEA.""" - def __init__(self): + def __init__(self, directory: Optional[Union[str, os.PathLike]] = None): """Instantiate the resources.""" + self._directory = str(Path(directory).absolute()) if directory is not None else str(Path(".").absolute()) self.protocol_registry = ProtocolRegistry() self.handler_registry = HandlerRegistry() self.behaviour_registry = BehaviourRegistry() @@ -468,19 +469,15 @@ def __init__(self): self._registries = [self.protocol_registry, self.handler_registry, self.behaviour_registry, self.task_registry] - @classmethod - def from_resource_dir(cls, directory: str, agent_context: AgentContext) -> Optional['Resources']: - """ - Parse the resource directory. + @property + def directory(self) -> str: + """Get the directory.""" + return self._directory - :param directory: the agent's resources directory. - :param agent_context: the agent's context object - :return: None - """ - resources = Resources() - resources.protocol_registry.populate(directory) - resources.populate_skills(directory, agent_context) - return resources + def load(self, agent_context: AgentContext) -> None: + """Load all the resources.""" + self.protocol_registry.populate(self.directory) + self.populate_skills(self.directory, agent_context) def populate_skills(self, directory: str, agent_context: AgentContext) -> None: """ @@ -567,7 +564,6 @@ def __init__(self, resources: Resources, decision_maker_out_queue: Queue): @property def resources(self) -> Resources: """Get resources.""" - assert self._resources is not None, "No resources initialized. Call setup." return self._resources @property @@ -575,7 +571,7 @@ def decision_maker_out_queue(self) -> Queue: """Get decision maker (out) queue.""" return self._decision_maker_out_queue - def get_active_handlers(self, protocol_id: str) -> List[Handler]: + def get_active_handlers(self, protocol_id: str) -> Optional[List[Handler]]: """ Get active handlers. diff --git a/aea/skills/base.py b/aea/skills/base.py index 207f1a2886..75e5bc69cb 100644 --- a/aea/skills/base.py +++ b/aea/skills/base.py @@ -28,6 +28,7 @@ from queue import Queue from typing import Optional, List, Dict, Any, cast +from aea.connections.base import ConnectionStatus from aea.configurations.base import BehaviourConfig, HandlerConfig, TaskConfig, SharedClassConfig, SkillConfig, \ ProtocolId, DEFAULT_SKILL_CONFIG_FILE from aea.configurations.loader import ConfigLoader @@ -78,6 +79,11 @@ def agent_address(self) -> str: """Get address.""" return self._agent_context.address + @property + def connection_status(self) -> ConnectionStatus: + """Get connection status.""" + return self._agent_context.connection_status + @property def outbox(self) -> OutBox: """Get outbox.""" diff --git a/deploy-image/docker-env.sh b/deploy-image/docker-env.sh index 06ca5fa060..200d5ffd94 100755 --- a/deploy-image/docker-env.sh +++ b/deploy-image/docker-env.sh @@ -1,7 +1,7 @@ #!/bin/bash # Swap the following lines if you want to work with 'latest' -DOCKER_IMAGE_TAG=aea-deploy:0.1.12 +DOCKER_IMAGE_TAG=aea-deploy:0.1.13 # DOCKER_IMAGE_TAG=aea-deploy:latest DOCKER_BUILD_CONTEXT_DIR=.. diff --git a/develop-image/docker-env.sh b/develop-image/docker-env.sh index 5a6f5e5bd4..b13150ad5a 100755 --- a/develop-image/docker-env.sh +++ b/develop-image/docker-env.sh @@ -1,7 +1,7 @@ #!/bin/bash # Swap the following lines if you want to work with 'latest' -DOCKER_IMAGE_TAG=aea-develop:0.1.12 +DOCKER_IMAGE_TAG=aea-develop:0.1.13 # DOCKER_IMAGE_TAG=aea-develop:latest DOCKER_BUILD_CONTEXT_DIR=.. diff --git a/docs/app-areas.md b/docs/app-areas.md index ee9ba036f1..07e39b36b0 100644 --- a/docs/app-areas.md +++ b/docs/app-areas.md @@ -8,13 +8,13 @@ There are five general application areas for Fetch.ai AEAs. * **Digital data sales agents**: pure software agents that attach to data sources and sell it via the open economic framework. * **Representative**: an agent which represents an individual's activities on the Fetch.ai network. -## Multi-agent system vs agent-based modelling +## Multi-agent system versus agent-based modelling -The Fetch.ai multi agent system is a real world multi-agent technological system and, although there is some overlap, it is not the same as agent based modelling where the goal is scientific behaviourial observation rather than practical economic gain. +The Fetch.ai multi agent system is a real world multi-agent technological system and, although there is some overlap, it is not the same as agent based modelling where the goal is scientific behavioural observation rather than practical economic gain. Moreover, there is no restriction to *multi*. Single-agent applications are also possible. -
\ No newline at end of file +
diff --git a/docs/car-park-alt.md b/docs/car-park-alt.md new file mode 100644 index 0000000000..b5698f34bc --- /dev/null +++ b/docs/car-park-alt.md @@ -0,0 +1,213 @@ +!!! Warning + Work in progress. + + + +# Car Park Agent Application + +The Fetch.ai car park agent application is made up of three components: + +* A Raspberry Pi hardware device with camera module viewing a car park. +* A car park agent GUI running on the Raspberry Pi that collects data on free spaces and serves it up for purchase on the Fetch.ai network. +* A remote client agent GUI that connects to the Raspberry Pi and buys data. + + +| Hardware | Car Park Agent GUI | Client Agent GUI | +| ------------- |:-------------:|:-----:| +| | | | + + + + +## Raspberry Pi hardware set up + +The hardware set up is the most time consuming part of these instructions. + +We assume the developer has some familiarity with Raspberry Pi and refer them to the manufacturer's instructions. However, we do list any problems we encountered and their solutions below. + +!!! Note + We have used the Raspberry Pi 4. You are welcome to use earlier versions but the set up may change slightly. + +Follow the manufacturer's instructions to set up the Raspberry Pi 4 hardware device: https://projects.raspberrypi.org/en/projects/raspberry-pi-setting-up/2. + + +### Install and update the OS + +Install the Raspbian OS and update it. + +``` bash +sudo apt update -y +sudo apt-get update +sudo apt-get dist-upgrade +sudo reboot +``` + +### Enable camera, ssh, and VNC + +Click on the Raspberry symbol in the top left of the screen. + +Select Preferences then Raspberry Pi Configuration. + +
+ +
+
+ +Enable the Camera, SSH, and VNC options. +
+ +
+ + +### Set up camera module + +Follow the manufacturer's instructions to set up the Raspberry Pi Camera module: https://projects.raspberrypi.org/en/projects/getting-started-with-picamera. + +Configure and test the camera software: https://www.raspberrypi.org/documentation/configuration/camera.md. + +Set up your Pi to physically view the car park. We'll leave that to you. + + +### Potential issues + +1. Make sure you use the first port, `HDMI 0` on the Pi for the initial set up monitor. +2. If you install the Pi with a used SD card, you will need to reformat the card with NOOBS: https://www.raspberrypi.org/downloads/noobs/. +3. Fix the screen resolution issues by editing the configuration. + +``` bash +sudo raspi-config +``` +Select the `1920X1080` resolution option - number 31. + +Then update the configuration file as follows. Open it. + +``` bash +sudo nano /boot/config.txt +``` +And make sure the following three lines are commented out. + +``` bash +# Enable DRM VC4 V3D driver on top of the dispmanx display stack +# dtoverlay=vc4-fkms-v3d +# max_framebuffers=2 +``` + + + +## Agent server application installation + +Now we are ready to install the car park agent GUI server application on the Raspberry Pi. + +### Get the code + +``` bash +cd ~/Desktop +git clone https://github.com/fetchai/carpark_agent.git +cd carpark_agent +``` + +### Download datafile + +This is required for the machine learning algorithms. + +``` bash +./car_detection/weights/download_weights.sh +``` +Install the required libraries. + +``` bash +sudo apt-get install gcc htop vim mc python3-dev ffmpeg virtualenv libatlas-base-dev libsm6 libxext6 clang libblas3 liblapack3 liblapack-dev libblas-dev cython gfortran build-essential libgdal-dev libopenblas-dev liblapack3 liblapacke liblapacke-dev liblcms2-utils liblcms2-2 libwebpdemux2 python3-scipy python3-numpy python3-matplotlib libjasper-dev libqtgui4 libqt4-test protobuf-compiler python3-opencv gpsd gpsd-clients +``` + +### Activate a virtual environment. + +``` bash +pip3 install virtualenv +./run_scripts/create_venv.sh +source venv/bin/activate +``` + +### Install the software + +``` bash +python setup.py develop +``` + +!!! Note + We recommend that using `develop` as this creates a link to the code and so any changes you make will take immediate effect when you run the code. + + +### Run it + +``` bash +./run_scripts/run_carpark_agent.sh +``` +You should now see the agent running. + +### Ensure agent start on boot (RPi4 only) + +Ensure the startup script runs whenever we the Raspberry Pi turns on. + +``` bash +crontab -e +``` +Pick an editor which will open a text file. Scroll to the bottom and add the following line. + +``` bash +@reboot /home/pi/Desktop/carpark_agent/run_scripts/run_carpark_agent.sh +``` + +Save and reboot. The agent should now start automatically on reboot. + + +### Get the Pi's ip address + +We will need the ip address of the Raspberry Pi to connect remotely. + +``` bash +ifconfig +``` +Returns something like: +``` bash +... +wlan0: flags=4163 mtu 1500 + inet 192.168.11.9 netmask 255.255.255.0 broadcast 192.168.11.255 +... +``` +The `inet` value is the Raspberry Pi's ip address. + + + + +### Connect to the app remotely + +Download and install the VNC viewer onto your remote laptop: https://www.realvnc.com/en/connect/download/viewer/. + +Add the Pi's ip address. You will be prompted for the Raspberry Pi password. The Raspberry Pi's desktop should appear. + + +### Get your remote desktop ip + +Follow the instructions to get your remote ip address. + +### Connect to the Raspberry Pi + +Start up the agent, if it is not running - but it should be. + +``` bash +cd Desktop/carpark_agent +./run_scripts/run_carpark_agent.sh +``` + +# STOPPED HERE - Monday 28th October +# The Pi server app instructions come next + +From "When it starts up and you see the output from the camera, you can move your camera around so it is looking at the area... \ No newline at end of file diff --git a/docs/car-park.md b/docs/car-park.md index aa40a2d7d0..39bb6101a6 100644 --- a/docs/car-park.md +++ b/docs/car-park.md @@ -1,213 +1,58 @@ -!!! Warning - Work in progress. - - - # Car Park Agent Application -The Fetch.ai car park agent application is made up of three components: - -* A Raspberry Pi hardware device with camera module viewing a car park. -* A car park agent GUI running on the Raspberry Pi that collects data on free spaces and serves it up for purchase on the Fetch.ai network. -* A remote client agent GUI that connects to the Raspberry Pi and buys data. - - -| Hardware | Car Park Agent GUI | Client Agent GUI | -| ------------- |:-------------:|:-----:| -| | | | - - - +The Fetch.ai car park agent demo is documented in its own repo [here](https://github.com/fetchai/carpark_agent). -## Raspberry Pi hardware set up -The hardware set up is the most time consuming part of these instructions. +## To test the AEA functionality (without the detection) -We assume the developer has some familiarity with Raspberry Pi and refer them to the manufacturer's instructions. However, we do list any problems we encountered and their solutions below. - -!!! Note - We have used the Raspberry Pi 4. You are welcome to use earlier versions but the set up may change slightly. - -Follow the manufacturer's instructions to set up the Raspberry Pi 4 hardware device: https://projects.raspberrypi.org/en/projects/raspberry-pi-setting-up/2. - - -### Install and update the OS - -Install the Raspbian OS and update it. - -``` bash -sudo apt update -y -sudo apt-get update -sudo apt-get dist-upgrade -sudo reboot +First, create the carpark detection agent: ``` - -### Enable camera, ssh, and VNC - -Click on the Raspberry symbol in the top left of the screen. - -Select Preferences then Raspberry Pi Configuration. - -
- -
-
- -Enable the Camera, SSH, and VNC options. -
- -
- - -### Set up camera module - -Follow the manufacturer's instructions to set up the Raspberry Pi Camera module: https://projects.raspberrypi.org/en/projects/getting-started-with-picamera. - -Configure and test the camera software: https://www.raspberrypi.org/documentation/configuration/camera.md. - -Set up your Pi to physically view the car park. We'll leave that to you. - - -### Potential issues - -1. Make sure you use the first port, `HDMI 0` on the Pi for the initial setup monitor. -2. If you install the Pi with a used SD card, you will need to reformat the card with NOOBS: https://www.raspberrypi.org/downloads/noobs/. -3. Fix the screen resolution issues by editing the configuration. - -``` bash -sudo raspi-config +aea create car_detector +cd car_detector +aea add skill carpark_detection +aea install ``` -Select the `1920X1080` resolution option - number 31. -Then update the configuration file as follows. Open it. - -``` bash -sudo nano /boot/config.txt +Then, create the carpark client agent: ``` -And make sure the following three lines are commented out. - -``` bash -# Enable DRM VC4 V3D driver on top of the dispmanx display stack -# dtoverlay=vc4-fkms-v3d -# max_framebuffers=2 +aea create car_data_buyer +cd car_data_buyer +aea add skill carpark_client +aea install +aea generate-key fetchai ``` - - -## Agent server application installation - -Now we are ready to install the car park agent GUI server application on the Raspberry Pi. - -### Get the code - -``` bash -cd ~/Desktop -git clone https://github.com/fetchai/carpark_agent.git -cd carpark_agent -``` - -### Download datafile - -This is required for the machine learning algorithms. - -``` bash -./car_detection/weights/download_weights.sh -``` -Install the required libraries. - -``` bash -sudo apt-get install gcc htop vim mc python3-dev ffmpeg virtualenv libatlas-base-dev libsm6 libxext6 clang libblas3 liblapack3 liblapack-dev libblas-dev cython gfortran build-essential libgdal-dev libopenblas-dev liblapack3 liblapacke liblapacke-dev liblcms2-utils liblcms2-2 libwebpdemux2 python3-scipy python3-numpy python3-matplotlib libjasper-dev libqtgui4 libqt4-test protobuf-compiler python3-opencv gpsd gpsd-clients +Add the ledger info to both aea configs: ``` - -### Activate a virtual environment. - -``` bash -pip3 install virtualenv -./run_scripts/create_venv.sh -source venv/bin/activate +ledger_apis: + - ledger_api: + ledger: fetchai + addr: alpha.fetch-ai.com + port: 80 ``` -### Install the software - -``` bash -python setup.py develop +Fund the carpark client agent: ``` - -!!! Note - We recommend that using `develop` as this creates a link to the code and so any changes you make will take immediate effect when you run the code. - - -### Run it - -``` bash -./run_scripts/run_carpark_agent.sh +cd .. +python scripts/fetchai_wealth_generation.py --private-key car_data_buyer/fet_private_key.txt --amount 10000000000 --addr alpha.fetch-ai.com --port 80 ``` -You should now see the agent running. - -### Ensure agent start on boot (RPi4 only) -Ensure the startup script runs whenever we the Raspberry Pi turns on. - -``` bash -crontab -e +Then, in the carpark detection agent comment out database related settings: ``` -Pick an editor which will open a text file. Scroll to the bottom and add the following line. - -``` bash -@reboot /home/pi/Desktop/carpark_agent/run_scripts/run_carpark_agent.sh +# db_is_rel_to_cwd: true +# db_rel_dir: ../temp_files ``` -Save and reboot. The agent should now start automatically on reboot. - - -### Get the Pi's ip address - -We will need the ip address of the Raspberry Pi to connect remotely. - -``` bash -ifconfig +Then, in the client agent do: ``` -Returns something like: -``` bash -... -wlan0: flags=4163 mtu 1500 - inet 192.168.11.9 netmask 255.255.255.0 broadcast 192.168.11.255 -... +max_detection_age: 36000000 ``` -The `inet` value is the Raspberry Pi's ip address. - - - -### Connect to the app remotely - -Download and install the VNC viewer onto your remote laptop: https://www.realvnc.com/en/connect/download/viewer/. - -Add the Pi's ip address. You will be prompted for the Raspberry Pi password. The Raspberry Pi's desktop should appear. - - -### Get your remote desktop ip - -Follow the instructiona to get your remote ip address. - -### Connect to the Raspberry Pi - -Start up the agent, if it is not running - but it should be. - -``` bash -cd Desktop/carpark_agent -./run_scripts/run_carpark_agent.sh +python scripts/oef/launch.py -c ./scripts/oef/launch_config.json ``` -# STOPPED HERE - Monday 28th October -# The Pi server app instructions come next +Finally, run both agents with `aea run`. -From "When it starts up and you see the output from the camera, you can move your camera around so it is looking at the area... \ No newline at end of file +
diff --git a/docs/cli-gui.md b/docs/cli-gui.md index 5194d65546..9a9c316008 100644 --- a/docs/cli-gui.md +++ b/docs/cli-gui.md @@ -1,49 +1,52 @@ -The AEA Command Line Interface (CLI) can also be invoked from a Graphical User Interface (GUI) which can be access from a web browser. +You can invoke the AEA Command Line Interface (CLI) from a Graphical User Interface (GUI) accessed from a web browser. -These instructions will take you through building an agent, starting an OEF Node and running the agent - all from the GUI. Once you can do this, the other operations should be fairly self-explanatory. +These instructions will take you through building an agent, starting an OEF Node, and running the agent - all from the GUI. ## Preliminaries -Ensure you have the framework installed and the CLI is working by following the [quick-start guide](quickstart.md). +Follow the Preliminaries and Installation instructions here. -Please install the extra dependencies for the CLI GUI: - ```python - pip install aea[cli_gui] - ``` +Install the extra dependencies for the CLI GUI. + +```python +pip install aea[cli_gui] +``` ## Starting the GUI -Go to your working folder, where you want to create new agents. If you followed the quick start guide, this will be in the my_aea directory. Start the local web-server: +Go to the directory in which you will create new agents. If you followed the quick start guide, this will be `my_aea`. + +Start the local web-server. ``` bash aea gui ``` - Open this page in a browser: [http://127.0.0.1:8080](http://127.0.0.1:8080) -You should see the following page displayed: +You should see the following page.
![new gui screen](assets/cli_gui01_clean.png)
-On the left-hand side we can see any agents you have created and any protocols, connections and skills they have. Initially this will be empty - or if you have created an agent using the CLI in the quick-start guide and not deleted it then that should be listed. +On the left-hand side we can see any agents you have created and the protocols, connections, and skills they have. Initially this will be empty - unless you have run the quick start previously and not deleted those agents. -On the right-hand side is the Registry which shows all the protocols, connections and skills which are available to you to construct your agents out of. +On the right-hand side is the Registry which shows all the protocols, connections, and skills which are available to your agent. -To create a new agent and run it, follow these steps: +To create a new agent and run it, follow these steps.
![gui sequence](assets/cli_gui02_sequence.png)
1. In the [Create Agent id] box on the left. type the name of your agent - e.g. my_new_agent. This should now be the currently selected agent - but you can click on its name in the list to make sure. -2. Click the [Create Agent] button - the newly created agent should appear in the [Local Agents] table -3. On the right hand side, find the Echo skill and click on it - this will select it -4. Click on the [Add skill] button - which should actually now say "Add echo skill to my_new_agent agent" -5. Start an OEF Node, by clicking on the [Start OEF Node] button. Wait for the text saying "A thing of beauty is a joy forever..." to appear. This shows that the node has started successfully +2. Click the [Create Agent] button - the newly created agent should appear in the [Local Agents] table. +3. On the right hand side, find the Echo skill and click on it - this will select it. +4. Click on the [Add skill] button - which should now say "Add echo skill to my_new_agent agent". +5. Start an OEF Node by clicking on the [Start OEF Node] button. Wait for the text saying "A thing of beauty is a joy forever..." to appear. When you see that, the node has started successfully. + +
![start node](assets/cli_gui03_oef_node.png)
-
![start node](assets/cli_gui03_oef_node.png)
+6. Start the agent running by clicking on the [start agent] button. You should see the output from the echo agent appearing on the screen. -6. Start the agent running, by clicking on the [start agent] button - you should see the output from the echo agent appearing on the screen +
![start agent](assets/cli_gui04_new_agent.png)
-
![start agent](assets/cli_gui04_new_agent.png)
+This is how your whole page should look if you followed the instructions correctly. -This is how your whole page should look if you followed the instructions correctly
![whole screen running](assets/cli_gui05_full_running_agent.png)
diff --git a/docs/core-components.md b/docs/core-components.md index 1ab263f1a5..6ed4bc56bb 100644 --- a/docs/core-components.md +++ b/docs/core-components.md @@ -56,7 +56,7 @@ A skill encapsulates implementations of the abstract base classes `Handler`, `Be * `Handler`: each skill has none, one or more `Handler` objects, each responsible for the registered messaging protocol. Handlers implement agents' reactive behaviour. If the agent understands the protocol referenced in a received `Envelope`, the `Handler` reacts appropriately to the corresponding message. Each `Handler` is responsible for only one protocol. A `Handler` is also capable of dealing with internal messages. * `Behaviour`: none, one or more `Behaviours` encapsulate actions that cause interactions with other agents initiated by the agent. Behaviours implement agents' proactiveness. -* `Task`: none, one or more Tasks encapsulate background work internal to the agent. +* `Task`: none, one or more `Tasks` encapsulate background work internal to the agent. ## Agent diff --git a/docs/design-principles.md b/docs/design-principles.md index 2d10192b01..dd3ac85c5c 100644 --- a/docs/design-principles.md +++ b/docs/design-principles.md @@ -1,6 +1,6 @@ -We have collated 8 principles which guide AEA framework development: +Eight principles guide AEA framework development: -* **Accessibility**: easy of use. +* **Accessibility**: ease of use. * **Modularity**: encourages module creation and sharing and reuse. * **Openness**: easily extensible with third party libraries. * **Conciseness**: conceptually simple. diff --git a/docs/diagram.md b/docs/diagram.md index 837fd3b034..e50e88e6c7 100644 --- a/docs/diagram.md +++ b/docs/diagram.md @@ -2,15 +2,18 @@ Work in progress. -The framework can be divided into two parts: a) a **core** part that is developed by the Fetch.ai team as well as external contributors, -and b) **extensions** (also known as **packages**) developed by any developer. This allows for a modular and scalable framework. -Currently, the framework supports three types of packages which can be added to the core part as modules: +The framework has two distinctive parts. + +* A **core** that is developed by the Fetch.ai team as well as external contributors. +* **extensions** (also known as **packages**) developed by any developer which promotes a modular and scalable framework. + +Currently, the framework supports three types of packages which can be added to the core as modules. * Skills * Protocols * Connections -The following figure illustrates the framework's architecture: +The following figure illustrates the framework's architecture.
![The AEA Framework Architecture](assets/framework-architecture.png)
diff --git a/docs/fipa-skill.md b/docs/fipa-skill.md index f0c1e50008..bce93c43ba 100644 --- a/docs/fipa-skill.md +++ b/docs/fipa-skill.md @@ -80,7 +80,7 @@ This class deals with representing potential transactions between agents. FIPA negotiation skill is not fully developed. -Follow the Preliminaries and Installation instructions here. +Follow the Preliminaries and Installation instructions here. Then, download the examples and packages directory. diff --git a/docs/gym-skill.md b/docs/gym-skill.md index 8212af2e19..adfe6a5f32 100644 --- a/docs/gym-skill.md +++ b/docs/gym-skill.md @@ -1,4 +1,4 @@ -The AEA gym skill demonstrates how a custom Reinforcement Learning agent, that uses openai's gym library, may be embedded into an Autonomous Economic Agent. +The AEA gym skill demonstrates how a custom Reinforcement Learning agent, that uses OpenAI's gym library, may be embedded into an Autonomous Economic Agent. ## Demo instructions @@ -76,4 +76,4 @@ aea delete my_gym_agent ``` -
\ No newline at end of file +
diff --git a/docs/hacking-an-agent.md b/docs/hacking-an-agent.md index 588cafe237..714cebb629 100644 --- a/docs/hacking-an-agent.md +++ b/docs/hacking-an-agent.md @@ -1,6 +1,98 @@ -!!! Warning - Not recommended. +## Preliminaries + +These instructions detail the Python code you need for running an AEA outside the `cli` tool. + +!!! Note + You have already coded up your agent. See the build your own skill guide for a reminder. + + +## Imports + +First, import the necessary common Python libraries and classes. + +``` python +import os +import time +from threading import Thread +``` + +Then, import the application specific libraries. + +``` python +from aea.aea import AEA +from aea.connections.stub.connection import StubConnection +from aea.crypto.ledger_apis import LedgerApis +from aea.crypto.wallet import Wallet +from aea.mail.base import MailBox, Envelope +from aea.protocols.default.message import DefaultMessage +from aea.protocols.default.serialization import DefaultSerializer +from aea.registries.base import Resources +``` + + + +## Initialise the agent + +Create a private key. +``` bash +python scripts/generate_private_key.py my_key.txt +``` + +Create a wallet object with a private key. +``` python +wallet = Wallet({'default': 'my_key.txt'}) +``` + +Create a `Connection` and a `MailBox`. +``` python +stub_connection = StubConnection(input_file_path='input.txt', output_file_path='output.txt') +mailbox = MailBox(stub_connection) +``` + +For ledger APIs, we simply feed the agent an empty dictionary (meaning we do not require any). +``` python +ledger_apis = LedgerApis({}) +``` + +Create the resources pointing to the working directory. +``` python +resources = Resources('') +``` + +Now we have everything we need for initialisation. +``` python +my_agent = AEA("my_agent", mailbox, wallet, ledger_apis, resources) +``` + +## Add skills and protocols + +We can add the echo skill as follows... !!! Note - Coming soon. + Work in progress. + +## Run the agent + +Create a thread and add the agent to it. + +``` python +t = Thread(target=my_agent.start) +``` + +Start the agent. + +``` python +t.start() +``` + +## Terminate the agent + +Finalise the agent thread. + +``` python +my_agent.stop() +t.join() +t = None +``` +
\ No newline at end of file diff --git a/docs/index.md b/docs/index.md index 9b324b8e84..290e42614b 100644 --- a/docs/index.md +++ b/docs/index.md @@ -2,11 +2,11 @@ Do you want to create software to work for you and enrich your life? -Autonomous Economic Agents - or AEAs - work continously for your benefit without you having to do anything more than write them and start them up. +Autonomous Economic Agents (AEAs) work continuously for your benefit without you having to do anything more than write them and start them up. -AEAs are able to act independent of your constant input and to autonomously develop new capabilities. Their goal is to create economic gain for you, their owner. +AEAs act independently of constant input and autonomously develop new capabilities. Their goal is to create economic gain for you, their owner. -AEAs have a wide range of application areas. Check out the demo section for examples. +AEAs have a wide range of application areas. Check out the demo section for examples. Bridging Web 2.0 to Web 3.0, AEAs are the future, now. diff --git a/docs/logging.md b/docs/logging.md index d2c42f6d37..ed1f3f5d4c 100644 --- a/docs/logging.md +++ b/docs/logging.md @@ -1,8 +1,8 @@ -The AEA framework supports flexible logging capabilities with the standard [Python logging library](https://docs.python.org/3/library/logging.html). +The AEA framework supports flexible logging capabilities with the standard Python logging library. -In this tutorial, we will configure logging for an agent. +In this tutorial, we configure logging for an agent. -First of all, create your agent: +First of all, create your agent. ``` bash @@ -10,8 +10,9 @@ aea create my_agent cd my_agent ``` -The `aea-config.yaml` file should look like: -```yaml +The `aea-config.yaml` file should look like this. + +``` yaml aea_version: 0.1.6 agent_name: my_agent authors: '' @@ -32,16 +33,14 @@ logging_config: version: 1 ``` -By updating the `logging_config` section, you can configure -the loggers of your application. +By updating the `logging_config` section, you can configure the loggers of your application. + +The format of this section is specified in the `logging.config` module. -The format of this section is specified in the -[`logging.config`](https://docs.python.org/3/library/logging.config.html) -module. -At [this section](https://docs.python.org/3/library/logging.config.html#configuration-dictionary-schema) +At this section you'll find the definition of the configuration dictionary schema. -An example of `logging_config` value is reported below: +Below is an example of the `logging_config` value. ```yaml logging_config: @@ -69,10 +68,7 @@ logging_config: propagate: true ``` -This configuration will set up a logger with name `aea`, -print both on console (see `console` handler) and on file -(see `logfile` handler) with format specified by the -`standard` formatter. +This configuration will set up a logger with name `aea`. It prints both on console and on file with a format specified by the `standard` formatter.
\ No newline at end of file diff --git a/docs/oef-ledger.md b/docs/oef-ledger.md index b0266a6669..8f7293512e 100644 --- a/docs/oef-ledger.md +++ b/docs/oef-ledger.md @@ -1,4 +1,4 @@ -In the AEA framework universe, agents run alongside OEF search and discovery nodes against the Fetch.ai ledger and external ledger systems. +In the AEA framework universe, agents run alongside OEF search and discovery nodes which are running against the Fetch.ai ledger and external ledger systems. -
![The AEA, OEF, and Ledger systems](assets/oef-ledger.png)
\ No newline at end of file +
![The AEA, OEF, and Ledger systems](assets/oef-ledger.png)
diff --git a/docs/protocol.md b/docs/protocol.md index 9cd7c89399..4a6ed45cc2 100644 --- a/docs/protocol.md +++ b/docs/protocol.md @@ -88,7 +88,7 @@ A `models.py` module is provided by the `oef` protocol which includes classes an The `fipa` protocol definition includes a `FIPAMessage` class which gets a `protocol_id` of `fipa`. -It defines FIPA negotiating terms by way of a `Peformative(Enum)`. +It defines FIPA negotiating terms by way of a `Performative(Enum)`. ``` python class Performative(Enum): @@ -165,4 +165,4 @@ The serialisation methods `encode` and `decode` implement transformations from ` -
\ No newline at end of file +
diff --git a/docs/quickstart.md b/docs/quickstart.md index c4783ad867..2cd77ec94d 100644 --- a/docs/quickstart.md +++ b/docs/quickstart.md @@ -59,17 +59,16 @@ pip install aea[cli] ### Known issues -If the installation steps fail, it might be because some of - the dependencies cannot be built on your system. +If the installation steps fail, it might a dependency issue. The following hints can help: -- Ubuntu/Debian systems only: install Python 3.7 headers +- Ubuntu/Debian systems only: install Python 3.7 headers. ```bash sudo apt-get install python3.7-dev ``` -- Windows users: install [build tools for Visual Studio](https://visualstudio.microsoft.com/downloads/#build-tools-for-visual-studio-2019). +- Windows users: install tools for Visual Studio. ## Echo skill demo @@ -94,13 +93,11 @@ cd my_first_agent ### Add the echo skill ``` bash - aea add skill echo ``` This copies the echo application code for the behaviours, handlers, and tasks into the skill, ready to run. - ### Add a stub connection AEAs use messages for communication. We will add a stub connection to send messages to and receive messages from the AEA. diff --git a/docs/skill-guide.md b/docs/skill-guide.md index ddfe458013..2a057f51cb 100644 --- a/docs/skill-guide.md +++ b/docs/skill-guide.md @@ -1,4 +1,4 @@ -The scaffolding tool allows you create the folder structure required for a skill. +The scaffolding tool allows you to create the folder structure required for a skill. !!! Note Before developing your first skill, please read the skill guide. @@ -6,14 +6,16 @@ The scaffolding tool allows you create the folder ## Step 1: Setup -Ensure, you have followed the preliminaries and installation. We will first create an agent and add a scaffold skill, which we call `my_search`: +Make sure you have followed the preliminaries and installation instructions from the quick start. + +We will first create an agent and add a scaffold skill, which we call `my_search`. ``` bash aea create my_agent && cd my_agent aea scaffold skill my_search ``` -In the following steps, we will replace each one of the scaffolded `Behaviour`, `Handler` and `Task` in `my_agent/skills/my_search` with our implementation. We will build a simple skill which lets the agent send a search query to the [OEF](https://docs.fetch.ai/oef/) and process the resulting response. +In the following steps, we replace each one of the scaffolded `Behaviour`, `Handler` and `Task` in `my_agent/skills/my_search` with our implementation. We will build a simple skill which lets the agent send a search query to the [OEF](https://docs.fetch.ai/oef/) and process the resulting response. ## Step 2: Develop a Behaviour @@ -79,7 +81,7 @@ class MySearchBehaviour(Behaviour): logger.info("[{}]: tearing down MySearchBehaviour".format(self.context.agent_name)) ``` -Searches are proactive and as such well placed in a `Behaviour`. +Searches are proactive and, as such, well placed in a `Behaviour`. We place this code in `my_agent/skills/my_search/behaviours.py`. @@ -200,9 +202,9 @@ We place this code in `my_agent/skills/my_search/tasks.py`. ## Step 5: Create the config file -Based on our skill components above, we create the following config file: +Based on our skill components above, we create the following config file. -```yaml +``` yaml name: my_search authors: Fetch.ai Limited version: 0.1.0 @@ -231,20 +233,20 @@ We place this code in `my_agent/skills/my_search/skill.yaml`. ## Step 6: Add the oef protocol -Our agent does not have the oef protocol yet. Hence, we add it like so: +Our agent does not have the oef protocol yet so let's add it. ```bash aea add protocol oef ``` ## Step 7: Run the agent -We first start an oef node (see the connection section for more details) in a separate terminal window: +We first start an oef node (see the connection section for more details) in a separate terminal window. ```bash python scripts/oef/launch.py -c ./scripts/oef/launch_config.json ``` -We can then launch our agent: +We can then launch our agent. ```bash aea run ``` @@ -252,8 +254,9 @@ aea run Stop the agent with `CTRL + C`. -## Now it's your turn: +## Now it's your turn + +We hope this step by step introduction has helped you develop your own skill. We are excited to see what you will build. -We hope this step by step introduction has helped you to develop your own skill. We are excited to see what you will build.
diff --git a/docs/steps.md b/docs/steps.md index 625a6e80a1..efb9e54b49 100644 --- a/docs/steps.md +++ b/docs/steps.md @@ -2,9 +2,9 @@
  • There are a number of ways to build an agent.
    • -
    • We recommended you build an AEA project with the CLI tool as mentioned in the quick start guide. See information on the CLI tool here.
    • +
    • We recommended you build an AEA project with the CLI tool as mentioned in the quick start guide. See information on the CLI tool here.
    • [Coming soon!] Using the CLI fetch command, pull in an already built project and run as normal.
    • -
    • The last option is to install the AEA without the CLI tool with pip install aea and, from there, import classes directly.
    • +
    • The last option is to install the AEA without the CLI tool with pip install aea and, from there, import classes directly. See the guide for this here.
    diff --git a/docs/tac.md b/docs/tac.md index 337ead70fb..6eec01a541 100644 --- a/docs/tac.md +++ b/docs/tac.md @@ -10,7 +10,7 @@ Make sure you are running diff --git a/docs/weather-skills.md b/docs/weather-skills.md index 2f63c6ea7b..956bc37e6b 100644 --- a/docs/weather-skills.md +++ b/docs/weather-skills.md @@ -1,4 +1,7 @@ -The AEA weather skills demonstrate an interaction between two AEAs; one as the provider of weather data (the weather station), the other as the seller of weather data (the weather client). +The AEA weather skills demonstrate an interaction between two AEAs. + +* The provider of weather data (the weather station). +* The seller of weather data (the weather client). ## Prerequisites @@ -26,8 +29,8 @@ svn export https://github.com/fetchai/agents-aea.git/trunk/packages svn export https://github.com/fetchai/agents-aea.git/trunk/scripts ``` -## Launch the OEF Node (for search and discovery): -In a separate terminal, launch an OEF node locally: +## Launch an OEF node +In a separate terminal, launch a local OEF node (for search and discovery). ``` bash python scripts/oef/launch.py -c ./scripts/oef/launch_config.json ``` @@ -38,7 +41,7 @@ Keep it running for all the following demos. The AEAs negotiate and then transfer the data. No payment takes place. This demo serves as a demonstration of the negotiation steps. -### Create the weather station AEA: +### Create the weather station AEA In the root directory, create the weather station AEA. ``` bash aea create my_weather_station @@ -98,7 +101,7 @@ aea delete my_weather_client ## Demo 2: Fetch.ai ledger payment -A demo to run the same scenario but with a true ledger transaction on Fetch.ai test net. This demo assumes the weather client trusts the weather station to send the weather data upon successful payment. +A demo to run the same scenario but with a true ledger transaction on Fetch.ai `testnet`. This demo assumes the weather client trusts the weather station to send the weather data upon successful payment. ### Create the weather station (ledger version) @@ -110,9 +113,9 @@ cd my_weather_station aea add skill weather_station_ledger ``` -### Create the weather client (ledger version): +### Create the weather client (ledger version) -In another terminal, create the AEA that will query the weather station +In another terminal, create the AEA that will query the weather station. ``` bash aea create my_weather_client @@ -120,7 +123,7 @@ cd my_weather_client aea add skill weather_client_ledger ``` -Additionally, create the private key for the weather client AEA +Additionally, create the private key for the weather client AEA. ```bash aea generate-key fetchai ``` @@ -128,7 +131,7 @@ aea generate-key fetchai ### Update the AEA configs Both in `weather_station/aea-config.yaml` and -`weather_client/aea-config.yaml`, replace `ledger_apis: []` with: +`weather_client/aea-config.yaml`, replace `ledger_apis: []` with the following. ``` yaml ledger_apis: @@ -140,7 +143,7 @@ ledger_apis: ### Fund the weather client AEA -Create some wealth for your weather client on the Fetch.ai test net (it takes a while): +Create some wealth for your weather client on the Fetch.ai `testnet`. (It takes a while). ``` bash cd .. python scripts/fetchai_wealth_generation.py --private-key weather_client/fet_private_key.txt --amount 10000000 --addr alpha.fetch-ai.com --port 80 @@ -149,12 +152,12 @@ cd my_weather_client ### Run the AEAs -Run both AEAs, from their respective terminals +Run both AEAs from their respective terminals. ``` bash aea run ``` -You will see that the AEAs negotiate and then transact using the Fetch.ai test net. +You will see that the AEAs negotiate and then transact using the Fetch.ai `testnet`. ### Delete the AEAs @@ -168,7 +171,7 @@ aea delete my_weather_client ## Demo 3: Ethereum ledger payment -A demo to run the same scenario but with a true ledger transaction on Fetch.ai test net. This demo assumes the weather client trusts the weather station to send the weather data upon successful payment. +A demo to run the same scenario but with a true ledger transaction on the Ethereum Ropsten `testnet`. This demo assumes the weather client trusts the weather station to send the weather data upon successful payment. ### Create the weather station (ledger version) @@ -180,9 +183,9 @@ cd my_weather_station aea add skill weather_station_ledger ``` -### Create the weather client (ledger version): +### Create the weather client (ledger version) -In another terminal, create the AEA that will query the weather station +In another terminal, create the AEA that will query the weather station. ``` bash aea create my_weather_client @@ -190,7 +193,7 @@ cd my_weather_client aea add skill weather_client_ledger ``` -Additionally, create the private key for the weather client AEA +Additionally, create the private key for the weather client AEA. ```bash aea generate-key ethereum ``` @@ -198,7 +201,7 @@ aea generate-key ethereum ### Update the AEA configs Both in `weather_station/aea-config.yaml` and -`weather_client/aea-config.yaml`, replace `ledger_apis: []` with: +`weather_client/aea-config.yaml`, replace `ledger_apis: []` with the following. ``` yaml ledger_apis: @@ -210,41 +213,40 @@ ledger_apis: ### Update the skill configs -In the weather station skill config (`my_weather_station/skills/weather_station_ledger/skill.yaml`) under strategy change the `currency_pbk` and `ledger_id` as follows: -``` +In the weather station skill config (`my_weather_station/skills/weather_station_ledger/skill.yaml`) under strategy, amend the `currency_pbk` and `ledger_id` as follows. +``` bash currency_pbk: 'ETH' ledger_id: 'ethereum' ``` -and under ledgers change to: -``` +Amend `ledgers` to the following. +``` bash ledgers: ['ethereum'] ``` -In the weather client skill config (`my_weather_client/skills/weather_client_ledger/skill.yaml`) under strategy change the `currency_pbk` and `ledger_id` as follows: -``` +In the weather client skill config (`my_weather_client/skills/weather_client_ledger/skill.yaml`) under strategy change the `currency_pbk` and `ledger_id`. +``` bash max_buyer_tx_fee: 20000 currency_pbk: 'ETH' ledger_id: 'ethereum' ``` -and under ledgers change to: -``` +Amend `ledgers` to the following. +``` basgh ledgers: ['ethereum'] ``` ### Fund the weather client AEA -Create some wealth for your weather client on the Ethereum Ropsten test net: +Create some wealth for your weather client on the Ethereum Ropsten test net. -Go to Metamask [Faucet](https://faucet.metamask.io) and request some test ETH for the account your weather client AEA is using (you need to first load your AEAs private key into MetaMask). Your private key is at `weather_client/eth_private_key.txt`. +Go to the MetaMask Faucet and request some test ETH for the account your weather client AEA is using (you need to first load your AEAs private key into MetaMask). Your private key is at `weather_client/eth_private_key.txt`. ### Run the AEAs -Run both AEAs, from their respective terminals +Run both AEAs, from their respective terminals. ``` bash aea run ``` - -You will see that the AEAs negotiate and then transact using the Fetch.ai test net. +You will see that the AEAs negotiate and then transact using the Ethereum `testnet`. ### Delete the AEAs diff --git a/mkdocs.yml b/mkdocs.yml index 3b4fc9afc2..5f6fec4397 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -33,7 +33,7 @@ nav: - "TAC external app": 'tac.md' # - "FIPA skill": 'fipa-skill.md' # - "TAC skill": 'tac-skill.md' - # - "Car park agent": 'car-park.md' + - "Car park agent": 'car-park.md' - Architecture: - "Design principles": 'design-principles.md' - "Architectural diagram": 'diagram.md' diff --git a/packages/connections/gym/connection.py b/packages/connections/gym/connection.py index 26cb667cfc..bbfc02cce9 100644 --- a/packages/connections/gym/connection.py +++ b/packages/connections/gym/connection.py @@ -125,6 +125,14 @@ def _send(self, envelope: Envelope) -> None: destination = envelope.to self._queues[destination].put_nowait(envelope) + def receive(self) -> None: + """ + Receives an envelope. + + :return: None. + """ + pass + def disconnect(self) -> None: """ Disconnect. @@ -151,7 +159,6 @@ def __init__(self, public_key: str, gym_env: gym.Env): self._connection = None # type: Optional[Queue] - self._stopped = True self.in_thread = None # type: Optional[Thread] self.out_thread = None # type: Optional[Thread] @@ -161,7 +168,7 @@ def _fetch(self) -> None: :return: None """ - while not self._stopped: + while self.connection_status.is_connected: try: envelope = self.out_queue.get(block=True, timeout=2.0) self.send(envelope) @@ -175,26 +182,21 @@ def _receive_loop(self) -> None: :return: None """ assert self._connection is not None, "Call connect before calling _receive_loop." - while not self._stopped: + while self.connection_status.is_connected: try: data = self._connection.get(timeout=2.0) self.in_queue.put_nowait(data) except queue.Empty: pass - @property - def is_established(self) -> bool: - """Return True if the connection has been established, False otherwise.""" - return self._connection is not None - def connect(self) -> None: """ Connect to the gym. :return: None """ - if self._stopped: - self._stopped = False + if not self.connection_status.is_connected: + self.connection_status.is_connected = True self._connection = self.channel.connect() self.in_thread = Thread(target=self._receive_loop) self.out_thread = Thread(target=self._fetch) @@ -209,8 +211,8 @@ def disconnect(self) -> None: """ assert self.in_thread is not None, "Call connect before disconnect." assert self.out_thread is not None, "Call connect before disconnect." - if not self._stopped: - self._stopped = True + if self.connection_status.is_connected: + self.connection_status.is_connected = False self.in_thread.join() self.out_thread.join() self.in_thread = None @@ -225,7 +227,7 @@ def send(self, envelope: Envelope) -> None: :param envelope: the envelop :return: None """ - if not self.is_established: + if not self.connection_status.is_connected: raise ConnectionError("Connection not established yet. Please use 'connect()'.") self.channel.send(envelope) diff --git a/packages/connections/p2p/README.md b/packages/connections/p2p/README.md new file mode 100644 index 0000000000..7f649b750b --- /dev/null +++ b/packages/connections/p2p/README.md @@ -0,0 +1,36 @@ +## Get started + +- Follow the instructions here to install the fetch.ai ledger: + + https://github.com/fetchai/ledger + + After you have the ledger setup you can run the following command to start the messenger server: + + ./libs/messenger/examples/example-messenger-messegner-server + + +- Install the fetch-p2p-api to the aea framework: + + pip install git+https://github.com/fetchai/peer-to-peer-api.git + + +##Create the agent + +- Create an aea agent: + + aea create my_agent + +- Add the peer to peer connection: + + cd my_agent + aea add connection p2p + + +- Run the agent: + + aea run my_agent --connection p2p + + +In order to see that everything is running, you have to create an envelope and send it. + + diff --git a/packages/connections/p2p/__init__.py b/packages/connections/p2p/__init__.py new file mode 100644 index 0000000000..59faabbd49 --- /dev/null +++ b/packages/connections/p2p/__init__.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- + +# ------------------------------------------------------------------------------ +# +# Copyright 2018-2019 Fetch.AI Limited +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ------------------------------------------------------------------------------ + +"""Peer to Peer connection and channel.""" diff --git a/packages/connections/p2p/connection.py b/packages/connections/p2p/connection.py new file mode 100644 index 0000000000..0bd52c8c37 --- /dev/null +++ b/packages/connections/p2p/connection.py @@ -0,0 +1,214 @@ +# -*- coding: utf-8 -*- + +# ------------------------------------------------------------------------------ +# +# Copyright 2018-2019 Fetch.AI Limited +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ------------------------------------------------------------------------------ + +"""Peer to Peer connection and channel.""" +import logging +import queue +from queue import Queue +from threading import Thread +from typing import Optional, cast, Dict, List, Any + +from aea.configurations.base import ConnectionConfig +from aea.connections.base import Channel, Connection +from aea.mail.base import Envelope + +from fetch.p2p.api.http_calls import HTTPCalls + +logger = logging.getLogger(__name__) + + +class PeerToPeerChannel(Channel): + """A wrapper for an SDK or API.""" + + def __init__(self, public_key: str, provider_addr: str, provider_port: int): + """ + Initialize a channel. + + :param public_key: the public key + """ + self.public_key = public_key + self.provider_addr = provider_addr + self.provider_port = provider_port + self.in_queue = Queue() # type: Queue + self._httpCall = None # type: HTTPCalls + logger.info("Initialised the peer to peer channel") + + def connect(self) -> Optional[Queue]: + """ + Connect. + + :return: an asynchronous queue, that constitutes the communication channel. + """ + self._httpCall = HTTPCalls(server_address=self.provider_addr, port=self.provider_port) + logger.info("Connected") + self.try_register() + return self.in_queue + + def try_register(self) -> bool: + """Try to register to the provider.""" + try: + logger.info(self.public_key) + query = self._httpCall.register(sender_address=self.public_key, mailbox=True) + return query['status'] == "OK" + except Exception: + logger.warning("Could not register to the provider.") + return False + + def send(self, envelope: Envelope) -> None: + """ + Process the envelopes. + + :param envelope: the envelope + :return: None + """ + self._httpCall.send_message(sender_address=envelope.sender, + receiver_address=envelope.to, + protocol=envelope.protocol_id, + context=b"None", + payload=envelope.message) + + def receive(self) -> None: + """Receive the messages from the provider.""" + messages = self._httpCall.get_messages(sender_address=self.public_key) # type: List[Dict[str, Any]] + for message in messages: + logger.info(message) + envelope = Envelope(to=message['TO']['RECEIVER_ADDRESS'], + sender=message['FROM']['SENDER_ADDRESS'], + protocol_id=message['PROTOCOL'], + message=message['PAYLOAD']) + self.in_queue.put(envelope) + + def disconnect(self) -> None: + """ + Disconnect. + + :return: None + """ + self._httpCall.unregister(self.public_key) + self._httpCall.disconnect() + + +class PeerToPeerConnection(Connection): + """Proxy to the functionality of the SDK or API.""" + + def __init__(self, public_key: str, provider_addr: str, provider_port: int = 8000): + """ + Initialize a connection to an SDK or API. + + :param public_key: the public key used in the protocols. + """ + super().__init__() + self.public_key = public_key + + self.channel = PeerToPeerChannel(public_key, provider_addr, provider_port) + self._connection = None # type: Optional[Queue] + self.in_thread = None # type: Optional[Thread] + self.out_thread = None # type: Optional[Thread] + + def _fetch(self) -> None: + """ + Fetch the envelopes from the outqueue and send them. + + :return: None + """ + while self.connection_status.is_connected: + try: + envelope = self.out_queue.get(block=True, timeout=2.0) + self.send(envelope) + except queue.Empty: + pass + + def _receive_loop(self) -> None: + """ + Receive messages. + + :return: None + """ + assert self._connection is not None, "Call connect before calling _receive_loop." + while self.connection_status.is_connected: + try: + self.channel.receive() + envelope = self._connection.get(timeout=2.0) + self.in_queue.put_nowait(envelope) + except queue.Empty: + pass + + def connect(self) -> None: + """ + Connect to the gym. + + :return: None + """ + if not self.connection_status.is_connected: + self.connection_status.is_connected = True + self._connection = self.channel.connect() + self.in_thread = Thread(target=self._receive_loop) + self.out_thread = Thread(target=self._fetch) + self.in_thread.start() + self.out_thread.start() + + def disconnect(self) -> None: + """ + Disconnect from the gym. + + :return: None + """ + assert self.in_thread is not None, "Call connect before disconnect." + assert self.out_thread is not None, "Call connect before disconnect." + if self.connection_status.is_connected: + self.connection_status.is_connected = False + self.in_thread.join() + self.out_thread.join() + self.in_thread = None + self.out_thread = None + self.channel.disconnect() + self.stop() + + def send(self, envelope: Envelope) -> None: + """ + Send an envelope. + + :param envelope: the envelop + :return: None + """ + if not self.connection_status.is_connected: + raise ConnectionError("Connection not established yet. Please use 'connect()'.") + self.channel.send(envelope) + + def stop(self) -> None: + """ + Tear down the connection. + + :return: None + """ + self._connection = None + + @classmethod + def from_config(cls, public_key: str, connection_configuration: ConnectionConfig) -> 'Connection': + """ + Get the Gym connection from the connection configuration. + + :param public_key: the public key of the agent. + :param connection_configuration: the connection configuration object. + :return: the connection object + """ + addr = cast(str, connection_configuration.config.get("addr")) + port = cast(int, connection_configuration.config.get("port")) + return PeerToPeerConnection(public_key, addr, port) diff --git a/packages/connections/p2p/connection.yaml b/packages/connections/p2p/connection.yaml new file mode 100644 index 0000000000..5510fb5484 --- /dev/null +++ b/packages/connections/p2p/connection.yaml @@ -0,0 +1,12 @@ +name: p2p +authors: Fetch.AI Limited +version: 0.1.0 +license: Apache 2.0 +url: "" +description: "The p2p connection provides a connection with the fetch.ai mail provider." +class_name: PeerToPeerConnection +supported_protocols: [] +config: + addr: ${addr:127.0.0.1} + port: ${port:8000} + diff --git a/packages/skills/carpark_client/dialogues.py b/packages/skills/carpark_client/dialogues.py index b8b566a43d..a31d3a4b4f 100644 --- a/packages/skills/carpark_client/dialogues.py +++ b/packages/skills/carpark_client/dialogues.py @@ -28,6 +28,7 @@ from enum import Enum import logging +import random from typing import Any, Dict, Optional, cast from aea.helpers.dialogue.base import DialogueLabel @@ -159,7 +160,6 @@ def __init__(self, **kwargs) -> None: """ SharedClass.__init__(self, **kwargs) self._dialogues = {} # type: Dict[DialogueLabel, Dialogue] - self._dialogue_id = 0 self._dialogue_stats = DialogueStats() @property @@ -178,9 +178,7 @@ def _next_dialogue_id(self) -> int: :return: the next id """ - self._dialogue_id += 1 - print("_next_dialogue_id: _dialogue_id = {}".format(self._dialogue_id)) - return self._dialogue_id + return random.randint(1, 10000000) def is_belonging_to_registered_dialogue(self, fipa_msg: Message, sender: Address, agent_pbk: Address) -> bool: """ diff --git a/packages/skills/carpark_detection/behaviours.py b/packages/skills/carpark_detection/behaviours.py old mode 100644 new mode 100755 index ca1a7e0dd5..b6509ba48a --- a/packages/skills/carpark_detection/behaviours.py +++ b/packages/skills/carpark_detection/behaviours.py @@ -19,13 +19,15 @@ """This package contains a scaffold of a behaviour.""" +import datetime import logging import os import subprocess -from typing import cast, TYPE_CHECKING +from typing import Optional, cast, TYPE_CHECKING from aea.skills.base import Behaviour from aea.protocols.oef.message import OEFMessage +from aea.protocols.oef.models import Description from aea.protocols.oef.serialization import OEFSerializer, DEFAULT_OEF if TYPE_CHECKING: @@ -50,7 +52,6 @@ class CarParkDetectionAndGUIBehaviour(Behaviour): def __init__(self, **kwargs): """Initialise the behaviour.""" - print("*****kwargs: {}".format(kwargs)) self.image_capture_interval = kwargs.pop('image_capture_interval') if 'image_capture_interval' in kwargs.keys() else DEFAULT_IMAGE_CAPTURE_INTERVAL self.default_latitude = kwargs.pop('default_latitude') if 'default_latitude' in kwargs.keys() else DEFAULT_LAT self.default_longitude = kwargs.pop('default_longitude') if 'default_longitude' in kwargs.keys() else DEFAULT_LON @@ -121,8 +122,12 @@ class ServiceRegistrationBehaviour(Behaviour): def __init__(self, **kwargs): """Initialise the behaviour.""" + self._services_interval = kwargs.pop('services_interval', 30) # type: int super().__init__(**kwargs) - self._registered = False + self._last_connection_status = self.context.connection_status.is_connected + self._last_update_time = datetime.datetime.now() # type: datetime.datetime + self._registered_service_description = None # type: Optional[Description] + self._oef_msf_id = 0 def setup(self) -> None: """ @@ -130,9 +135,12 @@ def setup(self) -> None: :return: None """ + strategy = cast(Strategy, self.context.strategy) + self._record_oef_status() balance = self.context.ledger_apis.token_balance('fetchai', cast(str, self.context.agent_addresses.get('fetchai'))) - + strategy.db.set_system_status("ledger-status", self.context.ledger_apis.last_tx_statuses['fetchai']) logger.info("[{}]: starting balance on fetchai ledger={}.".format(self.context.agent_name, balance)) + self._register_service() def act(self) -> None: """ @@ -140,41 +148,87 @@ def act(self) -> None: :return: None """ - if self._registered: - return + self._update_connection_status() + if self._is_time_to_update_services(): + self._unregister_service() + self._register_service() + + def _register_service(self) -> None: + """ + Register to the OEF Service Directory. + :return: None + """ strategy = cast(Strategy, self.context.strategy) if strategy.has_service_description(): desc = strategy.get_service_description() + self._registered_service_description = desc + self._oef_msf_id += 1 msg = OEFMessage(oef_type=OEFMessage.Type.REGISTER_SERVICE, - id=REGISTER_ID, + id=self._oef_msf_id, service_description=desc, service_id=SERVICE_ID) self.context.outbox.put_message(to=DEFAULT_OEF, sender=self.context.agent_public_key, protocol_id=OEFMessage.protocol_id, message=OEFSerializer().encode(msg)) - logger.info("[{}]: registering car park detection services on OEF.".format(self.context.agent_name)) - self._registered = True + logger.info("[{}]: updating car park detection services on OEF.".format(self.context.agent_name)) - def teardown(self) -> None: + def _unregister_service(self) -> None: """ - Implement the task teardown. + Unregister service from OEF Service Directory. :return: None """ - balance = self.context.ledger_apis.token_balance('fetchai', cast(str, self.context.agent_addresses.get('fetchai'))) - logger.info("[{}]: ending balance on fetchai ledger={}.".format(self.context.agent_name, balance)) - if self._registered: - strategy = cast(Strategy, self.context.strategy) - desc = strategy.get_service_description() + if self._registered_service_description is not None: + self._oef_msf_id += 1 msg = OEFMessage(oef_type=OEFMessage.Type.UNREGISTER_SERVICE, - id=UNREGISTER_ID, - service_description=desc, + id=self._oef_msf_id, + service_description=self._registered_service_description, service_id=SERVICE_ID) self.context.outbox.put_message(to=DEFAULT_OEF, sender=self.context.agent_public_key, protocol_id=OEFMessage.protocol_id, message=OEFSerializer().encode(msg)) logger.info("[{}]: unregistering car park detection services from OEF.".format(self.context.agent_name)) - self._registered = False + self._registered_service_description = None + + def _is_time_to_update_services(self) -> bool: + """ + Check if the agent should update the service directory. + + :return: bool indicating the action + """ + now = datetime.datetime.now() + diff = now - self._last_update_time + result = diff.total_seconds() > self._services_interval + if result: + self._last_update_time = now + return result + + def _update_connection_status(self) -> None: + """ + Update the connection status in the db. + + :return: None + """ + if self.context.connection_status.is_connected != self._last_connection_status: + self._last_connection_status = self.context.connection_status.is_connected + self._record_oef_status() + + def _record_oef_status(self): + strategy = cast(Strategy, self.context.strategy) + if self._last_connection_status: + strategy.db.set_system_status("oef-status", "Connected") + else: + strategy.db.set_system_status("oef-status", "Disconnected") + + def teardown(self) -> None: + """ + Implement the task teardown. + + :return: None + """ + self._unregister_service() + balance = self.context.ledger_apis.token_balance('fetchai', cast(str, self.context.agent_addresses.get('fetchai'))) + logger.info("[{}]: ending balance on fetchai ledger={}.".format(self.context.agent_name, balance)) diff --git a/packages/skills/carpark_detection/detection_database.py b/packages/skills/carpark_detection/detection_database.py index ad4d06dbf4..a1ca4ea6d2 100644 --- a/packages/skills/carpark_detection/detection_database.py +++ b/packages/skills/carpark_detection/detection_database.py @@ -18,12 +18,15 @@ # ------------------------------------------------------------------------------ """Communicate between the database and the python objects.""" -import sqlite3 +import logging import os import shutil +import sqlite3 import skimage # type: ignore import time +logger = logging.getLogger("aea.carpark_detection_skill") + class DetectionDatabase: """Communicate between the database and the python objects.""" @@ -57,7 +60,7 @@ def is_db_exits(self): def reset_database(self): """Reset the database and remove all data.""" # If we need to reset the database, then remove the table and any stored images - print("Database being reset") + logger.info("Database being reset.") # Remove the actual database file if os.path.isfile(self.database_path): @@ -68,14 +71,14 @@ def reset_database(self): shutil.rmtree(self.processed_image_dir) # Recreate them - print("initialise_backend") + logger.info("Initialising backend ...") self.initialise_backend() - print("FINISH initialise_backend") + logger.info("Finished initialising backend!") def reset_mask(self): """Just reset the detection mask.""" # If we need to reset the database, then remove the table and any stored images - print("Database being reset") + logger.info("Mask being reset.") # Remove the actual database file if os.path.isfile(self.mask_image_path): @@ -115,8 +118,6 @@ def initialise_backend(self): self.set_system_status("lon", "UNKNOWN") self.set_system_status("db", "Exists") - print("**** backend initialised") - def set_fet(self, amount, t): """Record how much FET we have and when we last read it from the ledger.""" self.execute_single_sql( @@ -317,7 +318,7 @@ def execute_single_sql(self, command, print_exceptions=True): conn.commit() except Exception as e: if print_exceptions: - print("Exception in database: {}".format(e)) + logger.warning("Exception in database: {}".format(e)) finally: if conn is not None: conn.close() diff --git a/packages/skills/carpark_detection/dialogues.py b/packages/skills/carpark_detection/dialogues.py index b350c6095f..e4de01ec91 100644 --- a/packages/skills/carpark_detection/dialogues.py +++ b/packages/skills/carpark_detection/dialogues.py @@ -111,8 +111,6 @@ def __init__(self) -> None: self._other_initiated = {Dialogue.EndState.SUCCESSFUL: 0, Dialogue.EndState.DECLINED_PROPOSE: 0} # type: Dict[Dialogue.EndState, int] - print("__init__: self._other_initiated = {}".format(self._other_initiated)) - @property def other_initiated(self) -> Dict[Dialogue.EndState, int]: """Get the stats dictionary on other initiated dialogues.""" diff --git a/packages/skills/carpark_detection/strategy.py b/packages/skills/carpark_detection/strategy.py index b46375167c..fd64608947 100644 --- a/packages/skills/carpark_detection/strategy.py +++ b/packages/skills/carpark_detection/strategy.py @@ -18,6 +18,8 @@ # ------------------------------------------------------------------------------ """This module contains the strategy class.""" + +import logging import os from typing import Any, Dict, List, Tuple, TYPE_CHECKING, cast import time @@ -37,6 +39,8 @@ DEFAULT_DB_IS_REL_TO_CWD = False DEFAULT_DB_REL_DIR = "temp_files_placeholder" +logger = logging.getLogger("aea.carpark_detection_skill") + class Strategy(SharedClass): """This class defines a strategy for the agent.""" @@ -61,12 +65,14 @@ def __init__(self, **kwargs) -> None: self.data_price_fet = kwargs.pop('data_price_fet') if 'data_price_fet' in kwargs.keys() else DEFAULT_PRICE super().__init__(**kwargs) + self.db = DetectionDatabase(db_dir, False) + balance = self.context.ledger_apis.token_balance('fetchai', cast(str, self.context.agent_addresses.get('fetchai'))) + self.db.set_system_status("ledger-status", self.context.ledger_apis.last_tx_statuses['fetchai']) if not os.path.isdir(db_dir): - print("WARNING - DATABASE dir does not exist") + logger.warning("Database directory does not exist!") - self.db = DetectionDatabase(db_dir, False) self.record_balance(balance) self.other_carpark_processes_running = False diff --git a/packages/skills/carpark_detection/temp_files_placeholder/detection_results.db b/packages/skills/carpark_detection/temp_files_placeholder/detection_results.db old mode 100644 new mode 100755 index c34158d031..06f558d9a8 Binary files a/packages/skills/carpark_detection/temp_files_placeholder/detection_results.db and b/packages/skills/carpark_detection/temp_files_placeholder/detection_results.db differ diff --git a/packages/skills/carpark_detection/temp_files_placeholder/mask.tiff b/packages/skills/carpark_detection/temp_files_placeholder/mask.tiff old mode 100644 new mode 100755 index 41454a7d68..e154b19241 Binary files a/packages/skills/carpark_detection/temp_files_placeholder/mask.tiff and b/packages/skills/carpark_detection/temp_files_placeholder/mask.tiff differ diff --git a/packages/skills/carpark_detection/temp_files_placeholder/mask_ref.tiff b/packages/skills/carpark_detection/temp_files_placeholder/mask_ref.tiff old mode 100644 new mode 100755 index 9d28fa1fa2..16bf8ef23d Binary files a/packages/skills/carpark_detection/temp_files_placeholder/mask_ref.tiff and b/packages/skills/carpark_detection/temp_files_placeholder/mask_ref.tiff differ diff --git a/scripts/generate_private_key.py b/scripts/generate_private_key.py old mode 100644 new mode 100755 index 7508aceec1..73af5c5979 --- a/scripts/generate_private_key.py +++ b/scripts/generate_private_key.py @@ -25,8 +25,6 @@ It prints the key in PEM format to the specified file. """ -from cryptography.hazmat.primitives.serialization import Encoding, PrivateFormat, NoEncryption - import argparse from aea.crypto.default import DefaultCrypto @@ -38,7 +36,5 @@ args = parser.parse_args() crypto = DefaultCrypto() - pem = crypto._private_key.private_bytes(Encoding.PEM, PrivateFormat.TraditionalOpenSSL, NoEncryption()) # type: ignore file = open(args.out_file, "wb") - file.write(pem) - file.close() + crypto.dump(file) diff --git a/setup.cfg b/setup.cfg index d83be73407..44080b34bd 100644 --- a/setup.cfg +++ b/setup.cfg @@ -22,6 +22,9 @@ strict_optional = True [mypy-base58] ignore_missing_imports = True +[mypy-cryptography.exceptions] +ignore_missing_imports = True + [mypy-cryptography.hazmat.primitives] ignore_missing_imports = True @@ -70,6 +73,12 @@ ignore_missing_imports = True [mypy-eth_keys.*] ignore_missing_imports = True +[mypy-hexbytes] +ignore_missing_imports = True + +[mypy-fetch.p2p.api.*] +ignore_missing_imports = True + # Per-module options for examples dir: [mypy-numpy] diff --git a/setup.py b/setup.py index 3c0ca4a23d..c6e023a7f5 100644 --- a/setup.py +++ b/setup.py @@ -53,17 +53,17 @@ def get_aea_extras() -> Dict[str, List[str]]: def get_all_extras() -> Dict: fetch_ledger_deps = [ - "fetchai-ledger-api" + "fetchai-ledger-api==0.8.1" ] - ethereum_deps = [ - "web3", - "eth-account" + ethereum_ledger_deps = [ + "web3==5.2.2", + "eth-account==0.4.0" ] crypto_deps = [ *fetch_ledger_deps, - *ethereum_deps + *ethereum_ledger_deps ] cli_deps = [ @@ -84,7 +84,7 @@ def get_all_extras() -> Dict: "cli": cli_deps, "cli_gui": cli_gui, "fetch": fetch_ledger_deps, - "ethereum": ethereum_deps, + "ethereum": ethereum_ledger_deps, "crypto": crypto_deps } extras.update(get_aea_extras()) diff --git a/tests/conftest.py b/tests/conftest.py index de8a415a15..6440c714ca 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -84,6 +84,11 @@ def tcpping(ip, port) -> bool: class DummyConnection(Connection): """A dummy connection that just stores the messages.""" + def __init__(self): + """Initialize.""" + super().__init__() + self.connection_status.is_connected = True + def connect(self): """Connect.""" pass @@ -92,15 +97,14 @@ def disconnect(self): """Disconnect.""" pass - @property - def is_established(self) -> bool: - """Check if the connection is established.""" - return True - def send(self, envelope: 'Envelope'): """Send an envelope.""" self.out_queue.put(envelope) + def receive(self, envelope: 'Envelope'): + """Receive an envelope.""" + self.in_queue.put(envelope) + @classmethod def from_config(cls, public_key: str, connection_configuration: ConnectionConfig) -> 'Connection': """Return a connection obj fom a configuration.""" diff --git a/tests/data/aea-config.example_w_keys.yaml b/tests/data/aea-config.example_w_keys.yaml new file mode 100644 index 0000000000..d12c285049 --- /dev/null +++ b/tests/data/aea-config.example_w_keys.yaml @@ -0,0 +1,40 @@ +aea_version: 0.1.1 +agent_name: myagent +authors: Fetch.AI Limited +version: 0.1.0 +license: Apache 2.0 +url: "" +connections: +- default-oef +default_connection: default-oef +private_key_paths: +- private_key_path: + ledger: default + path: 'private.pem' +- private_key_path: + ledger: fetchai + path: 'fet_private_key.txt' +- private_key_path: + ledger: ethereum + path: 'eth_private_key.txt' +protocols: +- oef +- default +- tac +- fipa +skills: +- echo_skill +description: "An example of agent configuration file for testing purposes." +logging_config: + disable_existing_loggers: false + version: 1 +registry_path: ../../packages +ledger_apis: +- ledger_api: + ledger: fetchai + addr: example.fetch-ai.com + port: 8080 +- ledger_api: + ledger: ethereum + addr: example.ethereum.com + port: 8080 diff --git a/tests/data/dummy_aea/connections/local/connection.py b/tests/data/dummy_aea/connections/local/connection.py index 2b59091788..75ef752ec8 100644 --- a/tests/data/dummy_aea/connections/local/connection.py +++ b/tests/data/dummy_aea/connections/local/connection.py @@ -44,7 +44,7 @@ class LocalNode: def __init__(self): """Initialize a local (i.e. non-networked) implementation of an OEF Node.""" - self.agents = dict() # type: Dict[str, Description] + self.agents = defaultdict(lambda: []) # type: Dict[str, List[Description]] self.services = defaultdict(lambda: []) # type: Dict[str, List[Description]] self._lock = threading.Lock() @@ -99,8 +99,12 @@ def handle_oef_message(self, envelope: Envelope) -> None: oef_type = OEFMessage.Type(oef_message.get("type")) if oef_type == OEFMessage.Type.REGISTER_SERVICE: self.register_service(sender, cast(Description, oef_message.get("service_description"))) + elif oef_type == OEFMessage.Type.REGISTER_AGENT: + self.register_agent(sender, cast(Description, oef_message.get("agent_description"))) elif oef_type == OEFMessage.Type.UNREGISTER_SERVICE: self.unregister_service(sender, request_id, cast(Description, oef_message.get("service_description"))) + elif oef_type == OEFMessage.Type.UNREGISTER_AGENT: + self.unregister_agent(sender, request_id, cast(Description, oef_message.get("agent_description"))) elif oef_type == OEFMessage.Type.SEARCH_AGENTS: self.search_agents(sender, request_id, cast(Query, oef_message.get("query"))) elif oef_type == OEFMessage.Type.SEARCH_SERVICES: @@ -121,7 +125,7 @@ def handle_agent_message(self, envelope: Envelope) -> None: if destination not in self._queues: msg = OEFMessage(oef_type=OEFMessage.Type.DIALOGUE_ERROR, id=STUB_DIALOGUE_ID, dialogue_id=STUB_DIALOGUE_ID, origin=destination) msg_bytes = OEFSerializer().encode(msg) - error_envelope = Envelope(to=destination, sender=envelope.sender, protocol_id=OEFMessage.protocol_id, message=msg_bytes) + error_envelope = Envelope(to=envelope.sender, sender=DEFAULT_OEF, protocol_id=OEFMessage.protocol_id, message=msg_bytes) self._send(error_envelope) return else: @@ -138,9 +142,20 @@ def register_service(self, public_key: str, service_description: Description): with self._lock: self.services[public_key].append(service_description) + def register_agent(self, public_key: str, agent_description: Description): + """ + Register a service agent in the service directory of the node. + + :param public_key: the public key of the service agent to be registered. + :param agent_description: the description of the service agent to be registered. + :return: None + """ + with self._lock: + self.agents[public_key].append(agent_description) + def register_service_wide(self, public_key: str, service_description: Description): """Register service wide.""" - raise NotImplementedError + raise NotImplementedError # pragma: no cover def unregister_service(self, public_key: str, msg_id: int, service_description: Description) -> None: """ @@ -162,6 +177,26 @@ def unregister_service(self, public_key: str, msg_id: int, service_description: if len(self.services[public_key]) == 0: self.services.pop(public_key) + def unregister_agent(self, public_key: str, msg_id: int, agent_description: Description) -> None: + """ + Unregister an agent. + + :param agent_description: + :param public_key: the public key of the service agent to be unregistered. + :param msg_id: the message id of the request. + :return: None + """ + with self._lock: + if public_key not in self.agents: + msg = OEFMessage(oef_type=OEFMessage.Type.OEF_ERROR, id=msg_id, operation=OEFMessage.OEFErrorOperation.UNREGISTER_AGENT) + msg_bytes = OEFSerializer().encode(msg) + envelope = Envelope(to=public_key, sender=DEFAULT_OEF, protocol_id=OEFMessage.protocol_id, message=msg_bytes) + self._send(envelope) + else: + self.agents[public_key].remove(agent_description) + if len(self.agents[public_key]) == 0: + self.agents.pop(public_key) + def search_agents(self, public_key: str, search_id: int, query: Query) -> None: """ Search the agents in the local Agent Directory, and send back the result. @@ -178,9 +213,10 @@ def search_agents(self, public_key: str, search_id: int, query: Query) -> None: if query.model is None: result = list(set(self.services.keys())) else: - for agent_public_key, description in self.agents.items(): - if query.model == description.data_model: - result.append(agent_public_key) + for agent_public_key, descriptions in self.agents.items(): + for description in descriptions: + if query.model == description.data_model: + result.append(agent_public_key) msg = OEFMessage(oef_type=OEFMessage.Type.SEARCH_RESULT, id=search_id, agents=sorted(set(result))) msg_bytes = OEFSerializer().encode(msg) @@ -317,16 +353,12 @@ def _receive_loop(self): except queue.Empty: pass - @property - def is_established(self) -> bool: - """Return True if the connection has been established, False otherwise.""" - return self._connection is not None - def connect(self): """Connect to the local OEF Node.""" if self._stopped: self._stopped = False self._connection = self.channel.connect() + self.connection_status.is_connected = True self.in_thread = Thread(target=self._receive_loop) self.out_thread = Thread(target=self._fetch) self.in_thread.start() @@ -340,12 +372,13 @@ def disconnect(self): self.out_thread.join() self.in_thread = None self.out_thread = None + self.connection_status.is_connected = False self.channel.disconnect() self.stop() def send(self, envelope: Envelope): """Send a message.""" - if not self.is_established: + if not self.connection_status.is_connected: raise ConnectionError("Connection not established yet. Please use 'connect()'.") self.channel.send(envelope) diff --git a/tests/test_aea.py b/tests/test_aea.py index 64eb6ece02..639b9e0744 100644 --- a/tests/test_aea.py +++ b/tests/test_aea.py @@ -18,19 +18,27 @@ # ------------------------------------------------------------------------------ """This module contains the tests for aea/aea.py.""" import os +import tempfile import time from pathlib import Path from threading import Thread +import yaml + +from aea import AEA_DIR from aea.aea import AEA +from aea.configurations.base import ProtocolConfig from aea.connections.local.connection import LocalNode, OEFLocalConnection from aea.crypto.ledger_apis import LedgerApis from aea.crypto.wallet import Wallet from aea.mail.base import MailBox, Envelope +from aea.protocols.base import Protocol from aea.protocols.default.message import DefaultMessage from aea.protocols.default.serialization import DefaultSerializer from aea.protocols.fipa.message import FIPAMessage from aea.protocols.fipa.serialization import FIPASerializer +from aea.registries.base import Resources +from aea.skills.base import Skill from .conftest import CUR_PATH, DummyConnection @@ -42,12 +50,15 @@ def test_initialise_AEA(): private_key_pem_path = os.path.join(CUR_PATH, "data", "priv.pem") wallet = Wallet({'default': private_key_pem_path}) ledger_apis = LedgerApis({}) - my_AEA = AEA("Agent0", mailbox1, wallet, ledger_apis, directory=str(Path(CUR_PATH, "aea"))) - assert AEA("Agent0", mailbox1, wallet, ledger_apis), "Agent is not initialised" + my_AEA = AEA("Agent0", mailbox1, wallet, ledger_apis, resources=Resources(str(Path(CUR_PATH, "aea")))) assert my_AEA.context == my_AEA._context, "Cannot access the Agent's Context" + assert not my_AEA.context.connection_status.is_connected, "AEA should not be connected." my_AEA.setup() assert my_AEA.resources is not None,\ "Resources must not be None after setup" + my_AEA.resources = Resources(str(Path(CUR_PATH, "aea"))) + assert my_AEA.resources is not None,\ + "Resources must not be None after set" def test_act(): @@ -65,7 +76,7 @@ def test_act(): mailbox, wallet, ledger_apis, - directory=str(Path(CUR_PATH, "data", "dummy_aea"))) + resources=Resources(str(Path(CUR_PATH, "data", "dummy_aea")))) t = Thread(target=agent.start) try: t.start() @@ -102,7 +113,7 @@ def test_react(): mailbox, wallet, ledger_apis, - directory=str(Path(CUR_PATH, "data", "dummy_aea"))) + resources=Resources(str(Path(CUR_PATH, "data", "dummy_aea")))) t = Thread(target=agent.start) try: t.start() @@ -141,7 +152,7 @@ def test_handle(): mailbox, wallet, ledger_apis, - directory=str(Path(CUR_PATH, "data", "dummy_aea"))) + resources=Resources(str(Path(CUR_PATH, "data", "dummy_aea")))) t = Thread(target=agent.start) try: t.start() @@ -180,3 +191,109 @@ def test_handle(): finally: agent.stop() t.join() + + +class TestInitializeAEAProgrammaticallyFromResourcesDir: + """Test that we can initialize the agent by providing the resource object loaded from dir.""" + + @classmethod + def setup_class(cls): + """Set the test up.""" + cls.agent_name = "MyAgent" + cls.private_key_pem_path = os.path.join(CUR_PATH, "data", "priv.pem") + cls.wallet = Wallet({'default': cls.private_key_pem_path}) + cls.ledger_apis = LedgerApis({}) + cls.connection = DummyConnection() + cls.mailbox = MailBox(cls.connection) + + cls.resources = Resources(os.path.join(CUR_PATH, "data", "dummy_aea")) + cls.aea = AEA(cls.agent_name, cls.mailbox, cls.wallet, cls.ledger_apis, cls.resources) + + cls.expected_message = DefaultMessage(type=DefaultMessage.Type.BYTES, content=b"hello") + cls.connection.receive(Envelope(to="", sender="", protocol_id="default", message=DefaultSerializer().encode(cls.expected_message))) + + cls.t = Thread(target=cls.aea.start) + cls.t.start() + + def test_initialize_aea_programmatically(self): + """Test that we can initialize an AEA programmatically.""" + time.sleep(1.0) + + dummy_behaviour = next(filter(lambda x: x.__class__.__name__ == "DummyBehaviour", self.aea.resources.behaviour_registry.fetch("dummy")), None) + assert dummy_behaviour is not None + assert dummy_behaviour.nb_act_called > 0 + + dummy_task = next(filter(lambda x: x.__class__.__name__ == "DummyTask", self.aea.resources.task_registry.fetch("dummy")), None) + assert dummy_task is not None + assert dummy_task.nb_execute_called > 0 + + dummy_handler = next(filter(lambda x: x.__class__.__name__ == "DummyHandler", self.aea.resources.handler_registry.fetch("default")), None) + assert dummy_handler is not None + assert len(dummy_handler.handled_messages) == 1 + assert dummy_handler.handled_messages[0] == self.expected_message + + @classmethod + def teardown_class(cls): + """Tear the test down.""" + cls.aea.stop() + cls.t.join() + + +class TestInitializeAEAProgrammaticallyBuildResources: + """Test that we can initialize the agent by building the resource object.""" + + @classmethod + def setup_class(cls): + """Set the test up.""" + cls.agent_name = "MyAgent" + cls.private_key_pem_path = os.path.join(CUR_PATH, "data", "priv.pem") + cls.wallet = Wallet({'default': cls.private_key_pem_path}) + cls.ledger_apis = LedgerApis({}) + cls.connection = DummyConnection() + cls.mailbox = MailBox(cls.connection) + + cls.temp = tempfile.mkdtemp(prefix="test_aea_resources") + cls.resources = Resources(cls.temp) + cls.aea = AEA(cls.agent_name, cls.mailbox, cls.wallet, cls.ledger_apis, resources=cls.resources) + + cls.default_protocol_configuration = ProtocolConfig.from_json( + yaml.safe_load(open(Path(AEA_DIR, "protocols", "default", "protocol.yaml")))) + cls.default_protocol = Protocol("default", + DefaultSerializer(), + cls.default_protocol_configuration) + cls.resources.protocol_registry.register(("default", None), cls.default_protocol) + + cls.error_skill = Skill.from_dir(Path(AEA_DIR, "skills", "error"), cls.aea.context) + cls.dummy_skill = Skill.from_dir(Path(CUR_PATH, "data", "dummy_skill"), cls.aea.context) + cls.resources.add_skill(cls.dummy_skill) + cls.resources.add_skill(cls.error_skill) + + cls.expected_message = DefaultMessage(type=DefaultMessage.Type.BYTES, content=b"hello") + cls.connection.receive(Envelope(to="", sender="", protocol_id="default", message=DefaultSerializer().encode(cls.expected_message))) + + cls.t = Thread(target=cls.aea.start) + cls.t.start() + + def test_initialize_aea_programmatically(self): + """Test that we can initialize an AEA programmatically.""" + time.sleep(1.0) + + dummy_behaviour = next(filter(lambda x: x.__class__.__name__ == "DummyBehaviour", self.aea.resources.behaviour_registry.fetch("dummy")), None) + assert dummy_behaviour is not None + assert dummy_behaviour.nb_act_called > 0 + + dummy_task = next(filter(lambda x: x.__class__.__name__ == "DummyTask", self.aea.resources.task_registry.fetch("dummy")), None) + assert dummy_task is not None + assert dummy_task.nb_execute_called > 0 + + dummy_handler = next(filter(lambda x: x.__class__.__name__ == "DummyHandler", self.aea.resources.handler_registry.fetch("default")), None) + assert dummy_handler is not None + assert len(dummy_handler.handled_messages) == 1 + assert dummy_handler.handled_messages[0] == self.expected_message + + @classmethod + def teardown_class(cls): + """Tear the test down.""" + cls.aea.stop() + cls.t.join() + Path(cls.temp).rmdir() diff --git a/tests/test_connections/test_local/test_search_services.py b/tests/test_connections/test_local/test_search_services.py index b73db22b37..4501e26917 100644 --- a/tests/test_connections/test_local/test_search_services.py +++ b/tests/test_connections/test_local/test_search_services.py @@ -287,7 +287,7 @@ def setup_class(cls): def test_from_config(self): """Test the configuration loading.""" con = OEFLocalConnection.from_config(public_key="pk", connection_configuration=ConnectionConfig()) - assert not con.is_established, "We are connected..." + assert not con.connection_status.is_connected, "We are connected..." @classmethod def teardown_class(cls): diff --git a/tests/test_connections/test_oef/test_communication.py b/tests/test_connections/test_oef/test_communication.py index f00467f78e..46603b3179 100644 --- a/tests/test_connections/test_oef/test_communication.py +++ b/tests/test_connections/test_oef/test_communication.py @@ -23,6 +23,7 @@ import time from queue import Queue from typing import cast +from threading import Thread from unittest import mock import pytest @@ -517,7 +518,7 @@ def teardown_class(cls): class TestOefConnection: - """Tests the con.is_established property.""" + """Tests the con.connection_status.is_connected property.""" @pytest.fixture(autouse=True) def _start_oef_node(self, network_node): @@ -531,16 +532,21 @@ def test_connection(self): mailbox.disconnect() def test_oef_connect(self): - """Test the OEFConnection.""" + """Test the OEFConnection with a wrong address.""" con = OEFConnection(public_key="pk", oef_addr="this_is_not_an_address") - assert not con.is_established - with pytest.raises(ConnectionError): - con.connect() + assert not con.connection_status.is_connected + connection_thread = Thread(target=con.connect) + connection_thread.start() + time.sleep(2.0) + assert not con.connection_status.is_connected + con._stopped = True + con._core.stop() + connection_thread.join() def test_oef_from_config(self): """Test the Connection from config File.""" con = OEFConnection.from_config(public_key="pk", connection_configuration=ConnectionConfig()) - assert not con.is_established, "We are connected..." + assert not con.connection_status.is_connected, "We are connected..." class TestOefConstraint: diff --git a/tests/test_connections/test_stub.py b/tests/test_connections/test_stub.py index 1012cc212e..8a657e0530 100644 --- a/tests/test_connections/test_stub.py +++ b/tests/test_connections/test_stub.py @@ -62,7 +62,7 @@ def test_reception(self): def test_connection_is_established(self): """Test the stub connection is established and then bad formatted messages.""" - assert self.connection.is_established + assert self.connection.connection_status.is_connected msg = DefaultMessage(type=DefaultMessage.Type.BYTES, content=b"hello") encoded_envelope = "{},{},{},{}".format("any", "any", DefaultMessage.protocol_id, DefaultSerializer().encode(msg).decode("utf-8")) encoded_envelope = base64.b64encode(encoded_envelope.encode("utf-8")) @@ -72,7 +72,7 @@ def test_connection_is_established(self): def test_connection_from_config(self): """Test loading a connection from config file.""" stub_con = StubConnection.from_config(public_key="pk", connection_configuration=ConnectionConfig()) - assert not stub_con.is_established + assert not stub_con.connection_status.is_connected def test_send_message(self): """Test that the messages in the outbox are posted on the output file.""" diff --git a/tests/test_crypto/test_ledger_apis.py b/tests/test_crypto/test_ledger_apis.py index f7bd8d676e..81b0b7de1c 100644 --- a/tests/test_crypto/test_ledger_apis.py +++ b/tests/test_crypto/test_ledger_apis.py @@ -20,20 +20,27 @@ """This module contains the tests for the crypto/helpers module.""" import logging -# from unittest import mock +import os +from typing import Dict +from unittest import mock import pytest +from hexbytes import HexBytes -from aea.crypto.ethereum import ETHEREUM -from aea.crypto.fetchai import FETCHAI -from aea.crypto.ledger_apis import LedgerApis, DEFAULT_FETCHAI_CONFIG # , _try_to_instantiate_fetchai_ledger_api, _try_to_instantiate_ethereum_ledger_api - +from aea.crypto.ethereum import ETHEREUM, EthereumCrypto +from aea.crypto.fetchai import FETCHAI, FetchAICrypto +from aea.crypto.ledger_apis import LedgerApis, DEFAULT_FETCHAI_CONFIG, \ + _try_to_instantiate_fetchai_ledger_api, \ + _try_to_instantiate_ethereum_ledger_api +from tests.conftest import CUR_PATH logger = logging.getLogger(__name__) DEFAULT_ETHEREUM_CONFIG = ("https://ropsten.infura.io/v3/f00f7b3ba0e848ddbdc8941c527447fe", 3) fet_address = "B3t9pv4rYccWqCjeuoXsDoeXLiKxVAQh6Q3CLAiNZZQ2mtqF1" eth_address = "0x21795D753752ccC1AC728002D23Ba33cbF13b8b0" +GAS_PRICE = '50' +GAS_ID = 'gwei' class TestLedgerApis: @@ -46,58 +53,155 @@ def test_initialisation(self): assert ledger_apis.configs.get(ETHEREUM) == DEFAULT_ETHEREUM_CONFIG assert ledger_apis.has_fetchai assert ledger_apis.has_ethereum + assert isinstance(ledger_apis.last_tx_statuses, Dict) unknown_config = ("UknownPath", 8080) with pytest.raises(ValueError): - ledger_apis = LedgerApis({"UNKNOWN": unknown_config}) - - # def test_token_balance(self): - # """Test the token_balance for the different tokens.""" - # ledger_apis = LedgerApis({ETHEREUM: DEFAULT_ETHEREUM_CONFIG, - # FETCHAI: DEFAULT_FETCHAI_CONFIG}) - - # with mock.patch.object(ledger_apis, 'token_balance', return_value=10): - # balance = ledger_apis.token_balance(FETCHAI, eth_address) - # assert balance == 10 - # balance = ledger_apis.token_balance(ETHEREUM, eth_address) - # assert balance == 10, "The specific address has some eth" - # with mock.patch.object(ledger_apis, 'token_balance', return_value=0): - # balance = ledger_apis.token_balance(ETHEREUM, fet_address) - # assert balance == 0, "Should trigger the Exception and the balance will be 0" - # with mock.patch.object(ledger_apis, 'token_balance', return_value=Exception): - # balance = ledger_apis.token_balance(ETHEREUM, fet_address) - # assert balance == 0, "Should trigger the Exception and the balance will be 0" - # with pytest.raises(AssertionError): - # balance = ledger_apis.token_balance("UNKNOWN", fet_address) - # assert balance == 0, "Unknown identifier so it will return 0" - # def test_transfer(self): - # """Test the transfer function for the supported tokens.""" - # private_key_path = os.path.join(CUR_PATH, "data", "eth_private_key.txt") - # eth_obj = EthereumCrypto(private_key_path=private_key_path) - # private_key_path = os.path.join(CUR_PATH, 'data', "fet_private_key.txt") - # fet_obj = FetchAICrypto(private_key_path=private_key_path) - # ledger_apis = LedgerApis({ETHEREUM: DEFAULT_ETHEREUM_CONFIG, - # FETCHAI: DEFAULT_FETCHAI_CONFIG}) - # - # with mock.patch.object(ledger_apis, 'transfer', - # return_value= "97fcacaaf94b62318c4e4bbf53fd2608c15062f17a6d1bffee0ba7af9b710e35"): - # tx_digest = ledger_apis.transfer(FETCHAI, fet_obj, fet_address, amount=10, tx_fee=10) - # assert tx_digest is not None - # with mock.patch.object(ledger_apis, 'is_tx_settled', return_value= True): - # assert ledger_apis.is_tx_settled(identifier=FETCHAI, tx_digest=tx_digest, amount=10) - # with mock.patch.object(ledger_apis, 'is_tx_settled', return_value= False): - # assert not ledger_apis.is_tx_settled(identifier=FETCHAI, tx_digest=tx_digest, amount=10) - # with mock.patch.object(ledger_apis, 'transfer', - # return_value="97fcacaaf94b62318c4e4bbf53fd2608c15062f17a6d1bffee0ba7af9b710e35"): - # tx_digest = ledger_apis.transfer(ETHEREUM, eth_obj, eth_address, amount=10, tx_fee=200000) - # assert tx_digest is not None - # with mock.patch.object(ledger_apis, 'is_tx_settled', return_value= True): - # assert ledger_apis.is_tx_settled(identifier=FETCHAI, tx_digest=tx_digest, amount=10) - # with mock.patch.object(ledger_apis, 'is_tx_settled', return_value= False): - # assert not ledger_apis.is_tx_settled(identifier=FETCHAI, tx_digest=tx_digest, amount=10) - # def test_try_to_instantiate_fetchai_ledger_api(self): - # """Test the instantiation of the fetchai ledger api.""" - # _try_to_instantiate_fetchai_ledger_api(addr="127.0.0.1", port=80) - - # def test__try_to_instantiate_ethereum_ledger_api(self): - # """Test the instantiation of the ethereum ledger api.""" - # _try_to_instantiate_ethereum_ledger_api(addr="127.0.0.1", port=80) + LedgerApis({"UNKNOWN": unknown_config}) + + def test_eth_token_balance(self): + """Test the token_balance for the eth tokens.""" + ledger_apis = LedgerApis({ETHEREUM: DEFAULT_ETHEREUM_CONFIG, + FETCHAI: DEFAULT_FETCHAI_CONFIG}) + + api = ledger_apis.apis[ETHEREUM] + with mock.patch.object(api.eth, 'getBalance', return_value=10): + balance = ledger_apis.token_balance(ETHEREUM, eth_address) + assert balance == 10 + assert ledger_apis.last_tx_statuses[ETHEREUM] == 'OK' + + with mock.patch.object(api.eth, 'getBalance', return_value=0, side_effect=Exception): + balance = ledger_apis.token_balance(ETHEREUM, fet_address) + assert balance == 0, "This must be 0 since the address is wrong" + assert ledger_apis.last_tx_statuses[ETHEREUM] == 'ERROR' + + def test_unknown_token_balance(self): + """Test the token_balance for the unknown tokens.""" + ledger_apis = LedgerApis({ETHEREUM: DEFAULT_ETHEREUM_CONFIG, + FETCHAI: DEFAULT_FETCHAI_CONFIG}) + with pytest.raises(AssertionError): + balance = ledger_apis.token_balance("UNKNOWN", fet_address) + assert balance == 0, "Unknown identifier so it will return 0" + + def test_fet_token_balance(self): + """Test the token_balance for the fet tokens.""" + ledger_apis = LedgerApis({ETHEREUM: DEFAULT_ETHEREUM_CONFIG, + FETCHAI: DEFAULT_FETCHAI_CONFIG}) + + api = ledger_apis.apis[FETCHAI] + with mock.patch.object(api.tokens, 'balance', return_value=10): + balance = ledger_apis.token_balance(FETCHAI, fet_address) + assert balance == 10 + assert ledger_apis.last_tx_statuses[FETCHAI] == 'OK' + + with mock.patch.object(api.tokens, 'balance', return_value=0, side_effect=Exception): + balance = ledger_apis.token_balance(FETCHAI, eth_address) + assert balance == 0, "This must be 0 since the address is wrong" + assert ledger_apis.last_tx_statuses[FETCHAI] == 'ERROR' + + def test_transfer_fetchai(self): + """Test the transfer function for fetchai token.""" + private_key_path = os.path.join(CUR_PATH, 'data', "fet_private_key.txt") + fet_obj = FetchAICrypto(private_key_path=private_key_path) + ledger_apis = LedgerApis({ETHEREUM: DEFAULT_ETHEREUM_CONFIG, + FETCHAI: DEFAULT_FETCHAI_CONFIG}) + + with mock.patch.object(ledger_apis.apis.get(FETCHAI).tokens, 'transfer', + return_value="97fcacaaf94b62318c4e4bbf53fd2608c15062f17a6d1bffee0ba7af9b710e35"): + with mock.patch.object(ledger_apis.apis.get(FETCHAI), 'sync'): + tx_digest = ledger_apis.transfer(FETCHAI, fet_obj, fet_address, amount=10, tx_fee=10) + assert tx_digest is not None + assert ledger_apis.last_tx_statuses[FETCHAI] == 'OK' + + def test_failed_transfer_fetchai(self): + """Test the transfer function for fetchai token fails.""" + private_key_path = os.path.join(CUR_PATH, 'data', "fet_private_key.txt") + fet_obj = FetchAICrypto(private_key_path=private_key_path) + ledger_apis = LedgerApis({ETHEREUM: DEFAULT_ETHEREUM_CONFIG, + FETCHAI: DEFAULT_FETCHAI_CONFIG}) + + with mock.patch.object(ledger_apis.apis.get(FETCHAI).tokens, 'transfer', + return_value="97fcacaaf94b62318c4e4bbf53fd2608c15062f17a6d1bffee0ba7af9b710e35"): + with mock.patch.object(ledger_apis.apis.get(FETCHAI), 'sync', side_effect=Exception): + tx_digest = ledger_apis.transfer(FETCHAI, fet_obj, fet_address, amount=10, tx_fee=10) + assert tx_digest is None + assert ledger_apis.last_tx_statuses[FETCHAI] == 'ERROR' + + def test_transfer_ethereum(self): + """Test the transfer function for ethereum token.""" + private_key_path = os.path.join(CUR_PATH, "data", "eth_private_key.txt") + eth_obj = EthereumCrypto(private_key_path=private_key_path) + ledger_apis = LedgerApis({ETHEREUM: DEFAULT_ETHEREUM_CONFIG, + FETCHAI: DEFAULT_FETCHAI_CONFIG}) + with mock.patch.object(ledger_apis.apis.get(ETHEREUM).eth, 'getTransactionCount', return_value=5): + with mock.patch.object(ledger_apis.apis.get(ETHEREUM).eth.account, 'signTransaction', + return_value=mock.Mock()): + result = HexBytes('0xf85f808082c35094d898d5e829717c72e7438bad593076686d7d164a80801ba005c2e99ecee98a12fbf28ab9577423f42e9e88f2291b3acc8228de743884c874a077d6bc77a47ad41ec85c96aac2ad27f05a039c4787fca8a1e5ee2d8c7ec1bb6a') + with mock.patch.object(ledger_apis.apis.get(ETHEREUM).eth, 'sendRawTransaction', + return_value=result): + with mock.patch.object(ledger_apis.apis.get(ETHEREUM).eth, "getTransactionReceipt", + return_value=b'0xa13f2f926233bc4638a20deeb8aaa7e8d6a96e487392fa55823f925220f6efed'): + tx_digest = ledger_apis.transfer(ETHEREUM, eth_obj, eth_address, amount=10, tx_fee=200000) + assert tx_digest is not None + assert ledger_apis.last_tx_statuses[ETHEREUM] == 'OK' + + def test_failed_transfer_ethereum(self): + """Test the transfer function for ethereum token fails.""" + private_key_path = os.path.join(CUR_PATH, "data", "eth_private_key.txt") + eth_obj = EthereumCrypto(private_key_path=private_key_path) + ledger_apis = LedgerApis({ETHEREUM: DEFAULT_ETHEREUM_CONFIG, + FETCHAI: DEFAULT_FETCHAI_CONFIG}) + with mock.patch.object(ledger_apis.apis.get(ETHEREUM).eth, 'getTransactionCount', return_value=5, side_effect=Exception): + tx_digest = ledger_apis.transfer(ETHEREUM, eth_obj, eth_address, amount=10, tx_fee=200000) + assert tx_digest is None + assert ledger_apis.last_tx_statuses[ETHEREUM] == 'ERROR' + + def test_is_tx_settled_fetchai(self): + """Test if the transaction is settled for fetchai.""" + ledger_apis = LedgerApis({ETHEREUM: DEFAULT_ETHEREUM_CONFIG, + FETCHAI: DEFAULT_FETCHAI_CONFIG}) + tx_digest = "97fcacaaf94b62318c4e4bbf53fd2608c15062f17a6d1bffee0ba7af9b710e35" + with pytest.raises(AssertionError): + ledger_apis.is_tx_settled("Unknown", tx_digest=tx_digest, amount=10) + + with mock.patch.object(ledger_apis.apis[FETCHAI].tx, "status", return_value='Submitted'): + is_successful = ledger_apis.is_tx_settled(FETCHAI, tx_digest=tx_digest, amount=10) + assert is_successful + assert ledger_apis.last_tx_statuses[FETCHAI] == 'OK' + + with mock.patch.object(ledger_apis.apis[FETCHAI].tx, "status", side_effect=Exception): + is_successful = ledger_apis.is_tx_settled(FETCHAI, tx_digest=tx_digest, amount=10) + assert not is_successful + assert ledger_apis.last_tx_statuses[FETCHAI] == 'ERROR' + + def test_is_tx_settled_ethereum(self): + """Test if the transaction is settled for eth.""" + ledger_apis = LedgerApis({ETHEREUM: DEFAULT_ETHEREUM_CONFIG, + FETCHAI: DEFAULT_FETCHAI_CONFIG}) + tx_digest = "97fcacaaf94b62318c4e4bbf53fd2608c15062f17a6d1bffee0ba7af9b710e35" + result = HexBytes( + '0xf85f808082c35094d898d5e829717c72e7438bad593076686d7d164a80801ba005c2e99ecee98a12fbf28ab9577423f42e9e88f2291b3acc8228de743884c874a077d6bc77a47ad41ec85c96aac2ad27f05a039c4787fca8a1e5ee2d8c7ec1bb6a') + with mock.patch.object(ledger_apis.apis[ETHEREUM].eth, "getTransactionReceipt", return_value=result): + is_successful = ledger_apis.is_tx_settled(ETHEREUM, tx_digest=tx_digest, amount=10) + assert is_successful + assert ledger_apis.last_tx_statuses[ETHEREUM] == 'OK' + + with mock.patch.object(ledger_apis.apis[ETHEREUM].eth, "getTransactionReceipt", side_effect=Exception): + is_successful = ledger_apis.is_tx_settled(ETHEREUM, tx_digest=tx_digest, amount=10) + assert not is_successful + assert ledger_apis.last_tx_statuses[ETHEREUM] == 'ERROR' + + def test_try_to_instantiate_fetchai_ledger_api(self): + """Test the instantiation of the fetchai ledger api.""" + _try_to_instantiate_fetchai_ledger_api(addr="127.0.0.1", port=80) + from fetchai.ledger.api import LedgerApi + with mock.patch.object(LedgerApi, "__init__", side_effect=Exception): + with pytest.raises(SystemExit): + _try_to_instantiate_fetchai_ledger_api(addr="127.0.0.1", port=80) + + def test__try_to_instantiate_ethereum_ledger_api(self): + """Test the instantiation of the ethereum ledger api.""" + _try_to_instantiate_ethereum_ledger_api(addr="127.0.0.1", port=80) + from web3 import Web3 + with mock.patch.object(Web3, "__init__", side_effect=Exception): + with pytest.raises(SystemExit): + _try_to_instantiate_ethereum_ledger_api(addr="127.0.0.1", port=80) diff --git a/tests/test_gui/__init__.py b/tests/test_gui/__init__.py new file mode 100644 index 0000000000..4bbd0b1527 --- /dev/null +++ b/tests/test_gui/__init__.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# ------------------------------------------------------------------------------ +# +# Copyright 2018-2019 Fetch.AI Limited +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ------------------------------------------------------------------------------ + +"""The tests for the cli gui.""" diff --git a/tests/test_gui/test_agents.py b/tests/test_gui/test_agents.py new file mode 100644 index 0000000000..3de099f003 --- /dev/null +++ b/tests/test_gui/test_agents.py @@ -0,0 +1,108 @@ +# -*- coding: utf-8 -*- +# ------------------------------------------------------------------------------ +# +# Copyright 2018-2019 Fetch.AI Limited +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ------------------------------------------------------------------------------ + +"""This test module contains the tests for the `aea gui` sub-commands.""" +import json + +from .test_base import TestBase + + +class TestHomePageExits(TestBase): + """Test that the gui home page exits and has the correct title.""" + + def test_home_page_exits(self): + """Test that the home-page exits.""" + # sends HTTP GET request to the application + # on the specified path + result = self.app.get('/') + + # assert the status code of the response + assert result.status_code == 200 + assert "Fetch.AI AEA CLI REST API" in str(result.data) + + +class TestCreateAndList(TestBase): + """Test that we can create an agent and get list of agents.""" + + def test_agents_create_and_list(self): + """Test that we can create an agent and get list of agents.""" + agent_name = "test_agent" + + # Make sure there are no agents in the directory + response_list = self.app.get( + 'api/agent', + data=None, + content_type='application/json', + ) + data = json.loads(response_list.get_data(as_text=True)) + assert len(data) == 0 + assert response_list.status_code == 200 + + # Make an agent + assert self.create_agent(agent_name).status_code == 201 + + # Ensure there is now one agent + response_list = self.app.get( + 'api/agent', + data=None, + content_type='application/json', + ) + data = json.loads(response_list.get_data(as_text=True)) + assert response_list.status_code == 200 + assert len(data) == 1 + assert data[0]['id'] == agent_name + + # Delete the agent + response_delete = self.app.delete( + 'api/agent/' + agent_name, + data=None, + content_type='application/json', + ) + assert response_delete.status_code == 200 + + # Ensure there are now agents + response_list = self.app.get( + 'api/agent', + data=None, + content_type='application/json', + ) + data = json.loads(response_list.get_data(as_text=True)) + assert response_list.status_code == 200 + assert len(data) == 0 + + +class TestDuplicateAgentError(TestBase): + """Test that if you try and create two agents of the same name we get an error.""" + + def test_duplicate_agent_error(self): + """Test that if you try and create two agents of the same name we get an error.""" + response_list = self.app.get( + 'api/agent', + data=None, + content_type='application/json', + ) + data = json.loads(response_list.get_data(as_text=True)) + assert len(data) == 0 + assert response_list.status_code == 200 + + # Make an agent + assert self.create_agent("test_agent").status_code == 201 + + # Attempt tp make the same agent again + assert self.create_agent("test_agent").status_code == 400 diff --git a/tests/test_gui/test_base.py b/tests/test_gui/test_base.py new file mode 100644 index 0000000000..25c21ea516 --- /dev/null +++ b/tests/test_gui/test_base.py @@ -0,0 +1,153 @@ +# -*- coding: utf-8 -*- +# ------------------------------------------------------------------------------ +# +# Copyright 2018-2019 Fetch.AI Limited +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ------------------------------------------------------------------------------ + +"""This test module contains the tests for the `aea gui` sub-commands.""" +import os +import shutil +import json + +import tempfile + +import aea.cli_gui + + +class TestBase: + """Base class for testing gui entry points.""" + + @classmethod + def setup_class(cls): + """Set the test up.""" + cls.temp_dir = tempfile.mkdtemp() + cls.cwd = os.getcwd() + os.chdir(cls.temp_dir) + + cls.app = aea.cli_gui.run_test() + cls.app.debug = True + cls.app.testing = True + + def create_agent(self, name): + """Create an aea project.""" + return self.app.post( + 'api/agent', + content_type='application/json', + data=json.dumps(name)) + + @classmethod + def teardown_class(cls): + """Teardowm the test.""" + os.chdir(cls.cwd) + try: + shutil.rmtree(cls.temp_dir) + except (OSError, IOError): + pass + + def _test_create_and_list(self, item_type, new_item_name): + agent_name = "test_agent" + + # Make an agent + assert self.create_agent(agent_name).status_code == 201 + + # Get list of items on this agent + response_list = self.app.get( + 'api/agent/' + agent_name + "/" + item_type, + data=None, + content_type='application/json', + ) + data = json.loads(response_list.get_data(as_text=True)) + assert response_list.status_code == 200 + prev_count = len(data) + + # Get list of items from the package + response_list = self.app.get( + 'api/' + item_type, + data=None, + content_type='application/json', + ) + data = json.loads(response_list.get_data(as_text=True)) + assert response_list.status_code == 200 + assert len(data) > 0 + + # Add a item + response_create = self.app.post( + 'api/agent/' + agent_name + "/" + item_type, + content_type='application/json', + data=json.dumps(new_item_name) + ) + assert response_create.status_code == 201 + + # Get list of items + response_list = self.app.get( + 'api/agent/' + agent_name + "/" + item_type, + data=None, + content_type='application/json', + ) + data = json.loads(response_list.get_data(as_text=True)) + assert response_list.status_code == 200 + assert len(data) == prev_count + 1 + new_connection_exists = False + for element in data: + assert element['id'] != "" + assert element['description'] != "" + if element['id'] == new_item_name: + new_connection_exists = True + assert new_connection_exists + + # Remove the item + response_delete = self.app.delete( + 'api/agent/' + agent_name + "/" + item_type + "/" + new_item_name, + data=None, + content_type='application/json', + ) + assert response_delete.status_code == 201 + + # Get list of items + response_list = self.app.get( + 'api/agent/' + agent_name + "/" + item_type, + data=None, + content_type='application/json', + ) + data = json.loads(response_list.get_data(as_text=True)) + assert response_list.status_code == 200 + assert len(data) == prev_count + + # Scaffold item + scaffold_item_name = "scaffold_item" + response_create = self.app.post( + 'api/agent/' + agent_name + "/" + item_type + "/scaffold", + content_type='application/json', + data=json.dumps(scaffold_item_name) + ) + assert response_create.status_code == 201 + + # Get list of items + response_list = self.app.get( + 'api/agent/' + agent_name + "/" + item_type, + data=None, + content_type='application/json', + ) + data = json.loads(response_list.get_data(as_text=True)) + assert response_list.status_code == 200 + assert len(data) == prev_count + 1 + new_connection_exists = False + for element in data: + assert element['id'] != "" + assert element['description'] != "" + if element['id'] == scaffold_item_name: + new_connection_exists = True + assert new_connection_exists diff --git a/tests/test_gui/test_items.py b/tests/test_gui/test_items.py new file mode 100644 index 0000000000..b0f6fff178 --- /dev/null +++ b/tests/test_gui/test_items.py @@ -0,0 +1,45 @@ +# -*- coding: utf-8 -*- +# ------------------------------------------------------------------------------ +# +# Copyright 2018-2019 Fetch.AI Limited +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ------------------------------------------------------------------------------ + +"""This test module contains the tests for the `aea gui` sub-commands.""" +from .test_base import TestBase + + +class TestCreateConnectionAndList(TestBase): + """Test that we can create connections and list them.""" + + def test_create_and_list_connections(self): + """Test that we can create connections and list them.""" + self._test_create_and_list("connection", "local") + + +class TestCreateProtocolAndList(TestBase): + """Test that we can create protocols and list them.""" + + def test_create_and_list_protocols(self): + """Test that we can create protocols and list them.""" + self._test_create_and_list("protocol", "fipa") + + +class TestCreateSkillAndList(TestBase): + """Test that we can create skills and list them.""" + + def test_create_and_list_skills(self): + """Test that we can create skills and list them.""" + self._test_create_and_list("skill", "scaffold") diff --git a/tests/test_gui/test_run_agent.py b/tests/test_gui/test_run_agent.py new file mode 100644 index 0000000000..9ec3159255 --- /dev/null +++ b/tests/test_gui/test_run_agent.py @@ -0,0 +1,84 @@ +# -*- coding: utf-8 -*- +# ------------------------------------------------------------------------------ +# +# Copyright 2018-2019 Fetch.AI Limited +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# ------------------------------------------------------------------------------ + +"""This test module contains the tests for the `aea gui` sub-commands.""" +import json +import time +from .test_base import TestBase + + +class TestRunAgent(TestBase): + """Test for running and agent, reading TTY and errors.""" + + def test_create_and_run_agent(self): + """Test for running and agent, reading TTY and errors.""" + agent_name = "test_agent" + + # Make an agent + assert self.create_agent(agent_name).status_code == 201 + + # Add the local connection + response_add = self.app.post( + 'api/agent/' + agent_name + "/connection", + content_type='application/json', + data=json.dumps("local") + ) + assert response_add.status_code == 201 + + # run the agent with local connection (as no OEF node is running) + response_run = self.app.post( + 'api/agent/' + agent_name + "/run", + content_type='application/json', + data=json.dumps("local") + ) + assert response_run.status_code == 201 + + time.sleep(2) + + # Get the running status + response_status = self.app.get( + 'api/agent/' + agent_name + "/run", + data=None, + content_type='application/json', + ) + assert response_status.status_code == 200 + data = json.loads(response_status.get_data(as_text=True)) + + assert data["error"] == "" + assert "RUNNING" in data["status"] + + # Stop the agent running + response_stop = self.app.delete( + 'api/agent/' + agent_name + "/run", + data=None, + content_type='application/json', + ) + assert response_stop.status_code == 200 + + # Get the running status + response_status = self.app.get( + 'api/agent/' + agent_name + "/run", + data=None, + content_type='application/json', + ) + assert response_status.status_code == 200 + data = json.loads(response_status.get_data(as_text=True)) + + assert "process terminate" in data["error"] + assert "NOT_STARTED" in data["status"] diff --git a/tests/test_registries.py b/tests/test_registries.py index b7b71e20c7..875a4544e3 100644 --- a/tests/test_registries.py +++ b/tests/test_registries.py @@ -138,8 +138,9 @@ def setup_class(cls): private_key_pem_path = os.path.join(CUR_PATH, "data", "priv.pem") wallet = Wallet({'default': private_key_pem_path}) ledger_apis = LedgerApis({}) - cls.aea = AEA(cls.agent_name, mailbox, wallet, ledger_apis, directory=cls.agent_folder) - cls.resources = Resources.from_resource_dir(os.path.join(cls.agent_folder), cls.aea.context) + cls.resources = Resources(os.path.join(cls.agent_folder)) + cls.aea = AEA(cls.agent_name, mailbox, wallet, ledger_apis, resources=cls.resources) + cls.resources.load(cls.aea.context) cls.expected_skills = {"dummy", "error"} @@ -298,7 +299,7 @@ def setup_class(cls): private_key_pem_path = os.path.join(CUR_PATH, "data", "priv.pem") wallet = Wallet({'default': private_key_pem_path}) ledger_apis = LedgerApis({}) - cls.aea = AEA(cls.agent_name, mailbox, wallet, ledger_apis, directory=cls.agent_folder) + cls.aea = AEA(cls.agent_name, mailbox, wallet, ledger_apis, resources=Resources(cls.agent_folder)) def test_handle_internal_messages(self): """Test that the internal messages are handled.""" diff --git a/tests/test_skills/test_base.py b/tests/test_skills/test_base.py index c69e325230..9ea06e0c1e 100644 --- a/tests/test_skills/test_base.py +++ b/tests/test_skills/test_base.py @@ -26,7 +26,8 @@ import aea.registries.base -from aea.aea import AEA +from aea.aea import AEA, Resources +from aea.connections.base import ConnectionStatus from aea.crypto.wallet import Wallet from aea.crypto.ledger_apis import LedgerApis from aea.decision_maker.base import OwnershipState, Preferences @@ -41,7 +42,7 @@ def test_agent_context_ledger_apis(): wallet = Wallet({'default': private_key_pem_path}) mailbox1 = MailBox(DummyConnection()) ledger_apis = LedgerApis({"fetchai": ('alpha.fetch-ai.com', 80)}) - my_aea = AEA("Agent0", mailbox1, wallet, ledger_apis, directory=str(Path(CUR_PATH, "data", "dummy_aea"))) + my_aea = AEA("Agent0", mailbox1, wallet, ledger_apis, resources=Resources(str(Path(CUR_PATH, "data", "dummy_aea")))) assert set(my_aea.context.ledger_apis.apis.keys()) == {"fetchai"} fetchai_ledger_api_obj = my_aea.context.ledger_apis.apis["fetchai"] @@ -59,7 +60,7 @@ def setup_class(cls): cls.wallet = Wallet({'default': private_key_pem_path}) cls.ledger_apis = LedgerApis({"fetchai": ("alpha.fetch-ai.com", 80)}) cls.mailbox1 = MailBox(DummyConnection()) - cls.my_aea = AEA("Agent0", cls.mailbox1, cls.wallet, cls.ledger_apis, directory=str(Path(CUR_PATH, "data", "dummy_aea"))) + cls.my_aea = AEA("Agent0", cls.mailbox1, cls.wallet, cls.ledger_apis, resources=Resources(str(Path(CUR_PATH, "data", "dummy_aea")))) cls.skill_context = SkillContext(cls.my_aea.context) def test_agent_name(self): @@ -78,6 +79,10 @@ def test_agent_address(self): """Test the default agent's address.""" assert self.skill_context.agent_address == self.my_aea.wallet.addresses['default'] + def test_connection_status(self): + """Test the default agent's connection status.""" + assert isinstance(self.skill_context.connection_status, ConnectionStatus) + def test_decision_maker_message_queue(self): """Test the decision maker's queue.""" assert isinstance(self.skill_context.decision_maker_message_queue, Queue) @@ -137,7 +142,7 @@ def setup_class(cls): cls.wallet = Wallet({'default': private_key_pem_path}) ledger_apis = LedgerApis({}) cls.mailbox1 = MailBox(DummyConnection()) - cls.my_aea = AEA("agent_name", cls.mailbox1, cls.wallet, ledger_apis, directory=str(Path(CUR_PATH, "data", "dummy_aea"))) + cls.my_aea = AEA("agent_name", cls.mailbox1, cls.wallet, ledger_apis, resources=Resources(str(Path(CUR_PATH, "data", "dummy_aea")))) cls.agent_context = cls.my_aea.context def test_missing_handler(self): diff --git a/tests/test_skills/test_error.py b/tests/test_skills/test_error.py index 6bdfe83191..6fcd82dc24 100644 --- a/tests/test_skills/test_error.py +++ b/tests/test_skills/test_error.py @@ -30,6 +30,7 @@ from aea.protocols.fipa.message import FIPAMessage from aea.protocols.fipa.serialization import FIPASerializer from aea.protocols.oef.message import OEFMessage +from aea.registries.base import Resources from aea.skills.base import SkillContext from aea.skills.error.behaviours import ErrorBehaviour from aea.skills.error.handlers import ErrorHandler @@ -53,7 +54,7 @@ def setup_class(cls): cls.connection = DummyConnection() cls.mailbox1 = MailBox(cls.connection) cls.my_aea = AEA(cls.agent_name, cls.mailbox1, cls.wallet, cls.ledger_apis, timeout=2.0, - directory=str(Path(CUR_PATH, "data/dummy_aea"))) + resources=Resources(str(Path(CUR_PATH, "data/dummy_aea")))) cls.skill_context = SkillContext(cls.my_aea._context) cls.my_error_handler = ErrorHandler(skill_context=cls.skill_context)