Skip to content

feat(nano): implement address balance in global state RFC#1431

Closed
msbrogli wants to merge 1 commit into
masterfrom
feat/nano-address-balance
Closed

feat(nano): implement address balance in global state RFC#1431
msbrogli wants to merge 1 commit into
masterfrom
feat/nano-address-balance

Conversation

@msbrogli

@msbrogli msbrogli commented Sep 25, 2025

Copy link
Copy Markdown
Member

Motivation

Implements the RFC HathorNetwork/rfcs#108, introducing a protocol-level global address-balance map and a TransferHeader that bridges UTXO balances and this shared balance domain. See the linked RFC for the full motivation, design, and rationale.

The implementation is gated behind the TRANSFER_HEADER feature activation bit so the new transaction shape and global-state semantics only apply once the feature is active.

Acceptance Criteria

  • A new TransferHeader transaction header is parseable, serializable, and included in the transaction sighash, with at most one allowed per transaction.
  • TransferHeader verification enforces the RFC's structural rules (positive amounts, referenced InputAddress entries, unique (address, token) pairs on each side, no overlap between debit and credit sides, valid regular addresses, resolved token_index, script authorization).
  • Transaction-level balance verification extends Hathor's per-token accounting equation to include transfer inputs as sources and transfer outputs as sinks, enabling UTXO<->global transfers, pure global-balance transfers, and caller-funded nano calls.
  • Per-address replay protection via monotonic seqnums is implemented, reusing the nano-contract seqnum window constants (MAX_SEQNUM_JUMP_SIZE at block execution, MAX_SEQNUM_DIFF_MEMPOOL in the mempool).
  • Block execution atomically applies transfer-header debits/credits and seqnum updates alongside nano-contract state changes, with rollback on failure; the block executor's topological sorter respects address-seqnum ordering.
  • Global address balances and seqnums are persisted in the same patricia-trie structure used for nano-contract state, exposed through block_storage APIs (get_address_balance, get_address_seqnum).
  • Nano-contract blueprint-facing syscalls get_address_balance, get_address_balance_before_current_call, and transfer_to_address are available, with transfer_to_address rejecting contract IDs and raising on insufficient treasury balance.
  • When a transaction carries both TransferHeader and NanoHeader with non-empty transfer inputs, only the caller address may be debited and all TxTransferInputs must reference address_index = 0.
  • GET /thin_wallet/address_balance returns the address's global balances per token and current global seqnum alongside the existing UTXO-derived tokens_data.
  • The feature is behind the TRANSFER_HEADER feature-activation bit.

Checklist

@msbrogli msbrogli self-assigned this Sep 25, 2025
@msbrogli msbrogli moved this from Todo to In Progress (WIP) in Hathor Network Sep 25, 2025
@github-actions

github-actions Bot commented Sep 25, 2025

Copy link
Copy Markdown

🐰 Bencher Report

Branchfeat/nano-address-balance
Testbedubuntu-22.04

🚨 1 Alert

BenchmarkMeasure
Units
ViewBenchmark Result
(Result Δ%)
Lower Boundary
(Limit %)
sync-v2 (up to 20000 blocks)Latency
minutes (m)
📈 plot
🚷 threshold
🚨 alert (🔔)
1.37 m
(-19.74%)Baseline: 1.70 m
1.53 m
(112.14%)

Click to view all benchmark results
BenchmarkLatencyBenchmark Result
minutes (m)
(Result Δ%)
Lower Boundary
minutes (m)
(Limit %)
Upper Boundary
minutes (m)
(Limit %)
sync-v2 (up to 20000 blocks)📈 view plot
🚷 view threshold
🚨 view alert (🔔)
1.37 m
(-19.74%)Baseline: 1.70 m
1.53 m
(112.14%)

2.04 m
(66.88%)
🐰 View full continuous benchmarking report in Bencher

@msbrogli msbrogli force-pushed the feat/nano-address-balance branch 13 times, most recently from 8899037 to c63ed89 Compare October 2, 2025 16:50
@jansegre jansegre assigned jansegre and unassigned msbrogli Oct 24, 2025
@jansegre jansegre force-pushed the feat/nano-address-balance branch from c63ed89 to 67993eb Compare October 24, 2025 14:57
@jansegre jansegre force-pushed the feat/nano-address-balance branch from 67993eb to d99ce6c Compare February 10, 2026 16:33
@jansegre jansegre marked this pull request as ready for review February 10, 2026 16:33
@jansegre jansegre self-requested a review as a code owner February 10, 2026 16:33
@msbrogli msbrogli force-pushed the master branch 2 times, most recently from eb416fa to 21d7909 Compare February 12, 2026 23:12
@jansegre jansegre force-pushed the feat/nano-address-balance branch from d99ce6c to 572f68d Compare February 18, 2026 16:38
@jansegre jansegre moved this from In Progress (WIP) to In Progress (Done) in Hathor Network Feb 18, 2026
@codecov

codecov Bot commented Feb 18, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 82.76836% with 61 lines in your changes missing coverage. Please review.
✅ Project coverage is 85.15%. Comparing base (5b9ebbe) to head (f0cff82).
⚠️ Report is 5 commits behind head on master.

