tigerbeetle 0.0.34 → 0.0.37

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (249) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +10 -0
  3. data/ext/tb_client/extconf.rb +13 -13
  4. data/ext/tb_client/tigerbeetle/LICENSE +177 -0
  5. data/ext/tb_client/tigerbeetle/build.zig +2327 -0
  6. data/ext/tb_client/tigerbeetle/src/aof.zig +1000 -0
  7. data/ext/tb_client/tigerbeetle/src/build_multiversion.zig +808 -0
  8. data/ext/tb_client/tigerbeetle/src/cdc/amqp/protocol.zig +1283 -0
  9. data/ext/tb_client/tigerbeetle/src/cdc/amqp/spec.zig +1704 -0
  10. data/ext/tb_client/tigerbeetle/src/cdc/amqp/types.zig +341 -0
  11. data/ext/tb_client/tigerbeetle/src/cdc/amqp.zig +1450 -0
  12. data/ext/tb_client/tigerbeetle/src/cdc/runner.zig +1659 -0
  13. data/ext/tb_client/tigerbeetle/src/clients/c/samples/main.c +406 -0
  14. data/ext/tb_client/tigerbeetle/src/clients/c/tb_client/context.zig +1084 -0
  15. data/ext/tb_client/tigerbeetle/src/clients/c/tb_client/echo_client.zig +286 -0
  16. data/ext/tb_client/tigerbeetle/src/clients/c/tb_client/packet.zig +158 -0
  17. data/ext/tb_client/tigerbeetle/src/clients/c/tb_client/signal.zig +229 -0
  18. data/ext/tb_client/tigerbeetle/src/clients/c/tb_client/signal_fuzz.zig +110 -0
  19. data/ext/tb_client/tigerbeetle/src/clients/c/tb_client.h +386 -0
  20. data/ext/tb_client/tigerbeetle/src/clients/c/tb_client.zig +34 -0
  21. data/ext/tb_client/tigerbeetle/src/clients/c/tb_client_exports.zig +281 -0
  22. data/ext/tb_client/tigerbeetle/src/clients/c/tb_client_header.zig +312 -0
  23. data/ext/tb_client/tigerbeetle/src/clients/c/tb_client_header_test.zig +138 -0
  24. data/ext/tb_client/tigerbeetle/src/clients/c/test.zig +466 -0
  25. data/ext/tb_client/tigerbeetle/src/clients/docs_samples.zig +157 -0
  26. data/ext/tb_client/tigerbeetle/src/clients/docs_types.zig +90 -0
  27. data/ext/tb_client/tigerbeetle/src/clients/dotnet/ci.zig +203 -0
  28. data/ext/tb_client/tigerbeetle/src/clients/dotnet/docs.zig +79 -0
  29. data/ext/tb_client/tigerbeetle/src/clients/dotnet/dotnet_bindings.zig +542 -0
  30. data/ext/tb_client/tigerbeetle/src/clients/go/ci.zig +109 -0
  31. data/ext/tb_client/tigerbeetle/src/clients/go/docs.zig +86 -0
  32. data/ext/tb_client/tigerbeetle/src/clients/go/go_bindings.zig +370 -0
  33. data/ext/tb_client/tigerbeetle/src/clients/go/pkg/native/tb_client.h +386 -0
  34. data/ext/tb_client/tigerbeetle/src/clients/java/ci.zig +167 -0
  35. data/ext/tb_client/tigerbeetle/src/clients/java/docs.zig +126 -0
  36. data/ext/tb_client/tigerbeetle/src/clients/java/java_bindings.zig +996 -0
  37. data/ext/tb_client/tigerbeetle/src/clients/java/src/client.zig +748 -0
  38. data/ext/tb_client/tigerbeetle/src/clients/java/src/jni.zig +3238 -0
  39. data/ext/tb_client/tigerbeetle/src/clients/java/src/jni_tests.zig +1718 -0
  40. data/ext/tb_client/tigerbeetle/src/clients/java/src/jni_thread_cleaner.zig +190 -0
  41. data/ext/tb_client/tigerbeetle/src/clients/node/ci.zig +104 -0
  42. data/ext/tb_client/tigerbeetle/src/clients/node/docs.zig +75 -0
  43. data/ext/tb_client/tigerbeetle/src/clients/node/node.zig +522 -0
  44. data/ext/tb_client/tigerbeetle/src/clients/node/node_bindings.zig +267 -0
  45. data/ext/tb_client/tigerbeetle/src/clients/node/src/c.zig +3 -0
  46. data/ext/tb_client/tigerbeetle/src/clients/node/src/translate.zig +379 -0
  47. data/ext/tb_client/tigerbeetle/src/clients/python/ci.zig +131 -0
  48. data/ext/tb_client/tigerbeetle/src/clients/python/docs.zig +63 -0
  49. data/ext/tb_client/tigerbeetle/src/clients/python/python_bindings.zig +588 -0
  50. data/ext/tb_client/tigerbeetle/src/clients/rust/assets/tb_client.h +386 -0
  51. data/ext/tb_client/tigerbeetle/src/clients/rust/ci.zig +73 -0
  52. data/ext/tb_client/tigerbeetle/src/clients/rust/docs.zig +106 -0
  53. data/ext/tb_client/tigerbeetle/src/clients/rust/rust_bindings.zig +305 -0
  54. data/ext/tb_client/tigerbeetle/src/config.zig +296 -0
  55. data/ext/tb_client/tigerbeetle/src/constants.zig +790 -0
  56. data/ext/tb_client/tigerbeetle/src/copyhound.zig +202 -0
  57. data/ext/tb_client/tigerbeetle/src/counting_allocator.zig +72 -0
  58. data/ext/tb_client/tigerbeetle/src/direction.zig +11 -0
  59. data/ext/tb_client/tigerbeetle/src/docs_website/build.zig +158 -0
  60. data/ext/tb_client/tigerbeetle/src/docs_website/src/content.zig +156 -0
  61. data/ext/tb_client/tigerbeetle/src/docs_website/src/docs.zig +252 -0
  62. data/ext/tb_client/tigerbeetle/src/docs_website/src/file_checker.zig +313 -0
  63. data/ext/tb_client/tigerbeetle/src/docs_website/src/html.zig +87 -0
  64. data/ext/tb_client/tigerbeetle/src/docs_website/src/page_writer.zig +63 -0
  65. data/ext/tb_client/tigerbeetle/src/docs_website/src/redirects.zig +47 -0
  66. data/ext/tb_client/tigerbeetle/src/docs_website/src/search_index_writer.zig +28 -0
  67. data/ext/tb_client/tigerbeetle/src/docs_website/src/service_worker_writer.zig +61 -0
  68. data/ext/tb_client/tigerbeetle/src/docs_website/src/single_page_writer.zig +169 -0
  69. data/ext/tb_client/tigerbeetle/src/docs_website/src/website.zig +46 -0
  70. data/ext/tb_client/tigerbeetle/src/ewah.zig +445 -0
  71. data/ext/tb_client/tigerbeetle/src/ewah_benchmark.zig +128 -0
  72. data/ext/tb_client/tigerbeetle/src/ewah_fuzz.zig +171 -0
  73. data/ext/tb_client/tigerbeetle/src/fuzz_tests.zig +179 -0
  74. data/ext/tb_client/tigerbeetle/src/integration_tests.zig +662 -0
  75. data/ext/tb_client/tigerbeetle/src/io/common.zig +155 -0
  76. data/ext/tb_client/tigerbeetle/src/io/darwin.zig +1093 -0
  77. data/ext/tb_client/tigerbeetle/src/io/linux.zig +1880 -0
  78. data/ext/tb_client/tigerbeetle/src/io/test.zig +1005 -0
  79. data/ext/tb_client/tigerbeetle/src/io/windows.zig +1598 -0
  80. data/ext/tb_client/tigerbeetle/src/io.zig +34 -0
  81. data/ext/tb_client/tigerbeetle/src/iops.zig +134 -0
  82. data/ext/tb_client/tigerbeetle/src/list.zig +236 -0
  83. data/ext/tb_client/tigerbeetle/src/lsm/binary_search.zig +848 -0
  84. data/ext/tb_client/tigerbeetle/src/lsm/binary_search_benchmark.zig +179 -0
  85. data/ext/tb_client/tigerbeetle/src/lsm/cache_map.zig +424 -0
  86. data/ext/tb_client/tigerbeetle/src/lsm/cache_map_fuzz.zig +420 -0
  87. data/ext/tb_client/tigerbeetle/src/lsm/compaction.zig +2117 -0
  88. data/ext/tb_client/tigerbeetle/src/lsm/composite_key.zig +182 -0
  89. data/ext/tb_client/tigerbeetle/src/lsm/forest.zig +1119 -0
  90. data/ext/tb_client/tigerbeetle/src/lsm/forest_fuzz.zig +1102 -0
  91. data/ext/tb_client/tigerbeetle/src/lsm/forest_table_iterator.zig +200 -0
  92. data/ext/tb_client/tigerbeetle/src/lsm/groove.zig +1495 -0
  93. data/ext/tb_client/tigerbeetle/src/lsm/k_way_merge.zig +739 -0
  94. data/ext/tb_client/tigerbeetle/src/lsm/k_way_merge_benchmark.zig +166 -0
  95. data/ext/tb_client/tigerbeetle/src/lsm/manifest.zig +754 -0
  96. data/ext/tb_client/tigerbeetle/src/lsm/manifest_level.zig +1294 -0
  97. data/ext/tb_client/tigerbeetle/src/lsm/manifest_level_fuzz.zig +510 -0
  98. data/ext/tb_client/tigerbeetle/src/lsm/manifest_log.zig +1263 -0
  99. data/ext/tb_client/tigerbeetle/src/lsm/manifest_log_fuzz.zig +628 -0
  100. data/ext/tb_client/tigerbeetle/src/lsm/node_pool.zig +247 -0
  101. data/ext/tb_client/tigerbeetle/src/lsm/scan_buffer.zig +116 -0
  102. data/ext/tb_client/tigerbeetle/src/lsm/scan_builder.zig +543 -0
  103. data/ext/tb_client/tigerbeetle/src/lsm/scan_fuzz.zig +938 -0
  104. data/ext/tb_client/tigerbeetle/src/lsm/scan_lookup.zig +293 -0
  105. data/ext/tb_client/tigerbeetle/src/lsm/scan_merge.zig +362 -0
  106. data/ext/tb_client/tigerbeetle/src/lsm/scan_range.zig +99 -0
  107. data/ext/tb_client/tigerbeetle/src/lsm/scan_state.zig +17 -0
  108. data/ext/tb_client/tigerbeetle/src/lsm/scan_tree.zig +1036 -0
  109. data/ext/tb_client/tigerbeetle/src/lsm/schema.zig +617 -0
  110. data/ext/tb_client/tigerbeetle/src/lsm/scratch_memory.zig +84 -0
  111. data/ext/tb_client/tigerbeetle/src/lsm/segmented_array.zig +1500 -0
  112. data/ext/tb_client/tigerbeetle/src/lsm/segmented_array_benchmark.zig +149 -0
  113. data/ext/tb_client/tigerbeetle/src/lsm/segmented_array_fuzz.zig +7 -0
  114. data/ext/tb_client/tigerbeetle/src/lsm/set_associative_cache.zig +865 -0
  115. data/ext/tb_client/tigerbeetle/src/lsm/table.zig +607 -0
  116. data/ext/tb_client/tigerbeetle/src/lsm/table_memory.zig +843 -0
  117. data/ext/tb_client/tigerbeetle/src/lsm/table_value_iterator.zig +105 -0
  118. data/ext/tb_client/tigerbeetle/src/lsm/timestamp_range.zig +40 -0
  119. data/ext/tb_client/tigerbeetle/src/lsm/tree.zig +630 -0
  120. data/ext/tb_client/tigerbeetle/src/lsm/tree_fuzz.zig +933 -0
  121. data/ext/tb_client/tigerbeetle/src/lsm/zig_zag_merge.zig +557 -0
  122. data/ext/tb_client/tigerbeetle/src/message_buffer.zig +469 -0
  123. data/ext/tb_client/tigerbeetle/src/message_bus.zig +1214 -0
  124. data/ext/tb_client/tigerbeetle/src/message_bus_fuzz.zig +936 -0
  125. data/ext/tb_client/tigerbeetle/src/message_pool.zig +343 -0
  126. data/ext/tb_client/tigerbeetle/src/multiversion.zig +2195 -0
  127. data/ext/tb_client/tigerbeetle/src/queue.zig +390 -0
  128. data/ext/tb_client/tigerbeetle/src/repl/completion.zig +201 -0
  129. data/ext/tb_client/tigerbeetle/src/repl/parser.zig +1356 -0
  130. data/ext/tb_client/tigerbeetle/src/repl/terminal.zig +496 -0
  131. data/ext/tb_client/tigerbeetle/src/repl.zig +1034 -0
  132. data/ext/tb_client/tigerbeetle/src/scripts/amqp.zig +973 -0
  133. data/ext/tb_client/tigerbeetle/src/scripts/cfo.zig +1866 -0
  134. data/ext/tb_client/tigerbeetle/src/scripts/changelog.zig +304 -0
  135. data/ext/tb_client/tigerbeetle/src/scripts/ci.zig +227 -0
  136. data/ext/tb_client/tigerbeetle/src/scripts/client_readmes.zig +658 -0
  137. data/ext/tb_client/tigerbeetle/src/scripts/devhub.zig +466 -0
  138. data/ext/tb_client/tigerbeetle/src/scripts/release.zig +1058 -0
  139. data/ext/tb_client/tigerbeetle/src/scripts.zig +105 -0
  140. data/ext/tb_client/tigerbeetle/src/shell.zig +1195 -0
  141. data/ext/tb_client/tigerbeetle/src/stack.zig +260 -0
  142. data/ext/tb_client/tigerbeetle/src/state_machine/auditor.zig +911 -0
  143. data/ext/tb_client/tigerbeetle/src/state_machine/workload.zig +2079 -0
  144. data/ext/tb_client/tigerbeetle/src/state_machine.zig +4872 -0
  145. data/ext/tb_client/tigerbeetle/src/state_machine_fuzz.zig +288 -0
  146. data/ext/tb_client/tigerbeetle/src/state_machine_tests.zig +3128 -0
  147. data/ext/tb_client/tigerbeetle/src/static_allocator.zig +82 -0
  148. data/ext/tb_client/tigerbeetle/src/stdx/bit_set.zig +157 -0
  149. data/ext/tb_client/tigerbeetle/src/stdx/bounded_array.zig +292 -0
  150. data/ext/tb_client/tigerbeetle/src/stdx/debug.zig +65 -0
  151. data/ext/tb_client/tigerbeetle/src/stdx/flags.zig +1414 -0
  152. data/ext/tb_client/tigerbeetle/src/stdx/mlock.zig +92 -0
  153. data/ext/tb_client/tigerbeetle/src/stdx/prng.zig +677 -0
  154. data/ext/tb_client/tigerbeetle/src/stdx/radix.zig +336 -0
  155. data/ext/tb_client/tigerbeetle/src/stdx/ring_buffer.zig +511 -0
  156. data/ext/tb_client/tigerbeetle/src/stdx/sort_test.zig +112 -0
  157. data/ext/tb_client/tigerbeetle/src/stdx/stdx.zig +1160 -0
  158. data/ext/tb_client/tigerbeetle/src/stdx/testing/low_level_hash_vectors.zig +142 -0
  159. data/ext/tb_client/tigerbeetle/src/stdx/testing/snaptest.zig +361 -0
  160. data/ext/tb_client/tigerbeetle/src/stdx/time_units.zig +275 -0
  161. data/ext/tb_client/tigerbeetle/src/stdx/unshare.zig +295 -0
  162. data/ext/tb_client/tigerbeetle/src/stdx/vendored/aegis.zig +436 -0
  163. data/ext/tb_client/tigerbeetle/src/stdx/windows.zig +48 -0
  164. data/ext/tb_client/tigerbeetle/src/stdx/zipfian.zig +402 -0
  165. data/ext/tb_client/tigerbeetle/src/storage.zig +489 -0
  166. data/ext/tb_client/tigerbeetle/src/storage_fuzz.zig +180 -0
  167. data/ext/tb_client/tigerbeetle/src/testing/bench.zig +146 -0
  168. data/ext/tb_client/tigerbeetle/src/testing/cluster/grid_checker.zig +53 -0
  169. data/ext/tb_client/tigerbeetle/src/testing/cluster/journal_checker.zig +61 -0
  170. data/ext/tb_client/tigerbeetle/src/testing/cluster/manifest_checker.zig +76 -0
  171. data/ext/tb_client/tigerbeetle/src/testing/cluster/message_bus.zig +110 -0
  172. data/ext/tb_client/tigerbeetle/src/testing/cluster/network.zig +412 -0
  173. data/ext/tb_client/tigerbeetle/src/testing/cluster/state_checker.zig +331 -0
  174. data/ext/tb_client/tigerbeetle/src/testing/cluster/storage_checker.zig +458 -0
  175. data/ext/tb_client/tigerbeetle/src/testing/cluster.zig +1198 -0
  176. data/ext/tb_client/tigerbeetle/src/testing/exhaustigen.zig +128 -0
  177. data/ext/tb_client/tigerbeetle/src/testing/fixtures.zig +181 -0
  178. data/ext/tb_client/tigerbeetle/src/testing/fuzz.zig +144 -0
  179. data/ext/tb_client/tigerbeetle/src/testing/id.zig +97 -0
  180. data/ext/tb_client/tigerbeetle/src/testing/io.zig +317 -0
  181. data/ext/tb_client/tigerbeetle/src/testing/marks.zig +126 -0
  182. data/ext/tb_client/tigerbeetle/src/testing/packet_simulator.zig +533 -0
  183. data/ext/tb_client/tigerbeetle/src/testing/reply_sequence.zig +154 -0
  184. data/ext/tb_client/tigerbeetle/src/testing/state_machine.zig +389 -0
  185. data/ext/tb_client/tigerbeetle/src/testing/storage.zig +1247 -0
  186. data/ext/tb_client/tigerbeetle/src/testing/table.zig +249 -0
  187. data/ext/tb_client/tigerbeetle/src/testing/time.zig +98 -0
  188. data/ext/tb_client/tigerbeetle/src/testing/tmp_tigerbeetle.zig +212 -0
  189. data/ext/tb_client/tigerbeetle/src/testing/vortex/constants.zig +26 -0
  190. data/ext/tb_client/tigerbeetle/src/testing/vortex/faulty_network.zig +580 -0
  191. data/ext/tb_client/tigerbeetle/src/testing/vortex/java_driver/ci.zig +39 -0
  192. data/ext/tb_client/tigerbeetle/src/testing/vortex/logged_process.zig +214 -0
  193. data/ext/tb_client/tigerbeetle/src/testing/vortex/rust_driver/ci.zig +34 -0
  194. data/ext/tb_client/tigerbeetle/src/testing/vortex/supervisor.zig +766 -0
  195. data/ext/tb_client/tigerbeetle/src/testing/vortex/workload.zig +543 -0
  196. data/ext/tb_client/tigerbeetle/src/testing/vortex/zig_driver.zig +181 -0
  197. data/ext/tb_client/tigerbeetle/src/tidy.zig +1448 -0
  198. data/ext/tb_client/tigerbeetle/src/tigerbeetle/benchmark_driver.zig +227 -0
  199. data/ext/tb_client/tigerbeetle/src/tigerbeetle/benchmark_load.zig +1069 -0
  200. data/ext/tb_client/tigerbeetle/src/tigerbeetle/cli.zig +1422 -0
  201. data/ext/tb_client/tigerbeetle/src/tigerbeetle/inspect.zig +1658 -0
  202. data/ext/tb_client/tigerbeetle/src/tigerbeetle/inspect_integrity.zig +518 -0
  203. data/ext/tb_client/tigerbeetle/src/tigerbeetle/libtb_client.zig +36 -0
  204. data/ext/tb_client/tigerbeetle/src/tigerbeetle/main.zig +646 -0
  205. data/ext/tb_client/tigerbeetle/src/tigerbeetle.zig +958 -0
  206. data/ext/tb_client/tigerbeetle/src/time.zig +236 -0
  207. data/ext/tb_client/tigerbeetle/src/trace/event.zig +745 -0
  208. data/ext/tb_client/tigerbeetle/src/trace/statsd.zig +462 -0
  209. data/ext/tb_client/tigerbeetle/src/trace.zig +556 -0
  210. data/ext/tb_client/tigerbeetle/src/unit_tests.zig +321 -0
  211. data/ext/tb_client/tigerbeetle/src/vopr.zig +1785 -0
  212. data/ext/tb_client/tigerbeetle/src/vortex.zig +101 -0
  213. data/ext/tb_client/tigerbeetle/src/vsr/checkpoint_trailer.zig +473 -0
  214. data/ext/tb_client/tigerbeetle/src/vsr/checksum.zig +208 -0
  215. data/ext/tb_client/tigerbeetle/src/vsr/checksum_benchmark.zig +43 -0
  216. data/ext/tb_client/tigerbeetle/src/vsr/client.zig +768 -0
  217. data/ext/tb_client/tigerbeetle/src/vsr/client_replies.zig +532 -0
  218. data/ext/tb_client/tigerbeetle/src/vsr/client_sessions.zig +338 -0
  219. data/ext/tb_client/tigerbeetle/src/vsr/clock.zig +1019 -0
  220. data/ext/tb_client/tigerbeetle/src/vsr/fault_detector.zig +279 -0
  221. data/ext/tb_client/tigerbeetle/src/vsr/free_set.zig +1381 -0
  222. data/ext/tb_client/tigerbeetle/src/vsr/free_set_fuzz.zig +315 -0
  223. data/ext/tb_client/tigerbeetle/src/vsr/grid.zig +1460 -0
  224. data/ext/tb_client/tigerbeetle/src/vsr/grid_blocks_missing.zig +757 -0
  225. data/ext/tb_client/tigerbeetle/src/vsr/grid_scrubber.zig +797 -0
  226. data/ext/tb_client/tigerbeetle/src/vsr/journal.zig +2586 -0
  227. data/ext/tb_client/tigerbeetle/src/vsr/marzullo.zig +308 -0
  228. data/ext/tb_client/tigerbeetle/src/vsr/message_header.zig +1777 -0
  229. data/ext/tb_client/tigerbeetle/src/vsr/multi_batch.zig +715 -0
  230. data/ext/tb_client/tigerbeetle/src/vsr/multi_batch_fuzz.zig +185 -0
  231. data/ext/tb_client/tigerbeetle/src/vsr/repair_budget.zig +333 -0
  232. data/ext/tb_client/tigerbeetle/src/vsr/replica.zig +12355 -0
  233. data/ext/tb_client/tigerbeetle/src/vsr/replica_format.zig +416 -0
  234. data/ext/tb_client/tigerbeetle/src/vsr/replica_reformat.zig +165 -0
  235. data/ext/tb_client/tigerbeetle/src/vsr/replica_test.zig +2910 -0
  236. data/ext/tb_client/tigerbeetle/src/vsr/routing.zig +1075 -0
  237. data/ext/tb_client/tigerbeetle/src/vsr/superblock.zig +1603 -0
  238. data/ext/tb_client/tigerbeetle/src/vsr/superblock_fuzz.zig +484 -0
  239. data/ext/tb_client/tigerbeetle/src/vsr/superblock_quorums.zig +405 -0
  240. data/ext/tb_client/tigerbeetle/src/vsr/superblock_quorums_fuzz.zig +355 -0
  241. data/ext/tb_client/tigerbeetle/src/vsr/sync.zig +29 -0
  242. data/ext/tb_client/tigerbeetle/src/vsr.zig +1727 -0
  243. data/lib/tb_client/shared_lib.rb +12 -5
  244. data/lib/tigerbeetle/client.rb +1 -1
  245. data/lib/tigerbeetle/platforms.rb +9 -0
  246. data/lib/tigerbeetle/version.rb +2 -2
  247. data/tigerbeetle.gemspec +22 -5
  248. metadata +242 -3
  249. data/ext/tb_client/pkg.tar.gz +0 -0
