// 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 block import "fmt" import "sort" import "sync" import "strings" //import "runtime/debug" import "encoding/binary" //import "golang.org/x/crypto/sha3" import "github.com/deroproject/derohe/cryptography/crypto" //import "github.com/deroproject/derohe/astrobwt" type MiniBlocksCollection struct { Collection map[uint32]MiniBlock sync.RWMutex } // create a collection func CreateMiniBlockCollection() *MiniBlocksCollection { return &MiniBlocksCollection{Collection: map[uint32]MiniBlock{}} } // purge all heights less than this height func (c *MiniBlocksCollection) PurgeHeight(height int64) (purge_count int) { c.Lock() defer c.Unlock() for k, mbl := range c.Collection { if mbl.Height <= height { purge_count++ delete(c.Collection, k) } } return purge_count } func (c *MiniBlocksCollection) Count() int { c.Lock() defer c.Unlock() return len(c.Collection) } // check if already inserted func (c *MiniBlocksCollection) IsAlreadyInserted(mbl MiniBlock) bool { return c.IsCollision(mbl) } // check if collision will occur func (c *MiniBlocksCollection) IsCollision(mbl MiniBlock) bool { uid := mbl.GetMiniID() c.RLock() defer c.RUnlock() if _, ok := c.Collection[uid]; ok { //fmt.Printf("collision uid %08X %s %08X %s stack %s\n",uid,mbl.GetHash(), col.GetMiniID(), col.GetHash(),debug.Stack()) return true } return false } // check whether the miniblock is connected func (c *MiniBlocksCollection) IsConnected(mbl MiniBlock) bool { if mbl.Genesis { return true } c.RLock() defer c.RUnlock() for i := uint8(0); i < mbl.PastCount; i++ { if _, ok := c.Collection[mbl.Past[i]]; !ok { return false } } return true } // get distance from base func (c *MiniBlocksCollection) CalculateDistance(mbl MiniBlock) uint32 { if mbl.Genesis { return 0 } c.RLock() defer c.RUnlock() max_distance := uint32(0) for i := uint8(0); i < mbl.PastCount; i++ { if prev, ok := c.Collection[mbl.Past[i]]; !ok { panic("past should be present") } else if prev.Distance > max_distance { max_distance = prev.Distance } } return max_distance } func (c *MiniBlocksCollection) Get(id uint32) (mbl MiniBlock) { c.RLock() defer c.RUnlock() var ok bool if mbl, ok = c.Collection[id]; !ok { panic("past should be present") } return mbl } // insert a miniblock func (c *MiniBlocksCollection) InsertMiniBlock(mbl MiniBlock) (err error, result bool) { if c.IsCollision(mbl) { return fmt.Errorf("collision %x", mbl.Serialize()), false } if !c.IsConnected(mbl) { return fmt.Errorf("not connected"), false } prev_distance := c.CalculateDistance(mbl) hash := mbl.GetHash() uid := binary.BigEndian.Uint32(hash[:]) c.Lock() defer c.Unlock() if _, ok := c.Collection[uid]; ok { return fmt.Errorf("collision1"), false } if mbl.Genesis { mbl.Height = int64(binary.BigEndian.Uint64(mbl.Check[:])) } else { for i := uint8(0); i < mbl.PastCount; i++ { if prev, ok := c.Collection[mbl.Past[i]]; !ok { return fmt.Errorf("no past found"), false } else { if mbl.Timestamp < prev.Timestamp { return fmt.Errorf("timestamp less than parent"), false // childs timestamp cannot be less than parent, atleast one is fudging } mbl.PastMiniBlocks = append(mbl.PastMiniBlocks, prev) mbl.Height = prev.Height } } mbl.Distance = prev_distance + 1 } c.Collection[uid] = mbl return nil, true } // get all the genesis blocks func (c *MiniBlocksCollection) GetAllGenesisMiniBlocks() (mbls []MiniBlock) { c.Lock() defer c.Unlock() for _, mbl := range c.Collection { if mbl.Genesis { mbls = append(mbls, mbl) } } return } // get all the tips from the map, this is n² func (c *MiniBlocksCollection) GetAllTips() (mbls []MiniBlock) { c.Lock() defer c.Unlock() clone := map[uint32]MiniBlock{} var clone_list []MiniBlock for k, v := range c.Collection { clone[k] = v clone_list = append(clone_list, v) } for _, mbl := range clone_list { if mbl.Genesis { continue // genesis tips do no have valid past } for i := uint8(0); i < mbl.PastCount; i++ { delete(clone, mbl.Past[i]) } } for _, v := range clone { mbls = append(mbls, v) } mbls = MiniBlocks_SortByDistanceDesc(mbls) return } // get all the tips from the map, this is atleast O(n) func (c *MiniBlocksCollection) GetAllTipsAtHeight(height int64) (mbls []MiniBlock) { c.Lock() defer c.Unlock() clone := map[uint32]MiniBlock{} var clone_list []MiniBlock for k, v := range c.Collection { if v.Height == height { clone[k] = v clone_list = append(clone_list, v) } } for _, mbl := range clone_list { if mbl.Genesis { continue // genesis tips do no have valid past } for i := uint8(0); i < mbl.PastCount; i++ { delete(clone, mbl.Past[i]) } } for _, v := range clone { mbls = append(mbls, v) } mbls = MiniBlocks_SortByDistanceDesc(mbls) return } // this works in all case func (c *MiniBlocksCollection) GetGenesisFromMiniBlock(mbl MiniBlock) (genesis []MiniBlock) { if mbl.Genesis { genesis = append(genesis, mbl) return } if len(mbl.PastMiniBlocks) >= 1 { // we do not need locks as all history is connected return GetGenesisFromMiniBlock(mbl) } c.Lock() defer c.Unlock() var tmp_genesis []MiniBlock for i := uint8(0); i < mbl.PastCount; i++ { if pmbl, ok := c.Collection[mbl.Past[i]]; ok { tmp_genesis = append(tmp_genesis, GetGenesisFromMiniBlock(pmbl)...) } else { return } } return MiniBlocks_Unique(tmp_genesis) } // this works in all cases, but it may return truncated history,all returns must be checked for connectivity func (c *MiniBlocksCollection) GetEntireMiniBlockHistory(mbl MiniBlock) (history []MiniBlock) { if mbl.Genesis { history = append(history, mbl) return } if len(mbl.PastMiniBlocks) >= 1 { // we do not need locks as all history is connected return GetEntireMiniBlockHistory(mbl) } c.Lock() defer c.Unlock() for i := uint8(0); i < mbl.PastCount; i++ { if pmbl, ok := c.Collection[mbl.Past[i]]; ok { history = append(history, GetEntireMiniBlockHistory(pmbl)...) } else { return } } history = append(history, mbl) // add self unique := MiniBlocks_Unique(history) return MiniBlocks_SortByTimeAsc(unique) } // gets the genesis from the tips // this function only works, if the miniblock has been expanded func GetGenesisFromMiniBlock(mbl MiniBlock) (genesis []MiniBlock) { if mbl.Genesis { genesis = append(genesis, mbl) return } var queue []MiniBlock queue = append(queue, mbl.PastMiniBlocks...) for len(queue) > 0 { item := queue[0] queue = queue[1:] // Dequeue if item.Genesis { genesis = append(genesis, item) } else { queue = append(queue, item.PastMiniBlocks...) } } return } // get entire history,its in sorted form func GetEntireMiniBlockHistory(mbls ...MiniBlock) (history []MiniBlock) { var queue []MiniBlock queue = append(queue, mbls...) for len(queue) > 0 { item := queue[0] queue = queue[1:] // Dequeue history = append(history, item) //mini blocks might be duplicated if !item.Genesis { queue = append(queue, item.PastMiniBlocks...) } } history = MiniBlocks_Unique(history) history = MiniBlocks_SortByTimeAsc(history) // sort on the basis of timestamps return } // this sorts by distance, in descending order // if distance is equal, then it sorts by its id which is collision free func MiniBlocks_SortByDistanceDesc(mbls []MiniBlock) (sorted []MiniBlock) { sorted = append(sorted, mbls...) sort.SliceStable(sorted, func(i, j int) bool { // sort descending on the basis of Distance if sorted[i].Distance == sorted[j].Distance { return sorted[i].GetMiniID() > sorted[j].GetMiniID() // higher mini id first } return sorted[i].Distance > sorted[j].Distance }) return sorted } // this sorts by timestamp,ascending order // if timestamp is equal, then it sorts by its id which is collision free func MiniBlocks_SortByTimeAsc(mbls []MiniBlock) (sorted []MiniBlock) { sorted = append(sorted, mbls...) sort.SliceStable(sorted, func(i, j int) bool { // sort on the basis of timestamps if sorted[i].Timestamp == sorted[j].Timestamp { return sorted[i].GetMiniID() < sorted[j].GetMiniID() } return sorted[i].Timestamp < sorted[j].Timestamp }) return sorted } func MiniBlocks_Unique(mbls []MiniBlock) (unique []MiniBlock) { unique_map := map[crypto.Hash]MiniBlock{} for _, mbl := range mbls { unique_map[mbl.GetHash()] = mbl } for _, v := range unique_map { unique = append(unique, v) } return } // will filter the mbls having the specific tips // this will also remove any blocks which do not refer to base func MiniBlocks_FilterOnlyGenesis(mbls []MiniBlock, tips []crypto.Hash) (result []MiniBlock) { var baselist []MiniBlock for _, mbl := range mbls { if mbl.Genesis { baselist = append(baselist, mbl) } } switch len(tips) { case 0: panic("atleast 1 tip must be present") case 1: pid1 := binary.BigEndian.Uint32(tips[0][:]) return MiniBlocks_Filter(baselist, []uint32{pid1}) case 2: pid1 := binary.BigEndian.Uint32(tips[0][:]) pid2 := binary.BigEndian.Uint32(tips[1][:]) return MiniBlocks_Filter(baselist, []uint32{pid1, pid2}) default: panic("only max 2 tips are supported") } } /* // this will filter if the blocks have any pids // this will remove any nonbase blocks func MiniBlocks_FilterPidsSkipGenesis(mbls []MiniBlock, pids []uint32) (result []MiniBlock) { var nongenesislist []MiniBlock for _, mbl := range mbls { if !mbl.Genesis { nongenesislist = append(nongenesislist, mbl) } } return MiniBlocks_Filter(nongenesislist, pids) } */ // this will filter if the blocks have any pids func MiniBlocks_Filter(mbls []MiniBlock, pids []uint32) (result []MiniBlock) { switch len(pids) { case 0: panic("atleast 1 pid must be present") case 1: pid1 := pids[0] for _, mbl := range mbls { if mbl.PastCount == uint8(len(pids)) && mbl.HasPid(pid1) { result = append(result, mbl) } } case 2: pid1 := pids[0] pid2 := pids[1] for _, mbl := range mbls { if mbl.PastCount == uint8(len(pids)) && mbl.HasPid(pid1) && mbl.HasPid(pid2) { result = append(result, mbl) } } default: panic("only max 2 tips are supported") } return } // draw out a dot graph func (c *MiniBlocksCollection) Graph() string { w := new(strings.Builder) w.WriteString("digraph miniblock_graphs { \n") for _, mbl := range c.Collection { // draw all nodes color := "green" if mbl.Genesis { color = "white" } w.WriteString(fmt.Sprintf("node [ fontsize=12 style=filled ]\n{\n")) w.WriteString(fmt.Sprintf("L%08x [ fillcolor=%s label = \"%08x %d height %d\" ];\n", mbl.GetMiniID(), color, mbl.GetMiniID(), 0, mbl.Distance)) w.WriteString(fmt.Sprintf("}\n")) if !mbl.Genesis { // render connections w.WriteString(fmt.Sprintf("L%08x -> L%08x ;\n", mbl.Past[0], mbl.GetMiniID())) if mbl.PastCount == 2 { w.WriteString(fmt.Sprintf("L%08x -> L%08x ;\n", mbl.Past[1], mbl.GetMiniID())) } } } w.WriteString("}\n") return w.String() }