Files with missing lines Patch % Lines
hathor/verification/transfer_header_verifier.py 73.52% 9 Missing and 9 partials ⚠️
hathor/consensus/consensus.py 11.11% 7 Missing and 1 partial ⚠️
hathor/dag_builder/vertex_exporter.py 84.09% 5 Missing and 2 partials ⚠️
hathor/transaction/vertex_parser/_headers.py 64.70% 4 Missing and 2 partials ⚠️
...thor/transaction/vertex_parser/_transfer_header.py 87.75% 3 Missing and 3 partials ⚠️
hathor/dag_builder/builder.py 75.00% 2 Missing and 2 partials ⚠️
hathor/nanocontracts/execution/block_executor.py 90.24% 2 Missing and 2 partials ⚠️
hathor/nanocontracts/runner/runner.py 66.66% 2 Missing and 2 partials ⚠️
hathor/transaction/transaction.py 85.71% 2 Missing and 2 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #1431      +/-   ##
==========================================
- Coverage   85.25%   85.15%   -0.11%     
==========================================
  Files         464      467       +3     
  Lines       30439    30778     +339     
  Branches     4612     4675      +63     
==========================================
+ Hits        25951    26209     +258     
- Misses       3601     3657      +56     
- Partials      887      912      +25     
Flag Coverage Δ
test-lib 85.15% <82.76%> (-0.10%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Comment thread hathor/nanocontracts/runner/runner.py Outdated
# XXX Should we fail?
return

# XXX Should we check for the size to prevent miscalling with a contract id?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes! Transfer to contracts should be made exclusively by deposits.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.

raise NanoContractDoesNotExist(contract_id.hex())
token_proxy = TokenProxy(self)
return NCContractStorage(trie=trie, nc_id=contract_id, token_proxy=token_proxy)
block_proxy = TokenProxy(self)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe rename TokenProxy to RestrictedBlockProxy?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

@@ -51,3 +51,6 @@ def create_token(
token_symbol=token_symbol,
token_version=token_version
)

def add_address_balance(self, address: Address, amount: Amount, token_id: TokenUid) -> None:

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing docstring.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added docstring.

return Amount(balance)

def add_address_balance(self, address: Address, amount: Amount, token_id: TokenUid) -> None:
key = AddressBalanceKey(address, token_id)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't we assert that address is an actual address (never a contract id)?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. Done.

Comment thread hathor/nanocontracts/blueprint_env.py Outdated
fees=fees or (),
)

def transfer_to_address(self, address: Address, amount: Amount) -> None:

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about the token id?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. I've added support for the storing arbitrary tokens in the global address balance from the contracts.

raise TooManySigOps(f'sigops count greater than max: {sigops_count} > {MAX_SCRIPT_SIGOPS_COUNT}')

try:
raw_script_eval(

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Make sure it works properly for P2SH.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. I've added some tests.

Comment on lines +57 to +58
tx2.nc_transfer_input = 10 HTR main
tx2.nc_transfer_output = 10 HTR main

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we allow an input and an output of the same token? I guess not.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just checked the verifier and it is not allowed. How's this test passing? Are they using different addresses?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing test adding to the mempool with an output greater than the available balance.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing test where the execution with transfer headers is successful. Actually, at least three tests, one with transfer headers, another with a contract transfer, and, finally, another with transfer headers and contract transfer.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've added those 3 success tests.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing test where the execution fails (but not because of the transfer). In this case, the transfer should not be persisted. Another test where the execution fails because of the transfer (low balance). In this case, the transfer should be persisted as well.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've added execution fail tests. Do you mean "the transfer should not be persisted as well"? A transfer that tries to move more than the available balance should fail without affecting the balance, no?

@jansegre jansegre moved this from In Progress (Done) to In Review (WIP) in Hathor Network Feb 19, 2026
@jansegre jansegre force-pushed the feat/nano-address-balance branch from 572f68d to 7f00f0f Compare February 27, 2026 16:58
@jansegre jansegre moved this from In Review (WIP) to In Progress (Done) in Hathor Network Mar 2, 2026
@jansegre jansegre force-pushed the feat/nano-address-balance branch 2 times, most recently from 428ed51 to dafb129 Compare March 4, 2026 16:55
@jansegre jansegre requested a review from glevco March 5, 2026 16:44
@jansegre jansegre force-pushed the feat/nano-address-balance branch 3 times, most recently from 21face1 to f70f9a3 Compare March 6, 2026 16:42
@jansegre jansegre force-pushed the feat/nano-address-balance branch 2 times, most recently from ab46753 to f0cff82 Compare March 25, 2026 16:19
@jansegre jansegre moved this from In Progress (Done) to In Progress (WIP) in Hathor Network Mar 30, 2026
@jansegre jansegre moved this from In Progress (WIP) to In Progress (Done) in Hathor Network Apr 9, 2026
@jansegre jansegre force-pushed the feat/nano-address-balance branch 3 times, most recently from 161c7ff to 1ae4222 Compare April 14, 2026 16:51
@jansegre jansegre changed the title feat(nano): Add address balance in global state feat(nano): implement address balance in global state RFC Apr 14, 2026
@jansegre jansegre force-pushed the feat/nano-address-balance branch from 1ae4222 to 84e4d43 Compare April 15, 2026 02:14
@jansegre jansegre closed this Apr 23, 2026
@github-project-automation github-project-automation Bot moved this from In Progress (Done) to Waiting to be deployed in Hathor Network Apr 23, 2026
@jansegre jansegre moved this from Waiting to be deployed to Done in Hathor Network Apr 23, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Archived in project

Development

Successfully merging this pull request may close these issues.

2 participants