diff options
author | Wei-Ning Huang <w@cobinhood.com> | 2018-10-12 13:01:15 +0800 |
---|---|---|
committer | Wei-Ning Huang <w@dexon.org> | 2019-03-12 12:19:09 +0800 |
commit | 085aac156c7bc415466bd11a3e75eb546ac35466 (patch) | |
tree | 94ab15c68c839a3929af9c39d6aaed7bf3191d00 /dex | |
parent | ba01972f09a63ad5bfbb81367be907b89e1b5102 (diff) | |
download | dexon-085aac156c7bc415466bd11a3e75eb546ac35466.tar.gz dexon-085aac156c7bc415466bd11a3e75eb546ac35466.tar.zst dexon-085aac156c7bc415466bd11a3e75eb546ac35466.zip |
dex: add api_backend.go and it's dependencies
Diffstat (limited to 'dex')
-rw-r--r-- | dex/api_backend.go | 217 | ||||
-rw-r--r-- | dex/backend.go | 15 | ||||
-rw-r--r-- | dex/bloombits.go | 138 | ||||
-rw-r--r-- | dex/gasprice/gasprice.go | 189 |
4 files changed, 557 insertions, 2 deletions
diff --git a/dex/api_backend.go b/dex/api_backend.go new file mode 100644 index 000000000..8a147aab2 --- /dev/null +++ b/dex/api_backend.go @@ -0,0 +1,217 @@ +// 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) 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/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] } |