All articles

Every coin counted. Kaspa nodes learn to find all of a token's coins, with a cap on the work

The headline Every coin counted above a debate panel of Maxim Biryukov, D-Stacks, IzioDev and Hans Moog behind a red desk, each with a short quote from the R&D chat
Design by kaspa.news, with profile photos from GitHub and Telegram.

Maxim Biryukov is close to giving Kaspa nodes a question they can't answer today. Show me every coin that belongs to this token. Before it can land, the developers reviewing it had to settle something else first. How much work should one question be allowed to cost a node? D-Stacks warned that a single request could send a node through millions of database lookups, and the answer they reached is a budget on every request.

The change adds an optional index to Rusty Kaspa, the main Kaspa node software, switched on with --covenant-index (pull request). It passes all of its automated checks, and D-Stacks has said it can be merged once the tests pass. It still needs a final approval and isn't in any release yet.

A node that knows your tokens

A node that runs the address index can already list the coins sitting at an address. A KCC-20 token works differently. Its coins are spread across every holder's address, and what ties them together is the token's covenant ID, an identifier that follows a contract from one transaction to the next. Today no node can be asked for all the coins that carry one covenant ID. So wallets and apps that show tokens rely on indexers, separate programs that read the chain block by block and keep their own database.

Builders asked for a fix on Kas-Smiths, the forum where Kaspa standards are discussed. A builder called Karrrl wanted one call that shows "all holders, all listed tokens, total circulating supply for any covenant token without a separate backend" (Kas-Smiths). Another, ingram_it, put the stakes bluntly. "If an additional server is required between the user and the blockchain, then the user is no longer interacting directly with the blockchain, but with the developer's infrastructure" (Kas-Smiths). Manyfest replied that the call was planned, and argued that an app doesn't have to trust its indexer blindly, because it can check what the indexer tells it (Kas-Smiths).

Michael Sutton had backed the idea months earlier. "In general I tend to think we should not limit things by our own imagination," he wrote, calling covenant indexing "cheap enough and most likely valuable enough to be worth it" (R&D chat). Later he gave the reason that matters most to token holders. Making sense of a token's state may still need an outside indexer, he wrote, but in some cases he still wants evidence from the node itself that no other coins exist under a covenant, for example to track the full supply of a token.

Michael Sutton on Telegram

How the index works

The groundwork came from D-Stacks, who rebuilt the address index so a node answers in pages instead of sending everything at once. When it was merged, IzioDev said it "sets a ground for get utxos by covenant id that is next" (R&D chat).

Maxim's index sits on top of that work. For every coin that carries a covenant ID, the node keeps a small pointer, sorted by covenant ID, then by when the coin was created, then by address. Amounts are read from the existing address index, so nothing is stored twice. A wallet can ask for every coin of one or more tokens, optionally only from a certain stretch of time or only at certain addresses. The answer comes back page by page, with a cursor that tells the wallet where to carry on (pull request).

One question, millions of lookups

The trouble was in how those pages were limited. In the first version, a page stopped after a set number of coins, 1,000 by default and up to 5,000 on an ordinary node. The work the node did to find them wasn't counted. A request could also name up to 5,000 covenant IDs and 5,000 addresses, and D-Stacks pointed out that the node might have to look up every pairing of the two.

D-Stacks on Telegram

"I think this creates a major discrepancy between the limit we supply and the db work performed, that may be exploitable," D-Stacks wrote. The proposed fix was to limit each request by the database work it causes, not by the number of coins it returns.

Maxim doubted the worst case would ever happen. A real request was "more likely 5000+5000 instead of multiplication," he replied (R&D chat). An attacker would first have to create all those coins on the chain, which he said comes at the "same cost as bloating utxos" (R&D chat). Node operators can also switch the index off or only let trusted clients use it (R&D chat). Maxim said he didn't mind changing the limit, but warned that counting work instead of coins would be a "terrible user experience" (R&D chat).

D-Stacks didn't back down. "I agree, on terrible user experience, but I think security should come first" (R&D chat). Anyone can shape on-chain activity on purpose to set up such a request, D-Stacks added (R&D chat). A little later came a sharper version of the worry. Even one covenant ID, one address and a long stretch of time might be enough, D-Stacks suggested, if the node makes a fresh lookup at each new point in that stretch (R&D chat).

Should the node guard itself at all?

IzioDev questioned whether limits like these protect a node in the first place.

IzioDev on Telegram

The real protection sits in front of the node, IzioDev wrote, at the load balancer that spreads traffic across servers. And if the node did take on that job, where would it stop? IzioDev asked whether the next step would be to "add authentication mechanisms" to the node too (R&D chat). D-Stacks agreed that "a ddos can never realistically be mitigated, but i don't think that is a reason to leave a dos vector open" (R&D chat). A DoS attack tries to knock a service over by overloading it, and a DDoS does the same from many machines at once.

Hans Moog, who works on vProgs and posts in the chat as Guy Fawkes, went further. He asked whether this index belongs in the node at all.

Hans Moog on Telegram

He suggested the node could serve the latest unspent coins of a covenant, while historical queries move to a separate service with its own index. He thinks people will likely want much more over time, such as an index of every KCC-20 token with its symbol and balances, and a separate service could grow into that without changing the node. D-Stacks replied that this index only holds unspent coins (R&D chat). Hans answered that searching them by a range of time is "still a historic query" (R&D chat). D-Stacks said those time ranges are what let the node split its answers into pages (R&D chat). Nobody else took up Hans' wider point in the chat, and the index stayed inside the node.

A budget on every question

Maxim had already said that limiting work instead of coins wasn't hard. His worry was a page that runs out of work before it finds anything. "It would be confusing," he wrote.

Maxim Biryukov on Telegram

D-Stacks still preferred a work limit, but didn't want to block the change alone if everyone else thought the risk wasn't real (R&D chat). A few days later Maxim made the switch.

Each request now carries a budget. Jumping to a new spot in the database costs 10 units, and stepping to the next entry costs 1. The default is 1,000 units, and a node only accepts more than 5,000 if its operator starts it with --unsaferpc. A page never holds more coins than its budget (commit). The cost is the one Maxim described. A search that has to look through a lot can come back with a thin page, or an empty one with a cursor, and the wallet keeps asking.

D-Stacks then gave the go-ahead to merge if the tests pass, with two optional suggestions. Both aim to keep a plain request for every coin of a token, with no addresses given, returning full pages, and D-Stacks was also fine leaving it as is (R&D chat). IzioDev wanted to keep it simple.

IzioDev on Telegram

Maxim asked the reviewers to approve or point to the changes they need, and IzioDev promised to check shortly (R&D chat).

What changes for token holders

Once it is merged and released, a wallet connected to its own node could list every coin of a KCC-20 token and add up its supply, with the node itself as evidence that nothing is missing. The node answers which coins exist, not what they mean. Reading balances and other token details still needs knowledge of the token's contract, which today means an indexer or an app that understands it.

The index is optional, so public nodes that leave it off won't answer these questions. The first version also leaves out the JavaScript and browser toolkit that many wallets use. IzioDev has offered to add that on top (R&D chat).

The pull request now waits for a final approval. Until a release ships it, wallets that show KCC-20 tokens keep leaning on indexers.

Telegram sources link to public messages in the Kaspa Core R&D (public) channel.

More Kaspa Articles

All articles