github.com/trezor/blockbook@v0.4.1-0.20240328132726-e9a08582ee2c/bchain/coins/pivx/pivxparser.go (about)

     1  package pivx
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/binary"
     6  	"encoding/hex"
     7  	"encoding/json"
     8  	"fmt"
     9  	"io"
    10  	"math/big"
    11  
    12  	"github.com/juju/errors"
    13  	"github.com/martinboehm/btcd/blockchain"
    14  	"github.com/martinboehm/btcd/wire"
    15  	"github.com/martinboehm/btcutil/chaincfg"
    16  	"github.com/trezor/blockbook/bchain"
    17  	"github.com/trezor/blockbook/bchain/coins/btc"
    18  )
    19  
    20  // magic numbers
    21  const (
    22  	MainnetMagic wire.BitcoinNet = 0xe9fdc490
    23  	TestnetMagic wire.BitcoinNet = 0xba657645
    24  
    25  	// Zerocoin op codes
    26  	OP_ZEROCOINMINT  = 0xc1
    27  	OP_ZEROCOINSPEND = 0xc2
    28  )
    29  
    30  // chain parameters
    31  var (
    32  	MainNetParams chaincfg.Params
    33  	TestNetParams chaincfg.Params
    34  )
    35  
    36  func init() {
    37  	// PIVX mainnet Address encoding magics
    38  	MainNetParams = chaincfg.MainNetParams
    39  	MainNetParams.Net = MainnetMagic
    40  	MainNetParams.PubKeyHashAddrID = []byte{30} // starting with 'D'
    41  	MainNetParams.ScriptHashAddrID = []byte{13}
    42  	MainNetParams.PrivateKeyID = []byte{212}
    43  
    44  	// PIVX testnet Address encoding magics
    45  	TestNetParams = chaincfg.TestNet3Params
    46  	TestNetParams.Net = TestnetMagic
    47  	TestNetParams.PubKeyHashAddrID = []byte{139} // starting with 'x' or 'y'
    48  	TestNetParams.ScriptHashAddrID = []byte{19}
    49  	TestNetParams.PrivateKeyID = []byte{239}
    50  }
    51  
    52  // PivXParser handle
    53  type PivXParser struct {
    54  	*btc.BitcoinLikeParser
    55  	baseparser                         *bchain.BaseParser
    56  	BitcoinOutputScriptToAddressesFunc btc.OutputScriptToAddressesFunc
    57  }
    58  
    59  // NewPivXParser returns new PivXParser instance
    60  func NewPivXParser(params *chaincfg.Params, c *btc.Configuration) *PivXParser {
    61  	p := &PivXParser{
    62  		BitcoinLikeParser: btc.NewBitcoinLikeParser(params, c),
    63  		baseparser:        &bchain.BaseParser{},
    64  	}
    65  	p.BitcoinOutputScriptToAddressesFunc = p.OutputScriptToAddressesFunc
    66  	p.OutputScriptToAddressesFunc = p.outputScriptToAddresses
    67  	return p
    68  }
    69  
    70  // GetChainParams contains network parameters for the main PivX network
    71  func GetChainParams(chain string) *chaincfg.Params {
    72  	if !chaincfg.IsRegistered(&MainNetParams) {
    73  		err := chaincfg.Register(&MainNetParams)
    74  		if err == nil {
    75  			err = chaincfg.Register(&TestNetParams)
    76  		}
    77  		if err != nil {
    78  			panic(err)
    79  		}
    80  	}
    81  	switch chain {
    82  	case "test":
    83  		return &TestNetParams
    84  	default:
    85  		return &MainNetParams
    86  	}
    87  }
    88  
    89  // ParseBlock parses raw block to our Block struct
    90  func (p *PivXParser) ParseBlock(b []byte) (*bchain.Block, error) {
    91  	r := bytes.NewReader(b)
    92  	w := wire.MsgBlock{}
    93  	h := wire.BlockHeader{}
    94  	err := h.Deserialize(r)
    95  	if err != nil {
    96  		return nil, errors.Annotatef(err, "Deserialize")
    97  	}
    98  
    99  	if h.Version > 3 && h.Version < 7 {
   100  		// Skip past AccumulatorCheckpoint (block version 4, 5 and 6)
   101  		r.Seek(32, io.SeekCurrent)
   102  	}
   103  
   104  	if h.Version > 7 {
   105  		// Skip new hashFinalSaplingRoot (block version 8 or newer)
   106  		r.Seek(32, io.SeekCurrent)
   107  	}
   108  
   109  	err = p.PivxDecodeTransactions(r, 0, &w)
   110  	if err != nil {
   111  		return nil, errors.Annotatef(err, "DecodeTransactions")
   112  	}
   113  
   114  	txs := make([]bchain.Tx, len(w.Transactions))
   115  	for ti, t := range w.Transactions {
   116  		txs[ti] = p.TxFromMsgTx(t, false)
   117  	}
   118  
   119  	return &bchain.Block{
   120  		BlockHeader: bchain.BlockHeader{
   121  			Size: len(b),
   122  			Time: h.Timestamp.Unix(),
   123  		},
   124  		Txs: txs,
   125  	}, nil
   126  }
   127  
   128  // PackTx packs transaction to byte array using protobuf
   129  func (p *PivXParser) PackTx(tx *bchain.Tx, height uint32, blockTime int64) ([]byte, error) {
   130  	return p.baseparser.PackTx(tx, height, blockTime)
   131  }
   132  
   133  // UnpackTx unpacks transaction from protobuf byte array
   134  func (p *PivXParser) UnpackTx(buf []byte) (*bchain.Tx, uint32, error) {
   135  	return p.baseparser.UnpackTx(buf)
   136  }
   137  
   138  // ParseTx parses byte array containing transaction and returns Tx struct
   139  func (p *PivXParser) ParseTx(b []byte) (*bchain.Tx, error) {
   140  	t := wire.MsgTx{}
   141  	r := bytes.NewReader(b)
   142  	if err := t.Deserialize(r); err != nil {
   143  		return nil, err
   144  	}
   145  	tx := p.TxFromMsgTx(&t, true)
   146  	tx.Hex = hex.EncodeToString(b)
   147  	return &tx, nil
   148  }
   149  
   150  // TxFromMsgTx parses tx and adds handling for OP_ZEROCOINSPEND inputs
   151  func (p *PivXParser) TxFromMsgTx(t *wire.MsgTx, parseAddresses bool) bchain.Tx {
   152  	vin := make([]bchain.Vin, len(t.TxIn))
   153  	for i, in := range t.TxIn {
   154  
   155  		// extra check to not confuse Tx with single OP_ZEROCOINSPEND input as a coinbase Tx
   156  		if !isZeroCoinSpendScript(in.SignatureScript) && blockchain.IsCoinBaseTx(t) {
   157  			vin[i] = bchain.Vin{
   158  				Coinbase: hex.EncodeToString(in.SignatureScript),
   159  				Sequence: in.Sequence,
   160  			}
   161  			break
   162  		}
   163  
   164  		s := bchain.ScriptSig{
   165  			Hex: hex.EncodeToString(in.SignatureScript),
   166  			// missing: Asm,
   167  		}
   168  
   169  		txid := in.PreviousOutPoint.Hash.String()
   170  
   171  		vin[i] = bchain.Vin{
   172  			Txid:      txid,
   173  			Vout:      in.PreviousOutPoint.Index,
   174  			Sequence:  in.Sequence,
   175  			ScriptSig: s,
   176  		}
   177  	}
   178  	vout := make([]bchain.Vout, len(t.TxOut))
   179  	for i, out := range t.TxOut {
   180  		addrs := []string{}
   181  		if parseAddresses {
   182  			addrs, _, _ = p.OutputScriptToAddressesFunc(out.PkScript)
   183  		}
   184  		s := bchain.ScriptPubKey{
   185  			Hex:       hex.EncodeToString(out.PkScript),
   186  			Addresses: addrs,
   187  			// missing: Asm,
   188  			// missing: Type,
   189  		}
   190  		var vs big.Int
   191  		vs.SetInt64(out.Value)
   192  		vout[i] = bchain.Vout{
   193  			ValueSat:     vs,
   194  			N:            uint32(i),
   195  			ScriptPubKey: s,
   196  		}
   197  	}
   198  	tx := bchain.Tx{
   199  		Txid:     t.TxHash().String(),
   200  		Version:  t.Version,
   201  		LockTime: t.LockTime,
   202  		Vin:      vin,
   203  		Vout:     vout,
   204  		// skip: BlockHash,
   205  		// skip: Confirmations,
   206  		// skip: Time,
   207  		// skip: Blocktime,
   208  	}
   209  	return tx
   210  }
   211  
   212  // ParseTxFromJson parses JSON message containing transaction and returns Tx struct
   213  func (p *PivXParser) ParseTxFromJson(msg json.RawMessage) (*bchain.Tx, error) {
   214  	var tx bchain.Tx
   215  	err := json.Unmarshal(msg, &tx)
   216  	if err != nil {
   217  		return nil, err
   218  	}
   219  
   220  	for i := range tx.Vout {
   221  		vout := &tx.Vout[i]
   222  		// convert vout.JsonValue to big.Int and clear it, it is only temporary value used for unmarshal
   223  		vout.ValueSat, err = p.AmountToBigInt(vout.JsonValue)
   224  		if err != nil {
   225  			return nil, err
   226  		}
   227  		vout.JsonValue = ""
   228  
   229  		if vout.ScriptPubKey.Addresses == nil {
   230  			vout.ScriptPubKey.Addresses = []string{}
   231  		}
   232  	}
   233  
   234  	return &tx, nil
   235  }
   236  
   237  // outputScriptToAddresses converts ScriptPubKey to bitcoin addresses
   238  func (p *PivXParser) outputScriptToAddresses(script []byte) ([]string, bool, error) {
   239  	if isZeroCoinSpendScript(script) {
   240  		return []string{"Zerocoin Spend"}, false, nil
   241  	}
   242  	if isZeroCoinMintScript(script) {
   243  		return []string{"Zerocoin Mint"}, false, nil
   244  	}
   245  
   246  	rv, s, _ := p.BitcoinOutputScriptToAddressesFunc(script)
   247  	return rv, s, nil
   248  }
   249  
   250  func (p *PivXParser) GetAddrDescForUnknownInput(tx *bchain.Tx, input int) bchain.AddressDescriptor {
   251  	if len(tx.Vin) > input {
   252  		scriptHex := tx.Vin[input].ScriptSig.Hex
   253  
   254  		if scriptHex != "" {
   255  			script, _ := hex.DecodeString(scriptHex)
   256  			return script
   257  		}
   258  	}
   259  
   260  	s := make([]byte, 10)
   261  	return s
   262  }
   263  
   264  func (p *PivXParser) PivxDecodeTransactions(r *bytes.Reader, pver uint32, blk *wire.MsgBlock) error {
   265  	maxTxPerBlock := uint64((wire.MaxBlockPayload / 10) + 1)
   266  
   267  	txCount, err := wire.ReadVarInt(r, pver)
   268  	if err != nil {
   269  		return err
   270  	}
   271  
   272  	// Prevent more transactions than could possibly fit into a block.
   273  	// It would be possible to cause memory exhaustion and panics without
   274  	// a sane upper bound on this count.
   275  	if txCount > maxTxPerBlock {
   276  		str := fmt.Sprintf("too many transactions to fit into a block "+
   277  			"[count %d, max %d]", txCount, maxTxPerBlock)
   278  		return &wire.MessageError{Func: "utils.decodeTransactions", Description: str}
   279  	}
   280  
   281  	blk.Transactions = make([]*wire.MsgTx, 0, txCount)
   282  	for i := uint64(0); i < txCount; i++ {
   283  		tx := wire.MsgTx{}
   284  
   285  		// read version & seek back to original state
   286  		var version uint32 = 0
   287  		if err = binary.Read(r, binary.LittleEndian, &version); err != nil {
   288  			return err
   289  		}
   290  		if _, err = r.Seek(-4, io.SeekCurrent); err != nil {
   291  			return err
   292  		}
   293  
   294  		txVersion := version & 0xffff
   295  		enc := wire.WitnessEncoding
   296  
   297  		// shielded transactions
   298  		if txVersion >= 3 {
   299  			enc = wire.BaseEncoding
   300  		}
   301  
   302  		err := p.PivxDecode(&tx, r, pver, enc)
   303  		if err != nil {
   304  			return err
   305  		}
   306  		blk.Transactions = append(blk.Transactions, &tx)
   307  	}
   308  
   309  	return nil
   310  }
   311  
   312  func (p *PivXParser) PivxDecode(MsgTx *wire.MsgTx, r *bytes.Reader, pver uint32, enc wire.MessageEncoding) error {
   313  	if err := MsgTx.BtcDecode(r, pver, enc); err != nil {
   314  		return err
   315  	}
   316  
   317  	// extra
   318  	version := uint32(MsgTx.Version)
   319  	txVersion := version & 0xffff
   320  
   321  	if txVersion >= 3 {
   322  		// valueBalance
   323  		r.Seek(9, io.SeekCurrent)
   324  
   325  		vShieldedSpend, err := wire.ReadVarInt(r, 0)
   326  		if err != nil {
   327  			return err
   328  		}
   329  		if vShieldedSpend > 0 {
   330  			r.Seek(int64(vShieldedSpend*384), io.SeekCurrent)
   331  		}
   332  
   333  		vShieldOutput, err := wire.ReadVarInt(r, 0)
   334  		if err != nil {
   335  			return err
   336  		}
   337  		if vShieldOutput > 0 {
   338  			r.Seek(int64(vShieldOutput*948), io.SeekCurrent)
   339  		}
   340  
   341  		// bindingSig
   342  		r.Seek(64, io.SeekCurrent)
   343  	}
   344  
   345  	return nil
   346  }
   347  
   348  // Checks if script is OP_ZEROCOINMINT
   349  func isZeroCoinMintScript(signatureScript []byte) bool {
   350  	return len(signatureScript) > 1 && signatureScript[0] == OP_ZEROCOINMINT
   351  }
   352  
   353  // Checks if script is OP_ZEROCOINSPEND
   354  func isZeroCoinSpendScript(signatureScript []byte) bool {
   355  	return len(signatureScript) >= 100 && signatureScript[0] == OP_ZEROCOINSPEND
   356  }