github.com/trezor/blockbook@v0.4.1-0.20240328132726-e9a08582ee2c/bchain/coins/firo/firoparser.go (about) 1 package firo 2 3 import ( 4 "bytes" 5 "encoding/binary" 6 "encoding/json" 7 "io" 8 9 "github.com/martinboehm/btcd/chaincfg/chainhash" 10 "github.com/martinboehm/btcd/wire" 11 "github.com/martinboehm/btcutil/chaincfg" 12 "github.com/trezor/blockbook/bchain" 13 "github.com/trezor/blockbook/bchain/coins/btc" 14 ) 15 16 const ( 17 OpZeroCoinMint = 0xc1 18 OpZeroCoinSpend = 0xc2 19 OpSigmaMint = 0xc3 20 OpSigmaSpend = 0xc4 21 OpLelantusMint = 0xc5 22 OpLelantusJMint = 0xc6 23 OpLelantusJoinSplit = 0xc7 24 OpLelantusJoinSplitPayload = 0xc9 25 26 MainnetMagic wire.BitcoinNet = 0xe3d9fef1 27 TestnetMagic wire.BitcoinNet = 0xcffcbeea 28 RegtestMagic wire.BitcoinNet = 0xfabfb5da 29 30 GenesisBlockTime = 1414776286 31 SwitchToMTPBlockHeader = 1544443200 32 SwitchToProgPowBlockHeaderTestnet = 1630069200 33 SwitchToProgPowBlockHeaderMainnet = 1635228000 34 MTPL = 64 35 36 SpendTxID = "0000000000000000000000000000000000000000000000000000000000000000" 37 38 TransactionQuorumCommitmentType = 6 39 ) 40 41 var ( 42 MainNetParams chaincfg.Params 43 TestNetParams chaincfg.Params 44 RegtestParams chaincfg.Params 45 ) 46 47 func init() { 48 // mainnet 49 MainNetParams = chaincfg.MainNetParams 50 MainNetParams.Net = MainnetMagic 51 52 MainNetParams.AddressMagicLen = 1 53 MainNetParams.PubKeyHashAddrID = []byte{0x52} 54 MainNetParams.ScriptHashAddrID = []byte{0x07} 55 56 // testnet 57 TestNetParams = chaincfg.TestNet3Params 58 TestNetParams.Net = TestnetMagic 59 60 TestNetParams.AddressMagicLen = 1 61 TestNetParams.PubKeyHashAddrID = []byte{0x41} 62 TestNetParams.ScriptHashAddrID = []byte{0xb2} 63 64 // regtest 65 RegtestParams = chaincfg.RegressionNetParams 66 RegtestParams.Net = RegtestMagic 67 } 68 69 // FiroParser handle 70 type FiroParser struct { 71 *btc.BitcoinLikeParser 72 } 73 74 // NewFiroParser returns new FiroParser instance 75 func NewFiroParser(params *chaincfg.Params, c *btc.Configuration) *FiroParser { 76 return &FiroParser{ 77 BitcoinLikeParser: btc.NewBitcoinLikeParser(params, c), 78 } 79 } 80 81 // GetChainParams contains network parameters for the main Firo network, 82 // the regression test Firo network, the test Firo network and 83 // the simulation test Firo network, in this order 84 func GetChainParams(chain string) *chaincfg.Params { 85 if !chaincfg.IsRegistered(&MainNetParams) { 86 err := chaincfg.Register(&MainNetParams) 87 if err == nil { 88 err = chaincfg.Register(&TestNetParams) 89 } 90 if err == nil { 91 err = chaincfg.Register(&RegtestParams) 92 } 93 if err != nil { 94 panic(err) 95 } 96 } 97 switch chain { 98 case "test": 99 return &TestNetParams 100 case "regtest": 101 return &RegtestParams 102 default: 103 return &MainNetParams 104 } 105 } 106 107 // GetAddressesFromAddrDesc returns addresses for given address descriptor with flag if the addresses are searchable 108 func (p *FiroParser) GetAddressesFromAddrDesc(addrDesc bchain.AddressDescriptor) ([]string, bool, error) { 109 110 if len(addrDesc) > 0 { 111 switch addrDesc[0] { 112 case OpZeroCoinMint: 113 return []string{"Zeromint"}, false, nil 114 case OpZeroCoinSpend: 115 return []string{"Zerospend"}, false, nil 116 case OpSigmaMint: 117 return []string{"Sigmamint"}, false, nil 118 case OpSigmaSpend: 119 return []string{"Sigmaspend"}, false, nil 120 case OpLelantusMint: 121 return []string{"LelantusMint"}, false, nil 122 case OpLelantusJMint: 123 return []string{"LelantusJMint"}, false, nil 124 case OpLelantusJoinSplit: 125 return []string{"LelantusJoinSplit"}, false, nil 126 case OpLelantusJoinSplitPayload: 127 return []string{"LelantusJoinSplit"}, false, nil 128 } 129 } 130 131 return p.OutputScriptToAddressesFunc(addrDesc) 132 } 133 134 // PackTx packs transaction to byte array using protobuf 135 func (p *FiroParser) PackTx(tx *bchain.Tx, height uint32, blockTime int64) ([]byte, error) { 136 return p.BaseParser.PackTx(tx, height, blockTime) 137 } 138 139 // UnpackTx unpacks transaction from protobuf byte array 140 func (p *FiroParser) UnpackTx(buf []byte) (*bchain.Tx, uint32, error) { 141 return p.BaseParser.UnpackTx(buf) 142 } 143 144 // TxFromFiroMsgTx converts bitcoin wire Tx to bchain.Tx 145 func (p *FiroParser) TxFromFiroMsgTx(t *FiroMsgTx, parseAddresses bool) bchain.Tx { 146 btx := p.TxFromMsgTx(&t.MsgTx, parseAddresses) 147 148 // NOTE: wire.MsgTx.TxHash() doesn't include extra 149 btx.Txid = t.TxHash().String() 150 151 return btx 152 } 153 154 // ParseBlock parses raw block to our Block struct 155 func (p *FiroParser) ParseBlock(b []byte) (*bchain.Block, error) { 156 reader := bytes.NewReader(b) 157 158 // parse standard block header first 159 header, err := parseBlockHeader(reader) 160 if err != nil { 161 return nil, err 162 } 163 164 // then ProgPow or MTP header 165 if isProgPow(header, p.Params.Net == TestnetMagic) { 166 progPowHeader := ProgPowBlockHeader{} 167 168 // header 169 err = binary.Read(reader, binary.LittleEndian, &progPowHeader) 170 if err != nil { 171 return nil, err 172 } 173 } else { 174 if isMTP(header) { 175 mtpHeader := MTPBlockHeader{} 176 mtpHashDataRoot := MTPHashDataRoot{} 177 178 // header 179 err = binary.Read(reader, binary.LittleEndian, &mtpHeader) 180 if err != nil { 181 return nil, err 182 } 183 184 // hash data root 185 err = binary.Read(reader, binary.LittleEndian, &mtpHashDataRoot) 186 if err != nil { 187 return nil, err 188 } 189 190 isAllZero := true 191 for i := 0; i < 16; i++ { 192 if mtpHashDataRoot.HashRootMTP[i] != 0 { 193 isAllZero = false 194 break 195 } 196 } 197 198 199 if !isAllZero { 200 // hash data 201 mtpHashData := MTPHashData{} 202 err = binary.Read(reader, binary.LittleEndian, &mtpHashData) 203 if err != nil { 204 return nil, err 205 } 206 207 // proof 208 for i := 0; i < MTPL*3; i++ { 209 var numberProofBlocks uint8 210 211 err = binary.Read(reader, binary.LittleEndian, &numberProofBlocks) 212 if err != nil { 213 return nil, err 214 } 215 216 for j := uint8(0); j < numberProofBlocks; j++ { 217 var mtpData [16]uint8 218 219 err = binary.Read(reader, binary.LittleEndian, mtpData[:]) 220 if err != nil { 221 return nil, err 222 } 223 } 224 } 225 } 226 } 227 } 228 229 // parse txs 230 ntx, err := wire.ReadVarInt(reader, 0) 231 if err != nil { 232 return nil, err 233 } 234 235 txs := make([]bchain.Tx, ntx) 236 237 for i := uint64(0); i < ntx; i++ { 238 tx := FiroMsgTx{} 239 240 // read version and seek back 241 var version uint32 = 0 242 if err = binary.Read(reader, binary.LittleEndian, &version); err != nil { 243 return nil, err 244 } 245 246 if _, err = reader.Seek(-4, io.SeekCurrent); err != nil { 247 return nil, err 248 } 249 250 txVersion := version & 0xffff 251 txType := (version >> 16) & 0xffff 252 253 enc := wire.WitnessEncoding 254 255 // transaction quorum commitment could not be parsed with witness flag 256 if txVersion == 3 && txType == TransactionQuorumCommitmentType { 257 enc = wire.BaseEncoding 258 } 259 260 if err = tx.FiroDecode(reader, 0, enc); err != nil { 261 return nil, err 262 } 263 264 btx := p.TxFromFiroMsgTx(&tx, false) 265 266 if err = p.parseFiroTx(&btx); err != nil { 267 return nil, err 268 } 269 270 txs[i] = btx 271 } 272 273 return &bchain.Block{ 274 BlockHeader: bchain.BlockHeader{ 275 Size: len(b), 276 Time: header.Timestamp.Unix(), 277 }, 278 Txs: txs, 279 }, nil 280 } 281 282 // ParseTxFromJson parses JSON message containing transaction and returns Tx struct 283 func (p *FiroParser) ParseTxFromJson(msg json.RawMessage) (*bchain.Tx, error) { 284 var tx bchain.Tx 285 err := json.Unmarshal(msg, &tx) 286 if err != nil { 287 return nil, err 288 } 289 290 for i := range tx.Vout { 291 vout := &tx.Vout[i] 292 // convert vout.JsonValue to big.Int and clear it, it is only temporary value used for unmarshal 293 vout.ValueSat, err = p.AmountToBigInt(vout.JsonValue) 294 if err != nil { 295 return nil, err 296 } 297 vout.JsonValue = "" 298 } 299 300 p.parseFiroTx(&tx) 301 302 return &tx, nil 303 } 304 305 func (p *FiroParser) parseFiroTx(tx *bchain.Tx) error { 306 for i := range tx.Vin { 307 vin := &tx.Vin[i] 308 309 // FIXME: right now we treat zerocoin spend vin as coinbase 310 // change this after blockbook support special type of vin 311 if vin.Txid == SpendTxID { 312 vin.Coinbase = vin.Txid 313 vin.Txid = "" 314 vin.Sequence = 0 315 vin.Vout = 0 316 } 317 } 318 319 return nil 320 } 321 322 func parseBlockHeader(r io.Reader) (*wire.BlockHeader, error) { 323 h := &wire.BlockHeader{} 324 err := h.Deserialize(r) 325 return h, err 326 } 327 328 func isMTP(h *wire.BlockHeader) bool { 329 epoch := h.Timestamp.Unix() 330 331 // the genesis block never be MTP block 332 return epoch > GenesisBlockTime && epoch >= SwitchToMTPBlockHeader 333 } 334 335 func isProgPow(h *wire.BlockHeader, isTestNet bool) bool { 336 epoch := h.Timestamp.Unix() 337 338 // the genesis block never be MTP block 339 return isTestNet && epoch >= SwitchToProgPowBlockHeaderTestnet || !isTestNet && epoch >= SwitchToProgPowBlockHeaderMainnet 340 } 341 342 type MTPHashDataRoot struct { 343 HashRootMTP [16]uint8 344 } 345 346 type MTPHashData struct { 347 BlockMTP [128][128]uint64 348 } 349 350 type MTPBlockHeader struct { 351 VersionMTP int32 352 MTPHashValue chainhash.Hash 353 Reserved1 chainhash.Hash 354 Reserved2 chainhash.Hash 355 } 356 357 type ProgPowBlockHeader struct { 358 Nonce64 int64 359 MixHash chainhash.Hash 360 }