예제 #1
0
        private static CommandHandlingResult StartRegularCashout(
            AcceptCashoutCommand command,
            IEventPublisher publisher)
        {
            publisher.PublishEvent(new CashoutStartedEvent
            {
                OperationId       = command.OperationId,
                BlockchainType    = command.BlockchainType,
                BlockchainAssetId = command.BlockchainAssetId,
                HotWalletAddress  = command.HotWalletAddress,
                ToAddress         = command.ToAddress,
                AssetId           = command.AssetId,
                Amount            = command.Amount,
                ClientId          = command.ClientId
            });

            return(CommandHandlingResult.Ok());
        }
예제 #2
0
        public async Task <CommandHandlingResult> Handle(AcceptCashoutCommand command, IEventPublisher publisher)
        {
            var blockchainConfiguration = _blockchainConfigurationProvider.GetConfiguration(command.BlockchainType);

            var recipientClientId = !_disableDirectCrossClientCashouts
                ? await _walletsClient.TryGetClientIdAsync(command.BlockchainType, command.ToAddress)
                : null;

            if (recipientClientId.HasValue)
            {
                return(StartCrossClientCashaout(command, publisher, recipientClientId.Value));
            }

            if (blockchainConfiguration.SupportCashoutAggregation)
            {
                return(await StartBatchedCashoutAsync(command, publisher, blockchainConfiguration.CashoutsAggregation));
            }

            return(StartRegularCashout(command, publisher));
        }
