public async Task Cannot_add_unhashed_block()
 {
     await Assert.ThrowsAsync <SqliteException>(async() =>
     {
         CurrencyBlock block = new CurrencyBlock();
         await Fixture.Value.AddAsync(block);
     });
 }
 private void BeforeSave(CurrencyBlock block)
 {
     foreach (var transaction in block.Transactions)
     {
         transaction.Hash = HashProvider.ComputeHashBytes(transaction);
     }
     block.Hash = block.ToHashBytes(HashProvider);
 }
 public async Task Cannot_add_non_unique_block()
 {
     await Assert.ThrowsAsync <SqliteException>(async() =>
     {
         CurrencyBlock block = CoinSettings.GenesisBlock;
         BeforeSave(block);
         await Fixture.Value.AddAsync(block);
     });
 }
        public void Can_retrieve_genesis_block_by_hash()
        {
            CurrencyBlock block = CoinSettings.GenesisBlock;

            BeforeSave(block);

            var retrieved = Fixture.Value.GetByHashAsync(block.Hash);

            Assert.NotNull(retrieved);
        }
Example #5
0
        public async Task <CurrencyBlock> MineAsync(string address)
        {
            var lastBlock = await _blockchain.GetLastBlockAsync();

            var transactions = _blockchain.StreamAllTransactions();

            CurrencyBlock baseBlock = GenerateNextBlock(address, lastBlock, transactions);

            return((CurrencyBlock)_proofOfWork.ProveWorkFor(baseBlock, _blockchain.GetDifficulty(baseBlock.Index.GetValueOrDefault())));
        }
        public async Task <IActionResult> VerifyLastBlock([FromBody] CurrencyBlock block)
        {
            var result = await _node.CheckReceivedBlocksAsync(block);

            if (result == null)
            {
                return(Accepted(new
                {
                    Message = "Requesting blockchain to check."
                }));
            }

            if (result.Value)
            {
                return(Ok(block));
            }

            return(StatusCode((int)HttpStatusCode.Conflict, new
            {
                Message = "Blockchain is up to date."
            }));
        }
