Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feature/ethereum-service #4

Open
wants to merge 19 commits into
base: master
Choose a base branch
from
Open
1 change: 0 additions & 1 deletion Package.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
// swift-tools-version:5.0
// The swift-tools-version declares the minimum version of Swift required to build this package.

import PackageDescription

let package = Package(
Expand Down
15 changes: 14 additions & 1 deletion Sources/Relayer/main.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import Foundation
import UB
import RelayerFramework
import UB

let handler = Handler()

Expand All @@ -9,5 +9,18 @@ node.delegate = handler
node.add(transport: CoreBluetoothTransport())

// @todo handle CLI input, repl
let service = EthereumService(url: URL(string: "https://rinkeby.infura.io/f7a08ae0242843f1b1cf480454a6bba5")!)


Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Limit vertical whitespace to a single empty line. Currently 2.

//Get Balance
let msg = Message(
proto: UBID(repeating: 1, count: 1),
recipient: Addr(repeating: 1, count: 1),
from: Addr(repeating: 1, count: 1),
origin: UBID(repeating: 1, count: 1),
message: "000F64928EcA02147075c7614A7d67B0C3Cb37D5DA".hexDecodedData()
)

service.handle(message: msg, node: node)

RunLoop.current.run()
18 changes: 18 additions & 0 deletions Sources/RelayerFramework/Extensions/Data+HexEncodedString.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import Foundation

extension Data {
/// A hexadecimal string representation of the bytes.
func hexEncodedString() -> String {
let hexDigits = Array("0123456789abcdef".utf16)
var hexChars = [UTF16.CodeUnit]()
hexChars.reserveCapacity(count * 2)

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lines should not have trailing whitespace.

for byte in self {
let (index1, index2) = Int(byte).quotientAndRemainder(dividingBy: 16)
hexChars.append(hexDigits[index1])
hexChars.append(hexDigits[index2])
}

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lines should not have trailing whitespace.

return String(utf16CodeUnits: hexChars, count: hexChars.count)
}
}
30 changes: 30 additions & 0 deletions Sources/RelayerFramework/Extensions/String+HexDecodedData.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import Foundation

extension String {
/// A data representation of the hexadecimal bytes in this string.
func hexDecodedData() -> Data {
// Get the UTF8 characters of this string
let chars = Array(utf8)

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lines should not have trailing whitespace.

// Keep the bytes in an UInt8 array and later convert it to Data
var bytes = [UInt8]()
bytes.reserveCapacity(count / 2)

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lines should not have trailing whitespace.

// It is a lot faster to use a lookup map instead of strtoul
let map: [UInt8] = [
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, // 01234567
0x08, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 89:;<=>?
0x00, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x00, // @ABCDEFG
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 // HIJKLMNO
]

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lines should not have trailing whitespace.

// Grab two characters at a time, map them and turn it into a byte
for i in stride(from: 0, to: count, by: 2) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Variable name should be between 3 and 40 characters long: 'i'

let index1 = Int(chars[i] & 0x1F ^ 0x10)
let index2 = Int(chars[i + 1] & 0x1F ^ 0x10)
bytes.append(map[index1] << 4 | map[index2])
}

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lines should not have trailing whitespace.

return Data(bytes)
}
}
6 changes: 3 additions & 3 deletions Sources/RelayerFramework/Handler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,18 @@ public class Handler: UB.NodeDelegate {
fileprivate var services = [UB.UBID: Service]()

/// Initializes a new Handler.
public init() { }
public init() {}

// @todo there probably should be more stuff here at one point.

/// :nodoc:
public func node(_: Node, didReceiveMessage message: Message) {
public func node(_ node: Node, didReceiveMessage message: Message) {
// @todo check if the message was just sent to us
if message.proto.count == 0 {
return
}

guard let service = services[message.proto] else { return }
service.handle(message: message)
service.handle(message: message, node: node)
}
}
152 changes: 152 additions & 0 deletions Sources/RelayerFramework/Services/EthereumService.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
import Foundation
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar blocks of code found in 2 locations. Consider refactoring.

import UB

public class EthereumService: Service {
let url: URL

/// Initializes an Ethereum Service with a RPC HTTP url
ec2 marked this conversation as resolved.
Show resolved Hide resolved
public init(url: URL) {
self.url = url
}

// handle gets called when an Ethereum service is called.
ec2 marked this conversation as resolved.
Show resolved Hide resolved
public func handle(message: Message, node: Node) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Function handle has 63 lines of code (exceeds 25 allowed). Consider refactoring.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Function handle has a Cognitive Complexity of 10 (exceeds 5 allowed). Consider refactoring.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Function body should span 40 lines or less excluding comments and whitespace: currently spans 53 lines

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Function handle has 58 lines of code (exceeds 25 allowed). Consider refactoring.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Function body should span 40 lines or less excluding comments and whitespace: currently spans 48 lines

// The first btye of the message is the JSONRPC method
let methodID = message.message[0]

switch methodID {
case 0:
let address = "0x" + message.message[1 ..< message.message.count].hexEncodedString()
getBalance(address: address) { result in
switch result {
case let .success(balance):
let messageResponse = Message(
proto: UBID(repeating: 1, count: 1),
recipient: message.origin,
from: message.recipient,
origin: message.recipient,
message: balance
)
node.send(messageResponse)
case let .failure(error):
print(error)
}
}
case 1:
let address = "0x" + message.message[1 ..< message.message.count].hexEncodedString()
getTransactionCount(address: address) { result in
switch result {
case let .success(nonce):
let messageResponse = Message(
proto: UBID(repeating: 1, count: 1),
recipient: message.origin,
from: message.recipient,
origin: message.recipient,
message: nonce
)
node.send(messageResponse)
case let .failure(error):
print(error)
}
}
case 2:
let signedTransaction = "0x" + message.message[1 ..< message.message.count].hexEncodedString()
sendRawTransaction(signedTransaction: signedTransaction) { result in
switch result {
case let .success(balance):
let messageResponse = Message(
proto: UBID(repeating: 1, count: 1),
recipient: message.origin,
from: message.recipient,
origin: message.recipient,
message: balance
)
node.send(messageResponse)
case let .failure(error):
print(error)
}
}
return
default:
print("default")
return
}
}

internal func httpRequest(request: URLRequest, completion: @escaping (Result<Data, Error>) -> Void) {
_ = URLSession.shared.dataTask(with: request) { data, _, error in
guard let data = data, error == nil else {
print(error?.localizedDescription ?? "No data")
completion(.failure(error!))
return
}
completion(.success(data))
}.resume()
}

internal func getBalance(address: String, completion: @escaping (Result<Data, Error>) -> Void) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar blocks of code found in 2 locations. Consider refactoring.

var request = URLRequest(url: url)
request.httpMethod = "POST"
let payload: [String: Any] = [
ec2 marked this conversation as resolved.
Show resolved Hide resolved
"jsonrpc": "2.0",
"method": "eth_getBalance",
"params": [address, "latest"],
"id": 1
]
let jsonPayload = try! JSONSerialization.data(withJSONObject: payload)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Force tries should be avoided.

ec2 marked this conversation as resolved.
Show resolved Hide resolved
request.httpBody = jsonPayload

httpRequest(request: request) { result in
switch result {
case let .success(balance):
completion(.success(balance))
case let .failure(error):
completion(.failure(error))
}
}
}

