tigerbeetle 0.0.40 → 0.17.8

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 (293) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE +0 -25
  3. data/README.md +670 -80
  4. data/docs/migration.md +201 -0
  5. data/sig/tigerbeetle.rbs +271 -0
  6. data/src/ext/tigerbeetle/extconf.rb +47 -0
  7. data/src/ext/tigerbeetle/lib/aarch64-linux-gnu.2.27/libtb_client.so +0 -0
  8. data/src/ext/tigerbeetle/lib/aarch64-linux-musl/libtb_client.so +0 -0
  9. data/src/ext/tigerbeetle/lib/aarch64-macos/libtb_client.dylib +0 -0
  10. data/src/ext/tigerbeetle/lib/x86_64-linux-gnu.2.27/libtb_client.so +0 -0
  11. data/src/ext/tigerbeetle/lib/x86_64-linux-musl/libtb_client.so +0 -0
  12. data/src/ext/tigerbeetle/lib/x86_64-macos/libtb_client.dylib +0 -0
  13. data/src/ext/tigerbeetle/lib/x86_64-windows/tb_client.dll +0 -0
  14. data/src/ext/tigerbeetle/rb_tb_gen.h +458 -0
  15. data/{ext/tb_client/tigerbeetle/src/clients/rust/assets → src/ext/tigerbeetle}/tb_client.h +18 -16
  16. data/src/ext/tigerbeetle/tigerbeetle.c +310 -0
  17. data/src/tigerbeetle/bindings.rb +347 -0
  18. data/src/tigerbeetle/client.rb +129 -0
  19. data/src/tigerbeetle/completion_dispatcher.rb +108 -0
  20. data/src/tigerbeetle/id.rb +40 -0
  21. data/src/tigerbeetle/tb.rb +3 -0
  22. data/src/tigerbeetle/version.rb +3 -0
  23. data/src/tigerbeetle.rb +39 -0
  24. metadata +33 -350
  25. data/CHANGELOG.md +0 -162
  26. data/ext/tb_client/extconf.rb +0 -41
  27. data/ext/tb_client/tigerbeetle/LICENSE +0 -177
  28. data/ext/tb_client/tigerbeetle/build.zig +0 -2296
  29. data/ext/tb_client/tigerbeetle/src/aof.zig +0 -1000
  30. data/ext/tb_client/tigerbeetle/src/build/fetch.zig +0 -112
  31. data/ext/tb_client/tigerbeetle/src/build_multiversion.zig +0 -808
  32. data/ext/tb_client/tigerbeetle/src/cdc/amqp/protocol.zig +0 -1283
  33. data/ext/tb_client/tigerbeetle/src/cdc/amqp/spec.zig +0 -1704
  34. data/ext/tb_client/tigerbeetle/src/cdc/amqp/types.zig +0 -341
  35. data/ext/tb_client/tigerbeetle/src/cdc/amqp.zig +0 -1450
  36. data/ext/tb_client/tigerbeetle/src/cdc/runner.zig +0 -1659
  37. data/ext/tb_client/tigerbeetle/src/clients/c/samples/main.c +0 -406
  38. data/ext/tb_client/tigerbeetle/src/clients/c/tb_client/context.zig +0 -1092
  39. data/ext/tb_client/tigerbeetle/src/clients/c/tb_client/echo_client.zig +0 -286
  40. data/ext/tb_client/tigerbeetle/src/clients/c/tb_client/packet.zig +0 -158
  41. data/ext/tb_client/tigerbeetle/src/clients/c/tb_client/signal.zig +0 -229
  42. data/ext/tb_client/tigerbeetle/src/clients/c/tb_client/signal_fuzz.zig +0 -110
  43. data/ext/tb_client/tigerbeetle/src/clients/c/tb_client.h +0 -386
  44. data/ext/tb_client/tigerbeetle/src/clients/c/tb_client.zig +0 -34
  45. data/ext/tb_client/tigerbeetle/src/clients/c/tb_client_exports.zig +0 -281
  46. data/ext/tb_client/tigerbeetle/src/clients/c/tb_client_header.zig +0 -312
  47. data/ext/tb_client/tigerbeetle/src/clients/c/tb_client_header_test.zig +0 -138
  48. data/ext/tb_client/tigerbeetle/src/clients/c/test.zig +0 -466
  49. data/ext/tb_client/tigerbeetle/src/clients/docs_samples.zig +0 -157
  50. data/ext/tb_client/tigerbeetle/src/clients/docs_types.zig +0 -90
  51. data/ext/tb_client/tigerbeetle/src/clients/dotnet/ci.zig +0 -203
  52. data/ext/tb_client/tigerbeetle/src/clients/dotnet/docs.zig +0 -79
  53. data/ext/tb_client/tigerbeetle/src/clients/dotnet/dotnet_bindings.zig +0 -542
  54. data/ext/tb_client/tigerbeetle/src/clients/go/ci.zig +0 -109
  55. data/ext/tb_client/tigerbeetle/src/clients/go/docs.zig +0 -86
  56. data/ext/tb_client/tigerbeetle/src/clients/go/go_bindings.zig +0 -370
  57. data/ext/tb_client/tigerbeetle/src/clients/go/pkg/native/tb_client.h +0 -386
  58. data/ext/tb_client/tigerbeetle/src/clients/java/ci.zig +0 -167
  59. data/ext/tb_client/tigerbeetle/src/clients/java/docs.zig +0 -126
  60. data/ext/tb_client/tigerbeetle/src/clients/java/java_bindings.zig +0 -996
  61. data/ext/tb_client/tigerbeetle/src/clients/java/src/client.zig +0 -748
  62. data/ext/tb_client/tigerbeetle/src/clients/java/src/jni.zig +0 -3238
  63. data/ext/tb_client/tigerbeetle/src/clients/java/src/jni_tests.zig +0 -1718
  64. data/ext/tb_client/tigerbeetle/src/clients/java/src/jni_thread_cleaner.zig +0 -190
  65. data/ext/tb_client/tigerbeetle/src/clients/node/ci.zig +0 -104
  66. data/ext/tb_client/tigerbeetle/src/clients/node/docs.zig +0 -75
  67. data/ext/tb_client/tigerbeetle/src/clients/node/node.zig +0 -522
  68. data/ext/tb_client/tigerbeetle/src/clients/node/node_bindings.zig +0 -267
  69. data/ext/tb_client/tigerbeetle/src/clients/node/src/c.zig +0 -3
  70. data/ext/tb_client/tigerbeetle/src/clients/node/src/translate.zig +0 -379
  71. data/ext/tb_client/tigerbeetle/src/clients/python/ci.zig +0 -131
  72. data/ext/tb_client/tigerbeetle/src/clients/python/docs.zig +0 -63
  73. data/ext/tb_client/tigerbeetle/src/clients/python/python_bindings.zig +0 -588
  74. data/ext/tb_client/tigerbeetle/src/clients/rust/ci.zig +0 -73
  75. data/ext/tb_client/tigerbeetle/src/clients/rust/docs.zig +0 -106
  76. data/ext/tb_client/tigerbeetle/src/clients/rust/rust_bindings.zig +0 -305
  77. data/ext/tb_client/tigerbeetle/src/config.zig +0 -296
  78. data/ext/tb_client/tigerbeetle/src/constants.zig +0 -790
  79. data/ext/tb_client/tigerbeetle/src/copyhound.zig +0 -202
  80. data/ext/tb_client/tigerbeetle/src/counting_allocator.zig +0 -72
  81. data/ext/tb_client/tigerbeetle/src/direction.zig +0 -120
  82. data/ext/tb_client/tigerbeetle/src/docs_website/build.zig +0 -158
  83. data/ext/tb_client/tigerbeetle/src/docs_website/src/content.zig +0 -156
  84. data/ext/tb_client/tigerbeetle/src/docs_website/src/docs.zig +0 -252
  85. data/ext/tb_client/tigerbeetle/src/docs_website/src/file_checker.zig +0 -313
  86. data/ext/tb_client/tigerbeetle/src/docs_website/src/html.zig +0 -87
  87. data/ext/tb_client/tigerbeetle/src/docs_website/src/page_writer.zig +0 -63
  88. data/ext/tb_client/tigerbeetle/src/docs_website/src/redirects.zig +0 -47
  89. data/ext/tb_client/tigerbeetle/src/docs_website/src/search_index_writer.zig +0 -28
  90. data/ext/tb_client/tigerbeetle/src/docs_website/src/service_worker_writer.zig +0 -61
  91. data/ext/tb_client/tigerbeetle/src/docs_website/src/single_page_writer.zig +0 -169
  92. data/ext/tb_client/tigerbeetle/src/docs_website/src/website.zig +0 -46
  93. data/ext/tb_client/tigerbeetle/src/ewah.zig +0 -445
  94. data/ext/tb_client/tigerbeetle/src/ewah_benchmark.zig +0 -128
  95. data/ext/tb_client/tigerbeetle/src/ewah_fuzz.zig +0 -171
  96. data/ext/tb_client/tigerbeetle/src/fuzz_tests.zig +0 -179
  97. data/ext/tb_client/tigerbeetle/src/integration_tests.zig +0 -662
  98. data/ext/tb_client/tigerbeetle/src/io/common.zig +0 -155
  99. data/ext/tb_client/tigerbeetle/src/io/darwin.zig +0 -1093
  100. data/ext/tb_client/tigerbeetle/src/io/linux.zig +0 -1880
  101. data/ext/tb_client/tigerbeetle/src/io/test.zig +0 -1005
  102. data/ext/tb_client/tigerbeetle/src/io/windows.zig +0 -1598
  103. data/ext/tb_client/tigerbeetle/src/io.zig +0 -34
  104. data/ext/tb_client/tigerbeetle/src/iops.zig +0 -134
  105. data/ext/tb_client/tigerbeetle/src/list.zig +0 -236
  106. data/ext/tb_client/tigerbeetle/src/lsm/binary_search.zig +0 -848
  107. data/ext/tb_client/tigerbeetle/src/lsm/binary_search_benchmark.zig +0 -179
  108. data/ext/tb_client/tigerbeetle/src/lsm/cache_map.zig +0 -424
  109. data/ext/tb_client/tigerbeetle/src/lsm/cache_map_fuzz.zig +0 -420
  110. data/ext/tb_client/tigerbeetle/src/lsm/compaction.zig +0 -2114
  111. data/ext/tb_client/tigerbeetle/src/lsm/composite_key.zig +0 -185
  112. data/ext/tb_client/tigerbeetle/src/lsm/forest.zig +0 -1146
  113. data/ext/tb_client/tigerbeetle/src/lsm/forest_fuzz.zig +0 -1102
  114. data/ext/tb_client/tigerbeetle/src/lsm/forest_table_iterator.zig +0 -200
  115. data/ext/tb_client/tigerbeetle/src/lsm/groove.zig +0 -1495
  116. data/ext/tb_client/tigerbeetle/src/lsm/k_way_merge.zig +0 -739
  117. data/ext/tb_client/tigerbeetle/src/lsm/k_way_merge_benchmark.zig +0 -166
  118. data/ext/tb_client/tigerbeetle/src/lsm/manifest.zig +0 -754
  119. data/ext/tb_client/tigerbeetle/src/lsm/manifest_level.zig +0 -1294
  120. data/ext/tb_client/tigerbeetle/src/lsm/manifest_level_fuzz.zig +0 -510
  121. data/ext/tb_client/tigerbeetle/src/lsm/manifest_log.zig +0 -1241
  122. data/ext/tb_client/tigerbeetle/src/lsm/manifest_log_fuzz.zig +0 -628
  123. data/ext/tb_client/tigerbeetle/src/lsm/node_pool.zig +0 -247
  124. data/ext/tb_client/tigerbeetle/src/lsm/scan_buffer.zig +0 -116
  125. data/ext/tb_client/tigerbeetle/src/lsm/scan_builder.zig +0 -543
  126. data/ext/tb_client/tigerbeetle/src/lsm/scan_fuzz.zig +0 -938
  127. data/ext/tb_client/tigerbeetle/src/lsm/scan_lookup.zig +0 -293
  128. data/ext/tb_client/tigerbeetle/src/lsm/scan_merge.zig +0 -359
  129. data/ext/tb_client/tigerbeetle/src/lsm/scan_range.zig +0 -99
  130. data/ext/tb_client/tigerbeetle/src/lsm/scan_state.zig +0 -17
  131. data/ext/tb_client/tigerbeetle/src/lsm/scan_tree.zig +0 -962
  132. data/ext/tb_client/tigerbeetle/src/lsm/schema.zig +0 -617
  133. data/ext/tb_client/tigerbeetle/src/lsm/scratch_memory.zig +0 -84
  134. data/ext/tb_client/tigerbeetle/src/lsm/segmented_array.zig +0 -1500
  135. data/ext/tb_client/tigerbeetle/src/lsm/segmented_array_benchmark.zig +0 -149
  136. data/ext/tb_client/tigerbeetle/src/lsm/segmented_array_fuzz.zig +0 -7
  137. data/ext/tb_client/tigerbeetle/src/lsm/set_associative_cache.zig +0 -865
  138. data/ext/tb_client/tigerbeetle/src/lsm/table.zig +0 -607
  139. data/ext/tb_client/tigerbeetle/src/lsm/table_memory.zig +0 -843
  140. data/ext/tb_client/tigerbeetle/src/lsm/table_value_iterator.zig +0 -90
  141. data/ext/tb_client/tigerbeetle/src/lsm/timestamp_range.zig +0 -40
  142. data/ext/tb_client/tigerbeetle/src/lsm/tree.zig +0 -629
  143. data/ext/tb_client/tigerbeetle/src/lsm/tree_fuzz.zig +0 -933
  144. data/ext/tb_client/tigerbeetle/src/lsm/zig_zag_merge.zig +0 -534
  145. data/ext/tb_client/tigerbeetle/src/message_buffer.zig +0 -469
  146. data/ext/tb_client/tigerbeetle/src/message_bus.zig +0 -1219
  147. data/ext/tb_client/tigerbeetle/src/message_bus_fuzz.zig +0 -936
  148. data/ext/tb_client/tigerbeetle/src/message_pool.zig +0 -343
  149. data/ext/tb_client/tigerbeetle/src/multiversion.zig +0 -2195
  150. data/ext/tb_client/tigerbeetle/src/queue.zig +0 -390
  151. data/ext/tb_client/tigerbeetle/src/repl/completion.zig +0 -201
  152. data/ext/tb_client/tigerbeetle/src/repl/parser.zig +0 -1356
  153. data/ext/tb_client/tigerbeetle/src/repl/terminal.zig +0 -496
  154. data/ext/tb_client/tigerbeetle/src/repl.zig +0 -1034
  155. data/ext/tb_client/tigerbeetle/src/scripts/amqp.zig +0 -973
  156. data/ext/tb_client/tigerbeetle/src/scripts/cfo.zig +0 -1866
  157. data/ext/tb_client/tigerbeetle/src/scripts/changelog.zig +0 -304
  158. data/ext/tb_client/tigerbeetle/src/scripts/ci.zig +0 -227
  159. data/ext/tb_client/tigerbeetle/src/scripts/client_readmes.zig +0 -658
  160. data/ext/tb_client/tigerbeetle/src/scripts/devhub.zig +0 -466
  161. data/ext/tb_client/tigerbeetle/src/scripts/release.zig +0 -1058
  162. data/ext/tb_client/tigerbeetle/src/scripts.zig +0 -105
  163. data/ext/tb_client/tigerbeetle/src/shell.zig +0 -1195
  164. data/ext/tb_client/tigerbeetle/src/stack.zig +0 -260
  165. data/ext/tb_client/tigerbeetle/src/state_machine/auditor.zig +0 -911
  166. data/ext/tb_client/tigerbeetle/src/state_machine/workload.zig +0 -2079
  167. data/ext/tb_client/tigerbeetle/src/state_machine.zig +0 -4872
  168. data/ext/tb_client/tigerbeetle/src/state_machine_fuzz.zig +0 -288
  169. data/ext/tb_client/tigerbeetle/src/state_machine_tests.zig +0 -3128
  170. data/ext/tb_client/tigerbeetle/src/static_allocator.zig +0 -82
  171. data/ext/tb_client/tigerbeetle/src/stdx/bit_set.zig +0 -157
  172. data/ext/tb_client/tigerbeetle/src/stdx/bounded_array.zig +0 -292
  173. data/ext/tb_client/tigerbeetle/src/stdx/debug.zig +0 -65
  174. data/ext/tb_client/tigerbeetle/src/stdx/flags.zig +0 -1414
  175. data/ext/tb_client/tigerbeetle/src/stdx/huge_page_allocator.zig +0 -115
  176. data/ext/tb_client/tigerbeetle/src/stdx/mlock.zig +0 -92
  177. data/ext/tb_client/tigerbeetle/src/stdx/prng.zig +0 -677
  178. data/ext/tb_client/tigerbeetle/src/stdx/radix.zig +0 -336
  179. data/ext/tb_client/tigerbeetle/src/stdx/ring_buffer.zig +0 -511
  180. data/ext/tb_client/tigerbeetle/src/stdx/sort_test.zig +0 -112
  181. data/ext/tb_client/tigerbeetle/src/stdx/stdx.zig +0 -1163
  182. data/ext/tb_client/tigerbeetle/src/stdx/testing/low_level_hash_vectors.zig +0 -142
  183. data/ext/tb_client/tigerbeetle/src/stdx/testing/snaptest.zig +0 -361
  184. data/ext/tb_client/tigerbeetle/src/stdx/time_units.zig +0 -275
  185. data/ext/tb_client/tigerbeetle/src/stdx/unshare.zig +0 -295
  186. data/ext/tb_client/tigerbeetle/src/stdx/vendored/aegis.zig +0 -436
  187. data/ext/tb_client/tigerbeetle/src/stdx/windows.zig +0 -48
  188. data/ext/tb_client/tigerbeetle/src/stdx/zipfian.zig +0 -402
  189. data/ext/tb_client/tigerbeetle/src/storage.zig +0 -489
  190. data/ext/tb_client/tigerbeetle/src/storage_fuzz.zig +0 -180
  191. data/ext/tb_client/tigerbeetle/src/testing/bench.zig +0 -146
  192. data/ext/tb_client/tigerbeetle/src/testing/cluster/grid_checker.zig +0 -53
  193. data/ext/tb_client/tigerbeetle/src/testing/cluster/journal_checker.zig +0 -61
  194. data/ext/tb_client/tigerbeetle/src/testing/cluster/manifest_checker.zig +0 -76
  195. data/ext/tb_client/tigerbeetle/src/testing/cluster/message_bus.zig +0 -110
  196. data/ext/tb_client/tigerbeetle/src/testing/cluster/network.zig +0 -412
  197. data/ext/tb_client/tigerbeetle/src/testing/cluster/state_checker.zig +0 -331
  198. data/ext/tb_client/tigerbeetle/src/testing/cluster/storage_checker.zig +0 -458
  199. data/ext/tb_client/tigerbeetle/src/testing/cluster.zig +0 -1198
  200. data/ext/tb_client/tigerbeetle/src/testing/exhaustigen.zig +0 -128
  201. data/ext/tb_client/tigerbeetle/src/testing/fixtures.zig +0 -181
  202. data/ext/tb_client/tigerbeetle/src/testing/fuzz.zig +0 -144
  203. data/ext/tb_client/tigerbeetle/src/testing/id.zig +0 -97
  204. data/ext/tb_client/tigerbeetle/src/testing/io.zig +0 -317
  205. data/ext/tb_client/tigerbeetle/src/testing/marks.zig +0 -126
  206. data/ext/tb_client/tigerbeetle/src/testing/packet_simulator.zig +0 -533
  207. data/ext/tb_client/tigerbeetle/src/testing/reply_sequence.zig +0 -154
  208. data/ext/tb_client/tigerbeetle/src/testing/state_machine.zig +0 -389
  209. data/ext/tb_client/tigerbeetle/src/testing/storage.zig +0 -1247
  210. data/ext/tb_client/tigerbeetle/src/testing/table.zig +0 -249
  211. data/ext/tb_client/tigerbeetle/src/testing/time.zig +0 -98
  212. data/ext/tb_client/tigerbeetle/src/testing/tmp_tigerbeetle.zig +0 -212
  213. data/ext/tb_client/tigerbeetle/src/testing/vortex/constants.zig +0 -26
  214. data/ext/tb_client/tigerbeetle/src/testing/vortex/faulty_network.zig +0 -579
  215. data/ext/tb_client/tigerbeetle/src/testing/vortex/java_driver/ci.zig +0 -39
  216. data/ext/tb_client/tigerbeetle/src/testing/vortex/logged_process.zig +0 -214
  217. data/ext/tb_client/tigerbeetle/src/testing/vortex/rust_driver/ci.zig +0 -34
  218. data/ext/tb_client/tigerbeetle/src/testing/vortex/supervisor.zig +0 -785
  219. data/ext/tb_client/tigerbeetle/src/testing/vortex/workload.zig +0 -543
  220. data/ext/tb_client/tigerbeetle/src/testing/vortex/zig_driver.zig +0 -181
  221. data/ext/tb_client/tigerbeetle/src/tidy.zig +0 -1449
  222. data/ext/tb_client/tigerbeetle/src/tigerbeetle/benchmark_driver.zig +0 -227
  223. data/ext/tb_client/tigerbeetle/src/tigerbeetle/benchmark_load.zig +0 -1069
  224. data/ext/tb_client/tigerbeetle/src/tigerbeetle/cli.zig +0 -1422
  225. data/ext/tb_client/tigerbeetle/src/tigerbeetle/inspect.zig +0 -1658
  226. data/ext/tb_client/tigerbeetle/src/tigerbeetle/inspect_integrity.zig +0 -518
  227. data/ext/tb_client/tigerbeetle/src/tigerbeetle/libtb_client.zig +0 -36
  228. data/ext/tb_client/tigerbeetle/src/tigerbeetle/main.zig +0 -646
  229. data/ext/tb_client/tigerbeetle/src/tigerbeetle.zig +0 -958
  230. data/ext/tb_client/tigerbeetle/src/time.zig +0 -236
  231. data/ext/tb_client/tigerbeetle/src/trace/event.zig +0 -745
  232. data/ext/tb_client/tigerbeetle/src/trace/statsd.zig +0 -462
  233. data/ext/tb_client/tigerbeetle/src/trace.zig +0 -556
  234. data/ext/tb_client/tigerbeetle/src/unit_tests.zig +0 -321
  235. data/ext/tb_client/tigerbeetle/src/vopr.zig +0 -1785
  236. data/ext/tb_client/tigerbeetle/src/vortex.zig +0 -101
  237. data/ext/tb_client/tigerbeetle/src/vsr/checkpoint_trailer.zig +0 -473
  238. data/ext/tb_client/tigerbeetle/src/vsr/checksum.zig +0 -208
  239. data/ext/tb_client/tigerbeetle/src/vsr/checksum_benchmark.zig +0 -43
  240. data/ext/tb_client/tigerbeetle/src/vsr/client.zig +0 -768
  241. data/ext/tb_client/tigerbeetle/src/vsr/client_replies.zig +0 -532
  242. data/ext/tb_client/tigerbeetle/src/vsr/client_sessions.zig +0 -338
  243. data/ext/tb_client/tigerbeetle/src/vsr/clock.zig +0 -1019
  244. data/ext/tb_client/tigerbeetle/src/vsr/fault_detector.zig +0 -279
  245. data/ext/tb_client/tigerbeetle/src/vsr/free_set.zig +0 -1381
  246. data/ext/tb_client/tigerbeetle/src/vsr/free_set_fuzz.zig +0 -315
  247. data/ext/tb_client/tigerbeetle/src/vsr/grid.zig +0 -1460
  248. data/ext/tb_client/tigerbeetle/src/vsr/grid_blocks_missing.zig +0 -757
  249. data/ext/tb_client/tigerbeetle/src/vsr/grid_scrubber.zig +0 -797
  250. data/ext/tb_client/tigerbeetle/src/vsr/journal.zig +0 -2586
  251. data/ext/tb_client/tigerbeetle/src/vsr/marzullo.zig +0 -308
  252. data/ext/tb_client/tigerbeetle/src/vsr/message_header.zig +0 -1777
  253. data/ext/tb_client/tigerbeetle/src/vsr/multi_batch.zig +0 -715
  254. data/ext/tb_client/tigerbeetle/src/vsr/multi_batch_fuzz.zig +0 -185
  255. data/ext/tb_client/tigerbeetle/src/vsr/repair_budget.zig +0 -333
  256. data/ext/tb_client/tigerbeetle/src/vsr/replica.zig +0 -12356
  257. data/ext/tb_client/tigerbeetle/src/vsr/replica_format.zig +0 -416
  258. data/ext/tb_client/tigerbeetle/src/vsr/replica_reformat.zig +0 -165
  259. data/ext/tb_client/tigerbeetle/src/vsr/replica_test.zig +0 -2928
  260. data/ext/tb_client/tigerbeetle/src/vsr/routing.zig +0 -1075
  261. data/ext/tb_client/tigerbeetle/src/vsr/superblock.zig +0 -1603
  262. data/ext/tb_client/tigerbeetle/src/vsr/superblock_fuzz.zig +0 -484
  263. data/ext/tb_client/tigerbeetle/src/vsr/superblock_quorums.zig +0 -405
  264. data/ext/tb_client/tigerbeetle/src/vsr/superblock_quorums_fuzz.zig +0 -355
  265. data/ext/tb_client/tigerbeetle/src/vsr/sync.zig +0 -29
  266. data/ext/tb_client/tigerbeetle/src/vsr.zig +0 -1727
  267. data/lib/tb_client/shared_lib.rb +0 -66
  268. data/lib/tb_client.rb +0 -282
  269. data/lib/tigerbeetle/account.rb +0 -38
  270. data/lib/tigerbeetle/account_balance.rb +0 -23
  271. data/lib/tigerbeetle/account_filter.rb +0 -31
  272. data/lib/tigerbeetle/atomic_counter.rb +0 -14
  273. data/lib/tigerbeetle/client.rb +0 -214
  274. data/lib/tigerbeetle/converters/account.rb +0 -63
  275. data/lib/tigerbeetle/converters/account_balance.rb +0 -31
  276. data/lib/tigerbeetle/converters/account_filter.rb +0 -32
  277. data/lib/tigerbeetle/converters/base.rb +0 -35
  278. data/lib/tigerbeetle/converters/create_accounts_result.rb +0 -21
  279. data/lib/tigerbeetle/converters/create_transfers_result.rb +0 -21
  280. data/lib/tigerbeetle/converters/query_filter.rb +0 -33
  281. data/lib/tigerbeetle/converters/time.rb +0 -23
  282. data/lib/tigerbeetle/converters/transfer.rb +0 -64
  283. data/lib/tigerbeetle/converters/uint_128.rb +0 -24
  284. data/lib/tigerbeetle/converters.rb +0 -12
  285. data/lib/tigerbeetle/error.rb +0 -4
  286. data/lib/tigerbeetle/id.rb +0 -30
  287. data/lib/tigerbeetle/platforms.rb +0 -9
  288. data/lib/tigerbeetle/query_filter.rb +0 -31
  289. data/lib/tigerbeetle/request.rb +0 -7
  290. data/lib/tigerbeetle/transfer.rb +0 -40
  291. data/lib/tigerbeetle/version.rb +0 -4
  292. data/lib/tigerbeetle.rb +0 -13
  293. data/tigerbeetle.gemspec +0 -60
