diff options
34 files changed, 1316 insertions, 376 deletions
diff --git a/cmd/geth/blocktestcmd.go b/cmd/geth/blocktestcmd.go index 4eff82e3d..a667cfd60 100644 --- a/cmd/geth/blocktestcmd.go +++ b/cmd/geth/blocktestcmd.go @@ -22,7 +22,6 @@ import ( "github.com/codegangsta/cli" "github.com/ethereum/go-ethereum/cmd/utils" - "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/eth" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/tests" @@ -103,7 +102,7 @@ func runBlockTest(ctx *cli.Context) { func runOneBlockTest(ctx *cli.Context, test *tests.BlockTest) (*eth.Ethereum, error) { cfg := utils.MakeEthConfig(ClientIdentifier, Version, ctx) - cfg.NewDB = func(path string) (common.Database, error) { return ethdb.NewMemDatabase() } + cfg.NewDB = func(path string) (ethdb.Database, error) { return ethdb.NewMemDatabase() } cfg.MaxPeers = 0 // disable network cfg.Shh = false // disable whisper cfg.NAT = nil // disable port mapping diff --git a/cmd/geth/chaincmd.go b/cmd/geth/chaincmd.go index c42045918..c5bc4b66a 100644 --- a/cmd/geth/chaincmd.go +++ b/cmd/geth/chaincmd.go @@ -29,6 +29,7 @@ import ( "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/logger/glog" ) @@ -191,7 +192,7 @@ func hashish(x string) bool { return err != nil } -func closeAll(dbs ...common.Database) { +func closeAll(dbs ...ethdb.Database) { for _, db := range dbs { db.Close() } diff --git a/cmd/geth/js_test.go b/cmd/geth/js_test.go index 67c36dfe7..2fd5a531d 100644 --- a/cmd/geth/js_test.go +++ b/cmd/geth/js_test.go @@ -103,7 +103,7 @@ func testREPL(t *testing.T, config func(*eth.Config)) (string, *testjethre, *eth Name: "test", SolcPath: testSolcPath, PowTest: true, - NewDB: func(path string) (common.Database, error) { return db, nil }, + NewDB: func(path string) (ethdb.Database, error) { return db, nil }, } if config != nil { config(conf) diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 95fb649e6..b45ef0af2 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -508,7 +508,7 @@ func SetupEth(ctx *cli.Context) { } // MakeChain creates a chain manager from set command line flags. -func MakeChain(ctx *cli.Context) (chain *core.ChainManager, chainDb common.Database) { +func MakeChain(ctx *cli.Context) (chain *core.ChainManager, chainDb ethdb.Database) { datadir := ctx.GlobalString(DataDirFlag.Name) cache := ctx.GlobalInt(CacheFlag.Name) diff --git a/common/natspec/natspec_e2e_test.go b/common/natspec/natspec_e2e_test.go index fc8ca6af2..ea28b457e 100644 --- a/common/natspec/natspec_e2e_test.go +++ b/common/natspec/natspec_e2e_test.go @@ -143,7 +143,7 @@ func testEth(t *testing.T) (ethereum *eth.Ethereum, err error) { MaxPeers: 0, PowTest: true, Etherbase: common.HexToAddress(testAddress), - NewDB: func(path string) (common.Database, error) { return db, nil }, + NewDB: func(path string) (ethdb.Database, error) { return db, nil }, }) if err != nil { diff --git a/core/bench_test.go b/core/bench_test.go index baae8a7a5..d05b7d30b 100644 --- a/core/bench_test.go +++ b/core/bench_test.go @@ -144,7 +144,7 @@ func genUncles(i int, gen *BlockGen) { func benchInsertChain(b *testing.B, disk bool, gen func(int, *BlockGen)) { // Create the database in memory or in a temporary directory. - var db common.Database + var db ethdb.Database if !disk { db, _ = ethdb.NewMemDatabase() } else { diff --git a/core/block_processor.go b/core/block_processor.go index b480948a0..238b2db95 100644 --- a/core/block_processor.go +++ b/core/block_processor.go @@ -26,6 +26,7 @@ import ( "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/logger" "github.com/ethereum/go-ethereum/logger/glog" @@ -41,7 +42,7 @@ const ( ) type BlockProcessor struct { - chainDb common.Database + chainDb ethdb.Database // Mutex for locking the block processor. Blocks can only be handled one at a time mutex sync.Mutex // Canonical block chain @@ -68,7 +69,7 @@ type GasPool interface { SubGas(gas, price *big.Int) error } -func NewBlockProcessor(db common.Database, pow pow.PoW, chainManager *ChainManager, eventMux *event.TypeMux) *BlockProcessor { +func NewBlockProcessor(db ethdb.Database, pow pow.PoW, chainManager *ChainManager, eventMux *event.TypeMux) *BlockProcessor { sm := &BlockProcessor{ chainDb: db, mem: make(map[string]*big.Int), diff --git a/core/chain_makers.go b/core/chain_makers.go index f89218f82..d3b7c42b6 100644 --- a/core/chain_makers.go +++ b/core/chain_makers.go @@ -22,6 +22,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/ethdb" "github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/pow" ) @@ -142,7 +143,7 @@ func (b *BlockGen) PrevBlock(index int) *types.Block { // Blocks created by GenerateChain do not contain valid proof of work // values. Inserting them into ChainManager requires use of FakePow or // a similar non-validating proof of work implementation. -func GenerateChain(parent *types.Block, db common.Database, n int, gen func(int, *BlockGen)) []*types.Block { +func GenerateChain(parent *types.Block, db ethdb.Database, n int, gen func(int, *BlockGen)) []*types.Block { statedb := state.New(parent.Root(), db) blocks := make(types.Blocks, n) genblock := func(i int, h *types.Header) *types.Block { @@ -185,7 +186,7 @@ func makeHeader(parent *types.Block, state *state.StateDB) *types.Header { // newCanonical creates a new deterministic canonical chain by running // InsertChain on the result of makeChain. -func newCanonical(n int, db common.Database) (*BlockProcessor, error) { +func newCanonical(n int, db ethdb.Database) (*BlockProcessor, error) { evmux := &event.TypeMux{} WriteTestNetGenesisBlock(db, 0) @@ -201,7 +202,7 @@ func newCanonical(n int, db common.Database) (*BlockProcessor, error) { return bman, err } -func makeChain(parent *types.Block, n int, db common.Database, seed int) []*types.Block { +func makeChain(parent *types.Block, n int, db ethdb.Database, seed int) []*types.Block { return GenerateChain(parent, db, n, func(i int, b *BlockGen) { b.SetCoinbase(common.Address{0: byte(seed), 19: byte(i)}) }) diff --git a/core/chain_manager.go b/core/chain_manager.go index 407945f8e..1218b1a6e 100644 --- a/core/chain_manager.go +++ b/core/chain_manager.go @@ -30,6 +30,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/ethdb" "github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/logger" "github.com/ethereum/go-ethereum/logger/glog" @@ -60,7 +61,7 @@ const ( type ChainManager struct { //eth EthManager - chainDb common.Database + chainDb ethdb.Database processor types.BlockProcessor eventMux *event.TypeMux genesisBlock *types.Block @@ -90,7 +91,7 @@ type ChainManager struct { pow pow.PoW } -func NewChainManager(chainDb common.Database, pow pow.PoW, mux *event.TypeMux) (*ChainManager, error) { +func NewChainManager(chainDb ethdb.Database, pow pow.PoW, mux *event.TypeMux) (*ChainManager, error) { headerCache, _ := lru.New(headerCacheLimit) bodyCache, _ := lru.New(bodyCacheLimit) bodyRLPCache, _ := lru.New(bodyCacheLimit) diff --git a/core/chain_manager_test.go b/core/chain_manager_test.go index a20480de8..67ca41f00 100644 --- a/core/chain_manager_test.go +++ b/core/chain_manager_test.go @@ -46,7 +46,7 @@ func thePow() pow.PoW { return pow } -func theChainManager(db common.Database, t *testing.T) *ChainManager { +func theChainManager(db ethdb.Database, t *testing.T) *ChainManager { var eventMux event.TypeMux WriteTestNetGenesisBlock(db, 0) chainMan, err := NewChainManager(db, thePow(), &eventMux) @@ -380,7 +380,7 @@ func makeChainWithDiff(genesis *types.Block, d []int, seed byte) []*types.Block return chain } -func chm(genesis *types.Block, db common.Database) *ChainManager { +func chm(genesis *types.Block, db ethdb.Database) *ChainManager { var eventMux event.TypeMux bc := &ChainManager{chainDb: db, genesisBlock: genesis, eventMux: &eventMux, pow: FakePow{}} bc.headerCache, _ = lru.New(100) diff --git a/core/chain_util.go b/core/chain_util.go index 0e3fa31f9..33d94cebd 100644 --- a/core/chain_util.go +++ b/core/chain_util.go @@ -22,6 +22,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/logger" "github.com/ethereum/go-ethereum/logger/glog" "github.com/ethereum/go-ethereum/params" @@ -111,7 +112,7 @@ func CalcGasLimit(parent *types.Block) *big.Int { } // GetCanonicalHash retrieves a hash assigned to a canonical block number. -func GetCanonicalHash(db common.Database, number uint64) common.Hash { +func GetCanonicalHash(db ethdb.Database, number uint64) common.Hash { data, _ := db.Get(append(blockNumPrefix, big.NewInt(int64(number)).Bytes()...)) if len(data) == 0 { return common.Hash{} @@ -124,7 +125,7 @@ func GetCanonicalHash(db common.Database, number uint64) common.Hash { // last block hash is only updated upon a full block import, the last header // hash is updated already at header import, allowing head tracking for the // fast synchronization mechanism. -func GetHeadHeaderHash(db common.Database) common.Hash { +func GetHeadHeaderHash(db ethdb.Database) common.Hash { data, _ := db.Get(headHeaderKey) if len(data) == 0 { return common.Hash{} @@ -133,7 +134,7 @@ func GetHeadHeaderHash(db common.Database) common.Hash { } // GetHeadBlockHash retrieves the hash of the current canonical head block. -func GetHeadBlockHash(db common.Database) common.Hash { +func GetHeadBlockHash(db ethdb.Database) common.Hash { data, _ := db.Get(headBlockKey) if len(data) == 0 { return common.Hash{} @@ -143,14 +144,14 @@ func GetHeadBlockHash(db common.Database) common.Hash { // GetHeaderRLP retrieves a block header in its raw RLP database encoding, or nil // if the header's not found. -func GetHeaderRLP(db common.Database, hash common.Hash) rlp.RawValue { +func GetHeaderRLP(db ethdb.Database, hash common.Hash) rlp.RawValue { data, _ := db.Get(append(append(blockPrefix, hash[:]...), headerSuffix...)) return data } // GetHeader retrieves the block header corresponding to the hash, nil if none // found. -func GetHeader(db common.Database, hash common.Hash) *types.Header { +func GetHeader(db ethdb.Database, hash common.Hash) *types.Header { data := GetHeaderRLP(db, hash) if len(data) == 0 { return nil @@ -164,14 +165,14 @@ func GetHeader(db common.Database, hash common.Hash) *types.Header { } // GetBodyRLP retrieves the block body (transactions and uncles) in RLP encoding. -func GetBodyRLP(db common.Database, hash common.Hash) rlp.RawValue { +func GetBodyRLP(db ethdb.Database, hash common.Hash) rlp.RawValue { data, _ := db.Get(append(append(blockPrefix, hash[:]...), bodySuffix...)) return data } // GetBody retrieves the block body (transactons, uncles) corresponding to the // hash, nil if none found. -func GetBody(db common.Database, hash common.Hash) *types.Body { +func GetBody(db ethdb.Database, hash common.Hash) *types.Body { data := GetBodyRLP(db, hash) if len(data) == 0 { return nil @@ -186,7 +187,7 @@ func GetBody(db common.Database, hash common.Hash) *types.Body { // GetTd retrieves a block's total difficulty corresponding to the hash, nil if // none found. -func GetTd(db common.Database, hash common.Hash) *big.Int { +func GetTd(db ethdb.Database, hash common.Hash) *big.Int { data, _ := db.Get(append(append(blockPrefix, hash.Bytes()...), tdSuffix...)) if len(data) == 0 { return nil @@ -201,7 +202,7 @@ func GetTd(db common.Database, hash common.Hash) *big.Int { // GetBlock retrieves an entire block corresponding to the hash, assembling it // back from the stored header and body. -func GetBlock(db common.Database, hash common.Hash) *types.Block { +func GetBlock(db ethdb.Database, hash common.Hash) *types.Block { // Retrieve the block header and body contents header := GetHeader(db, hash) if header == nil { @@ -216,7 +217,7 @@ func GetBlock(db common.Database, hash common.Hash) *types.Block { } // WriteCanonicalHash stores the canonical hash for the given block number. -func WriteCanonicalHash(db common.Database, hash common.Hash, number uint64) error { +func WriteCanonicalHash(db ethdb.Database, hash common.Hash, number uint64) error { key := append(blockNumPrefix, big.NewInt(int64(number)).Bytes()...) if err := db.Put(key, hash.Bytes()); err != nil { glog.Fatalf("failed to store number to hash mapping into database: %v", err) @@ -226,7 +227,7 @@ func WriteCanonicalHash(db common.Database, hash common.Hash, number uint64) err } // WriteHeadHeaderHash stores the head header's hash. -func WriteHeadHeaderHash(db common.Database, hash common.Hash) error { +func WriteHeadHeaderHash(db ethdb.Database, hash common.Hash) error { if err := db.Put(headHeaderKey, hash.Bytes()); err != nil { glog.Fatalf("failed to store last header's hash into database: %v", err) return err @@ -235,7 +236,7 @@ func WriteHeadHeaderHash(db common.Database, hash common.Hash) error { } // WriteHeadBlockHash stores the head block's hash. -func WriteHeadBlockHash(db common.Database, hash common.Hash) error { +func WriteHeadBlockHash(db ethdb.Database, hash common.Hash) error { if err := db.Put(headBlockKey, hash.Bytes()); err != nil { glog.Fatalf("failed to store last block's hash into database: %v", err) return err @@ -244,7 +245,7 @@ func WriteHeadBlockHash(db common.Database, hash common.Hash) error { } // WriteHeader serializes a block header into the database. -func WriteHeader(db common.Database, header *types.Header) error { +func WriteHeader(db ethdb.Database, header *types.Header) error { data, err := rlp.EncodeToBytes(header) if err != nil { return err @@ -259,7 +260,7 @@ func WriteHeader(db common.Database, header *types.Header) error { } // WriteBody serializes the body of a block into the database. -func WriteBody(db common.Database, hash common.Hash, body *types.Body) error { +func WriteBody(db ethdb.Database, hash common.Hash, body *types.Body) error { data, err := rlp.EncodeToBytes(body) if err != nil { return err @@ -274,7 +275,7 @@ func WriteBody(db common.Database, hash common.Hash, body *types.Body) error { } // WriteTd serializes the total difficulty of a block into the database. -func WriteTd(db common.Database, hash common.Hash, td *big.Int) error { +func WriteTd(db ethdb.Database, hash common.Hash, td *big.Int) error { data, err := rlp.EncodeToBytes(td) if err != nil { return err @@ -289,7 +290,7 @@ func WriteTd(db common.Database, hash common.Hash, td *big.Int) error { } // WriteBlock serializes a block into the database, header and body separately. -func WriteBlock(db common.Database, block *types.Block) error { +func WriteBlock(db ethdb.Database, block *types.Block) error { // Store the body first to retain database consistency if err := WriteBody(db, block.Hash(), &types.Body{block.Transactions(), block.Uncles()}); err != nil { return err @@ -302,27 +303,27 @@ func WriteBlock(db common.Database, block *types.Block) error { } // DeleteCanonicalHash removes the number to hash canonical mapping. -func DeleteCanonicalHash(db common.Database, number uint64) { +func DeleteCanonicalHash(db ethdb.Database, number uint64) { db.Delete(append(blockNumPrefix, big.NewInt(int64(number)).Bytes()...)) } // DeleteHeader removes all block header data associated with a hash. -func DeleteHeader(db common.Database, hash common.Hash) { +func DeleteHeader(db ethdb.Database, hash common.Hash) { db.Delete(append(append(blockPrefix, hash.Bytes()...), headerSuffix...)) } // DeleteBody removes all block body data associated with a hash. -func DeleteBody(db common.Database, hash common.Hash) { +func DeleteBody(db ethdb.Database, hash common.Hash) { db.Delete(append(append(blockPrefix, hash.Bytes()...), bodySuffix...)) } // DeleteTd removes all block total difficulty data associated with a hash. -func DeleteTd(db common.Database, hash common.Hash) { +func DeleteTd(db ethdb.Database, hash common.Hash) { db.Delete(append(append(blockPrefix, hash.Bytes()...), tdSuffix...)) } // DeleteBlock removes all block data associated with a hash. -func DeleteBlock(db common.Database, hash common.Hash) { +func DeleteBlock(db ethdb.Database, hash common.Hash) { DeleteHeader(db, hash) DeleteBody(db, hash) DeleteTd(db, hash) @@ -333,7 +334,7 @@ func DeleteBlock(db common.Database, hash common.Hash) { // or nil if not found. This method is only used by the upgrade mechanism to // access the old combined block representation. It will be dropped after the // network transitions to eth/63. -func GetBlockByHashOld(db common.Database, hash common.Hash) *types.Block { +func GetBlockByHashOld(db ethdb.Database, hash common.Hash) *types.Block { data, _ := db.Get(append(blockHashPre, hash[:]...)) if len(data) == 0 { return nil diff --git a/core/genesis.go b/core/genesis.go index 3a8f0af0c..727e2c75f 100644 --- a/core/genesis.go +++ b/core/genesis.go @@ -27,13 +27,14 @@ 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/ethdb" "github.com/ethereum/go-ethereum/logger" "github.com/ethereum/go-ethereum/logger/glog" "github.com/ethereum/go-ethereum/params" ) // WriteGenesisBlock writes the genesis block to the database as block number 0 -func WriteGenesisBlock(chainDb common.Database, reader io.Reader) (*types.Block, error) { +func WriteGenesisBlock(chainDb ethdb.Database, reader io.Reader) (*types.Block, error) { contents, err := ioutil.ReadAll(reader) if err != nil { return nil, err @@ -110,7 +111,7 @@ func WriteGenesisBlock(chainDb common.Database, reader io.Reader) (*types.Block, // GenesisBlockForTesting creates a block in which addr has the given wei balance. // The state trie of the block is written to db. -func GenesisBlockForTesting(db common.Database, addr common.Address, balance *big.Int) *types.Block { +func GenesisBlockForTesting(db ethdb.Database, addr common.Address, balance *big.Int) *types.Block { statedb := state.New(common.Hash{}, db) obj := statedb.GetOrNewStateObject(addr) obj.SetBalance(balance) @@ -124,7 +125,7 @@ func GenesisBlockForTesting(db common.Database, addr common.Address, balance *bi return block } -func WriteGenesisBlockForTesting(db common.Database, addr common.Address, balance *big.Int) *types.Block { +func WriteGenesisBlockForTesting(db ethdb.Database, addr common.Address, balance *big.Int) *types.Block { testGenesis := fmt.Sprintf(`{ "nonce":"0x%x", "gasLimit":"0x%x", @@ -137,7 +138,7 @@ func WriteGenesisBlockForTesting(db common.Database, addr common.Address, balanc return block } -func WriteTestNetGenesisBlock(chainDb common.Database, nonce uint64) (*types.Block, error) { +func WriteTestNetGenesisBlock(chainDb ethdb.Database, nonce uint64) (*types.Block, error) { testGenesis := fmt.Sprintf(`{ "nonce":"0x%x", "gasLimit":"0x%x", diff --git a/core/helper_test.go b/core/helper_test.go index b21f31d7c..81ea6fc22 100644 --- a/core/helper_test.go +++ b/core/helper_test.go @@ -22,7 +22,7 @@ import ( "github.com/ethereum/go-ethereum/core/types" // "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/event" ) @@ -32,7 +32,7 @@ type TestManager struct { // stateManager *StateManager eventMux *event.TypeMux - db common.Database + db ethdb.Database txPool *TxPool blockChain *ChainManager Blocks []*types.Block @@ -74,7 +74,7 @@ func (tm *TestManager) EventMux() *event.TypeMux { // return nil // } -func (tm *TestManager) Db() common.Database { +func (tm *TestManager) Db() ethdb.Database { return tm.db } diff --git a/core/manager.go b/core/manager.go index 8b0401b03..0f108a6de 100644 --- a/core/manager.go +++ b/core/manager.go @@ -18,7 +18,7 @@ package core import ( "github.com/ethereum/go-ethereum/accounts" - "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/event" ) @@ -28,7 +28,7 @@ type Backend interface { BlockProcessor() *BlockProcessor ChainManager() *ChainManager TxPool() *TxPool - ChainDb() common.Database - DappDb() common.Database + ChainDb() ethdb.Database + DappDb() ethdb.Database EventMux() *event.TypeMux } diff --git a/core/state/state_object.go b/core/state/state_object.go index 69c64ae40..353f2357b 100644 --- a/core/state/state_object.go +++ b/core/state/state_object.go @@ -23,6 +23,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/logger" "github.com/ethereum/go-ethereum/logger/glog" "github.com/ethereum/go-ethereum/rlp" @@ -56,7 +57,7 @@ func (self Storage) Copy() Storage { type StateObject struct { // State database for storing state changes - db common.Database + db ethdb.Database trie *trie.SecureTrie // Address belonging to this account @@ -87,7 +88,7 @@ type StateObject struct { dirty bool } -func NewStateObject(address common.Address, db common.Database) *StateObject { +func NewStateObject(address common.Address, db ethdb.Database) *StateObject { object := &StateObject{db: db, address: address, balance: new(big.Int), gasPool: new(big.Int), dirty: true} object.trie = trie.NewSecure((common.Hash{}).Bytes(), db) object.storage = make(Storage) @@ -96,7 +97,7 @@ func NewStateObject(address common.Address, db common.Database) *StateObject { return object } -func NewStateObjectFromBytes(address common.Address, data []byte, db common.Database) *StateObject { +func NewStateObjectFromBytes(address common.Address, data []byte, db ethdb.Database) *StateObject { // TODO clean me up var extobject struct { Nonce uint64 diff --git a/core/state/statedb.go b/core/state/statedb.go index b754f0887..24f97e32a 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -21,6 +21,7 @@ import ( "math/big" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/logger" "github.com/ethereum/go-ethereum/logger/glog" "github.com/ethereum/go-ethereum/trie" @@ -32,7 +33,7 @@ import ( // * Contracts // * Accounts type StateDB struct { - db common.Database + db ethdb.Database trie *trie.SecureTrie root common.Hash @@ -47,7 +48,7 @@ type StateDB struct { } // Create a new state from a given trie -func New(root common.Hash, db common.Database) *StateDB { +func New(root common.Hash, db ethdb.Database) *StateDB { trie := trie.NewSecure(root[:], db) return &StateDB{root: root, db: db, trie: trie, stateObjects: make(map[string]*StateObject), refund: new(big.Int), logs: make(map[common.Hash]Logs)} } diff --git a/core/transaction_util.go b/core/transaction_util.go index ce2ceac46..69c6bc36f 100644 --- a/core/transaction_util.go +++ b/core/transaction_util.go @@ -32,7 +32,7 @@ var ( ) // PutTransactions stores the transactions in the given database -func PutTransactions(db common.Database, block *types.Block, txs types.Transactions) { +func PutTransactions(db ethdb.Database, block *types.Block, txs types.Transactions) { batch := new(leveldb.Batch) _, batchWrite := db.(*ethdb.LDBDatabase) @@ -78,7 +78,7 @@ func PutTransactions(db common.Database, block *types.Block, txs types.Transacti } // PutReceipts stores the receipts in the current database -func PutReceipts(db common.Database, receipts types.Receipts) error { +func PutReceipts(db ethdb.Database, receipts types.Receipts) error { batch := new(leveldb.Batch) _, batchWrite := db.(*ethdb.LDBDatabase) @@ -108,7 +108,7 @@ func PutReceipts(db common.Database, receipts types.Receipts) error { } // GetReceipt returns a receipt by hash -func GetReceipt(db common.Database, txHash common.Hash) *types.Receipt { +func GetReceipt(db ethdb.Database, txHash common.Hash) *types.Receipt { data, _ := db.Get(append(receiptsPre, txHash[:]...)) if len(data) == 0 { return nil @@ -124,7 +124,7 @@ func GetReceipt(db common.Database, txHash common.Hash) *types.Receipt { // GetBlockReceipts returns the receipts generated by the transactions // included in block's given hash. -func GetBlockReceipts(db common.Database, hash common.Hash) types.Receipts { +func GetBlockReceipts(db ethdb.Database, hash common.Hash) types.Receipts { data, _ := db.Get(append(blockReceiptsPre, hash[:]...)) if len(data) == 0 { return nil @@ -141,7 +141,7 @@ func GetBlockReceipts(db common.Database, hash common.Hash) types.Receipts { // PutBlockReceipts stores the block's transactions associated receipts // and stores them by block hash in a single slice. This is required for // forks and chain reorgs -func PutBlockReceipts(db common.Database, block *types.Block, receipts types.Receipts) error { +func PutBlockReceipts(db ethdb.Database, block *types.Block, receipts types.Receipts) error { rs := make([]*types.ReceiptForStorage, len(receipts)) for i, receipt := range receipts { rs[i] = (*types.ReceiptForStorage)(receipt) diff --git a/eth/backend.go b/eth/backend.go index deb6d3d0f..349dfa613 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -128,7 +128,7 @@ type Config struct { // NewDB is used to create databases. // If nil, the default is to create leveldb databases on disk. - NewDB func(path string) (common.Database, error) + NewDB func(path string) (ethdb.Database, error) } func (cfg *Config) parseBootNodes() []*discover.Node { @@ -210,11 +210,8 @@ type Ethereum struct { shutdownChan chan bool // DB interfaces - chainDb common.Database // Block chain databe - dappDb common.Database // Dapp database - - // Closed when databases are flushed and closed - databasesClosed chan bool + chainDb ethdb.Database // Block chain database + dappDb ethdb.Database // Dapp database //*** SERVICES *** // State manager for processing new blocks and managing the over all states @@ -267,7 +264,7 @@ func New(config *Config) (*Ethereum, error) { newdb := config.NewDB if newdb == nil { - newdb = func(path string) (common.Database, error) { return ethdb.NewLDBDatabase(path, config.DatabaseCache) } + newdb = func(path string) (ethdb.Database, error) { return ethdb.NewLDBDatabase(path, config.DatabaseCache) } } // Open the chain database and perform any upgrades needed @@ -337,7 +334,6 @@ func New(config *Config) (*Ethereum, error) { eth := &Ethereum{ shutdownChan: make(chan bool), - databasesClosed: make(chan bool), chainDb: chainDb, dappDb: dappDb, eventMux: &event.TypeMux{}, @@ -527,8 +523,8 @@ func (s *Ethereum) BlockProcessor() *core.BlockProcessor { return s.blockProcess func (s *Ethereum) TxPool() *core.TxPool { return s.txPool } func (s *Ethereum) Whisper() *whisper.Whisper { return s.whisper } func (s *Ethereum) EventMux() *event.TypeMux { return s.eventMux } -func (s *Ethereum) ChainDb() common.Database { return s.chainDb } -func (s *Ethereum) DappDb() common.Database { return s.dappDb } +func (s *Ethereum) ChainDb() ethdb.Database { return s.chainDb } +func (s *Ethereum) DappDb() ethdb.Database { return s.dappDb } func (s *Ethereum) IsListening() bool { return true } // Always listening func (s *Ethereum) PeerCount() int { return s.net.PeerCount() } func (s *Ethereum) Peers() []*p2p.Peer { return s.net.Peers() } @@ -549,8 +545,6 @@ func (s *Ethereum) Start() error { if err != nil { return err } - // periodically flush databases - go s.syncDatabases() if s.AutoDAG { s.StartAutoDAG() @@ -566,32 +560,6 @@ func (s *Ethereum) Start() error { return nil } -// sync databases every minute. If flushing fails we exit immediatly. The system -// may not continue under any circumstances. -func (s *Ethereum) syncDatabases() { - ticker := time.NewTicker(1 * time.Minute) -done: - for { - select { - case <-ticker.C: - // don't change the order of database flushes - if err := s.dappDb.Flush(); err != nil { - glog.Fatalf("fatal error: flush dappDb: %v (Restart your node. We are aware of this issue)\n", err) - } - if err := s.chainDb.Flush(); err != nil { - glog.Fatalf("fatal error: flush chainDb: %v (Restart your node. We are aware of this issue)\n", err) - } - case <-s.shutdownChan: - break done - } - } - - s.chainDb.Close() - s.dappDb.Close() - - close(s.databasesClosed) -} - func (s *Ethereum) StartForTest() { jsonlogger.LogJson(&logger.LogStarting{ ClientString: s.net.Name, @@ -622,12 +590,13 @@ func (s *Ethereum) Stop() { } s.StopAutoDAG() + s.chainDb.Close() + s.dappDb.Close() close(s.shutdownChan) } // This function will wait for a shutdown and resumes main thread execution func (s *Ethereum) WaitForShutdown() { - <-s.databasesClosed <-s.shutdownChan } @@ -717,7 +686,7 @@ func dagFiles(epoch uint64) (string, string) { return dag, "full-R" + dag } -func saveBlockchainVersion(db common.Database, bcVersion int) { +func saveBlockchainVersion(db ethdb.Database, bcVersion int) { d, _ := db.Get([]byte("BlockchainVersion")) blockchainVersion := common.NewValue(d).Uint() @@ -728,7 +697,7 @@ func saveBlockchainVersion(db common.Database, bcVersion int) { // upgradeChainDatabase ensures that the chain database stores block split into // separate header and body entries. -func upgradeChainDatabase(db common.Database) error { +func upgradeChainDatabase(db ethdb.Database) error { // Short circuit if the head block is stored already as separate header and body data, err := db.Get([]byte("LastBlock")) if err != nil { diff --git a/eth/downloader/downloader.go b/eth/downloader/downloader.go index d28985b3e..f038e24e4 100644 --- a/eth/downloader/downloader.go +++ b/eth/downloader/downloader.go @@ -130,10 +130,9 @@ type Downloader struct { interrupt int32 // Atomic boolean to signal termination // Statistics - importStart time.Time // Instance when the last blocks were taken from the cache - importQueue []*Block // Previously taken blocks to check import progress - importDone int // Number of taken blocks already imported from the last batch - importLock sync.Mutex + syncStatsOrigin uint64 // Origin block number where syncing started at + syncStatsHeight uint64 // Highest block number known when syncing started + syncStatsLock sync.RWMutex // Lock protecting the sync stats fields // Callbacks hasBlock hashCheckFn // Checks if a block is present in the chain @@ -161,6 +160,7 @@ type Downloader struct { cancelLock sync.RWMutex // Lock to protect the cancel channel in delivers // Testing hooks + syncInitHook func(uint64, uint64) // Method to call upon initiating a new sync run bodyFetchHook func([]*types.Header) // Method to call upon starting a block body fetch chainInsertHook func([]*Block) // Method to call upon inserting a chain of blocks (possibly in multiple invocations) } @@ -192,27 +192,14 @@ func New(mux *event.TypeMux, hasBlock hashCheckFn, getBlock blockRetrievalFn, he } } -// Stats retrieves the current status of the downloader. -func (d *Downloader) Stats() (pending int, cached int, importing int, estimate time.Duration) { - // Fetch the download status - pending, cached = d.queue.Size() +// Boundaries retrieves the synchronisation boundaries, specifically the origin +// block where synchronisation started at (may have failed/suspended) and the +// latest known block which the synchonisation targets. +func (d *Downloader) Boundaries() (uint64, uint64) { + d.syncStatsLock.RLock() + defer d.syncStatsLock.RUnlock() - // Figure out the import progress - d.importLock.Lock() - defer d.importLock.Unlock() - - for len(d.importQueue) > 0 && d.hasBlock(d.importQueue[0].RawBlock.Hash()) { - d.importQueue = d.importQueue[1:] - d.importDone++ - } - importing = len(d.importQueue) - - // Make an estimate on the total sync - estimate = 0 - if d.importDone > 0 { - estimate = time.Since(d.importStart) / time.Duration(d.importDone) * time.Duration(pending+cached+importing) - } - return + return d.syncStatsOrigin, d.syncStatsHeight } // Synchronising returns whether the downloader is currently retrieving blocks. @@ -333,14 +320,29 @@ func (d *Downloader) syncWithPeer(p *peer, hash common.Hash, td *big.Int) (err e switch { case p.version == eth61: - // Old eth/61, use forward, concurrent hash and block retrieval algorithm - number, err := d.findAncestor61(p) + // Look up the sync boundaries: the common ancestor and the target block + latest, err := d.fetchHeight61(p) + if err != nil { + return err + } + origin, err := d.findAncestor61(p) if err != nil { return err } + d.syncStatsLock.Lock() + if d.syncStatsHeight <= origin || d.syncStatsOrigin > origin { + d.syncStatsOrigin = origin + } + d.syncStatsHeight = latest + d.syncStatsLock.Unlock() + + // Initiate the sync using a concurrent hash and block retrieval algorithm + if d.syncInitHook != nil { + d.syncInitHook(origin, latest) + } errc := make(chan error, 2) - go func() { errc <- d.fetchHashes61(p, td, number+1) }() - go func() { errc <- d.fetchBlocks61(number + 1) }() + go func() { errc <- d.fetchHashes61(p, td, origin+1) }() + go func() { errc <- d.fetchBlocks61(origin + 1) }() // If any fetcher fails, cancel the other if err := <-errc; err != nil { @@ -351,14 +353,29 @@ func (d *Downloader) syncWithPeer(p *peer, hash common.Hash, td *big.Int) (err e return <-errc case p.version >= eth62: - // New eth/62, use forward, concurrent header and block body retrieval algorithm - number, err := d.findAncestor(p) + // Look up the sync boundaries: the common ancestor and the target block + latest, err := d.fetchHeight(p) if err != nil { return err } + origin, err := d.findAncestor(p) + if err != nil { + return err + } + d.syncStatsLock.Lock() + if d.syncStatsHeight <= origin || d.syncStatsOrigin > origin { + d.syncStatsOrigin = origin + } + d.syncStatsHeight = latest + d.syncStatsLock.Unlock() + + // Initiate the sync using a concurrent hash and block retrieval algorithm + if d.syncInitHook != nil { + d.syncInitHook(origin, latest) + } errc := make(chan error, 2) - go func() { errc <- d.fetchHeaders(p, td, number+1) }() - go func() { errc <- d.fetchBodies(number + 1) }() + go func() { errc <- d.fetchHeaders(p, td, origin+1) }() + go func() { errc <- d.fetchBodies(origin + 1) }() // If any fetcher fails, cancel the other if err := <-errc; err != nil { @@ -401,6 +418,50 @@ func (d *Downloader) Terminate() { d.cancel() } +// fetchHeight61 retrieves the head block of the remote peer to aid in estimating +// the total time a pending synchronisation would take. +func (d *Downloader) fetchHeight61(p *peer) (uint64, error) { + glog.V(logger.Debug).Infof("%v: retrieving remote chain height", p) + + // Request the advertised remote head block and wait for the response + go p.getBlocks([]common.Hash{p.head}) + + timeout := time.After(blockSoftTTL) + for { + select { + case <-d.cancelCh: + return 0, errCancelBlockFetch + + case <-d.headerCh: + // Out of bounds eth/62 block headers received, ignore them + + case <-d.bodyCh: + // Out of bounds eth/62 block bodies received, ignore them + + case <-d.hashCh: + // Out of bounds hashes received, ignore them + + case blockPack := <-d.blockCh: + // Discard anything not from the origin peer + if blockPack.peerId != p.id { + glog.V(logger.Debug).Infof("Received blocks from incorrect peer(%s)", blockPack.peerId) + break + } + // Make sure the peer actually gave something valid + blocks := blockPack.blocks + if len(blocks) != 1 { + glog.V(logger.Debug).Infof("%v: invalid number of head blocks: %d != 1", p, len(blocks)) + return 0, errBadPeer + } + return blocks[0].NumberU64(), nil + + case <-timeout: + glog.V(logger.Debug).Infof("%v: head block timeout", p) + return 0, errTimeout + } + } +} + // findAncestor61 tries to locate the common ancestor block of the local chain and // a remote peers blockchain. In the general case when our node was in sync and // on the correct chain, checking the top N blocks should already get us a match. @@ -776,6 +837,50 @@ func (d *Downloader) fetchBlocks61(from uint64) error { } } +// fetchHeight retrieves the head header of the remote peer to aid in estimating +// the total time a pending synchronisation would take. +func (d *Downloader) fetchHeight(p *peer) (uint64, error) { + glog.V(logger.Debug).Infof("%v: retrieving remote chain height", p) + + // Request the advertised remote head block and wait for the response + go p.getRelHeaders(p.head, 1, 0, false) + + timeout := time.After(headerTTL) + for { + select { + case <-d.cancelCh: + return 0, errCancelBlockFetch + + case headerPack := <-d.headerCh: + // Discard anything not from the origin peer + if headerPack.peerId != p.id { + glog.V(logger.Debug).Infof("Received headers from incorrect peer(%s)", headerPack.peerId) + break + } + // Make sure the peer actually gave something valid + headers := headerPack.headers + if len(headers) != 1 { + glog.V(logger.Debug).Infof("%v: invalid number of head headers: %d != 1", p, len(headers)) + return 0, errBadPeer + } + return headers[0].Number.Uint64(), nil + + case <-d.bodyCh: + // Out of bounds block bodies received, ignore them + + case <-d.hashCh: + // Out of bounds eth/61 hashes received, ignore them + + case <-d.blockCh: + // Out of bounds eth/61 blocks received, ignore them + + case <-timeout: + glog.V(logger.Debug).Infof("%v: head header timeout", p) + return 0, errTimeout + } + } +} + // findAncestor tries to locate the common ancestor block of the local chain and // a remote peers blockchain. In the general case when our node was in sync and // on the correct chain, checking the top N blocks should already get us a match. @@ -973,7 +1078,7 @@ func (d *Downloader) fetchHeaders(p *peer, td *big.Int, from uint64) error { // Otherwise insert all the new headers, aborting in case of junk glog.V(logger.Detail).Infof("%v: inserting %d headers from #%d", p, len(headerPack.headers), from) - inserts := d.queue.Insert(headerPack.headers) + inserts := d.queue.Insert(headerPack.headers, from) if len(inserts) != len(headerPack.headers) { glog.V(logger.Debug).Infof("%v: stale headers", p) return errBadPeer @@ -1203,16 +1308,10 @@ func (d *Downloader) process() { d.process() } }() - // Release the lock upon exit (note, before checking for reentry!), and set + // Release the lock upon exit (note, before checking for reentry!) // the import statistics to zero. - defer func() { - d.importLock.Lock() - d.importQueue = nil - d.importDone = 0 - d.importLock.Unlock() + defer atomic.StoreInt32(&d.processing, 0) - atomic.StoreInt32(&d.processing, 0) - }() // Repeat the processing as long as there are blocks to import for { // Fetch the next batch of blocks @@ -1223,13 +1322,6 @@ func (d *Downloader) process() { if d.chainInsertHook != nil { d.chainInsertHook(blocks) } - // Reset the import statistics - d.importLock.Lock() - d.importStart = time.Now() - d.importQueue = blocks - d.importDone = 0 - d.importLock.Unlock() - // Actually import the blocks glog.V(logger.Debug).Infof("Inserting chain with %d blocks (#%v - #%v)\n", len(blocks), blocks[0].RawBlock.Number(), blocks[len(blocks)-1].RawBlock.Number()) for len(blocks) != 0 { diff --git a/eth/downloader/downloader_test.go b/eth/downloader/downloader_test.go index dbcf93607..885fab8bd 100644 --- a/eth/downloader/downloader_test.go +++ b/eth/downloader/downloader_test.go @@ -20,6 +20,7 @@ import ( "errors" "fmt" "math/big" + "sync" "sync/atomic" "testing" "time" @@ -99,6 +100,8 @@ type downloadTester struct { peerHashes map[string][]common.Hash // Hash chain belonging to different test peers peerBlocks map[string]map[common.Hash]*types.Block // Blocks belonging to different test peers peerChainTds map[string]map[common.Hash]*big.Int // Total difficulties of the blocks in the peer chains + + lock sync.RWMutex } // newTester creates a new downloader test mocker. @@ -118,8 +121,8 @@ func newTester() *downloadTester { // sync starts synchronizing with a remote peer, blocking until it completes. func (dl *downloadTester) sync(id string, td *big.Int) error { + dl.lock.RLock() hash := dl.peerHashes[id][0] - // If no particular TD was requested, load from the peer's blockchain if td == nil { td = big.NewInt(1) @@ -127,8 +130,9 @@ func (dl *downloadTester) sync(id string, td *big.Int) error { td = diff } } - err := dl.downloader.synchronise(id, hash, td) + dl.lock.RUnlock() + err := dl.downloader.synchronise(id, hash, td) for { // If the queue is empty and processing stopped, break hashes, blocks := dl.downloader.queue.Size() @@ -143,26 +147,41 @@ func (dl *downloadTester) sync(id string, td *big.Int) error { // hasBlock checks if a block is pres ent in the testers canonical chain. func (dl *downloadTester) hasBlock(hash common.Hash) bool { + dl.lock.RLock() + defer dl.lock.RUnlock() + return dl.getBlock(hash) != nil } // getBlock retrieves a block from the testers canonical chain. func (dl *downloadTester) getBlock(hash common.Hash) *types.Block { + dl.lock.RLock() + defer dl.lock.RUnlock() + return dl.ownBlocks[hash] } // headBlock retrieves the current head block from the canonical chain. func (dl *downloadTester) headBlock() *types.Block { + dl.lock.RLock() + defer dl.lock.RUnlock() + return dl.getBlock(dl.ownHashes[len(dl.ownHashes)-1]) } // getTd retrieves the block's total difficulty from the canonical chain. func (dl *downloadTester) getTd(hash common.Hash) *big.Int { + dl.lock.RLock() + defer dl.lock.RUnlock() + return dl.ownChainTd[hash] } // insertChain injects a new batch of blocks into the simulated chain. func (dl *downloadTester) insertChain(blocks types.Blocks) (int, error) { + dl.lock.Lock() + defer dl.lock.Unlock() + for i, block := range blocks { if _, ok := dl.ownBlocks[block.ParentHash()]; !ok { return i, errors.New("unknown parent") @@ -183,9 +202,12 @@ func (dl *downloadTester) newPeer(id string, version int, hashes []common.Hash, // specific delay time on processing the network packets sent to it, simulating // potentially slow network IO. func (dl *downloadTester) newSlowPeer(id string, version int, hashes []common.Hash, blocks map[common.Hash]*types.Block, delay time.Duration) error { + dl.lock.Lock() + defer dl.lock.Unlock() + err := dl.downloader.RegisterPeer(id, version, hashes[0], dl.peerGetRelHashesFn(id, delay), dl.peerGetAbsHashesFn(id, delay), dl.peerGetBlocksFn(id, delay), - nil, dl.peerGetAbsHeadersFn(id, delay), dl.peerGetBodiesFn(id, delay)) + dl.peerGetRelHeadersFn(id, delay), dl.peerGetAbsHeadersFn(id, delay), dl.peerGetBodiesFn(id, delay)) if err == nil { // Assign the owned hashes and blocks to the peer (deep copy) dl.peerHashes[id] = make([]common.Hash, len(hashes)) @@ -207,6 +229,9 @@ func (dl *downloadTester) newSlowPeer(id string, version int, hashes []common.Ha // dropPeer simulates a hard peer removal from the connection pool. func (dl *downloadTester) dropPeer(id string) { + dl.lock.Lock() + defer dl.lock.Unlock() + delete(dl.peerHashes, id) delete(dl.peerBlocks, id) delete(dl.peerChainTds, id) @@ -221,6 +246,9 @@ func (dl *downloadTester) peerGetRelHashesFn(id string, delay time.Duration) fun return func(head common.Hash) error { time.Sleep(delay) + dl.lock.RLock() + defer dl.lock.RUnlock() + // Gather the next batch of hashes hashes := dl.peerHashes[id] result := make([]common.Hash, 0, MaxHashFetch) @@ -250,6 +278,9 @@ func (dl *downloadTester) peerGetAbsHashesFn(id string, delay time.Duration) fun return func(head uint64, count int) error { time.Sleep(delay) + dl.lock.RLock() + defer dl.lock.RUnlock() + // Gather the next batch of hashes hashes := dl.peerHashes[id] result := make([]common.Hash, 0, count) @@ -271,6 +302,10 @@ func (dl *downloadTester) peerGetAbsHashesFn(id string, delay time.Duration) fun func (dl *downloadTester) peerGetBlocksFn(id string, delay time.Duration) func([]common.Hash) error { return func(hashes []common.Hash) error { time.Sleep(delay) + + dl.lock.RLock() + defer dl.lock.RUnlock() + blocks := dl.peerBlocks[id] result := make([]*types.Block, 0, len(hashes)) for _, hash := range hashes { @@ -284,6 +319,27 @@ func (dl *downloadTester) peerGetBlocksFn(id string, delay time.Duration) func([ } } +// peerGetRelHeadersFn constructs a GetBlockHeaders function based on a hashed +// origin; associated with a particular peer in the download tester. The returned +// function can be used to retrieve batches of headers from the particular peer. +func (dl *downloadTester) peerGetRelHeadersFn(id string, delay time.Duration) func(common.Hash, int, int, bool) error { + return func(origin common.Hash, amount int, skip int, reverse bool) error { + // Find the canonical number of the hash + dl.lock.RLock() + number := uint64(0) + for num, hash := range dl.peerHashes[id] { + if hash == origin { + number = uint64(len(dl.peerHashes[id]) - num - 1) + break + } + } + dl.lock.RUnlock() + + // Use the absolute header fetcher to satisfy the query + return dl.peerGetAbsHeadersFn(id, delay)(number, amount, skip, reverse) + } +} + // peerGetAbsHeadersFn constructs a GetBlockHeaders function based on a numbered // origin; associated with a particular peer in the download tester. The returned // function can be used to retrieve batches of headers from the particular peer. @@ -291,6 +347,9 @@ func (dl *downloadTester) peerGetAbsHeadersFn(id string, delay time.Duration) fu return func(origin uint64, amount int, skip int, reverse bool) error { time.Sleep(delay) + dl.lock.RLock() + defer dl.lock.RUnlock() + // Gather the next batch of hashes hashes := dl.peerHashes[id] blocks := dl.peerBlocks[id] @@ -315,6 +374,10 @@ func (dl *downloadTester) peerGetAbsHeadersFn(id string, delay time.Duration) fu func (dl *downloadTester) peerGetBodiesFn(id string, delay time.Duration) func([]common.Hash) error { return func(hashes []common.Hash) error { time.Sleep(delay) + + dl.lock.RLock() + defer dl.lock.RUnlock() + blocks := dl.peerBlocks[id] transactions := make([][]*types.Transaction, 0, len(hashes)) @@ -384,13 +447,23 @@ func testThrottling(t *testing.T, protocol int) { errc <- tester.sync("peer", nil) }() // Iteratively take some blocks, always checking the retrieval count - for len(tester.ownBlocks) < targetBlocks+1 { + for { + // Check the retrieval count synchronously (! reason for this ugly block) + tester.lock.RLock() + retrieved := len(tester.ownBlocks) + tester.lock.RUnlock() + if retrieved >= targetBlocks+1 { + break + } // Wait a bit for sync to throttle itself var cached int for start := time.Now(); time.Since(start) < time.Second; { time.Sleep(25 * time.Millisecond) + tester.downloader.queue.lock.RLock() cached = len(tester.downloader.queue.blockPool) + tester.downloader.queue.lock.RUnlock() + if cached == blockCacheLimit || len(tester.ownBlocks)+cached+int(atomic.LoadUint32(&blocked)) == targetBlocks+1 { break } @@ -583,6 +656,67 @@ func testEmptyBlockShortCircuit(t *testing.T, protocol int) { } } +// Tests that headers are enqueued continuously, preventing malicious nodes from +// stalling the downloader by feeding gapped header chains. +func TestMissingHeaderAttack62(t *testing.T) { testMissingHeaderAttack(t, 62) } +func TestMissingHeaderAttack63(t *testing.T) { testMissingHeaderAttack(t, 63) } +func TestMissingHeaderAttack64(t *testing.T) { testMissingHeaderAttack(t, 64) } + +func testMissingHeaderAttack(t *testing.T, protocol int) { + // Create a small enough block chain to download + targetBlocks := blockCacheLimit - 15 + hashes, blocks := makeChain(targetBlocks, 0, genesis) + + tester := newTester() + + // Attempt a full sync with an attacker feeding gapped headers + tester.newPeer("attack", protocol, hashes, blocks) + missing := targetBlocks / 2 + delete(tester.peerBlocks["attack"], hashes[missing]) + + if err := tester.sync("attack", nil); err == nil { + t.Fatalf("succeeded attacker synchronisation") + } + // Synchronise with the valid peer and make sure sync succeeds + tester.newPeer("valid", protocol, hashes, blocks) + if err := tester.sync("valid", nil); err != nil { + t.Fatalf("failed to synchronise blocks: %v", err) + } + if imported := len(tester.ownBlocks); imported != len(hashes) { + t.Fatalf("synchronised block mismatch: have %v, want %v", imported, len(hashes)) + } +} + +// Tests that if requested headers are shifted (i.e. first is missing), the queue +// detects the invalid numbering. +func TestShiftedHeaderAttack62(t *testing.T) { testShiftedHeaderAttack(t, 62) } +func TestShiftedHeaderAttack63(t *testing.T) { testShiftedHeaderAttack(t, 63) } +func TestShiftedHeaderAttack64(t *testing.T) { testShiftedHeaderAttack(t, 64) } + +func testShiftedHeaderAttack(t *testing.T, protocol int) { + // Create a small enough block chain to download + targetBlocks := blockCacheLimit - 15 + hashes, blocks := makeChain(targetBlocks, 0, genesis) + + tester := newTester() + + // Attempt a full sync with an attacker feeding shifted headers + tester.newPeer("attack", protocol, hashes, blocks) + delete(tester.peerBlocks["attack"], hashes[len(hashes)-2]) + + if err := tester.sync("attack", nil); err == nil { + t.Fatalf("succeeded attacker synchronisation") + } + // Synchronise with the valid peer and make sure sync succeeds + tester.newPeer("valid", protocol, hashes, blocks) + if err := tester.sync("valid", nil); err != nil { + t.Fatalf("failed to synchronise blocks: %v", err) + } + if imported := len(tester.ownBlocks); imported != len(hashes) { + t.Fatalf("synchronised block mismatch: have %v, want %v", imported, len(hashes)) + } +} + // Tests that if a peer sends an invalid body for a requested block, it gets // dropped immediately by the downloader. func TestInvalidBlockBodyAttack62(t *testing.T) { testInvalidBlockBodyAttack(t, 62) } @@ -727,3 +861,259 @@ func testBlockBodyAttackerDropping(t *testing.T, protocol int) { } } } + +// Tests that synchronisation boundaries (origin block number and highest block +// number) is tracked and updated correctly. +func TestSyncBoundaries61(t *testing.T) { testSyncBoundaries(t, 61) } +func TestSyncBoundaries62(t *testing.T) { testSyncBoundaries(t, 62) } +func TestSyncBoundaries63(t *testing.T) { testSyncBoundaries(t, 63) } +func TestSyncBoundaries64(t *testing.T) { testSyncBoundaries(t, 64) } + +func testSyncBoundaries(t *testing.T, protocol int) { + // Create a small enough block chain to download + targetBlocks := blockCacheLimit - 15 + hashes, blocks := makeChain(targetBlocks, 0, genesis) + + // Set a sync init hook to catch boundary changes + starting := make(chan struct{}) + progress := make(chan struct{}) + + tester := newTester() + tester.downloader.syncInitHook = func(origin, latest uint64) { + starting <- struct{}{} + <-progress + } + // Retrieve the sync boundaries and ensure they are zero (pristine sync) + if origin, latest := tester.downloader.Boundaries(); origin != 0 || latest != 0 { + t.Fatalf("Pristine boundary mismatch: have %v/%v, want %v/%v", origin, latest, 0, 0) + } + // Synchronise half the blocks and check initial boundaries + tester.newPeer("peer-half", protocol, hashes[targetBlocks/2:], blocks) + pending := new(sync.WaitGroup) + pending.Add(1) + + go func() { + defer pending.Done() + if err := tester.sync("peer-half", nil); err != nil { + t.Fatalf("failed to synchronise blocks: %v", err) + } + }() + <-starting + if origin, latest := tester.downloader.Boundaries(); origin != 0 || latest != uint64(targetBlocks/2+1) { + t.Fatalf("Initial boundary mismatch: have %v/%v, want %v/%v", origin, latest, 0, targetBlocks/2+1) + } + progress <- struct{}{} + pending.Wait() + + // Synchronise all the blocks and check continuation boundaries + tester.newPeer("peer-full", protocol, hashes, blocks) + pending.Add(1) + + go func() { + defer pending.Done() + if err := tester.sync("peer-full", nil); err != nil { + t.Fatalf("failed to synchronise blocks: %v", err) + } + }() + <-starting + if origin, latest := tester.downloader.Boundaries(); origin != uint64(targetBlocks/2+1) || latest != uint64(targetBlocks) { + t.Fatalf("Completing boundary mismatch: have %v/%v, want %v/%v", origin, latest, targetBlocks/2+1, targetBlocks) + } + progress <- struct{}{} + pending.Wait() +} + +// Tests that synchronisation boundaries (origin block number and highest block +// number) is tracked and updated correctly in case of a fork (or manual head +// revertal). +func TestForkedSyncBoundaries61(t *testing.T) { testForkedSyncBoundaries(t, 61) } +func TestForkedSyncBoundaries62(t *testing.T) { testForkedSyncBoundaries(t, 62) } +func TestForkedSyncBoundaries63(t *testing.T) { testForkedSyncBoundaries(t, 63) } +func TestForkedSyncBoundaries64(t *testing.T) { testForkedSyncBoundaries(t, 64) } + +func testForkedSyncBoundaries(t *testing.T, protocol int) { + // Create a forked chain to simulate origin revertal + common, fork := MaxHashFetch, 2*MaxHashFetch + hashesA, hashesB, blocksA, blocksB := makeChainFork(common+fork, fork, genesis) + + // Set a sync init hook to catch boundary changes + starting := make(chan struct{}) + progress := make(chan struct{}) + + tester := newTester() + tester.downloader.syncInitHook = func(origin, latest uint64) { + starting <- struct{}{} + <-progress + } + // Retrieve the sync boundaries and ensure they are zero (pristine sync) + if origin, latest := tester.downloader.Boundaries(); origin != 0 || latest != 0 { + t.Fatalf("Pristine boundary mismatch: have %v/%v, want %v/%v", origin, latest, 0, 0) + } + // Synchronise with one of the forks and check boundaries + tester.newPeer("fork A", protocol, hashesA, blocksA) + pending := new(sync.WaitGroup) + pending.Add(1) + + go func() { + defer pending.Done() + if err := tester.sync("fork A", nil); err != nil { + t.Fatalf("failed to synchronise blocks: %v", err) + } + }() + <-starting + if origin, latest := tester.downloader.Boundaries(); origin != 0 || latest != uint64(len(hashesA)-1) { + t.Fatalf("Initial boundary mismatch: have %v/%v, want %v/%v", origin, latest, 0, len(hashesA)-1) + } + progress <- struct{}{} + pending.Wait() + + // Simulate a successful sync above the fork + tester.downloader.syncStatsOrigin = tester.downloader.syncStatsHeight + + // Synchronise with the second fork and check boundary resets + tester.newPeer("fork B", protocol, hashesB, blocksB) + pending.Add(1) + + go func() { + defer pending.Done() + if err := tester.sync("fork B", nil); err != nil { + t.Fatalf("failed to synchronise blocks: %v", err) + } + }() + <-starting + if origin, latest := tester.downloader.Boundaries(); origin != uint64(common) || latest != uint64(len(hashesB)-1) { + t.Fatalf("Forking boundary mismatch: have %v/%v, want %v/%v", origin, latest, common, len(hashesB)-1) + } + progress <- struct{}{} + pending.Wait() +} + +// Tests that if synchronisation is aborted due to some failure, then the boundary +// origin is not updated in the next sync cycle, as it should be considered the +// continuation of the previous sync and not a new instance. +func TestFailedSyncBoundaries61(t *testing.T) { testFailedSyncBoundaries(t, 61) } +func TestFailedSyncBoundaries62(t *testing.T) { testFailedSyncBoundaries(t, 62) } +func TestFailedSyncBoundaries63(t *testing.T) { testFailedSyncBoundaries(t, 63) } +func TestFailedSyncBoundaries64(t *testing.T) { testFailedSyncBoundaries(t, 64) } + +func testFailedSyncBoundaries(t *testing.T, protocol int) { + // Create a small enough block chain to download + targetBlocks := blockCacheLimit - 15 + hashes, blocks := makeChain(targetBlocks, 0, genesis) + + // Set a sync init hook to catch boundary changes + starting := make(chan struct{}) + progress := make(chan struct{}) + + tester := newTester() + tester.downloader.syncInitHook = func(origin, latest uint64) { + starting <- struct{}{} + <-progress + } + // Retrieve the sync boundaries and ensure they are zero (pristine sync) + if origin, latest := tester.downloader.Boundaries(); origin != 0 || latest != 0 { + t.Fatalf("Pristine boundary mismatch: have %v/%v, want %v/%v", origin, latest, 0, 0) + } + // Attempt a full sync with a faulty peer + tester.newPeer("faulty", protocol, hashes, blocks) + missing := targetBlocks / 2 + delete(tester.peerBlocks["faulty"], hashes[missing]) + + pending := new(sync.WaitGroup) + pending.Add(1) + + go func() { + defer pending.Done() + if err := tester.sync("faulty", nil); err == nil { + t.Fatalf("succeeded faulty synchronisation") + } + }() + <-starting + if origin, latest := tester.downloader.Boundaries(); origin != 0 || latest != uint64(targetBlocks) { + t.Fatalf("Initial boundary mismatch: have %v/%v, want %v/%v", origin, latest, 0, targetBlocks) + } + progress <- struct{}{} + pending.Wait() + + // Synchronise with a good peer and check that the boundary origin remind the same after a failure + tester.newPeer("valid", protocol, hashes, blocks) + pending.Add(1) + + go func() { + defer pending.Done() + if err := tester.sync("valid", nil); err != nil { + t.Fatalf("failed to synchronise blocks: %v", err) + } + }() + <-starting + if origin, latest := tester.downloader.Boundaries(); origin != 0 || latest != uint64(targetBlocks) { + t.Fatalf("Completing boundary mismatch: have %v/%v, want %v/%v", origin, latest, 0, targetBlocks) + } + progress <- struct{}{} + pending.Wait() +} + +// Tests that if an attacker fakes a chain height, after the attack is detected, +// the boundary height is successfully reduced at the next sync invocation. +func TestFakedSyncBoundaries61(t *testing.T) { testFakedSyncBoundaries(t, 61) } +func TestFakedSyncBoundaries62(t *testing.T) { testFakedSyncBoundaries(t, 62) } +func TestFakedSyncBoundaries63(t *testing.T) { testFakedSyncBoundaries(t, 63) } +func TestFakedSyncBoundaries64(t *testing.T) { testFakedSyncBoundaries(t, 64) } + +func testFakedSyncBoundaries(t *testing.T, protocol int) { + // Create a small block chain + targetBlocks := blockCacheLimit - 15 + hashes, blocks := makeChain(targetBlocks+3, 0, genesis) + + // Set a sync init hook to catch boundary changes + starting := make(chan struct{}) + progress := make(chan struct{}) + + tester := newTester() + tester.downloader.syncInitHook = func(origin, latest uint64) { + starting <- struct{}{} + <-progress + } + // Retrieve the sync boundaries and ensure they are zero (pristine sync) + if origin, latest := tester.downloader.Boundaries(); origin != 0 || latest != 0 { + t.Fatalf("Pristine boundary mismatch: have %v/%v, want %v/%v", origin, latest, 0, 0) + } + // Create and sync with an attacker that promises a higher chain than available + tester.newPeer("attack", protocol, hashes, blocks) + for i := 1; i < 3; i++ { + delete(tester.peerBlocks["attack"], hashes[i]) + } + + pending := new(sync.WaitGroup) + pending.Add(1) + + go func() { + defer pending.Done() + if err := tester.sync("attack", nil); err == nil { + t.Fatalf("succeeded attacker synchronisation") + } + }() + <-starting + if origin, latest := tester.downloader.Boundaries(); origin != 0 || latest != uint64(targetBlocks+3) { + t.Fatalf("Initial boundary mismatch: have %v/%v, want %v/%v", origin, latest, 0, targetBlocks+3) + } + progress <- struct{}{} + pending.Wait() + + // Synchronise with a good peer and check that the boundary height has been reduced to the true value + tester.newPeer("valid", protocol, hashes[3:], blocks) + pending.Add(1) + + go func() { + defer pending.Done() + if err := tester.sync("valid", nil); err != nil { + t.Fatalf("failed to synchronise blocks: %v", err) + } + }() + <-starting + if origin, latest := tester.downloader.Boundaries(); origin != 0 || latest != uint64(targetBlocks) { + t.Fatalf("Initial boundary mismatch: have %v/%v, want %v/%v", origin, latest, 0, targetBlocks) + } + progress <- struct{}{} + pending.Wait() +} diff --git a/eth/downloader/queue.go b/eth/downloader/queue.go index 7db78327b..49d1046fb 100644 --- a/eth/downloader/queue.go +++ b/eth/downloader/queue.go @@ -57,6 +57,7 @@ type queue struct { headerPool map[common.Hash]*types.Header // [eth/62] Pending headers, mapping from their hashes headerQueue *prque.Prque // [eth/62] Priority queue of the headers to fetch the bodies for + headerHead common.Hash // [eth/62] Hash of the last queued header to verify order pendPool map[string]*fetchRequest // Currently pending block retrieval operations @@ -91,6 +92,7 @@ func (q *queue) Reset() { q.headerPool = make(map[common.Hash]*types.Header) q.headerQueue.Reset() + q.headerHead = common.Hash{} q.pendPool = make(map[string]*fetchRequest) @@ -186,7 +188,7 @@ func (q *queue) Insert61(hashes []common.Hash, fifo bool) []common.Hash { // Insert adds a set of headers for the download queue for scheduling, returning // the new headers encountered. -func (q *queue) Insert(headers []*types.Header) []*types.Header { +func (q *queue) Insert(headers []*types.Header, from uint64) []*types.Header { q.lock.Lock() defer q.lock.Unlock() @@ -196,13 +198,24 @@ func (q *queue) Insert(headers []*types.Header) []*types.Header { // Make sure no duplicate requests are executed hash := header.Hash() if _, ok := q.headerPool[hash]; ok { - glog.V(logger.Warn).Infof("Header %x already scheduled", hash) + glog.V(logger.Warn).Infof("Header #%d [%x] already scheduled", header.Number.Uint64(), hash[:4]) continue } + // Make sure chain order is honored and preserved throughout + if header.Number == nil || header.Number.Uint64() != from { + glog.V(logger.Warn).Infof("Header #%v [%x] broke chain ordering, expected %d", header.Number, hash[:4], from) + break + } + if q.headerHead != (common.Hash{}) && q.headerHead != header.ParentHash { + glog.V(logger.Warn).Infof("Header #%v [%x] broke chain ancestry", header.Number, hash[:4]) + break + } // Queue the header for body retrieval inserts = append(inserts, header) q.headerPool[hash] = header q.headerQueue.Push(header, -float32(header.Number.Uint64())) + q.headerHead = hash + from++ } return inserts } diff --git a/eth/handler.go b/eth/handler.go index 77fe09e8f..52c9c4151 100644 --- a/eth/handler.go +++ b/eth/handler.go @@ -28,6 +28,7 @@ import ( "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/eth/downloader" "github.com/ethereum/go-ethereum/eth/fetcher" + "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/logger" "github.com/ethereum/go-ethereum/logger/glog" @@ -61,7 +62,7 @@ func (ep extProt) GetBlock(hashes []common.Hash) error { return ep.getBlocks(has type ProtocolManager struct { txpool txPool chainman *core.ChainManager - chaindb common.Database + chaindb ethdb.Database downloader *downloader.Downloader fetcher *fetcher.Fetcher @@ -86,7 +87,7 @@ type ProtocolManager struct { // NewProtocolManager returns a new ethereum sub protocol manager. The Ethereum sub protocol manages peers capable // with the ethereum network. -func NewProtocolManager(networkId int, mux *event.TypeMux, txpool txPool, pow pow.PoW, chainman *core.ChainManager, chaindb common.Database) *ProtocolManager { +func NewProtocolManager(networkId int, mux *event.TypeMux, txpool txPool, pow pow.PoW, chainman *core.ChainManager, chaindb ethdb.Database) *ProtocolManager { // Create the protocol manager with the base fields manager := &ProtocolManager{ eventMux: mux, diff --git a/ethdb/database.go b/ethdb/database.go index 9e80e5409..047821c30 100644 --- a/ethdb/database.go +++ b/ethdb/database.go @@ -61,9 +61,7 @@ type LDBDatabase struct { quitChan chan chan error // Quit channel to stop the metrics collection before closing the database } -// NewLDBDatabase returns a LevelDB wrapped object. LDBDatabase does not persist data by -// it self but requires a background poller which syncs every X. `Flush` should be called -// when data needs to be stored and written to disk. +// NewLDBDatabase returns a LevelDB wrapped object. func NewLDBDatabase(file string, cache int) (*LDBDatabase, error) { // Calculate the cache allowance for this particular database cache = int(float64(cache) * cacheRatio[filepath.Base(file)]) @@ -142,11 +140,6 @@ func (self *LDBDatabase) NewIterator() iterator.Iterator { return self.db.NewIterator(nil, nil) } -// Flush flushes out the queue to leveldb -func (self *LDBDatabase) Flush() error { - return nil -} - func (self *LDBDatabase) Close() { // Stop the metrics collection to avoid internal database races self.quitLock.Lock() @@ -159,12 +152,14 @@ func (self *LDBDatabase) Close() { glog.V(logger.Error).Infof("metrics failure in '%s': %v\n", self.fn, err) } } - // Flush and close the database - if err := self.Flush(); err != nil { - glog.V(logger.Error).Infof("flushing '%s' failed: %v\n", self.fn, err) + err := self.db.Close() + if glog.V(logger.Error) { + if err == nil { + glog.Infoln("closed db:", self.fn) + } else { + glog.Errorf("error closing db %s: %v", self.fn, err) + } } - self.db.Close() - glog.V(logger.Error).Infoln("flushed and closed db:", self.fn) } func (self *LDBDatabase) LDB() *leveldb.DB { @@ -268,3 +263,23 @@ func (self *LDBDatabase) meter(refresh time.Duration) { } } } + +// TODO: remove this stuff and expose leveldb directly + +func (db *LDBDatabase) NewBatch() Batch { + return &ldbBatch{db: db.db, b: new(leveldb.Batch)} +} + +type ldbBatch struct { + db *leveldb.DB + b *leveldb.Batch +} + +func (b *ldbBatch) Put(key, value []byte) error { + b.b.Put(key, value) + return nil +} + +func (b *ldbBatch) Write() error { + return b.db.Write(b.b, nil) +} diff --git a/common/db.go b/ethdb/interface.go index 60c090cdc..f4b787a52 100644 --- a/common/db.go +++ b/ethdb/interface.go @@ -14,13 +14,17 @@ // 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 common +package ethdb -// Database interface type Database interface { Put(key []byte, value []byte) error Get(key []byte) ([]byte, error) Delete(key []byte) error Close() - Flush() error + NewBatch() Batch +} + +type Batch interface { + Put(key, value []byte) error + Write() error } diff --git a/ethdb/memory_database.go b/ethdb/memory_database.go index d50f8f9d4..81911f23f 100644 --- a/ethdb/memory_database.go +++ b/ethdb/memory_database.go @@ -36,8 +36,7 @@ func NewMemDatabase() (*MemDatabase, error) { } func (db *MemDatabase) Put(key []byte, value []byte) error { - db.db[string(key)] = value - + db.db[string(key)] = common.CopyBytes(value) return nil } @@ -92,6 +91,25 @@ func (db *MemDatabase) LastKnownTD() []byte { return data } -func (db *MemDatabase) Flush() error { +func (db *MemDatabase) NewBatch() Batch { + return &memBatch{db: db} +} + +type kv struct{ k, v []byte } + +type memBatch struct { + db *MemDatabase + writes []kv +} + +func (w *memBatch) Put(key, value []byte) error { + w.writes = append(w.writes, kv{key, common.CopyBytes(value)}) + return nil +} + +func (w *memBatch) Write() error { + for _, kv := range w.writes { + w.db.db[string(kv.k)] = kv.v + } return nil } diff --git a/jsre/ethereum_js.go b/jsre/ethereum_js.go index f33bb7c25..2d7dbfec0 100644 --- a/jsre/ethereum_js.go +++ b/jsre/ethereum_js.go @@ -650,7 +650,7 @@ module.exports = SolidityTypeBytes; 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 coder.js * @author Marek Kotewicz <marek@ethdev.com> * @date 2015 @@ -680,7 +680,7 @@ var SolidityCoder = function (types) { * * @method _requireType * @param {String} type - * @returns {SolidityType} + * @returns {SolidityType} * @throws {Error} throws if no matching type is found */ SolidityCoder.prototype._requireType = function (type) { @@ -726,7 +726,7 @@ SolidityCoder.prototype.encodeParams = function (types, params) { return acc + solidityType.staticPartLength(types[index]); }, 0); - var result = this.encodeMultiWithOffset(types, solidityTypes, encodeds, dynamicOffset); + var result = this.encodeMultiWithOffset(types, solidityTypes, encodeds, dynamicOffset); return result; }; @@ -751,7 +751,7 @@ SolidityCoder.prototype.encodeMultiWithOffset = function (types, solidityTypes, // TODO: figure out nested arrays }); - + types.forEach(function (type, i) { if (isDynamic(i)) { var e = self.encodeWithOffset(types[i], solidityTypes[i], encodeds[i], dynamicOffset); @@ -771,7 +771,7 @@ SolidityCoder.prototype.encodeWithOffset = function (type, solidityType, encoded var nestedName = solidityType.nestedName(type); var nestedStaticPartLength = solidityType.staticPartLength(nestedName); var result = encoded[0]; - + (function () { var previousLength = 2; // in int if (solidityType.isDynamicArray(nestedName)) { @@ -781,7 +781,7 @@ SolidityCoder.prototype.encodeWithOffset = function (type, solidityType, encoded } } })(); - + // first element is length, skip it (function () { for (var i = 0; i < encoded.length - 1; i++) { @@ -792,7 +792,7 @@ SolidityCoder.prototype.encodeWithOffset = function (type, solidityType, encoded return result; })(); - + } else if (solidityType.isStaticArray(type)) { return (function () { var nestedName = solidityType.nestedName(type); @@ -805,7 +805,7 @@ SolidityCoder.prototype.encodeWithOffset = function (type, solidityType, encoded var previousLength = 0; // in int for (var i = 0; i < encoded.length; i++) { // calculate length of previous item - previousLength += +(encoded[i - 1] || [])[0] || 0; + previousLength += +(encoded[i - 1] || [])[0] || 0; result += f.formatInputInt(offset + i * nestedStaticPartLength + previousLength * 32).encode(); } })(); @@ -848,7 +848,7 @@ SolidityCoder.prototype.decodeParam = function (type, bytes) { SolidityCoder.prototype.decodeParams = function (types, bytes) { var solidityTypes = this.getSolidityTypes(types); var offsets = this.getOffsets(types, solidityTypes); - + return solidityTypes.map(function (solidityType, index) { return solidityType.decode(bytes, offsets[index], types[index], index); }); @@ -856,10 +856,10 @@ SolidityCoder.prototype.decodeParams = function (types, bytes) { SolidityCoder.prototype.getOffsets = function (types, solidityTypes) { var lengths = solidityTypes.map(function (solidityType, index) { - return solidityType.staticPartLength(types[index]); + return solidityType.staticPartLength(types[index]); // get length }); - + for (var i = 0; i < lengths.length; i++) { // sum with length of previous element var previous = (lengths[i - 1] || 0); @@ -938,7 +938,7 @@ module.exports = SolidityTypeDynamicBytes; 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 * @author Marek Kotewicz <marek@ethdev.com> * @date 2015 @@ -1002,7 +1002,7 @@ var formatInputDynamicBytes = function (value) { * @returns {SolidityParam} */ var formatInputString = function (value) { - var result = utils.fromAscii(value).substr(2); + var result = utils.fromUtf8(value).substr(2); var length = result.length / 2; var l = Math.floor((result.length + 63) / 64); result = utils.padRight(result, l * 64); @@ -1082,7 +1082,7 @@ var formatOutputUInt = function (param) { * @returns {BigNumber} input bytes formatted to real */ var formatOutputReal = function (param) { - return formatOutputInt(param).dividedBy(new BigNumber(2).pow(128)); + return formatOutputInt(param).dividedBy(new BigNumber(2).pow(128)); }; /** @@ -1093,7 +1093,7 @@ var formatOutputReal = function (param) { * @returns {BigNumber} input bytes formatted to ureal */ var formatOutputUReal = function (param) { - return formatOutputUInt(param).dividedBy(new BigNumber(2).pow(128)); + return formatOutputUInt(param).dividedBy(new BigNumber(2).pow(128)); }; /** @@ -1139,7 +1139,7 @@ var formatOutputDynamicBytes = function (param) { */ var formatOutputString = function (param) { var length = (new BigNumber(param.dynamicPart().slice(0, 64), 16)).toNumber() * 2; - return utils.toAscii(param.dynamicPart().substr(64, length)); + return utils.toUtf8(param.dynamicPart().substr(64, length)); }; /** @@ -1228,7 +1228,7 @@ module.exports = SolidityTypeInt; 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 param.js * @author Marek Kotewicz <marek@ethdev.com> * @date 2015 @@ -1247,7 +1247,7 @@ var SolidityParam = function (value, offset) { /** * This method should be used to get length of params's dynamic part - * + * * @method dynamicPartLength * @returns {Number} length of dynamic part (in bytes) */ @@ -1275,7 +1275,7 @@ SolidityParam.prototype.withOffset = function (offset) { * @param {SolidityParam} result of combination */ SolidityParam.prototype.combine = function (param) { - return new SolidityParam(this.value + param.value); + return new SolidityParam(this.value + param.value); }; /** @@ -1307,8 +1307,8 @@ SolidityParam.prototype.offsetAsBytes = function () { */ SolidityParam.prototype.staticPart = function () { if (!this.isDynamic()) { - return this.value; - } + return this.value; + } return this.offsetAsBytes(); }; @@ -1340,7 +1340,7 @@ SolidityParam.prototype.encode = function () { * @returns {String} */ SolidityParam.encodeList = function (params) { - + // updating offsets var totalOffset = params.length * 32; var offsetParams = params.map(function (param) { @@ -1466,13 +1466,13 @@ SolidityType.prototype.staticPartLength = function (name) { /** * Should be used to determine if type is dynamic array - * eg: + * eg: * "type[]" => true * "type[4]" => false * * @method isDynamicArray * @param {String} name - * @return {Bool} true if the type is dynamic array + * @return {Bool} true if the type is dynamic array */ SolidityType.prototype.isDynamicArray = function (name) { var nestedTypes = this.nestedTypes(name); @@ -1481,13 +1481,13 @@ SolidityType.prototype.isDynamicArray = function (name) { /** * Should be used to determine if type is static array - * eg: + * eg: * "type[]" => false * "type[4]" => true * * @method isStaticArray * @param {String} name - * @return {Bool} true if the type is static array + * @return {Bool} true if the type is static array */ SolidityType.prototype.isStaticArray = function (name) { var nestedTypes = this.nestedTypes(name); @@ -1496,7 +1496,7 @@ SolidityType.prototype.isStaticArray = function (name) { /** * Should return length of static array - * eg. + * eg. * "int[32]" => 32 * "int256[14]" => 14 * "int[2][3]" => 3 @@ -1571,7 +1571,7 @@ SolidityType.prototype.nestedTypes = function (name) { * Should be used to encode the value * * @method encode - * @param {Object} value + * @param {Object} value * @param {String} name * @return {String} encoded value */ @@ -1585,7 +1585,7 @@ SolidityType.prototype.encode = function (value, name) { var result = []; result.push(f.formatInputInt(length).encode()); - + value.forEach(function (v) { result.push(self.encode(v, nestedName)); }); @@ -1659,12 +1659,12 @@ SolidityType.prototype.decode = function (bytes, offset, name) { return result; })(); } else if (this.isDynamicType(name)) { - + return (function () { var dynamicOffset = parseInt('0x' + bytes.substr(offset * 2, 64)); // in bytes var length = parseInt('0x' + bytes.substr(dynamicOffset * 2, 64)); // in bytes var roundedLength = Math.floor((length + 31) / 32); // in int - + return self._outputFormatter(new SolidityParam(bytes.substr(dynamicOffset * 2, ( 1 + roundedLength) * 64), 0)); })(); } @@ -1697,7 +1697,7 @@ var SolidityType = require('./type'); */ var SolidityTypeUInt = function () { this._inputFormatter = f.formatInputInt; - this._outputFormatter = f.formatOutputInt; + this._outputFormatter = f.formatOutputUInt; }; SolidityTypeUInt.prototype = new SolidityType({}); @@ -1787,13 +1787,13 @@ if (typeof XMLHttpRequest === 'undefined') { /** * Utils - * + * * @module utils */ /** * Utility functions - * + * * @class [utils] config * @constructor */ @@ -1860,7 +1860,7 @@ module.exports = { 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 sha3.js * @author Marek Kotewicz <marek@ethdev.com> * @date 2015 @@ -1876,7 +1876,7 @@ module.exports = function (str, isNew) { console.warn('new usage: \'web3.sha3("hello")\''); console.warn('see https://github.com/ethereum/web3.js/pull/205'); console.warn('if you need to hash hex value, you can do \'sha3("0xfff", true)\''); - str = utils.toAscii(str); + str = utils.toUtf8(str); } return sha3(str, { @@ -1885,7 +1885,7 @@ module.exports = function (str, isNew) { }; -},{"./utils":20,"crypto-js/sha3":47}],20:[function(require,module,exports){ +},{"./utils":20,"crypto-js/sha3":48}],20:[function(require,module,exports){ /* This file is part of ethereum.js. @@ -1902,7 +1902,7 @@ module.exports = function (str, isNew) { 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 @@ -1910,19 +1910,20 @@ module.exports = function (str, isNew) { /** * Utils - * + * * @module utils */ /** * Utility functions - * + * * @class [utils] utils * @constructor */ var BigNumber = require('bignumber.js'); +var utf8 = require('utf8'); var unitMap = { 'wei': '1', @@ -1977,9 +1978,30 @@ var padRight = function (string, chars, sign) { return string + (new Array(chars - string.length + 1).join(sign ? sign : "0")); }; -/** - * Should be called to get sting from it's hex representation - * TODO: it should be called toUTF8 +/** + * Should be called to get utf8 from it's hex representation + * + * @method toUtf8 + * @param {String} string in hex + * @returns {String} ascii string representation of hex value + */ +var toUtf8 = 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); + str += String.fromCharCode(code); + } + + return utf8.decode(str); +}; + +/** + * Should be called to get ascii from it's hex representation * * @method toAscii * @param {String} string in hex @@ -1997,40 +2019,44 @@ var toAscii = function(hex) { str += String.fromCharCode(code); } - return decodeURIComponent(escape(str)); // jshint ignore:line + return str; }; - + /** - * Shold be called to get hex representation (prefixed by 0x) of ascii string + * Shold be called to get hex representation (prefixed by 0x) of utf8 string * - * @method toHexNative + * @method fromUtf8 * @param {String} string + * @param {Number} optional padding * @returns {String} hex representation of input string */ -var toHexNative = function(str) { - str = unescape(encodeURIComponent(str)); // jshint ignore:line +var fromUtf8 = function(str) { + str = utf8.encode(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; + return "0x" + hex; }; /** - * Shold be called to get hex representation (prefixed by 0x) of ascii string + * 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"; +var fromAscii = function(str) { + var hex = ""; + for(var i = 0; i < str.length; i++) { + var code = str.charCodeAt(i); + var n = code.toString(16); + hex += n.length < 2 ? '0' + n : n; + } + return "0x" + hex; }; @@ -2052,13 +2078,13 @@ var transformToFullName = function (json) { /** * 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('('); + var length = name.indexOf('('); return length !== -1 ? name.substr(0, length) : name; }; @@ -2113,7 +2139,7 @@ var toHex = function (val) { return fromDecimal(val); if (isObject(val)) - return fromAscii(JSON.stringify(val)); + return fromUtf8(JSON.stringify(val)); // if its a negative number, pass it through fromDecimal if (isString(val)) { @@ -2156,7 +2182,7 @@ var getValueOfUnit = function (unit) { * - -- microether szabo micro * - -- milliether finney milli * - ether -- -- - * - kether einstein grand + * - kether einstein grand * - mether * - gether * - tether @@ -2169,7 +2195,7 @@ var getValueOfUnit = function (unit) { var fromWei = function(number, unit) { var returnValue = toBigNumber(number).dividedBy(getValueOfUnit(unit)); - return isBigNumber(number) ? returnValue : returnValue.toString(10); + return isBigNumber(number) ? returnValue : returnValue.toString(10); }; /** @@ -2178,12 +2204,12 @@ var fromWei = function(number, unit) { * Possible units are: * SI Short SI Full Effigy Other * - kwei femtoether ada - * - mwei picoether babbage + * - mwei picoether babbage * - gwei nanoether shannon nano * - -- microether szabo micro * - -- milliether finney milli * - ether -- -- - * - kether einstein grand + * - kether einstein grand * - mether * - gether * - tether @@ -2196,7 +2222,7 @@ var fromWei = function(number, unit) { var toWei = function(number, unit) { var returnValue = toBigNumber(number).times(getValueOfUnit(unit)); - return isBigNumber(number) ? returnValue : returnValue.toString(10); + return isBigNumber(number) ? returnValue : returnValue.toString(10); }; /** @@ -2215,7 +2241,7 @@ var toBigNumber = function(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); }; @@ -2242,7 +2268,7 @@ var toTwosComplement = function (number) { * @return {Boolean} */ var isStrictAddress = function (address) { - return /^0x[0-9a-f]{40}$/.test(address); + return /^0x[0-9a-f]{40}$/i.test(address); }; /** @@ -2253,7 +2279,7 @@ var isStrictAddress = function (address) { * @return {Boolean} */ var isAddress = function (address) { - return /^(0x)?[0-9a-f]{40}$/.test(address); + return /^(0x)?[0-9a-f]{40}$/i.test(address); }; /** @@ -2267,7 +2293,7 @@ var toAddress = function (address) { if (isStrictAddress(address)) { return address; } - + if (/^[0-9a-f]{40}$/.test(address)) { return '0x' + address; } @@ -2281,7 +2307,7 @@ var toAddress = function (address) { * * @method isBigNumber * @param {Object} - * @return {Boolean} + * @return {Boolean} */ var isBigNumber = function (object) { return object instanceof BigNumber || @@ -2290,7 +2316,7 @@ var isBigNumber = function (object) { /** * Returns true if object is string, otherwise false - * + * * @method isString * @param {Object} * @return {Boolean} @@ -2341,12 +2367,12 @@ var isBoolean = function (object) { * @return {Boolean} */ var isArray = function (object) { - return object instanceof Array; + return object instanceof Array; }; /** * Returns true if given string is valid json object - * + * * @method isJson * @param {String} * @return {Boolean} @@ -2365,7 +2391,9 @@ module.exports = { toHex: toHex, toDecimal: toDecimal, fromDecimal: fromDecimal, + toUtf8: toUtf8, toAscii: toAscii, + fromUtf8: fromUtf8, fromAscii: fromAscii, transformToFullName: transformToFullName, extractDisplayName: extractDisplayName, @@ -2386,10 +2414,9 @@ module.exports = { isJson: isJson }; - -},{"bignumber.js":"bignumber.js"}],21:[function(require,module,exports){ +},{"bignumber.js":"bignumber.js","utf8":50}],21:[function(require,module,exports){ module.exports={ - "version": "0.12.1" + "version": "0.13.0" } },{}],22:[function(require,module,exports){ @@ -2426,6 +2453,7 @@ var db = require('./web3/methods/db'); var shh = require('./web3/methods/shh'); var watches = require('./web3/methods/watches'); var Filter = require('./web3/filter'); +var IsSyncing = require('./web3/syncing'); var utils = require('./utils/utils'); var formatters = require('./web3/formatters'); var RequestManager = require('./web3/requestmanager'); @@ -2480,6 +2508,10 @@ web3.version = {}; web3.version.api = version.version; web3.eth = {}; +web3.eth.isSyncing = function (callback) { + return new IsSyncing(callback); +}; + /*jshint maxparams:4 */ web3.eth.filter = function (fil, callback) { return new Filter(fil, watches.eth(), formatters.outputLogFormatter, callback); @@ -2499,14 +2531,16 @@ web3.setProvider = function (provider) { web3.isConnected = function(){ return (this.currentProvider && this.currentProvider.isConnected()); }; -web3.reset = function () { - RequestManager.getInstance().reset(); +web3.reset = function (keepIsSyncing) { + RequestManager.getInstance().reset(keepIsSyncing); c.defaultBlock = 'latest'; c.defaultAccount = undefined; }; web3.toHex = utils.toHex; web3.toAscii = utils.toAscii; +web3.toUtf8 = utils.toUtf8; web3.fromAscii = utils.fromAscii; +web3.fromUtf8 = utils.fromUtf8; web3.toDecimal = utils.toDecimal; web3.fromDecimal = utils.fromDecimal; web3.toBigNumber = utils.toBigNumber; @@ -2569,7 +2603,7 @@ setupMethods(web3.shh, shh.methods); module.exports = web3; -},{"./utils/config":18,"./utils/sha3":19,"./utils/utils":20,"./version.json":21,"./web3/batch":24,"./web3/filter":28,"./web3/formatters":29,"./web3/method":35,"./web3/methods/db":36,"./web3/methods/eth":37,"./web3/methods/net":38,"./web3/methods/shh":39,"./web3/methods/watches":40,"./web3/property":42,"./web3/requestmanager":43}],23:[function(require,module,exports){ +},{"./utils/config":18,"./utils/sha3":19,"./utils/utils":20,"./version.json":21,"./web3/batch":24,"./web3/filter":28,"./web3/formatters":29,"./web3/method":35,"./web3/methods/db":36,"./web3/methods/eth":37,"./web3/methods/net":38,"./web3/methods/shh":39,"./web3/methods/watches":40,"./web3/property":42,"./web3/requestmanager":43,"./web3/syncing":44}],23:[function(require,module,exports){ /* This file is part of ethereum.js. @@ -2586,7 +2620,7 @@ module.exports = web3; 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 allevents.js * @author Marek Kotewicz <marek@ethdev.com> * @date 2014 @@ -2675,7 +2709,7 @@ module.exports = AllSolidityEvents; 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 batch.js * @author Marek Kotewicz <marek@ethdev.com> * @date 2015 @@ -2720,7 +2754,7 @@ Batch.prototype.execute = function () { requests[index].callback(null, (requests[index].format ? requests[index].format(result.result) : result.result)); } }); - }); + }); }; module.exports = Batch; @@ -2743,13 +2777,13 @@ module.exports = Batch; 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 contract.js * @author Marek Kotewicz <marek@ethdev.com> * @date 2014 */ -var web3 = require('../web3'); +var web3 = require('../web3'); var utils = require('../utils/utils'); var coder = require('../solidity/coder'); var SolidityEvent = require('./event'); @@ -2806,7 +2840,7 @@ var addEventsToContract = function (contract, abi) { var All = new AllEvents(events, contract.address); All.attachToContract(contract); - + events.map(function (json) { return new SolidityEvent(json, contract.address); }).forEach(function (e) { @@ -2846,7 +2880,7 @@ var checkForContractAddress = function(contract, abi, callback){ // stop watching after 50 blocks (timeout) if(count > 50) { - + filter.stopWatching(); callbackFired = true; @@ -2866,7 +2900,7 @@ var checkForContractAddress = function(contract, abi, callback){ if(callbackFired) return; - + filter.stopWatching(); callbackFired = true; @@ -2910,7 +2944,7 @@ var ContractFactory = function (abi) { /** * Should be called to create new contract on a blockchain - * + * * @method new * @param {Any} contract constructor param1 (optional) * @param {Any} contract constructor param2 (optional) @@ -2984,10 +3018,10 @@ ContractFactory.prototype.at = function (address, callback) { // attach functions addFunctionsToContract(contract, this.abi); addEventsToContract(contract, this.abi); - + if (callback) { callback(null, contract); - } + } return contract; }; @@ -3022,7 +3056,7 @@ module.exports = contract; 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 errors.js * @author Marek Kotewicz <marek@ethdev.com> * @date 2015 @@ -3062,7 +3096,7 @@ module.exports = { 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 event.js * @author Marek Kotewicz <marek@ethdev.com> * @date 2014 @@ -3132,7 +3166,7 @@ SolidityEvent.prototype.signature = function () { /** * Should be used to encode indexed params and options to one final object - * + * * @method encode * @param {Object} indexed * @param {Object} options @@ -3163,7 +3197,7 @@ SolidityEvent.prototype.encode = function (indexed, options) { if (value === undefined || value === null) { return null; } - + if (utils.isArray(value)) { return value.map(function (v) { return '0x' + coder.encodeParam(i.type, v); @@ -3185,17 +3219,17 @@ SolidityEvent.prototype.encode = function (indexed, options) { * @return {Object} result object with decoded indexed && not indexed params */ SolidityEvent.prototype.decode = function (data) { - + data.data = data.data || ''; data.topics = data.topics || []; var argTopics = this._anonymous ? data.topics : data.topics.slice(1); var indexedData = argTopics.map(function (topics) { return topics.slice(2); }).join(""); - var indexedParams = coder.decodeParams(this.types(true), indexedData); + var indexedParams = coder.decodeParams(this.types(true), indexedData); var notIndexedData = data.data.slice(2); var notIndexedParams = coder.decodeParams(this.types(false), notIndexedData); - + var result = formatters.outputLogFormatter(data); result.event = this.displayName(); result.address = data.address; @@ -3230,7 +3264,7 @@ SolidityEvent.prototype.execute = function (indexed, options, callback) { indexed = {}; } } - + var o = this.encode(indexed, options); var formatter = this.decode.bind(this); return new Filter(o, watches.eth(), formatter, callback); @@ -3301,7 +3335,7 @@ var toTopic = function(value){ if(value.indexOf('0x') === 0) return value; else - return utils.fromAscii(value); + return utils.fromUtf8(value); }; /// This method should be called on options object, to verify deprecated properties && lazy load dynamic ones @@ -3311,7 +3345,7 @@ var getOptions = function (options) { if (utils.isString(options)) { return options; - } + } options = options || {}; @@ -3327,8 +3361,8 @@ var getOptions = function (options) { to: options.to, address: options.address, fromBlock: formatters.inputBlockNumberFormatter(options.fromBlock), - toBlock: formatters.inputBlockNumberFormatter(options.toBlock) - }; + toBlock: formatters.inputBlockNumberFormatter(options.toBlock) + }; }; /** @@ -3336,7 +3370,7 @@ Adds the callback and sets up the methods, to iterate over the results. @method getLogsAtStart @param {Object} self -@param {funciton} +@param {funciton} */ var getLogsAtStart = function(self, callback){ // call getFilterLogs for the first watch callback start @@ -3371,12 +3405,14 @@ var pollFilter = function(self) { }); } - messages.forEach(function (message) { - message = self.formatter ? self.formatter(message) : message; - self.callbacks.forEach(function (callback) { - callback(null, message); + if(utils.isArray(messages)) { + messages.forEach(function (message) { + message = self.formatter ? self.formatter(message) : message; + self.callbacks.forEach(function (callback) { + callback(null, message); + }); }); - }); + } }; RequestManager.getInstance().startPolling({ @@ -3396,6 +3432,7 @@ var Filter = function (options, methods, formatter, callback) { this.implementation = implementation; this.filterId = null; this.callbacks = []; + this.getLogsCallbacks = []; this.pollFilters = []; this.formatter = formatter; this.implementation.newFilter(this.options, function(error, id){ @@ -3406,6 +3443,13 @@ var Filter = function (options, methods, formatter, callback) { } else { self.filterId = id; + // check if there are get pending callbacks as a consequence + // of calling get() with filterId unassigned. + self.getLogsCallbacks.forEach(function (cb){ + self.get(cb); + }); + self.getLogsCallbacks = []; + // get filter logs for the already existing watch calls self.callbacks.forEach(function(cb){ getLogsAtStart(self, cb); @@ -3444,16 +3488,25 @@ Filter.prototype.stopWatching = function () { Filter.prototype.get = function (callback) { var self = this; if (utils.isFunction(callback)) { - this.implementation.getLogs(this.filterId, function(err, res){ - if (err) { - callback(err); - } else { - callback(null, res.map(function (log) { - return self.formatter ? self.formatter(log) : log; - })); - } - }); + if (this.filterId === null) { + // If filterId is not set yet, call it back + // when newFilter() assigns it. + this.getLogsCallbacks.push(callback); + } else { + this.implementation.getLogs(this.filterId, function(err, res){ + if (err) { + callback(err); + } else { + callback(null, res.map(function (log) { + return self.formatter ? self.formatter(log) : log; + })); + } + }); + } } else { + if (this.filterId === null) { + throw new Error('Filter ID Error: filter().get() can\'t be chained synchronous, please provide a callback for the get() method.'); + } var logs = this.implementation.getLogs(this.filterId); return logs.map(function (log) { return self.formatter ? self.formatter(log) : log; @@ -3483,7 +3536,7 @@ module.exports = Filter; 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 * @author Marek Kotewicz <marek@ethdev.com> * @author Fabian Vogelsteller <fabian@ethdev.com> @@ -3550,7 +3603,7 @@ var inputCallFormatter = function (options){ options[key] = utils.fromDecimal(options[key]); }); - return options; + return options; }; /** @@ -3575,12 +3628,12 @@ var inputTransactionFormatter = function (options){ options[key] = utils.fromDecimal(options[key]); }); - return options; + return options; }; /** * Formats the output of a transaction to its proper values - * + * * @method outputTransactionFormatter * @param {Object} tx * @returns {Object} @@ -3599,7 +3652,7 @@ var outputTransactionFormatter = function (tx){ /** * Formats the output of a transaction receipt to its proper values - * + * * @method outputTransactionReceiptFormatter * @param {Object} receipt * @returns {Object} @@ -3625,7 +3678,7 @@ var outputTransactionReceiptFormatter = function (receipt){ * Formats the output of a block to its proper values * * @method outputBlockFormatter - * @param {Object} block + * @param {Object} block * @returns {Object} */ var outputBlockFormatter = function(block) { @@ -3653,7 +3706,7 @@ var outputBlockFormatter = function(block) { /** * Formats the output of a log - * + * * @method outputLogFormatter * @param {Object} log object * @returns {Object} log @@ -3690,10 +3743,10 @@ var inputPostFormatter = function(post) { // format the following options post.topics = post.topics.map(function(topic){ - return utils.fromAscii(topic); + return utils.fromUtf8(topic); }); - return post; + return post; }; /** @@ -3710,7 +3763,7 @@ var outputPostFormatter = function(post){ post.ttl = utils.toDecimal(post.ttl); post.workProved = utils.toDecimal(post.workProved); post.payloadRaw = post.payload; - post.payload = utils.toAscii(post.payload); + post.payload = utils.toUtf8(post.payload); if (utils.isJson(post.payload)) { post.payload = JSON.parse(post.payload); @@ -3721,7 +3774,7 @@ var outputPostFormatter = function(post){ post.topics = []; } post.topics = post.topics.map(function(topic){ - return utils.toAscii(topic); + return utils.toUtf8(topic); }); return post; @@ -3739,6 +3792,16 @@ var inputAddressFormatter = function (address) { throw 'invalid address'; }; + +var outputSyncingFormatter = function(result) { + + result.startingBlock = utils.toDecimal(result.startingBlock); + result.currentBlock = utils.toDecimal(result.currentBlock); + result.highestBlock = utils.toDecimal(result.highestBlock); + + return result; +}; + module.exports = { inputDefaultBlockNumberFormatter: inputDefaultBlockNumberFormatter, inputBlockNumberFormatter: inputBlockNumberFormatter, @@ -3751,7 +3814,8 @@ module.exports = { outputTransactionReceiptFormatter: outputTransactionReceiptFormatter, outputBlockFormatter: outputBlockFormatter, outputLogFormatter: outputLogFormatter, - outputPostFormatter: outputPostFormatter + outputPostFormatter: outputPostFormatter, + outputSyncingFormatter: outputSyncingFormatter }; @@ -3869,8 +3933,8 @@ SolidityFunction.prototype.call = function () { if (!callback) { var output = web3.eth.call(payload, defaultBlock); return this.unpackOutput(output); - } - + } + var self = this; web3.eth.call(payload, defaultBlock, function (error, output) { callback(error, self.unpackOutput(output)); @@ -3944,11 +4008,11 @@ SolidityFunction.prototype.request = function () { var callback = this.extractCallback(args); var payload = this.toPayload(args); var format = this.unpackOutput.bind(this); - + return { method: this._constant ? 'eth_call' : 'eth_sendTransaction', callback: callback, - params: [payload], + params: [payload], format: format }; }; @@ -4079,7 +4143,7 @@ HttpProvider.prototype.send = function (payload) { try { result = JSON.parse(result); } catch(e) { - throw errors.InvalidResponse(request.responseText); + throw errors.InvalidResponse(request.responseText); } return result; @@ -4093,7 +4157,7 @@ HttpProvider.prototype.send = function (payload) { * @param {Function} callback triggered on end with (err, result) */ HttpProvider.prototype.sendAsync = function (payload, callback) { - var request = this.prepareRequest(true); + var request = this.prepareRequest(true); request.onreadystatechange = function() { if (request.readyState === 4) { @@ -4103,13 +4167,13 @@ HttpProvider.prototype.sendAsync = function (payload, callback) { try { result = JSON.parse(result); } catch(e) { - error = errors.InvalidResponse(request.responseText); + error = errors.InvalidResponse(request.responseText); } callback(error, result); } }; - + try { request.send(JSON.stringify(payload)); } catch(error) { @@ -4157,7 +4221,7 @@ module.exports = HttpProvider; 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 iban.js * @author Marek Kotewicz <marek@ethdev.com> * @date 2015 @@ -4289,7 +4353,7 @@ Iban.isValid = function (iban) { * @returns {Boolean} true if it is, otherwise false */ Iban.prototype.isValid = function () { - return /^XE[0-9]{2}(ETH[0-9A-Z]{13}|[0-9A-Z]{30})$/.test(this._iban) && + return /^XE[0-9]{2}(ETH[0-9A-Z]{13}|[0-9A-Z]{30,31})$/.test(this._iban) && mod9710(iso13616Prepare(this._iban)) === 1; }; @@ -4357,7 +4421,7 @@ Iban.prototype.address = function () { var base36 = this._iban.substr(4); var asBn = new BigNumber(base36, 36); return padLeft(asBn.toString(16), 20); - } + } return ''; }; @@ -4401,9 +4465,9 @@ var errorTimeout = function (method, id) { var err = { "jsonrpc": "2.0", "error": { - "code": -32603, + "code": -32603, "message": "IPC Request timed out for method \'" + method + "\'" - }, + }, "id": id }; return JSON.stringify(err); @@ -4413,7 +4477,7 @@ var IpcProvider = function (path, net) { var _this = this; this.responseCallbacks = {}; this.path = path; - + this.connection = net.connect({path: this.path}); this.connection.on('error', function(e){ @@ -4423,7 +4487,7 @@ var IpcProvider = function (path, net) { this.connection.on('end', function(){ _this._timeout(); - }); + }); // LISTEN FOR CONNECTION RESPONSES @@ -4462,7 +4526,7 @@ Will parse the response and make an array out of it. IpcProvider.prototype._parseResponse = function(data) { var _this = this, returnValues = []; - + // DE-CHUNKER var dechunkedData = data .replace(/\}\{/g,'}|--|{') // }{ @@ -4566,7 +4630,7 @@ IpcProvider.prototype.send = function (payload) { try { result = JSON.parse(data); } catch(e) { - throw errors.InvalidResponse(data); + throw errors.InvalidResponse(data); } return result; @@ -4743,7 +4807,7 @@ Method.prototype.extractCallback = function (args) { /** * Should be called to check if the number of arguments is correct - * + * * @method validateArgs * @param {Array} arguments * @throws {Error} if it is not @@ -4756,7 +4820,7 @@ Method.prototype.validateArgs = function (args) { /** * Should be called to format input args of method - * + * * @method formatInput * @param {Array} * @return {Array} @@ -4784,7 +4848,7 @@ Method.prototype.formatOutput = function (result) { /** * Should attach function to method - * + * * @method attachToObject * @param {Object} * @param {Function} @@ -4798,7 +4862,7 @@ Method.prototype.attachToObject = function (obj) { obj[name[0]] = obj[name[0]] || {}; obj[name[0]][name[1]] = func; } else { - obj[name[0]] = func; + obj[name[0]] = func; } }; @@ -5186,6 +5250,11 @@ var properties = [ outputFormatter: utils.toDecimal }), new Property({ + name: 'syncing', + getter: 'eth_syncing', + outputFormatter: formatters.outputSyncingFormatter + }), + new Property({ name: 'gasPrice', getter: 'eth_gasPrice', outputFormatter: formatters.outputBigNumberFormatter @@ -5284,8 +5353,8 @@ var Method = require('../method'); var formatters = require('../formatters'); var post = new Method({ - name: 'post', - call: 'shh_post', + name: 'post', + call: 'shh_post', params: 1, inputFormatter: [formatters.inputPostFormatter] }); @@ -5460,7 +5529,7 @@ module.exports = { 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 namereg.js * @author Marek Kotewicz <marek@ethdev.com> * @date 2015 @@ -5516,7 +5585,7 @@ var Property = function (options) { /** * Should be called to format input args of method - * + * * @method formatInput * @param {Array} * @return {Array} @@ -5551,7 +5620,7 @@ Property.prototype.extractCallback = function (args) { /** * Should attach function to method - * + * * @method attachToObject * @param {Object} * @param {Function} @@ -5568,7 +5637,7 @@ Property.prototype.attachToObject = function (obj) { obj = obj[names[0]]; name = names[1]; } - + Object.defineProperty(obj, name, proto); var toAsyncName = function (prefix, name) { @@ -5648,7 +5717,7 @@ module.exports = Property; 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 requestmanager.js * @author Jeffrey Wilcke <jeff@ethdev.com> * @author Marek Kotewicz <marek@ethdev.com> @@ -5730,7 +5799,7 @@ RequestManager.prototype.sendAsync = function (data, callback) { if (err) { return callback(err); } - + if (!Jsonrpc.getInstance().isValidResponse(result)) { return callback(errors.InvalidResponse(result)); } @@ -5763,7 +5832,7 @@ RequestManager.prototype.sendBatch = function (data, callback) { } callback(err, results); - }); + }); }; /** @@ -5811,11 +5880,15 @@ RequestManager.prototype.stopPolling = function (pollId) { * * @method reset */ -RequestManager.prototype.reset = function () { +RequestManager.prototype.reset = function (keepIsSyncing) { for (var key in this.polls) { - this.polls[key].uninstall(); + // remove all polls, except sync polls, + // they need to be removed manually by calling syncing.stopWatching() + if(!keepIsSyncing || key.indexOf('syncPoll_') === -1) { + this.polls[key].uninstall(); + delete this.polls[key]; + } } - this.polls = {}; if (this.timeout) { clearTimeout(this.timeout); @@ -5843,10 +5916,10 @@ RequestManager.prototype.poll = function () { } var pollsData = []; - var pollsKeys = []; + var pollsIds = []; for (var key in this.polls) { pollsData.push(this.polls[key].data); - pollsKeys.push(key); + pollsIds.push(key); } if (pollsData.length === 0) { @@ -5855,8 +5928,17 @@ RequestManager.prototype.poll = function () { var payload = Jsonrpc.getInstance().toBatchPayload(pollsData); + // map the request id to they poll id + var pollsIdMap = {}; + payload.forEach(function(load, index){ + pollsIdMap[load.id] = pollsIds[index]; + }); + + var self = this; this.provider.sendAsync(payload, function (error, results) { + + // TODO: console log? if (error) { return; @@ -5865,25 +5947,23 @@ RequestManager.prototype.poll = function () { if (!utils.isArray(results)) { throw errors.InvalidResponse(results); } + results.map(function (result) { + var id = pollsIdMap[result.id]; - results.map(function (result, index) { - var key = pollsKeys[index]; // make sure the filter is still installed after arrival of the request - if (self.polls[key]) { - result.callback = self.polls[key].callback; + if (self.polls[id]) { + result.callback = self.polls[id].callback; return result; } else return false; }).filter(function (result) { - return !!result; + return !!result; }).filter(function (result) { var valid = Jsonrpc.getInstance().isValidResponse(result); if (!valid) { result.callback(errors.InvalidResponse(result)); } return valid; - }).filter(function (result) { - return utils.isArray(result.result) && result.result.length > 0; }).forEach(function (result) { result.callback(null, result.result); }); @@ -5910,7 +5990,110 @@ module.exports = RequestManager; 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 syncing.js + * @authors: + * Fabian Vogelsteller <fabian@ethdev.com> + * @date 2015 + */ + +var RequestManager = require('./requestmanager'); +var Method = require('./method'); +var formatters = require('./formatters'); +var utils = require('../utils/utils'); + + + +/** +Adds the callback and sets up the methods, to iterate over the results. + +@method pollSyncing +@param {Object} self +*/ +var pollSyncing = function(self) { + var lastSyncState = false; + + var onMessage = function (error, sync) { + if (error) { + return self.callbacks.forEach(function (callback) { + callback(error); + }); + } + + if(utils.isObject(sync)) + sync = self.implementation.outputFormatter(sync); + + self.callbacks.forEach(function (callback) { + if(lastSyncState !== sync) { + + // call the callback with true first so the app can stop anything, before receiving the sync data + if(!lastSyncState && utils.isObject(sync)) + callback(null, true); + + // call on the next CPU cycle, so the actions of the sync stop can be processes first + setTimeout(function() { + callback(null, sync); + }, 1); + + lastSyncState = sync; + } + }); + }; + + RequestManager.getInstance().startPolling({ + method: self.implementation.call, + params: [], + }, self.pollId, onMessage, self.stopWatching.bind(self)); + +}; + +var IsSyncing = function (callback) { + this.pollId = 'syncPoll_'+ Math.floor(Math.random() * 1000); + this.callbacks = []; + this.implementation = new Method({ + name: 'isSyncing', + call: 'eth_syncing', + params: 0, + outputFormatter: formatters.outputSyncingFormatter + }); + + this.addCallback(callback); + pollSyncing(this); + + return this; +}; + +IsSyncing.prototype.addCallback = function (callback) { + if(callback) + this.callbacks.push(callback); + return this; +}; + +IsSyncing.prototype.stopWatching = function () { + RequestManager.getInstance().stopPolling(this.pollId); + this.callbacks = []; +}; + +module.exports = IsSyncing; + + +},{"../utils/utils":20,"./formatters":29,"./method":35,"./requestmanager":43}],45:[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 transfer.js * @author Marek Kotewicz <marek@ethdev.com> * @date 2015 @@ -5932,7 +6115,7 @@ var exchangeAbi = require('../contracts/SmartExchange.json'); * @param {Function} callback, callback */ var transfer = function (from, to, value, callback) { - var iban = new Iban(to); + var iban = new Iban(to); if (!iban.isValid()) { throw new Error('invalid iban address'); } @@ -5940,7 +6123,7 @@ var transfer = function (from, to, value, callback) { if (iban.isDirect()) { return transferToAddress(from, iban.address(), value, callback); } - + if (!callback) { var address = namereg.addr(iban.institution()); return deposit(from, address, value, iban.client()); @@ -5949,7 +6132,7 @@ var transfer = function (from, to, value, callback) { namereg.addr(iban.institution(), function (err, address) { return deposit(from, address, value, iban.client(), callback); }); - + }; /** @@ -5990,9 +6173,9 @@ var deposit = function (from, to, value, client, callback) { module.exports = transfer; -},{"../contracts/SmartExchange.json":3,"../web3":22,"./contract":25,"./iban":32,"./namereg":41}],45:[function(require,module,exports){ +},{"../contracts/SmartExchange.json":3,"../web3":22,"./contract":25,"./iban":32,"./namereg":41}],46:[function(require,module,exports){ -},{}],46:[function(require,module,exports){ +},{}],47:[function(require,module,exports){ ;(function (root, factory) { if (typeof exports === "object") { // CommonJS @@ -6735,7 +6918,7 @@ module.exports = transfer; return CryptoJS; })); -},{}],47:[function(require,module,exports){ +},{}],48:[function(require,module,exports){ ;(function (root, factory, undef) { if (typeof exports === "object") { // CommonJS @@ -7059,7 +7242,7 @@ module.exports = transfer; return CryptoJS.SHA3; })); -},{"./core":46,"./x64-core":48}],48:[function(require,module,exports){ +},{"./core":47,"./x64-core":49}],49:[function(require,module,exports){ ;(function (root, factory) { if (typeof exports === "object") { // CommonJS @@ -7364,7 +7547,253 @@ module.exports = transfer; return CryptoJS; })); -},{"./core":46}],"bignumber.js":[function(require,module,exports){ +},{"./core":47}],50:[function(require,module,exports){ +/*! https://mths.be/utf8js v2.0.0 by @mathias */ +;(function(root) { + + // Detect free variables 'exports' + var freeExports = typeof exports == 'object' && exports; + + // Detect free variable 'module' + var freeModule = typeof module == 'object' && module && + module.exports == freeExports && module; + + // Detect free variable 'global', from Node.js or Browserified code, + // and use it as 'root' + var freeGlobal = typeof global == 'object' && global; + if (freeGlobal.global === freeGlobal || freeGlobal.window === freeGlobal) { + root = freeGlobal; + } + + /*--------------------------------------------------------------------------*/ + + var stringFromCharCode = String.fromCharCode; + + // Taken from https://mths.be/punycode + function ucs2decode(string) { + var output = []; + var counter = 0; + var length = string.length; + var value; + var extra; + while (counter < length) { + value = string.charCodeAt(counter++); + if (value >= 0xD800 && value <= 0xDBFF && counter < length) { + // high surrogate, and there is a next character + extra = string.charCodeAt(counter++); + if ((extra & 0xFC00) == 0xDC00) { // low surrogate + output.push(((value & 0x3FF) << 10) + (extra & 0x3FF) + 0x10000); + } else { + // unmatched surrogate; only append this code unit, in case the next + // code unit is the high surrogate of a surrogate pair + output.push(value); + counter--; + } + } else { + output.push(value); + } + } + return output; + } + + // Taken from https://mths.be/punycode + function ucs2encode(array) { + var length = array.length; + var index = -1; + var value; + var output = ''; + while (++index < length) { + value = array[index]; + if (value > 0xFFFF) { + value -= 0x10000; + output += stringFromCharCode(value >>> 10 & 0x3FF | 0xD800); + value = 0xDC00 | value & 0x3FF; + } + output += stringFromCharCode(value); + } + return output; + } + + function checkScalarValue(codePoint) { + if (codePoint >= 0xD800 && codePoint <= 0xDFFF) { + throw Error( + 'Lone surrogate U+' + codePoint.toString(16).toUpperCase() + + ' is not a scalar value' + ); + } + } + /*--------------------------------------------------------------------------*/ + + function createByte(codePoint, shift) { + return stringFromCharCode(((codePoint >> shift) & 0x3F) | 0x80); + } + + function encodeCodePoint(codePoint) { + if ((codePoint & 0xFFFFFF80) == 0) { // 1-byte sequence + return stringFromCharCode(codePoint); + } + var symbol = ''; + if ((codePoint & 0xFFFFF800) == 0) { // 2-byte sequence + symbol = stringFromCharCode(((codePoint >> 6) & 0x1F) | 0xC0); + } + else if ((codePoint & 0xFFFF0000) == 0) { // 3-byte sequence + checkScalarValue(codePoint); + symbol = stringFromCharCode(((codePoint >> 12) & 0x0F) | 0xE0); + symbol += createByte(codePoint, 6); + } + else if ((codePoint & 0xFFE00000) == 0) { // 4-byte sequence + symbol = stringFromCharCode(((codePoint >> 18) & 0x07) | 0xF0); + symbol += createByte(codePoint, 12); + symbol += createByte(codePoint, 6); + } + symbol += stringFromCharCode((codePoint & 0x3F) | 0x80); + return symbol; + } + + function utf8encode(string) { + var codePoints = ucs2decode(string); + var length = codePoints.length; + var index = -1; + var codePoint; + var byteString = ''; + while (++index < length) { + codePoint = codePoints[index]; + byteString += encodeCodePoint(codePoint); + } + return byteString; + } + + /*--------------------------------------------------------------------------*/ + + function readContinuationByte() { + if (byteIndex >= byteCount) { + throw Error('Invalid byte index'); + } + + var continuationByte = byteArray[byteIndex] & 0xFF; + byteIndex++; + + if ((continuationByte & 0xC0) == 0x80) { + return continuationByte & 0x3F; + } + + // If we end up here, it’s not a continuation byte + throw Error('Invalid continuation byte'); + } + + function decodeSymbol() { + var byte1; + var byte2; + var byte3; + var byte4; + var codePoint; + + if (byteIndex > byteCount) { + throw Error('Invalid byte index'); + } + + if (byteIndex == byteCount) { + return false; + } + + // Read first byte + byte1 = byteArray[byteIndex] & 0xFF; + byteIndex++; + + // 1-byte sequence (no continuation bytes) + if ((byte1 & 0x80) == 0) { + return byte1; + } + + // 2-byte sequence + if ((byte1 & 0xE0) == 0xC0) { + var byte2 = readContinuationByte(); + codePoint = ((byte1 & 0x1F) << 6) | byte2; + if (codePoint >= 0x80) { + return codePoint; + } else { + throw Error('Invalid continuation byte'); + } + } + + // 3-byte sequence (may include unpaired surrogates) + if ((byte1 & 0xF0) == 0xE0) { + byte2 = readContinuationByte(); + byte3 = readContinuationByte(); + codePoint = ((byte1 & 0x0F) << 12) | (byte2 << 6) | byte3; + if (codePoint >= 0x0800) { + checkScalarValue(codePoint); + return codePoint; + } else { + throw Error('Invalid continuation byte'); + } + } + + // 4-byte sequence + if ((byte1 & 0xF8) == 0xF0) { + byte2 = readContinuationByte(); + byte3 = readContinuationByte(); + byte4 = readContinuationByte(); + codePoint = ((byte1 & 0x0F) << 0x12) | (byte2 << 0x0C) | + (byte3 << 0x06) | byte4; + if (codePoint >= 0x010000 && codePoint <= 0x10FFFF) { + return codePoint; + } + } + + throw Error('Invalid UTF-8 detected'); + } + + var byteArray; + var byteCount; + var byteIndex; + function utf8decode(byteString) { + byteArray = ucs2decode(byteString); + byteCount = byteArray.length; + byteIndex = 0; + var codePoints = []; + var tmp; + while ((tmp = decodeSymbol()) !== false) { + codePoints.push(tmp); + } + return ucs2encode(codePoints); + } + + /*--------------------------------------------------------------------------*/ + + var utf8 = { + 'version': '2.0.0', + 'encode': utf8encode, + 'decode': utf8decode + }; + + // Some AMD build optimizers, like r.js, check for specific condition patterns + // like the following: + if ( + typeof define == 'function' && + typeof define.amd == 'object' && + define.amd + ) { + define(function() { + return utf8; + }); + } else if (freeExports && !freeExports.nodeType) { + if (freeModule) { // in Node.js or RingoJS v0.8.0+ + freeModule.exports = utf8; + } else { // in Narwhal or RingoJS v0.7.0- + var object = {}; + var hasOwnProperty = object.hasOwnProperty; + for (var key in utf8) { + hasOwnProperty.call(utf8, key) && (freeExports[key] = utf8[key]); + } + } + } else { // in Rhino or a web browser + root.utf8 = utf8; + } + +}(this)); + +},{}],"bignumber.js":[function(require,module,exports){ 'use strict'; module.exports = BigNumber; // jshint ignore:line @@ -7391,6 +7820,6 @@ if (typeof window !== 'undefined' && typeof window.web3 === 'undefined') { module.exports = web3; -},{"./lib/web3":22,"./lib/web3/contract":25,"./lib/web3/httpprovider":31,"./lib/web3/iban":32,"./lib/web3/ipcprovider":33,"./lib/web3/namereg":41,"./lib/web3/transfer":44}]},{},["web3"]) +},{"./lib/web3":22,"./lib/web3/contract":25,"./lib/web3/httpprovider":31,"./lib/web3/iban":32,"./lib/web3/ipcprovider":33,"./lib/web3/namereg":41,"./lib/web3/transfer":45}]},{},["web3"]) //# sourceMappingURL=web3-light.js.map ` diff --git a/miner/agent.go b/miner/agent.go index 7ccf8d2e0..e80b222c8 100644 --- a/miner/agent.go +++ b/miner/agent.go @@ -19,11 +19,12 @@ package miner import ( "sync" + "sync/atomic" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/logger" "github.com/ethereum/go-ethereum/logger/glog" "github.com/ethereum/go-ethereum/pow" - "sync/atomic" ) type CpuAgent struct { @@ -63,7 +64,7 @@ func (self *CpuAgent) Stop() { func (self *CpuAgent) Start() { self.mu.Lock() defer self.mu.Unlock() - + if !atomic.CompareAndSwapInt32(&self.isMining, 0, 1) { return // agent already started } diff --git a/miner/worker.go b/miner/worker.go index 7ad7475ea..22d0b9b6e 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -29,6 +29,7 @@ import ( "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/logger" "github.com/ethereum/go-ethereum/logger/glog" @@ -100,7 +101,7 @@ type worker struct { eth core.Backend chain *core.ChainManager proc *core.BlockProcessor - chainDb common.Database + chainDb ethdb.Database coinbase common.Address gasPrice *big.Int diff --git a/rpc/api/admin.go b/rpc/api/admin.go index 5e392ae32..8af69b189 100644 --- a/rpc/api/admin.go +++ b/rpc/api/admin.go @@ -55,7 +55,6 @@ var ( "admin_exportChain": (*adminApi).ExportChain, "admin_importChain": (*adminApi).ImportChain, "admin_verbosity": (*adminApi).Verbosity, - "admin_chainSyncStatus": (*adminApi).ChainSyncStatus, "admin_setSolc": (*adminApi).SetSolc, "admin_datadir": (*adminApi).DataDir, "admin_startRPC": (*adminApi).StartRPC, @@ -232,17 +231,6 @@ func (self *adminApi) Verbosity(req *shared.Request) (interface{}, error) { return true, nil } -func (self *adminApi) ChainSyncStatus(req *shared.Request) (interface{}, error) { - pending, cached, importing, estimate := self.ethereum.Downloader().Stats() - - return map[string]interface{}{ - "blocksAvailable": pending, - "blocksWaitingForImport": cached, - "importing": importing, - "estimate": estimate.String(), - }, nil -} - func (self *adminApi) SetSolc(req *shared.Request) (interface{}, error) { args := new(SetSolcArgs) if err := self.coder.Decode(req.Params, &args); err != nil { diff --git a/rpc/api/admin_js.go b/rpc/api/admin_js.go index 25dbb4a8d..413ea8d47 100644 --- a/rpc/api/admin_js.go +++ b/rpc/api/admin_js.go @@ -143,10 +143,6 @@ web3._extend({ new web3._extend.Property({ name: 'datadir', getter: 'admin_datadir' - }), - new web3._extend.Property({ - name: 'chainSyncStatus', - getter: 'admin_chainSyncStatus' }) ] }); diff --git a/rpc/api/eth.go b/rpc/api/eth.go index a93e41157..30366a951 100644 --- a/rpc/api/eth.go +++ b/rpc/api/eth.go @@ -55,6 +55,7 @@ var ( "eth_protocolVersion": (*ethApi).ProtocolVersion, "eth_coinbase": (*ethApi).Coinbase, "eth_mining": (*ethApi).IsMining, + "eth_syncing": (*ethApi).IsSyncing, "eth_gasPrice": (*ethApi).GasPrice, "eth_getStorage": (*ethApi).GetStorage, "eth_storageAt": (*ethApi).GetStorage, @@ -166,6 +167,20 @@ func (self *ethApi) IsMining(req *shared.Request) (interface{}, error) { return self.xeth.IsMining(), nil } +func (self *ethApi) IsSyncing(req *shared.Request) (interface{}, error) { + current := self.ethereum.ChainManager().CurrentBlock().NumberU64() + origin, height := self.ethereum.Downloader().Boundaries() + + if current < height { + return map[string]interface{}{ + "startingBlock": newHexNum(big.NewInt(int64(origin)).Bytes()), + "currentBlock": newHexNum(big.NewInt(int64(current)).Bytes()), + "highestBlock": newHexNum(big.NewInt(int64(height)).Bytes()), + }, nil + } + return false, nil +} + func (self *ethApi) GasPrice(req *shared.Request) (interface{}, error) { return newHexNum(self.xeth.DefaultGasPrice().Bytes()), nil } diff --git a/rpc/api/utils.go b/rpc/api/utils.go index 5072dc2cd..76b2c531d 100644 --- a/rpc/api/utils.go +++ b/rpc/api/utils.go @@ -32,7 +32,6 @@ var ( AutoCompletion = map[string][]string{ "admin": []string{ "addPeer", - "chainSyncStatus", "datadir", "exportChain", "getContractInfo", @@ -99,6 +98,7 @@ var ( "sendRawTransaction", "sendTransaction", "sign", + "syncing", }, "miner": []string{ "hashrate", diff --git a/tests/block_test_util.go b/tests/block_test_util.go index 30488951d..e31ca6344 100644 --- a/tests/block_test_util.go +++ b/tests/block_test_util.go @@ -197,7 +197,7 @@ func (test *BlockTest) makeEthConfig() *eth.Config { Verbosity: 5, Etherbase: common.Address{}, AccountManager: accounts.NewManager(ks), - NewDB: func(path string) (common.Database, error) { return ethdb.NewMemDatabase() }, + NewDB: func(path string) (ethdb.Database, error) { return ethdb.NewMemDatabase() }, } } diff --git a/tests/util.go b/tests/util.go index a9b5011a9..72d927ada 100644 --- a/tests/util.go +++ b/tests/util.go @@ -27,6 +27,7 @@ import ( "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/ethdb" ) func checkLogs(tlog []Log, logs state.Logs) error { @@ -87,7 +88,7 @@ func (self Log) Topics() [][]byte { return t } -func StateObjectFromAccount(db common.Database, addr string, account Account) *state.StateObject { +func StateObjectFromAccount(db ethdb.Database, addr string, account Account) *state.StateObject { obj := state.NewStateObject(common.HexToAddress(addr), db) obj.SetBalance(common.Big(account.Balance)) |