ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

如何用 Anoma 节点存储验证 write、append 与 commit 的读取可见性?

如何用 Anoma 节点存储验证 write、append 与 commit 的读取可见性? 如何用 Anoma 节点存储验证 write、append 与 commit 的读取可见性【免费下载链接】anoma-archiveReference implementation of Anoma项目地址: https://gitcode.com/GitHub_Trending/an/anoma-archive在 anoma-archiveAnoma 参考实现Elixir/OTP 项目中节点状态由存储引擎 Anoma.Node.Transaction.Storage 承载它是一张带时间戳height的表支撑型键值存储。如果你刚读完它的接口文档或需要在开发中确认「write 之后同高度能否读到」「append 的集合是否按预期合并」「commit 之后数据是否真的落到了 Mnesia 表」这篇文章给出一条可以在本仓库内直接执行的验证路径从 iex 逐条调用存储 API 并核对返回值再到mix test用仓库自带的断言示例批量复核。环境与准备项目 mix.exs 声明elixir: ~ 1.17依赖通过mix deps.get拉取外部 git 依赖清单见 global_deps.exs。编译后可进入交互环境iex -S mix。下文代码中的node_id是同一个字符串例如verify-storage-1。同一个存储实例的所有调用必须传同一个node_id否则会路由到不同的 Storage 进程内部通过Registry.via(node_id, __MODULE__)寻址。仓库 USAGE.md 说明这类示例节点是 ephemeral 的每次启动都是干净状态可以随启随弃。本文只启动 Storage 引擎本身不需要拉起完整节点。先看懂语义height 决定可见性storage.ex 的 moduledoc 给出了读写的统一规则以 height 为时间轴写入想写入 heightT存储的uncommitted_height必须已经到达T-1写入成功后uncommitted_height变为T。若T比uncommitted_height 1更大调用会被阻塞等到「轮到你写」为止。读取读T uncommitted_height是即时读过去返回该 key 在 T的最新写入值从未写过则返回:absent。读T uncommitted_height是「读未来」调用被挂起直到对应高度的WriteEvent发生才返回。特别地handle_call({:read, {0, _key}}, ...)分支规定读 height 0 恒返回:absent存储从 0 起步0 之前没有写入。commit/3把 uncommitted 的键值与更新历史写入 Mnesia 表values、updates并把本轮交易写入blocks表uncommitted_height保持不变下一轮写入仍从height 1继续。这套语义是后面每一步「期望读到什么」的依据。启动 Storage 引擎iex alias Anoma.Node.Transaction.Storage iex node_id verify-storage-1 iex {:ok, _pid} Storage.start_link(node_id: node_id)start_link接受node_id: String.t()与可选的uncommitted_height默认 0表示尚未写入过。启动时会初始化该节点名下的 Mnesia 表storage.ex 中init/1调用Tables.initialize_tables_for_node/1。验证 write同高度写入立刻可读最小验证路径来自仓库示例 e_transaction.ex 的write_then_read/1与write_then_read_other/1Storage.write(node_id, {1, [{[abc], 123}]}) # 在 height 1 写入 key [abc] 123 Storage.read(node_id, {1, [abc]}) # {:ok, 123} Storage.read(node_id, {1, [def]}) # :absent从未写过的 key预期结果来自示例中的模式匹配断言{:ok, 123} Storage.read(node_id, {1, [abc]})、:absent Storage.read(node_id, {1, [def]})。如果read返回的不是{:ok, 123}说明调用时uncommitted_height与写入高度不一致见文末排查项。也可以一次写多个 key 再分别读回示例write_multiple_then_read/1Storage.write(node_id, {1, [{[abc], 123}, {[bcd], 231}]}) Storage.read(node_id, {1, [abc]}) # {:ok, 123} Storage.read(node_id, {1, [bcd]}) # {:ok, 231}验证 append集合值的按高度合并append/2的语义见 storage.ex 的append/2文档把新值与该 key 最近一次值的MapSet.union合并因此它只对集合值有意义。示例append_then_read/1new_set MapSet.new([value]) Storage.append(node_id, {1, [{[set], new_set}]}) Storage.read(node_id, {1, [set]}) # {:ok, #MapSet[value]}同高度对同一 key append 两次示例append_then_read_several/1set1 MapSet.new([value1]) set2 MapSet.new([value2]) Storage.append(node_id, {1, [{[set], set1}, {[set], set2}]}) new_set MapSet.new([value1, value2]) Storage.read(node_id, {1, [set]}) # {:ok, ^new_set}跨高度 append 会在上一高度的值上继续累加示例append_twice_then_read/1set1 MapSet.new([value1]) Storage.append(node_id, {1, [{[set], set1}]}) Storage.read(node_id, {1, [set]}) # {:ok, ^set1} set2 MapSet.new([value2]) Storage.append(node_id, {2, [{[set], set2}]}) appended_set MapSet.new([value1, value2]) Storage.read(node_id, {2, [set]}) # {:ok, ^appended_set}另外add/2提供同一高度内「write append」的组合入口示例add_append/1# 先执行 append_then_read 的初始化后在 height 2 同时 write 与 append new_value_set MapSet.new([new_value]) Storage.add(node_id, {2, %{ write: [{[abc], 234}], append: [{[set], new_value_set}] }}) Storage.read(node_id, {2, [abc]}) # {:ok, 234} Storage.read(node_id, {2, [set]}) # {:ok, MapSet.new([new_value, value])}注意write在同一高度只写入一次Map.put_new而append会逐条合并——这正是两者返回值形态不同的原因。读取「未来高度」会阻塞到写入发生如果你在读之前写入还没落地read不会立刻返回而是挂起等待对应高度的WriteEvent。示例write_future_then_write_present/1的写法# height 2 的写入先被阻塞此时 uncommitted_height 还是 0 Task.async(fn - Storage.write(node_id, {2, [{[abc], 123}]}) end) # height 2 的读同样要等 height 2 的写入完成 task2 Task.async(fn - Storage.read(node_id, {2, [abc]}) end) Storage.write(node_id, {1, [{[other], 999}]}) # 推进到 height 1 {:ok, 123} Task.await(task2)更完整的交错场景是示例complicated_storage/1并发发起 height 3/2/1/0 的读主进程先写 height 1 的[def]、再让 height 2 的写[abc] 123排队、最后写 height 3 的[abc] 401四个读的最终结果为task1: {:ok, 401} # height 3读到 height 3 的写入 task2: {:ok, 123} # height 2读到 height 2 的写入 task3: :absent # height 1height 1 只写了 [def][abc] 尚未出现 task4: :absent # height 0恒为 :absent这组结果直接体现了「读 T 返回 T 的最新值」与「height 0 恒 absent」两条规则。验证 commit落表后可直接查询 Mnesiacommit/3的调用形态示例append_twice_then_read_with_commit/1set1 MapSet.new([value1]) Storage.append(node_id, {1, [{[set], set1}]}) Storage.read(node_id, {1, [set]}) # {:ok, ^set1} Storage.commit(node_id, 1, nil) # 第二参数是块轮次 round第三参数是本轮交易列表或 nil set2 MapSet.new([value2]) Storage.append(node_id, {2, [{[set], set2}]}) appended_set MapSet.new([value1, value2]) Storage.read(node_id, {2, [set]}) # {:ok, ^appended_set}这里的可验证点是commit 后uncommitted_height不变height 2 的 append 仍能读到 height 1 的值——说明读取路径能穿透到已提交的 Mnesia 表。要确认数据确实落表可用 Storage 暴露的用户查询 APIvalues_table/1、updates_table/1、blocks_table/1直接读 Mnesia# 已提交的键值 {:atomic, [{values_table, {1, [set]}, value}]} :mnesia.transaction(fn - :mnesia.read({Storage.values_table(node_id), {1, [set]}}) end) # 该 key 的全部更新高度降序 {:atomic, updates} :mnesia.transaction(fn - :mnesia.read({Storage.updates_table(node_id), [set]}) end)blocks表则记录每轮提交了什么。示例zero_counter_submit/1展示了完整的验证手法先:mnesia.subscribe({:table, blocks_table, :simple})监听写事件执行交易流水线后用assert_receive收到{^blocks_table, [anoma, block, 1], _}的写入事件再在 Mnesia 事务里读出块内容并和预期的Mempool.Tx名词做模式匹配{:atomic, block} :mnesia.transaction(fn - :mnesia.read({Storage.blocks_table(node_id), [anoma, block, 1]}) end)在iex里可以直接执行同样的:mnesia.read语句查看返回值:mnesia.subscribe/1与assert_receive属于 ExUnit 上下文交互式验证时可省略订阅只保留表读取。用仓库自带测试复核仓库把 e_transaction.ex 的全部示例函数批量生成为 ExUnit 测试test/transaction_test.exs 通过use TestHelper.GenerateExampleTests, for: Anoma.Node.Examples.ETransaction为每个示例函数建一个测试。运行mix test test/transaction_test.exs每个测试即一次「操作 模式匹配断言」例如append_twice_then_read会断言{:ok, ^appended_set} Storage.read(node_id, {2, [set]})append_twice_then_read_with_commit会断言 commit 之后 height 2 的读仍返回并集。测试通过就是上面各步可见性行为的批量复核。也可以只跑与 commit 相关的单个示例来缩小验证面mix test test/transaction_test.exs --only storage_commit--only依赖 ExUnit 的 tag 机制若你的环境里该 tag 未生效直接跑整文件mix test test/transaction_test.exs即可。限制与排查写的高度必须恰好是uncommitted_height 1write/2在T uncommitted_height 1时会阻塞调用直到等待的高度被写入block_spawnHeightFilter事件。如果你发现write迟迟不返回先确认前序高度的写入是否已完成。读未来高度必然挂起read(node_id, {T, key})在T uncommitted_height时会等到 heightT的WriteEvent。验证「阻塞」行为本身时用Task.asyncTask.await或测试里的assert_receive来观察返回时序不要让主进程直接同步读未来。height 0 的读永远是:absent这与存储从 0 起步、uncommitted_height初值为 0 的定义一致。commit/3的第一个参数是块轮次block_round决定blocks表条目的 key[anoma, block, round]示例中直接传1/2与写入高度对齐。本文所有高度、key、数值123、value1等均取自仓库示例中的断言值属于文档示例它们用于构造可核对的期望结果而不是生产数据的固定预期。完成上述验证后Storage 的行为写入、集合合并、提交落表就有了逐条对应的返回值证据。若要继续看 Storage 在完整交易流水线中的角色Mempool 执行、分片 ordering仓库中 e_mempool.ex 与test/mempool_test.exs是下一步的对应入口。【免费下载链接】anoma-archiveReference implementation of Anoma项目地址: https://gitcode.com/GitHub_Trending/an/anoma-archive创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表