public async Task<IList<Mutation>> Validate(ParsedMutation mutation, IReadOnlyList<SignatureEvidence> authentication, IReadOnlyDictionary<AccountKey, AccountStatus> accounts) { await ValidateAccountMutations(mutation.AccountMutations, accounts, authentication); await ValidateDataMutations(mutation.DataRecords, authentication); return new Mutation[0]; }
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"); } ParsedMutation parsedMutation = ParsedMutation.Parse(mutation); IReadOnlyDictionary <AccountKey, AccountStatus> accounts = await ValidateMutation(mutation, parsedMutation); ValidateAuthentication(authentication, MessageSerializer.ComputeHash(rawMutation.ToByteArray())); DateTime date = DateTime.UtcNow; IList <Mutation> generatedMutations = 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); List <ByteString> transactions = new List <ByteString>() { new ByteString(serializedTransaction) }; transactions.AddRange(await Task.WhenAll(generatedMutations.Select(async generatedMutation => { await ValidateMutation(generatedMutation, ParsedMutation.Parse(generatedMutation)); Transaction generatedTransaction = new Transaction(new ByteString(MessageSerializer.SerializeMutation(generatedMutation)), date, ByteString.Empty); return(new ByteString(MessageSerializer.SerializeTransaction(generatedTransaction))); }))); try { await this.store.AddTransactions(transactions); } catch (ConcurrentMutationException) { throw new TransactionInvalidException("OptimisticConcurrency"); } return(new ByteString(MessageSerializer.ComputeHash(serializedTransaction))); }
public async Task Validate_ComputeAddress() { PermissionBasedValidator validator = CreateValidator( new string[] { "0123456789abcdef11223344" }, new Dictionary<string, PermissionSet>() { ["/"] = PermissionSet.Unset, ["/a/"] = PermissionSet.AllowAll }); Dictionary<AccountKey, AccountStatus> accounts = new Dictionary<AccountKey, AccountStatus>() { [AccountKey.Parse("/a/", "/b/")] = new AccountStatus(AccountKey.Parse("/a/", "/b/"), 150, ByteString.Empty) }; ParsedMutation mutation = new ParsedMutation( new[] { new AccountStatus(AccountKey.Parse("/a/", "/b/"), 100, ByteString.Empty) }, new KeyValuePair<RecordKey, ByteString>[] { }); await validator.Validate( mutation, new[] { new SignatureEvidence(ByteString.Parse("0123456789abcdef"), ByteString.Parse("11223344")) }, accounts); }
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))); }
private async Task <IReadOnlyDictionary <AccountKey, AccountStatus> > ValidateMutation(Mutation mutation, ParsedMutation parsedMutation) { 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"); } // 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"); } return(accounts); }
private async Task<IReadOnlyDictionary<AccountKey, AccountStatus>> ValidateMutation(Mutation mutation, ParsedMutation parsedMutation) { 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"); // 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"); return accounts; }
private static async Task TestAccountChange(PermissionSet accountPermissions, long previousBalance, long newBalance, bool emptyVersion = false) { PermissionBasedValidator validator = CreateValidator( new string[0], new Dictionary<string, PermissionSet>() { ["/"] = PermissionSet.Unset, ["/a/"] = accountPermissions }); ByteString version = emptyVersion ? ByteString.Empty : ByteString.Parse("abcdef"); Dictionary<AccountKey, AccountStatus> accounts = new Dictionary<AccountKey, AccountStatus>() { [AccountKey.Parse("/a/", "/b/")] = new AccountStatus(AccountKey.Parse("/a/", "/b/"), previousBalance, version) }; ParsedMutation mutation = new ParsedMutation( new[] { new AccountStatus(AccountKey.Parse("/a/", "/b/"), newBalance, version) }, new KeyValuePair<RecordKey, ByteString>[0]); await validator.Validate(mutation, new SignatureEvidence[0], accounts); }
public async Task Validate_Inheritance() { TestPermissionsProvider firstValidator = new TestPermissionsProvider( new string[0], new Dictionary<string, PermissionSet>() { ["/"] = PermissionSet.AllowAll, ["/a/"] = PermissionSet.AllowAll }); TestPermissionsProvider secondValidator = new TestPermissionsProvider( new string[0], new Dictionary<string, PermissionSet>() { ["/"] = PermissionSet.DenyAll, ["/a/"] = PermissionSet.Unset }); // Level 1: / // Allow + Deny = Deny // Level 2: /a/ // Allow + Unset = Allow // Result: Allow PermissionBasedValidator validator = new PermissionBasedValidator(new[] { firstValidator, secondValidator }); ParsedMutation mutation = new ParsedMutation( new AccountStatus[0], new[] { new KeyValuePair<RecordKey, ByteString>(new RecordKey(RecordType.Data, LedgerPath.Parse("/a/"), "a"), ByteString.Parse("aabb")) }); await validator.Validate(mutation, new SignatureEvidence[0], new Dictionary<AccountKey, AccountStatus>()); }
public async Task Validate_DataMutationError() { PermissionBasedValidator validator = CreateValidator( new string[0], new Dictionary<string, PermissionSet>() { ["/"] = PermissionSet.Unset, ["/a/"] = new PermissionSet(Access.Permit, Access.Permit, Access.Permit, Access.Deny) }); ParsedMutation mutation = new ParsedMutation( new AccountStatus[0], new[] { new KeyValuePair<RecordKey, ByteString>(new RecordKey(RecordType.Data, LedgerPath.Parse("/a/"), "a"), ByteString.Parse("aabb")) }); TransactionInvalidException exception = await Assert.ThrowsAsync<TransactionInvalidException>(() => validator.Validate(mutation, new SignatureEvidence[0], new Dictionary<AccountKey, AccountStatus>())); Assert.Equal("CannotModifyData", exception.Reason); }
public async Task Validate_DataMutationSuccess() { PermissionBasedValidator validator = CreateValidator( new string[0], new Dictionary<string, PermissionSet>() { ["/"] = PermissionSet.Unset, ["/a/"] = new PermissionSet(Access.Deny, Access.Deny, Access.Deny, Access.Deny, Access.Permit) }); Dictionary<AccountKey, AccountStatus> accounts = new Dictionary<AccountKey, AccountStatus>(); ParsedMutation mutation = new ParsedMutation( new AccountStatus[0], new[] { new KeyValuePair<RecordKey, ByteString>(new RecordKey(RecordType.Data, LedgerPath.Parse("/a/"), "a"), ByteString.Parse("aabb")) }); await validator.Validate(mutation, new SignatureEvidence[0], accounts); }