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];
        }
Exemple #2
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);
        }
Exemple #4
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");
            }

            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)));
        }
Exemple #5
0
        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);
        }