├── .gitignore ├── bin └── ethersim ├── lib ├── utils.js ├── provider.js ├── main.js ├── compiler.js ├── server.js ├── manager.js └── blockchain.js ├── package.json ├── test ├── blockchain.js ├── utils.js └── manager.js ├── LICENSE └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | TODO 3 | *.sw* 4 | -------------------------------------------------------------------------------- /bin/ethersim: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var EtherSim = require('..'); 4 | 5 | EtherSim.startServer(); 6 | 7 | -------------------------------------------------------------------------------- /lib/utils.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | optionalCallback: function (cb) { 3 | if (typeof(cb) == 'undefined') { 4 | return function (err, val) { return err ? err : val; } 5 | } 6 | return cb; 7 | } 8 | }; 9 | -------------------------------------------------------------------------------- /lib/provider.js: -------------------------------------------------------------------------------- 1 | 2 | TestProvider = function(_manager) { 3 | this.manager = _manager; 4 | }; 5 | 6 | TestProvider.prototype.send = function(payload) { 7 | return this.manager.request(payload); 8 | } 9 | 10 | TestProvider.prototype.sendAsync = function(payload, callback) { 11 | return this.manager.request(payload, callback); 12 | } 13 | 14 | TestProvider.prototype.isConnected = function() { 15 | return true; 16 | } 17 | 18 | module.exports = TestProvider; 19 | 20 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ethersim", 3 | "version": "0.5.1", 4 | "bin": { 5 | "ethersim": "./bin/ethersim" 6 | }, 7 | "main": "./lib/main.js", 8 | "directories": { 9 | "lib": "./lib" 10 | }, 11 | "dependencies": { 12 | "async": "^1.5.0", 13 | "bignumber.js": "^2.1.4", 14 | "body-parser": "^1.13.2", 15 | "datejs": "^1.0.0-rc3", 16 | "ethereumjs-account": "^2.0.2", 17 | "ethereumjs-block": "^1.2.2", 18 | "ethereumjs-tx": "^1.1.0", 19 | "ethereumjs-util": "^4.0.1", 20 | "ethereumjs-vm": "^1.2.1", 21 | "http-proxy": "^1.12.0", 22 | "jayson": "^1.2.1", 23 | "merkle-patricia-tree": "2.1.2", 24 | "request": "^2.60.0", 25 | "shelljs": "^0.6.0", 26 | "web3": "^0.15.1", 27 | "yargs": "^3.29.0" 28 | }, 29 | "devDependencies": { 30 | "mocha": "^2.2.5" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /test/blockchain.js: -------------------------------------------------------------------------------- 1 | var Blockchain = require('../lib/blockchain.js'); 2 | var assert = require('assert'); 3 | 4 | describe('fastrpc.blockchain', function() { 5 | 6 | describe("#accountAddresses", function() { 7 | var blockchain = new Blockchain(); 8 | 9 | it("should return list of addresses", function(done) { 10 | blockchain.addAccount({ 11 | secretKey: '3cd7232cd6f3fc66a57a6bedc1a8ed6c228fff0a327e169c2bcc5e869ed49511' 12 | }, function (err, _) { 13 | assert.deepEqual( 14 | blockchain.accountAddresses(), 15 | ["0xca35b7d915458ef540ade6068dfe2f44e8fa733c"]); 16 | done(); 17 | }); 18 | }); 19 | 20 | }); 21 | 22 | describe("#addBlock", function() { 23 | var blockchain = new Blockchain(); 24 | blockchain.addBlock(); 25 | 26 | it("increase block number", function() { 27 | assert.deepEqual(blockchain.blockNumber(), 1); 28 | }); 29 | 30 | it("add block", function() { 31 | assert.deepEqual(blockchain.blocks.length, 2); 32 | }); 33 | 34 | }); 35 | 36 | }); 37 | 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Iuri Matias 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /lib/main.js: -------------------------------------------------------------------------------- 1 | var Server = require('./server.js'); 2 | var Provider = require('./provider.js'); 3 | var Manager = require('./manager.js'); 4 | var argv = require('yargs').argv; 5 | 6 | InitObject = function() { 7 | this.manager = new Manager(); 8 | this.provider = new Provider(this.manager); 9 | 10 | return this; 11 | } 12 | 13 | InitObject.prototype.createAccounts = function(num, cb) { this.manager.createAccounts(num, cb); } 14 | InitObject.prototype.setBalance= function(address, balance, callback) { this.manager.ethersim_setBalance(address, balance, callback); } 15 | InitObject.prototype.mine = function() { this.manager.mine(); } 16 | InitObject.prototype.reset= function() { this.manager.reset(); } 17 | InitObject.prototype.jump = function(seconds) { this.manager.jump(seconds); } 18 | 19 | EtherSim = { 20 | startServer: function() { 21 | Server.startServer(argv.p || argv.port); 22 | }, 23 | 24 | Provider: Provider, 25 | Manager: Manager, 26 | 27 | init: InitObject, 28 | 29 | web3Provider: function() { 30 | var manager = new this.Manager(); 31 | var provider = new this.Provider(manager); 32 | 33 | return provider; 34 | } 35 | } 36 | 37 | module.exports = EtherSim; 38 | -------------------------------------------------------------------------------- /test/utils.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'); 2 | var utils = require('../lib/utils.js'); 3 | 4 | describe('utils.optionalCallback', function() { 5 | var expectedReturn = true; 6 | var expectedError = "Example error message"; 7 | 8 | var exampleFunction = function (cb) { 9 | cb = utils.optionalCallback(cb); 10 | return cb(null, expectedReturn); 11 | }; 12 | 13 | var errorFunction = function (cb) { 14 | cb = utils.optionalCallback(cb); 15 | return cb(expectedError); 16 | }; 17 | 18 | it("should return callback arguments when no callback is supplied", function () { 19 | assert.equal(exampleFunction(), expectedReturn); 20 | }); 21 | 22 | it("should not return callback arguments when a callback is supplied", function () { 23 | var noop = function () {}; 24 | assert.equal(exampleFunction(noop), undefined); 25 | }); 26 | 27 | it("should return callback errors when no callback is supplied", function () { 28 | assert.equal(errorFunction(), expectedError); 29 | }); 30 | 31 | it("should not return callback errors when a callback is supplied", function () { 32 | var noop = function () {}; 33 | assert.equal(errorFunction(noop), undefined); 34 | }); 35 | 36 | it("should pass callback arguments to the given callback", function (done) { 37 | exampleFunction(function (err, res) { 38 | assert.equal(err, null); 39 | assert.equal(res, expectedReturn); 40 | done(); 41 | }); 42 | }); 43 | 44 | it("should pass callback errors to the given callback", function (done) { 45 | errorFunction(function (err, _) { 46 | assert.equal(err, expectedError); 47 | done(); 48 | }); 49 | }); 50 | }); 51 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ---- 2 | ## Warning: moved 3 | 4 | EtherSim has been adopted by [EthereumJS](https://github.com/ethereumjs/) and subsequently refactored to what is now known as [TestRPC](https://github.com/ethereumjs/testrpc) 5 | 6 | There is also a [fork](https://github.com/nexusdev/EtherSim) by the amazing guys at [NexusDev](https://github.com/nexusdev/) who did contribute upstream in a true open source spirit. However TestRPC is far better maintained these days and it's recommended devs use it. 7 | 8 | What is EtherSim 9 | ====== 10 | 11 | [![Join the chat at https://gitter.im/iurimatias/embark-framework](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/iurimatias/embark-framework?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) 12 | 13 | EtherSim is a Limited Ethereum RPC simulator for testing and development purposes. EtherSim is used by the [Embark Framework](https://github.com/iurimatias/embark-framework) 1.x series 14 | 15 | **warning** 16 | 17 | Installation 18 | ====== 19 | 20 | ```Bash 21 | $ npm install -g ethersim 22 | ``` 23 | 24 | Usage - as a RPC Server 25 | ====== 26 | 27 | ```Bash 28 | $ ethersim 29 | ``` 30 | 31 | Usage - as a Lib 32 | ====== 33 | 34 | Setup 35 | 36 | ```Javascript 37 | var EtherSim = require('ethersim'); 38 | var sim = new EtherSim.init(); 39 | 40 | var Web3 = require('web3'); 41 | var web3 = new Web3(); 42 | 43 | web3.setProvider(sim.provider); 44 | ``` 45 | 46 | Adding accounts 47 | 48 | ```Javascript 49 | sim.createAccounts(10, function() {}) 50 | web3.eth.accounts //=> [..10..accounts..] 51 | ``` 52 | 53 | Set Balance 54 | 55 | ```Javascript 56 | sim.setBalance(web3.eth.accounts[0], 123450000, function() {}) 57 | web3.eth.getBalance(web3.eth.accounts[0], function(err, balance) {console.log(balance.toNumber())}) //=> 123450000 58 | 59 | // send ether from one account to another 60 | web3.eth.sendTransaction({value: 1000, from: web3.eth.accounts[0], to: web3.eth.accounts[1], gasLimit: 10000},function() {console.log("transaction sent")}) 61 | 62 | // mine transaction 63 | sim.mine() 64 | ``` 65 | 66 | Time Travel 67 | 68 | ```Javascript 69 | web3.eth.getBlock('latest') // => current time 70 | 71 | sim.jump("5 hours") 72 | sim.mine(); 73 | 74 | web3.eth.getBlock('latest') // => will be 5 hours ahead 75 | ``` 76 | 77 | Start Over 78 | 79 | ```Javascript 80 | sim.reset() 81 | ``` 82 | 83 | -------------------------------------------------------------------------------- /lib/compiler.js: -------------------------------------------------------------------------------- 1 | var shelljs = require('shelljs'); 2 | var shelljs_global = require('shelljs/global'); 3 | 4 | Compiler = function() { 5 | }; 6 | 7 | Compiler.prototype.compile_solidity = function(contractFile) { 8 | var cmd, result, output, version, json, compiled_object; 9 | 10 | cmd = "solc --version"; 11 | 12 | result = exec(cmd, {silent: true}); 13 | output = result.output; 14 | version = output.split('\n')[1].split(' ')[1].slice(0,5); 15 | 16 | if (version == '0.1.1') { 17 | cmd = "solc --input-file " + contractFile + " --combined-json binary,json-abi"; 18 | } 19 | else { 20 | cmd = "solc --input-file " + contractFile + " --combined-json bin,abi"; 21 | } 22 | 23 | result = exec(cmd, {silent: true}); 24 | output = result.output; 25 | 26 | if (result.code === 1) { 27 | throw new Error(result.output); 28 | } 29 | 30 | json = JSON.parse(output).contracts; 31 | compiled_object = {} 32 | 33 | for (var className in json) { 34 | var contract = json[className]; 35 | 36 | compiled_object[className] = {}; 37 | compiled_object[className].code = contract.binary || contract.bin; 38 | compiled_object[className].info = {}; 39 | compiled_object[className].info.abiDefinition = JSON.parse(contract["abi"] || contract["json-abi"]); 40 | } 41 | 42 | return compiled_object; 43 | } 44 | 45 | Compiler.prototype.compile_serpent = function(contractFile) { 46 | var cmd, result, output, json, compiled_object; 47 | 48 | cmd = "serpent compile " + contractFile; 49 | 50 | result = exec(cmd, {silent: true}); 51 | code = result.output; 52 | 53 | if (result.code === 1) { 54 | throw new Error(result.output); 55 | } 56 | 57 | cmd = "serpent mk_full_signature " + contractFile; 58 | result = exec(cmd, {silent: true}); 59 | 60 | if (result.code === 1) { 61 | throw new Error(result.output); 62 | } 63 | 64 | json = JSON.parse(result.output.trim()); 65 | className = contractFile.split('.')[0].split("/").pop(); 66 | 67 | for (var i=0; i < json.length; i++) { 68 | var elem = json[i]; 69 | 70 | if (elem.outputs.length > 0) { 71 | elem.constant = true; 72 | } 73 | } 74 | 75 | compiled_object = {} 76 | compiled_object[className] = {}; 77 | compiled_object[className].code = code.trim(); 78 | compiled_object[className].info = {}; 79 | compiled_object[className].info.abiDefinition = json; 80 | 81 | return compiled_object; 82 | } 83 | 84 | 85 | Compiler.prototype.compile = function(contractFile) { 86 | var extension = contractFile.split('.')[1]; 87 | 88 | if (extension === 'sol') { 89 | return this.compile_solidity(contractFile); 90 | } 91 | else if (extension === 'se') { 92 | return this.compile_serpent(contractFile); 93 | } 94 | else { 95 | throw new Error("extension not known"); 96 | } 97 | }; 98 | 99 | module.exports = Compiler; 100 | -------------------------------------------------------------------------------- /lib/server.js: -------------------------------------------------------------------------------- 1 | var jayson = require("jayson"); 2 | var Manager = require('./manager.js'); 3 | var Provider = require('./provider.js'); 4 | var httpProxy = require('http-proxy'); 5 | var pkg = require("../package.json"); 6 | 7 | var manager = new Manager(); 8 | 9 | //'Access-Control-Allow-Origin': '*' 10 | 11 | // This is a super big hack from the following link to 12 | // dynamically create functions with the right arity. 13 | // http://stackoverflow.com/questions/13271474/override-the-arity-of-a-function 14 | // 15 | // The point is so we can leave all those functions in the manager 16 | // and not have it clog things up here. jayson will error if a function 17 | // doesn't have any arity. 18 | var argNames = 'abcdefghijklmnopqrstuvwxyz'; 19 | var makeArgs = function(n) { return [].slice.call(argNames, 0, n).join(','); }; 20 | 21 | function giveArity(f, n) { 22 | return eval('(function('+makeArgs(n)+') { return f.apply(this, arguments); })') 23 | } 24 | 25 | var createHandler = function(method) { 26 | var fn = manager[method]; 27 | 28 | var wrapped = function() { 29 | console.log(method); 30 | var args = Array.prototype.slice.call(arguments); 31 | var callback = args.pop(); 32 | args.push(function(err, result) { 33 | if (err != null) { 34 | callback({code: -32000, message: err.message || err}); 35 | } else { 36 | callback(null, result); 37 | } 38 | }) 39 | 40 | fn.apply(manager, args); 41 | } 42 | 43 | return giveArity(wrapped, fn.length); 44 | }; 45 | 46 | Server = { 47 | startServer: function(port) { 48 | 49 | if (port == null) { 50 | port = 8101; 51 | } 52 | 53 | var servicePort = port + 1; 54 | 55 | var methods = [ 56 | 'eth_accounts', 57 | 'eth_blockNumber', 58 | 'eth_call', 59 | 'eth_coinbase', 60 | 'eth_compileSolidity', 61 | 'eth_gasPrice', 62 | 'eth_getBalance', 63 | 'eth_getBlockByNumber', 64 | 'eth_getBlockByHash', 65 | 'eth_getCompilers', 66 | 'eth_getCode', 67 | 'eth_getFilterChanges', 68 | 'eth_getTransactionByHash', 69 | 'eth_getTransactionCount', 70 | 'eth_getTransactionReceipt', 71 | 'eth_newBlockFilter', 72 | 'eth_sendTransaction', 73 | 'eth_sendRawTransaction', 74 | 'eth_uninstallFilter', 75 | 'web3_clientVersion', 76 | 'evm_snapshot', 77 | 'evm_revert' 78 | ]; 79 | 80 | var functions = {}; 81 | 82 | methods.forEach(function(method) { 83 | functions[method] = createHandler(method); 84 | }); 85 | 86 | // TODO: the reviver option is a hack to allow batches to work with jayson 87 | // it become unecessary after the fix of this bug https://github.com/ethereum/web3.js/issues/345 88 | var server = jayson.server(functions, { 89 | reviver: function(key, val) { 90 | if (typeof val === 'object' && val.hasOwnProperty('method') && 91 | val.method === 'eth_call' && val.hasOwnProperty('params') && 92 | val.params.constructor === Array && val.params.length === 1) 93 | val.params.push('latest'); 94 | return val; 95 | } 96 | }); 97 | var http = server.http(); 98 | http.listen(servicePort); 99 | 100 | var proxy = httpProxy.createProxyServer({target:'http://localhost:' + servicePort}).listen(port); 101 | 102 | // Proxy the request adding on our headers. 103 | proxy.on('proxyRes', function(proxyRes, req, res) { 104 | 105 | // Make OPTIONS requests okay. 106 | if (req.method == "OPTIONS") { 107 | proxyRes.statusCode = 200; 108 | proxyRes.statusMessage = "OK" 109 | } 110 | 111 | // Add access control headers to all requests. 112 | proxyRes.headers['Access-Control-Allow-Origin'] = '*'; 113 | proxyRes.headers['Access-Control-Allow-Methods'] = '*'; 114 | proxyRes.headers['Access-Control-Allow-Headers'] = 'Origin, X-Requested-With, Content-Type, Accept'; 115 | }); 116 | 117 | proxy.on('error', function(error, req, res) { 118 | console.log(error); 119 | }); 120 | 121 | console.log("EtherSim v" + pkg.version); 122 | 123 | manager.createAccounts(10, function(err, result) { 124 | console.log(""); 125 | console.log("Available Accounts"); 126 | console.log("=================="); 127 | 128 | var accounts = Object.keys(manager.blockchain.accounts); 129 | 130 | for (var i = 0; i < accounts.length; i++) { 131 | console.log(accounts[i]); 132 | } 133 | 134 | manager.ethersim_setBalance(accounts[0], '10000000000000000000', function() {}); 135 | 136 | console.log(""); 137 | console.log("Listening on localhost:" + port); 138 | }); 139 | 140 | } 141 | } 142 | 143 | module.exports = Server; 144 | -------------------------------------------------------------------------------- /lib/manager.js: -------------------------------------------------------------------------------- 1 | var async = require('async'); 2 | var Blockchain = require('./blockchain.js'); 3 | var Compiler = require('./compiler.js'); 4 | var fs = require('fs'); 5 | var utils = require('ethereumjs-util'); 6 | var optionalCallback = require('./utils.js').optionalCallback; 7 | var pkg = require('../package.json'); 8 | require('datejs'); 9 | 10 | Manager = function () { 11 | this.blockchain = new Blockchain(); 12 | } 13 | 14 | Manager.prototype.createAccounts = function(initialAccounts, callback) { 15 | var self = this; 16 | // Add accounts for testing purposes. 17 | if (typeof(initialAccounts) == 'function') { 18 | return initialAccounts(); 19 | } 20 | 21 | async.times(initialAccounts, function(n, next) { 22 | self.blockchain.addAccount(next); 23 | }, callback); 24 | }; 25 | 26 | Manager.prototype.mine = function() { 27 | this.blockchain.mine(); 28 | } 29 | 30 | Manager.prototype.reset = function() { 31 | this.blockchain = new Blockchain(); 32 | } 33 | 34 | Manager.prototype.jump = function(text) { 35 | var seconds = (Date.parse(text) - Date.parse("now")) / 1000 | 0; 36 | this.blockchain.increaseTime(seconds); 37 | } 38 | 39 | Manager.prototype.response = function(params, result) { 40 | return {"id":params.id,"jsonrpc":"2.0","result":result}; 41 | } 42 | 43 | // Handle individual requests. 44 | 45 | Manager.prototype.request = function(params, cb) { 46 | if (Object.prototype.toString.call(params) === '[object Array]') { 47 | var results = []; 48 | for (var i=0; i < params.length; i+=1) { 49 | results.push(this.request(params[i])); 50 | } 51 | return cb(null, results); 52 | } 53 | 54 | var fn = this[params.method]; 55 | var args = params.params || []; 56 | 57 | if (cb) { 58 | var that = this; 59 | args.push(function (err, res) { 60 | try { 61 | return cb(err, that.response(params, res)); 62 | } catch (err) { 63 | if (err == ["TypeError: Cannot read property 'stopWatching' of undefined]"]) { 64 | // TODO: weird edge case when deploying embark tests 65 | return cb(err, null); 66 | } 67 | else { 68 | return cb(err, that.response(params, res)); 69 | } 70 | } 71 | }); 72 | } 73 | 74 | var result = fn.apply(this, args); 75 | 76 | return this.response(params, result); 77 | }; 78 | 79 | Manager.prototype.eth_accounts = function(callback) { 80 | callback = optionalCallback(callback); 81 | return callback(null, this.blockchain.accountAddresses()); 82 | }; 83 | 84 | Manager.prototype.eth_blockNumber = function(callback) { 85 | callback = optionalCallback(callback); 86 | return callback(null, this.blockchain.toHex(this.blockchain.blockNumber())); 87 | }; 88 | 89 | Manager.prototype.eth_coinbase = function(callback) { 90 | callback = optionalCallback(callback); 91 | return callback(null, this.blockchain.coinbase); 92 | }; 93 | 94 | Manager.prototype.eth_mining = function(callback) { 95 | callback = optionalCallback(callback); 96 | return callback(null, false); 97 | }; 98 | 99 | Manager.prototype.eth_hashrate = function(callback) { 100 | callback = optionalCallback(callback); 101 | return callback(null, '0x' + utils.intToHex(0)); 102 | }; 103 | 104 | Manager.prototype.eth_gasPrice = function(callback) { 105 | callback = optionalCallback(callback); 106 | return callback(null, '0x' + this.blockchain.gasPrice()); 107 | }; 108 | 109 | Manager.prototype.eth_getBalance = function(address, block_number, callback) { 110 | if (callback !== undefined) this.blockchain.getBalance(address, callback); 111 | return "please use an async call"; 112 | }; 113 | 114 | Manager.prototype.ethersim_setBalance = function(address, balance, callback) { 115 | this.blockchain.setBalance(address, balance, callback); 116 | }; 117 | 118 | Manager.prototype.eth_getCode = function(address, block_number, callback) { 119 | callback = optionalCallback(callback); 120 | return callback(null, this.blockchain.getCode(address)); 121 | }; 122 | 123 | Manager.prototype.eth_getBlockByNumber = function(block_number, include_transactions, callback) { 124 | callback = optionalCallback(callback); 125 | return callback(null, this.blockchain.getBlockByNumber(block_number)); 126 | }; 127 | 128 | Manager.prototype.eth_getBlockByHash = function(block_hash, include_transactions, callback) { 129 | callback = optionalCallback(callback); 130 | return callback(null, this.blockchain.getBlockByHash(block_hash)); 131 | }; 132 | 133 | Manager.prototype.eth_getTransactionReceipt = function(tx_hash, callback) { 134 | callback = optionalCallback(callback); 135 | return callback(null, this.blockchain.getTransactionReceipt(tx_hash)); 136 | }; 137 | 138 | Manager.prototype.eth_getTransactionByHash = function(tx_hash, callback) { 139 | callback = optionalCallback(callback); 140 | return callback(null, this.blockchain.getTransactionByHash(tx_hash)); 141 | } 142 | 143 | Manager.prototype.eth_getTransactionCount = function(address, block_number, callback) { 144 | callback = optionalCallback(callback); 145 | this.blockchain.getTransactionCount(address, callback); 146 | } 147 | 148 | Manager.prototype.eth_sendTransaction = function(tx_data, callback) { 149 | callback = optionalCallback(callback); 150 | this.blockchain.queueTransaction(tx_data, callback); 151 | }; 152 | 153 | Manager.prototype.eth_sendRawTransaction = function(rawTx, callback) { 154 | callback = optionalCallback(callback); 155 | return callback(new Error("eth_sendRawTransaction not implemented yet, but it will be soon")); 156 | }; 157 | 158 | Manager.prototype.eth_call = function(tx_data, block_number, callback) { 159 | callback = optionalCallback(callback); 160 | this.blockchain.queueCall(tx_data, callback); 161 | }; 162 | 163 | Manager.prototype.eth_newBlockFilter = function(callback) { 164 | var filter_id = utils.addHexPrefix(utils.intToHex(this.blockchain.latest_filter_id)); 165 | this.blockchain.latest_filter_id += 1; 166 | callback(null, filter_id); 167 | }; 168 | 169 | Manager.prototype.eth_getFilterChanges = function(filter_id, callback) { 170 | var blockHash = this.blockchain.latestBlock().hash().toString("hex"); 171 | // Mine a block after each request to getFilterChanges so block filters work. 172 | this.blockchain.mine(); 173 | callback = optionalCallback(callback); 174 | return callback(null, [blockHash]); 175 | }; 176 | 177 | Manager.prototype.eth_uninstallFilter = function(filter_id, callback) { 178 | callback(null, true); 179 | }; 180 | 181 | Manager.prototype.eth_getCompilers = function(callback) { 182 | callback(null, ["solidity"]); 183 | } 184 | 185 | Manager.prototype.eth_compileSolidity = function(code, callback) { 186 | var compiler = new Compiler(); 187 | fs.writeFileSync("/tmp/solCompiler.sol", code); 188 | compiled = compiler.compile_solidity("/tmp/solCompiler.sol"); 189 | callback = optionalCallback(callback); 190 | return callback(null, compiled); 191 | }; 192 | 193 | Manager.prototype.web3_clientVersion = function(callback) { 194 | callback = optionalCallback(callback); 195 | return callback(null, "EtherSim/v" + pkg.version + "/ethereum-js") 196 | }; 197 | 198 | /* Functions for testing purposes only. */ 199 | 200 | Manager.prototype.evm_snapshot = function(callback) { 201 | callback = optionalCallback(callback); 202 | return callback(null, this.blockchain.snapshot()); 203 | }; 204 | 205 | Manager.prototype.evm_revert = function(snapshot_id, callback) { 206 | callback = optionalCallback(callback); 207 | return callback(null, this.blockchain.revert(snapshot_id)); 208 | }; 209 | 210 | module.exports = Manager; 211 | -------------------------------------------------------------------------------- /test/manager.js: -------------------------------------------------------------------------------- 1 | var Blockchain = require('../lib/manager.js'); 2 | var Provider = require('../lib/provider.js'); 3 | var Web3 = require('web3'); 4 | var assert = require('assert'); 5 | 6 | describe('fastrpc.manager', function() { 7 | const TEST_ACCOUNTS = 10; 8 | var block, contractAddress, manager, web3; 9 | 10 | beforeEach(function(done) { 11 | manager = new Manager(); 12 | web3 = new Web3(new Provider(manager)); 13 | manager.createAccounts(TEST_ACCOUNTS, done); 14 | }); 15 | 16 | describe("#request", function() { 17 | 18 | describe("eth_accounts", function() { 19 | it("should return list of addresses", function() { 20 | var accounts = web3.eth.accounts; 21 | assert.deepEqual(accounts.length, TEST_ACCOUNTS); 22 | }); 23 | }); 24 | 25 | describe("eth_blockNumber", function() { 26 | it("should return correct block number", function() { 27 | var number = web3.eth.blockNumber; 28 | assert.deepEqual(number, 0); 29 | 30 | manager.mine(); 31 | 32 | var number = web3.eth.blockNumber; 33 | assert.deepEqual(number, 1); 34 | }); 35 | }); 36 | 37 | describe("eth_coinbase", function() { 38 | it("should return correct address", function() { 39 | var coinbase = web3.eth.coinbase; 40 | 41 | assert.deepEqual(coinbase, manager.blockchain.accountAddresses()[0]); 42 | }); 43 | }); 44 | 45 | describe("eth_mining", function() { 46 | it("should return correct address", function() { 47 | var mining = web3.eth.mining; 48 | 49 | assert.deepEqual(mining, false); 50 | }); 51 | }); 52 | 53 | describe("eth_hashrate", function() { 54 | it("should return hashrate", function() { 55 | var hashrate = web3.eth.hashrate; 56 | 57 | assert.deepEqual(hashrate, 0); 58 | }); 59 | }); 60 | 61 | describe("eth_gasPrice", function() { 62 | it("should return gas price", function() { 63 | var gasPrice = web3.eth.gasPrice; 64 | assert.deepEqual(gasPrice.toNumber(), '1'); 65 | }); 66 | }); 67 | 68 | describe("eth_getBalance", function() { 69 | it("should return balance", function(done) { 70 | var balance = web3.eth.getBalance(web3.eth.accounts[0], function(err, result) { 71 | assert.deepEqual(result.toNumber(), 0); 72 | done(); 73 | }); 74 | }); 75 | }); 76 | 77 | describe("ethersim_setBalance", function() { 78 | it("should set the account balance", function(done) { 79 | var targetBalance = 5; 80 | var account = web3.eth.accounts[0]; 81 | var checkBalance = function () { 82 | web3.eth.getBalance(account, function(err, result) { 83 | assert.deepEqual(result.toNumber(), targetBalance); 84 | done(); 85 | }); 86 | }; 87 | manager.ethersim_setBalance(account, targetBalance, checkBalance); 88 | }); 89 | }); 90 | 91 | describe("eth_getStorageAt", function() { 92 | it("should return storage at a specific position"); //, function() { 93 | }); 94 | 95 | describe("eth_getCode", function() { 96 | var account, transactionHash; 97 | var code = '0x60606040525b60646000600050819055505b60c280601e6000396000f30060606040526000357c0100000000000000000000000000000000000000000000000000000000900480632a1afcd914604b57806360fe47b114606a5780636d4ce63c14607b576049565b005b6054600450609a565b6040518082815260200191505060405180910390f35b607960048035906020015060a3565b005b608460045060b1565b6040518082815260200191505060405180910390f35b60006000505481565b806000600050819055505b50565b6000600060005054905060bf565b9056'; 98 | 99 | beforeEach(function(done) { 100 | account = web3.eth.accounts[0]; 101 | manager.ethersim_setBalance(account, 100000000, function () { 102 | web3.eth.sendTransaction({ 103 | from: account, 104 | data: code 105 | }, function(err, result) { 106 | transactionHash = result; 107 | done(); 108 | }); 109 | }); 110 | }); 111 | 112 | it("should return code at a specific address", function() { 113 | var receipt = web3.eth.getTransactionReceipt(transactionHash); 114 | var result = web3.eth.getCode(receipt.contractAddress); 115 | 116 | assert.deepEqual(result, code); 117 | }); 118 | }); 119 | 120 | describe("eth_getBlockByNumber", function() { 121 | it("should return block given the block number", function() { 122 | block = manager.blockchain.blocks[0]; 123 | var blockHash = web3.eth.getBlock(0); 124 | 125 | resultHash = { 126 | number: 0, 127 | hash: blockHash.hash, 128 | parentHash: '0x0000000000000000000000000000000000000000000000000000000000000000', 129 | nonce: '0x0', 130 | sha3Uncles: '0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347', 131 | logsBloom: '0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', 132 | transactionsRoot: undefined, 133 | stateRoot: '0x0000000000000000000000000000000000000000000000000000000000000000', 134 | receiptsRoot: '0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421', 135 | miner: '0x0000000000000000000000000000000000000000', 136 | difficulty: { s: 1, e: 0, c: [ 0 ] }, 137 | totalDifficulty: { s: 1, e: 0, c: [ 0 ] }, 138 | extraData: '0x0', 139 | size: 1000, 140 | gasLimit: 3141592, 141 | gasUsed: 0, 142 | timestamp: blockHash.timestamp, 143 | transactions: [], 144 | uncles: [] 145 | } 146 | 147 | assert.deepEqual(blockHash, resultHash); 148 | }); 149 | }); 150 | 151 | describe("eth_getBlockByHash", function() { 152 | it("should return block given the block hash", function() { 153 | block = manager.blockchain.blocks[0]; 154 | block = web3.eth.getBlock(0); 155 | var blockHash = web3.eth.getBlock(block.hash); 156 | 157 | resultHash = { 158 | number: 0, 159 | hash: blockHash.hash, 160 | parentHash: '0x0000000000000000000000000000000000000000000000000000000000000000', 161 | nonce: '0x0', 162 | sha3Uncles: '0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347', 163 | logsBloom: '0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', 164 | transactionsRoot: undefined, 165 | stateRoot: '0x0000000000000000000000000000000000000000000000000000000000000000', 166 | receiptsRoot: '0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421', 167 | miner: '0x0000000000000000000000000000000000000000', 168 | difficulty: { s: 1, e: 0, c: [ 0 ] }, 169 | totalDifficulty: { s: 1, e: 0, c: [ 0 ] }, 170 | extraData: '0x0', 171 | size: 1000, 172 | gasLimit: 3141592, 173 | gasUsed: 0, 174 | timestamp: blockHash.timestamp, 175 | transactions: [], 176 | uncles: [] 177 | }; 178 | 179 | assert.deepEqual(blockHash, resultHash); 180 | }); 181 | }); 182 | 183 | describe("eth_getBlockTransactionCountByNumber", function() { 184 | it("should return number of transactions in a given block"); //, function() { 185 | }); 186 | 187 | describe("eth_getBlockTransactionCountByHash", function() { 188 | it("should return number of transactions in a given block"); //, function() { 189 | }); 190 | 191 | describe("eth_getUncleByBlockNumberAndIndex", function() { 192 | it("should return uncles in a given block"); //, function() { 193 | }); 194 | 195 | describe("eth_getUncleByBlockHashAndIndex", function() { 196 | it("should return uncles in a given block"); //, function() { 197 | }); 198 | 199 | describe("eth_getTransactionByHash", function() { 200 | it("should return transaction"); //, function() { 201 | }); 202 | 203 | describe("eth_getTransactionByBlockNumberAndIndex", function() { 204 | it("should return transaction"); //, function() { 205 | }); 206 | 207 | describe("eth_getTransactionByBlockHashAndIndex", function() { 208 | it("should return transaction"); //, function() { 209 | }); 210 | 211 | describe("eth_getTransactionReceipt", function() { 212 | var account, transactionHash; 213 | 214 | beforeEach(function(done) { 215 | account = web3.eth.accounts[0]; 216 | manager.ethersim_setBalance(account, 100000000, done); 217 | }); 218 | 219 | describe("contract creation", function() { 220 | beforeEach(function(done) { 221 | var code = '60606040525b60646000600050819055505b60c280601e6000396000f30060606040526000357c0100000000000000000000000000000000000000000000000000000000900480632a1afcd914604b57806360fe47b114606a5780636d4ce63c14607b576049565b005b6054600450609a565b6040518082815260200191505060405180910390f35b607960048035906020015060a3565b005b608460045060b1565b6040518082815260200191505060405180910390f35b60006000505481565b806000600050819055505b50565b6000600060005054905060bf565b9056'; 222 | 223 | web3.eth.sendTransaction({ 224 | from: account, 225 | data: code 226 | }, function(err, txHash) { 227 | transactionHash = txHash; 228 | done(); 229 | }); 230 | }); 231 | 232 | it("should return receipt", function() { 233 | var receipt = web3.eth.getTransactionReceipt(transactionHash); 234 | assert(/^0x[a-f0-9]+$/.test(receipt.contractAddress)); 235 | }); 236 | 237 | }); 238 | }); 239 | 240 | describe("eth_getTransactionCount", function() { 241 | it("should return number of transactions sent from an address"); //, function() { 242 | }); 243 | 244 | describe("eth_sendTransaction", function() { 245 | 246 | var account; 247 | 248 | beforeEach(function(done) { 249 | account = web3.eth.accounts[0]; 250 | manager.ethersim_setBalance(account, 100000000, done); 251 | }); 252 | 253 | describe("sending funds", function() { 254 | var transactionHash = ""; 255 | 256 | beforeEach(function (done) { 257 | web3.eth.sendTransaction({ 258 | from: account, 259 | to: web3.eth.accounts[1], 260 | value: 12345 261 | }, function(error, result) { 262 | transactionHash = result; 263 | done(); 264 | }); 265 | }); 266 | 267 | it("should transfer funds", function(done) { 268 | web3.eth.getBalance(web3.eth.accounts[1], function(error, results) { 269 | assert.deepEqual(results.toNumber(), 12345); 270 | done(); 271 | }); 272 | }); 273 | 274 | it("should return receipt", function() { 275 | var receipt = web3.eth.getTransactionReceipt(transactionHash); 276 | assert.deepEqual(receipt.transactionHash, transactionHash); 277 | }); 278 | }); 279 | }); 280 | 281 | describe("contract creation", function() { 282 | var tokenSource, tokenCompiled; 283 | 284 | beforeEach(function() { 285 | tokenSource = 'contract token { mapping (address => uint) public coinBalanceOf; event CoinTransfer(address sender, address receiver, uint amount); function token(uint supply) { if (supply == 0) supply = 10000; coinBalanceOf[msg.sender] = supply; } function sendCoin(address receiver, uint amount) returns(bool sufficient) { if (coinBalanceOf[msg.sender] < amount) return false; coinBalanceOf[msg.sender] -= amount; coinBalanceOf[receiver] += amount; CoinTransfer(msg.sender, receiver, amount); return true; } }' 286 | 287 | tokenCompiled = web3.eth.compile.solidity(tokenSource) 288 | }); 289 | 290 | it("should create contract correct", function(done) { 291 | var account = web3.eth.accounts[0]; 292 | 293 | manager.ethersim_setBalance(account, 100000000, function () { 294 | var supply = 10000; 295 | var tokenContract = web3.eth.contract(tokenCompiled.token.info.abiDefinition); 296 | var token = tokenContract.new(supply, { from:account, data:tokenCompiled.token.code, gas: 1000000 }, function(e, contract) { 297 | if(!e) { 298 | if(!contract.address) { 299 | console.log("Contract transaction send: TransactionHash: " + contract.transactionHash + " waiting to be mined..."); 300 | } else { 301 | console.log("Contract mined! Address: " + contract.address); 302 | } 303 | } 304 | else { 305 | throw new Error("ERROR CREATING CONTRACT"); 306 | } 307 | done(); 308 | }); 309 | }); 310 | }); 311 | }); 312 | 313 | describe("eth_call", function() { 314 | var callResult; 315 | 316 | beforeEach(function(done) { 317 | var account = web3.eth.accounts[0]; 318 | var code = '60606040525b60646000600050819055505b60c280601e6000396000f30060606040526000357c0100000000000000000000000000000000000000000000000000000000900480632a1afcd914604b57806360fe47b114606a5780636d4ce63c14607b576049565b005b6054600450609a565b6040518082815260200191505060405180910390f35b607960048035906020015060a3565b005b608460045060b1565b6040518082815260200191505060405180910390f35b60006000505481565b806000600050819055505b50565b6000600060005054905060bf565b9056'; 319 | 320 | block = manager.blockchain.blocks[0]; 321 | 322 | manager.ethersim_setBalance(account, 100000000, function () { 323 | web3.eth.sendTransaction({ 324 | from: account, 325 | data: code 326 | 327 | }, function(error, txHash) { 328 | var receipt = web3.eth.getTransactionReceipt(txHash); 329 | 330 | web3.eth.call({ 331 | from: account, 332 | data: '0x6d4ce63c', 333 | to: receipt.contractAddress 334 | 335 | }, function(error, _callResult) { 336 | callResult = _callResult; 337 | done(); 338 | }); 339 | }); 340 | }); 341 | }); 342 | 343 | it("should return value", function() { 344 | assert.deepEqual(eval(callResult), 100); 345 | }); 346 | }); 347 | }); 348 | }); 349 | 350 | -------------------------------------------------------------------------------- /lib/blockchain.js: -------------------------------------------------------------------------------- 1 | var Account = require('ethereumjs-account'); 2 | var Block = require('ethereumjs-block'); 3 | var crypto = require('crypto'); 4 | var optionalCallback = require('./utils.js').optionalCallback; 5 | var VM = require('ethereumjs-vm'); 6 | var Trie = require('merkle-patricia-tree'); 7 | var Transaction = require('ethereumjs-tx'); 8 | var utils = require('ethereumjs-util'); 9 | 10 | Blockchain = function() { 11 | this.stateTrie = new Trie(); 12 | this.vm = new VM(this.stateTrie); 13 | this.nonces = {}; 14 | this.accounts = {}; 15 | this.blocks = []; 16 | this.gasLimit = '0x2fefd8'; 17 | this.coinbase = null; 18 | this.contracts = {}; 19 | this.blockHashes = {}; 20 | this.transactions = {}; 21 | this.latest_filter_id = 1; 22 | this.transaction_queue = []; 23 | this.transaction_processing == false; 24 | this.pendingBlock = this.createBlock(); 25 | this.lastBlockHash = "0x0000000000000000000000000000000000000000000000000000000000000000"; 26 | this.snapshots = []; 27 | this.logger = console; 28 | this.time_difference = 0; 29 | 30 | this.mine(); 31 | } 32 | 33 | Blockchain.prototype.setLogger = function(logger) { 34 | this.logger = logger; 35 | } 36 | 37 | Blockchain.prototype.getLogger = function() { 38 | return this.logger; 39 | } 40 | 41 | Blockchain.prototype.toHex = function(val) { 42 | if (typeof val == "number") { 43 | val = utils.intToHex(val); 44 | } 45 | 46 | if (val instanceof Buffer) { 47 | val = val.toString("hex"); 48 | 49 | if (val == "") { 50 | val = "0"; 51 | } 52 | } 53 | 54 | return utils.addHexPrefix(val); 55 | } 56 | 57 | Blockchain.prototype.addAccount = function(opts, callback) { 58 | var self = this; 59 | 60 | if (typeof(opts) == "function") { 61 | callback = opts; 62 | opts = {}; 63 | } 64 | callback = optionalCallback(callback); 65 | 66 | if (typeof(opts) == "undefined") { 67 | opts = {}; 68 | } 69 | 70 | var secretKey = opts.secretKey ? Buffer(this.toHex(opts.secretKey).slice(2), 'hex') : crypto.randomBytes(32); 71 | var publicKey = utils.privateToPublic(new Buffer(secretKey)); 72 | var address = utils.pubToAddress(new Buffer(publicKey)); 73 | 74 | account = new Account(); 75 | 76 | this.stateTrie.put(address, account.serialize(), function(err, result) { 77 | if (err != null) { 78 | callback(err); 79 | return; 80 | } 81 | 82 | var data = { 83 | secretKey: secretKey, 84 | publicKey: publicKey, 85 | address: self.toHex(address), 86 | account: account 87 | }; 88 | 89 | if (self.coinbase == null) { 90 | self.coinbase = self.toHex(address); 91 | } 92 | 93 | self.accounts[self.toHex(address)] = data; 94 | 95 | callback(null, data); 96 | }); 97 | } 98 | 99 | Blockchain.prototype.setBalance = function (address, balance, cb) { 100 | var that = this; 101 | address = Buffer(that.toHex(address).slice(2), 'hex'); 102 | balance = Buffer(that.toHex(balance).slice(2), 'hex'); 103 | 104 | if (!cb) { cb = function(){}; } 105 | 106 | that.vm.stateManager.putAccountBalance(address, balance, function () { 107 | that.vm.stateManager.cache.flush(cb); 108 | }); 109 | } 110 | 111 | Blockchain.prototype.accountAddresses = function() { 112 | return Object.keys(this.accounts); 113 | } 114 | 115 | Blockchain.prototype.createBlock = function() { 116 | var block = new Block(); 117 | var parent = this.blocks.length != 0 ? this.blocks[this.blocks.length - 1] : null; 118 | 119 | block.header.gasLimit = this.gasLimit; 120 | 121 | // Ensure we have the right block number for the VM. 122 | block.header.number = this.toHex(this.blocks.length); 123 | 124 | // Set the timestamp before processing txs 125 | block.header.timestamp = this.toHex(this.currentTime()); 126 | 127 | if (parent != null) { 128 | block.header.parentHash = this.toHex(parent.hash()); 129 | } 130 | 131 | return block; 132 | } 133 | 134 | Blockchain.prototype.setGasLimit = function(newLimit) { 135 | if (typeof(newLimit) == "number") { 136 | newLimit = utils.intToHex(newLimit); 137 | } 138 | this.gasLimit = newLimit; 139 | this.pendingBlock.header.gasLimit = newLimit; 140 | }; 141 | 142 | Blockchain.prototype.blockNumber = function() { 143 | return utils.bufferToInt(this.blocks[this.blocks.length - 1].header.number); 144 | }; 145 | 146 | Blockchain.prototype.currentTime = function() { 147 | var real_time = new Date().getTime() / 1000 | 0; 148 | return real_time + (this.time_difference || 0); 149 | }; 150 | 151 | Blockchain.prototype.increaseTime = function(seconds) { 152 | this.time_difference += seconds; 153 | this.mine(); // to ensure change in next mine 154 | }; 155 | 156 | Blockchain.prototype.mine = function() { 157 | var block = this.pendingBlock; 158 | 159 | this.blocks.push(block); 160 | 161 | // Update our caches. 162 | this.blockHashes[this.toHex(block.hash())] = block; 163 | this.lastBlockHash = this.toHex(block.hash()); 164 | 165 | this.pendingBlock = this.createBlock(); 166 | } 167 | 168 | //TODO: add params to this to specify block params 169 | Blockchain.prototype.addBlock = function() { 170 | this.mine() 171 | } 172 | 173 | Blockchain.prototype.latestBlock = function() { 174 | return this.blocks[this.blocks.length - 1]; 175 | } 176 | 177 | Blockchain.prototype.gasPrice = function() { 178 | return '1'; 179 | } 180 | 181 | Blockchain.prototype.getBalance = function(address, callback) { 182 | var self = this; 183 | 184 | address = new Buffer(utils.stripHexPrefix(address), "hex"); 185 | this.vm.stateManager.getAccountBalance(address, function(err, result) { 186 | if (err != null) { 187 | callback(err); 188 | } else { 189 | callback(null, self.toHex(result)); 190 | } 191 | }); 192 | } 193 | 194 | Blockchain.prototype.getTransactionCount = function(address, callback) { 195 | var self = this; 196 | address = new Buffer(utils.stripHexPrefix(address)); 197 | this.vm.stateManager.getAccount(address, function(err, result) { 198 | if (err != null) { 199 | callback(err); 200 | } else { 201 | callback(null, self.toHex(result.nonce)); 202 | } 203 | }); 204 | } 205 | 206 | Blockchain.prototype.getCode = function(address) { 207 | address = this.toHex(address); 208 | return this.contracts[address] || ""; 209 | } 210 | 211 | Blockchain.prototype.getBlockByNumber = function(number) { 212 | 213 | if (number == "latest" || number == "pending") { 214 | block = this.latestBlock(); 215 | number = this.blocks.length - 1; 216 | } else { 217 | block = this.blocks[utils.bufferToInt(number)]; 218 | } 219 | 220 | var self = this; 221 | return { 222 | number: self.toHex(number), 223 | hash: self.toHex(block.hash()), 224 | parentHash: self.toHex(block.header.parentHash), 225 | nonce: self.toHex(block.header.nonce), 226 | sha3Uncles: self.toHex(block.header.uncleHash), 227 | logsBloom: self.toHex(block.header.bloom), 228 | transactionsRoot: self.toHex(block.header.transactionTrie), 229 | stateRoot: self.toHex(block.header.stateRoot), 230 | receiptsRoot: self.toHex(block.header.receiptTrie), 231 | miner: self.toHex(block.header.coinbase), 232 | difficulty: self.toHex(block.header.difficulty), 233 | totalDifficulty: self.toHex(block.header.difficulty), // TODO: Figure out what to do here. 234 | extraData: self.toHex(block.header.extraData), 235 | size: self.toHex(1000), // TODO: Do something better here 236 | gasLimit: self.toHex(block.header.gasLimit), 237 | gasUsed: self.toHex(block.header.gasUsed), 238 | timestamp: self.toHex(block.header.timestamp), 239 | transactions: [], //block.transactions.map(function(tx) {return tx.toJSON(true)}), 240 | uncles: [], // block.uncleHeaders.map(function(uncleHash) {return self.toHex(uncleHash)}) 241 | }; 242 | } 243 | 244 | Blockchain.prototype.getBlockByHash = function(hash) { 245 | var block = this.blockHashes[this.toHex(hash)]; 246 | return this.getBlockByNumber(this.toHex(block.header.number)); 247 | } 248 | 249 | Blockchain.prototype.getTransactionReceipt = function(hash) { 250 | var result = this.transactions[hash]; 251 | 252 | if (result !== undefined) { 253 | return { 254 | transactionHash: hash, 255 | transactionIndex: this.toHex(utils.intToHex(0)), 256 | blockHash: this.toHex(result.block.hash()), 257 | blockNumber: result.block_number, 258 | cumulativeGasUsed: result.gasUsed, // TODO: What should this be? 259 | gasUsed: result.gasUsed, 260 | contractAddress: result.createdAddress, 261 | logs: result.logs 262 | }; 263 | } 264 | else { 265 | return null; 266 | } 267 | } 268 | 269 | Blockchain.prototype.getTransactionByHash = function(hash) { 270 | var result = this.transactions[hash]; 271 | 272 | if (result !== undefined) { 273 | var tx = result.tx; 274 | 275 | return { 276 | hash: hash, 277 | nonce: this.toHex(tx.nonce), 278 | blockHash: this.toHex(result.block.hash()), 279 | blockNumber: result.block_number, 280 | transactionIndex: "0x0", 281 | from: this.toHex(tx.getSenderAddress()), 282 | to: this.toHex(tx.to), 283 | value: this.toHex(tx.value), // 520464 284 | gas: this.toHex(tx.gasLimit), // 520464 285 | gasPrice: this.toHex(tx.gasPrice), 286 | input: this.toHex(tx.data), 287 | }; 288 | } 289 | else { 290 | return null; 291 | } 292 | } 293 | 294 | Blockchain.prototype.queueTransaction = function(tx_params, callback) { 295 | this.queueAction("eth_sendTransaction", tx_params, callback); 296 | }; 297 | 298 | Blockchain.prototype.queueCall = function(tx_params, callback) { 299 | this.queueAction("eth_call", tx_params, callback); 300 | }; 301 | 302 | Blockchain.prototype.queueAction = function(method, tx_params, callback) { 303 | if (tx_params.from == null) { 304 | if (method === 'eth_call') 305 | tx_params.from = this.coinbase; 306 | else { 307 | callback(new Error("from not found; is required")); 308 | return; 309 | } 310 | } 311 | 312 | tx_params.from = this.toHex(tx_params.from); 313 | 314 | var rawTx = { 315 | gasPrice: "0x1", 316 | gasLimit: this.gasLimit, 317 | value: '0x0', 318 | data: '' 319 | }; 320 | 321 | if (tx_params.gasPrice != null) { 322 | rawTx.gasPrice = this.toHex(tx_params.gasPrice); 323 | } 324 | 325 | if (tx_params.gas != null) { 326 | rawTx.gasLimit = this.toHex(tx_params.gas); 327 | } 328 | 329 | if (tx_params.gasPrice != null) { 330 | rawTx.gasPrice = this.toHex(tx_params.gasPrice); 331 | } 332 | 333 | if (tx_params.to != null) { 334 | rawTx.to = this.toHex(tx_params.to); 335 | } 336 | 337 | if (tx_params.value != null) { 338 | rawTx.value = this.toHex(tx_params.value); 339 | } 340 | 341 | if (tx_params.data != null) { 342 | rawTx.data = this.toHex(tx_params.data); 343 | } 344 | 345 | if (tx_params.nonce != null) { 346 | rawTx.nonce = this.toHex(tx_params.nonce); 347 | } 348 | 349 | this.transaction_queue.push({ 350 | method: method, 351 | from: tx_params.from, 352 | rawTx: rawTx, 353 | callback: callback 354 | }); 355 | 356 | // We know there's work, so get started. 357 | this.processNextAction(); 358 | }; 359 | 360 | Blockchain.prototype.processNextAction = function(override) { 361 | var self = this; 362 | 363 | if (override != true) { 364 | if (this.transaction_processing == true || this.transaction_queue.length == 0) { 365 | return; 366 | } 367 | } 368 | 369 | var queued = this.transaction_queue.shift(); 370 | 371 | this.transaction_processing = true; 372 | 373 | var intermediary = function(err, result) { 374 | queued.callback(err, result); 375 | 376 | if (self.transaction_queue.length > 0) { 377 | self.processNextAction(true); 378 | } else { 379 | self.transaction_processing = false; 380 | } 381 | }; 382 | 383 | // Update the latest unmined block's timestamp before calls or txs 384 | this.updateCurrentTime(); 385 | 386 | if (queued.method == "eth_sendTransaction") { 387 | this.processTransaction(queued.from, queued.rawTx, intermediary); 388 | } else { 389 | this.processCall(queued.from, queued.rawTx, intermediary); 390 | } 391 | }; 392 | 393 | Blockchain.prototype.processTransaction = function(from, rawTx, callback) { 394 | var self = this; 395 | 396 | var block = this.pendingBlock; 397 | var address = new Buffer(utils.stripHexPrefix(from), "hex"); 398 | var privateKey = new Buffer(this.accounts[from].secretKey, 'hex'); 399 | 400 | this.stateTrie.get(address, function(err, val) { 401 | var account = new Account(val); 402 | 403 | // If the user specified a nonce, use that instead. 404 | if (rawTx.nonce == null) { 405 | rawTx.nonce = self.toHex(account.nonce); 406 | } 407 | 408 | var tx = new Transaction(rawTx); 409 | 410 | tx.sign(privateKey); 411 | 412 | var tx_hash = self.toHex(tx.hash()); 413 | 414 | // Add the transaction to the block. 415 | block.transactions.push(tx); 416 | 417 | self.vm.runBlock({ 418 | block: block, 419 | generate: true 420 | }, function(err, results) { 421 | if (err) { 422 | callback(err); 423 | return; 424 | } 425 | 426 | if (results.error != null) { 427 | callback(new Error("VM error: " + results.error)); 428 | return; 429 | } 430 | 431 | var receipt = results.receipts[0]; 432 | var result = results.results[0]; 433 | 434 | if (result.vm.exception != 1) { 435 | callback(new Error("VM Exception while executing transaction: " + result.vm.exceptionError)); 436 | return; 437 | } 438 | 439 | var logs = []; 440 | 441 | for (var i = 0; i < receipt.logs.length; i++) { 442 | var log = receipt.logs[i]; 443 | var address = self.toHex(log[0]); 444 | var topics = [] 445 | 446 | for (var j = 0; j < log[1].length; j++) { 447 | topics.push(self.toHex(log[1][j])); 448 | } 449 | 450 | var data = self.toHex(log[2]); 451 | 452 | logs.push({ 453 | logIndex: self.toHex(i), 454 | transactionIndex: "0x0", 455 | transactionHash: tx_hash, 456 | blockHash: self.toHex(block.hash()), 457 | blockNumber: self.toHex(block.header.number), 458 | address: address, 459 | data: data, 460 | topics: topics, 461 | type: "mined" 462 | }); 463 | } 464 | 465 | var tx_result = { 466 | tx: tx, 467 | block_number: self.toHex(block.header.number), 468 | block: block, 469 | stateRoot: self.toHex(receipt.stateRoot), 470 | gasUsed: self.toHex(receipt.gasUsed), 471 | bitvector: self.toHex(receipt.bitvector), 472 | logs: logs, 473 | createdAddress: result.createdAddress != null ? self.toHex(result.createdAddress) : null, 474 | bloom: result.bloom, 475 | amountSpent: result.amountSpent 476 | }; 477 | 478 | self.transactions[tx_hash] = tx_result; 479 | 480 | self.logger.log(""); 481 | 482 | if (tx_result.createdAddress != null) { 483 | self.logger.log(" Contract created: " + tx_result.createdAddress); 484 | self.contracts[tx_result.createdAddress] = rawTx.data; 485 | } 486 | 487 | self.logger.log(" Gas usage: " + utils.bufferToInt(self.toHex(tx_result.gasUsed))); 488 | self.logger.log(""); 489 | 490 | self.mine(); 491 | 492 | callback(null, tx_hash); 493 | }); 494 | }); 495 | }; 496 | 497 | Blockchain.prototype.processCall = function(from, rawTx, callback) { 498 | var self = this; 499 | 500 | var block = this.latestBlock(); 501 | var address = new Buffer(utils.stripHexPrefix(from), "hex"); 502 | var privateKey = new Buffer(this.accounts[from].secretKey, 'hex'); 503 | 504 | this.stateTrie.get(address, function(err, val) { 505 | var account = new Account(val); 506 | 507 | // If the user specified a nonce, use that instead. 508 | if (rawTx.nonce == null) { 509 | rawTx.nonce = self.toHex(account.nonce); 510 | } 511 | 512 | var tx = new Transaction(rawTx); 513 | tx.sign(privateKey); 514 | 515 | var tx_hash = self.toHex(tx.hash()); 516 | 517 | self.stateTrie.checkpoint(); 518 | 519 | self.vm.runTx({ 520 | tx: tx, 521 | block: block 522 | }, function(err, results) { 523 | self.stateTrie.revert(); 524 | 525 | if (err) { 526 | callback(err); 527 | return; 528 | } 529 | 530 | if (results.error != null) { 531 | callback(new Error("VM error: " + results.error)); 532 | return; 533 | } 534 | 535 | if (results.vm.exception != 1) { 536 | callback(new Error("VM Exception while executing transaction: " + results.vm.exceptionError)); 537 | return; 538 | } 539 | 540 | callback(null, self.toHex(results.vm.return || "0x0")); 541 | }); 542 | }); 543 | }; 544 | 545 | // Note: Snapshots have 1-based ids. 546 | Blockchain.prototype.snapshot = function() { 547 | this.snapshots.push(this.stateTrie.root); 548 | 549 | this.logger.log("Saved snapshot #" + this.snapshots.length); 550 | 551 | return this.toHex(this.snapshots.length); 552 | }; 553 | 554 | Blockchain.prototype.revert = function(snapshot_id) { 555 | // Convert from hex. 556 | snapshot_id = utils.bufferToInt(snapshot_id); 557 | 558 | this.logger.log("Reverting to snapshot #" + snapshot_id); 559 | 560 | if (snapshot_id > this.snapshots.length) { 561 | return false; 562 | } 563 | 564 | // Convert to zero based. 565 | snapshot_id = snapshot_id - 1; 566 | 567 | // Revert to previous state. 568 | this.stateTrie.root = this.snapshots[snapshot_id]; 569 | 570 | // Remove all snapshots after and including the one we reverted to. 571 | this.snapshots.splice(snapshot_id); 572 | 573 | return true; 574 | }; 575 | 576 | Blockchain.prototype.updateCurrentTime = function() { 577 | var block = this.pendingBlock; 578 | block.header.timestamp = this.toHex(this.currentTime()); 579 | } 580 | 581 | module.exports = Blockchain; 582 | --------------------------------------------------------------------------------