From 60a434b4d48ca4589df2f4bec3b74ac2211f54e7 Mon Sep 17 00:00:00 2001 From: PMunch Date: Thu, 10 Jan 2019 12:50:51 +0100 Subject: [PATCH] Polished event loop system --- src/web3.nim | 101 +++++++++++++++++++++++++++++++++------------------ 1 file changed, 65 insertions(+), 36 deletions(-) diff --git a/src/web3.nim b/src/web3.nim index 66611ff..33e51de 100644 --- a/src/web3.nim +++ b/src/web3.nim @@ -16,8 +16,14 @@ type ip: string port: int - Contract = ref object - address: array[20, byte] + Receiver[T] = ref object + contract: T + ip: string + port: int + + EventListener[T] = ref object + receiver: Receiver[T] + lastBlock: string #proc initWeb3*(address: string, port: int): Web3 = # ## Just creates a simple dummy wrapper object for now. Functionality should @@ -302,13 +308,16 @@ macro contract(cname: untyped, body: untyped): untyped = result = newStmtList() let address = ident "address" + client = ident "client" receipt = genSym(nskForVar) - contract = ident "contract" + receiver = ident "receiver" + eventListener = ident "eventListener" result.add quote do: type #`cname` = distinct Contract `cname` = ref object `address`: array[20, byte] + `client`: RpcHttpClient #callbacks: tuple[ # Transfer: seq[proc (fromAddr: Address, toAddr: Address, value: Uint256)] #] @@ -324,7 +333,6 @@ macro contract(cname: untyped, body: untyped): untyped = signature = getMethodSignature(obj.functionObject) procName = ident obj.functionObject.name senderName = ident "sender" - client = ident "client" output = if obj.functionObject.stateMutability in {payable, nonpayable}: ident "Address" @@ -341,10 +349,9 @@ macro contract(cname: untyped, body: untyped): untyped = nnkCall.newTree(ident "encode", ident input.name) ) var procDef = quote do: - proc `procName`(`senderName`: Sender[`cname`]): `output` = - var `client` = newRpcHttpClient() - `client`.httpMethod(MethodPost) - waitFor `client`.connect(`senderName`.ip, Port(`senderName`.port)) + proc `procName`(`senderName`: Sender[`cname`]): Future[`output`] {.async.} = + `senderName`.contract.`client`.httpMethod(MethodPost) + await `senderName`.contract.`client`.connect(`senderName`.ip, Port(`senderName`.port)) for input in obj.functionObject.inputs: procDef[3].add nnkIdentDefs.newTree( ident input.name, @@ -359,7 +366,7 @@ macro contract(cname: untyped, body: untyped): untyped = #cc.to = `senderName`.contract.Contract.address cc.to = `senderName`.contract.address cc.data = some("0x" & ($keccak_256.digest(`signature`))[0..<8].toLower & `encodedParams`) - let response = waitFor `client`.eth_call(cc, "latest") + let response = await `senderName`.contract.`client`.eth_call(cc, "latest") return response else: procDef[6].add quote do: @@ -368,7 +375,7 @@ macro contract(cname: untyped, body: untyped): untyped = #cc.to = some(`senderName`.contract.Contract.address) cc.to = some(`senderName`.contract.address) cc.data = "0x" & ($keccak_256.digest(`signature`))[0..<8].toLower & `encodedParams` - let response = waitFor `client`.eth_sendTransaction(cc) + let response = await `senderName`.contract.`client`.eth_sendTransaction(cc) return response result.add procDef of event: @@ -418,7 +425,7 @@ macro contract(cname: untyped, body: untyped): untyped = `receipt`.topics[0] == "0x" & ($keccak_256.digest(`signature`)).toLower , quote do: `argParseBody` - for `callback` in `contract`.callbacks.`cbident`: + for `callback` in `eventListener`.receiver.contract.callbacks.`cbident`: `call` ) else: @@ -430,27 +437,23 @@ macro contract(cname: untyped, body: untyped): untyped = newEmptyNode() ) result.add quote do: - proc eventListen(`contract`: `cname`) = - var client = newRpcHttpClient() - client.httpMethod(MethodPost) - waitFor client.connect("127.0.0.1", Port(8545)) - var lastBlock = "0x" & (parseHexInt((waitFor client.eth_blockNumber())[2..^1]) + 1).toHex[^2..^1] - while true: - waitFor client.connect("127.0.0.1", Port(8545)) - let response = waitFor client.eth_getLogs(FilterOptions(fromBlock: some(lastBlock), toBlock: none(string), address: some(`contract`.address.toStr), topics: none(seq[string]))) - if response.len > 0: - lastBlock = "0x" & (parseHexInt(response[^1].blockNumber[2..^1]) + 1).toHex[^2..^1] - for `receipt` in response: - `argParse` - sleep(1000) + proc initEventListener(`receiver`: Receiver[`cname`]): EventListener[`cname`] = + `receiver`.contract.client.httpMethod(MethodPost) + waitFor `receiver`.contract.client.connect(`receiver`.ip, Port(`receiver`.port)) + var lastBlock = "0x" & (parseHexInt((waitFor `receiver`.contract.client.eth_blockNumber())[2..^1]) + 1).toHex[^2..^1] + EventListener[`cname`](receiver: `receiver`, lastBlock: lastBlock) + + proc listen(`eventListener`: EventListener[`cname`]) {.async.} = + await `eventListener`.receiver.contract.client.connect(`eventListener`.receiver.ip, Port(`eventListener`.receiver.port)) + let response = await `eventListener`.receiver.contract.client.eth_getLogs(FilterOptions(fromBlock: some(`eventListener`.lastBlock), toBlock: none(string), address: some(`eventListener`.receiver.contract.address.toStr), topics: none(seq[string]))) + if response.len > 0: + `eventListener`.lastBlock = "0x" & (parseHexInt(response[^1].blockNumber[2..^1]) + 1).toHex[^2..^1] + for `receipt` in response: + `argParse` + echo argParse.repr echo result.repr -contract(TestContract): - proc sendCoin(receiver: Address, amount: Uint): Bool - proc getBalance(address: Address): Uint {.view.} - proc Transfer(fromAddr: indexed[Address], toAddr: indexed[Address], value: Uint256) {.event.} - # This call will generate the `cc.data` part to call that contract method in the code below #sendCoin(fromHex(Stuint[256], "e375b6fb6d0bf0d86707884f3952fee3977251fe"), 600.to(Stuint[256])) @@ -477,9 +480,12 @@ contract(TestContract): #let response = waitFor w3.eth.eth_sendTransaction(cc) #echo response -proc sender[T](contract: T, ip: string, port: int, address: array[20, byte]): Sender[T] = +proc initSender[T](contract: T, ip: string, port: int, address: array[20, byte]): Sender[T] = Sender[T](contract: contract, address: address, ip: ip, port: port) +proc initReceiver[T](contract: T, ip: string, port: int): Receiver[T] = + Receiver[T](contract: contract, ip: ip, port: port) + #proc sendCoin(sender: Sender[MyContract], receiver: Address, amount: Uint): Bool = # echo "Hello world" # return 1.to(Stint[256]).Bool @@ -521,19 +527,42 @@ macro toAddress(input: string): untyped = #)) #) -var x = TestContract(address: "254dffcd3277C0b1660F6d42EFbB754edaBAbC2B".toAddress)#.TestContract +contract(TestContract): + proc sendCoin(receiver: Address, amount: Uint): Bool + proc getBalance(address: Address): Uint {.view.} + proc Transfer(fromAddr: indexed[Address], toAddr: indexed[Address], value: Uint256) {.event.} -echo x.sender("127.0.0.1", 8545, "90f8bf6a479f320ead074411a4b0e7944ea8c9c1".toAddress).getBalance( +var + x = TestContract(address: + "254dffcd3277C0b1660F6d42EFbB754edaBAbC2B".toAddress, + client: newRpcHttpClient() + ) + sender = x.initSender("127.0.0.1", 8545, + "90f8bf6a479f320ead074411a4b0e7944ea8c9c1".toAddress) + receiver = x.initReceiver("127.0.0.1", 8545) + eventListener = receiver.initEventListener() + +x.callbacks.Transfer.add proc (fromAddr, toAddr: Address, value: Uint256) = + echo $value, " coins were transferred from ", fromAddr.toHex, " to ", toAddr.toHex + +echo waitFor sender.getBalance( fromHex(Stuint[256], "ffcf8fdee72ac11b5c542428b35eef5769c409f0") ) -echo toHex(x.sender("127.0.0.1", 8545, "90f8bf6a479f320ead074411a4b0e7944ea8c9c1".toAddress).sendCoin( +echo toHex(waitFor sender.sendCoin( fromHex(Stuint[256], "ffcf8fdee72ac11b5c542428b35eef5769c409f0"), 256.to(Stuint[256]) )) -echo x.sender("127.0.0.1", 8545, "90f8bf6a479f320ead074411a4b0e7944ea8c9c1".toAddress).getBalance( +echo waitFor sender.getBalance( fromHex(Stuint[256], "ffcf8fdee72ac11b5c542428b35eef5769c409f0") ) -#echo "0x" & $keccak_256.digest("Transfer(address,address,uint256)") -#joinThreads([thr]) + +waitFor eventListener.listen() + +echo toHex(waitFor sender.sendCoin( + fromHex(Stuint[256], "ffcf8fdee72ac11b5c542428b35eef5769c409f0"), + 10.to(Stuint[256]) +)) + +waitFor eventListener.listen()