refactor provider view to not depend on libstatus

This commit is contained in:
Iuri Matias 2021-06-09 15:39:56 -04:00
parent 1b7799944e
commit ca990796a0
4 changed files with 251 additions and 221 deletions

View File

@ -96,6 +96,10 @@ Typically this means the method has no return type and so `result =` isn't neces
This usually means a method that has no return type is being discarded
### required type for <variable>: <Type> but expression '<variable>' is of type: <Type>
This tpyically means there is an import missing
## Warnings
### QML anchor warnings

View File

@ -1,19 +1,12 @@
import NimQml
import ../../status/[status, ens, chat/stickers, wallet, settings]
import ../../status/[status, ens, chat/stickers, wallet, settings, provider]
import ../../status/types
import ../../status/libstatus/accounts
import ../../status/libstatus/core
import ../../status/libstatus/settings as status_settings
import json, json_serialization, sets, strutils
import chronicles
import nbaser
import stew/byteutils
from base32 import nil
const AUTH_METHODS = toHashSet(["eth_accounts", "eth_coinbase", "eth_sendTransaction", "eth_sign", "keycard_signTypedData", "eth_signTypedData", "eth_signTypedData_v3", "personal_sign", "personal_ecRecover"])
const SIGN_METHODS = toHashSet(["eth_sign", "personal_sign", "eth_signTypedData", "eth_signTypedData_v3"])
const ACC_METHODS = toHashSet(["eth_accounts", "eth_coinbase"])
const HTTPS_SCHEME = "https"
const IPFS_GATEWAY = ".infura.status.im"
const SWARM_GATEWAY = "swarm-gateways.net"
@ -23,65 +16,6 @@ const base58 = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
logScope:
topics = "provider-view"
type
RequestTypes {.pure.} = enum
Web3SendAsyncReadOnly = "web3-send-async-read-only",
HistoryStateChanged = "history-state-changed",
APIRequest = "api-request"
Unknown = "unknown"
ResponseTypes {.pure.} = enum
Web3SendAsyncCallback = "web3-send-async-callback",
APIResponse = "api-response",
Web3ResponseError = "web3-response-error"
type
Payload = ref object
id: JsonNode
rpcMethod: string
Web3SendAsyncReadOnly = ref object
messageId: JsonNode
payload: Payload
request: string
hostname: string
APIRequest = ref object
isAllowed: bool
messageId: JsonNode
permission: Permission
hostname: string
proc requestType(message: string): RequestTypes =
let data = message.parseJson
result = RequestTypes.Unknown
try:
result = parseEnum[RequestTypes](data["type"].getStr())
except:
warn "Unknown request type received", value=data["permission"].getStr()
proc toWeb3SendAsyncReadOnly(message: string): Web3SendAsyncReadOnly =
let data = message.parseJson
result = Web3SendAsyncReadOnly(
messageId: data["messageId"],
request: $data["payload"],
hostname: data{"hostname"}.getStr(),
payload: Payload(
id: data["payload"]{"id"},
rpcMethod: data["payload"]["method"].getStr()
)
)
proc toAPIRequest(message: string): APIRequest =
let data = message.parseJson
result = APIRequest(
messageId: data["messageId"],
isAllowed: data{"isAllowed"}.getBool(),
permission: data["permission"].getStr().toPermission(),
hostname: data{"hostname"}.getStr()
)
QtObject:
type Web3ProviderView* = ref object of QObject
status*: Status
@ -100,153 +34,6 @@ QtObject:
result.dappsAddress = ""
result.setup
proc process(data: Web3SendAsyncReadOnly, status: Status): string =
if AUTH_METHODS.contains(data.payload.rpcMethod) and not status.permissions.hasPermission(data.hostname, Permission.Web3):
return $ %* {
"type": ResponseTypes.Web3SendAsyncCallback,
"messageId": data.messageId,
"error": {
"code": 4100
}
}
if data.payload.rpcMethod == "eth_sendTransaction":
try:
let request = data.request.parseJson
let fromAddress = request["params"][0]["from"].getStr()
let to = request["params"][0]{"to"}.getStr()
let value = if (request["params"][0]["value"] != nil):
request["params"][0]["value"].getStr()
else:
"0"
let password = request["password"].getStr()
let selectedGasLimit = request["selectedGasLimit"].getStr()
let selectedGasPrice = request["selectedGasPrice"].getStr()
let txData = if (request["params"][0].hasKey("data") and request["params"][0]["data"].kind != JNull):
request["params"][0]["data"].getStr()
else:
""
var success: bool
# TODO make this async
let response = wallet.sendTransaction(fromAddress, to, value, selectedGasLimit, selectedGasPrice, password, success, txData)
let errorMessage = if not success:
if response == "":
"web3-response-error"
else:
response
else:
""
return $ %* {
"type": ResponseTypes.Web3SendAsyncCallback,
"messageId": data.messageId,
"error": errorMessage,
"result": {
"jsonrpc": "2.0",
"id": data.payload.id,
"result": if (success): response else: ""
}
}
except Exception as e:
error "Error sending the transaction", msg = e.msg
return $ %* {
"type": ResponseTypes.Web3SendAsyncCallback,
"messageId": data.messageId,
"error": {
"code": 4100,
"message": e.msg
}
}
if SIGN_METHODS.contains(data.payload.rpcMethod):
try:
let request = data.request.parseJson
var params = request["params"]
let password = hashPassword(request["password"].getStr())
let dappAddress = status_settings.getSetting[string](Setting.DappsAddress)
var rpcResult = "{}"
case data.payload.rpcMethod:
of "eth_signTypedData", "eth_signTypedData_v3":
rpcResult = signTypedData(params[1].getStr(), dappAddress, password)
else:
rpcResult = signMessage($ %* {
"data": params[0].getStr(),
"password": password,
"account": dappAddress
})
let jsonRpcResult = rpcResult.parseJson
let success: bool = not jsonRpcResult.hasKey("error")
let errorMessage = if success: "" else: jsonRpcResult["error"]{"message"}.getStr()
let response = if success: jsonRpcResult["result"].getStr() else: ""
return $ %* {
"type": ResponseTypes.Web3SendAsyncCallback,
"messageId": data.messageId,
"error": errorMessage,
"result": {
"jsonrpc": "2.0",
"id": if data.payload.id == nil: newJNull() else: data.payload.id,
"result": if (success): response else: ""
}
}
except Exception as e:
error "Error signing message", msg = e.msg
return $ %* {
"type": ResponseTypes.Web3SendAsyncCallback,
"messageId": data.messageId,
"error": {
"code": 4100,
"message": e.msg
}
}
if ACC_METHODS.contains(data.payload.rpcMethod):
let dappAddress = status_settings.getSetting[string](Setting.DappsAddress)
return $ %* {
"type": ResponseTypes.Web3SendAsyncCallback,
"messageId": data.messageId,
"result": {
"jsonrpc": "2.0",
"id": data.payload.id,
"result": if data.payload.rpcMethod == "eth_coinbase": newJString(dappAddress) else: %*[dappAddress]
}
}
let rpcResult = callRPC(data.request)
return $ %* {
"type": ResponseTypes.Web3SendAsyncCallback,
"messageId": data.messageId,
"error": (if rpcResult == "": newJString("web3-response-error") else: newJNull()),
"result": rpcResult.parseJson
}
proc process*(data: APIRequest, status: Status): string =
var value:JsonNode = case data.permission
of Permission.Web3: %* [status_settings.getSetting[string](Setting.DappsAddress, "0x0000000000000000000000000000000000000000")]
of Permission.ContactCode: %* status_settings.getSetting[string](Setting.PublicKey, "0x0")
of Permission.Unknown: newJNull()
let isAllowed = data.isAllowed and data.permission != Permission.Unknown
info "API request received", host=data.hostname, value=data.permission, isAllowed
if isAllowed: status.permissions.addPermission(data.hostname, data.permission)
return $ %* {
"type": ResponseTypes.APIResponse,
"isAllowed": isAllowed,
"permission": data.permission,
"messageId": data.messageId,
"data": value
}
proc hasPermission*(self: Web3ProviderView, hostname: string, permission: string): bool {.slot.} =
result = self.status.permissions.hasPermission(hostname, permission.toPermission())
@ -254,13 +41,10 @@ QtObject:
self.status.permissions.revoke("web3".toPermission())
proc postMessage*(self: Web3ProviderView, message: string): string {.slot.} =
case message.requestType():
of RequestTypes.Web3SendAsyncReadOnly: message.toWeb3SendAsyncReadOnly().process(self.status)
of RequestTypes.HistoryStateChanged: """{"type":"TODO-IMPLEMENT-THIS"}""" ############# TODO:
of RequestTypes.APIRequest: message.toAPIRequest().process(self.status)
else: """{"type":"TODO-IMPLEMENT-THIS"}""" ##################### TODO:
result = self.status.provider.postMessage(message)
proc getNetworkId*(self: Web3ProviderView): int {.slot.} = getCurrentNetworkDetails().config.networkId
proc getNetworkId*(self: Web3ProviderView): int {.slot.} =
self.status.settings.getCurrentNetworkDetails().config.networkId
QtProperty[int] networkId:
read = getNetworkId

