public async Task <IList <OutboundTransaction> > GetUnprocessedTransactions() { string account = $"/asset/{assetName}/out/"; string asset = $"/asset/{assetName}/"; HttpClient client = new HttpClient(); HttpResponseMessage accountsResponse = await client.GetAsync(new Uri(tedChainUri, $"query/subaccounts?account={account}")); JArray records = JArray.Parse(await accountsResponse.EnsureSuccessStatusCode().Content.ReadAsStringAsync()); List <OutboundTransaction> result = new List <OutboundTransaction>(); foreach (JObject record in records) { ByteString mutationHash = ByteString.Parse((string)record["version"]); ByteString key = ByteString.Parse((string)record["key"]); RecordKey recordKey = RecordKey.Parse(key); if (recordKey.RecordType != RecordType.Account || recordKey.Name != $"/asset/{assetName}/") { continue; } HttpResponseMessage transactionResponse = await client.GetAsync(new Uri(tedChainUri, $"query/transaction?mutation_hash={mutationHash}")); JObject rawTransaction = JObject.Parse(await transactionResponse.EnsureSuccessStatusCode().Content.ReadAsStringAsync()); Transaction transaction = MessageSerializer.DeserializeTransaction(ByteString.Parse((string)rawTransaction["raw"])); Mutation mutation = MessageSerializer.DeserializeMutation(transaction.Mutation); // TODO: Validate that the record mutation has an empty version string target = GetPayingAddress(mutation); if (target != null) { long value = ParseInt(ByteString.Parse((string)record["value"])); result.Add(new OutboundTransaction(key, value, mutationHash, target)); } } return(result); }
private JsonResult TransactionToJson(ByteString rawData) { Transaction transaction = MessageSerializer.DeserializeTransaction(rawData); Mutation mutation = MessageSerializer.DeserializeMutation(transaction.Mutation); return(Json(new { transaction_hash = new ByteString(MessageSerializer.ComputeHash(rawData.ToByteArray())).ToString(), mutation_hash = new ByteString(MessageSerializer.ComputeHash(transaction.Mutation.ToByteArray())).ToString(), mutation = new { @namespace = mutation.Namespace.ToString(), records = mutation.Records.Select(GetRecordJson).ToArray(), metadata = mutation.Metadata.ToString() }, timestamp = transaction.Timestamp, transaction_metadata = transaction.TransactionMetadata.ToString() })); }
public async Task AddTransactions(IEnumerable <ByteString> transactions) { using (var dbTransaction = Context.Database.BeginTransaction(IsolationLevel.Serializable)) { 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); await UpdateAccounts(mutation, mutationHash); var newTransaction = new Models.Transaction { TransactionHash = transactionHash, MutationHash = mutationHash, RawData = rawTransactionBuffer }; Transactions.Add(newTransaction); await Context.SaveChangesAsync(); await AddTransaction(newTransaction.Id, mutationHash, mutation); } dbTransaction.Commit(); } catch (Exception ex) { _logger.LogError(ex.Message); dbTransaction.Rollback(); throw new Exception(ex.Message); } } }
public async Task <Mutation> GetMutation(ByteString hash) { Mutation mutation; if (!Transactions.TryGetValue(hash, out mutation)) { string account = $"/asset/{assetName}/out/"; string asset = $"/asset/{assetName}/"; HttpClient client = new HttpClient(); HttpResponseMessage getTransactionResponse = await client.GetAsync(new Uri(tedChainUri, $"query/transaction?mutation_hash={hash.ToString()}")); JToken rawTransaction = JToken.Parse(await getTransactionResponse.EnsureSuccessStatusCode().Content.ReadAsStringAsync()); Transaction transaction = MessageSerializer.DeserializeTransaction(ByteString.Parse((string)rawTransaction["raw"])); mutation = MessageSerializer.DeserializeMutation(transaction.Mutation); Transactions.Add(hash, mutation); } return(mutation); }
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; } }
public async Task <ByteString> PostTransaction(ByteString rawMutation, IReadOnlyList <SignatureEvidence> authentication) { Mutation mutation; try { // Verify that the mutation can be deserialized mutation = MessageSerializer.DeserializeMutation(rawMutation); } catch (InvalidProtocolBufferException) { throw new TransactionInvalidException("InvalidMutation"); } if (!mutation.Namespace.Equals(this.Namespace)) { throw new TransactionInvalidException("InvalidNamespace"); } if (mutation.Records.Count == 0) { throw new TransactionInvalidException("InvalidMutation"); } if (mutation.Records.Any(record => record.Key.Value.Count > MaxKeySize)) { throw new TransactionInvalidException("InvalidMutation"); } ValidateAuthentication(authentication, MessageSerializer.ComputeHash(rawMutation.ToByteArray())); ParsedMutation parsedMutation = ParsedMutation.Parse(mutation); // All assets must have an overall zero balance IReadOnlyDictionary <AccountKey, AccountStatus> accounts = await this.store.GetAccounts(parsedMutation.AccountMutations.Select(entry => entry.AccountKey)); var groups = parsedMutation.AccountMutations .GroupBy(account => account.AccountKey.Asset.FullPath) .Select(group => group.Sum(entry => entry.Balance - accounts[entry.AccountKey].Balance)); if (groups.Any(group => group != 0)) { throw new TransactionInvalidException("UnbalancedTransaction"); } DateTime date = DateTime.UtcNow; await this.validator.Validate(parsedMutation, authentication, accounts); TransactionMetadata metadata = new TransactionMetadata(authentication); byte[] rawMetadata = SerializeMetadata(metadata); Transaction transaction = new Transaction(rawMutation, date, new ByteString(rawMetadata)); byte[] serializedTransaction = MessageSerializer.SerializeTransaction(transaction); try { await this.store.AddTransactions(new[] { new ByteString(serializedTransaction) }); } catch (ConcurrentMutationException) { throw new TransactionInvalidException("OptimisticConcurrency"); } return(new ByteString(MessageSerializer.ComputeHash(serializedTransaction))); }
public void Mutation_Invalid() { Assert.Throws <InvalidProtocolBufferException>(() => MessageSerializer.DeserializeMutation(ByteString.Parse("01"))); }
public async Task AddTransactions(IEnumerable <ByteString> transactions) { using (await m_lock.LockAsync()) { using (SqlTransaction context = Connection.BeginTransaction(IsolationLevel.Snapshot)) { 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); IReadOnlyList <Record> conflicts = await ExecuteQuery <Record>( "EXEC [Openchain].[AddTransaction] @instance, @transactionHash, @mutationHash, @rawData, @records;", reader => mutation.Records.First(record => record.Key.Equals(new ByteString((byte[])reader[0]))), new Dictionary <string, object>() { ["instance"] = this.instanceId, ["transactionHash"] = transactionHash, ["mutationHash"] = mutationHash, ["rawData"] = rawTransactionBuffer, ["type:records"] = "Openchain.RecordMutationTable", ["records"] = mutation.Records.Select(record => { SqlDataRecord result = new SqlDataRecord(recordMutationMetadata); RecordKey key = ParseRecordKey(record.Key); result.SetBytes(0, 0, record.Key.ToByteArray(), 0, record.Key.Value.Count); if (record.Value == null) { result.SetDBNull(1); } else { result.SetBytes(1, 0, record.Value.ToByteArray(), 0, record.Value.Value.Count); } result.SetBytes(2, 0, record.Version.ToByteArray(), 0, record.Version.Value.Count); result.SetString(3, key.Name); result.SetByte(4, (byte)key.RecordType); return(result); }).ToList() }, context); if (conflicts.Count > 0) { throw new ConcurrentMutationException(conflicts[0]); } } context.Commit(); } catch (Exception ex) { if (!(ex is ConcurrentMutationException)) { var excep = ex; } throw; } } } }