예제 #3
0
        private async Task <CommandHandlingResult> StartBatchedCashoutAsync(
            AcceptCashoutCommand command,
            IEventPublisher publisher,
            CashoutsAggregationConfiguration aggregationConfiguration)
        {
            if (await _closedBatchedCashoutRepository.IsCashoutClosedAsync(command.OperationId))
            {
                return(CommandHandlingResult.Ok());
            }

            var activeCashoutBatchId = await _activeCashoutsBatchIdRepository.GetActiveOrNextBatchId
                                       (
                command.BlockchainType,
                command.BlockchainAssetId,
                command.HotWalletAddress,
                CashoutsBatchAggregate.GetNextId
                                       );

            _chaosKitty.Meow(command.OperationId);

            var batch = await _cashoutsBatchRepository.GetOrAddAsync
                        (
                activeCashoutBatchId.BatchId,
                () => CashoutsBatchAggregate.Start
                (
                    activeCashoutBatchId.BatchId,
                    command.BlockchainType,
                    command.AssetId,
                    command.BlockchainAssetId,
                    command.HotWalletAddress,
                    aggregationConfiguration.CountThreshold,
                    aggregationConfiguration.AgeThreshold
                )
                        );

            _chaosKitty.Meow(command.OperationId);

            var cashout = batch.Cashouts.SingleOrDefault(p => p.CashoutId == command.OperationId) ??
                          new BatchedCashoutValueType(command.OperationId, command.ClientId, command.ToAddress, command.Amount, batch.Cashouts.Count, DateTime.UtcNow);

            var isCashoutShouldWaitForNextBatch = !(batch.IsStillFillingUp || batch.Cashouts.Contains(cashout));

            if (isCashoutShouldWaitForNextBatch)
            {
                return(CommandHandlingResult.Fail(_cqrsSettings.RetryDelay));
            }

            var transitionResult = batch.AddCashout(cashout);

            if (transitionResult.ShouldSaveAggregate())
            {
                await _cashoutsBatchRepository.SaveAsync(batch);

                _chaosKitty.Meow(command.OperationId);
            }

            if (transitionResult.ShouldPublishEvents())
            {
                if (batch.State == CashoutsBatchState.FillingUp && cashout.IndexInBatch == 0)
                {
                    publisher.PublishEvent
                    (
                        new BatchFillingStartedEvent
                    {
                        BatchId = batch.BatchId
                    }
                    );
                }
                else if (batch.State == CashoutsBatchState.Filled)
                {
                    publisher.PublishEvent
                    (
                        new BatchFilledEvent
                    {
                        BatchId = batch.BatchId
                    }
                    );
                }

                _chaosKitty.Meow(command.OperationId);

                publisher.PublishEvent
                (
                    new BatchedCashoutStartedEvent
                {
                    BatchId           = batch.BatchId,
                    OperationId       = command.OperationId,
                    BlockchainType    = command.BlockchainType,
                    BlockchainAssetId = command.BlockchainAssetId,
                    AssetId           = command.AssetId,
                    HotWalletAddress  = command.HotWalletAddress,
                    ToAddress         = command.ToAddress,
                    Amount            = command.Amount,
                    ClientId          = command.ClientId
                }
                );

                _chaosKitty.Meow(command.OperationId);
            }

            return(CommandHandlingResult.Ok());
        }
        public async Task BatchFillingStartedEvent_Published_On_Transient_Failure()
        {
            // == Step 1 - Failure ==

            // Arrange 1

            _batchRepositoryMock
            .Setup(x => x.SaveAsync(It.Is <CashoutsBatchAggregate>(p => p.BatchId == _batch.BatchId)))
            .Throws <InvalidOperationException>();

            // Act  1

            var cashout1Id = Guid.NewGuid();
            var clientId   = Guid.NewGuid();

            var command1 =
                new AcceptCashoutCommand
            {
                BlockchainType    = "Bitcoin",
                BlockchainAssetId = "BTC",
                AssetId           = "LykkeBTC",
                Amount            = 50,
                ClientId          = clientId,
                OperationId       = cashout1Id,
                HotWalletAddress  = "HotWallet",
                ToAddress         = "Destination-first"
            };
            await Assert.ThrowsAsync <InvalidOperationException>(() => _handler.Handle
                                                                 (
                                                                     command1,
                                                                     _eventsPublisherMock.Object
                                                                 ));

            Assert.Equal(1, _batch.Cashouts.Count);
            Assert.Equal(cashout1Id, _batch.Cashouts.Single().CashoutId);
            Assert.Equal(0, _batch.Cashouts.Single().IndexInBatch);
            _eventsPublisherMock.VerifyNoOtherCalls();

            // Assert 2

            _batchRepositoryMock
            .Setup(x => x.SaveAsync(It.Is <CashoutsBatchAggregate>(p => p.BatchId == _batch.BatchId)))
            .Returns(Task.CompletedTask);
            _batchRepositoryMock.Invocations.Clear();

            var cashout2Id = Guid.NewGuid();

            // Act 2

            await _handler.Handle
            (
                new AcceptCashoutCommand
            {
                BlockchainType    = "Bitcoin",
                BlockchainAssetId = "BTC",
                AssetId           = "LykkeBTC",
                Amount            = 50,
                ClientId          = Guid.NewGuid(),
                OperationId       = cashout2Id,
                HotWalletAddress  = "HotWallet",
                ToAddress         = "Destination-2"
            },
                _eventsPublisherMock.Object
            );

            // Arrange 2
            Assert.Equal(2, _batch.Cashouts.Count);

            Assert.Equal(cashout2Id, _batch.Cashouts.Single(p => p.CashoutId == cashout2Id).CashoutId);
            Assert.Equal(1, _batch.Cashouts.Single(p => p.CashoutId == cashout2Id).IndexInBatch);

            _eventsPublisherMock
            .Verify
            (
                x => x.PublishEvent
                (
                    It.Is <BatchedCashoutStartedEvent>(p => p.OperationId == cashout2Id)
                ),
                Times.Once
            );
            _eventsPublisherMock.VerifyNoOtherCalls();



            // Act 3

            await _handler.Handle
            (
                command1,
                _eventsPublisherMock.Object
            );

            // Arrange 3

            _eventsPublisherMock
            .Verify
            (
                x => x.PublishEvent
                (
                    It.Is <BatchFillingStartedEvent>(p => p.BatchId == _batch.BatchId)
                ),
                Times.Once
            );

            _eventsPublisherMock
            .Verify
            (
                x => x.PublishEvent
                (
                    It.Is <BatchedCashoutStartedEvent>(p => p.OperationId == cashout1Id)
                ),
                Times.Once
            );

            _eventsPublisherMock.VerifyNoOtherCalls();

            Assert.Equal(0, _batch.Cashouts.Single(p => p.CashoutId == cashout1Id).IndexInBatch);
            Assert.Equal(2, _batch.Cashouts.Count);
        }
        public async Task Batch_Filling_Completed_With_Batch_Saving_Failure()
        {
            // == Step 1 - Failure ==

            // Arrange 1

            var clientId  = Guid.NewGuid();
            var cashoutId = Guid.NewGuid();
            var command   = new AcceptCashoutCommand
            {
                BlockchainType    = "Bitcoin",
                BlockchainAssetId = "BTC",
                AssetId           = "LykkeBTC",
                Amount            = 50,
                ClientId          = clientId,
                OperationId       = cashoutId,
                HotWalletAddress  = "HotWallet",
                ToAddress         = "Destination-last"
            };

            for (var i = 0; i < _countTreshold - 1; ++i)
            {
                _batch.AddCashout(new BatchedCashoutValueType(Guid.NewGuid(), Guid.NewGuid(), "Destination-{i}", 100 * i, i, DateTime.UtcNow));
            }

            _batchRepositoryMock
            .Setup(x => x.SaveAsync(It.Is <CashoutsBatchAggregate>(p => p.BatchId == _batch.BatchId)))
            .Throws <InvalidOperationException>();

            // Act/Assert 1

            await Assert.ThrowsAsync <InvalidOperationException>(() => _handler.Handle(command, _eventsPublisherMock.Object));

            // Assert 1

            _batchRepositoryMock.Verify(x => x.SaveAsync(It.Is <CashoutsBatchAggregate>(p => p.BatchId == _batch.BatchId)), Times.Once);
            _eventsPublisherMock.VerifyNoOtherCalls();

            // == Step 2 - Success ==

            // Arrange 2

            _batchRepositoryMock
            .Setup(x => x.SaveAsync(It.Is <CashoutsBatchAggregate>(p => p.BatchId == _batch.BatchId)))
            .Callback(() => { });
            _batchRepositoryMock.Invocations.Clear();

            // Act 2

            var handlingResult = await _handler.Handle(command, _eventsPublisherMock.Object);

            // Assert 2

            Assert.False(handlingResult.Retry);
            Assert.Equal(CashoutsBatchState.Filled, _batch.State);
            Assert.Equal(_countTreshold, _batch.Cashouts.Count);
            Assert.Equal(cashoutId, _batch.Cashouts.Last().CashoutId);

            _batchRepositoryMock.Verify(x => x.SaveAsync(It.IsAny <CashoutsBatchAggregate>()), Times.Never);
            _eventsPublisherMock.Verify(x => x.PublishEvent(It.Is <BatchFilledEvent>(p => p.BatchId == _batch.BatchId)), Times.Once);
            _eventsPublisherMock
            .Verify
            (
                x => x.PublishEvent
                (
                    It.Is <BatchedCashoutStartedEvent>(p =>
                                                       p.BatchId == _batch.BatchId &&
                                                       p.Amount == 50 &&
                                                       p.AssetId == "LykkeBTC" &&
                                                       p.BlockchainAssetId == "BTC" &&
                                                       p.BlockchainType == "Bitcoin" &&
                                                       p.ClientId == clientId &&
                                                       p.HotWalletAddress == "HotWallet" &&
                                                       p.OperationId == cashoutId &&
                                                       p.ToAddress == "Destination-last")
                ),
                Times.Once
            );
            _eventsPublisherMock.VerifyNoOtherCalls();
        }