Compare commits

...

770 Commits

Author SHA1 Message Date
voidlizard beaf9f6538 LICENSE to hbs2-storage-ncq3 2025-08-27 10:37:05 +03:00
voidlizard 65aa06bafc wip, storage audit log, disabled by default 2025-08-27 10:27:12 +03:00
voidlizard dfead1d585 wip, loosed auth time frame for Mailbox proto 2025-08-26 11:08:40 +03:00
voidlizard 63b6aa47c8 wip, fixing 2025-08-26 09:02:00 +03:00
voidlizard 9b1d1d9aa1 test sweep 2025-08-26 08:34:05 +03:00
voidlizard 82b2fa1770 wip 2025-08-26 07:32:06 +03:00
voidlizard 4b59cbceff wip 2025-08-26 07:27:21 +03:00
voidlizard 1c10e66978 version bump to 0.25.3 2025-08-26 06:01:57 +03:00
voidlizard ed44770f55 NCQv1 storage removed 2025-08-26 05:31:08 +03:00
voidlizard d04926ee05 wip, test fix 2025-08-25 12:30:23 +03:00
voidlizard 4723fc10d5 minor endurance test fix 2025-08-25 09:51:36 +03:00
voidlizard ccc2154f1e wip, ncq3 fixes + storage update 2025-08-25 07:16:36 +03:00
voidlizard 70e7639dcf lower log level for something 2025-08-24 17:01:14 +03:00
voidlizard eb6d450d95 wip 2025-08-24 16:57:56 +03:00
voidlizard 1c812e8227 wip 2025-08-24 11:19:59 +03:00
voidlizard 8374fb5a3a fixing stucked garbage problem 2025-08-24 09:01:21 +03:00
voidlizard 91a2563134 some kludges? in order file gc to work faster 2025-08-24 08:44:19 +03:00
voidlizard e36cb783c4 wip 2025-08-24 06:30:27 +03:00
voidlizard e4a7e56c7f wip 2025-08-23 22:34:51 +03:00
voidlizard 689ca15b47 wip 2025-08-23 22:20:17 +03:00
voidlizard 4e89506788 wip, idx compact debug 2025-08-23 19:37:14 +03:00
voidlizard 4897fb4efa wip, wipe code 2025-08-23 12:56:05 +03:00
voidlizard 3d9589676d wip, ncq3 maintainance tools 2025-08-23 12:26:13 +03:00
voidlizard c3f65af033 Revert "wip, mac os x shit"
This reverts commit 2ab1535802.
2025-08-22 21:22:15 +03:00
voidlizard 2ab1535802 wip, mac os x shit 2025-08-22 21:20:11 +03:00
voidlizard 8d58c5d818 wip, fixing 2025-08-22 20:03:38 +03:00
voidlizard 0869b57971 wip, fix writing tails 2025-08-22 18:59:00 +03:00
voidlizard 996be0c16c bugfix 2025-08-22 17:17:36 +03:00
voidlizard 783481af76 macos fsync issues 2025-08-22 16:14:21 +03:00
voidlizard 96726cf287 macos fsync issues 2025-08-22 15:49:52 +03:00
voidlizard 3508ad1258 macos fsync issues 2025-08-22 15:44:50 +03:00
voidlizard d79c7b89e5 switched to ncq3 2025-08-22 13:52:39 +03:00
voidlizard 051fe680be before switch to ncq3 2025-08-22 13:40:58 +03:00
voidlizard 7a357dd8c4 fixing wrong state on crash exit 2025-08-22 12:12:13 +03:00
voidlizard ac629634c0 wip 2025-08-21 16:44:10 +03:00
voidlizard 1a36018aa5 wip, NCQ3 failed 2025-08-21 15:28:23 +03:00
voidlizard 2f28d65e59 wip 2025-08-21 14:26:06 +03:00
voidlizard 789c798e7e hbs2-peer migrate from NCQv1, test 2025-08-21 13:36:14 +03:00
voidlizard 513f03eeb3 removed old storage dependency in migration 2025-08-21 12:05:42 +03:00
voidlizard 2a1260ac97 NCQv1 -> NCQ3 migration 2025-08-20 18:57:55 +03:00
voidlizard 500ad351a5 bytestring offloading 2025-08-20 13:13:25 +03:00
voidlizard 79788fd134 introduced FileLocation 2025-08-20 11:04:27 +03:00
voidlizard 4ab17008c4 endurance test + multiple fixes 2025-08-19 20:21:57 +03:00
voidlizard 421be6ec9d compact+index race/crash fixed 2025-08-19 15:20:11 +03:00
voidlizard dba8eb3464 endurance test skeleton 2025-08-18 16:48:41 +03:00
voidlizard 3067bb6e5a test description 2025-08-17 09:03:23 +03:00
voidlizard 14bc9cff1e wip, store original hash in ref value 2025-08-17 08:49:23 +03:00
voidlizard c786027f4f wip 2025-08-14 13:38:55 +03:00
voidlizard 59a54fabb8 wip, faster putBlock 2025-08-14 12:57:58 +03:00
voidlizard 56d71cb988 wip 2025-08-14 12:23:02 +03:00
voidlizard 3d7e2794d7 wip 2025-08-14 11:20:43 +03:00
voidlizard 548828e47f wip 2025-08-14 10:03:54 +03:00
voidlizard 59e9f8a718 wip, 3x speed degradation on read+write scenario against NCQ1 2025-08-14 09:53:00 +03:00
voidlizard 802f6d65f3 updated 2025-08-14 08:32:03 +03:00
voidlizard 241b6346b5 wip 2025-08-14 08:20:28 +03:00
voidlizard 6293fdebf2 wip 2025-08-14 07:46:43 +03:00
voidlizard 09ec309ade wip 2025-08-14 07:21:40 +03:00
voidlizard 91211220ba removed excess imports 2025-08-12 19:26:21 +03:00
voidlizard c7058a9b6f wip, NCQ3.NCQStorage basic test 2025-08-12 09:10:24 +03:00
voidlizard 6f3f9cae24 for-llm script to dump project files for llm 2025-08-12 09:09:59 +03:00
voidlizard 5728549a2f wip, ncq3 test 2025-08-12 05:37:00 +03:00
voidlizard 29ed5a7ecc NCQStorage3 -> NCQStorage 2025-08-01 14:39:29 +03:00
voidlizard d71a66111a lock 2025-08-01 13:05:35 +03:00
voidlizard 3c37f9e468 wiped NCQ2 2025-08-01 12:42:21 +03:00
voidlizard b18463f927 test, check entry hash on read 2025-08-01 12:29:38 +03:00
voidlizard 6c107ad99f ncqFileTryRecover 2025-08-01 12:29:38 +03:00
voidlizard 16cd0efa5b wip, ncq3 crash test 2025-08-01 12:29:38 +03:00
voidlizard f1fa32b9f8 bf6 str:cut function 2025-08-01 07:44:08 +03:00
voidlizard 617ad99912 wip, fixed merge
O(E(K)) > O(ENTRY) => skip entry
2025-07-31 16:22:51 +03:00
voidlizard bdf0395b1e wip 2025-07-31 15:22:36 +03:00
voidlizard 5afd9c6048 wip, deletion 2025-07-31 14:00:10 +03:00
voidlizard 1b003ed124 wip 2025-07-31 12:15:26 +03:00
voidlizard ced2239d53 asDouble fix 2025-07-31 07:23:04 +03:00
voidlizard 63ff57f54b wip, index => 20 bytes of payload (key:8 offset:8 size:4) + some bf6 functions 2025-07-31 07:20:37 +03:00
voidlizard e45c507f80 fix 2025-07-30 21:03:45 +03:00
voidlizard 8d7728ad71 wip 2025-07-30 20:03:55 +03:00
voidlizard a5d9f4193b wip 2025-07-30 18:37:06 +03:00
voidlizard 1f2fdde9c7 wip, ported some tests to NCQ3 2025-07-30 18:04:16 +03:00
voidlizard b57919aa85 wip, fix 2025-07-30 16:54:17 +03:00
voidlizard 6c3dc29041 wip, data file merge 2025-07-30 15:46:37 +03:00
voidlizard 88447330b6 wip, background sweep and index compaction 2025-07-30 13:06:50 +03:00
voidlizard a5dbfe5e0b wip, sweep routines 2025-07-29 18:04:08 +03:00
voidlizard 29d5025e19 wip 2025-07-29 14:40:52 +03:00
voidlizard 9e5247f19c wip 2025-07-29 14:21:24 +03:00
voidlizard d3004ad354 wip, new index layout fixed 2025-07-29 13:23:12 +03:00
voidlizard 4b003fe2ec wip 2025-07-29 11:37:40 +03:00
voidlizard 7365aa3813 wip 2025-07-28 16:47:38 +03:00
voidlizard a8051ca302 wip 2025-07-28 15:07:52 +03:00
voidlizard fd19634bd1 wip 2025-07-28 13:05:18 +03:00
voidlizard a1992fbda2 wip 2025-07-28 11:42:24 +03:00
voidlizard 71ab399cd4 wip 2025-07-28 11:07:10 +03:00
voidlizard af41c701a0 wip 2025-07-28 10:38:34 +03:00
voidlizard 0ac052c634 wip 2025-07-23 15:00:59 +03:00
voidlizard c67ffc2679 wip 2025-07-23 14:26:24 +03:00
voidlizard 1f589cfe55 wip 2025-07-23 12:36:22 +03:00
voidlizard 33d17f01ed test harness for probes db 2025-07-23 12:36:22 +03:00
voidlizard e8d019eaa2 wip 2025-07-23 12:36:22 +03:00
voidlizard 39ac3e8832 wip 2025-07-23 12:36:22 +03:00
voidlizard 0c71a7dab0 wip, wtf 2025-07-23 12:36:22 +03:00
voidlizard e51b72d57c wip, fixed test a little bit 2025-07-23 12:36:22 +03:00
voidlizard 6fb8d808a0 wip 2025-07-21 20:43:06 +03:00
voidlizard 6e23bad397 scan-for-tombs 2025-07-21 20:38:43 +03:00
voidlizard d05166d5a1 wip 2025-07-21 18:02:23 +03:00
voidlizard 77ee8410cc wip wtf 2025-07-21 12:45:46 +03:00
voidlizard e08f68fbaf wip 2025-07-21 11:02:39 +03:00
voidlizard ba0a631ee2 wip 2025-07-21 09:10:21 +03:00
voidlizard 07c20a78eb wip 2025-07-21 06:03:48 +03:00
voidlizard 55e96e79ea wip, fossil && state gc 2025-07-14 08:25:44 +03:00
voidlizard a87b2a11fa wip, race fixed? 2025-07-13 17:15:59 +03:00
voidlizard 15bec48522 wip, trying PendingEntry state 2025-07-13 16:56:01 +03:00
voidlizard a49ee574de wip 2025-07-13 16:20:33 +03:00
voidlizard 263347f9fc wip 2025-07-13 09:04:56 +03:00
voidlizard 2f77530e60 wip, return merge 2025-07-13 08:44:01 +03:00
voidlizard 249f402368 wip 2025-07-13 07:34:51 +03:00
voidlizard 51be225fc1 deferred rm no go 2025-07-11 20:37:40 +03:00
voidlizard 2807f326c6 deferred rm, sux 2025-07-11 20:35:11 +03:00
voidlizard 4f5842ef7f wip 2025-07-11 15:06:30 +03:00
voidlizard 9b123448f4 wip, minor optimization 2025-07-11 14:38:40 +03:00
voidlizard 2219171ca8 wip, minor optimization 2025-07-11 14:36:22 +03:00
voidlizard 8da69dc38e parallel lookup attempt; does not work. ncqLocateMt 2025-07-11 14:06:19 +03:00
voidlizard f6b756fd31 wip, minor fix 2025-07-11 13:16:08 +03:00
voidlizard ec7eea1ac6 wip, merge loop 2025-07-11 12:54:38 +03:00
voidlizard e5b4b27901 wip 2025-07-11 12:37:13 +03:00
voidlizard 930c824dbb wtf? 2025-07-11 10:34:52 +03:00
voidlizard d1aa0a6f2c wip 2025-07-11 07:17:30 +03:00
voidlizard d69a2d7595 wip 2025-07-11 07:10:13 +03:00
voidlizard 955fb65dce wip 2025-07-10 19:07:40 +03:00
voidlizard f79236bc3f wip 2025-07-10 07:05:22 +03:00
voidlizard b36cd7f667 wip 2025-07-08 09:42:52 +03:00
voidlizard a1e6ff50f9 wip, ncq2 new writer 2025-07-03 16:22:54 +03:00
voidlizard 91a0af9ee3 ncq2 new writer 2025-07-03 13:49:51 +03:00
voidlizard 240ae9179d wip 2025-06-23 08:01:11 +03:00
voidlizard c5d578e2df wip 2025-06-23 07:15:51 +03:00
voidlizard f4cc7b1530 wip 2025-06-22 14:30:26 +03:00
voidlizard 617ce7d4db wip 2025-06-22 09:29:05 +03:00
voidlizard d8c34e3585 write contention test 2025-06-21 08:02:18 +03:00
voidlizard b28e669049 bf6 path:split 2025-06-20 08:36:02 +03:00
voidlizard c5dd315ab5 wip, storage tuning 2025-06-09 10:12:33 +03:00
voidlizard 1eb0159b2d wip 2025-06-09 09:21:09 +03:00
voidlizard ef88112822 FuckOS X storage patch / 2 2025-06-09 07:42:27 +03:00
voidlizard 67081eac22 FuckOS X storage patch 2025-06-09 07:38:14 +03:00
voidlizard 9a90884d46 bf6 bunch of tests for pm 2025-06-08 12:12:35 +03:00
voidlizard a7dd973732 bf6 pm fix wip 2025-06-08 12:06:48 +03:00
voidlizard fd0f0f05f5 hbs2 hash command re-implemented 2025-06-06 07:17:07 +03:00
voidlizard 8e7165331c hbs2 fsck re-implemented 2025-06-06 06:43:40 +03:00
voidlizard fc7a5c5e9f fixing memory hunger on intensive write 2025-06-05 15:43:48 +03:00
voidlizard 050914ac7a some fixes to migration procedure 2025-06-04 13:56:54 +03:00
voidlizard 3a5a35005e parallel check and remove files while migrate 2025-06-04 12:47:09 +03:00
voidlizard 460bc10daa minor fix 2025-06-04 12:02:10 +03:00
voidlizard f3b2ca3081 storage migration routine 2025-06-04 11:31:02 +03:00
voidlizard 5a8ad51ee4 new merge 2025-06-03 10:10:36 +03:00
voidlizard b308c10343 wip 2025-06-03 07:28:28 +03:00
voidlizard b0851401d7 check for compact 2025-05-29 17:55:38 +03:00
voidlizard 52fc45d30c ncqCompact 2025-05-29 17:20:03 +03:00
voidlizard 25ceea216e ncqCompact 2025-05-29 11:44:28 +03:00
voidlizard f4f2b26be6 ncqCompact 2025-05-29 11:38:38 +03:00
voidlizard a5cd25a34a wip, ncqLinearScanForCompact 2025-05-29 10:17:05 +03:00
voidlizard 09528cbf9a index:now and merge:now flags 2025-05-27 07:15:35 +03:00
voidlizard a1d6916ed9 wip, tombs count calculation 2025-05-27 06:37:27 +03:00
voidlizard 5ec77f6ab5 wip 2025-05-26 18:44:37 +03:00
voidlizard 892712799e wip 2025-05-26 18:41:40 +03:00
voidlizard ee0025c6a3 maybe fixed TCP loop on error -- additional probe 2025-05-26 18:10:11 +03:00
voidlizard 8ab74bec71 maybe fixed TCP loop on error 2025-05-26 18:08:17 +03:00
voidlizard d03273fa3e wip 2025-05-26 09:14:18 +03:00
voidlizard 3639ccec25 wip 2025-05-22 13:02:24 +03:00
voidlizard fbb6c1730a wip 2025-05-21 07:07:18 +03:00
voidlizard f3c424862f WIP, broken storage? 2025-05-21 06:04:29 +03:00
voidlizard ce36509c67 wip, seems references work 2025-05-20 11:59:15 +03:00
voidlizard c4c368f19f wip 2025-05-20 10:06:27 +03:00
voidlizard 6a04798c8e wip, ncq migrate script 2025-05-20 09:18:19 +03:00
voidlizard fe2a1fff97 wip 2025-05-20 07:10:21 +03:00
voidlizard 68ed3a4ab7 wip 2025-05-19 18:35:40 +03:00
voidlizard 3a8041f93e wip 2025-05-19 18:33:08 +03:00
voidlizard 3450c97baa wip 2025-05-19 16:41:43 +03:00
voidlizard 9f21d78416 wip 2025-05-19 15:33:31 +03:00
voidlizard 5f87d12551 wip 2025-05-19 14:44:36 +03:00
voidlizard 4620fb2087 wip 2025-05-19 13:29:40 +03:00
voidlizard 5c6666ce65 bf6, fixed pm 2025-05-19 13:19:31 +03:00
voidlizard 78f833a140 bf6, fixed pm 2025-05-19 11:30:33 +03:00
voidlizard 427115c42b wip, removing obsolete code 2025-05-19 08:25:20 +03:00
voidlizard 5f08753132 wip, file lock introduced 2025-05-19 07:10:50 +03:00
voidlizard 3ef135a25c wip, fuckup recovery 2025-05-18 11:18:10 +03:00
voidlizard 4b683a8713 wip 2025-05-18 10:14:13 +03:00
voidlizard b03b6f6b5a wip, tests 2025-05-18 07:05:46 +03:00
voidlizard c736e3b4d4 minor 2025-05-17 15:45:47 +03:00
voidlizard 63f4e69bbb ncq:test:ncq:fuckup-recovery1 2025-05-17 15:27:19 +03:00
voidlizard ff65d8e15d ncq:test:ncq:fuckup-recovery1 2025-05-17 15:18:26 +03:00
Dmitry Zuykov afa1350cd0 fixed git push --force 2025-05-17 13:19:30 +03:00
Dmitry Zuykov 5d546075aa wip 2025-05-17 13:00:49 +03:00
Dmitry Zuykov 22ba474e65 wip 2025-05-17 12:55:11 +03:00
Dmitry Zuykov 48c9abc2fe wip 2025-05-17 12:54:14 +03:00
Dmitry Zuykov 34585e0007 wip 2025-05-17 12:54:04 +03:00
Dmitry Zuykov bb4fa83022 wip, test against sqlite 2025-05-16 18:54:44 +03:00
Dmitry Zuykov af295029ec wip, merge storage 2025-05-16 11:25:36 +03:00
Dmitry Zuykov 98a97ba55f wip, merge storage 2025-05-16 10:45:21 +03:00
Dmitry Zuykov 31a476a73a wip, merge storage 2025-05-16 10:01:22 +03:00
Dmitry Zuykov 90b9204e58 wip, ncq fixed races 2025-05-16 05:52:43 +03:00
Dmitry Zuykov 35e701c127 wip 2025-05-15 05:04:58 +03:00
Dmitry Zuykov 4d13802e29 wip 2025-05-14 16:31:17 +03:00
Dmitry Zuykov c0b6b0984d wip 2025-05-14 16:18:59 +03:00
Dmitry Zuykov c27ddfa468 wip, removed obsolete stuff 2025-05-14 14:14:35 +03:00
Dmitry Zuykov 11159be468 wip, Storage 2025-05-14 14:09:30 +03:00
Dmitry Zuykov 77589bfbbd wip, Storage 2025-05-14 13:40:00 +03:00
Dmitry Zuykov f26cae04a3 wip, references 2025-05-14 13:29:01 +03:00
Dmitry Zuykov 67acde04d6 wip, new block format 2025-05-14 12:39:20 +03:00
Dmitry Zuykov 38821dd138 wip, implementing new block structure 2025-05-14 11:24:06 +03:00
Dmitry Zuykov a97685a74d wip, tcq 2025-05-13 16:41:32 +03:00
Dmitry Zuykov 41ce441f82 wip, fixed ncqCheckDeleted 2025-05-13 14:36:50 +03:00
Dmitry Zuykov 407bfadbff wip 2025-05-13 12:39:34 +03:00
Dmitry Zuykov 77a0052ffb wip 2025-05-13 11:41:41 +03:00
Dmitry Zuykov 9722fa7c01 wip 2025-05-13 10:46:09 +03:00
Dmitry Zuykov aa3d32387d wip, tcq 2025-05-13 10:41:03 +03:00
Dmitry Zuykov 7196fcc54c wip 2025-05-13 07:59:12 +03:00
Dmitry Zuykov bb9df9c25b wip 2025-05-13 07:13:22 +03:00
Dmitry Zuykov efe2a2cda9 wip 2025-05-12 15:18:15 +03:00
Dmitry Zuykov 461fe80c5c wip, deleted code 2025-05-12 12:46:53 +03:00
voidlizard 6708a2d7a7 wip 2025-05-12 11:53:06 +03:00
Dmitry Zuykov db41293fa2 wip, compiles 2025-05-12 10:56:54 +03:00
Dmitry Zuykov 24a46e1c02 wip, wip 2025-05-12 10:56:54 +03:00
Dmitry Zuykov 03ec08509a wip, remove deleted records log 2025-05-12 10:56:54 +03:00
Dmitry Zuykov 671273b817 hbs2-log-structured 2025-05-12 10:56:54 +03:00
voidlizard 3dece0d8e3 wip 2025-05-12 10:56:54 +03:00
voidlizard 326989a9fa wip 2025-05-12 10:56:54 +03:00
voidlizard b8b2ed4d14 wip 2025-05-12 10:56:54 +03:00
voidlizard d7dadfed41 wip, only delete once 2025-05-12 10:56:54 +03:00
voidlizard 55d1743a93 wip: fixing TCP busyloop 2025-05-12 10:56:54 +03:00
voidlizard 622b9ef531 okay 2025-05-12 10:56:54 +03:00
voidlizard 0159c15e58 wip 2025-05-12 10:56:54 +03:00
voidlizard 7597ed2822 wip 2025-05-12 10:56:54 +03:00
voidlizard 569b55d401 wip, ncq storage 2025-05-12 10:56:54 +03:00
Dmitry Zuykov 13cb6a730c wip 2025-05-12 10:56:54 +03:00
Dmitry Zuykov bd3511ad50 bf6: some random: functions 2025-05-12 10:56:54 +03:00
b0oh cecd905071 Remove waiting for refchan on run directory as it fails on freshly created sync 2025-05-12 10:56:54 +03:00
voidlizard c1caa97b0e .fixme-new config updated 2025-05-12 10:56:54 +03:00
b0oh e96cf1782d Remove unused import in sync prelude 2025-05-12 10:56:54 +03:00
voidlizard 6d5534ffe3 wip 2025-03-18 11:48:27 +03:00
voidlizard 21f60b6ce9 wip, fix tcp 2025-03-18 08:11:24 +03:00
voidlizard 0d90b4df05 wip, fix tcp 2025-03-18 08:06:13 +03:00
voidlizard 4757465fc3 wip, fix tcp 2025-03-18 07:51:45 +03:00
voidlizard 8b06b53f00 tcp. try to gc unused queues 2025-03-18 07:29:28 +03:00
b0oh 3843066d83 Add poll and waiting for refchan when refchan not fetched 2025-03-15 12:24:01 +03:00
voidlizard b7e5fcdbe3 wip, busyloop removed? maybe leak 2025-03-15 09:51:16 +03:00
voidlizard 025b269562 wip. fixing TCP busyloop 2025-03-15 07:44:43 +03:00
voidlizard fd8cafc9fc download stuck detector causes problem on mac 2025-03-13 20:48:51 +03:00
voidlizard 242d3d849d wip, tracking busyloop 2025-03-13 19:30:09 +03:00
voidlizard 78c168fcaa wip, hunting busyloop 2025-03-13 18:41:29 +03:00
voidlizard 541ecccd0b hunting busyloops 2025-03-08 13:56:53 +03:00
voidlizard 1c4b9e7971 tail behavour for string-like 2025-03-08 09:24:56 +03:00
voidlizard cc001b0451 wip, hunting busyloops 2025-03-04 18:43:46 +03:00
voidlizard a85a95d1f2 bf6 regress fixed 2025-03-04 15:36:46 +03:00
voidlizard 36f9937d7c hunting busyloop 2025-03-04 15:22:43 +03:00
voidlizard c51de1b2dd bf6 changes integrated 2025-03-04 13:24:47 +03:00
Dmitry Zuikov 0182c75144 block upload degradation fix? 2025-02-23 18:02:26 +03:00
voidlizard 596d8f1e16 debug 2025-02-23 17:43:46 +03:00
voidlizard 7e67021fc6 debug 2025-02-23 17:31:15 +03:00
voidlizard b931bd4ba8 repo:purge function 2025-02-23 10:58:17 +03:00
voidlizard 4a3785eaaf repo:purge function 2025-02-23 10:37:48 +03:00
voidlizard fac2612776 wip 2025-02-23 09:23:58 +03:00
voidlizard 1edd50008c bf6: show man for bound alias 2025-02-23 06:45:20 +03:00
voidlizard f96f37f9d1 fork feature handles, fork itself is postponed 2025-02-22 11:28:03 +03:00
Snail 59c27c5d5d Drop hbs2-git, Move needed modules to hbs2-git3 2025-02-21 18:57:44 +04:00
voidlizard 1cf7ec6cbc wip 2025-02-21 14:01:34 +03:00
voidlizard 6ad7d67c6f wip 2025-02-21 13:38:08 +03:00
voidlizard fdce8aee8e wip 2025-02-21 13:33:58 +03:00
voidlizard 978478f753 dns example 2025-02-21 12:54:57 +03:00
voidlizard ed7f18154f wip 2025-02-21 09:32:22 +03:00
voidlizard ba5e122392 wip 2025-02-20 09:28:44 +03:00
voidlizard 128fa751cc hbs2-peer, fix content-type and content-disposition processing 2025-02-20 09:04:05 +03:00
voidlizard ca033d2c1c some documentation and minor refactoring 2025-02-20 07:58:20 +03:00
voidlizard 74659bffc6 remotes alias 2025-02-20 07:58:20 +03:00
Snail 8cecdc1e69 man stubs 2025-02-20 07:58:20 +03:00
Snail 8514b23aef Fix running `ghcid` in shell 2025-02-20 07:58:20 +03:00
Snail 186d4c5f01 bf6-git-hbs2 2025-02-19 17:30:36 +03:00
voidlizard da131c0110 wip 2025-02-19 15:38:21 +03:00
voidlizard 0786150ef4 added man entry for repo:init 2025-02-19 13:41:20 +03:00
voidlizard 71679e5b65 top:file:run args passing 2025-02-19 12:10:23 +03:00
voidlizard 62aae7ccc3 runnable bf6 scripts with shebangs 2025-02-19 12:10:23 +03:00
voidlizard d9ae9febdd сheck hypothesis of loops in ByPass 2025-02-19 12:05:50 +03:00
voidlizard 2e36a9401c busyloop candidate 2025-02-18 12:51:46 +03:00
voidlizard e6d8489ce5 hunting random freezes 2025-02-18 12:22:59 +03:00
voidlizard df98ba225e hunting random freezes 2025-02-18 08:36:00 +03:00
voidlizard ca05c2a17a pipeline timeout exceptions 2025-02-18 08:17:53 +03:00
Snail 5418e7527b Hide some commands from hbs2-git3 help 2025-02-17 23:54:12 +03:00
voidlizard 6df825226d maybe fix TCP busyloop 2025-02-17 06:01:09 +03:00
voidlizard 9a0ab0e024 urgent fix download bug 2025-02-16 05:44:38 +03:00
voidlizard 1674813c01 urgent fix download bug 2025-02-16 05:38:55 +03:00
voidlizard 778b6b9d06 init 2025-02-15 20:55:28 +03:00
voidlizard 3e4762d880 better. most probably fix rpc lock issue 2025-02-15 14:00:29 +03:00
voidlizard 8ca4dd79bf most probably fix rpc lock issue 2025-02-15 13:51:36 +03:00
voidlizard dd310888a5 wip, rpc issue fix? 2025-02-15 13:48:16 +03:00
voidlizard 96ac593e71 attemtps to minimize memory footprint 2025-02-15 10:13:21 +03:00
voidlizard 52dff69f74 fix 6yhqpH81tK address-in-use-after-restart-macos 2025-02-14 08:00:20 +03:00
voidlizard 4ca064bec9 fixed nix build on darwin 2025-02-13 22:44:15 +03:00
voidlizard 695f271cf8 git: latest checkpoint output 2025-02-13 15:26:11 +03:00
voidlizard 768a8181e7 bf6 telegram bot example 2025-02-13 15:05:57 +03:00
voidlizard 69a827e266 fixed checkpoint timestamp format 2025-02-13 13:11:35 +03:00
voidlizard f4abd662f8 git console, output bug 2025-02-13 13:10:19 +03:00
voidlizard a54fcb6ade wip 2025-02-13 12:47:11 +03:00
voidlizard cf63dbaa94 better console output 2025-02-13 10:58:43 +03:00
voidlizard 9e2257f9ef wip, console logs 2025-02-13 10:41:54 +03:00
voidlizard 4c33ffbe8c wip 2025-02-13 08:53:30 +03:00
voidlizard 274fefc84d some scripts added 2025-02-13 07:51:09 +03:00
voidlizard 543ba01ef7 bf6: apply 2025-02-12 18:09:57 +03:00
voidlizard 2029220eb8 bf6: --run 2025-02-12 17:35:31 +03:00
voidlizard acd6698dbf bf6 executable + maybe splices support 2025-02-12 17:32:09 +03:00
voidlizard 79e850ebe2 fixed? git clone and clone test 2025-02-12 14:10:40 +03:00
voidlizard 513e2deef8 simple-test 2025-02-12 13:51:59 +03:00
voidlizard deeea55760 wip 2025-02-11 19:53:45 +03:00
voidlizard 96b5b051b3 hbs2-dashboard and hbs2-fixer removed 2025-02-11 13:09:46 +03:00
voidlizard 57b480a454 hbs2-peer extract multicast worker probe 2025-02-11 12:51:11 +03:00
voidlizard 9346e8311d wiped extra code 2025-02-11 12:36:26 +03:00
voidlizard 0d3036f7be hbs2-peer extract multicast worker 2025-02-11 12:26:29 +03:00
voidlizard 9d0f1f7976 wip 2025-02-11 10:27:32 +03:00
voidlizard c093c9237b hbs2-peer, cache control tune 2025-02-10 15:53:12 +03:00
voidlizard 78c826f1a0 bf6/suckless new functions 2025-02-10 15:46:29 +03:00
voidlizard 8bd4458b42 mac build fix 2025-02-09 18:09:36 +03:00
voidlizard 826f64f2d2 possible mac unix socket fix, static build fix 2025-02-09 16:27:49 +03:00
voidlizard 7b30dddbe8 hbs2-peer timeout fix 2025-02-09 12:08:55 +03:00
voidlizard 98c1be5999 hbs2-peer, simple rpc watchdog 2025-02-09 05:52:44 +03:00
voidlizard 0d25eaa32f hbs2-peer, simple rpc watchdog 2025-02-09 05:51:09 +03:00
voidlizard b2a48c6625 hbs2-peer do reset command 2025-02-08 20:01:20 +03:00
voidlizard 0b1773afbf suckless/bf6 json function 2025-02-08 13:26:05 +03:00
voidlizard 6cd3af1003 wip 2025-02-08 11:26:08 +03:00
voidlizard 7dc5b48d32 hbs2-peer http-port on by default 2025-02-07 13:44:45 +03:00
voidlizard 23d61378e6 hbs2-peer http cache control 2025-02-07 10:33:22 +03:00
voidlizard 988dd836b9 suckless/bf6 multiple fixes for html and htmx 2025-02-07 09:02:44 +03:00
voidlizard 0b2c0af8c1 bf6: forked and stuff for site generation 2025-02-06 21:08:04 +03:00
voidlizard 193ecc99ab wip 2025-02-06 19:36:15 +03:00
voidlizard 95b93698ac support web:bind alias redirect 2025-02-06 16:03:28 +03:00
voidlizard 2628d7efa0 wip 2025-02-06 10:39:12 +03:00
voidlizard 27ac86cdfd strftime 2025-02-06 00:42:36 +03:00
voidlizard 80f503c873 bf6: suckless-script call:proc:raw 2025-02-05 19:02:14 +03:00
voidlizard f2f38c5ca9 wip 2025-02-05 13:56:48 +03:00
voidlizard 227f29e8bb removing excess constraints 2025-02-05 12:45:08 +03:00
voidlizard b91f0323e6 wip 2025-02-04 22:07:17 +03:00
voidlizard 995710fce2 version bump 2025-02-04 12:24:45 +03:00
voidlizard 3144004351 fix 2025-02-04 10:35:35 +03:00
voidlizard 29c59994e1 suckless: recursive imports, primitive import loop detection 2025-02-04 09:09:43 +03:00
voidlizard 46b02d458f suckless: recursive imports, primitive import loop detection 2025-02-04 08:52:12 +03:00
voidlizard 9c81855a13 wip 2025-02-03 20:05:52 +03:00
voidlizard 23e690b302 wip, suckless to support imports + simple web publishing 2025-02-03 12:25:54 +03:00
voidlizard 6a7741e4ae suckless, page.ss example fixed 2025-02-03 08:07:38 +03:00
voidlizard 4aee79eb66 suckless, page.ss example 2025-02-03 08:00:37 +03:00
voidlizard ac12723dd1 suckless, css dsl 2025-02-03 07:58:53 +03:00
voidlizard 04b291157f suckless, html5 tags 2025-02-02 21:08:10 +03:00
voidlizard 83db37ce96 suckless to suck less 2025-02-02 20:47:11 +03:00
voidlizard f7f404fd94 compiles 2025-02-02 20:35:28 +03:00
voidlizard 0f6fc6223c HBS2.System.Dir moved to suckless
... and bound
2025-02-02 20:29:39 +03:00
voidlizard 599725fbd5 suckless-script html fix 2025-02-02 18:28:53 +03:00
voidlizard d0e8c6741d templ.ss example mutated 2025-02-02 17:37:58 +03:00
voidlizard 0d10939e15 suckless, basic html templates 2025-02-02 17:02:10 +03:00
voidlizard 945e8ca18b wip, webroot 2025-02-02 13:31:27 +03:00
voidlizard 5891271d93 wip, fix compilation 2025-02-02 13:04:42 +03:00
voidlizard a3a5c46cc0 wip, grove + webroot 2025-02-02 12:51:19 +03:00
voidlizard b68ac88544 wip 2025-02-02 11:45:50 +03:00
voidlizard 786a30333e succ suckless-conf 2025-02-02 09:37:27 +03:00
voidlizard a48676b217 suckless, some functions 2025-02-02 00:29:53 +03:00
voidlizard c2b49f3fd7 suckless-conf improvements 2025-02-01 20:43:37 +03:00
voidlizard 5a1e95b79c grep to suckless-script 2025-02-01 18:31:08 +03:00
voidlizard d126fcf375 wip 2025-02-01 12:48:11 +03:00
voidlizard 73b6c969bd wip 2025-02-01 12:45:18 +03:00
voidlizard 304e04038a hbs2-peer fetch -p to exit on Ctrl-c or q keys 2025-02-01 10:43:48 +03:00
voidlizard 83a73e7e0d wip, logging 2025-02-01 08:00:57 +03:00
voidlizard 478f43a528 wip-2 2025-01-31 15:39:59 +03:00
voidlizard b5d633b122 wip 2025-01-31 11:26:53 +03:00
voidlizard d993501b1f wip, updateRepoHead 2025-01-31 10:41:02 +03:00
voidlizard f7aa86a647 wip 2025-01-30 13:54:29 +03:00
voidlizard 7736cb76fa wip 2025-01-30 13:47:16 +03:00
voidlizard c38bf86c93 wip, null tx for repo init 2025-01-30 13:42:51 +03:00
voidlizard 148aa69533 wip 2025-01-30 13:26:45 +03:00
voidlizard 733e7f5a9c wip 2025-01-30 13:22:40 +03:00
voidlizard bdaedc92a0 wip, missed file 2025-01-30 13:15:33 +03:00
voidlizard a3bffd2f00 wip 2025-01-30 13:05:56 +03:00
voidlizard 7b7e44414f wip 2025-01-30 12:07:21 +03:00
voidlizard 1448a000b6 wip 2025-01-30 10:11:46 +03:00
voidlizard e33d123c52 wip 2025-01-30 10:11:22 +03:00
voidlizard 34f61a7bc8 wip, about to remove repo:ref 2025-01-30 08:56:10 +03:00
voidlizard e723a47080 wip 2025-01-28 14:04:58 +03:00
voidlizard 7f24126c8a maybe fix eval + opaque problem 2025-01-26 08:29:40 +03:00
voidlizard b6ce70689d wip 2025-01-25 11:01:06 +03:00
voidlizard bf3e8a49b0 wip 2025-01-25 07:37:54 +03:00
voidlizard 7807156cfa wip, Repo.fork 2025-01-25 04:52:20 +03:00
voidlizard f12f4d1e12 wip, kinda function composition? 2025-01-24 20:45:54 +03:00
voidlizard b8db094714 wip 2025-01-24 18:41:16 +03:00
voidlizard 5b3d299d5e wip, words, lines 2025-01-24 18:09:33 +03:00
voidlizard 5df971bbac wip, cons, top:stdin 2025-01-24 17:49:17 +03:00
voidlizard b4f8cd8354 wip 2025-01-24 17:07:47 +03:00
voidlizard 661fe8135b wip, fixed excess evaluation 2025-01-24 16:55:07 +03:00
voidlizard dae0aa191a wip, evaluation, broken 2025-01-24 15:29:29 +03:00
voidlizard 816786fd5b wip 2025-01-24 14:11:18 +03:00
voidlizard 2804332ae9 wip 2025-01-24 11:59:25 +03:00
voidlizard baed40f7c6 wip 2025-01-24 07:01:00 +03:00
voidlizard 4b2da6bcad wip 2025-01-24 06:47:52 +03:00
voidlizard 6dbff6b598 wip, Logger and some cleanup 2025-01-24 06:34:37 +03:00
voidlizard 4275f0cf30 repo:key -> repo:ref for name consistency 2025-01-24 06:20:35 +03:00
voidlizard fac5a61b6a fuzzy-parse: escaping in string tokens 2025-01-23 22:52:58 +03:00
voidlizard 71b11a70d8 string literal escaping 2025-01-23 22:26:35 +03:00
voidlizard dcd13663d9 wip. Better wait. Uses the probe Download.wip ...
instead of sqlite query (RpcDownloadList)
2025-01-23 05:39:11 +03:00
voidlizard a240bc23f0 wip, bugfix 2025-01-22 20:15:25 +03:00
voidlizard 873cfbeacd bugfix 2025-01-22 17:46:56 +03:00
voidlizard 9c1dad1c5e removed nixbwrap 2025-01-22 11:30:36 +03:00
voidlizard 3d78882f62 wip, better wait for blocks 2025-01-22 11:02:51 +03:00
voidlizard b263bfbdc9 wip 2025-01-22 10:00:46 +03:00
voidlizard 242101fd9c wip 2025-01-22 08:35:05 +03:00
voidlizard f2027bb19e wip 2025-01-22 06:17:59 +03:00
voidlizard d9cd91398a debug 2025-01-21 11:21:42 +03:00
voidlizard bdc9fb80b3 waitRepo 2025-01-21 10:41:32 +03:00
voidlizard 4fb13f6d17 wip, debug 2025-01-20 17:30:03 +03:00
voidlizard 8303a347b6 wip, debug 2025-01-20 16:35:34 +03:00
voidlizard 2602ecdbff wip, debug 2025-01-20 16:13:40 +03:00
voidlizard f50311bd03 wip 2025-01-20 15:44:07 +03:00
voidlizard 99c9168fb6 fuck the fucking nix. really hate this piece of shit 2025-01-20 14:39:54 +03:00
voidlizard 02c4a1598a temporary added hbs2-git 2025-01-20 14:25:56 +03:00
voidlizard 62a011934d suckless-conf, the fixme-new fuckup unfucked 2025-01-20 12:15:31 +03:00
voidlizard 00e24115ec wip, trim .ref files 2025-01-20 11:46:16 +03:00
voidlizard dec9fbcc3d wip 2025-01-20 10:00:50 +03:00
voidlizard ba873cd46a wip, some man entries 2025-01-20 09:51:10 +03:00
voidlizard 8bb17eaafb wip 2025-01-20 09:37:45 +03:00
voidlizard 4ba2f040ae suckless-conf, hide entries 2025-01-20 09:23:30 +03:00
voidlizard e7081e495c wip, code removing 2025-01-20 08:19:10 +03:00
voidlizard 22a4ef450c wip, bugfix 2025-01-19 23:56:56 +03:00
voidlizard dbd4b81ca7 wip, debug 2025-01-19 23:15:58 +03:00
voidlizard c6f65d2242 wip, fixing bugs 2025-01-19 23:10:33 +03:00
voidlizard 4b09dfc8a9 wip, waitForRepo fixed 2025-01-19 23:05:57 +03:00
voidlizard 19fe110fee wip, missed 2025-01-19 22:49:52 +03:00
voidlizard 99bb9c9dba wip, waitRepo 2025-01-19 22:45:38 +03:00
voidlizard 3b9bb622c5 wip,fix 2025-01-19 21:01:39 +03:00
voidlizard 3d27321241 wip 2025-01-19 20:51:46 +03:00
voidlizard b5410825c3 wip 2025-01-19 18:22:29 +03:00
voidlizard 2298295972 wip, init lwwref 2025-01-19 14:21:51 +03:00
voidlizard 187e9d2ba7 wip, code cleanup 2025-01-17 20:17:16 +03:00
voidlizard eaadafd599 wip, git-remote-helper 2025-01-17 13:48:03 +03:00
voidlizard 2e0c0fc879 wip 2025-01-17 12:37:42 +03:00
voidlizard 61cccbd5d5 wip 2025-01-17 11:58:25 +03:00
voidlizard dc60abfd6b wip 2025-01-17 11:42:21 +03:00
voidlizard a6f4a5a1fa wip 2025-01-17 11:19:14 +03:00
voidlizard 1e008e449c wip 2025-01-17 11:10:40 +03:00
voidlizard d6dc01d939 missed file 2025-01-17 11:03:39 +03:00
voidlizard 0bc0ae1e27 wip 2025-01-17 11:01:18 +03:00
voidlizard 2e4dd73713 wip, git-remote-helper 2025-01-17 10:46:16 +03:00
voidlizard 0405d111aa wip 2025-01-17 07:59:15 +03:00
voidlizard 10ff2ceaff wip, refs calculation 2025-01-17 07:47:17 +03:00
voidlizard 250099af7e wip 2025-01-16 07:25:39 +03:00
voidlizard 3ebb44be5a wip, removed excess code 2025-01-15 20:31:54 +03:00
voidlizard 62eba43739 wip, .ref trimming 2025-01-15 15:48:48 +03:00
voidlizard 712063c5f9 wip, txList updated 2025-01-15 15:13:11 +03:00
voidlizard 339a7dce1d wip, import as packs 2025-01-15 14:52:25 +03:00
voidlizard 5dc82c5a81 wip, removing obsolete stuff, mkdir for state 2025-01-15 11:55:31 +03:00
voidlizard c3fc7fa69b wip 2025-01-15 11:12:05 +03:00
voidlizard a7aaa83a8c z 2025-01-15 10:17:49 +03:00
voidlizard e725861292 wip, checkpoints 2025-01-15 10:05:46 +03:00
voidlizard 8ba476be17 wip 2025-01-15 09:53:25 +03:00
voidlizard 8d20f10837 wip 2025-01-15 09:06:23 +03:00
voidlizard 2349ec4157 wip, export checkpoints 2025-01-15 08:24:29 +03:00
voidlizard be512971d6 wip, export checkpoints 2025-01-15 08:14:10 +03:00
voidlizard bde53133a9 wip, export checkpoints 2025-01-15 08:08:44 +03:00
voidlizard e6d1eadb7a wip, git packs 2025-01-14 12:24:43 +03:00
voidlizard 33cec9f40f wip 2025-01-14 07:53:05 +03:00
voidlizard cb3752da63 wip, git:list:objects to display hashes 2025-01-12 16:57:08 +03:00
voidlizard ad13361266 wip, wtf 2025-01-12 13:37:05 +03:00
voidlizard 0d4fbcc368 wip 2025-01-12 12:52:14 +03:00
voidlizard cb307a4ca6 wip,compact kinda work 2025-01-12 12:46:38 +03:00
voidlizard fca0786356 wip, works but not dedupes 2025-01-12 10:39:41 +03:00
voidlizard 13133148dc wip, works but mem O(k) 2025-01-12 09:32:47 +03:00
voidlizard 340bb9aaa8 wip, renamed function 2025-01-11 15:28:41 +03:00
voidlizard 8a5ffe2e7b compact-index 2025-01-11 15:26:38 +03:00
voidlizard 48132864a6 wip 2025-01-11 12:07:42 +03:00
voidlizard 44f242a723 wip 2025-01-11 10:29:02 +03:00
voidlizard 97100dbc90 make antiquotes work 2025-01-09 14:12:33 +03:00
voidlizard 7742ad81ce wip 2025-01-09 11:35:42 +03:00
voidlizard eb839eca94 wip 2025-01-08 13:31:42 +03:00
voidlizard 568fb735a5 wip 2025-01-08 12:54:13 +03:00
voidlizard 218ff12000 wip 2025-01-08 12:01:16 +03:00
voidlizard ff7abbefc7 wip 2025-01-06 16:17:50 +03:00
voidlizard 01de679589 wip 2025-01-03 12:46:30 +03:00
voidlizard 772ea4235e wtf/wip 2025-01-03 09:56:06 +03:00
voidlizard 62c6ba26cb bloom filter test 2025-01-03 09:10:37 +03:00
voidlizard 289e9f7120 wip 2025-01-02 10:45:55 +03:00
voidlizard 82f97e148c wip 2025-01-02 10:44:09 +03:00
voidlizard 1f1b96f3b4 wip 2025-01-02 09:50:41 +03:00
voidlizard 691c7a0160 wip 2025-01-02 08:55:37 +03:00
voidlizard 58a69f9970 wip, not good 2025-01-01 12:53:56 +03:00
voidlizard 50fd755dee wip 2025-01-01 12:49:35 +03:00
voidlizard fddf121371 real-test 2025-01-01 11:37:06 +03:00
voidlizard 98be2b056b wip 2025-01-01 10:46:10 +03:00
voidlizard 2dd26b3050 wip, massive refactoring 2025-01-01 08:51:47 +03:00
voidlizard 5e374b68cd argh 2024-12-30 23:14:59 +03:00
voidlizard 6ad858d175 wip 2024-12-30 21:44:58 +03:00
voidlizard 33d0493edc wip 2024-12-30 21:37:18 +03:00
voidlizard 693e650b93 wip 2024-12-30 15:27:56 +03:00
voidlizard b9e9d4799f wip 2024-12-30 13:32:57 +03:00
voidlizard b1836d2081 sqlite test 2024-12-30 10:15:24 +03:00
voidlizard 943ae395c4 wip 2024-12-30 09:37:53 +03:00
voidlizard a304510d02 wip 2024-12-29 09:30:08 +03:00
voidlizard 2d98966ec6 wip, exporting references 2024-12-28 12:19:00 +03:00
voidlizard 348cbd2c8d wip 2024-12-28 10:14:55 +03:00
voidlizard 06089dcffb wip 2024-12-28 09:13:30 +03:00
voidlizard 2e4558d444 wip 2024-12-27 17:38:40 +03:00
voidlizard 0c29dcf52c wip 2024-12-27 16:10:31 +03:00
voidlizard 8b58bab92b compiles 2024-12-26 18:28:21 +03:00
voidlizard f484c8e203 wip 2024-12-26 18:24:38 +03:00
voidlizard 05e9a3facc wip 2024-12-26 18:12:45 +03:00
voidlizard b02e704600 wip 2024-12-26 13:52:25 +03:00
voidlizard 7ecabd2bab wip 2024-12-25 16:00:13 +03:00
voidlizard 143a67386b wip 2024-12-25 14:10:07 +03:00
voidlizard 2effcc242c wip 2024-12-25 11:33:31 +03:00
voidlizard 3b1dc869ba wip 2024-12-25 07:46:20 +03:00
voidlizard 6e39900d6b wip2 2024-12-25 07:08:30 +03:00
voidlizard 3773c7857b wip 2024-12-24 18:51:11 +03:00
voidlizard 83bcba17ae works single-treaded 2024-12-24 12:11:00 +03:00
voidlizard 0c50d1cc98 wip 2024-12-24 11:44:23 +03:00
voidlizard 7f344a7f72 wip 2024-12-23 18:03:43 +03:00
voidlizard 050603f82b wip 2024-12-23 07:45:06 +03:00
voidlizard 0fcbfcc635 motherfucking binary search in index 2024-12-23 00:13:27 +03:00
voidlizard e536d639fa wip 2024-12-22 15:04:18 +03:00
voidlizard 8804450d7f commit traversal with level, does not suck much 2024-12-22 10:03:44 +03:00
voidlizard b066868965 wip 2024-12-21 06:12:03 +03:00
voidlizard 5d6400892b wip 2024-12-21 05:52:34 +03:00
voidlizard 0eb2744979 wip 2024-12-21 05:36:24 +03:00
voidlizard b70735df26 fix for invalid commit format 2024-12-20 19:29:16 +03:00
voidlizard d954104fe9 wip39 2024-12-08 10:42:55 +03:00
voidlizard 3bd8422a6f wip37 2024-12-08 08:16:30 +03:00
voidlizard debe84f3ca wip36 2024-12-07 19:29:10 +03:00
voidlizard c7c323ca9e wip35 2024-12-07 14:45:58 +03:00
voidlizard 4724a85a52 wip33 2024-12-07 13:27:41 +03:00
voidlizard 974196ad9e wip32 2024-12-07 13:25:12 +03:00
voidlizard 826b6a933f wip31 2024-12-07 12:04:32 +03:00
voidlizard e31dbdcf26 wip30 2024-12-07 11:25:41 +03:00
voidlizard 326f0a2b96 wip29 2024-12-06 15:50:12 +03:00
voidlizard c16422addc wip28 2024-12-06 14:01:20 +03:00
voidlizard 77fb8f419d wip27 2024-12-06 13:53:40 +03:00
voidlizard 1f1c9be9df wip26 2024-12-06 13:50:12 +03:00
voidlizard a22e1635de wip25 2024-12-06 13:46:28 +03:00
voidlizard 95f9cf6933 wip24 2024-12-06 13:42:13 +03:00
voidlizard 332f8d3eae wip23 2024-12-06 10:40:36 +03:00
voidlizard 9bdede5643 wtf 2024-12-06 09:09:03 +03:00
voidlizard 2907c9830e wip22 test:git:cblock:object:cat 2024-12-06 07:18:51 +03:00
voidlizard eecab152a6 wip21 2024-12-05 14:01:16 +03:00
voidlizard 5b73884f00 wip20 2024-12-05 10:44:29 +03:00
voidlizard 86a73fbe67 wip19 2024-12-05 10:23:12 +03:00
voidlizard 284425bd1d wip18 2024-12-05 09:40:07 +03:00
voidlizard bbc1c7c342 wip17, oopsie 2024-12-05 08:14:18 +03:00
voidlizard 857b93a810 wip16 2024-12-05 07:10:55 +03:00
voidlizard 93d8848a72 wip15 2024-12-04 13:42:59 +03:00
voidlizard ba4990a039 wip14 2024-12-04 13:42:59 +03:00
voidlizard dbcff19aed wip13 2024-12-04 13:42:59 +03:00
voidlizard a7aae31cac wip12 2024-12-04 13:42:59 +03:00
voidlizard bd3395f775 wip11 2024-12-04 13:42:59 +03:00
voidlizard 02a9c24302 wip10, missed stuff 2024-12-04 13:42:59 +03:00
voidlizard 1012368cb5 wip8 2024-12-04 13:42:59 +03:00
voidlizard 4941c5442c wip7 2024-12-04 13:42:59 +03:00
voidlizard 6105fb0446 wip6 2024-12-04 13:42:59 +03:00
voidlizard 4a380c62c3 wip4 2024-12-04 13:42:59 +03:00
voidlizard 778e172b9d wip3 2024-12-04 13:42:59 +03:00
voidlizard 1c0952ad95 wip 2024-12-04 13:42:59 +03:00
voidlizard 5ee4c3630a wip 2024-12-04 13:42:59 +03:00
voidlizard 715019dbb3 basic git repo recursion 2024-12-04 13:42:59 +03:00
voidlizard bd0bd4f50c wip 2024-12-04 13:42:59 +03:00
voidlizard fd0cebd1d8 missed 2024-12-04 13:42:59 +03:00
voidlizard 025492be2d nice 2024-12-04 13:42:59 +03:00
voidlizard fce2906adc wip-5 2024-12-04 13:42:59 +03:00
voidlizard d51d947e56 wip-2 2024-12-04 13:42:59 +03:00
voidlizard fca8d74f8d wip 2024-12-04 13:42:59 +03:00
voidlizard 272a706828 wip 2024-12-04 13:42:59 +03:00
voidlizard 77a02c286e modneishe, naprimer 2024-12-04 13:42:59 +03:00
voidlizard 708971964d wip 2024-12-04 13:42:59 +03:00
voidlizard 5cec24daa7 wip 2024-12-04 13:42:59 +03:00
voidlizard 8a8108e2ef wip 2024-12-04 13:42:59 +03:00
voidlizard 3acb1aaabe wip 2024-12-04 13:42:59 +03:00
voidlizard 49b16606aa wip, some probes 2024-12-04 13:42:59 +03:00
voidlizard 08816dfc46 bring back persistent DownloadQ 2024-12-04 13:42:59 +03:00
voidlizard 0a473c117e wip 2024-12-04 13:42:59 +03:00
voidlizard e5950b9ca9 ... 2024-12-04 13:42:59 +03:00
voidlizard 2afb24323f x3 2024-12-04 13:42:59 +03:00
voidlizard 4350a9b6b2 wip 2024-12-04 13:42:59 +03:00
voidlizard ee02bbab51 better 2024-12-04 13:42:59 +03:00
voidlizard 2334645944 wip 2024-12-04 13:42:59 +03:00
voidlizard 54d7e1af6f moar betta 2024-12-04 13:42:59 +03:00
voidlizard fc9d1fc4e8 much betta 2024-12-04 13:42:59 +03:00
voidlizard f666d89c18 DDD works 2024-12-04 13:42:59 +03:00
voidlizard 63abb6d4cb betta 2024-12-04 13:42:59 +03:00
voidlizard 6fb7842149 DDD works 2024-12-04 13:42:59 +03:00
voidlizard 88ac2021d4 much betta 2024-12-04 13:42:59 +03:00
voidlizard cb17fd9382 code cleanup 2024-12-04 13:42:59 +03:00
voidlizard fa31750a80 wip 2024-12-04 13:42:59 +03:00
voidlizard 9004dfaa5e wip 2024-12-04 13:42:58 +03:00
voidlizard b6bd4cf441 this 2024-12-04 13:42:58 +03:00
voidlizard 9a24890bbc okay? 2024-12-04 13:42:58 +03:00
voidlizard 4151d06158 betta or not? 2024-12-04 13:42:58 +03:00
voidlizard 3e0464d1d7 wip 2024-12-04 13:42:58 +03:00
voidlizard b767792d77 betta? 2024-12-04 13:42:58 +03:00
voidlizard fe444bb7f7 betta? 2024-12-04 13:42:58 +03:00
voidlizard 6164cd4aba removed useless code 2024-12-04 13:42:58 +03:00
voidlizard ae902850b5 wip 2024-12-04 13:42:58 +03:00
voidlizard f0ff437787 moar agressive 2024-12-04 13:42:58 +03:00
voidlizard 2ee94a18a6 wip 2024-12-04 13:42:58 +03:00
voidlizard 0f64ad8ddb better 2024-12-04 13:42:58 +03:00
voidlizard 44985a8212 before-permutation 2024-12-04 13:42:58 +03:00
voidlizard d5abcf331e more or less works 2024-12-04 13:42:58 +03:00
voidlizard 1ad5be6b10 same shit 2024-12-04 13:42:58 +03:00
voidlizard 14ed31dc38 wip 2024-12-04 13:42:58 +03:00
voidlizard 23118aaea3 wip 2024-12-04 13:42:58 +03:00
voidlizard a109e9acb7 wip 2024-12-04 13:42:58 +03:00
voidlizard d74573ac5f shitty 2024-12-04 13:42:58 +03:00
voidlizard cd33fc0edc somehow 2024-12-04 13:42:58 +03:00
voidlizard e84693b247 fucked on high speeds / multiple peers 2024-12-04 13:42:58 +03:00
voidlizard fe88522200 better 2024-12-04 13:42:58 +03:00
voidlizard ca995a8228 good but fucked a little bit 2024-12-04 13:42:58 +03:00
voidlizard 79a352a83f works? 2024-12-04 13:42:58 +03:00
voidlizard 2fff2a7203 not good 2024-12-04 13:42:58 +03:00
voidlizard 77dbdd0000 sort of work? 2024-12-04 13:42:58 +03:00
voidlizard 16d687ed51 work but poor 2024-12-04 13:42:58 +03:00
voidlizard b7906e6f55 does not work 2024-12-04 13:42:58 +03:00
voidlizard 81040cbc97 fucked 2024-12-04 13:42:58 +03:00
voidlizard f64b71c4be FUCKED 2024-12-04 13:42:58 +03:00
voidlizard 7de1415d7f wip 2024-12-04 13:42:58 +03:00
voidlizard e53273d3b6 wip 2024-12-04 13:42:58 +03:00
voidlizard 0828a6e01e wip 2024-12-04 13:42:58 +03:00
voidlizard 8ef0c2a6a4 wip 2024-12-04 13:42:58 +03:00
voidlizard ec9e7230cc wip 2024-12-04 13:42:58 +03:00
voidlizard c762b48bb8 wip 2024-12-04 13:42:58 +03:00
voidlizard b501dddb73 tune 2024-12-04 13:42:58 +03:00
voidlizard 415f345642 fix 2024-12-04 13:42:58 +03:00
voidlizard 3421a14c80 fix 2024-12-04 13:42:58 +03:00
voidlizard 44d6f72526 okay 2024-12-04 13:42:58 +03:00
voidlizard bcc973eb1a better 2024-12-04 13:42:58 +03:00
voidlizard 21b97f4661 better? 2024-12-04 13:42:58 +03:00
voidlizard 6762c7497d somehow works 2024-12-04 13:42:58 +03:00
voidlizard a3a954bed0 fixed wtf 2024-12-04 13:42:58 +03:00
voidlizard 85fe082868 fixed wtf 2024-12-04 13:42:58 +03:00
voidlizard 686c11a7ad wip 2024-12-04 13:42:58 +03:00
voidlizard 3c0e6af814 wip 2024-12-04 13:42:58 +03:00
voidlizard 058b3a9da1 todo 2024-12-04 13:42:58 +03:00
voidlizard 91333e6f13 wip 2024-12-04 13:42:58 +03:00
voidlizard 097f8bea21 wip 2024-12-04 13:42:58 +03:00
voidlizard 54a805ae6e wip 2024-12-04 13:42:58 +03:00
voidlizard 9c10655a0c fucked 2024-12-04 13:42:58 +03:00
voidlizard b6c97a254a fucked 2024-12-04 13:42:58 +03:00
voidlizard 3e737feb0c wip 2024-12-04 13:42:58 +03:00
voidlizard 0326f08392 wip, block download rework 2024-12-04 13:42:58 +03:00
voidlizard d07db60f74 cache block sizes 2024-12-04 13:42:58 +03:00
voidlizard 87d84b0531 wip 2024-12-04 13:42:58 +03:00
voidlizard 8e37ae86ce request-block-size 2024-12-04 13:42:58 +03:00
voidlizard 5239e39fbe wip, fix download 2024-12-04 13:42:58 +03:00
voidlizard 5b03bdd05b tune 2024-12-04 13:42:58 +03:00
voidlizard e1c071f86e tune 2024-12-04 13:42:58 +03:00
voidlizard 4fb9c948be wip, tcp 2024-12-04 13:42:58 +03:00
voidlizard 2c6d260895 wip, tcp 2024-12-04 13:42:58 +03:00
voidlizard dd61c4a09c wip 2024-12-04 13:42:58 +03:00
voidlizard a360dfb7ec wip, TCP 2024-12-04 13:42:58 +03:00
voidlizard e64f3e9c41 wip, refactored 2024-12-04 13:42:58 +03:00
voidlizard 35b91e5a79 forced Cache cleanup 2024-12-04 13:42:58 +03:00
voidlizard 349b61b8f9 wip, debug 2024-12-04 13:42:58 +03:00
voidlizard 49c67a49dc wip 2024-12-04 13:42:58 +03:00
voidlizard a2955197a3 tcp rewritten 2024-12-04 13:42:58 +03:00
voidlizard ef6b7379c3 tools to flake.nix 2024-12-04 13:42:58 +03:00
voidlizard 8cce932c6c debug 2024-12-04 13:42:58 +03:00
voidlizard 19ddaa9c18 HttpWorker exceptions 2024-12-04 13:42:58 +03:00
voidlizard 6014c4c04e wip 2024-12-04 13:42:58 +03:00
voidlizard 8af3a21be1 continue with TCP leaks 2024-12-04 13:42:58 +03:00
voidlizard fa74de1fdb TCP connection leak 2024-12-04 13:42:58 +03:00
voidlizard 43eb9abb7e merged 2024-12-04 13:42:58 +03:00
voidlizard 11c1124994 reallyBusy counter 2024-12-04 13:42:58 +03:00
voidlizard b11f233a60 BlockDownload probe 2024-12-04 13:42:58 +03:00
voidlizard 4c4e773fa5 added TCP spawned parameter to probe 2024-12-04 13:42:58 +03:00
voidlizard 6cca320c34 some TCP cleanup 2024-12-04 13:42:58 +03:00
voidlizard 4f1f1a0e7e BasicBrains probe 2024-12-04 13:42:58 +03:00
voidlizard 1b9a1f30df callRpcWaitRetry 2024-12-04 13:42:58 +03:00
voidlizard 795702000d monotonic time probe 2024-12-04 13:42:58 +03:00
voidlizard 03b638a571 bypass messaging probes 2024-12-04 13:42:58 +03:00
voidlizard c972edcc4a ghc metrics probe 2024-12-04 13:42:58 +03:00
voidlizard cf5f5cdc57 probes fix 2024-12-04 13:42:58 +03:00
Snail 5e997acc59 peermain probes 2024-12-04 13:42:58 +03:00
voidlizard 74782d00d2 gc params tuned for hbs2-peer 2024-12-04 13:42:58 +03:00
voidlizard 49a1791011 wip 2024-12-04 13:42:58 +03:00
voidlizard 251b9ce5c3 TCP probe 2024-12-04 13:42:58 +03:00
voidlizard 39e790ef32 storage w. AnyProbe 2024-12-04 13:42:58 +03:00
voidlizard 66091d5171 dontHandle to Prelude 2024-12-04 13:42:58 +03:00
voidlizard 2c8ef85980 fixed-messaging-and-basic-probes 2024-12-04 13:42:58 +03:00
voidlizard 82b3106ca6 fixed-messaging-and-basic-probes 2024-12-04 13:42:58 +03:00
voidlizard 2abfbb0fb4 fixed Unix.hs 2024-12-04 13:42:58 +03:00
Snail 632d19a2a3 qblf update 2024-12-04 13:42:58 +03:00
Andrei Borzenkov 9f309c4b8c Add base-url configuration option to the hbs2-git-dashboard 2024-12-04 13:42:58 +03:00
Dmitry Bushmelev 68ccf9d8a7 Add undelete function to hbs2-sync 2024-12-04 13:22:18 +03:00
Snail a70988c379 TestWalkMerkleConditional 2024-11-26 08:14:43 +03:00
Snail 52de19c184 walkMerkleConditional, streamMerkleConditional 2024-11-26 08:14:43 +03:00
Snail 7b6c423816 Fix nix overlay 2024-11-26 08:14:43 +03:00
Snail cdee58b03c Move AdHocStorage to separate module 2024-11-20 11:00:51 +03:00
Snail 9d57701cf2 fsck 2024-11-20 11:00:51 +03:00
Snail d0010f1994 debug `readBundleRefs` 2024-11-20 11:00:51 +03:00
voidlizard 3c1ad164af storage w. AnyProbe 2024-11-20 11:00:51 +03:00
Snail 16d3fd208b qblf update 2024-11-20 11:00:51 +03:00
Yura Shelyag ed7a402fc3 Fixed die and SIGINT exit 2024-11-01 16:02:08 +03:00
voidlizard 9fca167dd3 recv packet length measuring 2024-10-18 12:03:33 +03:00
voidlizard 954b2266ab download tuned for low MTU values, needs investigation 2024-10-18 10:27:28 +03:00
Vladimir Krutkin 6eb63e9cce Redesign issue header 2024-10-14 14:41:55 +03:00
Vladimir Krutkin 85f3053d85 Fix dashboard config `develop-assets` option 2024-10-14 14:41:55 +03:00
Andrei Borzenkov 9640543924
Create home-manager module for hbs2-peer and hbs2-git-dashboard 2024-10-11 21:06:16 +04:00
voidlizard 0b4c20d91b fixed(?) nix develop wtf on Debian 2024-10-10 12:38:16 +03:00
voidlizard 109971d74c fixed fixme-new wtf with lazy io 2024-10-10 12:22:30 +03:00
b0oh cb70ac7c66 Add sync init by refchan 2024-10-10 12:07:53 +03:00
voidlizard e3e0ff4cd5 removed excess console output 2024-10-08 15:16:21 +03:00
voidlizard ef7fc8960e fix 3Z8iftDL9X 2024-10-08 14:19:43 +03:00
voidlizard 990e7f09b8 fixme 2024-10-08 11:22:49 +03:00
voidlizard fc557d5c6f fixme-new cat fixed? 2024-10-07 11:36:21 +03:00
voidlizard 91363d25e4 wip 2024-10-07 10:01:27 +03:00
Andrei Borzenkov e8f6c4dfd5
Fix nix build and nix develop, remove redundand flake inputs and remove obsolete dependency 2024-10-07 10:27:57 +04:00
voidlizard 8846bfdc7e sigil convenience functions 2024-10-07 06:22:35 +03:00
voidlizard a4759c99eb flake.lock 2024-10-07 05:51:01 +03:00
voidlizard 5324e83031 basic fixme editing 2024-10-07 05:35:32 +03:00
voidlizard 04de089750 update dependencies 2024-10-07 05:20:28 +03:00
voidlizard eb38601339 Merge commit '371cf53e3ad667c9ede4b56d55a511fd9583b2e6' as 'miscellaneous/db-pipe' 2024-10-07 05:06:33 +03:00
voidlizard 371cf53e3a Squashed 'miscellaneous/db-pipe/' content from commit 7f28fdcb2
git-subtree-dir: miscellaneous/db-pipe
git-subtree-split: 7f28fdcb2ba9ccd426facffebf100e98522d7eac
2024-10-07 05:06:33 +03:00
voidlizard 7c515bb267 Merge commit 'cf85d2df2af00bf8c8d59666aa16ee4a85a3ba20' as 'miscellaneous/fuzzy-parse' 2024-10-07 05:06:03 +03:00
voidlizard cf85d2df2a Squashed 'miscellaneous/fuzzy-parse/' content from commit a834b152e
git-subtree-dir: miscellaneous/fuzzy-parse
git-subtree-split: a834b152e29d632c816eefe117036e5d9330bd03
2024-10-07 05:06:03 +03:00
voidlizard cec6ff3c41 libsodium-1.0.19 2024-10-07 05:05:30 +03:00
voidlizard 5c058ee23f Merge commit '874f8a01f7686240d5758fa2f28f4daac3dc488e' as 'miscellaneous/libsodium/source' 2024-10-07 05:04:26 +03:00
voidlizard 874f8a01f7 Squashed 'miscellaneous/libsodium/source/' content from commit fb4533b0a
git-subtree-dir: miscellaneous/libsodium/source
git-subtree-split: fb4533b0a941b3a5b1db5687d1b008a5853d1f29
2024-10-07 05:04:26 +03:00
voidlizard b538089274 Merge commit '2702905cfdc5738c3202e12a5e79a55d0be6cbfc' as 'miscellaneous/saltine' 2024-10-07 05:03:46 +03:00
voidlizard 2702905cfd Squashed 'miscellaneous/saltine/' content from commit 6930947c5
git-subtree-dir: miscellaneous/saltine
git-subtree-split: 6930947c556970daf8bdac71f9bdc3bb592b80c9
2024-10-07 05:03:46 +03:00
voidlizard b5d2ffabb6 Merge commit 'e1cbd3eb64ebb4a5fd2a060bb770a566be67628c' as 'miscellaneous/suckless-conf' 2024-10-07 05:03:16 +03:00
voidlizard e1cbd3eb64 Squashed 'miscellaneous/suckless-conf/' content from commit ff6f1a2e0
git-subtree-dir: miscellaneous/suckless-conf
git-subtree-split: ff6f1a2e053005a52af5c7375fb66e8bb89bce2d
2024-10-07 05:03:16 +03:00
Snail e29b15f90c Test walkMerkle, walkMerkleV2, streamMerkle 2024-10-05 06:07:16 +03:00
voidlizard 1715e1dd92 fixme 2024-10-05 04:51:04 +03:00
Andrei Borzenkov 5e8dd6cd46
Move bytestring-mmap inside repo and add compatiblity layer 2024-10-04 20:27:17 +04:00
Andrei Borzenkov 86ce779306
Make PR description 2024-10-03 14:56:11 +04:00
Andrei Borzenkov be86429c4b
Initial static builds for HBS2
Use `nix build .#static` to start building
2024-10-03 14:16:05 +04:00
Dmitry Zuikov 4a8f734584 hbs2-git-dashboard quick fix 2024-10-03 06:24:47 +03:00
Dmitry Zuikov 86fcde758b hbs2-git-dashboard updated; status - wip 2024-10-03 06:15:17 +03:00
Dmitry Zuikov d7e8e909b5 fixme updated 2024-10-03 05:58:05 +03:00
Dmitry Zuikov debea1beb9 merged hbs2-sync init; fixme updated 2024-10-03 05:56:26 +03:00
b0oh dab6f34536 Add documentation for hbs2-sync init 2024-10-02 13:45:00 +07:00
b0oh 7bd4d6c6c3 Add sync init without arguments 2024-10-01 15:16:33 +07:00
Dmitry Zuikov 981a4e587a ghc-9.6.6; sqlite: fixed sqlite text/blob json wtf 2024-09-25 17:08:18 +03:00
Dmitry Zuikov 1608bf9257 shit 2024-09-25 12:23:13 +03:00
Dmitry Zuikov fe908dcacb ghc version bump in Makefile 2024-09-25 11:36:46 +03:00
b0oh dc9a86a603 Check if config already exists for hbs2-sync init 2024-09-25 11:34:28 +03:00
b0oh aa9355e7d7 Make a better report for invalid author argument in hbs2-sync init 2024-09-25 11:33:50 +03:00
b0oh ba5665fe2d Add init --auto to hbs2-sync 2024-09-25 11:33:37 +03:00
Dmitry Zuikov 835c01bfaa ghc-9.6.6 + updated db-pipe 2024-09-25 11:28:17 +03:00
Dmitry Zuikov c240b8ad9e fixme-new 2024-09-25 11:19:41 +03:00
Dmitry Zuikov c66cb4a8ee fixme template changed 2024-09-16 07:38:12 +03:00
Dmitry Zuikov 84f801e6d6 added source function to add per-user configs 2024-09-15 11:20:03 +03:00
Dmitry Zuikov ff9ef2ddec merged refchan notifications and fixme-new (wip) 2024-09-15 10:20:14 +03:00
Dmitry Zuikov 0bba3721e6 fixed type in flake.nix 2024-09-02 19:10:25 +03:00
Dmitry Zuikov c9ae19bc76 cabal build fix 2024-08-30 06:05:15 +03:00
Dmitry Zuikov 7e2dd9ba56 fixed fuzzy-parse dependency reference 2024-08-27 09:31:25 +03:00
Dmitry Zuikov 58fecd442b indexed keys, hbs2-keyman and hbs2-sync updated 2024-08-27 09:16:10 +03:00
Vladimir Krutkin f7119564fb Added the manifest update command, fixed bugs 2024-08-26 17:15:13 +03:00
Dmitry Zuikov 573a9f3377 wip, hbs2-sync rc 2024-08-09 15:38:28 +03:00
Dmitry Zuikov 7b7a89f13d wip 2024-08-08 20:58:54 +03:00
Dmitry Zuikov 0053a95f27 wip 2024-08-08 19:18:25 +03:00
Dmitry Zuikov 6b87966e2a minor issue fix 2024-08-08 18:55:19 +03:00
Dmitry Zuikov ccaa7d1687 wip, somehow works with sqlite 2024-08-08 18:42:25 +03:00
Dmitry Zuikov 8bb16c352f wip, cursed 2024-08-08 17:01:23 +03:00
Dmitry Zuikov 975bb8cb12 wip 2024-08-08 11:32:01 +03:00
Dmitry Zuikov fde773522c wip, keymanClient from busyloop 2024-08-08 05:52:00 +03:00
Dmitry Zuikov 2761af5d60 Revert "not-good"
This reverts commit 7e0305891b.
2024-08-08 05:26:47 +03:00
Dmitry Zuikov 8a8e347a35 Revert "Revert "wip, speedup""
This reverts commit 68c8c7bef3.
2024-08-08 05:26:39 +03:00
Dmitry Zuikov 68c8c7bef3 Revert "wip, speedup"
This reverts commit 7b69d85dd9.
2024-08-08 05:26:13 +03:00
Dmitry Zuikov 7e0305891b not-good 2024-08-08 05:25:59 +03:00
Dmitry Zuikov 7b69d85dd9 wip, speedup 2024-08-07 21:54:13 +03:00
Dmitry Zuikov dfe9d0ba9b wip, cat command 2024-08-07 19:39:14 +03:00
Dmitry Zuikov 71bad89b7d fixed fuzzy-parse reference 2024-08-07 18:09:49 +03:00
Dmitry Zuikov 9bab121743 merged hbs2-cli ans hbs2-sync 2024-08-07 15:20:19 +03:00
Dmitry Zuikov 557e0f1b90 hbs2 metadata create preformance fix 2024-06-17 06:34:38 +03:00
Dmitry Zuikov 9aafab745d hbs2-peer reflog cat 2024-04-09 13:09:13 +03:00
Dmitry Zuikov 958bedc7ed .hbs2-git to gitignore 2024-04-09 12:22:46 +03:00
Dmitry Zuikov 26a023d60d hbs2-peer refchan cat && hbs2-peer reflog cat 2024-04-09 12:04:13 +03:00
Dmitry Zuikov e9eaae2795 fixme 2024-04-02 07:03:56 +03:00
558 changed files with 54756 additions and 11803 deletions

1
.envrc
View File

@ -1,3 +1,4 @@
## wtf
if [ -f .envrc.local ]; then
source_env .envrc.local
fi

2
.fixme-new/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
state.db
config.local

78
.fixme-new/config Normal file
View File

@ -0,0 +1,78 @@
; fixme-files **/*.hs docs/devlog.md
; no-debug
; debug
fixme-prefix FIXME:
fixme-prefix TODO:
fixme-prefix PR:
fixme-prefix REVIEW:
fixme-prefix PATCH:
fixme-attribs assigned workflow :class
fixme-attribs class
fixme-attribs :committer-name :commit-time
fixme-value-set :workflow :new :backlog :wip :test :fixed :done :ready :merged
fixme-value-set class hardcode performance boilerplate ui
; fixme-value-set cat bug feat refactor
fixme-value-set scope mvp-0 mvp-1 backlog
fixme-files **/*.txt docs/devlog.md
fixme-files **/*.hs
fixme-exclude **/.**
fixme-exclude dist-newstyle
fixme-exclude miscellaneous
fixme-file-comments "*.scm" ";"
fixme-comments ";" "--"
(define-template short
(quot
(simple
(trim 10 $fixme-key) " "
(if (~ FIXME $fixme-tag)
(then (fgd red (align 6 $fixme-tag)) )
(else (if (~ TODO $fixme-tag)
(then (fgd green (align 6 $fixme-tag)))
(else (align 6 $fixme-tag)) ) )
)
(align 10 ("[" $workflow "]")) " "
(align 8 $class) " "
(align 12 $assigned) " "
(align 20 (trim 20 $committer-name)) " "
(trim 40 ($fixme-title)) " "
(nl))
)
)
(set-template default short)
(define (ls) (report))
(define (lss s) (report workflow ~ s))
(define (done s) (modify s workflow :done))
(define (wip s) (modify s workflow :wip))
(define (test s) (modify s workflow :test))
(define (new s) (modify s workflow :new))
(define (backlog s) (modify s workflow :backlog))
(define (ready s) (modify s workflow :ready))
(define (merged s) (modify s workflow :merged))
(define (assign s who) (modify s :assigned who))
;; refchan settings
refchan 3WtddmcE8zzgBAPR7Bu7mKMaVMTN423NNXSPUJp3Hx42
source ./refchan.local

BIN
.fixme-new/fixme.log Normal file

Binary file not shown.

View File

@ -20,8 +20,7 @@ fixme-files docs/notes/**/*.txt
fixme-files-ignore .direnv/** dist-newstyle/**
fixme-id-show-len 10
fixme-id-show-len 12
fixme-attribs assigned workflow resolution cat scope

View File

@ -1,2 +1,3 @@
(fixme-set "workflow" "done" "RsTry2C5Gk")
(fixme-set "workflow" "done" "DYfcfsNCrU")

2
.gitattributes vendored Normal file
View File

@ -0,0 +1,2 @@
.fixme-new/log merge=fixme-log-merge
.fixme-new/fixme.log merge=fixme-log-merge

15
.gitignore vendored
View File

@ -1,12 +1,7 @@
dist-newstyle
.fixme-new/refchan.local
dist-newstyle/
bin/
.direnv/
.fixme/state.db
result
# VS Code
settings.json
.hbs2-git3/
cabal.project.local
*.key
.backup/
temp/

View File

@ -1,5 +0,0 @@
title: "hbs2 project repo"
author: "Dmitry Zuikov"
public: yes
Project description TBD

View File

@ -1,3 +1,4 @@
# 0.24.1.1 2024-04-02
- Don't do HTTP redirect on /ref/XXXXXXXXXX requests; show content directly
# 0.24.1.2 2024-04-27
- Bump scotty version

31
LICENSE Normal file
View File

@ -0,0 +1,31 @@
Copyright (c) 2023, 2024
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following
disclaimer in the documentation and/or other materials provided
with the distribution.
* Neither the name of nor the names of other
contributors may be used to endorse or promote products derived
from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@ -5,22 +5,52 @@ SHELL := bash
MAKEFLAGS += --warn-undefined-variables
MAKEFLAGS += --no-builtin-rules
GHC_VERSION := 9.4.8
RT_DIR := test/RT
VPATH += test/RT
RT_FILES := $(wildcard $(RT_DIR)/*.rt)
OUT_FILES := $(RT_FILES:.rt=.out)
GHC_VERSION := 9.6.6
BIN_DIR := ./bin
BINS := \
bf6 \
hbs2 \
hbs2-peer \
hbs2-keyman \
hbs2-fixer \
hbs2-git-subscribe \
git-remote-hbs2 \
git-hbs2 \
hbs2-cli \
hbs2-sync \
fixme-new \
hbs2-git3 \
git-remote-hbs23 \
ncq3 \
hbs2-obsolete \
tcq \
test-ncq \
RT_DIR := tests/RT
ifeq ($(origin .RECIPEPREFIX), undefined)
$(error This Make does not support .RECIPEPREFIX. Please use GNU Make 4.0 or later)
endif
.RECIPEPREFIX = >
rt: $(OUT_FILES)
%.out: %.rt
> @hbs2-cli --run $< > $(dir $<)$(notdir $@)
> @hbs2-cli \
[define r [eq? [parse:top:file root $(dir $<)$(notdir $@)] \
[parse:top:file root $(dir $<)$(basename $(notdir $@)).baseline]]] \
and [print '"[RT]"' space \
[if r [ansi green _ [concat ✅ OK space space]] \
[ansi red~ _ [concat ❌FAIL]]] \
: space $(notdir $(basename $@))] \
and println
> $(RM) $(dir $<)$(notdir $@)
$(BIN_DIR):
> @mkdir -p $@
@ -32,11 +62,15 @@ symlinks: $(BIN_DIR)
> path=`find dist-newstyle -type f -name $$bin -path "*$(GHC_VERSION)*" | head -n 1`; \
> if [ -n "$$path" ]; then \
> echo "Creating symlink for $$bin"; \
> ln -sf $$PWD/$$path $(BIN_DIR)/$$bin; \
> ln -sfn $$PWD/$$path $(BIN_DIR)/$$bin; \
#> cp $$PWD/$$path $(BIN_DIR)/$$bin; \
> else \
> echo "Binary $$bin for GHC $(GHC_VERSION) not found"; \
> fi; \
> done
> ln -sfn ../hbs2-git3/bf6/git-hbs2 bin/git-hbs2
> ln -sfn ../hbs2-git3/bf6/hbs2-git bin/hbs2-git
> ln -sfn ../bf6/hbs2 bin/hbs2
.PHONY: build
@ -52,8 +86,8 @@ test-raft:
> nix develop -c ghcid -c 'cabal repl' raft-algo -T RaftAlgo.Proto.devTest
README.md:
pandoc README.md -t gfm -s -o README1.md --table-of-contents
@mv README1.md README.md
@echo Remove old TOC before publishing!
> pandoc README.md -t gfm -s -o README1.md --table-of-contents
> @mv README1.md README.md
> @echo Remove old TOC before publishing!

View File

@ -142,6 +142,8 @@ Were using it for our non-public projects.
## How to install
### nix flakes
Assuming you know what the Nix and Nix flakes are ( See
[nixos.org](https://nixos.org) if you dont )
@ -158,6 +160,54 @@ Alternative option:
--substituters http://nix.hbs2.net:6000 \
--trusted-public-keys git.hbs2.net-1:HYIYU3xWetj0NasmHrxsWQTVzQUjawOE8ejZAW2xUS4=
### Home Manager module
The following snippet of code tries to show how to bring the HBS2 flake
from the flake input and use its packages with Home Manager.
Dont forget to replace exampleName with your username!
```nix
# flake.nix
{
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
home-manager = {
url = "github:nix-community/home-manager";
inputs.nixpkgs.follows = "nixpkgs";
};
hbs2.url = "git+https://git.hbs2.net/BTThPdHKF8XnEq4m6wzbKHKA6geLFK4ydYhBXAqBdHSP";
};
outputs = {nixpkgs, home-manager, hbs2, ...}: {
homeConfigurations."exampleName" =
let system = "x86_64-linux"; # use your system here
in home-manager.lib.homeManagerConfiguration {
pkgs = nixpkgs.legacyPackages.${system};
modules = [
hbs2.homeManagerModules.${system}.default
{
services.hbs2 = {
enable = true;
git-dashboard.enable = true; # optional
};
}
# ...
];
};
};
}
```
Option `services.hbs2.enable` will add all hbs2 binaries into your environment and
create `hbs2-peer` user service to run it automatically at the background.
Option `services.hbs2.git-dashboard.enable` will create `hbs2-git-dashboard` user service.
## How to generate peers key?
hbs2 keyring-new > new-peer-key.key

206
bf6/hbs2 Executable file
View File

@ -0,0 +1,206 @@
#! /usr/bin/env -S hbs2-cli file
; # println *args
; # (println (grep (sym "-g") *args))
(define (--help)
(begin
(println [concat
"hbs2-cli wrapper" chr:lf
"supported commands list:" chr:lf
" "
])
)
)
(match *args
( (list? [sym? store] ...)
(begin
(local optdef
`( [-g 1 GROUPKEY]
[--group-key 1 GROUPKEY] ))
(local split (cli:split optdef ...))
(local opts (nth 0 split))
(local args (nth 1 split))
(local gk (@? GROUPKEY opts))
(local fname (head args))
(local kwa `[ ,(if gk [list :gk gk] '() ) ])
; (display kwa)
(println
(if fname
(hbs2:tree:metadata:file kwa fname)
(hbs2:tree:metadata:stdin kwa)))
)
)
( (list? [sym? hash] ...)
(begin
(local what (if (eq? (type ...) :list) ... '()))
(display (eval `(hbs2:hash ,@what)))
)
)
( (list? [sym? has] hash)
(begin
(local s (hbs2:peer:storage:block:size hash))
(cond
( (eq? :no-block s) (die))
( _ (print s))
))
)
( (list? [sym? cat] ...)
(begin
(local optdef `( [-H 0 HASHES]
[--raw 0 RAW]
[--metadata 0 META]
[--m 0 META]
))
(local parsed (cli:split optdef ...))
(local opts (nth 0 parsed))
(local hash (head (nth 1 parsed)))
(if (@? HASHES opts)
(begin
(iterate [fn x . println x] [tail [hbs2:tree:scan:deep hash]])
(quit)
))
(if (@? RAW opts)
(begin
(bytes:put (hbs2:peer:storage:block:get hash))
(quit)
)
)
(if (@? META opts)
(begin
(display (hbs2:tree:metadata:get hash))
(quit)
)
)
(hbs2:tree:read:stdout hash)
)
)
( (list? [sym? del] ...)
(begin
(local optdef `( [-y 0 YES]
[-r 0 REC]
))
(local parsed (cli:split optdef ...))
(local opts (nth 0 parsed))
(local hash (head (nth 1 parsed)))
(local hashes
(cond
( (@? REC opts) (hbs2:tree:scan:deep hash) )
( _ (hbs2:tree:scan hash) )
)
)
(define (ask ha)
(if (@? YES opts) true
(begin
(print "deleting " ha " ")
(print "sure [y/n]? ") (flush:stdout)
(local answ (str:getchar:stdin))
(newline)
(eq? (upper answ) "Y")
))
)
(cond
( (and (@? YES opts) (@? REC opts)) (hbs2:tree:delete hash))
( _
(for (reverse hashes)
[fn ha .
[begin
(local y (or (@? YES opts) (ask ha)))
(if y
(begin
(hbs2:peer:storage:block:del ha)
))
]])
)
)
)
)
( (list? [sym? keyring] [sym? new] ...)
(begin
(local optdef `( [-n 1 NUM]
[--number 1 NUM]
))
(local opts (nth 0 (cli:split optdef ...)))
; (println opts)
(print (hbs2:keyring:new (@? NUM opts)))
)
)
( (list? [sym? reflog] [sym? get] hash)
(display (hbs2:reflog:get hash))
)
( (list? [sym? reflog] [sym? fetch] hash)
(hbs2:reflog:fetch hash)
)
( (list? [sym? metadata] [sym? dump] hash)
(display (hbs2:tree:metadata:get hash))
)
( (list? [sym? deps] hash)
(iterate println (hbs2:tree:scan:deep hash) )
)
( (list? [sym? fsck] [sym? -h])
(begin
(println "usage: hbs2 fsck <PATH>")
(println "default for <PATH> is hbs2-peer storage path")
)
)
( (list? [sym? fsck] ...)
(begin
(local sto1 (if (eq? (type ...) :list) (car ...) '()))
(if sto1
(run:proc:attached tcq ncq:fsck (concat sto1 :/ :0))
(begin
(local answ (fallback #f '(call:proc hbs2-peer poke)))
(unless answ (die "hbs2-peer seems down, but you may pass storage directory manually"))
(local sto (lookup:uw storage: answ))
(println (ansi :red _ "check") space sto)
(run:proc:attached tcq ncq:fsck (concat sto :/ :0))
)
)
)
)
( _ (--help) )
)
; vim: filetype=scheme syntax=scheme

75
bf6/ncq-migrate.ss Normal file
View File

@ -0,0 +1,75 @@
(define STORAGE (path:join (env HOME) .local/share/hbs2 ))
(define REFS (path:join STORAGE refs) )
(define BLOCKS (path:join STORAGE blocks) )
(define NEW (path:join (env HOME) tmp/ncq0))
(define refs (glob REFS '[*/**] ))
(define blocks (glob BLOCKS '[*/**] ))
(define (readref x)
(begin
(local ref (concat (reverse (take 2 (reverse (split :/ x))))))
(local refval (str:file x))
`(,(sym ref) ,(sym refval))))
(define (readhash x)
(sym (concat (reverse (take 2 (reverse (split :/ x))))))
)
(local zu (map readref refs))
; (println zu)
(println STORAGE)
(println NEW)
; debug
(define ncq (ncq:open NEW))
(define (writeref x)
(match x
( (list? a b )
(begin
(ncq:set:ref ncq a b)
(println ref space a space b)
)
)
(_ '())
))
(define (import-refs) (for zu writeref))
(define (import-blocks)
(begin
; (local (write x) (ncq:put ncq (bytes:file x)))
(for blocks (fn x .
(begin
(local h0 (sym (readhash x)))
(local here (ncq:has ncq h0))
(if (not here)
(begin
(local ha (sym (ncq:put ncq (bytes:strict:file x))))
(local s (coalesce "" (ncq:has ncq ha)))
(local ok (if (eq? ha h0) (ansi :green _ ok) (ansi :red _ fail)))
(println block space ok space (align -6 (str s)) space ha space h0 space )
; (println block space ok space space ha space h0 space )
(if (not (eq? ha h0)) (die "*** block import error:" ha space h0)))
(println "block" space (ansi :yellow _ "skip") space h0)
)
)))
)
)
(import-blocks)
(import-refs)
(debug)
(ncq:fossilize ncq)
(println done)

View File

@ -1,9 +1,20 @@
packages: **/*.cabal
examples/*/*.cabal
**/*/*.cabal
allow-newer: all
constraints:
pandoc >=3.1.11
, suckless-conf >= 0.1.2.7
, http-client >=0.7.16 && <0.8
, typed-process >= 0.2.13.0
debug-info: True
-- executable-static: True
-- profiling: True
-- library-profiling: False
--library-profiling: False
debug-info: True

View File

@ -1,3 +1,7 @@
## 2024-12-02
Пробуем новую структуру репозитория
## 2024-02-24
wtf?

View File

@ -5,7 +5,7 @@ $(basename $(1))-$(REV)$(suffix $(1))
endef
all: hbs2-git-problem hbs2-git-new-repo hbs2-git-doc
all: hbs2-git-problem hbs2-git-new-repo hbs2-git-doc hbs2-mailbox
.PHONY: all clean
@ -20,13 +20,23 @@ hbs2-git-new-repo: hbs2-git-new-repo.pdf
hbs2-git-doc: hbs2-git-doc-0.24.1.pdf
hbs2-mailbox: hbs2-mailbox.pdf
publish-hbs2-git-doc: hbs2-git-doc-0.24.1.pdf
$(eval TARGET := $(call make_target,$<))
$(eval HASH := $(shell hbs2 metadata create --hash --auto $(TARGET)))
@echo Updating $(HBS2GITDOCLWW) $(HASH)
hbs2-peer lwwref update -v $(HASH) $(HBS2GITDOCLWW)
publish: publish-hbs2-git-doc
publish-hbs2-mailbox: hbs2-mailbox.pdf
@echo not implemented yet
# $(eval TARGET := $(call make_target,$<))
# $(eval HASH := $(shell hbs2 metadata create --hash --auto $(TARGET)))
# @echo Updating $(HBS2GITDOCLWW) $(HASH)
# hbs2-peer lwwref update -v $(HASH) $(HBS2GITDOCLWW)
publish: publish-hbs2-git-doc publish-hbs2-mailbox
clean:
rm -f *.aux *.log *.nav *.out *.snm *.vrb *.toc *.pdf

View File

@ -668,8 +668,8 @@ Cloning into '8vFu9S79ysdWag4wek53YWXbC5nCRLF7arGp6181G4js'...
git hbs2 export --encrypted ./gk-new.key C6tTuapmG7sE8QktQo4q4tBr8kNWKvBruNb36HYThpuy
\end{verbatim}
Для обновления манифеста --- редактировать файл \texttt{.hbs2-git/manifest} и сделать
git commit/push либо же вызвать \texttt{git hbs2 export <LWWREF>}
Для обновления манифеста --- редактировать файл \texttt{.hbs2-git/manifest}
и вызвать \texttt{git hbs2 manifest update <LWWREF>}
\subsubsection{Смотреть групповой ключ}

View File

@ -0,0 +1,758 @@
%
\documentclass[11pt,a4paper]{article}
\usepackage{polyglossia}
\usepackage{xltxtra}
\usepackage[margin=2cm,a4paper]{geometry}% http://ctan.org/pkg/geometry
\usepackage{pdfpages}
\usepackage{graphicx}
\usepackage[ddmmyyyy]{datetime}
\usepackage{booktabs}
\usepackage{enumitem}
\usepackage{amssymb}
\usepackage{amsmath}
\usepackage{bm}
\usepackage[nomessages]{fp}
\usepackage{caption}
\usepackage{url}
\usepackage{indentfirst}
\usepackage[parfill]{parskip}
\usepackage[ colorlinks=true
, linkcolor=black
, anchorcolor=black
, citecolor=black
, filecolor=black
, menucolor=black
, runcolor=black
, urlcolor=blue]{hyperref}
\usepackage{tikz}
\usetikzlibrary{arrows,snakes,shapes,backgrounds,positioning,calc}
\usepackage{marvosym}
\usepackage{pifont}
\usepackage{fontspec}
\usepackage{fontawesome5}
\usepackage{listings}
\usepackage{verbatim}
\usepackage{xcolor}
\usepackage{float} % Needed for the floating environment
\setmainlanguage{russian}
\defaultfontfeatures{Ligatures=TeX,Mapping=tex-text}
\setmainfont{Liberation Serif}
\newfontfamily\cyrillicfont{Liberation Serif}[Script=Cyrillic]
\newfontfamily{\cyrillicfonttt}{Liberation Mono}[Scale=0.8]
\setlist{noitemsep}
\setlength{\intextsep}{2cm}
\newcommand{\term}[2]{\textit{#2}}
\newcommand{\Peer}{\term{peer}{пир}}
\newcommand{\Relay}{\term{relay}{Relay}}
\newcommand{\Acc}{\term{acc}{Accumulator}}
\newcommand{\Dude}{\term{dude}{Dude}}
\newcommand{\Mailbox}{\term{mailbox}{Mailbox}}
\renewcommand{\dateseparator}{.}
\renewcommand*\contentsname{Содержание}
\lstset{
language=Haskell,
basicstyle=\ttfamily\small,
keywordstyle=\color{blue},
commentstyle=\color{green},
stringstyle=\color{red},
% numberstyle=\tiny\color{gray},
% numbers=left,
% stepnumber=1,
showstringspaces=false,
breaklines=true,
frame=single,
}
\newfloat{Code}{t}{myc}
\graphicspath{ {img/}}
\title{Протокол <<Mailbox>>}
\begin{document}
\maketitle
\section{О документе}
Документ рассматривает протокол доставки данных <<Mailbox>> по паттерну $*
\rightarrow 1$ <<email>> в P2P окружении, как подпротокола для hbs2-peer.
Протокол предполагается к использованию в ситуациях, когда между
\term{actor}{акторами} нет общего авторизованного канала связи (в смысле
hbs2-peer).
Протокол не подразумевает нахождения акторов постоянно онлайн.
Протокол не подразумевает использования механизмов вроде DNS, сертификатов PKCS
и Authority, или каких-либо (скомпрометированных) централизованных сервисов.
Протокол не подразумевает постоянной связности сети.
Для адресации используются публичные ключи подписи.
Для E2E шифрования используется механизм групповых ключей.
Для упаковки и распространения данных используются примитивы hbs2-peer:
\term{block}{блоки}, \term{merkle}{(шифрованные) деревья Меркла} с метаданными,
и протоколы для работы с ними.
Отличие от протоколов IMAP,SMTP,POP3 в том, что это другой протокол для другого
окружения и исходящий из других предпосылок.
Теоретически, в качестве несложного упражнения, можно поднять сервер IMAP как
локальный фронтенд для hbs2-peer и тогда это будет IMAP-via-P2P.
\section{Предпосылки}
В текущей реализации HBS2 существуют следующие релевантные виды каналов
(протоколов,\term{ref}{ссылок}):
\paragraph{RefLog:}
Обеспечивает коммуникацию по паттерну $1 \rightarrow *$, то есть один -- ко
всем, канал распространяет сообщения одного автора. Пруфом записи является
подпись \term{ksign}{ключом подписи} автора. \term{peer}{Пиры} должны
подписаться на канал для его распространения, распространять канал (ссылку)
может любой любой подписанный на него \term{peer}{пир}, так как валидность
записей проверяется подписью автора. Канал является \term{GSET}{CRDT G-SET}
записей.
Метафорой рефлога может являться твиттер-аккаунт либо канал в телеграме, с одним
писателем и множеством подписчиков.
\paragraph{RefChan:}
Обеспечивает коммуникацию по паттерну ${A} \rightarrow {R}$, то есть определяет
множество \term{author}{авторов} $A$ и множество \term{reader}{читателей} $R$, и
пруфом записи является подпись \term{author}{автора}, а
\term{permission}{разрешением} на чтение --- опциональное шифрование сообщения
\term{GK0}{групповым ключом}, куда входят читатели $R$, то есть $GK = \{ k_i
\}_{i \in R}$, где каждый $k_i$ --- секретный ключ, зашифрованный публичным
ключом $r_i$ из множества $R$.
Кроме того, \term{refchan}{RefChan} определяет множество пиров ${P}$, которые
могут отправлять сообщение в данный \term{refchan}{RefChan} и принимаются только
такие сообщения.
Данное ограничение необходимо для борьбы с атакой Сивиллы в случае, если \Peer{}
игнорирует настройки ${A}$.
Кроме того, у \term{refchan}{рефчана} есть владелец, который может менять
настройки $A,R$, а блок настроек представляет собой \term{lww}{CRDT LWW регистр}
со ссылкой на блок настроек, подписанный ключом владельца.
Как видно, распространять сообщения из \term{refchan}{рефчана} могут только пиры
$p_i \in P$
То есть, распространять транзакции может кто угодно, т.к каждая транзакция
подписана ключом \term{peer}{пира}, но вот при запросе состояния будут
учитываться только ответы пиров $p_i \in P$.
Метафорой \term{refchan}{рефчана} является модерируемый чат с ограниченным
множеством участников и администраторами.
Таким образом, при наличии этих протоколов, мы можем
\begin{enumerate}
\item посылать сообщения от одного автора всему миру, то есть тем пирам, которые
слушают (подписаны) на данный рефлог или
\item осуществлять коммуникацию между ограниченными множествами пиров и
авторов/читателей.
\end{enumerate}
Общим является то, что бы получать обновления рефлога или рефчана, мы (как пир)
должны быть на них \term{subscribed}{подписаны}, т.е мы должны знать, что такие
\term{ref}{ссылки} существуют и явно на них подписаться.
Возникает вопрос, как можно обеспечить коммуникацию между произвольными
\term{actor}{акторами} Алиса и Боб, у которых нет общего канала.
Куда писать Алисе, что бы её сообщение достигло Боба? Рефчана, куда бы входили
бы и Алиса и Боб в общем случае еще не существует, канал связи отсутствует.
Алиса может быть подписана на какую-то ссылку Боба, но Боб не подписан на каналы
Алисы. Или наоборот.
Предлагается ввести новый протокол, \term{mailbox}{Mailbox}, который будет
обеспечивать коммуникацию по паттерну $ * \rightarrow 1 $, то есть кто угодно
может отправлять сообщения в почтовый ящик получателя.
Получатель проверяет почтовый ящик и забирает оттуда сообщения.
При этом обеспечивается отправка и доставка в условиях, когда \term{peer}{пиры}
получателя и отправителя не находятся онлайн всё время.
Данный протокол может быть полезен при установлении канала связи (например,
создании общего рефчана), или просто оффлайн обмене сообщениями в условиях
необязательного наличия каналов, например, при рассылке патчей и пулл/мерж
реквестов в git или создании тикетов или для отсылки \textit{реакций}, в общем
--- в любом случае, когда между акторами нет какого-то прямого канала.
Важным является то, что получатель подписан только на свои, известные ему
каналы, куда все (при выполнении определённых условий) могут отправлять
сообщения.
\section{Протокол}
Протокол является подпротоколом \textit{hbs2-peer} и в отношении него верно всё,
что верно для семейства этих протоколов --- авторизация и аутентификация пиров,
черные и белые списки пиров, транспортное шифрование сообщений через ByPass и
так далее.
Идентификаторами являются публичные ключи подписи и шифрования.
Для e2e шифрования используется тот же механизм групповых ключей.
Передаваемыми единицами являются либо короткие сообщения
\texttt{SmallEncryptedBlock} либо \term{merkle}{деревья Меркла} с шифрованием и
метаданными.
Протокол использует примитивы \textit{hbs2-core} и \textit{hbs2-peer}, как
минимум:
\begin{itemize}
\item[-] SignedBox
\item[-] SmallEncryptedBlock
\item[-] MerkleTree
\end{itemize}
Протокол определяет служебные сообщения, специфичные для него, однако обмен
данными идёт через обычные протоколы (GetBlock,GetBlockSize).
Короткие сообщения могут доставляться непосредственно через (сигнальные)
сообщения протокола.
\subsection{Участники}
\paragraph{Пир} Узел hbs2, поддерживающий данный протокол
\paragraph{Актор} также \term{dude}{Dude}. Отправители и получатели сообщений.
Требуется определить, что явлется идентификатором, или идентификаторами \Dude{}.
\paragraph{Message} Сообщение.
Определяется отправителем, получателем (получателями?), и содержимым.
Видится,что сообщения могут быть двух классов: \textit{маленькое}, где всё
сообщение вместе со служебной информацией помещается в один пакет и может быть
доставлено непосредственно через коммуникационный протокол (GOSSIP), и
\textit{большое}, когда \Peer{} поддерживающий данный протокол -- будет
выкачивать все ссылки на части сообщения (большой текст, аттачменты и т.п.)
\paragraph{Mailbox} Единица хранения и распространения сообщений.
Mailbox бывают видов \term{Relay}{Relay} и \term{Accumulator}{Accumulator}.
Разница между ними в том, что \Relay{} просто принимает и выкачивает сообщения,
пришедшие по протоколу, и не пытается опрашивать соседей и объединять все
известные сообщения дла \Dude{} в общее множество.
Назначание \Relay{} --- временное хранение сообщений, пока их не заберёт один из
\term{acc}{аккумуляторов}. \Mailbox{} \Relay{} занимает фиксированное, заранее
определенное место на диске
Поскольку мы в общем не знаем, забрали ли сообщение или нет, видится так, что
\Relay{} организует ограниченную очередь сообщений, и при исчерпании лимита
места, отведённого под почтовый ящик -- просто удаляет наиболее старые сообщения
из очереди.
Назначание \Acc{} -- хранить все сообщения для своего \Dude{}, т.е это его
<<распределённый почтовый аккаунт>>.
То есть, \Acc{} образуют \term{GSET}{CRDT G-SET} сообщений, и постепенно
сходятся к одному значению (объединению всех сообщений всех \Acc{}).
Очевидно, нужно предусмотреть или записи вида \textit{Tomb}, или иной способ
удаления сообщений, например, через команду протокола.
\Acc{} опрашивает всех соседей, получает ссылки на \term{merkle}{деревья~Меркла}
сообщений, выкачивает сообщения и объединяет их в общее множество.
\subsection{Примеры}
\subsection*{Минимальная конфигурация}
Два пира при условии наличия прямой сетевой доступности в обоих направлениях.
\begin{figure}[h!]
\centering
\begin{tikzpicture}[ every label/.style={font=\scriptsize},
every node/.style={font=\scriptsize},
handle/.style={ draw=black
, circle
, inner sep=2pt
},
box/.style={ draw=black
, rounded corners,
, anchor=base
, font=\scriptsize
, minimum height=1.5cm
, text width=1.5cm
, align=center
},
]
\node[box,minimum height=2cm,label={below:{hbs2-peer}}] (dudeA) {{\underline{Dude~A}}\\ \Acc{}};
\node[ box
, minimum height=2cm
, label={below:{hbs2-peer}}
, right=2.5cm of dudeA
] (dudeB) {{\underline{Dude~B}}\\ \Acc{}};
\draw[<->] (dudeA) -- (dudeB)
node[midway,above] {Mailbox}
node[midway,below] {GOSSIP};
\end{tikzpicture}
\caption{минимальная конфигурация}
\end{figure}
\pagebreak
\begin{itemize}
\item[-] Обмен сообщениями возможен только при одновременном нахождении обоих
пиров онлайн и наличия между ними связи
\item[-] При потере узла Dude~A или Dude~B теряют все адресованные им сообщения
\end{itemize}
\subsection*{Примерно оптимальная конфигурация}
\begin{figure}[h!]
\centering
\begin{tikzpicture}[ every label/.style={font=\scriptsize},
every node/.style={font=\scriptsize},
handle/.style={ draw=black
, circle
, inner sep=2pt
},
box/.style={ draw=black
, rounded corners,
, anchor=base
, font=\scriptsize
, minimum height=1.5cm
, text width=1.5cm
, align=center
},
]
\node[box,minimum height=2cm,label={below:{hbs2-peer}}] (dudeA) {{\underline{Dude~A}}\\ \Acc{}};
\node[ box
, minimum height=2cm
, label={below:{hbs2-peer}}
, right=1.5cm of dudeA
] (relayA) {{\underline{Relay~1}}\\ \Relay{}};
\node[ box
, minimum height=2cm
, label={below:{hbs2-peer}}
, below=1.5cm of dudeA
] (A1) {{\underline{A1}}\\ \Acc{}};
\node[ box
, minimum height=2cm
, label={below:{hbs2-peer}}
, right=1.5cm of relayA
] (relayB) {{\underline{Relay~2}}\\ \Relay{}};
\node[ box
, minimum height=2cm
, label={below:{hbs2-peer}}
, right=1.5cm of relayB
] (dudeB) {{\underline{Dude~B}}\\ \Acc{}};
\node[ box
, minimum height=2cm
, label={below:{hbs2-peer}}
, below=1.5cm of dudeB
] (B1) {{\underline{B1}}\\ \Acc{}};
\node[ box, circle, draw, dashed
, minimum size=2.5cm
, minimum height=2.5cm
, yshift=-0.5cm
, right=2.75cm of A1
, label={below: protocol}
] (gossip) {{\underline{Mailbox}}\\GOSSIP };
\draw[<->,dashed] (dudeA) -- (relayA);
\draw[<->,dashed] (dudeB) -- (relayB);
\draw[<->,dashed] (dudeA) -- (A1);
\draw[<->,dashed] (dudeB) -- (B1);
\draw[<->,dashed] (dudeA) -- (gossip);
\draw[<->,dashed] (dudeB) -- (gossip);
\draw[<->,dashed] (relayA) -- (gossip);
\draw[<->,dashed] (relayB) -- (gossip);
\draw[<->,dashed] (A1) -- (gossip);
\draw[<->,dashed] (B1) -- (gossip);
\end{tikzpicture}
\caption{Примерно оптимальная конфигурация}
\end{figure}
\begin{itemize}
\item[-] Каждый Dude имеет некоторое количество Mailbox типа \Acc{} и \Relay{}.
\item[-] Часть из них находится на пирах, которые большую часть времени
доступны.
\item[-] Часть доступных пиров имеет между собой прямую связь по GOSSIP.
\item[-] Не требуется полная связность сети между Dude~A и Dude~B, достаточно,
что бы была цепочка соединений, доступных хотя бы время от времени.
\item[-] Сообщения Dude~A и Dude~B реплицированы между узлами типа \Acc{} (для
каждого Dude -- свои мейлбоксы, естественно) и сообщения будут утрачены
только в случае полной одновременной утраты всех узлов такого типа или если
на всех этих узлах будут удалены \term{mailbox}{мейлбоксы} для Dude~A или
Dude~B.
\end{itemize}
\pagebreak
\section{Структуры данных}
\subsection{Message}
\begin{figure}[h!]
\centering
\begin{tikzpicture}[ every label/.style={font=\scriptsize},
every node/.style={font=\scriptsize},
handle/.style={ draw=black
, circle
, inner sep=2pt
},
box/.style={ draw=black
, rounded corners,
, anchor=base
, font=\scriptsize
, minimum height=1.5cm
, text width=1.5cm
, align=center
},
]
\node[ draw
, minimum height=2cm
, minimum width=12cm
% , label={[yshift=5mm]south:SignedBox}
] (msg) {};
\node[draw,below=5mm of msg.north west,anchor=north west,xshift=2mm
] (sender) {$Sender$};
\node[above=1.5cm of sender.north west, anchor = south west, text width=1.8cm] (label1) {Публичный ключ отправителя};
\draw[->] (label1.south) -- ($(sender.north west)!(label1.south)!(sender.north east)$);
% \node[draw,below=5mm of msg.north west,anchor=north west,xshift=2mm
\node[draw,right=5mm of sender
] (flags) {$\{F\}$};
\node[draw,right=1mm of flags
] (rcpt) {$\{Recipients\}$};
\node[draw,right=1mm of rcpt
] (gk) {$GK^*$};
\node[draw,right=1mm of gk
] (ref) {$\{Ref\}$};
\node[draw,right=1mm of ref,minimum width=4cm
] (payload) {$Payload$};
\node[above=1.5cm of payload.north west, anchor = south west, text width=2cm]
(labelP) {SmallEncryptedBlock};
\draw[->] (labelP.south) -- ($(payload.north west)!(labelP.south)!(payload.north east)$);
\node[ draw
, above=2mm of flags.north west, xshift=-2.5mm
, anchor=north west
, minimum width = 10cm
, minimum height = 1.1cm
, label={[yshift=-1mm]south:SignedBox}
] (box) {};
\end{tikzpicture}
\caption{Структура сообщения}
\end{figure}
\paragraph{Sender:} Публичный (адрес) ключ подписи отправителя
\paragraph{F:} Флаги (опции) сообщения. Например, TTL. TBD.
\paragraph{Recipients:} Публичные ключи подписи (адреса) получателей
Так как \term{peer}{пиру} нужно знать, в какой \Mailbox{} положить сообщение
\paragraph{GK:} (Опционально) групповой ключ шифрования, которым зашифровано
сообщение
\paragraph{Refs:} Ссылки на части сообщения, (зашифрованные)
\term{merkle}{деревья} с метаданными
\paragraph{Payload:} Непосредственное короткое сообщение
\section{Сообщения протокола}
\subsection{SEND}
Пир~A \Dude~A посылает сообщение \Dude~B или списку \Dude{} через Пир~X.
Если Пир~X не поддерживает протокол -- то сообщение не обрабатывается.
Если Пир~X поддерживает протокол -- то сообщение пересылается соседям Пир~X.
Если Пир~X имеет \Mailbox{} для одного из получателей (\Dude{}) --- то сообщение
кладётся в \Mailbox{}.
Если это \Acc{} -- то просто кладётся. Если задана квота на размер и размер
\Mailbox{} превышен (переполнен), то сообщение может игнорироваться.
Если это \Relay{} то кладётся, если квота размера не превышена. Если превышена,
то удаляются наиболее старые сообщения, пока не освободится достаточно места на
диске.
Если не удалось, то сообщение удаляется.
Если сообщение содержит хэш-ссылки (вложения), то они скачиваются в соответствии
с политиками (размеры,etc).
Каждая ссылка сообщения проверяется на целостность, скачивание продолжается,
пока оно не станет целостным или до тех пор, пока (определяется политикой).
Если пир \Dude~A не имеет блоков, на которые ссылается сообщениe --- то мы
прекращаем скачивать зависимости. Возможно, такое сообщение стоит дропнуть.
Авторизация: сообщение SEND подписано \Dude~A, отправителем сообщения.
\subsection{CHECK-MAIL}
Получатель (владелец \Mailbox{}) запрашивает хэш \term{merkle}{дерева Меркла} сообщений,
содержащихся в \Mailbox{}.
Авторизация: сообщение SEND подписано \Dude --- владельцем \Mailbox{}.
\subsection{MAIL-STATUS}
Ответ на сообщение \texttt{CHECK-MAIL}, содержит хэш ссылку
\term{merkle}{дерева Меркла} сообщений, содержащихся в \Mailbox{}
или признак отсутствия сообщений.
Поведение. Получаем сообщения из дерева, пишем в результат только валидные.
Если затесались невалидные -- то это повод для каких-то действий в отношении
пира, который обслуживает \Mailbox{}.
Авторизация: сообщение подписано \Dude --- владельцем \Mailbox{}.
\subsection{DELETE}
Удалить сообщение для \Mailbox{}.
Содержит признак рассылать по GOSSIP или нет, допустим, оно адресовано только
одному конкретному узлу.
Содержит предикат, какие сообщения удалять (все, для определенного отправителя,
старше, чем X, больше, чем X, и т.п.). TBD.
Полезно для освобождения ресурсов и экономии сетевого трафика.
Опциональное.
Авторизация: сообщение подписано \Dude --- владельцем \Mailbox{}.
\subsection{SET-POLICY}
Устанавливает политики обработки сообщений и \Mailbox{}.
Параметры: GOSSIP (да/нет)
Данные: \term{term}{дерево Меркла} текстового файла с инструкциями.
Авторизация: сообщение подписано \Dude --- владельцем \Mailbox{}.
Инструкции: TBD, расширяемо.
Возможный примерный вид:
\begin{verbatim}
dudes drop *
peers drop *
dudes accept GmtNGbawvxqykjDaBiT3LsqP7xqDPHVTxqfBqoFQ6Mre
dudes accept 3WtddmcE8zzgBAPR7Bu7mKMaVMTN423NNXSPUJp3Hx42
dudes delete G5K9QvFaomXdP4Y9HcYEt3diS2cCWyU8nBd2eTzrcq1j
dude set-pow-factor 94wrDGvcnSitP8a6rxLSTPBhXSwdGYrQqkuk2FcuiM3T 10
peer set-pow-factor Gu5FxngYYwpRfCUS9DJBGyH3tvtjXFbcZ7CbxmJPWEGH 10
peers accept yFSaUfb97ZRtQqzHWdERsR7KJvN8qyWX1M8rJcxnsiu
peer cooldown * 120
peer cooldown yFSaUfb97ZRtQqzHWdERsR7KJvN8qyWX1M8rJcxnsiu 60
dude cooldown * 120
dude cooldown G5K9QvFaomXdP4Y9HcYEt3diS2cCWyU8nBd2eTzrcq1j 300
\end{verbatim}
\section{Возможные атаки и противодействие им}
\subsection{Спам}
Массовые нежелательные рассылки.
\paragraph{Тактика борьбы:}
\begin{itemize}
\item[-] Отвергать сообщения с множеством реципиентов.
\item[-] Вводить cooldown периоды для пиров и \Dude{}.
\item[-] Вводить (общие) белые списки и принимать сообщения только от них.
\item[-] Сделать ненулевой стоимость попадания в белые списки.
\item[-] Ввести иструменты репутации и т.п.
\item[-] Ввести регулируемый PoW на сообщения.
\end{itemize}
\subsubsection{DoS}
Атаки на работоспособность пира и сети в целом.
\subsubsection{Посылка огромных данных}
TBD
\subsubsection{Посылка невалидных данных}
TBD
\subsubsection{Ссылки на отсутствующие данные}
TBD
\subsubsection{Анализ метаданных, построение графа взаимодействий}
Поскольку \texttt{GOSSIP} проходит через пиров и имеет открытые метаданные,
можно сохранять граф коммуникаций и запоминать публичные ключи.
Что бы этому противодействовать -- можно только взаимодействовать с заведомо
надёжными пирами через, возможно, отдельную сеть.
К сожалению.
Для по-настоящему анонимного и неотслеживаемого общения нужно использовать
другие механизмы.
\section{Примеры применения}
\subsection{Issues/Pull requests}
\begin{figure}[h!]
\centering
\begin{tikzpicture}[ every label/.style={font=\scriptsize},
every node/.style={font=\scriptsize},
handle/.style={ draw=black
, circle
, inner sep=2pt
},
box/.style={ draw=black
, rounded corners,
, anchor=base
, font=\scriptsize
, minimum height=1.5cm
, text width=1.5cm
, align=center
},
db/.style={ cylinder
, draw
, fill=gray!10
, minimum height=1cm
, minimum width=1.5cm
, shape border rotate=90
, aspect=0.5
}
]
\node[box] (hbs2-peer1) {hbs2-peer1};
\node[box,right=3cm of hbs2-peer1] (hbs2-peer2) {\underline{hbs2-peer2}\\Relay};
\node[box,below=3cm of hbs2-peer2]
(hbs2-peerN)
{\underline{hbs2-peerN}\\\Acc{}};
\draw[->] (hbs2-peer1) -- (hbs2-peer2)
node[below,midway] {MAILBOX:SEND}
node[above,midway] {PR~Message};
\draw[->] (hbs2-peer2) -- (hbs2-peerN)
node[left,midway] {MAILBOX:SEND}
node[left,midway,yshift=4mm] {PR~Message};
\node[box,right=2cm of hbs2-peerN] (process) {filter-process};
\node[box,right=2cm of process] (fixme) {fixme};
\node[db,right=1cm of fixme,anchor=west,yshift=-4mm] (db) {fixme-state};
\draw[->] (process.150) -- ($(hbs2-peerN.north east)!(process.150)!(hbs2-peerN.south east)$)
node[midway,above] {MAIL-CHECK};
\draw[->] (process.180) -- ($(hbs2-peerN.north east)!(process.180)!(hbs2-peerN.south east)$)
node[midway,above] {READ};
\draw[->] (process) -- (fixme)
node[above,midway] {import};
\draw[->] (fixme.south) -- ($(fixme.south) - (0,+2cm)$) -| (hbs2-peerN.south)
node[below,near start] {refchan:export};
\draw[->] (fixme.east) -- (db.152);
\end{tikzpicture}
\end{figure}
Пользователь формирует сообщение специального вида (plaintext/fixme) которое
посылается по протоколу MAILBOX получателю -- владельцу мейлобокса, который
указан в manifest проекта, как контакт для посылки подобных сообщений.
На некоем хосте существует процесс, который время от времени проверяет
\Mailbox{} и при обнаружении новых сообщений экспортирует их в fixme,
который, в свою очередь, помещает их в RefChan делая доступными и видимыми
для подписчиков этого рефчана.
Обновления данного Issue/PR возможны, если в качестве fixme-key выбран некий
уникальный идентификатор, который и будет указан в каждом сообщении.
\end{document}

View File

@ -0,0 +1,8 @@
TODO: asap-exponential-backoff-on-download
Увеличивать таймаут между запросами блока с
какой-то степенью; достаточно пологой
TODO: download-drop-cli-command
Сделать команду hbs2-peer download drop
которая удалит все активные скачивания из очереди

View File

@ -0,0 +1,30 @@
TODO: fixme-refchan-to-manifest
добавить настройку рефчана для fixme в манифест проекта
TODO: fixme-refchan-allow
добавить настройку для разрешения fixme для проекта.
только если разрешено --- пир подписывается на этот рефчан
и тянет из него issues
TODO: fixme-init
инициализация fixme в каталоге репозитория.
проконтроллировать, что нормально работает с bare
репо
TODO: fixme-refchan-import
встроить обновление стейта fixme в
конвейры hbs2-git-dashboard
(видимо, отдельным конвейром)
FIXME: poll-fixme-refchans
сейчас новые рефчаны с fixme будут подтянуты
только при перезапуске. надо встроить явный
poll
FIXME: commit-and-blob-catch-inconsistency
похоже, возникают ситуации, когда fixme-new захватывает
blob и commit некорректно (из разных коммитов?), и
hbs2-git-dashboard, бывает, не может найти blob в индексе.

View File

@ -0,0 +1,34 @@
FIXME: poll-fixme-refchans
поллить рефчаны fixme и обновлять
в случае изменений.
Сейчас не обновляются
FIXME: commit-cache-inconsistency
Встретилась ситуация, когда commit помечен, как processed, но не все блобы
из него попали в кэш.
Похожие ситуации возникают и в hbs2-git.
Похоже, надо как-то инвертировать подход: когда искомые данные
встречаются в кэше --- отдаём из него, а когда нет --- ищем
в источнике (рефчане, дереве, репозитории).
Значит, в этих источниках должен быть некий индекс.
В git он есть.
В hbs2-git он вроде бы тоже есть.
Возможно, это будет незначительно медленнее при выдаче,
но сильно быстрее при индексации и система будет, типа,
самовосстанавливающаяся.
Возможно, это приведёт к тому, что все схемы выродятся
в таблицу "object", для ускорения доступа к которой
будут создаваться индексные таблицы (aka materialized view)
на её же основе только средствами sqlite.

View File

@ -0,0 +1,29 @@
TODO: hbs2-peer-queues
$workflow: backlog
Сделать механизм очередей ( циклических FIFO буферов )
с управлением ( put/get ) по RPC
hbs2-peer постоянно в памяти;
Тогда мы решаем проблемы блокировок в sqlite:
Процесс продюсер -- пишет в очередь через hbs2-peer ( на диск )
Процесс консьюмер -- читает оттуда и обновляет БД, когда к этому
готов.
Таким образом, мы избегаем проблем с блокировками и
получаем понятный асинхронный механизм взаимодействия
между разными программами из hbs2.
Технически их можно сделать на основе компактов, в каждый
compact пишутся сообщения в формате (n, bytestring), после
чтения консьюмером -- сообщения удаляются.
По превышению файлом компакта некоего размера --
производим компактизацию, т.е начинаем писать в новый
файл, а старый удаляем, как только в нём не останется
ничего для чтения... Ну или как-то так.

39
docs/todo/hbs2-peer.txt Normal file
View File

@ -0,0 +1,39 @@
TODO: ASAP-bloom-filter-of-blocks
Каждый пир поддерживает фильтр Блума для блоков и раздаёт этот фильтр по
протоколу.
Протокол подразумевает как отдачу всего фильтра целиком ( тут подходит
держать его в LWWRef)
Так и просто запросы к нему.
Запрос должен пролезать в UDP, таким образом, выглядит так, что это
список чисел с номерами бит, т.е в худшем случае (8 байт на число)
один запрос это проверка 128 блоков за раз. Поскольку CBOR у нас
кодирует числа с переменной длиной, можно ожидать, что в среднем
будет получше.
Это ускорит, возможно, на порядок поиск блоков, который тем хуже,
чем больше в системе пиров.
Открытые вопросы:
- Параметры фильтра Блума? Зашитые в систему, или зависящие от
пира (и тогда мы пересчитываем их)
- Надо ли качать фильтры целиком (кажется, что нет, но можно
запоминать/обновлять для каждого пира, и время от времени
чистить)
- Если параметры фильтра могут меняться для пира, как
согласовывать хэш функции? Если их зашивать и менять только
коэффициенты, то не слишком ли плохие будут хэш функции?
- Какие атаки может вызвать?
- Как эффективно хранить?

37
docs/todo/hbs2-sync.txt Normal file
View File

@ -0,0 +1,37 @@
FIXME: race-on-large-files
добавляем большой файл ( ~4GB )
делаем hbs2-sync run на хосте A
делаем hbs2-sync run на хосте B
результат: файл удалён (tomb transaction)
вероятно, гонка по какой-то причине.
кажется, это backup-mode гадит
TODO: hbs2-sync-recovery
сделать команды для получения всех
меркл-деревьев (игнорировать tomb-ы)
сделать команду для постинга транзакции с
новым таймстемпом (восстановить файл из tomb-а не копируя его)
TODO: hbs2-sync-group-key-policy
сейчас на каждый файл генерируется новый групповой
ключ.
это хорошо, но если добавить большой файл
удалить большой файл
добавить большой файл обратно --- получится
адовое дублирование данных.
Возможное решение --- держать групповой ключ в кэше,
и устраивать его ротацию время от времени.
TODO: hbs2-sync-whole-state
сейчас будет выкачан весь рефчан, включая удалённые данные (tombs)
там, где они не нужны.
это плохо для файлопомойки.
нужно найти решения для проблемы

View File

@ -0,0 +1,7 @@
PR: Initial support for static builds using nix
Still work in progress.
Suddenly, I didn't manage to make flake that is able to build
both static and dynamic binaries.
To run static builds use `nix build .#static` command at the project's root
commit: be86429c4b806fe20069e8efbf921c20b9e17ee3

View File

@ -0,0 +1,92 @@
;; all key ids are PUBLIC
(define hosts
;; host-id sign-key encrypt-key
`[
(minime 4Z1ebkksoiZ9j4vZE9jnghxPDmc1ihXdNC6cX39phkLD
9Fp8Y5c9Fp612sjby3bL8P3SnUjjK2bz4F38nmVASpzb)
(expert CxJaFMBykhTdUiXxgdWF2pjxV5cWtw3yjDozNniUYRRC
Hg6XD19KGQrVjMYrCNeuaGfhTn7BCCUGR8c3brSWnzQi)
(minipig 44onTKSrAjXQ42Ahu6Z8d5X35g23pTTbSgRudNow9ZEn
D17PC8RGELG2wvTUoeAVhZvpf5R2txQHdwtYxGAJ9M1h)
]
)
(define (sign-key host)
(str (nth 1 (assoc host hosts))))
(define (encrypt-key host)
(str (nth 2 (assoc host hosts))))
(define my-refchan-head
`[
(version 2)
(quorum 1)
(wait 10)
(peer "CVDMz8BiSvRsgWNbJ4u9vRwXthN8LoF8XbbbjoL2cNFd" 1)
(peer "5GnroAC8FXNRL8rcgJj6RTu9mt1AbuNd5MZVnDBcCKzb" 1)
(peer "J8dFP5TbUQxUpVbVqZ3NKKPwrhvUCTQKC6xrVWUGkrR6" 1)
(author ,(sign-key minime))
(author ,(sign-key expert))
(author ,(sign-key minipig))
(reader ,(encrypt-key minime))
(reader ,(encrypt-key expert))
(reader ,(encrypt-key minipig))
]
)
(define (create-refchan)
[hbs2:refchan:create my-refchan-head]
)
;; created once by create-refchan
(define REFCHAN :Aze8PNNexhfz629UfaE79oyRW8Rf7fTGSVoJW4qD95Z7)
(define HOST [car [car [call:proc hostname]]])
(define (update-refchan)
[hbs2:refchan:head:update REFCHAN my-refchan-head]
)
(define (create:name:update host)
(begin
(local pk (sign-key host))
(local tx (hbs2:refchan:tx:raw:create pk [unwords :name host]))
tx
)
)
(define (post:name:update)
(begin
(local tx (create:name:update HOST))
(hbs2:refchan:tx:propose REFCHAN tx)
)
)
(define (state:get)
(begin
; won't work on ipv6 address 'cause of their stupid : as separator
(local (strip x) [sym [car [split :: [last [split :// x]]]]] )
(local self [list [sym [car [cdr [car [ grep peer-key [hbs2:peer:poke] ]]]]] :127.0.0.1])
(local txs (grep :propose (hbs2:refchan:tx:raw:list REFCHAN)))
(local (hostname e) (car (cdr (car (top:string (bytes:decode [nth 4 _1]))))) )
(local peers (map (lambda [x] [car [cdr x]]) (car (call:proc hbs2-peer do peer-info))))
(local peers2 (map [fn 1 [list (lookup:uw :key _1) (strip (lookup:uw :addr _1))]] peers))
(local peers3 (filter [fn 1 [not (eq? [nth 1 _1] :192.168.1.1)]] peers2))
(local state (map [fn 1 [list [nth 2 _1] [hostname _1] ]] txs))
(local (entry e) [list [lookup:uw [nth 0 e] (cons self peers3)] [nth 1 e] ])
(local res [map [fn 1 [entry _1]] state])
res
)
)

View File

@ -0,0 +1,6 @@
module Main where
import RefChanQBLF.CLI qualified as CLI
main :: IO ()
main = CLI.main

View File

@ -1,806 +0,0 @@
{-# Language TemplateHaskell #-}
{-# Language UndecidableInstances #-}
{-# Language AllowAmbiguousTypes #-}
module Main where
import HBS2.Prelude
import HBS2.Defaults
import HBS2.Merkle
import HBS2.Hash
import HBS2.Clock
import HBS2.Base58
import HBS2.OrDie
import HBS2.Data.Types.Refs
import HBS2.Net.Proto.Types
import HBS2.Actors.Peer
import HBS2.Peer.Proto.RefChan
import HBS2.Peer.Proto.AnyRef
import HBS2.Data.Types.SignedBox
import HBS2.Net.Messaging.Unix
import HBS2.Data.Bundle
import HBS2.Net.Auth.Credentials
import HBS2.Data.Detect
import HBS2.Actors.Peer.Types()
import HBS2.Storage.Simple
import HBS2.System.Logger.Simple
import HBS2.Net.Proto.QBLF
import Demo.QBLF.Transactions
import Data.Config.Suckless
import Control.Monad.Trans.Maybe
import Codec.Serialise
import Control.Monad.Reader
import Data.ByteString(ByteString)
import Data.ByteString.Char8 qualified as BS
import Data.ByteString.Lazy qualified as LBS
import Data.List qualified as List
import Lens.Micro.Platform hiding ((.=))
import Options.Applicative hiding (info)
import Options.Applicative qualified as O
import System.Directory
import Data.HashSet qualified as HashSet
import Data.HashMap.Strict (HashMap)
import Data.HashMap.Strict qualified as HashMap
import Data.Maybe
import Data.Word
import System.Random
import UnliftIO
import Web.Scotty hiding (request,header)
import Network.HTTP.Types.Status
import Data.Cache (Cache)
import Data.Cache qualified as Cache
import Control.Monad.Except
{- HLINT ignore "Use newtype instead of data" -}
-- TODO: config
-- сделать конфиг, а то слишком много уже параметров в CLI
data HttpPortOpt
data RefChanOpt
data SocketOpt
data ActorOpt
data DefStateOpt
data StateRefOpt
data QBLFRefKey
type MyRefKey = AnyRefKey QBLFRefKey HBS2Basic
instance Monad m => HasCfgKey HttpPortOpt (Maybe Int) m where
key = "http"
instance {-# OVERLAPPING #-} (HasConf m, HasCfgKey HttpPortOpt (Maybe Int) m) => HasCfgValue HttpPortOpt (Maybe Int) m where
cfgValue = val <$> getConf
where
val syn = lastMay [ fromIntegral e
| ListVal (Key s [LitIntVal e]) <- syn, s == key @HttpPortOpt @(Maybe Int) @m
]
instance Monad m => HasCfgKey RefChanOpt (Maybe String) m where
key = "refchan"
instance Monad m => HasCfgKey SocketOpt (Maybe String) m where
key = "socket"
instance Monad m => HasCfgKey ActorOpt (Maybe String) m where
key = "actor"
instance Monad m => HasCfgKey DefStateOpt (Maybe String) m where
key = "default-state"
instance Monad m => HasCfgKey StateRefOpt (Maybe String) m where
key = "state-ref"
class ToBalance e tx where
toBalance :: tx -> [(Account e, Amount)]
tracePrefix :: SetLoggerEntry
tracePrefix = toStderr . logPrefix "[trace] "
debugPrefix :: SetLoggerEntry
debugPrefix = toStderr . logPrefix "[debug] "
errorPrefix :: SetLoggerEntry
errorPrefix = toStderr . logPrefix "[error] "
warnPrefix :: SetLoggerEntry
warnPrefix = toStderr . logPrefix "[warn] "
noticePrefix :: SetLoggerEntry
noticePrefix = toStderr . logPrefix "[notice] "
infoPrefix :: SetLoggerEntry
infoPrefix = toStdout . logPrefix ""
silently :: MonadIO m => m a -> m ()
silently m = do
setLoggingOff @DEBUG
setLoggingOff @INFO
setLoggingOff @ERROR
setLoggingOff @WARN
setLoggingOff @NOTICE
void m
withLogging :: MonadIO m => m a -> m ()
withLogging m = do
-- setLogging @TRACE tracePrefix
setLogging @DEBUG debugPrefix
setLogging @INFO infoPrefix
setLogging @ERROR errorPrefix
setLogging @WARN warnPrefix
setLogging @NOTICE noticePrefix
m
setLoggingOff @DEBUG
setLoggingOff @INFO
setLoggingOff @ERROR
setLoggingOff @WARN
setLoggingOff @NOTICE
data MyEnv =
MyEnv
{ mySelf :: Peer UNIX
, myFab :: Fabriq UNIX
, myChan :: RefChanId UNIX
, myRef :: MyRefKey
, mySto :: AnyStorage
, myCred :: PeerCredentials HBS2Basic
, myHttpPort :: Int
, myFetch :: Cache HashRef ()
}
newtype App m a = App { fromApp :: ReaderT MyEnv m a }
deriving newtype ( Functor
, Applicative
, Monad
, MonadIO
, MonadUnliftIO
, MonadReader MyEnv
, MonadTrans
)
runApp :: (MonadIO m, PeerMessaging UNIX) => MyEnv -> App m a -> m a
runApp env m = runReaderT (fromApp m) env
instance Monad m => HasFabriq UNIX (App m) where
getFabriq = asks myFab
instance Monad m => HasOwnPeer UNIX (App m) where
ownPeer = asks mySelf
instance Monad m => HasStorage (App m) where
getStorage = asks mySto
data ConsensusQBLF
data StateQBLF = StateQBLF { fromStateQBLF :: HashRef }
data MyError =
DeserializationError | SignatureError | TxUnsupported | SomeOtherError
deriving stock (Eq,Ord,Show)
check :: MonadIO m => MyError -> Either e a -> ExceptT MyError m a
check w = \case
Right x -> ExceptT $ pure (Right x)
Left{} -> ExceptT $ pure (Left w)
fiasco :: MonadIO m => MyError -> ExceptT MyError m a
fiasco x = ExceptT $ pure $ Left x
ok :: MonadIO m => a -> ExceptT MyError m a
ok x = ExceptT $ pure $ Right x
type ForConsensus m = (MonadIO m, Serialise (QBLFMessage ConsensusQBLF))
instance Serialise (QBLFMerge ConsensusQBLF)
instance Serialise (QBLFMessage ConsensusQBLF)
instance Serialise (QBLFAnnounce ConsensusQBLF)
instance Serialise (QBLFCommit ConsensusQBLF)
instance Monad m => HasTimeLimits UNIX (RefChanNotify UNIX) (App m) where
tryLockForPeriod _ _ = pure True
instance (ForConsensus m, MonadUnliftIO m) => IsQBLF ConsensusQBLF (App m) where
type QBLFActor ConsensusQBLF = Actor L4Proto
type QBLFTransaction ConsensusQBLF = QBLFDemoToken L4Proto
type QBLFState ConsensusQBLF = DAppState
qblfMoveForward _ s1 = do
env <- ask
fetchMissed env s1
pure True
qblfNewState (DAppState h0) txs = do
sto <- asks mySto
chan <- asks myChan
self <- asks mySelf
creds <- asks myCred
let sk = view peerSignSk creds
let pk = view peerSignPk creds
-- основная проблема в том, что мы пересортировываем весь state
-- однако, если считать его уже отсортированным, то, может быть,
-- все будет не так уж плохо.
-- так-то мы можем вообще его на диске держать
root <- if List.null txs then do
pure h0
else do
hashes <- liftIO $ mapM (putBlock sto . serialise) txs <&> catMaybes
current <- readLog (getBlock sto) h0
let new = HashSet.fromList ( current <> fmap HashRef hashes )
let pt = toPTree (MaxSize 256) (MaxNum 256) (HashSet.toList new)
-- пробуем разослать бандлы с транзакциями
runMaybeT do
ref <- MaybeT $ createBundle sto (fmap HashRef hashes)
let refval = makeBundleRefValue @L4Proto pk sk (BundleRefSimple ref)
r <- MaybeT $ liftIO $ putBlock sto (serialise refval)
lift $ request self (ActionRequest @UNIX chan (RefChanAnnounceBlock (HashRef r)))
r <- makeMerkle 0 pt $ \(hx,_,bs) -> do
th <- liftIO (enqueueBlock sto bs)
debug $ "WRITE TX" <+> pretty hx
request self (ActionRequest @UNIX chan (RefChanAnnounceBlock (HashRef r)))
pure (HashRef r)
debug $ "PROPOSED NEW STATE:" <+> pretty root
pure $ DAppState root
qblfCommit s0 s1 = do
debug $ "COMMIT:" <+> pretty s0 <+> pretty s1
sto <- asks mySto
chan <- asks myChan
ref <- asks myRef
debug $ "UPDATING REF" <+> pretty ref <+> pretty s1
liftIO $ updateRef sto ref (fromHashRef (fromDAppState s1))
pure ()
qblfBroadCast msg = do
self <- asks mySelf
creds <- asks myCred
chan <- asks myChan
let sk = view peerSignSk creds
let pk = view peerSignPk creds
nonce <- randomIO @Word64 <&> serialise <&> LBS.toStrict
let box = makeSignedBox @UNIX pk sk (LBS.toStrict (serialise msg) <> nonce)
let notify = Notify @UNIX chan box
request self notify
case msg of
QBLFMsgAnn _ (QBLFAnnounce _ _) -> do
-- TODO: maybe-announce-new-state-here
pure ()
_ -> none
-- TODO: optimize-qblf-merge
-- будет нормально работать до десятков/сотен тысяч транз,
-- а потом помрёт.
-- варианты:
-- 1. перенести логику в БД
-- 2. кэшировать всё, что можно
qblfMerge s0 s1 = do
chan <- asks myChan
self <- asks mySelf
creds <- asks myCred
let sk = view peerSignSk creds
let pk = view peerSignPk creds
debug $ "MERGE. Proposed state:" <+> pretty s1
sto <- asks mySto
let readFn = liftIO . getBlock sto
tx1 <- mapM (readLog readFn) (fmap fromDAppState s1) <&> mconcat
tx0 <- readLog readFn (fromDAppState s0) <&> HashSet.fromList
let txNew = HashSet.fromList tx1 `HashSet.difference` tx0
if List.null txNew then do
pure s0
else do
debug $ "READ TXS" <+> pretty s1 <+> pretty (length tx1)
r <- forM tx1 $ \t -> runMaybeT do
-- игнорируем ранее добавленные транзакции
guard (not (HashSet.member t tx0))
bs <- MaybeT $ liftIO $ getBlock sto (fromHashRef t)
tx <- MaybeT $ pure $ deserialiseOrFail @(QBLFDemoToken L4Proto) bs & either (const Nothing) Just
case tx of
Emit box -> do
(pk, e@(EmitTx a q _)) <- MaybeT $ pure $ unboxSignedBox0 @(EmitTx L4Proto) box
guard ( chan == pk )
debug $ "VALID EMIT TRANSACTION" <+> pretty t <+> pretty (AsBase58 a) <+> pretty q
pure ([(t,e)], mempty)
(Move box) -> do
(_, m@(MoveTx _ _ qty _)) <- MaybeT $ pure $ unboxSignedBox0 @(MoveTx L4Proto) box
guard (qty > 0)
debug $ "MOVE TRANSACTION" <+> pretty t
pure (mempty, [(t,m)])
let parsed = catMaybes r
let emits = foldMap (view _1) parsed
let moves = foldMap (view _2) parsed & List.sortOn fst
bal0 <- balances (fromDAppState s0)
-- баланс с учётом новых emit
let balE = foldMap (toBalance @L4Proto . snd) emits
& HashMap.fromListWith (+)
& HashMap.unionWith (+) bal0
let moves' = updBalances @L4Proto balE moves
let merged = fmap fst emits <> fmap fst moves'
let pt = toPTree (MaxSize 256) (MaxNum 256) (HashSet.toList (tx0 <> HashSet.fromList merged))
root <- makeMerkle 0 pt $ \(_,_,bs) -> do
void $ liftIO (putBlock sto bs)
let new = DAppState (HashRef root)
-- FIXME: garbage-collect-discarded-states
async $ void $ balances (fromDAppState new)
debug $ "MERGED" <+> pretty new
pure new
instance HasStorage (ReaderT AnyStorage IO) where
getStorage = ask
instance ToBalance e (EmitTx e) where
toBalance (EmitTx a qty _) = [(a, qty)]
instance ToBalance e (MoveTx e) where
toBalance (MoveTx a1 a2 qty _) = [(a1, -qty), (a2, qty)]
balances :: forall e s m . ( e ~ L4Proto
, MonadIO m
, HasStorage m
-- , FromStringMaybe (PubKey 'Sign s)
, s ~ Encryption e
, ToBalance L4Proto (EmitTx L4Proto)
, ToBalance L4Proto (MoveTx L4Proto)
, Pretty (AsBase58 (PubKey 'Sign s))
)
=> HashRef
-> m (HashMap (Account e) Amount)
balances root = do
sto <- getStorage
let pk = SomeRefKey (HashRef "6ChGmfYkwM6646oKkj8r8MAjdViTsdtZSi6tgqk3tbh", root)
cached <- runMaybeT do
rval <- MaybeT $ liftIO $ getRef sto pk
val <- MaybeT $ liftIO $ getBlock sto rval
MaybeT $ deserialiseOrFail @(HashMap (Account e) Amount) val
& either (const $ pure Nothing) (pure . Just)
case cached of
Just bal -> pure bal
Nothing -> do
txs <- readLog (liftIO . getBlock sto) root
r <- forM txs $ \h -> runMaybeT do
blk <- MaybeT $ liftIO $ getBlock sto (fromHashRef h)
tx <- MaybeT $ pure $ deserialiseOrFail @(QBLFDemoToken L4Proto) blk & either (const Nothing) Just
case tx of
Emit box -> do
(_, emit) <- MaybeT $ pure $ unboxSignedBox0 @(EmitTx L4Proto) box
pure $ toBalance @e emit
Move box -> do
(_, move) <- MaybeT $ pure $ unboxSignedBox0 @(MoveTx L4Proto) box
pure $ toBalance @e move
let val = catMaybes r & mconcat & HashMap.fromListWith (+)
runMaybeT do
checkComplete sto root >>= guard
rv <- MaybeT $ liftIO $ putBlock sto (serialise val)
liftIO $ updateRef sto pk rv
pure val
-- TODO: optimize-upd-balances
-- можно сгруппировать по аккаунтам
-- и проверять только те транзакции, которые относятся
-- к связанной (транзакциями) группе аккаунтов.
-- то есть, разбить на кластеры, у которых отсутствуют пересечения по
-- аккаунтам и проверять независимо и параллельно, например
-- причем, прямо этой функцией
--
-- updBalances :: HashMap (Account L4Proto) Amount
-- -> [(tx, b)]
-- -> [(tx, b)]
updBalances :: forall e a tx . (ForRefChans e, ToBalance e tx)
=> HashMap (Account e) Amount
-> [(a, tx)]
-> [(a, tx)]
updBalances = go
where
go bal [] = empty
go bal (t:rest) =
if good then
t : go nb rest
else
go bal rest
where
nb = HashMap.unionWith (+) bal (HashMap.fromList (toBalance @e (snd t)))
good = HashMap.filter (<0) nb & HashMap.null
fetchMissed :: forall e w m . ( MonadIO m
, Request e (RefChanNotify e) m
, e ~ UNIX
, w ~ ConsensusQBLF
)
=> MyEnv
-> QBLFState w
-> m ()
fetchMissed env s = do
let tube = mySelf env
let chan = myChan env
let cache = myFetch env
let sto = mySto env
let href = fromDAppState s
here <- liftIO $ hasBlock sto (fromHashRef href) <&> isJust
wip <- liftIO $ Cache.lookup cache href <&> isJust
when here do
liftIO $ Cache.delete cache href
unless (here || wip) do
debug $ "We might be need to fetch" <+> pretty s
liftIO $ Cache.insert cache href ()
request @UNIX tube (ActionRequest @UNIX chan (RefChanFetch (fromDAppState s)))
runMe :: ForConsensus IO => Config -> IO ()
runMe conf = withLogging $ flip runReaderT conf do
debug $ "runMe" <+> pretty conf
kr <- cfgValue @ActorOpt @(Maybe String) `orDie` "actor's key not set"
chan' <- cfgValue @RefChanOpt @(Maybe String) `orDie` "refchan not set"
sa <- cfgValue @SocketOpt @(Maybe String) `orDie` "socket not set"
pno <- cfgValue @HttpPortOpt @(Maybe Int) <&> fromMaybe 3011
ds <- cfgValue @DefStateOpt @(Maybe String)
ref <- ( cfgValue @StateRefOpt @(Maybe String)
<&> maybe Nothing fromStringMay
) `orDie` "state-ref not set"
sc <- liftIO $ BS.readFile kr
creds <- pure (parseCredentials @HBS2Basic (AsCredFile sc)) `orDie` "bad keyring file"
chan <- pure (fromStringMay @(RefChanId L4Proto) chan') `orDie` "invalid REFCHAN"
here <- liftIO $ doesFileExist sa
when here do
liftIO $ removeFile sa
server <- newMessagingUnixOpts [MUNoFork] True 1.0 sa
abus <- async $ runMessagingUnix server
let tube = fromString sa
-- FIXME: fix-default-storage
xdg <- liftIO $ getXdgDirectory XdgData defStorePath <&> fromString
sto' <- simpleStorageInit @HbSync [StoragePrefix xdg]
let sto = AnyStorage sto'
sw <- liftIO $ replicateM 4 $ async $ simpleStorageWorker sto'
-- FIXME: fix-hardcoded-timeout
fetches <- liftIO $ Cache.newCache (Just (toTimeSpec (TimeoutSec 30)))
let myEnv = MyEnv tube
(Fabriq server)
chan
ref
sto
creds
pno
fetches
let dss = ds >>= fromStringMay
s0 <- readOrCreateStateRef dss sto ref
debug $ "STATE0:" <+> pretty s0
-- получить голову
-- из головы получить акторов
headBlk <- getRefChanHead @L4Proto sto (RefChanHeadKey chan) `orDie` "can't read head block"
let self = view peerSignPk creds & Actor @L4Proto
let actors = view refChanHeadAuthors headBlk
& HashSet.toList
& fmap (Actor @L4Proto)
runApp myEnv do
-- FIXME: timeout-hardcode
let w = realToFrac 5
-- FIXME: use-actors-asap
qblf <- qblfInit @ConsensusQBLF self actors (DAppState (HashRef s0)) w
consensus <- async do
pause @'Seconds 0.5
qblfRun qblf
-- FIXME: web-port-to-config
web <- async $ liftIO $ scotty (fromIntegral (myHttpPort myEnv)) $ do
post "/tx" $ do
r <- runExceptT do
bin <- lift body
let hBin = hashObject @HbSync bin
debug $ "GOT TX" <+> pretty hBin
tok <- check DeserializationError =<< pure (deserialiseOrFail @(QBLFDemoToken L4Proto) bin)
tx <- case tok of
(Emit box) -> do
(sign, tx) <- maybe (ExceptT $ pure $ Left SignatureError) pure $ unboxSignedBox0 box
if sign == chan then
pure hBin
else
fiasco SignatureError
(Move box) -> do
(sign, tx) <- maybe (ExceptT $ pure $ Left SignatureError) pure $ unboxSignedBox0 box
pure hBin
qblfEnqueue qblf tok
pure hBin
case r of
Left SignatureError -> do
err $ viaShow SignatureError
status status401
Left e -> do
err $ viaShow e
status status400
Right tx -> do
debug $ "TX ENQUEUED OK" <+> pretty tx
status status200
link web
runProto $ List.singleton $ makeResponse (myProto myEnv qblf chan)
void $ waitAnyCatchCancel $ [abus] <> sw
where
myProto :: forall e m . ( MonadIO m
, Request e (RefChanNotify e) m
, e ~ UNIX
)
=> MyEnv
-> QBLF ConsensusQBLF
-> RefChanId e
-> RefChanNotify e
-> m ()
myProto _ qblf _ (ActionRequest{}) = do
pure ()
myProto env qblf chan (Notify _ msg) = do
let sto = mySto env
let tube = mySelf env
let coco = hashObject @HbSync $ serialise msg
void $ runMaybeT do
(_, wrapped) <- MaybeT $ pure $ unboxSignedBox0 @ByteString @UNIX msg
qbmess <- MaybeT $ pure $ deserialiseOrFail @(QBLFMessage ConsensusQBLF) (LBS.fromStrict wrapped)
& either (const Nothing) Just
states <- case qbmess of
QBLFMsgAnn _ (QBLFAnnounce s0 s1) -> do
pure [s0, s1]
QBLFMsgHeartBeat _ _ s0 _-> do
pure [s0]
_ -> do
pure mempty
-- FIXME: full-download-guarantee
lift $ forM_ states (fetchMissed env)
qblfAcceptMessage qblf qbmess
-- debug $ "RefChanQBLFMain(3)" <+> "got message" <+> pretty (AsBase58 chan) <+> pretty coco
readOrCreateStateRef mbDs sto ref = do
debug $ "MyRef:" <+> pretty (hashObject @HbSync ref)
fix \spin -> do
mbref <- liftIO $ getRef @_ @HbSync sto ref
case mbref of
Nothing -> do
debug "STATE is empty"
maybe1 mbDs none $ \ds -> do
debug $ "UPDATE REF" <+> pretty (hashObject @HbSync ref) <+> pretty (HashRef ds)
liftIO $ updateRef sto ref ds
pause @'Seconds 0.25
spin
Just val -> do
pure val
type Config = [Syntax MegaParsec]
main :: IO ()
main = join . customExecParser (prefs showHelpOnError) $
O.info (helper <*> globalOptions)
( fullDesc
<> header "refchan-qblf-worker"
<> progDesc "for test and demo purposed"
)
where
globalOptions = applyConfig <$> commonOpts <*> cli
applyConfig :: Maybe FilePath -> (Config -> IO ()) -> IO ()
applyConfig config m = do
maybe1 config (m mempty) $ \conf -> do
top <- readFile conf <&> parseTop <&> either (pure mempty) id
m top
commonOpts = optional $ strOption (long "config" <> short 'c' <> help "Config file")
cli = hsubparser ( command "run" (O.info pRun (progDesc "run qblf servant" ) )
<> command "gen" (O.info pGen (progDesc "generate transcation") )
<> command "post" (O.info pPostTx (progDesc "post transaction") )
<> command "check" (O.info pCheckTx (progDesc "check transaction") )
<> command "balances" (O.info pBalances (progDesc "show balances") )
)
pRun = do
pure runMe
pGen = hsubparser
( command "tx-emit" ( O.info pGenEmit (progDesc "generate emit") )
<> command "tx-move" ( O.info pGenMove (progDesc "generate move") )
)
pGenEmit = do
kr <- strOption ( long "keyring" <> short 'k' <> help "keyring file" )
amnt <- option @Amount auto ( long "amount" <> short 'n' <> help "amount" )
dest <- strArgument ( metavar "ADDRESS" )
pure $ const $ silently do
sc <- BS.readFile kr
creds <- pure (parseCredentials @HBS2Basic (AsCredFile sc)) `orDie` "bad keyring file"
let pk = view peerSignPk creds
let sk = view peerSignSk creds
acc <- pure (fromStringMay @(RefChanId L4Proto) dest) `orDie` "bad account address"
tx <- makeEmitTx @L4Proto pk sk acc amnt
LBS.putStr $ serialise tx
pGenMove = do
kr <- strOption ( long "wallet" <> short 'w' <> help "wallet (keyring) file" )
amnt <- option @Amount auto ( long "amount" <> short 'n' <> help "amount" )
dest <- strArgument ( metavar "ADDRESS" )
pure $ const $ silently do
sc <- BS.readFile kr
creds <- pure (parseCredentials @HBS2Basic (AsCredFile sc)) `orDie` "bad keyring file"
let pk = view peerSignPk creds
let sk = view peerSignSk creds
acc <- pure (fromStringMay @(RefChanId L4Proto) dest) `orDie` "bad account address"
tx <- makeMoveTx @L4Proto pk sk acc amnt
LBS.putStr $ serialise tx
pCheckTx = do
kr <- strOption ( long "keyring" <> short 'k' <> help "keyring file" )
pure $ const do
sc <- BS.readFile kr
creds <- pure (parseCredentials @HBS2Basic (AsCredFile sc)) `orDie` "bad keyring file"
let pk = view peerSignPk creds
let sk = view peerSignSk creds
tx <- LBS.getContents <&> deserialise @(QBLFDemoToken L4Proto)
case tx of
Emit box -> do
void $ pure (unboxSignedBox0 @(EmitTx L4Proto) @L4Proto box) `orDie` "bad emit tx"
Move box -> do
void $ pure (unboxSignedBox0 @(MoveTx L4Proto) @L4Proto box) `orDie` "bad move tx"
pure ()
pPostTx = pure $ const do
error "not supported anymore / TODO via http"
-- rc <- strArgument ( metavar "REFCHAN" )
-- sa <- strArgument ( metavar "UNIX-SOCKET" ) <&> fromString
-- pure $ withLogging do
-- rchan <- pure (fromStringMay @(RefChanId L4Proto) rc) `orDie` "bad refchan"
-- print "JOPA"
-- -- FIXME: wrap-client-boilerplate
-- inbox <- newMessagingUnix False 1.0 sa
-- wInbox <- async $ runMessagingUnix inbox
-- let env = MyEnv (fromString sa) (Fabriq inbox) rchan
-- msg <- (LBS.getContents <&> deserialiseOrFail) `orDie` "transaction decode error"
-- runApp env do
-- request (mySelf env) (msg :: QBLFDemoTran UNIX)
-- pause @'Seconds 0.1
-- cancel wInbox
pBalances = do
state <- strArgument ( metavar "STATE" )
pure $ const $ withLogging do
xdg <- liftIO $ getXdgDirectory XdgData defStorePath <&> fromString
sto' <- simpleStorageInit @HbSync [StoragePrefix xdg]
let sto = AnyStorage sto'
sw <- liftIO $ replicateM 4 $ async $ simpleStorageWorker sto'
root <- pure (fromStringMay @HashRef state) `orDie` "Bad STATE reference"
flip runReaderT sto $ do
debug $ "calculating balances for" <+> pretty root
bal <- balances root
forM_ (HashMap.toList bal) $ \(acc, qty) -> do
liftIO $ print $ pretty (AsBase58 acc) <+> pretty qty

View File

@ -1,131 +0,0 @@
{-# Language UndecidableInstances #-}
module Demo.QBLF.Transactions where
import HBS2.Prelude.Plated
import HBS2.Hash
import HBS2.Base58
import HBS2.Peer.Proto
import HBS2.Data.Types.Refs (HashRef(..))
import HBS2.Data.Types.SignedBox
import HBS2.Net.Auth.Credentials
import HBS2.Net.Messaging.Unix (UNIX)
import Data.Hashable(Hashable(..))
import Codec.Serialise
import Data.ByteString.Lazy (ByteString)
import Data.Word (Word64)
import System.Random
newtype Actor e =
Actor { fromActor :: PubKey 'Sign (Encryption e) }
deriving stock (Generic)
deriving stock instance Eq (PubKey 'Sign (Encryption e)) => Eq (Actor e)
deriving newtype instance Hashable (PubKey 'Sign (Encryption e)) => Hashable (Actor e)
instance Pretty (AsBase58 (PubKey 'Sign (Encryption e))) => Pretty (Actor e) where
pretty (Actor a) = pretty (AsBase58 a)
type Account e = PubKey 'Sign (Encryption e)
newtype Amount = Amount Integer
deriving stock (Eq,Show,Ord,Data,Generic)
deriving newtype (Read,Enum,Num,Integral,Real,Pretty)
newtype DAppState = DAppState { fromDAppState :: HashRef }
deriving stock (Eq,Show,Ord,Data,Generic)
deriving newtype (Hashable,Pretty)
instance Hashed HbSync DAppState where
hashObject (DAppState (HashRef h)) = h
data EmitTx e = EmitTx (Account e) Amount Word64
deriving stock (Generic)
data MoveTx e = MoveTx (Account e) (Account e) Amount Word64
deriving stock (Generic)
data QBLFDemoToken e =
Emit (SignedBox (EmitTx e) e) -- proof: owner's key
| Move (SignedBox (MoveTx e) e) -- proof: wallet's key
deriving stock (Generic)
instance ForRefChans e => Serialise (Actor e)
instance Serialise DAppState
instance Serialise Amount
instance Serialise (PubKey 'Sign (Encryption e)) => Serialise (EmitTx e)
instance Serialise (PubKey 'Sign (Encryption e)) => Serialise (MoveTx e)
instance (Serialise (Account e), ForRefChans e) => Serialise (QBLFDemoToken e)
type ForQBLFDemoToken e = ( Eq (PubKey 'Sign (Encryption e))
, Eq (Signature (Encryption e))
, Pretty (AsBase58 (PubKey 'Sign (Encryption e)))
, ForSignedBox e
, FromStringMaybe (PubKey 'Sign (Encryption e))
, Serialise (PubKey 'Sign (Encryption e))
, Serialise (Signature (Encryption e))
, Hashable (PubKey 'Sign (Encryption e))
)
deriving stock instance (ForQBLFDemoToken e) => Eq (QBLFDemoToken e)
instance ForQBLFDemoToken e => Hashable (QBLFDemoToken e) where
hashWithSalt salt = \case
Emit box -> hashWithSalt salt box
Move box -> hashWithSalt salt box
newtype QBLFDemoTran e =
QBLFDemoTran (SignedBox (QBLFDemoToken e) e)
deriving stock Generic
instance ForRefChans e => Serialise (QBLFDemoTran e)
deriving newtype instance
(Eq (PubKey 'Sign (Encryption e)), Eq (Signature (Encryption e)))
=> Eq (QBLFDemoTran e)
deriving newtype instance
(Eq (Signature (Encryption e)), ForRefChans e)
=> Hashable (QBLFDemoTran e)
instance Serialise (QBLFDemoTran UNIX) => HasProtocol UNIX (QBLFDemoTran UNIX) where
type instance ProtocolId (QBLFDemoTran UNIX) = 0xFFFF0001
type instance Encoded UNIX = ByteString
decode = either (const Nothing) Just . deserialiseOrFail
encode = serialise
makeEmitTx :: forall e m . ( MonadIO m
, ForRefChans e
, Signatures (Encryption e)
)
=> PubKey 'Sign (Encryption e)
-> PrivKey 'Sign (Encryption e)
-> Account e
-> Amount
-> m (QBLFDemoToken e)
makeEmitTx pk sk acc amount = do
nonce <- randomIO
let box = makeSignedBox @e pk sk (EmitTx @e acc amount nonce)
pure (Emit @e box)
makeMoveTx :: forall e m . ( MonadIO m
, ForRefChans e
, Signatures (Encryption e)
)
=> PubKey 'Sign (Encryption e) -- from pk
-> PrivKey 'Sign (Encryption e) -- from sk
-> Account e
-> Amount -- amount
-> m (QBLFDemoToken e)
makeMoveTx pk sk acc amount = do
nonce <- randomIO
let box = makeSignedBox @e pk sk (MoveTx @e pk acc amount nonce)
pure (Move @e box)

View File

@ -0,0 +1,182 @@
module RefChanQBLF.App where
import Codec.Serialise
import Control.Monad.Cont
import Control.Monad.Trans.Maybe
import Data.ByteString qualified as BS
import Data.ByteString.Lazy qualified as LBS
import Data.Cache qualified as Cache
import Data.HashSet qualified as HashSet
import HBS2.Actors.Peer
import HBS2.Actors.Peer.Types ()
import HBS2.Clock
import HBS2.Data.Types.Refs
import HBS2.Data.Types.SignedBox
import HBS2.Defaults
import HBS2.Hash
import HBS2.Net.Auth.Credentials
import HBS2.Net.Messaging.Unix
import HBS2.Net.Proto.QBLF
import HBS2.Net.Proto.Service
import HBS2.OrDie
import HBS2.Peer.Proto.RefChan
import HBS2.Prelude
import HBS2.Storage.Simple
import HBS2.System.Logger.Simple
import Lens.Micro.Platform hiding ((.=))
import System.Directory
import RefChanQBLF.Common
import RefChanQBLF.Impl
import RefChanQBLF.RPCServer
import RefChanQBLF.Transactions
data QBLFAppConf = QBLFAppConf
{ qapActorKeyring :: FilePath
, qapRefchanID :: RefChanId L4Proto
, qapSocket :: FilePath
, qapAppSocket :: FilePath
, qapDefState :: Maybe (Hash HbSync)
, qapStateRef :: MyRefKey
}
withSimpleAnyStorage :: FilePath -> (AnyStorage -> IO r) -> IO r
withSimpleAnyStorage storepath go = do
-- FIXME: fix-default-storage
xdg <- getXdgDirectory XdgData storepath <&> fromString
sto' <- simpleStorageInit @HbSync [StoragePrefix xdg]
flip runContT go do
replicateM 4 $ contAsync $ simpleStorageWorker sto'
pure $ AnyStorage sto'
loadCreds :: FilePath -> IO (PeerCredentials 'HBS2Basic)
loadCreds fpath = do
bs <- BS.readFile fpath
pure (parseCredentials @'HBS2Basic (AsCredFile bs)) `orDie` "bad keyring file"
runQBLFApp :: (ForConsensus IO) => QBLFAppConf -> IO ()
runQBLFApp QBLFAppConf {..} = withLogging do
creds <- loadCreds qapActorKeyring
whenM (doesFileExist qapSocket) $ removeFile qapSocket
-- FIXME: fix-hardcoded-timeout
fetches <- Cache.newCache (Just (toTimeSpec (TimeoutSec 30)))
flip runContT pure do
sto <- ContT $ withSimpleAnyStorage defStorePath
server <- newMessagingUnixOpts [MUNoFork] True 1.0 qapSocket
contAsync $ runMessagingUnix server
s0 <- lift $ readOrCreateStateRef qapDefState sto qapStateRef
debug $ "STATE0:" <+> pretty s0
let myEnv =
MyEnv
{ mySelf = fromString qapSocket -- Peer UNIX
, myFab = (Fabriq server) -- Fabriq UNIX
, myChan = qapRefchanID -- RefChanId UNIX
, myRef = qapStateRef -- MyRefKey
, mySto = sto -- AnyStorage
, myCred = creds -- PeerCredentials 'HBS2Basic
-- , myAppSoPath = appso -- TODO ?
, myFetch = fetches -- Cache HashRef ()
}
lift $ runMyAppT myEnv do
-- FIXME: timeout-hardcode
let w = realToFrac 5
-- получить голову
-- из головы получить акторов
headBlk <-
getRefChanHead @L4Proto sto (RefChanHeadKey qapRefchanID)
`orDie` "can't read head block"
-- FIXME: use-actors-asap
let self = Actor $ view peerSignPk creds
let actors = fmap Actor $ HashSet.toList $ view refChanHeadAuthors headBlk
qblf <- qblfInit @ConsensusQBLF self actors (DAppState (HashRef s0)) w
flip runContT pure do
contAsync do
pause @'Seconds 0.5
qblfRun qblf
do
srv <- liftIO $ newMessagingUnix True 1.0 qapAppSocket
contAsync $ runMessagingUnix srv
let qenv =
QRPCEnv
{ qrpcenvQConsensus = qblf
, qrpcenvRefchanId = qapRefchanID
, qrpcenvFabriq = Fabriq srv
, qrpcenvOwnPeer = fromString qapAppSocket
}
contAsync $ liftIO $ runQRPCT qenv do
runProto @UNIX
[ makeResponse (makeServer @QBLFAppRPC)
]
lift $ runProto [makeResponse (myProto myEnv qblf qapRefchanID)]
where
myProto
:: forall e m
. ( MonadIO m
, Request e (RefChanNotify e) m
, e ~ UNIX
)
=> MyEnv
-> QBLF ConsensusQBLF
-> RefChanId e
-> RefChanNotify e
-> m ()
myProto _ _qblf _ (ActionRequest {}) = do
pure ()
myProto env qblf _chan (Notify _ msg) = do
void $ runMaybeT do
(_, wrapped) <- MaybeT $ pure $ unboxSignedBox0 msg
qbmess <-
MaybeT $
pure $
deserialiseOrFail @(QBLFMessage ConsensusQBLF) (LBS.fromStrict wrapped)
& either (const Nothing) Just
states <- case qbmess of
QBLFMsgAnn _ (QBLFAnnounce s0 s1) -> do
pure [s0, s1]
QBLFMsgHeartBeat _ _ s0 _ -> do
pure [s0]
_ -> do
pure mempty
-- FIXME: full-download-guarantee
lift $ forM_ states (fetchMissed env)
qblfAcceptMessage qblf qbmess
-- debug $ "RefChanQBLFMain(3)" <+> "got message" <+> pretty (AsBase58 chan) <+> pretty coco
readOrCreateStateRef :: Maybe (Hash HbSync) -> AnyStorage -> MyRefKey -> IO (Hash HbSync)
readOrCreateStateRef mbDs sto ref = do
debug $ "MyRef:" <+> pretty (hashObject @HbSync ref)
fix \spin -> do
mbref <- readStateHashMay sto ref
case mbref of
Nothing -> do
debug "STATE is empty"
mbDs & maybe none \ds -> do
debug $ "UPDATE REF" <+> pretty (hashObject @HbSync ref) <+> pretty (HashRef ds)
updateRef sto ref ds
pause @'Seconds 0.25
spin
Just val -> do
pure val
readStateHashMay :: AnyStorage -> MyRefKey -> IO (Maybe (Hash HbSync))
readStateHashMay sto ref =
getRef @_ @HbSync sto ref

View File

@ -0,0 +1,214 @@
module RefChanQBLF.CLI where
import HBS2.Actors.Peer
import HBS2.Actors.Peer.Types ()
import HBS2.Base58
import HBS2.Clock
import HBS2.Data.Types.Refs
import HBS2.Data.Types.SignedBox
import HBS2.Defaults
import HBS2.Hash
import HBS2.Net.Auth.Credentials
import HBS2.Net.Messaging.Unix
import HBS2.Net.Proto.Service
import HBS2.OrDie
import HBS2.Peer.Proto.AnyRef
import HBS2.Peer.Proto.RefChan
import HBS2.Prelude
import HBS2.Storage.Simple
import HBS2.System.Logger.Simple
import Codec.Serialise
import Control.Arrow hiding ((<+>))
import Control.Monad.Cont
import Control.Monad.Except
import Control.Monad.Reader
import Data.ByteString qualified as BS
import Data.ByteString.Char8 qualified as BS8
import Data.ByteString.Lazy qualified as LBS
import Data.Config.Suckless
import Data.HashMap.Strict qualified as HashMap
import Data.String.Conversions (cs)
import Lens.Micro.Platform hiding ((.=))
import Options.Applicative hiding (info)
import Options.Applicative qualified as O
import System.Directory
import System.Exit qualified as Exit
import UnliftIO
import RefChanQBLF.App
import RefChanQBLF.Common
import RefChanQBLF.Impl
import RefChanQBLF.RPCServer
import RefChanQBLF.Transactions
type Config = [Syntax C]
main :: IO ()
main = join . customExecParser (prefs showHelpOnError) $
O.info (helper <*> globalOptions)
( fullDesc
<> header "refchan-qblf-worker"
<> progDesc "for test and demo purposed"
)
where
globalOptions = applyConfig <$> commonOpts <*> cli
applyConfig :: Maybe FilePath -> (Config -> IO ()) -> IO ()
applyConfig config m = do
maybe1 config (m mempty) $ \conf -> do
top <- readFile conf <&> parseTop <&> either (pure mempty) id
m top
commonOpts = optional $ strOption (long "config" <> short 'c' <> help "Config file")
cli = hsubparser ( command "run" (O.info pRun (progDesc "run qblf servant" ) )
<> command "gen" (O.info pGen (progDesc "generate transcation") )
<> command "post" (O.info pPostTx (progDesc "post transaction") )
<> command "check" (O.info pCheckTx (progDesc "check transaction") )
<> command "balances" (O.info pBalances (progDesc "show balances") )
)
pGen = hsubparser
( command "tx-emit" ( O.info pGenEmit (progDesc "generate emit") )
<> command "tx-move" ( O.info pGenMove (progDesc "generate move") )
)
pGenEmit = do
kr <- strOption ( long "keyring" <> short 'k' <> help "keyring file" )
amnt <- option @Amount auto ( long "amount" <> short 'n' <> help "amount" )
dest <- strArgument ( metavar "ADDRESS" )
pure $ const $ silently do
sc <- BS.readFile kr
creds <- pure (parseCredentials @'HBS2Basic (AsCredFile sc)) `orDie` "bad keyring file"
let pk = view peerSignPk creds
let sk = view peerSignSk creds
acc <- pure (fromStringMay @(RefChanId L4Proto) dest) `orDie` "bad account address"
tx <- makeEmitDemoToken @_ @L4Proto pk sk acc amnt
LBS.putStr $ serialise tx
pGenMove = do
kr <- strOption ( long "wallet" <> short 'w' <> help "wallet (keyring) file" )
amnt <- option @Amount auto ( long "amount" <> short 'n' <> help "amount" )
dest <- strArgument ( metavar "ADDRESS" )
pure $ const $ silently do
sc <- BS.readFile kr
creds <- pure (parseCredentials @'HBS2Basic (AsCredFile sc)) `orDie` "bad keyring file"
let pk = view peerSignPk creds
let sk = view peerSignSk creds
acc <- pure (fromStringMay @(RefChanId L4Proto) dest) `orDie` "bad account address"
tx <- makeMoveDemoToken @_ @L4Proto pk sk acc amnt
LBS.putStr $ serialise tx
pCheckTx = do
pure $ const do
tx <- either (Exit.die . ("QBLFDemoToken deserialise error: " <>) . show) pure
. deserialiseOrFail @(QBLFDemoToken 'HBS2Basic)
=<< LBS.getContents
case tx of
Emit box ->
BS8.hPutStrLn stderr . cs . show . pretty . first AsBase58
=<< pure (unboxSignedBox0 box) `orDie` "bad emit tx"
Move box ->
BS8.hPutStrLn stderr . cs . show . pretty . first AsBase58
=<< pure (unboxSignedBox0 box) `orDie` "bad move tx"
pure ()
pBalances :: Parser (Config -> IO ())
pBalances = do
mstateref <- optional do
option (fromStringP @HashRef "qblf state hash")
(long "state-hash" <> metavar "HASH-REF")
pure \syn -> withLogging do
bal <- flip runContT pure do
sto <- ContT $ withSimpleAnyStorage defStorePath
lift do
stateHashRef :: HashRef
<- mstateref & flip maybe pure do
either Exit.die pure =<< runExceptT do
stref <- (flip runReader syn $ cfgValue @StateRefOpt @(Maybe String))
& orE "state-ref key not found in config"
<&> fromStringMay
& orEM "state-ref key parse error"
HashRef <$> do
liftIO (readStateHashMay sto stref)
& orEM "State is not created yed"
flip runReaderT sto $ do
debug $ "calculating balances for" <+> pretty stateHashRef
balances stateHashRef
forM_ (HashMap.toList bal) $ \(acc, qty) -> do
liftIO $ print $ pretty (AsBase58 acc) <+> pretty qty
fromStringP :: (FromStringMaybe a) => String -> ReadM a
fromStringP msg = eitherReader $
maybe (Left ("Can not parse " <> msg)) Right . fromStringMay . cs
refchanP :: ReadM (RefChanId L4Proto)
refchanP = fromStringP "refchan id"
pPostTx :: Parser (Config -> IO ())
pPostTx = do
pure \syn -> withLogging do
debug $ "runQBLFApp" <+> pretty syn
appsopath <- maybe (Exit.die "app-socket path not found in config") pure do
flip runReader syn do
cfgValue @AppSocketOpt @(Maybe String)
tx <- either (Exit.die . ("QBLFDemoToken deserialise error: " <>) . show) pure
. deserialiseOrFail @(QBLFDemoToken 'HBS2Basic)
=<< LBS.getContents
messagingUnix :: MessagingUnix <- newMessagingUnix False 1.0 appsopath
ep <- makeServiceCaller @QBLFAppRPC @UNIX (msgUnixSelf messagingUnix)
flip runContT pure do
contAsync $ runMessagingUnix messagingUnix
contAsync $ runReaderT (runServiceClient ep) messagingUnix
lift do
maybe (Exit.die "RPC server is not available") pure
=<< callRpcWaitMay @PingRPC (TimeoutSec 0.42) ep ()
r :: Text
<- callRpcWaitMay @PostTxRPC (TimeoutSec 3) ep tx
& peelMWith Exit.die do
orE "RPC server timeout" >>> leftEM show >>> leftEM show
LBS.putStr . cs $ r
pRun :: Parser (Config -> IO ())
pRun = pure \conf -> withLogging do
debug $ "runQBLFApp" <+> pretty conf
runQBLFApp =<< (either Exit.die pure . parseQBLFAppConf) conf
parseQBLFAppConf :: Config -> Either String QBLFAppConf
parseQBLFAppConf = runReaderT do
qapActorKeyring <- cfgValue @ActorOpt @(Maybe String)
& orEM "actor's key not set"
qapRefchanID <- cfgValue @RefChanOpt @(Maybe String)
& orEM "refchan not set"
<&> fromStringMay @(RefChanId L4Proto)
& orEM "invalid REFCHAN value in config"
qapSocket <- cfgValue @SocketOpt @(Maybe String)
& orEM "socket not set"
qapAppSocket <- cfgValue @AppSocketOpt @(Maybe String)
& orEM "app socket not set"
qapDefState <- cfgValue @DefStateOpt @(Maybe String)
<&> (>>= fromStringMay)
qapStateRef <- cfgValue @StateRefOpt @(Maybe String)
& orEM "state-ref key not found in config"
<&> fromStringMay
& orEM "state-ref key parse error"
pure QBLFAppConf {..}

View File

@ -0,0 +1,58 @@
module RefChanQBLF.Common where
import HBS2.Data.Types
import HBS2.Peer.RPC.Client.Unix ()
import Control.Monad.Cont
import Control.Monad.Except
import Data.Bool
import Data.Text (Text)
import GHC.Generics (Generic)
import Prettyprinter
import UnliftIO
data MyError
= DeserializationError
| SignatureError
| SignerDoesNotMatchRefchan Text Text
| TxUnsupported
| SomeOtherError
deriving stock (Generic, Show)
instance Serialise MyError
instance Exception MyError
whenM :: (Monad m) => m Bool -> m () -> m ()
whenM mb mu = bool (pure ()) mu =<< mb
contAsync :: (MonadUnliftIO m) => m a -> ContT r m ()
contAsync = (link =<<) . ContT . withAsync
orE :: (MonadError e m) => e -> Maybe b -> m b
orE msg = maybe (throwError msg) pure
orEM :: (MonadError e m) => e -> m (Maybe b) -> m b
orEM msg mb = orE msg =<< mb
leftE :: (MonadError e m) => (a -> e) -> Either a b -> m b
leftE toe = either (throwError . toe) pure
leftEM :: (MonadError e m) => (a -> e) -> m (Either a b) -> m b
leftEM toe meab = leftE toe =<< meab
peelMWith
:: (Monad m)
=> (e -> m a)
-> (b -> Either e a)
-> m b
-> m a
peelMWith ema bea mb = either ema pure . bea =<< mb
newtype PrettyEither e a = PrettyEither (Either e a)
instance
(Pretty e, Pretty a)
=> Pretty (PrettyEither e a)
where
pretty (PrettyEither ea) = case ea of
Left e -> "Left" <+> pretty e
Right a -> "Right" <+> pretty a

View File

@ -0,0 +1,476 @@
{-# OPTIONS_GHC -fno-warn-orphans #-}
{-# Language TemplateHaskell #-}
{-# Language UndecidableInstances #-}
{-# Language AllowAmbiguousTypes #-}
module RefChanQBLF.Impl where
import HBS2.Actors.Peer
import HBS2.Actors.Peer.Types()
import HBS2.Base58
import HBS2.Data.Bundle
import HBS2.Data.Detect
import HBS2.Data.Types.Refs
import HBS2.Data.Types.SignedBox
import HBS2.Hash
import HBS2.Merkle
import HBS2.Net.Auth.Credentials
import HBS2.Net.Messaging.Unix
import HBS2.Net.Proto.QBLF
import HBS2.Peer.Proto.AnyRef
import HBS2.Peer.Proto.RefChan
import HBS2.Prelude
import HBS2.Storage.Simple
import HBS2.System.Logger.Simple
import RefChanQBLF.Common
import RefChanQBLF.Transactions
import Data.Config.Suckless
import Control.Monad.Except
import Control.Monad.Trans.Maybe
import Codec.Serialise
import Control.Monad.Reader
import Data.ByteString.Lazy qualified as LBS
import Data.List qualified as List
import Lens.Micro.Platform hiding ((.=))
import Options.Applicative hiding (info)
import Data.HashSet qualified as HashSet
import Data.HashMap.Strict (HashMap)
import Data.HashMap.Strict qualified as HashMap
import Data.Maybe
import Data.Word
import System.Random
import UnliftIO
import Data.Cache (Cache)
import Data.Cache qualified as Cache
{- HLINT ignore "Use newtype instead of data" -}
-- TODO: config
-- сделать конфиг, а то слишком много уже параметров в CLI
data AppSocketOpt
data RefChanOpt
data SocketOpt
data ActorOpt
data DefStateOpt
data StateRefOpt
data QBLFRefKey
type MyRefKey = AnyRefKey QBLFRefKey 'HBS2Basic
instance HasCfgKey AppSocketOpt (Maybe String) where
key = "app-socket"
instance HasCfgKey RefChanOpt (Maybe String) where
key = "refchan"
instance HasCfgKey SocketOpt (Maybe String) where
key = "socket"
instance HasCfgKey ActorOpt (Maybe String) where
key = "actor"
instance HasCfgKey DefStateOpt (Maybe String) where
key = "default-state"
instance HasCfgKey StateRefOpt (Maybe String) where
key = "state-ref"
class ToBalance s tx where
toBalance :: tx -> [(Account s, Amount)]
tracePrefix :: SetLoggerEntry
tracePrefix = toStderr . logPrefix "[trace] "
debugPrefix :: SetLoggerEntry
debugPrefix = toStderr . logPrefix "[debug] "
errorPrefix :: SetLoggerEntry
errorPrefix = toStderr . logPrefix "[error] "
warnPrefix :: SetLoggerEntry
warnPrefix = toStderr . logPrefix "[warn] "
noticePrefix :: SetLoggerEntry
noticePrefix = toStderr . logPrefix "[notice] "
infoPrefix :: SetLoggerEntry
infoPrefix = toStdout . logPrefix ""
silently :: MonadIO m => m a -> m ()
silently m = do
setLoggingOff @DEBUG
setLoggingOff @INFO
setLoggingOff @ERROR
setLoggingOff @WARN
setLoggingOff @NOTICE
void m
withLogging :: MonadIO m => m a -> m ()
withLogging m = do
-- setLogging @TRACE tracePrefix
setLogging @DEBUG debugPrefix
setLogging @INFO infoPrefix
setLogging @ERROR errorPrefix
setLogging @WARN warnPrefix
setLogging @NOTICE noticePrefix
m
setLoggingOff @DEBUG
setLoggingOff @INFO
setLoggingOff @ERROR
setLoggingOff @WARN
setLoggingOff @NOTICE
data MyEnv =
MyEnv
{ mySelf :: Peer UNIX
, myFab :: Fabriq UNIX
, myChan :: RefChanId UNIX
, myRef :: MyRefKey
, mySto :: AnyStorage
, myCred :: PeerCredentials 'HBS2Basic
-- , myHttpPort :: Int
, myFetch :: Cache HashRef ()
}
newtype MyAppT m a = MyAppT { fromQAppT :: ReaderT MyEnv m a }
deriving newtype ( Functor
, Applicative
, Monad
, MonadIO
, MonadUnliftIO
, MonadReader MyEnv
, MonadTrans
)
runMyAppT :: (MonadIO m, PeerMessaging UNIX) => MyEnv -> MyAppT m a -> m a
runMyAppT env m = runReaderT (fromQAppT m) env
instance Monad m => HasFabriq UNIX (MyAppT m) where
getFabriq = asks myFab
instance Monad m => HasOwnPeer UNIX (MyAppT m) where
ownPeer = asks mySelf
instance Monad m => HasStorage (MyAppT m) where
getStorage = asks mySto
data ConsensusQBLF
data StateQBLF = StateQBLF { fromStateQBLF :: HashRef }
check :: MonadIO m => MyError -> Either e a -> ExceptT MyError m a
check w = \case
Right x -> ExceptT $ pure (Right x)
Left{} -> ExceptT $ pure (Left w)
fiasco :: MonadIO m => MyError -> ExceptT MyError m a
fiasco x = ExceptT $ pure $ Left x
ok :: MonadIO m => a -> ExceptT MyError m a
ok x = ExceptT $ pure $ Right x
type ForConsensus m = (MonadIO m, Serialise (QBLFMessage ConsensusQBLF))
instance Serialise (QBLFMerge ConsensusQBLF)
instance Serialise (QBLFMessage ConsensusQBLF)
instance Serialise (QBLFAnnounce ConsensusQBLF)
instance Serialise (QBLFCommit ConsensusQBLF)
instance Monad m => HasTimeLimits UNIX (RefChanNotify UNIX) (MyAppT m) where
tryLockForPeriod _ _ = pure True
instance (ForConsensus m, MonadUnliftIO m) => IsQBLF ConsensusQBLF (MyAppT m) where
type QBLFActor ConsensusQBLF = Actor 'HBS2Basic
type QBLFTransaction ConsensusQBLF = QBLFDemoToken 'HBS2Basic
type QBLFState ConsensusQBLF = DAppState
qblfMoveForward _ s1 = do
env <- ask
fetchMissed env s1
pure True
qblfNewState (DAppState h0) txs = do
sto <- asks mySto
chan <- asks myChan
self <- asks mySelf
creds <- asks myCred
let sk = view peerSignSk creds
let pk = view peerSignPk creds
-- основная проблема в том, что мы пересортировываем весь state
-- однако, если считать его уже отсортированным, то, может быть,
-- все будет не так уж плохо.
-- так-то мы можем вообще его на диске держать
root <- if List.null txs then do
pure h0
else do
hashes <- liftIO $ mapM (putBlock sto . serialise) txs <&> catMaybes
current <- readLogThrow (getBlock sto) h0
let new = HashSet.fromList ( current <> fmap HashRef hashes )
let pt = toPTree (MaxSize 256) (MaxNum 256) (HashSet.toList new)
-- пробуем разослать бандлы с транзакциями
runMaybeT do
ref <- MaybeT $ createBundle sto (fmap HashRef hashes)
let refval = makeBundleRefValue @'HBS2Basic pk sk (BundleRefSimple ref)
r <- MaybeT $ liftIO $ putBlock sto (serialise refval)
lift $ request self (ActionRequest @UNIX chan (RefChanAnnounceBlock (HashRef r)))
r <- makeMerkle 0 pt $ \(hx,_,bs) -> do
_th <- liftIO (enqueueBlock sto bs)
debug $ "WRITE TX" <+> pretty hx
request self (ActionRequest @UNIX chan (RefChanAnnounceBlock (HashRef r)))
pure (HashRef r)
debug $ "PROPOSED NEW STATE:" <+> pretty root
pure $ DAppState root
qblfCommit s0 s1 = do
debug $ "COMMIT:" <+> pretty s0 <+> pretty s1
sto <- asks mySto
_chan <- asks myChan
ref <- asks myRef
debug $ "UPDATING REF" <+> pretty ref <+> pretty s1
liftIO $ updateRef sto ref (fromHashRef (fromDAppState s1))
pure ()
qblfBroadCast msg = do
self <- asks mySelf
creds <- asks myCred
chan <- asks myChan
let sk = view peerSignSk creds
let pk = view peerSignPk creds
nonce <- randomIO @Word64 <&> serialise <&> LBS.toStrict
let box = makeSignedBox pk sk (LBS.toStrict (serialise msg) <> nonce)
let notify = Notify @UNIX chan box
request self notify
case msg of
QBLFMsgAnn _ (QBLFAnnounce _ _) -> do
-- TODO: maybe-announce-new-state-here
pure ()
_ -> none
-- TODO: optimize-qblf-merge
-- будет нормально работать до десятков/сотен тысяч транз,
-- а потом помрёт.
-- варианты:
-- 1. перенести логику в БД
-- 2. кэшировать всё, что можно
qblfMerge s0 s1 = do
chan <- asks myChan
_self <- asks mySelf
creds <- asks myCred
let _sk = view peerSignSk creds
let _pk = view peerSignPk creds
debug $ "MERGE. Proposed state:" <+> pretty s1
sto <- asks mySto
let readFn = liftIO . getBlock sto
tx1 <- mapM (readLogThrow readFn) (fmap fromDAppState s1) <&> mconcat
tx0 <- readLogThrow readFn (fromDAppState s0) <&> HashSet.fromList
let txNew = HashSet.fromList tx1 `HashSet.difference` tx0
if List.null txNew then do
pure s0
else do
debug $ "READ TXS" <+> pretty s1 <+> pretty (length tx1)
r <- forM tx1 $ \t -> runMaybeT do
-- игнорируем ранее добавленные транзакции
guard (not (HashSet.member t tx0))
bs <- MaybeT $ liftIO $ getBlock sto (fromHashRef t)
tx <- MaybeT $ pure $ deserialiseOrFail @(QBLFDemoToken 'HBS2Basic) bs & either (const Nothing) Just
case tx of
Emit box -> do
(pk', e@(EmitTx a q _)) <- MaybeT $ pure $ unboxSignedBox0 @(EmitTx 'HBS2Basic) box
guard ( chan == pk' )
debug $ "VALID EMIT TRANSACTION" <+> pretty t <+> pretty (AsBase58 a) <+> pretty q
pure ([(t,e)], mempty)
(Move box) -> do
(_, m@(MoveTx _ _ qty _)) <- MaybeT $ pure $ unboxSignedBox0 @(MoveTx 'HBS2Basic) box
guard (qty > 0)
debug $ "MOVE TRANSACTION" <+> pretty t
pure (mempty, [(t,m)])
let parsed = catMaybes r
let emits = foldMap (view _1) parsed
let moves = foldMap (view _2) parsed & List.sortOn fst
bal0 <- balances (fromDAppState s0)
-- баланс с учётом новых emit
let balE = foldMap (toBalance @'HBS2Basic. snd) emits
& HashMap.fromListWith (+)
& HashMap.unionWith (+) bal0
let moves' = updBalances @L4Proto balE moves
let merged = fmap fst emits <> fmap fst moves'
let pt = toPTree (MaxSize 256) (MaxNum 256) (HashSet.toList (tx0 <> HashSet.fromList merged))
root <- makeMerkle 0 pt $ \(_,_,bs) -> do
void $ liftIO (putBlock sto bs)
let new = DAppState (HashRef root)
-- FIXME: garbage-collect-discarded-states
async $ void $ balances (fromDAppState new)
debug $ "MERGED" <+> pretty new
pure new
instance HasStorage (ReaderT AnyStorage IO) where
getStorage = ask
instance ToBalance e (EmitTx e) where
toBalance (EmitTx a qty _) = [(a, qty)]
instance ToBalance e (MoveTx e) where
toBalance (MoveTx a1 a2 qty _) = [(a1, -qty), (a2, qty)]
balances :: forall e s m . ( e ~ L4Proto
, MonadIO m
, HasStorage m
-- , FromStringMaybe (PubKey 'Sign s)
, s ~ Encryption e
, ToBalance s (EmitTx s)
, ToBalance s (MoveTx s)
, Pretty (AsBase58 (PubKey 'Sign s))
)
=> HashRef
-> m (HashMap (Account s) Amount)
balances root = do
sto <- getStorage
let pk = SomeRefKey (HashRef "6ChGmfYkwM6646oKkj8r8MAjdViTsdtZSi6tgqk3tbh", root)
cached <- runMaybeT do
rval <- MaybeT $ liftIO $ getRef sto pk
val <- MaybeT $ liftIO $ getBlock sto rval
MaybeT $ deserialiseOrFail @(HashMap (Account s) Amount) val
& either (const $ pure Nothing) (pure . Just)
case cached of
Just bal -> pure bal
Nothing -> do
txs <- readLogThrow (liftIO . getBlock sto) root
r <- forM txs $ \h -> runMaybeT do
blk <- MaybeT $ liftIO $ getBlock sto (fromHashRef h)
tx <- MaybeT $ pure $ deserialiseOrFail @(QBLFDemoToken s) blk & either (const Nothing) Just
case tx of
Emit box -> do
(_, emit) <- MaybeT $ pure $ unboxSignedBox0 @(EmitTx s) box
pure $ toBalance @s emit
Move box -> do
(_, move) <- MaybeT $ pure $ unboxSignedBox0 @(MoveTx s) box
pure $ toBalance @s move
let val = catMaybes r & mconcat & HashMap.fromListWith (+)
runMaybeT do
checkComplete sto root >>= guard
rv <- MaybeT $ liftIO $ putBlock sto (serialise val)
liftIO $ updateRef sto pk rv
pure val
-- TODO: optimize-upd-balances
-- можно сгруппировать по аккаунтам
-- и проверять только те транзакции, которые относятся
-- к связанной (транзакциями) группе аккаунтов.
-- то есть, разбить на кластеры, у которых отсутствуют пересечения по
-- аккаунтам и проверять независимо и параллельно, например
-- причем, прямо этой функцией
--
-- updBalances :: HashMap (Account L4Proto) Amount
-- -> [(tx, b)]
-- -> [(tx, b)]
updBalances :: forall e s a tx . (ForRefChans e, ToBalance s tx, s ~ Encryption e)
=> HashMap (Account s) Amount
-> [(a, tx)]
-> [(a, tx)]
updBalances = go
where
go _bal [] = empty
go bal (t:rest) =
if good then
t : go nb rest
else
go bal rest
where
nb = HashMap.unionWith (+) bal (HashMap.fromList (toBalance @s (snd t)))
good = HashMap.filter (<0) nb & HashMap.null
fetchMissed :: forall e w m . ( MonadIO m
, Request e (RefChanNotify e) m
, e ~ UNIX
, w ~ ConsensusQBLF
)
=> MyEnv
-> QBLFState w
-> m ()
fetchMissed env s = do
let tube = mySelf env
let chan = myChan env
let cache = myFetch env
let sto = mySto env
let href = fromDAppState s
here <- liftIO $ hasBlock sto (fromHashRef href) <&> isJust
wip <- liftIO $ Cache.lookup cache href <&> isJust
when here do
liftIO $ Cache.delete cache href
unless (here || wip) do
debug $ "We might be need to fetch" <+> pretty s
liftIO $ Cache.insert cache href ()
request @UNIX tube (ActionRequest @UNIX chan (RefChanFetch (fromDAppState s)))

View File

@ -0,0 +1,141 @@
{-# LANGUAGE StrictData #-}
module RefChanQBLF.RPCServer where
import HBS2.Actors.Peer
import HBS2.Base58
import HBS2.Data.Types.SignedBox
import HBS2.Hash
import HBS2.Net.Messaging.Unix
import HBS2.Net.Proto.QBLF
import HBS2.Net.Proto.Service
import HBS2.System.Logger.Simple
import Codec.Serialise
import Control.Monad
import Control.Monad.Except
import Control.Monad.Reader
import Data.ByteString.Lazy (ByteString)
import Data.Function
import Data.String.Conversions (cs)
import Data.Text (Text)
import Data.Text qualified as T
import GHC.Generics (Generic)
import Prettyprinter
import UnliftIO
import RefChanQBLF.Common
import RefChanQBLF.Impl
import RefChanQBLF.Transactions
data PingRPC
data PostTxRPC
type QBLFAppRPC =
'[ PingRPC
, PostTxRPC
]
instance HasProtocol UNIX (ServiceProto QBLFAppRPC UNIX) where
type ProtocolId (ServiceProto QBLFAppRPC UNIX) = 0x0B1F0B1F
type Encoded UNIX = ByteString
decode = either (const Nothing) Just . deserialiseOrFail
encode = serialise
type instance Input PingRPC = ()
type instance Output PingRPC = Text
type instance Input PostTxRPC = QBLFDemoToken 'HBS2Basic
type instance Output PostTxRPC = Either RPCServerError (Either MyError Text)
data QRPCEnv = QRPCEnv
{ qrpcenvQConsensus :: QBLF ConsensusQBLF
, qrpcenvRefchanId :: PubKey 'Sign 'HBS2Basic
, qrpcenvFabriq :: Fabriq UNIX
, qrpcenvOwnPeer :: Peer UNIX
}
newtype QRPCAppT m a = QRPCAppT {fromQRPCAppT :: ReaderT QRPCEnv m a}
deriving newtype
( Functor
, Applicative
, Monad
, MonadIO
, MonadUnliftIO
, MonadReader QRPCEnv
, MonadTrans
)
instance (Monad m) => HasFabriq UNIX (QRPCAppT m) where
getFabriq = asks qrpcenvFabriq
instance (Monad m) => HasOwnPeer UNIX (QRPCAppT m) where
ownPeer = asks qrpcenvOwnPeer
instance (Monad m) => HasQBLFEnv (ResponseM UNIX (QRPCAppT m)) where
getQBLFEnv = lift ask
runQRPCT
:: (MonadIO m, PeerMessaging UNIX)
=> QRPCEnv
-> QRPCAppT m a
-> m a
runQRPCT env m = runReaderT (fromQRPCAppT m) env
class HasQBLFEnv m where
getQBLFEnv :: m QRPCEnv
data RPCServerError = RPCServerError Text
deriving (Generic, Show)
instance Serialise RPCServerError
wrapErrors :: (MonadUnliftIO m) => m a -> m (Either RPCServerError a)
wrapErrors =
UnliftIO.tryAny >=> flip either (pure . Right) \e -> do
debug $ "RPC ServerError" <+> viaShow e
pure $ (Left . RPCServerError . T.pack . show) e
instance (MonadIO m, HasQBLFEnv m) => HandleMethod m PingRPC where
handleMethod _ = do
debug $ "RPC PING"
pure "pong"
instance
( MonadUnliftIO m
, HasQBLFEnv m
)
=> HandleMethod m PostTxRPC
where
handleMethod tok = wrapErrors $ UnliftIO.try do
let txhash = (hashObject @HbSync . serialise) tok
ptok = pretty tok
debug $ "RPC got post tx" <+> pretty txhash <+> ptok
refchanId <- qrpcenvRefchanId <$> getQBLFEnv
validateQBLFToken refchanId tok
& either throwIO pure
qblf <- qrpcenvQConsensus <$> getQBLFEnv
qblfEnqueue qblf tok
debug $ "TX ENQUEUED OK" <+> ptok
pure $ "Enqueued: " <> (cs . show) ptok
validateQBLFToken
:: (MonadError MyError m)
=> PubKey 'Sign 'HBS2Basic
-> QBLFDemoToken 'HBS2Basic
-> m ()
validateQBLFToken chan = \case
Emit box -> do
(signer, _tx) <- orE SignatureError $ unboxSignedBox0 box
unless (signer == chan) do
throwError
( SignerDoesNotMatchRefchan
((cs . show . pretty . AsBase58) signer)
((cs . show . pretty . AsBase58) chan)
)
Move box -> do
(_sign, _tx) <- orE SignatureError $ unboxSignedBox0 box
pure ()

View File

@ -0,0 +1,199 @@
{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE UndecidableInstances #-}
module RefChanQBLF.Transactions where
import Data.String.Conversions (cs)
import HBS2.Base58
import HBS2.Data.Types.Refs (HashRef (..))
import HBS2.Data.Types.SignedBox
import HBS2.Hash
import HBS2.Net.Auth.Credentials
import HBS2.Net.Messaging.Unix (UNIX)
import HBS2.Peer.Proto
import HBS2.Prelude.Plated
import Codec.Serialise
import Control.Arrow hiding ((<+>))
import Data.ByteString.Lazy (ByteString)
import Data.Hashable (Hashable (..))
import Data.Word (Word64)
import System.Random
import RefChanQBLF.Common
newtype Actor s = Actor {fromActor :: PubKey 'Sign s}
deriving stock (Generic)
deriving stock instance (Eq (PubKey 'Sign s)) => Eq (Actor s)
deriving newtype instance (Hashable (PubKey 'Sign s)) => Hashable (Actor s)
instance (Pretty (AsBase58 (PubKey 'Sign s))) => Pretty (Actor s) where
pretty (Actor a) = pretty (AsBase58 a)
type Account s = PubKey 'Sign s
newtype Amount = Amount Integer
deriving stock (Eq, Show, Ord, Data, Generic)
deriving newtype (Read, Enum, Num, Integral, Real, Pretty)
newtype DAppState = DAppState {fromDAppState :: HashRef}
deriving stock (Eq, Show, Ord, Data, Generic)
deriving newtype (Hashable, Pretty)
instance Hashed HbSync DAppState where
hashObject (DAppState (HashRef h)) = h
data EmitTx s = EmitTx (Account s) Amount Word64
deriving stock (Generic)
instance (Pretty (AsBase58 (PubKey 'Sign s))) => Pretty (EmitTx s) where
pretty (EmitTx acc amount n) =
"Emit"
<+> "to:"
<> pretty (AsBase58 acc)
<+> "amount:"
<> pretty amount
<+> "nonce:"
<> pretty n
data MoveTx s = MoveTx (Account s) (Account s) Amount Word64
deriving stock (Generic)
instance (Pretty (AsBase58 (PubKey 'Sign s))) => Pretty (MoveTx s) where
pretty (MoveTx accfrom accto amount n) =
"Move"
<+> "from:"
<> pretty (AsBase58 accfrom)
<+> "to:"
<> pretty (AsBase58 accto)
<+> "amount:"
<> pretty amount
<+> "nonce:"
<> pretty n
data QBLFDemoToken s
= Emit (SignedBox (EmitTx s) s) -- proof: owner's key
| Move (SignedBox (MoveTx s) s) -- proof: wallet's key
deriving stock (Generic)
instance
( Pretty (AsBase58 (PubKey 'Sign s))
, Signatures s
, Eq (Signature s)
, FromStringMaybe (PubKey 'Sign s)
, Serialise (PubKey 'Sign s)
, Serialise (Signature s)
, Hashable (PubKey 'Sign s)
)
=> Pretty (QBLFDemoToken s)
where
pretty = \case
Emit box -> pretty (WhiteSignedBox @s box)
Move box -> pretty (WhiteSignedBox @s box)
newtype WhiteSignedBox s a = WhiteSignedBox (SignedBox a s)
instance
( Pretty (AsBase58 (PubKey 'Sign s))
, Pretty a
, Serialise a
)
=> Pretty (WhiteSignedBox s a)
where
pretty (WhiteSignedBox (SignedBox pk bs _sign)) =
"SignedBox"
<+> "Hash:"
<+> pretty ((hashObject @HbSync . serialise) bs)
<+> "SignedBy:"
<+> pretty (AsBase58 pk)
<+> "("
<> pretty ((PrettyEither . left show . deserialiseOrFail @a . cs) bs)
<> ")"
instance (ForQBLFDemoToken s) => Serialise (Actor s)
instance Serialise DAppState
instance Serialise Amount
instance (ForQBLFDemoToken s) => Serialise (EmitTx s)
instance (ForQBLFDemoToken s) => Serialise (MoveTx s)
instance (ForQBLFDemoToken s) => Serialise (QBLFDemoToken s)
type ForQBLFDemoToken s =
( Eq (PubKey 'Sign s)
, Eq (Signature s)
, Pretty (AsBase58 (PubKey 'Sign s))
, ForSignedBox s
, FromStringMaybe (PubKey 'Sign s)
, Serialise (PubKey 'Sign s)
, Serialise (Signature s)
, Hashable (PubKey 'Sign s)
)
deriving stock instance (ForQBLFDemoToken s) => Eq (QBLFDemoToken s)
instance (ForQBLFDemoToken s) => Hashable (QBLFDemoToken s) where
hashWithSalt salt = \case
Emit box -> hashWithSalt salt box
Move box -> hashWithSalt salt box
newtype QBLFDemoTran e
= QBLFDemoTran (SignedBox (QBLFDemoToken (Encryption e)) (Encryption e))
deriving stock (Generic)
instance (ForRefChans e) => Serialise (QBLFDemoTran e)
deriving newtype instance
(Eq (PubKey 'Sign (Encryption e)), Eq (Signature (Encryption e)))
=> Eq (QBLFDemoTran e)
deriving newtype instance
(Eq (Signature (Encryption e)), ForRefChans e)
=> Hashable (QBLFDemoTran e)
instance HasProtocol UNIX (QBLFDemoTran UNIX) where
type ProtocolId (QBLFDemoTran UNIX) = 0xFFFF0001
type Encoded UNIX = ByteString
decode = either (const Nothing) Just . deserialiseOrFail
encode = serialise
makeEmitDemoToken
:: forall s e m
. ( MonadIO m
, ForRefChans e
, ForQBLFDemoToken s
, Signatures (Encryption e)
, s ~ Encryption e
)
=> PubKey 'Sign s
-> PrivKey 'Sign s
-> Account s
-> Amount
-> m (QBLFDemoToken s)
makeEmitDemoToken pk sk acc amount = do
nonce <- randomIO
let box = makeSignedBox @s pk sk (EmitTx acc amount nonce)
pure (Emit @s box)
makeMoveDemoToken
:: forall s e m
. ( MonadIO m
, ForQBLFDemoToken s
, ForRefChans e
, Signatures s
, s ~ Encryption e
)
=> PubKey 'Sign s -- from pk
-> PrivKey 'Sign s -- from sk
-> Account s
-> Amount -- amount
-> m (QBLFDemoToken s)
makeMoveDemoToken pk sk acc amount = do
nonce <- randomIO
let box = makeSignedBox @s pk sk (MoveTx pk acc amount nonce)
pure (Move @s box)

View File

@ -19,6 +19,7 @@ common warnings
common common-deps
build-depends:
base, hbs2-core, hbs2-peer, hbs2-storage-simple, hbs2-qblf
, hbs2-qblf
, aeson
, async
, bytestring
@ -57,6 +58,18 @@ common common-deps
, interpolatedstring-perl6
, unliftio
, attoparsec
, clock
, data-textual
, network
, network-ip
, optparse-applicative
, string-conversions
, text
, time
common shared-properties
ghc-options:
-Wall
@ -94,68 +107,48 @@ common shared-properties
, MultiParamTypeClasses
, OverloadedStrings
, QuasiQuotes
, RecordWildCards
, ScopedTypeVariables
, StandaloneDeriving
, TupleSections
, TypeApplications
, TypeOperators
, TypeFamilies
library
import: shared-properties
import: common-deps
hs-source-dirs: lib
exposed-modules:
RefChanQBLF.App
RefChanQBLF.CLI
RefChanQBLF.Common
RefChanQBLF.Impl
RefChanQBLF.RPCServer
RefChanQBLF.Transactions
executable refchan-qblf
import: shared-properties
import: common-deps
default-language: Haskell2010
build-depends:
refchan-qblf
ghc-options:
-- -prof
-- -fprof-auto
other-modules:
Demo.QBLF.Transactions
-- other-extensions:
-- type: exitcode-stdio-1.0
hs-source-dirs: app lib
main-is: RefChanQBLFMain.hs
build-depends:
base, hbs2-core, hbs2-qblf, hbs2-storage-simple
, async
, attoparsec
, bytestring
, cache
, clock
, containers
, data-default
, data-textual
, directory
, hashable
, microlens-platform
, mtl
, mwc-random
, network
, network-ip
, optparse-applicative
, prettyprinter
, QuickCheck
, random
, safe
, serialise
, stm
, streaming
, tasty
, tasty-hunit
, text
, time
, transformers
, uniplate
, vector
, unliftio
hs-source-dirs: app
main-is: Main.hs
test-suite refchan-qblf-proto-test
import: shared-properties
default-language: Haskell2010
import: common-deps
other-modules:

35
examples/site/bar.ss Normal file
View File

@ -0,0 +1,35 @@
; это страница, на которую ссылается foo.ss
(define (bar-page)
[html :html [kw]
[html :head [kw]
[html :title [kw] Suckless HTML Page]
[html :meta [kw :charset UTF-8]]
[html :style [kw]
[css body [kw font-family sans-serif margin-left 20px max-width 1024px]]
[css table [kw border-collapse collapse width auto]]
[css (list td th) [kw border [sym (unwords 1px solid #ccc)]
padding 8px
text-align left]]
[css th [kw background-color #f2f2f2 white-space nowrap]]
[css .che [kw margin-right 8px]]
]
]
[html :body [kw]
[html :h1 [kw] Some other page]
[html :h2 [kw] Built with Suckless Script]
[html :p [kw] This is an example page generated using hbs2.]
Just some text
]]
)

96
examples/site/foo.ss Normal file
View File

@ -0,0 +1,96 @@
; это наш "сайт" -- poo.ss
; просто какой-то левый json
[define source [json:file miscellaneous/fuzzy-parse/nix/pkgs.json]]
(define (foo-page bar)
[html :html [kw]
[html :head [kw]
[html :title [kw] Suckless HTML Page]
[html :meta [kw :charset UTF-8]]
[html :style [kw]
[css body [kw font-family sans-serif margin-left 20px max-width 1024px]]
[css table [kw border-collapse collapse width auto]]
[css (list td th) [kw border [sym (unwords 1px solid #ccc)]
padding 8px
text-align left]]
[css th [kw background-color #f2f2f2 white-space nowrap]]
[css .che [kw margin-right 8px]]
]
]
[html :body [kw]
[html :h1 [kw] Super Cool HBS2 Suckless Script Example Page]
[html :h2 [kw] Built with Suckless Script]
[html :p [kw] This is an example page generated using hbs2.]
[html :p [kw] [html :a [kw href [concat ../../tree/ bar]] Referes to bar ] ]
[html :form [kw action # method POST]
[html :label [kw for cb1]
[html :input [kw :type checkbox name checkbox1 :id cb1 :class che]]
I agree with the terms
]
[html :br]
[html :input [kw :type text :name username :placeholder "Enter your name"]]
[html :br]
[html :input [kw :type submit :value Submit]]
]
[html :br]
[html :p [kw]
This text contains
[html :b [kw] bold]
chr:comma
[html :i [kw] italic]
:and
[html :u [kw] :underlined]
styles.
]
[html :br]
; Unicode test section
[html :p [kw] Russian: Привет, мир!]
[html :p [kw] Chinese: 你好世界]
[html :p [kw] Korean: 안녕하세요, 세계!]
[html :br]
[html :table [kw]
[html :thead [kw]
[html :tr [kw]
[html :th [kw] Package]
[html :th [kw] Version]
]
]
[html :tbody [kw]
[map [fn 1 [html :tr [kw] [html :th [kw] [car _1]]
[html :td [kw] [nth 1 _1]] ] ] source]
]
]
[html :br]
[html :p [kw]
For more information, visit
[html :a [kw href http://example.com] our website]
"."
]
]
]
)

44
examples/site/site.ss Normal file
View File

@ -0,0 +1,44 @@
; [eval [cons :begin [top:file bar.ss]]]
(import bar.ss)
(import foo.ss)
(define site-root-ref :4X65y4YvUjRL2gtA9Ec3YDDP4bnxjTGhfjpoah96t3z1)
(define (as-html n) [kw :file-name n :mime-type "text/html; charset=utf-8"]) ; метаданные что бы hbs2-peer отображал как вебстраницу
(define bar.html (bar-page)) ; генерим страничку
(define bar.hash (hbs2:tree:metadata:string [as-html :bar.html] bar.html)) ; сохраняем как дерево с метаданными
(define foo.html (foo-page bar.hash))
(define foo.hash (hbs2:tree:metadata:string [as-html :foo.html] foo.html)) ; сохраняем как дерево с метаданными
(define grove [hbs2:grove:annotated [kw webroot foo.hash] [list foo.hash bar.hash]])
; println :bar.html space "hash:" space bar.hash
println Grove: space grove ; hello.hash
hbs2:lwwref:update site-root-ref grove
; newline
; print [hbs2:lwwref:get site-root-ref]
(define url [sym [join / http://localhost:5000/ref site-root-ref]]) ; вычисляем url
; newline
; print url
; print bar.html
; print foo.html
; print site-root-ref
(call:proc "firefox" url) ; вызываем фарфокс

30
fixme-new/LICENSE Normal file
View File

@ -0,0 +1,30 @@
Copyright (c) 2023,
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following
disclaimer in the documentation and/or other materials provided
with the distribution.
* Neither the name of nor the names of other
contributors may be used to endorse or promote products derived
from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

22
fixme-new/README.md Normal file
View File

@ -0,0 +1,22 @@
## The new glorious fixme
This is a new fixme implementation! It's a re-implementation
of fixme aiming for using multiple sources for issues, not
only git repo, and able to share the state via hbs2
privimites.
It will replace the old good fixme and will reuse all the
code from there that could be re-used.
It's indendent to be mostly compatible with the old
fixme, but we will see.
The binary is called fixme-new in order not to be confused
with old fixme, but it's only for a while.
It will be replaced as soon, as this fixme will be fully
operational.

View File

@ -0,0 +1,8 @@
module Main where
import Fixme.Run
main :: IO ()
main = do
runFixmeCLI runCLI

61
fixme-new/examples/config Normal file
View File

@ -0,0 +1,61 @@
; fixme-files **/*.hs docs/devlog.md
; no-debug
; debug
fixme-prefix FIXME:
fixme-prefix TODO:
fixme-prefix PR:
fixme-prefix REVIEW:
fixme-git-scan-filter-days 30
fixme-attribs assigned workflow type
fixme-attribs resolution cat scope
fixme-value-set workflow new backlog wip test fixed done
; fixme-value-set cat bug feat refactor
fixme-value-set scope mvp-0 mvp-1 backlog
fixme-files **/*.txt docs/devlog.md
fixme-files **/*.hs
fixme-file-comments "*.scm" ";"
fixme-comments ";" "--"
(define-template short
(quot
(simple
(trim 10 $fixme-key) " "
(if (~ FIXME $fixme-tag)
(then (fgd red (align 6 $fixme-tag)) )
(else (if (~ TODO $fixme-tag)
(then (fgd green (align 6 $fixme-tag)))
(else (align 6 $fixme-tag)) ) )
)
(align 10 ("[" $workflow "]")) " "
(align 8 $type) " "
(align 12 $assigned) " "
(align 20 (trim 20 $committer-name)) " "
(trim 50 ($fixme-title)) " "
(nl))
)
)
(set-template default short)
(define (ls) (report))
(define (ls:wip) (report workflow ~ wip))
(define (stage) (fixme:stage:show))

View File

@ -0,0 +1,6 @@
fixme-pager (quot (bat "--file-name" $file "-H" $before))
fixme-def-context 2 5

View File

@ -1,12 +1,11 @@
cabal-version: 3.0
name: hbs2-git
version: 0.24.1.1
-- synopsis:
name: fixme-new
version: 0.25.0.1
synopsis: reimplemented fixme
-- description:
license: BSD-3-Clause
license-file: LICENSE
author: Dmitry Zuikov
maintainer: dzuikov@gmail.com
-- copyright:
category: System
build-type: Simple
@ -56,10 +55,15 @@ common shared-properties
hbs2-core
, hbs2-peer
, hbs2-storage-simple
, hbs2-keyman
, hbs2-keyman-direct-lib
, hbs2-git3
, hbs2-cli
, db-pipe
, suckless-conf
, fuzzy-parse
, aeson
, aeson-pretty
, attoparsec
, atomic-write
, bytestring
@ -69,15 +73,20 @@ common shared-properties
, exceptions
, filepath
, filepattern
, generic-lens
, generic-deriving
, interpolatedstring-perl6
, memory
, microlens-platform
, mtl
, safe
, serialise
, scientific
, streaming
, stm
, split
, text
, temporary
, time
, timeit
, transformers
@ -91,80 +100,47 @@ common shared-properties
, random
, vector
, unix
, uuid
library
import: shared-properties
other-modules:
Fixme.Run.Internal
Fixme.Run.Internal.RefChan
exposed-modules:
HBS2.Git.Local
HBS2.Git.Local.CLI
HBS2.Git.Data.Tx
HBS2.Git.Data.GK
HBS2.Git.Data.RefLog
HBS2.Git.Data.LWWBlock
HBS2.Git.Client.Prelude
HBS2.Git.Client.App.Types
HBS2.Git.Client.App.Types.GitEnv
HBS2.Git.Client.App
HBS2.Git.Client.Config
HBS2.Git.Client.State
HBS2.Git.Client.RefLog
HBS2.Git.Client.Export
HBS2.Git.Client.Import
HBS2.Git.Client.Progress
Fixme
Fixme.Config
Fixme.Run
Fixme.Types
Fixme.Prelude
Fixme.State
Fixme.Scan
Fixme.Scan.Git.Local
Fixme.GK
build-depends: base
, base16-bytestring
, binary
, unix
hs-source-dirs: hbs2-git-client-lib
hs-source-dirs: lib
executable hbs2-git-subscribe
executable fixme-new
import: shared-properties
main-is: Main.hs
main-is: FixmeMain.hs
-- other-modules:
-- other-extensions:
build-depends:
base, hbs2-git
base, fixme-new, hbs2-core, hbs2-peer
, binary
, vector
, optparse-applicative
hs-source-dirs: git-hbs2-subscribe
default-language: GHC2021
executable git-hbs2
import: shared-properties
main-is: Main.hs
-- other-modules:
-- other-extensions:
build-depends:
base, hbs2-git
, binary
, vector
, optparse-applicative
hs-source-dirs: git-hbs2
default-language: GHC2021
executable git-remote-hbs2
import: shared-properties
main-is: Main.hs
-- other-modules:
-- other-extensions:
build-depends:
base, hbs2-git
, binary
, vector
, optparse-applicative
hs-source-dirs: git-remote-hbs2
hs-source-dirs: app
default-language: GHC2021

8
fixme-new/lib/Fixme.hs Normal file
View File

@ -0,0 +1,8 @@
module Fixme
( module Fixme.Types
, module Fixme.Prelude
) where
import Fixme.Prelude
import Fixme.Types

View File

@ -0,0 +1,41 @@
module Fixme.Config where
import Fixme.Prelude
import Fixme.Types
import HBS2.System.Dir
import System.Environment
import System.Directory (getXdgDirectory, XdgDirectory(..))
binName :: FixmePerks m => m FilePath
binName = pure "fixme-new" -- liftIO getProgName
localConfigDir :: (FixmePerks m, MonadReader FixmeEnv m) => m FilePath
localConfigDir = do
p <- asks fixmeEnvWorkDir >>= readTVarIO
b <- binName
pure (p </> ("." <> b))
fixmeWorkDir :: (FixmePerks m, MonadReader FixmeEnv m) => m FilePath
fixmeWorkDir = asks fixmeEnvWorkDir >>= readTVarIO
localConfig:: (FixmePerks m, MonadReader FixmeEnv m) => m FilePath
localConfig = localConfigDir <&> (</> "config")
userConfigs :: FixmePerks m => m [FilePath]
userConfigs= do
bin <- binName
h <- home
xdg <- liftIO (getXdgDirectory XdgConfig bin)
let conf1 = h </> ("." <> bin)
let conf2 = xdg </> "config"
pure [conf2, conf1]
localDBName :: FilePath
localDBName = "state.db"
localDBPath :: (FixmePerks m, MonadReader FixmeEnv m) => m FilePath
localDBPath = localConfigDir <&> (</> localDBName)

96
fixme-new/lib/Fixme/GK.hs Normal file
View File

@ -0,0 +1,96 @@
{-# Language MultiWayIf #-}
module Fixme.GK where
import Fixme.Prelude
import Fixme.Config
import Fixme.Types
import HBS2.OrDie
-- import HBS2.System.Dir
import HBS2.Storage.Operations.ByteString
import HBS2.Storage.Operations.Class
import HBS2.Net.Auth.GroupKeySymm
import HBS2.Peer.Proto.RefChan as RefChan
import HBS2.System.Dir
-- import HBS2.Net.Auth.Credentials
import Control.Monad.Trans.Maybe
import Data.HashSet qualified as HS
import Data.HashMap.Strict qualified as HM
import Data.Maybe
import Lens.Micro.Platform
data GroupKeyOpError =
NoRefChanHead
| NoReadersSet
| GKLoadFailed
deriving (Eq,Ord,Show,Typeable)
instance Exception GroupKeyOpError
groupKeyFile :: forall m . (FixmePerks m, MonadReader FixmeEnv m) => m FilePath
groupKeyFile = do
dir <- localConfigDir
pure $ dir </> "gk0"
-- TODO: rotate-group-key
loadGroupKey :: forall s m . (s ~ 'HBS2Basic, FixmePerks m) => FixmeM m (Maybe (HashRef, GroupKey 'Symm s))
loadGroupKey = do
sto <- getStorage
gkF <- groupKeyFile
runMaybeT do
rchan <- lift (asks fixmeEnvRefChan >>= readTVarIO) >>= toMPlus
rch <- getRefChanHead @L4Proto sto (RefChanHeadKey rchan)
>>= orThrow NoRefChanHead
guard ( not $ HS.null (view refChanHeadReaders rch) )
flip fix 0 $ \next -> \case
attempt | attempt > 2 -> throwIO GKLoadFailed
attempt -> do
let readers = view refChanHeadReaders rch
gkHash <- liftIO (try @_ @IOError $ readFile gkF)
<&> either (const Nothing) ( (=<<) (fromStringMay @HashRef) . headMay . lines )
debug $ "GK0" <+> pretty gkHash
case gkHash of
Nothing -> do
debug "generate new group key"
gknew <- generateGroupKey @'HBS2Basic Nothing (HS.toList readers)
ha <- writeAsMerkle sto (serialise gknew)
liftIO $ writeFile gkF (show $ pretty ha)
next (succ attempt)
Just h -> do
now <- liftIO $ getPOSIXTime <&> round
gk' <- loadGroupKeyMaybe @s sto h
(_,gk) <- maybe1 gk' (rm gkF >> next (succ attempt)) (pure . (h,))
let ts = getGroupKeyTimestamp gk & fromMaybe 0
-- FIXME: timeout-hardcode
-- $class: hardcode
if | now - ts > 2592000 -> do
rm gkF
next (succ attempt)
| HM.keysSet (recipients gk) /= readers -> do
rm gkF
next (succ attempt)
| otherwise -> do
pure (h,gk)

View File

@ -0,0 +1,24 @@
module Fixme.Prelude
( module All
, GitHash(..)
, GitRef(..)
, Serialise(..)
, serialise, deserialiseOrFail, deserialise
, module Exported
) where
import HBS2.Prelude.Plated as All
import HBS2.Hash as All
import HBS2.Data.Types.Refs as All
import HBS2.Misc.PrettyStuff as All
import HBS2.System.Logger.Simple.ANSI as All
import HBS2.Git.Local (GitHash(..),GitRef(..))
import Codec.Serialise (Serialise(..),serialise,deserialise,deserialiseOrFail)
import Data.Functor as All
import Data.Function as All
import UnliftIO as All
import System.FilePattern as All
import Control.Monad.Reader as All
import Data.Config.Suckless.Script as Exported

592
fixme-new/lib/Fixme/Run.hs Normal file
View File

@ -0,0 +1,592 @@
module Fixme.Run where
import Prelude hiding (init)
import Fixme.Prelude hiding (indent)
import Fixme.Types
import Fixme.Config
import Fixme.State
import Fixme.Run.Internal
import Fixme.Run.Internal.RefChan
import Fixme.Scan.Git.Local as Git
import Fixme.Scan as Scan
import Fixme.GK as GK
import Data.Config.Suckless.Script.File
import HBS2.KeyMan.Keys.Direct
import HBS2.Git.Local.CLI
import HBS2.Peer.Proto.RefChan.Types
import HBS2.CLI.Run.KeyMan (keymanNewCredentials)
import HBS2.OrDie
import HBS2.Peer.CLI.Detect
import HBS2.Net.Auth.GroupKeySymm
import HBS2.Data.Types.SignedBox
import HBS2.Base58
import HBS2.Storage.Operations.ByteString
import HBS2.Net.Auth.Credentials
import HBS2.Merkle
import HBS2.Data.Types.Refs
import HBS2.Storage
import HBS2.Storage.Compact
import HBS2.System.Dir
import DBPipe.SQLite hiding (field)
import Data.Config.Suckless
import Data.Aeson.Encode.Pretty as Aeson
import Data.ByteString (ByteString)
import Data.ByteString.Lazy qualified as LBS
import Data.ByteString.Lazy.Char8 qualified as LBS8
import Data.Either
import Data.Maybe
import Data.HashSet qualified as HS
import Data.HashMap.Strict (HashMap)
import Data.HashMap.Strict qualified as HM
import Data.HashSet (HashSet)
import Data.Set qualified as Set
import Data.Generics.Product.Fields (field)
import Data.List qualified as List
import Data.Text qualified as Text
import Data.Text.IO qualified as Text
import Text.InterpolatedString.Perl6 (qc)
import Data.Coerce
import Control.Monad.Identity
import Lens.Micro.Platform
import System.Environment
import System.Process.Typed
import Control.Monad
import Control.Monad.Trans.Cont
import Control.Monad.Trans.Maybe
import System.IO.Temp qualified as Temp
import System.IO qualified as IO
import Streaming.Prelude qualified as S
{- HLINT ignore "Functor law" -}
recover :: (FixmePerks m) => FixmeEnv -> m a -> m a
recover env m = flip fix 0 $ \next attempt
-> do m
`catch` (\PeerNotConnected -> do
if attempt < 1 then do
runWithRPC env $ next (succ attempt)
else do
throwIO PeerNotConnected
)
withFixmeCLI :: (FixmePerks m, MonadReader FixmeEnv m) => FixmeEnv -> FixmeM m a -> m a
withFixmeCLI env m = do
recover env do
withFixmeEnv env m
runWithRPC :: (FixmePerks m) => FixmeEnv -> m a -> m a
runWithRPC FixmeEnv{..} m = do
soname <- detectRPC
`orDie` "can't locate hbs2-peer rpc"
flip runContT pure do
client <- lift $ race (pause @'Seconds 1) (newMessagingUnix False 1.0 soname)
>>= orThrowUser ("can't connect to" <+> pretty soname)
void $ ContT $ withAsync $ runMessagingUnix client
peerAPI <- makeServiceCaller @PeerAPI (fromString soname)
refChanAPI <- makeServiceCaller @RefChanAPI (fromString soname)
storageAPI <- makeServiceCaller @StorageAPI (fromString soname)
let endpoints = [ Endpoint @UNIX peerAPI
, Endpoint @UNIX refChanAPI
, Endpoint @UNIX storageAPI
]
void $ ContT $ withAsync $ liftIO $ runReaderT (runServiceClientMulti endpoints) client
let newEnv = Just (MyPeerClientEndpoints soname peerAPI refChanAPI storageAPI)
liftIO $ atomically $ writeTVar fixmeEnvMyEndpoints newEnv
lift m
runFixmeCLI :: forall a m . FixmePerks m => FixmeM m a -> m a
runFixmeCLI m = do
git <- findGitDir
env <- FixmeEnv
<$> newMVar ()
<*> newTVarIO mempty
<*> (pwd >>= newTVarIO)
<*> newTVarIO Nothing
<*> newTVarIO git
<*> newTVarIO mempty
<*> newTVarIO mempty
<*> newTVarIO mempty
<*> newTVarIO builtinAttribs
<*> newTVarIO builtinAttribVals
<*> newTVarIO mempty
<*> newTVarIO defCommentMap
<*> newTVarIO Nothing
<*> newTVarIO mzero
<*> newTVarIO mempty
<*> newTVarIO mempty
<*> newTVarIO defaultCatAction
<*> newTVarIO defaultTemplate
<*> newTVarIO mempty
<*> newTVarIO (1,3)
<*> newTVarIO mzero
<*> newTVarIO mzero
<*> newTVarIO mzero
<*> newTVarIO mzero
<*> newTVarIO mempty
-- FIXME: defer-evolve
-- не все действия требуют БД,
-- хорошо бы, что бы она не создавалась,
-- если не требуется
recover env do
runReaderT ( setupLogger >> fromFixmeM (handle @_ @SomeException (err . viaShow) evolve >> m) ) env
`finally` flushLoggers
where
setupLogger = do
setLogging @ERROR $ toStderr . logPrefix "[error] "
setLogging @WARN $ toStderr . logPrefix "[warn] "
setLogging @NOTICE $ toStdout . logPrefix ""
pure ()
flushLoggers = do
silence
-- FIXME: tied-fucking-context
defaultCatAction = CatAction $ \dict lbs -> do
LBS.putStr lbs
pure ()
silence :: FixmePerks m => m ()
silence = do
setLoggingOff @DEBUG
setLoggingOff @ERROR
setLoggingOff @WARN
setLoggingOff @NOTICE
setLoggingOff @TRACE
readConfig :: (FixmePerks m) => FixmeM m [Syntax C]
readConfig = do
user <- userConfigs
lo <- localConfig
w <- for (lo : user) $ \conf -> do
try @_ @IOException (liftIO $ readFile conf)
<&> fromRight mempty
<&> parseTop
>>= either (error.show) pure
updateScanMagic
pure $ mconcat w
runCLI :: FixmePerks m => FixmeM m ()
runCLI = do
argz <- liftIO getArgs
forms <- parseTop (unlines $ unwords <$> splitForms argz)
& either (error.show) pure
runTop forms
runTop :: forall m . FixmePerks m => [Syntax C] -> FixmeM m ()
runTop forms = do
tvd <- newTVarIO mempty
let dict = makeDict @C do
internalEntries
entry $ bindMatch "--help" $ nil_ \case
HelpEntryBound what -> helpEntry what
[StringLike s] -> helpList False (Just s)
_ -> helpList False Nothing
entry $ bindMatch "fixme-prefix" $ nil_ \case
[StringLike pref] -> do
t <- lift $ asks fixmeEnvTags
atomically (modifyTVar t (HS.insert (FixmeTag $ fromString pref)))
_ -> throwIO $ BadFormException @C nil
entry $ bindMatch "fixme-attribs" $ nil_ \case
StringLikeList xs -> do
ta <- lift $ asks fixmeEnvAttribs
atomically $ modifyTVar ta (<> HS.fromList (fmap fromString xs))
_ -> throwIO $ BadFormException @C nil
entry $ bindMatch "fixme-files" $ nil_ \case
StringLikeList xs -> do
w <- lift fixmeWorkDir
t <- lift $ asks fixmeEnvFileMask
atomically (modifyTVar t (<> fmap (w </>) xs))
_ -> throwIO $ BadFormException @C nil
entry $ bindMatch "fixme-exclude" $ nil_ \case
StringLikeList xs -> do
w <- lift fixmeWorkDir
t <- lift $ asks fixmeEnvFileExclude
atomically (modifyTVar t (<> fmap (w </>) xs))
_ -> throwIO $ BadFormException @C nil
entry $ bindMatch "fixme-file-comments" $ nil_ $ \case
[StringLike ft, StringLike b] -> do
let co = Text.pack b & HS.singleton
t <- lift $ asks fixmeEnvFileComments
atomically (modifyTVar t (HM.insertWith (<>) (commentKey ft) co))
_ -> throwIO $ BadFormException @C nil
entry $ bindMatch "fixme-comments" $ nil_ \case
(StringLikeList xs) -> do
t <- lift $ asks fixmeEnvDefComments
let co = fmap Text.pack xs & HS.fromList
atomically $ modifyTVar t (<> co)
_ -> throwIO $ BadFormException @C nil
entry $ bindMatch "fixme-value-set" $ nil_ \case
(StringLike n : StringLikeList xs) -> do
t <- lift $ asks fixmeEnvAttribValues
let name = fromString n
let vals = fmap fromString xs & HS.fromList
atomically $ modifyTVar t (HM.insertWith (<>) name vals)
_ -> throwIO $ BadFormException @C nil
entry $ bindMatch "fixme-pager" $ nil_ \case
[ListVal cmd0] -> do
t <- lift $ asks fixmeEnvCatAction
let action = CatAction $ \dict lbs -> do
let ccmd = case inject dict cmd0 of
(StringLike p : StringLikeList xs) -> Just (p, xs)
_ -> Nothing
debug $ pretty ccmd
maybe1 ccmd none $ \(p, args) -> do
let input = byteStringInput lbs
let cmd = setStdin input $ setStderr closed
$ proc p args
void $ runProcess cmd
atomically $ writeTVar t action
_ -> throwIO $ BadFormException @C nil
entry $ bindMatch "fixme-def-context" $ nil_ \case
[LitIntVal a, LitIntVal b] -> do
t <- lift $ asks fixmeEnvCatContext
atomically $ writeTVar t (fromIntegral a, fromIntegral b)
_ -> throwIO $ BadFormException @C nil
entry $ bindMatch "modify" $ nil_ \case
[ FixmeHashLike w, StringLike k, StringLike v ] -> lift do
void $ runMaybeT do
key <- lift (selectFixmeKey w) >>= toMPlus
lift $ modifyFixme key [(fromString k, fromString v)]
_ -> throwIO $ BadFormException @C nil
entry $ bindMatch "delete" $ nil_ \case
[ FixmeHashLike w ] -> lift do
void $ runMaybeT do
key <- lift (selectFixmeKey w) >>= toMPlus
lift $ modifyFixme key [("deleted", "true")]
_ -> throwIO $ BadFormException @C nil
entry $ bindMatch "cat" $ nil_ $ \case
[ FixmeHashLike w ] -> lift do
cat_ w
_ -> throwIO $ BadFormException @C nil
entry $ bindMatch "create" $ nil_ $ \syn -> do
me' <- lookupValue "me"
me <- case me' of
StringLike who -> pure who
_ -> do
user <- liftIO $ lookupEnv "USER" <&> fromMaybe "stranger"
try @_ @SomeException (readProcess (shell [qc|git config user.name|]))
<&> either (const user) (headDef user . lines . LBS8.unpack . view _2)
let title = case syn of
StringLikeList xs -> unwords xs
_ -> "new-issue"
lift $ edit_ (Left (me,title))
entry $ bindMatch "edit" $ nil_ $ \case
[ FixmeHashLike w] -> lift $ void $ runMaybeT do
key <- lift (selectFixmeKey w) >>= toMPlus
fme <- lift (getFixme key) >>= toMPlus
lift $ edit_ (Right fme)
_ -> throwIO $ BadFormException @C nil
entry $ bindMatch "dump" $ nil_ $ \case
[ FixmeHashLike w ] -> lift $ void $ runMaybeT do
key <- lift (selectFixmeKey w) >>= toMPlus
fme <- lift $ getFixme key
liftIO $ print $ pretty fme
_ -> throwIO $ BadFormException @C nil
-- magic <- lift $ asks fixmeEnvScanMagic >>= readTVarIO
-- liftIO $ print $ pretty magic
entry $ bindMatch "report" $ nil_ $ lift . \case
( SymbolVal "template" : StringLike t : p ) -> do
report (Just t) p
( SymbolVal "--template" : StringLike t : p ) -> do
report (Just t) p
p -> do
report Nothing p
entry $ bindMatch "fixme:key:show" $ nil_ \case
[ FixmeHashLike w ] -> lift $ void $ runMaybeT do
key <- lift (selectFixmeKey w) >>= toMPlus
liftIO $ print $ pretty key
_ -> throwIO $ BadFormException @C nil
entry $ bindMatch "fixme:scan-magic" $ nil_ $ const do
magic <- lift $ asks fixmeEnvScanMagic >>= readTVarIO
liftIO $ print $ pretty magic
entry $ bindMatch "fixme:gk:show" $ nil_ $ const do
w <- lift loadGroupKey
case w of
Just (h,_) -> do
liftIO $ print $ pretty h
_ -> do
liftIO $ print $ pretty "none"
entry $ bindMatch "fixme:path" $ nil_ $ const do
path <- lift fixmeWorkDir
liftIO $ print $ pretty path
entry $ bindMatch "fixme:files" $ nil_ $ const do
w <- lift fixmeWorkDir
incl <- lift (asks fixmeEnvFileMask >>= readTVarIO)
excl <- lift (asks fixmeEnvFileExclude >>= readTVarIO)
glob incl excl w $ \fn -> do
liftIO $ putStrLn (makeRelative w fn)
pure True
entry $ bindMatch "fixme:state:drop" $ nil_ $ const $ lift do
cleanupDatabase
entry $ bindMatch "fixme:state:cleanup" $ nil_ $ const $ lift do
cleanupDatabase
entry $ bindMatch "fixme:state:count-by-attribute" $ nil_ $ \case
[StringLike s] -> lift do
rs <- countByAttribute (fromString s)
for_ rs $ \(n,v) -> do
liftIO $ print $ pretty n <+> pretty v
_ -> throwIO $ BadFormException @C nil
entry $ bindMatch "fixme:git:import" $ nil_ $ const $ lift do
import_
entry $ bindMatch "fixme:git:list" $ nil_ $ const do
fxs <- lift scanFiles
for_ fxs $ \fme -> do
liftIO $ print $ pretty fme
-- TODO: some-uncommited-shit
-- TODO: some-shit
-- one
-- TODO: some-shit
-- new text
entry $ bindMatch "env:show" $ nil_ $ const $ do
lift printEnv
entry $ bindMatch "refchan:show" $ nil_ $ const do
tref <- lift $ asks fixmeEnvRefChan
r <- readTVarIO tref
liftIO $ print $ pretty (fmap AsBase58 r)
entry $ bindMatch "refchan" $ nil_ \case
[SignPubKeyLike rchan] -> do
tref<- lift $ asks fixmeEnvRefChan
atomically $ writeTVar tref (Just rchan)
_ -> throwIO $ BadFormException @C nil
entry $ bindMatch "author" $ nil_ \case
[SignPubKeyLike au] -> do
t <- lift $ asks fixmeEnvAuthor
atomically $ writeTVar t (Just au)
_ -> throwIO $ BadFormException @C nil
entry $ bindMatch "reader" $ nil_ \case
[EncryptPubKeyLike reader] -> do
t <- lift $ asks fixmeEnvReader
atomically $ writeTVar t (Just reader)
_ -> throwIO $ BadFormException @C nil
entry $ bindMatch "git:commits" $ const $ do
co <- lift listCommits <&> fmap (mkStr @C . view _1)
pure $ mkList co
entry $ bindMatch "fixme:refchan:export" $ nil_ $ \case
[SymbolVal "dry"] -> do
notice $ yellow "export is running in dry mode"
void $ lift $ refchanExport [RefChanExportDry]
_ -> void $ lift $ refchanExport ()
entry $ bindMatch "fixme:refchan:import" $ nil_ $ \case
_ -> void $ lift $ refchanImport
entry $ bindMatch "fixme:gk:export" $ nil_ $ \case
_ -> void $ lift $ refchanExportGroupKeys
entry $ bindMatch "source" $ nil_ $ \case
[StringLike path] -> do
ppath <- if List.isPrefixOf "." path then do
dir <- lift localConfigDir
let rest = tail $ splitDirectories path
pure $ joinPath (dir:rest)
else do
canonicalizePath path
debug $ red "SOURCE FILE" <+> pretty ppath
-- FIXME: raise-warning?
content <- liftIO $ try @_ @IOException (readFile ppath)
<&> fromRight mempty
<&> parseTop
>>= either (error.show) pure
lift $ runEval tvd content
_ -> throwIO $ BadFormException @C nil
entry $ bindMatch "update" $ nil_ $ const $ lift do
refchanUpdate
entry $ bindMatch "update" $ nil_ $ const $ lift do
refchanUpdate
entry $ bindMatch "fixme:refchan:update" $ nil_ $ const $ lift do
refchanUpdate
entry $ bindMatch "cache:ignore" $ nil_ $ const $ lift do
tf <- asks fixmeEnvFlags
atomically $ modifyTVar tf (HS.insert FixmeIgnoreCached)
entry $ bindMatch "git:blobs" $ \_ -> do
blobs <- lift (listBlobs Nothing)
elems <- for blobs $ \(f,h) -> do
pure $ mkList @C [ mkStr f, mkSym ".", mkStr h ]
pure $ mkList @C elems
entry $ bindMatch "init" $ nil_ $ const $ do
lift init
brief "initializes a new refchan" $
desc ( vcat [
"Refchan is an ACL-controlled CRDT channel useful for syncronizing"
, "fixme-new state amongst the different remote setups/peers/directories"
, "use it if you want to use fixme-new in a distributed fashion"
]
) $
args [] $
returns "string" "refchan-key" $ do
entry $ bindMatch "fixme:refchan:init" $ nil_ $ \case
[] -> lift $ fixmeRefChanInit Nothing
[SignPubKeyLike rc] -> lift $ fixmeRefChanInit (Just rc)
_ -> throwIO $ BadFormException @C nil
entry $ bindMatch "set-template" $ nil_ \case
[SymbolVal who, SymbolVal w] -> do
templates <- lift $ asks fixmeEnvTemplates
t <- readTVarIO templates
for_ (HM.lookup w t) $ \tpl -> do
atomically $ modifyTVar templates (HM.insert who tpl)
_ -> throwIO $ BadFormException @C nil
entry $ bindMatch "define-template" $ nil_ $ \case
[SymbolVal who, IsSimpleTemplate body ] -> do
t <- lift $ asks fixmeEnvTemplates
atomically $ modifyTVar t (HM.insert who (Simple (SimpleTemplate body)))
_ -> throwIO $ BadFormException @C nil
entry $ bindMatch "log:trace:on" $ nil_ $ const do
lift $ setLogging @TRACE $ toStderr . logPrefix ""
entry $ bindMatch "log:trace:off" $ nil_ $ const do
lift $ setLoggingOff @TRACE
entry $ bindMatch "log:debug:on" $ nil_ $ const do
lift $ setLogging @DEBUG $ toStderr . logPrefix ""
entry $ bindMatch "log:debug:off" $ nil_ $ const do
lift $ setLoggingOff @DEBUG
entry $ bindMatch "debug:peer:check" $ nil_ $ const do
peer <- lift $ getClientAPI @PeerAPI @UNIX
poked <- callRpcWaitMay @RpcPoke (TimeoutSec 1) peer ()
<&> fromMaybe "hbs2-peer not connected"
liftIO $ putStrLn poked
argz <- liftIO getArgs
conf <- readConfig
let args = zipWith (\i s -> bindValue (mkId ("$_" <> show i)) (mkStr @C s )) [1..] argz
& HM.unions
let finalDict = dict <> args -- :: Dict C (FixmeM m)
atomically $ writeTVar tvd finalDict
runEval tvd (conf <> forms) >>= eatNil display

View File

@ -0,0 +1,960 @@
{-# Language PatternSynonyms #-}
{-# Language ViewPatterns #-}
module Fixme.Run.Internal where
import Prelude hiding (init)
import Fixme.Prelude hiding (indent)
import Fixme.Types
import Fixme.Config
import Fixme.State
import Fixme.Scan.Git.Local as Git
import Fixme.Scan as Scan
import Fixme.GK
import HBS2.Git.Local.CLI
import HBS2.CLI.Run.MetaData (createTreeWithMetadata,getTreeContents,getGroupKeyHash)
import HBS2.Merkle.MetaData
import HBS2.OrDie
import HBS2.Base58
import HBS2.Net.Auth.GroupKeySymm
import HBS2.Data.Types.SignedBox
import HBS2.Peer.Proto.RefChan as RefChan
import HBS2.Peer.RPC.Client.RefChan
import HBS2.Storage.Operations.ByteString
import HBS2.System.Dir
import HBS2.Net.Auth.Credentials
import DBPipe.SQLite hiding (field)
import HBS2.CLI.Run.KeyMan (keymanNewCredentials)
import HBS2.KeyMan.Keys.Direct
import Data.Config.Suckless.Script.File
import Data.List qualified as L
import Data.List.Split (chunksOf)
import Data.Aeson.Encode.Pretty as Aeson
import Data.ByteString.Lazy qualified as LBS
import Data.ByteString.Lazy.Char8 qualified as LBS8
import Data.ByteString qualified as BS
import Data.Either
import Data.Map qualified as Map
import Data.Set qualified as Set
import Data.Maybe
import Data.Generics.Product.Fields (field)
import Data.HashSet qualified as HS
import Data.HashMap.Strict (HashMap)
import Data.HashMap.Strict qualified as HM
import Data.Text qualified as Text
import Data.Text.Encoding (encodeUtf8)
import Text.InterpolatedString.Perl6 (qc)
import Data.Coerce
import Data.Word
import Data.UUID.V4 qualified as UUID
import Lens.Micro.Platform
import System.Process.Typed
import Control.Monad.Trans.Cont
import Control.Monad.Trans.Maybe
import Control.Monad.Except
import Control.Concurrent.STM (flushTQueue)
import Data.Time.Clock.POSIX (utcTimeToPOSIXSeconds)
import System.Directory (getModificationTime)
import System.IO as IO
import System.Environment (lookupEnv)
import System.IO.Temp qualified as Temp
import Streaming.Prelude qualified as S
pattern IsSimpleTemplate :: forall {c} . [Syntax c] -> Syntax c
pattern IsSimpleTemplate xs <- ListVal (SymbolVal "simple" : xs)
{- HLINT ignore "Functor law" -}
defaultTemplate :: HashMap Id FixmeTemplate
defaultTemplate = HM.fromList [ ("default", Simple (SimpleTemplate short)) ]
where
short = parseTop s & fromRight mempty
s = [qc|
(trim 10 $fixme-key) " "
(align 6 $fixme-tag) " "
(trim 50 ($fixme-title))
(nl)
|]
templateExample :: String
templateExample = [qc|
; this is an optional template example
; for nicer fixme list
;(define-template short
; (quot
; (simple
; (trim 10 $fixme-key) " "
;
; (if (~ FIXME $fixme-tag)
; (then (fgd red (align 6 $fixme-tag)) )
; (else (if (~ TODO $fixme-tag)
; (then (fgd green (align 6 $fixme-tag)))
; (else (align 6 $fixme-tag)) ) )
; )
;
;
; (align 10 ("[" $workflow "]")) " "
; (align 8 $class) " "
; (align 12 $assigned) " "
; (align 20 (trim 20 $committer-name)) " "
; (trim 50 ($fixme-title)) " "
; (nl))
; )
;)
; (set-template default short)
|]
init :: FixmePerks m => FixmeM m ()
init = do
lo <- localConfigDir
let lo0 = takeFileName lo
mkdir lo
touch (lo </> "config")
let gitignore = lo </> ".gitignore"
here <- doesPathExist gitignore
confPath <- localConfig
unless here do
liftIO $ appendFile confPath $ show $ vcat
[ mempty
, ";; this is a default fixme config"
, ";;"
, "fixme-prefix" <+> "FIXME:"
, "fixme-prefix" <+> "TODO:"
, "fixme-value-set" <+> hsep [":workflow", ":new",":wip",":backlog",":done"]
, "fixme-file-comments" <+> dquotes "*.scm" <+> dquotes ";"
, "fixme-comments" <+> dquotes ";" <+> dquotes "--" <+> dquotes "#"
, mempty
]
exts <- listBlobs Nothing
<&> fmap (takeExtension . fst)
<&> HS.toList . HS.fromList
for_ exts $ \e -> do
unless (e `elem` [".gitignore",".local"] ) do
liftIO $ appendFile confPath $
show $ ( "fixme-files" <+> dquotes ("**/*" <> pretty e) <> line )
liftIO $ appendFile confPath $ show $ vcat
[ "fixme-exclude" <+> dquotes "**/.**"
]
liftIO $ appendFile confPath $ show $ vcat
[ mempty
, pretty templateExample
, ";; uncomment to source any other local settings file"
, ";; source ./my.local"
, mempty
]
unless here do
liftIO $ writeFile gitignore $ show $
vcat [ pretty ("." </> localDBName)
]
notice $ green "default config created:" <+> ".fixme-new/config" <> line
<> "edit it for your project" <> line
<> "typically you need to add it to git"
<> line
<> "use (source ./some.local) form to add your personal settings" <> line
<> "which should not be shared amongst the whole project" <> line
<> "and add " <> yellow ".fixme-new/some.local" <+> "file to .gitignore"
<> line
notice $ "run" <> line <> vcat [
mempty
, "git add" <+> pretty (lo0 </> ".gitignore")
, "git add" <+> pretty (lo0 </> "config")
, mempty
]
printEnv :: FixmePerks m => FixmeM m ()
printEnv = do
g <- asks fixmeEnvGitDir >>= readTVarIO
masks <- asks fixmeEnvFileMask >>= readTVarIO
excl <- asks fixmeEnvFileExclude >>= readTVarIO
tags <- asks fixmeEnvTags >>= readTVarIO
days <- asks fixmeEnvGitScanDays >>= readTVarIO
comments1 <- asks fixmeEnvDefComments >>= readTVarIO <&> HS.toList
comments2 <- asks fixmeEnvFileComments >>= readTVarIO
<&> HM.toList
<&> fmap (over _2 HS.toList)
attr <- asks fixmeEnvAttribs >>= readTVarIO <&> HS.toList
vals <- asks fixmeEnvAttribValues >>= readTVarIO <&> HM.toList
dir <- asks fixmeEnvWorkDir >>= readTVarIO
liftIO $ print $ "; workdir" <+> pretty dir
for_ tags $ \m -> do
liftIO $ print $ "fixme-prefix" <+> pretty m
for_ masks $ \m -> do
liftIO $ print $ "fixme-files" <+> dquotes (pretty m)
for_ excl $ \m -> do
liftIO $ print $ "fixme-exclude" <+> dquotes (pretty m)
for_ days $ \d -> do
liftIO $ print $ "fixme-git-scan-filter-days" <+> pretty d
for_ comments1 $ \d -> do
liftIO $ print $ "fixme-comments" <+> dquotes (pretty d)
for_ comments2 $ \(ft, comm') -> do
for_ comm' $ \comm -> do
liftIO $ print $ "fixme-file-comments"
<+> dquotes (pretty ft) <+> dquotes (pretty comm)
for_ attr $ \a -> do
liftIO $ print $ "fixme-attribs"
<+> pretty a
for_ vals$ \(v, vs) -> do
liftIO $ print $ "fixme-value-set" <+> pretty v <+> hsep (fmap pretty (HS.toList vs))
for_ g $ \git -> do
liftIO $ print $ "fixme-git-dir" <+> dquotes (pretty git)
dbPath <- localDBPath
liftIO $ print $ "; fixme-state-path" <+> dquotes (pretty dbPath)
(before,after) <- asks fixmeEnvCatContext >>= readTVarIO
liftIO $ print $ "fixme-def-context" <+> pretty before <+> pretty after
ma <- asks fixmeEnvMacro >>= readTVarIO <&> HM.toList
for_ ma $ \(n, syn) -> do
liftIO $ print $ parens ("define-macro" <+> pretty n <+> pretty syn)
rchan <- asks fixmeEnvRefChan >>= readTVarIO
liftIO $ print $ ("refchan" <+> pretty (AsBase58 <$> rchan))
author <- asks fixmeEnvAuthor >>= readTVarIO
liftIO $ print $ ("author" <+> pretty (AsBase58 <$> author))
reader <- asks fixmeEnvReader >>= readTVarIO
liftIO $ print $ ("reader" <+> pretty (AsBase58 <$> reader))
scanOneFile :: FixmePerks m => FilePath -> FixmeM m [Fixme]
scanOneFile fn = do
lbs <- liftIO $ LBS.readFile fn
scanBlob (Just fn) lbs
scanFiles :: FixmePerks m => FixmeM m [Fixme]
scanFiles = do
w <- fixmeWorkDir
incl <- asks fixmeEnvFileMask >>= readTVarIO
excl <- asks fixmeEnvFileExclude >>= readTVarIO
keys <- newTVarIO (mempty :: HashMap Text Integer)
S.toList_ do
glob incl excl w $ \fn -> do
ts <- liftIO $ getModificationTime fn <&> round . utcTimeToPOSIXSeconds
let fnShort = makeRelative w fn
lbs <- liftIO (try @_ @IOException $ LBS.readFile fn)
<&> fromRight mempty
fxs0 <- lift $ scanBlob (Just fn) lbs
for_ fxs0 $ \fme -> do
let key = fromString (fnShort <> "#") <> coerce (fixmeTitle fme) <> ":" :: Text
atomically $ modifyTVar keys (HM.insertWith (+) key 1)
no <- readTVarIO keys <&> HM.lookup key <&> fromMaybe 0
let keyText = key <> fromString (show no)
let keyHash = FixmeKey $ fromString $ show $ pretty $ hashObject @HbSync (serialise keyText)
let f2 = mempty { fixmeTs = Just (fromIntegral ts)
, fixmeKey = keyHash
, fixmeAttr = HM.fromList
[ ( "fixme-key-string", FixmeAttrVal keyText)
, ( "file", FixmeAttrVal (fromString fnShort))
]
, fixmePlain = fixmePlain fme
}
let fmeNew = (fme <> f2) & fixmeDerivedFields
S.yield fmeNew
pure True
report :: (FixmePerks m, HasPredicate q, HasItemOrder q) => Maybe FilePath -> q -> FixmeM m ()
report t q = do
tpl <- asks fixmeEnvTemplates >>= readTVarIO
<&> HM.lookup (maybe "default" fromString t)
fxs <- listFixme (WithLimit Nothing q)
case tpl of
Nothing ->
liftIO $ LBS.putStr $ Aeson.encodePretty (fmap fixmeAttr fxs)
Just (Simple (SimpleTemplate simple)) -> do
for_ fxs $ \(Fixme{..}) -> do
let subst = [ (mkId k, mkStr @C v) | (k,v) <- HM.toList fixmeAttr ]
let what = render (SimpleTemplate (inject subst simple))
& fromRight "render error"
liftIO $ hPutDoc stdout what
edit_ :: FixmePerks m
=> Either (String,String) Fixme
-> FixmeM m ()
edit_ what = do
now <- liftIO $ getPOSIXTime <&> round
editor <- liftIO $ lookupEnv "EDITOR" >>= orThrowUser "EDITOR not set"
let txt = case what of
Right fx0 -> do
let fxm = fx0 & set (field @"fixmeAttr") mempty
& set (field @"fixmeStart") mzero
& set (field @"fixmeEnd") mzero
show $ pretty fxm
Left (me,title) -> [qc|TODO: {title}
$commit-time: {pretty now}
$committer-name: {pretty me}
Issue text...
|]
let setKey k fx = case what of
Right w -> fx & set (field @"fixmeKey") (fixmeKey w)
Left{} -> fx & set (field @"fixmeKey") (fromString p)
where
p = show $ pretty
$ hashObject @HbSync
$ fromString @LBS8.ByteString
$ show k
flip runContT pure $ callCC \exit -> do
fname <- liftIO $ Temp.writeTempFile "." "fixme-issue" txt
ContT $ bracket none (const $ rm fname)
h1 <- liftIO (BS.readFile fname)
<&> hashObject @HbSync
debug $ "hash1" <+> pretty h1
void $ runProcess $ shell [qc|{editor} {fname}|]
s <- liftIO $ BS.readFile fname <&> LBS.fromStrict
let h2 = hashObject @HbSync s
fxs <- lift $ scanBlobOpts NoIndents Nothing s
debug $ "hash before/after" <+> pretty h1 <+> pretty h2
when (h1 == h2) $ exit ()
lift $ withState $ transactional do
for fxs $ \f -> do
key <- liftIO $ UUID.nextRandom <&> show
let norm = f & set (field @"fixmeStart") mzero
& set (field @"fixmeEnd") mzero
& setKey key
& set (field @"fixmeTs") (Just $ fromIntegral now)
& fixmeDerivedFields
notice $ "fixme" <+> pretty (fixmeKey norm)
insertFixme norm
import_ :: FixmePerks m => FixmeM m ()
import_ = do
fxs0 <- scanFiles
fxs <- flip filterM fxs0 $ \fme -> do
let fn = fixmeGet "file" fme <&> Text.unpack . coerce
seen <- maybe1 fn (pure False) selectIsAlreadyScannedFile
pure (not seen)
hashes <- catMaybes <$> flip runContT pure do
p <- ContT $ bracket startGitHash stopProcess
let files = mapMaybe (fixmeGet "file") fxs
& HS.fromList
& HS.toList
& fmap (Text.unpack . coerce)
for files $ \f -> do
mbHash <- lift $ gitHashPathStdin p f
case mbHash of
Just ha ->
pure $ Just (f, ha)
Nothing ->
pure Nothing
versioned <- listBlobs Nothing <&> HM.fromList
let commited = HM.elems versioned & HS.fromList
let blobs = HM.fromList hashes
let isVersioned = maybe False (`HM.member` versioned)
withState $ transactional do
for_ fxs $ \fme -> do
let fn = fixmeGet "file" fme <&> Text.unpack . coerce
fmeRich <- lift $ maybe1 fn (pure mempty) (`getMetaDataFromGitBlame` fme)
let blob = fn >>= flip HM.lookup blobs
>>= \b -> pure (fixmeSet "blob" (fromString (show $ pretty $ b)) mempty)
notice $ "fixme" <+> pretty (fixmeKey fme) <+> pretty fn
insertFixme (fromMaybe mempty blob <> fmeRich <> fme)
-- TODO: add-scanned-only-on-commited
-- $workflow: test
-- поведение: если файл в гите И закоммичен -- то
-- добавляем в сканированные.
--
-- если не в гите -- то добавляем в сканированные
--
for_ fn $ \f -> do
let add = not (isVersioned fn)
|| maybe False (`HS.member` commited) (HM.lookup f blobs)
when add do
insertScannedFile f
cat_ :: FixmePerks m => Text -> FixmeM m ()
cat_ hash = do
(before,after) <- asks fixmeEnvCatContext >>= readTVarIO
gd <- fixmeGetGitDirCLIOpt
CatAction action <- asks fixmeEnvCatAction >>= readTVarIO
dir <- fixmeWorkDir
void $ flip runContT pure do
callCC \exit -> do
mha <- lift $ selectFixmeKey hash
ha <- ContT $ maybe1 mha (pure ())
fme' <- lift $ getFixme ha
fx@Fixme{..} <- ContT $ maybe1 fme' (pure ())
let dict = [ ("$file", mkStr @C (show $ pretty fixmeKey)) ]
<>
[ (mkId k, mkStr @C v) | (k,v) <- HM.toList fixmeAttr ]
<>
[ (mkId "$before", mkStr @C (FixmeAttrVal $ Text.pack $ show 1))
] & HM.fromList
let fallText0 = [qc|{show $ pretty fixmeTag} {show $ pretty fixmeTitle}|]
& encodeUtf8
& LBS8.fromStrict
let fallback = LBS8.unlines $ fallText0 : fmap (LBS8.fromStrict . encodeUtf8 . coerce) fixmePlain
let fbAction = action (HM.toList dict)
let gh' = HM.lookup "blob" fixmeAttr
-- FIXME: define-fallback-action
gh <- ContT $ maybe1 gh' (liftIO (fbAction fallback))
let cmd = [qc|git {gd} cat-file blob {pretty gh}|] :: String
w <- gitRunCommand cmd
<&> either (const Nothing) Just
maybe1 w (liftIO $ fbAction fallback) $ \lbs -> do
let start = fromMaybe 0 fixmeStart & fromIntegral & (\x -> x - before) & max 0
-- FIXME: off-by-one-error
let bbefore = if start == 0 then 1 else before + 1
-- warn $ red "before" <+> pretty before <+> pretty bbefore
let origLen = maybe 0 fromIntegral fixmeEnd - maybe 0 fromIntegral fixmeStart & max 1
let lno = max 1 $ origLen + after + before
let val = mkStr @C (FixmeAttrVal $ Text.pack $ show bbefore)
let ddict = HM.toList (HM.insert "$before" val dict)
let piece = LBS8.lines lbs & drop start & take lno
liftIO $ action ddict (LBS8.unlines piece)
exit ()
class HasRefChanExportOpts a where
refchanExportDry :: a -> Bool
data RefChanExportOpts =
RefChanExportDry
deriving (Eq,Ord,Show,Enum)
instance HasRefChanExportOpts [RefChanExportOpts] where
refchanExportDry what = RefChanExportDry `elem` what
instance HasRefChanExportOpts () where
refchanExportDry _ = False
refchanExport :: (FixmePerks m, HasRefChanExportOpts a) => a -> FixmeM m Int
refchanExport opts = do
let dry = refchanExportDry opts
sto <- getStorage
rchanAPI <- getClientAPI @RefChanAPI @UNIX
chan <- asks fixmeEnvRefChan
>>= readTVarIO
>>= orThrowUser "refchan not set"
au <- asks fixmeEnvAuthor
>>= readTVarIO
>>= orThrowUser "author's key not set"
creds <- runKeymanClientRO $ loadCredentials au
>>= orThrowUser "can't read credentials"
let (pk,sk) = (view peerSignPk creds, view peerSignSk creds)
gk0 <- loadGroupKey
-- TODO: this-may-cause-to-tx-flood
-- сделать какой-то период релакса,
-- что ли
now <- liftIO $ getPOSIXTime <&> round
withState do
what <- select @FixmeExported [qc|
select distinct o,?,k,cast (v as text)
from object obj
where not exists (select null from scanned where hash = obj.nonce)
order by o, k, v, w
|] (Only now)
let chu = chunksOf 10000 what
flip runContT pure do
for_ chu $ \x -> callCC \next -> do
-- FIXME: encrypt-tree
-- 6. как делать доступ к историческим данным
-- 6.1 новые ключи в этот же рефчан
-- 6.2 или новые ключи в какой-то еще рефчан
let s = maybe "[ ]" (const $ yellow "[@]") gk0
let gk = snd <$> gk0
href <- liftIO $ createTreeWithMetadata sto gk mempty (serialise x)
>>= orThrowPassIO
let tx = AnnotatedHashRef Nothing href
lift do
let lbs = serialise tx
let box = makeSignedBox @'HBS2Basic @BS.ByteString pk sk (LBS.toStrict lbs)
warn $ "POST" <+> pretty (length x) <+> s <> "tree" <+> pretty href <+> pretty (hashObject @HbSync (serialise box))
unless dry do
r <- callRpcWaitMay @RpcRefChanPropose (TimeoutSec 1) rchanAPI (chan, box)
when (isNothing r) do
err $ red "hbs2-peer rpc calling timeout"
pure $ length what
refchanUpdate :: FixmePerks m => FixmeM m ()
refchanUpdate = do
refchanImport
rchan <- asks fixmeEnvRefChan
>>= readTVarIO
>>= orThrowUser "refchan not set"
api <- getClientAPI @RefChanAPI @UNIX
sto <- getStorage
h0 <- callService @RpcRefChanGet api rchan
>>= orThrowUser "can't request refchan head"
rch <- RefChan.getRefChanHead @L4Proto sto (RefChanHeadKey rchan)
>>= orThrowUser "can't request refchan head"
let w = view refChanHeadWaitAccept rch
refchanExportGroupKeys
txn <- refchanExport ()
unless (txn == 0) do
notice $ "wait refchan" <+> pretty (AsBase58 rchan) <+> "to update..."
-- TODO: implement-refchan-update-notifications
-- FIXME: use-wait-time-from-refchan-head
-- TODO: fix-this-lame-polling
flip fix 0 $ \next -> \case
n | n >= w -> pure ()
n -> do
h <- callService @RpcRefChanGet api rchan
>>= orThrowUser "can't request refchan head"
if h0 /= h then
pure ()
else do
pause @'Seconds 1
liftIO $ hPutStr stderr (show $ pretty (w - n) <> " \r")
next (succ n)
none
refchanImport
refchanImport :: FixmePerks m => FixmeM m ()
refchanImport = do
sto <- getStorage
chan <- asks fixmeEnvRefChan
>>= readTVarIO
>>= orThrowUser "refchan not set"
ttsmap <- newTVarIO HM.empty
accepts <- newTVarIO HM.empty
tq <- newTQueueIO
ignCached <- asks fixmeEnvFlags >>= readTVarIO <&> HS.member FixmeIgnoreCached
let goodToGo x | ignCached = pure True
| otherwise = do
here <- selectIsAlreadyScanned x
pure $ not here
fixmeGkSign <- putBlock sto "FIXMEGROUPKEYBLOCKv1" <&> fmap HashRef
>>= orThrowUser "hbs2 storage error. aborted"
walkRefChanTx @UNIX goodToGo chan $ \txh u -> do
case u of
A (AcceptTran (Just ts) _ what) -> do
debug $ red "ACCEPT" <+> pretty ts <+> pretty what
atomically $ modifyTVar ttsmap (HM.insertWith max what (coerce @_ @Word64 ts))
atomically $ modifyTVar accepts (HM.insertWith (<>) what (HS.singleton txh))
scanned <- selectIsAlreadyScanned what
when scanned do
withState $ insertScanned txh
A _ -> none
P1 ppk orig (ProposeTran _ box) -> void $ runMaybeT do
(_, bs) <- unboxSignedBox0 box & toMPlus
AnnotatedHashRef sn href <- deserialiseOrFail @AnnotatedHashRef (LBS.fromStrict bs)
& toMPlus . either (const Nothing) Just
scanned <- lift $ selectIsAlreadyScanned href
when (not scanned || ignCached) do
let isGk = sn == Just fixmeGkSign
if isGk then do
atomically $ writeTQueue tq (Left (txh, orig, href, href))
else do
what <- liftIO (runExceptT $ getTreeContents sto href)
<&> either (const Nothing) Just
>>= toMPlus
let exported = deserialiseOrFail @[FixmeExported] what
& either (const Nothing) Just
case exported of
Just e -> do
for_ e $ \x -> do
atomically $ writeTQueue tq (Right (txh, orig, href, x))
Nothing -> do
lift $ withState $ insertScanned txh
imported <- atomically $ flushTQueue tq
withState $ transactional do
for_ imported $ \case
Left (txh, orig, href, gk) -> do
-- hx <- writeAsMerkle sto (serialise gk)
-- notice $ "import GK" <+> pretty hx <+> "from" <+> pretty href
-- let tx = AnnotatedHashRef _ href <- deserialiseOrFail @AnnotatedHashRef (LBS.fromStrict bs)
-- & toMPlus . either (const Nothing) Just
insertScanned txh
-- TODO: ASAP-notify-hbs2-keyman
-- у нас два варианта:
-- 1. звать runKeymanClient и в нём записывать в БД
-- с возможностью блокировок
-- 2. каким-то образом делать отложенную запись,
-- например, писать лог групповых ключей
-- куда-то, откуда hbs2-keyman сможет
-- обновить их при запуске
--
-- лог групповых ключей мы можем писать:
-- 1. в рефлог, на который подписан и кейман
-- 2. в рефчан, на который подписан и кейман
-- неожиданные плюсы:
-- + у нас уже есть такой рефчан!
-- всё, что надо сделать -- это записать ключи туда
-- с одной стороны туповато: перекладывать транзы из
-- рефчана в рефчан. с другой стороны -- не нужны никакие
-- новые механизмы. рефчан, в общем-то, локальный(?),
-- блоки никуда за пределы хоста не поедут (?) и сеть
-- грузить не будут (?)
--
-- 3. в рефчан, используя notify
-- 4. в еще какую переменную, которая будет
-- локальна
-- 5. в какой-то лог. который кейман будет
-- процессировать при hbs2-keyman update
--
-- поскольку БД кеймана блокируется целиком при апдейтах,
-- единственное, куда писать проблематично -- это сама БД.
--
pure ()
Right (txh, h, href, i) -> do
w <- readTVarIO ttsmap <&> fromMaybe (exportedWeight i) . HM.lookup h
let item = i { exportedWeight = w }
if exportedWeight item /= 0 then do
notice $ "import" <+> pretty (exportedKey item) <+> pretty (exportedWeight item)
insertFixmeExported (localNonce (href,i)) item
else do
debug $ "SKIP TX!" <+> pretty txh
atx <- readTVarIO accepts <&> fromMaybe mempty . HM.lookup h
insertScanned txh
insertScanned href
for_ atx insertScanned
refchanExportGroupKeys :: FixmePerks m => FixmeM m ()
refchanExportGroupKeys = do
let gkHash x = hashObject @HbSync ("GKSCAN" <> serialise x) & HashRef
sto <- getStorage
chan <- asks fixmeEnvRefChan
>>= readTVarIO
>>= orThrowUser "refchan not set"
ignCached <- asks fixmeEnvFlags >>= readTVarIO <&> HS.member FixmeIgnoreCached
let goodToGo x | ignCached = pure True
| otherwise = do
here <- selectIsAlreadyScanned (gkHash x)
pure $ not here
debug "refchanExportGroupKeys"
skip <- newTVarIO HS.empty
gkz <- newTVarIO HS.empty
fixmeSign <- putBlock sto "FIXMEGROUPKEYBLOCKv1" <&> fmap HashRef
walkRefChanTx @UNIX goodToGo chan $ \txh u -> do
case u of
P orig (ProposeTran _ box) -> void $ runMaybeT do
(_, bs) <- unboxSignedBox0 box & toMPlus
AnnotatedHashRef _ href <- deserialiseOrFail @AnnotatedHashRef (LBS.fromStrict bs)
& toMPlus . either (const Nothing) Just
result <- lift $ try @_ @OperationError (getGroupKeyHash href)
case result of
Right (Just gk,_) -> do
atomically do
modifyTVar gkz (HS.insert gk)
modifyTVar skip (HS.insert txh)
Right (Nothing,_) -> do
atomically $ modifyTVar skip (HS.insert txh)
Left UnsupportedFormat -> do
debug $ "unsupported" <+> pretty href
atomically $ modifyTVar skip (HS.insert txh)
Left e -> do
debug $ "other error" <+> viaShow e
_ -> none
l <- readTVarIO skip <&> HS.toList
r <- readTVarIO gkz <&> HS.toList
withState $ transactional do
for_ l (insertScanned . gkHash)
rchan <- asks fixmeEnvRefChan
>>= readTVarIO
>>= orThrowUser "refchan not set"
api <- getClientAPI @RefChanAPI @UNIX
rch <- RefChan.getRefChanHead @L4Proto sto (RefChanHeadKey rchan)
>>= orThrowUser "can't request refchan head"
au <- asks fixmeEnvAuthor
>>= readTVarIO
>>= orThrowUser "author's key not set"
creds <- runKeymanClientRO $ loadCredentials au
>>= orThrowUser "can't read credentials"
let (pk,sk) = (view peerSignPk creds, view peerSignSk creds)
keyz <- Set.fromList <$> S.toList_ do
for_ r $ \gkh -> void $ runMaybeT do
debug $ red $ "FOR GK" <+> pretty gkh
gk <- loadGroupKeyMaybe @'HBS2Basic sto gkh >>= toMPlus
-- the original groupkey should be indexed as well
lift $ S.yield gkh
gks <- liftIO (runKeymanClientRO $ findMatchedGroupKeySecret sto gk)
when (isNothing gks) do
-- lift $ withState (insertScanned (gkHash txh))
warn $ "unaccessible group key" <+> pretty gkh
mzero
gk1 <- generateGroupKey @'HBS2Basic gks (HS.toList $ view refChanHeadReaders rch)
let lbs = serialise gk1
gkh1 <- writeAsMerkle sto lbs <&> HashRef
debug $ red "prepare new gk0" <+> pretty (LBS.length lbs) <+> pretty gkh <+> pretty (groupKeyId gk)
lift $ S.yield gkh1
notice $ yellow $ "new gk:" <+> pretty (Set.size keyz)
-- let nitems = 262144 `div` (125 * HS.size (view refChanHeadReaders rch) )
-- let chunks = Map.elems keyz & chunksOf nitems
-- TODO: gk:performance-vs-reliability
-- ситуация такова: групповой ключ это меркл-дерево
-- для одного и того же блоба могут быть разные меркл-деревья,
-- так как могут быть разные настройки.
--
-- если распространять ключи по-одному, то хотя бы тот же ключ,
-- который мы создали изначально -- будет доступен по своему хэшу,
-- как отдельный артефакт.
--
-- Если писать их пачками, где каждый ключ представлен непосредственно,
-- то на принимающей стороне нет гарантии, что меркл дерево будет писаться
-- с таким же параметрами, хотя и может.
--
-- Решение: делать групповой ключ БЛОКОМ. тогда его размер будет ограничен,
-- но он хотя бы будет всегда однозначно определён хэшем.
--
-- Решение: ссылаться не на групповой ключ, а на хэш его секрета
-- что ломает текущую схему и обратная совместимость будет морокой.
--
-- Решение: добавить в hbs2-keyman возможно индексации единичного
-- ключа, и индексировать таким образом *исходные* ключи.
--
-- Тогда можно эти вот ключи писать пачками, их хэши не имеют особого значения,
-- если мы проиндексируем оригинальный ключ и будем знать, на какой секрет он
-- ссылается.
--
-- Заметим, что в один блок поместится аж >2000 читателей, что должно быть
-- более, чем достаточно => при таких группах вероятность утечки секрета
-- стремится к 1.0, так как большинство клало болт на меры безопасности.
--
-- Кстати говоря, проблема недостаточного количества авторов в ключе легко
-- решается полем ORIGIN, т.к мы можем эти самые ключи разделять.
--
-- Что бы не стоять перед такой проблемой, мы всегда можем распостранять эти ключи
-- по-одному, ЛИБО добавить в производный ключ поле
-- ORIGIN: где будет хэш изначального ключа.
--
-- Это нормально, так как мы сможем проверить, что у этих ключей
-- (текущий и ORIGIN) одинаковые хэши секретов.
--
-- Это всё равно оставляет возможность еще одной DoS атаки на сервис,
-- с распространением кривых ключей, но это хотя бы выяснимо, ну и атака
-- может быть только в рамках рефчана, т.е лечится выкидыванием пиров /
-- исключением зловредных авторов.
for_ (Set.toList keyz) $ \href -> do
let tx = AnnotatedHashRef fixmeSign href
let lbs = serialise tx
let box = makeSignedBox @'HBS2Basic @BS.ByteString pk sk (LBS.toStrict lbs)
warn $ "post gk tx" <+> "tree" <+> pretty href
result <- callRpcWaitMay @RpcRefChanPropose (TimeoutSec 1) api (chan, box)
when (isNothing result) do
err $ red "hbs2-peer rpc calling timeout"

View File

@ -0,0 +1,284 @@
{-# Language MultiWayIf #-}
module Fixme.Run.Internal.RefChan (fixmeRefChanInit) where
import Prelude hiding (init)
import Fixme.Prelude hiding (indent)
import Fixme.Types
import Fixme.Config
import HBS2.OrDie
import HBS2.Base58
import HBS2.Net.Auth.GroupKeySymm
import HBS2.Data.Types.SignedBox
import HBS2.Peer.Proto.RefChan as RefChan
import HBS2.Storage.Operations.ByteString
import HBS2.System.Dir
import HBS2.Net.Auth.Credentials
import HBS2.CLI.Run.KeyMan (keymanNewCredentials)
import HBS2.KeyMan.Keys.Direct
import Data.ByteString.Lazy.Char8 qualified as LBS8
import Data.Either
import Data.Maybe
import Data.List qualified as List
import Data.HashSet qualified as HS
import Data.HashMap.Strict qualified as HM
import Text.InterpolatedString.Perl6 (qc)
import Lens.Micro.Platform
import System.Process.Typed
import Control.Monad.Trans.Cont
import Control.Monad.Trans.Maybe
import Data.Word
import System.IO qualified as IO
{- HLINT ignore "Functor law"-}
notEmpty :: [a] -> Maybe [a]
notEmpty = \case
[] -> Nothing
x -> Just x
data RefChanInitFSM =
InitInit
| SetupNewRefChan
| SetupExitFailure
| CheckRefChan (PubKey 'Sign 'HBS2Basic)
| RefChanHeadFound (PubKey 'Sign 'HBS2Basic) (RefChanHeadBlock L4Proto)
| WaitRefChanHeadStart (PubKey 'Sign 'HBS2Basic) Word64
| WaitRefChanHead (PubKey 'Sign 'HBS2Basic) Word64
fixmeRefChanInit :: FixmePerks m => Maybe (PubKey 'Sign 'HBS2Basic) -> FixmeM m ()
fixmeRefChanInit mbRc = do
let rch0 = refChanHeadDefault @L4Proto
sto <- getStorage
peer <- getClientAPI @PeerAPI @UNIX
rchanApi <- getClientAPI @RefChanAPI @UNIX
dir <- localConfigDir
confFile <- localConfig
rchan <- asks fixmeEnvRefChan
>>= readTVarIO
poked <- callRpcWaitMay @RpcPoke (TimeoutSec 1) peer ()
>>= orThrowUser "hbs2-peer not connected"
<&> parseTop
<&> fromRight mempty
pkey <- [ fromStringMay @(PubKey 'Sign 'HBS2Basic) x
| ListVal [SymbolVal "peer-key:", StringLike x ] <- poked
] & headMay . catMaybes & orThrowUser "hbs2-peer key not set"
let refChanClause r = mkList @C [ mkSym "refchan"
, mkSym (show $ pretty (AsBase58 r))
]
flip runContT pure $ callCC \done -> do
flip fix InitInit $ \next -> \case
InitInit -> do
case (rchan, mbRc) of
(Nothing, Nothing) -> next SetupNewRefChan
(_, Just r2) -> next (CheckRefChan r2)
(Just r1, Nothing) -> next (CheckRefChan r1)
CheckRefChan rc -> do
notice $ "check refchan:" <+> pretty (AsBase58 rc)
notice $ "subscribe to refchan" <+> pretty (AsBase58 rc)
-- FIXME: poll-time-hardcode
-- $class: hardcode
void $ callService @RpcPollAdd peer (rc, "refchan", 17)
notice $ "fetch refchan head" <+> pretty (AsBase58 rc)
void $ lift $ callRpcWaitMay @RpcRefChanHeadFetch (TimeoutSec 1) rchanApi rc
now <- liftIO $ getPOSIXTime <&> round
pause @'Seconds 1
next $ WaitRefChanHead rc now
WaitRefChanHeadStart rc t -> do
notice $ "wait for refchan head" <+> pretty (AsBase58 rc)
next (WaitRefChanHead rc t)
WaitRefChanHead rc t -> do
now <- liftIO $ getPOSIXTime <&> round
let s = 60 - (now -t)
hd <- getRefChanHead @L4Proto sto (RefChanHeadKey rc)
liftIO $ IO.hPutStr stderr $ show $ "waiting" <+> pretty s <+> " \r"
if | now - t < 60 && isNothing hd -> do
pause @'Seconds 1
next $ WaitRefChanHead rc t
| now - t > 60 && isNothing hd -> do
err "refchan wait timeout"
next $ SetupExitFailure
| isJust hd -> do
next $ RefChanHeadFound rc (fromJust hd)
| otherwise -> next $ SetupExitFailure
RefChanHeadFound rc hd -> do
notice $ "found refchan head for" <+> pretty (AsBase58 rc)
void $ lift $ callRpcWaitMay @RpcRefChanFetch (TimeoutSec 1) rchanApi rc
author <- lift $ asks fixmeEnvAuthor >>= readTVarIO
let readers = view refChanHeadReaders hd
let authors = view refChanHeadAuthors hd
-- hbs2-keyman/hbs2-keyman-direct-lib/HBS2/KeyMan/Keys/Direct.hs
rs <- liftIO (runKeymanClientRO $ loadKeyRingEntries (HS.toList readers))
let isReader = case rs of
[] -> False
_ -> True
let canRead = if isReader then
green "yes"
else
red "no"
notice $ "reader:" <+> canRead
let isAuthor = maybe1 author False (`HS.member` authors)
let canWrite = if isAuthor
then green "yes"
else red "no"
notice $ "author:" <+> canWrite
unless isReader do
warn $ yellow "no reader key found" <> line
<> "it's may be ok, if this refchan is not encrypted" <> line
<> "otherwise, make your encryption key a member of this refchan head"
<> line
unless isAuthor do
warn $ red "no author key found" <> line
<> "it's may be ok if you have only read-only access to this refchan" <> line
<> "otherwise, use" <+> yellow "author KEY" <+> "settings in the .fixme-new/config" <> line
<> "and make sure it is added to the refchan head"
<> line
unless (isJust rchan) do
notice $ "adding refchan to" <+> pretty confFile
liftIO do
appendFile confFile $ show $
line
<> vcat [ pretty (refChanClause rc) ]
SetupExitFailure -> do
err "refchan init failed"
SetupNewRefChan -> do
notice $ green "default peer" <+> pretty (AsBase58 pkey)
signK' <- lift $ runKeymanClientRO $ listCredentials
<&> headMay
signK <- ContT $ maybe1 signK' (throwIO $ userError "no default author key found in hbs2-keyman")
notice $ green "default author" <+> pretty (AsBase58 signK)
-- TODO: use-hbs2-git-api?
(_, gkh', _) <- readProcess (shell [qc|git hbs2 key|])
<&> over _2 ( (fromStringMay @HashRef) <=< (notEmpty . headDef "" . lines . LBS8.unpack) )
<&> \x -> case view _1 x of
ExitFailure _ -> set _2 Nothing x
ExitSuccess -> x
notice $ green "group key" <+> maybe "none" pretty gkh'
readers <- fromMaybe mempty <$> runMaybeT do
gh <- toMPlus gkh'
gk <- loadGroupKeyMaybe @'HBS2Basic sto gh
>>= toMPlus
pure $ HM.keys (recipients gk)
notice $ green "readers" <+> pretty (length readers)
rk <- lift $ runKeymanClientRO $ loadKeyRingEntries readers
<&> fmap snd . headMay
let rch1 = rch0 & set refChanHeadReaders (HS.fromList readers)
& set refChanHeadAuthors (HS.singleton signK)
& set refChanHeadPeers (HM.singleton pkey 1)
let unlucky = HM.null (view refChanHeadPeers rch1)
|| HS.null (view refChanHeadAuthors rch1)
liftIO $ print $ pretty rch1
if unlucky then do
warn $ red $ "refchan definition is not complete;" <+>
"you may add missed keys, edit the" <+>
"defition and add if manually or repeat init attempt"
<> line
else do
notice "refchan definition seems okay, adding new refchan"
refchan <- lift $ keymanNewCredentials (Just "refchan") 0
creds <- lift $ runKeymanClientRO $ loadCredentials refchan
>>= orThrowUser "can't load credentials"
let box = makeSignedBox @'HBS2Basic (view peerSignPk creds) (view peerSignSk creds) rch1
href <- writeAsMerkle sto (serialise box)
callService @RpcPollAdd peer (refchan, "refchan", 17)
>>= orThrowUser "can't subscribe to refchan"
callService @RpcRefChanHeadPost rchanApi (HashRef href)
>>= orThrowUser "can't post refchan head"
let nonce = take 6 $ show $ pretty (AsBase58 refchan)
let rchanFile = "refchan-" <> nonce <> ".local"
let rchanFilePath = dir </> rchanFile
let note = ";; author and reader are inferred automatically" <> line
<> ";; from hbs2-keyman data" <> line
<> ";; edit them if needed" <> line
<> ";; reader is *your* reading public key." <> line
<> ";; author is *your* signing public key." <> line
let theirReaderKeyClause = maybe1 rk ";; reader ..."$ \(KeyringEntry pk _ _) -> do
pretty $ mkList @C [ mkSym "reader", mkSym (show $ pretty (AsBase58 pk) ) ]
let theirAuthorClause = mkList @C [ mkSym "author", mkSym (show $ pretty (AsBase58 signK) ) ]
let content = line
<> note
<> line
<> vcat [ theirReaderKeyClause
, pretty theirAuthorClause
]
liftIO do
writeFile rchanFilePath $
show content
notice $ "adding refchan to" <+> pretty confFile
liftIO do
appendFile confFile $ show $
line
<> vcat [ pretty (refChanClause refchan) ]
next $ CheckRefChan refchan

250
fixme-new/lib/Fixme/Scan.hs Normal file
View File

@ -0,0 +1,250 @@
{-# Language MultiWayIf #-}
module Fixme.Scan
( scanBlobOpts
, scanBlob
, scanMagic
, updateScanMagic
, NoIndents(..)
) where
import Fixme.Prelude hiding (indent)
import Fixme.Types
import Data.ByteString.Lazy.Char8 (ByteString)
import Data.ByteString.Lazy.Char8 qualified as LBS8
import Data.Text qualified as Text
import Data.Text.Encoding (decodeUtf8With)
import Data.Text.Encoding.Error (ignore)
import Data.Char (isSpace)
import Data.Maybe
import Data.HashSet qualified as HS
import Data.HashMap.Strict qualified as HM
import Data.Coerce
import Data.Generics.Product.Fields (field)
import Lens.Micro.Platform
import Text.InterpolatedString.Perl6 (qc)
import Streaming.Prelude qualified as S
{- HLINT ignore "Functor law" -}
data SfEnv =
SfEnv { lno :: Int -- ^ line number
, l0 :: Int -- ^ fixme indent
, eln :: Int -- ^ empty lines counter
}
deriving stock Generic
succEln :: SfEnv -> ByteString -> SfEnv
succEln f s | LBS8.null s = over (field @"eln") succ f
| otherwise = set (field @"eln") 0 f
data Sx = S0 | Sf SfEnv
data S = S Sx [(Int,ByteString)]
data FixmePart = FixmePart Int FixmeWhat
deriving stock (Show,Data,Generic)
data FixmeWhat = FixmeHead Int Int Text Text
| FixmeLine Text
| FixmeAttr FixmeAttrName FixmeAttrVal
deriving stock (Show,Data,Generic)
data P = P0 [FixmePart] | P1 Int Fixme [FixmePart]
scanMagic :: FixmePerks m => FixmeM m HashRef
scanMagic = do
env <- ask
w <- atomically do
tagz <- fixmeEnvTags env & readTVar
co <- fixmeEnvDefComments env & readTVar
fco <- fixmeEnvFileComments env & readTVar
m <- fixmeEnvFileMask env & readTVar
e <- fixmeEnvFileExclude env & readTVar
a <- fixmeEnvAttribs env & readTVar
v <- fixmeEnvAttribValues env & readTVar
pure $ serialise (tagz, co, fco, m, e, a, v)
pure $ HashRef $ hashObject w
updateScanMagic :: (FixmePerks m) => FixmeM m ()
updateScanMagic = do
t <- asks fixmeEnvScanMagic
magic <- scanMagic
atomically $ writeTVar t (Just magic)
class IsScanBlobOptions a where
ignoreIndents :: a -> Bool
data NoIndents = NoIndents
instance IsScanBlobOptions () where
ignoreIndents = const False
instance IsScanBlobOptions NoIndents where
ignoreIndents = const True
scanBlob :: forall m . (FixmePerks m)
=> Maybe FilePath
-> ByteString
-> FixmeM m [Fixme]
scanBlob = scanBlobOpts ()
scanBlobOpts :: forall o m . (IsScanBlobOptions o, FixmePerks m)
=> o
-> Maybe FilePath
-> ByteString
-> FixmeM m [Fixme]
scanBlobOpts o fpath lbs = do
let indents = not (ignoreIndents o)
tagz <- asks fixmeEnvTags
>>= readTVarIO
<&> HS.toList
<&> fmap (Text.unpack . coerce)
<&> filter (not . null)
<&> fmap LBS8.pack
comments <- fixmeGetCommentsFor fpath
<&> filter (not . LBS8.null) . fmap (LBS8.pack . Text.unpack)
anames <- asks fixmeEnvAttribs >>= readTVarIO <&> HS.toList
let setters = [ ( LBS8.pack [qc|${show $ pretty n}:|], n ) | n <- anames ]
let ls = LBS8.lines lbs & zip [0..]
parts <- S.toList_ do
flip fix (S S0 ls) $ \next -> \case
S S0 ((lno,x):xs) -> do
(l,bs) <- eatPrefix0 Nothing comments x
let mtag = headMay [ t | t <- tagz, LBS8.isPrefixOf t bs ]
case mtag of
Nothing ->
next (S S0 xs)
Just tag -> do
emitFixmeStart lno l tag (LBS8.drop (LBS8.length tag) bs)
next (S (Sf (SfEnv lno l 0)) xs)
S sf@(Sf env@(SfEnv{..})) (x : xs) -> do
(li,bs) <- eatPrefix0 (Just l0) comments (snd x)
if | eln > 1 -> next (S S0 (x:xs))
| indents && li <= l0 && not (LBS8.null bs) -> next (S S0 (x:xs))
| otherwise -> do
let stripped = LBS8.dropWhile isSpace bs
let attr = headMay [ (s, LBS8.drop (LBS8.length a) stripped)
| (a,s) <- setters, LBS8.isPrefixOf a stripped
]
case attr of
Just (a,v) -> do
let vv = LBS8.toStrict v & decodeUtf8With ignore & Text.strip
emitFixmeAttr (fst x) l0 a (FixmeAttrVal vv)
Nothing -> do
emitFixmeLine (fst x) l0 bs
next (S (Sf (succEln env bs)) xs)
S _ [] -> pure ()
-- debug $ vcat (fmap viaShow parts)
S.toList_ do
flip fix (P0 parts) $ \next -> \case
(P0 (FixmePart l h@FixmeHead{} : rs)) -> do
next (P1 l (fromHead h) rs)
(P1 _ fx (FixmePart l h@FixmeHead{} : rs)) -> do
emitFixme fx
next (P1 l (fromHead h) rs)
(P1 _ fx (FixmePart lno (FixmeLine what) : rs)) -> do
next (P1 lno (setLno lno $ over (field @"fixmePlain") (<> [FixmePlainLine what]) fx) rs)
(P1 _ fx (FixmePart lno (FixmeAttr a v) : rs)) -> do
next (P1 lno (setLno lno $ over (field @"fixmeAttr") (<> HM.singleton a v) fx) rs)
(P1 _ fx []) -> emitFixme fx
(P0 ( _ : rs ) ) -> next (P0 rs)
(P0 []) -> pure ()
where
setLno lno fx@Fixme{} = do
let lno1 = Just (FixmeOffset (fromIntegral lno))
set (field @"fixmeEnd") lno1 fx
emitFixme e = do
S.yield $ over (field @"fixmePlain") dropEmpty e
where
dropEmpty = dropWhile $ \case
FixmePlainLine "" -> True
_ -> False
-- FIXME: jopakita
fromHead = \case
FixmeHead lno _ tag title ->
Fixme (FixmeTag tag)
(FixmeTitle title)
mempty
Nothing
(Just (FixmeOffset (fromIntegral lno)))
Nothing
mempty
mempty
_ -> mempty
emitFixmeStart lno lvl tagbs restbs = do
let tag = decodeUtf8With ignore (LBS8.toStrict tagbs) & Text.strip
let rest = decodeUtf8With ignore (LBS8.toStrict restbs) & Text.strip
S.yield (FixmePart lno (FixmeHead lno lvl tag rest))
emitFixmeAttr lno _ name val = do
S.yield (FixmePart lno (FixmeAttr name val))
emitFixmeLine lno _ restbs = do
let rest = decodeUtf8With ignore (LBS8.toStrict restbs) & Text.stripEnd
S.yield (FixmePart lno (FixmeLine rest))
eatPrefix0 lim' comments x = do
over _2 LBS8.pack <$> do
flip fix (0, LBS8.unpack x) $ \next w@(k, left) -> do
let lim = fromMaybe (succ k) lim'
if k > lim then
pure (k, left)
else
case w of
(n, ' ' : rest) -> next (n+1, if k == lim then ' ' : rest else rest)
(n, '\t' : rest) -> next (n+8, if k == lim then '\t' : rest else rest)
(n, rest) -> do
let comm = headMay [ co | co <- comments, LBS8.isPrefixOf co (LBS8.pack rest) ]
case comm of
Nothing -> pure (n, rest)
Just co -> next (n+1, drop (fromIntegral $ LBS8.length co) rest)

View File

@ -0,0 +1,369 @@
{-# Language MultiWayIf #-}
{-# Language PatternSynonyms #-}
{-# Language ViewPatterns #-}
module Fixme.Scan.Git.Local where
import Prelude hiding (init)
import Fixme.Prelude hiding (indent)
import Fixme.Types
import Fixme.State
import Fixme.Scan as Scan
import HBS2.Storage
import HBS2.Storage.Compact
import HBS2.System.Dir
import HBS2.Git.Local.CLI
import DBPipe.SQLite hiding (field)
import Data.Config.Suckless
import Data.Text.Fuzzy.Tokenize
import Data.ByteString.Char8 qualified as BS
import Data.ByteString.Lazy.Char8 qualified as LBS8
import Data.ByteString.Lazy (ByteString)
import Data.Either
import Data.Fixed
import Data.List qualified as List
import Data.List.Split (chunksOf)
import Data.Maybe
import Data.HashMap.Strict (HashMap)
import Data.HashMap.Strict qualified as HM
import Data.HashSet qualified as HS
import Data.HashSet (HashSet)
import Data.Text qualified as Text
import Data.Text.Encoding (decodeUtf8With)
import Data.Text.Encoding.Error (ignore)
import Data.Word
import Text.InterpolatedString.Perl6 (qc)
import Data.Coerce
import Data.Generics.Product.Fields (field)
import Lens.Micro.Platform
import System.Process.Typed
import Control.Monad.Trans.Cont
import Control.Monad.Trans.Maybe
import System.IO qualified as IO
import System.IO.Temp (emptySystemTempFile)
import System.TimeIt
import Data.Map qualified as Map
import Streaming.Prelude qualified as S
{- HLINT ignore "Functor law" -}
listCommits :: FixmePerks m => FixmeM m [(GitHash, HashMap FixmeAttrName FixmeAttrVal)]
listCommits = do
gd <- fixmeGetGitDirCLIOpt
days <- asks fixmeEnvGitScanDays
>>= readTVarIO
<&> fmap ( \x -> "--since" <+> squotes (pretty x <+> "days ago"))
<&> fromMaybe mempty
<&> show
let cmd = [qc|git {gd} log --all --format="%H '%cn' '%ce' %ct" {days}|]
debug $ yellow "listCommits" <+> pretty cmd
gitRunCommand cmd
<&> fromRight mempty
<&> LBS8.lines
<&> mapMaybe extract
where
extract :: ByteString -> Maybe (GitHash, HashMap FixmeAttrName FixmeAttrVal)
extract lbs = do
let txt = decodeUtf8With ignore (LBS8.toStrict lbs)
let r = tokenize @Text spec txt
case r of
[co, n, e, t] -> do
let gh = fromStringMay @GitHash (Text.unpack co)
let bag = [ ("commit", co)
, ("commit-time", t)
, ("committer-name", n)
, ("committer-email", e)
, ("committer", [qc|{n} <{e}>|])
] & fmap ( over _1 FixmeAttrName . over _2 FixmeAttrVal)
& HM.fromList
(,) <$> gh <*> pure bag
_ -> Nothing
spec = sq <> delims " \t"
listBlobs :: (FixmePerks m, MonadReader FixmeEnv m) => Maybe GitHash -> m [(FilePath,GitHash)]
listBlobs mco = do
gd <- fixmeGetGitDirCLIOpt
let what = maybe "HEAD" (show . pretty) mco
gitRunCommand [qc|git {gd} ls-tree -r -l -t {what}|]
<&> fromRight mempty
<&> fmap LBS8.words . LBS8.lines
<&> mapMaybe
(\case
[a,"blob",h,_,fn] -> (LBS8.unpack fn,) <$> fromStringMay @GitHash (LBS8.unpack h)
_ -> Nothing)
filterBlobs0 :: FixmePerks m
=> [(Bool,FilePattern)]
-> [(FilePath,GitHash)]
-> FixmeM m [(FilePath,GitHash)]
filterBlobs0 pat xs = do
-- pat <- asks fixmeEnvFileMask >>= readTVarIO <&> fmap (True,)
let src = [ ((f,h),f) | (f,h) <- xs ]
let r = [(h,f) | (_,(f,h),_) <- matchMany pat src] & HM.fromList & HM.toList
pure $ [ (b,a) | (a,b) <- r ]
filterBlobs :: FixmePerks m
=> [(FilePath,GitHash)]
-> FixmeM m [(FilePath,GitHash)]
filterBlobs xs = do
pat <- asks fixmeEnvFileMask >>= readTVarIO <&> fmap (True,)
filterBlobs0 pat xs
listRelevantBlobs :: FixmePerks m
=> FixmeM m [(FilePath, GitHash)]
listRelevantBlobs = do
commits <- listCommits
S.toList_ $ do
for_ commits $ \(co, _) -> do
found <- lift $ listBlobs (Just co) >>= filterBlobs
S.each found
listFixmies :: FixmePerks m
=> FixmeM m [Fixme]
listFixmies = do
flip runContT pure do
blobs <- lift listRelevantBlobs
gitCat <- ContT $ bracket startGitCatFile (hClose . getStdin)
let ssin = getStdin gitCat
let ssout = getStdout gitCat
liftIO $ IO.hSetBuffering ssin LineBuffering
for_ blobs $ \(fp,h) -> do
liftIO $ IO.hPrint ssin (pretty h) >> IO.hFlush ssin
prefix <- liftIO (BS.hGetLine ssout) <&> BS.words
case prefix of
[bh, "blob", ssize] -> do
let mslen = readMay @Int (BS.unpack ssize)
len <- ContT $ maybe1 mslen (pure ())
blob <- liftIO $ LBS8.hGet ssout len
void $ liftIO $ BS.hGetLine ssout
poor <- lift (Scan.scanBlob (Just fp) blob)
liftIO $ mapM_ (print . pretty) poor
_ -> pure ()
pure mempty
gitListStage :: (FixmePerks m)
=> FixmeM m [Either (FilePath, GitHash) (FilePath, GitHash)]
gitListStage = do
gd <- fixmeGetGitDirCLIOpt
modified <- gitRunCommand [qc|git {gd} status --porcelain|]
<&> fromRight mempty
<&> fmap LBS8.words . LBS8.lines
<&> mapMaybe ( \case
["M", fn] -> Just (LBS8.unpack fn)
_ -> Nothing
)
new <- S.toList_ $ do
for_ modified $ \fn -> void $ runMaybeT do
e <- gitRunCommand [qc|git {gd} hash-object {fn}|]
>>= toMPlus
<&> maybe mempty LBS8.unpack . headMay . LBS8.words
<&> fromStringMay @GitHash
>>= toMPlus
lift (S.yield $ (fn,e))
old <- gitRunCommand [qc|git {gd} ls-files -s|]
<&> fromRight mempty
<&> fmap LBS8.words . LBS8.lines
<&> mapMaybe ( \case
[_, h, _, fn] -> (LBS8.unpack fn,) <$> fromStringMay @GitHash (LBS8.unpack h)
_ -> Nothing
)
new1 <- filterBlobs new <&> fmap Left
old1 <- filterBlobs old <&> fmap Right
pure (old1 <> new1)
getMetaDataFromGitBlame :: FixmePerks m => FilePath -> Fixme -> FixmeM m Fixme
getMetaDataFromGitBlame f fx0 = do
gd <- fixmeGetGitDirCLIOpt
fromMaybe mempty <$> runMaybeT do
l0 <- fixmeStart fx0 & toMPlus <&> fromIntegral <&> succ
let cmd = [qc|git {gd} blame {f} -L{l0},{l0} -t -l -p|]
s0 <- gitRunCommand cmd
<&> LBS8.unpack . fromRight mempty
s <- parseTop s0 & toMPlus
let ko = headMay (words <$> lines s0)
>>= headMay
>>= (\z -> do
if z == "0000000000000000000000000000000000000000"
then Nothing
else Just z )
>>= fromStringMay @GitHash
pieces <- for s $ \case
ListVal (SymbolVal "committer" : StringLikeList w) | isJust ko -> do
let co = FixmeAttrVal $ fromString $ unwords w
pure $ mempty { fixmeAttr = HM.singleton "committer-name" co }
ListVal (SymbolVal "committer-mail" : StringLikeList w) | isJust ko -> do
let co = FixmeAttrVal $ fromString $ unwords w
pure $ mempty { fixmeAttr = HM.singleton "committer-email" co }
ListVal [SymbolVal "committer-time", TimeStampLike t] | isJust ko -> do
let ct = FixmeAttrVal $ fromString $ show t
pure $ mempty { fixmeAttr = HM.singleton "commit-time" ct, fixmeTs = Just t }
_ -> pure mempty
let coco = mempty { fixmeAttr = maybe mempty (HM.singleton "commit" . fromString . show . pretty) ko }
pure $ mconcat pieces <> coco
gitExtractFileMetaData :: FixmePerks m => [FilePath] -> FixmeM m (HashMap FilePath Fixme)
gitExtractFileMetaData fns = do
-- FIXME: magic-number
let chunks = chunksOf 64 fns
gd <- fixmeGetGitDirCLIOpt
commitz <- S.toList_ $ for_ chunks $ \chu -> do
let filez = unwords chu
let cmd = [qc|git {gd} log --diff-filter=AMR --pretty=format:'entry %H %at "%an" "%ae"' -- {filez}|]
ss <- gitRunCommand cmd
<&> fromRight mempty
<&> fmap LBS8.unpack . LBS8.lines
for_ ss $ \s -> do
let syn = parseTop s & fromRight mempty
case syn of
[ListVal [SymbolVal "entry", SymbolVal (Id e), LitIntVal t, StringLike n, StringLike m]] -> do
-- liftIO $ print $ pretty e <+> pretty syn
S.yield (fromString @GitHash (Text.unpack e), (t,n,m) )
_ -> pure ()
let co = HM.fromList commitz
& HM.toList
rich0 <- S.toList_ $ do
for_ co $ \(c, (t,n,m)) -> do
let pat = [ (True, f) | f <- fns ]
blobz <- lift $ listBlobs (Just c) >>= filterBlobs0 pat
for_ blobz $ \(f,h) -> do
let attr = HM.fromList [ ("commit", FixmeAttrVal (fromString $ show $ pretty c))
, ("commit-time", FixmeAttrVal (fromString $ show $ pretty t))
, ("committer-name", FixmeAttrVal (fromString n))
, ("committer-email", FixmeAttrVal (fromString m))
, ("committer", FixmeAttrVal (fromString $ [qc|{n} <{m}>|]))
, ("file", FixmeAttrVal (fromString f))
, ("blob", FixmeAttrVal (fromString $ show $ pretty $ h))
]
let what = mempty { fixmeAttr = attr }
S.yield (f,t,what)
let rich = List.sortBy (\a b -> compare (view _2 a) (view _2 b)) rich0
pure $ HM.fromList [ (view _1 w, view _3 w) | w <- rich ]
data GitBlobInfo = GitBlobInfo FilePath GitHash
deriving stock (Eq,Ord,Data,Generic,Show)
instance Hashable GitBlobInfo
data GitIndexEntry =
GitCommit Word64 (HashSet GitBlobInfo)
deriving stock (Eq,Ord,Data,Generic,Show)
instance Serialise GitBlobInfo
instance Serialise GitIndexEntry
listCommitForIndex :: forall m . (FixmePerks m, MonadReader FixmeEnv m) => ( (GitHash, GitIndexEntry) -> m ()) -> m ()
listCommitForIndex fn = do
gd <- fixmeGetGitDirCLIOpt
let cmd = [qc|git {gd} log --all --format="%H %ct"|]
debug $ yellow "listCommits" <+> pretty cmd
s0 <- gitRunCommand cmd
<&> fromRight mempty
<&> fmap (words . LBS8.unpack) . LBS8.lines
<&> mapMaybe ( \case
[a,b] -> (,) <$> fromStringMay @GitHash a <*> makeIndexEntry0 a b
_ -> Nothing
)
for_ s0 $ \(h, GitCommit w _) -> do
blobz <- listBlobs (Just h) <&> HS.fromList . fmap ( uncurry GitBlobInfo )
fn (h, GitCommit w blobz)
where
makeIndexEntry0 _ t = GitCommit <$> readMay t <*> pure mempty
gitCatBlob :: (FixmePerks m, MonadReader FixmeEnv m) => GitHash -> m ByteString
gitCatBlob h = do
gd <- fixmeGetGitDirCLIOpt
(_,s,_) <- readProcess $ shell [qc|git {gd} cat-file blob {pretty h}|]
pure s
startGitHash :: (FixmePerks m, MonadReader FixmeEnv m) => m (Process Handle Handle ())
startGitHash = do
gd <- fixmeGetGitDirCLIOpt
let cmd = [qc|git {gd} hash-object --stdin-paths|]
debug $ pretty cmd
let config = setStdin createPipe $ setStdout createPipe $ setStderr closed $ shell cmd
startProcess config
gitHashPathStdin :: FixmePerks m => (Process Handle Handle e) -> FilePath -> FixmeM m (Maybe GitHash)
gitHashPathStdin prc file = do
let ssin = getStdin prc
let sout = getStdout prc
liftIO $ IO.hPutStrLn ssin file >> IO.hFlush ssin
liftIO (IO.hGetLine sout) <&> fromStringMay @GitHash
startGitCatFile :: (FixmePerks m, MonadReader FixmeEnv m) => m (Process Handle Handle ())
startGitCatFile = do
gd <- fixmeGetGitDirCLIOpt
let cmd = [qc|git {gd} cat-file --batch|]
debug $ pretty cmd
let config = setStdin createPipe $ setStdout createPipe $ setStderr closed $ shell cmd
-- ssin <- getStdin config
startProcess config

View File

@ -0,0 +1,599 @@
{-# LANGUAGE PatternSynonyms, ViewPatterns #-}
{-# LANGUAGE UndecidableInstances #-}
{-# OPTIONS_GHC -fno-warn-orphans #-}
module Fixme.State
( evolve
, withState
, cleanupDatabase
, listFixme
, countFixme
, countByAttribute
, insertFixme
, insertFixmeExported
, modifyFixme
, insertScannedFile
, insertScanned
, selectIsAlreadyScannedFile
, selectIsAlreadyScanned
, listAllScanned
, selectFixmeKey
, getFixme
, insertTree
, FixmeExported(..)
, HasPredicate(..)
, SelectPredicate(..)
, HasLimit(..)
, HasItemOrder(..)
, ItemOrder(..)
, Reversed(..)
, LocalNonce(..)
, WithLimit(..)
, QueryOffset(..)
, QueryLimit(..)
, QueryLimitClause(..)
) where
import Fixme.Prelude hiding (key)
import Fixme.Types
import Fixme.Config
import HBS2.Base58
import HBS2.System.Dir
import DBPipe.SQLite hiding (field)
import Data.HashSet (HashSet)
import Data.HashSet qualified as HS
import Data.Aeson as Aeson
import Data.ByteString.Lazy qualified as LBS
import Data.HashMap.Strict qualified as HM
import Text.InterpolatedString.Perl6 (qc)
import Data.Text qualified as Text
import Data.Text.Encoding qualified as Text
import Data.Maybe
import Data.List qualified as List
import Control.Monad.Trans.Maybe
import Data.Coerce
import Data.Word (Word64)
import System.Directory (getModificationTime)
import Data.Time.Clock.POSIX (utcTimeToPOSIXSeconds)
-- TODO: runPipe-omitted
-- runPipe нигде не запускается, значит, все изменения
-- будут закоммичены в БД только по явному вызову
-- commitAll или transactional
-- это может объясняеть некоторые артефакты.
-- Но это и удобно: кажется, что можно менять БД
-- на лету бесплатно
newtype SomeHash h = SomeHash { fromSomeHash :: h }
deriving newtype (IsString)
instance Pretty (AsBase58 h) => ToField (SomeHash h) where
toField (SomeHash h) = toField ( show $ pretty (AsBase58 h))
instance IsString (SomeHash h) => FromField (SomeHash h) where
fromField = fmap fromString . fromField @String
pattern Operand :: forall {c} . Text -> Syntax c
pattern Operand what <- (operand -> Just what)
pattern BinOp :: forall {c} . Id -> Syntax c
pattern BinOp what <- (binOp -> Just what)
binOp :: Syntax c -> Maybe Id
binOp = \case
SymbolVal "~" -> Just "like"
SymbolVal "&&" -> Just "and"
SymbolVal "||" -> Just "or"
_ -> Nothing
operand :: Syntax c -> Maybe Text
operand = \case
SymbolVal c -> Just (coerce c)
LitStrVal s -> Just s
LitIntVal i -> Just (Text.pack (show i))
LitScientificVal v -> Just (Text.pack (show v))
_ -> Nothing
instance ToField HashRef where
toField x = toField $ show $ pretty x
instance FromField HashRef where
fromField = fmap (fromString @HashRef) . fromField @String
evolve :: FixmePerks m => FixmeM m ()
evolve = do
dbPath <- localDBPath
debug $ "evolve" <+> pretty dbPath
mkdir (takeDirectory dbPath)
withState do
createTables
withState :: forall m a . (FixmePerks m, MonadReader FixmeEnv m) => DBPipeM m a -> m a
withState what = do
lock <- asks fixmeLock
db <- withMVar lock $ \_ -> do
t <- asks fixmeEnvDb
mdb <- readTVarIO t
case mdb of
Just d -> pure (Right d)
Nothing -> do
path <- localDBPath
newDb <- try @_ @IOException (newDBPipeEnv dbPipeOptsDef path)
case newDb of
Left e -> pure (Left e)
Right db -> do
debug "set-new-db"
atomically $ writeTVar t (Just db)
pure $ Right db
either throwIO (`withDB` what) db
createTables :: FixmePerks m => DBPipeM m ()
createTables = do
-- ddl [qc| create table if not exists tree
-- ( hash text not null
-- , nonce text not null
-- , primary key (hash,nonce)
-- )
-- |]
ddl [qc| create table if not exists scanned
( hash text not null primary key )
|]
ddl [qc| create table if not exists object
( o text not null
, w integer not null
, k text not null
, v blob not null
, nonce text null
, primary key (o,k)
)
|]
class HasPredicate a where
predicate :: a -> SelectPredicate
class HasLimit a where
limit :: a -> Maybe QueryLimitClause
data ItemOrder = Direct | Reverse
class HasItemOrder a where
itemOrder :: a -> ItemOrder
itemOrder = const Direct
newtype Reversed a = Reversed a
instance HasItemOrder (Reversed a) where
itemOrder = const Reverse
-- TODO: move-to-db-pipe?
newtype QueryOffset = QueryOffset Word64
deriving newtype (Show,Eq,Ord,Num,Enum,Integral,Real,ToField,FromField,Pretty)
-- TODO: move-to-db-pipe?
newtype QueryLimit = QueryLimit Word64
deriving newtype (Show,Eq,Ord,Num,Enum,Integral,Real,ToField,FromField,Pretty)
type QueryLimitClause = (QueryOffset, QueryLimit)
instance HasLimit () where
limit _ = Nothing
data WithLimit q = WithLimit (Maybe QueryLimitClause) q
instance HasItemOrder q => HasItemOrder (WithLimit q) where
itemOrder (WithLimit _ q) = itemOrder q
instance HasItemOrder [Syntax c] where
itemOrder = const Direct
instance HasItemOrder () where
itemOrder = const Direct
instance HasPredicate q => HasPredicate (WithLimit q) where
predicate (WithLimit _ query) = predicate query
instance HasLimit (WithLimit a) where
limit (WithLimit l _) = l
instance HasPredicate q => HasPredicate (Reversed q) where
predicate (Reversed q) = predicate q
instance HasLimit q => HasLimit (Reversed q) where
limit (Reversed q) = limit q
data SelectPredicate =
All
| FixmeHashExactly Text
| AttrLike Text Text
| And SelectPredicate SelectPredicate
| Or SelectPredicate SelectPredicate
| Not SelectPredicate
| Ignored
deriving stock (Data,Generic,Show)
instance HasPredicate () where
predicate = const All
instance HasPredicate SelectPredicate where
predicate = id
instance IsContext c => HasPredicate [Syntax c] where
predicate s = goPred $ unlist $ go s
where
goPred :: Syntax c -> SelectPredicate
goPred = \case
ListVal [SymbolVal "not", a] -> Not (goPred a)
ListVal [SymbolVal "or", a, b] -> Or (goPred a) (goPred b)
ListVal [SymbolVal "and", a, b] -> And (goPred a) (goPred b)
ListVal [SymbolVal "like", StringLike a, StringLike b] -> AttrLike (Text.pack a) (Text.pack b)
_ -> Ignored
go :: [Syntax c] -> Syntax c
go = \case
( SymbolVal "!" : rest ) -> do
mkList [mkSym "not", unlist (go rest)]
( Operand a : SymbolVal "~" : Operand b : rest ) -> do
go (mkList [mkSym "like", mkStr a, mkStr b] : rest)
( w : SymbolVal "&&" : rest ) -> do
mkList [mkSym "and", unlist w, unlist (go rest)]
( w : SymbolVal "||" : rest ) -> do
mkList [mkSym "or", unlist w, unlist (go rest)]
w -> mkList w
unlist = \case
ListVal [x] -> x
x -> x
{- HLINT ignore "Functor law" -}
{- HLINT ignore "Eta reduce" -}
data Bound = forall a . (ToField a, Show a) => Bound a
instance ToField Bound where
toField (Bound x) = toField x
instance Show Bound where
show (Bound x) = show x
genPredQ :: Text -> SelectPredicate -> (Text, [Bound])
genPredQ tbl what = go what
where
go = \case
All -> ("true", mempty)
FixmeHashExactly x ->
([qc|(o.o = ?)|], [Bound x])
AttrLike name val -> do
let x = val <> "%"
let binds = [Bound x]
([qc|(json_extract({tbl}.blob, '$."{name}"') like ?)|], binds)
Not a -> do
let (sql, bound) = go a
([qc|(coalesce(not {sql},true))|], bound)
And a b -> do
let (asql, abound) = go a
let (bsql, bbound) = go b
([qc|{asql} and {bsql}|], abound <> bbound)
Or a b -> do
let asql = go a
let bsql = go b
([qc|{fst asql} or {fst bsql}|], snd asql <> snd bsql)
Ignored -> ("true", mempty)
cleanupDatabase :: (FixmePerks m, MonadReader FixmeEnv m) => m ()
cleanupDatabase = do
warn $ red "cleanupDatabase"
withState $ transactional do
update_ [qc|delete from object|]
update_ [qc|delete from scanned|]
scannedKey :: (FixmePerks m, MonadReader FixmeEnv m) => Fixme -> m HashRef
scannedKey fme = do
magic <- asks fixmeEnvScanMagic >>= readTVarIO
let file = fixmeAttr fme & HM.lookup "file"
let w = fixmeTs fme
pure $ hashObject @HbSync ( serialise (magic,w,file) ) & HashRef
scannedKeyForFile :: (FixmePerks m, MonadReader FixmeEnv m) => FilePath-> m HashRef
scannedKeyForFile file = do
dir <- fixmeWorkDir
magic <- asks fixmeEnvScanMagic >>= readTVarIO
let fn = dir </> file
w <- liftIO $ getModificationTime fn <&> round . utcTimeToPOSIXSeconds
pure $ hashObject @HbSync ( serialise (magic,w,file) ) & HashRef
selectIsAlreadyScannedFile :: (FixmePerks m, MonadReader FixmeEnv m) => FilePath -> m Bool
selectIsAlreadyScannedFile file = do
k <- scannedKeyForFile file
selectIsAlreadyScanned k
selectIsAlreadyScanned :: (FixmePerks m, MonadReader FixmeEnv m) => HashRef -> m Bool
selectIsAlreadyScanned k = withState do
what <- select @(Only Int) [qc|select 1 from scanned where hash = ? limit 1|] (Only k)
pure $ not $ List.null what
insertTree :: FixmePerks m => HashRef -> FixmeKey -> FixmeAttrName -> DBPipeM m ()
insertTree h o k = do
insert [qc| insert into tree (hash,o,k)
values (?,?,?)
on conflict (hash,o,k) do nothing
|] (h,o,k)
listAllScanned :: (FixmePerks m, MonadReader FixmeEnv m) => m (HashSet HashRef)
listAllScanned = withState do
select_ [qc|select hash from scanned|] <&> HS.fromList . fmap ( fromSomeHash . fromOnly )
insertScannedFile :: (FixmePerks m, MonadReader FixmeEnv m) => FilePath -> DBPipeM m ()
insertScannedFile file = do
k <- lift $ scannedKeyForFile file
insertScanned k
insertScanned:: (FixmePerks m) => HashRef -> DBPipeM m ()
insertScanned k = do
insert [qc| insert into scanned (hash)
values(?)
on conflict (hash) do nothing|]
(Only k)
selectFixmeKey :: (FixmePerks m, MonadReader FixmeEnv m) => Text -> m (Maybe FixmeKey)
selectFixmeKey s = do
withState do
select @(Only FixmeKey) [qc|select distinct(o) from object where o like ? order by w desc|] (Only (s<>"%"))
<&> fmap fromOnly
<&> headMay
sqliteToAeson :: FromJSON a => Text -> Maybe a
sqliteToAeson = Aeson.decode . LBS.fromStrict . Text.encodeUtf8
countFixme :: (FixmePerks m, MonadReader FixmeEnv m) => m Int
countFixme = do
let present = [qc|coalesce(json_extract(s1.blob, '$.deleted'),'false') <> 'true' |] :: String
let sql = [qc|
with s1 as (
select cast (json_insert(json_group_object(o.k, o.v), '$.w', max(o.w)) as text) as blob
from object o
group by o.o
)
select count(s1.blob) from s1
where
{present}
|]
debug $ pretty sql
withState $ select_ @_ @(Only Int) sql
<&> maybe 0 fromOnly . headMay
countByAttribute :: ( FixmePerks m
, MonadReader FixmeEnv m
)
=> FixmeAttrName
-> m [(FixmeAttrVal, Int)]
countByAttribute name = do
let sql = [qc|
select v, count(1) from object o
where not exists
( select null from object o1
where o1.o = o.o
and o1.k = 'deleted' and o1.v == 'true'
)
and o.k = ?
group by v
|]
withState $ select sql (Only name)
listFixme :: ( FixmePerks m
, MonadReader FixmeEnv m
, HasPredicate q
, HasLimit q
, HasItemOrder q
)
=> q
-> m [Fixme]
listFixme expr = do
let (w,bound) = genPredQ "s1" (predicate expr)
let present = [qc|and coalesce(json_extract(s1.blob, '$.deleted'),'false') <> 'true' |] :: String
let (limitClause, lbound) = case limit expr of
Just (o,l) -> ([qc|limit ? offset ?|] :: String, [Bound l, Bound o])
Nothing -> (mempty, [])
let o = case itemOrder expr of
Direct -> "asc" :: String
Reverse -> "desc"
let sql = [qc|
with s1 as (
select cast (json_insert(json_group_object(o.k, o.v), '$.fixme-timestamp', cast(max(o.w) as text)) as text) as blob
from object o
group by o.o
)
select s1.blob from s1
where
{w}
{present}
order by
json_extract(s1.blob, '$.commit-time') {o} nulls last,
json_extract(s1.blob, '$.w') {o} nulls last
{limitClause}
|]
debug $ pretty sql
withState $ select @(Only Text) sql (bound <> lbound)
<&> fmap (sqliteToAeson . fromOnly)
<&> catMaybes
getFixme :: (FixmePerks m, MonadReader FixmeEnv m) => FixmeKey -> m (Maybe Fixme)
getFixme key = do
let sql = [qc|
select cast (json_insert(json_group_object(o.k, o.v), '$.fixme-timestamp', cast(max(o.w) as text)) as text) as blob
from object o
where o.o = ?
group by o.o
limit 1
|]
runMaybeT do
lift (withState $ select @(Only Text) sql (Only key))
<&> fmap (sqliteToAeson . fromOnly)
<&> catMaybes
<&> headMay
>>= toMPlus
modifyFixme :: (FixmePerks m)
=> FixmeKey
-> [(FixmeAttrName, FixmeAttrVal)]
-> FixmeM m ()
modifyFixme o a' = do
FixmeEnv{..} <- ask
attrNames <- readTVarIO fixmeEnvAttribs
values <- readTVarIO fixmeEnvAttribValues
now <- liftIO getPOSIXTime <&> fromIntegral . round
let a = [ (k,v) | (k,v) <- a'
, k `HS.member` attrNames
, not (HM.member k values) || v `HS.member` fromMaybe mempty (HM.lookup k values)
]
let w = mempty { fixmeAttr = HM.fromList a, fixmeKey = o, fixmeTs = Just now }
withState $ insertFixme w
insertFixme :: (FixmePerks m, MonadReader FixmeEnv m) => Fixme -> DBPipeM m ()
insertFixme fme = do
void $ runMaybeT do
let o = fixmeKey fme
w <- fixmeTs fme & toMPlus
let attrs = fixmeAttr fme
let txt = fixmePlain fme & Text.unlines . fmap coerce
let sql = [qc|
insert into object (o, w, k, v)
values (?, ?, ?, ?)
on conflict (o, k)
do update set
v = case
when excluded.w > object.w and (excluded.v <> object.v) then excluded.v
else object.v
end,
w = case
when excluded.w > object.w and (excluded.v <> object.v) then excluded.w
else object.w
end,
nonce = case when excluded.w > object.w and (excluded.v <> object.v) then excluded.nonce
else object.nonce
end
|]
for_ (fixmeStart fme) $ \s -> do
lift $ insert sql (o,w,"fixme-start",s)
for_ (fixmeEnd fme) $ \s -> do
lift $ insert sql (o,w,"fixme-end",s)
for_ (HM.toList attrs) $ \(k,v) -> do
lift $ insert sql (o,w,k,v)
lift $ insert sql (o,w,"fixme-text",txt)
data FixmeExported =
FixmeExported
{ exportedKey :: FixmeKey
, exportedWeight :: Word64
, exportedName :: FixmeAttrName
, exportedValue :: FixmeAttrVal
}
deriving stock Generic
instance FromRow FixmeExported
instance ToRow FixmeExported
instance Serialise FixmeExported
class LocalNonce a where
localNonce :: a -> HashRef
instance LocalNonce FixmeExported where
localNonce FixmeExported{..} =
HashRef $ hashObject @HbSync
$ serialise (exportedKey,exportedName,exportedValue,exportedWeight)
instance LocalNonce (HashRef, FixmeExported) where
localNonce (h, e) = HashRef $ hashObject @HbSync
$ serialise (h, localNonce e)
data WithNonce a = WithNonce HashRef a
instance ToRow (WithNonce FixmeExported) where
toRow (WithNonce nonce f@FixmeExported{..}) = toRow (exportedKey, exportedWeight, exportedName, exportedValue, nonce)
insertFixmeExported :: FixmePerks m => HashRef -> FixmeExported -> DBPipeM m ()
insertFixmeExported h item = do
let sql = [qc|
insert into object (o, w, k, v, nonce)
values (?, ?, ?, ?, ?)
on conflict (o, k)
do update set
v = case
when excluded.w > object.w then excluded.v
else object.v
end,
w = case
when excluded.w > object.w then excluded.w
else object.w
end,
nonce = case
when excluded.w > object.w then excluded.nonce
else object.nonce
end
|]
insert sql (WithNonce h item)
insertScanned h

View File

@ -0,0 +1,759 @@
{-# LANGUAGE PatternSynonyms, ViewPatterns, TemplateHaskell #-}
{-# LANGUAGE UndecidableInstances #-}
{-# OPTIONS_GHC -fno-warn-orphans #-}
module Fixme.Types
( module Fixme.Types
, module Exported
) where
import Fixme.Prelude hiding (align)
import HBS2.Base58
import DBPipe.SQLite hiding (field)
import HBS2.Git.Local
import HBS2.OrDie
import HBS2.System.Dir
import HBS2.Storage as Exported
import HBS2.Peer.CLI.Detect
import HBS2.Peer.RPC.Client as Exported hiding (encode,decode)
import HBS2.Peer.RPC.Client.Unix as Exported hiding (encode,decode)
import HBS2.Peer.RPC.API.Peer as Exported
import HBS2.Peer.RPC.API.RefChan as Exported
import HBS2.Peer.RPC.API.Storage as Exported
import HBS2.Peer.RPC.Client.StorageClient as Exported
import Data.Config.Suckless
import Prettyprinter.Render.Terminal
import Control.Applicative
import Data.Aeson as Aeson
import Data.Aeson.KeyMap as Aeson hiding (null)
import Data.Aeson.Key qualified as Aeson
import Data.Aeson.Types as Aeson
import Data.ByteString (ByteString)
import Data.ByteString.Char8 qualified as BS8
import Data.ByteString.Lazy qualified as LBS
import Data.HashMap.Strict (HashMap)
import Data.HashMap.Strict qualified as HM
import Data.HashSet (HashSet)
import Data.HashSet qualified as HS
import Data.Word (Word64,Word32)
import Data.Maybe
import Data.Coerce
import Data.Text qualified as Text
import Data.List qualified as List
import Data.Map qualified as Map
import System.FilePath
import Text.InterpolatedString.Perl6 (qc)
import Data.Generics.Product.Fields (field)
import Lens.Micro.Platform
data MyPeerClientEndpoints =
MyPeerClientEndpoints
{ _peerSocket :: FilePath
, _peerPeerAPI :: ServiceCaller PeerAPI UNIX
, _peerRefChanAPI :: ServiceCaller RefChanAPI UNIX
, _peerStorageAPI :: ServiceCaller StorageAPI UNIX
}
makeLenses 'MyPeerClientEndpoints
-- FIXME: move-to-suckless-conf
pattern FixmeHashLike :: forall {c} . Text -> Syntax c
pattern FixmeHashLike e <- (fixmeHashFromSyn -> Just e)
pattern TimeStampLike :: forall {c} . FixmeTimestamp -> Syntax c
pattern TimeStampLike e <- (tsFromFromSyn -> Just e)
instance MkId FixmeAttrName where
mkId (k :: FixmeAttrName) = Id ("$" <> coerce k)
fixmeHashFromSyn :: Syntax c -> Maybe Text
fixmeHashFromSyn = \case
StringLike s -> do
let (_,value) = span (`elem` "#%~:") s
Just $ Text.pack value
_ -> Nothing
tsFromFromSyn :: Syntax c -> Maybe FixmeTimestamp
tsFromFromSyn = \case
LitIntVal n -> Just (fromIntegral n)
_ -> Nothing
newtype FixmeTag = FixmeTag { fromFixmeTag :: Text }
deriving newtype (Eq,Ord,Show,IsString,Hashable,Semigroup,Monoid,ToField,FromField,FromJSON,ToJSON)
deriving stock (Data,Generic)
newtype FixmeTitle = FixmeTitle { fromFixmeTitle :: Text }
deriving newtype (Eq,Ord,Show,IsString,Semigroup,Monoid,ToField,FromField,Hashable,FromJSON,ToJSON)
deriving stock (Data,Generic)
newtype FixmePlainLine = FixmePlainLine { fromFixmeText :: Text }
deriving newtype (Eq,Ord,Show,IsString,Semigroup,Monoid,ToField,FromField,FromJSON,ToJSON)
deriving stock (Data,Generic)
newtype FixmeAttrName = FixmeAttrName { fromFixmeAttrName :: Text }
deriving newtype (Eq,Ord,Show,IsString,Hashable)
deriving newtype (ToField,FromField)
deriving newtype (ToJSON,FromJSON,ToJSONKey,FromJSONKey)
deriving stock (Data,Generic)
newtype FixmeAttrVal = FixmeAttrVal { fromFixmeAttrVal :: Text }
deriving newtype (Eq,Ord,Show,IsString,Hashable,ToField,FromField,ToJSON,FromJSON,Semigroup,Monoid)
deriving stock (Data,Generic)
newtype FixmeTimestamp = FixmeTimestamp Word64
deriving newtype (Eq,Ord,Show,Enum,Num,Integral,Real,ToField,FromField,ToJSON)
deriving stock (Data,Generic)
newtype FixmeKey = FixmeKey Text
deriving newtype (Eq,Ord,Show,ToField,FromField,Pretty,FromJSON,ToJSON,Semigroup,Monoid,IsString)
deriving stock (Data,Generic)
newtype FixmeOffset = FixmeOffset Word32
deriving newtype (Eq,Ord,Show,Num,ToField,FromField,ToJSON)
deriving newtype (Integral,Real,Enum)
deriving stock (Data,Generic)
instance FromStringMaybe FixmeKey where
fromStringMay s = pure (fromString s)
data Fixme =
Fixme
{ fixmeTag :: FixmeTag
, fixmeTitle :: FixmeTitle
, fixmeKey :: FixmeKey
, fixmeTs :: Maybe FixmeTimestamp
, fixmeStart :: Maybe FixmeOffset
, fixmeEnd :: Maybe FixmeOffset
, fixmePlain :: [FixmePlainLine]
, fixmeAttr :: HashMap FixmeAttrName FixmeAttrVal
}
deriving stock (Ord,Eq,Show,Data,Generic)
instance Monoid Fixme where
mempty = Fixme mempty mempty mempty Nothing Nothing Nothing mempty mempty
instance Semigroup Fixme where
(<>) a b = b { fixmeTs = fixmeTs b <|> fixmeTs a
, fixmeTitle = fixmeAttrNonEmpty (fixmeTitle a) (fixmeTitle b)
, fixmeTag = fixmeAttrNonEmpty (fixmeTag a) (fixmeTag b)
, fixmeStart = fixmeStart b <|> fixmeStart a
, fixmeEnd = fixmeEnd b <|> fixmeEnd a
, fixmePlain = fixmePlain b
, fixmeAttr = fixmeAttr a <> fixmeAttr b
}
fixmeGet :: FixmeAttrName -> Fixme -> Maybe FixmeAttrVal
fixmeGet name Fixme{..} = HM.lookup name fixmeAttr
fixmeSet :: FixmeAttrName -> FixmeAttrVal -> Fixme -> Fixme
fixmeSet name val fx = fx { fixmeAttr = HM.insert name val (fixmeAttr fx) }
instance FromJSON FixmeOffset where
parseJSON = \case
Number x -> pure (FixmeOffset (ceiling x))
String s -> do
n <- maybe (fail "invalid FixmeOffset value") pure (readMay (Text.unpack s))
pure $ FixmeOffset n
_ -> fail "invalid FixmeOffset value"
instance FromJSON FixmeTimestamp where
parseJSON = \case
Number x -> pure (FixmeTimestamp (ceiling x))
String s -> do
n <- maybe (fail "invalid FixmeOffset value") pure (readMay (Text.unpack s))
pure $ FixmeTimestamp n
_ -> fail "invalid FixmeTimestamp value"
instance FromJSON Fixme where
parseJSON = withObject "Fixme" $ \o -> do
fixmeKey <- o .: "fixme-key"
fixmeTag <- o .: "fixme-tag"
fixmeTitle <- o .: "fixme-title"
fixmeStart <- o .:? "fixme-start"
fixmeEnd <- o .:? "fixme-end"
fixmeTs <- o .:? "fixme-timestamp"
fixmePlainTxt <- o .:? "fixme-text" <&> fromMaybe mempty
let fixmePlain = fmap FixmePlainLine (Text.lines fixmePlainTxt)
let wtf = [ unpackItem k v
| (k,v) <- Aeson.toList o
, k /= "fixme-text"
] & catMaybes
let fixmeAttr = HM.fromList wtf
return Fixme{..}
where
unpackItem k v = do
(FixmeAttrName (Aeson.toText k),) <$>
case v of
String x -> pure (FixmeAttrVal x)
Number x -> pure (FixmeAttrVal (Text.pack $ show x))
_ -> Nothing
newtype FixmeThin = FixmeThin (HashMap FixmeAttrName FixmeAttrVal)
deriving newtype (Semigroup,Monoid,Eq,Ord,Show,ToJSON,FromJSON)
deriving stock (Data,Generic)
type FixmePerks m = ( MonadUnliftIO m
, MonadIO m
)
data UpdateAction = forall c . IsContext c => UpdateAction { runUpdateAction :: Syntax c -> IO () }
data ReadLogAction = forall c . IsContext c => ReadLogAction { runReadLog :: Syntax c -> IO () }
-- FIXME: fucking-context-hardcode-wtf-1
data CatAction = CatAction { catAction :: [(Id, Syntax C)] -> LBS.ByteString -> IO () }
data SimpleTemplate = forall c . (IsContext c, Data (Context c), Data c) => SimpleTemplate [Syntax c]
class HasSequence w where
getSequence :: w -> Word64
newtype FromFixmeKey a = FromFixmeKey a
data CompactAction =
Deleted Word64 HashRef
| Modified Word64 HashRef FixmeAttrName FixmeAttrVal
| Added Word64 Fixme
deriving stock (Eq,Ord,Show,Generic)
class MkKey a where
mkKey :: a -> ByteString
instance MkKey CompactAction where
mkKey (Deleted _ h) = "D" <> LBS.toStrict (serialise h)
mkKey (Modified _ h _ _) = "M" <> LBS.toStrict (serialise h)
mkKey (Added _ fixme) = "A" <> coerce (hashObject @HbSync $ serialise fixme)
instance MkKey (FromFixmeKey Fixme) where
mkKey (FromFixmeKey fx@Fixme{..}) =
maybe k2 (mappend "A" . LBS.toStrict . serialise) (HM.lookup "fixme-key" fixmeAttr)
where k2 = mappend "A" $ serialise fx & LBS.toStrict
instance IsContext c => MkStr c GitHash where
mkStr ha = mkStr (show $ pretty ha)
instance IsContext c => MkStr c GitRef where
mkStr ha = mkStr (show $ pretty ha)
instance IsContext c => MkStr c HashRef where
mkStr ha = mkStr (show $ pretty ha)
instance IsContext c => MkStr c FixmeAttrVal where
mkStr v = mkStr (coerce @_ @Text v)
instance IsContext c => MkStr c (AsBase58 ByteString) where
mkStr v = mkStr (show $ pretty v)
instance IsContext c => MkStr c FixmeAttrName where
mkStr v = mkStr (coerce @_ @Text v)
instance Pretty CompactAction where
pretty = \case
Deleted s r -> pretty $ mkList @C [ mkSym "deleted", mkInt s, mkStr r ]
Modified s r k v -> pretty $ mkList @C [ mkSym "modified", mkInt s, mkStr r, mkStr k, mkStr v ]
-- FIXME: normal-pretty-instance
e@(Added w fx) -> do
pretty $ mkList @C [ mkSym "added", mkStr (AsBase58 $ mkKey e) ]
instance Serialise CompactAction
pattern CompactActionSeq :: Word64 -> CompactAction
pattern CompactActionSeq s <- (seqOf -> Just s)
{-# COMPLETE CompactActionSeq #-}
seqOf :: CompactAction -> Maybe Word64
seqOf = \case
Deleted w _ -> Just w
Modified w _ _ _ -> Just w
Added w _ -> Just w
instance HasSequence CompactAction where
getSequence x = fromMaybe 0 (seqOf x)
data FixmeTemplate =
Simple SimpleTemplate
data RenderError = RenderError String
deriving stock (Eq,Show,Typeable)
class FixmeRenderTemplate a b where
render :: a -> Either RenderError b
data FixmeOpts =
FixmeOpts
{ fixmeOptNoEvolve :: Bool
}
deriving stock (Eq,Ord,Show,Data,Generic)
instance Monoid FixmeOpts where
mempty = FixmeOpts False
instance Semigroup FixmeOpts where
(<>) _ b = FixmeOpts (fixmeOptNoEvolve b)
data PeerNotConnected = PeerNotConnected
deriving (Show,Typeable)
instance Exception PeerNotConnected
data FixmeFlags =
FixmeIgnoreCached
deriving stock (Eq,Ord,Enum,Show,Generic)
instance Hashable FixmeFlags
-- hashWithSalt s e = undefined
data FixmeEnv =
FixmeEnv
{ fixmeLock :: MVar ()
, fixmeEnvOpts :: TVar FixmeOpts
, fixmeEnvWorkDir :: TVar FilePath
, fixmeEnvDb :: TVar (Maybe DBPipeEnv)
, fixmeEnvGitDir :: TVar (Maybe FilePath)
, fixmeEnvFileMask :: TVar [FilePattern]
, fixmeEnvFileExclude :: TVar [FilePattern]
, fixmeEnvTags :: TVar (HashSet FixmeTag)
, fixmeEnvAttribs :: TVar (HashSet FixmeAttrName)
, fixmeEnvAttribValues :: TVar (HashMap FixmeAttrName (HashSet FixmeAttrVal))
, fixmeEnvDefComments :: TVar (HashSet Text)
, fixmeEnvFileComments :: TVar (HashMap FilePath (HashSet Text))
, fixmeEnvGitScanDays :: TVar (Maybe Integer)
, fixmeEnvScanMagic :: TVar (Maybe HashRef)
, fixmeEnvUpdateActions :: TVar [UpdateAction]
, fixmeEnvReadLogActions :: TVar [ReadLogAction]
, fixmeEnvCatAction :: TVar CatAction
, fixmeEnvTemplates :: TVar (HashMap Id FixmeTemplate)
, fixmeEnvMacro :: TVar (HashMap Id (Syntax C))
, fixmeEnvCatContext :: TVar (Int,Int)
, fixmeEnvMyEndpoints :: TVar (Maybe MyPeerClientEndpoints)
, fixmeEnvRefChan :: TVar (Maybe (PubKey 'Sign 'HBS2Basic))
, fixmeEnvAuthor :: TVar (Maybe (PubKey 'Sign 'HBS2Basic))
, fixmeEnvReader :: TVar (Maybe (PubKey 'Encrypt 'HBS2Basic))
, fixmeEnvFlags :: TVar (HashSet FixmeFlags)
}
deriving stock (Generic)
fixmeGetCommentsFor :: FixmePerks m => Maybe FilePath -> FixmeM m [Text]
fixmeGetCommentsFor Nothing = do
asks fixmeEnvDefComments >>= readTVarIO
<&> HS.toList
fixmeGetCommentsFor (Just fp) = do
cof <- asks fixmeEnvFileComments >>= readTVarIO
def <- asks fixmeEnvDefComments >>= readTVarIO
let r = maybe mempty HS.toList (HM.lookup (commentKey fp) cof)
<> HS.toList def
pure r
{- HLINT ignore "Functor law" -}
fixmeGetGitDirCLIOpt :: (FixmePerks m, MonadReader FixmeEnv m) => m String
fixmeGetGitDirCLIOpt = do
asks fixmeEnvGitDir
>>= readTVarIO
<&> fmap (\d -> [qc|--git-dir {d}|])
<&> fromMaybe ""
builtinAttribs :: HashSet FixmeAttrName
builtinAttribs = HS.singleton "deleted"
builtinAttribVals :: HashMap FixmeAttrName (HashSet FixmeAttrVal)
builtinAttribVals = HM.fromList [("deleted", HS.fromList ["true","false"])]
newtype FixmeM m a = FixmeM { fromFixmeM :: ReaderT FixmeEnv m a }
deriving newtype ( Applicative
, Functor
, Monad
, MonadIO
, MonadUnliftIO
, MonadReader FixmeEnv
)
fixmeEnvBare :: forall m . FixmePerks m => m FixmeEnv
fixmeEnvBare = do
FixmeEnv
<$> newMVar ()
<*> newTVarIO mempty
<*> (pwd >>= newTVarIO)
<*> newTVarIO Nothing
<*> newTVarIO Nothing
<*> newTVarIO mempty
<*> newTVarIO mempty
<*> newTVarIO mempty
<*> newTVarIO builtinAttribs
<*> newTVarIO builtinAttribVals
<*> newTVarIO mempty
<*> newTVarIO defCommentMap
<*> newTVarIO Nothing
<*> newTVarIO mzero
<*> newTVarIO mempty
<*> newTVarIO mempty
<*> newTVarIO (CatAction $ \_ _ -> pure ())
<*> newTVarIO mempty
<*> newTVarIO mempty
<*> newTVarIO (1,3)
<*> newTVarIO mzero
<*> newTVarIO mzero
<*> newTVarIO mzero
<*> newTVarIO mzero
<*> newTVarIO mempty
withFixmeEnv :: FixmePerks m => FixmeEnv -> FixmeM m a -> m a
withFixmeEnv env what = runReaderT ( fromFixmeM what) env
instance Serialise FixmeTag
instance Serialise FixmeTitle
instance Serialise FixmePlainLine
instance Serialise FixmeAttrName
instance Serialise FixmeAttrVal
instance Serialise FixmeTimestamp
instance Serialise FixmeOffset
instance Serialise FixmeKey
instance Serialise Fixme
instance (FixmePerks m, MonadReader FixmeEnv m) => HasClientAPI PeerAPI UNIX m where
getClientAPI = getApiOrThrow peerPeerAPI
instance (FixmePerks m, MonadReader FixmeEnv m) => HasClientAPI RefChanAPI UNIX m where
getClientAPI = getApiOrThrow peerRefChanAPI
instance (FixmePerks m, MonadReader FixmeEnv m) => HasClientAPI StorageAPI UNIX m where
getClientAPI = getApiOrThrow peerStorageAPI
instance (FixmePerks m) => HasStorage (FixmeM m) where
getStorage = do
api <- getClientAPI @StorageAPI @UNIX
pure $ AnyStorage (StorageClient api)
getApiOrThrow :: (MonadReader FixmeEnv m, MonadIO m)
=> Getting b MyPeerClientEndpoints b -> m b
getApiOrThrow getter =
asks fixmeEnvMyEndpoints
>>= readTVarIO
>>= orThrow PeerNotConnected
<&> view getter
instance ToField GitHash where
toField h = toField (show $ pretty h)
instance ToField GitRef where
toField h = toField (show $ pretty h)
instance FromField GitRef where
fromField = fmap fromString . fromField @String
instance FromField GitHash where
fromField = fmap fromString . fromField @String
instance Pretty FixmeTimestamp where
pretty = pretty . coerce @_ @Word64
instance Pretty FixmeOffset where
pretty = pretty . coerce @_ @Word32
instance Pretty FixmeAttrName where
pretty = pretty . coerce @_ @Text
instance Pretty FixmeAttrVal where
pretty = pretty . coerce @_ @Text
instance Pretty FixmeTitle where
pretty = pretty . coerce @_ @Text
instance Pretty FixmeTag where
pretty = pretty . coerce @_ @Text
instance Pretty FixmePlainLine where
pretty = pretty . coerce @_ @Text
instance Pretty Fixme where
pretty Fixme{..} =
pretty fixmeTag <+> pretty fixmeTitle
<> fstart
<> fend
<> la
<> lls
<> line
where
fstart = case fixmeStart of
Just s -> line <> pretty ([qc| $fixme-start: {show $ pretty s}|] :: String)
Nothing -> mempty
fend = case fixmeEnd of
Just s -> line <> pretty ([qc| $fixme-end: {show $ pretty s}|] :: String)
Nothing -> mempty
la | not (HM.null fixmeAttr) = do
let a = HM.toList fixmeAttr
let ss = [ [qc| ${show $ pretty n}: {show $ pretty v}|] | (n,v) <- a ] :: [String]
line <> vcat ( fmap pretty ss ) <> line
| otherwise = mempty
lls | not (null fixmePlain) = line <> vcat (fmap pretty fixmePlain)
| otherwise = mempty
defCommentMap :: HashMap FilePath (HashSet Text)
defCommentMap = HM.fromList
[ comment ".cabal" ["--"]
, comment ".hs" ["--"]
, comment ".c" ["//"]
, comment ".h" ["//"]
, comment ".cc" ["//"]
, comment ".cpp" ["//"]
, comment ".cxx" ["//"]
, comment "Makefile" ["#"]
]
where
comment a b = (a, HS.fromList b)
commentKey :: FilePath -> FilePath
commentKey fp =
case takeExtension fp of
"" -> takeFileName fp
xs -> xs
type ContextShit c = (Data c, Data (Context c), IsContext c, Data (Syntax c))
cc0 :: forall c . ContextShit c => Context c
cc0 = noContext :: Context c
inject :: forall c a . (ContextShit c, Data a) => [(Id,Syntax c)] -> a -> a
inject repl target =
flip transformBi target $ \case
(SymbolVal x) | issubst x -> fromMaybe mt (Map.lookup x rmap)
other -> other
where
mt = Literal (noContext @c) (LitStr "")
rmap = Map.fromList repl
issubst (Id x) = Text.isPrefixOf "$" x
pattern NL :: forall {c}. Syntax c
pattern NL <- ListVal [SymbolVal "nl"]
instance FixmeRenderTemplate SimpleTemplate (Doc AnsiStyle) where
render (SimpleTemplate syn) = Right $ mconcat $
flip fix (mempty,syn) $ \next -> \case
(acc, NL : rest) -> next (acc <> nl, rest)
(acc, ListVal [StringLike w] : rest) -> next (acc <> txt w, rest)
(acc, StringLike w : rest) -> next (acc <> txt w, rest)
(acc, ListVal [SymbolVal "trim", LitIntVal n, e] : rest) -> next (acc <> trim n (deep' [e]), rest)
(acc, ListVal [SymbolVal "align", LitIntVal n, e] : rest) -> next (acc <> align n (deep' [e]), rest)
(acc, ListVal [SymbolVal "fg", SymbolVal co, e] : rest) -> next (acc <> fmap (fg_ (color_ co)) (deep [e]), rest)
(acc, ListVal [SymbolVal "bg", SymbolVal co, e] : rest) -> next (acc <> fmap (bg_ (color_ co)) (deep [e]), rest)
(acc, ListVal [SymbolVal "fgd", SymbolVal co, e] : rest) -> next (acc <> fmap (fgd_ (color_ co)) (deep [e]), rest)
(acc, ListVal [SymbolVal "bgd", SymbolVal co, e] : rest) -> next (acc <> fmap (bgd_ (color_ co)) (deep [e]), rest)
(acc, ListVal [ SymbolVal "if", cond
, ListVal (SymbolVal "then" : then_)
, ListVal (SymbolVal "else" : else_)
] : rest) -> do
let r = case cond of
ListVal [SymbolVal "~", StringLike p, evaluated -> Just x] ->
Text.isPrefixOf (Text.pack p) x
_ -> False
next (acc <> if r then deep then_ else deep else_, rest)
(acc, ListVal es : rest) -> next (acc <> deep es, rest)
(acc, e : rest) -> next (acc <> p e, rest)
(acc, []) -> acc
where
evaluated :: (ContextShit c) => Syntax c -> Maybe Text
evaluated what = Just (deep' [what] & Text.concat)
color_ = \case
"black" -> Just Black
"red" -> Just Red
"green" -> Just Green
"yellow" -> Just Yellow
"blue" -> Just Blue
"magenta" -> Just Magenta
"cyan" -> Just Cyan
"white" -> Just White
_ -> Nothing
fg_ = maybe id (annotate . color)
bg_ = maybe id (annotate . bgColor)
fgd_ = maybe id (annotate . colorDull)
bgd_ = maybe id (annotate . bgColorDull)
untxt = fmap pretty
align n0 s0 | n > 0 = untxt [Text.justifyLeft n ' ' s]
| otherwise = untxt [Text.justifyRight (abs n) ' ' s]
where
n = fromIntegral n0
s = mconcat s0
trim n0 s0 | n >= 0 = untxt [ Text.take n s ]
| otherwise = untxt [ Text.takeEnd (abs n) s ]
where
n = fromIntegral n0
s = mconcat s0
deep :: forall c . (ContextShit c) => [Syntax c] -> [Doc AnsiStyle]
deep sy = either mempty List.singleton (render (SimpleTemplate sy))
deep' :: forall c . (ContextShit c) => [Syntax c] -> [Text]
deep' sy = do
let what = deep sy
[ Text.pack (show x) | x <- what]
nl = [ line ]
txt s = [fromString s]
p e = untxt [Text.pack (show $ pretty e)]
instance FixmeRenderTemplate SimpleTemplate Text where
render (SimpleTemplate syn) = Right $ Text.concat $
flip fix (mempty,syn) $ \next -> \case
(acc, NL : rest) -> next (acc <> nl, rest)
(acc, ListVal [StringLike w] : rest) -> next (acc <> txt w, rest)
(acc, StringLike w : rest) -> next (acc <> txt w, rest)
(acc, ListVal [SymbolVal "trim", LitIntVal n, e] : rest) -> next (acc <> trim n (deep [e]), rest)
(acc, ListVal [SymbolVal "align", LitIntVal n, e] : rest) -> next (acc <> align n (deep [e]), rest)
(acc, ListVal es : rest) -> next (acc <> deep es, rest)
(acc, e : rest) -> next (acc <> p e, rest)
(acc, []) -> acc
where
align n0 s0 | n > 0 = [Text.justifyLeft n ' ' s]
| otherwise = [Text.justifyRight (abs n) ' ' s]
where
n = fromIntegral n0
s = mconcat s0
trim n0 s0 | n >= 0 = [ Text.take n s ]
| otherwise = [ Text.takeEnd (abs n) s ]
where
n = fromIntegral n0
s = mconcat s0
deep :: forall c . (ContextShit c) => [Syntax c] -> [Text]
deep sy = either mempty List.singleton (render (SimpleTemplate sy))
nl = [ "\n" ]
txt s = [fromString s]
p e = [Text.pack (show $ pretty e)]
newtype ViaSerialise a = ViaSerialise a
instance Serialise a => Hashed HbSync (ViaSerialise a) where
hashObject (ViaSerialise x) = hashObject (serialise x)
fixmeTitleNonEmpty :: FixmeTitle -> FixmeTitle -> FixmeTitle
fixmeTitleNonEmpty a b = case (coerce a :: Text, coerce b :: Text) of
(x,y) | Text.null x && not (Text.null y) -> FixmeTitle y
(x,y) | not (Text.null x) && Text.null y -> FixmeTitle x
(_,y) -> FixmeTitle y
fixmeAttrNonEmpty :: Coercible a Text => a -> a -> a
fixmeAttrNonEmpty a b = case (coerce a :: Text, coerce b :: Text) of
(x,y) | Text.null x && not (Text.null y) -> b
(x,y) | not (Text.null x) && Text.null y -> a
(_,_) -> b
fixmeDerivedFields :: Fixme -> Fixme
fixmeDerivedFields fx = do
-- TODO: refactor-this-out
-- чревато ошибками, надо как-то переписать
-- по-человечески.
fxEnd
<> fx
<> fxKey
<> fxCo
<> tag
<> fxLno
<> fxTs
-- always last
<> fxMisc
where
email = HM.lookup "commiter-email" (fixmeAttr fx)
& maybe mempty (\x -> " <" <> x <> ">")
comitter = HM.lookup "commiter-name" (fixmeAttr fx)
<&> (<> email)
tag = mempty { fixmeAttr = HM.singleton "fixme-tag" (FixmeAttrVal (coerce $ fixmeTag fx)) }
key = HM.singleton "fixme-key" (FixmeAttrVal $ coerce $ (fixmeKey fx))
fxKey = mempty { fixmeAttr = key }
lno = succ <$> fixmeStart fx <&> FixmeAttrVal . fromString . show
fxLno = mempty { fixmeAttr = maybe mempty (HM.singleton "line") lno }
fxE = join $ for (fixmeStart fx) $ \n -> do
Just $ FixmeOffset $ fromIntegral $ fromIntegral n + length (fixmePlain fx)
fxEnd = mempty { fixmeEnd = fxE }
fxCo =
maybe mempty (\c -> mempty { fixmeAttr = HM.singleton "committer" c }) comitter
fxTs =
maybe mempty (\c -> mempty { fixmeAttr = HM.singleton "fixme-timestamp" (fromString (show c)) }) (fixmeTs fx)
fxMisc =
fx & over (field @"fixmeAttr")
(HM.insert "fixme-title" (FixmeAttrVal (coerce (fixmeTitle fx))))
mkFixmeFileName :: FilePath -> Fixme
mkFixmeFileName fp =
mempty { fixmeAttr = HM.singleton "file" (FixmeAttrVal (fromString fp)) }

View File

@ -0,0 +1 @@
fixme-new manual

View File

@ -1,130 +1,15 @@
{
"nodes": {
"db-pipe": {
"inputs": {
"haskell-flake-utils": "haskell-flake-utils",
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1708680396,
"narHash": "sha256-ZPwDreNdnyCS/hNdaE0OqVhytm+SzZGRfGRTRvBuSzE=",
"ref": "refs/heads/master",
"rev": "221fde04a00a9c38d2f6c0d05b1e1c3457d5a827",
"revCount": 7,
"type": "git",
"url": "https://git.hbs2.net/5xrwbTzzweS9yeJQnrrUY9gQJfhJf84pbyHhF2MMmSft"
},
"original": {
"type": "git",
"url": "https://git.hbs2.net/5xrwbTzzweS9yeJQnrrUY9gQJfhJf84pbyHhF2MMmSft"
}
},
"fixme": {
"inputs": {
"haskell-flake-utils": "haskell-flake-utils_2",
"nixpkgs": [
"nixpkgs"
],
"suckless-conf": "suckless-conf"
},
"locked": {
"lastModified": 1697356303,
"narHash": "sha256-hJbJZtx7gdcXaKL8n5J8b/eVyoYe9VxM+037ZK7q8Gw=",
"ref": "refs/heads/master",
"rev": "e9b1dcfd78dc766a2255a8125c14b24f0d728c0e",
"revCount": 139,
"type": "git",
"url": "https://git.hbs2.net/Fujv1Uy4W5d9Z7REEArMxbXSJ8nLLn4dYuvaAs8b86hr"
},
"original": {
"type": "git",
"url": "https://git.hbs2.net/Fujv1Uy4W5d9Z7REEArMxbXSJ8nLLn4dYuvaAs8b86hr"
}
},
"flake-utils": {
"inputs": {
"systems": "systems"
},
"locked": {
"lastModified": 1644229661,
"narHash": "sha256-1YdnJAsNy69bpcjuoKdOYQX0YxZBiCYZo4Twxerqv7k=",
"lastModified": 1726560853,
"narHash": "sha256-X6rJYSESBVr3hBoH0WbKE5KvhPU5bloyZ2L4K60/fPQ=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "3cecb5b042f7f209c56ffd8371b2711a290ec797",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"flake-utils_2": {
"locked": {
"lastModified": 1644229661,
"narHash": "sha256-1YdnJAsNy69bpcjuoKdOYQX0YxZBiCYZo4Twxerqv7k=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "3cecb5b042f7f209c56ffd8371b2711a290ec797",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"flake-utils_3": {
"locked": {
"lastModified": 1644229661,
"narHash": "sha256-1YdnJAsNy69bpcjuoKdOYQX0YxZBiCYZo4Twxerqv7k=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "3cecb5b042f7f209c56ffd8371b2711a290ec797",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"flake-utils_4": {
"locked": {
"lastModified": 1644229661,
"narHash": "sha256-1YdnJAsNy69bpcjuoKdOYQX0YxZBiCYZo4Twxerqv7k=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "3cecb5b042f7f209c56ffd8371b2711a290ec797",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"flake-utils_5": {
"locked": {
"lastModified": 1644229661,
"narHash": "sha256-1YdnJAsNy69bpcjuoKdOYQX0YxZBiCYZo4Twxerqv7k=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "3cecb5b042f7f209c56ffd8371b2711a290ec797",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"flake-utils_6": {
"locked": {
"lastModified": 1644229661,
"narHash": "sha256-1YdnJAsNy69bpcjuoKdOYQX0YxZBiCYZo4Twxerqv7k=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "3cecb5b042f7f209c56ffd8371b2711a290ec797",
"rev": "c1dfcf08411b08f6b8615f7d8971a2bfa81d5e8a",
"type": "github"
},
"original": {
@ -135,68 +20,16 @@
},
"haskell-flake-utils": {
"inputs": {
"flake-utils": "flake-utils"
"flake-utils": [
"flake-utils"
]
},
"locked": {
"lastModified": 1698938553,
"narHash": "sha256-oXpTKXioqFbl2mhhvpJIAvgNd+wYyv4ekI+YnJHEJ6s=",
"lastModified": 1707809372,
"narHash": "sha256-wfTL9PlCSOqSSyU4eenFFI7pHrV21gba4GEILnI4nAU=",
"owner": "ivanovs-4",
"repo": "haskell-flake-utils",
"rev": "19b273b5dc401a0a565e7f75cf50a593871b80c9",
"type": "github"
},
"original": {
"owner": "ivanovs-4",
"repo": "haskell-flake-utils",
"type": "github"
}
},
"haskell-flake-utils_2": {
"inputs": {
"flake-utils": "flake-utils_2"
},
"locked": {
"lastModified": 1672412555,
"narHash": "sha256-Kaa8F7nQFR3KuS6Y9WRUxeJeZlp6CCubyrRfmiEsW4k=",
"owner": "ivanovs-4",
"repo": "haskell-flake-utils",
"rev": "896219e5bde6efac72198550454e9dd9b5ed9ac9",
"type": "github"
},
"original": {
"owner": "ivanovs-4",
"repo": "haskell-flake-utils",
"type": "github"
}
},
"haskell-flake-utils_3": {
"inputs": {
"flake-utils": "flake-utils_3"
},
"locked": {
"lastModified": 1672412555,
"narHash": "sha256-Kaa8F7nQFR3KuS6Y9WRUxeJeZlp6CCubyrRfmiEsW4k=",
"owner": "ivanovs-4",
"repo": "haskell-flake-utils",
"rev": "896219e5bde6efac72198550454e9dd9b5ed9ac9",
"type": "github"
},
"original": {
"owner": "ivanovs-4",
"repo": "haskell-flake-utils",
"type": "github"
}
},
"haskell-flake-utils_4": {
"inputs": {
"flake-utils": "flake-utils_4"
},
"locked": {
"lastModified": 1698938553,
"narHash": "sha256-oXpTKXioqFbl2mhhvpJIAvgNd+wYyv4ekI+YnJHEJ6s=",
"owner": "ivanovs-4",
"repo": "haskell-flake-utils",
"rev": "19b273b5dc401a0a565e7f75cf50a593871b80c9",
"rev": "3cbdc5d6093e8b4464ae64097e0c8c61e4414ff2",
"type": "github"
},
"original": {
@ -206,46 +39,11 @@
"type": "github"
}
},
"haskell-flake-utils_5": {
"inputs": {
"flake-utils": "flake-utils_5"
},
"locked": {
"lastModified": 1672412555,
"narHash": "sha256-Kaa8F7nQFR3KuS6Y9WRUxeJeZlp6CCubyrRfmiEsW4k=",
"owner": "ivanovs-4",
"repo": "haskell-flake-utils",
"rev": "896219e5bde6efac72198550454e9dd9b5ed9ac9",
"type": "github"
},
"original": {
"owner": "ivanovs-4",
"repo": "haskell-flake-utils",
"rev": "896219e5bde6efac72198550454e9dd9b5ed9ac9",
"type": "github"
}
},
"haskell-flake-utils_6": {
"inputs": {
"flake-utils": "flake-utils_6"
},
"locked": {
"lastModified": 1672412555,
"narHash": "sha256-Kaa8F7nQFR3KuS6Y9WRUxeJeZlp6CCubyrRfmiEsW4k=",
"owner": "ivanovs-4",
"repo": "haskell-flake-utils",
"rev": "896219e5bde6efac72198550454e9dd9b5ed9ac9",
"type": "github"
},
"original": {
"owner": "ivanovs-4",
"repo": "haskell-flake-utils",
"type": "github"
}
},
"hspup": {
"inputs": {
"haskell-flake-utils": "haskell-flake-utils_5",
"haskell-flake-utils": [
"haskell-flake-utils"
],
"nixpkgs": [
"nixpkgs"
]
@ -266,11 +64,11 @@
},
"nixpkgs": {
"locked": {
"lastModified": 1707451808,
"narHash": "sha256-UwDBUNHNRsYKFJzyTMVMTF5qS4xeJlWoeyJf+6vvamU=",
"lastModified": 1727089097,
"narHash": "sha256-ZMHMThPsthhUREwDebXw7GX45bJnBCVbfnH1g5iuSPc=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "442d407992384ed9c0e6d352de75b69079904e4e",
"rev": "568bfef547c14ca438c56a0bece08b8bb2b71a9c",
"type": "github"
},
"original": {
@ -282,74 +80,26 @@
},
"root": {
"inputs": {
"db-pipe": "db-pipe",
"fixme": "fixme",
"haskell-flake-utils": "haskell-flake-utils_4",
"flake-utils": "flake-utils",
"haskell-flake-utils": "haskell-flake-utils",
"hspup": "hspup",
"nixpkgs": "nixpkgs",
"saltine": "saltine",
"suckless-conf": "suckless-conf_2"
"nixpkgs": "nixpkgs"
}
},
"saltine": {
"flake": false,
"systems": {
"locked": {
"lastModified": 1651348885,
"narHash": "sha256-0guvfkdOrofElDildQWE8QDwh+T/u2WY3HVYmOu4g3w=",
"owner": "tel",
"repo": "saltine",
"rev": "3d3a54cf46f78b71b4b55653482fb6f4cee6b77d",
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "tel",
"repo": "saltine",
"rev": "3d3a54cf46f78b71b4b55653482fb6f4cee6b77d",
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
},
"suckless-conf": {
"inputs": {
"haskell-flake-utils": "haskell-flake-utils_3",
"nixpkgs": [
"fixme",
"nixpkgs"
]
},
"locked": {
"lastModified": 1697354514,
"narHash": "sha256-5doedGj2QU4vPuw1VZor1GGEJTxu0zFeO/PsybFIcn8=",
"owner": "voidlizard",
"repo": "suckless-conf",
"rev": "3f87278bc10ac4f14a6d9d2c75cbbed228509129",
"type": "github"
},
"original": {
"owner": "voidlizard",
"repo": "suckless-conf",
"type": "github"
}
},
"suckless-conf_2": {
"inputs": {
"haskell-flake-utils": "haskell-flake-utils_6",
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1704001322,
"narHash": "sha256-D7T/8wAg5J4KkRw0uB90w3+adY11aQaX7rjmQPXkkQc=",
"ref": "refs/heads/master",
"rev": "8cfc1272bb79ef6ad62ae6a625f21e239916d196",
"revCount": 28,
"type": "git",
"url": "https://git.hbs2.net/JAuk1UJzZfbDGKVazSQU5yYQ3NGfk4gVeZzBCduf5TgQ"
},
"original": {
"type": "git",
"url": "https://git.hbs2.net/JAuk1UJzZfbDGKVazSQU5yYQ3NGfk4gVeZzBCduf5TgQ"
}
}
},
"root": "root",

214
flake.nix
View File

@ -5,69 +5,127 @@ inputs = {
nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable";
# haskell-flake-utils.url = "github:ivanovs-4/haskell-flake-utils";
haskell-flake-utils.url = "github:ivanovs-4/haskell-flake-utils/master";
flake-utils.url = "github:numtide/flake-utils";
haskell-flake-utils = { # we don't use haskell-flake-utils directly, but we override input evrywhere
url = "github:ivanovs-4/haskell-flake-utils/master";
inputs.flake-utils.follows = "flake-utils";
};
hspup.url = "github:voidlizard/hspup";
hspup.inputs.nixpkgs.follows = "nixpkgs";
fixme.url = "git+https://git.hbs2.net/Fujv1Uy4W5d9Z7REEArMxbXSJ8nLLn4dYuvaAs8b86hr";
fixme.inputs.nixpkgs.follows = "nixpkgs";
suckless-conf.url = "git+https://git.hbs2.net/JAuk1UJzZfbDGKVazSQU5yYQ3NGfk4gVeZzBCduf5TgQ";
suckless-conf.inputs.nixpkgs.follows = "nixpkgs";
db-pipe.url = "git+https://git.hbs2.net/5xrwbTzzweS9yeJQnrrUY9gQJfhJf84pbyHhF2MMmSft";
db-pipe.inputs.nixpkgs.follows = "nixpkgs";
saltine = {
url = "github:tel/saltine/3d3a54cf46f78b71b4b55653482fb6f4cee6b77d";
flake = false;
};
hspup.inputs.haskell-flake-utils.follows = "haskell-flake-utils";
};
outputs = { self, nixpkgs, haskell-flake-utils, ... }@inputs:
outputs = { self, nixpkgs, flake-utils, ... }@inputs:
let
packageNames = [
packageNames =
topLevelPackages ++ keymanPackages;
keymanPackages =
[
"hbs2-keyman"
"hbs2-keyman-direct-lib"
];
topLevelPackages =
[
"hbs2"
"hbs2-peer"
"hbs2-core"
"hbs2-storage-simple"
"hbs2-git"
"hbs2-qblf"
"hbs2-keyman"
"hbs2-share"
"hbs2-fixer"
"hbs2-storage-ncq"
"hbs2-git3"
"hbs2-cli"
"hbs2-sync"
"hbs2-log-structured"
"fixme-new"
"suckless-conf"
];
miscellaneous =
[
"bytestring-mmap"
"db-pipe"
"fuzzy-parse"
"suckless-conf"
];
jailbreakUnbreak = pkgs: pkg:
pkgs.haskell.lib.doJailbreak (pkg.overrideAttrs (_: { meta = { }; }));
# gitHbs2Script = pkgs.stdenv.mkDerivation {
# pname = "git-hbs2";
# version = "1.0";
# src = ./hbs2-git3/bf6;
# installPhase = ''
# mkdir -p $out/bin
# install -m755 git-hbs2 $out/bin/git-hbs2
# '';
# };
hpOverridesPre = pkgs: new: old: with pkgs.haskell.lib; {
scotty = new.callHackage "scotty" "0.21" {};
skylighting-lucid = new.callHackage "skylighting-lucid" "1.0.4" { };
wai-app-file-cgi = dontCoverage (dontCheck (jailbreakUnbreak pkgs old.wai-app-file-cgi));
libyaml =
if pkgs.hostPlatform.isStatic
then old.libyaml.overrideDerivation (drv: {
postPatch = let sed = "${pkgs.gnused}/bin/sed"; in ''
${sed} -i -e 's/buffer_init/snoyberg_buffer_init/' c/helper.c include/helper.h
${sed} -i -e 's/"buffer_init"/"snoyberg_buffer_init"/' src/Text/Libyaml.hs
'';
})
else old.libyaml;
};
overrideComposable = pkgs: hpkgs: overrides:
hpkgs.override (oldAttrs: {
overrides = pkgs.lib.composeExtensions (oldAttrs.overrides or (_: _: { })) overrides;
});
makePkgsFromDirOverride = pkgs: ov: pkgNames: mkPath:
pkgs.lib.genAttrs pkgNames (name:
ov (pkgs.haskellPackages.callCabal2nix name "${self}/${mkPath name}" {})
);
makePkgsFromDir = pkgs: makePkgsFromDirOverride pkgs (q: q);
makePkgsFromDirWithMan = pkgs: makePkgsFromDirOverride pkgs (q:
q.overrideDerivation (drv: {
postInstall = ''
if [ -d man ]; then
mkdir -p $out
cp -r man $out/
fi
'';
})
);
ourHaskellPackages = pkgs: ({}
// makePkgsFromDirWithMan pkgs topLevelPackages (n: n)
// makePkgsFromDirWithMan pkgs keymanPackages (name: "hbs2-keyman/${name}")
// makePkgsFromDir pkgs miscellaneous (name: "miscellaneous/${name}")
);
overlay = final: prev: {
haskellPackages = overrideComposable prev prev.haskellPackages
(new: old:
hpOverridesPre prev new old
// ourHaskellPackages final
);
};
in
haskell-flake-utils.lib.simpleCabalProject2flake {
inherit self nixpkgs;
systems = [ "x86_64-linux" "aarch64-linux" "x86_64-darwin" "aarch64-darwin" ];
name = "hbs2";
haskellFlakes = with inputs; [
suckless-conf
db-pipe
];
inherit packageNames;
packageDirs = {
"hbs2" = "./hbs2";
"hbs2-tests" = "./hbs2-tests";
"hbs2-core" = "./hbs2-core";
"hbs2-storage-simple" = "./hbs2-storage-simple";
"hbs2-peer" = "./hbs2-peer";
"hbs2-keyman" = "./hbs2-keyman";
"hbs2-share" = "./hbs2-share";
"hbs2-git" = "./hbs2-git";
"hbs2-fixer" = "./hbs2-fixer";
{ overlays.default = overlay; }
//
(flake-utils.lib.eachSystem ["x86_64-linux" "aarch64-linux" "x86_64-darwin" "aarch64-darwin"]
(system:
let
pkgs = import nixpkgs {
inherit system;
overlays = [overlay];
};
hpPreOverrides = {pkgs, ...}: final: prev: with pkgs; {
saltine = prev.callCabal2nix "saltine" inputs.saltine { inherit (pkgs) libsodium; };
};
packagePostOverrides = { pkgs }: with pkgs; with haskell.lib; [
packagePostOverrides = pkg: with pkgs.haskell.lib.compose; pkgs.lib.pipe pkg [
disableExecutableProfiling
disableLibraryProfiling
dontBenchmark
@ -81,35 +139,71 @@ outputs = { self, nixpkgs, haskell-flake-utils, ... }@inputs:
dontCheck
(compose.overrideCabal (drv: {
(overrideCabal (drv: {
preBuild = ''
export GIT_HASH="${self.rev or self.dirtyRev or "dirty"}"
'';
disallowGhcReference = false;
}))
];
shell = {pkgs, ...}:
pkgs.haskellPackages.shellFor {
packages = _: pkgs.lib.attrsets.attrVals packageNames pkgs.haskellPackages;
makePackages = pkgs:
let ps = pkgs.lib.mapAttrs
(_name: packagePostOverrides) # we can't apply overrides inside our overlay because it will remove linking info
(pkgs.lib.getAttrs packageNames (ourHaskellPackages pkgs))
;
in ps // {
bf6-git-hbs2 = pkgs.callPackage ./nix/bf6-hbs2-git.nix { inherit (ps) suckless-conf; };
};
packagesDynamic = makePackages pkgs;
packagesStatic = makePackages pkgs.pkgsStatic;
in {
legacyPackages = pkgs;
homeManagerModules.default = import ./nix/hm-module.nix self;
packages =
packagesDynamic //
{
default =
pkgs.symlinkJoin {
name = "hbs2-all";
paths = builtins.attrValues packagesDynamic;
};
static =
pkgs.symlinkJoin {
name = "hbs2-static";
paths = builtins.attrValues packagesStatic;
};
};
devShells.default = pkgs.haskellPackages.shellFor {
packages = p: builtins.attrValues (ourHaskellPackages pkgs) ++ [
p.skylighting-core # needed for hbs2-tests which we did not expose
];
# withHoogle = true;
buildInputs = (
with pkgs.haskellPackages; ([
with pkgs.haskellPackages; [
ghc
ghcid
cabal-install
haskell-language-server
hoogle
htags
# htags
text-icu
magic
pkgs.icu72
pkgs.openssl
weeder
])
]
++
[ pkgs.pkg-config
pkgs.libsodium
pkgs.file
pkgs.zlib
pkgs.fuse
inputs.hspup.packages.${pkgs.system}.default
inputs.fixme.packages.${pkgs.system}.default
]
);
@ -118,6 +212,8 @@ outputs = { self, nixpkgs, haskell-flake-utils, ... }@inputs:
'';
};
};
}
));
}

30
hbs2-cli/LICENSE Normal file
View File

@ -0,0 +1,30 @@
Copyright (c) 2024, Dmitry Zuikov
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following
disclaimer in the documentation and/or other materials provided
with the distribution.
* Neither the name of Dmitry Zuikov nor the names of other
contributors may be used to endorse or promote products derived
from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

136
hbs2-cli/app/Main.hs Normal file
View File

@ -0,0 +1,136 @@
{-# Language AllowAmbiguousTypes #-}
{-# Language UndecidableInstances #-}
module Main where
import HBS2.CLI.Prelude
import HBS2.CLI.Run
import HBS2.CLI.Run.Help
import HBS2.CLI.Run.KeyMan
import HBS2.CLI.Run.Keyring
import HBS2.CLI.Run.GroupKey
import HBS2.CLI.Run.Sigil
import HBS2.CLI.Run.MetaData
import HBS2.CLI.Run.Tree
import HBS2.CLI.Run.Peer
import HBS2.CLI.Run.RefLog
import HBS2.CLI.Run.RefChan
import HBS2.CLI.Run.LWWRef
import HBS2.CLI.Run.Mailbox
import HBS2.CLI.NCQ3.Migrate
import Data.Config.Suckless.Script.File as SF
import HBS2.Peer.RPC.Client.Unix
import HBS2.Net.Auth.Schema()
import System.Environment
import System.IO qualified as IO
type RefLogId = PubKey 'Sign 'HBS2Basic
{- HLINT ignore "Functor law" -}
setupLogger :: MonadIO m => m ()
setupLogger = do
-- setLogging @DEBUG $ toStderr . logPrefix "[debug] "
setLogging @ERROR $ toStderr . logPrefix "[error] "
setLogging @WARN $ toStderr . logPrefix "[warn] "
setLogging @NOTICE $ toStderr . logPrefix ""
setLogging @INFO $ toStderr . logPrefix ""
pure ()
flushLoggers :: MonadIO m => m ()
flushLoggers = do
silence
silence :: MonadIO m => m ()
silence = do
setLoggingOff @DEBUG
setLoggingOff @ERROR
setLoggingOff @WARN
setLoggingOff @NOTICE
main :: IO ()
main = do
setupLogger
cli <- getArgs <&> unlines . fmap unwords . splitForms
>>= either (error.show) pure . parseTop
let runScript dict argz what = liftIO do
script <- either (error.show) pure $ parseTop what
runHBS2Cli $ recover $ runM dict do
bindCliArgs argz
void $ evalTop script
let dict = makeDict do
internalEntries
keymanEntries
keyringEntries
groupKeyEntries
sigilEntries
treeEntries
metaDataEntries
peerEntries
reflogEntries
refchanEntries
lwwRefEntries
mailboxEntries
migrateEntries
helpEntries
SF.entries
entry $ bindMatch "--help" $ nil_ \case
HelpEntryBound what -> helpEntry what
[StringLike s] -> helpList False (Just s)
_ -> helpList False Nothing
entry $ bindMatch "debug:cli:show" $ nil_ \case
_ -> display cli
entry $ bindMatch "#!" $ nil_ $ const none
entry $ bindMatch "stdin" $ nil_ $ \case
argz -> do
liftIO getContents >>= runScript dict argz
entry $ bindMatch "file" $ nil_ $ \case
( StringLike fn : argz ) -> do
liftIO (readFile fn) >>= runScript dict argz
e -> error (show $ pretty $ mkList e)
runHBS2Cli do
-- error (show $ pretty cli)
case cli of
( cmd@(ListVal [StringLike "file", StringLike fn]) : _ ) -> do
void $ run dict [cmd]
( cmd@(ListVal [StringLike "stdin"]) : _ ) -> do
void $ run dict [cmd]
( cmd@(ListVal [StringLike "--help"]) : _ ) -> do
void $ run dict [cmd]
[] -> do
eof <- liftIO IO.isEOF
if eof then
void $ run dict [mkForm "help" []]
else do
what <- liftIO getContents
>>= either (error.show) pure . parseTop
recover $ run dict what >>= eatNil display
_ -> do
recover $ run dict cli >>= eatNil display

146
hbs2-cli/hbs2-cli.cabal Normal file
View File

@ -0,0 +1,146 @@
cabal-version: 3.0
name: hbs2-cli
version: 0.25.3.0
-- synopsis:
-- description:
license: BSD-3-Clause
license-file: LICENSE
author: Dmitry Zuikov
-- copyright:
category: System
build-type: Simple
-- extra-doc-files: CHANGELOG.md
-- extra-source-files:
common shared-properties
ghc-options:
-Wall
-fno-warn-type-defaults
-threaded
-rtsopts
-O2
"-with-rtsopts=-N4 -A64m -AL256m -I0"
default-language: GHC2021
default-extensions:
ApplicativeDo
, BangPatterns
, BlockArguments
, ConstraintKinds
, DataKinds
, DeriveDataTypeable
, DeriveGeneric
, DerivingStrategies
, DerivingVia
, ExtendedDefaultRules
, FlexibleContexts
, FlexibleInstances
, GADTs
, GeneralizedNewtypeDeriving
, ImportQualifiedPost
, LambdaCase
, MultiParamTypeClasses
, OverloadedStrings
, QuasiQuotes
, RecordWildCards
, ScopedTypeVariables
, StandaloneDeriving
, TupleSections
, TypeApplications
, TypeFamilies
, PatternSynonyms
, ViewPatterns
build-depends:
hbs2-core
, hbs2-peer
, hbs2-storage-simple
, hbs2-storage-ncq
, hbs2-keyman-direct-lib
, db-pipe
, suckless-conf
, attoparsec
, atomic-write
, bytestring
, binary
, containers
, directory
, exceptions
, filepath
, filepattern
, generic-lens
, hashable
, interpolatedstring-perl6
, memory
, microlens-platform
, mtl
, safe
, serialise
, streaming
, stm
, text
, time
, timeit
, transformers
, typed-process
, unordered-containers
, unliftio
, unliftio-core
, zlib
, prettyprinter
, prettyprinter-ansi-terminal
, random
, vector
, unix
, split
library
import: shared-properties
exposed-modules:
HBS2.CLI
HBS2.CLI.Prelude
HBS2.CLI.Bind
HBS2.CLI.Run
HBS2.CLI.Run.Internal
HBS2.CLI.Run.Internal.GroupKey
HBS2.CLI.Run.Internal.Merkle
HBS2.CLI.Run.Internal.KeyMan
HBS2.CLI.Run.Internal.RefChan
HBS2.CLI.Run.Internal.RefLog
HBS2.CLI.Run.GroupKey
HBS2.CLI.Run.KeyMan
HBS2.CLI.Run.Keyring
HBS2.CLI.Run.Tree
HBS2.CLI.Run.MetaData
HBS2.CLI.Run.Peer
HBS2.CLI.Run.RefLog
HBS2.CLI.Run.RefChan
HBS2.CLI.Run.LWWRef
HBS2.CLI.Run.Mailbox
HBS2.CLI.Run.Sigil
HBS2.CLI.Run.Help
HBS2.CLI.NCQ3.Migrate
build-depends: base
, magic
hs-source-dirs: lib
executable hbs2-cli
import: shared-properties
main-is: Main.hs
-- other-modules:
-- other-extensions:
build-depends:
base, hbs2-cli
hs-source-dirs: app
default-language: GHC2021

1
hbs2-cli/lib/HBS2/CLI.hs Normal file
View File

@ -0,0 +1 @@
module HBS2.CLI where

View File

@ -0,0 +1,4 @@
module HBS2.CLI.Bind where
import HBS2.CLI.Prelude

View File

@ -0,0 +1,57 @@
{-# Language AllowAmbiguousTypes #-}
module HBS2.CLI.NCQ3.Migrate where
import HBS2.CLI.Prelude
import HBS2.CLI.Run.Internal
import HBS2.Peer.NCQ3.Migrate.NCQ
import HBS2.Net.Auth.Schema()
import HBS2.Peer.RPC.API.Peer
import HBS2.Peer.RPC.Client
import HBS2.Peer.RPC.Client.Unix
import HBS2.Storage
import HBS2.Storage.NCQ3.Internal.Prelude
import HBS2.Peer.Proto.RefLog
import HBS2.Peer.Proto.RefChan
import HBS2.Peer.Proto.LWWRef
import Streaming.Prelude qualified as S
migrateEntries :: forall c m . ( MonadUnliftIO m
, IsContext c
, Exception (BadFormException c)
, HasClientAPI PeerAPI UNIX m
, HasStorage m
) => MakeDictM c m ()
migrateEntries = do
brief "migrate NCQv1 => NCQ3"
$ args [ arg "path" "src"
, arg "path" "dst"
]
$ entry $ bindMatch "ncq3:migrate:ncq" $ nil_ $ \case
[ StringLike src, StringLike dst] -> do
api <- getClientAPI @PeerAPI @UNIX
refs <- callRpcWaitMay @RpcPollList2 (1.0 :: Timeout 'Seconds) api (Nothing, Nothing)
<&> fromMaybe mempty
rrefs <- S.toList_ <$> for refs $ \(pk, s, _) -> case s of
"reflog" -> S.yield (WrapRef $ RefLogKey @'HBS2Basic pk)
"refchan" -> do
S.yield (WrapRef $ RefChanLogKey @'HBS2Basic pk)
S.yield (WrapRef $ RefChanHeadKey @'HBS2Basic pk)
"lwwref" -> S.yield (WrapRef $ LWWRefKey @'HBS2Basic pk)
_ -> none
lift $ migrateNCQ1 nicelog rrefs src dst
e -> throwIO $ BadFormException (mkList e)
nicelog :: forall m . MonadIO m => Doc AnsiStyle -> m ()
nicelog doc = liftIO $ hPutDoc stdout (doc <> line)

View File

@ -0,0 +1,28 @@
module HBS2.CLI.Prelude
( module HBS2.Prelude.Plated
, module HBS2.OrDie
, module UnliftIO
, module Data.Config.Suckless
, module Data.HashMap.Strict
, module Control.Monad.Reader
, module HBS2.System.Logger.Simple.ANSI
, module HBS2.Misc.PrettyStuff
, qc,qq,q
, Generic
, pattern SignPubKeyLike
) where
import HBS2.Prelude.Plated
import HBS2.OrDie
import HBS2.System.Logger.Simple.ANSI
import HBS2.Misc.PrettyStuff
import HBS2.Net.Auth.Credentials
import Data.HashMap.Strict
import Data.Config.Suckless
import Control.Monad.Reader
import UnliftIO
import Text.InterpolatedString.Perl6 (qc,q,qq)

View File

@ -0,0 +1,9 @@
{-# Language UndecidableInstances #-}
module HBS2.CLI.Run
( module HBS2.CLI.Run.Internal
) where
import HBS2.CLI.Run.Internal

View File

@ -0,0 +1,200 @@
module HBS2.CLI.Run.GroupKey
( module HBS2.CLI.Run.GroupKey
, loadGroupKey
) where
import HBS2.CLI.Prelude hiding (mapMaybe)
import HBS2.Data.Types.Refs
import HBS2.Storage.Operations.Class
import HBS2.Storage.Operations.ByteString
import HBS2.Base58
import HBS2.CLI.Run.Internal
import HBS2.CLI.Run.Internal.GroupKey as G
import HBS2.Net.Auth.GroupKeySymm as Symm
import HBS2.Storage
import HBS2.KeyMan.Keys.Direct
import HBS2.Peer.RPC.API.Storage
import HBS2.Peer.RPC.Client
import HBS2.Peer.RPC.Client.Unix
import Data.Text qualified as Text
import Data.ByteString.Lazy.Char8 as LBS8
import Data.ByteString.Lazy as LBS
import Data.ByteString.Char8 as BS8
import Data.HashMap.Strict qualified as HM
import Control.Monad.Except
import Codec.Serialise
{- HLINT ignore "Functor law" -}
groupKeyEntries :: forall c m . ( MonadUnliftIO m
, IsContext c
, Exception (BadFormException c)
, HasClientAPI StorageAPI UNIX m
, HasStorage m
) => MakeDictM c m ()
groupKeyEntries = do
entry $ bindMatch "hbs2:groupkey:load" $ \case
[HashLike h] -> do
sto <- getStorage
gk <- loadGroupKey h
>>= orThrowUser "can not load groupkey"
pure $ mkStr (show $ pretty $ AsGroupKeyFile gk)
_ -> throwIO $ BadFormException @C nil
brief "stores groupkey to the peer's storage" $
args [arg "string" "groupkey"] $
returns "string" "hash" $
entry $ bindMatch "hbs2:groupkey:store" $ \case
[LitStrVal s] -> do
let lbs = LBS8.pack (Text.unpack s)
gk <- pure (Symm.parseGroupKey @'HBS2Basic $ AsGroupKeyFile lbs)
`orDie` "invalid group key"
sto <- getStorage
ha <- writeAsMerkle sto (serialise gk)
pure $ mkStr (show $ pretty ha)
_ -> throwIO $ BadFormException @c nil
brief "publish groupkey to the given refchan" $
args [arg "string" "refchan", arg "string" "groupkey-blob|groupkey-hash"] $
desc "groupkey may be also hash of te stored groupkey" $
entry $ bindMatch "hbs2:groupkey:publish" $ nil_ $ \case
[SignPubKeyLike rchan, LitStrVal gk] -> do
-- get
-- check
-- store
-- find refchan
-- post tx as metadata
notice $ red "not implemented yet"
[SignPubKeyLike rchan, HashLike gkh] -> do
notice $ red "not implemented yet"
_ -> throwIO $ BadFormException @c nil
-- $ hbs2-cli print [hbs2:groupkey:update [hbs2:groupkey:load 6XJGpJszP6f68fmhF17AtJ9PTgE7BKk8RMBHWQ2rXu6N] \
-- [list [remove . CcRDzezX1XQdPxRMuMKzJkfHFB4yG7vGJeTYvScKkbP8] \
-- [add . 5sJXsw7qhmq521hwhE67jYvrD6ZNVazc89rFwfWaQPyY]] ]
--
entry $ bindMatch "hbs2:groupkey:update" $ \case
[LitStrVal s, ListVal ins] -> do
sto <- getStorage
let lbs = LBS8.pack (Text.unpack s)
gk <- pure (Symm.parseGroupKey @'HBS2Basic $ AsGroupKeyFile lbs)
`orDie` "invalid group key"
gk1 <- modifyGroupKey gk ins
pure $ mkStr (show $ pretty $ AsGroupKeyFile gk1)
_ -> throwIO $ BadFormException @C nil
brief "create group key" $
args [ arg "keys" "list" ] $
desc "list of encryption public keys of members" $
entry $ bindMatch "hbs2:groupkey:create" $ \syn -> do
case syn of
[ListVal (StringLikeList keys)] -> do
s <- groupKeyFromKeyList keys
<&> AsGroupKeyFile
<&> show . pretty
pure $ mkStr s
StringLikeList keys -> do
s <- groupKeyFromKeyList keys
<&> AsGroupKeyFile
<&> show . pretty
pure $ mkStr s
_ -> throwIO $ BadFormException @C nil
entry $ bindMatch "hbs2:groupkey:dump" $ nil_ $ \syn -> do
case syn of
-- TODO: from-file
-- TODO: from-stdin
-- TODO: base58 file
[HashLike gkh] -> do
gk <- loadGroupKey gkh
liftIO $ print $ pretty gk
_ -> throwIO $ BadFormException @C nil
entry $ bindMatch "hbs2:groupkey:list-public-keys" $ \syn -> do
case syn of
[LitStrVal s] -> do
let lbs = LBS8.pack (Text.unpack s)
gk <- pure (Symm.parseGroupKey @'HBS2Basic $ AsGroupKeyFile lbs)
`orDie` "invalid group key"
let rcpt = recipients gk & HM.keys & fmap (mkStr . show . pretty . AsBase58)
pure $ mkList @c rcpt
_ -> throwIO $ BadFormException @C nil
brief "find groupkey secret in hbs2-keyman" $
args [arg "string" "group-key-hash"] $
returns "secret-key-id" "string" $
entry $ bindMatch "hbs2:groupkey:find-secret" $ \case
[HashLike gkh] -> do
sto <- getStorage
gk <- loadGroupKey gkh >>= orThrowUser "can't load groupkey"
what <- runKeymanClientRO $ findMatchedGroupKeySecret sto gk
>>= orThrowUser "groupkey secret not found"
let gid = generateGroupKeyId GroupKeyIdBasic1 what
pure $ mkStr (show $ pretty gid)
_ -> throwIO $ BadFormException @c nil
entry $ bindMatch "hbs2:groupkey:decrypt-block" $ \case
[BlobLike bs] -> do
sto <- getStorage
let lbs = LBS.fromStrict bs
seb <- pure (deserialiseOrFail lbs)
`orDie` "invalid SmallEncryptedBlock"
decrypted <- G.decryptBlock sto seb
pure $ mkForm @c "blob" [mkStr (BS8.unpack decrypted)]
_ -> throwIO $ BadFormException @C nil
entry $ bindMatch "hbs2:groupkey:encrypt-block" $ \case
[StringLike gkh, BlobLike what] -> do
sto <- getStorage
gk <- loadGroupKey (fromString gkh)
`orDie` "can't load group key"
seb <- G.encryptBlock sto gk what
pure $ mkForm "blob" [mkStr (LBS8.unpack (serialise seb))]
_ -> throwIO $ BadFormException @C nil

View File

@ -0,0 +1,28 @@
module HBS2.CLI.Run.Help where
import HBS2.CLI.Prelude
import HBS2.CLI.Run.Internal
import Data.HashMap.Strict qualified as HM
import Data.List qualified as List
import Data.Text qualified as Text
helpEntries :: (MonadUnliftIO m, IsContext c) => MakeDictM c m ()
helpEntries = do
entry $ bindMatch "help" $ nil_ $ \syn -> do
display_ $ "hbs2-cli tool" <> line
case syn of
[StringLike "--documented"] -> do
helpList True Nothing
(StringLike p : _) -> do
helpList False (Just p)
HelpEntryBound what -> helpEntry what
_ -> helpList False Nothing

View File

@ -0,0 +1,242 @@
{-# Language TemplateHaskell #-}
{-# Language UndecidableInstances #-}
{-# Language AllowAmbiguousTypes #-}
module HBS2.CLI.Run.Internal
( module HBS2.CLI.Run.Internal
, module SC
) where
import HBS2.CLI.Prelude
import HBS2.System.Dir
import HBS2.OrDie
import HBS2.Base58
import HBS2.Data.Types.Refs
import HBS2.Storage
import HBS2.Peer.CLI.Detect
import HBS2.Peer.RPC.Client
import HBS2.Peer.RPC.Client.Unix
import HBS2.Peer.RPC.API.Peer
import HBS2.Peer.RPC.API.RefLog
import HBS2.Peer.RPC.API.RefChan
import HBS2.Peer.RPC.API.LWWRef
import HBS2.Peer.RPC.API.Storage
import HBS2.Peer.RPC.API.RefChan
import HBS2.Peer.RPC.Client.StorageClient
import Data.Config.Suckless.Script qualified as SC
import Data.Config.Suckless.Script hiding (internalEntries)
import Control.Monad.Trans.Maybe
import Control.Monad.Trans.Cont
import Data.ByteString.Char8 qualified as BS8
import Data.ByteString.Lazy qualified as LBS
import Data.ByteString qualified as BS
import Data.Text qualified as Text
import Lens.Micro.Platform
data HBS2CliEnv =
HBS2CliEnv
{ _peerSocket :: FilePath
, _peerRefChanAPI :: ServiceCaller RefChanAPI UNIX
, _peerRefLogAPI :: ServiceCaller RefLogAPI UNIX
, _peerLwwRefAPI :: ServiceCaller LWWRefAPI UNIX
, _peerPeerAPI :: ServiceCaller PeerAPI UNIX
, _peerStorageAPI :: ServiceCaller StorageAPI UNIX
}
makeLenses 'HBS2CliEnv
newtype HBS2Cli m a = HBS2Cli { fromHBS2Cli :: ReaderT (TVar (Maybe HBS2CliEnv)) m a }
deriving newtype ( Applicative
, Functor
, Monad
, MonadIO
, MonadUnliftIO
, MonadReader (TVar (Maybe HBS2CliEnv))
)
withHBS2Cli :: TVar (Maybe HBS2CliEnv) -> HBS2Cli m a -> m a
withHBS2Cli env action = runReaderT (fromHBS2Cli action) env
recover :: HBS2Cli IO a -> HBS2Cli IO a
recover what = do
catch what $ \case
PeerNotConnectedException -> do
soname <- detectRPC
`orDie` "can't locate hbs2-peer rpc"
flip runContT pure do
client <- lift $ race (pause @'Seconds 1) (newMessagingUnix False 1.0 soname)
>>= orThrowUser ("can't connect to" <+> pretty soname)
void $ ContT $ withAsync $ runMessagingUnix client
peerAPI <- makeServiceCaller @PeerAPI (fromString soname)
refLogAPI <- makeServiceCaller @RefLogAPI (fromString soname)
refChanAPI <- makeServiceCaller @RefChanAPI (fromString soname)
storageAPI <- makeServiceCaller @StorageAPI (fromString soname)
lwwAPI <- makeServiceCaller @LWWRefAPI (fromString soname)
-- let sto = AnyStorage (StorageClient storageAPI)
let endpoints = [ Endpoint @UNIX peerAPI
, Endpoint @UNIX refLogAPI
, Endpoint @UNIX refChanAPI
, Endpoint @UNIX lwwAPI
, Endpoint @UNIX storageAPI
]
void $ ContT $ withAsync $ liftIO $ runReaderT (runServiceClientMulti endpoints) client
let env = Just (HBS2CliEnv soname refChanAPI refLogAPI lwwAPI peerAPI storageAPI)
tv <- newTVarIO env
liftIO $ withHBS2Cli tv what
runHBS2Cli :: MonadUnliftIO m => HBS2Cli m a -> m a
runHBS2Cli action = do
noenv <- newTVarIO Nothing
withHBS2Cli noenv action
data PeerException =
PeerNotConnectedException
deriving stock (Show, Typeable)
instance Exception PeerException
instance (MonadUnliftIO m, HasClientAPI api proto m) => HasClientAPI api proto (RunM c m) where
getClientAPI = lift (getClientAPI @api @proto)
instance (MonadUnliftIO m, HasStorage m) => HasStorage (RunM c m) where
getStorage = lift getStorage
instance (MonadUnliftIO m, HasClientAPI StorageAPI UNIX m, HasStorage m) => HasStorage (ContT a (RunM c m)) where
getStorage = lift getStorage
instance (MonadUnliftIO m, HasClientAPI api proto m) => HasClientAPI api proto (ContT a (RunM c m)) where
getClientAPI = lift $ getClientAPI @api @proto
instance MonadUnliftIO m => HasClientAPI RefChanAPI UNIX (HBS2Cli m) where
getClientAPI = do
what <- ask >>= readTVarIO >>= orThrow PeerNotConnectedException
pure $ view peerRefChanAPI what
instance MonadUnliftIO m => HasClientAPI RefLogAPI UNIX (HBS2Cli m) where
getClientAPI = do
what <- ask >>= readTVarIO >>= orThrow PeerNotConnectedException
pure $ view peerRefLogAPI what
instance MonadUnliftIO m => HasClientAPI PeerAPI UNIX (HBS2Cli m) where
getClientAPI = do
what <- ask >>= readTVarIO >>= orThrow PeerNotConnectedException
pure $ view peerPeerAPI what
instance MonadUnliftIO m => HasClientAPI StorageAPI UNIX (HBS2Cli m) where
getClientAPI = do
what <- ask >>= readTVarIO >>= orThrow PeerNotConnectedException
pure $ view peerStorageAPI what
instance MonadUnliftIO m => HasClientAPI LWWRefAPI UNIX (HBS2Cli m) where
getClientAPI = do
what <- ask >>= readTVarIO >>= orThrow PeerNotConnectedException
pure $ view peerLwwRefAPI what
instance MonadUnliftIO m => HasStorage (HBS2Cli m) where
getStorage = getClientAPI @StorageAPI @UNIX <&> AnyStorage . StorageClient
internalEntries :: forall c m . (IsContext c, Exception (BadFormException c), MonadUnliftIO m) => MakeDictM c m ()
internalEntries = do
SC.internalEntries
entry $ bindMatch "--run" $ \case
[] -> do
liftIO getContents
<&> parseTop
>>= either (error.show) (pure . fmap (fixContext @_ @c))
>>= evalTop
[StringLike fn] -> do
liftIO (readFile fn)
<&> parseTop
>>= either (error.show) (pure . fmap (fixContext @_ @c))
>>= evalTop
_ -> throwIO (BadFormException @c nil)
-- TODO: re-implement-all-on-top-of-opaque
entry $ bindMatch "hbs2:hash" $ \case
[] -> liftIO do
LBS.getContents
<&> mkSym . HashRef . hashObject @HbSync
[ StringLike fn ] -> liftIO do
LBS.readFile fn
<&> mkSym . HashRef . hashObject @HbSync
[isOpaqueOf @LBS.ByteString -> Just s ] -> do
pure $ mkSym $ HashRef $ hashObject @HbSync s
[isOpaqueOf @BS.ByteString -> Just s ] -> do
pure $ mkSym $ HashRef $ hashObject @HbSync s
e -> throwIO (BadFormException @c (mkList e))
entry $ bindMatch "blob:base58" $ \case
[LitStrVal t] -> do
bs <- pure (Text.unpack t & BS8.pack & fromBase58)
`orDie` "invalid base58"
<&> BS8.unpack
pure (mkForm "blob" [mkStr @c bs])
_ -> throwIO (BadFormException @c nil)
let decodeB58 t = do
pure (Text.unpack t & BS8.pack & fromBase58)
`orDie` "invalid base58"
let decodeAndOut t = do
liftIO $ BS8.putStr =<< decodeB58 t
entry $ bindMatch "base58:encode" $ \case
[LitStrVal t] -> do
let s = Text.unpack t & BS8.pack & toBase58 & BS8.unpack
pure (mkForm "blob:base58" [mkStr @c s])
[ListVal [SymbolVal "blob", LitStrVal t]] -> do
let s = Text.unpack t & BS8.pack & toBase58 & BS8.unpack
pure (mkForm "blob:base58" [mkStr @c s])
e -> throwIO (BadFormException @c nil)
entry $ bindMatch "base58:decode" $ \case
[ListVal [SymbolVal "blob:base58", LitStrVal t]] -> do
s <- decodeB58 t <&> BS8.unpack
pure $ mkForm "blob" [mkStr @c s]
e -> throwIO (BadFormException @c nil)
entry $ bindMatch "base58:put" $ nil_ $ \case
[ListVal [SymbolVal "blob:base58", LitStrVal t]] ->
decodeAndOut t
[LitStrVal t] -> decodeAndOut t
e -> throwIO (BadFormException @c nil)
entry $ bindMatch "test:opaque" $ \case
[ LitIntVal n ] -> mkOpaque n
[ StringLike s ] -> mkOpaque s
_ -> mkOpaque ()

View File

@ -0,0 +1,107 @@
module HBS2.CLI.Run.Internal.GroupKey
( module HBS2.CLI.Run.Internal.GroupKey
, SmallEncryptedBlock(..)
) where
import HBS2.CLI.Prelude hiding (mapMaybe)
import HBS2.CLI.Run.Internal
import HBS2.Base58
import HBS2.Hash
import HBS2.Storage
import HBS2.Data.Types.Refs
import HBS2.Data.Types.SmallEncryptedBlock
import HBS2.Storage.Operations.Class
import HBS2.Storage.Operations.ByteString
import HBS2.KeyMan.Keys.Direct
import HBS2.Net.Auth.GroupKeySymm as Symm
import HBS2.Peer.RPC.Client.Unix
import HBS2.Peer.RPC.Client
import HBS2.Peer.RPC.API.Storage
import Data.HashMap.Strict qualified as HM
import Data.HashSet qualified as HS
import Data.Maybe
import Control.Monad.Trans.Cont
import Control.Monad.Except
import Codec.Serialise
import Data.ByteString (ByteString)
groupKeyFromKeyList :: MonadUnliftIO m => [String] -> m (GroupKey 'Symm HBS2Basic)
groupKeyFromKeyList ks = do
let members = mapMaybe (fromStringMay @(PubKey 'Encrypt 'HBS2Basic)) ks
Symm.generateGroupKey @'HBS2Basic Nothing members
encryptBlock :: (MonadUnliftIO m, Serialise t)
=> AnyStorage
-> GroupKey 'Symm 'HBS2Basic
-> t
-> m (SmallEncryptedBlock t)
encryptBlock sto gk x = do
let HbSyncHash non = hashObject (serialise x)
gks <- runKeymanClientRO (extractGroupKeySecret gk)
>>= orThrowUser "can't extract group key secret"
Symm.encryptBlock sto gks (Right gk) (Just non) x
decryptBlock :: (MonadUnliftIO m, Serialise t)
=> AnyStorage
-> SmallEncryptedBlock t
-> m t
decryptBlock sto seb = do
let find gk = runKeymanClientRO (findMatchedGroupKeySecret sto gk)
-- FIXME: improve-error-diagnostics
runExceptT (Symm.decryptBlock sto find seb)
>>= orThrowUser "can't decrypt block"
loadGroupKey :: ( IsContext c
, MonadUnliftIO m
, HasStorage m
, HasClientAPI StorageAPI UNIX m
) => HashRef -> RunM c m (Maybe (GroupKey 'Symm HBS2Basic))
loadGroupKey h = do
flip runContT pure do
sto <- getStorage
raw <- runExceptT (readFromMerkle sto (SimpleKey (fromHashRef h)))
<&> either (const Nothing) Just
bs <- ContT (maybe1 raw (pure Nothing))
let gk = deserialiseOrFail bs
& either (const Nothing) Just
pure gk
modifyGroupKey :: (IsContext c, MonadUnliftIO m)
=> GroupKey 'Symm 'HBS2Basic
-> [Syntax c]
-> m (GroupKey 'Symm HBS2Basic)
modifyGroupKey gk ins = do
gks <- runKeymanClient do
extractGroupKeySecret gk
`orDie` "can't extract group key secret"
let r = catMaybes [ fromStringMay @(PubKey 'Encrypt HBS2Basic) k
| ListVal [SymbolVal "remove", StringLike k] <- ins
] & HS.fromList
let a = catMaybes [ fromStringMay @(PubKey 'Encrypt HBS2Basic) k
| ListVal [SymbolVal "add", StringLike k] <- ins
] & HS.fromList
let x = recipients gk & HM.keysSet
let new = x `HS.difference` r `mappend` a & HS.toList
generateGroupKey @'HBS2Basic (Just gks) new

View File

@ -0,0 +1,55 @@
module HBS2.CLI.Run.Internal.KeyMan where
import HBS2.CLI.Prelude
import HBS2.CLI.Run.Internal
import HBS2.Hash
import HBS2.System.Dir
import HBS2.Net.Auth.Credentials
import HBS2.KeyMan.Config (getDefaultKeyPath)
import HBS2.KeyMan.Keys.Direct
import HBS2.KeyMan.State
import HBS2.KeyMan.App.Types
import Codec.Serialise
import Data.Either
import Data.ByteString.Lazy qualified as LBS
import Data.Text.Encoding qualified as TE
import Data.Text.IO qualified as TIO
import System.Process.Typed
import Text.InterpolatedString.Perl6 (qc)
keymanGetConfig :: (IsContext c, MonadUnliftIO m) => m [Syntax c]
keymanGetConfig = do
(_,lbs,_) <- readProcess (shell [qc|hbs2-keyman config|] & setStderr closed)
let conf = TE.decodeUtf8 (LBS.toStrict lbs)
& parseTop
& fromRight mempty
pure $ fmap fixContext conf
keymanUpdate :: MonadUnliftIO m => m ()
keymanUpdate = do
void $ runProcess (shell [qc|hbs2-keyman update|] & setStderr closed & setStdout closed)
keymanNewCredentials :: MonadUnliftIO m => Maybe String -> Int -> m (PubKey 'Sign 'HBS2Basic)
keymanNewCredentials suff n = do
conf <- keymanGetConfig @C
path <- getDefaultKeyPath conf
creds <- newCredentialsEnc @'HBS2Basic n
let s = show $ pretty $ AsCredFile (AsBase58 creds)
let psk = view peerSignPk creds
let fpath = path </> show (pretty (AsBase58 psk) <> "-" <> pretty suff <> ".key")
liftIO $ writeFile fpath s
keymanUpdate
pure psk

View File

@ -0,0 +1,156 @@
module HBS2.CLI.Run.Internal.Merkle where
import HBS2.CLI.Prelude
import HBS2.Defaults
import HBS2.CLI.Run.Internal
import HBS2.CLI.Run.Internal.GroupKey as G
import HBS2.Hash
import HBS2.Net.Auth.GroupKeySymm as Symm
import HBS2.Data.Types.Refs
import HBS2.Data.Detect
import HBS2.Merkle
import HBS2.Storage
import HBS2.Storage.Operations.ByteString
import HBS2.Peer.RPC.Client.Unix
import HBS2.Peer.RPC.Client
import HBS2.Peer.RPC.API.Storage
import HBS2.KeyMan.Keys.Direct
import HBS2.Net.Auth.Schema()
import Codec.Serialise
import Data.Coerce
import Data.ByteString.Lazy qualified as LBS
import Data.HashMap.Strict qualified as HM
import Data.Text qualified as Text
import Control.Monad.Trans.Maybe
import Control.Monad.Trans.Cont
import Control.Monad.Except
import Data.Maybe
--FIXME: move-somewhere-else
getGroupKeyHash :: ( MonadUnliftIO m
, HasStorage m
, HasClientAPI StorageAPI UNIX m
)
=> HashRef
-> m (Maybe HashRef, MTreeAnn [HashRef])
getGroupKeyHash h = do
flip runContT pure do
sto <- lift getStorage
headBlock <- getBlock sto (fromHashRef h)
>>= orThrow MissedBlockError
<&> deserialiseOrFail @(MTreeAnn [HashRef])
>>= orThrow UnsupportedFormat
case _mtaCrypt headBlock of
(EncryptGroupNaClSymm hash _) ->
pure $ (Just $ HashRef hash, headBlock)
_ -> pure (Nothing, headBlock)
-- TODO: client-api-candidate
createTreeWithMetadata :: (MonadUnliftIO m)
=> AnyStorage
-> Maybe (GroupKey 'Symm 'HBS2Basic)
-> HashMap Text Text
-> LBS.ByteString
-> m (Either OperationError HashRef)
createTreeWithMetadata sto mgk meta lbs = do -- flip runContT pure do
let mt = vcat [ pretty k <> ":" <+> dquotes (pretty v) | (k,v) <- HM.toList meta ]
& show & Text.pack
case mgk of
Nothing -> Right <$> createSimpleTree mt
Just gk -> createEncryptedTree gk mt
where
createSimpleTree mt = do
t0 <- writeAsMerkle sto lbs
>>= getBlock sto
>>= orThrowUser "can't read merkle tree just written"
<&> deserialiseOrFail @(MTree [HashRef])
>>= orThrowUser "merkle tree corrupted/invalid"
let mann = MTreeAnn (ShortMetadata mt) NullEncryption t0
putBlock sto (serialise mann)
>>= orThrowUser "can't write tree"
<&> HashRef
-- FIXME: support-encryption
createEncryptedTree gk mt = do
-- 1. find key
mgks <- runKeymanClientRO do
extractGroupKeySecret gk
gks <- orThrowUser "can't get groupkey's secret" mgks
-- FIXME: consider-other-nonce-calculation
-- надо считать начальный нонс (от чего / как?)
-- нонс: да так-то пофиг от чего, но:
-- если брать рандомные места в байтстроке --
-- она зафорсится
-- что вообще зависит от начального нонса:
-- если в файл будет допись в конец, то
-- "старые" блоки останутся такими же, как были
-- что хорошо для дедуплицирования, но
-- потенциально это менее безопасно.
-- можно еще с метаданными похэшировать, тогда
-- нонс будет более уникальный; но поменялись метаданные -- поменялось всё
let s0 = LBS.take ( 1024 * 1024 ) lbs
let (HbSyncHash nonce) = hashObject @HbSync s0
-- куда-то девать зашифрованные метаданные
--
let segments = readChunkedBS lbs defBlockSize
seb <- G.encryptBlock sto gk (ShortMetadata mt)
hmeta <- putBlock sto (serialise seb)
>>= orThrowUser "can't put block"
let source = ToEncryptSymmBS gks (Right gk) nonce segments (AnnHashRef hmeta) Nothing
runExceptT $ writeAsMerkle sto source <&> HashRef
getTreeContents :: forall m . ( MonadUnliftIO m
, MonadIO m
, MonadError OperationError m
)
=> AnyStorage
-> HashRef
-> m LBS.ByteString
getTreeContents sto href = do
blk <- getBlock sto (coerce href)
>>= orThrowError MissedBlockError
let q = tryDetect (coerce href) blk
case q of
Merkle _ -> do
readFromMerkle sto (SimpleKey (coerce href))
MerkleAnn (MTreeAnn {_mtaCrypt = NullEncryption }) -> do
readFromMerkle sto (SimpleKey (coerce href))
MerkleAnn ann@(MTreeAnn {_mtaCrypt = EncryptGroupNaClSymm gkh _}) -> do
rcpts <- Symm.loadGroupKeyMaybe @'HBS2Basic sto (HashRef gkh)
>>= orThrowError (GroupKeyNotFound 11)
<&> HM.keys . Symm.recipients
let findStuff g = do
runKeymanClientRO @IO $ findMatchedGroupKeySecret sto g
readFromMerkle sto (ToDecryptBS (coerce href) (liftIO . findStuff))
_ -> throwError UnsupportedFormat

View File

@ -0,0 +1,61 @@
{-# Language AllowAmbiguousTypes #-}
module HBS2.CLI.Run.Internal.RefChan (createNewRefChan) where
import HBS2.CLI.Prelude hiding (mapMaybe)
import HBS2.CLI.Run.Internal
import HBS2.CLI.Run.Internal.KeyMan
import HBS2.Peer.Proto.RefChan
import HBS2.Storage
import HBS2.Net.Auth.Credentials
import HBS2.Net.Auth.Schema()
import HBS2.Data.Types.SignedBox
import HBS2.Data.Types.Refs
import HBS2.Storage.Operations.Class
import HBS2.KeyMan.Keys.Direct
import HBS2.Peer.RPC.Client.Unix
import HBS2.Peer.RPC.Client
import HBS2.Peer.RPC.API.Storage
import HBS2.Peer.RPC.API.Peer
import HBS2.Peer.RPC.API.RefChan
import Lens.Micro.Platform
createNewRefChan :: forall c m . ( IsContext c
, MonadUnliftIO m
, Exception (BadFormException c)
, HasClientAPI RefChanAPI UNIX m
, HasClientAPI StorageAPI UNIX m
, HasClientAPI PeerAPI UNIX m
, HasStorage m
)
=> Maybe (PubKey Sign HBS2Basic)
-> RefChanHeadBlock L4Proto
-> m (PubKey Sign HBS2Basic)
createNewRefChan mbk rch = do
peerApi <- getClientAPI @PeerAPI @UNIX
rchanApi <- getClientAPI @RefChanAPI @UNIX
sto <- getStorage
refchan <- maybe1 mbk (keymanNewCredentials (Just "refchan") 0) pure
creds <- runKeymanClientRO $ loadCredentials refchan
>>= orThrowUser "can't load credentials"
let box = makeSignedBox @'HBS2Basic (view peerSignPk creds) (view peerSignSk creds) rch
href <- writeAsMerkle sto (serialise box)
--FIXME: timeout-hardcode
callService @RpcPollAdd peerApi (refchan, "refchan", 17)
>>= orThrowUser "can't subscribe to refchan"
callService @RpcRefChanHeadPost rchanApi (HashRef href)
>>= orThrowUser "can't post refchan head"
pure refchan

View File

@ -0,0 +1,109 @@
{-# Language AllowAmbiguousTypes #-}
module HBS2.CLI.Run.Internal.RefLog (copyTransactions, RefLogCLIException(..),decodeRefLogTx) where
import HBS2.CLI.Prelude hiding (mapMaybe)
import HBS2.CLI.Run.Internal
import HBS2.CLI.Run.Internal.KeyMan
import HBS2.Peer.Proto.RefLog
import HBS2.Base58
import HBS2.Storage
import HBS2.Data.Detect
import HBS2.Net.Auth.Credentials
import HBS2.Net.Auth.Schema()
import HBS2.Peer.Proto
import HBS2.Data.Types.SignedBox
import HBS2.Data.Types.Refs
import HBS2.Storage.Operations.Class
import HBS2.KeyMan.Keys.Direct
import HBS2.Peer.RPC.Client.Unix
import HBS2.Peer.RPC.Client
import HBS2.Peer.RPC.API.Storage
import HBS2.Peer.RPC.API.Peer
import HBS2.Peer.RPC.API.RefLog
import Codec.Serialise
import Control.Monad.Trans.Maybe
import Data.ByteString.Lazy qualified as LBS
import Data.Coerce
import Data.Maybe
import Lens.Micro.Platform
data RefLogCLIException =
RefLogRpcTimeout
| RefLogNoCredentials String
deriving (Typeable, Show)
instance Exception RefLogCLIException
type ForCloneRefLog e s m = ( s ~ Encryption e
, MonadUnliftIO m
, HasClientAPI RefLogAPI UNIX m
, HasClientAPI StorageAPI UNIX m
, HasClientAPI PeerAPI UNIX m
, HasStorage m
, Signatures s
, IsRefPubKey s
, Serialise (Nonce (RefLogUpdate e))
)
-- useful for forking git repositories
-- it accepts credential lookup method
-- since reflog B may be inferred from some other secret
-- normally, you dont need this method
copyTransactions :: forall e s m . (ForCloneRefLog e s m, s ~ Encryption e, e ~ L4Proto)
=> m (PeerCredentials s) -- ^ obtain credentials for reflog B
-> PubKey Sign s -- ^ original reflog
-> PubKey Sign s -- ^ destination reflog
-> m ()
copyTransactions cre a b = do
api <- getClientAPI @RefLogAPI @UNIX
sto <- getStorage
creds <- cre
let pk = view peerSignPk creds
let sk = view peerSignSk creds
void $ runMaybeT do
rvA <- lift (callRpcWaitMay @RpcRefLogGet (TimeoutSec 1) api a)
>>= orThrow RefLogRpcTimeout
>>= toMPlus
logA <- readLogThrow (getBlock sto) rvA
new <- for logA $ \h -> runMaybeT do
RefLogUpdate{..} <- getBlock sto (coerce h)
>>= toMPlus
<&> deserialiseOrFail @(RefLogUpdate e)
>>= toMPlus
lift (makeRefLogUpdate @e pk sk _refLogUpdData)
lift $ for_ (catMaybes new) $ \n -> do
void $ callService @RpcRefLogPost api n
decodeRefLogTx :: forall c. IsContext c => Maybe HashRef -> LBS.ByteString -> Syntax c
decodeRefLogTx h lbs = do
let ha = maybe (hashObject @HbSync lbs) coerce h
case tryDetect ha lbs of
SeqRef (SequentialRef n (AnnotatedHashRef ann ha)) ->
mkForm "seqref" [mkInt n, mkForm "annref" [mkSym (show $ pretty ann), mkSym (show $ pretty ha)]]
AnnRef (AnnotatedHashRef ann ha) -> do
mkForm "annref" [mkSym (show $ pretty ann), mkSym (show $ pretty ha)]
Blob{} -> mkForm "blob" [mkSym (show $ pretty ha)]
_ -> mkForm "tree" [mkSym (show $ pretty ha)]

View File

@ -0,0 +1,54 @@
module HBS2.CLI.Run.KeyMan
( module HBS2.CLI.Run.KeyMan
, keymanNewCredentials
) where
import HBS2.CLI.Prelude
import HBS2.CLI.Run.Internal
import HBS2.CLI.Run.Internal.KeyMan
import HBS2.Hash
import HBS2.System.Dir
import HBS2.KeyMan.Config (getDefaultKeyPath)
import HBS2.KeyMan.Keys.Direct
import HBS2.KeyMan.State
import HBS2.KeyMan.App.Types
import Codec.Serialise
import Data.Either
import Data.ByteString.Lazy qualified as LBS
import Data.Text.Encoding qualified as TE
import Data.Text.IO qualified as TIO
import System.Process.Typed
import Text.InterpolatedString.Perl6 (qc)
keymanEntries :: (MonadUnliftIO m, IsContext c) => MakeDictM c m ()
keymanEntries = do
entry $ bindMatch "hbs2:keyman:list" $ nil_ \case
_ -> do
void $ runKeymanClient $ KeyManClient $ do
k <- listKeys
display_ $ vcat (fmap pretty k)
entry $ bindMatch "hbs2:keyman:update" $ nil_ $ \_ -> do
keymanUpdate
entry $ bindMatch "hbs2:keyman:config" $ \_ -> do
mkForm "dict" <$> keymanGetConfig
args [ arg "string" "keyring-data"] $
entry $ bindMatch "hbs2:keyman:keys:add" $ \case
[ LitStrVal ke ] -> do
conf <- keymanGetConfig @C
path <- getDefaultKeyPath conf
let n = hashObject @HbSync (serialise ke) & pretty & show
let fname = n `addExtension` ".key"
let fpath = path </> fname
liftIO $ TIO.writeFile fpath ke
keymanUpdate
pure $ mkStr fpath
_ -> throwIO (BadFormException @C nil)

View File

@ -0,0 +1,60 @@
module HBS2.CLI.Run.Keyring where
import HBS2.CLI.Prelude
import HBS2.CLI.Run.Internal
import HBS2.Net.Auth.Credentials
import HBS2.KeyMan.App.Types
import Data.ByteString qualified as BS
import Data.ByteString.Char8 qualified as BS8
import Data.Text qualified as Text
keyringEntries :: forall c m . ( MonadUnliftIO m
, IsContext c
, Exception (BadFormException c)
) => MakeDictM c m ()
keyringEntries = do
entry $ bindMatch "hbs2:keyring:list-encryption" $ \syn -> do
lbs <- case syn of
[ ListVal [ SymbolVal "file", StringLike fn ] ] -> do
liftIO $ BS.readFile fn
[ LitStrVal s ] -> do
pure (BS8.pack (Text.unpack s))
_ -> throwIO (BadFormException @C nil)
cred <- pure (parseCredentials @'HBS2Basic (AsCredFile lbs))
`orDie` "bad keyring file"
let e = [ mkStr @c (show (pretty (AsBase58 p))) | KeyringEntry p _ _ <- view peerKeyring cred ]
pure $ mkList @c e
brief "creates a new keyring (credentials)"
$ args [arg "int?" "encrypt-keys-num"]
$ returns "keyring" "string"
$ entry $ bindMatch "hbs2:keyring:new" $ \syn -> do
n <- case syn of
[LitIntVal k] -> pure k
[] -> pure 1
_ -> throwIO (BadFormException @C nil)
cred0 <- newCredentials @'HBS2Basic
cred <- foldM (\cred _ -> addKeyPair Nothing cred) cred0 [1..n]
pure $ mkStr @c $ show $ pretty $ AsCredFile $ AsBase58 cred
entry $ bindMatch "hbs2:keyring:show" $ \case
[StringLike fn] -> do
bs <- liftIO $ BS.readFile fn
cred <- parseCredentials @'HBS2Basic (AsCredFile bs)
& orThrowUser "bad credentials file"
pure $ mkStr $ show $ pretty (ListKeyringKeys cred)
_ -> throwIO $ BadFormException @c nil

View File

@ -0,0 +1,129 @@
module HBS2.CLI.Run.LWWRef where
import HBS2.CLI.Prelude
import HBS2.CLI.Run.Internal
import HBS2.CLI.Run.Internal.KeyMan
import HBS2.Data.Types.Refs
import HBS2.Storage
import HBS2.Peer.CLI.Detect
import HBS2.Peer.RPC.Client.Unix
import HBS2.Peer.RPC.Client
import HBS2.Peer.RPC.API.Peer
import HBS2.Peer.RPC.API.LWWRef
import HBS2.Peer.Proto.LWWRef
import HBS2.Base58
import HBS2.Net.Auth.Credentials
import HBS2.Net.Auth.Schema()
import HBS2.Data.Types.SignedBox
import HBS2.KeyMan.Keys.Direct
import HBS2.KeyMan.App.Types
import Control.Monad.Trans.Cont
lwwRefEntries :: forall c m . ( IsContext c
, MonadUnliftIO m
, Exception (BadFormException c)
, HasClientAPI PeerAPI UNIX m
, HasClientAPI LWWRefAPI UNIX m
) => MakeDictM c m ()
lwwRefEntries = do
brief "creates a new lwwref"
$ desc "Creates a new keyring; adds it to keyman and subsribes hbs2-peer to listen this lwwref"
$ returns "string" "lwwref public key"
$ entry $ bindMatch "hbs2:lwwref:create" $ \case
[] -> do
key <- keymanNewCredentials (Just "lwwref") 0
api <- getClientAPI @PeerAPI @UNIX
void $ callService @RpcPollAdd api (key, "lwwref", 31)
pure $ mkForm "pk" [mkStr (show $ pretty (AsBase58 key))]
_ -> throwIO (BadFormException @C nil)
brief "lists all lwwref that hbs2-peer is subscribed to"
$ noArgs
$ returns "list of string" "lwwref list"
$ entry $ bindMatch "hbs2:lwwref:list" $ \case
[] -> do
api <- getClientAPI @PeerAPI @UNIX
r <- callService @RpcPollList2 api (Just "lwwref", Nothing)
>>= orThrowUser "can't get lwwref list"
pure $ mkList $ fmap (mkStr . show . pretty . AsBase58 . view _1) r
_ -> throwIO (BadFormException @C nil)
brief "fetches lwwref value"
$ desc "makes peer to request lwwref from neighbors"
$ args [arg "string" "lwwref"]
$ returns "atom" "okay"
$ entry $ bindMatch "hbs2:lwwref:fetch" $ \case
[StringLike puk] -> do
lww <- orThrowUser "bad lwwref key" (fromStringMay puk)
api <- getClientAPI @LWWRefAPI @UNIX
void $ callService @RpcLWWRefFetch api lww
pure $ mkStr "okay"
_ -> throwIO (BadFormException @C nil)
brief "get lwwref value"
$ args [arg "string" "lwwref"]
$ returns "string" "hashref"
$ examples [qc|
(hbs2:lwwref:get BTThPdHKF8XnEq4m6wzbKHKA6geLFK4ydYhBXAqBdHSP)
(lwwref
(seq 4)
(value "74vDGwBYebH3oM6xPXC7kqpgu6deqi7E549QpvHvvQKf")
)
|]
$ entry $ bindMatch "hbs2:lwwref:get" $ \case
[StringLike puk] -> do
ref <- orThrowUser "bad lwwref key" (fromStringMay puk)
api <- getClientAPI @LWWRefAPI @UNIX
what <- callService @RpcLWWRefGet api ref
>>= orThrowUser "can't get lwwref value"
pure $ mkStr (show $ pretty what)
_ -> throwIO (BadFormException @C nil)
brief "updates lwwref"
$ desc "updates lwwref value and increments it's counter"
$ args [arg "string" "lwwref", arg "string" "hash"]
$ returns "nil" ""
$ entry $ bindMatch "hbs2:lwwref:update" $ \case
[StringLike puks, HashLike new] -> do
puk <- orThrowUser "bad lwwref key" (fromStringMay puks)
api <- getClientAPI @LWWRefAPI @UNIX
(sk,pk) <- liftIO $ runKeymanClient do
creds <- loadCredentials puk
>>= orThrowUser "can't load credentials"
pure ( view peerSignSk creds, view peerSignPk creds )
what <- callService @RpcLWWRefGet api puk
>>= orThrowUser "can't get lwwref value"
sno' <- case what of
Nothing -> pure 0
Just lwwv -> pure (lwwSeq lwwv)
let sno = succ sno'
let box = makeSignedBox pk sk (LWWRef sno new Nothing)
callService @RpcLWWRefUpdate api box
>>= orThrowUser "lww ref update error"
pure nil
_ -> throwIO (BadFormException @C nil)

View File

@ -0,0 +1,305 @@
module HBS2.CLI.Run.Mailbox where
import HBS2.CLI.Prelude
import HBS2.CLI.Run.Internal
import HBS2.CLI.Run.Internal.Merkle
import HBS2.Net.Auth.GroupKeySymm
import HBS2.Peer.Proto.Mailbox
import HBS2.Peer.Proto.Mailbox.Policy.Basic
import HBS2.Base58
import HBS2.System.Dir
import HBS2.Data.Types.Refs
import HBS2.Hash
import HBS2.Storage
import HBS2.KeyMan.Keys.Direct as K
import Codec.Serialise
import Data.Text qualified as Text
import Data.Text.Encoding (encodeUtf8)
import Control.Monad.Except
import Data.ByteString qualified as BS
import Data.ByteString.Lazy qualified as LBS
import Data.ByteString.Lazy.Char8 qualified as LBS8
import Data.Coerce
import Data.Either
createShortMessageFromByteString :: forall s m . ( MonadUnliftIO m
, s ~ HBS2Basic
, HasStorage m
)
=> LBS8.ByteString
-> m (Message s)
createShortMessageFromByteString lbs = do
let ls0 = LBS8.lines lbs
let (hbs, rest1) = break LBS8.null ls0
let payload = dropWhile LBS8.null rest1 & LBS8.unlines
let headers = parseTop (LBS8.unpack (LBS8.unlines hbs)) & fromRight mempty
flagz <- defMessageFlags
sender <- headMay [ Left s | ListVal [SymbolVal "sender", HashLike s] <- headers ]
& orThrowUser "sender not defined"
let rcpts = [ Left s | ListVal [SymbolVal "recipient", HashLike s] <- headers ]
sto <- getStorage
let cms = CreateMessageServices
sto
( runKeymanClientRO . loadCredentials )
( runKeymanClientRO . loadKeyRingEntry )
createMessage cms flagz Nothing sender rcpts mempty (LBS8.toStrict payload)
mailboxEntries :: forall c m . ( IsContext c
, MonadUnliftIO m
, HasStorage m
, Exception (BadFormException c)
) => MakeDictM c m ()
mailboxEntries = do
brief "creates a new object of Message from file"
$ args [arg "string" "filename"]
$ desc [qc|
hbs2:mailbox:message:create:short:file FILENAME
FILENAME is file with format:
field1 VALUE
field2 VALUE
<blank>
message text...
<EOF>
;;
supported fields:
sender <SIGIL-HASH>
recipient <SIGIL-HASH>
|]
$ returns "blob" "message"
$ entry $ bindMatch "hbs2:mailbox:message:create:short:file" $ \case
[StringLike fn] -> lift do
lbs <- liftIO $ LBS8.readFile fn
mess <- createShortMessageFromByteString lbs
mkOpaque (serialise mess)
_ -> throwIO (BadFormException @c nil)
brief "creates a new multipart message"
$ desc [qc|
;; creates multipart message
hbs2:mailbox:message:create:multipart [kw k1 v1 kn kv]
WHERE
k ::= sender | recipient | body | part
sender ::= HASH(sigil)
body ::= STRING
part ::= FILENAME
|]
$ examples [qc|
[hbs2:peer:storage:block:put
[hbs2:mailbox:message:create:multipart
[kw sender ghna99Xtm33ncfdUBT3htBUoEyT16wTZGMdm24BQ1kh
recipient 4e9moTcp9AW13wRYYWg5F8HWooVH1PuQ7zsf5g2JYPWj
body [str:file body.txt]
part patch1.patch
]]]
NOTE:
Each "part" will be represented as encrypted merkle tree
with metadata, i.e. it will be created in storage.
So it's a good idea to remove excessive/unrequired trees using
hbs2 del -r command.
|]
$ returns "bytes" "message"
$ entry $ bindMatch "hbs2:mailbox:message:create:multipart" $ \syn -> lift do
sto <- getStorage
let cms = CreateMessageServices
sto
( runKeymanClientRO . loadCredentials )
( runKeymanClientRO . loadKeyRingEntry )
flagz <- defMessageFlags
tsender <- newTVarIO Nothing
tbody <- newTVarIO (mempty :: LBS.ByteString)
trcpt <- newTVarIO mempty
tparts <- newTVarIO mempty
case syn of
[ListVal (SymbolVal "dict" : parts)] -> do
for_ parts $ \case
ListVal [StringLike "sender", HashLike ss] -> do
atomically $ writeTVar tsender (Just ss)
ListVal [StringLike "recipient", HashLike ss] -> do
atomically $ modifyTVar trcpt (ss:)
ListVal [StringLike "body", StringLike s] -> do
let lbs = encodeUtf8 (fromString s) & LBS.fromStrict
atomically $ modifyTVar tbody (LBS.append lbs)
ListVal [StringLike "part", StringLike fn] -> do
let what = takeFileName fn & fromString
let rfn = liftIO (LBS.readFile fn)
let meta = [("file-name:", what)]
atomically $ modifyTVar tparts ( [(meta,rfn)] <> )
_ -> pure ()
_ -> throwIO (BadFormException @c nil)
sender <- readTVarIO tsender >>= orThrowUser "sender not set"
rcpt <- readTVarIO trcpt <&> fmap Left
body <- readTVarIO tbody
parts <- readTVarIO tparts
mess <- createMessage cms flagz Nothing
(Left sender)
rcpt
parts
(LBS.toStrict body)
mkOpaque (serialise mess)
entry $ bindMatch "hbs2:mailbox:message:dump" $ nil_ \syn -> lift do
lbs <- case syn of
[ HashLike h ] -> do
sto <- getStorage
getBlock sto (coerce h) >>= orThrowUser "message not found"
[ StringLike fn ] -> do
liftIO $ LBS.readFile fn
_ -> throwIO (BadFormException @c nil)
let rms = ReadMessageServices ( liftIO . runKeymanClientRO . extractGroupKeySecret)
(s,mess,co) <- deserialiseOrFail @(Message HBS2Basic) lbs
& orThrowUser "malformed message"
>>= readMessage rms
-- TODO: implement-normally
liftIO do
print $ "sender" <+> pretty (AsBase58 s)
for_ (messageRecipients mess) $ \r -> do
print $ "recipient" <+> pretty (AsBase58 r)
for_ (messageParts mess) $ \p -> do
print $ "attachment" <+> pretty p
putStrLn ""
BS.putStr co
entry $ bindMatch "hbs2:mailbox:message:read:file" $ nil_ \case
[StringLike s] -> lift do
sto <- getStorage
let rms = ReadMessageServices ( liftIO . runKeymanClientRO . extractGroupKeySecret)
(s,_,bs) <- liftIO (LBS.readFile s)
<&> deserialiseOrFail @(Message HBS2Basic)
>>= orThrowUser "invalid message format"
>>= readMessage rms
liftIO $ BS.putStr bs
_ -> throwIO (BadFormException @c nil)
entry $ bindMatch "hbs2:mailbox:message:read:storage" $ nil_ \case
[HashLike h] -> lift do
sto <- getStorage
let rms = ReadMessageServices ( liftIO . runKeymanClientRO . extractGroupKeySecret)
(s,_,bs) <- getBlock sto (coerce h)
>>= orThrowUser "message not found"
<&> deserialiseOrFail @(Message HBS2Basic)
>>= orThrowUser "invalid message format"
>>= readMessage rms
liftIO $ BS.putStr bs
_ -> throwIO (BadFormException @c nil)
entry $ bindMatch "hbs2:mailbox:policy:basic:read:syntax" $ \case
[ListVal syn] -> do
po <- parseBasicPolicy syn >>= orThrowUser "malformed policy"
mkOpaque po
_ -> throwIO (BadFormException @c nil)
entry $ bindMatch "hbs2:mailbox:policy:basic:read:file" $ \case
[StringLike fn] -> lift do
what <- liftIO (readFile fn)
<&> parseTop
>>= either (error.show) pure
>>= parseBasicPolicy
>>= orThrowUser "invalid policy"
mkOpaque what
_ -> throwIO (BadFormException @c nil)
entry $ bindMatch "hbs2:mailbox:policy:basic:read:storage" $ \case
[HashLike href] -> lift do
sto <- getStorage
what <- runExceptT (getTreeContents sto href)
>>= orThrowPassIO
<&> parseTop . LBS8.unpack
>>= either (error.show) pure
>>= parseBasicPolicy
>>= orThrowUser "invalid policy"
mkOpaque what
_ -> throwIO (BadFormException @c nil)
entry $ bindMatch "hbs2:mailbox:policy:basic:accept:peer" $ \case
[SignPubKeyLike who, OpaqueVal box] -> lift do
p <- fromOpaqueThrow @(BasicPolicy HBS2Basic) "expected BasicPolicy" box
r <- policyAcceptPeer @HBS2Basic p who
pure $ mkBool @c r
_ -> throwIO (BadFormException @c nil)
entry $ bindMatch "hbs2:mailbox:policy:basic:accept:sender" $ \case
[SignPubKeyLike who, OpaqueVal box] -> lift do
p <- fromOpaqueThrow @(BasicPolicy HBS2Basic) "expected BasicPolicy" box
r <- policyAcceptSender @HBS2Basic p who
pure $ mkBool @c r
_ -> throwIO (BadFormException @c nil)
entry $ bindMatch "hbs2:mailbox:policy:basic:dump" $ nil_ $ \case
[OpaqueVal box] -> lift do
p <- fromOpaqueThrow @(BasicPolicy HBS2Basic) "expected BasicPolicy" box
liftIO $ print $ vcat (fmap pretty (getAsSyntax @c p))
_ -> throwIO (BadFormException @c nil)

View File

@ -0,0 +1,307 @@
{-# Language MultiWayIf #-}
module HBS2.CLI.Run.MetaData
( metaDataEntries
, createTreeWithMetadata
, getTreeContents
, getGroupKeyHash
) where
import HBS2.CLI.Prelude
import HBS2.CLI.Run.Internal
import HBS2.CLI.Run.Internal.GroupKey as G
import HBS2.CLI.Run.Internal.Merkle
import HBS2.Data.Types.Refs
import HBS2.Merkle
import HBS2.System.Dir
import HBS2.Storage
import HBS2.Storage.Operations.ByteString
import HBS2.Net.Auth.Schema()
import HBS2.Peer.RPC.API.Storage
import HBS2.Peer.RPC.Client
import HBS2.Peer.RPC.Client.Unix
import Codec.Serialise
import Control.Monad.Trans.Maybe
import Control.Monad.Trans.Cont
import Data.ByteString.Lazy qualified as LBS
import Data.ByteString.Lazy (ByteString)
import Data.ByteString.Char8 qualified as BS8
import Data.Either
import Data.Set qualified as Set
import Data.HashMap.Strict qualified as HM
import Data.Maybe
import Data.Text.Encoding qualified as TE
import Data.Text qualified as Text
import Data.Text.IO qualified as TIO
import Magic.Data
import Magic.Init (magicLoadDefault,magicOpen)
import Magic.Operations (magicFile)
{- HLINT ignore "Functor law" -}
data CreateMetaDataOpt =
Auto
| Stdin
| Encrypted String
| MetaDataEntry Id String
| MetaDataFile FilePath
deriving stock (Eq,Ord,Show,Data,Generic)
txt :: Pretty a => a -> Text
txt a = Text.pack (show $ pretty a)
metaFromSyntax :: [Syntax c] -> HashMap Text Text
metaFromSyntax syn =
HM.fromList [ (t k, t v) | (ListVal [ k, v ]) <- syn ]
where
t x = Text.pack (show $ pretty x)
type ForMetadata c m = ( IsContext c
, MonadUnliftIO m
, Exception (BadFormException c)
, HasStorage m
, HasClientAPI StorageAPI UNIX m
)
metaDataEntries :: forall c m . ( ForMetadata c m
) => MakeDictM c m ()
metaDataEntries = do
brief "update group key for tree"
$ args [arg "string" "tree", arg "list" "update-ops"]
$ desc ( "update-ops is a list of pairs, like" <> line
<> indent 4 ( parens ("list"
<+> indent 2 (vcat [ parens "remove . PUBLIC-KEY-ID"
, parens "add . PUBLIC-KEY-ID"
]))))
$ returns "string" "new-tree-hash"
$ examples [qc|
(define gk (hbs2:groupkey:load 6XJGpJszP6f68fmhF17AtJ9PTgE7BKk8RMBHWQ2rXu6N))
(hbs2:groupkey:update gk
(list (remove . CcRDzezX1XQdPxRMuMKzJkfHFB4yG7vGJeTYvScKkbP8)
(add . EiwWxY3xwTfnLKJdzzxNMZgA2ZvYAZd9e8B8pFeCtnrn)))
|]
$ entry $ bindMatch "hbs2:tree:metadata:update-gk" $ \case
[StringLike tree, ListVal ins] -> do
ha <- orThrowUser "invalid hash" (fromStringMay tree)
-- 1. load-group-key
(gkh', headBlk) <- getGroupKeyHash ha
gkh <- orThrowUser "not encrypted" gkh'
gk <- loadGroupKey gkh
>>= orThrowUser "can't load gk"
gk1 <- modifyGroupKey gk ins
sto <- getStorage
gk1h <- writeAsMerkle sto (serialise gk1)
case headBlk of
w@(MTreeAnn { _mtaCrypt = EncryptGroupNaClSymm _ nonce }) -> do
let w1 = w { _mtaCrypt = EncryptGroupNaClSymm gk1h nonce }
h <- putBlock sto (serialise w1)
>>= orThrowUser "can't put block"
pure $ mkStr (show $ pretty h)
_ -> pure nil
_ -> throwIO (BadFormException @c nil)
brief "get group key from encrypted tree"
$ args [arg "string" "tree-hash"]
$ returns "group-key-hash" "string"
$ examples [qc|
(hbs2:tree:metadata:get-gk 7J2BZYskBjmDsWZHvVoGGorZDrFYkbbQweRauaYGSTNd)
5fshZRucawt47YJLuD1rVXRez2dcvCbz17m69YyduTEm
|]
$ entry $ bindMatch "hbs2:tree:metadata:get-gk" $ \case
[ StringLike hash ] -> flip runContT pure do
(gk,_) <- lift $ getGroupKeyHash (fromString hash)
case gk of
Just h -> pure $ mkStr (show $ pretty h)
_ -> pure nil
_ -> throwIO (BadFormException @c nil)
brief "get metadata from tree"
$ args [arg "symbol?" "method", arg "string" "tree-hash"]
$ returns "group-key-hash" "string"
$ desc ( opt "symbol?" ":parsed" <+> "return metadata as dict" <> line
<> "if other value or absense then return metadata as string"
)
$ examples [qc|
(hbs2:tree:metadata:get 7J2BZYskBjmDsWZHvVoGGorZDrFYkbbQweRauaYGSTNd)
((mime-type: "text/plain; charset=us-ascii") (file-name: "qqq.txt"))
(hbs2:tree:metadata:get :raw 7J2BZYskBjmDsWZHvVoGGorZDrFYkbbQweRauaYGSTNd
mime-type: "text/plain; charset=us-ascii"
file-name: "qqq.txt"
|]
$ entry $ bindMatch "hbs2:tree:metadata:get"
$ \case
[ StringLike hash ] -> do
r <- flip runContT pure do
sto <- getStorage
runMaybeT do
headBlock <- getBlock sto (fromString hash)
>>= toMPlus
<&> deserialiseOrFail @(MTreeAnn [HashRef])
>>= toMPlus
case headBlock of
MTreeAnn { _mtaMeta = ShortMetadata s } -> do
pure $ mkStr s
MTreeAnn { _mtaMeta = AnnHashRef h, _mtaCrypt = NullEncryption } -> do
getBlock sto h
>>= toMPlus
<&> LBS.toStrict
<&> TE.decodeUtf8
<&> mkStr
MTreeAnn { _mtaMeta = AnnHashRef h } -> do
getBlock sto h
>>= toMPlus
<&> deserialiseOrFail @(SmallEncryptedBlock AnnMetaData)
>>= toMPlus
>>= lift . lift . G.decryptBlock sto
<&> \case
ShortMetadata s -> mkStr s
_ -> nil
_ -> mzero
maybe1 r (pure nil) $ \case
TextLike r0 -> do
let xs = parseTop r0
& either mempty (fmap fixContext)
pure $ mkList xs
_ -> pure $ fromMaybe nil r
_ -> throwIO (BadFormException @c nil)
let metadataCreateMan = brief "creates a tree with metadata"
let kw = arg "kw" "opts"
metadataCreateMan $ args [kw, arg "string" "filename"] $
entry $ bindMatch "hbs2:tree:metadata:file" $ \case
[ syn@(ListVal{}), StringLike fn ] -> do
meta0 <- liftIO do
magic <- magicOpen [MagicMimeType,MagicMime,MagicMimeEncoding]
magicLoadDefault magic
mime <- magicFile magic fn
pure $ HM.fromList [ ("file-name", Text.pack (takeFileName fn))
, ("mime-type", Text.pack mime)
]
doCreateMetadataTree meta0 syn (liftIO $ LBS.readFile fn)
_ -> throwIO (BadFormException @c nil)
metadataCreateMan $ args [kw] $
entry $ bindMatch "hbs2:tree:metadata:stdin" $ \case
[syn@(ListVal{})] -> do
_reader <- hIsTerminalDevice stdin >>= \case
_ -> pure (liftIO LBS.getContents)
doCreateMetadataTree mempty syn _reader
_ -> throwIO (BadFormException @c nil)
metadataCreateMan $ args [kw, arg "string" "input"] $
entry $ bindMatch "hbs2:tree:metadata:string" $ \case
[ syn@(ListVal{}), TextLike content ] -> do
-- liftIO $ TIO.putStr content
doCreateMetadataTree mempty syn (pure $ LBS.fromStrict $ TE.encodeUtf8 content)
_ -> throwIO (BadFormException @c nil)
entry $ bindMatch "cbor:base58" $ \case
[ LitStrVal x ] -> do
pure $ mkForm "cbor:base58" [mkStr x]
_ -> throwIO (BadFormException @c nil)
groupKeyFromSyntax :: Syntax c -> Either (Syntax c) (Maybe HashRef)
groupKeyFromSyntax = \case
ListVal es -> do
let mbGk = headMay [ z | z@(ListVal [ TextLike "gk", v ]) <- es ]
case mbGk of
Just (ListVal [ TextLike "gk", HashLike v]) -> Right (Just v)
Just w@(ListVal [ TextLike "gk", v]) -> Left w
_ -> Right Nothing
_ -> Right Nothing
loadGroupKeyFromSyntax :: ( ForMetadata c m )
=> Syntax c
-> RunM c m (Maybe (GroupKey 'Symm 'HBS2Basic))
loadGroupKeyFromSyntax syn = runMaybeT do
hash <- case groupKeyFromSyntax syn of
Right w -> toMPlus w
Left e -> throwIO (BadFormException e)
toMPlus =<< lift (loadGroupKey hash)
metadataFromSyntax :: Syntax c -> HashMap Text Text
metadataFromSyntax = \case
ListVal es -> HM.fromList [ (k,v) | ListVal [ TextLike k, TextLike v] <- es, k /= "gk" ]
_ -> mempty
doCreateMetadataTree :: forall c m . ForMetadata c m
=> HashMap Text Text
-> Syntax c
-> m ByteString
-> RunM c m (Syntax c)
doCreateMetadataTree meta0 syn getLbs = do
let meta = metadataFromSyntax syn
let gkh = groupKeyFromSyntax syn
gk <- loadGroupKeyFromSyntax syn
-- notice $ "GK" <+> pretty (isRight gkh) <+> pretty gk
case (gkh, gk) of
(Right (Just _), Nothing) -> throwIO (GroupKeyNotFound 1)
_ -> none
sto <- getStorage
lbs <- lift getLbs
href <- lift (createTreeWithMetadata sto gk (meta0 <> meta) lbs)
>>= orThrow StorageError
pure $ mkStr (show $ pretty href)

View File

@ -0,0 +1,145 @@
{-# Language ViewPatterns #-}
{-# Language PatternSynonyms #-}
module HBS2.CLI.Run.Peer where
import HBS2.CLI.Prelude
import HBS2.CLI.Run.Internal
import HBS2.Hash
import HBS2.Base58
import HBS2.Data.Types.Refs
import HBS2.Storage
import HBS2.Peer.RPC.Client
import HBS2.Peer.CLI.Detect
import HBS2.Peer.RPC.Client.Unix
import HBS2.Peer.RPC.API.Peer
import HBS2.Peer.RPC.API.RefLog
import HBS2.Peer.RPC.API.Storage
import HBS2.Peer.RPC.API.LWWRef
import HBS2.Net.Auth.Schema()
import Data.List qualified as L
import Data.Maybe
import Control.Monad.Trans.Cont
import Data.Text qualified as Text
import Data.ByteString qualified as BS
import Data.ByteString.Lazy.Char8 qualified as LBS8
import Data.ByteString.Lazy qualified as LBS
import Lens.Micro.Platform
import Text.InterpolatedString.Perl6 (qc)
{- HLINT ignore "Functor law" -}
putTextLit :: forall c m . (IsContext c, MonadUnliftIO m)
=> AnyStorage
-> Text
-> RunM c m (Syntax c)
putTextLit sto s = do
h <- putBlock sto (LBS8.pack (Text.unpack s))
`orDie` "can't store block"
<&> HashRef
pure (mkStr @c (show $ pretty h))
peerEntries :: forall c m . ( IsContext c
, MonadUnliftIO m
, HasClientAPI PeerAPI UNIX m
, HasClientAPI StorageAPI UNIX m
, HasStorage m
, Exception (BadFormException c)
) => MakeDictM c m ()
peerEntries = do
entry $ bindMatch "hbs2:peer:detect" $ \case
_ -> detectRPC <&> maybe (nil @c) mkStr
entry $ bindMatch "hbs2:peer:storage:block:get" $ \case
[StringLike s] -> do
flip runContT pure do
sto <- getStorage
ha <- pure (fromStringMay @HashRef s)
`orDie` "invalid hash"
lbs <- getBlock sto (fromHashRef ha)
`orDie` show ("missed-block" <+> pretty ha)
mkOpaque lbs
_ -> throwIO $ BadFormException @c nil
entry $ bindMatch "hbs2:peer:storage:block:del" $ \case
[HashLike ha] -> do
flip runContT pure do
sto <- getStorage
delBlock sto (fromHashRef ha)
pure nil
_ -> throwIO $ BadFormException @c nil
entry $ bindMatch "hbs2:peer:storage:block:size" $ \case
[HashLike ha] -> do
flip runContT pure do
sto <- getStorage
mbsz <- hasBlock sto (fromHashRef ha)
pure $ maybe (mkSym "no-block") mkInt mbsz
_ -> throwIO $ BadFormException @c nil
-- stores *small* block
entry $ bindMatch "hbs2:peer:storage:block:put" $ \case
[isOpaqueOf @LBS.ByteString -> Just lbs] -> do
sto <- getStorage
(putBlock sto lbs <&> fmap (mkSym . show . pretty . HashRef) )
>>= orThrowUser "storage error"
[isOpaqueOf @BS.ByteString -> Just bs] -> do
sto <- getStorage
(putBlock sto (LBS.fromStrict bs) <&> fmap (mkSym . show . pretty . HashRef) )
>>= orThrowUser "storage error"
-- FIXME: deprecate-this
[ListVal [SymbolVal "blob", LitStrVal s]] -> do
flip runContT pure do
sto <- getStorage
lift $ putTextLit sto s
[LitStrVal s] -> do
flip runContT pure do
sto <- getStorage
lift $ putTextLit sto s
[] -> do
bs <- liftIO BS.getContents
sto <- getStorage
putBlock sto (LBS.fromStrict bs) >>= \case
Nothing -> pure nil
Just h -> pure $ mkSym (show $ pretty $ HashRef h)
e -> throwIO $ BadFormException @c (mkList e)
brief "checks if peer available"
$ noArgs
$ returns "dict" "dictionary of peer attributes"
$ examples [qc|
(hbs2:peer:poke)
(
(peer-key: "35gKUG1mwBTr3tQpjWwR2kBYEnDmHxesoJL5Lj7tMjq3")
(udp: "0.0.0.0:7354")
(tcp: "tcp://0.0.0.0:3001")
(local-multicast: "239.192.152.145:10153")
(rpc: "/tmp/hbs2-rpc.socket")
(http-port: 5000))
|]
$ entry $ bindMatch "hbs2:peer:poke" $ \case
_ -> do
api <- getClientAPI @PeerAPI @UNIX
callRpcWaitMay @RpcPoke (TimeoutSec 1) api ()
<&> fromMaybe ""
<&> parseTop
<&> either (const nil) (mkList . fmap fixContext)

View File

@ -0,0 +1,405 @@
module HBS2.CLI.Run.RefChan
( module HBS2.CLI.Run.RefChan
, keymanNewCredentials
) where
import HBS2.CLI.Prelude
import HBS2.CLI.Run.Internal
import HBS2.CLI.Run.Internal.KeyMan
import HBS2.CLI.Run.Internal.RefChan
import HBS2.Data.Types.Refs
import HBS2.Peer.CLI.Detect
import HBS2.Peer.RPC.Client.Unix
import HBS2.Peer.RPC.API.Peer
import HBS2.Peer.RPC.API.RefChan
import HBS2.Peer.RPC.Client.RefChan as Client
import HBS2.Storage.Operations.ByteString
-- import HBS2.Net.Proto
-- import HBS2.Net.Auth.Credentials
-- import HBS2.Base58
-- import HBS2.Defaults
-- import HBS2.Events
-- import HBS2.Peer.Proto.Peer
-- import HBS2.Net.Proto.Sessions
import HBS2.Data.Types.Refs
import HBS2.Data.Detect
-- import HBS2.Data.Types.SignedBox
-- import HBS2.Storage
import HBS2.Peer.Proto.RefChan
import Data.Either
import HBS2.Base58
import HBS2.Net.Auth.Credentials
import HBS2.Net.Auth.Schema()
import HBS2.Data.Types.SignedBox
import HBS2.Peer.RPC.Client
import HBS2.Peer.RPC.API.Storage
import HBS2.Storage
import HBS2.KeyMan.Keys.Direct
import HBS2.KeyMan.App.Types
import Data.HashMap.Strict qualified as HM
import Data.HashSet qualified as HS
import Data.Coerce
import Control.Monad.Trans.Cont
import Control.Monad.Trans.Maybe
import Control.Monad.Except
import Data.ByteString.Lazy qualified as LBS
import Data.ByteString.Lazy.Char8 qualified as LBS8
import Data.ByteString.Char8 qualified as BS8
import Data.Text qualified as Text
import Codec.Serialise
import Control.Concurrent.STM qualified as STM
import Text.InterpolatedString.Perl6 (qc)
import Streaming.Prelude qualified as S
refchanEntries :: forall c m . ( IsContext c
, MonadUnliftIO m
, Exception (BadFormException c)
, HasClientAPI RefChanAPI UNIX m
, HasClientAPI StorageAPI UNIX m
, HasClientAPI PeerAPI UNIX m
, HasStorage m
) => MakeDictM c m ()
refchanEntries = do
brief "requests all rechans that peer is subcribed to"
$ args []
$ returns "list" "list of all refchans"
$ examples [qc|
(hbs2:refchan:list)
("Atg67E6CPMJWKvR9BvwZTTEjg3Hjz4CYCaEARGANepGP"
"A5W6jPBjzvdpxaQ2e8xBLYaRZjPXzi4yX7xjC52gTiKk"
"EjjK7rpgRRJ4yzAhTcwis4XawwagCbmkns8n73ogY3uS")
|]
$ entry $ bindMatch "hbs2:refchan:list" $ \case
[] -> do
flip runContT pure do
api <- getClientAPI @PeerAPI @UNIX
r <- callService @RpcPollList2 api (Just "refchan", Nothing)
>>= orThrowUser "can't get refchan list"
pure $ mkList $ fmap (mkStr . show . pretty . AsBase58 . view _1) r
_ -> throwIO (BadFormException @c nil)
brief "reads refchan head block"
$ args [arg "symbol" "parsed|_", arg "string" "PUBKEY"]
$ returns "" "string"
$ examples [qc|
(hbs2:refchan:head:get :parsed ExTZuEy2qVBRshWdSwfxeKcMmQLbK2f5NxRwhvXda9qd)
(version 2)
(quorum 1)
(wait 10)
(peer "5tZfGUoQ79EzFUvyyY5Wh1LzN2oaqhrn9kPnfk6ByHpf" 1)
(peer "35gKUG1mwBTr3tQpjWwR2kBYEnDmHxesoJL5Lj7tMjq3" 1)
(peer "5GnroAC8FXNRL8rcgJj6RTu9mt1AbuNd5MZVnDBcCKzb" 1)
(author "Gu5FxngYYwpRfCUS9DJBGyH3tvtjXFbcZ7CbxmJPWEGH")
(author "ExTZuEy2qVBRshWdSwfxeKcMmQLbK2f5NxRwhvXda9qd")
(reader "5UXrEhYECJ2kEQZZPEf4TisfWsLNdh2nGYQQz8X9ioMv")
(reader "CcRDzezX1XQdPxRMuMKzJkfHFB4yG7vGJeTYvScKkbP8")
; (head-extensions: (count: 0) (size 0))
(hbs2:refchan:head:get :whatever ExTZuEy2qVBRshWdSwfxeKcMmQLbK2f5NxRwhvXda9qd)
HucjFUznHJeA2UYZCdUFHtnE3pTwhCW5Dp7LV3ArZBcr
|]
$ entry $ bindMatch "hbs2:refchan:head:get" $ \case
[StringLike what, SignPubKeyLike puk] -> do
flip runContT pure do
callCC $ \exit -> do
w <- lift (getRefChanHeadHash @UNIX puk)
hx <- ContT $ maybe1 w (pure nil)
case what of
"parsed" -> do
hdblk <- lift (Client.getRefChanHead @UNIX puk)
exit $ mkStr (show $ pretty hdblk)
_ -> exit $ mkStr (show $ pretty $ AsBase58 hx)
pure nil
_ -> throwIO (BadFormException @c nil)
entry $ bindMatch "hbs2:refchan:head:update" $ \syn -> do
(rchan, rch) <- case syn of
[SignPubKeyLike rchan, StringLike headFile] -> do
rch <- liftIO (readFile headFile)
<&> fromStringMay @(RefChanHeadBlock L4Proto)
>>= orThrowUser "can't parse RefChanHeadBlock"
pure (rchan, rch)
[SignPubKeyLike rchan, ListVal syn] -> do
rch <- fromStringMay @(RefChanHeadBlock L4Proto) (show $ vcat (fmap pretty syn))
& orThrowUser "can't parse RefChanHeadBlock"
pure (rchan, rch)
_ -> throwIO (BadFormException @c nil)
sto <- getStorage
rchanApi <- getClientAPI @RefChanAPI @UNIX
creds <- runKeymanClient $ loadCredentials rchan
>>= orThrowUser "can't load credentials"
let box = makeSignedBox @'HBS2Basic (view peerSignPk creds) (view peerSignSk creds) rch
href <- writeAsMerkle sto (serialise box)
callService @RpcRefChanHeadPost rchanApi (HashRef href)
>>= orThrowUser "can't post refchan head"
pure nil
entry $ bindMatch "hbs2:refchan:get" $ \case
[SignPubKeyLike rchan] -> do
api <- getClientAPI @RefChanAPI @UNIX
h <- callService @RpcRefChanGet api rchan
>>= orThrowUser "can't request refchan head"
pure $ maybe nil (mkStr . show . pretty . AsBase58) h
_ -> throwIO (BadFormException @c nil)
entry $ bindMatch "hbs2:refchan:create" $ \syn -> do
peerApi <- getClientAPI @PeerAPI @UNIX
rch <- case syn of
[ListVal es] -> do
fromStringMay @(RefChanHeadBlock L4Proto) (show $ vcat (fmap pretty es))
& orThrowUser "Invalid refchan head syntax"
[StringLike headFile] -> do
liftIO (readFile headFile)
<&> fromStringMay @(RefChanHeadBlock L4Proto)
>>= orThrowUser "can't parse RefChanHeadBlock"
[] -> do
poked <- callService @RpcPoke peerApi ()
>>= orThrowUser "can't poke hbs2-peer"
<&> parseTop
>>= orThrowUser "invalid hbs2-peer attributes"
ke <- [ x
| ListVal [SymbolVal "peer-key:", SignPubKeyLike x] <- poked
] & headMay & orThrowUser "hbs2-peer key not found"
let rch0 = refChanHeadDefault @L4Proto
& set refChanHeadPeers (HM.singleton ke 1)
& set refChanHeadAuthors (HS.singleton ke)
pure rch0
_ -> throwIO (BadFormException @c nil)
refchan <- createNewRefChan @c Nothing rch
pure $ mkSym (show $ pretty (AsBase58 refchan))
brief "prints refchan head example"
$ returns "nil" mempty
$ entry $ bindMatch "hbs2:refchan:head:example" $ nil_ $ \case
[] -> flip runContT pure do
let rch0 = refChanHeadDefault @L4Proto
api <- getClientAPI @PeerAPI @UNIX
pips <- callService @RpcPeers api ()
<&> either (const mempty) (HM.fromList . fmap ((,1) . fst) . take 3)
creds <- replicateM 3 (newCredentialsEnc @HBS2Basic 1)
let authors = fmap (view peerSignPk) creds
& HS.fromList
let readers = foldMap (view peerKeyring) creds
& fmap (view krPk)
& take 3
& HS.fromList
let rch = ( set refChanHeadPeers pips
. set refChanHeadAuthors authors
. set refChanHeadReaders readers
. set refChanHeadNotifiers authors
) rch0
liftIO $ print $
";" <+> "this is an example of refchan head block config"
<> line
<> ";" <+> "edit it before applying" <> line
<> ";" <+> "set up the actual keys / credentials you need" <> line
<> line <> line
<> ";" <+> "(version INT) is the head block version" <> line
<> ";" <+> "the refchan head block will be set only" <>line
<> ";" <+> "if it's version if greater than the already existed one" <> line
<> line
<> ";" <+> "(quorum INT) is a number of accept messages issued by peers" <> line
<> ";" <+> "to include propose message to the refchan" <> line
<> line
<> ";" <+> "(wait INT) is an quorum wait time in seconds" <> line
<> line
<> ";" <+> "(peer PUBKEY WEIGHT) sets the peer allowed for posting propose/accept messages" <> line
<> ";" <+> "PUBKEY is a SIGNATURE public key as base58 string" <> line
<> ";" <+> "only messages from that peers will be accepted" <> line
<> ";" <+> "WEIGHT is not used yet but reserved for the future" <> line
<> ";" <+> "this parameter is optional but there is should be some peers or" <> line
<> ";" <+> "all messages will be sent to nowhere" <> line
<> line
<> ";" <+> "(author PUBKEY) adds 'author' i.e. key that is allowed to sign the propose message" <> line
<> ";" <+> "PUBKEY is a SIGNATURE public key as base58 string" <> line
<> ";" <+> "only the propose messages signed by one of thise keys will be accepted" <> line
<> line
<> ";" <+> "(notifier PUBKEY) adds 'notifier' i.e. key that is allowed to sign the notify message" <> line
<> ";" <+> "PUBKEY is a SIGNATURE public key as base58 string" <> line
<> ";" <+> "only the propose messages signed by one of thise keys will be accepted" <> line
<> ";" <+> "notify messages are not written to the refchan merkle tree" <> line
<> ";" <+> "and they useful for implementing any sort of ephemeral messaging" <> line
<> ";" <+> "those clauses are OPTIONAL and may be omitted" <> line
<> line
<> ";" <+> "(reader PUBKEY) adds 'author' i.e. key that is allowed to decrypt messages" <> line
<> ";" <+> "PUBKEY is a ENCRYPTION public key as base58 string" <> line
<> ";" <+> "NOTE: messages in a refchan are not encrypted by default" <> line
<> ";" <+> " it's totally up to an application for this refchan" <> line
<> ";" <+> " therefore this clause is just used for setting reader keys to" <> line
<> ";" <+> " implement any ACL/encrypting mechanism" <> line
<> ";" <+> " i.e. groupkey may be inherited from the RefChanHead block" <> line
<> ";" <+> " to encrypt data posted to a refchan" <> line
<> ";" <+> "those clauses are OPTIONAL and may be omitted" <> line
<> line
<> pretty rch
_ -> throwIO (BadFormException @c nil)
brief "creates RefChanUpdate/AnnotatedHashRef transaction for refchan" $
args [arg "string" "sign-key", arg "string" "payload-tree-hash"] $
entry $ bindMatch "hbs2:refchan:tx:annref:create" $ \case
[SignPubKeyLike signpk, HashLike hash] -> do
sto <- getStorage
void $ hasBlock sto (fromHashRef hash) `orDie` "no block found"
let lbs = AnnotatedHashRef Nothing hash & serialise
creds <- runKeymanClientRO $ loadCredentials signpk >>= orThrowUser "can't find credentials"
let box = makeSignedBox @HBS2Basic (view peerSignPk creds) (view peerSignSk creds) (LBS.toStrict lbs) & serialise
pure $ mkForm @c "blob" [mkStr (LBS8.unpack box)]
_ -> throwIO (BadFormException @c nil)
brief "creates RefChanUpdate/SeqRef transaction for refchan" $
args [arg "string" "sign-key", arg "string" "payload-tree-hash", arg "(-t int)?" "seqno"] $
entry $ bindMatch "hbs2:refchan:tx:seqref:create" $ \syn -> do
now <- liftIO $ getPOSIXTime <&> round
let (opts, argz) = splitOpts [("-s",1)] syn
let s = headDef now [ x | MatchOption "-n" (LitIntVal x) <- opts]
case opts of
[SignPubKeyLike signpk, HashLike hash] -> do
sto <- getStorage
void $ hasBlock sto (fromHashRef hash) `orDie` "no block found"
let lbs = SequentialRef s (AnnotatedHashRef Nothing hash) & serialise
creds <- runKeymanClientRO $ loadCredentials signpk >>= orThrowUser "can't find credentials"
let box = makeSignedBox @HBS2Basic (view peerSignPk creds) (view peerSignSk creds) (LBS.toStrict lbs) & serialise
pure $ mkForm @c "blob" [mkStr (LBS8.unpack box)]
_ -> throwIO (BadFormException @c nil)
brief "creates RefChanUpdate/Raw transaction for refchan" $
args [arg "string" "sign-key", arg "string" "data"] $
entry $ bindMatch "hbs2:refchan:tx:raw:create" $ \syn -> do
case syn of
[SignPubKeyLike signpk, StringLike x] -> do
let lbs = LBS8.pack x & serialise
creds <- runKeymanClientRO $ loadCredentials signpk >>= orThrowUser "can't find credentials"
let box = makeSignedBox @HBS2Basic (view peerSignPk creds) (view peerSignSk creds) (LBS.toStrict lbs) & serialise
mkOpaque @c box
_ -> throwIO (BadFormException @c nil)
entry $ bindMatch "hbs2:refchan:tx:raw:list" $ \case
[SignPubKeyLike rchan] -> lift do
q <- newTQueueIO
walkRefChanTx @UNIX (const $ pure True) rchan $ \txh u -> do
case u of
A (AcceptTran (Just ts) self what) -> do
let tx = fromIntegral ts :: Integer
let hs = show $ pretty self
let they = show $ pretty what
let x = mkForm @c "accept" [ mkSym hs, mkInt tx, mkSym they ]
atomically $ writeTQueue q x
A _ -> none
P1 ppk h (ProposeTran _ box) -> void $ runMaybeT do
(pk, bs) <- unboxSignedBox0 box & toMPlus
bss <- deserialiseOrFail @LBS.ByteString (LBS.fromStrict bs) & toMPlus
e <- mkOpaque bss
let hs = show $ pretty h
let ppks = show (pretty (AsBase58 ppk))
let pks = show (pretty (AsBase58 pk))
let x = mkForm @c "propose" [ mkSym hs, mkSym ppks, mkSym pks, e ]
atomically $ writeTQueue q x
P0{} -> none
mkList <$> atomically (STM.flushTQueue q)
e -> throwIO (BadFormException @c (mkList e))
brief "posts Propose transaction to the refchan" $
args [arg "string" "refchan", arg "blob" "signed-box"] $
entry $ bindMatch "hbs2:refchan:tx:propose" $ nil_ $ \syn -> do
(chan,lbs) <- case syn of
[SignPubKeyLike rchan, ListVal [SymbolVal "blob", LitStrVal box]] -> do
bbox <- Text.unpack box & LBS8.pack & deserialiseOrFail & orThrowUser "bad transaction"
pure (rchan, bbox)
[SignPubKeyLike rchan, MatchOpaqueVal @_ @LBS.ByteString lbs] -> do
pure (rchan, lbs)
_ -> throwIO (BadFormException @c (mkList syn))
api <- getClientAPI @RefChanAPI @UNIX
box <- deserialiseOrFail lbs & orThrowUser "invalid box"
void $ callService @RpcRefChanPropose api (chan, box)

View File

@ -0,0 +1,234 @@
module HBS2.CLI.Run.RefLog
( module HBS2.CLI.Run.RefLog
, module HBS2.CLI.Run.Internal.RefLog
) where
import HBS2.CLI.Prelude
import HBS2.CLI.Run.Internal
import HBS2.CLI.Run.Internal.KeyMan
import HBS2.CLI.Run.Internal.RefLog
import HBS2.Data.Types.Refs
import HBS2.Merkle
import HBS2.Data.Detect
import HBS2.Storage
import HBS2.Peer.RPC.Client
import HBS2.Peer.CLI.Detect
import HBS2.Peer.RPC.Client.Unix
import HBS2.Peer.RPC.API.Peer
import HBS2.Peer.RPC.API.RefLog
import HBS2.Peer.RPC.API.Storage
import HBS2.Peer.Proto hiding (request)
import HBS2.Base58
import HBS2.Net.Auth.Credentials
import HBS2.Net.Auth.Schema()
import HBS2.KeyMan.Keys.Direct
import HBS2.KeyMan.App.Types
import Codec.Serialise
import Data.Coerce
import Data.Either
import Data.ByteString qualified as BS
import Data.ByteString.Char8 qualified as BS8
import Data.ByteString (ByteString)
import Data.ByteString.Lazy.Char8 qualified as LBS8
import Data.ByteString.Lazy qualified as LBS
import Data.Text.Encoding qualified as TE
import Data.Text qualified as Text
import Control.Monad.Trans.Cont
import Streaming.Prelude qualified as S
getCredentialsForReflog :: MonadUnliftIO m => RefLogKey 'HBS2Basic -> m (PeerCredentials 'HBS2Basic)
getCredentialsForReflog reflog = do
runKeymanClientRO (loadCredentials reflog)
>>= orThrowUser "credentials not found"
mkRefLogUpdateFrom :: (MonadUnliftIO m) => RefLogKey 'HBS2Basic -> m ByteString -> m (RefLogUpdate L4Proto)
mkRefLogUpdateFrom reflog mbs = do
what <- getCredentialsForReflog reflog
let puk = view peerSignPk what
let privk = view peerSignSk what
txraw <- mbs
makeRefLogUpdate @L4Proto @'HBS2Basic (coerce puk) privk txraw
reflogEntries :: forall c m . ( IsContext c
, Exception (BadFormException c)
, MonadUnliftIO m
, HasStorage m
, HasClientAPI PeerAPI UNIX m
, HasClientAPI RefLogAPI UNIX m
, HasClientAPI StorageAPI UNIX m
) => MakeDictM c m ()
reflogEntries = do
entry $ bindMatch "hbs2:reflog:create" $ \case
[] -> do
reflog <- keymanNewCredentials (Just "reflog") 0
api <- getClientAPI @PeerAPI @UNIX
void $ callService @RpcPollAdd api (reflog, "reflog", 31)
pure $ mkStr (show $ pretty (AsBase58 reflog))
_ -> throwIO (BadFormException @C nil)
entry $ bindMatch "hbs2:reflog:add" $ \case
[SignPubKeyLike reflog] -> do
-- reflog <- keymanNewCredentials (Just "reflog") 0
api <- getClientAPI @PeerAPI @UNIX
void $ callService @RpcPollAdd api (reflog, "reflog", 31)
pure $ mkStr (show $ pretty (AsBase58 reflog))
_ -> throwIO (BadFormException @C nil)
entry $ bindMatch "hbs2:reflog:tx:annhashref:create" $ \case
[StringLike puk, StringLike hash] -> do
reflog <- orThrowUser "bad reflog key" (fromStringMay puk)
sto <- getStorage
hashref <- orThrowUser "bad hash" (fromStringMay @HashRef hash)
void $ hasBlock sto (fromHashRef hashref) `orDie` "no block"
let sref = AnnotatedHashRef Nothing hashref
rlu <- mkRefLogUpdateFrom reflog (pure $ LBS.toStrict $ serialise sref) <&> serialise
pure $ mkForm "blob" [mkStr (LBS8.unpack rlu)]
_ -> throwIO (BadFormException @C nil)
entry $ bindMatch "hbs2:reflog:tx:copy:all" $ nil_ \case
[SignPubKeyLike a, SignPubKeyLike b] -> do
let cre = runKeymanClientRO (loadCredentials b)
>>= orThrow (RefLogNoCredentials (show $ pretty (AsBase58 b)))
copyTransactions cre a b
e -> throwIO (BadFormException @c (mkList e))
entry $ bindMatch "hbs2:reflog:tx:post" $ nil_ \case
[BlobLike blob] -> do
caller <- getClientAPI @RefLogAPI @UNIX
wtf <- deserialiseOrFail @(RefLogUpdate L4Proto) (LBS.fromStrict blob)
& orThrowUser "invalid tx"
void $ callService @RpcRefLogPost caller wtf
_ -> throwIO (BadFormException @C nil)
entry $ bindMatch "hbs2:reflog:tx:seqref:create" $ \case
[StringLike puk, LitIntVal sn, StringLike hash] -> do
reflog <- orThrowUser "bad reflog key" (fromStringMay puk)
sto <- getStorage
hashref <- orThrowUser "bad hash" (fromStringMay @HashRef hash)
void $ hasBlock sto (fromHashRef hashref) `orDie` "no block"
let sref = SequentialRef sn (AnnotatedHashRef Nothing hashref)
rlu <- mkRefLogUpdateFrom reflog (pure $ LBS.toStrict $ serialise sref) <&> serialise
pure $ mkForm "blob" [mkStr (LBS8.unpack rlu)]
_ -> throwIO (BadFormException @C nil)
entry $ bindMatch "hbs2:reflog:tx:raw:create" $ \case
[SymbolVal "stdin", SignPubKeyLike reflog] -> do
rlu <- mkRefLogUpdateFrom (RefLogKey reflog) ( liftIO BS.getContents )
<&> serialise
pure $ mkForm "blob" [mkStr (LBS8.unpack rlu)]
[LitStrVal s, StringLike rlo] -> do
reflog <- orThrowUser "bad reflog" (fromStringMay rlo)
rlu <- mkRefLogUpdateFrom reflog ( pure (BS8.pack (Text.unpack s)) )
<&> serialise
pure $ mkForm "blob" [mkStr (LBS8.unpack rlu)]
_ -> throwIO (BadFormException @C nil)
entry $ bindMatch "hbs2:reflog:get" $ \case
[StringLike puk] -> do
flip runContT pure do
reflog <- orThrowUser "bad reflog key" (fromStringMay puk)
api <- getClientAPI @RefLogAPI @UNIX
what <- callService @RpcRefLogGet api reflog
>>= orThrowUser "can't get reflog"
pure $ mkStr (show $ pretty what)
_ -> throwIO (BadFormException @C nil)
entry $ bindMatch "hbs2:reflog:fetch" $ \case
[StringLike puk] -> do
flip runContT pure do
reflog <- orThrowUser "bad reflog key" (fromStringMay puk)
api <- getClientAPI @RefLogAPI @UNIX
void $ callService @RpcRefLogFetch api reflog
pure $ mkStr "okay"
_ -> throwIO (BadFormException @C nil)
entry $ bindMatch "hbs2:reflog:list" $ \case
[] -> do
flip runContT pure do
api <- getClientAPI @PeerAPI @UNIX
r <- callService @RpcPollList2 api (Just "reflog", Nothing)
>>= orThrowUser "can't get reflog list"
pure $ mkList $ fmap (mkStr . show . pretty . AsBase58 . view _1) r
_ -> throwIO (BadFormException @C nil)
entry $ bindMatch "hbs2:reflog:tx:decode" $ \case
[HashLike s] -> do
sto <- getStorage
blk <- getBlock sto (coerce s)
pure $ maybe1 blk nil (decodeRefLogTx @c (Just s))
[MatchOpaqueVal @_ @(HashRef, ByteString) (ha,bs)] -> do
pure $ decodeRefLogTx @c (Just ha) (LBS.fromStrict bs)
e -> throwIO $ BadFormException @c nil
entry $ bindMatch "hbs2:reflog:tx:list" $ \case
[e, SignPubKeyLike puk] -> do
flip runContT pure do
callCC \exit -> do
api <- getClientAPI @RefLogAPI @UNIX
sto <- getStorage
r <- callService @RpcRefLogGet api puk
>>= orThrowUser "can't get reflog value"
rlh <- ContT $ maybe1 r (pure nil)
hashes <- S.toList_ do
walkMerkle @[HashRef] (fromHashRef rlh) (getBlock sto) $ \case
(Left _) -> lift $ exit nil
(Right (hs :: [HashRef])) -> S.each hs
rr <- forM hashes $ \ha -> do
tx <- getBlock sto (coerce ha)
>>= orThrowUser "missed-block"
<&> deserialiseOrFail @(RefLogUpdate L4Proto)
>>= orThrowUser "invalid-tx"
let bs = view refLogUpdData tx
payload <- mkOpaque (ha,bs)
lift $ apply_ e [payload]
pure $ mkList rr
_ -> throwIO (BadFormException @C nil)

View File

@ -0,0 +1,135 @@
{-# OPTIONS_GHC -Wno-name-shadowing #-}
module HBS2.CLI.Run.Sigil where
import HBS2.CLI.Prelude
import HBS2.CLI.Run.Internal
import HBS2.Data.Types.Refs
import HBS2.Base58
import HBS2.Storage
import HBS2.Data.Types.SignedBox
import HBS2.Net.Auth.Credentials
import HBS2.Net.Auth.Credentials.Sigil
import Data.List qualified as L
import Data.ByteString.Char8 qualified as BS8
import Data.ByteString.Lazy qualified as LBS
import Lens.Micro.Platform
sigilEntries :: forall c m . (c ~ C, IsContext c, MonadUnliftIO m, HasStorage m)
=> MakeDictM c m ()
sigilEntries = do
entry $ bindMatch "hbs2:sigil:sign-pubkey" $ \case
[ ListVal (SymbolVal sigil : (hasKey "sign-pubkey" -> Just s)) ] -> do
pure s
_ -> throwIO $ BadFormException @C nil
entry $ bindMatch "hbs2:sigil:encrypt-pubkey" $ \case
[ ListVal (SymbolVal sigil : (hasKey "encrypt-pubkey" -> Just s)) ] -> do
pure s
_ -> throwIO $ BadFormException @C nil
brief "parses sigil"
$ args [ arg "sigil" "string" ]
$ examples [qc|hbs2:sigil:parse [str:read-file some.sigil]|]
$ entry $ bindMatch "hbs2:sigil:parse" $ \case
[StringLike s] -> do
let bs = BS8.pack s
sigil <- pure (parseSerialisableFromBase58 @(Sigil 'HBS2Basic) bs)
`orDie` "parse sigil failed"
(_,sd) <- pure (unboxSignedBox0 @(SigilData 'HBS2Basic) (sigilData sigil))
`orDie` "signature check failed"
pure (parseTop $ show $ parens ("sigil" <> line <> indent 2 (vcat $ [pretty sigil, pretty sd])))
`orDie` "bad sigil"
<&> head
_ -> throwIO $ BadFormException @C nil
brief "loads sigil from hbs2 store as base58 string"
$ args [arg "hash" "string" ]
$ returns "sigil" "string"
$ entry $ bindMatch "hbs2:sigil:load:base58" $ \case
[HashLike key] -> lift do
sto <- getStorage
r <- loadSigil @HBS2Basic sto key >>= orThrowUser "no sigil found"
pure $ mkStr @c ( show $ pretty $ AsBase58 r )
_ -> throwIO $ BadFormException @c nil
brief "stores sigil to hbs2 store"
$ args [arg "string" "file" ]
$ returns "string" "hash"
$ entry $ bindMatch "hbs2:sigil:store:file" $ \case
[StringLike fn] -> lift do
sto <- getStorage
lbs <- liftIO (LBS.readFile fn)
sigil <- decodeSigil @HBS2Basic lbs & orThrowUser "invalid sigil file"
href <- storeSigil sto sigil
pure $ mkStr ( show $ pretty href )
_ -> throwIO $ BadFormException @c nil
brief "create sigil from keyring" $
desc [qc|
;; creates from keyring, uses first encryption key if found
hbs2:sigil:create:from-keyring KEYRING-FILE
;; creates from keyring, uses n-th encryption key if found, N starts from 1
hbs2:sigil:create:from-keyring KEYRING-FILE N
;; creates from keyring, uses encryption key wit prefix S if found
hbs2:sigil:create:from-keyring KEYRING-FILE S
|]
$ entry $ bindMatch "hbs2:sigil:create:from-keyring" $ \syn -> do
let readKeyring fn = liftIO (BS8.readFile fn)
<&> parseCredentials @'HBS2Basic . AsCredFile
>>= orThrowUser "malformed keyring file"
(cred, KeyringEntry enc _ _) <- case syn of
[ StringLike fn ] -> do
s <- readKeyring fn
kr <- headMay (view peerKeyring s) & orThrowUser "encryption key missed"
pure (s,kr)
[ StringLike fn, LitIntVal n ] -> do
s <- readKeyring fn
kr <- headMay (drop (fromIntegral (max 0 (n-1))) (view peerKeyring s))
& orThrowUser "encryption key not found"
pure (s,kr)
[ StringLike fn, StringLike p ] -> do
s <- readKeyring fn
kr <- findKey p (view peerKeyring s) & orThrowUser "encryption key not found"
pure (s,kr)
_ -> throwIO $ BadFormException @c nil
sigil <- pure (makeSigilFromCredentials @'HBS2Basic cred enc Nothing Nothing)
`orDie` "can't create a sigil"
pure $ mkStr (show $ pretty $ AsBase58 sigil)
where
findKey s xs = headMay [ e
| e@(KeyringEntry k _ _) <- xs
, L.isPrefixOf s (show $ pretty (AsBase58 k))
]

View File

@ -0,0 +1,194 @@
module HBS2.CLI.Run.Tree
( treeEntries
) where
import HBS2.CLI.Prelude
import HBS2.CLI.Run.Internal
import HBS2.CLI.Run.Internal.GroupKey as G
import HBS2.CLI.Run.Internal.Merkle
import HBS2.Defaults
import HBS2.Base58
import HBS2.Data.Types.Refs
import HBS2.Merkle
import HBS2.Data.Detect
import HBS2.System.Dir
import HBS2.Storage
import HBS2.Storage.Operations.Class
import HBS2.Storage.Operations.ByteString
import HBS2.Storage.Operations.Missed
import HBS2.Storage.Operations.Delete
import HBS2.Net.Auth.Schema()
import HBS2.Peer.RPC.API.Storage
import HBS2.Peer.RPC.Client
import HBS2.Peer.RPC.Client.Unix
import Data.ByteString.Lazy qualified as LBS
import Data.Coerce
import Data.Text qualified as Text
import Control.Monad.Except
import Codec.Serialise
import Streaming.Prelude qualified as S
pattern GroveHashes :: forall {c}. [HashRef] -> [Syntax c]
pattern GroveHashes hashes <- ( groveHashes -> hashes )
groveHashes :: [Syntax c] -> [HashRef]
groveHashes = \case
[ ListVal (HashLikeList hashes) ] -> hashes
HashLikeList hashes -> hashes
_ -> mempty
treeEntries :: forall c m . ( IsContext c
, MonadUnliftIO m
, Exception (BadFormException c)
, HasStorage m
, HasClientAPI StorageAPI UNIX m
) => MakeDictM c m ()
treeEntries = do
brief "reads merkle tree data from storage"
$ args [arg "string" "tree"]
$ desc "hbs2:tree:read HASH"
$ returns "bytestring" "data"
$ entry $ bindMatch "hbs2:tree:read" $ \case
[ HashLike h ] -> lift do
sto <- getStorage
co <- runExceptT (getTreeContents sto h)
>>= orThrowPassIO
mkOpaque co
_ -> throwIO (BadFormException @c nil)
brief "reads merkle tree data from storage to stdout"
$ args [arg "string" "tree"]
$ desc "hbs2:tree:read:stdout HASH"
$ returns "nil" ""
$ entry $ bindMatch "hbs2:tree:read:stdout" $ nil_ \case
[ HashLike h ] -> lift do
sto <- getStorage
runExceptT (getTreeContents sto h)
>>= orThrowPassIO
>>= liftIO . LBS.putStr
_ -> throwIO (BadFormException @c nil)
brief "creates a 'grove' -- an annotated hashref list"
$ args [arg "list of hashes" "trees"]
$ desc [qc|hbs2:grove creates a 'grove' - merkle tree of list of hashes of merkle trees
It's just an easy way to create a such thing, you may browse it by hbs2 cat -H
|]
$ returns "hash" "string"
$ entry $ bindMatch "hbs2:grove" $ \case
HashLikeList hashes@(x:_) -> lift do
sto <- getStorage
let pt = toPTree (MaxSize defHashListChunk) (MaxNum defTreeChildNum) hashes
mkSym . show . pretty <$> liftIO (makeMerkle 0 pt $ \(_,_,bss) -> do
void $ putBlock sto bss)
_ -> throwIO (BadFormException @c nil)
entry $ bindMatch "hbs2:grove:annotated" $ \case
(ListVal ann : GroveHashes hashes) -> lift do
sto <- getStorage
let pt = toPTree (MaxSize defHashListChunk) (MaxNum defTreeChildNum) hashes
tree <- liftIO (makeMerkle 0 pt $ \(_,_,bss) -> do
void $ putBlock sto bss)
block <- getBlock sto tree
>>= orThrow MissedBlockError
<&> deserialiseOrFail @(MTree [HashRef])
>>= orThrow UnsupportedFormat
let kwa = Text.unlines $ fmap (Text.pack . show . pretty) ann
let mann = MTreeAnn (ShortMetadata kwa) NullEncryption block
r <- putBlock sto (serialise mann)
>>= orThrowUser "can't write tree"
<&> HashRef
pure $ mkSym (show $ pretty r)
_ -> throwIO (BadFormException @c nil)
entry $ bindMatch "hbs2:tree:missed" $ \case
[HashLike href] -> do
sto <- getStorage
findMissedBlocks sto href
<&> mkList . fmap (mkStr @c . show . pretty)
_ -> throwIO (BadFormException @c nil)
entry $ bindMatch "hbs2:tree:refs" $ \case
[HashLike href] -> do
sto <- getStorage
blk <- getBlock sto (coerce href)
>>= orThrow MissedBlockError
let refs = extractBlockRefs (coerce href) blk
pure $ mkList @c (fmap (mkStr . show . pretty) refs)
_ -> throwIO (BadFormException @c nil)
brief "shallow scan of a block/tree" $
entry $ bindMatch "hbs2:tree:scan" $ \case
[HashLike href] -> do
sto <- getStorage
r <- S.toList_ $
deepScan ScanShallow (const none) (coerce href) (getBlock sto) $ \ha -> S.yield ha
-- let refs = extractBlockRefs (coerce href) blk
pure $ mkList @c (fmap (mkSym . show . pretty . HashRef) r)
_ -> throwIO (BadFormException @c nil)
brief "delete tree" $
entry $ bindMatch "hbs2:tree:delete" $ nil_ \case
[HashLike href] -> do
sto <- getStorage
deleteMerkleTree sto href
_ -> throwIO (BadFormException @c nil)
brief "shallow scan of a block/tree" $
entry $ bindMatch "hbs2:tree:scan:deep" $ \case
[HashLike href] -> do
sto <- getStorage
r <- S.toList_ $
deepScan ScanDeep (const none) (coerce href) (getBlock sto) $ \ha -> S.yield ha
-- let refs = extractBlockRefs (coerce href) blk
pure $ mkList @c (fmap (mkSym . show . pretty . HashRef) r)
_ -> throwIO (BadFormException @c nil)
brief "shallow scan of a block/tree" $
entry $ bindMatch "hbs2:tree:scan:deep:stdout" $ nil_ \case
[HashLike href] -> do
sto <- getStorage
deepScan ScanDeep (const none) (coerce href) (getBlock sto) $ \ha -> do
liftIO $ print $ pretty ha
_ -> throwIO (BadFormException @c nil)

View File

@ -0,0 +1 @@
hbs2-cli manual

View File

@ -1,6 +1,6 @@
cabal-version: 3.0
name: hbs2-core
version: 0.24.1.1
version: 0.25.3.0
-- synopsis:
-- description:
license: BSD-3-Clause
@ -41,8 +41,10 @@ common shared-properties
, ConstraintKinds
, DataKinds
, DeriveDataTypeable
, DeriveFoldable
, DeriveFunctor
, DeriveGeneric
, DeriveTraversable
, DerivingStrategies
, DerivingVia
, ExtendedDefaultRules
@ -94,8 +96,9 @@ library
, HBS2.Polling
, HBS2.Hash
, HBS2.Merkle
, HBS2.Merkle.MetaData
, HBS2.Merkle.Walk
, HBS2.Net.Auth.Schema
, HBS2.Net.Auth.GroupKeyAsymm
, HBS2.Net.Auth.GroupKeySymm
, HBS2.Net.Auth.Credentials
, HBS2.Net.Auth.Credentials.Sigil
@ -105,6 +108,7 @@ library
, HBS2.Net.Messaging.UDP
, HBS2.Net.Messaging.TCP
, HBS2.Net.Messaging.Unix
, HBS2.Net.Messaging.Pipe
, HBS2.Net.Messaging.Stream
, HBS2.Net.Messaging.Encrypted.RandomPrefix
, HBS2.Net.Messaging.Encrypted.ByPass
@ -119,17 +123,15 @@ library
, HBS2.Prelude
, HBS2.Prelude.Plated
, HBS2.Storage
, HBS2.Storage.AdHocStorage
, HBS2.Storage.Operations.Class
, HBS2.Storage.Operations.ByteString
, HBS2.Storage.Operations.Missed
, HBS2.Storage.Operations.Delete
, HBS2.System.Logger.Simple
, HBS2.System.Logger.Simple.ANSI
, HBS2.System.Logger.Simple.Class
, HBS2.System.Dir
, HBS2.Net.Dialog.Core
, HBS2.Net.Dialog.Client
, HBS2.Net.Dialog.Helpers.List
, HBS2.Net.Dialog.Helpers.Streaming
, HBS2.Misc.PrettyStuff
, HBS2.Version
@ -172,6 +174,7 @@ library
, network-multicast
, network-simple
, network-byte-order
, psqueues
, prettyprinter
, prettyprinter-ansi-terminal
, mwc-random
@ -180,7 +183,7 @@ library
, resourcet
, safe
, safe-exceptions
, saltine ^>=0.2.0.1
, saltine >=0.2.0.1
, serialise
, sockaddr
, split
@ -188,6 +191,7 @@ library
, stm-chans
, string-conversions
, streaming
, streaming-bytestring
, string-conversions
, suckless-conf
, template-haskell
@ -196,6 +200,7 @@ library
, time
, transformers
, uniplate
, unix
, unordered-containers
, unliftio
, unliftio-core
@ -216,7 +221,6 @@ test-suite test
-- , TestUniqProtoId
, FakeMessaging
, HasProtocol
, DialogSpec
, TestScheduled
, TestDerivedKey

View File

@ -7,18 +7,21 @@ module HBS2.Actors
) where
import HBS2.Prelude
import HBS2.Clock
import Control.Concurrent.STM
import Control.Concurrent.STM.TBMQueue qualified as TBMQ
import Control.Concurrent.STM.TBMQueue (TBMQueue)
import Control.Concurrent.STM.TVar qualified as TVar
import Control.Monad
import Control.Concurrent.Async
import Data.Function
import Data.Functor
import Data.Kind
import Control.Concurrent
import Control.Concurrent.Async
import Control.Exception
data PipelineExcepion =
PipelineAddJobTimeout
deriving stock (Show,Typeable)
instance Exception PipelineExcepion
data Pipeline m a =
Pipeline
@ -51,9 +54,15 @@ stopPipeline pip = liftIO $ do
pause ( 0.01 :: Timeout 'Seconds) >> next
addJob :: forall a m m1 . (MonadIO m, MonadIO m1) => Pipeline m a -> m a -> m1 ()
addJob pip act = liftIO $ do
doWrite <- atomically $ TVar.readTVar ( stopAdding pip )
unless doWrite $ do
addJob pip' act' = liftIO $ do
doWrite <- atomically $ TVar.readTVar ( stopAdding pip' )
-- FIXME: exception-timeout-hardcode
race (pause @'Seconds 3) (doAddJob doWrite pip' act') >>= \case
Left{} -> throwIO PipelineAddJobTimeout
_ -> pure ()
where
doAddJob w pip act =
unless w $ do
atomically $ TBMQ.writeTBMQueue (toQueue pip) act

View File

@ -17,8 +17,6 @@ import HBS2.Events
import HBS2.Net.Auth.Credentials
import HBS2.Net.Messaging
import HBS2.Net.PeerLocator
import HBS2.Net.PeerLocator.Static
import HBS2.Net.Proto
import HBS2.Net.Proto.Sessions
import HBS2.Prelude.Plated
import HBS2.Storage
@ -26,23 +24,26 @@ import HBS2.System.Logger.Simple
import Data.Config.Suckless.KeyValue (HasConf(..))
import Control.Monad
import Control.Monad.Trans.Maybe
import Control.Concurrent.Async
import Control.Monad.Trans.Cont
import Control.Monad.Reader
import Control.Monad.Trans.Writer.CPS qualified as CPS
import Data.ByteString.Lazy (ByteString)
import Data.Cache (Cache)
import Data.Cache qualified as Cache
import Data.Dynamic
import Data.Foldable hiding (find)
import Data.Map qualified as Map
import Data.Maybe
import GHC.TypeLits
import Lens.Micro.Platform as Lens
import Data.HashMap.Strict (HashMap)
import Data.HashMap.Strict qualified as HashMap
import Control.Concurrent.STM.TVar
import Control.Concurrent.STM
import Control.Monad.IO.Unlift
import Data.List qualified as L
import Data.Monoid qualified as Monoid
import UnliftIO
import UnliftIO.Concurrent (getNumCapabilities)
import Codec.Serialise (serialise, deserialiseOrFail)
@ -129,6 +130,9 @@ data PeerEnv e =
, _envSweepers :: TVar (HashMap SKey [PeerM e IO ()])
, _envReqMsgLimit :: Cache (Peer e, Integer, Encoded e) ()
, _envReqProtoLimit :: Cache (Peer e, Integer) ()
, _envSentCounter :: TVar Int
, _envRecvCounter :: TVar Int
, _envProbe :: TVar AnyProbe
}
newtype PeerM e m a = PeerM { fromPeerM :: ReaderT (PeerEnv e) m a }
@ -263,8 +267,9 @@ instance ( MonadIO m
, Show (Peer e)
) => Request e msg m where
request peer_e msg = do
let proto = protoId @e @msg (Proxy @msg)
pipe <- getFabriq @e
pip <- getFabriq @e
me <- ownPeer @e
-- TODO: check if a request were sent to peer and timeout is here
@ -281,7 +286,7 @@ instance ( MonadIO m
trace $ "REQUEST: not allowed to send for proto" <+> viaShow proto
when allowed do
sendTo pipe (To peer_e) (From me) (AnyMessage @(Encoded e) @e proto (encode msg))
sendTo pip (To peer_e) (From me) (AnyMessage @(Encoded e) @e proto (encode msg))
-- trace $ "REQUEST: after sendTo" <+> viaShow peer_e
@ -330,6 +335,11 @@ sweep = do
liftIO $ atomically $ modifyTVar' sw (<> HashMap.fromList (mconcat alive))
addJobIO :: IO () -> PeerM e IO ()
addJobIO m = do
PeerEnv{..} <- ask
addJob _envDeferred m
instance ( Typeable (EventKey e p)
, Typeable (Event e p)
, Hashable (EventKey e p)
@ -390,32 +400,81 @@ newPeerEnv pl s bus p = do
_envSweepers <- liftIO (newTVarIO mempty)
_envReqMsgLimit <- liftIO (Cache.newCache (Just defRequestLimit))
_envReqProtoLimit <- liftIO (Cache.newCache (Just defRequestLimit))
_envSentCounter <- liftIO (newTVarIO 0)
_envRecvCounter <- liftIO (newTVarIO 0)
_envProbe <- liftIO (newTVarIO (AnyProbe ()))
pure PeerEnv {..}
runPeerM :: forall e m . ( MonadIO m
peerEnvSetProbe :: (MonadIO m) => PeerEnv e -> AnyProbe -> m ()
peerEnvSetProbe PeerEnv {..} p = liftIO $ atomically $ writeTVar _envProbe p
-- peerEnvAddProbe :: (MonadIO m) => PeerEnv e -> AnyProbe -> m ()
-- peerEnvAddProbe PeerEnv {..} p = liftIO $ atomically $ modifyTVar _envProbe p
peerEnvCollectProbes :: (MonadIO m) => PeerEnv e -> m ()
peerEnvCollectProbes PeerEnv {..} = do
probe <- liftIO $ readTVarIO _envProbe
acceptReport probe =<< CPS.execWriterT do
-- _envDeferred :: Pipeline IO ()
item "sessions" =<< (liftIO . Cache.size) _envSessions
events <- liftReadTVar _envEvents
item "events-keys" $ HashMap.size events
item "events-values-all" $ calcValuesLengthTotal events
item "expire-times" =<< (liftIO . Cache.size) _envExpireTimes
sweepers <- liftReadTVar _envSweepers
item "sweepers-keys" $ HashMap.size sweepers
item "sweepers-values-all" $ calcValuesLengthTotal sweepers
item "req-msg-limit" =<< (liftIO . Cache.size) _envReqMsgLimit
item "req-proto-limit" =<< (liftIO . Cache.size) _envReqProtoLimit
item "data-sent" =<< (liftIO . readTVarIO) _envSentCounter
item "data-recv" =<< (liftIO . readTVarIO) _envRecvCounter
where
calcValuesLengthTotal = Monoid.getSum . foldMap (Monoid.Sum . L.length)
liftReadTVar = liftIO . readTVarIO
item k v = CPS.tell [(k, fromIntegral v)]
runPeerM :: forall e m . ( MonadUnliftIO m
, HasPeer e
, Ord (Peer e)
, Pretty (Peer e)
, Hashable (Encoded e)
, HasNonces () m
)
=> PeerEnv e
-> PeerM e m ()
-> m ()
runPeerM env f = do
runPeerM env@PeerEnv{..} f = flip runContT pure do
let de = view envDeferred env
as <- liftIO $ replicateM 16 $ async $ runPipeline de
n <- liftIO getNumCapabilities <&> max 2 . div 2
as <- liftIO $ replicateM n $ asyncLinked $ runPipeline _envDeferred
sw <- liftIO $ async $ forever $ withPeerM env $ do
pause defSweepTimeout
se <- asks (view envSessions)
liftIO $ Cache.purgeExpired se
liftIO do
Cache.purgeExpired _envSessions
Cache.purgeExpired _envReqMsgLimit
Cache.purgeExpired _envReqProtoLimit
sweep
void $ runReaderT (fromPeerM f) env
void $ liftIO $ stopPipeline de
void $ ContT $ bracket none $ const $ do
void $ liftIO $ stopPipeline _envDeferred
liftIO $ mapM_ cancel (as <> [sw])
pure ()
lift $ void $ runReaderT (fromPeerM f) env
withPeerM :: MonadIO m => PeerEnv e -> PeerM e m a -> m a
withPeerM env action = runReaderT (fromPeerM action) env
@ -431,7 +490,7 @@ runProto :: forall e m . ( MonadIO m
runProto hh = do
me <- ownPeer @e @m
pipe <- getFabriq @e
pipf <- getFabriq @e
let resp = [ (pid, a) | a@AnyProtocol { myProtoId = pid } <- hh ]
@ -439,7 +498,7 @@ runProto hh = do
forever $ do
messages <- receive @_ @e pipe (To me)
messages <- receive @_ @e pipf (To me)
for_ messages $ \(From pip, AnyMessage n msg :: AnyMessage (Encoded e) e) -> do
@ -453,6 +512,7 @@ runProto hh = do
}) -> maybe (pure ()) (runResponseM pip . h) (decoder msg)
instance (Monad m, HasProtocol e p) => HasThatPeer p e (ResponseM e m) where
thatPeer = asks (view answTo)
@ -480,7 +540,9 @@ instance ( HasProtocol e p
who <- thatPeer @p
self <- lift $ ownPeer @e
fab <- lift $ getFabriq @e
sendTo fab (To who) (From self) (AnyMessage @(Encoded e) @e proto (encode msg))
let raw = encode msg
-- atomically $ modifyTVar
sendTo fab (To who) (From self) (AnyMessage @(Encoded e) @e proto raw)
instance ( MonadIO m
-- , HasProtocol e p

View File

@ -2,15 +2,10 @@
module HBS2.Actors.Peer.Types where
import HBS2.Prelude
import HBS2.Storage
import HBS2.Net.Proto.Types
import HBS2.Net.Messaging
import HBS2.Hash
import Control.Monad.Trans.Class
import Data.ByteString.Lazy (ByteString)
import Control.Monad
import Codec.Serialise
class HasProtocol e p => HasTimeLimits e p m where
tryLockForPeriod :: Peer e -> p -> m Bool
@ -24,7 +19,16 @@ instance {-# OVERLAPPABLE #-}
-- instance HasConf m => HasConf (ResponseM e m)
data PeerCounters =
PeerStats
{ peerDataSent :: Int
, peerDataRecv :: Int
}
class HasPeerCounters m where
getPeerCounters :: m PeerCounters
setPeerCounters :: PeerCounters -> m ()
updatePeerCountes :: (PeerCounters -> PeerCounters) -> m ()
class (Monad m, HasProtocol e p) => HasGossip e p m where
gossip :: p -> m ()

View File

@ -4,10 +4,8 @@ import Data.ByteString.Base58 (encodeBase58, bitcoinAlphabet, decodeBase58,Alpha
import Data.ByteString qualified as BS
import Data.ByteString.Char8 qualified as BS8
import Data.ByteString.Char8 (ByteString)
import Data.ByteString.Lazy.Char8 qualified as LBS8
import Data.ByteString.Lazy qualified as LBS
import Data.Word
import Data.Char (ord)
import Numeric
import Prettyprinter
@ -41,6 +39,9 @@ instance Pretty (AsBase58 LBS.ByteString) where
instance Show (AsBase58 ByteString) where
show (AsBase58 bs) = BS8.unpack $ toBase58 bs
instance Show (AsBase58 LBS.ByteString) where
show (AsBase58 bs) = BS8.unpack . toBase58 . LBS.toStrict $ bs
byteToHex :: Word8 -> String
byteToHex byte = pad $ showHex byte ""

View File

@ -1,8 +1,9 @@
{-# LANGUAGE CPP #-}
{-# OPTIONS_GHC -fno-warn-orphans #-}
module HBS2.Clock
( module HBS2.Clock
, module System.Clock
, POSIXTime, getPOSIXTime, getEpoch
, POSIXTime, getPOSIXTime, NominalDiffTime
)where
import Data.Functor
@ -14,7 +15,6 @@ import Data.Proxy
import Data.Time
import Prettyprinter
import System.Clock
import Data.Time.Clock
import Data.Time.Clock.POSIX (POSIXTime, getPOSIXTime)
import Data.Word
@ -50,7 +50,7 @@ instance IsTimeout t => Expired (Timeout t) TimeSpec where
-- expired timeout ts = False
toNominalDiffTime :: IsTimeout t => Timeout t -> NominalDiffTime
toNominalDiffTime = fromRational . (/ (10^6)) . fromIntegral . toMicroSeconds
toNominalDiffTime = fromRational . (/ (10^(6 :: Integer))) . fromIntegral . toMicroSeconds
class IsTimeout a => MonadPause a m where
pause :: Timeout a -> m ()
@ -97,7 +97,7 @@ instance IsTimeout 'Minutes where
toNanoSeconds (TimeoutMin x) = round (x * 60 * 1e9)
instance IsTimeout 'NomDiffTime where
toNanoSeconds (TimeoutNDT t) = round (realToFrac (nominalDiffTimeToSeconds t) * 1e9)
toNanoSeconds (TimeoutNDT t) = round (realToFrac (nominalDiffTimeToSeconds t) * (1e9 :: Double))
instance IsTimeout 'TS where
toNanoSeconds (TimeoutTS s) = fromIntegral $ toNanoSecs s
@ -108,6 +108,9 @@ class Expires a where
-- FIXME: dangerous!
expiresIn _ = Nothing
timeSpecDeltaSeconds :: RealFrac a => TimeSpec -> TimeSpec -> a
timeSpecDeltaSeconds a b = realToFrac . (*1e-9) . realToFrac $ toNanoSecs (max a b - min a b)
getEpoch :: MonadIO m => m Word64
getEpoch = liftIO getPOSIXTime <&> floor

View File

@ -26,8 +26,8 @@ import Streaming()
{- HLINT ignore "Use newtype instead of data" -}
data BundleRefValue e =
BundleRefValue (SignedBox BundleRef e)
data BundleRefValue s =
BundleRefValue (SignedBox BundleRef s)
deriving stock (Generic)
instance ForSignedBox e => Serialise (BundleRefValue e)
@ -39,13 +39,13 @@ data BundleRef =
instance Serialise BundleRef
makeBundleRefValue :: forall e . (ForSignedBox e, Signatures (Encryption e))
=> PubKey 'Sign (Encryption e)
-> PrivKey 'Sign (Encryption e)
makeBundleRefValue :: forall s . (ForSignedBox s, Signatures s)
=> PubKey 'Sign s
-> PrivKey 'Sign s
-> BundleRef
-> BundleRefValue e
-> BundleRefValue s
makeBundleRefValue pk sk ref = BundleRefValue $ makeSignedBox @e pk sk ref
makeBundleRefValue pk sk ref = BundleRefValue $ makeSignedBox @s pk sk ref
-- у нас может быть много способов хранить данные:
-- сжимать целиком (эффективно, но медленно)
@ -178,7 +178,7 @@ instance MonadIO m => ImportBundle HashRef m where
go hd bs
| LBS.null bs = pure $ Right ()
| otherwise = do
let ss = bundleHeadSectionSize hd
let _ss = bundleHeadSectionSize hd
let (bsh, allBsRest) = LBS.splitAt sectionHeadSize bs
case deserialiseOrFail @BundleSection bsh of
Left{} -> do

View File

@ -1,30 +1,38 @@
module HBS2.Data.Detect where
module HBS2.Data.Detect
( module HBS2.Data.Detect
, module HBS2.Merkle.Walk
, module HBS2.Merkle
)
where
import HBS2.Prelude.Plated
import HBS2.Hash
import HBS2.Data.Types
import HBS2.Merkle
import HBS2.Merkle.Walk
import HBS2.Storage
import HBS2.System.Logger.Simple
-- import HBS2.System.Logger.Simple
import Data.Foldable (for_)
-- import Data.Foldable (for_)
import Control.Monad.Trans.Maybe
import Codec.Serialise (deserialiseOrFail)
import Data.ByteString.Lazy (ByteString)
import Data.ByteString.Lazy qualified as LBS
import Data.Either
import Data.Function
import Data.Functor
-- import Data.Function
-- import Data.Functor
import Data.Coerce
import Data.Maybe
import Control.Concurrent.STM
import Data.HashMap.Strict qualified as HashMap
import Data.HashMap.Strict (HashMap)
-- import Data.HashMap.Strict (HashMap)
import Data.List qualified as List
import UnliftIO qualified
import Streaming.Prelude qualified as S
import Streaming qualified as S
-- import Streaming qualified as S
data BlobType = Merkle (MTree [HashRef])
| MerkleAnn (MTreeAnn [HashRef])
@ -46,6 +54,38 @@ tryDetect hash obj = rights [mbAnn, mbLink, mbMerkle, mbSeq] & headDef orBlob
data ScanLevel = ScanShallow | ScanDeep
extractBlockRefs :: Hash HbSync -> ByteString -> [HashRef]
extractBlockRefs hx bs =
case tryDetect hx bs of
(SeqRef (SequentialRef _ (AnnotatedHashRef a' b))) ->
coerce <$> catMaybes [a', Just b]
AnnRef (AnnotatedHashRef ann h) -> do
coerce <$> catMaybes [ann, Just h]
Merkle (MNode _ hs) -> fmap HashRef hs
MerkleAnn (MTreeAnn{..}) -> do
let meta = case _mtaMeta of
AnnHashRef ha -> [ha]
_ -> mempty
let c = case _mtaCrypt of
CryptAccessKeyNaClAsymm hs -> [hs]
EncryptGroupNaClSymm1 hs _ -> [hs]
EncryptGroupNaClSymm2 _ hs _ -> [hs]
_ -> mempty
let t = case _mtaTree of
MNode _ hs -> hs
_ -> mempty
fmap HashRef (meta <> c <> t)
_ -> mempty
-- TODO: control-nesting-level-to-avoid-abuse
@ -159,6 +199,15 @@ readLog getBlk (HashRef h) =
Left{} -> pure ()
Right (hrr :: [HashRef]) -> S.each hrr
readLogThrow :: forall m . ( MonadIO m )
=> ( Hash HbSync -> IO (Maybe ByteString) )
-> HashRef
-> m [HashRef]
readLogThrow getBlk (HashRef h) =
S.toList_ do
either UnliftIO.throwIO pure =<<
streamMerkle (liftIO . getBlk) h
-- FIXME: make-it-stop-after-first-missed-block
checkComplete :: forall sto m . (MonadIO m, Storage sto HbSync ByteString IO)

View File

@ -9,7 +9,6 @@ import System.FilePath
import System.Directory
import Data.List as L
import Data.Maybe
import Data.ByteString.Lazy qualified as LBS
import Data.ByteString qualified as BS
import Lens.Micro.Platform
import UnliftIO
@ -23,7 +22,7 @@ splitPattern fp = (pref, flt)
pref = joinPath pref'
flt = case flt' of
[] -> "*"
xs -> joinPath flt'
_xs -> joinPath flt'
(pref', flt') = L.span isNotP (splitDirectories fp)
isNotP s = isNothing (find isP s)
isP c = c `elem` ("?*" :: [Char])

View File

@ -2,7 +2,6 @@ module HBS2.Data.Types
( module X
-- , module HBS2.Data.Types.Crypto
, AsSyntax(..)
, LoadedRef(..)
)
where

View File

@ -8,7 +8,7 @@ import Data.ByteString (ByteString)
-- TODO: encryption-type-into-tags
-- FIXME: show-scrambled?
newtype EncryptedBox t = EncryptedBox { unEncryptedBox :: ByteString }
deriving stock (Generic,Show,Data)
deriving stock (Eq,Generic,Show,Data)
instance Serialise (EncryptedBox t)

View File

@ -1,14 +1,12 @@
{-# Language UndecidableInstances #-}
module HBS2.Data.Types.Peer where
import Codec.Serialise
import Data.ByteString qualified as BS
import Data.Hashable
import Lens.Micro.Platform
import HBS2.Prelude
import HBS2.Net.Auth.Credentials
import HBS2.Net.Proto.Types
type PingSign e = Signature (Encryption e)

Some files were not shown because too many files have changed in this diff Show More