mirror of
https://github.com/kaspanet/kaspad.git
synced 2025-06-11 16:46:42 +00:00
[NOD-1316] Refactor TestGHOSTDAG to enable arbitrary DAGs (#899)
* Add VirtualBlueHashes to BlockDAG * Refactor TestGHOSTDAG to read DAGs from json files * Added a new DAG for the ghostdag test suite * Pass BehaviorFlags to delayed blocks
This commit is contained in:
parent
bbb9dfa4cd
commit
c82a951a24
@ -285,6 +285,17 @@ func (dag *BlockDAG) SelectedTipBlueScore() uint64 {
|
||||
return dag.selectedTip().blueScore
|
||||
}
|
||||
|
||||
// VirtualBlueHashes returns the blue of the current virtual block
|
||||
func (dag *BlockDAG) VirtualBlueHashes() []*daghash.Hash {
|
||||
dag.RLock()
|
||||
defer dag.RUnlock()
|
||||
hashes := make([]*daghash.Hash, len(dag.virtual.blues))
|
||||
for i, blue := range dag.virtual.blues {
|
||||
hashes[i] = blue.hash
|
||||
}
|
||||
return hashes
|
||||
}
|
||||
|
||||
// VirtualBlueScore returns the blue score of the current virtual block
|
||||
func (dag *BlockDAG) VirtualBlueScore() uint64 {
|
||||
return dag.virtual.blueScore
|
||||
|
@ -12,6 +12,7 @@ import (
|
||||
type delayedBlock struct {
|
||||
block *util.Block
|
||||
processTime mstime.Time
|
||||
flags BehaviorFlags
|
||||
}
|
||||
|
||||
func (dag *BlockDAG) isKnownDelayedBlock(hash *daghash.Hash) bool {
|
||||
@ -19,12 +20,13 @@ func (dag *BlockDAG) isKnownDelayedBlock(hash *daghash.Hash) bool {
|
||||
return exists
|
||||
}
|
||||
|
||||
func (dag *BlockDAG) addDelayedBlock(block *util.Block, delay time.Duration) error {
|
||||
func (dag *BlockDAG) addDelayedBlock(block *util.Block, flags BehaviorFlags, delay time.Duration) error {
|
||||
processTime := dag.Now().Add(delay)
|
||||
log.Debugf("Adding block to delayed blocks queue (block hash: %s, process time: %s)", block.Hash().String(), processTime)
|
||||
delayedBlock := &delayedBlock{
|
||||
block: block,
|
||||
processTime: processTime,
|
||||
flags: flags,
|
||||
}
|
||||
|
||||
dag.delayedBlocks[*block.Hash()] = delayedBlock
|
||||
@ -42,7 +44,7 @@ func (dag *BlockDAG) processDelayedBlocks() error {
|
||||
break
|
||||
}
|
||||
delayedBlock := dag.popDelayedBlock()
|
||||
_, _, err := dag.processBlockNoLock(delayedBlock.block, BFAfterDelay)
|
||||
_, _, err := dag.processBlockNoLock(delayedBlock.block, delayedBlock.flags|BFAfterDelay)
|
||||
if err != nil {
|
||||
log.Errorf("Error while processing delayed block (block %s): %s", delayedBlock.block.Hash().String(), err)
|
||||
// Rule errors should not be propagated as they refer only to the delayed block,
|
||||
|
@ -1,7 +1,10 @@
|
||||
package blockdag
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strings"
|
||||
@ -13,12 +16,19 @@ import (
|
||||
"github.com/kaspanet/kaspad/util/daghash"
|
||||
)
|
||||
|
||||
type testBlockData struct {
|
||||
parents []string
|
||||
id string // id is a virtual entity that is used only for tests so we can define relations between blocks without knowing their hash
|
||||
expectedScore uint64
|
||||
expectedSelectedParent string
|
||||
expectedBlues []string
|
||||
type block struct {
|
||||
ID string // id is a virtual entity that is used only for tests so we can define relations between blocks without knowing their hash
|
||||
ExpectedScore uint64
|
||||
ExpectedSelectedParent string
|
||||
ExpectedBlues []string
|
||||
Parents []string
|
||||
}
|
||||
|
||||
type testData struct {
|
||||
K dagconfig.KType
|
||||
GenesisID string
|
||||
ExpectedReds []string
|
||||
Blocks []block
|
||||
}
|
||||
|
||||
// TestGHOSTDAG iterates over several dag simulations, and checks
|
||||
@ -26,158 +36,26 @@ type testBlockData struct {
|
||||
// block are calculated as expected.
|
||||
func TestGHOSTDAG(t *testing.T) {
|
||||
dagParams := dagconfig.SimnetParams
|
||||
err := filepath.Walk("./testdata/dags/", func(path string, info os.FileInfo, err error) error {
|
||||
if info.IsDir() {
|
||||
return nil
|
||||
}
|
||||
var test testData
|
||||
file, err := os.Open(path)
|
||||
if err != nil {
|
||||
t.Fatalf("TestGHOSTDAG: failed opening file: %s", path)
|
||||
}
|
||||
decoder := json.NewDecoder(file)
|
||||
decoder.DisallowUnknownFields()
|
||||
err = decoder.Decode(&test)
|
||||
if err != nil {
|
||||
t.Fatalf("TestGHOSTDAG: test: %s, failed decoding json: %v", info.Name(), err)
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
k dagconfig.KType
|
||||
expectedReds []string
|
||||
dagData []*testBlockData
|
||||
}{
|
||||
{
|
||||
k: 3,
|
||||
expectedReds: []string{"F", "G", "H", "I", "O", "P"},
|
||||
dagData: []*testBlockData{
|
||||
{
|
||||
parents: []string{"A"},
|
||||
id: "B",
|
||||
expectedScore: 1,
|
||||
expectedSelectedParent: "A",
|
||||
expectedBlues: []string{"A"},
|
||||
},
|
||||
{
|
||||
parents: []string{"B"},
|
||||
id: "C",
|
||||
expectedScore: 2,
|
||||
expectedSelectedParent: "B",
|
||||
expectedBlues: []string{"B"},
|
||||
},
|
||||
{
|
||||
parents: []string{"A"},
|
||||
id: "D",
|
||||
expectedScore: 1,
|
||||
expectedSelectedParent: "A",
|
||||
expectedBlues: []string{"A"},
|
||||
},
|
||||
{
|
||||
parents: []string{"C", "D"},
|
||||
id: "E",
|
||||
expectedScore: 4,
|
||||
expectedSelectedParent: "C",
|
||||
expectedBlues: []string{"C", "D"},
|
||||
},
|
||||
{
|
||||
parents: []string{"A"},
|
||||
id: "F",
|
||||
expectedScore: 1,
|
||||
expectedSelectedParent: "A",
|
||||
expectedBlues: []string{"A"},
|
||||
},
|
||||
{
|
||||
parents: []string{"F"},
|
||||
id: "G",
|
||||
expectedScore: 2,
|
||||
expectedSelectedParent: "F",
|
||||
expectedBlues: []string{"F"},
|
||||
},
|
||||
{
|
||||
parents: []string{"A"},
|
||||
id: "H",
|
||||
expectedScore: 1,
|
||||
expectedSelectedParent: "A",
|
||||
expectedBlues: []string{"A"},
|
||||
},
|
||||
{
|
||||
parents: []string{"A"},
|
||||
id: "I",
|
||||
expectedScore: 1,
|
||||
expectedSelectedParent: "A",
|
||||
expectedBlues: []string{"A"},
|
||||
},
|
||||
{
|
||||
parents: []string{"E", "G"},
|
||||
id: "J",
|
||||
expectedScore: 5,
|
||||
expectedSelectedParent: "E",
|
||||
expectedBlues: []string{"E"},
|
||||
},
|
||||
{
|
||||
parents: []string{"J"},
|
||||
id: "K",
|
||||
expectedScore: 6,
|
||||
expectedSelectedParent: "J",
|
||||
expectedBlues: []string{"J"},
|
||||
},
|
||||
{
|
||||
parents: []string{"I", "K"},
|
||||
id: "L",
|
||||
expectedScore: 7,
|
||||
expectedSelectedParent: "K",
|
||||
expectedBlues: []string{"K"},
|
||||
},
|
||||
{
|
||||
parents: []string{"L"},
|
||||
id: "M",
|
||||
expectedScore: 8,
|
||||
expectedSelectedParent: "L",
|
||||
expectedBlues: []string{"L"},
|
||||
},
|
||||
{
|
||||
parents: []string{"M"},
|
||||
id: "N",
|
||||
expectedScore: 9,
|
||||
expectedSelectedParent: "M",
|
||||
expectedBlues: []string{"M"},
|
||||
},
|
||||
{
|
||||
parents: []string{"M"},
|
||||
id: "O",
|
||||
expectedScore: 9,
|
||||
expectedSelectedParent: "M",
|
||||
expectedBlues: []string{"M"},
|
||||
},
|
||||
{
|
||||
parents: []string{"M"},
|
||||
id: "P",
|
||||
expectedScore: 9,
|
||||
expectedSelectedParent: "M",
|
||||
expectedBlues: []string{"M"},
|
||||
},
|
||||
{
|
||||
parents: []string{"M"},
|
||||
id: "Q",
|
||||
expectedScore: 9,
|
||||
expectedSelectedParent: "M",
|
||||
expectedBlues: []string{"M"},
|
||||
},
|
||||
{
|
||||
parents: []string{"M"},
|
||||
id: "R",
|
||||
expectedScore: 9,
|
||||
expectedSelectedParent: "M",
|
||||
expectedBlues: []string{"M"},
|
||||
},
|
||||
{
|
||||
parents: []string{"R"},
|
||||
id: "S",
|
||||
expectedScore: 10,
|
||||
expectedSelectedParent: "R",
|
||||
expectedBlues: []string{"R"},
|
||||
},
|
||||
{
|
||||
parents: []string{"N", "O", "P", "Q", "S"},
|
||||
id: "T",
|
||||
expectedScore: 13,
|
||||
expectedSelectedParent: "S",
|
||||
expectedBlues: []string{"S", "Q", "N"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for i, test := range tests {
|
||||
func() {
|
||||
resetExtraNonceForTest()
|
||||
dagParams.K = test.k
|
||||
dag, teardownFunc, err := DAGSetup(fmt.Sprintf("TestGHOSTDAG%d", i), true, Config{
|
||||
dagParams.K = test.K
|
||||
dag, teardownFunc, err := DAGSetup(fmt.Sprintf("TestGHOSTDAG %s", info.Name()), true, Config{
|
||||
DAGParams: &dagParams,
|
||||
})
|
||||
if err != nil {
|
||||
@ -188,32 +66,33 @@ func TestGHOSTDAG(t *testing.T) {
|
||||
genesisNode := dag.genesis
|
||||
blockByIDMap := make(map[string]*blockNode)
|
||||
idByBlockMap := make(map[*blockNode]string)
|
||||
blockByIDMap["A"] = genesisNode
|
||||
idByBlockMap[genesisNode] = "A"
|
||||
blockByIDMap[test.GenesisID] = genesisNode
|
||||
idByBlockMap[genesisNode] = test.GenesisID
|
||||
|
||||
for _, blockData := range test.dagData {
|
||||
for _, blockData := range test.Blocks {
|
||||
parents := blockSet{}
|
||||
for _, parentID := range blockData.parents {
|
||||
for _, parentID := range blockData.Parents {
|
||||
parent := blockByIDMap[parentID]
|
||||
parents.add(parent)
|
||||
}
|
||||
|
||||
block, err := PrepareBlockForTest(dag, parents.hashes(), nil)
|
||||
if err != nil {
|
||||
t.Fatalf("TestGHOSTDAG: block %v got unexpected error from PrepareBlockForTest: %v", blockData.id, err)
|
||||
t.Fatalf("TestGHOSTDAG: block %s got unexpected error from PrepareBlockForTest: %v", blockData.ID,
|
||||
err)
|
||||
}
|
||||
|
||||
utilBlock := util.NewBlock(block)
|
||||
isOrphan, isDelayed, err := dag.ProcessBlock(utilBlock, BFNoPoWCheck)
|
||||
if err != nil {
|
||||
t.Fatalf("TestGHOSTDAG: dag.ProcessBlock got unexpected error for block %v: %v", blockData.id, err)
|
||||
t.Fatalf("TestGHOSTDAG: dag.ProcessBlock got unexpected error for block %s: %v", blockData.ID, err)
|
||||
}
|
||||
if isDelayed {
|
||||
t.Fatalf("TestGHOSTDAG: block %s "+
|
||||
"is too far in the future", blockData.id)
|
||||
"is too far in the future", blockData.ID)
|
||||
}
|
||||
if isOrphan {
|
||||
t.Fatalf("TestGHOSTDAG: block %v was unexpectedly orphan", blockData.id)
|
||||
t.Fatalf("TestGHOSTDAG: block %s was unexpectedly orphan", blockData.ID)
|
||||
}
|
||||
|
||||
node, ok := dag.index.LookupNode(utilBlock.Hash())
|
||||
@ -221,8 +100,8 @@ func TestGHOSTDAG(t *testing.T) {
|
||||
t.Fatalf("block %s does not exist in the DAG", utilBlock.Hash())
|
||||
}
|
||||
|
||||
blockByIDMap[blockData.id] = node
|
||||
idByBlockMap[node] = blockData.id
|
||||
blockByIDMap[blockData.ID] = node
|
||||
idByBlockMap[node] = blockData.ID
|
||||
|
||||
bluesIDs := make([]string, 0, len(node.blues))
|
||||
for _, blue := range node.blues {
|
||||
@ -231,17 +110,17 @@ func TestGHOSTDAG(t *testing.T) {
|
||||
selectedParentID := idByBlockMap[node.selectedParent]
|
||||
fullDataStr := fmt.Sprintf("blues: %v, selectedParent: %v, score: %v",
|
||||
bluesIDs, selectedParentID, node.blueScore)
|
||||
if blockData.expectedScore != node.blueScore {
|
||||
t.Errorf("Test %d: Block %v expected to have score %v but got %v (fulldata: %v)",
|
||||
i, blockData.id, blockData.expectedScore, node.blueScore, fullDataStr)
|
||||
if blockData.ExpectedScore != node.blueScore {
|
||||
t.Errorf("Test %s: Block %s expected to have score %v but got %v (fulldata: %v)",
|
||||
info.Name(), blockData.ID, blockData.ExpectedScore, node.blueScore, fullDataStr)
|
||||
}
|
||||
if blockData.expectedSelectedParent != selectedParentID {
|
||||
t.Errorf("Test %d: Block %v expected to have selected parent %v but got %v (fulldata: %v)",
|
||||
i, blockData.id, blockData.expectedSelectedParent, selectedParentID, fullDataStr)
|
||||
if blockData.ExpectedSelectedParent != selectedParentID {
|
||||
t.Errorf("Test %s: Block %s expected to have selected parent %v but got %v (fulldata: %v)",
|
||||
info.Name(), blockData.ID, blockData.ExpectedSelectedParent, selectedParentID, fullDataStr)
|
||||
}
|
||||
if !reflect.DeepEqual(blockData.expectedBlues, bluesIDs) {
|
||||
t.Errorf("Test %d: Block %v expected to have blues %v but got %v (fulldata: %v)",
|
||||
i, blockData.id, blockData.expectedBlues, bluesIDs, fullDataStr)
|
||||
if !reflect.DeepEqual(blockData.ExpectedBlues, bluesIDs) {
|
||||
t.Errorf("Test %s: Block %s expected to have blues %v but got %v (fulldata: %v)",
|
||||
info.Name(), blockData.ID, blockData.ExpectedBlues, bluesIDs, fullDataStr)
|
||||
}
|
||||
}
|
||||
|
||||
@ -259,16 +138,22 @@ func TestGHOSTDAG(t *testing.T) {
|
||||
delete(reds, blueID)
|
||||
}
|
||||
}
|
||||
if !checkReds(test.expectedReds, reds) {
|
||||
if !checkReds(test.ExpectedReds, reds) {
|
||||
redsIDs := make([]string, 0, len(reds))
|
||||
for id := range reds {
|
||||
redsIDs = append(redsIDs, id)
|
||||
}
|
||||
sort.Strings(redsIDs)
|
||||
sort.Strings(test.expectedReds)
|
||||
t.Errorf("Test %d: Expected reds %v but got %v", i, test.expectedReds, redsIDs)
|
||||
sort.Strings(test.ExpectedReds)
|
||||
t.Errorf("Test %s: Expected reds %v but got %v", info.Name(), test.ExpectedReds, redsIDs)
|
||||
}
|
||||
}()
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -86,7 +86,7 @@ func (dag *BlockDAG) checkBlockDelay(block *util.Block, flags BehaviorFlags) (is
|
||||
}
|
||||
|
||||
if isDelayed {
|
||||
err := dag.addDelayedBlock(block, delay)
|
||||
err := dag.addDelayedBlock(block, flags, delay)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
@ -114,7 +114,7 @@ func (dag *BlockDAG) checkMissingParents(block *util.Block, flags BehaviorFlags)
|
||||
if isParentDelayed {
|
||||
// Add Millisecond to ensure that parent process time will be after its child.
|
||||
delay += time.Millisecond
|
||||
err := dag.addDelayedBlock(block, delay)
|
||||
err := dag.addDelayedBlock(block, flags, delay)
|
||||
if err != nil {
|
||||
return false, false, err
|
||||
}
|
||||
|
233
domain/blockdag/testdata/dags/dag0.json
vendored
Normal file
233
domain/blockdag/testdata/dags/dag0.json
vendored
Normal file
@ -0,0 +1,233 @@
|
||||
{
|
||||
"K": 4,
|
||||
"GenesisID": "A",
|
||||
"ExpectedReds": [
|
||||
"Q",
|
||||
"H",
|
||||
"I"
|
||||
],
|
||||
"Blocks": [
|
||||
{
|
||||
"ID": "B",
|
||||
"ExpectedScore": 1,
|
||||
"ExpectedSelectedParent": "A",
|
||||
"ExpectedBlues": [
|
||||
"A"
|
||||
],
|
||||
"Parents": [
|
||||
"A"
|
||||
]
|
||||
},
|
||||
{
|
||||
"ID": "C",
|
||||
"ExpectedScore": 2,
|
||||
"ExpectedSelectedParent": "B",
|
||||
"ExpectedBlues": [
|
||||
"B"
|
||||
],
|
||||
"Parents": [
|
||||
"B"
|
||||
]
|
||||
},
|
||||
{
|
||||
"ID": "D",
|
||||
"ExpectedScore": 1,
|
||||
"ExpectedSelectedParent": "A",
|
||||
"ExpectedBlues": [
|
||||
"A"
|
||||
],
|
||||
"Parents": [
|
||||
"A"
|
||||
]
|
||||
},
|
||||
{
|
||||
"ID": "E",
|
||||
"ExpectedScore": 4,
|
||||
"ExpectedSelectedParent": "C",
|
||||
"ExpectedBlues": [
|
||||
"C",
|
||||
"D"
|
||||
],
|
||||
"Parents": [
|
||||
"C",
|
||||
"D"
|
||||
]
|
||||
},
|
||||
{
|
||||
"ID": "F",
|
||||
"ExpectedScore": 1,
|
||||
"ExpectedSelectedParent": "A",
|
||||
"ExpectedBlues": [
|
||||
"A"
|
||||
],
|
||||
"Parents": [
|
||||
"A"
|
||||
]
|
||||
},
|
||||
{
|
||||
"ID": "G",
|
||||
"ExpectedScore": 2,
|
||||
"ExpectedSelectedParent": "F",
|
||||
"ExpectedBlues": [
|
||||
"F"
|
||||
],
|
||||
"Parents": [
|
||||
"F"
|
||||
]
|
||||
},
|
||||
{
|
||||
"ID": "H",
|
||||
"ExpectedScore": 1,
|
||||
"ExpectedSelectedParent": "A",
|
||||
"ExpectedBlues": [
|
||||
"A"
|
||||
],
|
||||
"Parents": [
|
||||
"A"
|
||||
]
|
||||
},
|
||||
{
|
||||
"ID": "I",
|
||||
"ExpectedScore": 1,
|
||||
"ExpectedSelectedParent": "A",
|
||||
"ExpectedBlues": [
|
||||
"A"
|
||||
],
|
||||
"Parents": [
|
||||
"A"
|
||||
]
|
||||
},
|
||||
{
|
||||
"ID": "J",
|
||||
"ExpectedScore": 7,
|
||||
"ExpectedSelectedParent": "E",
|
||||
"ExpectedBlues": [
|
||||
"E",
|
||||
"F",
|
||||
"G"
|
||||
],
|
||||
"Parents": [
|
||||
"E",
|
||||
"G"
|
||||
]
|
||||
},
|
||||
{
|
||||
"ID": "K",
|
||||
"ExpectedScore": 8,
|
||||
"ExpectedSelectedParent": "J",
|
||||
"ExpectedBlues": [
|
||||
"J"
|
||||
],
|
||||
"Parents": [
|
||||
"J"
|
||||
]
|
||||
},
|
||||
{
|
||||
"ID": "L",
|
||||
"ExpectedScore": 9,
|
||||
"ExpectedSelectedParent": "K",
|
||||
"ExpectedBlues": [
|
||||
"K"
|
||||
],
|
||||
"Parents": [
|
||||
"I",
|
||||
"K"
|
||||
]
|
||||
},
|
||||
{
|
||||
"ID": "M",
|
||||
"ExpectedScore": 10,
|
||||
"ExpectedSelectedParent": "L",
|
||||
"ExpectedBlues": [
|
||||
"L"
|
||||
],
|
||||
"Parents": [
|
||||
"L"
|
||||
]
|
||||
},
|
||||
{
|
||||
"ID": "N",
|
||||
"ExpectedScore": 11,
|
||||
"ExpectedSelectedParent": "M",
|
||||
"ExpectedBlues": [
|
||||
"M"
|
||||
],
|
||||
"Parents": [
|
||||
"M"
|
||||
]
|
||||
},
|
||||
{
|
||||
"ID": "O",
|
||||
"ExpectedScore": 11,
|
||||
"ExpectedSelectedParent": "M",
|
||||
"ExpectedBlues": [
|
||||
"M"
|
||||
],
|
||||
"Parents": [
|
||||
"M"
|
||||
]
|
||||
},
|
||||
{
|
||||
"ID": "P",
|
||||
"ExpectedScore": 11,
|
||||
"ExpectedSelectedParent": "M",
|
||||
"ExpectedBlues": [
|
||||
"M"
|
||||
],
|
||||
"Parents": [
|
||||
"M"
|
||||
]
|
||||
},
|
||||
{
|
||||
"ID": "Q",
|
||||
"ExpectedScore": 11,
|
||||
"ExpectedSelectedParent": "M",
|
||||
"ExpectedBlues": [
|
||||
"M"
|
||||
],
|
||||
"Parents": [
|
||||
"M"
|
||||
]
|
||||
},
|
||||
{
|
||||
"ID": "R",
|
||||
"ExpectedScore": 11,
|
||||
"ExpectedSelectedParent": "M",
|
||||
"ExpectedBlues": [
|
||||
"M"
|
||||
],
|
||||
"Parents": [
|
||||
"M"
|
||||
]
|
||||
},
|
||||
{
|
||||
"ID": "S",
|
||||
"ExpectedScore": 12,
|
||||
"ExpectedSelectedParent": "R",
|
||||
"ExpectedBlues": [
|
||||
"R"
|
||||
],
|
||||
"Parents": [
|
||||
"R"
|
||||
]
|
||||
},
|
||||
{
|
||||
"ID": "T",
|
||||
"ExpectedScore": 16,
|
||||
"ExpectedSelectedParent": "S",
|
||||
"ExpectedBlues": [
|
||||
"S",
|
||||
"P",
|
||||
"N",
|
||||
"O"
|
||||
],
|
||||
"Parents": [
|
||||
"N",
|
||||
"O",
|
||||
"P",
|
||||
"Q",
|
||||
"S"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
386
domain/blockdag/testdata/dags/dag1.json
vendored
Normal file
386
domain/blockdag/testdata/dags/dag1.json
vendored
Normal file
@ -0,0 +1,386 @@
|
||||
{
|
||||
"K": 4,
|
||||
"GenesisID": "0",
|
||||
"ExpectedReds": [
|
||||
"12",
|
||||
"30",
|
||||
"6",
|
||||
"27",
|
||||
"4",
|
||||
"16",
|
||||
"7",
|
||||
"23",
|
||||
"24",
|
||||
"11",
|
||||
"15",
|
||||
"19",
|
||||
"9"
|
||||
],
|
||||
"Blocks": [
|
||||
{
|
||||
"ID": "1",
|
||||
"ExpectedScore": 1,
|
||||
"ExpectedSelectedParent": "0",
|
||||
"ExpectedBlues": [
|
||||
"0"
|
||||
],
|
||||
"Parents": [
|
||||
"0"
|
||||
]
|
||||
},
|
||||
{
|
||||
"ID": "2",
|
||||
"ExpectedScore": 1,
|
||||
"ExpectedSelectedParent": "0",
|
||||
"ExpectedBlues": [
|
||||
"0"
|
||||
],
|
||||
"Parents": [
|
||||
"0"
|
||||
]
|
||||
},
|
||||
{
|
||||
"ID": "3",
|
||||
"ExpectedScore": 1,
|
||||
"ExpectedSelectedParent": "0",
|
||||
"ExpectedBlues": [
|
||||
"0"
|
||||
],
|
||||
"Parents": [
|
||||
"0"
|
||||
]
|
||||
},
|
||||
{
|
||||
"ID": "4",
|
||||
"ExpectedScore": 2,
|
||||
"ExpectedSelectedParent": "1",
|
||||
"ExpectedBlues": [
|
||||
"1"
|
||||
],
|
||||
"Parents": [
|
||||
"1"
|
||||
]
|
||||
},
|
||||
{
|
||||
"ID": "5",
|
||||
"ExpectedScore": 3,
|
||||
"ExpectedSelectedParent": "2",
|
||||
"ExpectedBlues": [
|
||||
"2",
|
||||
"3"
|
||||
],
|
||||
"Parents": [
|
||||
"2",
|
||||
"3"
|
||||
]
|
||||
},
|
||||
{
|
||||
"ID": "6",
|
||||
"ExpectedScore": 2,
|
||||
"ExpectedSelectedParent": "3",
|
||||
"ExpectedBlues": [
|
||||
"3"
|
||||
],
|
||||
"Parents": [
|
||||
"3"
|
||||
]
|
||||
},
|
||||
{
|
||||
"ID": "7",
|
||||
"ExpectedScore": 3,
|
||||
"ExpectedSelectedParent": "6",
|
||||
"ExpectedBlues": [
|
||||
"6"
|
||||
],
|
||||
"Parents": [
|
||||
"6"
|
||||
]
|
||||
},
|
||||
{
|
||||
"ID": "8",
|
||||
"ExpectedScore": 3,
|
||||
"ExpectedSelectedParent": "2",
|
||||
"ExpectedBlues": [
|
||||
"2",
|
||||
"1"
|
||||
],
|
||||
"Parents": [
|
||||
"1",
|
||||
"2"
|
||||
]
|
||||
},
|
||||
{
|
||||
"ID": "9",
|
||||
"ExpectedScore": 5,
|
||||
"ExpectedSelectedParent": "5",
|
||||
"ExpectedBlues": [
|
||||
"5",
|
||||
"6"
|
||||
],
|
||||
"Parents": [
|
||||
"5",
|
||||
"6"
|
||||
]
|
||||
},
|
||||
{
|
||||
"ID": "10",
|
||||
"ExpectedScore": 5,
|
||||
"ExpectedSelectedParent": "8",
|
||||
"ExpectedBlues": [
|
||||
"8",
|
||||
"4"
|
||||
],
|
||||
"Parents": [
|
||||
"8",
|
||||
"4"
|
||||
]
|
||||
},
|
||||
{
|
||||
"ID": "11",
|
||||
"ExpectedScore": 7,
|
||||
"ExpectedSelectedParent": "9",
|
||||
"ExpectedBlues": [
|
||||
"9",
|
||||
"7"
|
||||
],
|
||||
"Parents": [
|
||||
"7",
|
||||
"9"
|
||||
]
|
||||
},
|
||||
{
|
||||
"ID": "12",
|
||||
"ExpectedScore": 8,
|
||||
"ExpectedSelectedParent": "9",
|
||||
"ExpectedBlues": [
|
||||
"9",
|
||||
"8",
|
||||
"10"
|
||||
],
|
||||
"Parents": [
|
||||
"10",
|
||||
"9"
|
||||
]
|
||||
},
|
||||
{
|
||||
"ID": "13",
|
||||
"ExpectedScore": 6,
|
||||
"ExpectedSelectedParent": "8",
|
||||
"ExpectedBlues": [
|
||||
"8",
|
||||
"3",
|
||||
"5"
|
||||
],
|
||||
"Parents": [
|
||||
"5",
|
||||
"8"
|
||||
]
|
||||
},
|
||||
{
|
||||
"ID": "14",
|
||||
"ExpectedScore": 8,
|
||||
"ExpectedSelectedParent": "13",
|
||||
"ExpectedBlues": [
|
||||
"13",
|
||||
"10"
|
||||
],
|
||||
"Parents": [
|
||||
"13",
|
||||
"10"
|
||||
]
|
||||
},
|
||||
{
|
||||
"ID": "15",
|
||||
"ExpectedScore": 9,
|
||||
"ExpectedSelectedParent": "11",
|
||||
"ExpectedBlues": [
|
||||
"11",
|
||||
"13"
|
||||
],
|
||||
"Parents": [
|
||||
"11",
|
||||
"13"
|
||||
]
|
||||
},
|
||||
{
|
||||
"ID": "16",
|
||||
"ExpectedScore": 8,
|
||||
"ExpectedSelectedParent": "11",
|
||||
"ExpectedBlues": [
|
||||
"11"
|
||||
],
|
||||
"Parents": [
|
||||
"11"
|
||||
]
|
||||
},
|
||||
{
|
||||
"ID": "17",
|
||||
"ExpectedScore": 9,
|
||||
"ExpectedSelectedParent": "14",
|
||||
"ExpectedBlues": [
|
||||
"14"
|
||||
],
|
||||
"Parents": [
|
||||
"14"
|
||||
]
|
||||
},
|
||||
{
|
||||
"ID": "18",
|
||||
"ExpectedScore": 7,
|
||||
"ExpectedSelectedParent": "13",
|
||||
"ExpectedBlues": [
|
||||
"13"
|
||||
],
|
||||
"Parents": [
|
||||
"13"
|
||||
]
|
||||
},
|
||||
{
|
||||
"ID": "19",
|
||||
"ExpectedScore": 10,
|
||||
"ExpectedSelectedParent": "15",
|
||||
"ExpectedBlues": [
|
||||
"15"
|
||||
],
|
||||
"Parents": [
|
||||
"18",
|
||||
"15"
|
||||
]
|
||||
},
|
||||
{
|
||||
"ID": "20",
|
||||
"ExpectedScore": 10,
|
||||
"ExpectedSelectedParent": "17",
|
||||
"ExpectedBlues": [
|
||||
"17"
|
||||
],
|
||||
"Parents": [
|
||||
"16",
|
||||
"17"
|
||||
]
|
||||
},
|
||||
{
|
||||
"ID": "21",
|
||||
"ExpectedScore": 12,
|
||||
"ExpectedSelectedParent": "20",
|
||||
"ExpectedBlues": [
|
||||
"20",
|
||||
"18"
|
||||
],
|
||||
"Parents": [
|
||||
"18",
|
||||
"20"
|
||||
]
|
||||
},
|
||||
{
|
||||
"ID": "22",
|
||||
"ExpectedScore": 13,
|
||||
"ExpectedSelectedParent": "21",
|
||||
"ExpectedBlues": [
|
||||
"21"
|
||||
],
|
||||
"Parents": [
|
||||
"19",
|
||||
"21"
|
||||
]
|
||||
},
|
||||
{
|
||||
"ID": "23",
|
||||
"ExpectedScore": 11,
|
||||
"ExpectedSelectedParent": "17",
|
||||
"ExpectedBlues": [
|
||||
"17",
|
||||
"12"
|
||||
],
|
||||
"Parents": [
|
||||
"12",
|
||||
"17"
|
||||
]
|
||||
},
|
||||
{
|
||||
"ID": "24",
|
||||
"ExpectedScore": 13,
|
||||
"ExpectedSelectedParent": "23",
|
||||
"ExpectedBlues": [
|
||||
"23",
|
||||
"20"
|
||||
],
|
||||
"Parents": [
|
||||
"20",
|
||||
"23"
|
||||
]
|
||||
},
|
||||
{
|
||||
"ID": "25",
|
||||
"ExpectedScore": 13,
|
||||
"ExpectedSelectedParent": "21",
|
||||
"ExpectedBlues": [
|
||||
"21"
|
||||
],
|
||||
"Parents": [
|
||||
"21"
|
||||
]
|
||||
},
|
||||
{
|
||||
"ID": "26",
|
||||
"ExpectedScore": 15,
|
||||
"ExpectedSelectedParent": "22",
|
||||
"ExpectedBlues": [
|
||||
"22",
|
||||
"25"
|
||||
],
|
||||
"Parents": [
|
||||
"22",
|
||||
"24",
|
||||
"25"
|
||||
]
|
||||
},
|
||||
{
|
||||
"ID": "27",
|
||||
"ExpectedScore": 9,
|
||||
"ExpectedSelectedParent": "16",
|
||||
"ExpectedBlues": [
|
||||
"16"
|
||||
],
|
||||
"Parents": [
|
||||
"16"
|
||||
]
|
||||
},
|
||||
{
|
||||
"ID": "28",
|
||||
"ExpectedScore": 14,
|
||||
"ExpectedSelectedParent": "25",
|
||||
"ExpectedBlues": [
|
||||
"25"
|
||||
],
|
||||
"Parents": [
|
||||
"23",
|
||||
"25"
|
||||
]
|
||||
},
|
||||
{
|
||||
"ID": "29",
|
||||
"ExpectedScore": 17,
|
||||
"ExpectedSelectedParent": "26",
|
||||
"ExpectedBlues": [
|
||||
"26",
|
||||
"28"
|
||||
],
|
||||
"Parents": [
|
||||
"26",
|
||||
"28"
|
||||
]
|
||||
},
|
||||
{
|
||||
"ID": "30",
|
||||
"ExpectedScore": 10,
|
||||
"ExpectedSelectedParent": "27",
|
||||
"ExpectedBlues": [
|
||||
"27"
|
||||
],
|
||||
"Parents": [
|
||||
"27"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user