240
src/status/provider.nim Normal file
View File

@ -0,0 +1,240 @@
import ens, chat/stickers, wallet, settings, permissions
import ../eventemitter
import types
import libstatus/accounts
import libstatus/core
import libstatus/settings as status_settings
import json, json_serialization, sets, strutils
import chronicles
import nbaser
import stew/byteutils
from base32 import nil
logScope:
topics = "provider-model"
type
RequestTypes {.pure.} = enum
Web3SendAsyncReadOnly = "web3-send-async-read-only",
HistoryStateChanged = "history-state-changed",
APIRequest = "api-request"
Unknown = "unknown"
ResponseTypes {.pure.} = enum
Web3SendAsyncCallback = "web3-send-async-callback",
APIResponse = "api-response",
Web3ResponseError = "web3-response-error"
type
Payload = ref object
id: JsonNode
rpcMethod: string
Web3SendAsyncReadOnly = ref object
messageId: JsonNode
payload: Payload
request: string
hostname: string
APIRequest = ref object
isAllowed: bool
messageId: JsonNode
permission: Permission
hostname: string
const AUTH_METHODS = toHashSet(["eth_accounts", "eth_coinbase", "eth_sendTransaction", "eth_sign", "keycard_signTypedData", "eth_signTypedData", "eth_signTypedData_v3", "personal_sign", "personal_ecRecover"])
const SIGN_METHODS = toHashSet(["eth_sign", "personal_sign", "eth_signTypedData", "eth_signTypedData_v3"])
const ACC_METHODS = toHashSet(["eth_accounts", "eth_coinbase"])
type ProviderModel* = ref object
events*: EventEmitter
permissions*: PermissionsModel
proc newProviderModel*(events: EventEmitter, permissions: PermissionsModel): ProviderModel =
result = ProviderModel()
result.events = events
result.permissions = permissions
proc requestType(message: string): RequestTypes =
let data = message.parseJson
result = RequestTypes.Unknown
try:
result = parseEnum[RequestTypes](data["type"].getStr())
except:
warn "Unknown request type received", value=data["permission"].getStr()
proc toWeb3SendAsyncReadOnly(message: string): Web3SendAsyncReadOnly =
let data = message.parseJson
result = Web3SendAsyncReadOnly(
messageId: data["messageId"],
request: $data["payload"],
hostname: data{"hostname"}.getStr(),
payload: Payload(
id: data["payload"]{"id"},
rpcMethod: data["payload"]["method"].getStr()
)
)
proc toAPIRequest(message: string): APIRequest =
let data = message.parseJson
result = APIRequest(
messageId: data["messageId"],
isAllowed: data{"isAllowed"}.getBool(),
permission: data["permission"].getStr().toPermission(),
hostname: data{"hostname"}.getStr()
)
proc process(self: ProviderModel, data: Web3SendAsyncReadOnly): string =
if AUTH_METHODS.contains(data.payload.rpcMethod) and not self.permissions.hasPermission(data.hostname, Permission.Web3):
return $ %* {
"type": ResponseTypes.Web3SendAsyncCallback,
"messageId": data.messageId,
"error": {
"code": 4100
}
}
if data.payload.rpcMethod == "eth_sendTransaction":
try:
let request = data.request.parseJson
let fromAddress = request["params"][0]["from"].getStr()
let to = request["params"][0]{"to"}.getStr()
let value = if (request["params"][0]["value"] != nil):
request["params"][0]["value"].getStr()
else:
"0"
let password = request["password"].getStr()
let selectedGasLimit = request["selectedGasLimit"].getStr()
let selectedGasPrice = request["selectedGasPrice"].getStr()
let txData = if (request["params"][0].hasKey("data") and request["params"][0]["data"].kind != JNull):
request["params"][0]["data"].getStr()
else:
""
var success: bool
# TODO make this async
let response = wallet.sendTransaction(fromAddress, to, value, selectedGasLimit, selectedGasPrice, password, success, txData)
let errorMessage = if not success:
if response == "":
"web3-response-error"
else:
response
else:
""
return $ %* {
"type": ResponseTypes.Web3SendAsyncCallback,
"messageId": data.messageId,
"error": errorMessage,
"result": {
"jsonrpc": "2.0",
"id": data.payload.id,
"result": if (success): response else: ""
}
}
except Exception as e:
error "Error sending the transaction", msg = e.msg
return $ %* {
"type": ResponseTypes.Web3SendAsyncCallback,
"messageId": data.messageId,
"error": {
"code": 4100,
"message": e.msg
}
}
if SIGN_METHODS.contains(data.payload.rpcMethod):
try:
let request = data.request.parseJson
var params = request["params"]
let password = hashPassword(request["password"].getStr())
let dappAddress = status_settings.getSetting[string](Setting.DappsAddress)
var rpcResult = "{}"
case data.payload.rpcMethod:
of "eth_signTypedData", "eth_signTypedData_v3":
rpcResult = signTypedData(params[1].getStr(), dappAddress, password)
else:
rpcResult = signMessage($ %* {
"data": params[0].getStr(),
"password": password,
"account": dappAddress
})
let jsonRpcResult = rpcResult.parseJson
let success: bool = not jsonRpcResult.hasKey("error")
let errorMessage = if success: "" else: jsonRpcResult["error"]{"message"}.getStr()
let response = if success: jsonRpcResult["result"].getStr() else: ""
return $ %* {
"type": ResponseTypes.Web3SendAsyncCallback,
"messageId": data.messageId,
"error": errorMessage,
"result": {
"jsonrpc": "2.0",
"id": if data.payload.id == nil: newJNull() else: data.payload.id,
"result": if (success): response else: ""
}
}
except Exception as e:
error "Error signing message", msg = e.msg
return $ %* {
"type": ResponseTypes.Web3SendAsyncCallback,
"messageId": data.messageId,
"error": {
"code": 4100,
"message": e.msg
}
}
if ACC_METHODS.contains(data.payload.rpcMethod):
let dappAddress = status_settings.getSetting[string](Setting.DappsAddress)
return $ %* {
"type": ResponseTypes.Web3SendAsyncCallback,
"messageId": data.messageId,
"result": {
"jsonrpc": "2.0",
"id": data.payload.id,
"result": if data.payload.rpcMethod == "eth_coinbase": newJString(dappAddress) else: %*[dappAddress]
}
}
let rpcResult = callRPC(data.request)
return $ %* {
"type": ResponseTypes.Web3SendAsyncCallback,
"messageId": data.messageId,
"error": (if rpcResult == "": newJString("web3-response-error") else: newJNull()),
"result": rpcResult.parseJson
}
proc process*(self: ProviderModel, data: APIRequest): string =
var value:JsonNode = case data.permission
of Permission.Web3: %* [status_settings.getSetting[string](Setting.DappsAddress, "0x0000000000000000000000000000000000000000")]
of Permission.ContactCode: %* status_settings.getSetting[string](Setting.PublicKey, "0x0")
of Permission.Unknown: newJNull()
let isAllowed = data.isAllowed and data.permission != Permission.Unknown
info "API request received", host=data.hostname, value=data.permission, isAllowed
if isAllowed: self.permissions.addPermission(data.hostname, data.permission)
return $ %* {
"type": ResponseTypes.APIResponse,
"isAllowed": isAllowed,
"permission": data.permission,
"messageId": data.messageId,
"data": value
}
proc postMessage*(self: ProviderModel, message: string): string =
case message.requestType():
of RequestTypes.Web3SendAsyncReadOnly: self.process(message.toWeb3SendAsyncReadOnly())
of RequestTypes.HistoryStateChanged: """{"type":"TODO-IMPLEMENT-THIS"}""" ############# TODO:
of RequestTypes.APIRequest: self.process(message.toAPIRequest())
else: """{"type":"TODO-IMPLEMENT-THIS"}""" ##################### TODO:

View File

@ -2,7 +2,7 @@ import libstatus/accounts as libstatus_accounts
import libstatus/core as libstatus_core
import libstatus/settings as libstatus_settings
import types as libstatus_types
import chat, accounts, wallet, node, network, messages, contacts, profile, stickers, permissions, fleet, settings, mailservers, browser, tokens
import chat, accounts, wallet, node, network, messages, contacts, profile, stickers, permissions, fleet, settings, mailservers, browser, tokens, provider
import ../eventemitter
import ./tasks/task_runner_impl
@ -26,6 +26,7 @@ type Status* = ref object
mailservers*: MailserversModel
browser*: BrowserModel
tokens*: TokensModel
provider*: ProviderModel
proc newStatusInstance*(fleetConfig: string): Status =
result = Status()
@ -47,6 +48,7 @@ proc newStatusInstance*(fleetConfig: string): Status =
result.mailservers = mailservers.newMailserversModel(result.events)
result.browser = browser.newBrowserModel(result.events)
result.tokens = tokens.newTokensModel(result.events)
result.provider = provider.newProviderModel(result.events, result.permissions)
proc initNode*(self: Status) =
self.tasks.init()