public async Task AddTransactions(IEnumerable <ByteString> transactions) { List <byte[]> transactionHashes = new List <byte[]>(); List <Record> lockedRecords = new List <Record>(); byte[] lockToken = Guid.NewGuid().ToByteArray(); try { foreach (ByteString rawTransaction in transactions) { byte[] rawTransactionBuffer = rawTransaction.ToByteArray(); Transaction transaction = MessageSerializer.DeserializeTransaction(rawTransaction); byte[] transactionHash = MessageSerializer.ComputeHash(rawTransactionBuffer); byte[] mutationHash = MessageSerializer.ComputeHash(transaction.Mutation.ToByteArray()); Mutation mutation = MessageSerializer.DeserializeMutation(transaction.Mutation); List <byte[]> records = new List <byte[]>(); #if DEBUG Logger.LogDebug($"Add transaction {new ByteString(transactionHash)} token {new ByteString(lockToken)}"); #endif transactionHashes.Add(transactionHash); // add pending transaction var ptr = new MongoDbPendingTransaction { MutationHash = mutationHash, TransactionHash = transactionHash, RawData = rawTransactionBuffer, LockTimestamp = DateTime.UtcNow, InitialRecords = new List <MongoDbRecord>(), AddedRecords = new List <byte[]>(), LockToken = lockToken }; await PendingTransactionCollection.InsertOneAsync(ptr); // lock records foreach (var r in mutation.Records) { var previous = await LockRecord(lockToken, r); if (previous != null) { ptr.InitialRecords.Add(previous); lockedRecords.Add(r); } else if (r.Value != null) { ptr.AddedRecords.Add(r.Key.ToByteArray()); lockedRecords.Add(r); } } // save original records await PendingTransactionCollection.UpdateOneAsync( x => x.TransactionHash.Equals(transactionHash), Builders <MongoDbPendingTransaction> .Update .Set(x => x.InitialRecords, ptr.InitialRecords) .Set(x => x.AddedRecords, ptr.AddedRecords) ); // update records foreach (var rec in mutation.Records) { MongoDbRecord r = BuildMongoDbRecord(rec); if (r.Value == null) { if (r.Version.Length == 0) // No record expected { var res = await RecordCollection.CountAsync(x => x.Key.Equals(r.Key)); if (res != 0) // a record exists { throw new ConcurrentMutationException(rec); } } else { // specific version expected var res = await RecordCollection.CountAsync(x => x.Key.Equals(r.Key) && x.Version.Equals(r.Version)); if (res != 1) // expected version not found { throw new ConcurrentMutationException(rec); } } } else { if (r.Version.Length == 0) { r.Version = mutationHash; r.TransactionLock = lockToken; try { await RecordCollection.InsertOneAsync(r); } catch (MongoWriteException ex) when(ex.WriteError.Category == ServerErrorCategory.DuplicateKey) { throw new ConcurrentMutationException(rec); } } else { var res = await RecordCollection.UpdateOneAsync( x => x.Key.Equals(r.Key) && x.Version.Equals(r.Version) && x.TransactionLock.Equals(lockToken), Builders <MongoDbRecord> .Update .Set(x => x.Value, r.Value) .Set(x => x.Version, mutationHash) ); if (res.MatchedCount != 1 || res.ModifiedCount != 1) { throw new ConcurrentMutationException(rec); } } records.Add(r.Key); } } // add transaction var tr = new MongoDbTransaction { MutationHash = mutationHash, TransactionHash = transactionHash, RawData = rawTransactionBuffer, Records = records }; await TransactionCollection.InsertOneAsync(tr); } // unlock records List <ByteString> l = new List <ByteString>(); foreach (var r in lockedRecords) { if (!l.Contains(r.Key)) { await UnlockRecord(lockToken, r); l.Add(r.Key); } } // remove pending transaction foreach (var hash in transactionHashes) { await PendingTransactionCollection.DeleteOneAsync(x => x.TransactionHash.Equals(hash)); } #if DEBUG Logger.LogDebug($"Transaction committed token {new ByteString(lockToken)}"); #endif } catch (Exception ex1) { #if DEBUG Logger.LogDebug($"Error committing transaction batch {ex1.Message} token {new ByteString(lockToken)}"); #endif foreach (var hash in transactionHashes) { #if DEBUG Logger.LogDebug($"Rollbacking transaction 0 {new ByteString(hash)}"); #endif try { await RollbackTransaction(hash); } catch (Exception ex2) { throw new AggregateException(ex2, ex1); } } throw; } }