git.gammaspectra.live/P2Pool/p2pool-observer@v0.0.0-20240403172525-d7dfbf7231c8/p2pool/api/p2poolapi.go (about)

     1  package api
     2  
     3  import (
     4  	"bytes"
     5  	"errors"
     6  	"git.gammaspectra.live/P2Pool/p2pool-observer/monero/block"
     7  	"git.gammaspectra.live/P2Pool/p2pool-observer/monero/randomx"
     8  	"git.gammaspectra.live/P2Pool/p2pool-observer/p2pool/sidechain"
     9  	p2pooltypes "git.gammaspectra.live/P2Pool/p2pool-observer/p2pool/types"
    10  	"git.gammaspectra.live/P2Pool/p2pool-observer/types"
    11  	"git.gammaspectra.live/P2Pool/p2pool-observer/utils"
    12  	"github.com/floatdrop/lru"
    13  	"io"
    14  	"net/http"
    15  	"net/netip"
    16  	"net/url"
    17  	"strconv"
    18  	"sync/atomic"
    19  	"time"
    20  )
    21  
    22  type P2PoolApi struct {
    23  	Host                    string
    24  	Client                  *http.Client
    25  	consensus               atomic.Pointer[sidechain.Consensus]
    26  	derivationCache         sidechain.DerivationCacheInterface
    27  	difficultyByHeightCache *lru.LRU[uint64, types.Difficulty]
    28  }
    29  
    30  func NewP2PoolApi(host string) *P2PoolApi {
    31  	return &P2PoolApi{
    32  		Host: host,
    33  		Client: &http.Client{
    34  			Timeout: time.Second * 15,
    35  		},
    36  		derivationCache:         sidechain.NewDerivationLRUCache(),
    37  		difficultyByHeightCache: lru.New[uint64, types.Difficulty](1024),
    38  	}
    39  }
    40  
    41  func (p *P2PoolApi) WaitSync() (err error) {
    42  	defer func() {
    43  		if r := recover(); r != nil {
    44  			err = errors.New("panicked")
    45  		}
    46  	}()
    47  	status := p.Status()
    48  	for ; p == nil || !p.Status().Synchronized; status = p.Status() {
    49  		if p == nil {
    50  			utils.Logf("API", "Not synchronized (nil), waiting five seconds")
    51  		} else {
    52  			utils.Logf("API", "Not synchronized (height %d, id %s, blocks %d), waiting five seconds", status.Height, status.Id, status.Blocks)
    53  		}
    54  		time.Sleep(time.Second * 5)
    55  	}
    56  	utils.Logf("API", "SYNCHRONIZED (height %d, id %s, blocks %d)", status.Height, status.Id, status.Blocks)
    57  	utils.Logf("API", "Consensus id = %s\n", p.Consensus().Id)
    58  	return nil
    59  }
    60  
    61  func (p *P2PoolApi) WaitSyncStart() (err error) {
    62  	defer func() {
    63  		if r := recover(); r != nil {
    64  			err = errors.New("panicked")
    65  		}
    66  	}()
    67  	status := p.Status()
    68  	for ; p == nil || !p.Status().Synchronized; status = p.Status() {
    69  		if p == nil {
    70  			utils.Logf("API", "Not synchronized (nil), waiting one seconds")
    71  		} else {
    72  			utils.Logf("API", "Not synchronized (height %d, id %s, blocks %d)", status.Height, status.Id, status.Blocks)
    73  			break
    74  		}
    75  		time.Sleep(time.Second * 1)
    76  	}
    77  	if status.Synchronized {
    78  		utils.Logf("API", "SYNCHRONIZED (height %d, id %s, blocks %d)", status.Height, status.Id, status.Blocks)
    79  	}
    80  	utils.Logf("API", "Consensus id = %s\n", p.Consensus().Id)
    81  	return nil
    82  }
    83  
    84  func (p *P2PoolApi) InsertAlternate(b *sidechain.PoolBlock) {
    85  	buf, _ := b.MarshalBinary()
    86  	uri, _ := url.Parse(p.Host + "/archive/insert_alternate")
    87  	response, err := p.Client.Do(&http.Request{
    88  		Method: "POST",
    89  		URL:    uri,
    90  		Body:   io.NopCloser(bytes.NewReader(buf)),
    91  	})
    92  	if err != nil {
    93  		return
    94  	}
    95  	defer response.Body.Close()
    96  }
    97  
    98  func (p *P2PoolApi) LightByMainId(id types.Hash) *sidechain.PoolBlock {
    99  	if response, err := p.Client.Get(p.Host + "/archive/light_block_by_main_id/" + id.String()); err != nil {
   100  		return nil
   101  	} else {
   102  		defer response.Body.Close()
   103  
   104  		if buf, err := io.ReadAll(response.Body); err != nil {
   105  			return nil
   106  		} else {
   107  			b := &sidechain.PoolBlock{}
   108  
   109  			if err = utils.UnmarshalJSON(buf, &b); err != nil || b.ShareVersion() == sidechain.ShareVersion_None {
   110  				return nil
   111  			}
   112  
   113  			return b
   114  		}
   115  	}
   116  }
   117  
   118  func (p *P2PoolApi) LightByMainIdWithHint(id, templateIdHint types.Hash) *sidechain.PoolBlock {
   119  	if response, err := p.Client.Get(p.Host + "/archive/light_block_by_main_id/" + id.String() + "?templateIdHint=" + templateIdHint.String()); err != nil {
   120  		return nil
   121  	} else {
   122  		defer response.Body.Close()
   123  
   124  		if buf, err := io.ReadAll(response.Body); err != nil {
   125  			return nil
   126  		} else {
   127  			b := &sidechain.PoolBlock{}
   128  
   129  			if err = utils.UnmarshalJSON(buf, &b); err != nil || b.ShareVersion() == sidechain.ShareVersion_None {
   130  				return nil
   131  			}
   132  
   133  			return b
   134  		}
   135  	}
   136  }
   137  
   138  func (p *P2PoolApi) ByMainId(id types.Hash) *sidechain.PoolBlock {
   139  	if response, err := p.Client.Get(p.Host + "/archive/block_by_main_id/" + id.String()); err != nil {
   140  		return nil
   141  	} else {
   142  		defer response.Body.Close()
   143  
   144  		if buf, err := io.ReadAll(response.Body); err != nil {
   145  			return nil
   146  		} else {
   147  			var result p2pooltypes.P2PoolBinaryBlockResult
   148  
   149  			if err = utils.UnmarshalJSON(buf, &result); err != nil || result.Version == 0 {
   150  				return nil
   151  			}
   152  
   153  			b := &sidechain.PoolBlock{}
   154  			if err = b.UnmarshalBinary(p.Consensus(), p.derivationCache, result.Blob); err != nil || int(b.ShareVersion()) != result.Version {
   155  				return nil
   156  			}
   157  			return b
   158  		}
   159  	}
   160  }
   161  
   162  func (p *P2PoolApi) ByMainIdWithHint(id, templateIdHint types.Hash) *sidechain.PoolBlock {
   163  	if response, err := p.Client.Get(p.Host + "/archive/block_by_main_id/" + id.String() + "?templateIdHint=" + templateIdHint.String()); err != nil {
   164  		return nil
   165  	} else {
   166  		defer response.Body.Close()
   167  
   168  		if buf, err := io.ReadAll(response.Body); err != nil {
   169  			return nil
   170  		} else {
   171  			var result p2pooltypes.P2PoolBinaryBlockResult
   172  
   173  			if err = utils.UnmarshalJSON(buf, &result); err != nil || result.Version == 0 {
   174  				return nil
   175  			}
   176  
   177  			b := &sidechain.PoolBlock{}
   178  			if err = b.UnmarshalBinary(p.Consensus(), p.derivationCache, result.Blob); err != nil || int(b.ShareVersion()) != result.Version {
   179  				return nil
   180  			}
   181  			return b
   182  		}
   183  	}
   184  }
   185  
   186  func (p *P2PoolApi) LightByTemplateId(id types.Hash) sidechain.UniquePoolBlockSlice {
   187  	if response, err := p.Client.Get(p.Host + "/archive/light_blocks_by_template_id/" + id.String()); err != nil {
   188  		return nil
   189  	} else {
   190  		defer response.Body.Close()
   191  
   192  		if buf, err := io.ReadAll(response.Body); err != nil {
   193  			return nil
   194  		} else {
   195  			var result sidechain.UniquePoolBlockSlice
   196  
   197  			if err = utils.UnmarshalJSON(buf, &result); err != nil || len(result) == 0 {
   198  				return nil
   199  			}
   200  
   201  			return result
   202  		}
   203  	}
   204  }
   205  
   206  func (p *P2PoolApi) ByTemplateId(id types.Hash) *sidechain.PoolBlock {
   207  	if response, err := p.Client.Get(p.Host + "/sidechain/block_by_template_id/" + id.String()); err != nil {
   208  		return nil
   209  	} else {
   210  		defer response.Body.Close()
   211  
   212  		if buf, err := io.ReadAll(response.Body); err != nil {
   213  			return nil
   214  		} else {
   215  			var result p2pooltypes.P2PoolBinaryBlockResult
   216  
   217  			if err = utils.UnmarshalJSON(buf, &result); err != nil {
   218  				return nil
   219  			} else if result.Version == 0 {
   220  				// Fallback into archive
   221  				if response, err := p.Client.Get(p.Host + "/archive/blocks_by_template_id/" + id.String()); err != nil {
   222  					return nil
   223  				} else {
   224  					defer response.Body.Close()
   225  
   226  					if buf, err := io.ReadAll(response.Body); err != nil {
   227  						return nil
   228  					} else {
   229  						var result []p2pooltypes.P2PoolBinaryBlockResult
   230  
   231  						if err = utils.UnmarshalJSON(buf, &result); err != nil || len(result) == 0 {
   232  							return nil
   233  						}
   234  
   235  						for _, r := range result {
   236  							//Get first block that matches
   237  							if r.Version == 0 {
   238  								continue
   239  							}
   240  							b := &sidechain.PoolBlock{}
   241  							if err = b.UnmarshalBinary(p.Consensus(), p.derivationCache, r.Blob); err != nil || int(b.ShareVersion()) != r.Version {
   242  								continue
   243  							}
   244  							return b
   245  						}
   246  						return nil
   247  					}
   248  				}
   249  			}
   250  
   251  			b := &sidechain.PoolBlock{}
   252  			if err = b.UnmarshalBinary(p.Consensus(), p.derivationCache, result.Blob); err != nil {
   253  				return nil
   254  			}
   255  			return b
   256  		}
   257  	}
   258  }
   259  
   260  func (p *P2PoolApi) LightBySideHeight(height uint64) sidechain.UniquePoolBlockSlice {
   261  	if response, err := p.Client.Get(p.Host + "/archive/light_blocks_by_side_height/" + strconv.FormatUint(height, 10)); err != nil {
   262  		return nil
   263  	} else {
   264  		defer response.Body.Close()
   265  
   266  		if buf, err := io.ReadAll(response.Body); err != nil {
   267  			return nil
   268  		} else {
   269  			var result sidechain.UniquePoolBlockSlice
   270  
   271  			if err = utils.UnmarshalJSON(buf, &result); err != nil || len(result) == 0 {
   272  				return nil
   273  			}
   274  
   275  			return result
   276  		}
   277  	}
   278  }
   279  
   280  func (p *P2PoolApi) BySideHeight(height uint64) sidechain.UniquePoolBlockSlice {
   281  	if response, err := p.Client.Get(p.Host + "/sidechain/blocks_by_height/" + strconv.FormatUint(height, 10)); err != nil {
   282  		return nil
   283  	} else {
   284  		defer response.Body.Close()
   285  
   286  		if buf, err := io.ReadAll(response.Body); err != nil {
   287  			return nil
   288  		} else {
   289  			var result []p2pooltypes.P2PoolBinaryBlockResult
   290  
   291  			if err = utils.UnmarshalJSON(buf, &result); err != nil {
   292  				return nil
   293  			} else if len(result) == 0 {
   294  				// Fallback into archive
   295  				if response, err := p.Client.Get(p.Host + "/archive/blocks_by_side_height/" + strconv.FormatUint(height, 10)); err != nil {
   296  					return nil
   297  				} else {
   298  					defer response.Body.Close()
   299  
   300  					if buf, err := io.ReadAll(response.Body); err != nil {
   301  						return nil
   302  					} else {
   303  						var result []p2pooltypes.P2PoolBinaryBlockResult
   304  
   305  						if err = utils.UnmarshalJSON(buf, &result); err != nil || len(result) == 0 {
   306  							return nil
   307  						}
   308  
   309  						results := make([]*sidechain.PoolBlock, 0, len(result))
   310  						for _, r := range result {
   311  							if r.Version == 0 {
   312  								return nil
   313  							}
   314  							b := &sidechain.PoolBlock{}
   315  							if err = b.UnmarshalBinary(p.Consensus(), p.derivationCache, r.Blob); err != nil {
   316  								return nil
   317  							}
   318  							results = append(results, b)
   319  						}
   320  						return results
   321  					}
   322  				}
   323  			}
   324  
   325  			results := make([]*sidechain.PoolBlock, 0, len(result))
   326  			for _, r := range result {
   327  				if r.Version == 0 {
   328  					return nil
   329  				}
   330  				b := &sidechain.PoolBlock{}
   331  				if err = b.UnmarshalBinary(p.Consensus(), p.derivationCache, r.Blob); err != nil {
   332  					return nil
   333  				}
   334  				results = append(results, b)
   335  			}
   336  			return results
   337  		}
   338  	}
   339  }
   340  
   341  func (p *P2PoolApi) LightByMainHeight(height uint64) sidechain.UniquePoolBlockSlice {
   342  	if response, err := p.Client.Get(p.Host + "/archive/light_blocks_by_main_height/" + strconv.FormatUint(height, 10)); err != nil {
   343  		return nil
   344  	} else {
   345  		defer response.Body.Close()
   346  
   347  		if buf, err := io.ReadAll(response.Body); err != nil {
   348  			return nil
   349  		} else {
   350  			var result sidechain.UniquePoolBlockSlice
   351  
   352  			if err = utils.UnmarshalJSON(buf, &result); err != nil || len(result) == 0 {
   353  				return nil
   354  			}
   355  
   356  			return result
   357  		}
   358  	}
   359  }
   360  
   361  func (p *P2PoolApi) ByMainHeight(height uint64) sidechain.UniquePoolBlockSlice {
   362  	if response, err := p.Client.Get(p.Host + "/archive/blocks_by_main_height/" + strconv.FormatUint(height, 10)); err != nil {
   363  		return nil
   364  	} else {
   365  		defer response.Body.Close()
   366  
   367  		if buf, err := io.ReadAll(response.Body); err != nil {
   368  			return nil
   369  		} else {
   370  			var result []p2pooltypes.P2PoolBinaryBlockResult
   371  
   372  			if err = utils.UnmarshalJSON(buf, &result); err != nil || len(result) == 0 {
   373  				return nil
   374  			}
   375  
   376  			results := make([]*sidechain.PoolBlock, 0, len(result))
   377  			for _, r := range result {
   378  				if r.Version == 0 {
   379  					return nil
   380  				}
   381  				b := &sidechain.PoolBlock{}
   382  				if err = b.UnmarshalBinary(p.Consensus(), p.derivationCache, r.Blob); err != nil {
   383  					return nil
   384  				}
   385  				results = append(results, b)
   386  			}
   387  			return results
   388  		}
   389  	}
   390  }
   391  
   392  func (p *P2PoolApi) DifficultyByHeight(height uint64) types.Difficulty {
   393  	if v := p.difficultyByHeightCache.Get(height); v == nil {
   394  		if diff := p.MainDifficultyByHeight(height); diff != types.ZeroDifficulty {
   395  			p.difficultyByHeightCache.Set(height, diff)
   396  			return diff
   397  		}
   398  		return types.ZeroDifficulty
   399  	} else {
   400  		return *v
   401  	}
   402  }
   403  
   404  func (p *P2PoolApi) SeedByHeight(height uint64) types.Hash {
   405  	seedHeight := randomx.SeedHeight(height)
   406  	if v := p.MainHeaderByHeight(seedHeight); v != nil {
   407  		return v.Id
   408  	}
   409  	return types.ZeroHash
   410  }
   411  
   412  func (p *P2PoolApi) PeerList() []byte {
   413  	if response, err := p.Client.Get(p.Host + "/server/peerlist"); err != nil {
   414  		return nil
   415  	} else {
   416  		defer response.Body.Close()
   417  		buf, err := io.ReadAll(response.Body)
   418  		if err == nil {
   419  			return buf
   420  		}
   421  	}
   422  
   423  	return nil
   424  }
   425  
   426  func (p *P2PoolApi) ConnectionCheck(addrPort netip.AddrPort) *p2pooltypes.P2PoolConnectionCheckInformation[*sidechain.PoolBlock] {
   427  	if response, err := p.Client.Get(p.Host + "/server/connection_check/" + addrPort.String()); err != nil {
   428  		return nil
   429  	} else {
   430  		defer response.Body.Close()
   431  
   432  		if buf, err := io.ReadAll(response.Body); err != nil {
   433  			return nil
   434  		} else {
   435  			var result p2pooltypes.P2PoolConnectionCheckInformation[*sidechain.PoolBlock]
   436  
   437  			if err = utils.UnmarshalJSON(buf, &result); err != nil {
   438  				return nil
   439  			}
   440  
   441  			return &result
   442  		}
   443  	}
   444  }
   445  
   446  func (p *P2PoolApi) MinerData() *p2pooltypes.MinerData {
   447  	if response, err := p.Client.Get(p.Host + "/mainchain/miner_data"); err != nil {
   448  		return nil
   449  	} else {
   450  		defer response.Body.Close()
   451  
   452  		if buf, err := io.ReadAll(response.Body); err != nil {
   453  			return nil
   454  		} else {
   455  			var result p2pooltypes.MinerData
   456  
   457  			if err = utils.UnmarshalJSON(buf, &result); err != nil {
   458  				return nil
   459  			}
   460  
   461  			return &result
   462  		}
   463  	}
   464  }
   465  
   466  func (p *P2PoolApi) MainTip() *block.Header {
   467  	if response, err := p.Client.Get(p.Host + "/mainchain/tip"); err != nil {
   468  		return nil
   469  	} else {
   470  		defer response.Body.Close()
   471  
   472  		if buf, err := io.ReadAll(response.Body); err != nil {
   473  			return nil
   474  		} else {
   475  			var result block.Header
   476  
   477  			if err = utils.UnmarshalJSON(buf, &result); err != nil {
   478  				return nil
   479  			}
   480  
   481  			return &result
   482  		}
   483  	}
   484  }
   485  
   486  func (p *P2PoolApi) MainHeaderById(id types.Hash) *block.Header {
   487  	if response, err := p.Client.Get(p.Host + "/mainchain/header_by_id/" + id.String()); err != nil {
   488  		return nil
   489  	} else {
   490  		defer response.Body.Close()
   491  
   492  		if buf, err := io.ReadAll(response.Body); err != nil {
   493  			return nil
   494  		} else {
   495  			var result block.Header
   496  
   497  			if err = utils.UnmarshalJSON(buf, &result); err != nil {
   498  				return nil
   499  			}
   500  
   501  			return &result
   502  		}
   503  	}
   504  }
   505  
   506  func (p *P2PoolApi) MainHeaderByHeight(height uint64) *block.Header {
   507  	if response, err := p.Client.Get(p.Host + "/mainchain/header_by_height/" + strconv.FormatUint(height, 10)); err != nil {
   508  		return nil
   509  	} else {
   510  		defer response.Body.Close()
   511  
   512  		if buf, err := io.ReadAll(response.Body); err != nil {
   513  			return nil
   514  		} else {
   515  			var result block.Header
   516  
   517  			if err = utils.UnmarshalJSON(buf, &result); err != nil {
   518  				return nil
   519  			}
   520  
   521  			return &result
   522  		}
   523  	}
   524  }
   525  
   526  func (p *P2PoolApi) MainDifficultyByHeight(height uint64) types.Difficulty {
   527  	if response, err := p.Client.Get(p.Host + "/mainchain/difficulty_by_height/" + strconv.FormatUint(height, 10)); err != nil {
   528  		return types.ZeroDifficulty
   529  	} else {
   530  		defer response.Body.Close()
   531  
   532  		if buf, err := io.ReadAll(response.Body); err != nil {
   533  			return types.ZeroDifficulty
   534  		} else {
   535  			var result types.Difficulty
   536  
   537  			if err = utils.UnmarshalJSON(buf, &result); err != nil {
   538  				return types.ZeroDifficulty
   539  			}
   540  
   541  			return result
   542  		}
   543  	}
   544  }
   545  
   546  func (p *P2PoolApi) StateFromTemplateId(id types.Hash) (chain, uncles sidechain.UniquePoolBlockSlice) {
   547  	if response, err := p.Client.Get(p.Host + "/sidechain/state/" + id.String()); err != nil {
   548  		return nil, nil
   549  	} else {
   550  		defer response.Body.Close()
   551  
   552  		if buf, err := io.ReadAll(response.Body); err != nil {
   553  			return nil, nil
   554  		} else {
   555  			var result p2pooltypes.P2PoolSideChainStateResult
   556  
   557  			if err = utils.UnmarshalJSON(buf, &result); err != nil {
   558  				return nil, nil
   559  			}
   560  
   561  			chain = make([]*sidechain.PoolBlock, 0, len(result.Chain))
   562  			uncles = make([]*sidechain.PoolBlock, 0, len(result.Uncles))
   563  
   564  			for _, r := range result.Chain {
   565  				b := &sidechain.PoolBlock{}
   566  				if err = b.UnmarshalBinary(p.Consensus(), p.derivationCache, r.Blob); err != nil {
   567  					return nil, nil
   568  				}
   569  				chain = append(chain, b)
   570  			}
   571  
   572  			for _, r := range result.Uncles {
   573  				b := &sidechain.PoolBlock{}
   574  				if err = b.UnmarshalBinary(p.Consensus(), p.derivationCache, r.Blob); err != nil {
   575  					return nil, nil
   576  				}
   577  				uncles = append(uncles, b)
   578  			}
   579  
   580  			return chain, uncles
   581  		}
   582  	}
   583  }
   584  
   585  func (p *P2PoolApi) WindowFromTemplateId(id types.Hash) (chain, uncles sidechain.UniquePoolBlockSlice) {
   586  	if response, err := p.Client.Get(p.Host + "/archive/window_from_template_id/" + id.String()); err != nil {
   587  		return nil, nil
   588  	} else {
   589  		defer response.Body.Close()
   590  
   591  		if buf, err := io.ReadAll(response.Body); err != nil {
   592  			return nil, nil
   593  		} else {
   594  			var result p2pooltypes.P2PoolSideChainStateResult
   595  
   596  			if err = utils.UnmarshalJSON(buf, &result); err != nil {
   597  				return nil, nil
   598  			}
   599  
   600  			chain = make([]*sidechain.PoolBlock, 0, len(result.Chain))
   601  			uncles = make([]*sidechain.PoolBlock, 0, len(result.Uncles))
   602  
   603  			for _, r := range result.Chain {
   604  				b := &sidechain.PoolBlock{}
   605  				if err = b.UnmarshalBinary(p.Consensus(), p.derivationCache, r.Blob); err != nil {
   606  					return nil, nil
   607  				}
   608  				chain = append(chain, b)
   609  			}
   610  
   611  			for _, r := range result.Uncles {
   612  				b := &sidechain.PoolBlock{}
   613  				if err = b.UnmarshalBinary(p.Consensus(), p.derivationCache, r.Blob); err != nil {
   614  					return nil, nil
   615  				}
   616  				uncles = append(uncles, b)
   617  			}
   618  
   619  			return chain, uncles
   620  		}
   621  	}
   622  }
   623  
   624  func (p *P2PoolApi) StateFromTip() (chain, uncles sidechain.UniquePoolBlockSlice) {
   625  	if response, err := p.Client.Get(p.Host + "/sidechain/state/tip"); err != nil {
   626  		return nil, nil
   627  	} else {
   628  		defer response.Body.Close()
   629  
   630  		if buf, err := io.ReadAll(response.Body); err != nil {
   631  			return nil, nil
   632  		} else {
   633  			var result p2pooltypes.P2PoolSideChainStateResult
   634  
   635  			if err = utils.UnmarshalJSON(buf, &result); err != nil {
   636  				return nil, nil
   637  			}
   638  
   639  			chain = make([]*sidechain.PoolBlock, 0, len(result.Chain))
   640  			uncles = make([]*sidechain.PoolBlock, 0, len(result.Uncles))
   641  
   642  			for _, r := range result.Chain {
   643  				b := &sidechain.PoolBlock{}
   644  				if err = b.UnmarshalBinary(p.Consensus(), p.derivationCache, r.Blob); err != nil {
   645  					return nil, nil
   646  				}
   647  				chain = append(chain, b)
   648  			}
   649  
   650  			for _, r := range result.Uncles {
   651  				b := &sidechain.PoolBlock{}
   652  				if err = b.UnmarshalBinary(p.Consensus(), p.derivationCache, r.Blob); err != nil {
   653  					return nil, nil
   654  				}
   655  				uncles = append(uncles, b)
   656  			}
   657  
   658  			return chain, uncles
   659  		}
   660  	}
   661  }
   662  
   663  func (p *P2PoolApi) Tip() *sidechain.PoolBlock {
   664  	if response, err := p.Client.Get(p.Host + "/sidechain/tip"); err != nil {
   665  		return nil
   666  	} else {
   667  		defer response.Body.Close()
   668  
   669  		if buf, err := io.ReadAll(response.Body); err != nil {
   670  			return nil
   671  		} else {
   672  			var result p2pooltypes.P2PoolBinaryBlockResult
   673  
   674  			if err = utils.UnmarshalJSON(buf, &result); err != nil {
   675  				return nil
   676  			}
   677  
   678  			if result.Version == 0 {
   679  				return nil
   680  			}
   681  			b := &sidechain.PoolBlock{}
   682  			if err = b.UnmarshalBinary(p.Consensus(), p.derivationCache, result.Blob); err != nil {
   683  				return nil
   684  			}
   685  			return b
   686  		}
   687  	}
   688  }
   689  
   690  func (p *P2PoolApi) getConsensus() *sidechain.Consensus {
   691  	if response, err := p.Client.Get(p.Host + "/sidechain/consensus"); err != nil {
   692  		return nil
   693  	} else {
   694  		defer response.Body.Close()
   695  
   696  		if buf, err := io.ReadAll(response.Body); err != nil {
   697  			return nil
   698  		} else {
   699  			c, _ := sidechain.NewConsensusFromJSON(buf)
   700  
   701  			return c
   702  		}
   703  	}
   704  }
   705  
   706  func (p *P2PoolApi) Consensus() *sidechain.Consensus {
   707  	if c := p.consensus.Load(); c == nil {
   708  		if c = p.getConsensus(); c != nil {
   709  			p.consensus.Store(c)
   710  		}
   711  		return c
   712  	} else {
   713  		return c
   714  	}
   715  }
   716  
   717  func (p *P2PoolApi) Status() *p2pooltypes.P2PoolSideChainStatusResult {
   718  	if response, err := p.Client.Get(p.Host + "/sidechain/status"); err != nil {
   719  		return nil
   720  	} else {
   721  		defer response.Body.Close()
   722  
   723  		if buf, err := io.ReadAll(response.Body); err != nil {
   724  			return nil
   725  		} else {
   726  			result := &p2pooltypes.P2PoolSideChainStatusResult{}
   727  
   728  			if err = utils.UnmarshalJSON(buf, result); err != nil {
   729  				return nil
   730  			}
   731  
   732  			return result
   733  		}
   734  	}
   735  }