github.com/cryptohub-digital/blockbook-fork@v0.0.0-20230713133354-673c927af7f1/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/cryptohub-digital/blockbook-fork/bchain"
    10  	"github.com/cryptohub-digital/blockbook-fork/bchain/coins/btc"
    11  	"github.com/martinboehm/btcd/chaincfg/chainhash"
    12  	"github.com/martinboehm/btcd/wire"
    13  	"github.com/martinboehm/btcutil/chaincfg"
    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  			if !isAllZero {
   199  				// hash data
   200  				mtpHashData := MTPHashData{}
   201  				err = binary.Read(reader, binary.LittleEndian, &mtpHashData)
   202  				if err != nil {
   203  					return nil, err
   204  				}
   205  
   206  				// proof
   207  				for i := 0; i < MTPL*3; i++ {
   208  					var numberProofBlocks uint8
   209  
   210  					err = binary.Read(reader, binary.LittleEndian, &numberProofBlocks)
   211  					if err != nil {
   212  						return nil, err
   213  					}
   214  
   215  					for j := uint8(0); j < numberProofBlocks; j++ {
   216  						var mtpData [16]uint8
   217  
   218  						err = binary.Read(reader, binary.LittleEndian, mtpData[:])
   219  						if err != nil {
   220  							return nil, err
   221  						}
   222  					}
   223  				}
   224  			}
   225  		}
   226  	}
   227  
   228  	// parse txs
   229  	ntx, err := wire.ReadVarInt(reader, 0)
   230  	if err != nil {
   231  		return nil, err
   232  	}
   233  
   234  	txs := make([]bchain.Tx, ntx)
   235  
   236  	for i := uint64(0); i < ntx; i++ {
   237  		tx := FiroMsgTx{}
   238  
   239  		// read version and seek back
   240  		var version uint32 = 0
   241  		if err = binary.Read(reader, binary.LittleEndian, &version); err != nil {
   242  			return nil, err
   243  		}
   244  
   245  		if _, err = reader.Seek(-4, io.SeekCurrent); err != nil {
   246  			return nil, err
   247  		}
   248  
   249  		txVersion := version & 0xffff
   250  		txType := (version >> 16) & 0xffff
   251  
   252  		enc := wire.WitnessEncoding
   253  
   254  		// transaction quorum commitment could not be parsed with witness flag
   255  		if txVersion == 3 && txType == TransactionQuorumCommitmentType {
   256  			enc = wire.BaseEncoding
   257  		}
   258  
   259  		if err = tx.FiroDecode(reader, 0, enc); err != nil {
   260  			return nil, err
   261  		}
   262  
   263  		btx := p.TxFromFiroMsgTx(&tx, false)
   264  
   265  		if err = p.parseFiroTx(&btx); err != nil {
   266  			return nil, err
   267  		}
   268  
   269  		txs[i] = btx
   270  	}
   271  
   272  	return &bchain.Block{
   273  		BlockHeader: bchain.BlockHeader{
   274  			Size: len(b),
   275  			Time: header.Timestamp.Unix(),
   276  		},
   277  		Txs: txs,
   278  	}, nil
   279  }
   280  
   281  // ParseTxFromJson parses JSON message containing transaction and returns Tx struct
   282  func (p *FiroParser) ParseTxFromJson(msg json.RawMessage) (*bchain.Tx, error) {
   283  	var tx bchain.Tx
   284  	err := json.Unmarshal(msg, &tx)
   285  	if err != nil {
   286  		return nil, err
   287  	}
   288  
   289  	for i := range tx.Vout {
   290  		vout := &tx.Vout[i]
   291  		// convert vout.JsonValue to big.Int and clear it, it is only temporary value used for unmarshal
   292  		vout.ValueSat, err = p.AmountToBigInt(vout.JsonValue)
   293  		if err != nil {
   294  			return nil, err
   295  		}
   296  		vout.JsonValue = ""
   297  	}
   298  
   299  	p.parseFiroTx(&tx)
   300  
   301  	return &tx, nil
   302  }
   303  
   304  func (p *FiroParser) parseFiroTx(tx *bchain.Tx) error {
   305  	for i := range tx.Vin {
   306  		vin := &tx.Vin[i]
   307  
   308  		// FIXME: right now we treat zerocoin spend vin as coinbase
   309  		// change this after blockbook support special type of vin
   310  		if vin.Txid == SpendTxID {
   311  			vin.Coinbase = vin.Txid
   312  			vin.Txid = ""
   313  			vin.Sequence = 0
   314  			vin.Vout = 0
   315  		}
   316  	}
   317  
   318  	return nil
   319  }
   320  
   321  func parseBlockHeader(r io.Reader) (*wire.BlockHeader, error) {
   322  	h := &wire.BlockHeader{}
   323  	err := h.Deserialize(r)
   324  	return h, err
   325  }
   326  
   327  func isMTP(h *wire.BlockHeader) bool {
   328  	epoch := h.Timestamp.Unix()
   329  
   330  	// the genesis block never be MTP block
   331  	return epoch > GenesisBlockTime && epoch >= SwitchToMTPBlockHeader
   332  }
   333  
   334  func isProgPow(h *wire.BlockHeader, isTestNet bool) bool {
   335  	epoch := h.Timestamp.Unix()
   336  
   337  	// the genesis block never be MTP block
   338  	return isTestNet && epoch >= SwitchToProgPowBlockHeaderTestnet || !isTestNet && epoch >= SwitchToProgPowBlockHeaderMainnet
   339  }
   340  
   341  type MTPHashDataRoot struct {
   342  	HashRootMTP [16]uint8
   343  }
   344  
   345  type MTPHashData struct {
   346  	BlockMTP [128][128]uint64
   347  }
   348  
   349  type MTPBlockHeader struct {
   350  	VersionMTP   int32
   351  	MTPHashValue chainhash.Hash
   352  	Reserved1    chainhash.Hash
   353  	Reserved2    chainhash.Hash
   354  }
   355  
   356  type ProgPowBlockHeader struct {
   357  	Nonce64 int64
   358  	MixHash chainhash.Hash
   359  }