git.gammaspectra.live/P2Pool/consensus@v0.0.0-20240403173234-a039820b20c9/p2pool/sidechain/blobcache.go (about)

     1  package sidechain
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/binary"
     6  	"git.gammaspectra.live/P2Pool/consensus/monero"
     7  	"git.gammaspectra.live/P2Pool/consensus/monero/address"
     8  	"git.gammaspectra.live/P2Pool/consensus/types"
     9  	"git.gammaspectra.live/P2Pool/consensus/utils"
    10  	"slices"
    11  )
    12  
    13  // BlockSaveEpochSize could be up to 256?
    14  const BlockSaveEpochSize = 32
    15  
    16  const (
    17  	BlockSaveOptionTemplate                    = 1 << 0
    18  	BlockSaveOptionDeterministicPrivateKeySeed = 1 << 1
    19  	BlockSaveOptionDeterministicBlobs          = 1 << 2
    20  	BlockSaveOptionUncles                      = 1 << 3
    21  
    22  	BlockSaveFieldSizeInBits = 8
    23  
    24  	BlockSaveOffsetAddress    = BlockSaveFieldSizeInBits
    25  	BlockSaveOffsetMainFields = BlockSaveFieldSizeInBits * 2
    26  )
    27  
    28  func (c *SideChain) uncompressedBlockId(block *PoolBlock) []byte {
    29  	templateId := block.SideTemplateId(c.Consensus())
    30  	buf := make([]byte, 0, 4+len(templateId))
    31  	return append([]byte("RAW\x00"), buf...)
    32  }
    33  
    34  func (c *SideChain) compressedBlockId(block *PoolBlock) []byte {
    35  	templateId := block.SideTemplateId(c.Consensus())
    36  	buf := make([]byte, 0, 4+len(templateId))
    37  	return append([]byte("PAK\x00"), buf...)
    38  }
    39  
    40  func (c *SideChain) saveBlock(block *PoolBlock) {
    41  	go func() {
    42  		c.server.Store(block)
    43  
    44  		return
    45  
    46  		//TODO: make this a worker with a queue?
    47  
    48  		if !block.Verified.Load() || block.Invalid.Load() {
    49  			blob, _ := block.MarshalBinary()
    50  
    51  			if err := c.server.SetBlob(c.uncompressedBlockId(block), blob); err != nil {
    52  				utils.Errorf("", "error saving %s: %s", block.SideTemplateId(c.Consensus()).String(), err.Error())
    53  			}
    54  			return
    55  		}
    56  		if block.Depth.Load() >= c.Consensus().ChainWindowSize {
    57  			//TODO: check for compressed blob existence before saving uncompressed
    58  			blob, _ := block.MarshalBinary()
    59  
    60  			if err := c.server.SetBlob(c.uncompressedBlockId(block), blob); err != nil {
    61  				utils.Errorf("", "error saving %s: %s", block.SideTemplateId(c.Consensus()).String(), err.Error())
    62  			}
    63  			return
    64  		}
    65  		c.sidechainLock.RLock()
    66  		defer c.sidechainLock.RUnlock()
    67  
    68  		calculatedOutputs, _ := c.calculateOutputs(block)
    69  		calcBlob, _ := calculatedOutputs.MarshalBinary()
    70  		blockBlob, _ := block.Main.Coinbase.Outputs.MarshalBinary()
    71  		storeBlob := bytes.Compare(calcBlob, blockBlob) != 0
    72  
    73  		fullBlockTemplateHeight := block.Side.Height - (block.Side.Height % BlockSaveEpochSize)
    74  
    75  		minerAddressOffset := uint64(0)
    76  
    77  		mainFieldsOffset := uint64(0)
    78  
    79  		parent := c.getParent(block)
    80  
    81  		//only store keys when not deterministic
    82  		isDeterministicPrivateKeySeed := parent != nil && c.isPoolBlockTransactionKeyIsDeterministic(block)
    83  
    84  		if isDeterministicPrivateKeySeed && block.ShareVersion() > ShareVersion_V1 {
    85  			expectedSeed := parent.Side.CoinbasePrivateKeySeed
    86  			if parent.Main.PreviousId != block.Main.PreviousId {
    87  				expectedSeed = parent.CalculateTransactionPrivateKeySeed()
    88  			}
    89  			if block.Side.CoinbasePrivateKeySeed != expectedSeed {
    90  				isDeterministicPrivateKeySeed = false
    91  			}
    92  		}
    93  
    94  		blob := make([]byte, 0, 4096*2)
    95  
    96  		var blockFlags uint64
    97  
    98  		if isDeterministicPrivateKeySeed {
    99  			blockFlags |= BlockSaveOptionDeterministicPrivateKeySeed
   100  		}
   101  
   102  		if !storeBlob {
   103  			blockFlags |= BlockSaveOptionDeterministicBlobs
   104  		}
   105  
   106  		transactionOffsets := make([]uint64, len(block.Main.Transactions))
   107  		transactionOffsetsStored := 0
   108  
   109  		parentTransactions := make([]types.Hash, 0, 512)
   110  
   111  		if block.Side.Height != fullBlockTemplateHeight {
   112  			tmp := parent
   113  			for offset := uint64(1); tmp != nil && offset < BlockSaveEpochSize; offset++ {
   114  				if !tmp.Verified.Load() || tmp.Invalid.Load() {
   115  					break
   116  				}
   117  
   118  				if minerAddressOffset == 0 && tmp.Side.PublicKey == block.Side.PublicKey {
   119  					minerAddressOffset = block.Side.Height - tmp.Side.Height
   120  				}
   121  
   122  				if mainFieldsOffset == 0 && tmp.Main.Coinbase.Version == block.Main.Coinbase.Version && tmp.Main.Coinbase.UnlockTime == block.Main.Coinbase.UnlockTime && tmp.Main.Coinbase.GenHeight == block.Main.Coinbase.GenHeight && tmp.Main.PreviousId == block.Main.PreviousId && tmp.Main.MajorVersion == block.Main.MajorVersion && tmp.Main.MinorVersion == block.Main.MinorVersion {
   123  					mainFieldsOffset = block.Side.Height - tmp.Side.Height
   124  				}
   125  
   126  				if transactionOffsetsStored != len(transactionOffsets) {
   127  					//store last offset to not spend time looking on already checked sections
   128  					prevLen := len(parentTransactions)
   129  					for _, txHash := range tmp.Main.Transactions {
   130  						//if it doesn't exist yet
   131  						if slices.Index(parentTransactions, txHash) == -1 {
   132  							parentTransactions = append(parentTransactions, txHash)
   133  						}
   134  					}
   135  
   136  					for tIndex, tOffset := range transactionOffsets {
   137  						if tOffset == 0 {
   138  							if foundIndex := slices.Index(parentTransactions[prevLen:], block.Main.Transactions[tIndex]); foundIndex != -1 {
   139  								transactionOffsets[tIndex] = uint64(prevLen) + uint64(foundIndex) + 1
   140  								transactionOffsetsStored++
   141  							}
   142  						}
   143  					}
   144  				}
   145  
   146  				// early exit
   147  				if tmp.Side.Height == fullBlockTemplateHeight || (transactionOffsetsStored == len(transactionOffsets) && mainFieldsOffset != 0 && minerAddressOffset != 0) {
   148  					break
   149  				}
   150  				tmp = c.getParent(tmp)
   151  			}
   152  		}
   153  
   154  		if parent == nil || block.Side.Height == fullBlockTemplateHeight { //store full blocks every once in a while, or when there is no parent block
   155  			blockFlags |= BlockSaveOptionTemplate
   156  		} else {
   157  			if minerAddressOffset > 0 {
   158  				blockFlags |= minerAddressOffset << BlockSaveOffsetAddress
   159  			}
   160  			if mainFieldsOffset > 0 {
   161  				blockFlags |= mainFieldsOffset << BlockSaveOffsetMainFields
   162  			}
   163  		}
   164  
   165  		if len(block.Side.Uncles) > 0 {
   166  			blockFlags |= BlockSaveOptionUncles
   167  		}
   168  
   169  		blob = binary.AppendUvarint(blob, blockFlags)
   170  
   171  		// side data
   172  
   173  		// miner address
   174  		if (blockFlags&BlockSaveOffsetAddress) == 0 || (blockFlags&BlockSaveOptionTemplate) != 0 {
   175  			blob = append(blob, block.Side.PublicKey[address.PackedAddressSpend][:]...)
   176  			blob = append(blob, block.Side.PublicKey[address.PackedAddressView][:]...)
   177  		} else {
   178  			blob = binary.AppendUvarint(blob, minerAddressOffset)
   179  		}
   180  
   181  		// private key seed, if needed
   182  		if (blockFlags&BlockSaveOptionDeterministicPrivateKeySeed) == 0 || (block.ShareVersion() > ShareVersion_V1 && (blockFlags&BlockSaveOptionTemplate) != 0) {
   183  			blob = append(blob, block.Side.CoinbasePrivateKeySeed[:]...)
   184  			//public may be needed on invalid - TODO check
   185  			//blob = append(blob, block.CoinbaseExtra(SideCoinbasePublicKey)...)
   186  		}
   187  
   188  		// parent
   189  		blob = append(blob, block.Side.Parent[:]...)
   190  
   191  		// uncles
   192  		if (blockFlags & BlockSaveOptionUncles) > 0 {
   193  			blob = binary.AppendUvarint(blob, uint64(len(block.Side.Uncles)))
   194  			for _, uncleId := range block.Side.Uncles {
   195  				blob = append(blob, uncleId[:]...)
   196  			}
   197  		}
   198  
   199  		//no height saved except on templates
   200  		if (blockFlags & BlockSaveOptionTemplate) != 0 {
   201  			blob = binary.AppendUvarint(blob, block.Side.Height)
   202  		}
   203  
   204  		//difficulty
   205  		if (blockFlags & BlockSaveOptionTemplate) != 0 {
   206  			blob = binary.AppendUvarint(blob, block.Side.Difficulty.Lo)
   207  			blob = binary.AppendUvarint(blob, block.Side.CumulativeDifficulty.Lo)
   208  			blob = binary.AppendUvarint(blob, block.Side.CumulativeDifficulty.Hi)
   209  		} else {
   210  			//store signed difference
   211  			blob = binary.AppendVarint(blob, int64(block.Side.Difficulty.Lo)-int64(parent.Side.Difficulty.Lo))
   212  		}
   213  
   214  		// main data
   215  		// header
   216  		if (blockFlags&BlockSaveOffsetMainFields) == 0 || (blockFlags&BlockSaveOptionTemplate) != 0 {
   217  			blob = append(blob, block.Main.MajorVersion)
   218  			blob = append(blob, block.Main.MinorVersion)
   219  			//timestamp is used as difference only
   220  			blob = binary.AppendUvarint(blob, block.Main.Timestamp)
   221  			blob = append(blob, block.Main.PreviousId[:]...)
   222  			blob = binary.LittleEndian.AppendUint32(blob, block.Main.Nonce)
   223  		} else {
   224  			blob = binary.AppendUvarint(blob, mainFieldsOffset)
   225  			//store signed difference
   226  			blob = binary.AppendVarint(blob, int64(block.Main.Timestamp)-int64(parent.Main.Timestamp))
   227  			blob = binary.LittleEndian.AppendUint32(blob, block.Main.Nonce)
   228  		}
   229  
   230  		// coinbase
   231  		if (blockFlags&BlockSaveOffsetMainFields) == 0 || (blockFlags&BlockSaveOptionTemplate) != 0 {
   232  			blob = append(blob, block.Main.Coinbase.Version)
   233  			blob = binary.AppendUvarint(blob, block.Main.Coinbase.UnlockTime)
   234  			blob = binary.AppendUvarint(blob, block.Main.Coinbase.GenHeight)
   235  			blob = binary.AppendUvarint(blob, block.Main.Coinbase.TotalReward-monero.TailEmissionReward)
   236  			blob = binary.AppendUvarint(blob, uint64(len(block.CoinbaseExtra(SideExtraNonce))))
   237  			blob = append(blob, block.CoinbaseExtra(SideExtraNonce)...)
   238  		} else {
   239  			blob = binary.AppendUvarint(blob, mainFieldsOffset)
   240  			//store signed difference with parent, not template
   241  			blob = binary.AppendVarint(blob, int64(block.Main.Timestamp)-int64(parent.Main.Timestamp))
   242  			blob = binary.LittleEndian.AppendUint32(blob, block.Main.Nonce)
   243  			blob = binary.AppendVarint(blob, int64(block.Main.Coinbase.TotalReward)-int64(parent.Main.Coinbase.TotalReward))
   244  			blob = binary.AppendUvarint(blob, uint64(len(block.CoinbaseExtra(SideExtraNonce))))
   245  			blob = append(blob, block.CoinbaseExtra(SideExtraNonce)...)
   246  		}
   247  
   248  		// coinbase blob, if needed
   249  		if (blockFlags & BlockSaveOptionDeterministicBlobs) == 0 {
   250  			blob = append(blob, blockBlob...)
   251  		}
   252  
   253  		//transactions
   254  		if (blockFlags & BlockSaveOptionTemplate) != 0 {
   255  			blob = binary.AppendUvarint(blob, uint64(len(block.Main.Transactions)))
   256  			for _, txId := range block.Main.Transactions {
   257  				blob = append(blob, txId[:]...)
   258  			}
   259  		} else {
   260  			blob = binary.AppendUvarint(blob, uint64(len(block.Main.Transactions)))
   261  			for i, v := range transactionOffsets {
   262  				blob = binary.AppendUvarint(blob, v)
   263  				if v == 0 {
   264  					blob = append(blob, block.Main.Transactions[i][:]...)
   265  				}
   266  			}
   267  		}
   268  
   269  		fullBlob, _ := block.MarshalBinary()
   270  		prunedBlob, _ := block.MarshalBinaryFlags(true, false)
   271  		compactBlob, _ := block.MarshalBinaryFlags(true, true)
   272  
   273  		if (blockFlags & BlockSaveOptionTemplate) != 0 {
   274  			utils.Logf("", "compress block (template) %s in compressed %d bytes, full %d bytes, pruned %d bytes, compact %d bytes", block.SideTemplateId(c.Consensus()).String(), len(blob), len(fullBlob), len(prunedBlob), len(compactBlob))
   275  		} else {
   276  			utils.Logf("", "compress block %s in compressed %d bytes, full %d bytes, pruned %d bytes, compact %d bytes", block.SideTemplateId(c.Consensus()).String(), len(blob), len(fullBlob), len(prunedBlob), len(compactBlob))
   277  		}
   278  
   279  		if err := c.server.SetBlob(c.compressedBlockId(block), blob); err != nil {
   280  			utils.Logf("error saving %s: %s", block.SideTemplateId(c.Consensus()).String(), err.Error())
   281  		}
   282  
   283  	}()
   284  }