Example #7
0
        public async Task <CurrencyBlock> AddBlockAsync(CurrencyBlock block)
        {
            var previousBlock = await GetLastBlockAsync();

            Contract.Assert(block != null);
            Contract.Assert(previousBlock != null);

            // It only adds the block if it's valid (we need to compare to the previous one)
            if (true || CheckBlock(block, previousBlock))
            {
                await _blocks.AddAsync(block);

                // After adding the block it removes the transactions of this block from the list of pending transactions
                await _transactions.DeleteAsync(block.Transactions.Select(x => x.Id));

                _logger?.LogInformation($"Block added: {block.Hash}");
                _logger?.LogDebug($"Block added: {JsonConvert.SerializeObject(block)}");

                return(block);
            }

            return(null);
        }
        private static async Task TransformBlockAsync(CurrencyBlock block, IDbConnection db)
        {
            if (block == null)
            {
                return;
            }

            const string objectsSql = "SELECT bo.* " +
                                      "FROM 'Block' b " +
                                      "INNER JOIN 'BlockObject' bo ON bo.'BlockIndex' = b.'Index' " +
                                      "ORDER BY b.'Index', bo.'Index'";

            block.Objects = (await db.QueryAsync <BlockObject>(objectsSql, new { block.Index })).AsList();

            const string transactionsSql = "SELECT t.* " +
                                           "FROM 'BlockTransaction' t " +
                                           "WHERE t.'BlockIndex' = @Index";

            block.Transactions = (await db.QueryAsync <Transaction>(transactionsSql, new { block.Index })).AsList();

            foreach (var transaction in block.Transactions)
            {
                const string transactionItemSql = "SELECT i.* " +
                                                  "FROM 'BlockTransactionItem' i " +
                                                  "WHERE i.Type = @Type AND i.TransactionId = @Id";

                transaction.Data = new TransactionData
                {
                    Inputs = (await db.QueryAsync <TransactionItem>(transactionItemSql,
                                                                    new { Type = TransactionDataType.Input, transaction.Id })).AsList(),

                    Outputs = (await db.QueryAsync <TransactionItem>(transactionItemSql,
                                                                     new { Type = TransactionDataType.Output, transaction.Id })).AsList()
                };
            }
        }
        public async Task AddAsync(CurrencyBlock block)
        {
            using (var db = new SqliteConnection($"Data Source={DataFile}"))
            {
                await db.OpenAsync();

                using (var t = db.BeginTransaction())
                {
                    var index = await db.QuerySingleAsync <long>(
                        "INSERT INTO 'Block' ('PreviousHash','Timestamp','Nonce','Hash') VALUES (@PreviousHash,@Timestamp,@Nonce,@Hash); " +
                        "SELECT LAST_INSERT_ROWID();", block, t);

                    foreach (var @object in block.Objects ?? Enumerable.Empty <BlockObject>())
                    {
                        await db.ExecuteAsync("INSERT INTO 'BlockObject' ('BlockIndex','Id','Hash','Type') VALUES (@BlockIndex,@Id,@Hash,@Type);",
                                              new
                        {
                            BlockIndex = index,
                            @object.Index,
                            @object.Timestamp,
                            @object.Hash
                        }, t);
                    }

                    foreach (var transaction in block.Transactions ?? Enumerable.Empty <Transaction>())
                    {
                        await db.ExecuteAsync("INSERT INTO 'BlockTransaction' ('BlockIndex','Id','Hash','Type') VALUES (@BlockIndex,@Id,@Hash,@Type);",
                                              new
                        {
                            BlockIndex = index,
                            transaction.Id,
                            transaction.Hash,
                            transaction.Type
                        }, t);

                        foreach (var input in transaction.Data?.Inputs ?? Enumerable.Empty <TransactionItem>())
                        {
                            await db.ExecuteAsync("INSERT INTO 'BlockTransactionItem' ('TransactionId','Type','Index','Address','Amount','Signature') VALUES (@TransactionId,@Type,@Index,@Address,@Amount,@Signature);",
                                                  new
                            {
                                input.TransactionId,
                                Type = TransactionDataType.Input,
                                input.Index,
                                input.Address,
                                input.Amount,
                                input.Signature
                            }, t);
                        }

                        foreach (var output in transaction.Data?.Outputs ?? Enumerable.Empty <TransactionItem>())
                        {
                            await db.ExecuteAsync("INSERT INTO 'BlockTransactionItem' ('TransactionId','Type','Index','Address','Amount','Signature') VALUES (@TransactionId,@Type,@Index,@Address,@Amount,@Signature);",
                                                  new
                            {
                                output.TransactionId,
                                Type = TransactionDataType.Output,
                                output.Index,
                                output.Address,
                                output.Amount,
                                output.Signature
                            }, t);
                        }
                    }

                    t.Commit();

                    block.Index = index;
                }
            }
        }
Example #10
0
        public bool CheckBlock(CurrencyBlock newBlock, CurrencyBlock previousBlock)
        {
            var blockHash = newBlock.ToHashBytes(_hashProvider);

            if (previousBlock.Index + 1 != newBlock.Index)
            { // Check if the block is the last one
                var message = $"Invalid index: expected '{previousBlock.Index + 1}' but got '{newBlock.Index}'";
                _logger?.LogError(message);
                throw new BlockAssertionException(message);
            }
            if (previousBlock.Hash != newBlock.PreviousHash)
            {     // Check if the previous block is correct
                var message = $"Invalid previoushash: expected '{previousBlock.Hash}' got '{newBlock.PreviousHash}'";
                _logger?.LogError(message);
                throw new BlockAssertionException(message);
            }
            if (blockHash != newBlock.Hash)
            {     // Check if the hash is correct
                var message = $"Invalid hash: expected '{blockHash}' got '{newBlock.Hash}'";
                throw new BlockAssertionException(message);
            }
            if (newBlock.GetDifficulty() >= GetDifficulty(newBlock.Index ?? 0))
            {             // If the difficulty level of the proof-of-work challenge is correct
                var message = $"Invalid proof-of-work difficulty: expected '{newBlock.GetDifficulty()}' to be smaller than '{GetDifficulty(newBlock.Index ?? 0)}'";
                _logger?.LogError(message);
                throw new BlockAssertionException(message);
            }

            // For each transaction in this block, check if it is valid
            foreach (var transaction in newBlock.Transactions)
            {
                CheckTransaction(transaction);
            }

            // Check if the sum of output transactions are equal the sum of input transactions + the reward for the block miner
            {
                var sumOfInputsAmount  = newBlock.Transactions.SelectMany(x => x.Data.Inputs).Select(x => x.Amount).Sum();
                var sumOfOutputsAmount = newBlock.Transactions.SelectMany(x => x.Data.Outputs).Select(x => x.Amount).Sum();

                if (sumOfInputsAmount < sumOfOutputsAmount)
                {
                    var message = $"Invalid block balance: inputs sum '{sumOfInputsAmount}', outputs sum '{sumOfOutputsAmount}'";
                    _logger?.LogError(message);
                    throw new BlockAssertionException(message);
                }
            }

            // Check if there is only 1 fee transaction and 1 reward transaction
            {
                var feeTransactions = newBlock.Transactions.Count(x => x.Type == TransactionType.Fee);
                if (feeTransactions > 1)
                {
                    var message = $"Invalid fee transaction count: expected '1' got '${feeTransactions}'";
                    _logger?.LogError(message);
                    throw new BlockAssertionException(message);
                }
                var rewardTransactions = newBlock.Transactions.Count(x => x.Type == TransactionType.Reward);
                if (rewardTransactions > 1)
                {
                    var message = $"Invalid reward transaction count: expected '1' got '${rewardTransactions}'";
                    _logger?.LogError(message);
                    throw new BlockAssertionException(message);
                }
            }

            return(true);
        }
