diff options
50 files changed, 5524 insertions, 4243 deletions
@@ -4,8 +4,8 @@ Ethereum Go Client © 2014 Jeffrey Wilcke. | Linux | OSX | Windows | Tests ----------|---------|-----|---------|------ -develop | [![Build+Status](https://build.ethdev.com/buildstatusimage?builder=Linux%20Go%20develop%20branch)](https://build.ethdev.com/builders/Linux%20Go%20develop%20branch/builds/-1) | [![Build+Status](https://build.ethdev.com/buildstatusimage?builder=Linux%20Go%20develop%20branch)](https://build.ethdev.com/builders/OSX%20Go%20develop%20branch/builds/-1) | N/A | [![Buildr+Status](https://travis-ci.org/ethereum/go-ethereum.svg?branch=develop)](https://travis-ci.org/ethereum/go-ethereum) [![Coverage Status](https://coveralls.io/repos/ethereum/go-ethereum/badge.svg?branch=develop)](https://coveralls.io/r/ethereum/go-ethereum?branch=develop) -master | [![Build+Status](https://build.ethdev.com/buildstatusimage?builder=Linux%20Go%20master%20branch)](https://build.ethdev.com/builders/Linux%20Go%20master%20branch/builds/-1) | [![Build+Status](https://build.ethdev.com/buildstatusimage?builder=OSX%20Go%20master%20branch)](https://build.ethdev.com/builders/OSX%20Go%20master%20branch/builds/-1) | N/A | [![Buildr+Status](https://travis-ci.org/ethereum/go-ethereum.svg?branch=master)](https://travis-ci.org/ethereum/go-ethereum) [![Coverage Status](https://coveralls.io/repos/ethereum/go-ethereum/badge.svg?branch=master)](https://coveralls.io/r/ethereum/go-ethereum?branch=master) +develop | [![Build+Status](https://build.ethdev.com/buildstatusimage?builder=Linux%20Go%20develop%20branch)](https://build.ethdev.com/builders/Linux%20Go%20develop%20branch/builds/-1) | [![Build+Status](https://build.ethdev.com/buildstatusimage?builder=Linux%20Go%20develop%20branch)](https://build.ethdev.com/builders/OSX%20Go%20develop%20branch/builds/-1) | [![Build+Status](https://build.ethdev.com/buildstatusimage?builder=Windows%20Go%20develop%20branch)](https://build.ethdev.com/builders/Windows%20Go%20develop%20branch/builds/-1) | [![Buildr+Status](https://travis-ci.org/ethereum/go-ethereum.svg?branch=develop)](https://travis-ci.org/ethereum/go-ethereum) [![Coverage Status](https://coveralls.io/repos/ethereum/go-ethereum/badge.svg?branch=develop)](https://coveralls.io/r/ethereum/go-ethereum?branch=develop) +master | [![Build+Status](https://build.ethdev.com/buildstatusimage?builder=Linux%20Go%20master%20branch)](https://build.ethdev.com/builders/Linux%20Go%20master%20branch/builds/-1) | [![Build+Status](https://build.ethdev.com/buildstatusimage?builder=OSX%20Go%20master%20branch)](https://build.ethdev.com/builders/OSX%20Go%20master%20branch/builds/-1) | [![Build+Status](https://build.ethdev.com/buildstatusimage?builder=Windows%20Go%20master%20branch)](https://build.ethdev.com/builders/Windows%20Go%20master%20branch/builds/-1) | [![Buildr+Status](https://travis-ci.org/ethereum/go-ethereum.svg?branch=master)](https://travis-ci.org/ethereum/go-ethereum) [![Coverage Status](https://coveralls.io/repos/ethereum/go-ethereum/badge.svg?branch=master)](https://coveralls.io/r/ethereum/go-ethereum?branch=master) [![Bugs](https://badge.waffle.io/ethereum/go-ethereum.png?label=bug&title=Bugs)](https://waffle.io/ethereum/go-ethereum) [![Stories in Ready](https://badge.waffle.io/ethereum/go-ethereum.png?label=ready&title=Ready)](https://waffle.io/ethereum/go-ethereum) @@ -40,7 +40,7 @@ godep go build -v ./cmd/geth Instead of `build`, you can use `install` which will also install the resulting binary. -For prerequisites and detailed build instructions please see the [Wiki](https://github.com/ethereum/go-ethereum/wiki/Building-Ethereum(Go)) +For prerequisites and detailed build instructions please see the [Wiki](https://github.com/ethereum/go-ethereum/wiki/Building-Ethereum) If you intend to develop on go-ethereum, check the [Developers' Guide](https://github.com/ethereum/go-ethereum/wiki/Developers'-Guide) diff --git a/blockpool/blockpool.go b/blockpool/blockpool.go deleted file mode 100644 index a60b6f43c..000000000 --- a/blockpool/blockpool.go +++ /dev/null @@ -1,911 +0,0 @@ -package blockpool - -import ( - "fmt" - "math/big" - "sync" - "time" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core" - "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/errs" - "github.com/ethereum/go-ethereum/event" - "github.com/ethereum/go-ethereum/logger" - "github.com/ethereum/go-ethereum/logger/glog" - "github.com/ethereum/go-ethereum/pow" -) - -var ( - // max number of block hashes sent in one request - blockHashesBatchSize = 256 - // max number of blocks sent in one request - blockBatchSize = 64 - // interval between two consecutive block checks (and requests) - blocksRequestInterval = 3 * time.Second - // level of redundancy in block requests sent - blocksRequestRepetition = 1 - // interval between two consecutive block hash checks (and requests) - blockHashesRequestInterval = 3 * time.Second - // max number of idle iterations, ie., check through a section without new blocks coming in - blocksRequestMaxIdleRounds = 20 - // timeout interval: max time allowed for peer without sending a block hash - blockHashesTimeout = 60 * time.Second - // timeout interval: max time allowed for peer without sending a block - blocksTimeout = 60 * time.Second - // timeout interval: max time allowed for best peer to remain idle (not send new block after sync complete) - idleBestPeerTimeout = 60 * time.Second - // duration of suspension after peer fatal error during which peer is not allowed to reconnect - peerSuspensionInterval = 300 * time.Second - // status is logged every statusUpdateInterval - statusUpdateInterval = 3 * time.Second - // - nodeCacheSize = 1000 -) - -// blockpool config, values default to constants -type Config struct { - BlockHashesBatchSize int - BlockBatchSize int - BlocksRequestRepetition int - BlocksRequestMaxIdleRounds int - NodeCacheSize int - BlockHashesRequestInterval time.Duration - BlocksRequestInterval time.Duration - BlockHashesTimeout time.Duration - BlocksTimeout time.Duration - IdleBestPeerTimeout time.Duration - PeerSuspensionInterval time.Duration - StatusUpdateInterval time.Duration -} - -// blockpool errors -const ( - ErrInvalidBlock = iota - ErrInvalidPoW - ErrInsufficientChainInfo - ErrIdleTooLong - ErrIncorrectTD - ErrUnrequestedBlock -) - -// error descriptions -var errorToString = map[int]string{ - ErrInvalidBlock: "Invalid block", // fatal - ErrInvalidPoW: "Invalid PoW", // fatal - ErrInsufficientChainInfo: "Insufficient chain info", // fatal - ErrIdleTooLong: "Idle too long", // fatal - ErrIncorrectTD: "Incorrect Total Difficulty", // should be fatal, not now temporarily - ErrUnrequestedBlock: "Unrequested block", -} - -// error severity -func severity(code int) logger.LogLevel { - switch code { - case ErrIncorrectTD: - return logger.WarnLevel - case ErrUnrequestedBlock: - return logger.WarnLevel - default: - return logger.ErrorLevel - } -} - -// init initialises the Config, zero values fall back to constants -func (self *Config) init() { - if self.BlockHashesBatchSize == 0 { - self.BlockHashesBatchSize = blockHashesBatchSize - } - if self.BlockBatchSize == 0 { - self.BlockBatchSize = blockBatchSize - } - if self.BlocksRequestRepetition == 0 { - self.BlocksRequestRepetition = blocksRequestRepetition - } - if self.BlocksRequestMaxIdleRounds == 0 { - self.BlocksRequestMaxIdleRounds = blocksRequestMaxIdleRounds - } - if self.BlockHashesRequestInterval == 0 { - self.BlockHashesRequestInterval = blockHashesRequestInterval - } - if self.BlocksRequestInterval == 0 { - self.BlocksRequestInterval = blocksRequestInterval - } - if self.BlockHashesTimeout == 0 { - self.BlockHashesTimeout = blockHashesTimeout - } - if self.BlocksTimeout == 0 { - self.BlocksTimeout = blocksTimeout - } - if self.IdleBestPeerTimeout == 0 { - self.IdleBestPeerTimeout = idleBestPeerTimeout - } - if self.PeerSuspensionInterval == 0 { - self.PeerSuspensionInterval = peerSuspensionInterval - } - if self.NodeCacheSize == 0 { - self.NodeCacheSize = nodeCacheSize - } - if self.StatusUpdateInterval == 0 { - self.StatusUpdateInterval = statusUpdateInterval - } -} - -// node is the basic unit of the internal model of block chain/tree in the blockpool -type node struct { - lock sync.RWMutex - hash common.Hash - block *types.Block - hashBy string - blockBy string - peers map[string]bool - td *big.Int -} - -type index struct { - int -} - -// entry is the struct kept and indexed in the pool -type entry struct { - node *node - section *section - index *index -} - -type BlockPool struct { - Config *Config - - // the minimal interface with blockchain manager - hasBlock func(hash common.Hash) bool // query if block is known - insertChain func(types.Blocks) error // add section to blockchain - verifyPoW func(pow.Block) bool // soft PoW verification - chainEvents *event.TypeMux // ethereum eventer for chainEvents - - tdSub event.Subscription // subscription to core.ChainHeadEvent - td *big.Int // our own total difficulty - - pool map[common.Hash]*entry // the actual blockpool - peers *peers // peers manager in peers.go - - status *status // info about blockpool (UI interface) in status.go - - lock sync.RWMutex - chainLock sync.RWMutex - // alloc-easy pool of hash slices - hashSlicePool chan []common.Hash - - nodeCache map[common.Hash]*node - nodeCacheLock sync.RWMutex - nodeCacheList []common.Hash - - // waitgroup is used in tests to wait for result-critical routines - // as well as in determining idle / syncing status - wg sync.WaitGroup // - quit chan bool // chan used for quitting parallel routines - running bool // -} - -// public constructor -// after blockpool returned, config can be set -// BlockPool.Start will call Config.init to set missing values -func New( - hasBlock func(hash common.Hash) bool, - insertChain func(types.Blocks) error, - verifyPoW func(pow.Block) bool, - chainEvents *event.TypeMux, - td *big.Int, -) *BlockPool { - - return &BlockPool{ - Config: &Config{}, - hasBlock: hasBlock, - insertChain: insertChain, - verifyPoW: verifyPoW, - chainEvents: chainEvents, - td: td, - } -} - -// allows restart -func (self *BlockPool) Start() { - self.lock.Lock() - defer self.lock.Unlock() - - if self.running { - return - } - - // set missing values - self.Config.init() - - self.hashSlicePool = make(chan []common.Hash, 150) - self.nodeCache = make(map[common.Hash]*node) - self.status = newStatus() - self.quit = make(chan bool) - self.pool = make(map[common.Hash]*entry) - self.running = true - - self.peers = &peers{ - errors: &errs.Errors{ - Package: "Blockpool", - Errors: errorToString, - Level: severity, - }, - peers: make(map[string]*peer), - blacklist: make(map[string]time.Time), - status: self.status, - bp: self, - } - - // subscribe and listen to core.ChainHeadEvent{} for uptodate TD - self.tdSub = self.chainEvents.Subscribe(core.ChainHeadEvent{}) - - // status update interval - timer := time.NewTicker(self.Config.StatusUpdateInterval) - go func() { - for { - select { - case <-self.quit: - return - case event := <-self.tdSub.Chan(): - if ev, ok := event.(core.ChainHeadEvent); ok { - td := ev.Block.Td - var height *big.Int - if (ev.Block.HeaderHash == common.Hash{}) { - height = ev.Block.Header().Number - } - glog.V(logger.Detail).Infof("ChainHeadEvent: height: %v, td: %v, hash: %s", height, td, hex(ev.Block.Hash())) - self.setTD(td) - self.peers.lock.Lock() - - if best := self.peers.best; best != nil { - // only switch if we strictly go above otherwise we may stall if only - if td.Cmp(best.td) > 0 { - self.peers.best = nil - self.switchPeer(best, nil) - } - } - self.peers.lock.Unlock() - } - case <-timer.C: - glog.V(logger.Detail).Infof("status:\n%v", self.Status()) - } - } - }() - glog.V(logger.Info).Infoln("Blockpool started") -} - -func (self *BlockPool) Stop() { - self.lock.Lock() - if !self.running { - self.lock.Unlock() - return - } - self.running = false - - self.lock.Unlock() - - glog.V(logger.Info).Infoln("Stopping...") - - self.tdSub.Unsubscribe() - close(self.quit) - - self.lock.Lock() - self.peers = nil - self.pool = nil - self.lock.Unlock() - - glog.V(logger.Info).Infoln("Stopped") -} - -// Wait blocks until active processes finish -func (self *BlockPool) Wait(t time.Duration) { - self.lock.Lock() - if !self.running { - self.lock.Unlock() - return - } - self.lock.Unlock() - - glog.V(logger.Info).Infoln("Waiting for processes to complete...") - w := make(chan bool) - go func() { - self.wg.Wait() - close(w) - }() - - select { - case <-w: - glog.V(logger.Info).Infoln("Processes complete") - case <-time.After(t): - glog.V(logger.Warn).Infoln("Timeout") - } -} - -/* -AddPeer is called by the eth protocol instance running on the peer after -the status message has been received with total difficulty and current block hash - -Called a second time with the same peer id, it is used to update chain info for a peer. -This is used when a new (mined) block message is received. - -RemovePeer needs to be called when the peer disconnects. - -Peer info is currently not persisted across disconnects (or sessions) except for suspension - -*/ -func (self *BlockPool) AddPeer( - - td *big.Int, currentBlockHash common.Hash, - peerId string, - requestBlockHashes func(common.Hash) error, - requestBlocks func([]common.Hash) error, - peerError func(*errs.Error), - -) (best bool, suspended bool) { - - return self.peers.addPeer(td, currentBlockHash, peerId, requestBlockHashes, requestBlocks, peerError) -} - -// RemovePeer needs to be called when the peer disconnects -func (self *BlockPool) RemovePeer(peerId string) { - self.peers.removePeer(peerId, true) -} - -/* -AddBlockHashes - -Entry point for eth protocol to add block hashes received via BlockHashesMsg - -Only hashes from the best peer are handled - -Initiates further hash requests until a known parent is reached (unless cancelled by a peerSwitch event, i.e., when a better peer becomes best peer) -Launches all block request processes on each chain section - -The first argument is an iterator function. Using this block hashes are decoded from the rlp message payload on demand. As a result, AddBlockHashes needs to run synchronously for one peer since the message is discarded if the caller thread returns. -*/ -func (self *BlockPool) AddBlockHashes(next func() (common.Hash, bool), peerId string) { - - bestpeer, best := self.peers.getPeer(peerId) - if !best { - return - } - // bestpeer is still the best peer - - self.wg.Add(1) - defer func() { self.wg.Done() }() - - self.status.lock.Lock() - self.status.activePeers[bestpeer.id]++ - self.status.lock.Unlock() - - var n int - var hash common.Hash - var ok, headSection, peerswitch bool - var sec, child, parent *section - var entry *entry - var nodes []*node - - hash, ok = next() - bestpeer.lock.RLock() - - glog.V(logger.Debug).Infof("AddBlockHashes: peer <%s> starting from [%s] (peer head: %s)", peerId, hex(bestpeer.parentHash), hex(bestpeer.currentBlockHash)) - - // first check if we are building the head section of a peer's chain - if bestpeer.parentHash == hash { - if self.hasBlock(bestpeer.currentBlockHash) { - bestpeer.lock.RUnlock() - return - } - /* - When peer is promoted in switchPeer, a new header section process is launched. - Once the head section skeleton is actually created here, it is signaled to the process - so that it can quit. - In the special case that the node for parent of the head block is found in the blockpool - (with or without fetched block), a singleton section containing only the head block node is created. - */ - headSection = true - if entry := self.get(bestpeer.currentBlockHash); entry == nil { - glog.V(logger.Detail).Infof("AddBlockHashes: peer <%s> (head: %s) head section starting from [%s] ", peerId, hex(bestpeer.currentBlockHash), hex(bestpeer.parentHash)) - // if head block is not yet in the pool, create entry and start node list for section - self.nodeCacheLock.Lock() - n := self.findOrCreateNode(bestpeer.currentBlockHash, peerId) - n.block = bestpeer.currentBlock - n.blockBy = peerId - n.td = bestpeer.td - self.nodeCacheLock.Unlock() - - // nodes is a list of nodes in one section ordered top-bottom (old to young) - nodes = append(nodes, n) - } else { - // otherwise set child section iff found node is the root of a section - // this is a possible scenario when a singleton head section was created - // on an earlier occasion when this peer or another with the same block was best peer - if entry.node == entry.section.bottom { - child = entry.section - glog.V(logger.Detail).Infof("AddBlockHashes: peer <%s>: connects to child section root %s", peerId, hex(bestpeer.currentBlockHash)) - } - } - } else { - // otherwise : we are not building the head section of the peer - glog.V(logger.Detail).Infof("AddBlockHashes: peer <%s> (head: %s) section starting from [%s] ", peerId, hex(bestpeer.currentBlockHash), hex(hash)) - } - // the switch channel signals peerswitch event - bestpeer.lock.RUnlock() - - // iterate over hashes coming from peer (first round we have hash set above) -LOOP: - for ; ok; hash, ok = next() { - n++ - select { - case <-self.quit: - // global quit for blockpool - return - - case <-bestpeer.switchC: - // if the peer is demoted, no more hashes read - glog.V(logger.Detail).Infof("AddBlockHashes: demoted peer <%s> (head: %s)", peerId, hex(bestpeer.currentBlockHash), hex(hash)) - peerswitch = true - break LOOP - default: - } - - // if we reach the blockchain we stop reading further blockhashes - if self.hasBlock(hash) { - // check if known block connecting the downloaded chain to our blockchain - glog.V(logger.Detail).Infof("AddBlockHashes: peer <%s> (head: %s) found block %s in the blockchain", peerId, hex(bestpeer.currentBlockHash), hex(hash)) - if len(nodes) == 1 { - glog.V(logger.Detail).Infof("AddBlockHashes: singleton section pushed to blockchain peer <%s> (head: %s) found block %s in the blockchain", peerId, hex(bestpeer.currentBlockHash), hex(hash)) - - // create new section if needed and push it to the blockchain - sec = self.newSection(nodes) - sec.addSectionToBlockChain(bestpeer) - } else { - - /* - not added hash yet but according to peer child section built - earlier chain connects with blockchain - this maybe a potential vulnarability - the root block arrives (or already there but its parenthash was not pointing to known block in the blockchain) - we start inserting -> error -> remove the entire chain - instead of punishing this peer - solution: when switching peers always make sure best peers own head block - and td together with blockBy are recorded on the node - */ - if len(nodes) == 0 && child != nil { - glog.V(logger.Detail).Infof("AddBlockHashes: child section [%s] pushed to blockchain peer <%s> (head: %s) found block %s in the blockchain", sectionhex(child), peerId, hex(bestpeer.currentBlockHash), hex(hash)) - - child.addSectionToBlockChain(bestpeer) - } - } - break LOOP - } - - // look up node in the pool - entry = self.get(hash) - if entry != nil { - // reached a known chain in the pool - if entry.node == entry.section.bottom && n == 1 { - /* - The first block hash received is an orphan node in the pool - - This also supports clients that (despite the spec) include <from> hash in their - response to hashes request. Note that by providing <from> we can link sections - without having to wait for the root block of the child section to arrive, so it allows for superior performance. - */ - glog.V(logger.Detail).Infof("AddBlockHashes: peer <%s> (head: %s) found head block [%s] as root of connecting child section [%s] skipping", peerId, hex(bestpeer.currentBlockHash), hex(hash), sectionhex(entry.section)) - // record the entry's chain section as child section - child = entry.section - continue LOOP - } - // otherwise record entry's chain section as parent connecting it to the pool - glog.V(logger.Detail).Infof("AddBlockHashes: peer <%s> (head: %s) found block [%s] in section [%s]. Connected to pool.", peerId, hex(bestpeer.currentBlockHash), hex(hash), sectionhex(entry.section)) - parent = entry.section - break LOOP - } - - // finally if node for block hash does not exist, create it and append node to section nodes - self.nodeCacheLock.Lock() - nodes = append(nodes, self.findOrCreateNode(hash, peerId)) - self.nodeCacheLock.Unlock() - } //for - - /* - we got here if - - run out of hashes (parent = nil) sent by our best peer - - our peer is demoted (peerswitch = true) - - reached blockchain or blockpool - - quitting - */ - self.chainLock.Lock() - - glog.V(logger.Detail).Infof("AddBlockHashes: peer <%s> (head: %s): %v nodes in new section", peerId, hex(bestpeer.currentBlockHash), len(nodes)) - /* - Handle forks where connecting node is mid-section by splitting section at fork. - No splitting needed if connecting node is head of a section. - */ - if parent != nil && entry != nil && entry.node != parent.top && len(nodes) > 0 { - glog.V(logger.Detail).Infof("AddBlockHashes: peer <%s> (head: %s): fork after %s", peerId, hex(bestpeer.currentBlockHash), hex(hash)) - - self.splitSection(parent, entry) - - self.status.lock.Lock() - self.status.values.Forks++ - self.status.lock.Unlock() - } - - // If new section is created, link it to parent/child sections. - sec = self.linkSections(nodes, parent, child) - - if sec != nil { - glog.V(logger.Detail).Infof("AddBlockHashes: peer <%s> (head: %s): section [%s] created", peerId, hex(bestpeer.currentBlockHash), sectionhex(sec)) - } - - self.chainLock.Unlock() - - /* - If a blockpool node is reached (parent section is not nil), - activate section (unless our peer is demoted by now). - This can be the bottom half of a newly split section in case of a fork. - - bestPeer is nil if we got here after our peer got demoted while processing. - In this case no activation should happen - */ - if parent != nil && !peerswitch { - glog.V(logger.Detail).Infof("AddBlockHashes: peer <%s> (head: %s): parent section [%s]", peerId, hex(bestpeer.currentBlockHash), sectionhex(parent)) - self.activateChain(parent, bestpeer, bestpeer.switchC, nil) - } - - /* - If a new section was created, register section iff head section or no child known - Activate it with this peer. - */ - if sec != nil { - // switch on section process (it is paused by switchC) - if !peerswitch { - if headSection || child == nil { - bestpeer.lock.Lock() - bestpeer.sections = append(bestpeer.sections, sec.top.hash) - bestpeer.lock.Unlock() - } - /* - Request another batch of older block hashes for parent section here. - But only once, repeating only when the section's root block arrives. - Otherwise no way to check if it arrived. - */ - bestpeer.requestBlockHashes(sec.bottom.hash) - glog.V(logger.Detail).Infof("AddBlockHashes: peer <%s> (head: %s): start requesting blocks for section [%s]", peerId, hex(bestpeer.currentBlockHash), sectionhex(sec)) - sec.activate(bestpeer) - } else { - glog.V(logger.Detail).Infof("AddBlockHashes: peer <%s> (head: %s) no longer best: delay requesting blocks for section [%s]", peerId, hex(bestpeer.currentBlockHash), sectionhex(sec)) - sec.deactivate() - } - } - - // If we are processing peer's head section, signal it to headSection process that it is created. - - if headSection { - glog.V(logger.Detail).Infof("AddBlockHashes: peer <%s> (head: %s) head section registered on head section process", peerId, hex(bestpeer.currentBlockHash)) - - var headSec *section - switch { - case sec != nil: - headSec = sec - case child != nil: - headSec = child - default: - headSec = parent - } - if !peerswitch { - glog.V(logger.Detail).Infof("AddBlockHashes: peer <%s> (head: %s) head section [%s] created signalled to head section process", peerId, hex(bestpeer.currentBlockHash), sectionhex(headSec)) - bestpeer.headSectionC <- headSec - } - } -} - -/* - AddBlock is the entry point for the eth protocol to call when blockMsg is received. - - It has a strict interpretation of the protocol in that if the block received has not been requested, it results in an error. - - At the same time it is opportunistic in that if a requested block may be provided by any peer. - - The received block is checked for PoW. Only the first PoW-valid block for a hash is considered legit. - - If the block received is the head block of the current best peer, signal it to the head section process -*/ -func (self *BlockPool) AddBlock(block *types.Block, peerId string) { - - self.status.lock.Lock() - self.status.activePeers[peerId]++ - self.status.lock.Unlock() - - hash := block.Hash() - - // check if block is already inserted in the blockchain - if self.hasBlock(hash) { - return - } - - sender, _ := self.peers.getPeer(peerId) - if sender == nil { - return - } - sender.lock.Lock() - tdFromCurrentHead, currentBlockHash := sender.setChainInfoFromBlock(block) - - entry := self.get(hash) - - /* @zelig !!! - requested 5 hashes from both A & B. A responds sooner then B, process blocks. Close section. - delayed B sends you block ... UNREQUESTED. Blocked - if entry == nil { - glog.V(logger.Detail).Infof("AddBlock: unrequested block %s received from peer <%s> (head: %s)", hex(hash), peerId, hex(sender.currentBlockHash)) - sender.addError(ErrUnrequestedBlock, "%x", hash) - - self.status.lock.Lock() - self.status.badPeers[peerId]++ - self.status.lock.Unlock() - return - } - */ - - var bnode *node - if entry == nil { - self.nodeCacheLock.Lock() - bnode = self.findOrCreateNode(currentBlockHash, peerId) - self.nodeCacheLock.Unlock() - } else { - bnode = entry.node - } - - bnode.lock.Lock() - - // check if block already received - if bnode.block != nil { - glog.V(logger.Detail).Infof("AddBlock: block %s from peer <%s> (head: %s) already sent by <%s> ", hex(hash), peerId, hex(sender.currentBlockHash), bnode.blockBy) - // register peer on node as source - if bnode.peers == nil { - bnode.peers = make(map[string]bool) - } - foundBlockCurrentHead, found := bnode.peers[sender.id] - if !found || foundBlockCurrentHead { - // if found but not FoundBlockCurrentHead, then no update - // necessary (||) - bnode.peers[sender.id] = (currentBlockHash == hash) - // for those that are false, TD will update their head - // for those that are true, TD is checked ! - // this is checked at the time of TD calculation in checkTD - } - sender.setChainInfoFromNode(bnode) - } else { - /* - @zelig needs discussing - Viktor: pow check can be delayed in a go routine and therefore cache - creation is not blocking - // validate block for PoW - if !self.verifyPoW(block) { - glog.V(logger.Warn).Warnf("AddBlock: invalid PoW on block %s from peer <%s> (head: %s)", hex(hash), peerId, hex(sender.currentBlockHash)) - sender.addError(ErrInvalidPoW, "%x", hash) - - self.status.lock.Lock() - self.status.badPeers[peerId]++ - self.status.lock.Unlock() - - return - } - */ - bnode.block = block - bnode.blockBy = peerId - glog.V(logger.Detail).Infof("AddBlock: set td on node %s from peer <%s> (head: %s) to %v (was %v) ", hex(hash), peerId, hex(sender.currentBlockHash), bnode.td, tdFromCurrentHead) - bnode.td = tdFromCurrentHead - self.status.lock.Lock() - self.status.values.Blocks++ - self.status.values.BlocksInPool++ - self.status.lock.Unlock() - } - bnode.lock.Unlock() - currentBlockC := sender.currentBlockC - switchC := sender.switchC - sender.lock.Unlock() - - // this must be called without peerlock. - // peerlock held can halt the loop and block on select forever - if tdFromCurrentHead != nil { - select { - case currentBlockC <- block: - case <-switchC: // peer is not best peer - } - } -} - -func (self *BlockPool) findOrCreateNode(hash common.Hash, peerId string) (bnode *node) { - bnode, _ = self.nodeCache[hash] - if bnode == nil { - bnode = &node{ - hash: hash, - hashBy: peerId, - } - self.nodeCache[hash] = bnode - // purge oversize cache - if len(self.nodeCache) > self.Config.NodeCacheSize { - delete(self.nodeCache, self.nodeCacheList[0]) - self.nodeCacheList = append(self.nodeCacheList[1:], hash) - } else { - self.nodeCacheList = append(self.nodeCacheList, hash) - } - - self.status.lock.Lock() - self.status.values.BlockHashes++ - self.status.lock.Unlock() - } - return -} - -/* - activateChain iterates down a chain section by section. - It activates the section process on incomplete sections with peer. - It relinks orphaned sections with their parent if root block (and its parent hash) is known. -*/ -func (self *BlockPool) activateChain(sec *section, p *peer, switchC chan bool, connected map[common.Hash]*section) { - - var i int - -LOOP: - for sec != nil { - parent := sec.parent - glog.V(logger.Detail).Infof("activateChain: section [%s] activated by peer <%s>", sectionhex(sec), p.id) - sec.activate(p) - if i > 0 && connected != nil { - connected[sec.top.hash] = sec - } - /* - Need to relink both complete and incomplete sections - An incomplete section could have been blockHashesRequestsComplete before being delinked from its parent. - */ - if parent == nil { - if sec.bottom.block != nil { - if entry := self.get(sec.bottom.block.ParentHash()); entry != nil { - parent = entry.section - glog.V(logger.Detail).Infof("activateChain: [%s]-[%s] link", sectionhex(parent), sectionhex(sec)) - link(parent, sec) - } - } else { - glog.V(logger.Detail).Infof("activateChain: section [%s] activated by peer <%s> has missing root block", sectionhex(sec), p.id) - } - } - sec = parent - - // stop if peer got demoted or global quit - select { - case <-switchC: - break LOOP - case <-self.quit: - break LOOP - default: - } - } -} - -// check if block's actual TD (calculated after successful insertChain) is identical to TD advertised for peer's head block. -func (self *BlockPool) checkTD(nodes ...*node) { - for _, n := range nodes { - // skip check if queued future block - n.lock.RLock() - if n.td != nil && !n.block.Queued() { - glog.V(logger.Detail).Infof("peer td %v =?= block td %v", n.td, n.block.Td) - // @zelig: Commented out temp untill the rest of the network has been fixed. - if n.td.Cmp(n.block.Td) != 0 { - self.peers.peerError(n.blockBy, ErrIncorrectTD, "on block %x peer td %v =?= block td %v", n.hash, n.td, n.block.Td) - self.status.lock.Lock() - self.status.badPeers[n.blockBy]++ - self.status.lock.Unlock() - } - } - n.lock.RUnlock() - } -} - -// requestBlocks must run in separate go routine, otherwise -// switchpeer -> activateChain -> activate deadlocks on section process select and peers.lock -func (self *BlockPool) requestBlocks(attempts int, hashes []common.Hash) { - self.wg.Add(1) - go func() { - self.peers.requestBlocks(attempts, hashes) - self.wg.Done() - }() -} - -// convenience methods to access adjacent sections -func (self *BlockPool) getParent(sec *section) *section { - self.chainLock.RLock() - defer self.chainLock.RUnlock() - return sec.parent -} - -func (self *BlockPool) getChild(sec *section) *section { - self.chainLock.RLock() - defer self.chainLock.RUnlock() - return sec.child -} - -// accessor and setter for entries in the pool -func (self *BlockPool) get(hash common.Hash) *entry { - self.lock.RLock() - defer self.lock.RUnlock() - return self.pool[hash] -} - -func (self *BlockPool) set(hash common.Hash, e *entry) { - self.lock.Lock() - defer self.lock.Unlock() - self.pool[hash] = e -} - -// accessor and setter for total difficulty -func (self *BlockPool) getTD() *big.Int { - self.lock.RLock() - defer self.lock.RUnlock() - return self.td -} - -func (self *BlockPool) setTD(td *big.Int) { - self.lock.Lock() - defer self.lock.Unlock() - self.td = td -} - -func (self *BlockPool) remove(sec *section) { - // delete node entries from pool index under pool lock - self.lock.Lock() - defer self.lock.Unlock() - - for _, node := range sec.nodes { - delete(self.pool, node.hash) - } - if sec.initialised && sec.poolRootIndex != 0 { - self.status.lock.Lock() - self.status.values.BlocksInPool -= len(sec.nodes) - sec.missing - self.status.lock.Unlock() - } -} - -// get/put for optimised allocation similar to sync.Pool -func (self *BlockPool) getHashSlice() (s []common.Hash) { - select { - case s = <-self.hashSlicePool: - default: - s = make([]common.Hash, self.Config.BlockBatchSize) - } - return -} - -func (self *BlockPool) putHashSlice(s []common.Hash) { - if len(s) == self.Config.BlockBatchSize { - select { - case self.hashSlicePool <- s: - default: - } - } -} - -// pretty prints hash (byte array) with first 4 bytes in hex -func hex(hash common.Hash) (name string) { - if (hash == common.Hash{}) { - name = "" - } else { - name = fmt.Sprintf("%x", hash[:4]) - } - return -} - -// pretty prints a section using first 4 bytes in hex of bottom and top blockhash of the section -func sectionhex(section *section) (name string) { - if section == nil { - name = "" - } else { - name = fmt.Sprintf("%x-%x", section.bottom.hash[:4], section.top.hash[:4]) - } - return -} diff --git a/blockpool/blockpool_test.go b/blockpool/blockpool_test.go deleted file mode 100644 index e79991f15..000000000 --- a/blockpool/blockpool_test.go +++ /dev/null @@ -1,433 +0,0 @@ -package blockpool - -import ( - "testing" - "time" -) - -// using the mock framework in blockpool_util_test -// we test various scenarios here - -func TestPeerWithKnownBlock(t *testing.T) { - _, blockPool, blockPoolTester := newTestBlockPool(t) - blockPoolTester.refBlockChain[0] = nil - blockPoolTester.blockChain[0] = nil - blockPool.Start() - - peer0 := blockPoolTester.newPeer("0", 1, 0) - peer0.AddPeer() - - blockPool.Wait(waitTimeout) - blockPool.Stop() - // no request on known block - peer0.checkBlockHashesRequests() -} - -func TestPeerWithKnownParentBlock(t *testing.T) { - _, blockPool, blockPoolTester := newTestBlockPool(t) - blockPoolTester.initRefBlockChain(1) - blockPoolTester.blockChain[0] = nil - blockPool.Start() - - peer0 := blockPoolTester.newPeer("0", 1, 1) - peer0.AddPeer() - peer0.serveBlocks(0, 1) - - blockPool.Wait(waitTimeout) - blockPool.Stop() - peer0.checkBlocksRequests([]int{1}) - peer0.checkBlockHashesRequests() - blockPoolTester.refBlockChain[1] = []int{} - blockPoolTester.checkBlockChain(blockPoolTester.refBlockChain) -} - -func TestSimpleChain(t *testing.T) { - _, blockPool, blockPoolTester := newTestBlockPool(t) - blockPoolTester.blockChain[0] = nil - blockPoolTester.initRefBlockChain(2) - - blockPool.Start() - - peer1 := blockPoolTester.newPeer("peer1", 2, 2) - peer1.AddPeer() - peer1.serveBlocks(1, 2) - go peer1.serveBlockHashes(2, 1, 0) - peer1.serveBlocks(0, 1) - - blockPool.Wait(waitTimeout) - blockPool.Stop() - blockPoolTester.refBlockChain[2] = []int{} - blockPoolTester.checkBlockChain(blockPoolTester.refBlockChain) -} - -func TestChainConnectingWithParentHash(t *testing.T) { - _, blockPool, blockPoolTester := newTestBlockPool(t) - blockPoolTester.blockChain[0] = nil - blockPoolTester.initRefBlockChain(3) - - blockPool.Start() - - peer1 := blockPoolTester.newPeer("peer1", 3, 3) - peer1.AddPeer() - go peer1.serveBlocks(2, 3) - go peer1.serveBlockHashes(3, 2, 1) - peer1.serveBlocks(0, 1, 2) - - blockPool.Wait(waitTimeout) - blockPool.Stop() - blockPoolTester.refBlockChain[3] = []int{} - blockPoolTester.checkBlockChain(blockPoolTester.refBlockChain) -} - -func TestMultiSectionChain(t *testing.T) { - _, blockPool, blockPoolTester := newTestBlockPool(t) - blockPoolTester.blockChain[0] = nil - blockPoolTester.initRefBlockChain(5) - - blockPool.Start() - - peer1 := blockPoolTester.newPeer("peer1", 5, 5) - - peer1.AddPeer() - go peer1.serveBlocks(4, 5) - go peer1.serveBlockHashes(5, 4, 3) - go peer1.serveBlocks(2, 3, 4) - go peer1.serveBlockHashes(3, 2, 1, 0) - peer1.serveBlocks(0, 1, 2) - - blockPool.Wait(waitTimeout) - blockPool.Stop() - blockPoolTester.refBlockChain[5] = []int{} - blockPoolTester.checkBlockChain(blockPoolTester.refBlockChain) -} - -func TestNewBlocksOnPartialChain(t *testing.T) { - _, blockPool, blockPoolTester := newTestBlockPool(t) - blockPoolTester.blockChain[0] = nil - blockPoolTester.initRefBlockChain(7) - blockPool.Start() - - peer1 := blockPoolTester.newPeer("peer1", 5, 5) - blockPoolTester.tds = make(map[int]int) - blockPoolTester.tds[5] = 5 - - peer1.AddPeer() - go peer1.serveBlocks(4, 5) // partially complete section - go peer1.serveBlockHashes(5, 4, 3) - peer1.serveBlocks(3, 4) // partially complete section - - // peer1 found new blocks - peer1.td = 7 - peer1.currentBlock = 7 - peer1.AddPeer() - peer1.sendBlocks(6, 7) - go peer1.serveBlockHashes(7, 6, 5) - go peer1.serveBlocks(2, 3) - go peer1.serveBlocks(5, 6) - go peer1.serveBlockHashes(3, 2, 1) // tests that hash request from known chain root is remembered - peer1.serveBlocks(0, 1, 2) - - blockPool.Wait(waitTimeout) - blockPool.Stop() - blockPoolTester.refBlockChain[7] = []int{} - blockPoolTester.checkBlockChain(blockPoolTester.refBlockChain) -} - -func TestPeerSwitchUp(t *testing.T) { - _, blockPool, blockPoolTester := newTestBlockPool(t) - blockPoolTester.blockChain[0] = nil - blockPoolTester.initRefBlockChain(7) - - blockPool.Start() - - peer1 := blockPoolTester.newPeer("peer1", 6, 6) - peer2 := blockPoolTester.newPeer("peer2", 7, 7) - - peer1.AddPeer() - go peer1.serveBlocks(5, 6) - go peer1.serveBlockHashes(6, 5, 4, 3) // - peer1.serveBlocks(2, 3) // section partially complete, block 3 will be preserved after peer demoted - peer2.AddPeer() // peer2 is promoted as best peer, peer1 is demoted - go peer2.serveBlocks(6, 7) // - go peer2.serveBlocks(4, 5) // tests that block request for earlier section is remembered - go peer1.serveBlocks(3, 4) // tests that connecting section by demoted peer is remembered and blocks are accepted from demoted peer - go peer2.serveBlockHashes(3, 2, 1, 0) // tests that known chain section is activated, hash requests from 3 is remembered - peer2.serveBlocks(0, 1, 2) // final blocks linking to blockchain sent - - blockPool.Wait(waitTimeout) - blockPool.Stop() - blockPoolTester.refBlockChain[7] = []int{} - blockPoolTester.checkBlockChain(blockPoolTester.refBlockChain) -} - -func TestPeerSwitchDownOverlapSectionWithoutRootBlock(t *testing.T) { - _, blockPool, blockPoolTester := newTestBlockPool(t) - blockPoolTester.blockChain[0] = nil - blockPoolTester.initRefBlockChain(6) - blockPool.Start() - - peer1 := blockPoolTester.newPeer("peer1", 4, 4) - peer2 := blockPoolTester.newPeer("peer2", 6, 6) - - peer2.AddPeer() - peer2.serveBlocks(5, 6) // partially complete, section will be preserved - peer2.serveBlockHashes(6, 5, 4) // no go: make sure skeleton is created - peer1.AddPeer() // inferior peer1 is promoted as best peer - blockPool.RemovePeer("peer2") // peer2 disconnects - go peer1.serveBlockHashes(4, 3, 2, 1, 0) // - go peer1.serveBlocks(3, 4) // - go peer1.serveBlocks(4, 5) // tests that section set by demoted peer is remembered and blocks are accepted from new peer if they have it even if peers original TD is lower - peer1.serveBlocks(0, 1, 2, 3) - - blockPool.Wait(waitTimeout) - blockPool.Stop() - blockPoolTester.refBlockChain[6] = []int{} // tests that idle sections are not inserted in blockchain - blockPoolTester.checkBlockChain(blockPoolTester.refBlockChain) -} - -func TestPeerSwitchDownOverlapSectionWithRootBlock(t *testing.T) { - _, blockPool, blockPoolTester := newTestBlockPool(t) - blockPoolTester.blockChain[0] = nil - blockPoolTester.initRefBlockChain(6) - blockPool.Start() - - peer1 := blockPoolTester.newPeer("peer1", 4, 4) - peer2 := blockPoolTester.newPeer("peer2", 6, 6) - - peer2.AddPeer() - peer2.serveBlocks(5, 6) // partially complete, section will be preserved - go peer2.serveBlockHashes(6, 5, 4) // - peer2.serveBlocks(3, 4) // !incomplete section - time.Sleep(100 * time.Millisecond) // make sure block 4 added - peer1.AddPeer() // inferior peer1 is promoted as best peer - blockPool.RemovePeer("peer2") // peer2 disconnects - go peer1.serveBlockHashes(4, 3, 2, 1, 0) // tests that hash request are directly connecting if the head block exists - go peer1.serveBlocks(4, 5) // tests that section set by demoted peer is remembered and blocks are accepted from new peer if they have it even if peers original TD is lower - peer1.serveBlocks(0, 1, 2, 3) - - blockPool.Wait(waitTimeout) - blockPool.Stop() - blockPoolTester.refBlockChain[6] = []int{} // tests that idle sections are not inserted in blockchain - blockPoolTester.checkBlockChain(blockPoolTester.refBlockChain) -} - -func TestPeerSwitchDownDisjointSection(t *testing.T) { - _, blockPool, blockPoolTester := newTestBlockPool(t) - blockPoolTester.blockChain[0] = nil - blockPoolTester.initRefBlockChain(3) - blockPool.Start() - - peer1 := blockPoolTester.newPeer("peer1", 3, 3) - peer2 := blockPoolTester.newPeer("peer2", 6, 6) - - peer2.AddPeer() - peer2.serveBlocks(5, 6) // partially complete, section will be preserved - go peer2.serveBlockHashes(6, 5, 4) // - peer2.serveBlocks(3, 4, 5) // - time.Sleep(100 * time.Millisecond) // make sure blocks are received - peer1.AddPeer() // inferior peer1 is promoted as best peer - blockPool.RemovePeer("peer2") // peer2 disconnects - go peer1.serveBlocks(2, 3) // - go peer1.serveBlockHashes(3, 2, 1) // - peer1.serveBlocks(0, 1, 2) // - - blockPool.Wait(waitTimeout) - blockPool.Stop() - blockPoolTester.refBlockChain[3] = []int{} // tests that idle sections are not inserted in blockchain - blockPoolTester.checkBlockChain(blockPoolTester.refBlockChain) -} - -func TestPeerSwitchBack(t *testing.T) { - _, blockPool, blockPoolTester := newTestBlockPool(t) - blockPoolTester.blockChain[0] = nil - blockPoolTester.initRefBlockChain(8) - - blockPool.Start() - - peer1 := blockPoolTester.newPeer("peer1", 11, 11) - peer2 := blockPoolTester.newPeer("peer2", 8, 8) - - peer2.AddPeer() - go peer2.serveBlocks(7, 8) - go peer2.serveBlockHashes(8, 7, 6) - go peer2.serveBlockHashes(6, 5, 4) - peer2.serveBlocks(4, 5) // section partially complete - peer1.AddPeer() // peer1 is promoted as best peer - peer1.serveBlocks(10, 11) // - peer1.serveBlockHashes(11, 10) // only gives useless results - blockPool.RemovePeer("peer1") // peer1 disconnects - go peer2.serveBlockHashes(4, 3, 2, 1, 0) // tests that asking for hashes from 4 is remembered - go peer2.serveBlocks(3, 4, 5, 6, 7, 8) // tests that section 4, 5, 6 and 7, 8 are remembered for missing blocks - peer2.serveBlocks(0, 1, 2, 3) - - blockPool.Wait(waitTimeout) - blockPool.Stop() - blockPoolTester.refBlockChain[8] = []int{} - blockPoolTester.checkBlockChain(blockPoolTester.refBlockChain) -} - -func TestForkSimple(t *testing.T) { - _, blockPool, blockPoolTester := newTestBlockPool(t) - blockPoolTester.blockChain[0] = nil - blockPoolTester.initRefBlockChain(9) - blockPoolTester.refBlockChain[3] = []int{4, 7} - delete(blockPoolTester.refBlockChain, 6) - - blockPool.Start() - blockPoolTester.tds = make(map[int]int) - blockPoolTester.tds[6] = 10 - peer1 := blockPoolTester.newPeer("peer1", 9, 9) - peer2 := blockPoolTester.newPeer("peer2", 10, 6) - - peer1.AddPeer() - go peer1.serveBlocks(8, 9) - go peer1.serveBlockHashes(9, 8, 7, 3, 2) - peer1.serveBlocks(1, 2, 3, 7, 8) - peer2.AddPeer() // peer2 is promoted as best peer - go peer2.serveBlocks(5, 6) // - go peer2.serveBlockHashes(6, 5, 4, 3, 2) // fork on 3 -> 4 (earlier child: 7) - go peer2.serveBlocks(1, 2, 3, 4, 5) - go peer2.serveBlockHashes(2, 1, 0) - peer2.serveBlocks(0, 1, 2) - - blockPool.Wait(waitTimeout) - blockPool.Stop() - blockPoolTester.refBlockChain[6] = []int{} - blockPoolTester.refBlockChain[3] = []int{4} - delete(blockPoolTester.refBlockChain, 7) - delete(blockPoolTester.refBlockChain, 8) - delete(blockPoolTester.refBlockChain, 9) - blockPoolTester.checkBlockChain(blockPoolTester.refBlockChain) - -} - -func TestForkSwitchBackByNewBlocks(t *testing.T) { - _, blockPool, blockPoolTester := newTestBlockPool(t) - blockPoolTester.blockChain[0] = nil - blockPoolTester.initRefBlockChain(11) - blockPoolTester.refBlockChain[3] = []int{4, 7} - delete(blockPoolTester.refBlockChain, 6) - - blockPool.Start() - blockPoolTester.tds = make(map[int]int) - blockPoolTester.tds[6] = 10 - peer1 := blockPoolTester.newPeer("peer1", 9, 9) - peer2 := blockPoolTester.newPeer("peer2", 10, 6) - - peer1.AddPeer() - go peer1.serveBlocks(8, 9) // - go peer1.serveBlockHashes(9, 8, 7, 3, 2) // - peer1.serveBlocks(7, 8) // partial section - // time.Sleep(1 * time.Second) - peer2.AddPeer() // - go peer2.serveBlocks(5, 6) // - go peer2.serveBlockHashes(6, 5, 4, 3, 2) // peer2 forks on block 3 - peer2.serveBlocks(1, 2, 3, 4, 5) // - - // peer1 finds new blocks - peer1.td = 11 - peer1.currentBlock = 11 - peer1.AddPeer() - go peer1.serveBlocks(10, 11) - go peer1.serveBlockHashes(11, 10, 9) - go peer1.serveBlocks(9, 10) - // time.Sleep(1 * time.Second) - go peer1.serveBlocks(3, 7) // tests that block requests on earlier fork are remembered - go peer1.serveBlockHashes(2, 1, 0) // tests that hash request from root of connecting chain section (added by demoted peer) is remembered - peer1.serveBlocks(0, 1) - - blockPool.Wait(waitTimeout) - blockPool.Stop() - blockPoolTester.refBlockChain[11] = []int{} - blockPoolTester.refBlockChain[3] = []int{7} - delete(blockPoolTester.refBlockChain, 6) - delete(blockPoolTester.refBlockChain, 5) - delete(blockPoolTester.refBlockChain, 4) - blockPoolTester.checkBlockChain(blockPoolTester.refBlockChain) - -} - -func TestForkSwitchBackByPeerSwitchBack(t *testing.T) { - _, blockPool, blockPoolTester := newTestBlockPool(t) - blockPoolTester.blockChain[0] = nil - blockPoolTester.initRefBlockChain(9) - blockPoolTester.refBlockChain[3] = []int{4, 7} - delete(blockPoolTester.refBlockChain, 6) - - blockPool.Start() - - blockPoolTester.tds = make(map[int]int) - blockPoolTester.tds[6] = 10 - - blockPoolTester.tds = make(map[int]int) - blockPoolTester.tds[6] = 10 - - peer1 := blockPoolTester.newPeer("peer1", 9, 9) - peer2 := blockPoolTester.newPeer("peer2", 10, 6) - - peer1.AddPeer() - go peer1.serveBlocks(8, 9) - go peer1.serveBlockHashes(9, 8, 7, 3, 2) - peer1.serveBlocks(7, 8) - peer2.AddPeer() - go peer2.serveBlocks(5, 6) // - go peer2.serveBlockHashes(6, 5, 4, 3, 2) // peer2 forks on block 3 - peer2.serveBlocks(2, 3, 4, 5) // - blockPool.RemovePeer("peer2") // peer2 disconnects, peer1 is promoted again as best peer - go peer1.serveBlocks(1, 2) // - go peer1.serveBlockHashes(2, 1, 0) // - go peer1.serveBlocks(3, 7) // tests that block requests on earlier fork are remembered and orphan section relinks to existing parent block - peer1.serveBlocks(0, 1) - - blockPool.Wait(waitTimeout) - blockPool.Stop() - blockPoolTester.refBlockChain[9] = []int{} - blockPoolTester.refBlockChain[3] = []int{7} - delete(blockPoolTester.refBlockChain, 6) - delete(blockPoolTester.refBlockChain, 5) - delete(blockPoolTester.refBlockChain, 4) - blockPoolTester.checkBlockChain(blockPoolTester.refBlockChain) - -} - -func TestForkCompleteSectionSwitchBackByPeerSwitchBack(t *testing.T) { - _, blockPool, blockPoolTester := newTestBlockPool(t) - blockPoolTester.blockChain[0] = nil - blockPoolTester.initRefBlockChain(9) - blockPoolTester.refBlockChain[3] = []int{4, 7} - delete(blockPoolTester.refBlockChain, 6) - - blockPool.Start() - - blockPoolTester.tds = make(map[int]int) - blockPoolTester.tds[6] = 10 - - peer1 := blockPoolTester.newPeer("peer1", 9, 9) - peer2 := blockPoolTester.newPeer("peer2", 10, 6) - - peer1.AddPeer() - go peer1.serveBlocks(8, 9) - go peer1.serveBlockHashes(9, 8, 7) - peer1.serveBlocks(3, 7, 8) // make sure this section is complete - // time.Sleep(2 * time.Second) // - peer1.serveBlockHashes(7, 3, 2) // block 3/7 is section boundary - peer1.serveBlocks(2, 3) // partially complete sections block 2 missing - peer2.AddPeer() // - go peer2.serveBlocks(5, 6) // - go peer2.serveBlockHashes(6, 5, 4, 3, 2) // peer2 forks on block 3 - time.Sleep(100 * time.Millisecond) // - peer2.serveBlocks(2, 3, 4, 5) // block 2 still missing. - blockPool.RemovePeer("peer2") // peer2 disconnects, peer1 is promoted again as best peer - go peer1.serveBlockHashes(2, 1) // - peer1.serveBlocks(0, 1, 2) - - blockPool.Wait(waitTimeout) - blockPool.Stop() - blockPoolTester.refBlockChain[9] = []int{} - blockPoolTester.refBlockChain[3] = []int{7} - delete(blockPoolTester.refBlockChain, 6) - delete(blockPoolTester.refBlockChain, 5) - delete(blockPoolTester.refBlockChain, 4) - blockPoolTester.checkBlockChain(blockPoolTester.refBlockChain) - -} diff --git a/blockpool/blockpool_util_test.go b/blockpool/blockpool_util_test.go deleted file mode 100644 index e52c0f753..000000000 --- a/blockpool/blockpool_util_test.go +++ /dev/null @@ -1,373 +0,0 @@ -package blockpool - -import ( - "fmt" - "math/big" - "sync" - "testing" - "time" - - "github.com/ethereum/go-ethereum/blockpool/test" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/errs" - "github.com/ethereum/go-ethereum/event" - "github.com/ethereum/go-ethereum/pow" -) - -var ( - waitTimeout = 60 * time.Second - testBlockHashesRequestInterval = 10 * time.Millisecond - testBlocksRequestInterval = 10 * time.Millisecond - requestWatchInterval = 10 * time.Millisecond -) - -// test blockChain is an integer trie -type blockChain map[int][]int - -// blockPoolTester provides the interface between tests and a blockPool -// -// refBlockChain is used to guide which blocks will be accepted as valid -// blockChain gives the current state of the blockchain and -// accumulates inserts so that we can check the resulting chain -type blockPoolTester struct { - hashPool *test.TestHashPool - lock sync.RWMutex - reqlock sync.RWMutex - blocksRequestsMap map[int]bool - refBlockChain blockChain - blockChain blockChain - blockPool *BlockPool - t *testing.T - chainEvents *event.TypeMux - tds map[int]int -} - -func newTestBlockPool(t *testing.T) (hashPool *test.TestHashPool, blockPool *BlockPool, b *blockPoolTester) { - hashPool = test.NewHashPool() - b = &blockPoolTester{ - t: t, - hashPool: hashPool, - blockChain: make(blockChain), - refBlockChain: make(blockChain), - blocksRequestsMap: make(map[int]bool), - chainEvents: &event.TypeMux{}, - } - b.blockPool = New(b.hasBlock, b.insertChain, b.verifyPoW, b.chainEvents, common.Big0) - blockPool = b.blockPool - blockPool.Config.BlockHashesRequestInterval = testBlockHashesRequestInterval - blockPool.Config.BlocksRequestInterval = testBlocksRequestInterval - return -} - -func (self *blockPoolTester) Errorf(format string, params ...interface{}) { - // fmt.Printf(format+"\n", params...) - self.t.Errorf(format, params...) -} - -// blockPoolTester implements the 3 callbacks needed by the blockPool: -// hasBlock, insetChain, verifyPoW as well as provides the eventer -// to subscribe to head insertions -func (self *blockPoolTester) hasBlock(block common.Hash) (ok bool) { - self.lock.RLock() - defer self.lock.RUnlock() - indexes := self.hashPool.HashesToIndexes([]common.Hash{block}) - i := indexes[0] - _, ok = self.blockChain[i] - // fmt.Printf("has block %v (%x...): %v\n", i, block[0:4], ok) - return -} - -// mock insertChain relies on refBlockChain to determine block validity -func (self *blockPoolTester) insertChain(blocks types.Blocks) error { - self.lock.Lock() - defer self.lock.Unlock() - var parent, child int - var children, refChildren []int - var ok bool - for _, block := range blocks { - child = self.hashPool.HashesToIndexes([]common.Hash{block.Hash()})[0] - td := child - if self.tds != nil { - td, ok = self.tds[child] - } - if !ok { - td = child - } - block.Td = big.NewInt(int64(td)) - _, ok = self.blockChain[child] - if ok { - // fmt.Printf("block %v already in blockchain\n", child) - continue // already in chain - } - parent = self.hashPool.HashesToIndexes([]common.Hash{block.ParentHeaderHash})[0] - children, ok = self.blockChain[parent] - if !ok { - return fmt.Errorf("parent %v not in blockchain ", parent) - } - ok = false - var found bool - refChildren, found = self.refBlockChain[parent] - if found { - for _, c := range refChildren { - if c == child { - ok = true - } - } - if !ok { - return fmt.Errorf("invalid block %v", child) - } - } else { - ok = true - } - if ok { - // accept any blocks if parent not in refBlockChain - self.blockChain[parent] = append(children, child) - self.blockChain[child] = nil - } - } - return nil -} - -// mock soft block validation always succeeds -func (self *blockPoolTester) verifyPoW(pblock pow.Block) bool { - return true -} - -// test helper that compares the resulting blockChain to the desired blockChain -func (self *blockPoolTester) checkBlockChain(blockChain map[int][]int) { - self.lock.RLock() - defer self.lock.RUnlock() - // for k, v := range self.blockChain { - // fmt.Printf("got: %v -> %v\n", k, v) - // } - // for k, v := range blockChain { - // fmt.Printf("expected: %v -> %v\n", k, v) - // } - if len(blockChain) != len(self.blockChain) { - self.Errorf("blockchain incorrect (zlength differ)") - } - for k, v := range blockChain { - vv, ok := self.blockChain[k] - if !ok || !test.ArrayEq(v, vv) { - self.Errorf("blockchain incorrect on %v -> %v (!= %v)", k, vv, v) - } - } -} - -// peerTester provides the peer callbacks for the blockPool -// it registers actual callbacks so that the result can be compared to desired behaviour -// provides helper functions to mock the protocol calls to the blockPool -type peerTester struct { - // containers to record request and error callbacks - blockHashesRequests []int - blocksRequests [][]int - blocksRequestsMap map[int]bool - peerErrors []int - - blockPool *BlockPool - hashPool *test.TestHashPool - lock sync.RWMutex - bt *blockPoolTester - id string - td int - currentBlock int - t *testing.T -} - -// peerTester constructor takes hashPool and blockPool from the blockPoolTester -func (self *blockPoolTester) newPeer(id string, td int, cb int) *peerTester { - return &peerTester{ - id: id, - td: td, - currentBlock: cb, - hashPool: self.hashPool, - blockPool: self.blockPool, - t: self.t, - bt: self, - blocksRequestsMap: self.blocksRequestsMap, - } -} - -func (self *peerTester) Errorf(format string, params ...interface{}) { - // fmt.Printf(format+"\n", params...) - self.t.Errorf(format, params...) -} - -// helper to compare actual and expected block requests -func (self *peerTester) checkBlocksRequests(blocksRequests ...[]int) { - if len(blocksRequests) > len(self.blocksRequests) { - self.Errorf("blocks requests incorrect (length differ)\ngot %v\nexpected %v", self.blocksRequests, blocksRequests) - } else { - for i, rr := range blocksRequests { - r := self.blocksRequests[i] - if !test.ArrayEq(r, rr) { - self.Errorf("blocks requests incorrect\ngot %v\nexpected %v", self.blocksRequests, blocksRequests) - } - } - } -} - -// helper to compare actual and expected block hash requests -func (self *peerTester) checkBlockHashesRequests(blocksHashesRequests ...int) { - rr := blocksHashesRequests - self.lock.RLock() - r := self.blockHashesRequests - self.lock.RUnlock() - if len(r) != len(rr) { - self.Errorf("block hashes requests incorrect (length differ)\ngot %v\nexpected %v", r, rr) - } else { - if !test.ArrayEq(r, rr) { - self.Errorf("block hashes requests incorrect\ngot %v\nexpected %v", r, rr) - } - } -} - -// waiter function used by peer.serveBlocks -// blocking until requests appear -// this mocks proper wire protocol behaviour -// since block requests are sent to any random peers -// block request map is shared between peers -// times out after waitTimeout -func (self *peerTester) waitBlocksRequests(blocksRequest ...int) { - timeout := time.After(waitTimeout) - rr := blocksRequest - for { - self.lock.RLock() - r := self.blocksRequestsMap - // fmt.Printf("[%s] blocks request check %v (%v)\n", self.id, rr, r) - i := 0 - for i = 0; i < len(rr); i++ { - _, ok := r[rr[i]] - if !ok { - break - } - } - self.lock.RUnlock() - - if i == len(rr) { - return - } - time.Sleep(requestWatchInterval) - select { - case <-timeout: - default: - } - } -} - -// waiter function used by peer.serveBlockHashes -// blocking until requests appear -// this mocks proper wire protocol behaviour -// times out after a period -func (self *peerTester) waitBlockHashesRequests(blocksHashesRequest int) { - timeout := time.After(waitTimeout) - rr := blocksHashesRequest - for i := 0; ; { - self.lock.RLock() - r := self.blockHashesRequests - self.lock.RUnlock() - // fmt.Printf("[%s] block hash request check %v (%v)\n", self.id, rr, r) - for ; i < len(r); i++ { - if rr == r[i] { - return - } - } - time.Sleep(requestWatchInterval) - select { - case <-timeout: - default: - } - } -} - -// mocks a simple blockchain 0 (genesis) ... n (head) -func (self *blockPoolTester) initRefBlockChain(n int) { - for i := 0; i < n; i++ { - self.refBlockChain[i] = []int{i + 1} - } -} - -// peerTester functions that mimic protocol calls to the blockpool -// registers the peer with the blockPool -func (self *peerTester) AddPeer() (best bool) { - hash := self.hashPool.IndexesToHashes([]int{self.currentBlock})[0] - best, _ = self.blockPool.AddPeer(big.NewInt(int64(self.td)), hash, self.id, self.requestBlockHashes, self.requestBlocks, self.peerError) - return -} - -// peer sends blockhashes if and when gets a request -func (self *peerTester) serveBlockHashes(indexes ...int) { - // fmt.Printf("ready to serve block hashes %v\n", indexes) - - self.waitBlockHashesRequests(indexes[0]) - self.sendBlockHashes(indexes...) -} - -// peer sends blockhashes not waiting for request -func (self *peerTester) sendBlockHashes(indexes ...int) { - // fmt.Printf("adding block hashes %v\n", indexes) - hashes := self.hashPool.IndexesToHashes(indexes) - i := 1 - next := func() (hash common.Hash, ok bool) { - if i < len(hashes) { - hash = hashes[i] - ok = true - i++ - } - return - } - self.blockPool.AddBlockHashes(next, self.id) -} - -// peer sends blocks if and when there is a request -// (in the shared request store, not necessarily to a specific peer) -func (self *peerTester) serveBlocks(indexes ...int) { - // fmt.Printf("ready to serve blocks %v\n", indexes[1:]) - self.waitBlocksRequests(indexes[1:]...) - self.sendBlocks(indexes...) -} - -// peer sends blocks not waiting for request -func (self *peerTester) sendBlocks(indexes ...int) { - // fmt.Printf("adding blocks %v \n", indexes) - hashes := self.hashPool.IndexesToHashes(indexes) - for i := 1; i < len(hashes); i++ { - // fmt.Printf("adding block %v %x\n", indexes[i], hashes[i][:4]) - self.blockPool.AddBlock(&types.Block{HeaderHash: hashes[i], ParentHeaderHash: hashes[i-1]}, self.id) - } -} - -// the 3 mock peer callbacks - -// records block hashes requests by the blockPool -// -1 is special: not found (a hash never seen) -func (self *peerTester) requestBlockHashes(hash common.Hash) error { - indexes := self.hashPool.HashesToIndexes([]common.Hash{hash}) - // fmt.Printf("[%s] block hash request %v %x\n", self.id, indexes[0], hash[:4]) - self.lock.Lock() - defer self.lock.Unlock() - self.blockHashesRequests = append(self.blockHashesRequests, indexes[0]) - return nil -} - -// records block requests by the blockPool -func (self *peerTester) requestBlocks(hashes []common.Hash) error { - indexes := self.hashPool.HashesToIndexes(hashes) - // fmt.Printf("blocks request %v %x...\n", indexes, hashes[0][:4]) - self.bt.reqlock.Lock() - defer self.bt.reqlock.Unlock() - self.blocksRequests = append(self.blocksRequests, indexes) - for _, i := range indexes { - self.blocksRequestsMap[i] = true - } - return nil -} - -// records the error codes of all the peerErrors found the blockPool -func (self *peerTester) peerError(err *errs.Error) { - self.peerErrors = append(self.peerErrors, err.Code) - if err.Fatal() { - self.blockPool.RemovePeer(self.id) - } -} diff --git a/blockpool/config_test.go b/blockpool/config_test.go deleted file mode 100644 index e882fefe1..000000000 --- a/blockpool/config_test.go +++ /dev/null @@ -1,49 +0,0 @@ -package blockpool - -import ( - "testing" - "time" - - "github.com/ethereum/go-ethereum/blockpool/test" - "github.com/ethereum/go-ethereum/event" -) - -func TestBlockPoolConfig(t *testing.T) { - test.LogInit() - blockPool := &BlockPool{Config: &Config{}, chainEvents: &event.TypeMux{}} - blockPool.Start() - c := blockPool.Config - test.CheckInt("BlockHashesBatchSize", c.BlockHashesBatchSize, blockHashesBatchSize, t) - test.CheckInt("BlockBatchSize", c.BlockBatchSize, blockBatchSize, t) - test.CheckInt("BlocksRequestRepetition", c.BlocksRequestRepetition, blocksRequestRepetition, t) - test.CheckInt("BlocksRequestMaxIdleRounds", c.BlocksRequestMaxIdleRounds, blocksRequestMaxIdleRounds, t) - test.CheckInt("NodeCacheSize", c.NodeCacheSize, nodeCacheSize, t) - test.CheckDuration("BlockHashesRequestInterval", c.BlockHashesRequestInterval, blockHashesRequestInterval, t) - test.CheckDuration("BlocksRequestInterval", c.BlocksRequestInterval, blocksRequestInterval, t) - test.CheckDuration("BlockHashesTimeout", c.BlockHashesTimeout, blockHashesTimeout, t) - test.CheckDuration("BlocksTimeout", c.BlocksTimeout, blocksTimeout, t) - test.CheckDuration("IdleBestPeerTimeout", c.IdleBestPeerTimeout, idleBestPeerTimeout, t) - test.CheckDuration("PeerSuspensionInterval", c.PeerSuspensionInterval, peerSuspensionInterval, t) - test.CheckDuration("StatusUpdateInterval", c.StatusUpdateInterval, statusUpdateInterval, t) -} - -func TestBlockPoolOverrideConfig(t *testing.T) { - test.LogInit() - blockPool := &BlockPool{Config: &Config{}, chainEvents: &event.TypeMux{}} - c := &Config{128, 32, 1, 0, 500, 300 * time.Millisecond, 100 * time.Millisecond, 90 * time.Second, 0, 30 * time.Second, 30 * time.Second, 4 * time.Second} - - blockPool.Config = c - blockPool.Start() - test.CheckInt("BlockHashesBatchSize", c.BlockHashesBatchSize, 128, t) - test.CheckInt("BlockBatchSize", c.BlockBatchSize, 32, t) - test.CheckInt("BlocksRequestRepetition", c.BlocksRequestRepetition, blocksRequestRepetition, t) - test.CheckInt("BlocksRequestMaxIdleRounds", c.BlocksRequestMaxIdleRounds, blocksRequestMaxIdleRounds, t) - test.CheckInt("NodeCacheSize", c.NodeCacheSize, 500, t) - test.CheckDuration("BlockHashesRequestInterval", c.BlockHashesRequestInterval, 300*time.Millisecond, t) - test.CheckDuration("BlocksRequestInterval", c.BlocksRequestInterval, 100*time.Millisecond, t) - test.CheckDuration("BlockHashesTimeout", c.BlockHashesTimeout, 90*time.Second, t) - test.CheckDuration("BlocksTimeout", c.BlocksTimeout, blocksTimeout, t) - test.CheckDuration("IdleBestPeerTimeout", c.IdleBestPeerTimeout, 30*time.Second, t) - test.CheckDuration("PeerSuspensionInterval", c.PeerSuspensionInterval, 30*time.Second, t) - test.CheckDuration("StatusUpdateInterval", c.StatusUpdateInterval, 4*time.Second, t) -} diff --git a/blockpool/errors_test.go b/blockpool/errors_test.go deleted file mode 100644 index 2ab2d47f5..000000000 --- a/blockpool/errors_test.go +++ /dev/null @@ -1,224 +0,0 @@ -package blockpool - -import ( - "testing" - "time" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/pow" -) - -func TestInvalidBlock(t *testing.T) { - _, blockPool, blockPoolTester := newTestBlockPool(t) - blockPoolTester.blockChain[0] = nil - blockPoolTester.initRefBlockChain(2) - blockPoolTester.refBlockChain[2] = []int{} - - blockPool.Start() - - peer1 := blockPoolTester.newPeer("peer1", 1, 3) - peer1.AddPeer() - go peer1.serveBlocks(2, 3) - go peer1.serveBlockHashes(3, 2, 1, 0) - peer1.serveBlocks(0, 1, 2) - - blockPool.Wait(waitTimeout) - blockPool.Stop() - blockPoolTester.refBlockChain[2] = []int{} - blockPoolTester.checkBlockChain(blockPoolTester.refBlockChain) - if len(peer1.peerErrors) == 1 { - if peer1.peerErrors[0] != ErrInvalidBlock { - t.Errorf("wrong error, got %v, expected %v", peer1.peerErrors[0], ErrInvalidBlock) - } - } else { - t.Errorf("expected %v error, got %v", ErrInvalidBlock, peer1.peerErrors) - } -} - -func TestVerifyPoW(t *testing.T) { - t.Skip() // :FIXME: - - _, blockPool, blockPoolTester := newTestBlockPool(t) - blockPoolTester.blockChain[0] = nil - blockPoolTester.initRefBlockChain(3) - first := false - blockPoolTester.blockPool.verifyPoW = func(b pow.Block) bool { - bb, _ := b.(*types.Block) - indexes := blockPoolTester.hashPool.HashesToIndexes([]common.Hash{bb.Hash()}) - if indexes[0] == 2 && !first { - first = true - return false - } else { - return true - } - - } - - blockPool.Start() - - peer1 := blockPoolTester.newPeer("peer1", 1, 3) - peer2 := blockPoolTester.newPeer("peer2", 1, 3) - peer1.AddPeer() - peer2.AddPeer() - go peer1.serveBlocks(2, 3) - go peer1.serveBlockHashes(3, 2, 1, 0) - peer1.serveBlocks(0, 1, 2, 3) - blockPoolTester.blockPool.verifyPoW = func(b pow.Block) bool { - return true - } - peer2.serveBlocks(1, 2) - - blockPool.Wait(waitTimeout) - blockPool.Stop() - blockPoolTester.refBlockChain[3] = []int{} - blockPoolTester.checkBlockChain(blockPoolTester.refBlockChain) - if len(peer1.peerErrors) == 1 { - if peer1.peerErrors[0] != ErrInvalidPoW { - t.Errorf("wrong error, expected %v, got %v", ErrInvalidPoW, peer1.peerErrors[0]) - } - } else { - t.Errorf("expected %v error, got %v", ErrInvalidPoW, peer1.peerErrors) - } -} - -func TestUnrequestedBlock(t *testing.T) { - t.Skip() // :FIXME: - - _, blockPool, blockPoolTester := newTestBlockPool(t) - blockPoolTester.blockChain[0] = nil - blockPool.Start() - - peer1 := blockPoolTester.newPeer("peer1", 1, 3) - peer1.AddPeer() - peer1.sendBlocks(1, 2) - - blockPool.Stop() - if len(peer1.peerErrors) == 1 { - if peer1.peerErrors[0] != ErrUnrequestedBlock { - t.Errorf("wrong error, got %v, expected %v", peer1.peerErrors[0], ErrUnrequestedBlock) - } - } else { - t.Errorf("expected %v error, got %v", ErrUnrequestedBlock, peer1.peerErrors) - } -} - -func TestErrInsufficientChainInfo(t *testing.T) { - _, blockPool, blockPoolTester := newTestBlockPool(t) - blockPool.Config.BlockHashesTimeout = 100 * time.Millisecond - blockPool.Start() - - peer1 := blockPoolTester.newPeer("peer1", 1, 3) - peer1.AddPeer() - - blockPool.Wait(waitTimeout) - blockPool.Stop() - if len(peer1.peerErrors) == 1 { - if peer1.peerErrors[0] != ErrInsufficientChainInfo { - t.Errorf("wrong error, got %v, expected %v", peer1.peerErrors[0], ErrInsufficientChainInfo) - } - } else { - t.Errorf("expected %v error, got %v", ErrInsufficientChainInfo, peer1.peerErrors) - } -} - -func TestIncorrectTD(t *testing.T) { - _, blockPool, blockPoolTester := newTestBlockPool(t) - blockPoolTester.blockChain[0] = nil - blockPoolTester.initRefBlockChain(3) - - blockPool.Start() - - peer1 := blockPoolTester.newPeer("peer1", 1, 3) - peer1.AddPeer() - go peer1.serveBlocks(2, 3) - go peer1.serveBlockHashes(3, 2, 1, 0) - peer1.serveBlocks(0, 1, 2) - - blockPool.Wait(waitTimeout) - blockPool.Stop() - blockPoolTester.refBlockChain[3] = []int{} - blockPoolTester.checkBlockChain(blockPoolTester.refBlockChain) - if len(peer1.peerErrors) == 1 { - if peer1.peerErrors[0] != ErrIncorrectTD { - t.Errorf("wrong error, got %v, expected %v", peer1.peerErrors[0], ErrIncorrectTD) - } - } else { - t.Errorf("expected %v error, got %v", ErrIncorrectTD, peer1.peerErrors) - } -} - -func TestSkipIncorrectTDonFutureBlocks(t *testing.T) { - _, blockPool, blockPoolTester := newTestBlockPool(t) - blockPoolTester.blockChain[0] = nil - blockPoolTester.initRefBlockChain(3) - - blockPool.insertChain = func(blocks types.Blocks) error { - err := blockPoolTester.insertChain(blocks) - if err == nil { - for _, block := range blocks { - if block.Td.Cmp(common.Big3) == 0 { - block.Td = common.Big3 - block.SetQueued(true) - break - } - } - } - return err - } - - blockPool.Start() - - peer1 := blockPoolTester.newPeer("peer1", 3, 3) - peer1.AddPeer() - go peer1.serveBlocks(2, 3) - go peer1.serveBlockHashes(3, 2, 1, 0) - peer1.serveBlocks(0, 1, 2) - - blockPool.Wait(waitTimeout) - blockPool.Stop() - blockPoolTester.refBlockChain[3] = []int{} - blockPoolTester.checkBlockChain(blockPoolTester.refBlockChain) - if len(peer1.peerErrors) > 0 { - t.Errorf("expected no error, got %v (1 of %v)", peer1.peerErrors[0], len(peer1.peerErrors)) - } -} - -func TestPeerSuspension(t *testing.T) { - _, blockPool, blockPoolTester := newTestBlockPool(t) - blockPool.Config.PeerSuspensionInterval = 100 * time.Millisecond - - blockPool.Start() - - peer1 := blockPoolTester.newPeer("peer1", 3, 3) - peer1.AddPeer() - bestpeer, _ := blockPool.peers.getPeer("peer1") - if bestpeer == nil { - t.Errorf("peer1 not best peer") - return - } - peer1.serveBlocks(2, 3) - - blockPool.peers.peerError("peer1", 0, "") - bestpeer, _ = blockPool.peers.getPeer("peer1") - if bestpeer != nil { - t.Errorf("peer1 not removed on error") - return - } - peer1.AddPeer() - bestpeer, _ = blockPool.peers.getPeer("peer1") - if bestpeer != nil { - t.Errorf("peer1 not removed on reconnect") - return - } - time.Sleep(100 * time.Millisecond) - peer1.AddPeer() - - bestpeer, _ = blockPool.peers.getPeer("peer1") - if bestpeer == nil { - t.Errorf("peer1 not connected after PeerSuspensionInterval") - return - } - blockPool.Stop() - -} diff --git a/blockpool/peers.go b/blockpool/peers.go deleted file mode 100644 index eb2ec6a1f..000000000 --- a/blockpool/peers.go +++ /dev/null @@ -1,639 +0,0 @@ -package blockpool - -import ( - "math/big" - "math/rand" - "sort" - "sync" - "time" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/errs" - "github.com/ethereum/go-ethereum/logger" - "github.com/ethereum/go-ethereum/logger/glog" -) - -// the blockpool's model of a peer -type peer struct { - lock sync.RWMutex - - // last known blockchain status - td *big.Int - tdAdvertised bool - currentBlockHash common.Hash - currentBlock *types.Block - parentHash common.Hash - headSection *section - - id string - - // peer callbacks - requestBlockHashes func(common.Hash) error - requestBlocks func([]common.Hash) error - peerError func(*errs.Error) - errors *errs.Errors - - sections []common.Hash - - // channels to push new head block and head section for peer a - currentBlockC chan *types.Block - headSectionC chan *section - - // channels to signal peer switch and peer quit to section processes - idleC chan bool - switchC chan bool - - bp *BlockPool - - // timers for head section process - blockHashesRequestTimer <-chan time.Time - blocksRequestTimer <-chan time.Time - headInfoTimer <-chan time.Time - bestIdleTimer <-chan time.Time - - addToBlacklist func(id string) - - idle bool -} - -// peers is the component keeping a record of peers in a hashmap -// -type peers struct { - lock sync.RWMutex - bllock sync.Mutex - - bp *BlockPool - errors *errs.Errors - peers map[string]*peer - best *peer - status *status - blacklist map[string]time.Time -} - -// peer constructor -func (self *peers) newPeer( - td *big.Int, - currentBlockHash common.Hash, - id string, - requestBlockHashes func(common.Hash) error, - requestBlocks func([]common.Hash) error, - peerError func(*errs.Error), -) (p *peer) { - - p = &peer{ - errors: self.errors, - td: td, - currentBlockHash: currentBlockHash, - id: id, - requestBlockHashes: requestBlockHashes, - requestBlocks: requestBlocks, - peerError: peerError, - currentBlockC: make(chan *types.Block), - headSectionC: make(chan *section), - switchC: make(chan bool), - bp: self.bp, - idle: true, - addToBlacklist: self.addToBlacklist, - } - close(p.switchC) //! hack :(((( - // at creation the peer is recorded in the peer pool - self.peers[id] = p - return -} - -// dispatches an error to a peer if still connected, adds it to the blacklist -func (self *peers) peerError(id string, code int, format string, params ...interface{}) { - self.lock.RLock() - peer, ok := self.peers[id] - self.lock.RUnlock() - if ok { - peer.addError(code, format, params...) - } else { - self.addToBlacklist(id) - } -} - -// record time of offence in blacklist to implement suspension for PeerSuspensionInterval -func (self *peers) addToBlacklist(id string) { - self.bllock.Lock() - defer self.bllock.Unlock() - self.blacklist[id] = time.Now() -} - -// suspended checks if peer is still suspended, caller should hold peers.lock -func (self *peers) suspended(id string) (s bool) { - self.bllock.Lock() - defer self.bllock.Unlock() - if suspendedAt, ok := self.blacklist[id]; ok { - if s = suspendedAt.Add(self.bp.Config.PeerSuspensionInterval).After(time.Now()); !s { - // no longer suspended, delete entry - delete(self.blacklist, id) - } - } - return -} - -func (self *peer) addError(code int, format string, params ...interface{}) { - err := self.errors.New(code, format, params...) - self.peerError(err) - if err.Fatal() { - self.addToBlacklist(self.id) - } else { - go self.bp.peers.removePeer(self.id, false) - } -} - -// caller must hold peer lock -func (self *peer) setChainInfo(td *big.Int, currentBlockHash common.Hash) { - self.lock.Lock() - defer self.lock.Unlock() - if self.currentBlockHash != currentBlockHash { - previousBlockHash := self.currentBlockHash - glog.V(logger.Debug).Infof("addPeer: Update peer <%s> with td %v (was %v) and current block %s (was %v)", self.id, td, self.td, hex(currentBlockHash), hex(previousBlockHash)) - - self.td = td - self.currentBlockHash = currentBlockHash - self.currentBlock = nil - self.parentHash = common.Hash{} - self.headSection = nil - } - self.tdAdvertised = true -} - -func (self *peer) setChainInfoFromBlock(block *types.Block) (td *big.Int, currentBlockHash common.Hash) { - hash := block.Hash() - // this happens when block came in a newblock message but - // also if sent in a blockmsg (for instance, if we requested, only if we - // dont apply on blockrequests the restriction of flood control) - currentBlockHash = self.currentBlockHash - if currentBlockHash == hash { - if self.currentBlock == nil { - // signal to head section process - glog.V(logger.Detail).Infof("AddBlock: head block %s for peer <%s> (head: %s) received\n", hex(hash), self.id, hex(currentBlockHash)) - td = self.td - } else { - glog.V(logger.Detail).Infof("AddBlock: head block %s for peer <%s> (head: %s) already known", hex(hash), self.id, hex(currentBlockHash)) - } - } - return -} - -// this will use the TD given by the first peer to update peer td, this helps second best peer selection -func (self *peer) setChainInfoFromNode(n *node) { - // in case best peer is lost - block := n.block - hash := block.Hash() - if n.td != nil && n.td.Cmp(self.td) > 0 { - glog.V(logger.Detail).Infof("AddBlock: update peer <%s> - head: %v->%v - TD: %v->%v", self.id, hex(self.currentBlockHash), hex(hash), self.td, n.td) - self.td = n.td - self.currentBlockHash = block.Hash() - self.parentHash = block.ParentHash() - self.currentBlock = block - self.headSection = nil - } -} - -// distribute block request among known peers -func (self *peers) requestBlocks(attempts int, hashes []common.Hash) { - self.lock.RLock() - - defer self.lock.RUnlock() - peerCount := len(self.peers) - // on first attempt use the best peer - if attempts == 0 && self.best != nil { - glog.V(logger.Detail).Infof("request %v missing blocks from best peer <%s>", len(hashes), self.best.id) - self.best.requestBlocks(hashes) - return - } - repetitions := self.bp.Config.BlocksRequestRepetition - if repetitions > peerCount { - repetitions = peerCount - } - i := 0 - indexes := rand.Perm(peerCount)[0:repetitions] - sort.Ints(indexes) - - glog.V(logger.Detail).Infof("request %v missing blocks from %v/%v peers", len(hashes), repetitions, peerCount) - for _, peer := range self.peers { - if i == indexes[0] { - glog.V(logger.Detail).Infof("request length: %v", len(hashes)) - glog.V(logger.Detail).Infof("request %v missing blocks [%x/%x] from peer <%s>", len(hashes), hashes[0][:4], hashes[len(hashes)-1][:4], peer.id) - peer.requestBlocks(hashes) - indexes = indexes[1:] - if len(indexes) == 0 { - break - } - } - i++ - } - self.bp.putHashSlice(hashes) -} - -// addPeer implements the logic for blockpool.AddPeer -// returns 2 bool values -// 1. true iff peer is promoted as best peer in the pool -// 2. true iff peer is still suspended -func (self *peers) addPeer( - td *big.Int, - currentBlockHash common.Hash, - id string, - requestBlockHashes func(common.Hash) error, - requestBlocks func([]common.Hash) error, - peerError func(*errs.Error), -) (best bool, suspended bool) { - - self.lock.Lock() - defer self.lock.Unlock() - var previousBlockHash common.Hash - if self.suspended(id) { - suspended = true - return - } - p, found := self.peers[id] - if found { - // when called on an already connected peer, it means a newBlockMsg is received - // peer head info is updated - p.setChainInfo(td, currentBlockHash) - self.status.lock.Lock() - self.status.values.NewBlocks++ - self.status.lock.Unlock() - } else { - p = self.newPeer(td, currentBlockHash, id, requestBlockHashes, requestBlocks, peerError) - - self.status.lock.Lock() - - self.status.peers[id]++ - self.status.values.NewBlocks++ - self.status.lock.Unlock() - - glog.V(logger.Debug).Infof("addPeer: add new peer <%v> with td %v and current block %s", id, td, hex(currentBlockHash)) - } - - // check if peer's current head block is known - if self.bp.hasBlock(currentBlockHash) { - // peer not ahead - glog.V(logger.Debug).Infof("addPeer: peer <%v> with td %v and current block %s is behind", id, td, hex(currentBlockHash)) - return false, false - } - - if self.best == p { - // new block update for active current best peer -> request hashes - glog.V(logger.Debug).Infof("addPeer: <%s> already the best peer. Request new head section info from %s", id, hex(currentBlockHash)) - - if (previousBlockHash != common.Hash{}) { - glog.V(logger.Detail).Infof("addPeer: <%s> head changed: %s -> %s ", id, hex(previousBlockHash), hex(currentBlockHash)) - p.headSectionC <- nil - if entry := self.bp.get(previousBlockHash); entry != nil { - glog.V(logger.Detail).Infof("addPeer: <%s> previous head : %v found in pool, activate", id, hex(previousBlockHash)) - self.bp.activateChain(entry.section, p, p.switchC, nil) - p.sections = append(p.sections, previousBlockHash) - } - } - best = true - } else { - // baseline is our own TD - currentTD := self.bp.getTD() - bestpeer := self.best - if bestpeer != nil { - bestpeer.lock.RLock() - defer bestpeer.lock.RUnlock() - currentTD = self.best.td - } - if td.Cmp(currentTD) > 0 { - self.status.lock.Lock() - self.status.bestPeers[p.id]++ - self.status.lock.Unlock() - glog.V(logger.Debug).Infof("addPeer: peer <%v> (td: %v > current td %v) promoted best peer", id, td, currentTD) - // fmt.Printf("best peer %v - \n", bestpeer, id) - self.bp.switchPeer(bestpeer, p) - self.best = p - best = true - } - } - - return -} - -// removePeer is called (via RemovePeer) by the eth protocol when the peer disconnects -func (self *peers) removePeer(id string, del bool) { - self.lock.Lock() - defer self.lock.Unlock() - - p, found := self.peers[id] - if !found { - return - } - p.lock.Lock() - defer p.lock.Unlock() - - if del { - delete(self.peers, id) - glog.V(logger.Debug).Infof("addPeer: remove peer <%v> (td: %v)", id, p.td) - } - // if current best peer is removed, need to find a better one - if self.best == p { - var newp *peer - // only peers that are ahead of us are considered - max := self.bp.getTD() - // peer with the highest self-acclaimed TD is chosen - for _, pp := range self.peers { - // demoted peer's td should be 0 - if pp.id == id { - pp.td = common.Big0 - pp.currentBlockHash = common.Hash{} - continue - } - pp.lock.RLock() - if pp.td.Cmp(max) > 0 { - max = pp.td - newp = pp - } - pp.lock.RUnlock() - } - if newp != nil { - self.status.lock.Lock() - self.status.bestPeers[p.id]++ - self.status.lock.Unlock() - glog.V(logger.Debug).Infof("addPeer: peer <%v> (td: %v) promoted best peer", newp.id, newp.td) - } else { - glog.V(logger.Warn).Infof("addPeer: no suitable peers found") - } - self.best = newp - // fmt.Printf("remove peer %v - %v\n", p.id, newp) - self.bp.switchPeer(p, newp) - } -} - -// switchPeer launches section processes -func (self *BlockPool) switchPeer(oldp, newp *peer) { - - // first quit AddBlockHashes, requestHeadSection and activateChain - // by closing the old peer's switchC channel - if oldp != nil { - glog.V(logger.Detail).Infof("<%s> quit peer processes", oldp.id) - // fmt.Printf("close %v - %v\n", oldp.id, newp) - close(oldp.switchC) - } - if newp != nil { - // if new best peer has no head section yet, create it and run it - // otherwise head section is an element of peer.sections - newp.idleC = make(chan bool) - newp.switchC = make(chan bool) - if newp.headSection == nil { - glog.V(logger.Detail).Infof("[%s] head section for [%s] not created, requesting info", newp.id, hex(newp.currentBlockHash)) - - if newp.idle { - self.wg.Add(1) - newp.idle = false - self.syncing() - } - - go func() { - newp.run() - if !newp.idle { - self.wg.Done() - newp.idle = true - } - }() - - } - - var connected = make(map[common.Hash]*section) - var sections []common.Hash - for _, hash := range newp.sections { - glog.V(logger.Detail).Infof("activate chain starting from section [%s]", hex(hash)) - // if section not connected (ie, top of a contiguous sequence of sections) - if connected[hash] == nil { - // if not deleted, then reread from pool (it can be orphaned top half of a split section) - if entry := self.get(hash); entry != nil { - self.activateChain(entry.section, newp, newp.switchC, connected) - connected[hash] = entry.section - sections = append(sections, hash) - } - } - } - glog.V(logger.Detail).Infof("<%s> section processes (%v non-contiguous sequences, was %v before)", newp.id, len(sections), len(newp.sections)) - // need to lock now that newp is exposed to section processesr - newp.lock.Lock() - newp.sections = sections - newp.lock.Unlock() - } - // finally deactivate section process for sections where newp didnt activate - // newp activating section process changes the quit channel for this reason - if oldp != nil { - glog.V(logger.Detail).Infof("<%s> quit section processes", oldp.id) - close(oldp.idleC) - } -} - -// getPeer looks up peer by id, returns peer and a bool value -// that is true iff peer is current best peer -func (self *peers) getPeer(id string) (p *peer, best bool) { - self.lock.RLock() - defer self.lock.RUnlock() - if self.best != nil && self.best.id == id { - return self.best, true - } - p = self.peers[id] - return -} - -// head section process - -func (self *peer) handleSection(sec *section) { - self.lock.Lock() - defer self.lock.Unlock() - glog.V(logger.Detail).Infof("HeadSection: <%s> (head: %s) head section received [%s]-[%s]", self.id, hex(self.currentBlockHash), sectionhex(self.headSection), sectionhex(sec)) - - self.headSection = sec - self.blockHashesRequestTimer = nil - - if sec == nil { - if self.idle { - self.idle = false - self.bp.wg.Add(1) - self.bp.syncing() - } - - self.headInfoTimer = time.After(self.bp.Config.BlockHashesTimeout) - self.bestIdleTimer = nil - - glog.V(logger.Detail).Infof("HeadSection: <%s> head block hash changed (mined block received). New head %s", self.id, hex(self.currentBlockHash)) - } else { - if !self.idle { - self.idle = true - self.bp.wg.Done() - } - - self.headInfoTimer = nil - self.bestIdleTimer = time.After(self.bp.Config.IdleBestPeerTimeout) - glog.V(logger.Detail).Infof("HeadSection: <%s> (head: %s) head section [%s] created. Idle...", self.id, hex(self.currentBlockHash), sectionhex(sec)) - } -} - -func (self *peer) getCurrentBlock(currentBlock *types.Block) { - // called by update or after AddBlock signals that head block of current peer is received - self.lock.Lock() - defer self.lock.Unlock() - if currentBlock == nil { - if entry := self.bp.get(self.currentBlockHash); entry != nil { - entry.node.lock.Lock() - currentBlock = entry.node.block - entry.node.lock.Unlock() - } - if currentBlock != nil { - glog.V(logger.Detail).Infof("HeadSection: <%s> head block %s found in blockpool", self.id, hex(self.currentBlockHash)) - } else { - glog.V(logger.Detail).Infof("HeadSection: <%s> head block %s not found... requesting it", self.id, hex(self.currentBlockHash)) - self.requestBlocks([]common.Hash{self.currentBlockHash}) - self.blocksRequestTimer = time.After(self.bp.Config.BlocksRequestInterval) - return - } - } else { - glog.V(logger.Detail).Infof("HeadSection: <%s> head block %s received (parent: %s)", self.id, hex(self.currentBlockHash), hex(currentBlock.ParentHash())) - } - - self.currentBlock = currentBlock - self.parentHash = currentBlock.ParentHash() - glog.V(logger.Detail).Infof("HeadSection: <%s> head block %s found (parent: %s)... requesting hashes", self.id, hex(self.currentBlockHash), hex(self.parentHash)) - self.blockHashesRequestTimer = time.After(0) - self.blocksRequestTimer = nil -} - -func (self *peer) getBlockHashes() bool { - self.lock.Lock() - defer self.lock.Unlock() - //if connecting parent is found - if self.bp.hasBlock(self.parentHash) { - glog.V(logger.Detail).Infof("HeadSection: <%s> parent block %s found in blockchain", self.id, hex(self.parentHash)) - err := self.bp.insertChain(types.Blocks([]*types.Block{self.currentBlock})) - - self.bp.status.lock.Lock() - self.bp.status.values.BlocksInChain++ - self.bp.status.values.BlocksInPool-- - if err != nil { - self.addError(ErrInvalidBlock, "%v", err) - self.bp.status.badPeers[self.id]++ - } else { - // XXX added currentBlock check (?) - if self.currentBlock != nil && self.currentBlock.Td != nil && !self.currentBlock.Queued() { - glog.V(logger.Detail).Infof("HeadSection: <%s> inserted %s to blockchain... check TD %v =?= %v", self.id, hex(self.parentHash), self.td, self.currentBlock.Td) - if self.td.Cmp(self.currentBlock.Td) != 0 { - self.addError(ErrIncorrectTD, "on block %x %v =?= %v", hex(self.parentHash), self.td, self.currentBlock.Td) - self.bp.status.badPeers[self.id]++ - } - } - - headKey := self.parentHash - height := self.bp.status.chain[headKey] + 1 - self.bp.status.chain[self.currentBlockHash] = height - if height > self.bp.status.values.LongestChain { - self.bp.status.values.LongestChain = height - } - delete(self.bp.status.chain, headKey) - } - self.bp.status.lock.Unlock() - } else { - if parent := self.bp.get(self.parentHash); parent != nil { - if self.bp.get(self.currentBlockHash) == nil { - glog.V(logger.Detail).Infof("HeadSection: <%s> connecting parent %s found in pool... creating singleton section", self.id, hex(self.parentHash)) - self.bp.nodeCacheLock.Lock() - n, ok := self.bp.nodeCache[self.currentBlockHash] - if !ok { - panic("not found in nodeCache") - } - self.bp.nodeCacheLock.Unlock() - self.bp.newSection([]*node{n}).activate(self) - } else { - glog.V(logger.Detail).Infof("HeadSection: <%s> connecting parent %s found in pool...head section [%s] exists...not requesting hashes", self.id, hex(self.parentHash), sectionhex(parent.section)) - self.bp.activateChain(parent.section, self, self.switchC, nil) - } - } else { - glog.V(logger.Detail).Infof("HeadSection: <%s> section [%s] requestBlockHashes", self.id, sectionhex(self.headSection)) - self.requestBlockHashes(self.currentBlockHash) - self.blockHashesRequestTimer = time.After(self.bp.Config.BlockHashesRequestInterval) - return false - } - } - self.blockHashesRequestTimer = nil - if !self.idle { - self.idle = true - self.headInfoTimer = nil - self.bestIdleTimer = time.After(self.bp.Config.IdleBestPeerTimeout) - self.bp.wg.Done() - } - return true -} - -// main loop for head section process -func (self *peer) run() { - - self.blocksRequestTimer = time.After(0) - self.headInfoTimer = time.After(self.bp.Config.BlockHashesTimeout) - self.bestIdleTimer = nil - - var ping = time.NewTicker(5 * time.Second) - -LOOP: - for { - select { - // to minitor section process behaviour - case <-ping.C: - glog.V(logger.Detail).Infof("HeadSection: <%s> section with head %s, idle: %v", self.id, hex(self.currentBlockHash), self.idle) - - // signal from AddBlockHashes that head section for current best peer is created - // if sec == nil, it signals that chain info has updated (new block message) - case sec := <-self.headSectionC: - self.handleSection(sec) - - // periodic check for block hashes or parent block/section - case <-self.blockHashesRequestTimer: - self.getBlockHashes() - - // signal from AddBlock that head block of current best peer has been received - case currentBlock := <-self.currentBlockC: - self.getCurrentBlock(currentBlock) - - // keep requesting until found or timed out - case <-self.blocksRequestTimer: - self.getCurrentBlock(nil) - - // quitting on timeout - case <-self.headInfoTimer: - self.peerError(self.bp.peers.errors.New(ErrInsufficientChainInfo, "timed out without providing block hashes or head block (td: %v, head: %s)", self.td, hex(self.currentBlockHash))) - - self.bp.status.lock.Lock() - self.bp.status.badPeers[self.id]++ - self.bp.status.lock.Unlock() - // there is no persistence here, so GC will just take care of cleaning up - - // signal for peer switch, quit - case <-self.switchC: - var complete = "incomplete " - if self.idle { - complete = "complete" - } - glog.V(logger.Detail).Infof("HeadSection: <%s> section with head %s %s... quit request loop due to peer switch", self.id, hex(self.currentBlockHash), complete) - break LOOP - - // global quit for blockpool - case <-self.bp.quit: - break LOOP - - // best - case <-self.bestIdleTimer: - self.peerError(self.bp.peers.errors.New(ErrIdleTooLong, "timed out without providing new blocks (td: %v, head: %s)...quitting", self.td, hex(self.currentBlockHash))) - - self.bp.status.lock.Lock() - self.bp.status.badPeers[self.id]++ - self.bp.status.lock.Unlock() - glog.V(logger.Detail).Infof("HeadSection: <%s> (headsection [%s]) quit channel closed : timed out without providing new blocks...quitting", self.id, sectionhex(self.headSection)) - } - } - - if !self.idle { - self.idle = true - self.bp.wg.Done() - } -} diff --git a/blockpool/peers_test.go b/blockpool/peers_test.go deleted file mode 100644 index 639abbc26..000000000 --- a/blockpool/peers_test.go +++ /dev/null @@ -1,211 +0,0 @@ -package blockpool - -import ( - "flag" - "math/big" - "testing" - "time" - - "github.com/ethereum/go-ethereum/core" - "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/logger" - "github.com/ethereum/go-ethereum/logger/glog" -) - -var ( - _ = flag.Set("alsologtostderr", "true") - // _ = flag.Set("log_dir", ".") - _ = flag.Set("v", "5") -) - -// the actual tests -func TestAddPeer(t *testing.T) { - glog.V(logger.Error).Infoln("logging...") - hashPool, blockPool, blockPoolTester := newTestBlockPool(t) - peer0 := blockPoolTester.newPeer("peer0", 2, 2) - peer1 := blockPoolTester.newPeer("peer1", 4, 4) - peer2 := blockPoolTester.newPeer("peer2", 6, 6) - var bestpeer *peer - - blockPool.Start() - - // pool - best := peer0.AddPeer() - if !best { - t.Errorf("peer0 (TD=2) not accepted as best") - return - } - if blockPool.peers.best.id != "peer0" { - t.Errorf("peer0 (TD=2) not set as best") - return - } - peer0.serveBlocks(1, 2) - - best = peer2.AddPeer() - if !best { - t.Errorf("peer2 (TD=6) not accepted as best") - return - } - if blockPool.peers.best.id != "peer2" { - t.Errorf("peer2 (TD=6) not set as best") - return - } - peer2.serveBlocks(5, 6) - - best = peer1.AddPeer() - if best { - t.Errorf("peer1 (TD=4) accepted as best") - return - } - if blockPool.peers.best.id != "peer2" { - t.Errorf("peer2 (TD=6) not set any more as best") - return - } - if blockPool.peers.best.td.Cmp(big.NewInt(int64(6))) != 0 { - t.Errorf("peer2 TD=6 not set") - return - } - - peer2.td = 8 - peer2.currentBlock = 8 - best = peer2.AddPeer() - if !best { - t.Errorf("peer2 (TD=8) not accepted as best") - return - } - if blockPool.peers.best.id != "peer2" { - t.Errorf("peer2 (TD=8) not set as best") - return - } - if blockPool.peers.best.td.Cmp(big.NewInt(int64(8))) != 0 { - t.Errorf("peer2 TD = 8 not updated") - return - } - - peer1.td = 6 - peer1.currentBlock = 6 - best = peer1.AddPeer() - if best { - t.Errorf("peer1 (TD=6) should not be set as best") - return - } - if blockPool.peers.best.id == "peer1" { - t.Errorf("peer1 (TD=6) should not be set as best") - return - } - bestpeer, best = blockPool.peers.getPeer("peer1") - if bestpeer.td.Cmp(big.NewInt(int64(6))) != 0 { - t.Errorf("peer1 TD=6 should be updated") - return - } - - blockPool.RemovePeer("peer2") - bestpeer, best = blockPool.peers.getPeer("peer2") - if bestpeer != nil { - t.Errorf("peer2 not removed") - return - } - - if blockPool.peers.best.id != "peer1" { - t.Errorf("existing peer1 (TD=6) should be set as best peer") - return - } - - blockPool.RemovePeer("peer1") - bestpeer, best = blockPool.peers.getPeer("peer1") - if bestpeer != nil { - t.Errorf("peer1 not removed") - return - } - - if blockPool.peers.best.id != "peer0" { - t.Errorf("existing peer0 (TD=2) should be set as best peer") - return - } - - blockPool.RemovePeer("peer0") - bestpeer, best = blockPool.peers.getPeer("peer0") - if bestpeer != nil { - t.Errorf("peer0 not removed") - return - } - - // adding back earlier peer ok - peer0.currentBlock = 5 - peer0.td = 5 - best = peer0.AddPeer() - if !best { - t.Errorf("peer0 (TD=5) should be set as best") - return - } - - if blockPool.peers.best.id != "peer0" { - t.Errorf("peer0 (TD=5) should be set as best") - return - } - peer0.serveBlocks(4, 5) - - hash := hashPool.IndexesToHashes([]int{6})[0] - newblock := &types.Block{Td: big.NewInt(int64(6)), HeaderHash: hash} - blockPool.chainEvents.Post(core.ChainHeadEvent{newblock}) - time.Sleep(100 * time.Millisecond) - if blockPool.peers.best != nil { - t.Errorf("no peer should be ahead of self") - return - } - best = peer1.AddPeer() - if blockPool.peers.best != nil { - t.Errorf("after peer1 (TD=6) still no peer should be ahead of self") - return - } - - best = peer2.AddPeer() - if !best { - t.Errorf("peer2 (TD=8) not accepted as best") - return - } - - blockPool.RemovePeer("peer2") - if blockPool.peers.best != nil { - t.Errorf("no peer should be ahead of self") - return - } - - blockPool.Stop() -} - -func TestPeerPromotionByTdOnBlock(t *testing.T) { - _, blockPool, blockPoolTester := newTestBlockPool(t) - blockPoolTester.blockChain[0] = nil - blockPoolTester.initRefBlockChain(4) - peer0 := blockPoolTester.newPeer("peer0", 2, 2) - peer1 := blockPoolTester.newPeer("peer1", 1, 1) - peer2 := blockPoolTester.newPeer("peer2", 4, 4) - - blockPool.Start() - - peer0.AddPeer() - peer0.serveBlocks(1, 2) - best := peer1.AddPeer() - // this tests that peer1 is not promoted over peer0 yet - if best { - t.Errorf("peer1 (TD=1) should not be set as best") - return - } - best = peer2.AddPeer() - peer2.serveBlocks(3, 4) - peer2.serveBlockHashes(4, 3, 2, 1) - peer1.sendBlocks(3, 4) - - blockPool.RemovePeer("peer2") - if blockPool.peers.best.id != "peer1" { - t.Errorf("peer1 (TD=3) should be set as best") - return - } - peer1.serveBlocks(0, 1, 2, 3) - - blockPool.Wait(waitTimeout) - blockPool.Stop() - blockPoolTester.refBlockChain[4] = []int{} - blockPoolTester.checkBlockChain(blockPoolTester.refBlockChain) -} diff --git a/blockpool/section.go b/blockpool/section.go deleted file mode 100644 index cab88e561..000000000 --- a/blockpool/section.go +++ /dev/null @@ -1,673 +0,0 @@ -package blockpool - -import ( - "sync" - "time" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/logger" - "github.com/ethereum/go-ethereum/logger/glog" -) - -/* - section is the worker on each chain section in the block pool - - remove the section if there are blocks missing after an absolute time - - remove the section if there are maxIdleRounds of idle rounds of block requests with no response - - periodically polls the chain section for missing blocks which are then requested from peers - - registers the process controller on the peer so that if the peer is promoted as best peer the second time (after a disconnect of a better one), all active processes are switched back on unless they removed (inserted in blockchain, invalid or expired) - - when turned off (if peer disconnects and new peer connects with alternative chain), no blockrequests are made but absolute expiry timer is ticking - - when turned back on it recursively calls itself on the root of the next chain section -*/ -type section struct { - lock sync.RWMutex - - parent *section // connecting section back in time towards blockchain - child *section // connecting section forward in time - - top *node // the topmost node = head node = youngest node within the chain section - bottom *node // the bottom node = root node = oldest node within the chain section - nodes []*node - - peer *peer - parentHash common.Hash - - blockHashes []common.Hash - - poolRootIndex int - - bp *BlockPool - - controlC chan *peer // to (de)register the current best peer - poolRootC chan *peer // indicate connectedness to blockchain (well, known blocks) - offC chan bool // closed if process terminated - suicideC chan bool // initiate suicide on the section - quitInitC chan bool // to signal end of initialisation - forkC chan chan bool // freeze section process while splitting - switchC chan bool // switching - idleC chan bool // channel to indicate thai food - processC chan *node // - missingC chan *node // - - blocksRequestTimer <-chan time.Time - blockHashesRequestTimer <-chan time.Time - suicideTimer <-chan time.Time - - blocksRequests int - blockHashesRequests int - - blocksRequestsComplete bool - blockHashesRequestsComplete bool - ready bool - same bool - initialised bool - active bool - - step int - idle int - missing int - lastMissing int - depth int - invalid bool - poolRoot bool -} - -// -func (self *BlockPool) newSection(nodes []*node) *section { - sec := §ion{ - bottom: nodes[len(nodes)-1], - top: nodes[0], - nodes: nodes, - poolRootIndex: len(nodes), - bp: self, - controlC: make(chan *peer), - poolRootC: make(chan *peer), - offC: make(chan bool), - } - - for i, n := range nodes { - entry := &entry{node: n, section: sec, index: &index{i}} - self.set(n.hash, entry) - } - - glog.V(logger.Detail).Infof("[%s] setup section process", sectionhex(sec)) - - go sec.run() - return sec -} - -func (self *section) addSectionToBlockChain(p *peer) { - self.bp.wg.Add(1) - go func() { - - self.lock.Lock() - defer self.lock.Unlock() - defer func() { - self.bp.wg.Done() - }() - - var nodes []*node - var n *node - var keys []common.Hash - var blocks []*types.Block - for self.poolRootIndex > 0 { - n = self.nodes[self.poolRootIndex-1] - n.lock.RLock() - block := n.block - n.lock.RUnlock() - if block == nil { - break - } - self.poolRootIndex-- - keys = append(keys, n.hash) - blocks = append(blocks, block) - nodes = append(nodes, n) - } - - if len(blocks) == 0 { - return - } - - self.bp.lock.Lock() - for _, key := range keys { - delete(self.bp.pool, key) - } - self.bp.lock.Unlock() - - glog.V(logger.Debug).Infof("[%s] insert %v blocks [%v/%v] into blockchain", sectionhex(self), len(blocks), hex(blocks[0].Hash()), hex(blocks[len(blocks)-1].Hash())) - err := self.bp.insertChain(blocks) - if err != nil { - self.invalid = true - self.bp.peers.peerError(n.blockBy, ErrInvalidBlock, "%v", err) - glog.V(logger.Error).Infof("invalid block %x", n.hash) - glog.V(logger.Error).Infof("penalise peers %v (hash), %v (block)", n.hashBy, n.blockBy) - - // or invalid block and the entire chain needs to be removed - self.removeChain() - } else { - // check tds - self.bp.wg.Add(1) - go func() { - self.bp.checkTD(nodes...) - self.bp.wg.Done() - }() - // if all blocks inserted in this section - // then need to try to insert blocks in child section - if self.poolRootIndex == 0 { - // if there is a child section, then recursively call itself: - // also if section process is not terminated, - // then signal blockchain connectivity with poolRootC - if child := self.bp.getChild(self); child != nil { - select { - case <-child.offC: - glog.V(logger.Detail).Infof("[%s] add complete child section [%s] to the blockchain", sectionhex(self), sectionhex(child)) - case child.poolRootC <- p: - glog.V(logger.Detail).Infof("[%s] add incomplete child section [%s] to the blockchain", sectionhex(self), sectionhex(child)) - } - child.addSectionToBlockChain(p) - } else { - glog.V(logger.Detail).Infof("[%s] no child section in pool", sectionhex(self)) - } - glog.V(logger.Detail).Infof("[%s] section completely inserted to blockchain - remove", sectionhex(self)) - // complete sections are removed. if called from within section process, - // this must run in its own go routine to avoid deadlock - self.remove() - } - } - - self.bp.status.lock.Lock() - if err == nil { - headKey := blocks[0].ParentHash() - height := self.bp.status.chain[headKey] + len(blocks) - self.bp.status.chain[blocks[len(blocks)-1].Hash()] = height - if height > self.bp.status.values.LongestChain { - self.bp.status.values.LongestChain = height - } - delete(self.bp.status.chain, headKey) - } - self.bp.status.values.BlocksInChain += len(blocks) - self.bp.status.values.BlocksInPool -= len(blocks) - if err != nil { - self.bp.status.badPeers[n.blockBy]++ - } - self.bp.status.lock.Unlock() - - }() - -} - -func (self *section) run() { - - // absolute time after which sub-chain is killed if not complete (some blocks are missing) - self.suicideC = make(chan bool) - self.forkC = make(chan chan bool) - self.suicideTimer = time.After(self.bp.Config.BlocksTimeout) - - // node channels for the section - // container for missing block hashes - var checking bool - var ping = time.NewTicker(5 * time.Second) - -LOOP: - for !self.blockHashesRequestsComplete || !self.blocksRequestsComplete { - - select { - case <-ping.C: - var name = "no peer" - if self.peer != nil { - name = self.peer.id - } - glog.V(logger.Detail).Infof("[%s] peer <%s> active: %v", sectionhex(self), name, self.active) - - // global quit from blockpool - case <-self.bp.quit: - break LOOP - - // pause for peer switching - case <-self.switchC: - self.switchC = nil - - case p := <-self.poolRootC: - // signal on pool root channel indicates that the blockpool is - // connected to the blockchain, insert the longest chain of blocks - // ignored in idle mode to avoid inserting chain sections of non-live peers - self.poolRoot = true - // switch off hash requests in case they were on - self.blockHashesRequestTimer = nil - self.blockHashesRequestsComplete = true - self.switchOn(p) - - // peer quit or demoted, put section in idle mode - case <-self.idleC: - // peer quit or demoted, put section in idle mode - glog.V(logger.Debug).Infof("[%s] peer <%s> quit or demoted", sectionhex(self), self.peer.id) - self.switchOff() - self.idleC = nil - - // timebomb - if section is not complete in time, nuke the entire chain - case <-self.suicideTimer: - self.removeChain() - glog.V(logger.Debug).Infof("[%s] timeout. (%v total attempts): missing %v/%v/%v...suicide", sectionhex(self), self.blocksRequests, self.missing, self.lastMissing, self.depth) - self.suicideTimer = nil - break LOOP - - // closing suicideC triggers section suicide: removes section nodes from pool and terminates section process - case <-self.suicideC: - glog.V(logger.Detail).Infof("[%s] quit", sectionhex(self)) - break LOOP - - // alarm for checking blocks in the section - case <-self.blocksRequestTimer: - glog.V(logger.Detail).Infof("[%s] alarm: block request time", sectionhex(self)) - self.processC = self.missingC - - // alarm for checking parent of the section or sending out hash requests - case <-self.blockHashesRequestTimer: - glog.V(logger.Detail).Infof("[%s] alarm: hash request time", sectionhex(self)) - self.blockHashesRequest() - - // activate this section process with a peer - case p := <-self.controlC: - if p == nil { - self.switchOff() - } else { - self.switchOn(p) - } - self.bp.wg.Done() - // blocks the process until section is split at the fork - case waiter := <-self.forkC: - <-waiter - self.initialised = false - self.quitInitC = nil - - // - case n, ok := <-self.processC: - // channel closed, first iteration finished - if !ok && !self.initialised { - glog.V(logger.Detail).Infof("[%s] section initalised: missing %v/%v/%v", sectionhex(self), self.missing, self.lastMissing, self.depth) - self.initialised = true - self.processC = nil - self.checkRound() - checking = false - break - } - if !checking { - self.step = 0 - self.missing = 0 - checking = true - } - self.step++ - - n.lock.RLock() - block := n.block - n.lock.RUnlock() - - // if node has no block, request it (buffer it for batch request) - // feed it to missingC channel for the next round - if block == nil { - pos := self.missing % self.bp.Config.BlockBatchSize - if pos == 0 { - if self.missing != 0 { - self.bp.requestBlocks(self.blocksRequests, self.blockHashes[:]) - } - self.blockHashes = self.bp.getHashSlice() - } - self.blockHashes[pos] = n.hash - self.missing++ - self.missingC <- n - } else { - // checking for parent block - if self.poolRoot { - // if node has got block (received via async AddBlock call from protocol) - if self.step == self.lastMissing { - // current root of the pool - glog.V(logger.Detail).Infof("[%s] received block for current pool root %s", sectionhex(self), hex(n.hash)) - self.addSectionToBlockChain(self.peer) - } - } else { - if (self.parentHash == common.Hash{}) && n == self.bottom { - self.parentHash = block.ParentHash() - glog.V(logger.Detail).Infof("[%s] got parent head block hash %s...checking", sectionhex(self), hex(self.parentHash)) - self.blockHashesRequest() - } - } - } - if self.initialised && self.step == self.lastMissing { - glog.V(logger.Detail).Infof("[%s] check if new blocks arrived (attempt %v): missing %v/%v/%v", sectionhex(self), self.blocksRequests, self.missing, self.lastMissing, self.depth) - self.checkRound() - checking = false - } - } // select - } // for - - close(self.offC) - if self.peer != nil { - self.active = false - self.bp.wg.Done() - } - - glog.V(logger.Detail).Infof("[%s] section process terminated: %v blocks retrieved (%v attempts), hash requests complete on root (%v attempts).", sectionhex(self), self.depth, self.blocksRequests, self.blockHashesRequests) - -} - -func (self *section) switchOn(newpeer *peer) { - - oldpeer := self.peer - // reset switchC/switchC to current best peer - self.idleC = newpeer.idleC - self.switchC = newpeer.switchC - self.peer = newpeer - - if oldpeer != newpeer { - oldp := "no peer" - newp := "no peer" - if oldpeer != nil { - oldp = oldpeer.id - } - if newpeer != nil { - newp = newpeer.id - } - - glog.V(logger.Detail).Infof("[%s] active mode <%s> -> <%s>", sectionhex(self), oldp, newp) - } - - // activate section with current peer - if oldpeer == nil { - self.bp.wg.Add(1) - self.active = true - - if !self.blockHashesRequestsComplete { - self.blockHashesRequestTimer = time.After(0) - } - if !self.blocksRequestsComplete { - if !self.initialised { - if self.quitInitC != nil { - <-self.quitInitC - } - self.missingC = make(chan *node, self.bp.Config.BlockHashesBatchSize) - self.processC = make(chan *node, self.bp.Config.BlockHashesBatchSize) - self.quitInitC = make(chan bool) - - self.step = 0 - self.missing = 0 - self.depth = len(self.nodes) - self.lastMissing = self.depth - - self.feedNodes() - } else { - self.blocksRequestTimer = time.After(0) - } - } - } -} - -// put the section to idle mode -func (self *section) switchOff() { - // active -> idle - if self.peer != nil { - oldp := "no peer" - oldpeer := self.peer - if oldpeer != nil { - oldp = oldpeer.id - } - glog.V(logger.Detail).Infof("[%s] idle mode peer <%s> -> <> (%v total attempts): missing %v/%v/%v", sectionhex(self), oldp, self.blocksRequests, self.missing, self.lastMissing, self.depth) - - self.active = false - self.peer = nil - // turn off timers - self.blocksRequestTimer = nil - self.blockHashesRequestTimer = nil - - if self.quitInitC != nil { - <-self.quitInitC - self.quitInitC = nil - } - self.processC = nil - self.bp.wg.Done() - } -} - -// iterates through nodes of a section to feed processC -// used to initialise chain section -func (self *section) feedNodes() { - // if not run at least once fully, launch iterator - self.bp.wg.Add(1) - go func() { - self.lock.Lock() - defer self.lock.Unlock() - defer func() { - self.bp.wg.Done() - }() - var n *node - INIT: - for _, n = range self.nodes { - select { - case self.processC <- n: - case <-self.bp.quit: - break INIT - } - } - close(self.processC) - close(self.quitInitC) - }() -} - -func (self *section) blockHashesRequest() { - - if self.switchC != nil { - self.bp.chainLock.Lock() - parentSection := self.parent - - if parentSection == nil { - - // only link to new parent if not switching peers - if (self.parentHash != common.Hash{}) { - if parent := self.bp.get(self.parentHash); parent != nil { - parentSection = parent.section - glog.V(logger.Detail).Infof("[%s] blockHashesRequest: parent section [%s] linked\n", sectionhex(self), sectionhex(parentSection)) - link(parentSection, self) - } else { - if self.bp.hasBlock(self.parentHash) { - self.poolRoot = true - glog.V(logger.Detail).Infof("[%s] blockHashesRequest: parentHash known ... inserting section in blockchain", sectionhex(self)) - self.addSectionToBlockChain(self.peer) - self.blockHashesRequestTimer = nil - self.blockHashesRequestsComplete = true - } - } - } - } - self.bp.chainLock.Unlock() - - if !self.poolRoot { - if parentSection != nil { - // activate parent section with this peer - // but only if not during switch mode - glog.V(logger.Detail).Infof("[%s] parent section [%s] activated\n", sectionhex(self), sectionhex(parentSection)) - self.bp.activateChain(parentSection, self.peer, self.peer.switchC, nil) - // if not root of chain, switch off - glog.V(logger.Detail).Infof("[%s] parent found, hash requests deactivated (after %v total attempts)\n", sectionhex(self), self.blockHashesRequests) - self.blockHashesRequestTimer = nil - self.blockHashesRequestsComplete = true - } else { - self.blockHashesRequests++ - glog.V(logger.Detail).Infof("[%s] hash request on root (%v total attempts)\n", sectionhex(self), self.blockHashesRequests) - self.peer.requestBlockHashes(self.bottom.hash) - self.blockHashesRequestTimer = time.After(self.bp.Config.BlockHashesRequestInterval) - } - } - } -} - -// checks number of missing blocks after each round of request and acts accordingly -func (self *section) checkRound() { - if self.missing == 0 { - // no missing blocks - glog.V(logger.Detail).Infof("[%s] section checked: got all blocks. process complete (%v total blocksRequests): missing %v/%v/%v", sectionhex(self), self.blocksRequests, self.missing, self.lastMissing, self.depth) - self.blocksRequestsComplete = true - self.blocksRequestTimer = nil - } else { - // some missing blocks - glog.V(logger.Detail).Infof("[%s] section checked: missing %v/%v/%v", sectionhex(self), self.missing, self.lastMissing, self.depth) - self.blocksRequests++ - pos := self.missing % self.bp.Config.BlockBatchSize - if pos == 0 { - pos = self.bp.Config.BlockBatchSize - } - self.bp.requestBlocks(self.blocksRequests, self.blockHashes[:pos]) - - // handle idle rounds - if self.missing == self.lastMissing { - // idle round - if self.same { - // more than once - self.idle++ - // too many idle rounds - if self.idle >= self.bp.Config.BlocksRequestMaxIdleRounds { - glog.V(logger.Detail).Infof("[%s] block requests had %v idle rounds (%v total attempts): missing %v/%v/%v\ngiving up...", sectionhex(self), self.idle, self.blocksRequests, self.missing, self.lastMissing, self.depth) - self.removeChain() - } - } else { - self.idle = 0 - } - self.same = true - } else { - self.same = false - } - self.lastMissing = self.missing - // put processC offline - self.processC = nil - self.blocksRequestTimer = time.After(self.bp.Config.BlocksRequestInterval) - } -} - -/* - link connects two sections via parent/child fields - creating a doubly linked list - caller must hold BlockPool chainLock -*/ -func link(parent *section, child *section) { - if parent != nil { - exChild := parent.child - parent.child = child - if exChild != nil && exChild != child { - if child != nil { - // if child is nil it is not a real fork - glog.V(logger.Detail).Infof("[%s] chain fork [%s] -> [%s]", sectionhex(parent), sectionhex(exChild), sectionhex(child)) - } - exChild.parent = nil - } - } - if child != nil { - exParent := child.parent - if exParent != nil && exParent != parent { - if parent != nil { - // if parent is nil it is not a real fork, but suicide delinking section - glog.V(logger.Detail).Infof("[%s] chain reverse fork [%s] -> [%s]", sectionhex(child), sectionhex(exParent), sectionhex(parent)) - } - exParent.child = nil - } - child.parent = parent - } -} - -/* - handle forks where connecting node is mid-section - by splitting section at fork - no splitting needed if connecting node is head of a section - caller must hold chain lock -*/ -func (self *BlockPool) splitSection(parent *section, entry *entry) { - glog.V(logger.Detail).Infof("[%s] split section at fork", sectionhex(parent)) - parent.deactivate() - waiter := make(chan bool) - parent.wait(waiter) - chain := parent.nodes - parent.nodes = chain[entry.index.int:] - parent.top = parent.nodes[0] - parent.poolRootIndex -= entry.index.int - orphan := self.newSection(chain[0:entry.index.int]) - link(orphan, parent.child) - close(waiter) - orphan.deactivate() -} - -func (self *section) wait(waiter chan bool) { - self.forkC <- waiter -} - -func (self *BlockPool) linkSections(nodes []*node, parent, child *section) (sec *section) { - // if new section is created, link it to parent/child sections - // and launch section process fetching block and further hashes - if len(nodes) > 0 { - sec = self.newSection(nodes) - glog.V(logger.Debug).Infof("[%s]->[%s](%v)->[%s] new chain section", sectionhex(parent), sectionhex(sec), len(nodes), sectionhex(child)) - link(parent, sec) - link(sec, child) - } else { - if parent != nil && child != nil { - // now this can only happen if we allow response to hash request to include <from> hash - // in this case we just link parent and child (without needing root block of child section) - glog.V(logger.Debug).Infof("[%s]->[%s] connecting known sections", sectionhex(parent), sectionhex(child)) - link(parent, child) - } - } - return -} - -func (self *section) activate(p *peer) { - self.bp.wg.Add(1) - select { - case <-self.offC: - glog.V(logger.Detail).Infof("[%s] completed section process. cannot activate for peer <%s>", sectionhex(self), p.id) - self.bp.wg.Done() - case self.controlC <- p: - glog.V(logger.Detail).Infof("[%s] activate section process for peer <%s>", sectionhex(self), p.id) - } -} - -func (self *section) deactivate() { - self.bp.wg.Add(1) - self.controlC <- nil -} - -// removes this section exacly -func (self *section) remove() { - select { - case <-self.offC: - close(self.suicideC) - glog.V(logger.Detail).Infof("[%s] remove: suicide", sectionhex(self)) - case <-self.suicideC: - glog.V(logger.Detail).Infof("[%s] remove: suicided already", sectionhex(self)) - default: - glog.V(logger.Detail).Infof("[%s] remove: suicide", sectionhex(self)) - close(self.suicideC) - } - self.unlink() - self.bp.remove(self) - glog.V(logger.Detail).Infof("[%s] removed section.", sectionhex(self)) - -} - -// remove a section and all its descendents from the pool -func (self *section) removeChain() { - // need to get the child before removeSection delinks the section - self.bp.chainLock.RLock() - child := self.child - self.bp.chainLock.RUnlock() - - glog.V(logger.Detail).Infof("[%s] remove chain", sectionhex(self)) - self.remove() - if child != nil { - child.removeChain() - } -} - -// unlink a section from its parent/child -func (self *section) unlink() { - // first delink from child and parent under chainlock - self.bp.chainLock.Lock() - link(nil, self) - link(self, nil) - self.bp.chainLock.Unlock() -} diff --git a/blockpool/status.go b/blockpool/status.go deleted file mode 100644 index 02e358510..000000000 --- a/blockpool/status.go +++ /dev/null @@ -1,111 +0,0 @@ -package blockpool - -import ( - "fmt" - "sync" - - "github.com/ethereum/go-ethereum/common" -) - -type statusValues struct { - BlockHashes int // number of hashes fetched this session - BlockHashesInPool int // number of hashes currently in the pool - Blocks int // number of blocks fetched this session - BlocksInPool int // number of blocks currently in the pool - BlocksInChain int // number of blocks inserted/connected to the blockchain this session - NewBlocks int // number of new blocks (received with new blocks msg) this session - Forks int // number of chain forks in the blockchain (poolchain) this session - LongestChain int // the longest chain inserted since the start of session (aka session blockchain height) - BestPeer []byte //Pubkey - Syncing bool // requesting, updating etc - Peers int // cumulative number of all different registered peers since the start of this session - ActivePeers int // cumulative number of all different peers that contributed a hash or block since the start of this session - LivePeers int // number of live peers registered with the block pool (supposed to be redundant but good sanity check - BestPeers int // cumulative number of all peers that at some point were promoted as best peer (peer with highest TD status) this session - BadPeers int // cumulative number of all peers that violated the protocol (invalid block or pow, unrequested hash or block, etc) -} - -type status struct { - lock sync.Mutex - values statusValues - chain map[common.Hash]int - peers map[string]int - bestPeers map[string]int - badPeers map[string]int - activePeers map[string]int -} - -func newStatus() *status { - return &status{ - chain: make(map[common.Hash]int), - peers: make(map[string]int), - bestPeers: make(map[string]int), - badPeers: make(map[string]int), - activePeers: make(map[string]int), - } -} - -type Status struct { - statusValues -} - -// blockpool status for reporting -func (self *BlockPool) Status() *Status { - self.status.lock.Lock() - defer self.status.lock.Unlock() - self.status.values.ActivePeers = len(self.status.activePeers) - self.status.values.BestPeers = len(self.status.bestPeers) - self.status.values.BadPeers = len(self.status.badPeers) - self.status.values.LivePeers = len(self.peers.peers) - self.status.values.Peers = len(self.status.peers) - self.status.values.BlockHashesInPool = len(self.pool) - return &Status{self.status.values} -} - -func (self *Status) String() string { - return fmt.Sprintf(` - Syncing: %v - BlockHashes: %v - BlockHashesInPool: %v - Blocks: %v - BlocksInPool: %v - BlocksInChain: %v - NewBlocks: %v - Forks: %v - LongestChain: %v - Peers: %v - LivePeers: %v - ActivePeers: %v - BestPeers: %v - BadPeers: %v -`, - self.Syncing, - self.BlockHashes, - self.BlockHashesInPool, - self.Blocks, - self.BlocksInPool, - self.BlocksInChain, - self.NewBlocks, - self.Forks, - self.LongestChain, - self.Peers, - self.LivePeers, - self.ActivePeers, - self.BestPeers, - self.BadPeers, - ) -} - -func (self *BlockPool) syncing() { - self.status.lock.Lock() - defer self.status.lock.Unlock() - if !self.status.values.Syncing { - self.status.values.Syncing = true - go func() { - self.wg.Wait() - self.status.lock.Lock() - self.status.values.Syncing = false - self.status.lock.Unlock() - }() - } -} diff --git a/blockpool/status_test.go b/blockpool/status_test.go deleted file mode 100644 index f7e63e421..000000000 --- a/blockpool/status_test.go +++ /dev/null @@ -1,244 +0,0 @@ -package blockpool - -import ( - "fmt" - "testing" - "time" - - "github.com/ethereum/go-ethereum/blockpool/test" -) - -var statusFields = []string{ - "BlockHashes", - "BlockHashesInPool", - "Blocks", - "BlocksInPool", - "BlocksInChain", - "NewBlocks", - "Forks", - "LongestChain", - "Peers", - "LivePeers", - "ActivePeers", - "BestPeers", - "BadPeers", -} - -func getStatusValues(s *Status) []int { - return []int{ - s.BlockHashes, - s.BlockHashesInPool, - s.Blocks, - s.BlocksInPool, - s.BlocksInChain, - s.NewBlocks, - s.Forks, - s.LongestChain, - s.Peers, - s.LivePeers, - s.ActivePeers, - s.BestPeers, - s.BadPeers, - } -} - -func checkStatus(t *testing.T, bp *BlockPool, syncing bool, expected []int) (err error) { - s := bp.Status() - if s.Syncing != syncing { - err = fmt.Errorf("status for Syncing incorrect. expected %v, got %v", syncing, s.Syncing) - return - } - got := getStatusValues(s) - for i, v := range expected { - err = test.CheckInt(statusFields[i], got[i], v, t) - if err != nil { - return - } - } - return -} - -func TestBlockPoolStatus(t *testing.T) { - var err error - n := 3 - for n > 0 { - n-- - err = testBlockPoolStatus(t) - if err != nil { - t.Log(err) - continue - } else { - return - } - } - if err != nil { - t.Errorf("no pass out of 3: %v", err) - } -} - -func testBlockPoolStatus(t *testing.T) (err error) { - - _, blockPool, blockPoolTester := newTestBlockPool(t) - blockPoolTester.blockChain[0] = nil - blockPoolTester.initRefBlockChain(12) - blockPoolTester.refBlockChain[3] = []int{4, 7} - blockPoolTester.refBlockChain[5] = []int{10} - blockPoolTester.refBlockChain[6] = []int{11} - blockPoolTester.refBlockChain[9] = []int{6} - delete(blockPoolTester.refBlockChain, 10) - - blockPool.Start() - - peer1 := blockPoolTester.newPeer("peer1", 9, 9) - peer2 := blockPoolTester.newPeer("peer2", 10, 10) - peer3 := blockPoolTester.newPeer("peer3", 11, 11) - peer4 := blockPoolTester.newPeer("peer4", 9, 9) - peer2.blocksRequestsMap = peer1.blocksRequestsMap - - var expected []int - expected = []int{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} - err = checkStatus(nil, blockPool, false, expected) - if err != nil { - return - } - - peer1.AddPeer() - - expected = []int{0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 1, 0} - err = checkStatus(nil, blockPool, true, expected) - if err != nil { - return - } - - peer1.serveBlocks(8, 9) - expected = []int{1, 0, 1, 1, 0, 1, 0, 0, 1, 1, 1, 1, 0} - err = checkStatus(nil, blockPool, true, expected) - if err != nil { - return - } - - peer1.serveBlockHashes(9, 8, 7, 3, 2) - expected = []int{5, 5, 1, 1, 0, 1, 0, 0, 1, 1, 1, 1, 0} - err = checkStatus(nil, blockPool, true, expected) - if err != nil { - return - } - - peer1.serveBlocks(3, 7, 8) - expected = []int{5, 5, 3, 3, 0, 1, 0, 0, 1, 1, 1, 1, 0} - err = checkStatus(nil, blockPool, true, expected) - if err != nil { - return - } - - peer1.serveBlocks(2, 3) - expected = []int{5, 5, 4, 4, 0, 1, 0, 0, 1, 1, 1, 1, 0} - err = checkStatus(nil, blockPool, true, expected) - if err != nil { - return - } - - peer4.AddPeer() - expected = []int{5, 5, 4, 4, 0, 2, 0, 0, 2, 2, 1, 1, 0} - err = checkStatus(nil, blockPool, true, expected) - if err != nil { - return - } - - peer2.AddPeer() - expected = []int{5, 5, 4, 4, 0, 3, 0, 0, 3, 3, 1, 2, 0} - err = checkStatus(nil, blockPool, true, expected) - if err != nil { - return - } - - peer2.serveBlocks(5, 10) - peer2.serveBlockHashes(10, 5, 4, 3, 2) - expected = []int{8, 8, 5, 5, 0, 3, 1, 0, 3, 3, 2, 2, 0} - err = checkStatus(nil, blockPool, true, expected) - if err != nil { - return - } - - peer2.serveBlocks(2, 3, 4) - expected = []int{8, 8, 6, 6, 0, 3, 1, 0, 3, 3, 2, 2, 0} - err = checkStatus(nil, blockPool, true, expected) - if err != nil { - return - } - - blockPool.RemovePeer("peer2") - expected = []int{8, 8, 6, 6, 0, 3, 1, 0, 3, 2, 2, 2, 0} - err = checkStatus(nil, blockPool, true, expected) - if err != nil { - return - } - - peer1.serveBlockHashes(2, 1, 0) - expected = []int{9, 9, 6, 6, 0, 3, 1, 0, 3, 2, 2, 2, 0} - err = checkStatus(nil, blockPool, true, expected) - if err != nil { - return - } - - peer1.serveBlocks(1, 2) - expected = []int{9, 9, 7, 7, 0, 3, 1, 0, 3, 2, 2, 2, 0} - err = checkStatus(nil, blockPool, true, expected) - if err != nil { - return - } - - peer1.serveBlocks(4, 5) - expected = []int{9, 9, 8, 8, 0, 3, 1, 0, 3, 2, 2, 2, 0} - err = checkStatus(nil, blockPool, true, expected) - if err != nil { - return - } - - peer3.AddPeer() - expected = []int{9, 9, 8, 8, 0, 4, 1, 0, 4, 3, 2, 3, 0} - err = checkStatus(nil, blockPool, true, expected) - if err != nil { - return - } - - peer3.serveBlocks(6, 11) - expected = []int{10, 9, 9, 9, 0, 4, 1, 0, 4, 3, 3, 3, 0} - err = checkStatus(nil, blockPool, true, expected) - if err != nil { - return - } - - peer3.serveBlockHashes(11, 6, 9) - expected = []int{11, 11, 9, 9, 0, 4, 1, 0, 4, 3, 3, 3, 0} - err = checkStatus(nil, blockPool, true, expected) - if err != nil { - return - } - - peer4.sendBlocks(11, 12) - expected = []int{11, 11, 9, 9, 0, 4, 1, 0, 4, 3, 4, 3, 0} - err = checkStatus(nil, blockPool, true, expected) - if err != nil { - return - } - peer3.serveBlocks(9, 6) - expected = []int{11, 11, 10, 10, 0, 4, 1, 0, 4, 3, 4, 3, 0} - err = checkStatus(nil, blockPool, true, expected) - if err != nil { - return - } - - peer3.serveBlocks(0, 1) - blockPool.Wait(waitTimeout) - time.Sleep(200 * time.Millisecond) - - expected = []int{11, 3, 11, 3, 8, 4, 1, 8, 4, 3, 4, 3, 0} - err = checkStatus(nil, blockPool, false, expected) - blockPool.Stop() - - if err != nil { - return - } - return nil -} diff --git a/blockpool/test/hash_pool.go b/blockpool/test/hash_pool.go deleted file mode 100644 index df3c750f9..000000000 --- a/blockpool/test/hash_pool.go +++ /dev/null @@ -1,55 +0,0 @@ -package test - -import ( - "sync" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/crypto" -) - -// hashPool is a test helper, that allows random hashes to be referred to by integers -type TestHashPool struct { - intToHash - hashToInt - lock sync.Mutex -} - -func NewHashPool() *TestHashPool { - return &TestHashPool{intToHash: make(intToHash), hashToInt: make(hashToInt)} -} - -type intToHash map[int]common.Hash - -type hashToInt map[common.Hash]int - -func newHash(i int) common.Hash { - return common.BytesToHash(crypto.Sha3([]byte(string(i)))) -} - -func (self *TestHashPool) IndexesToHashes(indexes []int) (hashes []common.Hash) { - self.lock.Lock() - defer self.lock.Unlock() - for _, i := range indexes { - hash, found := self.intToHash[i] - if !found { - hash = newHash(i) - self.intToHash[i] = hash - self.hashToInt[hash] = i - } - hashes = append(hashes, hash) - } - return -} - -func (self *TestHashPool) HashesToIndexes(hashes []common.Hash) (indexes []int) { - self.lock.Lock() - defer self.lock.Unlock() - for _, hash := range hashes { - i, found := self.hashToInt[hash] - if !found { - i = -1 - } - indexes = append(indexes, i) - } - return -} diff --git a/blockpool/test/logger.go b/blockpool/test/logger.go deleted file mode 100644 index 2828ffc83..000000000 --- a/blockpool/test/logger.go +++ /dev/null @@ -1,74 +0,0 @@ -package test - -import ( - "log" - "os" - "sync" - "testing" - - "github.com/ethereum/go-ethereum/logger" -) - -// logging in tests - -var once sync.Once - -/* usage: -func TestFunc(t *testing.T) { - test.LogInit() - // test -} -*/ -func LogInit() { - once.Do(func() { - logger.NewStdLogSystem(os.Stdout, log.LstdFlags, logger.LogLevel(logger.DebugDetailLevel)) - }) -} - -type testLogger struct{ t *testing.T } - -/* usage: -func TestFunc(t *testing.T) { - defer test.Testlog.Detach() - // test -} -*/ -func Testlog(t *testing.T) testLogger { - logger.Reset() - l := testLogger{t} - logger.AddLogSystem(l) - return l -} - -func (l testLogger) LogPrint(msg logger.LogMsg) { - l.t.Log(msg.String()) -} - -func (testLogger) Detach() { - logger.Flush() - logger.Reset() -} - -type benchLogger struct{ b *testing.B } - -/* usage: -func BenchmarkFunc(b *testing.B) { - defer test.Benchlog.Detach() - // test -} -*/ -func Benchlog(b *testing.B) benchLogger { - logger.Reset() - l := benchLogger{b} - logger.AddLogSystem(l) - return l -} - -func (l benchLogger) LogPrint(msg logger.LogMsg) { - l.b.Log(msg.String()) -} - -func (benchLogger) Detach() { - logger.Flush() - logger.Reset() -} diff --git a/blockpool/test/util.go b/blockpool/test/util.go deleted file mode 100644 index 930601278..000000000 --- a/blockpool/test/util.go +++ /dev/null @@ -1,41 +0,0 @@ -package test - -import ( - "fmt" - "testing" - "time" -) - -// miscellaneous test helpers - -func CheckInt(name string, got int, expected int, t *testing.T) (err error) { - if got != expected { - err = fmt.Errorf("status for %v incorrect. expected %v, got %v", name, expected, got) - if t != nil { - t.Error(err) - } - } - return -} - -func CheckDuration(name string, got time.Duration, expected time.Duration, t *testing.T) (err error) { - if got != expected { - err = fmt.Errorf("status for %v incorrect. expected %v, got %v", name, expected, got) - if t != nil { - t.Error(err) - } - } - return -} - -func ArrayEq(a, b []int) bool { - if len(a) != len(b) { - return false - } - for i := range a { - if a[i] != b[i] { - return false - } - } - return true -} diff --git a/cmd/geth/admin.go b/cmd/geth/admin.go index c42e91615..bd09291bf 100644 --- a/cmd/geth/admin.go +++ b/cmd/geth/admin.go @@ -1,6 +1,7 @@ package main import ( + "errors" "fmt" "os" "time" @@ -9,6 +10,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/logger/glog" "github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rpc" @@ -26,6 +28,7 @@ func (js *jsre) adminBindings() { admin := t.Object() admin.Set("suggestPeer", js.suggestPeer) admin.Set("startRPC", js.startRPC) + admin.Set("stopRPC", js.stopRPC) admin.Set("nodeInfo", js.nodeInfo) admin.Set("peers", js.peers) admin.Set("newAccount", js.newAccount) @@ -50,15 +53,11 @@ func (js *jsre) adminBindings() { debug.Set("printBlock", js.printBlock) debug.Set("dumpBlock", js.dumpBlock) debug.Set("getBlockRlp", js.getBlockRlp) + debug.Set("setHead", js.setHead) + debug.Set("block", js.debugBlock) } -func (js *jsre) downloadProgress(call otto.FunctionCall) otto.Value { - current, max := js.ethereum.Downloader().Stats() - - return js.re.ToVal(fmt.Sprintf("%d/%d", current, max)) -} - -func (js *jsre) getBlockRlp(call otto.FunctionCall) otto.Value { +func (js *jsre) getBlock(call otto.FunctionCall) (*types.Block, error) { var block *types.Block if len(call.ArgumentList) > 0 { if call.Argument(0).IsNumber() { @@ -68,12 +67,66 @@ func (js *jsre) getBlockRlp(call otto.FunctionCall) otto.Value { hash, _ := call.Argument(0).ToString() block = js.ethereum.ChainManager().GetBlock(common.HexToHash(hash)) } else { - fmt.Println("invalid argument for dump. Either hex string or number") + return nil, errors.New("invalid argument for dump. Either hex string or number") } + return block, nil + } - } else { - block = js.ethereum.ChainManager().CurrentBlock() + return nil, errors.New("requires block number or block hash as argument") +} + +func (js *jsre) debugBlock(call otto.FunctionCall) otto.Value { + block, err := js.getBlock(call) + if err != nil { + fmt.Println(err) + return otto.UndefinedValue() + } + + if block == nil { + fmt.Println("block not found") + return otto.UndefinedValue() + } + + old := vm.Debug + vm.Debug = true + _, err = js.ethereum.BlockProcessor().RetryProcess(block) + if err != nil { + glog.Infoln(err) + } + vm.Debug = old + + return otto.UndefinedValue() +} + +func (js *jsre) setHead(call otto.FunctionCall) otto.Value { + block, err := js.getBlock(call) + if err != nil { + fmt.Println(err) + return otto.UndefinedValue() + } + + if block == nil { + fmt.Println("block not found") + return otto.UndefinedValue() + } + + js.ethereum.ChainManager().SetHead(block) + return otto.UndefinedValue() +} + +func (js *jsre) downloadProgress(call otto.FunctionCall) otto.Value { + current, max := js.ethereum.Downloader().Stats() + + return js.re.ToVal(fmt.Sprintf("%d/%d", current, max)) +} + +func (js *jsre) getBlockRlp(call otto.FunctionCall) otto.Value { + block, err := js.getBlock(call) + if err != nil { + fmt.Println(err) + return otto.UndefinedValue() } + if block == nil { fmt.Println("block not found") return otto.UndefinedValue() @@ -174,6 +227,13 @@ func (js *jsre) startRPC(call otto.FunctionCall) otto.Value { return otto.TrueValue() } +func (js *jsre) stopRPC(call otto.FunctionCall) otto.Value { + if rpc.Stop() == nil { + return otto.TrueValue() + } + return otto.FalseValue() +} + func (js *jsre) suggestPeer(call otto.FunctionCall) otto.Value { nodeURL, err := call.Argument(0).ToString() if err != nil { diff --git a/cmd/geth/js.go b/cmd/geth/js.go index 0061f20cf..6e5a6f1c7 100644 --- a/cmd/geth/js.go +++ b/cmd/geth/js.go @@ -25,7 +25,8 @@ import ( "strings" "github.com/ethereum/go-ethereum/cmd/utils" - "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/common/docserver" + "github.com/ethereum/go-ethereum/common/natspec" "github.com/ethereum/go-ethereum/eth" re "github.com/ethereum/go-ethereum/jsre" "github.com/ethereum/go-ethereum/rpc" @@ -139,10 +140,17 @@ var net = web3.net; js.re.Eval(globalRegistrar + "registrar = new GlobalRegistrar(\"" + globalRegistrarAddr + "\");") } -func (self *jsre) ConfirmTransaction(tx *types.Transaction) bool { - p := fmt.Sprintf("Confirm Transaction %v\n[y/n] ", tx) - answer, _ := self.Prompt(p) - return strings.HasPrefix(strings.Trim(answer, " "), "y") +var ds, _ = docserver.New(utils.JSpathFlag.String()) + +func (self *jsre) ConfirmTransaction(tx string) bool { + if self.ethereum.NatSpec { + notice := natspec.GetNotice(self.xeth, tx, ds) + fmt.Println(notice) + answer, _ := self.Prompt("Confirm Transaction\n[y/n] ") + return strings.HasPrefix(strings.Trim(answer, " "), "y") + } else { + return true + } } func (self *jsre) UnlockAccount(addr []byte) bool { diff --git a/cmd/geth/main.go b/cmd/geth/main.go index dab167bbb..ddbd1f129 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -24,8 +24,6 @@ import ( "bufio" "fmt" "io/ioutil" - "log" - "net/http" "os" "runtime" "strconv" @@ -49,7 +47,7 @@ import _ "net/http/pprof" const ( ClientIdentifier = "Geth" - Version = "0.9.10" + Version = "0.9.11" ) var app = utils.NewApp(Version, "the go-ethereum command line interface") @@ -231,11 +229,13 @@ JavaScript API. See https://github.com/ethereum/go-ethereum/wiki/Javascipt-Conso utils.MinerThreadsFlag, utils.MiningEnabledFlag, utils.NATFlag, + utils.NatspecEnabledFlag, utils.NodeKeyFileFlag, utils.NodeKeyHexFlag, utils.RPCEnabledFlag, utils.RPCListenAddrFlag, utils.RPCPortFlag, + utils.WhisperEnabledFlag, utils.VMDebugFlag, utils.ProtocolVersionFlag, utils.NetworkIdFlag, @@ -246,6 +246,14 @@ JavaScript API. See https://github.com/ethereum/go-ethereum/wiki/Javascipt-Conso utils.LogVModuleFlag, utils.LogFileFlag, utils.LogJSONFlag, + utils.PProfEanbledFlag, + utils.PProfPortFlag, + } + app.Before = func(ctx *cli.Context) error { + if ctx.GlobalBool(utils.PProfEanbledFlag.Name) { + utils.StartPProf(ctx) + } + return nil } // missing: @@ -260,11 +268,6 @@ JavaScript API. See https://github.com/ethereum/go-ethereum/wiki/Javascipt-Conso } func main() { - // Start up the default http server for pprof - go func() { - log.Println(http.ListenAndServe("localhost:6060", nil)) - }() - fmt.Printf("Welcome to the FRONTIER\n") runtime.GOMAXPROCS(runtime.NumCPU()) defer logger.Flush() @@ -336,6 +339,7 @@ func unlockAccount(ctx *cli.Context, am *accounts.Manager, account string) (pass } func startEth(ctx *cli.Context, eth *eth.Ethereum) { + // Start Ethereum itself utils.StartEthereum(eth) am := eth.AccountManager() diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index a1d9eedda..b8f3982e2 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -2,6 +2,9 @@ package utils import ( "crypto/ecdsa" + "fmt" + "log" + "net/http" "os" "path" "runtime" @@ -93,6 +96,10 @@ var ( Name: "identity", Usage: "node name", } + NatspecEnabledFlag = cli.BoolFlag{ + Name: "natspec", + Usage: "Enable NatSpec confirmation notice", + } // miner settings MinerThreadsFlag = cli.IntFlag{ @@ -136,10 +143,33 @@ var ( Usage: "Send json structured log output to a file or '-' for standard output (default: no json output)", Value: "", } + LogToStdErrFlag = cli.BoolFlag{ + Name: "logtostderr", + Usage: "Logs are written to standard error instead of to files.", + } + LogVModuleFlag = cli.GenericFlag{ + Name: "vmodule", + Usage: "The syntax of the argument is a comma-separated list of pattern=N, where pattern is a literal file name (minus the \".go\" suffix) or \"glob\" pattern and N is a V level.", + Value: glog.GetVModule(), + } VMDebugFlag = cli.BoolFlag{ Name: "vmdebug", Usage: "Virtual Machine debug output", } + BacktraceAtFlag = cli.GenericFlag{ + Name: "backtrace_at", + Usage: "When set to a file and line number holding a logging statement a stack trace will be written to the Info log", + Value: glog.GetTraceLocation(), + } + PProfEanbledFlag = cli.BoolFlag{ + Name: "pprof", + Usage: "Whether the profiling server should be enabled", + } + PProfPortFlag = cli.IntFlag{ + Name: "pprofport", + Usage: "Port on which the profiler should listen", + Value: 6060, + } // RPC settings RPCEnabledFlag = cli.BoolFlag{ @@ -190,25 +220,15 @@ var ( Usage: "Port mapping mechanism (any|none|upnp|pmp|extip:<IP>)", Value: "any", } + WhisperEnabledFlag = cli.BoolFlag{ + Name: "shh", + Usage: "Whether the whisper sub-protocol is enabled", + } JSpathFlag = cli.StringFlag{ Name: "jspath", Usage: "JS library path to be used with console and js subcommands", Value: ".", } - BacktraceAtFlag = cli.GenericFlag{ - Name: "backtrace_at", - Usage: "When set to a file and line number holding a logging statement a stack trace will be written to the Info log", - Value: glog.GetTraceLocation(), - } - LogToStdErrFlag = cli.BoolFlag{ - Name: "logtostderr", - Usage: "Logs are written to standard error instead of to files.", - } - LogVModuleFlag = cli.GenericFlag{ - Name: "vmodule", - Usage: "The syntax of the argument is a comma-separated list of pattern=N, where pattern is a literal file name (minus the \".go\" suffix) or \"glob\" pattern and N is a V level.", - Value: glog.GetVModule(), - } ) func GetNAT(ctx *cli.Context) nat.Interface { @@ -268,8 +288,9 @@ func MakeEthConfig(clientID, version string, ctx *cli.Context) *eth.Config { MaxPeers: ctx.GlobalInt(MaxPeersFlag.Name), Port: ctx.GlobalString(ListenPortFlag.Name), NAT: GetNAT(ctx), + NatSpec: ctx.GlobalBool(NatspecEnabledFlag.Name), NodeKey: GetNodeKey(ctx), - Shh: true, + Shh: ctx.GlobalBool(WhisperEnabledFlag.Name), Dial: true, BootNodes: ctx.GlobalString(BootnodesFlag.Name), } @@ -319,3 +340,10 @@ func StartRPC(eth *eth.Ethereum, ctx *cli.Context) { xeth := xeth.New(eth, nil) _ = rpc.Start(xeth, config) } + +func StartPProf(ctx *cli.Context) { + address := fmt.Sprintf("localhost:%d", ctx.GlobalInt(PProfPortFlag.Name)) + go func() { + log.Println(http.ListenAndServe(address, nil)) + }() +} diff --git a/common/bytes.go b/common/bytes.go index 5bdacd810..5d1245107 100644 --- a/common/bytes.go +++ b/common/bytes.go @@ -147,6 +147,23 @@ func Hex2Bytes(str string) []byte { return h } +func Hex2BytesFixed(str string, flen int) []byte { + + h, _ := hex.DecodeString(str) + if len(h) == flen { + return h + } else { + if len(h) > flen { + return h[len(h)-flen : len(h)] + } else { + hh := make([]byte, flen) + copy(hh[flen-len(h):flen], h[:]) + return hh + } + } + +} + func StringToByteFunc(str string, cb func(str string) []byte) (ret []byte) { if len(str) > 1 && str[0:2] == "0x" && !strings.Contains(str, "\n") { ret = Hex2Bytes(str[2:]) diff --git a/common/docserver/docserver.go b/common/docserver/docserver.go new file mode 100644 index 000000000..5e076aa7e --- /dev/null +++ b/common/docserver/docserver.go @@ -0,0 +1,82 @@ +package docserver + +import ( + "fmt" + "io/ioutil" + "net/http" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" +) + +// http://golang.org/pkg/net/http/#RoundTripper +var ( + schemes = map[string]func(*DocServer) http.RoundTripper{ + // Simple File server from local disk file:///etc/passwd :) + "file": fileServerOnDocRoot, + } +) + +func fileServerOnDocRoot(ds *DocServer) http.RoundTripper { + return http.NewFileTransport(http.Dir(ds.DocRoot)) +} + +type DocServer struct { + *http.Transport + DocRoot string +} + +func New(docRoot string) (self *DocServer, err error) { + self = &DocServer{ + Transport: &http.Transport{}, + DocRoot: docRoot, + } + err = self.RegisterProtocols(schemes) + return +} + +// Clients should be reused instead of created as needed. Clients are safe for concurrent use by multiple goroutines. + +// A Client is higher-level than a RoundTripper (such as Transport) and additionally handles HTTP details such as cookies and redirects. + +func (self *DocServer) Client() *http.Client { + return &http.Client{ + Transport: self, + } +} + +func (self *DocServer) RegisterProtocols(schemes map[string]func(*DocServer) http.RoundTripper) (err error) { + for scheme, rtf := range schemes { + self.RegisterProtocol(scheme, rtf(self)) + } + return +} + +func (self *DocServer) GetAuthContent(uri string, hash common.Hash) (content []byte, err error) { + // retrieve content + resp, err := self.Client().Get(uri) + defer func() { + if resp != nil { + resp.Body.Close() + } + }() + if err != nil { + return + } + content, err = ioutil.ReadAll(resp.Body) + if err != nil { + return + } + + // check hash to authenticate content + hashbytes := crypto.Sha3(content) + var chash common.Hash + copy(chash[:], hashbytes) + if chash != hash { + content = nil + err = fmt.Errorf("content hash mismatch") + } + + return + +} diff --git a/common/docserver/docserver_test.go b/common/docserver/docserver_test.go new file mode 100644 index 000000000..400d7447a --- /dev/null +++ b/common/docserver/docserver_test.go @@ -0,0 +1,38 @@ +package docserver + +import ( + "io/ioutil" + "os" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" +) + +func TestGetAuthContent(t *testing.T) { + text := "test" + hash := common.Hash{} + copy(hash[:], crypto.Sha3([]byte(text))) + ioutil.WriteFile("/tmp/test.content", []byte(text), os.ModePerm) + + ds, err := New("/tmp/") + content, err := ds.GetAuthContent("file:///test.content", hash) + if err != nil { + t.Errorf("no error expected, got %v", err) + } + if string(content) != text { + t.Errorf("incorrect content. expected %v, got %v", text, string(content)) + } + + hash = common.Hash{} + content, err = ds.GetAuthContent("file:///test.content", hash) + expected := "content hash mismatch" + if err == nil { + t.Errorf("expected error, got nothing") + } else { + if err.Error() != expected { + t.Errorf("expected error '%s' got '%v'", expected, err) + } + } + +} diff --git a/common/natspec/natspec.go b/common/natspec/natspec.go index 8e6568cef..38e7c1a9d 100644 --- a/common/natspec/natspec.go +++ b/common/natspec/natspec.go @@ -1,20 +1,137 @@ package natspec import ( + "bytes" + "encoding/json" "fmt" "github.com/robertkrimen/otto" + "strings" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/docserver" + "github.com/ethereum/go-ethereum/common/resolver" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/xeth" ) +type abi2method map[[8]byte]*method + type NatSpec struct { - jsvm *otto.Otto + jsvm *otto.Otto + userDocJson, abiDocJson []byte + userDoc userDoc + tx, data string + // abiDoc abiDoc +} + +func getFallbackNotice(comment, tx string) string { + + return "About to submit transaction (" + comment + "): " + tx + } -// TODO: should initialise with abi and userdoc jsons -func New() (self *NatSpec, err error) { +func GetNotice(xeth *xeth.XEth, tx string, http *docserver.DocServer) (notice string) { + + ns, err := New(xeth, tx, http) + if err != nil { + if ns == nil { + return getFallbackNotice("no NatSpec info found for contract", tx) + } else { + return getFallbackNotice("invalid NatSpec info", tx) + } + } + + notice, err2 := ns.Notice() + + if err2 != nil { + return getFallbackNotice("NatSpec notice error \""+err2.Error()+"\"", tx) + } + + return + +} + +func New(xeth *xeth.XEth, tx string, http *docserver.DocServer) (self *NatSpec, err error) { + + // extract contract address from tx + + var obj map[string]json.RawMessage + err = json.Unmarshal([]byte(tx), &obj) + if err != nil { + return + } + var tmp []map[string]string + err = json.Unmarshal(obj["params"], &tmp) + if err != nil { + return + } + contractAddress := tmp[0]["to"] - self = new(NatSpec) - self.jsvm = otto.New() + // retrieve contract hash from state + if !xeth.IsContract(contractAddress) { + err = fmt.Errorf("NatSpec error: contract not found") + return + } + codehex := xeth.CodeAt(contractAddress) + codeHash := common.BytesToHash(crypto.Sha3(common.Hex2Bytes(codehex[2:]))) + // parse out host/domain + // set up nameresolver with natspecreg + urlhint contract addresses + res := resolver.New( + xeth, + resolver.URLHintContractAddress, + resolver.HashRegContractAddress, + ) + + // resolve host via HashReg/UrlHint Resolver + uri, hash, err := res.KeyToUrl(codeHash) + if err != nil { + return + } + + // get content via http client and authenticate content using hash + content, err := http.GetAuthContent(uri, hash) + if err != nil { + return + } + + // get abi, userdoc + var obj2 map[string]json.RawMessage + err = json.Unmarshal(content, &obj2) + if err != nil { + return + } + + abi := []byte(obj2["abi"]) + userdoc := []byte(obj2["userdoc"]) + + self, err = NewWithDocs(abi, userdoc, tx) + return +} + +func NewWithDocs(abiDocJson, userDocJson []byte, tx string) (self *NatSpec, err error) { + + var obj map[string]json.RawMessage + err = json.Unmarshal([]byte(tx), &obj) + if err != nil { + return + } + var tmp []map[string]string + err = json.Unmarshal(obj["params"], &tmp) + if err != nil { + return + } + data := tmp[0]["data"] + + self = &NatSpec{ + jsvm: otto.New(), + abiDocJson: abiDocJson, + userDocJson: userDocJson, + tx: tx, + data: data, + } + + // load and require natspec js (but it is meant to be protected environment) _, err = self.jsvm.Run(natspecJS) if err != nil { return @@ -24,20 +141,78 @@ func New() (self *NatSpec, err error) { return } + err = json.Unmarshal(userDocJson, &self.userDoc) + // err = parseAbiJson(abiDocJson, &self.abiDoc) + return } -func (self *NatSpec) Notice(transaction, abi, method, expression string) (string, error) { - var err error - if _, err = self.jsvm.Run("var transaction = " + transaction + ";"); err != nil { +// type abiDoc []method + +// type method struct { +// Name string `json:name` +// Inputs []input `json:inputs` +// abiKey [8]byte +// } + +// type input struct { +// Name string `json:name` +// Type string `json:type` +// } + +// json skeleton for abi doc (contract method definitions) +type method struct { + Notice string `json:notice` + name string +} + +type userDoc struct { + Methods map[string]*method `json:methods` +} + +func (self *NatSpec) makeAbi2method(abiKey [8]byte) (meth *method) { + for signature, m := range self.userDoc.Methods { + name := strings.Split(signature, "(")[0] + hash := []byte(common.Bytes2Hex(crypto.Sha3([]byte(signature)))) + var key [8]byte + copy(key[:], hash[:8]) + if bytes.Equal(key[:], abiKey[:]) { + meth = m + meth.name = name + return + } + } + return +} + +func (self *NatSpec) Notice() (notice string, err error) { + var abiKey [8]byte + if len(self.data) < 10 { + err = fmt.Errorf("Invalid transaction data") + return + } + copy(abiKey[:], self.data[2:10]) + meth := self.makeAbi2method(abiKey) + + if meth == nil { + err = fmt.Errorf("abi key does not match any method") + return + } + notice, err = self.noticeForMethod(self.tx, meth.name, meth.Notice) + return +} + +func (self *NatSpec) noticeForMethod(tx string, name, expression string) (notice string, err error) { + + if _, err = self.jsvm.Run("var transaction = " + tx + ";"); err != nil { return "", fmt.Errorf("natspec.js error setting transaction: %v", err) } - if _, err = self.jsvm.Run("var abi = " + abi + ";"); err != nil { + if _, err = self.jsvm.Run("var abi = " + string(self.abiDocJson) + ";"); err != nil { return "", fmt.Errorf("natspec.js error setting abi: %v", err) } - if _, err = self.jsvm.Run("var method = '" + method + "';"); err != nil { + if _, err = self.jsvm.Run("var method = '" + name + "';"); err != nil { return "", fmt.Errorf("natspec.js error setting method: %v", err) } diff --git a/common/natspec/natspec_e2e_test.go b/common/natspec/natspec_e2e_test.go new file mode 100644 index 000000000..147abe162 --- /dev/null +++ b/common/natspec/natspec_e2e_test.go @@ -0,0 +1,340 @@ +package natspec + +import ( + "io/ioutil" + "math/big" + "os" + "testing" + + "github.com/ethereum/go-ethereum/accounts" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/docserver" + "github.com/ethereum/go-ethereum/common/resolver" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/eth" + "github.com/ethereum/go-ethereum/rpc" + xe "github.com/ethereum/go-ethereum/xeth" +) + +type testFrontend struct { + t *testing.T + ethereum *eth.Ethereum + xeth *xe.XEth + api *rpc.EthereumApi + coinbase string + stateDb *state.StateDB + txc uint64 + lastConfirm string + makeNatSpec bool +} + +const ( + testAccount = "e273f01c99144c438695e10f24926dc1f9fbf62d" + testBalance = "1000000000000" +) + +const testFileName = "long_file_name_for_testing_registration_of_URLs_longer_than_32_bytes.content" + +const testNotice = "Register key `utils.toHex(_key)` <- content `utils.toHex(_content)`" +const testExpNotice = "Register key 0xadd1a7d961cff0242089674ec2ef6fca671ab15e1fe80e38859fc815b98d88ab <- content 0xc00d5bcc872e17813df6ec5c646bb281a6e2d3b454c2c400c78192adf3344af9" +const testExpNotice2 = `About to submit transaction (NatSpec notice error "abi key does not match any method"): {"id":6,"jsonrpc":"2.0","method":"eth_transact","params":[{"from":"0xe273f01c99144c438695e10f24926dc1f9fbf62d","to":"0xb737b91f8e95cf756766fc7c62c9a8ff58470381","value":"100000000000","gas":"100000","gasPrice":"100000","data":"0x31e12c20"}]}` +const testExpNotice3 = `About to submit transaction (no NatSpec info found for contract): {"id":6,"jsonrpc":"2.0","method":"eth_transact","params":[{"from":"0xe273f01c99144c438695e10f24926dc1f9fbf62d","to":"0x8b839ad85686967a4f418eccc81962eaee314ac3","value":"100000000000","gas":"100000","gasPrice":"100000","data":"0x300a3bbfc00d5bcc872e17813df6ec5c646bb281a6e2d3b454c2c400c78192adf3344af900000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000"}]}` + +const testUserDoc = ` +{ + "source": "...", + "language": "Solidity", + "languageVersion": 1, + "methods": { + "register(uint256,uint256)": { + "notice": "` + testNotice + `" + } + }, + "invariants": [ + { "notice": "" } + ], + "construction": [ + { "notice": "" } + ] +} +` + +const testABI = ` +[{ + "name": "register", + "constant": false, + "type": "function", + "inputs": [{ + "name": "_key", + "type": "uint256" + }, { + "name": "_content", + "type": "uint256" + }], + "outputs": [] +}] +` + +const testDocs = ` +{ + "userdoc": ` + testUserDoc + `, + "abi": ` + testABI + ` +} +` + +func (f *testFrontend) UnlockAccount(acc []byte) bool { + f.t.Logf("Unlocking account %v\n", common.Bytes2Hex(acc)) + f.ethereum.AccountManager().Unlock(acc, "password") + return true +} + +func (f *testFrontend) ConfirmTransaction(tx string) bool { + //f.t.Logf("ConfirmTransaction called tx = %v", tx) + if f.makeNatSpec { + ds, err := docserver.New("/tmp/") + if err != nil { + f.t.Errorf("Error creating DocServer: %v", err) + } + f.lastConfirm = GetNotice(f.xeth, tx, ds) + } + return true +} + +var port = 30300 + +func testEth(t *testing.T) (ethereum *eth.Ethereum, err error) { + os.RemoveAll("/tmp/eth-natspec/") + err = os.MkdirAll("/tmp/eth-natspec/keys/e273f01c99144c438695e10f24926dc1f9fbf62d/", os.ModePerm) + if err != nil { + t.Errorf("%v", err) + return + } + err = os.MkdirAll("/tmp/eth-natspec/data", os.ModePerm) + if err != nil { + t.Errorf("%v", err) + return + } + ks := crypto.NewKeyStorePlain("/tmp/eth-natspec/keys") + ioutil.WriteFile("/tmp/eth-natspec/keys/e273f01c99144c438695e10f24926dc1f9fbf62d/e273f01c99144c438695e10f24926dc1f9fbf62d", + []byte(`{"Id":"RhRXD+fNRKS4jx+7ZfEsNA==","Address":"4nPwHJkUTEOGleEPJJJtwfn79i0=","PrivateKey":"h4ACVpe74uIvi5Cg/2tX/Yrm2xdr3J7QoMbMtNX2CNc="}`), os.ModePerm) + + port++ + ethereum, err = eth.New(ð.Config{ + DataDir: "/tmp/eth-natspec", + AccountManager: accounts.NewManager(ks), + Name: "test", + }) + + if err != nil { + t.Errorf("%v", err) + return + } + + return +} + +func testInit(t *testing.T) (self *testFrontend) { + + core.GenesisData = []byte(`{ + "` + testAccount + `": {"balance": "` + testBalance + `"} + }`) + + ethereum, err := testEth(t) + if err != nil { + t.Errorf("error creating ethereum: %v", err) + return + } + err = ethereum.Start() + if err != nil { + t.Errorf("error starting ethereum: %v", err) + return + } + + self = &testFrontend{t: t, ethereum: ethereum} + self.xeth = xe.New(ethereum, self) + self.api = rpc.NewEthereumApi(self.xeth) + + addr := self.xeth.Coinbase() + self.coinbase = addr + if addr != "0x"+testAccount { + t.Errorf("CoinBase %v does not match TestAccount 0x%v", addr, testAccount) + } + t.Logf("CoinBase is %v", addr) + + balance := self.xeth.BalanceAt(testAccount) + /*if balance != core.TestBalance { + t.Errorf("Balance %v does not match TestBalance %v", balance, core.TestBalance) + }*/ + t.Logf("Balance is %v", balance) + + self.stateDb = self.ethereum.ChainManager().State().Copy() + + return + +} + +func (self *testFrontend) insertTx(addr, contract, fnsig string, args []string) { + + //cb := common.HexToAddress(self.coinbase) + //coinbase := self.ethereum.ChainManager().State().GetStateObject(cb) + + hash := common.Bytes2Hex(crypto.Sha3([]byte(fnsig))) + data := "0x" + hash[0:8] + for _, arg := range args { + data = data + common.Bytes2Hex(common.Hex2BytesFixed(arg, 32)) + } + self.t.Logf("Tx data: %v", data) + + jsontx := ` +[{ + "from": "` + addr + `", + "to": "` + contract + `", + "value": "100000000000", + "gas": "100000", + "gasPrice": "100000", + "data": "` + data + `" +}] +` + req := &rpc.RpcRequest{ + Jsonrpc: "2.0", + Method: "eth_transact", + Params: []byte(jsontx), + Id: 6, + } + + var reply interface{} + err0 := self.api.GetRequestReply(req, &reply) + if err0 != nil { + self.t.Errorf("GetRequestReply error: %v", err0) + } + + //self.xeth.Transact(addr, contract, "100000000000", "100000", "100000", data) + +} + +func (self *testFrontend) applyTxs() { + + cb := common.HexToAddress(self.coinbase) + block := self.ethereum.ChainManager().NewBlock(cb) + coinbase := self.stateDb.GetStateObject(cb) + coinbase.SetGasPool(big.NewInt(10000000)) + txs := self.ethereum.TxPool().GetTransactions() + + for i := 0; i < len(txs); i++ { + for _, tx := range txs { + //self.t.Logf("%v %v %v", i, tx.Nonce(), self.txc) + if tx.Nonce() == self.txc { + _, gas, err := core.ApplyMessage(core.NewEnv(self.stateDb, self.ethereum.ChainManager(), tx, block), tx, coinbase) + //self.ethereum.TxPool().RemoveSet([]*types.Transaction{tx}) + self.t.Logf("ApplyMessage: gas %v err %v", gas, err) + self.txc++ + } + } + } + + //self.ethereum.TxPool().RemoveSet(txs) + self.xeth = self.xeth.WithState(self.stateDb) + +} + +func (self *testFrontend) registerURL(hash common.Hash, url string) { + hashHex := common.Bytes2Hex(hash[:]) + urlBytes := []byte(url) + var bb bool = true + var cnt byte + for bb { + bb = len(urlBytes) > 0 + urlb := urlBytes + if len(urlb) > 32 { + urlb = urlb[:32] + } + urlHex := common.Bytes2Hex(urlb) + self.insertTx(self.coinbase, resolver.URLHintContractAddress, "register(uint256,uint8,uint256)", []string{hashHex, common.Bytes2Hex([]byte{cnt}), urlHex}) + if len(urlBytes) > 32 { + urlBytes = urlBytes[32:] + } else { + urlBytes = nil + } + cnt++ + } +} + +func (self *testFrontend) setOwner() { + + self.insertTx(self.coinbase, resolver.HashRegContractAddress, "setowner()", []string{}) + + /*owner := self.xeth.StorageAt("0x"+resolver.HashRegContractAddress, "0x0000000000000000000000000000000000000000000000000000000000000000") + self.t.Logf("owner = %v", owner) + if owner != self.coinbase { + self.t.Errorf("setowner() unsuccessful, owner != coinbase") + }*/ +} + +func (self *testFrontend) registerNatSpec(codehash, dochash common.Hash) { + + codeHex := common.Bytes2Hex(codehash[:]) + docHex := common.Bytes2Hex(dochash[:]) + self.insertTx(self.coinbase, resolver.HashRegContractAddress, "register(uint256,uint256)", []string{codeHex, docHex}) +} + +func (self *testFrontend) testResolver() *resolver.Resolver { + return resolver.New(self.xeth, resolver.URLHintContractAddress, resolver.HashRegContractAddress) +} + +func TestNatspecE2E(t *testing.T) { + + tf := testInit(t) + defer tf.ethereum.Stop() + + resolver.CreateContracts(tf.xeth, testAccount) + t.Logf("URLHint contract registered at %v", resolver.URLHintContractAddress) + t.Logf("HashReg contract registered at %v", resolver.HashRegContractAddress) + tf.applyTxs() + + ioutil.WriteFile("/tmp/"+testFileName, []byte(testDocs), os.ModePerm) + dochash := common.BytesToHash(crypto.Sha3([]byte(testDocs))) + + codehex := tf.xeth.CodeAt(resolver.HashRegContractAddress) + codehash := common.BytesToHash(crypto.Sha3(common.Hex2Bytes(codehex[2:]))) + + tf.setOwner() + tf.registerNatSpec(codehash, dochash) + tf.registerURL(dochash, "file:///"+testFileName) + tf.applyTxs() + + chash, err := tf.testResolver().KeyToContentHash(codehash) + if err != nil { + t.Errorf("Can't find content hash") + } + t.Logf("chash = %x err = %v", chash, err) + url, err2 := tf.testResolver().ContentHashToUrl(dochash) + if err2 != nil { + t.Errorf("Can't find URL hint") + } + t.Logf("url = %v err = %v", url, err2) + + // NatSpec info for register method of HashReg contract installed + // now using the same transactions to check confirm messages + + tf.makeNatSpec = true + tf.registerNatSpec(codehash, dochash) + t.Logf("Confirm message: %v\n", tf.lastConfirm) + if tf.lastConfirm != testExpNotice { + t.Errorf("Wrong confirm message, expected '%v', got '%v'", testExpNotice, tf.lastConfirm) + } + + tf.setOwner() + t.Logf("Confirm message for unknown method: %v\n", tf.lastConfirm) + if tf.lastConfirm != testExpNotice2 { + t.Errorf("Wrong confirm message, expected '%v', got '%v'", testExpNotice2, tf.lastConfirm) + } + + tf.registerURL(dochash, "file:///test.content") + t.Logf("Confirm message for unknown contract: %v\n", tf.lastConfirm) + if tf.lastConfirm != testExpNotice3 { + t.Errorf("Wrong confirm message, expected '%v', got '%v'", testExpNotice3, tf.lastConfirm) + } + +} diff --git a/common/natspec/natspec_js.go b/common/natspec/natspec_js.go index 96d8204fb..571044bde 100644 --- a/common/natspec/natspec_js.go +++ b/common/natspec/natspec_js.go @@ -1,4 +1,4044 @@ package natspec -const natspecJS = `require=function t(e,n,r){function i(f,u){if(!n[f]){if(!e[f]){var s="function"==typeof require&&require;if(!u&&s)return s(f,!0);if(o)return o(f,!0);var c=new Error("Cannot find module '"+f+"'");throw c.code="MODULE_NOT_FOUND",c}var a=n[f]={exports:{}};e[f][0].call(a.exports,function(t){var n=e[f][1][t];return i(n?n:t)},a,a.exports,t,e,n,r)}return n[f].exports}for(var o="function"==typeof require&&require,f=0;f<r.length;f++)i(r[f]);return i}({1:[function(){},{}],2:[function(t,e){function n(){if(!f){f=!0;for(var t,e=o.length;e;){t=o,o=[];for(var n=-1;++n<e;)t[n]();e=o.length}f=!1}}function r(){}var i=e.exports={},o=[],f=!1;i.nextTick=function(t){o.push(t),f||setTimeout(n,0)},i.title="browser",i.browser=!0,i.env={},i.argv=[],i.version="",i.on=r,i.addListener=r,i.once=r,i.off=r,i.removeListener=r,i.removeAllListeners=r,i.emit=r,i.binding=function(){throw new Error("process.binding is not supported")},i.cwd=function(){return"/"},i.chdir=function(){throw new Error("process.chdir is not supported")},i.umask=function(){return 0}},{}],3:[function(t,e){var n=t("./utils"),r=t("./types"),i=t("./const"),o=t("./formatters"),f=function(t){console.error("parser does not support type: "+t)},u=function(t){return"[]"===t.slice(-2)},s=function(t,e){return u(t)||"string"===t?o.formatInputInt(e.length):""},c=r.inputTypes(),a=function(t,e){var n="",r="",i="";return t.forEach(function(t,r){n+=s(t.type,e[r])}),t.forEach(function(n,o){for(var s=!1,a=0;a<c.length&&!s;a++)s=c[a].type(t[o].type,e[o]);s||f(t[o].type);var l=c[a-1].format;u(t[o].type)?i+=e[o].reduce(function(t,e){return t+l(e)},""):"string"===t[o].type?i+=l(e[o]):r+=l(e[o])}),n+=r+i},l=function(t){return u(t)||"string"===t?2*i.ETH_PADDING:0},p=r.outputTypes(),h=function(t,e){e=e.slice(2);var n=[],s=2*i.ETH_PADDING,c=t.reduce(function(t,e){return t+l(e.type)},0),a=e.slice(0,c);return e=e.slice(c),t.forEach(function(i,c){for(var l=!1,h=0;h<p.length&&!l;h++)l=p[h].type(t[c].type);l||f(t[c].type);var g=p[h-1].format;if(u(t[c].type)){var m=o.formatOutputUInt(a.slice(0,s));a=a.slice(s);for(var d=[],v=0;m>v;v++)d.push(g(e.slice(0,s))),e=e.slice(s);n.push(d)}else r.prefixedType("string")(t[c].type)?(a=a.slice(s),n.push(g(e.slice(0,s))),e=e.slice(s)):(n.push(g(e.slice(0,s))),e=e.slice(s))}),n},g=function(t){var e={};return t.forEach(function(t){var r=n.extractDisplayName(t.name),i=n.extractTypeName(t.name),o=function(){var e=Array.prototype.slice.call(arguments);return a(t.inputs,e)};void 0===e[r]&&(e[r]=o),e[r][i]=o}),e},m=function(t){var e={};return t.forEach(function(t){var r=n.extractDisplayName(t.name),i=n.extractTypeName(t.name),o=function(e){return h(t.outputs,e)};void 0===e[r]&&(e[r]=o),e[r][i]=o}),e};e.exports={inputParser:g,outputParser:m,formatInput:a,formatOutput:h}},{"./const":4,"./formatters":5,"./types":6,"./utils":7}],4:[function(t,e){(function(n){if("build"!==n.env.NODE_ENV)var r=t("bignumber.js");var i=["wei","Kwei","Mwei","Gwei","szabo","finney","ether","grand","Mether","Gether","Tether","Pether","Eether","Zether","Yether","Nether","Dether","Vether","Uether"];e.exports={ETH_PADDING:32,ETH_SIGNATURE_LENGTH:4,ETH_UNITS:i,ETH_BIGNUMBER_ROUNDING_MODE:{ROUNDING_MODE:r.ROUND_DOWN},ETH_POLLING_TIMEOUT:1e3}}).call(this,t("_process"))},{_process:2,"bignumber.js":8}],5:[function(t,e){(function(n){if("build"!==n.env.NODE_ENV)var r=t("bignumber.js");var i=t("./utils"),o=t("./const"),f=function(t,e,n){return new Array(e-t.length+1).join(n?n:"0")+t},u=function(t){var e=2*o.ETH_PADDING;return t instanceof r||"number"==typeof t?("number"==typeof t&&(t=new r(t)),r.config(o.ETH_BIGNUMBER_ROUNDING_MODE),t=t.round(),t.lessThan(0)&&(t=new r("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",16).plus(t).plus(1)),t=t.toString(16)):t=0===t.indexOf("0x")?t.substr(2):"string"==typeof t?u(new r(t)):(+t).toString(16),f(t,e)},s=function(t){return i.fromAscii(t,o.ETH_PADDING).substr(2)},c=function(t){return"000000000000000000000000000000000000000000000000000000000000000"+(t?"1":"0")},a=function(t){return u(new r(t).times(new r(2).pow(128)))},l=function(t){return"1"===new r(t.substr(0,1),16).toString(2).substr(0,1)},p=function(t){return t=t||"0",l(t)?new r(t,16).minus(new r("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",16)).minus(1):new r(t,16)},h=function(t){return t=t||"0",new r(t,16)},g=function(t){return p(t).dividedBy(new r(2).pow(128))},m=function(t){return h(t).dividedBy(new r(2).pow(128))},d=function(t){return"0x"+t},v=function(t){return"0000000000000000000000000000000000000000000000000000000000000001"===t?!0:!1},w=function(t){return i.toAscii(t)},y=function(t){return"0x"+t.slice(t.length-40,t.length)};e.exports={formatInputInt:u,formatInputString:s,formatInputBool:c,formatInputReal:a,formatOutputInt:p,formatOutputUInt:h,formatOutputReal:g,formatOutputUReal:m,formatOutputHash:d,formatOutputBool:v,formatOutputString:w,formatOutputAddress:y}}).call(this,t("_process"))},{"./const":4,"./utils":7,_process:2,"bignumber.js":8}],6:[function(t,e){var n=t("./formatters"),r=function(t){return function(e){return 0===e.indexOf(t)}},i=function(t){return function(e){return t===e}},o=function(){return[{type:r("uint"),format:n.formatInputInt},{type:r("int"),format:n.formatInputInt},{type:r("hash"),format:n.formatInputInt},{type:r("string"),format:n.formatInputString},{type:r("real"),format:n.formatInputReal},{type:r("ureal"),format:n.formatInputReal},{type:i("address"),format:n.formatInputInt},{type:i("bool"),format:n.formatInputBool}]},f=function(){return[{type:r("uint"),format:n.formatOutputUInt},{type:r("int"),format:n.formatOutputInt},{type:r("hash"),format:n.formatOutputHash},{type:r("string"),format:n.formatOutputString},{type:r("real"),format:n.formatOutputReal},{type:r("ureal"),format:n.formatOutputUReal},{type:i("address"),format:n.formatOutputAddress},{type:i("bool"),format:n.formatOutputBool}]};e.exports={prefixedType:r,namedType:i,inputTypes:o,outputTypes:f}},{"./formatters":5}],7:[function(t,e){var n=t("./const"),r=function(t,e){for(var n=!1,r=0;r<t.length&&!n;r++)n=e(t[r]);return n?r-1:-1},i=function(t){var e="",n=0,r=t.length;for("0x"===t.substring(0,2)&&(n=2);r>n;n+=2){var i=parseInt(t.substr(n,2),16);if(0===i)break;e+=String.fromCharCode(i)}return e},o=function(t){for(var e="",n=0;n<t.length;n++){var r=t.charCodeAt(n).toString(16);e+=r.length<2?"0"+r:r}return e},f=function(t,e){e=void 0===e?0:e;for(var n=o(t);n.length<2*e;)n+="00";return"0x"+n},u=function(t){var e=t.indexOf("(");return-1!==e?t.substr(0,e):t},s=function(t){var e=t.indexOf("(");return-1!==e?t.substr(e+1,t.length-1-(e+1)).replace(" ",""):""},c=function(t){return t.filter(function(t){return"function"===t.type})},a=function(t){return t.filter(function(t){return"event"===t.type})},l=function(t){for(var e="string"==typeof t?0===t.indexOf("0x")?parseInt(t.substr(2),16):parseInt(t):t,r=0,i=n.ETH_UNITS;e>3e3&&r<i.length-1;)e/=1e3,r++;for(var o=e.toString().length<e.toFixed(2).length?e.toString():e.toFixed(2),f=function(t,e,n){return e+","+n};;){var u=o;if(o=o.replace(/(\d)(\d\d\d[\.\,])/,f),u===o)break}return o+" "+i[r]};e.exports={findIndex:r,toAscii:i,fromAscii:f,extractDisplayName:u,extractTypeName:s,filterFunctions:c,filterEvents:a,toEth:l}},{"./const":4}],8:[function(t,e){!function(n){"use strict";function r(t){function e(t,r){var i,o,f,u,s,c,a=this;if(!(a instanceof e))return $&&U(26,"constructor call without new",t),new e(t,r);if(null!=r&&z(r,2,64,L,"base")){if(r=0|r,c=t+"",10==r)return a=new e(t instanceof e?t:c),F(a,B+a.e+1,H);if((u="number"==typeof t)&&0*t!=0||!new RegExp("^-?"+(i="["+N.slice(0,r)+"]+")+"(?:\\."+i+")?$",37>r?"i":"").test(c))return m(a,c,u,r);u?(a.s=0>1/t?(c=c.slice(1),-1):1,$&&c.replace(/^0\.0*|\./,"").length>15&&U(L,O,t),u=!1):a.s=45===c.charCodeAt(0)?(c=c.slice(1),-1):1,c=n(c,10,r,a.s)}else{if(t instanceof e)return a.s=t.s,a.e=t.e,a.c=(t=t.c)?t.slice():t,void(L=0);if((u="number"==typeof t)&&0*t==0){if(a.s=0>1/t?(t=-t,-1):1,t===~~t){for(o=0,f=t;f>=10;f/=10,o++);return a.e=o,a.c=[t],void(L=0)}c=t+""}else{if(!d.test(c=t+""))return m(a,c,u);a.s=45===c.charCodeAt(0)?(c=c.slice(1),-1):1}}for((o=c.indexOf("."))>-1&&(c=c.replace(".","")),(f=c.search(/e/i))>0?(0>o&&(o=f),o+=+c.slice(f+1),c=c.substring(0,f)):0>o&&(o=c.length),f=0;48===c.charCodeAt(f);f++);for(s=c.length;48===c.charCodeAt(--s););if(c=c.slice(f,s+1))if(s=c.length,u&&$&&s>15&&U(L,O,a.s*t),o=o-f-1,o>q)a.c=a.e=null;else if(k>o)a.c=[a.e=0];else{if(a.e=o,a.c=[],f=(o+1)%I,0>o&&(f+=I),s>f){for(f&&a.c.push(+c.slice(0,f)),s-=I;s>f;)a.c.push(+c.slice(f,f+=I));c=c.slice(f),f=I-c.length}else f-=s;for(;f--;c+="0");a.c.push(+c)}else a.c=[a.e=0];L=0}function n(t,n,r,i){var f,u,s,a,p,h,g,m=t.indexOf("."),d=B,v=H;for(37>r&&(t=t.toLowerCase()),m>=0&&(s=Y,Y=0,t=t.replace(".",""),g=new e(r),p=g.pow(t.length-m),Y=s,g.c=c(l(o(p.c),p.e),10,n),g.e=g.c.length),h=c(t,r,n),u=s=h.length;0==h[--s];h.pop());if(!h[0])return"0";if(0>m?--u:(p.c=h,p.e=u,p.s=i,p=G(p,g,d,v,n),h=p.c,a=p.r,u=p.e),f=u+d+1,m=h[f],s=n/2,a=a||0>f||null!=h[f+1],a=4>v?(null!=m||a)&&(0==v||v==(p.s<0?3:2)):m>s||m==s&&(4==v||a||6==v&&1&h[f-1]||v==(p.s<0?8:7)),1>f||!h[0])t=a?l("1",-d):"0";else{if(h.length=f,a)for(--n;++h[--f]>n;)h[f]=0,f||(++u,h.unshift(1));for(s=h.length;!h[--s];);for(m=0,t="";s>=m;t+=N.charAt(h[m++]));t=l(t,u)}return t}function h(t,n,r,i){var f,u,s,c,p;if(r=null!=r&&z(r,0,8,i,b)?0|r:H,!t.c)return t.toString();if(f=t.c[0],s=t.e,null==n)p=o(t.c),p=19==i||24==i&&C>=s?a(p,s):l(p,s);else if(t=F(new e(t),n,r),u=t.e,p=o(t.c),c=p.length,19==i||24==i&&(u>=n||C>=u)){for(;n>c;p+="0",c++);p=a(p,u)}else if(n-=s,p=l(p,u),u+1>c){if(--n>0)for(p+=".";n--;p+="0");}else if(n+=u-c,n>0)for(u+1==c&&(p+=".");n--;p+="0");return t.s<0&&f?"-"+p:p}function S(t,n){var r,i,o=0;for(s(t[0])&&(t=t[0]),r=new e(t[0]);++o<t.length;){if(i=new e(t[o]),!i.s){r=i;break}n.call(r,i)&&(r=i)}return r}function A(t,e,n,r,i){return(e>t||t>n||t!=p(t))&&U(r,(i||"decimal places")+(e>t||t>n?" out of range":" not an integer"),t),!0}function R(t,e,n){for(var r=1,i=e.length;!e[--i];e.pop());for(i=e[0];i>=10;i/=10,r++);return(n=r+n*I-1)>q?t.c=t.e=null:k>n?t.c=[t.e=0]:(t.e=n,t.c=e),t}function U(t,e,n){var r=new Error(["new BigNumber","cmp","config","div","divToInt","eq","gt","gte","lt","lte","minus","mod","plus","precision","random","round","shift","times","toDigits","toExponential","toFixed","toFormat","toFraction","pow","toPrecision","toString","BigNumber"][t]+"() "+e+": "+n);throw r.name="BigNumber Error",L=0,r}function F(t,e,n,r){var i,o,f,u,s,c,a,l=t.c,p=_;if(l){t:{for(i=1,u=l[0];u>=10;u/=10,i++);if(o=e-i,0>o)o+=I,f=e,s=l[c=0],a=s/p[i-f-1]%10|0;else if(c=v((o+1)/I),c>=l.length){if(!r)break t;for(;l.length<=c;l.push(0));s=a=0,i=1,o%=I,f=o-I+1}else{for(s=u=l[c],i=1;u>=10;u/=10,i++);o%=I,f=o-I+i,a=0>f?0:s/p[i-f-1]%10|0}if(r=r||0>e||null!=l[c+1]||(0>f?s:s%p[i-f-1]),r=4>n?(a||r)&&(0==n||n==(t.s<0?3:2)):a>5||5==a&&(4==n||r||6==n&&(o>0?f>0?s/p[i-f]:0:l[c-1])%10&1||n==(t.s<0?8:7)),1>e||!l[0])return l.length=0,r?(e-=t.e+1,l[0]=p[e%I],t.e=-e||0):l[0]=t.e=0,t;if(0==o?(l.length=c,u=1,c--):(l.length=c+1,u=p[I-o],l[c]=f>0?w(s/p[i-f]%p[f])*u:0),r)for(;;){if(0==c){for(o=1,f=l[0];f>=10;f/=10,o++);for(f=l[0]+=u,u=1;f>=10;f/=10,u++);o!=u&&(t.e++,l[0]==E&&(l[0]=1));break}if(l[c]+=u,l[c]!=E)break;l[c--]=0,u=1}for(o=l.length;0===l[--o];l.pop());}t.e>q?t.c=t.e=null:t.e<k&&(t.c=[t.e=0])}return t}var G,L=0,M=e.prototype,P=new e(1),B=20,H=4,C=-7,j=21,k=-1e7,q=1e7,$=!0,z=A,V=!1,W=1,Y=100,Z={decimalSeparator:".",groupSeparator:",",groupSize:3,secondaryGroupSize:0,fractionGroupSeparator:" ",fractionGroupSize:0};return e.another=r,e.ROUND_UP=0,e.ROUND_DOWN=1,e.ROUND_CEIL=2,e.ROUND_FLOOR=3,e.ROUND_HALF_UP=4,e.ROUND_HALF_DOWN=5,e.ROUND_HALF_EVEN=6,e.ROUND_HALF_CEIL=7,e.ROUND_HALF_FLOOR=8,e.EUCLID=9,e.config=function(){var t,e,n=0,r={},i=arguments,o=i[0],f=o&&"object"==typeof o?function(){return o.hasOwnProperty(e)?null!=(t=o[e]):void 0}:function(){return i.length>n?null!=(t=i[n++]):void 0};return f(e="DECIMAL_PLACES")&&z(t,0,D,2,e)&&(B=0|t),r[e]=B,f(e="ROUNDING_MODE")&&z(t,0,8,2,e)&&(H=0|t),r[e]=H,f(e="EXPONENTIAL_AT")&&(s(t)?z(t[0],-D,0,2,e)&&z(t[1],0,D,2,e)&&(C=0|t[0],j=0|t[1]):z(t,-D,D,2,e)&&(C=-(j=0|(0>t?-t:t)))),r[e]=[C,j],f(e="RANGE")&&(s(t)?z(t[0],-D,-1,2,e)&&z(t[1],1,D,2,e)&&(k=0|t[0],q=0|t[1]):z(t,-D,D,2,e)&&(0|t?k=-(q=0|(0>t?-t:t)):$&&U(2,e+" cannot be zero",t))),r[e]=[k,q],f(e="ERRORS")&&(t===!!t||1===t||0===t?(L=0,z=($=!!t)?A:u):$&&U(2,e+y,t)),r[e]=$,f(e="CRYPTO")&&(t===!!t||1===t||0===t?(V=!(!t||!g||"object"!=typeof g),t&&!V&&$&&U(2,"crypto unavailable",g)):$&&U(2,e+y,t)),r[e]=V,f(e="MODULO_MODE")&&z(t,0,9,2,e)&&(W=0|t),r[e]=W,f(e="POW_PRECISION")&&z(t,0,D,2,e)&&(Y=0|t),r[e]=Y,f(e="FORMAT")&&("object"==typeof t?Z=t:$&&U(2,e+" not an object",t)),r[e]=Z,r},e.max=function(){return S(arguments,M.lt)},e.min=function(){return S(arguments,M.gt)},e.random=function(){var t=9007199254740992,n=Math.random()*t&2097151?function(){return w(Math.random()*t)}:function(){return 8388608*(1073741824*Math.random()|0)+(8388608*Math.random()|0)};return function(t){var r,i,o,f,u,s=0,c=[],a=new e(P);if(t=null!=t&&z(t,0,D,14)?0|t:B,f=v(t/I),V)if(g&&g.getRandomValues){for(r=g.getRandomValues(new Uint32Array(f*=2));f>s;)u=131072*r[s]+(r[s+1]>>>11),u>=9e15?(i=g.getRandomValues(new Uint32Array(2)),r[s]=i[0],r[s+1]=i[1]):(c.push(u%1e14),s+=2);s=f/2}else if(g&&g.randomBytes){for(r=g.randomBytes(f*=7);f>s;)u=281474976710656*(31&r[s])+1099511627776*r[s+1]+4294967296*r[s+2]+16777216*r[s+3]+(r[s+4]<<16)+(r[s+5]<<8)+r[s+6],u>=9e15?g.randomBytes(7).copy(r,s):(c.push(u%1e14),s+=7);s=f/7}else $&&U(14,"crypto unavailable",g);if(!s)for(;f>s;)u=n(),9e15>u&&(c[s++]=u%1e14);for(f=c[--s],t%=I,f&&t&&(u=_[I-t],c[s]=w(f/u)*u);0===c[s];c.pop(),s--);if(0>s)c=[o=0];else{for(o=-1;0===c[0];c.shift(),o-=I);for(s=1,u=c[0];u>=10;u/=10,s++);I>s&&(o-=I-s)}return a.e=o,a.c=c,a}}(),G=function(){function t(t,e,n){var r,i,o,f,u=0,s=t.length,c=e%T,a=e/T|0;for(t=t.slice();s--;)o=t[s]%T,f=t[s]/T|0,r=a*o+f*c,i=c*o+r%T*T+u,u=(i/n|0)+(r/T|0)+a*f,t[s]=i%n;return u&&t.unshift(u),t}function n(t,e,n,r){var i,o;if(n!=r)o=n>r?1:-1;else for(i=o=0;n>i;i++)if(t[i]!=e[i]){o=t[i]>e[i]?1:-1;break}return o}function r(t,e,n,r){for(var i=0;n--;)t[n]-=i,i=t[n]<e[n]?1:0,t[n]=i*r+t[n]-e[n];for(;!t[0]&&t.length>1;t.shift());}return function(o,f,u,s,c){var a,l,p,h,g,m,d,v,y,b,O,N,x,_,T,D,S,A=o.s==f.s?1:-1,R=o.c,U=f.c;if(!(R&&R[0]&&U&&U[0]))return new e(o.s&&f.s&&(R?!U||R[0]!=U[0]:U)?R&&0==R[0]||!U?0*A:A/0:0/0);for(v=new e(A),y=v.c=[],l=o.e-f.e,A=u+l+1,c||(c=E,l=i(o.e/I)-i(f.e/I),A=A/I|0),p=0;U[p]==(R[p]||0);p++);if(U[p]>(R[p]||0)&&l--,0>A)y.push(1),h=!0;else{for(_=R.length,D=U.length,p=0,A+=2,g=w(c/(U[0]+1)),g>1&&(U=t(U,g,c),R=t(R,g,c),D=U.length,_=R.length),x=D,b=R.slice(0,D),O=b.length;D>O;b[O++]=0);S=U.slice(),S.unshift(0),T=U[0],U[1]>=c/2&&T++;do g=0,a=n(U,b,D,O),0>a?(N=b[0],D!=O&&(N=N*c+(b[1]||0)),g=w(N/T),g>1?(g>=c&&(g=c-1),m=t(U,g,c),d=m.length,O=b.length,a=n(m,b,d,O),1==a&&(g--,r(m,d>D?S:U,d,c))):(0==g&&(a=g=1),m=U.slice()),d=m.length,O>d&&m.unshift(0),r(b,m,O,c),-1==a&&(O=b.length,a=n(U,b,D,O),1>a&&(g++,r(b,O>D?S:U,O,c))),O=b.length):0===a&&(g++,b=[0]),y[p++]=g,a&&b[0]?b[O++]=R[x]||0:(b=[R[x]],O=1);while((x++<_||null!=b[0])&&A--);h=null!=b[0],y[0]||y.shift()}if(c==E){for(p=1,A=y[0];A>=10;A/=10,p++);F(v,u+(v.e=p+l*I-1)+1,s,h)}else v.e=l,v.r=+h;return v}}(),m==function(){var t=/^(-?)0([xbo])(\w[\w.]*$)/i,n=/^([^.]+)\.$/,r=/^\.([^.]+)$/,i=/^-?(Infinity|NaN)$/,o=/^\s*\+([\w.])|^\s+|\s+$/g;return function(f,u,s,c){var a,l=s?u:u.replace(o,"$1");if(i.test(l))f.s=isNaN(l)?null:0>l?-1:1;else{if(!s&&(l=l.replace(t,function(t,e,n){return a="x"==(n=n.toLowerCase())?16:"b"==n?2:8,c&&c!=a?t:e}),c&&(a=c,l=l.replace(n,"$1").replace(r,"0.$1")),u!=l))return new e(l,a);$&&U(L,"not a"+(c?" base "+c:"")+" number",u),f.s=null}f.c=f.e=null,L=0}}(),M.absoluteValue=M.abs=function(){var t=new e(this);return t.s<0&&(t.s=1),t},M.ceil=function(){return F(new e(this),this.e+1,2)},M.comparedTo=M.cmp=function(t,n){return L=1,f(this,new e(t,n))},M.decimalPlaces=M.dp=function(){var t,e,n=this.c;if(!n)return null;if(t=((e=n.length-1)-i(this.e/I))*I,e=n[e])for(;e%10==0;e/=10,t--);return 0>t&&(t=0),t},M.dividedBy=M.div=function(t,n){return L=3,G(this,new e(t,n),B,H)},M.dividedToIntegerBy=M.divToInt=function(t,n){return L=4,G(this,new e(t,n),0,1)},M.equals=M.eq=function(t,n){return L=5,0===f(this,new e(t,n))},M.floor=function(){return F(new e(this),this.e+1,3)},M.greaterThan=M.gt=function(t,n){return L=6,f(this,new e(t,n))>0},M.greaterThanOrEqualTo=M.gte=function(t,n){return L=7,1===(n=f(this,new e(t,n)))||0===n},M.isFinite=function(){return!!this.c},M.isInteger=M.isInt=function(){return!!this.c&&i(this.e/I)>this.c.length-2},M.isNaN=function(){return!this.s},M.isNegative=M.isNeg=function(){return this.s<0},M.isZero=function(){return!!this.c&&0==this.c[0]},M.lessThan=M.lt=function(t,n){return L=8,f(this,new e(t,n))<0},M.lessThanOrEqualTo=M.lte=function(t,n){return L=9,-1===(n=f(this,new e(t,n)))||0===n},M.minus=M.sub=function(t,n){var r,o,f,u,s=this,c=s.s;if(L=10,t=new e(t,n),n=t.s,!c||!n)return new e(0/0);if(c!=n)return t.s=-n,s.plus(t);var a=s.e/I,l=t.e/I,p=s.c,h=t.c;if(!a||!l){if(!p||!h)return p?(t.s=-n,t):new e(h?s:0/0);if(!p[0]||!h[0])return h[0]?(t.s=-n,t):new e(p[0]?s:3==H?-0:0)}if(a=i(a),l=i(l),p=p.slice(),c=a-l){for((u=0>c)?(c=-c,f=p):(l=a,f=h),f.reverse(),n=c;n--;f.push(0));f.reverse()}else for(o=(u=(c=p.length)<(n=h.length))?c:n,c=n=0;o>n;n++)if(p[n]!=h[n]){u=p[n]<h[n];break}if(u&&(f=p,p=h,h=f,t.s=-t.s),n=(o=h.length)-(r=p.length),n>0)for(;n--;p[r++]=0);for(n=E-1;o>c;){if(p[--o]<h[o]){for(r=o;r&&!p[--r];p[r]=n);--p[r],p[o]+=E}p[o]-=h[o]}for(;0==p[0];p.shift(),--l);return p[0]?R(t,p,l):(t.s=3==H?-1:1,t.c=[t.e=0],t)},M.modulo=M.mod=function(t,n){var r,i,o=this;return L=11,t=new e(t,n),!o.c||!t.s||t.c&&!t.c[0]?new e(0/0):!t.c||o.c&&!o.c[0]?new e(o):(9==W?(i=t.s,t.s=1,r=G(o,t,0,3),t.s=i,r.s*=i):r=G(o,t,0,W),o.minus(r.times(t)))},M.negated=M.neg=function(){var t=new e(this);return t.s=-t.s||null,t},M.plus=M.add=function(t,n){var r,o=this,f=o.s;if(L=12,t=new e(t,n),n=t.s,!f||!n)return new e(0/0);if(f!=n)return t.s=-n,o.minus(t);var u=o.e/I,s=t.e/I,c=o.c,a=t.c;if(!u||!s){if(!c||!a)return new e(f/0);if(!c[0]||!a[0])return a[0]?t:new e(c[0]?o:0*f)}if(u=i(u),s=i(s),c=c.slice(),f=u-s){for(f>0?(s=u,r=a):(f=-f,r=c),r.reverse();f--;r.push(0));r.reverse()}for(f=c.length,n=a.length,0>f-n&&(r=a,a=c,c=r,n=f),f=0;n;)f=(c[--n]=c[n]+a[n]+f)/E|0,c[n]%=E;return f&&(c.unshift(f),++s),R(t,c,s)},M.precision=M.sd=function(t){var e,n,r=this,i=r.c;if(null!=t&&t!==!!t&&1!==t&&0!==t&&($&&U(13,"argument"+y,t),t!=!!t&&(t=null)),!i)return null;if(n=i.length-1,e=n*I+1,n=i[n]){for(;n%10==0;n/=10,e--);for(n=i[0];n>=10;n/=10,e++);}return t&&r.e+1>e&&(e=r.e+1),e},M.round=function(t,n){var r=new e(this);return(null==t||z(t,0,D,15))&&F(r,~~t+this.e+1,null!=n&&z(n,0,8,15,b)?0|n:H),r},M.shift=function(t){var n=this;return z(t,-x,x,16,"argument")?n.times("1e"+p(t)):new e(n.c&&n.c[0]&&(-x>t||t>x)?n.s*(0>t?0:1/0):n)},M.squareRoot=M.sqrt=function(){var t,n,r,f,u,s=this,c=s.c,a=s.s,l=s.e,p=B+4,h=new e("0.5");if(1!==a||!c||!c[0])return new e(!a||0>a&&(!c||c[0])?0/0:c?s:1/0);if(a=Math.sqrt(+s),0==a||a==1/0?(n=o(c),(n.length+l)%2==0&&(n+="0"),a=Math.sqrt(n),l=i((l+1)/2)-(0>l||l%2),a==1/0?n="1e"+l:(n=a.toExponential(),n=n.slice(0,n.indexOf("e")+1)+l),r=new e(n)):r=new e(a+""),r.c[0])for(l=r.e,a=l+p,3>a&&(a=0);;)if(u=r,r=h.times(u.plus(G(s,u,p,1))),o(u.c).slice(0,a)===(n=o(r.c)).slice(0,a)){if(r.e<l&&--a,n=n.slice(a-3,a+1),"9999"!=n&&(f||"4999"!=n)){(!+n||!+n.slice(1)&&"5"==n.charAt(0))&&(F(r,r.e+B+2,1),t=!r.times(r).eq(s));break}if(!f&&(F(u,u.e+B+2,0),u.times(u).eq(s))){r=u;break}p+=4,a+=4,f=1}return F(r,r.e+B+1,H,t)},M.times=M.mul=function(t,n){var r,o,f,u,s,c,a,l,p,h,g,m,d,v,w,y=this,b=y.c,O=(L=17,t=new e(t,n)).c;if(!(b&&O&&b[0]&&O[0]))return!y.s||!t.s||b&&!b[0]&&!O||O&&!O[0]&&!b?t.c=t.e=t.s=null:(t.s*=y.s,b&&O?(t.c=[0],t.e=0):t.c=t.e=null),t;for(o=i(y.e/I)+i(t.e/I),t.s*=y.s,a=b.length,h=O.length,h>a&&(d=b,b=O,O=d,f=a,a=h,h=f),f=a+h,d=[];f--;d.push(0));for(v=E,w=T,f=h;--f>=0;){for(r=0,g=O[f]%w,m=O[f]/w|0,s=a,u=f+s;u>f;)l=b[--s]%w,p=b[s]/w|0,c=m*l+p*g,l=g*l+c%w*w+d[u]+r,r=(l/v|0)+(c/w|0)+m*p,d[u--]=l%v;d[u]=r}return r?++o:d.shift(),R(t,d,o)},M.toDigits=function(t,n){var r=new e(this);return t=null!=t&&z(t,1,D,18,"precision")?0|t:null,n=null!=n&&z(n,0,8,18,b)?0|n:H,t?F(r,t,n):r},M.toExponential=function(t,e){return h(this,null!=t&&z(t,0,D,19)?~~t+1:null,e,19)},M.toFixed=function(t,e){return h(this,null!=t&&z(t,0,D,20)?~~t+this.e+1:null,e,20)},M.toFormat=function(t,e){var n=h(this,null!=t&&z(t,0,D,21)?~~t+this.e+1:null,e,21);if(this.c){var r,i=n.split("."),o=+Z.groupSize,f=+Z.secondaryGroupSize,u=Z.groupSeparator,s=i[0],c=i[1],a=this.s<0,l=a?s.slice(1):s,p=l.length;if(f&&(r=o,o=f,f=r,p-=r),o>0&&p>0){for(r=p%o||o,s=l.substr(0,r);p>r;r+=o)s+=u+l.substr(r,o);f>0&&(s+=u+l.slice(r)),a&&(s="-"+s)}n=c?s+Z.decimalSeparator+((f=+Z.fractionGroupSize)?c.replace(new RegExp("\\d{"+f+"}\\B","g"),"$&"+Z.fractionGroupSeparator):c):s}return n},M.toFraction=function(t){var n,r,i,f,u,s,c,a,l,p=$,h=this,g=h.c,m=new e(P),d=r=new e(P),v=c=new e(P);if(null!=t&&($=!1,s=new e(t),$=p,(!(p=s.isInt())||s.lt(P))&&($&&U(22,"max denominator "+(p?"out of range":"not an integer"),t),t=!p&&s.c&&F(s,s.e+1,1).gte(P)?s:null)),!g)return h.toString();for(l=o(g),f=m.e=l.length-h.e-1,m.c[0]=_[(u=f%I)<0?I+u:u],t=!t||s.cmp(m)>0?f>0?m:d:s,u=q,q=1/0,s=new e(l),c.c[0]=0;a=G(s,m,0,1),i=r.plus(a.times(v)),1!=i.cmp(t);)r=v,v=i,d=c.plus(a.times(i=d)),c=i,m=s.minus(a.times(i=m)),s=i;return i=G(t.minus(r),v,0,1),c=c.plus(i.times(d)),r=r.plus(i.times(v)),c.s=d.s=h.s,f*=2,n=G(d,v,f,H).minus(h).abs().cmp(G(c,r,f,H).minus(h).abs())<1?[d.toString(),v.toString()]:[c.toString(),r.toString()],q=u,n},M.toNumber=function(){var t=this;return+t||(t.s?0*t.s:0/0)},M.toPower=M.pow=function(t){var n,r,i=w(0>t?-t:+t),o=this;if(!z(t,-x,x,23,"exponent")&&(!isFinite(t)||i>x&&(t/=0)||parseFloat(t)!=t&&!(t=0/0)))return new e(Math.pow(+o,t));for(n=Y?v(Y/I+2):0,r=new e(P);;){if(i%2){if(r=r.times(o),!r.c)break;n&&r.c.length>n&&(r.c.length=n)}if(i=w(i/2),!i)break;o=o.times(o),n&&o.c&&o.c.length>n&&(o.c.length=n)}return 0>t&&(r=P.div(r)),n?F(r,Y,H):r},M.toPrecision=function(t,e){return h(this,null!=t&&z(t,1,D,24,"precision")?0|t:null,e,24)},M.toString=function(t){var e,r=this,i=r.s,f=r.e;return null===f?i?(e="Infinity",0>i&&(e="-"+e)):e="NaN":(e=o(r.c),e=null!=t&&z(t,2,64,25,"base")?n(l(e,f),0|t,10,i):C>=f||f>=j?a(e,f):l(e,f),0>i&&r.c[0]&&(e="-"+e)),e},M.truncated=M.trunc=function(){return F(new e(this),this.e+1,1)},M.valueOf=M.toJSON=function(){return this.toString()},null!=t&&e.config(t),e}function i(t){var e=0|t;return t>0||t===e?e:e-1}function o(t){for(var e,n,r=1,i=t.length,o=t[0]+"";i>r;){for(e=t[r++]+"",n=I-e.length;n--;e="0"+e);o+=e}for(i=o.length;48===o.charCodeAt(--i););return o.slice(0,i+1||1)}function f(t,e){var n,r,i=t.c,o=e.c,f=t.s,u=e.s,s=t.e,c=e.e;if(!f||!u)return null;if(n=i&&!i[0],r=o&&!o[0],n||r)return n?r?0:-u:f;if(f!=u)return f;if(n=0>f,r=s==c,!i||!o)return r?0:!i^n?1:-1;if(!r)return s>c^n?1:-1;for(u=(s=i.length)<(c=o.length)?s:c,f=0;u>f;f++)if(i[f]!=o[f])return i[f]>o[f]^n?1:-1;return s==c?0:s>c^n?1:-1}function u(t,e,n){return(t=p(t))>=e&&n>=t}function s(t){return"[object Array]"==Object.prototype.toString.call(t)}function c(t,e,n){for(var r,i,o=[0],f=0,u=t.length;u>f;){for(i=o.length;i--;o[i]*=e);for(o[r=0]+=N.indexOf(t.charAt(f++));r<o.length;r++)o[r]>n-1&&(null==o[r+1]&&(o[r+1]=0),o[r+1]+=o[r]/n|0,o[r]%=n)}return o.reverse()}function a(t,e){return(t.length>1?t.charAt(0)+"."+t.slice(1):t)+(0>e?"e":"e+")+e}function l(t,e){var n,r;if(0>e){for(r="0.";++e;r+="0");t=r+t}else if(n=t.length,++e>n){for(r="0",e-=n;--e;r+="0");t+=r}else n>e&&(t=t.slice(0,e)+"."+t.slice(e));return t}function p(t){return t=parseFloat(t),0>t?v(t):w(t)}var h,g,m,d=/^-?(\d+(\.\d*)?|\.\d+)(e[+-]?\d+)?$/i,v=Math.ceil,w=Math.floor,y=" not a boolean or binary digit",b="rounding mode",O="number type has more than 15 significant digits",N="0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ$_",E=1e14,I=14,x=9007199254740991,_=[1,10,100,1e3,1e4,1e5,1e6,1e7,1e8,1e9,1e10,1e11,1e12,1e13],T=1e7,D=1e9;if(h=r(),"function"==typeof define&&define.amd)define(function(){return h});else if("undefined"!=typeof e&&e.exports){if(e.exports=h,!g)try{g=t("crypto")}catch(S){}}else n.BigNumber=h}(this)},{crypto:1}],natspec:[function(t,e){var n=t("./node_modules/ethereum.js/lib/abi.js"),r=function(){var t=function(t,e){Object.keys(t).forEach(function(n){e[n]=t[n]})},e=function(t){return Object.keys(t).reduce(function(t,e){return t+"var "+e+" = context['"+e+"'];\n"},"")},r=function(t,e){return t.filter(function(t){return t.name===e})[0]},i=function(t,e){var r=n.formatOutput(t.inputs,"0x"+e.params[0].data.slice(10));return t.inputs.reduce(function(t,e,n){return t[e.name]=r[n],t},{})},o=function(t,e){var n,r="",i=/\` + "`" + `(?:\\.|[^` + "`" + `\\])*\` + "`" + `/gim,o=0;try{for(;null!==(n=i.exec(t));){var f=i.lastIndex-n[0].length,u=n[0].slice(1,n[0].length-1);r+=t.slice(o,f);var s=e(u);r+=s,o=i.lastIndex}r+=t.slice(o)}catch(c){throw new Error("Natspec evaluation failed, wrong input params")}return r},f=function(n,f){var u={};if(f)try{var s=r(f.abi,f.method),c=i(s,f.transaction);t(c,u)}catch(a){throw new Error("Natspec evaluation failed, method does not exist")}var l=e(u),p=o(n,function(t){var e=new Function("context",l+"return "+t+";");return e(u).toString()});return p},u=function(t,e){try{return f(t,e)}catch(n){return n.message}};return{evaluateExpression:f,evaluateExpressionSafe:u}}();e.exports=r},{"./node_modules/ethereum.js/lib/abi.js":3}]},{},[]); +const natspecJS = //`require=function t(e,n,r){function i(f,u){if(!n[f]){if(!e[f]){var s="function"==typeof require&&require;if(!u&&s)return s(f,!0);if(o)return o(f,!0);var c=new Error("Cannot find module '"+f+"'");throw c.code="MODULE_NOT_FOUND",c}var a=n[f]={exports:{}};e[f][0].call(a.exports,function(t){var n=e[f][1][t];return i(n?n:t)},a,a.exports,t,e,n,r)}return n[f].exports}for(var o="function"==typeof require&&require,f=0;f<r.length;f++)i(r[f]);return i}({1:[function(){},{}],2:[function(t,e){function n(){if(!f){f=!0;for(var t,e=o.length;e;){t=o,o=[];for(var n=-1;++n<e;)t[n]();e=o.length}f=!1}}function r(){}var i=e.exports={},o=[],f=!1;i.nextTick=function(t){o.push(t),f||setTimeout(n,0)},i.title="browser",i.browser=!0,i.env={},i.argv=[],i.version="",i.on=r,i.addListener=r,i.once=r,i.off=r,i.removeListener=r,i.removeAllListeners=r,i.emit=r,i.binding=function(){throw new Error("process.binding is not supported")},i.cwd=function(){return"/"},i.chdir=function(){throw new Error("process.chdir is not supported")},i.umask=function(){return 0}},{}],3:[function(t,e){var n=t("./utils"),r=t("./types"),i=t("./const"),o=t("./formatters"),f=function(t){console.error("parser does not support type: "+t)},u=function(t){return"[]"===t.slice(-2)},s=function(t,e){return u(t)||"string"===t?o.formatInputInt(e.length):""},c=r.inputTypes(),a=function(t,e){var n="",r="",i="";return t.forEach(function(t,r){n+=s(t.type,e[r])}),t.forEach(function(n,o){for(var s=!1,a=0;a<c.length&&!s;a++)s=c[a].type(t[o].type,e[o]);s||f(t[o].type);var l=c[a-1].format;u(t[o].type)?i+=e[o].reduce(function(t,e){return t+l(e)},""):"string"===t[o].type?i+=l(e[o]):r+=l(e[o])}),n+=r+i},l=function(t){return u(t)||"string"===t?2*i.ETH_PADDING:0},p=r.outputTypes(),h=function(t,e){e=e.slice(2);var n=[],s=2*i.ETH_PADDING,c=t.reduce(function(t,e){return t+l(e.type)},0),a=e.slice(0,c);return e=e.slice(c),t.forEach(function(i,c){for(var l=!1,h=0;h<p.length&&!l;h++)l=p[h].type(t[c].type);l||f(t[c].type);var g=p[h-1].format;if(u(t[c].type)){var m=o.formatOutputUInt(a.slice(0,s));a=a.slice(s);for(var d=[],v=0;m>v;v++)d.push(g(e.slice(0,s))),e=e.slice(s);n.push(d)}else r.prefixedType("string")(t[c].type)?(a=a.slice(s),n.push(g(e.slice(0,s))),e=e.slice(s)):(n.push(g(e.slice(0,s))),e=e.slice(s))}),n},g=function(t){var e={};return t.forEach(function(t){var r=n.extractDisplayName(t.name),i=n.extractTypeName(t.name),o=function(){var e=Array.prototype.slice.call(arguments);return a(t.inputs,e)};void 0===e[r]&&(e[r]=o),e[r][i]=o}),e},m=function(t){var e={};return t.forEach(function(t){var r=n.extractDisplayName(t.name),i=n.extractTypeName(t.name),o=function(e){return h(t.outputs,e)};void 0===e[r]&&(e[r]=o),e[r][i]=o}),e};e.exports={inputParser:g,outputParser:m,formatInput:a,formatOutput:h}},{"./const":4,"./formatters":5,"./types":6,"./utils":7}],4:[function(t,e){(function(n){if("build"!==n.env.NODE_ENV)var r=t("bignumber.js");var i=["wei","Kwei","Mwei","Gwei","szabo","finney","ether","grand","Mether","Gether","Tether","Pether","Eether","Zether","Yether","Nether","Dether","Vether","Uether"];e.exports={ETH_PADDING:32,ETH_SIGNATURE_LENGTH:4,ETH_UNITS:i,ETH_BIGNUMBER_ROUNDING_MODE:{ROUNDING_MODE:r.ROUND_DOWN},ETH_POLLING_TIMEOUT:1e3}}).call(this,t("_process"))},{_process:2,"bignumber.js":8}],5:[function(t,e){(function(n){if("build"!==n.env.NODE_ENV)var r=t("bignumber.js");var i=t("./utils"),o=t("./const"),f=function(t,e,n){return new Array(e-t.length+1).join(n?n:"0")+t},u=function(t){var e=2*o.ETH_PADDING;return t instanceof r||"number"==typeof t?("number"==typeof t&&(t=new r(t)),r.config(o.ETH_BIGNUMBER_ROUNDING_MODE),t=t.round(),t.lessThan(0)&&(t=new r("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",16).plus(t).plus(1)),t=t.toString(16)):t=0===t.indexOf("0x")?t.substr(2):"string"==typeof t?u(new r(t)):(+t).toString(16),f(t,e)},s=function(t){return i.fromAscii(t,o.ETH_PADDING).substr(2)},c=function(t){return"000000000000000000000000000000000000000000000000000000000000000"+(t?"1":"0")},a=function(t){return u(new r(t).times(new r(2).pow(128)))},l=function(t){return"1"===new r(t.substr(0,1),16).toString(2).substr(0,1)},p=function(t){return t=t||"0",l(t)?new r(t,16).minus(new r("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",16)).minus(1):new r(t,16)},h=function(t){return t=t||"0",new r(t,16)},g=function(t){return p(t).dividedBy(new r(2).pow(128))},m=function(t){return h(t).dividedBy(new r(2).pow(128))},d=function(t){return"0x"+t},v=function(t){return"0000000000000000000000000000000000000000000000000000000000000001"===t?!0:!1},w=function(t){return i.toAscii(t)},y=function(t){return"0x"+t.slice(t.length-40,t.length)};e.exports={formatInputInt:u,formatInputString:s,formatInputBool:c,formatInputReal:a,formatOutputInt:p,formatOutputUInt:h,formatOutputReal:g,formatOutputUReal:m,formatOutputHash:d,formatOutputBool:v,formatOutputString:w,formatOutputAddress:y}}).call(this,t("_process"))},{"./const":4,"./utils":7,_process:2,"bignumber.js":8}],6:[function(t,e){var n=t("./formatters"),r=function(t){return function(e){return 0===e.indexOf(t)}},i=function(t){return function(e){return t===e}},o=function(){return[{type:r("uint"),format:n.formatInputInt},{type:r("int"),format:n.formatInputInt},{type:r("hash"),format:n.formatInputInt},{type:r("string"),format:n.formatInputString},{type:r("real"),format:n.formatInputReal},{type:r("ureal"),format:n.formatInputReal},{type:i("address"),format:n.formatInputInt},{type:i("bool"),format:n.formatInputBool}]},f=function(){return[{type:r("uint"),format:n.formatOutputUInt},{type:r("int"),format:n.formatOutputInt},{type:r("hash"),format:n.formatOutputHash},{type:r("string"),format:n.formatOutputString},{type:r("real"),format:n.formatOutputReal},{type:r("ureal"),format:n.formatOutputUReal},{type:i("address"),format:n.formatOutputAddress},{type:i("bool"),format:n.formatOutputBool}]};e.exports={prefixedType:r,namedType:i,inputTypes:o,outputTypes:f}},{"./formatters":5}],7:[function(t,e){var n=t("./const"),r=function(t,e){for(var n=!1,r=0;r<t.length&&!n;r++)n=e(t[r]);return n?r-1:-1},i=function(t){var e="",n=0,r=t.length;for("0x"===t.substring(0,2)&&(n=2);r>n;n+=2){var i=parseInt(t.substr(n,2),16);if(0===i)break;e+=String.fromCharCode(i)}return e},o=function(t){for(var e="",n=0;n<t.length;n++){var r=t.charCodeAt(n).toString(16);e+=r.length<2?"0"+r:r}return e},f=function(t,e){e=void 0===e?0:e;for(var n=o(t);n.length<2*e;)n+="00";return"0x"+n},u=function(t){var e=t.indexOf("(");return-1!==e?t.substr(0,e):t},s=function(t){var e=t.indexOf("(");return-1!==e?t.substr(e+1,t.length-1-(e+1)).replace(" ",""):""},c=function(t){return t.filter(function(t){return"function"===t.type})},a=function(t){return t.filter(function(t){return"event"===t.type})},l=function(t){for(var e="string"==typeof t?0===t.indexOf("0x")?parseInt(t.substr(2),16):parseInt(t):t,r=0,i=n.ETH_UNITS;e>3e3&&r<i.length-1;)e/=1e3,r++;for(var o=e.toString().length<e.toFixed(2).length?e.toString():e.toFixed(2),f=function(t,e,n){return e+","+n};;){var u=o;if(o=o.replace(/(\d)(\d\d\d[\.\,])/,f),u===o)break}return o+" "+i[r]};e.exports={findIndex:r,toAscii:i,fromAscii:f,extractDisplayName:u,extractTypeName:s,filterFunctions:c,filterEvents:a,toEth:l}},{"./const":4}],8:[function(t,e){!function(n){"use strict";function r(t){function e(t,r){var i,o,f,u,s,c,a=this;if(!(a instanceof e))return $&&U(26,"constructor call without new",t),new e(t,r);if(null!=r&&z(r,2,64,L,"base")){if(r=0|r,c=t+"",10==r)return a=new e(t instanceof e?t:c),F(a,B+a.e+1,H);if((u="number"==typeof t)&&0*t!=0||!new RegExp("^-?"+(i="["+N.slice(0,r)+"]+")+"(?:\\."+i+")?$",37>r?"i":"").test(c))return m(a,c,u,r);u?(a.s=0>1/t?(c=c.slice(1),-1):1,$&&c.replace(/^0\.0*|\./,"").length>15&&U(L,O,t),u=!1):a.s=45===c.charCodeAt(0)?(c=c.slice(1),-1):1,c=n(c,10,r,a.s)}else{if(t instanceof e)return a.s=t.s,a.e=t.e,a.c=(t=t.c)?t.slice():t,void(L=0);if((u="number"==typeof t)&&0*t==0){if(a.s=0>1/t?(t=-t,-1):1,t===~~t){for(o=0,f=t;f>=10;f/=10,o++);return a.e=o,a.c=[t],void(L=0)}c=t+""}else{if(!d.test(c=t+""))return m(a,c,u);a.s=45===c.charCodeAt(0)?(c=c.slice(1),-1):1}}for((o=c.indexOf("."))>-1&&(c=c.replace(".","")),(f=c.search(/e/i))>0?(0>o&&(o=f),o+=+c.slice(f+1),c=c.substring(0,f)):0>o&&(o=c.length),f=0;48===c.charCodeAt(f);f++);for(s=c.length;48===c.charCodeAt(--s););if(c=c.slice(f,s+1))if(s=c.length,u&&$&&s>15&&U(L,O,a.s*t),o=o-f-1,o>q)a.c=a.e=null;else if(k>o)a.c=[a.e=0];else{if(a.e=o,a.c=[],f=(o+1)%I,0>o&&(f+=I),s>f){for(f&&a.c.push(+c.slice(0,f)),s-=I;s>f;)a.c.push(+c.slice(f,f+=I));c=c.slice(f),f=I-c.length}else f-=s;for(;f--;c+="0");a.c.push(+c)}else a.c=[a.e=0];L=0}function n(t,n,r,i){var f,u,s,a,p,h,g,m=t.indexOf("."),d=B,v=H;for(37>r&&(t=t.toLowerCase()),m>=0&&(s=Y,Y=0,t=t.replace(".",""),g=new e(r),p=g.pow(t.length-m),Y=s,g.c=c(l(o(p.c),p.e),10,n),g.e=g.c.length),h=c(t,r,n),u=s=h.length;0==h[--s];h.pop());if(!h[0])return"0";if(0>m?--u:(p.c=h,p.e=u,p.s=i,p=G(p,g,d,v,n),h=p.c,a=p.r,u=p.e),f=u+d+1,m=h[f],s=n/2,a=a||0>f||null!=h[f+1],a=4>v?(null!=m||a)&&(0==v||v==(p.s<0?3:2)):m>s||m==s&&(4==v||a||6==v&&1&h[f-1]||v==(p.s<0?8:7)),1>f||!h[0])t=a?l("1",-d):"0";else{if(h.length=f,a)for(--n;++h[--f]>n;)h[f]=0,f||(++u,h.unshift(1));for(s=h.length;!h[--s];);for(m=0,t="";s>=m;t+=N.charAt(h[m++]));t=l(t,u)}return t}function h(t,n,r,i){var f,u,s,c,p;if(r=null!=r&&z(r,0,8,i,b)?0|r:H,!t.c)return t.toString();if(f=t.c[0],s=t.e,null==n)p=o(t.c),p=19==i||24==i&&C>=s?a(p,s):l(p,s);else if(t=F(new e(t),n,r),u=t.e,p=o(t.c),c=p.length,19==i||24==i&&(u>=n||C>=u)){for(;n>c;p+="0",c++);p=a(p,u)}else if(n-=s,p=l(p,u),u+1>c){if(--n>0)for(p+=".";n--;p+="0");}else if(n+=u-c,n>0)for(u+1==c&&(p+=".");n--;p+="0");return t.s<0&&f?"-"+p:p}function S(t,n){var r,i,o=0;for(s(t[0])&&(t=t[0]),r=new e(t[0]);++o<t.length;){if(i=new e(t[o]),!i.s){r=i;break}n.call(r,i)&&(r=i)}return r}function A(t,e,n,r,i){return(e>t||t>n||t!=p(t))&&U(r,(i||"decimal places")+(e>t||t>n?" out of range":" not an integer"),t),!0}function R(t,e,n){for(var r=1,i=e.length;!e[--i];e.pop());for(i=e[0];i>=10;i/=10,r++);return(n=r+n*I-1)>q?t.c=t.e=null:k>n?t.c=[t.e=0]:(t.e=n,t.c=e),t}function U(t,e,n){var r=new Error(["new BigNumber","cmp","config","div","divToInt","eq","gt","gte","lt","lte","minus","mod","plus","precision","random","round","shift","times","toDigits","toExponential","toFixed","toFormat","toFraction","pow","toPrecision","toString","BigNumber"][t]+"() "+e+": "+n);throw r.name="BigNumber Error",L=0,r}function F(t,e,n,r){var i,o,f,u,s,c,a,l=t.c,p=_;if(l){t:{for(i=1,u=l[0];u>=10;u/=10,i++);if(o=e-i,0>o)o+=I,f=e,s=l[c=0],a=s/p[i-f-1]%10|0;else if(c=v((o+1)/I),c>=l.length){if(!r)break t;for(;l.length<=c;l.push(0));s=a=0,i=1,o%=I,f=o-I+1}else{for(s=u=l[c],i=1;u>=10;u/=10,i++);o%=I,f=o-I+i,a=0>f?0:s/p[i-f-1]%10|0}if(r=r||0>e||null!=l[c+1]||(0>f?s:s%p[i-f-1]),r=4>n?(a||r)&&(0==n||n==(t.s<0?3:2)):a>5||5==a&&(4==n||r||6==n&&(o>0?f>0?s/p[i-f]:0:l[c-1])%10&1||n==(t.s<0?8:7)),1>e||!l[0])return l.length=0,r?(e-=t.e+1,l[0]=p[e%I],t.e=-e||0):l[0]=t.e=0,t;if(0==o?(l.length=c,u=1,c--):(l.length=c+1,u=p[I-o],l[c]=f>0?w(s/p[i-f]%p[f])*u:0),r)for(;;){if(0==c){for(o=1,f=l[0];f>=10;f/=10,o++);for(f=l[0]+=u,u=1;f>=10;f/=10,u++);o!=u&&(t.e++,l[0]==E&&(l[0]=1));break}if(l[c]+=u,l[c]!=E)break;l[c--]=0,u=1}for(o=l.length;0===l[--o];l.pop());}t.e>q?t.c=t.e=null:t.e<k&&(t.c=[t.e=0])}return t}var G,L=0,M=e.prototype,P=new e(1),B=20,H=4,C=-7,j=21,k=-1e7,q=1e7,$=!0,z=A,V=!1,W=1,Y=100,Z={decimalSeparator:".",groupSeparator:",",groupSize:3,secondaryGroupSize:0,fractionGroupSeparator:" ",fractionGroupSize:0};return e.another=r,e.ROUND_UP=0,e.ROUND_DOWN=1,e.ROUND_CEIL=2,e.ROUND_FLOOR=3,e.ROUND_HALF_UP=4,e.ROUND_HALF_DOWN=5,e.ROUND_HALF_EVEN=6,e.ROUND_HALF_CEIL=7,e.ROUND_HALF_FLOOR=8,e.EUCLID=9,e.config=function(){var t,e,n=0,r={},i=arguments,o=i[0],f=o&&"object"==typeof o?function(){return o.hasOwnProperty(e)?null!=(t=o[e]):void 0}:function(){return i.length>n?null!=(t=i[n++]):void 0};return f(e="DECIMAL_PLACES")&&z(t,0,D,2,e)&&(B=0|t),r[e]=B,f(e="ROUNDING_MODE")&&z(t,0,8,2,e)&&(H=0|t),r[e]=H,f(e="EXPONENTIAL_AT")&&(s(t)?z(t[0],-D,0,2,e)&&z(t[1],0,D,2,e)&&(C=0|t[0],j=0|t[1]):z(t,-D,D,2,e)&&(C=-(j=0|(0>t?-t:t)))),r[e]=[C,j],f(e="RANGE")&&(s(t)?z(t[0],-D,-1,2,e)&&z(t[1],1,D,2,e)&&(k=0|t[0],q=0|t[1]):z(t,-D,D,2,e)&&(0|t?k=-(q=0|(0>t?-t:t)):$&&U(2,e+" cannot be zero",t))),r[e]=[k,q],f(e="ERRORS")&&(t===!!t||1===t||0===t?(L=0,z=($=!!t)?A:u):$&&U(2,e+y,t)),r[e]=$,f(e="CRYPTO")&&(t===!!t||1===t||0===t?(V=!(!t||!g||"object"!=typeof g),t&&!V&&$&&U(2,"crypto unavailable",g)):$&&U(2,e+y,t)),r[e]=V,f(e="MODULO_MODE")&&z(t,0,9,2,e)&&(W=0|t),r[e]=W,f(e="POW_PRECISION")&&z(t,0,D,2,e)&&(Y=0|t),r[e]=Y,f(e="FORMAT")&&("object"==typeof t?Z=t:$&&U(2,e+" not an object",t)),r[e]=Z,r},e.max=function(){return S(arguments,M.lt)},e.min=function(){return S(arguments,M.gt)},e.random=function(){var t=9007199254740992,n=Math.random()*t&2097151?function(){return w(Math.random()*t)}:function(){return 8388608*(1073741824*Math.random()|0)+(8388608*Math.random()|0)};return function(t){var r,i,o,f,u,s=0,c=[],a=new e(P);if(t=null!=t&&z(t,0,D,14)?0|t:B,f=v(t/I),V)if(g&&g.getRandomValues){for(r=g.getRandomValues(new Uint32Array(f*=2));f>s;)u=131072*r[s]+(r[s+1]>>>11),u>=9e15?(i=g.getRandomValues(new Uint32Array(2)),r[s]=i[0],r[s+1]=i[1]):(c.push(u%1e14),s+=2);s=f/2}else if(g&&g.randomBytes){for(r=g.randomBytes(f*=7);f>s;)u=281474976710656*(31&r[s])+1099511627776*r[s+1]+4294967296*r[s+2]+16777216*r[s+3]+(r[s+4]<<16)+(r[s+5]<<8)+r[s+6],u>=9e15?g.randomBytes(7).copy(r,s):(c.push(u%1e14),s+=7);s=f/7}else $&&U(14,"crypto unavailable",g);if(!s)for(;f>s;)u=n(),9e15>u&&(c[s++]=u%1e14);for(f=c[--s],t%=I,f&&t&&(u=_[I-t],c[s]=w(f/u)*u);0===c[s];c.pop(),s--);if(0>s)c=[o=0];else{for(o=-1;0===c[0];c.shift(),o-=I);for(s=1,u=c[0];u>=10;u/=10,s++);I>s&&(o-=I-s)}return a.e=o,a.c=c,a}}(),G=function(){function t(t,e,n){var r,i,o,f,u=0,s=t.length,c=e%T,a=e/T|0;for(t=t.slice();s--;)o=t[s]%T,f=t[s]/T|0,r=a*o+f*c,i=c*o+r%T*T+u,u=(i/n|0)+(r/T|0)+a*f,t[s]=i%n;return u&&t.unshift(u),t}function n(t,e,n,r){var i,o;if(n!=r)o=n>r?1:-1;else for(i=o=0;n>i;i++)if(t[i]!=e[i]){o=t[i]>e[i]?1:-1;break}return o}function r(t,e,n,r){for(var i=0;n--;)t[n]-=i,i=t[n]<e[n]?1:0,t[n]=i*r+t[n]-e[n];for(;!t[0]&&t.length>1;t.shift());}return function(o,f,u,s,c){var a,l,p,h,g,m,d,v,y,b,O,N,x,_,T,D,S,A=o.s==f.s?1:-1,R=o.c,U=f.c;if(!(R&&R[0]&&U&&U[0]))return new e(o.s&&f.s&&(R?!U||R[0]!=U[0]:U)?R&&0==R[0]||!U?0*A:A/0:0/0);for(v=new e(A),y=v.c=[],l=o.e-f.e,A=u+l+1,c||(c=E,l=i(o.e/I)-i(f.e/I),A=A/I|0),p=0;U[p]==(R[p]||0);p++);if(U[p]>(R[p]||0)&&l--,0>A)y.push(1),h=!0;else{for(_=R.length,D=U.length,p=0,A+=2,g=w(c/(U[0]+1)),g>1&&(U=t(U,g,c),R=t(R,g,c),D=U.length,_=R.length),x=D,b=R.slice(0,D),O=b.length;D>O;b[O++]=0);S=U.slice(),S.unshift(0),T=U[0],U[1]>=c/2&&T++;do g=0,a=n(U,b,D,O),0>a?(N=b[0],D!=O&&(N=N*c+(b[1]||0)),g=w(N/T),g>1?(g>=c&&(g=c-1),m=t(U,g,c),d=m.length,O=b.length,a=n(m,b,d,O),1==a&&(g--,r(m,d>D?S:U,d,c))):(0==g&&(a=g=1),m=U.slice()),d=m.length,O>d&&m.unshift(0),r(b,m,O,c),-1==a&&(O=b.length,a=n(U,b,D,O),1>a&&(g++,r(b,O>D?S:U,O,c))),O=b.length):0===a&&(g++,b=[0]),y[p++]=g,a&&b[0]?b[O++]=R[x]||0:(b=[R[x]],O=1);while((x++<_||null!=b[0])&&A--);h=null!=b[0],y[0]||y.shift()}if(c==E){for(p=1,A=y[0];A>=10;A/=10,p++);F(v,u+(v.e=p+l*I-1)+1,s,h)}else v.e=l,v.r=+h;return v}}(),m==function(){var t=/^(-?)0([xbo])(\w[\w.]*$)/i,n=/^([^.]+)\.$/,r=/^\.([^.]+)$/,i=/^-?(Infinity|NaN)$/,o=/^\s*\+([\w.])|^\s+|\s+$/g;return function(f,u,s,c){var a,l=s?u:u.replace(o,"$1");if(i.test(l))f.s=isNaN(l)?null:0>l?-1:1;else{if(!s&&(l=l.replace(t,function(t,e,n){return a="x"==(n=n.toLowerCase())?16:"b"==n?2:8,c&&c!=a?t:e}),c&&(a=c,l=l.replace(n,"$1").replace(r,"0.$1")),u!=l))return new e(l,a);$&&U(L,"not a"+(c?" base "+c:"")+" number",u),f.s=null}f.c=f.e=null,L=0}}(),M.absoluteValue=M.abs=function(){var t=new e(this);return t.s<0&&(t.s=1),t},M.ceil=function(){return F(new e(this),this.e+1,2)},M.comparedTo=M.cmp=function(t,n){return L=1,f(this,new e(t,n))},M.decimalPlaces=M.dp=function(){var t,e,n=this.c;if(!n)return null;if(t=((e=n.length-1)-i(this.e/I))*I,e=n[e])for(;e%10==0;e/=10,t--);return 0>t&&(t=0),t},M.dividedBy=M.div=function(t,n){return L=3,G(this,new e(t,n),B,H)},M.dividedToIntegerBy=M.divToInt=function(t,n){return L=4,G(this,new e(t,n),0,1)},M.equals=M.eq=function(t,n){return L=5,0===f(this,new e(t,n))},M.floor=function(){return F(new e(this),this.e+1,3)},M.greaterThan=M.gt=function(t,n){return L=6,f(this,new e(t,n))>0},M.greaterThanOrEqualTo=M.gte=function(t,n){return L=7,1===(n=f(this,new e(t,n)))||0===n},M.isFinite=function(){return!!this.c},M.isInteger=M.isInt=function(){return!!this.c&&i(this.e/I)>this.c.length-2},M.isNaN=function(){return!this.s},M.isNegative=M.isNeg=function(){return this.s<0},M.isZero=function(){return!!this.c&&0==this.c[0]},M.lessThan=M.lt=function(t,n){return L=8,f(this,new e(t,n))<0},M.lessThanOrEqualTo=M.lte=function(t,n){return L=9,-1===(n=f(this,new e(t,n)))||0===n},M.minus=M.sub=function(t,n){var r,o,f,u,s=this,c=s.s;if(L=10,t=new e(t,n),n=t.s,!c||!n)return new e(0/0);if(c!=n)return t.s=-n,s.plus(t);var a=s.e/I,l=t.e/I,p=s.c,h=t.c;if(!a||!l){if(!p||!h)return p?(t.s=-n,t):new e(h?s:0/0);if(!p[0]||!h[0])return h[0]?(t.s=-n,t):new e(p[0]?s:3==H?-0:0)}if(a=i(a),l=i(l),p=p.slice(),c=a-l){for((u=0>c)?(c=-c,f=p):(l=a,f=h),f.reverse(),n=c;n--;f.push(0));f.reverse()}else for(o=(u=(c=p.length)<(n=h.length))?c:n,c=n=0;o>n;n++)if(p[n]!=h[n]){u=p[n]<h[n];break}if(u&&(f=p,p=h,h=f,t.s=-t.s),n=(o=h.length)-(r=p.length),n>0)for(;n--;p[r++]=0);for(n=E-1;o>c;){if(p[--o]<h[o]){for(r=o;r&&!p[--r];p[r]=n);--p[r],p[o]+=E}p[o]-=h[o]}for(;0==p[0];p.shift(),--l);return p[0]?R(t,p,l):(t.s=3==H?-1:1,t.c=[t.e=0],t)},M.modulo=M.mod=function(t,n){var r,i,o=this;return L=11,t=new e(t,n),!o.c||!t.s||t.c&&!t.c[0]?new e(0/0):!t.c||o.c&&!o.c[0]?new e(o):(9==W?(i=t.s,t.s=1,r=G(o,t,0,3),t.s=i,r.s*=i):r=G(o,t,0,W),o.minus(r.times(t)))},M.negated=M.neg=function(){var t=new e(this);return t.s=-t.s||null,t},M.plus=M.add=function(t,n){var r,o=this,f=o.s;if(L=12,t=new e(t,n),n=t.s,!f||!n)return new e(0/0);if(f!=n)return t.s=-n,o.minus(t);var u=o.e/I,s=t.e/I,c=o.c,a=t.c;if(!u||!s){if(!c||!a)return new e(f/0);if(!c[0]||!a[0])return a[0]?t:new e(c[0]?o:0*f)}if(u=i(u),s=i(s),c=c.slice(),f=u-s){for(f>0?(s=u,r=a):(f=-f,r=c),r.reverse();f--;r.push(0));r.reverse()}for(f=c.length,n=a.length,0>f-n&&(r=a,a=c,c=r,n=f),f=0;n;)f=(c[--n]=c[n]+a[n]+f)/E|0,c[n]%=E;return f&&(c.unshift(f),++s),R(t,c,s)},M.precision=M.sd=function(t){var e,n,r=this,i=r.c;if(null!=t&&t!==!!t&&1!==t&&0!==t&&($&&U(13,"argument"+y,t),t!=!!t&&(t=null)),!i)return null;if(n=i.length-1,e=n*I+1,n=i[n]){for(;n%10==0;n/=10,e--);for(n=i[0];n>=10;n/=10,e++);}return t&&r.e+1>e&&(e=r.e+1),e},M.round=function(t,n){var r=new e(this);return(null==t||z(t,0,D,15))&&F(r,~~t+this.e+1,null!=n&&z(n,0,8,15,b)?0|n:H),r},M.shift=function(t){var n=this;return z(t,-x,x,16,"argument")?n.times("1e"+p(t)):new e(n.c&&n.c[0]&&(-x>t||t>x)?n.s*(0>t?0:1/0):n)},M.squareRoot=M.sqrt=function(){var t,n,r,f,u,s=this,c=s.c,a=s.s,l=s.e,p=B+4,h=new e("0.5");if(1!==a||!c||!c[0])return new e(!a||0>a&&(!c||c[0])?0/0:c?s:1/0);if(a=Math.sqrt(+s),0==a||a==1/0?(n=o(c),(n.length+l)%2==0&&(n+="0"),a=Math.sqrt(n),l=i((l+1)/2)-(0>l||l%2),a==1/0?n="1e"+l:(n=a.toExponential(),n=n.slice(0,n.indexOf("e")+1)+l),r=new e(n)):r=new e(a+""),r.c[0])for(l=r.e,a=l+p,3>a&&(a=0);;)if(u=r,r=h.times(u.plus(G(s,u,p,1))),o(u.c).slice(0,a)===(n=o(r.c)).slice(0,a)){if(r.e<l&&--a,n=n.slice(a-3,a+1),"9999"!=n&&(f||"4999"!=n)){(!+n||!+n.slice(1)&&"5"==n.charAt(0))&&(F(r,r.e+B+2,1),t=!r.times(r).eq(s));break}if(!f&&(F(u,u.e+B+2,0),u.times(u).eq(s))){r=u;break}p+=4,a+=4,f=1}return F(r,r.e+B+1,H,t)},M.times=M.mul=function(t,n){var r,o,f,u,s,c,a,l,p,h,g,m,d,v,w,y=this,b=y.c,O=(L=17,t=new e(t,n)).c;if(!(b&&O&&b[0]&&O[0]))return!y.s||!t.s||b&&!b[0]&&!O||O&&!O[0]&&!b?t.c=t.e=t.s=null:(t.s*=y.s,b&&O?(t.c=[0],t.e=0):t.c=t.e=null),t;for(o=i(y.e/I)+i(t.e/I),t.s*=y.s,a=b.length,h=O.length,h>a&&(d=b,b=O,O=d,f=a,a=h,h=f),f=a+h,d=[];f--;d.push(0));for(v=E,w=T,f=h;--f>=0;){for(r=0,g=O[f]%w,m=O[f]/w|0,s=a,u=f+s;u>f;)l=b[--s]%w,p=b[s]/w|0,c=m*l+p*g,l=g*l+c%w*w+d[u]+r,r=(l/v|0)+(c/w|0)+m*p,d[u--]=l%v;d[u]=r}return r?++o:d.shift(),R(t,d,o)},M.toDigits=function(t,n){var r=new e(this);return t=null!=t&&z(t,1,D,18,"precision")?0|t:null,n=null!=n&&z(n,0,8,18,b)?0|n:H,t?F(r,t,n):r},M.toExponential=function(t,e){return h(this,null!=t&&z(t,0,D,19)?~~t+1:null,e,19)},M.toFixed=function(t,e){return h(this,null!=t&&z(t,0,D,20)?~~t+this.e+1:null,e,20)},M.toFormat=function(t,e){var n=h(this,null!=t&&z(t,0,D,21)?~~t+this.e+1:null,e,21);if(this.c){var r,i=n.split("."),o=+Z.groupSize,f=+Z.secondaryGroupSize,u=Z.groupSeparator,s=i[0],c=i[1],a=this.s<0,l=a?s.slice(1):s,p=l.length;if(f&&(r=o,o=f,f=r,p-=r),o>0&&p>0){for(r=p%o||o,s=l.substr(0,r);p>r;r+=o)s+=u+l.substr(r,o);f>0&&(s+=u+l.slice(r)),a&&(s="-"+s)}n=c?s+Z.decimalSeparator+((f=+Z.fractionGroupSize)?c.replace(new RegExp("\\d{"+f+"}\\B","g"),"$&"+Z.fractionGroupSeparator):c):s}return n},M.toFraction=function(t){var n,r,i,f,u,s,c,a,l,p=$,h=this,g=h.c,m=new e(P),d=r=new e(P),v=c=new e(P);if(null!=t&&($=!1,s=new e(t),$=p,(!(p=s.isInt())||s.lt(P))&&($&&U(22,"max denominator "+(p?"out of range":"not an integer"),t),t=!p&&s.c&&F(s,s.e+1,1).gte(P)?s:null)),!g)return h.toString();for(l=o(g),f=m.e=l.length-h.e-1,m.c[0]=_[(u=f%I)<0?I+u:u],t=!t||s.cmp(m)>0?f>0?m:d:s,u=q,q=1/0,s=new e(l),c.c[0]=0;a=G(s,m,0,1),i=r.plus(a.times(v)),1!=i.cmp(t);)r=v,v=i,d=c.plus(a.times(i=d)),c=i,m=s.minus(a.times(i=m)),s=i;return i=G(t.minus(r),v,0,1),c=c.plus(i.times(d)),r=r.plus(i.times(v)),c.s=d.s=h.s,f*=2,n=G(d,v,f,H).minus(h).abs().cmp(G(c,r,f,H).minus(h).abs())<1?[d.toString(),v.toString()]:[c.toString(),r.toString()],q=u,n},M.toNumber=function(){var t=this;return+t||(t.s?0*t.s:0/0)},M.toPower=M.pow=function(t){var n,r,i=w(0>t?-t:+t),o=this;if(!z(t,-x,x,23,"exponent")&&(!isFinite(t)||i>x&&(t/=0)||parseFloat(t)!=t&&!(t=0/0)))return new e(Math.pow(+o,t));for(n=Y?v(Y/I+2):0,r=new e(P);;){if(i%2){if(r=r.times(o),!r.c)break;n&&r.c.length>n&&(r.c.length=n)}if(i=w(i/2),!i)break;o=o.times(o),n&&o.c&&o.c.length>n&&(o.c.length=n)}return 0>t&&(r=P.div(r)),n?F(r,Y,H):r},M.toPrecision=function(t,e){return h(this,null!=t&&z(t,1,D,24,"precision")?0|t:null,e,24)},M.toString=function(t){var e,r=this,i=r.s,f=r.e;return null===f?i?(e="Infinity",0>i&&(e="-"+e)):e="NaN":(e=o(r.c),e=null!=t&&z(t,2,64,25,"base")?n(l(e,f),0|t,10,i):C>=f||f>=j?a(e,f):l(e,f),0>i&&r.c[0]&&(e="-"+e)),e},M.truncated=M.trunc=function(){return F(new e(this),this.e+1,1)},M.valueOf=M.toJSON=function(){return this.toString()},null!=t&&e.config(t),e}function i(t){var e=0|t;return t>0||t===e?e:e-1}function o(t){for(var e,n,r=1,i=t.length,o=t[0]+"";i>r;){for(e=t[r++]+"",n=I-e.length;n--;e="0"+e);o+=e}for(i=o.length;48===o.charCodeAt(--i););return o.slice(0,i+1||1)}function f(t,e){var n,r,i=t.c,o=e.c,f=t.s,u=e.s,s=t.e,c=e.e;if(!f||!u)return null;if(n=i&&!i[0],r=o&&!o[0],n||r)return n?r?0:-u:f;if(f!=u)return f;if(n=0>f,r=s==c,!i||!o)return r?0:!i^n?1:-1;if(!r)return s>c^n?1:-1;for(u=(s=i.length)<(c=o.length)?s:c,f=0;u>f;f++)if(i[f]!=o[f])return i[f]>o[f]^n?1:-1;return s==c?0:s>c^n?1:-1}function u(t,e,n){return(t=p(t))>=e&&n>=t}function s(t){return"[object Array]"==Object.prototype.toString.call(t)}function c(t,e,n){for(var r,i,o=[0],f=0,u=t.length;u>f;){for(i=o.length;i--;o[i]*=e);for(o[r=0]+=N.indexOf(t.charAt(f++));r<o.length;r++)o[r]>n-1&&(null==o[r+1]&&(o[r+1]=0),o[r+1]+=o[r]/n|0,o[r]%=n)}return o.reverse()}function a(t,e){return(t.length>1?t.charAt(0)+"."+t.slice(1):t)+(0>e?"e":"e+")+e}function l(t,e){var n,r;if(0>e){for(r="0.";++e;r+="0");t=r+t}else if(n=t.length,++e>n){for(r="0",e-=n;--e;r+="0");t+=r}else n>e&&(t=t.slice(0,e)+"."+t.slice(e));return t}function p(t){return t=parseFloat(t),0>t?v(t):w(t)}var h,g,m,d=/^-?(\d+(\.\d*)?|\.\d+)(e[+-]?\d+)?$/i,v=Math.ceil,w=Math.floor,y=" not a boolean or binary digit",b="rounding mode",O="number type has more than 15 significant digits",N="0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ$_",E=1e14,I=14,x=9007199254740991,_=[1,10,100,1e3,1e4,1e5,1e6,1e7,1e8,1e9,1e10,1e11,1e12,1e13],T=1e7,D=1e9;if(h=r(),"function"==typeof define&&define.amd)define(function(){return h});else if("undefined"!=typeof e&&e.exports){if(e.exports=h,!g)try{g=t("crypto")}catch(S){}}else n.BigNumber=h}(this)},{crypto:1}],natspec:[function(t,e){var n=t("./node_modules/ethereum.js/lib/abi.js"),r=function(){var t=function(t,e){Object.keys(t).forEach(function(n){e[n]=t[n]})},e=function(t){return Object.keys(t).reduce(function(t,e){return t+"var "+e+" = context['"+e+"'];\n"},"")},r=function(t,e){return t.filter(function(t){return t.name===e})[0]},i=function(t,e){var r=n.formatOutput(t.inputs,"0x"+e.params[0].data.slice(10));return t.inputs.reduce(function(t,e,n){return t[e.name]=r[n],t},{})},o=function(t,e){var n,r="",i=/\` + "`" + `(?:\\.|[^` + "`" + `\\])*\` + "`" + `/gim,o=0;try{for(;null!==(n=i.exec(t));){var f=i.lastIndex-n[0].length,u=n[0].slice(1,n[0].length-1);r+=t.slice(o,f);var s=e(u);r+=s,o=i.lastIndex}r+=t.slice(o)}catch(c){throw new Error("Natspec evaluation failed, wrong input params")}return r},f=function(n,f){var u={};if(f)try{var s=r(f.abi,f.method),c=i(s,f.transaction);t(c,u)}catch(a){throw new Error("Natspec evaluation failed, method does not exist")}var l=e(u),p=o(n,function(t){var e=new Function("context",l+"return "+t+";");return e(u).toString()});return p},u=function(t,e){try{return f(t,e)}catch(n){return n.message}};return{evaluateExpression:f,evaluateExpressionSafe:u}}();e.exports=r},{"./node_modules/ethereum.js/lib/abi.js":3}]},{},[]); +` +require=(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){ + +},{}],2:[function(require,module,exports){ +/* + This file is part of ethereum.js. + + ethereum.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. + + ethereum.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 ethereum.js. If not, see <http://www.gnu.org/licenses/>. +*/ +/** + * @file abi.js + * @author Marek Kotewicz <marek@ethdev.com> + * @author Gav Wood <g@ethdev.com> + * @date 2014 + */ + +var utils = require('../utils/utils'); +var c = require('../utils/config'); +var types = require('./types'); +var f = require('./formatters'); +var solUtils = require('./utils'); + +/** + * throw incorrect type error + * + * @method throwTypeError + * @param {String} type + * @throws incorrect type error + */ +var throwTypeError = function (type) { + throw new Error('parser does not support type: ' + type); +}; + +/** This method should be called if we want to check if givent type is an array type + * + * @method isArrayType + * @param {String} type name + * @returns {Boolean} true if it is, otherwise false + */ +var isArrayType = function (type) { + return type.slice(-2) === '[]'; +}; + +/** + * This method should be called to return dynamic type length in hex + * + * @method dynamicTypeBytes + * @param {String} type + * @param {String|Array} dynamic type + * @return {String} length of dynamic type in hex or empty string if type is not dynamic + */ +var dynamicTypeBytes = function (type, value) { + // TODO: decide what to do with array of strings + if (isArrayType(type) || type === 'bytes') + return f.formatInputInt(value.length); + return ""; +}; + +var inputTypes = types.inputTypes(); + +/** + * Formats input params to bytes + * + * @method formatInput + * @param {Array} abi inputs of method + * @param {Array} params that will be formatted to bytes + * @returns bytes representation of input params + */ +var formatInput = function (inputs, params) { + var bytes = ""; + var toAppendConstant = ""; + var toAppendArrayContent = ""; + + /// first we iterate in search for dynamic + inputs.forEach(function (input, index) { + bytes += dynamicTypeBytes(input.type, params[index]); + }); + + inputs.forEach(function (input, i) { + /*jshint maxcomplexity:5 */ + var typeMatch = false; + for (var j = 0; j < inputTypes.length && !typeMatch; j++) { + typeMatch = inputTypes[j].type(inputs[i].type, params[i]); + } + if (!typeMatch) { + throwTypeError(inputs[i].type); + } + + var formatter = inputTypes[j - 1].format; + + if (isArrayType(inputs[i].type)) + toAppendArrayContent += params[i].reduce(function (acc, curr) { + return acc + formatter(curr); + }, ""); + else if (inputs[i].type === 'bytes') + toAppendArrayContent += formatter(params[i]); + else + toAppendConstant += formatter(params[i]); + }); + + bytes += toAppendConstant + toAppendArrayContent; + + return bytes; +}; + +/** + * This method should be called to predict the length of dynamic type + * + * @method dynamicBytesLength + * @param {String} type + * @returns {Number} length of dynamic type, 0 or multiplication of ETH_PADDING (32) + */ +var dynamicBytesLength = function (type) { + if (isArrayType(type) || type === 'bytes') + return c.ETH_PADDING * 2; + return 0; +}; + +var outputTypes = types.outputTypes(); + +/** + * Formats output bytes back to param list + * + * @method formatOutput + * @param {Array} abi outputs of method + * @param {String} bytes represention of output + * @returns {Array} output params + */ +var formatOutput = function (outs, output) { + + output = output.slice(2); + var result = []; + var padding = c.ETH_PADDING * 2; + + var dynamicPartLength = outs.reduce(function (acc, curr) { + return acc + dynamicBytesLength(curr.type); + }, 0); + + var dynamicPart = output.slice(0, dynamicPartLength); + output = output.slice(dynamicPartLength); + + outs.forEach(function (out, i) { + /*jshint maxcomplexity:6 */ + var typeMatch = false; + for (var j = 0; j < outputTypes.length && !typeMatch; j++) { + typeMatch = outputTypes[j].type(outs[i].type); + } + + if (!typeMatch) { + throwTypeError(outs[i].type); + } + + var formatter = outputTypes[j - 1].format; + if (isArrayType(outs[i].type)) { + var size = f.formatOutputUInt(dynamicPart.slice(0, padding)); + dynamicPart = dynamicPart.slice(padding); + var array = []; + for (var k = 0; k < size; k++) { + array.push(formatter(output.slice(0, padding))); + output = output.slice(padding); + } + result.push(array); + } + else if (types.prefixedType('bytes')(outs[i].type)) { + dynamicPart = dynamicPart.slice(padding); + result.push(formatter(output.slice(0, padding))); + output = output.slice(padding); + } else { + result.push(formatter(output.slice(0, padding))); + output = output.slice(padding); + } + }); + + return result; +}; + +/** + * Should be called to create input parser for contract with given abi + * + * @method inputParser + * @param {Array} contract abi + * @returns {Object} input parser object for given json abi + * TODO: refactor creating the parser, do not double logic from contract + */ +var inputParser = function (json) { + var parser = {}; + json.forEach(function (method) { + var displayName = utils.extractDisplayName(method.name); + var typeName = utils.extractTypeName(method.name); + + var impl = function () { + var params = Array.prototype.slice.call(arguments); + return formatInput(method.inputs, params); + }; + + if (parser[displayName] === undefined) { + parser[displayName] = impl; + } + + parser[displayName][typeName] = impl; + }); + + return parser; +}; + +/** + * Should be called to create output parser for contract with given abi + * + * @method outputParser + * @param {Array} contract abi + * @returns {Object} output parser for given json abi + */ +var outputParser = function (json) { + var parser = {}; + json.forEach(function (method) { + + var displayName = utils.extractDisplayName(method.name); + var typeName = utils.extractTypeName(method.name); + + var impl = function (output) { + return formatOutput(method.outputs, output); + }; + + if (parser[displayName] === undefined) { + parser[displayName] = impl; + } + + parser[displayName][typeName] = impl; + }); + + return parser; +}; + +var formatConstructorParams = function (abi, params) { + var constructor = solUtils.getConstructor(abi, params.length); + if (!constructor) { + if (params.length > 0) { + console.warn("didn't found matching constructor, using default one"); + } + return ''; + } + return formatInput(constructor.inputs, params); +}; + +module.exports = { + inputParser: inputParser, + outputParser: outputParser, + formatInput: formatInput, + formatOutput: formatOutput, + formatConstructorParams: formatConstructorParams +}; + +},{"../utils/config":6,"../utils/utils":7,"./formatters":3,"./types":4,"./utils":5}],3:[function(require,module,exports){ +/* + This file is part of ethereum.js. + + ethereum.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. + + ethereum.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 ethereum.js. If not, see <http://www.gnu.org/licenses/>. +*/ +/** @file formatters.js + * @authors: + * Marek Kotewicz <marek@ethdev.com> + * @date 2015 + */ + +var BigNumber = require('bignumber.js'); +var utils = require('../utils/utils'); +var c = require('../utils/config'); + +/** + * Formats input value to byte representation of int + * If value is negative, return it's two's complement + * If the value is floating point, round it down + * + * @method formatInputInt + * @param {String|Number|BigNumber} value that needs to be formatted + * @returns {String} right-aligned byte representation of int + */ +var formatInputInt = function (value) { + var padding = c.ETH_PADDING * 2; + BigNumber.config(c.ETH_BIGNUMBER_ROUNDING_MODE); + return utils.padLeft(utils.toTwosComplement(value).round().toString(16), padding); +}; + +/** + * Formats input value to byte representation of string + * + * @method formatInputString + * @param {String} + * @returns {String} left-algined byte representation of string + */ +var formatInputString = function (value) { + return utils.fromAscii(value, c.ETH_PADDING).substr(2); +}; + +/** + * Formats input value to byte representation of bool + * + * @method formatInputBool + * @param {Boolean} + * @returns {String} right-aligned byte representation bool + */ +var formatInputBool = function (value) { + return '000000000000000000000000000000000000000000000000000000000000000' + (value ? '1' : '0'); +}; + +/** + * Formats input value to byte representation of real + * Values are multiplied by 2^m and encoded as integers + * + * @method formatInputReal + * @param {String|Number|BigNumber} + * @returns {String} byte representation of real + */ +var formatInputReal = function (value) { + return formatInputInt(new BigNumber(value).times(new BigNumber(2).pow(128))); +}; + +/** + * Check if input value is negative + * + * @method signedIsNegative + * @param {String} value is hex format + * @returns {Boolean} true if it is negative, otherwise false + */ +var signedIsNegative = function (value) { + return (new BigNumber(value.substr(0, 1), 16).toString(2).substr(0, 1)) === '1'; +}; + +/** + * Formats right-aligned output bytes to int + * + * @method formatOutputInt + * @param {String} bytes + * @returns {BigNumber} right-aligned output bytes formatted to big number + */ +var formatOutputInt = function (value) { + + value = value || "0"; + + // check if it's negative number + // it it is, return two's complement + if (signedIsNegative(value)) { + return new BigNumber(value, 16).minus(new BigNumber('ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', 16)).minus(1); + } + return new BigNumber(value, 16); +}; + +/** + * Formats right-aligned output bytes to uint + * + * @method formatOutputUInt + * @param {String} bytes + * @returns {BigNumeber} right-aligned output bytes formatted to uint + */ +var formatOutputUInt = function (value) { + value = value || "0"; + return new BigNumber(value, 16); +}; + +/** + * Formats right-aligned output bytes to real + * + * @method formatOutputReal + * @param {String} + * @returns {BigNumber} input bytes formatted to real + */ +var formatOutputReal = function (value) { + return formatOutputInt(value).dividedBy(new BigNumber(2).pow(128)); +}; + +/** + * Formats right-aligned output bytes to ureal + * + * @method formatOutputUReal + * @param {String} + * @returns {BigNumber} input bytes formatted to ureal + */ +var formatOutputUReal = function (value) { + return formatOutputUInt(value).dividedBy(new BigNumber(2).pow(128)); +}; + +/** + * Should be used to format output hash + * + * @method formatOutputHash + * @param {String} + * @returns {String} right-aligned output bytes formatted to hex + */ +var formatOutputHash = function (value) { + return "0x" + value; +}; + +/** + * Should be used to format output bool + * + * @method formatOutputBool + * @param {String} + * @returns {Boolean} right-aligned input bytes formatted to bool + */ +var formatOutputBool = function (value) { + return value === '0000000000000000000000000000000000000000000000000000000000000001' ? true : false; +}; + +/** + * Should be used to format output string + * + * @method formatOutputString + * @param {Sttring} left-aligned hex representation of string + * @returns {String} ascii string + */ +var formatOutputString = function (value) { + return utils.toAscii(value); +}; + +/** + * Should be used to format output address + * + * @method formatOutputAddress + * @param {String} right-aligned input bytes + * @returns {String} address + */ +var formatOutputAddress = function (value) { + return "0x" + value.slice(value.length - 40, value.length); +}; + +module.exports = { + formatInputInt: formatInputInt, + formatInputString: formatInputString, + formatInputBool: formatInputBool, + formatInputReal: formatInputReal, + formatOutputInt: formatOutputInt, + formatOutputUInt: formatOutputUInt, + formatOutputReal: formatOutputReal, + formatOutputUReal: formatOutputUReal, + formatOutputHash: formatOutputHash, + formatOutputBool: formatOutputBool, + formatOutputString: formatOutputString, + formatOutputAddress: formatOutputAddress +}; + + +},{"../utils/config":6,"../utils/utils":7,"bignumber.js":8}],4:[function(require,module,exports){ +/* + This file is part of ethereum.js. + + ethereum.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. + + ethereum.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 ethereum.js. If not, see <http://www.gnu.org/licenses/>. +*/ +/** @file types.js + * @authors: + * Marek Kotewicz <marek@ethdev.com> + * @date 2015 + */ + +var f = require('./formatters'); + +/// @param expected type prefix (string) +/// @returns function which checks if type has matching prefix. if yes, returns true, otherwise false +var prefixedType = function (prefix) { + return function (type) { + return type.indexOf(prefix) === 0; + }; +}; + +/// @param expected type name (string) +/// @returns function which checks if type is matching expected one. if yes, returns true, otherwise false +var namedType = function (name) { + return function (type) { + return name === type; + }; +}; + +/// Setups input formatters for solidity types +/// @returns an array of input formatters +var inputTypes = function () { + + return [ + { type: prefixedType('uint'), format: f.formatInputInt }, + { type: prefixedType('int'), format: f.formatInputInt }, + { type: prefixedType('bytes'), format: f.formatInputString }, + { type: prefixedType('real'), format: f.formatInputReal }, + { type: prefixedType('ureal'), format: f.formatInputReal }, + { type: namedType('address'), format: f.formatInputInt }, + { type: namedType('bool'), format: f.formatInputBool } + ]; +}; + +/// Setups output formaters for solidity types +/// @returns an array of output formatters +var outputTypes = function () { + + return [ + { type: prefixedType('uint'), format: f.formatOutputUInt }, + { type: prefixedType('int'), format: f.formatOutputInt }, + { type: prefixedType('bytes'), format: f.formatOutputString }, + { type: prefixedType('real'), format: f.formatOutputReal }, + { type: prefixedType('ureal'), format: f.formatOutputUReal }, + { type: namedType('address'), format: f.formatOutputAddress }, + { type: namedType('bool'), format: f.formatOutputBool } + ]; +}; + +module.exports = { + prefixedType: prefixedType, + namedType: namedType, + inputTypes: inputTypes, + outputTypes: outputTypes +}; + + +},{"./formatters":3}],5:[function(require,module,exports){ +/* + This file is part of ethereum.js. + + ethereum.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. + + ethereum.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 ethereum.js. If not, see <http://www.gnu.org/licenses/>. +*/ +/** + * @file utils.js + * @author Marek Kotewicz <marek@ethdev.com> + * @date 2015 + */ + +/** + * Returns the contstructor with matching number of arguments + * + * @method getConstructor + * @param {Array} abi + * @param {Number} numberOfArgs + * @returns {Object} constructor function abi + */ +var getConstructor = function (abi, numberOfArgs) { + return abi.filter(function (f) { + return f.type === 'constructor' && f.inputs.length === numberOfArgs; + })[0]; +}; + +/** + * Filters all functions from input abi + * + * @method filterFunctions + * @param {Array} abi + * @returns {Array} abi array with filtered objects of type 'function' + */ +var filterFunctions = function (json) { + return json.filter(function (current) { + return current.type === 'function'; + }); +}; + +/** + * Filters all events from input abi + * + * @method filterEvents + * @param {Array} abi + * @returns {Array} abi array with filtered objects of type 'event' + */ +var filterEvents = function (json) { + return json.filter(function (current) { + return current.type === 'event'; + }); +}; + +module.exports = { + getConstructor: getConstructor, + filterFunctions: filterFunctions, + filterEvents: filterEvents +}; + + +},{}],6:[function(require,module,exports){ +/* + This file is part of ethereum.js. + + ethereum.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. + + ethereum.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 ethereum.js. If not, see <http://www.gnu.org/licenses/>. +*/ +/** @file config.js + * @authors: + * Marek Kotewicz <marek@ethdev.com> + * @date 2015 + */ + +/** + * Utils + * + * @module utils + */ + +/** + * Utility functions + * + * @class [utils] config + * @constructor + */ + +/// required to define ETH_BIGNUMBER_ROUNDING_MODE +var BigNumber = require('bignumber.js'); + +var ETH_UNITS = [ + 'wei', + 'Kwei', + 'Mwei', + 'Gwei', + 'szabo', + 'finney', + 'ether', + 'grand', + 'Mether', + 'Gether', + 'Tether', + 'Pether', + 'Eether', + 'Zether', + 'Yether', + 'Nether', + 'Dether', + 'Vether', + 'Uether' +]; + +module.exports = { + ETH_PADDING: 32, + ETH_SIGNATURE_LENGTH: 4, + ETH_UNITS: ETH_UNITS, + ETH_BIGNUMBER_ROUNDING_MODE: { ROUNDING_MODE: BigNumber.ROUND_DOWN }, + ETH_POLLING_TIMEOUT: 1000, + ETH_DEFAULTBLOCK: 'latest' +}; + + +},{"bignumber.js":8}],7:[function(require,module,exports){ +/* + This file is part of ethereum.js. + + ethereum.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. + + ethereum.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 ethereum.js. If not, see <http://www.gnu.org/licenses/>. +*/ +/** @file utils.js + * @authors: + * Marek Kotewicz <marek@ethdev.com> + * @date 2015 + */ + +/** + * Utils + * + * @module utils + */ + +/** + * Utility functions + * + * @class [utils] utils + * @constructor + */ + +var BigNumber = require('bignumber.js'); + +var unitMap = { + 'wei': '1', + 'kwei': '1000', + 'ada': '1000', + 'mwei': '1000000', + 'babbage': '1000000', + 'gwei': '1000000000', + 'shannon': '1000000000', + 'szabo': '1000000000000', + 'finney': '1000000000000000', + 'ether': '1000000000000000000', + 'kether': '1000000000000000000000', + 'grand': '1000000000000000000000', + 'einstein': '1000000000000000000000', + 'mether': '1000000000000000000000000', + 'gether': '1000000000000000000000000000', + 'tether': '1000000000000000000000000000000' +}; + +/** + * Should be called to pad string to expected length + * + * @method padLeft + * @param {String} string to be padded + * @param {Number} characters that result string should have + * @param {String} sign, by default 0 + * @returns {String} right aligned string + */ +var padLeft = function (string, chars, sign) { + return new Array(chars - string.length + 1).join(sign ? sign : "0") + string; +}; + +/** Finds first index of array element matching pattern + * + * @method findIndex + * @param {Array} + * @param {Function} pattern + * @returns {Number} index of element + */ +var findIndex = function (array, callback) { + var end = false; + var i = 0; + for (; i < array.length && !end; i++) { + end = callback(array[i]); + } + return end ? i - 1 : -1; +}; + +/** + * Should be called to get sting from it's hex representation + * + * @method toAscii + * @param {String} string in hex + * @returns {String} ascii string representation of hex value + */ +var toAscii = function(hex) { +// Find termination + var str = ""; + var i = 0, l = hex.length; + if (hex.substring(0, 2) === '0x') { + i = 2; + } + for (; i < l; i+=2) { + var code = parseInt(hex.substr(i, 2), 16); + if (code === 0) { + break; + } + + str += String.fromCharCode(code); + } + + return str; +}; + +/** + * Shold be called to get hex representation (prefixed by 0x) of ascii string + * + * @method fromAscii + * @param {String} string + * @returns {String} hex representation of input string + */ +var toHexNative = function(str) { + var hex = ""; + for(var i = 0; i < str.length; i++) { + var n = str.charCodeAt(i).toString(16); + hex += n.length < 2 ? '0' + n : n; + } + + return hex; +}; + +/** + * Shold be called to get hex representation (prefixed by 0x) of ascii string + * + * @method fromAscii + * @param {String} string + * @param {Number} optional padding + * @returns {String} hex representation of input string + */ +var fromAscii = function(str, pad) { + pad = pad === undefined ? 0 : pad; + var hex = toHexNative(str); + while (hex.length < pad*2) + hex += "00"; + return "0x" + hex; +}; + +/** + * Should be called to get display name of contract function + * + * @method extractDisplayName + * @param {String} name of function/event + * @returns {String} display name for function/event eg. multiply(uint256) -> multiply + */ +var extractDisplayName = function (name) { + var length = name.indexOf('('); + return length !== -1 ? name.substr(0, length) : name; +}; + +/// @returns overloaded part of function/event name +var extractTypeName = function (name) { + /// TODO: make it invulnerable + var length = name.indexOf('('); + return length !== -1 ? name.substr(length + 1, name.length - 1 - (length + 1)).replace(' ', '') : ""; +}; + +/** + * Converts value to it's decimal representation in string + * + * @method toDecimal + * @param {String|Number|BigNumber} + * @return {String} + */ +var toDecimal = function (value) { + return toBigNumber(value).toNumber(); +}; + +/** + * Converts value to it's hex representation + * + * @method fromDecimal + * @param {String|Number|BigNumber} + * @return {String} + */ +var fromDecimal = function (value) { + var number = toBigNumber(value); + var result = number.toString(16); + + return number.lessThan(0) ? '-0x' + result.substr(1) : '0x' + result; +}; + +/** + * Auto converts any given value into it's hex representation. + * + * And even stringifys objects before. + * + * @method toHex + * @param {String|Number|BigNumber|Object} + * @return {String} + */ +var toHex = function (val) { + /*jshint maxcomplexity:7 */ + + if (isBoolean(val)) + return fromDecimal(+val); + + if (isBigNumber(val)) + return fromDecimal(val); + + if (isObject(val)) + return fromAscii(JSON.stringify(val)); + + // if its a negative number, pass it through fromDecimal + if (isString(val)) { + if (val.indexOf('-0x') === 0) + return fromDecimal(val); + else if (!isFinite(val)) + return fromAscii(val); + } + + return fromDecimal(val); +}; + +/** + * Returns value of unit in Wei + * + * @method getValueOfUnit + * @param {String} unit the unit to convert to, default ether + * @returns {BigNumber} value of the unit (in Wei) + * @throws error if the unit is not correct:w + */ +var getValueOfUnit = function (unit) { + unit = unit ? unit.toLowerCase() : 'ether'; + var unitValue = unitMap[unit]; + if (unitValue === undefined) { + throw new Error('This unit doesn\'t exists, please use the one of the following units' + JSON.stringify(unitMap, null, 2)); + } + return new BigNumber(unitValue, 10); +}; + +/** + * Takes a number of wei and converts it to any other ether unit. + * + * Possible units are: + * - kwei/ada + * - mwei/babbage + * - gwei/shannon + * - szabo + * - finney + * - ether + * - kether/grand/einstein + * - mether + * - gether + * - tether + * + * @method fromWei + * @param {Number|String} number can be a number, number string or a HEX of a decimal + * @param {String} unit the unit to convert to, default ether + * @return {String|Object} When given a BigNumber object it returns one as well, otherwise a number +*/ +var fromWei = function(number, unit) { + var returnValue = toBigNumber(number).dividedBy(getValueOfUnit(unit)); + + return isBigNumber(number) ? returnValue : returnValue.toString(10); +}; + +/** + * Takes a number of a unit and converts it to wei. + * + * Possible units are: + * - kwei/ada + * - mwei/babbage + * - gwei/shannon + * - szabo + * - finney + * - ether + * - kether/grand/einstein + * - mether + * - gether + * - tether + * + * @method toWei + * @param {Number|String|BigNumber} number can be a number, number string or a HEX of a decimal + * @param {String} unit the unit to convert from, default ether + * @return {String|Object} When given a BigNumber object it returns one as well, otherwise a number +*/ +var toWei = function(number, unit) { + var returnValue = toBigNumber(number).times(getValueOfUnit(unit)); + + return isBigNumber(number) ? returnValue : returnValue.toString(10); +}; + +/** + * Takes an input and transforms it into an bignumber + * + * @method toBigNumber + * @param {Number|String|BigNumber} a number, string, HEX string or BigNumber + * @return {BigNumber} BigNumber +*/ +var toBigNumber = function(number) { + /*jshint maxcomplexity:5 */ + number = number || 0; + if (isBigNumber(number)) + return number; + + if (isString(number) && (number.indexOf('0x') === 0 || number.indexOf('-0x') === 0)) { + return new BigNumber(number.replace('0x',''), 16); + } + + return new BigNumber(number.toString(10), 10); +}; + +/** + * Takes and input transforms it into bignumber and if it is negative value, into two's complement + * + * @method toTwosComplement + * @param {Number|String|BigNumber} + * @return {BigNumber} + */ +var toTwosComplement = function (number) { + var bigNumber = toBigNumber(number); + if (bigNumber.lessThan(0)) { + return new BigNumber("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", 16).plus(bigNumber).plus(1); + } + return bigNumber; +}; + +/** + * Checks if the given string is strictly an address + * + * @method isStrictAddress + * @param {String} address the given HEX adress + * @return {Boolean} +*/ +var isStrictAddress = function (address) { + return /^0x[0-9a-f]{40}$/.test(address); +}; + +/** + * Checks if the given string is an address + * + * @method isAddress + * @param {String} address the given HEX adress + * @return {Boolean} +*/ +var isAddress = function (address) { + return /^(0x)?[0-9a-f]{40}$/.test(address); +}; + +/** + * Transforms given string to valid 20 bytes-length addres with 0x prefix + * + * @method toAddress + * @param {String} address + * @return {String} formatted address + */ +var toAddress = function (address) { + if (isStrictAddress(address)) { + return address; + } + + if (/^[0-9a-f]{40}$/.test(address)) { + return '0x' + address; + } + + return '0x' + padLeft(toHex(address).substr(2), 40); +}; + +/** + * Returns true if object is BigNumber, otherwise false + * + * @method isBigNumber + * @param {Object} + * @return {Boolean} + */ +var isBigNumber = function (object) { + return object instanceof BigNumber || + (object && object.constructor && object.constructor.name === 'BigNumber'); +}; + +/** + * Returns true if object is string, otherwise false + * + * @method isString + * @param {Object} + * @return {Boolean} + */ +var isString = function (object) { + return typeof object === 'string' || + (object && object.constructor && object.constructor.name === 'String'); +}; + +/** + * Returns true if object is function, otherwise false + * + * @method isFunction + * @param {Object} + * @return {Boolean} + */ +var isFunction = function (object) { + return typeof object === 'function'; +}; + +/** + * Returns true if object is Objet, otherwise false + * + * @method isObject + * @param {Object} + * @return {Boolean} + */ +var isObject = function (object) { + return typeof object === 'object'; +}; + +/** + * Returns true if object is boolean, otherwise false + * + * @method isBoolean + * @param {Object} + * @return {Boolean} + */ +var isBoolean = function (object) { + return typeof object === 'boolean'; +}; + +/** + * Returns true if object is array, otherwise false + * + * @method isArray + * @param {Object} + * @return {Boolean} + */ +var isArray = function (object) { + return object instanceof Array; +}; + +/** + * Returns true if given string is valid json object + * + * @method isJson + * @param {String} + * @return {Boolean} + */ +var isJson = function (str) { + try { + return !!JSON.parse(str); + } catch (e) { + return false; + } +}; + +module.exports = { + padLeft: padLeft, + findIndex: findIndex, + toHex: toHex, + toDecimal: toDecimal, + fromDecimal: fromDecimal, + toAscii: toAscii, + fromAscii: fromAscii, + extractDisplayName: extractDisplayName, + extractTypeName: extractTypeName, + toWei: toWei, + fromWei: fromWei, + toBigNumber: toBigNumber, + toTwosComplement: toTwosComplement, + toAddress: toAddress, + isBigNumber: isBigNumber, + isStrictAddress: isStrictAddress, + isAddress: isAddress, + isFunction: isFunction, + isString: isString, + isObject: isObject, + isBoolean: isBoolean, + isArray: isArray, + isJson: isJson +}; + + +},{"bignumber.js":8}],8:[function(require,module,exports){ +/*! 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 <M8ch88l@gmail.com> + 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); + + // 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); + +},{"crypto":1}],"natspec":[function(require,module,exports){ +/* + This file is part of natspec.js. + + natspec.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. + + natspec.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 natspec.js. If not, see <http://www.gnu.org/licenses/>. +*/ +/** @file natspec.js + * @authors: + * Marek Kotewicz <marek@ethdev.com> + * @date 2015 + */ + +var abi = require('./node_modules/web3/lib/solidity/abi.js'); + +/** + * This object should be used to evaluate natspec expression + * It has one method evaluateExpression which shoul be used + */ +var natspec = (function () { + /** + * Helper method + * Should be called to copy values from object to global context + * + * @method copyToContext + * @param {Object} object from which we want to copy properties + * @param {Object} object to which we copy + */ + var copyToContext = function (obj, context) { + Object.keys(obj).forEach(function (key) { + context[key] = obj[key]; + }); + } + + /** + * Should be used to generate codes, which will be evaluated + * + * @method generateCode + * @param {Object} object from which code will be generated + * @return {String} javascript code which is used to initalized variables + */ + var generateCode = function (obj) { + return Object.keys(obj).reduce(function (acc, key) { + return acc + "var " + key + " = context['" + key + "'];\n"; + }, ""); + }; + + /** + * Helper method + * Should be called to get method with given name from the abi + * + * @method getMethodWithName + * @param {Array} contract's abi + * @param {String} name of the method that we are looking for + * @return {Object} abi for method with name + */ + var getMethodWithName = function(abi, name) { + return abi.filter(function (method) { + return method.name === name; + })[0]; + }; + + /** + * Should be used to get all contract method input variables + * + * @method getMethodInputParams + * @param {Object} abi for certain method + * @param {Object} transaction object + * @return {Object} object with all contract's method input variables + */ + var getMethodInputParams = function (method, transaction) { + // do it with output formatter (cause we have to decode) + var params = abi.formatOutput(method.inputs, '0x' + transaction.params[0].data.slice(10)); + + return method.inputs.reduce(function (acc, current, index) { + acc[current.name] = params[index]; + return acc; + }, {}); + }; + + /** + * Should be called when we want to evaluate natspec expression + * Replaces all natspec 'subexpressions' with evaluated value + * + * @method mapExpressionToEvaluate + * @param {String} expression to evaluate + * @param {Function} callback which is called to evaluate te expression + * @return {String} evaluated expression + */ + var mapExpressionsToEvaluate = function (expression, cb) { + var evaluatedExpression = ""; + + // match everything in quotes + var pattern = /\` + "`" + `(?:\\.|[^` + "`" + `\\])*\` + "`" + `/gim + var match; + var lastIndex = 0; + try { + while ((match = pattern.exec(expression)) !== null) { + var startIndex = pattern.lastIndex - match[0].length; + var toEval = match[0].slice(1, match[0].length - 1); + evaluatedExpression += expression.slice(lastIndex, startIndex); + var evaluatedPart = cb(toEval); + evaluatedExpression += evaluatedPart; + lastIndex = pattern.lastIndex; + } + + evaluatedExpression += expression.slice(lastIndex); + } + catch (err) { + throw new Error("Natspec evaluation failed, wrong input params"); + } + + return evaluatedExpression; + }; + + /** + * Should be called to evaluate single expression + * Is internally using javascript's 'eval' method + * + * @method evaluateExpression + * @param {String} expression which should be evaluated + * @param {Object} [call] object containing contract abi, transaction, called method + * @return {String} evaluated expression + * @throws exception if method is not found or we are trying to evaluate input params that does not exists + */ + + var utils = require('../utils/utils'); + + var evaluateExpression = function (expression, call) { + //var self = this; + var context = {}; + + if (!!call) { + try { + var method = getMethodWithName(call.abi, call.method); + var params = getMethodInputParams(method, call.transaction); + copyToContext(params, context); + } + catch (err) { + throw new Error("Natspec evaluation failed, method does not exist"); + } + } + + var code = generateCode(context); + + var evaluatedExpression = mapExpressionsToEvaluate(expression, function (toEval) { + //var fn = new Function("context", "toHex", code + "return " + toEval + ";"); + //return fn(context, toHex).toString(); + var fn = new Function("context", "utils", code + "return " + toEval + ";"); + return fn(context, utils).toString(); + }); + + return evaluatedExpression; + }; + + /** + * Safe version of evaluateExpression + * Instead of throwing an exception it returns it as a string + * + * @method evaluateExpressionSafe + * @param {String} expression which should be evaluated + * @param {Object} [call] object containing contract abi, transaction, called method + * @return {String} evaluated expression + */ + var evaluateExpressionSafe = function (expression, call) { + try { + return evaluateExpression(expression, call); + } + catch (err) { + return err.message; + } + }; + + return { + evaluateExpression: evaluateExpression, + evaluateExpressionSafe: evaluateExpressionSafe + }; + +})(); + +module.exports = natspec; + + +},{"./node_modules/web3/lib/solidity/abi.js":2,"../utils/utils":7}]},{},[]); +` + //# sourceMappingURL=natspec.js.map` diff --git a/common/natspec/natspec_test.go b/common/natspec/natspec_test.go index 3b548817b..35a59469a 100644 --- a/common/natspec/natspec_test.go +++ b/common/natspec/natspec_test.go @@ -4,87 +4,141 @@ import ( "testing" ) -func TestNotice(t *testing.T) { +func makeUserdoc(desc string) []byte { + return []byte(` +{ + "source": "...", + "language": "Solidity", + "languageVersion": 1, + "methods": { + "multiply(uint256)": { + "notice": "` + desc + `" + }, + "balance(address)": { + "notice": "` + "`(balanceInmGAV / 1000).fixed(0,3)`" + ` GAV is the total funds available to ` + "`who.address()`." + `" + } + }, + "invariants": [ + { "notice": "The sum total amount of GAV in the system is 1 million." } + ], + "construction": [ + { "notice": "Endows ` + "`message.caller.address()`" + ` with 1m GAV." } + ] +} +`) +} - tx := ` - { - "jsonrpc": "2.0", - "method": "eth_call", - "params": [{ - "to": "0x8521742d3f456bd237e312d6e30724960f72517a", - "data": "0xc6888fa1000000000000000000000000000000000000000000000000000000000000007a" - }], - "id": 6 - } - ` - - abi := ` - [{ - "name": "multiply", - "constant": false, - "type": "function", - "inputs": [{ - "name": "a", - "type": "uint256" - }], - "outputs": [{ - "name": "d", - "type": "uint256" - }] +var data = "0xc6888fa1000000000000000000000000000000000000000000000000000000000000007a" + +var tx = ` +{ + "jsonrpc": "2.0", + "method": "eth_call", + "params": [{ + "to": "0x8521742d3f456bd237e312d6e30724960f72517a", + "data": "0xc6888fa1000000000000000000000000000000000000000000000000000000000000007a" + }], + "id": 6 +} +` + +var abi = []byte(` +[{ + "name": "multiply", + "constant": false, + "type": "function", + "inputs": [{ + "name": "a", + "type": "uint256" + }], + "outputs": [{ + "name": "d", + "type": "uint256" }] - ` +}] +`) + +func TestNotice(t *testing.T) { desc := "Will multiply `a` by 7 and return `a * 7`." + expected := "Will multiply 122 by 7 and return 854." - method := "multiply" + userdoc := makeUserdoc(desc) - ns, err := New() + ns, err := NewWithDocs(abi, userdoc, tx) if err != nil { - t.Errorf("NewNATSpec error %v", err) + t.Errorf("New: error: %v", err) } - notice, err := ns.Notice(tx, abi, method, desc) + notice, err := ns.Notice() if err != nil { - t.Errorf("expected no error got %v", err) + t.Errorf("expected no error, got %v", err) } - expected := "Will multiply 122 by 7 and return 854." if notice != expected { t.Errorf("incorrect notice. expected %v, got %v", expected, notice) } else { t.Logf("returned notice \"%v\"", notice) } +} + +// test missing method +func TestMissingMethod(t *testing.T) { - notice, err = ns.Notice(tx, abi, method, "Will multiply 122 by \"7\" and return 854.") + desc := "Will multiply `a` by 7 and return `a * 7`." + userdoc := makeUserdoc(desc) + expected := "natspec.js error evaluating expression: Natspec evaluation failed, method does not exist" + + ns, err := NewWithDocs(abi, userdoc, tx) + if err != nil { + t.Errorf("New: error: %v", err) + } - expected = "natspec.js error setting expression: (anonymous): Line 1:41 Unexpected number" + notice, err := ns.noticeForMethod(tx, "missing_method", "") if err == nil { - t.Errorf("expected error, got nothing (notice: '%v')", err, notice) + t.Errorf("expected error, got nothing (notice: '%v')", notice) } else { if err.Error() != expected { t.Errorf("expected error '%s' got '%v' (notice: '%v')", expected, err, notice) } } +} + +// test invalid desc - // https://github.com/ethereum/natspec.js/issues/1 - badDesc := "Will multiply `e` by 7 and return `a * 7`." - notice, err = ns.Notice(tx, abi, method, badDesc) +func TestInvalidDesc(t *testing.T) { - expected = "natspec.js error evaluating expression: Natspec evaluation failed, wrong input params" + desc := "Will multiply 122 by \"7\" and return 854." + expected := "invalid character '7' after object key:value pair" + userdoc := makeUserdoc(desc) + + _, err := NewWithDocs(abi, userdoc, tx) if err == nil { - t.Errorf("expected error, got nothing (notice: '%v')", notice) + t.Errorf("expected error, got nothing", err) } else { if err.Error() != expected { - t.Errorf("expected error '%s' got '%v' (notice: '%v')", expected, err, notice) + t.Errorf("expected error '%s' got '%v'", expected, err) } } +} + +// test wrong input params +func TestWrongInputParams(t *testing.T) { + + desc := "Will multiply `e` by 7 and return `a * 7`." + expected := "natspec.js error evaluating expression: Natspec evaluation failed, wrong input params" - notice, err = ns.Notice(tx, abi, "missing_method", desc) + userdoc := makeUserdoc(desc) + + ns, err := NewWithDocs(abi, userdoc, tx) + if err != nil { + t.Errorf("New: error: %v", err) + } - expected = "natspec.js error evaluating expression: Natspec evaluation failed, method does not exist" + notice, err := ns.Notice() if err == nil { t.Errorf("expected error, got nothing (notice: '%v')", notice) diff --git a/common/resolver/contracts.go b/common/resolver/contracts.go new file mode 100644 index 000000000..4aad95e43 --- /dev/null +++ b/common/resolver/contracts.go @@ -0,0 +1,36 @@ +package resolver + +const ( // built-in contracts address and code + ContractCodeURLhint = "0x60c180600c6000396000f30060003560e060020a90048063300a3bbf14601557005b6024600435602435604435602a565b60006000f35b6000600084815260200190815260200160002054600160a060020a0316600014806078575033600160a060020a03166000600085815260200190815260200160002054600160a060020a0316145b607f5760bc565b336000600085815260200190815260200160002081905550806001600085815260200190815260200160002083610100811060b657005b01819055505b50505056" + /* + contract URLhint { + function register(uint256 _hash, uint8 idx, uint256 _url) { + if (owner[_hash] == 0 || owner[_hash] == msg.sender) { + owner[_hash] = msg.sender; + url[_hash][idx] = _url; + } + } + mapping (uint256 => address) owner; + mapping (uint256 => uint256[256]) url; + } + */ + + ContractCodeHashReg = "0x609880600c6000396000f30060003560e060020a9004806331e12c2014601f578063d66d6c1014602b57005b6025603d565b60006000f35b6037600435602435605d565b60006000f35b600054600160a060020a0316600014605357605b565b336000819055505b565b600054600160a060020a031633600160a060020a031614607b576094565b8060016000848152602001908152602001600020819055505b505056" + /* + contract HashReg { + function setowner() { + if (owner == 0) { + owner = msg.sender; + } + } + function register(uint256 _key, uint256 _content) { + if (msg.sender == owner) { + content[_key] = _content; + } + } + address owner; + mapping (uint256 => uint256) content; + } + */ + +) diff --git a/common/resolver/resolver.go b/common/resolver/resolver.go new file mode 100644 index 000000000..1e6d03ffb --- /dev/null +++ b/common/resolver/resolver.go @@ -0,0 +1,128 @@ +package resolver + +import ( + "encoding/binary" + "fmt" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" + xe "github.com/ethereum/go-ethereum/xeth" +) + +/* +Resolver implements the Ethereum DNS mapping +HashReg : Key Hash (hash of domain name or contract code) -> Content Hash +UrlHint : Content Hash -> Url Hint + +The resolver is meant to be called by the roundtripper transport implementation +of a url scheme +*/ + +// contract addresses will be hardcoded after they're created +var URLHintContractAddress string = "0000000000000000000000000000000000000000000000000000000000001234" +var HashRegContractAddress string = "0000000000000000000000000000000000000000000000000000000000005678" + +func CreateContracts(xeth *xe.XEth, addr string) { + var err error + URLHintContractAddress, err = xeth.Transact(addr, "", "100000000000", "1000000", "100000", ContractCodeURLhint) + if err != nil { + panic(err) + } + HashRegContractAddress, err = xeth.Transact(addr, "", "100000000000", "1000000", "100000", ContractCodeHashReg) + if err != nil { + panic(err) + } +} + +type Resolver struct { + backend Backend + urlHintContractAddress string + hashRegContractAddress string +} + +type Backend interface { + StorageAt(string, string) string +} + +func New(eth Backend, uhca, nrca string) *Resolver { + return &Resolver{eth, uhca, nrca} +} + +func (self *Resolver) KeyToContentHash(khash common.Hash) (chash common.Hash, err error) { + // look up in hashReg + key := storageAddress(storageMapping(storageIdx2Addr(1), khash[:])) + hash := self.backend.StorageAt(self.hashRegContractAddress, key) + + if hash == "0x0" || len(hash) < 3 { + err = fmt.Errorf("GetHashReg: content hash not found") + return + } + + copy(chash[:], common.Hex2BytesFixed(hash[2:], 32)) + return +} + +func (self *Resolver) ContentHashToUrl(chash common.Hash) (uri string, err error) { + // look up in URL reg + var str string = " " + var idx uint32 + for len(str) > 0 { + mapaddr := storageMapping(storageIdx2Addr(1), chash[:]) + key := storageAddress(storageFixedArray(mapaddr, storageIdx2Addr(idx))) + hex := self.backend.StorageAt(self.urlHintContractAddress, key) + str = string(common.Hex2Bytes(hex[2:])) + l := len(str) + for (l > 0) && (str[l-1] == 0) { + l-- + } + str = str[:l] + uri = uri + str + idx++ + } + + if len(uri) == 0 { + err = fmt.Errorf("GetURLhint: URL hint not found") + } + return +} + +func (self *Resolver) KeyToUrl(key common.Hash) (uri string, hash common.Hash, err error) { + // look up in urlHint + hash, err = self.KeyToContentHash(key) + if err != nil { + return + } + uri, err = self.ContentHashToUrl(hash) + return +} + +func storageIdx2Addr(varidx uint32) []byte { + data := make([]byte, 32) + binary.BigEndian.PutUint32(data[28:32], varidx) + return data +} + +func storageMapping(addr, key []byte) []byte { + data := make([]byte, 64) + copy(data[0:32], key[0:32]) + copy(data[32:64], addr[0:32]) + return crypto.Sha3(data) +} + +func storageFixedArray(addr, idx []byte) []byte { + var carry byte + for i := 31; i >= 0; i-- { + var b byte = addr[i] + idx[i] + carry + if b < addr[i] { + carry = 1 + } else { + carry = 0 + } + addr[i] = b + } + return addr +} + +func storageAddress(addr []byte) string { + return common.ToHex(addr) +} diff --git a/common/resolver/resolver_test.go b/common/resolver/resolver_test.go new file mode 100644 index 000000000..f5eb51437 --- /dev/null +++ b/common/resolver/resolver_test.go @@ -0,0 +1,88 @@ +package resolver + +import ( + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" +) + +type testBackend struct { + // contracts mock + contracts map[string](map[string]string) +} + +var ( + text = "test" + codehash = common.StringToHash("1234") + hash = common.BytesToHash(crypto.Sha3([]byte(text))) + url = "bzz://bzzhash/my/path/contr.act" +) + +func NewTestBackend() *testBackend { + self := &testBackend{} + self.contracts = make(map[string](map[string]string)) + + self.contracts[HashRegContractAddress] = make(map[string]string) + key := storageAddress(storageMapping(storageIdx2Addr(1), codehash[:])) + self.contracts[HashRegContractAddress][key] = hash.Hex() + + self.contracts[URLHintContractAddress] = make(map[string]string) + mapaddr := storageMapping(storageIdx2Addr(1), hash[:]) + + key = storageAddress(storageFixedArray(mapaddr, storageIdx2Addr(0))) + self.contracts[URLHintContractAddress][key] = common.ToHex([]byte(url)) + key = storageAddress(storageFixedArray(mapaddr, storageIdx2Addr(1))) + self.contracts[URLHintContractAddress][key] = "0x00" + + return self +} + +func (self *testBackend) StorageAt(ca, sa string) (res string) { + c := self.contracts[ca] + if c == nil { + return + } + res = c[sa] + return +} + +func TestKeyToContentHash(t *testing.T) { + b := NewTestBackend() + res := New(b, URLHintContractAddress, HashRegContractAddress) + + got, err := res.KeyToContentHash(codehash) + if err != nil { + t.Errorf("expected no error, got %v", err) + } else { + if got != hash { + t.Errorf("incorrect result, expected %x, got %x: ", hash.Hex(), got.Hex()) + } + } +} + +func TestContentHashToUrl(t *testing.T) { + b := NewTestBackend() + res := New(b, URLHintContractAddress, HashRegContractAddress) + got, err := res.ContentHashToUrl(hash) + if err != nil { + t.Errorf("expected no error, got %v", err) + } else { + if string(got) != url { + t.Errorf("incorrect result, expected %v, got %s: ", url, string(got)) + } + } +} + +func TestKeyToUrl(t *testing.T) { + b := NewTestBackend() + res := New(b, URLHintContractAddress, HashRegContractAddress) + got, _, err := res.KeyToUrl(codehash) + if err != nil { + t.Errorf("expected no error, got %v", err) + } else { + if string(got) != url { + t.Errorf("incorrect result, expected %v, got %s: ", url, string(got)) + } + } +} diff --git a/core/block_processor.go b/core/block_processor.go index e3c284979..28636a725 100644 --- a/core/block_processor.go +++ b/core/block_processor.go @@ -21,7 +21,7 @@ import ( const ( // must be bumped when consensus algorithm is changed, this forces the upgradedb // command to be run (forces the blocks to be imported again using the new algorithm) - BlockChainVersion = 1 + BlockChainVersion = 2 ) var statelogger = logger.NewLogger("BLOCK") @@ -85,8 +85,8 @@ func (self *BlockProcessor) ApplyTransaction(coinbase *state.StateObject, stated _, gas, err := ApplyMessage(NewEnv(statedb, self.bc, tx, block), tx, cb) if err != nil && (IsNonceErr(err) || state.IsGasLimitErr(err) || IsInvalidTxErr(err)) { // If the account is managed, remove the invalid nonce. - from, _ := tx.From() - self.bc.TxState().RemoveNonce(from, tx.Nonce()) + //from, _ := tx.From() + //self.bc.TxState().RemoveNonce(from, tx.Nonce()) return nil, nil, err } @@ -149,28 +149,42 @@ func (self *BlockProcessor) ApplyTransactions(coinbase *state.StateObject, state return receipts, err } +func (sm *BlockProcessor) RetryProcess(block *types.Block) (logs state.Logs, err error) { + // Processing a blocks may never happen simultaneously + sm.mutex.Lock() + defer sm.mutex.Unlock() + + header := block.Header() + if !sm.bc.HasBlock(header.ParentHash) { + return nil, ParentError(header.ParentHash) + } + parent := sm.bc.GetBlock(header.ParentHash) + + return sm.processWithParent(block, parent) +} + // Process block will attempt to process the given block's transactions and applies them // on top of the block's parent state (given it exists) and will return wether it was // successful or not. -func (sm *BlockProcessor) Process(block *types.Block) (td *big.Int, logs state.Logs, err error) { +func (sm *BlockProcessor) Process(block *types.Block) (logs state.Logs, err error) { // Processing a blocks may never happen simultaneously sm.mutex.Lock() defer sm.mutex.Unlock() header := block.Header() if sm.bc.HasBlock(header.Hash()) { - return nil, nil, &KnownBlockError{header.Number, header.Hash()} + return nil, &KnownBlockError{header.Number, header.Hash()} } if !sm.bc.HasBlock(header.ParentHash) { - return nil, nil, ParentError(header.ParentHash) + return nil, ParentError(header.ParentHash) } parent := sm.bc.GetBlock(header.ParentHash) return sm.processWithParent(block, parent) } -func (sm *BlockProcessor) processWithParent(block, parent *types.Block) (td *big.Int, logs state.Logs, err error) { +func (sm *BlockProcessor) processWithParent(block, parent *types.Block) (logs state.Logs, err error) { sm.lastAttemptedBlock = block // Create a new state based on the parent's root (e.g., create copy) @@ -183,7 +197,7 @@ func (sm *BlockProcessor) processWithParent(block, parent *types.Block) (td *big // There can be at most two uncles if len(block.Uncles()) > 2 { - return nil, nil, ValidationError("Block can only contain one uncle (contained %v)", len(block.Uncles())) + return nil, ValidationError("Block can only contain one uncle (contained %v)", len(block.Uncles())) } receipts, err := sm.TransitionState(state, parent, block, false) @@ -232,7 +246,7 @@ func (sm *BlockProcessor) processWithParent(block, parent *types.Block) (td *big } // Calculate the td for this block - td = CalculateTD(block, parent) + //td = CalculateTD(block, parent) // Sync the current block's state to the database state.Sync() @@ -244,7 +258,7 @@ func (sm *BlockProcessor) processWithParent(block, parent *types.Block) (td *big putTx(sm.extraDb, tx, block, uint64(i)) } - return td, state.Logs(), nil + return state.Logs(), nil } // Validates the current block. Returns an error if the block was invalid, diff --git a/core/blocks.go b/core/blocks.go new file mode 100644 index 000000000..b26e8f6ee --- /dev/null +++ b/core/blocks.go @@ -0,0 +1,7 @@ +package core + +import "github.com/ethereum/go-ethereum/common" + +var badHashes = []common.Hash{ + common.HexToHash("f269c503aed286caaa0d114d6a5320e70abbc2febe37953207e76a2873f2ba79"), +} diff --git a/core/chain_makers.go b/core/chain_makers.go index 810741820..250671ef8 100644 --- a/core/chain_makers.go +++ b/core/chain_makers.go @@ -93,12 +93,12 @@ func makeChain(bman *BlockProcessor, parent *types.Block, max int, db common.Dat blocks := make(types.Blocks, max) for i := 0; i < max; i++ { block := makeBlock(bman, parent, i, db, seed) - td, _, err := bman.processWithParent(block, parent) + _, err := bman.processWithParent(block, parent) if err != nil { fmt.Println("process with parent failed", err) panic(err) } - block.Td = td + block.Td = CalculateTD(block, parent) blocks[i] = block parent = block } diff --git a/core/chain_manager.go b/core/chain_manager.go index 4f1e1e68a..1df56b27f 100644 --- a/core/chain_manager.go +++ b/core/chain_manager.go @@ -26,11 +26,10 @@ var ( blockNumPre = []byte("block-num-") ) -const blockCacheLimit = 10000 - -type StateQuery interface { - GetAccount(addr []byte) *state.StateObject -} +const ( + blockCacheLimit = 10000 + maxFutureBlocks = 256 +) func CalcDifficulty(block, parent *types.Header) *big.Int { diff := new(big.Int) @@ -95,13 +94,35 @@ type ChainManager struct { } func NewChainManager(blockDb, stateDb common.Database, mux *event.TypeMux) *ChainManager { - bc := &ChainManager{blockDb: blockDb, stateDb: stateDb, genesisBlock: GenesisBlock(stateDb), eventMux: mux, quit: make(chan struct{}), cache: NewBlockCache(blockCacheLimit)} + bc := &ChainManager{ + blockDb: blockDb, + stateDb: stateDb, + genesisBlock: GenesisBlock(stateDb), + eventMux: mux, + quit: make(chan struct{}), + cache: NewBlockCache(blockCacheLimit), + } bc.setLastBlock() + + // Check the current state of the block hashes and make sure that we do not have any of the bad blocks in our chain + for _, hash := range badHashes { + if block := bc.GetBlock(hash); block != nil { + glog.V(logger.Error).Infof("Found bad hash. Reorganising chain to state %x\n", block.ParentHash().Bytes()[:4]) + block = bc.GetBlock(block.ParentHash()) + if block == nil { + glog.Fatal("Unable to complete. Parent block not found. Corrupted DB?") + } + bc.SetHead(block) + + glog.V(logger.Error).Infoln("Chain reorg was successfull. Resuming normal operation") + } + } + bc.transState = bc.State().Copy() // Take ownership of this particular state bc.txState = state.ManageState(bc.State().Copy()) - bc.futureBlocks = NewBlockCache(254) + bc.futureBlocks = NewBlockCache(maxFutureBlocks) bc.makeCache() go bc.update() @@ -109,6 +130,26 @@ func NewChainManager(blockDb, stateDb common.Database, mux *event.TypeMux) *Chai return bc } +func (bc *ChainManager) SetHead(head *types.Block) { + bc.mu.Lock() + defer bc.mu.Unlock() + + for block := bc.currentBlock; block != nil && block.Hash() != head.Hash(); block = bc.GetBlock(block.Header().ParentHash) { + bc.removeBlock(block) + } + + bc.cache = NewBlockCache(blockCacheLimit) + bc.currentBlock = head + bc.makeCache() + + statedb := state.New(head.Root(), bc.stateDb) + bc.txState = state.ManageState(statedb) + bc.transState = statedb.Copy() + bc.setTotalDifficulty(head.Td) + bc.insert(head) + bc.setLastBlock() +} + func (self *ChainManager) Td() *big.Int { self.mu.RLock() defer self.mu.RUnlock() @@ -287,7 +328,12 @@ func (self *ChainManager) Export(w io.Writer) error { last := self.currentBlock.NumberU64() for nr := uint64(0); nr <= last; nr++ { - if err := self.GetBlockByNumber(nr).EncodeRLP(w); err != nil { + block := self.GetBlockByNumber(nr) + if block == nil { + return fmt.Errorf("export failed on #%d: not found", nr) + } + + if err := block.EncodeRLP(w); err != nil { return err } } @@ -461,7 +507,7 @@ func (self *ChainManager) InsertChain(chain types.Blocks) error { } // Call in to the block processor and check for errors. It's likely that if one block fails // all others will fail too (unless a known block is returned). - td, logs, err := self.processor.Process(block) + logs, err := self.processor.Process(block) if err != nil { if IsKnownBlockErr(err) { continue @@ -486,13 +532,14 @@ func (self *ChainManager) InsertChain(chain types.Blocks) error { h := block.Header() - glog.V(logger.Error).Infof("INVALID block #%v (%x)\n", h.Number, h.Hash().Bytes()[:4]) + glog.V(logger.Error).Infof("INVALID block #%v (%x)\n", h.Number, h.Hash().Bytes()) glog.V(logger.Error).Infoln(err) glog.V(logger.Debug).Infoln(block) return err } - block.Td = td + + block.Td = new(big.Int).Set(CalculateTD(block, self.GetBlock(block.ParentHash()))) self.mu.Lock() { @@ -502,14 +549,14 @@ func (self *ChainManager) InsertChain(chain types.Blocks) error { self.write(block) // Compare the TD of the last known block in the canonical chain to make sure it's greater. // At this point it's possible that a different chain (fork) becomes the new canonical chain. - if td.Cmp(self.td) > 0 { + if block.Td.Cmp(self.td) > 0 { //if block.Header().Number.Cmp(new(big.Int).Add(cblock.Header().Number, common.Big1)) < 0 { if block.Number().Cmp(cblock.Number()) <= 0 { chash := cblock.Hash() hash := block.Hash() if glog.V(logger.Info) { - glog.Infof("Split detected. New head #%v (%x) TD=%v, was #%v (%x) TD=%v\n", block.Header().Number, hash[:4], td, cblock.Header().Number, chash[:4], self.td) + glog.Infof("Split detected. New head #%v (%x) TD=%v, was #%v (%x) TD=%v\n", block.Header().Number, hash[:4], block.Td, cblock.Header().Number, chash[:4], self.td) } // during split we merge two different chains and create the new canonical chain self.merge(self.getBlockByNumber(block.NumberU64()), block) @@ -518,7 +565,7 @@ func (self *ChainManager) InsertChain(chain types.Blocks) error { queueEvent.splitCount++ } - self.setTotalDifficulty(td) + self.setTotalDifficulty(block.Td) self.insert(block) jsonlogger.LogJson(&logger.EthChainNewHead{ diff --git a/core/chain_manager_test.go b/core/chain_manager_test.go index 19afe0d5c..f16c0f0c3 100644 --- a/core/chain_manager_test.go +++ b/core/chain_manager_test.go @@ -69,15 +69,16 @@ func printChain(bc *ChainManager) { func testChain(chainB types.Blocks, bman *BlockProcessor) (*big.Int, error) { td := new(big.Int) for _, block := range chainB { - td2, _, err := bman.bc.processor.Process(block) + _, err := bman.bc.processor.Process(block) if err != nil { if IsKnownBlockErr(err) { continue } return nil, err } - block.Td = td2 - td = td2 + parent := bman.bc.GetBlock(block.ParentHash()) + block.Td = CalculateTD(block, parent) + td = block.Td bman.bc.mu.Lock() { diff --git a/core/filter.go b/core/filter.go index 4508b35b3..a924709f2 100644 --- a/core/filter.go +++ b/core/filter.go @@ -134,7 +134,8 @@ Logs: for i, topics := range self.topics { for _, topic := range topics { var match bool - if log.Topics[i] == topic { + // common.Hash{} is a match all (wildcard) + if (topic == common.Hash{}) || log.Topics[i] == topic { match = true } if !match { diff --git a/core/genesis.go b/core/genesis.go index 8ef1e140f..e72834822 100644 --- a/core/genesis.go +++ b/core/genesis.go @@ -36,7 +36,7 @@ func GenesisBlock(db common.Database) *types.Block { Balance string Code string } - err := json.Unmarshal(genesisData, &accounts) + err := json.Unmarshal(GenesisData, &accounts) if err != nil { fmt.Println("enable to decode genesis json data:", err) os.Exit(1) @@ -52,11 +52,12 @@ func GenesisBlock(db common.Database) *types.Block { } statedb.Sync() genesis.Header().Root = statedb.Root() + genesis.Td = params.GenesisDifficulty return genesis } -var genesisData = []byte(`{ +var GenesisData = []byte(`{ "0000000000000000000000000000000000000001": {"balance": "1"}, "0000000000000000000000000000000000000002": {"balance": "1"}, "0000000000000000000000000000000000000003": {"balance": "1"}, diff --git a/core/state/managed_state.go b/core/state/managed_state.go index 9e6be9980..5114f7a7a 100644 --- a/core/state/managed_state.go +++ b/core/state/managed_state.go @@ -62,6 +62,7 @@ func (ms *ManagedState) NewNonce(addr common.Address) uint64 { } } account.nonces = append(account.nonces, true) + return uint64(len(account.nonces)-1) + account.nstart } diff --git a/core/transaction_pool.go b/core/transaction_pool.go index 94a94f93d..eaddcfa09 100644 --- a/core/transaction_pool.go +++ b/core/transaction_pool.go @@ -28,6 +28,8 @@ const txPoolQueueSize = 50 type TxPoolHook chan *types.Transaction type TxMsg struct{ Tx *types.Transaction } +type stateFn func() *state.StateDB + const ( minGasPrice = 1000000 ) @@ -47,7 +49,7 @@ type TxPool struct { // Quiting channel quit chan bool // The state function which will allow us to do some pre checkes - currentState func() *state.StateDB + currentState stateFn // The actual pool txs map[common.Hash]*types.Transaction invalidHashes *set.Set @@ -57,7 +59,7 @@ type TxPool struct { eventMux *event.TypeMux } -func NewTxPool(eventMux *event.TypeMux, currentStateFn func() *state.StateDB) *TxPool { +func NewTxPool(eventMux *event.TypeMux, currentStateFn stateFn) *TxPool { return &TxPool{ txs: make(map[common.Hash]*types.Transaction), queueChan: make(chan *types.Transaction, txPoolQueueSize), diff --git a/core/types/block.go b/core/types/block.go index c47b555ed..f9206ec76 100644 --- a/core/types/block.go +++ b/core/types/block.go @@ -347,22 +347,20 @@ func (self *Block) Copy() *Block { } func (self *Block) String() string { - return fmt.Sprintf(`BLOCK(%x): Size: %v TD: %v { -NoNonce: %x -Header: -[ + return fmt.Sprintf(`Block(#%v): Size: %v TD: %v { +MinerHash: %x %v -] Transactions: %v Uncles: %v } -`, self.header.Hash(), self.Size(), self.Td, self.header.HashNoNonce(), self.header, self.transactions, self.uncles) +`, self.Number(), self.Size(), self.Td, self.header.HashNoNonce(), self.header, self.transactions, self.uncles) } func (self *Header) String() string { - return fmt.Sprintf(` + return fmt.Sprintf(`Header(%x): +[ ParentHash: %x UncleHash: %x Coinbase: %x @@ -377,8 +375,8 @@ func (self *Header) String() string { Time: %v Extra: %s MixDigest: %x - Nonce: %x`, - self.ParentHash, self.UncleHash, self.Coinbase, self.Root, self.TxHash, self.ReceiptHash, self.Bloom, self.Difficulty, self.Number, self.GasLimit, self.GasUsed, self.Time, self.Extra, self.MixDigest, self.Nonce) + Nonce: %x +]`, self.Hash(), self.ParentHash, self.UncleHash, self.Coinbase, self.Root, self.TxHash, self.ReceiptHash, self.Bloom, self.Difficulty, self.Number, self.GasLimit, self.GasUsed, self.Time, self.Extra, self.MixDigest, self.Nonce) } type Blocks []*Block diff --git a/core/types/common.go b/core/types/common.go index 4397d4938..dbdaaba0c 100644 --- a/core/types/common.go +++ b/core/types/common.go @@ -10,7 +10,7 @@ import ( ) type BlockProcessor interface { - Process(*Block) (*big.Int, state.Logs, error) + Process(*Block) (state.Logs, error) } const bloomLength = 256 diff --git a/eth/backend.go b/eth/backend.go index 5798e641a..88456e448 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -50,6 +50,7 @@ type Config struct { LogLevel int LogJSON string VmDebug bool + NatSpec bool MaxPeers int Port string @@ -144,6 +145,7 @@ type Ethereum struct { // logger logger.LogSystem Mining bool + NatSpec bool DataDir string etherbase common.Address clientVersion string @@ -208,6 +210,7 @@ func New(config *Config) (*Ethereum, error) { clientVersion: config.Name, // TODO should separate from Name ethVersionId: config.ProtocolVersion, netVersionId: config.NetworkId, + NatSpec: config.NatSpec, } eth.chainManager = core.NewChainManager(blockDb, stateDb, eth.EventMux()) diff --git a/eth/downloader/downloader.go b/eth/downloader/downloader.go index addcbcc44..cfc494b2f 100644 --- a/eth/downloader/downloader.go +++ b/eth/downloader/downloader.go @@ -472,6 +472,8 @@ func (d *Downloader) process() error { } break } else if err != nil { + // immediatly unregister the false peer but do not disconnect + d.UnregisterPeer(d.activePeer) // Reset chain completely. This needs much, much improvement. // instead: check all blocks leading down to this block false block and remove it blocks = nil diff --git a/eth/handler.go b/eth/handler.go index 5c0660d84..622f22132 100644 --- a/eth/handler.go +++ b/eth/handler.go @@ -36,6 +36,7 @@ pm.chainman.InsertChain(blocks) import ( "fmt" + "math" "math/big" "sync" @@ -326,7 +327,7 @@ func (pm *ProtocolManager) BroadcastBlock(hash common.Hash, block *types.Block) } // Broadcast block to peer set // XXX due to the current shit state of the network disable the limit - //peers = peers[:int(math.Sqrt(float64(len(peers))))] + peers = peers[:int(math.Sqrt(float64(len(peers))))] for _, peer := range peers { peer.sendNewBlock(block) } diff --git a/miner/worker.go b/miner/worker.go index 63645cd54..d5ffb398a 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -253,11 +253,23 @@ func (self *worker) commitNewWork() { // Keep track of transactions which return errors so they can be removed var ( - remove = set.New() - tcount = 0 + remove = set.New() + tcount = 0 + ignoredTransactors = set.New() ) //gasLimit: for _, tx := range transactions { + // We can skip err. It has already been validated in the tx pool + from, _ := tx.From() + // Move on to the next transaction when the transactor is in ignored transactions set + // This may occur when a transaction hits the gas limit. When a gas limit is hit and + // the transaction is processed (that could potentially be included in the block) it + // will throw a nonce error because the previous transaction hasn't been processed. + // Therefor we need to ignore any transaction after the ignored one. + if ignoredTransactors.Has(from) { + continue + } + self.current.state.StartRecord(tx.Hash(), common.Hash{}, 0) err := self.commitTransaction(tx) @@ -265,14 +277,18 @@ func (self *worker) commitNewWork() { case core.IsNonceErr(err) || core.IsInvalidTxErr(err): // Remove invalid transactions from, _ := tx.From() + self.chain.TxState().RemoveNonce(from, tx.Nonce()) remove.Add(tx.Hash()) if glog.V(logger.Detail) { glog.Infof("TX (%x) failed, will be removed: %v\n", tx.Hash().Bytes()[:4], err) - //glog.Infoln(tx) } case state.IsGasLimitErr(err): + from, _ := tx.From() + // ignore the transactor so no nonce errors will be thrown for this account + // next time the worker is run, they'll be picked up again. + ignoredTransactors.Add(from) //glog.V(logger.Debug).Infof("Gas limit reached for block. %d TXs included in this block\n", i) //break gasLimit default: diff --git a/rpc/api.go b/rpc/api.go index bf5066f9a..66283752b 100644 --- a/rpc/api.go +++ b/rpc/api.go @@ -2,7 +2,7 @@ package rpc import ( "encoding/json" - // "fmt" + "fmt" "math/big" "sync" @@ -167,6 +167,12 @@ func (api *EthereumApi) GetRequestReply(req *RpcRequest, reply *interface{}) err return err } + // call ConfirmTransaction first + tx, _ := json.Marshal(req) + if !api.xeth().ConfirmTransaction(string(tx)) { + return fmt.Errorf("Transaction not confirmed") + } + v, err := api.xeth().Transact(args.From, args.To, args.Value.String(), args.Gas.String(), args.GasPrice.String(), args.Data) if err != nil { return err diff --git a/rpc/args.go b/rpc/args.go index 4b3840285..d31773ff7 100644 --- a/rpc/args.go +++ b/rpc/args.go @@ -53,22 +53,23 @@ func blockHeight(raw interface{}, number *int64) error { return nil } -func numString(raw interface{}, number *int64) error { +func numString(raw interface{}) (*big.Int, error) { + var number *big.Int // Parse as integer num, ok := raw.(float64) if ok { - *number = int64(num) - return nil + number = big.NewInt(int64(num)) + return number, nil } // Parse as string/hexstring str, ok := raw.(string) - if !ok { - return NewInvalidTypeError("", "not a number or string") + if ok { + number = common.String2Big(str) + return number, nil } - *number = common.String2Big(str).Int64() - return nil + return nil, NewInvalidTypeError("", "not a number or string") } // func toNumber(v interface{}) (int64, error) { @@ -202,33 +203,36 @@ func (args *NewTxArgs) UnmarshalJSON(b []byte) (err error) { args.To = ext.To args.Data = ext.Data - var num int64 + var num *big.Int if ext.Value == nil { - num = 0 + num = big.NewInt(0) } else { - if err := numString(ext.Value, &num); err != nil { + num, err = numString(ext.Value) + if err != nil { return err } } - args.Value = big.NewInt(num) + args.Value = num + num = nil if ext.Gas == nil { - num = 0 + num = big.NewInt(0) } else { - if err := numString(ext.Gas, &num); err != nil { + if num, err = numString(ext.Gas); err != nil { return err } } - args.Gas = big.NewInt(num) + args.Gas = num + num = nil if ext.GasPrice == nil { - num = 0 + num = big.NewInt(0) } else { - if err := numString(ext.GasPrice, &num); err != nil { + if num, err = numString(ext.GasPrice); err != nil { return err } } - args.GasPrice = big.NewInt(num) + args.GasPrice = num // Check for optional BlockNumber param if len(obj) > 1 { @@ -286,33 +290,33 @@ func (args *CallArgs) UnmarshalJSON(b []byte) (err error) { } args.To = ext.To - var num int64 + var num *big.Int if ext.Value == nil { - num = int64(0) + num = big.NewInt(0) } else { - if err := numString(ext.Value, &num); err != nil { + if num, err = numString(ext.Value); err != nil { return err } } - args.Value = big.NewInt(num) + args.Value = num if ext.Gas == nil { - num = int64(0) + num = big.NewInt(0) } else { - if err := numString(ext.Gas, &num); err != nil { + if num, err = numString(ext.Gas); err != nil { return err } } - args.Gas = big.NewInt(num) + args.Gas = num if ext.GasPrice == nil { - num = int64(0) + num = big.NewInt(0) } else { - if err := numString(ext.GasPrice, &num); err != nil { + if num, err = numString(ext.GasPrice); err != nil { return err } } - args.GasPrice = big.NewInt(num) + args.GasPrice = num args.Data = ext.Data @@ -655,6 +659,7 @@ func (args *BlockFilterArgs) UnmarshalJSON(b []byte) (err error) { // return NewDecodeParamError(fmt.Sprintf("ToBlock %v", err)) var num int64 + var numBig *big.Int // if blank then latest if obj[0].FromBlock == nil { @@ -682,22 +687,22 @@ func (args *BlockFilterArgs) UnmarshalJSON(b []byte) (err error) { args.Latest = num if obj[0].Limit == nil { - num = defaultLogLimit + numBig = big.NewInt(defaultLogLimit) } else { - if err := numString(obj[0].Limit, &num); err != nil { + if numBig, err = numString(obj[0].Limit); err != nil { return err } } - args.Max = int(num) + args.Max = int(numBig.Int64()) if obj[0].Offset == nil { - num = defaultLogOffset + numBig = big.NewInt(defaultLogOffset) } else { - if err := numString(obj[0].Offset, &num); err != nil { + if numBig, err = numString(obj[0].Offset); err != nil { return err } } - args.Skip = int(num) + args.Skip = int(numBig.Int64()) if obj[0].Address != nil { marg, ok := obj[0].Address.([]interface{}) @@ -739,10 +744,14 @@ func (args *BlockFilterArgs) UnmarshalJSON(b []byte) (err error) { for j, jv := range argarray { if v, ok := jv.(string); ok { topicdbl[i][j] = v + } else if jv == nil { + topicdbl[i][j] = "" } else { return NewInvalidTypeError(fmt.Sprintf("topic[%d][%d]", i, j), "is not a string") } } + } else if iv == nil { + topicdbl[i] = []string{""} } else { return NewInvalidTypeError(fmt.Sprintf("topic[%d]", i), "not a string or array") } @@ -890,16 +899,16 @@ func (args *WhisperMessageArgs) UnmarshalJSON(b []byte) (err error) { args.From = obj[0].From args.Topics = obj[0].Topics - var num int64 - if err := numString(obj[0].Priority, &num); err != nil { + var num *big.Int + if num, err = numString(obj[0].Priority); err != nil { return err } - args.Priority = uint32(num) + args.Priority = uint32(num.Int64()) - if err := numString(obj[0].Ttl, &num); err != nil { + if num, err = numString(obj[0].Ttl); err != nil { return err } - args.Ttl = uint32(num) + args.Ttl = uint32(num.Int64()) return nil } @@ -969,11 +978,11 @@ func (args *FilterIdArgs) UnmarshalJSON(b []byte) (err error) { return NewInsufficientParamsError(len(obj), 1) } - var num int64 - if err := numString(obj[0], &num); err != nil { + var num *big.Int + if num, err = numString(obj[0]); err != nil { return err } - args.Id = int(num) + args.Id = int(num.Int64()) return nil } diff --git a/rpc/http.go b/rpc/http.go index 790442a28..f9c646908 100644 --- a/rpc/http.go +++ b/rpc/http.go @@ -5,7 +5,6 @@ import ( "fmt" "io" "io/ioutil" - "net" "net/http" "github.com/ethereum/go-ethereum/logger" @@ -15,6 +14,7 @@ import ( ) var rpclogger = logger.NewLogger("RPC") +var rpclistener *stoppableTCPListener const ( jsonrpcver = "2.0" @@ -22,11 +22,19 @@ const ( ) func Start(pipe *xeth.XEth, config RpcConfig) error { - l, err := net.Listen("tcp", fmt.Sprintf("%s:%d", config.ListenAddress, config.ListenPort)) + if rpclistener != nil { + if fmt.Sprintf("%s:%d", config.ListenAddress, config.ListenPort) != rpclistener.Addr().String() { + return fmt.Errorf("RPC service already running on %s ", rpclistener.Addr().String()) + } + return nil // RPC service already running on given host/port + } + + l, err := newStoppableTCPListener(fmt.Sprintf("%s:%d", config.ListenAddress, config.ListenPort)) if err != nil { rpclogger.Errorf("Can't listen on %s:%d: %v", config.ListenAddress, config.ListenPort, err) return err } + rpclistener = l var handler http.Handler if len(config.CorsDomain) > 0 { @@ -35,9 +43,9 @@ func Start(pipe *xeth.XEth, config RpcConfig) error { opts.AllowedOrigins = []string{config.CorsDomain} c := cors.New(opts) - handler = c.Handler(JSONRPC(pipe)) + handler = newStoppableHandler(c.Handler(JSONRPC(pipe)), l.stop) } else { - handler = JSONRPC(pipe) + handler = newStoppableHandler(JSONRPC(pipe), l.stop) } go http.Serve(l, handler) @@ -45,6 +53,15 @@ func Start(pipe *xeth.XEth, config RpcConfig) error { return nil } +func Stop() error { + if rpclistener != nil { + rpclistener.Stop() + rpclistener = nil + } + + return nil +} + // JSONRPC returns a handler that implements the Ethereum JSON-RPC API. func JSONRPC(pipe *xeth.XEth) http.Handler { api := NewEthereumApi(pipe) diff --git a/rpc/responses.go b/rpc/responses.go index 5d1be8f34..884b7e69b 100644 --- a/rpc/responses.go +++ b/rpc/responses.go @@ -24,7 +24,6 @@ type BlockRes struct { Size *hexnum `json:"size"` ExtraData *hexdata `json:"extraData"` GasLimit *hexnum `json:"gasLimit"` - MinGasPrice *hexnum `json:"minGasPrice"` GasUsed *hexnum `json:"gasUsed"` UnixTimestamp *hexnum `json:"timestamp"` Transactions []*TransactionRes `json:"transactions"` @@ -48,7 +47,6 @@ func (b *BlockRes) MarshalJSON() ([]byte, error) { Size *hexnum `json:"size"` ExtraData *hexdata `json:"extraData"` GasLimit *hexnum `json:"gasLimit"` - MinGasPrice *hexnum `json:"minGasPrice"` GasUsed *hexnum `json:"gasUsed"` UnixTimestamp *hexnum `json:"timestamp"` Transactions []*TransactionRes `json:"transactions"` @@ -69,7 +67,6 @@ func (b *BlockRes) MarshalJSON() ([]byte, error) { ext.Size = b.Size ext.ExtraData = b.ExtraData ext.GasLimit = b.GasLimit - ext.MinGasPrice = b.MinGasPrice ext.GasUsed = b.GasUsed ext.UnixTimestamp = b.UnixTimestamp ext.Transactions = b.Transactions @@ -94,7 +91,6 @@ func (b *BlockRes) MarshalJSON() ([]byte, error) { Size *hexnum `json:"size"` ExtraData *hexdata `json:"extraData"` GasLimit *hexnum `json:"gasLimit"` - MinGasPrice *hexnum `json:"minGasPrice"` GasUsed *hexnum `json:"gasUsed"` UnixTimestamp *hexnum `json:"timestamp"` Transactions []*hexdata `json:"transactions"` @@ -115,7 +111,6 @@ func (b *BlockRes) MarshalJSON() ([]byte, error) { ext.Size = b.Size ext.ExtraData = b.ExtraData ext.GasLimit = b.GasLimit - ext.MinGasPrice = b.MinGasPrice ext.GasUsed = b.GasUsed ext.UnixTimestamp = b.UnixTimestamp ext.Transactions = make([]*hexdata, len(b.Transactions)) @@ -151,7 +146,6 @@ func NewBlockRes(block *types.Block, fullTx bool) *BlockRes { res.Size = newHexNum(block.Size().Int64()) res.ExtraData = newHexData(block.Header().Extra) res.GasLimit = newHexNum(block.GasLimit()) - // res.MinGasPrice = res.GasUsed = newHexNum(block.GasUsed()) res.UnixTimestamp = newHexNum(block.Time()) diff --git a/rpc/types.go b/rpc/types.go index bc9a46ed5..1784759a4 100644 --- a/rpc/types.go +++ b/rpc/types.go @@ -23,6 +23,13 @@ import ( "math/big" "strings" + "errors" + "net" + "net/http" + "time" + + "io" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" ) @@ -257,3 +264,95 @@ type RpcErrorObject struct { Message string `json:"message"` // Data interface{} `json:"data"` } + +type listenerHasStoppedError struct { + msg string +} + +func (self listenerHasStoppedError) Error() string { + return self.msg +} + +var listenerStoppedError = listenerHasStoppedError{"Listener stopped"} + +// When https://github.com/golang/go/issues/4674 is fixed this could be replaced +type stoppableTCPListener struct { + *net.TCPListener + stop chan struct{} // closed when the listener must stop +} + +// Wraps the default handler and checks if the RPC service was stopped. In that case it returns an +// error indicating that the service was stopped. This will only happen for connections which are +// kept open (HTTP keep-alive) when the RPC service was shutdown. +func newStoppableHandler(h http.Handler, stop chan struct{}) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + select { + case <-stop: + w.Header().Set("Content-Type", "application/json") + jsonerr := &RpcErrorObject{-32603, "RPC service stopped"} + send(w, &RpcErrorResponse{Jsonrpc: jsonrpcver, Id: nil, Error: jsonerr}) + default: + h.ServeHTTP(w, r) + } + }) +} + +// Stop the listener and all accepted and still active connections. +func (self *stoppableTCPListener) Stop() { + close(self.stop) +} + +func newStoppableTCPListener(addr string) (*stoppableTCPListener, error) { + wl, err := net.Listen("tcp", addr) + if err != nil { + return nil, err + } + + if tcpl, ok := wl.(*net.TCPListener); ok { + stop := make(chan struct{}) + l := &stoppableTCPListener{tcpl, stop} + return l, nil + } + + return nil, errors.New("Unable to create TCP listener for RPC service") +} + +func (self *stoppableTCPListener) Accept() (net.Conn, error) { + for { + self.SetDeadline(time.Now().Add(time.Duration(1 * time.Second))) + c, err := self.TCPListener.AcceptTCP() + + select { + case <-self.stop: + if c != nil { // accept timeout + c.Close() + } + self.TCPListener.Close() + return nil, listenerStoppedError + default: + } + + if err != nil { + if netErr, ok := err.(net.Error); ok && netErr.Timeout() && netErr.Temporary() { + continue // regular timeout + } + } + + return &closableConnection{c, self.stop}, err + } +} + +type closableConnection struct { + *net.TCPConn + closed chan struct{} +} + +func (self *closableConnection) Read(b []byte) (n int, err error) { + select { + case <-self.closed: + self.TCPConn.Close() + return 0, io.EOF + default: + return self.TCPConn.Read(b) + } +} diff --git a/xeth/frontend.go b/xeth/frontend.go index 8deb5c98c..fe1d57c50 100644 --- a/xeth/frontend.go +++ b/xeth/frontend.go @@ -1,9 +1,5 @@ package xeth -import ( - "github.com/ethereum/go-ethereum/core/types" -) - // Frontend should be implemented by users of XEth. Its methods are // called whenever XEth makes a decision that requires user input. type Frontend interface { @@ -21,12 +17,12 @@ type Frontend interface { // // ConfirmTransaction is not used for Call transactions // because they cannot change any state. - ConfirmTransaction(tx *types.Transaction) bool + ConfirmTransaction(tx string) bool } // dummyFrontend is a non-interactive frontend that allows all // transactions but cannot not unlock any keys. type dummyFrontend struct{} -func (dummyFrontend) UnlockAccount([]byte) bool { return false } -func (dummyFrontend) ConfirmTransaction(*types.Transaction) bool { return true } +func (dummyFrontend) UnlockAccount([]byte) bool { return false } +func (dummyFrontend) ConfirmTransaction(string) bool { return true } diff --git a/xeth/xeth.go b/xeth/xeth.go index c1a2ec283..afcb33e4c 100644 --- a/xeth/xeth.go +++ b/xeth/xeth.go @@ -148,10 +148,10 @@ func (self *XEth) AtStateNum(num int64) *XEth { } } - return self.withState(st) + return self.WithState(st) } -func (self *XEth) withState(statedb *state.StateDB) *XEth { +func (self *XEth) WithState(statedb *state.StateDB) *XEth { xeth := &XEth{ backend: self.backend, } @@ -608,6 +608,12 @@ func (self *XEth) Call(fromStr, toStr, valueStr, gasStr, gasPriceStr, dataStr st return common.ToHex(res), err } +func (self *XEth) ConfirmTransaction(tx string) bool { + + return self.frontend.ConfirmTransaction(tx) + +} + func (self *XEth) Transact(fromStr, toStr, valueStr, gasStr, gasPriceStr, codeStr string) (string, error) { var ( from = common.HexToAddress(fromStr) |