internal func getTransactionCount(address: String, completion: @escaping (Result<Data, Error>) -> Void) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar blocks of code found in 2 locations. Consider refactoring.

var request = URLRequest(url: url)
request.httpMethod = "POST"
let payload: [String: Any] = [
"jsonrpc": "2.0",
"method": "eth_getTransactionCount",
"params": [address, "latest"],
"id": 1
]
let jsonPayload = try! JSONSerialization.data(withJSONObject: payload)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Force tries should be avoided.

request.httpBody = jsonPayload

httpRequest(request: request) { result in
switch result {
case let .success(nonce):
completion(.success(nonce))
case let .failure(error):
completion(.failure(error))
}
}
}

internal func sendRawTransaction(signedTransaction: String, completion: @escaping (Result<Data, Error>) -> Void) {
var request = URLRequest(url: url)
request.httpMethod = "POST"
let payload: [String: Any] = [
"jsonrpc": "2.0",
"method": "eth_sendRawTransaction",
"params": [signedTransaction],
"id": 1
]
let jsonPayload = try! JSONSerialization.data(withJSONObject: payload)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Force tries should be avoided.

request.httpBody = jsonPayload

httpRequest(request: request) { result in
switch result {
case let .success(txHash):
completion(.success(txHash))
case let .failure(error):
completion(.failure(error))
}
}
}
}
2 changes: 1 addition & 1 deletion Sources/RelayerFramework/Services/Service.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,5 @@ public protocol Service: AnyObject {
///
/// - Parameters:
/// - message: The received message to handle.
func handle(message: UB.Message)
func handle(message: UB.Message, node: UB.Node)
}
1 change: 0 additions & 1 deletion Tests/LinuxMain.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import XCTest


var tests = [XCTestCaseEntry]()

XCTMain(tests)
27 changes: 27 additions & 0 deletions Tests/RelayerFrameworkTests/RelayerFrameworkTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
@testable import RelayerFramework
import XCTest

final class RelayerFrameworkTests: XCTestCase {
func testGetBalance() {
let didFinish = self.expectation(description: #function)
let url: URL = URL(string: "https://rinkeby.infura.io/f7a08ae0242843f1b1cf480454a6bba5")!
let ethService = EthereumService(url: url)
var bal = ""
ethService.getBalance(address: "0x0F64928EcA02147075c7614A7d67B0C3Cb37D5DA") { result in
switch result {
case let .success(balance):
bal = String(data: balance, encoding: .utf8)!
didFinish.fulfill()
case let .failure(error):
print(error)
}
}

wait(for: [didFinish], timeout: 5)
XCTAssertEqual(bal, "{\"jsonrpc\":\"2.0\",\"id\":1,\"result\":\"0x1a55734bf06dc800\"}")
}

static var allTests = [
("testGetBalance", testGetBalance),
]
}
8 changes: 8 additions & 0 deletions Tests/RelayerFrameworkTests/stubs/Transport.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
//
// Transport.swift
// Relayer
//
// Created by Eric Tu on 9/25/19.
//

import Foundation
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
import class Foundation.Bundle
ec2 marked this conversation as resolved.
Show resolved Hide resolved
import Relayer
import XCTest

final class RelayerTests: XCTestCase {
func testGetBalance() {
let url: URL = URL(string: "https://rinkeby.infura.io/f7a08ae0242843f1b1cf480454a6bba5")!
let ethService = Relayer.EthereumService(url: url)
}

// func testExample() throws {
// // This is an example of a functional test case.
// // Use XCTAssert and related functions to verify your tests produce the correct
Expand Down