@@ -1,2079 +0,0 @@
1
- //! The Workload drives an end-to-end test: from client requests, through consensus and the state
2
- //! machine, down to the storage engine, and back.
3
- //!
4
- //! The Workload constructs messages to create and query accounts and transfers, and validates the
5
- //! replies.
6
- //!
7
- //! Goals:
8
- //!
9
- //! * Run in a fixed amount of memory. (For long-running tests or performance testing).
10
- //! * Query and verify transfers arbitrarily far back. (To exercise the storage engine).
11
- //!
12
- //! Transfer Encoding:
13
- //!
14
- //! * `Transfer.id` is a deterministic, reversible permutation of an ascending index.
15
- //! * With the transfer's index as a seed, the Workload knows the eventual outcome of the transfer.
16
- //! * `Transfer.user_data` is a checksum of the remainder of the transfer's data
17
- //! (excluding `timestamp` and `user_data` itself). This helps `on_lookup_transfers` to
18
- //! validate its results.
19
- //!
20
- const std = @import("std");
21
- const assert = std.debug.assert;
22
-
23
- const stdx = @import("stdx");
24
- const maybe = stdx.maybe;
25
- const Ratio = stdx.PRNG.Ratio;
26
- const ratio = stdx.PRNG.ratio;
27
-
28
- const constants = @import("../constants.zig");
29
- const tb = @import("../tigerbeetle.zig");
30
- const vsr = @import("../vsr.zig");
31
- const accounting_auditor = @import("auditor.zig");
32
- const Auditor = accounting_auditor.AccountingAuditor;
33
- const IteratorForCreateType = accounting_auditor.IteratorForCreateType;
34
- const IdPermutation = @import("../testing/id.zig").IdPermutation;
35
- const TimestampRange = @import("../lsm/timestamp_range.zig").TimestampRange;
36
- const fuzz = @import("../testing/fuzz.zig");
37
-
38
- const PriorityQueue = std.PriorityQueue;
39
-
40
- const TransferOutcome = enum {
41
- /// The transfer is guaranteed to commit.
42
- /// For example, a single-phase transfer between valid accounts without balance limits.
43
- success,
44
- /// The transfer is invalid. For example, the `ledger` field is missing.
45
- failure,
46
- /// Due to races with timeouts or other transfers, the outcome of the transfer is uncertain.
47
- /// For example, post/void-pending transfers race with their timeout.
48
- unknown,
49
- };
50
-
51
- /// A Transfer generated from the plan is guaranteed to have a matching `outcome`, but it may use a
52
- /// different Method. (For example, `method=pending` may fall back to `method=single_phase` if the
53
- /// Auditor's pending transfer queue is full).
54
- const TransferPlan = struct {
55
- /// When false, send invalid payments that are guaranteed to be rejected with an error.
56
- valid: bool,
57
-
58
- /// When `limit` is set, at least one of the following is true:
59
- ///
60
- /// * the debit account has debits_must_not_exceed_credits
61
- /// * the credit account has credits_must_not_exceed_debits
62
- ///
63
- limit: bool,
64
-
65
- method: Method,
66
-
67
- const Method = enum {
68
- single_phase,
69
- pending,
70
- post_pending,
71
- void_pending,
72
- };
73
-
74
- fn outcome(self: TransferPlan) TransferOutcome {
75
- if (!self.valid) return .failure;
76
- if (self.limit) return .unknown;
77
- return switch (self.method) {
78
- .single_phase, .pending => .success,
79
- .post_pending, .void_pending => .unknown,
80
- };
81
- }
82
- };
83
-
84
- const TransferTemplate = struct {
85
- ledger: u32,
86
- result: accounting_auditor.CreateTransferResultSet,
87
- };
88
-
89
- const TransferBatchQueue = PriorityQueue(TransferBatch, void, struct {
90
- /// Ascending order.
91
- fn compare(_: void, a: TransferBatch, b: TransferBatch) std.math.Order {
92
- assert(a.min != b.min);
93
- assert(a.max != b.max);
94
- return std.math.order(a.min, b.min);
95
- }
96
- }.compare);
97
-
98
- const TransferBatch = struct {
99
- /// Index of the first transfer in the batch.
100
- min: usize,
101
- /// Index of the last transfer in the batch.
102
- max: usize,
103
- };
104
-
105
- /// Indexes: [valid:bool][limit:bool][method]
106
- const transfer_templates = table: {
107
- @setEvalBranchQuota(4_000);
108
-
109
- const SNGL = @intFromEnum(TransferPlan.Method.single_phase);
110
- const PEND = @intFromEnum(TransferPlan.Method.pending);
111
- const POST = @intFromEnum(TransferPlan.Method.post_pending);
112
- const VOID = @intFromEnum(TransferPlan.Method.void_pending);
113
- const Result = accounting_auditor.CreateTransferResultSet;
114
- const result = Result.init;
115
-
116
- const InitValues = std.enums.EnumFieldStruct(
117
- tb.CreateTransferResult.Ordered,
118
- bool,
119
- false,
120
- );
121
- const two_phase_ok: InitValues = .{
122
- .ok = true,
123
- .pending_transfer_already_posted = true,
124
- .pending_transfer_already_voided = true,
125
- .pending_transfer_expired = true,
126
- };
127
-
128
- const limits = result(.{
129
- .exceeds_credits = true,
130
- .exceeds_debits = true,
131
- });
132
-
133
- const either = struct {
134
- fn either(a: Result, b: Result) Result {
135
- var c = a;
136
- c.setUnion(b);
137
- return c;
138
- }
139
- }.either;
140
-
141
- const template = struct {
142
- fn template(ledger: u32, transfer_result: Result) TransferTemplate {
143
- return .{
144
- .ledger = ledger,
145
- .result = transfer_result,
146
- };
147
- }
148
- }.template;
149
-
150
- // [valid:bool][limit:bool][method]
151
- var templates: [2][2][std.meta.fields(TransferPlan.Method).len]TransferTemplate = undefined;
152
-
153
- // template(ledger, result)
154
- templates[0][0][SNGL] = template(0, result(.{ .ledger_must_not_be_zero = true }));
155
- templates[0][0][PEND] = template(0, result(.{ .ledger_must_not_be_zero = true }));
156
- templates[0][0][POST] = template(9, result(.{ .pending_transfer_has_different_ledger = true }));
157
- templates[0][0][VOID] = template(9, result(.{ .pending_transfer_has_different_ledger = true }));
158
-
159
- templates[0][1][SNGL] = template(0, result(.{ .ledger_must_not_be_zero = true }));
160
- templates[0][1][PEND] = template(0, result(.{ .ledger_must_not_be_zero = true }));
161
- templates[0][1][POST] = template(9, result(.{ .pending_transfer_has_different_ledger = true }));
162
- templates[0][1][VOID] = template(9, result(.{ .pending_transfer_has_different_ledger = true }));
163
-
164
- templates[1][0][SNGL] = template(1, result(.{ .ok = true }));
165
- templates[1][0][PEND] = template(1, result(.{ .ok = true }));
166
- templates[1][0][POST] = template(1, result(two_phase_ok));
167
- templates[1][0][VOID] = template(1, result(two_phase_ok));
168
-
169
- templates[1][1][SNGL] = template(1, either(limits, result(.{ .ok = true })));
170
- templates[1][1][PEND] = template(1, either(limits, result(.{ .ok = true })));
171
- templates[1][1][POST] = template(1, either(limits, result(two_phase_ok)));
172
- templates[1][1][VOID] = template(1, either(limits, result(two_phase_ok)));
173
-
174
- break :table templates;
175
- };
176
-
177
- pub fn WorkloadType(comptime AccountingStateMachine: type) type {
178
- const Operation = AccountingStateMachine.Operation;
179
-
180
- const Action = enum(u8) {
181
- create_accounts = @intFromEnum(Operation.create_accounts),
182
- create_transfers = @intFromEnum(Operation.create_transfers),
183
- lookup_accounts = @intFromEnum(Operation.lookup_accounts),
184
- lookup_transfers = @intFromEnum(Operation.lookup_transfers),
185
- get_account_transfers = @intFromEnum(Operation.get_account_transfers),
186
- get_account_balances = @intFromEnum(Operation.get_account_balances),
187
- query_accounts = @intFromEnum(Operation.query_accounts),
188
- query_transfers = @intFromEnum(Operation.query_transfers),
189
- get_change_events = @intFromEnum(Operation.get_change_events),
190
-
191
- deprecated_create_accounts_unbatched = @intFromEnum(
192
- Operation.deprecated_create_accounts_unbatched,
193
- ),
194
- deprecated_create_transfers_unbatched = @intFromEnum(
195
- Operation.deprecated_create_transfers_unbatched,
196
- ),
197
- deprecated_lookup_accounts_unbatched = @intFromEnum(
198
- Operation.deprecated_lookup_accounts_unbatched,
199
- ),
200
- deprecated_lookup_transfers_unbatched = @intFromEnum(
201
- Operation.deprecated_lookup_transfers_unbatched,
202
- ),
203
- deprecated_get_account_transfers_unbatched = @intFromEnum(
204
- Operation.deprecated_get_account_transfers_unbatched,
205
- ),
206
- deprecated_get_account_balances_unbatched = @intFromEnum(
207
- Operation.deprecated_get_account_balances_unbatched,
208
- ),
209
- deprecated_query_accounts_unbatched = @intFromEnum(
210
- Operation.deprecated_query_accounts_unbatched,
211
- ),
212
- deprecated_query_transfers_unbatched = @intFromEnum(
213
- Operation.deprecated_query_transfers_unbatched,
214
- ),
215
- };
216
-
217
- const Lookup = enum {
218
- /// Query a transfer that has either been committed or rejected.
219
- delivered,
220
- /// Query a transfer whose `create_transfers` is in-flight.
221
- sending,
222
- };
223
-
224
- return struct {
225
- const Workload = @This();
226
-
227
- pub const Options = OptionsType(AccountingStateMachine, Action, Lookup);
228
-
229
- prng: *stdx.PRNG,
230
- auditor: Auditor,
231
- options: Options,
232
-
233
- transfer_plan_seed: u64,
234
-
235
- /// Whether a `create_accounts` message has ever been sent.
236
- accounts_sent: bool = false,
237
-
238
- /// The index of the next transfer to send.
239
- transfers_sent: usize = 0,
240
-
241
- /// All transfers below this index have been delivered.
242
- /// Any transfers above this index that have been delivered are stored in
243
- /// `transfers_delivered_recently`.
244
- transfers_delivered_past: usize = 0,
245
-
246
- /// Track index ranges of `create_transfers` batches that have committed but are greater
247
- /// than or equal to `transfers_delivered_past` (which is still in-flight).
248
- transfers_delivered_recently: TransferBatchQueue,
249
-
250
- /// Track the number of pending transfers that have been sent but not committed.
251
- transfers_pending_in_flight: usize = 0,
252
-
253
- /// Transfers that succeeded and must result in `exists` when retried.
254
- transfers_retry_exists: std.ArrayListUnmanaged(tb.Transfer),
255
-
256
- /// IDs of transfers that failed with transient codes
257
- /// and must result in `id_already_failed` when retried.
258
- transfers_retry_failed: std.AutoArrayHashMapUnmanaged(u128, void),
259
-
260
- pub fn init(
261
- allocator: std.mem.Allocator,
262
- prng: *stdx.PRNG,
263
- options: Options,
264
- ) !Workload {
265
- assert(options.accounts_batch_size_span + options.accounts_batch_size_min <=
266
- AccountingStateMachine.batch_max.create_accounts);
267
- assert(options.accounts_batch_size_span >= 1);
268
- assert(options.transfers_batch_size_span + options.transfers_batch_size_min <=
269
- AccountingStateMachine.batch_max.create_transfers);
270
- assert(options.transfers_batch_size_span >= 1);
271
-
272
- var auditor = try Auditor.init(allocator, prng, options.auditor_options);
273
- errdefer auditor.deinit(allocator);
274
-
275
- var transfers_delivered_recently = TransferBatchQueue.init(allocator, {});
276
- errdefer transfers_delivered_recently.deinit();
277
- try transfers_delivered_recently.ensureTotalCapacity(
278
- options.auditor_options.client_count * constants.client_request_queue_max,
279
- );
280
-
281
- for (auditor.accounts, 0..) |*account, i| {
282
- const query_intersection =
283
- auditor.query_intersections[prng.index(auditor.query_intersections)];
284
-
285
- account.* = std.mem.zeroInit(tb.Account, .{
286
- .id = auditor.account_index_to_id(i),
287
- .user_data_64 = query_intersection.user_data_64,
288
- .user_data_32 = query_intersection.user_data_32,
289
- .code = query_intersection.code,
290
- .ledger = 1,
291
- });
292
-
293
- if (prng.chance(options.account_limit_probability)) {
294
- const b = prng.boolean();
295
- account.flags.debits_must_not_exceed_credits = b;
296
- account.flags.credits_must_not_exceed_debits = !b;
297
- }
298
-
299
- account.flags.history = prng.chance(options.account_history_probability);
300
- }
301
-
302
- var transfers_retry_failed: std.AutoArrayHashMapUnmanaged(u128, void) = .{};
303
- try transfers_retry_failed.ensureTotalCapacity(
304
- allocator,
305
- options.transfers_retry_failed_max,
306
- );
307
- errdefer transfers_retry_failed.deinit(allocator);
308
-
309
- var transfers_retry_exists: std.ArrayListUnmanaged(tb.Transfer) = try .initCapacity(
310
- allocator,
311
- options.transfers_retry_exists_max,
312
- );
313
- errdefer transfers_retry_exists.deinit(allocator);
314
-
315
- return .{
316
- .prng = prng,
317
- .auditor = auditor,
318
- .options = options,
319
- .transfer_plan_seed = prng.int(u64),
320
- .transfers_delivered_recently = transfers_delivered_recently,
321
- .transfers_retry_failed = transfers_retry_failed,
322
- .transfers_retry_exists = transfers_retry_exists,
323
- };
324
- }
325
-
326
- pub fn deinit(self: *Workload, allocator: std.mem.Allocator) void {
327
- self.auditor.deinit(allocator);
328
- self.transfers_delivered_recently.deinit();
329
- self.transfers_retry_failed.deinit(allocator);
330
- self.transfers_retry_exists.deinit(allocator);
331
- }
332
-
333
- pub fn done(self: *const Workload) bool {
334
- if (self.transfers_delivered_recently.len != 0) return false;
335
- return self.auditor.done();
336
- }
337
-
338
- /// A client may build multiple requests to queue up while another is in-flight.
339
- pub fn build_request(
340
- self: *Workload,
341
- client_index: usize,
342
- body_buffer: []align(@alignOf(vsr.Header)) u8,
343
- ) struct {
344
- operation: Operation,
345
- size: usize,
346
- } {
347
- assert(client_index < self.auditor.options.client_count);
348
- assert(body_buffer.len == constants.message_body_size_max);
349
-
350
- const action = action: {
351
- if (!self.accounts_sent and self.prng.boolean()) {
352
- // Early in the test make sure some accounts get created.
353
- self.accounts_sent = true;
354
- break :action .create_accounts;
355
- }
356
-
357
- break :action self.prng.enum_weighted(Action, self.options.operations);
358
- };
359
-
360
- const operation: Operation = @enumFromInt(@intFromEnum(action));
361
- const event_size: u32 = operation.event_size();
362
- const event_max: u32 = operation.event_max(self.options.batch_size_limit);
363
- assert(event_max > 0);
364
- assert(body_buffer.len >= event_size * event_max);
365
-
366
- const result_size: u32 = operation.result_size();
367
- const result_max = operation.result_max(self.options.batch_size_limit);
368
- assert(result_max > 0);
369
- assert(constants.message_body_size_max >=
370
- result_size * result_max);
371
-
372
- if (!operation.is_multi_batch()) {
373
- const size = self.build_request_batch(
374
- client_index,
375
- action,
376
- body_buffer,
377
- event_max,
378
- );
379
- assert(size <= body_buffer.len);
380
- return .{
381
- .operation = operation,
382
- .size = size,
383
- };
384
- }
385
- assert(operation.is_multi_batch());
386
-
387
- var body_encoder = vsr.multi_batch.MultiBatchEncoder.init(
388
- body_buffer[0..self.options.batch_size_limit],
389
- .{
390
- .element_size = event_size,
391
- },
392
- );
393
- var event_count: u32 = 0;
394
- var result_count: u32 = 0;
395
- for (0..self.options.multi_batch_per_request_limit) |_| {
396
- const writable = body_encoder.writable() orelse break;
397
- if (writable.len == 0) break;
398
-
399
- const event_count_remain: u32 =
400
- if (operation.is_batchable())
401
- event_max - event_count
402
- else
403
- 1;
404
- const batch_size = self.build_request_batch(
405
- client_index,
406
- action,
407
- writable,
408
- event_count_remain,
409
- );
410
- assert(batch_size <= writable.len);
411
-
412
- // Checking if the expected result will fit in the multi-batch reply.
413
- const reply_trailer_size: u32 = vsr.multi_batch.trailer_total_size(.{
414
- .element_size = result_size,
415
- .batch_count = body_encoder.batch_count + 1,
416
- });
417
- const result_count_expected: u32 =
418
- operation.result_count_expected(writable[0..batch_size]);
419
- const reply_message_size: u32 =
420
- ((result_count + result_count_expected) * result_size) + reply_trailer_size;
421
- if (reply_message_size > constants.message_body_size_max) {
422
- // For operations that produce 1:1 result per event
423
- // (e.g., `create_*` and `lookup_*`), this was already validated
424
- // when checking if `event_count` fits within the multi-batch request.
425
- assert(!operation.is_batchable());
426
- break;
427
- }
428
- assert(result_count + result_count_expected <= result_max);
429
-
430
- body_encoder.add(@intCast(batch_size));
431
- event_count += @intCast(@divExact(batch_size, event_size));
432
- assert(event_count <= event_max);
433
-
434
- result_count += result_count_expected;
435
- assert(result_count <= result_max);
436
-
437
- // Maybe single-batch request.
438
- if (body_encoder.batch_count == 1 and self.prng.boolean()) break;
439
- }
440
- maybe(event_count == 0);
441
- assert(result_count == 0 or event_count > 0);
442
- assert(body_encoder.batch_count > 0);
443
- assert(body_encoder.batch_count <= self.options.multi_batch_per_request_limit);
444
-
445
- const bytes_written = body_encoder.finish();
446
- assert(bytes_written <= self.options.batch_size_limit);
447
-
448
- return .{
449
- .operation = operation,
450
- .size = bytes_written,
451
- };
452
- }
453
-
454
- fn build_request_batch(
455
- self: *Workload,
456
- client_index: usize,
457
- action: Action,
458
- body: []u8,
459
- batch_limit: u32,
460
- ) usize {
461
- switch (action) {
462
- inline else => |action_comptime| {
463
- const operation_comptime = comptime std.enums.nameCast(
464
- Operation,
465
- action_comptime,
466
- );
467
- const Event = operation_comptime.EventType();
468
- const event_size: u32 = operation_comptime.event_size();
469
- const batchable: []Event = self.batch(
470
- Event,
471
- action_comptime,
472
- body,
473
- batch_limit,
474
- );
475
- assert(batchable.len <= batch_limit);
476
-
477
- const count = switch (action_comptime) {
478
- .create_accounts,
479
- .deprecated_create_accounts_unbatched,
480
- => self.build_create_accounts(
481
- client_index,
482
- batchable,
483
- ),
484
- .create_transfers,
485
- .deprecated_create_transfers_unbatched,
486
- => self.build_create_transfers(
487
- client_index,
488
- batchable,
489
- ),
490
- .lookup_accounts,
491
- .deprecated_lookup_accounts_unbatched,
492
- => self.build_lookup_accounts(batchable),
493
- .lookup_transfers,
494
- .deprecated_lookup_transfers_unbatched,
495
- => self.build_lookup_transfers(batchable),
496
- .get_account_transfers,
497
- .get_account_balances,
498
- .deprecated_get_account_transfers_unbatched,
499
- .deprecated_get_account_balances_unbatched,
500
- => self.build_get_account_filter(
501
- client_index,
502
- action_comptime,
503
- batchable,
504
- ),
505
- .query_accounts,
506
- .query_transfers,
507
- .deprecated_query_accounts_unbatched,
508
- .deprecated_query_transfers_unbatched,
509
- => self.build_query_filter(
510
- client_index,
511
- action_comptime,
512
- batchable,
513
- ),
514
- .get_change_events => self.build_get_change_events_filter(
515
- client_index,
516
- batchable,
517
- ),
518
- };
519
- assert(count <= batchable.len);
520
- assert(count <= batch_limit);
521
-
522
- const batch_size: usize = count * event_size;
523
- assert(batch_size <= body.len);
524
- maybe(batch_size == 0);
525
- return batch_size;
526
- },
527
- }
528
- }
529
-
530
- /// `on_reply` is called for replies in commit order.
531
- pub fn on_reply(
532
- self: *Workload,
533
- client_index: usize,
534
- operation: Operation,
535
- timestamp: u64,
536
- request_body: []const u8,
537
- reply_body: []const u8,
538
- ) void {
539
- assert(timestamp != 0);
540
- assert(request_body.len <= constants.message_body_size_max);
541
- assert(reply_body.len <= constants.message_body_size_max);
542
-
543
- if (!operation.is_multi_batch()) {
544
- return self.on_reply_batch(
545
- client_index,
546
- operation,
547
- timestamp,
548
- request_body,
549
- reply_body,
550
- );
551
- }
552
- assert(operation.is_multi_batch());
553
-
554
- const event_size: u32 = operation.event_size();
555
- const result_size: u32 = operation.result_size();
556
- var body_decoder = vsr.multi_batch.MultiBatchDecoder.init(request_body, .{
557
- .element_size = event_size,
558
- }) catch unreachable;
559
- assert(body_decoder.batch_count() > 0);
560
- var reply_decoder = vsr.multi_batch.MultiBatchDecoder.init(reply_body, .{
561
- .element_size = result_size,
562
- }) catch unreachable;
563
- assert(reply_decoder.batch_count() > 0);
564
- assert(body_decoder.batch_count() == reply_decoder.batch_count());
565
-
566
- const prepare_nanoseconds = struct {
567
- fn prepare_nanoseconds(
568
- operation_inner: Operation,
569
- input_len: usize,
570
- batch_size_limit: u32,
571
- ) u64 {
572
- return switch (operation_inner) {
573
- .pulse => Operation.create_transfers.event_max(
574
- batch_size_limit,
575
- ),
576
- .create_accounts => @divExact(input_len, @sizeOf(tb.Account)),
577
- .create_transfers => @divExact(input_len, @sizeOf(tb.Transfer)),
578
- .lookup_accounts => 0,
579
- .lookup_transfers => 0,
580
- .get_account_transfers => 0,
581
- .get_account_balances => 0,
582
- .query_accounts => 0,
583
- .query_transfers => 0,
584
- .get_change_events => 0,
585
- else => unreachable,
586
- };
587
- }
588
- }.prepare_nanoseconds;
589
- var batch_timestamp: u64 = timestamp - prepare_nanoseconds(
590
- operation,
591
- body_decoder.payload.len,
592
- self.options.batch_size_limit,
593
- );
594
- while (body_decoder.pop()) |batch_body| {
595
- const batch_reply = reply_decoder.pop().?;
596
- batch_timestamp += prepare_nanoseconds(
597
- operation,
598
- batch_body.len,
599
- self.options.batch_size_limit,
600
- );
601
- self.on_reply_batch(
602
- client_index,
603
- operation,
604
- batch_timestamp,
605
- batch_body,
606
- batch_reply,
607
- );
608
- }
609
- assert(reply_decoder.pop() == null);
610
- }
611
-
612
- pub fn on_reply_batch(
613
- self: *Workload,
614
- client_index: usize,
615
- operation: Operation,
616
- timestamp: u64,
617
- request_body: []const u8,
618
- reply_body: []const u8,
619
- ) void {
620
- switch (operation) {
621
- .create_accounts,
622
- .deprecated_create_accounts_unbatched,
623
- => self.auditor.on_create_accounts(
624
- client_index,
625
- timestamp,
626
- stdx.bytes_as_slice(.exact, tb.Account, request_body),
627
- stdx.bytes_as_slice(.exact, tb.CreateAccountsResult, reply_body),
628
- ),
629
- .create_transfers,
630
- .deprecated_create_transfers_unbatched,
631
- => self.on_create_transfers(
632
- client_index,
633
- timestamp,
634
- stdx.bytes_as_slice(.exact, tb.Transfer, request_body),
635
- stdx.bytes_as_slice(.exact, tb.CreateTransfersResult, reply_body),
636
- ),
637
- .lookup_accounts,
638
- .deprecated_lookup_accounts_unbatched,
639
- => self.auditor.on_lookup_accounts(
640
- client_index,
641
- timestamp,
642
- stdx.bytes_as_slice(.exact, u128, request_body),
643
- stdx.bytes_as_slice(.exact, tb.Account, reply_body),
644
- ),
645
- .lookup_transfers,
646
- .deprecated_lookup_transfers_unbatched,
647
- => self.on_lookup_transfers(
648
- client_index,
649
- timestamp,
650
- stdx.bytes_as_slice(.exact, u128, request_body),
651
- stdx.bytes_as_slice(.exact, tb.Transfer, reply_body),
652
- ),
653
- inline .get_account_transfers,
654
- .deprecated_get_account_transfers_unbatched,
655
- => |operation_comptime| self.on_get_account_transfers(
656
- operation_comptime,
657
- timestamp,
658
- stdx.bytes_as_slice(.exact, tb.AccountFilter, request_body),
659
- stdx.bytes_as_slice(.exact, tb.Transfer, reply_body),
660
- ),
661
- inline .get_account_balances,
662
- .deprecated_get_account_balances_unbatched,
663
- => |operation_comptime| self.on_get_account_balances(
664
- operation_comptime,
665
- timestamp,
666
- stdx.bytes_as_slice(.exact, tb.AccountFilter, request_body),
667
- stdx.bytes_as_slice(.exact, tb.AccountBalance, reply_body),
668
- ),
669
- inline .query_accounts,
670
- .deprecated_query_accounts_unbatched,
671
- => |operation_comptime| self.on_query(
672
- operation_comptime,
673
- timestamp,
674
- stdx.bytes_as_slice(.exact, tb.QueryFilter, request_body),
675
- stdx.bytes_as_slice(.exact, tb.Account, reply_body),
676
- ),
677
- inline .query_transfers,
678
- .deprecated_query_transfers_unbatched,
679
- => |operation_comptime| self.on_query(
680
- operation_comptime,
681
- timestamp,
682
- stdx.bytes_as_slice(.exact, tb.QueryFilter, request_body),
683
- stdx.bytes_as_slice(.exact, tb.Transfer, reply_body),
684
- ),
685
- .get_change_events => self.on_get_change_events(
686
- timestamp,
687
- stdx.bytes_as_slice(.exact, tb.ChangeEventsFilter, request_body),
688
- stdx.bytes_as_slice(.exact, tb.ChangeEvent, reply_body),
689
- ),
690
- //Not handled by the client.
691
- .pulse => unreachable,
692
- }
693
- }
694
-
695
- /// `on_pulse` is called for pulse operations in commit order.
696
- pub fn on_pulse(
697
- self: *Workload,
698
- operation: Operation,
699
- timestamp: u64,
700
- ) void {
701
- assert(timestamp != 0);
702
- assert(operation == .pulse);
703
-
704
- self.auditor.expire_pending_transfers(timestamp);
705
- }
706
-
707
- fn build_create_accounts(
708
- self: *Workload,
709
- client_index: usize,
710
- accounts: []tb.Account,
711
- ) usize {
712
- const results = self.auditor.expect_create_accounts(client_index);
713
- for (accounts, 0..) |*account, i| {
714
- const account_index = self.prng.index(self.auditor.accounts);
715
- account.* = self.auditor.accounts[account_index];
716
- account.debits_pending = 0;
717
- account.debits_posted = 0;
718
- account.credits_pending = 0;
719
- account.credits_posted = 0;
720
- account.timestamp = 0;
721
- results[i] = accounting_auditor.CreateAccountResultSet{};
722
-
723
- if (self.prng.chance(self.options.create_account_invalid_probability)) {
724
- account.ledger = 0;
725
- // The result depends on whether the id already exists:
726
- results[i].insert(.exists_with_different_ledger);
727
- results[i].insert(.ledger_must_not_be_zero);
728
- } else {
729
- if (!self.auditor.accounts_state[account_index].created) {
730
- results[i].insert(.ok);
731
- }
732
- // Even if the account doesn't exist yet, we may race another request.
733
- results[i].insert(.exists);
734
- }
735
- assert(results[i].count() > 0);
736
- }
737
- return accounts.len;
738
- }
739
-
740
- fn build_create_transfers(
741
- self: *Workload,
742
- client_index: usize,
743
- transfers: []tb.Transfer,
744
- ) usize {
745
- const results = self.auditor.expect_create_transfers(client_index);
746
- assert(results.len >= transfers.len);
747
- var transfers_count: usize = transfers.len;
748
- var i: usize = 0;
749
- while (i < transfers_count) {
750
- const transfer_index = self.transfers_sent;
751
- const transfer_plan = self.transfer_index_to_plan(transfer_index);
752
- const transfer_id = self.transfer_index_to_id(transfer_index);
753
- results[i] = self.build_transfer(
754
- transfer_id,
755
- transfer_plan,
756
- &transfers[i],
757
- ) orelse {
758
- // This transfer index can't be built; stop with what we have so far.
759
- // Hopefully it will be unblocked before the next `create_transfers`.
760
- transfers_count = i;
761
- break;
762
- };
763
-
764
- if (i != 0 and results[i].count() == 1 and results[i - 1].count() == 1) {
765
- // To support random `lookup_transfers`, linked transfers can't be planned.
766
- // Instead, link transfers opportunistically, when consecutive transfers can be
767
- // linked without altering any of their outcomes.
768
-
769
- if (results[i].contains(.ok) and results[i - 1].contains(.ok) and
770
- self.prng.chance(self.options.linked_valid_probability))
771
- {
772
- transfers[i - 1].flags.linked = true;
773
- }
774
-
775
- if (!results[i].contains(.ok) and !results[i - 1].contains(.ok) and
776
- self.prng.chance(self.options.linked_invalid_probability))
777
- {
778
- // Convert the previous transfer to a single-phase no-limit transfer, but
779
- // link it to the current transfer — it will still fail.
780
- const result_set_opt = self.build_transfer(transfers[i - 1].id, .{
781
- .valid = true,
782
- .limit = false,
783
- .method = .single_phase,
784
- }, &transfers[i - 1]);
785
- if (result_set_opt) |result_set| {
786
- assert(result_set.count() == 1);
787
- assert(result_set.contains(.ok));
788
-
789
- transfers[i - 1].flags.linked = true;
790
- results[i - 1] = accounting_auditor.CreateTransferResultSet.init(.{
791
- .linked_event_failed = true,
792
- });
793
- }
794
- }
795
- }
796
- assert(results[i].count() > 0);
797
-
798
- if (transfers[i].flags.pending) self.transfers_pending_in_flight += 1;
799
- i += 1;
800
- self.transfers_sent += 1;
801
- }
802
- assert(transfers_count == i);
803
- assert(transfers_count <= transfers.len);
804
-
805
- self.build_retry_transfers(transfers[0..transfers_count], results);
806
-
807
- // Checksum transfers only after the whole batch is ready.
808
- // The opportunistic linking backtracks to modify transfers.
809
- for (transfers[0..transfers_count]) |*transfer| {
810
- transfer.user_data_128 = vsr.checksum(std.mem.asBytes(transfer));
811
- }
812
-
813
- return transfers_count;
814
- }
815
-
816
- fn build_retry_transfers(
817
- self: *Workload,
818
- transfers: []tb.Transfer,
819
- results: []accounting_auditor.CreateTransferResultSet,
820
- ) void {
821
- assert(results.len >= transfers.len);
822
-
823
- // Neither the first nor the last id can regress to preserve the
824
- // `transfers_delivered_recently` and `transfers_delivered_past` logic.
825
- // So we must insert retries in the middle of the batch.
826
- if (transfers.len <= 1) return;
827
- for (1..transfers.len - 1) |i| {
828
- if (self.transfers_retry_exists.items.len == 0 and
829
- self.transfers_retry_failed.count() == 0) break;
830
-
831
- // To support random `lookup_transfers`, we replace the transfer with a retry,
832
- // without altering the outcome for this specific `transfer_index`.
833
- const transfer_index = self.transfer_id_to_index(transfers[i].id);
834
- const transfer_plan = self.transfer_index_to_plan(transfer_index);
835
- const can_retry = !transfer_plan.valid and
836
- !transfers[i].flags.linked and
837
- !transfers[i - 1].flags.linked;
838
- if (can_retry and
839
- self.prng.chance(self.options.create_transfer_retry_probability))
840
- {
841
- switch (self.prng.chances(.{
842
- .exists = @intFromBool(self.transfers_retry_exists.items.len > 0),
843
- .failed = @intFromBool(self.transfers_retry_failed.count() > 0),
844
- })) {
845
- .exists => {
846
- // Retry a successfully completed transfer, result == `exists`.
847
- const index = self.prng.index(self.transfers_retry_exists.items);
848
- transfers[i] = self.transfers_retry_exists.swapRemove(index);
849
- results[i] = .initOne(.exists);
850
- },
851
- .failed => {
852
- // Retry a failed transfer ID, result == `id_already_failed`.
853
- const index = self.prng.index(self.transfers_retry_failed.keys());
854
- const id_failed = self.transfers_retry_failed.keys()[index];
855
- self.transfers_retry_failed.swapRemoveAt(index);
856
- transfers[i] = std.mem.zeroInit(tb.Transfer, .{ .id = id_failed });
857
- results[i] = .initOne(.id_already_failed);
858
- },
859
- }
860
- }
861
- }
862
- }
863
-
864
- fn build_lookup_accounts(self: *Workload, lookup_ids: []u128) usize {
865
- for (lookup_ids) |*id| {
866
- if (self.prng.chance(self.options.lookup_account_invalid_probability)) {
867
- // Pick an account with valid index (rather than "random.int(u128)") because the
868
- // Auditor must decode the id to check for a matching account.
869
- id.* = self.auditor.account_index_to_id(self.prng.int(usize));
870
- } else {
871
- const account_index = self.prng.index(self.auditor.accounts);
872
- id.* = self.auditor.accounts[account_index].id;
873
- }
874
- }
875
- return lookup_ids.len;
876
- }
877
-
878
- fn build_lookup_transfers(self: *const Workload, lookup_ids: []u128) usize {
879
- const delivered = self.transfers_delivered_past;
880
- const lookup_window = self.prng.enum_weighted(Lookup, self.options.lookup_transfer);
881
- const lookup_window_start = switch (lookup_window) {
882
- .delivered => self.prng.int_inclusive(usize, delivered),
883
- .sending => self.prng.range_inclusive(
884
- usize,
885
- delivered,
886
- self.transfers_sent,
887
- ),
888
- };
889
-
890
- // +1 to make the span-max inclusive.
891
- const lookup_window_size = @min(
892
- fuzz.random_int_exponential(
893
- self.prng,
894
- usize,
895
- self.options.lookup_transfer_span_mean,
896
- ),
897
- self.transfers_sent - lookup_window_start,
898
- );
899
- if (lookup_window_size == 0) return 0;
900
-
901
- for (lookup_ids) |*lookup_id| {
902
- lookup_id.* = self.transfer_index_to_id(
903
- lookup_window_start + self.prng.int_inclusive(usize, lookup_window_size - 1),
904
- );
905
- }
906
- return lookup_ids.len;
907
- }
908
-
909
- fn build_get_account_filter(
910
- self: *const Workload,
911
- client_index: usize,
912
- comptime action: Action,
913
- body: []tb.AccountFilter,
914
- ) usize {
915
- _ = client_index;
916
- comptime assert(action == .get_account_transfers or
917
- action == .get_account_balances or
918
- action == .deprecated_get_account_transfers_unbatched or
919
- action == .deprecated_get_account_balances_unbatched);
920
- assert(body.len == 1);
921
- const account_filter = &body[0];
922
- account_filter.* = tb.AccountFilter{
923
- .account_id = 0,
924
- .user_data_128 = 0,
925
- .user_data_64 = 0,
926
- .user_data_32 = 0,
927
- .code = 0,
928
- .limit = 0,
929
- .flags = .{
930
- .credits = false,
931
- .debits = false,
932
- .reversed = false,
933
- },
934
- .timestamp_min = 0,
935
- .timestamp_max = 0,
936
- };
937
-
938
- account_filter.account_id = if (self.auditor.pick_account(.{
939
- .created = null,
940
- .debits_must_not_exceed_credits = null,
941
- .credits_must_not_exceed_debits = null,
942
- })) |account| account.id else
943
- // Pick an account with valid index (rather than "random.int(u128)") because the
944
- // Auditor must decode the id to check for a matching account.
945
- self.auditor.account_index_to_id(self.prng.int(usize));
946
-
947
- // It may be an invalid account.
948
- const account_state: ?*const Auditor.AccountState = self.auditor.get_account_state(
949
- account_filter.account_id,
950
- );
951
-
952
- account_filter.flags.reversed = self.prng.boolean();
953
-
954
- // The timestamp range is restrictive to the number of transfers inserted at the
955
- // moment the filter was generated. Only when this filter is in place we can assert
956
- // the expected result count.
957
- if (account_state != null and
958
- self.prng.chance(self.options.account_filter_timestamp_range_probability))
959
- {
960
- account_filter.flags.credits = true;
961
- account_filter.flags.debits = true;
962
- account_filter.limit = account_state.?.transfers_count(account_filter.flags);
963
- account_filter.timestamp_min = account_state.?.transfer_timestamp_min;
964
- account_filter.timestamp_max = account_state.?.transfer_timestamp_max;
965
-
966
- // Exclude the first or the last result depending on the sort order,
967
- // if there are more than one single transfer.
968
- account_filter.timestamp_min += @intFromBool(!account_filter.flags.reversed);
969
- account_filter.timestamp_max -|= @intFromBool(account_filter.flags.reversed);
970
- } else {
971
- switch (self.prng.enum_uniform(enum { none, debits, credits, all })) {
972
- .none => {}, // Testing invalid flags.
973
- .debits => account_filter.flags.debits = true,
974
- .credits => account_filter.flags.credits = true,
975
- .all => {
976
- account_filter.flags.debits = true;
977
- account_filter.flags.credits = true;
978
- },
979
- }
980
-
981
- const operation = comptime std.enums.nameCast(Operation, action);
982
- const batch_result_max = operation.result_max(self.options.batch_size_limit);
983
- account_filter.limit = switch (self.prng.enum_uniform(enum {
984
- zero,
985
- one,
986
- random,
987
- batch_max,
988
- int_max,
989
- })) {
990
- .zero => 0,
991
- .one => 1,
992
- .random => self.prng.int_inclusive(u32, batch_result_max),
993
- .batch_max => batch_result_max,
994
- .int_max => std.math.maxInt(u32),
995
- };
996
- }
997
-
998
- return 1;
999
- }
1000
-
1001
- fn build_query_filter(
1002
- self: *const Workload,
1003
- client_index: usize,
1004
- comptime action: Action,
1005
- body: []tb.QueryFilter,
1006
- ) usize {
1007
- _ = client_index;
1008
- comptime assert(action == .query_accounts or
1009
- action == .query_transfers or
1010
- action == .deprecated_query_accounts_unbatched or
1011
- action == .deprecated_query_transfers_unbatched);
1012
- assert(body.len == 1);
1013
- const query_filter = &body[0];
1014
-
1015
- const operation = comptime std.enums.nameCast(Operation, action);
1016
- const batch_result_max = operation.result_max(self.options.batch_size_limit);
1017
- const limit: u32 = switch (self.prng.enum_uniform(enum {
1018
- zero,
1019
- one,
1020
- random,
1021
- batch_max,
1022
- int_max,
1023
- })) {
1024
- .zero => 0,
1025
- .one => 1,
1026
- .random => self.prng.int_inclusive(u32, batch_result_max),
1027
- .batch_max => batch_result_max,
1028
- .int_max => std.math.maxInt(u32),
1029
- };
1030
-
1031
- if (self.prng.chance(self.options.query_filter_not_found_probability)) {
1032
- query_filter.* = .{
1033
- .user_data_128 = 0,
1034
- .user_data_64 = 0,
1035
- .user_data_32 = 0,
1036
- .code = 0,
1037
- .ledger = 999, // Non-existent ledger
1038
- .limit = limit,
1039
- .flags = .{
1040
- .reversed = false,
1041
- },
1042
- .timestamp_min = 0,
1043
- .timestamp_max = 0,
1044
- };
1045
- } else {
1046
- const query_intersection_index = self.prng.index(self.auditor.query_intersections);
1047
- const query_intersection =
1048
- self.auditor.query_intersections[query_intersection_index];
1049
-
1050
- query_filter.* = .{
1051
- .user_data_128 = 0,
1052
- .user_data_64 = query_intersection.user_data_64,
1053
- .user_data_32 = query_intersection.user_data_32,
1054
- .code = query_intersection.code,
1055
- .ledger = 0,
1056
- .limit = limit,
1057
- .flags = .{
1058
- .reversed = self.prng.boolean(),
1059
- },
1060
- .timestamp_min = 0,
1061
- .timestamp_max = 0,
1062
- };
1063
-
1064
- // Maybe filter by timestamp:
1065
- const state = switch (action) {
1066
- .query_accounts,
1067
- .deprecated_query_accounts_unbatched,
1068
- => &query_intersection.accounts,
1069
- .query_transfers,
1070
- .deprecated_query_transfers_unbatched,
1071
- => &query_intersection.transfers,
1072
- else => unreachable,
1073
- };
1074
-
1075
- if (state.count > 1 and state.count <= batch_result_max and
1076
- self.prng.chance(self.options.query_filter_timestamp_range_probability))
1077
- {
1078
- // Excluding the first or last object:
1079
- if (query_filter.flags.reversed) {
1080
- query_filter.timestamp_min = state.timestamp_min;
1081
- query_filter.timestamp_max = state.timestamp_max - 1;
1082
- } else {
1083
- query_filter.timestamp_min = state.timestamp_min + 1;
1084
- query_filter.timestamp_max = state.timestamp_max;
1085
- }
1086
- // Later we can assert that results.len == count - 1:
1087
- query_filter.limit = state.count;
1088
- }
1089
- }
1090
-
1091
- return 1;
1092
- }
1093
-
1094
- fn build_get_change_events_filter(
1095
- self: *Workload,
1096
- client_index: usize,
1097
- body: []tb.ChangeEventsFilter,
1098
- ) usize {
1099
- _ = client_index;
1100
- assert(body.len == 1);
1101
- const filter = &body[0];
1102
-
1103
- const snapshot = self.auditor.changes_tracker.acquire_snapshot() orelse {
1104
- // We can only track a limited set of events,
1105
- // so we issue a query with an invalid filter when the results can't be asserted.
1106
- filter.* = switch (self.prng.enum_uniform(enum {
1107
- zeroed,
1108
- invalid_timestamps,
1109
- })) {
1110
- .zeroed => .{
1111
- .limit = 0,
1112
- .timestamp_min = 0,
1113
- .timestamp_max = 0,
1114
- },
1115
- .invalid_timestamps => filter: {
1116
- const timestamp: u64 = self.prng.range_inclusive(
1117
- u64,
1118
- TimestampRange.timestamp_min,
1119
- TimestampRange.timestamp_max,
1120
- );
1121
- break :filter .{
1122
- .limit = self.prng.int(u32),
1123
- .timestamp_min = timestamp + 1,
1124
- .timestamp_max = timestamp,
1125
- };
1126
- },
1127
- };
1128
- return 1;
1129
- };
1130
- assert(snapshot.count_total() > 0);
1131
-
1132
- const limit: u32 = switch (self.prng.enum_uniform(enum {
1133
- exact,
1134
- batch_max,
1135
- int_max,
1136
- })) {
1137
- .exact => snapshot.count_total(),
1138
- .batch_max => Operation.get_change_events.result_max(
1139
- self.options.batch_size_limit,
1140
- ),
1141
- .int_max => std.math.maxInt(u32),
1142
- };
1143
- filter.* = .{
1144
- .limit = limit,
1145
- .timestamp_min = snapshot.timestamp_min,
1146
- .timestamp_max = snapshot.timestamp_max,
1147
- };
1148
- return 1;
1149
- }
1150
-
1151
- /// The transfer built is guaranteed to match the TransferPlan's outcome.
1152
- /// The transfer built is _not_ guaranteed to match the TransferPlan's method.
1153
- ///
1154
- /// Returns `null` if the transfer plan cannot be fulfilled (because there aren't enough
1155
- /// accounts created).
1156
- fn build_transfer(
1157
- self: *Workload,
1158
- transfer_id: u128,
1159
- transfer_plan: TransferPlan,
1160
- transfer: *tb.Transfer,
1161
- ) ?accounting_auditor.CreateTransferResultSet {
1162
- // If the specified method is unavailable, swap it.
1163
- // Changing the method may narrow the TransferOutcome (unknown→success, unknown→failure)
1164
- // but never broaden it (success→unknown, success→failure).
1165
- const method = method: {
1166
- const default = transfer_plan.method;
1167
- if (default == .pending and
1168
- self.auditor.pending_expiries.count() + self.transfers_pending_in_flight ==
1169
- self.auditor.options.transfers_pending_max)
1170
- {
1171
- break :method .single_phase;
1172
- }
1173
-
1174
- if (default == .post_pending or default == .void_pending) {
1175
- if (self.auditor.pending_transfers.count() == 0) {
1176
- break :method .single_phase;
1177
- }
1178
- }
1179
- break :method default;
1180
- };
1181
-
1182
- const index_valid = @intFromBool(transfer_plan.valid);
1183
- const index_limit = @intFromBool(transfer_plan.limit);
1184
- const index_method = @intFromEnum(method);
1185
- const transfer_template = &transfer_templates[index_valid][index_limit][index_method];
1186
-
1187
- const limit_debits = transfer_plan.limit and self.prng.boolean();
1188
- const limit_credits = transfer_plan.limit and (self.prng.boolean() or !limit_debits);
1189
- assert(transfer_plan.limit == (limit_debits or limit_credits));
1190
-
1191
- const debit_account = self.auditor.pick_account(.{
1192
- .created = true,
1193
- .debits_must_not_exceed_credits = limit_debits,
1194
- .credits_must_not_exceed_debits = null,
1195
- }) orelse return null;
1196
- assert(!limit_debits or debit_account.flags.debits_must_not_exceed_credits);
1197
-
1198
- const credit_account = self.auditor.pick_account(.{
1199
- .created = true,
1200
- .debits_must_not_exceed_credits = null,
1201
- .credits_must_not_exceed_debits = limit_credits,
1202
- .exclude = debit_account.id,
1203
- }) orelse return null;
1204
- assert(!limit_credits or credit_account.flags.credits_must_not_exceed_debits);
1205
-
1206
- const query_intersection_index = self.prng.index(
1207
- self.auditor.query_intersections,
1208
- );
1209
- const query_intersection = self.auditor.query_intersections[query_intersection_index];
1210
-
1211
- transfer.* = .{
1212
- .id = transfer_id,
1213
- .debit_account_id = debit_account.id,
1214
- .credit_account_id = credit_account.id,
1215
- // "user_data_128" will be set to a checksum of the Transfer.
1216
- .user_data_128 = 0,
1217
- .user_data_64 = query_intersection.user_data_64,
1218
- .user_data_32 = query_intersection.user_data_32,
1219
- .code = query_intersection.code,
1220
- .pending_id = 0,
1221
- .timeout = 0,
1222
- .ledger = transfer_template.ledger,
1223
- .flags = .{},
1224
- .timestamp = 0,
1225
- .amount = self.prng.int_inclusive(u128, std.math.maxInt(u8)),
1226
- };
1227
-
1228
- switch (method) {
1229
- .single_phase => {},
1230
- .pending => {
1231
- transfer.flags = .{ .pending = true };
1232
- // Bound the timeout to ensure we never hit `overflows_timeout`.
1233
- transfer.timeout = 1 + @as(u32, @min(
1234
- std.math.maxInt(u32) / 2,
1235
- fuzz.random_int_exponential(
1236
- self.prng,
1237
- u32,
1238
- self.options.pending_timeout_mean,
1239
- ),
1240
- ));
1241
- },
1242
- .post_pending, .void_pending => {
1243
- // Don't depend on `HashMap.keyIterator()` being deterministic.
1244
- // Pick a random "target" key, then post/void the id it is nearest to.
1245
- const target = self.prng.int(u128);
1246
- var previous: ?u128 = null;
1247
- var iterator = self.auditor.pending_transfers.keyIterator();
1248
- while (iterator.next()) |id| {
1249
- if (previous == null or
1250
- @max(target, id.*) - @min(target, id.*) <
1251
- @max(target, previous.?) - @min(target, previous.?))
1252
- {
1253
- previous = id.*;
1254
- }
1255
- }
1256
-
1257
- // If there were no pending ids, the method would have been changed.
1258
- const pending_id = previous.?;
1259
- const pending_transfer = self.auditor.pending_transfers.getPtr(previous.?).?;
1260
- const dr = pending_transfer.debit_account_index;
1261
- const cr = pending_transfer.credit_account_index;
1262
- const pending_query_intersection = self.auditor
1263
- .query_intersections[pending_transfer.query_intersection_index];
1264
- // Don't use the default '0' parameters because the StateMachine overwrites 0s
1265
- // with the pending transfer's values, invalidating the post/void transfer
1266
- // checksum.
1267
- transfer.debit_account_id = self.auditor.account_index_to_id(dr);
1268
- transfer.credit_account_id = self.auditor.account_index_to_id(cr);
1269
- transfer.user_data_64 = pending_query_intersection.user_data_64;
1270
- transfer.user_data_32 = pending_query_intersection.user_data_32;
1271
- transfer.code = pending_query_intersection.code;
1272
- if (method == .post_pending) {
1273
- transfer.amount =
1274
- self.prng.range_inclusive(u128, 0, pending_transfer.amount);
1275
- } else {
1276
- transfer.amount = pending_transfer.amount;
1277
- }
1278
- transfer.pending_id = pending_id;
1279
- transfer.flags = .{
1280
- .post_pending_transfer = method == .post_pending,
1281
- .void_pending_transfer = method == .void_pending,
1282
- };
1283
- },
1284
- }
1285
- assert(transfer_template.result.count() > 0);
1286
- return transfer_template.result;
1287
- }
1288
-
1289
- fn batch(
1290
- self: *const Workload,
1291
- comptime T: type,
1292
- comptime action: Action,
1293
- body: []u8,
1294
- event_count_remain: u32,
1295
- ) []T {
1296
- const batch_min = switch (action) {
1297
- .create_accounts,
1298
- .lookup_accounts,
1299
- .deprecated_create_accounts_unbatched,
1300
- .deprecated_lookup_accounts_unbatched,
1301
- => self.options.accounts_batch_size_min,
1302
- .create_transfers,
1303
- .lookup_transfers,
1304
- .deprecated_create_transfers_unbatched,
1305
- .deprecated_lookup_transfers_unbatched,
1306
- => self.options.transfers_batch_size_min,
1307
- .get_account_transfers,
1308
- .get_account_balances,
1309
- .query_accounts,
1310
- .query_transfers,
1311
- .deprecated_get_account_transfers_unbatched,
1312
- .deprecated_get_account_balances_unbatched,
1313
- .deprecated_query_accounts_unbatched,
1314
- .deprecated_query_transfers_unbatched,
1315
- .get_change_events,
1316
- => 1,
1317
- };
1318
- const batch_span = switch (action) {
1319
- .create_accounts,
1320
- .lookup_accounts,
1321
- .deprecated_create_accounts_unbatched,
1322
- .deprecated_lookup_accounts_unbatched,
1323
- => self.options.accounts_batch_size_span,
1324
- .create_transfers,
1325
- .lookup_transfers,
1326
- .deprecated_create_transfers_unbatched,
1327
- .deprecated_lookup_transfers_unbatched,
1328
- => self.options.transfers_batch_size_span,
1329
- .get_account_transfers,
1330
- .get_account_balances,
1331
- .query_accounts,
1332
- .query_transfers,
1333
- .deprecated_get_account_transfers_unbatched,
1334
- .deprecated_get_account_balances_unbatched,
1335
- .deprecated_query_accounts_unbatched,
1336
- .deprecated_query_transfers_unbatched,
1337
- .get_change_events,
1338
- => 0,
1339
- };
1340
-
1341
- const slice = stdx.bytes_as_slice(.inexact, T, body);
1342
- const batch_size = @min(
1343
- batch_min + self.prng.int_inclusive(usize, batch_span),
1344
- event_count_remain,
1345
- );
1346
-
1347
- return slice[0..batch_size];
1348
- }
1349
-
1350
- fn transfer_id_to_index(self: *const Workload, id: u128) usize {
1351
- // -1 because id=0 is not valid, so index=0→id=1.
1352
- return @as(usize, @intCast(self.options.transfer_id_permutation.decode(id))) - 1;
1353
- }
1354
-
1355
- fn transfer_index_to_id(self: *const Workload, index: usize) u128 {
1356
- // +1 so that index=0 is encoded as a valid id.
1357
- return self.options.transfer_id_permutation.encode(index + 1);
1358
- }
1359
-
1360
- /// To support `lookup_transfers`, the `TransferPlan` is deterministic based on:
1361
- /// * `Workload.transfer_plan_seed`, and
1362
- /// * the transfer `index`.
1363
- fn transfer_index_to_plan(self: *const Workload, index: usize) TransferPlan {
1364
- var prng = stdx.PRNG.from_seed(self.transfer_plan_seed ^ @as(u64, index));
1365
- const method: TransferPlan.Method = blk: {
1366
- if (prng.chance(self.options.create_transfer_pending_probability)) {
1367
- break :blk .pending;
1368
- }
1369
- if (prng.chance(self.options.create_transfer_post_probability)) {
1370
- break :blk .post_pending;
1371
- }
1372
- if (prng.chance(self.options.create_transfer_void_probability)) {
1373
- break :blk .void_pending;
1374
- }
1375
- break :blk .single_phase;
1376
- };
1377
- return .{
1378
- .valid = !prng.chance(self.options.create_transfer_invalid_probability),
1379
- .limit = prng.chance(self.options.create_transfer_limit_probability),
1380
- .method = method,
1381
- };
1382
- }
1383
-
1384
- fn on_create_transfers(
1385
- self: *Workload,
1386
- client_index: usize,
1387
- timestamp: u64,
1388
- transfers: []const tb.Transfer,
1389
- results_sparse: []const tb.CreateTransfersResult,
1390
- ) void {
1391
- self.auditor.on_create_transfers(client_index, timestamp, transfers, results_sparse);
1392
- if (transfers.len == 0) return;
1393
-
1394
- const transfer_index_min = self.transfer_id_to_index(transfers[0].id);
1395
- const transfer_index_max = self.transfer_id_to_index(transfers[transfers.len - 1].id);
1396
- assert(transfer_index_min <= transfer_index_max);
1397
-
1398
- self.transfers_delivered_recently.add(.{
1399
- .min = transfer_index_min,
1400
- .max = transfer_index_max,
1401
- }) catch unreachable;
1402
-
1403
- while (self.transfers_delivered_recently.peek()) |delivered| {
1404
- if (self.transfers_delivered_past == delivered.min) {
1405
- self.transfers_delivered_past = delivered.max + 1;
1406
- _ = self.transfers_delivered_recently.remove();
1407
- } else {
1408
- assert(self.transfers_delivered_past < delivered.min);
1409
- break;
1410
- }
1411
- }
1412
-
1413
- const CreateTransfersResultIterator = IteratorForCreateType(tb.CreateTransfersResult);
1414
- var results_iterator: CreateTransfersResultIterator = .init(results_sparse);
1415
- for (transfers, 0..) |*transfer, i| {
1416
- const result: tb.CreateTransferResult = results_iterator.take(i) orelse .ok;
1417
- if (transfer.flags.pending and result != .exists) {
1418
- self.transfers_pending_in_flight -= 1;
1419
- }
1420
-
1421
- // Add some successfully completed transfers to be retried in the next request.
1422
- if (result == .ok and !transfer.flags.linked and
1423
- self.transfers_retry_exists.items.len <
1424
- self.options.transfers_retry_exists_max and
1425
- self.prng.chance(self.options.create_transfer_retry_probability))
1426
- {
1427
- var transfer_exists = transfer.*;
1428
- assert(transfer_exists.timestamp == 0);
1429
- assert(transfer_exists.user_data_128 != 0);
1430
-
1431
- transfer_exists.user_data_128 = 0; // This will be replaced by the checksum.
1432
- self.transfers_retry_exists.appendAssumeCapacity(transfer_exists);
1433
- }
1434
-
1435
- // Enqueue the `id`s of transient errors to be retried in the next request.
1436
- if (result != .ok and result.transient() and
1437
- self.transfers_retry_failed.count() <
1438
- self.options.transfers_retry_failed_max)
1439
- {
1440
- self.transfers_retry_failed.putAssumeCapacityNoClobber(
1441
- transfer.id,
1442
- {},
1443
- );
1444
- }
1445
- }
1446
- }
1447
-
1448
- fn on_lookup_transfers(
1449
- self: *Workload,
1450
- client_index: usize,
1451
- timestamp: u64,
1452
- ids: []const u128,
1453
- results: []const tb.Transfer,
1454
- ) void {
1455
- self.auditor.on_lookup_transfers(client_index, timestamp, ids, results);
1456
-
1457
- var transfers = accounting_auditor.IteratorForLookupType(tb.Transfer).init(results);
1458
- for (ids) |transfer_id| {
1459
- const transfer_index = self.transfer_id_to_index(transfer_id);
1460
- const transfer_outcome = self.transfer_index_to_plan(transfer_index).outcome();
1461
- const result = transfers.take(transfer_id);
1462
-
1463
- if (result) |transfer| validate_transfer_checksum(transfer);
1464
-
1465
- if (transfer_index >= self.transfers_sent) {
1466
- // This transfer hasn't been created yet.
1467
- assert(result == null);
1468
- continue;
1469
- }
1470
-
1471
- switch (transfer_outcome) {
1472
- .success => {
1473
- if (transfer_index < self.transfers_delivered_past) {
1474
- // The transfer was delivered; it must exist.
1475
- assert(result != null);
1476
- } else {
1477
- var it = self.transfers_delivered_recently.iterator();
1478
- while (it.next()) |delivered| {
1479
- if (transfer_index >= delivered.min and
1480
- transfer_index <= delivered.max)
1481
- {
1482
- // The transfer was delivered recently; it must exist.
1483
- assert(result != null);
1484
- break;
1485
- }
1486
- } else {
1487
- // The `create_transfers` has not committed (it may be in-flight).
1488
- assert(result == null);
1489
- }
1490
- }
1491
- },
1492
- // An invalid transfer is never persisted.
1493
- .failure => assert(result == null),
1494
- // Due to races and timeouts, these transfer types may not succeed.
1495
- .unknown => {},
1496
- }
1497
- }
1498
- }
1499
-
1500
- fn on_get_account_transfers(
1501
- self: *Workload,
1502
- comptime operation: Operation,
1503
- timestamp: u64,
1504
- body: []const tb.AccountFilter,
1505
- results: []const tb.Transfer,
1506
- ) void {
1507
- _ = timestamp;
1508
- comptime assert(operation == .get_account_transfers or
1509
- operation == .deprecated_get_account_transfers_unbatched);
1510
- assert(body.len == 1);
1511
-
1512
- const batch_result_max = operation.result_max(self.options.batch_size_limit);
1513
- const account_filter = &body[0];
1514
- assert(results.len <= account_filter.limit);
1515
- assert(results.len <= batch_result_max);
1516
-
1517
- const account_state = self.auditor.get_account_state(
1518
- account_filter.account_id,
1519
- ) orelse {
1520
- // Invalid account id.
1521
- assert(results.len == 0);
1522
- return;
1523
- };
1524
-
1525
- const filter_valid = account_state.created and
1526
- (account_filter.flags.credits or account_filter.flags.debits) and
1527
- account_filter.limit > 0 and
1528
- account_filter.timestamp_min <= account_filter.timestamp_max;
1529
- if (!filter_valid) {
1530
- // Invalid filter.
1531
- assert(results.len == 0);
1532
- return;
1533
- }
1534
-
1535
- self.validate_account_filter_result_count(
1536
- operation,
1537
- account_state,
1538
- account_filter,
1539
- results.len,
1540
- );
1541
-
1542
- var timestamp_previous: u64 = if (account_filter.flags.reversed)
1543
- account_state.transfer_timestamp_max +| 1
1544
- else
1545
- account_state.transfer_timestamp_min -| 1;
1546
-
1547
- for (results) |*transfer| {
1548
- if (account_filter.flags.reversed) {
1549
- assert(transfer.timestamp < timestamp_previous);
1550
- } else {
1551
- assert(transfer.timestamp > timestamp_previous);
1552
- }
1553
- timestamp_previous = transfer.timestamp;
1554
-
1555
- assert(account_filter.timestamp_min == 0 or
1556
- transfer.timestamp >= account_filter.timestamp_min);
1557
- assert(account_filter.timestamp_max == 0 or
1558
- transfer.timestamp <= account_filter.timestamp_max);
1559
-
1560
- validate_transfer_checksum(transfer);
1561
-
1562
- const transfer_index = self.transfer_id_to_index(transfer.id);
1563
- assert(transfer_index < self.transfers_sent);
1564
-
1565
- const transfer_plan = self.transfer_index_to_plan(transfer_index);
1566
- assert(transfer_plan.valid);
1567
- assert(transfer_plan.outcome() != .failure);
1568
- if (transfer.flags.pending) assert(transfer_plan.method == .pending);
1569
- if (transfer.flags.post_pending_transfer) {
1570
- assert(transfer_plan.method == .post_pending);
1571
- }
1572
- if (transfer.flags.void_pending_transfer) {
1573
- assert(transfer_plan.method == .void_pending);
1574
- }
1575
- if (transfer_plan.method == .single_phase) assert(!transfer.flags.pending and
1576
- !transfer.flags.post_pending_transfer and
1577
- !transfer.flags.void_pending_transfer);
1578
-
1579
- assert(transfer.debit_account_id == account_filter.account_id or
1580
- transfer.credit_account_id == account_filter.account_id);
1581
- assert(account_filter.flags.credits or account_filter.flags.debits);
1582
- assert(account_filter.flags.credits or
1583
- transfer.debit_account_id == account_filter.account_id);
1584
- assert(account_filter.flags.debits or
1585
- transfer.credit_account_id == account_filter.account_id);
1586
-
1587
- if (transfer_plan.limit) {
1588
- // The plan does not guarantee the "limit" flag for posting
1589
- // or voiding pending transfers.
1590
- const post_or_void_pending_transfer = transfer.flags.post_pending_transfer or
1591
- transfer.flags.void_pending_transfer;
1592
- assert(post_or_void_pending_transfer == (transfer.pending_id != 0));
1593
-
1594
- const dr_account = self.auditor.get_account(transfer.debit_account_id).?;
1595
- const cr_account = self.auditor.get_account(transfer.credit_account_id).?;
1596
- assert(
1597
- post_or_void_pending_transfer or
1598
- dr_account.flags.debits_must_not_exceed_credits or
1599
- cr_account.flags.credits_must_not_exceed_debits,
1600
- );
1601
- }
1602
- }
1603
- }
1604
-
1605
- fn on_get_account_balances(
1606
- self: *Workload,
1607
- comptime operation: Operation,
1608
- timestamp: u64,
1609
- body: []const tb.AccountFilter,
1610
- results: []const tb.AccountBalance,
1611
- ) void {
1612
- _ = timestamp;
1613
- comptime assert(operation == .get_account_balances or
1614
- operation == .deprecated_get_account_balances_unbatched);
1615
- assert(body.len == 1);
1616
-
1617
- const batch_result_max = operation.result_max(self.options.batch_size_limit);
1618
- const account_filter = &body[0];
1619
- assert(results.len <= account_filter.limit);
1620
- assert(results.len <= batch_result_max);
1621
-
1622
- const account_state = self.auditor.get_account_state(
1623
- account_filter.account_id,
1624
- ) orelse {
1625
- // Invalid account id.
1626
- assert(results.len == 0);
1627
- return;
1628
- };
1629
-
1630
- const filter_valid = account_state.created and
1631
- self.auditor.get_account(account_filter.account_id).?.flags.history and
1632
- (account_filter.flags.credits or account_filter.flags.debits) and
1633
- account_filter.limit > 0 and
1634
- account_filter.timestamp_min <= account_filter.timestamp_max;
1635
- if (!filter_valid) {
1636
- // Invalid filter.
1637
- assert(results.len == 0);
1638
- return;
1639
- }
1640
-
1641
- self.validate_account_filter_result_count(
1642
- operation,
1643
- account_state,
1644
- account_filter,
1645
- results.len,
1646
- );
1647
-
1648
- var timestamp_last: u64 = if (account_filter.flags.reversed)
1649
- account_state.transfer_timestamp_max +| 1
1650
- else
1651
- account_state.transfer_timestamp_min -| 1;
1652
-
1653
- for (results) |*balance| {
1654
- assert(if (account_filter.flags.reversed)
1655
- balance.timestamp < timestamp_last
1656
- else
1657
- balance.timestamp > timestamp_last);
1658
- timestamp_last = balance.timestamp;
1659
-
1660
- assert(account_filter.timestamp_min == 0 or
1661
- balance.timestamp >= account_filter.timestamp_min);
1662
- assert(account_filter.timestamp_max == 0 or
1663
- balance.timestamp <= account_filter.timestamp_max);
1664
- }
1665
- }
1666
-
1667
- fn validate_account_filter_result_count(
1668
- self: *const Workload,
1669
- comptime operation: Operation,
1670
- account_state: *const Auditor.AccountState,
1671
- account_filter: *const tb.AccountFilter,
1672
- result_count: usize,
1673
- ) void {
1674
- comptime assert(operation == .get_account_transfers or
1675
- operation == .get_account_balances or
1676
- operation == .deprecated_get_account_transfers_unbatched or
1677
- operation == .deprecated_get_account_balances_unbatched);
1678
- maybe(account_filter.limit == 0);
1679
-
1680
- const batch_result_max = operation.result_max(self.options.batch_size_limit);
1681
- const transfer_count = account_state.transfers_count(account_filter.flags);
1682
- if (account_filter.timestamp_min == 0 and account_filter.timestamp_max == 0) {
1683
- assert(account_filter.limit <= batch_result_max or
1684
- account_filter.limit == std.math.maxInt(u32));
1685
- assert(result_count ==
1686
- @min(account_filter.limit, batch_result_max, transfer_count));
1687
- } else {
1688
- // If timestamp range is set, then the limit is exactly the number of transfer
1689
- // at the time the filter was generated, but new transfers could have been
1690
- // inserted since then.
1691
- assert(account_filter.limit <= transfer_count);
1692
- assert(account_filter.timestamp_max >= account_filter.timestamp_min);
1693
- if (account_filter.flags.reversed) {
1694
- // This filter is only set if there is at least one transfer, so the first
1695
- // transfer timestamp never changes.
1696
- assert(account_filter.timestamp_min == account_state.transfer_timestamp_min);
1697
- // The filter `timestamp_max` was decremented to skip one result.
1698
- assert(account_filter.timestamp_max < account_state.transfer_timestamp_max);
1699
- } else {
1700
- // The filter `timestamp_min` was incremented to skip one result.
1701
- assert(account_filter.timestamp_min > account_state.transfer_timestamp_min);
1702
- // New transfers can update `transfer_timestamp_max`.
1703
- assert(account_filter.timestamp_max <= account_state.transfer_timestamp_max);
1704
- }
1705
-
1706
- // Either `transfer_count` is greater than the batch size (so removing a result
1707
- // doesn't make a difference) or there is exactly one less result that was
1708
- // excluded by the timestamp filter.
1709
- assert((result_count == batch_result_max and transfer_count > batch_result_max) or
1710
- result_count == account_filter.limit - 1);
1711
- }
1712
- }
1713
-
1714
- fn on_query(
1715
- self: *Workload,
1716
- comptime operation: Operation,
1717
- timestamp: u64,
1718
- body: []const tb.QueryFilter,
1719
- results: []const operation.ResultType(),
1720
- ) void {
1721
- _ = timestamp;
1722
- comptime assert(operation == .query_accounts or
1723
- operation == .query_transfers or
1724
- operation == .deprecated_query_accounts_unbatched or
1725
- operation == .deprecated_query_transfers_unbatched);
1726
- assert(body.len == 1);
1727
-
1728
- const batch_result_max: u32 = operation.result_max(self.options.batch_size_limit);
1729
- const filter = &body[0];
1730
-
1731
- if (filter.ledger != 0) {
1732
- // No results expected.
1733
- assert(results.len == 0);
1734
- return;
1735
- }
1736
-
1737
- assert(filter.user_data_64 != 0);
1738
- assert(filter.user_data_32 != 0);
1739
- assert(filter.code != 0);
1740
- assert(filter.user_data_128 == 0);
1741
- assert(filter.ledger == 0);
1742
- maybe(filter.limit == 0);
1743
- maybe(filter.timestamp_min == 0);
1744
- maybe(filter.timestamp_max == 0);
1745
-
1746
- const query_intersection_index = filter.code - 1;
1747
- const query_intersection = self.auditor.query_intersections[query_intersection_index];
1748
- const state = switch (operation) {
1749
- .query_accounts,
1750
- .deprecated_query_accounts_unbatched,
1751
- => &query_intersection.accounts,
1752
- .query_transfers,
1753
- .deprecated_query_transfers_unbatched,
1754
- => &query_intersection.transfers,
1755
- else => unreachable,
1756
- };
1757
-
1758
- assert(results.len <= filter.limit);
1759
- assert(results.len <= batch_result_max);
1760
-
1761
- if (filter.timestamp_min > 0 or filter.timestamp_max > 0) {
1762
- assert(filter.limit <= state.count);
1763
- assert(filter.timestamp_min > 0);
1764
- assert(filter.timestamp_max > 0);
1765
- assert(filter.timestamp_min <= filter.timestamp_max);
1766
-
1767
- // Filtering by timestamp always exclude one single result.
1768
- assert(results.len == filter.limit - 1);
1769
- } else {
1770
- assert(results.len == @min(
1771
- filter.limit,
1772
- batch_result_max,
1773
- state.count,
1774
- ));
1775
- }
1776
-
1777
- var timestamp_previous: u64 = if (filter.flags.reversed)
1778
- std.math.maxInt(u64)
1779
- else
1780
- 0;
1781
-
1782
- for (results) |*result| {
1783
- if (filter.flags.reversed) {
1784
- assert(result.timestamp < timestamp_previous);
1785
- } else {
1786
- assert(result.timestamp > timestamp_previous);
1787
- }
1788
- timestamp_previous = result.timestamp;
1789
-
1790
- if (filter.timestamp_min > 0) {
1791
- assert(result.timestamp >= filter.timestamp_min);
1792
- }
1793
- if (filter.timestamp_max > 0) {
1794
- assert(result.timestamp <= filter.timestamp_max);
1795
- }
1796
-
1797
- assert(result.user_data_64 == filter.user_data_64);
1798
- assert(result.user_data_32 == filter.user_data_32);
1799
- assert(result.code == filter.code);
1800
-
1801
- if (operation == .query_transfers or
1802
- operation == .deprecated_query_transfers_unbatched)
1803
- {
1804
- validate_transfer_checksum(result);
1805
- }
1806
- }
1807
- }
1808
-
1809
- fn on_get_change_events(
1810
- self: *Workload,
1811
- timestamp: u64,
1812
- body: []const tb.ChangeEventsFilter,
1813
- results: []const tb.ChangeEvent,
1814
- ) void {
1815
- assert(body.len == 1);
1816
- self.auditor.on_get_change_events(timestamp, body[0], results);
1817
-
1818
- for (results) |*result| {
1819
- assert(stdx.zeroed(&result.reserved));
1820
- switch (result.type) {
1821
- .single_phase => {
1822
- assert(result.timestamp == result.transfer_timestamp);
1823
- assert(!result.transfer_flags.pending);
1824
- assert(!result.transfer_flags.post_pending_transfer);
1825
- assert(!result.transfer_flags.void_pending_transfer);
1826
- assert(result.transfer_pending_id == 0);
1827
- assert(result.transfer_amount <= result.debit_account_debits_posted);
1828
- assert(result.transfer_amount <= result.credit_account_credits_posted);
1829
- },
1830
- .two_phase_pending => {
1831
- assert(result.timestamp == result.transfer_timestamp);
1832
- assert(result.transfer_flags.pending);
1833
- assert(!result.transfer_flags.post_pending_transfer);
1834
- assert(!result.transfer_flags.void_pending_transfer);
1835
- assert(result.transfer_pending_id == 0);
1836
- assert(result.transfer_amount <= result.debit_account_debits_pending);
1837
- assert(result.transfer_amount <= result.credit_account_credits_pending);
1838
- },
1839
- .two_phase_posted => {
1840
- assert(result.timestamp == result.transfer_timestamp);
1841
- assert(result.transfer_flags.post_pending_transfer);
1842
- assert(!result.transfer_flags.pending);
1843
- assert(!result.transfer_flags.void_pending_transfer);
1844
- assert(result.transfer_pending_id != 0);
1845
- assert(result.transfer_amount <= result.debit_account_debits_posted);
1846
- assert(result.transfer_amount <= result.credit_account_credits_posted);
1847
- },
1848
- .two_phase_voided => {
1849
- assert(result.timestamp == result.transfer_timestamp);
1850
- assert(result.transfer_flags.void_pending_transfer);
1851
- assert(!result.transfer_flags.pending);
1852
- assert(!result.transfer_flags.post_pending_transfer);
1853
- assert(result.transfer_pending_id != 0);
1854
- },
1855
- .two_phase_expired => {
1856
- assert(result.transfer_timeout > 0);
1857
- const timeout_ns: u64 =
1858
- @as(u64, result.transfer_timeout) * std.time.ns_per_s;
1859
- assert(result.timestamp >= result.transfer_timestamp + timeout_ns);
1860
- assert(result.transfer_flags.pending);
1861
- assert(!result.transfer_flags.post_pending_transfer);
1862
- assert(!result.transfer_flags.void_pending_transfer);
1863
- assert(result.transfer_pending_id == 0);
1864
- },
1865
- }
1866
- assert(result.transfer_flags.closing_debit == result.debit_account_flags.closed);
1867
- assert(result.transfer_flags.closing_credit == result.credit_account_flags.closed);
1868
- validate_get_event_checksum(result);
1869
- }
1870
- }
1871
-
1872
- /// Verify the transfer's integrity.
1873
- fn validate_transfer_checksum(transfer: *const tb.Transfer) void {
1874
- const checksum_actual = transfer.user_data_128;
1875
- var check = transfer.*;
1876
- check.user_data_128 = 0;
1877
- check.timestamp = 0;
1878
- const checksum_expect = vsr.checksum(std.mem.asBytes(&check));
1879
- assert(checksum_expect == checksum_actual);
1880
- }
1881
-
1882
- fn validate_get_event_checksum(event: *const tb.ChangeEvent) void {
1883
- const transfer: tb.Transfer = .{
1884
- .id = event.transfer_id,
1885
- .debit_account_id = event.debit_account_id,
1886
- .credit_account_id = event.credit_account_id,
1887
- .amount = event.transfer_amount,
1888
- .pending_id = event.transfer_pending_id,
1889
- .user_data_128 = event.transfer_user_data_128,
1890
- .user_data_64 = event.transfer_user_data_64,
1891
- .user_data_32 = event.transfer_user_data_32,
1892
- .timeout = event.transfer_timeout,
1893
- .ledger = event.ledger,
1894
- .code = event.transfer_code,
1895
- .flags = event.transfer_flags,
1896
- .timestamp = event.timestamp,
1897
- };
1898
- validate_transfer_checksum(&transfer);
1899
- }
1900
- };
1901
- }
1902
-
1903
- fn OptionsType(
1904
- comptime AccountingStateMachine: type,
1905
- comptime Action: type,
1906
- comptime Lookup: type,
1907
- ) type {
1908
- return struct {
1909
- batch_size_limit: u32,
1910
- multi_batch_per_request_limit: u32,
1911
-
1912
- auditor_options: Auditor.Options,
1913
- transfer_id_permutation: IdPermutation,
1914
-
1915
- operations: stdx.PRNG.EnumWeightsType(Action),
1916
-
1917
- create_account_invalid_probability: Ratio,
1918
- create_transfer_invalid_probability: Ratio,
1919
- create_transfer_limit_probability: Ratio,
1920
- create_transfer_pending_probability: Ratio,
1921
- create_transfer_post_probability: Ratio,
1922
- create_transfer_void_probability: Ratio,
1923
- create_transfer_retry_probability: Ratio,
1924
- lookup_account_invalid_probability: Ratio,
1925
-
1926
- account_filter_invalid_account_probability: Ratio,
1927
- account_filter_timestamp_range_probability: Ratio,
1928
-
1929
- query_filter_not_found_probability: Ratio,
1930
- query_filter_timestamp_range_probability: Ratio,
1931
- lookup_transfer: stdx.PRNG.EnumWeightsType(Lookup),
1932
-
1933
- // Size of timespan for querying, measured in transfers
1934
- lookup_transfer_span_mean: usize,
1935
-
1936
- account_limit_probability: Ratio,
1937
- account_history_probability: Ratio,
1938
-
1939
- /// This probability is only checked for consecutive guaranteed-successful transfers.
1940
- linked_valid_probability: Ratio,
1941
- /// This probability is only checked for consecutive invalid transfers.
1942
- linked_invalid_probability: Ratio,
1943
-
1944
- pending_timeout_mean: u32,
1945
-
1946
- accounts_batch_size_min: usize,
1947
- accounts_batch_size_span: usize, // inclusive
1948
- transfers_batch_size_min: usize,
1949
- transfers_batch_size_span: usize, // inclusive
1950
-
1951
- /// Maximum number of failed transfer IDs to keep in the retry list.
1952
- transfers_retry_failed_max: usize,
1953
-
1954
- /// Maximum number of successfully completed transfers to keep in the retry list.
1955
- transfers_retry_exists_max: usize,
1956
-
1957
- const Options = @This();
1958
- const Operation = AccountingStateMachine.Operation;
1959
-
1960
- pub fn generate(prng: *stdx.PRNG, options: struct {
1961
- batch_size_limit: u32,
1962
- multi_batch_per_request_limit: u32,
1963
- client_count: usize,
1964
- in_flight_max: usize,
1965
- }) Options {
1966
- assert(
1967
- options.batch_size_limit <= constants.message_body_size_max,
1968
- );
1969
-
1970
- const batch_create_accounts_limit = @min(
1971
- Operation.create_accounts.event_max(options.batch_size_limit),
1972
- Operation.deprecated_create_accounts_unbatched.event_max(options.batch_size_limit),
1973
- );
1974
- assert(batch_create_accounts_limit > 0);
1975
- assert(batch_create_accounts_limit <=
1976
- AccountingStateMachine.batch_max.create_accounts);
1977
-
1978
- const batch_create_transfers_limit = @min(
1979
- Operation.create_transfers.event_max(options.batch_size_limit),
1980
- Operation.deprecated_create_transfers_unbatched.event_max(
1981
- options.batch_size_limit,
1982
- ),
1983
- );
1984
- assert(batch_create_transfers_limit > 0);
1985
- assert(batch_create_transfers_limit <=
1986
- AccountingStateMachine.batch_max.create_transfers);
1987
- return .{
1988
- .batch_size_limit = options.batch_size_limit,
1989
- .multi_batch_per_request_limit = options.multi_batch_per_request_limit,
1990
- .auditor_options = .{
1991
- .accounts_max = prng.range_inclusive(usize, 2, 128),
1992
- .account_id_permutation = IdPermutation.generate(prng),
1993
- .client_count = options.client_count,
1994
- .transfers_pending_max = 256,
1995
- .changes_events_max = Operation
1996
- .get_change_events.event_max(options.batch_size_limit),
1997
- .in_flight_max = options.in_flight_max,
1998
- .pulse_expiries_max = @max(
1999
- Operation.create_transfers.event_max(options.batch_size_limit),
2000
- Operation.deprecated_create_transfers_unbatched.event_max(
2001
- options.batch_size_limit,
2002
- ),
2003
- ),
2004
- },
2005
- .transfer_id_permutation = IdPermutation.generate(prng),
2006
- .operations = .{
2007
- .create_accounts = prng.range_inclusive(u64, 1, 10),
2008
- .create_transfers = prng.range_inclusive(u64, 1, 100),
2009
- .lookup_accounts = prng.range_inclusive(u64, 1, 20),
2010
- .lookup_transfers = prng.range_inclusive(u64, 1, 20),
2011
- .get_account_transfers = prng.range_inclusive(u64, 1, 20),
2012
- .get_account_balances = prng.range_inclusive(u64, 1, 20),
2013
- .query_accounts = prng.range_inclusive(u64, 1, 20),
2014
- .query_transfers = prng.range_inclusive(u64, 1, 20),
2015
- .get_change_events = prng.range_inclusive(u64, 1, 20),
2016
-
2017
- .deprecated_create_accounts_unbatched = prng.range_inclusive(u64, 1, 10),
2018
- .deprecated_create_transfers_unbatched = prng.range_inclusive(u64, 1, 100),
2019
- .deprecated_lookup_accounts_unbatched = prng.range_inclusive(u64, 1, 20),
2020
- .deprecated_lookup_transfers_unbatched = prng.range_inclusive(u64, 1, 20),
2021
- .deprecated_get_account_transfers_unbatched = prng.range_inclusive(u64, 1, 20),
2022
- .deprecated_get_account_balances_unbatched = prng.range_inclusive(u64, 1, 20),
2023
- .deprecated_query_accounts_unbatched = prng.range_inclusive(u64, 1, 20),
2024
- .deprecated_query_transfers_unbatched = prng.range_inclusive(u64, 1, 20),
2025
- },
2026
- .create_account_invalid_probability = ratio(1, 100),
2027
- .create_transfer_invalid_probability = ratio(1, 100),
2028
- .create_transfer_limit_probability = ratio(prng.int_inclusive(u8, 100), 100),
2029
- .create_transfer_pending_probability = ratio(prng.range_inclusive(u8, 1, 100), 100),
2030
- .create_transfer_post_probability = ratio(prng.range_inclusive(u8, 1, 50), 100),
2031
- .create_transfer_void_probability = ratio(prng.range_inclusive(u8, 1, 50), 100),
2032
- .create_transfer_retry_probability = ratio(prng.range_inclusive(u8, 1, 10), 100),
2033
- .lookup_account_invalid_probability = ratio(1, 100),
2034
-
2035
- .account_filter_invalid_account_probability = ratio(
2036
- prng.range_inclusive(u8, 1, 20),
2037
- 100,
2038
- ),
2039
- .account_filter_timestamp_range_probability = ratio(
2040
- prng.range_inclusive(u8, 1, 80),
2041
- 100,
2042
- ),
2043
-
2044
- .query_filter_not_found_probability = ratio(prng.range_inclusive(u8, 1, 20), 100),
2045
- .query_filter_timestamp_range_probability = ratio(
2046
- prng.range_inclusive(u8, 1, 80),
2047
- 100,
2048
- ),
2049
-
2050
- .lookup_transfer = .{
2051
- .delivered = prng.range_inclusive(u64, 1, 10),
2052
- .sending = prng.range_inclusive(u64, 1, 10),
2053
- },
2054
- .lookup_transfer_span_mean = prng.range_inclusive(usize, 10, 1000),
2055
- .account_limit_probability = ratio(prng.int_inclusive(u8, 80), 100),
2056
- .account_history_probability = ratio(prng.int_inclusive(u8, 80), 100),
2057
- .linked_valid_probability = ratio(prng.int_inclusive(u8, 100), 100),
2058
- // 100% chance: this only applies to consecutive invalid transfers, which are rare.
2059
- .linked_invalid_probability = ratio(100, 100),
2060
- // One second.
2061
- .pending_timeout_mean = 1,
2062
- .accounts_batch_size_min = 0,
2063
- .accounts_batch_size_span = prng.range_inclusive(
2064
- usize,
2065
- 1,
2066
- batch_create_accounts_limit,
2067
- ),
2068
- .transfers_batch_size_min = 0,
2069
- .transfers_batch_size_span = prng.range_inclusive(
2070
- usize,
2071
- 1,
2072
- batch_create_transfers_limit,
2073
- ),
2074
- .transfers_retry_failed_max = 128,
2075
- .transfers_retry_exists_max = 128,
2076
- };
2077
- }
2078
- };
2079
- }