@@ -0,0 +1,3128 @@
1
+ const std = @import("std");
2
+ const assert = std.debug.assert;
3
+ const math = std.math;
4
+ const mem = std.mem;
5
+
6
+ const stdx = @import("stdx");
7
+ const maybe = stdx.maybe;
8
+
9
+ const tb = @import("tigerbeetle.zig");
10
+ const vsr = @import("vsr.zig");
11
+ const constants = vsr.constants;
12
+
13
+ const MultiBatchEncoder = vsr.multi_batch.MultiBatchEncoder;
14
+ const MultiBatchDecoder = vsr.multi_batch.MultiBatchDecoder;
15
+
16
+ const TimestampRange = @import("lsm/timestamp_range.zig").TimestampRange;
17
+
18
+ const Account = tb.Account;
19
+ const AccountBalance = tb.AccountBalance;
20
+ const Transfer = tb.Transfer;
21
+ const CreateAccountsResult = tb.CreateAccountsResult;
22
+ const CreateTransfersResult = tb.CreateTransfersResult;
23
+
24
+ const CreateAccountResult = tb.CreateAccountResult;
25
+ const CreateTransferResult = tb.CreateTransferResult;
26
+
27
+ const AccountFilter = tb.AccountFilter;
28
+ const QueryFilter = tb.QueryFilter;
29
+ const ChangeEventsFilter = tb.ChangeEventsFilter;
30
+ const ChangeEvent = tb.ChangeEvent;
31
+ const ChangeEventType = tb.ChangeEventType;
32
+
33
+ const StateMachineType = @import("state_machine.zig").StateMachineType;
34
+
35
+ const testing = std.testing;
36
+
37
+ pub const TestContext = struct {
38
+ const TimeSim = @import("testing/time.zig").TimeSim;
39
+ const Storage = @import("testing/storage.zig").Storage;
40
+ const Tracer = Storage.Tracer;
41
+ const data_file_size_min = @import("vsr/superblock.zig").data_file_size_min;
42
+ const SuperBlock = @import("vsr/superblock.zig").SuperBlockType(Storage);
43
+ const Grid = @import("vsr/grid.zig").GridType(Storage);
44
+ const fixtures = @import("testing/fixtures.zig");
45
+
46
+ pub const StateMachine = StateMachineType(Storage);
47
+
48
+ pub const Operation = enum {
49
+ create_accounts,
50
+ create_transfers,
51
+ lookup_accounts,
52
+ lookup_transfers,
53
+ get_account_transfers,
54
+ get_account_balances,
55
+ query_accounts,
56
+ query_transfers,
57
+ get_change_events,
58
+
59
+ const VersionMap = std.EnumArray(Operation, StateMachine.Operation);
60
+ /// Variations of operations supported by the state machine,
61
+ /// including deprecated ones used by old clients.
62
+ const versions: []const VersionMap = &.{
63
+ .init(.{
64
+ .create_accounts = .create_accounts,
65
+ .create_transfers = .create_transfers,
66
+ .lookup_accounts = .lookup_accounts,
67
+ .lookup_transfers = .lookup_transfers,
68
+ .get_account_transfers = .get_account_transfers,
69
+ .get_account_balances = .get_account_balances,
70
+ .query_accounts = .query_accounts,
71
+ .query_transfers = .query_transfers,
72
+ .get_change_events = .get_change_events,
73
+ }),
74
+ .init(.{
75
+ .create_accounts = .deprecated_create_accounts_unbatched,
76
+ .create_transfers = .deprecated_create_transfers_unbatched,
77
+ .lookup_accounts = .deprecated_lookup_accounts_unbatched,
78
+ .lookup_transfers = .deprecated_lookup_transfers_unbatched,
79
+ .get_account_transfers = .deprecated_get_account_transfers_unbatched,
80
+ .get_account_balances = .deprecated_get_account_balances_unbatched,
81
+ .query_accounts = .deprecated_query_accounts_unbatched,
82
+ .query_transfers = .deprecated_query_transfers_unbatched,
83
+ .get_change_events = .get_change_events,
84
+ }),
85
+ };
86
+ };
87
+
88
+ storage: Storage,
89
+ time_sim: TimeSim,
90
+ trace: Tracer,
91
+ superblock: SuperBlock,
92
+ grid: Grid,
93
+ state_machine: StateMachine,
94
+ op: u64,
95
+ busy: bool,
96
+
97
+ pub fn init(ctx: *TestContext, allocator: mem.Allocator) !void {
98
+ ctx.storage = try fixtures.init_storage(allocator, .{ .size = 4096 });
99
+ errdefer ctx.storage.deinit(allocator);
100
+
101
+ ctx.time_sim = fixtures.init_time(.{});
102
+
103
+ ctx.trace = try fixtures.init_tracer(allocator, ctx.time_sim.time(), .{});
104
+ errdefer ctx.trace.deinit(allocator);
105
+
106
+ ctx.superblock = try fixtures.init_superblock(allocator, &ctx.storage, .{
107
+ .storage_size_limit = data_file_size_min,
108
+ });
109
+ errdefer ctx.superblock.deinit(allocator);
110
+
111
+ // Pretend that the superblock is open so that the Forest can initialize.
112
+ ctx.superblock.opened = true;
113
+ ctx.superblock.working.vsr_state.checkpoint.header.op = 0;
114
+
115
+ ctx.grid = try fixtures.init_grid(allocator, &ctx.trace, &ctx.superblock, .{});
116
+ errdefer ctx.grid.deinit(allocator);
117
+
118
+ const batch_size_limit = 30 * @max(@sizeOf(Account), @sizeOf(Transfer));
119
+ assert(batch_size_limit <= constants.message_body_size_max);
120
+ try ctx.state_machine.init(
121
+ allocator,
122
+ ctx.time_sim.time(),
123
+ &ctx.grid,
124
+ .{
125
+ .batch_size_limit = batch_size_limit,
126
+ .lsm_forest_compaction_block_count = StateMachine.Forest.Options
127
+ .compaction_block_count_min,
128
+ .lsm_forest_node_count = 1,
129
+ .cache_entries_accounts = 0,
130
+ .cache_entries_transfers = 0,
131
+ .cache_entries_transfers_pending = 0,
132
+ .log_trace = true,
133
+ .aof_recovery = false,
134
+ },
135
+ );
136
+ errdefer ctx.state_machine.deinit(allocator);
137
+ // Usually, `pulse_next_timestamp` starts in an unknown state, signaling that the state
138
+ // machine needs a `pulse` to scan for pending transfers and correctly determine when to
139
+ // process the next expiry. However, this initial `pulse` unnecessarily bumps time, making
140
+ // unit tests that depend on the `timestamp` harder to reason about.
141
+ //
142
+ // Since this is a newly created state machine, we can bypass the initial check, ensuring
143
+ // that there will be no `timestamp` bumps between operations unless actual pending
144
+ // transfers get expired.
145
+ ctx.state_machine.expire_pending_transfers
146
+ .pulse_next_timestamp = TimestampRange.timestamp_max;
147
+
148
+ ctx.op = 1;
149
+ ctx.busy = false;
150
+ }
151
+
152
+ pub fn deinit(ctx: *TestContext, allocator: mem.Allocator) void {
153
+ ctx.state_machine.deinit(allocator);
154
+ ctx.grid.deinit(allocator);
155
+ ctx.superblock.deinit(allocator);
156
+ ctx.trace.deinit(allocator);
157
+ ctx.storage.deinit(allocator);
158
+ ctx.* = undefined;
159
+ }
160
+
161
+ fn callback(state_machine: *StateMachine) void {
162
+ const ctx: *TestContext = @fieldParentPtr("state_machine", state_machine);
163
+ assert(ctx.busy);
164
+ ctx.busy = false;
165
+ }
166
+
167
+ fn submit(
168
+ context: *TestContext,
169
+ operation: TestContext.StateMachine.Operation,
170
+ input_buffer: []align(16) u8,
171
+ input_size: u32,
172
+ output_buffer: *align(16) [constants.message_body_size_max]u8,
173
+ ) []const u8 {
174
+ const message_body: []align(16) const u8 = message_body: {
175
+ if (!operation.is_multi_batch()) {
176
+ break :message_body input_buffer[0..input_size];
177
+ }
178
+ assert(operation.is_multi_batch());
179
+ const event_size = operation.event_size();
180
+ var body_encoder = MultiBatchEncoder.init(input_buffer, .{
181
+ .element_size = event_size,
182
+ });
183
+ body_encoder.add(input_size);
184
+ const bytes_written = body_encoder.finish();
185
+ assert(bytes_written > 0);
186
+ break :message_body input_buffer[0..bytes_written];
187
+ };
188
+ context.prepare(operation, message_body);
189
+
190
+ const pulse_needed = context.state_machine.pulse_needed(
191
+ context.state_machine.prepare_timestamp,
192
+ );
193
+ maybe(pulse_needed);
194
+ // Pulse is executed in a best-effort manner
195
+ // after committing the current pipelined operation.
196
+ defer if (pulse_needed) context.pulse();
197
+
198
+ const reply_actual_size = context.execute(
199
+ context.op,
200
+ operation,
201
+ message_body,
202
+ output_buffer,
203
+ );
204
+
205
+ if (!operation.is_multi_batch()) {
206
+ return output_buffer[0..reply_actual_size];
207
+ }
208
+ assert(operation.is_multi_batch());
209
+
210
+ const result_size = operation.result_size();
211
+ var reply_decoder = MultiBatchDecoder.init(
212
+ output_buffer[0..reply_actual_size],
213
+ .{ .element_size = result_size },
214
+ ) catch unreachable;
215
+ assert(reply_decoder.batch_count() == 1);
216
+ return reply_decoder.peek();
217
+ }
218
+
219
+ pub fn prepare(
220
+ context: *TestContext,
221
+ operation: TestContext.StateMachine.Operation,
222
+ message_body_used: []align(16) const u8,
223
+ ) void {
224
+ context.state_machine.commit_timestamp = context.state_machine.prepare_timestamp;
225
+ context.state_machine.prepare_timestamp += 1;
226
+ context.state_machine.prepare(
227
+ operation,
228
+ message_body_used,
229
+ );
230
+ }
231
+
232
+ fn pulse(context: *TestContext) void {
233
+ if (context.state_machine.pulse_needed(context.state_machine.prepare_timestamp)) {
234
+ const operation = vsr.Operation.pulse.cast(TestContext.StateMachine.Operation);
235
+ context.prepare(operation, &.{});
236
+ const pulse_size = context.execute(
237
+ context.op,
238
+ operation,
239
+ &.{},
240
+ undefined, // Output is never used for pulse.
241
+ );
242
+ assert(pulse_size == 0);
243
+ context.op += 1;
244
+ }
245
+ }
246
+
247
+ pub fn execute(
248
+ context: *TestContext,
249
+ op: u64,
250
+ operation: TestContext.StateMachine.Operation,
251
+ message_body_used: []align(16) const u8,
252
+ output_buffer: *align(16) [constants.message_body_size_max]u8,
253
+ ) usize {
254
+ const timestamp = context.state_machine.prepare_timestamp;
255
+ context.busy = true;
256
+ context.state_machine.prefetch_timestamp = timestamp;
257
+ context.state_machine.prefetch(
258
+ TestContext.callback,
259
+ op,
260
+ op,
261
+ operation,
262
+ message_body_used,
263
+ );
264
+ while (context.busy) context.storage.run();
265
+
266
+ return context.state_machine.commit(
267
+ 1,
268
+ op,
269
+ timestamp,
270
+ operation,
271
+ message_body_used,
272
+ output_buffer,
273
+ );
274
+ }
275
+
276
+ fn get_account_from_cache(context: *TestContext, id: u128) ?Account {
277
+ return switch (context.state_machine.forest.grooves.accounts.get(id)) {
278
+ .found_object => |a| a,
279
+ .found_orphaned_id => unreachable,
280
+ .not_found => null,
281
+ };
282
+ }
283
+ };
284
+
285
+ const TestAction = union(enum) {
286
+ // Set the account's balance.
287
+ setup: struct {
288
+ account: u128,
289
+ debits_pending: u128,
290
+ debits_posted: u128,
291
+ credits_pending: u128,
292
+ credits_posted: u128,
293
+ },
294
+
295
+ tick: struct {
296
+ value: i64,
297
+ unit: enum { nanoseconds, seconds },
298
+ },
299
+
300
+ commit: TestContext.Operation,
301
+ account: TestCreateAccount,
302
+ transfer: TestCreateTransfer,
303
+
304
+ lookup_account: struct {
305
+ id: u128,
306
+ data: ?struct {
307
+ debits_pending: u128,
308
+ debits_posted: u128,
309
+ credits_pending: u128,
310
+ credits_posted: u128,
311
+ flag_closed: ?enum { CLSD } = null,
312
+ } = null,
313
+ },
314
+ lookup_transfer: struct {
315
+ id: u128,
316
+ data: union(enum) {
317
+ exists: bool,
318
+ amount: u128,
319
+ timestamp: u64,
320
+ },
321
+ },
322
+
323
+ get_account_balances: TestGetAccountBalances,
324
+ get_account_balances_result: struct {
325
+ transfer_id: u128,
326
+ debits_pending: u128,
327
+ debits_posted: u128,
328
+ credits_pending: u128,
329
+ credits_posted: u128,
330
+ },
331
+
332
+ get_account_transfers: TestGetAccountTransfers,
333
+ get_account_transfers_result: u128,
334
+
335
+ query_accounts: TestQueryAccounts,
336
+ query_accounts_result: struct {
337
+ id: u128,
338
+ data: ?struct {
339
+ debits_pending: u128,
340
+ debits_posted: u128,
341
+ credits_pending: u128,
342
+ credits_posted: u128,
343
+ flag_closed: ?enum { CLSD } = null,
344
+ } = null,
345
+ },
346
+
347
+ query_transfers: TestQueryTransfers,
348
+ query_transfers_result: u128,
349
+
350
+ get_change_events: TestGetChangeEventsFilter,
351
+ get_change_events_result: TestGetChangeEventsResult,
352
+ };
353
+
354
+ const TestCreateAccount = struct {
355
+ id: u128,
356
+ debits_pending: u128 = 0,
357
+ debits_posted: u128 = 0,
358
+ credits_pending: u128 = 0,
359
+ credits_posted: u128 = 0,
360
+ user_data_128: u128 = 0,
361
+ user_data_64: u64 = 0,
362
+ user_data_32: u32 = 0,
363
+ reserved: u1 = 0,
364
+ ledger: u32,
365
+ code: u16,
366
+ flags_linked: ?enum { LNK } = null,
367
+ flags_debits_must_not_exceed_credits: ?enum { @"D<C" } = null,
368
+ flags_credits_must_not_exceed_debits: ?enum { @"C<D" } = null,
369
+ flags_history: ?enum { HIST } = null,
370
+ flags_imported: ?enum { IMP } = null,
371
+ flags_closed: ?enum { CLSD } = null,
372
+ flags_padding: u10 = 0,
373
+ timestamp: u64 = 0,
374
+ result: CreateAccountResult,
375
+
376
+ fn event(a: TestCreateAccount) Account {
377
+ return .{
378
+ .id = a.id,
379
+ .debits_pending = a.debits_pending,
380
+ .debits_posted = a.debits_posted,
381
+ .credits_pending = a.credits_pending,
382
+ .credits_posted = a.credits_posted,
383
+ .user_data_128 = a.user_data_128,
384
+ .user_data_64 = a.user_data_64,
385
+ .user_data_32 = a.user_data_32,
386
+ .reserved = a.reserved,
387
+ .ledger = a.ledger,
388
+ .code = a.code,
389
+ .flags = .{
390
+ .linked = a.flags_linked != null,
391
+ .debits_must_not_exceed_credits = a.flags_debits_must_not_exceed_credits != null,
392
+ .credits_must_not_exceed_debits = a.flags_credits_must_not_exceed_debits != null,
393
+ .history = a.flags_history != null,
394
+ .imported = a.flags_imported != null,
395
+ .closed = a.flags_closed != null,
396
+ .padding = a.flags_padding,
397
+ },
398
+ .timestamp = a.timestamp,
399
+ };
400
+ }
401
+ };
402
+
403
+ const TestCreateTransfer = struct {
404
+ id: u128,
405
+ debit_account_id: u128,
406
+ credit_account_id: u128,
407
+ amount: u128 = 0,
408
+ pending_id: u128 = 0,
409
+ user_data_128: u128 = 0,
410
+ user_data_64: u64 = 0,
411
+ user_data_32: u32 = 0,
412
+ timeout: u32 = 0,
413
+ ledger: u32,
414
+ code: u16,
415
+ flags_linked: ?enum { LNK } = null,
416
+ flags_pending: ?enum { PEN } = null,
417
+ flags_post_pending_transfer: ?enum { POS } = null,
418
+ flags_void_pending_transfer: ?enum { VOI } = null,
419
+ flags_balancing_debit: ?enum { BDR } = null,
420
+ flags_balancing_credit: ?enum { BCR } = null,
421
+ flags_imported: ?enum { IMP } = null,
422
+ flags_closing_debit: ?enum { CDR } = null,
423
+ flags_closing_credit: ?enum { CCR } = null,
424
+ flags_padding: u5 = 0,
425
+ timestamp: u64 = 0,
426
+ result: CreateTransferResult,
427
+
428
+ fn event(t: TestCreateTransfer) Transfer {
429
+ return .{
430
+ .id = t.id,
431
+ .debit_account_id = t.debit_account_id,
432
+ .credit_account_id = t.credit_account_id,
433
+ .amount = t.amount,
434
+ .pending_id = t.pending_id,
435
+ .user_data_128 = t.user_data_128,
436
+ .user_data_64 = t.user_data_64,
437
+ .user_data_32 = t.user_data_32,
438
+ .timeout = t.timeout,
439
+ .ledger = t.ledger,
440
+ .code = t.code,
441
+ .flags = .{
442
+ .linked = t.flags_linked != null,
443
+ .pending = t.flags_pending != null,
444
+ .post_pending_transfer = t.flags_post_pending_transfer != null,
445
+ .void_pending_transfer = t.flags_void_pending_transfer != null,
446
+ .balancing_debit = t.flags_balancing_debit != null,
447
+ .balancing_credit = t.flags_balancing_credit != null,
448
+ .imported = t.flags_imported != null,
449
+ .closing_debit = t.flags_closing_debit != null,
450
+ .closing_credit = t.flags_closing_credit != null,
451
+ .padding = t.flags_padding,
452
+ },
453
+ .timestamp = t.timestamp,
454
+ };
455
+ }
456
+ };
457
+
458
+ const TestAccountFilter = struct {
459
+ account_id: u128,
460
+ user_data_128: ?u128 = null,
461
+ user_data_64: ?u64 = null,
462
+ user_data_32: ?u32 = null,
463
+ code: ?u16 = null,
464
+ // When non-null, the filter is set to the timestamp at which the specified transfer (by id) was
465
+ // created.
466
+ timestamp_min_transfer_id: ?u128 = null,
467
+ timestamp_max_transfer_id: ?u128 = null,
468
+ limit: u32,
469
+ flags_debits: ?enum { DR } = null,
470
+ flags_credits: ?enum { CR } = null,
471
+ flags_reversed: ?enum { REV } = null,
472
+ };
473
+
474
+ const TestQueryFilter = struct {
475
+ user_data_128: u128,
476
+ user_data_64: u64,
477
+ user_data_32: u32,
478
+ ledger: u32,
479
+ code: u16,
480
+ timestamp_min_transfer_id: ?u128 = null,
481
+ timestamp_max_transfer_id: ?u128 = null,
482
+ limit: u32,
483
+ flags_reversed: ?enum { REV } = null,
484
+ };
485
+
486
+ const TestGetChangeEventsFilter = struct {
487
+ timestamp_min_transfer_id: ?u128 = null,
488
+ timestamp_max_transfer_id: ?u128 = null,
489
+ limit: u32,
490
+ };
491
+
492
+ const TestGetChangeEventsResult = struct {
493
+ const Balance = struct {
494
+ account_id: u128,
495
+ debits_pending: u128,
496
+ debits_posted: u128,
497
+ credits_pending: u128,
498
+ credits_posted: u128,
499
+ closed: ?enum { CLSD } = null,
500
+ };
501
+
502
+ event_type: ?enum { PEN, POS, VOI, EXP } = null,
503
+ timestamp_transfer: ?u128 = null,
504
+ amount: u128,
505
+ transfer_pending_id: ?u128 = null,
506
+ dr_account: Balance,
507
+ cr_account: Balance,
508
+
509
+ fn match(
510
+ self: *const TestGetChangeEventsResult,
511
+ accounts: *std.AutoHashMap(u128, Account),
512
+ transfers: *std.AutoHashMap(u128, Transfer),
513
+ event: *const ChangeEvent,
514
+ ) bool {
515
+ if (self.timestamp_transfer) |id| {
516
+ const transfer = transfers.get(id).?;
517
+ if (event.type == .two_phase_expired) return false;
518
+ if (event.timestamp != transfer.timestamp) return false;
519
+ if (!match_transfer(event, &transfer)) return false;
520
+ }
521
+ if (self.event_type) |event_type| {
522
+ const expected: ChangeEventType = switch (event_type) {
523
+ .PEN => .two_phase_pending,
524
+ .POS => .two_phase_posted,
525
+ .VOI => .two_phase_voided,
526
+ .EXP => .two_phase_expired,
527
+ };
528
+ if (event.type != expected) return false;
529
+ } else {
530
+ if (event.type != .single_phase) return false;
531
+ }
532
+ if (event.transfer_amount != self.amount) return false;
533
+ if (self.transfer_pending_id) |transfer_pending_id| {
534
+ switch (event.type) {
535
+ .two_phase_pending, .single_phase => return false,
536
+ .two_phase_posted, .two_phase_voided => {
537
+ if (event.transfer_pending_id != transfer_pending_id) return false;
538
+ },
539
+ .two_phase_expired => {
540
+ const transfer = transfers.get(transfer_pending_id).?;
541
+ if (transfer.timeout == 0) return false;
542
+ if (event.timestamp <
543
+ transfer.timestamp + transfer.timeout_ns()) return false;
544
+ if (!match_transfer(event, &transfer)) return false;
545
+ },
546
+ }
547
+ }
548
+
549
+ const dr_account = accounts.get(self.dr_account.account_id).?;
550
+ if (dr_account.ledger != event.ledger) return false;
551
+ if (self.dr_account.account_id != event.debit_account_id) return false;
552
+ if (dr_account.timestamp != event.debit_account_timestamp) return false;
553
+ if (self.dr_account.debits_pending != event.debit_account_debits_pending) return false;
554
+ if (self.dr_account.debits_posted != event.debit_account_debits_posted) return false;
555
+ if (self.dr_account.credits_pending != event.debit_account_credits_pending) return false;
556
+ if (self.dr_account.credits_posted != event.debit_account_credits_posted) return false;
557
+ if ((self.dr_account.closed == .CLSD) != event.debit_account_flags.closed) return false;
558
+
559
+ const cr_account = accounts.get(self.cr_account.account_id).?;
560
+ if (cr_account.ledger != event.ledger) return false;
561
+ if (self.cr_account.account_id != event.credit_account_id) return false;
562
+ if (cr_account.timestamp != event.credit_account_timestamp) return false;
563
+ if (self.cr_account.debits_pending != event.credit_account_debits_pending) return false;
564
+ if (self.cr_account.debits_posted != event.credit_account_debits_posted) return false;
565
+ if (self.cr_account.credits_pending != event.credit_account_credits_pending) return false;
566
+ if (self.cr_account.credits_posted != event.credit_account_credits_posted) return false;
567
+ if ((self.cr_account.closed == .CLSD) != event.credit_account_flags.closed) return false;
568
+
569
+ return true;
570
+ }
571
+
572
+ fn match_transfer(event: *const ChangeEvent, transfer: *const tb.Transfer) bool {
573
+ if (event.transfer_timestamp != transfer.timestamp) return false;
574
+ if (event.transfer_id != transfer.id) return false;
575
+ if (event.transfer_amount != transfer.amount and
576
+ // The in-memory model keeps the `AMOUNT_MAX`.
577
+ transfer.amount != std.math.maxInt(u128)) return false;
578
+ if (event.transfer_pending_id != transfer.pending_id) return false;
579
+ if (event.transfer_user_data_128 != transfer.user_data_128) return false;
580
+ if (event.transfer_user_data_64 != transfer.user_data_64) return false;
581
+ if (event.transfer_user_data_32 != transfer.user_data_32) return false;
582
+ if (event.transfer_code != transfer.code) return false;
583
+ if (event.ledger != transfer.ledger) return false;
584
+ if (event.ledger != transfer.ledger) return false;
585
+ if (@as(u16, @bitCast(transfer.flags)) !=
586
+ @as(u16, @bitCast(event.transfer_flags))) return false;
587
+ return true;
588
+ }
589
+ };
590
+
591
+ // Operations that share the same input.
592
+ const TestGetAccountBalances = TestAccountFilter;
593
+ const TestGetAccountTransfers = TestAccountFilter;
594
+ const TestQueryAccounts = TestQueryFilter;
595
+ const TestQueryTransfers = TestQueryFilter;
596
+
597
+ fn check(test_table: []const u8) !void {
598
+ const parse_table = @import("testing/table.zig").parse;
599
+ const test_actions = parse_table(TestAction, test_table);
600
+
601
+ // Runs the same test for each variation of supported operations,
602
+ // simulating different client versions.
603
+ for (TestContext.Operation.versions) |*version_map| {
604
+ try check_version(
605
+ test_actions.const_slice(),
606
+ version_map,
607
+ );
608
+ }
609
+ }
610
+
611
+ fn check_version(
612
+ test_actions: []const TestAction,
613
+ version_map: *const TestContext.Operation.VersionMap,
614
+ ) !void {
615
+ const allocator = std.testing.allocator;
616
+
617
+ var context: TestContext = undefined;
618
+ try context.init(allocator);
619
+ defer context.deinit(allocator);
620
+
621
+ var accounts = std.AutoHashMap(u128, Account).init(allocator);
622
+ defer accounts.deinit();
623
+
624
+ var transfers = std.AutoHashMap(u128, Transfer).init(allocator);
625
+ defer transfers.deinit();
626
+
627
+ var request = std.ArrayListAligned(u8, 16).init(allocator);
628
+ defer request.deinit();
629
+
630
+ try request.ensureTotalCapacity(constants.message_body_size_max);
631
+
632
+ var reply = std.ArrayListAligned(u8, 16).init(allocator);
633
+ defer reply.deinit();
634
+
635
+ var operation: ?TestContext.Operation = null;
636
+ for (test_actions) |test_action| {
637
+ switch (test_action) {
638
+ .setup => |b| {
639
+ assert(operation == null);
640
+
641
+ const account = context.get_account_from_cache(b.account).?;
642
+ var account_new = account;
643
+
644
+ account_new.debits_pending = b.debits_pending;
645
+ account_new.debits_posted = b.debits_posted;
646
+ account_new.credits_pending = b.credits_pending;
647
+ account_new.credits_posted = b.credits_posted;
648
+ assert(!account_new.debits_exceed_credits(0));
649
+ assert(!account_new.credits_exceed_debits(0));
650
+
651
+ if (!stdx.equal_bytes(Account, &account_new, &account)) {
652
+ context.state_machine.forest.grooves.accounts.update(.{
653
+ .old = &account,
654
+ .new = &account_new,
655
+ });
656
+ }
657
+ },
658
+ .tick => |ticks| {
659
+ assert(ticks.value != 0);
660
+
661
+ const interval_ns: u64 = @abs(ticks.value) *
662
+ @as(u64, switch (ticks.unit) {
663
+ .nanoseconds => 1,
664
+ .seconds => std.time.ns_per_s,
665
+ });
666
+
667
+ // The `parse` logic already computes `maxInt - value` when a unsigned int is
668
+ // represented as a negative number. However, we need to use a signed int and
669
+ // perform our own calculation to account for the unit.
670
+ context.state_machine.prepare_timestamp += if (ticks.value > 0)
671
+ interval_ns
672
+ else
673
+ TimestampRange.timestamp_max - interval_ns;
674
+
675
+ // Pulse is executed when the cluster is idle.
676
+ context.pulse();
677
+ },
678
+ .account => |a| {
679
+ assert(operation == null or operation.? == .create_accounts);
680
+ operation = .create_accounts;
681
+
682
+ var event = a.event();
683
+ try request.appendSlice(std.mem.asBytes(&event));
684
+ if (a.result == .ok) {
685
+ if (a.timestamp == 0) {
686
+ event.timestamp = context.state_machine.prepare_timestamp + 1 +
687
+ @divExact(request.items.len, @sizeOf(Account));
688
+ }
689
+
690
+ try accounts.put(a.id, event);
691
+ } else {
692
+ const result = CreateAccountsResult{
693
+ .index = @intCast(@divExact(request.items.len, @sizeOf(Account)) - 1),
694
+ .result = a.result,
695
+ };
696
+ try reply.appendSlice(std.mem.asBytes(&result));
697
+ }
698
+ },
699
+ .transfer => |t| {
700
+ assert(operation == null or operation.? == .create_transfers);
701
+ operation = .create_transfers;
702
+
703
+ var event = t.event();
704
+ try request.appendSlice(std.mem.asBytes(&event));
705
+ if (t.result == .ok) {
706
+ if (t.timestamp == 0) {
707
+ event.timestamp = context.state_machine.prepare_timestamp + 1 +
708
+ @divExact(request.items.len, @sizeOf(Transfer));
709
+ }
710
+
711
+ if (event.pending_id != 0) {
712
+ // Fill in default values.
713
+ const t_pending = transfers.get(event.pending_id).?;
714
+ inline for (.{
715
+ "debit_account_id",
716
+ "credit_account_id",
717
+ "ledger",
718
+ "code",
719
+ "user_data_128",
720
+ "user_data_64",
721
+ "user_data_32",
722
+ }) |field| {
723
+ if (@field(event, field) == 0) {
724
+ @field(event, field) = @field(t_pending, field);
725
+ }
726
+ }
727
+
728
+ if (event.flags.void_pending_transfer) {
729
+ if (event.amount == 0) event.amount = t_pending.amount;
730
+ }
731
+ }
732
+ try transfers.put(t.id, event);
733
+ } else {
734
+ const result = CreateTransfersResult{
735
+ .index = @intCast(@divExact(request.items.len, @sizeOf(Transfer)) - 1),
736
+ .result = t.result,
737
+ };
738
+ try reply.appendSlice(std.mem.asBytes(&result));
739
+ }
740
+ },
741
+ .lookup_account => |a| {
742
+ assert(operation == null or operation.? == .lookup_accounts);
743
+ operation = .lookup_accounts;
744
+
745
+ try request.appendSlice(std.mem.asBytes(&a.id));
746
+ if (a.data) |data| {
747
+ var account = accounts.get(a.id).?;
748
+ account.debits_pending = data.debits_pending;
749
+ account.debits_posted = data.debits_posted;
750
+ account.credits_pending = data.credits_pending;
751
+ account.credits_posted = data.credits_posted;
752
+ account.flags.closed = data.flag_closed != null;
753
+ try reply.appendSlice(std.mem.asBytes(&account));
754
+ }
755
+ },
756
+ .lookup_transfer => |t| {
757
+ assert(operation == null or operation.? == .lookup_transfers);
758
+ operation = .lookup_transfers;
759
+
760
+ try request.appendSlice(std.mem.asBytes(&t.id));
761
+ switch (t.data) {
762
+ .exists => |exists| {
763
+ if (exists) {
764
+ var transfer = transfers.get(t.id).?;
765
+ try reply.appendSlice(std.mem.asBytes(&transfer));
766
+ }
767
+ },
768
+ .amount => |amount| {
769
+ var transfer = transfers.get(t.id).?;
770
+ transfer.amount = amount;
771
+ try reply.appendSlice(std.mem.asBytes(&transfer));
772
+ },
773
+ .timestamp => |timestamp| {
774
+ var transfer = transfers.get(t.id).?;
775
+ transfer.timestamp = timestamp;
776
+ try reply.appendSlice(std.mem.asBytes(&transfer));
777
+ },
778
+ }
779
+ },
780
+ .get_account_balances => |f| {
781
+ assert(operation == null or operation.? == .get_account_balances);
782
+ operation = .get_account_balances;
783
+
784
+ const timestamp_min =
785
+ if (f.timestamp_min_transfer_id) |id| transfers.get(id).?.timestamp else 0;
786
+ const timestamp_max =
787
+ if (f.timestamp_max_transfer_id) |id| transfers.get(id).?.timestamp else 0;
788
+
789
+ const event = AccountFilter{
790
+ .account_id = f.account_id,
791
+ .user_data_128 = f.user_data_128 orelse 0,
792
+ .user_data_64 = f.user_data_64 orelse 0,
793
+ .user_data_32 = f.user_data_32 orelse 0,
794
+ .code = f.code orelse 0,
795
+ .timestamp_min = timestamp_min,
796
+ .timestamp_max = timestamp_max,
797
+ .limit = f.limit,
798
+ .flags = .{
799
+ .debits = f.flags_debits != null,
800
+ .credits = f.flags_credits != null,
801
+ .reversed = f.flags_reversed != null,
802
+ },
803
+ };
804
+ try request.appendSlice(std.mem.asBytes(&event));
805
+ },
806
+ .get_account_balances_result => |r| {
807
+ assert(operation.? == .get_account_balances);
808
+
809
+ const balance = AccountBalance{
810
+ .debits_pending = r.debits_pending,
811
+ .debits_posted = r.debits_posted,
812
+ .credits_pending = r.credits_pending,
813
+ .credits_posted = r.credits_posted,
814
+ .timestamp = transfers.get(r.transfer_id).?.timestamp,
815
+ };
816
+ try reply.appendSlice(std.mem.asBytes(&balance));
817
+ },
818
+ .get_account_transfers => |f| {
819
+ assert(operation == null or operation.? == .get_account_transfers);
820
+ operation = .get_account_transfers;
821
+
822
+ const timestamp_min =
823
+ if (f.timestamp_min_transfer_id) |id| transfers.get(id).?.timestamp else 0;
824
+ const timestamp_max =
825
+ if (f.timestamp_max_transfer_id) |id| transfers.get(id).?.timestamp else 0;
826
+
827
+ const event = AccountFilter{
828
+ .account_id = f.account_id,
829
+ .user_data_128 = f.user_data_128 orelse 0,
830
+ .user_data_64 = f.user_data_64 orelse 0,
831
+ .user_data_32 = f.user_data_32 orelse 0,
832
+ .code = f.code orelse 0,
833
+ .timestamp_min = timestamp_min,
834
+ .timestamp_max = timestamp_max,
835
+ .limit = f.limit,
836
+ .flags = .{
837
+ .debits = f.flags_debits != null,
838
+ .credits = f.flags_credits != null,
839
+ .reversed = f.flags_reversed != null,
840
+ },
841
+ };
842
+ try request.appendSlice(std.mem.asBytes(&event));
843
+ },
844
+ .get_account_transfers_result => |id| {
845
+ assert(operation.? == .get_account_transfers);
846
+ try reply.appendSlice(std.mem.asBytes(&transfers.get(id).?));
847
+ },
848
+ .query_accounts => |f| {
849
+ assert(operation == null or operation.? == .query_accounts);
850
+ operation = .query_accounts;
851
+
852
+ const timestamp_min = if (f.timestamp_min_transfer_id) |id|
853
+ accounts.get(id).?.timestamp
854
+ else
855
+ 0;
856
+ const timestamp_max = if (f.timestamp_max_transfer_id) |id|
857
+ accounts.get(id).?.timestamp
858
+ else
859
+ 0;
860
+
861
+ const event = QueryFilter{
862
+ .user_data_128 = f.user_data_128,
863
+ .user_data_64 = f.user_data_64,
864
+ .user_data_32 = f.user_data_32,
865
+ .ledger = f.ledger,
866
+ .code = f.code,
867
+ .timestamp_min = timestamp_min,
868
+ .timestamp_max = timestamp_max,
869
+ .limit = f.limit,
870
+ .flags = .{
871
+ .reversed = f.flags_reversed != null,
872
+ },
873
+ };
874
+ try request.appendSlice(std.mem.asBytes(&event));
875
+ },
876
+ .query_accounts_result => |a| {
877
+ assert(operation.? == .query_accounts);
878
+ var account = accounts.get(a.id).?;
879
+ if (a.data) |data| {
880
+ account.debits_pending = data.debits_pending;
881
+ account.debits_posted = data.debits_posted;
882
+ account.credits_pending = data.credits_pending;
883
+ account.credits_posted = data.credits_posted;
884
+ account.flags.closed = data.flag_closed != null;
885
+ }
886
+ try reply.appendSlice(std.mem.asBytes(&account));
887
+ },
888
+ .query_transfers => |f| {
889
+ assert(operation == null or operation.? == .query_transfers);
890
+ operation = .query_transfers;
891
+
892
+ const timestamp_min = if (f.timestamp_min_transfer_id) |id|
893
+ transfers.get(id).?.timestamp
894
+ else
895
+ 0;
896
+ const timestamp_max = if (f.timestamp_max_transfer_id) |id|
897
+ transfers.get(id).?.timestamp
898
+ else
899
+ 0;
900
+
901
+ const event = QueryFilter{
902
+ .user_data_128 = f.user_data_128,
903
+ .user_data_64 = f.user_data_64,
904
+ .user_data_32 = f.user_data_32,
905
+ .ledger = f.ledger,
906
+ .code = f.code,
907
+ .timestamp_min = timestamp_min,
908
+ .timestamp_max = timestamp_max,
909
+ .limit = f.limit,
910
+ .flags = .{
911
+ .reversed = f.flags_reversed != null,
912
+ },
913
+ };
914
+ try request.appendSlice(std.mem.asBytes(&event));
915
+ },
916
+ .query_transfers_result => |id| {
917
+ assert(operation.? == .query_transfers);
918
+ try reply.appendSlice(std.mem.asBytes(&transfers.get(id).?));
919
+ },
920
+ .get_change_events => |f| {
921
+ assert(operation == null or operation.? == .get_change_events);
922
+ operation = .get_change_events;
923
+ const timestamp_min = if (f.timestamp_min_transfer_id) |id|
924
+ transfers.get(id).?.timestamp
925
+ else
926
+ 0;
927
+ const timestamp_max = if (f.timestamp_max_transfer_id) |id|
928
+ transfers.get(id).?.timestamp
929
+ else
930
+ 0;
931
+
932
+ const event = ChangeEventsFilter{
933
+ .timestamp_min = timestamp_min,
934
+ .timestamp_max = timestamp_max,
935
+ .limit = f.limit,
936
+ };
937
+ try request.appendSlice(std.mem.asBytes(&event));
938
+ },
939
+ .get_change_events_result => |*t| {
940
+ assert(operation.? == .get_change_events);
941
+ try reply.appendSlice(std.mem.asBytes(t));
942
+ },
943
+ .commit => |commit_operation| {
944
+ assert(operation == null or operation.? == commit_operation);
945
+ assert(!context.busy);
946
+
947
+ const reply_actual_buffer = try allocator.alignedAlloc(
948
+ u8,
949
+ 16,
950
+ constants.message_body_size_max,
951
+ );
952
+ defer allocator.free(reply_actual_buffer);
953
+
954
+ const payload_size: u32 = @intCast(request.items.len);
955
+ request.expandToCapacity();
956
+
957
+ const operation_actual = version_map.get(commit_operation);
958
+ const reply_actual = context.submit(
959
+ operation_actual,
960
+ request.items,
961
+ payload_size,
962
+ reply_actual_buffer[0..constants.message_body_size_max],
963
+ );
964
+
965
+ switch (operation_actual) {
966
+ inline else => |operation_actual_comptime| {
967
+ const Result = operation_actual_comptime.ResultType();
968
+ try testing.expectEqualSlices(
969
+ Result,
970
+ stdx.bytes_as_slice(.exact, Result, reply.items),
971
+ stdx.bytes_as_slice(.exact, Result, reply_actual),
972
+ );
973
+ },
974
+ .get_change_events => {
975
+ const results_actual = stdx.bytes_as_slice(
976
+ .exact,
977
+ ChangeEvent,
978
+ reply_actual,
979
+ );
980
+ const results_expected = stdx.bytes_as_slice(
981
+ .exact,
982
+ TestGetChangeEventsResult,
983
+ reply.items,
984
+ );
985
+ try testing.expectEqual(results_actual.len, results_expected.len);
986
+ for (results_actual, results_expected) |*actual, *expected| {
987
+ try testing.expect(expected.match(&accounts, &transfers, actual));
988
+ }
989
+ },
990
+ .pulse => unreachable,
991
+ }
992
+
993
+ request.clearRetainingCapacity();
994
+ reply.clearRetainingCapacity();
995
+ operation = null;
996
+ },
997
+ }
998
+ }
999
+
1000
+ assert(operation == null);
1001
+ assert(request.items.len == 0);
1002
+ assert(reply.items.len == 0);
1003
+ }
1004
+
1005
+ test "create_accounts" {
1006
+ try check(
1007
+ \\ account A1 0 0 0 0 U2 U2 U2 _ L3 C4 _ _ _ _ _ _ _ _ ok
1008
+ \\ account A0 1 1 1 1 _ _ _ 1 L0 C0 _ D<C C<D _ _ _ 1 1 timestamp_must_be_zero
1009
+ \\ account A0 1 1 1 1 _ _ _ 1 L0 C0 _ D<C C<D _ _ _ 1 _ reserved_field
1010
+ \\ account A0 1 1 1 1 _ _ _ _ L0 C0 _ D<C C<D _ _ _ 1 _ reserved_flag
1011
+ \\ account A0 1 1 1 1 _ _ _ _ L0 C0 _ D<C C<D _ _ _ _ _ id_must_not_be_zero
1012
+ \\ account -0 1 1 1 1 _ _ _ _ L0 C0 _ D<C C<D _ _ _ _ _ id_must_not_be_int_max
1013
+ \\ account A1 0 0 0 0 U1 U1 U1 _ L9 C9 _ D<C _ _ _ _ _ _ exists_with_different_flags
1014
+ \\ account A1 0 0 0 0 U1 U1 U1 _ L9 C9 _ _ C<D _ _ _ _ _ exists_with_different_flags
1015
+ \\ account A1 0 0 0 0 U1 U1 U1 _ L9 C9 _ _ _ _ _ _ _ _ exists_with_different_user_data_128
1016
+ \\ account A1 0 0 0 0 U2 U1 U1 _ L9 C9 _ _ _ _ _ _ _ _ exists_with_different_user_data_64
1017
+ \\ account A1 0 0 0 0 U2 U2 U1 _ L9 C9 _ _ _ _ _ _ _ _ exists_with_different_user_data_32
1018
+ \\ account A1 0 0 0 0 U2 U2 U2 _ L9 C9 _ _ _ _ _ _ _ _ exists_with_different_ledger
1019
+ \\ account A1 0 0 0 0 U2 U2 U2 _ L3 C9 _ _ _ _ _ _ _ _ exists_with_different_code
1020
+ \\ account A1 0 0 0 0 U2 U2 U2 _ L3 C4 _ _ _ _ _ _ _ _ exists
1021
+ \\ account A2 1 1 1 1 U1 U1 U1 _ L0 C0 _ D<C C<D _ _ _ _ _ flags_are_mutually_exclusive
1022
+ \\ account A2 1 1 1 1 U1 U1 U1 _ L9 C9 _ D<C _ _ _ _ _ _ debits_pending_must_be_zero
1023
+ \\ account A2 0 1 1 1 U1 U1 U1 _ L9 C9 _ D<C _ _ _ _ _ _ debits_posted_must_be_zero
1024
+ \\ account A2 0 0 1 1 U1 U1 U1 _ L9 C9 _ D<C _ _ _ _ _ _ credits_pending_must_be_zero
1025
+ \\ account A2 0 0 0 1 U1 U1 U1 _ L9 C9 _ D<C _ _ _ _ _ _ credits_posted_must_be_zero
1026
+ \\ account A2 0 0 0 0 U1 U1 U1 _ L0 C0 _ D<C _ _ _ _ _ _ ledger_must_not_be_zero
1027
+ \\ account A2 0 0 0 0 U1 U1 U1 _ L9 C0 _ D<C _ _ _ _ _ _ code_must_not_be_zero
1028
+ \\ commit create_accounts
1029
+ \\
1030
+ \\ lookup_account -0 _
1031
+ \\ lookup_account A0 _
1032
+ \\ lookup_account A1 0 0 0 0 _
1033
+ \\ lookup_account A2 _
1034
+ \\ commit lookup_accounts
1035
+ );
1036
+ }
1037
+
1038
+ test "create_accounts: empty" {
1039
+ try check(
1040
+ \\ commit create_transfers
1041
+ );
1042
+ }
1043
+
1044
+ test "linked accounts" {
1045
+ try check(
1046
+ \\ account A7 0 0 0 0 _ _ _ _ L1 C1 _ _ _ _ _ _ _ _ ok // An individual event (successful):
1047
+
1048
+ // A chain of 4 events (the last event in the chain closes the chain with linked=false):
1049
+ \\ account A1 0 0 0 0 _ _ _ _ L1 C1 LNK _ _ _ _ _ _ _ linked_event_failed // Commit/rollback.
1050
+ \\ account A2 0 0 0 0 _ _ _ _ L1 C1 LNK _ _ _ _ _ _ _ linked_event_failed // Commit/rollback.
1051
+ \\ account A1 0 0 0 0 _ _ _ _ L1 C1 LNK _ _ _ _ _ _ _ exists // Fail with .exists.
1052
+ \\ account A3 0 0 0 0 _ _ _ _ L1 C1 _ _ _ _ _ _ _ _ linked_event_failed // Fail without committing.
1053
+
1054
+ // An individual event (successful):
1055
+ // This does not see any effect from the failed chain above.
1056
+ \\ account A1 0 0 0 0 _ _ _ _ L1 C1 _ _ _ _ _ _ _ _ ok
1057
+
1058
+ // A chain of 2 events (the first event fails the chain):
1059
+ \\ account A1 0 0 0 0 _ _ _ _ L1 C2 LNK _ _ _ _ _ _ _ exists_with_different_flags
1060
+ \\ account A2 0 0 0 0 _ _ _ _ L1 C1 _ _ _ _ _ _ _ _ linked_event_failed
1061
+
1062
+ // An individual event (successful):
1063
+ \\ account A2 0 0 0 0 _ _ _ _ L1 C1 _ _ _ _ _ _ _ _ ok
1064
+
1065
+ // A chain of 2 events (the last event fails the chain):
1066
+ \\ account A3 0 0 0 0 _ _ _ _ L1 C1 LNK _ _ _ _ _ _ _ linked_event_failed
1067
+ \\ account A1 0 0 0 0 _ _ _ _ L2 C1 _ _ _ _ _ _ _ _ exists_with_different_ledger
1068
+
1069
+ // A chain of 2 events (successful):
1070
+ \\ account A3 0 0 0 0 _ _ _ _ L1 C1 LNK _ _ _ _ _ _ _ ok
1071
+ \\ account A4 0 0 0 0 _ _ _ _ L1 C1 _ _ _ _ _ _ _ _ ok
1072
+ \\ commit create_accounts
1073
+ \\
1074
+ \\ lookup_account A7 0 0 0 0 _
1075
+ \\ lookup_account A1 0 0 0 0 _
1076
+ \\ lookup_account A2 0 0 0 0 _
1077
+ \\ lookup_account A3 0 0 0 0 _
1078
+ \\ lookup_account A4 0 0 0 0 _
1079
+ \\ commit lookup_accounts
1080
+ );
1081
+
1082
+ try check(
1083
+ \\ account A7 0 0 0 0 _ _ _ _ L1 C1 _ _ _ _ _ _ _ _ ok // An individual event (successful):
1084
+
1085
+ // A chain of 4 events:
1086
+ \\ account A1 0 0 0 0 _ _ _ _ L1 C1 LNK _ _ _ _ _ _ _ linked_event_failed // Commit/rollback.
1087
+ \\ account A2 0 0 0 0 _ _ _ _ L1 C1 LNK _ _ _ _ _ _ _ linked_event_failed // Commit/rollback.
1088
+ \\ account A1 0 0 0 0 _ _ _ _ L1 C1 LNK _ _ _ _ _ _ _ exists // Fail with .exists.
1089
+ \\ account A3 0 0 0 0 _ _ _ _ L1 C1 _ _ _ _ _ _ _ _ linked_event_failed // Fail without committing.
1090
+ \\ commit create_accounts
1091
+ \\
1092
+ \\ lookup_account A7 0 0 0 0 _
1093
+ \\ lookup_account A1 _
1094
+ \\ lookup_account A2 _
1095
+ \\ lookup_account A3 _
1096
+ \\ commit lookup_accounts
1097
+ );
1098
+
1099
+ // TODO How can we test that events were in fact rolled back in LIFO order?
1100
+ // All our rollback handlers appear to be commutative.
1101
+ }
1102
+
1103
+ test "linked_event_chain_open" {
1104
+ try check(
1105
+ // A chain of 3 events (the last event in the chain closes the chain with linked=false):
1106
+ \\ account A1 0 0 0 0 _ _ _ _ L1 C1 LNK _ _ _ _ _ _ _ ok
1107
+ \\ account A2 0 0 0 0 _ _ _ _ L1 C1 LNK _ _ _ _ _ _ _ ok
1108
+ \\ account A3 0 0 0 0 _ _ _ _ L1 C1 _ _ _ _ _ _ _ _ ok
1109
+
1110
+ // An open chain of 2 events:
1111
+ \\ account A4 0 0 0 0 _ _ _ _ L1 C1 LNK _ _ _ _ _ _ _ linked_event_failed
1112
+ \\ account A5 0 0 0 0 _ _ _ _ L1 C1 LNK _ _ _ _ _ _ _ linked_event_chain_open
1113
+ \\ commit create_accounts
1114
+ \\
1115
+ \\ lookup_account A1 0 0 0 0 _
1116
+ \\ lookup_account A2 0 0 0 0 _
1117
+ \\ lookup_account A3 0 0 0 0 _
1118
+ \\ lookup_account A4 _
1119
+ \\ lookup_account A5 _
1120
+ \\ commit lookup_accounts
1121
+ );
1122
+ }
1123
+
1124
+ test "linked_event_chain_open for an already failed batch" {
1125
+ try check(
1126
+ // An individual event (successful):
1127
+ \\ account A1 0 0 0 0 _ _ _ _ L1 C1 _ _ _ _ _ _ _ _ ok
1128
+
1129
+ // An open chain of 3 events (the second one fails):
1130
+ \\ account A2 0 0 0 0 _ _ _ _ L1 C1 LNK _ _ _ _ _ _ _ linked_event_failed
1131
+ \\ account A1 0 0 0 0 _ _ _ _ L1 C1 LNK _ _ _ _ _ _ _ exists_with_different_flags
1132
+ \\ account A3 0 0 0 0 _ _ _ _ L1 C1 LNK _ _ _ _ _ _ _ linked_event_chain_open
1133
+ \\ commit create_accounts
1134
+ \\
1135
+ \\ lookup_account A1 0 0 0 0 _
1136
+ \\ lookup_account A2 _
1137
+ \\ lookup_account A3 _
1138
+ \\ commit lookup_accounts
1139
+ );
1140
+ }
1141
+
1142
+ test "linked_event_chain_open for a batch of 1" {
1143
+ try check(
1144
+ \\ account A1 0 0 0 0 _ _ _ _ L1 C1 LNK _ _ _ _ _ _ _ linked_event_chain_open
1145
+ \\ commit create_accounts
1146
+ \\
1147
+ \\ lookup_account A1 _
1148
+ \\ commit lookup_accounts
1149
+ );
1150
+ }
1151
+
1152
+ // The goal is to ensure that:
1153
+ // 1. all CreateTransferResult enums are covered, with
1154
+ // 2. enums tested in the order that they are defined, for easier auditing of coverage, and that
1155
+ // 3. state machine logic cannot be reordered in any way, breaking determinism.
1156
+ test "create_transfers/lookup_transfers" {
1157
+ try check(
1158
+ \\ account A1 0 0 0 0 _ _ _ _ L1 C1 _ _ _ _ _ _ _ _ ok
1159
+ \\ account A2 0 0 0 0 _ _ _ _ L2 C2 _ _ _ _ _ _ _ _ ok
1160
+ \\ account A3 0 0 0 0 _ _ _ _ L1 C1 _ _ _ _ _ _ _ _ ok
1161
+ \\ account A4 0 0 0 0 _ _ _ _ L1 C1 _ D<C _ _ _ _ _ _ ok
1162
+ \\ account A5 0 0 0 0 _ _ _ _ L1 C1 _ _ C<D _ _ _ _ _ ok
1163
+ \\ commit create_accounts
1164
+
1165
+ // Set up initial balances.
1166
+ \\ setup A1 100 200 0 0
1167
+ \\ setup A2 0 0 0 0
1168
+ \\ setup A3 0 0 110 210
1169
+ \\ setup A4 20 -700 0 -500
1170
+ \\ setup A5 0 -1000 10 -1100
1171
+
1172
+ // Bump the state machine time to `maxInt - 3s` for testing timeout overflow.
1173
+ \\ tick -3 seconds
1174
+
1175
+ // Test errors by descending precedence.
1176
+ \\ transfer T0 A0 A0 9 T1 _ _ _ _ L0 C0 _ PEN _ _ _ _ _ _ _ P1 1 timestamp_must_be_zero
1177
+ \\ transfer T0 A0 A0 9 T1 _ _ _ _ L0 C0 _ PEN _ _ _ _ _ _ _ P1 _ reserved_flag
1178
+ \\ transfer T0 A0 A0 9 T1 _ _ _ _ L0 C0 _ PEN _ _ _ _ _ _ _ _ _ id_must_not_be_zero
1179
+ \\ transfer -0 A0 A0 9 T1 _ _ _ _ L0 C0 _ PEN _ _ _ _ _ _ _ _ _ id_must_not_be_int_max
1180
+ \\ transfer T1 A0 A0 9 T1 _ _ _ _ L0 C0 _ PEN _ _ _ _ _ _ _ _ _ debit_account_id_must_not_be_zero
1181
+ \\ transfer T1 -0 A0 9 T1 _ _ _ _ L0 C0 _ PEN _ _ _ _ _ _ _ _ _ debit_account_id_must_not_be_int_max
1182
+ \\ transfer T1 A8 A0 9 T1 _ _ _ _ L0 C0 _ PEN _ _ _ _ _ _ _ _ _ credit_account_id_must_not_be_zero
1183
+ \\ transfer T1 A8 -0 9 T1 _ _ _ _ L0 C0 _ PEN _ _ _ _ _ _ _ _ _ credit_account_id_must_not_be_int_max
1184
+ \\ transfer T1 A8 A8 9 T1 _ _ _ _ L0 C0 _ PEN _ _ _ _ _ _ _ _ _ accounts_must_be_different
1185
+ \\ transfer T1 A8 A9 9 T1 _ _ _ _ L0 C0 _ PEN _ _ _ _ _ _ _ _ _ pending_id_must_be_zero
1186
+ \\ transfer T1 A8 A9 9 _ _ _ _ 1 L0 C0 _ _ _ _ _ _ _ _ _ _ _ timeout_reserved_for_pending_transfer
1187
+ \\ transfer T1 A8 A9 9 _ _ _ _ _ L0 C0 _ _ _ _ _ _ _ CDR _ _ _ closing_transfer_must_be_pending
1188
+ \\ transfer T1 A8 A9 9 _ _ _ _ _ L0 C0 _ _ _ _ _ _ _ _ CCR _ _ closing_transfer_must_be_pending
1189
+ \\ transfer T1 A8 A9 9 _ _ _ _ _ L0 C0 _ PEN _ _ _ _ _ _ _ _ _ ledger_must_not_be_zero
1190
+ \\ transfer T1 A8 A9 9 _ _ _ _ _ L9 C0 _ PEN _ _ _ _ _ _ _ _ _ code_must_not_be_zero
1191
+ // `debit_account_not_found` is a transient error, T1 cannot be reused:
1192
+ \\ transfer T1 A8 A9 9 _ _ _ _ _ L9 C1 _ PEN _ _ _ _ _ _ _ _ _ debit_account_not_found
1193
+ \\ transfer T1 A1 A3 123 _ _ _ _ _ L1 C1 _ _ _ _ _ _ _ _ _ _ _ id_already_failed
1194
+ // `credit_account_not_found` is a transient error, T2 cannot be reused:
1195
+ \\ transfer T2 A1 A9 9 _ _ _ _ _ L9 C1 _ PEN _ _ _ _ _ _ _ _ _ credit_account_not_found
1196
+ \\ transfer T2 A1 A3 123 _ _ _ _ _ L1 C1 _ _ _ _ _ _ _ _ _ _ _ id_already_failed
1197
+ \\ commit create_transfers
1198
+ \\
1199
+ \\ transfer T3 A1 A2 1 _ _ _ _ _ L9 C1 _ PEN _ _ _ _ _ _ _ _ _ accounts_must_have_the_same_ledger
1200
+ \\ transfer T3 A1 A3 1 _ _ _ _ _ L9 C1 _ PEN _ _ _ _ _ _ _ _ _ transfer_must_have_the_same_ledger_as_accounts
1201
+ \\ transfer T3 A1 A3 -99 _ _ _ _ _ L1 C1 _ PEN _ _ _ _ _ _ _ _ _ overflows_debits_pending // amount = max - A1.debits_pending + 1
1202
+ \\ transfer T3 A1 A3 -109 _ _ _ _ _ L1 C1 _ PEN _ _ _ _ _ _ _ _ _ overflows_credits_pending // amount = max - A3.credits_pending + 1
1203
+ \\ transfer T3 A1 A3 -199 _ _ _ _ _ L1 C1 _ PEN _ _ _ _ _ _ _ _ _ overflows_debits_posted // amount = max - A1.debits_posted + 1
1204
+ \\ transfer T3 A1 A3 -209 _ _ _ _ _ L1 C1 _ PEN _ _ _ _ _ _ _ _ _ overflows_credits_posted // amount = max - A3.credits_posted + 1
1205
+ \\ transfer T3 A1 A3 -299 _ _ _ _ _ L1 C1 _ PEN _ _ _ _ _ _ _ _ _ overflows_debits // amount = max - A1.debits_pending - A1.debits_posted + 1
1206
+ \\ transfer T3 A1 A3 -319 _ _ _ _ _ L1 C1 _ PEN _ _ _ _ _ _ _ _ _ overflows_credits // amount = max - A3.credits_pending - A3.credits_posted + 1
1207
+ \\ transfer T3 A4 A5 199 _ _ _ _ 999 L1 C1 _ PEN _ _ _ _ _ _ _ _ _ overflows_timeout
1208
+ // `exceeds_credits` is a transient error, T3 cannot be reused:
1209
+ \\ transfer T3 A4 A5 199 _ _ _ _ _ L1 C1 _ _ _ _ _ _ _ _ _ _ _ exceeds_credits // amount = A4.credits_posted - A4.debits_pending - A4.debits_posted + 1
1210
+ \\ transfer T3 A1 A3 123 _ _ _ _ _ L1 C1 _ _ _ _ _ _ _ _ _ _ _ id_already_failed
1211
+ // `exceeds_debits` is a transient error, T4 cannot be reused:
1212
+ \\ transfer T4 A4 A5 91 _ _ _ _ _ L1 C1 _ _ _ _ _ _ _ _ _ _ _ exceeds_debits // amount = A5.debits_posted - A5.credits_pending - A5.credits_posted + 1
1213
+ \\ transfer T4 A1 A3 123 _ _ _ _ _ L1 C1 _ _ _ _ _ _ _ _ _ _ _ id_already_failed
1214
+ \\
1215
+ \\ transfer T5 A1 A3 123 _ _ _ _ 1 L1 C1 _ PEN _ _ _ _ _ _ _ _ _ ok
1216
+ \\ commit create_transfers
1217
+
1218
+ // Ensure that idempotence is checked first:
1219
+ \\ transfer T5 A1 A3 123 _ _ _ _ 1 L2 C1 _ PEN _ _ _ _ _ _ _ _ _ exists_with_different_ledger
1220
+ \\ transfer T5 A1 A3 -0 _ U1 U1 U1 _ L1 C2 _ _ _ _ _ _ _ _ _ _ _ exists_with_different_flags
1221
+ \\ transfer T5 A3 A1 -0 _ U1 U1 U1 1 L1 C2 _ PEN _ _ _ _ _ _ _ _ _ exists_with_different_debit_account_id
1222
+ \\ transfer T5 A1 A4 -0 _ U1 U1 U1 1 L1 C2 _ PEN _ _ _ _ _ _ _ _ _ exists_with_different_credit_account_id
1223
+ \\ transfer T5 A1 A3 -0 _ U1 U1 U1 1 L1 C1 _ PEN _ _ _ _ _ _ _ _ _ exists_with_different_amount
1224
+ \\ transfer T5 A1 A3 123 _ U1 U1 U1 1 L1 C2 _ PEN _ _ _ _ _ _ _ _ _ exists_with_different_user_data_128
1225
+ \\ transfer T5 A1 A3 123 _ _ U1 U1 1 L1 C2 _ PEN _ _ _ _ _ _ _ _ _ exists_with_different_user_data_64
1226
+ \\ transfer T5 A1 A3 123 _ _ _ U1 1 L1 C2 _ PEN _ _ _ _ _ _ _ _ _ exists_with_different_user_data_32
1227
+ \\ transfer T5 A1 A3 123 _ _ _ _ 2 L1 C2 _ PEN _ _ _ _ _ _ _ _ _ exists_with_different_timeout
1228
+ \\ transfer T5 A1 A3 123 _ _ _ _ 1 L1 C2 _ PEN _ _ _ _ _ _ _ _ _ exists_with_different_code
1229
+ \\ transfer T5 A1 A3 123 _ _ _ _ 1 L1 C1 _ PEN _ _ _ _ _ _ _ _ _ exists
1230
+ \\
1231
+ \\ transfer T6 A3 A1 7 _ _ _ _ _ L1 C2 _ _ _ _ _ _ _ _ _ _ _ ok
1232
+ \\ transfer T7 A1 A3 3 _ _ _ _ _ L1 C2 _ _ _ _ _ _ _ _ _ _ _ ok
1233
+ \\ transfer T8 A1 A3 0 _ _ _ _ _ L1 C2 _ _ _ _ _ _ _ _ _ _ _ ok
1234
+ \\ commit create_transfers
1235
+ \\
1236
+ \\ lookup_account A1 223 203 0 7 _
1237
+ \\ lookup_account A3 0 7 233 213 _
1238
+ \\ commit lookup_accounts
1239
+ \\
1240
+ \\ lookup_transfer T5 exists true
1241
+ \\ lookup_transfer T6 exists true
1242
+ \\ lookup_transfer T7 exists true
1243
+ \\ lookup_transfer T8 exists true
1244
+ \\ lookup_transfer -0 exists false
1245
+ \\ commit lookup_transfers
1246
+ );
1247
+ }
1248
+
1249
+ test "create/lookup 2-phase transfers" {
1250
+ try check(
1251
+ \\ account A1 0 0 0 0 _ _ _ _ L1 C1 _ _ _ _ _ _ _ _ ok
1252
+ \\ account A2 0 0 0 0 _ _ _ _ L1 C1 _ _ _ _ _ _ _ _ ok
1253
+ \\ commit create_accounts
1254
+
1255
+ // First phase.
1256
+ \\ transfer T1 A1 A2 15 _ _ _ _ _ L1 C1 _ _ _ _ _ _ _ _ _ _ _ ok // Not pending!
1257
+ \\ transfer T2 A1 A2 15 _ _ _ _ 1000 L1 C1 _ PEN _ _ _ _ _ _ _ _ _ ok
1258
+ \\ transfer T3 A1 A2 15 _ _ _ _ 50 L1 C1 _ PEN _ _ _ _ _ _ _ _ _ ok
1259
+ \\ transfer T4 A1 A2 15 _ _ _ _ 1 L1 C1 _ PEN _ _ _ _ _ _ _ _ _ ok
1260
+ \\ transfer T5 A1 A2 7 _ U9 U9 U9 50 L1 C1 _ PEN _ _ _ _ _ _ _ _ _ ok
1261
+ \\ transfer T6 A1 A2 1 _ _ _ _ 0 L1 C1 _ PEN _ _ _ _ _ _ _ _ _ ok
1262
+ \\ transfer T7 A1 A2 1 _ _ _ _ 0 L1 C1 _ PEN _ _ _ _ _ _ _ _ _ ok
1263
+ \\ commit create_transfers
1264
+
1265
+ // Check balances before resolving.
1266
+ \\ lookup_account A1 54 15 0 0 _
1267
+ \\ lookup_account A2 0 0 54 15 _
1268
+ \\ commit lookup_accounts
1269
+
1270
+ // Bump the state machine time in +1s for testing the timeout expiration.
1271
+ \\ tick 1 seconds
1272
+
1273
+ // Second phase.
1274
+ \\ transfer T101 A1 A2 13 T2 U1 U1 U1 _ L1 C1 _ _ POS _ _ _ _ _ _ _ _ ok
1275
+ \\ transfer T0 A8 A9 16 T0 U2 U2 U2 50 L6 C7 _ PEN POS VOI _ _ _ _ _ _ 1 timestamp_must_be_zero
1276
+ \\ transfer T0 A8 A9 16 T0 U2 U2 U2 50 L6 C7 _ PEN POS VOI _ _ _ _ _ _ _ id_must_not_be_zero
1277
+ \\ transfer -0 A8 A9 16 T0 U2 U2 U2 50 L6 C7 _ PEN POS VOI _ _ _ _ _ _ _ id_must_not_be_int_max
1278
+ \\ transfer T101 A1 A2 15 T3 U2 U2 U2 _ L1 C1 _ _ _ VOI _ _ _ _ _ _ _ exists_with_different_flags
1279
+ \\ transfer T101 A1 A2 14 T2 U1 U1 U1 _ L1 C1 _ _ POS _ _ _ _ _ _ _ _ exists_with_different_amount
1280
+ \\ transfer T101 A1 A2 _ T2 U1 U1 U1 _ L1 C1 _ _ POS _ _ _ _ _ _ _ _ exists_with_different_amount
1281
+ \\ transfer T101 A1 A2 13 T3 U2 U2 U2 _ L1 C1 _ _ POS _ _ _ _ _ _ _ _ exists_with_different_pending_id
1282
+ \\ transfer T101 A1 A2 13 T2 U2 U2 U2 _ L1 C1 _ _ POS _ _ _ _ _ _ _ _ exists_with_different_user_data_128
1283
+ \\ transfer T101 A1 A2 13 T2 U1 U2 U2 _ L1 C1 _ _ POS _ _ _ _ _ _ _ _ exists_with_different_user_data_64
1284
+ \\ transfer T101 A1 A2 13 T2 U1 U1 U2 _ L1 C1 _ _ POS _ _ _ _ _ _ _ _ exists_with_different_user_data_32
1285
+ \\ transfer T101 A1 A2 13 T2 U1 U1 U1 _ L1 C1 _ _ POS _ _ _ _ _ _ _ _ exists
1286
+ \\ transfer T102 A8 A9 16 T0 U2 U2 U2 50 L6 C7 _ PEN POS VOI _ _ _ _ _ _ _ flags_are_mutually_exclusive
1287
+ \\ transfer T102 A8 A9 16 T0 U2 U2 U2 50 L6 C7 _ PEN POS VOI BDR _ _ _ _ _ _ flags_are_mutually_exclusive
1288
+ \\ transfer T102 A8 A9 16 T0 U2 U2 U2 50 L6 C7 _ PEN POS VOI BDR BCR _ _ _ _ _ flags_are_mutually_exclusive
1289
+ \\ transfer T102 A8 A9 16 T0 U2 U2 U2 50 L6 C7 _ PEN POS VOI _ BCR _ _ _ _ _ flags_are_mutually_exclusive
1290
+ \\ transfer T102 A8 A9 16 T0 U2 U2 U2 50 L6 C7 _ PEN _ VOI _ _ _ _ _ _ _ flags_are_mutually_exclusive
1291
+ \\ transfer T102 A8 A9 16 T0 U2 U2 U2 50 L6 C7 _ _ _ VOI BDR _ _ _ _ _ _ flags_are_mutually_exclusive
1292
+ \\ transfer T102 A8 A9 16 T0 U2 U2 U2 50 L6 C7 _ _ _ VOI BDR BCR _ _ _ _ _ flags_are_mutually_exclusive
1293
+ \\ transfer T102 A8 A9 16 T0 U2 U2 U2 50 L6 C7 _ _ _ VOI _ BCR _ _ _ _ _ flags_are_mutually_exclusive
1294
+ \\ transfer T102 A8 A9 16 T0 U2 U2 U2 50 L6 C7 _ _ POS _ BDR _ _ _ _ _ _ flags_are_mutually_exclusive
1295
+ \\ transfer T102 A8 A9 16 T0 U2 U2 U2 50 L6 C7 _ _ POS _ BDR BCR _ _ _ _ _ flags_are_mutually_exclusive
1296
+ \\ transfer T102 A8 A9 16 T0 U2 U2 U2 50 L6 C7 _ _ POS _ _ BCR _ _ _ _ _ flags_are_mutually_exclusive
1297
+ \\ transfer T102 A8 A9 16 T0 U2 U2 U2 50 L6 C7 _ _ _ VOI _ _ _ _ _ _ _ pending_id_must_not_be_zero
1298
+ \\ transfer T102 A8 A9 16 -0 U2 U2 U2 50 L6 C7 _ _ _ VOI _ _ _ _ _ _ _ pending_id_must_not_be_int_max
1299
+ \\ transfer T102 A8 A9 16 102 U2 U2 U2 50 L6 C7 _ _ _ VOI _ _ _ _ _ _ _ pending_id_must_be_different
1300
+ \\ transfer T102 A8 A9 16 103 U2 U2 U2 50 L6 C7 _ _ _ VOI _ _ _ _ _ _ _ timeout_reserved_for_pending_transfer
1301
+ \\ commit create_transfers
1302
+
1303
+ // `pending_transfer_not_found` is a transient error, T102 cannot be reused:
1304
+ \\ transfer T102 A8 A9 16 103 U2 U2 U2 _ L6 C7 _ _ _ VOI _ _ _ _ _ _ _ pending_transfer_not_found
1305
+ \\ transfer T102 A1 A2 13 _ U1 U1 U1 _ L1 C1 _ _ _ _ _ _ _ _ _ _ _ id_already_failed
1306
+ \\
1307
+ \\ transfer T103 A8 A9 16 T1 U2 U2 U2 _ L6 C7 _ _ _ VOI _ _ _ _ _ _ _ pending_transfer_not_pending
1308
+ \\ transfer T103 A8 A9 16 T3 U2 U2 U2 _ L6 C7 _ _ _ VOI _ _ _ _ _ _ _ pending_transfer_has_different_debit_account_id
1309
+ \\ transfer T103 A1 A9 16 T3 U2 U2 U2 _ L6 C7 _ _ _ VOI _ _ _ _ _ _ _ pending_transfer_has_different_credit_account_id
1310
+ \\ transfer T103 A1 A2 16 T3 U2 U2 U2 _ L6 C7 _ _ _ VOI _ _ _ _ _ _ _ pending_transfer_has_different_ledger
1311
+ \\ transfer T103 A1 A2 16 T3 U2 U2 U2 _ L1 C7 _ _ _ VOI _ _ _ _ _ _ _ pending_transfer_has_different_code
1312
+ \\ transfer T103 A1 A2 16 T3 U2 U2 U2 _ L1 C1 _ _ _ VOI _ _ _ _ _ _ _ exceeds_pending_transfer_amount
1313
+ \\ transfer T103 A1 A2 14 T3 U2 U2 U2 _ L1 C1 _ _ _ VOI _ _ _ _ _ _ _ pending_transfer_has_different_amount
1314
+ \\ transfer T103 A1 A2 13 T2 U1 U1 U1 _ L1 C1 _ _ POS _ _ _ _ _ _ _ _ pending_transfer_already_posted
1315
+ \\ transfer T103 A1 A2 15 T3 U1 U1 U1 _ L1 C1 _ _ _ VOI _ _ _ _ _ _ _ ok
1316
+ \\ transfer T104 A1 A2 13 T3 U1 U1 U1 _ L1 C1 _ _ POS _ _ _ _ _ _ _ _ pending_transfer_already_voided
1317
+ \\ transfer T104 A1 A2 15 T4 U1 U1 U1 _ L1 C1 _ _ _ VOI _ _ _ _ _ _ _ pending_transfer_expired
1318
+ \\ commit create_transfers
1319
+
1320
+ // Transfers posted/voided with optional fields must not raise `exists_with_different_*`.
1321
+ // But transfers posted with posted.amount≠pending.amount may return
1322
+ // exists_with_different_amount.
1323
+ \\ transfer T101 A0 A0 14 T2 U0 U0 U0 _ L0 C0 _ _ POS _ _ _ _ _ _ _ _ exists_with_different_amount // t.amount > e.amount
1324
+ \\ transfer T101 A0 A0 14 T2 U0 U0 U0 _ L0 C0 _ _ POS _ _ _ _ _ _ _ _ exists_with_different_amount
1325
+ \\ transfer T101 A0 A0 12 T2 U0 U0 U0 _ L0 C0 _ _ POS _ _ _ _ _ _ _ _ exists_with_different_amount // t.amount < e.amount
1326
+ \\
1327
+ \\ transfer T105 A0 A0 8 T5 U0 U0 U0 _ L0 C0 _ _ POS _ _ _ _ _ _ _ _ exceeds_pending_transfer_amount // t.amount > p.amount
1328
+ \\ transfer T105 A0 A0 -0 T5 U0 U0 U0 _ L0 C0 _ _ POS _ _ _ _ _ _ _ _ ok
1329
+ \\ transfer T105 A0 A0 7 T5 U0 U0 U0 _ L1 C1 _ _ POS _ _ _ _ _ _ _ _ exists
1330
+ \\ transfer T105 A0 A0 7 T5 U0 U0 U0 _ L0 C0 _ _ POS _ _ _ _ _ _ _ _ exists // ledger/code = 0
1331
+ \\ transfer T105 A0 A0 -0 T5 U0 U0 U0 _ L1 C1 _ _ POS _ _ _ _ _ _ _ _ exists // amount = max
1332
+ \\ transfer T105 A0 A0 8 T5 U0 U0 U0 _ L0 C0 _ _ POS _ _ _ _ _ _ _ _ exists_with_different_amount // t.amount > p.amount
1333
+ \\ transfer T105 A0 A0 6 T5 U0 U0 U0 _ L0 C0 _ _ POS _ _ _ _ _ _ _ _ exists_with_different_amount // t.amount < e.amount
1334
+ \\ transfer T105 A0 A0 0 T5 U0 U0 U0 _ L1 C1 _ _ POS _ _ _ _ _ _ _ _ exists_with_different_amount
1335
+ \\
1336
+ \\ transfer T106 A0 A0 -1 T6 U0 U0 U0 _ L1 C1 _ _ POS _ _ _ _ _ _ _ _ exceeds_pending_transfer_amount
1337
+ \\ transfer T106 A0 A0 -0 T6 U0 U0 U0 _ L1 C1 _ _ POS _ _ _ _ _ _ _ _ ok
1338
+ \\ transfer T106 A0 A0 -0 T6 U0 U0 U0 _ L1 C1 _ _ POS _ _ _ _ _ _ _ _ exists
1339
+ \\ transfer T106 A0 A0 1 T6 U0 U0 U0 _ L0 C0 _ _ POS _ _ _ _ _ _ _ _ exists
1340
+ \\ transfer T106 A0 A0 2 T6 U0 U0 U0 _ L1 C1 _ _ POS _ _ _ _ _ _ _ _ exists_with_different_amount // t.amount > p.amount
1341
+ \\ transfer T106 A0 A0 0 T6 U0 U0 U0 _ L1 C1 _ _ POS _ _ _ _ _ _ _ _ exists_with_different_amount // t.amount < p.amount
1342
+ \\
1343
+ \\ transfer T107 A0 A0 0 T7 U0 U0 U0 _ L0 C0 _ _ POS _ _ _ _ _ _ _ _ ok
1344
+ \\ transfer T107 A0 A0 0 T7 U0 U0 U0 _ L0 C0 _ _ POS _ _ _ _ _ _ _ _ exists
1345
+ \\ transfer T107 A0 A0 1 T7 U0 U0 U0 _ L0 C0 _ _ POS _ _ _ _ _ _ _ _ exists_with_different_amount // t.amount > e.amount
1346
+ \\ commit create_transfers
1347
+
1348
+ // Check balances after resolving.
1349
+ \\ lookup_account A1 0 36 0 0 _
1350
+ \\ lookup_account A2 0 0 0 36 _
1351
+ \\ commit lookup_accounts
1352
+
1353
+ // The posted transfer amounts are set to the actual amount posted (which may be less than
1354
+ // the "client" set as the amount).
1355
+ \\ lookup_transfer T101 amount 13
1356
+ \\ lookup_transfer T105 amount 7
1357
+ \\ lookup_transfer T106 amount 1
1358
+ \\ lookup_transfer T107 amount 0
1359
+ \\ commit lookup_transfers
1360
+ );
1361
+ }
1362
+
1363
+ test "create/lookup 2-phase transfers (amount=maxInt)" {
1364
+ try check(
1365
+ \\ account A1 0 0 0 0 _ _ _ _ L1 C1 _ _ _ _ _ _ _ _ ok
1366
+ \\ account A2 0 0 0 0 _ _ _ _ L1 C1 _ _ _ _ _ _ _ _ ok
1367
+ \\ commit create_accounts
1368
+
1369
+ // Posting maxInt(u128) is a pun – it is interpreted as "send full pending amount", which in
1370
+ // this case is exactly maxInt(u127).
1371
+ \\ transfer T1 A1 A2 -0 _ _ _ _ _ L1 C1 _ PEN _ _ _ _ _ _ _ _ _ ok
1372
+ \\ transfer T2 A1 A2 -0 T1 _ _ _ _ L1 C1 _ _ POS _ _ _ _ _ _ _ _ ok
1373
+ \\ transfer T2 A1 A2 -0 T1 _ _ _ _ L1 C1 _ _ POS _ _ _ _ _ _ _ _ exists
1374
+ \\ commit create_transfers
1375
+
1376
+ // Check balances after resolving.
1377
+ \\ lookup_account A1 0 -0 0 0 _
1378
+ \\ lookup_account A2 0 0 0 -0 _
1379
+ \\ commit lookup_accounts
1380
+ \\
1381
+ \\ lookup_transfer T1 amount -0
1382
+ \\ lookup_transfer T2 amount -0
1383
+ \\ commit lookup_transfers
1384
+ );
1385
+ }
1386
+
1387
+ test "create/lookup expired transfers" {
1388
+ try check(
1389
+ \\ account A1 0 0 0 0 _ _ _ _ L1 C1 _ _ _ _ _ _ _ _ ok
1390
+ \\ account A2 0 0 0 0 _ _ _ _ L1 C1 _ _ _ _ _ _ _ _ ok
1391
+ \\ commit create_accounts
1392
+
1393
+ // First phase.
1394
+ \\ transfer T1 A1 A2 10 _ _ _ _ 0 L1 C1 _ PEN _ _ _ _ _ _ _ _ _ ok // Timeout zero will never expire.
1395
+ \\ transfer T2 A1 A2 11 _ _ _ _ 1 L1 C1 _ PEN _ _ _ _ _ _ _ _ _ ok
1396
+ \\ transfer T3 A1 A2 12 _ _ _ _ 2 L1 C1 _ PEN _ _ _ _ _ _ _ _ _ ok
1397
+ \\ transfer T4 A1 A2 13 _ _ _ _ 3 L1 C1 _ PEN _ _ _ _ _ _ _ _ _ ok
1398
+ \\ commit create_transfers
1399
+
1400
+ // Check balances before expiration.
1401
+ \\ lookup_account A1 46 0 0 0 _
1402
+ \\ lookup_account A2 0 0 46 0 _
1403
+ \\ commit lookup_accounts
1404
+
1405
+ // Check balances after 1s.
1406
+ \\ tick 1 seconds
1407
+ \\ lookup_account A1 35 0 0 0 _
1408
+ \\ lookup_account A2 0 0 35 0 _
1409
+ \\ commit lookup_accounts
1410
+
1411
+ // Check balances after 1s.
1412
+ \\ tick 1 seconds
1413
+ \\ lookup_account A1 23 0 0 0 _
1414
+ \\ lookup_account A2 0 0 23 0 _
1415
+ \\ commit lookup_accounts
1416
+
1417
+ // Check balances after 1s.
1418
+ \\ tick 1 seconds
1419
+ \\ lookup_account A1 10 0 0 0 _
1420
+ \\ lookup_account A2 0 0 10 0 _
1421
+ \\ commit lookup_accounts
1422
+
1423
+ // Second phase.
1424
+ \\ transfer T101 A1 A2 10 T1 U1 U1 U1 _ L1 C1 _ _ POS _ _ _ _ _ _ _ _ ok
1425
+ \\ transfer T102 A1 A2 11 T2 U1 U1 U1 _ L1 C1 _ _ POS _ _ _ _ _ _ _ _ pending_transfer_expired
1426
+ \\ transfer T103 A1 A2 12 T3 U1 U1 U1 _ L1 C1 _ _ POS _ _ _ _ _ _ _ _ pending_transfer_expired
1427
+ \\ transfer T104 A1 A2 13 T4 U1 U1 U1 _ L1 C1 _ _ POS _ _ _ _ _ _ _ _ pending_transfer_expired
1428
+ \\ commit create_transfers
1429
+
1430
+ // Check final balances.
1431
+ \\ lookup_account A1 0 10 0 0 _
1432
+ \\ lookup_account A2 0 0 0 10 _
1433
+ \\ commit lookup_accounts
1434
+
1435
+ // Check transfers.
1436
+ \\ lookup_transfer T101 exists true
1437
+ \\ lookup_transfer T102 exists false
1438
+ \\ lookup_transfer T103 exists false
1439
+ \\ lookup_transfer T104 exists false
1440
+ \\ commit lookup_transfers
1441
+ );
1442
+ }
1443
+
1444
+ test "create_transfers: empty" {
1445
+ try check(
1446
+ \\ commit create_transfers
1447
+ );
1448
+ }
1449
+
1450
+ test "create_transfers/lookup_transfers: failed transfer does not exist" {
1451
+ try check(
1452
+ \\ account A1 0 0 0 0 _ _ _ _ L1 C1 _ _ _ _ _ _ _ _ ok
1453
+ \\ account A2 0 0 0 0 _ _ _ _ L1 C1 _ _ _ _ _ _ _ _ ok
1454
+ \\ commit create_accounts
1455
+ \\
1456
+ \\ transfer T1 A1 A2 15 _ _ _ _ _ L1 C1 _ _ _ _ _ _ _ _ _ _ _ ok
1457
+ \\ transfer T2 A1 A2 15 _ _ _ _ _ L0 C1 _ _ _ _ _ _ _ _ _ _ _ ledger_must_not_be_zero
1458
+ \\ commit create_transfers
1459
+ \\
1460
+ \\ lookup_account A1 0 15 0 0 _
1461
+ \\ lookup_account A2 0 0 0 15 _
1462
+ \\ commit lookup_accounts
1463
+ \\
1464
+ \\ lookup_transfer T1 exists true
1465
+ \\ lookup_transfer T2 exists false
1466
+ \\ commit lookup_transfers
1467
+ );
1468
+ }
1469
+
1470
+ test "create_transfers: failed linked-chains are undone" {
1471
+ try check(
1472
+ \\ account A1 0 0 0 0 _ _ _ _ L1 C1 _ _ _ _ _ _ _ _ ok
1473
+ \\ account A2 0 0 0 0 _ _ _ _ L1 C1 _ _ _ _ _ _ _ _ ok
1474
+ \\ commit create_accounts
1475
+ \\
1476
+ \\ transfer T1 A1 A2 15 _ _ _ _ _ L1 C1 LNK _ _ _ _ _ _ _ _ _ _ linked_event_failed
1477
+ \\ transfer T2 A1 A2 15 _ _ _ _ _ L0 C1 _ _ _ _ _ _ _ _ _ _ _ ledger_must_not_be_zero
1478
+ \\ commit create_transfers
1479
+ \\
1480
+ \\ transfer T3 A1 A2 15 _ _ _ _ 1 L1 C1 LNK PEN _ _ _ _ _ _ _ _ _ linked_event_failed
1481
+ \\ transfer T4 A1 A2 15 _ _ _ _ _ L0 C1 _ _ _ _ _ _ _ _ _ _ _ ledger_must_not_be_zero
1482
+ \\ commit create_transfers
1483
+ \\
1484
+ \\ lookup_account A1 0 0 0 0 _
1485
+ \\ lookup_account A2 0 0 0 0 _
1486
+ \\ commit lookup_accounts
1487
+ \\
1488
+ \\ lookup_transfer T1 exists false
1489
+ \\ lookup_transfer T2 exists false
1490
+ \\ lookup_transfer T3 exists false
1491
+ \\ lookup_transfer T4 exists false
1492
+ \\ commit lookup_transfers
1493
+ );
1494
+ }
1495
+
1496
+ test "create_transfers: failed linked-chains are undone within a commit" {
1497
+ try check(
1498
+ \\ account A1 0 0 0 0 _ _ _ _ L1 C1 _ D<C _ _ _ _ _ _ ok
1499
+ \\ account A2 0 0 0 0 _ _ _ _ L1 C1 _ _ _ _ _ _ _ _ ok
1500
+ \\ commit create_accounts
1501
+ \\
1502
+ \\ setup A1 0 0 0 20
1503
+ \\
1504
+ \\ transfer T1 A1 A2 15 _ _ _ _ _ L1 C1 LNK _ _ _ _ _ _ _ _ _ _ linked_event_failed
1505
+ \\ transfer T2 A1 A2 5 _ _ _ _ _ L0 C1 _ _ _ _ _ _ _ _ _ _ _ ledger_must_not_be_zero
1506
+ \\ transfer T3 A1 A2 15 _ _ _ _ _ L1 C1 _ _ _ _ _ _ _ _ _ _ _ ok
1507
+ \\ commit create_transfers
1508
+ \\
1509
+ \\ lookup_account A1 0 15 0 20 _
1510
+ \\ lookup_account A2 0 0 0 15 _
1511
+ \\ commit lookup_accounts
1512
+ \\
1513
+ \\ lookup_transfer T1 exists false
1514
+ \\ lookup_transfer T2 exists false
1515
+ \\ lookup_transfer T3 exists true
1516
+ \\ commit lookup_transfers
1517
+ );
1518
+ }
1519
+
1520
+ test "create_transfers: balancing_debit | balancing_credit (*_must_not_exceed_*)" {
1521
+ try check(
1522
+ \\ account A1 0 0 0 0 _ _ _ _ L1 C1 _ D<C _ _ _ _ _ _ ok
1523
+ \\ account A2 0 0 0 0 _ _ _ _ L1 C1 _ _ C<D _ _ _ _ _ ok
1524
+ \\ account A3 0 0 0 0 _ _ _ _ L1 C1 _ _ _ _ _ _ _ _ ok
1525
+ \\ commit create_accounts
1526
+ \\
1527
+ \\ setup A1 1 0 0 10
1528
+ \\ setup A2 0 10 2 0
1529
+ \\
1530
+ \\ transfer T1 A1 A3 3 _ _ _ _ _ L2 C1 _ _ _ _ BDR _ _ _ _ _ _ transfer_must_have_the_same_ledger_as_accounts
1531
+ \\ transfer T1 A3 A2 3 _ _ _ _ _ L2 C1 _ _ _ _ _ BCR _ _ _ _ _ transfer_must_have_the_same_ledger_as_accounts
1532
+ \\ transfer T1 A1 A3 3 _ _ _ _ _ L1 C1 _ _ _ _ BDR _ _ _ _ _ _ ok
1533
+ \\ transfer T2 A1 A3 13 _ _ _ _ _ L1 C1 _ _ _ _ BDR _ _ _ _ _ _ ok
1534
+ \\ transfer T3 A3 A2 3 _ _ _ _ _ L1 C1 _ _ _ _ _ BCR _ _ _ _ _ ok
1535
+ \\ transfer T4 A3 A2 13 _ _ _ _ _ L1 C1 _ _ _ _ _ BCR _ _ _ _ _ ok
1536
+ \\ transfer T5 A1 A3 1 _ _ _ _ _ L1 C1 _ _ _ _ BDR _ _ _ _ _ _ ok // Amount reduced to 0.
1537
+ \\ transfer T6 A1 A3 1 _ _ _ _ _ L1 C1 _ _ _ _ BDR BCR _ _ _ _ _ ok // ↑
1538
+ \\ transfer T7 A3 A2 1 _ _ _ _ _ L1 C1 _ _ _ _ _ BCR _ _ _ _ _ ok // ↑
1539
+ \\ transfer T8 A1 A2 1 _ _ _ _ _ L1 C1 _ _ _ _ BDR BCR _ _ _ _ _ ok // ↑
1540
+ \\ transfer T1 A1 A3 2 _ _ _ _ _ L1 C1 _ _ _ _ BDR _ _ _ _ _ _ exists_with_different_amount // Less than the transfer amount.
1541
+ \\ transfer T1 A1 A3 0 _ _ _ _ _ L1 C1 _ _ _ _ BDR _ _ _ _ _ _ exists_with_different_amount // ↑
1542
+ \\ transfer T1 A1 A3 3 _ _ _ _ _ L1 C1 _ _ _ _ BDR _ _ _ _ _ _ exists // Greater-than-or-equal-to the transfer amount.
1543
+ \\ transfer T1 A1 A3 4 _ _ _ _ _ L1 C1 _ _ _ _ BDR _ _ _ _ _ _ exists // ↑
1544
+ \\ transfer T2 A1 A3 6 _ _ _ _ _ L1 C1 _ _ _ _ BDR _ _ _ _ _ _ exists // Equal to the transfer amount.
1545
+ \\ transfer T2 A1 A3 0 _ _ _ _ _ L1 C1 _ _ _ _ BDR _ _ _ _ _ _ exists_with_different_amount // Less than the transfer amount.
1546
+ \\ transfer T3 A3 A2 2 _ _ _ _ _ L1 C1 _ _ _ _ _ BCR _ _ _ _ _ exists_with_different_amount // Less than the transfer amount.
1547
+ \\ transfer T3 A3 A2 0 _ _ _ _ _ L1 C1 _ _ _ _ _ BCR _ _ _ _ _ exists_with_different_amount // ↑
1548
+ \\ transfer T3 A3 A2 3 _ _ _ _ _ L1 C1 _ _ _ _ _ BCR _ _ _ _ _ exists
1549
+ \\ transfer T3 A3 A2 4 _ _ _ _ _ L1 C1 _ _ _ _ _ BCR _ _ _ _ _ exists
1550
+ \\ transfer T4 A3 A2 5 _ _ _ _ _ L1 C1 _ _ _ _ _ BCR _ _ _ _ _ exists // Greater-than-or-equal-to the transfer amount.
1551
+ \\ transfer T4 A3 A2 6 _ _ _ _ _ L1 C1 _ _ _ _ _ BCR _ _ _ _ _ exists // ↑
1552
+ \\ transfer T4 A3 A2 0 _ _ _ _ _ L1 C1 _ _ _ _ _ BCR _ _ _ _ _ exists_with_different_amount // Less than the transfer amount.
1553
+ \\ commit create_transfers
1554
+ \\
1555
+ \\ lookup_account A1 1 9 0 10 _
1556
+ \\ lookup_account A2 0 10 2 8 _
1557
+ \\ lookup_account A3 0 8 0 9 _
1558
+ \\ commit lookup_accounts
1559
+ \\
1560
+ \\ lookup_transfer T1 amount 3
1561
+ \\ lookup_transfer T2 amount 6
1562
+ \\ lookup_transfer T3 amount 3
1563
+ \\ lookup_transfer T4 amount 5
1564
+ \\ lookup_transfer T5 amount 0
1565
+ \\ lookup_transfer T6 amount 0
1566
+ \\ lookup_transfer T7 amount 0
1567
+ \\ lookup_transfer T8 amount 0
1568
+ \\ commit lookup_transfers
1569
+ );
1570
+ }
1571
+
1572
+ test "create_transfers: balancing_debit | balancing_credit (*_must_not_exceed_*, exceeds_*)" {
1573
+ try check(
1574
+ \\ account A1 0 0 0 0 _ _ _ _ L1 C1 _ D<C _ _ _ _ _ _ ok
1575
+ \\ account A2 0 0 0 0 _ _ _ _ L1 C1 _ _ _ _ _ _ _ _ ok
1576
+ \\ account A3 0 0 0 0 _ _ _ _ L1 C1 _ _ C<D _ _ _ _ _ ok
1577
+ \\ account A4 0 0 0 0 _ _ _ _ L1 C1 _ _ _ _ _ _ _ _ ok
1578
+ \\ commit create_accounts
1579
+ \\
1580
+ \\ setup A1 0 0 0 4
1581
+ \\ setup A2 0 5 0 0
1582
+ \\ setup A3 0 4 0 0
1583
+ \\ setup A4 0 0 0 5
1584
+ \\
1585
+ \\ transfer T1 A1 A2 10 _ _ _ _ _ L1 C1 _ _ _ _ _ BCR _ _ _ _ _ exceeds_credits
1586
+ \\ transfer T2 A1 A2 10 _ _ _ _ _ L1 C1 _ _ _ _ BDR _ _ _ _ _ _ ok
1587
+ \\ transfer T3 A4 A3 10 _ _ _ _ _ L1 C1 _ _ _ _ BDR _ _ _ _ _ _ exceeds_debits
1588
+ \\ transfer T4 A4 A3 10 _ _ _ _ _ L1 C1 _ _ _ _ _ BCR _ _ _ _ _ ok
1589
+ \\ commit create_transfers
1590
+ \\
1591
+ \\ lookup_account A1 0 4 0 4 _
1592
+ \\ lookup_account A2 0 5 0 4 _
1593
+ \\ lookup_account A3 0 4 0 4 _
1594
+ \\ lookup_account A4 0 4 0 5 _
1595
+ \\ commit lookup_accounts
1596
+ \\
1597
+ \\ lookup_transfer T1 exists false
1598
+ \\ lookup_transfer T2 amount 4
1599
+ \\ lookup_transfer T3 exists false
1600
+ \\ lookup_transfer T4 amount 4
1601
+ \\ commit lookup_transfers
1602
+ );
1603
+ }
1604
+
1605
+ test "create_transfers: balancing_debit | balancing_credit (¬*_must_not_exceed_*)" {
1606
+ try check(
1607
+ \\ account A1 0 0 0 0 _ _ _ _ L1 C1 _ _ _ _ _ _ _ _ ok
1608
+ \\ account A2 0 0 0 0 _ _ _ _ L1 C1 _ _ _ _ _ _ _ _ ok
1609
+ \\ account A3 0 0 0 0 _ _ _ _ L1 C1 _ _ _ _ _ _ _ _ ok
1610
+ \\ commit create_accounts
1611
+ \\
1612
+ \\ setup A1 1 0 0 10
1613
+ \\ setup A2 0 10 2 0
1614
+ \\
1615
+ \\ transfer T1 A3 A1 99 _ _ _ _ _ L1 C1 _ _ _ _ BDR BCR _ _ _ _ _ ok // Amount reduced to 0.
1616
+ \\ transfer T2 A3 A1 99 _ _ _ _ _ L1 C1 _ _ _ _ BDR _ _ _ _ _ _ ok // ↑
1617
+ \\ transfer T3 A2 A3 99 _ _ _ _ _ L1 C1 _ _ _ _ _ BCR _ _ _ _ _ ok // ↑
1618
+ \\ transfer T4 A1 A3 99 _ _ _ _ _ L1 C1 _ _ _ _ BDR _ _ _ _ _ _ ok
1619
+ \\ transfer T5 A1 A3 99 _ _ _ _ _ L1 C1 _ _ _ _ BDR _ _ _ _ _ _ ok // Amount reduced to 0.
1620
+ \\ transfer T6 A3 A2 99 _ _ _ _ _ L1 C1 _ _ _ _ _ BCR _ _ _ _ _ ok
1621
+ \\ transfer T7 A3 A2 99 _ _ _ _ _ L1 C1 _ _ _ _ _ BCR _ _ _ _ _ ok // Amount reduced to 0.
1622
+ \\ commit create_transfers
1623
+ \\
1624
+ \\ lookup_account A1 1 9 0 10 _
1625
+ \\ lookup_account A2 0 10 2 8 _
1626
+ \\ lookup_account A3 0 8 0 9 _
1627
+ \\ commit lookup_accounts
1628
+ \\
1629
+ \\ lookup_transfer T1 amount 0
1630
+ \\ lookup_transfer T2 amount 0
1631
+ \\ lookup_transfer T3 amount 0
1632
+ \\ lookup_transfer T4 amount 9
1633
+ \\ lookup_transfer T5 amount 0
1634
+ \\ lookup_transfer T6 amount 8
1635
+ \\ lookup_transfer T7 amount 0
1636
+ \\ commit lookup_transfers
1637
+ );
1638
+ }
1639
+
1640
+ test "create_transfers: balancing_debit | balancing_credit (amount=0)" {
1641
+ try check(
1642
+ \\ account A1 0 0 0 0 _ _ _ _ L1 C1 _ D<C _ _ _ _ _ _ ok
1643
+ \\ account A2 0 0 0 0 _ _ _ _ L1 C1 _ _ C<D _ _ _ _ _ ok
1644
+ \\ account A3 0 0 0 0 _ _ _ _ L1 C1 _ _ C<D _ _ _ _ _ ok
1645
+ \\ account A4 0 0 0 0 _ _ _ _ L1 C1 _ _ _ _ _ _ _ _ ok
1646
+ \\ commit create_accounts
1647
+ \\
1648
+ \\ setup A1 1 0 0 10
1649
+ \\ setup A2 0 10 2 0
1650
+ \\ setup A3 0 10 2 0
1651
+ \\
1652
+ // Test amount=0 transfers:
1653
+ \\ transfer T1 A1 A4 0 _ _ _ _ _ L1 C1 _ _ _ _ BDR _ _ _ _ _ _ ok
1654
+ \\ transfer T2 A4 A2 0 _ _ _ _ _ L1 C1 _ _ _ _ _ BCR _ _ _ _ _ ok
1655
+ \\ transfer T3 A1 A4 0 _ _ _ _ _ L1 C1 _ _ _ _ BDR BCR _ _ _ _ _ ok
1656
+ \\ transfer T4 A4 A3 0 _ _ _ _ _ L1 C1 _ PEN _ _ _ BCR _ _ _ _ _ ok
1657
+ // The respective balancing flag reduces nonzero amounts to zero even though A4 lacks
1658
+ // must_not_exceed (since its net balance is zero):
1659
+ \\ transfer T5 A4 A1 1 _ _ _ _ _ L1 C1 _ _ _ _ BDR _ _ _ _ _ _ ok
1660
+ \\ transfer T6 A2 A4 1 _ _ _ _ _ L1 C1 _ _ _ _ _ BCR _ _ _ _ _ ok
1661
+ \\ commit create_transfers
1662
+ \\
1663
+ // None of the accounts' balances have changed -- none of the transfers moved any money.
1664
+ \\ lookup_account A1 1 0 0 10 _
1665
+ \\ lookup_account A2 0 10 2 0 _
1666
+ \\ lookup_account A3 0 10 2 0 _
1667
+ \\ lookup_account A4 0 0 0 0 _
1668
+ \\ commit lookup_accounts
1669
+ \\
1670
+ \\ lookup_transfer T1 amount 0
1671
+ \\ lookup_transfer T2 amount 0
1672
+ \\ lookup_transfer T3 amount 0
1673
+ \\ lookup_transfer T4 amount 0
1674
+ \\ lookup_transfer T5 amount 0
1675
+ \\ lookup_transfer T6 amount 0
1676
+ \\ commit lookup_transfers
1677
+ );
1678
+ }
1679
+
1680
+ test "create_transfers: balancing_debit | balancing_credit (amount=maxInt, balance≈maxInt)" {
1681
+ try check(
1682
+ \\ account A1 0 0 0 0 _ _ _ _ L1 C1 _ D<C _ _ _ _ _ _ ok
1683
+ \\ account A2 0 0 0 0 _ _ _ _ L1 C1 _ D<C _ _ _ _ _ _ ok
1684
+ \\ account A3 0 0 0 0 _ _ _ _ L1 C1 _ _ C<D _ _ _ _ _ ok
1685
+ \\ account A4 0 0 0 0 _ _ _ _ L1 C1 _ _ C<D _ _ _ _ _ ok
1686
+ \\ commit create_accounts
1687
+ \\
1688
+ \\ setup A1 0 0 0 -1
1689
+ \\ setup A4 0 -1 0 0
1690
+ \\
1691
+ \\ transfer T1 A1 A2 -0 _ _ _ _ _ L1 C1 _ _ _ _ BDR _ _ _ _ _ _ ok
1692
+ \\ transfer T2 A3 A4 -0 _ _ _ _ _ L1 C1 _ _ _ _ _ BCR _ _ _ _ _ ok
1693
+ \\ commit create_transfers
1694
+ \\
1695
+ \\ lookup_account A1 0 -1 0 -1 _
1696
+ \\ lookup_account A2 0 0 0 -1 _
1697
+ \\ lookup_account A3 0 -1 0 0 _
1698
+ \\ lookup_account A4 0 -1 0 -1 _
1699
+ \\ commit lookup_accounts
1700
+ \\
1701
+ \\ lookup_transfer T1 amount -1
1702
+ \\ lookup_transfer T2 amount -1
1703
+ \\ commit lookup_transfers
1704
+ );
1705
+ }
1706
+
1707
+ test "create_transfers: balancing_debit & balancing_credit" {
1708
+ try check(
1709
+ \\ account A1 0 0 0 0 _ _ _ _ L1 C1 _ D<C _ _ _ _ _ _ ok
1710
+ \\ account A2 0 0 0 0 _ _ _ _ L1 C1 _ _ C<D _ _ _ _ _ ok
1711
+ \\ account A3 0 0 0 0 _ _ _ _ L1 C1 _ _ _ _ _ _ _ _ ok
1712
+ \\ commit create_accounts
1713
+ \\
1714
+ \\ setup A1 0 0 0 20
1715
+ \\ setup A2 0 10 0 0
1716
+ \\ setup A3 0 99 0 0
1717
+ \\
1718
+ \\ transfer T1 A1 A2 1 _ _ _ _ _ L1 C1 _ _ _ _ BDR BCR _ _ _ _ _ ok
1719
+ \\ transfer T2 A1 A2 12 _ _ _ _ _ L1 C1 _ _ _ _ BDR BCR _ _ _ _ _ ok
1720
+ \\ transfer T3 A1 A2 1 _ _ _ _ _ L1 C1 _ _ _ _ BDR BCR _ _ _ _ _ ok // Amount reduced to 0.
1721
+ \\ transfer T4 A1 A3 12 _ _ _ _ _ L1 C1 _ _ _ _ BDR BCR _ _ _ _ _ ok
1722
+ \\ transfer T5 A1 A3 1 _ _ _ _ _ L1 C1 _ _ _ _ BDR BCR _ _ _ _ _ ok // Amount reduced to 0.
1723
+ \\ commit create_transfers
1724
+ \\
1725
+ \\ lookup_account A1 0 20 0 20 _
1726
+ \\ lookup_account A2 0 10 0 10 _
1727
+ \\ lookup_account A3 0 99 0 10 _
1728
+ \\ commit lookup_accounts
1729
+ \\
1730
+ \\ lookup_transfer T1 amount 1
1731
+ \\ lookup_transfer T2 amount 9
1732
+ \\ lookup_transfer T3 amount 0
1733
+ \\ lookup_transfer T4 amount 10
1734
+ \\ lookup_transfer T5 amount 0
1735
+ \\ commit lookup_transfers
1736
+ );
1737
+ }
1738
+
1739
+ test "create_transfers: balancing_debit/balancing_credit + pending" {
1740
+ try check(
1741
+ \\ account A1 0 0 0 0 _ _ _ _ L1 C1 _ D<C _ _ _ _ _ _ ok
1742
+ \\ account A2 0 0 0 0 _ _ _ _ L1 C1 _ _ C<D _ _ _ _ _ ok
1743
+ \\ commit create_accounts
1744
+ \\
1745
+ \\ setup A1 0 0 0 10
1746
+ \\ setup A2 0 10 0 0
1747
+ \\
1748
+ \\ transfer T1 A1 A2 3 _ _ _ _ _ L1 C1 _ PEN _ _ BDR _ _ _ _ _ _ ok
1749
+ \\ transfer T2 A1 A2 13 _ _ _ _ _ L1 C1 _ PEN _ _ BDR _ _ _ _ _ _ ok
1750
+ \\ transfer T3 A1 A2 1 _ _ _ _ _ L1 C1 _ PEN _ _ BDR _ _ _ _ _ _ ok // Amount reduced to 0.
1751
+ \\ commit create_transfers
1752
+ \\
1753
+ \\ lookup_account A1 10 0 0 10 _
1754
+ \\ lookup_account A2 0 10 10 0 _
1755
+ \\ commit lookup_accounts
1756
+ \\
1757
+ \\ transfer T4 A1 A2 3 T1 _ _ _ _ L1 C1 _ _ POS _ _ _ _ _ _ _ _ ok
1758
+ \\ transfer T5 A1 A2 5 T2 _ _ _ _ L1 C1 _ _ POS _ _ _ _ _ _ _ _ ok
1759
+ \\ commit create_transfers
1760
+ \\
1761
+ \\ lookup_transfer T1 amount 3
1762
+ \\ lookup_transfer T2 amount 7
1763
+ \\ lookup_transfer T3 amount 0
1764
+ \\ lookup_transfer T4 amount 3
1765
+ \\ lookup_transfer T5 amount 5
1766
+ \\ commit lookup_transfers
1767
+ );
1768
+ }
1769
+
1770
+ test "create_transfers: multiple debits, single credit, balancing debits" {
1771
+ // See `recipes/multi-debit-credit-transfers.md`.
1772
+ // Source accounts: A1, A2, A3
1773
+ // Control account: A4
1774
+ // Limit account: A5
1775
+ // Destination account: A6
1776
+
1777
+ // Sufficient funds:
1778
+ try check(
1779
+ \\ account A1 0 0 0 0 _ _ _ _ L1 C1 _ D<C _ _ _ _ _ _ ok
1780
+ \\ account A2 0 0 0 0 _ _ _ _ L1 C1 _ D<C _ _ _ _ _ _ ok
1781
+ \\ account A3 0 0 0 0 _ _ _ _ L1 C1 _ D<C _ _ _ _ _ _ ok
1782
+ \\ account A4 0 0 0 0 _ _ _ _ L1 C1 _ _ _ _ _ _ _ _ ok
1783
+ \\ account A5 0 0 0 0 _ _ _ _ L1 C1 _ D<C _ _ _ _ _ _ ok
1784
+ \\ account A6 0 0 0 0 _ _ _ _ L1 C1 _ _ _ _ _ _ _ _ ok
1785
+ \\ commit create_accounts
1786
+ \\
1787
+ \\ setup A1 0 0 0 40
1788
+ \\ setup A2 0 0 0 40
1789
+ \\ setup A3 0 0 0 21
1790
+ \\
1791
+ \\ transfer T1 A4 A5 100 _ _ _ _ 0 L1 C1 LNK _ _ _ _ _ _ _ _ _ _ ok
1792
+ \\ transfer T2 A1 A4 100 _ _ _ _ 0 L1 C1 LNK _ _ _ BDR BCR _ _ _ _ _ ok
1793
+ \\ transfer T3 A2 A4 100 _ _ _ _ 0 L1 C1 LNK _ _ _ BDR BCR _ _ _ _ _ ok
1794
+ \\ transfer T4 A3 A4 100 _ _ _ _ 0 L1 C1 LNK _ _ _ BDR BCR _ _ _ _ _ ok
1795
+ \\ transfer T5 A4 A6 100 _ _ _ _ 0 L1 C1 LNK _ _ _ _ _ _ _ _ _ _ ok
1796
+ \\ transfer T6 A5 A4 -0 _ _ _ _ 0 L1 C1 _ _ _ _ _ BCR _ _ _ _ _ ok
1797
+ \\ commit create_transfers
1798
+ \\
1799
+ \\ lookup_account A1 0 40 0 40 _
1800
+ \\ lookup_account A2 0 40 0 40 _
1801
+ \\ lookup_account A3 0 20 0 21 _
1802
+ \\ lookup_account A4 0 200 0 200 _
1803
+ \\ lookup_account A5 0 100 0 100 _
1804
+ \\ lookup_account A6 0 0 0 100 _
1805
+ \\ commit lookup_accounts
1806
+ );
1807
+
1808
+ // Insufficient funds.
1809
+ try check(
1810
+ \\ account A1 0 0 0 0 _ _ _ _ L1 C1 _ D<C _ _ _ _ _ _ ok
1811
+ \\ account A2 0 0 0 0 _ _ _ _ L1 C1 _ D<C _ _ _ _ _ _ ok
1812
+ \\ account A3 0 0 0 0 _ _ _ _ L1 C1 _ D<C _ _ _ _ _ _ ok
1813
+ \\ account A4 0 0 0 0 _ _ _ _ L1 C1 _ _ _ _ _ _ _ _ ok
1814
+ \\ account A5 0 0 0 0 _ _ _ _ L1 C1 _ D<C _ _ _ _ _ _ ok
1815
+ \\ account A6 0 0 0 0 _ _ _ _ L1 C1 _ _ _ _ _ _ _ _ ok
1816
+ \\ commit create_accounts
1817
+ \\
1818
+ \\ setup A1 0 0 0 40
1819
+ \\ setup A2 0 0 0 40
1820
+ \\ setup A3 0 0 0 19
1821
+ \\
1822
+ \\ transfer T1 A4 A5 100 _ _ _ _ 0 L1 C1 LNK _ _ _ _ _ _ _ _ _ _ linked_event_failed
1823
+ \\ transfer T2 A1 A4 100 _ _ _ _ 0 L1 C1 LNK _ _ _ BDR BCR _ _ _ _ _ linked_event_failed
1824
+ \\ transfer T3 A2 A4 100 _ _ _ _ 0 L1 C1 LNK _ _ _ BDR BCR _ _ _ _ _ linked_event_failed
1825
+ \\ transfer T4 A3 A4 100 _ _ _ _ 0 L1 C1 LNK _ _ _ BDR BCR _ _ _ _ _ linked_event_failed
1826
+ \\ transfer T5 A4 A6 100 _ _ _ _ 0 L1 C1 LNK _ _ _ _ _ _ _ _ _ _ linked_event_failed
1827
+ \\ transfer T6 A5 A4 -0 _ _ _ _ 0 L1 C1 _ _ _ _ _ BCR _ _ _ _ _ exceeds_credits
1828
+ \\ commit create_transfers
1829
+ );
1830
+ }
1831
+
1832
+ test "create_transfers: per-transfer balance invariant" {
1833
+ // Temporarily enforce `credits_must_not_exceed_debits` on account `A2`.
1834
+ try check(
1835
+ \\ account A1 0 0 0 0 _ _ _ _ L1 C1 _ _ _ _ _ _ _ _ ok
1836
+ \\ account A2 0 0 0 0 _ _ _ _ L1 C1 _ _ _ _ _ _ _ _ ok
1837
+ \\ account A3 0 0 0 0 _ _ _ _ L1 C1 _ _ C<D _ _ _ _ _ ok
1838
+ \\ commit create_accounts
1839
+ \\
1840
+ \\ setup A2 0 40 0 0
1841
+ \\
1842
+ \\ transfer T1 A1 A2 41 _ _ _ _ 0 L1 C1 LNK _ _ _ _ _ _ _ _ _ _ linked_event_failed
1843
+ \\ transfer T2 A2 A3 1 _ _ _ _ 0 L1 C1 LNK PEN _ _ BDR _ _ _ _ _ _ exceeds_debits
1844
+ \\ transfer T3 A2 A3 0 T2 _ _ _ 0 L1 C1 _ _ _ VOI _ _ _ _ _ _ _ linked_event_failed
1845
+ \\ commit create_transfers
1846
+ \\
1847
+ // Ids failed in a linked chain can be reused, but
1848
+ // `exceeds_debits` is a transient error, T2 cannot be reused:
1849
+ \\ transfer T1 A1 A2 0 _ _ _ _ 0 L1 C1 _ _ _ _ _ _ _ _ _ _ _ ok
1850
+ \\ transfer T2 A2 A3 0 _ _ _ _ 0 L1 C1 _ _ _ _ _ _ _ _ _ _ _ id_already_failed
1851
+ \\ transfer T3 A2 A3 0 _ _ _ _ 0 L1 C1 _ _ _ _ _ _ _ _ _ _ _ ok
1852
+ \\ commit create_transfers
1853
+ \\
1854
+ \\ transfer T4 A1 A2 40 _ _ _ _ 0 L1 C1 LNK _ _ _ _ _ _ _ _ _ _ ok
1855
+ \\ transfer T5 A2 A3 1 _ _ _ _ 0 L1 C1 LNK PEN _ _ BDR _ _ _ _ _ _ ok
1856
+ \\ transfer T6 A2 A3 0 T5 _ _ _ 0 L1 C1 _ _ _ VOI _ _ _ _ _ _ _ ok
1857
+ \\ commit create_transfers
1858
+ \\
1859
+ \\ lookup_account A1 0 40 0 0 _
1860
+ \\ lookup_account A2 0 40 0 40 _
1861
+ \\ lookup_account A3 0 0 0 0 _
1862
+ \\ commit lookup_accounts
1863
+ );
1864
+
1865
+ // Temporarily enforce `debits_must_not_exceed_credits` on account `A1`.
1866
+ try check(
1867
+ \\ account A1 0 0 0 0 _ _ _ _ L1 C1 _ _ _ _ _ _ _ _ ok
1868
+ \\ account A2 0 0 0 0 _ _ _ _ L1 C1 _ _ _ _ _ _ _ _ ok
1869
+ \\ account A3 0 0 0 0 _ _ _ _ L1 C1 _ D<C _ _ _ _ _ _ ok
1870
+ \\ commit create_accounts
1871
+ \\
1872
+ \\ setup A1 0 0 0 40
1873
+ \\
1874
+ \\ transfer T1 A1 A2 41 _ _ _ _ 0 L1 C1 LNK _ _ _ _ _ _ _ _ _ _ linked_event_failed
1875
+ \\ transfer T2 A3 A1 1 _ _ _ _ 0 L1 C1 LNK PEN _ _ _ BCR _ _ _ _ _ exceeds_credits
1876
+ \\ transfer T3 A3 A1 0 T2 _ _ _ 0 L1 C1 _ _ _ VOI _ _ _ _ _ _ _ linked_event_failed
1877
+ \\ commit create_transfers
1878
+ \\
1879
+ // Ids failed in a linked chain can be reused, but
1880
+ // `exceeds_credits` is a transient error, T2 cannot be reused:
1881
+ \\ transfer T1 A1 A2 0 _ _ _ _ 0 L1 C1 _ _ _ _ _ _ _ _ _ _ _ ok
1882
+ \\ transfer T2 A3 A1 0 _ _ _ _ 0 L1 C1 _ _ _ _ _ _ _ _ _ _ _ id_already_failed
1883
+ \\ transfer T3 A3 A1 0 _ _ _ _ 0 L1 C1 _ _ _ _ _ _ _ _ _ _ _ ok
1884
+ \\ commit create_transfers
1885
+ \\
1886
+ \\ transfer T4 A1 A2 40 _ _ _ _ 0 L1 C1 LNK _ _ _ _ _ _ _ _ _ _ ok
1887
+ \\ transfer T5 A3 A1 1 _ _ _ _ 0 L1 C1 LNK PEN _ _ _ BCR _ _ _ _ _ ok
1888
+ \\ transfer T6 A3 A1 0 T5 _ _ _ 0 L1 C1 _ _ _ VOI _ _ _ _ _ _ _ ok
1889
+ \\ commit create_transfers
1890
+ \\
1891
+ \\ lookup_account A1 0 40 0 40 _
1892
+ \\ lookup_account A2 0 0 0 40 _
1893
+ \\ lookup_account A3 0 0 0 0 _
1894
+ \\ commit lookup_accounts
1895
+ );
1896
+ }
1897
+
1898
+ test "imported events: imported batch" {
1899
+ try check(
1900
+ \\ tick 10 nanoseconds
1901
+ // The first event determines if the batch is either imported or not.
1902
+ \\ account A1 0 0 0 0 _ _ _ _ L1 C1 _ _ _ _ IMP _ _ 1 ok
1903
+ \\ account A2 0 0 0 0 _ _ _ _ L1 C1 _ _ _ _ _ _ _ 0 imported_event_expected
1904
+ \\ account A2 0 0 0 0 _ _ _ _ L1 C1 _ _ _ _ IMP _ _ 2 ok
1905
+ \\ commit create_accounts
1906
+ \\ account A3 0 0 0 0 _ _ _ _ L1 C1 _ _ _ _ _ _ _ 0 ok
1907
+ \\ account A4 0 0 0 0 _ _ _ _ L1 C1 _ _ _ _ IMP _ _ 3 imported_event_not_expected
1908
+ \\ commit create_accounts
1909
+ \\
1910
+ \\ transfer T1 A1 A2 3 _ _ _ _ _ L1 C2 _ _ _ _ _ _ IMP _ _ _ 10 ok
1911
+ \\ transfer T2 A1 A2 3 _ _ _ _ _ L1 C2 _ _ _ _ _ _ _ _ _ _ 0 imported_event_expected
1912
+ \\ commit create_transfers
1913
+ \\ transfer T3 A1 A2 3 _ _ _ _ _ L1 C2 _ _ _ _ _ _ _ _ _ _ 0 ok
1914
+ \\ transfer T4 A1 A2 3 _ _ _ _ _ L1 C2 _ _ _ _ _ _ IMP _ _ _ 0 imported_event_not_expected
1915
+ \\ commit create_transfers
1916
+ );
1917
+ }
1918
+
1919
+ test "imported events: timestamp" {
1920
+ try check(
1921
+ \\ tick 10 nanoseconds
1922
+ \\
1923
+ \\ account A1 0 0 0 0 _ _ _ _ L1 C1 _ _ _ _ IMP _ _ 0 imported_event_timestamp_out_of_range
1924
+ \\ account A1 0 0 0 0 _ _ _ _ L1 C1 _ _ _ _ IMP _ _ -1 imported_event_timestamp_out_of_range
1925
+ \\ account A1 0 0 0 0 _ _ _ _ L1 C1 _ _ _ _ IMP _ _ 99 imported_event_timestamp_must_not_advance
1926
+ \\ account A1 0 0 0 0 _ _ _ _ L1 C1 _ _ _ _ IMP _ _ 2 ok
1927
+ \\ account A2 0 0 0 0 _ _ _ _ L1 C1 _ _ _ _ IMP _ _ 1 imported_event_timestamp_must_not_regress
1928
+ \\ account A2 0 0 0 0 _ _ _ _ L1 C1 _ _ _ _ IMP _ _ 3 ok
1929
+ \\ account A3 0 0 0 0 _ _ _ _ L1 C1 _ _ _ _ IMP _ _ 9 ok
1930
+ \\ commit create_accounts
1931
+ \\
1932
+ \\ account A2 0 0 0 0 _ _ _ _ L1 C1 _ _ _ _ IMP _ _ 99 imported_event_timestamp_must_not_advance
1933
+ \\ account A2 0 0 0 0 _ _ _ _ L1 C1 _ _ _ _ IMP _ _ 1 exists
1934
+ \\ account A2 0 0 0 0 _ _ _ _ L1 C1 _ _ _ _ IMP _ _ 3 exists
1935
+ \\ account A2 0 0 0 0 _ _ _ _ L1 C1 _ _ _ _ IMP _ _ 4 exists
1936
+ \\ account A3 0 0 0 0 _ _ _ _ L1 C1 _ _ _ _ IMP _ _ 9 exists
1937
+ \\ commit create_accounts
1938
+ \\
1939
+ \\ transfer T1 A1 A2 3 _ _ _ _ _ L1 C2 _ _ _ _ _ _ IMP _ _ _ 0 imported_event_timestamp_out_of_range
1940
+ \\ transfer T1 A1 A2 3 _ _ _ _ _ L1 C2 _ _ _ _ _ _ IMP _ _ _ -1 imported_event_timestamp_out_of_range
1941
+ \\ transfer T1 A1 A2 3 _ _ _ _ _ L1 C2 _ _ _ _ _ _ IMP _ _ _ 99 imported_event_timestamp_must_not_advance
1942
+ \\ transfer T1 A1 A2 3 _ _ _ _ _ L1 C2 _ _ _ _ _ _ IMP _ _ _ 2 imported_event_timestamp_must_not_regress // The same timestamp as the dr account.
1943
+ \\ transfer T1 A1 A2 3 _ _ _ _ _ L1 C2 _ _ _ _ _ _ IMP _ _ _ 3 imported_event_timestamp_must_not_regress // The same timestamp as the cr account.
1944
+ \\ transfer T1 A3 A2 3 _ _ _ _ _ L1 C2 _ _ _ _ _ _ IMP _ _ _ 4 imported_event_timestamp_must_postdate_debit_account
1945
+ \\ transfer T1 A1 A3 3 _ _ _ _ _ L1 C2 _ _ _ _ _ _ IMP _ _ _ 4 imported_event_timestamp_must_postdate_credit_account
1946
+ \\ transfer T1 A1 A2 3 _ _ _ _ _ L1 C2 _ _ _ _ _ _ IMP _ _ _ 4 ok
1947
+ \\ transfer T2 A1 A2 3 _ _ _ _ _ L1 C2 _ _ _ _ _ _ IMP _ _ _ 3 imported_event_timestamp_must_not_regress
1948
+ \\ transfer T2 A1 A2 3 _ _ _ _ _ L1 C2 _ _ _ _ _ _ IMP _ _ _ 5 ok
1949
+ \\ commit create_transfers
1950
+ \\
1951
+ \\ transfer T2 A1 A2 3 _ _ _ _ _ L1 C2 _ _ _ _ _ _ IMP _ _ _ 99 imported_event_timestamp_must_not_advance
1952
+ \\ transfer T2 A1 A2 3 _ _ _ _ _ L1 C2 _ _ _ _ _ _ IMP _ _ _ 4 exists // T2 `exists` regardless different timestamps.
1953
+ \\ transfer T2 A1 A2 3 _ _ _ _ _ L1 C2 _ _ _ _ _ _ IMP _ _ _ 5 exists
1954
+ \\ transfer T2 A1 A2 3 _ _ _ _ _ L1 C2 _ _ _ _ _ _ IMP _ _ _ 6 exists
1955
+ \\ commit create_transfers
1956
+ \\
1957
+ \\ transfer T3 A1 A2 3 _ _ _ _ _ L1 C2 _ _ _ _ _ _ IMP _ _ _ 10 ok
1958
+ \\ commit create_transfers
1959
+ \\
1960
+ \\ account A4 0 0 0 0 _ _ _ _ L1 C1 _ _ _ _ IMP _ _ 10 imported_event_timestamp_must_not_regress // The same timestamp as a transfer.
1961
+ \\ commit create_accounts
1962
+ );
1963
+ }
1964
+
1965
+ test "imported events: pending transfers" {
1966
+ try check(
1967
+ \\ tick 10 nanoseconds
1968
+ \\
1969
+ \\ account A1 0 0 0 0 _ _ _ _ L1 C1 _ _ _ _ IMP _ _ 1 ok
1970
+ \\ account A2 0 0 0 0 _ _ _ _ L1 C1 _ _ _ _ IMP _ _ 2 ok
1971
+ \\ commit create_accounts
1972
+ \\
1973
+ \\ transfer T1 A1 A2 3 _ _ _ _ _ L1 C2 _ _ _ _ _ _ IMP _ _ _ 3 ok
1974
+ \\ transfer T2 A1 A2 4 _ _ _ _ 1 L1 C2 _ PEN _ _ _ _ IMP _ _ _ 4 imported_event_timeout_must_be_zero
1975
+ \\ transfer T2 A1 A2 4 _ _ _ _ 0 L1 C2 _ PEN _ _ _ _ IMP _ _ _ 4 ok
1976
+ \\ commit create_transfers
1977
+ \\
1978
+ \\ lookup_account A1 4 3 0 0 _
1979
+ \\ lookup_account A2 0 0 4 3 _
1980
+ \\ commit lookup_accounts
1981
+ \\
1982
+ \\ transfer T3 A1 A2 4 T2 _ _ _ _ L1 C2 _ _ POS _ _ _ IMP _ _ _ 5 ok
1983
+ \\ commit create_transfers
1984
+ \\
1985
+ \\ lookup_account A1 0 7 0 0 _
1986
+ \\ lookup_account A2 0 0 0 7 _
1987
+ \\ commit lookup_accounts
1988
+ \\
1989
+ \\ lookup_transfer T1 timestamp 3
1990
+ \\ lookup_transfer T2 timestamp 4
1991
+ \\ lookup_transfer T3 timestamp 5
1992
+ \\ commit lookup_transfers
1993
+ );
1994
+ }
1995
+
1996
+ test "imported events: linked chain" {
1997
+ try check(
1998
+ \\ tick 10 nanoseconds
1999
+ \\
2000
+ \\ account A1 0 0 0 0 _ _ _ _ L1 C1 LNK _ _ _ IMP _ _ 1 linked_event_failed
2001
+ \\ account A2 0 0 0 0 _ _ _ _ L1 C1 LNK _ _ _ IMP _ _ 2 linked_event_failed
2002
+ \\ account A3 0 0 0 0 _ _ _ _ L1 C1 _ _ _ _ IMP _ _ 0 imported_event_timestamp_out_of_range
2003
+ \\ commit create_accounts
2004
+ \\
2005
+ \\ account A1 0 0 0 0 _ _ _ _ L1 C1 LNK _ _ _ IMP _ _ 1 ok
2006
+ \\ account A2 0 0 0 0 _ _ _ _ L1 C1 LNK _ _ _ IMP _ _ 2 ok
2007
+ \\ account A3 0 0 0 0 _ _ _ _ L1 C1 _ _ _ _ IMP _ _ 3 ok
2008
+ \\ commit create_accounts
2009
+ \\
2010
+ \\ transfer T1 A1 A2 3 _ _ _ _ _ L1 C2 LNK _ _ _ _ _ IMP _ _ _ 4 linked_event_failed
2011
+ \\ transfer T2 A1 A2 3 _ _ _ _ _ L1 C2 LNK _ _ _ _ _ IMP _ _ _ 5 linked_event_failed
2012
+ \\ transfer T3 A1 A2 3 _ _ _ _ _ L1 C2 _ _ _ _ _ _ IMP _ _ _ 0 imported_event_timestamp_out_of_range
2013
+ \\ commit create_transfers
2014
+ \\
2015
+ \\ transfer T1 A1 A2 3 _ _ _ _ _ L1 C2 LNK _ _ _ _ _ IMP _ _ _ 4 ok
2016
+ \\ transfer T2 A1 A2 3 _ _ _ _ _ L1 C2 LNK _ _ _ _ _ IMP _ _ _ 5 ok
2017
+ \\ transfer T3 A1 A2 3 _ _ _ _ _ L1 C2 _ _ _ _ _ _ IMP _ _ _ 6 ok
2018
+ \\ commit create_transfers
2019
+ \\
2020
+ \\ lookup_account A1 0 9 0 0 _
2021
+ \\ lookup_account A2 0 0 0 9 _
2022
+ \\ lookup_account A3 0 0 0 0 _
2023
+ \\ commit lookup_accounts
2024
+ \\
2025
+ \\ lookup_transfer T1 timestamp 4
2026
+ \\ lookup_transfer T2 timestamp 5
2027
+ \\ lookup_transfer T3 timestamp 6
2028
+ \\ commit lookup_transfers
2029
+ );
2030
+ }
2031
+
2032
+ test "create_accounts: closed accounts" {
2033
+ try check(
2034
+ // Accounts can be created already closed.
2035
+ \\ account A1 0 0 0 0 _ _ _ _ L1 C1 _ _ _ _ _ CLSD _ _ ok
2036
+ \\ commit create_accounts
2037
+ \\
2038
+ \\ lookup_account A1 0 0 0 0 CLSD
2039
+ \\ commit lookup_accounts
2040
+ );
2041
+ }
2042
+
2043
+ test "create_transfers: closing accounts" {
2044
+ try check(
2045
+ \\ account A1 0 0 0 0 _ _ _ _ L1 C1 _ _ _ _ _ _ _ _ ok
2046
+ \\ account A2 0 0 0 0 _ _ _ _ L1 C1 _ _ _ _ _ _ _ _ ok
2047
+ \\ account A3 0 0 0 0 _ _ _ _ L1 C1 _ _ _ _ _ _ _ _ ok
2048
+ \\ commit create_accounts
2049
+ \\
2050
+ // Closing the debit account.
2051
+ \\ transfer T1 A1 A2 15 _ _ _ _ _ L1 C1 _ _ _ _ _ _ _ _ _ _ _ ok
2052
+ \\ transfer T2 A1 A2 0 _ _ _ _ _ L1 C1 _ _ _ _ _ _ _ CDR _ _ _ closing_transfer_must_be_pending
2053
+ \\ transfer T2 A1 A2 0 _ _ _ _ 0 L1 C1 _ PEN _ _ _ _ _ CDR _ _ _ ok
2054
+ \\ transfer T2 A1 A2 0 _ _ _ _ 0 L1 C1 _ PEN _ _ _ _ _ CDR _ _ _ exists
2055
+ // `debit_account_already_closed` is a transient error, T3 cannot be reused:
2056
+ \\ transfer T3 A1 A2 5 _ _ _ _ _ L1 C1 _ _ _ _ _ _ _ _ _ _ _ debit_account_already_closed
2057
+ \\ transfer T3 A1 A2 5 _ _ _ _ _ L1 C1 _ _ _ _ _ _ _ _ _ _ _ id_already_failed
2058
+ // `credit_account_already_closed` is a transient error, T4 cannot be reused:
2059
+ \\ transfer T4 A2 A1 5 _ _ _ _ _ L1 C1 _ _ _ _ _ _ _ _ _ _ _ credit_account_already_closed
2060
+ \\ transfer T4 A1 A2 5 _ _ _ _ _ L1 C1 _ _ _ _ _ _ _ _ _ _ _ id_already_failed
2061
+ \\ commit create_transfers
2062
+ \\
2063
+ \\ lookup_account A1 0 15 0 0 CLSD
2064
+ \\ lookup_account A2 0 0 0 15 _
2065
+ \\ commit lookup_accounts
2066
+ // `debit_account_already_closed` is a transient error, T5 cannot be reused:
2067
+ \\ transfer T5 A1 A2 0 T2 _ _ _ _ L1 C1 _ _ POS _ _ _ _ _ _ _ _ debit_account_already_closed
2068
+ \\ transfer T5 A1 A2 0 T2 _ _ _ _ L1 C1 _ _ _ VOI _ _ _ _ _ _ _ id_already_failed
2069
+ \\
2070
+ \\ transfer T6 A1 A2 0 T2 _ _ _ _ L1 C1 _ _ _ VOI _ _ _ _ _ _ _ ok // Re-opening the account.
2071
+ \\ transfer T6 A1 A2 0 T2 _ _ _ _ L1 C1 _ _ _ VOI _ _ _ _ _ _ _ exists
2072
+ \\ transfer T7 A1 A2 5 _ _ _ _ _ L1 C1 _ _ _ _ _ _ _ _ _ _ _ ok
2073
+ \\ commit create_transfers
2074
+ \\
2075
+ \\ lookup_account A1 0 20 0 0 _
2076
+ \\ lookup_account A2 0 0 0 20 _
2077
+ \\ commit lookup_accounts
2078
+ \\
2079
+ // Closing the credit account with a timeout.
2080
+ // Pending transfer can be voided, but not posted in a closed account.
2081
+ \\ transfer T8 A1 A2 10 _ _ _ _ 1 L1 C1 _ PEN _ _ _ _ _ _ _ _ _ ok
2082
+ \\ transfer T9 A1 A2 10 _ _ _ _ 0 L1 C1 _ PEN _ _ _ _ _ _ _ _ _ ok
2083
+ \\ transfer T10 A1 A2 0 _ _ _ _ 2 L1 C1 _ PEN _ _ _ _ _ _ CCR _ _ ok
2084
+ \\ transfer T10 A1 A2 0 _ _ _ _ 2 L1 C1 _ PEN _ _ _ _ _ _ CCR _ _ exists
2085
+ // `credit_account_already_closed` is a transient error, T11 cannot be reused:
2086
+ \\ transfer T11 A1 A2 10 T9 _ _ _ _ L1 C1 _ _ POS _ _ _ _ _ _ _ _ credit_account_already_closed
2087
+ \\ transfer T11 A1 A2 10 T9 _ _ _ _ L1 C1 _ _ _ VOI _ _ _ _ _ _ _ id_already_failed
2088
+ \\
2089
+ \\ transfer T12 A1 A2 10 T9 _ _ _ _ L1 C1 _ _ _ VOI _ _ _ _ _ _ _ ok
2090
+ \\ transfer T12 A1 A2 10 T9 _ _ _ _ L1 C1 _ _ _ VOI _ _ _ _ _ _ _ exists
2091
+ \\ commit create_transfers
2092
+ \\
2093
+ \\ lookup_account A1 10 20 0 0 _
2094
+ \\ lookup_account A2 0 0 10 20 CLSD
2095
+ \\ commit lookup_accounts
2096
+ \\
2097
+ // Pending balance can expire for closed accounts.
2098
+ \\ tick 1 seconds
2099
+ \\ lookup_account A1 0 20 0 0 _
2100
+ \\ lookup_account A2 0 0 0 20 CLSD
2101
+ \\ commit lookup_accounts
2102
+ \\
2103
+ // Pending closing accounts can expire after the timeout.
2104
+ \\ tick 1 seconds
2105
+ \\ lookup_account A1 0 20 0 0 _
2106
+ \\ lookup_account A2 0 0 0 20 _
2107
+ \\ commit lookup_accounts
2108
+ \\
2109
+ // Closing both accounts.
2110
+ \\ transfer T13 A1 A2 0 _ _ _ _ _ L1 C1 _ _ _ _ _ _ _ CDR CCR _ _ closing_transfer_must_be_pending
2111
+ \\ transfer T13 A1 A2 0 _ _ _ _ 0 L1 C1 _ PEN _ _ _ _ _ CDR CCR _ _ ok
2112
+ \\ transfer T13 A1 A2 0 _ _ _ _ 0 L1 C1 _ PEN _ _ _ _ _ CDR CCR _ _ exists
2113
+ // `debit_account_already_closed` is a transient error, T14 cannot be reused:
2114
+ \\ transfer T14 A1 A3 5 _ _ _ _ _ L1 C1 _ _ _ _ _ _ _ _ _ _ _ debit_account_already_closed
2115
+ \\ transfer T14 A1 A3 5 _ _ _ _ _ L1 C1 _ _ _ _ _ _ _ _ _ _ _ id_already_failed
2116
+ // `credit_account_already_closed` is a transient error, T15 cannot be reused:
2117
+ \\ transfer T15 A3 A2 5 _ _ _ _ _ L1 C1 _ _ _ _ _ _ _ _ _ _ _ credit_account_already_closed
2118
+ \\ transfer T15 A3 A2 5 _ _ _ _ _ L1 C1 _ _ _ _ _ _ _ _ _ _ _ id_already_failed
2119
+ \\ commit create_transfers
2120
+ \\
2121
+ \\ lookup_account A1 0 20 0 0 CLSD
2122
+ \\ lookup_account A2 0 0 0 20 CLSD
2123
+ \\ commit lookup_accounts
2124
+ \\
2125
+ // Cannot close an already closed account.
2126
+ // `debit_account_already_closed` is a transient error, T16 cannot be reused:
2127
+ \\ transfer T16 A1 A3 0 _ _ _ _ 0 L1 C1 _ PEN _ _ _ _ _ CDR _ _ _ debit_account_already_closed
2128
+ \\ transfer T16 A1 A3 0 _ _ _ _ 0 L1 C1 _ PEN _ _ _ _ _ CDR _ _ _ id_already_failed
2129
+ // `credit_account_already_closed` is a transient error, T17 cannot be reused:
2130
+ \\ transfer T17 A3 A2 0 _ _ _ _ 0 L1 C1 _ PEN _ _ _ _ _ _ CCR _ _ credit_account_already_closed
2131
+ \\ transfer T17 A3 A2 0 _ _ _ _ 0 L1 C1 _ PEN _ _ _ _ _ _ CCR _ _ id_already_failed
2132
+ \\ commit create_transfers
2133
+ );
2134
+ }
2135
+
2136
+ test "get_account_transfers: single-phase" {
2137
+ try check(
2138
+ \\ account A1 0 0 0 0 _ _ _ _ L1 C1 _ _ _ _ _ _ _ _ ok
2139
+ \\ account A2 0 0 0 0 _ _ _ _ L1 C1 _ _ _ _ _ _ _ _ ok
2140
+ \\ commit create_accounts
2141
+ \\
2142
+ \\ transfer T1 A1 A2 10 _ U1000 U10 U1 _ L1 C1 _ _ _ _ _ _ _ _ _ _ _ ok
2143
+ \\ transfer T2 A2 A1 11 _ U1001 U10 U2 _ L1 C2 _ _ _ _ _ _ _ _ _ _ _ ok
2144
+ \\ transfer T3 A1 A2 12 _ U1000 U20 U2 _ L1 C1 _ _ _ _ _ _ _ _ _ _ _ ok
2145
+ \\ transfer T4 A2 A1 13 _ U1001 U20 U1 _ L1 C2 _ _ _ _ _ _ _ _ _ _ _ ok
2146
+ \\ commit create_transfers
2147
+ \\
2148
+ // Debits + credits, chronological.
2149
+ \\ get_account_transfers A1 _ _ _ _ _ _ 10 DR CR _
2150
+ \\ get_account_transfers_result T1
2151
+ \\ get_account_transfers_result T2
2152
+ \\ get_account_transfers_result T3
2153
+ \\ get_account_transfers_result T4
2154
+ \\ commit get_account_transfers
2155
+ \\
2156
+ // Debits + credits, limit=2.
2157
+ \\ get_account_transfers A1 _ _ _ _ _ _ 2 DR CR _
2158
+ \\ get_account_transfers_result T1
2159
+ \\ get_account_transfers_result T2
2160
+ \\ commit get_account_transfers
2161
+ \\
2162
+ // Debits + credits, timestamp_min>0.
2163
+ \\ get_account_transfers A1 _ _ _ _ T3 _ 10 DR CR _
2164
+ \\ get_account_transfers_result T3
2165
+ \\ get_account_transfers_result T4
2166
+ \\ commit get_account_transfers
2167
+ \\
2168
+ // Debits + credits, timestamp_max>0.
2169
+ \\ get_account_transfers A1 _ _ _ _ _ T2 10 DR CR _
2170
+ \\ get_account_transfers_result T1
2171
+ \\ get_account_transfers_result T2
2172
+ \\ commit get_account_transfers
2173
+ \\
2174
+ // Debits + credits, 0 < timestamp_min ≤ timestamp_max.
2175
+ \\ get_account_transfers A1 _ _ _ _ T2 T3 10 DR CR _
2176
+ \\ get_account_transfers_result T2
2177
+ \\ get_account_transfers_result T3
2178
+ \\ commit get_account_transfers
2179
+ \\
2180
+ // Debits + credits, reverse-chronological.
2181
+ \\ get_account_transfers A1 _ _ _ _ _ _ 10 DR CR REV
2182
+ \\ get_account_transfers_result T4
2183
+ \\ get_account_transfers_result T3
2184
+ \\ get_account_transfers_result T2
2185
+ \\ get_account_transfers_result T1
2186
+ \\ commit get_account_transfers
2187
+ \\
2188
+ // Debits only.
2189
+ \\ get_account_transfers A1 _ _ _ _ _ _ 10 DR _ _
2190
+ \\ get_account_transfers_result T1
2191
+ \\ get_account_transfers_result T3
2192
+ \\ commit get_account_transfers
2193
+ \\
2194
+ // Credits only.
2195
+ \\ get_account_transfers A1 _ _ _ _ _ _ 10 _ CR _
2196
+ \\ get_account_transfers_result T2
2197
+ \\ get_account_transfers_result T4
2198
+ \\ commit get_account_transfers
2199
+ \\
2200
+ // Debits + credits + user_data_128, chronological.
2201
+ \\ get_account_transfers A1 U1001 _ _ _ _ _ 10 DR CR _
2202
+ \\ get_account_transfers_result T2
2203
+ \\ get_account_transfers_result T4
2204
+ \\ commit get_account_transfers
2205
+ \\
2206
+ // Debits + credits + user_data_64, chronological.
2207
+ \\ get_account_transfers A1 _ U10 _ _ _ _ 10 DR CR _
2208
+ \\ get_account_transfers_result T1
2209
+ \\ get_account_transfers_result T2
2210
+ \\ commit get_account_transfers
2211
+ \\
2212
+ // Debits + credits + user_data_32, chronological.
2213
+ \\ get_account_transfers A1 _ _ U1 _ _ _ 10 DR CR _
2214
+ \\ get_account_transfers_result T1
2215
+ \\ get_account_transfers_result T4
2216
+ \\ commit get_account_transfers
2217
+ \\
2218
+ // Debits + credits + code, chronological.
2219
+ \\ get_account_transfers A1 _ _ _ C1 _ _ 10 DR CR _
2220
+ \\ get_account_transfers_result T1
2221
+ \\ get_account_transfers_result T3
2222
+ \\ commit get_account_transfers
2223
+ \\
2224
+ // Debits + credits + all filters, 0 < timestamp_min ≤ timestamp_max, chronological.
2225
+ \\ get_account_transfers A1 U1000 U10 U1 C1 T1 T3 10 DR CR _
2226
+ \\ get_account_transfers_result T1
2227
+ \\ commit get_account_transfers
2228
+ \\
2229
+ // Debits only + all filters, 0 < timestamp_min ≤ timestamp_max, chronological.
2230
+ \\ get_account_transfers A1 U1000 U10 U1 C1 T1 T3 10 DR _ _
2231
+ \\ get_account_transfers_result T1
2232
+ \\ commit get_account_transfers
2233
+ \\
2234
+ // Credits only + all filters, 0 < timestamp_min ≤ timestamp_max, chronological.
2235
+ \\ get_account_transfers A2 U1000 U10 U1 C1 T1 T3 10 _ CR _
2236
+ \\ get_account_transfers_result T1
2237
+ \\ commit get_account_transfers
2238
+ \\
2239
+ // Not found.
2240
+ \\ get_account_transfers A1 U1000 U20 U2 C2 _ _ 10 DR CR _
2241
+ \\ commit get_account_transfers
2242
+ );
2243
+ }
2244
+
2245
+ test "get_account_transfers: two-phase" {
2246
+ try check(
2247
+ \\ account A1 0 0 0 0 _ _ _ _ L1 C1 _ _ _ HIST _ _ _ _ ok
2248
+ \\ account A2 0 0 0 0 _ _ _ _ L1 C1 _ _ _ HIST _ _ _ _ ok
2249
+ \\ commit create_accounts
2250
+ \\
2251
+ \\ transfer T1 A1 A2 2 _ _ _ _ 0 L1 C1 _ PEN _ _ _ _ _ _ _ _ _ ok
2252
+ \\ transfer T2 A1 A2 1 T1 _ _ _ 0 L1 C1 _ _ POS _ _ _ _ _ _ _ _ ok
2253
+ \\ commit create_transfers
2254
+ \\
2255
+ \\ get_account_transfers A1 _ _ _ _ _ _ 10 DR CR _
2256
+ \\ get_account_transfers_result T1
2257
+ \\ get_account_transfers_result T2
2258
+ \\ commit get_account_transfers
2259
+ );
2260
+ }
2261
+
2262
+ test "get_account_transfers: invalid filter" {
2263
+ try check(
2264
+ \\ account A1 0 0 0 0 _ _ _ _ L1 C1 _ _ _ _ _ _ _ _ ok
2265
+ \\ account A2 0 0 0 0 _ _ _ _ L1 C1 _ _ _ _ _ _ _ _ ok
2266
+ \\ commit create_accounts
2267
+ \\
2268
+ \\ transfer T1 A1 A2 2 _ _ _ _ 0 L1 C1 _ PEN _ _ _ _ _ _ _ _ _ ok
2269
+ \\ transfer T2 A1 A2 1 T1 _ _ _ 0 L1 C1 _ _ POS _ _ _ _ _ _ _ _ ok
2270
+ \\ commit create_transfers
2271
+ \\
2272
+ // Invalid account.
2273
+ \\ get_account_transfers A3 _ _ _ _ _ _ 10 DR CR _
2274
+ \\ commit get_account_transfers // Empty result.
2275
+ \\
2276
+ // Invalid filter flags.
2277
+ \\ get_account_transfers A1 _ _ _ _ _ _ 10 _ _ _
2278
+ \\ commit get_account_transfers // Empty result.
2279
+ \\
2280
+ // Invalid timestamp_min > timestamp_max.
2281
+ \\ get_account_transfers A1 _ _ _ _ T2 T1 10 DR CR _
2282
+ \\ commit get_account_transfers // Empty result.
2283
+ \\
2284
+ // Invalid limit.
2285
+ \\ get_account_transfers A1 _ _ _ _ _ _ 0 DR CR _
2286
+ \\ commit get_account_transfers // Empty result.
2287
+ \\
2288
+ // Success.
2289
+ \\ get_account_transfers A1 _ _ _ C1 _ _ 10 DR CR _
2290
+ \\ get_account_transfers_result T1
2291
+ \\ get_account_transfers_result T2
2292
+ \\ commit get_account_transfers
2293
+ );
2294
+ }
2295
+
2296
+ test "get_account_balances: single-phase" {
2297
+ try check(
2298
+ \\ account A1 0 0 0 0 _ _ _ _ L1 C1 _ _ _ HIST _ _ _ _ ok
2299
+ \\ account A2 0 0 0 0 _ _ _ _ L1 C1 _ _ _ HIST _ _ _ _ ok
2300
+ \\ commit create_accounts
2301
+ \\
2302
+ \\ transfer T1 A1 A2 10 _ U1000 U10 U1 _ L1 C1 _ _ _ _ _ _ _ _ _ _ _ ok
2303
+ \\ transfer T2 A2 A1 11 _ U1001 U10 U2 _ L1 C2 _ _ _ _ _ _ _ _ _ _ _ ok
2304
+ \\ transfer T3 A1 A2 12 _ U1000 U20 U2 _ L1 C1 _ _ _ _ _ _ _ _ _ _ _ ok
2305
+ \\ transfer T4 A2 A1 13 _ U1001 U20 U1 _ L1 C2 _ _ _ _ _ _ _ _ _ _ _ ok
2306
+ \\ commit create_transfers
2307
+ \\
2308
+ // Debits + credits, chronological.
2309
+ \\ get_account_balances A1 _ _ _ _ _ _ 10 DR CR _
2310
+ \\ get_account_balances_result T1 0 10 0 0
2311
+ \\ get_account_balances_result T2 0 10 0 11
2312
+ \\ get_account_balances_result T3 0 22 0 11
2313
+ \\ get_account_balances_result T4 0 22 0 24
2314
+ \\ commit get_account_balances
2315
+ \\
2316
+ // Debits + credits, limit=2.
2317
+ \\ get_account_balances A1 _ _ _ _ _ _ 2 DR CR _
2318
+ \\ get_account_balances_result T1 0 10 0 0
2319
+ \\ get_account_balances_result T2 0 10 0 11
2320
+ \\ commit get_account_balances
2321
+ \\
2322
+ // Debits + credits, timestamp_min>0.
2323
+ \\ get_account_balances A1 _ _ _ _ T3 _ 10 DR CR _
2324
+ \\ get_account_balances_result T3 0 22 0 11
2325
+ \\ get_account_balances_result T4 0 22 0 24
2326
+ \\ commit get_account_balances
2327
+ \\
2328
+ // Debits + credits, timestamp_max>0.
2329
+ \\ get_account_balances A1 _ _ _ _ _ T2 10 DR CR _
2330
+ \\ get_account_balances_result T1 0 10 0 0
2331
+ \\ get_account_balances_result T2 0 10 0 11
2332
+ \\ commit get_account_balances
2333
+ \\
2334
+ // Debits + credits, 0 < timestamp_min ≤ timestamp_max.
2335
+ \\ get_account_balances A1 _ _ _ _ T2 T3 10 DR CR _
2336
+ \\ get_account_balances_result T2 0 10 0 11
2337
+ \\ get_account_balances_result T3 0 22 0 11
2338
+ \\ commit get_account_balances
2339
+ \\
2340
+ // Debits + credits, reverse-chronological.
2341
+ \\ get_account_balances A1 _ _ _ _ _ _ 10 DR CR REV
2342
+ \\ get_account_balances_result T4 0 22 0 24
2343
+ \\ get_account_balances_result T3 0 22 0 11
2344
+ \\ get_account_balances_result T2 0 10 0 11
2345
+ \\ get_account_balances_result T1 0 10 0 0
2346
+ \\ commit get_account_balances
2347
+ \\
2348
+ // Debits only.
2349
+ \\ get_account_balances A1 _ _ _ _ _ _ 10 DR _ _
2350
+ \\ get_account_balances_result T1 0 10 0 0
2351
+ \\ get_account_balances_result T3 0 22 0 11
2352
+ \\ commit get_account_balances
2353
+ \\
2354
+ // Credits only.
2355
+ \\ get_account_balances A1 _ _ _ _ _ _ 10 _ CR _
2356
+ \\ get_account_balances_result T2 0 10 0 11
2357
+ \\ get_account_balances_result T4 0 22 0 24
2358
+ \\ commit get_account_balances
2359
+ \\
2360
+ // Debits + credits + user_data_128, chronological.
2361
+ \\ get_account_balances A1 U1001 _ _ _ _ _ 10 DR CR _
2362
+ \\ get_account_balances_result T2 0 10 0 11
2363
+ \\ get_account_balances_result T4 0 22 0 24
2364
+ \\ commit get_account_balances
2365
+ \\
2366
+ // Debits + credits + user_data_64, chronological.
2367
+ \\ get_account_balances A1 _ U10 _ _ _ _ 10 DR CR _
2368
+ \\ get_account_balances_result T1 0 10 0 0
2369
+ \\ get_account_balances_result T2 0 10 0 11
2370
+ \\ commit get_account_balances
2371
+ \\
2372
+ // Debits + credits + user_data_32, chronological.
2373
+ \\ get_account_balances A1 _ _ U1 _ _ _ 10 DR CR _
2374
+ \\ get_account_balances_result T1 0 10 0 0
2375
+ \\ get_account_balances_result T4 0 22 0 24
2376
+ \\ commit get_account_balances
2377
+ \\
2378
+ // Debits + credits + code, chronological.
2379
+ \\ get_account_balances A1 _ _ _ C1 _ _ 10 DR CR _
2380
+ \\ get_account_balances_result T1 0 10 0 0
2381
+ \\ get_account_balances_result T3 0 22 0 11
2382
+ \\ commit get_account_balances
2383
+ \\
2384
+ // Debits + credits + all filters, 0 < timestamp_min ≤ timestamp_max, chronological.
2385
+ \\ get_account_balances A1 U1000 U10 U1 C1 T1 T3 10 DR CR _
2386
+ \\ get_account_balances_result T1 0 10 0 0
2387
+ \\ commit get_account_balances
2388
+ \\
2389
+ // Debits only + all filters, 0 < timestamp_min ≤ timestamp_max, chronological.
2390
+ \\ get_account_balances A1 U1000 U10 U1 C1 T1 T3 10 DR _ _
2391
+ \\ get_account_balances_result T1 0 10 0 0
2392
+ \\ commit get_account_balances
2393
+ \\
2394
+ // Credits only + all filters, 0 < timestamp_min ≤ timestamp_max, chronological.
2395
+ \\ get_account_balances A2 U1000 U10 U1 C1 T1 T3 10 _ CR _
2396
+ \\ get_account_balances_result T1 0 0 0 10
2397
+ \\ commit get_account_balances
2398
+ \\
2399
+ // Not found.
2400
+ \\ get_account_balances A1 U1000 U20 U2 C2 _ _ 10 DR CR _
2401
+ \\ commit get_account_balances
2402
+ );
2403
+ }
2404
+
2405
+ test "get_account_balances: two-phase" {
2406
+ try check(
2407
+ \\ account A1 0 0 0 0 _ _ _ _ L1 C1 _ _ _ HIST _ _ _ _ ok
2408
+ \\ account A2 0 0 0 0 _ _ _ _ L1 C1 _ _ _ HIST _ _ _ _ ok
2409
+ \\ commit create_accounts
2410
+ \\
2411
+ \\ transfer T1 A1 A2 1 _ _ _ _ 0 L1 C1 _ PEN _ _ _ _ _ _ _ _ _ ok
2412
+ \\ transfer T2 A1 A2 1 T1 _ _ _ 0 L1 C1 _ _ POS _ _ _ _ _ _ _ _ ok
2413
+ \\ commit create_transfers
2414
+ \\
2415
+ \\ get_account_balances A1 _ _ _ _ _ _ 10 DR CR _
2416
+ \\ get_account_balances_result T1 1 0 0 0
2417
+ \\ get_account_balances_result T2 0 1 0 0
2418
+ \\ commit get_account_balances
2419
+ );
2420
+ }
2421
+
2422
+ test "get_account_balances: invalid filter" {
2423
+ try check(
2424
+ \\ account A1 0 0 0 0 _ _ _ _ L1 C1 _ _ _ HIST _ _ _ _ ok
2425
+ \\ account A2 0 0 0 0 _ _ _ _ L1 C1 _ _ _ _ _ _ _ _ ok
2426
+ \\ commit create_accounts
2427
+ \\
2428
+ \\ transfer T1 A1 A2 2 _ _ _ _ 0 L1 C1 _ _ _ _ _ _ _ _ _ _ _ ok
2429
+ \\ transfer T2 A1 A2 1 _ _ _ _ 0 L1 C1 _ _ _ _ _ _ _ _ _ _ _ ok
2430
+ \\ commit create_transfers
2431
+ \\
2432
+ // Invalid account.
2433
+ \\ get_account_balances A3 _ _ _ _ _ _ 10 DR CR _
2434
+ \\ commit get_account_balances // Empty result.
2435
+ \\
2436
+ // Account without flags.history.
2437
+ \\ get_account_balances A2 _ _ _ _ _ _ 10 DR CR _
2438
+ \\ commit get_account_balances // Empty result.
2439
+ \\
2440
+ // Invalid filter flags.
2441
+ \\ get_account_balances A1 _ _ _ _ _ _ 10 _ _ _
2442
+ \\ commit get_account_balances // Empty result.
2443
+ \\
2444
+ // Invalid timestamp_min > timestamp_max.
2445
+ \\ get_account_balances A1 _ _ _ _ T2 T1 10 DR CR _
2446
+ \\ commit get_account_balances // Empty result.
2447
+ \\
2448
+ // Invalid limit.
2449
+ \\ get_account_balances A1 _ _ _ _ _ _ 0 DR CR _
2450
+ \\ commit get_account_balances // Empty result.
2451
+ \\
2452
+ // Success.
2453
+ \\ get_account_balances A1 _ _ _ C1 _ _ 10 DR CR _
2454
+ \\ get_account_balances_result T1 0 2 0 0
2455
+ \\ get_account_balances_result T2 0 3 0 0
2456
+ \\ commit get_account_balances
2457
+ );
2458
+ }
2459
+
2460
+ test "query_accounts" {
2461
+ try check(
2462
+ \\ account A1 0 0 0 0 U1000 U10 U1 _ L1 C1 _ _ _ _ _ _ _ _ ok
2463
+ \\ account A2 0 0 0 0 U1000 U11 U2 _ L2 C2 _ _ _ _ _ _ _ _ ok
2464
+ \\ account A3 0 0 0 0 U1000 U10 U3 _ L3 C3 _ _ _ _ _ _ _ _ ok
2465
+ \\ account A4 0 0 0 0 U1000 U11 U4 _ L4 C4 _ _ _ _ _ _ _ _ ok
2466
+ \\ account A5 0 0 0 0 U2000 U10 U1 _ L3 C5 _ _ _ _ _ _ _ _ ok
2467
+ \\ account A6 0 0 0 0 U2000 U11 U2 _ L2 C6 _ _ _ _ _ _ _ _ ok
2468
+ \\ account A7 0 0 0 0 U2000 U10 U3 _ L1 C7 _ _ _ _ _ _ _ _ ok
2469
+ \\ account A8 0 0 0 0 U1000 U10 U1 _ L1 C1 _ _ _ _ _ _ _ _ ok
2470
+ \\ commit create_accounts
2471
+
2472
+ // WHERE user_data_128=1000:
2473
+ \\ query_accounts U1000 U0 U0 L0 C0 _ _ L-0 _
2474
+ \\ query_accounts_result A1 _
2475
+ \\ query_accounts_result A2 _
2476
+ \\ query_accounts_result A3 _
2477
+ \\ query_accounts_result A4 _
2478
+ \\ query_accounts_result A8 _
2479
+ \\ commit query_accounts
2480
+
2481
+ // WHERE user_data_128=1000 ORDER BY DESC:
2482
+ \\ query_accounts U1000 U0 U0 L0 C0 _ _ L-0 REV
2483
+ \\ query_accounts_result A8 _
2484
+ \\ query_accounts_result A4 _
2485
+ \\ query_accounts_result A3 _
2486
+ \\ query_accounts_result A2 _
2487
+ \\ query_accounts_result A1 _
2488
+ \\ commit query_accounts
2489
+
2490
+ // WHERE user_data_64=10 AND user_data_32=3
2491
+ \\ query_accounts U0 U10 U3 L0 C0 _ _ L-0 _
2492
+ \\ query_accounts_result A3 _
2493
+ \\ query_accounts_result A7 _
2494
+ \\ commit query_accounts
2495
+
2496
+ // WHERE user_data_64=10 AND user_data_32=3 ORDER BY DESC:
2497
+ \\ query_accounts U0 U10 U3 L0 C0 _ _ L-0 REV
2498
+ \\ query_accounts_result A7 _
2499
+ \\ query_accounts_result A3 _
2500
+ \\ commit query_accounts
2501
+
2502
+ // WHERE user_data_64=11 AND user_data_32=2 AND code=2:
2503
+ \\ query_accounts U0 U11 U2 L2 C0 _ _ L-0 _
2504
+ \\ query_accounts_result A2 _
2505
+ \\ query_accounts_result A6 _
2506
+ \\ commit query_accounts
2507
+
2508
+ // WHERE user_data_64=11 AND user_data_32=2 AND code=2 ORDER BY DESC:
2509
+ \\ query_accounts U0 U11 U2 L2 C0 _ _ L-0 REV
2510
+ \\ query_accounts_result A6 _
2511
+ \\ query_accounts_result A2 _
2512
+ \\ commit query_accounts
2513
+
2514
+ // WHERE user_data_128=1000 AND user_data_64=10
2515
+ // AND user_data_32=1 AND ledger=1 AND code=1:
2516
+ \\ query_accounts U1000 U10 U1 L1 C1 _ _ L-0 _
2517
+ \\ query_accounts_result A1 _
2518
+ \\ query_accounts_result A8 _
2519
+ \\ commit query_accounts
2520
+
2521
+ // WHERE user_data_128=1000 AND user_data_64=10
2522
+ // AND user_data_32=1 AND ledger=1 AND code=1 ORDER BY DESC:
2523
+ \\ query_accounts U1000 U10 U1 L1 C1 _ _ L-0 REV
2524
+ \\ query_accounts_result A8 _
2525
+ \\ query_accounts_result A1 _
2526
+ \\ commit query_accounts
2527
+
2528
+ // WHERE user_data_128=1000 AND timestamp >= A3.timestamp:
2529
+ \\ query_accounts U1000 U0 U0 L0 C0 A3 _ L-0 _
2530
+ \\ query_accounts_result A3 _
2531
+ \\ query_accounts_result A4 _
2532
+ \\ query_accounts_result A8 _
2533
+ \\ commit query_accounts
2534
+
2535
+ // WHERE user_data_128=1000 AND timestamp <= A3.timestamp:
2536
+ \\ query_accounts U1000 U0 U0 L0 C0 _ A3 L-0 _
2537
+ \\ query_accounts_result A1 _
2538
+ \\ query_accounts_result A2 _
2539
+ \\ query_accounts_result A3 _
2540
+ \\ commit query_accounts
2541
+
2542
+ // WHERE user_data_128=1000 AND timestamp BETWEEN A2.timestamp AND A4.timestamp:
2543
+ \\ query_accounts U1000 U0 U0 L0 C0 A2 A4 L-0 _
2544
+ \\ query_accounts_result A2 _
2545
+ \\ query_accounts_result A3 _
2546
+ \\ query_accounts_result A4 _
2547
+ \\ commit query_accounts
2548
+
2549
+ // SELECT * :
2550
+ \\ query_accounts U0 U0 U0 L0 C0 _ _ L-0 _
2551
+ \\ query_accounts_result A1 _
2552
+ \\ query_accounts_result A2 _
2553
+ \\ query_accounts_result A3 _
2554
+ \\ query_accounts_result A4 _
2555
+ \\ query_accounts_result A5 _
2556
+ \\ query_accounts_result A6 _
2557
+ \\ query_accounts_result A7 _
2558
+ \\ query_accounts_result A8 _
2559
+ \\ commit query_accounts
2560
+
2561
+ // SELECT * ORDER BY DESC:
2562
+ \\ query_accounts U0 U0 U0 L0 C0 _ _ L-0 REV
2563
+ \\ query_accounts_result A8 _
2564
+ \\ query_accounts_result A7 _
2565
+ \\ query_accounts_result A6 _
2566
+ \\ query_accounts_result A5 _
2567
+ \\ query_accounts_result A4 _
2568
+ \\ query_accounts_result A3 _
2569
+ \\ query_accounts_result A2 _
2570
+ \\ query_accounts_result A1 _
2571
+ \\ commit query_accounts
2572
+
2573
+ // SELECT * WHERE timestamp >= A2.timestamp LIMIT 3:
2574
+ \\ query_accounts U0 U0 U0 L0 C0 A2 _ L3 _
2575
+ \\ query_accounts_result A2 _
2576
+ \\ query_accounts_result A3 _
2577
+ \\ query_accounts_result A4 _
2578
+ \\ commit query_accounts
2579
+
2580
+ // SELECT * LIMIT 1:
2581
+ \\ query_accounts U0 U0 U0 L0 C0 _ _ L1 _
2582
+ \\ query_accounts_result A1 _
2583
+ \\ commit query_accounts
2584
+
2585
+ // SELECT * ORDER BY DESC LIMIT 1:
2586
+ \\ query_accounts U0 U0 U0 L0 C0 _ _ L1 REV
2587
+ \\ query_accounts_result A8 _
2588
+ \\ commit query_accounts
2589
+
2590
+ // NOT FOUND:
2591
+
2592
+ // SELECT * LIMIT 0:
2593
+ \\ query_accounts U0 U0 U0 L0 C0 _ _ L0 _
2594
+ \\ commit query_accounts
2595
+
2596
+ // WHERE user_data_128=3000
2597
+ \\ query_accounts U3000 U0 U0 L0 C0 _ _ L-0 _
2598
+ \\ commit query_accounts
2599
+
2600
+ // WHERE user_data_128=1000 AND code=5
2601
+ \\ query_accounts U1000 U0 U0 L0 C5 _ _ L-0 _
2602
+ \\ commit query_accounts
2603
+
2604
+ // WHERE user_data_128=1000 AND user_data_64=10
2605
+ // AND user_data_32=1 AND ledger=1 AND code=2:
2606
+ \\ query_accounts U1000 U10 U1 L1 C2 _ _ L-0 _
2607
+ \\ commit query_accounts
2608
+
2609
+ // WHERE user_data_128=1000 AND timestamp BETWEEN A5.timestamp AND A7.timestamp:
2610
+ \\ query_accounts U1000 U0 U0 L0 C0 A5 A7 L-0 _
2611
+ \\ commit query_accounts
2612
+ );
2613
+ }
2614
+
2615
+ test "query_transfers" {
2616
+ try check(
2617
+ \\ account A1 0 0 0 0 _ _ _ _ L1 C1 _ _ _ _ _ _ _ _ ok
2618
+ \\ account A2 0 0 0 0 _ _ _ _ L1 C1 _ _ _ _ _ _ _ _ ok
2619
+ \\ account A3 0 0 0 0 _ _ _ _ L2 C1 _ _ _ _ _ _ _ _ ok
2620
+ \\ account A4 0 0 0 0 _ _ _ _ L2 C1 _ _ _ _ _ _ _ _ ok
2621
+ \\ commit create_accounts
2622
+
2623
+ // Creating transfers:
2624
+ \\ transfer T1 A1 A2 0 _ U1000 U10 U1 _ L1 C1 _ _ _ _ _ _ _ _ _ _ _ ok
2625
+ \\ transfer T2 A3 A4 11 _ U1000 U11 U2 _ L2 C2 _ _ _ _ _ _ _ _ _ _ _ ok
2626
+ \\ transfer T3 A2 A1 12 _ U1000 U10 U3 _ L1 C3 _ _ _ _ _ _ _ _ _ _ _ ok
2627
+ \\ transfer T4 A4 A3 13 _ U1000 U11 U4 _ L2 C4 _ _ _ _ _ _ _ _ _ _ _ ok
2628
+ \\ transfer T5 A2 A1 14 _ U2000 U10 U1 _ L1 C5 _ _ _ _ _ _ _ _ _ _ _ ok
2629
+ \\ transfer T6 A4 A3 15 _ U2000 U11 U2 _ L2 C6 _ _ _ _ _ _ _ _ _ _ _ ok
2630
+ \\ transfer T7 A1 A2 16 _ U2000 U10 U3 _ L1 C7 _ _ _ _ _ _ _ _ _ _ _ ok
2631
+ \\ transfer T8 A2 A1 17 _ U1000 U10 U1 _ L1 C1 _ _ _ _ _ _ _ _ _ _ _ ok
2632
+ \\ commit create_transfers
2633
+
2634
+ // WHERE user_data_128=1000:
2635
+ \\ query_transfers U1000 U0 U0 L0 C0 _ _ L-0 _
2636
+ \\ query_transfers_result T1
2637
+ \\ query_transfers_result T2
2638
+ \\ query_transfers_result T3
2639
+ \\ query_transfers_result T4
2640
+ \\ query_transfers_result T8
2641
+ \\ commit query_transfers
2642
+
2643
+ // WHERE user_data_128=1000 ORDER BY DESC:
2644
+ \\ query_transfers U1000 U0 U0 L0 C0 _ _ L-0 REV
2645
+ \\ query_transfers_result T8
2646
+ \\ query_transfers_result T4
2647
+ \\ query_transfers_result T3
2648
+ \\ query_transfers_result T2
2649
+ \\ query_transfers_result T1
2650
+ \\ commit query_transfers
2651
+
2652
+ // WHERE user_data_64=10 AND user_data_32=3
2653
+ \\ query_transfers U0 U10 U3 L0 C0 _ _ L-0 _
2654
+ \\ query_transfers_result T3
2655
+ \\ query_transfers_result T7
2656
+ \\ commit query_transfers
2657
+
2658
+ // WHERE user_data_64=10 AND user_data_32=3 ORDER BY DESC:
2659
+ \\ query_transfers U0 U10 U3 L0 C0 _ _ L-0 REV
2660
+ \\ query_transfers_result T7
2661
+ \\ query_transfers_result T3
2662
+ \\ commit query_transfers
2663
+
2664
+ // WHERE user_data_64=11 AND user_data_32=2 AND code=2:
2665
+ \\ query_transfers U0 U11 U2 L2 C0 _ _ L-0 _
2666
+ \\ query_transfers_result T2
2667
+ \\ query_transfers_result T6
2668
+ \\ commit query_transfers
2669
+
2670
+ // WHERE user_data_64=11 AND user_data_32=2 AND code=2 ORDER BY DESC:
2671
+ \\ query_transfers U0 U11 U2 L2 C0 _ _ L-0 REV
2672
+ \\ query_transfers_result T6
2673
+ \\ query_transfers_result T2
2674
+ \\ commit query_transfers
2675
+
2676
+ // WHERE user_data_128=1000 AND user_data_64=10
2677
+ // AND user_data_32=1 AND ledger=1 AND code=1:
2678
+ \\ query_transfers U1000 U10 U1 L1 C1 _ _ L-0 _
2679
+ \\ query_transfers_result T1
2680
+ \\ query_transfers_result T8
2681
+ \\ commit query_transfers
2682
+
2683
+ // WHERE user_data_128=1000 AND user_data_64=10
2684
+ // AND user_data_32=1 AND ledger=1 AND code=1 ORDER BY DESC:
2685
+ \\ query_transfers U1000 U10 U1 L1 C1 _ _ L-0 REV
2686
+ \\ query_transfers_result T8
2687
+ \\ query_transfers_result T1
2688
+ \\ commit query_transfers
2689
+
2690
+ // WHERE user_data_128=1000 AND timestamp >= T3.timestamp:
2691
+ \\ query_transfers U1000 U0 U0 L0 C0 A3 _ L-0 _
2692
+ \\ query_transfers_result T3
2693
+ \\ query_transfers_result T4
2694
+ \\ query_transfers_result T8
2695
+ \\ commit query_transfers
2696
+
2697
+ // WHERE user_data_128=1000 AND timestamp <= T3.timestamp:
2698
+ \\ query_transfers U1000 U0 U0 L0 C0 _ A3 L-0 _
2699
+ \\ query_transfers_result T1
2700
+ \\ query_transfers_result T2
2701
+ \\ query_transfers_result T3
2702
+ \\ commit query_transfers
2703
+
2704
+ // WHERE user_data_128=1000 AND timestamp BETWEEN T2.timestamp AND T4.timestamp:
2705
+ \\ query_transfers U1000 U0 U0 L0 C0 A2 A4 L-0 _
2706
+ \\ query_transfers_result T2
2707
+ \\ query_transfers_result T3
2708
+ \\ query_transfers_result T4
2709
+ \\ commit query_transfers
2710
+
2711
+ // SELECT * :
2712
+ \\ query_transfers U0 U0 U0 L0 C0 _ _ L-0 _
2713
+ \\ query_transfers_result T1
2714
+ \\ query_transfers_result T2
2715
+ \\ query_transfers_result T3
2716
+ \\ query_transfers_result T4
2717
+ \\ query_transfers_result T5
2718
+ \\ query_transfers_result T6
2719
+ \\ query_transfers_result T7
2720
+ \\ query_transfers_result T8
2721
+ \\ commit query_transfers
2722
+
2723
+ // SELECT * ORDER BY DESC:
2724
+ \\ query_transfers U0 U0 U0 L0 C0 _ _ L-0 REV
2725
+ \\ query_transfers_result T8
2726
+ \\ query_transfers_result T7
2727
+ \\ query_transfers_result T6
2728
+ \\ query_transfers_result T5
2729
+ \\ query_transfers_result T4
2730
+ \\ query_transfers_result T3
2731
+ \\ query_transfers_result T2
2732
+ \\ query_transfers_result T1
2733
+ \\ commit query_transfers
2734
+
2735
+ // SELECT * WHERE timestamp >= A2.timestamp LIMIT 3:
2736
+ \\ query_transfers U0 U0 U0 L0 C0 A2 _ L3 _
2737
+ \\ query_transfers_result T2
2738
+ \\ query_transfers_result T3
2739
+ \\ query_transfers_result T4
2740
+ \\ commit query_transfers
2741
+
2742
+ // SELECT * LIMIT 1:
2743
+ \\ query_transfers U0 U0 U0 L0 C0 _ _ L1 _
2744
+ \\ query_transfers_result T1
2745
+ \\ commit query_transfers
2746
+
2747
+ // SELECT * ORDER BY DESC LIMIT 1:
2748
+ \\ query_transfers U0 U0 U0 L0 C0 _ _ L1 REV
2749
+ \\ query_transfers_result T8
2750
+ \\ commit query_transfers
2751
+
2752
+ // NOT FOUND:
2753
+
2754
+ // SELECT * LIMIT 0:
2755
+ \\ query_transfers U0 U0 U0 L0 C0 _ _ L0 _
2756
+ \\ commit query_transfers
2757
+
2758
+ // WHERE user_data_128=3000
2759
+ \\ query_transfers U3000 U0 U0 L0 C0 _ _ L-0 _
2760
+ \\ commit query_transfers
2761
+
2762
+ // WHERE user_data_128=1000 AND code=5
2763
+ \\ query_transfers U1000 U0 U0 L0 C5 _ _ L-0 _
2764
+ \\ commit query_transfers
2765
+
2766
+ // WHERE user_data_128=1000 AND user_data_64=10
2767
+ // AND user_data_32=1 AND ledger=1 AND code=2:
2768
+ \\ query_transfers U1000 U10 U1 L1 C2 _ _ L-0 _
2769
+ \\ commit query_transfers
2770
+
2771
+ // WHERE user_data_128=1000 AND timestamp BETWEEN T5.timestamp AND T7.timestamp:
2772
+ \\ query_transfers U1000 U0 U0 L0 C0 A5 A7 L-0 _
2773
+ \\ commit query_transfers
2774
+ );
2775
+ }
2776
+
2777
+ test "get_change_events" {
2778
+ try check(
2779
+ \\ account A1 0 0 0 0 _ _ _ _ L1 C1 _ _ _ _ _ _ _ _ ok
2780
+ \\ account A2 0 0 0 0 _ _ _ _ L1 C1 _ _ _ _ _ _ _ _ ok
2781
+ \\ account A3 0 0 0 0 _ _ _ _ L1 C1 _ _ _ _ _ _ _ _ ok
2782
+ \\ account A4 0 0 0 0 _ _ _ _ L1 C1 _ _ _ _ _ _ _ _ ok
2783
+ \\ commit create_accounts
2784
+
2785
+ // First phase.
2786
+ \\ transfer T1 A1 A2 10 _ _ _ _ _ L1 C1 _ _ _ _ _ _ _ _ _ _ _ ok // Not pending.
2787
+ \\ transfer T2 A1 A2 11 _ _ _ _ 0 L1 C1 _ PEN _ _ _ _ _ _ _ _ _ ok // Timeout zero will never expire.
2788
+ \\ transfer T3 A1 A2 12 _ _ _ _ 1 L1 C1 _ PEN _ _ _ _ _ _ _ _ _ ok // Will expire.
2789
+ \\ transfer T4 A1 A2 13 _ _ _ _ 2 L1 C1 _ PEN _ _ _ _ _ _ _ _ _ ok // Will be posted.
2790
+ \\ transfer T5 A1 A2 14 _ _ _ _ 2 L1 C1 _ PEN _ _ _ _ _ _ _ _ _ ok // Will be voided.
2791
+ // Closes the debit and credit accounts.
2792
+ \\ transfer T6 A3 A1 0 _ _ _ _ 0 L1 C1 _ PEN _ _ _ _ _ CDR _ _ _ ok
2793
+ \\ transfer T7 A1 A4 0 _ _ _ _ 0 L1 C1 _ PEN _ _ _ _ _ _ CCR _ _ ok
2794
+ \\ commit create_transfers
2795
+
2796
+ // Bump the state machine time in +1s for testing the timeout expiration.
2797
+ \\ tick 1 seconds
2798
+
2799
+ // Second phase.
2800
+ \\ transfer T14 A0 A0 -0 T4 _ _ _ _ L0 C0 _ _ POS _ _ _ _ _ _ _ _ ok // Posts T4.
2801
+ \\ transfer T15 A0 A0 0 T5 _ _ _ _ L0 C0 _ _ _ VOI _ _ _ _ _ _ _ ok // Voids T5.
2802
+ // Reopens the debit and credit accounts.
2803
+ \\ transfer T16 A0 A0 0 T6 _ _ _ _ L0 C0 _ _ _ VOI _ _ _ _ _ _ _ ok
2804
+ \\ transfer T17 A0 A0 0 T7 _ _ _ _ L0 C0 _ _ _ VOI _ _ _ _ _ _ _ ok
2805
+ \\ commit create_transfers
2806
+
2807
+ // Check the events.
2808
+ \\ get_change_events _ T6 5
2809
+ \\ get_change_events_result _ T1 10 _ D1 0 10 0 0 _ C2 0 0 0 10 _
2810
+ \\ get_change_events_result PEN T2 11 _ D1 11 10 0 0 _ C2 0 0 11 10 _
2811
+ \\ get_change_events_result PEN T3 12 _ D1 23 10 0 0 _ C2 0 0 23 10 _
2812
+ \\ get_change_events_result PEN T4 13 _ D1 36 10 0 0 _ C2 0 0 36 10 _
2813
+ \\ get_change_events_result PEN T5 14 _ D1 50 10 0 0 _ C2 0 0 50 10 _
2814
+ \\ commit get_change_events
2815
+ \\
2816
+ \\ get_change_events T6 _ -0
2817
+ \\ get_change_events_result PEN T6 0 _ D3 0 0 0 0 CLSD A1 50 10 0 0 _
2818
+ \\ get_change_events_result PEN T7 0 _ D1 50 10 0 0 _ C4 0 0 0 0 CLSD
2819
+ \\ get_change_events_result EXP _ 12 T3 D1 38 10 0 0 _ C2 0 0 38 10 _
2820
+ \\ get_change_events_result POS T14 13 T4 D1 25 23 0 0 _ C2 0 0 25 23 _
2821
+ \\ get_change_events_result VOI T15 14 T5 D1 11 23 0 0 _ C2 0 0 11 23 _
2822
+ \\ get_change_events_result VOI T16 0 T6 D3 0 0 0 0 _ C1 11 23 0 0 _
2823
+ \\ get_change_events_result VOI T17 0 T7 D1 11 23 0 0 _ C4 0 0 0 0 _
2824
+ \\ commit get_change_events
2825
+ );
2826
+ }
2827
+
2828
+ // Sanity test to check the maximum batch size.
2829
+ // For a comprehensive test of all operations, see the `input_valid` test.
2830
+ test "StateMachine: batch_elements_max" {
2831
+ const Operation = vsr.tigerbeetle.Operation;
2832
+
2833
+ const events_max: u32 = @divExact(
2834
+ constants.message_body_size_max,
2835
+ @max(@sizeOf(Account), @sizeOf(Transfer)),
2836
+ );
2837
+
2838
+ // No multi-batch encode.
2839
+ try testing.expectEqual(events_max, Operation.deprecated_create_accounts_unbatched.event_max(
2840
+ constants.message_body_size_max,
2841
+ ));
2842
+ try testing.expectEqual(events_max, Operation.deprecated_lookup_accounts_unbatched.event_max(
2843
+ constants.message_body_size_max,
2844
+ ));
2845
+ try testing.expectEqual(events_max, Operation.deprecated_create_transfers_unbatched.event_max(
2846
+ constants.message_body_size_max,
2847
+ ));
2848
+ try testing.expectEqual(events_max, Operation.deprecated_lookup_transfers_unbatched.event_max(
2849
+ constants.message_body_size_max,
2850
+ ));
2851
+
2852
+ // Multi-batch encoded (the size corresponding to one element is occupied by the trailer).
2853
+ try testing.expectEqual(events_max - 1, Operation.create_accounts.event_max(
2854
+ constants.message_body_size_max,
2855
+ ));
2856
+ try testing.expectEqual(events_max - 1, Operation.create_transfers.event_max(
2857
+ constants.message_body_size_max,
2858
+ ));
2859
+ try testing.expectEqual(events_max - 1, Operation.lookup_accounts.event_max(
2860
+ constants.message_body_size_max,
2861
+ ));
2862
+ try testing.expectEqual(events_max - 1, Operation.lookup_transfers.event_max(
2863
+ constants.message_body_size_max,
2864
+ ));
2865
+ }
2866
+
2867
+ // Tests the input validation logic for both multi-batch encoded messages and
2868
+ // the former single-batch format.
2869
+ test "StateMachine: input_valid" {
2870
+ const allocator = std.testing.allocator;
2871
+ const input = try allocator.alignedAlloc(u8, 16, 2 * constants.message_body_size_max);
2872
+ defer allocator.free(input);
2873
+
2874
+ const build_input = struct {
2875
+ fn build_input(buffer: []align(16) u8, options: struct {
2876
+ operation: TestContext.StateMachine.Operation,
2877
+ event_count: u32,
2878
+ }) []align(16) const u8 {
2879
+ const event_size = options.operation.event_size();
2880
+ const payload_size: u32 = options.event_count * event_size;
2881
+ if (options.operation.is_multi_batch()) {
2882
+ var body_encoder = vsr.multi_batch.MultiBatchEncoder.init(buffer, .{
2883
+ .element_size = event_size,
2884
+ });
2885
+ assert(payload_size <= body_encoder.writable().?.len);
2886
+ body_encoder.add(payload_size);
2887
+ const bytes_written = body_encoder.finish();
2888
+ assert(bytes_written > 0);
2889
+ return buffer[0..bytes_written];
2890
+ }
2891
+
2892
+ return buffer[0..payload_size];
2893
+ }
2894
+ }.build_input;
2895
+
2896
+ var context: TestContext = undefined;
2897
+ try context.init(std.testing.allocator);
2898
+ defer context.deinit(std.testing.allocator);
2899
+
2900
+ const operations = std.enums.values(TestContext.StateMachine.Operation);
2901
+ for (operations) |operation| {
2902
+ if (operation == .pulse) continue;
2903
+ const event_size = operation.event_size();
2904
+ maybe(event_size == 0);
2905
+
2906
+ const event_min: u32, const event_max: u32 = limits: {
2907
+ if (event_size == 0) {
2908
+ break :limits .{ 0, 0 };
2909
+ }
2910
+ if (!operation.is_batchable()) {
2911
+ break :limits .{ 1, 1 };
2912
+ }
2913
+ break :limits .{
2914
+ 0,
2915
+ operation.event_max(context.state_machine.batch_size_limit),
2916
+ };
2917
+ };
2918
+ assert(event_min <= event_max);
2919
+
2920
+ try std.testing.expect(context.state_machine.input_valid(
2921
+ operation,
2922
+ build_input(input, .{
2923
+ .event_count = 0,
2924
+ .operation = operation,
2925
+ }),
2926
+ ) == (event_min == 0));
2927
+ if (event_size == 0) {
2928
+ assert(event_min == 0);
2929
+ assert(event_max == 0);
2930
+ continue;
2931
+ }
2932
+
2933
+ try std.testing.expect(context.state_machine.input_valid(
2934
+ operation,
2935
+ build_input(input, .{
2936
+ .event_count = 1,
2937
+ .operation = operation,
2938
+ }),
2939
+ ));
2940
+ try std.testing.expect(context.state_machine.input_valid(
2941
+ operation,
2942
+ build_input(input, .{
2943
+ .event_count = event_max,
2944
+ .operation = operation,
2945
+ }),
2946
+ ));
2947
+ const too_much_data = build_input(input, .{
2948
+ .event_count = event_max + 1,
2949
+ .operation = operation,
2950
+ });
2951
+ if (too_much_data.len < constants.message_body_size_max) {
2952
+ try std.testing.expect(!context.state_machine.input_valid(
2953
+ operation,
2954
+ too_much_data,
2955
+ ));
2956
+ } else {
2957
+ // Don't test input larger than the message body limit, since input_valid()
2958
+ // would panic on an assert.
2959
+ }
2960
+ }
2961
+ }
2962
+
2963
+ // Tests multi-batched query filters.
2964
+ // Multi-batch filters are valid as long as the sum of `filter.limit` stays within the maximum
2965
+ // number of results that can fit in the reply message.
2966
+ test "StateMachine: query multi-batch input_valid" {
2967
+ const allocator = std.testing.allocator;
2968
+ const input = try allocator.alignedAlloc(u8, 16, 2 * constants.message_body_size_max);
2969
+ defer allocator.free(input);
2970
+
2971
+ var context: TestContext = undefined;
2972
+ try context.init(std.testing.allocator);
2973
+ defer context.deinit(std.testing.allocator);
2974
+
2975
+ const build_input = struct {
2976
+ fn build_input(
2977
+ operation: TestContext.StateMachine.Operation,
2978
+ limits: []const u32,
2979
+ buffer: []align(16) u8,
2980
+ ) []align(16) const u8 {
2981
+ switch (operation) {
2982
+ .get_account_transfers,
2983
+ .get_account_balances,
2984
+ => {
2985
+ var body_encoder = vsr.multi_batch.MultiBatchEncoder.init(buffer, .{
2986
+ .element_size = @sizeOf(AccountFilter),
2987
+ });
2988
+ if (limits.len == 0) body_encoder.add(0) else for (limits) |limit| {
2989
+ const batch: []u8 = body_encoder.writable().?;
2990
+ const filter: *AccountFilter = @alignCast(std.mem.bytesAsValue(
2991
+ AccountFilter,
2992
+ batch[0..@sizeOf(AccountFilter)],
2993
+ ));
2994
+ filter.* = .{
2995
+ .account_id = 0,
2996
+ .user_data_128 = 0,
2997
+ .user_data_64 = 0,
2998
+ .user_data_32 = 0,
2999
+ .code = 0,
3000
+ .timestamp_min = 0,
3001
+ .timestamp_max = 0,
3002
+ .limit = limit,
3003
+ .flags = .{
3004
+ .debits = false,
3005
+ .credits = false,
3006
+ .reversed = false,
3007
+ },
3008
+ };
3009
+ body_encoder.add(@sizeOf(AccountFilter));
3010
+ }
3011
+ return buffer[0..body_encoder.finish()];
3012
+ },
3013
+ .query_accounts,
3014
+ .query_transfers,
3015
+ => {
3016
+ var body_encoder = vsr.multi_batch.MultiBatchEncoder.init(buffer, .{
3017
+ .element_size = @sizeOf(QueryFilter),
3018
+ });
3019
+ if (limits.len == 0) body_encoder.add(0) else for (limits) |limit| {
3020
+ const batch: []u8 = body_encoder.writable().?;
3021
+ const filter: *QueryFilter = @alignCast(std.mem.bytesAsValue(
3022
+ QueryFilter,
3023
+ batch[0..@sizeOf(QueryFilter)],
3024
+ ));
3025
+ filter.* = .{
3026
+ .user_data_128 = 0,
3027
+ .user_data_64 = 0,
3028
+ .user_data_32 = 0,
3029
+ .code = 0,
3030
+ .ledger = 0,
3031
+ .timestamp_min = 0,
3032
+ .timestamp_max = 0,
3033
+ .limit = limit,
3034
+ .flags = .{
3035
+ .reversed = false,
3036
+ },
3037
+ };
3038
+ body_encoder.add(@sizeOf(QueryFilter));
3039
+ }
3040
+ return buffer[0..body_encoder.finish()];
3041
+ },
3042
+ else => unreachable,
3043
+ }
3044
+ }
3045
+ }.build_input;
3046
+
3047
+ const operations = &[_]TestContext.StateMachine.Operation{
3048
+ .get_account_transfers,
3049
+ .get_account_balances,
3050
+ .query_accounts,
3051
+ .query_transfers,
3052
+ };
3053
+
3054
+ for (operations) |operation| {
3055
+ const batch_max = operation.result_max(context.state_machine.batch_size_limit);
3056
+
3057
+ // Valid inputs:
3058
+ try std.testing.expect(context.state_machine.input_valid(
3059
+ operation,
3060
+ build_input(operation, &.{0}, input),
3061
+ ));
3062
+ try std.testing.expect(context.state_machine.input_valid(
3063
+ operation,
3064
+ build_input(operation, &.{ 0, 0 }, input),
3065
+ ));
3066
+ try std.testing.expect(context.state_machine.input_valid(
3067
+ operation,
3068
+ build_input(operation, &.{1}, input),
3069
+ ));
3070
+ try std.testing.expect(context.state_machine.input_valid(
3071
+ operation,
3072
+ build_input(operation, &.{ 1, 1, 1 }, input),
3073
+ ));
3074
+ try std.testing.expect(context.state_machine.input_valid(
3075
+ operation,
3076
+ build_input(operation, &.{batch_max}, input),
3077
+ ));
3078
+ try std.testing.expect(context.state_machine.input_valid(
3079
+ operation,
3080
+ build_input(operation, &.{ 0, batch_max }, input),
3081
+ ));
3082
+ try std.testing.expect(context.state_machine.input_valid(
3083
+ operation,
3084
+ build_input(operation, &.{ 0, 1, batch_max - 1 }, input),
3085
+ ));
3086
+ try std.testing.expect(context.state_machine.input_valid(
3087
+ operation,
3088
+ build_input(operation, &.{ 1, 1, batch_max - 2 }, input),
3089
+ ));
3090
+ try std.testing.expect(context.state_machine.input_valid(
3091
+ operation,
3092
+ build_input(operation, &.{
3093
+ @divFloor(batch_max, 2),
3094
+ stdx.div_ceil(batch_max, 2),
3095
+ }, input),
3096
+ ));
3097
+ try std.testing.expect(context.state_machine.input_valid(
3098
+ operation,
3099
+ build_input(operation, &.{std.math.maxInt(u32)}, input),
3100
+ ));
3101
+
3102
+ // Invalid inputs:
3103
+ try std.testing.expect(!context.state_machine.input_valid(
3104
+ operation,
3105
+ build_input(operation, &.{}, input),
3106
+ ));
3107
+ try std.testing.expect(!context.state_machine.input_valid(
3108
+ operation,
3109
+ build_input(operation, &.{ 1, batch_max }, input),
3110
+ ));
3111
+ try std.testing.expect(!context.state_machine.input_valid(
3112
+ operation,
3113
+ build_input(operation, &.{ 1, std.math.maxInt(u32) }, input),
3114
+ ));
3115
+ try std.testing.expect(!context.state_machine.input_valid(
3116
+ operation,
3117
+ build_input(operation, &.{ batch_max, batch_max }, input),
3118
+ ));
3119
+ try std.testing.expect(!context.state_machine.input_valid(
3120
+ operation,
3121
+ build_input(operation, &.{
3122
+ @divFloor(batch_max, 2),
3123
+ stdx.div_ceil(batch_max, 2),
3124
+ 1,
3125
+ }, input),
3126
+ ));
3127
+ }
3128
+ }