Skip to content

Commit

Permalink
Merge pull request #1 from nevstad/feature/network
Browse files Browse the repository at this point in the history
Implement simple node network, not quite peer to peer, but close.
  • Loading branch information
nevstad committed Apr 15, 2019
2 parents 907920c + 7d635cf commit a7216ed
Show file tree
Hide file tree
Showing 16 changed files with 700 additions and 180 deletions.
11 changes: 5 additions & 6 deletions README.md
@@ -1,23 +1,22 @@
# ⛓ A simple Blockchain implementation for macOS, written in Swift.
# ⛓ A simple Blockchain implementation written in Swift.

A Blockchain implementation that loosely mimics Bitcoin's key features.

## ✅ Features

* 🎭 Secure and anonymous Wallets
* 🎭 Secure and Anonymous Wallets
* 🔐 Verified Transations
* 🛠 A Proof-of-Work system

* 🛠 Proof-of-Work System
* 🌐 Simple Decentralization

## ⛔️ Missing features:

* 🗄 Persistent block store
* 🌐 Decentralization

## 🚦 Requirements

* ✓ Swift 4.0+
* ✓ macOS 10.12+, iOS 10.0+, tvOS 10.0+
* ✓ macOS 10.14+, iOS 12.0+, tvOS 12.0+


## 📣 Shoutout
Expand Down
2 changes: 1 addition & 1 deletion Sources/BlockchainSwift/Core/Block.swift
Expand Up @@ -7,7 +7,7 @@

import Foundation

