Example #1
0
        public async Task <DepositWalletLock> LockAsync(
            DepositWalletKey key,
            decimal balance,
            long block,
            Func <Guid> operationIdFactory)
        {
            var partitionKey = DepositWalletLockEntity.GetPartitionKey(key);
            var rowKey       = DepositWalletLockEntity.GetRowKey(key);

            var entity = await _storage.GetOrInsertAsync
                         (
                partitionKey,
                rowKey,
                // ReSharper disable once ImplicitlyCapturedClosure
                () => DepositWalletLockEntity.Create
                (
                    key,
                    balance,
                    block,
                    operationIdFactory()
                )
                         );

            return(DepositWalletLock.Create(key, entity.OperationId, entity.Balance, entity.Block));
        }
Example #2
0
        public async Task InvalidDepositAmounEnrolledToMeTest()
        {
            var blockchainType                = "Stellar";
            var hotWallet                     = "hot-wallet";
            var depositWallet                 = "deposit-wallet";
            var operationId                   = Guid.NewGuid();
            Mock <ILogFactory> logFactory     = new Mock <ILogFactory>();
            var hotWalletProviderMock         = new Mock <IHotWalletsProvider>();
            var blockchainApiClientMock       = new Mock <IBlockchainApiClient>();
            var cqrsEngineMock                = new Mock <ICqrsEngine>();
            var enrolledBalanceRepositoryMock = new Mock <IEnrolledBalanceRepository>();
            var cashinRepositoryMock          = new Mock <ICashinRepository>();
            var depositWalletLockRepository   = new Mock <IDepositWalletLockRepository>();
            var chaosKittyMock                = new Mock <IChaosKitty>();
            var xlmBlockchainAsset            = new BlockchainAsset
                                                (
                new AssetContract
            {
                AssetId  = "XLM",
                Accuracy = 7,
                Name     = "Stellar XLM"
            }
                                                );
            var blockchainAssets = new Dictionary <string, BlockchainAsset>
            {
                { xlmBlockchainAsset.AssetId, xlmBlockchainAsset }
            };
            var xlmAsset = new Asset
            {
                Id = "XLM-asset",
                BlockchainIntegrationLayerAssetId = xlmBlockchainAsset.AssetId,
                BlockchainIntegrationLayerId      = blockchainType,
                CashinMinimalAmount = 1
            };
            var assets = new Dictionary <string, Asset>
            {
                { xlmAsset.BlockchainIntegrationLayerAssetId, xlmAsset }
            };

            hotWalletProviderMock
            .Setup(x => x.GetHotWalletAddress(It.Is <string>(b => b == blockchainType)))
            .Returns(hotWallet);

            cashinRepositoryMock
            .Setup(x => x.GetOrAddAsync
                   (
                       It.Is <string>(b => b == blockchainType),
                       It.Is <string>(d => d == depositWallet),
                       It.Is <string>(a => a == xlmBlockchainAsset.AssetId),
                       It.Is <Guid>(o => o == operationId),
                       It.IsAny <Func <CashinAggregate> >()
                   ))
            .ReturnsAsync(() => CashinAggregate.StartWaitingForActualBalance
                          (
                              operationId,
                              xlmAsset.Id,
                              xlmBlockchainAsset.Accuracy,
                              xlmBlockchainAsset.Accuracy,
                              xlmBlockchainAsset.AssetId,
                              blockchainType,
                              (decimal)xlmAsset.CashinMinimalAmount,
                              depositWallet,
                              hotWallet
                          ));

            var balanceProcessor = new BalanceProcessor(
                blockchainType,
                EmptyLogFactory.Instance,
                hotWalletProviderMock.Object,
                blockchainApiClientMock.Object,
                cqrsEngineMock.Object,
                enrolledBalanceRepositoryMock.Object,
                assets,
                blockchainAssets);

            // 1. Deposit 100 is detected on DW at block 5000
            // 2. Balance processor has detected this balance, published EnrollToMatchingEngineCommand with balance 100,
            //     but failed to save aggregate state due to Azure Storage unavailability here -
            //     https://github.com/LykkeCity/Lykke.Job.BlockchainCashinDetector/blob/895c9d879e59af5c1312ef5f09f8e76a97607679/src/Lykke.Job.BlockchainCashinDetector/Workflow/PeriodicalHandlers/BalanceProcessor.cs#L173
            // In current implementation this turns to the publicshing LockDepositWalletCommand

            // Arrange

            depositWalletLockRepository
            .Setup(x => x.LockAsync
                   (
                       It.Is <DepositWalletKey>(k =>
                                                k.DepositWalletAddress == depositWallet &&
                                                k.BlockchainType == blockchainType &&
                                                k.BlockchainAssetId == xlmBlockchainAsset.AssetId),
                       It.Is <decimal>(b => b == 100),
                       It.Is <long>(d => d == 5000),
                       It.IsAny <Func <Guid> >()
                   ))
            .ReturnsAsync <DepositWalletKey, decimal, long, Func <Guid>, IDepositWalletLockRepository, DepositWalletLock>
            (
                (key, balance, block, newOperationIdFactory) =>
                DepositWalletLock.Create
                (
                    key,
                    operationId,
                    100,
                    5000
                )
            );

            blockchainApiClientMock
            .Setup(x => x.EnumerateWalletBalanceBatchesAsync
                   (
                       It.IsAny <int>(),
                       It.IsAny <Func <string, int> >(),
                       It.IsAny <Func <IReadOnlyList <WalletBalance>, Task <bool> > >()
                   ))
            .ReturnsAsync <int, Func <string, int>, Func <IReadOnlyList <WalletBalance>, Task <bool> >, IBlockchainApiClient, EnumerationStatistics>
            (
                (batchSize, accuracyProvider, enumerationCallback) =>
            {
                enumerationCallback(new List <WalletBalance>
                {
                    new WalletBalance
                    (
                        new WalletBalanceContract
                    {
                        Address = depositWallet,
                        AssetId = xlmBlockchainAsset.AssetId,
                        Balance = Conversions.CoinsToContract(100, xlmBlockchainAsset.Accuracy),
                        Block   = 5000
                    },
                        assetAccuracy: xlmBlockchainAsset.Accuracy
                    )
                }).GetAwaiter().GetResult();

                return(new EnumerationStatistics(1, 1, TimeSpan.FromMilliseconds(1)));
            }
            );

            cashinRepositoryMock
            .Setup(x => x.SaveAsync(It.IsAny <CashinAggregate>()))
            .Throws <CashinAggregatePersistingFailureTestException>();

            // Act / Assert

            await Assert.ThrowsAsync <CashinAggregatePersistingFailureTestException>(async() =>
            {
                await balanceProcessor.ProcessAsync(100);
            });

            depositWalletLockRepository.Verify(x => x.LockAsync
                                               (
                                                   It.Is <DepositWalletKey>(k =>
                                                                            k.DepositWalletAddress == depositWallet &&
                                                                            k.BlockchainType == blockchainType &&
                                                                            k.BlockchainAssetId == xlmBlockchainAsset.AssetId),
                                                   It.Is <decimal>(b => b == 100),
                                                   It.Is <long>(d => d == 5000),
                                                   It.IsAny <Func <Guid> >()
                                               ));

            cqrsEngineMock.Verify(
                x => x.SendCommand
                (
                    It.Is <EnrollToMatchingEngineCommand>(c =>
                                                          c.DepositWalletAddress == depositWallet &&
                                                          c.AssetId == xlmAsset.Id &&
                                                          c.BlockchainType == blockchainType &&
                                                          c.BlockchainAssetId == xlmBlockchainAsset.AssetId &&
                                                          c.OperationId == operationId &&
                                                          // ReSharper disable once CompareOfFloatsByEqualityOperator
                                                          c.MatchingEngineOperationAmount == 100.0d),
                    It.Is <string>(c => c == BlockchainCashinDetectorBoundedContext.Name),
                    It.Is <string>(c => c == BlockchainCashinDetectorBoundedContext.Name),
                    It.IsAny <uint>()
                ),
                Times.Once);

            // The bug is:
            // 3. One more deposit has detected and DW balance is changed to 300 at block 5001
            // 4. Balance processor has detected balance 300, publish EnrollToMatchingEngineCommand with balance 300 and saved aggregate.
            // 5. First enrollement command is processed and 100 is enrolled to ME
            // 6. Second enrollement command with amount = 300 is processed but it's deduplicated by ME
            // 7. Finally aggregate contains ME amount = 300, but actually only 100 is enrolled
            // Should be:
            // There should be the same balance 100 at block 5000 as in first iteration

            // Arrange

            depositWalletLockRepository
            .Setup(x => x.LockAsync
                   (
                       It.Is <DepositWalletKey>(k =>
                                                k.DepositWalletAddress == depositWallet &&
                                                k.BlockchainType == blockchainType &&
                                                k.BlockchainAssetId == xlmBlockchainAsset.AssetId),
                       It.Is <decimal>(b => b == 300),
                       It.Is <long>(d => d == 5001),
                       It.IsAny <Func <Guid> >()
                   ))
            .ReturnsAsync <DepositWalletKey, decimal, long, Func <Guid>, IDepositWalletLockRepository, DepositWalletLock>
            (
                (key, balance, block, newOperationIdFactory) =>
                DepositWalletLock.Create
                (
                    key,
                    operationId,
                    100,
                    5000
                )
            );

            blockchainApiClientMock
            .Setup(x => x.EnumerateWalletBalanceBatchesAsync
                   (
                       It.IsAny <int>(),
                       It.IsAny <Func <string, int> >(),
                       It.IsAny <Func <IReadOnlyList <WalletBalance>, Task <bool> > >()
                   ))
            .ReturnsAsync <int, Func <string, int>, Func <IReadOnlyList <WalletBalance>, Task <bool> >, IBlockchainApiClient, EnumerationStatistics>
            (
                (batchSize, accuracyProvider, enumerationCallback) =>
            {
                enumerationCallback(new List <WalletBalance>
                {
                    new WalletBalance
                    (
                        new WalletBalanceContract
                    {
                        Address = depositWallet,
                        AssetId = xlmBlockchainAsset.AssetId,
                        Balance = Conversions.CoinsToContract(300, xlmBlockchainAsset.Accuracy),
                        Block   = 5001
                    },
                        assetAccuracy: xlmBlockchainAsset.Accuracy
                    )
                }).GetAwaiter().GetResult();

                return(new EnumerationStatistics(1, 1, TimeSpan.FromMilliseconds(1)));
            }
            );

            cashinRepositoryMock
            .Setup(x => x.SaveAsync(It.IsAny <CashinAggregate>()))
            .Returns(Task.CompletedTask);

            depositWalletLockRepository.Invocations.Clear();
            cqrsEngineMock.Invocations.Clear();
            cashinRepositoryMock.Invocations.Clear();

            // Act

            await balanceProcessor.ProcessAsync(100);

            // Verify

            depositWalletLockRepository.Verify(
                x => x.LockAsync
                (
                    It.Is <DepositWalletKey>(k =>
                                             k.DepositWalletAddress == depositWallet &&
                                             k.BlockchainType == blockchainType &&
                                             k.BlockchainAssetId == xlmBlockchainAsset.AssetId),
                    It.Is <decimal>(b => b == 300),
                    It.Is <long>(d => d == 5001),
                    It.IsAny <Func <Guid> >()
                ),
                Times.Once);

            cqrsEngineMock.Verify(
                x => x.SendCommand
                (
                    It.Is <EnrollToMatchingEngineCommand>(c =>
                                                          c.DepositWalletAddress == depositWallet &&
                                                          c.AssetId == xlmAsset.Id &&
                                                          c.BlockchainType == blockchainType &&
                                                          c.BlockchainAssetId == xlmBlockchainAsset.AssetId &&
                                                          c.OperationId == operationId &&
                                                          // ReSharper disable once CompareOfFloatsByEqualityOperator
                                                          c.MatchingEngineOperationAmount == 100.0d),
                    It.Is <string>(c => c == BlockchainCashinDetectorBoundedContext.Name),
                    It.Is <string>(c => c == BlockchainCashinDetectorBoundedContext.Name),
                    It.IsAny <uint>()
                ),
                Times.Once);


            cashinRepositoryMock.Verify(x => x.SaveAsync
                                        (
                                            It.Is <CashinAggregate>(a =>
                                                                    a.OperationAmount == 100 &&
                                                                    a.MeAmount == 100 &&
                                                                    a.BalanceAmount == 100 &&
                                                                    a.BalanceBlock == 5000 &&
                                                                    a.DepositWalletAddress == depositWallet &&
                                                                    a.HotWalletAddress == hotWallet &&
                                                                    a.OperationId == operationId &&
                                                                    a.AssetAccuracy == xlmBlockchainAsset.Accuracy &&
                                                                    a.AssetId == xlmAsset.Id &&
                                                                    a.BlockchainAssetId == xlmBlockchainAsset.AssetId &&
                                                                    a.BlockchainType == blockchainType &&
                                                                    a.CashinMinimalAmount == (decimal)xlmAsset.CashinMinimalAmount &&
                                                                    a.State == CashinState.Started
                                                                    )
                                        ));
        }