It seems as if the BitCoin fad has eased up a bit, especially after these types of currencies suffered some serious blows. Be that as it may, the BlockChain is still a very interesting technology, and it won’t die soon.
In my previous article, “Introduction to Blockchain Programming,” I explained how to get started with BlockChain programs in VB. Today, let’s take it further, and create a test application. Please ensure that you have the code as explained in Part 1, because we will continue to build on to that application.
Code
Add a Module for our Extension methods:
Public Module Extensions
<System.Runtime.CompilerServices.Extension>
Public Function CutArray(Of T)(tSrc As T(), uStart As UInt64, _
uLimit As UInt64) As T()
If uLimit < 0 Then
uLimit = BitConverter.ToUInt64(BitConverter._
GetBytes(tSrc.LongLength), 0) + uLimit
End If
Dim uLength As UInt64 = uLimit - uStart
Dim tResult As T() = New T(uLength - 1) {}
For i As UInt64 = 0 To uLength - 1
tResult(i) = tSrc(i + uStart)
Next
Return tResult
End Function
<System.Runtime.CompilerServices.Extension>
Public Function NullArray(Of TSource)(ieSrc As _
IEnumerable(Of TSource)) As Boolean
Return ieSrc.All(Function(t) t Is Nothing)
End Function
Private ReadOnly HexStrings As String() = New String() {"00",
"01", "02", "03", "04", "05", "06", "07", "08", "09", "0a",
"0b", "0c", "0d", "0e", "0f", "10", "11", "12", "13", "14",
"15", "16", "17", "18", "19", "1a", "1b", "1c", "1d", "1e",
"1f", "20", "21", "22", "23", "24", "25", "26", "27", "28",
"29", "2a", "2b", "2c", "2d", "2e", "2f", "30", "31", "32",
"33", "34", "35", "36", "37", "38", "39", "3a", "3b", "3c",
"3d", "3e", "3f", "40", "41", "42", "43", "44", "45", "46",
"47", "48", "49", "4a", "4b", "4c", "4d", "4e", "4f", "50",
"51", "52", "53", "54", "55", "56", "57", "58", "59", "5a",
"5b", "5c", "5d", "5e", "5f", "60", "61", "62", "63", "64",
"65", "66", "67", "68", "69", "6a", "6b", "6c", "6d", "6e",
"6f", "70", "71", "72", "73", "74", "75", "76", "77", "78",
"79", "7a", "7b", "7c", "7d", "7e", "7f", "80", "81", "82",
"83", "84", "85", "86", "87", "88", "89", "8a", "8b", "8c",
"8d", "8e", "8f", "90", "91", "92", "93", "94", "95", "96",
"97", "98", "99", "9a", "9b", "9c", "9d", "9e", "9f", "a0",
"a1", "a2", "a3", "a4", "a5", "a6", "a7", "a8", "a9", "aa",
"ab", "ac", "ad", "ae", "af", "b0", "b1", "b2", "b3", "b4",
"b5", "b6", "b7", "b8", "b9", "ba", "bb", "bc", "bd", "be",
"bf", "c0", "c1", "c2", "c3", "c4", "c5", "c6", "c7", "c8",
"c9", "ca", "cb", "cc", "cd", "ce", "cf", "d0", "d1", "d2",
"d3", "d4", "d5", "d6", "d7", "d8", "d9", "da", "db", "dc",
"dd", "de", "df", "e0", "e1", "e2", "e3", "e4", "e5", "e6",
"e7", "e8", "e9", "ea", "be", "ec", "ed", "ee", "ef", "f0",
"f1", "f2", "f3", "f4", "f5", "f6", "f7", "f8", "f9", "fa",
"fb", "fc", "fd", "fe", "ff"}
<System.Runtime.CompilerServices.Extension>
Public Function ConvertToHex(value As Byte) As String
Return HexStrings(value)
End Function
End Module
The Extension Module provides extension methods for the NullArray and CutArray methods, as well as the ConvertToHex method. Add the Message class.
Class Message
Private ReadOnly btToken As Byte() = {34, 64, 8, 145}
Public Message As New Dictionary(Of String, String)() From {
{"MessageHandshake", "Handshake"},
{"MessageDisconnect", "Disconnect"},
{"MessagePing", "Ping"},
{"MessagePong", "Pong"},
{"MessageGetPeers", "Get peers"},
{"MessagePeers", "Peers"},
{"MessageTransactions", "Transactions"},
{"MessageBlock", "Blocks"},
{"MessageGetChain", "Get chain"},
{"MessageNotInChain", "Not in chain"}
}
End Class
The Message class contains a Message method, which is a Dictionary object that holds the various messages can be output by our Token objects.
Add the RLPDecode class:
Public NotInheritable Class RLPDecode
Private Shared ReadOnly uOffsetShort As UInt64 = &H80
Private Shared ReadOnly uOffsetLong As UInt64 = &HB8
Private Shared ReadOnly uOffsetShortList As UInt64 = &HC0
Private Shared ReadOnly uOffsetLongList As UInt64 = &HF8
Private Shared ReadOnly uMax As UInt64 = &HFF
Public Shared Function Decode(btData As Byte(), _
uPos As UInt64) As Decode
If btData Is Nothing OrElse btData.Length < 1 Then
Return Nothing
End If
Dim uPrefix As UInt64 = btData(uPos) And uMax
If uPrefix = uOffsetShort Then
Return New Decode(uPos + 1, New Byte(-1) {})
ElseIf uPrefix < uOffsetShort Then
Return New Decode(uPos + 1, New Byte() {btData(uPos)})
ElseIf uPrefix < uOffsetLong Then
Dim len As UInt64 = uPrefix - uOffsetShort
Return New Decode(uPos + 1 + len, btData.CutArray(uPos + _
1, uPos + 1 + len))
ElseIf uPrefix < uOffsetShortList Then
Dim uLength As UInt64 = uPrefix - uOffsetLong + 1
Dim uBytesLength As UInt64 = Converter.ConvertByteArray _
ToUInt64(btData.CutArray(uPos + 1, uPos + 1 + uLength))
Return New Decode(uPos + 1 + uLength + uBytesLength, _
btData.CutArray(uPos + 1 + uLength, uPos + 1 + _
uLength + uBytesLength))
ElseIf uPrefix < uOffsetLongList Then
Dim uLen As UInt64 = uPrefix - uOffsetShortList
Dim uPrev As UInt64 = uPos
uPos += 1
Return DecodeList(btData, uPos, uPrev, uLen)
ElseIf uPrefix < uMax Then
Dim uLength As UInt64 = uPrefix - uOffsetLongList + 1
Dim uListLength As UInt64 = Converter.ConvertByteArrayTo_
UInt64(btData.CutArray(uPos + 1, uPos + 1 + uLength))
uPos = uPos + uLength + 1
Dim uPrev As UInt64 = uListLength
Return DecodeList(btData, uPos, uPrev, uListLength)
End If
End Function
Private Shared Function DecodeList(uData As Byte(), uPos As _
UInt64, uPrev As UInt64, uLength As UInt64) As Decode
Dim lstSllice As New List(Of [Object])()
Dim i As UInt64 = 0
While i < uLength
Dim dResult As Decode = Decode(uData, uPos)
lstSllice.Add(dResult.GetDecoded())
uPrev = dResult.GetPos()
i += (uPrev - uPos)
uPos = uPrev
End While
Return New Decode(uPos, lstSllice.ToArray())
End Function
Public Shared Function StringToByteArray(strHex As String) _
As Byte()
If strHex.Length Mod 2 = 1 Then
Throw New Exception("No odd number of digits")
End If
Dim arrTemp As Byte() = New Byte((strHex.Length >> 1) _
- 1) {}
For i As Integer = 0 To (strHex.Length >> 1) - 1
arrTemp(i) = CByte((GetHexVal(Hex(i << 1)) << 4) + _
(GetHexVal(Hex((i << 1) + 1))))
Next
Return arrTemp
End Function
Private Shared Function GetHexVal(chrHex As Char) As Integer
Dim iVal As Integer = Val(chrHex)
Return iVal - (If(iVal < 58, 48, 87))
End Function
End Class
Recursive Length Prefix (RLP) encodes nested arrays of binary data.
Add the RLPEncode class:
Public NotInheritable Class RLPEncode
Private Shared ReadOnly uength As UInt64 = UInt64.MaxValue
Private Shared ReadOnly uSized As Integer = 56
Private Shared ReadOnly uOffsetShortItem As Integer = &H80
Private Shared ReadOnly uOffsetShortList As Integer = &HC0
Public Shared Function Encode(oInput As [Object]) As Byte()
If TypeOf oInput Is Array Then
Dim arrInput As [Object]() = DirectCast(oInput, _
[Object]())
If arrInput.Length = 0 Then
Return EncodeLength(arrInput.Length, uOffsetShortList)
End If
Dim btOutput As Byte() = New Byte(-1) {}
For Each arrayObject As [Object] In arrInput
btOutput = Encoder.JoinByteArrays(btOutput, _
Encode(arrayObject))
Next
Dim btPrefix As Byte() = EncodeLength(btOutput.Length, _
uOffsetShortList)
Return Encoder.JoinByteArrays(btPrefix, btOutput)
Else
If oInput Is Nothing Then
Throw New ArgumentNullException("Null input")
End If
Dim btHexInput As Byte() = Encoder.ToHex(oInput)
If btHexInput.Length = 1 Then
Return btHexInput
Else
Dim btFirst As Byte() = EncodeLength(btHexInput.Length, _
uOffsetShortItem)
Return Encoder.JoinByteArrays(btFirst, btHexInput)
End If
End If
End Function
Public Shared Function EncodeLength(uLength As ULong, iOffset _
As Integer) As Byte()
If uLength < uSized Then
Dim btFirst As Byte = CByte(uLength + iOffset)
Return New Byte() {btFirst}
ElseIf DirectCast(uLength, UInt64) < uength Then
Dim btLength As Byte() = Encoder.ConvertUInt64ToByte _
Array(DirectCast(uLength, UInt64))
Dim btFirst As Byte = CByte(btLength.Length + iOffset + _
uSized - 1)
Return Encoder.JoinByteArrays(New Byte() {btFirst}, _
btLength)
End If
Throw New Exception("Input too long")
End Function
End Class
Together, the Decode and Encode classes deal with nested arrays of binary data. They encode and decode the structure of integers.
Everything is now set up. Now, we can add a test class library to make use of all the BlockChain functionalities.
Create a Test class for each of the previous project’s classes. Let’s start with the CompactEncoderTest class that should be added to the new Class library.
Imports Microsoft.VisualStudio.TestTools.UnitTesting
Imports HTG_Ethereum
Imports System.Text
Public Class CompactEncoderTest
Private Shared ReadOnly T As Byte = 16
<TestMethod>
Public Sub TestCompactEncodeOne()
Dim test As Byte() = New Byte() {1, 2, 3, 4, 5}
Dim expected As Byte() = New Byte() {&H11, &H23, &H45}
Dim result As Byte() = CompactEncoder.CompactEncode(test)
Assert.AreEqual(Encoding.ASCII.GetString(result),
Encoding.ASCII.GetString(expected))
End Sub
<TestMethod>
Public Sub TestCompactEncodeTwo()
Dim test As Byte() = New Byte() {0, 1, 2, 3, 4, 5}
Dim expected As Byte() = New Byte() {&H0, &H1, &H23, _
&H45}
Dim result As Byte() = CompactEncoder.CompactEncode(test)
Assert.AreEqual(Encoding.ASCII.GetString(result), _
Encoding.ASCII.GetString(expected))
End Sub
<TestMethod>
Public Sub TestCompactEncodeThree()
Dim test As Byte() = New Byte() {0, 15, 1, 12, 11, 8,
T}
Dim expected As Byte() = New Byte() {&H20, &HF, _
&H1C, &HB8}
Dim result As Byte() = CompactEncoder.CompactEncode(test)
Assert.AreEqual(Encoding.ASCII.GetString(result), _
Encoding.ASCII.GetString(expected))
End Sub
<TestMethod>
Public Sub TestCompactEncodeFour()
Dim test As Byte() = New Byte() {15, 1, 12, 11, 8, T}
Dim expected As Byte() = New Byte() {&H3F, &H1C, &HB8}
Dim result As Byte() = CompactEncoder.CompactEncode(test)
Assert.AreEqual(Encoding.ASCII.GetString(result), _
Encoding.ASCII.GetString(expected))
End Sub
<TestMethod>
Public Sub TestCompactHexDecode()
Dim test As Byte() = Encoding.ASCII.GetBytes("verb")
Dim expected As Byte() = New Byte() {7, 6, 6, 5, 7, 2,
6, 2, 16}
Dim result As Byte() = CompactEncoder.CompactHexDecode(test)
Assert.AreEqual(Encoding.ASCII.GetString(result),
Encoding.ASCII.GetString(expected))
End Sub
<TestMethod>
Public Sub TestCompactDecodeOne()
Dim test As Byte() = New Byte() {&H11, &H23, &H45}
Dim expected As Byte() = New Byte() {1, 2, 3, 4, 5}
Dim result As Byte() = CompactEncoder.CompactDecode(test)
Assert.AreEqual(Encoding.ASCII.GetString(result), _
Encoding.ASCII.GetString(expected))
End Sub
<TestMethod>
Public Sub TestCompactDecodeTwo()
Dim test As Byte() = New Byte() {&H0, &H1, &H23, _
&H45}
Dim expected As Byte() = New Byte() {0, 1, 2, 3, 4, 5}
Dim result As Byte() = CompactEncoder.CompactDecode(test)
Assert.AreEqual(Encoding.ASCII.GetString(result), _
Encoding.ASCII.GetString(expected))
End Sub
<TestMethod>
Public Sub TestCompactDecodeThree()
Dim test As Byte() = New Byte() {&H20, &HF, &H1C,
&HB8}
Dim expected As Byte() = New Byte() {0, 15, 1, 12, 11, 8,
T}
Dim result As Byte() = CompactEncoder.CompactDecode(test)
Assert.AreEqual(Encoding.ASCII.GetString(result), _
Encoding.ASCII.GetString(expected))
End Sub
<TestMethod>
Public Sub TestCompactDecodeFour()
Dim test As Byte() = New Byte() {&H3F, &H1C, &HB8}
Dim expected As Byte() = New Byte() {15, 1, 12, 11, 8, T}
Dim result As Byte() = CompactEncoder.CompactDecode(test)
Assert.AreEqual(Encoding.ASCII.GetString(result), _
Encoding.ASCII.GetString(expected))
End Sub
End Class
These send Test to interrogate the results and compares them to what was expected.
Add the Test for RLPDecoder:
Imports Microsoft.VisualStudio.TestTools.UnitTesting
Imports HTG_Ethereum
Imports System.Text
<TestClass>
Public Class RLPDecoderTest
<TestMethod>
Public Sub TestDecodeSingleCharacter()
Dim test As String = "64"
Dim expected As String = "d"
Dim result As [Object] = Encoding.ASCII.GetString _
(DirectCast(RLPDecode.Decode(RLPDecode.StringToByteArray _
(test), UInt64.MinValue).GetDecoded(), Byte()))
Assert.AreEqual(expected, result)
End Sub
<TestMethod>
Public Sub TestDecodeSingleString()
Dim test As String = "83646f67"
Dim expected As String = "dog"
Dim result As [Object] = Encoding.ASCII.GetString _
(DirectCast(RLPDecode.Decode(RLPDecode.StringToByteArray _
(test), UInt64.MinValue).GetDecoded(), Byte()))
Assert.AreEqual(expected, result)
End Sub
<TestMethod>
Public Sub TestDecodeEmptyString()
Dim test As String = "80"
Dim expected As String = ""
Dim result As [Object] = Encoding.ASCII.GetString _
(DirectCast(RLPDecode.Decode(RLPDecode.StringToByteArray _
(test), UInt64.MinValue).GetDecoded(), Byte()))
Assert.AreEqual(expected, result)
End Sub
<TestMethod>
Public Sub TestDecodeArrayOfEmptyStrings()
Dim test As String = "c0"
Dim expected As String() = New String() {}
Dim expectedBool As Boolean = (expected Is Nothing OrElse _
expected.Length = 0)
Dim result As [Object]() = DirectCast(RLPDecode.Decode _
(RLPDecode.StringToByteArray(test), UInt64.MinValue) _
.GetDecoded(), [Object]())
Dim resultBool As Boolean = result.NullArray()
Assert.AreEqual(expectedBool, resultBool)
End Sub
<TestMethod>
Public Sub TestDecodeZero()
Dim test As String = "80"
Dim expected As UInt64 = 0
Dim result As [Object] = Converter.ConvertByteArrayTo _
UInt64(DirectCast(RLPDecode.Decode(RLPDecode.StringTo _
ByteArray(test), UInt64.MinValue).GetDecoded(), Byte()))
Assert.AreEqual(expected, result)
End Sub
<TestMethod>
Public Sub TestDecodeLowInteger()
Dim test As String = "0f"
Dim expected As UInt64 = 15
Dim result As [Object] = Converter.ConvertByteArrayTo _
UInt64(DirectCast(RLPDecode.Decode(RLPDecode.StringTo _
ByteArray(test), UInt64.MinValue).GetDecoded(), Byte()))
Assert.AreEqual(expected, result)
End Sub
<TestMethod>
Public Sub TestDecodeMediumInteger()
Dim test As String = "820400"
Dim expected As UInt64 = 1024
Dim result As [Object] = Converter.ConvertByteArrayTo _
UInt64(DirectCast(RLPDecode.Decode(RLPDecode.StringTo _
ByteArray(test), UInt64.MinValue).GetDecoded(), Byte()))
Assert.AreEqual(expected, result)
End Sub
<TestMethod>
Public Sub TestDecodeBigInteger()
Dim test As String = "88ffffffffffffffff"
Dim expected As UInt64 = 18446744073709551615UL
Dim result As [Object] = Converter.ConvertByteArrayTo _
UInt64(DirectCast(RLPDecode.Decode(RLPDecode.StringTo _
ByteArray(test), UInt64.MinValue).GetDecoded(), Byte()))
Assert.AreEqual(expected, result)
End Sub
<TestMethod>
Public Sub TestDecodeLongString()
Dim test As String = "b8384c6f72656d20697073756d20646f6c6f _
722073697420616d65742c20636f6e7365637465747572206164697 _
069736963696e6720656c6974"
Dim expected As String = "test"
Dim result As [Object] = Encoding.ASCII.GetString(DirectCast _
(RLPDecode.Decode(RLPDecode.StringToByteArray(test), _
UInt64.MinValue).GetDecoded(), Byte()))
Assert.AreEqual(expected, result)
End Sub
<TestMethod>
Public Sub TestDecodeEmptyStringList()
Dim test As String = "c0"
Dim expected As String() = New String(-1) {}
Dim expectedBool As Boolean = (expected Is Nothing OrElse _
expected.Length = 0)
Dim result As [Object]() = DirectCast(RLPDecode.Decode _
(RLPDecode.StringToByteArray(test), UInt64.MinValue) _
.GetDecoded(), [Object]())
Dim resultBool As Boolean = result.NullArray()
Assert.AreEqual(expectedBool, resultBool)
End Sub
<TestMethod>
Public Sub TestDecodeShortStringList()
Dim test As String = "cc83646f6783676f6483636174"
Dim expected As String() = New String() {"dog", "god", "cat"}
Dim result As [Object]() = DirectCast(RLPDecode.Decode _
(RLPDecode.StringToByteArray(test), UInt64.MinValue) _
.GetDecoded(), [Object]())
Assert.AreEqual(expected(0), Encoding.ASCII.GetString _
(DirectCast(result(0), Byte())))
Assert.AreEqual(expected(1), Encoding.ASCII.GetString _
(DirectCast(result(1), Byte())))
Assert.AreEqual(expected(2), Encoding.ASCII.GetString _
(DirectCast(result(2), Byte())))
End Sub
<TestMethod>
Public Sub TestDecodeLongStringList()
' fails'
Dim test As String = "f83e83636174b8384c6f72656d20697073756 _
d20646f6c6f722073697420616d65742c20636f6e736563746574757 _
2206164697069736963696e6720656c6974"
Dim expected As String() = New String() {"cat", "test"}
Dim result As [Object]() = DirectCast(RLPDecode.Decode _
(RLPDecode.StringToByteArray(test), UInt64.MinValue) _
.GetDecoded(), [Object]())
Assert.AreEqual(expected(0), Encoding.ASCII.GetString _
(DirectCast(result(0), Byte())))
Assert.AreEqual(expected(1), Encoding.ASCII.GetString _
(DirectCast(result(1), Byte())))
End Sub
<TestMethod>
Public Sub TestDecodeMultiList()
' fails'
Dim test As String = "cc01c48363617483646f67c102"
Dim expected As [Object]() = New [Object]() {DirectCast(1, _
UInt64), New [Object]() {"cat"}, "dog", New [Object]() _
{DirectCast(2, UInt64)}}
Dim result As [Object]() = DirectCast(RLPDecode.Decode _
(RLPDecode.StringToByteArray(test), UInt64.MinValue) _
.GetDecoded(), [Object]())
Assert.AreEqual(expected(0), Converter.ConvertByteArrayTo _
UInt64(DirectCast(result(0), Byte())))
Assert.AreEqual(expected(1).ToString(), result(1).ToString())
Assert.AreEqual(expected(2), Encoding.ASCII.GetString _
(DirectCast(result(2), Byte())))
Assert.AreEqual(DirectCast(expected(3), [Object]())(0), _
Converter.ConvertByteArrayToUInt64(DirectCast(DirectCast _
(result(3), [Object]())(0), Byte())))
End Sub
<TestMethod>
Public Sub TestDecodeListOfEmptyLists()
' fails'
Dim test As String = "c4c2c0c0c0"
Dim expected As [Object]() = New [Object]() {New [Object]() _
{New [Object]() {}, New [Object]() {}}, New [Object]() {}}
Dim result As [Object]() = DirectCast(RLPDecode.Decode _
(RLPDecode.StringToByteArray(test), UInt64.MinValue) _
.GetDecoded(), [Object]())
Assert.AreEqual(expected.NullArray(), result.NullArray())
End Sub
<TestMethod>
Public Sub TestDecodeTwoListsOfEmptyLists()
Dim test As String = "c7c0c1c0c3c0c1c0"
Dim expected As [Object]() = New [Object]() {New [Object]() _
{}, New [Object]() {New [Object]() {}}, New [Object]() _
{New [Object]() {}, New [Object]() {New [Object]() {}}}}
Dim result As [Object]() = DirectCast(RLPDecode.Decode _
(RLPDecode.StringToByteArray(test), UInt64.MinValue) _
.GetDecoded(), [Object]())
Assert.AreEqual(expected.NullArray(), result.NullArray())
End Sub
End Class
It makes use of the methods and tries to obtain the correct results.
Finally, add the Test class for the DiscReason class:
Imports Microsoft.VisualStudio.TestTools.UnitTesting
Imports HTG_Ethereum
<TestClass>
Public Class DiscReasonTest
<TestMethod>
Public Sub TestGetDiscReason()
Assert.AreEqual(Disconnected.GetReason(0), "Disconnect _
requested")
Assert.AreEqual(Disconnected.GetReason(1), "Disconnect _
TCP sys error")
Assert.AreEqual(Disconnected.GetReason(6), "Disconnect _
wrong genesis block")
Assert.AreEqual(Disconnected.GetReason(7), "Disconnect _
incompatible network")
Assert.AreEqual(Disconnected.GetReason(8), "Unknown")
Assert.AreEqual(Disconnected.GetReason(9), "Unknown")
Assert.AreEqual(Disconnected.GetReason(100), "Unknown")
End Sub
End Class
Conclusion
The BlockChain is a very complicated animal. It is an interesting technology, and it is still in its infancy, but knowing how it works is quite fun.