Protocol Oriented Blockchain in Swift

Part 1 of 3 — The Chain

RNDM
13 min readMar 6, 2019

It has been a little while since I put pen to paper and wrote an article around creating anything in Swift. For the most part, this has been because I have been working on our low-code, cross-platform open-source solutions revolving around the world of JavaScript on www.rndm.com and Github. However, I had a chance to dive back into the beautiful world of Swift when I wanted to take a look at building a blockchain.

When looking for inspiration, I was dismayed to find that the majority of articles around the web seem to suggest using reference type solutions such as classes to create blocks, payloads and chains in Swift. Whilst this may be the best way to build it in other languages, it just seemed to go against any and all principles of the Swift language.

With strong ties to functional programming paradigms, the immutability of structs and protocol driven programming experiences, Swift seems by default to be ideally positioned to create a blockchain. And so, I decided to create this article for the world to play with.

(TL;DR: If you are keen to get your hands on the working playground for this, you can do that through the RNDM Github Page here.)

Who is this article for?

I have to say from the start that though we will start off pretty slowly in our code, it will get pretty deep pretty fast. This article is designed for developers that want to challenge themselves and so might tend towards the more complex end of the Swift spectrum.

Where to start?

OK, caveat out of the way, let’s get started. Usually, a great way to start is by knowing where we want to finish. And where we want to finish is by having a table view that will subscribe to a blockchain that will present a payload containing an account number and a value. It will look like the below:

As you can see, I have skimped slightly on the design. But what we can see is that it provides two views. One displaying the account number and the current value. The second shows the full chain including the transactions that go in together the create the chain.

The code

Utils

For this project, we will want to make use of some simple utility methods. Swift has such a beautiful paradigm for this with extensions, so let’s quickly build out a few that will help us along our journey:

// This extension will allow us to take a look at a string encoding
// of a data object
extension Data {
func toString(encoding: String.Encoding = .utf8) -> String {
return String(data: self, encoding: encoding) ?? ""
}
}

We will also be making heavy use of the Encodable protocol, so let’s make use of this with three more extensions:

extension Encodable {
// This extension will prevent us from having to recreate the
// JSONEncode and writing the encoding mechanism each time
var encoded: Data? {
return try? JSONEncoder().encode(self)
}
// This makes use of the previous data extension we created earlier
var string: String {
return String(describing: encoded?.toString())
}
// This will give us an object to work with if the data does
// serialise into JSON
var json: Any? {
guard let data = encoded else { return nil }
return try? JSONSerialization
.jsonObject(with: data, options: .allowFragments)
}
}

Payload

We are going to start from the leaf and work our way back to the branch. SO for this project we will start at the payload. Effectively, the payload is the data associated with the block. In our case, we only want to elements created with the Payload: an account number and a value.

What is better for this than a struct?

public struct Payload {
public let value: Int
public let account: String
}

There are a few more items we are going to want to do with this Payload, so for now let’s add these in.

Firstly, we will want to be able to encode and decode a Payload to and from JSON data:

extension Payload: Codable {}

Next, let’s make the payload conform to the CustomStringLiteral so we can create our own description:

extension Payload: CustomStringConvertible {
var description: String {
return string
}
}

Finally, we will want to do some comparisons, and we can do that by having our payload conform to the Equatable protocol:

extension Payload: Equatable {}func == (lhs: Payload, rhs: Payload) -> Bool {
return lhs.description == rhs.description
}

That done, our Payload should look something like this:

// Final Payload.swift
public struct Payload {
public let value: Int
public let account: String
}
extension Payload: Codable {}extension Payload: CustomStringConvertible {
var description: String {
return string
}
}
extension Payload: Equatable {}func == (lhs: Payload, rhs: Payload) -> Bool {
return lhs.description == rhs.description
}

Block

The next element of our journey is the block. The block provides a few pieces of information that are important to the chain we want to build:

  • hash: a way of identifying the current block
  • previous: an optional way of identifying the parent of this block
  • transaction: a way of identifying the type of the transaction
  • index: a way of identifying the position of the block in the chain

Again, since this is immutable, we can create this as a struct:

struct Block: Codable {
let hash = UUID().uuidString
let payload: Payload
let transaction: String
let previous: String?
let index: Int
}

