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 }