public struct Block: Serializable {
public struct Block: Codable, Serializable {
/// The timestamp for when the block was generated and added to the chain
public let timestamp: UInt32

Expand Down
172 changes: 45 additions & 127 deletions Sources/BlockchainSwift/Core/Blockchain.swift
Expand Up @@ -21,160 +21,78 @@ public class Blockchain {
return subsidy / (1 + halvings)
}
}

/// Transaction error types
public enum TxError: Error {
case invalidValue
case insufficientBalance
case unverifiedTransaction
}

/// The blockchain
public private(set) var chain: [Block] = []
public private(set) var blocks: [Block] = []

/// Proof of Work Algorithm
public private(set) var pow = ProofOfWork(difficulty: 3)

/// Transation pool holds all transactions to go into the next block
public private(set) var mempool = [Transaction]()

/// Unspent Transaction Outputs
/// - This class keeps track off all current UTXOs, providing a quick lookup for balances and creating new transactions.
/// - For now, any transaction will use all available utxos for that address, meaning we have an easier job of things.
/// - Also, since we have no decentralization, we don't have to worry about reloading this based on the current blockchain whenver we have to sync blocks with other nodes.
public private(set) var utxos = [TransactionOutput]()
/// - For now, any transaction must use all available utxos for that address, meaning we have an easier job of things.
private var utxos = [TransactionOutput]()

/// Explicitly define Codable properties
private enum CodingKeys: CodingKey {
case mempool
case chain
}


/// Initialises our blockchain with a genesis block
public init(minerAddress: Data) {
mineGenesisBlock(minerAddress: minerAddress)
/// Returns the last block in the blockchain. Fatal error if we have no blocks.
public func lastBlockHash() -> Data {
return self.blocks.last?.hash ?? Data()
}

/// Creates a coinbase transaction
/// - Parameter address: The miner's address to be awarded a block reward
/// - Returns: The index of the block to whitch this transaction will be added
@discardableResult
private func createCoinbaseTransaction(for address: Data) -> Int {
// Generate a coinbase tx to reward block miner
let coinbaseTx = Transaction.coinbase(address: address, blockValue: currentBlockValue())
self.mempool.append(coinbaseTx)
self.utxos.append(contentsOf: coinbaseTx.outputs)
return self.chain.count + 1
/// Get the block value, or the block reward, at current block height
public func currentBlockValue() -> UInt64 {
return Coin.blockReward(at: UInt64(self.blocks.count))
}

/// Create a transaction to be added to the next block.
/// - Parameters:
/// - sender: The sender
/// - recipient: The recipient
/// - value: The value to transact
/// - Returns: The index of the block to whitch this transaction will be added
@discardableResult
public func createTransaction(sender: Wallet, recipientAddress: Data, value: UInt64) throws -> Int {
// You cannot send nothing
if value == 0 {
throw TxError.invalidValue
}

// Calculate transaction value and change, based on the sender's balance and the transaction's value
// - All utxos for the sender must be spent, and are indivisible.
let balance = self.balance(for: sender.address)
if value > balance {
throw TxError.insufficientBalance
}
let change = balance - value

// Create a transaction and sign it, making sure first the sender has the right to claim the spendale outputs
let spendableOutputs = self.utxos.filter { $0.address == sender.address }
guard let signedTxIns = try? sender.sign(utxos: spendableOutputs) else { return -1 }
for (i, txIn) in signedTxIns.enumerated() {
let originalOutputData = spendableOutputs[i].serialized().sha256()
if !ECDSA.verify(publicKey: sender.publicKey, data: originalOutputData, signature: txIn.signature) {
throw TxError.unverifiedTransaction
}
}

// Add transaction to the pool
let txOuts = [
TransactionOutput(value: value, address: recipientAddress),
TransactionOutput(value: change, address: sender.address)
]
self.mempool.append(Transaction(inputs: signedTxIns, outputs: txOuts))

// All spendable outputs for sender must be spent, and all outputs added
self.utxos.removeAll { $0.address == sender.address }
self.utxos.append(contentsOf: txOuts)

return self.chain.count + 1
}

/// Finds a transaction by id, iterating through every block (to optimize this, look into Merkle trees).
/// - Parameter txId: The txId
public func findTransaction(txId: String) -> Transaction? {
for block in chain {
for transaction in block.transactions {
if transaction.txId == txId {
return transaction
}
}
}
return nil
}

/// Create a new block in the chain, adding transactions curently in the mempool to the block
/// - Parameter proof: The proof of the PoW
/// Create a new block in the chain
/// - Parameter nonce: The Block nonce after successful PoW
/// - Parameter hash: The Block hash after successful PoW
/// - Parameter previousHash: The hash of the previous Block
/// - Parameter timestamp: The timestamp for when the Block was mined
/// - Parameter transactions: The transactions in the Block
@discardableResult
public func createBlock(nonce: UInt32, hash: Data, previousHash: Data, timestamp: UInt32, transactions: [Transaction]) -> Block {
let block = Block(timestamp: timestamp, transactions: transactions, nonce: nonce, hash: hash, previousHash: previousHash)
self.chain.append(block)
self.blocks.append(block)
self.updateSpendableOutputs(with: block)
return block
}

/// Mines our genesis block placing circulating supply in the reward pool,
/// and awarding the first block to Magnus
@discardableResult
private func mineGenesisBlock(minerAddress: Data) -> Block {
return mineBlock(previousHash: Data(), minerAddress: minerAddress)
}

/// Mines the next block using Proof of Work
/// - Parameter recipient: The miners address for block reward
public func mineBlock(previousHash: Data, minerAddress: Data) -> Block {
// Generate a coinbase tx to reward block miner
createCoinbaseTransaction(for: minerAddress)

// Do Proof of Work to mine block with all currently registered transactions, the create our block
let transactions = mempool
mempool.removeAll()
let timestamp = UInt32(Date().timeIntervalSince1970)
let proof = pow.work(prevHash: previousHash, timestamp: timestamp, transactions: transactions)
return createBlock(nonce: proof.nonce, hash: proof.hash, previousHash: previousHash, timestamp: timestamp, transactions: transactions)
/// Finds UTXOs for a specified address
/// - Parameter address: The wallet address whose UTXOs we want to find
public func findSpendableOutputs(for address: Data) -> [TransactionOutput] {
return self.utxos.filter({ $0.address == address })
}

/// Returns the last block in the blockchain. Fatal error if we have no blocks.
public func lastBlock() -> Block {
guard let last = chain.last else {
fatalError("Blockchain needs at least a genesis block!")
/// Updates UTXOs when a new block is added
/// - Parameter block: The block that has been added, whose transactions we must go through to find the nes UTXO state
public func updateSpendableOutputs(with block: Block) {
let spentOutputs = block.transactions.flatMap { $0.inputs.map { $0.previousOutput } }
for spentTxOut in spentOutputs {
self.utxos.removeAll { (txOut) -> Bool in
txOut.hash == spentTxOut.hash
}
}
return last
}

/// Get the block value, or the block reward, at current block height
public func currentBlockValue() -> UInt64 {
return Coin.blockReward(at: UInt64(self.chain.count))
self.utxos.append(contentsOf: block.transactions.flatMap({ $0.outputs }))
}

/// Returns the balannce for a specified address, defined by the sum of its unspent outputs
/// - Parameter address: The wallet address whose balance to find
public func balance(for address: Data) -> UInt64 {
var balance: UInt64 = 0
for output in utxos.filter({ $0.address == address }) {
balance += output.value
return findSpendableOutputs(for: address).map { $0.value }.reduce(0, +)
}

/// Finds a transaction by id, iterating through every block (to optimize this, look into Merkle trees).
/// - Parameter txHash: The Transaction hash
public func findTransaction(txHash: Data) -> Transaction? {
for block in self.blocks {
for transaction in block.transactions {
if transaction.txHash == txHash {
return transaction
}
}
}
return balance
return nil
}

}
2 changes: 1 addition & 1 deletion Sources/BlockchainSwift/Core/Serialization.swift
Expand Up @@ -11,7 +11,7 @@ protocol Serializable {
func serialized() -> Data
}
protocol Deserializable {
func deserialized() -> Self
static func deserialize(_ data: Data) throws -> Self
}

protocol BinaryConvertible {
Expand Down
8 changes: 7 additions & 1 deletion Sources/BlockchainSwift/Core/Transaction.swift
Expand Up @@ -7,7 +7,7 @@

import Foundation

public struct Transaction: Serializable {
public struct Transaction: Codable, Serializable {
/// Transaction inputs, which are sources for coins
public let inputs: [TransactionInput]

Expand Down Expand Up @@ -48,3 +48,9 @@ public struct Transaction: Serializable {
return Transaction(inputs: txIns, outputs: txOuts)
}
}

extension Transaction: Equatable {
public static func == (lhs: Transaction, rhs: Transaction) -> Bool {
return lhs.txHash == rhs.txHash
}
}
2 changes: 1 addition & 1 deletion Sources/BlockchainSwift/Core/TransactionInput.swift
Expand Up @@ -8,7 +8,7 @@
import Foundation

/// Inputs to a transaction
public struct TransactionInput: Serializable {
public struct TransactionInput: Codable, Serializable {
// A reference to the previous Transaction output
public let previousOutput: TransactionOutPoint

Expand Down
2 changes: 1 addition & 1 deletion Sources/BlockchainSwift/Core/TransactionOutPoint.swift
Expand Up @@ -8,7 +8,7 @@
import Foundation

/// The out-point of a transaction, referened in TransactionInput
public struct TransactionOutPoint: Serializable {
public struct TransactionOutPoint: Codable, Serializable {
/// The hash of the referenced transaction
public let hash: Data

Expand Down
6 changes: 5 additions & 1 deletion Sources/BlockchainSwift/Core/TransationOutput.swift
Expand Up @@ -7,7 +7,7 @@

import Foundation

public struct TransactionOutput: Serializable {
public struct TransactionOutput: Codable, Serializable {
/// Transaction value
public let value: UInt64

Expand All @@ -24,4 +24,8 @@ public struct TransactionOutput: Serializable {
public var hash: Data {
return serialized().sha256()
}

public func isLockedWith(publicKeyHash: Data) -> Bool {
return self.address == publicKeyHash
}
}

0 comments on commit a7216ed

Please sign in to comment.