-
Notifications
You must be signed in to change notification settings - Fork 10
feat: wallet-service nano contract update #96
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,7 @@ | ||
| - Feature Name: nano-contracts-support | ||
| - Start Date: 2025-01-02 | ||
| - Author: @andreabadesso | ||
| - Update Date: 2025-07-07 | ||
| - Author: @andreabadesso @r4mmer | ||
|
|
||
| # Summary | ||
|
|
||
|
|
@@ -12,16 +13,19 @@ The Hathor Network is introducing nano-contracts. To maintain complete transacti | |
|
|
||
| # Guide-level explanation | ||
|
|
||
| Currently, the wallet-service syncs with the fullnode through reliable integrations, which sends events in the exact order they occur. The daemon processes these events through a state machine (SyncMachine) that handles different types of events like new transactions, metadata changes, and voided transactions. | ||
| Currently, nano-contract transactions are any transaction with a nano-contract header, the protocol currently only supports nano-contract headers for normal transactions (version 1) and create token transactions (version 2), along with the usual tracking of inputs and outputs the main change that needs to be implemented is tracking the caller's (address caller from the header) relationship with the transaction, especially when they are not directly involved in any token transfer. | ||
|
|
||
| For nano-contracts, the main change is tracking the caller's relationship with the transaction, especially when they are not directly involved in token transfers. When a nano-contract transaction is received (identified by its version), we'll: | ||
| The wallet-service syncs with the fullnode through reliable integrations, which sends events in the exact order they occur. The daemon processes these events through a state machine (SyncMachine) that handles different types of events like new transactions, metadata changes, and voided transactions. | ||
|
|
||
| When a nano-contract transaction is received (identified by the header), we'll: | ||
|
|
||
| 1. Process normal inputs/outputs as usual | ||
| 2. Check if the caller is involved in inputs/outputs | ||
| 3. If not, explicitly add the transaction to their history | ||
| 4. Forward any contract-specific API calls to the fullnode | ||
| 4. Update the seqnum for the caller's address | ||
| 5. Forward any contract-specific API calls to the fullnode | ||
|
|
||
| This means that if address is the caller in a contract call that moves tokens from X to Y (e.g. a deposit action), all three addresses will see this transaction in their history: | ||
| This means that if address Z is the caller in a contract call that moves tokens from X to Y (e.g. a deposit action), all three addresses will see this transaction in their history: | ||
| - X and Y through normal UTXO tracking | ||
| - Z through explicit caller tracking | ||
|
|
||
|
|
@@ -37,53 +41,50 @@ From the user's perspective, they will be able to: | |
| - See all their contract interactions in their history, whether they moved tokens or just called the contract | ||
| - Create and execute contracts through the wallet-service API | ||
| - Query contract state through proxied fullnode APIs | ||
| ### Seqnum tracking | ||
|
|
||
| The `seqnum` is a metadata of an address, it is a sequentially increasing number (with each nano contract sent from the address) that starts with 0 and is reset with each block (and round of nano contract executions) that is used to deduplicate nano contracts executions. | ||
|
|
||
| The `seqnum` will be cached for each address caller of a nano contract in redis and when a new block arrives the redis cache will be invalidated/cleared so that all addresses are reset to 0. | ||
| # Reference-level explanation | ||
|
|
||
| ## Database Changes | ||
|
|
||
| No schema changes are required. The existing tables (`address_tx_history` and `wallet_tx_history`) will be used to track contract interactions, but we need to explicitly add entries for callers when they are not involved in inputs/outputs. | ||
|
|
||
| Important implementation notes: | ||
| 1. The `updateAddressTablesWithTx` function needs to be modified to handle zero balances efficiently: | ||
| - Keep updating the `address` table for transaction count | ||
| - Skip `address_balance` updates when all values (balance and authorities) are zero | ||
| - Always add entries to `address_tx_history`, even for zero balances | ||
|
|
||
| 2. The `voided` column in history tables applies to callers as well - if a nano-contract transaction is voided, all history entries (including the caller's) must be marked as voided | ||
| The relationship between a nano contract caller and the transaction will be made through the existing `address_tx_history` and `wallet_tx_history` tables, which require no changes but will have to be forced when the caller is not involved in any inputs/outputs. | ||
|
|
||
| ## Daemon Changes | ||
|
|
||
| ### SyncMachine Updates | ||
| The `handleVertexAccepted` service will be enhanced to handle nano-contract transactions: | ||
|
|
||
| 1. Detect nano-contract transactions (by version) | ||
| 2. Extract the caller information from the transaction metadata | ||
| 1. Detect nano-contract transactions | ||
| 2. Extract the caller information from the nano header | ||
| 3. If the caller is not involved in any inputs or outputs: | ||
| - Use `updateAddressTablesWithTx` with a zero balance to add the transaction to their history | ||
| - The function will handle incrementing transaction count and adding history entries | ||
| - Use `getAddressBalanceMap` to add a zero balance HTR entry to the transaction addresses. | ||
| - The `updateAddressTablesWithTx` function will handle incrementing transaction count and adding history entries. | ||
|
|
||
| Example flow: | ||
| ```typescript | ||
| // In handleVertexAccepted | ||
| if (isNanoContractTx(metadata)) { | ||
| const { caller } = metadata; | ||
| const addresses = new Set([ | ||
| ...inputs.map(i => i.address), | ||
| ...outputs.map(o => o.address) | ||
| ]); | ||
|
|
||
| // If caller is not in inputs or outputs, add to history | ||
| if (!addresses.has(caller)) { | ||
| // Create a map with zero balance for the HTR token | ||
| const zeroBalanceMap = { | ||
| [caller]: TokenBalanceMap.fromStringMap({ | ||
| '00': { unlocked: 0, locked: 0 } | ||
| }) | ||
| }; | ||
|
|
||
| await updateAddressTablesWithTx(mysql, txId, timestamp, zeroBalanceMap); | ||
| } | ||
| // In getAddressBalanceMap | ||
| // ... | ||
| for (const header of headers) { | ||
| if (header.id !== '10') { | ||
| // We currently only handle nano contract headers | ||
| continue; | ||
| } | ||
|
|
||
| const address = header.nc_address; | ||
|
|
||
| if (addressBalanceMap[address]) { | ||
| // Already have balance for the nc_address | ||
| continue; | ||
| } | ||
|
|
||
| // Create an empty balance HTR entry if nc_address did not already have balance on the tx | ||
| const emptyHTR = new TokenBalanceMap(); | ||
| const balance = emptyHTR.get(constants.NATIVE_TOKEN_UID) | ||
| emptyHTR.set(constants.NATIVE_TOKEN_UID, balance); | ||
| addressBalanceMap[address] = emptyHTR; | ||
| } | ||
| ``` | ||
|
|
||
|
|
@@ -94,7 +95,7 @@ Scenario 1: X (caller) sends to Y | |
| - Y appears in history (output) | ||
|
|
||
| Scenario 2: Z (caller) triggers X to send to Y | ||
| - Z appears in history (zero balance entry via updateAddressTablesWithTx) | ||
| - Z appears in history (zero balance entry via getAddressBalanceMap) | ||
| - X appears in history (input) | ||
| - Y appears in history (output) | ||
|
|
||
|
|
@@ -103,6 +104,17 @@ Scenario 3: Y (caller) triggers X to send to Y | |
| - Y appears in history (output) | ||
| ``` | ||
|
|
||
| ### Seqnum tracking | ||
|
|
||
| The `seqnum` should be easily searched by address and listed (for clearing when a block arrives). | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I was thinking about this tracking. We will set the current seqnum every time a new nano transaction arrives from the reliable integrations event. When building a new nano tx in the wallet lib, we need to know the next seqnum to use, so (i) we must have a way to fetch this seqnum from the wallet service, and (ii) how long does it take for the new event to be processed and the seqnum updated? Should we have a way to update this seqnum as soon as the transaction is pushed to the network (if done using the wallet service APIs)? |
||
|
|
||
| We could make it work with sparse keys with a common prefix but a better alternative is to use a JSON key, where each key will be the address and the value will be the seqnum. | ||
|
|
||
| - Clearing the cache can be done with [JSON.CLEAR](https://redis.io/docs/latest/commands/json.clear/) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I like the idea of using redis since we already have it in the stack But can you describe better how this is going to work? I mean, when are we going to clear cache? Or is the number only going up for all addresses? |
||
| - Incrementing the seqnum could be done with either [JSON.NUMINCRBY](https://redis.io/docs/latest/commands/json.numincrby/) or [JSON.GET](https://redis.io/docs/latest/commands/json.get/) + [JSON.SET](https://redis.io/docs/latest/commands/json.set/) | ||
|
|
||
| JSON keys can be inspected in many ways, but a good for debugging is [JSON.DEBUG MEMORY](https://redis.io/docs/latest/commands/json.debug-memory/) that returns the size of the key in bytes, so we know how much space it takes. | ||
|
|
||
| ## Wallet Service Changes | ||
|
|
||
| ### Proxy Endpoints | ||
|
|
@@ -122,7 +134,6 @@ All nano contract endpoints will be proxied transparently, maintaining the same | |
| - Preserve request parameters and body | ||
| - Handle errors appropriately (including 404s) | ||
| - Maintain proper authentication and rate limiting | ||
|
|
||
| # Drawbacks | ||
|
|
||
| 1. Additional storage requirements for tracking caller information | ||
|
|
@@ -162,4 +173,4 @@ Alternative approaches considered: | |
| - Would require duplicating transaction data | ||
| - Increases complexity of history queries | ||
| - Makes it harder to maintain consistency | ||
| - Rejected due to unnecessary data duplication | ||
| - Rejected due to unnecessary data duplication | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is not a problem bug "contract call that moves tokens from X to Y (e.g. a deposit action)" a deposit action moves tokens from X to the contract, not to an address Y. Maybe this address Y could be the change address.