You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
280 lines
8.9 KiB
280 lines
8.9 KiB
6 years ago
|
Debugging a Dapp using Remix & Geth
|
||
|
==========================================
|
||
|
|
||
|
The ultimate goal of this tutorial is to debug transactions that have
|
||
|
been created by a dapp front end.
|
||
|
|
||
|
It is easy in Remix to debug a transaction created from its own GUI.
|
||
|
However, setting up an environment that allows you to debug transactions
|
||
|
created outside of Remix, require a bit more of complexity.
|
||
|
|
||
|
We will need four tools for that :
|
||
|
|
||
|
> - Geth - this is the center piece and provides the blockchain
|
||
|
> environment. We will basically run geth in a dev mode.
|
||
|
> - Remix - this is the Ethereum IDE. We will use it to develop our
|
||
|
> Solidity contract.
|
||
|
> - Any code editor you want - in order to write your front end :)
|
||
|
|
||
|
Install the environment
|
||
|
-----------------------
|
||
|
|
||
|
### Install Metamask
|
||
|
|
||
|
Basically we will run our front end in the Metamask Chrome plugin ([Metamask](http://metamask.io)).
|
||
|
|
||
|
### Install Geth
|
||
|
|
||
|
[Geth](http://github.com/ethereum/go-ethereum/releases) is the official
|
||
|
Ethereum client.
|
||
|
|
||
|
Running the environment
|
||
|
-----------------------
|
||
|
|
||
|
### Run Geth
|
||
|
|
||
|
We will run a test node. This node will have a new empty state and will
|
||
|
not be synced to the main or ropsten network.
|
||
|
|
||
|
geth --ipcpath <test-chain-directory>/geth.ipc --datadir <test-chain-directory> --dev console
|
||
|
|
||
|
`<test-chain-directory>` is the folder where keys and chain data will be
|
||
|
stored.
|
||
|
|
||
|
`--ipcpath` defines the end point that other apps (like Metamask) use to
|
||
|
talk to geth.
|
||
|
|
||
|
`--datadir` specifies the data directory.
|
||
|
|
||
|
`--dev` sets the node into private chain mode and adds some debugging
|
||
|
flags.
|
||
|
|
||
|
Then we need to create accounts and mine a bit to generate some Ether:
|
||
|
|
||
|
// from the geth console :
|
||
|
personal.newAccount() // You can execute this command several time if you need more than one account.
|
||
|
miner.start() // generate some Ether.
|
||
|
miner.stop() // stop mining after 30s-60s - we could also keep mining.
|
||
|
|
||
|
Next time we run Geth, we will only need to mine transactions (no need
|
||
|
to recreate account).
|
||
|
|
||
|
|
||
|
### Starting Remix
|
||
|
|
||
|
In Mist click on `Develop` / `Open Remix IDE`
|
||
|
|
||
|
Remix will open in a new window. If this is the first time it is run,
|
||
|
the `Ballot` contract will be loaded.
|
||
|
|
||
|
Now, we need to check if Remix is connected to Mist:
|
||
|
|
||
|
Right panel / third tab from the left, `Injected Provider` should be
|
||
|
checked.
|
||
|
|
||
|
![image](remix4.png)
|
||
|
|
||
|
Right panel / second tab from the left, `Transaction Origin` should
|
||
|
contain accounts we have previously created in Geth.
|
||
|
|
||
|
![image](remix5.png)
|
||
|
|
||
|
Developing contract / front end
|
||
|
-------------------------------
|
||
|
|
||
|
### Donation contract - Dapp Back end
|
||
|
|
||
|
Here is a sample solidity contract.
|
||
|
|
||
|
Copy and paste the following inside remix:
|
||
|
|
||
|
```
|
||
|
{.sourceCode .none}
|
||
|
contract Donation {
|
||
|
address owner;
|
||
|
event fundMoved(address _to, uint _amount);
|
||
|
modifier onlyowner { if (msg.sender == owner) _; }
|
||
|
address[] _giver;
|
||
|
uint[] _values;
|
||
|
|
||
|
function Donation() {
|
||
|
owner = msg.sender;
|
||
|
}
|
||
|
|
||
|
function donate() payable {
|
||
|
addGiver(msg.value);
|
||
|
}
|
||
|
|
||
|
function moveFund(address _to, uint _amount) onlyowner {
|
||
|
uint balance = this.balance;
|
||
|
uint amount = _amount;
|
||
|
if (_amount <= this.balance) {
|
||
|
if (_to.send(this.balance)) {
|
||
|
fundMoved(_to, _amount);
|
||
|
} else {
|
||
|
throw;
|
||
|
}
|
||
|
} else {
|
||
|
throw;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function addGiver(uint _amount) internal {
|
||
|
_giver.push(msg.sender);
|
||
|
_values.push(_amount);
|
||
|
}
|
||
|
}
|
||
|
```
|
||
|
|
||
|
### Dapp Front end
|
||
|
|
||
|
and here is the front end:
|
||
|
|
||
|
```html
|
||
|
<div>
|
||
|
<div>Donation Contract</div>
|
||
|
<br/>
|
||
|
<input id='contractaddress' placeholder='contract address' />
|
||
|
<br/>
|
||
|
<div>
|
||
|
<br/>
|
||
|
<input id='fromGive' placeholder='from' /><input placeholder='amount' id='valueGive' /><button id="fallbackbtn" onclick="donate()">give</button>
|
||
|
<br/>
|
||
|
<br/>
|
||
|
<input id='fromMoveFund' placeholder='from' /><input id='moveFundTo' placeholder='move to' /><input id='amountToMove' placeholder='amount' /><button id="movefundbtn" onclick="movefund()">moveFund</button>
|
||
|
<br/>
|
||
|
<br/>
|
||
|
<div id='wait' ></div>
|
||
|
</div>
|
||
|
<br/>
|
||
|
<br/>
|
||
|
<div id='log'>
|
||
|
</div>
|
||
|
</div>
|
||
|
|
||
|
<script type="text/javascript">
|
||
|
function donate () {
|
||
|
var donation = contractspec.at(document.getElementById('contractaddress').value)
|
||
|
donation.donate({
|
||
|
from: document.getElementById('fromGive').value,
|
||
|
value: document.getElementById('valueGive').value
|
||
|
}, function (error, txHash) {
|
||
|
tryTillResponse(txHash, function (error, receipt) {
|
||
|
alert('done ' + txHash)
|
||
|
})
|
||
|
})
|
||
|
}
|
||
|
|
||
|
function movefund () {
|
||
|
var donation = contractspec.at(document.getElementById('contractaddress').value)
|
||
|
donation.moveFund(
|
||
|
document.getElementById('moveFundTo').value,
|
||
|
document.getElementById('amountToMove').value,
|
||
|
function (error, txHash) {
|
||
|
tryTillResponse(txHash, function (error, receipt) {
|
||
|
alert('done ' + txHash)
|
||
|
})
|
||
|
})
|
||
|
}
|
||
|
|
||
|
var contractspec = web3.eth.contract([{"constant":false,"inputs":[{"name":"_to","type":"address"},{"name":"_amount","type":"uint256"}],"name":"moveFund","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[],"name":"donate","outputs":[],"payable":true,"type":"function"},{"inputs":[],"payable":false,"type":"constructor"},{"anonymous":false,"inputs":[{"indexed":false,"name":"_to","type":"address"},{"indexed":false,"name":"_amount","type":"uint256"}],"name":"fundMoved","type":"event"}]);
|
||
|
|
||
|
function tryTillResponse (txhash, done) {
|
||
|
document.getElementById('wait').innerHTML = 'waiting for the transaction to be mined ...'
|
||
|
web3.eth.getTransactionReceipt(txhash, function (err, result) {
|
||
|
if (!err && !result) {
|
||
|
// Try again with a bit of delay
|
||
|
setTimeout(function () { tryTillResponse(txhash, done) }, 500)
|
||
|
} else {
|
||
|
document.getElementById('wait').innerHTML = ''
|
||
|
var log = document.createElement("div")
|
||
|
log.innerHTML = JSON.stringify(result)
|
||
|
document.getElementById('log').appendChild(log)
|
||
|
done(err,result)
|
||
|
}
|
||
|
})
|
||
|
}
|
||
|
</script>
|
||
|
```
|
||
|
|
||
|
I would suggest serving this file using `http-serve`, but you can use
|
||
|
any web server you like.
|
||
|
|
||
|
Example: Dapp Front End <https://github.com/ltfschoen/dapp_front_end>
|
||
|
|
||
|
### Important notice !
|
||
|
|
||
|
The variable `contractspec` contains the abi of the `donation` contract.
|
||
|
This means that if you change something in the contract interface
|
||
|
(function names, parameters, ...) you need to copy the new abi from
|
||
|
remix to the front end.
|
||
|
|
||
|
Deploying
|
||
|
---------
|
||
|
|
||
|
Right panel / Red button `Create`
|
||
|
|
||
|
![image](remix1.png)
|
||
|
|
||
|
This creates a new transaction that deploys the `Donation` contract
|
||
|
(Mist will ask for the usual passphrase check).
|
||
|
|
||
|
Wait for the transaction to be mined (don't forget to activate mining
|
||
|
`miner.start()`). Once this is done, you can use it by executing the
|
||
|
`moveFund` and `donate` function. But this is not what we want to
|
||
|
achieve. We want to run and debug those functions from the front end.
|
||
|
|
||
|
Remix also display the address of the contract. Save it, we'll need this
|
||
|
address later.
|
||
|
|
||
|
![image](remix2.png)
|
||
|
|
||
|
Debugging
|
||
|
---------
|
||
|
|
||
|
From Mist, browse the above front end. In the first field, paste the
|
||
|
address of the newly created contract. Now, let's call the first
|
||
|
function (label `give`).
|
||
|
|
||
|
You will need an account and a value.
|
||
|
|
||
|
The account could be any account that is declared in the Wallet section
|
||
|
of Mist. This is the sender of the transaction that we are going to
|
||
|
create. The value should be no more than the actual balance of the
|
||
|
account - the unit is in wei, so just put `100` (100 wei), that should
|
||
|
be fine.
|
||
|
|
||
|
Click on `Give` and wait for the transaction to be mined.
|
||
|
|
||
|
The HTML block with id `log` is filled by all the transactions created
|
||
|
from the front end. It was easier for the purpose of this tutorial to
|
||
|
just log transactions in a div but you can have your own logging
|
||
|
mechanism.
|
||
|
|
||
|
There is only one field that we need, this is the `transactionHash`.
|
||
|
|
||
|
Copy it and switch to Remix. On the right side, the fifth panel shows a
|
||
|
small "bug" icon, that is the debugger.
|
||
|
|
||
|
Paste the hash into the transaction field and click on the `play`
|
||
|
button.
|
||
|
|
||
|
![image](remix3.png)
|
||
|
|
||
|
You are now entering a debug session for the call to `donate`.
|
||
|
|
||
|
Debugging in Remix is easier than with common tools like gdb because you
|
||
|
can freely move in time. Use the slider to change the current step and
|
||
|
click on the panels below to expand them and explore the curret state,
|
||
|
local variables, etc. There are also breakpoints to move between
|
||
|
sections of the code quickly, but more on all that later.
|
||
|
|
||
|
At the time of this writing, there is an issue that could break the
|
||
|
contract creation. The a workaround for that at
|
||
|
<https://github.com/ethereum/go-ethereum/issues/3653> . Please follow
|
||
|
the workaround or wait for this issue to be closed.
|
||
|
|
||
|
Also, although retrieving a contract's storage when Remix is using the
|
||
|
JavaScript VM is working well, there is still work to be done when Remix
|
||
|
is using eth or geth as backend.
|