diff --git a/cmd/mist/assets/ext/eth.js/.gitignore b/cmd/mist/assets/ext/eth.js/.gitignore new file mode 100644 index 000000000..de3a847ac --- /dev/null +++ b/cmd/mist/assets/ext/eth.js/.gitignore @@ -0,0 +1,14 @@ +# See http://help.github.com/ignore-files/ for more about ignoring files. +# +# If you find yourself ignoring temporary files generated by your text editor +# or operating system, you probably want to add a global ignore instead: +# git config --global core.excludesfile ~/.gitignore_global + +/tmp +*/**/*un~ +*un~ +.DS_Store +*/**/.DS_Store +ethereum/ethereum +ethereal/ethereal + diff --git a/cmd/mist/assets/ext/eth.js/README.md b/cmd/mist/assets/ext/eth.js/README.md new file mode 100644 index 000000000..86e2969be --- /dev/null +++ b/cmd/mist/assets/ext/eth.js/README.md @@ -0,0 +1,18 @@ +# Ethereum JavaScript API + +This is the Ethereum compatible JavaScript API using `Promise`s +which implements the [Generic JSON RPC](https://github.com/ethereum/wiki/wiki/Generic-JSON-RPC) spec. + +For an example see `index.html`. + +**Please note this repo is in it's early stage.** + +If you'd like to run a WebSocket ethereum node check out +[go-ethereum](https://github.com/ethereum/go-ethereum). + +To install ethereum and spawn a node: + +``` +go get github.com/ethereum/go-ethereum/ethereum +ethereum -ws -loglevel=4 +``` diff --git a/cmd/mist/assets/ext/eth.js/httprpc.js b/cmd/mist/assets/ext/eth.js/httprpc.js new file mode 100644 index 000000000..085b4693d --- /dev/null +++ b/cmd/mist/assets/ext/eth.js/httprpc.js @@ -0,0 +1,70 @@ +(function () { + var HttpRpcProvider = function (host) { + this.handlers = []; + this.host = host; + }; + + function formatJsonRpcObject(object) { + return { + jsonrpc: '2.0', + method: object.call, + params: object.args, + id: object._id + } + }; + + function formatJsonRpcMessage(message) { + var object = JSON.parse(message); + + return { + _id: object.id, + data: object.result + }; + }; + + HttpRpcProvider.prototype.sendRequest = function (payload, cb) { + var data = formatJsonRpcObject(payload); + + var request = new XMLHttpRequest(); + request.open("POST", this.host, true); + request.send(JSON.stringify(data)); + request.onreadystatechange = function () { + if (request.readyState === 4 && cb) { + cb(request); + } + } + }; + + HttpRpcProvider.prototype.send = function (payload) { + var self = this; + this.sendRequest(payload, function (request) { + self.handlers.forEach(function (handler) { + handler.call(self, formatJsonRpcMessage(request.responseText)); + }); + }); + }; + + HttpRpcProvider.prototype.poll = function (payload, id) { + var self = this; + this.sendRequest(payload, function (request) { + var parsed = JSON.parse(request.responseText); + if (parsed.result instanceof Array ? parsed.result.length === 0 : !parsed.result) { + return; + } + self.handlers.forEach(function (handler) { + handler.call(self, {_event: payload.call, _id: id, data: parsed.result}); + }); + }); + }; + + Object.defineProperty(HttpRpcProvider.prototype, "onmessage", { + set: function (handler) { + this.handlers.push(handler); + } + }); + + if (typeof(web3) !== "undefined" && web3.providers !== undefined) { + web3.providers.HttpRpcProvider = HttpRpcProvider; + } +})(); + diff --git a/cmd/mist/assets/ext/eth.js/index.html b/cmd/mist/assets/ext/eth.js/index.html new file mode 100644 index 000000000..2b3f50a14 --- /dev/null +++ b/cmd/mist/assets/ext/eth.js/index.html @@ -0,0 +1,33 @@ + + + + + + + + + + + + + +