Here we have created a struct that when we initialise, will provide us with a memberwise initialiser that will have the four parameters without a value already set.

Finally, let’s make this conform to the Codable protocol:

extension Block: Codable {}

And we end up with the final Block looking like the below:

// Final Block.swift
struct Block: Codable {
let hash = UUID().uuidString
let payload: Payload
let transaction: String
let previous: String?
let index: Int
}
extension Block: Codable {}

Transaction

Next on our list is to create a suite of accepted transactions. For this we will make use of enums. Specifically, we will make use of associated value enums. There are 3 direct cases we want to handle and 2 special cases that we will only want values for:

// Final Transaction.swift
enum Transaction {
case creation(account: String, value: Int)
case exchange(from: String, to: String, value: Int)
case value(of: String)
}
extension Transaction {
static let creationDescription = "creation"
static let additionDescription = "addition"
static let subtractionDescription = "subtraction"
static let exchangeDescription = "exchange"
static let valueDescription = "value"
static func isValueTransaction(input: String) -> Bool {
return [valueDescription, creationDescription].contains(input)
}
}
extension Transaction: CustomStringConvertible {
var description: String {
switch self {
case .creation: return Transaction.creationDescription
case .exchange: return Transaction.exchangeDescription
case .value: return Transaction.valueDescription
}
}
}

The code above shows that we will have 3 supported transactions in our code: creation, exchange and value. We also have two more description strings for addition and subtraction. If you take a look at the middle part of the gif we included as our future output, then you would see that we have indeed got blocks in our chain that have addition and subtraction. However, we don’t have any that have a type of exchange. This will be because when we are building our chain, we are going to generate some different transactions for our chain based on the exchange type.

We have also created a static method called isValueTransaction. In our chain, both the value and creation transaction constitute a final value of an account, so we can inout a transaction string here and it will determine if the transaction was a value type.

Response

When interacting with a chain, not all transactions are successful. So we will create a protocol that will allow us to expose some properties and functionality, whilst allowing for a success and error state.

protocol Response: Codable, CustomStringConvertible {
var response: String { get }
func transact(transaction: Transaction) -> Response
func create(payload: Payload, transaction: String) -> Response
}

We start by creating a protocol that can be encoded, decoded and will also return a description. That protocol will include a response as a String and two methods: transact and create.

We will extend the Response protocol to allow for the description property:

extension Response {
var description: String {
return string
}
}

Then we will make both the transact and create methods optional, by extending the Response protocol and allowing each of these to simply return their parent object:

extension Response {
func create(payload: Payload, transaction: String) -> Response {
return self
}

func transact(transaction: Transaction) -> Response {
return self
}
}

Our final Response protocol should look something like this:

// Final Response.swift
protocol Response: Codable, CustomStringConvertible {
var response: String { get }
func transact(transaction: Transaction) -> Response
func create(payload: Payload, transaction: String) -> Response
}
extension Response {
var description: String {
return string
}
}
extension Response {
func create(payload: Payload, transaction: String) -> Response {
return self
}

func transact(transaction: Transaction) -> Response {
return self
}
}

Error

As we said earlier, we will have one of two response types: Error and a Success. In the case of the error, let’s build out an Error struct and make it conform to our Response protocol:

// Final Error.swift
struct Error: Response {
let response: String = "ERROR"
let transaction: String
let reason: String
}

Since we made all the methods optional, the only item we need to complete is the response, which we will define as “ERROR”. And we will add two more items: transaction, which will be the string value defining a transaction type, and the reason for the error.

Chain

And at last we have reached the reason for this particular article. The Chain.

This struct will give us the power we need to build out an immutable chain of blocks all connected to each other. So, let’s first define our struct:

struct Chain {
let response: String = "SUCCESS"
fileprivate let chain: [Block]
init(chain: [Block] = []) {
self.chain = chain
}
}

In here we have created a struct called Chain. It has a property called response which we have given a value of SUCCESS. The next item it has is a fileprivate property called chain, an array of Blocks. We will use this later in the file when defining an equatable state for the Chain, which is why we need it to be file private rather than private.

We also an two initialisers that will allow us to create a chain from either an empty state or a pre-populated chain. This type of initialisation and the concept of the struct means that when we start to build our chain, we will actually always be creating a new version of that chain.

