<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
    <title>Gabriel Koerich</title>
    <subtitle>Senior software engineer (15+ years). Solana&#x2F;Rust DeFi, AI agent orchestration, and engineering workflow automation. Available for AI consulting.</subtitle>
    <link rel="self" type="application/atom+xml" href="https://gabrielkoerich.com/atom.xml"/>
    <link rel="alternate" type="text/html" href="https://gabrielkoerich.com/"/>
    <generator uri="https://www.getzola.org/">Zola</generator>
    <updated>2026-10-16T00:00:00+00:00</updated>
    <id>https://gabrielkoerich.com/atom.xml</id>
    <entry xml:lang="en">
        <title>Four ways to count to five</title>
        <published>2026-10-16T00:00:00+00:00</published>
        <updated>2026-10-16T00:00:00+00:00</updated>
        <author>
            <name>Gabriel Koerich</name>
        </author>
        <link rel="alternate" type="text/html" href="https://gabrielkoerich.com/posts/four-ways-to-count-to-five/"/>
        <id>https://gabrielkoerich.com/posts/four-ways-to-count-to-five/</id>
        <summary>A recursive Fibonacci Solana program that only ran in my tests, rebuilt four ways with split crates, Anchor 1, Anchor 2 and Pinocchio, and measured.</summary>
        <content type="html" xml:base="https://gabrielkoerich.com/posts/four-ways-to-count-to-five/">&lt;p&gt;In February I wanted to see how small a Solana program could get without a framework. &lt;code&gt;solana-program&lt;&#x2F;code&gt; had been split into small crates, so I wrote &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;gabrielkoerich&#x2F;solana-fib-cpi&quot;&gt;solana-fib-cpi&lt;&#x2F;a&gt; with six of them: &lt;code&gt;solana-account-info&lt;&#x2F;code&gt;, &lt;code&gt;solana-cpi&lt;&#x2F;code&gt;, &lt;code&gt;solana-instruction&lt;&#x2F;code&gt;, &lt;code&gt;solana-msg&lt;&#x2F;code&gt;, &lt;code&gt;solana-program-entrypoint&lt;&#x2F;code&gt; and &lt;code&gt;solana-pubkey&lt;&#x2F;code&gt;. No Anchor, no &lt;code&gt;solana-program&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;The program computes Fibonacci numbers in the most wasteful way I could think of. One instruction creates a PDA at &lt;code&gt;[&amp;quot;fib&amp;quot;, payer]&lt;&#x2F;code&gt; with 25 bytes, &lt;code&gt;a&lt;&#x2F;code&gt;, &lt;code&gt;b&lt;&#x2F;code&gt;, &lt;code&gt;n&lt;&#x2F;code&gt; and a bump. Then the program calls itself through CPI, and every call advances &lt;code&gt;(a, b, n)&lt;&#x2F;code&gt; to &lt;code&gt;(b, a + b, n - 1)&lt;&#x2F;code&gt; until &lt;code&gt;n&lt;&#x2F;code&gt; reaches zero. Solana allows direct self-recursion and refuses indirect reentrancy, and the &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;solana.com&#x2F;docs&#x2F;core&#x2F;cpi&quot;&gt;CPI docs&lt;&#x2F;a&gt; put the stack depth at 5, so &lt;code&gt;n = 4&lt;&#x2F;code&gt; is as far as one transaction goes. Four steps from &lt;code&gt;a = 0, b = 1&lt;&#x2F;code&gt; ends at &lt;code&gt;b = 5&lt;&#x2F;code&gt;. Hence the title.&lt;&#x2F;p&gt;
&lt;p&gt;The tests passed. I meant to compare it with the new frameworks. The first thing I found was that it could not run on mainnet.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;it-only-ran-in-my-tests&quot;&gt;It only ran in my tests &lt;a class=&quot;zola-anchor&quot; href=&quot;#it-only-ran-in-my-tests&quot; aria-label=&quot;Link to this section&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;My tests registered the program with &lt;code&gt;processor!&lt;&#x2F;code&gt;, which runs it as native Rust inside the test. For this post I built every version with &lt;code&gt;cargo build-sbf&lt;&#x2F;code&gt; and ran the real &lt;code&gt;.so&lt;&#x2F;code&gt; in &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;LiteSVM&#x2F;litesvm&quot;&gt;LiteSVM&lt;&#x2F;a&gt; with the mainnet feature set. The February program failed on the first self-CPI:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;text&quot; class=&quot;language-text z-code&quot;&gt;&lt;code class=&quot;language-text&quot; data-lang=&quot;text&quot;&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;Program 3yDL675dKEnNZfvDz6dznB3n3qpXpiZJK4pSYkSAd1Xr invoke [1]
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;Program 11111111111111111111111111111111 invoke [2]
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;Program 11111111111111111111111111111111 success
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;Program log: init: a=0 b=1 n=4
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;Unknown program 3yDL675dKEnNZfvDz6dznB3n3qpXpiZJK4pSYkSAd1Xr
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;Program 3yDL675dKEnNZfvDz6dznB3n3qpXpiZJK4pSYkSAd1Xr failed: An account required by the instruction is missing
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;On mainnet, a program can only invoke a program that is one of its own instruction accounts, and calling yourself is no exception. My instruction passed the PDA, the payer and the System Program, never the program itself.&lt;&#x2F;p&gt;
&lt;p&gt;Switching my tests from the native processor to the &lt;code&gt;.so&lt;&#x2F;code&gt; did not catch it either. All six still passed. Agave 2.x has a feature gate, &lt;code&gt;lift_cpi_caller_restriction&lt;&#x2F;code&gt;, that looks the callee up in the whole transaction instead, and &lt;code&gt;solana-program-test&lt;&#x2F;code&gt; turns on every feature by default. That one was never activated on mainnet: its feature account does not exist, and newer Agave releases have dropped it. With the feature turned off in the test, three of the six fail with the same &lt;code&gt;Unknown program&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;The fix is a fourth readonly account, the program, forwarded on every self-CPI. It is in &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;gabrielkoerich&#x2F;solana-fib-cpi&quot;&gt;the repo&lt;&#x2F;a&gt; now, with tests that load the &lt;code&gt;.so&lt;&#x2F;code&gt; and turn that feature off, and every version below has it. I wrote about the same trap in the post on &lt;a href=&quot;https:&#x2F;&#x2F;gabrielkoerich.com&#x2F;posts&#x2F;running-a-quant-book-on-hyperliquid&#x2F;&quot;&gt;my quant book&lt;&#x2F;a&gt;, from the other side: a test only proves what it actually runs, and here that includes the runtime&#x27;s feature set.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;what-changed-since-february&quot;&gt;What changed since February &lt;a class=&quot;zola-anchor&quot; href=&quot;#what-changed-since-february&quot; aria-label=&quot;Link to this section&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;Before measuring anything I checked what my February code was standing on.&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;solana-program&lt;&#x2F;code&gt; now lives in &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;anza-xyz&#x2F;solana-sdk&quot;&gt;anza-xyz&#x2F;solana-sdk&lt;&#x2F;a&gt; and is a facade: version 5.1.0 is mostly re-exports of the small crates. The ones I used at &lt;code&gt;2&lt;&#x2F;code&gt; are now at 3.x and 4.x.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;Pubkey&lt;&#x2F;code&gt; is now &lt;code&gt;Address&lt;&#x2F;code&gt;. SDK v3 introduced &lt;code&gt;Address&lt;&#x2F;code&gt;, and &lt;code&gt;Pubkey&lt;&#x2F;code&gt; is a type alias of it.&lt;&#x2F;li&gt;
&lt;li&gt;Rent got cheaper. I had hardcoded &lt;code&gt;(128 + len) * 3480 * 2&lt;&#x2F;code&gt;. &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;solana-foundation&#x2F;solana-improvement-documents&#x2F;blob&#x2F;main&#x2F;proposals&#x2F;0194-deprecate-rent-exemption-threshold.md&quot;&gt;SIMD-0194&lt;&#x2F;a&gt; replaced that pair with a single &lt;code&gt;lamports_per_byte&lt;&#x2F;code&gt; of 6,960, and SIMD-0437 has been lowering it in steps since. Mainnet charges 5,080 per byte today, so my program overpaid by about 287,000 lamports per PDA, and the gap grows with each step down. Read &lt;code&gt;Rent::get()&lt;&#x2F;code&gt;.&lt;&#x2F;li&gt;
&lt;li&gt;The stack depth is still 5, so four nested CPIs. SIMD-0268 raises that to 8 nested CPIs and is accepted, but its feature is not active on mainnet yet.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h2 id=&quot;what-dropping-solana-program-buys&quot;&gt;What dropping &lt;code&gt;solana-program&lt;&#x2F;code&gt; buys &lt;a class=&quot;zola-anchor&quot; href=&quot;#what-dropping-solana-program-buys&quot; aria-label=&quot;Link to this section&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;The standard entrypoint in &lt;code&gt;solana-program-entrypoint&lt;&#x2F;code&gt; deserializes the input buffer into a &lt;code&gt;Vec&lt;&#x2F;code&gt; of &lt;code&gt;AccountInfo&lt;&#x2F;code&gt;, each one holding its lamports and data behind &lt;code&gt;Rc&amp;lt;RefCell&amp;lt;...&amp;gt;&amp;gt;&lt;&#x2F;code&gt;. That is a heap allocation and a set of reference counted cells before your code reads a single byte.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;anza-xyz&#x2F;pinocchio&quot;&gt;Pinocchio&lt;&#x2F;a&gt; does the opposite. Its README describes it as a library with &amp;quot;no external dependencies&amp;quot; whose entrypoint works &amp;quot;with no copies or allocations&amp;quot;. An &lt;code&gt;AccountView&lt;&#x2F;code&gt; points into the input buffer the runtime already wrote, and the crate is &lt;code&gt;no_std&lt;&#x2F;code&gt;, so a program can declare &lt;code&gt;no_allocator!()&lt;&#x2F;code&gt; and have no heap at all. The account views now come from the SDK itself, &lt;code&gt;solana-account-view&lt;&#x2F;code&gt; and &lt;code&gt;solana-instruction-view&lt;&#x2F;code&gt;, and Pinocchio is a thin layer over them.&lt;&#x2F;p&gt;
&lt;p&gt;The new frameworks build on that layer. Anchor v2 is the one most people will meet: its crates depend on Pinocchio 0.11, &lt;code&gt;pinocchio-system&lt;&#x2F;code&gt; and a handful of split crates, and not on &lt;code&gt;solana-program&lt;&#x2F;code&gt; at all. Its &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;otter-sec&#x2F;anchor&#x2F;tree&#x2F;v2.0.0-rc.1&#x2F;lang-v2&quot;&gt;README&lt;&#x2F;a&gt; calls it &amp;quot;a drop-in speedup for Anchor v1&amp;quot; that is &amp;quot;up to 94% smaller and 3× faster or more per instruction&amp;quot;. Those are its own numbers, so I measured mine.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;one-program-four-ways&quot;&gt;One program, four ways &lt;a class=&quot;zola-anchor&quot; href=&quot;#one-program-four-ways&quot; aria-label=&quot;Link to this section&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;Same logic, same accounts, same 25 bytes of state (plus the 8 byte discriminator in both Anchor versions), four builds:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;February, the split crates at 2.x, with the account fix and nothing else.&lt;&#x2F;li&gt;
&lt;li&gt;Anchor 1.2.1, the current stable release, on the split crates and their standard entrypoint.&lt;&#x2F;li&gt;
&lt;li&gt;Pinocchio 0.11.2, with &lt;code&gt;pinocchio-system&lt;&#x2F;code&gt; for the account creation.&lt;&#x2F;li&gt;
&lt;li&gt;Anchor 2.0.0-rc.1, the first release candidate on crates.io.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;All four were built with &lt;code&gt;cargo build-sbf&lt;&#x2F;code&gt; 3.1.10 and platform-tools v1.52, and run in LiteSVM with the mainnet feature set. Each transaction runs init with &lt;code&gt;n = 4&lt;&#x2F;code&gt;, five levels deep, and every version ends at &lt;code&gt;a = 3, b = 5&lt;&#x2F;code&gt;. Logging costs a lot and every library logs differently, so the main builds have no logs. Every number held across three runs. The code and the benchmark are in &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;gabrielkoerich&#x2F;gabrielkoerich&#x2F;tree&#x2F;main&#x2F;examples&#x2F;four-ways-to-count-to-five&quot;&gt;examples&#x2F;four-ways-to-count-to-five&lt;&#x2F;a&gt;, and &lt;code&gt;just bench&lt;&#x2F;code&gt; reproduces the table.&lt;&#x2F;p&gt;
&lt;p&gt;The Pinocchio version reads close to the February one, with views instead of infos:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; class=&quot;language-rust z-code&quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-storage z-type z-rust&quot;&gt;let&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;[&lt;&#x2F;span&gt;pda&lt;span class=&quot;z-punctuation z-separator z-rust&quot;&gt;,&lt;&#x2F;span&gt; payer&lt;span class=&quot;z-punctuation z-separator z-rust&quot;&gt;,&lt;&#x2F;span&gt; system&lt;span class=&quot;z-punctuation z-separator z-rust&quot;&gt;,&lt;&#x2F;span&gt; program&lt;span class=&quot;z-punctuation z-separator z-rust&quot;&gt;,&lt;&#x2F;span&gt; &lt;span class=&quot;z-keyword z-operator z-range z-rust&quot;&gt;..&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; &lt;span class=&quot;z-keyword z-operator z-assignment z-rust&quot;&gt;=&lt;&#x2F;span&gt; accounts &lt;span class=&quot;z-keyword z-control z-rust&quot;&gt;else&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-block z-begin z-rust&quot;&gt;{&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;    &lt;span class=&quot;z-keyword z-control z-rust&quot;&gt;return&lt;&#x2F;span&gt; &lt;span class=&quot;z-support z-type z-rust&quot;&gt;Err&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-path z-rust&quot;&gt;ProgramError&lt;span class=&quot;z-punctuation z-accessor z-rust&quot;&gt;::&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;NotEnoughAccountKeys&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-terminator z-rust&quot;&gt;;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-block z-end z-rust&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-terminator z-rust&quot;&gt;;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-keyword z-control z-rust&quot;&gt;if&lt;&#x2F;span&gt; pda&lt;span class=&quot;z-punctuation z-accessor z-dot z-rust&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-support z-function z-rust&quot;&gt;is_data_empty&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-block z-begin z-rust&quot;&gt;{&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;    &lt;span class=&quot;z-comment z-line z-double-slash z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-rust&quot;&gt;&#x2F;&#x2F;&lt;&#x2F;span&gt; ... check the signer, the System Program and the PDA address
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;    &lt;span class=&quot;z-meta z-path z-rust&quot;&gt;CreateAccount&lt;span class=&quot;z-punctuation z-accessor z-rust&quot;&gt;::&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;with_minimum_balance&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;payer&lt;span class=&quot;z-punctuation z-separator z-rust&quot;&gt;,&lt;&#x2F;span&gt; pda&lt;span class=&quot;z-punctuation z-separator z-rust&quot;&gt;,&lt;&#x2F;span&gt; &lt;span class=&quot;z-constant z-other z-rust&quot;&gt;DATA_LEN&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-rust&quot;&gt;,&lt;&#x2F;span&gt; program_id&lt;span class=&quot;z-punctuation z-separator z-rust&quot;&gt;,&lt;&#x2F;span&gt; &lt;span class=&quot;z-support z-type z-rust&quot;&gt;None&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator z-rust&quot;&gt;?&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;        &lt;span class=&quot;z-punctuation z-accessor z-dot z-rust&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-support z-function z-rust&quot;&gt;invoke_signed&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator z-bitwise z-rust&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;[&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-path z-rust&quot;&gt;Signer&lt;span class=&quot;z-punctuation z-accessor z-rust&quot;&gt;::&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;from&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator z-bitwise z-rust&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;seeds&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator z-rust&quot;&gt;?&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-terminator z-rust&quot;&gt;;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;    &lt;span class=&quot;z-comment z-line z-double-slash z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-rust&quot;&gt;&#x2F;&#x2F;&lt;&#x2F;span&gt; ... write a = 0, b = 1, n and the bump straight into the account
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-block z-end z-rust&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Source: &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;gabrielkoerich&#x2F;gabrielkoerich&#x2F;blob&#x2F;main&#x2F;examples&#x2F;four-ways-to-count-to-five&#x2F;pinocchio&#x2F;src&#x2F;lib.rs&quot;&gt;&lt;code&gt;pinocchio&#x2F;src&#x2F;lib.rs&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Anchor v2 looks like Anchor, minus the lifetimes, with state that is read in place:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; class=&quot;language-rust z-code&quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-annotation z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-annotation z-rust&quot;&gt;#&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;[&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-annotation z-rust&quot;&gt;derive&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-annotation z-parameters z-rust&quot;&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-annotation z-parameters z-rust&quot;&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;Accounts&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-annotation z-parameters z-rust&quot;&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-struct z-rust&quot;&gt;&lt;span class=&quot;z-storage z-modifier z-rust&quot;&gt;pub&lt;&#x2F;span&gt; &lt;span class=&quot;z-storage z-type z-struct z-rust&quot;&gt;struct&lt;&#x2F;span&gt; &lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-struct z-rust&quot;&gt;&lt;span class=&quot;z-entity z-name z-struct z-rust&quot;&gt;Fib&lt;&#x2F;span&gt; &lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-struct z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-block z-begin z-rust&quot;&gt;{&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-struct z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;    &lt;span class=&quot;z-meta z-annotation z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-annotation z-rust&quot;&gt;#&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;[&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-annotation z-rust&quot;&gt;account&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-annotation z-parameters z-rust&quot;&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-annotation z-parameters z-rust&quot;&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-struct z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-meta z-annotation z-rust&quot;&gt;&lt;span class=&quot;z-meta z-annotation z-parameters z-rust&quot;&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;        init_if_needed&lt;span class=&quot;z-punctuation z-separator z-rust&quot;&gt;,&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-struct z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-meta z-annotation z-rust&quot;&gt;&lt;span class=&quot;z-meta z-annotation z-parameters z-rust&quot;&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;        payer &lt;span class=&quot;z-keyword z-operator z-rust&quot;&gt;=&lt;&#x2F;span&gt; payer&lt;span class=&quot;z-punctuation z-separator z-rust&quot;&gt;,&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-struct z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-meta z-annotation z-rust&quot;&gt;&lt;span class=&quot;z-meta z-annotation z-parameters z-rust&quot;&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;        seeds &lt;span class=&quot;z-keyword z-operator z-rust&quot;&gt;=&lt;&#x2F;span&gt; [&lt;span class=&quot;z-string z-quoted z-double z-rust&quot;&gt;&lt;span class=&quot;z-storage z-type z-string z-rust&quot;&gt;b&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-rust&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;fib&lt;span class=&quot;z-punctuation z-definition z-string z-end z-rust&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-rust&quot;&gt;,&lt;&#x2F;span&gt; payer.&lt;span class=&quot;z-meta z-function-call z-rust&quot;&gt;&lt;span class=&quot;z-variable z-function z-rust&quot;&gt;address&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-rust&quot;&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;.&lt;span class=&quot;z-meta z-function-call z-rust&quot;&gt;&lt;span class=&quot;z-variable z-function z-rust&quot;&gt;as_ref&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-rust&quot;&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;]&lt;span class=&quot;z-punctuation z-separator z-rust&quot;&gt;,&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-struct z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-meta z-annotation z-rust&quot;&gt;&lt;span class=&quot;z-meta z-annotation z-parameters z-rust&quot;&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;        bump &lt;span class=&quot;z-keyword z-operator z-rust&quot;&gt;=&lt;&#x2F;span&gt; pda.bump&lt;span class=&quot;z-punctuation z-separator z-rust&quot;&gt;,&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-struct z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-meta z-annotation z-rust&quot;&gt;&lt;span class=&quot;z-meta z-annotation z-parameters z-rust&quot;&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;    &lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-annotation z-parameters z-rust&quot;&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-struct z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;    &lt;span class=&quot;z-storage z-modifier z-rust&quot;&gt;pub&lt;&#x2F;span&gt; &lt;span class=&quot;z-variable z-other z-member z-rust&quot;&gt;pda&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-type z-rust&quot;&gt;:&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-generic z-rust&quot;&gt;Account&lt;span class=&quot;z-punctuation z-definition z-generic z-begin z-rust&quot;&gt;&amp;lt;&lt;&#x2F;span&gt;FibState&lt;span class=&quot;z-punctuation z-definition z-generic z-end z-rust&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;,
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-struct z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;    &lt;span class=&quot;z-meta z-annotation z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-annotation z-rust&quot;&gt;#&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;[&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-annotation z-rust&quot;&gt;account&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-annotation z-parameters z-rust&quot;&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-annotation z-parameters z-rust&quot;&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;mut&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-annotation z-parameters z-rust&quot;&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-struct z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;    &lt;span class=&quot;z-storage z-modifier z-rust&quot;&gt;pub&lt;&#x2F;span&gt; &lt;span class=&quot;z-variable z-other z-member z-rust&quot;&gt;payer&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-type z-rust&quot;&gt;:&lt;&#x2F;span&gt; Signer,
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-struct z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;    &lt;span class=&quot;z-storage z-modifier z-rust&quot;&gt;pub&lt;&#x2F;span&gt; &lt;span class=&quot;z-variable z-other z-member z-rust&quot;&gt;system_program&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-type z-rust&quot;&gt;:&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-generic z-rust&quot;&gt;Program&lt;span class=&quot;z-punctuation z-definition z-generic z-begin z-rust&quot;&gt;&amp;lt;&lt;&#x2F;span&gt;System&lt;span class=&quot;z-punctuation z-definition z-generic z-end z-rust&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;,
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-struct z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;    &lt;span class=&quot;z-storage z-modifier z-rust&quot;&gt;pub&lt;&#x2F;span&gt; &lt;span class=&quot;z-variable z-other z-member z-rust&quot;&gt;fib_program&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-type z-rust&quot;&gt;:&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-generic z-rust&quot;&gt;Program&lt;span class=&quot;z-punctuation z-definition z-generic z-begin z-rust&quot;&gt;&amp;lt;&lt;&#x2F;span&gt;FibProgram&lt;span class=&quot;z-punctuation z-definition z-generic z-end z-rust&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;,
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-struct z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-block z-end z-rust&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-annotation z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-annotation z-rust&quot;&gt;#&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;[&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-annotation z-rust&quot;&gt;account&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-struct z-rust&quot;&gt;&lt;span class=&quot;z-storage z-modifier z-rust&quot;&gt;pub&lt;&#x2F;span&gt; &lt;span class=&quot;z-storage z-type z-struct z-rust&quot;&gt;struct&lt;&#x2F;span&gt; &lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-struct z-rust&quot;&gt;&lt;span class=&quot;z-entity z-name z-struct z-rust&quot;&gt;FibState&lt;&#x2F;span&gt; &lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-struct z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-block z-begin z-rust&quot;&gt;{&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-struct z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;    &lt;span class=&quot;z-storage z-modifier z-rust&quot;&gt;pub&lt;&#x2F;span&gt; &lt;span class=&quot;z-variable z-other z-member z-rust&quot;&gt;a&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-type z-rust&quot;&gt;:&lt;&#x2F;span&gt; PodU64,
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-struct z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;    &lt;span class=&quot;z-storage z-modifier z-rust&quot;&gt;pub&lt;&#x2F;span&gt; &lt;span class=&quot;z-variable z-other z-member z-rust&quot;&gt;b&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-type z-rust&quot;&gt;:&lt;&#x2F;span&gt; PodU64,
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-struct z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;    &lt;span class=&quot;z-storage z-modifier z-rust&quot;&gt;pub&lt;&#x2F;span&gt; &lt;span class=&quot;z-variable z-other z-member z-rust&quot;&gt;n&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-type z-rust&quot;&gt;:&lt;&#x2F;span&gt; PodU64,
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-struct z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;    &lt;span class=&quot;z-storage z-modifier z-rust&quot;&gt;pub&lt;&#x2F;span&gt; &lt;span class=&quot;z-variable z-other z-member z-rust&quot;&gt;bump&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-type z-rust&quot;&gt;:&lt;&#x2F;span&gt; &lt;span class=&quot;z-storage z-type z-rust&quot;&gt;u8&lt;&#x2F;span&gt;,
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-struct z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-block z-end z-rust&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Source: &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;gabrielkoerich&#x2F;gabrielkoerich&#x2F;blob&#x2F;main&#x2F;examples&#x2F;four-ways-to-count-to-five&#x2F;anchor-v2&#x2F;src&#x2F;lib.rs&quot;&gt;&lt;code&gt;anchor-v2&#x2F;src&#x2F;lib.rs&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;p&gt;The results:&lt;&#x2F;p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Version&lt;&#x2F;th&gt;&lt;th style=&quot;text-align: right&quot;&gt;&lt;code&gt;.so&lt;&#x2F;code&gt; bytes&lt;&#x2F;th&gt;&lt;th style=&quot;text-align: right&quot;&gt;Init, n = 4 (CU)&lt;&#x2F;th&gt;&lt;th style=&quot;text-align: right&quot;&gt;Deepest level alone (CU)&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Anchor 1.2.1&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;147,368&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;33,686&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;4,637&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;February, split crates 2.x&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;41,616&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;14,517&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;878&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Anchor 2.0.0-rc.1&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;42,480&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;17,505&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;766&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Pinocchio 0.11.2&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;13,792&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;7,595&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;90&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;p&gt;The deepest level is the last step, which does the Fibonacci arithmetic and returns without another CPI, so it is close to the fixed cost of entering each program.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;reading-the-numbers&quot;&gt;Reading the numbers &lt;a class=&quot;zola-anchor&quot; href=&quot;#reading-the-numbers&quot; aria-label=&quot;Link to this section&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;Anchor v1 is the expensive one. It costs 4.4 times what Pinocchio costs for the whole transaction, and its binary is more than ten times larger. Part of that is my program shape: with &lt;code&gt;init_if_needed&lt;&#x2F;code&gt; and a plain &lt;code&gt;bump&lt;&#x2F;code&gt;, Anchor v1 runs &lt;code&gt;find_program_address&lt;&#x2F;code&gt; and reads rent on every level, even when the account exists, because it cannot trust a stored bump on an account it might be creating. It also needs &lt;code&gt;exit()&lt;&#x2F;code&gt; before the self-CPI and &lt;code&gt;reload()&lt;&#x2F;code&gt; after, or the outer level writes its stale copy over the inner ones. A v1 program split into separate init and step instructions would pay less. It would also be a different program.&lt;&#x2F;p&gt;
&lt;p&gt;Anchor v2 halves it: 17,505 CU against 33,686, and a binary three and a half times smaller. It reads the bump from the account (&lt;code&gt;bump = pda.bump&lt;&#x2F;code&gt;), works on the account bytes in place, and needs no reload after the CPI. Its optional guardrails cost 88 CU, so turning them off is not where the difference is.&lt;&#x2F;p&gt;
&lt;p&gt;The levels make the comparison cleaner. After init, every recursive step adds a constant amount in each version, and the CPI itself is the same runtime work for all four:&lt;&#x2F;p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Version&lt;&#x2F;th&gt;&lt;th style=&quot;text-align: right&quot;&gt;CU per recursive step&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Anchor 1.2.1&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;6,687&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Anchor 2.0.0-rc.1&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;3,893&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;February, split crates 2.x&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;2,606&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Pinocchio 0.11.2&lt;&#x2F;td&gt;&lt;td style=&quot;text-align: right&quot;&gt;1,160&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;p&gt;Per step, v2 is 1.7 times cheaper than v1 for this program, not the &amp;quot;3× faster&amp;quot; in its README. The README benchmarks other programs, so both can be true. Pinocchio does the whole step for a third of what v2 spends, so most of v2&#x27;s remaining cost is the framework.&lt;&#x2F;p&gt;
&lt;p&gt;The hand-written split crates sit between them. The February version, without logs, is cheaper than Anchor v2 for the whole transaction and slightly more expensive at the deepest level. It still pays for the standard entrypoint, and it has none of Anchor&#x27;s checks.&lt;&#x2F;p&gt;
&lt;p&gt;Pinocchio is the floor, 90 CU for a level that reads 24 bytes, adds two numbers and writes them back. Most of what is left in its 7,595 is the CPIs themselves. The same direction shows up in &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;solana-foundation&#x2F;solana-improvement-documents&#x2F;blob&#x2F;main&#x2F;proposals&#x2F;0266-efficient-token-program.md&quot;&gt;SIMD-0266&lt;&#x2F;a&gt;, where the Token program rewritten on Pinocchio, p-token, takes &lt;code&gt;Approve&lt;&#x2F;code&gt; from 2,904 CU to 124.&lt;&#x2F;p&gt;
&lt;p&gt;Logs are not free either. The February program as I wrote it, with its &lt;code&gt;msg!&lt;&#x2F;code&gt; calls, costs 18,316 CU. Removing them saves 3,799, about a fifth of the transaction.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;what-anchor-v2-changes&quot;&gt;What Anchor v2 changes &lt;a class=&quot;zola-anchor&quot; href=&quot;#what-anchor-v2-changes&quot; aria-label=&quot;Link to this section&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;ul&gt;
&lt;li&gt;No &lt;code&gt;&amp;lt;&#x27;info&amp;gt;&lt;&#x2F;code&gt; lifetimes, and handlers take &lt;code&gt;&amp;amp;mut Context&amp;lt;T&amp;gt;&lt;&#x2F;code&gt;.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;Pubkey&lt;&#x2F;code&gt; becomes &lt;code&gt;Address&lt;&#x2F;code&gt;, following the SDK.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;Account&amp;lt;T&amp;gt;&lt;&#x2F;code&gt; is zero-copy by default, for &lt;code&gt;T: Pod&lt;&#x2F;code&gt;. The Borsh behaviour of v1 moves to &lt;code&gt;BorshAccount&amp;lt;T&amp;gt;&lt;&#x2F;code&gt;.&lt;&#x2F;li&gt;
&lt;li&gt;PDA bumps are computed at compile time when every seed is a literal.&lt;&#x2F;li&gt;
&lt;li&gt;The crate is &lt;code&gt;no_std&lt;&#x2F;code&gt;, with &lt;code&gt;alloc&lt;&#x2F;code&gt; as a default feature.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;It is still a release candidate. &lt;code&gt;2.0.0-rc.1&lt;&#x2F;code&gt; was published on 2026-08-12 and &lt;code&gt;2.0.0-rc.2&lt;&#x2F;code&gt; on 2026-10-12, and the v2 README still says &amp;quot;Alpha. Not audited&amp;quot;. Building this example hit three rough edges: the &lt;code&gt;#[program]&lt;&#x2F;code&gt; derive needed &lt;code&gt;wincode&lt;&#x2F;code&gt; as a direct dependency, there was no generated program type to use in &lt;code&gt;Program&amp;lt;T&amp;gt;&lt;&#x2F;code&gt;, and the self-CPI went through a hidden &lt;code&gt;__cpi_accounts_fib&lt;&#x2F;code&gt; module because the public &lt;code&gt;cpi&lt;&#x2F;code&gt; module disables the entrypoint.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;what-it-costs&quot;&gt;What it costs &lt;a class=&quot;zola-anchor&quot; href=&quot;#what-it-costs&quot; aria-label=&quot;Link to this section&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;Pinocchio&#x27;s 90 CU come with no checks I did not write: the &lt;code&gt;&#x2F;&#x2F; ...&lt;&#x2F;code&gt; in the snippet above is where the signer, the System Program and the PDA address get checked by hand, and the February version is the same. Anchor v2 brings the checks back as a release candidate with the rough edges above. Anchor v1 is the stable release and the most expensive one in every column.&lt;&#x2F;p&gt;
&lt;p&gt;Testing the real &lt;code&gt;.so&lt;&#x2F;code&gt; costs a second harness: every program built with &lt;code&gt;cargo build-sbf&lt;&#x2F;code&gt; and loaded into LiteSVM with the mainnet feature set, next to the &lt;code&gt;processor!&lt;&#x2F;code&gt; tests, which passed for a program that never ran on mainnet.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;what-i-would-do-now&quot;&gt;What I would do now &lt;a class=&quot;zola-anchor&quot; href=&quot;#what-i-would-do-now&quot; aria-label=&quot;Link to this section&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;For a program where compute matters, Pinocchio, with the account views from the SDK. For a program a team has to read and change for years, Anchor v2 once it is released, because it keeps the Anchor shape and drops most of the cost.&lt;&#x2F;p&gt;
&lt;p&gt;The title has an expiry date. SIMD-0268 raises nesting to 8 CPIs once its feature is active on mainnet, and then the same program counts to 34 in one transaction. Which of the four pays most for the extra four levels is the next thing to measure.&lt;&#x2F;p&gt;
</content>
    </entry>
    <entry xml:lang="en">
        <title>Solana without the fork</title>
        <published>2026-10-13T00:00:00+00:00</published>
        <updated>2026-10-13T00:00:00+00:00</updated>
        <author>
            <name>Gabriel Koerich</name>
        </author>
        <link rel="alternate" type="text/html" href="https://gabrielkoerich.com/posts/solana-without-the-fork/"/>
        <id>https://gabrielkoerich.com/posts/solana-without-the-fork/</id>
        <summary>I built a pinocchio program with stock nightly Rust, upstream LLVM and sbpf-linker. It passed every test and came out half the size of the platform-tools build.</summary>
        <content type="html" xml:base="https://gabrielkoerich.com/posts/solana-without-the-fork/">&lt;p&gt;Last week (Oct 9) a tweet from &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;x.com&#x2F;clairefxyz&#x2F;status&#x2F;2108550955570487632&quot;&gt;Claire Fan&lt;&#x2F;a&gt; showed up on my timeline: &amp;quot;For the first time in Solana history, we can generate Solana programs directly from upstream rustc and LLVM!&amp;quot; Starting with &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;blueshift-gg&#x2F;sbpf-linker&quot;&gt;sbpf-linker&lt;&#x2F;a&gt; 0.2.3, a &lt;code&gt;no_std&lt;&#x2F;code&gt; program builds with &lt;code&gt;cargo +nightly build&lt;&#x2F;code&gt;. Claire also wrote: &amp;quot;Anything that doesn&#x27;t work should be considered a bug, and I&#x27;ll fix it!&amp;quot;&lt;&#x2F;p&gt;
&lt;p&gt;I had to try it.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;every-program-goes-through-a-fork&quot;&gt;Every program goes through a fork &lt;a class=&quot;zola-anchor&quot; href=&quot;#every-program-goes-through-a-fork&quot; aria-label=&quot;Link to this section&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;Every Solana program you have deployed was compiled by a fork. Solana runs programs in a virtual machine that executes sBPF, its own flavor of eBPF, and the compiler that produces it is &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;anza-xyz&#x2F;platform-tools&quot;&gt;platform-tools&lt;&#x2F;a&gt;: Anza&#x27;s fork of &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;anza-xyz&#x2F;rust&quot;&gt;rustc&lt;&#x2F;a&gt; and of &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;anza-xyz&#x2F;llvm-project&quot;&gt;LLVM&lt;&#x2F;a&gt;. &lt;code&gt;cargo build-sbf&lt;&#x2F;code&gt; downloads it and builds with it.&lt;&#x2F;p&gt;
&lt;p&gt;Keeping a fork of a compiler current is a lot of work, and it shows in the version numbers. Platform-tools v1.57, the release I tested with, ships rustc 1.95.0-dev from August 17. The nightly I used is rustc 1.101.0. Every Rust feature from those six versions reaches Solana programs late.&lt;&#x2F;p&gt;
&lt;p&gt;sbpf-linker takes another route. Rust already has a &lt;code&gt;bpfel-unknown-none&lt;&#x2F;code&gt; target, and upstream LLVM compiles to it. The linker takes the eBPF object that rustc produces and turns it into an sBPF v3 program. Everything Solana specific lives in a &lt;code&gt;.cargo&#x2F;config.toml&lt;&#x2F;code&gt;: no stack unwinding, a 4 KiB stack frame, a few instruction forms LLVM must not emit, and &lt;code&gt;--cfg=target_os=&amp;quot;solana&amp;quot;&lt;&#x2F;code&gt; so existing crates take their Solana code paths.&lt;&#x2F;p&gt;
&lt;p&gt;The timing works because of sBPF v3. It moves syscalls to link time: a program calls a syscall by the murmur3 hash of its name, so the linker does not need to know anything about how the validator loads programs. I checked the &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;explorer.solana.com&#x2F;address&#x2F;5cC3foj77CWun58pC51ebHFUWavHWKarWyR5UUik7dnC&quot;&gt;feature account&lt;&#x2F;a&gt; on mainnet, and v3 deployment has been active since slot 428,976,000, on June 26. A program built this way can be deployed today. With Agave 4.4, expected in November, v3 &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;solana.com&#x2F;upgrades&#x2F;sbpfv3-programs&quot;&gt;becomes the only format&lt;&#x2F;a&gt; mainnet accepts for new deploys and upgrades.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;reading-it-first&quot;&gt;Reading it first &lt;a class=&quot;zola-anchor&quot; href=&quot;#reading-it-first&quot; aria-label=&quot;Link to this section&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;I read code before I run it, and a linker runs on every build. This one is a pleasant read. sbpf-linker is a handful of Rust files, and it does nothing a linker should not: no network calls, no other processes, and it writes only the &lt;code&gt;.so&lt;&#x2F;code&gt; it was asked for. The crates.io packages match their git tags file for file, every dependency comes from crates.io, and the release workflow pins its actions by commit and publishes with crates.io trusted publishing. The syscall hashes in the template and the builtins crate all match murmur3 of their names.&lt;&#x2F;p&gt;
&lt;p&gt;Everything below runs in Docker, with sbpf-linker built from its crates.io source, apart from the local validator at the end.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;a-counter&quot;&gt;A counter &lt;a class=&quot;zola-anchor&quot; href=&quot;#a-counter&quot; aria-label=&quot;Link to this section&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;The &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;blueshift-gg&#x2F;solana-upstream-bpf-template&quot;&gt;template&lt;&#x2F;a&gt; prints &amp;quot;Hello, world!&amp;quot;, builds into a 240 byte &lt;code&gt;.so&lt;&#x2F;code&gt;, and its test passes after one change I get to below. I wanted something closer to a real program: accounts, an owner check, data I write to, and errors.&lt;&#x2F;p&gt;
&lt;p&gt;So I wrote a counter with &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;anza-xyz&#x2F;pinocchio&quot;&gt;pinocchio&lt;&#x2F;a&gt; 0.11.2. One instruction adds the u64 in the instruction data to the u64 stored in an account the program owns.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; class=&quot;language-rust z-code&quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-keyword z-other z-rust&quot;&gt;use&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-path z-rust&quot;&gt;pinocchio&lt;span class=&quot;z-punctuation z-accessor z-rust&quot;&gt;::&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-block z-begin z-rust&quot;&gt;{&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-path z-rust&quot;&gt;error&lt;span class=&quot;z-punctuation z-accessor z-rust&quot;&gt;::&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;ProgramError&lt;span class=&quot;z-punctuation z-separator z-rust&quot;&gt;,&lt;&#x2F;span&gt; AccountView&lt;span class=&quot;z-punctuation z-separator z-rust&quot;&gt;,&lt;&#x2F;span&gt; Address&lt;span class=&quot;z-punctuation z-separator z-rust&quot;&gt;,&lt;&#x2F;span&gt; ProgramResult&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-block z-end z-rust&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-terminator z-rust&quot;&gt;;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-path z-rust&quot;&gt;pinocchio&lt;span class=&quot;z-punctuation z-accessor z-rust&quot;&gt;::&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;program_entrypoint&lt;span class=&quot;z-keyword z-operator z-logical z-rust&quot;&gt;!&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;process_instruction&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-terminator z-rust&quot;&gt;;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-path z-rust&quot;&gt;pinocchio&lt;span class=&quot;z-punctuation z-accessor z-rust&quot;&gt;::&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;no_allocator&lt;span class=&quot;z-keyword z-operator z-logical z-rust&quot;&gt;!&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-terminator z-rust&quot;&gt;;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-path z-rust&quot;&gt;pinocchio&lt;span class=&quot;z-punctuation z-accessor z-rust&quot;&gt;::&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;nostd_panic_handler&lt;span class=&quot;z-keyword z-operator z-logical z-rust&quot;&gt;!&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-terminator z-rust&quot;&gt;;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-storage z-modifier z-rust&quot;&gt;pub&lt;&#x2F;span&gt; &lt;span class=&quot;z-storage z-type z-function z-rust&quot;&gt;fn&lt;&#x2F;span&gt; &lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function z-rust&quot;&gt;process_instruction&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-parameters z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-parameters z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-parameter z-rust&quot;&gt;program_id&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-rust&quot;&gt;:&lt;&#x2F;span&gt; &lt;span class=&quot;z-keyword z-operator z-rust&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;Address, &lt;span class=&quot;z-variable z-parameter z-rust&quot;&gt;accounts&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-rust&quot;&gt;:&lt;&#x2F;span&gt; &lt;span class=&quot;z-keyword z-operator z-rust&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage z-modifier z-rust&quot;&gt;mut&lt;&#x2F;span&gt; [AccountView], &lt;span class=&quot;z-variable z-parameter z-rust&quot;&gt;data&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-rust&quot;&gt;:&lt;&#x2F;span&gt; &lt;span class=&quot;z-keyword z-operator z-rust&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;[&lt;span class=&quot;z-storage z-type z-rust&quot;&gt;u8&lt;&#x2F;span&gt;]&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-parameters z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-parameters z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt; &lt;span class=&quot;z-meta z-function z-return-type z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-separator z-rust&quot;&gt;-&amp;gt;&lt;&#x2F;span&gt; ProgramResult&lt;&#x2F;span&gt; &lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-block z-begin z-rust&quot;&gt;{&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;    &lt;span class=&quot;z-storage z-type z-rust&quot;&gt;let&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;[&lt;&#x2F;span&gt;counter&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; &lt;span class=&quot;z-keyword z-operator z-assignment z-rust&quot;&gt;=&lt;&#x2F;span&gt; accounts &lt;span class=&quot;z-keyword z-control z-rust&quot;&gt;else&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-block z-begin z-rust&quot;&gt;{&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;        &lt;span class=&quot;z-keyword z-control z-rust&quot;&gt;return&lt;&#x2F;span&gt; &lt;span class=&quot;z-support z-type z-rust&quot;&gt;Err&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-path z-rust&quot;&gt;ProgramError&lt;span class=&quot;z-punctuation z-accessor z-rust&quot;&gt;::&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;NotEnoughAccountKeys&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-terminator z-rust&quot;&gt;;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;    &lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-block z-end z-rust&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-terminator z-rust&quot;&gt;;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;    &lt;span class=&quot;z-keyword z-control z-rust&quot;&gt;if&lt;&#x2F;span&gt; &lt;span class=&quot;z-keyword z-operator z-logical z-rust&quot;&gt;!&lt;&#x2F;span&gt;counter&lt;span class=&quot;z-punctuation z-accessor z-dot z-rust&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-support z-function z-rust&quot;&gt;owned_by&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;program_id&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-block z-begin z-rust&quot;&gt;{&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;        &lt;span class=&quot;z-keyword z-control z-rust&quot;&gt;return&lt;&#x2F;span&gt; &lt;span class=&quot;z-support z-type z-rust&quot;&gt;Err&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-path z-rust&quot;&gt;ProgramError&lt;span class=&quot;z-punctuation z-accessor z-rust&quot;&gt;::&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;IncorrectProgramId&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-terminator z-rust&quot;&gt;;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;    &lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-block z-end z-rust&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;    &lt;span class=&quot;z-comment z-line z-double-slash z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-rust&quot;&gt;&#x2F;&#x2F;&lt;&#x2F;span&gt; ... writable check
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;    &lt;span class=&quot;z-storage z-type z-rust&quot;&gt;let&lt;&#x2F;span&gt; by&lt;span class=&quot;z-punctuation z-separator z-rust&quot;&gt;:&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;[&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage z-type z-rust&quot;&gt;u8&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-rust&quot;&gt;;&lt;&#x2F;span&gt; &lt;span class=&quot;z-constant z-numeric z-integer z-decimal z-rust&quot;&gt;8&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; &lt;span class=&quot;z-keyword z-operator z-assignment z-rust&quot;&gt;=&lt;&#x2F;span&gt; data&lt;span class=&quot;z-punctuation z-accessor z-dot z-rust&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-support z-function z-rust&quot;&gt;try_into&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-accessor z-dot z-rust&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-support z-function z-rust&quot;&gt;map_err&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function z-closure z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-parameters z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-parameters z-begin z-rust&quot;&gt;|&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function z-closure z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-parameters z-rust&quot;&gt;_&lt;span class=&quot;z-punctuation z-section z-parameters z-end z-rust&quot;&gt;|&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; &lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function z-closure z-rust&quot;&gt;&lt;span class=&quot;z-meta z-path z-rust&quot;&gt;ProgramError&lt;span class=&quot;z-punctuation z-accessor z-rust&quot;&gt;::&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;InvalidInstructionData&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator z-rust&quot;&gt;?&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-terminator z-rust&quot;&gt;;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;    &lt;span class=&quot;z-storage z-type z-rust&quot;&gt;let&lt;&#x2F;span&gt; &lt;span class=&quot;z-storage z-modifier z-rust&quot;&gt;mut&lt;&#x2F;span&gt; bytes &lt;span class=&quot;z-keyword z-operator z-assignment z-rust&quot;&gt;=&lt;&#x2F;span&gt; counter&lt;span class=&quot;z-punctuation z-accessor z-dot z-rust&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-support z-function z-rust&quot;&gt;try_borrow_mut&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator z-rust&quot;&gt;?&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-terminator z-rust&quot;&gt;;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;    &lt;span class=&quot;z-storage z-type z-rust&quot;&gt;let&lt;&#x2F;span&gt; stored&lt;span class=&quot;z-punctuation z-separator z-rust&quot;&gt;:&lt;&#x2F;span&gt; &lt;span class=&quot;z-keyword z-operator z-bitwise z-rust&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage z-modifier z-rust&quot;&gt;mut&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;[&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage z-type z-rust&quot;&gt;u8&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-rust&quot;&gt;;&lt;&#x2F;span&gt; &lt;span class=&quot;z-constant z-numeric z-integer z-decimal z-rust&quot;&gt;8&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; &lt;span class=&quot;z-keyword z-operator z-assignment z-rust&quot;&gt;=&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator z-bitwise z-rust&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage z-modifier z-rust&quot;&gt;mut&lt;&#x2F;span&gt; bytes&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;[&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator z-range z-rust&quot;&gt;..&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-accessor z-dot z-rust&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-support z-function z-rust&quot;&gt;try_into&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-accessor z-dot z-rust&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-support z-function z-rust&quot;&gt;map_err&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function z-closure z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-parameters z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-parameters z-begin z-rust&quot;&gt;|&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function z-closure z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-parameters z-rust&quot;&gt;_&lt;span class=&quot;z-punctuation z-section z-parameters z-end z-rust&quot;&gt;|&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; &lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function z-closure z-rust&quot;&gt;&lt;span class=&quot;z-meta z-path z-rust&quot;&gt;ProgramError&lt;span class=&quot;z-punctuation z-accessor z-rust&quot;&gt;::&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;InvalidAccountData&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator z-rust&quot;&gt;?&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-terminator z-rust&quot;&gt;;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;    &lt;span class=&quot;z-storage z-type z-rust&quot;&gt;let&lt;&#x2F;span&gt; next &lt;span class=&quot;z-keyword z-operator z-assignment z-rust&quot;&gt;=&lt;&#x2F;span&gt; &lt;span class=&quot;z-storage z-type z-rust&quot;&gt;u64&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-path z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-accessor z-rust&quot;&gt;::&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;from_le_bytes&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator z-arithmetic z-rust&quot;&gt;*&lt;&#x2F;span&gt;stored&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;        &lt;span class=&quot;z-punctuation z-accessor z-dot z-rust&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-support z-function z-rust&quot;&gt;checked_add&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage z-type z-rust&quot;&gt;u64&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-path z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-accessor z-rust&quot;&gt;::&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;from_le_bytes&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;by&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;        &lt;span class=&quot;z-punctuation z-accessor z-dot z-rust&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-support z-function z-rust&quot;&gt;ok_or&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-path z-rust&quot;&gt;ProgramError&lt;span class=&quot;z-punctuation z-accessor z-rust&quot;&gt;::&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;ArithmeticOverflow&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator z-rust&quot;&gt;?&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-terminator z-rust&quot;&gt;;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;    &lt;span class=&quot;z-keyword z-operator z-arithmetic z-rust&quot;&gt;*&lt;&#x2F;span&gt;stored &lt;span class=&quot;z-keyword z-operator z-assignment z-rust&quot;&gt;=&lt;&#x2F;span&gt; next&lt;span class=&quot;z-punctuation z-accessor z-dot z-rust&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-support z-function z-rust&quot;&gt;to_le_bytes&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-terminator z-rust&quot;&gt;;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;    &lt;span class=&quot;z-support z-type z-rust&quot;&gt;Ok&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-block z-end z-rust&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This is a plain pinocchio program. Nothing in it knows which compiler built it. I added pinocchio, ran &lt;code&gt;cargo +nightly build-bpf&lt;&#x2F;code&gt;, and it built on the first try.&lt;&#x2F;p&gt;
&lt;p&gt;That is less luck than it looks. pinocchio already pulls in its syscalls for &lt;code&gt;target_arch = &amp;quot;bpf&amp;quot;&lt;&#x2F;code&gt;, the upstream target, next to &lt;code&gt;target_os = &amp;quot;solana&amp;quot;&lt;&#x2F;code&gt;, and &lt;code&gt;solana-define-syscall&lt;&#x2F;code&gt; computes the murmur3 hashes at compile time when &lt;code&gt;static-syscalls&lt;&#x2F;code&gt; is set.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;five-tests-two-compilers&quot;&gt;Five tests, two compilers &lt;a class=&quot;zola-anchor&quot; href=&quot;#five-tests-two-compilers&quot; aria-label=&quot;Link to this section&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;Five &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;anza-xyz&#x2F;mollusk&quot;&gt;Mollusk&lt;&#x2F;a&gt; tests cover the add and each way it should fail: an overflow, an account owned by another program, a read only account, and instruction data that is not 8 bytes. I built the same crate through platform-tools v1.57 for sBPF v3 too, and ran the same five tests against both &lt;code&gt;.so&lt;&#x2F;code&gt; files.&lt;&#x2F;p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;&lt;&#x2F;th&gt;&lt;th&gt;Upstream and sbpf-linker&lt;&#x2F;th&gt;&lt;th&gt;platform-tools v1.57&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Tests&lt;&#x2F;td&gt;&lt;td&gt;5 of 5&lt;&#x2F;td&gt;&lt;td&gt;5 of 5&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;.so&lt;&#x2F;code&gt; size&lt;&#x2F;td&gt;&lt;td&gt;3,824 bytes&lt;&#x2F;td&gt;&lt;td&gt;7,680 bytes&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Add 5 to 37&lt;&#x2F;td&gt;&lt;td&gt;56 CU&lt;&#x2F;td&gt;&lt;td&gt;54 CU&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;p&gt;Half the size, and two compute units apart. I ran the set three times and got the same numbers each time. Mollusk counts compute units, it does not time anything.&lt;&#x2F;p&gt;
&lt;p&gt;On Solana, deploying a program means paying rent for every byte of it, so half the size is half the rent.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;on-a-real-validator&quot;&gt;On a real validator &lt;a class=&quot;zola-anchor&quot; href=&quot;#on-a-real-validator&quot; aria-label=&quot;Link to this section&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;Mollusk runs the program, it does not deploy it. So I deployed the upstream &lt;code&gt;.so&lt;&#x2F;code&gt; to &lt;code&gt;solana-test-validator&lt;&#x2F;code&gt; from Agave 4.3.0, the version mainnet runs, started with &lt;code&gt;--clone-feature-set&lt;&#x2F;code&gt; so it has exactly the features active on mainnet. The deploy went through, and three real transactions did what they should:&lt;&#x2F;p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Transaction&lt;&#x2F;th&gt;&lt;th&gt;Result&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Create the counter and add 37&lt;&#x2F;td&gt;&lt;td&gt;counter is 37&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Add 5&lt;&#x2F;td&gt;&lt;td&gt;counter is 42, in 56 CU, the same as in Mollusk&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Add &lt;code&gt;u64::MAX&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;rejected, &amp;quot;Program arithmetic overflowed&amp;quot;&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;p&gt;I first tried to run the validator in Docker, like everything else here. Agave only ships Linux binaries for x86_64, so on my Mac it ran under emulation, and it would not start: Agave 4 needs io_uring, and the emulated container does not have it (&amp;quot;Function not implemented&amp;quot;). Lifting Docker&#x27;s seccomp filter did not help. Agave could be built for arm64 inside Docker, but it is a large Rust build, so &lt;code&gt;just validator&lt;&#x2F;code&gt; downloads Agave&#x27;s own binary for your machine and runs it from there.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;one-thing-i-changed&quot;&gt;One thing I changed &lt;a class=&quot;zola-anchor&quot; href=&quot;#one-thing-i-changed&quot; aria-label=&quot;Link to this section&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;It is &lt;code&gt;cargo test&lt;&#x2F;code&gt;. The template sets &lt;code&gt;build-std&lt;&#x2F;code&gt; under &lt;code&gt;[unstable]&lt;&#x2F;code&gt;, which applies to the host build too, and the host build then fails with E0152, duplicate lang items. The builtins repo already has the fix in its own tests: put &lt;code&gt;-Z build-std&lt;&#x2F;code&gt; in the alias, so only the BPF build uses it.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;toml&quot; class=&quot;language-toml z-code&quot;&gt;&lt;code class=&quot;language-toml&quot; data-lang=&quot;toml&quot;&gt;&lt;span class=&quot;z-source z-toml&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-table z-begin z-toml&quot;&gt;[&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-tag z-table z-toml&quot;&gt;&lt;span class=&quot;z-entity z-name z-table z-toml&quot;&gt;alias&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-table z-end z-toml&quot;&gt;]&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-toml&quot;&gt;&lt;span class=&quot;z-meta z-tag z-key z-toml&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-toml&quot;&gt;build-bpf&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; &lt;span class=&quot;z-punctuation z-definition z-key-value z-toml&quot;&gt;=&lt;&#x2F;span&gt; &lt;span class=&quot;z-string z-quoted z-double z-basic z-toml&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-toml&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;build --release --target bpfel-unknown-none -Z build-std=core,alloc&lt;span class=&quot;z-punctuation z-definition z-string z-end z-toml&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;It is a few lines, and a PR I plan to send.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;where-i-would-like-to-help&quot;&gt;Where I would like to help &lt;a class=&quot;zola-anchor&quot; href=&quot;#where-i-would-like-to-help&quot; aria-label=&quot;Link to this section&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;The libcalls are the young part. LLVM turns some operations into calls to helper functions, and &lt;code&gt;solana-compiler-builtins&lt;&#x2F;code&gt; provides them for this target. Multiply works and is cheap. u128 division does not link yet: the linker asks for &lt;code&gt;__udivti3&lt;&#x2F;code&gt;, and after exporting it, for &lt;code&gt;__bpf_trap&lt;&#x2F;code&gt;. An f64 divide works but costs 736 CU against 192 on platform-tools. My counter never divides, so it never hit either problem, but any program that computes a price will.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;what-it-costs-today&quot;&gt;What it costs today &lt;a class=&quot;zola-anchor&quot; href=&quot;#what-it-costs-today&quot; aria-label=&quot;Link to this section&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;&lt;strong&gt;A pinned nightly.&lt;&#x2F;strong&gt; The target is Tier 3 in Rust, so &lt;code&gt;core&lt;&#x2F;code&gt; is built from source along with your program. The linker repo pins a nightly and a bot updates it, so you need to pin yours too.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;no_std&lt;&#x2F;code&gt; only.&lt;&#x2F;strong&gt; pinocchio fits. I did not try Anchor or &lt;code&gt;solana-program&lt;&#x2F;code&gt; on this path.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Young.&lt;&#x2F;strong&gt; Version 0.2.3. I would test a program here for a while before it holds anyone&#x27;s money.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;try-it&quot;&gt;Try it &lt;a class=&quot;zola-anchor&quot; href=&quot;#try-it&quot; aria-label=&quot;Link to this section&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;The counter, a Dockerfile with both toolchains and a &lt;code&gt;Justfile&lt;&#x2F;code&gt; are in &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;gabrielkoerich&#x2F;gabrielkoerich&#x2F;tree&#x2F;main&#x2F;examples&#x2F;solana-without-the-fork&quot;&gt;examples&#x2F;solana-without-the-fork&lt;&#x2F;a&gt;. &lt;code&gt;just test&lt;&#x2F;code&gt; builds both &lt;code&gt;.so&lt;&#x2F;code&gt; files and runs the five tests against each, and &lt;code&gt;just validator&lt;&#x2F;code&gt; deploys the upstream one to a local validator and sends it real transactions.&lt;&#x2F;p&gt;
&lt;p&gt;Thanks to &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;x.com&#x2F;clairefxyz&quot;&gt;Claire&lt;&#x2F;a&gt; and the &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;blueshift-gg&quot;&gt;Blueshift&lt;&#x2F;a&gt; team for doing the compiler and assembly work so the rest of us can build on stock Rust.&lt;&#x2F;p&gt;
</content>
    </entry>
    <entry xml:lang="en">
        <title>To queue or not to queue</title>
        <published>2026-10-12T00:00:00+00:00</published>
        <updated>2026-10-12T00:00:00+00:00</updated>
        <author>
            <name>Gabriel Koerich</name>
        </author>
        <link rel="alternate" type="text/html" href="https://gabrielkoerich.com/posts/to-queue-or-not-to-queue/"/>
        <id>https://gabrielkoerich.com/posts/to-queue-or-not-to-queue/</id>
        <summary>Beanstalkd ran 500 million jobs a month for my company. When to use a queue, when not to, and leases, retries and idempotency in Rust and TypeScript.</summary>
        <content type="html" xml:base="https://gabrielkoerich.com/posts/to-queue-or-not-to-queue/">&lt;p&gt;Ten years ago my company ran on &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;beanstalkd.github.io&#x2F;&quot;&gt;Beanstalkd&lt;&#x2F;a&gt;. Jobs went into tubes, each with a priority, an optional delay and a time to run. A worker reserved a job, and if it did not finish within that time the server released the job for someone else. A job that kept failing got buried, set aside where the server would never touch it again, until I kicked it back. At our peak that setup moved more than 500 million background jobs a month.&lt;&#x2F;p&gt;
&lt;p&gt;I rarely see Beanstalkd mentioned now. The project is mature, which is a kind way to say its last commits were in March 2025, and the conversation moved to managed queues, Redis job libraries and Kafka. The ideas stayed. A time to run is a visibility timeout. A buried job is a dead letter. Kicking is a redrive. Every queue I have used since has the same few parts, with different names.&lt;&#x2F;p&gt;
&lt;p&gt;This post is the follow up to &lt;a href=&quot;https:&#x2F;&#x2F;gabrielkoerich.com&#x2F;posts&#x2F;waiting-and-working-in-rust&#x2F;&quot;&gt;Waiting and working in Rust&lt;&#x2F;a&gt;. That one was about running work concurrently inside one process. This one is about the step after: when work should leave the process and go through a queue, when it should not, and what you have to get right either way. The examples are in Rust and TypeScript, and they all run: &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;gabrielkoerich&#x2F;gabrielkoerich&#x2F;tree&#x2F;main&#x2F;examples&#x2F;to-queue-or-not-to-queue&quot;&gt;examples&#x2F;to-queue-or-not-to-queue&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;what-a-queue-buys-you&quot;&gt;What a queue buys you &lt;a class=&quot;zola-anchor&quot; href=&quot;#what-a-queue-buys-you&quot; aria-label=&quot;Link to this section&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;A queue sits between the code that decides work should happen and the code that does it. The producer does not wait, so a request can return in milliseconds while the slow part, an email, a webhook, a settlement, happens later. A burst of a thousand events turns into a steady stream the workers can keep up with, instead of a thousand things running at once. When a worker fails, the message is still there, and a durable queue keeps it across crashes and deploys. And one event can feed several consumers that know nothing about each other.&lt;&#x2F;p&gt;
&lt;p&gt;Each of these has a price, and the price is the same everywhere: the work is no longer synchronous, so the caller cannot see its result, and the delivery guarantee you get is almost always at least once. &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;docs.aws.amazon.com&#x2F;AWSSimpleQueueService&#x2F;latest&#x2F;SQSDeveloperGuide&#x2F;sqs-queue-types.html&quot;&gt;SQS&lt;&#x2F;a&gt; says it plainly for standard queues: every message is delivered at least once, &amp;quot;but in some cases, a message may be delivered more than once&amp;quot;. Most brokers work the same way by default. So the question to design for is how many times a job ran.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;when-not-to-queue&quot;&gt;When not to queue &lt;a class=&quot;zola-anchor&quot; href=&quot;#when-not-to-queue&quot; aria-label=&quot;Link to this section&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;Most work does not need a queue.&lt;&#x2F;p&gt;
&lt;p&gt;If the caller needs the result, a queue only adds a second round trip and a place for the answer to get lost. If the work finishes in the request, run it in the request. If all you want is to stop a thousand tasks from running at once, you want a limit on concurrency, which is a semaphore:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; class=&quot;language-rust z-code&quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-storage z-type z-rust&quot;&gt;let&lt;&#x2F;span&gt; limit &lt;span class=&quot;z-keyword z-operator z-assignment z-rust&quot;&gt;=&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-path z-rust&quot;&gt;Arc&lt;span class=&quot;z-punctuation z-accessor z-rust&quot;&gt;::&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;new&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-path z-rust&quot;&gt;Semaphore&lt;span class=&quot;z-punctuation z-accessor z-rust&quot;&gt;::&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;new&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric z-integer z-decimal z-rust&quot;&gt;2&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-terminator z-rust&quot;&gt;;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-storage z-type z-rust&quot;&gt;let&lt;&#x2F;span&gt; handles&lt;span class=&quot;z-punctuation z-separator z-rust&quot;&gt;:&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-generic z-rust&quot;&gt;&lt;span class=&quot;z-support z-type z-rust&quot;&gt;Vec&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-generic z-begin z-rust&quot;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator z-rust&quot;&gt;_&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-generic z-end z-rust&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; &lt;span class=&quot;z-keyword z-operator z-assignment z-rust&quot;&gt;=&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric z-integer z-decimal z-rust&quot;&gt;0&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator z-range z-rust&quot;&gt;..&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric z-integer z-decimal z-rust&quot;&gt;30&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage z-type z-numeric z-rust&quot;&gt;u64&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;    &lt;span class=&quot;z-punctuation z-accessor z-dot z-rust&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-support z-function z-rust&quot;&gt;map&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function z-closure z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-parameters z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-parameters z-begin z-rust&quot;&gt;|&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function z-closure z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-parameters z-rust&quot;&gt;&lt;span class=&quot;z-variable z-parameter z-rust&quot;&gt;i&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-section z-parameters z-end z-rust&quot;&gt;|&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; &lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function z-closure z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-block z-begin z-rust&quot;&gt;{&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-closure z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;        &lt;span class=&quot;z-storage z-type z-rust&quot;&gt;let&lt;&#x2F;span&gt; limit &lt;span class=&quot;z-keyword z-operator z-assignment z-rust&quot;&gt;=&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-path z-rust&quot;&gt;Arc&lt;span class=&quot;z-punctuation z-accessor z-rust&quot;&gt;::&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;clone&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator z-bitwise z-rust&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;limit&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-terminator z-rust&quot;&gt;;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-closure z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;        &lt;span class=&quot;z-meta z-path z-rust&quot;&gt;tokio&lt;span class=&quot;z-punctuation z-accessor z-rust&quot;&gt;::&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;spawn&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;async &lt;span class=&quot;z-storage z-modifier z-rust&quot;&gt;move&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-block z-begin z-rust&quot;&gt;{&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-closure z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;            &lt;span class=&quot;z-storage z-type z-rust&quot;&gt;let&lt;&#x2F;span&gt; _permit &lt;span class=&quot;z-keyword z-operator z-assignment z-rust&quot;&gt;=&lt;&#x2F;span&gt; limit&lt;span class=&quot;z-punctuation z-accessor z-dot z-rust&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-support z-function z-rust&quot;&gt;acquire_owned&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-accessor z-dot z-rust&quot;&gt;.&lt;&#x2F;span&gt;await&lt;span class=&quot;z-punctuation z-accessor z-dot z-rust&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-support z-function z-rust&quot;&gt;unwrap&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-terminator z-rust&quot;&gt;;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-closure z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;            &lt;span class=&quot;z-support z-function z-rust&quot;&gt;settle&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator z-bitwise z-rust&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;Transfer &lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-block z-begin z-rust&quot;&gt;{&lt;&#x2F;span&gt; id&lt;span class=&quot;z-punctuation z-separator z-rust&quot;&gt;:&lt;&#x2F;span&gt; &lt;span class=&quot;z-support z-macro z-rust&quot;&gt;format!&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-string z-quoted z-double z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-rust&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;tx&lt;span class=&quot;z-constant z-other z-placeholder z-rust&quot;&gt;{i}&lt;&#x2F;span&gt;:0&lt;span class=&quot;z-punctuation z-definition z-string z-end z-rust&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-rust&quot;&gt;,&lt;&#x2F;span&gt; amount&lt;span class=&quot;z-punctuation z-separator z-rust&quot;&gt;:&lt;&#x2F;span&gt; i &lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-block z-end z-rust&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-accessor z-dot z-rust&quot;&gt;.&lt;&#x2F;span&gt;await&lt;span class=&quot;z-punctuation z-terminator z-rust&quot;&gt;;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-closure z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;        &lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-block z-end z-rust&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-closure z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;    &lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-block z-end z-rust&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;    &lt;span class=&quot;z-punctuation z-accessor z-dot z-rust&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-support z-function z-rust&quot;&gt;collect&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-terminator z-rust&quot;&gt;;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Source: &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;gabrielkoerich&#x2F;gabrielkoerich&#x2F;blob&#x2F;main&#x2F;examples&#x2F;to-queue-or-not-to-queue&#x2F;rust&#x2F;src&#x2F;bin&#x2F;queue.rs&quot;&gt;&lt;code&gt;rust&#x2F;src&#x2F;bin&#x2F;queue.rs&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Thirty settlements, two at a time, 330 ms on my machine, and nothing to deploy.&lt;&#x2F;p&gt;
&lt;p&gt;Two of my own projects make the same choice for different reasons. The broker in &lt;a href=&quot;https:&#x2F;&#x2F;gabrielkoerich.com&#x2F;posts&#x2F;a-password-manager-for-agents&#x2F;&quot;&gt;passbox&lt;&#x2F;a&gt; serves one connection at a time with no runtime and no queue, because a biometric prompt blocks anyway. &lt;a href=&quot;https:&#x2F;&#x2F;gabrielkoerich.com&#x2F;posts&#x2F;orchestrating-agents-for-humans&#x2F;&quot;&gt;orch&lt;&#x2F;a&gt; does have work waiting, but its queue is a table: tasks live in SQLite and GitHub issues, and a tick loop picks up whatever is ready every ten seconds. When the work already lives in a database, a loop that reads it can be the whole queue.&lt;&#x2F;p&gt;
&lt;p&gt;Postgres can serve a queue from an ordinary table with &lt;code&gt;SELECT ... FOR UPDATE SKIP LOCKED&lt;&#x2F;code&gt;, which the &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;www.postgresql.org&#x2F;docs&#x2F;current&#x2F;sql-select.html&quot;&gt;docs&lt;&#x2F;a&gt; describe as a way &amp;quot;to avoid lock contention with multiple consumers accessing a queue-like table&amp;quot;. If the database is already there, the queue can live in the same transaction as the data it is about.&lt;&#x2F;p&gt;
&lt;p&gt;Queues in a database had a bad reputation for a long time, and it was earned. Workers polling one table all waited on the same row locks, so a busy queue turned into lock contention. &lt;code&gt;SKIP LOCKED&lt;&#x2F;code&gt; fixed that part, but it still has costs. Every claim and every completion is a write, and in Postgres the old row versions stay until vacuum removes them, so a busy queue table needs vacuum to keep up. Workers poll unless something wakes them. The queue shares the database with everything else, so a backlog slows the application. And there is no fan out or replay. For jobs tied to data in the same database, at a volume it handles comfortably, it is a good queue. When the queue starts to compete with the app, or several services need the same events, it is time for a broker.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;an-event-bus-is-not-a-queue&quot;&gt;An event bus is not a queue &lt;a class=&quot;zola-anchor&quot; href=&quot;#an-event-bus-is-not-a-queue&quot; aria-label=&quot;Link to this section&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;In TypeScript the first thing to reach for is an event emitter, which is not a queue. Node&#x27;s &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;nodejs.org&#x2F;api&#x2F;events.html&quot;&gt;&lt;code&gt;EventEmitter&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; &amp;quot;calls all listeners synchronously in the order in which they were registered&amp;quot;. There is no buffer and no retry, and nothing survives a crash. An async listener makes it worse, because &lt;code&gt;emit&lt;&#x2F;code&gt; does not wait for it:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;ts&quot; class=&quot;language-ts z-code&quot;&gt;&lt;code class=&quot;language-ts&quot; data-lang=&quot;ts&quot;&gt;&lt;span class=&quot;z-source z-ts&quot;&gt;&lt;span class=&quot;z-meta z-var z-expr z-ts&quot;&gt;&lt;span class=&quot;z-storage z-type z-ts&quot;&gt;const&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-var-single-variable z-expr z-ts&quot;&gt;&lt;span class=&quot;z-meta z-definition z-variable z-ts&quot;&gt;&lt;span class=&quot;z-variable z-other z-constant z-ts&quot;&gt;bus&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; &lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator z-assignment z-ts&quot;&gt;=&lt;&#x2F;span&gt; &lt;span class=&quot;z-new z-expr z-ts&quot;&gt;&lt;span class=&quot;z-keyword z-operator z-new z-ts&quot;&gt;new&lt;&#x2F;span&gt; &lt;span class=&quot;z-entity z-name z-type z-ts&quot;&gt;EventEmitter&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-brace z-round z-ts&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-objectliteral z-ts&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-block z-ts&quot;&gt;{&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-object z-member z-ts&quot;&gt;&lt;span class=&quot;z-meta z-object-literal z-key z-ts&quot;&gt;captureRejections&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-object z-member z-ts&quot;&gt;&lt;span class=&quot;z-meta z-object-literal z-key z-ts&quot;&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value z-ts&quot;&gt;:&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; &lt;span class=&quot;z-constant z-language z-boolean z-true z-ts&quot;&gt;true&lt;&#x2F;span&gt; &lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-block z-ts&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-brace z-round z-ts&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-ts&quot;&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-ts&quot;&gt;&lt;span class=&quot;z-meta z-function-call z-ts&quot;&gt;&lt;span class=&quot;z-variable z-other z-object z-ts&quot;&gt;bus&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-accessor z-ts&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function z-ts&quot;&gt;on&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-brace z-round z-ts&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-string z-quoted z-single z-ts&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-ts&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;transfer&lt;span class=&quot;z-punctuation z-definition z-string z-end z-ts&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-comma z-ts&quot;&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-arrow z-ts&quot;&gt; &lt;span class=&quot;z-meta z-parameters z-ts&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-parameters z-begin z-ts&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-parameter z-ts&quot;&gt;id&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-type z-annotation z-ts&quot;&gt;&lt;span class=&quot;z-keyword z-operator z-type z-annotation z-ts&quot;&gt;:&lt;&#x2F;span&gt; &lt;span class=&quot;z-support z-type z-primitive z-ts&quot;&gt;string&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-parameters z-end z-ts&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; &lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-arrow z-ts&quot;&gt;&lt;span class=&quot;z-storage z-type z-function z-arrow z-ts&quot;&gt;=&amp;gt;&lt;&#x2F;span&gt; &lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-ts&quot;&gt;&lt;span class=&quot;z-support z-class z-console z-ts&quot;&gt;console&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-accessor z-ts&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-support z-function z-console z-ts&quot;&gt;log&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-brace z-round z-ts&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-string z-template z-ts&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-template z-begin z-ts&quot;&gt;`&lt;&#x2F;span&gt;saved &lt;span class=&quot;z-meta z-template z-expression z-ts&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-template-expression z-begin z-ts&quot;&gt;${&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-template z-expression z-ts&quot;&gt;&lt;span class=&quot;z-meta z-embedded z-line z-ts&quot;&gt;&lt;span class=&quot;z-variable z-other z-readwrite z-ts&quot;&gt;id&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-template-expression z-end z-ts&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-template z-end z-ts&quot;&gt;`&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-brace z-round z-ts&quot;&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-brace z-round z-ts&quot;&gt;)&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-ts&quot;&gt;&lt;span class=&quot;z-meta z-function-call z-ts&quot;&gt;&lt;span class=&quot;z-variable z-other z-object z-ts&quot;&gt;bus&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-accessor z-ts&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function z-ts&quot;&gt;on&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-brace z-round z-ts&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-string z-quoted z-single z-ts&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-ts&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;transfer&lt;span class=&quot;z-punctuation z-definition z-string z-end z-ts&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-comma z-ts&quot;&gt;,&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-arrow z-ts&quot;&gt;&lt;span class=&quot;z-storage z-modifier z-async z-ts&quot;&gt;async&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-parameters z-ts&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-parameters z-begin z-ts&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-parameter z-ts&quot;&gt;id&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-type z-annotation z-ts&quot;&gt;&lt;span class=&quot;z-keyword z-operator z-type z-annotation z-ts&quot;&gt;:&lt;&#x2F;span&gt; &lt;span class=&quot;z-support z-type z-primitive z-ts&quot;&gt;string&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-parameters z-end z-ts&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; &lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-arrow z-ts&quot;&gt;&lt;span class=&quot;z-storage z-type z-function z-arrow z-ts&quot;&gt;=&amp;gt;&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-block z-ts&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-block z-ts&quot;&gt;{&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-ts&quot;&gt;&lt;span class=&quot;z-meta z-arrow z-ts&quot;&gt;&lt;span class=&quot;z-meta z-block z-ts&quot;&gt;  &lt;span class=&quot;z-keyword z-control z-flow z-ts&quot;&gt;await&lt;&#x2F;span&gt; &lt;span class=&quot;z-new z-expr z-ts&quot;&gt;&lt;span class=&quot;z-keyword z-operator z-new z-ts&quot;&gt;new&lt;&#x2F;span&gt; &lt;span class=&quot;z-entity z-name z-type z-ts&quot;&gt;Promise&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-brace z-round z-ts&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-arrow z-ts&quot;&gt;&lt;span class=&quot;z-variable z-parameter z-ts&quot;&gt;r&lt;&#x2F;span&gt; &lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-arrow z-ts&quot;&gt;&lt;span class=&quot;z-storage z-type z-function z-arrow z-ts&quot;&gt;=&amp;gt;&lt;&#x2F;span&gt; &lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-ts&quot;&gt;&lt;span class=&quot;z-support z-function z-ts&quot;&gt;setTimeout&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-brace z-round z-ts&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other z-readwrite z-ts&quot;&gt;r&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-comma z-ts&quot;&gt;,&lt;&#x2F;span&gt; &lt;span class=&quot;z-constant z-numeric z-decimal z-ts&quot;&gt;10&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-brace z-round z-ts&quot;&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-brace z-round z-ts&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-ts&quot;&gt;&lt;span class=&quot;z-meta z-arrow z-ts&quot;&gt;&lt;span class=&quot;z-meta z-block z-ts&quot;&gt;  &lt;span class=&quot;z-keyword z-control z-trycatch z-ts&quot;&gt;throw&lt;&#x2F;span&gt; &lt;span class=&quot;z-new z-expr z-ts&quot;&gt;&lt;span class=&quot;z-keyword z-operator z-new z-ts&quot;&gt;new&lt;&#x2F;span&gt; &lt;span class=&quot;z-entity z-name z-type z-ts&quot;&gt;Error&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-brace z-round z-ts&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-string z-template z-ts&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-template z-begin z-ts&quot;&gt;`&lt;&#x2F;span&gt;notify failed for &lt;span class=&quot;z-meta z-template z-expression z-ts&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-template-expression z-begin z-ts&quot;&gt;${&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-template z-expression z-ts&quot;&gt;&lt;span class=&quot;z-meta z-embedded z-line z-ts&quot;&gt;&lt;span class=&quot;z-variable z-other z-readwrite z-ts&quot;&gt;id&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-template-expression z-end z-ts&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-template z-end z-ts&quot;&gt;`&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-brace z-round z-ts&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-ts&quot;&gt;&lt;span class=&quot;z-meta z-arrow z-ts&quot;&gt;&lt;span class=&quot;z-meta z-block z-ts&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-block z-ts&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-brace z-round z-ts&quot;&gt;)&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-ts&quot;&gt;&lt;span class=&quot;z-meta z-function-call z-ts&quot;&gt;&lt;span class=&quot;z-variable z-other z-object z-ts&quot;&gt;bus&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-accessor z-ts&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function z-ts&quot;&gt;on&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-brace z-round z-ts&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-string z-quoted z-single z-ts&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-ts&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;error&lt;span class=&quot;z-punctuation z-definition z-string z-end z-ts&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-comma z-ts&quot;&gt;,&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-arrow z-ts&quot;&gt; &lt;span class=&quot;z-meta z-parameters z-ts&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-parameters z-begin z-ts&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-parameter z-ts&quot;&gt;e&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-type z-annotation z-ts&quot;&gt;&lt;span class=&quot;z-keyword z-operator z-type z-annotation z-ts&quot;&gt;:&lt;&#x2F;span&gt; &lt;span class=&quot;z-entity z-name z-type z-ts&quot;&gt;Error&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-parameters z-end z-ts&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; &lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-arrow z-ts&quot;&gt;&lt;span class=&quot;z-storage z-type z-function z-arrow z-ts&quot;&gt;=&amp;gt;&lt;&#x2F;span&gt; &lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-ts&quot;&gt;&lt;span class=&quot;z-support z-class z-console z-ts&quot;&gt;console&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-accessor z-ts&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-support z-function z-console z-ts&quot;&gt;log&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-brace z-round z-ts&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-string z-template z-ts&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-template z-begin z-ts&quot;&gt;`&lt;&#x2F;span&gt;error event: &lt;span class=&quot;z-meta z-template z-expression z-ts&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-template-expression z-begin z-ts&quot;&gt;${&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-template z-expression z-ts&quot;&gt;&lt;span class=&quot;z-meta z-embedded z-line z-ts&quot;&gt;&lt;span class=&quot;z-variable z-other z-object z-ts&quot;&gt;e&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-accessor z-ts&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other z-property z-ts&quot;&gt;message&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-template-expression z-end z-ts&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-template z-end z-ts&quot;&gt;`&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-brace z-round z-ts&quot;&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-brace z-round z-ts&quot;&gt;)&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-ts&quot;&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-ts&quot;&gt;&lt;span class=&quot;z-meta z-function-call z-ts&quot;&gt;&lt;span class=&quot;z-support z-class z-console z-ts&quot;&gt;console&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-accessor z-ts&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-support z-function z-console z-ts&quot;&gt;log&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-brace z-round z-ts&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-string z-quoted z-single z-ts&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-ts&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;before emit&lt;span class=&quot;z-punctuation z-definition z-string z-end z-ts&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-brace z-round z-ts&quot;&gt;)&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-ts&quot;&gt;&lt;span class=&quot;z-meta z-var z-expr z-ts&quot;&gt;&lt;span class=&quot;z-storage z-type z-ts&quot;&gt;const&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-var-single-variable z-expr z-ts&quot;&gt;&lt;span class=&quot;z-meta z-definition z-variable z-ts&quot;&gt;&lt;span class=&quot;z-variable z-other z-constant z-ts&quot;&gt;hadListeners&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; &lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator z-assignment z-ts&quot;&gt;=&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-function-call z-ts&quot;&gt;&lt;span class=&quot;z-variable z-other z-object z-ts&quot;&gt;bus&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-accessor z-ts&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function z-ts&quot;&gt;emit&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-brace z-round z-ts&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-string z-quoted z-single z-ts&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-ts&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;transfer&lt;span class=&quot;z-punctuation z-definition z-string z-end z-ts&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-comma z-ts&quot;&gt;,&lt;&#x2F;span&gt; &lt;span class=&quot;z-string z-quoted z-single z-ts&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-ts&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;tx1:0&lt;span class=&quot;z-punctuation z-definition z-string z-end z-ts&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-brace z-round z-ts&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-ts&quot;&gt;&lt;span class=&quot;z-meta z-function-call z-ts&quot;&gt;&lt;span class=&quot;z-support z-class z-console z-ts&quot;&gt;console&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-accessor z-ts&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-support z-function z-console z-ts&quot;&gt;log&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-brace z-round z-ts&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-string z-template z-ts&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-template z-begin z-ts&quot;&gt;`&lt;&#x2F;span&gt;after emit, returned &lt;span class=&quot;z-meta z-template z-expression z-ts&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-template-expression z-begin z-ts&quot;&gt;${&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-template z-expression z-ts&quot;&gt;&lt;span class=&quot;z-meta z-embedded z-line z-ts&quot;&gt;&lt;span class=&quot;z-variable z-other z-readwrite z-ts&quot;&gt;hadListeners&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-template-expression z-end z-ts&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;, nothing was awaited&lt;span class=&quot;z-punctuation z-definition z-string z-template z-end z-ts&quot;&gt;`&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-brace z-round z-ts&quot;&gt;)&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Source: &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;gabrielkoerich&#x2F;gabrielkoerich&#x2F;blob&#x2F;main&#x2F;examples&#x2F;to-queue-or-not-to-queue&#x2F;typescript&#x2F;emitter.ts&quot;&gt;&lt;code&gt;typescript&#x2F;emitter.ts&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;text&quot; class=&quot;language-text z-code&quot;&gt;&lt;code class=&quot;language-text&quot; data-lang=&quot;text&quot;&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;before emit
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;saved tx1:0
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;after emit, returned true, nothing was awaited
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;error event: notify failed for tx1:0
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The sync listener ran inside &lt;code&gt;emit&lt;&#x2F;code&gt;. The async one failed later, and the error only reached anyone because of &lt;code&gt;captureRejections&lt;&#x2F;code&gt;. Without it, the Node docs warn, an async handler &amp;quot;can lead to an unhandled rejection&amp;quot;. An event bus decouples code inside one process, and anything on it is gone when the process dies.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;a-queue-inside-the-process&quot;&gt;A queue inside the process &lt;a class=&quot;zola-anchor&quot; href=&quot;#a-queue-inside-the-process&quot; aria-label=&quot;Link to this section&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;Between &amp;quot;no queue&amp;quot; and &amp;quot;a broker&amp;quot; there is a queue in memory, and this is where Rust and TypeScript differ.&lt;&#x2F;p&gt;
&lt;p&gt;Tokio gives you the queue and its backpressure. A bounded &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;docs.rs&#x2F;tokio&#x2F;latest&#x2F;tokio&#x2F;sync&#x2F;mpsc&#x2F;index.html&quot;&gt;&lt;code&gt;mpsc&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; channel &amp;quot;provides backpressure&amp;quot;: &lt;code&gt;send&lt;&#x2F;code&gt; waits until there is room. The unbounded version is documented as having &amp;quot;the ability of causing the process to run out of memory&amp;quot;. Two workers sharing one receiver, with a set of ids they have already handled:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; class=&quot;language-rust z-code&quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-storage z-type z-rust&quot;&gt;let&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;tx&lt;span class=&quot;z-punctuation z-separator z-rust&quot;&gt;,&lt;&#x2F;span&gt; rx&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; &lt;span class=&quot;z-keyword z-operator z-assignment z-rust&quot;&gt;=&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-path z-rust&quot;&gt;mpsc&lt;span class=&quot;z-punctuation z-accessor z-rust&quot;&gt;::&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-path z-rust&quot;&gt;channel&lt;span class=&quot;z-punctuation z-accessor z-rust&quot;&gt;::&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-generic z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-generic z-begin z-rust&quot;&gt;&amp;lt;&lt;&#x2F;span&gt;Transfer&lt;span class=&quot;z-punctuation z-definition z-generic z-end z-rust&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric z-integer z-decimal z-rust&quot;&gt;4&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-terminator z-rust&quot;&gt;;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-storage z-type z-rust&quot;&gt;let&lt;&#x2F;span&gt; rx &lt;span class=&quot;z-keyword z-operator z-assignment z-rust&quot;&gt;=&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-path z-rust&quot;&gt;Arc&lt;span class=&quot;z-punctuation z-accessor z-rust&quot;&gt;::&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;new&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-path z-rust&quot;&gt;Mutex&lt;span class=&quot;z-punctuation z-accessor z-rust&quot;&gt;::&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;new&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;rx&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-terminator z-rust&quot;&gt;;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-storage z-type z-rust&quot;&gt;let&lt;&#x2F;span&gt; seen &lt;span class=&quot;z-keyword z-operator z-assignment z-rust&quot;&gt;=&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-path z-rust&quot;&gt;Arc&lt;span class=&quot;z-punctuation z-accessor z-rust&quot;&gt;::&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;new&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-path z-rust&quot;&gt;Mutex&lt;span class=&quot;z-punctuation z-accessor z-rust&quot;&gt;::&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;new&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-path z-rust&quot;&gt;HashSet&lt;span class=&quot;z-punctuation z-accessor z-rust&quot;&gt;::&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;new&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-terminator z-rust&quot;&gt;;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-keyword z-control z-rust&quot;&gt;for&lt;&#x2F;span&gt; &lt;span class=&quot;z-keyword z-operator z-rust&quot;&gt;_&lt;&#x2F;span&gt; &lt;span class=&quot;z-keyword z-operator z-rust&quot;&gt;in&lt;&#x2F;span&gt; &lt;span class=&quot;z-constant z-numeric z-integer z-decimal z-rust&quot;&gt;0&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator z-range z-rust&quot;&gt;..&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric z-integer z-decimal z-rust&quot;&gt;2&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-block z-begin z-rust&quot;&gt;{&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;    &lt;span class=&quot;z-storage z-type z-rust&quot;&gt;let&lt;&#x2F;span&gt; rx &lt;span class=&quot;z-keyword z-operator z-assignment z-rust&quot;&gt;=&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-path z-rust&quot;&gt;Arc&lt;span class=&quot;z-punctuation z-accessor z-rust&quot;&gt;::&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;clone&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator z-bitwise z-rust&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;rx&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-terminator z-rust&quot;&gt;;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;    &lt;span class=&quot;z-storage z-type z-rust&quot;&gt;let&lt;&#x2F;span&gt; seen &lt;span class=&quot;z-keyword z-operator z-assignment z-rust&quot;&gt;=&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-path z-rust&quot;&gt;Arc&lt;span class=&quot;z-punctuation z-accessor z-rust&quot;&gt;::&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;clone&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator z-bitwise z-rust&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;seen&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-terminator z-rust&quot;&gt;;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;    &lt;span class=&quot;z-meta z-path z-rust&quot;&gt;tokio&lt;span class=&quot;z-punctuation z-accessor z-rust&quot;&gt;::&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;spawn&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;async &lt;span class=&quot;z-storage z-modifier z-rust&quot;&gt;move&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-block z-begin z-rust&quot;&gt;{&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;        &lt;span class=&quot;z-keyword z-control z-rust&quot;&gt;loop&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-block z-begin z-rust&quot;&gt;{&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;            &lt;span class=&quot;z-storage z-type z-rust&quot;&gt;let&lt;&#x2F;span&gt; &lt;span class=&quot;z-support z-type z-rust&quot;&gt;Some&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;t&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; &lt;span class=&quot;z-keyword z-operator z-assignment z-rust&quot;&gt;=&lt;&#x2F;span&gt; rx&lt;span class=&quot;z-punctuation z-accessor z-dot z-rust&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-support z-function z-rust&quot;&gt;lock&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-accessor z-dot z-rust&quot;&gt;.&lt;&#x2F;span&gt;await&lt;span class=&quot;z-punctuation z-accessor z-dot z-rust&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-support z-function z-rust&quot;&gt;recv&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-accessor z-dot z-rust&quot;&gt;.&lt;&#x2F;span&gt;await &lt;span class=&quot;z-keyword z-control z-rust&quot;&gt;else&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-block z-begin z-rust&quot;&gt;{&lt;&#x2F;span&gt; &lt;span class=&quot;z-keyword z-control z-rust&quot;&gt;break&lt;&#x2F;span&gt; &lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-block z-end z-rust&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-terminator z-rust&quot;&gt;;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;            &lt;span class=&quot;z-keyword z-control z-rust&quot;&gt;if&lt;&#x2F;span&gt; &lt;span class=&quot;z-keyword z-operator z-logical z-rust&quot;&gt;!&lt;&#x2F;span&gt;seen&lt;span class=&quot;z-punctuation z-accessor z-dot z-rust&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-support z-function z-rust&quot;&gt;lock&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-accessor z-dot z-rust&quot;&gt;.&lt;&#x2F;span&gt;await&lt;span class=&quot;z-punctuation z-accessor z-dot z-rust&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-support z-function z-rust&quot;&gt;insert&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;t&lt;span class=&quot;z-punctuation z-accessor z-dot z-rust&quot;&gt;.&lt;&#x2F;span&gt;id&lt;span class=&quot;z-punctuation z-accessor z-dot z-rust&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-support z-function z-rust&quot;&gt;clone&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-block z-begin z-rust&quot;&gt;{&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;                &lt;span class=&quot;z-keyword z-control z-rust&quot;&gt;continue&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-terminator z-rust&quot;&gt;;&lt;&#x2F;span&gt; &lt;span class=&quot;z-comment z-line z-double-slash z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-rust&quot;&gt;&#x2F;&#x2F;&lt;&#x2F;span&gt; delivered twice, already settled
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;            &lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-block z-end z-rust&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;            &lt;span class=&quot;z-support z-function z-rust&quot;&gt;settle&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator z-bitwise z-rust&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;t&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-accessor z-dot z-rust&quot;&gt;.&lt;&#x2F;span&gt;await&lt;span class=&quot;z-punctuation z-terminator z-rust&quot;&gt;;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;        &lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-block z-end z-rust&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;    &lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-block z-end z-rust&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-terminator z-rust&quot;&gt;;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-block z-end z-rust&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Node has no bounded channel in the standard library, so the TypeScript version writes one: a &lt;code&gt;put&lt;&#x2F;code&gt; that waits while the queue is full and a &lt;code&gt;take&lt;&#x2F;code&gt; that waits while it is empty. It is about forty lines in &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;gabrielkoerich&#x2F;gabrielkoerich&#x2F;blob&#x2F;main&#x2F;examples&#x2F;to-queue-or-not-to-queue&#x2F;typescript&#x2F;queue.ts&quot;&gt;&lt;code&gt;typescript&#x2F;queue.ts&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;. Both versions push 40 messages, ten of them duplicates, through a queue of four with two workers:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;text&quot; class=&quot;language-text z-code&quot;&gt;&lt;code class=&quot;language-text&quot; data-lang=&quot;text&quot;&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;rust:        settled 30 of 40 messages (30 unique) in 324ms, slowest send waited 22ms
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;typescript:  settled 30 of 40 messages (30 unique) in 315ms, slowest put waited 21ms
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The numbers match because the work is waiting, and both runtimes wait well. The differences are elsewhere. Tokio can run those two workers on two threads, and the compiler makes the shared set prove it is safe to share, with &lt;code&gt;Arc&lt;&#x2F;code&gt; and a &lt;code&gt;Mutex&lt;&#x2F;code&gt;. Node runs both workers on one thread, so the set needs no lock, and nothing in the language stops you from forgetting the bound. Tokio&#x27;s channel is bounded unless you pick the unbounded one on purpose. In TypeScript a bound exists only if you write it.&lt;&#x2F;p&gt;
&lt;p&gt;The duplicate check is the important line in both. A worker that does the work twice for a duplicate message has a bug, because the queue only ever promised at least once. The set in memory is the demo. In production the idempotency key goes into the database with the result, in the same write.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;events-in-a-queue-out-an-indexer&quot;&gt;Events in, a queue out: an indexer &lt;a class=&quot;zola-anchor&quot; href=&quot;#events-in-a-queue-out-an-indexer&quot; aria-label=&quot;Link to this section&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;The design I like most for this came from an indexer I built in TypeScript for a Solana program. It used both kinds of events, each for what it is good at.&lt;&#x2F;p&gt;
&lt;p&gt;The producer side received transactions by webhook. An RPC provider posted every transaction that touched the program to an authenticated endpoint, the indexer parsed and validated each one, and the parser emitted typed events on an in-process event bus: one per program event, one per parsed instruction and one per parsed transaction. Listeners were small classes with an optional &lt;code&gt;when&lt;&#x2F;code&gt; guard and a &lt;code&gt;handle&lt;&#x2F;code&gt;. One saved the parsed instructions to the database, a couple sent notifications, and one republished every program event to a Pub&#x2F;Sub topic, with the event type as a message attribute.&lt;&#x2F;p&gt;
&lt;p&gt;Webhooks were the choice on purpose. A websocket subscription such as &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;solana.com&#x2F;docs&#x2F;rpc&#x2F;websocket&#x2F;logssubscribe&quot;&gt;&lt;code&gt;logsSubscribe&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; works too, but it only delivers while the socket is connected, and a dropped connection is a silent gap. A webhook from a provider that retries until it gets a success response is more reliable, because the retry is the provider&#x27;s job: the endpoint answers &lt;code&gt;202&lt;&#x2F;code&gt; when it has the batch and &lt;code&gt;500&lt;&#x2F;code&gt; when it does not.&lt;&#x2F;p&gt;
&lt;p&gt;That last listener is the boundary. Everything before it is fast, local and in one process. Everything after it is durable and can fan out to any number of services that never import the indexer.&lt;&#x2F;p&gt;
&lt;p&gt;The consumer side was a worker on a Pub&#x2F;Sub subscription. It dispatched each message to a handler by its type attribute, and each handler could set retries, a delay, a unique lock so the same job never ran twice at once, and whether to ack on start or on finish. It acked on success, and on failure waited and nacked so Pub&#x2F;Sub would deliver it again. When it was at its limit of 30 running jobs, it nacked new messages instead of taking them.&lt;&#x2F;p&gt;
&lt;p&gt;The shape holds up. Reading it again next to the docs, these are the parts I would change.&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;A backfill for when the provider gives up. Webhook retries do not last forever. A stored cursor, the last signature processed, and a backfill with &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;solana.com&#x2F;docs&#x2F;rpc&#x2F;http&#x2F;getsignaturesforaddress&quot;&gt;&lt;code&gt;getSignaturesForAddress&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;, which pages through history with &lt;code&gt;before&lt;&#x2F;code&gt; and &lt;code&gt;until&lt;&#x2F;code&gt;, up to 1,000 signatures per call, would close any gap the provider leaves.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;A retried batch re-emits events. The parser emitted program events while it parsed. If one transaction in a batch failed after earlier ones had already emitted, the endpoint answered &lt;code&gt;500&lt;&#x2F;code&gt;, the provider sent the whole batch again, and those events fired twice, including the publish to Pub&#x2F;Sub. Each event needs a key that does not change between attempts, such as the transaction signature plus the instruction index, and every consumer has to treat a second copy as already done.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;The &lt;code&gt;202&lt;&#x2F;code&gt; went out before the listeners finished. The endpoint emitted the events and answered right away, and as the event bus section showed, &lt;code&gt;emit&lt;&#x2F;code&gt; does not wait for async listeners. A listener that failed after that, the Pub&#x2F;Sub publish included, never caused a &lt;code&gt;500&lt;&#x2F;code&gt;, so the provider never retried it. Awaiting the listeners that matter before answering, or writing to an outbox first, puts that failure back where the retry can see it.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;Let the subscription do the retries. Nacking to wait brings the message straight back. A &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;docs.cloud.google.com&#x2F;pubsub&#x2F;docs&#x2F;reference&#x2F;rest&#x2F;v1&#x2F;projects.subscriptions&quot;&gt;retry policy&lt;&#x2F;a&gt; on the subscription backs off instead, and the client&#x27;s flow control caps the messages in flight without nacking. The attempt counter the worker read was also &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;docs.cloud.google.com&#x2F;pubsub&#x2F;docs&#x2F;reference&#x2F;rest&#x2F;v1&#x2F;projects.subscriptions&#x2F;pull&quot;&gt;always 0&lt;&#x2F;a&gt; without a dead letter topic, so retry limits based on it never fired.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;Publish from the database, not next to it. Saving to the database and publishing to Pub&#x2F;Sub were two separate steps, so a crash between them loses one. The &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;microservices.io&#x2F;patterns&#x2F;data&#x2F;transactional-outbox.html&quot;&gt;transactional outbox&lt;&#x2F;a&gt; writes the message into the database in the same transaction as the data, and a separate process publishes it. It can publish twice, which is fine, because the consumers are idempotent anyway.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h2 id=&quot;events-all-the-way-down&quot;&gt;Events all the way down &lt;a class=&quot;zola-anchor&quot; href=&quot;#events-all-the-way-down&quot; aria-label=&quot;Link to this section&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;The biggest version of this I built was the automation engine at Bulldesk, running on the same servers and queues as those 500 million jobs a month. A customer drew a campaign as a flow of steps, for example: wait two days, send an email, if they opened it add a tag, otherwise send a text. Every step was a job, and almost everything between steps was an event.&lt;&#x2F;p&gt;
&lt;p&gt;It started with domain events. A lead was created, visited a page, had an order change status, moved stage in a CRM. Each of those fired an event, and a queued listener picked it up on its own &lt;code&gt;listeners&lt;&#x2F;code&gt; queue, 15 seconds later. The listener found the active campaigns whose trigger matched that event and dispatched the first step of each one as a job. I wrote up a small piece of the same idea in 2018, in &lt;a href=&quot;https:&#x2F;&#x2F;gabrielkoerich.com&#x2F;posts&#x2F;refactoring-to-events&#x2F;&quot;&gt;Refactoring to Events&lt;&#x2F;a&gt;: a Stripe webhook controller that maps each webhook type to its own Laravel event, with listeners doing the work.&lt;&#x2F;p&gt;
&lt;p&gt;Every step job began with one more event, &lt;code&gt;ActionStart&lt;&#x2F;code&gt;, dispatched so that the first listener to answer &lt;code&gt;false&lt;&#x2F;code&gt; stopped the job. Eight listeners voted in order: is this step already running, was it deleted, is sending disabled, is it inside the campaign&#x27;s schedule, is the lead still in the campaign, was this step already applied to this lead, is it a decision, do the segments still match. Each one was a few lines and checked one thing. A job that got a &lt;code&gt;false&lt;&#x2F;code&gt; was recorded as skipped.&lt;&#x2F;p&gt;
&lt;p&gt;When a step finished, its outcome was an event too: applied, not applied or skipped. Their listeners released the lock, wrote the log, scheduled the yes or no branch of a decision, and queued the next step, which is how a campaign moved forward without anything holding the whole flow in memory.&lt;&#x2F;p&gt;
&lt;p&gt;Order mattered inside one lead&#x27;s campaign, and almost nowhere else, so that is the only place the engine kept it. A lead&#x27;s steps ran one at a time even though each one was queued: a step dispatched the next only after it finished, delayed by the next step&#x27;s own wait plus ten seconds, on its own &lt;code&gt;automation&lt;&#x2F;code&gt; queue. Across leads, everything ran in parallel. A bulk send to a segment walked its leads and dispatched one job per lead to one of five sending queues, picked at random, with a random delay of up to 90 seconds, so a send to a large list spread across workers and across time instead of landing all at once. It also stored its position as it went and stopped when paused, so a paused or crashed send picked up where it left off. The queued listeners on one event were separate jobs and ran side by side, while the guards on &lt;code&gt;ActionStart&lt;&#x2F;code&gt; ran inline and in order, because each one had to answer before the next could decide.&lt;&#x2F;p&gt;
&lt;p&gt;The chain was the second design. The first one queued every action of a campaign the moment a lead entered it, each delayed to the time it should run: three weekly emails were three jobs sitting in the queue for weeks. That made a lot of small problems. When the one in the middle failed, the rest were already queued behind it with nothing tying them to it. Editing the third email was fine, because a queued job held a reference to the email, its class and id, and loaded the content when it ran. Changing the step was not. If the customer swapped that email for another one, the queued job pointed at a deleted email and did nothing. If they replaced it with a different action, the job for the old action was still waiting in the queue. And if they added actions to the campaign, leads already in it never got them, because their whole campaign had been queued the day they entered. So I changed it to queue only the first action, and have each action, when it finished, check for a next one and queue that, until the campaign ended. With the chain, a step was queued at most one wait ahead, and the guards on &lt;code&gt;ActionStart&lt;&#x2F;code&gt; checked the campaign again when it ran.&lt;&#x2F;p&gt;
&lt;p&gt;That was the point of splitting a campaign into small actions, each its own step. Every action did one thing, so it finished fast, and when something was wrong it failed fast, at one step, without touching the steps already done. A step that failed went to a failed jobs table in MySQL and its error to an error tracker, and from there I could retry one job or all of them. A retried step that succeeded queued the next one, so an exception in one action no longer broke the campaign for that lead, it carried on from where it stopped. Every piece was small and could change on its own. The trade is that the flow of a campaign lives in the event map rather than in any one file, so following one lead through it means reading the logs.&lt;&#x2F;p&gt;
&lt;p&gt;Those logs had a cost of their own. Every step wrote to the log every time it ran, for every lead in every campaign, all into the same MySQL database the app ran on. The same table was also the duplicate check. Before a step ran, the guard for &amp;quot;already applied&amp;quot; looked in it for that action, lead and campaign, and skipped the step if it found a row, unless the campaign was set to repeat or the lead was added by hand. So every step read the table before it wrote to it. Writes never stopped, and in the end the database could not keep up with them. When we exited, I was looking for a different kind of database for the automation logs, one built to take a lot of writes at once.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;locks-one-message-one-worker&quot;&gt;Locks: one message, one worker &lt;a class=&quot;zola-anchor&quot; href=&quot;#locks-one-message-one-worker&quot; aria-label=&quot;Link to this section&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;With one worker, taking a message is enough. With several, each message needs a lock, usually called a lease, so only one worker has it at a time. Without it, two workers read the same message and both run it.&lt;&#x2F;p&gt;
&lt;p&gt;The brokers give you the lease. Beanstalkd&#x27;s time to run, SQS&#x27;s &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;docs.aws.amazon.com&#x2F;AWSSimpleQueueService&#x2F;latest&#x2F;SQSDeveloperGuide&#x2F;sqs-visibility-timeout.html&quot;&gt;visibility timeout&lt;&#x2F;a&gt; and Pub&#x2F;Sub&#x27;s &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;docs.cloud.google.com&#x2F;pubsub&#x2F;docs&#x2F;subscription-properties&quot;&gt;ack deadline&lt;&#x2F;a&gt; all hide a message from other workers while one has it.&lt;&#x2F;p&gt;
&lt;p&gt;A table that workers poll has none of this unless you build it. In Postgres it is one query: &lt;code&gt;SKIP LOCKED&lt;&#x2F;code&gt; passes over the rows another worker is claiming right now, and a &lt;code&gt;locked_until&lt;&#x2F;code&gt; column is the lease after that:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;sql&quot; class=&quot;language-sql z-code&quot;&gt;&lt;code class=&quot;language-sql&quot; data-lang=&quot;sql&quot;&gt;&lt;span class=&quot;z-source z-sql&quot;&gt;&lt;span class=&quot;z-keyword z-other z-DML z-sql&quot;&gt;UPDATE&lt;&#x2F;span&gt; jobs
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-sql&quot;&gt;&lt;span class=&quot;z-keyword z-other z-DML z-sql&quot;&gt;SET&lt;&#x2F;span&gt; locked_until &lt;span class=&quot;z-keyword z-operator z-comparison z-sql&quot;&gt;=&lt;&#x2F;span&gt; now&lt;span class=&quot;z-meta z-block z-sql&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-scope z-begin z-sql&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-section z-scope z-end z-sql&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; &lt;span class=&quot;z-keyword z-operator z-math z-sql&quot;&gt;+&lt;&#x2F;span&gt; interval &lt;span class=&quot;z-string z-quoted z-single z-sql&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-sql&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;30 seconds&lt;span class=&quot;z-punctuation z-definition z-string z-end z-sql&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;,
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-sql&quot;&gt;    attempts     &lt;span class=&quot;z-keyword z-operator z-comparison z-sql&quot;&gt;=&lt;&#x2F;span&gt; attempts &lt;span class=&quot;z-keyword z-operator z-math z-sql&quot;&gt;+&lt;&#x2F;span&gt; &lt;span class=&quot;z-constant z-numeric z-sql&quot;&gt;1&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-sql&quot;&gt;&lt;span class=&quot;z-keyword z-other z-DML z-sql&quot;&gt;WHERE&lt;&#x2F;span&gt; id &lt;span class=&quot;z-keyword z-operator z-comparison z-sql&quot;&gt;=&lt;&#x2F;span&gt; (
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-sql&quot;&gt;  &lt;span class=&quot;z-keyword z-other z-DML z-sql&quot;&gt;SELECT&lt;&#x2F;span&gt; id &lt;span class=&quot;z-keyword z-other z-DML z-sql&quot;&gt;FROM&lt;&#x2F;span&gt; jobs
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-sql&quot;&gt;  &lt;span class=&quot;z-keyword z-other z-DML z-sql&quot;&gt;WHERE&lt;&#x2F;span&gt; run_at &lt;span class=&quot;z-keyword z-operator z-comparison z-sql&quot;&gt;&amp;lt;=&lt;&#x2F;span&gt; now&lt;span class=&quot;z-meta z-block z-sql&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-scope z-begin z-sql&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-section z-scope z-end z-sql&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-sql&quot;&gt;    &lt;span class=&quot;z-keyword z-operator z-logical z-sql&quot;&gt;AND&lt;&#x2F;span&gt; (locked_until &lt;span class=&quot;z-keyword z-operator z-logical z-sql&quot;&gt;IS&lt;&#x2F;span&gt; &lt;span class=&quot;z-constant z-language z-sql&quot;&gt;NULL&lt;&#x2F;span&gt; &lt;span class=&quot;z-keyword z-operator z-logical z-sql&quot;&gt;OR&lt;&#x2F;span&gt; locked_until &lt;span class=&quot;z-keyword z-operator z-comparison z-sql&quot;&gt;&amp;lt;&lt;&#x2F;span&gt; now&lt;span class=&quot;z-meta z-block z-sql&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-scope z-begin z-sql&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-section z-scope z-end z-sql&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;)
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-sql&quot;&gt;  &lt;span class=&quot;z-keyword z-other z-DML z-sql&quot;&gt;ORDER BY&lt;&#x2F;span&gt; id
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-sql&quot;&gt;  &lt;span class=&quot;z-keyword z-other z-DML z-sql&quot;&gt;LIMIT&lt;&#x2F;span&gt; &lt;span class=&quot;z-constant z-numeric z-sql&quot;&gt;1&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-sql&quot;&gt;  FOR &lt;span class=&quot;z-keyword z-other z-DML z-sql&quot;&gt;UPDATE&lt;&#x2F;span&gt; SKIP LOCKED
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-sql&quot;&gt;)
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-sql&quot;&gt;RETURNING id, payload, attempts;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Source: &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;gabrielkoerich&#x2F;gabrielkoerich&#x2F;blob&#x2F;main&#x2F;examples&#x2F;to-queue-or-not-to-queue&#x2F;postgres&#x2F;claim.sql&quot;&gt;&lt;code&gt;postgres&#x2F;claim.sql&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;p&gt;I ran it with workers claiming at the same time against three jobs, using &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;gabrielkoerich&#x2F;gabrielkoerich&#x2F;blob&#x2F;main&#x2F;examples&#x2F;to-queue-or-not-to-queue&#x2F;postgres&#x2F;demo.sh&quot;&gt;&lt;code&gt;postgres&#x2F;demo.sh&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;text&quot; class=&quot;language-text z-code&quot;&gt;&lt;code class=&quot;language-text&quot; data-lang=&quot;text&quot;&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;worker A holds job 1 in an open transaction
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;worker B: claimed 2 attempt 1      (skipped the row A was claiming)
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;worker C: claimed 3 attempt 1
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;worker D: nothing                  (job 1 is locked, 2 and 3 hold a lease)
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;job 1&amp;#39;s lease expires
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;worker E: claimed 1 attempt 2      (the work outlived its lease)
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The last line is the catch. A lease is a timer, and if the worker only acknowledges at the end, any job that runs longer than the lease goes to a second worker while the first is still on it. Now it runs twice, at the same time. There are two ways out. Acknowledge at the start, and a crash loses the job, which is at most once. Or acknowledge at the end and extend the lease while you work. SQS and the Pub&#x2F;Sub clients both let a worker extend its lease while it works. The indexer&#x27;s worker made this a choice per handler, with an option to ack on start.&lt;&#x2F;p&gt;
&lt;p&gt;A lease covers one message. Two different messages about the same thing, two deposits for the same account, need a lock on the thing itself. The indexer had that too, a unique lock per job key, but it lived in the memory of one worker process. With two instances, each had its own set of locks, and both could run the same job. A lock that several workers share has to live somewhere they all see: a row lock in the database the work writes to, or a Redis key set with &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;redis.io&#x2F;docs&#x2F;latest&#x2F;commands&#x2F;set&#x2F;&quot;&gt;&lt;code&gt;SET key token NX EX seconds&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;, released by a script that deletes it only if it still holds your token.&lt;&#x2F;p&gt;
&lt;p&gt;Bulldesk&#x27;s engine used the simple version, and it shows the catch from the other side. It ran on several servers with several queues, and its cache was Redis, so a lock in the cache was shared by every worker on every machine. The first guard on every step took one with the cache&#x27;s &lt;code&gt;add&lt;&#x2F;code&gt;, keyed on the step, the lead and the sending, for 60 seconds. Laravel&#x27;s &lt;code&gt;add&lt;&#x2F;code&gt; was a single Lua script, check that the key does not exist and set it with an expiry, and Redis runs a script atomically, so two workers could never both get it.&lt;&#x2F;p&gt;
&lt;p&gt;The timing left a gap, even if it rarely mattered. Actions were fast, and when something was wrong they failed fast, but the step jobs were allowed to run for 120 seconds, twice the lock. A step still running after a minute would have lost its lock, and a second worker could have started the same step for the same lead. The release had the matching gap: it was a plain delete, so a step that finished after its lock expired could delete the lock another worker had just taken, which is exactly the case the Redis docs&#x27; token check exists for. Fast work hides both. The rule for work that can be slow is that the lease outlasts the longest the work is allowed to take, and only the holder can release it.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;retries-and-backoff&quot;&gt;Retries and backoff &lt;a class=&quot;zola-anchor&quot; href=&quot;#retries-and-backoff&quot; aria-label=&quot;Link to this section&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;Every queue retries. The part you design is when to stop and how long to wait.&lt;&#x2F;p&gt;
&lt;p&gt;Errors come in two kinds. A timeout, a 429 or a dropped connection may succeed next time. A bad payload or an unknown account will fail forever, and retrying it only burns time and hides it. Retry the first kind, send the second straight to a dead letter queue, and give up on the first kind after a limit.&lt;&#x2F;p&gt;
&lt;p&gt;The wait between tries should grow, and it should be random. Exponential backoff alone makes every worker that failed together retry together. Full jitter waits a random time between zero and the exponential ceiling, which spreads them out:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; class=&quot;language-rust z-code&quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-comment z-line z-double-slash z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-rust&quot;&gt;&#x2F;&#x2F;&lt;&#x2F;span&gt; Full jitter: a random wait between zero and the exponential ceiling
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-storage z-type z-function z-rust&quot;&gt;fn&lt;&#x2F;span&gt; &lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function z-rust&quot;&gt;backoff&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-parameters z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-parameters z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-parameter z-rust&quot;&gt;attempt&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-rust&quot;&gt;:&lt;&#x2F;span&gt; &lt;span class=&quot;z-storage z-type z-rust&quot;&gt;u32&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-parameters z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-parameters z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt; &lt;span class=&quot;z-meta z-function z-return-type z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-separator z-rust&quot;&gt;-&amp;gt;&lt;&#x2F;span&gt; Duration&lt;&#x2F;span&gt; &lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-block z-begin z-rust&quot;&gt;{&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;    &lt;span class=&quot;z-storage z-type z-rust&quot;&gt;let&lt;&#x2F;span&gt; ceiling &lt;span class=&quot;z-keyword z-operator z-assignment z-rust&quot;&gt;=&lt;&#x2F;span&gt; &lt;span class=&quot;z-constant z-other z-rust&quot;&gt;BASE&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-accessor z-dot z-rust&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-support z-function z-rust&quot;&gt;saturating_mul&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric z-integer z-decimal z-rust&quot;&gt;1&lt;&#x2F;span&gt; &lt;span class=&quot;z-keyword z-operator z-comparison z-rust&quot;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator z-comparison z-rust&quot;&gt;&amp;lt;&lt;&#x2F;span&gt; attempt&lt;span class=&quot;z-punctuation z-accessor z-dot z-rust&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-support z-function z-rust&quot;&gt;min&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric z-integer z-decimal z-rust&quot;&gt;16&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-accessor z-dot z-rust&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-support z-function z-rust&quot;&gt;min&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-other z-rust&quot;&gt;CAP&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-terminator z-rust&quot;&gt;;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;    &lt;span class=&quot;z-storage z-type z-rust&quot;&gt;let&lt;&#x2F;span&gt; random &lt;span class=&quot;z-keyword z-operator z-assignment z-rust&quot;&gt;=&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-path z-rust&quot;&gt;RandomState&lt;span class=&quot;z-punctuation z-accessor z-rust&quot;&gt;::&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;new&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-accessor z-dot z-rust&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-support z-function z-rust&quot;&gt;build_hasher&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-accessor z-dot z-rust&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-support z-function z-rust&quot;&gt;finish&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-terminator z-rust&quot;&gt;;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;    &lt;span class=&quot;z-meta z-path z-rust&quot;&gt;Duration&lt;span class=&quot;z-punctuation z-accessor z-rust&quot;&gt;::&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;from_millis&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;random &lt;span class=&quot;z-keyword z-operator z-arithmetic z-rust&quot;&gt;%&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;ceiling&lt;span class=&quot;z-punctuation z-accessor z-dot z-rust&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-support z-function z-rust&quot;&gt;as_millis&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; &lt;span class=&quot;z-keyword z-operator z-rust&quot;&gt;as&lt;&#x2F;span&gt; &lt;span class=&quot;z-storage z-type z-rust&quot;&gt;u64&lt;&#x2F;span&gt; &lt;span class=&quot;z-keyword z-operator z-arithmetic z-rust&quot;&gt;+&lt;&#x2F;span&gt; &lt;span class=&quot;z-constant z-numeric z-integer z-decimal z-rust&quot;&gt;1&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-block z-end z-rust&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;pre data-lang=&quot;ts&quot; class=&quot;language-ts z-code&quot;&gt;&lt;code class=&quot;language-ts&quot; data-lang=&quot;ts&quot;&gt;&lt;span class=&quot;z-source z-ts&quot;&gt;&lt;span class=&quot;z-comment z-line z-double-slash z-ts&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-ts&quot;&gt;&#x2F;&#x2F;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment z-line z-double-slash z-ts&quot;&gt; Full jitter: a random wait between zero and the exponential ceiling&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-ts&quot;&gt;&lt;span class=&quot;z-meta z-var z-expr z-ts&quot;&gt;&lt;span class=&quot;z-storage z-type z-ts&quot;&gt;const&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-var-single-variable z-expr z-ts&quot;&gt;&lt;span class=&quot;z-meta z-definition z-variable z-ts&quot;&gt;&lt;span class=&quot;z-variable z-other z-constant z-ts&quot;&gt;&lt;span class=&quot;z-entity z-name z-function z-ts&quot;&gt;backoff&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; &lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator z-assignment z-ts&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-arrow z-ts&quot;&gt; &lt;span class=&quot;z-meta z-parameters z-ts&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-parameters z-begin z-ts&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-parameter z-ts&quot;&gt;attempt&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-type z-annotation z-ts&quot;&gt;&lt;span class=&quot;z-keyword z-operator z-type z-annotation z-ts&quot;&gt;:&lt;&#x2F;span&gt; &lt;span class=&quot;z-support z-type z-primitive z-ts&quot;&gt;number&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-parameters z-end z-ts&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; &lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-arrow z-ts&quot;&gt;&lt;span class=&quot;z-storage z-type z-function z-arrow z-ts&quot;&gt;=&amp;gt;&lt;&#x2F;span&gt; &lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-ts&quot;&gt;&lt;span class=&quot;z-support z-constant z-math z-ts&quot;&gt;Math&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-accessor z-ts&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-support z-function z-math z-ts&quot;&gt;random&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-brace z-round z-ts&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-brace z-round z-ts&quot;&gt;)&lt;&#x2F;span&gt; &lt;span class=&quot;z-keyword z-operator z-arithmetic z-ts&quot;&gt;*&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-function-call z-ts&quot;&gt;&lt;span class=&quot;z-support z-constant z-math z-ts&quot;&gt;Math&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-accessor z-ts&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-support z-function z-math z-ts&quot;&gt;min&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-brace z-round z-ts&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other z-constant z-ts&quot;&gt;CAP_MS&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-comma z-ts&quot;&gt;,&lt;&#x2F;span&gt; &lt;span class=&quot;z-variable z-other z-constant z-ts&quot;&gt;BASE_MS&lt;&#x2F;span&gt; &lt;span class=&quot;z-keyword z-operator z-arithmetic z-ts&quot;&gt;*&lt;&#x2F;span&gt; &lt;span class=&quot;z-constant z-numeric z-decimal z-ts&quot;&gt;2&lt;&#x2F;span&gt; &lt;span class=&quot;z-keyword z-operator z-arithmetic z-ts&quot;&gt;*&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator z-arithmetic z-ts&quot;&gt;*&lt;&#x2F;span&gt; &lt;span class=&quot;z-variable z-other z-readwrite z-ts&quot;&gt;attempt&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-brace z-round z-ts&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Source: &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;gabrielkoerich&#x2F;gabrielkoerich&#x2F;blob&#x2F;main&#x2F;examples&#x2F;to-queue-or-not-to-queue&#x2F;rust&#x2F;src&#x2F;bin&#x2F;retry.rs&quot;&gt;&lt;code&gt;rust&#x2F;src&#x2F;bin&#x2F;retry.rs&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;, &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;gabrielkoerich&#x2F;gabrielkoerich&#x2F;blob&#x2F;main&#x2F;examples&#x2F;to-queue-or-not-to-queue&#x2F;typescript&#x2F;retry.ts&quot;&gt;&lt;code&gt;typescript&#x2F;retry.ts&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Both versions run the same four messages, with a base of 100 ms, a cap of 2 seconds and five attempts:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;text&quot; class=&quot;language-text z-code&quot;&gt;&lt;code class=&quot;language-text&quot; data-lang=&quot;text&quot;&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;ok: settled on attempt 1
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;flaky: settled on attempt 3
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;dead letters: bad (unknown account), down (gave up after 5: 503)
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;In Rust the two kinds of error are an enum, and the &lt;code&gt;match&lt;&#x2F;code&gt; will not compile if a new kind is added and not handled. In TypeScript they are two classes and an &lt;code&gt;instanceof&lt;&#x2F;code&gt; check, and an error from somewhere else falls through to &amp;quot;permanent&amp;quot; unless you remember to classify it.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;exactly-once-is-always-scoped&quot;&gt;Exactly once is always scoped &lt;a class=&quot;zola-anchor&quot; href=&quot;#exactly-once-is-always-scoped&quot; aria-label=&quot;Link to this section&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;Every broker that offers exactly once means something narrower than the words.&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;SQS FIFO&lt;&#x2F;strong&gt; deduplicates by deduplication id, but only &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;docs.aws.amazon.com&#x2F;AWSSimpleQueueService&#x2F;latest&#x2F;SQSDeveloperGuide&#x2F;using-messagededuplicationid-property.html&quot;&gt;within a 5 minute window&lt;&#x2F;a&gt;.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Pub&#x2F;Sub&lt;&#x2F;strong&gt; exactly-once delivery &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;docs.cloud.google.com&#x2F;pubsub&#x2F;docs&#x2F;exactly-once-delivery&quot;&gt;only applies&lt;&#x2F;a&gt; &amp;quot;when subscribers connect to the service in the same region&amp;quot;, and push subscriptions do not support it.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Kafka&lt;&#x2F;strong&gt; has an idempotent producer and transactions that write to several partitions atomically. Its &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;kafka.apache.org&#x2F;42&#x2F;design&#x2F;design&#x2F;&quot;&gt;design docs&lt;&#x2F;a&gt; are clear that exactly once &amp;quot;for other destination systems generally requires cooperation with such systems&amp;quot;.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;That other system is your consumer. The database write, the webhook or the token transfer at the end of the pipeline is outside every one of those guarantees. Make that step idempotent, and at least once is enough.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;choosing-one&quot;&gt;Choosing one &lt;a class=&quot;zola-anchor&quot; href=&quot;#choosing-one&quot; aria-label=&quot;Link to this section&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;&lt;&#x2F;th&gt;&lt;th&gt;Delivery&lt;&#x2F;th&gt;&lt;th&gt;Ordering&lt;&#x2F;th&gt;&lt;th&gt;Replay&lt;&#x2F;th&gt;&lt;th&gt;Fits&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;docs.aws.amazon.com&#x2F;AWSSimpleQueueService&#x2F;latest&#x2F;SQSDeveloperGuide&#x2F;sqs-queue-types.html&quot;&gt;SQS&lt;&#x2F;a&gt;&lt;&#x2F;td&gt;&lt;td&gt;at least once, FIFO dedup for 5 min&lt;&#x2F;td&gt;&lt;td&gt;best effort, or per message group with FIFO&lt;&#x2F;td&gt;&lt;td&gt;no, retention up to 14 days&lt;&#x2F;td&gt;&lt;td&gt;jobs on AWS, little to operate&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;docs.cloud.google.com&#x2F;pubsub&#x2F;docs&#x2F;ordering&quot;&gt;Pub&#x2F;Sub&lt;&#x2F;a&gt;&lt;&#x2F;td&gt;&lt;td&gt;at least once, exactly once in region&lt;&#x2F;td&gt;&lt;td&gt;per ordering key&lt;&#x2F;td&gt;&lt;td&gt;retention up to 31 days&lt;&#x2F;td&gt;&lt;td&gt;fan out on GCP&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;kafka.apache.org&#x2F;42&#x2F;getting-started&#x2F;introduction&#x2F;&quot;&gt;Kafka&lt;&#x2F;a&gt;&lt;&#x2F;td&gt;&lt;td&gt;at least once, transactions inside Kafka&lt;&#x2F;td&gt;&lt;td&gt;per partition&lt;&#x2F;td&gt;&lt;td&gt;yes, consumers rewind their offset&lt;&#x2F;td&gt;&lt;td&gt;event logs many systems read&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;redis.io&#x2F;docs&#x2F;latest&#x2F;develop&#x2F;data-types&#x2F;streams&#x2F;&quot;&gt;Redis Streams&lt;&#x2F;a&gt;&lt;&#x2F;td&gt;&lt;td&gt;at least once with consumer groups&lt;&#x2F;td&gt;&lt;td&gt;per stream&lt;&#x2F;td&gt;&lt;td&gt;while the stream is kept&lt;&#x2F;td&gt;&lt;td&gt;already running Redis&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;www.postgresql.org&#x2F;docs&#x2F;current&#x2F;sql-select.html&quot;&gt;Postgres &lt;code&gt;SKIP LOCKED&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;&lt;&#x2F;td&gt;&lt;td&gt;what your transaction gives you&lt;&#x2F;td&gt;&lt;td&gt;whatever you order by&lt;&#x2F;td&gt;&lt;td&gt;it is a table&lt;&#x2F;td&gt;&lt;td&gt;work tied to data in the same database&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;beanstalkd&#x2F;beanstalkd&#x2F;blob&#x2F;master&#x2F;doc&#x2F;protocol.txt&quot;&gt;Beanstalkd&lt;&#x2F;a&gt;&lt;&#x2F;td&gt;&lt;td&gt;at least once, time to run&lt;&#x2F;td&gt;&lt;td&gt;priority, then arrival&lt;&#x2F;td&gt;&lt;td&gt;no&lt;&#x2F;td&gt;&lt;td&gt;fast, simple job queues on your own servers, with no replication&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;p&gt;Kafka is the odd one. A queue deletes a message once it is acked, and in Kafka events &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;kafka.apache.org&#x2F;42&#x2F;getting-started&#x2F;introduction&#x2F;&quot;&gt;&amp;quot;are not deleted after consumption&amp;quot;&lt;&#x2F;a&gt;, so a new consumer can read the whole history. That makes it an event log more than a queue, and it is the right tool when the events themselves are the record. It is moving toward queues too: Kafka 4.2 made &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;kafka.apache.org&#x2F;42&#x2F;getting-started&#x2F;upgrade&#x2F;&quot;&gt;share groups&lt;&#x2F;a&gt; production ready, where consumers share a partition and acknowledge each message on its own, instead of one consumer per partition committing an offset.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;what-it-costs&quot;&gt;What it costs &lt;a class=&quot;zola-anchor&quot; href=&quot;#what-it-costs&quot; aria-label=&quot;Link to this section&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;&lt;strong&gt;Duplicates are the default.&lt;&#x2F;strong&gt; Every option above delivers at least once in its normal mode. A consumer that is not idempotent breaks on its first retry.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Ordering costs throughput.&lt;&#x2F;strong&gt; Ordering keys, message groups and partitions all limit parallelism to one consumer per key. Ask for ordering only where the domain needs it, usually per account or per user, never globally.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;A queue hides failure.&lt;&#x2F;strong&gt; A request that fails shows an error. A job that fails sits in a dead letter queue that nobody reads. A dead letter queue needs an alert and an owner.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Another system to run.&lt;&#x2F;strong&gt; A broker is a service with its own outages, limits and bill. The table you already have, or a semaphore, costs nothing.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-rule-i-use&quot;&gt;The rule I use &lt;a class=&quot;zola-anchor&quot; href=&quot;#the-rule-i-use&quot; aria-label=&quot;Link to this section&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;Run it in the request if the caller needs the answer. Use a semaphore if the problem is too much at once. Use an in-process queue when one process does the work and losing it on a crash is acceptable. Use a broker when the work must survive the process, or when more than one service needs to hear about it. Whichever you pick, give every message a key, make every consumer idempotent, retry only what can succeed, and put an alert on the dead letters.&lt;&#x2F;p&gt;
</content>
    </entry>
    <entry xml:lang="en">
        <title>My finances in plain text</title>
        <published>2026-10-09T00:00:00+00:00</published>
        <updated>2026-10-09T00:00:00+00:00</updated>
        <author>
            <name>Gabriel Koerich</name>
        </author>
        <link rel="alternate" type="text/html" href="https://gabrielkoerich.com/posts/my-finances-in-plain-text/"/>
        <id>https://gabrielkoerich.com/posts/my-finances-in-plain-text/</id>
        <summary>Since 2022 every real I spend goes into a Beancount ledger, about 13,400 transactions, most never typed. The importers, categories and daily jobs behind it.</summary>
        <content type="html" xml:base="https://gabrielkoerich.com/posts/my-finances-in-plain-text/">&lt;p&gt;Since 2022 every real I spend goes into a text file. Bank accounts, credit cards, investments and my consulting company across many Brazilian banks, plus DeFi positions. About 13,400 transactions so far, and most of them I never typed.&lt;&#x2F;p&gt;
&lt;p&gt;The file is a &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;beancount.github.io&quot;&gt;Beancount&lt;&#x2F;a&gt; ledger. The part that took years is everything around it: getting the statements, turning them into transactions, putting each one in the right account, and doing that every day without me.&lt;&#x2F;p&gt;
&lt;p&gt;Today about 95% of it runs on its own. My part is small: a few statements only exist behind a bank login, so I download those and email them to myself, and now and then I fix a category the system got wrong. Downloading, importing, categorizing, checking balances, removing duplicates and archiving all happen without me.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;why-plain-text&quot;&gt;Why plain text &lt;a class=&quot;zola-anchor&quot; href=&quot;#why-plain-text&quot; aria-label=&quot;Link to this section&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;I tried the usual options first.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Finance apps&lt;&#x2F;strong&gt; are nice for a few months. Then they change the pricing, drop a bank, or shut down, and the history goes with them. You also cannot ask them a question they did not plan for.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;A spreadsheet&lt;&#x2F;strong&gt; holds everything and checks nothing. A typo in a formula moves money between categories and nobody notices for a year.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Open Finance aggregators&lt;&#x2F;strong&gt; exist in Brazil, but they are built for companies, they do not cover all my accounts, and they put a third party between me and my bank data.&lt;&#x2F;p&gt;
&lt;p&gt;Beancount is double entry accounting in a text file. Every transaction has to balance, and I can assert that an account holds an exact amount on a given day. If the numbers do not match the bank, &lt;code&gt;bean-check&lt;&#x2F;code&gt; fails. It lives in git, so every change has a diff. And because it is text, I can query it with SQL, read it with &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;beancount.github.io&#x2F;fava&#x2F;&quot;&gt;Fava&lt;&#x2F;a&gt;, and hand it to an agent.&lt;&#x2F;p&gt;
&lt;p&gt;The catch is that Beancount gives you the format and nothing else. Brazilian banks do not give individuals an API, they give you a PDF by email.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;getting-the-statements&quot;&gt;Getting the statements &lt;a class=&quot;zola-anchor&quot; href=&quot;#getting-the-statements&quot; aria-label=&quot;Link to this section&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;Every account has an entry in a config file that says where its statements come from:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;yaml&quot; class=&quot;language-yaml z-code&quot;&gt;&lt;code class=&quot;language-yaml&quot; data-lang=&quot;yaml&quot;&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;&lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;bank_a_credit_card&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value z-mapping z-yaml&quot;&gt;:&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;  &lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;download_method&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value z-mapping z-yaml&quot;&gt;:&lt;&#x2F;span&gt; &lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;email&lt;&#x2F;span&gt;      &lt;span class=&quot;z-comment z-line z-number-sign z-yaml&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-line z-number-sign z-yaml&quot;&gt;#&lt;&#x2F;span&gt; Proton Bridge over IMAP
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;&lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;bank_b_credit_card&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value z-mapping z-yaml&quot;&gt;:&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;  &lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;download_method&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value z-mapping z-yaml&quot;&gt;:&lt;&#x2F;span&gt; &lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;mail_app&lt;&#x2F;span&gt;   &lt;span class=&quot;z-comment z-line z-number-sign z-yaml&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-line z-number-sign z-yaml&quot;&gt;#&lt;&#x2F;span&gt; AppleScript against Mail.app
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;&lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;defi&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value z-mapping z-yaml&quot;&gt;:&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-yaml&quot;&gt;  &lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;&lt;span class=&quot;z-entity z-name z-tag z-yaml&quot;&gt;download_method&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-key-value z-mapping z-yaml&quot;&gt;:&lt;&#x2F;span&gt; &lt;span class=&quot;z-string z-unquoted z-plain z-out z-yaml&quot;&gt;api&lt;&#x2F;span&gt;        &lt;span class=&quot;z-comment z-line z-number-sign z-yaml&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-line z-number-sign z-yaml&quot;&gt;#&lt;&#x2F;span&gt; on-chain events
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Most banks send the monthly bill to my email, so the downloader searches the mailbox, grabs the attachment, and drops it in a folder per bank. Some accounts arrive through Proton Bridge, others through Mail.app because they go to a different address. DeFi has no statement at all, so that importer reads the contract events straight from the chain.&lt;&#x2F;p&gt;
&lt;p&gt;A few statements never reach my inbox. They only exist behind a bank login with 2FA. For those I download the file and email it to myself with a fixed subject. From there the pipeline treats it like any other bill. This is the main manual step left, a few minutes a month, and I stopped fighting it.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-importers&quot;&gt;The importers &lt;a class=&quot;zola-anchor&quot; href=&quot;#the-importers&quot; aria-label=&quot;Link to this section&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;An importer reads one kind of file and returns Beancount entries. I wrote fourteen of them on top of &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;beancount&#x2F;beangulp&quot;&gt;beangulp&lt;&#x2F;a&gt;: PDF credit card bills, bank statements as PDF, spreadsheet and CSV, brokerage notes, my company&#x27;s invoices and DeFi events.&lt;&#x2F;p&gt;
&lt;p&gt;Each importer has a test with a real statement next to it and the expected output in a &lt;code&gt;.beancount&lt;&#x2F;code&gt; file. When a bank changes its PDF layout, the test fails before the ledger gets wrong numbers.&lt;&#x2F;p&gt;
&lt;p&gt;None of these rules were new to me. I have worked with credit cards and finance for years, and I used to reconcile these statements by hand. The work was turning what I already knew into code:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Credit card dates are not transaction dates.&lt;&#x2F;strong&gt; A bill closes on some day of the month, and the balance on the bill only makes sense after that day. So the importer reads the close date from the PDF, moves older purchases to the day after it, and writes the balance assertion two days after close. Beancount checks balances at the start of the day, so that order matters.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Installments repeat across bills.&lt;&#x2F;strong&gt; &amp;quot;Amazon (5&#x2F;10)&amp;quot; shows up ten months in a row. The importer tags them &lt;code&gt;#recurring&lt;&#x2F;code&gt; so I can see how much of next month is already spent.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;One bank sends image PDFs.&lt;&#x2F;strong&gt; There is no text to extract, so that importer renders each page at 300 DPI and runs Tesseract in Portuguese. Tesseract reads the &amp;quot;C&amp;quot; for credit as &amp;quot;€&amp;quot; often enough that the code replaces it.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;The importer owns the balance assertion.&lt;&#x2F;strong&gt; Every statement ends with the balance the bank says I have. The importer writes that as a &lt;code&gt;balance&lt;&#x2F;code&gt; directive, so the moment I import, Beancount tells me if something is missing. I never let it add &lt;code&gt;pad&lt;&#x2F;code&gt; directives on its own. A pad hides a difference, and the difference is the thing I want to see.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;categorization&quot;&gt;Categorization &lt;a class=&quot;zola-anchor&quot; href=&quot;#categorization&quot; aria-label=&quot;Link to this section&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;A transaction from a bill says &amp;quot;PG *LOJA CENTRO 03&#x2F;06&amp;quot; and nothing else. Someone has to decide that it is household shopping.&lt;&#x2F;p&gt;
&lt;p&gt;The first pass is rules. A categorizer wraps every importer and matches the payee against a list of regular expressions, one list per account:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;python&quot; class=&quot;language-python z-code&quot;&gt;&lt;code class=&quot;language-python&quot; data-lang=&quot;python&quot;&gt;&lt;span class=&quot;z-source z-python&quot;&gt;&lt;span class=&quot;z-meta z-string z-python&quot;&gt;&lt;span class=&quot;z-string z-quoted z-double z-python&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-python&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-string z-python&quot;&gt;&lt;span class=&quot;z-string z-quoted z-double z-python&quot;&gt;Expenses:Shopping:Household&lt;span class=&quot;z-punctuation z-definition z-string z-end z-python&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-annotation z-variable z-python&quot;&gt;:&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-sequence z-list z-python&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-sequence z-begin z-python&quot;&gt;[&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-python&quot;&gt;&lt;span class=&quot;z-meta z-sequence z-list z-python&quot;&gt;    &lt;span class=&quot;z-meta z-string z-python&quot;&gt;&lt;span class=&quot;z-string z-quoted z-double z-python&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-python&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-string z-python&quot;&gt;&lt;span class=&quot;z-string z-quoted z-double z-python&quot;&gt;^(Mercado Livre|Leroy|Amazon|Havan|...)&lt;span class=&quot;z-punctuation z-definition z-string z-end z-python&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-sequence z-python&quot;&gt;,&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-python&quot;&gt;&lt;span class=&quot;z-meta z-sequence z-list z-python&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-sequence z-end z-python&quot;&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;,
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-python&quot;&gt;&lt;span class=&quot;z-meta z-string z-python&quot;&gt;&lt;span class=&quot;z-string z-quoted z-double z-python&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-python&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-string z-python&quot;&gt;&lt;span class=&quot;z-string z-quoted z-double z-python&quot;&gt;Income:Cashback&lt;span class=&quot;z-punctuation z-definition z-string z-end z-python&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-annotation z-variable z-python&quot;&gt;:&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-sequence z-list z-python&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-sequence z-begin z-python&quot;&gt;[&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-python&quot;&gt;&lt;span class=&quot;z-meta z-sequence z-list z-python&quot;&gt;    &lt;span class=&quot;z-meta z-string z-python&quot;&gt;&lt;span class=&quot;z-string z-quoted z-double z-python&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-python&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-string z-python&quot;&gt;&lt;span class=&quot;z-string z-quoted z-double z-python&quot;&gt;^(Cashback|Benefício do cartão)&lt;span class=&quot;z-punctuation z-definition z-string z-end z-python&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-sequence z-python&quot;&gt;,&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-python&quot;&gt;&lt;span class=&quot;z-meta z-sequence z-list z-python&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-sequence z-end z-python&quot;&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;,
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Today that is 65 accounts and around a thousand merchant patterns, collected one annoying transaction at a time. Specific rules go first, so a broad pattern does not catch a merchant that belongs somewhere else. A category that starts with &lt;code&gt;!&lt;&#x2F;code&gt; flags the transaction for review instead of trusting the match.&lt;&#x2F;p&gt;
&lt;p&gt;What the rules miss goes to &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;beancount&#x2F;smart_importer&quot;&gt;smart_importer&lt;&#x2F;a&gt;, which trains on my own history and guesses the payee and the account. It is right most of the time on merchants it has seen before and useless on new ones, which is why the rules come first.&lt;&#x2F;p&gt;
&lt;p&gt;When both get it wrong, I fix the category by hand. That is the other thing I still do. It happens a few times a month, mostly with a new merchant, and the fix often becomes a new pattern so the same merchant does not come back.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;duplicates&quot;&gt;Duplicates &lt;a class=&quot;zola-anchor&quot; href=&quot;#duplicates&quot; aria-label=&quot;Link to this section&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;This took longer than everything else.&lt;&#x2F;p&gt;
&lt;p&gt;The same money shows up in more than one place. A PIX between two of my banks appears in both statements. A bill gets downloaded twice. beangulp has a flag that compares new entries with the ledger and comments out duplicates, but by default it only checks that amounts are within 5% and dates within two days. Two purchases of the same price on the same day look like one.&lt;&#x2F;p&gt;
&lt;p&gt;So every card importer overrides the comparison and also requires the description to match. Transfers between my own accounts needed their own rule, restricted to the same pair of accounts, because a loan payment of R$ 3,500.00 should not match a PIX of R$ 3,500.00 just because the numbers are equal.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;running-it-every-day&quot;&gt;Running it every day &lt;a class=&quot;zola-anchor&quot; href=&quot;#running-it-every-day&quot; aria-label=&quot;Link to this section&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;All of this used to be a monthly evening with coffee. Now &lt;a href=&quot;https:&#x2F;&#x2F;gabrielkoerich.com&#x2F;posts&#x2F;orchestrating-agents-for-humans&#x2F;&quot;&gt;orch&lt;&#x2F;a&gt;, the tool I built to run coding agents on a schedule, does it every morning at 8.&lt;&#x2F;p&gt;
&lt;p&gt;The job is a markdown prompt. It tells an agent to read the config, download new statements for every automated account, run the close pipeline, run &lt;code&gt;bean-check&lt;&#x2F;code&gt;, archive the files, and send me a short Telegram message with what came in and what is behind. The agent follows a skill file that holds everything I learned about the importers, so it does not have to rediscover the date rules or the dedup bugs each time.&lt;&#x2F;p&gt;
&lt;p&gt;The agent is not doing the accounting. The importers and Beancount do that, and they are deterministic. The agent is there for the parts that break: Proton Bridge is not running, a bank changed the file name, a statement came in a format the importer has not seen. It reports those instead of guessing.&lt;&#x2F;p&gt;
&lt;p&gt;On a normal day I read the Telegram message and do nothing else.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;prices-and-everything-else&quot;&gt;Prices and everything else &lt;a class=&quot;zola-anchor&quot; href=&quot;#prices-and-everything-else&quot; aria-label=&quot;Link to this section&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;Once the ledger is complete, it can answer more than &amp;quot;how much did I spend&amp;quot;. Price sources pull SELIC, CDI and IPCA from the Central Bank, stock prices from brapi, US macro data from FRED, and a construction cost index from Sinduscon. The ledger has more than 54,000 price lines.&lt;&#x2F;p&gt;
&lt;p&gt;I also track sleep, HRV and steps as commodities in the same file. It sounds strange, but Beancount already knows how to store a number per day and plot it, so I did not need another tool.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;what-it-costs&quot;&gt;What it costs &lt;a class=&quot;zola-anchor&quot; href=&quot;#what-it-costs&quot; aria-label=&quot;Link to this section&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;&lt;strong&gt;Time.&lt;&#x2F;strong&gt; Each importer is a weekend at first and a few hours every time the bank changes the PDF. Fourteen of them is a lot of weekends.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Mail access on macOS.&lt;&#x2F;strong&gt; An agent launched by a background service does not inherit the terminal&#x27;s permissions, so the Mail.app accounts fail with an AppleScript authorization error until I grant access to the right process. The iCloud mailbox search also times out at 120 seconds on a big inbox. Some mornings the report just says those accounts were skipped.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;The last 5%.&lt;&#x2F;strong&gt; A few statements still need a manual download, and the categorizer still misses new merchants. I do not think the rest is worth automating.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;It is only mine.&lt;&#x2F;strong&gt; The importers are written for my banks and my categories, and right now nobody else could run them without reading the code.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;why-i-keep-doing-it&quot;&gt;Why I keep doing it &lt;a class=&quot;zola-anchor&quot; href=&quot;#why-i-keep-doing-it&quot; aria-label=&quot;Link to this section&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;I can ask the ledger anything. How much a loan really costs after fees and insurance. What I spend when I count the outflows no app counts as spending, like loan principal. Whether a month was unusual or just felt like it. The answers come from a file I own, checked against the bank, and nothing about it depends on a company staying in business.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;what-comes-next&quot;&gt;What comes next &lt;a class=&quot;zola-anchor&quot; href=&quot;#what-comes-next&quot; aria-label=&quot;Link to this section&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;&lt;strong&gt;Get it off my Mac.&lt;&#x2F;strong&gt; Everything runs on my laptop today. If the lid is closed at 8, the job does not run. I want the daily job on a small server at home. The Mac stays in the picture for two things that only exist there: Mail.app and the key that holds my secrets.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Teach passbox to fetch mail.&lt;&#x2F;strong&gt; I wrote &lt;a href=&quot;https:&#x2F;&#x2F;gabrielkoerich.com&#x2F;posts&#x2F;a-password-manager-for-agents&#x2F;&quot;&gt;passbox&lt;&#x2F;a&gt; so agents can use a secret without seeing it, and it already works across machines: a Linux box asks my Mac over Tailscale, and a fingerprint releases the secret. The price API keys already come from passbox, and the rest of the pipeline&#x27;s secrets, like the passwords that open the bank PDFs, are moving there now. The server will also need my statements, so passbox needs a Mail tool. The job asks the Mac to search a mailbox, and the Mac sends back the PDF attachments and nothing else from the inbox. The search has to be asynchronous, because the iCloud search already takes more than 120 seconds. The job starts it, gets a handle, and checks back later. This tool cannot ask for a fingerprint, because nobody is awake to give one when the job runs, so it is limited by what it returns instead.&lt;&#x2F;p&gt;
&lt;p&gt;Two other passbox changes come with the move. Today the broker cannot tell which machine is asking, so it cannot have rules like &amp;quot;the server can read the price API keys and nothing else&amp;quot;. Fixing that means running Tailscale inside the broker so it sees the real caller. And the server should get one approval in the morning that covers the secrets the day&#x27;s jobs need, instead of a prompt for each read.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Dockerize the whole pipeline, as two services.&lt;&#x2F;strong&gt; Fava already runs in a container. The downloaders, importers and price sources still run in my local Python environment. I want to split them into two images. The first one does the work: it downloads statements, imports them, fetches prices, commits the ledger and pushes. It is the only one that talks to passbox. The second one only shows the ledger: it runs Fava at home and does &lt;code&gt;git pull&lt;&#x2F;code&gt; on a loop. Since git is the only link between the two, they can run on different machines, and moving either one is starting a container somewhere else.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Open source some importers.&lt;&#x2F;strong&gt; The bank importers have nothing personal in them, because the categories live in a separate file. Few people use Beancount in Brazil, and each of them probably wrote their own parser for the same banks. The work before publishing is the test fixtures, which are my real statements today and need to become fake ones.&lt;&#x2F;p&gt;
&lt;p&gt;If you keep your finances in plain text, especially with Brazilian banks, I would like to know how you handle the statements. And if you would use these importers, tell me, because that decides which ones I publish first.&lt;&#x2F;p&gt;
&lt;hr &#x2F;&gt;
&lt;p&gt;&lt;strong&gt;Corrections, 2026-10-12.&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;The 5% tolerance in beangulp&#x27;s duplicate check applies to amounts. Dates match within two days.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
</content>
    </entry>
    <entry xml:lang="en">
        <title>Is Ed25519 quantum resistant? Checking Solana&#x27;s seed claim</title>
        <published>2026-10-08T00:00:00+00:00</published>
        <updated>2026-10-08T00:00:00+00:00</updated>
        <author>
            <name>Gabriel Koerich</name>
        </author>
        <link rel="alternate" type="text/html" href="https://gabrielkoerich.com/posts/is-ed25519-quantum-resistant/"/>
        <id>https://gabrielkoerich.com/posts/is-ed25519-quantum-resistant/</id>
        <summary>Breaking Ed25519 exposes the signing key, not the seed. Solana&#x27;s seed claim holds, but the exposed key still signs, so Solana still needs a network upgrade.</summary>
        <content type="html" xml:base="https://gabrielkoerich.com/posts/is-ed25519-quantum-resistant/">&lt;p&gt;I was scrolling Twitter today and found this post:&lt;&#x2F;p&gt;

&lt;blockquote class=&quot;twitter-tweet&quot; data-theme=&quot;dark&quot; data-dnt=&quot;true&quot; data-align=&quot;center&quot;&gt;
    &lt;p&gt;Unlike many networks, Solana users don&#x27;t need to go into &amp;quot;bunker mode&amp;quot; ahead of an accelerated quantum timeline or classical attack provided by AI.&lt;&#x2F;p&gt;
    Jacob Creech (@jacobvcreech) &lt;a href=&quot;https:&amp;#x2F;&amp;#x2F;x.com&amp;#x2F;jacobvcreech&amp;#x2F;status&amp;#x2F;2108042644819427521&quot;&gt;October 8, 2026&lt;&#x2F;a&gt;
&lt;&#x2F;blockquote&gt;
&lt;script async src=&quot;https:&#x2F;&#x2F;platform.twitter.com&#x2F;widgets.js&quot; charset=&quot;utf-8&quot;&gt;&lt;&#x2F;script&gt;
&lt;p&gt;Is Solana really quantum resistant? I did not think that was possible yet, so I started digging.&lt;&#x2F;p&gt;
&lt;p&gt;Creech was answering Justin Drake from the Ethereum Foundation. The day before, Drake had asked the industry to start planning for &amp;quot;bunker mode&amp;quot;: move funds to addresses whose public keys are still hidden behind a hash. His worry is that elliptic curve signatures break sooner than planned, by a quantum computer or by an AI that finds a better classical attack.&lt;&#x2F;p&gt;

&lt;blockquote class=&quot;twitter-tweet&quot; data-theme=&quot;dark&quot; data-dnt=&quot;true&quot; data-align=&quot;center&quot;&gt;
    &lt;p&gt;Today I call upon the blockchain industry to calmly begin planning for &amp;quot;bunker mode&amp;quot;. My personal recommendation is to set in motion a controlled mass migration of assets to fresh addresses, i.e. addresses whose pubkeys remain hidden behind a hash.&lt;&#x2F;p&gt;
    Justin Drake (@drakefjustin) &lt;a href=&quot;https:&amp;#x2F;&amp;#x2F;x.com&amp;#x2F;drakefjustin&amp;#x2F;status&amp;#x2F;2107837081313505768&quot;&gt;October 7, 2026&lt;&#x2F;a&gt;
&lt;&#x2F;blockquote&gt;
&lt;script async src=&quot;https:&#x2F;&#x2F;platform.twitter.com&#x2F;widgets.js&quot; charset=&quot;utf-8&quot;&gt;&lt;&#x2F;script&gt;
&lt;p&gt;Creech&#x27;s argument is that Solana does not need a bunker. Ed25519 derives every signing key by hashing a secret seed. Breaking the curve exposes the key, not the seed, so owners could later prove they know the seed and move to a safe scheme.&lt;&#x2F;p&gt;
&lt;p&gt;Both posts are about the same question: what exactly does an attacker get when the curve falls, and what is left for the owner? I read the papers, ran the math, and built Anza&#x27;s proof of concept to find out. The examples run in Docker: &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;gabrielkoerich&#x2F;gabrielkoerich&#x2F;tree&#x2F;main&#x2F;examples&#x2F;is-ed25519-quantum-resistant&quot;&gt;examples&#x2F;is-ed25519-quantum-resistant&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;what-breaks-and-how-soon&quot;&gt;What breaks, and how soon &lt;a class=&quot;zola-anchor&quot; href=&quot;#what-breaks-and-how-soon&quot; aria-label=&quot;Link to this section&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;Every major chain signs with an elliptic curve: secp256k1 for Bitcoin and Ethereum, Ed25519 for Solana. Their security rests on the discrete logarithm problem. Given a public point &lt;code&gt;A = s·B&lt;&#x2F;code&gt;, finding &lt;code&gt;s&lt;&#x2F;code&gt; is hard. &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;arxiv.org&#x2F;abs&#x2F;quant-ph&#x2F;9508027&quot;&gt;Shor&#x27;s algorithm&lt;&#x2F;a&gt; solves it in polynomial time on a large enough quantum computer.&lt;&#x2F;p&gt;
&lt;p&gt;&amp;quot;Large enough&amp;quot; keeps shrinking:&lt;&#x2F;p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Year&lt;&#x2F;th&gt;&lt;th&gt;Paper&lt;&#x2F;th&gt;&lt;th&gt;Cost to break a 256-bit curve&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;2017&lt;&#x2F;td&gt;&lt;td&gt;&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;arxiv.org&#x2F;abs&#x2F;1706.06752&quot;&gt;Roetteler, Naehrig, Svore, Lauter&lt;&#x2F;a&gt;&lt;&#x2F;td&gt;&lt;td&gt;About 2,330 logical qubits&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;2023&lt;&#x2F;td&gt;&lt;td&gt;&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;arxiv.org&#x2F;abs&#x2F;2306.08585&quot;&gt;Litinski&lt;&#x2F;a&gt;&lt;&#x2F;td&gt;&lt;td&gt;50 million Toffoli gates, one key every 10 minutes on 6,000 modules&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;2026&lt;&#x2F;td&gt;&lt;td&gt;&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;arxiv.org&#x2F;abs&#x2F;2603.28846&quot;&gt;Babbush, Gidney, Boneh, Drake and others&lt;&#x2F;a&gt;&lt;&#x2F;td&gt;&lt;td&gt;Fewer than 1,200 logical qubits, &amp;quot;in minutes using fewer than half a million physical qubits&amp;quot;&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;2026&lt;&#x2F;td&gt;&lt;td&gt;&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;eprint.iacr.org&#x2F;2026&#x2F;280&quot;&gt;Chevignard, Fouque, Schrottenloher&lt;&#x2F;a&gt;&lt;&#x2F;td&gt;&lt;td&gt;1,193 logical qubits&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;p&gt;The Babbush paper does not publish its circuit. &amp;quot;In the interest of responsible disclosure&amp;quot;, it proves the result with a zero knowledge proof instead. Classical attacks are far behind. The largest public breaks are around 113 bits for a &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;eprint.iacr.org&#x2F;2016&#x2F;382&quot;&gt;binary curve on FPGAs&lt;&#x2F;a&gt; in 2016 and 114 bits for a &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;doi.org&#x2F;10.1007&#x2F;978-3-319-78556-1_13&quot;&gt;prime curve on 2,000 CPU cores&lt;&#x2F;a&gt; in 2017. A 256-bit curve needs about 2^128 steps with Pollard&#x27;s rho. Drake&#x27;s AI worry is that someone finds a shortcut nobody has found in forty years of elliptic curve cryptography. No public result shows one.&lt;&#x2F;p&gt;
&lt;p&gt;Hashes are different. &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;arxiv.org&#x2F;abs&#x2F;quant-ph&#x2F;9605043&quot;&gt;Grover&#x27;s algorithm&lt;&#x2F;a&gt; speeds up a brute force search only by a square root, and &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;arxiv.org&#x2F;abs&#x2F;quant-ph&#x2F;9701001&quot;&gt;that bound is tight&lt;&#x2F;a&gt;. Searching 2^256 possibilities still takes about 2^128 quantum steps. Both tweets build on that gap between curves and hashes.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;where-each-chain-keeps-the-key&quot;&gt;Where each chain keeps the key &lt;a class=&quot;zola-anchor&quot; href=&quot;#where-each-chain-keeps-the-key&quot; aria-label=&quot;Link to this section&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;What the attacker needs is the public key. Here the three chains differ:&lt;&#x2F;p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;&lt;&#x2F;th&gt;&lt;th&gt;Bitcoin&lt;&#x2F;th&gt;&lt;th&gt;Ethereum&lt;&#x2F;th&gt;&lt;th&gt;Solana&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Address&lt;&#x2F;td&gt;&lt;td&gt;Hash of the key (P2PKH, P2WPKH), or the key itself (P2PK, Taproot)&lt;&#x2F;td&gt;&lt;td&gt;Last 20 bytes of the Keccak hash of the key&lt;&#x2F;td&gt;&lt;td&gt;The Ed25519 public key itself&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Key public after&lt;&#x2F;td&gt;&lt;td&gt;The first spend, or at creation for P2PK and Taproot&lt;&#x2F;td&gt;&lt;td&gt;The first transaction&lt;&#x2F;td&gt;&lt;td&gt;Creation&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Can a user hide today?&lt;&#x2F;td&gt;&lt;td&gt;Yes, with a fresh hashed address&lt;&#x2F;td&gt;&lt;td&gt;Yes, with an address that never sent a transaction&lt;&#x2F;td&gt;&lt;td&gt;Only by moving funds into a program account&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;p&gt;Hiding protects coins at rest. Every spend still reveals the key until the transaction confirms, and the Babbush paper warns that the first fast quantum computers &amp;quot;would enable on-spend attacks on public mempool transactions of some cryptocurrencies&amp;quot;.&lt;&#x2F;p&gt;
&lt;p&gt;Bitcoin&#x27;s exposure is measurable. Project Eleven&#x27;s &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;bitcoin-risq-list.projecteleven.com&quot;&gt;snapshot from September 14&lt;&#x2F;a&gt; counts 8,177,337 BTC in addresses whose public keys are already on chain. Most of it comes from address reuse, and 1.7 million sits in old P2PK outputs, Satoshi&#x27;s coins included.&lt;&#x2F;p&gt;
&lt;p&gt;On Solana there is nothing to count, because &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;solana.com&#x2F;docs&#x2F;core&#x2F;accounts&quot;&gt;an address is the public key&lt;&#x2F;a&gt;. Every funded account has been exposed since the moment it existed. The only accounts without an Ed25519 key are program accounts, like the Winternitz vault further down, and those are only as safe as the key that can upgrade the program.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-seed&quot;&gt;The seed &lt;a class=&quot;zola-anchor&quot; href=&quot;#the-seed&quot; aria-label=&quot;Link to this section&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;Creech&#x27;s claim is about how &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;eprint.iacr.org&#x2F;2011&#x2F;368&quot;&gt;Ed25519&lt;&#x2F;a&gt; makes its key. &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;www.rfc-editor.org&#x2F;rfc&#x2F;rfc8032&quot;&gt;RFC 8032&lt;&#x2F;a&gt; starts from a 32-byte seed, hashes it with SHA-512, and clamps the first half into the secret scalar &lt;code&gt;s&lt;&#x2F;code&gt;. The public key is &lt;code&gt;s·B&lt;&#x2F;code&gt;, and the second half of the hash becomes a prefix used to derive nonces:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;python&quot; class=&quot;language-python z-code&quot;&gt;&lt;code class=&quot;language-python&quot; data-lang=&quot;python&quot;&gt;&lt;span class=&quot;z-source z-python&quot;&gt;&lt;span class=&quot;z-meta z-statement z-import z-python&quot;&gt;&lt;span class=&quot;z-keyword z-control z-import z-python&quot;&gt;import&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-qualified-name z-python&quot;&gt;&lt;span class=&quot;z-meta z-generic-name z-python&quot;&gt;hashlib&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-python&quot;&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-python&quot;&gt;&lt;span class=&quot;z-meta z-qualified-name z-python&quot;&gt;&lt;span class=&quot;z-meta z-generic-name z-python&quot;&gt;digest&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; &lt;span class=&quot;z-keyword z-operator z-assignment z-python&quot;&gt;=&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-function-call z-python&quot;&gt;&lt;span class=&quot;z-meta z-qualified-name z-python&quot;&gt;&lt;span class=&quot;z-variable z-function z-python&quot;&gt;&lt;span class=&quot;z-support z-type z-python&quot;&gt;bytearray&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-python&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-arguments z-begin z-python&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-python&quot;&gt;&lt;span class=&quot;z-meta z-qualified-name z-python&quot;&gt;&lt;span class=&quot;z-meta z-generic-name z-python&quot;&gt;hashlib&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-qualified-name z-python&quot;&gt;&lt;span class=&quot;z-punctuation z-accessor z-dot z-python&quot;&gt;.&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-qualified-name z-python&quot;&gt;&lt;span class=&quot;z-variable z-function z-python&quot;&gt;&lt;span class=&quot;z-meta z-generic-name z-python&quot;&gt;sha512&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-python&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-arguments z-begin z-python&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-qualified-name z-python&quot;&gt;&lt;span class=&quot;z-meta z-generic-name z-python&quot;&gt;seed&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-section z-arguments z-end z-python&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-python&quot;&gt;&lt;span class=&quot;z-meta z-qualified-name z-python&quot;&gt;&lt;span class=&quot;z-punctuation z-accessor z-dot z-python&quot;&gt;.&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-qualified-name z-python&quot;&gt;&lt;span class=&quot;z-variable z-function z-python&quot;&gt;&lt;span class=&quot;z-meta z-generic-name z-python&quot;&gt;digest&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-python&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-arguments z-begin z-python&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-section z-arguments z-end z-python&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-section z-arguments z-end z-python&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-python&quot;&gt;&lt;span class=&quot;z-meta z-item-access z-python&quot;&gt;&lt;span class=&quot;z-meta z-qualified-name z-python&quot;&gt;&lt;span class=&quot;z-meta z-generic-name z-python&quot;&gt;digest&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-item-access z-python&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-brackets z-begin z-python&quot;&gt;[&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-item-access z-arguments z-python&quot;&gt;&lt;span class=&quot;z-constant z-numeric z-integer z-decimal z-python&quot;&gt;0&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-item-access z-python&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-brackets z-end z-python&quot;&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; &lt;span class=&quot;z-keyword z-operator z-assignment z-augmented z-python&quot;&gt;&amp;amp;=&lt;&#x2F;span&gt; &lt;span class=&quot;z-constant z-numeric z-integer z-decimal z-python&quot;&gt;248&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-python&quot;&gt;&lt;span class=&quot;z-meta z-item-access z-python&quot;&gt;&lt;span class=&quot;z-meta z-qualified-name z-python&quot;&gt;&lt;span class=&quot;z-meta z-generic-name z-python&quot;&gt;digest&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-item-access z-python&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-brackets z-begin z-python&quot;&gt;[&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-item-access z-arguments z-python&quot;&gt;&lt;span class=&quot;z-constant z-numeric z-integer z-decimal z-python&quot;&gt;31&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-item-access z-python&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-brackets z-end z-python&quot;&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; &lt;span class=&quot;z-keyword z-operator z-assignment z-augmented z-python&quot;&gt;&amp;amp;=&lt;&#x2F;span&gt; &lt;span class=&quot;z-constant z-numeric z-integer z-decimal z-python&quot;&gt;127&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-python&quot;&gt;&lt;span class=&quot;z-meta z-item-access z-python&quot;&gt;&lt;span class=&quot;z-meta z-qualified-name z-python&quot;&gt;&lt;span class=&quot;z-meta z-generic-name z-python&quot;&gt;digest&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-item-access z-python&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-brackets z-begin z-python&quot;&gt;[&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-item-access z-arguments z-python&quot;&gt;&lt;span class=&quot;z-constant z-numeric z-integer z-decimal z-python&quot;&gt;31&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-item-access z-python&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-brackets z-end z-python&quot;&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; &lt;span class=&quot;z-keyword z-operator z-assignment z-augmented z-python&quot;&gt;|=&lt;&#x2F;span&gt; &lt;span class=&quot;z-constant z-numeric z-integer z-decimal z-python&quot;&gt;64&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-python&quot;&gt;&lt;span class=&quot;z-meta z-qualified-name z-python&quot;&gt;&lt;span class=&quot;z-meta z-generic-name z-python&quot;&gt;s&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; &lt;span class=&quot;z-keyword z-operator z-assignment z-python&quot;&gt;=&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-function-call z-python&quot;&gt;&lt;span class=&quot;z-meta z-qualified-name z-python&quot;&gt;&lt;span class=&quot;z-support z-type z-python&quot;&gt;int&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-qualified-name z-python&quot;&gt;&lt;span class=&quot;z-punctuation z-accessor z-dot z-python&quot;&gt;.&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-qualified-name z-python&quot;&gt;&lt;span class=&quot;z-variable z-function z-python&quot;&gt;&lt;span class=&quot;z-meta z-generic-name z-python&quot;&gt;from_bytes&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-python&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-arguments z-begin z-python&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-item-access z-python&quot;&gt;&lt;span class=&quot;z-meta z-qualified-name z-python&quot;&gt;&lt;span class=&quot;z-meta z-generic-name z-python&quot;&gt;digest&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-item-access z-python&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-brackets z-begin z-python&quot;&gt;[&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-item-access z-arguments z-python&quot;&gt;&lt;span class=&quot;z-punctuation z-separator z-slice z-python&quot;&gt;:&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric z-integer z-decimal z-python&quot;&gt;32&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-item-access z-python&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-brackets z-end z-python&quot;&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-arguments z-python&quot;&gt;,&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-string z-python&quot;&gt;&lt;span class=&quot;z-string z-quoted z-double z-python&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-python&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-string z-python&quot;&gt;&lt;span class=&quot;z-string z-quoted z-double z-python&quot;&gt;little&lt;span class=&quot;z-punctuation z-definition z-string z-end z-python&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-section z-arguments z-end z-python&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The seed is what Solana keypair files store and what wallets derive from a mnemonic through SLIP-0010. Breaking the curve gives the attacker &lt;code&gt;s&lt;&#x2F;code&gt;. Going back to the seed means searching the 2^256 possible seeds for one that hashes to &lt;code&gt;s&lt;&#x2F;code&gt;, which Grover cuts only to about 2^128 quantum steps. For a wallet, the weakest link is the mnemonic: a 12-word phrase has 128 bits of entropy, so about 2^64 quantum steps, each one a full PBKDF2 key stretch. Both are far out of reach, so the claim holds: after the break, the owner still knows something the attacker cannot learn.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;what-the-scalar-can-still-do&quot;&gt;What the scalar can still do &lt;a class=&quot;zola-anchor&quot; href=&quot;#what-the-scalar-can-still-do&quot; aria-label=&quot;Link to this section&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;Knowing &lt;code&gt;s&lt;&#x2F;code&gt; is enough to sign. Ed25519 verification checks &lt;code&gt;S·B = R + k·A&lt;&#x2F;code&gt; and never checks how the nonce &lt;code&gt;r&lt;&#x2F;code&gt; was chosen. The seed&#x27;s prefix makes nonces deterministic, but any random &lt;code&gt;r&lt;&#x2F;code&gt; gives a valid signature.&lt;&#x2F;p&gt;
&lt;p&gt;I tested it. The script derives a key from a random seed, keeps only &lt;code&gt;s&lt;&#x2F;code&gt;, signs with a random nonce, and verifies with Python&#x27;s &lt;code&gt;cryptography&lt;&#x2F;code&gt; library:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;text&quot; class=&quot;language-text z-code&quot;&gt;&lt;code class=&quot;language-text&quot; data-lang=&quot;text&quot;&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;address (base58): 2yo4kayYa9i3tgmx2opVWTgq8dfdYUadsSxNATa28whS
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;s * B equals the address: True
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;forged signature verifies: True
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;So the seed only protects an account once the network stops accepting Ed25519 signatures. Anza&#x27;s &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;www.anza.xyz&#x2F;blog&#x2F;securing-solana-against-a-powerful-quantum-adversary&quot;&gt;post on the migration&lt;&#x2F;a&gt;, published in April after the Babbush paper, says the same: an owner can migrate after the break &amp;quot;provided legacy Ed25519 signing has been disabled&amp;quot;. That switch is Solana&#x27;s version of bunker mode, and no user can do it alone. It is a network upgrade, and until it lands every account on chain can be drained with a valid signature.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-proof-that-exists-today&quot;&gt;The proof that exists today &lt;a class=&quot;zola-anchor&quot; href=&quot;#the-proof-that-exists-today&quot; aria-label=&quot;Link to this section&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;The proof of concept Creech linked is &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;anza-xyz&#x2F;cryptography&#x2F;tree&#x2F;master&#x2F;experimental&#x2F;ed25519-pokos&quot;&gt;ed25519-pokos&lt;&#x2F;a&gt; in Anza&#x27;s cryptography repo, based on the paper &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;eprint.iacr.org&#x2F;2025&#x2F;1368&quot;&gt;Post-Quantum Readiness in EdDSA Chains&lt;&#x2F;a&gt; by Baldimtsi, Chalkias, Roy and Sedaghat. A Plonky3 STARK proves that the prover knows a seed behind two public hashes. STARKs use only hash functions, with no curve for Shor to break, so they are considered post-quantum, although Grover shrinks their security margin.&lt;&#x2F;p&gt;
&lt;p&gt;I built it at commit &lt;code&gt;54963bf&lt;&#x2F;code&gt; and ran the bundled example three times in Docker on an Apple Silicon Mac, with 14 vCPUs and Rust 1.99:&lt;&#x2F;p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Run&lt;&#x2F;th&gt;&lt;th&gt;Prove&lt;&#x2F;th&gt;&lt;th&gt;Verify&lt;&#x2F;th&gt;&lt;th&gt;Proof&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;1&lt;&#x2F;td&gt;&lt;td&gt;383 ms&lt;&#x2F;td&gt;&lt;td&gt;40 ms&lt;&#x2F;td&gt;&lt;td&gt;364,125 bytes&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;2&lt;&#x2F;td&gt;&lt;td&gt;358 ms&lt;&#x2F;td&gt;&lt;td&gt;45 ms&lt;&#x2F;td&gt;&lt;td&gt;366,525 bytes&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;3&lt;&#x2F;td&gt;&lt;td&gt;432 ms&lt;&#x2F;td&gt;&lt;td&gt;71 ms&lt;&#x2F;td&gt;&lt;td&gt;364,797 bytes&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;p&gt;These numbers describe one machine proving and checking one proof, off chain. On a chain the cost is different. Every validator would verify every migration proof, and the proof has to reach the chain somehow: 365 KB is about 90 times the 4,096-byte limit of v1 transactions and about 300 times the 1,232 bytes of legacy ones. So a proof this size cannot ride in a normal transaction, and these runs do not answer how a migration would carry or verify proofs at network scale. The README also calls the proof envelope stable, but its size changed on every run here.&lt;&#x2F;p&gt;
&lt;p&gt;The README also states the limits of the design.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;It proves a new derivation.&lt;&#x2F;strong&gt; The chain inside the proof is &lt;code&gt;SHA512(&amp;quot;ed25519-pokos&#x2F;derive-sk&#x2F;v1&amp;quot; || seed)&lt;&#x2F;code&gt;, a domain separated step that today&#x27;s wallets do not use. Keys created with &lt;code&gt;solana-keygen&lt;&#x2F;code&gt; or a BIP39 wallet need a circuit that follows their actual derivation.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;It does not prove the link to the public key.&lt;&#x2F;strong&gt; &amp;quot;It does not prove &lt;code&gt;sk → pk&lt;&#x2F;code&gt; inside the circuit; that link is enforced externally by the Ed25519 signature over the proof transcript.&amp;quot; The README says what that means: &amp;quot;In a post-quantum setting, once an adversary can recover &lt;code&gt;sk&lt;&#x2F;code&gt; from &lt;code&gt;pk&lt;&#x2F;code&gt;, the signature no longer adds meaningful security.&amp;quot; The authors call it &amp;quot;a temporary engineering compromise&amp;quot;.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;It is not zero knowledge yet.&lt;&#x2F;strong&gt; The README describes a zero knowledge proof, but at this commit the prover uses Plonky3&#x27;s plain &lt;code&gt;TwoAdicFriPcs&lt;&#x2F;code&gt; commitment, not the hiding one, so the proof can leak information about the trace it was built from.&lt;&#x2F;p&gt;
&lt;p&gt;Proving the link to the public key inside the circuit while keeping &lt;code&gt;sk&lt;&#x2F;code&gt; secret &amp;quot;exceeds 1 billion gates&amp;quot;, because elliptic curve math is expensive to prove. After Ed25519 is disabled, &lt;code&gt;s&lt;&#x2F;code&gt; no longer needs to stay secret: the attacker has it anyway. The proof can then reveal &lt;code&gt;s&lt;&#x2F;code&gt;, prove only the SHA-512 steps from the seed, and let the verifier check &lt;code&gt;s·B&lt;&#x2F;code&gt; against the address directly, which is cheap. The work that remains is a circuit for each derivation wallets really use, and binding the new post-quantum key into the proof so a copied proof cannot be replayed.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;ethereum-and-bitcoin-have-the-same-trick&quot;&gt;Ethereum and Bitcoin have the same trick &lt;a class=&quot;zola-anchor&quot; href=&quot;#ethereum-and-bitcoin-have-the-same-trick&quot; aria-label=&quot;Link to this section&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;The seed argument is not special to Solana. Vitalik Buterin described it for Ethereum in 2024, in &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;ethresear.ch&#x2F;t&#x2F;how-to-hard-fork-to-save-most-users-funds-in-a-quantum-emergency&#x2F;18901&quot;&gt;How to hard-fork to save most users&#x27; funds in a quantum emergency&lt;&#x2F;a&gt;. His plan: revert the blocks where theft started, disable normal transactions, and let users prove with a STARK that they know the hash preimage their key came from, since &amp;quot;most users&#x27; private keys are themselves the result of a bunch of hash calculations&amp;quot;. Bitcoin&#x27;s draft &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;bitcoin&#x2F;bips&#x2F;blob&#x2F;master&#x2F;bip-0361.mediawiki&quot;&gt;BIP-361&lt;&#x2F;a&gt; builds the same rescue into a plan to sunset legacy signatures, and Olaoluwa Osuntokun &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;groups.google.com&#x2F;g&#x2F;bitcoindev&#x2F;c&#x2F;Q06piCEJhkI&quot;&gt;proposed&lt;&#x2F;a&gt; a STARK proof of BIP-32 seed knowledge for Taproot keys.&lt;&#x2F;p&gt;
&lt;p&gt;What differs is which keys have no seed to prove:&lt;&#x2F;p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Chain&lt;&#x2F;th&gt;&lt;th&gt;Keys the seed proof cannot rescue&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Ethereum&lt;&#x2F;td&gt;&lt;td&gt;Keys imported as raw bytes, with no preimage&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Bitcoin&lt;&#x2F;td&gt;&lt;td&gt;Early P2PK coins. BIP-361 says it is &amp;quot;not currently believed possible to construct a rescue protocol for P2PK UTXOs&amp;quot;&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Solana&lt;&#x2F;td&gt;&lt;td&gt;Custody that never had a seed&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;p&gt;On Solana every normal key is an RFC 8032 seed by construction, so even a raw keypair file has something to prove. The exception is threshold custody. Schemes like &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;www.rfc-editor.org&#x2F;rfc&#x2F;rfc9591.html&quot;&gt;FROST&lt;&#x2F;a&gt; split a random scalar, and Blockdaemon&#x27;s docs say that for their EdDSA keys &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;builder-vault-tsm.docs.blockdaemon.com&#x2F;docs&#x2F;key-derivation-eddsa&quot;&gt;&amp;quot;there is no seed&amp;quot;&lt;&#x2F;a&gt;. Those accounts cannot use the proof and have to move before the break.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;what-replaces-the-curve&quot;&gt;What replaces the curve &lt;a class=&quot;zola-anchor&quot; href=&quot;#what-replaces-the-curve&quot; aria-label=&quot;Link to this section&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;After the switch, accounts need a new signature scheme, and every candidate is bigger:&lt;&#x2F;p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Scheme&lt;&#x2F;th&gt;&lt;th&gt;Public key&lt;&#x2F;th&gt;&lt;th&gt;Signature&lt;&#x2F;th&gt;&lt;th&gt;Based on&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Ed25519&lt;&#x2F;td&gt;&lt;td&gt;32 bytes&lt;&#x2F;td&gt;&lt;td&gt;64 bytes&lt;&#x2F;td&gt;&lt;td&gt;Elliptic curve&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Falcon-512&lt;&#x2F;td&gt;&lt;td&gt;897 bytes&lt;&#x2F;td&gt;&lt;td&gt;666 bytes&lt;&#x2F;td&gt;&lt;td&gt;Lattices&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;ML-DSA-44 (&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;csrc.nist.gov&#x2F;pubs&#x2F;fips&#x2F;204&#x2F;final&quot;&gt;FIPS 204&lt;&#x2F;a&gt;)&lt;&#x2F;td&gt;&lt;td&gt;1,312 bytes&lt;&#x2F;td&gt;&lt;td&gt;2,420 bytes&lt;&#x2F;td&gt;&lt;td&gt;Lattices&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;SLH-DSA-128s (&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;csrc.nist.gov&#x2F;pubs&#x2F;fips&#x2F;205&#x2F;final&quot;&gt;FIPS 205&lt;&#x2F;a&gt;)&lt;&#x2F;td&gt;&lt;td&gt;32 bytes&lt;&#x2F;td&gt;&lt;td&gt;7,856 bytes&lt;&#x2F;td&gt;&lt;td&gt;Hashes only&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;p&gt;A Solana transaction was limited to 1,232 bytes until September, when SIMD-0296 and SIMD-0385 raised it to 4,096 bytes with v1 transactions. Anza&#x27;s post discusses Falcon, &amp;quot;the smallest of the three NIST schemes&amp;quot; and still &amp;quot;roughly 10 times larger&amp;quot; than Ed25519, and proposes indexing the new accounts by a hash of the public key.&lt;&#x2F;p&gt;
&lt;p&gt;Drake goes further and wants to avoid structured math altogether, lattices included, and use only hash-based schemes like &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;eprint.iacr.org&#x2F;2019&#x2F;1086&quot;&gt;SPHINCS+&lt;&#x2F;a&gt;, which became SLH-DSA. Ethereum&#x27;s draft roadmap at &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;strawmap.org&quot;&gt;strawmap.org&lt;&#x2F;a&gt; already plans hash-based signatures for consensus, following &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;eprint.iacr.org&#x2F;2025&#x2F;055&quot;&gt;Hash-Based Multi-Signatures for Post-Quantum Ethereum&lt;&#x2F;a&gt;. The cost is in the table: 7,856 bytes per signature.&lt;&#x2F;p&gt;
&lt;p&gt;Solana already has one hash-based option. Blueshift&#x27;s &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;blueshift-gg&#x2F;solana-winternitz-vault&quot;&gt;Winternitz vault&lt;&#x2F;a&gt; uses one-time signatures, the idea &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;link.springer.com&#x2F;chapter&#x2F;10.1007&#x2F;0-387-34805-0_21&quot;&gt;Merkle&lt;&#x2F;a&gt; credited to Winternitz in 1989. Each signature reveals part of the key, so every spend closes the vault and moves the rest to a new one. It holds only SOL, and you have to opt in.&lt;&#x2F;p&gt;
&lt;p&gt;User accounts are not the only thing signed with Ed25519. Validator identity, gossip messages, the QUIC TLS certificates between nodes and shreds all use it. Alpenglow&#x27;s votes move to BLS, which a quantum computer also breaks. Proof of History is a sequential SHA-256 chain, and Grover does not speed that up.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;what-it-costs&quot;&gt;What it costs &lt;a class=&quot;zola-anchor&quot; href=&quot;#what-it-costs&quot; aria-label=&quot;Link to this section&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;&lt;strong&gt;Dormant accounts.&lt;&#x2F;strong&gt; Disabling Ed25519 stops every account that has not migrated, and an owner who is away when it happens needs a seed proof to get back in.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;The timing is a bet.&lt;&#x2F;strong&gt; The upgrade has to land before a capable attacker exists. Waiting for proof of an attack means waiting until the first drained accounts.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;One circuit per wallet type.&lt;&#x2F;strong&gt; RFC 8032 keys, BIP39 with SLIP-0010, and any wallet with its own derivation each need their own proof, and each needs an audit.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Bigger transactions.&lt;&#x2F;strong&gt; A post-quantum signature is ten to a hundred times the size of an Ed25519 signature, on a chain where a transaction tops out at 4,096 bytes.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Validators too.&lt;&#x2F;strong&gt; Identity keys, gossip, TLS and votes all need new schemes in the same window.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;is-solana-quantum-safe&quot;&gt;Is Solana quantum safe? &lt;a class=&quot;zola-anchor&quot; href=&quot;#is-solana-quantum-safe&quot; aria-label=&quot;Link to this section&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;No, and neither are Bitcoin or Ethereum. Creech&#x27;s Ed25519 claim checks out: the key is a hash of a seed, breaking the curve exposes the key and not the seed, and seeds never appear on chain. But the exposed key still signs, so users cannot skip the bunker. On Solana the bunker is a network upgrade.&lt;&#x2F;p&gt;
&lt;p&gt;Some of the pieces already exist:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Falcon implementations from Anza and Firedancer, according to the &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;solana.com&#x2F;news&#x2F;quantum-readiness&quot;&gt;Solana Foundation&lt;&#x2F;a&gt;.&lt;&#x2F;li&gt;
&lt;li&gt;An on-chain Falcon verifier that costs under 170k compute units, the reason the &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;solana-foundation&#x2F;solana-improvement-documents&#x2F;pull&#x2F;461&quot;&gt;Falcon syscall SIMD&lt;&#x2F;a&gt; was paused.&lt;&#x2F;li&gt;
&lt;li&gt;The Winternitz vault, usable today.&lt;&#x2F;li&gt;
&lt;li&gt;Anza&#x27;s seed proof, as a proof of concept.&lt;&#x2F;li&gt;
&lt;li&gt;v1 transactions up to 4,096 bytes, live since September (SIMD-0296 and SIMD-0385).&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;The pieces that make it safe do not:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;A SIMD that disables Ed25519 and migrates accounts. None has been proposed.&lt;&#x2F;li&gt;
&lt;li&gt;Seed proofs for the derivations wallets use today, bound to the new post-quantum key.&lt;&#x2F;li&gt;
&lt;li&gt;Post-quantum signatures for validator identity, gossip, TLS, shreds and Alpenglow&#x27;s BLS votes.&lt;&#x2F;li&gt;
&lt;li&gt;A plan for MPC custody, which has no seed to prove.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;The other chains are at a similar stage. Ethereum&#x27;s &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;ethereum.org&#x2F;roadmap&#x2F;future-proofing&#x2F;quantum-resistance&#x2F;&quot;&gt;roadmap&lt;&#x2F;a&gt; aims for full post-quantum protection by about 2029, and its user side change, &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;eips.ethereum.org&#x2F;EIPS&#x2F;eip-8141&quot;&gt;EIP-8141&lt;&#x2F;a&gt;, is a draft. Bitcoin&#x27;s &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;bitcoin&#x2F;bips&#x2F;blob&#x2F;master&#x2F;bip-0360.mediawiki&quot;&gt;BIP-360&lt;&#x2F;a&gt; and BIP-361 are drafts, and BIP-361&#x27;s sunset runs about five years after activation.&lt;&#x2F;p&gt;
&lt;p&gt;Whether Solana is safe depends on the rest landing before a capable quantum computer does. Anza puts that risk as almost negligible within two to three years and possible within five, which gives Solana a few years to specify and ship the migration.&lt;&#x2F;p&gt;
&lt;p&gt;If you work on any of these migrations, I would like to know how you plan to handle dormant accounts. The scripts are at &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;gabrielkoerich&#x2F;gabrielkoerich&#x2F;tree&#x2F;main&#x2F;examples&#x2F;is-ed25519-quantum-resistant&quot;&gt;examples&#x2F;is-ed25519-quantum-resistant&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;papers&quot;&gt;Papers &lt;a class=&quot;zola-anchor&quot; href=&quot;#papers&quot; aria-label=&quot;Link to this section&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;ul&gt;
&lt;li&gt;Shor, &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;arxiv.org&#x2F;abs&#x2F;quant-ph&#x2F;9508027&quot;&gt;Polynomial-Time Algorithms for Prime Factorization and Discrete Logarithms on a Quantum Computer&lt;&#x2F;a&gt;, 1994&lt;&#x2F;li&gt;
&lt;li&gt;Grover, &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;arxiv.org&#x2F;abs&#x2F;quant-ph&#x2F;9605043&quot;&gt;A fast quantum mechanical algorithm for database search&lt;&#x2F;a&gt;, 1996&lt;&#x2F;li&gt;
&lt;li&gt;Bennett, Bernstein, Brassard, Vazirani, &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;arxiv.org&#x2F;abs&#x2F;quant-ph&#x2F;9701001&quot;&gt;Strengths and Weaknesses of Quantum Computing&lt;&#x2F;a&gt;, 1997&lt;&#x2F;li&gt;
&lt;li&gt;Bernstein, Duif, Lange, Schwabe, Yang, &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;eprint.iacr.org&#x2F;2011&#x2F;368&quot;&gt;High-speed high-security signatures&lt;&#x2F;a&gt;, 2011&lt;&#x2F;li&gt;
&lt;li&gt;Roetteler, Naehrig, Svore, Lauter, &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;arxiv.org&#x2F;abs&#x2F;1706.06752&quot;&gt;Quantum resource estimates for computing elliptic curve discrete logarithms&lt;&#x2F;a&gt;, 2017&lt;&#x2F;li&gt;
&lt;li&gt;Bernstein, Hülsing, Kölbl, Niederhagen, Rijneveld, Schwabe, &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;eprint.iacr.org&#x2F;2019&#x2F;1086&quot;&gt;The SPHINCS+ Signature Framework&lt;&#x2F;a&gt;, 2019&lt;&#x2F;li&gt;
&lt;li&gt;Litinski, &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;arxiv.org&#x2F;abs&#x2F;2306.08585&quot;&gt;How to compute a 256-bit elliptic curve private key with only 50 million Toffoli gates&lt;&#x2F;a&gt;, 2023&lt;&#x2F;li&gt;
&lt;li&gt;Drake, Khovratovich, Kudinov, Wagner, &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;eprint.iacr.org&#x2F;2025&#x2F;055&quot;&gt;Hash-Based Multi-Signatures for Post-Quantum Ethereum&lt;&#x2F;a&gt;, 2025&lt;&#x2F;li&gt;
&lt;li&gt;Baldimtsi, Chalkias, Roy, Sedaghat, &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;eprint.iacr.org&#x2F;2025&#x2F;1368&quot;&gt;Post-Quantum Readiness in EdDSA Chains&lt;&#x2F;a&gt;, 2025&lt;&#x2F;li&gt;
&lt;li&gt;Babbush, Zalcman, Gidney, Broughton, Khattar, Neven, Bergamaschi, Drake, Boneh, &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;arxiv.org&#x2F;abs&#x2F;2603.28846&quot;&gt;Securing Elliptic Curve Cryptocurrencies against Quantum Vulnerabilities&lt;&#x2F;a&gt;, 2026&lt;&#x2F;li&gt;
&lt;li&gt;Chevignard, Fouque, Schrottenloher, &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;eprint.iacr.org&#x2F;2026&#x2F;280&quot;&gt;Reducing the Number of Qubits in Quantum Discrete Logarithms on Elliptic Curves&lt;&#x2F;a&gt;, 2026&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;hr &#x2F;&gt;
&lt;p&gt;&lt;strong&gt;Corrections, 2026-10-12.&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;I wrote the 4,096-byte transaction size as a proposal. v1 transactions with that limit went live on mainnet on September 15, before this post, so I corrected the four places that said otherwise.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
</content>
    </entry>
    <entry xml:lang="en">
        <title>Waiting and working in Rust</title>
        <published>2026-10-08T00:00:00+00:00</published>
        <updated>2026-10-08T00:00:00+00:00</updated>
        <author>
            <name>Gabriel Koerich</name>
        </author>
        <link rel="alternate" type="text/html" href="https://gabrielkoerich.com/posts/waiting-and-working-in-rust/"/>
        <id>https://gabrielkoerich.com/posts/waiting-and-working-in-rust/</id>
        <summary>Rust has at least five ways to do more than one thing. Should you use async? It is three questions: is the program waiting, working or reacting?</summary>
        <content type="html" xml:base="https://gabrielkoerich.com/posts/waiting-and-working-in-rust/">&lt;p&gt;Rust gives you at least five ways to do more than one thing: OS threads, an async runtime, a blocking pool inside that runtime, a data parallel pool, and a thread that owns a core and never sleeps. People ask &amp;quot;should I use async?&amp;quot; as if it were one question. It is three.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Is the program waiting?&lt;&#x2F;strong&gt; Sockets, timers, RPC calls, a database. The CPU is idle and the work is in somebody else&#x27;s hands. This is concurrency: many things in progress, few of them running.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Is the program working?&lt;&#x2F;strong&gt; Hashing, parsing, simulating, signing. The CPU is the bottleneck and the answer is more cores. This is parallelism: many things running at the same instant.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Does the program have to react within a bound?&lt;&#x2F;strong&gt; A market data tick, a packet, an interrupt. Here the number that matters is how late the worst reaction is.&lt;&#x2F;p&gt;
&lt;p&gt;Each tool in Rust answers one of these well and the others badly. I measured the differences on my machine, an M4 Max with 14 cores, so the numbers below are real but they are one machine. Run them on yours.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;how-async-works-in-rust&quot;&gt;How async works in Rust &lt;a class=&quot;zola-anchor&quot; href=&quot;#how-async-works-in-rust&quot; aria-label=&quot;Link to this section&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;A Rust &lt;code&gt;async fn&lt;&#x2F;code&gt; compiles to a state machine. Each &lt;code&gt;.await&lt;&#x2F;code&gt; is a point where the function can stop and save its local variables into that state machine. The future does nothing on its own. An executor calls &lt;code&gt;poll&lt;&#x2F;code&gt; on it. The future either finishes, or registers a waker and returns &lt;code&gt;Pending&lt;&#x2F;code&gt;. When the socket has data or the timer fires, the waker puts the task back in the executor&#x27;s queue, and the executor polls it again.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;It is cooperative.&lt;&#x2F;strong&gt; The executor cannot interrupt a task. A task gives up the thread only at an &lt;code&gt;.await&lt;&#x2F;code&gt; that is not ready. Code between two awaits runs to completion, however long it takes.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;It is cheap.&lt;&#x2F;strong&gt; A task is a heap allocation the size of its state machine, not a thread with a stack. A thread in Rust gets a &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;doc.rust-lang.org&#x2F;std&#x2F;thread&#x2F;index.html#stack-size&quot;&gt;2 MiB stack by default&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Dropping a future cancels it.&lt;&#x2F;strong&gt; It stops at its last await and never resumes. This is how timeouts and &lt;code&gt;select!&lt;&#x2F;code&gt; work, and also how you lose half a write if you are not careful.&lt;&#x2F;p&gt;
&lt;p&gt;Tokio&#x27;s &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;docs.rs&#x2F;tokio&#x2F;latest&#x2F;tokio&#x2F;runtime&#x2F;index.html&quot;&gt;multi thread runtime&lt;&#x2F;a&gt; runs one worker per core, each with a local queue. An idle worker steals tasks from a busy one. Tokio&#x27;s &lt;code&gt;current_thread&lt;&#x2F;code&gt; runtime runs everything on one thread, with no stealing. Tasks spawned with &lt;code&gt;tokio::spawn&lt;&#x2F;code&gt; still have to be &lt;code&gt;Send&lt;&#x2F;code&gt; on either runtime.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;waiting-one-runtime-many-tasks&quot;&gt;Waiting: one runtime, many tasks &lt;a class=&quot;zola-anchor&quot; href=&quot;#waiting-one-runtime-many-tasks&quot; aria-label=&quot;Link to this section&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;This is what async is for. I spawned 100,000 Tokio tasks that each sleep for 100 ms. All of them finished in 131 ms. One hundred thousand OS threads would reserve 200 GB of virtual stack, and the scheduler would spend its time switching between them.&lt;&#x2F;p&gt;
&lt;p&gt;Spawning is also cheaper. A thread spawn and join cost 17 to 20 µs here. A Tokio spawn and await cost about 8 µs. I did not break that number down, but each spawn in this test hands the task to a worker thread and wakes the caller back up.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;a href=&quot;https:&#x2F;&#x2F;gabrielkoerich.com&#x2F;posts&#x2F;orchestrating-agents-for-humans&#x2F;&quot;&gt;orch&lt;&#x2F;a&gt;, the service that runs my coding agents, is this shape. One process runs the tick loop, GitHub polling, tmux output capture every two seconds, a Discord websocket and a Telegram bot. Almost all of it is waiting, so it all lives on one Tokio runtime.&lt;&#x2F;p&gt;
&lt;p&gt;Cloudflare made the same choice at a much larger scale. &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;blog.cloudflare.com&#x2F;how-we-built-pingora-the-proxy-that-connects-cloudflare-to-the-internet&#x2F;&quot;&gt;Their Rust proxy, Pingora&lt;&#x2F;a&gt;, uses threads with work stealing instead of NGINX&#x27;s processes, so that every request can share one connection pool. Connection reuse for one large customer went from 87.1% to 99.92%, and the service used about 70% less CPU and 67% less memory.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-rule-that-breaks-it-do-not-block-the-runtime&quot;&gt;The rule that breaks it: do not block the runtime &lt;a class=&quot;zola-anchor&quot; href=&quot;#the-rule-that-breaks-it-do-not-block-the-runtime&quot; aria-label=&quot;Link to this section&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;Because async is cooperative, a task that does not await holds its worker thread. Alice Ryhl, one of the Tokio maintainers, &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;ryhl.io&#x2F;blog&#x2F;async-what-is-blocking&#x2F;&quot;&gt;gives the rule of thumb&lt;&#x2F;a&gt;: no more than 10 to 100 microseconds between awaits.&lt;&#x2F;p&gt;
&lt;p&gt;I measured what happens when you ignore it. On a &lt;code&gt;current_thread&lt;&#x2F;code&gt; runtime, one task waits on a 1 ms timer while another task calls &lt;code&gt;std::thread::sleep&lt;&#x2F;code&gt; for 100 ms. The 1 ms timer fired after 101 ms. Every other task on that thread waited the same way. On a multi thread runtime the damage is smaller, until enough tasks block that every worker is stuck.&lt;&#x2F;p&gt;
&lt;p&gt;What counts as blocking is a measure of time, not a list of functions:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;std::fs&lt;&#x2F;code&gt; calls, which can wait on a disk&lt;&#x2F;li&gt;
&lt;li&gt;a synchronous HTTP or database client&lt;&#x2F;li&gt;
&lt;li&gt;a &lt;code&gt;std::sync::Mutex&lt;&#x2F;code&gt; held across an &lt;code&gt;.await&lt;&#x2F;code&gt;, or held while another thread does slow work&lt;&#x2F;li&gt;
&lt;li&gt;CPU work longer than about 100 µs&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;For blocking IO, Tokio has &lt;code&gt;spawn_blocking&lt;&#x2F;code&gt;. It runs the closure on a separate pool of threads, &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;docs.rs&#x2F;tokio&#x2F;latest&#x2F;tokio&#x2F;runtime&#x2F;struct.Builder.html#method.max_blocking_threads&quot;&gt;up to 512 by default&lt;&#x2F;a&gt;, and gives you a future for the result. &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;docs.rs&#x2F;tokio&#x2F;latest&#x2F;tokio&#x2F;fs&#x2F;index.html&quot;&gt;&lt;code&gt;tokio::fs&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; itself is built on it, because most operating systems have no async file API. orch flushes its webhook dedup file this way, outside the lock, so a slow disk never holds an async worker:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; class=&quot;language-rust z-code&quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-storage z-type z-rust&quot;&gt;let&lt;&#x2F;span&gt; result &lt;span class=&quot;z-keyword z-operator z-assignment z-rust&quot;&gt;=&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-path z-rust&quot;&gt;tokio&lt;span class=&quot;z-punctuation z-accessor z-rust&quot;&gt;::&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-path z-rust&quot;&gt;task&lt;span class=&quot;z-punctuation z-accessor z-rust&quot;&gt;::&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;spawn_blocking&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage z-modifier z-rust&quot;&gt;move&lt;&#x2F;span&gt; &lt;span class=&quot;z-keyword z-operator z-logical z-rust&quot;&gt;||&lt;&#x2F;span&gt; &lt;span class=&quot;z-support z-function z-rust&quot;&gt;flush_dedup_file&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator z-bitwise z-rust&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;path&lt;span class=&quot;z-punctuation z-separator z-rust&quot;&gt;,&lt;&#x2F;span&gt; &lt;span class=&quot;z-keyword z-operator z-bitwise z-rust&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;entries&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-accessor z-dot z-rust&quot;&gt;.&lt;&#x2F;span&gt;await&lt;span class=&quot;z-punctuation z-terminator z-rust&quot;&gt;;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Source: &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;gabrielkoerich&#x2F;orch&#x2F;blob&#x2F;872859922a506602e810712d929e13135be1c7f7&#x2F;src&#x2F;channels&#x2F;github.rs#L188-L206&quot;&gt;&lt;code&gt;src&#x2F;channels&#x2F;github.rs&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;p&gt;The same codebase has the opposite case, on purpose. orch reads its GitHub token by running &lt;code&gt;gh auth token&lt;&#x2F;code&gt;, a blocking subprocess call of about 100 ms. It runs at most once an hour, under a lock, and the result is cached. Earlier attempts to &amp;quot;fix&amp;quot; it with &lt;code&gt;spawn_blocking&lt;&#x2F;code&gt; or &lt;code&gt;tokio::process&lt;&#x2F;code&gt; broke the auth flow. The function now carries &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;gabrielkoerich&#x2F;orch&#x2F;blob&#x2F;872859922a506602e810712d929e13135be1c7f7&#x2F;src&#x2F;github&#x2F;token.rs#L308-L321&quot;&gt;a comment&lt;&#x2F;a&gt; that says not to touch it. A blocking call that happens once an hour costs nothing. The rule is about how long and how often, and grep cannot find that for you.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;working-more-cores&quot;&gt;Working: more cores &lt;a class=&quot;zola-anchor&quot; href=&quot;#working-more-cores&quot; aria-label=&quot;Link to this section&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;For CPU work the tool is &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;docs.rs&#x2F;rayon&#x2F;latest&#x2F;rayon&#x2F;&quot;&gt;Rayon&lt;&#x2F;a&gt;. It keeps &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;docs.rs&#x2F;rayon&#x2F;latest&#x2F;rayon&#x2F;struct.ThreadPoolBuilder.html#method.num_threads&quot;&gt;one thread per logical CPU&lt;&#x2F;a&gt; and splits a parallel iterator across them with work stealing:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; class=&quot;language-rust z-code&quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-keyword z-other z-rust&quot;&gt;use&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-path z-rust&quot;&gt;rayon&lt;span class=&quot;z-punctuation z-accessor z-rust&quot;&gt;::&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-path z-rust&quot;&gt;prelude&lt;span class=&quot;z-punctuation z-accessor z-rust&quot;&gt;::&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator z-arithmetic z-rust&quot;&gt;*&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-terminator z-rust&quot;&gt;;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-storage z-type z-rust&quot;&gt;let&lt;&#x2F;span&gt; total&lt;span class=&quot;z-punctuation z-separator z-rust&quot;&gt;:&lt;&#x2F;span&gt; &lt;span class=&quot;z-storage z-type z-rust&quot;&gt;u64&lt;&#x2F;span&gt; &lt;span class=&quot;z-keyword z-operator z-assignment z-rust&quot;&gt;=&lt;&#x2F;span&gt; jobs&lt;span class=&quot;z-punctuation z-accessor z-dot z-rust&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-support z-function z-rust&quot;&gt;par_iter&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-accessor z-dot z-rust&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-support z-function z-rust&quot;&gt;map&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function z-closure z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-parameters z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-parameters z-begin z-rust&quot;&gt;|&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function z-closure z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-parameters z-rust&quot;&gt;&lt;span class=&quot;z-keyword z-operator z-rust&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-parameter z-rust&quot;&gt;j&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-section z-parameters z-end z-rust&quot;&gt;|&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; &lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function z-closure z-rust&quot;&gt;&lt;span class=&quot;z-support z-function z-rust&quot;&gt;work&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;j&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-accessor z-dot z-rust&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-support z-function z-rust&quot;&gt;sum&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-terminator z-rust&quot;&gt;;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;2,000 small jobs took 47 ms on one thread and 4 ms with &lt;code&gt;par_iter&lt;&#x2F;code&gt;. That is close to 12x on 14 cores, and the M4 Max has 10 performance cores and 4 efficiency cores, so 12x is about what the hardware allows.&lt;&#x2F;p&gt;
&lt;p&gt;Tokio can also run CPU work in parallel, and with small jobs it matched Rayon&#x27;s throughput. The cost shows up elsewhere: CPU work sits on the same workers as your network tasks.&lt;&#x2F;p&gt;
&lt;p&gt;To show it, I ran 56 jobs of 50 ms each, four per core, while a task on the same runtime waited on a 1 ms timer:&lt;&#x2F;p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;where the 50 ms jobs ran&lt;&#x2F;th&gt;&lt;th&gt;worst wait for a 1 ms timer&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Tokio workers&lt;&#x2F;td&gt;&lt;td&gt;126 to 223 ms&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Rayon, results sent back to Tokio&lt;&#x2F;td&gt;&lt;td&gt;11 to 114 ms&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;p&gt;On Tokio&#x27;s workers, the timer task had to wait for a worker to finish a job. Four jobs of 50 ms per worker is about 200 ms, which fits the 126 to 223 ms I measured. With Rayon, the runtime threads stayed free. The timer was still late, and the likely reason is that Rayon&#x27;s 14 threads and Tokio&#x27;s 14 workers share 14 cores, so the OS scheduler decides who runs. I did not measure that part. If you need the runtime to stay responsive under full CPU load, give Rayon fewer threads than you have cores.&lt;&#x2F;p&gt;
&lt;p&gt;The pattern to connect them is a oneshot channel:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; class=&quot;language-rust z-code&quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-keyword z-other z-rust&quot;&gt;use&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-path z-rust&quot;&gt;tokio&lt;span class=&quot;z-punctuation z-accessor z-rust&quot;&gt;::&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-path z-rust&quot;&gt;sync&lt;span class=&quot;z-punctuation z-accessor z-rust&quot;&gt;::&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;oneshot&lt;span class=&quot;z-punctuation z-terminator z-rust&quot;&gt;;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;async &lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-storage z-type z-function z-rust&quot;&gt;fn&lt;&#x2F;span&gt; &lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function z-rust&quot;&gt;hash&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-parameters z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-parameters z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-parameter z-rust&quot;&gt;data&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-rust&quot;&gt;:&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-generic z-rust&quot;&gt;&lt;span class=&quot;z-support z-type z-rust&quot;&gt;Vec&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-generic z-begin z-rust&quot;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage z-type z-rust&quot;&gt;u8&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-generic z-end z-rust&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-parameters z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-parameters z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt; &lt;span class=&quot;z-meta z-function z-return-type z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-separator z-rust&quot;&gt;-&amp;gt;&lt;&#x2F;span&gt; &lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;[&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage z-type z-rust&quot;&gt;u8&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-rust&quot;&gt;;&lt;&#x2F;span&gt; &lt;span class=&quot;z-constant z-numeric z-integer z-decimal z-rust&quot;&gt;32&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-block z-begin z-rust&quot;&gt;{&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;    &lt;span class=&quot;z-storage z-type z-rust&quot;&gt;let&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;tx&lt;span class=&quot;z-punctuation z-separator z-rust&quot;&gt;,&lt;&#x2F;span&gt; rx&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; &lt;span class=&quot;z-keyword z-operator z-assignment z-rust&quot;&gt;=&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-path z-rust&quot;&gt;oneshot&lt;span class=&quot;z-punctuation z-accessor z-rust&quot;&gt;::&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;channel&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-terminator z-rust&quot;&gt;;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;    &lt;span class=&quot;z-comment z-line z-double-slash z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-rust&quot;&gt;&#x2F;&#x2F;&lt;&#x2F;span&gt; A panic in here aborts the process unless the pool sets a panic_handler
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;    &lt;span class=&quot;z-meta z-path z-rust&quot;&gt;rayon&lt;span class=&quot;z-punctuation z-accessor z-rust&quot;&gt;::&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;spawn&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage z-modifier z-rust&quot;&gt;move&lt;&#x2F;span&gt; &lt;span class=&quot;z-keyword z-operator z-logical z-rust&quot;&gt;||&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-block z-begin z-rust&quot;&gt;{&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;        &lt;span class=&quot;z-storage z-type z-rust&quot;&gt;let&lt;&#x2F;span&gt; &lt;span class=&quot;z-keyword z-operator z-rust&quot;&gt;_&lt;&#x2F;span&gt; &lt;span class=&quot;z-keyword z-operator z-assignment z-rust&quot;&gt;=&lt;&#x2F;span&gt; tx&lt;span class=&quot;z-punctuation z-accessor z-dot z-rust&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-support z-function z-rust&quot;&gt;send&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-path z-rust&quot;&gt;blake3&lt;span class=&quot;z-punctuation z-accessor z-rust&quot;&gt;::&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;hash&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator z-bitwise z-rust&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;data&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-accessor z-dot z-rust&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-support z-function z-rust&quot;&gt;into&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-terminator z-rust&quot;&gt;;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;    &lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-block z-end z-rust&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-terminator z-rust&quot;&gt;;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;    rx&lt;span class=&quot;z-punctuation z-accessor z-dot z-rust&quot;&gt;.&lt;&#x2F;span&gt;await&lt;span class=&quot;z-punctuation z-accessor z-dot z-rust&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-support z-function z-rust&quot;&gt;expect&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-string z-quoted z-double z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-rust&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;hash task dropped the sender&lt;span class=&quot;z-punctuation z-definition z-string z-end z-rust&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-block z-end z-rust&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;&lt;code&gt;spawn_blocking&lt;&#x2F;code&gt; works for CPU work too, but its pool grows to 512 threads. &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;docs.rs&#x2F;tokio&#x2F;latest&#x2F;tokio&#x2F;task&#x2F;fn.spawn_blocking.html&quot;&gt;Tokio&#x27;s own docs&lt;&#x2F;a&gt; say to limit CPU work there with a semaphore, or to use Rayon.&lt;&#x2F;p&gt;
&lt;p&gt;So the split is simple. Waiting goes on the async runtime. Working goes on a pool sized to the cores. A program that does both keeps them apart and passes results through a channel.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;when-a-runtime-is-too-much&quot;&gt;When a runtime is too much &lt;a class=&quot;zola-anchor&quot; href=&quot;#when-a-runtime-is-too-much&quot; aria-label=&quot;Link to this section&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;The broker in &lt;a href=&quot;https:&#x2F;&#x2F;gabrielkoerich.com&#x2F;posts&#x2F;a-password-manager-for-agents&#x2F;&quot;&gt;passbox&lt;&#x2F;a&gt;, my password manager for agents, has no async runtime at all. It &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;gabrielkoerich&#x2F;passbox&#x2F;blob&#x2F;f4d4a937c1aabc50787e2a48514799dc2ef3e240&#x2F;src&#x2F;broker.rs#L585-L594&quot;&gt;accepts one connection at a time&lt;&#x2F;a&gt; on a Unix socket and answers it. A second thread &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;gabrielkoerich&#x2F;passbox&#x2F;blob&#x2F;f4d4a937c1aabc50787e2a48514799dc2ef3e240&#x2F;src&#x2F;broker.rs#L558-L576&quot;&gt;serves the network listener&lt;&#x2F;a&gt;, and a third is &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;gabrielkoerich&#x2F;passbox&#x2F;blob&#x2F;f4d4a937c1aabc50787e2a48514799dc2ef3e240&#x2F;src&#x2F;broker.rs#L600-L610&quot;&gt;a watchdog&lt;&#x2F;a&gt; that exits the process, dropping the key, when no request has arrived for too long.&lt;&#x2F;p&gt;
&lt;p&gt;The &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;passbox.gabrielkoerich.com&#x2F;design&#x2F;&quot;&gt;design doc&lt;&#x2F;a&gt; gives the reason in one line: no async runtime, &amp;quot;because a biometric prompt blocks anyway&amp;quot;. The two listener threads share the broker state behind one &lt;code&gt;Mutex&lt;&#x2F;code&gt;, so only one request touches it at a time.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;tokio.rs&#x2F;tokio&#x2F;tutorial&quot;&gt;Tokio&#x27;s own tutorial&lt;&#x2F;a&gt; lists the cases where it does not help: CPU bound work, reading a lot of files, and a program that sends a single request. Plain threads also have real advantages. A blocking stack trace shows where the thread is waiting. There is no runtime to start and no colored functions, and &lt;code&gt;std::thread::scope&lt;&#x2F;code&gt; lets threads borrow from the caller instead of requiring &lt;code&gt;&#x27;static&lt;&#x2F;code&gt; data. A handful of long lived threads that each own one job is a good design.&lt;&#x2F;p&gt;
&lt;p&gt;If you do want async on one thread without the &lt;code&gt;Send&lt;&#x2F;code&gt; bound, spawn tasks with &lt;code&gt;spawn_local&lt;&#x2F;code&gt; on a &lt;code&gt;LocalSet&lt;&#x2F;code&gt;. Those tasks can hold &lt;code&gt;Rc&lt;&#x2F;code&gt; and &lt;code&gt;RefCell&lt;&#x2F;code&gt; instead of &lt;code&gt;Arc&lt;&#x2F;code&gt; and &lt;code&gt;Mutex&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;reacting-when-the-worst-case-matters&quot;&gt;Reacting: when the worst case matters &lt;a class=&quot;zola-anchor&quot; href=&quot;#reacting-when-the-worst-case-matters&quot; aria-label=&quot;Link to this section&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;Async runtimes are built for throughput across many tasks. They are not built to bound how late one task can be. Every wake up goes through a queue, a parked worker has to be woken by the OS, and work stealing can move your task to a core with a cold cache.&lt;&#x2F;p&gt;
&lt;p&gt;I measured one way wake up latency three ways, sending one message at a time with a short idle pause between them:&lt;&#x2F;p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;receiver&lt;&#x2F;th&gt;&lt;th&gt;p50&lt;&#x2F;th&gt;&lt;th&gt;p99&lt;&#x2F;th&gt;&lt;th&gt;p99.9&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Tokio task, &lt;code&gt;tokio::sync::mpsc&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;8 µs&lt;&#x2F;td&gt;&lt;td&gt;440 to 620 µs&lt;&#x2F;td&gt;&lt;td&gt;1.0 to 1.7 ms&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;thread blocked on &lt;code&gt;std::sync::mpsc&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;td&gt;2.6 to 3 µs&lt;&#x2F;td&gt;&lt;td&gt;130 to 220 µs&lt;&#x2F;td&gt;&lt;td&gt;0.56 to 0.73 ms&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;thread spinning on an atomic&lt;&#x2F;td&gt;&lt;td&gt;0.13 to 0.25 µs&lt;&#x2F;td&gt;&lt;td&gt;3.5 to 5.4 µs&lt;&#x2F;td&gt;&lt;td&gt;17 to 240 µs&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;p&gt;The median tells you about throughput, the tail about the one tick you missed. A parked thread or task cost microseconds to wake at the median and hundreds of microseconds in the tail. The likely cause is that the OS has to schedule the thread again, which I did not measure directly. A thread that never sleeps is already running when the message lands.&lt;&#x2F;p&gt;
&lt;p&gt;For a hot path that has to react, the design changes:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;One thread owns the work.&lt;&#x2F;strong&gt; No runtime, no queue between the event and the handler.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;It spins instead of parking.&lt;&#x2F;strong&gt; It burns a whole core to never pay the wake up.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Nothing on the hot path allocates or takes a lock.&lt;&#x2F;strong&gt; Memory is allocated at start. Data arrives through a lock free single producer, single consumer ring buffer.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;No syscalls on the hot path.&lt;&#x2F;strong&gt; Logging goes through another ring to another thread.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;pre data-lang=&quot;rust&quot; class=&quot;language-rust z-code&quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-keyword z-other z-rust&quot;&gt;use&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-path z-rust&quot;&gt;rtrb&lt;span class=&quot;z-punctuation z-accessor z-rust&quot;&gt;::&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;RingBuffer&lt;span class=&quot;z-punctuation z-terminator z-rust&quot;&gt;;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-storage z-type z-rust&quot;&gt;let&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage z-modifier z-rust&quot;&gt;mut&lt;&#x2F;span&gt; producer&lt;span class=&quot;z-punctuation z-separator z-rust&quot;&gt;,&lt;&#x2F;span&gt; &lt;span class=&quot;z-storage z-modifier z-rust&quot;&gt;mut&lt;&#x2F;span&gt; consumer&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; &lt;span class=&quot;z-keyword z-operator z-assignment z-rust&quot;&gt;=&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-path z-rust&quot;&gt;RingBuffer&lt;span class=&quot;z-punctuation z-accessor z-rust&quot;&gt;::&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-generic z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-generic z-begin z-rust&quot;&gt;&amp;lt;&lt;&#x2F;span&gt;Tick&lt;span class=&quot;z-punctuation z-definition z-generic z-end z-rust&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-path z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-accessor z-rust&quot;&gt;::&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;new&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-numeric z-integer z-decimal z-rust&quot;&gt;4096&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-terminator z-rust&quot;&gt;;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-path z-rust&quot;&gt;std&lt;span class=&quot;z-punctuation z-accessor z-rust&quot;&gt;::&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-path z-rust&quot;&gt;thread&lt;span class=&quot;z-punctuation z-accessor z-rust&quot;&gt;::&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;spawn&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-storage z-modifier z-rust&quot;&gt;move&lt;&#x2F;span&gt; &lt;span class=&quot;z-keyword z-operator z-logical z-rust&quot;&gt;||&lt;&#x2F;span&gt; &lt;span class=&quot;z-keyword z-control z-rust&quot;&gt;loop&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-block z-begin z-rust&quot;&gt;{&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;    &lt;span class=&quot;z-keyword z-control z-rust&quot;&gt;match&lt;&#x2F;span&gt; consumer&lt;span class=&quot;z-punctuation z-accessor z-dot z-rust&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-support z-function z-rust&quot;&gt;pop&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-block z-begin z-rust&quot;&gt;{&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;        &lt;span class=&quot;z-support z-type z-rust&quot;&gt;Ok&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;tick&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; &lt;span class=&quot;z-keyword z-operator z-rust&quot;&gt;=&amp;gt;&lt;&#x2F;span&gt; &lt;span class=&quot;z-support z-function z-rust&quot;&gt;on_tick&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;tick&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-rust&quot;&gt;,&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;        &lt;span class=&quot;z-support z-type z-rust&quot;&gt;Err&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator z-rust&quot;&gt;_&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; &lt;span class=&quot;z-keyword z-operator z-rust&quot;&gt;=&amp;gt;&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-path z-rust&quot;&gt;std&lt;span class=&quot;z-punctuation z-accessor z-rust&quot;&gt;::&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-path z-rust&quot;&gt;hint&lt;span class=&quot;z-punctuation z-accessor z-rust&quot;&gt;::&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;spin_loop&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-rust&quot;&gt;,&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;    &lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-block z-end z-rust&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-block z-end z-rust&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-terminator z-rust&quot;&gt;;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The other half is the operating system. A spinning thread is still preempted when the kernel wants that core. On Linux you pin the thread to a core and keep the kernel off it. &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;docs.firedancer.io&#x2F;guide&#x2F;tuning.html&quot;&gt;Firedancer&lt;&#x2F;a&gt;, the Solana validator written in C, runs each stage as a &amp;quot;tile&amp;quot; that takes ownership of a core and never sleeps, and its docs say anything else the kernel runs on those cores steals time and adds jitter. They recommend the &lt;code&gt;nohz_full&lt;&#x2F;code&gt; and &lt;code&gt;rcu_nocbs&lt;&#x2F;code&gt; kernel parameters for that reason.&lt;&#x2F;p&gt;
&lt;p&gt;macOS cannot do this. Apple&#x27;s affinity API &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;developer.apple.com&#x2F;library&#x2F;archive&#x2F;releasenotes&#x2F;Performance&#x2F;RN-AffinityAPI&#x2F;&quot;&gt;only expresses hints&lt;&#x2F;a&gt;, and on Apple Silicon &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;developer.apple.com&#x2F;forums&#x2F;thread&#x2F;703361&quot;&gt;it is not supported at all&lt;&#x2F;a&gt;. That is why my spinning thread had a p99.9 anywhere from 17 to 240 µs: a median of 125 ns, and every so often the kernel took the core away. Measure latency work on the hardware and OS it will run on.&lt;&#x2F;p&gt;
&lt;p&gt;The &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;lmax-exchange.github.io&#x2F;disruptor&#x2F;user-guide&#x2F;index.html&quot;&gt;LMAX Disruptor&lt;&#x2F;a&gt;, a Java library built for an exchange, names the trade off directly. Its default wait strategy blocks and saves CPU. Its busy spin strategy is the fastest, and they say to use it only when there are fewer handler threads than physical cores.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;thread-per-core&quot;&gt;Thread per core &lt;a class=&quot;zola-anchor&quot; href=&quot;#thread-per-core&quot; aria-label=&quot;Link to this section&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;Between a work stealing runtime and a hand pinned thread there is a third design: one runtime per core, sharing nothing. &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;seastar.io&#x2F;shared-nothing&#x2F;&quot;&gt;Seastar&lt;&#x2F;a&gt;, the C++ framework under ScyllaDB, runs one thread per core and passes messages between them instead of sharing memory. In Rust, Glommio and ByteDance&#x27;s Monoio do the same with io_uring.&lt;&#x2F;p&gt;
&lt;p&gt;The reasons are concrete. With one thread per core, a request is never touched by two threads, so it needs no locks and tasks do not need to be &lt;code&gt;Send&lt;&#x2F;code&gt;. Glauber Costa, who wrote Glommio, &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;www.datadoghq.com&#x2F;blog&#x2F;engineering&#x2F;introducing-glommio&#x2F;&quot;&gt;points out&lt;&#x2F;a&gt; that a context switch costs about 5 µs while an NVMe read can finish in under 4 µs. At that point the scheduler is slower than the disk.&lt;&#x2F;p&gt;
&lt;p&gt;The cost is the reason Tokio steals work. Without stealing, one hot core stays hot while the others are idle. &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;bytedance&#x2F;monoio&quot;&gt;Monoio&#x27;s own README&lt;&#x2F;a&gt; warns that it can be slower than Tokio when load is unbalanced. Thread per core pays off when you can split the load evenly, by connection, by key or by shard.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;solana-takes-the-same-decision-away-from-you&quot;&gt;Solana takes the same decision away from you &lt;a class=&quot;zola-anchor&quot; href=&quot;#solana-takes-the-same-decision-away-from-you&quot; aria-label=&quot;Link to this section&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;An on-chain Solana program has none of this. It is &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;solana.com&#x2F;docs&#x2F;programs&#x2F;limitations&quot;&gt;single threaded&lt;&#x2F;a&gt;, it cannot spawn, and it has no async. Parallelism happens one level up. &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;solana.com&#x2F;news&#x2F;sealevel---parallel-processing-thousands-of-smart-contracts&quot;&gt;Every transaction declares every account&lt;&#x2F;a&gt; it will read or write, and the runtime runs transactions with no overlapping writes at the same time. The program is a function. The validator decides what runs in parallel.&lt;&#x2F;p&gt;
&lt;p&gt;The validator had to learn the same lesson inside its own scheduler. Until Agave 2.0, four threads pulled non-vote transactions independently and often took conflicting locks, because none of them knew what the others were doing. The fix, &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;www.anza.xyz&#x2F;blog&#x2F;introducing-the-central-scheduler-an-optional-feature-of-agave-v1-18&quot;&gt;opt-in in 1.18&lt;&#x2F;a&gt; and the default from 2.0, was a single scheduler thread that sees every transaction and routes each one to a worker that will not conflict.&lt;&#x2F;p&gt;
&lt;p&gt;Off chain, the same split applies to a bot. RPC calls, websocket subscriptions and keeping state in sync are waiting, so they go on Tokio. Building and signing a transaction when an opportunity appears is reacting, so it gets its own thread and a ring buffer between them.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;small-machines&quot;&gt;Small machines &lt;a class=&quot;zola-anchor&quot; href=&quot;#small-machines&quot; aria-label=&quot;Link to this section&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;On a microcontroller the async model fits better than anywhere else. &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;embassy.dev&#x2F;book&#x2F;&quot;&gt;Embassy&lt;&#x2F;a&gt; runs async Rust with no heap: tasks are statically allocated, and the CPU sleeps until an interrupt wakes the task that was waiting on it. The hardware interrupt is the waker. Embassy&#x27;s docs say this can use less power than an RTOS, because the executor does not have to guess when a task is ready.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;choosing&quot;&gt;Choosing &lt;a class=&quot;zola-anchor&quot; href=&quot;#choosing&quot; aria-label=&quot;Link to this section&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;the program&lt;&#x2F;th&gt;&lt;th&gt;use&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;waits on many sockets, timers or requests&lt;&#x2F;td&gt;&lt;td&gt;an async runtime, multi thread by default&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;does a little blocking IO inside async code&lt;&#x2F;td&gt;&lt;td&gt;&lt;code&gt;spawn_blocking&lt;&#x2F;code&gt;&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;blocks once in a long while for ~100 ms&lt;&#x2F;td&gt;&lt;td&gt;leave it, and write down why&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;does CPU work over many items&lt;&#x2F;td&gt;&lt;td&gt;Rayon, sized to the cores&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;does both&lt;&#x2F;td&gt;&lt;td&gt;async for IO, Rayon for CPU, a oneshot or channel between&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;has a few long lived jobs, or one human in the loop&lt;&#x2F;td&gt;&lt;td&gt;plain threads&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;has to react within microseconds&lt;&#x2F;td&gt;&lt;td&gt;one pinned thread, spinning, fed by a ring buffer&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;has even load across many connections&lt;&#x2F;td&gt;&lt;td&gt;thread per core&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;runs on a microcontroller&lt;&#x2F;td&gt;&lt;td&gt;Embassy&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;h2 id=&quot;what-it-costs&quot;&gt;What it costs &lt;a class=&quot;zola-anchor&quot; href=&quot;#what-it-costs&quot; aria-label=&quot;Link to this section&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;&lt;strong&gt;Async makes every function choose a side.&lt;&#x2F;strong&gt; An async function can only be awaited from async code, so the choice spreads through a codebase. Crossing back with &lt;code&gt;block_on&lt;&#x2F;code&gt; inside a runtime panics.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;tokio::spawn&lt;&#x2F;code&gt; needs &lt;code&gt;Send + &#x27;static&lt;&#x2F;code&gt;.&lt;&#x2F;strong&gt; Anything a task holds across an await has to be safe to move to another thread, on any runtime, unless it runs on a &lt;code&gt;LocalSet&lt;&#x2F;code&gt;. That is why so much async Rust is full of &lt;code&gt;Arc&lt;&#x2F;code&gt; and &lt;code&gt;Mutex&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Cancellation is silent.&lt;&#x2F;strong&gt; A future dropped inside &lt;code&gt;select!&lt;&#x2F;code&gt; stops at its last await. If that was halfway through a write, the write is half done. Each branch of a &lt;code&gt;select!&lt;&#x2F;code&gt; has to be safe to drop.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Spinning costs a core per thread.&lt;&#x2F;strong&gt; A busy loop shows 100% CPU forever, heats a laptop and empties a battery. Use it only when a few microseconds matter more than a whole core.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Pinning needs control of the machine.&lt;&#x2F;strong&gt; Kernel parameters, isolated cores and a known CPU. A cloud VM or a Mac gives you less of that.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Benchmarks lie by machine.&lt;&#x2F;strong&gt; Everything above ran on one laptop, with other programs open. The ratios hold more than the absolute numbers. Measure on the target.&lt;&#x2F;p&gt;
&lt;hr &#x2F;&gt;
&lt;p&gt;&lt;strong&gt;Corrections, 2026-10-12.&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;The oneshot example implied a panic in the Rayon task would reach &lt;code&gt;rx&lt;&#x2F;code&gt;. By default it aborts the process, so the snippet now says so.&lt;&#x2F;li&gt;
&lt;li&gt;The central scheduler was opt-in in Agave 1.18 and became the default in 2.0. I had dated the change to 1.18.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
</content>
    </entry>
    <entry xml:lang="en">
        <title>I thought the max was 128</title>
        <published>2026-10-07T00:00:00+00:00</published>
        <updated>2026-10-07T00:00:00+00:00</updated>
        <author>
            <name>Gabriel Koerich</name>
        </author>
        <link rel="alternate" type="text/html" href="https://gabrielkoerich.com/posts/i-thought-the-max-was-128/"/>
        <id>https://gabrielkoerich.com/posts/i-thought-the-max-was-128/</id>
        <summary>A Solana vault with 20 assets could be created but not withdrawn from. Three transaction limits, where 128 came from, and why a lookup table did not help.</summary>
        <content type="html" xml:base="https://gabrielkoerich.com/posts/i-thought-the-max-was-128/">&lt;p&gt;I am building a vault on Solana that holds several assets at once. A curator sets target weights, depositors get shares, and a withdrawal pays out in kind: a slice of every asset the vault holds.&lt;&#x2F;p&gt;
&lt;p&gt;The vault account stores its allocations in a fixed array of 20, because a zero copy account needs its data length fixed when it is created. The first version of the create instruction checked new vaults against that array length, so a curator could create a vault with 20 allocations. At that point I had not checked whether such a vault could be withdrawn from. It could not. So I measured the limits on a Solana transaction, found the one that binds, and capped the vault at 10. A test built a vault at 10 and withdrew from all of it in one transaction. It passed.&lt;&#x2F;p&gt;
&lt;p&gt;Then I checked the number I had built everything on against the Solana docs. The account limit I used was 128. On mainnet it is 64. A vault at the cap of 10 would take deposits and could never pay them back.&lt;&#x2F;p&gt;
&lt;p&gt;This post is about how accounts and transactions work on Solana, why the account limit and not bytes or compute decides how big this vault can be, how I got that limit wrong, and what the new v1 transaction format changes.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;how-solana-accounts-work&quot;&gt;How Solana accounts work &lt;a class=&quot;zola-anchor&quot; href=&quot;#how-solana-accounts-work&quot; aria-label=&quot;Link to this section&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;Everything on Solana is an account. An account has an address, a balance in lamports, an owner program, and a byte array of data. Programs hold no state of their own. A program&#x27;s state lives in accounts it owns, and only the owner can change their data.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Size is fixed when you create the account.&lt;&#x2F;strong&gt; You pay a rent deposit for the bytes. An account can grow later with &lt;code&gt;realloc&lt;&#x2F;code&gt;, &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;docs.rs&#x2F;solana-account-info&#x2F;latest&#x2F;solana_account_info&#x2F;constant.MAX_PERMITTED_DATA_INCREASE.html&quot;&gt;by at most 10 KiB per instruction&lt;&#x2F;a&gt;, and the payer tops up the deposit. In practice you decide the layout before the first user deposits, because every later change is a migration of live accounts that hold money.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Program derived addresses have no private key.&lt;&#x2F;strong&gt; A PDA is derived from seeds and the program id, and only that program can sign for it. This is how a program owns token accounts and holds funds.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;A transaction declares every account it touches, up front.&lt;&#x2F;strong&gt; Each account is listed with a flag for writable and for signer, and the runtime locks them before execution. Transactions that write to different accounts can run in parallel. Transactions that write to the same account run one after the other. This is how Solana runs in parallel without programs knowing anything about threads.&lt;&#x2F;p&gt;
&lt;p&gt;That last rule is where the limits come from.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;three-limits-on-one-transaction&quot;&gt;Three limits on one transaction &lt;a class=&quot;zola-anchor&quot; href=&quot;#three-limits-on-one-transaction&quot; aria-label=&quot;Link to this section&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;&lt;strong&gt;Bytes.&lt;&#x2F;strong&gt; A legacy or v0 transaction is &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;solana.com&#x2F;docs&#x2F;core&#x2F;transactions&quot;&gt;at most 1,232 bytes&lt;&#x2F;a&gt;, the IPv6 minimum MTU of 1,280 minus 48 bytes of headers. Every account address is 32 bytes.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Compute.&lt;&#x2F;strong&gt; &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;solana.com&#x2F;docs&#x2F;core&#x2F;fees&quot;&gt;Each instruction gets 200,000 compute units by default&lt;&#x2F;a&gt;. A transaction can request up to 1,400,000.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Accounts.&lt;&#x2F;strong&gt; A transaction can reference at most 64 unique account addresses. That count includes every program the transaction calls, and an account used by several instructions counts once.&lt;&#x2F;p&gt;
&lt;p&gt;An Address Lookup Table helps with bytes. &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;solana.com&#x2F;docs&#x2F;advanced&#x2F;lookup-tables&quot;&gt;A table stores up to 256 addresses&lt;&#x2F;a&gt; on chain, and a v0 transaction refers to each one by a one byte index instead of 32 bytes. It does not help with the account limit. In the words of the docs, the runtime loads at most 64 accounts per transaction regardless of how many addresses the tables it references hold.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;why-a-withdrawal-needs-so-many-accounts&quot;&gt;Why a withdrawal needs so many accounts &lt;a class=&quot;zola-anchor&quot; href=&quot;#why-a-withdrawal-needs-so-many-accounts&quot; aria-label=&quot;Link to this section&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;Two rules make a withdrawal large.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;It pays out of every allocation.&lt;&#x2F;strong&gt; Per allocation, the instruction needs the vault&#x27;s position in that reserve, the reserve, its price oracle, the reserve&#x27;s token authority, the treasury token account, the mint, and the depositor&#x27;s destination token account. That is 7 accounts per allocation.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;It needs a valuation from the same slot.&lt;&#x2F;strong&gt; The share price is the vault&#x27;s total value divided by its shares. Earlier, some instructions accepted a cached value up to 30 seconds old, and two checked nothing at all. Since refreshing is permissionless, a 30 second window let a caller choose which 30 seconds of prices to act on. Now a refresh instruction writes a slot number only after it values every leg, and every instruction that uses the value requires that slot to be the current one. So the refresh goes in the same transaction as the withdrawal, with its own accounts.&lt;&#x2F;p&gt;
&lt;p&gt;Together that is 11 fixed accounts plus 7 per allocation. Those are slots in the instructions, not unique addresses. Legs can share accounts, for example two reserves of the same mint share that mint. But a cap has to hold for the worst case, a vault where no two legs share anything, so the budget counts every slot as its own address.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;measuring-bytes-and-compute&quot;&gt;Measuring bytes and compute &lt;a class=&quot;zola-anchor&quot; href=&quot;#measuring-bytes-and-compute&quot; aria-label=&quot;Link to this section&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;I measured the refresh plus withdrawal pair in a local test environment.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Bytes ran out at 2 allocations&lt;&#x2F;strong&gt; in a legacy transaction. With a lookup table, 20 allocations compiled to 972 bytes, and 10 compiled to 581.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Compute measured 105k units at one allocation and 144k at two.&lt;&#x2F;strong&gt; That is about 39k per leg on a floor of 66k. Past about three allocations the pair goes over the 200k default and has to request more. Twenty allocations would land near 850k, under the 1.4M ceiling.&lt;&#x2F;p&gt;
&lt;p&gt;So bytes looked tight and a lookup table removed them, and compute had room to spare. The account limit was the one that decided the size of the vault.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;where-128-came-from&quot;&gt;Where 128 came from &lt;a class=&quot;zola-anchor&quot; href=&quot;#where-128-came-from&quot; aria-label=&quot;Link to this section&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;I designed the cap against 128 accounts. That number was everywhere I looked. The Solana SDK has a constant for it:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; class=&quot;language-rust z-code&quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-storage z-modifier z-rust&quot;&gt;pub&lt;&#x2F;span&gt; &lt;span class=&quot;z-storage z-type z-rust&quot;&gt;const&lt;&#x2F;span&gt; &lt;span class=&quot;z-constant z-other z-rust&quot;&gt;MAX_TX_ACCOUNT_LOCKS&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-rust&quot;&gt;:&lt;&#x2F;span&gt; &lt;span class=&quot;z-storage z-type z-rust&quot;&gt;usize&lt;&#x2F;span&gt; &lt;span class=&quot;z-keyword z-operator z-assignment z-rust&quot;&gt;=&lt;&#x2F;span&gt; &lt;span class=&quot;z-constant z-numeric z-integer z-decimal z-rust&quot;&gt;128&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-terminator z-rust&quot;&gt;;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Source: &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;anza-xyz&#x2F;solana-sdk&#x2F;blob&#x2F;transaction%40v2.2.3&#x2F;transaction&#x2F;src&#x2F;sanitized.rs#L21&quot;&gt;&lt;code&gt;solana-transaction&lt;&#x2F;code&gt; v2.2.3, &lt;code&gt;sanitized.rs&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;p&gt;At 128, the ceiling is 16 allocations. I put the cap at 10, to leave room in case a later change added an account per leg, and wrote a compile time check against the constant:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; class=&quot;language-rust z-code&quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-storage z-type z-rust&quot;&gt;const&lt;&#x2F;span&gt; &lt;span class=&quot;z-keyword z-operator z-rust&quot;&gt;_&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-rust&quot;&gt;:&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; &lt;span class=&quot;z-keyword z-operator z-assignment z-rust&quot;&gt;=&lt;&#x2F;span&gt; &lt;span class=&quot;z-support z-macro z-rust&quot;&gt;assert!&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;    &lt;span class=&quot;z-constant z-other z-rust&quot;&gt;FIXED_ACCOUNTS&lt;&#x2F;span&gt; &lt;span class=&quot;z-keyword z-operator z-arithmetic z-rust&quot;&gt;+&lt;&#x2F;span&gt; &lt;span class=&quot;z-constant z-other z-rust&quot;&gt;ACCOUNTS_PER_LEG&lt;&#x2F;span&gt; &lt;span class=&quot;z-keyword z-operator z-arithmetic z-rust&quot;&gt;*&lt;&#x2F;span&gt; &lt;span class=&quot;z-constant z-other z-rust&quot;&gt;MAX_ACTIVE_LEGS&lt;&#x2F;span&gt; &lt;span class=&quot;z-keyword z-operator z-comparison z-rust&quot;&gt;&amp;lt;=&lt;&#x2F;span&gt; &lt;span class=&quot;z-constant z-other z-rust&quot;&gt;MAX_TX_ACCOUNT_LOCKS&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-rust&quot;&gt;,&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;    &lt;span class=&quot;z-string z-quoted z-double z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-rust&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;a vault at the cap must stay withdrawable in one transaction&lt;span class=&quot;z-punctuation z-definition z-string z-end z-rust&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-terminator z-rust&quot;&gt;;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The end to end test at 10 allocations withdrew from every leg in one transaction, and it passed. That test does not settle the limit. Its legs share accounts, all ten use the same oracle, so its unique count is below the worst case, and I did not measure it.&lt;&#x2F;p&gt;
&lt;p&gt;The name gave me a second reason to think there was room. &lt;code&gt;MAX_TX_ACCOUNT_LOCKS&lt;&#x2F;code&gt; sounds like a count of locks, and I read locks as the writable accounts: the vault, its positions, the token accounts. The oracles, mints and programs are read-only, so I assumed they did not count. They do. The runtime takes a read lock on every read-only account, which lets other transactions read it in parallel, and the limit is checked against the length of the whole account list, writable or not:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; class=&quot;language-rust z-code&quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-storage z-modifier z-rust&quot;&gt;pub&lt;&#x2F;span&gt; &lt;span class=&quot;z-storage z-type z-function z-rust&quot;&gt;fn&lt;&#x2F;span&gt; &lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function z-rust&quot;&gt;validate_account_locks&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-parameters z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-parameters z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-parameters z-rust&quot;&gt;    &lt;span class=&quot;z-variable z-parameter z-rust&quot;&gt;account_keys&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-rust&quot;&gt;:&lt;&#x2F;span&gt; AccountKeys,
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-parameters z-rust&quot;&gt;    &lt;span class=&quot;z-variable z-parameter z-rust&quot;&gt;tx_account_lock_limit&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-rust&quot;&gt;:&lt;&#x2F;span&gt; &lt;span class=&quot;z-storage z-type z-rust&quot;&gt;usize&lt;&#x2F;span&gt;,
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-parameters z-rust&quot;&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-parameters z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-parameters z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt; &lt;span class=&quot;z-meta z-function z-return-type z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-separator z-rust&quot;&gt;-&amp;gt;&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-generic z-rust&quot;&gt;TransactionResult&lt;span class=&quot;z-punctuation z-definition z-generic z-begin z-rust&quot;&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-generic z-end z-rust&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; &lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-block z-begin z-rust&quot;&gt;{&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;    &lt;span class=&quot;z-keyword z-control z-rust&quot;&gt;if&lt;&#x2F;span&gt; account_keys&lt;span class=&quot;z-punctuation z-accessor z-dot z-rust&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-support z-function z-rust&quot;&gt;len&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; &lt;span class=&quot;z-keyword z-operator z-comparison z-rust&quot;&gt;&amp;gt;&lt;&#x2F;span&gt; tx_account_lock_limit &lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-block z-begin z-rust&quot;&gt;{&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;        &lt;span class=&quot;z-support z-type z-rust&quot;&gt;Err&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-path z-rust&quot;&gt;TransactionError&lt;span class=&quot;z-punctuation z-accessor z-rust&quot;&gt;::&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;TooManyAccountLocks&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;    &lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-block z-end z-rust&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; &lt;span class=&quot;z-keyword z-control z-rust&quot;&gt;else&lt;&#x2F;span&gt; &lt;span class=&quot;z-keyword z-control z-rust&quot;&gt;if&lt;&#x2F;span&gt; &lt;span class=&quot;z-support z-function z-rust&quot;&gt;has_duplicates&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;account_keys&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-block z-begin z-rust&quot;&gt;{&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;        &lt;span class=&quot;z-support z-type z-rust&quot;&gt;Err&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-path z-rust&quot;&gt;TransactionError&lt;span class=&quot;z-punctuation z-accessor z-rust&quot;&gt;::&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;AccountLoadedTwice&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;    &lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-block z-end z-rust&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; &lt;span class=&quot;z-keyword z-control z-rust&quot;&gt;else&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-block z-begin z-rust&quot;&gt;{&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;        &lt;span class=&quot;z-support z-type z-rust&quot;&gt;Ok&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;    &lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-block z-end z-rust&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-block z-end z-rust&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Source: &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;anza-xyz&#x2F;agave&#x2F;blob&#x2F;8063bd2f7ea538f9d532ca958c882f8caa7fa28d&#x2F;accounts-db&#x2F;src&#x2F;account_locks.rs#L143-L154&quot;&gt;Agave, &lt;code&gt;accounts-db&#x2F;src&#x2F;account_locks.rs&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;p&gt;&lt;code&gt;account_keys.len()&lt;&#x2F;code&gt; adds up the static keys and every address loaded from a lookup table, writable and read-only. Marking an account read-only lets more transactions run beside yours, but it does not make room under the limit.&lt;&#x2F;p&gt;
&lt;p&gt;The validator does not use the constant directly. In Agave the limit depends on a feature gate:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; class=&quot;language-rust z-code&quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-storage z-modifier z-rust&quot;&gt;pub&lt;&#x2F;span&gt; &lt;span class=&quot;z-storage z-type z-function z-rust&quot;&gt;fn&lt;&#x2F;span&gt; &lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function z-rust&quot;&gt;get_transaction_account_lock_limit&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-parameters z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-parameters z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator z-rust&quot;&gt;&amp;amp;&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-parameter z-rust&quot;&gt;self&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-parameters z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-parameters z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt; &lt;span class=&quot;z-meta z-function z-return-type z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-separator z-rust&quot;&gt;-&amp;gt;&lt;&#x2F;span&gt; &lt;span class=&quot;z-storage z-type z-rust&quot;&gt;usize&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; &lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-block z-begin z-rust&quot;&gt;{&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;    &lt;span class=&quot;z-keyword z-control z-rust&quot;&gt;if&lt;&#x2F;span&gt; &lt;span class=&quot;z-storage z-type z-rust&quot;&gt;let&lt;&#x2F;span&gt; &lt;span class=&quot;z-support z-type z-rust&quot;&gt;Some&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;transaction_account_lock_limit&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; &lt;span class=&quot;z-keyword z-operator z-assignment z-rust&quot;&gt;=&lt;&#x2F;span&gt; &lt;span class=&quot;z-variable z-language z-rust&quot;&gt;self&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-accessor z-dot z-rust&quot;&gt;.&lt;&#x2F;span&gt;transaction_account_lock_limit &lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-block z-begin z-rust&quot;&gt;{&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;        transaction_account_lock_limit
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;    &lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-block z-end z-rust&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; &lt;span class=&quot;z-keyword z-control z-rust&quot;&gt;else&lt;&#x2F;span&gt; &lt;span class=&quot;z-keyword z-control z-rust&quot;&gt;if&lt;&#x2F;span&gt; &lt;span class=&quot;z-variable z-language z-rust&quot;&gt;self&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-accessor z-dot z-rust&quot;&gt;.&lt;&#x2F;span&gt;feature_set&lt;span class=&quot;z-punctuation z-accessor z-dot z-rust&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-support z-function z-rust&quot;&gt;snapshot&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-accessor z-dot z-rust&quot;&gt;.&lt;&#x2F;span&gt;increase_tx_account_lock_limit &lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-block z-begin z-rust&quot;&gt;{&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;        &lt;span class=&quot;z-constant z-other z-rust&quot;&gt;MAX_TX_ACCOUNT_LOCKS&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;    &lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-block z-end z-rust&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; &lt;span class=&quot;z-keyword z-control z-rust&quot;&gt;else&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-block z-begin z-rust&quot;&gt;{&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;        &lt;span class=&quot;z-constant z-numeric z-integer z-decimal z-rust&quot;&gt;64&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;    &lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-block z-end z-rust&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-function z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-block z-end z-rust&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Source: &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;anza-xyz&#x2F;agave&#x2F;blob&#x2F;8063bd2f7ea538f9d532ca958c882f8caa7fa28d&#x2F;runtime&#x2F;src&#x2F;bank.rs#L3849-L3857&quot;&gt;Agave, &lt;code&gt;runtime&#x2F;src&#x2F;bank.rs&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;p&gt;The gate is &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;solana-labs&#x2F;solana&#x2F;issues&#x2F;27241&quot;&gt;&lt;code&gt;increase_tx_account_lock_limit&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;, &amp;quot;increase tx account lock limit to 128&amp;quot;. Its tracking issue lists activations on devnet and testnet, but neither cluster has the feature account today. On mainnet it was never activated:&lt;&#x2F;p&gt;
&lt;pre class=&quot;z-code&quot;&gt;&lt;code&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;$ solana feature status 9LZdXeKGeBV6hRLdxS1rHbHoEUsKqesCC2ZAPTPKJAbK -um
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;9LZdXeKGeBV6hRLdxS1rHbHoEUsKqesCC2ZAPTPKJAbK | inactive | NA | increase tx account lock limit to 128 #27241
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;solana.com&#x2F;docs&#x2F;core&#x2F;transactions&quot;&gt;The Solana docs say it directly&lt;&#x2F;a&gt;: max accounts per transaction is 64, and 128 only when that gate is activated, which it currently is not.&lt;&#x2F;p&gt;
&lt;p&gt;At 64, the worst case ceiling is 11 + 7n ≤ 64, so 7 allocations. A cap of 10 needs up to 81. The compile time check passed because it compared against the wrong constant. It is only as good as the number on the right side, and that number was wrong for every cluster I would deploy to.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-lookup-table-did-not-change-it&quot;&gt;The lookup table did not change it &lt;a class=&quot;zola-anchor&quot; href=&quot;#the-lookup-table-did-not-change-it&quot; aria-label=&quot;Link to this section&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;It is easy to read a lookup table as a way around the account limit. It only changes how accounts are written down. A v0 transaction with a lookup table fits ten legs in 581 bytes, and the runtime still refuses to load more than 64 unique accounts.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;what-v1-transactions-change&quot;&gt;What v1 transactions change &lt;a class=&quot;zola-anchor&quot; href=&quot;#what-v1-transactions-change&quot; aria-label=&quot;Link to this section&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;On September 15, 2026, at the start of mainnet epoch 1035, the v1 transaction format went live (&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;solana-foundation&#x2F;solana-improvement-documents&#x2F;blob&#x2F;main&#x2F;proposals&#x2F;0385-transaction-v1.md&quot;&gt;SIMD-0385&lt;&#x2F;a&gt;). The feature account for &lt;code&gt;enable_tx_v1&lt;&#x2F;code&gt; shows activation at slot 447,120,000. &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;solana.com&#x2F;upgrades&#x2F;larger-transaction-sizes&quot;&gt;Solana&#x27;s own comparison&lt;&#x2F;a&gt;:&lt;&#x2F;p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;&lt;&#x2F;th&gt;&lt;th&gt;legacy&lt;&#x2F;th&gt;&lt;th&gt;v0&lt;&#x2F;th&gt;&lt;th&gt;v1&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;size&lt;&#x2F;td&gt;&lt;td&gt;1,232 bytes&lt;&#x2F;td&gt;&lt;td&gt;1,232 bytes&lt;&#x2F;td&gt;&lt;td&gt;4,096 bytes&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;accounts&lt;&#x2F;td&gt;&lt;td&gt;about 32, bound by size&lt;&#x2F;td&gt;&lt;td&gt;64, through lookup tables&lt;&#x2F;td&gt;&lt;td&gt;64, inline&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;lookup tables&lt;&#x2F;td&gt;&lt;td&gt;not supported&lt;&#x2F;td&gt;&lt;td&gt;supported&lt;&#x2F;td&gt;&lt;td&gt;not supported&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;p&gt;For this vault, v1 removes the byte problem. Sixty four addresses at 32 bytes each are 2,048 bytes, which fits in 4,096 with room for instructions and signatures. A withdrawal no longer needs a lookup table, and neither does the client code that builds, extends and fetches one before every transaction.&lt;&#x2F;p&gt;
&lt;p&gt;It does not move the account limit. A v1 transaction can name 64 accounts, the same as v0, so the ceiling is still 7 allocations.&lt;&#x2F;p&gt;
&lt;p&gt;The change that would move it is &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;solana-foundation&#x2F;solana-improvement-documents&#x2F;blob&#x2F;main&#x2F;proposals&#x2F;0596-increase-txv1-account-lock-limit-to-96.md&quot;&gt;SIMD-0596&lt;&#x2F;a&gt;, which raises the account limit for v1 transactions to 96 and keeps legacy and v0 at 64. At 96 the ceiling is 12 allocations, and a cap of 10 fits with room for one more account per leg. Its status today is Draft, and it has no feature gate yet. Until it ships, the cap is 7.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;zero-copy-layouts-because-the-layout-is-forever&quot;&gt;Zero copy layouts, because the layout is forever &lt;a class=&quot;zola-anchor&quot; href=&quot;#zero-copy-layouts-because-the-layout-is-forever&quot; aria-label=&quot;Link to this section&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;&lt;code&gt;MAX_LEGS = 20&lt;&#x2F;code&gt; is an array length. It sets the vault account&#x27;s data length, and changing it means migrating every vault, so it stays at 20. How many allocations a vault may use is a separate constant, &lt;code&gt;MAX_ACTIVE_LEGS&lt;&#x2F;code&gt;. The bug at the start of this post was using the first as if it were the second.&lt;&#x2F;p&gt;
&lt;p&gt;Anchor gives you two ways to read an account. &lt;code&gt;Account&amp;lt;T&amp;gt;&lt;&#x2F;code&gt; deserializes the bytes with Borsh into a Rust struct, a copy, on every instruction. For large accounts that costs compute and memory, and a program has &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;solana.com&#x2F;docs&#x2F;core&#x2F;programs&quot;&gt;a 4,096 byte stack frame and a 32 KiB heap&lt;&#x2F;a&gt; by default. The vault account is 3,488 bytes and holds two arrays of 20 allocations, the current weights and the pending ones.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;code&gt;#[account(zero_copy)]&lt;&#x2F;code&gt; with &lt;code&gt;AccountLoader&amp;lt;T&amp;gt;&lt;&#x2F;code&gt; &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;www.anchor-lang.com&#x2F;docs&#x2F;features&#x2F;zero-copy&quot;&gt;skips the copy&lt;&#x2F;a&gt;. The struct is laid out as &lt;code&gt;#[repr(C)]&lt;&#x2F;code&gt; and must be &lt;code&gt;Pod&lt;&#x2F;code&gt;, and loading it is a cast of the account&#x27;s bytes to a reference. Nothing is deserialized. The price is that the in-memory layout is the on-chain layout, byte for byte:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; class=&quot;language-rust z-code&quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-annotation z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-annotation z-rust&quot;&gt;#&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;[&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-annotation z-rust&quot;&gt;zero_copy&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-struct z-rust&quot;&gt;&lt;span class=&quot;z-storage z-modifier z-rust&quot;&gt;pub&lt;&#x2F;span&gt; &lt;span class=&quot;z-storage z-type z-struct z-rust&quot;&gt;struct&lt;&#x2F;span&gt; &lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-struct z-rust&quot;&gt;&lt;span class=&quot;z-entity z-name z-struct z-rust&quot;&gt;Allocation&lt;&#x2F;span&gt; &lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-struct z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-block z-begin z-rust&quot;&gt;{&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-struct z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;    &lt;span class=&quot;z-storage z-modifier z-rust&quot;&gt;pub&lt;&#x2F;span&gt; &lt;span class=&quot;z-variable z-other z-member z-rust&quot;&gt;reserve&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-type z-rust&quot;&gt;:&lt;&#x2F;span&gt; Pubkey,         &lt;span class=&quot;z-comment z-line z-double-slash z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-rust&quot;&gt;&#x2F;&#x2F;&lt;&#x2F;span&gt; 32
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-struct z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;    &lt;span class=&quot;z-storage z-modifier z-rust&quot;&gt;pub&lt;&#x2F;span&gt; &lt;span class=&quot;z-variable z-other z-member z-rust&quot;&gt;value&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-type z-rust&quot;&gt;:&lt;&#x2F;span&gt; &lt;span class=&quot;z-storage z-type z-rust&quot;&gt;u64&lt;&#x2F;span&gt;,              &lt;span class=&quot;z-comment z-line z-double-slash z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-rust&quot;&gt;&#x2F;&#x2F;&lt;&#x2F;span&gt; 8
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-struct z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;    &lt;span class=&quot;z-storage z-modifier z-rust&quot;&gt;pub&lt;&#x2F;span&gt; &lt;span class=&quot;z-variable z-other z-member z-rust&quot;&gt;weight_bps&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-type z-rust&quot;&gt;:&lt;&#x2F;span&gt; &lt;span class=&quot;z-storage z-type z-rust&quot;&gt;u16&lt;&#x2F;span&gt;,         &lt;span class=&quot;z-comment z-line z-double-slash z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-rust&quot;&gt;&#x2F;&#x2F;&lt;&#x2F;span&gt; 2
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-struct z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;    &lt;span class=&quot;z-storage z-modifier z-rust&quot;&gt;pub&lt;&#x2F;span&gt; &lt;span class=&quot;z-variable z-other z-member z-rust&quot;&gt;_pad&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-type z-rust&quot;&gt;:&lt;&#x2F;span&gt; [&lt;span class=&quot;z-storage z-type z-rust&quot;&gt;u8&lt;&#x2F;span&gt;; 22],          &lt;span class=&quot;z-comment z-line z-double-slash z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-rust&quot;&gt;&#x2F;&#x2F;&lt;&#x2F;span&gt; explicit, 64 bytes in total
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-meta z-struct z-rust&quot;&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-block z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-block z-end z-rust&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;&lt;code&gt;Pod&lt;&#x2F;code&gt; forbids implicit padding, so every gap is a named &lt;code&gt;_pad&lt;&#x2F;code&gt; field. Fields go from largest alignment to smallest so the gaps stay small. Each struct ends with a reserved tail, &lt;code&gt;_reserved: [u64; N]&lt;&#x2F;code&gt;, so new fields can be carved out of it later without changing the account size.&lt;&#x2F;p&gt;
&lt;p&gt;Each struct then gets two size assertions, and both are needed:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rust&quot; class=&quot;language-rust z-code&quot;&gt;&lt;code class=&quot;language-rust&quot; data-lang=&quot;rust&quot;&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-comment z-line z-double-slash z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-rust&quot;&gt;&#x2F;&#x2F;&lt;&#x2F;span&gt; a sum of the fields, which proves there is no implicit padding
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-support z-macro z-rust&quot;&gt;const_assert_eq!&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-path z-rust&quot;&gt;size_of&lt;span class=&quot;z-punctuation z-accessor z-rust&quot;&gt;::&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-generic z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-generic z-begin z-rust&quot;&gt;&amp;lt;&lt;&#x2F;span&gt;Schedule&lt;span class=&quot;z-punctuation z-definition z-generic z-end z-rust&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-rust&quot;&gt;,&lt;&#x2F;span&gt; &lt;span class=&quot;z-constant z-numeric z-integer z-decimal z-rust&quot;&gt;8&lt;&#x2F;span&gt; &lt;span class=&quot;z-keyword z-operator z-arithmetic z-rust&quot;&gt;*&lt;&#x2F;span&gt; &lt;span class=&quot;z-constant z-numeric z-integer z-decimal z-rust&quot;&gt;8&lt;&#x2F;span&gt; &lt;span class=&quot;z-keyword z-operator z-arithmetic z-rust&quot;&gt;+&lt;&#x2F;span&gt; &lt;span class=&quot;z-constant z-numeric z-integer z-decimal z-rust&quot;&gt;3&lt;&#x2F;span&gt; &lt;span class=&quot;z-keyword z-operator z-arithmetic z-rust&quot;&gt;*&lt;&#x2F;span&gt; &lt;span class=&quot;z-constant z-numeric z-integer z-decimal z-rust&quot;&gt;32&lt;&#x2F;span&gt; &lt;span class=&quot;z-keyword z-operator z-arithmetic z-rust&quot;&gt;+&lt;&#x2F;span&gt; &lt;span class=&quot;z-comment z-block z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-rust&quot;&gt;&#x2F;*&lt;&#x2F;span&gt; ... &lt;span class=&quot;z-punctuation z-definition z-comment z-rust&quot;&gt;*&#x2F;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; &lt;span class=&quot;z-constant z-numeric z-integer z-decimal z-rust&quot;&gt;32&lt;&#x2F;span&gt; &lt;span class=&quot;z-keyword z-operator z-arithmetic z-rust&quot;&gt;*&lt;&#x2F;span&gt; &lt;span class=&quot;z-constant z-numeric z-integer z-decimal z-rust&quot;&gt;8&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-terminator z-rust&quot;&gt;;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;
&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-comment z-line z-double-slash z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-rust&quot;&gt;&#x2F;&#x2F;&lt;&#x2F;span&gt; a literal, which proves the account size did not change
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-rust&quot;&gt;&lt;span class=&quot;z-support z-macro z-rust&quot;&gt;const_assert_eq!&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-path z-rust&quot;&gt;size_of&lt;span class=&quot;z-punctuation z-accessor z-rust&quot;&gt;::&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-generic z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-generic z-begin z-rust&quot;&gt;&amp;lt;&lt;&#x2F;span&gt;Schedule&lt;span class=&quot;z-punctuation z-definition z-generic z-end z-rust&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-begin z-rust&quot;&gt;(&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-rust&quot;&gt;,&lt;&#x2F;span&gt; &lt;span class=&quot;z-constant z-numeric z-integer z-decimal z-rust&quot;&gt;424&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-group z-rust&quot;&gt;&lt;span class=&quot;z-punctuation z-section z-group z-end z-rust&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-terminator z-rust&quot;&gt;;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The literal looks redundant, and it caught a real bug. A change took 32 bytes of the reserved tail to add 24 bytes of new fields. The field sum was updated on both sides and still matched, and the account had silently become 8 bytes smaller. On a live account that would misread every field after the change, and only the literal noticed.&lt;&#x2F;p&gt;
&lt;p&gt;The reserved tail is made of &lt;code&gt;u64&lt;&#x2F;code&gt;, so it can only give up whole 8 byte slots. A 15 byte field costs 16 bytes of reserve, and the arithmetic has to say so.&lt;&#x2F;p&gt;
&lt;p&gt;One more trick saves bytes entirely. Some reserves have an optional strategy with its own settings. Instead of adding optional fields to every reserve, the strategy lives in a separate PDA derived from the reserve&#x27;s address. Whether the strategy is enabled is answered by whether that account exists, which costs zero bytes on the reserve.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;what-it-costs&quot;&gt;What it costs &lt;a class=&quot;zola-anchor&quot; href=&quot;#what-it-costs&quot; aria-label=&quot;Link to this section&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;&lt;strong&gt;Seven allocations.&lt;&#x2F;strong&gt; A curator who wants a 10 asset vault cannot have one until a paginated withdrawal works or the account limit goes up.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;A test that never builds the worst case.&lt;&#x2F;strong&gt; The test at 10 allocations shares one oracle across every leg, so its transaction has fewer unique accounts than a vault whose legs share nothing. A limit test has to build the largest transaction a user can create, on a runtime with the target cluster&#x27;s feature set.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Lookup tables for v0.&lt;&#x2F;strong&gt; Until clients move to v1, every operation that reads the vault&#x27;s value needs a lookup table, and the table has to hold more than the vault&#x27;s own addresses. A table built only from them left a deposit 84 bytes over the limit, because protocol accounts, mints and the depositor&#x27;s token accounts are in the same instructions.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Callers must raise the compute limit.&lt;&#x2F;strong&gt; The default 200k is not enough past three allocations, so a hand built transaction will fail.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Unused layout.&lt;&#x2F;strong&gt; Thirteen allocation slots, twice, sit in every vault account and do nothing until the limit or the withdrawal design changes.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;what-i-check-now&quot;&gt;What I check now &lt;a class=&quot;zola-anchor&quot; href=&quot;#what-i-check-now&quot; aria-label=&quot;Link to this section&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;Constants in an SDK describe what the code supports. Feature gates decide what a cluster enforces. Before a limit becomes a number users depend on, I check three things: the docs for the current value, &lt;code&gt;solana feature status -um&lt;&#x2F;code&gt; for any gate that changes it, and the SIMD for anything that will. Then I run the worst case transaction against a runtime with mainnet&#x27;s feature set.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;what-i-am-studying-next-paginated-withdrawals&quot;&gt;What I am studying next: paginated withdrawals &lt;a class=&quot;zola-anchor&quot; href=&quot;#what-i-am-studying-next-paginated-withdrawals&quot; aria-label=&quot;Link to this section&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;Seven assets is too few for a vault whose whole point is holding several of them. Waiting for SIMD-0596 to raise v1 transactions to 96 accounts would get to 12, and it is still a draft. So I am studying a withdrawal that is no longer one transaction.&lt;&#x2F;p&gt;
&lt;p&gt;I passed on this when I thought the limit was 128 and a cap of 10 was safe. It adds permanent state, and a cap can be raised later while that state can never be removed. Other multi-asset vault designs land in the same range for the same reason: one standard caps at 8 assets, and one basket protocol ran 15 per basket in one transaction before moving to multi-step auctions to go higher. At 7 the trade looks different.&lt;&#x2F;p&gt;
&lt;p&gt;The idea is to burn the shares and freeze what the depositor is owed in each asset in the first transaction, at that moment&#x27;s valuation, so nobody can burn, wait for one asset to move, and claim the rest at a better price. The vault sets those amounts aside as reserved, and later transactions pay them out a few legs at a time, each small enough to fit under 64 accounts.&lt;&#x2F;p&gt;
&lt;p&gt;It is not solved yet, because the first transaction still has to value every leg in the same slot. The refresh takes three accounts per leg (the vault&#x27;s position, the reserve and its oracle) plus a handful of fixed ones, so on its own it can value about 19 legs in one transaction when no two legs share an account. The burn adds its own accounts on top. Paginating the payout moves the ceiling from 7 into the teens. Reaching 20 would mean splitting the valuation too, and that collides with the rule that the valuation must come from the current slot.&lt;&#x2F;p&gt;
&lt;p&gt;The cost is known: every reserve total splits into reserved and available, and valuation, share supply checks, rebalance, swap based rebalance and moving liquidity between reserves all have to respect that split.&lt;&#x2F;p&gt;
&lt;p&gt;The layout is ready for whatever comes out of it. The vault account keeps 20 allocation slots, so raising the active cap is one constant and no migration.&lt;&#x2F;p&gt;
&lt;hr &#x2F;&gt;
&lt;p&gt;&lt;strong&gt;Corrections, 2026-10-12.&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;I wrote that the 128 account lock limit was active on devnet and testnet. Its tracking issue lists those activations, but neither cluster has the feature account today, so 64 is the limit there too.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
</content>
    </entry>
    <entry xml:lang="en">
        <title>Running a quant book on Hyperliquid</title>
        <published>2026-10-05T00:00:00+00:00</published>
        <updated>2026-10-05T00:00:00+00:00</updated>
        <author>
            <name>Gabriel Koerich</name>
        </author>
        <link rel="alternate" type="text/html" href="https://gabrielkoerich.com/posts/running-a-quant-book-on-hyperliquid/"/>
        <id>https://gabrielkoerich.com/posts/running-a-quant-book-on-hyperliquid/</id>
        <summary>A small systematic trading book on Hyperliquid run by one daemon on my Mac: how it is built, what backtests said, what live trading said, and the bugs.</summary>
        <content type="html" xml:base="https://gabrielkoerich.com/posts/running-a-quant-book-on-hyperliquid/">&lt;p&gt;For the last few months I have been running a small systematic trading book on Hyperliquid. It is a handful of strategies, each one on its own subaccount, managed by one daemon on my Mac. It trades crypto perps and, through HIP-3, stock and commodity perps like the S&amp;amp;P 500, gold and oil.&lt;&#x2F;p&gt;
&lt;p&gt;This post covers how it is built, what the backtests said, what live trading said instead, and the bugs that cost me the most. Most of what I learned is not about finance.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;why-hyperliquid&quot;&gt;Why Hyperliquid &lt;a class=&quot;zola-anchor&quot; href=&quot;#why-hyperliquid&quot; aria-label=&quot;Link to this section&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;&lt;strong&gt;It has an API that does not fight you.&lt;&#x2F;strong&gt; Orders, positions, fills and funding are one HTTP call away, and an agent wallet can sign orders without holding the master key.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Subaccounts are free.&lt;&#x2F;strong&gt; Each strategy gets its own account, its own margin and its own P&amp;amp;L. When something goes wrong, the damage stays in one box, and I can read a strategy&#x27;s equity straight from the exchange instead of reconstructing it.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;HIP-3 puts stocks and commodities on the same rails.&lt;&#x2F;strong&gt; The &lt;code&gt;xyz&lt;&#x2F;code&gt; dex lists &lt;code&gt;xyz:SP500&lt;&#x2F;code&gt;, &lt;code&gt;xyz:GOLD&lt;&#x2F;code&gt;, &lt;code&gt;xyz:CL&lt;&#x2F;code&gt; and single stocks next to BTC, on the same API and the same collateral.&lt;&#x2F;p&gt;
&lt;p&gt;That last one is also where most of my bugs came from, so I will come back to it.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-design&quot;&gt;The design &lt;a class=&quot;zola-anchor&quot; href=&quot;#the-design&quot; aria-label=&quot;Link to this section&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;A strategy is a &lt;strong&gt;sleeve&lt;&#x2F;strong&gt;. A sleeve is a Python process running a Nautilus Trader node, pinned to one subaccount. Its capital is whatever equity that subaccount holds.&lt;&#x2F;p&gt;
&lt;p&gt;The sleeves that matter today:&lt;&#x2F;p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Sleeve&lt;&#x2F;th&gt;&lt;th&gt;What it does&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;tsmom&lt;&#x2F;td&gt;&lt;td&gt;Time-series momentum. Long or short each coin by the sign of its 30-day return, on 8h bars, vol targeted&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;xsmomentum&lt;&#x2F;td&gt;&lt;td&gt;Cross-sectional momentum. Long the top 5, short the bottom 5 of a dynamic panel, rebalanced weekly&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;xsmom-xyz&lt;&#x2F;td&gt;&lt;td&gt;The same engine on HIP-3 stocks&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;carry&lt;&#x2F;td&gt;&lt;td&gt;Long spot, short perp, only when funding runs hot for a week&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;qmj&lt;&#x2F;td&gt;&lt;td&gt;Quality minus junk. Long majors, short speculative coins, beta neutral&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;smc&lt;&#x2F;td&gt;&lt;td&gt;Market structure breaks on 1h, gated by the 4h trend&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;dca&lt;&#x2F;td&gt;&lt;td&gt;Weekly BTC buys, plus a leveraged lump when the bull regime confirms&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;hedger&lt;&#x2F;td&gt;&lt;td&gt;Trades one BTC position to pull the whole book&#x27;s beta toward a target set by the market regime&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;p&gt;One daemon sits above them. It starts sleeves as detached processes, so restarting the daemon does not restart the sleeves: they keep trading, and the new daemon adopts them back through pid files. It writes a snapshot of every sleeve every 30 seconds to SQLite, and it pushes a live view to a small SolidJS dashboard over server-sent events.&lt;&#x2F;p&gt;
&lt;p&gt;Everything that acts on live money runs inside that daemon: the killswitches, the sleeve monitor, the daily resolvers that pick which coins each momentum sleeve trades. I run a lot of scheduled agent jobs for this project, and I deliberately kept trading out of them. A job runs in a fresh worktree that can be a few commits behind. A sleeve should never trade on code that is not the code I deployed.&lt;&#x2F;p&gt;
&lt;p&gt;The signing key comes from &lt;a href=&quot;https:&#x2F;&#x2F;gabrielkoerich.com&#x2F;posts&#x2F;a-password-manager-for-agents&#x2F;&quot;&gt;passbox&lt;&#x2F;a&gt;, behind a fingerprint and a grant scoped to the two entries the daemon needs. The credentials module also refuses to trade if the address it resolves is my master account.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;backtesting&quot;&gt;Backtesting &lt;a class=&quot;zola-anchor&quot; href=&quot;#backtesting&quot; aria-label=&quot;Link to this section&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;Every backtest goes through one runner. It uses Nautilus with Hyperliquid&#x27;s retail fees (4.5bp taker, 1.5bp maker) plus 1bp slippage, on a gap-free lake of 1-minute perp candles for BTC, ETH and SOL from 2021. Every run writes a JSON result that a &lt;code&gt;&#x2F;backtests&lt;&#x2F;code&gt; page indexes, so any strategy can be ranked against any other.&lt;&#x2F;p&gt;
&lt;p&gt;I made this a hard rule after the alternative piled up. A one-off polars script that prints a Sharpe ratio feels faster on the night you write it. Two weeks later nobody can compare it to anything, nobody remembers which fees it assumed, and the strategy ships live with numbers that only exist in a terminal scrollback.&lt;&#x2F;p&gt;
&lt;p&gt;Parameter search runs through sweep profiles: a base config pinned to what runs live and a grid of what to vary. Each cell gets a hash, so a re-run overwrites instead of duplicating.&lt;&#x2F;p&gt;
&lt;p&gt;The most important piece came last: a rolling walk-forward. Fit on 12 months, test on the next 6, step forward 6, repeat. Rank by the mean out-of-sample Sharpe and kill anything whose worst out-of-sample drawdown is too deep.&lt;&#x2F;p&gt;
&lt;p&gt;I built it because of one strategy. Crypto carry, a strategy from a 2023 paper that a 2025 replication put at a Sharpe of 6.45. My 2024 in-sample run showed Sharpe 2.45 and +36.7%. Over the full three years it returned −40% with a −53% drawdown, and the walk-forward mean out-of-sample Sharpe was −2.61. A single good year had carried the whole result.&lt;&#x2F;p&gt;
&lt;p&gt;Here is what the backtests got wrong more than once:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;One year carries everything.&lt;&#x2F;strong&gt; The SMC edge was almost all 2025. Without it, the BTC 4h mean Sharpe was negative. The three-sleeve combined Sharpe of 3.43 that I was excited about was mostly that year.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Fees and funding are the strategy.&lt;&#x2F;strong&gt; The first version of the basis carry sleeve lost 60% with a 3% win rate. A round trip cost enough that it needed about six days in the trade to break even, and the exit rule left after about 17 hours. Version two stays in the trade and was positive over 18 months. A funding tilt strategy had a Sharpe of −1.11, all of it fees. Holding BTC perps at 3x for three years loses most of the margin to funding alone.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Picking by recent performance picks noise.&lt;&#x2F;strong&gt; A TSMOM coin panel that looked like the 2025 winner was the worst over 4.5 years. Choosing panels by trailing strategy Sharpe gave a −90% drawdown. Adding two coins to one panel cut its out-of-sample Sharpe from 2.06 to 0.42.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;The simple combiner wins.&lt;&#x2F;strong&gt; Combining sleeves with inverse volatility beat equal weight, and both beat Markowitz. With a handful of sleeves and a few months of data, the covariance estimate is mostly noise, and an optimiser fits that noise.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;what-broke&quot;&gt;What broke &lt;a class=&quot;zola-anchor&quot; href=&quot;#what-broke&quot; aria-label=&quot;Link to this section&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;The backtest tells you whether an idea has an edge. It tells you nothing about whether your system runs the idea. Almost every loss I took that was not market risk came from the second part.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;the-prefix&quot;&gt;The prefix &lt;a class=&quot;zola-anchor&quot; href=&quot;#the-prefix&quot; aria-label=&quot;Link to this section&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;h3&gt;
&lt;p&gt;A HIP-3 coin has a different name depending on who you ask. &lt;code&gt;clearinghouseState&lt;&#x2F;code&gt; with &lt;code&gt;dex=xyz&lt;&#x2F;code&gt; returns &lt;code&gt;xyz:MSFT&lt;&#x2F;code&gt;. My position helper returns &lt;code&gt;MSFT&lt;&#x2F;code&gt; with a separate &lt;code&gt;dex&lt;&#x2F;code&gt; field. Nautilus has its own instrument id. I counted five names for the same perp across the layers.&lt;&#x2F;p&gt;
&lt;p&gt;The stock momentum sleeve had a component that closes positions no longer in the panel. It built &lt;code&gt;xyz:&lt;&#x2F;code&gt; + the coin name, received a name that already had the prefix, and asked for &lt;code&gt;xyz:xyz:MSFT&lt;&#x2F;code&gt;. That matched nothing in the panel, so every HIP-3 position looked stale, and it closed all nine of them five seconds after every start. For about a month.&lt;&#x2F;p&gt;
&lt;p&gt;It stayed hidden because:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;The close path stripped one prefix, so the close orders themselves went through fine&lt;&#x2F;li&gt;
&lt;li&gt;The sleeves had no logging config, so the INFO lines that would have shown it went nowhere&lt;&#x2F;li&gt;
&lt;li&gt;The unit test passed, because its fixture returned a bare &lt;code&gt;SP500&lt;&#x2F;code&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;The test checked that the code agreed with the fixture, and the fixture was wrong. The fix was one module that owns HIP-3 naming, and a rule: before you trust a test at an integration boundary, check its fixture against one real response.&lt;&#x2F;p&gt;
&lt;p&gt;Two dexes also means two of every state query. Any call without &lt;code&gt;dex=&amp;quot;xyz&amp;quot;&lt;&#x2F;code&gt; silently misses HIP-3 positions, orders and stops. The websocket feed I used was main-dex only. My own risk monitor reported zero positions for a sleeve that held fifteen. The SDK needs &lt;code&gt;perp_dexs=[&amp;quot;&amp;quot;, &amp;quot;xyz&amp;quot;]&lt;&#x2F;code&gt; or it raises &lt;code&gt;KeyError&lt;&#x2F;code&gt; on any xyz coin, and my risk executor shipped without it.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;the-empty-panel&quot;&gt;The empty panel &lt;a class=&quot;zola-anchor&quot; href=&quot;#the-empty-panel&quot; aria-label=&quot;Link to this section&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;h3&gt;
&lt;p&gt;The momentum sleeves trade a panel of coins that a resolver picks once a day. One morning Hyperliquid returned a storm of 429s. The resolver caught each error, skipped the coin and moved on. Every coin failed, so it wrote an empty panel, and then marked the day as done.&lt;&#x2F;p&gt;
&lt;p&gt;tsmom read the empty panel as &amp;quot;nothing qualifies&amp;quot; and closed all 13 positions. The &amp;quot;already ran today&amp;quot; guard blocked every retry until UTC midnight.&lt;&#x2F;p&gt;
&lt;p&gt;The fix is in the producer: an all-empty result is suspect. Refuse to write it, keep the last good panel, and alert. A failed run must never satisfy the guard that says the work is done.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;state-saved-before-the-side-effect&quot;&gt;State saved before the side effect &lt;a class=&quot;zola-anchor&quot; href=&quot;#state-saved-before-the-side-effect&quot; aria-label=&quot;Link to this section&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;h3&gt;
&lt;p&gt;The cross-sectional momentum sleeves stopped rebalancing for 12 days. There were four causes. The worst: an order rejection arrived about 3ms after the sleeve had already saved &amp;quot;rebalanced today&amp;quot;, so the next attempt was scheduled seven days out. The hedger had the same shape: it recorded a hedge step when the submit came back with a 429, then waited four hours for the next step.&lt;&#x2F;p&gt;
&lt;p&gt;Both fixes were the same: advance the state only after the side effect is confirmed.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;silence&quot;&gt;Silence &lt;a class=&quot;zola-anchor&quot; href=&quot;#silence&quot; aria-label=&quot;Link to this section&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;h3&gt;
&lt;p&gt;One sleeve froze for eight days without an error. The exchange adapter fetched order status reports in a batch, and one entry with a zero quantity made the whole batch return an empty list. Nothing crashed. The process was alive, and the log file kept being touched.&lt;&#x2F;p&gt;
&lt;p&gt;I added a heartbeat line every five minutes and a watchdog that restarts a sleeve whose heartbeat goes quiet for six hours. On its first day it caught a real ten-hour freeze. I eventually moved the patches for six upstream adapter bugs into one subclass of the Nautilus execution client, so they live in one file with tests instead of as monkey-patches spread across the code.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;stale-data-that-looks-fresh&quot;&gt;Stale data that looks fresh &lt;a class=&quot;zola-anchor&quot; href=&quot;#stale-data-that-looks-fresh&quot; aria-label=&quot;Link to this section&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;h3&gt;
&lt;p&gt;A resolver read candles from the backtest lake instead of the live API. The lake had stopped updating a month earlier. ETH showed a 30-day return of −30% when the real number was +17%, and the sleeve traded on it.&lt;&#x2F;p&gt;
&lt;p&gt;Now every live path that reads a file checks the timestamp of its last row against the clock and fails loudly when it is too old. Backtest helpers do not get reused in live code without that check.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;stops&quot;&gt;Stops &lt;a class=&quot;zola-anchor&quot; href=&quot;#stops&quot; aria-label=&quot;Link to this section&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;h3&gt;
&lt;p&gt;Every position should carry a stop. An audit across 12 subaccounts found 20 positions with a stop and 33 without. One sleeve had piled up 221 orphan stop orders, left behind by positions closed some other way. A trailing stop failed to trail because the code read &lt;code&gt;trigger&lt;&#x2F;code&gt; and the field is &lt;code&gt;triggerPx&lt;&#x2F;code&gt;, which also skipped the guard that stops a stop from ever being loosened.&lt;&#x2F;p&gt;
&lt;p&gt;The rules now: one stop per position, attached to the position, modified in place by order id, never stacked and never loosened. Most HIP-3 stocks are isolated-only at 10x, so liquidation sits around 5% from entry, and a volatility stop that widens to 10% sits well past it. That one is still open.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;the-config-that-lied&quot;&gt;The config that lied &lt;a class=&quot;zola-anchor&quot; href=&quot;#the-config-that-lied&quot; aria-label=&quot;Link to this section&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;h3&gt;
&lt;p&gt;The cross-sectional momentum sleeve ran at 1.8x gross for a month while three documents said 0.8x. The code had &lt;code&gt;leg_pct=0.18&lt;&#x2F;code&gt;, the docs had &lt;code&gt;0.08&lt;&#x2F;code&gt;. I found it when I moved sleeve parameters into a YAML file, because the migration has a test that pins the YAML-built config to a snapshot of the old one.&lt;&#x2F;p&gt;
&lt;p&gt;The parameters live in YAML now. The reasoning behind each value lives in a per-sleeve doc, never in a comment next to the value, because a comment does not move when the value does.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-risk-i-did-not-expect-me&quot;&gt;The risk I did not expect: me &lt;a class=&quot;zola-anchor&quot; href=&quot;#the-risk-i-did-not-expect-me&quot; aria-label=&quot;Link to this section&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;I also trade by hand in the Hyperliquid web app, and I had to admit that the riskiest component in the system was me.&lt;&#x2F;p&gt;
&lt;p&gt;So the daemon watches my main account every 10 seconds and computes a tilt score from 0 to 10. It goes up on adding to losers, opening right after a loss, revenge cycles, trading against the regime, and deep drawdown. At 6 it reduces positions; at 8 it closes longs. Most of it still runs in dry-run, and the only live action is tightening stops.&lt;&#x2F;p&gt;
&lt;p&gt;At 10, a small browser extension redirects the Hyperliquid web app to a lock page. It fails closed: if it cannot reach the daemon, an active lock stays on.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;what-it-costs&quot;&gt;What it costs &lt;a class=&quot;zola-anchor&quot; href=&quot;#what-it-costs&quot; aria-label=&quot;Link to this section&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;&lt;strong&gt;It runs on a laptop.&lt;&#x2F;strong&gt; When the lid closes, the killswitches stop. A Docker setup with a health check and restart policy works end to end, and I have not cut over yet. A dead sleeve once stayed dead for almost three hours because nothing restarted it.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;The guardrails are not all on.&lt;&#x2F;strong&gt; I put the P&amp;amp;L killswitches on hold in late August, and they are still off. Until I turn them back on, a drawdown halts nothing.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Docs drift.&lt;&#x2F;strong&gt; I keep a long learnings file, and the agents that work on this code read it. Parts of it describe files that no longer exist. The doc I wrote about how backtest results are named describes a filename scheme no file on disk uses. A wrong doc is worse than no doc, because an agent will trust it.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Small samples.&lt;&#x2F;strong&gt; The live P&amp;amp;L of each sleeve is weeks or months of data. It shows that the system runs, and says very little about whether the edge is real.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;what-comes-next&quot;&gt;What comes next &lt;a class=&quot;zola-anchor&quot; href=&quot;#what-comes-next&quot; aria-label=&quot;Link to this section&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;&lt;strong&gt;Off the Mac.&lt;&#x2F;strong&gt; Today the whole thing runs on the machine I also work on. The first step is to cut over to the Docker setup, so a crashed daemon comes back without me. The image is also what lets it run anywhere else, which is the real goal.&lt;&#x2F;p&gt;
&lt;p&gt;There are two places it could go. One is a small server at home, which I am already planning for the rest of my agent jobs. The other is a rented box in a datacenter. Home costs nothing per month, but a power cut or an ISP outage at home means positions without a daemon watching them, and for this workload that is a money risk. A datacenter box buys uptime that my house cannot. I have not decided, and the first week on either will run with the Mac as a standby.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;The key on a server.&lt;&#x2F;strong&gt; Neither box has a fingerprint sensor. passbox can already ask my Mac to approve over Tailscale, but then the trading box depends on my Mac being awake, and that is the dependency I am trying to remove. I have not decided how this works yet.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;One connection.&lt;&#x2F;strong&gt; Most of the 429 storms came from many sleeves and the daemon each polling REST for the same state. One shared websocket client, read by everyone, removes most of that traffic.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Research that is not live yet.&lt;&#x2F;strong&gt; A z-score gate for TSMOM, which skips weak signals, took its 4.5-year Sharpe from 0.32 to about 1.0 in the backtest. It is not in the live config. A walk-forward that refits parameters in each window is also not built. And the killswitches need to come back on, with thresholds that match what each strategy does on a normal bad week.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Open sourcing it.&lt;&#x2F;strong&gt; I am thinking about it. Most of the hard parts have nothing to do with which strategy you run: an execution client that fixes six Nautilus bugs on Hyperliquid, one module for HIP-3 names, the sweep and walk-forward harness, the tilt monitor and the browser lock. Getting it there means cleaning out a lot of code that only makes sense on my machine.&lt;&#x2F;p&gt;
&lt;p&gt;If you run Nautilus on Hyperliquid, or would use any of those parts, tell me. That will decide what I publish first.&lt;&#x2F;p&gt;
&lt;hr &#x2F;&gt;
&lt;p&gt;&lt;strong&gt;Corrections, 2026-10-12.&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Liquidation at 10x isolated sits around 5% from entry, not 9.5%: Hyperliquid&#x27;s maintenance margin is half the initial margin at max leverage.&lt;&#x2F;li&gt;
&lt;li&gt;The 6.45 Sharpe comes from a 2025 replication, the 2023 paper is the source of the strategy.&lt;&#x2F;li&gt;
&lt;li&gt;The walk-forward moves its training window forward each step, which is a rolling walk-forward, so I stopped calling it anchored.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
</content>
    </entry>
    <entry xml:lang="en">
        <title>The alarm was dead and every monitor was green</title>
        <published>2026-10-02T00:00:00+00:00</published>
        <updated>2026-10-02T00:00:00+00:00</updated>
        <author>
            <name>Gabriel Koerich</name>
        </author>
        <link rel="alternate" type="text/html" href="https://gabrielkoerich.com/posts/the-alarm-was-dead-and-every-monitor-was-green/"/>
        <id>https://gabrielkoerich.com/posts/the-alarm-was-dead-and-every-monitor-was-green/</id>
        <summary>My home alarm was dead while every monitor showed green. What I was checking wrong, why silence has to be loud, and the failures that look like success.</summary>
        <content type="html" xml:base="https://gabrielkoerich.com/posts/the-alarm-was-dead-and-every-monitor-was-green/">&lt;p&gt;The house was broken into once. The people who did it cut the power and waited for the backup to run out. That detail is the whole reason I stopped treating the security system as a gadget and started treating it as something that has to work when nobody is watching it.&lt;&#x2F;p&gt;
&lt;p&gt;So there is a small server at home now. Home Assistant runs the house, a recorder holds the cameras, an alarm panel covers the doors and the perimeter, a Zigbee mesh handles contacts and switches, and a machine with a GPU runs qwen3-vl, a vision model, that looks at motion clips and says what is in them. No camera footage goes to a vendor cloud. A monitoring service runs on a server outside the house, because a house cannot report its own blackout.&lt;&#x2F;p&gt;
&lt;p&gt;I spent a day rewiring part of it. By the end, the alarm had been unreachable for hours, the cameras had stopped analysing anything, and the dashboard was green the entire time.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;everything-was-green-because-i-was-checking-the-wrong-thing&quot;&gt;Everything was green because I was checking the wrong thing &lt;a class=&quot;zola-anchor&quot; href=&quot;#everything-was-green-because-i-was-checking-the-wrong-thing&quot; aria-label=&quot;Link to this section&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;The alarm monitor checked that the panel&#x27;s port accepted a TCP connection. It did, all day, but it never answered.&lt;&#x2F;p&gt;
&lt;p&gt;I found it by replaying the panel&#x27;s own protocol by hand: connect, send the auth packet, read the reply. The connection succeeded and the panel returned zero bytes, then timed out. A wrong password gives you a rejection. Silence means the listener is up and the service behind it is dead. The network module had come back from a power cut with its TCP accept loop running and nothing behind it.&lt;&#x2F;p&gt;
&lt;p&gt;The same shape had already bitten me that morning with the vision model. Its status endpoint answered instantly while every single inference hung forever. The monitor polled the status endpoint.&lt;&#x2F;p&gt;
&lt;p&gt;Both monitors were honest. They were measuring whether something was listening, which is almost never the question you care about.&lt;&#x2F;p&gt;
&lt;p&gt;So I replaced them. The alarm check now reads the panel&#x27;s state through Home Assistant, which fails if the panel stops answering. The vision check asks the model to actually generate two tokens. The recorder check makes an HTTP request and accepts a 401, because a 401 proves the service parsed the request and decided to challenge, while a dead box refuses or times out.&lt;&#x2F;p&gt;
&lt;p&gt;The best one crosses machines. The vision chain is only healthy if the Mac can still pull frames from the recorder, so that check fetches a frame and requires it to be a JPEG over a certain size. One check, three machines, and it fails if any link between them breaks. An empty 200 does not pass.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;silence-is-ambiguous-and-you-have-to-make-it-loud&quot;&gt;Silence is ambiguous and you have to make it loud &lt;a class=&quot;zola-anchor&quot; href=&quot;#silence-is-ambiguous-and-you-have-to-make-it-loud&quot; aria-label=&quot;Link to this section&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;One camera runs on battery. It sleeps almost all the time and wakes on motion. From the network it is indistinguishable from a camera that is dead, out of battery, or stolen.&lt;&#x2F;p&gt;
&lt;p&gt;The same problem shows up everywhere once you look. A door sensor that only transmits when the door opens. A job that pulls clips and finds none. A detection pipeline with nothing to detect. In every case &amp;quot;no news&amp;quot; has two readings and only one of them is good.&lt;&#x2F;p&gt;
&lt;p&gt;The fix is to decide in advance how long silence is allowed to last. The camera watcher raises an alert when the silence goes past that limit. A rejected password used to return the same &amp;quot;no connection&amp;quot; as a sleeping camera, so now it logs and notifies separately, because a stale credential should never look like a quiet night.&lt;&#x2F;p&gt;
&lt;p&gt;Related trap: a Zigbee device being joined and a Zigbee device being reachable are different facts. The bridge happily reported twelve devices joined while only two were in contact. Joined is a stored relationship. Reachable is a measurement. Reading the first and believing the second cost me an hour.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;telling-a-model-what-it-is-looking-at-makes-it-ignore-what-it-sees&quot;&gt;Telling a model what it is looking at makes it ignore what it sees &lt;a class=&quot;zola-anchor&quot; href=&quot;#telling-a-model-what-it-is-looking-at-makes-it-ignore-what-it-sees&quot; aria-label=&quot;Link to this section&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;The clip pipeline pulls a motion recording off the camera, extracts a frame, and asks a local vision model to describe it in one sentence.&lt;&#x2F;p&gt;
&lt;p&gt;My first prompt said where the camera was mounted and what it was looking at. Shown a frame with a person filling most of it, the model answered that no subjects were visible, and that this appeared to be an interior shot of a person seated indoors.&lt;&#x2F;p&gt;
&lt;p&gt;It saw the person. It then dismissed the person for failing to match the scene I had promised it. I had written the filter myself.&lt;&#x2F;p&gt;
&lt;p&gt;The same model, same frame, with the scene description removed, answered in one second: &amp;quot;a bearded man in a grey sweatshirt is seated in a chair, holding a phone&amp;quot;.&lt;&#x2F;p&gt;
&lt;p&gt;Context that sounds helpful becomes a filter that suppresses anything unexpected, and unexpected is the entire job of a security camera. The prompt now asks whether a person is present and then for one sentence. Nothing about where the camera is.&lt;&#x2F;p&gt;
&lt;p&gt;I pulled this part out into &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;gabrielkoerich&#x2F;vlm-security&quot;&gt;vlm-security&lt;&#x2F;a&gt;, which is the smallest version of the idea: camera motion comes in, a local model decides whether it is worth raising an alarm, and nothing leaves the house. That repo is the piece I would start from if I were building this again, because deciding whether motion matters at all is the part that makes the rest worth having.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;mermaid&quot; class=&quot;language-mermaid z-code&quot;&gt;&lt;code class=&quot;language-mermaid&quot; data-lang=&quot;mermaid&quot;&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;graph TB
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;    DVR[&amp;quot;&amp;lt;b&amp;gt;DVR&amp;lt;&#x2F;b&amp;gt;&amp;lt;br&#x2F;&amp;gt;cameras + motion detection&amp;quot;]
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;    DVR --&amp;gt;|&amp;quot;RTSP streams&amp;quot;| RING[&amp;quot;&amp;lt;b&amp;gt;frame_ring.py&amp;lt;&#x2F;b&amp;gt;&amp;lt;br&#x2F;&amp;gt;rolling buffer per camera&amp;quot;]
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;    DVR --&amp;gt;|&amp;quot;motion events&amp;quot;| HA[&amp;quot;&amp;lt;b&amp;gt;Home Assistant&amp;lt;&#x2F;b&amp;gt;&amp;lt;br&#x2F;&amp;gt;motion as binary_sensor&amp;quot;]
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;    HA --&amp;gt;|&amp;quot;polled every 2s&amp;quot;| BR[&amp;quot;&amp;lt;b&amp;gt;bridge.py&amp;lt;&#x2F;b&amp;gt;&amp;lt;br&#x2F;&amp;gt;gather, ask, decide&amp;quot;]
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;    RING --&amp;gt;|&amp;quot;frames around the event&amp;quot;| BR
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;    BR --&amp;gt;|&amp;quot;base64 frames + prompt&amp;quot;| VLM[&amp;quot;&amp;lt;b&amp;gt;Ollama&amp;lt;&#x2F;b&amp;gt;&amp;lt;br&#x2F;&amp;gt;vision model, local&amp;quot;]
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;    VLM --&amp;gt;|&amp;quot;PERSON &#x2F; VEHICLE &#x2F; COLOR &#x2F; WHERE&amp;quot;| BR
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;    BR --&amp;gt;|&amp;quot;on the property&amp;quot;| ACT[&amp;quot;alarm or notify&amp;lt;br&#x2F;&amp;gt;back through Home Assistant&amp;quot;]
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;    BR --&amp;gt;|&amp;quot;on the street&amp;quot;| LOG[&amp;quot;log only&amp;quot;]
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;    BR -.-&amp;gt;|&amp;quot;model unreachable&amp;quot;| ACT
&lt;&#x2F;span&gt;&lt;span class=&quot;z-text z-plain&quot;&gt;    DET[&amp;quot;&amp;lt;b&amp;gt;detector_service.py&amp;lt;&#x2F;b&amp;gt;&amp;lt;br&#x2F;&amp;gt;YOLO over a burst&amp;quot;] -.-&amp;gt;|&amp;quot;optional second opinion&amp;quot;| BR
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The dotted line from the model back to the alarm is the part worth copying. If the vision host cannot be reached while the system is armed, the alarm is raised anyway, so a dead model never turns into a disabled alarm.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-failures-that-look-like-success&quot;&gt;The failures that look like success &lt;a class=&quot;zola-anchor&quot; href=&quot;#the-failures-that-look-like-success&quot; aria-label=&quot;Link to this section&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;Three of these cost me real time, and they are all the same kind: something reported that it had worked.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;A download that returned the acknowledgement.&lt;&#x2F;strong&gt; Pulling clips off the camera failed silently because I had two protocol opcodes backwards. The device replied with a cheerful success code and sent no video. A successful handshake followed by nothing reads like a permissions problem, so the protocol was the last thing I suspected. Then the sixty eight byte error acknowledgement got written to disk as a clip, and sixty eight is not zero, so my &amp;quot;did it download&amp;quot; check passed and marked the clip done. Anything under twenty kilobytes is now a failure.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;A write that never arrived.&lt;&#x2F;strong&gt; I set a motor&#x27;s run time and the dashboard showed the new value. The radio was busy at that moment, the write never reached the device, and the screen was showing me what I had asked for instead of what the device had. A reboot resynced it to the real value and the change was gone. Now I confirm the device echoed it back.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;A restart that did not restart.&lt;&#x2F;strong&gt; Twice I was told the machine had been rebooted. Uptime said otherwise both times. Restarting the application and rebooting the host are different buttons that sound the same, and only one of them resets the radio I was trying to fix.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;things-that-come-back-and-the-one-that-did-not&quot;&gt;Things that come back, and the one that did not &lt;a class=&quot;zola-anchor&quot; href=&quot;#things-that-come-back-and-the-one-that-did-not&quot; aria-label=&quot;Link to this section&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;Everything has to start itself, because the night this matters the power will be out and nobody will be there to bring it back by hand. Most of it does.&lt;&#x2F;p&gt;
&lt;p&gt;One bridge never did. It had a retry loop around its work, which looked like enough. It was not, because the crash happened at import: the thing reads a token when the module loads, and at boot that token is not ready yet. The process died before its own loop existed. The startup script ran it once, so it stayed dead until I noticed, which is a bad property for the thing watching your front door.&lt;&#x2F;p&gt;
&lt;p&gt;A loop inside a process cannot catch a failure that happens before the loop runs. It is now wrapped in a shell loop that relaunches it, which covers both.&lt;&#x2F;p&gt;
&lt;p&gt;Same problem, harder to spot: after a reboot, the recorder&#x27;s basic motion events resumed on their own but its human detection events did not. The AI pipeline triggers only on human detection. So every entity looked available, basic motion ticked away, and the thing that actually reads the cameras had been blind for hours. That one now reloads itself shortly after every start.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;what-it-costs&quot;&gt;What it costs &lt;a class=&quot;zola-anchor&quot; href=&quot;#what-it-costs&quot; aria-label=&quot;Link to this section&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;&lt;strong&gt;Local only means you are the operator.&lt;&#x2F;strong&gt; No vendor is watching this at 3am. When the mesh dropped, nobody paged me, I just happened to look.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Battery cameras cannot be summoned.&lt;&#x2F;strong&gt; I checked the protocol, the firmware, and two open source reimplementations. There is no local wake command and no magic packet. The vendor app can wake it through its cloud, but on the local network a sleeping camera answers nothing. You can only be ready for when it appears. If you want a camera that is always there, run a cable.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Every address will move.&lt;&#x2F;strong&gt; The recorder, the alarm panel and the solar logger have all changed address on me, one of them four times in a day. Anything that matters gets found by hardware address or pinned, never typed into a config file.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Resistive heating will eat your backup.&lt;&#x2F;strong&gt; One room was on a temporary battery while I worked. I switched on an electric heater and took the whole room down, including the server and the Zigbee coordinator. Then I spent an hour debugging the software that had been perfectly fine.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;what-i-would-tell-myself-at-the-start&quot;&gt;What I would tell myself at the start &lt;a class=&quot;zola-anchor&quot; href=&quot;#what-i-would-tell-myself-at-the-start&quot; aria-label=&quot;Link to this section&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;Build the monitoring to fail when the thing stops being useful, not when a port closes. That is the only part of this that applies anywhere else.&lt;&#x2F;p&gt;
&lt;p&gt;The rest is specific and a bit boring: identify devices by hardware address, decide how much silence is allowed before you call it a fault, make anything unattended restart itself including when it dies on the first line, and do not tell a model what it is about to see.&lt;&#x2F;p&gt;
&lt;p&gt;And finish the physical work before you debug the software. I lost most of an afternoon chasing a dead Zigbee mesh that was really a heater, an overloaded battery, and a room that kept losing power while I theorised about radio interference.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;where-this-goes-next&quot;&gt;Where this goes next &lt;a class=&quot;zola-anchor&quot; href=&quot;#where-this-goes-next&quot; aria-label=&quot;Link to this section&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;The model doing the describing is &lt;code&gt;qwen3-vl:4b-instruct&lt;&#x2F;code&gt; through Ollama. It is a general model doing one very specific job, which is why the prompt mattered so much. It has never seen this house. It does not know which patch of ground is the street and which is the drive, so I tell it in a prompt every time, and that is exactly the paragraph that made it ignore a person standing in front of it.&lt;&#x2F;p&gt;
&lt;p&gt;Every event already saves the frame and what the model said about it. After a few months that is a record of what actually happens at one house, at every hour, in every light, including the boring frames where nothing happened, which are the ones a general model never trains on.&lt;&#x2F;p&gt;
&lt;p&gt;So the plan is to stop renting a general model and build a small one on that. Trained on this view, it would not need to be told where the street is, because it would have seen the street ten thousand times.&lt;&#x2F;p&gt;
&lt;p&gt;I will write about the small model when there is something to show. Until then, the MVP of the idea is here: &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;gabrielkoerich&#x2F;vlm-security&quot;&gt;github.com&#x2F;gabrielkoerich&#x2F;vlm-security&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;hr &#x2F;&gt;
&lt;p&gt;&lt;strong&gt;Corrections, 2026-10-12.&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;I wrote that a battery camera has no wake command at all. The vendor app can wake it through its cloud, what does not exist is a local wake, so I corrected the sentence.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
</content>
    </entry>
    <entry xml:lang="en">
        <title>A password manager for agents</title>
        <published>2026-09-29T00:00:00+00:00</published>
        <updated>2026-09-29T00:00:00+00:00</updated>
        <author>
            <name>Gabriel Koerich</name>
        </author>
        <link rel="alternate" type="text/html" href="https://gabrielkoerich.com/posts/a-password-manager-for-agents/"/>
        <id>https://gabrielkoerich.com/posts/a-password-manager-for-agents/</id>
        <summary>My agents read secrets from pass, and one passphrase opened everything. Why I built passbox, the alternatives I rejected and what I got wrong first.</summary>
        <content type="html" xml:base="https://gabrielkoerich.com/posts/a-password-manager-for-agents/">&lt;p&gt;I have twenty-three scheduled jobs that run coding agents against my finance project. They read API keys, an exchange signing key, a mail password. They got those from &lt;code&gt;pass&lt;&#x2F;code&gt;, which works like this: one GPG passphrase opens everything, the agent caches it, and from then on the agent can read every secret I own.&lt;&#x2F;p&gt;
&lt;p&gt;That was fine while I was the only one reading secrets. Now an agent reads them, and its transcript gets sent to a model provider.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-problem&quot;&gt;The problem &lt;a class=&quot;zola-anchor&quot; href=&quot;#the-problem&quot; aria-label=&quot;Link to this section&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;&lt;strong&gt;An agent that needs one secret reads the whole store.&lt;&#x2F;strong&gt; Nothing scopes a GPG passphrase to one file. Once the agent has it, &lt;code&gt;pass show anything&lt;&#x2F;code&gt; works. So &amp;quot;this job needs one API key&amp;quot; really means &amp;quot;this job can read everything I own&amp;quot;.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;The value lands in the context.&lt;&#x2F;strong&gt; &lt;code&gt;pass show api&#x2F;key&lt;&#x2F;code&gt; prints to stdout, the agent captures stdout, and now the secret is in a transcript that gets logged, retried, and sent to a model, and you cannot un-send it. A &lt;code&gt;.env&lt;&#x2F;code&gt; file is the same problem with fewer steps.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Nobody can say what was read.&lt;&#x2F;strong&gt; There is no record of which agent opened which secret at which time. When something leaks, the investigation starts at &amp;quot;well, it had access to everything&amp;quot;.&lt;&#x2F;p&gt;
&lt;p&gt;I also found hardcoded passwords in my own repo while looking into this, which is the usual way these things go.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-alternatives-and-why-i-did-not-use-them&quot;&gt;The alternatives, and why I did not use them &lt;a class=&quot;zola-anchor&quot; href=&quot;#the-alternatives-and-why-i-did-not-use-them&quot; aria-label=&quot;Link to this section&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;pass&lt;&#x2F;code&gt;&lt;&#x2F;strong&gt; is what I was already running and still run. It is a decade old, packaged everywhere, and losing your laptop is survivable by design. But entry names are plaintext filenames, and they show up in git commit subjects too. One passphrase opens everything, and it has no idea who is asking.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;A &lt;code&gt;.env&lt;&#x2F;code&gt; file&lt;&#x2F;strong&gt; puts values on disk in the clear, which is why it is gitignored, and why it ends up being pasted into chat when a colleague needs it.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;1Password&#x27;s CLI&lt;&#x2F;strong&gt; is good and I use it daily for SSH. It is built for a human at a keyboard. A signed-in session can read every item the account can see. &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;www.1password.dev&#x2F;cli&#x2F;reference&#x2F;commands&#x2F;run&#x2F;&quot;&gt;&lt;code&gt;op run&lt;&#x2F;code&gt;&lt;&#x2F;a&gt; can put secrets into one process, and a &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;www.1password.dev&#x2F;service-accounts&#x2F;&quot;&gt;service account&lt;&#x2F;a&gt; can be limited to chosen vaults. What I wanted and did not find was a fingerprint for one secret, asked for by one named agent.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Vault&lt;&#x2F;strong&gt; solves scoping properly and costs a server, a policy language, and an operator. For a laptop and a handful of scheduled jobs it is the wrong size. And you still end up with a token that, once leaked, opens what it was scoped to.&lt;&#x2F;p&gt;
&lt;p&gt;I still use two of them, but they were built for people. What changed is that the thing asking for a secret is now a program, and its memory is a transcript that goes somewhere else.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;what-i-built&quot;&gt;What I built &lt;a class=&quot;zola-anchor&quot; href=&quot;#what-i-built&quot; aria-label=&quot;Link to this section&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;passbox.gabrielkoerich.com&quot;&gt;passbox&lt;&#x2F;a&gt; is a password store where a fingerprint releases one or more secrets into one process.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;bash&quot; class=&quot;language-bash z-code&quot;&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;z-source z-shell z-bash&quot;&gt;&lt;span class=&quot;z-meta z-function-call z-shell&quot;&gt;&lt;span class=&quot;z-variable z-function z-shell&quot;&gt;passbox&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-shell&quot;&gt; exec&lt;span class=&quot;z-variable z-parameter z-option z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-parameter z-shell&quot;&gt; --&lt;&#x2F;span&gt;env&lt;&#x2F;span&gt; GITHUB_TOKEN=github&#x2F;token&lt;span class=&quot;z-keyword z-operator z-end-of-options z-shell&quot;&gt; --&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-shell&quot;&gt; gh api &#x2F;user&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;macOS raises a Touch ID prompt naming the agent and the secret. The value goes from the broker into that child process. It is never in the agent&#x27;s stdout, transcript, or tool result.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Entry names live inside the ciphertext.&lt;&#x2F;strong&gt; Files on disk are random ids, so the store leaks how many secrets you have and nothing else. No &lt;code&gt;github&#x2F;token&lt;&#x2F;code&gt; in a filename or a commit subject.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;The key is sealed to the Secure Enclave.&lt;&#x2F;strong&gt; There is no passphrase by default, so there is no file anyone can carry off and grind. The cost is real and I will come back to it.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Every release is audited&lt;&#x2F;strong&gt;, encrypted, one record per line: which agent, which secret, when, and what the decision was.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;The MCP server has no &lt;code&gt;get_secret&lt;&#x2F;code&gt; tool.&lt;&#x2F;strong&gt; It has &lt;code&gt;list_secrets&lt;&#x2F;code&gt;, which returns names, and &lt;code&gt;run_with_secret&lt;&#x2F;code&gt;, which runs your command and scrubs the value out of the output.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-part-i-got-wrong-first&quot;&gt;The part I got wrong first &lt;a class=&quot;zola-anchor&quot; href=&quot;#the-part-i-got-wrong-first&quot; aria-label=&quot;Link to this section&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;My first version prompted per read, with a five-minute approval window. It worked, but a trading job at 3am cannot answer a fingerprint prompt.&lt;&#x2F;p&gt;
&lt;p&gt;I tried a longer window, then a per-secret lease held in the broker, then a scoped bearer token. The token is a good mechanism and it shipped, but it still did not solve the problem, because a token expires and renewing one needs a human.&lt;&#x2F;p&gt;
&lt;p&gt;The answer was to stop reading at runtime:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;bash&quot; class=&quot;language-bash z-code&quot;&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span class=&quot;z-source z-shell z-bash&quot;&gt;&lt;span class=&quot;z-meta z-function-call z-shell&quot;&gt;&lt;span class=&quot;z-variable z-function z-shell&quot;&gt;passbox&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-shell&quot;&gt; exec&lt;span class=&quot;z-variable z-parameter z-option z-shell&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-parameter z-shell&quot;&gt; --&lt;&#x2F;span&gt;env&lt;&#x2F;span&gt; SIGNING_KEY=acme&#x2F;signing-key&lt;span class=&quot;z-keyword z-operator z-end-of-options z-shell&quot;&gt; --&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-arguments z-shell&quot;&gt; .&#x2F;daemon&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;One approval at launch, the value in the process environment, every child inherits it, and nothing calls passbox again for the life of the process. My daemon went from a fingerprint per order to zero reads in sixty seconds of live trading. I deleted the lease. Two mechanisms for one job means the docs start disagreeing with the code.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;what-it-costs&quot;&gt;What it costs &lt;a class=&quot;zola-anchor&quot; href=&quot;#what-it-costs&quot; aria-label=&quot;Link to this section&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;&lt;strong&gt;A machine with no sensor cannot approve.&lt;&#x2F;strong&gt; A Linux box can ask my Mac over Tailscale and the prompt appears here. I tested that from a Raspberry Pi and from a fly.io machine in another region. But the Mac has to be awake, unlocked, and lid open. A closed lid fails. The error says so and suggests opening the lid or using the recovery passphrase.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Unattended means no human gate.&lt;&#x2F;strong&gt; Injection and tokens both hand a value to something that runs without you. What you keep is that the value never enters the agent&#x27;s context, every release is recorded, and a grant covers named secrets instead of the whole store.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;The agent name is self-declared.&lt;&#x2F;strong&gt; The broker cannot verify it, because the process on the socket is always &lt;code&gt;passbox&lt;&#x2F;code&gt; with the real caller somewhere up the tree. You can trust the secret name in the prompt. Treat the agent name as a label.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Lose the Mac and the secrets are gone&lt;&#x2F;strong&gt;, unless you turned sync on. &lt;code&gt;pass&lt;&#x2F;code&gt; gets this right by default and passbox does not.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;It is young and unreviewed.&lt;&#x2F;strong&gt; Cross-implementation tests raise confidence. Python&#x27;s &lt;code&gt;cryptography&lt;&#x2F;code&gt; opens what CryptoKit sealed, and the reference &lt;code&gt;age&lt;&#x2F;code&gt; CLI reads the store.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;why-bother&quot;&gt;Why bother &lt;a class=&quot;zola-anchor&quot; href=&quot;#why-bother&quot; aria-label=&quot;Link to this section&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;The other option is to hand an agent the keys to everything and hope the transcript never leaks.&lt;&#x2F;p&gt;
&lt;p&gt;So I use passbox for the secrets my agents touch, &lt;code&gt;pass&lt;&#x2F;code&gt; for the ones I cannot afford to lose, and 1Password for the ones I need on my phone. passbox only does the one job: it sits between an agent and a credential.&lt;&#x2F;p&gt;
&lt;p&gt;If you run agents on your own machine, I would like to know whether this holds up for you.&lt;&#x2F;p&gt;
&lt;p&gt;If you want to understand it better, here is the &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;passbox.gabrielkoerich.com&#x2F;design&#x2F;&quot;&gt;design&lt;&#x2F;a&gt; and here&#x27;s the &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;passbox.gabrielkoerich.com&#x2F;roadmap&#x2F;&quot;&gt;roadmap and future ideas&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;Source and docs: &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;passbox.gabrielkoerich.com&quot;&gt;passbox.gabrielkoerich.com&lt;&#x2F;a&gt; · &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;gabrielkoerich&#x2F;passbox&quot;&gt;github.com&#x2F;gabrielkoerich&#x2F;passbox&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;hr &#x2F;&gt;
&lt;p&gt;&lt;strong&gt;Corrections, 2026-10-06.&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;From &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;gabrielkoerich&#x2F;passbox&#x2F;releases&#x2F;tag&#x2F;v0.13.12&quot;&gt;v0.13.12&lt;&#x2F;a&gt; to v0.13.42, passbox kept a plaintext list of entry names at &lt;code&gt;~&#x2F;.passbox&#x2F;names&lt;&#x2F;code&gt; so that &lt;code&gt;ls&lt;&#x2F;code&gt; needed no key. It never synced, but any process running as the user could read it, which broke the promise above that names live only inside the ciphertext. &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;gabrielkoerich&#x2F;passbox&#x2F;releases&#x2F;tag&#x2F;v0.13.43&quot;&gt;v0.13.43&lt;&#x2F;a&gt; removed it (&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;gabrielkoerich&#x2F;passbox&#x2F;commit&#x2F;72e46cd&quot;&gt;72e46cd&lt;&#x2F;a&gt;), so listing now needs approval and is audited like a read.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;gabrielkoerich&#x2F;passbox&#x2F;releases&#x2F;tag&#x2F;v0.13.44&quot;&gt;v0.13.44&lt;&#x2F;a&gt; also hardened the broker: decrypted secrets are wiped from memory after use, it refuses a debugger, and a crash writes no core dump (&lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;gabrielkoerich&#x2F;passbox&#x2F;commit&#x2F;ff0b994&quot;&gt;ff0b994&lt;&#x2F;a&gt;).&lt;&#x2F;li&gt;
&lt;li&gt;The 1Password paragraph said there is no &amp;quot;this one secret, for this one process&amp;quot;. &lt;code&gt;op run&lt;&#x2F;code&gt; does inject secrets into a single process, so I corrected it.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;&lt;strong&gt;Corrections, 2026-10-12.&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;I wrote that a fingerprint releases one secret. A process can ask for several and approve them with one prompt that lists them all, and it still cannot open any secret it did not ask for.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
</content>
    </entry>
    <entry xml:lang="en">
        <title>Orchestrating agents for humans</title>
        <published>2026-07-05T00:00:00+00:00</published>
        <updated>2026-07-05T00:00:00+00:00</updated>
        <author>
            <name>Gabriel Koerich</name>
        </author>
        <link rel="alternate" type="text/html" href="https://gabrielkoerich.com/posts/orchestrating-agents-for-humans/"/>
        <id>https://gabrielkoerich.com/posts/orchestrating-agents-for-humans/</id>
        <summary>I was the slow part of running Claude Code, Codex and OpenCode side by side, so I built orch: why Rust, the design, what broke and what it costs.</summary>
        <content type="html" xml:base="https://gabrielkoerich.com/posts/orchestrating-agents-for-humans/">&lt;p&gt;In February I was running Claude Code, Codex and OpenCode side by side in a grid of terminal tabs. Each one needed me to paste the issue, point it at the right branch, wait, read the diff, push, open the PR, and then remember which tab was doing what. The agents were fast. I was the slow part, and I was mostly doing clerical work.&lt;&#x2F;p&gt;
&lt;p&gt;I also wanted to test every model on real work. A new one ships every few weeks, each vendor is best at something different, and any of them can change its price, its limits or its terms tomorrow. I did not want to be tied to one of them. That is why orch does not care which agent runs a task: Claude, Codex, OpenCode, Kimi, MiniMax and GLM all go through the same pipeline, and switching models or dropping an agent is a config change.&lt;&#x2F;p&gt;
&lt;p&gt;So I wrote &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;gabrielkoerich&#x2F;orch&quot;&gt;orch&lt;&#x2F;a&gt;. It is a service that runs on my Mac, reads GitHub issues, picks an agent and a model for each one, runs the agent in its own git worktree, opens the PR, has a different agent review it, and merges it when CI passes. Since March it has closed almost 5,000 tasks across about 12,500 agent runs, split between Claude, OpenCode, Codex, MiniMax, Kimi and GLM.&lt;&#x2F;p&gt;
&lt;p&gt;Agents are tools. Orch is how one person uses many of them at once and still decides what ships. It takes a project from an issue to a merged PR without me, and it runs scheduled jobs with the right context for each one. Most days it feels like magic. This post is about the parts that are not magic: the design, and the bugs it took to get there.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-problems&quot;&gt;The problems &lt;a class=&quot;zola-anchor&quot; href=&quot;#the-problems&quot; aria-label=&quot;Link to this section&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;&lt;strong&gt;I was the scheduler.&lt;&#x2F;strong&gt; Every agent waited on me to give it work, and every finished agent waited on me to ship it. Three agents in parallel meant three times the context switching.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Agents share one checkout badly.&lt;&#x2F;strong&gt; Two agents in the same directory overwrite each other&#x27;s files, fight over the branch, and leave a state that neither of them understands.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Every vendor fails differently.&lt;&#x2F;strong&gt; One rate limits with a &amp;quot;try again at&amp;quot; timestamp. One runs out of credits. One renames a model and returns &amp;quot;model not found&amp;quot;. One goes silent and never exits. A single agent setup breaks on all of these.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Recurring work needs context.&lt;&#x2F;strong&gt; A nightly job that says &amp;quot;check the ledger&amp;quot; is useless unless the agent knows what the ledger is, what the rules of the repo are, and what went wrong last night.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;why-rust&quot;&gt;Why Rust &lt;a class=&quot;zola-anchor&quot; href=&quot;#why-rust&quot; aria-label=&quot;Link to this section&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;The &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;gabrielkoerich&#x2F;orchestrator-sh&quot;&gt;first version&lt;&#x2F;a&gt; was almost all bash, with a few small Python helpers for prompt templates, JSON parsing and cron matching. It worked for one project and a few tasks a day. Then it started to show its limits: every task started a Python process eight or more times just to render prompts, the scripts read the YAML config from disk on every call, and two ticks could dispatch the same task twice. In late February I rewrote it in Rust.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;One binary.&lt;&#x2F;strong&gt; Homebrew installs it and launchd runs it. There is no Python, &lt;code&gt;yq&lt;&#x2F;code&gt; or script directory that has to be on the right path when the service starts at boot.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;One process for many slow things.&lt;&#x2F;strong&gt; The tick loop, GitHub polling, tmux output capture every two seconds, the Discord websocket and the Telegram bot all run on Tokio in one process. Most of what orch does is wait on something, and async Rust is good at waiting.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;The compiler catches mistakes early.&lt;&#x2F;strong&gt; A task status is an enum, so a new status breaks every &lt;code&gt;match&lt;&#x2F;code&gt; that forgot it. CI runs clippy with warnings as errors. Many bugs fail the build before anyone reviews the diff.&lt;&#x2F;p&gt;
&lt;p&gt;The cost is that a change goes through a compile and a release before the service runs it. With a script I could edit a line and see the result on the next tick.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-design&quot;&gt;The design &lt;a class=&quot;zola-anchor&quot; href=&quot;#the-design&quot; aria-label=&quot;Link to this section&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;&lt;strong&gt;GitHub issues hold the state.&lt;&#x2F;strong&gt; A task is an issue, and its status is a label: &lt;code&gt;new&lt;&#x2F;code&gt;, &lt;code&gt;routed&lt;&#x2F;code&gt;, &lt;code&gt;in_progress&lt;&#x2F;code&gt;, &lt;code&gt;needs_review&lt;&#x2F;code&gt;, &lt;code&gt;in_review&lt;&#x2F;code&gt;, &lt;code&gt;done&lt;&#x2F;code&gt;, &lt;code&gt;blocked&lt;&#x2F;code&gt;. I can see and change the state of everything from my phone. Work that does not deserve an issue, like a scheduled report, is an internal task in SQLite and goes through the same pipeline.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;One worktree per task.&lt;&#x2F;strong&gt; Each task gets a branch and a worktree under &lt;code&gt;~&#x2F;.orch&#x2F;worktrees&#x2F;&amp;lt;project&amp;gt;&#x2F;&amp;lt;branch&amp;gt;&#x2F;&lt;&#x2F;code&gt;. The agent never touches my checkout. A tool-level sandbox blocks reads and writes to the main project directory.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;tmux is the runtime.&lt;&#x2F;strong&gt; Every agent runs in its own tmux session. I can attach to any of them, &lt;code&gt;orch stream&lt;&#x2F;code&gt; shows all of them live, and the same output goes to Telegram and Discord. If the service restarts, running tasks go back to &lt;code&gt;routed&lt;&#x2F;code&gt; and resume in their existing worktree.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;The router picks a complexity.&lt;&#x2F;strong&gt; An LLM call classifies each task as simple, medium or complex. A table in the config maps each level to a model per agent, so &lt;code&gt;complex&lt;&#x2F;code&gt; means Opus on Claude and the strongest Codex model on Codex. Labels like &lt;code&gt;agent:codex&lt;&#x2F;code&gt; or &lt;code&gt;complexity:simple&lt;&#x2F;code&gt; override it. Changing models is a config edit. A model list can also hold &lt;code&gt;opencode:free&lt;&#x2F;code&gt;, which orch expands at startup to every free model that &lt;code&gt;opencode models&lt;&#x2F;code&gt; returns. So far 1,785 runs have used 13 different free models, about one run in seven, at no cost.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;A different agent reviews.&lt;&#x2F;strong&gt; When a PR is open, orch picks a reviewer that is not the agent that wrote the code and not one that already reviewed this task. The reviewer reads the diff and returns approve or request changes. Changes go back to the same agent, in the same worktree, on the same PR. After two review cycles the task blocks and waits for me.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Context is built for every run.&lt;&#x2F;strong&gt; Before an agent starts, orch assembles its prompt from the issue and its comments, the repo&#x27;s &lt;code&gt;CLAUDE.md&lt;&#x2F;code&gt;, &lt;code&gt;AGENTS.md&lt;&#x2F;code&gt; and &lt;code&gt;README.md&lt;&#x2F;code&gt;, the file tree, the parent task if there is one, any review feedback, selected skills, and a memory of the last three attempts: what each agent tried, what it learned, and what failed. A retry starts where the last one stopped.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Jobs are cron plus that context.&lt;&#x2F;strong&gt; A project defines jobs in &lt;code&gt;.orch.yml&lt;&#x2F;code&gt; or as markdown files with a schedule in the frontmatter. A job either runs a shell command or creates a task, and the task gets the full context above. A job does not start again while its last task is still running. This is what runs my finance ledger: statements download, import, get categorized and reconciled on a schedule, and an agent with the repo&#x27;s rules handles whatever does not fit.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-issues-and-how-we-solved-them&quot;&gt;The issues, and how we solved them &lt;a class=&quot;zola-anchor&quot; href=&quot;#the-issues-and-how-we-solved-them&quot; aria-label=&quot;Link to this section&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;&lt;strong&gt;Agents analyzed instead of shipping.&lt;&#x2F;strong&gt; At one point more than sixteen tasks closed with no PR. The agents read the code, wrote a good summary, and committed nothing. A plugin was injecting a session hook that told them to plan with skills before anything else, and they did, every time. I removed the plugin and changed the runner: an issue that finishes with no code goes back to the router with a different agent. Internal tasks like reports can still finish without a PR.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;A model name in the router.&lt;&#x2F;strong&gt; A provider renamed a model, dispatch failed, and an agent fixed it by adding a table that rewrote the old name to the new one. Then another agent added a second name. Both PRs were reverted. The rule now is that the routing code contains no model names at all. When a model fails with &amp;quot;not found&amp;quot;, that one model goes into a long cooldown and the router picks the next one. A list of names breaks again the next time a vendor renames something, and the cooldown needs no names.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;A timer next to the cooldowns.&lt;&#x2F;strong&gt; When every agent was cooled, an agent added a per-task &amp;quot;retry after&amp;quot; timestamp so routing would stop logging errors. The timestamp was half the remaining cooldown, and some cooldowns last days. When the agents recovered, nine tasks were still stuck behind their own timers while &lt;code&gt;orch cooldown list&lt;&#x2F;code&gt; said there were no cooldowns at all. I removed it. Now the cooldown table is the only answer to &amp;quot;is this agent available&amp;quot;, and routing simply tries again on the next tick, ten seconds later. The error log is loud on purpose.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Blocking a whole repo for one bad merge.&lt;&#x2F;strong&gt; More velocity means more PRs, and every PR runs CI. One of my private projects shows how much: with orch working on it, CI ran almost all day.&lt;&#x2F;p&gt;
&lt;p&gt;Orch itself is public, so its Actions minutes are free. A private repo pays for them, and without a spending limit the bill gets out of control. The other option is to run your own runner. My GitHub Pro plan includes 3,000 Actions minutes a month, and I had a $10 spending limit on top of that. That one project used both in a week, and CI stopped until the next cycle.&lt;&#x2F;p&gt;
&lt;p&gt;An agent saw the billing error and made orch skip every new task for that repo for 24 hours. That was the wrong fix: for a full day no agent wrote code and no reviewer reviewed, for a failure that only matters at merge time.&lt;&#x2F;p&gt;
&lt;p&gt;Now the billing error blocks only the task that tried to merge. Everything else keeps going: agents write code, reviewers review, and the PRs wait. When the new cycle starts, I unblock them and they merge.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Cooldowns that were all different.&lt;&#x2F;strong&gt; Rate limits, empty credits, disabled orgs, exhausted billing cycles and silent hangs each had their own special case. They are now one mechanism: exponential backoff, &lt;code&gt;min(base * 3^(n-1), max)&lt;&#x2F;code&gt;, persisted in SQLite so a restart does not forget it, and reset on the next success. A vendor&#x27;s own &amp;quot;try again at&amp;quot; always wins. Silence detection catches the agents that hang without an error.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Token budgets on flat plans.&lt;&#x2F;strong&gt; Orch used to track tokens per task and warn when a task went over. Every agent I use is on a subscription, so the numbers meant nothing and the bookkeeping broke often. A migration dropped the columns. Hung agents, slow calls and runaway retries already have timeouts, a watchdog and attempt limits.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;An edited migration.&lt;&#x2F;strong&gt; An agent changed an existing SQL migration instead of adding a new one. SQLx checks a hash of every applied migration, so the service failed on startup for over an hour. Migrations are now immutable, and a test runs them twice on a fresh database.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;A PTY that did not need to exist.&lt;&#x2F;strong&gt; The first runner spawned agents in a pseudo terminal and forwarded output into tmux with &lt;code&gt;send-keys&lt;&#x2F;code&gt;. It broke in ways that were hard to see. tmux already gives every session a terminal, so I deleted about 700 lines and ran the agent directly in the session.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Agents changing the service that runs them.&lt;&#x2F;strong&gt; Agents edited the live config, ran &lt;code&gt;brew upgrade&lt;&#x2F;code&gt; on my machine, and filed issues saying the installed version was behind main. Each one was trying to help. Now the config, Homebrew and upgrades belong to me, and the agent explains a needed change in the PR instead.&lt;&#x2F;p&gt;
&lt;p&gt;Most of these fixes are also a paragraph in the repo&#x27;s &lt;code&gt;AGENTS.md&lt;&#x2F;code&gt;, under a section called &amp;quot;DO NOT TOUCH&amp;quot;. It lists each settled decision, the PR or issue that broke it, and why it was reverted. Every agent reads that file before it starts, and the reviewer reads it before it approves. It is the most useful file in the repo, and every entry in it is a mistake an agent made at least once.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;what-it-costs&quot;&gt;What it costs &lt;a class=&quot;zola-anchor&quot; href=&quot;#what-it-costs&quot; aria-label=&quot;Link to this section&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;&lt;strong&gt;It runs on one machine.&lt;&#x2F;strong&gt; Orch is a local service with no public endpoint. Webhooks do not reach it, so it polls GitHub every 45 seconds. When the Mac sleeps, nothing runs.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Agents still need supervision.&lt;&#x2F;strong&gt; Most fixes in the list above came from an agent solving the wrong problem well. Orch makes agents cheap to run, but it does not improve their judgement. The review agent catches a lot, and I still read what merges into anything that matters.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Subscriptions run out.&lt;&#x2F;strong&gt; With six vendors, some agent is always cooled. That is fine when the pool is large, and it means slow days when two vendors hit their limits at once.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;It has a lot of moving parts.&lt;&#x2F;strong&gt; A router, a reviewer, a scheduler, cooldowns and three chat channels. Most of the bugs now are in the space between those parts.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;why-bother&quot;&gt;Why bother &lt;a class=&quot;zola-anchor&quot; href=&quot;#why-bother&quot; aria-label=&quot;Link to this section&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;That was the moment I saw that the world changed, or at least the part of it that involves my work. I still write code, decide the architecture and design the systems. What changed is everything around that: I write the issues, read the reviews and decide what merges, and the clerical part runs on its own, at night included.&lt;&#x2F;p&gt;
&lt;p&gt;Orch now fixes and builds itself. A daily job reads its own database, writes a review of the last 24 hours, and files issues for what broke. Those issues go through the same pipeline as any other: an agent picks one up, another agent reviews the PR, and it merges. Feature work goes the same way. I write the issue and orch implements it. Every PR merged into orch in the last two weeks came from orch.&lt;&#x2F;p&gt;
&lt;p&gt;It does the same for every other project I run. The finance ledger and the next thing I start get the same loop: issues in, reviewed PRs out, and scheduled jobs that keep each project running.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;next-worktrees-that-share-their-files&quot;&gt;Next: worktrees that share their files &lt;a class=&quot;zola-anchor&quot; href=&quot;#next-worktrees-that-share-their-files&quot; aria-label=&quot;Link to this section&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;Every task gets a full git worktree. Git shares the history between worktrees, but every file is written to disk again, and every build installs its own dependencies. With many agents on one large repo, most of the disk and most of the setup time go to copies of the same files.&lt;&#x2F;p&gt;
&lt;p&gt;The plan is APFS clones. APFS, the filesystem on every Mac, can clone a file with &lt;code&gt;clonefile&lt;&#x2F;code&gt;, the call behind &lt;code&gt;cp -c&lt;&#x2F;code&gt;. A clone shares disk blocks with its source until one side writes, and then only the changed blocks are copied. A clone of a whole checkout takes almost no space and about a second to create, and each agent can still write anything. Orch would keep one ready checkout per project, with dependencies installed, and clone it for each task. Tools like &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;onnimonni&#x2F;git-cow&quot;&gt;git-cow&lt;&#x2F;a&gt; already do this.&lt;&#x2F;p&gt;
&lt;p&gt;This feature will be built the same way as everything else in orch. It starts as an issue. Orch picks it up, a different agent reviews it, and I decide if it merges.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;and-what-about-the-future&quot;&gt;And what about the future? &lt;a class=&quot;zola-anchor&quot; href=&quot;#and-what-about-the-future&quot; aria-label=&quot;Link to this section&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;p&gt;I am also thinking about a GUI. Today I follow orch through the CLI, tmux, Telegram and the GitHub issues. One window with every project, task, agent session and cooldown would make it easier to see what is happening and to step in when something needs me.&lt;&#x2F;p&gt;
&lt;p&gt;If you run coding agents on your own projects, try orch and tell me what breaks.&lt;&#x2F;p&gt;
&lt;p&gt;If you want to learn more and get involved, here are the &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;orch.gabrielkoerich.com&#x2F;&quot;&gt;docs&lt;&#x2F;a&gt;, with daily updates created automatically by orch itself, &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;orch.gabrielkoerich.com&#x2F;posts&#x2F;daily-review-2026-07-05&#x2F;&quot;&gt;like this one&lt;&#x2F;a&gt;, and the &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;gabrielkoerich&#x2F;orch&quot;&gt;source&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;hr &#x2F;&gt;
&lt;p&gt;&lt;strong&gt;Corrections, 2026-10-12.&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;I wrote that the reviewer can return approve, request changes or reject. orch only has approve and request changes.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
</content>
    </entry>
    <entry xml:lang="en">
        <title>A new start</title>
        <published>2026-02-11T00:00:00+00:00</published>
        <updated>2026-02-11T00:00:00+00:00</updated>
        <author>
            <name>Gabriel Koerich</name>
        </author>
        <link rel="alternate" type="text/html" href="https://gabrielkoerich.com/posts/a-new-start/"/>
        <id>https://gabrielkoerich.com/posts/a-new-start/</id>
        <summary>After years of silence I am restarting this blog, with short honest notes on AI coding agents, Solana and Rust, and fifteen years of engineering.</summary>
        <content type="html" xml:base="https://gabrielkoerich.com/posts/a-new-start/">&lt;p&gt;After years of silence, I&#x27;m restarting this blog.&lt;&#x2F;p&gt;
&lt;p&gt;I&#x27;ll write short, honest notes on what I&#x27;m doing day to day: building DeFi on Solana, running several AI coding agents in parallel, and shipping production systems. These are notes from the real problem-solving work, without hot takes or SEO tips.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;what-s-coming&quot;&gt;What&#x27;s coming &lt;a class=&quot;zola-anchor&quot; href=&quot;#what-s-coming&quot; aria-label=&quot;Link to this section&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;h2&gt;
&lt;ul&gt;
&lt;li&gt;AI coding agents in production: Claude Code, Codex and OpenCode side by side, what works, what doesn&#x27;t, and why I built &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;gabrielkoerich&#x2F;orch&quot;&gt;orch&lt;&#x2F;a&gt;.&lt;&#x2F;li&gt;
&lt;li&gt;Solana and Rust deep dives: patterns from Lulo&#x27;s protocol, CPI integrations, and testing strategies that catch bugs.&lt;&#x2F;li&gt;
&lt;li&gt;Engineering after 15 years: lessons I keep relearning, tools that survived 5+ years in my workflow, and principles that didn&#x27;t.&lt;&#x2F;li&gt;
&lt;li&gt;Automating the boring parts of running a company, drawing on what I built at Bulldesk and what I&#x27;m trying now.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;If you&#x27;d like to work together, &lt;a href=&quot;&#x2F;consulting&#x2F;&quot;&gt;I take on AI consulting&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
</content>
    </entry>
    <entry xml:lang="en">
        <title>Refactoring to Events</title>
        <published>2018-05-24T00:00:00+00:00</published>
        <updated>2018-05-24T00:00:00+00:00</updated>
        <author>
            <name>Gabriel Koerich</name>
        </author>
        <link rel="alternate" type="text/html" href="https://gabrielkoerich.com/posts/refactoring-to-events/"/>
        <id>https://gabrielkoerich.com/posts/refactoring-to-events/</id>
        <summary>A Stripe webhook controller full of ifs and elses, refactored step by step into events and listeners, one event per webhook type, so the controller gets thin.</summary>
        <content type="html" xml:base="https://gabrielkoerich.com/posts/refactoring-to-events/">&lt;p&gt;I know, we&#x27;re all used to seeing that kind of messy code with a lot of &lt;code&gt;ifs&lt;&#x2F;code&gt; and &lt;code&gt;elses&lt;&#x2F;code&gt;, right?&lt;&#x2F;p&gt;
&lt;p&gt;But today we&#x27;re going to try to change this a little bit.&lt;&#x2F;p&gt;
&lt;p&gt;Well, let&#x27;s start with a sample. It&#x27;s a class that handles Stripe webhooks.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;ts&quot; class=&quot;language-ts z-code&quot;&gt;&lt;code class=&quot;language-ts&quot; data-lang=&quot;ts&quot;&gt;&lt;span class=&quot;z-source z-ts&quot;&gt;&lt;span class=&quot;z-meta z-class z-ts&quot;&gt;&lt;span class=&quot;z-storage z-type z-class z-ts&quot;&gt;class&lt;&#x2F;span&gt; &lt;span class=&quot;z-entity z-name z-type z-class z-ts&quot;&gt;WebhookController&lt;&#x2F;span&gt; &lt;span class=&quot;z-storage z-modifier z-ts&quot;&gt;extends&lt;&#x2F;span&gt; &lt;span class=&quot;z-entity z-other z-inherited-class z-ts&quot;&gt;Controller&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-ts&quot;&gt;&lt;span class=&quot;z-meta z-class z-ts&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-block z-ts&quot;&gt;{&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-ts&quot;&gt;&lt;span class=&quot;z-meta z-class z-ts&quot;&gt;    &lt;span class=&quot;z-storage z-modifier z-ts&quot;&gt;public&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-function z-expression z-ts&quot;&gt;&lt;span class=&quot;z-storage z-type z-function z-ts&quot;&gt;function&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-definition z-function z-ts&quot;&gt;&lt;span class=&quot;z-entity z-name z-function z-ts&quot;&gt;handleWebhook&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-parameters z-ts&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-parameters z-begin z-ts&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-parameter z-ts&quot;&gt;Request&lt;&#x2F;span&gt; &lt;span class=&quot;z-variable z-parameter z-ts&quot;&gt;$request&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-parameters z-end z-ts&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-ts&quot;&gt;&lt;span class=&quot;z-meta z-class z-ts&quot;&gt;&lt;span class=&quot;z-meta z-function z-expression z-ts&quot;&gt;    &lt;span class=&quot;z-meta z-block z-ts&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-block z-ts&quot;&gt;{&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-ts&quot;&gt;&lt;span class=&quot;z-meta z-class z-ts&quot;&gt;&lt;span class=&quot;z-meta z-function z-expression z-ts&quot;&gt;&lt;span class=&quot;z-meta z-block z-ts&quot;&gt;        &lt;span class=&quot;z-variable z-other z-readwrite z-ts&quot;&gt;$payload&lt;&#x2F;span&gt; &lt;span class=&quot;z-keyword z-operator z-assignment z-ts&quot;&gt;=&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-function-call z-ts&quot;&gt;&lt;span class=&quot;z-entity z-name z-function z-ts&quot;&gt;json_decode&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-brace z-round z-ts&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other z-readwrite z-ts&quot;&gt;$request&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator z-arithmetic z-ts&quot;&gt;-&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator z-relational z-ts&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-ts&quot;&gt;&lt;span class=&quot;z-entity z-name z-function z-ts&quot;&gt;getContent&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-brace z-round z-ts&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-brace z-round z-ts&quot;&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-comma z-ts&quot;&gt;,&lt;&#x2F;span&gt; &lt;span class=&quot;z-constant z-language z-boolean z-true z-ts&quot;&gt;true&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-brace z-round z-ts&quot;&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-terminator z-statement z-ts&quot;&gt;;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-ts&quot;&gt;&lt;span class=&quot;z-meta z-class z-ts&quot;&gt;&lt;span class=&quot;z-meta z-function z-expression z-ts&quot;&gt;&lt;span class=&quot;z-meta z-block z-ts&quot;&gt;        &lt;span class=&quot;z-keyword z-control z-conditional z-ts&quot;&gt;if&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-brace z-round z-ts&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator z-logical z-ts&quot;&gt;!&lt;&#x2F;span&gt; &lt;span class=&quot;z-variable z-other z-readwrite z-ts&quot;&gt;$this&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator z-arithmetic z-ts&quot;&gt;-&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator z-relational z-ts&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-ts&quot;&gt;&lt;span class=&quot;z-entity z-name z-function z-ts&quot;&gt;isInTestingEnvironment&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-brace z-round z-ts&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-brace z-round z-ts&quot;&gt;)&lt;&#x2F;span&gt; &lt;span class=&quot;z-keyword z-operator z-logical z-ts&quot;&gt;&amp;amp;&amp;amp;&lt;&#x2F;span&gt; &lt;span class=&quot;z-keyword z-operator z-logical z-ts&quot;&gt;!&lt;&#x2F;span&gt; &lt;span class=&quot;z-variable z-other z-readwrite z-ts&quot;&gt;$this&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator z-arithmetic z-ts&quot;&gt;-&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator z-relational z-ts&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-ts&quot;&gt;&lt;span class=&quot;z-entity z-name z-function z-ts&quot;&gt;eventExistsOnStripe&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-brace z-round z-ts&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other z-readwrite z-ts&quot;&gt;$payload&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-array z-literal z-ts&quot;&gt;&lt;span class=&quot;z-meta z-brace z-square z-ts&quot;&gt;[&lt;&#x2F;span&gt;&lt;span class=&quot;z-string z-quoted z-single z-ts&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-ts&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;id&lt;span class=&quot;z-punctuation z-definition z-string z-end z-ts&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-brace z-square z-ts&quot;&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-brace z-round z-ts&quot;&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-brace z-round z-ts&quot;&gt;)&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-block z-ts&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-block z-ts&quot;&gt;{&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-ts&quot;&gt;&lt;span class=&quot;z-meta z-class z-ts&quot;&gt;&lt;span class=&quot;z-meta z-function z-expression z-ts&quot;&gt;&lt;span class=&quot;z-meta z-block z-ts&quot;&gt;&lt;span class=&quot;z-meta z-block z-ts&quot;&gt;            &lt;span class=&quot;z-keyword z-control z-flow z-ts&quot;&gt;return&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-terminator z-statement z-ts&quot;&gt;;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-ts&quot;&gt;&lt;span class=&quot;z-meta z-class z-ts&quot;&gt;&lt;span class=&quot;z-meta z-function z-expression z-ts&quot;&gt;&lt;span class=&quot;z-meta z-block z-ts&quot;&gt;&lt;span class=&quot;z-meta z-block z-ts&quot;&gt;        &lt;span class=&quot;z-punctuation z-definition z-block z-ts&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-ts&quot;&gt;&lt;span class=&quot;z-meta z-class z-ts&quot;&gt;&lt;span class=&quot;z-meta z-function z-expression z-ts&quot;&gt;&lt;span class=&quot;z-meta z-block z-ts&quot;&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-ts&quot;&gt;&lt;span class=&quot;z-meta z-class z-ts&quot;&gt;&lt;span class=&quot;z-meta z-function z-expression z-ts&quot;&gt;&lt;span class=&quot;z-meta z-block z-ts&quot;&gt;        &lt;span class=&quot;z-variable z-other z-readwrite z-ts&quot;&gt;$method&lt;&#x2F;span&gt; &lt;span class=&quot;z-keyword z-operator z-assignment z-ts&quot;&gt;=&lt;&#x2F;span&gt; &lt;span class=&quot;z-string z-quoted z-single z-ts&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-ts&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;handle&lt;span class=&quot;z-punctuation z-definition z-string z-end z-ts&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-ts&quot;&gt;&lt;span class=&quot;z-punctuation z-accessor z-ts&quot;&gt;.&lt;&#x2F;span&gt;&lt;span class=&quot;z-entity z-name z-function z-ts&quot;&gt;studly_case&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-brace z-round z-ts&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-ts&quot;&gt;&lt;span class=&quot;z-entity z-name z-function z-ts&quot;&gt;str_replace&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-brace z-round z-ts&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-string z-quoted z-single z-ts&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-ts&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;.&lt;span class=&quot;z-punctuation z-definition z-string z-end z-ts&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-comma z-ts&quot;&gt;,&lt;&#x2F;span&gt; &lt;span class=&quot;z-string z-quoted z-single z-ts&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-ts&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;_&lt;span class=&quot;z-punctuation z-definition z-string z-end z-ts&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-comma z-ts&quot;&gt;,&lt;&#x2F;span&gt; &lt;span class=&quot;z-variable z-other z-readwrite z-ts&quot;&gt;$payload&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-array z-literal z-ts&quot;&gt;&lt;span class=&quot;z-meta z-brace z-square z-ts&quot;&gt;[&lt;&#x2F;span&gt;&lt;span class=&quot;z-string z-quoted z-single z-ts&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-ts&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;type&lt;span class=&quot;z-punctuation z-definition z-string z-end z-ts&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-brace z-square z-ts&quot;&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-brace z-round z-ts&quot;&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-brace z-round z-ts&quot;&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-terminator z-statement z-ts&quot;&gt;;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-ts&quot;&gt;&lt;span class=&quot;z-meta z-class z-ts&quot;&gt;&lt;span class=&quot;z-meta z-function z-expression z-ts&quot;&gt;&lt;span class=&quot;z-meta z-block z-ts&quot;&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-ts&quot;&gt;&lt;span class=&quot;z-meta z-class z-ts&quot;&gt;&lt;span class=&quot;z-meta z-function z-expression z-ts&quot;&gt;&lt;span class=&quot;z-meta z-block z-ts&quot;&gt;        &lt;span class=&quot;z-keyword z-control z-conditional z-ts&quot;&gt;if&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-brace z-round z-ts&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-ts&quot;&gt;&lt;span class=&quot;z-entity z-name z-function z-ts&quot;&gt;method_exists&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-brace z-round z-ts&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other z-readwrite z-ts&quot;&gt;$this&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-comma z-ts&quot;&gt;,&lt;&#x2F;span&gt; &lt;span class=&quot;z-variable z-other z-readwrite z-ts&quot;&gt;$method&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-brace z-round z-ts&quot;&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-brace z-round z-ts&quot;&gt;)&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-block z-ts&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-block z-ts&quot;&gt;{&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-ts&quot;&gt;&lt;span class=&quot;z-meta z-class z-ts&quot;&gt;&lt;span class=&quot;z-meta z-function z-expression z-ts&quot;&gt;&lt;span class=&quot;z-meta z-block z-ts&quot;&gt;&lt;span class=&quot;z-meta z-block z-ts&quot;&gt;            &lt;span class=&quot;z-keyword z-control z-flow z-ts&quot;&gt;return&lt;&#x2F;span&gt; &lt;span class=&quot;z-variable z-other z-readwrite z-ts&quot;&gt;$this&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator z-arithmetic z-ts&quot;&gt;-&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator z-relational z-ts&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-objectliteral z-ts&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-block z-ts&quot;&gt;{&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-object z-member z-ts&quot;&gt;&lt;span class=&quot;z-variable z-other z-readwrite z-ts&quot;&gt;$method&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-block z-ts&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-brace z-round z-ts&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other z-readwrite z-ts&quot;&gt;$payload&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-brace z-round z-ts&quot;&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-terminator z-statement z-ts&quot;&gt;;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-ts&quot;&gt;&lt;span class=&quot;z-meta z-class z-ts&quot;&gt;&lt;span class=&quot;z-meta z-function z-expression z-ts&quot;&gt;&lt;span class=&quot;z-meta z-block z-ts&quot;&gt;&lt;span class=&quot;z-meta z-block z-ts&quot;&gt;        &lt;span class=&quot;z-punctuation z-definition z-block z-ts&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; &lt;span class=&quot;z-keyword z-control z-conditional z-ts&quot;&gt;else&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-block z-ts&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-block z-ts&quot;&gt;{&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-ts&quot;&gt;&lt;span class=&quot;z-meta z-class z-ts&quot;&gt;&lt;span class=&quot;z-meta z-function z-expression z-ts&quot;&gt;&lt;span class=&quot;z-meta z-block z-ts&quot;&gt;&lt;span class=&quot;z-meta z-block z-ts&quot;&gt;            &lt;span class=&quot;z-keyword z-control z-flow z-ts&quot;&gt;return&lt;&#x2F;span&gt; &lt;span class=&quot;z-variable z-other z-readwrite z-ts&quot;&gt;$this&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator z-arithmetic z-ts&quot;&gt;-&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator z-relational z-ts&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-ts&quot;&gt;&lt;span class=&quot;z-entity z-name z-function z-ts&quot;&gt;missingMethod&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-brace z-round z-ts&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-brace z-round z-ts&quot;&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-terminator z-statement z-ts&quot;&gt;;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-ts&quot;&gt;&lt;span class=&quot;z-meta z-class z-ts&quot;&gt;&lt;span class=&quot;z-meta z-function z-expression z-ts&quot;&gt;&lt;span class=&quot;z-meta z-block z-ts&quot;&gt;&lt;span class=&quot;z-meta z-block z-ts&quot;&gt;        &lt;span class=&quot;z-punctuation z-definition z-block z-ts&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-ts&quot;&gt;&lt;span class=&quot;z-meta z-class z-ts&quot;&gt;&lt;span class=&quot;z-meta z-function z-expression z-ts&quot;&gt;&lt;span class=&quot;z-meta z-block z-ts&quot;&gt;    &lt;span class=&quot;z-punctuation z-definition z-block z-ts&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-ts&quot;&gt;&lt;span class=&quot;z-meta z-class z-ts&quot;&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-ts&quot;&gt;&lt;span class=&quot;z-meta z-class z-ts&quot;&gt;    &lt;span class=&quot;z-storage z-modifier z-ts&quot;&gt;protected&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-function z-expression z-ts&quot;&gt;&lt;span class=&quot;z-storage z-type z-function z-ts&quot;&gt;function&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-definition z-function z-ts&quot;&gt;&lt;span class=&quot;z-entity z-name z-function z-ts&quot;&gt;handleCustomerSubscriptionDeleted&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-parameters z-ts&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-parameters z-begin z-ts&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-parameter z-ts&quot;&gt;array&lt;&#x2F;span&gt; &lt;span class=&quot;z-variable z-parameter z-ts&quot;&gt;$payload&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-parameters z-end z-ts&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-ts&quot;&gt;&lt;span class=&quot;z-meta z-class z-ts&quot;&gt;&lt;span class=&quot;z-meta z-function z-expression z-ts&quot;&gt;    &lt;span class=&quot;z-meta z-block z-ts&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-block z-ts&quot;&gt;{&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-ts&quot;&gt;&lt;span class=&quot;z-meta z-class z-ts&quot;&gt;&lt;span class=&quot;z-meta z-function z-expression z-ts&quot;&gt;&lt;span class=&quot;z-meta z-block z-ts&quot;&gt;        &lt;span class=&quot;z-variable z-other z-readwrite z-ts&quot;&gt;$user&lt;&#x2F;span&gt; &lt;span class=&quot;z-keyword z-operator z-assignment z-ts&quot;&gt;=&lt;&#x2F;span&gt; &lt;span class=&quot;z-variable z-other z-readwrite z-ts&quot;&gt;$this&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator z-arithmetic z-ts&quot;&gt;-&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator z-relational z-ts&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-ts&quot;&gt;&lt;span class=&quot;z-entity z-name z-function z-ts&quot;&gt;getUserByStripeId&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-brace z-round z-ts&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other z-readwrite z-ts&quot;&gt;$payload&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-array z-literal z-ts&quot;&gt;&lt;span class=&quot;z-meta z-brace z-square z-ts&quot;&gt;[&lt;&#x2F;span&gt;&lt;span class=&quot;z-string z-quoted z-single z-ts&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-ts&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;data&lt;span class=&quot;z-punctuation z-definition z-string z-end z-ts&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-brace z-square z-ts&quot;&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-array z-literal z-ts&quot;&gt;&lt;span class=&quot;z-meta z-brace z-square z-ts&quot;&gt;[&lt;&#x2F;span&gt;&lt;span class=&quot;z-string z-quoted z-single z-ts&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-ts&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;object&lt;span class=&quot;z-punctuation z-definition z-string z-end z-ts&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-brace z-square z-ts&quot;&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-array z-literal z-ts&quot;&gt;&lt;span class=&quot;z-meta z-brace z-square z-ts&quot;&gt;[&lt;&#x2F;span&gt;&lt;span class=&quot;z-string z-quoted z-single z-ts&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-ts&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;customer&lt;span class=&quot;z-punctuation z-definition z-string z-end z-ts&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-brace z-square z-ts&quot;&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-brace z-round z-ts&quot;&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-terminator z-statement z-ts&quot;&gt;;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-ts&quot;&gt;&lt;span class=&quot;z-meta z-class z-ts&quot;&gt;&lt;span class=&quot;z-meta z-function z-expression z-ts&quot;&gt;&lt;span class=&quot;z-meta z-block z-ts&quot;&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-ts&quot;&gt;&lt;span class=&quot;z-meta z-class z-ts&quot;&gt;&lt;span class=&quot;z-meta z-function z-expression z-ts&quot;&gt;&lt;span class=&quot;z-meta z-block z-ts&quot;&gt;        &lt;span class=&quot;z-keyword z-control z-conditional z-ts&quot;&gt;if&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-brace z-round z-ts&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other z-readwrite z-ts&quot;&gt;$user&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-brace z-round z-ts&quot;&gt;)&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-block z-ts&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-block z-ts&quot;&gt;{&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-ts&quot;&gt;&lt;span class=&quot;z-meta z-class z-ts&quot;&gt;&lt;span class=&quot;z-meta z-function z-expression z-ts&quot;&gt;&lt;span class=&quot;z-meta z-block z-ts&quot;&gt;&lt;span class=&quot;z-meta z-block z-ts&quot;&gt;            &lt;span class=&quot;z-variable z-other z-readwrite z-ts&quot;&gt;$user&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator z-arithmetic z-ts&quot;&gt;-&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator z-relational z-ts&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other z-readwrite z-ts&quot;&gt;subscriptions&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator z-arithmetic z-ts&quot;&gt;-&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator z-relational z-ts&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-ts&quot;&gt;&lt;span class=&quot;z-entity z-name z-function z-ts&quot;&gt;filter&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-brace z-round z-ts&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function z-expression z-ts&quot;&gt;&lt;span class=&quot;z-storage z-type z-function z-ts&quot;&gt;function&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-parameters z-ts&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-parameters z-begin z-ts&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-parameter z-ts&quot;&gt;$subscription&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-parameters z-end z-ts&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-definition z-function z-ts&quot;&gt;&lt;span class=&quot;z-entity z-name z-function z-ts&quot;&gt;use&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-parameters z-ts&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-parameters z-begin z-ts&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-parameter z-ts&quot;&gt;$payload&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-parameters z-end z-ts&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-block z-ts&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-block z-ts&quot;&gt;{&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-ts&quot;&gt;&lt;span class=&quot;z-meta z-class z-ts&quot;&gt;&lt;span class=&quot;z-meta z-function z-expression z-ts&quot;&gt;&lt;span class=&quot;z-meta z-block z-ts&quot;&gt;&lt;span class=&quot;z-meta z-block z-ts&quot;&gt;&lt;span class=&quot;z-meta z-function z-expression z-ts&quot;&gt;&lt;span class=&quot;z-meta z-block z-ts&quot;&gt;                &lt;span class=&quot;z-keyword z-control z-flow z-ts&quot;&gt;return&lt;&#x2F;span&gt; &lt;span class=&quot;z-variable z-other z-readwrite z-ts&quot;&gt;$subscription&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator z-arithmetic z-ts&quot;&gt;-&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator z-relational z-ts&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other z-readwrite z-ts&quot;&gt;stripe_id&lt;&#x2F;span&gt; &lt;span class=&quot;z-keyword z-operator z-comparison z-ts&quot;&gt;===&lt;&#x2F;span&gt; &lt;span class=&quot;z-variable z-other z-readwrite z-ts&quot;&gt;$payload&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-array z-literal z-ts&quot;&gt;&lt;span class=&quot;z-meta z-brace z-square z-ts&quot;&gt;[&lt;&#x2F;span&gt;&lt;span class=&quot;z-string z-quoted z-single z-ts&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-ts&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;data&lt;span class=&quot;z-punctuation z-definition z-string z-end z-ts&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-brace z-square z-ts&quot;&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-array z-literal z-ts&quot;&gt;&lt;span class=&quot;z-meta z-brace z-square z-ts&quot;&gt;[&lt;&#x2F;span&gt;&lt;span class=&quot;z-string z-quoted z-single z-ts&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-ts&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;object&lt;span class=&quot;z-punctuation z-definition z-string z-end z-ts&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-brace z-square z-ts&quot;&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-array z-literal z-ts&quot;&gt;&lt;span class=&quot;z-meta z-brace z-square z-ts&quot;&gt;[&lt;&#x2F;span&gt;&lt;span class=&quot;z-string z-quoted z-single z-ts&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-ts&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;id&lt;span class=&quot;z-punctuation z-definition z-string z-end z-ts&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-brace z-square z-ts&quot;&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-terminator z-statement z-ts&quot;&gt;;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-ts&quot;&gt;&lt;span class=&quot;z-meta z-class z-ts&quot;&gt;&lt;span class=&quot;z-meta z-function z-expression z-ts&quot;&gt;&lt;span class=&quot;z-meta z-block z-ts&quot;&gt;&lt;span class=&quot;z-meta z-block z-ts&quot;&gt;&lt;span class=&quot;z-meta z-function z-expression z-ts&quot;&gt;&lt;span class=&quot;z-meta z-block z-ts&quot;&gt;            &lt;span class=&quot;z-punctuation z-definition z-block z-ts&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-brace z-round z-ts&quot;&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator z-arithmetic z-ts&quot;&gt;-&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator z-relational z-ts&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-ts&quot;&gt;&lt;span class=&quot;z-entity z-name z-function z-ts&quot;&gt;each&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-brace z-round z-ts&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function z-expression z-ts&quot;&gt;&lt;span class=&quot;z-storage z-type z-function z-ts&quot;&gt;function&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-parameters z-ts&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-parameters z-begin z-ts&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-parameter z-ts&quot;&gt;$subscription&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-parameters z-end z-ts&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-block z-ts&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-block z-ts&quot;&gt;{&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-ts&quot;&gt;&lt;span class=&quot;z-meta z-class z-ts&quot;&gt;&lt;span class=&quot;z-meta z-function z-expression z-ts&quot;&gt;&lt;span class=&quot;z-meta z-block z-ts&quot;&gt;&lt;span class=&quot;z-meta z-block z-ts&quot;&gt;&lt;span class=&quot;z-meta z-function z-expression z-ts&quot;&gt;&lt;span class=&quot;z-meta z-block z-ts&quot;&gt;                &lt;span class=&quot;z-variable z-other z-readwrite z-ts&quot;&gt;$subscription&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator z-arithmetic z-ts&quot;&gt;-&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator z-relational z-ts&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-ts&quot;&gt;&lt;span class=&quot;z-entity z-name z-function z-ts&quot;&gt;markAsCancelled&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-brace z-round z-ts&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-brace z-round z-ts&quot;&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-terminator z-statement z-ts&quot;&gt;;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-ts&quot;&gt;&lt;span class=&quot;z-meta z-class z-ts&quot;&gt;&lt;span class=&quot;z-meta z-function z-expression z-ts&quot;&gt;&lt;span class=&quot;z-meta z-block z-ts&quot;&gt;&lt;span class=&quot;z-meta z-block z-ts&quot;&gt;&lt;span class=&quot;z-meta z-function z-expression z-ts&quot;&gt;&lt;span class=&quot;z-meta z-block z-ts&quot;&gt;            &lt;span class=&quot;z-punctuation z-definition z-block z-ts&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-brace z-round z-ts&quot;&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-terminator z-statement z-ts&quot;&gt;;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-ts&quot;&gt;&lt;span class=&quot;z-meta z-class z-ts&quot;&gt;&lt;span class=&quot;z-meta z-function z-expression z-ts&quot;&gt;&lt;span class=&quot;z-meta z-block z-ts&quot;&gt;&lt;span class=&quot;z-meta z-block z-ts&quot;&gt;        &lt;span class=&quot;z-punctuation z-definition z-block z-ts&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-ts&quot;&gt;&lt;span class=&quot;z-meta z-class z-ts&quot;&gt;&lt;span class=&quot;z-meta z-function z-expression z-ts&quot;&gt;&lt;span class=&quot;z-meta z-block z-ts&quot;&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-ts&quot;&gt;&lt;span class=&quot;z-meta z-class z-ts&quot;&gt;&lt;span class=&quot;z-meta z-function z-expression z-ts&quot;&gt;&lt;span class=&quot;z-meta z-block z-ts&quot;&gt;        &lt;span class=&quot;z-keyword z-control z-flow z-ts&quot;&gt;return&lt;&#x2F;span&gt; &lt;span class=&quot;z-new z-expr z-ts&quot;&gt;&lt;span class=&quot;z-keyword z-operator z-new z-ts&quot;&gt;new&lt;&#x2F;span&gt; &lt;span class=&quot;z-entity z-name z-type z-ts&quot;&gt;Response&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-brace z-round z-ts&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-string z-quoted z-single z-ts&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-ts&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;Webhook Handled&lt;span class=&quot;z-punctuation z-definition z-string z-end z-ts&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-comma z-ts&quot;&gt;,&lt;&#x2F;span&gt; &lt;span class=&quot;z-constant z-numeric z-decimal z-ts&quot;&gt;200&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-brace z-round z-ts&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-terminator z-statement z-ts&quot;&gt;;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-ts&quot;&gt;&lt;span class=&quot;z-meta z-class z-ts&quot;&gt;&lt;span class=&quot;z-meta z-function z-expression z-ts&quot;&gt;&lt;span class=&quot;z-meta z-block z-ts&quot;&gt;    &lt;span class=&quot;z-punctuation z-definition z-block z-ts&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;&lt;em&gt;No, you would never in your entire life guess that I got this code from the &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;raw.githubusercontent.com&#x2F;laravel&#x2F;cashier&#x2F;7.0&#x2F;src&#x2F;Http&#x2F;Controllers&#x2F;WebhookController.php&quot;&gt;Cashier repository&lt;&#x2F;a&gt;. Never.&lt;&#x2F;em&gt;&lt;&#x2F;p&gt;
&lt;p&gt;That&#x27;s fine, right? If we receive an event called &lt;code&gt;customer.subscription.deleted&lt;&#x2F;code&gt;, our method &lt;code&gt;handleCustomerSubscriptionDeleted&lt;&#x2F;code&gt; will handle it!&lt;&#x2F;p&gt;
&lt;p&gt;But that&#x27;s only fine while your application is starting and you don&#x27;t need to handle much. What if we need to receive &lt;strong&gt;all&lt;&#x2F;strong&gt; Stripe methods? Should we take the same approach?&lt;&#x2F;p&gt;
&lt;p&gt;Nope!&lt;&#x2F;p&gt;
&lt;p&gt;We have better ways to handle that, thanks to &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;laravel.com&#x2F;docs&#x2F;5.6&#x2F;events&quot;&gt;Laravel Events&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;All we need to do to write more consistent and understandable code is to create an event for every hook that we want to receive.&lt;&#x2F;p&gt;
&lt;p&gt;So if you need to listen to Stripe&#x27;s customer subscription deleted hook, you create an event called &lt;code&gt;StripeCustomerSubscriptionDeleted&lt;&#x2F;code&gt;, and then create the listeners that handle that event.&lt;&#x2F;p&gt;
&lt;p&gt;This makes our controller thinner, because it moves responsibilities to event listeners:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;ts&quot; class=&quot;language-ts z-code&quot;&gt;&lt;code class=&quot;language-ts&quot; data-lang=&quot;ts&quot;&gt;&lt;span class=&quot;z-source z-ts&quot;&gt;&lt;span class=&quot;z-meta z-class z-ts&quot;&gt;&lt;span class=&quot;z-storage z-type z-class z-ts&quot;&gt;class&lt;&#x2F;span&gt; &lt;span class=&quot;z-entity z-name z-type z-class z-ts&quot;&gt;WebhookController&lt;&#x2F;span&gt; &lt;span class=&quot;z-storage z-modifier z-ts&quot;&gt;extends&lt;&#x2F;span&gt; &lt;span class=&quot;z-entity z-other z-inherited-class z-ts&quot;&gt;Controller&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-ts&quot;&gt;&lt;span class=&quot;z-meta z-class z-ts&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-block z-ts&quot;&gt;{&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-ts&quot;&gt;&lt;span class=&quot;z-meta z-class z-ts&quot;&gt;    &lt;span class=&quot;z-storage z-modifier z-ts&quot;&gt;public&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-function z-expression z-ts&quot;&gt;&lt;span class=&quot;z-storage z-type z-function z-ts&quot;&gt;function&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-definition z-function z-ts&quot;&gt;&lt;span class=&quot;z-entity z-name z-function z-ts&quot;&gt;handleWebhook&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-parameters z-ts&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-parameters z-begin z-ts&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-parameter z-ts&quot;&gt;Request&lt;&#x2F;span&gt; &lt;span class=&quot;z-variable z-parameter z-ts&quot;&gt;$request&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-parameters z-end z-ts&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-ts&quot;&gt;&lt;span class=&quot;z-meta z-class z-ts&quot;&gt;&lt;span class=&quot;z-meta z-function z-expression z-ts&quot;&gt;    &lt;span class=&quot;z-meta z-block z-ts&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-block z-ts&quot;&gt;{&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-ts&quot;&gt;&lt;span class=&quot;z-meta z-class z-ts&quot;&gt;&lt;span class=&quot;z-meta z-function z-expression z-ts&quot;&gt;&lt;span class=&quot;z-meta z-block z-ts&quot;&gt;        &lt;span class=&quot;z-variable z-other z-readwrite z-ts&quot;&gt;$payload&lt;&#x2F;span&gt; &lt;span class=&quot;z-keyword z-operator z-assignment z-ts&quot;&gt;=&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-function-call z-ts&quot;&gt;&lt;span class=&quot;z-entity z-name z-function z-ts&quot;&gt;json_decode&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-brace z-round z-ts&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other z-readwrite z-ts&quot;&gt;$request&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator z-arithmetic z-ts&quot;&gt;-&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator z-relational z-ts&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-ts&quot;&gt;&lt;span class=&quot;z-entity z-name z-function z-ts&quot;&gt;getContent&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-brace z-round z-ts&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-brace z-round z-ts&quot;&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-comma z-ts&quot;&gt;,&lt;&#x2F;span&gt; &lt;span class=&quot;z-constant z-language z-boolean z-true z-ts&quot;&gt;true&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-brace z-round z-ts&quot;&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-terminator z-statement z-ts&quot;&gt;;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-ts&quot;&gt;&lt;span class=&quot;z-meta z-class z-ts&quot;&gt;&lt;span class=&quot;z-meta z-function z-expression z-ts&quot;&gt;&lt;span class=&quot;z-meta z-block z-ts&quot;&gt;        &lt;span class=&quot;z-keyword z-control z-conditional z-ts&quot;&gt;if&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-brace z-round z-ts&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator z-logical z-ts&quot;&gt;!&lt;&#x2F;span&gt; &lt;span class=&quot;z-variable z-other z-readwrite z-ts&quot;&gt;$this&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator z-arithmetic z-ts&quot;&gt;-&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator z-relational z-ts&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-ts&quot;&gt;&lt;span class=&quot;z-entity z-name z-function z-ts&quot;&gt;isInTestingEnvironment&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-brace z-round z-ts&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-brace z-round z-ts&quot;&gt;)&lt;&#x2F;span&gt; &lt;span class=&quot;z-keyword z-operator z-logical z-ts&quot;&gt;&amp;amp;&amp;amp;&lt;&#x2F;span&gt; &lt;span class=&quot;z-keyword z-operator z-logical z-ts&quot;&gt;!&lt;&#x2F;span&gt; &lt;span class=&quot;z-variable z-other z-readwrite z-ts&quot;&gt;$this&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator z-arithmetic z-ts&quot;&gt;-&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator z-relational z-ts&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-ts&quot;&gt;&lt;span class=&quot;z-entity z-name z-function z-ts&quot;&gt;eventExistsOnStripe&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-brace z-round z-ts&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other z-readwrite z-ts&quot;&gt;$payload&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-array z-literal z-ts&quot;&gt;&lt;span class=&quot;z-meta z-brace z-square z-ts&quot;&gt;[&lt;&#x2F;span&gt;&lt;span class=&quot;z-string z-quoted z-single z-ts&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-ts&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;id&lt;span class=&quot;z-punctuation z-definition z-string z-end z-ts&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-brace z-square z-ts&quot;&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-brace z-round z-ts&quot;&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-brace z-round z-ts&quot;&gt;)&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-block z-ts&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-block z-ts&quot;&gt;{&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-ts&quot;&gt;&lt;span class=&quot;z-meta z-class z-ts&quot;&gt;&lt;span class=&quot;z-meta z-function z-expression z-ts&quot;&gt;&lt;span class=&quot;z-meta z-block z-ts&quot;&gt;&lt;span class=&quot;z-meta z-block z-ts&quot;&gt;            &lt;span class=&quot;z-keyword z-control z-flow z-ts&quot;&gt;return&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-terminator z-statement z-ts&quot;&gt;;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-ts&quot;&gt;&lt;span class=&quot;z-meta z-class z-ts&quot;&gt;&lt;span class=&quot;z-meta z-function z-expression z-ts&quot;&gt;&lt;span class=&quot;z-meta z-block z-ts&quot;&gt;&lt;span class=&quot;z-meta z-block z-ts&quot;&gt;        &lt;span class=&quot;z-punctuation z-definition z-block z-ts&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-ts&quot;&gt;&lt;span class=&quot;z-meta z-class z-ts&quot;&gt;&lt;span class=&quot;z-meta z-function z-expression z-ts&quot;&gt;&lt;span class=&quot;z-meta z-block z-ts&quot;&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-ts&quot;&gt;&lt;span class=&quot;z-meta z-class z-ts&quot;&gt;&lt;span class=&quot;z-meta z-function z-expression z-ts&quot;&gt;&lt;span class=&quot;z-meta z-block z-ts&quot;&gt;        &lt;span class=&quot;z-variable z-other z-readwrite z-ts&quot;&gt;$class&lt;&#x2F;span&gt; &lt;span class=&quot;z-keyword z-operator z-assignment z-ts&quot;&gt;=&lt;&#x2F;span&gt; &lt;span class=&quot;z-string z-quoted z-single z-ts&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-ts&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;span class=&quot;z-constant z-character z-escape z-ts&quot;&gt;\A&lt;&#x2F;span&gt;pp&lt;span class=&quot;z-constant z-character z-escape z-ts&quot;&gt;\E&lt;&#x2F;span&gt;vent&lt;span class=&quot;z-constant z-character z-escape z-ts&quot;&gt;\S&lt;&#x2F;span&gt;tripe&lt;span class=&quot;z-punctuation z-definition z-string z-end z-ts&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-function-call z-ts&quot;&gt;&lt;span class=&quot;z-punctuation z-accessor z-ts&quot;&gt;.&lt;&#x2F;span&gt; &lt;span class=&quot;z-entity z-name z-function z-ts&quot;&gt;studly_case&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-brace z-round z-ts&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-ts&quot;&gt;&lt;span class=&quot;z-entity z-name z-function z-ts&quot;&gt;str_replace&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-brace z-round z-ts&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-string z-quoted z-single z-ts&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-ts&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;.&lt;span class=&quot;z-punctuation z-definition z-string z-end z-ts&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-comma z-ts&quot;&gt;,&lt;&#x2F;span&gt; &lt;span class=&quot;z-string z-quoted z-single z-ts&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-ts&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;_&lt;span class=&quot;z-punctuation z-definition z-string z-end z-ts&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-separator z-comma z-ts&quot;&gt;,&lt;&#x2F;span&gt; &lt;span class=&quot;z-variable z-other z-readwrite z-ts&quot;&gt;$payload&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-array z-literal z-ts&quot;&gt;&lt;span class=&quot;z-meta z-brace z-square z-ts&quot;&gt;[&lt;&#x2F;span&gt;&lt;span class=&quot;z-string z-quoted z-single z-ts&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-ts&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;type&lt;span class=&quot;z-punctuation z-definition z-string z-end z-ts&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-brace z-square z-ts&quot;&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-brace z-round z-ts&quot;&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-brace z-round z-ts&quot;&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-terminator z-statement z-ts&quot;&gt;;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-ts&quot;&gt;&lt;span class=&quot;z-meta z-class z-ts&quot;&gt;&lt;span class=&quot;z-meta z-function z-expression z-ts&quot;&gt;&lt;span class=&quot;z-meta z-block z-ts&quot;&gt;        
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-ts&quot;&gt;&lt;span class=&quot;z-meta z-class z-ts&quot;&gt;&lt;span class=&quot;z-meta z-function z-expression z-ts&quot;&gt;&lt;span class=&quot;z-meta z-block z-ts&quot;&gt;        &lt;span class=&quot;z-keyword z-control z-conditional z-ts&quot;&gt;if&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-brace z-round z-ts&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-ts&quot;&gt;&lt;span class=&quot;z-entity z-name z-function z-ts&quot;&gt;class_exists&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-brace z-round z-ts&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other z-readwrite z-ts&quot;&gt;$class&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-brace z-round z-ts&quot;&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-brace z-round z-ts&quot;&gt;)&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-block z-ts&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-block z-ts&quot;&gt;{&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-ts&quot;&gt;&lt;span class=&quot;z-meta z-class z-ts&quot;&gt;&lt;span class=&quot;z-meta z-function z-expression z-ts&quot;&gt;&lt;span class=&quot;z-meta z-block z-ts&quot;&gt;&lt;span class=&quot;z-meta z-block z-ts&quot;&gt;            &lt;span class=&quot;z-keyword z-control z-flow z-ts&quot;&gt;return&lt;&#x2F;span&gt; &lt;span class=&quot;z-new z-expr z-ts&quot;&gt;&lt;span class=&quot;z-keyword z-operator z-new z-ts&quot;&gt;new&lt;&#x2F;span&gt; &lt;span class=&quot;z-entity z-name z-type z-ts&quot;&gt;$class&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-brace z-round z-ts&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other z-readwrite z-ts&quot;&gt;$payload&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-brace z-round z-ts&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-terminator z-statement z-ts&quot;&gt;;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-ts&quot;&gt;&lt;span class=&quot;z-meta z-class z-ts&quot;&gt;&lt;span class=&quot;z-meta z-function z-expression z-ts&quot;&gt;&lt;span class=&quot;z-meta z-block z-ts&quot;&gt;&lt;span class=&quot;z-meta z-block z-ts&quot;&gt;        &lt;span class=&quot;z-punctuation z-definition z-block z-ts&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; 
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-ts&quot;&gt;&lt;span class=&quot;z-meta z-class z-ts&quot;&gt;&lt;span class=&quot;z-meta z-function z-expression z-ts&quot;&gt;&lt;span class=&quot;z-meta z-block z-ts&quot;&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-ts&quot;&gt;&lt;span class=&quot;z-meta z-class z-ts&quot;&gt;&lt;span class=&quot;z-meta z-function z-expression z-ts&quot;&gt;&lt;span class=&quot;z-meta z-block z-ts&quot;&gt;        &lt;span class=&quot;z-keyword z-control z-flow z-ts&quot;&gt;return&lt;&#x2F;span&gt; &lt;span class=&quot;z-variable z-other z-readwrite z-ts&quot;&gt;$this&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator z-arithmetic z-ts&quot;&gt;-&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator z-relational z-ts&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-ts&quot;&gt;&lt;span class=&quot;z-entity z-name z-function z-ts&quot;&gt;missingMethod&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-brace z-round z-ts&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-brace z-round z-ts&quot;&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-terminator z-statement z-ts&quot;&gt;;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-ts&quot;&gt;&lt;span class=&quot;z-meta z-class z-ts&quot;&gt;&lt;span class=&quot;z-meta z-function z-expression z-ts&quot;&gt;&lt;span class=&quot;z-meta z-block z-ts&quot;&gt;    &lt;span class=&quot;z-punctuation z-definition z-block z-ts&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-ts&quot;&gt;&lt;span class=&quot;z-meta z-class z-ts&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-block z-ts&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Now, for every event that Stripe sends us, the controller will try to call an event. We just need to create and handle it.&lt;&#x2F;p&gt;
&lt;p&gt;Then we create our event:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;ts&quot; class=&quot;language-ts z-code&quot;&gt;&lt;code class=&quot;language-ts&quot; data-lang=&quot;ts&quot;&gt;&lt;span class=&quot;z-source z-ts&quot;&gt;&lt;span class=&quot;z-meta z-class z-ts&quot;&gt;&lt;span class=&quot;z-storage z-type z-class z-ts&quot;&gt;class&lt;&#x2F;span&gt; &lt;span class=&quot;z-entity z-name z-type z-class z-ts&quot;&gt;StripeCustomerSubscriptionDeleted&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-ts&quot;&gt;&lt;span class=&quot;z-meta z-class z-ts&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-block z-ts&quot;&gt;{&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-ts&quot;&gt;&lt;span class=&quot;z-meta z-class z-ts&quot;&gt;    &lt;span class=&quot;z-comment z-block z-documentation z-ts&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-ts&quot;&gt;&#x2F;**&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-ts&quot;&gt;&lt;span class=&quot;z-meta z-class z-ts&quot;&gt;&lt;span class=&quot;z-comment z-block z-documentation z-ts&quot;&gt;     * Create a new instance.
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-ts&quot;&gt;&lt;span class=&quot;z-meta z-class z-ts&quot;&gt;&lt;span class=&quot;z-comment z-block z-documentation z-ts&quot;&gt;     &lt;span class=&quot;z-punctuation z-definition z-comment z-ts&quot;&gt;*&#x2F;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-ts&quot;&gt;&lt;span class=&quot;z-meta z-class z-ts&quot;&gt;    &lt;span class=&quot;z-storage z-modifier z-ts&quot;&gt;public&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-function z-expression z-ts&quot;&gt;&lt;span class=&quot;z-storage z-type z-function z-ts&quot;&gt;function&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-definition z-function z-ts&quot;&gt;&lt;span class=&quot;z-entity z-name z-function z-ts&quot;&gt;__construct&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-parameters z-ts&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-parameters z-begin z-ts&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-parameter z-ts&quot;&gt;array&lt;&#x2F;span&gt; &lt;span class=&quot;z-variable z-parameter z-ts&quot;&gt;$payload&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-parameters z-end z-ts&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-ts&quot;&gt;&lt;span class=&quot;z-meta z-class z-ts&quot;&gt;&lt;span class=&quot;z-meta z-function z-expression z-ts&quot;&gt;    &lt;span class=&quot;z-meta z-block z-ts&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-block z-ts&quot;&gt;{&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-ts&quot;&gt;&lt;span class=&quot;z-meta z-class z-ts&quot;&gt;&lt;span class=&quot;z-meta z-function z-expression z-ts&quot;&gt;&lt;span class=&quot;z-meta z-block z-ts&quot;&gt;        &lt;span class=&quot;z-variable z-other z-readwrite z-ts&quot;&gt;$this&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator z-arithmetic z-ts&quot;&gt;-&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator z-relational z-ts&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other z-readwrite z-ts&quot;&gt;payload&lt;&#x2F;span&gt; &lt;span class=&quot;z-keyword z-operator z-assignment z-ts&quot;&gt;=&lt;&#x2F;span&gt; &lt;span class=&quot;z-variable z-other z-readwrite z-ts&quot;&gt;$payload&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-terminator z-statement z-ts&quot;&gt;;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-ts&quot;&gt;&lt;span class=&quot;z-meta z-class z-ts&quot;&gt;&lt;span class=&quot;z-meta z-function z-expression z-ts&quot;&gt;&lt;span class=&quot;z-meta z-block z-ts&quot;&gt;    &lt;span class=&quot;z-punctuation z-definition z-block z-ts&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-ts&quot;&gt;&lt;span class=&quot;z-meta z-class z-ts&quot;&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-ts&quot;&gt;&lt;span class=&quot;z-meta z-class z-ts&quot;&gt;    &lt;span class=&quot;z-storage z-modifier z-ts&quot;&gt;public&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-function z-expression z-ts&quot;&gt;&lt;span class=&quot;z-storage z-type z-function z-ts&quot;&gt;function&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-definition z-function z-ts&quot;&gt;&lt;span class=&quot;z-entity z-name z-function z-ts&quot;&gt;getCustomer&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-parameters z-ts&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-parameters z-begin z-ts&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-parameters z-end z-ts&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-ts&quot;&gt;&lt;span class=&quot;z-meta z-class z-ts&quot;&gt;&lt;span class=&quot;z-meta z-function z-expression z-ts&quot;&gt;    &lt;span class=&quot;z-meta z-block z-ts&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-block z-ts&quot;&gt;{&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-ts&quot;&gt;&lt;span class=&quot;z-meta z-class z-ts&quot;&gt;&lt;span class=&quot;z-meta z-function z-expression z-ts&quot;&gt;&lt;span class=&quot;z-meta z-block z-ts&quot;&gt;&lt;span class=&quot;z-punctuation z-whitespace z-comment z-leading z-ts&quot;&gt;        &lt;&#x2F;span&gt;&lt;span class=&quot;z-comment z-line z-double-slash z-ts&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-ts&quot;&gt;&#x2F;&#x2F;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-comment z-line z-double-slash z-ts&quot;&gt; Get the current customer based on $payload&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-ts&quot;&gt;&lt;span class=&quot;z-meta z-class z-ts&quot;&gt;&lt;span class=&quot;z-meta z-function z-expression z-ts&quot;&gt;&lt;span class=&quot;z-meta z-block z-ts&quot;&gt;    &lt;span class=&quot;z-punctuation z-definition z-block z-ts&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-ts&quot;&gt;&lt;span class=&quot;z-meta z-class z-ts&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-block z-ts&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;And our listener:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;ts&quot; class=&quot;language-ts z-code&quot;&gt;&lt;code class=&quot;language-ts&quot; data-lang=&quot;ts&quot;&gt;&lt;span class=&quot;z-source z-ts&quot;&gt;&lt;span class=&quot;z-variable z-other z-readwrite z-ts&quot;&gt;final&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-class z-ts&quot;&gt;&lt;span class=&quot;z-storage z-type z-class z-ts&quot;&gt;class&lt;&#x2F;span&gt; &lt;span class=&quot;z-entity z-name z-type z-class z-ts&quot;&gt;StripeCancelSubscription&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-ts&quot;&gt;&lt;span class=&quot;z-meta z-class z-ts&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-block z-ts&quot;&gt;{&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-ts&quot;&gt;&lt;span class=&quot;z-meta z-class z-ts&quot;&gt;    &lt;span class=&quot;z-comment z-block z-documentation z-ts&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-ts&quot;&gt;&#x2F;**&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-ts&quot;&gt;&lt;span class=&quot;z-meta z-class z-ts&quot;&gt;&lt;span class=&quot;z-comment z-block z-documentation z-ts&quot;&gt;     * Handle the event.
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-ts&quot;&gt;&lt;span class=&quot;z-meta z-class z-ts&quot;&gt;&lt;span class=&quot;z-comment z-block z-documentation z-ts&quot;&gt;     &lt;span class=&quot;z-punctuation z-definition z-comment z-ts&quot;&gt;*&#x2F;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-ts&quot;&gt;&lt;span class=&quot;z-meta z-class z-ts&quot;&gt;    &lt;span class=&quot;z-storage z-modifier z-ts&quot;&gt;public&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-function z-expression z-ts&quot;&gt;&lt;span class=&quot;z-storage z-type z-function z-ts&quot;&gt;function&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-definition z-function z-ts&quot;&gt;&lt;span class=&quot;z-entity z-name z-function z-ts&quot;&gt;handle&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-parameters z-ts&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-parameters z-begin z-ts&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-parameter z-ts&quot;&gt;StripeCustomerSubscriptionDeleted&lt;&#x2F;span&gt; &lt;span class=&quot;z-variable z-parameter z-ts&quot;&gt;$event&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-parameters z-end z-ts&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-ts&quot;&gt;&lt;span class=&quot;z-meta z-class z-ts&quot;&gt;&lt;span class=&quot;z-meta z-function z-expression z-ts&quot;&gt;    &lt;span class=&quot;z-meta z-block z-ts&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-block z-ts&quot;&gt;{&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-ts&quot;&gt;&lt;span class=&quot;z-meta z-class z-ts&quot;&gt;&lt;span class=&quot;z-meta z-function z-expression z-ts&quot;&gt;&lt;span class=&quot;z-meta z-block z-ts&quot;&gt;        &lt;span class=&quot;z-variable z-other z-readwrite z-ts&quot;&gt;$customer&lt;&#x2F;span&gt; &lt;span class=&quot;z-keyword z-operator z-assignment z-ts&quot;&gt;=&lt;&#x2F;span&gt; &lt;span class=&quot;z-variable z-other z-readwrite z-ts&quot;&gt;$event&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator z-arithmetic z-ts&quot;&gt;-&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator z-relational z-ts&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-ts&quot;&gt;&lt;span class=&quot;z-entity z-name z-function z-ts&quot;&gt;getCustomer&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-brace z-round z-ts&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-brace z-round z-ts&quot;&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-terminator z-statement z-ts&quot;&gt;;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-ts&quot;&gt;&lt;span class=&quot;z-meta z-class z-ts&quot;&gt;&lt;span class=&quot;z-meta z-function z-expression z-ts&quot;&gt;&lt;span class=&quot;z-meta z-block z-ts&quot;&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-ts&quot;&gt;&lt;span class=&quot;z-meta z-class z-ts&quot;&gt;&lt;span class=&quot;z-meta z-function z-expression z-ts&quot;&gt;&lt;span class=&quot;z-meta z-block z-ts&quot;&gt;        &lt;span class=&quot;z-variable z-other z-readwrite z-ts&quot;&gt;$customer&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator z-arithmetic z-ts&quot;&gt;-&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator z-relational z-ts&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other z-readwrite z-ts&quot;&gt;subscriptions&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator z-arithmetic z-ts&quot;&gt;-&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator z-relational z-ts&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-ts&quot;&gt;&lt;span class=&quot;z-entity z-name z-function z-ts&quot;&gt;filter&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-brace z-round z-ts&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function z-expression z-ts&quot;&gt;&lt;span class=&quot;z-storage z-type z-function z-ts&quot;&gt;function&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-parameters z-ts&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-parameters z-begin z-ts&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-parameter z-ts&quot;&gt;$subscription&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-parameters z-end z-ts&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-definition z-function z-ts&quot;&gt;&lt;span class=&quot;z-entity z-name z-function z-ts&quot;&gt;use&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-parameters z-ts&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-parameters z-begin z-ts&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-parameter z-ts&quot;&gt;$event&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-parameters z-end z-ts&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-block z-ts&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-block z-ts&quot;&gt;{&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-ts&quot;&gt;&lt;span class=&quot;z-meta z-class z-ts&quot;&gt;&lt;span class=&quot;z-meta z-function z-expression z-ts&quot;&gt;&lt;span class=&quot;z-meta z-block z-ts&quot;&gt;&lt;span class=&quot;z-meta z-function z-expression z-ts&quot;&gt;&lt;span class=&quot;z-meta z-block z-ts&quot;&gt;            &lt;span class=&quot;z-keyword z-control z-flow z-ts&quot;&gt;return&lt;&#x2F;span&gt; &lt;span class=&quot;z-variable z-other z-readwrite z-ts&quot;&gt;$subscription&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator z-arithmetic z-ts&quot;&gt;-&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator z-relational z-ts&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other z-readwrite z-ts&quot;&gt;stripe_id&lt;&#x2F;span&gt; &lt;span class=&quot;z-keyword z-operator z-comparison z-ts&quot;&gt;===&lt;&#x2F;span&gt; &lt;span class=&quot;z-variable z-other z-readwrite z-ts&quot;&gt;$event&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator z-arithmetic z-ts&quot;&gt;-&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator z-relational z-ts&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-other z-readwrite z-ts&quot;&gt;payload&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-array z-literal z-ts&quot;&gt;&lt;span class=&quot;z-meta z-brace z-square z-ts&quot;&gt;[&lt;&#x2F;span&gt;&lt;span class=&quot;z-string z-quoted z-single z-ts&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-ts&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;data&lt;span class=&quot;z-punctuation z-definition z-string z-end z-ts&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-brace z-square z-ts&quot;&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-array z-literal z-ts&quot;&gt;&lt;span class=&quot;z-meta z-brace z-square z-ts&quot;&gt;[&lt;&#x2F;span&gt;&lt;span class=&quot;z-string z-quoted z-single z-ts&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-ts&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;object&lt;span class=&quot;z-punctuation z-definition z-string z-end z-ts&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-brace z-square z-ts&quot;&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-array z-literal z-ts&quot;&gt;&lt;span class=&quot;z-meta z-brace z-square z-ts&quot;&gt;[&lt;&#x2F;span&gt;&lt;span class=&quot;z-string z-quoted z-single z-ts&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-string z-begin z-ts&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;id&lt;span class=&quot;z-punctuation z-definition z-string z-end z-ts&quot;&gt;&amp;#39;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-brace z-square z-ts&quot;&gt;]&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-terminator z-statement z-ts&quot;&gt;;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-ts&quot;&gt;&lt;span class=&quot;z-meta z-class z-ts&quot;&gt;&lt;span class=&quot;z-meta z-function z-expression z-ts&quot;&gt;&lt;span class=&quot;z-meta z-block z-ts&quot;&gt;&lt;span class=&quot;z-meta z-function z-expression z-ts&quot;&gt;&lt;span class=&quot;z-meta z-block z-ts&quot;&gt;        &lt;span class=&quot;z-punctuation z-definition z-block z-ts&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-brace z-round z-ts&quot;&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator z-arithmetic z-ts&quot;&gt;-&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator z-relational z-ts&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-ts&quot;&gt;&lt;span class=&quot;z-entity z-name z-function z-ts&quot;&gt;each&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-brace z-round z-ts&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function z-expression z-ts&quot;&gt;&lt;span class=&quot;z-storage z-type z-function z-ts&quot;&gt;function&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-parameters z-ts&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-parameters z-begin z-ts&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-variable z-parameter z-ts&quot;&gt;$subscription&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-definition z-parameters z-end z-ts&quot;&gt;)&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; &lt;span class=&quot;z-meta z-block z-ts&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-block z-ts&quot;&gt;{&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-ts&quot;&gt;&lt;span class=&quot;z-meta z-class z-ts&quot;&gt;&lt;span class=&quot;z-meta z-function z-expression z-ts&quot;&gt;&lt;span class=&quot;z-meta z-block z-ts&quot;&gt;&lt;span class=&quot;z-meta z-function z-expression z-ts&quot;&gt;&lt;span class=&quot;z-meta z-block z-ts&quot;&gt;            &lt;span class=&quot;z-variable z-other z-readwrite z-ts&quot;&gt;$subscription&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator z-arithmetic z-ts&quot;&gt;-&lt;&#x2F;span&gt;&lt;span class=&quot;z-keyword z-operator z-relational z-ts&quot;&gt;&amp;gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-function-call z-ts&quot;&gt;&lt;span class=&quot;z-entity z-name z-function z-ts&quot;&gt;markAsCancelled&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-brace z-round z-ts&quot;&gt;(&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-brace z-round z-ts&quot;&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-terminator z-statement z-ts&quot;&gt;;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-ts&quot;&gt;&lt;span class=&quot;z-meta z-class z-ts&quot;&gt;&lt;span class=&quot;z-meta z-function z-expression z-ts&quot;&gt;&lt;span class=&quot;z-meta z-block z-ts&quot;&gt;&lt;span class=&quot;z-meta z-function z-expression z-ts&quot;&gt;&lt;span class=&quot;z-meta z-block z-ts&quot;&gt;        &lt;span class=&quot;z-punctuation z-definition z-block z-ts&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-brace z-round z-ts&quot;&gt;)&lt;&#x2F;span&gt;&lt;span class=&quot;z-punctuation z-terminator z-statement z-ts&quot;&gt;;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-ts&quot;&gt;&lt;span class=&quot;z-meta z-class z-ts&quot;&gt;&lt;span class=&quot;z-meta z-function z-expression z-ts&quot;&gt;&lt;span class=&quot;z-meta z-block z-ts&quot;&gt;    &lt;span class=&quot;z-punctuation z-definition z-block z-ts&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-ts&quot;&gt;&lt;span class=&quot;z-meta z-class z-ts&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-block z-ts&quot;&gt;}&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Now that we have our event and listener, we need to tell Laravel how to handle them. To do this, we just add them to the EventServiceProvider:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;ts&quot; class=&quot;language-ts z-code&quot;&gt;&lt;code class=&quot;language-ts&quot; data-lang=&quot;ts&quot;&gt;&lt;span class=&quot;z-source z-ts&quot;&gt;&lt;span class=&quot;z-meta z-class z-ts&quot;&gt;&lt;span class=&quot;z-storage z-type z-class z-ts&quot;&gt;class&lt;&#x2F;span&gt; &lt;span class=&quot;z-entity z-name z-type z-class z-ts&quot;&gt;EventServiceProvider&lt;&#x2F;span&gt; &lt;span class=&quot;z-storage z-modifier z-ts&quot;&gt;extends&lt;&#x2F;span&gt; &lt;span class=&quot;z-entity z-other z-inherited-class z-ts&quot;&gt;ServiceProvider&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-ts&quot;&gt;&lt;span class=&quot;z-meta z-class z-ts&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-block z-ts&quot;&gt;{&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-ts&quot;&gt;&lt;span class=&quot;z-meta z-class z-ts&quot;&gt;    &lt;span class=&quot;z-comment z-block z-documentation z-ts&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-ts&quot;&gt;&#x2F;**&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-ts&quot;&gt;&lt;span class=&quot;z-meta z-class z-ts&quot;&gt;&lt;span class=&quot;z-comment z-block z-documentation z-ts&quot;&gt;     * The event listener mappings for the application.
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-ts&quot;&gt;&lt;span class=&quot;z-meta z-class z-ts&quot;&gt;&lt;span class=&quot;z-comment z-block z-documentation z-ts&quot;&gt;     &lt;span class=&quot;z-punctuation z-definition z-comment z-ts&quot;&gt;*&#x2F;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-ts&quot;&gt;&lt;span class=&quot;z-meta z-class z-ts&quot;&gt;    &lt;span class=&quot;z-storage z-modifier z-ts&quot;&gt;protected&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-field z-declaration z-ts&quot;&gt; &lt;span class=&quot;z-meta z-definition z-property z-ts&quot;&gt;&lt;span class=&quot;z-variable z-object z-property z-ts&quot;&gt;$listen&lt;&#x2F;span&gt;&lt;&#x2F;span&gt; &lt;span class=&quot;z-keyword z-operator z-assignment z-ts&quot;&gt;=&lt;&#x2F;span&gt;&lt;span class=&quot;z-meta z-array z-literal z-ts&quot;&gt; &lt;span class=&quot;z-meta z-brace z-square z-ts&quot;&gt;[&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-ts&quot;&gt;&lt;span class=&quot;z-meta z-class z-ts&quot;&gt;&lt;span class=&quot;z-meta z-field z-declaration z-ts&quot;&gt;&lt;span class=&quot;z-meta z-array z-literal z-ts&quot;&gt;        &lt;span class=&quot;z-comment z-block z-ts&quot;&gt;&lt;span class=&quot;z-punctuation z-definition z-comment z-ts&quot;&gt;&#x2F;*&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-ts&quot;&gt;&lt;span class=&quot;z-meta z-class z-ts&quot;&gt;&lt;span class=&quot;z-meta z-field z-declaration z-ts&quot;&gt;&lt;span class=&quot;z-meta z-array z-literal z-ts&quot;&gt;&lt;span class=&quot;z-comment z-block z-ts&quot;&gt;         * Stripe Billing Events
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-ts&quot;&gt;&lt;span class=&quot;z-meta z-class z-ts&quot;&gt;&lt;span class=&quot;z-meta z-field z-declaration z-ts&quot;&gt;&lt;span class=&quot;z-meta z-array z-literal z-ts&quot;&gt;&lt;span class=&quot;z-comment z-block z-ts&quot;&gt;         &lt;span class=&quot;z-punctuation z-definition z-comment z-ts&quot;&gt;*&#x2F;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-ts&quot;&gt;&lt;span class=&quot;z-meta z-class z-ts&quot;&gt;&lt;span class=&quot;z-meta z-field z-declaration z-ts&quot;&gt;&lt;span class=&quot;z-meta z-array z-literal z-ts&quot;&gt;        \&lt;span class=&quot;z-variable z-other z-readwrite z-ts&quot;&gt;App&lt;&#x2F;span&gt;\&lt;span class=&quot;z-support z-class z-dom z-ts&quot;&gt;Event&lt;&#x2F;span&gt;\&lt;span class=&quot;z-variable z-other z-readwrite z-ts&quot;&gt;StripeCustomerSubscriptionDeleted&lt;&#x2F;span&gt;::&lt;span class=&quot;z-meta z-class z-ts&quot;&gt;&lt;span class=&quot;z-storage z-type z-class z-ts&quot;&gt;class&lt;&#x2F;span&gt; =&amp;gt; [
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-ts&quot;&gt;&lt;span class=&quot;z-meta z-class z-ts&quot;&gt;&lt;span class=&quot;z-meta z-field z-declaration z-ts&quot;&gt;&lt;span class=&quot;z-meta z-array z-literal z-ts&quot;&gt;&lt;span class=&quot;z-meta z-class z-ts&quot;&gt;            \&lt;span class=&quot;z-entity z-name z-type z-class z-ts&quot;&gt;App&lt;&#x2F;span&gt;\&lt;span class=&quot;z-entity z-name z-type z-class z-ts&quot;&gt;EventListener&lt;&#x2F;span&gt;\&lt;span class=&quot;z-entity z-name z-type z-class z-ts&quot;&gt;StripeCancelSubscription&lt;&#x2F;span&gt;:&lt;span class=&quot;z-entity z-name z-type z-class z-ts&quot;&gt;class&lt;&#x2F;span&gt;
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;span class=&quot;z-source z-ts&quot;&gt;&lt;span class=&quot;z-meta z-class z-ts&quot;&gt;&lt;span class=&quot;z-meta z-field z-declaration z-ts&quot;&gt;&lt;span class=&quot;z-meta z-array z-literal z-ts&quot;&gt;&lt;span class=&quot;z-meta z-class z-ts&quot;&gt;        ],
&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;And that&#x27;s it!&lt;&#x2F;p&gt;
&lt;p&gt;You can apply this in countless situations where you&#x27;re handling a lot of conditions.&lt;&#x2F;p&gt;
&lt;p&gt;Just create events and separate your handlers. Your code will be cleaner and easier to understand. And you can also run some of those listeners in queues, so your users don&#x27;t need to wait for a response that takes a long time to return.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Of course&lt;&#x2F;strong&gt;, my example only explains one situation. I really think this should only be applied if you have a lot of events and listeners to handle in your workflow. Otherwise, just keep the default approach.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;em&gt;Originally published on &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;gabrielkoerich.medium.com&#x2F;refactoring-to-events-72cba6873d9c&quot;&gt;Medium&lt;&#x2F;a&gt;&lt;&#x2F;em&gt;&lt;&#x2F;p&gt;
</content>
    </entry>
    <entry xml:lang="en">
        <title>How to forget mistakes and not regret the past?</title>
        <published>2018-01-25T00:00:00+00:00</published>
        <updated>2018-01-25T00:00:00+00:00</updated>
        <author>
            <name>Gabriel Koerich</name>
        </author>
        <link rel="alternate" type="text/html" href="https://gabrielkoerich.com/posts/how-to-forget-mistakes-and-not-regret-the-past/"/>
        <id>https://gabrielkoerich.com/posts/how-to-forget-mistakes-and-not-regret-the-past/</id>
        <summary>A short note on getting past a mistake: forgive yourself, own what it caused, learn from it, and swap regret for a plan for next time.</summary>
        <content type="html" xml:base="https://gabrielkoerich.com/posts/how-to-forget-mistakes-and-not-regret-the-past/">&lt;p&gt;Did some shit, right?&lt;&#x2F;p&gt;
&lt;p&gt;Okay, that&#x27;s normal. But how do you get past it?&lt;&#x2F;p&gt;
&lt;p&gt;First, forgive yourself. You&#x27;ll be stuck with yourself for a long time.&lt;&#x2F;p&gt;
&lt;p&gt;Apologize for what you did and for what it caused, and never curse yourself. You wouldn&#x27;t let someone else insult you like that, right? Don&#x27;t do it to yourself.&lt;&#x2F;p&gt;
&lt;p&gt;Forgiving yourself doesn&#x27;t remove your responsibility, and there&#x27;s no magic here. You have to understand the mistake, learn from it and turn it into something useful. That means making a plan and doing the right thing next time.&lt;&#x2F;p&gt;
&lt;p&gt;Forget about regret. Instead of &amp;quot;if only I had…&amp;quot;, say &amp;quot;now I know what to do when this happens again&amp;quot;. You can&#x27;t change anything in the past, ever.&lt;&#x2F;p&gt;
&lt;p&gt;Focus on making today better, and your future gets better with it. Everything you do today prepares you for tomorrow.&lt;&#x2F;p&gt;
&lt;p&gt;Nothing good lasts forever, and nothing bad does either. If it&#x27;s bad, it has to change. Nothing is permanent.&lt;&#x2F;p&gt;
&lt;p&gt;Getting stuck in a rut, or feeling you can&#x27;t change your situation, is dangerous. If you keep doing what you&#x27;ve always done, you&#x27;ll keep getting what you&#x27;ve always had. Keep changing: fix what isn&#x27;t working, and do something completely different from what you usually do or who you usually are.&lt;&#x2F;p&gt;
&lt;p&gt;Learn something new, do what you&#x27;ve never done, and think of and do something nobody would expect from you.&lt;&#x2F;p&gt;
&lt;p&gt;Does thinking about it scare you? Great. If you&#x27;re not afraid, you&#x27;re not trying something worthwhile or taking enough risks.&lt;&#x2F;p&gt;
&lt;p&gt;Everyone has a plan until they get punched in the mouth. What counts is what you do after that.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;em&gt;Originally published on &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;gabrielkoerich.medium.com&#x2F;como-esquecer-os-erros-e-n%C3%A3o-se-arrepender-do-passado-a0fd72e243f7&quot;&gt;Medium&lt;&#x2F;a&gt;&lt;&#x2F;em&gt;&lt;&#x2F;p&gt;
</content>
    </entry>
    <entry xml:lang="en">
        <title>Does the Japan case overthrow buy and hold?</title>
        <published>2017-06-17T00:00:00+00:00</published>
        <updated>2017-06-17T00:00:00+00:00</updated>
        <author>
            <name>Gabriel Koerich</name>
        </author>
        <link rel="alternate" type="text/html" href="https://gabrielkoerich.com/posts/does-the-japan-case-overthrow-buy-and-hold/"/>
        <id>https://gabrielkoerich.com/posts/does-the-japan-case-overthrow-buy-and-hold/</id>
        <summary>The Nikkei lost money for 20 years after 1989. Does that make buy and hold a bad strategy? Valuations, dividends and how Japan then differs from Brazil now.</summary>
        <content type="html" xml:base="https://gabrielkoerich.com/posts/does-the-japan-case-overthrow-buy-and-hold/">&lt;p&gt;Back to stock market risk, this time because of a comment on the last post about the Japanese market. For 20 years, from 1989 to 2009, the Nikkei had a negative annualized return.&lt;&#x2F;p&gt;
&lt;img src=&quot;https:&#x2F;&#x2F;gabrielkoerich.com&#x2F;images&#x2F;posts&#x2F;does-the-japan-case-overthrow-buy-and-hold.jpeg?h=781d7b89762c4ce1f345&quot; alt=&quot;Nikkei 225 index from 1985 to 2012&quot;&gt;
&lt;p&gt;&lt;em&gt;The Nikkei&#x27;s Terrible 20s: Every Investor&#x27;s Biggest Fear&lt;&#x2F;em&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Because of this, some people may see buy and hold as a bad strategy. But we can look more closely at the markets, the indices and the timing, and check whether the problem really is this way of investing.&lt;&#x2F;p&gt;
&lt;p&gt;The Japanese market had risen for almost 20 years with nothing to justify it. Over the previous 10 years, the Nikkei had gained 21.2% per year on average, and the numbers got too inflated. Shares traded above 80 times earnings, which shows how irrational the period was. For comparison, a market is considered overvalued when its P&#x2F;E is above 22.5. Sooner or later the bubble would burst.&lt;&#x2F;p&gt;
&lt;p&gt;The fact I consider most important, and the one that changes the results completely, is that the Nikkei 225, like the DJI, does not count dividends. Don&#x27;t ask me why. And even with the whole fall in the index, ignoring dividends, some stocks still rose in value: the ones that were not priced at the same absurd average as the market.&lt;&#x2F;p&gt;
&lt;p&gt;We also have to consider how different the two countries are. Brazil and Japan have completely different markets, economies and moments. In 1989, Japan was already a stable, mature economy. Brazil today is a different case: we still have a lot to develop in economic and financial terms.&lt;&#x2F;p&gt;
&lt;p&gt;Above all, however bad buy and hold was in this period, any other stock market strategy would have done worse. Nobody is a magician at timing these tops and bottoms. It makes no sense to judge a strategy that wins over long periods by its one exception.&lt;&#x2F;p&gt;
&lt;p&gt;So what can we take from this story?&lt;&#x2F;p&gt;
&lt;p&gt;As in the American bubble at the start of the last decade, the Japanese market&#x27;s P&#x2F;E was well above the overvalued line. The biggest problem is that even if we could spot a coming bubble this way, how would we know when it would burst? The market could keep rising for many years with no trouble. It takes a lot of study and discipline not to get caught in that frantic optimism. Personally, I would never invest in stocks with a P&#x2F;E that high.&lt;&#x2F;p&gt;
&lt;p&gt;Originally published on 10&#x2F;10&#x2F;2010 on &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;rendapassiva.wordpress.com&#x2F;2010&#x2F;10&#x2F;10&#x2F;o-caso-do-japao-derruba-o-buy-and-hold&quot;&gt;my old anonymous blog&lt;&#x2F;a&gt;, with some corrections and adaptations.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;em&gt;Originally published on &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;gabrielkoerich.medium.com&#x2F;o-caso-do-jap%C3%A3o-derruba-o-buy-and-hold-7c73cbf5c4aa&quot;&gt;Medium&lt;&#x2F;a&gt;&lt;&#x2F;em&gt;&lt;&#x2F;p&gt;
</content>
    </entry>
    <entry xml:lang="en">
        <title>Risk on the stock market [1]</title>
        <published>2017-06-16T00:00:00+00:00</published>
        <updated>2017-06-16T00:00:00+00:00</updated>
        <author>
            <name>Gabriel Koerich</name>
        </author>
        <link rel="alternate" type="text/html" href="https://gabrielkoerich.com/posts/risk-on-the-stock-market-1/"/>
        <id>https://gabrielkoerich.com/posts/risk-on-the-stock-market-1/</id>
        <summary>What risk means for a long term stock investor. Following Benjamin Graham, a price swing is not a loss, and the real risk is paying too much for a company.</summary>
        <content type="html" xml:base="https://gabrielkoerich.com/posts/risk-on-the-stock-market-1/">&lt;img src=&quot;https:&#x2F;&#x2F;gabrielkoerich.com&#x2F;images&#x2F;posts&#x2F;risk-on-the-stock-market-1.jpeg?h=0f45373c363f153e0870&quot; alt=&quot;Stock ticker board with prices and percentage changes&quot;&gt;
&lt;p&gt;Whenever I told someone that I invest in the stock market (I stopped telling people, for the reason below), I heard impressed comments and warnings that shares are very risky. That is the first thought people have about their own money. No one wants to risk losing what is theirs, but everyone wants to spend on the latest fashion, right?&lt;&#x2F;p&gt;
&lt;p&gt;Risk in the stock market deserves a longer discussion. Stocks are a very long-term investment, a place for money you will not need for a long time. People usually call risk a possible fall or swing in a share&#x27;s price, even when the fall is cyclical or temporary, and even when the investor has not sold.&lt;&#x2F;p&gt;
&lt;p&gt;That makes the definition of risk for shares confusing, because the same shares can look safe or unsafe in different periods, or even in the same one. This is roughly the point I want to make. Historically, stocks have had the highest long-term return (at least in the US), but if price volatility over that long term counts as risk, then the same stocks are both safe and unsafe.&lt;&#x2F;p&gt;
&lt;p&gt;This passage from &amp;quot;The Intelligent Investor&amp;quot; by Benjamin Graham, in my own free translation, shows the idea well:&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;&amp;quot;If a portfolio of shares of qualified companies shows a satisfactory return over the years, common sense says those shares have proven to be &#x27;safe&#x27;. Their market value certainly fluctuated during that period, but they were simply not sold below the price the investor paid.&amp;quot;&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;The book suggests we avoid the confusion completely by using the word risk only for a real loss: selling the shares at a loss, a management problem, steadily falling profits, poor prospects, stalled growth or, most often, paying too high a price for the company.&lt;&#x2F;p&gt;
&lt;p&gt;After a major crisis like the one in 2008, most investors see the market as very risky. They forget that the fall in prices has made stocks much safer than before. People leave the market when they should be buying, and they forget that over the long term stocks tend to return more than any other investment.&lt;&#x2F;p&gt;
&lt;p&gt;I hope to write more about risk in the next posts, and so keep justifying why I hold between 80% and 90% in stocks!&lt;&#x2F;p&gt;
&lt;p&gt;Originally published on 10&#x2F;06&#x2F;2010 on &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;rendapassiva.wordpress.com&#x2F;2010&#x2F;10&#x2F;06&#x2F;risco-na-bolsa-de%C2%A0valores&#x2F;&quot;&gt;my old anonymous blog&lt;&#x2F;a&gt;, with some corrections and adaptations.&lt;&#x2F;p&gt;
&lt;p&gt;PS: Today I have 40% of my portfolio in stocks. I admit I was much more optimistic about Brazil back then. A second part is coming, going deeper into risk, return and the efficient frontier, and into where Markowitz was wrong.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;em&gt;Originally published on &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;gabrielkoerich.medium.com&#x2F;risco-na-bolsa-de-valores-1-e1e7a355ca79&quot;&gt;Medium&lt;&#x2F;a&gt;&lt;&#x2F;em&gt;&lt;&#x2F;p&gt;
</content>
    </entry>
    <entry xml:lang="en">
        <title>Simple workflow with Laravel, a look behind Bulldesk</title>
        <published>2016-05-10T00:00:00+00:00</published>
        <updated>2016-05-10T00:00:00+00:00</updated>
        <author>
            <name>Gabriel Koerich</name>
        </author>
        <link rel="alternate" type="text/html" href="https://gabrielkoerich.com/posts/simple-workflow-with-laravel-a-look-behind-bulldesk/"/>
        <id>https://gabrielkoerich.com/posts/simple-workflow-with-laravel-a-look-behind-bulldesk/</id>
        <summary>The stack and the workflow behind Bulldesk in 2016: PHP 7 and Laravel, VueJS with Laravel Elixir, tests and deploys, piece by piece.</summary>
        <content type="html" xml:base="https://gabrielkoerich.com/posts/simple-workflow-with-laravel-a-look-behind-bulldesk/">&lt;img src=&quot;https:&#x2F;&#x2F;gabrielkoerich.com&#x2F;images&#x2F;posts&#x2F;simple-workflow-with-laravel-a-look-behind-bulldesk.jpeg?h=da1a6ba07f0d89c2ec66&quot; alt=&quot;Bulldesk logo over an office&quot;&gt;
&lt;p&gt;I&#x27;ve been meaning to write this post for a long time and kept putting it off.&lt;&#x2F;p&gt;
&lt;p&gt;Every now and then someone asks me what technology runs Bulldesk and what my workflow looks like. The stack is simple, so let&#x27;s go piece by piece.&lt;&#x2F;p&gt;
&lt;h4 id=&quot;php-7-laravel&quot;&gt;PHP 7 + Laravel &lt;a class=&quot;zola-anchor&quot; href=&quot;#php-7-laravel&quot; aria-label=&quot;Link to this section&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;h4&gt;
&lt;p&gt;I&#x27;ve loved this framework since I found it in 2012. It was on version 3 back then, and it&#x27;s absurd how much it has grown. Picking it was a bit of a gamble at the time. I already knew and admired Symfony, though, and Laravel used several Symfony components, so after CodeIgniter &amp;quot;ended&amp;quot; the choice wasn&#x27;t that hard.&lt;&#x2F;p&gt;
&lt;p&gt;PHP 7 came out in December. I didn&#x27;t have much patience, so I upgraded the production server in January. It wasn&#x27;t much of a risk, because the tests had been running on 7.0 in Travis since 2015. The speedup was nice, but nothing absurd.&lt;&#x2F;p&gt;
&lt;h4 id=&quot;vuejs-laravel-elixir&quot;&gt;VueJS + Laravel Elixir &lt;a class=&quot;zola-anchor&quot; href=&quot;#vuejs-laravel-elixir&quot; aria-label=&quot;Link to this section&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;h4&gt;
&lt;p&gt;Vue is the easiest JavaScript framework (yes, another one) I&#x27;ve ever used. It&#x27;s fast, light and intuitive. I&#x27;ve never enjoyed frontend work as much as I do now.&lt;&#x2F;p&gt;
&lt;p&gt;And of course I have to mention Laravel Elixir, a &amp;quot;helper&amp;quot; for Gulp. With it my &lt;code&gt;gulpfile.js&lt;&#x2F;code&gt; has only 35 fucking lines! It&#x27;s beautiful.&lt;&#x2F;p&gt;
&lt;h4 id=&quot;local-development-with-homestead&quot;&gt;Local development with Homestead &lt;a class=&quot;zola-anchor&quot; href=&quot;#local-development-with-homestead&quot; aria-label=&quot;Link to this section&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;h4&gt;
&lt;p&gt;On bigger projects the development environment has to match production. Vagrant and Homestead make that simple.&lt;&#x2F;p&gt;
&lt;p&gt;At Bulldesk we also run node.js for real-time notifications, Memcached for caching, Redis for sessions and Beanstalkd for queues. Installing all of that by hand would be a pain, and Homestead takes care of it.&lt;&#x2F;p&gt;
&lt;p&gt;It&#x27;s still not the easiest or fastest thing to install and set up, which may be why Taylor just launched Laravel Valet, but that&#x27;s a topic for another post.&lt;&#x2F;p&gt;
&lt;h4 id=&quot;deploy-with-laravel-forge-digital-ocean&quot;&gt;Deploy with Laravel Forge + Digital Ocean &lt;a class=&quot;zola-anchor&quot; href=&quot;#deploy-with-laravel-forge-digital-ocean&quot; aria-label=&quot;Link to this section&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;h4&gt;
&lt;p&gt;I don&#x27;t even have words for how good this combo is. I had used Digital Ocean before, but creating a server and setting it up from scratch is tedious.&lt;&#x2F;p&gt;
&lt;p&gt;At Bulldesk, every task that doesn&#x27;t need to answer the user right away goes to a queue that runs in the background. That covers all the automation, campaigns and email marketing.&lt;&#x2F;p&gt;
&lt;p&gt;Forge starts and stops workers, deploys from GitHub, and configures nginx, the firewall, the network, SSL and more. When I need a new server, it does everything for me. It saves a lot of work across the whole DevOps side.&lt;&#x2F;p&gt;
&lt;h4 id=&quot;backups-on-amazon-logs-on-papertrail-errors-on-bugsnag&quot;&gt;Backups on Amazon, logs on Papertrail, errors on Bugsnag &lt;a class=&quot;zola-anchor&quot; href=&quot;#backups-on-amazon-logs-on-papertrail-errors-on-bugsnag&quot; aria-label=&quot;Link to this section&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;h4&gt;
&lt;p&gt;Backups go to Amazon automatically twice a day.&lt;&#x2F;p&gt;
&lt;p&gt;Every log line from the app goes to Papertrail, which makes it much easier to follow what&#x27;s happening in real time. I don&#x27;t need to ssh into the server to find out what happened.&lt;&#x2F;p&gt;
&lt;p&gt;When an exception shows up, it goes straight to Bugsnag. Bugsnag is the one that wakes me up when something breaks.&lt;&#x2F;p&gt;
&lt;h4 id=&quot;that-s-all&quot;&gt;That&#x27;s all &lt;a class=&quot;zola-anchor&quot; href=&quot;#that-s-all&quot; aria-label=&quot;Link to this section&quot;&gt;#&lt;&#x2F;a&gt;
&lt;&#x2F;h4&gt;
&lt;p&gt;Or at least that&#x27;s all I can remember right now.&lt;&#x2F;p&gt;
&lt;p&gt;I like sharing this kind of thing because it shows me what other projects use too.&lt;&#x2F;p&gt;
&lt;p&gt;What would you do differently?&lt;&#x2F;p&gt;
&lt;p&gt;&lt;em&gt;Originally published on &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;gabrielkoerich.medium.com&#x2F;simples-workflow-com-laravel-uma-vis%C3%A3o-por-tr%C3%A1s-do-bulldesk-8a781e1a7684&quot;&gt;Medium&lt;&#x2F;a&gt;&lt;&#x2F;em&gt;&lt;&#x2F;p&gt;
</content>
    </entry>
    <entry xml:lang="en">
        <title>Please don&#x27;t create unnecessary abstractions</title>
        <published>2016-02-27T00:00:00+00:00</published>
        <updated>2016-02-27T00:00:00+00:00</updated>
        <author>
            <name>Gabriel Koerich</name>
        </author>
        <link rel="alternate" type="text/html" href="https://gabrielkoerich.com/posts/please-dont-create-unnecessary-abstractions/"/>
        <id>https://gabrielkoerich.com/posts/please-dont-create-unnecessary-abstractions/</id>
        <summary>Why a PostRepositoryInterface with one implementation is a waste, and why I start with as little code as possible and grow it when the project asks.</summary>
        <content type="html" xml:base="https://gabrielkoerich.com/posts/please-dont-create-unnecessary-abstractions/">&lt;p&gt;The last time I saw a discussion about Laravel, it scared me a little. People take abstraction very seriously and want to create interfaces and classes that nobody needs.&lt;&#x2F;p&gt;
&lt;p&gt;The question was about creating a &lt;code&gt;PostRepositoryInterface&lt;&#x2F;code&gt;. It would only be an abstraction over the default class, &lt;code&gt;PostEloquentRepository&lt;&#x2F;code&gt;, which in this case wraps the &lt;code&gt;Post&lt;&#x2F;code&gt; model methods. Several tutorials out there tell you to do exactly this, because &amp;quot;one day&amp;quot; you &amp;quot;may&amp;quot; want to move your persistence layer to Mongo or switch your ORM. Really?&lt;&#x2F;p&gt;
&lt;p&gt;A repository for the model is a good idea. As the project grows, the model can get too big and hard to maintain. To avoid repeating code in the controllers, put the methods you use in several places in the matching repository.&lt;&#x2F;p&gt;
&lt;p&gt;The problem starts when this turns into an abstraction nobody needs. What is a &lt;code&gt;PostRepositoryInterface&lt;&#x2F;code&gt; for, if all it does is hand you an instance of &lt;code&gt;PostEloquentRepository&lt;&#x2F;code&gt;? Will your implementation ever change? Almost nobody switches database type (relational or non-relational) or ORM in the middle of a project. And if you do, is changing one class all it takes? Of course not. I know it isn&#x27;t.&lt;&#x2F;p&gt;
&lt;p&gt;I always start from the idea that the first iteration should have as little code as possible. Don&#x27;t add interfaces that will only ever have one implementation, or other classes you don&#x27;t need. Later, it is much easier to take something simple and grow it into something more complete than to maintain an absurd abstraction when you no longer remember what it was for or why you wrote it.&lt;&#x2F;p&gt;
&lt;p&gt;I say this because I made this mistake many times. I had read many books on software design principles, and I thought everything had to be followed to the letter. That doesn&#x27;t always make sense. I ended up creating lots of classes and interfaces just because it felt like the right thing to do.&lt;&#x2F;p&gt;
&lt;p&gt;Someone could still argue that all this abstraction exists so you can swap the implementation in tests and never touch the persistence layer. Fine, maybe. But is the trade-off worth it? In my opinion, tests that mock every dependency end up testing almost nothing. They only catch typos. I prefer integration tests that do what a user would actually do on the production server.&lt;&#x2F;p&gt;
&lt;p&gt;So please, create abstractions only when you need them. How do you know when you need one? When a method or class takes a typehinted parameter or dependency that has more than one implementation. Then an interface is more than valid, it is required. When your application grows (we hope it will), you don&#x27;t want a monster. You want something simple whose complexity you can change quickly, without much stress.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;em&gt;Originally published on &lt;a rel=&quot;noopener&quot; target=&quot;_blank&quot; href=&quot;https:&#x2F;&#x2F;gabrielkoerich.medium.com&#x2F;por-favor-n%C3%A3o-crie-abstra%C3%A7%C3%B5es-desnecess%C3%A1rias-96e97ad2a51b&quot;&gt;Medium&lt;&#x2F;a&gt;&lt;&#x2F;em&gt;&lt;&#x2F;p&gt;
</content>
    </entry>
</feed>
