aboutsummaryrefslogtreecommitdiffstats
path: root/dex
diff options
context:
space:
mode:
authorWei-Ning Huang <w@cobinhood.com>2018-10-12 13:01:15 +0800
committerWei-Ning Huang <w@dexon.org>2019-04-09 21:32:49 +0800
commitefa16f95fce18659532cf650e4ce0b46ec9f9245 (patch)
tree8deaf1be29c0a3d839940f1cde1c7409953397a5 /dex
parent47b4be274b72cecfd653702413d4cae0f97f710e (diff)
downloaddexon-efa16f95fce18659532cf650e4ce0b46ec9f9245.tar.gz
dexon-efa16f95fce18659532cf650e4ce0b46ec9f9245.tar.zst
dexon-efa16f95fce18659532cf650e4ce0b46ec9f9245.zip
dex: add api_backend.go and it's dependencies
Diffstat (limited to 'dex')
-rw-r--r--dex/api_backend.go221
-rw-r--r--dex/backend.go15
-rw-r--r--dex/bloombits.go138
-rw-r--r--dex/config.go5
-rw-r--r--dex/gasprice/gasprice.go189
5 files changed, 566 insertions, 2 deletions
diff --git a/dex/api_backend.go b/dex/api_backend.go
new file mode 100644
index 000000000..151a5d7da
--- /dev/null
+++ b/dex/api_backend.go
@@ -0,0 +1,221 @@
+// Copyright 2015 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library 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.
+//
+// The go-ethereum library 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 the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
+
+package dex
+
+import (
+ "context"
+ "math/big"
+
+ "github.com/dexon-foundation/dexon/accounts"
+ "github.com/dexon-foundation/dexon/common"
+ "github.com/dexon-foundation/dexon/common/math"
+ "github.com/dexon-foundation/dexon/core"
+ "github.com/dexon-foundation/dexon/core/bloombits"
+ "github.com/dexon-foundation/dexon/core/rawdb"
+ "github.com/dexon-foundation/dexon/core/state"
+ "github.com/dexon-foundation/dexon/core/types"
+ "github.com/dexon-foundation/dexon/core/vm"
+ "github.com/dexon-foundation/dexon/eth/gasprice"
+
+ "github.com/dexon-foundation/dexon/ethdb"
+ "github.com/dexon-foundation/dexon/event"
+ "github.com/dexon-foundation/dexon/params"
+ "github.com/dexon-foundation/dexon/rpc"
+)
+
+// DexAPIBackend implements ethapi.Backend for full nodes
+type DexAPIBackend struct {
+ dex *Dexon
+ gpo *gasprice.Oracle
+}
+
+// ChainConfig returns the active chain configuration.
+func (b *DexAPIBackend) ChainConfig() *params.ChainConfig {
+ return b.dex.chainConfig
+}
+
+func (b *DexAPIBackend) CurrentBlock() *types.Block {
+ return b.dex.blockchain.CurrentBlock()
+}
+
+func (b *DexAPIBackend) SetHead(number uint64) {
+ b.dex.protocolManager.downloader.Cancel()
+ b.dex.blockchain.SetHead(number)
+}
+
+func (b *DexAPIBackend) HeaderByNumber(ctx context.Context, blockNr rpc.BlockNumber) (*types.Header, error) {
+ // Otherwise resolve and return the block
+ if blockNr == rpc.LatestBlockNumber {
+ return b.dex.blockchain.CurrentBlock().Header(), nil
+ }
+ return b.dex.blockchain.GetHeaderByNumber(uint64(blockNr)), nil
+}
+
+func (b *DexAPIBackend) HeaderByHash(ctx context.Context, hash common.Hash) (*types.Header, error) {
+ return b.dex.blockchain.GetHeaderByHash(hash), nil
+}
+
+func (b *DexAPIBackend) BlockByNumber(ctx context.Context, blockNr rpc.BlockNumber) (*types.Block, error) {
+ // Otherwise resolve and return the block
+ if blockNr == rpc.LatestBlockNumber {
+ return b.dex.blockchain.CurrentBlock(), nil
+ }
+ return b.dex.blockchain.GetBlockByNumber(uint64(blockNr)), nil
+}
+
+func (b *DexAPIBackend) StateAndHeaderByNumber(ctx context.Context, blockNr rpc.BlockNumber) (*state.StateDB, *types.Header, error) {
+ header, err := b.HeaderByNumber(ctx, blockNr)
+ if header == nil || err != nil {
+ return nil, nil, err
+ }
+ stateDb, err := b.dex.BlockChain().StateAt(header.Root)
+ return stateDb, header, err
+}
+
+func (b *DexAPIBackend) GetBlock(ctx context.Context, hash common.Hash) (*types.Block, error) {
+ return b.dex.blockchain.GetBlockByHash(hash), nil
+}
+
+func (b *DexAPIBackend) GetReceipts(ctx context.Context, hash common.Hash) (types.Receipts, error) {
+ if number := rawdb.ReadHeaderNumber(b.dex.chainDb, hash); number != nil {
+ return rawdb.ReadReceipts(b.dex.chainDb, hash, *number), nil
+ }
+ return nil, nil
+}
+
+func (b *DexAPIBackend) GetLogs(ctx context.Context, hash common.Hash) ([][]*types.Log, error) {
+ number := rawdb.ReadHeaderNumber(b.dex.chainDb, hash)
+ if number == nil {
+ return nil, nil
+ }
+ receipts := rawdb.ReadReceipts(b.dex.chainDb, hash, *number)
+ if receipts == nil {
+ return nil, nil
+ }
+ logs := make([][]*types.Log, len(receipts))
+ for i, receipt := range receipts {
+ logs[i] = receipt.Logs
+ }
+ return logs, nil
+}
+
+func (b *DexAPIBackend) GetTd(blockHash common.Hash) *big.Int {
+ return b.dex.blockchain.GetTdByHash(blockHash)
+}
+
+func (b *DexAPIBackend) GetEVM(ctx context.Context, msg core.Message, state *state.StateDB, header *types.Header) (*vm.EVM, func() error, error) {
+ state.SetBalance(msg.From(), math.MaxBig256)
+ vmError := func() error { return nil }
+
+ context := core.NewEVMContext(msg, header, b.dex.BlockChain(), nil)
+ return vm.NewEVM(context, state, b.dex.chainConfig, *b.dex.blockchain.GetVMConfig()), vmError, nil
+}
+
+func (b *DexAPIBackend) SubscribeRemovedLogsEvent(ch chan<- core.RemovedLogsEvent) event.Subscription {
+ return b.dex.BlockChain().SubscribeRemovedLogsEvent(ch)
+}
+
+func (b *DexAPIBackend) SubscribeChainEvent(ch chan<- core.ChainEvent) event.Subscription {
+ return b.dex.BlockChain().SubscribeChainEvent(ch)
+}
+
+func (b *DexAPIBackend) SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) event.Subscription {
+ return b.dex.BlockChain().SubscribeChainHeadEvent(ch)
+}
+
+func (b *DexAPIBackend) SubscribeChainSideEvent(ch chan<- core.ChainSideEvent) event.Subscription {
+ return b.dex.BlockChain().SubscribeChainSideEvent(ch)
+}
+
+func (b *DexAPIBackend) SubscribeLogsEvent(ch chan<- []*types.Log) event.Subscription {
+ return b.dex.BlockChain().SubscribeLogsEvent(ch)
+}
+
+func (b *DexAPIBackend) SendTx(ctx context.Context, signedTx *types.Transaction) error {
+ return b.dex.txPool.AddLocal(signedTx)
+}
+
+func (b *DexAPIBackend) GetPoolTransactions() (types.Transactions, error) {
+ pending, err := b.dex.txPool.Pending()
+ if err != nil {
+ return nil, err
+ }
+ var txs types.Transactions
+ for _, batch := range pending {
+ txs = append(txs, batch...)
+ }
+ return txs, nil
+}
+
+func (b *DexAPIBackend) GetPoolTransaction(hash common.Hash) *types.Transaction {
+ return b.dex.txPool.Get(hash)
+}
+
+func (b *DexAPIBackend) GetPoolNonce(ctx context.Context, addr common.Address) (uint64, error) {
+ return b.dex.txPool.State().GetNonce(addr), nil
+}
+
+func (b *DexAPIBackend) Stats() (pending int, queued int) {
+ return b.dex.txPool.Stats()
+}
+
+func (b *DexAPIBackend) TxPoolContent() (map[common.Address]types.Transactions, map[common.Address]types.Transactions) {
+ return b.dex.TxPool().Content()
+}
+
+func (b *DexAPIBackend) SubscribeNewTxsEvent(ch chan<- core.NewTxsEvent) event.Subscription {
+ return b.dex.TxPool().SubscribeNewTxsEvent(ch)
+}
+
+//func (b *DexAPIBackend) Downloader() *downloader.Downloader {
+// return b.dex.Downloader()
+//}
+
+func (b *DexAPIBackend) ProtocolVersion() int {
+ return b.dex.DexVersion()
+}
+
+func (b *DexAPIBackend) SuggestPrice(ctx context.Context) (*big.Int, error) {
+ return b.gpo.SuggestPrice(ctx)
+}
+
+func (b *DexAPIBackend) ChainDb() ethdb.Database {
+ return b.dex.ChainDb()
+}
+
+func (b *DexAPIBackend) EventMux() *event.TypeMux {
+ return b.dex.EventMux()
+}
+
+func (b *DexAPIBackend) AccountManager() *accounts.Manager {
+ return b.dex.AccountManager()
+}
+
+func (b *DexAPIBackend) RPCGasCap() *big.Int {
+ return b.dex.config.RPCGasCap
+}
+
+func (b *DexAPIBackend) BloomStatus() (uint64, uint64) {
+ sections, _, _ := b.dex.bloomIndexer.Sections()
+ return params.BloomBitsBlocks, sections
+}
+
+func (b *DexAPIBackend) ServiceFilter(ctx context.Context, session *bloombits.MatcherSession) {
+ for i := 0; i < bloomFilterThreads; i++ {
+ go session.Multiplex(bloomRetrievalBatch, bloomRetrievalWait, b.dex.bloomRequests)
+ }
+}
diff --git a/dex/backend.go b/dex/backend.go
index b74636de4..3b7fde400 100644
--- a/dex/backend.go
+++ b/dex/backend.go
@@ -48,8 +48,11 @@ type Dexon struct {
// Channel for shutting down the service
shutdownChan chan bool // Channel for shutting down the Ethereum
- txPool *core.TxPool
- blockchain *core.BlockChain
+
+ // Handlers
+ txPool *core.TxPool
+ blockchain *core.BlockChain
+ protocolManager *ProtocolManager
// DB interfaces
chainDb ethdb.Database // Block chain database
@@ -176,3 +179,11 @@ func CreateDB(ctx *node.ServiceContext, config *Config, name string) (ethdb.Data
}
return db, nil
}
+
+func (d *Dexon) AccountManager() *accounts.Manager { return d.accountManager }
+func (d *Dexon) BlockChain() *core.BlockChain { return d.blockchain }
+func (d *Dexon) TxPool() *core.TxPool { return d.txPool }
+func (d *Dexon) DexVersion() int { return int(d.protocolManager.SubProtocols[0].Version) }
+func (d *Dexon) EventMux() *event.TypeMux { return d.eventMux }
+func (d *Dexon) Engine() consensus.Engine { return d.engine }
+func (d *Dexon) ChainDb() ethdb.Database { return d.chainDb }
diff --git a/dex/bloombits.go b/dex/bloombits.go
new file mode 100644
index 000000000..827cae5f4
--- /dev/null
+++ b/dex/bloombits.go
@@ -0,0 +1,138 @@
+// Copyright 2017 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library 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.
+//
+// The go-ethereum library 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 the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
+
+package dex
+
+import (
+ "context"
+ "time"
+
+ "github.com/dexon-foundation/dexon/common"
+ "github.com/dexon-foundation/dexon/common/bitutil"
+ "github.com/dexon-foundation/dexon/core"
+ "github.com/dexon-foundation/dexon/core/bloombits"
+ "github.com/dexon-foundation/dexon/core/rawdb"
+ "github.com/dexon-foundation/dexon/core/types"
+ "github.com/dexon-foundation/dexon/ethdb"
+)
+
+const (
+ // bloomServiceThreads is the number of goroutines used globally by an Ethereum
+ // instance to service bloombits lookups for all running filters.
+ bloomServiceThreads = 16
+
+ // bloomFilterThreads is the number of goroutines used locally per filter to
+ // multiplex requests onto the global servicing goroutines.
+ bloomFilterThreads = 3
+
+ // bloomRetrievalBatch is the maximum number of bloom bit retrievals to service
+ // in a single batch.
+ bloomRetrievalBatch = 16
+
+ // bloomRetrievalWait is the maximum time to wait for enough bloom bit requests
+ // to accumulate request an entire batch (avoiding hysteresis).
+ bloomRetrievalWait = time.Duration(0)
+)
+
+// startBloomHandlers starts a batch of goroutines to accept bloom bit database
+// retrievals from possibly a range of filters and serving the data to satisfy.
+func (dex *Dexon) startBloomHandlers(sectionSize uint64) {
+ for i := 0; i < bloomServiceThreads; i++ {
+ go func() {
+ for {
+ select {
+ case <-dex.shutdownChan:
+ return
+
+ case request := <-dex.bloomRequests:
+ task := <-request
+ task.Bitsets = make([][]byte, len(task.Sections))
+ for i, section := range task.Sections {
+ head := rawdb.ReadCanonicalHash(dex.chainDb, (section+1)*sectionSize-1)
+ if compVector, err := rawdb.ReadBloomBits(dex.chainDb, task.Bit, section, head); err == nil {
+ if blob, err := bitutil.DecompressBytes(compVector, int(sectionSize/8)); err == nil {
+ task.Bitsets[i] = blob
+ } else {
+ task.Error = err
+ }
+ } else {
+ task.Error = err
+ }
+ }
+ request <- task
+ }
+ }
+ }()
+ }
+}
+
+const (
+ // bloomThrottling is the time to wait between processing two consecutive index
+ // sections. It's useful during chain upgrades to prevent disk overload.
+ bloomThrottling = 100 * time.Millisecond
+)
+
+// BloomIndexer implements a core.ChainIndexer, building up a rotated bloom bits index
+// for the Ethereum header bloom filters, permitting blazing fast filtering.
+type BloomIndexer struct {
+ size uint64 // section size to generate bloombits for
+ db ethdb.Database // database instance to write index data and metadata into
+ gen *bloombits.Generator // generator to rotate the bloom bits crating the bloom index
+ section uint64 // Section is the section number being processed currently
+ head common.Hash // Head is the hash of the last header processed
+}
+
+// NewBloomIndexer returns a chain indexer that generates bloom bits data for the
+// canonical chain for fast logs filtering.
+func NewBloomIndexer(db ethdb.Database, size, confirms uint64) *core.ChainIndexer {
+ backend := &BloomIndexer{
+ db: db,
+ size: size,
+ }
+ table := ethdb.NewTable(db, string(rawdb.BloomBitsIndexPrefix))
+
+ return core.NewChainIndexer(db, table, backend, size, confirms, bloomThrottling, "bloombits")
+}
+
+// Reset implements core.ChainIndexerBackend, starting a new bloombits index
+// section.
+func (b *BloomIndexer) Reset(ctx context.Context, section uint64, lastSectionHead common.Hash) error {
+ gen, err := bloombits.NewGenerator(uint(b.size))
+ b.gen, b.section, b.head = gen, section, common.Hash{}
+ return err
+}
+
+// Process implements core.ChainIndexerBackend, adding a new header's bloom into
+// the index.
+func (b *BloomIndexer) Process(ctx context.Context, header *types.Header) error {
+ b.gen.AddBloom(uint(header.Number.Uint64()-b.section*b.size), header.Bloom)
+ b.head = header.Hash()
+ return nil
+}
+
+// Commit implements core.ChainIndexerBackend, finalizing the bloom section and
+// writing it out into the database.
+func (b *BloomIndexer) Commit() error {
+ batch := b.db.NewBatch()
+ for i := 0; i < types.BloomBitLength; i++ {
+ bits, err := b.gen.Bitset(uint(i))
+ if err != nil {
+ return err
+ }
+ rawdb.WriteBloomBits(batch, uint(i), b.section, b.head, bitutil.CompressBytes(bits))
+ }
+ return batch.Write()
+}
diff --git a/dex/config.go b/dex/config.go
index 5a43496ab..75afb4b7f 100644
--- a/dex/config.go
+++ b/dex/config.go
@@ -17,6 +17,7 @@
package dex
import (
+ "math/big"
"os"
"os/user"
"path/filepath"
@@ -112,6 +113,10 @@ type Config struct {
// Type of the EWASM interpreter ("" for detault)
EWASMInterpreter string
+
// Type of the EVM interpreter ("" for default)
EVMInterpreter string
+
+ // RPCGasCap is the global gas cap for eth-call variants.
+ RPCGasCap *big.Int `toml:",omitempty"`
}
diff --git a/dex/gasprice/gasprice.go b/dex/gasprice/gasprice.go
new file mode 100644
index 000000000..9af33c682
--- /dev/null
+++ b/dex/gasprice/gasprice.go
@@ -0,0 +1,189 @@
+// Copyright 2015 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library 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.
+//
+// The go-ethereum library 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 the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
+
+package gasprice
+
+import (
+ "context"
+ "math/big"
+ "sort"
+ "sync"
+
+ "github.com/dexon-foundation/dexon/common"
+ "github.com/dexon-foundation/dexon/core/types"
+ "github.com/dexon-foundation/dexon/internal/ethapi"
+ "github.com/dexon-foundation/dexon/params"
+ "github.com/dexon-foundation/dexon/rpc"
+)
+
+var maxPrice = big.NewInt(500 * params.GWei)
+
+type Config struct {
+ Blocks int
+ Percentile int
+ Default *big.Int `toml:",omitempty"`
+}
+
+// Oracle recommends gas prices based on the content of recent
+// blocks. Suitable for both light and full clients.
+type Oracle struct {
+ backend ethapi.Backend
+ lastHead common.Hash
+ lastPrice *big.Int
+ cacheLock sync.RWMutex
+ fetchLock sync.Mutex
+
+ checkBlocks, maxEmpty, maxBlocks int
+ percentile int
+}
+
+// NewOracle returns a new oracle.
+func NewOracle(backend ethapi.Backend, params Config) *Oracle {
+ blocks := params.Blocks
+ if blocks < 1 {
+ blocks = 1
+ }
+ percent := params.Percentile
+ if percent < 0 {
+ percent = 0
+ }
+ if percent > 100 {
+ percent = 100
+ }
+ return &Oracle{
+ backend: backend,
+ lastPrice: params.Default,
+ checkBlocks: blocks,
+ maxEmpty: blocks / 2,
+ maxBlocks: blocks * 5,
+ percentile: percent,
+ }
+}
+
+// SuggestPrice returns the recommended gas price.
+func (gpo *Oracle) SuggestPrice(ctx context.Context) (*big.Int, error) {
+ gpo.cacheLock.RLock()
+ lastHead := gpo.lastHead
+ lastPrice := gpo.lastPrice
+ gpo.cacheLock.RUnlock()
+
+ head, _ := gpo.backend.HeaderByNumber(ctx, rpc.LatestBlockNumber)
+ headHash := head.Hash()
+ if headHash == lastHead {
+ return lastPrice, nil
+ }
+
+ gpo.fetchLock.Lock()
+ defer gpo.fetchLock.Unlock()
+
+ // try checking the cache again, maybe the last fetch fetched what we need
+ gpo.cacheLock.RLock()
+ lastHead = gpo.lastHead
+ lastPrice = gpo.lastPrice
+ gpo.cacheLock.RUnlock()
+ if headHash == lastHead {
+ return lastPrice, nil
+ }
+
+ blockNum := head.Number.Uint64()
+ ch := make(chan getBlockPricesResult, gpo.checkBlocks)
+ sent := 0
+ exp := 0
+ var blockPrices []*big.Int
+ for sent < gpo.checkBlocks && blockNum > 0 {
+ go gpo.getBlockPrices(ctx, types.MakeSigner(gpo.backend.ChainConfig(), big.NewInt(int64(blockNum))), blockNum, ch)
+ sent++
+ exp++
+ blockNum--
+ }
+ maxEmpty := gpo.maxEmpty
+ for exp > 0 {
+ res := <-ch
+ if res.err != nil {
+ return lastPrice, res.err
+ }
+ exp--
+ if res.price != nil {
+ blockPrices = append(blockPrices, res.price)
+ continue
+ }
+ if maxEmpty > 0 {
+ maxEmpty--
+ continue
+ }
+ if blockNum > 0 && sent < gpo.maxBlocks {
+ go gpo.getBlockPrices(ctx, types.MakeSigner(gpo.backend.ChainConfig(), big.NewInt(int64(blockNum))), blockNum, ch)
+ sent++
+ exp++
+ blockNum--
+ }
+ }
+ price := lastPrice
+ if len(blockPrices) > 0 {
+ sort.Sort(bigIntArray(blockPrices))
+ price = blockPrices[(len(blockPrices)-1)*gpo.percentile/100]
+ }
+ if price.Cmp(maxPrice) > 0 {
+ price = new(big.Int).Set(maxPrice)
+ }
+
+ gpo.cacheLock.Lock()
+ gpo.lastHead = headHash
+ gpo.lastPrice = price
+ gpo.cacheLock.Unlock()
+ return price, nil
+}
+
+type getBlockPricesResult struct {
+ price *big.Int
+ err error
+}
+
+type transactionsByGasPrice []*types.Transaction
+
+func (t transactionsByGasPrice) Len() int { return len(t) }
+func (t transactionsByGasPrice) Swap(i, j int) { t[i], t[j] = t[j], t[i] }
+func (t transactionsByGasPrice) Less(i, j int) bool { return t[i].GasPrice().Cmp(t[j].GasPrice()) < 0 }
+
+// getBlockPrices calculates the lowest transaction gas price in a given block
+// and sends it to the result channel. If the block is empty, price is nil.
+func (gpo *Oracle) getBlockPrices(ctx context.Context, signer types.Signer, blockNum uint64, ch chan getBlockPricesResult) {
+ block, err := gpo.backend.BlockByNumber(ctx, rpc.BlockNumber(blockNum))
+ if block == nil {
+ ch <- getBlockPricesResult{nil, err}
+ return
+ }
+
+ blockTxs := block.Transactions()
+ txs := make([]*types.Transaction, len(blockTxs))
+ copy(txs, blockTxs)
+ sort.Sort(transactionsByGasPrice(txs))
+
+ for _, tx := range txs {
+ sender, err := types.Sender(signer, tx)
+ if err == nil && sender != block.Coinbase() {
+ ch <- getBlockPricesResult{tx.GasPrice(), nil}
+ return
+ }
+ }
+ ch <- getBlockPricesResult{nil, nil}
+}
+
+type bigIntArray []*big.Int
+
+func (s bigIntArray) Len() int { return len(s) }
+func (s bigIntArray) Less(i, j int) bool { return s[i].Cmp(s[j]) < 0 }
+func (s bigIntArray) Swap(i, j int) { s[i], s[j] = s[j], s[i] }