Use Geth for contract tests

This commit is contained in:
Alex Browne 2018-05-21 13:56:32 -07:00
parent da3f783a9f
commit 577156fe5f
No known key found for this signature in database
GPG Key ID: 7974B08A447ABE31
22 changed files with 337 additions and 11 deletions

View File

@ -200,7 +200,8 @@ export class ZeroEx {
*/
public async awaitTransactionMinedAsync(
txHash: string,
pollingIntervalMs: number = 1000,
// TODO(albrow): Change this back to 1000
pollingIntervalMs: number = 100,
timeoutMs?: number,
): Promise<TransactionReceiptWithDecodedLogs> {
// Hack: Get Web3Wrapper from ContractWrappers

View File

@ -0,0 +1,20 @@
import * as chai from 'chai';
import * as _ from 'lodash';
import { constants } from '../../util/constants';
const expect = chai.expect;
// throws if the given promise does not reject with one of two expected error
// messages.
export const expectRevertOrAlwaysFailingTransaction = <T>(p: Promise<T>) => {
return expect(p)
.to.be.rejected()
.then(e => {
expect(e).to.satisfy(
(err: Error) =>
_.includes(err.message, constants.REVERT) ||
_.includes(err.message, constants.ALWAYS_FAILING_TRANSACTION),
);
});
};

View File

@ -1,6 +1,17 @@
import { Web3Wrapper } from '@0xproject/web3-wrapper';
import * as _ from 'lodash';
import * as Web3 from 'web3';
enum NodeType {
Geth = 'GETH',
Ganache = 'GANACHE',
}
// These are unique identifiers contained in the response of the
// web3_clientVersion call.
const GETH_VERSION_ID = 'Geth';
const GANACHE_VERSION_ID = 'EthereumJS TestRPC';
export class BlockchainLifecycle {
private _web3Wrapper: Web3Wrapper;
private _snapshotIdsStack: number[];
@ -8,17 +19,47 @@ export class BlockchainLifecycle {
this._web3Wrapper = web3Wrapper;
this._snapshotIdsStack = [];
}
// TODO: In order to run these tests on an actual node, we should check if we are running against
// TestRPC, if so, use snapshots, otherwise re-deploy contracts before every test
public async startAsync(): Promise<void> {
const snapshotId = await this._web3Wrapper.takeSnapshotAsync();
this._snapshotIdsStack.push(snapshotId);
const nodeType = await this._getNodeTypeAsync();
switch (nodeType) {
case NodeType.Ganache:
const snapshotId = await this._web3Wrapper.takeSnapshotAsync();
this._snapshotIdsStack.push(snapshotId);
break;
case NodeType.Geth:
const blockNumber = await this._web3Wrapper.getBlockNumberAsync();
this._snapshotIdsStack.push(blockNumber);
break;
default:
throw new Error(`Unknown node type: ${nodeType}`);
}
}
public async revertAsync(): Promise<void> {
const snapshotId = this._snapshotIdsStack.pop() as number;
const didRevert = await this._web3Wrapper.revertSnapshotAsync(snapshotId);
if (!didRevert) {
throw new Error(`Snapshot with id #${snapshotId} failed to revert`);
const nodeType = await this._getNodeTypeAsync();
switch (nodeType) {
case NodeType.Ganache:
const snapshotId = this._snapshotIdsStack.pop() as number;
const didRevert = await this._web3Wrapper.revertSnapshotAsync(snapshotId);
if (!didRevert) {
throw new Error(`Snapshot with id #${snapshotId} failed to revert`);
}
break;
case NodeType.Geth:
const blockNumber = this._snapshotIdsStack.pop() as number;
await this._web3Wrapper.setHeadAsync(blockNumber);
break;
default:
throw new Error(`Unknown node type: ${nodeType}`);
}
}
private async _getNodeTypeAsync(): Promise<NodeType> {
const version = await this._web3Wrapper.getNodeVersionAsync();
if (_.includes(version, GETH_VERSION_ID)) {
return NodeType.Geth;
} else if (_.includes(version, GANACHE_VERSION_ID)) {
return NodeType.Ganache;
} else {
throw new Error(`Unknown client version: ${version}`);
}
}
}

View File

@ -28,7 +28,7 @@ export const web3Factory = {
if (!hasAddresses) {
provider.addProvider(new EmptyWalletSubprovider());
}
provider.addProvider(new FakeGasEstimateSubprovider(constants.GAS_LIMIT));
// provider.addProvider(new FakeGasEstimateSubprovider(constants.GAS_LIMIT));
const logger = {
log: (arg: any) => {
fs.appendFileSync('ganache.log', `${arg}\n`);

View File

@ -0,0 +1,29 @@
FROM alpine:3.7
RUN \
apk add --update go git make gcc musl-dev linux-headers ca-certificates && \
# TODO(albrow): Change the Git URL and branch once we have all relvant PRs
# merged to upstream.
git clone --depth 1 --branch sethead-txpool-fix https://github.com/0xProject/go-ethereum && \
(cd go-ethereum && make geth) && \
cp go-ethereum/build/bin/geth /geth && \
apk del go git make gcc musl-dev linux-headers && \
rm -rf /go-ethereum && rm -rf /var/cache/apk/*
RUN mkdir ~/devnet
WORKDIR ~/devnet
COPY genesis.json .
COPY node0/ ./node0
COPY run.sh .
RUN /geth --datadir node0/ init genesis.json
EXPOSE 8501
EXPOSE 30310
ENTRYPOINT ./run.sh
# TODO(albrow): Send a single transaction to increment the block number from 0
# to 1. This seems to prevent bugs in the tests. (There's probably something
# Geth doesn't like about getting reset back to block 0).

75
packages/devnet/README.md Normal file
View File

@ -0,0 +1,75 @@
## 0x Devnet
A private, single-node PoA Ethereum network for testing purposes only. It uses
Geth and the PoA implementation called "Clique".
## Installation
The devnet requires Docker to run (the latest version is recommended).
In the package root directory, run:
```
docker build -t 0x-devnet .
```
## Usage
To start the network, run:
```
docker run -it --rm -p 8501:8501 0x-devnet
```
Depending on your OS and how you installed docker, you may need to prefix any
docker commands with `sudo`.
The Docker container exposes the JSON RPC API at port 8501, and this is the
primary way you are expected to interact with the devnet. The following
endpoints are supported: `personal,db,eth,net,web3,txpool,miner,debug`.
You can stop the network with `docker stop` and it will automatically clean up
after itself. (`docker stop` typically requires you to use `docker ps` to find
the name of the currently running container).
### Configuration
The devnet network only has a single node and using PoA instead of PoW. That
means that one node, called the "sealer", is the ultimate authority for
validating transactions and adding new blocks to the chain. Since there is no
PoW it also means that mining does not require significant computational
resources. You can learn more about PoA and the Geth-specific implementation
called "Clique" in [EIP-225](https://github.com/ethereum/EIPs/issues/225).
The address of the "sealer" is `0xe8816898d851d5b61b7f950627d04d794c07ca37`. The
password associated with the account is "password" and the (encrypted) private
keys are visible in the **node0/keystore** directory. This account is already
"unlocked" in the Geth node by default, so you can do things like sign and send
transactions from this account using the JSON RPC endpoints directly.
There are also a number of other addresses that have hard-coded starting
balances for testing purposes. You can see the details in the **genesis.json**
file. All of these accounts are also unlocked by default.
### Mining
The node will automatically (nearly instantly) mine a block whenever new
transactions are added to the transaction pool. If there are no transactions in
the pool, it will wait.
To stop mining, use the
[`miner.stop`](https://github.com/ethereum/go-ethereum/wiki/Management-APIs#miner_stop)
method.
To start mining again, you can use the
[`miner.start`](https://github.com/ethereum/go-ethereum/wiki/Management-APIs#miner_start)
JSON RPC method.
## Contributing
We strongly recommend that the community help us make improvements and determine
the future direction of the protocol. To report bugs within this package, please
create an issue in this repository.
Please read our [contribution guidelines](../../CONTRIBUTING.md) before getting
started.

View File

@ -0,0 +1,61 @@
{
"config": {
"chainId": 50,
"homesteadBlock": 1,
"eip150Block": 2,
"eip150Hash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"eip155Block": 3,
"eip158Block": 3,
"byzantiumBlock": 4,
"clique": {
"period": 0,
"epoch": 30000
}
},
"nonce": "0x0",
"timestamp": "0x5af1ffac",
"extraData":
"0x0000000000000000000000000000000000000000000000000000000000000000e8816898d851d5b61b7f950627d04d794c07ca370000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
"gasLimit": "0x47b760",
"difficulty": "0x1",
"mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"coinbase": "0x0000000000000000000000000000000000000000",
"alloc": {
"0xe8816898d851d5b61b7f950627d04d794c07ca37": {
"balance": "0x56BC75E2D63100000"
},
"0x5409ed021d9299bf6814279a6a1411a7e866a631": {
"balance": "0x56BC75E2D63100000"
},
"0x6ecbe1db9ef729cbe972c83fb886247691fb6beb": {
"balance": "0x56BC75E2D63100000"
},
"0xe36ea790bc9d7ab70c55260c66d52b1eca985f84": {
"balance": "0x56BC75E2D63100000"
},
"0xe834ec434daba538cd1b9fe1582052b880bd7e63": {
"balance": "0x56BC75E2D63100000"
},
"0x78dc5d2d739606d31509c31d654056a45185ecb6": {
"balance": "0x56BC75E2D63100000"
},
"0xa8dda8d7f5310e4a9e24f8eba77e091ac264f872": {
"balance": "0x56BC75E2D63100000"
},
"0x06cef8e666768cc40cc78cf93d9611019ddcb628": {
"balance": "0x56BC75E2D63100000"
},
"0x4404ac8bd8f9618d27ad2f1485aa1b2cfd82482d": {
"balance": "0x56BC75E2D63100000"
},
"0x7457d5e02197480db681d3fdf256c7aca21bdc12": {
"balance": "0x56BC75E2D63100000"
},
"0x91c987bf62d25945db517bdaa840a6c661374402": {
"balance": "0x56BC75E2D63100000"
}
},
"number": "0x0",
"gasUsed": "0x0",
"parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000"
}

View File

@ -0,0 +1 @@
{"address":"5409ed021d9299bf6814279a6a1411a7e866a631","crypto":{"cipher":"aes-128-ctr","ciphertext":"7c7bdd62b303eb3a42d5d8e935825ed5a05a47cb2cef71e346c61b1bd582f1aa","cipherparams":{"iv":"7fd6c9d9f9893f2c480735b5386b6d75"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":262144,"p":1,"r":8,"salt":"79cc86edc3a668845a68fabb3913710b7504922e47aac8513ab3d6a28d090218"},"mac":"8a593ae0d0b964e47625bc964b6d389f5687f5bde631b4913136db4ab1b8083e"},"id":"29f637ba-6a65-4401-a0d1-30e1554bd776","version":3}

View File

@ -0,0 +1 @@
{"address":"6ecbe1db9ef729cbe972c83fb886247691fb6beb","crypto":{"cipher":"aes-128-ctr","ciphertext":"ecaf4f2839d74d92e2cb87c2fc7d52862661b46e697d70acfbe43f0893db73ed","cipherparams":{"iv":"7641c3a107228f8a901c07a07ea1f70d"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":262144,"p":1,"r":8,"salt":"c67c9fb30648df6985c0490b6603382147e7dc1ea28ca8c934af4a453ec0555b"},"mac":"985dca9ce65ad400fa4c9009742be2d409f402fe05203fc1278cfd1451729e8d"},"id":"e8634edc-08e6-415e-8d65-7985c4c4a05c","version":3}

View File

@ -0,0 +1 @@
{"address":"e36ea790bc9d7ab70c55260c66d52b1eca985f84","crypto":{"cipher":"aes-128-ctr","ciphertext":"49f89d7d612049f5f3581fc7c97d32ec9c9a2ca3c11165587139f16bfb29de6b","cipherparams":{"iv":"9767e0687a097c5b57e9cb30eec9bc0a"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":262144,"p":1,"r":8,"salt":"3e8f23332df99d519b602a0f6f4724338ba3fd9e7e313c337a92ffd1cafa19f1"},"mac":"4892051a669d45bb7de32a5eab63ee8fe52485a02218ce1806515da2adbd6584"},"id":"3488ad36-4a9d-4282-8651-7939b822429d","version":3}

View File

@ -0,0 +1 @@
{"address":"e834ec434daba538cd1b9fe1582052b880bd7e63","crypto":{"cipher":"aes-128-ctr","ciphertext":"a8ae3896739c63fc3bfe034277f6a1924a1c0ddc3f6747391dada8e61e15a928","cipherparams":{"iv":"f4f4d786cd3650a428a8bac5a6c824b1"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":262144,"p":1,"r":8,"salt":"9acecc321bcab9b69ffdea494b8894ad0221c30f05c17d2302e315db8708ecc6"},"mac":"fc416b8f539fdc1e39e87a3bd2a69b04455875de701ced60cc8948b222171380"},"id":"0d9703e8-14fc-45d0-a425-2c40b8ae846a","version":3}

View File

@ -0,0 +1 @@
{"address":"78dc5d2d739606d31509c31d654056a45185ecb6","crypto":{"cipher":"aes-128-ctr","ciphertext":"25e90e593f08e9e3adc426c8685d90db5d1c04957e9dc8d5fab4ae30c3306b61","cipherparams":{"iv":"72ece22297a27363e795b678bcbd6be5"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":262144,"p":1,"r":8,"salt":"2201502b9d3c4e2076d9d15bfd9da3a6c75d9e2e574aabb29c3bc5a3b5ec55a5"},"mac":"13d709ed4bd2f5bf4973fc1373f8434835f0d12dc99b32c6fc14d9df7f41c62d"},"id":"3902dff4-5681-4646-b825-849f96efeec5","version":3}

View File

@ -0,0 +1 @@
{"address":"a8dda8d7f5310e4a9e24f8eba77e091ac264f872","crypto":{"cipher":"aes-128-ctr","ciphertext":"0d67c13cf0b130e8ffa1aaca5df372f727164e633f8e0e28a3e54d0884ffb568","cipherparams":{"iv":"619cd539cda9f40abb45bba00b5fe53d"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":262144,"p":1,"r":8,"salt":"4effcd9b6fe71ee31cfe9057290154329b9af3acb6dcc46be7f78b5b9dcd3f42"},"mac":"c6eecd25944f4250b7b875d76bfbb60cc4e8db1d081621d1a2ddb72ea4e52a6d"},"id":"556bd3f1-1e5b-47a4-9b6e-448b9989d7d3","version":3}

View File

@ -0,0 +1 @@
{"address":"06cef8e666768cc40cc78cf93d9611019ddcb628","crypto":{"cipher":"aes-128-ctr","ciphertext":"38c9ca150932dc8c5ec5c65796425b2de98295cae64db08b816da2c06fc52c20","cipherparams":{"iv":"512127e8e606c481612473e7bc4d38f1"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":262144,"p":1,"r":8,"salt":"16c4cabfd13cae2df66d8ff9acc7f503c95c808b00d0bb6a12932203889c679b"},"mac":"52297b496e8751627dea1ee17bf5cbea1926f90bcde3ffc8baa089184672f875"},"id":"31102097-86e4-4e19-ad73-03c3de67bf3b","version":3}

View File

@ -0,0 +1 @@
{"address":"4404ac8bd8f9618d27ad2f1485aa1b2cfd82482d","crypto":{"cipher":"aes-128-ctr","ciphertext":"ca7aedbacc960fc0fcb418606d7bdf042c36cc2808a5c94ac222cc0b44a9970d","cipherparams":{"iv":"3b1fe5da1cf5d6cd2ceaaf24c008c897"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":262144,"p":1,"r":8,"salt":"a94e4d41d77ff6dc54beda30c7a46d8f3cc312ebeffa0352d679f7e3fc5301dc"},"mac":"9a82bf60103d05878f8af3c07765c22cba3df9b1c4376eaf859e47b805666e42"},"id":"ab68c67b-e15a-4ade-b3d9-2180a32b28fe","version":3}

View File

@ -0,0 +1 @@
{"address":"7457d5e02197480db681d3fdf256c7aca21bdc12","crypto":{"cipher":"aes-128-ctr","ciphertext":"720dcc2889c7b3636f9f659650181b0d46d82420460e23454277273f528baaee","cipherparams":{"iv":"1510028e2b9988d1a73b71cbb692d085"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":262144,"p":1,"r":8,"salt":"5db2b62f4d1f55a3f24c014c4f23f3ec9a2992dca6c2a89c24a566f99a079396"},"mac":"22c6fb134fd0a748195ea83e9ccb490ab2c9a3e8761f9d74ea6d02abbdeb8a43"},"id":"704c31f8-8ca2-4b49-9fdc-5923f5712dad","version":3}

View File

@ -0,0 +1 @@
{"address":"91c987bf62d25945db517bdaa840a6c661374402","crypto":{"cipher":"aes-128-ctr","ciphertext":"8f461f3c74643f382f7fc1f71719d5a89ed8cf75854d8a1b53e133997b53a386","cipherparams":{"iv":"cf595fb7680d36b4f5a01599ee54d2d1"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":262144,"p":1,"r":8,"salt":"73a9e599369d2bfaedd044559415147240c3517f6cd1dec8f77a98993d1ceaf8"},"mac":"c8be4dc59ad28d40f7b549a6b72834d149c84d67dc35e687676bbee0e07be395"},"id":"21cca6fb-7876-4e39-a986-a0a37f90da6d","version":3}

View File

@ -0,0 +1 @@
{"address":"e8816898d851d5b61b7f950627d04d794c07ca37","crypto":{"cipher":"aes-128-ctr","ciphertext":"1ff4add6955cba7ddaf29f66d7d21c5e1d714ef6191fbc651ae60f2ea3c95e8f","cipherparams":{"iv":"3ff869fbdbe1a523cdb327780365976e"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":262144,"p":1,"r":8,"salt":"7372dbae5fb318f8684902e099c311d4188721d677974d729711762c7ef6030c"},"mac":"485fa5dc701067782baa1589716a53110c7f917eb259e35ebca7265bbb7150b1"},"id":"89edb004-5b00-4607-a3af-a0d9ab9b1c34","version":3}

View File

@ -0,0 +1,11 @@
password
password
password
password
password
password
password
password
password
password
password

23
packages/devnet/run.sh Executable file
View File

@ -0,0 +1,23 @@
set -e
# Create log directory for Geth
mkdir -p /var/log
# Start Geth in background and redirect output to log file
/geth --datadir node0/ --syncmode 'full' --nat none --nodiscover --port 30310 --txpool.journal '' \
--rpc --rpcaddr '0.0.0.0' --rpcport 8501 --rpcapi 'personal,db,eth,net,web3,txpool,miner,debug' \
--networkid 50 --gasprice '2000000000' --targetgaslimit '0x47b760' --mine --etherbase '0xe8816898d851d5b61b7f950627d04d794c07ca37' \
--unlock '0xe8816898d851d5b61b7f950627d04d794c07ca37,0x5409ed021d9299bf6814279a6a1411a7e866a631,0x6ecbe1db9ef729cbe972c83fb886247691fb6beb,0xe36ea790bc9d7ab70c55260c66d52b1eca985f84,0xe834ec434daba538cd1b9fe1582052b880bd7e63,0x78dc5d2d739606d31509c31d654056a45185ecb6,0xa8dda8d7f5310e4a9e24f8eba77e091ac264f872,0x06cef8e666768cc40cc78cf93d9611019ddcb628,0x4404ac8bd8f9618d27ad2f1485aa1b2cfd82482d,0x7457d5e02197480db681d3fdf256c7aca21bdc12,0x91c987bf62d25945db517bdaa840a6c661374402' \
--password=node0/password.txt \
> /var/log/geth &
# Wait for Geth to unlock the first account
sleep 2
# Send a single transaction. We have to do this so that debug.setHead works
# correctly. (Geth does not seem to like debug.setHead(0), so by sending this
# transaction we increase the current block number to 1).
/geth --datadir node0/ attach --exec 'eth.sendTransaction({"from": "0xe8816898d851d5b61b7f950627d04d794c07ca37", "to": "0x84bd1cfa409cb0bb9b23b8b1a33515b4ac00a0af", "value": "0x1"})'
# Use tail to re-attach to the log file and actually see the output.
tail -f /var/log/geth

View File

@ -281,7 +281,6 @@ export class Web3Wrapper {
};
const payload = {
jsonrpc: '2.0',
id: this._jsonRpcRequestId++,
method: 'eth_getLogs',
params: [serializedFilter],
};
@ -403,8 +402,44 @@ export class Web3Wrapper {
}
return receipt;
}
/**
* Start the CPU mining process with the given number of threads and
* generate a new DAG if need be.
* @param threads The number of threads to mine on.
*/
public async minerStartAsync(threads: number = 1): Promise<void> {
await this._sendRawPayloadAsync<boolean>({
method: 'miner_start',
params: [threads],
});
}
/**
* Stop the CPU mining process.
* @param threads The number of threads to mine on.
*/
public async minerStopAsync(): Promise<void> {
await this._sendRawPayloadAsync<boolean>({ method: 'miner_stop', params: [] });
}
/**
* Returns true if client is actively mining new blocks.
* @returns A boolean indicating whether the node is currently mining.
*/
public async isMiningAsync(): Promise<boolean> {
const isMining = await promisify<boolean>(this._web3.eth.getMining)();
return isMining;
}
/**
* Sets the current head of the local chain by block number. Note, this is a
* destructive action and may severely damage your chain. Use with extreme
* caution.
* @param blockNumber The block number to reset to.
*/
public async setHeadAsync(blockNumber: number): Promise<void> {
await this._sendRawPayloadAsync<void>({ method: 'debug_setHead', params: [this._web3.toHex(blockNumber)] });
}
private async _sendRawPayloadAsync<A>(payload: Partial<JSONRPCRequestPayload>): Promise<A> {
const sendAsync = this._web3.currentProvider.sendAsync.bind(this._web3.currentProvider);
payload.id = this._jsonRpcRequestId++;
const response = await promisify<JSONRPCResponsePayload>(sendAsync)(payload);
const result = response.result;
return result;

View File

@ -2,6 +2,7 @@ import * as chai from 'chai';
import * as Ganache from 'ganache-core';
import 'make-promises-safe';
import 'mocha';
import * as Web3 from 'web3';
import { Web3Wrapper } from '../src';
@ -37,4 +38,22 @@ describe('Web3Wrapper tests', () => {
expect(networkId).to.be.equal(NETWORK_ID);
});
});
describe('mining functions', () => {
it('starts and stops the miner', async () => {
// Note: depending on our provider, the miner may or may not already
// be mining. To account for both conditions, we have what might
// look like too many stops and starts here, but it is necessary.
await web3Wrapper.minerStopAsync();
let isMining = await web3Wrapper.isMiningAsync();
expect(isMining).to.be.false();
await web3Wrapper.minerStartAsync(1);
isMining = await web3Wrapper.isMiningAsync();
expect(isMining).to.be.true();
isMining = await web3Wrapper.isMiningAsync();
expect(isMining).to.be.true();
await web3Wrapper.minerStopAsync();
isMining = await web3Wrapper.isMiningAsync();
expect(isMining).to.be.false();
});
});
});