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.