// Copyright 2017-2021 DERO Project. All rights reserved. // Use of this source code in any form is governed by RESEARCH license. // license can be found in the LICENSE file. // GPG: 0F39 E425 8C65 3947 702A 8234 08B2 0360 A03A 9DE8 // // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY // EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF // MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL // THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, // PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, // STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF // THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. package p2p import "fmt" //import "net" import "time" import "math/big" import "math/bits" import "sync/atomic" import "encoding/binary" import "github.com/deroproject/derohe/config" import "github.com/deroproject/derohe/block" //import "github.com/deroproject/derohe/errormsg" import "github.com/deroproject/derohe/transaction" import "github.com/deroproject/graviton" import "github.com/deroproject/derohe/cryptography/crypto" //import "github.com/deroproject/derosuite/blockchain" // we are expecting other side to have a heavier PoW chain // this is for the case when the chain only moves in pruned state // if after bootstraping the chain can continousky sync for few minutes, this means we have got the job done func (connection *Connection) bootstrap_chain() { defer handle_connection_panic(connection) var request ChangeList var response Changes // var err error var zerohash crypto.Hash // we will request top 60 blocks ctopo := connection.TopoHeight var topos []int64 for i := ctopo - (max_request_topoheights - 1); i < ctopo; i++ { topos = append(topos, i) } connection.logger.Info("Bootstrap Initiated") for i := range topos { request.TopoHeights = append(request.TopoHeights, topos[i]) } fill_common(&request.Common) // fill common info if err := connection.RConn.Client.Call("Peer.ChangeSet", request, &response); err != nil { connection.logger.V(1).Error(err, "Call failed ChangeSet") return } // we have a response, see if its valid and try to add to get the blocks connection.logger.V(1).Info("changeset received", "keycount", response.KeyCount, "sccount", response.SCKeyCount) commit_version := uint64(0) { // fetch and commit balance tree chunksize := int64(640) chunks_estm := response.KeyCount / chunksize chunks := int64(1) // chunks need to be in power of 2 path_length := 0 for chunks < chunks_estm { chunks = chunks * 2 path_length++ } if chunks < 2 { chunks = 2 path_length = 1 } var section [8]byte total_keys := 0 for i := int64(0); i < chunks; i++ { binary.BigEndian.PutUint64(section[:], bits.Reverse64(uint64(i))) // place reverse path ts_request := Request_Tree_Section_Struct{Topo: request.TopoHeights[0], TreeName: []byte(config.BALANCE_TREE), Section: section[:], SectionLength: uint64(path_length)} var ts_response Response_Tree_Section_Struct fill_common(&ts_response.Common) if err := connection.RConn.Client.Call("Peer.TreeSection", ts_request, &ts_response); err != nil { connection.logger.V(2).Error(err, "Call failed TreeSection") return } else { // now we must write all the state changes to gravition var balance_tree *graviton.Tree if ss, err := chain.Store.Balance_store.LoadSnapshot(0); err != nil { panic(err) } else if balance_tree, err = ss.GetTree(config.BALANCE_TREE); err != nil { panic(err) } if len(ts_response.Keys) != len(ts_response.Values) { //rlog.Warnf("Incoming Key count %d value count %d \"%s\" ", len(ts_response.Keys), len(ts_response.Values), globals.CTXString(connection.logger)) connection.exit() return } //rlog.Debugf("chunk %d Will write %d keys\n", i, len(ts_response.Keys)) for j := range ts_response.Keys { balance_tree.Put(ts_response.Keys[j], ts_response.Values[j]) } total_keys += len(ts_response.Keys) commit_version, err = graviton.Commit(balance_tree) if err != nil { panic(err) } h, err := balance_tree.Hash() _ = h _ = err //rlog.Debugf("total keys %d hash %x err %s\n", total_keys, h, err) } connection.logger.Info("Bootstrap in progress(step1)", "percent", float32(i*100)/float32(chunks)) } } { // fetch and commit SC tree chunksize := int64(640) chunks_estm := response.SCKeyCount / chunksize chunks := int64(1) // chunks need to be in power of 2 path_length := 0 for chunks < chunks_estm { chunks = chunks * 2 path_length++ } if chunks < 2 { chunks = 2 path_length = 1 } var section [8]byte total_keys := 0 for i := int64(0); i < chunks; i++ { binary.BigEndian.PutUint64(section[:], bits.Reverse64(uint64(i))) // place reverse path ts_request := Request_Tree_Section_Struct{Topo: request.TopoHeights[0], TreeName: []byte(config.SC_META), Section: section[:], SectionLength: uint64(path_length)} var ts_response Response_Tree_Section_Struct fill_common(&ts_response.Common) if err := connection.RConn.Client.Call("Peer.TreeSection", ts_request, &ts_response); err != nil { connection.logger.V(2).Error(err, "Call failed TreeSection") return } else { // now we must write all the state changes to gravition var changed_trees []*graviton.Tree var sc_tree *graviton.Tree //var changed_trees []*graviton.Tree ss, err := chain.Store.Balance_store.LoadSnapshot(0) if err != nil { panic(err) } else if sc_tree, err = ss.GetTree(config.SC_META); err != nil { panic(err) } if len(ts_response.Keys) != len(ts_response.Values) { //rlog.Warnf("Incoming Key count %d value count %d \"%s\" ", len(ts_response.Keys), len(ts_response.Values), globals.CTXString(connection.logger)) connection.exit() return } //rlog.Debugf("SC chunk %d Will write %d keys\n", i, len(ts_response.Keys)) for j := range ts_response.Keys { sc_tree.Put(ts_response.Keys[j], ts_response.Values[j]) // we must fetch each individual SC tree sc_request := Request_Tree_Section_Struct{Topo: request.TopoHeights[0], TreeName: ts_response.Keys[j], Section: section[:], SectionLength: uint64(0)} var sc_response Response_Tree_Section_Struct fill_common(&sc_response.Common) if err := connection.RConn.Client.Call("Peer.TreeSection", sc_request, &sc_response); err != nil { connection.logger.V(2).Error(err, "Call failed TreeSection") return } else { var sc_data_tree *graviton.Tree if sc_data_tree, err = ss.GetTree(string(ts_response.Keys[j])); err != nil { panic(err) } else { for j := range sc_response.Keys { sc_data_tree.Put(sc_response.Keys[j], sc_response.Values[j]) } changed_trees = append(changed_trees, sc_data_tree) } } } total_keys += len(ts_response.Keys) changed_trees = append(changed_trees, sc_tree) commit_version, err = graviton.Commit(changed_trees...) if err != nil { panic(err) } h, err := sc_tree.Hash() _ = h _ = err //rlog.Debugf("total SC keys %d hash %x err %s\n", total_keys, h, err) } connection.logger.Info("Bootstrap in progress(step 2)", "percent", float32(i*100)/float32(chunks)) } } for i := int64(0); i <= request.TopoHeights[0]; i++ { chain.Store.Topo_store.Write(i, zerohash, commit_version, 0) // commit everything } for i := range response.CBlocks { // we must store the blocks var cbl block.Complete_Block // parse incoming block and deserialize it var bl block.Block // lets deserialize block first and see whether it is the requested object cbl.Bl = &bl err := bl.Deserialize(response.CBlocks[i].Block) if err != nil { // we have a block which could not be deserialized ban peer connection.logger.Error(err, "Error Incoming block could not be deserialised.") connection.exit() return } // give the chain some more time to respond atomic.StoreInt64(&connection.LastObjectRequestTime, time.Now().Unix()) if i == 0 { // whatever datastore we have written, its state hash must match // ToDo } // complete the txs for j := range response.CBlocks[i].Txs { var tx transaction.Transaction err = tx.Deserialize(response.CBlocks[i].Txs[j]) if err != nil { // we have a tx which could not be deserialized ban peer connection.logger.Error(err, "Error Incoming TX could not be deserialized") connection.exit() return } if bl.Tx_hashes[j] != tx.GetHash() { connection.logger.Error(err, "Error Incoming TX has mismatch.") connection.exit() return } cbl.Txs = append(cbl.Txs, &tx) } { // first lets save all the txs, together with their link to this block as height for i := 0; i < len(cbl.Txs); i++ { if err = chain.Store.Block_tx_store.WriteTX(bl.Tx_hashes[i], cbl.Txs[i].Serialize()); err != nil { panic(err) } } } diff := new(big.Int) if _, ok := diff.SetString(response.CBlocks[i].Difficulty, 10); !ok { // if Cumulative_Difficulty could not be parsed, kill connection connection.logger.Error(fmt.Errorf("Could not Parse Difficulty in common"), "", "diff", response.CBlocks[i].Difficulty) connection.exit() return } cdiff := new(big.Int) if _, ok := cdiff.SetString(response.CBlocks[i].Cumulative_Difficulty, 10); !ok { // if Cumulative_Difficulty could not be parsed, kill connection connection.logger.Error(fmt.Errorf("Could not Parse Difficulty in common"), "", "cdiff", response.CBlocks[i].Cumulative_Difficulty) connection.exit() return } if err = chain.Store.Block_tx_store.WriteBlock(bl.GetHash(), bl.Serialize(), diff, cdiff); err != nil { panic(fmt.Sprintf("error while writing block")) } // now we must write all the state changes to gravition var ss *graviton.Snapshot if ss, err = chain.Store.Balance_store.LoadSnapshot(0); err != nil { panic(err) } /*if len(response.CBlocks[i].Keys) != len(response.CBlocks[i].Values) { rlog.Warnf("Incoming Key count %d value count %d \"%s\" ", len(response.CBlocks[i].Keys), len(response.CBlocks[i].Values), globals.CTXString(connection.logger)) connection.exit() return }*/ write_count := 0 commit_version := ss.GetVersion() if i != 0 { var changed_trees []*graviton.Tree for _, change := range response.CBlocks[i].Changes { var tree *graviton.Tree if tree, err = ss.GetTree(string(change.TreeName)); err != nil { panic(err) } for j := range change.Keys { tree.Put(change.Keys[j], change.Values[j]) write_count++ } changed_trees = append(changed_trees, tree) } commit_version, err = graviton.Commit(changed_trees...) if err != nil { panic(err) } } connection.logger.V(2).Info("Writing version", "topoheight", request.TopoHeights[i], "keycount", write_count, "commit version ", commit_version) chain.Store.Topo_store.Write(request.TopoHeights[i], bl.GetHash(), commit_version, int64(bl.Height)) // commit everything } connection.logger.Info("Bootstrap completed successfully.") // load the chain from the disk chain.Initialise_Chain_From_DB() chain.Sync = true return }