public TryAddTransaction ( |
||
decodedTx | ||
return | bool |
public bool TryAddTransaction(DecodedTx decodedTx)
{
if (ContainsTransaction(decodedTx.Hash))
// unconfirmed tx already exists
return false;
var tx = decodedTx.Transaction;
UnconfirmedTx unconfirmedTx;
// allow concurrent transaction adds if underlying storage supports it
// in either case, lock waits for block add/rollback to finish
if (storageManager.IsUnconfirmedTxesConcurrent)
updateLock.EnterReadLock();
else
updateLock.EnterWriteLock();
try
{
using (var chainState = coreDaemon.GetChainState())
{
// verify each input is available to spend
var prevTxOutputKeys = new HashSet<TxOutputKey>();
var prevTxOutputs = ImmutableArray.CreateBuilder<PrevTxOutput>(tx.Inputs.Length);
var inputValue = 0UL;
for (var inputIndex = 0; inputIndex < tx.Inputs.Length; inputIndex++)
{
var input = tx.Inputs[inputIndex];
if (!prevTxOutputKeys.Add(input.PrevTxOutputKey))
// tx double spends one of its own inputs
return false;
UnspentTx unspentTx;
if (!chainState.TryGetUnspentTx(input.PrevTxHash, out unspentTx))
// input's prev output does not exist
return false;
if (input.PrevTxOutputIndex >= unspentTx.OutputStates.Length)
// input's prev output does not exist
return false;
if (unspentTx.OutputStates[(int)input.PrevTxOutputIndex] != OutputState.Unspent)
// input's prev output has already been spent
return false;
TxOutput txOutput;
if (!chainState.TryGetUnspentTxOutput(input.PrevTxOutputKey, out txOutput))
// input's prev output does not exist
return false;
var prevTxOutput = new PrevTxOutput(txOutput, unspentTx);
prevTxOutputs.Add(prevTxOutput);
checked { inputValue += prevTxOutput.Value; }
}
var outputValue = 0UL;
for (var outputIndex = 0; outputIndex < tx.Outputs.Length; outputIndex++)
{
var output = tx.Outputs[outputIndex];
checked { outputValue += output.Value; }
}
if (outputValue > inputValue)
// transaction spends more than its inputs
return false;
// validation passed
// create the unconfirmed tx
var blockTx = new DecodedBlockTx(-1, decodedTx);
var validatableTx = new ValidatableTx(blockTx, null, prevTxOutputs.ToImmutable());
unconfirmedTx = new UnconfirmedTx(validatableTx, DateTimeOffset.Now);
// add the unconfirmed tx
using (var handle = storageManager.OpenUnconfirmedTxesCursor())
{
var unconfirmedTxesCursor = handle.Item;
unconfirmedTxesCursor.BeginTransaction();
if (unconfirmedTxesCursor.TryAddTransaction(unconfirmedTx))
{
unconfirmedTxesCursor.CommitTransaction();
}
else
// unconfirmed tx already exists
return false;
}
}
}
finally
{
if (storageManager.IsUnconfirmedTxesConcurrent)
updateLock.ExitReadLock();
else
updateLock.ExitWriteLock();
}
UnconfirmedTxAdded?.Invoke(this, new UnconfirmedTxAddedEventArgs(unconfirmedTx));
return true;
}
public void TestUnconfTxAdded() { // create tx spending a previous output that exists var decodedTx = Transaction.Create( 0, ImmutableArray.Create(new TxInput(UInt256.One, 0, ImmutableArray<byte>.Empty, 0)), ImmutableArray.Create(new TxOutput(0, ImmutableArray<byte>.Empty)), 0); var tx = decodedTx.Transaction; // create prev output tx var unspentTx = new UnspentTx(tx.Inputs[0].PrevTxHash, 0, 1, 0, false, new OutputStates(1, OutputState.Unspent)); var txOutput = new TxOutput(0, ImmutableArray<byte>.Empty); // mock chain state with prev output var chainState = new Mock<IChainState>(); chainState.Setup(x => x.TryGetUnspentTx(tx.Inputs[0].PrevTxHash, out unspentTx)).Returns(true); chainState.Setup(x => x.TryGetUnspentTxOutput(tx.Inputs[0].PrevTxOutputKey, out txOutput)).Returns(true); // mock core daemon for chain state retrieval var coreDaemon = new Mock<ICoreDaemon>(); coreDaemon.Setup(x => x.GetChainState()).Returns(chainState.Object); using (var unconfirmedTxesBuilder = new UnconfirmedTxesBuilder(coreDaemon.Object, Mock.Of<ICoreStorage>(), storageManager)) { // try to add the tx Assert.IsTrue(unconfirmedTxesBuilder.TryAddTransaction(decodedTx)); // verify unconfirmed tx was added UnconfirmedTx unconfirmedTx; Assert.IsTrue(unconfirmedTxesBuilder.TryGetTransaction(tx.Hash, out unconfirmedTx)); Assert.IsNotNull(unconfirmedTx); // verify tx was indexed against its input var txesSpending = unconfirmedTxesBuilder.GetTransactionsSpending(tx.Inputs[0].PrevTxOutputKey); Assert.AreEqual(1, txesSpending.Count); Assert.AreEqual(tx.Hash, txesSpending.Values.Single().Hash); } }