Skip to content

Instantly share code, notes, and snippets.

@azamsharp
Created January 4, 2018 21:25
Show Gist options
  • Save azamsharp/be552f1d50b988533ba56264367ddbf9 to your computer and use it in GitHub Desktop.
Save azamsharp/be552f1d50b988533ba56264367ddbf9 to your computer and use it in GitHub Desktop.
//: Playground - noun: a place where people can play
import Cocoa
import Foundation
struct BrokenRule : Codable {
var contractName :String
var message :String
}
protocol SmartContract {
func apply(to transaction :Transaction)
}
class MaximumAmountTransferFundsContract : SmartContract {
func apply(to transaction: Transaction) {
if transaction.amount > 5000 {
transaction.addBrokenRule(BrokenRule(contractName: "MaximumAmountTransferFundsContract", message: "Your amount is more than $5000"))
}
}
}
class InternationalDomesticTransferFeesContract : SmartContract {
func apply(to transaction: Transaction) {
switch transaction.transactionType {
case .international:
transaction.fees = 10
case .domestic:
transaction.fees = 2
}
if transaction.amount < transaction.fees {
print("broken rule")
transaction.addBrokenRule(BrokenRule(contractName: "InternationalDomesticTransferFeesContract", message: "Insufficient funds"))
} else {
transaction.amount -= transaction.fees
}
}
}
enum TranslationType : String, Codable {
case international
case domestic
}
class Transaction : Codable {
var from :String
var to :String
var amount :Double
var fees :Double = 0.0
var transactionType :TranslationType = .domestic
var brokenRules :[BrokenRule] = [BrokenRule]()
var isValid :Bool {
get {
return self.brokenRules.count == 0
}
}
init(from :String, to:String, amount :Double, transactionType :TranslationType = .domestic) {
self.from = from
self.to = to
self.amount = amount
self.transactionType = transactionType
}
func addBrokenRule(_ brokenRule :BrokenRule) {
self.brokenRules.append(brokenRule)
}
}
class Block {
var index :Int = 0
var dateCreated :String
var previousHash :String = ""
var hash :String!
var nonce :Int
var key :String {
get {
let transactionData = try! JSONEncoder().encode(self.transactions)
let transactionsJSONString = String(data: transactionData, encoding: .utf8)
let keyPart = String(self.index) + self.dateCreated + self.previousHash + String(self.nonce)
return (keyPart + transactionsJSONString!)
}
}
private (set) var transactions :[Transaction] = [Transaction]()
func addTransaction(transaction :Transaction, contracts :[SmartContract] = [SmartContract]()) {
// run the smart contracts
contracts.forEach { contract in
contract.apply(to: transaction)
}
if transaction.isValid {
self.transactions.append(transaction)
}
}
init() {
self.dateCreated = Date().toString()
self.nonce = 0
}
}
class Blockchain {
private (set) var blocks :[Block] = [Block]()
private (set) var contracts :[SmartContract] = [SmartContract]()
init() {
// initialize contracts
initializeContracts()
}
private func initializeContracts() {
self.contracts = [InternationalDomesticTransferFeesContract(),MaximumAmountTransferFundsContract()]
}
func addBlock(_ block :Block) {
if block.transactions.count > 0 {
self.blocks.append(block)
}
}
// this function is used to create a next block
func generateNextBlock() -> Block {
let block = Block()
if self.blocks.isEmpty {
block.previousHash = "0000000000000000"
block.hash = generateHash(for :block)
}
else {
let previousBlock = getPreviousBlock()
block.previousHash = previousBlock.hash
block.index = self.blocks.count
block.hash = generateHash(for: block)
}
return block
}
private func getPreviousBlock() -> Block {
return self.blocks[self.blocks.count - 1]
}
func displayAllBlocks() {
for block in self.blocks {
print("------ Block \(block.index) ---------")
print("Date Created : \(block.dateCreated) ")
print("Nonce : \(block.nonce) ")
print("Previous Hash : \(block.previousHash) ")
print("Hash : \(block.hash!) ")
print("------Transactions--------")
block.transactions.forEach { transaction in
print("Transaction from \(transaction.from)")
print("Transaction to \(transaction.to)")
print("Transaction amount \(transaction.amount)")
print("Transaction Fees \(transaction.fees) ")
print("Transaction Type \(transaction.transactionType.rawValue) ")
}
}
}
func generateHash(for block:Block) -> String {
var hash = block.key.sha1Hash()
while(!hash.hasPrefix("00")) {
block.nonce += 1
hash = block.key.sha1Hash()
print(hash)
}
return hash
}
}
// String Extension
extension String {
func sha1Hash() -> String {
let task = Process()
task.launchPath = "/usr/bin/shasum"
task.arguments = []
let inputPipe = Pipe()
inputPipe.fileHandleForWriting.write(self.data(using: String.Encoding.utf8)!)
inputPipe.fileHandleForWriting.closeFile()
let outputPipe = Pipe()
task.standardOutput = outputPipe
task.standardInput = inputPipe
task.launch()
let data = outputPipe.fileHandleForReading.readDataToEndOfFile()
let hash = String(data: data, encoding: String.Encoding.utf8)!
return hash.replacingOccurrences(of: " -\n", with: "")
}
}
extension Date {
func toString() -> String {
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
return formatter.string(from: self)
}
}
let transaction = Transaction(from: "Azam", to: "Mary", amount: 56, transactionType : .international)
let blockchain = Blockchain()
let block = blockchain.generateNextBlock()
block.addTransaction(transaction: transaction, contracts: blockchain.contracts)
if !transaction.isValid {
transaction.brokenRules.forEach { brokenRule in
print("BrokenRule Name - \(brokenRule.contractName) ")
print("BrokenRule Message - \(brokenRule.message) ")
}
} else {
blockchain.addBlock(block)
blockchain.displayAllBlocks()
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment