Switch channels for function callback/iterators for Index
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
DataHoarder 2023-07-26 12:58:54 +02:00
parent deb3c6db01
commit a56a016f6c
Signed by: DataHoarder
SSH key fingerprint: SHA256:OLTRf6Fl87G52SiR7sWLGNzlJt4WOX+tfI2yxo0z7xk
10 changed files with 903 additions and 505 deletions

View file

@ -79,18 +79,13 @@ func main() {
return result
}
fillFoundBlockResult := func(params url.Values, foundBlocks []*index.FoundBlock) (result []*index.FoundBlock) {
result = make([]*index.FoundBlock, 0)
fillMiner := !params.Has("noMiner")
for _, foundBlock := range foundBlocks {
if fillMiner {
miner := indexDb.GetMiner(foundBlock.Miner)
foundBlock.MinerAddress = miner.Address()
foundBlock.MinerAlias = miner.Alias()
}
result = append(result, foundBlock)
fillFoundBlockResult := func(fillMiner bool, foundBlock *index.FoundBlock) (result *index.FoundBlock) {
if fillMiner {
miner := indexDb.GetMiner(foundBlock.Miner)
foundBlock.MinerAddress = miner.Address()
foundBlock.MinerAlias = miner.Alias()
}
return result
return foundBlock
}
fillSideBlockResult := func(fillUncles, fillMined, fillMiner bool, sideBlock *index.SideBlock) *index.SideBlock {
@ -114,14 +109,15 @@ func main() {
}
}
if fillUncles {
for u := range indexDb.GetSideBlocksByUncleOfId(sideBlock.TemplateId) {
index.QueryIterate(indexDb.GetSideBlocksByUncleOfId(sideBlock.TemplateId), func(_ int, u *index.SideBlock) (stop bool) {
sideBlock.Uncles = append(sideBlock.Uncles, index.SideBlockUncleEntry{
TemplateId: u.TemplateId,
Miner: u.Miner,
SideHeight: u.SideHeight,
Difficulty: u.Difficulty,
})
}
return false
})
}
return sideBlock
}
@ -242,7 +238,7 @@ func main() {
return result
}).(*totalKnownResult)
lastBlocksFound := indexDb.GetFoundBlocks("", 201)
lastBlocksFound := index.QueryIterateToSlice(indexDb.GetFoundBlocks("", 201))
mainTip := indexDb.GetMainBlockTip()
networkDifficulty := types.DifficultyFrom64(mainTip.Difficulty)
@ -357,7 +353,7 @@ func main() {
}
if len(lastBlocksFound) > 0 {
result.SideChain.LastFound = fillFoundBlockResult(nil, lastBlocksFound[:1])[0]
result.SideChain.LastFound = fillFoundBlockResult(false, lastBlocksFound[0])
}
lastPoolInfo.Store(result)
@ -536,7 +532,6 @@ func main() {
writer.Header().Set("Content-Type", "application/json; charset=utf-8")
writer.WriteHeader(http.StatusOK)
_ = httputils.EncodeJson(request, writer, indexDb.GetMainLikelySweepTransactionBySpendingGlobalOutputIndices(indices...))
})
serveMux.HandleFunc("/api/transaction_lookup/{txid:[0-9a-f]+}", func(writer http.ResponseWriter, request *http.Request) {
@ -755,8 +750,19 @@ func main() {
writer.Header().Set("Content-Type", "application/json; charset=utf-8")
writer.WriteHeader(http.StatusOK)
result := fillSideBlockChannelResult(params, indexDb.GetSideBlocksInWindow(from, window))
_ = httputils.StreamJsonSlice(request, writer, result)
fillUncles := !params.Has("noUncles")
fillMined := !params.Has("noMainStatus")
fillMiner := !params.Has("noMiner")
sideBlocks := indexDb.GetSideBlocksInWindow(from, window)
defer sideBlocks.Close()
_ = httputils.StreamJsonIterator(request, writer, func() (int, *index.SideBlock) {
i, v := sideBlocks.Next()
if v != nil {
v = fillSideBlockResult(fillUncles, fillMined, fillMiner, v)
}
return i, v
})
})
serveMux.HandleFunc("/api/side_blocks_in_window/{miner:[0-9]+|4[123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]+$}", func(writer http.ResponseWriter, request *http.Request) {
@ -813,8 +819,19 @@ func main() {
writer.Header().Set("Content-Type", "application/json; charset=utf-8")
writer.WriteHeader(http.StatusOK)
result := fillSideBlockChannelResult(params, indexDb.GetSideBlocksByMinerIdInWindow(miner.Id(), from, window))
_ = httputils.StreamJsonSlice(request, writer, result)
fillUncles := !params.Has("noUncles")
fillMined := !params.Has("noMainStatus")
fillMiner := !params.Has("noMiner")
sideBlocks := indexDb.GetSideBlocksByMinerIdInWindow(miner.Id(), from, window)
defer sideBlocks.Close()
_ = httputils.StreamJsonIterator(request, writer, func() (int, *index.SideBlock) {
i, v := sideBlocks.Next()
if v != nil {
v = fillSideBlockResult(fillUncles, fillMined, fillMiner, v)
}
return i, v
})
})
serveMux.HandleFunc("/api/sweeps", func(writer http.ResponseWriter, request *http.Request) {
@ -837,8 +854,13 @@ func main() {
writer.Header().Set("Content-Type", "application/json; charset=utf-8")
writer.WriteHeader(http.StatusOK)
_ = httputils.StreamJsonSlice(request, writer, indexDb.GetMainLikelySweepTransactions(limit))
result, err := indexDb.GetMainLikelySweepTransactions(limit)
if err != nil {
panic(err)
}
defer result.Close()
_ = httputils.StreamJsonIterator(request, writer, result.Next)
})
serveMux.HandleFunc("/api/sweeps/{miner:[0-9]+|4[123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]+$}", func(writer http.ResponseWriter, request *http.Request) {
@ -878,8 +900,13 @@ func main() {
writer.Header().Set("Content-Type", "application/json; charset=utf-8")
writer.WriteHeader(http.StatusOK)
_ = httputils.StreamJsonSlice(request, writer, indexDb.GetMainLikelySweepTransactionsByAddress(miner.Address(), limit))
result, err := indexDb.GetMainLikelySweepTransactionsByAddress(miner.Address(), limit)
if err != nil {
panic(err)
}
defer result.Close()
_ = httputils.StreamJsonIterator(request, writer, result.Next)
})
serveMux.HandleFunc("/api/payouts/{miner:[0-9]+|4[123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]+$}", func(writer http.ResponseWriter, request *http.Request) {
@ -934,14 +961,23 @@ func main() {
writer.Header().Set("Content-Type", "application/json; charset=utf-8")
writer.WriteHeader(http.StatusOK)
var result index.QueryIterator[index.Payout]
var err error
if timestamp > 0 {
_ = httputils.StreamJsonSlice(request, writer, indexDb.GetPayoutsByMinerIdFromTimestamp(miner.Id(), timestamp))
result, err = indexDb.GetPayoutsByMinerIdFromTimestamp(miner.Id(), timestamp)
} else if height > 0 {
_ = httputils.StreamJsonSlice(request, writer, indexDb.GetPayoutsByMinerIdFromHeight(miner.Id(), height))
result, err = indexDb.GetPayoutsByMinerIdFromHeight(miner.Id(), height)
} else {
_ = httputils.StreamJsonSlice(request, writer, indexDb.GetPayoutsByMinerId(miner.Id(), limit))
result, err = indexDb.GetPayoutsByMinerId(miner.Id(), limit)
}
if err != nil {
panic(err)
}
defer result.Close()
_ = httputils.StreamJsonIterator(request, writer, result.Next)
})
serveMux.HandleFunc("/api/redirect/block/{main_height:[0-9]+|.?[0-9A-Za-z]+$}", func(writer http.ResponseWriter, request *http.Request) {
@ -957,8 +993,8 @@ func main() {
http.Redirect(writer, request, fmt.Sprintf("%s/explorer/tx/%s", cmdutils.GetSiteUrl(cmdutils.SiteKeyP2PoolIo, request.Host == torHost), txId), http.StatusFound)
})
serveMux.HandleFunc("/api/redirect/block/{coinbase:[0-9]+|.?[0-9A-Za-z]+$}", func(writer http.ResponseWriter, request *http.Request) {
foundTargets := indexDb.GetFoundBlocks("WHERE side_height = $1", 1, utils.DecodeBinaryNumber(mux.Vars(request)["coinbase"]))
if len(foundTargets) == 0 {
foundTarget := index.QueryFirstResult(indexDb.GetFoundBlocks("WHERE side_height = $1", 1, utils.DecodeBinaryNumber(mux.Vars(request)["coinbase"])))
if foundTarget == nil {
writer.Header().Set("Content-Type", "application/json; charset=utf-8")
writer.WriteHeader(http.StatusNotFound)
buf, _ := utils.MarshalJSON(struct {
@ -970,7 +1006,7 @@ func main() {
return
}
http.Redirect(writer, request, fmt.Sprintf("%s/explorer/tx/%s", cmdutils.GetSiteUrl(cmdutils.SiteKeyP2PoolIo, request.Host == torHost), foundTargets[0].MainBlock.Id.String()), http.StatusFound)
http.Redirect(writer, request, fmt.Sprintf("%s/explorer/tx/%s", cmdutils.GetSiteUrl(cmdutils.SiteKeyP2PoolIo, request.Host == torHost), foundTarget.MainBlock.Id.String()), http.StatusFound)
})
serveMux.HandleFunc("/api/redirect/share/{height:[0-9]+|.?[0-9A-Za-z]+$}", func(writer http.ResponseWriter, request *http.Request) {
c := utils.DecodeBinaryNumber(mux.Vars(request)["height"])
@ -979,22 +1015,24 @@ func main() {
blockIdStart := c & 0xFFFF
blockStart := []byte{byte((blockIdStart >> 8) & 0xFF), byte(blockIdStart & 0xFF)}
shares := index.ChanToSlice(indexDb.GetSideBlocksByQuery("WHERE side_height = $1;", blockHeight))
sideBlocks, err := indexDb.GetSideBlocksByQuery("WHERE side_height = $1;", blockHeight)
if err != nil {
panic(err)
}
var b *index.SideBlock
for _, s := range shares {
index.QueryIterate(sideBlocks, func(_ int, s *index.SideBlock) (stop bool) {
if bytes.Compare(s.MainId[:2], blockStart) == 0 {
b = s
break
return true
}
}
if b == nil {
for _, s := range shares {
if bytes.Compare(s.TemplateId[:2], blockStart) == 0 {
b = s
break
}
if bytes.Compare(s.TemplateId[:2], blockStart) == 0 {
b = s
return true
}
}
return false
})
if b == nil {
writer.Header().Set("Content-Type", "application/json; charset=utf-8")
@ -1018,13 +1056,13 @@ func main() {
height := i >> n
outputIndex := i & ((1 << n) - 1)
b := indexDb.GetFoundBlocks("WHERE side_height = $1", 1, height)
b := index.QueryFirstResult(indexDb.GetFoundBlocks("WHERE side_height = $1", 1, height))
var tx *index.MainCoinbaseOutput
if len(b) != 0 {
tx = indexDb.GetMainCoinbaseOutputByIndex(b[0].MainBlock.CoinbaseId, outputIndex)
if b != nil {
tx = indexDb.GetMainCoinbaseOutputByIndex(b.MainBlock.CoinbaseId, outputIndex)
}
if len(b) == 0 || tx == nil {
if b == nil || tx == nil {
writer.Header().Set("Content-Type", "application/json; charset=utf-8")
writer.WriteHeader(http.StatusNotFound)
buf, _ := utils.MarshalJSON(struct {
@ -1036,13 +1074,13 @@ func main() {
return
}
http.Redirect(writer, request, fmt.Sprintf("/proof/%s/%d", b[0].MainBlock.Id.String(), tx.Index), http.StatusFound)
http.Redirect(writer, request, fmt.Sprintf("/proof/%s/%d", b.MainBlock.Id.String(), tx.Index), http.StatusFound)
})
serveMux.HandleFunc("/api/redirect/prove/{height:[0-9]+|.[0-9A-Za-z]+}/{miner:[0-9]+|.?[0-9A-Za-z]+}", func(writer http.ResponseWriter, request *http.Request) {
b := indexDb.GetFoundBlocks("WHERE side_height = $1", 1, utils.DecodeBinaryNumber(mux.Vars(request)["height"]))
b := index.QueryFirstResult(indexDb.GetFoundBlocks("WHERE side_height = $1", 1, utils.DecodeBinaryNumber(mux.Vars(request)["height"])))
miner := indexDb.GetMiner(utils.DecodeBinaryNumber(mux.Vars(request)["miner"]))
if len(b) == 0 || miner == nil {
if b == nil || miner == nil {
writer.Header().Set("Content-Type", "application/json; charset=utf-8")
writer.WriteHeader(http.StatusNotFound)
buf, _ := utils.MarshalJSON(struct {
@ -1054,7 +1092,7 @@ func main() {
return
}
tx := indexDb.GetMainCoinbaseOutputByMinerId(b[0].MainBlock.Id, miner.Id())
tx := indexDb.GetMainCoinbaseOutputByMinerId(b.MainBlock.Id, miner.Id())
if tx == nil {
writer.Header().Set("Content-Type", "application/json; charset=utf-8")
@ -1068,7 +1106,7 @@ func main() {
return
}
http.Redirect(writer, request, fmt.Sprintf("/proof/%s/%d", b[0].MainBlock.Id.String(), tx.Index), http.StatusFound)
http.Redirect(writer, request, fmt.Sprintf("/proof/%s/%d", b.MainBlock.Id.String(), tx.Index), http.StatusFound)
})
serveMux.HandleFunc("/api/redirect/miner/{miner:[0-9]+|.?[0-9A-Za-z]+}", func(writer http.ResponseWriter, request *http.Request) {
@ -1090,11 +1128,12 @@ func main() {
//other redirects
serveMux.HandleFunc("/api/redirect/last_found{kind:|/raw|/info}", func(writer http.ResponseWriter, request *http.Request) {
var lastFoundHash types.Hash
for _, b := range indexDb.GetFoundBlocks("", 1) {
lastFoundHash = b.MainBlock.SideTemplateId
b := index.QueryFirstResult(indexDb.GetFoundBlocks("", 1))
if b == nil {
writer.WriteHeader(http.StatusNotFound)
return
}
http.Redirect(writer, request, fmt.Sprintf("/api/block_by_id/%s%s?%s", lastFoundHash.String(), mux.Vars(request)["kind"], request.URL.RawQuery), http.StatusFound)
http.Redirect(writer, request, fmt.Sprintf("/api/block_by_id/%s%s?%s", b.MainBlock.SideTemplateId.String(), mux.Vars(request)["kind"], request.URL.RawQuery), http.StatusFound)
})
serveMux.HandleFunc("/api/redirect/tip{kind:|/raw|/info}", func(writer http.ResponseWriter, request *http.Request) {
http.Redirect(writer, request, fmt.Sprintf("/api/block_by_id/%s%s?%s", indexDb.GetSideBlockTip().TemplateId.String(), mux.Vars(request)["kind"], request.URL.RawQuery), http.StatusFound)
@ -1150,17 +1189,31 @@ func main() {
limit = 50
}
var result []*index.FoundBlock
if minerId != 0 {
result = fillFoundBlockResult(params, indexDb.GetFoundBlocks("WHERE miner = $1", limit, minerId))
} else {
result = fillFoundBlockResult(params, indexDb.GetFoundBlocks("", limit))
}
writer.Header().Set("Content-Type", "application/json; charset=utf-8")
writer.WriteHeader(http.StatusOK)
_ = httputils.EncodeJson(request, writer, result)
var result index.QueryIterator[index.FoundBlock]
var err error
if minerId != 0 {
result, err = indexDb.GetFoundBlocks("WHERE miner = $1", limit, minerId)
} else {
result, err = indexDb.GetFoundBlocks("", limit)
}
fillMiner := !params.Has("noMiner")
if err != nil {
panic(err)
}
defer result.Close()
_ = httputils.StreamJsonIterator(request, writer, func() (int, *index.FoundBlock) {
i, v := result.Next()
if v != nil {
v = fillFoundBlockResult(fillMiner, v)
}
return i, v
})
})
serveMux.HandleFunc("/api/side_blocks", func(writer http.ResponseWriter, request *http.Request) {
@ -1223,8 +1276,22 @@ func main() {
writer.Header().Set("Content-Type", "application/json; charset=utf-8")
writer.WriteHeader(http.StatusOK)
result := fillSideBlockChannelResult(params, indexDb.GetShares(limit, minerId, onlyBlocks, inclusion))
_ = httputils.StreamJsonSlice(request, writer, result)
fillUncles := !params.Has("noUncles")
fillMined := !params.Has("noMainStatus")
fillMiner := !params.Has("noMiner")
result, err := indexDb.GetShares(limit, minerId, onlyBlocks, inclusion)
if err != nil {
panic(err)
}
defer result.Close()
_ = httputils.StreamJsonIterator(request, writer, func() (int, *index.SideBlock) {
i, v := result.Next()
if v != nil {
v = fillSideBlockResult(fillUncles, fillMined, fillMiner, v)
}
return i, v
})
})
serveMux.HandleFunc("/api/shares", func(writer http.ResponseWriter, request *http.Request) {
@ -1280,8 +1347,23 @@ func main() {
writer.Header().Set("Content-Type", "application/json; charset=utf-8")
writer.WriteHeader(http.StatusOK)
result := fillSideBlockChannelResult(params, indexDb.GetShares(limit, minerId, onlyBlocks, index.InclusionInVerifiedChain))
_ = httputils.StreamJsonSlice(request, writer, result)
fillUncles := !params.Has("noUncles")
fillMined := !params.Has("noMainStatus")
fillMiner := !params.Has("noMiner")
result, err := indexDb.GetShares(limit, minerId, onlyBlocks, index.InclusionInVerifiedChain)
if err != nil {
panic(err)
}
defer result.Close()
_ = httputils.StreamJsonIterator(request, writer, func() (int, *index.SideBlock) {
i, v := result.Next()
if v != nil {
v = fillSideBlockResult(fillUncles, fillMined, fillMiner, v)
}
return i, v
})
})
serveMux.HandleFunc("/api/main_block_by_{by:id|height}/{block:[0-9a-f]+|[0-9]+}", func(writer http.ResponseWriter, request *http.Request) {
@ -1353,7 +1435,7 @@ func main() {
if id, err := types.HashFromString(mux.Vars(request)["block"]); err == nil {
if b := indexDb.GetTipSideBlockByTemplateId(id); b != nil {
block = b
} else if bs := index.ChanToSlice(indexDb.GetSideBlocksByTemplateId(id)); len(bs) != 0 {
} else if bs := index.QueryIterateToSlice(indexDb.GetSideBlocksByTemplateId(id)); len(bs) != 0 {
block = bs[0]
} else if b = indexDb.GetSideBlockByMainId(id); b != nil {
block = b
@ -1448,7 +1530,13 @@ func main() {
case "/payouts":
writer.Header().Set("Content-Type", "application/json; charset=utf-8")
writer.WriteHeader(http.StatusOK)
_ = httputils.StreamJsonSlice(request, writer, indexDb.GetPayoutsBySideBlock(block))
result, err := indexDb.GetPayoutsBySideBlock(block)
if err != nil {
panic(err)
}
defer result.Close()
_ = httputils.StreamJsonIterator(request, writer, result.Next)
case "/coinbase":
foundBlock := indexDb.GetMainBlockById(block.MainId)
if foundBlock == nil {
@ -1506,7 +1594,13 @@ func main() {
} else {
writer.Header().Set("Content-Type", "application/json; charset=utf-8")
writer.WriteHeader(http.StatusOK)
_ = httputils.EncodeJson(request, writer, fillMainCoinbaseOutputs(params, indexDb.GetMainCoinbaseOutputs(foundBlock.CoinbaseId)))
mainOutputs, err := indexDb.GetMainCoinbaseOutputs(foundBlock.CoinbaseId)
if err != nil {
panic(err)
}
defer mainOutputs.Close()
_ = httputils.EncodeJson(request, writer, fillMainCoinbaseOutputs(params, index.IterateToSliceWithoutPointer[index.MainCoinbaseOutput](mainOutputs)))
}
default:
writer.Header().Set("Content-Type", "application/json; charset=utf-8")
@ -1688,7 +1782,13 @@ func main() {
}
blocks := make([]poolBlock, 0, 200)
for _, b := range indexDb.GetFoundBlocks("", 200) {
result, err := indexDb.GetFoundBlocks("", 200)
if err != nil {
panic(err)
}
index.QueryIterate(result, func(_ int, b *index.FoundBlock) (stop bool) {
blocks = append(blocks, poolBlock{
Height: b.MainBlock.Height,
Hash: b.MainBlock.Id,
@ -1696,7 +1796,8 @@ func main() {
TotalHashes: b.CumulativeDifficulty.Lo,
Timestamp: b.MainBlock.Timestamp,
})
}
return false
})
writer.Header().Set("Content-Type", "application/json; charset=utf-8")
writer.WriteHeader(http.StatusOK)
@ -1724,7 +1825,8 @@ func main() {
poolInfo := lastPoolInfo.Load()
var lastBlockFound, lastBlockFoundTime uint64
for _, b := range indexDb.GetFoundBlocks("", 1) {
b := index.QueryFirstResult(indexDb.GetFoundBlocks("", 1))
if b != nil {
lastBlockFound = b.MainBlock.Height
lastBlockFoundTime = b.MainBlock.Timestamp
}
@ -1770,7 +1872,8 @@ func main() {
var lastBlockFound, lastBlockFoundTime uint64
var lastBlockFoundHash types.Hash
var lastBlockCumulativeDifficulty types.Difficulty
for _, b := range indexDb.GetFoundBlocks("", 1) {
b := index.QueryFirstResult(indexDb.GetFoundBlocks("", 1))
if b != nil {
lastBlockFound = b.MainBlock.Height
lastBlockFoundTime = b.MainBlock.Timestamp
lastBlockFoundHash = b.MainBlock.Id

View file

@ -35,7 +35,7 @@ func Outputs(p2api *api.P2PoolApi, indexDb *index.Index, tip *index.SideBlock, d
poolBlock := p2api.LightByMainId(tip.MainId)
if poolBlock != nil {
window := index.ChanToSlice(indexDb.GetSideBlocksInPPLNSWindow(tip))
window := index.QueryIterateToSlice(indexDb.GetSideBlocksInPPLNSWindow(tip))
if len(window) == 0 {
return nil, 0
}
@ -60,26 +60,32 @@ func Outputs(p2api *api.P2PoolApi, indexDb *index.Index, tip *index.SideBlock, d
}
return getByTemplateIdFull(h)
}
getUnclesOf := func(h types.Hash) chan *index.SideBlock {
result := make(chan *index.SideBlock)
getUnclesOf := func(h types.Hash) index.QueryIterator[index.SideBlock] {
parentEffectiveHeight := window[hintIndex].EffectiveHeight
if window[hintIndex].TemplateId != h {
parentEffectiveHeight = 0
}
go func() {
defer close(result)
for _, b := range window[hintIndex:] {
if b.UncleOf == h {
result <- b
startIndex := 0
return &index.FakeQueryResult[index.SideBlock]{
NextFunction: func() (int, *index.SideBlock) {
for _, b := range window[startIndex+hintIndex:] {
if b.UncleOf == h {
startIndex++
return startIndex, b
}
startIndex++
if parentEffectiveHeight != 0 && b.EffectiveHeight < parentEffectiveHeight {
//early exit
break
}
}
if parentEffectiveHeight != 0 && b.EffectiveHeight < parentEffectiveHeight {
//early exit
break
}
}
}()
return result
return 0, nil
},
}
}
return index.CalculateOutputs(indexDb,
@ -104,7 +110,7 @@ func PayoutHint(p2api *api.P2PoolApi, indexDb *index.Index, tip *index.SideBlock
if tip == nil {
return nil, 0
}
window := index.ChanToSlice(indexDb.GetSideBlocksInPPLNSWindow(tip))
window := index.QueryIterateToSlice(indexDb.GetSideBlocksInPPLNSWindow(tip))
if len(window) == 0 {
return nil, 0
}
@ -130,26 +136,31 @@ func PayoutHint(p2api *api.P2PoolApi, indexDb *index.Index, tip *index.SideBlock
}
return getByTemplateIdFull(h)
}
getUnclesOf := func(h types.Hash) chan *index.SideBlock {
result := make(chan *index.SideBlock)
getUnclesOf := func(h types.Hash) index.QueryIterator[index.SideBlock] {
parentEffectiveHeight := window[hintIndex].EffectiveHeight
if window[hintIndex].TemplateId != h {
parentEffectiveHeight = 0
}
go func() {
defer close(result)
for _, b := range window[hintIndex:] {
if b.UncleOf == h {
result <- b
startIndex := 0
return &index.FakeQueryResult[index.SideBlock]{
NextFunction: func() (int, *index.SideBlock) {
for _, b := range window[startIndex+hintIndex:] {
if b.UncleOf == h {
startIndex++
return startIndex, b
}
startIndex++
if parentEffectiveHeight != 0 && b.EffectiveHeight < parentEffectiveHeight {
//early exit
break
}
}
if parentEffectiveHeight != 0 && b.EffectiveHeight < parentEffectiveHeight {
//early exit
break
}
}
}()
return result
return 0, nil
},
}
}
blockDepth, err := index.BlocksInPPLNSWindow(tip, indexDb.Consensus(), p2api.MainDifficultyByHeight, getByTemplateId, getUnclesOf, func(b *index.SideBlock, weight types.Difficulty) {

View file

@ -244,7 +244,7 @@ func main() {
if r.startHeight-r.tipHeight > consensus.ChainWindowSize*2 &&
indexDb.GetTipSideBlockByTemplateId(bestTip.SideTemplateId(consensus)) != nil &&
indexDb.GetTipSideBlockByHeight(r.startHeight+consensus.ChainWindowSize+1) != nil &&
len(index.ChanToSlice(indexDb.GetSideBlocksByHeight(r.startHeight))) != 0 {
index.QueryHasResults(indexDb.GetSideBlocksByHeight(r.startHeight)) {
continue
}
@ -337,9 +337,11 @@ func main() {
}
}
for mb := range indexDb.GetMainBlocksByQuery("WHERE side_template_id IS NOT NULL ORDER BY height DESC;") {
mainBlocks, _ := indexDb.GetMainBlocksByQuery("WHERE side_template_id IS NOT NULL ORDER BY height DESC;")
index.QueryIterate(mainBlocks, func(_ int, mb *index.MainBlock) (stop bool) {
if err := utils2.FindAndInsertMainHeaderOutputs(mb, indexDb, client.GetDefaultClient(), getDifficultyByHeight, getByTemplateIdDirect, archiveCache.LoadByMainId, archiveCache.LoadByMainChainHeight, processBlock); err != nil {
log.Print(err)
}
}
return false
})
}

View file

@ -193,14 +193,19 @@ func setupEventHandler(p2api *api.P2PoolApi, indexDb *index.Index) {
break
}
sideBlockBuffer.Insert(cur)
for u := range indexDb.GetSideBlocksByUncleOfId(cur.TemplateId) {
index.QueryIterate(indexDb.GetSideBlocksByUncleOfId(cur.TemplateId), func(_ int, u *index.SideBlock) (stop bool) {
sideBlockBuffer.Insert(u)
}
return false
})
}
for _, b := range indexDb.GetFoundBlocks("", 5) {
foundBlockBuffer.Insert(b)
}
func() {
foundBlocks, _ := indexDb.GetFoundBlocks("", 5)
index.QueryIterate(foundBlocks, func(_ int, b *index.FoundBlock) (stop bool) {
foundBlockBuffer.Insert(b)
return false
})
}()
fillMainCoinbaseOutputs := func(outputs index.MainCoinbaseOutputs) (result index.MainCoinbaseOutputs) {
result = make(index.MainCoinbaseOutputs, 0, len(outputs))
@ -250,14 +255,15 @@ func setupEventHandler(p2api *api.P2PoolApi, indexDb *index.Index) {
}
}
for u := range indexDb.GetSideBlocksByUncleOfId(sideBlock.TemplateId) {
index.QueryIterate(indexDb.GetSideBlocksByUncleOfId(sideBlock.TemplateId), func(_ int, u *index.SideBlock) (stop bool) {
sideBlock.Uncles = append(sideBlock.Uncles, index.SideBlockUncleEntry{
TemplateId: u.TemplateId,
Miner: u.Miner,
SideHeight: u.SideHeight,
Difficulty: u.Difficulty,
})
}
return false
})
return sideBlock
}
@ -278,13 +284,15 @@ func setupEventHandler(p2api *api.P2PoolApi, indexDb *index.Index) {
break
}
var pushedNew bool
for u := range indexDb.GetSideBlocksByUncleOfId(cur.TemplateId) {
index.QueryIterate(indexDb.GetSideBlocksByUncleOfId(cur.TemplateId), func(_ int, u *index.SideBlock) (stop bool) {
if sideBlockBuffer.Insert(u) {
//first time seen
pushedNew = true
blocksToReport = append(blocksToReport, fillSideBlockResult(mainTip, u))
}
}
return false
})
if sideBlockBuffer.Insert(cur) {
//first time seen
pushedNew = true
@ -359,12 +367,17 @@ func setupEventHandler(p2api *api.P2PoolApi, indexDb *index.Index) {
}
}
}
for _, b := range indexDb.GetFoundBlocks("", 5) {
if foundBlockBuffer.Insert(b) {
//first time seen
blocksToReport = append(blocksToReport, fillFoundBlockResult(b))
}
}
func() {
foundBlocks, _ := indexDb.GetFoundBlocks("", 5)
index.QueryIterate(foundBlocks, func(_ int, b *index.FoundBlock) (stop bool) {
if foundBlockBuffer.Insert(b) {
//first time seen
blocksToReport = append(blocksToReport, fillFoundBlockResult(b))
}
return false
})
}()
}()
//sort for proper order
@ -431,7 +444,15 @@ func setupEventHandler(p2api *api.P2PoolApi, indexDb *index.Index) {
}
}
for _, b := range blocksToReport {
coinbaseOutputs := fillMainCoinbaseOutputs(indexDb.GetMainCoinbaseOutputs(b.MainBlock.CoinbaseId))
coinbaseOutputs := func() index.MainCoinbaseOutputs {
mainOutputs, err := indexDb.GetMainCoinbaseOutputs(b.MainBlock.CoinbaseId)
if err != nil {
panic(err)
}
defer mainOutputs.Close()
return fillMainCoinbaseOutputs(index.IterateToSliceWithoutPointer[index.MainCoinbaseOutput](mainOutputs))
}()
if len(coinbaseOutputs) == 0 {
//report next time
foundBlockBuffer.Remove(b)

View file

@ -16,7 +16,7 @@ func EncodeJson(r *http.Request, writer io.Writer, d any) error {
}
// StreamJsonSlice Streams a channel of values into a JSON list via a writer.
func StreamJsonSlice[T any](r *http.Request, writer io.Writer, stream chan T) error {
func StreamJsonSlice[T any](r *http.Request, writer io.Writer, stream <-chan T) error {
encoder := utils.NewJSONEncoder(writer)
if strings.Index(strings.ToLower(r.Header.Get("user-agent")), "mozilla") != -1 {
encoder.SetIndent("", " ")
@ -46,3 +46,35 @@ func StreamJsonSlice[T any](r *http.Request, writer io.Writer, stream chan T) er
}
return nil
}
// StreamJsonIterator Streams an iterator of values into a JSON list via a writer.
func StreamJsonIterator[T any](r *http.Request, writer io.Writer, next func() (int, *T)) error {
encoder := utils.NewJSONEncoder(writer)
if strings.Index(strings.ToLower(r.Header.Get("user-agent")), "mozilla") != -1 {
encoder.SetIndent("", " ")
}
// Write start of JSON list
_, _ = writer.Write([]byte{'[', 0xa})
var count uint64
defer func() {
// Write end of JSON list
_, _ = writer.Write([]byte{0xa, ']'})
}()
for {
_, v := next()
if v == nil {
break
}
if count > 0 {
// Write separator between list fields
_, _ = writer.Write([]byte{',', 0xa})
}
if err := encoder.EncodeWithOption(v, utils.JsonEncodeOptions...); err != nil {
return err
}
count++
}
return nil
}

View file

@ -18,7 +18,6 @@ import (
"log"
"reflect"
"regexp"
"runtime/pprof"
"slices"
"strings"
"sync"
@ -364,28 +363,18 @@ func (i *Index) SetMinerAlias(minerId uint64, alias string) error {
return nil
}
type RowScanInterface interface {
Scan(dest ...any) error
}
type Scannable interface {
ScanFromRow(i *Index, row RowScanInterface) error
}
func (i *Index) GetView(k string) string {
return i.views[k]
}
func (i *Index) Query(query string, callback func(row RowScanInterface) error, params ...any) error {
var parentError error
pprof.Do(context.Background(), pprof.Labels("query", query), func(ctx context.Context) {
if stmt, err := i.handle.Prepare(query); err != nil {
parentError = err
} else {
defer stmt.Close()
parentError = i.QueryStatement(stmt, callback, params...)
}
})
if stmt, err := i.handle.Prepare(query); err != nil {
parentError = err
} else {
defer stmt.Close()
parentError = i.QueryStatement(stmt, callback, params...)
}
return parentError
}
@ -409,71 +398,39 @@ func (i *Index) PrepareSideBlocksByQueryStatement(where string) (stmt *sql.Stmt,
return i.handle.Prepare(fmt.Sprintf("SELECT "+SideBlockSelectFields+" FROM side_blocks %s;", where))
}
func (i *Index) GetSideBlocksByQuery(where string, params ...any) chan *SideBlock {
func (i *Index) GetSideBlocksByQuery(where string, params ...any) (QueryIterator[SideBlock], error) {
if stmt, err := i.PrepareSideBlocksByQueryStatement(where); err != nil {
returnChannel := make(chan *SideBlock, 1)
close(returnChannel)
return returnChannel
return nil, err
} else {
return i.getSideBlocksByQueryStatement(where, stmt, params...)
return i.getSideBlocksByQueryStatement(stmt, params...)
}
}
func (i *Index) getSideBlocksByQueryStatement(sourceQuery string, stmt *sql.Stmt, params ...any) chan *SideBlock {
returnChannel := make(chan *SideBlock, 1)
go func() {
func (i *Index) getSideBlocksByQueryStatement(stmt *sql.Stmt, params ...any) (QueryIterator[SideBlock], error) {
if r, err := queryStatement[SideBlock](i, stmt, params...); err != nil {
defer stmt.Close()
defer close(returnChannel)
pprof.Do(context.Background(), pprof.Labels("sourceQuery", sourceQuery), func(ctx context.Context) {
err := i.QueryStatement(stmt, func(row RowScanInterface) (err error) {
b := &SideBlock{}
if err = b.ScanFromRow(i, row); err != nil {
return err
}
returnChannel <- b
return nil
}, params...)
if err != nil {
log.Print(err)
}
})
}()
return returnChannel
return nil, err
} else {
r.closer = func() {
stmt.Close()
}
return r, nil
}
}
func (i *Index) GetSideBlocksByQueryStatement(source string, stmt *sql.Stmt, params ...any) chan *SideBlock {
returnChannel := make(chan *SideBlock, 1)
go func() {
defer close(returnChannel)
pprof.Do(context.Background(), pprof.Labels("source", source), func(ctx context.Context) {
err := i.QueryStatement(stmt, func(row RowScanInterface) (err error) {
b := &SideBlock{}
if err = b.ScanFromRow(i, row); err != nil {
return err
}
returnChannel <- b
return nil
}, params...)
if err != nil {
log.Print(err)
}
})
}()
return returnChannel
func (i *Index) GetSideBlocksByQueryStatement(stmt *sql.Stmt, params ...any) (QueryIterator[SideBlock], error) {
if r, err := queryStatement[SideBlock](i, stmt, params...); err != nil {
return nil, err
} else {
return r, nil
}
}
func (i *Index) PrepareMainBlocksByQueryStatement(where string) (stmt *sql.Stmt, err error) {
return i.handle.Prepare(fmt.Sprintf("SELECT "+MainBlockSelectFields+" FROM main_blocks %s;", where))
}
func (i *Index) GetShares(limit, minerId uint64, onlyBlocks bool, inclusion BlockInclusion) chan *SideBlock {
func (i *Index) GetShares(limit, minerId uint64, onlyBlocks bool, inclusion BlockInclusion) (QueryIterator[SideBlock], error) {
if limit == 0 {
if minerId != 0 {
if onlyBlocks {
@ -505,164 +462,222 @@ func (i *Index) GetShares(limit, minerId uint64, onlyBlocks bool, inclusion Bloc
}
}
func (i *Index) GetFoundBlocks(where string, limit uint64, params ...any) []*FoundBlock {
result := make([]*FoundBlock, 0, limit)
if err := i.Query(fmt.Sprintf("SELECT * FROM "+i.views["found_main_blocks"]+" %s ORDER BY main_height DESC LIMIT %d;", where, limit), func(row RowScanInterface) error {
var d FoundBlock
if err := d.ScanFromRow(i, row); err != nil {
return err
func (i *Index) GetFoundBlocks(where string, limit uint64, params ...any) (QueryIterator[FoundBlock], error) {
if stmt, err := i.handle.Prepare(fmt.Sprintf("SELECT * FROM "+i.views["found_main_blocks"]+" %s ORDER BY main_height DESC LIMIT %d;", where, limit)); err != nil {
return nil, err
} else {
if r, err := queryStatement[FoundBlock](i, stmt, params...); err != nil {
defer stmt.Close()
return nil, err
} else {
r.closer = func() {
stmt.Close()
}
return r, nil
}
result = append(result, &d)
return nil
}, params...); err != nil {
return nil
}
return result
}
func (i *Index) GetMainBlocksByQuery(where string, params ...any) chan *MainBlock {
func (i *Index) GetMainBlocksByQuery(where string, params ...any) (QueryIterator[MainBlock], error) {
if stmt, err := i.PrepareMainBlocksByQueryStatement(where); err != nil {
returnChannel := make(chan *MainBlock, 1)
close(returnChannel)
return returnChannel
return nil, err
} else {
return i.getMainBlocksByQueryStatement(stmt, params...)
}
}
func (i *Index) getMainBlocksByQueryStatement(stmt *sql.Stmt, params ...any) chan *MainBlock {
returnChannel := make(chan *MainBlock, 1)
go func() {
func (i *Index) getMainBlocksByQueryStatement(stmt *sql.Stmt, params ...any) (QueryIterator[MainBlock], error) {
if r, err := queryStatement[MainBlock](i, stmt, params...); err != nil {
defer stmt.Close()
defer close(returnChannel)
err := i.QueryStatement(stmt, func(row RowScanInterface) (err error) {
b := &MainBlock{}
if err = b.ScanFromRow(i, row); err != nil {
return err
}
returnChannel <- b
return nil
}, params...)
if err != nil {
log.Print(err)
return nil, err
} else {
r.closer = func() {
stmt.Close()
}
}()
return returnChannel
return r, nil
}
}
func (i *Index) GetMainBlocksByQueryStatement(stmt *sql.Stmt, params ...any) chan *MainBlock {
returnChannel := make(chan *MainBlock, 1)
go func() {
defer close(returnChannel)
err := i.QueryStatement(stmt, func(row RowScanInterface) (err error) {
b := &MainBlock{}
if err = b.ScanFromRow(i, row); err != nil {
return err
}
func (i *Index) GetMainBlocksByQueryStatement(stmt *sql.Stmt, params ...any) (QueryIterator[MainBlock], error) {
if r, err := queryStatement[MainBlock](i, stmt, params...); err != nil {
return nil, err
} else {
return r, nil
}
}
returnChannel <- b
return nil
}, params...)
if err != nil {
log.Print(err)
func (i *Index) GetMainBlockById(id types.Hash) (b *MainBlock) {
if r, err := i.GetMainBlocksByQueryStatement(i.statements.GetMainBlockById, id[:]); err != nil {
log.Print(err)
} else {
defer r.Close()
if _, b = r.Next(); b == nil && r.Err() != nil {
log.Print(r.Err())
}
}()
return returnChannel
}
return b
}
func (i *Index) GetMainBlockById(id types.Hash) *MainBlock {
r := i.GetMainBlocksByQueryStatement(i.statements.GetMainBlockById, id[:])
defer ChanConsume(r)
return <-r
func (i *Index) GetMainBlockTip() (b *MainBlock) {
if r, err := i.GetMainBlocksByQueryStatement(i.statements.TipMainBlock); err != nil {
log.Print(err)
} else {
defer r.Close()
if _, b = r.Next(); b == nil && r.Err() != nil {
log.Print(r.Err())
}
}
return b
}
func (i *Index) GetMainBlockTip() *MainBlock {
r := i.GetMainBlocksByQueryStatement(i.statements.TipMainBlock)
defer ChanConsume(r)
return <-r
func (i *Index) GetMainBlockByCoinbaseId(id types.Hash) (b *MainBlock) {
if r, err := i.GetMainBlocksByQuery("WHERE coinbase_id = $1;", id[:]); err != nil {
log.Print(err)
} else {
defer r.Close()
if _, b = r.Next(); b == nil && r.Err() != nil {
log.Print(r.Err())
}
}
return b
}
func (i *Index) GetMainBlockByCoinbaseId(id types.Hash) *MainBlock {
r := i.GetMainBlocksByQuery("WHERE coinbase_id = $1;", id[:])
defer ChanConsume(r)
return <-r
func (i *Index) GetMainBlockByGlobalOutputIndex(globalOutputIndex uint64) (b *MainBlock) {
if r, err := i.GetMainBlocksByQuery("WHERE coinbase_id = (SELECT id FROM main_coinbase_outputs WHERE global_output_index = $1 LIMIT 1);", globalOutputIndex); err != nil {
log.Print(err)
} else {
defer r.Close()
if _, b = r.Next(); b == nil && r.Err() != nil {
log.Print(r.Err())
}
}
return b
}
func (i *Index) GetMainBlockByGlobalOutputIndex(globalOutputIndex uint64) *MainBlock {
r := i.GetMainBlocksByQuery("WHERE coinbase_id = (SELECT id FROM main_coinbase_outputs WHERE global_output_index = $1 LIMIT 1);", globalOutputIndex)
defer ChanConsume(r)
return <-r
func (i *Index) GetMainBlockByHeight(height uint64) (b *MainBlock) {
if r, err := i.GetMainBlocksByQueryStatement(i.statements.GetMainBlockByHeight, height); err != nil {
log.Print(err)
} else {
defer r.Close()
if _, b = r.Next(); b == nil && r.Err() != nil {
log.Print(r.Err())
}
}
return b
}
func (i *Index) GetMainBlockByHeight(height uint64) *MainBlock {
r := i.GetMainBlocksByQueryStatement(i.statements.GetMainBlockByHeight, height)
defer ChanConsume(r)
return <-r
func (i *Index) GetSideBlockByMainId(id types.Hash) (b *SideBlock) {
if r, err := i.GetSideBlocksByQueryStatement(i.statements.GetSideBlockByMainId, id[:]); err != nil {
log.Print(err)
} else {
defer r.Close()
if _, b = r.Next(); b == nil && r.Err() != nil {
log.Print(r.Err())
}
}
return b
}
func (i *Index) GetSideBlockByMainId(id types.Hash) *SideBlock {
r := i.GetSideBlocksByQueryStatement("GetSideBlockByMainId", i.statements.GetSideBlockByMainId, id[:])
defer ChanConsume(r)
return <-r
func (i *Index) GetSideBlocksByTemplateId(id types.Hash) QueryIterator[SideBlock] {
if r, err := i.GetSideBlocksByQuery("WHERE template_id = $1;", id[:]); err != nil {
log.Print(err)
} else {
return r
}
return nil
}
func (i *Index) GetSideBlocksByTemplateId(id types.Hash) chan *SideBlock {
return i.GetSideBlocksByQuery("WHERE template_id = $1;", id[:])
func (i *Index) GetSideBlocksByUncleOfId(id types.Hash) QueryIterator[SideBlock] {
if r, err := i.GetSideBlocksByQueryStatement(i.statements.GetSideBlockByUncleId, id[:]); err != nil {
log.Print(err)
} else {
return r
}
return nil
}
func (i *Index) GetSideBlocksByUncleOfId(id types.Hash) chan *SideBlock {
return i.GetSideBlocksByQueryStatement("GetSideBlocksByUncleOfId", i.statements.GetSideBlockByUncleId, id[:])
func (i *Index) GetTipSideBlockByTemplateId(id types.Hash) (b *SideBlock) {
if r, err := i.GetSideBlocksByQueryStatement(i.statements.TipSideBlocksTemplateId, id[:], InclusionInVerifiedChain); err != nil {
log.Print(err)
} else {
defer r.Close()
if _, b = r.Next(); b == nil && r.Err() != nil {
log.Print(r.Err())
}
}
return b
}
func (i *Index) GetTipSideBlockByTemplateId(id types.Hash) *SideBlock {
r := i.GetSideBlocksByQueryStatement("GetTipSideBlockByTemplateId", i.statements.TipSideBlocksTemplateId, id[:], InclusionInVerifiedChain)
defer ChanConsume(r)
return <-r
func (i *Index) GetSideBlocksByMainHeight(height uint64) QueryIterator[SideBlock] {
if r, err := i.GetSideBlocksByQuery("WHERE main_height = $1;", height); err != nil {
log.Print(err)
} else {
return r
}
return nil
}
func (i *Index) GetSideBlocksByMainHeight(height uint64) chan *SideBlock {
return i.GetSideBlocksByQuery("WHERE main_height = $1;", height)
func (i *Index) GetSideBlocksByHeight(height uint64) QueryIterator[SideBlock] {
if r, err := i.GetSideBlocksByQuery("WHERE side_height = $1;", height); err != nil {
log.Print(err)
} else {
return r
}
return nil
}
func (i *Index) GetSideBlocksByHeight(height uint64) chan *SideBlock {
return i.GetSideBlocksByQuery("WHERE side_height = $1;", height)
func (i *Index) GetTipSideBlockByHeight(height uint64) (b *SideBlock) {
if r, err := i.GetSideBlocksByQuery("WHERE side_height = $1 AND effective_height = $2 AND inclusion = $3;", height, height, InclusionInVerifiedChain); err != nil {
log.Print(err)
} else {
defer r.Close()
if _, b = r.Next(); b == nil && r.Err() != nil {
log.Print(r.Err())
}
}
return b
}
func (i *Index) GetTipSideBlockByHeight(height uint64) *SideBlock {
r := i.GetSideBlocksByQuery("WHERE side_height = $1 AND effective_height = $2 AND inclusion = $3;", height, height, InclusionInVerifiedChain)
defer ChanConsume(r)
return <-r
func (i *Index) GetSideBlockTip() (b *SideBlock) {
if r, err := i.GetSideBlocksByQueryStatement(i.statements.TipSideBlock, InclusionInVerifiedChain); err != nil {
log.Print(err)
} else {
defer r.Close()
if _, b = r.Next(); b == nil && r.Err() != nil {
log.Print(r.Err())
}
}
return b
}
func (i *Index) GetSideBlockTip() *SideBlock {
r := i.GetSideBlocksByQueryStatement("GetSideBlockTip", i.statements.TipSideBlock, InclusionInVerifiedChain)
defer ChanConsume(r)
return <-r
}
func (i *Index) GetSideBlocksInPPLNSWindow(tip *SideBlock) chan *SideBlock {
func (i *Index) GetSideBlocksInPPLNSWindow(tip *SideBlock) QueryIterator[SideBlock] {
return i.GetSideBlocksInWindow(tip.SideHeight, uint64(tip.WindowDepth))
}
func (i *Index) GetSideBlocksInWindow(startHeight, windowSize uint64) chan *SideBlock {
func (i *Index) GetSideBlocksInWindow(startHeight, windowSize uint64) QueryIterator[SideBlock] {
if startHeight < windowSize {
windowSize = startHeight
}
return i.GetSideBlocksByQuery("WHERE effective_height <= $1 AND effective_height > $2 AND inclusion = $3 ORDER BY effective_height DESC, side_height DESC;", startHeight, startHeight-windowSize, InclusionInVerifiedChain)
if r, err := i.GetSideBlocksByQuery("WHERE effective_height <= $1 AND effective_height > $2 AND inclusion = $3 ORDER BY effective_height DESC, side_height DESC;", startHeight, startHeight-windowSize, InclusionInVerifiedChain); err != nil {
log.Print(err)
} else {
return r
}
return nil
}
func (i *Index) GetSideBlocksByMinerIdInWindow(minerId, startHeight, windowSize uint64) chan *SideBlock {
func (i *Index) GetSideBlocksByMinerIdInWindow(minerId, startHeight, windowSize uint64) QueryIterator[SideBlock] {
if startHeight < windowSize {
windowSize = startHeight
}
return i.GetSideBlocksByQuery("WHERE miner = $1 AND effective_height <= $2 AND effective_height > $3 AND inclusion = $4 ORDER BY effective_height DESC, side_height DESC;", minerId, startHeight, startHeight-windowSize, InclusionInVerifiedChain)
if r, err := i.GetSideBlocksByQuery("WHERE miner = $1 AND effective_height <= $2 AND effective_height > $3 AND inclusion = $4 ORDER BY effective_height DESC, side_height DESC;", minerId, startHeight, startHeight-windowSize, InclusionInVerifiedChain); err != nil {
log.Print(err)
} else {
return r
}
return nil
}
func (i *Index) InsertOrUpdateSideBlock(b *SideBlock) error {
@ -763,207 +778,195 @@ func (i *Index) InsertOrUpdateMainBlock(b *MainBlock) error {
}
}
func (i *Index) GetPayoutsByMinerId(minerId uint64, limit uint64) chan *Payout {
out := make(chan *Payout, 1)
func (i *Index) GetPayoutsByMinerId(minerId uint64, limit uint64) (r QueryIterator[Payout], err error) {
var stmt *sql.Stmt
var params []any
go func() {
defer close(out)
resultFunc := func(row RowScanInterface) error {
p := &Payout{}
if err := p.ScanFromRow(i, row); err != nil {
return err
}
out <- p
return nil
if limit == 0 {
if stmt, err = i.handle.Prepare("SELECT * FROM " + i.views["payouts"] + " WHERE miner = $1 ORDER BY main_height DESC;"); err != nil {
return nil, err
}
params = []any{minerId}
} else {
if stmt, err = i.handle.Prepare("SELECT * FROM " + i.views["payouts"] + " WHERE miner = $1 ORDER BY main_height DESC LIMIT $2;"); err != nil {
return nil, err
}
params = []any{minerId, limit}
}
if limit == 0 {
if err := i.Query("SELECT * FROM "+i.views["payouts"]+" WHERE miner = $1 ORDER BY main_height DESC;", resultFunc, minerId); err != nil {
return
}
if r, err := queryStatement[Payout](i, stmt, params...); err != nil {
defer stmt.Close()
return nil, err
} else {
r.closer = func() {
stmt.Close()
}
return r, nil
}
}
func (i *Index) GetPayoutsByMinerIdFromHeight(minerId uint64, height uint64) (QueryIterator[Payout], error) {
if stmt, err := i.handle.Prepare("SELECT * FROM " + i.views["payouts"] + " WHERE miner = $1 AND main_height >= $2 ORDER BY main_height DESC;"); err != nil {
return nil, err
} else {
if r, err := queryStatement[Payout](i, stmt, minerId, height); err != nil {
defer stmt.Close()
return nil, err
} else {
if err := i.Query("SELECT * FROM "+i.views["payouts"]+" WHERE miner = $1 ORDER BY main_height DESC LIMIT $2;", resultFunc, minerId, limit); err != nil {
return
r.closer = func() {
stmt.Close()
}
return r, nil
}
}()
return out
}
func (i *Index) GetPayoutsByMinerIdFromHeight(minerId uint64, height uint64) chan *Payout {
out := make(chan *Payout, 1)
go func() {
defer close(out)
resultFunc := func(row RowScanInterface) error {
p := &Payout{}
if err := p.ScanFromRow(i, row); err != nil {
return err
}
out <- p
return nil
}
if err := i.Query("SELECT * FROM "+i.views["payouts"]+" WHERE miner = $1 AND main_height >= $2 ORDER BY main_height DESC;", resultFunc, minerId, height); err != nil {
return
}
}()
return out
}
func (i *Index) GetPayoutsByMinerIdFromTimestamp(minerId uint64, timestamp uint64) chan *Payout {
out := make(chan *Payout, 1)
go func() {
defer close(out)
resultFunc := func(row RowScanInterface) error {
p := &Payout{}
if err := p.ScanFromRow(i, row); err != nil {
return err
}
out <- p
return nil
}
if err := i.Query("SELECT * FROM "+i.views["payouts"]+" WHERE miner = $1 AND timestamp >= $2 ORDER BY main_height DESC;", resultFunc, minerId, timestamp); err != nil {
return
}
}()
return out
}
func (i *Index) GetPayoutsBySideBlock(b *SideBlock) chan *Payout {
out := make(chan *Payout, 1)
go func() {
defer close(out)
resultFunc := func(row RowScanInterface) error {
p := &Payout{}
if err := p.ScanFromRow(i, row); err != nil {
return err
}
out <- p
return nil
}
if err := i.Query("SELECT * FROM "+i.views["payouts"]+" WHERE miner = $1 AND ((side_height >= $2 AND including_height <= $2) OR main_id = $3) ORDER BY main_height DESC;", resultFunc, b.Miner, b.EffectiveHeight, &b.MainId); err != nil {
return
}
}()
return out
}
func (i *Index) GetMainCoinbaseOutputs(coinbaseId types.Hash) MainCoinbaseOutputs {
var outputs MainCoinbaseOutputs
if err := i.Query("SELECT "+MainCoinbaseOutputSelectFields+" FROM main_coinbase_outputs WHERE id = $1 ORDER BY index ASC;", func(row RowScanInterface) error {
var output MainCoinbaseOutput
if err := output.ScanFromRow(i, row); err != nil {
return err
}
outputs = append(outputs, output)
return nil
}, coinbaseId[:]); err != nil {
return nil
}
return outputs
}
func (i *Index) GetMainCoinbaseOutputByIndex(coinbaseId types.Hash, index uint64) *MainCoinbaseOutput {
var output MainCoinbaseOutput
if err := i.Query("SELECT "+MainCoinbaseOutputSelectFields+" FROM main_coinbase_outputs WHERE id = $1 AND index = $2 ORDER BY index ASC;", func(row RowScanInterface) error {
if err := output.ScanFromRow(i, row); err != nil {
return err
}
return nil
}, coinbaseId[:], index); err != nil {
return nil
}
if output.Id == types.ZeroHash {
return nil
}
return &output
}
func (i *Index) GetMainCoinbaseOutputByGlobalOutputIndex(globalOutputIndex uint64) *MainCoinbaseOutput {
var output MainCoinbaseOutput
if err := i.Query("SELECT "+MainCoinbaseOutputSelectFields+" FROM main_coinbase_outputs WHERE global_output_index = $1 ORDER BY index ASC;", func(row RowScanInterface) error {
if err := output.ScanFromRow(i, row); err != nil {
return err
}
return nil
}, globalOutputIndex); err != nil {
return nil
}
if output.Id == types.ZeroHash {
return nil
}
return &output
}
func (i *Index) GetMainLikelySweepTransactions(limit uint64) chan *MainLikelySweepTransaction {
out := make(chan *MainLikelySweepTransaction, 1)
go func() {
defer close(out)
scanFunc := func(row RowScanInterface) error {
var tx MainLikelySweepTransaction
if err := tx.ScanFromRow(i, row); err != nil {
return err
}
out <- &tx
return nil
}
if limit > 0 {
if err := i.Query("SELECT "+MainLikelySweepTransactionSelectFields+" FROM main_likely_sweep_transactions ORDER BY timestamp DESC LIMIT $1;", scanFunc, limit); err != nil {
return
}
func (i *Index) GetPayoutsByMinerIdFromTimestamp(minerId uint64, timestamp uint64) (QueryIterator[Payout], error) {
if stmt, err := i.handle.Prepare("SELECT * FROM " + i.views["payouts"] + " WHERE miner = $1 AND timestamp >= $2 ORDER BY main_height DESC;"); err != nil {
return nil, err
} else {
if r, err := queryStatement[Payout](i, stmt, minerId, timestamp); err != nil {
defer stmt.Close()
return nil, err
} else {
if err := i.Query("SELECT "+MainLikelySweepTransactionSelectFields+" FROM main_likely_sweep_transactions ORDER BY timestamp DESC;", scanFunc); err != nil {
return
r.closer = func() {
stmt.Close()
}
return r, nil
}
}()
return out
}
}
func (i *Index) GetMainLikelySweepTransactionsByAddress(addr *address.Address, limit uint64) chan *MainLikelySweepTransaction {
out := make(chan *MainLikelySweepTransaction, 1)
go func() {
defer close(out)
spendPub, viewPub := addr.SpendPublicKey().AsSlice(), addr.ViewPublicKey().AsSlice()
scanFunc := func(row RowScanInterface) error {
var tx MainLikelySweepTransaction
if err := tx.ScanFromRow(i, row); err != nil {
return err
}
out <- &tx
return nil
}
if limit > 0 {
if err := i.Query("SELECT "+MainLikelySweepTransactionSelectFields+" FROM main_likely_sweep_transactions WHERE miner_spend_public_key = $1 AND miner_view_public_key = $2 ORDER BY timestamp DESC LIMIT $3;", scanFunc, &spendPub, &viewPub, limit); err != nil {
return
}
func (i *Index) GetPayoutsBySideBlock(b *SideBlock) (QueryIterator[Payout], error) {
if stmt, err := i.handle.Prepare("SELECT * FROM " + i.views["payouts"] + " WHERE miner = $1 AND ((side_height >= $2 AND including_height <= $2) OR main_id = $3) ORDER BY main_height DESC;"); err != nil {
return nil, err
} else {
if r, err := queryStatement[Payout](i, stmt, b.Miner, b.EffectiveHeight, &b.MainId); err != nil {
defer stmt.Close()
return nil, err
} else {
if err := i.Query("SELECT "+MainLikelySweepTransactionSelectFields+" FROM main_likely_sweep_transactions WHERE miner_spend_public_key = $1 AND miner_view_public_key = $2 ORDER BY timestamp DESC;", scanFunc, &spendPub, &viewPub); err != nil {
return
r.closer = func() {
stmt.Close()
}
return r, nil
}
}()
}
}
return out
func (i *Index) GetMainCoinbaseOutputs(coinbaseId types.Hash) (QueryIterator[MainCoinbaseOutput], error) {
if stmt, err := i.handle.Prepare("SELECT " + MainCoinbaseOutputSelectFields + " FROM main_coinbase_outputs WHERE id = $1 ORDER BY index ASC;"); err != nil {
return nil, err
} else {
if r, err := queryStatement[MainCoinbaseOutput](i, stmt, coinbaseId[:]); err != nil {
defer stmt.Close()
return nil, err
} else {
r.closer = func() {
stmt.Close()
}
return r, nil
}
}
}
func (i *Index) GetMainCoinbaseOutputByIndex(coinbaseId types.Hash, index uint64) (o *MainCoinbaseOutput) {
if stmt, err := i.handle.Prepare("SELECT " + MainCoinbaseOutputSelectFields + " FROM main_coinbase_outputs WHERE id = $1 AND index = $2 ORDER BY index ASC;"); err != nil {
log.Print(err)
return nil
} else {
if r, err := queryStatement[MainCoinbaseOutput](i, stmt, coinbaseId[:], index); err != nil {
log.Print(err)
return nil
} else {
defer r.Close()
r.closer = func() {
stmt.Close()
}
defer r.Close()
if _, o = r.Next(); o == nil && r.Err() != nil {
log.Print(r.Err())
}
return o
}
}
}
func (i *Index) GetMainCoinbaseOutputByGlobalOutputIndex(globalOutputIndex uint64) (o *MainCoinbaseOutput) {
if stmt, err := i.handle.Prepare("SELECT " + MainCoinbaseOutputSelectFields + " FROM main_coinbase_outputs WHERE global_output_index = $1 ORDER BY index ASC;"); err != nil {
log.Print(err)
return nil
} else {
if r, err := queryStatement[MainCoinbaseOutput](i, stmt, globalOutputIndex); err != nil {
log.Print(err)
return nil
} else {
defer r.Close()
r.closer = func() {
stmt.Close()
}
defer r.Close()
if _, o = r.Next(); o == nil && r.Err() != nil {
log.Print(r.Err())
}
return o
}
}
}
func (i *Index) GetMainLikelySweepTransactions(limit uint64) (r QueryIterator[MainLikelySweepTransaction], err error) {
var stmt *sql.Stmt
var params []any
if limit > 0 {
if stmt, err = i.handle.Prepare("SELECT " + MainLikelySweepTransactionSelectFields + " FROM main_likely_sweep_transactions ORDER BY timestamp DESC LIMIT $1;"); err != nil {
return nil, err
}
params = []any{limit}
} else {
if stmt, err = i.handle.Prepare("SELECT " + MainLikelySweepTransactionSelectFields + " FROM main_likely_sweep_transactions ORDER BY timestamp DESC;"); err != nil {
return nil, err
}
params = []any{}
}
if r, err := queryStatement[MainLikelySweepTransaction](i, stmt, params...); err != nil {
defer stmt.Close()
return nil, err
} else {
r.closer = func() {
stmt.Close()
}
return r, nil
}
}
func (i *Index) GetMainLikelySweepTransactionsByAddress(addr *address.Address, limit uint64) (r QueryIterator[MainLikelySweepTransaction], err error) {
var stmt *sql.Stmt
var params []any
spendPub, viewPub := addr.SpendPublicKey().AsSlice(), addr.ViewPublicKey().AsSlice()
if limit > 0 {
if stmt, err = i.handle.Prepare("SELECT " + MainLikelySweepTransactionSelectFields + " FROM main_likely_sweep_transactions WHERE miner_spend_public_key = $1 AND miner_view_public_key = $2 ORDER BY timestamp DESC LIMIT $3;"); err != nil {
return nil, err
}
params = []any{&spendPub, &viewPub, limit}
} else {
if stmt, err = i.handle.Prepare("SELECT " + MainLikelySweepTransactionSelectFields + " FROM main_likely_sweep_transactions WHERE miner_spend_public_key = $1 AND miner_view_public_key = $2 ORDER BY timestamp DESC;"); err != nil {
return nil, err
}
params = []any{&spendPub, &viewPub}
}
if r, err := queryStatement[MainLikelySweepTransaction](i, stmt, params...); err != nil {
defer stmt.Close()
return nil, err
} else {
r.closer = func() {
stmt.Close()
}
return r, nil
}
}
type TransactionInputQueryResult struct {
@ -1367,17 +1370,3 @@ func (i *Index) InsertOrUpdatePoolBlock(b *sidechain.PoolBlock, inclusion BlockI
return nil
}
func ChanConsume[T any](s chan T) {
for range s {
}
}
func ChanToSlice[T any](s chan T) (r []T) {
r = make([]T, 0)
for v := range s {
r = append(r, v)
}
return r
}

66
cmd/index/iterator.go Normal file
View file

@ -0,0 +1,66 @@
package index
type IterateFunction[K, V any] func(key K, value V) (stop bool)
type IteratorFunction[K, V any] func(f IterateFunction[K, V]) (complete bool)
type Iterator[K, V any] interface {
All(f IterateFunction[K, V]) (complete bool)
}
func IteratorToIterator[K, V any](i Iterator[K, V], _ ...error) Iterator[K, V] {
return i
}
func IterateToSlice[T any](i Iterator[int, T], _ ...error) (s []T) {
i.All(func(key int, value T) (stop bool) {
s = append(s, value)
return false
})
return s
}
func IterateToSliceWithoutPointer[T any](i Iterator[int, *T], _ ...error) (s []T) {
i.All(func(key int, value *T) (stop bool) {
s = append(s, *value)
return false
})
return s
}
func SliceIterate[S ~[]T, T any](s S, f IterateFunction[int, T]) (complete bool) {
for i, v := range s {
if f(i, v) {
return (len(s) - 1) == i
}
}
return true
}
func ChanIterate[T any](c <-chan T, f IterateFunction[int, T]) (complete bool) {
var i int
for {
v, more := <-c
if !more {
return true
}
if f(i, v) {
return false
}
i++
}
}
func ChanConsume[T any](c <-chan T) {
for range c {
}
}
func ChanToSlice[S ~[]T, T any](c <-chan T) (s S) {
s = make(S, 0, len(c))
for v := range c {
s = append(s, v)
}
return s
}

170
cmd/index/query.go Normal file
View file

@ -0,0 +1,170 @@
package index
import (
"database/sql"
"errors"
)
type RowScanInterface interface {
Scan(dest ...any) error
}
type Scannable interface {
ScanFromRow(i *Index, row RowScanInterface) error
}
type QueryIterator[V any] interface {
All(f IterateFunction[int, *V]) (complete bool)
Next() (int, *V)
Close()
Err() error
}
func QueryIterate[V any](i QueryIterator[V], f IterateFunction[int, *V]) {
defer i.Close()
i.All(f)
if i.Err() != nil {
panic(i.Err())
}
}
func QueryIterateToSlice[T any](i QueryIterator[T], err ...error) (s []*T) {
if len(err) > 0 {
if err[0] != nil {
panic(err)
}
}
QueryIterate(i, func(key int, value *T) (stop bool) {
s = append(s, value)
return false
})
return s
}
func QueryFirstResult[T any](i QueryIterator[T], err ...error) (v *T) {
if len(err) > 0 {
if err[0] != nil {
panic(err)
}
}
QueryIterate(i, func(_ int, value *T) (stop bool) {
v = value
return true
})
return
}
func QueryHasResults[T any](i QueryIterator[T], err ...error) bool {
if len(err) > 0 {
if err[0] != nil {
panic(err)
}
}
var hasValue bool
QueryIterate(i, func(key int, value *T) (stop bool) {
if value != nil {
hasValue = true
}
return true
})
return hasValue
}
// queryStatement Queries a provided sql.Stmt and returns a QueryIterator
// After results are read QueryIterator must be closed/freed
func queryStatement[V any](index *Index, stmt *sql.Stmt, params ...any) (*QueryResult[V], error) {
var testV *V
if _, ok := any(testV).(Scannable); !ok {
return nil, errors.New("unsupported type")
}
if rows, err := stmt.Query(params...); err != nil {
return nil, err
} else {
return &QueryResult[V]{
index: index,
rows: rows,
}, err
}
}
type FakeQueryResult[V any] struct {
NextFunction func() (int, *V)
}
func (r *FakeQueryResult[V]) All(f IterateFunction[int, *V]) (complete bool) {
for {
if i, v := r.Next(); v == nil {
return true
} else {
if f(i, v) {
// do not allow resuming
return true
}
}
}
}
func (r *FakeQueryResult[V]) Next() (int, *V) {
return r.NextFunction()
}
func (r *FakeQueryResult[V]) Err() error {
return nil
}
func (r *FakeQueryResult[V]) Close() {
}
type QueryResult[V any] struct {
index *Index
rows *sql.Rows
closer func()
i int
err error
}
func (r *QueryResult[V]) All(f IterateFunction[int, *V]) (complete bool) {
for {
if i, v := r.Next(); v == nil {
return true
} else {
if f(i, v) {
// do not allow resuming
return true
}
}
}
}
func (r *QueryResult[V]) Next() (int, *V) {
if r.rows.Next() {
var v V
if r.err = any(&v).(Scannable).ScanFromRow(r.index, r.rows); r.err != nil {
return 0, nil
}
r.i++
return r.i - 1, &v
}
return 0, nil
}
func (r *QueryResult[V]) Err() error {
return r.err
}
func (r *QueryResult[V]) Close() {
if r.closer != nil {
r.closer()
r.closer = nil
}
if r.rows != nil {
r.rows.Close()
r.rows = nil
}
}

View file

@ -14,7 +14,7 @@ import (
)
type GetByTemplateIdFunc func(h types.Hash) *SideBlock
type GetUnclesByTemplateIdFunc func(h types.Hash) chan *SideBlock
type GetUnclesByTemplateIdFunc func(h types.Hash) QueryIterator[SideBlock]
type SideBlockWindowAddWeightFunc func(b *SideBlock, weight types.Difficulty)
type SideBlockWindowSlot struct {
@ -59,7 +59,7 @@ func IterateSideBlocksInPPLNSWindow(tip *SideBlock, consensus *sidechain.Consens
}
curWeight := types.DifficultyFrom64(cur.Difficulty)
for uncle := range getUnclesByTemplateId(cur.TemplateId) {
QueryIterate(getUnclesByTemplateId(cur.TemplateId), func(_ int, uncle *SideBlock) (stop bool) {
//Needs to be added regardless - for other consumers
if !slices.ContainsFunc(curEntry.Uncles, func(sideBlock *SideBlock) bool {
return sideBlock.TemplateId == uncle.TemplateId
@ -69,7 +69,7 @@ func IterateSideBlocksInPPLNSWindow(tip *SideBlock, consensus *sidechain.Consens
// Skip uncles which are already out of PPLNS window
if (tip.SideHeight - uncle.SideHeight) >= consensus.ChainWindowSize {
continue
return false
}
// Take some % of uncle's weight into this share
@ -78,7 +78,7 @@ func IterateSideBlocksInPPLNSWindow(tip *SideBlock, consensus *sidechain.Consens
// Skip uncles that push PPLNS weight above the limit
if newPplnsWeight.Cmp(maxPplnsWeight) > 0 {
continue
return false
}
curWeight = curWeight.Add(unclePenalty)
@ -87,7 +87,9 @@ func IterateSideBlocksInPPLNSWindow(tip *SideBlock, consensus *sidechain.Consens
}
pplnsWeight = newPplnsWeight
}
return false
})
// Always add non-uncle shares even if PPLNS weight goes above the limit
slotFunc(curEntry)
@ -161,7 +163,7 @@ func BlocksInPPLNSWindow(tip *SideBlock, consensus *sidechain.Consensus, difficu
}
curWeight := types.DifficultyFrom64(cur.Difficulty)
for uncle := range getUnclesByTemplateId(cur.TemplateId) {
QueryIterate(getUnclesByTemplateId(cur.TemplateId), func(_ int, uncle *SideBlock) (stop bool) {
//Needs to be added regardless - for other consumers
if !slices.ContainsFunc(curEntry.Uncles, func(sideBlock *SideBlock) bool {
return sideBlock.TemplateId == uncle.TemplateId
@ -171,7 +173,7 @@ func BlocksInPPLNSWindow(tip *SideBlock, consensus *sidechain.Consensus, difficu
// Skip uncles which are already out of PPLNS window
if (tip.SideHeight - uncle.SideHeight) >= consensus.ChainWindowSize {
continue
return false
}
// Take some % of uncle's weight into this share
@ -180,7 +182,7 @@ func BlocksInPPLNSWindow(tip *SideBlock, consensus *sidechain.Consensus, difficu
// Skip uncles that push PPLNS weight above the limit
if newPplnsWeight.Cmp(maxPplnsWeight) > 0 {
continue
return false
}
curWeight = curWeight.Add(unclePenalty)
@ -189,7 +191,9 @@ func BlocksInPPLNSWindow(tip *SideBlock, consensus *sidechain.Consensus, difficu
}
pplnsWeight = newPplnsWeight
}
return false
})
// Always add non-uncle shares even if PPLNS weight goes above the limit
bottomHeight = cur.SideHeight

View file

@ -138,13 +138,13 @@ func FindAndInsertMainHeader(h daemon.BlockHeader, indexDb *index.Index, storeFu
if sideBlock == nil {
//found deep orphan which found block, insert parents
cur := t
for cur != nil && len(index.ChanToSlice(indexDb.GetSideBlocksByTemplateId(cur.SideTemplateId(indexDb.Consensus())))) == 0 {
for cur != nil && !index.QueryHasResults(indexDb.GetSideBlocksByTemplateId(cur.SideTemplateId(indexDb.Consensus()))) {
orphanBlock, orphanUncles, err := indexDb.GetSideBlockFromPoolBlock(cur, index.InclusionOrphan)
if err != nil {
break
}
if len(index.ChanToSlice(indexDb.GetSideBlocksByTemplateId(orphanBlock.TemplateId))) > 0 {
if index.QueryHasResults(indexDb.GetSideBlocksByTemplateId(orphanBlock.TemplateId)) {
continue
}
if err := indexDb.InsertOrUpdateSideBlock(orphanBlock); err != nil {
@ -152,7 +152,7 @@ func FindAndInsertMainHeader(h daemon.BlockHeader, indexDb *index.Index, storeFu
}
for _, orphanUncle := range orphanUncles {
if len(index.ChanToSlice(indexDb.GetSideBlocksByTemplateId(orphanUncle.TemplateId))) > 0 {
if index.QueryHasResults(indexDb.GetSideBlocksByTemplateId(orphanUncle.TemplateId)) {
continue
}
if err := indexDb.InsertOrUpdateSideBlock(orphanUncle); err != nil {
@ -251,7 +251,7 @@ func FindAndInsertMainHeader(h daemon.BlockHeader, indexDb *index.Index, storeFu
}
func FindAndInsertMainHeaderOutputs(mb *index.MainBlock, indexDb *index.Index, client *client.Client, difficultyByHeight block.GetDifficultyByHeightFunc, getByTemplateId sidechain.GetByTemplateIdFunc, getByMainId sidechain.GetByMainIdFunc, getByMainHeight sidechain.GetByMainHeightFunc, processBlock func(b *sidechain.PoolBlock) error) error {
if len(indexDb.GetMainCoinbaseOutputs(mb.CoinbaseId)) == 0 {
if !index.QueryHasResults(indexDb.GetMainCoinbaseOutputs(mb.CoinbaseId)) {
//fill information
log.Printf("inserting coinbase outputs for %s, template id %s, coinbase id %s", mb.Id, mb.SideTemplateId, mb.CoinbaseId)
var t *sidechain.PoolBlock