Example #11
0
 public static string ToHash(this CurrencyBlock block, IHashProvider hashProvider)
 {
     return(hashProvider.ComputeHashString(block));
 }
Example #12
0
 public static byte[] ToHashBytes(this CurrencyBlock block, IHashProvider hashProvider)
 {
     return(hashProvider.ComputeHashBytes(block));
 }
Example #13
0
        private CurrencyBlock GenerateNextBlock(string address, Block previousBlock, IEnumerable <Transaction> pendingTransactions)
        {
            var index        = previousBlock.Index + 1;
            var previousHash = previousBlock.Hash;
            var timestamp    = DateTimeOffset.UtcNow.Ticks;

            _logger?.LogInformation($"Generating next block at index {index}, previousHash={previousHash}");

            // Gets all available transactions without considering data size or quantity
            var transactions = pendingTransactions.ToList();

            // Add fee transaction
            // INFO: usually it's a fee over transaction size (not amount)
            if (transactions.Count > 0)
            {
                var fee = _coinSettings.FeePerTransaction * transactions.Count;

                _logger?.LogInformation($"Found {transactions.Count} pending transactions, adding them to the new block with fee transaction of {fee}");

                var id = CryptoUtil.RandomString();

                var feeTransaction = new Transaction
                {
                    Id   = id,
                    Hash = null,
                    Type = TransactionType.Fee,
                    Data = new TransactionData
                    {
                        Outputs = new List <TransactionItem>
                        {
                            new TransactionItem
                            {
                                Index         = 1,
                                TransactionId = id,
                                Type          = TransactionDataType.Output,
                                Amount        = fee,              // satoshis format
                                Address       = address.FromHex() // INFO: Usually here is a locking script (to check who and when this transaction output can be used), in this case it's a simple destination address
                            }
                        }
                    }
                };

                transactions.Add(feeTransaction);
            }

            // Add reward transaction of 50 coins
            if (address != null)
            {
                var reward = _coinSettings.Mining.MiningReward;
                _logger?.LogInformation($"Adding reward transaction of {reward}");

                var id = CryptoUtil.RandomString();

                var rewardTransaction = new Transaction
                {
                    Id   = id,
                    Hash = null,
                    Type = TransactionType.Reward,
                    Data = new TransactionData
                    {
                        Outputs = new List <TransactionItem>
                        {
                            new TransactionItem
                            {
                                Index         = 1,
                                TransactionId = id,
                                Type          = TransactionDataType.Output,
                                Amount        = reward,
                                Address       = address.FromHex() // INFO: Usually here is a locking script (to check who and when this transaction output can be used), in this case it's a simple destination address
                            }
                        }
                    }
                };

                transactions.Add(rewardTransaction);
            }

            var nextBlock = new CurrencyBlock
            {
                Index        = index,
                Nonce        = 0,
                PreviousHash = previousHash,
                Timestamp    = timestamp,
                Transactions = transactions
            };

            return(nextBlock);
        }