diff --git a/Pipfile b/Pipfile index 94c25ff..f925e55 100644 --- a/Pipfile +++ b/Pipfile @@ -4,20 +4,20 @@ verify_ssl = true name = "pypi" [packages] -web3 = "*" +web3 = "<=6.15.1" twisted = "*" -eth-account = "<0.9" +eth-account = "<=0.10.0" eth-typing = "<4,>=3.5.2" eth-utils = "<3,>=2.3.1" eth-abi = "<5,>=4.2.1" eth-keys = "<0.5,>=0.4.0" python-statemachine = "*" rlp = "<4,>=3.0.0" -urllib3 = "<2,>=1.26.16" +urllib3 = "<=2.2.0" [dev-packages] # testing -eth-ape = "<7" +eth-ape = "*" pytest = "*" pytest-mock = "*" pytest-twisted = "*" diff --git a/Pipfile.lock b/Pipfile.lock index aae2406..d1599d9 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "91319a0eeb845379c14cef886d2ce39c1036f2d74ae11a65730c09f34e510273" + "sha256": "cacfecef859d960da66db27ae4b8fe44a1657a20ef725b9d48bd848f113d708a" }, "pipfile-spec": 6, "requires": { @@ -481,30 +481,31 @@ }, "eth-account": { "hashes": [ - "sha256:0ccc0edbb17021004356ae6e37887528b6e59e6ae6283f3917b9759a5887203b", - "sha256:ccb2d90a16c81c8ea4ca4dc76a70b50f1d63cea6aff3c5a5eddedf9e45143eca" + "sha256:474a2fccf7286230cf66502565f03b536921d7e1fdfceba198e42160e5ac4bc1", + "sha256:b7a83f506a8edf57926569e5f04471ce3f1700e572d3421b4ad0dad7a26c0978" ], "index": "pypi", - "markers": "python_version >= '3.6' and python_version < '4'", - "version": "==0.8.0" + "markers": "python_version >= '3.7' and python_version < '4'", + "version": "==0.10.0" }, "eth-hash": { "extras": [ "pycryptodome" ], "hashes": [ - "sha256:9f8daaa345764f8871dc461855049ac54ae4291d780279bce6fce7f24e3f17d3", - "sha256:ae72889e60db6acbb3872c288cfa02ed157f4c27630fcd7f9c8442302c31e478" + "sha256:b8d5a230a2b251f4a291e3164a23a14057c4a6de4b0aa4a16fa4dc9161b57e2f", + "sha256:bacdc705bfd85dadd055ecd35fd1b4f846b671add101427e089a4ca2e8db310a" ], "markers": "python_version >= '3.8' and python_version < '4'", - "version": "==0.6.0" + "version": "==0.7.0" }, "eth-keyfile": { "hashes": [ - "sha256:471be6e5386fce7b22556b3d4bde5558dbce46d2674f00848027cb0a20abdc8c", - "sha256:609773a1ad5956944a33348413cad366ec6986c53357a806528c8f61c4961560" + "sha256:02e3c2e564c7403b92db3fef8ecae3d21123b15787daecd5b643a57369c530f9", + "sha256:9e09f5bc97c8309876c06bdea7a94f0051c25ba3109b5df37afb815418322efe" ], - "version": "==0.6.1" + "markers": "python_version >= '3.8' and python_version < '4'", + "version": "==0.8.0" }, "eth-keys": { "hashes": [ @@ -516,11 +517,11 @@ }, "eth-rlp": { "hashes": [ - "sha256:e88e949a533def85c69fa94224618bbbd6de00061f4cff645c44621dab11cf33", - "sha256:f3263b548df718855d9a8dbd754473f383c0efc82914b0b849572ce3e06e71a6" + "sha256:d61dbda892ee1220f28fb3663c08f6383c305db9f1f5624dc585c9cd05115027", + "sha256:dd76515d71654277377d48876b88e839d61553aaf56952e580bb7cebef2b1517" ], - "markers": "python_version >= '3.7' and python_version < '4'", - "version": "==0.3.0" + "markers": "python_version >= '3.8' and python_version < '4'", + "version": "==1.0.1" }, "eth-typing": { "hashes": [ @@ -640,11 +641,11 @@ }, "idna": { "hashes": [ - "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca", - "sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f" + "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc", + "sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0" ], "markers": "python_version >= '3.5'", - "version": "==3.6" + "version": "==3.7" }, "incremental": { "hashes": [ @@ -860,20 +861,20 @@ }, "protobuf": { "hashes": [ - "sha256:05e314d425d020d6c4a0908f8640f64feb97451ee0d4ecaaaa24c5d1ed57a669", - "sha256:12f4b099e3d04dd9f27fcd42239797933c87e0ca73eb7881cf759640bd43cc0d", - "sha256:1788b91a86b932098b7fe873961dc14f5a7b064d0625fc9bd0c50e148b8ca5de", - "sha256:3dde0b638dc74f3a0d7df102117d739d206acc7e87872adda2b218eb5c21d86d", - "sha256:4cd6b78c4f2724db89e7c903a1bdf9f7fd8089211ad00d9a4432daae3ee6ddfd", - "sha256:6142a0643e49d6d16cd647045eb15c39f53ea6d41a9fc148e32537bda993685f", - "sha256:7b38a51382b89da2177f0d7c8f9308604c0ad44c6c088fdd7c994b30dd353192", - "sha256:b71463b938b86e25ba9e27e4638a18c2e5348d5b3297a668ede0008c022b1db6", - "sha256:d0e62e113c17d56ab1f11fa7b8f77260c84d31c7c8276650174b727469548647", - "sha256:f8a4a7ff368c28022eedd86e1e2a80315335eaccbc6c82451b179c91f488d9c8", - "sha256:ff577d05ece706f08e1bd3710bdaa677ff0b596f48b97b45721dcc09aeaf042d" + "sha256:38aa5f535721d5bb99861166c445c4105c4e285c765fbb2ac10f116e32dcd46d", + "sha256:3c388ea6ddfe735f8cf69e3f7dc7611e73107b60bdfcf5d0f024c3ccd3794e23", + "sha256:7ee014c2c87582e101d6b54260af03b6596728505c79f17c8586e7523aaa8f8c", + "sha256:8ca2a1d97c290ec7b16e4e5dff2e5ae150cc1582f55b5ab300d45cb0dfa90e51", + "sha256:9b557c317ebe6836835ec4ef74ec3e994ad0894ea424314ad3552bc6e8835b4e", + "sha256:b9ba3ca83c2e31219ffbeb9d76b63aad35a3eb1544170c55336993d7a18ae72c", + "sha256:d693d2504ca96750d92d9de8a103102dd648fda04540495535f0fec7577ed8fc", + "sha256:da612f2720c0183417194eeaa2523215c4fcc1a1949772dc65f05047e08d5932", + "sha256:e6039957449cb918f331d32ffafa8eb9255769c96aa0560d9a5bf0b4e00a2a33", + "sha256:f7417703f841167e5a27d48be13389d52ad705ec09eade63dfc3180a959215d7", + "sha256:fbfe61e7ee8c1860855696e3ac6cfd1b01af5498facc6834fcc345c9684fb2ca" ], "markers": "python_version >= '3.8'", - "version": "==5.26.0rc2" + "version": "==5.26.1" }, "pycryptodome": { "hashes": [ @@ -930,11 +931,11 @@ }, "referencing": { "hashes": [ - "sha256:39240f2ecc770258f28b642dd47fd74bc8b02484de54e1882b74b35ebd779bd5", - "sha256:c775fedf74bc0f9189c2a3be1c12fd03e8c23f4d371dce795df44e06c5b412f7" + "sha256:5773bd84ef41799a5a8ca72dc34590c041eb01bf9aa02632b4a973fb0181a844", + "sha256:d53ae300ceddd3169f1ffa9caf2cb7b769e92657e4fafb23d34b93679116dfd4" ], "markers": "python_version >= '3.8'", - "version": "==0.33.0" + "version": "==0.34.0" }, "regex": { "hashes": [ @@ -1158,11 +1159,11 @@ }, "setuptools": { "hashes": [ - "sha256:02fa291a0471b3a18b2b2481ed902af520c69e8ae0919c13da936542754b4c56", - "sha256:5c0806c7d9af348e6dd3777b4f4dbb42c7ad85b190104837488eab9a7c945cf8" + "sha256:0ff4183f8f42cd8fa3acea16c45205521a4ef28f73c6391d8a25e92893134f2e", + "sha256:c21c49fb1042386df081cb5d86759792ab89efca84cf114889191cd09aacc80c" ], "markers": "python_version >= '3.8'", - "version": "==69.1.1" + "version": "==69.2.0" }, "six": { "hashes": [ @@ -1182,29 +1183,29 @@ }, "twisted": { "hashes": [ - "sha256:186266675e991d73f7dc226bd1eb72f89b9329390f4dcf679e8544a586e8eefe", - "sha256:c7466b9d1b65325f41324349e27800305bdbd205f070ef836ce52f2702f151bb" + "sha256:039f2e6a49ab5108abd94de187fa92377abe5985c7a72d68d0ad266ba19eae63", + "sha256:6b38b6ece7296b5e122c9eb17da2eeab3d98a198f50ca9efd00fb03e5b4fd4ae" ], "index": "pypi", "markers": "python_full_version >= '3.8.0'", - "version": "==24.2.0rc1" + "version": "==24.3.0" }, "typing-extensions": { "hashes": [ - "sha256:ada05f19b82a2ea6eeac4e7412a2328e70b5237f05f3ffef49cae6db558a914e", - "sha256:aee036f82b858bd4b43160c5dc2deb1780129223957eab47c3cac26b93b50e65" + "sha256:83f085bd5ca59c80295fc2a82ab5dac679cbe02b9f33f7d83af68e241bea51b0", + "sha256:c1f94d72897edaf4ce775bb7558d5b79d8126906a14ea5ed1635921406c0387a" ], "markers": "python_version >= '3.8'", - "version": "==4.10.0rc1" + "version": "==4.11.0" }, "urllib3": { "hashes": [ - "sha256:34b97092d7e0a3a8cf7cd10e386f401b3737364026c45e622aa02903dffe0f07", - "sha256:f8ecc1bba5667413457c529ab955bf8c67b45db799d159066261719e328580a0" + "sha256:051d961ad0c62a94e50ecf1af379c3aba230c66c710493493560c0c223c49f20", + "sha256:ce3711610ddce217e6d113a2732fafad960a03fd0318c91faa79481e35c11224" ], "index": "pypi", - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'", - "version": "==1.26.18" + "markers": "python_version >= '3.8'", + "version": "==2.2.0" }, "web3": { "hashes": [ @@ -1683,32 +1684,32 @@ }, "black": { "hashes": [ - "sha256:057c3dc602eaa6fdc451069bd027a1b2635028b575a6c3acfd63193ced20d9c8", - "sha256:08654d0797e65f2423f850fc8e16a0ce50925f9337fb4a4a176a7aa4026e63f8", - "sha256:163baf4ef40e6897a2a9b83890e59141cc8c2a98f2dda5080dc15c00ee1e62cd", - "sha256:1e08fb9a15c914b81dd734ddd7fb10513016e5ce7e6704bdd5e1251ceee51ac9", - "sha256:4dd76e9468d5536abd40ffbc7a247f83b2324f0c050556d9c371c2b9a9a95e31", - "sha256:4f9de21bafcba9683853f6c96c2d515e364aee631b178eaa5145fc1c61a3cc92", - "sha256:61a0391772490ddfb8a693c067df1ef5227257e72b0e4108482b8d41b5aee13f", - "sha256:6981eae48b3b33399c8757036c7f5d48a535b962a7c2310d19361edeef64ce29", - "sha256:7e53a8c630f71db01b28cd9602a1ada68c937cbf2c333e6ed041390d6968faf4", - "sha256:810d445ae6069ce64030c78ff6127cd9cd178a9ac3361435708b907d8a04c693", - "sha256:93601c2deb321b4bad8f95df408e3fb3943d85012dddb6121336b8e24a0d1218", - "sha256:992e451b04667116680cb88f63449267c13e1ad134f30087dec8527242e9862a", - "sha256:9db528bccb9e8e20c08e716b3b09c6bdd64da0dd129b11e160bf082d4642ac23", - "sha256:a0057f800de6acc4407fe75bb147b0c2b5cbb7c3ed110d3e5999cd01184d53b0", - "sha256:ba15742a13de85e9b8f3239c8f807723991fbfae24bad92d34a2b12e81904982", - "sha256:bce4f25c27c3435e4dace4815bcb2008b87e167e3bf4ee47ccdc5ce906eb4894", - "sha256:ca610d29415ee1a30a3f30fab7a8f4144e9d34c89a235d81292a1edb2b55f540", - "sha256:d533d5e3259720fdbc1b37444491b024003e012c5173f7d06825a77508085430", - "sha256:d84f29eb3ee44859052073b7636533ec995bd0f64e2fb43aeceefc70090e752b", - "sha256:e37c99f89929af50ffaf912454b3e3b47fd64109659026b678c091a4cd450fb2", - "sha256:e8a6ae970537e67830776488bca52000eaa37fa63b9988e8c487458d9cd5ace6", - "sha256:faf2ee02e6612577ba0181f4347bcbcf591eb122f7841ae5ba233d12c39dcb4d" + "sha256:2818cf72dfd5d289e48f37ccfa08b460bf469e67fb7c4abb07edc2e9f16fb63f", + "sha256:41622020d7120e01d377f74249e677039d20e6344ff5851de8a10f11f513bf93", + "sha256:4acf672def7eb1725f41f38bf6bf425c8237248bb0804faa3965c036f7672d11", + "sha256:4be5bb28e090456adfc1255e03967fb67ca846a03be7aadf6249096100ee32d0", + "sha256:4f1373a7808a8f135b774039f61d59e4be7eb56b2513d3d2f02a8b9365b8a8a9", + "sha256:56f52cfbd3dabe2798d76dbdd299faa046a901041faf2cf33288bc4e6dae57b5", + "sha256:65b76c275e4c1c5ce6e9870911384bff5ca31ab63d19c76811cb1fb162678213", + "sha256:65c02e4ea2ae09d16314d30912a58ada9a5c4fdfedf9512d23326128ac08ac3d", + "sha256:6905238a754ceb7788a73f02b45637d820b2f5478b20fec82ea865e4f5d4d9f7", + "sha256:79dcf34b33e38ed1b17434693763301d7ccbd1c5860674a8f871bd15139e7837", + "sha256:7bb041dca0d784697af4646d3b62ba4a6b028276ae878e53f6b4f74ddd6db99f", + "sha256:7d5e026f8da0322b5662fa7a8e752b3fa2dac1c1cbc213c3d7ff9bdd0ab12395", + "sha256:9f50ea1132e2189d8dff0115ab75b65590a3e97de1e143795adb4ce317934995", + "sha256:a0c9c4a0771afc6919578cec71ce82a3e31e054904e7197deacbc9382671c41f", + "sha256:aadf7a02d947936ee418777e0247ea114f78aff0d0959461057cae8a04f20597", + "sha256:b5991d523eee14756f3c8d5df5231550ae8993e2286b8014e2fdea7156ed0959", + "sha256:bf21b7b230718a5f08bd32d5e4f1db7fc8788345c8aea1d155fc17852b3410f5", + "sha256:c45f8dff244b3c431b36e3224b6be4a127c6aca780853574c00faf99258041eb", + "sha256:c7ed6668cbbfcd231fa0dc1b137d3e40c04c7f786e626b405c62bcd5db5857e4", + "sha256:d7de8d330763c66663661a1ffd432274a2f92f07feeddd89ffd085b5744f85e7", + "sha256:e19cb1c6365fd6dc38a6eae2dcb691d7d83935c10215aef8e6c38edee3f77abd", + "sha256:e2af80566f43c85f5797365077fb64a393861a3730bd110971ab7a0c94e873e7" ], "index": "pypi", "markers": "python_version >= '3.8'", - "version": "==24.2.0" + "version": "==24.3.0" }, "cached-property": { "hashes": [ @@ -1895,49 +1896,42 @@ "markers": "python_version >= '3.7'", "version": "==8.1.7" }, - "commonmark": { - "hashes": [ - "sha256:452f9dc859be7f06631ddcb328b6919c67984aca654e5fefb3914d54691aed60", - "sha256:da2f38c92590f83de410ba1a3cbceafbc74fee9def35f9251ba9a971d6d66fd9" - ], - "version": "==0.9.1" - }, "cryptography": { "hashes": [ - "sha256:01911714117642a3f1792c7f376db572aadadbafcd8d75bb527166009c9f1d1b", - "sha256:0e89f7b84f421c56e7ff69f11c441ebda73b8a8e6488d322ef71746224c20fce", - "sha256:12d341bd42cdb7d4937b0cabbdf2a94f949413ac4504904d0cdbdce4a22cbf88", - "sha256:15a1fb843c48b4a604663fa30af60818cd28f895572386e5f9b8a665874c26e7", - "sha256:1cdcdbd117681c88d717437ada72bdd5be9de117f96e3f4d50dab3f59fd9ab20", - "sha256:1df6fcbf60560d2113b5ed90f072dc0b108d64750d4cbd46a21ec882c7aefce9", - "sha256:3c6048f217533d89f2f8f4f0fe3044bf0b2090453b7b73d0b77db47b80af8dff", - "sha256:3e970a2119507d0b104f0a8e281521ad28fc26f2820687b3436b8c9a5fcf20d1", - "sha256:44a64043f743485925d3bcac548d05df0f9bb445c5fcca6681889c7c3ab12764", - "sha256:4e36685cb634af55e0677d435d425043967ac2f3790ec652b2b88ad03b85c27b", - "sha256:5f8907fcf57392cd917892ae83708761c6ff3c37a8e835d7246ff0ad251d9298", - "sha256:69b22ab6506a3fe483d67d1ed878e1602bdd5912a134e6202c1ec672233241c1", - "sha256:6bfadd884e7280df24d26f2186e4e07556a05d37393b0f220a840b083dc6a824", - "sha256:6d0fbe73728c44ca3a241eff9aefe6496ab2656d6e7a4ea2459865f2e8613257", - "sha256:6ffb03d419edcab93b4b19c22ee80c007fb2d708429cecebf1dd3258956a563a", - "sha256:810bcf151caefc03e51a3d61e53335cd5c7316c0a105cc695f0959f2c638b129", - "sha256:831a4b37accef30cccd34fcb916a5d7b5be3cbbe27268a02832c3e450aea39cb", - "sha256:887623fe0d70f48ab3f5e4dbf234986b1329a64c066d719432d0698522749929", - "sha256:a0298bdc6e98ca21382afe914c642620370ce0470a01e1bef6dd9b5354c36854", - "sha256:a1327f280c824ff7885bdeef8578f74690e9079267c1c8bd7dc5cc5aa065ae52", - "sha256:c1f25b252d2c87088abc8bbc4f1ecbf7c919e05508a7e8628e6875c40bc70923", - "sha256:c3a5cbc620e1e17009f30dd34cb0d85c987afd21c41a74352d1719be33380885", - "sha256:ce8613beaffc7c14f091497346ef117c1798c202b01153a8cc7b8e2ebaaf41c0", - "sha256:d2a27aca5597c8a71abbe10209184e1a8e91c1fd470b5070a2ea60cafec35bcd", - "sha256:dad9c385ba8ee025bb0d856714f71d7840020fe176ae0229de618f14dae7a6e2", - "sha256:db4b65b02f59035037fde0998974d84244a64c3265bdef32a827ab9b63d61b18", - "sha256:e09469a2cec88fb7b078e16d4adec594414397e8879a4341c6ace96013463d5b", - "sha256:e53dc41cda40b248ebc40b83b31516487f7db95ab8ceac1f042626bc43a2f992", - "sha256:f1e85a178384bf19e36779d91ff35c7617c885da487d689b05c1366f9933ad74", - "sha256:f47be41843200f7faec0683ad751e5ef11b9a56a220d57f300376cd8aba81660", - "sha256:fb0cef872d8193e487fc6bdb08559c3aa41b659a7d9be48b2e10747f47863925", - "sha256:ffc73996c4fca3d2b6c1c8c12bfd3ad00def8621da24f547626bf06441400449" - ], - "version": "==42.0.4" + "sha256:0270572b8bd2c833c3981724b8ee9747b3ec96f699a9665470018594301439ee", + "sha256:111a0d8553afcf8eb02a4fea6ca4f59d48ddb34497aa8706a6cf536f1a5ec576", + "sha256:16a48c23a62a2f4a285699dba2e4ff2d1cff3115b9df052cdd976a18856d8e3d", + "sha256:1b95b98b0d2af784078fa69f637135e3c317091b615cd0905f8b8a087e86fa30", + "sha256:1f71c10d1e88467126f0efd484bd44bca5e14c664ec2ede64c32f20875c0d413", + "sha256:2424ff4c4ac7f6b8177b53c17ed5d8fa74ae5955656867f5a8affaca36a27abb", + "sha256:2bce03af1ce5a5567ab89bd90d11e7bbdff56b8af3acbbec1faded8f44cb06da", + "sha256:329906dcc7b20ff3cad13c069a78124ed8247adcac44b10bea1130e36caae0b4", + "sha256:37dd623507659e08be98eec89323469e8c7b4c1407c85112634ae3dbdb926fdd", + "sha256:3eaafe47ec0d0ffcc9349e1708be2aaea4c6dd4978d76bf6eb0cb2c13636c6fc", + "sha256:5e6275c09d2badf57aea3afa80d975444f4be8d3bc58f7f80d2a484c6f9485c8", + "sha256:6fe07eec95dfd477eb9530aef5bead34fec819b3aaf6c5bd6d20565da607bfe1", + "sha256:7367d7b2eca6513681127ebad53b2582911d1736dc2ffc19f2c3ae49997496bc", + "sha256:7cde5f38e614f55e28d831754e8a3bacf9ace5d1566235e39d91b35502d6936e", + "sha256:9481ffe3cf013b71b2428b905c4f7a9a4f76ec03065b05ff499bb5682a8d9ad8", + "sha256:98d8dc6d012b82287f2c3d26ce1d2dd130ec200c8679b6213b3c73c08b2b7940", + "sha256:a011a644f6d7d03736214d38832e030d8268bcff4a41f728e6030325fea3e400", + "sha256:a2913c5375154b6ef2e91c10b5720ea6e21007412f6437504ffea2109b5a33d7", + "sha256:a30596bae9403a342c978fb47d9b0ee277699fa53bbafad14706af51fe543d16", + "sha256:b03c2ae5d2f0fc05f9a2c0c997e1bc18c8229f392234e8a0194f202169ccd278", + "sha256:b6cd2203306b63e41acdf39aa93b86fb566049aeb6dc489b70e34bcd07adca74", + "sha256:b7ffe927ee6531c78f81aa17e684e2ff617daeba7f189f911065b2ea2d526dec", + "sha256:b8cac287fafc4ad485b8a9b67d0ee80c66bf3574f655d3b97ef2e1082360faf1", + "sha256:ba334e6e4b1d92442b75ddacc615c5476d4ad55cc29b15d590cc6b86efa487e2", + "sha256:ba3e4a42397c25b7ff88cdec6e2a16c2be18720f317506ee25210f6d31925f9c", + "sha256:c41fb5e6a5fe9ebcd58ca3abfeb51dffb5d83d6775405305bfa8715b76521922", + "sha256:cd2030f6650c089aeb304cf093f3244d34745ce0cfcc39f20c6fbfe030102e2a", + "sha256:cd65d75953847815962c84a4654a84850b2bb4aed3f26fadcc1c13892e1e29f6", + "sha256:e4985a790f921508f36f81831817cbc03b102d643b5fcb81cd33df3fa291a1a1", + "sha256:e807b3188f9eb0eaa7bbb579b462c5ace579f1cedb28107ce8b48a9f7ad3679e", + "sha256:f12764b8fffc7a123f641d7d049d382b73f96a34117e0b637b80643169cec8ac", + "sha256:f8837fe1d6ac4a8052a9a8ddab256bc006242696f03368a4009be7ee3075cdb7" + ], + "version": "==42.0.5" }, "cytoolz": { "hashes": [ @@ -2082,11 +2076,11 @@ }, "eip712": { "hashes": [ - "sha256:576476dd1d276e444a633ac22ab25209e18f8f41e5016e576a132d190043a4ba", - "sha256:6d2e07a83c66fb1cbe2448bb4dfea1c91913c4822b7d9b89231e5b61473ae426" + "sha256:698c52422cf852f3e7e0a93cd98d1b17494d49abdc8a787bf9c384d1d92e7f54", + "sha256:f5cabb4baea7a2edd0162045ab136850f5e7932f0ef888169a7a167b11d08235" ], "markers": "python_version >= '3.8' and python_version < '4'", - "version": "==0.2.2" + "version": "==0.2.5" }, "eth-abi": { "hashes": [ @@ -2099,21 +2093,21 @@ }, "eth-account": { "hashes": [ - "sha256:0ccc0edbb17021004356ae6e37887528b6e59e6ae6283f3917b9759a5887203b", - "sha256:ccb2d90a16c81c8ea4ca4dc76a70b50f1d63cea6aff3c5a5eddedf9e45143eca" + "sha256:474a2fccf7286230cf66502565f03b536921d7e1fdfceba198e42160e5ac4bc1", + "sha256:b7a83f506a8edf57926569e5f04471ce3f1700e572d3421b4ad0dad7a26c0978" ], "index": "pypi", - "markers": "python_version >= '3.6' and python_version < '4'", - "version": "==0.8.0" + "markers": "python_version >= '3.7' and python_version < '4'", + "version": "==0.10.0" }, "eth-ape": { "hashes": [ - "sha256:8975d5726d4d070955573e9604e2de12af833b61318e7210424b41e0b401b24f", - "sha256:fb3eb387a2e073f3ec2ebf3b86ae764e2cdfb8af1d316a82c010884c55b84dde" + "sha256:0869ace9f9fe039c779d83bdf8842982015fa306d521df89439a1eee75d0b9d5", + "sha256:200e793200317394f0db1d00f531bdad14d200b6e325f2afe2a13149434af740" ], "index": "pypi", "markers": "python_version >= '3.8' and python_version < '4'", - "version": "==0.6.27" + "version": "==0.7.13" }, "eth-bloom": { "hashes": [ @@ -2128,18 +2122,19 @@ "pycryptodome" ], "hashes": [ - "sha256:9f8daaa345764f8871dc461855049ac54ae4291d780279bce6fce7f24e3f17d3", - "sha256:ae72889e60db6acbb3872c288cfa02ed157f4c27630fcd7f9c8442302c31e478" + "sha256:b8d5a230a2b251f4a291e3164a23a14057c4a6de4b0aa4a16fa4dc9161b57e2f", + "sha256:bacdc705bfd85dadd055ecd35fd1b4f846b671add101427e089a4ca2e8db310a" ], "markers": "python_version >= '3.8' and python_version < '4'", - "version": "==0.6.0" + "version": "==0.7.0" }, "eth-keyfile": { "hashes": [ - "sha256:471be6e5386fce7b22556b3d4bde5558dbce46d2674f00848027cb0a20abdc8c", - "sha256:609773a1ad5956944a33348413cad366ec6986c53357a806528c8f61c4961560" + "sha256:02e3c2e564c7403b92db3fef8ecae3d21123b15787daecd5b643a57369c530f9", + "sha256:9e09f5bc97c8309876c06bdea7a94f0051c25ba3109b5df37afb815418322efe" ], - "version": "==0.6.1" + "markers": "python_version >= '3.8' and python_version < '4'", + "version": "==0.8.0" }, "eth-keys": { "hashes": [ @@ -2151,19 +2146,19 @@ }, "eth-pydantic-types": { "hashes": [ - "sha256:3a2f37a19a13e4660071c9d3f7931aa139d36af94b0a4aaf340d44c44ae9e577", - "sha256:4392ab016b4d3a5831517b17082a122268b4425f45ea2faf25d275a7cf79b927" + "sha256:0ddcdb4fb2c51d54e7adc29e6826e7c7229d99f680906986f70beaf9b4cd912d", + "sha256:d95951791931c94fbd869b7fdf50d45cd228520510dba83d6edc04cc04b8dafe" ], "markers": "python_version >= '3.8' and python_version < '4'", - "version": "==0.1.0a5" + "version": "==0.1.0" }, "eth-rlp": { "hashes": [ - "sha256:e88e949a533def85c69fa94224618bbbd6de00061f4cff645c44621dab11cf33", - "sha256:f3263b548df718855d9a8dbd754473f383c0efc82914b0b849572ce3e06e71a6" + "sha256:d61dbda892ee1220f28fb3663c08f6383c305db9f1f5624dc585c9cd05115027", + "sha256:dd76515d71654277377d48876b88e839d61553aaf56952e580bb7cebef2b1517" ], - "markers": "python_version >= '3.7' and python_version < '4'", - "version": "==0.3.0" + "markers": "python_version >= '3.8' and python_version < '4'", + "version": "==1.0.1" }, "eth-tester": { "extras": [ @@ -2195,19 +2190,27 @@ }, "ethpm-types": { "hashes": [ - "sha256:2a3b39cbf46bc8248f1c98bb56584c65496b63d1200a30f8d5daa74c4e4c7fd8", - "sha256:df6b0f06ab6ced834ec1c2858c30817095846739747313b41a681b0eb20b9251" + "sha256:41f898942848d901a38ecb7fcc09e2cf0969ef75d80947b660a2294c44e4dd99", + "sha256:ad235084dae18378350b97117c8a1f76973f51ee4a7c082e2ffc84bb2b85617b" ], "markers": "python_version >= '3.8' and python_version < '4'", - "version": "==0.5.11" + "version": "==0.6.9" }, "evm-trace": { "hashes": [ - "sha256:ddc73cf38eac187bee7c3d55992f9d74986a82350d8d78da105c4e3ec8130649", - "sha256:ed0cac773005dd4913feb61aca71d6eee059c1b4e3b150c45d32fffeace217ec" + "sha256:a05da6fb7bc6ad3e5d18527ca0b9cabe929359d7cea07812d5f059b3855b3a4b", + "sha256:b1472ed57baae076ae29567180f3292f6031beb3e220edafc9ab465484b3b602" ], "markers": "python_version >= '3.8' and python_version < '4'", - "version": "==0.1.2" + "version": "==0.1.3" + }, + "evmchains": { + "hashes": [ + "sha256:2ac9f8991c2a0fb9adfc6baccdce420a787345da2a2da2b5393cd20015f07362", + "sha256:f6932d2789a3fde1cfd1121933bed111948a7897658ad9cd7ecf54bc01475644" + ], + "markers": "python_version >= '3.8'", + "version": "==0.0.5" }, "executing": { "hashes": [ @@ -2219,11 +2222,11 @@ }, "filelock": { "hashes": [ - "sha256:521f5f56c50f8426f5e03ad3b281b490a87ef15bc6c526f168290f0c7148d44e", - "sha256:57dbda9b35157b05fb3e58ee91448612eb674172fab98ee235ccb0b5bee19a1c" + "sha256:404e5e9253aa60ad457cae1be07c0f0ca90a63931200a47d9b6a6af84fd7b45f", + "sha256:d13f466618bfde72bd2c18255e269f72542c6e70e7bac83a0232d6b1cc5c8cf4" ], "markers": "python_version >= '3.8'", - "version": "==3.13.1" + "version": "==3.13.4" }, "frozenlist": { "hashes": [ @@ -2390,11 +2393,11 @@ }, "idna": { "hashes": [ - "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca", - "sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f" + "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc", + "sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0" ], "markers": "python_version >= '3.5'", - "version": "==3.6" + "version": "==3.7" }, "ijson": { "hashes": [ @@ -2492,11 +2495,11 @@ }, "importlib-metadata": { "hashes": [ - "sha256:4805911c3a4ec7c3966410053e9ec6a1fecd629117df5adee56dfc9432a1081e", - "sha256:f238736bb06590ae52ac1fab06a3a9ef1d8dce2b7a35b5ab329371d6c8f5d2cc" + "sha256:30962b96c0c223483ed6cc7280e7f0199feb01a0e40cfae4d4450fc6fab1f570", + "sha256:b78938b926ee8d5f020fc4772d487045805a55ddbad2ecf21c6d60938dc7fcd2" ], "markers": "python_version >= '3.8'", - "version": "==7.0.1" + "version": "==7.1.0" }, "iniconfig": { "hashes": [ @@ -2508,11 +2511,11 @@ }, "ipython": { "hashes": [ - "sha256:39c6f9efc079fb19bfb0f17eee903978fe9a290b1b82d68196c641cecb76ea22", - "sha256:869335e8cded62ffb6fac8928e5287a05433d6462e3ebaac25f4216474dd6bc4" + "sha256:07232af52a5ba146dc3372c7bf52a0f890a23edf38d77caef8d53f9cdc2584c1", + "sha256:7468edaf4f6de3e1b912e57f66c241e6fd3c7099f2ec2136e239e142e800274d" ], "markers": "python_version >= '3.10'", - "version": "==8.22.1" + "version": "==8.23.0" }, "jedi": { "hashes": [ @@ -2631,6 +2634,14 @@ ], "version": "==1.2.0" }, + "markdown-it-py": { + "hashes": [ + "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1", + "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb" + ], + "markers": "python_version >= '3.8'", + "version": "==3.0.0" + }, "matplotlib-inline": { "hashes": [ "sha256:f1f41aab5328aa5aaea9b16d083b128102f8712542f819fe7e6a420ff581b311", @@ -2639,6 +2650,14 @@ "markers": "python_version >= '3.5'", "version": "==0.1.6" }, + "mdurl": { + "hashes": [ + "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", + "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba" + ], + "markers": "python_version >= '3.7'", + "version": "==0.1.2" + }, "morphys": { "hashes": [ "sha256:76d6dbaa4d65f597e59d332c81da786d83e4669387b9b2a750cfec74e7beec20" @@ -2801,45 +2820,54 @@ }, "numpy": { "hashes": [ - "sha256:03a8c78d01d9781b28a6989f6fa1bb2c4f2d51201cf99d3dd875df6fbd96b23b", - "sha256:08beddf13648eb95f8d867350f6a018a4be2e5ad54c8d8caed89ebca558b2818", - "sha256:1af303d6b2210eb850fcf03064d364652b7120803a0b872f5211f5234b399f20", - "sha256:1dda2e7b4ec9dd512f84935c5f126c8bd8b9f2fc001e9f54af255e8c5f16b0e0", - "sha256:2a02aba9ed12e4ac4eb3ea9421c420301a0c6460d9830d74a9df87efa4912010", - "sha256:2e4ee3380d6de9c9ec04745830fd9e2eccb3e6cf790d39d7b98ffd19b0dd754a", - "sha256:3373d5d70a5fe74a2c1bb6d2cfd9609ecf686d47a2d7b1d37a8f3b6bf6003aea", - "sha256:47711010ad8555514b434df65f7d7b076bb8261df1ca9bb78f53d3b2db02e95c", - "sha256:4c66707fabe114439db9068ee468c26bbdf909cac0fb58686a42a24de1760c71", - "sha256:50193e430acfc1346175fcbdaa28ffec49947a06918b7b92130744e81e640110", - "sha256:52b8b60467cd7dd1e9ed082188b4e6bb35aa5cdd01777621a1658910745b90be", - "sha256:60dedbb91afcbfdc9bc0b1f3f402804070deed7392c23eb7a7f07fa857868e8a", - "sha256:62b8e4b1e28009ef2846b4c7852046736bab361f7aeadeb6a5b89ebec3c7055a", - "sha256:666dbfb6ec68962c033a450943ded891bed2d54e6755e35e5835d63f4f6931d5", - "sha256:675d61ffbfa78604709862923189bad94014bef562cc35cf61d3a07bba02a7ed", - "sha256:679b0076f67ecc0138fd2ede3a8fd196dddc2ad3254069bcb9faf9a79b1cebcd", - "sha256:7349ab0fa0c429c82442a27a9673fc802ffdb7c7775fad780226cb234965e53c", - "sha256:7ab55401287bfec946ced39700c053796e7cc0e3acbef09993a9ad2adba6ca6e", - "sha256:7e50d0a0cc3189f9cb0aeb3a6a6af18c16f59f004b866cd2be1c14b36134a4a0", - "sha256:95a7476c59002f2f6c590b9b7b998306fba6a5aa646b1e22ddfeaf8f78c3a29c", - "sha256:96ff0b2ad353d8f990b63294c8986f1ec3cb19d749234014f4e7eb0112ceba5a", - "sha256:9fad7dcb1aac3c7f0584a5a8133e3a43eeb2fe127f47e3632d43d677c66c102b", - "sha256:9ff0f4f29c51e2803569d7a51c2304de5554655a60c5d776e35b4a41413830d0", - "sha256:a354325ee03388678242a4d7ebcd08b5c727033fcff3b2f536aea978e15ee9e6", - "sha256:a4abb4f9001ad2858e7ac189089c42178fcce737e4169dc61321660f1a96c7d2", - "sha256:ab47dbe5cc8210f55aa58e4805fe224dac469cde56b9f731a4c098b91917159a", - "sha256:afedb719a9dcfc7eaf2287b839d8198e06dcd4cb5d276a3df279231138e83d30", - "sha256:b3ce300f3644fb06443ee2222c2201dd3a89ea6040541412b8fa189341847218", - "sha256:b97fe8060236edf3662adfc2c633f56a08ae30560c56310562cb4f95500022d5", - "sha256:bfe25acf8b437eb2a8b2d49d443800a5f18508cd811fea3181723922a8a82b07", - "sha256:cd25bcecc4974d09257ffcd1f098ee778f7834c3ad767fe5db785be9a4aa9cb2", - "sha256:d209d8969599b27ad20994c8e41936ee0964e6da07478d6c35016bc386b66ad4", - "sha256:d5241e0a80d808d70546c697135da2c613f30e28251ff8307eb72ba696945764", - "sha256:edd8b5fe47dab091176d21bb6de568acdd906d1887a4584a15a9a96a1dca06ef", - "sha256:f870204a840a60da0b12273ef34f7051e98c3b5961b61b0c2c1be6dfd64fbcd3", - "sha256:ffa75af20b44f8dba823498024771d5ac50620e6915abac414251bd971b4529f" + "sha256:00236e0e8a588fef8f70e0535b898bcebd97becc0b27686d2fc7cb35b5d1ab91", + "sha256:015df68fd97bc00e1b7719e80cea401b23a601b639c6d6545922f7a21876b771", + "sha256:060635ab843ea0e2aa6ad153d5656193014eedd90ec4ef6e2b738d81bfe28170", + "sha256:070a8b1c93b0bf21c1a3c51514145acbba612e9f3fd86870c1ca37a36cebbfce", + "sha256:08d7d73d5b7d97decfb6584f41492f5584f81a3147514b67ac21ccccb3418b35", + "sha256:09bedcb99b9ac5472d2e63cd18be861750acc7570ae3661be7cb6018ce376694", + "sha256:09e7a6cab5eac8aca0f17ad29b42ee1cd357e09a76076d5f4cb90ca62a0229b8", + "sha256:1860507cb082ee8d9920db806d74d8a3936081b9ecf274b0fdb6d99b664680a1", + "sha256:1e2478ca8b4b0c5a7146fc316c83843bc47b2d73cf6c02000561794ae5dba537", + "sha256:25d43c681fefb4d7e0ffa949097b20eacbad4be9af7c136b1f69dc4c34c1f6d4", + "sha256:2b5f87d88212e54263f64257b28daa04f3fde627c204abd7557a80b582de4a63", + "sha256:39a65e8c127d51419942a9e0ec467273536acd373507ce64e63451690ed47bfc", + "sha256:5be315e916e7d4d372acf62dcc86900eb47b2f76c185d835634dd0503f441e35", + "sha256:5c62c0d071681391b9c73ba09b35cb46477659012fd88af2c877a2a9da84aa2f", + "sha256:5e289dafe89a0dd756430fa03332c428c897c41cc3143230c38d7d2bb9ad475e", + "sha256:67f9707c3df26ca5bce34162fe0721646504c5961ccfca94c294fbeaf42cfa5b", + "sha256:684eef178a2039cba72bce740cdf2f592e67a41885a0f09d5622380fc59af0f8", + "sha256:6e0438e248b5e7e46e80a686868d36d6a4ce875cedce87122d1616ffd8e2a669", + "sha256:706f66648712385f5ca5e22ad4f32d1a1a93c143882969d951122b5cf9e40a24", + "sha256:7511694264a1219458a4e77d185a7ee350506b4e1e3b2b82845a5e9db044b6f5", + "sha256:7517f752cad3d8bf297ed6421c63be769a03b8e3c34282eec803bae693dae67a", + "sha256:7d990411f2821bf2812ec66ae85e8f351103fe7c3a229152ab6f8c9a620e82eb", + "sha256:8798ee3db69d2f531b12897929583021206feb4d45234d035e5511a5bd0cee38", + "sha256:8a7c01e9c14216e386e42a0c75c76a015a002dd5ed833ffbdaa6a7f2aeed9258", + "sha256:8b510bab996ad7b7fa59ca14fdaae4c68a36ff0f71ccd9ddec769b58f9d19258", + "sha256:9085f9a3e4f994ee8027db503627ae34aa867dc5f00ee7fe2b930608534a9293", + "sha256:91103edc14b5b70bc25af26ea5d75a45b6490bed5f1da9478f5bbe82542ba1b5", + "sha256:9d96878db0d4f267e62e21f6feb7d0e7f07ec02784e705f37b7f6493a935c7fd", + "sha256:9da7cddeaf312a3645325a7da3b18bfad345cae5005cb4d6fcf24796bedaf239", + "sha256:afa4679bcbade6a4197c27874c0dacf5d45470d56cee8b1e2398e80859ab797c", + "sha256:b1bfbde0e9221920d02735ced823e53be46786589a5e8db91824bccd5115e5c8", + "sha256:c0af260d6818eab709b65953e1e5ce31a34d68230f488589b4bb96b13a28d18f", + "sha256:cdea89bba67157bd8ec2ba9613d9f5ba2d18deab113171ca106953fdf8f7f314", + "sha256:cf1b08d8ee6d24576c0552dee71f36859de157481ed283e839d630b50242bbe1", + "sha256:cfd4e2f1605e3a607674dd3173c03b2e2f8520fa3ec2db04f2da2a3d5339df1b", + "sha256:d4b56e9abe2c3cec5615725320e002396c1e4b78011831a78427c7ff7b185816", + "sha256:d93d29d07b2da78869793ec30321adda61a5a48b9e00d12160d0cd658f5f2e0b", + "sha256:dfcd76a018c728ce7a3e6e09717e7a3dfbffdf87a57118dbc5ddc2167a678258", + "sha256:f0e169ec6cbc1b8e5f6a235845a80961f76f88352082213a1728a0967a761ad2", + "sha256:f36b7ccac6a3bfb342a61dd08be73fbe0286d2cb64c976bb1ed22feda0deb16f", + "sha256:f6539759d26e9b60dd9691732528dda7fe46a8c82be6294d109203dce4a8b89c", + "sha256:f9e566457284cb55447eab7566fad2b59e17f01776bb1b76828a6a931d111c72", + "sha256:fb009d69b3a362240acc5155e3de8f90311eb7f9f3958803af866945b8c9ee43", + "sha256:fbee730ae5265735e2c9b006a0d3fe1443d08d9399d0103245b99ecba10ddff0", + "sha256:fe19044006aeaf783c64f22ee03330caccb4d3e54fe605b57444f448954b022d" ], "markers": "python_version >= '3.10'", - "version": "==1.26.4" + "version": "==2.0.0rc1" }, "packaging": { "hashes": [ @@ -2890,11 +2918,11 @@ }, "parso": { "hashes": [ - "sha256:8c07be290bb59f03588915921e29e8a50002acaf2cdc5fa0e0114f91709fafa0", - "sha256:c001d4636cd3aecdaf33cbb40aebb59b094be2a74c556778ef5576c175e19e75" + "sha256:a418670a20291dacd2dddc80c377c5c3791378ee1e8d12bffc35420643d43f18", + "sha256:eb3a7b58240fb99099a345571deecc0f9540ea5f4dd2fe14c2a99d6b281ab92d" ], "markers": "python_version >= '3.6'", - "version": "==0.8.3" + "version": "==0.8.4" }, "pathspec": { "hashes": [ @@ -2930,12 +2958,12 @@ }, "pre-commit": { "hashes": [ - "sha256:ba637c2d7a670c10daedc059f5c49b5bd0aadbccfcd7ec15592cf9665117532c", - "sha256:c3ef34f463045c88658c5b99f38c1e297abdcc0ff13f98d3370055fbbfabc67e" + "sha256:5eae9e10c2b5ac51577c3452ec0a490455c45a0533f7960f993a0d01e59decab", + "sha256:e209d61b8acdcf742404408531f0c37d49d2c734fd7cff2d6076083d191cb060" ], "index": "pypi", "markers": "python_version >= '3.9'", - "version": "==3.6.2" + "version": "==3.7.0" }, "prompt-toolkit": { "hashes": [ @@ -2947,20 +2975,20 @@ }, "protobuf": { "hashes": [ - "sha256:05e314d425d020d6c4a0908f8640f64feb97451ee0d4ecaaaa24c5d1ed57a669", - "sha256:12f4b099e3d04dd9f27fcd42239797933c87e0ca73eb7881cf759640bd43cc0d", - "sha256:1788b91a86b932098b7fe873961dc14f5a7b064d0625fc9bd0c50e148b8ca5de", - "sha256:3dde0b638dc74f3a0d7df102117d739d206acc7e87872adda2b218eb5c21d86d", - "sha256:4cd6b78c4f2724db89e7c903a1bdf9f7fd8089211ad00d9a4432daae3ee6ddfd", - "sha256:6142a0643e49d6d16cd647045eb15c39f53ea6d41a9fc148e32537bda993685f", - "sha256:7b38a51382b89da2177f0d7c8f9308604c0ad44c6c088fdd7c994b30dd353192", - "sha256:b71463b938b86e25ba9e27e4638a18c2e5348d5b3297a668ede0008c022b1db6", - "sha256:d0e62e113c17d56ab1f11fa7b8f77260c84d31c7c8276650174b727469548647", - "sha256:f8a4a7ff368c28022eedd86e1e2a80315335eaccbc6c82451b179c91f488d9c8", - "sha256:ff577d05ece706f08e1bd3710bdaa677ff0b596f48b97b45721dcc09aeaf042d" + "sha256:38aa5f535721d5bb99861166c445c4105c4e285c765fbb2ac10f116e32dcd46d", + "sha256:3c388ea6ddfe735f8cf69e3f7dc7611e73107b60bdfcf5d0f024c3ccd3794e23", + "sha256:7ee014c2c87582e101d6b54260af03b6596728505c79f17c8586e7523aaa8f8c", + "sha256:8ca2a1d97c290ec7b16e4e5dff2e5ae150cc1582f55b5ab300d45cb0dfa90e51", + "sha256:9b557c317ebe6836835ec4ef74ec3e994ad0894ea424314ad3552bc6e8835b4e", + "sha256:b9ba3ca83c2e31219ffbeb9d76b63aad35a3eb1544170c55336993d7a18ae72c", + "sha256:d693d2504ca96750d92d9de8a103102dd648fda04540495535f0fec7577ed8fc", + "sha256:da612f2720c0183417194eeaa2523215c4fcc1a1949772dc65f05047e08d5932", + "sha256:e6039957449cb918f331d32ffafa8eb9255769c96aa0560d9a5bf0b4e00a2a33", + "sha256:f7417703f841167e5a27d48be13389d52ad705ec09eade63dfc3180a959215d7", + "sha256:fbfe61e7ee8c1860855696e3ac6cfd1b01af5498facc6834fcc345c9684fb2ca" ], "markers": "python_version >= '3.8'", - "version": "==5.26.0rc2" + "version": "==5.26.1" }, "ptyprocess": { "hashes": [ @@ -3000,11 +3028,11 @@ }, "py-geth": { "hashes": [ - "sha256:43e061d89977593ca976776086e40f60f7d6118925a11285c99f6eb4d1d921d3", - "sha256:cb071185538ab35156557f53b8a11e44bde8a81c5788fa6ae966d1592f60935e" + "sha256:ae8771b028c68f6710e6434d2aa1310ce2ba3cc9099d244d9bb5a9e340786a92", + "sha256:c08d84f6dad4f86a9b8ffd74c0a0f160d600db0ee45dfc2a66d5e13522aeb039" ], - "markers": "python_version >= '3.7' and python_version < '4'", - "version": "==3.14.0" + "markers": "python_version >= '3.8' and python_version < '4'", + "version": "==4.4.0" }, "py-multibase": { "hashes": [ @@ -3029,10 +3057,11 @@ }, "pycparser": { "hashes": [ - "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9", - "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206" + "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6", + "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc" ], - "version": "==2.21" + "markers": "python_version >= '3.8'", + "version": "==2.22" }, "pycryptodome": { "hashes": [ @@ -3073,96 +3102,130 @@ }, "pydantic": { "hashes": [ - "sha256:0b6a909df3192245cb736509a92ff69e4fef76116feffec68e93a567347bae6f", - "sha256:4fd5c182a2488dc63e6d32737ff19937888001e2a6d86e94b3f233104a5d1fa9" + "sha256:b3ef57c62535b0941697cce638c08900d87fcb67e29cfa99e8a68f747f393f7a", + "sha256:d0caf5954bee831b6bfe7e338c32b9e30c85dfe080c843680783ac2b631673b4" ], - "markers": "python_version >= '3.8'", - "version": "==2.6.1" + "markers": "python_version >= '3.7'", + "version": "==2.5.3" }, "pydantic-core": { "hashes": [ - "sha256:02906e7306cb8c5901a1feb61f9ab5e5c690dbbeaa04d84c1b9ae2a01ebe9379", - "sha256:0ba503850d8b8dcc18391f10de896ae51d37fe5fe43dbfb6a35c5c5cad271a06", - "sha256:16aa02e7a0f539098e215fc193c8926c897175d64c7926d00a36188917717a05", - "sha256:18de31781cdc7e7b28678df7c2d7882f9692ad060bc6ee3c94eb15a5d733f8f7", - "sha256:22c5f022799f3cd6741e24f0443ead92ef42be93ffda0d29b2597208c94c3753", - "sha256:2924b89b16420712e9bb8192396026a8fbd6d8726224f918353ac19c4c043d2a", - "sha256:308974fdf98046db28440eb3377abba274808bf66262e042c412eb2adf852731", - "sha256:396fdf88b1b503c9c59c84a08b6833ec0c3b5ad1a83230252a9e17b7dfb4cffc", - "sha256:3ac426704840877a285d03a445e162eb258924f014e2f074e209d9b4ff7bf380", - "sha256:3b052c753c4babf2d1edc034c97851f867c87d6f3ea63a12e2700f159f5c41c3", - "sha256:3fab4e75b8c525a4776e7630b9ee48aea50107fea6ca9f593c98da3f4d11bf7c", - "sha256:406fac1d09edc613020ce9cf3f2ccf1a1b2f57ab00552b4c18e3d5276c67eb11", - "sha256:40a0bd0bed96dae5712dab2aba7d334a6c67cbcac2ddfca7dbcc4a8176445990", - "sha256:41dac3b9fce187a25c6253ec79a3f9e2a7e761eb08690e90415069ea4a68ff7a", - "sha256:459c0d338cc55d099798618f714b21b7ece17eb1a87879f2da20a3ff4c7628e2", - "sha256:459d6be6134ce3b38e0ef76f8a672924460c455d45f1ad8fdade36796df1ddc8", - "sha256:46b0d5520dbcafea9a8645a8164658777686c5c524d381d983317d29687cce97", - "sha256:47924039e785a04d4a4fa49455e51b4eb3422d6eaacfde9fc9abf8fdef164e8a", - "sha256:4bfcbde6e06c56b30668a0c872d75a7ef3025dc3c1823a13cf29a0e9b33f67e8", - "sha256:4f9ee4febb249c591d07b2d4dd36ebcad0ccd128962aaa1801508320896575ef", - "sha256:55749f745ebf154c0d63d46c8c58594d8894b161928aa41adbb0709c1fe78b77", - "sha256:5864b0242f74b9dd0b78fd39db1768bc3f00d1ffc14e596fd3e3f2ce43436a33", - "sha256:5f60f920691a620b03082692c378661947d09415743e437a7478c309eb0e4f82", - "sha256:60eb8ceaa40a41540b9acae6ae7c1f0a67d233c40dc4359c256ad2ad85bdf5e5", - "sha256:69a7b96b59322a81c2203be537957313b07dd333105b73db0b69212c7d867b4b", - "sha256:6ad84731a26bcfb299f9eab56c7932d46f9cad51c52768cace09e92a19e4cf55", - "sha256:6db58c22ac6c81aeac33912fb1af0e930bc9774166cdd56eade913d5f2fff35e", - "sha256:70651ff6e663428cea902dac297066d5c6e5423fda345a4ca62430575364d62b", - "sha256:72f7919af5de5ecfaf1eba47bf9a5d8aa089a3340277276e5636d16ee97614d7", - "sha256:732bd062c9e5d9582a30e8751461c1917dd1ccbdd6cafb032f02c86b20d2e7ec", - "sha256:7924e54f7ce5d253d6160090ddc6df25ed2feea25bfb3339b424a9dd591688bc", - "sha256:7afb844041e707ac9ad9acad2188a90bffce2c770e6dc2318be0c9916aef1469", - "sha256:7b883af50eaa6bb3299780651e5be921e88050ccf00e3e583b1e92020333304b", - "sha256:7beec26729d496a12fd23cf8da9944ee338c8b8a17035a560b585c36fe81af20", - "sha256:7bf26c2e2ea59d32807081ad51968133af3025c4ba5753e6a794683d2c91bf6e", - "sha256:7c31669e0c8cc68400ef0c730c3a1e11317ba76b892deeefaf52dcb41d56ed5d", - "sha256:7e6231aa5bdacda78e96ad7b07d0c312f34ba35d717115f4b4bff6cb87224f0f", - "sha256:870dbfa94de9b8866b37b867a2cb37a60c401d9deb4a9ea392abf11a1f98037b", - "sha256:88646cae28eb1dd5cd1e09605680c2b043b64d7481cdad7f5003ebef401a3039", - "sha256:8aafeedb6597a163a9c9727d8a8bd363a93277701b7bfd2749fbefee2396469e", - "sha256:8bde5b48c65b8e807409e6f20baee5d2cd880e0fad00b1a811ebc43e39a00ab2", - "sha256:8f9142a6ed83d90c94a3efd7af8873bf7cefed2d3d44387bf848888482e2d25f", - "sha256:936a787f83db1f2115ee829dd615c4f684ee48ac4de5779ab4300994d8af325b", - "sha256:98dc6f4f2095fc7ad277782a7c2c88296badcad92316b5a6e530930b1d475ebc", - "sha256:9957433c3a1b67bdd4c63717eaf174ebb749510d5ea612cd4e83f2d9142f3fc8", - "sha256:99af961d72ac731aae2a1b55ccbdae0733d816f8bfb97b41909e143de735f522", - "sha256:9b5f13857da99325dcabe1cc4e9e6a3d7b2e2c726248ba5dd4be3e8e4a0b6d0e", - "sha256:9d776d30cde7e541b8180103c3f294ef7c1862fd45d81738d156d00551005784", - "sha256:9da90d393a8227d717c19f5397688a38635afec89f2e2d7af0df037f3249c39a", - "sha256:a3b7352b48fbc8b446b75f3069124e87f599d25afb8baa96a550256c031bb890", - "sha256:a477932664d9611d7a0816cc3c0eb1f8856f8a42435488280dfbf4395e141485", - "sha256:a7e41e3ada4cca5f22b478c08e973c930e5e6c7ba3588fb8e35f2398cdcc1545", - "sha256:a90fec23b4b05a09ad988e7a4f4e081711a90eb2a55b9c984d8b74597599180f", - "sha256:a9e523474998fb33f7c1a4d55f5504c908d57add624599e095c20fa575b8d943", - "sha256:aa057095f621dad24a1e906747179a69780ef45cc8f69e97463692adbcdae878", - "sha256:aa6c8c582036275997a733427b88031a32ffa5dfc3124dc25a730658c47a572f", - "sha256:ae34418b6b389d601b31153b84dce480351a352e0bb763684a1b993d6be30f17", - "sha256:b0d7a9165167269758145756db43a133608a531b1e5bb6a626b9ee24bc38a8f7", - "sha256:b30b0dd58a4509c3bd7eefddf6338565c4905406aee0c6e4a5293841411a1286", - "sha256:b8f9186ca45aee030dc8234118b9c0784ad91a0bb27fc4e7d9d6608a5e3d386c", - "sha256:b94cbda27267423411c928208e89adddf2ea5dd5f74b9528513f0358bba019cb", - "sha256:cc6f6c9be0ab6da37bc77c2dda5f14b1d532d5dbef00311ee6e13357a418e646", - "sha256:ce232a6170dd6532096cadbf6185271e4e8c70fc9217ebe105923ac105da9978", - "sha256:cf903310a34e14651c9de056fcc12ce090560864d5a2bb0174b971685684e1d8", - "sha256:d5362d099c244a2d2f9659fb3c9db7c735f0004765bbe06b99be69fbd87c3f15", - "sha256:dffaf740fe2e147fedcb6b561353a16243e654f7fe8e701b1b9db148242e1272", - "sha256:e0f686549e32ccdb02ae6f25eee40cc33900910085de6aa3790effd391ae10c2", - "sha256:e4b52776a2e3230f4854907a1e0946eec04d41b1fc64069ee774876bbe0eab55", - "sha256:e4ba0884a91f1aecce75202473ab138724aa4fb26d7707f2e1fa6c3e68c84fbf", - "sha256:e6294e76b0380bb7a61eb8a39273c40b20beb35e8c87ee101062834ced19c545", - "sha256:ebb892ed8599b23fa8f1799e13a12c87a97a6c9d0f497525ce9858564c4575a4", - "sha256:eca58e319f4fd6df004762419612122b2c7e7d95ffafc37e890252f869f3fb2a", - "sha256:ed957db4c33bc99895f3a1672eca7e80e8cda8bd1e29a80536b4ec2153fa9804", - "sha256:ef551c053692b1e39e3f7950ce2296536728871110e7d75c4e7753fb30ca87f4", - "sha256:ef6113cd31411eaf9b39fc5a8848e71c72656fd418882488598758b2c8c6dfa0", - "sha256:f685dbc1fdadb1dcd5b5e51e0a378d4685a891b2ddaf8e2bba89bd3a7144e44a", - "sha256:f8ed79883b4328b7f0bd142733d99c8e6b22703e908ec63d930b06be3a0e7113", - "sha256:fe56851c3f1d6f5384b3051c536cc81b3a93a73faf931f404fef95217cf1e10d", - "sha256:ff7c97eb7a29aba230389a2661edf2e9e06ce616c7e35aa764879b6894a44b25" + "sha256:00646784f6cd993b1e1c0e7b0fdcbccc375d539db95555477771c27555e3c556", + "sha256:00b1087dabcee0b0ffd104f9f53d7d3eaddfaa314cdd6726143af6bc713aa27e", + "sha256:0348b1dc6b76041516e8a854ff95b21c55f5a411c3297d2ca52f5528e49d8411", + "sha256:036137b5ad0cb0004c75b579445a1efccd072387a36c7f217bb8efd1afbe5245", + "sha256:095b707bb287bfd534044166ab767bec70a9bba3175dcdc3371782175c14e43c", + "sha256:0c08de15d50fa190d577e8591f0329a643eeaed696d7771760295998aca6bc66", + "sha256:1302a54f87b5cd8528e4d6d1bf2133b6aa7c6122ff8e9dc5220fbc1e07bffebd", + "sha256:172de779e2a153d36ee690dbc49c6db568d7b33b18dc56b69a7514aecbcf380d", + "sha256:1b027c86c66b8627eb90e57aee1f526df77dc6d8b354ec498be9a757d513b92b", + "sha256:1ce830e480f6774608dedfd4a90c42aac4a7af0a711f1b52f807130c2e434c06", + "sha256:1fd0c1d395372843fba13a51c28e3bb9d59bd7aebfeb17358ffaaa1e4dbbe948", + "sha256:23598acb8ccaa3d1d875ef3b35cb6376535095e9405d91a3d57a8c7db5d29341", + "sha256:24368e31be2c88bd69340fbfe741b405302993242ccb476c5c3ff48aeee1afe0", + "sha256:26a92ae76f75d1915806b77cf459811e772d8f71fd1e4339c99750f0e7f6324f", + "sha256:27e524624eace5c59af499cd97dc18bb201dc6a7a2da24bfc66ef151c69a5f2a", + "sha256:2b8719037e570639e6b665a4050add43134d80b687288ba3ade18b22bbb29dd2", + "sha256:2c5bcf3414367e29f83fd66f7de64509a8fd2368b1edf4351e862910727d3e51", + "sha256:2dbe357bc4ddda078f79d2a36fc1dd0494a7f2fad83a0a684465b6f24b46fe80", + "sha256:2f5fa187bde8524b1e37ba894db13aadd64faa884657473b03a019f625cee9a8", + "sha256:2f6ffc6701a0eb28648c845f4945a194dc7ab3c651f535b81793251e1185ac3d", + "sha256:314ccc4264ce7d854941231cf71b592e30d8d368a71e50197c905874feacc8a8", + "sha256:36026d8f99c58d7044413e1b819a67ca0e0b8ebe0f25e775e6c3d1fabb3c38fb", + "sha256:36099c69f6b14fc2c49d7996cbf4f87ec4f0e66d1c74aa05228583225a07b590", + "sha256:36fa402dcdc8ea7f1b0ddcf0df4254cc6b2e08f8cd80e7010d4c4ae6e86b2a87", + "sha256:370ffecb5316ed23b667d99ce4debe53ea664b99cc37bfa2af47bc769056d534", + "sha256:3860c62057acd95cc84044e758e47b18dcd8871a328ebc8ccdefd18b0d26a21b", + "sha256:399ac0891c284fa8eb998bcfa323f2234858f5d2efca3950ae58c8f88830f145", + "sha256:3a0b5db001b98e1c649dd55afa928e75aa4087e587b9524a4992316fa23c9fba", + "sha256:3dcf1978be02153c6a31692d4fbcc2a3f1db9da36039ead23173bc256ee3b91b", + "sha256:4241204e4b36ab5ae466ecec5c4c16527a054c69f99bba20f6f75232a6a534e2", + "sha256:438027a975cc213a47c5d70672e0d29776082155cfae540c4e225716586be75e", + "sha256:43e166ad47ba900f2542a80d83f9fc65fe99eb63ceec4debec160ae729824052", + "sha256:478e9e7b360dfec451daafe286998d4a1eeaecf6d69c427b834ae771cad4b622", + "sha256:4ce8299b481bcb68e5c82002b96e411796b844d72b3e92a3fbedfe8e19813eab", + "sha256:4f86f1f318e56f5cbb282fe61eb84767aee743ebe32c7c0834690ebea50c0a6b", + "sha256:55a23dcd98c858c0db44fc5c04fc7ed81c4b4d33c653a7c45ddaebf6563a2f66", + "sha256:599c87d79cab2a6a2a9df4aefe0455e61e7d2aeede2f8577c1b7c0aec643ee8e", + "sha256:5aa90562bc079c6c290f0512b21768967f9968e4cfea84ea4ff5af5d917016e4", + "sha256:64634ccf9d671c6be242a664a33c4acf12882670b09b3f163cd00a24cffbd74e", + "sha256:667aa2eac9cd0700af1ddb38b7b1ef246d8cf94c85637cbb03d7757ca4c3fdec", + "sha256:6a31d98c0d69776c2576dda4b77b8e0c69ad08e8b539c25c7d0ca0dc19a50d6c", + "sha256:6af4b3f52cc65f8a0bc8b1cd9676f8c21ef3e9132f21fed250f6958bd7223bed", + "sha256:6c8edaea3089bf908dd27da8f5d9e395c5b4dc092dbcce9b65e7156099b4b937", + "sha256:71d72ca5eaaa8d38c8df16b7deb1a2da4f650c41b58bb142f3fb75d5ad4a611f", + "sha256:72f9a942d739f09cd42fffe5dc759928217649f070056f03c70df14f5770acf9", + "sha256:747265448cb57a9f37572a488a57d873fd96bf51e5bb7edb52cfb37124516da4", + "sha256:75ec284328b60a4e91010c1acade0c30584f28a1f345bc8f72fe8b9e46ec6a96", + "sha256:78d0768ee59baa3de0f4adac9e3748b4b1fffc52143caebddfd5ea2961595277", + "sha256:78ee52ecc088c61cce32b2d30a826f929e1708f7b9247dc3b921aec367dc1b23", + "sha256:7be719e4d2ae6c314f72844ba9d69e38dff342bc360379f7c8537c48e23034b7", + "sha256:7e1f4744eea1501404b20b0ac059ff7e3f96a97d3e3f48ce27a139e053bb370b", + "sha256:7e90d6cc4aad2cc1f5e16ed56e46cebf4877c62403a311af20459c15da76fd91", + "sha256:7ebe3416785f65c28f4f9441e916bfc8a54179c8dea73c23023f7086fa601c5d", + "sha256:7f41533d7e3cf9520065f610b41ac1c76bc2161415955fbcead4981b22c7611e", + "sha256:7f5025db12fc6de7bc1104d826d5aee1d172f9ba6ca936bf6474c2148ac336c1", + "sha256:86c963186ca5e50d5c8287b1d1c9d3f8f024cbe343d048c5bd282aec2d8641f2", + "sha256:86ce5fcfc3accf3a07a729779d0b86c5d0309a4764c897d86c11089be61da160", + "sha256:8a14c192c1d724c3acbfb3f10a958c55a2638391319ce8078cb36c02283959b9", + "sha256:8b93785eadaef932e4fe9c6e12ba67beb1b3f1e5495631419c784ab87e975670", + "sha256:8ed1af8692bd8d2a29d702f1a2e6065416d76897d726e45a1775b1444f5928a7", + "sha256:92879bce89f91f4b2416eba4429c7b5ca22c45ef4a499c39f0c5c69257522c7c", + "sha256:94fc0e6621e07d1e91c44e016cc0b189b48db053061cc22d6298a611de8071bb", + "sha256:982487f8931067a32e72d40ab6b47b1628a9c5d344be7f1a4e668fb462d2da42", + "sha256:9862bf828112e19685b76ca499b379338fd4c5c269d897e218b2ae8fcb80139d", + "sha256:99b14dbea2fdb563d8b5a57c9badfcd72083f6006caf8e126b491519c7d64ca8", + "sha256:9c6a5c79b28003543db3ba67d1df336f253a87d3112dac3a51b94f7d48e4c0e1", + "sha256:a19b794f8fe6569472ff77602437ec4430f9b2b9ec7a1105cfd2232f9ba355e6", + "sha256:a306cdd2ad3a7d795d8e617a58c3a2ed0f76c8496fb7621b6cd514eb1532cae8", + "sha256:a3dde6cac75e0b0902778978d3b1646ca9f438654395a362cb21d9ad34b24acf", + "sha256:a874f21f87c485310944b2b2734cd6d318765bcbb7515eead33af9641816506e", + "sha256:a983cca5ed1dd9a35e9e42ebf9f278d344603bfcb174ff99a5815f953925140a", + "sha256:aca48506a9c20f68ee61c87f2008f81f8ee99f8d7f0104bff3c47e2d148f89d9", + "sha256:b2602177668f89b38b9f84b7b3435d0a72511ddef45dc14446811759b82235a1", + "sha256:b3e5fe4538001bb82e2295b8d2a39356a84694c97cb73a566dc36328b9f83b40", + "sha256:b6ca36c12a5120bad343eef193cc0122928c5c7466121da7c20f41160ba00ba2", + "sha256:b89f4477d915ea43b4ceea6756f63f0288941b6443a2b28c69004fe07fde0d0d", + "sha256:b9a9d92f10772d2a181b5ca339dee066ab7d1c9a34ae2421b2a52556e719756f", + "sha256:c99462ffc538717b3e60151dfaf91125f637e801f5ab008f81c402f1dff0cd0f", + "sha256:cb92f9061657287eded380d7dc455bbf115430b3aa4741bdc662d02977e7d0af", + "sha256:cdee837710ef6b56ebd20245b83799fce40b265b3b406e51e8ccc5b85b9099b7", + "sha256:cf10b7d58ae4a1f07fccbf4a0a956d705356fea05fb4c70608bb6fa81d103cda", + "sha256:d15687d7d7f40333bd8266f3814c591c2e2cd263fa2116e314f60d82086e353a", + "sha256:d5c28525c19f5bb1e09511669bb57353d22b94cf8b65f3a8d141c389a55dec95", + "sha256:d5f916acf8afbcab6bacbb376ba7dc61f845367901ecd5e328fc4d4aef2fcab0", + "sha256:dab03ed811ed1c71d700ed08bde8431cf429bbe59e423394f0f4055f1ca0ea60", + "sha256:db453f2da3f59a348f514cfbfeb042393b68720787bbef2b4c6068ea362c8149", + "sha256:de2a0645a923ba57c5527497daf8ec5df69c6eadf869e9cd46e86349146e5975", + "sha256:dea7fcd62915fb150cdc373212141a30037e11b761fbced340e9db3379b892d4", + "sha256:dfcbebdb3c4b6f739a91769aea5ed615023f3c88cb70df812849aef634c25fbe", + "sha256:dfcebb950aa7e667ec226a442722134539e77c575f6cfaa423f24371bb8d2e94", + "sha256:e0641b506486f0b4cd1500a2a65740243e8670a2549bb02bc4556a83af84ae03", + "sha256:e33b0834f1cf779aa839975f9d8755a7c2420510c0fa1e9fa0497de77cd35d2c", + "sha256:e4ace1e220b078c8e48e82c081e35002038657e4b37d403ce940fa679e57113b", + "sha256:e4cf2d5829f6963a5483ec01578ee76d329eb5caf330ecd05b3edd697e7d768a", + "sha256:e574de99d735b3fc8364cba9912c2bec2da78775eba95cbb225ef7dda6acea24", + "sha256:e646c0e282e960345314f42f2cea5e0b5f56938c093541ea6dbf11aec2862391", + "sha256:e8a5ac97ea521d7bde7621d86c30e86b798cdecd985723c4ed737a2aa9e77d0c", + "sha256:eedf97be7bc3dbc8addcef4142f4b4164066df0c6f36397ae4aaed3eb187d8ab", + "sha256:ef633add81832f4b56d3b4c9408b43d530dfca29e68fb1b797dcb861a2c734cd", + "sha256:f27207e8ca3e5e021e2402ba942e5b4c629718e665c81b8b306f3c8b1ddbb786", + "sha256:f85f3843bdb1fe80e8c206fe6eed7a1caeae897e496542cee499c374a85c6e08", + "sha256:f8e81e4b55930e5ffab4a68db1af431629cf2e4066dbdbfef65348b8ab804ea8", + "sha256:f96ae96a060a8072ceff4cfde89d261837b4294a4f28b84a28765470d502ccc6", + "sha256:fd9e98b408384989ea4ab60206b8e100d8687da18b5c813c11e92fd8212a98e0", + "sha256:ffff855100bc066ff2cd3aa4a60bc9534661816b110f0243e59503ec2df38421" + ], + "markers": "python_version >= '3.7'", + "version": "==2.14.6" + }, + "pydantic-settings": { + "hashes": [ + "sha256:00b9f6a5e95553590434c0fa01ead0b216c3e10bc54ae02e37f359948643c5ed", + "sha256:0235391d26db4d2190cb9b31051c4b46882d28a51533f97440867f012d4da091" ], "markers": "python_version >= '3.8'", - "version": "==2.16.2" + "version": "==2.2.1" }, "pyethash": { "hashes": [ @@ -3224,21 +3287,21 @@ }, "pytest-mock": { "hashes": [ - "sha256:0972719a7263072da3a21c7f4773069bcc7486027d7e8e1f81d98a47e701bc4f", - "sha256:31a40f038c22cad32287bb43932054451ff5583ff094bca6f675df2f8bc1a6e9" + "sha256:0b72c38033392a5f4621342fe11e9219ac11ec9d375f8e2a0c164539e0d70f6f", + "sha256:2719255a1efeceadbc056d6bf3df3d1c5015530fb40cf347c0f9afac88410bd0" ], "index": "pypi", "markers": "python_version >= '3.8'", - "version": "==3.12.0" + "version": "==3.14.0" }, "pytest-twisted": { "hashes": [ - "sha256:1b63b73182bd1b995f30826a1d870c9ac0d08244ab0c871eb8bd0c8243acfb3d", - "sha256:209bf5a6452cfbfb61de8f015902c14ec8126400911507074bb2ee4ce8dfe313" + "sha256:a9b18bc9fca47d2886f8efe3fd27879a81f1c0bb49f1c560666c9e764491b632", + "sha256:b66554bbf03242b9e9d16226a59b4dfe5d09dc45d6e7b5b3545a8ec9fd726777" ], "index": "pypi", "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", - "version": "==1.14.0" + "version": "==1.14.1" }, "python-baseconv": { "hashes": [ @@ -3248,11 +3311,19 @@ }, "python-dateutil": { "hashes": [ - "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86", - "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9" + "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", + "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427" ], "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==2.8.2" + "version": "==2.9.0.post0" + }, + "python-dotenv": { + "hashes": [ + "sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca", + "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a" + ], + "markers": "python_version >= '3.8'", + "version": "==1.0.1" }, "pytz": { "hashes": [ @@ -3327,11 +3398,11 @@ }, "referencing": { "hashes": [ - "sha256:39240f2ecc770258f28b642dd47fd74bc8b02484de54e1882b74b35ebd779bd5", - "sha256:c775fedf74bc0f9189c2a3be1c12fd03e8c23f4d371dce795df44e06c5b412f7" + "sha256:5773bd84ef41799a5a8ca72dc34590c041eb01bf9aa02632b4a973fb0181a844", + "sha256:d53ae300ceddd3169f1ffa9caf2cb7b769e92657e4fafb23d34b93679116dfd4" ], "markers": "python_version >= '3.8'", - "version": "==0.33.0" + "version": "==0.34.0" }, "regex": { "hashes": [ @@ -3442,11 +3513,11 @@ }, "rich": { "hashes": [ - "sha256:a4eb26484f2c82589bd9a17c73d32a010b1e29d89f1604cd9bf3a2097b81bb5e", - "sha256:ba3a3775974105c221d31141f2c116f4fd65c5ceb0698657a11e9f295ec93fd0" + "sha256:4edbae314f59eb482f54e9e30bf00d33350aaa94f4bfcd4e9e3110e64d0d7222", + "sha256:9be308cb1fe2f1f57d67ce99e95af38a1e2bc71ad9813b0e247cf7ffbcc3a432" ], - "markers": "python_full_version >= '3.6.3' and python_full_version < '4.0.0'", - "version": "==12.6.0" + "markers": "python_full_version >= '3.7.0'", + "version": "==13.7.1" }, "rlp": { "hashes": [ @@ -3563,27 +3634,27 @@ }, "ruff": { "hashes": [ - "sha256:0a9efb032855ffb3c21f6405751d5e147b0c6b631e3ca3f6b20f917572b97eb6", - "sha256:0c126da55c38dd917621552ab430213bdb3273bb10ddb67bc4b761989210eb6e", - "sha256:1695700d1e25a99d28f7a1636d85bafcc5030bba9d0578c0781ba1790dbcf51c", - "sha256:1ec49be4fe6ddac0503833f3ed8930528e26d1e60ad35c2446da372d16651ce9", - "sha256:3b65494f7e4bed2e74110dac1f0d17dc8e1f42faaa784e7c58a98e335ec83d7e", - "sha256:5e1439c8f407e4f356470e54cdecdca1bd5439a0673792dbe34a2b0a551a2fe3", - "sha256:5e22676a5b875bd72acd3d11d5fa9075d3a5f53b877fe7b4793e4673499318ba", - "sha256:6a61ea0ff048e06de273b2e45bd72629f470f5da8f71daf09fe481278b175001", - "sha256:940de32dc8853eba0f67f7198b3e79bc6ba95c2edbfdfac2144c8235114d6726", - "sha256:b0c232af3d0bd8f521806223723456ffebf8e323bd1e4e82b0befb20ba18388e", - "sha256:c9d15fc41e6054bfc7200478720570078f0b41c9ae4f010bcc16bd6f4d1aacdd", - "sha256:cc9a91ae137d687f43a44c900e5d95e9617cb37d4c989e462980ba27039d239d", - "sha256:d450b7fbff85913f866a5384d8912710936e2b96da74541c82c1b458472ddb39", - "sha256:d920499b576f6c68295bc04e7b17b6544d9d05f196bb3aac4358792ef6f34325", - "sha256:e62ed7f36b3068a30ba39193a14274cd706bc486fad521276458022f7bccb31d", - "sha256:ecd46e3106850a5c26aee114e562c329f9a1fbe9e4821b008c4404f64ff9ce73", - "sha256:f63d96494eeec2fc70d909393bcd76c69f35334cdbd9e20d089fb3f0640216ca" + "sha256:26071fb530038602b984e3bbe1443ef82a38450c4dcb1344a9caf67234ff9756", + "sha256:28ccf3fb6d1162a73cd286c63a5e4d885f46a1f99f0b392924bc95ccbd18ea8f", + "sha256:2b0c4c70578ef1871a9ac5c85ed7a8c33470e976c73ba9211a111d2771b5f787", + "sha256:4056480f5cf38ad278667c31b0ef334c29acdfcea617cb89c4ccbc7d96f1637f", + "sha256:647f1fb5128a3e24ce68878b8050bb55044c45bb3f3ae4710d4da9ca96ede5cb", + "sha256:732ef99984275534f9466fbc01121523caf72aa8c2bdeb36fd2edf2bc294a992", + "sha256:7c8a2a0e0cab077a07465259ffe3b3c090e747ca8097c5dc4c36ca0fdaaac90d", + "sha256:878ef1a55ce931f3ca23b690b159cd0659f495a4c231a847b00ca55e4c688baf", + "sha256:93699d61116807edc5ca1cdf9d2d22cf8d93335d59e3ff0ca7aee62c1818a736", + "sha256:b11e09439d9df6cc12d9f622065834654417c40216d271f639512d80e80e3e53", + "sha256:b2e79f8e1b6bd5411d7ddad3f2abff3f9d371beda29daef86400d416dedb7e02", + "sha256:c466a52c522e6a08df0af018f550902f154f5649ad09e7f0d43da766e7399ebc", + "sha256:cf48ec2c4bfae7837dc325c431a2932dc23a1485e71c59591c1df471ba234e0e", + "sha256:e3da499ded004d0b956ab04248b2ae17e54a67ffc81353514ac583af5959a255", + "sha256:ecb87788284af96725643eae9ab3ac746d8cc09aad140268523b019f7ac3cd98", + "sha256:f1aa621beed533f46e9c7d6fe00e7f6e4570155b61d8f020387b72ace2b42e04", + "sha256:fc4006cbc6c11fefc25f122d2eb4731d7a3d815dc74d67c54991cc3f99c90177" ], "index": "pypi", "markers": "python_version >= '3.7'", - "version": "==0.2.2" + "version": "==0.3.6" }, "safe-pysha3": { "hashes": [ @@ -3601,11 +3672,11 @@ }, "setuptools": { "hashes": [ - "sha256:02fa291a0471b3a18b2b2481ed902af520c69e8ae0919c13da936542754b4c56", - "sha256:5c0806c7d9af348e6dd3777b4f4dbb42c7ad85b190104837488eab9a7c945cf8" + "sha256:0ff4183f8f42cd8fa3acea16c45205521a4ef28f73c6391d8a25e92893134f2e", + "sha256:c21c49fb1042386df081cb5d86759792ab89efca84cf114889191cd09aacc80c" ], "markers": "python_version >= '3.8'", - "version": "==69.1.1" + "version": "==69.2.0" }, "six": { "hashes": [ @@ -3624,58 +3695,58 @@ }, "sqlalchemy": { "hashes": [ - "sha256:03f448ffb731b48323bda68bcc93152f751436ad6037f18a42b7e16af9e91c07", - "sha256:0de1263aac858f288a80b2071990f02082c51d88335a1db0d589237a3435fe71", - "sha256:0fb3bffc0ced37e5aa4ac2416f56d6d858f46d4da70c09bb731a246e70bff4d5", - "sha256:120af1e49d614d2525ac247f6123841589b029c318b9afbfc9e2b70e22e1827d", - "sha256:1306102f6d9e625cebaca3d4c9c8f10588735ef877f0360b5cdb4fdfd3fd7131", - "sha256:15e19a84b84528f52a68143439d0c7a3a69befcd4f50b8ef9b7b69d2628ae7c4", - "sha256:1ab4e0448018d01b142c916cc7119ca573803a4745cfe341b8f95657812700ac", - "sha256:1fc19ae2e07a067663dd24fca55f8ed06a288384f0e6e3910420bf4b1270cc51", - "sha256:2f5c9dfb0b9ab5e3a8a00249534bdd838d943ec4cfb9abe176a6c33408430230", - "sha256:30d81cc1192dc693d49d5671cd40cdec596b885b0ce3b72f323888ab1c3863d5", - "sha256:33e8bde8fff203de50399b9039c4e14e42d4d227759155c21f8da4a47fc8053c", - "sha256:4535c49d961fe9a77392e3a630a626af5baa967172d42732b7a43496c8b28876", - "sha256:48217be1de7d29a5600b5c513f3f7664b21d32e596d69582be0a94e36b8309cb", - "sha256:5ada0438f5b74c3952d916c199367c29ee4d6858edff18eab783b3978d0db16d", - "sha256:5b78aa9f4f68212248aaf8943d84c0ff0f74efc65a661c2fc68b82d498311fd5", - "sha256:5cd20f58c29bbf2680039ff9f569fa6d21453fbd2fa84dbdb4092f006424c2e6", - "sha256:611068511b5531304137bcd7fe8117c985d1b828eb86043bd944cebb7fae3910", - "sha256:680b9a36029b30cf063698755d277885d4a0eab70a2c7c6e71aab601323cba45", - "sha256:6c5bad7c60a392850d2f0fee8f355953abaec878c483dd7c3836e0089f046bf6", - "sha256:6c7a596d0be71b7baa037f4ac10d5e057d276f65a9a611c46970f012752ebf2d", - "sha256:7f470327d06400a0aa7926b375b8e8c3c31d335e0884f509fe272b3c700a7254", - "sha256:86a6ed69a71fe6b88bf9331594fa390a2adda4a49b5c06f98e47bf0d392534f8", - "sha256:8dfc936870507da96aebb43e664ae3a71a7b96278382bcfe84d277b88e379b18", - "sha256:954d9735ee9c3fa74874c830d089a815b7b48df6f6b6e357a74130e478dbd951", - "sha256:9e56afce6431450442f3ab5973156289bd5ec33dd618941283847c9fd5ff06bf", - "sha256:a3012ab65ea42de1be81fff5fb28d6db893ef978950afc8130ba707179b4284a", - "sha256:ad862295ad3f644e3c2c0d8b10a988e1600d3123ecb48702d2c0f26771f1c396", - "sha256:b1d9d1bfd96eef3c3faedb73f486c89e44e64e40e5bfec304ee163de01cf996f", - "sha256:b86abba762ecfeea359112b2bb4490802b340850bbee1948f785141a5e020de8", - "sha256:b90053be91973a6fb6020a6e44382c97739736a5a9d74e08cc29b196639eb979", - "sha256:c4fbe6a766301f2e8a4519f4500fe74ef0a8509a59e07a4085458f26228cd7cc", - "sha256:ca891af9f3289d24a490a5fde664ea04fe2f4984cd97e26de7442a4251bd4b7c", - "sha256:cb0845e934647232b6ff5150df37ceffd0b67b754b9fdbb095233deebcddbd4a", - "sha256:ce850db091bf7d2a1f2fdb615220b968aeff3849007b1204bf6e3e50a57b3d32", - "sha256:d04e579e911562f1055d26dab1868d3e0bb905db3bccf664ee8ad109f035618a", - "sha256:d07ee7793f2aeb9b80ec8ceb96bc8cc08a2aec8a1b152da1955d64e4825fcbac", - "sha256:d177b7e82f6dd5e1aebd24d9c3297c70ce09cd1d5d37b43e53f39514379c029c", - "sha256:d7b5a3e2120982b8b6bd1d5d99e3025339f7fb8b8267551c679afb39e9c7c7f1", - "sha256:d873c21b356bfaf1589b89090a4011e6532582b3a8ea568a00e0c3aab09399dd", - "sha256:d997c5938a08b5e172c30583ba6b8aad657ed9901fc24caf3a7152eeccb2f1b4", - "sha256:dbcd77c4d94b23e0753c5ed8deba8c69f331d4fd83f68bfc9db58bc8983f49cd", - "sha256:e36aa62b765cf9f43a003233a8c2d7ffdeb55bc62eaa0a0380475b228663a38f", - "sha256:e97cf143d74a7a5a0f143aa34039b4fecf11343eed66538610debc438685db4a", - "sha256:eb15ef40b833f5b2f19eeae65d65e191f039e71790dd565c2af2a3783f72262f", - "sha256:ec1f5a328464daf7a1e4e385e4f5652dd9b1d12405075ccba1df842f7774b4fc", - "sha256:f9374e270e2553653d710ece397df67db9d19c60d2647bcd35bfc616f1622dcd", - "sha256:fa67d821c1fd268a5a87922ef4940442513b4e6c377553506b9db3b83beebbd8", - "sha256:fd8aafda7cdff03b905d4426b714601c0978725a19efc39f5f207b86d188ba01", - "sha256:ff2f1b7c963961d41403b650842dc2039175b906ab2093635d8319bef0b7d620" + "sha256:01d10638a37460616708062a40c7b55f73e4d35eaa146781c683e0fa7f6c43fb", + "sha256:04c487305ab035a9548f573763915189fc0fe0824d9ba28433196f8436f1449c", + "sha256:0dfefdb3e54cd15f5d56fd5ae32f1da2d95d78319c1f6dfb9bcd0eb15d603d5d", + "sha256:0f3ca96af060a5250a8ad5a63699180bc780c2edf8abf96c58af175921df847a", + "sha256:205f5a2b39d7c380cbc3b5dcc8f2762fb5bcb716838e2d26ccbc54330775b003", + "sha256:25664e18bef6dc45015b08f99c63952a53a0a61f61f2e48a9e70cec27e55f699", + "sha256:296195df68326a48385e7a96e877bc19aa210e485fa381c5246bc0234c36c78e", + "sha256:2a0732dffe32333211801b28339d2a0babc1971bc90a983e3035e7b0d6f06b93", + "sha256:3071ad498896907a5ef756206b9dc750f8e57352113c19272bdfdc429c7bd7de", + "sha256:308ef9cb41d099099fffc9d35781638986870b29f744382904bf9c7dadd08513", + "sha256:334184d1ab8f4c87f9652b048af3f7abea1c809dfe526fb0435348a6fef3d380", + "sha256:38b624e5cf02a69b113c8047cf7f66b5dfe4a2ca07ff8b8716da4f1b3ae81567", + "sha256:471fcb39c6adf37f820350c28aac4a7df9d3940c6548b624a642852e727ea586", + "sha256:4c142852ae192e9fe5aad5c350ea6befe9db14370b34047e1f0f7cf99e63c63b", + "sha256:4f6d971255d9ddbd3189e2e79d743ff4845c07f0633adfd1de3f63d930dbe673", + "sha256:52c8011088305476691b8750c60e03b87910a123cfd9ad48576d6414b6ec2a1d", + "sha256:52de4736404e53c5c6a91ef2698c01e52333988ebdc218f14c833237a0804f1b", + "sha256:5c7b02525ede2a164c5fa5014915ba3591730f2cc831f5be9ff3b7fd3e30958e", + "sha256:5ef3fbccb4058355053c51b82fd3501a6e13dd808c8d8cd2561e610c5456013c", + "sha256:5f20cb0a63a3e0ec4e169aa8890e32b949c8145983afa13a708bc4b0a1f30e03", + "sha256:61405ea2d563407d316c63a7b5271ae5d274a2a9fbcd01b0aa5503635699fa1e", + "sha256:77d29cb6c34b14af8a484e831ab530c0f7188f8efed1c6a833a2c674bf3c26ec", + "sha256:7b184e3de58009cc0bf32e20f137f1ec75a32470f5fede06c58f6c355ed42a72", + "sha256:7e614d7a25a43a9f54fcce4675c12761b248547f3d41b195e8010ca7297c369c", + "sha256:8197d6f7a3d2b468861ebb4c9f998b9df9e358d6e1cf9c2a01061cb9b6cf4e41", + "sha256:87a1d53a5382cdbbf4b7619f107cc862c1b0a4feb29000922db72e5a66a5ffc0", + "sha256:8c37f1050feb91f3d6c32f864d8e114ff5545a4a7afe56778d76a9aec62638ba", + "sha256:90453597a753322d6aa770c5935887ab1fc49cc4c4fdd436901308383d698b4b", + "sha256:988569c8732f54ad3234cf9c561364221a9e943b78dc7a4aaf35ccc2265f1930", + "sha256:99a1e69d4e26f71e750e9ad6fdc8614fbddb67cfe2173a3628a2566034e223c7", + "sha256:9b19836ccca0d321e237560e475fd99c3d8655d03da80c845c4da20dda31b6e1", + "sha256:9d6753305936eddc8ed190e006b7bb33a8f50b9854823485eed3a886857ab8d1", + "sha256:a13b917b4ffe5a0a31b83d051d60477819ddf18276852ea68037a144a506efb9", + "sha256:a88913000da9205b13f6f195f0813b6ffd8a0c0c2bd58d499e00a30eb508870c", + "sha256:b2a0e3cf0caac2085ff172c3faacd1e00c376e6884b5bc4dd5b6b84623e29e4f", + "sha256:b5d7ed79df55a731749ce65ec20d666d82b185fa4898430b17cb90c892741520", + "sha256:bab41acf151cd68bc2b466deae5deeb9e8ae9c50ad113444151ad965d5bf685b", + "sha256:bd9566b8e58cabd700bc367b60e90d9349cd16f0984973f98a9a09f9c64e86f0", + "sha256:bda7ce59b06d0f09afe22c56714c65c957b1068dee3d5e74d743edec7daba552", + "sha256:c2f9c762a2735600654c654bf48dad388b888f8ce387b095806480e6e4ff6907", + "sha256:c4520047006b1d3f0d89e0532978c0688219857eb2fee7c48052560ae76aca1e", + "sha256:d96710d834a6fb31e21381c6d7b76ec729bd08c75a25a5184b1089141356171f", + "sha256:dba622396a3170974f81bad49aacebd243455ec3cc70615aeaef9e9613b5bca5", + "sha256:dc4ee2d4ee43251905f88637d5281a8d52e916a021384ec10758826f5cbae305", + "sha256:dddaae9b81c88083e6437de95c41e86823d150f4ee94bf24e158a4526cbead01", + "sha256:de7202ffe4d4a8c1e3cde1c03e01c1a3772c92858837e8f3879b497158e4cb44", + "sha256:e5bbe55e8552019c6463709b39634a5fc55e080d0827e2a3a11e18eb73f5cdbd", + "sha256:ea311d4ee9a8fa67f139c088ae9f905fcf0277d6cd75c310a21a88bf85e130f5", + "sha256:fecd5089c4be1bcc37c35e9aa678938d2888845a134dd016de457b942cf5a758" ], "markers": "python_version >= '3.7'", - "version": "==2.0.27" + "version": "==2.0.29" }, "stack-data": { "hashes": [ @@ -3702,11 +3773,11 @@ }, "traitlets": { "hashes": [ - "sha256:2e5a030e6eff91737c643231bfcf04a65b0132078dad75e4936700b213652e74", - "sha256:8585105b371a04b8316a43d5ce29c098575c2e477850b62b848b964f1444527e" + "sha256:8cdd83c040dab7d1dee822678e5f5d100b514f7b72b01615b26fc5718916fdf9", + "sha256:fcdf85684a772ddeba87db2f398ce00b40ff550d1528c03c14dbf6a02003cd80" ], "markers": "python_version >= '3.8'", - "version": "==5.14.1" + "version": "==5.14.2" }, "trie": { "hashes": [ @@ -3718,20 +3789,20 @@ }, "typing-extensions": { "hashes": [ - "sha256:ada05f19b82a2ea6eeac4e7412a2328e70b5237f05f3ffef49cae6db558a914e", - "sha256:aee036f82b858bd4b43160c5dc2deb1780129223957eab47c3cac26b93b50e65" + "sha256:83f085bd5ca59c80295fc2a82ab5dac679cbe02b9f33f7d83af68e241bea51b0", + "sha256:c1f94d72897edaf4ce775bb7558d5b79d8126906a14ea5ed1635921406c0387a" ], "markers": "python_version >= '3.8'", - "version": "==4.10.0rc1" + "version": "==4.11.0" }, "urllib3": { "hashes": [ - "sha256:34b97092d7e0a3a8cf7cd10e386f401b3737364026c45e622aa02903dffe0f07", - "sha256:f8ecc1bba5667413457c529ab955bf8c67b45db799d159066261719e328580a0" + "sha256:051d961ad0c62a94e50ecf1af379c3aba230c66c710493493560c0c223c49f20", + "sha256:ce3711610ddce217e6d113a2732fafad960a03fd0318c91faa79481e35c11224" ], "index": "pypi", - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'", - "version": "==1.26.18" + "markers": "python_version >= '3.8'", + "version": "==2.2.0" }, "varint": { "hashes": [ @@ -4048,11 +4119,11 @@ }, "zipp": { "hashes": [ - "sha256:0e923e726174922dce09c53c59ad483ff7bbb8e572e00c7f7c46b88556409f31", - "sha256:84e64a1c28cf7e91ed2078bb8cc8c259cb19b76942096c8d7b84947690cabaf0" + "sha256:206f5a15f2af3dbaee80769fb7dc6f249695e940acca08dfb2a4769fe61e538b", + "sha256:2884ed22e7d8961de1c9a05142eb69a247f120291bc0206a00a7642f09b5b715" ], "markers": "python_version >= '3.8'", - "version": "==3.17.0" + "version": "==3.18.1" } } } diff --git a/atxm/exceptions.py b/atxm/exceptions.py index 05dfa17..1e15e36 100644 --- a/atxm/exceptions.py +++ b/atxm/exceptions.py @@ -1,6 +1,6 @@ from enum import Enum -from web3.types import PendingTx, TxReceipt +from web3.types import PendingTx class Fault(Enum): @@ -13,20 +13,21 @@ class Fault(Enum): # Strategy has been running for too long TIMEOUT = "timeout" - # Transaction reverted - REVERT = "revert" - # Something went wrong ERROR = "error" - # ... - INSUFFICIENT_FUNDS = "insufficient_funds" - class InsufficientFunds(Exception): """raised when a transaction exceeds the spending cap""" +class RPCException(Exception): + def __init__(self, error_code: int, error_message: str): + self.error_code = error_code + self.error_message = error_message + super().__init__(f"RPC Error [{error_code}]: {error_message}") + + class TransactionFaulted(Exception): """Raised when a transaction has been faulted.""" @@ -35,11 +36,3 @@ def __init__(self, tx: PendingTx, fault: Fault, message: str): self.fault = fault self.message = message super().__init__(message) - - -class TransactionReverted(TransactionFaulted): - """Raised when a transaction has been reverted.""" - - def __init__(self, tx: PendingTx, receipt: TxReceipt, message: str): - self.receipt = receipt - super().__init__(tx=tx, fault=Fault.REVERT, message=message) diff --git a/atxm/machine.py b/atxm/machine.py index abf3ce7..04feadb 100644 --- a/atxm/machine.py +++ b/atxm/machine.py @@ -1,26 +1,22 @@ from copy import copy, deepcopy -from typing import List, Optional +from typing import List, Optional, Union from eth_account.signers.local import LocalAccount -from eth_utils import ValidationError from statemachine import State, StateMachine from twisted.internet import reactor from twisted.internet.defer import Deferred from twisted.internet.task import LoopingCall from web3 import Web3 -from web3.exceptions import Web3Exception from web3.types import TxParams from atxm.exceptions import ( Fault, InsufficientFunds, TransactionFaulted, - TransactionReverted, ) from atxm.strategies import AsyncTxStrategy, TimeoutStrategy from atxm.tracker import _TxTracker from atxm.tx import ( - AsyncTx, FutureTx, PendingTx, TxHash, @@ -29,6 +25,7 @@ _get_average_blocktime, _get_confirmations, _get_receipt, + _process_send_raw_transaction_exception, _is_recoverable_send_tx_error, _make_tx_params, fire_hook, @@ -92,8 +89,8 @@ class _Machine(StateMachine): _BLOCK_INTERVAL = 20 # ~20 blocks _BLOCK_SAMPLE_SIZE = 10_000 # blocks - # max requeues/retries - _MAX_REDO_ATTEMPTS = 3 + # max retries (broadcast or active) + _MAX_RETRY_ATTEMPTS = 3 class LogObserver: """StateMachine observer for logging information about state/transitions.""" @@ -225,7 +222,7 @@ def _stop(self): def _wake(self) -> None: """Runs the looping call immediately.""" - log.info("[wake] running looping call now.") + self.log.info("[wake] running looping call now.") if self._task.running: # TODO instead of stopping/starting, can you set interval to 0 # and call reset to have looping call immediately? @@ -235,7 +232,7 @@ def _wake(self) -> None: def _sleep(self) -> None: if self._task.running: - log.info("[sleep] sleeping") + self.log.info("[sleep] sleeping") self._stop() # @@ -246,38 +243,31 @@ def __handle_active_transaction(self) -> None: """ Handles the currently tracked pending transaction. - The 4 possible outcomes for the pending ("active") transaction in one cycle: + The 3 possible outcomes for the pending ("active") transaction in one cycle: 1. paused - 2. reverted (fault) - 3. finalized - 4. strategize: retry, do nothing and wait, or fault + 2. finalized (successful or reverted) + 3. still pending: strategize and retry, do nothing and wait, or fault Returns True if the next queued transaction can be broadcasted right now. """ pending_tx = self._tx_tracker.pending - try: - receipt = _get_receipt(w3=self.w3, pending_tx=pending_tx) - - # Outcome 2: the pending transaction was reverted (final error) - except TransactionReverted as e: - self._tx_tracker.fault(fault_error=e) - return + receipt = _get_receipt(w3=self.w3, pending_tx=pending_tx) - # Outcome 3: pending transaction is finalized (final success) + # Outcome 2: pending transaction is finalized (final success) if receipt: final_txhash = receipt["transactionHash"] confirmations = _get_confirmations(w3=self.w3, tx=pending_tx) self.log.info( - f"[finalized] Transaction #atx-{pending_tx.id} has been finalized " - f"with {confirmations} confirmation(s) txhash: {final_txhash.hex()}" + f"[finalized] Transaction #atx-{pending_tx.id} with txhash: {final_txhash.hex()} " + f"and status {receipt['status']} has been finalized with {confirmations} confirmation(s)" ) self._tx_tracker.finalize_active_tx(receipt=receipt) return - # Outcome 4: re-strategize the pending transaction + # Outcome 3: re-strategize the pending transaction self.__strategize() # @@ -291,29 +281,18 @@ def __get_signer(self, address: str) -> LocalAccount: raise ValueError(f"Signer {address} not found") return signer - def __fire(self, tx: AsyncTx, msg: str) -> TxHash: + def __fire(self, tx: Union[FutureTx, PendingTx], msg: str) -> TxHash: """ Signs and broadcasts a transaction, handling RPC errors and internal state changes. - - On success, returns the `PendingTx` object. - On failure, returns None. - - Morphs a `FutureTx` into a `PendingTx` and advances it - into the active transaction slot if broadcast is successful. """ signer: LocalAccount = self.__get_signer(tx.params["from"]) try: txhash = self.w3.eth.send_raw_transaction( signer.sign_transaction(tx.params).rawTransaction ) - except ValidationError as e: - # special case for insufficient funds - if "Sender does not have enough" in str(e): - # TODO raised exception should be handled in some way #13. - raise InsufficientFunds - - raise e + except Exception as e: + raise _process_send_raw_transaction_exception(e) self.log.info( f"[{msg}] fired transaction #atx-{tx.id}|{tx.params['nonce']}|{txhash.hex()}" @@ -341,7 +320,7 @@ def __strategize(self) -> None: if not params_updated: # mandatory default timeout strategy prevents this from being a forever wait - log.info( + self.log.info( f"[wait] strategies made no suggested updates to " f"pending tx #{_active_copy.id} - skipping retry round" ) @@ -352,33 +331,39 @@ def __strategize(self) -> None: try: txhash = self.__fire(tx=_active_copy, msg=_names) - except InsufficientFunds: - # special case re-raise insufficient funds (for now) - # TODO #13 - # TODO should the following also be done? - # self._tx_tracker.update_failed_retry_attempt(_active_copy) - raise - except (ValidationError, Web3Exception, ValueError) as e: - self._tx_tracker.update_failed_retry_attempt(_active_copy) + except InsufficientFunds as e: + # special case + self.log.error( + f"[insufficient funds] transaction #atx-{_active_copy.id}|{_active_copy.params['nonce']} " + f"failed because of insufficient funds - {e}" + ) + # get hook from actual pending object (not a deep copy) + hook = self._tx_tracker.pending.on_insufficient_funds + fire_hook(hook, _active_copy, e) + return + except Exception as e: + self._tx_tracker.update_active_after_failed_strategy_update(_active_copy) self.__handle_retry_failure(_active_copy, e) return _active_copy.txhash = txhash - self._tx_tracker.update_after_retry(_active_copy) + self._tx_tracker.update_active_after_successful_strategy_update(_active_copy) pending_tx = self._tx_tracker.pending - self.log.info(f"[retry] transaction #{pending_tx.id} has been re-broadcasted") + self.log.info( + f"[retry] transaction #{pending_tx.id} has been re-broadcasted with updated params" + ) if pending_tx.on_broadcast: fire_hook(hook=pending_tx.on_broadcast, tx=pending_tx) def __handle_retry_failure(self, attempted_tx: PendingTx, e: Exception): - log.warn( + self.log.warn( f"[retry] transaction #atx-{attempted_tx.id}|{attempted_tx.params['nonce']} " f"failed with updated params - {str(e)}; retry again next round" ) - if attempted_tx.retries >= self._MAX_REDO_ATTEMPTS: - log.error( + if attempted_tx.retries >= self._MAX_RETRY_ATTEMPTS: + self.log.error( f"[retry] transaction #atx-{attempted_tx.id}|{attempted_tx.params['nonce']} " f"failed for { attempted_tx.retries} attempts; tx will no longer be retried" ) @@ -395,7 +380,7 @@ def __broadcast(self): Attempts to broadcast the next `FutureTx` in the queue. If the broadcast is not successful, it is re-queued. """ - future_tx = self._tx_tracker.pop() # popleft + future_tx = self._tx_tracker.head() # don't pop until successful future_tx.params = _make_tx_params(future_tx.params) # update nonce as necessary @@ -411,12 +396,16 @@ def __broadcast(self): try: txhash = self.__fire(tx=future_tx, msg="broadcast") - except InsufficientFunds: - # special case re-raise insufficient funds (for now) - # TODO #13 - raise - except (ValidationError, Web3Exception, ValueError) as e: - # either requeue OR fail and move on to subsequent txs + except InsufficientFunds as e: + # special case + self.log.error( + f"[insufficient funds] transaction #atx-{future_tx.id}|{future_tx.params['nonce']} " + f"failed because of insufficient funds - {e}" + ) + fire_hook(future_tx.on_insufficient_funds, future_tx, e) + return + except Exception as e: + # notify user of failure and have them decide self.__handle_broadcast_failure(future_tx, e) return @@ -428,30 +417,31 @@ def __broadcast(self): def __handle_broadcast_failure(self, future_tx: FutureTx, e: Exception): is_broadcast_failure = False if _is_recoverable_send_tx_error(e): - if future_tx.requeues >= self._MAX_REDO_ATTEMPTS: + if future_tx.retries >= self._MAX_RETRY_ATTEMPTS: is_broadcast_failure = True - log.error( + self.log.error( f"[broadcast] transaction #atx-{future_tx.id}|{future_tx.params['nonce']} " - f"failed for {future_tx.requeues} attempts; tx will not be requeued" + f"failed after {future_tx.retries} retry attempts" ) else: - log.warn( + self.log.warn( f"[broadcast] transaction #atx-{future_tx.id}|{future_tx.params['nonce']} " - f"failed - {str(e)}; requeueing tx" + f"failed - {e}; tx will be retried" ) - self._tx_tracker.requeue(future_tx) + self._tx_tracker.increment_broadcast_retries(future_tx) else: # non-recoverable is_broadcast_failure = True - log.error( + self.log.error( f"[broadcast] transaction #atx-{future_tx.id}|{future_tx.params['nonce']} " - f"has non-recoverable failure - {str(e)}; tx will not be requeued" + f"has non-recoverable failure - {e}" ) if is_broadcast_failure: - hook = future_tx.on_broadcast_failure - if hook: - fire_hook(hook, future_tx, e) + # since reporting failure; reset retry count + self._tx_tracker.reset_broadcast_retries(future_tx) + + fire_hook(future_tx.on_broadcast_failure, future_tx, e) # # Monitoring @@ -460,8 +450,10 @@ def __monitor_finalized(self) -> None: """Follow-up on finalized transactions for a little while.""" if not self._tx_tracker.finalized: return + + current_block = self.w3.eth.block_number for tx in self._tx_tracker.finalized.copy(): - confirmations = _get_confirmations(w3=self.w3, tx=tx) + confirmations = current_block - tx.block_number if confirmations >= self._TRACKING_CONFIRMATIONS: if tx in self._tx_tracker.finalized: self._tx_tracker.finalized.remove(tx) @@ -520,3 +512,12 @@ def queue_transaction( self._wake() return tx + + def remove_queued_transaction(self, tx: FutureTx): + """ + Removes a queued transaction; useful when an existing queued transaction has broadcast + failures, or a queued transaction is no longer necessary. + + Returns true if transaction was present and removed, false otherwise. + """ + return self._tx_tracker.remove_queued_tx(tx) diff --git a/atxm/strategies.py b/atxm/strategies.py index 892ddeb..7151d8f 100644 --- a/atxm/strategies.py +++ b/atxm/strategies.py @@ -1,3 +1,5 @@ +import time + import math from abc import ABC from datetime import datetime, timedelta @@ -134,6 +136,8 @@ class ExponentialSpeedupStrategy(AsyncTxStrategy): _MAX_TIP_FACTOR = 3 # max 3x over suggested tip + _MIN_TIME_BETWEEN_SPEEDUPS = 90 # 90s minimum between speedups + _NAME = "speedup" _GAS_PRICE_FIELD = "gasPrice" @@ -145,6 +149,7 @@ def __init__( w3: Web3, speedup_increase_percentage: float = _SPEEDUP_INCREASE_PERCENTAGE, max_tip_factor: int = _MAX_TIP_FACTOR, + min_time_between_speedups: int = _MIN_TIME_BETWEEN_SPEEDUPS, ): super().__init__(w3) @@ -161,6 +166,7 @@ def __init__( self.speedup_factor = 1 + speedup_increase_percentage self.max_tip_factor = max_tip_factor + self.min_time_between_speedups = min_time_between_speedups def _calculate_eip1559_speedup_fee(self, params: TxParams) -> Tuple[int, int, int]: current_base_fee = self.w3.eth.get_block("latest")["baseFeePerGas"] @@ -211,6 +217,10 @@ def _calculate_legacy_speedup_fee(self, params: TxParams) -> int: return math.ceil(base_price_to_increase * self.speedup_factor) def execute(self, pending: PendingTx) -> Optional[TxParams]: + if pending.last_updated + self.min_time_between_speedups > int(time.time()): + # too soon - don't update + return None + params = pending.params if self._GAS_PRICE_FIELD in pending.params: diff --git a/atxm/tracker.py b/atxm/tracker.py index e5e018a..85710ec 100644 --- a/atxm/tracker.py +++ b/atxm/tracker.py @@ -4,11 +4,11 @@ from copy import copy from json import JSONDecodeError from pathlib import Path -from typing import Callable, Deque, Dict, Optional, Set, Tuple +from typing import Callable, Deque, Dict, Optional, Set, Tuple, Union from web3.types import TxParams, TxReceipt -from atxm.exceptions import TransactionFaulted +from atxm.exceptions import InsufficientFunds, TransactionFaulted from atxm.logging import log from atxm.tx import ( FinalizedTx, @@ -100,7 +100,13 @@ def __set_active(self, tx: PendingTx) -> None: return log.debug(f"[tracker] tracked active transaction {tx.txhash.hex()}") - def update_after_retry(self, tx: PendingTx) -> PendingTx: + def __pop(self) -> FutureTx: + """Pop the next transaction from the queue.""" + return self.__queue.popleft() + + def update_active_after_successful_strategy_update( + self, tx: PendingTx + ) -> PendingTx: if not self.__active: raise RuntimeError("No active transaction to update") if tx.id != self.__active.id: @@ -110,10 +116,12 @@ def update_after_retry(self, tx: PendingTx) -> PendingTx: self.__active.txhash = tx.txhash self.__active.params = tx.params + self.__active.last_updated = int(time.time()) + self.__active.retries = 0 # reset retries to 0 return self.pending - def update_failed_retry_attempt(self, tx: PendingTx): + def update_active_after_failed_strategy_update(self, tx: PendingTx): if not self.__active: raise RuntimeError("No active transaction to update") if tx.id != self.__active.id: @@ -130,12 +138,22 @@ def morph(self, tx: FutureTx, txhash: TxHash) -> PendingTx: Morphs a future transaction into a pending transaction. Uses polymorphism to transform the future transaction into a pending transaction. """ + head_tx = self.head() + if tx.id != head_tx.id: + raise RuntimeError( + f"Mismatch between tx at the front of the queue ({head_tx.id}) and provided tx ({tx.id})" + ) tx.txhash = txhash - tx.created = int(time.time()) + now = int(time.time()) + tx.created = now + tx.last_updated = now + tx.retries = 0 # reset retries tx.capped = False tx.__class__ = PendingTx tx: PendingTx - self.__set_active(tx=tx) + + self.__pop() # remove from queue + self.__set_active(tx=tx) # set as active return tx def fault( @@ -151,6 +169,7 @@ def fault( ) hook = self.__active.on_fault + tx = self.__active txhash = tx.txhash.hex() @@ -164,8 +183,7 @@ def fault( f"{txhash}{f' ({fault_error.message})' if fault_error.message else ''}" ) self.clear_active() - if hook: - fire_hook(hook=hook, tx=tx) + fire_hook(hook=hook, tx=tx) def finalize_active_tx(self, receipt: TxReceipt) -> None: """ @@ -175,15 +193,14 @@ def finalize_active_tx(self, receipt: TxReceipt) -> None: if not self.__active: raise RuntimeError("No pending transaction to finalize") - hook = self.__active.on_finalized self.__active.receipt = receipt self.__active.__class__ = FinalizedTx tx = self.__active + hook = self.__active.on_finalized self.finalized.add(tx) # noqa log.info(f"[tracker] #atx-{tx.id} pending -> finalized") self.clear_active() - if hook: - fire_hook(hook=hook, tx=tx) + fire_hook(hook=hook, tx=tx) def clear_active(self) -> None: """Clear the active transaction (destructive operation).""" @@ -205,28 +222,28 @@ def queue(self) -> Tuple[FutureTx, ...]: """Return the queue of transactions.""" return tuple(self.__queue) - def pop(self) -> FutureTx: - """Pop the next transaction from the queue.""" - return self.__queue.popleft() + def head(self) -> FutureTx: + return self.__queue[0] - def requeue(self, tx: FutureTx) -> None: - """Re-queue a transaction for broadcast and subsequent tracking.""" - tx.requeues += 1 - self.__queue.append(tx) + def increment_broadcast_retries(self, tx: FutureTx) -> None: + tx.retries += 1 + self.commit() + + def reset_broadcast_retries(self, tx: FutureTx) -> None: + tx.retries = 0 self.commit() - log.info( - f"[tracker] re-queued transaction #atx-{tx.id} " - f"priority {len(self.__queue)}" - ) def queue_tx( self, params: TxParams, + on_broadcast_failure: Callable[[FutureTx, Exception], None], + on_fault: Callable[[FaultedTx], None], + on_finalized: Callable[[FinalizedTx], None], + on_insufficient_funds: Callable[ + [Union[FutureTx, PendingTx], InsufficientFunds], None + ], info: Dict[str, str] = None, on_broadcast: Optional[Callable[[PendingTx], None]] = None, - on_broadcast_failure: Optional[Callable[[FutureTx, Exception], None]] = None, - on_finalized: Optional[Callable[[FinalizedTx], None]] = None, - on_fault: Optional[Callable[[FaultedTx], None]] = None, ) -> FutureTx: """Queue a new transaction for broadcast and subsequent tracking.""" tx = FutureTx( @@ -236,10 +253,11 @@ def queue_tx( ) # configure hooks - tx.on_broadcast = on_broadcast tx.on_broadcast_failure = on_broadcast_failure - tx.on_finalized = on_finalized tx.on_fault = on_fault + tx.on_finalized = on_finalized + tx.on_broadcast = on_broadcast + tx.on_insufficient_funds = on_insufficient_funds self.__queue.append(tx) self.commit() @@ -248,3 +266,10 @@ def queue_tx( f"[tracker] queued transaction #atx-{tx.id} priority {len(self.__queue)}" ) return tx + + def remove_queued_tx(self, tx: FutureTx) -> bool: + try: + self.__queue.remove(tx) + return True + except ValueError: + return False diff --git a/atxm/tx.py b/atxm/tx.py index 660f598..f2b068b 100644 --- a/atxm/tx.py +++ b/atxm/tx.py @@ -1,14 +1,14 @@ import json from abc import ABC, abstractmethod from dataclasses import dataclass, field -from typing import Callable, Dict, Optional +from typing import Callable, Dict, Optional, Union from eth_typing import ChecksumAddress from eth_utils import encode_hex from hexbytes import HexBytes from web3.types import TxParams, TxReceipt -from atxm.exceptions import Fault +from atxm.exceptions import Fault, InsufficientFunds TxHash = HexBytes @@ -18,16 +18,19 @@ class AsyncTx(ABC): id: int final: bool = field(default=None, init=False) fault: Optional[Fault] = field(default=None, init=False) - on_broadcast: Optional[Callable[["PendingTx"], None]] = field( + on_broadcast_failure: Optional[Callable[["FutureTx", Exception], None]] = field( default=None, init=False ) - on_broadcast_failure: Optional[Callable[["FutureTx", Exception], None]] = field( + on_broadcast: Optional[Callable[["PendingTx"], None]] = field( default=None, init=False ) + on_fault: Optional[Callable[["FaultedTx"], None]] = field(default=None, init=False) on_finalized: Optional[Callable[["FinalizedTx"], None]] = field( default=None, init=False ) - on_fault: Optional[Callable[["FaultedTx"], None]] = field(default=None, init=False) + on_insufficient_funds: Optional[ + Callable[[Union["FutureTx", "PendingTx"], InsufficientFunds], None] + ] = field(default=None, init=False) def __repr__(self): return f"<{self.__class__.__name__} id={self.id} final={self.final}>" @@ -56,7 +59,7 @@ def from_dict(cls, data: Dict): class FutureTx(AsyncTx): params: TxParams info: Optional[Dict] = None - requeues: int = field(default=0, init=False) + retries: int = field(default=0, init=False) final: bool = field(default=False, init=False) def __hash__(self): @@ -93,6 +96,7 @@ def from_dict(cls, data: Dict): class PendingTx(AsyncTx): retries: int = field(default=0, init=False) final: bool = field(default=False, init=False) + last_updated: int = field(default=0, init=False) params: TxParams txhash: TxHash created: int @@ -153,6 +157,14 @@ def from_dict(cls, data: Dict): def txhash(self) -> TxHash: return self.receipt["transactionHash"] + @property + def block_number(self): + return self.receipt["blockNumber"] + + @property + def successful(self): + return self.receipt["status"] == 1 + @dataclass class FaultedTx(AsyncTx): diff --git a/atxm/utils.py b/atxm/utils.py index e43e567..05c407d 100644 --- a/atxm/utils.py +++ b/atxm/utils.py @@ -1,7 +1,8 @@ import contextlib -from typing import Callable, Optional, Union +from typing import Callable, Optional from cytoolz import memoize +from eth_utils import ValidationError from twisted.internet import reactor from web3 import Web3 from web3.exceptions import ( @@ -10,14 +11,12 @@ TooManyRequests, TransactionNotFound, ) -from web3.types import TxData, TxParams +from web3.types import RPCError, TxData, TxParams from web3.types import TxReceipt, Wei -from atxm.exceptions import ( - TransactionReverted, -) +from atxm.exceptions import InsufficientFunds, RPCException from atxm.logging import log -from atxm.tx import AsyncTx, FinalizedTx, PendingTx, TxHash +from atxm.tx import AsyncTx, PendingTx, TxHash @memoize @@ -55,10 +54,9 @@ def _get_receipt(w3: Web3, pending_tx: PendingTx) -> Optional[TxReceipt]: """ Hits eth_getTransaction and eth_getTransactionReceipt for the active pending txhash and checks if - it has been finalized or reverted. + it has been finalized or not. Returns the receipt if the transaction has been finalized. - NOTE: Performs state changes """ try: txdata = w3.eth.get_transaction(pending_tx.txhash) @@ -70,33 +68,32 @@ def _get_receipt(w3: Web3, pending_tx: PendingTx) -> Optional[TxReceipt]: if not receipt: return - status = receipt.get("status") + status = receipt["status"] if status == 0: # If status in response equals 1 the transaction was successful. # If it is equals 0 the transaction was reverted by EVM. # https://web3py.readthedocs.io/en/stable/web3.eth.html#web3.eth.Eth.get_transaction_receipt log.warn( - f"[reverted] Transaction {txdata['hash'].hex()} was reverted by EVM with status {status}" + f"[reverted] Transaction {txdata['nonce']}|{receipt['transactionHash'].hex()} " + f"was reverted by EVM with status {status} in block #{receipt['blockNumber']}" ) - raise TransactionReverted( - tx=pending_tx, receipt=receipt, message=f"Reverted with EVM status {status}" + else: + log.info( + f"[successful] Transaction {txdata['nonce']}|{receipt['transactionHash'].hex()} " + f"has been included in block #{receipt['blockNumber']}" ) - - log.info( - f"[accepted] Transaction {txdata['nonce']}|{txdata['hash'].hex()} " - f"has been included in block #{txdata['blockNumber']}" - ) return receipt -def _get_confirmations(w3: Web3, tx: Union[PendingTx, FinalizedTx]) -> int: - current_block = w3.eth.block_number +def _get_confirmations(w3: Web3, tx: PendingTx) -> int: tx_receipt = _get_receipt_from_txhash(w3=w3, txhash=tx.txhash) + if not tx_receipt: log.info(f"Transaction {tx.txhash.hex()} is pending or unconfirmed") return 0 tx_block = tx_receipt["blockNumber"] + current_block = w3.eth.block_number confirmations = current_block - tx_block return confirmations @@ -156,3 +153,24 @@ def _make_tx_params(data: TxData) -> TxParams: def _is_recoverable_send_tx_error(e: Exception) -> bool: return isinstance(e, (TooManyRequests, ProviderConnectionError, TimeExhausted)) + + +def _process_send_raw_transaction_exception(e: Exception): + try: + error = RPCError(**e.args[0]) + except TypeError: + # not an RPCError + if isinstance( + e, ValidationError + ) and "Sender does not have enough balance" in str(e): + raise InsufficientFunds(str(e)) from e + + raise e + else: + error_code = error["code"] + error_message = error["message"] + if error_code == -32000: # IPC Error + if "insufficient funds" in error_message: + raise InsufficientFunds(error_message) + + raise RPCException(error_code, error_message) from e diff --git a/dev-requirements.txt b/dev-requirements.txt index 40c4f4c..00a048f 100644 --- a/dev-requirements.txt +++ b/dev-requirements.txt @@ -5,52 +5,54 @@ asttokens==2.4.1 attrs==23.2.0; python_version >= '3.7' base58==1.0.3 bitarray==2.9.2 -black==24.2.0; python_version >= '3.8' +black==24.3.0; python_version >= '3.8' cached-property==1.5.2 certifi==2024.2.2; python_version >= '3.6' cffi==1.16.0; python_version >= '3.8' cfgv==3.4.0; python_version >= '3.8' charset-normalizer==3.3.2; python_full_version >= '3.7.0' click==8.1.7; python_version >= '3.7' -commonmark==0.9.1 -cryptography==42.0.4 +cryptography==42.0.5 cytoolz==0.12.3; implementation_name == 'cpython' dataclassy==0.11.1; python_version >= '3.6' decorator==5.1.1; python_version >= '3.5' deprecated==1.2.14; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3' distlib==0.3.8 -eip712==0.2.2; python_version >= '3.8' and python_version < '4' +eip712==0.2.5; python_version >= '3.8' and python_version < '4' eth-abi==4.2.1; python_full_version >= '3.7.2' and python_version < '4' -eth-account==0.8.0; python_version >= '3.6' and python_version < '4' -eth-ape==0.6.27; python_version >= '3.8' and python_version < '4' +eth-account==0.10.0; python_version >= '3.7' and python_version < '4' +eth-ape==0.7.13; python_version >= '3.8' and python_version < '4' eth-bloom==3.0.0; python_version >= '3.8' and python_version < '4' -eth-hash[pycryptodome]==0.6.0; python_version >= '3.8' and python_version < '4' -eth-keyfile==0.6.1 +eth-hash[pycryptodome]==0.7.0; python_version >= '3.8' and python_version < '4' +eth-keyfile==0.8.0; python_version >= '3.8' and python_version < '4' eth-keys==0.4.0 -eth-pydantic-types==0.1.0a5; python_version >= '3.8' and python_version < '4' -eth-rlp==0.3.0; python_version >= '3.7' and python_version < '4' +eth-pydantic-types==0.1.0; python_version >= '3.8' and python_version < '4' +eth-rlp==1.0.1; python_version >= '3.8' and python_version < '4' eth-tester[py-evm]==0.9.1b2 eth-typing==3.5.2; python_full_version >= '3.7.2' and python_version < '4' eth-utils==2.3.1; python_version >= '3.7' and python_version < '4' -ethpm-types==0.5.11; python_version >= '3.8' and python_version < '4' -evm-trace==0.1.2; python_version >= '3.8' and python_version < '4' +ethpm-types==0.6.9; python_version >= '3.8' and python_version < '4' +evm-trace==0.1.3; python_version >= '3.8' and python_version < '4' +evmchains==0.0.5; python_version >= '3.8' executing==2.0.1; python_version >= '3.5' -filelock==3.13.1; python_version >= '3.8' +filelock==3.13.4; python_version >= '3.8' frozenlist==1.4.1; python_version >= '3.8' greenlet==3.0.3; python_version >= '3.7' hexbytes==0.3.1; python_version >= '3.7' and python_version < '4' identify==2.5.35; python_version >= '3.8' -idna==3.6; python_version >= '3.5' +idna==3.7; python_version >= '3.5' ijson==3.2.3 -importlib-metadata==7.0.1; python_version >= '3.8' +importlib-metadata==7.1.0; python_version >= '3.8' iniconfig==2.0.0; python_version >= '3.7' -ipython==8.22.1; python_version >= '3.10' +ipython==8.23.0; python_version >= '3.10' jedi==0.19.1; python_version >= '3.6' jsonschema==4.21.1; python_version >= '3.8' jsonschema-specifications==2023.12.1; python_version >= '3.8' lazyasd==0.1.4 lru-dict==1.2.0 +markdown-it-py==3.0.0; python_version >= '3.8' matplotlib-inline==0.1.6; python_version >= '3.5' +mdurl==0.1.2; python_version >= '3.7' morphys==1.0 msgspec==0.18.6; python_version >= '3.8' multidict==6.0.5; python_version >= '3.7' @@ -60,60 +62,62 @@ numpy==1.26.4; python_version >= '3.10' packaging==23.2; python_version >= '3.7' pandas==1.5.3; python_version >= '3.8' parsimonious==0.9.0 -parso==0.8.3; python_version >= '3.6' +parso==0.8.4; python_version >= '3.6' pathspec==0.12.1; python_version >= '3.8' pexpect==4.9.0; sys_platform != 'win32' and sys_platform != 'emscripten' platformdirs==4.2.0; python_version >= '3.8' pluggy==1.4.0; python_version >= '3.8' -pre-commit==3.6.2; python_version >= '3.9' +pre-commit==3.7.0; python_version >= '3.9' prompt-toolkit==3.0.43; python_full_version >= '3.7.0' -protobuf==5.26.0rc2; python_version >= '3.8' +protobuf==5.26.1; python_version >= '3.8' ptyprocess==0.7.0 pure-eval==0.2.2 py-cid==0.3.0 py-ecc==6.0.0; python_version >= '3.6' and python_version < '4' py-evm==0.7.0a4 -py-geth==3.14.0; python_version >= '3.7' and python_version < '4' +py-geth==4.4.0; python_version >= '3.8' and python_version < '4' py-multibase==1.0.3 py-multicodec==0.2.1 py-multihash==0.2.3 -pycparser==2.21 +pycparser==2.22; python_version >= '3.8' pycryptodome==3.20.0 -pydantic==2.6.1; python_version >= '3.8' -pydantic-core==2.16.2; python_version >= '3.8' +pydantic==2.5.3; python_version >= '3.7' +pydantic-core==2.14.6; python_version >= '3.7' +pydantic-settings==2.2.1; python_version >= '3.8' pyethash==0.1.27 pygithub==1.59.1; python_version >= '3.7' pygments==2.17.2; python_version >= '3.7' pyjwt[crypto]==2.8.0; python_version >= '3.7' pynacl==1.5.0; python_version >= '3.6' pytest==7.4.4; python_version >= '3.7' -pytest-mock==3.12.0; python_version >= '3.8' -pytest-twisted==1.14.0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' +pytest-mock==3.14.0; python_version >= '3.8' +pytest-twisted==1.14.1; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' python-baseconv==1.2.2 -python-dateutil==2.8.2; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2' +python-dateutil==2.9.0.post0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2' +python-dotenv==1.0.1; python_version >= '3.8' pytz==2024.1 pyunormalize==15.1.0; python_version >= '3.6' pyyaml==6.0.1; python_version >= '3.6' -referencing==0.33.0; python_version >= '3.8' +referencing==0.34.0; python_version >= '3.8' regex==2023.12.25; python_version >= '3.7' requests==2.31.0; python_version >= '3.7' -rich==12.6.0; python_full_version >= '3.6.3' and python_full_version < '4.0.0' +rich==13.7.1; python_full_version >= '3.7.0' rlp==3.0.0 rpds-py==0.18.0; python_version >= '3.8' -ruff==0.2.2; python_version >= '3.7' +ruff==0.3.6; python_version >= '3.7' safe-pysha3==1.0.4 semantic-version==2.10.0; python_version >= '2.7' -setuptools==69.1.1; python_version >= '3.8' +setuptools==69.2.0; python_version >= '3.8' six==1.16.0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2' sortedcontainers==2.4.0 -sqlalchemy==2.0.27; python_version >= '3.7' +sqlalchemy==2.0.29; python_version >= '3.7' stack-data==0.6.3 toolz==0.12.1; python_version >= '3.7' tqdm==4.66.2; python_version >= '3.7' -traitlets==5.14.1; python_version >= '3.8' +traitlets==5.14.2; python_version >= '3.8' trie==2.2.0; python_version >= '3.7' and python_version < '4' -typing-extensions==4.10.0rc1; python_version >= '3.8' -urllib3==1.26.18; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5' +typing-extensions==4.11.0; python_version >= '3.8' +urllib3==2.2.0; python_version >= '3.8' varint==1.0.2 virtualenv==20.25.1; python_version >= '3.7' watchdog==3.0.0; python_version >= '3.7' @@ -122,4 +126,4 @@ web3==6.15.1; python_full_version >= '3.7.2' websockets==12.0; python_version >= '3.8' wrapt==1.16.0; python_version >= '3.6' yarl==1.9.4; python_version >= '3.7' -zipp==3.17.0; python_version >= '3.8' +zipp==3.18.1; python_version >= '3.8' diff --git a/diagram.png b/diagram.png index b985146..eb48802 100644 Binary files a/diagram.png and b/diagram.png differ diff --git a/examples/retrytx.py b/examples/retrytx.py index 2047032..7bc41fa 100644 --- a/examples/retrytx.py +++ b/examples/retrytx.py @@ -1,5 +1,6 @@ import os import sys +from typing import Union from eth_account import Account from twisted.internet import reactor @@ -7,8 +8,9 @@ from web3 import Web3, HTTPProvider from web3.middleware import geth_poa_middleware +from atxm.exceptions import InsufficientFunds from atxm.main import AutomaticTxMachine -from atxm.tx import PendingTx, FinalizedTx +from atxm.tx import FaultedTx, FutureTx, PendingTx, FinalizedTx # # Configuration @@ -17,7 +19,7 @@ observer = textFileLogObserver(sys.stdout) globalLogPublisher.addObserver(observer) -CHAIN_ID = 80001 +CHAIN_ID = 80002 ENDPOINT = os.environ["WEB3_PROVIDER_URI"] @@ -66,26 +68,37 @@ } # -# Define Hooks (optional) +# Define Hooks # def on_broadcast(tx: PendingTx): txhash = tx.txhash.hex() - print(f"[alert] Transaction has been broadcasted ({txhash})!") - print(f"View on PolygonScan: https://mumbai.polygonscan.com/tx/{txhash}") + print(f"[broadcast] Transaction ({tx.id}) has been broadcasted ({txhash})!") + + +def on_broadcast_failure(tx: FutureTx, e: Exception): + print( + f"[broadcast_failed] Transaction ({tx.id}) failed to broadcast {tx.id} with exception {e}" + ) def on_finalized(tx: FinalizedTx): txhash = tx.receipt["transactionHash"].hex() - mumbai_polygonscan = f"https://mumbai.polygonscan.com/tx/{txhash}" - print(f"[alert] Transaction has been finalized ({txhash})!") - print(f"View on PolygonScan: {mumbai_polygonscan}") + if tx.successful: + print(f"[success] Transaction ({tx.id}) has been finalized ({txhash})!") + else: + print( + f"[failure] Transaction ({tx.id}) has been finalized ({txhash}) with status {tx.receipt['status']}!" + ) -def on_fault(tx: PendingTx): - txhash = tx.txhash.hex() - print(f"[alert] Transaction has been capped ({txhash})!") +def on_fault(tx: FaultedTx): + print(f"[fault] Transaction ({tx.id}) has faulted with error {tx.fault.name}!") + + +def on_insufficient_funds(tx: Union[FutureTx, PendingTx], e: InsufficientFunds): + print(f"[insufficient_funds] Transaction ({tx.id}) has insufficient funds - {e}!") # @@ -97,11 +110,13 @@ def on_fault(tx: PendingTx): # required signer=account, params=[legacy_transaction, transaction_eip1559], + on_broadcast_failure=on_broadcast_failure, + on_fault=on_fault, + on_finalized=on_finalized, + on_insufficient_funds=on_insufficient_funds, # optional info={"message": "something wonderful is happening..."}, on_broadcast=on_broadcast, - on_finalized=on_finalized, - on_fault=on_fault, ) reactor.run() diff --git a/requirements.txt b/requirements.txt index 2cc75ac..315721b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,38 +8,38 @@ charset-normalizer==3.3.2; python_full_version >= '3.7.0' constantly==23.10.4; python_version >= '3.8' cytoolz==0.12.3; implementation_name == 'cpython' eth-abi==4.2.1; python_version < '4' and python_full_version >= '3.7.2' -eth-account==0.8.0; python_version >= '3.6' and python_version < '4' -eth-hash[pycryptodome]==0.6.0; python_version >= '3.8' and python_version < '4' -eth-keyfile==0.6.1 +eth-account==0.10.0; python_version >= '3.7' and python_version < '4' +eth-hash[pycryptodome]==0.7.0; python_version >= '3.8' and python_version < '4' +eth-keyfile==0.8.0; python_version >= '3.8' and python_version < '4' eth-keys==0.4.0 -eth-rlp==0.3.0; python_version >= '3.7' and python_version < '4' +eth-rlp==1.0.1; python_version >= '3.8' and python_version < '4' eth-typing==3.5.2; python_version < '4' and python_full_version >= '3.7.2' eth-utils==2.3.1; python_version >= '3.7' and python_version < '4' frozenlist==1.4.1; python_version >= '3.8' hexbytes==0.3.1; python_version >= '3.7' and python_version < '4' hyperlink==21.0.0 -idna==3.6; python_version >= '3.5' +idna==3.7; python_version >= '3.5' incremental==22.10.0 jsonschema==4.21.1; python_version >= '3.8' jsonschema-specifications==2023.12.1; python_version >= '3.8' lru-dict==1.2.0 multidict==6.0.5; python_version >= '3.7' parsimonious==0.9.0 -protobuf==5.26.0rc2; python_version >= '3.8' +protobuf==5.26.1; python_version >= '3.8' pycryptodome==3.20.0 python-statemachine==2.1.2; python_version < '3.13' and python_version >= '3.7' pyunormalize==15.1.0; python_version >= '3.6' -referencing==0.33.0; python_version >= '3.8' +referencing==0.34.0; python_version >= '3.8' regex==2023.12.25; python_version >= '3.7' requests==2.31.0; python_version >= '3.7' rlp==3.0.0 rpds-py==0.18.0; python_version >= '3.8' -setuptools==69.1.1; python_version >= '3.8' +setuptools==69.2.0; python_version >= '3.8' six==1.16.0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3' toolz==0.12.1; python_version >= '3.7' -twisted==24.2.0rc1; python_full_version >= '3.8.0' -typing-extensions==4.10.0rc1; python_version >= '3.8' -urllib3==1.26.18; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5' +twisted==24.3.0; python_full_version >= '3.8.0' +typing-extensions==4.11.0; python_version >= '3.8' +urllib3==2.2.0; python_version >= '3.8' web3==6.15.1; python_full_version >= '3.7.2' websockets==12.0; python_version >= '3.8' yarl==1.9.4; python_version >= '3.7' diff --git a/tests/test_api.py b/tests/test_api.py index f0489ac..d003dd8 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -12,12 +12,17 @@ def test_machine( machine, clock, mock_wake_sleep, + mocker, ): assert not machine.busy async_txs = machine.queue_transactions( params=[legacy_transaction, eip1559_transaction], signer=account, info={"message": "something wonderful is happening..."}, + on_broadcast_failure=mocker.Mock(), + on_fault=mocker.Mock(), + on_finalized=mocker.Mock(), + on_insufficient_funds=mocker.Mock(), ) assert len(async_txs) == 2 diff --git a/tests/test_faults.py b/tests/test_faults.py index 3fe125b..4eeeccf 100644 --- a/tests/test_faults.py +++ b/tests/test_faults.py @@ -2,12 +2,10 @@ import pytest_twisted from twisted.internet import reactor from twisted.internet.task import deferLater -from web3.types import TxReceipt from atxm.exceptions import Fault, TransactionFaulted from atxm.strategies import AsyncTxStrategy from atxm.tx import FaultedTx -from atxm.utils import _get_receipt_from_txhash def _broadcast_tx(machine, eip1559_transaction, account, mocker): @@ -16,7 +14,10 @@ def _broadcast_tx(machine, eip1559_transaction, account, mocker): atx = machine.queue_transaction( params=eip1559_transaction, signer=account, + on_broadcast_failure=mocker.Mock(), on_fault=fault_hook, + on_finalized=mocker.Mock(), + on_insufficient_funds=mocker.Mock(), ) # broadcast tx @@ -50,35 +51,12 @@ def _verify_tx_faulted(machine, atx, fault_hook, expected_fault: Fault): assert atx.final is False -def test_revert( - w3, +@pytest.mark.usefixtures("disable_auto_mining", "mock_wake_sleep") +def test_strategy_fault( machine, - clock, eip1559_transaction, account, - interval, - mock_wake_sleep, mocker, -): - atx, fault_hook = _broadcast_tx(machine, eip1559_transaction, account, mocker) - - assert machine.pending - - # tx auto-mined; force receipt to symbolize a revert of the tx - receipt = _get_receipt_from_txhash(w3, atx.txhash) - revert_receipt = dict(receipt) - revert_receipt["status"] = 0 - - mocker.patch.object( - w3.eth, "get_transaction_receipt", return_value=TxReceipt(revert_receipt) - ) - - _verify_tx_faulted(machine, atx, fault_hook, expected_fault=Fault.REVERT) - - -@pytest.mark.usefixtures("disable_auto_mining") -def test_strategy_fault( - w3, machine, clock, eip1559_transaction, account, interval, mock_wake_sleep, mocker ): faulty_strategy = mocker.Mock(spec=AsyncTxStrategy) @@ -97,9 +75,12 @@ def test_strategy_fault( assert atx.error == faulty_message -@pytest.mark.usefixtures("disable_auto_mining") +@pytest.mark.usefixtures("disable_auto_mining", "mock_wake_sleep") def test_timeout_strategy_fault( - w3, machine, clock, eip1559_transaction, account, interval, mock_wake_sleep, mocker + machine, + eip1559_transaction, + account, + mocker, ): atx, fault_hook = _broadcast_tx(machine, eip1559_transaction, account, mocker) diff --git a/tests/test_machine.py b/tests/test_machine.py index 3889050..2e9e7fd 100644 --- a/tests/test_machine.py +++ b/tests/test_machine.py @@ -1,3 +1,6 @@ +import time +from unittest.mock import ANY + import math from typing import List @@ -55,6 +58,7 @@ def test_queue_from_parameter_handling( account, eip1559_transaction, mock_wake_sleep, + mocker, ): # 1. "from" parameter does not match account with pytest.raises(ValueError): @@ -70,6 +74,10 @@ def test_queue_from_parameter_handling( _ = machine.queue_transaction( params=tx_params, signer=account, + on_broadcast_failure=mocker.Mock(), + on_fault=mocker.Mock(), + on_finalized=mocker.Mock(), + on_insufficient_funds=mocker.Mock(), ) # 2. no "from" parameter @@ -80,6 +88,10 @@ def test_queue_from_parameter_handling( atx = machine.queue_transaction( params=tx_params, signer=account, + on_broadcast_failure=mocker.Mock(), + on_fault=mocker.Mock(), + on_finalized=mocker.Mock(), + on_insufficient_funds=mocker.Mock(), ) assert atx.params["from"] == account.address, "same as signer account" @@ -89,6 +101,10 @@ def test_queue_from_parameter_handling( atx = machine.queue_transaction( params=tx_params, signer=account, + on_broadcast_failure=mocker.Mock(), + on_fault=mocker.Mock(), + on_finalized=mocker.Mock(), + on_insufficient_funds=mocker.Mock(), ) assert atx.params["from"] == account.address @@ -100,6 +116,7 @@ def test_queue( account, eip1559_transaction, mock_wake_sleep, + mocker, ): wake, _ = mock_wake_sleep @@ -112,6 +129,10 @@ def test_queue( atx = machine.queue_transaction( params=eip1559_transaction, signer=account, + on_broadcast_failure=mocker.Mock(), + on_fault=mocker.Mock(), + on_finalized=mocker.Mock(), + on_insufficient_funds=mocker.Mock(), ) assert isinstance(atx, FutureTx) @@ -152,6 +173,10 @@ def test_wake_after_queuing_when_idle_and_not_already_running( params=eip1559_transaction, signer=account, info={"message": "something wonderful is happening..."}, + on_broadcast_failure=mocker.Mock(), + on_fault=mocker.Mock(), + on_finalized=mocker.Mock(), + on_insufficient_funds=mocker.Mock(), ) assert stop_spy.call_count == 0, "no task to stop" @@ -184,6 +209,10 @@ def test_wake_after_queuing_when_idle_and_already_running( params=eip1559_transaction, signer=account, info={"message": "something wonderful is happening..."}, + on_broadcast_failure=mocker.Mock(), + on_fault=mocker.Mock(), + on_finalized=mocker.Mock(), + on_insufficient_funds=mocker.Mock(), ) assert stop_spy.call_count == 1, "task stopped" @@ -200,6 +229,7 @@ def test_wake_no_call_after_queuing_when_already_busy( eip1559_transaction, account, mock_wake_sleep, + mocker, ): wake, _ = mock_wake_sleep @@ -210,6 +240,10 @@ def test_wake_no_call_after_queuing_when_already_busy( params=eip1559_transaction, signer=account, info={"message": "something wonderful is happening..."}, + on_broadcast_failure=mocker.Mock(), + on_fault=mocker.Mock(), + on_finalized=mocker.Mock(), + on_insufficient_funds=mocker.Mock(), ) assert wake.call_count == 1 @@ -222,6 +256,10 @@ def test_wake_no_call_after_queuing_when_already_busy( params=eip1559_transaction, signer=account, info={"message": "something wonderful is happening..."}, + on_broadcast_failure=mocker.Mock(), + on_fault=mocker.Mock(), + on_finalized=mocker.Mock(), + on_insufficient_funds=mocker.Mock(), ) assert wake.call_count == 1 # remains unchanged @@ -232,6 +270,7 @@ def test_wake_no_call_after_queuing_when_already_paused( eip1559_transaction, account, mock_wake_sleep, + mocker, ): wake, sleep = mock_wake_sleep @@ -248,6 +287,10 @@ def test_wake_no_call_after_queuing_when_already_paused( params=eip1559_transaction, signer=account, info={"message": "something wonderful is happening..."}, + on_broadcast_failure=mocker.Mock(), + on_fault=mocker.Mock(), + on_finalized=mocker.Mock(), + on_insufficient_funds=mocker.Mock(), ) assert wake.call_count == 0 @@ -270,11 +313,15 @@ def test_broadcast( assert not machine.busy # Queue a transaction - hook = mocker.Mock() + broadcast_hook = mocker.Mock() atx = machine.queue_transaction( params=eip1559_transaction, signer=account, - on_broadcast=hook, + on_broadcast=broadcast_hook, + on_broadcast_failure=mocker.Mock(), + on_fault=mocker.Mock(), + on_finalized=mocker.Mock(), + on_insufficient_funds=mocker.Mock(), info={"message": "something wonderful is happening..."}, ) @@ -301,7 +348,7 @@ def test_broadcast( # wait for the hook to be called yield deferLater(reactor, 0.2, lambda: None) - assert hook.call_count == 1 + assert broadcast_hook.call_count == 1 assert atx.retries == 0 @@ -314,6 +361,131 @@ def test_broadcast( machine.stop() +@pytest_twisted.inlineCallbacks +def test_broadcast_insufficient_funds( + w3, + machine, + legacy_transaction, + account, + mocker, + mock_wake_sleep, +): + """ + Insufficient funds when using eth-tester is different than for other chains. For chains, it + fails with: + + ValueError: {'code': -32000, 'message': 'insufficient funds for gas * price + value: balance 0, tx cost 629999999979000, overshot 629999999979000'} + """ + wake, _ = mock_wake_sleep + + assert machine.current_state == machine._IDLE + assert not machine.busy + + # Queue a transaction + broadcast_hook = mocker.Mock() + broadcast_failure_hook = mocker.Mock() + insufficient_funds_hook = mocker.Mock() + fault_hook = mocker.Mock() + atx = machine.queue_transaction( + params=legacy_transaction, + signer=account, + on_broadcast=broadcast_hook, + on_broadcast_failure=broadcast_failure_hook, + on_insufficient_funds=insufficient_funds_hook, + on_fault=mocker.Mock(), + on_finalized=mocker.Mock(), + info={"message": "something wonderful is happening..."}, + ) + + assert wake.call_count == 1 + + # There is one queued transaction + assert len(machine.queued) == 1 + + value_error = ValueError( + { + "code": -32000, + "message": "insufficient funds for gas * price + value: balance 0, tx cost 629999999979000, overshot 629999999979000", + } + ) + mocker.patch.object(w3.eth, "send_raw_transaction", side_effect=value_error) + + machine.start(now=True) + + # wait for the hook to be called + yield deferLater(reactor, 0.2, lambda: None) + assert insufficient_funds_hook.call_count == 1 + insufficient_funds_hook.assert_called_with(atx, ANY) + + assert broadcast_failure_hook.call_count == 0, "broadcast failure hook not called" + assert broadcast_hook.call_count == 0, "broadcast hook not called" + assert fault_hook.call_count == 0, "fault hook not called" + + # check that tx still in queue + assert len(machine.queued) == 1 + + +@pytest_twisted.inlineCallbacks +def test_broadcast_insufficient_funds_eth_tester( + w3, + machine, + eip1559_transaction, + account, + mocker, + mock_wake_sleep, +): + """ + Insufficient funds when using eth-tester is different than for other chains. Unsure if chains + will move this direction of not, so covering this case for now. It fails with: + + eth_utils.exceptions.ValidationError: Sender does not have enough balance to + cover transaction value and gas (has 1000000000000000000000000, needs 2000000000021000000000000) + """ + wake, _ = mock_wake_sleep + + assert machine.current_state == machine._IDLE + assert not machine.busy + + transaction_params = dict(eip1559_transaction) + account_balance = w3.eth.get_balance(account.address) + transaction_params["value"] = account_balance * 2 # not affordable + + # Queue a transaction + broadcast_hook = mocker.Mock() + broadcast_failure_hook = mocker.Mock() + insufficient_funds_hook = mocker.Mock() + fault_hook = mocker.Mock() + atx = machine.queue_transaction( + params=transaction_params, + signer=account, + on_broadcast=broadcast_hook, + on_broadcast_failure=broadcast_failure_hook, + on_insufficient_funds=insufficient_funds_hook, + on_fault=mocker.Mock(), + on_finalized=mocker.Mock(), + info={"message": "something wonderful is happening..."}, + ) + + assert wake.call_count == 1 + + # There is one queued transaction + assert len(machine.queued) == 1 + + machine.start(now=True) + + # wait for the hook to be called + yield deferLater(reactor, 0.2, lambda: None) + assert insufficient_funds_hook.call_count == 1 + insufficient_funds_hook.assert_called_with(atx, ANY) + + assert broadcast_failure_hook.call_count == 0, "broadcast failure hook not called" + assert broadcast_hook.call_count == 0, "broadcast hook not called" + assert fault_hook.call_count == 0, "fault hook not called" + + # check that tx still in queue + assert len(machine.queued) == 1 + + @pytest_twisted.inlineCallbacks @pytest.mark.usefixtures("disable_auto_mining") @pytest.mark.parametrize( @@ -336,13 +508,17 @@ def test_broadcast_non_recoverable_error( assert not machine.busy # Queue a transaction - broadcast_failure_hook = mocker.Mock() broadcast_hook = mocker.Mock() + broadcast_failure_hook = mocker.Mock() + insufficient_funds_hook = mocker.Mock() atx = machine.queue_transaction( params=eip1559_transaction, signer=account, on_broadcast=broadcast_hook, on_broadcast_failure=broadcast_failure_hook, + on_insufficient_funds=insufficient_funds_hook, + on_fault=mocker.Mock(), + on_finalized=mocker.Mock(), info={"message": "something wonderful is happening..."}, ) @@ -357,30 +533,29 @@ def test_broadcast_non_recoverable_error( mocker.patch.object(w3.eth, "send_raw_transaction", side_effect=error) machine.start(now=True) - yield clock.advance(1) # wait for the hook to be called yield deferLater(reactor, 0.2, lambda: None) assert broadcast_failure_hook.call_count == 1 broadcast_failure_hook.assert_called_with(atx, error) - # The transaction failed and is not requeued - assert len(machine.queued) == 0 + # tx remains in queue + assert len(machine.queued) == 1 # run a few cycles for i in range(2): yield clock.advance(1) assert broadcast_hook.call_count == 0 + assert insufficient_funds_hook.call_count == 0 - assert atx.requeues == 0 + assert atx.retries == 0 # tx failed and not requeued - assert machine.current_state == machine._IDLE + assert machine.current_state == machine._BUSY - assert len(state_observer.transitions) == 2 + assert len(state_observer.transitions) == 1 assert state_observer.transitions[0] == (machine._IDLE, machine._BUSY) - assert state_observer.transitions[1] == (machine._BUSY, machine._IDLE) machine.stop() @@ -402,7 +577,7 @@ def test_broadcast_recoverable_error( mock_wake_sleep, ): # need more freedom with redo attempts for test - mocker.patch.object(machine, "_MAX_REDO_ATTEMPTS", 10) + mocker.patch.object(machine, "_MAX_RETRY_ATTEMPTS", 10) wake, _ = mock_wake_sleep @@ -410,13 +585,17 @@ def test_broadcast_recoverable_error( assert not machine.busy # Queue a transaction - broadcast_failure_hook = mocker.Mock() broadcast_hook = mocker.Mock() + broadcast_failure_hook = mocker.Mock() + insufficient_funds_hook = mocker.Mock() atx = machine.queue_transaction( params=eip1559_transaction, signer=account, on_broadcast=broadcast_hook, on_broadcast_failure=broadcast_failure_hook, + on_fault=mocker.Mock(), + on_finalized=mocker.Mock(), + on_insufficient_funds=insufficient_funds_hook, info={"message": "something wonderful is happening..."}, ) @@ -436,7 +615,7 @@ def test_broadcast_recoverable_error( for i in range(5): yield clock.advance(1) assert len(machine.queued) == 1 # remains in queue and not broadcasted - assert atx.requeues >= i + assert atx.retries >= i # call real method from now on mocker.patch.object(w3.eth, "send_raw_transaction", side_effect=real_method) @@ -459,6 +638,7 @@ def test_broadcast_recoverable_error( yield deferLater(reactor, 0.2, lambda: None) assert broadcast_hook.call_count == 1 assert broadcast_failure_hook.call_count == 0 + assert insufficient_funds_hook.call_count == 0 # tx only broadcasted and not finalized, so we are still busy assert machine.current_state == machine._BUSY @@ -474,7 +654,7 @@ def test_broadcast_recoverable_error( @pytest.mark.parametrize( "recoverable_error", [TooManyRequests, ProviderConnectionError, TimeExhausted] ) -def test_broadcast_recoverable_error_requeues_exceeded( +def test_broadcast_recoverable_error_retries_exceeded( recoverable_error, clock, w3, @@ -491,13 +671,16 @@ def test_broadcast_recoverable_error_requeues_exceeded( assert not machine.busy # Queue a transaction - broadcast_failure_hook = mocker.Mock() broadcast_hook = mocker.Mock() + broadcast_failure_hook = mocker.Mock() atx = machine.queue_transaction( params=eip1559_transaction, signer=account, on_broadcast=broadcast_hook, on_broadcast_failure=broadcast_failure_hook, + on_fault=mocker.Mock(), + on_finalized=mocker.Mock(), + on_insufficient_funds=mocker.Mock(), info={"message": "something wonderful is happening..."}, ) @@ -511,13 +694,13 @@ def test_broadcast_recoverable_error_requeues_exceeded( assert _is_recoverable_send_tx_error(error) mocker.patch.object(w3.eth, "send_raw_transaction", side_effect=error) - # repeat some cycles; tx fails then gets requeued since error is "recoverable" + # repeat some cycles; tx fails then gets retried since error is "recoverable" machine.start(now=True) # one less than max attempts - for i in range(machine._MAX_REDO_ATTEMPTS - 1): + for i in range(machine._MAX_RETRY_ATTEMPTS - 1): assert len(machine.queued) == 1 # remains in queue and not broadcasted yield clock.advance(1) - assert atx.requeues >= i + assert atx.retries >= i # push over the retry limit yield clock.advance(1) @@ -527,23 +710,20 @@ def test_broadcast_recoverable_error_requeues_exceeded( assert broadcast_failure_hook.call_count == 1 broadcast_failure_hook.assert_called_with(atx, error) - assert atx.requeues == machine._MAX_REDO_ATTEMPTS + # The transaction failed but remains in the queue, unless the user does something + assert len(machine.queued) == 1 - # The transaction failed and is not requeued - assert len(machine.queued) == 0 + # retries are reset + assert atx.retries == 0 # run a few cycles - for i in range(2): + for i in range(machine._MAX_RETRY_ATTEMPTS - 1): yield clock.advance(1) assert broadcast_hook.call_count == 0 - # tx failed and not requeued - assert machine.current_state == machine._IDLE - - assert len(state_observer.transitions) == 2 + assert len(state_observer.transitions) == 1 assert state_observer.transitions[0] == (machine._IDLE, machine._BUSY) - assert state_observer.transitions[1] == (machine._BUSY, machine._IDLE) machine.stop() @@ -563,11 +743,14 @@ def test_finalize( assert machine.current_state == machine._IDLE # Queue a transaction - hook = mocker.Mock() + finalized_hook = mocker.Mock() atx = machine.queue_transaction( params=eip1559_transaction, signer=account, - on_finalized=hook, + on_finalized=finalized_hook, + on_broadcast_failure=mocker.Mock(), + on_fault=mocker.Mock(), + on_insufficient_funds=mocker.Mock(), ) # There is one queued transaction @@ -599,10 +782,12 @@ def test_finalize( # async transaction reflects finalized state assert atx.final assert atx.receipt + assert atx.successful # wait for the hook to be called yield deferLater(reactor, 0.2, lambda: None) - assert hook.call_count == 1 + assert finalized_hook.call_count == 1 + finalized_hook.assert_called_with(atx) yield clock.advance(1) @@ -625,6 +810,7 @@ def test_follow( eip1559_transaction, account, mock_wake_sleep, + mocker, ): machine.start() assert machine.current_state == machine._IDLE @@ -632,6 +818,10 @@ def test_follow( atx = machine.queue_transaction( params=eip1559_transaction, signer=account, + on_broadcast_failure=mocker.Mock(), + on_fault=mocker.Mock(), + on_finalized=mocker.Mock(), + on_insufficient_funds=mocker.Mock(), ) # advance to broadcast the transaction @@ -676,6 +866,7 @@ def test_use_strategies_speedup_used( account, mocker, mock_wake_sleep, + strategies, ): machine.start() assert machine.current_state == machine._IDLE @@ -688,9 +879,15 @@ def test_use_strategies_speedup_used( params=eip1559_transaction, signer=account, on_broadcast=broadcast_hook, + on_broadcast_failure=mocker.Mock(), + on_fault=mocker.Mock(), + on_finalized=mocker.Mock(), + on_insufficient_funds=mocker.Mock(), ) - update_spy = mocker.spy(machine._tx_tracker, "update_after_retry") + update_spy = mocker.spy( + machine._tx_tracker, "update_active_after_successful_strategy_update" + ) # advance to broadcast the transaction while machine.pending is None: @@ -702,8 +899,8 @@ def test_use_strategies_speedup_used( broadcast_hook.assert_called_with(atx), "called with correct parameter" assert machine.current_state == machine._BUSY - original_params = dict(atx.params) + atx.last_updated = int(time.time() - strategies[0].min_time_between_speedups - 1) # need some cycles while tx unmined for strategies to kick in for i in range(2): @@ -767,7 +964,12 @@ def test_use_strategies_timeout_used( assert machine.current_state == machine._IDLE atx = machine.queue_transaction( - params=eip1559_transaction, signer=account, on_fault=fault_hook + params=eip1559_transaction, + signer=account, + on_broadcast_failure=mocker.Mock(), + on_fault=fault_hook, + on_finalized=mocker.Mock(), + on_insufficient_funds=mocker.Mock(), ) # advance to broadcast the transaction @@ -830,14 +1032,22 @@ def test_use_strategies_that_dont_make_updates( _configure_machine_strategies(machine, [strategy_1, strategy_2]) - update_spy = mocker.spy(machine._tx_tracker, "update_after_retry") + update_spy = mocker.spy( + machine._tx_tracker, "update_active_after_successful_strategy_update" + ) machine.start() assert machine.current_state == machine._IDLE broadcast_hook = mocker.Mock() atx = machine.queue_transaction( - params=eip1559_transaction, signer=account, on_broadcast=broadcast_hook + params=eip1559_transaction, + signer=account, + on_broadcast=broadcast_hook, + on_broadcast_failure=mocker.Mock(), + on_fault=mocker.Mock(), + on_finalized=mocker.Mock(), + on_insufficient_funds=mocker.Mock(), ) # advance to broadcast the transaction @@ -888,6 +1098,150 @@ def test_use_strategies_that_dont_make_updates( machine.stop() +@pytest_twisted.inlineCallbacks +@pytest.mark.usefixtures("disable_auto_mining", "mock_wake_sleep") +def test_insufficient_funds_after_strategy_update_eth_tester( + w3, + machine, + state_observer, + clock, + eip1559_transaction, + account, + mocker, +): + strategy_updated_params = dict(eip1559_transaction) + account_balance = w3.eth.get_balance(account.address) + strategy_updated_params["value"] = account_balance * 2 # not affordable + + strategy_1 = mocker.Mock(spec=AsyncTxStrategy) + strategy_1.name = "make_unaffordable" + strategy_1.execute.return_value = strategy_updated_params + + _configure_machine_strategies(machine, [strategy_1]) + + machine.start() + assert machine.current_state == machine._IDLE + + broadcast_hook = mocker.Mock() + fault_hook = mocker.Mock() + insufficient_funds_hook = mocker.Mock() + atx = machine.queue_transaction( + params=eip1559_transaction, + signer=account, + on_broadcast=broadcast_hook, + on_fault=fault_hook, + on_insufficient_funds=insufficient_funds_hook, + on_broadcast_failure=mocker.Mock(), + on_finalized=mocker.Mock(), + ) + + # advance to broadcast the transaction + while machine.pending is None: + yield clock.advance(1) + + assert machine.pending == atx + + # ensure that hook is called + yield deferLater(reactor, 0.2, lambda: None) + assert broadcast_hook.call_count == 1 + broadcast_hook.assert_called_with(atx), "called with correct parameter" + + assert len(machine.queued) == 0 + assert machine.current_state == machine._BUSY + + assert insufficient_funds_hook.call_count == 0, "not yet called" + + # get strategy to kick in + yield clock.advance(1) + + # ensure that hook is called + yield deferLater(reactor, 0.2, lambda: None) + assert insufficient_funds_hook.call_count == 1 + insufficient_funds_hook.assert_called_with(ANY, ANY) + assert fault_hook.call_count == 0, "fault hook not called" + + assert machine.pending == atx, "still pending" + + assert len(state_observer.transitions) == 1 + assert state_observer.transitions[0] == (machine._IDLE, machine._BUSY) + + machine.stop() + + +@pytest_twisted.inlineCallbacks +@pytest.mark.usefixtures("disable_auto_mining", "mock_wake_sleep") +def test_insufficient_funds_after_strategy_update( + w3, + machine, + state_observer, + clock, + eip1559_transaction, + account, + mocker, +): + strategy_1 = mocker.Mock(spec=AsyncTxStrategy) + strategy_1.name = "no_change_but_still_returns_non_none" + strategy_1.execute.return_value = eip1559_transaction + + _configure_machine_strategies(machine, [strategy_1]) + + machine.start() + assert machine.current_state == machine._IDLE + + broadcast_hook = mocker.Mock() + fault_hook = mocker.Mock() + insufficient_funds_hook = mocker.Mock() + atx = machine.queue_transaction( + params=eip1559_transaction, + signer=account, + on_broadcast=broadcast_hook, + on_fault=fault_hook, + on_insufficient_funds=insufficient_funds_hook, + on_broadcast_failure=mocker.Mock(), + on_finalized=mocker.Mock(), + ) + + # advance to broadcast the transaction + while machine.pending is None: + yield clock.advance(1) + + assert machine.pending == atx + + # ensure that hook is called + yield deferLater(reactor, 0.2, lambda: None) + assert broadcast_hook.call_count == 1 + broadcast_hook.assert_called_with(atx), "called with correct parameter" + + assert len(machine.queued) == 0 + assert machine.current_state == machine._BUSY + + assert insufficient_funds_hook.call_count == 0, "not yet called" + + value_error = ValueError( + { + "code": -32000, + "message": "insufficient funds for gas * price + value: balance 0, tx cost 629999999979000, overshot 629999999979000", + } + ) + mocker.patch.object(w3.eth, "send_raw_transaction", side_effect=value_error) + + # get strategy to kick in + yield clock.advance(1) + + # ensure that hook is called + yield deferLater(reactor, 0.2, lambda: None) + assert insufficient_funds_hook.call_count == 1 + insufficient_funds_hook.assert_called_with(ANY, ANY) + assert fault_hook.call_count == 0, "fault hook not called" + + assert machine.pending == atx, "still pending" + + assert len(state_observer.transitions) == 1 + assert state_observer.transitions[0] == (machine._IDLE, machine._BUSY) + + machine.stop() + + @pytest_twisted.inlineCallbacks @pytest.mark.usefixtures("disable_auto_mining") @pytest.mark.parametrize( @@ -914,7 +1268,7 @@ def test_retry_with_errors_but_recovers( mock_wake_sleep, ): # need more freedom with redo attempts for test - mocker.patch.object(machine, "_MAX_REDO_ATTEMPTS", 10) + mocker.patch.object(machine, "_MAX_RETRY_ATTEMPTS", 10) # strategies that don't make updates strategy_1 = mocker.Mock(spec=AsyncTxStrategy) @@ -924,7 +1278,9 @@ def test_retry_with_errors_but_recovers( _configure_machine_strategies(machine, [strategy_1]) - update_spy = mocker.spy(machine._tx_tracker, "update_after_retry") + update_spy = mocker.spy( + machine._tx_tracker, "update_active_after_successful_strategy_update" + ) machine.start() assert machine.current_state == machine._IDLE @@ -936,6 +1292,9 @@ def test_retry_with_errors_but_recovers( signer=account, on_fault=fault_hook, on_broadcast=broadcast_hook, + on_broadcast_failure=mocker.Mock(), + on_finalized=mocker.Mock(), + on_insufficient_funds=mocker.Mock(), ) # advance to broadcast the transaction @@ -1032,7 +1391,9 @@ def test_retry_with_errors_retries_exceeded( _configure_machine_strategies(machine, [strategy_1]) - update_spy = mocker.spy(machine._tx_tracker, "update_after_retry") + update_spy = mocker.spy( + machine._tx_tracker, "update_active_after_successful_strategy_update" + ) machine.start() assert machine.current_state == machine._IDLE @@ -1042,8 +1403,11 @@ def test_retry_with_errors_retries_exceeded( atx = machine.queue_transaction( params=eip1559_transaction, signer=account, + on_broadcast_failure=mocker.Mock(), on_fault=fault_hook, on_broadcast=broadcast_hook, + on_finalized=mocker.Mock(), + on_insufficient_funds=mocker.Mock(), ) # advance to broadcast the transaction @@ -1061,7 +1425,7 @@ def test_retry_with_errors_retries_exceeded( mocker.patch.object(w3.eth, "send_raw_transaction", side_effect=error) # retry max attempts - for i in range(machine._MAX_REDO_ATTEMPTS): + for i in range(machine._MAX_RETRY_ATTEMPTS): assert machine.pending is not None yield clock.advance(1) assert atx.retries >= i @@ -1078,7 +1442,7 @@ def test_retry_with_errors_retries_exceeded( assert fault_hook.call_count == 1 fault_hook.assert_called_with(atx) - assert atx.retries == machine._MAX_REDO_ATTEMPTS + assert atx.retries == machine._MAX_RETRY_ATTEMPTS assert len(machine.queued) == 0 assert atx.final is False @@ -1139,6 +1503,10 @@ def test_pause_when_busy(clock, machine, eip1559_transaction, account, mocker): _ = machine.queue_transaction( params=eip1559_transaction, signer=account, + on_broadcast_failure=mocker.Mock(), + on_fault=mocker.Mock(), + on_finalized=mocker.Mock(), + on_insufficient_funds=mocker.Mock(), ) # advance to broadcast the transaction @@ -1172,7 +1540,12 @@ def test_pause_when_busy(clock, machine, eip1559_transaction, account, mocker): @pytest.mark.usefixtures("disable_auto_mining") def test_simple_state_transitions( - ethereum_tester, machine, eip1559_transaction, account, mock_wake_sleep + ethereum_tester, + machine, + eip1559_transaction, + account, + mock_wake_sleep, + mocker, ): assert machine.current_state == machine._IDLE @@ -1201,6 +1574,10 @@ def test_simple_state_transitions( atx = machine.queue_transaction( params=eip1559_transaction, signer=account, + on_broadcast_failure=mocker.Mock(), + on_fault=mocker.Mock(), + on_finalized=mocker.Mock(), + on_insufficient_funds=mocker.Mock(), ) # broadcast tx diff --git a/tests/test_strategy.py b/tests/test_strategy.py index facd541..a7c2c20 100644 --- a/tests/test_strategy.py +++ b/tests/test_strategy.py @@ -1,3 +1,5 @@ +import time + import math import random from datetime import datetime, timedelta @@ -113,25 +115,56 @@ def test_speedup_strategy_constructor(w3): # other values speedup_increase = 0.223 max_tip_factor = 4 + min_time_between_speedups = 45 speedup_strategy = ExponentialSpeedupStrategy( w3=w3, speedup_increase_percentage=speedup_increase, max_tip_factor=max_tip_factor, + min_time_between_speedups=min_time_between_speedups, ) assert speedup_strategy.speedup_factor == (1 + speedup_increase) assert speedup_strategy.max_tip_factor == max_tip_factor + assert speedup_strategy.min_time_between_speedups == min_time_between_speedups assert speedup_strategy.name == "speedup" +def test_speedup_strategy_not_enough_time_passed( + w3, legacy_transaction, eip1559_transaction, mocker +): + speedup_percentage = 0.112 # 11.2% + min_time_between_speedup = 45 + speedup_strategy = ExponentialSpeedupStrategy( + w3=w3, + speedup_increase_percentage=speedup_percentage, + min_time_between_speedups=min_time_between_speedup, + ) + + pending_tx = mocker.Mock(spec=PendingTx) + pending_tx.id = 1 + pending_tx.last_updated = int(time.time()) # now + + # no speedup since not enough time passed + for transaction_params in [legacy_transaction, eip1559_transaction]: + pending_tx.params = dict(transaction_params) + tx_params = speedup_strategy.execute(pending_tx) + assert tx_params is None # no change since not enough time has passed + + def test_speedup_strategy_legacy_tx(w3, legacy_transaction, mocker): speedup_percentage = 0.112 # 11.2% + min_time_between_speedup = 45 speedup_strategy = ExponentialSpeedupStrategy( - w3, speedup_increase_percentage=speedup_percentage + w3=w3, + speedup_increase_percentage=speedup_percentage, + min_time_between_speedups=min_time_between_speedup, ) pending_tx = mocker.Mock(spec=PendingTx) pending_tx.id = 1 + pending_tx.last_updated = int( + time.time() - speedup_strategy.min_time_between_speedups - 1 + ) # enough time passed # generated gas price < existing tx gas price generated_gas_price = legacy_transaction["gasPrice"] - 1 # < what is in tx @@ -139,6 +172,8 @@ def test_speedup_strategy_legacy_tx(w3, legacy_transaction, mocker): assert legacy_transaction["gasPrice"] tx_params = dict(legacy_transaction) + + pending_tx.last_updated = int(time.time() - min_time_between_speedup - 1) for i in range(3): pending_tx.params = tx_params old_gas_price = tx_params["gasPrice"] @@ -199,6 +234,9 @@ def test_speedup_strategy_eip1559_tx_no_blockchain_change( old_max_fee_per_gas = eip1559_transaction["maxFeePerGas"] pending_tx.params = dict(eip1559_transaction) + pending_tx.last_updated = int( + time.time() - speedup_strategy.min_time_between_speedups - 1 + ) # enough time passed tx_params = speedup_strategy.execute(pending_tx) updated_max_priority_fee_per_gas = tx_params["maxPriorityFeePerGas"] @@ -239,6 +277,9 @@ def test_speedup_strategy_eip1559_tx_base_fee_decreased( ) pending_tx.params = dict(eip1559_transaction) + pending_tx.last_updated = int( + time.time() - speedup_strategy.min_time_between_speedups - 1 + ) # enough time passed tx_params = speedup_strategy.execute(pending_tx) updated_max_priority_fee_per_gas = tx_params["maxPriorityFeePerGas"] @@ -279,6 +320,9 @@ def test_speedup_strategy_eip1559_tx_base_fee_increased( ) pending_tx.params = dict(eip1559_transaction) + pending_tx.last_updated = int( + time.time() - speedup_strategy.min_time_between_speedups - 1 + ) # enough time passed tx_params = speedup_strategy.execute(pending_tx) updated_max_priority_fee_per_gas = tx_params["maxPriorityFeePerGas"] @@ -318,6 +362,9 @@ def test_speedup_strategy_eip1559_tx_no_max_fee(w3, eip1559_transaction, mocker) del tx_params["maxFeePerGas"] pending_tx.params = tx_params + pending_tx.last_updated = int( + time.time() - speedup_strategy.min_time_between_speedups - 1 + ) # enough time passed tx_params = speedup_strategy.execute(pending_tx) updated_max_priority_fee_per_gas = tx_params["maxPriorityFeePerGas"] @@ -351,6 +398,9 @@ def test_speedup_strategy_eip1559_tx_no_max_priority_fee( del tx_params["maxPriorityFeePerGas"] pending_tx.params = tx_params + pending_tx.last_updated = int( + time.time() - speedup_strategy.min_time_between_speedups - 1 + ) # enough time passed tx_params = speedup_strategy.execute(pending_tx) updated_max_priority_fee_per_gas = tx_params["maxPriorityFeePerGas"] @@ -375,6 +425,9 @@ def test_speedup_strategy_eip1559_tx_hit_max_tip(w3, eip1559_transaction, mocker ) = eip1559_setup(mocker, w3) pending_tx.params = dict(eip1559_transaction) + pending_tx.last_updated = int( + time.time() - speedup_strategy.min_time_between_speedups - 1 + ) # enough time passed expected_num_iterations_before_hitting_max_tip = math.ceil( math.log(max_tip_factor) / math.log(1 + speedup_percentage) ) @@ -401,10 +454,12 @@ def test_speedup_strategy_eip1559_tx_hit_max_tip(w3, eip1559_transaction, mocker def eip1559_setup(mocker, w3): speedup_percentage = round(random.randint(110, 230) / 1000, 3) # [11%, 23%] max_tip_factor = random.randint(2, 5) + min_time_between_speedups = random.randint(45, 90) speedup_strategy = ExponentialSpeedupStrategy( w3, speedup_increase_percentage=speedup_percentage, max_tip_factor=max_tip_factor, + min_time_between_speedups=min_time_between_speedups, ) pending_tx = mocker.Mock(spec=PendingTx) pending_tx.id = 1 diff --git a/tests/test_tracker.py b/tests/test_tracker.py index 1454d74..3f16710 100644 --- a/tests/test_tracker.py +++ b/tests/test_tracker.py @@ -16,7 +16,17 @@ def test_queue(eip1559_transaction, legacy_transaction, mocker): assert tx_tracker.pending is None assert len(tx_tracker.finalized) == 0 - tx = tx_tracker.queue_tx(params=eip1559_transaction) + broadcast_failure_hook_1 = mocker.Mock() + fault_hook_1 = mocker.Mock() + finalized_hook_1 = mocker.Mock() + insufficient_funds_hook_1 = mocker.Mock() + tx = tx_tracker.queue_tx( + params=eip1559_transaction, + on_broadcast_failure=broadcast_failure_hook_1, + on_fault=fault_hook_1, + on_finalized=finalized_hook_1, + on_insufficient_funds=insufficient_funds_hook_1, + ) assert len(tx_tracker.queue) == 1 assert isinstance(tx, FutureTx) assert tx_tracker.queue[0] == tx @@ -31,7 +41,18 @@ def test_queue(eip1559_transaction, legacy_transaction, mocker): assert len(tx_tracker.finalized) == 0 tx_2_info = {"description": "it's me!", "message": "me who?"} - tx_2 = tx_tracker.queue_tx(params=legacy_transaction, info=tx_2_info) + broadcast_failure_hook_2 = mocker.Mock() + fault_hook_2 = mocker.Mock() + finalized_hook_2 = mocker.Mock() + insufficient_funds_hook_2 = mocker.Mock() + tx_2 = tx_tracker.queue_tx( + params=legacy_transaction, + info=tx_2_info, + on_broadcast_failure=broadcast_failure_hook_2, + on_fault=fault_hook_2, + on_finalized=finalized_hook_2, + on_insufficient_funds=insufficient_funds_hook_2, + ) assert len(tx_tracker.queue) == 2 assert isinstance(tx_2, FutureTx) assert tx_tracker.queue[1] == tx_2 @@ -47,20 +68,23 @@ def test_queue(eip1559_transaction, legacy_transaction, mocker): # check hooks assert tx.on_broadcast is None - assert tx.on_broadcast_failure is None - assert tx.on_fault is None - assert tx.on_finalized is None - + assert tx.on_broadcast_failure == broadcast_failure_hook_1 + assert tx.on_fault == fault_hook_1 + assert tx.on_finalized == finalized_hook_1 + assert tx.on_insufficient_funds == insufficient_funds_hook_1 + + broadcast_failure_hook_3 = mocker.Mock() + fault_hook_3 = mocker.Mock() + finalized_hook_3 = mocker.Mock() + insufficient_funds_hook_3 = mocker.Mock() broadcast_hook = mocker.Mock() - broadcast_failure_hook = mocker.Mock() - fault_hook = mocker.Mock() - finalized_hook = mocker.Mock() tx_3 = tx_tracker.queue_tx( params=eip1559_transaction, on_broadcast=broadcast_hook, - on_broadcast_failure=broadcast_failure_hook, - on_fault=fault_hook, - on_finalized=finalized_hook, + on_broadcast_failure=broadcast_failure_hook_3, + on_fault=fault_hook_3, + on_finalized=finalized_hook_3, + on_insufficient_funds=insufficient_funds_hook_3, ) assert tx_3.params == eip1559_transaction assert tx_3.info is None @@ -68,9 +92,9 @@ def test_queue(eip1559_transaction, legacy_transaction, mocker): assert tx_3.fault is None assert tx_3.id == 2 assert tx_3.on_broadcast == broadcast_hook - assert tx_3.on_broadcast_failure == broadcast_failure_hook - assert tx_3.on_fault == fault_hook - assert tx_3.on_finalized == finalized_hook + assert tx_3.on_broadcast_failure == broadcast_failure_hook_3 + assert tx_3.on_fault == fault_hook_3 + assert tx_3.on_finalized == finalized_hook_3 assert len(tx_tracker.queue) == 3 assert isinstance(tx_3, FutureTx) @@ -80,77 +104,31 @@ def test_queue(eip1559_transaction, legacy_transaction, mocker): assert len(tx_tracker.finalized) == 0 -def test_pop(eip1559_transaction, legacy_transaction): - tx_tracker = _TxTracker(disk_cache=False) - tx_1 = tx_tracker.queue_tx(params=eip1559_transaction) - tx_2 = tx_tracker.queue_tx(params=legacy_transaction) - tx_3 = tx_tracker.queue_tx(params=eip1559_transaction) - - assert len(tx_tracker.queue) == 3 - - for tx in [tx_1, tx_2, tx_3]: - popped_tx = tx_tracker.pop() - assert popped_tx is tx - - with pytest.raises(IndexError): - tx_tracker.pop() - - -def test_requeue(eip1559_transaction, legacy_transaction): - tx_tracker = _TxTracker(disk_cache=False) - tx_1 = tx_tracker.queue_tx(params=eip1559_transaction) - assert tx_1.requeues == 0 - tx_2 = tx_tracker.queue_tx(params=legacy_transaction) - assert tx_2.requeues == 0 - tx_3 = tx_tracker.queue_tx(params=eip1559_transaction) - assert tx_3.requeues == 0 - - assert len(tx_tracker.queue) == 3 - - base_num_requeues = 4 - for i in range(1, base_num_requeues + 1): - prior_pop = None - for _ in tx_tracker.queue: - popped_tx = tx_tracker.pop() - assert popped_tx is not prior_pop, "requeue was an append, not a prepend" - - tx_tracker.requeue(popped_tx) - prior_pop = popped_tx - assert popped_tx.requeues == i - - assert len(tx_tracker.queue) == 3, "remains the same length" - - assert tx_1.requeues == base_num_requeues - assert tx_2.requeues == base_num_requeues - assert tx_3.requeues == base_num_requeues - - _ = tx_tracker.pop() # remove tx_1 - _ = tx_tracker.pop() # remove tx_2 - - tx_tracker.requeue(tx_2) - assert tx_2.requeues == base_num_requeues + 1 - assert tx_1.requeues == base_num_requeues - assert tx_3.requeues == base_num_requeues - - def test_morph(eip1559_transaction, legacy_transaction, mocker): tx_tracker = _TxTracker(disk_cache=False) broadcast_hook = mocker.Mock() broadcast_failure_hook = mocker.Mock() fault_hook = mocker.Mock() finalized_hook = mocker.Mock() + insufficient_funds_hook = mocker.Mock() tx_1 = tx_tracker.queue_tx( params=eip1559_transaction, on_broadcast=broadcast_hook, on_broadcast_failure=broadcast_failure_hook, on_fault=fault_hook, on_finalized=finalized_hook, + on_insufficient_funds=insufficient_funds_hook, + ) + tx_2 = tx_tracker.queue_tx( + params=legacy_transaction, + on_broadcast_failure=mocker.Mock(), + on_fault=mocker.Mock(), + on_finalized=mocker.Mock(), + on_insufficient_funds=mocker.Mock(), ) - tx_2 = tx_tracker.queue_tx(params=legacy_transaction) assert tx_1.id != tx_2.id tx_hash = TxHash("0xdeadbeef") - assert tx_tracker.pop() == tx_1 pending_tx = tx_tracker.morph(tx_1, tx_hash) assert isinstance(pending_tx, PendingTx) @@ -166,12 +144,14 @@ def test_morph(eip1559_transaction, legacy_transaction, mocker): assert tx_1.on_broadcast_failure == broadcast_failure_hook assert tx_1.on_fault == fault_hook assert tx_1.on_finalized == finalized_hook + assert tx_1.on_insufficient_funds == insufficient_funds_hook + assert tx_1 not in tx_tracker.queue + assert len(tx_tracker.queue) == 1 assert isinstance(tx_2, FutureTx), "unaffected by the morph" assert tx_tracker.pending is not tx_2 tx_2_hash = TxHash("0xdeadbeef2") - assert tx_tracker.pop() == tx_2 pending_tx_2 = tx_tracker.morph(tx_2, tx_2_hash) assert isinstance(pending_tx_2, PendingTx) assert pending_tx_2 is tx_2, "same underlying object" @@ -183,6 +163,9 @@ def test_morph(eip1559_transaction, legacy_transaction, mocker): tx_tracker.pending is not tx_tracker.pending ), "copy of object always returned" + assert tx_2 not in tx_tracker.queue + assert len(tx_tracker.queue) == 0 + @pytest_twisted.inlineCallbacks def test_fault(eip1559_transaction, legacy_transaction, mocker): @@ -191,14 +174,22 @@ def test_fault(eip1559_transaction, legacy_transaction, mocker): broadcast_failure_hook = mocker.Mock() fault_hook = mocker.Mock() finalized_hook = mocker.Mock() + insufficient_funds_hook = mocker.Mock() tx = tx_tracker.queue_tx( params=eip1559_transaction, on_broadcast=broadcast_hook, on_broadcast_failure=broadcast_failure_hook, on_fault=fault_hook, on_finalized=finalized_hook, + on_insufficient_funds=insufficient_funds_hook, + ) + tx_2 = tx_tracker.queue_tx( + params=legacy_transaction, + on_broadcast_failure=mocker.Mock(), + on_fault=mocker.Mock(), + on_finalized=mocker.Mock(), + on_insufficient_funds=mocker.Mock(), ) - tx_2 = tx_tracker.queue_tx(params=legacy_transaction) assert len(tx_tracker.queue) == 2 @@ -211,7 +202,6 @@ def test_fault(eip1559_transaction, legacy_transaction, mocker): tx_tracker.fault(fault_error) tx_hash = TxHash("0xdeadbeef") - assert tx_tracker.pop() == tx pending_tx = tx_tracker.morph(tx, tx_hash) assert tx_tracker.pending.params == tx.params @@ -255,7 +245,6 @@ def test_fault(eip1559_transaction, legacy_transaction, mocker): # repeat with no hook tx_hash_2 = TxHash("0xdeadbeef2") - assert tx_tracker.pop() == tx_2 pending_tx_2 = tx_tracker.morph(tx_2, tx_hash_2) assert tx_tracker.pending.params == tx_2.params @@ -263,11 +252,11 @@ def test_fault(eip1559_transaction, legacy_transaction, mocker): assert tx_tracker.pending.params == tx_2.params fault_error = TransactionFaulted( - tx=pending_tx_2, fault=Fault.REVERT, message=fault_message + tx=pending_tx_2, fault=Fault.TIMEOUT, message=fault_message ) tx_tracker.fault(fault_error) assert isinstance(tx_2, FaultedTx) - assert tx_2.fault == Fault.REVERT + assert tx_2.fault == Fault.TIMEOUT assert tx_2.error == fault_message # no active tx @@ -282,18 +271,25 @@ def test_fault(eip1559_transaction, legacy_transaction, mocker): assert hash(tx_2) != hash(tx) -def test_update_after_retry(eip1559_transaction, legacy_transaction, mocker): +def test_update_active_after_retry(eip1559_transaction, legacy_transaction, mocker): tx_tracker = _TxTracker(disk_cache=False) - tx = tx_tracker.queue_tx(params=eip1559_transaction) + tx = tx_tracker.queue_tx( + params=eip1559_transaction, + on_broadcast_failure=mocker.Mock(), + on_fault=mocker.Mock(), + on_finalized=mocker.Mock(), + on_insufficient_funds=mocker.Mock(), + ) assert tx_tracker.pending is None with pytest.raises(RuntimeError, match="No active transaction"): # there is no active tx - tx_tracker.update_after_retry(mocker.Mock(spec=PendingTx)) + tx_tracker.update_active_after_successful_strategy_update( + mocker.Mock(spec=PendingTx) + ) tx_hash = TxHash("0xdeadbeef") - assert tx_tracker.pop() == tx tx_tracker.morph(tx, tx_hash) assert isinstance(tx, PendingTx) assert tx_tracker.pending.params == tx.params @@ -301,7 +297,7 @@ def test_update_after_retry(eip1559_transaction, legacy_transaction, mocker): with pytest.raises(RuntimeError, match="Mismatch between active tx"): mocked_tx = mocker.Mock(spec=PendingTx) mocked_tx.id = 20 - tx_tracker.update_after_retry(mocked_tx) + tx_tracker.update_active_after_successful_strategy_update(mocked_tx) # first update new_params = legacy_transaction @@ -310,7 +306,7 @@ def test_update_after_retry(eip1559_transaction, legacy_transaction, mocker): pending_tx = tx_tracker.pending # obtain fresh copy pending_tx.params = new_params pending_tx.txhash = new_tx_hash - tx_tracker.update_after_retry(pending_tx) + tx_tracker.update_active_after_successful_strategy_update(pending_tx) assert tx.params == new_params assert tx.txhash == new_tx_hash @@ -321,22 +317,29 @@ def test_update_after_retry(eip1559_transaction, legacy_transaction, mocker): pending_tx = tx_tracker.pending # obtain fresh copy pending_tx.params = new_params pending_tx.txhash = new_tx_hash - tx_tracker.update_after_retry(pending_tx) + tx_tracker.update_active_after_successful_strategy_update(pending_tx) assert tx.params == new_params assert tx.txhash == new_tx_hash def test_update_failed_retry_attempt(eip1559_transaction, legacy_transaction, mocker): tx_tracker = _TxTracker(disk_cache=False) - tx = tx_tracker.queue_tx(params=eip1559_transaction) + tx = tx_tracker.queue_tx( + params=eip1559_transaction, + on_broadcast_failure=mocker.Mock(), + on_fault=mocker.Mock(), + on_finalized=mocker.Mock(), + on_insufficient_funds=mocker.Mock(), + ) assert tx_tracker.pending is None with pytest.raises(RuntimeError, match="No active transaction"): # there is no active tx - tx_tracker.update_failed_retry_attempt(mocker.Mock(spec=PendingTx)) + tx_tracker.update_active_after_failed_strategy_update( + mocker.Mock(spec=PendingTx) + ) tx_hash = TxHash("0xdeadbeef") - assert tx_tracker.pop() == tx tx_tracker.morph(tx, tx_hash) assert isinstance(tx, PendingTx) pending_tx = tx_tracker.pending @@ -346,18 +349,19 @@ def test_update_failed_retry_attempt(eip1559_transaction, legacy_transaction, mo with pytest.raises(RuntimeError, match="Mismatch between active tx"): mocked_tx = mocker.Mock(spec=PendingTx) mocked_tx.id = 20 - tx_tracker.update_failed_retry_attempt(mocked_tx) + tx_tracker.update_active_after_failed_strategy_update(mocked_tx) assert tx.retries == 0 for i in range(1, 5): - tx_tracker.update_failed_retry_attempt(tx_tracker.pending) + tx_tracker.update_active_after_failed_strategy_update(tx_tracker.pending) assert tx.retries == i assert tx_tracker.pending.retries == i +@pytest.mark.parametrize("receipt_status", [0, 1]) # failure, success @pytest_twisted.inlineCallbacks -def test_finalize_active_tx(eip1559_transaction, mocker, tx_receipt): +def test_finalize_active_tx(receipt_status, eip1559_transaction, tx_receipt, mocker): tx_tracker = _TxTracker(disk_cache=False) with pytest.raises(RuntimeError, match="No pending transaction to finalize"): @@ -374,10 +378,10 @@ def test_finalize_active_tx(eip1559_transaction, mocker, tx_receipt): on_broadcast_failure=broadcast_failure_hook, on_fault=fault_hook, on_finalized=finalized_hook, + on_insufficient_funds=mocker.Mock(), ) tx_hash = TxHash("0xdeadbeef") - assert tx_tracker.pop() == tx tx_tracker.morph(tx, tx_hash) assert isinstance(tx, PendingTx) pending_tx = tx_tracker.pending @@ -386,6 +390,7 @@ def test_finalize_active_tx(eip1559_transaction, mocker, tx_receipt): assert len(tx_tracker.finalized) == 0 + tx_receipt["status"] = receipt_status tx_tracker.finalize_active_tx(tx_receipt) assert isinstance(tx, FinalizedTx) @@ -396,6 +401,7 @@ def test_finalize_active_tx(eip1559_transaction, mocker, tx_receipt): yield deferLater(reactor, 0.2, lambda: None) assert finalized_hook.call_count == 1 finalized_hook.assert_called_with(tx) + assert tx.successful == (receipt_status == 1) # other hooks not called assert broadcast_hook.call_count == 0 @@ -410,7 +416,7 @@ def test_finalize_active_tx(eip1559_transaction, mocker, tx_receipt): def test_commit_restore( - eip1559_transaction, legacy_transaction, tx_receipt, tempfile_path + eip1559_transaction, legacy_transaction, tx_receipt, tempfile_path, mocker ): tx_tracker = _TxTracker(disk_cache=True, filepath=tempfile_path) @@ -419,16 +425,56 @@ def test_commit_restore( restored_tracker = _TxTracker(disk_cache=True, filepath=tempfile_path) _compare_trackers(tx_tracker, restored_tracker) - tx_1 = tx_tracker.queue_tx(params=eip1559_transaction, info={"name": "tx_1"}) - tx_2 = tx_tracker.queue_tx(params=legacy_transaction) - tx_3 = tx_tracker.queue_tx(params=eip1559_transaction, info={"name": "tx_3"}) - tx_4 = tx_tracker.queue_tx(params=legacy_transaction) - tx_5 = tx_tracker.queue_tx(params=eip1559_transaction, info={"name": "tx_5"}) - tx_6 = tx_tracker.queue_tx(params=legacy_transaction) + hook = mocker.Mock() + + tx_1 = tx_tracker.queue_tx( + params=eip1559_transaction, + info={"name": "tx_1"}, + on_broadcast_failure=hook, + on_fault=hook, + on_finalized=hook, + on_insufficient_funds=hook, + ) + tx_2 = tx_tracker.queue_tx( + params=legacy_transaction, + on_broadcast_failure=hook, + on_fault=hook, + on_finalized=hook, + on_insufficient_funds=hook, + ) + tx_3 = tx_tracker.queue_tx( + params=eip1559_transaction, + info={"name": "tx_3"}, + on_broadcast_failure=hook, + on_fault=hook, + on_finalized=hook, + on_insufficient_funds=hook, + ) + tx_4 = tx_tracker.queue_tx( + params=legacy_transaction, + on_broadcast_failure=hook, + on_fault=hook, + on_finalized=hook, + on_insufficient_funds=mocker.Mock(), + ) + tx_5 = tx_tracker.queue_tx( + params=eip1559_transaction, + info={"name": "tx_5"}, + on_broadcast_failure=hook, + on_fault=hook, + on_finalized=hook, + on_insufficient_funds=hook, + ) + tx_6 = tx_tracker.queue_tx( + params=legacy_transaction, + on_broadcast_failure=hook, + on_fault=hook, + on_finalized=hook, + on_insufficient_funds=hook, + ) # max tx_1 finalized tx_hash = TxHash("0xdeadbeef") - assert tx_tracker.pop() == tx_1 tx_tracker.morph(tx_1, tx_hash) tx_tracker.finalize_active_tx(tx_receipt) @@ -441,7 +487,6 @@ def test_commit_restore( # make tx_2 finalized tx_hash_2 = TxHash("0xdeadbeef2") - assert tx_tracker.pop() == tx_2 tx_tracker.morph(tx_2, tx_hash_2) tx_tracker.finalize_active_tx(tx_receipt) @@ -456,7 +501,6 @@ def test_commit_restore( # make tx_3 active tx_hash_3 = TxHash("0xdeadbeef3") - assert tx_tracker.pop() == tx_3 tx_tracker.morph(tx_3, tx_hash_3) assert tx_tracker.pending == tx_3