Again, let’s extend the chain for a description:

extension Chain: CustomStringConvertible {
var description: String {
return string
}
}

And let’s make it conform to Equatable:

extension Chain: Equatable {}func == (lhs: Chain, rhs: Chain) -> Bool {
return lhs.chain.count == rhs.chain.count
&& lhs.chain.reduce(true, {
return $0
&& rhs.chain[$1.index].hash == $1.hash
&& rhs.chain[$1.index].payload == $1.payload
})
}

OK, so the above has some interesting syntax. First, we are ensuring the chains are the same size. Then we are reducing down into a boolean value by comparing the hash the item in the right hand side chain with hash of the element at the same index of the left hand chain, as well as comparing the payload of the same items, which is so easy because we already conformed the Payload to Equatable!

And with this we can also traverse the entire chain to ensure that each block links up with the corresponding parent block:

extension Chain {
var isValid: Bool {
return chain.reduce(true) {
$0 && (
$1.index + 1 == chain.count
|| chain[$1.index + 1].previous == $1.hash
)
}
}
}

Now we can create a helpful function to find a block that has a corresponding value transaction type.

extension Chain {
func find(account: String) -> Block? {
for i in stride(from: chain.count - 1, through: 0, by: -1) {
let block = chain[i]
if block.payload.account == account
&& Transaction.isValueTransaction(input: block.transaction) {
return block
}
}
return nil
}
}

Since we are interested only in the last instance of Block where it is a value transaction that matches the given account number, we are traversing our chain from last to first.

Finally, we are going to make our chain conform to our Response protocol:

extension Chain: Response {
func create(payload: Payload, transaction: String) -> Response {
guard isValid else {
return Error(transaction: transaction.description,
reason: "status - INVALID")
}
var chain = self.chain
chain.append(Block(payload: payload,
transaction: transaction,
previous: chain.last?.hash,
index: chain.count)
)
return Chain(chain: chain)
}
}

The create method is the simplest one to work with. First, we validate the chain, or return an Error Response. Then we create a new mutable chain from our current chain, append a new block with the given Payload and transaction, then return a new instance of Chain.

Now on to the transact method:

extension Chain: Response {
...
func transact(transaction: Transaction) -> Response {
switch transaction {
case .creation(account: let account, value: let value):
if let _ = find(account: account) {
return Error(transaction: transaction.description,
reason: "account '\(account)' already exists")
}
return create(payload:
Payload(value: chain.count > 0 ? 0 : value,
account: account),
transaction: Transaction.creationDescription)
case .exchange(from: let from, to: let to, value: let value):
if (value <= 0) {
return Error(transaction: transaction.description,
reason: "value must be greater than 0")
}
if (to == from) {
return Error(transaction: transaction.description,
reason: "account identifiers must not be the same")
}
guard let fromAccount = find(account: from) else {
return Error(transaction: transaction.description,
reason: "the from account '\(from)' does not exist")
}
if (fromAccount.payload.value < value) {
return Error(transaction: transaction.description,
reason: "the from account does not contain enough credits")
}
let toAccount = find(account: to)
return (toAccount != nil ? self
: transact(transaction: Transaction.creation(account: to,
value: 0)))
.create(payload: Payload(value: -value, account: from),
transaction: Transaction.subtractionDescription)
.create(payload: Payload(value: value, account: to),
transaction: Transaction.additionDescription)
.create(payload: Payload(value: (toAccount?.payload.value ?? 0) + value, account: to),
transaction: Transaction.valueDescription)
.create(payload: Payload(value: fromAccount.payload.value - value, account: from),
transaction: Transaction.valueDescription)
default: return self
}
}
}

Well, if we though that there was a lot going on in any other method, then we might not have understood the complexity we were going to implement in our transaction method!

Let’s simplify it down for now: Out of the transactions we have, we only care about the creation and exchange transactions at this level, otherwise we will simple return the current instance of Chain.

The creation transaction is extremely simple: if we find an account of the same account number, then we won’t create a new account. Instead we will throw an Error. If we don’t then we will hit the create method in the Response conformity as above and return the newly created chain instance complete with the new account. We also want to set a rule that will allow a genesis block, ie.e the first block. This will be a special case that will allow us to create an account with a value greater than zero, otherwise we will only ever create a new account with a zero value. After all, we don’t want to generate credits out of thin air!

