diff --git a/blockdag/common_test.go b/blockdag/common_test.go index bfd3f90b2..b05e8e572 100644 --- a/blockdag/common_test.go +++ b/blockdag/common_test.go @@ -347,7 +347,7 @@ func (b *BlockDAG) TstSetCoinbaseMaturity(maturity uint16) { // important to note that this chain has no database associated with it, so // it is not usable with all functions and the tests must take care when making // use of it. -func newFakeDag(params *dagconfig.Params) *BlockDAG { +func newFakeDAG(params *dagconfig.Params) *BlockDAG { // Create a genesis block node and block index index populated with it // for use when creating the fake chain below. node := newBlockNode(¶ms.GenesisBlock.Header, newSet()) diff --git a/blockdag/dag.go b/blockdag/dag.go index 919d17492..9373b32bd 100644 --- a/blockdag/dag.go +++ b/blockdag/dag.go @@ -188,9 +188,9 @@ type BlockDAG struct { notifications []NotificationCallback } -// HaveBlock returns whether or not the chain instance has the block represented +// HaveBlock returns whether or not the DAG instance has the block represented // by the passed hash. This includes checking the various places a block can -// be like part of the main chain, on a side chain, or in the orphan pool. +// be in, like part of the DAG or the orphan pool. // // This function is safe for concurrent access. func (b *BlockDAG) HaveBlock(hash *daghash.Hash) (bool, error) { @@ -201,6 +201,25 @@ func (b *BlockDAG) HaveBlock(hash *daghash.Hash) (bool, error) { return exists || b.IsKnownOrphan(hash), nil } +// HaveBlocks returns whether or not the DAG instances has all blocks represented +// by the passed hashes. This includes checking the various places a block can +// be in, like part of the DAG or the orphan pool. +// +// This function is safe for concurrent access. +func (b *BlockDAG) HaveBlocks(hashes []daghash.Hash) (bool, error) { + for _, hash := range hashes { + haveBlock, err := b.HaveBlock(&hash) + if err != nil { + return false, err + } + if !haveBlock { + return false, nil + } + } + + return true, nil +} + // IsKnownOrphan returns whether the passed hash is currently a known orphan. // Keep in mind that only a limited number of orphans are held onto for a // limited amount of time, so this function must not be used as an absolute diff --git a/blockdag/dag_test.go b/blockdag/dag_test.go index 1c2b58b02..db5c868c9 100644 --- a/blockdag/dag_test.go +++ b/blockdag/dag_test.go @@ -120,8 +120,8 @@ func TestCalcSequenceLock(t *testing.T) { blockVersion := int32(0x20000000) - // Generate enough synthetic blocks to activate CSV. - chain := newFakeDag(netParams) + // Generate enough synthetic blocks for the rest of the test + chain := newFakeDAG(netParams) node := chain.dag.SelectedTip() blockTime := node.Header().Timestamp numBlocksToGenerate := uint32(5) @@ -448,7 +448,7 @@ func TestLocateInventory(t *testing.T) { // genesis -> 1 -> 2 -> ... -> 15 -> 16 -> 17 -> 18 // \-> 16a -> 17a tip := tstTip - dag := newFakeDag(&dagconfig.MainNetParams) + dag := newFakeDAG(&dagconfig.MainNetParams) branch0Nodes := chainedNodes(setFromSlice(dag.dag.Genesis()), 18) branch1Nodes := chainedNodes(setFromSlice(branch0Nodes[14]), 2) for _, node := range branch0Nodes { @@ -788,7 +788,7 @@ func TestHeightToHashRange(t *testing.T) { // genesis -> 1 -> 2 -> ... -> 15 -> 16 -> 17 -> 18 // \-> 16a -> 17a -> 18a (unvalidated) tip := tstTip - chain := newFakeDag(&dagconfig.MainNetParams) + chain := newFakeDAG(&dagconfig.MainNetParams) branch0Nodes := chainedNodes(setFromSlice(chain.dag.Genesis()), 18) branch1Nodes := chainedNodes(setFromSlice(branch0Nodes[14]), 3) for _, node := range branch0Nodes { @@ -880,7 +880,7 @@ func TestIntervalBlockHashes(t *testing.T) { // genesis -> 1 -> 2 -> ... -> 15 -> 16 -> 17 -> 18 // \-> 16a -> 17a -> 18a (unvalidated) tip := tstTip - chain := newFakeDag(&dagconfig.MainNetParams) + chain := newFakeDAG(&dagconfig.MainNetParams) branch0Nodes := chainedNodes(setFromSlice(chain.dag.Genesis()), 18) branch1Nodes := chainedNodes(setFromSlice(branch0Nodes[14]), 3) for _, node := range branch0Nodes { diff --git a/btcjson/dagsvrresults.go b/btcjson/dagsvrresults.go index 2dbeea4a2..50dd85e31 100644 --- a/btcjson/dagsvrresults.go +++ b/btcjson/dagsvrresults.go @@ -10,39 +10,39 @@ import "encoding/json" // the verbose flag is set. When the verbose flag is not set, getblockheader // returns a hex-encoded string. type GetBlockHeaderVerboseResult struct { - Hash string `json:"hash"` - Confirmations uint64 `json:"confirmations"` - Height int32 `json:"height"` - Version int32 `json:"version"` - VersionHex string `json:"versionHex"` - MerkleRoot string `json:"merkleroot"` - Time int64 `json:"time"` - Nonce uint64 `json:"nonce"` - Bits string `json:"bits"` - Difficulty float64 `json:"difficulty"` - PreviousHash string `json:"previousblockhash,omitempty"` - NextHash string `json:"nextblockhash,omitempty"` + Hash string `json:"hash"` + Confirmations uint64 `json:"confirmations"` + Height int32 `json:"height"` + Version int32 `json:"version"` + VersionHex string `json:"versionHex"` + MerkleRoot string `json:"merkleroot"` + Time int64 `json:"time"` + Nonce uint64 `json:"nonce"` + Bits string `json:"bits"` + Difficulty float64 `json:"difficulty"` + PreviousHashes []string `json:"previousblockhashes,omitempty"` + NextHash string `json:"nextblockhash,omitempty"` } // GetBlockVerboseResult models the data from the getblock command when the // verbose flag is set. When the verbose flag is not set, getblock returns a // hex-encoded string. type GetBlockVerboseResult struct { - Hash string `json:"hash"` - Confirmations uint64 `json:"confirmations"` - Size int32 `json:"size"` - Height int64 `json:"height"` - Version int32 `json:"version"` - VersionHex string `json:"versionHex"` - MerkleRoot string `json:"merkleroot"` - Tx []string `json:"tx,omitempty"` - RawTx []TxRawResult `json:"rawtx,omitempty"` - Time int64 `json:"time"` - Nonce uint32 `json:"nonce"` - Bits string `json:"bits"` - Difficulty float64 `json:"difficulty"` - PreviousHash string `json:"previousblockhash"` - NextHash string `json:"nextblockhash,omitempty"` + Hash string `json:"hash"` + Confirmations uint64 `json:"confirmations"` + Size int32 `json:"size"` + Height int64 `json:"height"` + Version int32 `json:"version"` + VersionHex string `json:"versionHex"` + MerkleRoot string `json:"merkleroot"` + Tx []string `json:"tx,omitempty"` + RawTx []TxRawResult `json:"rawtx,omitempty"` + Time int64 `json:"time"` + Nonce uint32 `json:"nonce"` + Bits string `json:"bits"` + Difficulty float64 `json:"difficulty"` + PreviousHashes []string `json:"previousblockhashes"` + NextHash string `json:"nextblockhash,omitempty"` } // CreateMultiSigResult models the data returned from the createmultisig @@ -80,7 +80,7 @@ type GetAddedNodeInfoResult struct { type SoftForkDescription struct { ID string `json:"id"` Version uint32 `json:"version"` - Reject struct { + Reject struct { Status bool `json:"status"` } `json:"reject"` } @@ -133,18 +133,18 @@ type GetBlockTemplateResultAux struct { type GetBlockTemplateResult struct { // Base fields from BIP 0022. CoinbaseAux is optional. One of // CoinbaseTxn or CoinbaseValue must be specified, but not both. - Bits string `json:"bits"` - CurTime int64 `json:"curtime"` - Height int64 `json:"height"` - PreviousHash string `json:"previousblockhash"` - SigOpLimit int64 `json:"sigoplimit,omitempty"` - SizeLimit int64 `json:"sizelimit,omitempty"` - Transactions []GetBlockTemplateResultTx `json:"transactions"` - Version int32 `json:"version"` - CoinbaseAux *GetBlockTemplateResultAux `json:"coinbaseaux,omitempty"` - CoinbaseTxn *GetBlockTemplateResultTx `json:"coinbasetxn,omitempty"` - CoinbaseValue *int64 `json:"coinbasevalue,omitempty"` - WorkID string `json:"workid,omitempty"` + Bits string `json:"bits"` + CurTime int64 `json:"curtime"` + Height int64 `json:"height"` + PreviousHashes []string `json:"previousblockhashes"` + SigOpLimit int64 `json:"sigoplimit,omitempty"` + SizeLimit int64 `json:"sizelimit,omitempty"` + Transactions []GetBlockTemplateResultTx `json:"transactions"` + Version int32 `json:"version"` + CoinbaseAux *GetBlockTemplateResultAux `json:"coinbaseaux,omitempty"` + CoinbaseTxn *GetBlockTemplateResultTx `json:"coinbasetxn,omitempty"` + CoinbaseValue *int64 `json:"coinbasevalue,omitempty"` + WorkID string `json:"workid,omitempty"` // Optional long polling from BIP 0022. LongPollID string `json:"longpollid,omitempty"` diff --git a/btcjson/dagsvrwscmds.go b/btcjson/dagsvrwscmds.go index 81d7702a8..d286d7fcc 100644 --- a/btcjson/dagsvrwscmds.go +++ b/btcjson/dagsvrwscmds.go @@ -177,32 +177,6 @@ func NewStopNotifySpentCmd(outPoints []OutPoint) *StopNotifySpentCmd { } } -// RescanCmd defines the rescan JSON-RPC command. -// -// NOTE: Deprecated. Use RescanBlocksCmd instead. -type RescanCmd struct { - BeginBlock string - Addresses []string - OutPoints []OutPoint - EndBlock *string -} - -// NewRescanCmd returns a new instance which can be used to issue a rescan -// JSON-RPC command. -// -// The parameters which are pointers indicate they are optional. Passing nil -// for optional parameters will use the default value. -// -// NOTE: Deprecated. Use NewRescanBlocksCmd instead. -func NewRescanCmd(beginBlock string, addresses []string, outPoints []OutPoint, endBlock *string) *RescanCmd { - return &RescanCmd{ - BeginBlock: beginBlock, - Addresses: addresses, - OutPoints: outPoints, - EndBlock: endBlock, - } -} - // RescanBlocksCmd defines the rescan JSON-RPC command. // // NOTE: This is a btcd extension ported from github.com/decred/dcrd/dcrjson @@ -236,6 +210,5 @@ func init() { MustRegisterCmd("stopnotifynewtransactions", (*StopNotifyNewTransactionsCmd)(nil), flags) MustRegisterCmd("stopnotifyspent", (*StopNotifySpentCmd)(nil), flags) MustRegisterCmd("stopnotifyreceived", (*StopNotifyReceivedCmd)(nil), flags) - MustRegisterCmd("rescan", (*RescanCmd)(nil), flags) MustRegisterCmd("rescanblocks", (*RescanBlocksCmd)(nil), flags) } diff --git a/btcjson/dagsvrwscmds_test.go b/btcjson/dagsvrwscmds_test.go index dd9c5cf1a..761c28b78 100644 --- a/btcjson/dagsvrwscmds_test.go +++ b/btcjson/dagsvrwscmds_test.go @@ -154,45 +154,6 @@ func TestDAGSvrWsCmds(t *testing.T) { OutPoints: []btcjson.OutPoint{{Hash: "123", Index: 0}}, }, }, - { - name: "rescan", - newCmd: func() (interface{}, error) { - return btcjson.NewCmd("rescan", "123", `["1Address"]`, `[{"hash":"0000000000000000000000000000000000000000000000000000000000000123","index":0}]`) - }, - staticCmd: func() interface{} { - addrs := []string{"1Address"} - ops := []btcjson.OutPoint{{ - Hash: "0000000000000000000000000000000000000000000000000000000000000123", - Index: 0, - }} - return btcjson.NewRescanCmd("123", addrs, ops, nil) - }, - marshalled: `{"jsonrpc":"1.0","method":"rescan","params":["123",["1Address"],[{"hash":"0000000000000000000000000000000000000000000000000000000000000123","index":0}]],"id":1}`, - unmarshalled: &btcjson.RescanCmd{ - BeginBlock: "123", - Addresses: []string{"1Address"}, - OutPoints: []btcjson.OutPoint{{Hash: "0000000000000000000000000000000000000000000000000000000000000123", Index: 0}}, - EndBlock: nil, - }, - }, - { - name: "rescan optional", - newCmd: func() (interface{}, error) { - return btcjson.NewCmd("rescan", "123", `["1Address"]`, `[{"hash":"123","index":0}]`, "456") - }, - staticCmd: func() interface{} { - addrs := []string{"1Address"} - ops := []btcjson.OutPoint{{Hash: "123", Index: 0}} - return btcjson.NewRescanCmd("123", addrs, ops, btcjson.String("456")) - }, - marshalled: `{"jsonrpc":"1.0","method":"rescan","params":["123",["1Address"],[{"hash":"123","index":0}],"456"],"id":1}`, - unmarshalled: &btcjson.RescanCmd{ - BeginBlock: "123", - Addresses: []string{"1Address"}, - OutPoints: []btcjson.OutPoint{{Hash: "123", Index: 0}}, - EndBlock: btcjson.String("456"), - }, - }, { name: "loadtxfilter", newCmd: func() (interface{}, error) { diff --git a/cmd/addblock/import.go b/cmd/addblock/import.go index 97c07bddb..c6256e21e 100644 --- a/cmd/addblock/import.go +++ b/cmd/addblock/import.go @@ -13,14 +13,11 @@ import ( "github.com/daglabs/btcd/blockdag" "github.com/daglabs/btcd/blockdag/indexers" - "github.com/daglabs/btcd/dagconfig/daghash" "github.com/daglabs/btcd/database" "github.com/daglabs/btcd/wire" "github.com/daglabs/btcutil" ) -var zeroHash = daghash.Hash{} - // importResults houses the stats and result as an import operation. type importResults struct { blocksProcessed int64 @@ -89,7 +86,7 @@ func (bi *blockImporter) readBlock() ([]byte, error) { // processBlock potentially imports the block into the database. It first // deserializes the raw block while checking for errors. Already known blocks // are skipped and orphan blocks are considered errors. Finally, it runs the -// block through the chain rules to ensure it follows all rules and matches +// block through the DAG rules to ensure it follows all rules and matches // up to the known checkpoint. Returns whether the block was imported along // with any potential errors. func (bi *blockImporter) processBlock(serializedBlock []byte) (bool, error) { @@ -114,16 +111,16 @@ func (bi *blockImporter) processBlock(serializedBlock []byte) (bool, error) { } // Don't bother trying to process orphans. - prevHash := &block.MsgBlock().Header.PrevBlock - if !prevHash.IsEqual(&zeroHash) { - exists, err := bi.dag.HaveBlock(prevHash) + prevBlocks := block.MsgBlock().Header.PrevBlocks + if len(prevBlocks) > 0 { + exist, err := bi.dag.HaveBlocks(prevBlocks) if err != nil { return false, err } - if !exists { + if !exist { return false, fmt.Errorf("import file contains block "+ "%v which does not link to the available "+ - "block chain", prevHash) + "block DAG", prevBlocks) } } diff --git a/cmd/findcheckpoint/findcheckpoint.go b/cmd/findcheckpoint/findcheckpoint.go index af3f3c0ab..f988ed36b 100644 --- a/cmd/findcheckpoint/findcheckpoint.go +++ b/cmd/findcheckpoint/findcheckpoint.go @@ -34,14 +34,13 @@ func loadBlockDB() (database.DB, error) { return db, nil } -// findCandidates searches the chain backwards for checkpoint candidates and +// findCandidates searches the DAG backwards for checkpoint candidates and // returns a slice of found candidates, if any. It also stops searching for -// candidates at the last checkpoint that is already hard coded into btcchain -// since there is no point in finding candidates before already existing -// checkpoints. -func findCandidates(dag *blockdag.BlockDAG, latestHash *daghash.Hash) ([]*dagconfig.Checkpoint, error) { - // Start with the latest block of the main chain. - block, err := dag.BlockByHash(latestHash) +// candidates at the last checkpoint that is already hard coded since there +// is no point in finding candidates before already existing checkpoints. +func findCandidates(dag *blockdag.BlockDAG, selectedTipHash *daghash.Hash) ([]*dagconfig.Checkpoint, error) { + // Start with the selected tip. + block, err := dag.BlockByHash(selectedTipHash) if err != nil { return nil, err } @@ -70,7 +69,7 @@ func findCandidates(dag *blockdag.BlockDAG, latestHash *daghash.Hash) ([]*dagcon } // For the first checkpoint, the required height is any block after the - // genesis block, so long as the chain has at least the required number + // genesis block, so long as the DAG has at least the required number // of confirmations (which is enforced above). if len(activeNetParams.Checkpoints) == 0 { requiredHeight = 1 @@ -82,7 +81,7 @@ func findCandidates(dag *blockdag.BlockDAG, latestHash *daghash.Hash) ([]*dagcon fmt.Print("Searching for candidates") defer fmt.Println() - // Loop backwards through the chain to find checkpoint candidates. + // Loop backwards through the DAG to find checkpoint candidates. candidates := make([]*dagconfig.Checkpoint, 0, cfg.NumCandidates) numTested := int32(0) for len(candidates) < cfg.NumCandidates && block.Height() > requiredHeight { @@ -107,8 +106,9 @@ func findCandidates(dag *blockdag.BlockDAG, latestHash *daghash.Hash) ([]*dagcon candidates = append(candidates, &checkpoint) } - prevHash := &block.MsgBlock().Header.PrevBlock - block, err = dag.BlockByHash(prevHash) + prevBlockHashes := block.MsgBlock().Header.PrevBlocks + selectedBlockHash := &prevBlockHashes[0] + block, err = dag.BlockByHash(selectedBlockHash) if err != nil { return nil, err } diff --git a/dagconfig/daghash/hash.go b/dagconfig/daghash/hash.go index fa6a5efb0..2cba65501 100644 --- a/dagconfig/daghash/hash.go +++ b/dagconfig/daghash/hash.go @@ -33,6 +33,15 @@ func (hash Hash) String() string { return hex.EncodeToString(hash[:]) } +func Strings(hashes []Hash) []string { + strings := make([]string, len(hashes)) + for i, hash := range hashes { + strings[i] = hash.String() + } + + return strings +} + // CloneBytes returns a copy of the bytes which represent the hash as a byte // slice. // @@ -69,6 +78,27 @@ func (hash *Hash) IsEqual(target *Hash) bool { return *hash == *target } +// AreEqual returns true if both slices contain the same hashes. +// Either slice must not contain duplicates. +func AreEqual(first []Hash, second []Hash) bool { + if len(first) != len(second) { + return false + } + + hashSet := make(map[Hash]bool) + for _, hash := range first { + hashSet[hash] = true + } + + for _, hash := range second { + if !hashSet[hash] { + return false + } + } + + return true +} + // NewHash returns a new Hash from a byte slice. An error is returned if // the number of bytes passed in is not HashSize. func NewHash(newHash []byte) (*Hash, error) { diff --git a/dagconfig/daghash/hash_test.go b/dagconfig/daghash/hash_test.go index b48a627cd..48f882eb3 100644 --- a/dagconfig/daghash/hash_test.go +++ b/dagconfig/daghash/hash_test.go @@ -194,3 +194,55 @@ func TestNewHashFromStr(t *testing.T) { } } } + +// TestAreEqual executes tests against the AreEqual function. +func TestAreEqual(t *testing.T) { + hash0, _ := NewHashFromStr("0000000000000000000000000000000000000000000000000000000000000000") + hash1, _ := NewHashFromStr("1111111111111111111111111111111111111111111111111111111111111111") + hash2, _ := NewHashFromStr("2222222222222222222222222222222222222222222222222222222222222222") + hash3, _ := NewHashFromStr("3333333333333333333333333333333333333333333333333333333333333333") + hashes0To2 := []Hash{*hash0, *hash1, *hash2} + hashes0To2Shifted := []Hash{*hash2, *hash0, *hash1} + hashes1To3 := []Hash{*hash1, *hash2, *hash3} + hashes0To3 := []Hash{*hash0, *hash1, *hash2, *hash3} + + tests := []struct { + name string + first []Hash + second []Hash + expected bool + }{ + { + name: "self-equality", + first: hashes0To2, + second: hashes0To2, + expected: true, + }, + { + name: "same members, different order", + first: hashes0To2, + second: hashes0To2Shifted, + expected: true, + }, + { + name: "same slice length but only some members are equal", + first: hashes0To2, + second: hashes1To3, + expected: false, + }, + { + name: "different slice lengths, one slice containing all the other's members", + first: hashes0To3, + second: hashes0To2, + expected: false, + }, + } + + for _, test := range tests { + result := AreEqual(test.first, test.second) + if result != test.expected { + t.Errorf("unexpected AreEqual result for"+ + " test \"%s\". Expected: %t, got: %t.", test.name, test.expected, result) + } + } +} diff --git a/integration/rpctest/blockgen.go b/integration/rpctest/blockgen.go index 1d6a8f0bb..5b98daf5f 100644 --- a/integration/rpctest/blockgen.go +++ b/integration/rpctest/blockgen.go @@ -185,7 +185,7 @@ func CreateBlock(prevBlock *btcutil.Block, inclusionTxs []*btcutil.Tx, var block wire.MsgBlock block.Header = wire.BlockHeader{ Version: blockVersion, - PrevBlock: *prevHash, + PrevBlocks: []daghash.Hash{*prevHash}, MerkleRoot: *merkles[len(merkles)-1], Timestamp: ts, Bits: net.PowLimitBits, diff --git a/integration/rpctest/memwallet.go b/integration/rpctest/memwallet.go index 72f8d4b5b..eb6980599 100644 --- a/integration/rpctest/memwallet.go +++ b/integration/rpctest/memwallet.go @@ -560,7 +560,7 @@ func (m *memWallet) ConfirmedBalance() btcutil.Amount { } // keyToAddr maps the passed private to corresponding p2pkh address. -func keyToAddr(key *btcec.PrivateKey, net *chaincfg.Params) (btcutil.Address, error) { +func keyToAddr(key *btcec.PrivateKey, net *dagconfig.Params) (btcutil.Address, error) { serializedKey := key.PubKey().SerializeCompressed() pubKeyAddr, err := btcutil.NewAddressPubKey(serializedKey, net) if err != nil { diff --git a/mining/cpuminer/cpuminer.go b/mining/cpuminer/cpuminer.go index f3184f293..8fdc0e6f6 100644 --- a/mining/cpuminer/cpuminer.go +++ b/mining/cpuminer/cpuminer.go @@ -162,9 +162,9 @@ func (m *CPUMiner) submitBlock(block *btcutil.Block) bool { // a new block, but the check only happens periodically, so it is // possible a block was found and submitted in between. msgBlock := block.MsgBlock() - if !msgBlock.Header.PrevBlock.IsEqual(&m.g.GetDAGState().SelectedTip.Hash) { + if !daghash.AreEqual(msgBlock.Header.PrevBlocks, m.g.GetDAGState().TipHashes) { log.Debugf("Block submitted via CPU miner with previous "+ - "block %s is stale", msgBlock.Header.PrevBlock) + "blocks %s is stale", msgBlock.Header.PrevBlocks) return false } @@ -248,7 +248,7 @@ func (m *CPUMiner) solveBlock(msgBlock *wire.MsgBlock, blockHeight int32, // The current block is stale if the DAG has changed. dagState := m.g.GetDAGState() - if !header.PrevBlock.IsEqual(&dagState.SelectedTip.Hash) { + if !daghash.AreEqual(header.PrevBlocks, dagState.TipHashes) { return false } diff --git a/mining/mining.go b/mining/mining.go index 36ece3eae..fd7e2b04c 100644 --- a/mining/mining.go +++ b/mining/mining.go @@ -764,7 +764,7 @@ mempoolLoop: var msgBlock wire.MsgBlock msgBlock.Header = wire.BlockHeader{ Version: nextBlockVersion, - PrevBlock: dagState.SelectedTip.Hash, + PrevBlocks: dagState.TipHashes, MerkleRoot: *merkles[len(merkles)-1], Timestamp: ts, Bits: reqDifficulty, diff --git a/netsync/manager.go b/netsync/manager.go index ae309972f..84661456e 100644 --- a/netsync/manager.go +++ b/netsync/manager.go @@ -783,7 +783,7 @@ func (sm *SyncManager) handleHeadersMsg(hmsg *headersMsg) { // add it to the list of headers. node := headerNode{hash: &blockHash} prevNode := prevNodeEl.Value.(*headerNode) - if prevNode.hash.IsEqual(&blockHeader.PrevBlock) { + if prevNode.hash.IsEqual(&blockHeader.PrevBlocks[0]) { // TODO: (Stas) This is wrong. Modified only to satisfy compilation. node.height = prevNode.height + 1 e := sm.headerList.PushBack(&node) if sm.startHeader == nil { diff --git a/rpcclient/notify.go b/rpcclient/notify.go index 417f3048e..dac168c96 100644 --- a/rpcclient/notify.go +++ b/rpcclient/notify.go @@ -1136,172 +1136,6 @@ func (r FutureRescanResult) Receive() error { return err } -// RescanAsync returns an instance of a type that can be used to get the result -// of the RPC at some future time by invoking the Receive function on the -// returned instance. -// -// See Rescan for the blocking version and more details. -// -// NOTE: Rescan requests are not issued on client reconnect and must be -// performed manually (ideally with a new start height based on the last -// rescan progress notification). See the OnClientConnected notification -// callback for a good callsite to reissue rescan requests on connect and -// reconnect. -// -// NOTE: This is a btcd extension and requires a websocket connection. -// -// NOTE: Deprecated. Use RescanBlocksAsync instead. -func (c *Client) RescanAsync(startBlock *daghash.Hash, - addresses []btcutil.Address, - outpoints []*wire.OutPoint) FutureRescanResult { - - // Not supported in HTTP POST mode. - if c.config.HTTPPostMode { - return newFutureError(ErrWebsocketsRequired) - } - - // Ignore the notification if the client is not interested in - // notifications. - if c.ntfnHandlers == nil { - return newNilFutureResult() - } - - // Convert block hashes to strings. - var startBlockHashStr string - if startBlock != nil { - startBlockHashStr = startBlock.String() - } - - // Convert addresses to strings. - addrs := make([]string, 0, len(addresses)) - for _, addr := range addresses { - addrs = append(addrs, addr.String()) - } - - // Convert outpoints. - ops := make([]btcjson.OutPoint, 0, len(outpoints)) - for _, op := range outpoints { - ops = append(ops, newOutPointFromWire(op)) - } - - cmd := btcjson.NewRescanCmd(startBlockHashStr, addrs, ops, nil) - return c.sendCmd(cmd) -} - -// Rescan rescans the block chain starting from the provided starting block to -// the end of the longest chain for transactions that pay to the passed -// addresses and transactions which spend the passed outpoints. -// -// The notifications of found transactions are delivered to the notification -// handlers associated with client and this call will not return until the -// rescan has completed. Calling this function has no effect if there are no -// notification handlers and will result in an error if the client is configured -// to run in HTTP POST mode. -// -// The notifications delivered as a result of this call will be via one of -// OnRedeemingTx (for transactions which spend from the one of the -// passed outpoints), OnRecvTx (for transactions that receive funds -// to one of the passed addresses), and OnRescanProgress (for rescan progress -// updates). -// -// See RescanEndBlock to also specify an ending block to finish the rescan -// without continuing through the best block on the main chain. -// -// NOTE: Rescan requests are not issued on client reconnect and must be -// performed manually (ideally with a new start height based on the last -// rescan progress notification). See the OnClientConnected notification -// callback for a good callsite to reissue rescan requests on connect and -// reconnect. -// -// NOTE: This is a btcd extension and requires a websocket connection. -// -// NOTE: Deprecated. Use RescanBlocks instead. -func (c *Client) Rescan(startBlock *daghash.Hash, - addresses []btcutil.Address, - outpoints []*wire.OutPoint) error { - - return c.RescanAsync(startBlock, addresses, outpoints).Receive() -} - -// RescanEndBlockAsync returns an instance of a type that can be used to get -// the result of the RPC at some future time by invoking the Receive function on -// the returned instance. -// -// See RescanEndBlock for the blocking version and more details. -// -// NOTE: This is a btcd extension and requires a websocket connection. -// -// NOTE: Deprecated. Use RescanBlocksAsync instead. -func (c *Client) RescanEndBlockAsync(startBlock *daghash.Hash, - addresses []btcutil.Address, outpoints []*wire.OutPoint, - endBlock *daghash.Hash) FutureRescanResult { - - // Not supported in HTTP POST mode. - if c.config.HTTPPostMode { - return newFutureError(ErrWebsocketsRequired) - } - - // Ignore the notification if the client is not interested in - // notifications. - if c.ntfnHandlers == nil { - return newNilFutureResult() - } - - // Convert block hashes to strings. - var startBlockHashStr, endBlockHashStr string - if startBlock != nil { - startBlockHashStr = startBlock.String() - } - if endBlock != nil { - endBlockHashStr = endBlock.String() - } - - // Convert addresses to strings. - addrs := make([]string, 0, len(addresses)) - for _, addr := range addresses { - addrs = append(addrs, addr.String()) - } - - // Convert outpoints. - ops := make([]btcjson.OutPoint, 0, len(outpoints)) - for _, op := range outpoints { - ops = append(ops, newOutPointFromWire(op)) - } - - cmd := btcjson.NewRescanCmd(startBlockHashStr, addrs, ops, - &endBlockHashStr) - return c.sendCmd(cmd) -} - -// RescanEndHeight rescans the block chain starting from the provided starting -// block up to the provided ending block for transactions that pay to the -// passed addresses and transactions which spend the passed outpoints. -// -// The notifications of found transactions are delivered to the notification -// handlers associated with client and this call will not return until the -// rescan has completed. Calling this function has no effect if there are no -// notification handlers and will result in an error if the client is configured -// to run in HTTP POST mode. -// -// The notifications delivered as a result of this call will be via one of -// OnRedeemingTx (for transactions which spend from the one of the -// passed outpoints), OnRecvTx (for transactions that receive funds -// to one of the passed addresses), and OnRescanProgress (for rescan progress -// updates). -// -// See Rescan to also perform a rescan through current end of the longest chain. -// -// NOTE: This is a btcd extension and requires a websocket connection. -// -// NOTE: Deprecated. Use RescanBlocks instead. -func (c *Client) RescanEndHeight(startBlock *daghash.Hash, - addresses []btcutil.Address, outpoints []*wire.OutPoint, - endBlock *daghash.Hash) error { - - return c.RescanEndBlockAsync(startBlock, addresses, outpoints, - endBlock).Receive() -} - // FutureLoadTxFilterResult is a future promise to deliver the result // of a LoadTxFilterAsync RPC invocation (or an applicable error). // diff --git a/rpcserver.go b/rpcserver.go index 088d2650c..1f66a5492 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -138,7 +138,7 @@ var rpcHandlersBeforeInit = map[string]commandHandler{ "getbestblock": handleGetBestBlock, "getbestblockhash": handleGetBestBlockHash, "getblock": handleGetBlock, - "getblockchaininfo": handleGetBlockChainInfo, + "getblockdaginfo": handleGetBlockDAGInfo, "getblockcount": handleGetBlockCount, "getblockhash": handleGetBlockHash, "getblockheader": handleGetBlockHeader, @@ -170,7 +170,7 @@ var rpcHandlersBeforeInit = map[string]commandHandler{ "submitblock": handleSubmitBlock, "uptime": handleUptime, "validateaddress": handleValidateAddress, - "verifychain": handleVerifyChain, + "verifydag": handleVerifyDAG, "verifymessage": handleVerifyMessage, "version": handleVersion, } @@ -1096,19 +1096,19 @@ func handleGetBlock(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (i params := s.cfg.ChainParams blockHeader := &blk.MsgBlock().Header blockReply := btcjson.GetBlockVerboseResult{ - Hash: c.Hash, - Version: blockHeader.Version, - VersionHex: fmt.Sprintf("%08x", blockHeader.Version), - MerkleRoot: blockHeader.MerkleRoot.String(), - PreviousHash: blockHeader.PrevBlock.String(), - Nonce: blockHeader.Nonce, - Time: blockHeader.Timestamp.Unix(), - Confirmations: uint64(1 + dagState.SelectedTip.Height - blockHeight), - Height: int64(blockHeight), - Size: int32(len(blkBytes)), - Bits: strconv.FormatInt(int64(blockHeader.Bits), 16), - Difficulty: getDifficultyRatio(blockHeader.Bits, params), - NextHash: nextHashString, + Hash: c.Hash, + Version: blockHeader.Version, + VersionHex: fmt.Sprintf("%08x", blockHeader.Version), + MerkleRoot: blockHeader.MerkleRoot.String(), + PreviousHashes: daghash.Strings(blockHeader.PrevBlocks), + Nonce: blockHeader.Nonce, + Time: blockHeader.Timestamp.Unix(), + Confirmations: uint64(1 + dagState.SelectedTip.Height - blockHeight), + Height: int64(blockHeight), + Size: int32(len(blkBytes)), + Bits: strconv.FormatInt(int64(blockHeader.Bits), 16), + Difficulty: getDifficultyRatio(blockHeader.Bits, params), + NextHash: nextHashString, } if c.VerboseTx == nil || !*c.VerboseTx { @@ -1156,19 +1156,19 @@ func softForkStatus(state blockdag.ThresholdState) (string, error) { } } -// handleGetBlockChainInfo implements the getblockchaininfo command. -func handleGetBlockChainInfo(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) { - // Obtain a snapshot of the current best known blockchain state. We'll +// handleGetBlockDAGInfo implements the getblockdaginfo command. +func handleGetBlockDAGInfo(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) { + // Obtain a snapshot of the current best known DAG state. We'll // populate the response to this call primarily from this snapshot. params := s.cfg.ChainParams - chain := s.cfg.DAG - dagState := chain.GetDAGState() + dag := s.cfg.DAG + dagState := dag.GetDAGState() - chainInfo := &btcjson.GetBlockChainInfoResult{ - Chain: params.Name, + chainInfo := &btcjson.GetBlockDAGInfoResult{ + DAG: params.Name, Blocks: dagState.SelectedTip.Height, Headers: dagState.SelectedTip.Height, - BestBlockHash: dagState.SelectedTip.Hash.String(), + TipHashes: daghash.Strings(dagState.TipHashes), Difficulty: getDifficultyRatio(dagState.SelectedTip.Bits, params), MedianTime: dagState.SelectedTip.MedianTime.Unix(), Pruned: false, @@ -1218,9 +1218,9 @@ func handleGetBlockChainInfo(s *rpcServer, cmd interface{}, closeChan <-chan str } } - // Query the chain for the current status of the deployment as + // Query the dag for the current status of the deployment as // identified by its deployment ID. - deploymentStatus, err := chain.ThresholdState(uint32(deployment)) + deploymentStatus, err := dag.ThresholdState(uint32(deployment)) if err != nil { context := "Failed to obtain deployment status" return nil, internalRPCError(err.Error(), context) @@ -1323,18 +1323,18 @@ func handleGetBlockHeader(s *rpcServer, cmd interface{}, closeChan <-chan struct params := s.cfg.ChainParams blockHeaderReply := btcjson.GetBlockHeaderVerboseResult{ - Hash: c.Hash, - Confirmations: uint64(1 + dagState.SelectedTip.Height - blockHeight), - Height: blockHeight, - Version: blockHeader.Version, - VersionHex: fmt.Sprintf("%08x", blockHeader.Version), - MerkleRoot: blockHeader.MerkleRoot.String(), - NextHash: nextHashString, - PreviousHash: blockHeader.PrevBlock.String(), - Nonce: uint64(blockHeader.Nonce), - Time: blockHeader.Timestamp.Unix(), - Bits: strconv.FormatInt(int64(blockHeader.Bits), 16), - Difficulty: getDifficultyRatio(blockHeader.Bits, params), + Hash: c.Hash, + Confirmations: uint64(1 + dagState.SelectedTip.Height - blockHeight), + Height: blockHeight, + Version: blockHeader.Version, + VersionHex: fmt.Sprintf("%08x", blockHeader.Version), + MerkleRoot: blockHeader.MerkleRoot.String(), + NextHash: nextHashString, + PreviousHashes: daghash.Strings(blockHeader.PrevBlocks), + Nonce: uint64(blockHeader.Nonce), + Time: blockHeader.Timestamp.Unix(), + Bits: strconv.FormatInt(int64(blockHeader.Bits), 16), + Difficulty: getDifficultyRatio(blockHeader.Bits, params), } return blockHeaderReply, nil } @@ -1695,22 +1695,22 @@ func (state *gbtWorkState) blockTemplateResult(useCoinbaseValue bool, submitOld targetDifficulty := fmt.Sprintf("%064x", blockdag.CompactToBig(header.Bits)) templateID := encodeTemplateID(state.prevHash, state.lastGenerated) reply := btcjson.GetBlockTemplateResult{ - Bits: strconv.FormatInt(int64(header.Bits), 16), - CurTime: header.Timestamp.Unix(), - Height: int64(template.Height), - PreviousHash: header.PrevBlock.String(), - SigOpLimit: blockdag.MaxSigOpsPerBlock, - SizeLimit: wire.MaxBlockPayload, - Transactions: transactions, - Version: header.Version, - LongPollID: templateID, - SubmitOld: submitOld, - Target: targetDifficulty, - MinTime: state.minTimestamp.Unix(), - MaxTime: maxTime.Unix(), - Mutable: gbtMutableFields, - NonceRange: gbtNonceRange, - Capabilities: gbtCapabilities, + Bits: strconv.FormatInt(int64(header.Bits), 16), + CurTime: header.Timestamp.Unix(), + Height: int64(template.Height), + PreviousHashes: daghash.Strings(header.PrevBlocks), + SigOpLimit: blockdag.MaxSigOpsPerBlock, + SizeLimit: wire.MaxBlockPayload, + Transactions: transactions, + Version: header.Version, + LongPollID: templateID, + SubmitOld: submitOld, + Target: targetDifficulty, + MinTime: state.minTimestamp.Unix(), + MaxTime: maxTime.Unix(), + Mutable: gbtMutableFields, + NonceRange: gbtNonceRange, + Capabilities: gbtCapabilities, } if useCoinbaseValue { @@ -1790,7 +1790,7 @@ func handleGetBlockTemplateLongPoll(s *rpcServer, longPollID string, useCoinbase // Return the block template now if the specific block template // identified by the long poll ID no longer matches the current block // template as this means the provided template is stale. - prevTemplateHash := &state.template.Block.Header.PrevBlock + prevTemplateHash := &state.template.Block.Header.PrevBlocks[0] // TODO: (Stas) This is probably wrong. Modified only to satisfy compilation if !prevHash.IsEqual(prevTemplateHash) || lastGenerated != state.lastGenerated.Unix() { @@ -1838,7 +1838,7 @@ func handleGetBlockTemplateLongPoll(s *rpcServer, longPollID string, useCoinbase // Include whether or not it is valid to submit work against the old // block template depending on whether or not a solution has already // been found and added to the block chain. - submitOld := prevHash.IsEqual(&state.template.Block.Header.PrevBlock) + submitOld := prevHash.IsEqual(&state.template.Block.Header.PrevBlocks[0]) // TODO: (Stas) This is probably wrong. Modified only to satisfy compilation result, err := state.blockTemplateResult(useCoinbaseValue, &submitOld) if err != nil { return nil, err @@ -2064,10 +2064,10 @@ func handleGetBlockTemplateProposal(s *rpcServer, request *btcjson.TemplateReque } block := btcutil.NewBlock(&msgBlock) - // Ensure the block is building from the expected previous block. - expectedPrevHash := s.cfg.DAG.GetDAGState().SelectedTip.Hash - prevHash := &block.MsgBlock().Header.PrevBlock - if !expectedPrevHash.IsEqual(prevHash) { + // Ensure the block is building from the expected previous blocks. + expectedPrevHashes := s.cfg.DAG.GetDAGState().TipHashes + prevHashes := block.MsgBlock().Header.PrevBlocks + if !daghash.AreEqual(expectedPrevHashes, prevHashes) { return "bad-prevblk", nil } @@ -2246,7 +2246,7 @@ func handleGetHeaders(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) // that are not related to wallet functionality. func handleGetInfo(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) { dagState := s.cfg.DAG.GetDAGState() - ret := &btcjson.InfoChainResult{ + ret := &btcjson.InfoDAGResult{ Version: int32(1000000*appMajor + 10000*appMinor + 100*appPatch), ProtocolVersion: int32(maxProtocolVersion), Blocks: dagState.SelectedTip.Height, @@ -3399,7 +3399,7 @@ func handleUptime(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (int func handleValidateAddress(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) { c := cmd.(*btcjson.ValidateAddressCmd) - result := btcjson.ValidateAddressChainResult{} + result := btcjson.ValidateAddressResult{} addr, err := btcutil.DecodeAddress(c.Address, s.cfg.ChainParams) if err != nil { // Return the default value (false) for IsValid. @@ -3412,7 +3412,7 @@ func handleValidateAddress(s *rpcServer, cmd interface{}, closeChan <-chan struc return result, nil } -func verifyChain(s *rpcServer, level, depth int32) error { +func verifyDAG(s *rpcServer, level, depth int32) error { dagState := s.cfg.DAG.GetDAGState() finishHeight := dagState.SelectedTip.Height - depth if finishHeight < 0 { @@ -3447,9 +3447,9 @@ func verifyChain(s *rpcServer, level, depth int32) error { return nil } -// handleVerifyChain implements the verifychain command. -func handleVerifyChain(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) { - c := cmd.(*btcjson.VerifyChainCmd) +// handleVerifyDAG implements the verifydag command. +func handleVerifyDAG(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) { + c := cmd.(*btcjson.VerifyDAGCmd) var checkLevel, checkDepth int32 if c.CheckLevel != nil { @@ -3459,7 +3459,7 @@ func handleVerifyChain(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) checkDepth = *c.CheckDepth } - err := verifyChain(s, checkLevel, checkDepth) + err := verifyDAG(s, checkLevel, checkDepth) return err == nil, nil } diff --git a/rpcserverhelp.go b/rpcserverhelp.go index 2a13d0170..e1b11d504 100644 --- a/rpcserverhelp.go +++ b/rpcserverhelp.go @@ -167,24 +167,24 @@ var helpDescsEnUS = map[string]string{ "getblock--result0": "Hex-encoded bytes of the serialized block", // GetBlockChainInfoCmd help. - "getblockchaininfo--synopsis": "Returns information about the current blockchain state and the status of any active soft-fork deployments.", + "getblockdaginfo--synopsis": "Returns information about the current blockDAG state and the status of any active soft-fork deployments.", - // GetBlockChainInfoResult help. - "getblockchaininforesult-chain": "The name of the chain the daemon is on (testnet, mainnet, etc)", - "getblockchaininforesult-blocks": "The number of blocks in the best known chain", - "getblockchaininforesult-headers": "The number of headers that we've gathered for in the best known chain", - "getblockchaininforesult-bestblockhash": "The block hash for the latest block in the main chain", - "getblockchaininforesult-difficulty": "The current chain difficulty", - "getblockchaininforesult-mediantime": "The median time from the PoV of the best block in the chain", - "getblockchaininforesult-verificationprogress": "An estimate for how much of the best chain we've verified", - "getblockchaininforesult-pruned": "A bool that indicates if the node is pruned or not", - "getblockchaininforesult-pruneheight": "The lowest block retained in the current pruned chain", - "getblockchaininforesult-chainwork": "The total cumulative work in the best chain", - "getblockchaininforesult-softforks": "The status of the super-majority soft-forks", - "getblockchaininforesult-bip9_softforks": "JSON object describing active BIP0009 deployments", - "getblockchaininforesult-bip9_softforks--key": "bip9_softforks", - "getblockchaininforesult-bip9_softforks--value": "An object describing a particular BIP009 deployment", - "getblockchaininforesult-bip9_softforks--desc": "The status of any defined BIP0009 soft-fork deployments", + // GetBlockDAGInfoResult help. + "getblockdaginforesult-dag": "The name of the DAG the daemon is on (testnet, mainnet, etc)", + "getblockdaginforesult-blocks": "The number of blocks in the best known chain", + "getblockdaginforesult-headers": "The number of headers that we've gathered for in the best known chain", + "getblockdaginforesult-tiphashes": "The block hashes for the tips in the DAG", + "getblockdaginforesult-difficulty": "The current chain difficulty", + "getblockdaginforesult-mediantime": "The median time from the PoV of the best block in the chain", + "getblockdaginforesult-verificationprogress": "An estimate for how much of the best chain we've verified", + "getblockdaginforesult-pruned": "A bool that indicates if the node is pruned or not", + "getblockdaginforesult-pruneheight": "The lowest block retained in the current pruned chain", + "getblockdaginforesult-dagwork": "The total cumulative work in the DAG", + "getblockdaginforesult-softforks": "The status of the super-majority soft-forks", + "getblockdaginforesult-bip9_softforks": "JSON object describing active BIP0009 deployments", + "getblockdaginforesult-bip9_softforks--key": "bip9_softforks", + "getblockdaginforesult-bip9_softforks--value": "An object describing a particular BIP009 deployment", + "getblockdaginforesult-bip9_softforks--desc": "The status of any defined BIP0009 soft-fork deployments", // SoftForkDescription help. "softforkdescription-reject": "The current activation status of the softfork", @@ -221,21 +221,21 @@ var helpDescsEnUS = map[string]string{ "searchrawtransactionsresult-size": "The size of the transaction in bytes", // GetBlockVerboseResult help. - "getblockverboseresult-hash": "The hash of the block (same as provided)", - "getblockverboseresult-confirmations": "The number of confirmations", - "getblockverboseresult-size": "The size of the block", - "getblockverboseresult-height": "The height of the block in the block chain", - "getblockverboseresult-version": "The block version", - "getblockverboseresult-versionHex": "The block version in hexadecimal", - "getblockverboseresult-merkleroot": "Root hash of the merkle tree", - "getblockverboseresult-tx": "The transaction hashes (only when verbosetx=false)", - "getblockverboseresult-rawtx": "The transactions as JSON objects (only when verbosetx=true)", - "getblockverboseresult-time": "The block time in seconds since 1 Jan 1970 GMT", - "getblockverboseresult-nonce": "The block nonce", - "getblockverboseresult-bits": "The bits which represent the block difficulty", - "getblockverboseresult-difficulty": "The proof-of-work difficulty as a multiple of the minimum difficulty", - "getblockverboseresult-previousblockhash": "The hash of the previous block", - "getblockverboseresult-nextblockhash": "The hash of the next block (only if there is one)", + "getblockverboseresult-hash": "The hash of the block (same as provided)", + "getblockverboseresult-confirmations": "The number of confirmations", + "getblockverboseresult-size": "The size of the block", + "getblockverboseresult-height": "The height of the block in the block chain", + "getblockverboseresult-version": "The block version", + "getblockverboseresult-versionHex": "The block version in hexadecimal", + "getblockverboseresult-merkleroot": "Root hash of the merkle tree", + "getblockverboseresult-tx": "The transaction hashes (only when verbosetx=false)", + "getblockverboseresult-rawtx": "The transactions as JSON objects (only when verbosetx=true)", + "getblockverboseresult-time": "The block time in seconds since 1 Jan 1970 GMT", + "getblockverboseresult-nonce": "The block nonce", + "getblockverboseresult-bits": "The bits which represent the block difficulty", + "getblockverboseresult-difficulty": "The proof-of-work difficulty as a multiple of the minimum difficulty", + "getblockverboseresult-previousblockhashes": "The hashes of the previous blocks", + "getblockverboseresult-nextblockhash": "The hash of the next block (only if there is one)", // GetBlockCountCmd help. "getblockcount--synopsis": "Returns the number of blocks in the longest block chain.", @@ -255,18 +255,18 @@ var helpDescsEnUS = map[string]string{ "getblockheader--result0": "The block header hash", // GetBlockHeaderVerboseResult help. - "getblockheaderverboseresult-hash": "The hash of the block (same as provided)", - "getblockheaderverboseresult-confirmations": "The number of confirmations", - "getblockheaderverboseresult-height": "The height of the block in the block chain", - "getblockheaderverboseresult-version": "The block version", - "getblockheaderverboseresult-versionHex": "The block version in hexadecimal", - "getblockheaderverboseresult-merkleroot": "Root hash of the merkle tree", - "getblockheaderverboseresult-time": "The block time in seconds since 1 Jan 1970 GMT", - "getblockheaderverboseresult-nonce": "The block nonce", - "getblockheaderverboseresult-bits": "The bits which represent the block difficulty", - "getblockheaderverboseresult-difficulty": "The proof-of-work difficulty as a multiple of the minimum difficulty", - "getblockheaderverboseresult-previousblockhash": "The hash of the previous block", - "getblockheaderverboseresult-nextblockhash": "The hash of the next block (only if there is one)", + "getblockheaderverboseresult-hash": "The hash of the block (same as provided)", + "getblockheaderverboseresult-confirmations": "The number of confirmations", + "getblockheaderverboseresult-height": "The height of the block in the block chain", + "getblockheaderverboseresult-version": "The block version", + "getblockheaderverboseresult-versionHex": "The block version in hexadecimal", + "getblockheaderverboseresult-merkleroot": "Root hash of the merkle tree", + "getblockheaderverboseresult-time": "The block time in seconds since 1 Jan 1970 GMT", + "getblockheaderverboseresult-nonce": "The block nonce", + "getblockheaderverboseresult-bits": "The bits which represent the block difficulty", + "getblockheaderverboseresult-difficulty": "The proof-of-work difficulty as a multiple of the minimum difficulty", + "getblockheaderverboseresult-previousblockhashes": "The hashes of the previous blocks", + "getblockheaderverboseresult-nextblockhash": "The hash of the next block (only if there is one)", // TemplateRequest help. "templaterequest-mode": "This is 'template', 'proposal', or omitted", @@ -290,29 +290,29 @@ var helpDescsEnUS = map[string]string{ "getblocktemplateresultaux-flags": "Hex-encoded byte-for-byte data to include in the coinbase signature script", // GetBlockTemplateResult help. - "getblocktemplateresult-bits": "Hex-encoded compressed difficulty", - "getblocktemplateresult-curtime": "Current time as seen by the server (recommended for block time); must fall within mintime/maxtime rules", - "getblocktemplateresult-height": "Height of the block to be solved", - "getblocktemplateresult-previousblockhash": "Hex-encoded big-endian hash of the previous block", - "getblocktemplateresult-sigoplimit": "Number of sigops allowed in blocks ", - "getblocktemplateresult-sizelimit": "Number of bytes allowed in blocks", - "getblocktemplateresult-transactions": "Array of transactions as JSON objects", - "getblocktemplateresult-version": "The block version", - "getblocktemplateresult-coinbaseaux": "Data that should be included in the coinbase signature script", - "getblocktemplateresult-coinbasetxn": "Information about the coinbase transaction", - "getblocktemplateresult-coinbasevalue": "Total amount available for the coinbase in Satoshi", - "getblocktemplateresult-workid": "This value must be returned with result if provided (not provided)", - "getblocktemplateresult-longpollid": "Identifier for long poll request which allows monitoring for expiration", - "getblocktemplateresult-longpolluri": "An alternate URI to use for long poll requests if provided (not provided)", - "getblocktemplateresult-submitold": "Not applicable", - "getblocktemplateresult-target": "Hex-encoded big-endian number which valid results must be less than", - "getblocktemplateresult-expires": "Maximum number of seconds (starting from when the server sent the response) this work is valid for", - "getblocktemplateresult-maxtime": "Maximum allowed time", - "getblocktemplateresult-mintime": "Minimum allowed time", - "getblocktemplateresult-mutable": "List of mutations the server explicitly allows", - "getblocktemplateresult-noncerange": "Two concatenated hex-encoded big-endian 32-bit integers which represent the valid ranges of nonces the miner may scan", - "getblocktemplateresult-capabilities": "List of server capabilities including 'proposal' to indicate support for block proposals", - "getblocktemplateresult-reject-reason": "Reason the proposal was invalid as-is (only applies to proposal responses)", + "getblocktemplateresult-bits": "Hex-encoded compressed difficulty", + "getblocktemplateresult-curtime": "Current time as seen by the server (recommended for block time); must fall within mintime/maxtime rules", + "getblocktemplateresult-height": "Height of the block to be solved", + "getblocktemplateresult-previousblockhashes": "Hex-encoded big-endian hashes of the previous blocks", + "getblocktemplateresult-sigoplimit": "Number of sigops allowed in blocks ", + "getblocktemplateresult-sizelimit": "Number of bytes allowed in blocks", + "getblocktemplateresult-transactions": "Array of transactions as JSON objects", + "getblocktemplateresult-version": "The block version", + "getblocktemplateresult-coinbaseaux": "Data that should be included in the coinbase signature script", + "getblocktemplateresult-coinbasetxn": "Information about the coinbase transaction", + "getblocktemplateresult-coinbasevalue": "Total amount available for the coinbase in Satoshi", + "getblocktemplateresult-workid": "This value must be returned with result if provided (not provided)", + "getblocktemplateresult-longpollid": "Identifier for long poll request which allows monitoring for expiration", + "getblocktemplateresult-longpolluri": "An alternate URI to use for long poll requests if provided (not provided)", + "getblocktemplateresult-submitold": "Not applicable", + "getblocktemplateresult-target": "Hex-encoded big-endian number which valid results must be less than", + "getblocktemplateresult-expires": "Maximum number of seconds (starting from when the server sent the response) this work is valid for", + "getblocktemplateresult-maxtime": "Maximum allowed time", + "getblocktemplateresult-mintime": "Minimum allowed time", + "getblocktemplateresult-mutable": "List of mutations the server explicitly allows", + "getblocktemplateresult-noncerange": "Two concatenated hex-encoded big-endian 32-bit integers which represent the valid ranges of nonces the miner may scan", + "getblocktemplateresult-capabilities": "List of server capabilities including 'proposal' to indicate support for block proposals", + "getblocktemplateresult-reject-reason": "Reason the proposal was invalid as-is (only applies to proposal responses)", // GetBlockTemplateCmd help. "getblocktemplate--synopsis": "Returns a JSON object with information necessary to construct a block to mine or accepts a proposal to validate.\n" + @@ -355,17 +355,17 @@ var helpDescsEnUS = map[string]string{ "gethashespersec--synopsis": "Returns a recent hashes per second performance measurement while generating coins (mining).", "gethashespersec--result0": "The number of hashes per second", - // InfoChainResult help. - "infochainresult-version": "The version of the server", - "infochainresult-protocolversion": "The latest supported protocol version", - "infochainresult-blocks": "The number of blocks processed", - "infochainresult-timeoffset": "The time offset", - "infochainresult-connections": "The number of connected peers", - "infochainresult-proxy": "The proxy used by the server", - "infochainresult-difficulty": "The current target difficulty", - "infochainresult-testnet": "Whether or not server is using testnet", - "infochainresult-relayfee": "The minimum relay fee for non-free transactions in BTC/KB", - "infochainresult-errors": "Any current errors", + // InfoDAGResult help. + "infodagresult-version": "The version of the server", + "infodagresult-protocolversion": "The latest supported protocol version", + "infodagresult-blocks": "The number of blocks processed", + "infodagresult-timeoffset": "The time offset", + "infodagresult-connections": "The number of connected peers", + "infodagresult-proxy": "The proxy used by the server", + "infodagresult-difficulty": "The current target difficulty", + "infodagresult-testnet": "Whether or not server is using testnet", + "infodagresult-relayfee": "The minimum relay fee for non-free transactions in BTC/KB", + "infodagresult-errors": "Any current errors", // InfoWalletResult help. "infowalletresult-version": "The version of the server", @@ -551,22 +551,22 @@ var helpDescsEnUS = map[string]string{ "submitblock--result1": "The reason the block was rejected", // ValidateAddressResult help. - "validateaddresschainresult-isvalid": "Whether or not the address is valid", - "validateaddresschainresult-address": "The bitcoin address (only when isvalid is true)", + "validateaddressresult-isvalid": "Whether or not the address is valid", + "validateaddressresult-address": "The bitcoin address (only when isvalid is true)", // ValidateAddressCmd help. "validateaddress--synopsis": "Verify an address is valid.", "validateaddress-address": "Bitcoin address to validate", // VerifyChainCmd help. - "verifychain--synopsis": "Verifies the block chain database.\n" + + "verifydag--synopsis": "Verifies the block DAG database.\n" + "The actual checks performed by the checklevel parameter are implementation specific.\n" + "For btcd this is:\n" + "checklevel=0 - Look up each block and ensure it can be loaded from the database.\n" + "checklevel=1 - Perform basic context-free sanity checks on each block.", - "verifychain-checklevel": "How thorough the block verification is", - "verifychain-checkdepth": "The number of blocks to check", - "verifychain--result0": "Whether or not the chain verified", + "verifydag-checklevel": "How thorough the block verification is", + "verifydag-checkdepth": "The number of blocks to check", + "verifydag--result0": "Whether or not the DAG verified", // VerifyMessageCmd help. "verifymessage--synopsis": "Verify a signed message.", @@ -678,7 +678,7 @@ var rpcResultTypes = map[string][]interface{}{ "getblockhash": {(*string)(nil)}, "getblockheader": {(*string)(nil), (*btcjson.GetBlockHeaderVerboseResult)(nil)}, "getblocktemplate": {(*btcjson.GetBlockTemplateResult)(nil), (*string)(nil), nil}, - "getblockchaininfo": {(*btcjson.GetBlockChainInfoResult)(nil)}, + "getblockdaginfo": {(*btcjson.GetBlockDAGInfoResult)(nil)}, "getcfilter": {(*string)(nil)}, "getcfilterheader": {(*string)(nil)}, "getconnectioncount": {(*int32)(nil)}, @@ -687,7 +687,7 @@ var rpcResultTypes = map[string][]interface{}{ "getgenerate": {(*bool)(nil)}, "gethashespersec": {(*float64)(nil)}, "getheaders": {(*[]string)(nil)}, - "getinfo": {(*btcjson.InfoChainResult)(nil)}, + "getinfo": {(*btcjson.InfoDAGResult)(nil)}, "getmempoolinfo": {(*btcjson.GetMempoolInfoResult)(nil)}, "getmininginfo": {(*btcjson.GetMiningInfoResult)(nil)}, "getnettotals": {(*btcjson.GetNetTotalsResult)(nil)}, @@ -705,8 +705,8 @@ var rpcResultTypes = map[string][]interface{}{ "stop": {(*string)(nil)}, "submitblock": {nil, (*string)(nil)}, "uptime": {(*int64)(nil)}, - "validateaddress": {(*btcjson.ValidateAddressChainResult)(nil)}, - "verifychain": {(*bool)(nil)}, + "validateaddress": {(*btcjson.ValidateAddressResult)(nil)}, + "verifydag": {(*bool)(nil)}, "verifymessage": {(*bool)(nil)}, "version": {(*map[string]btcjson.VersionResult)(nil)}, diff --git a/rpcwebsocket.go b/rpcwebsocket.go index f69069951..29c302bdc 100644 --- a/rpcwebsocket.go +++ b/rpcwebsocket.go @@ -16,7 +16,6 @@ import ( "errors" "fmt" "io" - "math" "sync" "time" @@ -26,7 +25,6 @@ import ( "github.com/daglabs/btcd/btcjson" "github.com/daglabs/btcd/dagconfig" "github.com/daglabs/btcd/dagconfig/daghash" - "github.com/daglabs/btcd/database" "github.com/daglabs/btcd/txscript" "github.com/daglabs/btcd/wire" "github.com/daglabs/btcutil" @@ -75,7 +73,6 @@ var wsHandlersBeforeInit = map[string]wsCommandHandler{ "stopnotifynewtransactions": handleStopNotifyNewTransactions, "stopnotifyspent": handleStopNotifySpent, "stopnotifyreceived": handleStopNotifyReceived, - "rescan": handleRescan, "rescanblocks": handleRescanBlocks, } @@ -1977,168 +1974,6 @@ func deserializeOutpoints(serializedOuts []btcjson.OutPoint) ([]*wire.OutPoint, return outpoints, nil } -type rescanKeys struct { - fallbacks map[string]struct{} - pubKeyHashes map[[ripemd160.Size]byte]struct{} - scriptHashes map[[ripemd160.Size]byte]struct{} - compressedPubKeys map[[33]byte]struct{} - uncompressedPubKeys map[[65]byte]struct{} - unspent map[wire.OutPoint]struct{} -} - -// unspentSlice returns a slice of currently-unspent outpoints for the rescan -// lookup keys. This is primarily intended to be used to register outpoints -// for continuous notifications after a rescan has completed. -func (r *rescanKeys) unspentSlice() []*wire.OutPoint { - ops := make([]*wire.OutPoint, 0, len(r.unspent)) - for op := range r.unspent { - opCopy := op - ops = append(ops, &opCopy) - } - return ops -} - -// ErrRescanReorg defines the error that is returned when an unrecoverable -// reorganize is detected during a rescan. -var ErrRescanReorg = btcjson.RPCError{ - Code: btcjson.ErrRPCDatabase, - Message: "Reorganize", -} - -// rescanBlock rescans all transactions in a single block. This is a helper -// function for handleRescan. -func rescanBlock(wsc *wsClient, lookups *rescanKeys, blk *btcutil.Block) { - for _, tx := range blk.Transactions() { - // Hexadecimal representation of this tx. Only created if - // needed, and reused for later notifications if already made. - var txHex string - - // All inputs and outputs must be iterated through to correctly - // modify the unspent map, however, just a single notification - // for any matching transaction inputs or outputs should be - // created and sent. - spentNotified := false - recvNotified := false - - for _, txin := range tx.MsgTx().TxIn { - if _, ok := lookups.unspent[txin.PreviousOutPoint]; ok { - delete(lookups.unspent, txin.PreviousOutPoint) - - if spentNotified { - continue - } - - if txHex == "" { - txHex = txHexString(tx.MsgTx()) - } - marshalledJSON, err := newRedeemingTxNotification(txHex, tx.Index(), blk) - if err != nil { - rpcsLog.Errorf("Failed to marshal redeemingtx notification: %v", err) - continue - } - - err = wsc.QueueNotification(marshalledJSON) - // Stop the rescan early if the websocket client - // disconnected. - if err == ErrClientQuit { - return - } - spentNotified = true - } - } - - for txOutIdx, txout := range tx.MsgTx().TxOut { - _, addrs, _, _ := txscript.ExtractPkScriptAddrs( - txout.PkScript, wsc.server.cfg.ChainParams) - - for _, addr := range addrs { - switch a := addr.(type) { - case *btcutil.AddressPubKeyHash: - if _, ok := lookups.pubKeyHashes[*a.Hash160()]; !ok { - continue - } - - case *btcutil.AddressScriptHash: - if _, ok := lookups.scriptHashes[*a.Hash160()]; !ok { - continue - } - - case *btcutil.AddressPubKey: - found := false - switch sa := a.ScriptAddress(); len(sa) { - case 33: // Compressed - var key [33]byte - copy(key[:], sa) - if _, ok := lookups.compressedPubKeys[key]; ok { - found = true - } - - case 65: // Uncompressed - var key [65]byte - copy(key[:], sa) - if _, ok := lookups.uncompressedPubKeys[key]; ok { - found = true - } - - default: - rpcsLog.Warnf("Skipping rescanned pubkey of unknown "+ - "serialized length %d", len(sa)) - continue - } - - // If the transaction output pays to the pubkey of - // a rescanned P2PKH address, include it as well. - if !found { - pkh := a.AddressPubKeyHash() - if _, ok := lookups.pubKeyHashes[*pkh.Hash160()]; !ok { - continue - } - } - - default: - // A new address type must have been added. Encode as a - // payment address string and check the fallback map. - addrStr := addr.EncodeAddress() - _, ok := lookups.fallbacks[addrStr] - if !ok { - continue - } - } - - outpoint := wire.OutPoint{ - Hash: *tx.Hash(), - Index: uint32(txOutIdx), - } - lookups.unspent[outpoint] = struct{}{} - - if recvNotified { - continue - } - - if txHex == "" { - txHex = txHexString(tx.MsgTx()) - } - ntfn := btcjson.NewRecvTxNtfn(txHex, - blockDetails(blk, tx.Index())) - - marshalledJSON, err := btcjson.MarshalCmd(nil, ntfn) - if err != nil { - rpcsLog.Errorf("Failed to marshal recvtx notification: %v", err) - return - } - - err = wsc.QueueNotification(marshalledJSON) - // Stop the rescan early if the websocket client - // disconnected. - if err == ErrClientQuit { - return - } - recvNotified = true - } - } - } -} - // rescanBlockFilter rescans a block for any relevant transactions for the // passed lookup keys. Any discovered transactions are returned hex encoded as // a string slice. @@ -2248,7 +2083,7 @@ func handleRescanBlocks(wsc *wsClient, icmd interface{}) (interface{}, error) { Message: "Failed to fetch block: " + err.Error(), } } - if lastBlockHash != nil && block.MsgBlock().Header.PrevBlock != *lastBlockHash { + if lastBlockHash != nil && block.MsgBlock().Header.PrevBlocks[0] != *lastBlockHash { // TODO: (Stas) This is likely wrong. Modified to satisfy compilation. return nil, &btcjson.RPCError{ Code: btcjson.ErrRPCInvalidParameter, Message: fmt.Sprintf("Block %v is not a child of %v", @@ -2269,375 +2104,6 @@ func handleRescanBlocks(wsc *wsClient, icmd interface{}) (interface{}, error) { return &discoveredData, nil } -// recoverFromReorg attempts to recover from a detected reorganize during a -// rescan. It fetches a new range of block shas from the database and -// verifies that the new range of blocks is on the same fork as a previous -// range of blocks. If this condition does not hold true, the JSON-RPC error -// for an unrecoverable reorganize is returned. -func recoverFromReorg(dag *blockdag.BlockDAG, minBlock, maxBlock int32, - lastBlock *daghash.Hash) ([]daghash.Hash, error) { - - hashList, err := dag.HeightRange(minBlock, maxBlock) - if err != nil { - rpcsLog.Errorf("Error looking up block range: %v", err) - return nil, &btcjson.RPCError{ - Code: btcjson.ErrRPCDatabase, - Message: "Database error: " + err.Error(), - } - } - if lastBlock == nil || len(hashList) == 0 { - return hashList, nil - } - - blk, err := dag.BlockByHash(&hashList[0]) - if err != nil { - rpcsLog.Errorf("Error looking up possibly reorged block: %v", - err) - return nil, &btcjson.RPCError{ - Code: btcjson.ErrRPCDatabase, - Message: "Database error: " + err.Error(), - } - } - jsonErr := descendantBlock(lastBlock, blk) - if jsonErr != nil { - return nil, jsonErr - } - return hashList, nil -} - -// descendantBlock returns the appropriate JSON-RPC error if a current block -// fetched during a reorganize is not a direct child of the parent block hash. -func descendantBlock(prevHash *daghash.Hash, curBlock *btcutil.Block) error { - curHash := &curBlock.MsgBlock().Header.PrevBlock - if !prevHash.IsEqual(curHash) { - rpcsLog.Errorf("Stopping rescan for reorged block %v "+ - "(replaced by block %v)", prevHash, curHash) - return &ErrRescanReorg - } - return nil -} - -// handleRescan implements the rescan command extension for websocket -// connections. -// -// NOTE: This does not smartly handle reorgs, and fixing requires database -// changes (for safe, concurrent access to full block ranges, and support -// for other chains than the best chain). It will, however, detect whether -// a reorg removed a block that was previously processed, and result in the -// handler erroring. Clients must handle this by finding a block still in -// the chain (perhaps from a rescanprogress notification) to resume their -// rescan. -func handleRescan(wsc *wsClient, icmd interface{}) (interface{}, error) { - cmd, ok := icmd.(*btcjson.RescanCmd) - if !ok { - return nil, btcjson.ErrRPCInternal - } - - outpoints := make([]*wire.OutPoint, 0, len(cmd.OutPoints)) - for i := range cmd.OutPoints { - cmdOutpoint := &cmd.OutPoints[i] - blockHash, err := daghash.NewHashFromStr(cmdOutpoint.Hash) - if err != nil { - return nil, rpcDecodeHexError(cmdOutpoint.Hash) - } - outpoint := wire.NewOutPoint(blockHash, cmdOutpoint.Index) - outpoints = append(outpoints, outpoint) - } - - numAddrs := len(cmd.Addresses) - if numAddrs == 1 { - rpcsLog.Info("Beginning rescan for 1 address") - } else { - rpcsLog.Infof("Beginning rescan for %d addresses", numAddrs) - } - - // Build lookup maps. - lookups := rescanKeys{ - fallbacks: map[string]struct{}{}, - pubKeyHashes: map[[ripemd160.Size]byte]struct{}{}, - scriptHashes: map[[ripemd160.Size]byte]struct{}{}, - compressedPubKeys: map[[33]byte]struct{}{}, - uncompressedPubKeys: map[[65]byte]struct{}{}, - unspent: map[wire.OutPoint]struct{}{}, - } - var compressedPubkey [33]byte - var uncompressedPubkey [65]byte - params := wsc.server.cfg.ChainParams - for _, addrStr := range cmd.Addresses { - addr, err := btcutil.DecodeAddress(addrStr, params) - if err != nil { - jsonErr := btcjson.RPCError{ - Code: btcjson.ErrRPCInvalidAddressOrKey, - Message: "Rescan address " + addrStr + ": " + - err.Error(), - } - return nil, &jsonErr - } - switch a := addr.(type) { - case *btcutil.AddressPubKeyHash: - lookups.pubKeyHashes[*a.Hash160()] = struct{}{} - - case *btcutil.AddressScriptHash: - lookups.scriptHashes[*a.Hash160()] = struct{}{} - - case *btcutil.AddressPubKey: - pubkeyBytes := a.ScriptAddress() - switch len(pubkeyBytes) { - case 33: // Compressed - copy(compressedPubkey[:], pubkeyBytes) - lookups.compressedPubKeys[compressedPubkey] = struct{}{} - - case 65: // Uncompressed - copy(uncompressedPubkey[:], pubkeyBytes) - lookups.uncompressedPubKeys[uncompressedPubkey] = struct{}{} - - default: - jsonErr := btcjson.RPCError{ - Code: btcjson.ErrRPCInvalidAddressOrKey, - Message: "Pubkey " + addrStr + " is of unknown length", - } - return nil, &jsonErr - } - - default: - // A new address type must have been added. Use encoded - // payment address string as a fallback until a fast path - // is added. - lookups.fallbacks[addrStr] = struct{}{} - } - } - for _, outpoint := range outpoints { - lookups.unspent[*outpoint] = struct{}{} - } - - dag := wsc.server.cfg.DAG - - minBlockHash, err := daghash.NewHashFromStr(cmd.BeginBlock) - if err != nil { - return nil, rpcDecodeHexError(cmd.BeginBlock) - } - minBlock, err := dag.BlockHeightByHash(minBlockHash) - if err != nil { - return nil, &btcjson.RPCError{ - Code: btcjson.ErrRPCBlockNotFound, - Message: "Error getting block: " + err.Error(), - } - } - - maxBlock := int32(math.MaxInt32) - if cmd.EndBlock != nil { - maxBlockHash, err := daghash.NewHashFromStr(*cmd.EndBlock) - if err != nil { - return nil, rpcDecodeHexError(*cmd.EndBlock) - } - maxBlock, err = dag.BlockHeightByHash(maxBlockHash) - if err != nil { - return nil, &btcjson.RPCError{ - Code: btcjson.ErrRPCBlockNotFound, - Message: "Error getting block: " + err.Error(), - } - } - } - - // lastBlock and lastBlockHash track the previously-rescanned block. - // They equal nil when no previous blocks have been rescanned. - var lastBlock *btcutil.Block - var lastBlockHash *daghash.Hash - - // A ticker is created to wait at least 10 seconds before notifying the - // websocket client of the current progress completed by the rescan. - ticker := time.NewTicker(10 * time.Second) - defer ticker.Stop() - - // Instead of fetching all block shas at once, fetch in smaller chunks - // to ensure large rescans consume a limited amount of memory. -fetchRange: - for minBlock < maxBlock { - // Limit the max number of hashes to fetch at once to the - // maximum number of items allowed in a single inventory. - // This value could be higher since it's not creating inventory - // messages, but this mirrors the limiting logic used in the - // peer-to-peer protocol. - maxLoopBlock := maxBlock - if maxLoopBlock-minBlock > wire.MaxInvPerMsg { - maxLoopBlock = minBlock + wire.MaxInvPerMsg - } - hashList, err := dag.HeightRange(minBlock, maxLoopBlock) - if err != nil { - rpcsLog.Errorf("Error looking up block range: %v", err) - return nil, &btcjson.RPCError{ - Code: btcjson.ErrRPCDatabase, - Message: "Database error: " + err.Error(), - } - } - if len(hashList) == 0 { - // The rescan is finished if no blocks hashes for this - // range were successfully fetched and a stop block - // was provided. - if maxBlock != math.MaxInt32 { - break - } - - // If the rescan is through the current block, set up - // the client to continue to receive notifications - // regarding all rescanned addresses and the current set - // of unspent outputs. - // - // This is done safely by temporarily grabbing exclusive - // access of the block manager. If no more blocks have - // been attached between this pause and the fetch above, - // then it is safe to register the websocket client for - // continuous notifications if necessary. Otherwise, - // continue the fetch loop again to rescan the new - // blocks (or error due to an irrecoverable reorganize). - pauseGuard := wsc.server.cfg.SyncMgr.Pause() - dagState := wsc.server.cfg.DAG.GetDAGState() - curHash := &dagState.SelectedTip.Hash - again := true - if lastBlockHash == nil || *lastBlockHash == *curHash { - again = false - n := wsc.server.ntfnMgr - n.RegisterSpentRequests(wsc, lookups.unspentSlice()) - n.RegisterTxOutAddressRequests(wsc, cmd.Addresses) - } - close(pauseGuard) - if err != nil { - rpcsLog.Errorf("Error fetching best block "+ - "hash: %v", err) - return nil, &btcjson.RPCError{ - Code: btcjson.ErrRPCDatabase, - Message: "Database error: " + - err.Error(), - } - } - if again { - continue - } - break - } - - loopHashList: - for i := range hashList { - blk, err := dag.BlockByHash(&hashList[i]) - if err != nil { - // Only handle reorgs if a block could not be - // found for the hash. - if dbErr, ok := err.(database.Error); !ok || - dbErr.ErrorCode != database.ErrBlockNotFound { - - rpcsLog.Errorf("Error looking up "+ - "block: %v", err) - return nil, &btcjson.RPCError{ - Code: btcjson.ErrRPCDatabase, - Message: "Database error: " + - err.Error(), - } - } - - // If an absolute max block was specified, don't - // attempt to handle the reorg. - if maxBlock != math.MaxInt32 { - rpcsLog.Errorf("Stopping rescan for "+ - "reorged block %v", - cmd.EndBlock) - return nil, &ErrRescanReorg - } - - // If the lookup for the previously valid block - // hash failed, there may have been a reorg. - // Fetch a new range of block hashes and verify - // that the previously processed block (if there - // was any) still exists in the database. If it - // doesn't, we error. - // - // A goto is used to branch executation back to - // before the range was evaluated, as it must be - // reevaluated for the new hashList. - minBlock += int32(i) - hashList, err = recoverFromReorg(dag, - minBlock, maxBlock, lastBlockHash) - if err != nil { - return nil, err - } - if len(hashList) == 0 { - break fetchRange - } - goto loopHashList - } - if i == 0 && lastBlockHash != nil { - // Ensure the new hashList is on the same fork - // as the last block from the old hashList. - jsonErr := descendantBlock(lastBlockHash, blk) - if jsonErr != nil { - return nil, jsonErr - } - } - - // A select statement is used to stop rescans if the - // client requesting the rescan has disconnected. - select { - case <-wsc.quit: - rpcsLog.Debugf("Stopped rescan at height %v "+ - "for disconnected client", blk.Height()) - return nil, nil - default: - rescanBlock(wsc, &lookups, blk) - lastBlock = blk - lastBlockHash = blk.Hash() - } - - // Periodically notify the client of the progress - // completed. Continue with next block if no progress - // notification is needed yet. - select { - case <-ticker.C: // fallthrough - default: - continue - } - - n := btcjson.NewRescanProgressNtfn(hashList[i].String(), - blk.Height(), blk.MsgBlock().Header.Timestamp.Unix()) - mn, err := btcjson.MarshalCmd(nil, n) - if err != nil { - rpcsLog.Errorf("Failed to marshal rescan "+ - "progress notification: %v", err) - continue - } - - if err = wsc.QueueNotification(mn); err == ErrClientQuit { - // Finished if the client disconnected. - rpcsLog.Debugf("Stopped rescan at height %v "+ - "for disconnected client", blk.Height()) - return nil, nil - } - } - - minBlock += int32(len(hashList)) - } - - // Notify websocket client of the finished rescan. Due to how btcd - // asynchronously queues notifications to not block calling code, - // there is no guarantee that any of the notifications created during - // rescan (such as rescanprogress, recvtx and redeemingtx) will be - // received before the rescan RPC returns. Therefore, another method - // is needed to safely inform clients that all rescan notifications have - // been sent. - n := btcjson.NewRescanFinishedNtfn(lastBlockHash.String(), - lastBlock.Height(), - lastBlock.MsgBlock().Header.Timestamp.Unix()) - if mn, err := btcjson.MarshalCmd(nil, n); err != nil { - rpcsLog.Errorf("Failed to marshal rescan finished "+ - "notification: %v", err) - } else { - // The rescan is finished, so we don't care whether the client - // has disconnected at this point, so discard error. - _ = wsc.QueueNotification(mn) - } - - rpcsLog.Info("Finished rescan") - return nil, nil -} - func init() { wsHandlers = wsHandlersBeforeInit }