From aa9fff3e68b1def0a9a22009c233150bf9ba481f Mon Sep 17 00:00:00 2001 From: Bas van Kervel Date: Mon, 14 Mar 2016 09:38:54 +0100 Subject: [PATCH] rpc: various fixes/enhancements rpc: be less restrictive on the request id rpc: improved documentation console: upgrade web3.js to version 0.16.0 rpc: cache http connections rpc: rename wsDomains parameter to wsOrigins --- cmd/geth/js.go | 2 +- cmd/geth/main.go | 2 +- cmd/geth/usage.go | 2 +- cmd/utils/flags.go | 8 +- cmd/utils/jeth.go | 177 ++- eth/api.go | 60 +- eth/backend.go | 9 +- jsre/ethereum_js.go | 3116 +++++++++++++++++++++++++++++++++++++++++-- node/api.go | 47 +- node/config.go | 4 +- node/node.go | 20 +- node/node_test.go | 2 +- rpc/doc.go | 14 +- rpc/http.go | 20 +- rpc/ipc_windows.go | 2 +- rpc/javascript.go | 68 +- rpc/json.go | 165 +-- rpc/json_test.go | 93 +- rpc/server_test.go | 198 +-- rpc/types.go | 51 +- rpc/utils.go | 2 +- rpc/websocket.go | 4 +- whisper/api.go | 4 +- 23 files changed, 3431 insertions(+), 639 deletions(-) diff --git a/cmd/geth/js.go b/cmd/geth/js.go index a4b14d7b18..68f19919aa 100644 --- a/cmd/geth/js.go +++ b/cmd/geth/js.go @@ -237,7 +237,7 @@ func (js *jsre) apiBindings() error { } // load only supported API's in javascript runtime - shortcuts := "var eth = web3.eth; " + shortcuts := "var eth = web3.eth; var personal = web3.personal; " for _, apiName := range apiNames { if apiName == "web3" || apiName == "rpc" { continue // manually mapped or ignore diff --git a/cmd/geth/main.go b/cmd/geth/main.go index 645743c13a..37cf3451a1 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -326,7 +326,7 @@ JavaScript API. See https://github.com/ethereum/go-ethereum/wiki/Javascipt-Conso utils.WSListenAddrFlag, utils.WSPortFlag, utils.WSApiFlag, - utils.WSAllowedDomainsFlag, + utils.WSAllowedOriginsFlag, utils.IPCDisabledFlag, utils.IPCApiFlag, utils.IPCPathFlag, diff --git a/cmd/geth/usage.go b/cmd/geth/usage.go index e2adf73057..b7ee50b796 100644 --- a/cmd/geth/usage.go +++ b/cmd/geth/usage.go @@ -94,7 +94,7 @@ var AppHelpFlagGroups = []flagGroup{ utils.WSListenAddrFlag, utils.WSPortFlag, utils.WSApiFlag, - utils.WSAllowedDomainsFlag, + utils.WSAllowedOriginsFlag, utils.IPCDisabledFlag, utils.IPCApiFlag, utils.IPCPathFlag, diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 2f10938e31..ef79baf1c6 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -287,9 +287,9 @@ var ( Usage: "API's offered over the WS-RPC interface", Value: rpc.DefaultHTTPApis, } - WSAllowedDomainsFlag = cli.StringFlag{ - Name: "wsdomains", - Usage: "Domains from which to accept websockets requests (can be spoofed)", + WSAllowedOriginsFlag = cli.StringFlag{ + Name: "wsorigins", + Usage: "Origins from which to accept websockets requests", Value: "", } ExecFlag = cli.StringFlag{ @@ -655,7 +655,7 @@ func MakeSystemNode(name, version string, extra []byte, ctx *cli.Context) *node. HTTPModules: strings.Split(ctx.GlobalString(RPCApiFlag.Name), ","), WSHost: MakeWSRpcHost(ctx), WSPort: ctx.GlobalInt(WSPortFlag.Name), - WSDomains: ctx.GlobalString(WSAllowedDomainsFlag.Name), + WSOrigins: ctx.GlobalString(WSAllowedOriginsFlag.Name), WSModules: strings.Split(ctx.GlobalString(WSApiFlag.Name), ","), } // Configure the Ethereum service diff --git a/cmd/utils/jeth.go b/cmd/utils/jeth.go index e5e520db20..35fcd4bedb 100644 --- a/cmd/utils/jeth.go +++ b/cmd/utils/jeth.go @@ -37,7 +37,8 @@ func NewJeth(re *jsre.JSRE, client rpc.Client) *Jeth { return &Jeth{re, client} } -func (self *Jeth) err(call otto.FunctionCall, code int, msg string, id *int64) (response otto.Value) { +// err returns an error object for the given error code and message. +func (self *Jeth) err(call otto.FunctionCall, code int, msg string, id interface{}) (response otto.Value) { m := rpc.JSONErrResponse{ Version: "2.0", Id: id, @@ -56,44 +57,50 @@ func (self *Jeth) err(call otto.FunctionCall, code int, msg string, id *int64) ( return res } -// UnlockAccount asks the user for the password and than executes the jeth.UnlockAccount callback in the jsre +// UnlockAccount asks the user for the password and than executes the jeth.UnlockAccount callback in the jsre. +// It will need the public address for the account to unlock as first argument. +// The second argument is an optional string with the password. If not given the user is prompted for the password. +// The third argument is an optional integer which specifies for how long the account will be unlocked (in seconds). func (self *Jeth) UnlockAccount(call otto.FunctionCall) (response otto.Value) { - var account, passwd string - timeout := int64(300) - var ok bool + var account, passwd otto.Value + duration := otto.NullValue() - if len(call.ArgumentList) == 0 { - fmt.Println("expected address of account to unlock") + if !call.Argument(0).IsString() { + fmt.Println("first argument must be the account to unlock") return otto.FalseValue() } - if len(call.ArgumentList) >= 1 { - if accountExport, err := call.Argument(0).Export(); err == nil { - if account, ok = accountExport.(string); ok { - if len(call.ArgumentList) == 1 { - fmt.Printf("Unlock account %s\n", account) - passwd, err = PromptPassword("Passphrase: ", true) - if err != nil { - return otto.FalseValue() - } - } - } + account = call.Argument(0) + + // if password is not given or as null value -> ask user for password + if call.Argument(1).IsUndefined() || call.Argument(1).IsNull() { + fmt.Printf("Unlock account %s\n", account) + if password, err := PromptPassword("Passphrase: ", true); err == nil { + passwd, _ = otto.ToValue(password) + } else { + throwJSExeception(err.Error()) } - } - if len(call.ArgumentList) >= 2 { - if passwdExport, err := call.Argument(1).Export(); err == nil { - passwd, _ = passwdExport.(string) + } else { + if !call.Argument(1).IsString() { + throwJSExeception("password must be a string") } + passwd = call.Argument(1) } - if len(call.ArgumentList) >= 3 { - if timeoutExport, err := call.Argument(2).Export(); err == nil { - timeout, _ = timeoutExport.(int64) + // third argument is the duration how long the account must be unlocked. + // verify that its a number. + if call.Argument(2).IsDefined() && !call.Argument(2).IsNull() { + if !call.Argument(2).IsNumber() { + throwJSExeception("unlock duration must be a number") } + duration = call.Argument(2) } - if val, err := call.Otto.Call("jeth.unlockAccount", nil, account, passwd, timeout); err == nil { + // jeth.unlockAccount will send the request to the backend. + if val, err := call.Otto.Call("jeth.unlockAccount", nil, account, passwd, duration); err == nil { return val + } else { + throwJSExeception(err.Error()) } return otto.FalseValue() @@ -134,19 +141,31 @@ func (self *Jeth) NewAccount(call otto.FunctionCall) (response otto.Value) { return otto.FalseValue() } +// Send will serialize the first argument, send it to the node and returns the response. func (self *Jeth) Send(call otto.FunctionCall) (response otto.Value) { - reqif, err := call.Argument(0).Export() + // verify we got a batch request (array) or a single request (object) + ro := call.Argument(0).Object() + if ro == nil || (ro.Class() != "Array" && ro.Class() != "Object") { + throwJSExeception("Internal Error: request must be an object or array") + } + + // convert otto vm arguments to go values by JSON serialising and parsing. + data, err := call.Otto.Call("JSON.stringify", nil, ro) if err != nil { - return self.err(call, -32700, err.Error(), nil) + throwJSExeception(err.Error()) } - jsonreq, err := json.Marshal(reqif) + jsonreq, _ := data.ToString() + + // parse arguments to JSON rpc requests, either to an array (batch) or to a single request. var reqs []rpc.JSONRequest batch := true - err = json.Unmarshal(jsonreq, &reqs) - if err != nil { + if err = json.Unmarshal([]byte(jsonreq), &reqs); err != nil { + // single request? reqs = make([]rpc.JSONRequest, 1) - err = json.Unmarshal(jsonreq, &reqs[0]) + if err = json.Unmarshal([]byte(jsonreq), &reqs[0]); err != nil { + throwJSExeception("invalid request") + } batch = false } @@ -154,47 +173,50 @@ func (self *Jeth) Send(call otto.FunctionCall) (response otto.Value) { call.Otto.Run("var ret_response = new Array(response_len);") for i, req := range reqs { - err := self.client.Send(&req) - if err != nil { + if err := self.client.Send(&req); err != nil { return self.err(call, -32603, err.Error(), req.Id) } result := make(map[string]interface{}) - err = self.client.Recv(&result) - if err != nil { + if err = self.client.Recv(&result); err != nil { return self.err(call, -32603, err.Error(), req.Id) } - _, isSuccessResponse := result["result"] - _, isErrorResponse := result["error"] - if !isSuccessResponse && !isErrorResponse { - return self.err(call, -32603, fmt.Sprintf("Invalid response"), new(int64)) - } - id, _ := result["id"] - call.Otto.Set("ret_id", id) - jsonver, _ := result["jsonrpc"] + + call.Otto.Set("ret_id", id) call.Otto.Set("ret_jsonrpc", jsonver) + call.Otto.Set("response_idx", i) - var payload []byte - if isSuccessResponse { - payload, _ = json.Marshal(result["result"]) - } else if isErrorResponse { - payload, _ = json.Marshal(result["error"]) + // call was successful + if res, ok := result["result"]; ok { + payload, _ := json.Marshal(res) + call.Otto.Set("ret_result", string(payload)) + response, err = call.Otto.Run(` + ret_response[response_idx] = { jsonrpc: ret_jsonrpc, id: ret_id, result: JSON.parse(ret_result) }; + `) + continue } - call.Otto.Set("ret_result", string(payload)) - call.Otto.Set("response_idx", i) - response, err = call.Otto.Run(` - ret_response[response_idx] = { jsonrpc: ret_jsonrpc, id: ret_id, result: JSON.parse(ret_result) }; - `) + // request returned an error + if res, ok := result["error"]; ok { + payload, _ := json.Marshal(res) + call.Otto.Set("ret_result", string(payload)) + response, err = call.Otto.Run(` + ret_response[response_idx] = { jsonrpc: ret_jsonrpc, id: ret_id, error: JSON.parse(ret_result) }; + `) + continue + } + + return self.err(call, -32603, fmt.Sprintf("Invalid response"), new(int64)) } if !batch { call.Otto.Run("ret_response = ret_response[0];") } + // if a callback was given execute it. if call.Argument(1).IsObject() { call.Otto.Set("callback", call.Argument(1)) call.Otto.Run(` @@ -207,53 +229,6 @@ func (self *Jeth) Send(call otto.FunctionCall) (response otto.Value) { return } -/* -// handleRequest will handle user agent requests by interacting with the user and sending -// the user response back to the geth service -func (self *Jeth) handleRequest(req *shared.Request) bool { - var err error - var args []interface{} - if err = json.Unmarshal(req.Params, &args); err != nil { - glog.V(logger.Info).Infof("Unable to parse agent request - %v\n", err) - return false - } - - switch req.Method { - case useragent.AskPasswordMethod: - return self.askPassword(req.Id, req.Jsonrpc, args) - case useragent.ConfirmTransactionMethod: - return self.confirmTransaction(req.Id, req.Jsonrpc, args) - } - - return false -} - -// askPassword will ask the user to supply the password for a given account -func (self *Jeth) askPassword(id interface{}, jsonrpc string, args []interface{}) bool { - var err error - var passwd string - if len(args) >= 1 { - if account, ok := args[0].(string); ok { - fmt.Printf("Unlock account %s\n", account) - } else { - return false - } - } - passwd, err = PromptPassword("Passphrase: ", true) - - if err = self.client.Send(shared.NewRpcResponse(id, jsonrpc, passwd, err)); err != nil { - glog.V(logger.Info).Infof("Unable to send user agent ask password response - %v\n", err) - } - - return err == nil -} - -func (self *Jeth) confirmTransaction(id interface{}, jsonrpc string, args []interface{}) bool { - // Accept all tx which are send from this console - return self.client.Send(shared.NewRpcResponse(id, jsonrpc, true, nil)) == nil -} -*/ - // throwJSExeception panics on an otto value, the Otto VM will then throw msg as a javascript error. func throwJSExeception(msg interface{}) otto.Value { p, _ := otto.ToValue(msg) diff --git a/eth/api.go b/eth/api.go index 676191fc23..138df908af 100644 --- a/eth/api.go +++ b/eth/api.go @@ -25,6 +25,7 @@ import ( "io/ioutil" "math/big" "os" + "runtime" "sync" "time" @@ -96,8 +97,8 @@ type PublicEthereumAPI struct { } // NewPublicEthereumAPI creates a new Ethereum protocol API. -func NewPublicEthereumAPI(e *Ethereum) *PublicEthereumAPI { - return &PublicEthereumAPI{e, NewGasPriceOracle(e)} +func NewPublicEthereumAPI(e *Ethereum, gpo *GasPriceOracle) *PublicEthereumAPI { + return &PublicEthereumAPI{e, gpo} } // GasPrice returns a suggestion for a gas price. @@ -108,11 +109,7 @@ func (s *PublicEthereumAPI) GasPrice() *big.Int { // GetCompilers returns the collection of available smart contract compilers func (s *PublicEthereumAPI) GetCompilers() ([]string, error) { solc, err := s.e.Solc() - if err != nil { - return nil, err - } - - if solc != nil { + if err != nil && solc != nil { return []string{"Solidity"}, nil } @@ -240,9 +237,15 @@ func NewPrivateMinerAPI(e *Ethereum) *PrivateMinerAPI { return &PrivateMinerAPI{e: e} } -// Start the miner with the given number of threads -func (s *PrivateMinerAPI) Start(threads rpc.HexNumber) (bool, error) { +// Start the miner with the given number of threads. If threads is nil the number of +// workers started is equal to the number of logical CPU's that are usable by this process. +func (s *PrivateMinerAPI) Start(threads *rpc.HexNumber) (bool, error) { s.e.StartAutoDAG() + + if threads == nil { + threads = rpc.NewHexNumber(runtime.NumCPU()) + } + err := s.e.StartMining(threads.Int(), "") if err == nil { return true, nil @@ -265,7 +268,7 @@ func (s *PrivateMinerAPI) SetExtra(extra string) (bool, error) { } // SetGasPrice sets the minimum accepted gas price for the miner. -func (s *PrivateMinerAPI) SetGasPrice(gasPrice rpc.Number) bool { +func (s *PrivateMinerAPI) SetGasPrice(gasPrice rpc.HexNumber) bool { s.e.Miner().SetGasPrice(gasPrice.BigInt()) return true } @@ -440,10 +443,15 @@ func (s *PrivateAccountAPI) NewAccount(password string) (common.Address, error) return common.Address{}, err } -// UnlockAccount will unlock the account associated with the given address with the given password for duration seconds. -// It returns an indication if the action was successful. -func (s *PrivateAccountAPI) UnlockAccount(addr common.Address, password string, duration int) bool { - if err := s.am.TimedUnlock(addr, password, time.Duration(duration)*time.Second); err != nil { +// UnlockAccount will unlock the account associated with the given address with +// the given password for duration seconds. If duration is nil it will use a +// default of 300 seconds. It returns an indication if the account was unlocked. +func (s *PrivateAccountAPI) UnlockAccount(addr common.Address, password string, duration *rpc.HexNumber) bool { + if duration == nil { + duration = rpc.NewHexNumber(300) + } + + if err := s.am.TimedUnlock(addr, password, time.Duration(duration.Int())*time.Second); err != nil { glog.V(logger.Info).Infof("%v\n", err) return false } @@ -458,7 +466,7 @@ func (s *PrivateAccountAPI) LockAccount(addr common.Address) bool { // PublicBlockChainAPI provides an API to access the Ethereum blockchain. // It offers only methods that operate on public data that is freely available to anyone. type PublicBlockChainAPI struct { - config *core.ChainConfig + config *core.ChainConfig bc *core.BlockChain chainDb ethdb.Database eventMux *event.TypeMux @@ -466,10 +474,11 @@ type PublicBlockChainAPI struct { newBlockSubscriptions map[string]func(core.ChainEvent) error // callbacks for new block subscriptions am *accounts.Manager miner *miner.Miner + gpo *GasPriceOracle } // NewPublicBlockChainAPI creates a new Etheruem blockchain API. -func NewPublicBlockChainAPI(config *core.ChainConfig, bc *core.BlockChain, m *miner.Miner, chainDb ethdb.Database, eventMux *event.TypeMux, am *accounts.Manager) *PublicBlockChainAPI { +func NewPublicBlockChainAPI(config *core.ChainConfig, bc *core.BlockChain, m *miner.Miner, chainDb ethdb.Database, gpo *GasPriceOracle, eventMux *event.TypeMux, am *accounts.Manager) *PublicBlockChainAPI { api := &PublicBlockChainAPI{ config: config, bc: bc, @@ -478,6 +487,7 @@ func NewPublicBlockChainAPI(config *core.ChainConfig, bc *core.BlockChain, m *mi eventMux: eventMux, am: am, newBlockSubscriptions: make(map[string]func(core.ChainEvent) error), + gpo: gpo, } go api.subscriptionLoop() @@ -674,8 +684,8 @@ func (m callmsg) Data() []byte { return m.data } type CallArgs struct { From common.Address `json:"from"` To *common.Address `json:"to"` - Gas rpc.HexNumber `json:"gas"` - GasPrice rpc.HexNumber `json:"gasPrice"` + Gas *rpc.HexNumber `json:"gas"` + GasPrice *rpc.HexNumber `json:"gasPrice"` Value rpc.HexNumber `json:"value"` Data string `json:"data"` } @@ -711,11 +721,11 @@ func (s *PublicBlockChainAPI) doCall(args CallArgs, blockNr rpc.BlockNumber) (st value: args.Value.BigInt(), data: common.FromHex(args.Data), } - if msg.gas.Cmp(common.Big0) == 0 { + if msg.gas == nil { msg.gas = big.NewInt(50000000) } - if msg.gasPrice.Cmp(common.Big0) == 0 { - msg.gasPrice = new(big.Int).Mul(big.NewInt(50), common.Shannon) + if msg.gasPrice == nil { + msg.gasPrice = s.gpo.SuggestPrice() } // Execute the call and return @@ -882,10 +892,10 @@ type PublicTransactionPoolAPI struct { } // NewPublicTransactionPoolAPI creates a new RPC service with methods specific for the transaction pool. -func NewPublicTransactionPoolAPI(e *Ethereum) *PublicTransactionPoolAPI { +func NewPublicTransactionPoolAPI(e *Ethereum, gpo *GasPriceOracle) *PublicTransactionPoolAPI { api := &PublicTransactionPoolAPI{ eventMux: e.EventMux(), - gpo: NewGasPriceOracle(e), + gpo: gpo, chainDb: e.ChainDb(), bc: e.BlockChain(), am: e.AccountManager(), @@ -1306,7 +1316,7 @@ func newTx(t *types.Transaction) *Tx { // SignTransaction will sign the given transaction with the from account. // The node needs to have the private key of the account corresponding with // the given from address and it needs to be unlocked. -func (s *PublicTransactionPoolAPI) SignTransaction(args *SignTransactionArgs) (*SignTransactionResult, error) { +func (s *PublicTransactionPoolAPI) SignTransaction(args SignTransactionArgs) (*SignTransactionResult, error) { if args.Gas == nil { args.Gas = rpc.NewHexNumber(defaultGas) } @@ -1397,7 +1407,7 @@ func (s *PublicTransactionPoolAPI) NewPendingTransactions(ctx context.Context) ( // Resend accepts an existing transaction and a new gas price and limit. It will remove the given transaction from the // pool and reinsert it with the new gas price and limit. -func (s *PublicTransactionPoolAPI) Resend(tx *Tx, gasPrice, gasLimit *rpc.HexNumber) (common.Hash, error) { +func (s *PublicTransactionPoolAPI) Resend(tx Tx, gasPrice, gasLimit *rpc.HexNumber) (common.Hash, error) { pending := s.txPool.GetTransactions() for _, p := range pending { diff --git a/eth/backend.go b/eth/backend.go index 20f516610e..f4282d59ff 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -272,11 +272,14 @@ func New(ctx *node.ServiceContext, config *Config) (*Ethereum, error) { // APIs returns the collection of RPC services the ethereum package offers. // NOTE, some of these services probably need to be moved to somewhere else. func (s *Ethereum) APIs() []rpc.API { + // share gas price oracle in API's + gpo := NewGasPriceOracle(s) + return []rpc.API{ { Namespace: "eth", Version: "1.0", - Service: NewPublicEthereumAPI(s), + Service: NewPublicEthereumAPI(s, gpo), Public: true, }, { Namespace: "eth", @@ -291,12 +294,12 @@ func (s *Ethereum) APIs() []rpc.API { }, { Namespace: "eth", Version: "1.0", - Service: NewPublicBlockChainAPI(s.chainConfig, s.BlockChain(), s.Miner(), s.ChainDb(), s.EventMux(), s.AccountManager()), + Service: NewPublicBlockChainAPI(s.chainConfig, s.BlockChain(), s.Miner(), s.ChainDb(), gpo, s.EventMux(), s.AccountManager()), Public: true, }, { Namespace: "eth", Version: "1.0", - Service: NewPublicTransactionPoolAPI(s), + Service: NewPublicTransactionPoolAPI(s, gpo), Public: true, }, { Namespace: "eth", diff --git a/jsre/ethereum_js.go b/jsre/ethereum_js.go index 6d56750362..dfdedeb110 100644 --- a/jsre/ethereum_js.go +++ b/jsre/ethereum_js.go @@ -1889,7 +1889,7 @@ module.exports = function (value, options) { }; -},{"crypto-js":57,"crypto-js/sha3":78}],20:[function(require,module,exports){ +},{"crypto-js":58,"crypto-js/sha3":79}],20:[function(require,module,exports){ /* This file is part of web3.js. @@ -1927,17 +1927,22 @@ module.exports = function (value, options) { var BigNumber = require('bignumber.js'); +var sha3 = require('./sha3.js'); var utf8 = require('utf8'); var unitMap = { + 'noether': '0', 'wei': '1', 'kwei': '1000', - 'ada': '1000', + 'Kwei': '1000', + 'babbage': '1000', 'femtoether': '1000', 'mwei': '1000000', - 'babbage': '1000000', + 'Mwei': '1000000', + 'lovelace': '1000000', 'picoether': '1000000', 'gwei': '1000000000', + 'Gwei': '1000000000', 'shannon': '1000000000', 'nanoether': '1000000000', 'nano': '1000000000', @@ -1950,7 +1955,6 @@ var unitMap = { 'ether': '1000000000000000000', 'kether': '1000000000000000000000', 'grand': '1000000000000000000000', - 'einstein': '1000000000000000000000', 'mether': '1000000000000000000000000', 'gether': '1000000000000000000000000000', 'tether': '1000000000000000000000000000000' @@ -2029,7 +2033,7 @@ var toAscii = function(hex) { }; /** - * Should be called to get hex representation (prefixed by 0x) of utf8 a string + * Should be called to get hex representation (prefixed by 0x) of utf8 string * * @method fromUtf8 * @param {String} string @@ -2185,13 +2189,13 @@ var getValueOfUnit = function (unit) { * * Possible units are: * SI Short SI Full Effigy Other - * - kwei femtoether ada - * - mwei picoether babbage + * - kwei femtoether babbage + * - mwei picoether lovelace * - gwei nanoether shannon nano * - -- microether szabo micro * - -- milliether finney milli * - ether -- -- - * - kether einstein grand + * - kether -- grand * - mether * - gether * - tether @@ -2212,13 +2216,14 @@ var fromWei = function(number, unit) { * * Possible units are: * SI Short SI Full Effigy Other - * - kwei femtoether ada - * - mwei picoether babbage + * - kwei femtoether babbage + * - mwei picoether lovelace * - gwei nanoether shannon nano * - -- microether szabo micro + * - -- microether szabo micro * - -- milliether finney milli * - ether -- -- - * - kether einstein grand + * - kether -- grand * - mether * - gether * - tether @@ -2288,7 +2293,66 @@ var isStrictAddress = function (address) { * @return {Boolean} */ var isAddress = function (address) { - return /^(0x)?[0-9a-f]{40}$/i.test(address); + if (!/^(0x)?[0-9a-f]{40}$/i.test(address)) { + // check if it has the basic requirements of an address + return false; + } else if (/^(0x)?[0-9a-f]{40}$/.test(address) || /^(0x)?[0-9A-F]{40}$/.test(address)) { + // If it's all small caps or all all caps, return true + return true; + } else { + // Otherwise check each case + return isChecksumAddress(address); + } +}; + + + +/** + * Checks if the given string is a checksummed address + * + * @method isChecksumAddress + * @param {String} address the given HEX adress + * @return {Boolean} +*/ +var isChecksumAddress = function (address) { + // Check each case + address = address.replace('0x',''); + var addressHash = sha3(address.toLowerCase()); + + for (var i = 0; i < 40; i++ ) { + // the nth letter should be uppercase if the nth digit of casemap is 1 + if ((parseInt(addressHash[i], 16) > 7 && address[i].toUpperCase() !== address[i]) || (parseInt(addressHash[i], 16) <= 7 && address[i].toLowerCase() !== address[i])) { + return false; + } + } + return true; +}; + + + +/** + * Makes a checksum address + * + * @method toChecksumAddress + * @param {String} address the given HEX adress + * @return {String} +*/ +var toChecksumAddress = function (address) { + if (typeof address === 'undefined') return ''; + + address = address.toLowerCase().replace('0x',''); + var addressHash = sha3(address); + var checksumAddress = '0x'; + + for (var i = 0; i < address.length; i++ ) { + // If ith character is 9 to f then make it uppercase + if (parseInt(addressHash[i], 16) > 7) { + checksumAddress += address[i].toUpperCase(); + } else { + checksumAddress += address[i]; + } + } + return checksumAddress; }; /** @@ -2414,6 +2478,8 @@ module.exports = { isBigNumber: isBigNumber, isStrictAddress: isStrictAddress, isAddress: isAddress, + isChecksumAddress: isChecksumAddress, + toChecksumAddress: toChecksumAddress, isFunction: isFunction, isString: isString, isObject: isObject, @@ -2422,9 +2488,9 @@ module.exports = { isJson: isJson }; -},{"bignumber.js":"bignumber.js","utf8":83}],21:[function(require,module,exports){ +},{"./sha3.js":19,"bignumber.js":"bignumber.js","utf8":84}],21:[function(require,module,exports){ module.exports={ - "version": "0.15.1" + "version": "0.15.3" } },{}],22:[function(require,module,exports){ @@ -2461,6 +2527,7 @@ var Eth = require('./web3/methods/eth'); var DB = require('./web3/methods/db'); var Shh = require('./web3/methods/shh'); var Net = require('./web3/methods/net'); +var Personal = require('./web3/methods/personal'); var Settings = require('./web3/settings'); var version = require('./version.json'); var utils = require('./utils/utils'); @@ -2480,6 +2547,7 @@ function Web3 (provider) { this.db = new DB(this); this.shh = new Shh(this); this.net = new Net(this); + this.personal = new Personal(this); this.settings = new Settings(); this.version = { api: version.version @@ -2521,8 +2589,14 @@ Web3.prototype.toBigNumber = utils.toBigNumber; Web3.prototype.toWei = utils.toWei; Web3.prototype.fromWei = utils.fromWei; Web3.prototype.isAddress = utils.isAddress; +Web3.prototype.isChecksumAddress = utils.isChecksumAddress; +Web3.prototype.toChecksumAddress = utils.toChecksumAddress; Web3.prototype.isIBAN = utils.isIBAN; -Web3.prototype.sha3 = sha3; + + +Web3.prototype.sha3 = function(string, options) { + return '0x' + sha3(string, options); +}; /** * Transforms direct icap to address @@ -2567,7 +2641,7 @@ Web3.prototype.createBatch = function () { module.exports = Web3; -},{"./utils/sha3":19,"./utils/utils":20,"./version.json":21,"./web3/batch":24,"./web3/extend":28,"./web3/httpprovider":32,"./web3/iban":33,"./web3/ipcprovider":34,"./web3/methods/db":37,"./web3/methods/eth":38,"./web3/methods/net":39,"./web3/methods/shh":40,"./web3/property":43,"./web3/requestmanager":44,"./web3/settings":45}],23:[function(require,module,exports){ +},{"./utils/sha3":19,"./utils/utils":20,"./version.json":21,"./web3/batch":24,"./web3/extend":28,"./web3/httpprovider":32,"./web3/iban":33,"./web3/ipcprovider":34,"./web3/methods/db":37,"./web3/methods/eth":38,"./web3/methods/net":39,"./web3/methods/personal":40,"./web3/methods/shh":41,"./web3/property":44,"./web3/requestmanager":45,"./web3/settings":46}],23:[function(require,module,exports){ /* This file is part of web3.js. @@ -2657,7 +2731,7 @@ AllSolidityEvents.prototype.attachToContract = function (contract) { module.exports = AllSolidityEvents; -},{"../utils/sha3":19,"../utils/utils":20,"./event":27,"./filter":29,"./formatters":30,"./methods/watches":41}],24:[function(require,module,exports){ +},{"../utils/sha3":19,"../utils/utils":20,"./event":27,"./filter":29,"./formatters":30,"./methods/watches":42}],24:[function(require,module,exports){ /* This file is part of web3.js. @@ -2894,6 +2968,62 @@ var ContractFactory = function (eth, abi) { this.eth = eth; this.abi = abi; + /** + * Should be called to create new contract on a blockchain + * + * @method new + * @param {Any} contract constructor param1 (optional) + * @param {Any} contract constructor param2 (optional) + * @param {Object} contract transaction object (required) + * @param {Function} callback + * @returns {Contract} returns contract instance + */ + this.new = function () { + var contract = new Contract(this.eth, this.abi); + + // parse arguments + var options = {}; // required! + var callback; + + var args = Array.prototype.slice.call(arguments); + if (utils.isFunction(args[args.length - 1])) { + callback = args.pop(); + } + + var last = args[args.length - 1]; + if (utils.isObject(last) && !utils.isArray(last)) { + options = args.pop(); + } + + var bytes = encodeConstructorParams(this.abi, args); + options.data += bytes; + + if (callback) { + + // wait for the contract address adn check if the code was deployed + this.eth.sendTransaction(options, function (err, hash) { + if (err) { + callback(err); + } else { + // add the transaction hash + contract.transactionHash = hash; + + // call callback for the first time + callback(null, contract); + + checkForContractAddress(contract, callback); + } + }); + } else { + var hash = this.eth.sendTransaction(options); + // add the transaction hash + contract.transactionHash = hash; + checkForContractAddress(contract); + } + + return contract; + }; + this.new.getData = this.getData.bind(this); }; @@ -2908,61 +3038,7 @@ var ContractFactory = function (eth, abi) { //return new ContractFactory(abi); //}; -/** - * Should be called to create new contract on a blockchain - * - * @method new - * @param {Any} contract constructor param1 (optional) - * @param {Any} contract constructor param2 (optional) - * @param {Object} contract transaction object (required) - * @param {Function} callback - * @returns {Contract} returns contract instance - */ -ContractFactory.prototype.new = function () { - var contract = new Contract(this.eth, this.abi); - - // parse arguments - var options = {}; // required! - var callback; - - var args = Array.prototype.slice.call(arguments); - if (utils.isFunction(args[args.length - 1])) { - callback = args.pop(); - } - - var last = args[args.length - 1]; - if (utils.isObject(last) && !utils.isArray(last)) { - options = args.pop(); - } - - var bytes = encodeConstructorParams(this.abi, args); - options.data += bytes; - - if (callback) { - - // wait for the contract address adn check if the code was deployed - this.eth.sendTransaction(options, function (err, hash) { - if (err) { - callback(err); - } else { - // add the transaction hash - contract.transactionHash = hash; - - // call callback for the first time - callback(null, contract); - - checkForContractAddress(contract, callback); - } - }); - } else { - var hash = this.eth.sendTransaction(options); - // add the transaction hash - contract.transactionHash = hash; - checkForContractAddress(contract); - } - return contract; -}; /** * Should be called to get access to existing contract on a blockchain @@ -3023,7 +3099,6 @@ var Contract = function (eth, abi, address) { module.exports = ContractFactory; - },{"../solidity/coder":7,"../utils/utils":20,"./allevents":23,"./event":27,"./function":31}],26:[function(require,module,exports){ /* This file is part of web3.js. @@ -3274,7 +3349,7 @@ SolidityEvent.prototype.attachToContract = function (contract) { module.exports = SolidityEvent; -},{"../solidity/coder":7,"../utils/sha3":19,"../utils/utils":20,"./filter":29,"./formatters":30,"./methods/watches":41}],28:[function(require,module,exports){ +},{"../solidity/coder":7,"../utils/sha3":19,"../utils/utils":20,"./filter":29,"./formatters":30,"./methods/watches":42}],28:[function(require,module,exports){ var formatters = require('./formatters'); var utils = require('./../utils/utils'); var Method = require('./method'); @@ -3324,7 +3399,7 @@ var extend = function (web3) { module.exports = extend; -},{"./../utils/utils":20,"./formatters":30,"./method":36,"./property":43}],29:[function(require,module,exports){ +},{"./../utils/utils":20,"./formatters":30,"./method":36,"./property":44}],29:[function(require,module,exports){ /* This file is part of web3.js. @@ -3495,7 +3570,7 @@ var Filter = function (requestManager, options, methods, formatter, callback) { pollFilter(self); // start to watch immediately - if(callback) { + if(typeof callback === 'function') { return self.watch(callback); } } @@ -3827,7 +3902,7 @@ var inputAddressFormatter = function (address) { } else if (utils.isAddress(address)) { return '0x' + address; } - throw 'invalid address'; + throw new Error('invalid address'); }; @@ -3836,8 +3911,6 @@ var outputSyncingFormatter = function(result) { result.startingBlock = utils.toDecimal(result.startingBlock); result.currentBlock = utils.toDecimal(result.currentBlock); result.highestBlock = utils.toDecimal(result.highestBlock); - result.pulledStates = utils.toDecimal(result.pulledStates); - result.knownStates = utils.toDecimal(result.knownStates); return result; }; @@ -4570,10 +4643,10 @@ IpcProvider.prototype._parseResponse = function(data) { // DE-CHUNKER var dechunkedData = data - .replace(/\}\{/g,'}|--|{') // }{ - .replace(/\}\]\[\{/g,'}]|--|[{') // }][{ - .replace(/\}\[\{/g,'}|--|[{') // }[{ - .replace(/\}\]\{/g,'}]|--|{') // }]{ + .replace(/\}[\n\r]?\{/g,'}|--|{') // }{ + .replace(/\}\][\n\r]?\[\{/g,'}]|--|[{') // }][{ + .replace(/\}[\n\r]?\[\{/g,'}|--|[{') // }[{ + .replace(/\}\][\n\r]?\{/g,'}]|--|{') // }]{ .split('|--|'); dechunkedData.forEach(function(data){ @@ -4594,7 +4667,7 @@ IpcProvider.prototype._parseResponse = function(data) { // start timeout to cancel all requests clearTimeout(_this.lastChunkTimeout); _this.lastChunkTimeout = setTimeout(function(){ - _this.timeout(); + _this._timeout(); throw errors.InvalidResponse(data); }, 1000 * 15); @@ -5227,6 +5300,13 @@ var methods = function () { inputFormatter: [formatters.inputTransactionFormatter] }); + var sign = new Method({ + name: 'sign', + call: 'eth_sign', + params: 2, + inputFormatter: [formatters.inputAddressFormatter, null] + }); + var call = new Method({ name: 'call', call: 'eth_call', @@ -5289,6 +5369,7 @@ var methods = function () { estimateGas, sendRawTransaction, sendTransaction, + sign, compileSolidity, compileLLL, compileSerpent, @@ -5359,7 +5440,7 @@ Eth.prototype.isSyncing = function (callback) { module.exports = Eth; -},{"../../utils/config":18,"../../utils/utils":20,"../contract":25,"../filter":29,"../formatters":30,"../iban":33,"../method":36,"../namereg":42,"../property":43,"../syncing":46,"../transfer":47,"./watches":41}],39:[function(require,module,exports){ +},{"../../utils/config":18,"../../utils/utils":20,"../contract":25,"../filter":29,"../formatters":30,"../iban":33,"../method":36,"../namereg":43,"../property":44,"../syncing":47,"../transfer":48,"./watches":42}],39:[function(require,module,exports){ /* This file is part of web3.js. @@ -5413,7 +5494,94 @@ var properties = function () { module.exports = Net; -},{"../../utils/utils":20,"../property":43}],40:[function(require,module,exports){ +},{"../../utils/utils":20,"../property":44}],40:[function(require,module,exports){ +/* + This file is part of web3.js. + + web3.js is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + web3.js is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with web3.js. If not, see . +*/ +/** + * @file eth.js + * @author Marek Kotewicz + * @author Fabian Vogelsteller + * @date 2015 + */ + +"use strict"; + +var Method = require('../method'); +var Property = require('../property'); +var formatters = require('../formatters'); + +function Personal(web3) { + this._requestManager = web3._requestManager; + + var self = this; + + methods().forEach(function(method) { + method.attachToObject(self); + method.setRequestManager(self._requestManager); + }); + + properties().forEach(function(p) { + p.attachToObject(self); + p.setRequestManager(self._requestManager); + }); +} + +var methods = function () { + var newAccount = new Method({ + name: 'newAccount', + call: 'personal_newAccount', + params: 1, + inputFormatter: [null] + }); + + var unlockAccount = new Method({ + name: 'unlockAccount', + call: 'personal_unlockAccount', + params: 3, + inputFormatter: [formatters.inputAddressFormatter, null, null] + }); + + var lockAccount = new Method({ + name: 'lockAccount', + call: 'personal_lockAccount', + params: 1, + inputFormatter: [formatters.inputAddressFormatter] + }); + + return [ + newAccount, + unlockAccount, + lockAccount + ]; +}; + +var properties = function () { + return [ + new Property({ + name: 'listAccounts', + getter: 'personal_listAccounts' + }) + ]; +}; + + +module.exports = Personal; + +},{"../formatters":30,"../method":36,"../property":44}],41:[function(require,module,exports){ /* This file is part of web3.js. @@ -5501,7 +5669,7 @@ var methods = function () { module.exports = Shh; -},{"../filter":29,"../formatters":30,"../method":36,"./watches":41}],41:[function(require,module,exports){ +},{"../filter":29,"../formatters":30,"../method":36,"./watches":42}],42:[function(require,module,exports){ /* This file is part of web3.js. @@ -5617,7 +5785,7 @@ module.exports = { }; -},{"../method":36}],42:[function(require,module,exports){ +},{"../method":36}],43:[function(require,module,exports){ /* This file is part of web3.js. @@ -5658,7 +5826,7 @@ module.exports = { }; -},{"../contracts/GlobalRegistrar.json":1,"../contracts/ICAPRegistrar.json":2}],43:[function(require,module,exports){ +},{"../contracts/GlobalRegistrar.json":1,"../contracts/ICAPRegistrar.json":2}],44:[function(require,module,exports){ /* This file is part of web3.js. @@ -5742,7 +5910,6 @@ Property.prototype.extractCallback = function (args) { */ Property.prototype.attachToObject = function (obj) { var proto = { - //get: this.buildGet() get: this.buildGet(), enumerable: true }; @@ -5805,7 +5972,7 @@ Property.prototype.request = function () { module.exports = Property; -},{"../utils/utils":20}],44:[function(require,module,exports){ +},{"../utils/utils":20}],45:[function(require,module,exports){ /* This file is part of web3.js. @@ -6072,7 +6239,7 @@ RequestManager.prototype.poll = function () { module.exports = RequestManager; -},{"../utils/config":18,"../utils/utils":20,"./errors":26,"./jsonrpc":35}],45:[function(require,module,exports){ +},{"../utils/config":18,"../utils/utils":20,"./errors":26,"./jsonrpc":35}],46:[function(require,module,exports){ var Settings = function () { @@ -6083,7 +6250,7 @@ var Settings = function () { module.exports = Settings; -},{}],46:[function(require,module,exports){ +},{}],47:[function(require,module,exports){ /* This file is part of web3.js. @@ -6178,7 +6345,7 @@ IsSyncing.prototype.stopWatching = function () { module.exports = IsSyncing; -},{"../utils/utils":20,"./formatters":30}],47:[function(require,module,exports){ +},{"../utils/utils":20,"./formatters":30}],48:[function(require,module,exports){ /* This file is part of web3.js. @@ -6272,9 +6439,9 @@ var deposit = function (eth, from, to, value, client, callback) { module.exports = transfer; -},{"../contracts/SmartExchange.json":3,"./iban":33}],48:[function(require,module,exports){ +},{"../contracts/SmartExchange.json":3,"./iban":33}],49:[function(require,module,exports){ -},{}],49:[function(require,module,exports){ +},{}],50:[function(require,module,exports){ ;(function (root, factory, undef) { if (typeof exports === "object") { // CommonJS @@ -6502,7 +6669,7 @@ module.exports = transfer; return CryptoJS.AES; })); -},{"./cipher-core":50,"./core":51,"./enc-base64":52,"./evpkdf":54,"./md5":59}],50:[function(require,module,exports){ +},{"./cipher-core":51,"./core":52,"./enc-base64":53,"./evpkdf":55,"./md5":60}],51:[function(require,module,exports){ ;(function (root, factory) { if (typeof exports === "object") { // CommonJS @@ -7378,7 +7545,7 @@ module.exports = transfer; })); -},{"./core":51}],51:[function(require,module,exports){ +},{"./core":52}],52:[function(require,module,exports){ ;(function (root, factory) { if (typeof exports === "object") { // CommonJS @@ -8121,7 +8288,7 @@ module.exports = transfer; return CryptoJS; })); -},{}],52:[function(require,module,exports){ +},{}],53:[function(require,module,exports){ ;(function (root, factory) { if (typeof exports === "object") { // CommonJS @@ -8245,7 +8412,7 @@ module.exports = transfer; return CryptoJS.enc.Base64; })); -},{"./core":51}],53:[function(require,module,exports){ +},{"./core":52}],54:[function(require,module,exports){ ;(function (root, factory) { if (typeof exports === "object") { // CommonJS @@ -8395,7 +8562,7 @@ module.exports = transfer; return CryptoJS.enc.Utf16; })); -},{"./core":51}],54:[function(require,module,exports){ +},{"./core":52}],55:[function(require,module,exports){ ;(function (root, factory, undef) { if (typeof exports === "object") { // CommonJS @@ -8528,7 +8695,7 @@ module.exports = transfer; return CryptoJS.EvpKDF; })); -},{"./core":51,"./hmac":56,"./sha1":75}],55:[function(require,module,exports){ +},{"./core":52,"./hmac":57,"./sha1":76}],56:[function(require,module,exports){ ;(function (root, factory, undef) { if (typeof exports === "object") { // CommonJS @@ -8595,7 +8762,7 @@ module.exports = transfer; return CryptoJS.format.Hex; })); -},{"./cipher-core":50,"./core":51}],56:[function(require,module,exports){ +},{"./cipher-core":51,"./core":52}],57:[function(require,module,exports){ ;(function (root, factory) { if (typeof exports === "object") { // CommonJS @@ -8739,7 +8906,7 @@ module.exports = transfer; })); -},{"./core":51}],57:[function(require,module,exports){ +},{"./core":52}],58:[function(require,module,exports){ ;(function (root, factory, undef) { if (typeof exports === "object") { // CommonJS @@ -8758,7 +8925,7 @@ module.exports = transfer; return CryptoJS; })); -},{"./aes":49,"./cipher-core":50,"./core":51,"./enc-base64":52,"./enc-utf16":53,"./evpkdf":54,"./format-hex":55,"./hmac":56,"./lib-typedarrays":58,"./md5":59,"./mode-cfb":60,"./mode-ctr":62,"./mode-ctr-gladman":61,"./mode-ecb":63,"./mode-ofb":64,"./pad-ansix923":65,"./pad-iso10126":66,"./pad-iso97971":67,"./pad-nopadding":68,"./pad-zeropadding":69,"./pbkdf2":70,"./rabbit":72,"./rabbit-legacy":71,"./rc4":73,"./ripemd160":74,"./sha1":75,"./sha224":76,"./sha256":77,"./sha3":78,"./sha384":79,"./sha512":80,"./tripledes":81,"./x64-core":82}],58:[function(require,module,exports){ +},{"./aes":50,"./cipher-core":51,"./core":52,"./enc-base64":53,"./enc-utf16":54,"./evpkdf":55,"./format-hex":56,"./hmac":57,"./lib-typedarrays":59,"./md5":60,"./mode-cfb":61,"./mode-ctr":63,"./mode-ctr-gladman":62,"./mode-ecb":64,"./mode-ofb":65,"./pad-ansix923":66,"./pad-iso10126":67,"./pad-iso97971":68,"./pad-nopadding":69,"./pad-zeropadding":70,"./pbkdf2":71,"./rabbit":73,"./rabbit-legacy":72,"./rc4":74,"./ripemd160":75,"./sha1":76,"./sha224":77,"./sha256":78,"./sha3":79,"./sha384":80,"./sha512":81,"./tripledes":82,"./x64-core":83}],59:[function(require,module,exports){ ;(function (root, factory) { if (typeof exports === "object") { // CommonJS @@ -8835,7 +9002,7 @@ module.exports = transfer; return CryptoJS.lib.WordArray; })); -},{"./core":51}],59:[function(require,module,exports){ +},{"./core":52}],60:[function(require,module,exports){ ;(function (root, factory) { if (typeof exports === "object") { // CommonJS @@ -9104,7 +9271,7 @@ module.exports = transfer; return CryptoJS.MD5; })); -},{"./core":51}],60:[function(require,module,exports){ +},{"./core":52}],61:[function(require,module,exports){ ;(function (root, factory, undef) { if (typeof exports === "object") { // CommonJS @@ -9183,7 +9350,7 @@ module.exports = transfer; return CryptoJS.mode.CFB; })); -},{"./cipher-core":50,"./core":51}],61:[function(require,module,exports){ +},{"./cipher-core":51,"./core":52}],62:[function(require,module,exports){ ;(function (root, factory, undef) { if (typeof exports === "object") { // CommonJS @@ -9300,7 +9467,7 @@ module.exports = transfer; return CryptoJS.mode.CTRGladman; })); -},{"./cipher-core":50,"./core":51}],62:[function(require,module,exports){ +},{"./cipher-core":51,"./core":52}],63:[function(require,module,exports){ ;(function (root, factory, undef) { if (typeof exports === "object") { // CommonJS @@ -9359,7 +9526,7 @@ module.exports = transfer; return CryptoJS.mode.CTR; })); -},{"./cipher-core":50,"./core":51}],63:[function(require,module,exports){ +},{"./cipher-core":51,"./core":52}],64:[function(require,module,exports){ ;(function (root, factory, undef) { if (typeof exports === "object") { // CommonJS @@ -9400,7 +9567,7 @@ module.exports = transfer; return CryptoJS.mode.ECB; })); -},{"./cipher-core":50,"./core":51}],64:[function(require,module,exports){ +},{"./cipher-core":51,"./core":52}],65:[function(require,module,exports){ ;(function (root, factory, undef) { if (typeof exports === "object") { // CommonJS @@ -9455,7 +9622,7 @@ module.exports = transfer; return CryptoJS.mode.OFB; })); -},{"./cipher-core":50,"./core":51}],65:[function(require,module,exports){ +},{"./cipher-core":51,"./core":52}],66:[function(require,module,exports){ ;(function (root, factory, undef) { if (typeof exports === "object") { // CommonJS @@ -9505,7 +9672,7 @@ module.exports = transfer; return CryptoJS.pad.Ansix923; })); -},{"./cipher-core":50,"./core":51}],66:[function(require,module,exports){ +},{"./cipher-core":51,"./core":52}],67:[function(require,module,exports){ ;(function (root, factory, undef) { if (typeof exports === "object") { // CommonJS @@ -9550,7 +9717,7 @@ module.exports = transfer; return CryptoJS.pad.Iso10126; })); -},{"./cipher-core":50,"./core":51}],67:[function(require,module,exports){ +},{"./cipher-core":51,"./core":52}],68:[function(require,module,exports){ ;(function (root, factory, undef) { if (typeof exports === "object") { // CommonJS @@ -9591,7 +9758,7 @@ module.exports = transfer; return CryptoJS.pad.Iso97971; })); -},{"./cipher-core":50,"./core":51}],68:[function(require,module,exports){ +},{"./cipher-core":51,"./core":52}],69:[function(require,module,exports){ ;(function (root, factory, undef) { if (typeof exports === "object") { // CommonJS @@ -9622,7 +9789,7 @@ module.exports = transfer; return CryptoJS.pad.NoPadding; })); -},{"./cipher-core":50,"./core":51}],69:[function(require,module,exports){ +},{"./cipher-core":51,"./core":52}],70:[function(require,module,exports){ ;(function (root, factory, undef) { if (typeof exports === "object") { // CommonJS @@ -9668,7 +9835,7 @@ module.exports = transfer; return CryptoJS.pad.ZeroPadding; })); -},{"./cipher-core":50,"./core":51}],70:[function(require,module,exports){ +},{"./cipher-core":51,"./core":52}],71:[function(require,module,exports){ ;(function (root, factory, undef) { if (typeof exports === "object") { // CommonJS @@ -9814,7 +9981,7 @@ module.exports = transfer; return CryptoJS.PBKDF2; })); -},{"./core":51,"./hmac":56,"./sha1":75}],71:[function(require,module,exports){ +},{"./core":52,"./hmac":57,"./sha1":76}],72:[function(require,module,exports){ ;(function (root, factory, undef) { if (typeof exports === "object") { // CommonJS @@ -10005,7 +10172,7 @@ module.exports = transfer; return CryptoJS.RabbitLegacy; })); -},{"./cipher-core":50,"./core":51,"./enc-base64":52,"./evpkdf":54,"./md5":59}],72:[function(require,module,exports){ +},{"./cipher-core":51,"./core":52,"./enc-base64":53,"./evpkdf":55,"./md5":60}],73:[function(require,module,exports){ ;(function (root, factory, undef) { if (typeof exports === "object") { // CommonJS @@ -10198,7 +10365,7 @@ module.exports = transfer; return CryptoJS.Rabbit; })); -},{"./cipher-core":50,"./core":51,"./enc-base64":52,"./evpkdf":54,"./md5":59}],73:[function(require,module,exports){ +},{"./cipher-core":51,"./core":52,"./enc-base64":53,"./evpkdf":55,"./md5":60}],74:[function(require,module,exports){ ;(function (root, factory, undef) { if (typeof exports === "object") { // CommonJS @@ -10338,7 +10505,7 @@ module.exports = transfer; return CryptoJS.RC4; })); -},{"./cipher-core":50,"./core":51,"./enc-base64":52,"./evpkdf":54,"./md5":59}],74:[function(require,module,exports){ +},{"./cipher-core":51,"./core":52,"./enc-base64":53,"./evpkdf":55,"./md5":60}],75:[function(require,module,exports){ ;(function (root, factory) { if (typeof exports === "object") { // CommonJS @@ -10606,7 +10773,7 @@ module.exports = transfer; return CryptoJS.RIPEMD160; })); -},{"./core":51}],75:[function(require,module,exports){ +},{"./core":52}],76:[function(require,module,exports){ ;(function (root, factory) { if (typeof exports === "object") { // CommonJS @@ -10757,7 +10924,7 @@ module.exports = transfer; return CryptoJS.SHA1; })); -},{"./core":51}],76:[function(require,module,exports){ +},{"./core":52}],77:[function(require,module,exports){ ;(function (root, factory, undef) { if (typeof exports === "object") { // CommonJS @@ -10838,7 +11005,7 @@ module.exports = transfer; return CryptoJS.SHA224; })); -},{"./core":51,"./sha256":77}],77:[function(require,module,exports){ +},{"./core":52,"./sha256":78}],78:[function(require,module,exports){ ;(function (root, factory) { if (typeof exports === "object") { // CommonJS @@ -11038,7 +11205,7 @@ module.exports = transfer; return CryptoJS.SHA256; })); -},{"./core":51}],78:[function(require,module,exports){ +},{"./core":52}],79:[function(require,module,exports){ ;(function (root, factory, undef) { if (typeof exports === "object") { // CommonJS @@ -11362,7 +11529,7 @@ module.exports = transfer; return CryptoJS.SHA3; })); -},{"./core":51,"./x64-core":82}],79:[function(require,module,exports){ +},{"./core":52,"./x64-core":83}],80:[function(require,module,exports){ ;(function (root, factory, undef) { if (typeof exports === "object") { // CommonJS @@ -11446,7 +11613,7 @@ module.exports = transfer; return CryptoJS.SHA384; })); -},{"./core":51,"./sha512":80,"./x64-core":82}],80:[function(require,module,exports){ +},{"./core":52,"./sha512":81,"./x64-core":83}],81:[function(require,module,exports){ ;(function (root, factory, undef) { if (typeof exports === "object") { // CommonJS @@ -11770,7 +11937,7 @@ module.exports = transfer; return CryptoJS.SHA512; })); -},{"./core":51,"./x64-core":82}],81:[function(require,module,exports){ +},{"./core":52,"./x64-core":83}],82:[function(require,module,exports){ ;(function (root, factory, undef) { if (typeof exports === "object") { // CommonJS @@ -12541,7 +12708,7 @@ module.exports = transfer; return CryptoJS.TripleDES; })); -},{"./cipher-core":50,"./core":51,"./enc-base64":52,"./evpkdf":54,"./md5":59}],82:[function(require,module,exports){ +},{"./cipher-core":51,"./core":52,"./enc-base64":53,"./evpkdf":55,"./md5":60}],83:[function(require,module,exports){ ;(function (root, factory) { if (typeof exports === "object") { // CommonJS @@ -12846,15 +13013,19 @@ module.exports = transfer; return CryptoJS; })); -},{"./core":51}],83:[function(require,module,exports){ +},{"./core":52}],84:[function(require,module,exports){ /*! https://mths.be/utf8js v2.0.0 by @mathias */ ;(function(root) { + // Detect free variables exports var freeExports = typeof exports == 'object' && exports; + // Detect free variable module var freeModule = typeof module == 'object' && module && module.exports == freeExports && module; + // Detect free variable global, from Node.js or Browserified code, + // and use it as root var freeGlobal = typeof global == 'object' && global; if (freeGlobal.global === freeGlobal || freeGlobal.window === freeGlobal) { root = freeGlobal; @@ -13089,12 +13260,2691 @@ module.exports = transfer; }(this)); },{}],"bignumber.js":[function(require,module,exports){ -'use strict'; +/*! bignumber.js v2.0.7 https://github.com/MikeMcl/bignumber.js/LICENCE */ + +;(function (global) { + 'use strict'; + + /* + bignumber.js v2.0.7 + A JavaScript library for arbitrary-precision arithmetic. + https://github.com/MikeMcl/bignumber.js + Copyright (c) 2015 Michael Mclaughlin + MIT Expat Licence + */ + + + var BigNumber, crypto, parseNumeric, + isNumeric = /^-?(\d+(\.\d*)?|\.\d+)(e[+-]?\d+)?$/i, + mathceil = Math.ceil, + mathfloor = Math.floor, + notBool = ' not a boolean or binary digit', + roundingMode = 'rounding mode', + tooManyDigits = 'number type has more than 15 significant digits', + ALPHABET = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ$_', + BASE = 1e14, + LOG_BASE = 14, + MAX_SAFE_INTEGER = 0x1fffffffffffff, // 2^53 - 1 + // MAX_INT32 = 0x7fffffff, // 2^31 - 1 + POWS_TEN = [1, 10, 100, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9, 1e10, 1e11, 1e12, 1e13], + SQRT_BASE = 1e7, + + /* + * The limit on the value of DECIMAL_PLACES, TO_EXP_NEG, TO_EXP_POS, MIN_EXP, MAX_EXP, and + * the arguments to toExponential, toFixed, toFormat, and toPrecision, beyond which an + * exception is thrown (if ERRORS is true). + */ + MAX = 1E9; // 0 to MAX_INT32 + + + /* + * Create and return a BigNumber constructor. + */ + function another(configObj) { + var div, + + // id tracks the caller function, so its name can be included in error messages. + id = 0, + P = BigNumber.prototype, + ONE = new BigNumber(1), + + + /********************************* EDITABLE DEFAULTS **********************************/ + + + /* + * The default values below must be integers within the inclusive ranges stated. + * The values can also be changed at run-time using BigNumber.config. + */ + + // The maximum number of decimal places for operations involving division. + DECIMAL_PLACES = 20, // 0 to MAX + + /* + * The rounding mode used when rounding to the above decimal places, and when using + * toExponential, toFixed, toFormat and toPrecision, and round (default value). + * UP 0 Away from zero. + * DOWN 1 Towards zero. + * CEIL 2 Towards +Infinity. + * FLOOR 3 Towards -Infinity. + * HALF_UP 4 Towards nearest neighbour. If equidistant, up. + * HALF_DOWN 5 Towards nearest neighbour. If equidistant, down. + * HALF_EVEN 6 Towards nearest neighbour. If equidistant, towards even neighbour. + * HALF_CEIL 7 Towards nearest neighbour. If equidistant, towards +Infinity. + * HALF_FLOOR 8 Towards nearest neighbour. If equidistant, towards -Infinity. + */ + ROUNDING_MODE = 4, // 0 to 8 + + // EXPONENTIAL_AT : [TO_EXP_NEG , TO_EXP_POS] + + // The exponent value at and beneath which toString returns exponential notation. + // Number type: -7 + TO_EXP_NEG = -7, // 0 to -MAX + + // The exponent value at and above which toString returns exponential notation. + // Number type: 21 + TO_EXP_POS = 21, // 0 to MAX + + // RANGE : [MIN_EXP, MAX_EXP] + + // The minimum exponent value, beneath which underflow to zero occurs. + // Number type: -324 (5e-324) + MIN_EXP = -1e7, // -1 to -MAX + + // The maximum exponent value, above which overflow to Infinity occurs. + // Number type: 308 (1.7976931348623157e+308) + // For MAX_EXP > 1e7, e.g. new BigNumber('1e100000000').plus(1) may be slow. + MAX_EXP = 1e7, // 1 to MAX + + // Whether BigNumber Errors are ever thrown. + ERRORS = true, // true or false + + // Change to intValidatorNoErrors if ERRORS is false. + isValidInt = intValidatorWithErrors, // intValidatorWithErrors/intValidatorNoErrors + + // Whether to use cryptographically-secure random number generation, if available. + CRYPTO = false, // true or false + + /* + * The modulo mode used when calculating the modulus: a mod n. + * The quotient (q = a / n) is calculated according to the corresponding rounding mode. + * The remainder (r) is calculated as: r = a - n * q. + * + * UP 0 The remainder is positive if the dividend is negative, else is negative. + * DOWN 1 The remainder has the same sign as the dividend. + * This modulo mode is commonly known as 'truncated division' and is + * equivalent to (a % n) in JavaScript. + * FLOOR 3 The remainder has the same sign as the divisor (Python %). + * HALF_EVEN 6 This modulo mode implements the IEEE 754 remainder function. + * EUCLID 9 Euclidian division. q = sign(n) * floor(a / abs(n)). + * The remainder is always positive. + * + * The truncated division, floored division, Euclidian division and IEEE 754 remainder + * modes are commonly used for the modulus operation. + * Although the other rounding modes can also be used, they may not give useful results. + */ + MODULO_MODE = 1, // 0 to 9 + + // The maximum number of significant digits of the result of the toPower operation. + // If POW_PRECISION is 0, there will be unlimited significant digits. + POW_PRECISION = 100, // 0 to MAX + + // The format specification used by the BigNumber.prototype.toFormat method. + FORMAT = { + decimalSeparator: '.', + groupSeparator: ',', + groupSize: 3, + secondaryGroupSize: 0, + fractionGroupSeparator: '\xA0', // non-breaking space + fractionGroupSize: 0 + }; + + + /******************************************************************************************/ + + + // CONSTRUCTOR + + + /* + * The BigNumber constructor and exported function. + * Create and return a new instance of a BigNumber object. + * + * n {number|string|BigNumber} A numeric value. + * [b] {number} The base of n. Integer, 2 to 64 inclusive. + */ + function BigNumber( n, b ) { + var c, e, i, num, len, str, + x = this; + + // Enable constructor usage without new. + if ( !( x instanceof BigNumber ) ) { + + // 'BigNumber() constructor call without new: {n}' + if (ERRORS) raise( 26, 'constructor call without new', n ); + return new BigNumber( n, b ); + } + + // 'new BigNumber() base not an integer: {b}' + // 'new BigNumber() base out of range: {b}' + if ( b == null || !isValidInt( b, 2, 64, id, 'base' ) ) { + + // Duplicate. + if ( n instanceof BigNumber ) { + x.s = n.s; + x.e = n.e; + x.c = ( n = n.c ) ? n.slice() : n; + id = 0; + return; + } + + if ( ( num = typeof n == 'number' ) && n * 0 == 0 ) { + x.s = 1 / n < 0 ? ( n = -n, -1 ) : 1; + + // Fast path for integers. + if ( n === ~~n ) { + for ( e = 0, i = n; i >= 10; i /= 10, e++ ); + x.e = e; + x.c = [n]; + id = 0; + return; + } + + str = n + ''; + } else { + if ( !isNumeric.test( str = n + '' ) ) return parseNumeric( x, str, num ); + x.s = str.charCodeAt(0) === 45 ? ( str = str.slice(1), -1 ) : 1; + } + } else { + b = b | 0; + str = n + ''; + + // Ensure return value is rounded to DECIMAL_PLACES as with other bases. + // Allow exponential notation to be used with base 10 argument. + if ( b == 10 ) { + x = new BigNumber( n instanceof BigNumber ? n : str ); + return round( x, DECIMAL_PLACES + x.e + 1, ROUNDING_MODE ); + } + + // Avoid potential interpretation of Infinity and NaN as base 44+ values. + // Any number in exponential form will fail due to the [Ee][+-]. + if ( ( num = typeof n == 'number' ) && n * 0 != 0 || + !( new RegExp( '^-?' + ( c = '[' + ALPHABET.slice( 0, b ) + ']+' ) + + '(?:\\.' + c + ')?$',b < 37 ? 'i' : '' ) ).test(str) ) { + return parseNumeric( x, str, num, b ); + } + + if (num) { + x.s = 1 / n < 0 ? ( str = str.slice(1), -1 ) : 1; + + if ( ERRORS && str.replace( /^0\.0*|\./, '' ).length > 15 ) { + + // 'new BigNumber() number type has more than 15 significant digits: {n}' + raise( id, tooManyDigits, n ); + } + + // Prevent later check for length on converted number. + num = false; + } else { + x.s = str.charCodeAt(0) === 45 ? ( str = str.slice(1), -1 ) : 1; + } + + str = convertBase( str, 10, b, x.s ); + } + + // Decimal point? + if ( ( e = str.indexOf('.') ) > -1 ) str = str.replace( '.', '' ); + + // Exponential form? + if ( ( i = str.search( /e/i ) ) > 0 ) { + + // Determine exponent. + if ( e < 0 ) e = i; + e += +str.slice( i + 1 ); + str = str.substring( 0, i ); + } else if ( e < 0 ) { + + // Integer. + e = str.length; + } + + // Determine leading zeros. + for ( i = 0; str.charCodeAt(i) === 48; i++ ); + + // Determine trailing zeros. + for ( len = str.length; str.charCodeAt(--len) === 48; ); + str = str.slice( i, len + 1 ); + + if (str) { + len = str.length; + + // Disallow numbers with over 15 significant digits if number type. + // 'new BigNumber() number type has more than 15 significant digits: {n}' + if ( num && ERRORS && len > 15 ) raise( id, tooManyDigits, x.s * n ); + + e = e - i - 1; + + // Overflow? + if ( e > MAX_EXP ) { + + // Infinity. + x.c = x.e = null; + + // Underflow? + } else if ( e < MIN_EXP ) { + + // Zero. + x.c = [ x.e = 0 ]; + } else { + x.e = e; + x.c = []; + + // Transform base + + // e is the base 10 exponent. + // i is where to slice str to get the first element of the coefficient array. + i = ( e + 1 ) % LOG_BASE; + if ( e < 0 ) i += LOG_BASE; + + if ( i < len ) { + if (i) x.c.push( +str.slice( 0, i ) ); + + for ( len -= LOG_BASE; i < len; ) { + x.c.push( +str.slice( i, i += LOG_BASE ) ); + } + + str = str.slice(i); + i = LOG_BASE - str.length; + } else { + i -= len; + } + + for ( ; i--; str += '0' ); + x.c.push( +str ); + } + } else { + + // Zero. + x.c = [ x.e = 0 ]; + } + + id = 0; + } + + + // CONSTRUCTOR PROPERTIES + + + BigNumber.another = another; + + BigNumber.ROUND_UP = 0; + BigNumber.ROUND_DOWN = 1; + BigNumber.ROUND_CEIL = 2; + BigNumber.ROUND_FLOOR = 3; + BigNumber.ROUND_HALF_UP = 4; + BigNumber.ROUND_HALF_DOWN = 5; + BigNumber.ROUND_HALF_EVEN = 6; + BigNumber.ROUND_HALF_CEIL = 7; + BigNumber.ROUND_HALF_FLOOR = 8; + BigNumber.EUCLID = 9; + + + /* + * Configure infrequently-changing library-wide settings. + * + * Accept an object or an argument list, with one or many of the following properties or + * parameters respectively: + * + * DECIMAL_PLACES {number} Integer, 0 to MAX inclusive + * ROUNDING_MODE {number} Integer, 0 to 8 inclusive + * EXPONENTIAL_AT {number|number[]} Integer, -MAX to MAX inclusive or + * [integer -MAX to 0 incl., 0 to MAX incl.] + * RANGE {number|number[]} Non-zero integer, -MAX to MAX inclusive or + * [integer -MAX to -1 incl., integer 1 to MAX incl.] + * ERRORS {boolean|number} true, false, 1 or 0 + * CRYPTO {boolean|number} true, false, 1 or 0 + * MODULO_MODE {number} 0 to 9 inclusive + * POW_PRECISION {number} 0 to MAX inclusive + * FORMAT {object} See BigNumber.prototype.toFormat + * decimalSeparator {string} + * groupSeparator {string} + * groupSize {number} + * secondaryGroupSize {number} + * fractionGroupSeparator {string} + * fractionGroupSize {number} + * + * (The values assigned to the above FORMAT object properties are not checked for validity.) + * + * E.g. + * BigNumber.config(20, 4) is equivalent to + * BigNumber.config({ DECIMAL_PLACES : 20, ROUNDING_MODE : 4 }) + * + * Ignore properties/parameters set to null or undefined. + * Return an object with the properties current values. + */ + BigNumber.config = function () { + var v, p, + i = 0, + r = {}, + a = arguments, + o = a[0], + has = o && typeof o == 'object' + ? function () { if ( o.hasOwnProperty(p) ) return ( v = o[p] ) != null; } + : function () { if ( a.length > i ) return ( v = a[i++] ) != null; }; + + // DECIMAL_PLACES {number} Integer, 0 to MAX inclusive. + // 'config() DECIMAL_PLACES not an integer: {v}' + // 'config() DECIMAL_PLACES out of range: {v}' + if ( has( p = 'DECIMAL_PLACES' ) && isValidInt( v, 0, MAX, 2, p ) ) { + DECIMAL_PLACES = v | 0; + } + r[p] = DECIMAL_PLACES; + + // ROUNDING_MODE {number} Integer, 0 to 8 inclusive. + // 'config() ROUNDING_MODE not an integer: {v}' + // 'config() ROUNDING_MODE out of range: {v}' + if ( has( p = 'ROUNDING_MODE' ) && isValidInt( v, 0, 8, 2, p ) ) { + ROUNDING_MODE = v | 0; + } + r[p] = ROUNDING_MODE; + + // EXPONENTIAL_AT {number|number[]} + // Integer, -MAX to MAX inclusive or [integer -MAX to 0 inclusive, 0 to MAX inclusive]. + // 'config() EXPONENTIAL_AT not an integer: {v}' + // 'config() EXPONENTIAL_AT out of range: {v}' + if ( has( p = 'EXPONENTIAL_AT' ) ) { + + if ( isArray(v) ) { + if ( isValidInt( v[0], -MAX, 0, 2, p ) && isValidInt( v[1], 0, MAX, 2, p ) ) { + TO_EXP_NEG = v[0] | 0; + TO_EXP_POS = v[1] | 0; + } + } else if ( isValidInt( v, -MAX, MAX, 2, p ) ) { + TO_EXP_NEG = -( TO_EXP_POS = ( v < 0 ? -v : v ) | 0 ); + } + } + r[p] = [ TO_EXP_NEG, TO_EXP_POS ]; + + // RANGE {number|number[]} Non-zero integer, -MAX to MAX inclusive or + // [integer -MAX to -1 inclusive, integer 1 to MAX inclusive]. + // 'config() RANGE not an integer: {v}' + // 'config() RANGE cannot be zero: {v}' + // 'config() RANGE out of range: {v}' + if ( has( p = 'RANGE' ) ) { + + if ( isArray(v) ) { + if ( isValidInt( v[0], -MAX, -1, 2, p ) && isValidInt( v[1], 1, MAX, 2, p ) ) { + MIN_EXP = v[0] | 0; + MAX_EXP = v[1] | 0; + } + } else if ( isValidInt( v, -MAX, MAX, 2, p ) ) { + if ( v | 0 ) MIN_EXP = -( MAX_EXP = ( v < 0 ? -v : v ) | 0 ); + else if (ERRORS) raise( 2, p + ' cannot be zero', v ); + } + } + r[p] = [ MIN_EXP, MAX_EXP ]; + + // ERRORS {boolean|number} true, false, 1 or 0. + // 'config() ERRORS not a boolean or binary digit: {v}' + if ( has( p = 'ERRORS' ) ) { + + if ( v === !!v || v === 1 || v === 0 ) { + id = 0; + isValidInt = ( ERRORS = !!v ) ? intValidatorWithErrors : intValidatorNoErrors; + } else if (ERRORS) { + raise( 2, p + notBool, v ); + } + } + r[p] = ERRORS; + + // CRYPTO {boolean|number} true, false, 1 or 0. + // 'config() CRYPTO not a boolean or binary digit: {v}' + // 'config() crypto unavailable: {crypto}' + if ( has( p = 'CRYPTO' ) ) { + + if ( v === !!v || v === 1 || v === 0 ) { + CRYPTO = !!( v && crypto && typeof crypto == 'object' ); + if ( v && !CRYPTO && ERRORS ) raise( 2, 'crypto unavailable', crypto ); + } else if (ERRORS) { + raise( 2, p + notBool, v ); + } + } + r[p] = CRYPTO; + + // MODULO_MODE {number} Integer, 0 to 9 inclusive. + // 'config() MODULO_MODE not an integer: {v}' + // 'config() MODULO_MODE out of range: {v}' + if ( has( p = 'MODULO_MODE' ) && isValidInt( v, 0, 9, 2, p ) ) { + MODULO_MODE = v | 0; + } + r[p] = MODULO_MODE; + + // POW_PRECISION {number} Integer, 0 to MAX inclusive. + // 'config() POW_PRECISION not an integer: {v}' + // 'config() POW_PRECISION out of range: {v}' + if ( has( p = 'POW_PRECISION' ) && isValidInt( v, 0, MAX, 2, p ) ) { + POW_PRECISION = v | 0; + } + r[p] = POW_PRECISION; + + // FORMAT {object} + // 'config() FORMAT not an object: {v}' + if ( has( p = 'FORMAT' ) ) { + + if ( typeof v == 'object' ) { + FORMAT = v; + } else if (ERRORS) { + raise( 2, p + ' not an object', v ); + } + } + r[p] = FORMAT; + + return r; + }; + + + /* + * Return a new BigNumber whose value is the maximum of the arguments. + * + * arguments {number|string|BigNumber} + */ + BigNumber.max = function () { return maxOrMin( arguments, P.lt ); }; + + + /* + * Return a new BigNumber whose value is the minimum of the arguments. + * + * arguments {number|string|BigNumber} + */ + BigNumber.min = function () { return maxOrMin( arguments, P.gt ); }; + + + /* + * Return a new BigNumber with a random value equal to or greater than 0 and less than 1, + * and with dp, or DECIMAL_PLACES if dp is omitted, decimal places (or less if trailing + * zeros are produced). + * + * [dp] {number} Decimal places. Integer, 0 to MAX inclusive. + * + * 'random() decimal places not an integer: {dp}' + * 'random() decimal places out of range: {dp}' + * 'random() crypto unavailable: {crypto}' + */ + BigNumber.random = (function () { + var pow2_53 = 0x20000000000000; + + // Return a 53 bit integer n, where 0 <= n < 9007199254740992. + // Check if Math.random() produces more than 32 bits of randomness. + // If it does, assume at least 53 bits are produced, otherwise assume at least 30 bits. + // 0x40000000 is 2^30, 0x800000 is 2^23, 0x1fffff is 2^21 - 1. + var random53bitInt = (Math.random() * pow2_53) & 0x1fffff + ? function () { return mathfloor( Math.random() * pow2_53 ); } + : function () { return ((Math.random() * 0x40000000 | 0) * 0x800000) + + (Math.random() * 0x800000 | 0); }; + + return function (dp) { + var a, b, e, k, v, + i = 0, + c = [], + rand = new BigNumber(ONE); + + dp = dp == null || !isValidInt( dp, 0, MAX, 14 ) ? DECIMAL_PLACES : dp | 0; + k = mathceil( dp / LOG_BASE ); + + if (CRYPTO) { + + // Browsers supporting crypto.getRandomValues. + if ( crypto && crypto.getRandomValues ) { + + a = crypto.getRandomValues( new Uint32Array( k *= 2 ) ); + + for ( ; i < k; ) { + + // 53 bits: + // ((Math.pow(2, 32) - 1) * Math.pow(2, 21)).toString(2) + // 11111 11111111 11111111 11111111 11100000 00000000 00000000 + // ((Math.pow(2, 32) - 1) >>> 11).toString(2) + // 11111 11111111 11111111 + // 0x20000 is 2^21. + v = a[i] * 0x20000 + (a[i + 1] >>> 11); + + // Rejection sampling: + // 0 <= v < 9007199254740992 + // Probability that v >= 9e15, is + // 7199254740992 / 9007199254740992 ~= 0.0008, i.e. 1 in 1251 + if ( v >= 9e15 ) { + b = crypto.getRandomValues( new Uint32Array(2) ); + a[i] = b[0]; + a[i + 1] = b[1]; + } else { + + // 0 <= v <= 8999999999999999 + // 0 <= (v % 1e14) <= 99999999999999 + c.push( v % 1e14 ); + i += 2; + } + } + i = k / 2; + + // Node.js supporting crypto.randomBytes. + } else if ( crypto && crypto.randomBytes ) { + + // buffer + a = crypto.randomBytes( k *= 7 ); + + for ( ; i < k; ) { + + // 0x1000000000000 is 2^48, 0x10000000000 is 2^40 + // 0x100000000 is 2^32, 0x1000000 is 2^24 + // 11111 11111111 11111111 11111111 11111111 11111111 11111111 + // 0 <= v < 9007199254740992 + v = ( ( a[i] & 31 ) * 0x1000000000000 ) + ( a[i + 1] * 0x10000000000 ) + + ( a[i + 2] * 0x100000000 ) + ( a[i + 3] * 0x1000000 ) + + ( a[i + 4] << 16 ) + ( a[i + 5] << 8 ) + a[i + 6]; + + if ( v >= 9e15 ) { + crypto.randomBytes(7).copy( a, i ); + } else { + + // 0 <= (v % 1e14) <= 99999999999999 + c.push( v % 1e14 ); + i += 7; + } + } + i = k / 7; + } else if (ERRORS) { + raise( 14, 'crypto unavailable', crypto ); + } + } + + // Use Math.random: CRYPTO is false or crypto is unavailable and ERRORS is false. + if (!i) { + + for ( ; i < k; ) { + v = random53bitInt(); + if ( v < 9e15 ) c[i++] = v % 1e14; + } + } + + k = c[--i]; + dp %= LOG_BASE; + + // Convert trailing digits to zeros according to dp. + if ( k && dp ) { + v = POWS_TEN[LOG_BASE - dp]; + c[i] = mathfloor( k / v ) * v; + } + + // Remove trailing elements which are zero. + for ( ; c[i] === 0; c.pop(), i-- ); + + // Zero? + if ( i < 0 ) { + c = [ e = 0 ]; + } else { + + // Remove leading elements which are zero and adjust exponent accordingly. + for ( e = -1 ; c[0] === 0; c.shift(), e -= LOG_BASE); -module.exports = BigNumber; // jshint ignore:line + // Count the digits of the first element of c to determine leading zeros, and... + for ( i = 1, v = c[0]; v >= 10; v /= 10, i++); + // adjust the exponent accordingly. + if ( i < LOG_BASE ) e -= LOG_BASE - i; + } + + rand.e = e; + rand.c = c; + return rand; + }; + })(); + + + // PRIVATE FUNCTIONS + + + // Convert a numeric string of baseIn to a numeric string of baseOut. + function convertBase( str, baseOut, baseIn, sign ) { + var d, e, k, r, x, xc, y, + i = str.indexOf( '.' ), + dp = DECIMAL_PLACES, + rm = ROUNDING_MODE; + + if ( baseIn < 37 ) str = str.toLowerCase(); + + // Non-integer. + if ( i >= 0 ) { + k = POW_PRECISION; + + // Unlimited precision. + POW_PRECISION = 0; + str = str.replace( '.', '' ); + y = new BigNumber(baseIn); + x = y.pow( str.length - i ); + POW_PRECISION = k; + + // Convert str as if an integer, then restore the fraction part by dividing the + // result by its base raised to a power. + y.c = toBaseOut( toFixedPoint( coeffToString( x.c ), x.e ), 10, baseOut ); + y.e = y.c.length; + } + + // Convert the number as integer. + xc = toBaseOut( str, baseIn, baseOut ); + e = k = xc.length; + + // Remove trailing zeros. + for ( ; xc[--k] == 0; xc.pop() ); + if ( !xc[0] ) return '0'; + + if ( i < 0 ) { + --e; + } else { + x.c = xc; + x.e = e; + + // sign is needed for correct rounding. + x.s = sign; + x = div( x, y, dp, rm, baseOut ); + xc = x.c; + r = x.r; + e = x.e; + } + + d = e + dp + 1; + + // The rounding digit, i.e. the digit to the right of the digit that may be rounded up. + i = xc[d]; + k = baseOut / 2; + r = r || d < 0 || xc[d + 1] != null; + + r = rm < 4 ? ( i != null || r ) && ( rm == 0 || rm == ( x.s < 0 ? 3 : 2 ) ) + : i > k || i == k &&( rm == 4 || r || rm == 6 && xc[d - 1] & 1 || + rm == ( x.s < 0 ? 8 : 7 ) ); + + if ( d < 1 || !xc[0] ) { + + // 1^-dp or 0. + str = r ? toFixedPoint( '1', -dp ) : '0'; + } else { + xc.length = d; + + if (r) { + + // Rounding up may mean the previous digit has to be rounded up and so on. + for ( --baseOut; ++xc[--d] > baseOut; ) { + xc[d] = 0; + + if ( !d ) { + ++e; + xc.unshift(1); + } + } + } + + // Determine trailing zeros. + for ( k = xc.length; !xc[--k]; ); + + // E.g. [4, 11, 15] becomes 4bf. + for ( i = 0, str = ''; i <= k; str += ALPHABET.charAt( xc[i++] ) ); + str = toFixedPoint( str, e ); + } + + // The caller will add the sign. + return str; + } + + + // Perform division in the specified base. Called by div and convertBase. + div = (function () { + + // Assume non-zero x and k. + function multiply( x, k, base ) { + var m, temp, xlo, xhi, + carry = 0, + i = x.length, + klo = k % SQRT_BASE, + khi = k / SQRT_BASE | 0; + + for ( x = x.slice(); i--; ) { + xlo = x[i] % SQRT_BASE; + xhi = x[i] / SQRT_BASE | 0; + m = khi * xlo + xhi * klo; + temp = klo * xlo + ( ( m % SQRT_BASE ) * SQRT_BASE ) + carry; + carry = ( temp / base | 0 ) + ( m / SQRT_BASE | 0 ) + khi * xhi; + x[i] = temp % base; + } + + if (carry) x.unshift(carry); + + return x; + } + + function compare( a, b, aL, bL ) { + var i, cmp; + + if ( aL != bL ) { + cmp = aL > bL ? 1 : -1; + } else { + + for ( i = cmp = 0; i < aL; i++ ) { + + if ( a[i] != b[i] ) { + cmp = a[i] > b[i] ? 1 : -1; + break; + } + } + } + return cmp; + } + + function subtract( a, b, aL, base ) { + var i = 0; + + // Subtract b from a. + for ( ; aL--; ) { + a[aL] -= i; + i = a[aL] < b[aL] ? 1 : 0; + a[aL] = i * base + a[aL] - b[aL]; + } + + // Remove leading zeros. + for ( ; !a[0] && a.length > 1; a.shift() ); + } + + // x: dividend, y: divisor. + return function ( x, y, dp, rm, base ) { + var cmp, e, i, more, n, prod, prodL, q, qc, rem, remL, rem0, xi, xL, yc0, + yL, yz, + s = x.s == y.s ? 1 : -1, + xc = x.c, + yc = y.c; + + // Either NaN, Infinity or 0? + if ( !xc || !xc[0] || !yc || !yc[0] ) { + + return new BigNumber( + + // Return NaN if either NaN, or both Infinity or 0. + !x.s || !y.s || ( xc ? yc && xc[0] == yc[0] : !yc ) ? NaN : + + // Return ±0 if x is ±0 or y is ±Infinity, or return ±Infinity as y is ±0. + xc && xc[0] == 0 || !yc ? s * 0 : s / 0 + ); + } + + q = new BigNumber(s); + qc = q.c = []; + e = x.e - y.e; + s = dp + e + 1; + + if ( !base ) { + base = BASE; + e = bitFloor( x.e / LOG_BASE ) - bitFloor( y.e / LOG_BASE ); + s = s / LOG_BASE | 0; + } + + // Result exponent may be one less then the current value of e. + // The coefficients of the BigNumbers from convertBase may have trailing zeros. + for ( i = 0; yc[i] == ( xc[i] || 0 ); i++ ); + if ( yc[i] > ( xc[i] || 0 ) ) e--; + + if ( s < 0 ) { + qc.push(1); + more = true; + } else { + xL = xc.length; + yL = yc.length; + i = 0; + s += 2; + + // Normalise xc and yc so highest order digit of yc is >= base / 2. + + n = mathfloor( base / ( yc[0] + 1 ) ); + + // Not necessary, but to handle odd bases where yc[0] == ( base / 2 ) - 1. + // if ( n > 1 || n++ == 1 && yc[0] < base / 2 ) { + if ( n > 1 ) { + yc = multiply( yc, n, base ); + xc = multiply( xc, n, base ); + yL = yc.length; + xL = xc.length; + } + + xi = yL; + rem = xc.slice( 0, yL ); + remL = rem.length; + + // Add zeros to make remainder as long as divisor. + for ( ; remL < yL; rem[remL++] = 0 ); + yz = yc.slice(); + yz.unshift(0); + yc0 = yc[0]; + if ( yc[1] >= base / 2 ) yc0++; + // Not necessary, but to prevent trial digit n > base, when using base 3. + // else if ( base == 3 && yc0 == 1 ) yc0 = 1 + 1e-15; + + do { + n = 0; + + // Compare divisor and remainder. + cmp = compare( yc, rem, yL, remL ); + + // If divisor < remainder. + if ( cmp < 0 ) { + + // Calculate trial digit, n. + + rem0 = rem[0]; + if ( yL != remL ) rem0 = rem0 * base + ( rem[1] || 0 ); + + // n is how many times the divisor goes into the current remainder. + n = mathfloor( rem0 / yc0 ); + + // Algorithm: + // 1. product = divisor * trial digit (n) + // 2. if product > remainder: product -= divisor, n-- + // 3. remainder -= product + // 4. if product was < remainder at 2: + // 5. compare new remainder and divisor + // 6. If remainder > divisor: remainder -= divisor, n++ + + if ( n > 1 ) { + + // n may be > base only when base is 3. + if (n >= base) n = base - 1; + + // product = divisor * trial digit. + prod = multiply( yc, n, base ); + prodL = prod.length; + remL = rem.length; + + // Compare product and remainder. + // If product > remainder. + // Trial digit n too high. + // n is 1 too high about 5% of the time, and is not known to have + // ever been more than 1 too high. + while ( compare( prod, rem, prodL, remL ) == 1 ) { + n--; + + // Subtract divisor from product. + subtract( prod, yL < prodL ? yz : yc, prodL, base ); + prodL = prod.length; + cmp = 1; + } + } else { + + // n is 0 or 1, cmp is -1. + // If n is 0, there is no need to compare yc and rem again below, + // so change cmp to 1 to avoid it. + // If n is 1, leave cmp as -1, so yc and rem are compared again. + if ( n == 0 ) { + + // divisor < remainder, so n must be at least 1. + cmp = n = 1; + } + + // product = divisor + prod = yc.slice(); + prodL = prod.length; + } + + if ( prodL < remL ) prod.unshift(0); + + // Subtract product from remainder. + subtract( rem, prod, remL, base ); + remL = rem.length; + + // If product was < remainder. + if ( cmp == -1 ) { + + // Compare divisor and new remainder. + // If divisor < new remainder, subtract divisor from remainder. + // Trial digit n too low. + // n is 1 too low about 5% of the time, and very rarely 2 too low. + while ( compare( yc, rem, yL, remL ) < 1 ) { + n++; + + // Subtract divisor from remainder. + subtract( rem, yL < remL ? yz : yc, remL, base ); + remL = rem.length; + } + } + } else if ( cmp === 0 ) { + n++; + rem = [0]; + } // else cmp === 1 and n will be 0 + + // Add the next digit, n, to the result array. + qc[i++] = n; + + // Update the remainder. + if ( rem[0] ) { + rem[remL++] = xc[xi] || 0; + } else { + rem = [ xc[xi] ]; + remL = 1; + } + } while ( ( xi++ < xL || rem[0] != null ) && s-- ); + + more = rem[0] != null; + + // Leading zero? + if ( !qc[0] ) qc.shift(); + } + + if ( base == BASE ) { + + // To calculate q.e, first get the number of digits of qc[0]. + for ( i = 1, s = qc[0]; s >= 10; s /= 10, i++ ); + round( q, dp + ( q.e = i + e * LOG_BASE - 1 ) + 1, rm, more ); + + // Caller is convertBase. + } else { + q.e = e; + q.r = +more; + } + + return q; + }; + })(); + + + /* + * Return a string representing the value of BigNumber n in fixed-point or exponential + * notation rounded to the specified decimal places or significant digits. + * + * n is a BigNumber. + * i is the index of the last digit required (i.e. the digit that may be rounded up). + * rm is the rounding mode. + * caller is caller id: toExponential 19, toFixed 20, toFormat 21, toPrecision 24. + */ + function format( n, i, rm, caller ) { + var c0, e, ne, len, str; + + rm = rm != null && isValidInt( rm, 0, 8, caller, roundingMode ) + ? rm | 0 : ROUNDING_MODE; + + if ( !n.c ) return n.toString(); + c0 = n.c[0]; + ne = n.e; + + if ( i == null ) { + str = coeffToString( n.c ); + str = caller == 19 || caller == 24 && ne <= TO_EXP_NEG + ? toExponential( str, ne ) + : toFixedPoint( str, ne ); + } else { + n = round( new BigNumber(n), i, rm ); + + // n.e may have changed if the value was rounded up. + e = n.e; + + str = coeffToString( n.c ); + len = str.length; + + // toPrecision returns exponential notation if the number of significant digits + // specified is less than the number of digits necessary to represent the integer + // part of the value in fixed-point notation. + + // Exponential notation. + if ( caller == 19 || caller == 24 && ( i <= e || e <= TO_EXP_NEG ) ) { + + // Append zeros? + for ( ; len < i; str += '0', len++ ); + str = toExponential( str, e ); + + // Fixed-point notation. + } else { + i -= ne; + str = toFixedPoint( str, e ); + + // Append zeros? + if ( e + 1 > len ) { + if ( --i > 0 ) for ( str += '.'; i--; str += '0' ); + } else { + i += e - len; + if ( i > 0 ) { + if ( e + 1 == len ) str += '.'; + for ( ; i--; str += '0' ); + } + } + } + } + + return n.s < 0 && c0 ? '-' + str : str; + } + + + // Handle BigNumber.max and BigNumber.min. + function maxOrMin( args, method ) { + var m, n, + i = 0; + + if ( isArray( args[0] ) ) args = args[0]; + m = new BigNumber( args[0] ); + + for ( ; ++i < args.length; ) { + n = new BigNumber( args[i] ); + + // If any number is NaN, return NaN. + if ( !n.s ) { + m = n; + break; + } else if ( method.call( m, n ) ) { + m = n; + } + } + + return m; + } + + + /* + * Return true if n is an integer in range, otherwise throw. + * Use for argument validation when ERRORS is true. + */ + function intValidatorWithErrors( n, min, max, caller, name ) { + if ( n < min || n > max || n != truncate(n) ) { + raise( caller, ( name || 'decimal places' ) + + ( n < min || n > max ? ' out of range' : ' not an integer' ), n ); + } + + return true; + } + + + /* + * Strip trailing zeros, calculate base 10 exponent and check against MIN_EXP and MAX_EXP. + * Called by minus, plus and times. + */ + function normalise( n, c, e ) { + var i = 1, + j = c.length; + + // Remove trailing zeros. + for ( ; !c[--j]; c.pop() ); + + // Calculate the base 10 exponent. First get the number of digits of c[0]. + for ( j = c[0]; j >= 10; j /= 10, i++ ); + + // Overflow? + if ( ( e = i + e * LOG_BASE - 1 ) > MAX_EXP ) { + + // Infinity. + n.c = n.e = null; + + // Underflow? + } else if ( e < MIN_EXP ) { + + // Zero. + n.c = [ n.e = 0 ]; + } else { + n.e = e; + n.c = c; + } + + return n; + } + + + // Handle values that fail the validity test in BigNumber. + parseNumeric = (function () { + var basePrefix = /^(-?)0([xbo])/i, + dotAfter = /^([^.]+)\.$/, + dotBefore = /^\.([^.]+)$/, + isInfinityOrNaN = /^-?(Infinity|NaN)$/, + whitespaceOrPlus = /^\s*\+|^\s+|\s+$/g; + + return function ( x, str, num, b ) { + var base, + s = num ? str : str.replace( whitespaceOrPlus, '' ); + + // No exception on ±Infinity or NaN. + if ( isInfinityOrNaN.test(s) ) { + x.s = isNaN(s) ? null : s < 0 ? -1 : 1; + } else { + if ( !num ) { + + // basePrefix = /^(-?)0([xbo])(?=\w[\w.]*$)/i + s = s.replace( basePrefix, function ( m, p1, p2 ) { + base = ( p2 = p2.toLowerCase() ) == 'x' ? 16 : p2 == 'b' ? 2 : 8; + return !b || b == base ? p1 : m; + }); + + if (b) { + base = b; + + // E.g. '1.' to '1', '.1' to '0.1' + s = s.replace( dotAfter, '$1' ).replace( dotBefore, '0.$1' ); + } + + if ( str != s ) return new BigNumber( s, base ); + } + + // 'new BigNumber() not a number: {n}' + // 'new BigNumber() not a base {b} number: {n}' + if (ERRORS) raise( id, 'not a' + ( b ? ' base ' + b : '' ) + ' number', str ); + x.s = null; + } + + x.c = x.e = null; + id = 0; + } + })(); + + + // Throw a BigNumber Error. + function raise( caller, msg, val ) { + var error = new Error( [ + 'new BigNumber', // 0 + 'cmp', // 1 + 'config', // 2 + 'div', // 3 + 'divToInt', // 4 + 'eq', // 5 + 'gt', // 6 + 'gte', // 7 + 'lt', // 8 + 'lte', // 9 + 'minus', // 10 + 'mod', // 11 + 'plus', // 12 + 'precision', // 13 + 'random', // 14 + 'round', // 15 + 'shift', // 16 + 'times', // 17 + 'toDigits', // 18 + 'toExponential', // 19 + 'toFixed', // 20 + 'toFormat', // 21 + 'toFraction', // 22 + 'pow', // 23 + 'toPrecision', // 24 + 'toString', // 25 + 'BigNumber' // 26 + ][caller] + '() ' + msg + ': ' + val ); + + error.name = 'BigNumber Error'; + id = 0; + throw error; + } + + + /* + * Round x to sd significant digits using rounding mode rm. Check for over/under-flow. + * If r is truthy, it is known that there are more digits after the rounding digit. + */ + function round( x, sd, rm, r ) { + var d, i, j, k, n, ni, rd, + xc = x.c, + pows10 = POWS_TEN; + + // if x is not Infinity or NaN... + if (xc) { + + // rd is the rounding digit, i.e. the digit after the digit that may be rounded up. + // n is a base 1e14 number, the value of the element of array x.c containing rd. + // ni is the index of n within x.c. + // d is the number of digits of n. + // i is the index of rd within n including leading zeros. + // j is the actual index of rd within n (if < 0, rd is a leading zero). + out: { + + // Get the number of digits of the first element of xc. + for ( d = 1, k = xc[0]; k >= 10; k /= 10, d++ ); + i = sd - d; + + // If the rounding digit is in the first element of xc... + if ( i < 0 ) { + i += LOG_BASE; + j = sd; + n = xc[ ni = 0 ]; + + // Get the rounding digit at index j of n. + rd = n / pows10[ d - j - 1 ] % 10 | 0; + } else { + ni = mathceil( ( i + 1 ) / LOG_BASE ); + + if ( ni >= xc.length ) { + + if (r) { + + // Needed by sqrt. + for ( ; xc.length <= ni; xc.push(0) ); + n = rd = 0; + d = 1; + i %= LOG_BASE; + j = i - LOG_BASE + 1; + } else { + break out; + } + } else { + n = k = xc[ni]; + + // Get the number of digits of n. + for ( d = 1; k >= 10; k /= 10, d++ ); + + // Get the index of rd within n. + i %= LOG_BASE; + + // Get the index of rd within n, adjusted for leading zeros. + // The number of leading zeros of n is given by LOG_BASE - d. + j = i - LOG_BASE + d; + + // Get the rounding digit at index j of n. + rd = j < 0 ? 0 : n / pows10[ d - j - 1 ] % 10 | 0; + } + } + + r = r || sd < 0 || + + // Are there any non-zero digits after the rounding digit? + // The expression n % pows10[ d - j - 1 ] returns all digits of n to the right + // of the digit at j, e.g. if n is 908714 and j is 2, the expression gives 714. + xc[ni + 1] != null || ( j < 0 ? n : n % pows10[ d - j - 1 ] ); + + r = rm < 4 + ? ( rd || r ) && ( rm == 0 || rm == ( x.s < 0 ? 3 : 2 ) ) + : rd > 5 || rd == 5 && ( rm == 4 || r || rm == 6 && + + // Check whether the digit to the left of the rounding digit is odd. + ( ( i > 0 ? j > 0 ? n / pows10[ d - j ] : 0 : xc[ni - 1] ) % 10 ) & 1 || + rm == ( x.s < 0 ? 8 : 7 ) ); + + if ( sd < 1 || !xc[0] ) { + xc.length = 0; + + if (r) { + + // Convert sd to decimal places. + sd -= x.e + 1; + + // 1, 0.1, 0.01, 0.001, 0.0001 etc. + xc[0] = pows10[ sd % LOG_BASE ]; + x.e = -sd || 0; + } else { + + // Zero. + xc[0] = x.e = 0; + } + + return x; + } + + // Remove excess digits. + if ( i == 0 ) { + xc.length = ni; + k = 1; + ni--; + } else { + xc.length = ni + 1; + k = pows10[ LOG_BASE - i ]; + + // E.g. 56700 becomes 56000 if 7 is the rounding digit. + // j > 0 means i > number of leading zeros of n. + xc[ni] = j > 0 ? mathfloor( n / pows10[ d - j ] % pows10[j] ) * k : 0; + } + + // Round up? + if (r) { + + for ( ; ; ) { + + // If the digit to be rounded up is in the first element of xc... + if ( ni == 0 ) { + + // i will be the length of xc[0] before k is added. + for ( i = 1, j = xc[0]; j >= 10; j /= 10, i++ ); + j = xc[0] += k; + for ( k = 1; j >= 10; j /= 10, k++ ); + + // if i != k the length has increased. + if ( i != k ) { + x.e++; + if ( xc[0] == BASE ) xc[0] = 1; + } + + break; + } else { + xc[ni] += k; + if ( xc[ni] != BASE ) break; + xc[ni--] = 0; + k = 1; + } + } + } + + // Remove trailing zeros. + for ( i = xc.length; xc[--i] === 0; xc.pop() ); + } + + // Overflow? Infinity. + if ( x.e > MAX_EXP ) { + x.c = x.e = null; + + // Underflow? Zero. + } else if ( x.e < MIN_EXP ) { + x.c = [ x.e = 0 ]; + } + } + + return x; + } + + + // PROTOTYPE/INSTANCE METHODS + + + /* + * Return a new BigNumber whose value is the absolute value of this BigNumber. + */ + P.absoluteValue = P.abs = function () { + var x = new BigNumber(this); + if ( x.s < 0 ) x.s = 1; + return x; + }; + + + /* + * Return a new BigNumber whose value is the value of this BigNumber rounded to a whole + * number in the direction of Infinity. + */ + P.ceil = function () { + return round( new BigNumber(this), this.e + 1, 2 ); + }; + + + /* + * Return + * 1 if the value of this BigNumber is greater than the value of BigNumber(y, b), + * -1 if the value of this BigNumber is less than the value of BigNumber(y, b), + * 0 if they have the same value, + * or null if the value of either is NaN. + */ + P.comparedTo = P.cmp = function ( y, b ) { + id = 1; + return compare( this, new BigNumber( y, b ) ); + }; + + + /* + * Return the number of decimal places of the value of this BigNumber, or null if the value + * of this BigNumber is ±Infinity or NaN. + */ + P.decimalPlaces = P.dp = function () { + var n, v, + c = this.c; + + if ( !c ) return null; + n = ( ( v = c.length - 1 ) - bitFloor( this.e / LOG_BASE ) ) * LOG_BASE; + + // Subtract the number of trailing zeros of the last number. + if ( v = c[v] ) for ( ; v % 10 == 0; v /= 10, n-- ); + if ( n < 0 ) n = 0; + + return n; + }; + + + /* + * n / 0 = I + * n / N = N + * n / I = 0 + * 0 / n = 0 + * 0 / 0 = N + * 0 / N = N + * 0 / I = 0 + * N / n = N + * N / 0 = N + * N / N = N + * N / I = N + * I / n = I + * I / 0 = I + * I / N = N + * I / I = N + * + * Return a new BigNumber whose value is the value of this BigNumber divided by the value of + * BigNumber(y, b), rounded according to DECIMAL_PLACES and ROUNDING_MODE. + */ + P.dividedBy = P.div = function ( y, b ) { + id = 3; + return div( this, new BigNumber( y, b ), DECIMAL_PLACES, ROUNDING_MODE ); + }; + + + /* + * Return a new BigNumber whose value is the integer part of dividing the value of this + * BigNumber by the value of BigNumber(y, b). + */ + P.dividedToIntegerBy = P.divToInt = function ( y, b ) { + id = 4; + return div( this, new BigNumber( y, b ), 0, 1 ); + }; + + + /* + * Return true if the value of this BigNumber is equal to the value of BigNumber(y, b), + * otherwise returns false. + */ + P.equals = P.eq = function ( y, b ) { + id = 5; + return compare( this, new BigNumber( y, b ) ) === 0; + }; + + + /* + * Return a new BigNumber whose value is the value of this BigNumber rounded to a whole + * number in the direction of -Infinity. + */ + P.floor = function () { + return round( new BigNumber(this), this.e + 1, 3 ); + }; + + + /* + * Return true if the value of this BigNumber is greater than the value of BigNumber(y, b), + * otherwise returns false. + */ + P.greaterThan = P.gt = function ( y, b ) { + id = 6; + return compare( this, new BigNumber( y, b ) ) > 0; + }; + + + /* + * Return true if the value of this BigNumber is greater than or equal to the value of + * BigNumber(y, b), otherwise returns false. + */ + P.greaterThanOrEqualTo = P.gte = function ( y, b ) { + id = 7; + return ( b = compare( this, new BigNumber( y, b ) ) ) === 1 || b === 0; + + }; + + + /* + * Return true if the value of this BigNumber is a finite number, otherwise returns false. + */ + P.isFinite = function () { + return !!this.c; + }; + + + /* + * Return true if the value of this BigNumber is an integer, otherwise return false. + */ + P.isInteger = P.isInt = function () { + return !!this.c && bitFloor( this.e / LOG_BASE ) > this.c.length - 2; + }; + + + /* + * Return true if the value of this BigNumber is NaN, otherwise returns false. + */ + P.isNaN = function () { + return !this.s; + }; + + + /* + * Return true if the value of this BigNumber is negative, otherwise returns false. + */ + P.isNegative = P.isNeg = function () { + return this.s < 0; + }; + + + /* + * Return true if the value of this BigNumber is 0 or -0, otherwise returns false. + */ + P.isZero = function () { + return !!this.c && this.c[0] == 0; + }; + + + /* + * Return true if the value of this BigNumber is less than the value of BigNumber(y, b), + * otherwise returns false. + */ + P.lessThan = P.lt = function ( y, b ) { + id = 8; + return compare( this, new BigNumber( y, b ) ) < 0; + }; + + + /* + * Return true if the value of this BigNumber is less than or equal to the value of + * BigNumber(y, b), otherwise returns false. + */ + P.lessThanOrEqualTo = P.lte = function ( y, b ) { + id = 9; + return ( b = compare( this, new BigNumber( y, b ) ) ) === -1 || b === 0; + }; + + + /* + * n - 0 = n + * n - N = N + * n - I = -I + * 0 - n = -n + * 0 - 0 = 0 + * 0 - N = N + * 0 - I = -I + * N - n = N + * N - 0 = N + * N - N = N + * N - I = N + * I - n = I + * I - 0 = I + * I - N = N + * I - I = N + * + * Return a new BigNumber whose value is the value of this BigNumber minus the value of + * BigNumber(y, b). + */ + P.minus = P.sub = function ( y, b ) { + var i, j, t, xLTy, + x = this, + a = x.s; + + id = 10; + y = new BigNumber( y, b ); + b = y.s; + + // Either NaN? + if ( !a || !b ) return new BigNumber(NaN); + + // Signs differ? + if ( a != b ) { + y.s = -b; + return x.plus(y); + } + + var xe = x.e / LOG_BASE, + ye = y.e / LOG_BASE, + xc = x.c, + yc = y.c; + + if ( !xe || !ye ) { + + // Either Infinity? + if ( !xc || !yc ) return xc ? ( y.s = -b, y ) : new BigNumber( yc ? x : NaN ); + + // Either zero? + if ( !xc[0] || !yc[0] ) { + + // Return y if y is non-zero, x if x is non-zero, or zero if both are zero. + return yc[0] ? ( y.s = -b, y ) : new BigNumber( xc[0] ? x : + + // IEEE 754 (2008) 6.3: n - n = -0 when rounding to -Infinity + ROUNDING_MODE == 3 ? -0 : 0 ); + } + } + + xe = bitFloor(xe); + ye = bitFloor(ye); + xc = xc.slice(); + + // Determine which is the bigger number. + if ( a = xe - ye ) { + + if ( xLTy = a < 0 ) { + a = -a; + t = xc; + } else { + ye = xe; + t = yc; + } + + t.reverse(); + + // Prepend zeros to equalise exponents. + for ( b = a; b--; t.push(0) ); + t.reverse(); + } else { + + // Exponents equal. Check digit by digit. + j = ( xLTy = ( a = xc.length ) < ( b = yc.length ) ) ? a : b; + + for ( a = b = 0; b < j; b++ ) { + + if ( xc[b] != yc[b] ) { + xLTy = xc[b] < yc[b]; + break; + } + } + } + + // x < y? Point xc to the array of the bigger number. + if (xLTy) t = xc, xc = yc, yc = t, y.s = -y.s; + + b = ( j = yc.length ) - ( i = xc.length ); + + // Append zeros to xc if shorter. + // No need to add zeros to yc if shorter as subtract only needs to start at yc.length. + if ( b > 0 ) for ( ; b--; xc[i++] = 0 ); + b = BASE - 1; + + // Subtract yc from xc. + for ( ; j > a; ) { + + if ( xc[--j] < yc[j] ) { + for ( i = j; i && !xc[--i]; xc[i] = b ); + --xc[i]; + xc[j] += BASE; + } + + xc[j] -= yc[j]; + } + + // Remove leading zeros and adjust exponent accordingly. + for ( ; xc[0] == 0; xc.shift(), --ye ); + + // Zero? + if ( !xc[0] ) { + + // Following IEEE 754 (2008) 6.3, + // n - n = +0 but n - n = -0 when rounding towards -Infinity. + y.s = ROUNDING_MODE == 3 ? -1 : 1; + y.c = [ y.e = 0 ]; + return y; + } + + // No need to check for Infinity as +x - +y != Infinity && -x - -y != Infinity + // for finite x and y. + return normalise( y, xc, ye ); + }; + + + /* + * n % 0 = N + * n % N = N + * n % I = n + * 0 % n = 0 + * -0 % n = -0 + * 0 % 0 = N + * 0 % N = N + * 0 % I = 0 + * N % n = N + * N % 0 = N + * N % N = N + * N % I = N + * I % n = N + * I % 0 = N + * I % N = N + * I % I = N + * + * Return a new BigNumber whose value is the value of this BigNumber modulo the value of + * BigNumber(y, b). The result depends on the value of MODULO_MODE. + */ + P.modulo = P.mod = function ( y, b ) { + var q, s, + x = this; + + id = 11; + y = new BigNumber( y, b ); + + // Return NaN if x is Infinity or NaN, or y is NaN or zero. + if ( !x.c || !y.s || y.c && !y.c[0] ) { + return new BigNumber(NaN); + + // Return x if y is Infinity or x is zero. + } else if ( !y.c || x.c && !x.c[0] ) { + return new BigNumber(x); + } + + if ( MODULO_MODE == 9 ) { + + // Euclidian division: q = sign(y) * floor(x / abs(y)) + // r = x - qy where 0 <= r < abs(y) + s = y.s; + y.s = 1; + q = div( x, y, 0, 3 ); + y.s = s; + q.s *= s; + } else { + q = div( x, y, 0, MODULO_MODE ); + } + + return x.minus( q.times(y) ); + }; + + + /* + * Return a new BigNumber whose value is the value of this BigNumber negated, + * i.e. multiplied by -1. + */ + P.negated = P.neg = function () { + var x = new BigNumber(this); + x.s = -x.s || null; + return x; + }; + + + /* + * n + 0 = n + * n + N = N + * n + I = I + * 0 + n = n + * 0 + 0 = 0 + * 0 + N = N + * 0 + I = I + * N + n = N + * N + 0 = N + * N + N = N + * N + I = N + * I + n = I + * I + 0 = I + * I + N = N + * I + I = I + * + * Return a new BigNumber whose value is the value of this BigNumber plus the value of + * BigNumber(y, b). + */ + P.plus = P.add = function ( y, b ) { + var t, + x = this, + a = x.s; + + id = 12; + y = new BigNumber( y, b ); + b = y.s; + + // Either NaN? + if ( !a || !b ) return new BigNumber(NaN); + + // Signs differ? + if ( a != b ) { + y.s = -b; + return x.minus(y); + } + + var xe = x.e / LOG_BASE, + ye = y.e / LOG_BASE, + xc = x.c, + yc = y.c; + + if ( !xe || !ye ) { + + // Return ±Infinity if either ±Infinity. + if ( !xc || !yc ) return new BigNumber( a / 0 ); + + // Either zero? + // Return y if y is non-zero, x if x is non-zero, or zero if both are zero. + if ( !xc[0] || !yc[0] ) return yc[0] ? y : new BigNumber( xc[0] ? x : a * 0 ); + } + + xe = bitFloor(xe); + ye = bitFloor(ye); + xc = xc.slice(); + + // Prepend zeros to equalise exponents. Faster to use reverse then do unshifts. + if ( a = xe - ye ) { + if ( a > 0 ) { + ye = xe; + t = yc; + } else { + a = -a; + t = xc; + } + + t.reverse(); + for ( ; a--; t.push(0) ); + t.reverse(); + } + + a = xc.length; + b = yc.length; + + // Point xc to the longer array, and b to the shorter length. + if ( a - b < 0 ) t = yc, yc = xc, xc = t, b = a; + + // Only start adding at yc.length - 1 as the further digits of xc can be ignored. + for ( a = 0; b; ) { + a = ( xc[--b] = xc[b] + yc[b] + a ) / BASE | 0; + xc[b] %= BASE; + } + + if (a) { + xc.unshift(a); + ++ye; + } + + // No need to check for zero, as +x + +y != 0 && -x + -y != 0 + // ye = MAX_EXP + 1 possible + return normalise( y, xc, ye ); + }; + + + /* + * Return the number of significant digits of the value of this BigNumber. + * + * [z] {boolean|number} Whether to count integer-part trailing zeros: true, false, 1 or 0. + */ + P.precision = P.sd = function (z) { + var n, v, + x = this, + c = x.c; + + // 'precision() argument not a boolean or binary digit: {z}' + if ( z != null && z !== !!z && z !== 1 && z !== 0 ) { + if (ERRORS) raise( 13, 'argument' + notBool, z ); + if ( z != !!z ) z = null; + } + + if ( !c ) return null; + v = c.length - 1; + n = v * LOG_BASE + 1; + + if ( v = c[v] ) { + + // Subtract the number of trailing zeros of the last element. + for ( ; v % 10 == 0; v /= 10, n-- ); + + // Add the number of digits of the first element. + for ( v = c[0]; v >= 10; v /= 10, n++ ); + } + + if ( z && x.e + 1 > n ) n = x.e + 1; + + return n; + }; + + + /* + * Return a new BigNumber whose value is the value of this BigNumber rounded to a maximum of + * dp decimal places using rounding mode rm, or to 0 and ROUNDING_MODE respectively if + * omitted. + * + * [dp] {number} Decimal places. Integer, 0 to MAX inclusive. + * [rm] {number} Rounding mode. Integer, 0 to 8 inclusive. + * + * 'round() decimal places out of range: {dp}' + * 'round() decimal places not an integer: {dp}' + * 'round() rounding mode not an integer: {rm}' + * 'round() rounding mode out of range: {rm}' + */ + P.round = function ( dp, rm ) { + var n = new BigNumber(this); + + if ( dp == null || isValidInt( dp, 0, MAX, 15 ) ) { + round( n, ~~dp + this.e + 1, rm == null || + !isValidInt( rm, 0, 8, 15, roundingMode ) ? ROUNDING_MODE : rm | 0 ); + } + + return n; + }; + + + /* + * Return a new BigNumber whose value is the value of this BigNumber shifted by k places + * (powers of 10). Shift to the right if n > 0, and to the left if n < 0. + * + * k {number} Integer, -MAX_SAFE_INTEGER to MAX_SAFE_INTEGER inclusive. + * + * If k is out of range and ERRORS is false, the result will be ±0 if k < 0, or ±Infinity + * otherwise. + * + * 'shift() argument not an integer: {k}' + * 'shift() argument out of range: {k}' + */ + P.shift = function (k) { + var n = this; + return isValidInt( k, -MAX_SAFE_INTEGER, MAX_SAFE_INTEGER, 16, 'argument' ) + + // k < 1e+21, or truncate(k) will produce exponential notation. + ? n.times( '1e' + truncate(k) ) + : new BigNumber( n.c && n.c[0] && ( k < -MAX_SAFE_INTEGER || k > MAX_SAFE_INTEGER ) + ? n.s * ( k < 0 ? 0 : 1 / 0 ) + : n ); + }; + + + /* + * sqrt(-n) = N + * sqrt( N) = N + * sqrt(-I) = N + * sqrt( I) = I + * sqrt( 0) = 0 + * sqrt(-0) = -0 + * + * Return a new BigNumber whose value is the square root of the value of this BigNumber, + * rounded according to DECIMAL_PLACES and ROUNDING_MODE. + */ + P.squareRoot = P.sqrt = function () { + var m, n, r, rep, t, + x = this, + c = x.c, + s = x.s, + e = x.e, + dp = DECIMAL_PLACES + 4, + half = new BigNumber('0.5'); + + // Negative/NaN/Infinity/zero? + if ( s !== 1 || !c || !c[0] ) { + return new BigNumber( !s || s < 0 && ( !c || c[0] ) ? NaN : c ? x : 1 / 0 ); + } + + // Initial estimate. + s = Math.sqrt( +x ); + + // Math.sqrt underflow/overflow? + // Pass x to Math.sqrt as integer, then adjust the exponent of the result. + if ( s == 0 || s == 1 / 0 ) { + n = coeffToString(c); + if ( ( n.length + e ) % 2 == 0 ) n += '0'; + s = Math.sqrt(n); + e = bitFloor( ( e + 1 ) / 2 ) - ( e < 0 || e % 2 ); + + if ( s == 1 / 0 ) { + n = '1e' + e; + } else { + n = s.toExponential(); + n = n.slice( 0, n.indexOf('e') + 1 ) + e; + } + + r = new BigNumber(n); + } else { + r = new BigNumber( s + '' ); + } + + // Check for zero. + // r could be zero if MIN_EXP is changed after the this value was created. + // This would cause a division by zero (x/t) and hence Infinity below, which would cause + // coeffToString to throw. + if ( r.c[0] ) { + e = r.e; + s = e + dp; + if ( s < 3 ) s = 0; + + // Newton-Raphson iteration. + for ( ; ; ) { + t = r; + r = half.times( t.plus( div( x, t, dp, 1 ) ) ); + + if ( coeffToString( t.c ).slice( 0, s ) === ( n = + coeffToString( r.c ) ).slice( 0, s ) ) { + + // The exponent of r may here be one less than the final result exponent, + // e.g 0.0009999 (e-4) --> 0.001 (e-3), so adjust s so the rounding digits + // are indexed correctly. + if ( r.e < e ) --s; + n = n.slice( s - 3, s + 1 ); + + // The 4th rounding digit may be in error by -1 so if the 4 rounding digits + // are 9999 or 4999 (i.e. approaching a rounding boundary) continue the + // iteration. + if ( n == '9999' || !rep && n == '4999' ) { + + // On the first iteration only, check to see if rounding up gives the + // exact result as the nines may infinitely repeat. + if ( !rep ) { + round( t, t.e + DECIMAL_PLACES + 2, 0 ); + + if ( t.times(t).eq(x) ) { + r = t; + break; + } + } + + dp += 4; + s += 4; + rep = 1; + } else { + + // If rounding digits are null, 0{0,4} or 50{0,3}, check for exact + // result. If not, then there are further digits and m will be truthy. + if ( !+n || !+n.slice(1) && n.charAt(0) == '5' ) { + + // Truncate to the first rounding digit. + round( r, r.e + DECIMAL_PLACES + 2, 1 ); + m = !r.times(r).eq(x); + } + + break; + } + } + } + } + + return round( r, r.e + DECIMAL_PLACES + 1, ROUNDING_MODE, m ); + }; + + + /* + * n * 0 = 0 + * n * N = N + * n * I = I + * 0 * n = 0 + * 0 * 0 = 0 + * 0 * N = N + * 0 * I = N + * N * n = N + * N * 0 = N + * N * N = N + * N * I = N + * I * n = I + * I * 0 = N + * I * N = N + * I * I = I + * + * Return a new BigNumber whose value is the value of this BigNumber times the value of + * BigNumber(y, b). + */ + P.times = P.mul = function ( y, b ) { + var c, e, i, j, k, m, xcL, xlo, xhi, ycL, ylo, yhi, zc, + base, sqrtBase, + x = this, + xc = x.c, + yc = ( id = 17, y = new BigNumber( y, b ) ).c; + + // Either NaN, ±Infinity or ±0? + if ( !xc || !yc || !xc[0] || !yc[0] ) { + + // Return NaN if either is NaN, or one is 0 and the other is Infinity. + if ( !x.s || !y.s || xc && !xc[0] && !yc || yc && !yc[0] && !xc ) { + y.c = y.e = y.s = null; + } else { + y.s *= x.s; + + // Return ±Infinity if either is ±Infinity. + if ( !xc || !yc ) { + y.c = y.e = null; + + // Return ±0 if either is ±0. + } else { + y.c = [0]; + y.e = 0; + } + } + + return y; + } + + e = bitFloor( x.e / LOG_BASE ) + bitFloor( y.e / LOG_BASE ); + y.s *= x.s; + xcL = xc.length; + ycL = yc.length; + + // Ensure xc points to longer array and xcL to its length. + if ( xcL < ycL ) zc = xc, xc = yc, yc = zc, i = xcL, xcL = ycL, ycL = i; + + // Initialise the result array with zeros. + for ( i = xcL + ycL, zc = []; i--; zc.push(0) ); + + base = BASE; + sqrtBase = SQRT_BASE; + + for ( i = ycL; --i >= 0; ) { + c = 0; + ylo = yc[i] % sqrtBase; + yhi = yc[i] / sqrtBase | 0; + + for ( k = xcL, j = i + k; j > i; ) { + xlo = xc[--k] % sqrtBase; + xhi = xc[k] / sqrtBase | 0; + m = yhi * xlo + xhi * ylo; + xlo = ylo * xlo + ( ( m % sqrtBase ) * sqrtBase ) + zc[j] + c; + c = ( xlo / base | 0 ) + ( m / sqrtBase | 0 ) + yhi * xhi; + zc[j--] = xlo % base; + } + + zc[j] = c; + } + + if (c) { + ++e; + } else { + zc.shift(); + } + + return normalise( y, zc, e ); + }; + + + /* + * Return a new BigNumber whose value is the value of this BigNumber rounded to a maximum of + * sd significant digits using rounding mode rm, or ROUNDING_MODE if rm is omitted. + * + * [sd] {number} Significant digits. Integer, 1 to MAX inclusive. + * [rm] {number} Rounding mode. Integer, 0 to 8 inclusive. + * + * 'toDigits() precision out of range: {sd}' + * 'toDigits() precision not an integer: {sd}' + * 'toDigits() rounding mode not an integer: {rm}' + * 'toDigits() rounding mode out of range: {rm}' + */ + P.toDigits = function ( sd, rm ) { + var n = new BigNumber(this); + sd = sd == null || !isValidInt( sd, 1, MAX, 18, 'precision' ) ? null : sd | 0; + rm = rm == null || !isValidInt( rm, 0, 8, 18, roundingMode ) ? ROUNDING_MODE : rm | 0; + return sd ? round( n, sd, rm ) : n; + }; + + + /* + * Return a string representing the value of this BigNumber in exponential notation and + * rounded using ROUNDING_MODE to dp fixed decimal places. + * + * [dp] {number} Decimal places. Integer, 0 to MAX inclusive. + * [rm] {number} Rounding mode. Integer, 0 to 8 inclusive. + * + * 'toExponential() decimal places not an integer: {dp}' + * 'toExponential() decimal places out of range: {dp}' + * 'toExponential() rounding mode not an integer: {rm}' + * 'toExponential() rounding mode out of range: {rm}' + */ + P.toExponential = function ( dp, rm ) { + return format( this, + dp != null && isValidInt( dp, 0, MAX, 19 ) ? ~~dp + 1 : null, rm, 19 ); + }; + + + /* + * Return a string representing the value of this BigNumber in fixed-point notation rounding + * to dp fixed decimal places using rounding mode rm, or ROUNDING_MODE if rm is omitted. + * + * Note: as with JavaScript's number type, (-0).toFixed(0) is '0', + * but e.g. (-0.00001).toFixed(0) is '-0'. + * + * [dp] {number} Decimal places. Integer, 0 to MAX inclusive. + * [rm] {number} Rounding mode. Integer, 0 to 8 inclusive. + * + * 'toFixed() decimal places not an integer: {dp}' + * 'toFixed() decimal places out of range: {dp}' + * 'toFixed() rounding mode not an integer: {rm}' + * 'toFixed() rounding mode out of range: {rm}' + */ + P.toFixed = function ( dp, rm ) { + return format( this, dp != null && isValidInt( dp, 0, MAX, 20 ) + ? ~~dp + this.e + 1 : null, rm, 20 ); + }; + + + /* + * Return a string representing the value of this BigNumber in fixed-point notation rounded + * using rm or ROUNDING_MODE to dp decimal places, and formatted according to the properties + * of the FORMAT object (see BigNumber.config). + * + * FORMAT = { + * decimalSeparator : '.', + * groupSeparator : ',', + * groupSize : 3, + * secondaryGroupSize : 0, + * fractionGroupSeparator : '\xA0', // non-breaking space + * fractionGroupSize : 0 + * }; + * + * [dp] {number} Decimal places. Integer, 0 to MAX inclusive. + * [rm] {number} Rounding mode. Integer, 0 to 8 inclusive. + * + * 'toFormat() decimal places not an integer: {dp}' + * 'toFormat() decimal places out of range: {dp}' + * 'toFormat() rounding mode not an integer: {rm}' + * 'toFormat() rounding mode out of range: {rm}' + */ + P.toFormat = function ( dp, rm ) { + var str = format( this, dp != null && isValidInt( dp, 0, MAX, 21 ) + ? ~~dp + this.e + 1 : null, rm, 21 ); + + if ( this.c ) { + var i, + arr = str.split('.'), + g1 = +FORMAT.groupSize, + g2 = +FORMAT.secondaryGroupSize, + groupSeparator = FORMAT.groupSeparator, + intPart = arr[0], + fractionPart = arr[1], + isNeg = this.s < 0, + intDigits = isNeg ? intPart.slice(1) : intPart, + len = intDigits.length; + + if (g2) i = g1, g1 = g2, g2 = i, len -= i; + + if ( g1 > 0 && len > 0 ) { + i = len % g1 || g1; + intPart = intDigits.substr( 0, i ); + + for ( ; i < len; i += g1 ) { + intPart += groupSeparator + intDigits.substr( i, g1 ); + } + + if ( g2 > 0 ) intPart += groupSeparator + intDigits.slice(i); + if (isNeg) intPart = '-' + intPart; + } + + str = fractionPart + ? intPart + FORMAT.decimalSeparator + ( ( g2 = +FORMAT.fractionGroupSize ) + ? fractionPart.replace( new RegExp( '\\d{' + g2 + '}\\B', 'g' ), + '$&' + FORMAT.fractionGroupSeparator ) + : fractionPart ) + : intPart; + } + + return str; + }; + + + /* + * Return a string array representing the value of this BigNumber as a simple fraction with + * an integer numerator and an integer denominator. The denominator will be a positive + * non-zero value less than or equal to the specified maximum denominator. If a maximum + * denominator is not specified, the denominator will be the lowest value necessary to + * represent the number exactly. + * + * [md] {number|string|BigNumber} Integer >= 1 and < Infinity. The maximum denominator. + * + * 'toFraction() max denominator not an integer: {md}' + * 'toFraction() max denominator out of range: {md}' + */ + P.toFraction = function (md) { + var arr, d0, d2, e, exp, n, n0, q, s, + k = ERRORS, + x = this, + xc = x.c, + d = new BigNumber(ONE), + n1 = d0 = new BigNumber(ONE), + d1 = n0 = new BigNumber(ONE); + + if ( md != null ) { + ERRORS = false; + n = new BigNumber(md); + ERRORS = k; + + if ( !( k = n.isInt() ) || n.lt(ONE) ) { + + if (ERRORS) { + raise( 22, + 'max denominator ' + ( k ? 'out of range' : 'not an integer' ), md ); + } + + // ERRORS is false: + // If md is a finite non-integer >= 1, round it to an integer and use it. + md = !k && n.c && round( n, n.e + 1, 1 ).gte(ONE) ? n : null; + } + } + + if ( !xc ) return x.toString(); + s = coeffToString(xc); + + // Determine initial denominator. + // d is a power of 10 and the minimum max denominator that specifies the value exactly. + e = d.e = s.length - x.e - 1; + d.c[0] = POWS_TEN[ ( exp = e % LOG_BASE ) < 0 ? LOG_BASE + exp : exp ]; + md = !md || n.cmp(d) > 0 ? ( e > 0 ? d : n1 ) : n; + + exp = MAX_EXP; + MAX_EXP = 1 / 0; + n = new BigNumber(s); + + // n0 = d1 = 0 + n0.c[0] = 0; + + for ( ; ; ) { + q = div( n, d, 0, 1 ); + d2 = d0.plus( q.times(d1) ); + if ( d2.cmp(md) == 1 ) break; + d0 = d1; + d1 = d2; + n1 = n0.plus( q.times( d2 = n1 ) ); + n0 = d2; + d = n.minus( q.times( d2 = d ) ); + n = d2; + } + + d2 = div( md.minus(d0), d1, 0, 1 ); + n0 = n0.plus( d2.times(n1) ); + d0 = d0.plus( d2.times(d1) ); + n0.s = n1.s = x.s; + e *= 2; + + // Determine which fraction is closer to x, n0/d0 or n1/d1 + arr = div( n1, d1, e, ROUNDING_MODE ).minus(x).abs().cmp( + div( n0, d0, e, ROUNDING_MODE ).minus(x).abs() ) < 1 + ? [ n1.toString(), d1.toString() ] + : [ n0.toString(), d0.toString() ]; + + MAX_EXP = exp; + return arr; + }; + + + /* + * Return the value of this BigNumber converted to a number primitive. + */ + P.toNumber = function () { + var x = this; + + // Ensure zero has correct sign. + return +x || ( x.s ? x.s * 0 : NaN ); + }; + + + /* + * Return a BigNumber whose value is the value of this BigNumber raised to the power n. + * If n is negative round according to DECIMAL_PLACES and ROUNDING_MODE. + * If POW_PRECISION is not 0, round to POW_PRECISION using ROUNDING_MODE. + * + * n {number} Integer, -9007199254740992 to 9007199254740992 inclusive. + * (Performs 54 loop iterations for n of 9007199254740992.) + * + * 'pow() exponent not an integer: {n}' + * 'pow() exponent out of range: {n}' + */ + P.toPower = P.pow = function (n) { + var k, y, + i = mathfloor( n < 0 ? -n : +n ), + x = this; + + // Pass ±Infinity to Math.pow if exponent is out of range. + if ( !isValidInt( n, -MAX_SAFE_INTEGER, MAX_SAFE_INTEGER, 23, 'exponent' ) && + ( !isFinite(n) || i > MAX_SAFE_INTEGER && ( n /= 0 ) || + parseFloat(n) != n && !( n = NaN ) ) ) { + return new BigNumber( Math.pow( +x, n ) ); + } + + // Truncating each coefficient array to a length of k after each multiplication equates + // to truncating significant digits to POW_PRECISION + [28, 41], i.e. there will be a + // minimum of 28 guard digits retained. (Using + 1.5 would give [9, 21] guard digits.) + k = POW_PRECISION ? mathceil( POW_PRECISION / LOG_BASE + 2 ) : 0; + y = new BigNumber(ONE); + + for ( ; ; ) { + + if ( i % 2 ) { + y = y.times(x); + if ( !y.c ) break; + if ( k && y.c.length > k ) y.c.length = k; + } + + i = mathfloor( i / 2 ); + if ( !i ) break; + + x = x.times(x); + if ( k && x.c && x.c.length > k ) x.c.length = k; + } + + if ( n < 0 ) y = ONE.div(y); + return k ? round( y, POW_PRECISION, ROUNDING_MODE ) : y; + }; + + + /* + * Return a string representing the value of this BigNumber rounded to sd significant digits + * using rounding mode rm or ROUNDING_MODE. If sd is less than the number of digits + * necessary to represent the integer part of the value in fixed-point notation, then use + * exponential notation. + * + * [sd] {number} Significant digits. Integer, 1 to MAX inclusive. + * [rm] {number} Rounding mode. Integer, 0 to 8 inclusive. + * + * 'toPrecision() precision not an integer: {sd}' + * 'toPrecision() precision out of range: {sd}' + * 'toPrecision() rounding mode not an integer: {rm}' + * 'toPrecision() rounding mode out of range: {rm}' + */ + P.toPrecision = function ( sd, rm ) { + return format( this, sd != null && isValidInt( sd, 1, MAX, 24, 'precision' ) + ? sd | 0 : null, rm, 24 ); + }; + + + /* + * Return a string representing the value of this BigNumber in base b, or base 10 if b is + * omitted. If a base is specified, including base 10, round according to DECIMAL_PLACES and + * ROUNDING_MODE. If a base is not specified, and this BigNumber has a positive exponent + * that is equal to or greater than TO_EXP_POS, or a negative exponent equal to or less than + * TO_EXP_NEG, return exponential notation. + * + * [b] {number} Integer, 2 to 64 inclusive. + * + * 'toString() base not an integer: {b}' + * 'toString() base out of range: {b}' + */ + P.toString = function (b) { + var str, + n = this, + s = n.s, + e = n.e; + + // Infinity or NaN? + if ( e === null ) { + + if (s) { + str = 'Infinity'; + if ( s < 0 ) str = '-' + str; + } else { + str = 'NaN'; + } + } else { + str = coeffToString( n.c ); + + if ( b == null || !isValidInt( b, 2, 64, 25, 'base' ) ) { + str = e <= TO_EXP_NEG || e >= TO_EXP_POS + ? toExponential( str, e ) + : toFixedPoint( str, e ); + } else { + str = convertBase( toFixedPoint( str, e ), b | 0, 10, s ); + } + + if ( s < 0 && n.c[0] ) str = '-' + str; + } + + return str; + }; + + + /* + * Return a new BigNumber whose value is the value of this BigNumber truncated to a whole + * number. + */ + P.truncated = P.trunc = function () { + return round( new BigNumber(this), this.e + 1, 1 ); + }; + + + + /* + * Return as toString, but do not accept a base argument. + */ + P.valueOf = P.toJSON = function () { + return this.toString(); + }; + + + // Aliases for BigDecimal methods. + //P.add = P.plus; // P.add included above + //P.subtract = P.minus; // P.sub included above + //P.multiply = P.times; // P.mul included above + //P.divide = P.div; + //P.remainder = P.mod; + //P.compareTo = P.cmp; + //P.negate = P.neg; + + + if ( configObj != null ) BigNumber.config(configObj); + + return BigNumber; + } + + + // PRIVATE HELPER FUNCTIONS + + + function bitFloor(n) { + var i = n | 0; + return n > 0 || n === i ? i : i - 1; + } + + + // Return a coefficient array as a string of base 10 digits. + function coeffToString(a) { + var s, z, + i = 1, + j = a.length, + r = a[0] + ''; + + for ( ; i < j; ) { + s = a[i++] + ''; + z = LOG_BASE - s.length; + for ( ; z--; s = '0' + s ); + r += s; + } + + // Determine trailing zeros. + for ( j = r.length; r.charCodeAt(--j) === 48; ); + return r.slice( 0, j + 1 || 1 ); + } + + + // Compare the value of BigNumbers x and y. + function compare( x, y ) { + var a, b, + xc = x.c, + yc = y.c, + i = x.s, + j = y.s, + k = x.e, + l = y.e; + + // Either NaN? + if ( !i || !j ) return null; + + a = xc && !xc[0]; + b = yc && !yc[0]; + + // Either zero? + if ( a || b ) return a ? b ? 0 : -j : i; + + // Signs differ? + if ( i != j ) return i; + + a = i < 0; + b = k == l; + + // Either Infinity? + if ( !xc || !yc ) return b ? 0 : !xc ^ a ? 1 : -1; + + // Compare exponents. + if ( !b ) return k > l ^ a ? 1 : -1; + + j = ( k = xc.length ) < ( l = yc.length ) ? k : l; + + // Compare digit by digit. + for ( i = 0; i < j; i++ ) if ( xc[i] != yc[i] ) return xc[i] > yc[i] ^ a ? 1 : -1; + + // Compare lengths. + return k == l ? 0 : k > l ^ a ? 1 : -1; + } + + + /* + * Return true if n is a valid number in range, otherwise false. + * Use for argument validation when ERRORS is false. + * Note: parseInt('1e+1') == 1 but parseFloat('1e+1') == 10. + */ + function intValidatorNoErrors( n, min, max ) { + return ( n = truncate(n) ) >= min && n <= max; + } + + + function isArray(obj) { + return Object.prototype.toString.call(obj) == '[object Array]'; + } + + + /* + * Convert string of baseIn to an array of numbers of baseOut. + * Eg. convertBase('255', 10, 16) returns [15, 15]. + * Eg. convertBase('ff', 16, 10) returns [2, 5, 5]. + */ + function toBaseOut( str, baseIn, baseOut ) { + var j, + arr = [0], + arrL, + i = 0, + len = str.length; + + for ( ; i < len; ) { + for ( arrL = arr.length; arrL--; arr[arrL] *= baseIn ); + arr[ j = 0 ] += ALPHABET.indexOf( str.charAt( i++ ) ); + + for ( ; j < arr.length; j++ ) { + + if ( arr[j] > baseOut - 1 ) { + if ( arr[j + 1] == null ) arr[j + 1] = 0; + arr[j + 1] += arr[j] / baseOut | 0; + arr[j] %= baseOut; + } + } + } + + return arr.reverse(); + } + + + function toExponential( str, e ) { + return ( str.length > 1 ? str.charAt(0) + '.' + str.slice(1) : str ) + + ( e < 0 ? 'e' : 'e+' ) + e; + } + + + function toFixedPoint( str, e ) { + var len, z; + + // Negative exponent? + if ( e < 0 ) { + + // Prepend zeros. + for ( z = '0.'; ++e; z += '0' ); + str = z + str; + + // Positive exponent + } else { + len = str.length; + + // Append zeros. + if ( ++e > len ) { + for ( z = '0', e -= len; --e; z += '0' ); + str += z; + } else if ( e < len ) { + str = str.slice( 0, e ) + '.' + str.slice(e); + } + } + + return str; + } + + + function truncate(n) { + n = parseFloat(n); + return n < 0 ? mathceil(n) : mathfloor(n); + } + + + // EXPORT + + + BigNumber = another(); + + // AMD. + if ( typeof define == 'function' && define.amd ) { + define( function () { return BigNumber; } ); + + // Node and other environments that support module.exports. + } else if ( typeof module != 'undefined' && module.exports ) { + module.exports = BigNumber; + if ( !crypto ) try { crypto = require('crypto'); } catch (e) {} + + // Browser. + } else { + global.BigNumber = BigNumber; + } +})(this); -},{}],"web3":[function(require,module,exports){ +},{"crypto":49}],"web3":[function(require,module,exports){ var Web3 = require('./lib/web3'); // dont override global variable @@ -13105,5 +15955,5 @@ if (typeof window !== 'undefined' && typeof window.Web3 === 'undefined') { module.exports = Web3; },{"./lib/web3":22}]},{},["web3"]) -//# sourceMappingURL=web3-light.js.map +//# sourceMappingURL=web3.js.map ` diff --git a/node/api.go b/node/api.go index 8a25784104..f199a8d3d3 100644 --- a/node/api.go +++ b/node/api.go @@ -25,6 +25,7 @@ import ( "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/p2p" "github.com/ethereum/go-ethereum/p2p/discover" + "github.com/ethereum/go-ethereum/rpc" "github.com/rcrowley/go-metrics" ) @@ -58,14 +59,33 @@ func (api *PrivateAdminAPI) AddPeer(url string) (bool, error) { } // StartRPC starts the HTTP RPC API server. -func (api *PrivateAdminAPI) StartRPC(host string, port int, cors string, apis string) (bool, error) { +func (api *PrivateAdminAPI) StartRPC(host *string, port *rpc.HexNumber, cors *string, apis *string) (bool, error) { api.node.lock.Lock() defer api.node.lock.Unlock() if api.node.httpHandler != nil { return false, fmt.Errorf("HTTP RPC already running on %s", api.node.httpEndpoint) } - if err := api.node.startHTTP(fmt.Sprintf("%s:%d", host, port), api.node.rpcAPIs, strings.Split(apis, ","), cors); err != nil { + + if host == nil { + host = &api.node.httpHost + } + if port == nil { + port = rpc.NewHexNumber(api.node.httpPort) + } + if cors == nil { + cors = &api.node.httpCors + } + + modules := api.node.httpWhitelist + if apis != nil { + modules = nil + for _, m := range strings.Split(*apis, ",") { + modules = append(modules, strings.TrimSpace(m)) + } + } + + if err := api.node.startHTTP(fmt.Sprintf("%s:%d", *host, port.Int()), api.node.rpcAPIs, modules, *cors); err != nil { return false, err } return true, nil @@ -84,14 +104,33 @@ func (api *PrivateAdminAPI) StopRPC() (bool, error) { } // StartWS starts the websocket RPC API server. -func (api *PrivateAdminAPI) StartWS(host string, port int, cors string, apis string) (bool, error) { +func (api *PrivateAdminAPI) StartWS(host *string, port *rpc.HexNumber, allowedOrigins *string, apis *string) (bool, error) { api.node.lock.Lock() defer api.node.lock.Unlock() if api.node.wsHandler != nil { return false, fmt.Errorf("WebSocket RPC already running on %s", api.node.wsEndpoint) } - if err := api.node.startWS(fmt.Sprintf("%s:%d", host, port), api.node.rpcAPIs, strings.Split(apis, ","), cors); err != nil { + + if host == nil { + host = &api.node.wsHost + } + if port == nil { + port = rpc.NewHexNumber(api.node.wsPort) + } + if allowedOrigins == nil { + allowedOrigins = &api.node.wsOrigins + } + + modules := api.node.wsWhitelist + if apis != nil { + modules = nil + for _, m := range strings.Split(*apis, ",") { + modules = append(modules, strings.TrimSpace(m)) + } + } + + if err := api.node.startWS(fmt.Sprintf("%s:%d", *host, port.Int()), api.node.rpcAPIs, modules, *allowedOrigins); err != nil { return false, err } return true, nil diff --git a/node/config.go b/node/config.go index 301ec636ee..23a745c2c3 100644 --- a/node/config.go +++ b/node/config.go @@ -127,10 +127,10 @@ type Config struct { // ephemeral nodes). WSPort int - // WSDomains is the list of domain to accept websocket requests from. Please be + // WSOrigins is the list of domain to accept websocket requests from. Please be // aware that the server can only act upon the HTTP request the client sends and // cannot verify the validity of the request header. - WSDomains string + WSOrigins string // WSModules is a list of API modules to expose via the websocket RPC interface. // If the module list is empty, all RPC API endpoints designated public will be diff --git a/node/node.go b/node/node.go index 62cc3895b2..18c3f91d87 100644 --- a/node/node.go +++ b/node/node.go @@ -62,15 +62,19 @@ type Node struct { ipcListener net.Listener // IPC RPC listener socket to serve API requests ipcHandler *rpc.Server // IPC RPC request handler to process the API requests + httpHost string // HTTP hostname + httpPort int // HTTP post httpEndpoint string // HTTP endpoint (interface + port) to listen at (empty = HTTP disabled) httpWhitelist []string // HTTP RPC modules to allow through this endpoint httpCors string // HTTP RPC Cross-Origin Resource Sharing header httpListener net.Listener // HTTP RPC listener socket to server API requests httpHandler *rpc.Server // HTTP RPC request handler to process the API requests + wsHost string // Websocket host + wsPort int // Websocket post wsEndpoint string // Websocket endpoint (interface + port) to listen at (empty = websocket disabled) wsWhitelist []string // Websocket RPC modules to allow through this endpoint - wsDomains string // Websocket RPC allowed origin domains + wsOrigins string // Websocket RPC allowed origin domains wsListener net.Listener // Websocket RPC listener socket to server API requests wsHandler *rpc.Server // Websocket RPC request handler to process the API requests @@ -110,12 +114,16 @@ func New(conf *Config) (*Node, error) { }, serviceFuncs: []ServiceConstructor{}, ipcEndpoint: conf.IPCEndpoint(), + httpHost: conf.HTTPHost, + httpPort: conf.HTTPPort, httpEndpoint: conf.HTTPEndpoint(), httpWhitelist: conf.HTTPModules, httpCors: conf.HTTPCors, + wsHost: conf.WSHost, + wsPort: conf.WSPort, wsEndpoint: conf.WSEndpoint(), wsWhitelist: conf.WSModules, - wsDomains: conf.WSDomains, + wsOrigins: conf.WSOrigins, eventmux: new(event.TypeMux), }, nil } @@ -231,7 +239,7 @@ func (n *Node) startRPC(services map[reflect.Type]Service) error { n.stopInProc() return err } - if err := n.startWS(n.wsEndpoint, apis, n.wsWhitelist, n.wsDomains); err != nil { + if err := n.startWS(n.wsEndpoint, apis, n.wsWhitelist, n.wsOrigins); err != nil { n.stopHTTP() n.stopIPC() n.stopInProc() @@ -383,7 +391,7 @@ func (n *Node) stopHTTP() { } // startWS initializes and starts the websocket RPC endpoint. -func (n *Node) startWS(endpoint string, apis []rpc.API, modules []string, cors string) error { +func (n *Node) startWS(endpoint string, apis []rpc.API, modules []string, wsOrigins string) error { // Short circuit if the WS endpoint isn't being exposed if endpoint == "" { return nil @@ -411,14 +419,14 @@ func (n *Node) startWS(endpoint string, apis []rpc.API, modules []string, cors s if listener, err = net.Listen("tcp", endpoint); err != nil { return err } - go rpc.NewWSServer(cors, handler).Serve(listener) + go rpc.NewWSServer(wsOrigins, handler).Serve(listener) glog.V(logger.Info).Infof("WebSocket endpoint opened: ws://%s", endpoint) // All listeners booted successfully n.wsEndpoint = endpoint n.wsListener = listener n.wsHandler = handler - n.wsDomains = cors + n.wsOrigins = wsOrigins return nil } diff --git a/node/node_test.go b/node/node_test.go index 532115d3ca..372fc6b10a 100644 --- a/node/node_test.go +++ b/node/node_test.go @@ -554,7 +554,7 @@ func TestAPIGather(t *testing.T) { {"multi.v2.nested_theOneMethod", "multi.v2.nested"}, } for i, test := range tests { - if err := client.Send(rpc.JSONRequest{Id: new(int64), Version: "2.0", Method: test.Method}); err != nil { + if err := client.Send(rpc.JSONRequest{Id: []byte("1"), Version: "2.0", Method: test.Method}); err != nil { t.Fatalf("test %d: failed to send API request: %v", i, err) } reply := new(rpc.JSONSuccessResponse) diff --git a/rpc/doc.go b/rpc/doc.go index c9dba3270f..77202634fb 100644 --- a/rpc/doc.go +++ b/rpc/doc.go @@ -29,11 +29,23 @@ Methods that satisfy the following criteria are made available for remote access - method returned value(s) must be exported or builtin types An example method: - func (s *CalcService) Div(a, b int) (int, error) + func (s *CalcService) Add(a, b int) (int, error) When the returned error isn't nil the returned integer is ignored and the error is send back to the client. Otherwise the returned integer is send back to the client. +Optional arguments are supported by accepting pointer values as arguments. E.g. +if we want to do the addition in an optional finite field we can accept a mod +argument as pointer value. + + func (s *CalService) Add(a, b int, mod *int) (int, error) + +This RPC method can be called with 2 integers and a null value as third argument. +In that case the mod argument will be nil. Or it can be called with 3 integers, +in that case mod will be pointing to the given third argument. Since the optional +argument is the last argument the RPC package will also accept 2 integers as +arguments. It will pass the mod argument as nil to the RPC method. + The server offers the ServeCodec method which accepts a ServerCodec instance. It will read requests from the codec, process the request and sends the response back to the client using the codec. The server can execute requests concurrently. Responses diff --git a/rpc/http.go b/rpc/http.go index dd1ec2c018..9283ce0ec9 100644 --- a/rpc/http.go +++ b/rpc/http.go @@ -20,13 +20,12 @@ import ( "bytes" "encoding/json" "fmt" + "io" "io/ioutil" "net/http" "net/url" "strings" - "io" - "github.com/rs/cors" ) @@ -36,8 +35,9 @@ const ( // httpClient connects to a geth RPC server over HTTP. type httpClient struct { - endpoint *url.URL // HTTP-RPC server endpoint - lastRes []byte // HTTP requests are synchronous, store last response + endpoint *url.URL // HTTP-RPC server endpoint + httpClient http.Client // reuse connection + lastRes []byte // HTTP requests are synchronous, store last response } // NewHTTPClient create a new RPC clients that connection to a geth RPC server @@ -57,30 +57,22 @@ func (client *httpClient) Send(msg interface{}) error { var err error client.lastRes = nil - if body, err = json.Marshal(msg); err != nil { return err } - httpReq, err := http.NewRequest("POST", client.endpoint.String(), bytes.NewBuffer(body)) + resp, err := client.httpClient.Post(client.endpoint.String(), "application/json", bytes.NewReader(body)) if err != nil { return err } - httpReq.Header.Set("Content-Type", "application/json") - httpClient := http.Client{} - resp, err := httpClient.Do(httpReq) - if err != nil { - return err - } defer resp.Body.Close() - if resp.StatusCode == http.StatusOK { client.lastRes, err = ioutil.ReadAll(resp.Body) return err } - return fmt.Errorf("unable to handle request") + return fmt.Errorf("request failed: %s", resp.Status) } // Recv will try to deserialize the last received response into the given msg. diff --git a/rpc/ipc_windows.go b/rpc/ipc_windows.go index c5f69589e9..8762cdb0df 100644 --- a/rpc/ipc_windows.go +++ b/rpc/ipc_windows.go @@ -22,7 +22,7 @@ import ( "net" "time" - "github.com/microsoft/go-winio" + winio "github.com/microsoft/go-winio" ) // ipcListen will create a named pipe on the given endpoint. diff --git a/rpc/javascript.go b/rpc/javascript.go index c4fa80c0b1..64af8968f3 100644 --- a/rpc/javascript.go +++ b/rpc/javascript.go @@ -19,48 +19,15 @@ package rpc var ( // Holds geth specific RPC extends which can be used to extend web3 WEB3Extensions = map[string]string{ - "personal": Personal_JS, - "txpool": TxPool_JS, - "admin": Admin_JS, - "eth": Eth_JS, - "miner": Miner_JS, - "debug": Debug_JS, - "net": Net_JS, + "txpool": TxPool_JS, + "admin": Admin_JS, + "eth": Eth_JS, + "miner": Miner_JS, + "debug": Debug_JS, + "net": Net_JS, } ) -const Personal_JS = ` -web3._extend({ - property: 'personal', - methods: - [ - new web3._extend.Method({ - name: 'newAccount', - call: 'personal_newAccount', - params: 1, - outputFormatter: web3._extend.utils.toAddress - }), - new web3._extend.Method({ - name: 'unlockAccount', - call: 'personal_unlockAccount', - params: 3, - }), - new web3._extend.Method({ - name: 'lockAccount', - call: 'personal_lockAccount', - params: 1 - }) - ], - properties: - [ - new web3._extend.Property({ - name: 'listAccounts', - getter: 'personal_listAccounts' - }) - ] -}); -` - const TxPool_JS = ` web3._extend({ property: 'txpool', @@ -124,22 +91,22 @@ web3._extend({ new web3._extend.Method({ name: 'startRPC', call: 'admin_startRPC', - params: 4 + params: 4, + inputFormatter: [null, null, null, null] }), new web3._extend.Method({ name: 'stopRPC', - call: 'admin_stopRPC', - params: 0 + call: 'admin_stopRPC' }), new web3._extend.Method({ name: 'startWS', call: 'admin_startWS', - params: 4 + params: 4, + inputFormatter: [null, null, null, null] }), new web3._extend.Method({ name: 'stopWS', - call: 'admin_stopWS', - params: 0 + call: 'admin_stopWS' }), new web3._extend.Method({ name: 'setGlobalRegistrar', @@ -219,7 +186,7 @@ web3._extend({ name: 'sign', call: 'eth_sign', params: 2, - inputFormatter: [web3._extend.utils.toAddress, null] + inputFormatter: [web3._extend.formatters.inputAddressFormatter, null] }), new web3._extend.Method({ name: 'resend', @@ -414,19 +381,18 @@ web3._extend({ new web3._extend.Method({ name: 'start', call: 'miner_start', - params: 1 + params: 1, + inputFormatter: [null] }), new web3._extend.Method({ name: 'stop', - call: 'miner_stop', - params: 1 + call: 'miner_stop' }), new web3._extend.Method({ name: 'setEtherbase', call: 'miner_setEtherbase', params: 1, - inputFormatter: [web3._extend.formatters.formatInputInt], - outputFormatter: web3._extend.formatters.formatOutputBool + inputFormatter: [web3._extend.formatters.inputAddressFormatter] }), new web3._extend.Method({ name: 'setExtra', diff --git a/rpc/json.go b/rpc/json.go index a0bfcac049..8a3bea2eeb 100644 --- a/rpc/json.go +++ b/rpc/json.go @@ -21,6 +21,7 @@ import ( "fmt" "io" "reflect" + "strconv" "strings" "sync" @@ -40,14 +41,14 @@ const ( type JSONRequest struct { Method string `json:"method"` Version string `json:"jsonrpc"` - Id *int64 `json:"id,omitempty"` + Id json.RawMessage `json:"id,omitempty"` Payload json.RawMessage `json:"params,omitempty"` } // JSON-RPC response type JSONSuccessResponse struct { Version string `json:"jsonrpc"` - Id int64 `json:"id"` + Id interface{} `json:"id,omitempty"` Result interface{} `json:"result"` } @@ -60,9 +61,9 @@ type JSONError struct { // JSON-RPC error response type JSONErrResponse struct { - Version string `json:"jsonrpc"` - Id *int64 `json:"id,omitempty"` - Error JSONError `json:"error"` + Version string `json:"jsonrpc"` + Id interface{} `json:"id,omitempty"` + Error JSONError `json:"error"` } // JSON-RPC notification payload @@ -78,16 +79,16 @@ type jsonNotification struct { Params jsonSubscription `json:"params"` } -// jsonCodec reads and writes JSON-RPC messages to the underlying connection. It also has support for parsing arguments -// and serializing (result) objects. +// jsonCodec reads and writes JSON-RPC messages to the underlying connection. It +// also has support for parsing arguments and serializing (result) objects. type jsonCodec struct { - closed chan interface{} - closer sync.Once - d *json.Decoder - muEncoder sync.Mutex - e *json.Encoder - req JSONRequest - rw io.ReadWriteCloser + closer sync.Once // close closed channel once + closed chan interface{} // closed on Close + decMu sync.Mutex // guards d + d *json.Decoder // decodes incoming requests + encMu sync.Mutex // guards e + e *json.Encoder // encodes responses + rw io.ReadWriteCloser // connection } // NewJSONCodec creates a new RPC server codec with support for JSON-RPC 2.0 @@ -109,9 +110,13 @@ func isBatch(msg json.RawMessage) bool { return false } -// ReadRequestHeaders will read new requests without parsing the arguments. It will return a collection of requests, an -// indication if these requests are in batch form or an error when the incoming message could not be read/parsed. +// ReadRequestHeaders will read new requests without parsing the arguments. It will +// return a collection of requests, an indication if these requests are in batch +// form or an error when the incoming message could not be read/parsed. func (c *jsonCodec) ReadRequestHeaders() ([]rpcRequest, bool, RPCError) { + c.decMu.Lock() + defer c.decMu.Unlock() + var incomingMsg json.RawMessage if err := c.d.Decode(&incomingMsg); err != nil { return nil, false, &invalidRequestError{err.Error()} @@ -124,21 +129,38 @@ func (c *jsonCodec) ReadRequestHeaders() ([]rpcRequest, bool, RPCError) { return parseRequest(incomingMsg) } -// parseRequest will parse a single request from the given RawMessage. It will return the parsed request, an indication -// if the request was a batch or an error when the request could not be parsed. +// checkReqId returns an error when the given reqId isn't valid for RPC method calls. +// valid id's are strings, numbers or null +func checkReqId(reqId json.RawMessage) error { + if len(reqId) == 0 { + return fmt.Errorf("missing request id") + } + if _, err := strconv.ParseFloat(string(reqId), 64); err == nil { + return nil + } + var str string + if err := json.Unmarshal(reqId, &str); err == nil { + return nil + } + return fmt.Errorf("invalid request id") +} + +// parseRequest will parse a single request from the given RawMessage. It will return +// the parsed request, an indication if the request was a batch or an error when +// the request could not be parsed. func parseRequest(incomingMsg json.RawMessage) ([]rpcRequest, bool, RPCError) { var in JSONRequest if err := json.Unmarshal(incomingMsg, &in); err != nil { return nil, false, &invalidMessageError{err.Error()} } - if in.Id == nil { - return nil, false, &invalidMessageError{"Server cannot handle notifications"} + if err := checkReqId(in.Id); err != nil { + return nil, false, &invalidMessageError{err.Error()} } - // subscribe are special, they will always use `subscribeMethod` as service method + // subscribe are special, they will always use `subscribeMethod` as first param in the payload if in.Method == subscribeMethod { - reqs := []rpcRequest{rpcRequest{id: *in.Id, isPubSub: true}} + reqs := []rpcRequest{rpcRequest{id: &in.Id, isPubSub: true}} if len(in.Payload) > 0 { // first param must be subscription name var subscribeMethod [1]string @@ -156,7 +178,7 @@ func parseRequest(incomingMsg json.RawMessage) ([]rpcRequest, bool, RPCError) { } if in.Method == unsubscribeMethod { - return []rpcRequest{rpcRequest{id: *in.Id, isPubSub: true, + return []rpcRequest{rpcRequest{id: &in.Id, isPubSub: true, method: unsubscribeMethod, params: in.Payload}}, false, nil } @@ -167,10 +189,10 @@ func parseRequest(incomingMsg json.RawMessage) ([]rpcRequest, bool, RPCError) { } if len(in.Payload) == 0 { - return []rpcRequest{rpcRequest{service: elems[0], method: elems[1], id: *in.Id}}, false, nil + return []rpcRequest{rpcRequest{service: elems[0], method: elems[1], id: &in.Id}}, false, nil } - return []rpcRequest{rpcRequest{service: elems[0], method: elems[1], id: *in.Id, params: in.Payload}}, false, nil + return []rpcRequest{rpcRequest{service: elems[0], method: elems[1], id: &in.Id, params: in.Payload}}, false, nil } // parseBatchRequest will parse a batch request into a collection of requests from the given RawMessage, an indication @@ -183,14 +205,17 @@ func parseBatchRequest(incomingMsg json.RawMessage) ([]rpcRequest, bool, RPCErro requests := make([]rpcRequest, len(in)) for i, r := range in { - if r.Id == nil { - return nil, true, &invalidMessageError{"Server cannot handle notifications"} + if err := checkReqId(r.Id); err != nil { + return nil, false, &invalidMessageError{err.Error()} } - // (un)subscribe are special, they will always use the same service.method + id := &in[i].Id + + // subscribe are special, they will always use `subscribeMethod` as first param in the payload if r.Method == subscribeMethod { - requests[i] = rpcRequest{id: *r.Id, isPubSub: true} + requests[i] = rpcRequest{id: id, isPubSub: true} if len(r.Payload) > 0 { + // first param must be subscription name var subscribeMethod [1]string if err := json.Unmarshal(r.Payload, &subscribeMethod); err != nil { glog.V(logger.Debug).Infof("Unable to parse subscription method: %v\n", err) @@ -207,7 +232,7 @@ func parseBatchRequest(incomingMsg json.RawMessage) ([]rpcRequest, bool, RPCErro } if r.Method == unsubscribeMethod { - requests[i] = rpcRequest{id: *r.Id, isPubSub: true, method: unsubscribeMethod, params: r.Payload} + requests[i] = rpcRequest{id: id, isPubSub: true, method: unsubscribeMethod, params: r.Payload} continue } @@ -217,9 +242,9 @@ func parseBatchRequest(incomingMsg json.RawMessage) ([]rpcRequest, bool, RPCErro } if len(r.Payload) == 0 { - requests[i] = rpcRequest{service: elems[0], method: elems[1], id: *r.Id, params: nil} + requests[i] = rpcRequest{service: elems[0], method: elems[1], id: id, params: nil} } else { - requests[i] = rpcRequest{service: elems[0], method: elems[1], id: *r.Id, params: r.Payload} + requests[i] = rpcRequest{service: elems[0], method: elems[1], id: id, params: r.Payload} } } @@ -236,58 +261,38 @@ func (c *jsonCodec) ParseRequestArguments(argTypes []reflect.Type, params interf } } -func countArguments(args json.RawMessage) (int, error) { - var cnt []interface{} - if err := json.Unmarshal(args, &cnt); err != nil { - return -1, nil +// parsePositionalArguments tries to parse the given args to an array of values with the given types. +// It returns the parsed values or an error when the args could not be parsed. Missing optional arguments +// are returned as reflect.Zero values. +func parsePositionalArguments(args json.RawMessage, callbackArgs []reflect.Type) ([]reflect.Value, RPCError) { + params := make([]interface{}, 0, len(callbackArgs)) + for _, t := range callbackArgs { + params = append(params, reflect.New(t).Interface()) } - return len(cnt), nil -} - -// parsePositionalArguments tries to parse the given args to an array of values with the given types. It returns the -// parsed values or an error when the args could not be parsed. -func parsePositionalArguments(args json.RawMessage, argTypes []reflect.Type) ([]reflect.Value, RPCError) { - argValues := make([]reflect.Value, len(argTypes)) - params := make([]interface{}, len(argTypes)) - n, err := countArguments(args) - if err != nil { + if err := json.Unmarshal(args, ¶ms); err != nil { return nil, &invalidParamsError{err.Error()} } - if n != len(argTypes) { - return nil, &invalidParamsError{fmt.Sprintf("insufficient params, want %d have %d", len(argTypes), n)} - } - - for i, t := range argTypes { - if t.Kind() == reflect.Ptr { - // values must be pointers for the Unmarshal method, reflect. - // Dereference otherwise reflect.New would create **SomeType - argValues[i] = reflect.New(t.Elem()) - params[i] = argValues[i].Interface() - - // when not specified blockNumbers are by default latest (-1) - if blockNumber, ok := params[i].(*BlockNumber); ok { - *blockNumber = BlockNumber(-1) - } - } else { - argValues[i] = reflect.New(t) - params[i] = argValues[i].Interface() - // when not specified blockNumbers are by default latest (-1) - if blockNumber, ok := params[i].(*BlockNumber); ok { - *blockNumber = BlockNumber(-1) - } - } + if len(params) > len(callbackArgs) { + return nil, &invalidParamsError{fmt.Sprintf("too many params, want %d got %d", len(callbackArgs), len(params))} } - if err := json.Unmarshal(args, ¶ms); err != nil { - return nil, &invalidParamsError{err.Error()} + // assume missing params are null values + for i := len(params); i < len(callbackArgs); i++ { + params = append(params, nil) } - // Convert pointers back to values where necessary - for i, a := range argValues { - if a.Kind() != argTypes[i].Kind() { - argValues[i] = reflect.Indirect(argValues[i]) + argValues := make([]reflect.Value, len(params)) + for i, p := range params { + // verify that JSON null values are only supplied for optional arguments (ptr types) + if p == nil && callbackArgs[i].Kind() != reflect.Ptr { + return nil, &invalidParamsError{fmt.Sprintf("invalid or missing value for params[%d]", i)} + } + if p == nil { + argValues[i] = reflect.Zero(callbackArgs[i]) + } else { // deref pointers values creates previously with reflect.New + argValues[i] = reflect.ValueOf(p).Elem() } } @@ -295,7 +300,7 @@ func parsePositionalArguments(args json.RawMessage, argTypes []reflect.Type) ([] } // CreateResponse will create a JSON-RPC success response with the given id and reply as result. -func (c *jsonCodec) CreateResponse(id int64, reply interface{}) interface{} { +func (c *jsonCodec) CreateResponse(id interface{}, reply interface{}) interface{} { if isHexNum(reflect.TypeOf(reply)) { return &JSONSuccessResponse{Version: jsonRPCVersion, Id: id, Result: fmt.Sprintf(`%#x`, reply)} } @@ -303,13 +308,13 @@ func (c *jsonCodec) CreateResponse(id int64, reply interface{}) interface{} { } // CreateErrorResponse will create a JSON-RPC error response with the given id and error. -func (c *jsonCodec) CreateErrorResponse(id *int64, err RPCError) interface{} { +func (c *jsonCodec) CreateErrorResponse(id interface{}, err RPCError) interface{} { return &JSONErrResponse{Version: jsonRPCVersion, Id: id, Error: JSONError{Code: err.Code(), Message: err.Error()}} } // CreateErrorResponseWithInfo will create a JSON-RPC error response with the given id and error. // info is optional and contains additional information about the error. When an empty string is passed it is ignored. -func (c *jsonCodec) CreateErrorResponseWithInfo(id *int64, err RPCError, info interface{}) interface{} { +func (c *jsonCodec) CreateErrorResponseWithInfo(id interface{}, err RPCError, info interface{}) interface{} { return &JSONErrResponse{Version: jsonRPCVersion, Id: id, Error: JSONError{Code: err.Code(), Message: err.Error(), Data: info}} } @@ -327,8 +332,8 @@ func (c *jsonCodec) CreateNotification(subid string, event interface{}) interfac // Write message to client func (c *jsonCodec) Write(res interface{}) error { - c.muEncoder.Lock() - defer c.muEncoder.Unlock() + c.encMu.Lock() + defer c.encMu.Unlock() return c.e.Encode(res) } diff --git a/rpc/json_test.go b/rpc/json_test.go index 39aae1f54e..418b0261d7 100644 --- a/rpc/json_test.go +++ b/rpc/json_test.go @@ -3,7 +3,9 @@ package rpc import ( "bufio" "bytes" + "encoding/json" "reflect" + "strconv" "testing" ) @@ -51,8 +53,16 @@ func TestJSONRequestParsing(t *testing.T) { t.Fatalf("Expected method 'Add' but got '%s'", requests[0].method) } - if requests[0].id != 1234 { - t.Fatalf("Expected id 1234 but got %d", requests[0].id) + if rawId, ok := requests[0].id.(*json.RawMessage); ok { + id, e := strconv.ParseInt(string(*rawId), 0, 64) + if e != nil { + t.Fatalf("%v", e) + } + if id != 1234 { + t.Fatalf("Expected id 1234 but got %s", id) + } + } else { + t.Fatalf("invalid request, expected *json.RawMesage got %T", requests[0].id) } var arg int @@ -71,3 +81,82 @@ func TestJSONRequestParsing(t *testing.T) { t.Fatalf("expected %d == 11 && %d == 22", v[0].Int(), v[1].Int()) } } + +func TestJSONRequestParamsParsing(t *testing.T) { + + var ( + stringT = reflect.TypeOf("") + intT = reflect.TypeOf(0) + intPtrT = reflect.TypeOf(new(int)) + + stringV = reflect.ValueOf("abc") + i = 1 + intV = reflect.ValueOf(i) + intPtrV = reflect.ValueOf(&i) + ) + + var validTests = []struct { + input string + argTypes []reflect.Type + expected []reflect.Value + }{ + {`[]`, []reflect.Type{}, []reflect.Value{}}, + {`[]`, []reflect.Type{intPtrT}, []reflect.Value{intPtrV}}, + {`[1]`, []reflect.Type{intT}, []reflect.Value{intV}}, + {`[1,"abc"]`, []reflect.Type{intT, stringT}, []reflect.Value{intV, stringV}}, + {`[null]`, []reflect.Type{intPtrT}, []reflect.Value{intPtrV}}, + {`[null,"abc"]`, []reflect.Type{intPtrT, stringT, intPtrT}, []reflect.Value{intPtrV, stringV, intPtrV}}, + {`[null,"abc",null]`, []reflect.Type{intPtrT, stringT, intPtrT}, []reflect.Value{intPtrV, stringV, intPtrV}}, + } + + codec := jsonCodec{} + + for _, test := range validTests { + params := (json.RawMessage)([]byte(test.input)) + args, err := codec.ParseRequestArguments(test.argTypes, params) + + if err != nil { + t.Fatal(err) + } + + var match []interface{} + json.Unmarshal([]byte(test.input), &match) + + if len(args) != len(test.argTypes) { + t.Fatalf("expected %d parsed args, got %d", len(test.argTypes), len(args)) + } + + for i, arg := range args { + expected := test.expected[i] + + if arg.Kind() != expected.Kind() { + t.Errorf("expected type for param %d in %s", i, test.input) + } + + if arg.Kind() == reflect.Int && arg.Int() != expected.Int() { + t.Errorf("expected int(%d), got int(%d) in %s", expected.Int(), arg.Int(), test.input) + } + + if arg.Kind() == reflect.String && arg.String() != expected.String() { + t.Errorf("expected string(%s), got string(%s) in %s", expected.String(), arg.String(), test.input) + } + } + } + + var invalidTests = []struct { + input string + argTypes []reflect.Type + }{ + {`[]`, []reflect.Type{intT}}, + {`[null]`, []reflect.Type{intT}}, + {`[1]`, []reflect.Type{stringT}}, + {`[1,2]`, []reflect.Type{stringT}}, + {`["abc", null]`, []reflect.Type{stringT, intT}}, + } + + for i, test := range invalidTests { + if _, err := codec.ParseRequestArguments(test.argTypes, test.input); err == nil { + t.Errorf("expected test %d - %s to fail", i, test.input) + } + } +} diff --git a/rpc/server_test.go b/rpc/server_test.go index c60db38df2..de47e1afd0 100644 --- a/rpc/server_test.go +++ b/rpc/server_test.go @@ -18,10 +18,9 @@ package rpc import ( "encoding/json" - "fmt" + "net" "reflect" "testing" - "time" "golang.org/x/net/context" ) @@ -69,10 +68,6 @@ func (s *Service) Subscription(ctx context.Context) (Subscription, error) { return nil, nil } -func (s *Service) SubsriptionWithArgs(ctx context.Context, a, b int) (Subscription, error) { - return nil, nil -} - func TestServerRegisterName(t *testing.T) { server := NewServer() service := new(Service) @@ -94,182 +89,67 @@ func TestServerRegisterName(t *testing.T) { t.Errorf("Expected 4 callbacks for service 'calc', got %d", len(svc.callbacks)) } - if len(svc.subscriptions) != 2 { - t.Errorf("Expected 2 subscriptions for service 'calc', got %d", len(svc.subscriptions)) + if len(svc.subscriptions) != 1 { + t.Errorf("Expected 1 subscription for service 'calc', got %d", len(svc.subscriptions)) } } -// dummy codec used for testing RPC method execution -type ServerTestCodec struct { - counter int - input []byte - output string - closer chan interface{} -} - -func (c *ServerTestCodec) ReadRequestHeaders() ([]rpcRequest, bool, RPCError) { - c.counter += 1 +func testServerMethodExecution(t *testing.T, method string) { + server := NewServer() + service := new(Service) - if c.counter == 1 { - var req JSONRequest - json.Unmarshal(c.input, &req) - return []rpcRequest{rpcRequest{id: *req.Id, isPubSub: false, service: "test", method: req.Method, params: req.Payload}}, false, nil + if err := server.RegisterName("test", service); err != nil { + t.Fatalf("%v", err) } - // requests are executes in parallel, wait a bit before returning an error so that the previous request has time to - // be executed - timer := time.NewTimer(time.Duration(2) * time.Second) - <-timer.C + stringArg := "string arg" + intArg := 1122 + argsArg := &Args{"abcde"} + params := []interface{}{stringArg, intArg, argsArg} - return nil, false, &invalidRequestError{"connection closed"} -} + request := map[string]interface{}{ + "id": 12345, + "method": "test_" + method, + "version": "2.0", + "params": params, + } -func (c *ServerTestCodec) ParseRequestArguments(argTypes []reflect.Type, payload interface{}) ([]reflect.Value, RPCError) { + clientConn, serverConn := net.Pipe() + defer clientConn.Close() - args, _ := payload.(json.RawMessage) + go server.ServeCodec(NewJSONCodec(serverConn), OptionMethodInvocation) - argValues := make([]reflect.Value, len(argTypes)) - params := make([]interface{}, len(argTypes)) + out := json.NewEncoder(clientConn) + in := json.NewDecoder(clientConn) - n, err := countArguments(args) - if err != nil { - return nil, &invalidParamsError{err.Error()} + if err := out.Encode(request); err != nil { + t.Fatal(err) } - if n != len(argTypes) { - return nil, &invalidParamsError{fmt.Sprintf("insufficient params, want %d have %d", len(argTypes), n)} + response := JSONSuccessResponse{Result: &Result{}} + if err := in.Decode(&response); err != nil { + t.Fatal(err) } - for i, t := range argTypes { - if t.Kind() == reflect.Ptr { - // values must be pointers for the Unmarshal method, reflect. - // Dereference otherwise reflect.New would create **SomeType - argValues[i] = reflect.New(t.Elem()) - params[i] = argValues[i].Interface() - - // when not specified blockNumbers are by default latest (-1) - if blockNumber, ok := params[i].(*BlockNumber); ok { - *blockNumber = BlockNumber(-1) - } - } else { - argValues[i] = reflect.New(t) - params[i] = argValues[i].Interface() - - // when not specified blockNumbers are by default latest (-1) - if blockNumber, ok := params[i].(*BlockNumber); ok { - *blockNumber = BlockNumber(-1) - } + if result, ok := response.Result.(*Result); ok { + if result.String != stringArg { + t.Errorf("expected %s, got : %s\n", stringArg, result.String) } - } - - if err := json.Unmarshal(args, ¶ms); err != nil { - return nil, &invalidParamsError{err.Error()} - } - - // Convert pointers back to values where necessary - for i, a := range argValues { - if a.Kind() != argTypes[i].Kind() { - argValues[i] = reflect.Indirect(argValues[i]) + if result.Int != intArg { + t.Errorf("expected %d, got %d\n", intArg, result.Int) } - } - - return argValues, nil -} - -func (c *ServerTestCodec) CreateResponse(id int64, reply interface{}) interface{} { - return &JSONSuccessResponse{Version: jsonRPCVersion, Id: id, Result: reply} -} - -func (c *ServerTestCodec) CreateErrorResponse(id *int64, err RPCError) interface{} { - return &JSONErrResponse{Version: jsonRPCVersion, Id: id, Error: JSONError{Code: err.Code(), Message: err.Error()}} -} - -func (c *ServerTestCodec) CreateErrorResponseWithInfo(id *int64, err RPCError, info interface{}) interface{} { - return &JSONErrResponse{Version: jsonRPCVersion, Id: id, - Error: JSONError{Code: err.Code(), Message: err.Error(), Data: info}} -} - -func (c *ServerTestCodec) CreateNotification(subid string, event interface{}) interface{} { - return &jsonNotification{Version: jsonRPCVersion, Method: notificationMethod, - Params: jsonSubscription{Subscription: subid, Result: event}} -} - -func (c *ServerTestCodec) Write(msg interface{}) error { - if len(c.output) == 0 { // only capture first response - if o, err := json.Marshal(msg); err != nil { - return err - } else { - c.output = string(o) + if !reflect.DeepEqual(result.Args, argsArg) { + t.Errorf("expected %v, got %v\n", argsArg, result) } + } else { + t.Fatalf("invalid response: expected *Result - got: %T", response.Result) } - - return nil -} - -func (c *ServerTestCodec) Close() { - close(c.closer) -} - -func (c *ServerTestCodec) Closed() <-chan interface{} { - return c.closer } func TestServerMethodExecution(t *testing.T) { - server := NewServer() - service := new(Service) - - if err := server.RegisterName("test", service); err != nil { - t.Fatalf("%v", err) - } - - id := int64(12345) - req := JSONRequest{ - Method: "echo", - Version: "2.0", - Id: &id, - } - args := []interface{}{"string arg", 1122, &Args{"qwerty"}} - req.Payload, _ = json.Marshal(&args) - - input, _ := json.Marshal(&req) - codec := &ServerTestCodec{input: input, closer: make(chan interface{})} - go server.ServeCodec(codec, OptionMethodInvocation) - - <-codec.closer - - expected := `{"jsonrpc":"2.0","id":12345,"result":{"String":"string arg","Int":1122,"Args":{"S":"qwerty"}}}` - - if expected != codec.output { - t.Fatalf("expected %s, got %s\n", expected, codec.output) - } + testServerMethodExecution(t, "echo") } func TestServerMethodWithCtx(t *testing.T) { - server := NewServer() - service := new(Service) - - if err := server.RegisterName("test", service); err != nil { - t.Fatalf("%v", err) - } - - id := int64(12345) - req := JSONRequest{ - Method: "echoWithCtx", - Version: "2.0", - Id: &id, - } - args := []interface{}{"string arg", 1122, &Args{"qwerty"}} - req.Payload, _ = json.Marshal(&args) - - input, _ := json.Marshal(&req) - codec := &ServerTestCodec{input: input, closer: make(chan interface{})} - go server.ServeCodec(codec, OptionMethodInvocation) - - <-codec.closer - - expected := `{"jsonrpc":"2.0","id":12345,"result":{"String":"string arg","Int":1122,"Args":{"S":"qwerty"}}}` - - if expected != codec.output { - t.Fatalf("expected %s, got %s\n", expected, codec.output) - } + testServerMethodExecution(t, "echoWithCtx") } diff --git a/rpc/types.go b/rpc/types.go index 596fdf2649..a1f36fbd26 100644 --- a/rpc/types.go +++ b/rpc/types.go @@ -56,7 +56,7 @@ type service struct { // serverRequest is an incoming request type serverRequest struct { - id int64 + id interface{} svcname string rcvr reflect.Value callb *callback @@ -85,7 +85,7 @@ type Server struct { type rpcRequest struct { service string method string - id int64 + id interface{} isPubSub bool params interface{} } @@ -106,12 +106,12 @@ type ServerCodec interface { ReadRequestHeaders() ([]rpcRequest, bool, RPCError) // Parse request argument to the given types ParseRequestArguments([]reflect.Type, interface{}) ([]reflect.Value, RPCError) - // Assemble success response - CreateResponse(int64, interface{}) interface{} - // Assemble error response - CreateErrorResponse(*int64, RPCError) interface{} + // Assemble success response, expects response id and payload + CreateResponse(interface{}, interface{}) interface{} + // Assemble error response, expects response id and error + CreateErrorResponse(interface{}, RPCError) interface{} // Assemble error response with extra information about the error through info - CreateErrorResponseWithInfo(id *int64, err RPCError, info interface{}) interface{} + CreateErrorResponseWithInfo(id interface{}, err RPCError, info interface{}) interface{} // Create notification response CreateNotification(string, interface{}) interface{} // Write msg to client. @@ -207,43 +207,6 @@ func (h *HexNumber) BigInt() *big.Int { return (*big.Int)(h) } -type Number int64 - -func (n *Number) UnmarshalJSON(data []byte) error { - input := strings.TrimSpace(string(data)) - - if len(input) >= 2 && input[0] == '"' && input[len(input)-1] == '"' { - input = input[1 : len(input)-1] - } - - if len(input) == 0 { - *n = Number(latestBlockNumber.Int64()) - return nil - } - - in := new(big.Int) - _, ok := in.SetString(input, 0) - - if !ok { // test if user supplied string tag - return fmt.Errorf(`invalid number %s`, data) - } - - if in.Cmp(earliestBlockNumber) >= 0 && in.Cmp(maxBlockNumber) <= 0 { - *n = Number(in.Int64()) - return nil - } - - return fmt.Errorf("blocknumber not in range [%d, %d]", earliestBlockNumber, maxBlockNumber) -} - -func (n *Number) Int64() int64 { - return *(*int64)(n) -} - -func (n *Number) BigInt() *big.Int { - return big.NewInt(n.Int64()) -} - var ( pendingBlockNumber = big.NewInt(-2) latestBlockNumber = big.NewInt(-1) diff --git a/rpc/utils.go b/rpc/utils.go index d43c504957..86938e9b37 100644 --- a/rpc/utils.go +++ b/rpc/utils.go @@ -232,7 +232,7 @@ func newSubscriptionID() (string, error) { // on which the given client connects. func SupportedModules(client Client) (map[string]string, error) { req := JSONRequest{ - Id: new(int64), + Id: []byte("1"), Version: "2.0", Method: "rpc_modules", } diff --git a/rpc/websocket.go b/rpc/websocket.go index 499eedabe6..1303f98db1 100644 --- a/rpc/websocket.go +++ b/rpc/websocket.go @@ -88,10 +88,10 @@ func wsHandshakeValidator(allowedOrigins []string) func(*websocket.Config, *http } // NewWSServer creates a new websocket RPC server around an API provider. -func NewWSServer(cors string, handler *Server) *http.Server { +func NewWSServer(allowedOrigins string, handler *Server) *http.Server { return &http.Server{ Handler: websocket.Server{ - Handshake: wsHandshakeValidator(strings.Split(cors, ",")), + Handshake: wsHandshakeValidator(strings.Split(allowedOrigins, ",")), Handler: func(conn *websocket.Conn) { handler.ServeCodec(NewJSONCodec(&wsReaderWriterCloser{conn}), OptionMethodInvocation|OptionSubscriptions) diff --git a/whisper/api.go b/whisper/api.go index 575b9bc894..1b82ea3c05 100644 --- a/whisper/api.go +++ b/whisper/api.go @@ -84,7 +84,7 @@ type NewFilterArgs struct { } // NewWhisperFilter creates and registers a new message filter to watch for inbound whisper messages. -func (s *PublicWhisperAPI) NewFilter(args *NewFilterArgs) (*rpc.HexNumber, error) { +func (s *PublicWhisperAPI) NewFilter(args NewFilterArgs) (*rpc.HexNumber, error) { if s.w == nil { return nil, whisperOffLineErr } @@ -171,7 +171,7 @@ type PostArgs struct { } // Post injects a message into the whisper network for distribution. -func (s *PublicWhisperAPI) Post(args *PostArgs) (bool, error) { +func (s *PublicWhisperAPI) Post(args PostArgs) (bool, error) { if s.w == nil { return false, whisperOffLineErr }