The exchange one is actually also pretty simple. The first four statements are only there to protect the chain from things like creating an exchange less than or equal to zero, doing an exchange from an account to the same account, creating an exchange from an account that doesn’t exist, or with a value greater than an account has. These are simply our chains ‘business’ rules.

The last part is really where all our other code has led to.

If we cannot find the to account, we will create it with zero value other wise we will use our own instance. After this, it is a series of transactions: subtract the value from one account, add the value to the next account, set the values of the two accounts. Done.

Now our final Chain file should look like this:

// Final Chain.swift
struct Chain {
let response: String = "SUCCESS"
fileprivate let chain: [Block]
init(chain: [Block] = []) {
self.chain = chain
}
}
extension Chain: CustomStringConvertible {
var description: String {
return string
}
}
extension Chain: Equatable {}func == (lhs: Chain, rhs: Chain) -> Bool {
return lhs.chain.count == rhs.chain.count
&& lhs.chain.reduce(true, {
return $0
&& rhs.chain[$1.index].hash == $1.hash
&& rhs.chain[$1.index].payload == $1.payload
})
}
extension Chain {
var isValid: Bool {
return chain.reduce(true) {
$0 && (
$1.index + 1 == chain.count
|| chain[$1.index + 1].previous == $1.hash
)
}
}
}
extension Chain {
func find(account: String) -> Block? {
for i in stride(from: chain.count - 1, through: 0, by: -1) {
let block = chain[i]
if block.payload.account == account
&& Transaction.isValueTransaction(input: block.transaction) {
return block
}
}
return nil
}
}
extension Chain: Response {
func create(payload: Payload, transaction: String) -> Response {
guard isValid else {
return Error(transaction: transaction.description,
reason: "status - INVALID")
}
var chain = self.chain
chain.append(Block(payload: payload,
transaction: transaction,
previous: chain.last?.hash,
index: chain.count)
)
return Chain(chain: chain)
}
func transact(transaction: Transaction) -> Response {
switch transaction {
case .creation(account: let account, value: let value):
if let _ = find(account: account) {
return Error(transaction: transaction.description,
reason: "account '\(account)' already exists")
}
return create(payload:
Payload(value: chain.count > 0 ? 0 : value,
account: account),
transaction: Transaction.creationDescription)
case .exchange(from: let from, to: let to, value: let value):
if (value <= 0) {
return Error(transaction: transaction.description,
reason: "value must be greater than 0")
}
if (to == from) {
return Error(transaction: transaction.description,
reason: "account identifiers must not be the same")
}
guard let fromAccount = find(account: from) else {
return Error(transaction: transaction.description,
reason: "the from account '\(from)' does not exist")
}
if (fromAccount.payload.value < value) {
return Error(transaction: transaction.description,
reason: "the from account does not contain enough credits")
}
let toAccount = find(account: to)
return (toAccount != nil ? self
: transact(transaction: Transaction.creation(account: to,
value: 0)))
.create(payload: Payload(value: -value, account: from),
transaction: Transaction.subtractionDescription)
.create(payload: Payload(value: value, account: to),
transaction: Transaction.additionDescription)
.create(payload: Payload(value: (toAccount?.payload.value ?? 0) + value, account: to),
transaction: Transaction.valueDescription)
.create(payload: Payload(value: fromAccount.payload.value - value, account: from),
transaction: Transaction.valueDescription)
default: return self
}
}
}

And that is that! We have a chain that is immutable and protocol oriented. Of courses, there are a plethora of ways to create a protocol oriented Chain and this is only one of them. However, I hope it opens your eyes to the sheer power of immutability within Swift.

In part 2 of this series, we will create a mock ‘Server’ and ‘Client’ so we can build out something that will take a URLRequest and deliver a chain for our client to consume.

About Paul:

Paul is a Cross-Platform Tech Lead with experience across iOS, Android and Web. In his spare time, apart from teaching people to code, he can be found writing out low-code, open-source solutions and advocating the automation of development work as well as its integration with machine learning and AI. You can check out his latest projects here:

https://www.rndm.com
https://www.github.com/rndm-com/

--

--