From a05990b4174b470070072ea48231b8d20feda1e2 Mon Sep 17 00:00:00 2001 From: Francisco Gindre Date: Mon, 12 Sep 2022 13:18:40 -0300 Subject: [PATCH 1/2] [#525] Release 0.16.9-beta --- .../Resources/checkpoints/mainnet/1787500.json | 8 ++++++++ .../Resources/checkpoints/mainnet/1790000.json | 8 ++++++++ .../Resources/checkpoints/mainnet/1792500.json | 8 ++++++++ .../Resources/checkpoints/mainnet/1795000.json | 8 ++++++++ .../Resources/checkpoints/mainnet/1797500.json | 8 ++++++++ .../Resources/checkpoints/mainnet/1800000.json | 8 ++++++++ .../Resources/checkpoints/mainnet/1802500.json | 8 ++++++++ .../Resources/checkpoints/mainnet/1805000.json | 8 ++++++++ ZcashLightClientKit.podspec | 2 +- changelog.md | 13 +++++++++++++ 10 files changed, 78 insertions(+), 1 deletion(-) create mode 100644 Sources/ZcashLightClientKit/Resources/checkpoints/mainnet/1787500.json create mode 100644 Sources/ZcashLightClientKit/Resources/checkpoints/mainnet/1790000.json create mode 100644 Sources/ZcashLightClientKit/Resources/checkpoints/mainnet/1792500.json create mode 100644 Sources/ZcashLightClientKit/Resources/checkpoints/mainnet/1795000.json create mode 100644 Sources/ZcashLightClientKit/Resources/checkpoints/mainnet/1797500.json create mode 100644 Sources/ZcashLightClientKit/Resources/checkpoints/mainnet/1800000.json create mode 100644 Sources/ZcashLightClientKit/Resources/checkpoints/mainnet/1802500.json create mode 100644 Sources/ZcashLightClientKit/Resources/checkpoints/mainnet/1805000.json diff --git a/Sources/ZcashLightClientKit/Resources/checkpoints/mainnet/1787500.json b/Sources/ZcashLightClientKit/Resources/checkpoints/mainnet/1787500.json new file mode 100644 index 00000000..be24e89d --- /dev/null +++ b/Sources/ZcashLightClientKit/Resources/checkpoints/mainnet/1787500.json @@ -0,0 +1,8 @@ +{ + "network": "main", + "height": "1787500", + "hash": "00000000013abc1b6771cf8ce5d7b385bef21fdcff3e809e96ad9ea119c0d0fa", + "time": 1661588407, + "saplingTree": "0103e6f1a4409bc20886cfe28eb0d0eb002050edec85b427da3164dd8e8f3d7d4500190000000153f1e09fad18b246d3acf132c9abb67fbb13b5116eeaa226b8553d3e19b25a530000015af73bf615b470dbb8f7d024c13ece622e091cce7bee801e9d54c1ad4e40e02f0149816bfc59f0deaccd1ada4c1e86b19e4413d0721784c4d052c3dc67f5c1805000000001b50664d826ff3b88aaa2ba08c69b8f21dfa710d79860c1441b5c6dfaddae483d00016378d71fb6680e282583c075d721a06b4b3b75d31df554364d4b0251def8a71001936597568d67c917899fd32efdff7afa023fbca108ebcbb402bf0ea9baacf33701f1dfff89a6bedf83c3219c1fd7261883687af993e64603b2b2be8a5fbf89754b01d31d49250f8d4970c03c939f38c3e321a8a96cdaf82a3bee23e73bd7eba7f55a01adf75581cb0b5b7aa87ebed8f1e54ac62217bf676b979298dd8b4300506dd72a01b9e02a948bb911d47d1e51ab6c7050074518cb1f6ff3856f93356d6dde2f8e0700000001f07e315c5670804489d63e01a937077019b5902af6e00ddc3efaea01ef792914000139af7ec003ac4e37c263bab5642fcc4ec294a1e328ff025832eec45239041f6e", + "orchardTree": "01e6a2858cd4376f3333f54f2033cf1211a3bfc0eb24b9b80ac2cf1446dae2f31c001f0140e2ea00e36545674bc7f09751f3391d628c78399ba803b6173e9d738a5fcc320000000179b60c3bf1b8cce227df010f5c49c22c7d777d5209738683c9b697756b31a81f016f91c16f2e3960269dac6fb301b534e027bb03374126e5dbf5e202c7422c861800011e71b797efd14accdf8a33abde065d670e6fa02a777d9e7d47be234e86b5431701deb97e143f39b0820e3421fd8c38cd340bc9cfac21262ddf86e0eafd75ff570a01b5045572a2467061c9282dea3618ba48f5b6dc05d83d1ad38d57e70735e8b514013b5384e5504f03e481ba341a2f14cd449a0a390acfeb5c56a155f6d99e188813000001794870c4958f10ee3be337bd2bd6ff6784d19662c242a40ad54a3f48bb2523230001bdd331201a0524ecdd6f812cf323d1fe0173f165c181bd74199480a6211bb91f01782a84e2fe01efa2b41d6875b0537d55ead77c75811ee929f0cf9236f976383700019886882316b2f5a810dda1ece4d983ed360175e2cc5ed2b3781ee1d9f1e05f2801081565fb9592a4fab10edbfb15d2aa45c89eb85837f4f298c586cb21d45b772901f791f26a6ace2c09ba3deee8db26c3299e8e9ea6f4fdcac0846b2121721c310100000000000000000000" +} diff --git a/Sources/ZcashLightClientKit/Resources/checkpoints/mainnet/1790000.json b/Sources/ZcashLightClientKit/Resources/checkpoints/mainnet/1790000.json new file mode 100644 index 00000000..ab42998e --- /dev/null +++ b/Sources/ZcashLightClientKit/Resources/checkpoints/mainnet/1790000.json @@ -0,0 +1,8 @@ +{ + "network": "main", + "height": "1790000", + "hash": "000000000100dac22c6e557360ef3c4283d9f60c6f4c5a1e0076d3b1fe97d0d4", + "time": 1661777577, + "saplingTree": "019d3cd586fd43ea7b42b862cf823edcd874462ed3a955f1c1dfd371d86dccd5700019000000015df1e3c5a797bbbe4f4848f60f21c0bb5f7bbf3a3168cee8a1cf5689b22286590001144654b0777c00cbe78b716bacc819d39f13945657bea3ff7e0c88bf1f68894d0001164cf0832981b185067cef308aefc2cc7c93a218b7ef456b3813f58ec2162d4301ec4614750b2c957a25a24685bbd536845cebe0739d3baad2ce645105e8584d5d000168ac0ff0fb620aab894183850de738f2a79424b36e11d5daf6a77e68b771081e00012a04464db06df9410ca6b17e29f72c4b4b3c5795cee583c5422168eabb7e84210001e8e58e5db11fbfce460285fef5113a83e8b5f0ac542ce3887d722a8e2a413c2f0001fd8156cb2a632eb0265b6f49309d30a762bdbbe2e8d6148ed26ab69e6861472a01187bf80dcc94180caeacb5860ca78ef66b0977c7ef6b773428a3e19b676aeb47014e7c8a253ae2f1d791c4bb7f5026736ea71839162c1b1da41cb6a6e64a49261e0001367383639b55ca56d7008a7a64d73b875703860d1a657cc1cc0df5a9ae5d5c2b0001f07e315c5670804489d63e01a937077019b5902af6e00ddc3efaea01ef792914000139af7ec003ac4e37c263bab5642fcc4ec294a1e328ff025832eec45239041f6e", + "orchardTree": "017fed4159767f6bfb26e8136b85e4a179606f7298de17664fdcc220df21b7a02d001f01cdd388ecf7f914294552caad1d570970b47974ec73494e2f04a80e7b391afc10011f6a01e993dbacd0a5487b8a7b06275a252d0d3895fab490e2030a255a9b0c06000000012c0afb3c10723bacf85c6611273be9e7d4676128dbabec978f0fa5221223893600000001285b5cb86d8544386b6a36c49e2bdb88fbd5f59a43437cfe271d95dfb4bcf82e01b20850820c9e07dfee4fd137ebe7f4d92ff7e318f3f1581b4b53014118d6d1170000000001045adfb00ce9f9ceddcebe2658b24be1beb059888c4f93f68bde2615aca3bf0c010d67deaf001eabcb4ded2f92fd69bb00fea4118f38c2b18b9dc2ae7e8c52480701f6a82e767e738485f26942fae63d20665479e996d8404cf44a451f9585fc5b32019886882316b2f5a810dda1ece4d983ed360175e2cc5ed2b3781ee1d9f1e05f2801081565fb9592a4fab10edbfb15d2aa45c89eb85837f4f298c586cb21d45b772901f791f26a6ace2c09ba3deee8db26c3299e8e9ea6f4fdcac0846b2121721c310100000000000000000000" +} diff --git a/Sources/ZcashLightClientKit/Resources/checkpoints/mainnet/1792500.json b/Sources/ZcashLightClientKit/Resources/checkpoints/mainnet/1792500.json new file mode 100644 index 00000000..892f919b --- /dev/null +++ b/Sources/ZcashLightClientKit/Resources/checkpoints/mainnet/1792500.json @@ -0,0 +1,8 @@ +{ + "network": "main", + "height": "1792500", + "hash": "0000000000feda98d580a542579c369cbedb9677e2a60b45858dd3c3970aec48", + "time": 1661965969, + "saplingTree": "01056e79c206e1ec677117980ba8ffd3109f8df1911bf60d6a1f17194ca42d7a23018271f1b6a7b1034a1cb52e214e01fa76fe9b57b4ecc51513fdfee851a552e55c1901d12b2f51a9766ca6bb0e39e2fb8f15d3d27a620b7807b156df4d5519b115c239000166bee33838512fb2a13169dbec4a023d71eddccda1f6f364429b9012970e430f0001f498d5859edb460c87552d732c800e3cb655f87dd39edfb1123ee44f170e6b710001b8ea5420dbf802c4aa031ea066906c68e0b134ecd9f7ce1a8a9595301621b1270159b5fea41595326ec50f60bb37bc915c5525204dbb077ee6096a733e5e6be95d0001a18b7820f47bf1fe5304af022fa5c0dbe4d6d2b3c2b8f43d82e89bd29603ee3e0001ef928eac8166f4fa704d28411595a6d9e48fbce2fdb0cc1c22ff5eb65b8ea60301f62c314465ee352a914c74a495044a37f32ed19057b1e8b1139c24ae5fe1eb6d000001a54b39b5536ee32eaccd615c97b5894e59ca339fa58b170542e565957973c2640001abc74ef752650de527e13ef419675a18fa8abc0ea648bb79c9bad4ddc3a3c33300000001707d2f9501c9e36a368499bad696bc5aaa721b6d08f9a40f4ec8851e9deb373401f07e315c5670804489d63e01a937077019b5902af6e00ddc3efaea01ef792914000139af7ec003ac4e37c263bab5642fcc4ec294a1e328ff025832eec45239041f6e", + "orchardTree": "01d12a9e0d3c96789f73acf81fbbd59e1352c9510eddd29d7b3e79fdb6c34e873801ac79591488cf371014ca4577af93bf3b81be2312c23315f0d30007fee8d5a22e1f0115bacc61ac12c6c35f4601d1fa17909700158b676b305700615c8b22c50c0f11012075bbb910e3877bea5dfd4594729900eef32aa66effa3aaa2354a455a507f1801f5a06cb7bb2283e8e1bb8e5eb22ac57741f666f1c5ef2db8e5f644957c86481e0001f4924dc1afdcef8b935fda84922dab90e523b0100e329d24295e4ab809f38d19012ce7342f56d5319d6c54a63a326e33b8dced10d6cd696abf270e8305d8b6101c01e05e25ecc131faff939ad965861a12eda859ef69fd6928c65c978ae0d827592301d646e453f3ee10362226de45b8e9377ac12191351816efdf5d3254668345422e01b2642250d10f8ec40181ee561a18b60870aa700977107805b91d2f52b872f122012eb7bc0fff59658e28f1bf9b79fa184d21ff2679637745b2e6c51657993e001b01a50a617a7ffe94d3b52041db8dd9a0830c2fe42e394ce95ad4b6265bae2c7112000001f901961738a6f66400aee2908298ed5b83c53adea294db77b57d10eedbfcb5370187ae41d92d3780530a81c2db1a35768479f5843a62c64846320039681551062e00000000000001020d51b05be2e276efa73f2977f5a82defed553b1086897f0bea9b03f4dbd41a000000000000000000" +} diff --git a/Sources/ZcashLightClientKit/Resources/checkpoints/mainnet/1795000.json b/Sources/ZcashLightClientKit/Resources/checkpoints/mainnet/1795000.json new file mode 100644 index 00000000..6f9f8be5 --- /dev/null +++ b/Sources/ZcashLightClientKit/Resources/checkpoints/mainnet/1795000.json @@ -0,0 +1,8 @@ +{ + "network": "main", + "height": "1795000", + "hash": "0000000000080087f9812e87732355691e4bdd43fdbbf517618959c81cccdda1", + "time": 1662152700, + "saplingTree": "01b68aa765ae73bf3eddef03a2a0127126964d9a543859d462540467a4772425610019000001aa8687526bc08a818db298cf940aa78c30792a927a58ee1b6d11715b2507d9280000013788d7a49d2a299ce893eeb4175e702b4380f070ccd32d4b4beebdaae9d2dc3d00012653cc481a3bd5901dd5728d9754f9555932c00647c15ebd5a13cb1db579383301764ef57a84dee173a7d8b135065b75be0b012d5931af754bd339f6572ad84a5a01642fd0ec665cb43eff91032c290ee9cccf50b234af0f5ea94798973b3efc392f000190a2aa5d8963fe49141ced07138011754b00493211523505d488a2cf3ba24a320168569b74e5b9370152695d797cbe7174538b8e33304539f72efef6b58a9a4f04010b05111ee7d748441446ecc08f652247197356bbf83d73d7d07448ed4754192b017e137de7b7b21bbf9c3f9720196427864e7fe2fd8070aba9237b24fbec95914a0113c1af933a4048a7b09cb0e6ccd27691f4649385596d8111fe602bf4b255db29010470b81e369a600bdd7d6bb96c409554913044eb1c66dd7659a6e68129136165014d7bd573014ae0f6d462262ec28df786ef65aa9bcd5e2d6c54f26b4ee3633c2b0127bd15f9da068d7c81f1396a0ee87b700755451b9f4cebfa11919f3a4285713c016bce4a3d7e7ae87037b3ba0b9789dd1a1737e355cb862aba330cec03aec244710001707d2f9501c9e36a368499bad696bc5aaa721b6d08f9a40f4ec8851e9deb373401f07e315c5670804489d63e01a937077019b5902af6e00ddc3efaea01ef792914000139af7ec003ac4e37c263bab5642fcc4ec294a1e328ff025832eec45239041f6e", + "orchardTree": "012d8880e630c07430af4529f928e8d5d6d7f19f69a4449e7b6ce739f73a2e2b2f014fc16e1b120f140566bfd6a4e40797c72c87af205508ae98259bcdba377d3a0d1f01caa4adc8466eb027a6c6095ab1a20fecbe058ef63db381fd1d1f08628bb5a41801235d173241458aebb1b6d757489969e24a1a09720606ba56b1eae566bfbdb01300000126b2135c0942f13da6de1470ee2e5bc26c98f8d6d808ee71f2e7d7683ada763400000171071f52e8986c28e68db191de9cf8b88e39af84909289207ca4f56d346e85160000018f9f481c4fb6151f6868afef177dbd28f208ddb31bc4951185f31f4b6996420d014e57bc25f0a5322059b756e0b4bea83442a5e418d3486b57c617757d9098552501e0e364cb2abe4d1967bca145c4af1774eb2d84bca3af756581870661685cfb160001eb3af18b241fa811548c29205f42601bee7b58ba55fc529542836f1cf477bd080001f63a616ca09e1106337cee385439f0bdb7e14ebf6d2b3eaafc4c69ed04f147300000000001020d51b05be2e276efa73f2977f5a82defed553b1086897f0bea9b03f4dbd41a000000000000000000" +} diff --git a/Sources/ZcashLightClientKit/Resources/checkpoints/mainnet/1797500.json b/Sources/ZcashLightClientKit/Resources/checkpoints/mainnet/1797500.json new file mode 100644 index 00000000..c9b787c3 --- /dev/null +++ b/Sources/ZcashLightClientKit/Resources/checkpoints/mainnet/1797500.json @@ -0,0 +1,8 @@ +{ + "network": "main", + "height": "1797500", + "hash": "000000000062762b9e9de093b9375fe1bfc451ff05a2c11a0339c90ec3e879b0", + "time": 1662341718, + "saplingTree": "0125d05417c5144dfe8aec67961d71a700c93b51d82486803f8c4719be2708274b01c675445684630272ec881b8283397137140ef8089bac18b3f07b0a7f3f8f6c54190192d55e6fef209eede1572a74f52fb42e347bb17221b179f28443e8ac190e480701489b0714407a588eaa9b6be421f77049a98bb005b79c3df1f8d592201360f94a000000000163669ec32a05e1726bfd63268924daf5bb3f6794a05a7824f87492708cbc3b2a0001e4bfc91695ef7bce9e29f48595b8cc278af7b1857039f2fdcf75bd58b579bc6201b6a313b5daf1ebb237d55661ce5d6f6a93018b407f2386635b727bdfe8ea6019000001dfe1241acfb16d56a4163ad86a0bb7b763b4f5da28b17b4413a3e6e8dbb7d728019a4fe2a045f95d86fe1a5fa9d413c4f36e42e1f03fbb2b5bed842d96d3f71e1b018e123a755b1d549c6f87ac048fa15664bbc3b10a0ef155b046eb4bc7e5b7061d000000000000000001c5d9822e7aa76d3a758e8dc25ffd8b8c9d4051c66fb92fd7aa263905e238920e0139af7ec003ac4e37c263bab5642fcc4ec294a1e328ff025832eec45239041f6e", + "orchardTree": "01a23430438ea5dafb4b7b4db2111f6280226cf69c172a8a763d1218f92ed6c23a001f01e4aeacad6fe90d4122dffbad65537ce856480aadcca1866d9682b2b968cc5e2f000001d142373141d5e6a48be5d097631fec923007c886d34e2b1ff5fbb55fa4f33810010f5906e6e3c4bf7d8015ff88c532795ea4b49f35dbb41a6bdf8592d09439a92c01c6580c00bd5c6da40f2026554c86788192aa5e8cd5f7486d8d51cc258cb312200001e41507a73031aa566ffb009506cd5b30992b0ebd34fc3fc78449a886a4317a0d0000015b224fa4142c9508f0b2dfd1e64fff63e996b697a4dd9502f9a3fd917cbfc42200010fbd0f1d4f428961e0b32b28994bfdd80e1e68b330883bd0889f9ca8f948b6190136b0d3765a45a227aedb1925c5670bc6364856ffcbaf19b52ab06f179ed897350132fe04f3088f035c4ca4c818b3e720ac26ac172f30b27dcd3e3bbcbb8192c11001fe9e4b5837bc7acc9356055fda92475bd0593e36a180e93549bb1ce2ab52e20e01f63a616ca09e1106337cee385439f0bdb7e14ebf6d2b3eaafc4c69ed04f147300000000001020d51b05be2e276efa73f2977f5a82defed553b1086897f0bea9b03f4dbd41a000000000000000000" +} diff --git a/Sources/ZcashLightClientKit/Resources/checkpoints/mainnet/1800000.json b/Sources/ZcashLightClientKit/Resources/checkpoints/mainnet/1800000.json new file mode 100644 index 00000000..8a0cab04 --- /dev/null +++ b/Sources/ZcashLightClientKit/Resources/checkpoints/mainnet/1800000.json @@ -0,0 +1,8 @@ +{ + "network": "main", + "height": "1800000", + "hash": "0000000000f908c93f3a7f4288b1f73a5c5621397ae94e4a894ef44ecceaa961", + "time": 1662529994, + "saplingTree": "01394fa4810bafa4d2231cb3dcc560e43e9f02418e82635d6a3619276e76407118014a496ba29efc52eaf66430ebe6be9d9b8dc10ab790cf624bd9b16d404e32e52619017ce5fbd2d63aee2d62820d40951f1efdb1ea9d4b7b3c40470d44b61b3537303c01b4c88e366d13efe090502c5e1c147a70f9be10226d97a16f726aef466245a9400001ecaf654b7bb55264015e8e5cf8ca20a01f79dafea9ea174a216727160a3596180128dfcd78a6488832dedf7d4bc7af36360a3ced5ac10f26a49451d2543096672b0001867d8b2f5148a10776a109b8cc6c0fbe006256eddd007ecdc8ac703ae03db126000001836b32ac862719c718b58435f87bba6d66a476711854121212299657ab973d440120d4f1b55c4e9e43442c08a201d4518369927b976a61c12add79cfdcd62c515000000109545546d941eb229dba133374ec57484d65febeffed0eaf9101e039db7eb06d01f98dd99c797f8ff594d93854a1f42f17bcf85d22e75fa9a06ce53b395ae1e671000148f810d1aaaa173f8c744fca6efe293f1b7be578def47bd9e45e647a35df5e3001d224af2c322521ed4297213b51e327cfa001cb6b7c04fd647a8c60d5ecff6717010bfec213bc4e3bfb6039ad8e0d01f1b6662367b4e38a2b185f227d77591b3b2001cf2ef2a7179af6eb4d2552757c05466c345a169d15c5da6dc13ba8fd34e3495700000001c5d9822e7aa76d3a758e8dc25ffd8b8c9d4051c66fb92fd7aa263905e238920e0139af7ec003ac4e37c263bab5642fcc4ec294a1e328ff025832eec45239041f6e", + "orchardTree": "01e0e4bfc911716e9548d6d4cb84cf8b66f62df3647a176f9aa3c66453013ff206010b0e83345c27139830a0fff89f018987eaf3b6ed776ea138ab846e064c7685041f0001821067594b408dbac72a9967d0d135053f174f0adc604af6887867ba1a1f0735017f46cc72632f201b8854c67456f91fe83418040e8be1bf983231da9d4e8c1a0000013a6f5f208f42b3640fe9a182cc4653f0b46cf1c62f7ca31ffcd14a542d8f7917016560e4ea4138ea5bcae07924ce84454871d1da8efb6ce4a59acb78f3287a30140001cb756a9be049d541b1cb9d4e58344c3cc6a34863345ac0c122744c026f29f7200000000000000001c5633b986425941f3be7634113839a2de1d40a43529148a16fc979cfd2245d310001c31609e44f34c7d006f03facc5589989fc465061ad30e83b7d4f61a3dc2d353200000001020d51b05be2e276efa73f2977f5a82defed553b1086897f0bea9b03f4dbd41a000000000000000000" +} diff --git a/Sources/ZcashLightClientKit/Resources/checkpoints/mainnet/1802500.json b/Sources/ZcashLightClientKit/Resources/checkpoints/mainnet/1802500.json new file mode 100644 index 00000000..07a7e6fa --- /dev/null +++ b/Sources/ZcashLightClientKit/Resources/checkpoints/mainnet/1802500.json @@ -0,0 +1,8 @@ +{ + "network": "main", + "height": "1802500", + "hash": "00000000017fbac093c9dd6d349b88685f369cbbe2c12b9a0c9ed6830bfc8e91", + "time": 1662718546, + "saplingTree": "018672213d179722fdc5221d5199441fc70a068db8c20a02ebf8bec70dac9f4e08017a0d68f3d30e1f5e3690c627ead792ae7ce1d08761b095ed717db285e60a0166190001c44e548845f6a825e9fdcdcec964674a84c48cff34c112d9828c1d5f21a5502a00000000014aea74e6cc2dbae2ff085618b475820a68a5969215246e00353f68129994a72900011489a9121bb1593a210f489a44b3fb8e2fe7b0b1143d89cf338b1da874438149012ea4d7a0b0e68673080d9f47f4c92728839fe0991626ae337f9ad6bfa598872b000001629e5d548ad857baaa6716719e67708b8b696b77b23ef900d49820d028ac604101cf6d58fea133cccc9307a56ef90ca4f8b32af3fb8b494b583724275da946a1370000000001cddcf07098de80d473262d9e1594738168a73a2e27bb986b9ce8a43635ccd03d00013f6c68973c08f026717c32a6be0d4083febb8c2d63f2e895a6d07021afa3a51c000001c5d9822e7aa76d3a758e8dc25ffd8b8c9d4051c66fb92fd7aa263905e238920e0139af7ec003ac4e37c263bab5642fcc4ec294a1e328ff025832eec45239041f6e", + "orchardTree": "011618cffe419b3ae6d6f94ed2138925766f616262a81b11220dd64310db9a602201b50f80ddf6be2c181b9e4795a48aa30def25318410e19071bbcae266517237061f01000ec86443fda797bdee68d22ad1c8e3159ce016343aed4bdc4044b30808ff0d000192dd02c90b3e87a42dc8a5a1bc59b832e21937614d71aec0ca0dcb4293c8802e000000015226ac6b1c91f0b90680b4cf4175a38450727aeaec887792adb9ef13c411de0b0001bc359c17ecd3d5fb2896bfcf890ebc51de0eda3c55734dc24523a41b24267f1a0001c8b40d0cea90b7642884cdca72286ca44b278ae6e9ad2e2bbee1b30680b1a60d00010deddae26bf93da080b26610dae9ddccb9243015487b922f3c05198476c4ca2400019902d40b5aab956a3001b26499f3f689c08ffc9b5989d14840ea01f443dfe61c01c5633b986425941f3be7634113839a2de1d40a43529148a16fc979cfd2245d310001c31609e44f34c7d006f03facc5589989fc465061ad30e83b7d4f61a3dc2d353200000001020d51b05be2e276efa73f2977f5a82defed553b1086897f0bea9b03f4dbd41a000000000000000000" +} diff --git a/Sources/ZcashLightClientKit/Resources/checkpoints/mainnet/1805000.json b/Sources/ZcashLightClientKit/Resources/checkpoints/mainnet/1805000.json new file mode 100644 index 00000000..7a068b5e --- /dev/null +++ b/Sources/ZcashLightClientKit/Resources/checkpoints/mainnet/1805000.json @@ -0,0 +1,8 @@ +{ + "network": "main", + "height": "1805000", + "hash": "00000000011cfeaec0ee1d6230aa8d3642ad4c22db6d541ff43520ed2035f7eb", + "time": 1662907506, + "saplingTree": "016d31b4c64ae5c17308b9d90572e6de5993b3755e861e040949fe4438caf1000e00190000016c0be2c033839aec91d32becbaf583282f918ad917865f323a5c88d687f1bb1501ba7e5ccb08c9579b4fa81fa555c3fa94e6872eee2bf8f01641f6103e9a975b4b019c049c4abf367e07e168ba327e86a7cb04d11d9ad446e2356f2870cfe9e958340001f29ca1e8f612cc422bfeb2713a89062d8695f44436cb8ac0b57ff5f2f1d8da5401e4195653dc559c9590e0248719443c666c82d6e7521d2688d32a914070eb7a540001f8d79dde2f627bfd60d0c1131cd3ad6b779c9619f6dbb0ab0515778a9a9fe24d0000000001235b463d441fae8a4ee172bb40b1a3853ba4a07ea44377b06295db356608ea3a013c2d33c9c5afba6b720ffd9ea7c69af1635c2f98d291e2eaf358f2c87232ec2a0000013ec9722bf3aeef59c292db71e3de87d2a3868cdd2902b867328e24c30c8dbe1b0000018c2d6adea2ad4faf20eccfc2c2a2c59192fb53d3204b3a2757f1c247dadec16b0001c5d9822e7aa76d3a758e8dc25ffd8b8c9d4051c66fb92fd7aa263905e238920e0139af7ec003ac4e37c263bab5642fcc4ec294a1e328ff025832eec45239041f6e", + "orchardTree": "01a1eac07fe90382f2003de69239f281a9798b7f984bc20a3559d5f886a9e07035001f019f6fb5bad161ee9f26dbe23c485e0f25d083e17c1e68b91525f33d659d35ac1d00013957a17ebe53945197d684304f29a77486a0f081c6a4a3f4c73b33b88a834a1101e151d0e8b30866bf9779bb3d1105c3124db863690458c855537e967679bd903c000000000001bad8cfde5bec689cc645ed5c9e37f6e6f1753fa9a95f40ccfe25ac91bcc2481e019927318c01f928c75112b5d0c116e9a7244d58684396512e5e24569acb94581a0000010ca90449f94a0ea6eb64ff88b438e14649280613827022401d54a8635e63943100011759fa02e2fae57b40cd62bd3829225c15bad6430caf25a526d51f329091293e014660573ed7dbd889063d06f1d814a4f28a5ba4da76c2c5c8e11ad980f070353601c31609e44f34c7d006f03facc5589989fc465061ad30e83b7d4f61a3dc2d353200000001020d51b05be2e276efa73f2977f5a82defed553b1086897f0bea9b03f4dbd41a000000000000000000" +} diff --git a/ZcashLightClientKit.podspec b/ZcashLightClientKit.podspec index 907fea8d..9f9c6728 100644 --- a/ZcashLightClientKit.podspec +++ b/ZcashLightClientKit.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'ZcashLightClientKit' - s.version = '0.16.8-beta' + s.version = '0.16.9-beta' s.summary = 'Zcash Light Client wallet SDK for iOS' s.description = <<-DESC diff --git a/changelog.md b/changelog.md index 48b2a04a..935b6aec 100644 --- a/changelog.md +++ b/changelog.md @@ -1,3 +1,16 @@ +# 0.16.9-beta +Checkpoints added: +Mainnet +```` +Sources/ZcashLightClientKit/Resources/checkpoints/mainnet/1787500.json +Sources/ZcashLightClientKit/Resources/checkpoints/mainnet/1790000.json +Sources/ZcashLightClientKit/Resources/checkpoints/mainnet/1792500.json +Sources/ZcashLightClientKit/Resources/checkpoints/mainnet/1795000.json +Sources/ZcashLightClientKit/Resources/checkpoints/mainnet/1797500.json +Sources/ZcashLightClientKit/Resources/checkpoints/mainnet/1800000.json +Sources/ZcashLightClientKit/Resources/checkpoints/mainnet/1802500.json +Sources/ZcashLightClientKit/Resources/checkpoints/mainnet/1805000.json +```` # 0.16.8-beta Checkpoints added: Mainnet From 2b65bd46e467cd13ad0cfbcfcdd432efba9726f7 Mon Sep 17 00:00:00 2001 From: Lukas Korba Date: Thu, 1 Sep 2022 14:58:41 +0200 Subject: [PATCH 2/2] [476] CompactBlockProcessor to async/await - getting rid of the Operation Queue - the cleanup is needed - the update of tests is needed - tested and it successfully finishes the sync process [476] CompactBlockProcessor to async/await - old processNewBlocks() removed [476] CompactBlockProcessor to async/await - unused operations removed [476] CompactBlockProcessor to async/await - unit tests update [476] CompactBlockProcessor to async/await - unit tests refactored [476] CompactBlockProcessor to async/await - cleanup of deprecated method [476] CompactBlockProcessor to async/await - fail(error) was called even for canceled tasks but that must be excluded [476] CompactBlockProcessor to async/await - removal of all ZcashOperations from the code (unit test will follow) [476] CompactBlockProcessor to async/await - network tests in building and success order again [476] CompactBlockProcessor to async/await - offline tests in building and success order [476] CompactBlockProcessor to async/await (519) - cleanup of suspending the task [476] CompactBlockProcessor to async/await (519) - most comments resolved [476] CompactBlockProcessor to async/await (519) - thread safe state for both sync and async context [476] CompactBlockProcessor to async/await (519) - fixed build for a sample project [476] CompactBlockProcessor to async/await (519) - func testStartNotifiesSuscriptors() reverted [476] CompactBlockProcessor to async/await (519) - TODO added to track why we used NSLock instead of an Actor - Task priority enhanced [476] CompactBlockProcessor to async/await (519) - cleanup in Tasks and priorities --- .../SyncBlocksViewController.swift | 8 +- .../Processor/CompactBlockDownload.swift | 160 ++++++ .../CompactBlockDownloadOperation.swift | 318 ------------ .../Processor/CompactBlockEnhancement.swift | 119 +++++ .../CompactBlockEnhancementOperation.swift | 175 ------- .../Processor/CompactBlockProcessor.swift | 476 ++++-------------- .../Processor/CompactBlockScanning.swift | 183 +++++++ .../CompactBlockScanningOperation.swift | 252 ---------- .../CompactBlockValidationInformation.swift | 98 ++-- .../Processor/FetchUnspentTxOutputs.swift | 82 +++ .../FetchUnspentTxOutputsOperation.swift | 126 ----- .../Block/Processor/FigureNextBatch.swift | 34 ++ .../Processor/FigureNextBatchOperation.swift | 58 --- .../Block/Processor/ZcashOperation.swift | 68 --- .../Service/LightWalletGRPCService.swift | 6 +- .../ZcashLightClientKit/Synchronizer.swift | 2 +- .../Synchronizer/SDKSynchronizer.swift | 7 +- .../BlockScanOperationTests.swift | 265 ---------- Tests/NetworkTests/BlockScanTests.swift | 195 +++++++ Tests/NetworkTests/BlockStreamingTest.swift | 172 +++---- ...erationTests.swift => DownloadTests.swift} | 38 +- .../BlockBatchValidationTests.swift | 291 +++++------ 22 files changed, 1138 insertions(+), 1995 deletions(-) create mode 100644 Sources/ZcashLightClientKit/Block/Processor/CompactBlockDownload.swift delete mode 100644 Sources/ZcashLightClientKit/Block/Processor/CompactBlockDownloadOperation.swift create mode 100644 Sources/ZcashLightClientKit/Block/Processor/CompactBlockEnhancement.swift delete mode 100644 Sources/ZcashLightClientKit/Block/Processor/CompactBlockEnhancementOperation.swift create mode 100644 Sources/ZcashLightClientKit/Block/Processor/CompactBlockScanning.swift delete mode 100644 Sources/ZcashLightClientKit/Block/Processor/CompactBlockScanningOperation.swift create mode 100644 Sources/ZcashLightClientKit/Block/Processor/FetchUnspentTxOutputs.swift delete mode 100644 Sources/ZcashLightClientKit/Block/Processor/FetchUnspentTxOutputsOperation.swift create mode 100644 Sources/ZcashLightClientKit/Block/Processor/FigureNextBatch.swift delete mode 100644 Sources/ZcashLightClientKit/Block/Processor/FigureNextBatchOperation.swift delete mode 100644 Sources/ZcashLightClientKit/Block/Processor/ZcashOperation.swift delete mode 100644 Tests/NetworkTests/BlockScanOperationTests.swift create mode 100644 Tests/NetworkTests/BlockScanTests.swift rename Tests/NetworkTests/{DownloadOperationTests.swift => DownloadTests.swift} (54%) diff --git a/Example/ZcashLightClientSample/ZcashLightClientSample/Sync Blocks/SyncBlocksViewController.swift b/Example/ZcashLightClientSample/ZcashLightClientSample/Sync Blocks/SyncBlocksViewController.swift index f3c039dd..2e7760c2 100644 --- a/Example/ZcashLightClientSample/ZcashLightClientSample/Sync Blocks/SyncBlocksViewController.swift +++ b/Example/ZcashLightClientSample/ZcashLightClientSample/Sync Blocks/SyncBlocksViewController.swift @@ -31,7 +31,7 @@ class SyncBlocksViewController: UIViewController { // swiftlint:disable:next force_try try! wallet.initialize() processor = CompactBlockProcessor(initializer: wallet) - statusLabel.text = textFor(state: processor?.state ?? .stopped) + statusLabel.text = textFor(state: processor?.state.getState() ?? .stopped) progressBar.progress = 0 NotificationCenter.default.addObserver( @@ -70,7 +70,7 @@ class SyncBlocksViewController: UIViewController { @IBAction func startStop() { guard let processor = processor else { return } - switch processor.state { + switch processor.state.getState() { case .stopped: startProcessor() default: @@ -92,7 +92,7 @@ class SyncBlocksViewController: UIViewController { func stopProcessor() { guard let processor = processor else { return } - processor.stop(cancelTasks: true) + processor.stop() updateUI() } @@ -114,7 +114,7 @@ class SyncBlocksViewController: UIViewController { } func updateUI() { - guard let state = processor?.state else { return } + guard let state = processor?.state.getState() else { return } statusLabel.text = textFor(state: state) startPause.setTitle(buttonText(for: state), for: .normal) diff --git a/Sources/ZcashLightClientKit/Block/Processor/CompactBlockDownload.swift b/Sources/ZcashLightClientKit/Block/Processor/CompactBlockDownload.swift new file mode 100644 index 00000000..58eefb02 --- /dev/null +++ b/Sources/ZcashLightClientKit/Block/Processor/CompactBlockDownload.swift @@ -0,0 +1,160 @@ +// +// CompactBlockDownload.swift +// ZcashLightClientKit +// +// Created by Francisco Gindre on 10/16/19. +// Copyright © 2019 Electric Coin Company. All rights reserved. +// + +import Foundation + +extension CompactBlockProcessor { + func compactBlockStreamDownload( + blockBufferSize: Int, + startHeight: BlockHeight? = nil, + targetHeight: BlockHeight? = nil + ) async throws { + try Task.checkCancellation() + + setState(.downloading) + + var buffer: [ZcashCompactBlock] = [] + var targetHeightInternal: BlockHeight? + + do { + targetHeightInternal = targetHeight + if targetHeight == nil { + targetHeightInternal = try await service.latestBlockHeightAsync() + } + guard let latestHeight = targetHeightInternal else { + throw LightWalletServiceError.generalError(message: "missing target height on compactBlockStreamDownload") + } + let latestDownloaded = try await storage.latestHeightAsync() + let startHeight = max(startHeight ?? BlockHeight.empty(), latestDownloaded) + + let stream = service.blockStream( + startHeight: startHeight, + endHeight: latestHeight + ) + + for try await zcashCompactBlock in stream { + buffer.append(zcashCompactBlock) + if buffer.count >= blockBufferSize { + // TODO: writeAsync doesn't make sense here, awaiting it or calling blocking API have the same result and impact + try storage.write(blocks: buffer) + buffer.removeAll(keepingCapacity: true) + } + + let progress = BlockProgress( + startHeight: startHeight, + targetHeight: latestHeight, + progressHeight: zcashCompactBlock.height + ) + notifyProgress(.download(progress)) + } + // TODO: writeAsync doesn't make sense here, awaiting it or calling blocking API have the same result and impact + try storage.write(blocks: buffer) + buffer.removeAll(keepingCapacity: true) + } catch { + guard let err = error as? LightWalletServiceError, case .userCancelled = err else { + throw error + } + } + } +} + +extension CompactBlockProcessor { + func compactBlockDownload( + downloader: CompactBlockDownloading, + range: CompactBlockRange + ) async throws { + try Task.checkCancellation() + + do { + try await downloader.downloadBlockRangeAsync(range) + } catch { + throw error + } + } +} + +extension CompactBlockProcessor { + enum CompactBlockBatchDownloadError: Error { + case startHeightMissing + case batchDownloadFailed(range: CompactBlockRange, error: Error?) + } + + func compactBlockBatchDownload( + range: CompactBlockRange, + batchSize: Int = 100, + maxRetries: Int = 5 + ) async throws { + try Task.checkCancellation() + + var startHeight = range.lowerBound + let targetHeight = range.upperBound + + do { + let localDownloadedHeight = try await self.storage.latestHeightAsync() + + if localDownloadedHeight != BlockHeight.empty() && localDownloadedHeight > startHeight { + LoggerProxy.warn("provided startHeight (\(startHeight)) differs from local latest downloaded height (\(localDownloadedHeight))") + startHeight = localDownloadedHeight + 1 + } + + var currentHeight = startHeight + notifyProgress( + .download( + BlockProgress( + startHeight: currentHeight, + targetHeight: targetHeight, + progressHeight: currentHeight + ) + ) + ) + + while !Task.isCancelled && currentHeight <= targetHeight { + var retries = 0 + var success = true + var localError: Error? + + let range = CompactBlockRange(uncheckedBounds: (lower: currentHeight, upper: min(currentHeight + batchSize, targetHeight))) + + repeat { + do { + let stream: AsyncThrowingStream = service.blockRange(range) + + var blocks: [ZcashCompactBlock] = [] + for try await compactBlock in stream { + blocks.append(compactBlock) + } + try storage.insert(blocks) + success = true + } catch { + success = false + localError = error + retries += 1 + } + } while !Task.isCancelled && !success && retries < maxRetries + + if retries >= maxRetries { + throw CompactBlockBatchDownloadError.batchDownloadFailed(range: range, error: localError) + } + + notifyProgress( + .download( + BlockProgress( + startHeight: startHeight, + targetHeight: targetHeight, + progressHeight: range.upperBound + ) + ) + ) + + currentHeight = range.upperBound + 1 + } + } catch { + throw error + } + } +} diff --git a/Sources/ZcashLightClientKit/Block/Processor/CompactBlockDownloadOperation.swift b/Sources/ZcashLightClientKit/Block/Processor/CompactBlockDownloadOperation.swift deleted file mode 100644 index 61d1734c..00000000 --- a/Sources/ZcashLightClientKit/Block/Processor/CompactBlockDownloadOperation.swift +++ /dev/null @@ -1,318 +0,0 @@ -// -// CompactBlockDownloadOperation.swift -// ZcashLightClientKit -// -// Created by Francisco Gindre on 10/16/19. -// Copyright © 2019 Electric Coin Company. All rights reserved. -// - -import Foundation - -class CompactBlockDownloadOperation: ZcashOperation { - override var isConcurrent: Bool { false } - override var isAsynchronous: Bool { false } - - private var downloader: CompactBlockDownloading - private var range: CompactBlockRange - private var cancelableTask: Task? - private var done = false - - required init(downloader: CompactBlockDownloading, range: CompactBlockRange) { - self.range = range - self.downloader = downloader - super.init() - self.name = "Download Operation: \(range)" - } - - override func main() { - guard !shouldCancel() else { - cancel() - return - } - self.startedHandler?() - - cancelableTask = Task { - do { - try await downloader.downloadBlockRangeAsync(range) - self.done = true - } catch { - self.fail(error: error) - } - } - - while !done && !isCancelled { - sleep(1) - } - } - - override func fail(error: Error? = nil) { - self.cancelableTask?.cancel() - super.fail(error: error) - } - - override func cancel() { - self.cancelableTask?.cancel() - super.cancel() - } -} - -protocol CompactBlockProgressDelegate: AnyObject { - func progressUpdated(_ progress: CompactBlockProgress) -} - -class CompactBlockStreamDownloadOperation: ZcashOperation { - enum CompactBlockStreamDownloadOperationError: Error { - case startHeightMissing - } - - override var isConcurrent: Bool { false } - override var isAsynchronous: Bool { false } - - private var storage: CompactBlockStorage - private var service: LightWalletService - private var done = false - private var cancelableTask: Task? - private var startHeight: BlockHeight? - private var targetHeight: BlockHeight? - private var blockBufferSize: Int - private var buffer: [ZcashCompactBlock] = [] - - private weak var progressDelegate: CompactBlockProgressDelegate? - - /// Creates an Compact Block Stream Download Operation Operation - /// - Parameters: - /// - service: instance that conforms to `LightWalletService` - /// - storage: instance that conforms to `CompactBlockStorage` - /// - blockBufferSize: the number of blocks that the stream downloader will store in memory - /// before writing them to disk. Making this number smaller makes the downloader easier on RAM - /// memory while being less efficient on disk writes. Making it bigger takes up more RAM memory - /// but is less straining on Disk Writes. Too little or too big buffer will make this less efficient. - /// - startHeight: the height this downloader will start downloading from. If `nil`, - /// it will start from the latest height found on the local cacheDb - /// - targetHeight: the upper bound for this stream download. If `nil`, the - /// streamer will call `service.latestBlockHeight()` - /// - progressDelegate: Optional delegate to report ongoing progress conforming to - /// `CompactBlockProgressDelegate` - /// - required init( - service: LightWalletService, - storage: CompactBlockStorage, - blockBufferSize: Int, - startHeight: BlockHeight? = nil, - targetHeight: BlockHeight? = nil, - progressDelegate: CompactBlockProgressDelegate? = nil - ) { - self.storage = storage - self.service = service - self.startHeight = startHeight - self.targetHeight = targetHeight - self.progressDelegate = progressDelegate - self.blockBufferSize = blockBufferSize - super.init() - self.name = "Download Stream Operation" - } - - override func main() { - guard !shouldCancel() else { - cancel() - return - } - self.startedHandler?() - - cancelableTask = Task { - do { - if self.targetHeight == nil { - self.targetHeight = try await service.latestBlockHeightAsync() - } - guard let latestHeight = self.targetHeight else { - throw LightWalletServiceError.generalError(message: "missing target height on block stream operation") - } - let latestDownloaded = try await storage.latestHeightAsync() - let startHeight = max(self.startHeight ?? BlockHeight.empty(), latestDownloaded) - - let stream = service.blockStream( - startHeight: startHeight, - endHeight: latestHeight - ) - - for try await zcashCompactBlock in stream { - try self.cache(zcashCompactBlock, flushCache: false) - let progress = BlockProgress( - startHeight: startHeight, - targetHeight: latestHeight, - progressHeight: zcashCompactBlock.height - ) - self.progressDelegate?.progressUpdated(.download(progress)) - } - try self.flush() - self.done = true - } catch { - if let err = error as? LightWalletServiceError, case .userCancelled = err { - self.done = true - } else { - self.fail(error: error) - } - } - } - - while !done && !isCancelled { - sleep(1) - } - } - - override func fail(error: Error? = nil) { - self.cancelableTask?.cancel() - super.fail(error: error) - } - - override func cancel() { - self.cancelableTask?.cancel() - super.cancel() - } - - func cache(_ block: ZcashCompactBlock, flushCache: Bool) throws { - self.buffer.append(block) - - if flushCache || buffer.count >= blockBufferSize { - try flush() - } - } - - func flush() throws { - try self.storage.write(blocks: self.buffer) - self.buffer.removeAll(keepingCapacity: true) - } -} - -class CompactBlockBatchDownloadOperation: ZcashOperation { - enum CompactBlockBatchDownloadOperationError: Error { - case startHeightMissing - case batchDownloadFailed(range: CompactBlockRange, error: Error?) - } - - override var isConcurrent: Bool { false } - override var isAsynchronous: Bool { false } - - private var batch: Int - private var done = false - private var maxRetries: Int - private var storage: CompactBlockStorage - private var service: LightWalletService - private var cancelableTask: Task? - private var startHeight: BlockHeight - private var targetHeight: BlockHeight - - private weak var progressDelegate: CompactBlockProgressDelegate? - - required init( - service: LightWalletService, - storage: CompactBlockStorage, - startHeight: BlockHeight, - targetHeight: BlockHeight, - batchSize: Int = 100, - maxRetries: Int = 5, - progressDelegate: CompactBlockProgressDelegate? = nil - ) { - self.storage = storage - self.service = service - self.startHeight = startHeight - self.targetHeight = targetHeight - self.progressDelegate = progressDelegate - self.batch = batchSize - self.maxRetries = maxRetries - super.init() - self.name = "Download Batch Operation" - } - - override func main() { - guard !shouldCancel() else { - cancel() - return - } - self.startedHandler?() - - cancelableTask = Task { - do { - let localDownloadedHeight = try await self.storage.latestHeightAsync() - - if localDownloadedHeight != BlockHeight.empty() && localDownloadedHeight > startHeight { - LoggerProxy.warn("provided startHeight (\(startHeight)) differs from local latest downloaded height (\(localDownloadedHeight))") - startHeight = localDownloadedHeight + 1 - } - - var currentHeight = startHeight - self.progressDelegate?.progressUpdated( - .download( - BlockProgress( - startHeight: currentHeight, - targetHeight: targetHeight, - progressHeight: currentHeight - ) - ) - ) - - while !isCancelled && currentHeight <= targetHeight { - var retries = 0 - var success = true - var localError: Error? - - let range = nextRange(currentHeight: currentHeight, targetHeight: targetHeight) - - repeat { - do { - let stream: AsyncThrowingStream = service.blockRange(range) - - var blocks: [ZcashCompactBlock] = [] - for try await compactBlock in stream { - blocks.append(compactBlock) - } - try storage.insert(blocks) - success = true - } catch { - success = false - localError = error - retries += 1 - } - } while !isCancelled && !success && retries < maxRetries - - if retries >= maxRetries { - throw CompactBlockBatchDownloadOperationError.batchDownloadFailed(range: range, error: localError) - } - - self.progressDelegate?.progressUpdated( - .download( - BlockProgress( - startHeight: startHeight, - targetHeight: targetHeight, - progressHeight: range.upperBound - ) - ) - ) - - currentHeight = range.upperBound + 1 - } - self.done = true - } catch { - self.fail(error: error) - } - } - - while !done && !isCancelled { - sleep(1) - } - } - - override func fail(error: Error? = nil) { - self.cancelableTask?.cancel() - super.fail(error: error) - } - - override func cancel() { - self.cancelableTask?.cancel() - super.cancel() - } - - func nextRange(currentHeight: BlockHeight, targetHeight: BlockHeight) -> CompactBlockRange { - CompactBlockRange(uncheckedBounds: (lower: currentHeight, upper: min(currentHeight + batch, targetHeight))) - } -} diff --git a/Sources/ZcashLightClientKit/Block/Processor/CompactBlockEnhancement.swift b/Sources/ZcashLightClientKit/Block/Processor/CompactBlockEnhancement.swift new file mode 100644 index 00000000..29b921ea --- /dev/null +++ b/Sources/ZcashLightClientKit/Block/Processor/CompactBlockEnhancement.swift @@ -0,0 +1,119 @@ +// +// CompactBlockEnhancement.swift +// ZcashLightClientKit +// +// Created by Francisco Gindre on 4/10/20. +// + +import Foundation + +extension CompactBlockProcessor { + enum EnhancementError: Error { + case noRawData(message: String) + case unknownError + case decryptError(error: Error) + case txIdNotFound(txId: Data) + } + + private func enhance(transaction: TransactionEntity) async throws -> ConfirmedTransactionEntity { + LoggerProxy.debug("Zoom.... Enhance... Tx: \(transaction.transactionId.toHexStringTxId())") + + let transaction = try await downloader.fetchTransactionAsync(txId: transaction.transactionId) + + let transactionID = transaction.transactionId.toHexStringTxId() + let block = String(describing: transaction.minedHeight) + LoggerProxy.debug("Decrypting and storing transaction id: \(transactionID) block: \(block)") + + guard let rawBytes = transaction.raw?.bytes else { + let error = EnhancementError.noRawData( + message: "Critical Error: transaction id: \(transaction.transactionId.toHexStringTxId()) has no data" + ) + LoggerProxy.error("\(error)") + throw error + } + + guard let minedHeight = transaction.minedHeight else { + let error = EnhancementError.noRawData( + message: "Critical Error - Attempt to decrypt and store an unmined transaction. Id: \(transaction.transactionId.toHexStringTxId())" + ) + LoggerProxy.error("\(error)") + throw error + } + + guard rustBackend.decryptAndStoreTransaction(dbData: config.dataDb, txBytes: rawBytes, minedHeight: Int32(minedHeight), networkType: config.network.networkType) else { + if let rustError = rustBackend.lastError() { + throw EnhancementError.decryptError(error: rustError) + } + throw EnhancementError.unknownError + } + guard let confirmedTx = try transactionRepository.findConfirmedTransactionBy(rawId: transaction.transactionId) else { + throw EnhancementError.txIdNotFound(txId: transaction.transactionId) + } + return confirmedTx + } + + func compactBlockEnhancement(range: CompactBlockRange) async throws { + try Task.checkCancellation() + + LoggerProxy.debug("Started Enhancing range: \(range)") + setState(.enhancing) + + let blockRange = range.blockRange() + var retries = 0 + let maxRetries = 5 + + // fetch transactions + do { + guard let transactions = try transactionRepository.findTransactions(in: blockRange, limit: Int.max), !transactions.isEmpty else { + LoggerProxy.debug("no transactions detected on range: \(blockRange.printRange)") + return + } + + for index in 0 ..< transactions.count { + let transaction = transactions[index] + var retry = true + + while retry && retries < maxRetries { + try Task.checkCancellation() + do { + let confirmedTx = try await enhance(transaction: transaction) + retry = false + notifyProgress( + .enhance( + EnhancementStreamProgress( + totalTransactions: transactions.count, + enhancedTransactions: index + 1, + lastFoundTransaction: confirmedTx, + range: range + ) + ) + ) + } catch { + retries += 1 + LoggerProxy.error("could not enhance txId \(transaction.transactionId.toHexStringTxId()) - Error: \(error)") + if retries > maxRetries { + throw error + } + } + } + } + } catch { + LoggerProxy.error("error enhancing transactions! \(error)") + throw error + } + + if let foundTxs = try? transactionRepository.findConfirmedTransactions(in: blockRange, offset: 0, limit: Int.max) { + notifyTransactions(foundTxs, in: blockRange) + } + + if Task.isCancelled { + LoggerProxy.debug("Warning: compactBlockEnhancement on range \(range) cancelled") + } + } +} + +private extension BlockRange { + var printRange: String { + "\(self.start.height) ... \(self.end.height)" + } +} diff --git a/Sources/ZcashLightClientKit/Block/Processor/CompactBlockEnhancementOperation.swift b/Sources/ZcashLightClientKit/Block/Processor/CompactBlockEnhancementOperation.swift deleted file mode 100644 index 1d19fca7..00000000 --- a/Sources/ZcashLightClientKit/Block/Processor/CompactBlockEnhancementOperation.swift +++ /dev/null @@ -1,175 +0,0 @@ -// -// CompactBlockEnhancementOperation.swift -// ZcashLightClientKit -// -// Created by Francisco Gindre on 4/10/20. -// - -import Foundation - -class CompactBlockEnhancementOperation: ZcashOperation { - enum EnhancementError: Error { - case noRawData(message: String) - case unknownError - case decryptError(error: Error) - case txIdNotFound(txId: Data) - } - - override var isConcurrent: Bool { false } - override var isAsynchronous: Bool { false } - - var rustBackend: ZcashRustBackendWelding.Type - var txFoundHandler: (([ConfirmedTransactionEntity], BlockRange) -> Void)? - var downloader: CompactBlockDownloading - var repository: TransactionRepository - var range: BlockRange - var maxRetries: Int = 5 - var retries: Int = 0 - - private(set) var network: NetworkType - private weak var progressDelegate: CompactBlockProgressDelegate? - private var dataDb: URL - private var cancelableTask: Task? - private var done = false - - init( - rustWelding: ZcashRustBackendWelding.Type, - dataDb: URL, - downloader: CompactBlockDownloading, - repository: TransactionRepository, - range: BlockRange, - networkType: NetworkType, - progressDelegate: CompactBlockProgressDelegate? = nil - ) { - rustBackend = rustWelding - self.dataDb = dataDb - self.downloader = downloader - self.repository = repository - self.range = range - self.progressDelegate = progressDelegate - self.network = networkType - - super.init() - } - - override func main() { - guard !shouldCancel() else { - cancel() - return - } - - self.startedHandler?() - - cancelableTask = Task { - // fetch transactions - do { - guard let transactions = try repository.findTransactions(in: self.range, limit: Int.max), !transactions.isEmpty else { - LoggerProxy.debug("no transactions detected on range: \(range.printRange)") - return - } - - for index in 0 ..< transactions.count { - let transaction = transactions[index] - var retry = true - - while retry && self.retries < maxRetries { - do { - let confirmedTx = try await enhance(transaction: transaction) - retry = false - self.reportProgress( - totalTransactions: transactions.count, - enhanced: index + 1, - txEnhanced: confirmedTx - ) - } catch { - self.retries += 1 - LoggerProxy.error("could not enhance txId \(transaction.transactionId.toHexStringTxId()) - Error: \(error)") - if retries > maxRetries { - throw error - } - } - } - } - } catch { - LoggerProxy.error("error enhancing transactions! \(error)") - self.fail(error: error) - return - } - - if let handler = self.txFoundHandler, let foundTxs = try? repository.findConfirmedTransactions(in: self.range, offset: 0, limit: Int.max) { - handler(foundTxs, self.range) - } - self.done = true - } - - while !done && !isCancelled { - sleep(1) - } - } - - func reportProgress(totalTransactions: Int, enhanced: Int, txEnhanced: ConfirmedTransactionEntity) { - self.progressDelegate?.progressUpdated( - .enhance( - EnhancementStreamProgress( - totalTransactions: totalTransactions, - enhancedTransactions: enhanced, - lastFoundTransaction: txEnhanced, - range: self.range.compactBlockRange - ) - ) - ) - } - - func enhance(transaction: TransactionEntity) async throws -> ConfirmedTransactionEntity { - LoggerProxy.debug("Zoom.... Enhance... Tx: \(transaction.transactionId.toHexStringTxId())") - - let transaction = try await downloader.fetchTransactionAsync(txId: transaction.transactionId) - - let transactionID = transaction.transactionId.toHexStringTxId() - let block = String(describing: transaction.minedHeight) - LoggerProxy.debug("Decrypting and storing transaction id: \(transactionID) block: \(block)") - - guard let rawBytes = transaction.raw?.bytes else { - let error = EnhancementError.noRawData( - message: "Critical Error: transaction id: \(transaction.transactionId.toHexStringTxId()) has no data" - ) - LoggerProxy.error("\(error)") - throw error - } - - guard let minedHeight = transaction.minedHeight else { - let error = EnhancementError.noRawData( - message: "Critical Error - Attempt to decrypt and store an unmined transaction. Id: \(transaction.transactionId.toHexStringTxId())" - ) - LoggerProxy.error("\(error)") - throw error - } - - guard rustBackend.decryptAndStoreTransaction(dbData: dataDb, txBytes: rawBytes, minedHeight: Int32(minedHeight), networkType: network) else { - if let rustError = rustBackend.lastError() { - throw EnhancementError.decryptError(error: rustError) - } - throw EnhancementError.unknownError - } - guard let confirmedTx = try self.repository.findConfirmedTransactionBy(rawId: transaction.transactionId) else { - throw EnhancementError.txIdNotFound(txId: transaction.transactionId) - } - return confirmedTx - } - - override func fail(error: Error? = nil) { - self.cancelableTask?.cancel() - super.fail(error: error) - } - - override func cancel() { - self.cancelableTask?.cancel() - super.cancel() - } -} - -private extension BlockRange { - var printRange: String { - "\(self.start.height) ... \(self.end.height)" - } -} diff --git a/Sources/ZcashLightClientKit/Block/Processor/CompactBlockProcessor.swift b/Sources/ZcashLightClientKit/Block/Processor/CompactBlockProcessor.swift index 73990491..d2767c80 100644 --- a/Sources/ZcashLightClientKit/Block/Processor/CompactBlockProcessor.swift +++ b/Sources/ZcashLightClientKit/Block/Processor/CompactBlockProcessor.swift @@ -30,6 +30,7 @@ public enum CompactBlockProcessorError: Error { case wrongConsensusBranchId(expectedLocally: ConsensusBranchID, found: ConsensusBranchID) case networkMismatch(expected: NetworkType, found: NetworkType) case saplingActivationMismatch(expected: BlockHeight, found: BlockHeight) + case unknown } /** @@ -311,11 +312,30 @@ public class CompactBlockProcessor { case synced } - public private(set) var state: State = .stopped { - didSet { - transitionState(from: oldValue, to: self.state) + // TODO: this isn't an Actor even though it looks like a good candidate, the reason: + // `state` lives in both sync and async environments. An Actor is demanding async context only + // so we can't take the advantage unless we encapsulate all `state` reads/writes to async context. + // Therefore solution with class + lock works for us butr eventually will be replaced. + // The future of CompactBlockProcessor is an actor (we won't need to encapsulate the state separately), issue 523, + // https://github.com/zcash/ZcashLightClientKit/issues/523 + public class ThreadSafeState { + private var state: State = .stopped + let lock = NSLock() + + func setState(_ newState: State) { + lock.lock() + defer { lock.unlock() } + state = newState + } + + public func getState() -> State { + lock.lock() + defer { lock.unlock() } + return state } } + + public internal(set) var state = ThreadSafeState() var config: Configuration { willSet { @@ -328,7 +348,7 @@ public class CompactBlockProcessor { } var shouldStart: Bool { - switch self.state { + switch self.state.getState() { case .stopped, .synced, .error: return !maxAttemptsReached default: @@ -336,19 +356,19 @@ public class CompactBlockProcessor { } } - private var service: LightWalletService + var service: LightWalletService private(set) var downloader: CompactBlockDownloading - private var storage: CompactBlockStorage - private var transactionRepository: TransactionRepository - private var accountRepository: AccountRepository - private var rustBackend: ZcashRustBackendWelding.Type + var storage: CompactBlockStorage + var transactionRepository: TransactionRepository + var accountRepository: AccountRepository + var rustBackend: ZcashRustBackendWelding.Type private var retryAttempts: Int = 0 private var backoffTimer: Timer? private var lowerBoundHeight: BlockHeight? private var latestBlockHeight: BlockHeight private var lastChainValidationFailure: BlockHeight? private var consecutiveChainValidationErrors: Int = 0 - private var processingError: Error? + var processingError: Error? private var foundBlocks = false private var maxAttempts: Int { config.retries @@ -358,13 +378,7 @@ public class CompactBlockProcessor { BlockHeight(self.config.downloadBatchSize) } - private var operationQueue: OperationQueue = { - let queue = OperationQueue() - queue.name = "CompactBlockProcessorQueue" - queue.maxConcurrentOperationCount = 1 - return queue - }() - + private var cancelableTask: Task? /// Initializes a CompactBlockProcessor instance /// - Parameters: @@ -431,7 +445,13 @@ public class CompactBlockProcessor { } deinit { - self.operationQueue.cancelAllOperations() + cancelableTask?.cancel() + } + + func setState(_ newState: State) { + let oldValue = state.getState() + state.setState(newState) + transitionState(from: oldValue, to: newState) } static func validateServerInfo( @@ -487,19 +507,14 @@ public class CompactBlockProcessor { self.backoffTimer?.invalidate() self.backoffTimer = nil } - guard !operationQueue.isSuspended else { - LoggerProxy.debug("restarting suspended queue") - operationQueue.isSuspended = false - return - } guard shouldStart else { - switch self.state { + switch self.state.getState() { case .error(let e): // max attempts have been reached LoggerProxy.info("max retry attempts reached with error: \(e)") notifyError(CompactBlockProcessorError.maxAttemptsReached(attempts: self.maxAttempts)) - self.state = .stopped + setState(.stopped) case .stopped: // max attempts have been reached LoggerProxy.info("max retry attempts reached") @@ -514,7 +529,7 @@ public class CompactBlockProcessor { return } - self.nextBatchTask() + self.nextBatch() } /** @@ -523,18 +538,14 @@ public class CompactBlockProcessor { Note: retry count is reset - Parameter cancelTasks: cancel the pending tasks. Defaults to true */ - public func stop(cancelTasks: Bool = true) { + public func stop() { self.backoffTimer?.invalidate() self.backoffTimer = nil - if cancelTasks { - operationQueue.cancelAllOperations() - } else { - self.operationQueue.isSuspended = true - } + cancelableTask?.cancel() self.retryAttempts = 0 - self.state = .stopped + setState(.stopped) } /** @@ -577,7 +588,7 @@ public class CompactBlockProcessor { - Throws CompactBlockProcessorError.invalidConfiguration if block height is invalid or if processor is already started */ func setStartHeight(_ startHeight: BlockHeight) throws { - guard self.state == .stopped, startHeight >= config.network.constants.saplingActivationHeight else { + guard self.state.getState() == .stopped, startHeight >= config.network.constants.saplingActivationHeight else { throw CompactBlockProcessorError.invalidConfiguration } @@ -612,243 +623,34 @@ public class CompactBlockProcessor { }) } - /** - processes new blocks on the given range based on the configuration set for this instance - the way operations are queued is implemented based on the following good practice https://forums.developer.apple.com/thread/25761 - - */ - // swiftlint:disable cyclomatic_complexity + + /// Processes new blocks on the given range based on the configuration set for this instance func processNewBlocks(range: CompactBlockRange) { self.foundBlocks = true self.backoffTimer?.invalidate() self.backoffTimer = nil - let cfg = self.config - let downloadBlockOperation = CompactBlockStreamDownloadOperation( - service: self.service, - storage: self.storage, - blockBufferSize: self.config.downloadBufferSize, - startHeight: range.lowerBound, - targetHeight: range.upperBound, - progressDelegate: self - ) - - downloadBlockOperation.startedHandler = { [weak self] in - DispatchQueue.main.async { - self?.state = .downloading - } - } - - downloadBlockOperation.errorHandler = { [weak self] error in - DispatchQueue.main.async { - guard let self = self else { return } - self.processingError = error - self.fail(error) - } - } - - let validateChainOperation = CompactBlockValidationOperation( - rustWelding: self.rustBackend, - cacheDb: cfg.cacheDb, - dataDb: cfg.dataDb, - networkType: self.config.network.networkType - ) - - let downloadValidateAdapterOperation = BlockOperation { [weak validateChainOperation, weak downloadBlockOperation] in - validateChainOperation?.error = downloadBlockOperation?.error - } - - validateChainOperation.completionHandler = { [weak self] _, cancelled in - guard !cancelled else { - DispatchQueue.main.async { - self?.state = .stopped - LoggerProxy.debug("Warning: validateChainOperation operation cancelled") - } - return - } - - LoggerProxy.debug("validateChainFinished") - } - - validateChainOperation.errorHandler = { [weak self] error in - DispatchQueue.main.async { [weak self] in - guard let self = self else { return } - - guard let validationError = error as? CompactBlockValidationError else { - LoggerProxy.error("Warning: validateChain operation returning generic error: \(error)") - return - } - - switch validationError { - case .validationFailed(let height): - LoggerProxy.debug("chain validation at height: \(height)") - self.validationFailed(at: height) - case .failedWithError(let e): - guard let validationFailure = e else { - LoggerProxy.error("validation failed without a specific error") - self.fail(CompactBlockProcessorError.generalError(message: "validation failed without a specific error")) - return - } + cancelableTask = Task(priority: .userInitiated) { + do { + try await compactBlockStreamDownload( + blockBufferSize: config.downloadBufferSize, + startHeight: range.lowerBound, + targetHeight: range.upperBound + ) + try await compactBlockValidation() + try await compactBlockBatchScanning(range: range) + try await compactBlockEnhancement(range: range) + try await fetchUnspentTxOutputs(range: range) + } catch { + if error is CancellationError { - self.fail(validationFailure) + } + + if !(Task.isCancelled) { + fail(error) } } } - - validateChainOperation.startedHandler = { [weak self] in - DispatchQueue.main.async { [weak self] in - self?.state = .validating - } - } - - let scanBlocksOperation = CompactBlockBatchScanningOperation( - rustWelding: rustBackend, - cacheDb: config.cacheDb, - dataDb: config.dataDb, - transactionRepository: transactionRepository, - range: range, - batchSize: UInt32(self.config.scanningBatchSize), - networkType: self.config.network.networkType, - progressDelegate: self - ) - - let validateScanningAdapterOperation = BlockOperation { [weak scanBlocksOperation, weak validateChainOperation] in - scanBlocksOperation?.error = validateChainOperation?.error - } - - scanBlocksOperation.startedHandler = { [weak self] in - DispatchQueue.main.async { [weak self] in - self?.state = .scanning - } - } - - scanBlocksOperation.completionHandler = { [weak self] _, cancelled in - guard !cancelled else { - DispatchQueue.main.async { [weak self] in - self?.state = .stopped - LoggerProxy.debug("Warning: scanBlocksOperation operation cancelled") - } - return - } - } - - scanBlocksOperation.errorHandler = { [weak self] error in - DispatchQueue.main.async { [weak self] in - guard let self = self else { return } - self.processingError = error - self.fail(error) - } - } - - let enhanceOperation = CompactBlockEnhancementOperation( - rustWelding: rustBackend, - dataDb: config.dataDb, - downloader: downloader, - repository: transactionRepository, - range: range.blockRange(), - networkType: self.config.network.networkType - ) - - enhanceOperation.startedHandler = { - LoggerProxy.debug("Started Enhancing range: \(range)") - DispatchQueue.main.async { [weak self] in - self?.state = .enhancing - } - } - - enhanceOperation.txFoundHandler = { [weak self] txs, range in - self?.notifyTransactions(txs, in: range) - } - - enhanceOperation.completionHandler = { _, cancelled in - guard !cancelled else { - LoggerProxy.debug("Warning: enhance operation on range \(range) cancelled") - return - } - } - - enhanceOperation.errorHandler = { [weak self] error in - DispatchQueue.main.async { [weak self] in - guard let self = self else { return } - - self.processingError = error - self.fail(error) - } - } - - let scanEnhanceAdapterOperation = BlockOperation { [weak enhanceOperation, weak scanBlocksOperation] in - enhanceOperation?.error = scanBlocksOperation?.error - } - - let fetchOperation = FetchUnspentTxOutputsOperation( - accountRepository: accountRepository, - downloader: self.downloader, - rustbackend: rustBackend, - dataDb: config.dataDb, - startHeight: config.walletBirthday, - networkType: self.config.network.networkType - ) - - fetchOperation.startedHandler = { [weak self] in - DispatchQueue.main.async { [weak self] in - self?.state = .fetching - } - } - - fetchOperation.completionHandler = { [weak self] _, cancelled in - guard !cancelled else { - LoggerProxy.debug("Warning: fetch operation on range \(range) cancelled") - return - } - DispatchQueue.main.async { [weak self] in - self?.processBatchFinished(range: range) - } - } - - fetchOperation.errorHandler = { [weak self] error in - DispatchQueue.main.async { [weak self] in - guard let self = self else { return } - - self.processingError = error - self.fail(error) - } - } - - fetchOperation.fetchedUTXOsHandler = { result in - NotificationCenter.default.post( - name: .blockProcessorStoredUTXOs, - object: self, - userInfo: [CompactBlockProcessorNotificationKey.refreshedUTXOs: result] - ) - } - - let enhanceFetchAdapterOperation = BlockOperation { [weak fetchOperation, weak enhanceOperation] in - fetchOperation?.error = enhanceOperation?.error - } - - downloadValidateAdapterOperation.addDependency(downloadBlockOperation) - validateChainOperation.addDependency(downloadValidateAdapterOperation) - validateScanningAdapterOperation.addDependency(validateChainOperation) - scanBlocksOperation.addDependency(validateScanningAdapterOperation) - scanEnhanceAdapterOperation.addDependency(scanBlocksOperation) - enhanceOperation.addDependency(scanEnhanceAdapterOperation) - enhanceFetchAdapterOperation.addDependency(enhanceOperation) - fetchOperation.addDependency(enhanceFetchAdapterOperation) - - operationQueue.addOperations( - [ - downloadBlockOperation, - downloadValidateAdapterOperation, - validateChainOperation, - validateScanningAdapterOperation, - scanBlocksOperation, - scanEnhanceAdapterOperation, - enhanceOperation, - enhanceFetchAdapterOperation, - fetchOperation - ], - waitUntilFinished: false - ) } func calculateProgress(start: BlockHeight, current: BlockHeight, latest: BlockHeight) -> Float { @@ -892,35 +694,35 @@ public class CompactBlockProcessor { } func severeFailure(_ error: Error) { - operationQueue.cancelAllOperations() + cancelableTask?.cancel() LoggerProxy.error("show stoppper failure: \(error)") self.backoffTimer?.invalidate() self.retryAttempts = config.retries self.processingError = error - self.state = .error(error) + setState(.error(error)) self.notifyError(error) } func fail(_ error: Error) { // todo specify: failure LoggerProxy.error("\(error)") - operationQueue.cancelAllOperations() + cancelableTask?.cancel() self.retryAttempts += 1 self.processingError = error - switch self.state { + switch self.state.getState() { case .error: notifyError(error) default: break } - self.state = .error(error) + setState(.error(error)) guard self.maxAttemptsReached else { return } // don't set a new timer if there are no more attempts. self.setTimer() } func retryProcessing(range: CompactBlockRange) { - operationQueue.cancelAllOperations() + cancelableTask?.cancel() // update retries self.retryAttempts += 1 self.processingError = nil @@ -935,7 +737,7 @@ public class CompactBlockProcessor { // process next batch // processNewBlocks(range: Self.nextBatchBlockRange(latestHeight: latestBlockHeight, latestDownloadedHeight: try downloader.lastDownloadedBlockHeight(), walletBirthday: config.walletBirthday)) - nextBatchTask() + nextBatch() } catch { self.fail(error) } @@ -972,48 +774,9 @@ public class CompactBlockProcessor { } } - @available(*, deprecated, message: "This static method will be removed soon, use `nextBatchTask()` instead.") private func nextBatch() { - self.state = .downloading - NextStateHelper.nextState( - service: self.service, - downloader: self.downloader, - config: self.config, - rustBackend: self.rustBackend, - queue: nil - ) { result in - DispatchQueue.main.async { [weak self] in - guard let self = self else { return } - switch result { - case .success(let nextState): - switch nextState { - case .finishProcessing(let height): - self.latestBlockHeight = height - self.processingFinished(height: height) - case .processNewBlocks(let range): - self.latestBlockHeight = range.upperBound - self.lowerBoundHeight = range.lowerBound - self.processNewBlocks(range: range) - case let .wait(latestHeight, latestDownloadHeight): - // Lightwalletd might be syncing - self.lowerBoundHeight = latestDownloadHeight - self.latestBlockHeight = latestHeight - LoggerProxy.info( - "Lightwalletd might be syncing: latest downloaded block height is: \(latestDownloadHeight)" + - "while latest blockheight is reported at: \(latestHeight)" - ) - self.processingFinished(height: latestDownloadHeight) - } - case .failure(let error): - self.severeFailure(error) - } - } - } - } - - private func nextBatchTask() { - self.state = .downloading - Task { [self] in + setState(.downloading) + Task { @MainActor [self] in do { let nextState = try await NextStateHelper.nextStateAsync( service: self.service, @@ -1045,9 +808,9 @@ public class CompactBlockProcessor { } } - private func validationFailed(at height: BlockHeight) { + internal func validationFailed(at height: BlockHeight) { // cancel all Tasks - operationQueue.cancelAllOperations() + cancelableTask?.cancel() // register latest failure self.lastChainValidationFailure = height @@ -1078,13 +841,13 @@ public class CompactBlockProcessor { ) // process next batch - self.nextBatchTask() + self.nextBatch() } catch { self.fail(error) } } - private func processBatchFinished(range: CompactBlockRange) { + internal func processBatchFinished(range: CompactBlockRange) { guard processingError == nil else { retryProcessing(range: range) return @@ -1098,7 +861,7 @@ public class CompactBlockProcessor { return } - nextBatchTask() + nextBatch() } private func processingFinished(height: BlockHeight) { @@ -1110,7 +873,7 @@ public class CompactBlockProcessor { CompactBlockProcessorNotificationKey.foundBlocks: self.foundBlocks ] ) - self.state = .synced + setState(.synced) setTimer() } @@ -1297,33 +1060,27 @@ extension UnifiedAddressShim: UnifiedAddress { } extension CompactBlockProcessor { - func refreshUTXOs(tAddress: String, startHeight: BlockHeight, result: @escaping (Result) -> Void) { + func refreshUTXOs(tAddress: String, startHeight: BlockHeight) async throws -> RefreshedUTXOs { let dataDb = self.config.dataDb - self.downloader.fetchUnspentTransactionOutputs(tAddress: tAddress, startHeight: startHeight) { [weak self] fetchResult in - switch fetchResult { - case .success(let utxos): - DispatchQueue.main.async { - self?.operationQueue.addOperation { [self] in - guard let self = self else { return } - do { - guard try self.rustBackend.clearUtxos( - dbData: dataDb, - address: tAddress, - sinceHeight: startHeight - 1, - networkType: self.config.network.networkType - ) >= 0 else { - result(.failure(CompactBlockProcessorError.generalError(message: "attempted to clear utxos but -1 was returned"))) - return - } - } catch { - result(.failure(self.mapError(error))) - } - result(.success(self.storeUTXOs(utxos, in: dataDb))) - } - } - case .failure(let error): - result(.failure(self?.mapError(error) ?? error)) + + let stream: AsyncThrowingStream = downloader.fetchUnspentTransactionOutputs(tAddress: tAddress, startHeight: startHeight) + var utxos: [UnspentTransactionOutputEntity] = [] + + do { + for try await utxo in stream { + utxos.append(utxo) } + guard try rustBackend.clearUtxos( + dbData: dataDb, + address: tAddress, + sinceHeight: startHeight - 1, + networkType: self.config.network.networkType + ) >= 0 else { + throw CompactBlockProcessorError.generalError(message: "attempted to clear utxos but -1 was returned") + } + return storeUTXOs(utxos, in: dataDb) + } catch { + throw mapError(error) } } @@ -1386,6 +1143,7 @@ extension CompactBlockProcessorError: LocalizedError { case let .wrongConsensusBranchId(expectedLocally, found): // swiftlint:disable:next line_length return "The remote server you are connecting to is publishing a different branch ID \(found) than the one your App is expecting to be (\(expectedLocally)). This could be caused by your App being out of date or the server you are connecting you being either on a different network or out of date after a network upgrade." + case .unknown: return "Unknown error occured." } } @@ -1405,12 +1163,6 @@ extension CompactBlockProcessorError: LocalizedError { } } -extension CompactBlockProcessor: CompactBlockProgressDelegate { - func progressUpdated(_ progress: CompactBlockProgress) { - notifyProgress(progress) - } -} - extension CompactBlockProcessor: EnhancementStreamDelegate { func transactionEnhancementProgressUpdated(_ progress: EnhancementProgress) { NotificationCenter.default.post( @@ -1423,38 +1175,12 @@ extension CompactBlockProcessor: EnhancementStreamDelegate { extension CompactBlockProcessor { enum NextStateHelper { - // swiftlint:disable:next function_parameter_count - static func nextState( - service: LightWalletService, - downloader: CompactBlockDownloading, - config: Configuration, - rustBackend: ZcashRustBackendWelding.Type, - queue: DispatchQueue?, - result: @escaping (Result) -> Void - ) { - let dispatchQueue = queue ?? DispatchQueue.global(qos: .userInitiated) - - dispatchQueue.async { - do { - let nextResult = try self.nextState( - service: service, - downloader: downloader, - config: config, - rustBackend: rustBackend - ) - result(.success(nextResult)) - } catch { - result(.failure(error)) - } - } - } - static func nextStateAsync( service: LightWalletService, downloader: CompactBlockDownloading, config: Configuration, rustBackend: ZcashRustBackendWelding.Type - ) async throws -> FigureNextBatchOperation.NextState { + ) async throws -> NextState { let task = Task(priority: .userInitiated) { // TODO: refactor to async call, issue 463, PR 493 // https://github.com/zcash/ZcashLightClientKit/issues/463 @@ -1473,7 +1199,7 @@ extension CompactBlockProcessor { downloader: CompactBlockDownloading, config: Configuration, rustBackend: ZcashRustBackendWelding.Type - ) throws -> FigureNextBatchOperation.NextState { + ) throws -> NextState { let info = try service.getInfo() try CompactBlockProcessor.validateServerInfo( diff --git a/Sources/ZcashLightClientKit/Block/Processor/CompactBlockScanning.swift b/Sources/ZcashLightClientKit/Block/Processor/CompactBlockScanning.swift new file mode 100644 index 00000000..f85f1b8d --- /dev/null +++ b/Sources/ZcashLightClientKit/Block/Processor/CompactBlockScanning.swift @@ -0,0 +1,183 @@ +// +// CompactBlockProcessing.swift +// ZcashLightClientKit +// +// Created by Francisco Gindre on 10/15/19. +// Copyright © 2019 Electric Coin Company. All rights reserved. +// + +import Foundation + +extension CompactBlockProcessor { + func compactBlockBatchScanning(range: CompactBlockRange) async throws { + try Task.checkCancellation() + + setState(.scanning) + let batchSize = UInt32(config.scanningBatchSize) + + do { + if batchSize == 0 { + let scanStartTime = Date() + guard self.rustBackend.scanBlocks(dbCache: config.cacheDb, dbData: config.dataDb, limit: batchSize, networkType: config.network.networkType) else { + let error: Error = rustBackend.lastError() ?? CompactBlockProcessorError.unknown + LoggerProxy.debug("block scanning failed with error: \(String(describing: error))") + throw error + } + let scanFinishTime = Date() + NotificationCenter.default.post( + SDKMetrics.progressReportNotification( + progress: BlockProgress( + startHeight: range.lowerBound, + targetHeight: range.upperBound, + progressHeight: range.upperBound + ), + start: scanStartTime, + end: scanFinishTime, + task: .scanBlocks + ) + ) + let seconds = scanFinishTime.timeIntervalSinceReferenceDate - scanStartTime.timeIntervalSinceReferenceDate + LoggerProxy.debug("Scanned \(range.count) blocks in \(seconds) seconds") + } else { + let scanStartHeight = try transactionRepository.lastScannedHeight() + let targetScanHeight = range.upperBound + + var scannedNewBlocks = false + var lastScannedHeight = scanStartHeight + + repeat { + try Task.checkCancellation() + + let previousScannedHeight = lastScannedHeight + let scanStartTime = Date() + guard self.rustBackend.scanBlocks( + dbCache: config.cacheDb, + dbData: config.dataDb, + limit: batchSize, + networkType: config.network.networkType + ) else { + let error: Error = rustBackend.lastError() ?? CompactBlockProcessorError.unknown + LoggerProxy.debug("block scanning failed with error: \(String(describing: error))") + throw error + } + let scanFinishTime = Date() + + lastScannedHeight = try transactionRepository.lastScannedHeight() + + scannedNewBlocks = previousScannedHeight != lastScannedHeight + if scannedNewBlocks { + let progress = BlockProgress(startHeight: scanStartHeight, targetHeight: targetScanHeight, progressHeight: lastScannedHeight) + notifyProgress(.scan(progress)) + NotificationCenter.default.post( + SDKMetrics.progressReportNotification( + progress: progress, + start: scanStartTime, + end: scanFinishTime, + task: .scanBlocks + ) + ) + + let heightCount = lastScannedHeight - previousScannedHeight + let seconds = scanFinishTime.timeIntervalSinceReferenceDate - scanStartTime.timeIntervalSinceReferenceDate + LoggerProxy.debug("Scanned \(heightCount) blocks in \(seconds) seconds") + } + } while !Task.isCancelled && scannedNewBlocks && lastScannedHeight < targetScanHeight + if Task.isCancelled { + setState(.stopped) + LoggerProxy.debug("Warning: compactBlockBatchScanning cancelled") + } + } + } catch { + LoggerProxy.debug("block scanning failed with error: \(String(describing: error))") + throw error + } + } +} + +extension CompactBlockProcessor { + func compactBlockScanning( + rustWelding: ZcashRustBackendWelding.Type, + cacheDb: URL, + dataDb: URL, + limit: UInt32 = 0, + networkType: NetworkType + ) throws { + try Task.checkCancellation() + + guard rustBackend.scanBlocks(dbCache: cacheDb, dbData: dataDb, limit: limit, networkType: networkType) else { + let error: Error = rustBackend.lastError() ?? CompactBlockProcessorError.unknown + LoggerProxy.debug("block scanning failed with error: \(String(describing: error))") + throw error + } + } +} + +public enum SDKMetrics { + struct BlockMetricReport { + var startHeight: BlockHeight + var targetHeight: BlockHeight + var duration: TimeInterval + var task: TaskReported + } + + enum TaskReported: String { + case scanBlocks + } + + static let startBlockHeightKey = "SDKMetrics.startBlockHeightKey" + static let targetBlockHeightKey = "SDKMetrics.targetBlockHeightKey" + static let progressHeightKey = "SDKMetrics.progressHeight" + static let startDateKey = "SDKMetrics.startDateKey" + static let endDateKey = "SDKMetrics.endDateKey" + static let taskReportedKey = "SDKMetrics.taskReported" + static let notificationName = Notification.Name("SDKMetrics.Notification") + + static func blockReportFromNotification(_ notification: Notification) -> BlockMetricReport? { + guard + notification.name == notificationName, + let info = notification.userInfo, + let startHeight = info[startBlockHeightKey] as? BlockHeight, + let targetHeight = info[targetBlockHeightKey] as? BlockHeight, + let task = info[taskReportedKey] as? TaskReported, + let startDate = info[startDateKey] as? Date, + let endDate = info[endDateKey] as? Date + else { + return nil + } + + return BlockMetricReport( + startHeight: startHeight, + targetHeight: targetHeight, + duration: abs( + startDate.timeIntervalSinceReferenceDate - endDate.timeIntervalSinceReferenceDate + ), + task: task + ) + } + + static func progressReportNotification( + progress: BlockProgress, + start: Date, + end: Date, + task: SDKMetrics.TaskReported + ) -> Notification { + var notification = Notification(name: notificationName) + notification.userInfo = [ + startBlockHeightKey: progress.startHeight, + targetBlockHeightKey: progress.targetHeight, + progressHeightKey: progress.progressHeight, + startDateKey: start, + endDateKey: end, + taskReportedKey: task + ] + + return notification + } +} + +extension String.StringInterpolation { + mutating func appendInterpolation(_ value: SDKMetrics.BlockMetricReport) { + let literal = "\(value.task) - \(abs(value.startHeight - value.targetHeight)) processed on \(value.duration) seconds" + appendLiteral(literal) + } +} diff --git a/Sources/ZcashLightClientKit/Block/Processor/CompactBlockScanningOperation.swift b/Sources/ZcashLightClientKit/Block/Processor/CompactBlockScanningOperation.swift deleted file mode 100644 index 5afc17d3..00000000 --- a/Sources/ZcashLightClientKit/Block/Processor/CompactBlockScanningOperation.swift +++ /dev/null @@ -1,252 +0,0 @@ -// -// CompactBlockProcessingOperation.swift -// ZcashLightClientKit -// -// Created by Francisco Gindre on 10/15/19. -// Copyright © 2019 Electric Coin Company. All rights reserved. -// - -import Foundation - -class CompactBlockScanningOperation: ZcashOperation { - override var isConcurrent: Bool { false } - - override var isAsynchronous: Bool { false } - - var rustBackend: ZcashRustBackendWelding.Type - - private var cacheDb: URL - private var dataDb: URL - private var limit: UInt32 - private var network: NetworkType - init(rustWelding: ZcashRustBackendWelding.Type, cacheDb: URL, dataDb: URL, limit: UInt32 = 0, networkType: NetworkType) { - rustBackend = rustWelding - self.cacheDb = cacheDb - self.dataDb = dataDb - self.limit = limit - self.network = networkType - super.init() - } - - override func main() { - guard !shouldCancel() else { - cancel() - return - } - self.startedHandler?() - guard self.rustBackend.scanBlocks(dbCache: self.cacheDb, dbData: self.dataDb, limit: limit, networkType: network) else { - self.error = self.rustBackend.lastError() ?? ZcashOperationError.unknown - LoggerProxy.debug("block scanning failed with error: \(String(describing: self.error))") - self.fail() - return - } - } -} - -public enum SDKMetrics { - struct BlockMetricReport { - var startHeight: BlockHeight - var targetHeight: BlockHeight - var duration: TimeInterval - var task: TaskReported - } - - enum TaskReported: String { - case scanBlocks - } - - static let startBlockHeightKey = "SDKMetrics.startBlockHeightKey" - static let targetBlockHeightKey = "SDKMetrics.targetBlockHeightKey" - static let progressHeightKey = "SDKMetrics.progressHeight" - static let startDateKey = "SDKMetrics.startDateKey" - static let endDateKey = "SDKMetrics.endDateKey" - static let taskReportedKey = "SDKMetrics.taskReported" - static let notificationName = Notification.Name("SDKMetrics.Notification") - - static func blockReportFromNotification(_ notification: Notification) -> BlockMetricReport? { - guard - notification.name == notificationName, - let info = notification.userInfo, - let startHeight = info[startBlockHeightKey] as? BlockHeight, - let targetHeight = info[targetBlockHeightKey] as? BlockHeight, - let task = info[taskReportedKey] as? TaskReported, - let startDate = info[startDateKey] as? Date, - let endDate = info[endDateKey] as? Date - else { - return nil - } - - return BlockMetricReport( - startHeight: startHeight, - targetHeight: targetHeight, - duration: abs( - startDate.timeIntervalSinceReferenceDate - endDate.timeIntervalSinceReferenceDate - ), - task: task - ) - } - - static func progressReportNotification( - progress: BlockProgress, - start: Date, - end: Date, - task: SDKMetrics.TaskReported - ) -> Notification { - var notification = Notification(name: notificationName) - notification.userInfo = [ - startBlockHeightKey: progress.startHeight, - targetBlockHeightKey: progress.targetHeight, - progressHeightKey: progress.progressHeight, - startDateKey: start, - endDateKey: end, - taskReportedKey: task - ] - - return notification - } -} - -extension String.StringInterpolation { - mutating func appendInterpolation(_ value: SDKMetrics.BlockMetricReport) { - let literal = "\(value.task) - \(abs(value.startHeight - value.targetHeight)) processed on \(value.duration) seconds" - appendLiteral(literal) - } -} - -class CompactBlockBatchScanningOperation: ZcashOperation { - override var isConcurrent: Bool { false } - override var isAsynchronous: Bool { false } - - var rustBackend: ZcashRustBackendWelding.Type - - private var cacheDb: URL - private var dataDb: URL - private var batchSize: UInt32 - private var blockRange: CompactBlockRange - private var transactionRepository: TransactionRepository - private var network: NetworkType - private var cancelableTask: Task? - private var done = false - - private weak var progressDelegate: CompactBlockProgressDelegate? - - init( - rustWelding: ZcashRustBackendWelding.Type, - cacheDb: URL, - dataDb: URL, - transactionRepository: TransactionRepository, - range: CompactBlockRange, - batchSize: UInt32, - networkType: NetworkType, - progressDelegate: CompactBlockProgressDelegate? = nil - ) { - rustBackend = rustWelding - self.cacheDb = cacheDb - self.dataDb = dataDb - self.transactionRepository = transactionRepository - self.blockRange = range - self.batchSize = batchSize - self.progressDelegate = progressDelegate - self.network = networkType - super.init() - } - - override func main() { - guard !shouldCancel() else { - cancel() - return - } - - self.startedHandler?() - - cancelableTask = Task { - do { - if batchSize == 0 { - let scanStartTime = Date() - guard self.rustBackend.scanBlocks(dbCache: self.cacheDb, dbData: self.dataDb, limit: batchSize, networkType: network) else { - self.scanFailed(self.rustBackend.lastError() ?? ZcashOperationError.unknown) - return - } - let scanFinishTime = Date() - NotificationCenter.default.post( - SDKMetrics.progressReportNotification( - progress: BlockProgress( - startHeight: self.blockRange.lowerBound, - targetHeight: self.blockRange.upperBound, - progressHeight: self.blockRange.upperBound - ), - start: scanStartTime, - end: scanFinishTime, - task: .scanBlocks - ) - ) - let seconds = scanFinishTime.timeIntervalSinceReferenceDate - scanStartTime.timeIntervalSinceReferenceDate - LoggerProxy.debug("Scanned \(blockRange.count) blocks in \(seconds) seconds") - } else { - let scanStartHeight = try transactionRepository.lastScannedHeight() - let targetScanHeight = blockRange.upperBound - - var scannedNewBlocks = false - var lastScannedHeight = scanStartHeight - - repeat { - guard !shouldCancel() else { - cancel() - return - } - let previousScannedHeight = lastScannedHeight - let scanStartTime = Date() - guard self.rustBackend.scanBlocks( - dbCache: self.cacheDb, - dbData: self.dataDb, - limit: batchSize, - networkType: network - ) else { - self.scanFailed(self.rustBackend.lastError() ?? ZcashOperationError.unknown) - return - } - let scanFinishTime = Date() - - lastScannedHeight = try transactionRepository.lastScannedHeight() - - scannedNewBlocks = previousScannedHeight != lastScannedHeight - if scannedNewBlocks { - let progress = BlockProgress(startHeight: scanStartHeight, targetHeight: targetScanHeight, progressHeight: lastScannedHeight) - progressDelegate?.progressUpdated(.scan(progress)) - NotificationCenter.default.post( - SDKMetrics.progressReportNotification( - progress: progress, - start: scanStartTime, - end: scanFinishTime, - task: .scanBlocks - ) - ) - - let heightCount = lastScannedHeight - previousScannedHeight - let seconds = scanFinishTime.timeIntervalSinceReferenceDate - scanStartTime.timeIntervalSinceReferenceDate - LoggerProxy.debug("Scanned \(heightCount) blocks in \(seconds) seconds") - } - } while !self.isCancelled && scannedNewBlocks && lastScannedHeight < targetScanHeight - self.done = true - } - } catch { - scanFailed(error) - } - } - - while !done && !isCancelled { - sleep(1) - } - } - - func scanFailed(_ error: Error) { - self.cancelableTask?.cancel() - LoggerProxy.debug("block scanning failed with error: \(String(describing: self.error))") - super.fail(error: error) - } - - override func cancel() { - self.cancelableTask?.cancel() - super.cancel() - } -} diff --git a/Sources/ZcashLightClientKit/Block/Processor/CompactBlockValidationInformation.swift b/Sources/ZcashLightClientKit/Block/Processor/CompactBlockValidationInformation.swift index b778bcdb..fccf1fec 100644 --- a/Sources/ZcashLightClientKit/Block/Processor/CompactBlockValidationInformation.swift +++ b/Sources/ZcashLightClientKit/Block/Processor/CompactBlockValidationInformation.swift @@ -8,78 +8,58 @@ import Foundation -enum CompactBlockValidationError: Error { - case validationFailed(height: BlockHeight) - case failedWithError(_ error: Error?) -} -class CompactBlockValidationOperation: ZcashOperation { - override var isConcurrent: Bool { false } - - override var isAsynchronous: Bool { false } - - var rustBackend: ZcashRustBackendWelding.Type - - private var cacheDb: URL - private var dataDb: URL - private var network: NetworkType - private var cancelableTask: Task? - private var done = false - - init( - rustWelding: ZcashRustBackendWelding.Type, - cacheDb: URL, - dataDb: URL, - networkType: NetworkType - ) { - rustBackend = rustWelding - self.cacheDb = cacheDb - self.dataDb = dataDb - self.network = networkType - super.init() +extension CompactBlockProcessor { + enum CompactBlockValidationError: Error { + case validationFailed(height: BlockHeight) + case failedWithError(_ error: Error?) } - - override func main() { - guard !shouldCancel() else { - cancel() - return - } - self.startedHandler?() + func compactBlockValidation() async throws { + try Task.checkCancellation() + + setState(.validating) - cancelableTask = Task { - let result = self.rustBackend.validateCombinedChain(dbCache: cacheDb, dbData: dataDb, networkType: self.network) - + let result = rustBackend.validateCombinedChain(dbCache: config.cacheDb, dbData: config.dataDb, networkType: config.network.networkType) + + do { switch result { case 0: let error = CompactBlockValidationError.failedWithError(rustBackend.lastError()) - self.error = error - LoggerProxy.debug("block scanning failed with error: \(String(describing: self.error))") - self.fail(error: error) + LoggerProxy.debug("block scanning failed with error: \(String(describing: error))") + throw error case ZcashRustBackendWeldingConstants.validChain: - self.done = true + if Task.isCancelled { + setState(.stopped) + LoggerProxy.debug("Warning: compactBlockValidation cancelled") + } + LoggerProxy.debug("validateChainFinished") break default: let error = CompactBlockValidationError.validationFailed(height: BlockHeight(result)) - self.error = error - LoggerProxy.debug("block scanning failed with error: \(String(describing: self.error))") - self.fail(error: error) + LoggerProxy.debug("block scanning failed with error: \(String(describing: error))") + throw error + } + } catch { + guard let validationError = error as? CompactBlockValidationError else { + LoggerProxy.error("Warning: compactBlockValidation returning generic error: \(error)") + return + } + + switch validationError { + case .validationFailed(let height): + LoggerProxy.debug("chain validation at height: \(height)") + validationFailed(at: height) + case .failedWithError(let err): + guard let validationFailure = err else { + LoggerProxy.error("validation failed without a specific error") + self.fail(CompactBlockProcessorError.generalError(message: "validation failed without a specific error")) + return + } + + throw validationFailure } } - - while !done && !isCancelled { - sleep(1) - } - } - - override func fail(error: Error? = nil) { - self.cancelableTask?.cancel() - super.fail(error: error) - } - - override func cancel() { - self.cancelableTask?.cancel() - super.cancel() } } diff --git a/Sources/ZcashLightClientKit/Block/Processor/FetchUnspentTxOutputs.swift b/Sources/ZcashLightClientKit/Block/Processor/FetchUnspentTxOutputs.swift new file mode 100644 index 00000000..2e5e5265 --- /dev/null +++ b/Sources/ZcashLightClientKit/Block/Processor/FetchUnspentTxOutputs.swift @@ -0,0 +1,82 @@ +// +// FetchUnspentTxOutputs.swift +// ZcashLightClientKit +// +// Created by Francisco Gindre on 6/2/21. +// + +import Foundation + +extension CompactBlockProcessor { + enum FetchUTXOError: Error { + case clearingFailed(_ error: Error?) + case fetchFailed(error: Error) + } + + func fetchUnspentTxOutputs(range: CompactBlockRange) async throws { + try Task.checkCancellation() + + setState(.fetching) + + do { + let tAddresses = try accountRepository.getAll().map({ $0.transparentAddress }) + do { + for tAddress in tAddresses { + guard try rustBackend.clearUtxos( + dbData: config.dataDb, + address: tAddress, + sinceHeight: config.walletBirthday - 1, + networkType: config.network.networkType + ) >= 0 else { + throw rustBackend.lastError() ?? RustWeldingError.genericError(message: "attempted to clear utxos but -1 was returned") + } + } + } catch { + throw FetchUTXOError.clearingFailed(error) + } + + var utxos: [UnspentTransactionOutputEntity] = [] + let stream: AsyncThrowingStream = downloader.fetchUnspentTransactionOutputs(tAddresses: tAddresses, startHeight: config.walletBirthday) + for try await transaction in stream { + utxos.append(transaction) + } + + var refreshed: [UnspentTransactionOutputEntity] = [] + var skipped: [UnspentTransactionOutputEntity] = [] + + for utxo in utxos { + do { + try rustBackend.putUnspentTransparentOutput( + dbData: config.dataDb, + address: utxo.address, + txid: utxo.txid.bytes, + index: utxo.index, + script: utxo.script.bytes, + value: Int64(utxo.valueZat), + height: utxo.height, + networkType: config.network.networkType + ) ? refreshed.append(utxo) : skipped.append(utxo) + } catch { + LoggerProxy.error("failed to put utxo - error: \(error)") + skipped.append(utxo) + } + } + + let result = (inserted: refreshed, skipped: skipped) + + NotificationCenter.default.post( + name: .blockProcessorStoredUTXOs, + object: self, + userInfo: [CompactBlockProcessorNotificationKey.refreshedUTXOs: result] + ) + + if Task.isCancelled { + LoggerProxy.debug("Warning: fetchUnspentTxOutputs on range \(range) cancelled") + } else { + processBatchFinished(range: range) + } + } catch { + throw error + } + } +} diff --git a/Sources/ZcashLightClientKit/Block/Processor/FetchUnspentTxOutputsOperation.swift b/Sources/ZcashLightClientKit/Block/Processor/FetchUnspentTxOutputsOperation.swift deleted file mode 100644 index 06765bc2..00000000 --- a/Sources/ZcashLightClientKit/Block/Processor/FetchUnspentTxOutputsOperation.swift +++ /dev/null @@ -1,126 +0,0 @@ -// -// FetchUnspentTxOutputsOperation.swift -// ZcashLightClientKit -// -// Created by Francisco Gindre on 6/2/21. -// - -import Foundation - -class FetchUnspentTxOutputsOperation: ZcashOperation { - enum FetchUTXOError: Error { - case clearingFailed(_ error: Error?) - case fetchFailed(error: Error) - } - - override var isConcurrent: Bool { false } - override var isAsynchronous: Bool { false } - - var fetchedUTXOsHandler: ((RefreshedUTXOs) -> Void)? - - private var accountRepository: AccountRepository - private var downloader: CompactBlockDownloading - private var rustbackend: ZcashRustBackendWelding.Type - private var startHeight: BlockHeight - private var network: NetworkType - private var dataDb: URL - private var cancelableTask: Task? - private var done = false - - init( - accountRepository: AccountRepository, - downloader: CompactBlockDownloading, - rustbackend: ZcashRustBackendWelding.Type, - dataDb: URL, - startHeight: BlockHeight, - networkType: NetworkType - ) { - self.dataDb = dataDb - self.accountRepository = accountRepository - self.downloader = downloader - self.rustbackend = rustbackend - self.startHeight = startHeight - self.network = networkType - } - - override func main() { - guard !shouldCancel() else { - cancel() - return - } - - self.startedHandler?() - - cancelableTask = Task { - do { - let tAddresses = try accountRepository.getAll().map({ $0.transparentAddress }) - do { - for tAddress in tAddresses { - guard try self.rustbackend.clearUtxos( - dbData: dataDb, - address: tAddress, - sinceHeight: startHeight - 1, - networkType: network - ) >= 0 else { - throw rustbackend.lastError() ?? RustWeldingError.genericError(message: "attempted to clear utxos but -1 was returned") - } - } - } catch { - throw FetchUTXOError.clearingFailed(error) - } - - var utxos: [UnspentTransactionOutputEntity] = [] - let stream: AsyncThrowingStream = downloader.fetchUnspentTransactionOutputs(tAddresses: tAddresses, startHeight: startHeight) - for try await transaction in stream { - utxos.append(transaction) - } - - let result = storeUTXOs(utxos, in: dataDb) - - self.fetchedUTXOsHandler?(result) - self.done = true - } catch { - self.fail(error: error) - } - } - - while !done && !isCancelled { - sleep(1) - } - } - - private func storeUTXOs(_ utxos: [UnspentTransactionOutputEntity], in dataDb: URL) -> RefreshedUTXOs { - var refreshed: [UnspentTransactionOutputEntity] = [] - var skipped: [UnspentTransactionOutputEntity] = [] - - for utxo in utxos { - do { - try self.rustbackend.putUnspentTransparentOutput( - dbData: dataDb, - address: utxo.address, - txid: utxo.txid.bytes, - index: utxo.index, - script: utxo.script.bytes, - value: Int64(utxo.valueZat), - height: utxo.height, - networkType: network - ) ? refreshed.append(utxo) : skipped.append(utxo) - } catch { - LoggerProxy.error("failed to put utxo - error: \(error)") - skipped.append(utxo) - } - } - - return (inserted: refreshed, skipped: skipped) - } - - override func fail(error: Error? = nil) { - self.cancelableTask?.cancel() - super.fail(error: error) - } - - override func cancel() { - self.cancelableTask?.cancel() - super.cancel() - } -} diff --git a/Sources/ZcashLightClientKit/Block/Processor/FigureNextBatch.swift b/Sources/ZcashLightClientKit/Block/Processor/FigureNextBatch.swift new file mode 100644 index 00000000..10fb5243 --- /dev/null +++ b/Sources/ZcashLightClientKit/Block/Processor/FigureNextBatch.swift @@ -0,0 +1,34 @@ +// +// FigureNextBatch.swift +// ZcashLightClientKit +// +// Created by Francisco Gindre on 6/17/21. +// + +import Foundation + +extension CompactBlockProcessor { + enum NextState { + case finishProcessing(height: BlockHeight) + case processNewBlocks(range: CompactBlockRange) + case wait(latestHeight: BlockHeight, latestDownloadHeight: BlockHeight) + } + + @discardableResult + func figureNextBatch( + downloader: CompactBlockDownloading + ) async throws -> NextState { + try Task.checkCancellation() + + do { + return try await CompactBlockProcessor.NextStateHelper.nextStateAsync( + service: service, + downloader: downloader, + config: config, + rustBackend: rustBackend + ) + } catch { + throw error + } + } +} diff --git a/Sources/ZcashLightClientKit/Block/Processor/FigureNextBatchOperation.swift b/Sources/ZcashLightClientKit/Block/Processor/FigureNextBatchOperation.swift deleted file mode 100644 index cecd8bf7..00000000 --- a/Sources/ZcashLightClientKit/Block/Processor/FigureNextBatchOperation.swift +++ /dev/null @@ -1,58 +0,0 @@ -// -// FigureNextBatchOperation.swift -// ZcashLightClientKit -// -// Created by Francisco Gindre on 6/17/21. -// - -import Foundation - -class FigureNextBatchOperation: ZcashOperation { - enum NextState { - case finishProcessing(height: BlockHeight) - case processNewBlocks(range: CompactBlockRange) - case wait(latestHeight: BlockHeight, latestDownloadHeight: BlockHeight) - } - - private var service: LightWalletService - private var downloader: CompactBlockDownloading - private var config: CompactBlockProcessor.Configuration - private var rustBackend: ZcashRustBackendWelding.Type - private(set) var result: NextState? - - required init( - downloader: CompactBlockDownloading, - service: LightWalletService, - config: CompactBlockProcessor.Configuration, - rustBackend: ZcashRustBackendWelding.Type - ) { - self.service = service - self.config = config - self.downloader = downloader - self.rustBackend = rustBackend - - super.init() - - self.name = "Next Batch Operation" - } - - override func main() { - guard !shouldCancel() else { - cancel() - return - } - - self.startedHandler?() - - do { - result = try CompactBlockProcessor.NextStateHelper.nextState( - service: self.service, - downloader: self.downloader, - config: self.config, - rustBackend: self.rustBackend - ) - } catch { - self.fail(error: error) - } - } -} diff --git a/Sources/ZcashLightClientKit/Block/Processor/ZcashOperation.swift b/Sources/ZcashLightClientKit/Block/Processor/ZcashOperation.swift deleted file mode 100644 index 0b5fb6f1..00000000 --- a/Sources/ZcashLightClientKit/Block/Processor/ZcashOperation.swift +++ /dev/null @@ -1,68 +0,0 @@ -// -// ZcashOperation.swift -// ZcashLightClientKit -// -// Created by Francisco Gindre on 10/27/19. -// Copyright © 2019 Electric Coin Company. All rights reserved. -// - -import Foundation - -typealias ZcashOperationCompletionBlock = (_ finished: Bool, _ cancelled: Bool) -> Void -typealias ZcashOperationStartedBlock = () -> Void -typealias ZcashOperationErrorBlock = (_ error: Error) -> Void - -enum ZcashOperationError: Error { - case unknown -} - -class ZcashOperation: Operation { - var error: Error? - var startedHandler: ZcashOperationStartedBlock? - var errorHandler: ZcashOperationErrorBlock? - var completionHandler: ZcashOperationCompletionBlock? - var handlerDispatchQueue = DispatchQueue.main - - override init() { - super.init() - - completionBlock = { [weak self] in - guard let self = self, let handler = self.completionHandler else { return } - - handler(self.isFinished, self.isCancelled) - } - } - - convenience init(completionDispatchQueue: DispatchQueue = DispatchQueue.main) { - self.init() - self.handlerDispatchQueue = completionDispatchQueue - } - - func shouldCancel() -> Bool { - self.error != nil || isCancelled || dependencyCancelled() - } - - func dependencyCancelled() -> Bool { - self.dependencies.first { $0.isCancelled } != nil - } - - func fail(error: Error? = nil) { - defer { - self.cancel() - } - - if let error = error { - self.error = error - } - LoggerProxy.debug("\(self) failed") - - guard let errorHandler = self.errorHandler else { - return - } - - self.handlerDispatchQueue.async { [weak self] in - let error = error ?? (self?.error ?? ZcashOperationError.unknown) - errorHandler(error) - } - } -} diff --git a/Sources/ZcashLightClientKit/Service/LightWalletGRPCService.swift b/Sources/ZcashLightClientKit/Service/LightWalletGRPCService.swift index 276b598a..b023a30d 100644 --- a/Sources/ZcashLightClientKit/Service/LightWalletGRPCService.swift +++ b/Sources/ZcashLightClientKit/Service/LightWalletGRPCService.swift @@ -369,7 +369,7 @@ extension LightWalletGRPCService: LightWalletServiceNonBlockingAPI { } continuation.finish(throwing: nil) } catch { - continuation.finish(throwing: error) + continuation.finish(throwing: error.mapToServiceError()) } } } @@ -551,7 +551,7 @@ extension LightWalletGRPCService: LightWalletServiceNonBlockingAPI { } continuation.finish(throwing: nil) } catch { - continuation.finish(throwing: error) + continuation.finish(throwing: error.mapToServiceError()) } } } @@ -619,7 +619,7 @@ extension LightWalletGRPCService: LightWalletServiceNonBlockingAPI { } continuation.finish(throwing: nil) } catch { - continuation.finish(throwing: error) + continuation.finish(throwing: error.mapToServiceError()) } } } diff --git a/Sources/ZcashLightClientKit/Synchronizer.swift b/Sources/ZcashLightClientKit/Synchronizer.swift index 67f11235..c7948e47 100644 --- a/Sources/ZcashLightClientKit/Synchronizer.swift +++ b/Sources/ZcashLightClientKit/Synchronizer.swift @@ -201,7 +201,7 @@ public protocol Synchronizer { /// Returns the latests UTXOs for the given address from the specified height on - func refreshUTXOs(address: String, from height: BlockHeight, result: @escaping (Result) -> Void) + func refreshUTXOs(address: String, from height: BlockHeight) async throws -> RefreshedUTXOs /// Returns the last stored unshielded balance func getTransparentBalance(accountIndex: Int) throws -> WalletBalance diff --git a/Sources/ZcashLightClientKit/Synchronizer/SDKSynchronizer.swift b/Sources/ZcashLightClientKit/Synchronizer/SDKSynchronizer.swift index 66c3e1f8..04c8e869 100644 --- a/Sources/ZcashLightClientKit/Synchronizer/SDKSynchronizer.swift +++ b/Sources/ZcashLightClientKit/Synchronizer/SDKSynchronizer.swift @@ -192,7 +192,7 @@ public class SDKSynchronizer: Synchronizer { return } - blockProcessor.stop(cancelTasks: true) + blockProcessor.stop() self.status = .stopped } @@ -668,8 +668,8 @@ public class SDKSynchronizer: Synchronizer { } } - public func refreshUTXOs(address: String, from height: BlockHeight, result: @escaping (Result) -> Void) { - self.blockProcessor.refreshUTXOs(tAddress: address, startHeight: height, result: result) + public func refreshUTXOs(address: String, from height: BlockHeight) async throws -> RefreshedUTXOs { + try await blockProcessor.refreshUTXOs(tAddress: address, startHeight: height) } @available(*, deprecated, message: "This function will be removed soon, use the one returning a `Zatoshi` value instead") public func getShieldedBalance(accountIndex: Int = 0) -> Int64 { @@ -885,6 +885,7 @@ public class SDKSynchronizer: Synchronizer { return SynchronizerError.lightwalletdValidationFailed(underlyingError: compactBlockProcessorError) case .saplingActivationMismatch: return SynchronizerError.lightwalletdValidationFailed(underlyingError: compactBlockProcessorError) + case .unknown: break } } diff --git a/Tests/NetworkTests/BlockScanOperationTests.swift b/Tests/NetworkTests/BlockScanOperationTests.swift deleted file mode 100644 index 39208a01..00000000 --- a/Tests/NetworkTests/BlockScanOperationTests.swift +++ /dev/null @@ -1,265 +0,0 @@ -// -// BlockScanOperationTests.swift -// ZcashLightClientKitTests -// -// Created by Francisco Gindre on 10/17/19. -// Copyright © 2019 Electric Coin Company. All rights reserved. -// - -import XCTest -import SQLite -@testable import TestUtils -@testable import ZcashLightClientKit - -// swiftlint:disable implicitly_unwrapped_optional force_try force_unwrapping print_function_usage -class BlockScanOperationTests: XCTestCase { - let rustWelding = ZcashRustBackend.self - - var operationQueue = OperationQueue() - var cacheDbURL: URL! - var dataDbURL: URL! - - var uvk = UVFakeKey( - extfvk: "zxviewtestsapling1qw88ayg8qqqqpqyhg7jnh9mlldejfqwu46pm40ruwstd8znq3v3l4hjf33qcu2a5e36katshcfhcxhzgyfugj2lkhmt40j45cv38rv3frnghzkxcx73k7m7afw9j7ujk7nm4dx5mv02r26umxqgar7v3x390w2h3crqqgjsjly7jy4vtwzrmustm5yudpgcydw7x78awca8wqjvkqj8p8e3ykt7lrgd7xf92fsfqjs5vegfsja4ekzpfh5vtccgvs5747xqm6qflmtqpr8s9u", // swiftlint:disable:this line_length - extpub: "02075a7f5f7507d64022dad5954849f216b0f1b09b2d588be663d8e7faeb5aaf61" - ) - - var walletBirthDay = Checkpoint.birthday( - with: 1386000, - network: ZcashNetworkBuilder.network(for: .testnet) - ) - - var network = ZcashNetworkBuilder.network(for: .testnet) - var blockRepository: BlockRepository! - - override func setUp() { - // Put setup code here. This method is called before the invocation of each test method in the class. - super.setUp() - self.cacheDbURL = try! __cacheDbURL() - self.dataDbURL = try! __dataDbURL() - - deleteDBs() - operationQueue.maxConcurrentOperationCount = 1 - } - - private func deleteDBs() { - try? FileManager.default.removeItem(at: cacheDbURL) - try? FileManager.default.removeItem(at: dataDbURL) - } - - override func tearDown() { - // Put teardown code here. This method is called after the invocation of each test method in the class. - super.tearDown() - operationQueue.cancelAllOperations() - - try? FileManager.default.removeItem(at: cacheDbURL) - try? FileManager.default.removeItem(at: dataDbURL) - } - - func testSingleDownloadAndScanOperation() { - logger = SampleLogger(logLevel: .debug) - - XCTAssertNoThrow(try rustWelding.initDataDb(dbData: dataDbURL, networkType: network.networkType)) - - let downloadStartedExpect = XCTestExpectation(description: "\(self.description) download started") - let downloadExpect = XCTestExpectation(description: "\(self.description) download") - let scanStartedExpect = XCTestExpectation(description: "\(self.description) scan started") - let scanExpect = XCTestExpectation(description: "\(self.description) scan") - let latestScannedBlockExpect = XCTestExpectation(description: "\(self.description) latestScannedHeight") - let service = LightWalletGRPCService( - endpoint: LightWalletEndpoint( - address: "lightwalletd.testnet.electriccoin.co", - port: 9067 - ) - ) - let blockCount = 100 - let range = network.constants.saplingActivationHeight ... network.constants.saplingActivationHeight + blockCount - let downloadOperation = CompactBlockDownloadOperation( - downloader: CompactBlockDownloader.sqlDownloader( - service: service, - at: cacheDbURL - )!, - range: range - ) - let scanOperation = CompactBlockScanningOperation( - rustWelding: rustWelding, - cacheDb: cacheDbURL, - dataDb: dataDbURL, - networkType: network.networkType - ) - - downloadOperation.startedHandler = { - downloadStartedExpect.fulfill() - } - - downloadOperation.completionHandler = { finished, cancelled in - downloadExpect.fulfill() - XCTAssertTrue(finished) - XCTAssertFalse(cancelled) - } - - downloadOperation.errorHandler = { error in - XCTFail("Download Operation failed with Error: \(error)") - } - - scanOperation.startedHandler = { - scanStartedExpect.fulfill() - } - - scanOperation.completionHandler = { finished, cancelled in - scanExpect.fulfill() - XCTAssertFalse(cancelled) - XCTAssertTrue(finished) - } - - scanOperation.errorHandler = { error in - XCTFail("Scan Operation failed with Error: \(error)") - } - - scanOperation.addDependency(downloadOperation) - var latestScannedheight = BlockHeight.empty() - let latestScannedBlockOperation = BlockOperation { - let repository = BlockSQLDAO(dbProvider: SimpleConnectionProvider.init(path: self.dataDbURL.absoluteString, readonly: true)) - latestScannedheight = repository.lastScannedBlockHeight() - } - - latestScannedBlockOperation.completionBlock = { - latestScannedBlockExpect.fulfill() - XCTAssertEqual(latestScannedheight, range.upperBound) - } - - latestScannedBlockOperation.addDependency(scanOperation) - - operationQueue.addOperations( - [downloadOperation, scanOperation, latestScannedBlockOperation], - waitUntilFinished: false - ) - - wait( - for: [downloadStartedExpect, downloadExpect, scanStartedExpect, scanExpect, latestScannedBlockExpect], - timeout: 10, - enforceOrder: true - ) - } - @objc func observeBenchmark(_ notification: Notification) { - guard let report = SDKMetrics.blockReportFromNotification(notification) else { - return - } - - print("observed benchmark: \(report)") - } - func testScanValidateDownload() throws { - logger = SampleLogger(logLevel: .debug) - - NotificationCenter.default.addObserver( - self, - selector: #selector(observeBenchmark(_:)), - name: SDKMetrics.notificationName, - object: nil - ) - - try self.rustWelding.initDataDb(dbData: dataDbURL, networkType: network.networkType) - - guard try self.rustWelding.initAccountsTable(dbData: self.dataDbURL, uvks: [uvk], networkType: network.networkType) else { - XCTFail("failed to init account table") - return - } - - try self.rustWelding.initBlocksTable( - dbData: dataDbURL, - height: Int32(walletBirthDay.height), - hash: walletBirthDay.hash, - time: walletBirthDay.time, - saplingTree: walletBirthDay.saplingTree, - networkType: network.networkType - ) - - let service = LightWalletGRPCService(endpoint: LightWalletEndpointBuilder.eccTestnet) - let storage = CompactBlockStorage(url: cacheDbURL, readonly: false) - try storage.createTable() - - let downloadExpectation = XCTestExpectation(description: "download expectation") - let validateExpectation = XCTestExpectation(description: "validate expectation") - let scanExpectation = XCTestExpectation(description: "scan expectation") - - let downloadOperation = CompactBlockStreamDownloadOperation( - service: service, - storage: storage, - blockBufferSize: 10, - startHeight: walletBirthDay.height, - targetHeight: walletBirthDay.height + 10000, - progressDelegate: self - ) - - downloadOperation.completionHandler = { finished, cancelled in - XCTAssert(finished) - XCTAssertFalse(cancelled) - downloadExpectation.fulfill() - } - - downloadOperation.errorHandler = { error in - if let lwdError = error as? LightWalletServiceError { - switch lwdError { - case .timeOut: - XCTAssert(true) - default: - XCTFail("LWD Service error found, but should have been a timeLimit reached Error - \(lwdError)") - } - } else { - XCTFail("Error should have been a timeLimit reached Error - \(error)") - } - } - - let validationOperation = CompactBlockValidationOperation( - rustWelding: rustWelding, - cacheDb: cacheDbURL, - dataDb: dataDbURL, - networkType: network.networkType - ) - - validationOperation.errorHandler = { error in - self.operationQueue.cancelAllOperations() - XCTFail("failed with error \(error)") - } - - validationOperation.completionHandler = { finished, cancelled in - XCTAssert(finished) - XCTAssertFalse(cancelled) - validateExpectation.fulfill() - } - - let transactionRepository = TransactionRepositoryBuilder.build(dataDbURL: dataDbURL) - let scanningOperation = CompactBlockBatchScanningOperation( - rustWelding: rustWelding, - cacheDb: cacheDbURL, - dataDb: dataDbURL, - transactionRepository: transactionRepository, - range: CompactBlockRange( - uncheckedBounds: (walletBirthDay.height, walletBirthDay.height + 10000) - ), - batchSize: 1000, - networkType: network.networkType, - progressDelegate: self - ) - - scanningOperation.completionHandler = { finished, cancelled in - XCTAssert(finished) - XCTAssertFalse(cancelled) - scanExpectation.fulfill() - } - - operationQueue.addOperations([downloadOperation, validationOperation, scanningOperation], waitUntilFinished: false) - - wait(for: [downloadExpectation, validateExpectation, scanExpectation], timeout: 300, enforceOrder: true) - } -} - -extension BlockScanOperationTests: CompactBlockProgressDelegate { - func progressUpdated(_ progress: CompactBlockProgress) { - } -} - -struct UVFakeKey: UnifiedViewingKey { - var extfvk: ExtendedFullViewingKey - var extpub: ExtendedPublicKey -} diff --git a/Tests/NetworkTests/BlockScanTests.swift b/Tests/NetworkTests/BlockScanTests.swift new file mode 100644 index 00000000..920fb0e5 --- /dev/null +++ b/Tests/NetworkTests/BlockScanTests.swift @@ -0,0 +1,195 @@ +// +// BlockScanTests.swift +// ZcashLightClientKitTests +// +// Created by Francisco Gindre on 10/17/19. +// Copyright © 2019 Electric Coin Company. All rights reserved. +// + +import XCTest +import SQLite +@testable import TestUtils +@testable import ZcashLightClientKit + +// swiftlint:disable implicitly_unwrapped_optional force_try force_unwrapping print_function_usage +class BlockScanTests: XCTestCase { + let rustWelding = ZcashRustBackend.self + + var cacheDbURL: URL! + var dataDbURL: URL! + + var uvk = UVFakeKey( + extfvk: "zxviewtestsapling1qw88ayg8qqqqpqyhg7jnh9mlldejfqwu46pm40ruwstd8znq3v3l4hjf33qcu2a5e36katshcfhcxhzgyfugj2lkhmt40j45cv38rv3frnghzkxcx73k7m7afw9j7ujk7nm4dx5mv02r26umxqgar7v3x390w2h3crqqgjsjly7jy4vtwzrmustm5yudpgcydw7x78awca8wqjvkqj8p8e3ykt7lrgd7xf92fsfqjs5vegfsja4ekzpfh5vtccgvs5747xqm6qflmtqpr8s9u", // swiftlint:disable:this line_length + extpub: "02075a7f5f7507d64022dad5954849f216b0f1b09b2d588be663d8e7faeb5aaf61" + ) + + var walletBirthDay = Checkpoint.birthday( + with: 1386000, + network: ZcashNetworkBuilder.network(for: .testnet) + ) + + var network = ZcashNetworkBuilder.network(for: .testnet) + var blockRepository: BlockRepository! + + override func setUp() { + // Put setup code here. This method is called before the invocation of each test method in the class. + super.setUp() + self.cacheDbURL = try! __cacheDbURL() + self.dataDbURL = try! __dataDbURL() + + deleteDBs() + } + + private func deleteDBs() { + try? FileManager.default.removeItem(at: cacheDbURL) + try? FileManager.default.removeItem(at: dataDbURL) + } + + override func tearDown() { + // Put teardown code here. This method is called after the invocation of each test method in the class. + super.tearDown() + + try? FileManager.default.removeItem(at: cacheDbURL) + try? FileManager.default.removeItem(at: dataDbURL) + } + + func testSingleDownloadAndScan() async throws { + logger = SampleLogger(logLevel: .debug) + + XCTAssertNoThrow(try rustWelding.initDataDb(dbData: dataDbURL, networkType: network.networkType)) + + let storage = try! TestDbBuilder.inMemoryCompactBlockStorage() + let service = LightWalletGRPCService( + endpoint: LightWalletEndpoint( + address: "lightwalletd.testnet.electriccoin.co", + port: 9067 + ) + ) + let blockCount = 100 + let range = network.constants.saplingActivationHeight ... network.constants.saplingActivationHeight + blockCount + let downloader = CompactBlockDownloader.sqlDownloader( + service: service, + at: cacheDbURL + )! + + let processorConfig = CompactBlockProcessor.Configuration.standard( + for: network, + walletBirthday: network.constants.saplingActivationHeight + ) + let compactBlockProcessor = CompactBlockProcessor( + service: service, + storage: storage, + backend: ZcashRustBackend.self, + config: processorConfig + ) + + let repository = BlockSQLDAO(dbProvider: SimpleConnectionProvider.init(path: self.dataDbURL.absoluteString, readonly: true)) + var latestScannedheight = BlockHeight.empty() + do { + try await compactBlockProcessor.compactBlockDownload( + downloader: downloader, + range: range + ) + XCTAssertFalse(Task.isCancelled) + try compactBlockProcessor.compactBlockScanning( + rustWelding: rustWelding, + cacheDb: cacheDbURL, + dataDb: dataDbURL, + networkType: network.networkType + ) + latestScannedheight = repository.lastScannedBlockHeight() + XCTAssertEqual(latestScannedheight, range.upperBound) + } catch { + XCTFail("Download failed with error: \(error)") + } + } + + @objc func observeBenchmark(_ notification: Notification) { + guard let report = SDKMetrics.blockReportFromNotification(notification) else { + return + } + + print("observed benchmark: \(report)") + } + + func testScanValidateDownload() async throws { + logger = SampleLogger(logLevel: .debug) + + NotificationCenter.default.addObserver( + self, + selector: #selector(observeBenchmark(_:)), + name: SDKMetrics.notificationName, + object: nil + ) + + try self.rustWelding.initDataDb(dbData: dataDbURL, networkType: network.networkType) + + guard try self.rustWelding.initAccountsTable(dbData: self.dataDbURL, uvks: [uvk], networkType: network.networkType) else { + XCTFail("failed to init account table") + return + } + + try self.rustWelding.initBlocksTable( + dbData: dataDbURL, + height: Int32(walletBirthDay.height), + hash: walletBirthDay.hash, + time: walletBirthDay.time, + saplingTree: walletBirthDay.saplingTree, + networkType: network.networkType + ) + + let service = LightWalletGRPCService(endpoint: LightWalletEndpointBuilder.eccTestnet) + let storage = CompactBlockStorage(url: cacheDbURL, readonly: false) + try storage.createTable() + + var processorConfig = CompactBlockProcessor.Configuration( + cacheDb: cacheDbURL, + dataDb: dataDbURL, + walletBirthday: network.constants.saplingActivationHeight, + network: network + ) + processorConfig.scanningBatchSize = 1000 + + let compactBlockProcessor = CompactBlockProcessor( + service: service, + storage: storage, + backend: rustWelding, + config: processorConfig + ) + + let range = CompactBlockRange( + uncheckedBounds: (walletBirthDay.height, walletBirthDay.height + 10000) + ) + + do { + try await compactBlockProcessor.compactBlockStreamDownload( + blockBufferSize: 10, + startHeight: range.lowerBound, + targetHeight: range.upperBound + ) + XCTAssertFalse(Task.isCancelled) + + try await compactBlockProcessor.compactBlockValidation() + XCTAssertFalse(Task.isCancelled) + + try await compactBlockProcessor.compactBlockBatchScanning(range: range) + XCTAssertFalse(Task.isCancelled) + } catch { + if let lwdError = error as? LightWalletServiceError { + switch lwdError { + case .timeOut: + XCTAssert(true) + default: + XCTFail("LWD Service error found, but should have been a timeLimit reached Error - \(lwdError)") + } + } else { + XCTFail("Error should have been a timeLimit reached Error - \(error)") + } + } + } +} + +struct UVFakeKey: UnifiedViewingKey { + var extfvk: ExtendedFullViewingKey + var extpub: ExtendedPublicKey +} diff --git a/Tests/NetworkTests/BlockStreamingTest.swift b/Tests/NetworkTests/BlockStreamingTest.swift index 57220a82..9d509a43 100644 --- a/Tests/NetworkTests/BlockStreamingTest.swift +++ b/Tests/NetworkTests/BlockStreamingTest.swift @@ -11,12 +11,6 @@ import XCTest // swiftlint:disable print_function_usage class BlockStreamingTest: XCTestCase { - var queue: OperationQueue = { - let queue = OperationQueue() - queue.maxConcurrentOperationCount = 1 - return queue - }() - override func setUpWithError() throws { try super.setUpWithError() logger = SampleLogger(logLevel: .debug) @@ -27,7 +21,7 @@ class BlockStreamingTest: XCTestCase { try? FileManager.default.removeItem(at: __dataDbURL()) } - func testStreamOperation() throws { + func testStream() throws { let expectation = XCTestExpectation(description: "blockstream expectation") let service = LightWalletGRPCService( @@ -61,9 +55,7 @@ class BlockStreamingTest: XCTestCase { wait(for: [expectation], timeout: 1000) } - func testStreamOperationCancellation() throws { - let expectation = XCTestExpectation(description: "blockstream expectation") - + func testStreamCancellation() async throws { let service = LightWalletGRPCService( host: LightWalletEndpointBuilder.eccTestnet.host, port: 9067, @@ -71,37 +63,38 @@ class BlockStreamingTest: XCTestCase { singleCallTimeout: 10000, streamingCallTimeout: 10000 ) + let storage = try TestDbBuilder.inMemoryCompactBlockStorage() - let startHeight = try service.latestBlockHeight() - 100_000 - let operation = CompactBlockStreamDownloadOperation( + let processorConfig = CompactBlockProcessor.Configuration.standard( + for: ZcashNetworkBuilder.network(for: .testnet), + walletBirthday: ZcashNetworkBuilder.network(for: .testnet).constants.saplingActivationHeight + ) + + let compactBlockProcessor = CompactBlockProcessor( service: service, storage: storage, - blockBufferSize: 10, - startHeight: startHeight, - progressDelegate: self + backend: ZcashRustBackend.self, + config: processorConfig ) - operation.completionHandler = { _, cancelled in - XCTAssert(cancelled) - expectation.fulfill() + let cancelableTask = Task { + do { + try await compactBlockProcessor.compactBlockStreamDownload( + blockBufferSize: 10, + startHeight: startHeight + ) + XCTAssertTrue(Task.isCancelled) + } catch { + XCTFail("failed with error: \(error)") + } } - operation.errorHandler = { error in - XCTFail("failed with error: \(error)") - expectation.fulfill() - } - - queue.addOperation(operation) - DispatchQueue.main.asyncAfter(deadline: .now() + 3, execute: { - self.queue.cancelAllOperations() - }) - wait(for: [expectation], timeout: 1000) + try await Task.sleep(nanoseconds: 3_000_000_000) + cancelableTask.cancel() } - func testStreamOperationTimeout() throws { - let expectation = XCTestExpectation(description: "blockstream expectation") - let errorExpectation = XCTestExpectation(description: "blockstream error expectation") + func testStreamTimeout() async throws { let service = LightWalletGRPCService( host: LightWalletEndpointBuilder.eccTestnet.host, port: 9067, @@ -109,49 +102,49 @@ class BlockStreamingTest: XCTestCase { singleCallTimeout: 1000, streamingCallTimeout: 3000 ) + let storage = try TestDbBuilder.inMemoryCompactBlockStorage() - let startHeight = try service.latestBlockHeight() - 100_000 - let operation = CompactBlockStreamDownloadOperation( + + let processorConfig = CompactBlockProcessor.Configuration.standard( + for: ZcashNetworkBuilder.network(for: .testnet), + walletBirthday: ZcashNetworkBuilder.network(for: .testnet).constants.saplingActivationHeight + ) + + let compactBlockProcessor = CompactBlockProcessor( service: service, storage: storage, - blockBufferSize: 10, - startHeight: startHeight, - progressDelegate: self + backend: ZcashRustBackend.self, + config: processorConfig ) - operation.completionHandler = { finished, _ in - XCTAssert(finished) - - expectation.fulfill() - } + let date = Date() - operation.errorHandler = { error in + do { + try await compactBlockProcessor.compactBlockStreamDownload( + blockBufferSize: 10, + startHeight: startHeight + ) + } catch { if let lwdError = error as? LightWalletServiceError { switch lwdError { case .timeOut: XCTAssert(true) default: - XCTFail("LWD Service erro found, but should have been a timeLimit reached Error") + XCTFail("LWD Service error found, but should have been a timeLimit reached Error") } } else { XCTFail("Error should have been a timeLimit reached Error") } - errorExpectation.fulfill() } - queue.addOperation(operation) - let date = Date() - wait(for: [errorExpectation], timeout: 4) let now = Date() let elapsed = now.timeIntervalSince(date) print("took \(elapsed) seconds") } - func testBatchOperation() throws { - let expectation = XCTestExpectation(description: "blockbatch expectation") - + func testBatch() async throws { let service = LightWalletGRPCService( host: LightWalletEndpointBuilder.eccTestnet.host, port: 9067, @@ -162,34 +155,29 @@ class BlockStreamingTest: XCTestCase { let storage = try TestDbBuilder.diskCompactBlockStorage(at: __dataDbURL() ) let targetHeight = try service.latestBlockHeight() let startHeight = targetHeight - 10_000 - let operation = CompactBlockBatchDownloadOperation( + + let processorConfig = CompactBlockProcessor.Configuration.standard( + for: ZcashNetworkBuilder.network(for: .testnet), + walletBirthday: ZcashNetworkBuilder.network(for: .testnet).constants.saplingActivationHeight + ) + + let compactBlockProcessor = CompactBlockProcessor( service: service, storage: storage, - startHeight: startHeight, - targetHeight: targetHeight, - progressDelegate: self + backend: ZcashRustBackend.self, + config: processorConfig ) - operation.completionHandler = { _, cancelled in - if cancelled { - XCTFail("operation cancelled") - } - expectation.fulfill() - } - - operation.errorHandler = { error in + let range = CompactBlockRange(uncheckedBounds: (startHeight, targetHeight)) + do { + try await compactBlockProcessor.compactBlockBatchDownload(range: range) + XCTAssertFalse(Task.isCancelled) + } catch { XCTFail("failed with error: \(error)") - expectation.fulfill() } - - queue.addOperation(operation) - - wait(for: [expectation], timeout: 120) } - func testBatchOperationCancellation() throws { - let expectation = XCTestExpectation(description: "blockbatch expectation") - + func testBatchCancellation() async throws { let service = LightWalletGRPCService( host: LightWalletEndpointBuilder.eccTestnet.host, port: 9067, @@ -200,36 +188,30 @@ class BlockStreamingTest: XCTestCase { let storage = try TestDbBuilder.diskCompactBlockStorage(at: __dataDbURL() ) let targetHeight = try service.latestBlockHeight() let startHeight = targetHeight - 100_000 - let operation = CompactBlockBatchDownloadOperation( + + let processorConfig = CompactBlockProcessor.Configuration.standard( + for: ZcashNetworkBuilder.network(for: .testnet), + walletBirthday: ZcashNetworkBuilder.network(for: .testnet).constants.saplingActivationHeight + ) + + let compactBlockProcessor = CompactBlockProcessor( service: service, storage: storage, - startHeight: startHeight, - targetHeight: targetHeight, - progressDelegate: self + backend: ZcashRustBackend.self, + config: processorConfig ) - operation.completionHandler = { _, cancelled in - XCTAssert(cancelled) - expectation.fulfill() + let range = CompactBlockRange(uncheckedBounds: (startHeight, targetHeight)) + let cancelableTask = Task { + do { + try await compactBlockProcessor.compactBlockBatchDownload(range: range) + XCTAssertTrue(Task.isCancelled) + } catch { + XCTFail("failed with error: \(error)") + } } - operation.errorHandler = { error in - XCTFail("failed with error: \(error)") - expectation.fulfill() - } - - queue.addOperation(operation) - DispatchQueue.main.asyncAfter(deadline: .now() + 3, execute: { - self.queue.cancelAllOperations() - }) - wait(for: [expectation], timeout: 1000) - } -} - -extension BlockStreamingTest: CompactBlockProgressDelegate { - func progressUpdated(_ progress: CompactBlockProgress) { - print("progressHeight: \(String(describing: progress.progressHeight))") - print("startHeight: \(progress.progress)") - print("targetHeight: \(String(describing: progress.targetHeight))") + try await Task.sleep(nanoseconds: 3_000_000_000) + cancelableTask.cancel() } } diff --git a/Tests/NetworkTests/DownloadOperationTests.swift b/Tests/NetworkTests/DownloadTests.swift similarity index 54% rename from Tests/NetworkTests/DownloadOperationTests.swift rename to Tests/NetworkTests/DownloadTests.swift index 34f47012..442e7d95 100644 --- a/Tests/NetworkTests/DownloadOperationTests.swift +++ b/Tests/NetworkTests/DownloadTests.swift @@ -1,5 +1,5 @@ // -// DownloadOperationTests.swift +// DownloadTests.swift // ZcashLightClientKitTests // // Created by Francisco Gindre on 10/16/19. @@ -12,40 +12,38 @@ import SQLite @testable import ZcashLightClientKit // swiftlint:disable force_try -class DownloadOperationTests: XCTestCase { - var operationQueue = OperationQueue() +class DownloadTests: XCTestCase { var network = ZcashNetworkBuilder.network(for: .testnet) override func tearDown() { super.tearDown() - operationQueue.cancelAllOperations() } - func testSingleOperation() { - let expect = XCTestExpectation(description: self.description) - + func testSingleDownload() async throws { let service = LightWalletGRPCService(endpoint: LightWalletEndpointBuilder.eccTestnet) let storage = try! TestDbBuilder.inMemoryCompactBlockStorage() let downloader = CompactBlockDownloader(service: service, storage: storage) let blockCount = 100 let activationHeight = network.constants.saplingActivationHeight let range = activationHeight ... activationHeight + blockCount - let downloadOperation = CompactBlockDownloadOperation(downloader: downloader, range: range) - downloadOperation.completionHandler = { finished, cancelled in - expect.fulfill() - XCTAssertTrue(finished) - XCTAssertFalse(cancelled) + let processorConfig = CompactBlockProcessor.Configuration.standard( + for: network, + walletBirthday: network.constants.saplingActivationHeight + ) + let compactBlockProcessor = CompactBlockProcessor( + service: service, + storage: storage, + backend: ZcashRustBackend.self, + config: processorConfig + ) + + do { + try await compactBlockProcessor.compactBlockDownload(downloader: downloader, range: range) + } catch { + XCTFail("Download failed with error: \(error)") } - downloadOperation.errorHandler = { error in - XCTFail("Donwload Operation failed with error: \(error)") - } - - operationQueue.addOperation(downloadOperation) - - wait(for: [expect], timeout: 10) - XCTAssertEqual(try! storage.latestHeight(), range.upperBound) } } diff --git a/Tests/OfflineTests/BlockBatchValidationTests.swift b/Tests/OfflineTests/BlockBatchValidationTests.swift index faea5486..ec8178eb 100644 --- a/Tests/OfflineTests/BlockBatchValidationTests.swift +++ b/Tests/OfflineTests/BlockBatchValidationTests.swift @@ -11,29 +11,13 @@ import XCTest // swiftlint:disable force_try type_body_length class BlockBatchValidationTests: XCTestCase { - var queue: OperationQueue = { - let queue = OperationQueue() - queue.name = "Test Queue" - queue.maxConcurrentOperationCount = 1 - return queue - }() - - override func setUp() { - // Put setup code here. This method is called before the invocation of each test method in the class. - super.setUp() - } - - override func tearDown() { - // Put teardown code here. This method is called after the invocation of each test method in the class. - super.tearDown() - } - - func testBranchIdFailure() throws { + func testBranchIdFailure() async throws { let network = ZcashNetworkBuilder.network(for: .mainnet) let service = MockLightWalletService( latestBlockHeight: 1210000, service: LightWalletGRPCService(endpoint: LightWalletEndpointBuilder.default) ) + let storage = try! TestDbBuilder.inMemoryCompactBlockStorage() let repository = ZcashConsoleFakeStorage(latestBlockHeight: 1220000) let downloader = CompactBlockDownloader(service: service, storage: repository) let config = CompactBlockProcessor.Configuration( @@ -59,17 +43,18 @@ class BlockBatchValidationTests: XCTestCase { let mockRust = MockRustBackend.self mockRust.consensusBranchID = Int32(0xd34d) - - let operation = FigureNextBatchOperation(downloader: downloader, service: service, config: config, rustBackend: mockRust) - let expectation = XCTestExpectation(description: "failure expectation") - let startedExpectation = XCTestExpectation(description: "start Expectation") - - operation.startedHandler = { - startedExpectation.fulfill() - } - - operation.errorHandler = { error in - expectation.fulfill() + + let compactBlockProcessor = CompactBlockProcessor( + service: service, + storage: storage, + backend: mockRust, + config: config + ) + + do { + try await compactBlockProcessor.figureNextBatch(downloader: downloader) + XCTAssertFalse(Task.isCancelled) + } catch { switch error { case CompactBlockProcessorError.wrongConsensusBranchId: break @@ -77,19 +62,15 @@ class BlockBatchValidationTests: XCTestCase { XCTFail("Expected CompactBlockProcessorError.wrongConsensusBranchId but found \(error)") } } - queue.addOperations([operation], waitUntilFinished: false) - - wait(for: [startedExpectation, expectation], timeout: 1, enforceOrder: true) - XCTAssertNotNil(operation.error) - XCTAssertTrue(operation.isCancelled) } - func testBranchNetworkMismatchFailure() throws { + func testBranchNetworkMismatchFailure() async throws { let network = ZcashNetworkBuilder.network(for: .mainnet) let service = MockLightWalletService( latestBlockHeight: 1210000, service: LightWalletGRPCService(endpoint: LightWalletEndpointBuilder.default) ) + let storage = try! TestDbBuilder.inMemoryCompactBlockStorage() let repository = ZcashConsoleFakeStorage(latestBlockHeight: 1220000) let downloader = CompactBlockDownloader(service: service, storage: repository) let config = CompactBlockProcessor.Configuration( @@ -115,17 +96,18 @@ class BlockBatchValidationTests: XCTestCase { let mockRust = MockRustBackend.self mockRust.consensusBranchID = 0xd34db4d - - let operation = FigureNextBatchOperation(downloader: downloader, service: service, config: config, rustBackend: mockRust) - let expectation = XCTestExpectation(description: "failure expectation") - let startedExpectation = XCTestExpectation(description: "start Expectation") - - operation.startedHandler = { - startedExpectation.fulfill() - } - - operation.errorHandler = { error in - expectation.fulfill() + + let compactBlockProcessor = CompactBlockProcessor( + service: service, + storage: storage, + backend: mockRust, + config: config + ) + + do { + try await compactBlockProcessor.figureNextBatch(downloader: downloader) + XCTAssertFalse(Task.isCancelled) + } catch { switch error { case CompactBlockProcessorError.networkMismatch(expected: .mainnet, found: .testnet): break @@ -133,20 +115,15 @@ class BlockBatchValidationTests: XCTestCase { XCTFail("Expected CompactBlockProcessorError.networkMismatch but found \(error)") } } - - queue.addOperations([operation], waitUntilFinished: false) - - wait(for: [startedExpectation, expectation], timeout: 1, enforceOrder: true) - XCTAssertNotNil(operation.error) - XCTAssertTrue(operation.isCancelled) } - func testBranchNetworkTypeWrongFailure() throws { + func testBranchNetworkTypeWrongFailure() async throws { let network = ZcashNetworkBuilder.network(for: .testnet) let service = MockLightWalletService( latestBlockHeight: 1210000, service: LightWalletGRPCService(endpoint: LightWalletEndpointBuilder.default) ) + let storage = try! TestDbBuilder.inMemoryCompactBlockStorage() let repository = ZcashConsoleFakeStorage(latestBlockHeight: 1220000) let downloader = CompactBlockDownloader(service: service, storage: repository) let config = CompactBlockProcessor.Configuration( @@ -172,17 +149,18 @@ class BlockBatchValidationTests: XCTestCase { let mockRust = MockRustBackend.self mockRust.consensusBranchID = 0xd34db4d - - let operation = FigureNextBatchOperation(downloader: downloader, service: service, config: config, rustBackend: mockRust) - let expectation = XCTestExpectation(description: "failure expectation") - let startedExpectation = XCTestExpectation(description: "start Expectation") - - operation.startedHandler = { - startedExpectation.fulfill() - } - - operation.errorHandler = { error in - expectation.fulfill() + + let compactBlockProcessor = CompactBlockProcessor( + service: service, + storage: storage, + backend: mockRust, + config: config + ) + + do { + try await compactBlockProcessor.figureNextBatch(downloader: downloader) + XCTAssertFalse(Task.isCancelled) + } catch { switch error { case CompactBlockProcessorError.generalError: break @@ -190,20 +168,15 @@ class BlockBatchValidationTests: XCTestCase { XCTFail("Expected CompactBlockProcessorError.generalError but found \(error)") } } - - queue.addOperations([operation], waitUntilFinished: false) - - wait(for: [startedExpectation, expectation], timeout: 1, enforceOrder: true) - XCTAssertNotNil(operation.error) - XCTAssertTrue(operation.isCancelled) } - func testSaplingActivationHeightMismatch() throws { + func testSaplingActivationHeightMismatch() async throws { let network = ZcashNetworkBuilder.network(for: .mainnet) let service = MockLightWalletService( latestBlockHeight: 1210000, service: LightWalletGRPCService(endpoint: LightWalletEndpointBuilder.default) ) + let storage = try! TestDbBuilder.inMemoryCompactBlockStorage() let repository = ZcashConsoleFakeStorage(latestBlockHeight: 1220000) let downloader = CompactBlockDownloader(service: service, storage: repository) let config = CompactBlockProcessor.Configuration( @@ -230,17 +203,18 @@ class BlockBatchValidationTests: XCTestCase { let mockRust = MockRustBackend.self mockRust.consensusBranchID = 0xd34db4d - - let operation = FigureNextBatchOperation(downloader: downloader, service: service, config: config, rustBackend: mockRust) - let expectation = XCTestExpectation(description: "failure expectation") - let startedExpectation = XCTestExpectation(description: "start Expectation") - - operation.startedHandler = { - startedExpectation.fulfill() - } - - operation.errorHandler = { error in - expectation.fulfill() + + let compactBlockProcessor = CompactBlockProcessor( + service: service, + storage: storage, + backend: mockRust, + config: config + ) + + do { + try await compactBlockProcessor.figureNextBatch(downloader: downloader) + XCTAssertFalse(Task.isCancelled) + } catch { switch error { case CompactBlockProcessorError.saplingActivationMismatch( expected: network.constants.saplingActivationHeight, @@ -251,15 +225,9 @@ class BlockBatchValidationTests: XCTestCase { XCTFail("Expected CompactBlockProcessorError.saplingActivationMismatch but found \(error)") } } - - queue.addOperations([operation], waitUntilFinished: false) - - wait(for: [startedExpectation, expectation], timeout: 1, enforceOrder: true) - XCTAssertNotNil(operation.error) - XCTAssertTrue(operation.isCancelled) } - func testResultIsWait() throws { + func testResultIsWait() async throws { let network = ZcashNetworkBuilder.network(for: .mainnet) let expectedLatestHeight = BlockHeight(1210000) @@ -268,10 +236,11 @@ class BlockBatchValidationTests: XCTestCase { service: LightWalletGRPCService(endpoint: LightWalletEndpointBuilder.default) ) let expectedStoreLatestHeight = BlockHeight(1220000) - let expectedResult = FigureNextBatchOperation.NextState.wait( + let expectedResult = CompactBlockProcessor.NextState.wait( latestHeight: expectedLatestHeight, latestDownloadHeight: expectedLatestHeight ) + let storage = try! TestDbBuilder.inMemoryCompactBlockStorage() let repository = ZcashConsoleFakeStorage(latestBlockHeight: expectedStoreLatestHeight) let downloader = CompactBlockDownloader(service: service, storage: repository) let config = CompactBlockProcessor.Configuration( @@ -298,50 +267,41 @@ class BlockBatchValidationTests: XCTestCase { let mockRust = MockRustBackend.self mockRust.consensusBranchID = 0xd34db4d - - let operation = FigureNextBatchOperation(downloader: downloader, service: service, config: config, rustBackend: mockRust) - let completedExpectation = XCTestExpectation(description: "completed expectation") - let startedExpectation = XCTestExpectation(description: "start Expectation") - - operation.startedHandler = { - startedExpectation.fulfill() - } - - operation.errorHandler = { error in + + let compactBlockProcessor = CompactBlockProcessor( + service: service, + storage: storage, + backend: mockRust, + config: config + ) + + var nextBatch: CompactBlockProcessor.NextState? + do { + nextBatch = try await compactBlockProcessor.figureNextBatch(downloader: downloader) + XCTAssertFalse(Task.isCancelled) + } catch { XCTFail("this shouldn't happen: \(error)") } - - operation.completionHandler = { finished, cancelled in - completedExpectation.fulfill() - XCTAssertTrue(finished) - XCTAssertFalse(cancelled) - } - - queue.addOperations([operation], waitUntilFinished: false) - wait(for: [startedExpectation, completedExpectation], timeout: 1, enforceOrder: true) - XCTAssertNil(operation.error) - XCTAssertFalse(operation.isCancelled) - - guard let result = operation.result else { + guard let _ = nextBatch else { XCTFail("result should not be nil") return } XCTAssertTrue( { - switch result { + switch nextBatch { case .wait(latestHeight: expectedLatestHeight, latestDownloadHeight: expectedLatestHeight): return true default: return false } }(), - "Expected \(expectedResult) got: \(result)" + "Expected \(expectedResult) got: \(String(describing: nextBatch))" ) } - func testResultProcessNew() throws { + func testResultProcessNew() async throws { let network = ZcashNetworkBuilder.network(for: .mainnet) let expectedLatestHeight = BlockHeight(1230000) let service = MockLightWalletService( @@ -350,13 +310,14 @@ class BlockBatchValidationTests: XCTestCase { ) let expectedStoreLatestHeight = BlockHeight(1220000) let walletBirthday = BlockHeight(1210000) - let expectedResult = FigureNextBatchOperation.NextState.processNewBlocks( + let expectedResult = CompactBlockProcessor.NextState.processNewBlocks( range: CompactBlockProcessor.nextBatchBlockRange( latestHeight: expectedLatestHeight, latestDownloadedHeight: expectedStoreLatestHeight, walletBirthday: walletBirthday ) ) + let storage = try! TestDbBuilder.inMemoryCompactBlockStorage() let repository = ZcashConsoleFakeStorage(latestBlockHeight: expectedStoreLatestHeight) let downloader = CompactBlockDownloader(service: service, storage: repository) let config = CompactBlockProcessor.Configuration( @@ -383,50 +344,41 @@ class BlockBatchValidationTests: XCTestCase { let mockRust = MockRustBackend.self mockRust.consensusBranchID = 0xd34db4d - - let operation = FigureNextBatchOperation(downloader: downloader, service: service, config: config, rustBackend: mockRust) - let completedExpectation = XCTestExpectation(description: "completed expectation") - let startedExpectation = XCTestExpectation(description: "start Expectation") - - operation.startedHandler = { - startedExpectation.fulfill() - } - - operation.errorHandler = { _ in - XCTFail("this shouldn't happen") - } - - operation.completionHandler = { finished, cancelled in - completedExpectation.fulfill() - XCTAssertTrue(finished) - XCTAssertFalse(cancelled) - } - - queue.addOperations([operation], waitUntilFinished: false) - wait(for: [startedExpectation, completedExpectation], timeout: 1, enforceOrder: true) - XCTAssertNil(operation.error) - XCTAssertFalse(operation.isCancelled) - - guard let result = operation.result else { + let compactBlockProcessor = CompactBlockProcessor( + service: service, + storage: storage, + backend: mockRust, + config: config + ) + + var nextBatch: CompactBlockProcessor.NextState? + do { + nextBatch = try await compactBlockProcessor.figureNextBatch(downloader: downloader) + XCTAssertFalse(Task.isCancelled) + } catch { + XCTFail("this shouldn't happen: \(error)") + } + + guard let _ = nextBatch else { XCTFail("result should not be nil") return } XCTAssertTrue( { - switch result { + switch nextBatch { case .processNewBlocks(range: CompactBlockRange(uncheckedBounds: (expectedStoreLatestHeight + 1, expectedLatestHeight))): return true default: return false } }(), - "Expected \(expectedResult) got: \(result)" + "Expected \(expectedResult) got: \(String(describing: nextBatch))" ) } - func testResultProcessorFinished() throws { + func testResultProcessorFinished() async throws { let network = ZcashNetworkBuilder.network(for: .mainnet) let expectedLatestHeight = BlockHeight(1230000) let service = MockLightWalletService( @@ -435,7 +387,8 @@ class BlockBatchValidationTests: XCTestCase { ) let expectedStoreLatestHeight = BlockHeight(1230000) let walletBirthday = BlockHeight(1210000) - let expectedResult = FigureNextBatchOperation.NextState.finishProcessing(height: expectedStoreLatestHeight) + let expectedResult = CompactBlockProcessor.NextState.finishProcessing(height: expectedStoreLatestHeight) + let storage = try! TestDbBuilder.inMemoryCompactBlockStorage() let repository = ZcashConsoleFakeStorage(latestBlockHeight: expectedStoreLatestHeight) let downloader = CompactBlockDownloader(service: service, storage: repository) let config = CompactBlockProcessor.Configuration( @@ -462,46 +415,38 @@ class BlockBatchValidationTests: XCTestCase { let mockRust = MockRustBackend.self mockRust.consensusBranchID = 0xd34db4d - - let operation = FigureNextBatchOperation(downloader: downloader, service: service, config: config, rustBackend: mockRust) - let completedExpectation = XCTestExpectation(description: "completed expectation") - let startedExpectation = XCTestExpectation(description: "start Expectation") - - operation.startedHandler = { - startedExpectation.fulfill() - } - - operation.errorHandler = { _ in - XCTFail("this shouldn't happen") - } - - operation.completionHandler = { finished, cancelled in - completedExpectation.fulfill() - XCTAssertTrue(finished) - XCTAssertFalse(cancelled) - } - - queue.addOperations([operation], waitUntilFinished: false) - wait(for: [startedExpectation, completedExpectation], timeout: 1, enforceOrder: true) - XCTAssertNil(operation.error) - XCTAssertFalse(operation.isCancelled) - - guard let result = operation.result else { + let compactBlockProcessor = CompactBlockProcessor( + service: service, + storage: storage, + backend: mockRust, + config: config + ) + + var nextBatch: CompactBlockProcessor.NextState? + do { + nextBatch = try await compactBlockProcessor.figureNextBatch(downloader: downloader) + XCTAssertFalse(Task.isCancelled) + } catch { + XCTFail("this shouldn't happen: \(error)") + } + + guard let _ = nextBatch else { XCTFail("result should not be nil") return } XCTAssertTrue( { - switch result { + + switch nextBatch { case .finishProcessing(height: expectedLatestHeight): return true default: return false } }(), - "Expected \(expectedResult) got: \(result)" + "Expected \(expectedResult) got: \(String(describing: nextBatch))" ) } }