std::name_reg

+ + +
+ + + + diff --git a/cmd/mist/assets/ext/eth.js/main.js b/cmd/mist/assets/ext/eth.js/main.js new file mode 100644 index 000000000..5c7ca0603 --- /dev/null +++ b/cmd/mist/assets/ext/eth.js/main.js @@ -0,0 +1,432 @@ +(function(window) { + function isPromise(o) { + return o instanceof Promise + } + + function flattenPromise (obj) { + if (obj instanceof Promise) { + return Promise.resolve(obj); + } + + if (obj instanceof Array) { + return new Promise(function (resolve) { + var promises = obj.map(function (o) { + return flattenPromise(o); + }); + + return Promise.all(promises).then(function (res) { + for (var i = 0; i < obj.length; i++) { + obj[i] = res[i]; + } + resolve(obj); + }); + }); + } + + if (obj instanceof Object) { + return new Promise(function (resolve) { + var keys = Object.keys(obj); + var promises = keys.map(function (key) { + return flattenPromise(obj[key]); + }); + + return Promise.all(promises).then(function (res) { + for (var i = 0; i < keys.length; i++) { + obj[keys[i]] = res[i]; + } + resolve(obj); + }); + }); + } + + return Promise.resolve(obj); + }; + + var ethMethods = function () { + var blockCall = function (args) { + return typeof args[0] === "string" ? "blockByHash" : "blockByNumber"; + }; + + var transactionCall = function (args) { + return typeof args[0] === "string" ? 'transactionByHash' : 'transactionByNumber'; + }; + + var uncleCall = function (args) { + return typeof args[0] === "string" ? 'uncleByHash' : 'uncleByNumber'; + }; + + var methods = [ + { name: 'balanceAt', call: 'balanceAt' }, + { name: 'stateAt', call: 'stateAt' }, + { name: 'countAt', call: 'countAt'}, + { name: 'codeAt', call: 'codeAt' }, + { name: 'transact', call: 'transact' }, + { name: 'call', call: 'call' }, + { name: 'block', call: blockCall }, + { name: 'transaction', call: transactionCall }, + { name: 'uncle', call: uncleCall }, + { name: 'compile', call: 'compile' } + ]; + return methods; + }; + + var ethProperties = function () { + return [ + { name: 'coinbase', getter: 'coinbase', setter: 'setCoinbase' }, + { name: 'listening', getter: 'listening', setter: 'setListening' }, + { name: 'mining', getter: 'mining', setter: 'setMining' }, + { name: 'gasPrice', getter: 'gasPrice' }, + { name: 'account', getter: 'account' }, + { name: 'accounts', getter: 'accounts' }, + { name: 'peerCount', getter: 'peerCount' }, + { name: 'defaultBlock', getter: 'defaultBlock', setter: 'setDefaultBlock' }, + { name: 'number', getter: 'number'} + ]; + }; + + var dbMethods = function () { + return [ + { name: 'put', call: 'put' }, + { name: 'get', call: 'get' }, + { name: 'putString', call: 'putString' }, + { name: 'getString', call: 'getString' } + ]; + }; + + var shhMethods = function () { + return [ + { name: 'post', call: 'post' }, + { name: 'newIdentity', call: 'newIdentity' }, + { name: 'haveIdentity', call: 'haveIdentity' }, + { name: 'newGroup', call: 'newGroup' }, + { name: 'addToGroup', call: 'addToGroup' } + ]; + }; + + var ethWatchMethods = function () { + var newFilter = function (args) { + return typeof args[0] === 'string' ? 'newFilterString' : 'newFilter'; + }; + + return [ + { name: 'newFilter', call: newFilter }, + { name: 'uninstallFilter', call: 'uninstallFilter' }, + { name: 'getMessages', call: 'getMessages' } + ]; + }; + + var shhWatchMethods = function () { + return [ + { name: 'newFilter', call: 'shhNewFilter' }, + { name: 'uninstallFilter', call: 'shhUninstallFilter' }, + { name: 'getMessage', call: 'shhGetMessages' } + ]; + }; + + var setupMethods = function (obj, methods) { + methods.forEach(function (method) { + obj[method.name] = function () { + return flattenPromise(Array.prototype.slice.call(arguments)).then(function (args) { + var call = typeof method.call === "function" ? method.call(args) : method.call; + return {call: call, args: args}; + }).then(function (request) { + return new Promise(function (resolve, reject) { + web3.provider.send(request, function (result) { + //if (result || typeof result === "boolean") { + resolve(result); + return; + //} + //reject(result); + }); + }); + }).catch(function( err) { + console.error(err); + }); + }; + }); + }; + + var setupProperties = function (obj, properties) { + properties.forEach(function (property) { + var proto = {}; + proto.get = function () { + return new Promise(function(resolve, reject) { + web3.provider.send({call: property.getter}, function(result) { + resolve(result); + }); + }); + }; + if (property.setter) { + proto.set = function (val) { + return flattenPromise([val]).then(function (args) { + return new Promise(function (resolve) { + web3.provider.send({call: property.setter, args: args}, function (result) { + resolve(result); + }); + }); + }).catch(function (err) { + console.error(err); + }); + } + } + Object.defineProperty(obj, property.name, proto); + }); + }; + + var web3 = { + _callbacks: {}, + _events: {}, + providers: {}, + toHex: function(str) { + var hex = ""; + for(var i = 0; i < str.length; i++) { + var n = str.charCodeAt(i).toString(16); + hex += n.length < 2 ? '0' + n : n; + } + + return hex; + }, + + toAscii: function(hex) { + // Find termination + var str = ""; + var i = 0, l = hex.length; + for(; i < l; i+=2) { + var code = hex.charCodeAt(i) + if(code == 0) { + break; + } + + str += String.fromCharCode(parseInt(hex.substr(i, 2), 16)); + } + + return str; + }, + + toDecimal: function (val) { + return parseInt(val, 16); + }, + + fromAscii: function(str, pad) { + pad = pad === undefined ? 32 : pad; + var hex = this.toHex(str); + while(hex.length < pad*2) + hex += "00"; + return hex + }, + + eth: { + prototype: Object(), + watch: function (params) { + return new Filter(params, ethWatch); + }, + }, + + db: { + prototype: Object() + }, + + shh: { + prototype: Object(), + watch: function (params) { + return new Filter(params, shhWatch); + } + }, + + on: function(event, id, cb) { + if(web3._events[event] === undefined) { + web3._events[event] = {}; + } + + web3._events[event][id] = cb; + return this + }, + + off: function(event, id) { + if(web3._events[event] !== undefined) { + delete web3._events[event][id]; + } + + return this + }, + + trigger: function(event, id, data) { + var callbacks = web3._events[event]; + if (!callbacks || !callbacks[id]) { + return; + } + var cb = callbacks[id]; + cb(data); + }, + }; + + var eth = web3.eth; + setupMethods(eth, ethMethods()); + setupProperties(eth, ethProperties()); + setupMethods(web3.db, dbMethods()); + setupMethods(web3.shh, shhMethods()); + + var ethWatch = { + changed: 'changed' + }; + setupMethods(ethWatch, ethWatchMethods()); + var shhWatch = { + changed: 'shhChanged' + }; + setupMethods(shhWatch, shhWatchMethods()); + + var ProviderManager = function() { + this.queued = []; + this.polls = []; + this.ready = false; + this.provider = undefined; + this.id = 1; + + var self = this; + var poll = function () { + if (self.provider && self.provider.poll) { + self.polls.forEach(function (data) { + data.data._id = self.id; + self.id++; + self.provider.poll(data.data, data.id); + }); + } + setTimeout(poll, 12000); + }; + poll(); + }; + + ProviderManager.prototype.send = function(data, cb) { + data._id = this.id; + if (cb) { + web3._callbacks[data._id] = cb; + } + + data.args = data.args || []; + this.id++; + + if(this.provider !== undefined) { + this.provider.send(data); + } else { + console.warn("provider is not set"); + this.queued.push(data); + } + }; + + ProviderManager.prototype.set = function(provider) { + if(this.provider !== undefined && this.provider.unload !== undefined) { + this.provider.unload(); + } + + this.provider = provider; + this.ready = true; + }; + + ProviderManager.prototype.sendQueued = function() { + for(var i = 0; this.queued.length; i++) { + // Resend + this.send(this.queued[i]); + } + }; + + ProviderManager.prototype.installed = function() { + return this.provider !== undefined; + }; + + ProviderManager.prototype.startPolling = function (data, pollId) { + if (!this.provider || !this.provider.poll) { + return; + } + this.polls.push({data: data, id: pollId}); + }; + + ProviderManager.prototype.stopPolling = function (pollId) { + for (var i = this.polls.length; i--;) { + var poll = this.polls[i]; + if (poll.id === pollId) { + this.polls.splice(i, 1); + } + } + }; + + web3.provider = new ProviderManager(); + + web3.setProvider = function(provider) { + provider.onmessage = messageHandler; + web3.provider.set(provider); + web3.provider.sendQueued(); + }; + + var Filter = function(options, impl) { + this.impl = impl; + this.callbacks = []; + + var self = this; + this.promise = impl.newFilter(options); + this.promise.then(function (id) { + self.id = id; + web3.on(impl.changed, id, self.trigger.bind(self)); + web3.provider.startPolling({call: impl.changed, args: [id]}, id); + }); + }; + + Filter.prototype.arrived = function(callback) { + this.changed(callback); + } + + Filter.prototype.changed = function(callback) { + var self = this; + this.promise.then(function(id) { + self.callbacks.push(callback); + }); + }; + + Filter.prototype.trigger = function(messages) { + for(var i = 0; i < this.callbacks.length; i++) { + this.callbacks[i].call(this, messages); + } + }; + + Filter.prototype.uninstall = function() { + var self = this; + this.promise.then(function (id) { + self.impl.uninstallFilter(id); + web3.provider.stopPolling(id); + web3.off(impl.changed, id); + }); + }; + + Filter.prototype.messages = function() { + var self = this; + return this.promise.then(function (id) { + return self.impl.getMessages(id); + }); + }; + + function messageHandler(data) { + if(data._event !== undefined) { + web3.trigger(data._event, data._id, data.data); + return; + } + + if(data._id) { + var cb = web3._callbacks[data._id]; + if (cb) { + cb.call(this, data.data) + delete web3._callbacks[data._id]; + } + } + } + + /* + // Install default provider + if(!web3.provider.installed()) { + var sock = new web3.WebSocket("ws://localhost:40404/eth"); + + web3.setProvider(sock); + } + */ + + window.web3 = web3; + +})(this); diff --git a/cmd/mist/assets/ext/eth.js/qt.js b/cmd/mist/assets/ext/eth.js/qt.js new file mode 100644 index 000000000..644c37737 --- /dev/null +++ b/cmd/mist/assets/ext/eth.js/qt.js @@ -0,0 +1,27 @@ +(function() { + var QtProvider = function() { + this.handlers = []; + + var self = this; + navigator.qt.onmessage = function (message) { + self.handlers.forEach(function (handler) { + handler.call(self, JSON.parse(message.data)); + }); + } + }; + + QtProvider.prototype.send = function(payload) { + navigator.qt.postMessage(JSON.stringify(payload)); + }; + + Object.defineProperty(QtProvider.prototype, "onmessage", { + set: function(handler) { + this.handlers.push(handler); + }, + }); + + if(typeof(web3) !== "undefined" && web3.providers !== undefined) { + web3.providers.QtProvider = QtProvider; + } +})(); + diff --git a/cmd/mist/assets/ext/eth.js/websocket.js b/cmd/mist/assets/ext/eth.js/websocket.js new file mode 100644 index 000000000..732a086f2 --- /dev/null +++ b/cmd/mist/assets/ext/eth.js/websocket.js @@ -0,0 +1,51 @@ +(function() { + var WebSocketProvider = function(host) { + // onmessage handlers + this.handlers = []; + // queue will be filled with messages if send is invoked before the ws is ready + this.queued = []; + this.ready = false; + + this.ws = new WebSocket(host); + + var self = this; + this.ws.onmessage = function(event) { + for(var i = 0; i < self.handlers.length; i++) { + self.handlers[i].call(self, JSON.parse(event.data), event) + } + }; + + this.ws.onopen = function() { + self.ready = true; + + for(var i = 0; i < self.queued.length; i++) { + // Resend + self.send(self.queued[i]); + } + }; + }; + WebSocketProvider.prototype.send = function(payload) { + if(this.ready) { + var data = JSON.stringify(payload); + + this.ws.send(data); + } else { + this.queued.push(payload); + } + }; + + WebSocketProvider.prototype.onMessage = function(handler) { + this.handlers.push(handler); + }; + + WebSocketProvider.prototype.unload = function() { + this.ws.close(); + }; + Object.defineProperty(WebSocketProvider.prototype, "onmessage", { + set: function(provider) { this.onMessage(provider); } + }); + + if(typeof(web3) !== "undefined" && web3.providers !== undefined) { + web3.providers.WebSocketProvider = WebSocketProvider; + } +})(); diff --git a/cmd/mist/assets/ext/setup.js b/cmd/mist/assets/ext/setup.js new file mode 100644 index 000000000..8317937b3 --- /dev/null +++ b/cmd/mist/assets/ext/setup.js @@ -0,0 +1,8 @@ +(function() { + if (typeof(Promise) === "undefined") + window.Promise = Q.Promise; + + var eth = web3.eth; + + web3.setProvider(new web3.providers.QtProvider()); +})() diff --git a/cmd/mist/assets/qml/webapp.qml b/cmd/mist/assets/qml/webapp.qml index c35f325d5..bd7399dc9 100644 --- a/cmd/mist/assets/qml/webapp.qml +++ b/cmd/mist/assets/qml/webapp.qml @@ -155,7 +155,8 @@ Rectangle { experimental.preferences.javascriptEnabled: true experimental.preferences.navigatorQtObjectEnabled: true experimental.preferences.developerExtrasEnabled: true - experimental.userScripts: ["../ext/qt_messaging_adapter.js", "../ext/q.js", "../ext/big.js", "../ext/string.js", "../ext/html_messaging.js"] + //experimental.userScripts: ["../ext/qt_messaging_adapter.js", "../ext/q.js", "../ext/big.js", "../ext/string.js", "../ext/html_messaging.js"] + experimental.userScripts: ["../ext/q.js", "../ext/eth.js/main.js", "../ext/eth.js/qt.js", "../ext/setup.js"] experimental.onMessageReceived: { console.log("[onMessageReceived]: ", message.data) // TODO move to messaging.js @@ -164,51 +165,88 @@ Rectangle { try { switch(data.call) { case "compile": - postData(data._seed, eth.compile(data.args[0])) + postData(data._id, eth.compile(data.args[0])) break - case "getCoinBase": - postData(data._seed, eth.coinBase()) + case "coinbase": + postData(data._id, eth.coinBase()) + + case "account": + postData(data._id, eth.key().address); + + case "isListening": + postData(data._id, eth.isListening()) break - case "getIsListening": - postData(data._seed, eth.isListening()) + case "isMining": + postData(data._id, eth.isMining()) break - case "getIsMining": - postData(data._seed, eth.isMining()) + case "peerCount": + postData(data._id, eth.peerCount()) break - case "getPeerCount": - postData(data._seed, eth.peerCount()) - - break - - case "getCountAt": + case "countAt": require(1) - postData(data._seed, eth.txCountAt(data.args[0])) + postData(data._id, eth.txCountAt(data.args[0])) break - case "getCodeAt": + case "codeAt": require(1) var code = eth.codeAt(data.args[0]) - postData(data._seed, code); + postData(data._id, code); break - case "getBlockByNumber": + case "blockByNumber": + require(1) var block = eth.blockByNumber(data.args[0]) - postData(data._seed, block) - + postData(data._id, block) break - case "getBlockByHash": + case "blockByHash": + require(1) var block = eth.blockByHash(data.args[0]) - postData(data._seed, block) + postData(data._id, block) + break + + require(2) + var block = eth.blockByHash(data.args[0]) + postData(data._id, block.transactions[data.args[1]]) + break + + case "transactionByHash": + case "transactionByNumber": + require(2) + + var block; + if (data.call === "transactionByHash") + block = eth.blockByHash(data.args[0]) + else + block = eth.blockByNumber(data.args[0]) + + var tx = block.transactions.get(data.args[1]) + + postData(data._id, tx) + break + + case "uncleByHash": + case "uncleByNumber": + require(2) + + var block; + if (data.call === "uncleByHash") + block = eth.blockByHash(data.args[0]) + else + block = eth.blockByNumber(data.args[0]) + + var uncle = block.uncles.get(data.args[1]) + + postData(data._id, uncle) break @@ -216,50 +254,28 @@ Rectangle { require(5) var tx = eth.transact(data.args) - postData(data._seed, tx) + postData(data._id, tx) break - case "getStorageAt": + case "stateAt": require(2); var storage = eth.storageAt(data.args[0], data.args[1]); - postData(data._seed, storage) + postData(data._id, storage) break case "call": require(1); var ret = eth.call(data.args) - postData(data._seed, ret) - + postData(data._id, ret) break - case "getEachStorage": - require(1); - var storage = JSON.parse(eth.eachStorage(data.args[0])) - postData(data._seed, storage) - - break - - case "getTransactionsFor": - require(1); - var txs = eth.transactionsFor(data.args[0], true) - postData(data._seed, txs) - - break - - case "getBalanceAt": + case "balanceAt": require(1); - postData(data._seed, eth.balanceAt(data.args[0])); - - break - - case "getKey": - var key = eth.key().privateKey; - - postData(data._seed, key) + postData(data._id, eth.balanceAt(data.args[0])); break case "watch": @@ -268,45 +284,34 @@ Rectangle { case "disconnect": require(1) - postData(data._seed, null) - - break; - - case "getSecretToAddress": - require(1) - - var addr = eth.secretToAddress(data.args[0]) - console.log("getsecret", addr) - postData(data._seed, addr) - + postData(data._id, null) break; case "messages": require(1); var messages = JSON.parse(eth.getMessages(data.args[0])) - postData(data._seed, messages) - + postData(data._id, messages) break case "mutan": require(1) var code = eth.compileMutan(data.args[0]) - postData(data._seed, "0x"+code) - + postData(data._id, "0x"+code) break; case "newFilterString": require(1) var id = eth.newFilterString(data.args[0]) - postData(data._seed, id); + postData(data._id, id); break; + case "newFilter": require(1) var id = eth.newFilter(data.args[0]) - postData(data._seed, id); + postData(data._id, id); break; case "getMessages": @@ -314,7 +319,7 @@ Rectangle { var messages = eth.messages(data.args[0]); var m = JSON.parse(JSON.parse(JSON.stringify(messages))) - postData(data._seed, m); + postData(data._id, m); break; @@ -326,13 +331,13 @@ Rectangle { } catch(e) { console.log(data.call + ": " + e) - postData(data._seed, null); + postData(data._id, null); } } function post(seed, data) { - postData(data._seed, data) + postData(data._id, data) } function require(args, num) { @@ -341,7 +346,7 @@ Rectangle { } } function postData(seed, data) { - webview.experimental.postMessage(JSON.stringify({data: data, _seed: seed})) + webview.experimental.postMessage(JSON.stringify({data: data, _id: seed})) } function postEvent(event, data) { webview.experimental.postMessage(JSON.stringify({data: data, _event: event})) diff --git a/ethchain/filter.go b/ethchain/filter.go index b0edea7a0..a88d36467 100644 --- a/ethchain/filter.go +++ b/ethchain/filter.go @@ -90,7 +90,7 @@ func (self *Filter) Find() []*ethstate.Message { for i := 0; !quit && block != nil; i++ { // Quit on latest switch { - case block.Number.Uint64() == earliestBlockNo: + case block.Number.Uint64() == earliestBlockNo, block.Number.Uint64() == 0: quit = true case self.max <= len(messages): break diff --git a/ethchain/state_manager.go b/ethchain/state_manager.go index 62970f621..d48d96a51 100644 --- a/ethchain/state_manager.go +++ b/ethchain/state_manager.go @@ -194,7 +194,7 @@ done: } // Notify all subscribers - self.eth.EventMux().Post(TxPostEvent{tx}) + go self.eth.EventMux().Post(TxPostEvent{tx}) receipts = append(receipts, receipt) handled = append(handled, tx) diff --git a/ethpipe/js_types.go b/ethpipe/js_types.go index 05c12fbf6..956a49ab7 100644 --- a/ethpipe/js_types.go +++ b/ethpipe/js_types.go @@ -19,6 +19,7 @@ type JSBlock struct { Number int `json:"number"` Hash string `json:"hash"` Transactions *ethutil.List `json:"transactions"` + Uncles *ethutil.List `json:"uncles"` Time int64 `json:"time"` Coinbase string `json:"coinbase"` Name string `json:"name"` @@ -33,18 +34,24 @@ func NewJSBlock(block *ethchain.Block) *JSBlock { return &JSBlock{} } - var ptxs []*JSTransaction - for _, tx := range block.Transactions() { - ptxs = append(ptxs, NewJSTx(tx, block.State())) + ptxs := make([]*JSTransaction, len(block.Transactions())) + for i, tx := range block.Transactions() { + ptxs[i] = NewJSTx(tx, block.State()) } + txlist := ethutil.NewList(ptxs) - list := ethutil.NewList(ptxs) + puncles := make([]*JSBlock, len(block.Uncles)) + for i, uncle := range block.Uncles { + puncles[i] = NewJSBlock(uncle) + } + ulist := ethutil.NewList(puncles) return &JSBlock{ ref: block, Size: block.Size().String(), Number: int(block.Number.Uint64()), GasUsed: block.GasUsed.String(), GasLimit: block.GasLimit.String(), Hash: ethutil.Bytes2Hex(block.Hash()), - Transactions: list, Time: block.Time, + Transactions: txlist, Uncles: ulist, + Time: block.Time, Coinbase: ethutil.Bytes2Hex(block.Coinbase), PrevHash: ethutil.Bytes2Hex(block.PrevHash), } diff --git a/ethutil/list.go b/ethutil/list.go index 6919a02f5..db276f1e3 100644 --- a/ethutil/list.go +++ b/ethutil/list.go @@ -69,7 +69,8 @@ func (self *List) Interface() interface{} { // For JavaScript <3 func (self *List) ToJSON() string { - var list []interface{} + // make(T, 0) != nil + list := make([]interface{}, 0) for i := 0; i < self.Length; i++ { list = append(list, self.Get(i)) }