Advanced - Sending multiple transactions in a block
In this tutorial, I shall go through how we can send multiple transactions within the same block.
If you have been using the current methods sendAsset
and doInvoke
, you would have realised that the second transaction coming out from the same address will fail if done quickly enough. This is due to the second transaction being unaware of the first transaction and thus it tries to spend the same inputs as the first transaction has used. Therefore, this is double spending and the node rejects our second transaction.
A look at the Balance object
Within the Balance object, the assetBalance object looks like this::
{
balance: 1,
spent: [],
unspent: [
{ txid: 'abc', index: 0, value: 10 },
{ txid: 'abc', index: 1, value: 5 }
],
unconfirmed: []
}
Other than balance
, each of the arrays is a list of Coin
objects that basically represent a spendable transaction output. When we are constructing a transaction involving assets, neon-js
selects coins from the unspent
array, selecting more until there is enough to meet the demand as stated in the intents
. The selected coins are transformed and added to the transaction as inputs
while the intents
are transformed and added to the transaction as outputs
.
Once the transaction is sent off to a RPC node, we should not reuse the selected coins as inputs for a second transaction as that would be double spending. However, if we were to retrieve a new Balance
object from the 3rd party provider such as neoscan, the Balance
object will not take in account the recently sent transaction.
Applying Transaction
To deal with this problem, the program will have to retain the old Balance
object and reuse it to make the second transaction. We register the transaction with the Balance
object by calling the applyTx
method:
const tx // We assume that this is a tx that sends 3 NEO away.
console.log(tx.hash) // ghi
balance.applyTx(tx)
Now, our asset balance should look like:
{
balance: 1,
spent: [{ txid: 'abc', index: 0, value: 10 }],
unspent: [{ txid: 'abc', index: 1, value: 5 }],
unconfirmed: [
// This is the change from spending the 5 neo
{ txid: 'ghi', index: 0, value: 2}
]
}
We can see that in order for us to send that 3 neo, we actually spent 5 neo and got back 2 neo. However, this effectively locks out 5 neo as we are unable to use that 2 neo until the transaction is confirmed. However, now we can create a new transaction without worry of double spending. Our second transaction can spend up to 10 neo.
Confirming Transaction
Once the transaction is confirmed, we can always reset by grabbing a fresh Balance
object by asking our 3rd party provider. But for cases where we do not want to do that, the confirm
function is a simple function to move all unconfirmed
coins to unspent
:
balance.confirm()
Now, our asset balance will look like:
{
balance: 1,
spent: [{ txid: 'abc', index: 0, value: 10 }],
unspent: [
{ txid: 'abc',index: 0, value: 10 },
{ txid: 'ghi', index: 0, value: 2}
],
unconfirmed: []
}