Open piotrpietruszka opened 2 months ago
Note that transaction id is generated entirely offline so even if you get the id after u send the transaction, it doesn't necessarily mean it's successful. It takes around 5 seconds for the tx to be finalized hence only after 5 seconds or so, you get the result about the id.
Our sdk basically interacts with the blockchain rpc node to fetch the info about a tx id which is why it says tx not found because the tx has not been finalized.
Or, were u suggestion we make the change what you described at the js sdk level to show it's in pending state? If so, this could be done. Though, if the tx fails, the rpc API would still return it as tx not found error however we could still form the custom failure response at the sdk level again if we want to.
Note that transaction id is generated entirely offline so even if you get the id after u send the transaction, it doesn't necessarily mean it's successful.
Describing the state as PENDING, SUCCESS, FAILD sounds reasonable.
Our sdk basically interacts with the blockchain rpc node to fetch the info about a tx id which is why it says tx not found because the tx has not been finalized.
Though, if the tx fails, the rpc API would still return it as tx not found error however we could still form the custom failure response at the sdk level again if we want to.
From UI, design perspective we are sharing TX ID directly after the creation. For example to check TX in a ledger. Why only finalised TXs can be found? Failed, pending TXs are stored off-chain?
Or, were u suggestion we make the change what you described at the js sdk level to show it's in pending state? If so, this could be done.
Solving this on SDK level can be temporary solution. But keep in mind that even for failed transaction we need similar amount of details as for success one to display to user.
Ah I see what you are getting at. Currently, we do not store failed transactions on ledger. We can definitely make this change so we do start storing all txes on ledger whether successful or failure. Just created a GitHub issue for it: https://github.com/Nuklai/nuklaivm/issues/52
Will make this change before the next reset of our chain. Thanks for elaborating about the use case. I agree it makes sense to store failed transactions on ledger as well.
While the status SUCCESS or FAILED tx will be stored on the ledger, the sdk can handle the PENDING status response as it doesn't make sense to store this on ledger.
Has there ever been a case whereby a transaction has failed in your experience?
The reason I ask is because I wanted to take some time to explain how hypervms work.
So, a transaction is created via the SDK and the tx id is immediately returned. However, this doesn't necessarily mean the transaction has even had a time to go through hypervm.
From the hyperchain-js-sdk code, this is what's happening:
// Sign the transaction
const [txSigned, err] = tx.sign(authFactory, actionRegistry, authRegistry)
if (err) {
return {
submit: async () => {
throw new Error('Transaction failed, cannot submit.')
},
txSigned: {} as Transaction,
err: err as Error
}
}
const submit = async (): Promise<SubmitTransactionResponse> => {
const [txBytes, err] = txSigned.toBytes()
if (err) {
throw new Error(`Transaction failed, cannot submit. Err: ${err}`)
}
return await this.submitTransaction(txBytes)
}
return { submit, txSigned, err: undefined }
So, if the tx was not formed correctly, it would fail immediately.
On nuklai-js-sdk code, we have different functions for different transactions but they all work essentially the same. For a simple transfer transaction,
const { submit, txSigned, err } =
await hyperApiService.generateTransaction(
genesisInfo.genesis,
actionRegistry,
authRegistry,
[transfer],
authFactory
)
if (err) {
throw err
}
await submit()
return txSigned.id().toString()
For minting asset transaction,
const { submit, txSigned, err } =
await hyperApiService.generateTransaction(
genesisInfo.genesis,
actionRegistry,
authRegistry,
[mintAsset],
authFactory
)
if (err) {
throw err
}
await submit()
return txSigned.id().toString()
Basically, the submit basically calls the submitTx
RPC API of hypersdk if it's a valid transaction. And I took a look at this function and it seems that it basically sends the tx to the mempool and is immediately returned with txID(whether successful or not).
Unfortunately, since this is an RPC API of hypersdk, it's not so easy to change it at the nuklaivm protocol level of how this functionality behaves. Also, regarding showing the PENDING state for a tx, it makes sense to just immediately show any tx as PENDING as soon as it's submitted because the current API of nuklai-js-sdk to retrieve the tx status with getTransactionInfo
function interacts with the RPC API which will still not be able to detect any pending transaction UNTIL it's either SUCESSFUL or FAILED.
tldr; With that said, I have put out a change at our nuklaivm protocol level to record the failed transactions so at least if the transaction fails, you can still get the record of it by retrieving info about the tx id from the node. But, this won't go into effect until we reset our blockchain with these new updates(which will likely happen in a few weeks since this same update also includes the updates to the dataset support on our vm).
Problem
At the moment of the call we know that transaction exists, but instead of returning pending state it is treated as not found.
Current flow
{ "jsonrpc": "2.0", "result": { "txId": "2BXbsTS4K2Era4aXiPSipaPS8aWrRYTatcSmtFxz8onqSrBbU6" }, "id": 3 }
{"jsonrpc":"2.0","id":40,"method":"nuklaivm.tx","params":{"txId":"2BXbsTS4K2Era4aXiPSipaPS8aWrRYTatcSmtFxz8onqSrBbU6"}}
{ "jsonrpc": "2.0", "error": { "code": -32000, "message": "tx not found", "data": null }, "id": 40 }
{"jsonrpc":"2.0","id":68,"method":"nuklaivm.tx","params":{"txId":"2BXbsTS4K2Era4aXiPSipaPS8aWrRYTatcSmtFxz8onqSrBbU6"}}
{ "jsonrpc": "2.0", "result": { "timestamp": 1726236555200, "success": true, "units": [ 224, 7, 14, 50, 26 ], "fee": 321 }, "id": 68 }
{ "jsonrpc": "2.0", "result": { "txId": "2BXbsTS4K2Era4aXiPSipaPS8aWrRYTatcSmtFxz8onqSrBbU6" }, "id": 3 }
or better unify responses (it's very natural that create transaction endpoint will return transaction model):
{ "jsonrpc": "2.0", "result": { "timestamp": 1726236555200, "status": "CREATED", <- NEW FIELD "units": [ ... ], "fee": 321 }, "id": 68 }
{ "jsonrpc": "2.0", "result": { "timestamp": 1726236555200, "status": "PENDING", <- NEW FIELD "units": [ ... ], "fee": 321 }, "id": 68 }
{ "jsonrpc": "2.0", "result": { "timestamp": 1726236555200, "status": "SUCCESS", <- NEW FIELD "units": [ ... ], "fee": 321 }, "id": 68 }