From 7ce9b1d8693610a3945258e32ef64129dd7b665b Mon Sep 17 00:00:00 2001 From: Jonathan Chappelow Date: Fri, 27 May 2022 22:54:28 -0500 Subject: [PATCH] deserialize all the things --- dex/networks/ltc/block.go | 88 ++- dex/networks/ltc/block2215586testnet4.dat | Bin 0 -> 18915 bytes dex/networks/ltc/block_online_test.go | 6 +- dex/networks/ltc/block_test.go | 19 +- dex/networks/ltc/testnet4Block2215586.dat | Bin 0 -> 18915 bytes dex/networks/ltc/tx.go | 556 ++++++++++++++++++ ...7ac355249ef5751800c4dd927ddf57d9db2_mp.dat | Bin 0 -> 2987 bytes ...e9a254565c0dc9ce54917d9111267547fcde03.dat | Bin 0 -> 203 bytes ...254565c0dc9ce54917d9111267547fcde03_mp.dat | Bin 0 -> 2203 bytes dex/networks/ltc/tx_test.go | 130 ++++ ...e03139f924eaf685ae964b3076594d65349_mp.dat | Bin 0 -> 1333 bytes ...52c4212f0ae77e3c8d92c9cb85c29f4dc1f47c.dat | Bin 0 -> 169 bytes ...0e7a7b07e6dc76d2ba95a33710db02a0049_mp.dat | Bin 0 -> 2203 bytes go.mod | 2 + go.sum | 5 + 15 files changed, 774 insertions(+), 32 deletions(-) create mode 100644 dex/networks/ltc/block2215586testnet4.dat create mode 100644 dex/networks/ltc/testnet4Block2215586.dat create mode 100644 dex/networks/ltc/tx.go create mode 100644 dex/networks/ltc/tx62e17562a697e3167e2b68bce5a927ac355249ef5751800c4dd927ddf57d9db2_mp.dat create mode 100644 dex/networks/ltc/tx8b8343978dbef95d54da796977e9a254565c0dc9ce54917d9111267547fcde03.dat create mode 100644 dex/networks/ltc/tx8b8343978dbef95d54da796977e9a254565c0dc9ce54917d9111267547fcde03_mp.dat create mode 100644 dex/networks/ltc/tx_test.go create mode 100644 dex/networks/ltc/txcc202c44db4d6faec57f42566c6b1e03139f924eaf685ae964b3076594d65349_mp.dat create mode 100644 dex/networks/ltc/txde22f4de7116b8482a691cc5e552c4212f0ae77e3c8d92c9cb85c29f4dc1f47c.dat create mode 100644 dex/networks/ltc/txe6f8fdb15b27e603adcfa5aa4107a0e7a7b07e6dc76d2ba95a33710db02a0049_mp.dat diff --git a/dex/networks/ltc/block.go b/dex/networks/ltc/block.go index d23dfbed63..5731cd4f52 100644 --- a/dex/networks/ltc/block.go +++ b/dex/networks/ltc/block.go @@ -15,12 +15,58 @@ const ( // mwebVer is the bit of the block header's version that indicates the // presence of a MWEB. mwebVer = 0x20000000 // 1 << 29 - - // EmptyTxHash is the txid of an empty wire.MsgTx that may be included in a - // block with a MW integration transaction (HogEx). - EmptyTxHash = "f702453dd03b0f055e5437d76128141803984fb10acb85fc3b2184fae2f3fa78" ) +func parseMWEB(blk io.Reader) error { + dec := newDecoder(blk) + // src/mweb/mweb_models.h - struct Block + // "A convenience wrapper around a possibly-null extension block."" + // OptionalPtr around a mw::Block. Read the option byte: + hasMWEB, err := dec.readByte() + if err != nil { + return fmt.Errorf("failed to check MWEB option byte: %w", err) + } + if hasMWEB == 0 { + return nil + } + + // src/libmw/include/mw/models/block/Block.h - class Block + // (1) Header and (2) TxBody + + // src/libmw/include/mw/models/block/Header.h - class Header + // height + if _, err = dec.readVLQ(); err != nil { + return fmt.Errorf("failed to decode MWEB height: %w", err) + } + + // 3x Hash + 2x BlindingFactor + if err = dec.discardBytes(32*3 + 32*2); err != nil { + return fmt.Errorf("failed to decode MWEB junk: %w", err) + } + + // Number of TXOs: outputMMRSize + if _, err = dec.readVLQ(); err != nil { + return fmt.Errorf("failed to decode TXO count: %w", err) + } + + // Number of kernels: kernelMMRSize + if _, err = dec.readVLQ(); err != nil { + return fmt.Errorf("failed to decode kernel count: %w", err) + } + + // TxBody + _, err = dec.readMWTXBody() + if err != nil { + return fmt.Errorf("failed to decode MWEB tx: %w", err) + } + // if len(kern0) > 0 { + // mwebTxID := chainhash.Hash(blake3.Sum256(kern0)) + // fmt.Println(mwebTxID.String()) + // } + + return nil +} + // DeserializeBlock decodes the bytes of a serialized Litecoin block. This // function exists because MWEB changes both the block and transaction // serializations. Blocks may have a MW "extension block" for "peg-out" @@ -30,9 +76,7 @@ const ( // (also known as a HogEx transaction), all still in the regular LTC block. The // peg-in txns decode correctly, but the integration tx is a special transaction // with the witness tx flag with bit 3 set (8), which prevents correct -// wire.MsgTx deserialization. These integration transactions are replaced with -// a dummy MsgTx recognizable by EmptyTxHash. We make no effort to decode the EW -// or any of the peg-out transactions, both of which have unique encodings. +// wire.MsgTx deserialization. // Refs: // https://github.com/litecoin-project/lips/blob/master/lip-0002.mediawiki#PegOut_Transactions // https://github.com/litecoin-project/lips/blob/master/lip-0003.mediawiki#Specification @@ -48,36 +92,32 @@ func DeserializeBlock(blk io.Reader) (*wire.MsgBlock, error) { return nil, fmt.Errorf("failed to deserialize block header: %w", err) } - // May contain a MWEB *after* the txns. - containsMWEB := hdr.Version&mwebVer != 0 - // This block's transactions txnCount, err := wire.ReadVarInt(blk, 0) if err != nil { return nil, fmt.Errorf("failed to parse transaction count: %w", err) } - // We can only decode the canonical txns, not the integ/hogex txn at the end - // or the mw peg-in txs in the EB. + // We can only decode the canonical txns, not the mw peg-in txs in the EB. + var hasHogEx bool txns := make([]*wire.MsgTx, 0, int(txnCount)) for i := 0; i < cap(txns); i++ { - msgTx := &wire.MsgTx{} - err = msgTx.Deserialize(blk) + msgTx, err := DeserializeTx(blk) if err != nil { - if !containsMWEB { - return nil, fmt.Errorf("failed to deserialize transaction %d of %d in block %v: %w", - i+1, txnCount, hdr.BlockHash(), err) - } - break // the last tx should be an integration tx (hogex) + return nil, fmt.Errorf("failed to deserialize transaction %d of %d in block %v: %w", + i+1, txnCount, hdr.BlockHash(), err) } - txns = append(txns, msgTx) // txns = append(txns, msgTx) - } - for i := len(txns); i < cap(txns); i++ { // should be just one, but be tolerant - txns = append(txns, &wire.MsgTx{}) // just a placeholder => EmptyTxHash + txns = append(txns, msgTx.MsgTx) // txns = append(txns, msgTx) + hasHogEx = msgTx.IsHogEx // hogex is the last txn } // If containsMWEB, blk.Len (remaining) will be non-zero since we did not - // fully read the integ/hogex tx or any of the EB. + // fully read the EB. The block is still optional, apparently. + if hdr.Version&mwebVer != 0 && hasHogEx { + if err = parseMWEB(blk); err != nil { + return nil, err + } + } return &wire.MsgBlock{ Header: *hdr, diff --git a/dex/networks/ltc/block2215586testnet4.dat b/dex/networks/ltc/block2215586testnet4.dat new file mode 100644 index 0000000000000000000000000000000000000000..f23ae0ec7bc017d9b0917768ee845b7d80bbe797 GIT binary patch literal 18915 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 UINT_MAX -- 9 bytes (255 + 8 bytes) + chSize, err := d.readByte() + if err != nil { + return 0, err + } + switch chSize { + case 253: + sz, err := d.readUint16() + if err != nil { + return 0, err + } + return uint64(sz), nil + case 254: + sz, err := d.readUint32() + if err != nil { + return 0, err + } + return uint64(sz), nil + case 255: + sz, err := d.readUint64() + if err != nil { + return 0, err + } + return sz, nil + default: // < 253 + return uint64(chSize), nil + } +} + +// variable length quantity, not supposed to be part of the wire protocol. +// Not the same as CompactSize type in the C++ code, but rather VARINT. +// https://en.wikipedia.org/wiki/Variable-length_quantity +// This function is borrowed from dcrd. +func (d *decoder) readVLQ() (uint64, error) { + var n uint64 + for { + val, err := d.readByte() + if err != nil { + return 0, err + } + n = (n << 7) | uint64(val&0x7f) + if val&0x80 != 0x80 { + break + } + n++ + } + + return n, nil +} + +// readTxIn reads the next sequence of bytes from r as a transaction input. +func (d *decoder) readTxIn(ti *wire.TxIn) error { + err := d.readOutPoint(&ti.PreviousOutPoint) + if err != nil { + return err + } + + ti.SignatureScript, err = wire.ReadVarBytes(d, pver, wire.MaxMessagePayload, "sigScript") + if err != nil { + return err + } + if d.tee != nil { + _ = wire.WriteVarBytes(d.tee, pver, ti.SignatureScript) + } + + ti.Sequence, err = d.readUint32() + return err +} + +// readTxOut reads the next sequence of bytes from r as a transaction output. +func (d *decoder) readTxOut(to *wire.TxOut) error { + v, err := d.readUint64() + if err != nil { + return err + } + + pkScript, err := wire.ReadVarBytes(d, pver, wire.MaxMessagePayload, "pkScript") + if err != nil { + return err + } + if d.tee != nil { + _ = wire.WriteVarBytes(d.tee, pver, pkScript) + } + + to.Value = int64(v) + to.PkScript = pkScript + + return nil +} + +func (d *decoder) discardBytes(n int64) error { + w := io.Discard + if d.tee != nil { + w = d.tee + } + m, err := io.CopyN(w, d, n) + if err != nil { + return err + } + if m != n { + return fmt.Errorf("only discarded %d of %d bytes", m, n) + } + return nil +} + +func (d *decoder) discardVect() error { + sz, err := d.readCompactSize() + if err != nil { + return err + } + return d.discardBytes(int64(sz)) +} + +func (d *decoder) readMWTX() ([]byte, bool, error) { + // src/mweb/mweb_models.h - struct Tx + // "A convenience wrapper around a possibly-null MWEB transcation." + // Read the uint8_t is_set of OptionalPtr. + haveMWTX, err := d.readByte() + if err != nil { + return nil, false, err + } + if haveMWTX == 0 { + return nil, true, nil // HogEx - that's all folks + } + + // src/libmw/include/mw/models/tx/Transaction.h - class Transaction + // READWRITE(obj.m_kernelOffset); // class BlindingFactor + // READWRITE(obj.m_stealthOffset); // class BlindingFactor + // READWRITE(obj.m_body); // class TxBody + + // src/libmw/include/mw/models/crypto/BlindingFactor.h - class BlindingFactor + // just 32 bytes: + // BigInt<32> m_value; + // READWRITE(obj.m_value); + // x2 for both kernel_offset and stealth_offset BlindingFactor + if err = d.discardBytes(64); err != nil { + return nil, false, err + } + + // TxBody + kern0, err := d.readMWTXBody() + if err != nil { + return nil, false, err + } + return kern0, false, nil +} + +func (d *decoder) readMWTXBody() ([]byte, error) { + // src/libmw/include/mw/models/tx/TxBody.h - class TxBody + // READWRITE(obj.m_inputs, obj.m_outputs, obj.m_kernels); + // std::vector m_inputs; + // std::vector m_outputs; + // std::vector m_kernels; + + // inputs + numIn, err := d.readCompactSize() + if err != nil { + return nil, err + } + for i := 0; i < int(numIn); i++ { + // src/libmw/include/mw/models/tx/Input.h - class Input + // - features uint8_t + // - outputID mw::Hash - 32 bytes BigInt<32> + // - commit Commitment - 33 bytes BigInt for SIZE 33 + // - outputPubKey PublicKey - 33 bytes BigInt<33> + // (if features includes STEALTH_KEY_FEATURE_BIT) input_pubkey PublicKey + // (if features includes EXTRA_DATA_FEATURE_BIT) extraData vector + // - signature Signature - 64 bytes BigInt for SIZE 64 + // + // enum FeatureBit { + // STEALTH_KEY_FEATURE_BIT = 0x01, + // EXTRA_DATA_FEATURE_BIT = 0x02 + // }; + feats, err := d.readByte() + if err != nil { + return nil, err + } + if err = d.discardBytes(32 + 33 + 33); err != nil { // outputID, commitment, outputPubKey + return nil, err + } + if feats&0x1 != 0 { // input pubkey + if err = d.discardBytes(33); err != nil { + return nil, err + } + } + if feats&0x2 != 0 { // extraData + if err = d.discardVect(); err != nil { + return nil, err + } + } + if err = d.discardBytes(64); err != nil { // sig + return nil, err + } + } // inputs + + // outputs + numOut, err := d.readCompactSize() + if err != nil { + return nil, err + } + for i := 0; i < int(numOut); i++ { + // src/libmw/include/mw/models/tx/Output.h - class Output + // commit + // sender pubkey + // receiver pubkey + // message OutputMessage --- fuuuuuuuu + // proof RangeProof + // signature + if err = d.discardBytes(33 + 33 + 33); err != nil { // commitment, sender pk, receiver pk + return nil, err + } + + // OutputMessage + feats, err := d.readByte() + if err != nil { + return nil, err + } + // enum FeatureBit { + // STANDARD_FIELDS_FEATURE_BIT = 0x01, + // EXTRA_DATA_FEATURE_BIT = 0x02 + // }; + if feats&0x1 != 0 { // pubkey | view_tag uint8_t | masked_value uint64_t | nonce 16-bytes + if err = d.discardBytes(33 + 1 + 8 + 16); err != nil { + return nil, err + } + } + if feats&0x2 != 0 { // extraData + if err = d.discardVect(); err != nil { + return nil, err + } + } + + // RangeProof "The proof itself, at most 675 bytes long." + // std::vector m_bytes; -- except it's actually used like a [675]byte... + if err = d.discardBytes(675 + 64); err != nil { // proof + sig + return nil, err + } + } // outputs + + // kernels + numKerns, err := d.readCompactSize() + if err != nil { + return nil, err + } + // Capture the first kernel since pure MW txns (no canonical inputs or + // outputs) that live in the MWEB but are also seen in mempool have their + // hash computed as blake3_256(kernel0). + var kern0 []byte + d.tee = new(bytes.Buffer) + for i := 0; i < int(numKerns); i++ { + // src/libmw/include/mw/models/tx/Kernel.h - class Kernel + + // enum FeatureBit { + // FEE_FEATURE_BIT = 0x01, + // PEGIN_FEATURE_BIT = 0x02, + // PEGOUT_FEATURE_BIT = 0x04, + // HEIGHT_LOCK_FEATURE_BIT = 0x08, + // STEALTH_EXCESS_FEATURE_BIT = 0x10, + // EXTRA_DATA_FEATURE_BIT = 0x20, + // ALL_FEATURE_BITS = FEE_FEATURE_BIT | PEGIN... + // }; + feats, err := d.readByte() + if err != nil { + return nil, err + } + if feats&0x1 != 0 { // fee + _, err = d.readVLQ() // vlq for amount? in the wire protocol?!? + if err != nil { + return nil, err + } + } + if feats&0x2 != 0 { // pegin amt + _, err = d.readVLQ() + if err != nil { + return nil, err + } + } + if feats&0x4 != 0 { // pegouts vector + sz, err := d.readCompactSize() + if err != nil { + return nil, err + } + for i := uint64(0); i < sz; i++ { + _, err = d.readVLQ() // pegout amt + if err != nil { + return nil, err + } + if err = d.discardVect(); err != nil { // pkScript + return nil, err + } + } + } + if feats&0x8 != 0 { // lockHeight + _, err = d.readVLQ() + if err != nil { + return nil, err + } + } + if feats&0x10 != 0 { // stealth_excess pubkey + if err = d.discardBytes(33); err != nil { + return nil, err + } + } + if feats&0x20 != 0 { // extraData vector + if err = d.discardVect(); err != nil { + return nil, err + } + } + // "excess" commitment and signature + if err = d.discardBytes(33 + 64); err != nil { + return nil, err + } + + if i == 0 { + kern0 = d.tee.Bytes() + d.tee = nil + } + } // kernels + + return kern0, nil +} + +type Tx struct { + *wire.MsgTx + IsHogEx bool + Kern0 []byte +} + +func (tx *Tx) TxHash() chainhash.Hash { + // A pure-MW tx can only be in mempool or the EB, not the canonical block. + if len(tx.Kern0) > 0 && len(tx.TxIn) == 0 && len(tx.TxOut) == 0 { + // CTransaction::ComputeHash in src/primitives/transaction.cpp. + // Fortunately also a 32 byte hash so we can use chainhash.Hash. + return blake3.Sum256(tx.Kern0) + } + return tx.MsgTx.TxHash() +} + +// DeserializeTx +func DeserializeTx(r io.Reader) (*Tx, error) { + // mostly taken from wire/msgtx.go + msgTx := &wire.MsgTx{} + dec := newDecoder(r) + + version, err := dec.readUint32() + if err != nil { + return nil, err + } + // if version != 0 { + // return nil, fmt.Errorf("only tx version 0 supported, got %d", version) + // } + msgTx.Version = int32(version) + + count, err := dec.readCompactSize() + if err != nil { + return nil, err + } + + // A count of zero (meaning no TxIn's to the uninitiated) means that the + // value is a TxFlagMarker, and hence indicates the presence of a flag. + var flag [1]byte + if count == 0 { + // The count varint was in fact the flag marker byte. Next, we need to + // read the flag value, which is a single byte. + if _, err = io.ReadFull(r, flag[:]); err != nil { + return nil, err + } + + // Flag bits 0 or 3 must be set. + if flag[0]&0b1001 == 0 { + return nil, fmt.Errorf("witness tx but flag byte is %x", flag) + } + + // With the Segregated Witness specific fields decoded, we can + // now read in the actual txin count. + count, err = dec.readCompactSize() + if err != nil { + return nil, err + } + } + + if count > maxTxInPerMessage { + return nil, fmt.Errorf("too many transaction inputs to fit into "+ + "max message size [count %d, max %d]", count, maxTxInPerMessage) + } + + msgTx.TxIn = make([]*wire.TxIn, count) + for i := range msgTx.TxIn { + txIn := &wire.TxIn{} + err = dec.readTxIn(txIn) + if err != nil { + return nil, err + } + msgTx.TxIn[i] = txIn + } + + count, err = dec.readCompactSize() + if err != nil { + return nil, err + } + if count > maxTxOutPerMessage { + return nil, fmt.Errorf("too many transactions outputs to fit into "+ + "max message size [count %d, max %d]", count, maxTxOutPerMessage) + } + + msgTx.TxOut = make([]*wire.TxOut, count) + for i := range msgTx.TxOut { + txOut := &wire.TxOut{} + err = dec.readTxOut(txOut) + if err != nil { + return nil, err + } + msgTx.TxOut[i] = txOut + } + + if flag[0]&0x01 != 0 { + for _, txIn := range msgTx.TxIn { + witCount, err := dec.readCompactSize() + if err != nil { + return nil, err + } + if witCount > maxWitnessItemsPerInput { + return nil, fmt.Errorf("too many witness items: %d > max %d", + witCount, maxWitnessItemsPerInput) + } + txIn.Witness = make(wire.TxWitness, witCount) + for j := range txIn.Witness { + txIn.Witness[j], err = wire.ReadVarBytes(r, pver, + maxWitnessItemSize, "script witness item") + if err != nil { + return nil, err + } + } + } + } + + // check for a MW tx based on flag 0x08 + // src/primitives/transaction.h - class CTransaction + // Serialized like normal tx except with an optional MWEB::Tx after outputs + // and before locktime. + var kern0 []byte + var isHogEx bool + if flag[0]&0x08 != 0 { + kern0, isHogEx, err = dec.readMWTX() + if err != nil { + return nil, err + } + if isHogEx && len(msgTx.TxOut) == 0 { + return nil, errors.New("no outputs on HogEx txn") + } + } + + msgTx.LockTime, err = dec.readUint32() + if err != nil { + return nil, err + } + + return &Tx{msgTx, isHogEx, kern0}, nil +} diff --git a/dex/networks/ltc/tx62e17562a697e3167e2b68bce5a927ac355249ef5751800c4dd927ddf57d9db2_mp.dat b/dex/networks/ltc/tx62e17562a697e3167e2b68bce5a927ac355249ef5751800c4dd927ddf57d9db2_mp.dat new file mode 100644 index 0000000000000000000000000000000000000000..c60743d8cc56dca9aaeb12eaa4fadacd9b0c1c60 GIT binary patch literal 2987 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 literal 0 HcmV?d00001 diff --git a/dex/networks/ltc/tx_test.go b/dex/networks/ltc/tx_test.go new file mode 100644 index 0000000000..2d1c00a19b --- /dev/null +++ b/dex/networks/ltc/tx_test.go @@ -0,0 +1,130 @@ +// This code is available on the terms of the project LICENSE.md file, +// also available online at https://blueoakcouncil.org + +package ltc + +import ( + "bytes" + _ "embed" + "testing" +) + +var ( + // peg-in txn 8b8343978dbef95d54da796977e9a254565c0dc9ce54917d9111267547fcde03 + // MEMPOOL, not in block, 2203 bytes, version 2 + // with: + // - flag 0x9 + // - 1 regular input / 1 regular output + // - 0 mw inputs / 2 mw outputs + // - 1 kernel with peg-in amt and fee amt and stealth pubkey + //go:embed tx8b8343978dbef95d54da796977e9a254565c0dc9ce54917d9111267547fcde03_mp.dat + tx8b83439mp []byte + + // peg-in txn 8b8343978dbef95d54da796977e9a254565c0dc9ce54917d9111267547fcde03 + // This is the same tx as above, but now in the block stripped of mw tx + // data and with the flag with the mw bit unset. + // IN BLOCK, 203 bytes, version 2 + // with: + // - flag 0x1 (mw removed) + // - 1 regular input / 1 regular output + //go:embed tx8b8343978dbef95d54da796977e9a254565c0dc9ce54917d9111267547fcde03.dat + tx8b83439 []byte + + // Pure MW-only txn (no canonical inputs or outputs). + // MEMPOOL, not in block, 2203 bytes, version 2 + // with: + // - flag 0x8 + // - 0 regular inputs / 0 regular outputs + // - 1 mw input / 2 mw outputs + // - 1 kernel with fee amt and stealth pubkey + //go:embed txe6f8fdb15b27e603adcfa5aa4107a0e7a7b07e6dc76d2ba95a33710db02a0049_mp.dat + txe6f8fdbmp []byte + + // Pure MW-only txn (no canonical inputs or outputs). + // MEMPOOL, not in block, 2987 bytes, version 2 + // with: + // - flag 0x8 + // - 0 regular inputs / 0 regular outputs + // - 5 mw inputs / 2 mw outputs + // - 1 kernel with fee amt and stealth pubkey + //go:embed tx62e17562a697e3167e2b68bce5a927ac355249ef5751800c4dd927ddf57d9db2_mp.dat + tx62e1756mp []byte + + // Pure MW-only txn (no canonical inputs or outputs). + // MEMPOOL, not in block, 1333 bytes, version 2 + // with: + // - flag 0x8 + // - 0 regular inputs / 0 regular outputs + // - 1 mw inputs / 1 mw outputs + // - 1 kernel with fee amt and pegouts and stealth pubkey + //go:embed txcc202c44db4d6faec57f42566c6b1e03139f924eaf685ae964b3076594d65349_mp.dat + txcc202c4mp []byte + + // HogEx with multiple inputs and outputs, IN BLOCK 2269979. + // Flag 0x8 (omit witness data), null mw tx (but not omitted), 169 bytes. + //go:embed txde22f4de7116b8482a691cc5e552c4212f0ae77e3c8d92c9cb85c29f4dc1f47c.dat + txde22f4d []byte +) + +func TestDeserializeTx(t *testing.T) { + tests := []struct { + name string + tx []byte + wantHash string + wantLockTime uint32 // last field after any mw tx data is the ideal check + }{ + { + "mempool peg-in tx 8b8343978dbef95d54da796977e9a254565c0dc9ce54917d9111267547fcde03", + tx8b83439mp, + "8b8343978dbef95d54da796977e9a254565c0dc9ce54917d9111267547fcde03", + 2269978, + }, + { + "block peg-in tx 8b8343978dbef95d54da796977e9a254565c0dc9ce54917d9111267547fcde03", + tx8b83439, + "8b8343978dbef95d54da796977e9a254565c0dc9ce54917d9111267547fcde03", + 2269978, + }, + { + "mempool MW tx e6f8fdb15b27e603adcfa5aa4107a0e7a7b07e6dc76d2ba95a33710db02a0049", + txe6f8fdbmp, + "e6f8fdb15b27e603adcfa5aa4107a0e7a7b07e6dc76d2ba95a33710db02a0049", + 2269917, + }, + { + "mempool MW tx 62e17562a697e3167e2b68bce5a927ac355249ef5751800c4dd927ddf57d9db2", + tx62e1756mp, + "62e17562a697e3167e2b68bce5a927ac355249ef5751800c4dd927ddf57d9db2", + 2269919, + }, + { + "mempool MW tx cc202c44db4d6faec57f42566c6b1e03139f924eaf685ae964b3076594d65349", + txcc202c4mp, + "cc202c44db4d6faec57f42566c6b1e03139f924eaf685ae964b3076594d65349", + 2269977, + }, + { + "HogEx tx de22f4de7116b8482a691cc5e552c4212f0ae77e3c8d92c9cb85c29f4dc1f47c", + txde22f4d, + "de22f4de7116b8482a691cc5e552c4212f0ae77e3c8d92c9cb85c29f4dc1f47c", + 0, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + msgTx, err := DeserializeTx(bytes.NewReader(tt.tx)) + if err != nil { + t.Fatal(err) + } + if msgTx.LockTime != tt.wantLockTime { + t.Errorf("want locktime %d, got %d", tt.wantLockTime, msgTx.LockTime) + } + + txHash := msgTx.TxHash() + if txHash.String() != tt.wantHash { + t.Errorf("Wanted tx hash %v, got %v", tt.wantHash, txHash) + } + }) + } +} diff --git a/dex/networks/ltc/txcc202c44db4d6faec57f42566c6b1e03139f924eaf685ae964b3076594d65349_mp.dat b/dex/networks/ltc/txcc202c44db4d6faec57f42566c6b1e03139f924eaf685ae964b3076594d65349_mp.dat new file mode 100644 index 0000000000000000000000000000000000000000..9994dda61899d64ffd0f7bf30ab0c2ab2e5f1b25 GIT binary patch literal 1333 zcmV-51U4JsT!8}(7W&KMEJ0Rhb-1*v#{bLZf)sAI5bf0%FW)xG~v z=33Hk_XoiG>2e5YvYGoM;Q@~FgS8rK3kq%7NoGrp@+n+$M=54!{u9>%Y&D^{VbBtq zPTLI;%??xEY~M~0u;5esR+@yL$4A4-r{9W!Cylix13eG>wwdI zxN2s+*L5oS`CtT`&!q_H%5<>#dV?=bWkZ7a)Qev)6pD_i;zbe`NAM2(>Tf=M!nO~@ zqdQ(@9a2S|Q@AHO<$TB@rvV7&maJP5VeDQVknPDobzLZeZT(lF<&r+R@f~EVJSn9E zf4jxIc}P6T(+u7e*KCA~g=jKCL#0Nymgi>PUzpj6N z{_*MXRYDDjSv-RPCIJGE-wKiy;@|e>@9(NvO&yjtL)iw4=l&f9)*k}VPEb5~$dYnV zQH{^eGUSI~zu{n?eGzY=7m~5|#|tn|Gmdjw63t zbu1(`|7?N(c@?PO{O`t`40N@(Bi39L3a%y!z)8_jd-HaWr4l2lS`cpju1OSGy2Np} zuuyr+sxeXNS`#eR+0C!~m$x&(;NxkF5=gDN2=xrMQU7+nq^cCh4C%(v;k1N|?*;*W zydNkZG}gee5<>xA3Yn15BAh$u!xkz)myxRut#w)bCfF4)Ko5K23xY#itbsWj zxoqJ)bhq&G29%`Pg9(aBR#2S+dJKPe2i?IdIV(yFPzj3I3Obx@p% z_c>{&`7aunc8VsI>;!h(7fPsUqafS++9@&oZu!9h6_QQ?;L`vW02I+0eg3HNyjZII zRz7W??xT{;u9E_Q#TAMy?x9)TzZg(-FU52xX!m9Pl{B$D;uT77Wl@s|7C*Z#QfyTED z;hT@&Pf{_e4XE=8G+?^@>1N}eRrMzrvLwLz#27>_O4a^b@o`VYsy|_Nx%1yGo^*EI HWUw3njBQ3D literal 0 HcmV?d00001 diff --git a/dex/networks/ltc/txe6f8fdb15b27e603adcfa5aa4107a0e7a7b07e6dc76d2ba95a33710db02a0049_mp.dat b/dex/networks/ltc/txe6f8fdb15b27e603adcfa5aa4107a0e7a7b07e6dc76d2ba95a33710db02a0049_mp.dat new file mode 100644 index 0000000000000000000000000000000000000000..c1094e85bc6b6219eb1e08bbb2b2597e716ddb1e GIT binary patch literal 2203 zcmV;M2xRvH000002mk;9?-GM-PAjG6au)qINz zHNLA~OcH5YOorT7SKNs3@0u;L^#Kput5#&2BJDwav2(#?B?jULHlu68R zDI?^RAh>IBGn(8frqA`7W`vBS*^1|VP=Ew0)>+#Vei-0wCC`P|RyHq1b#YOtFk|CkS8~fJ`~tb#$=9T03^@(ARUZ%zUT%5 zrIeU~bSdiRe>wdzkJ|6TlqZ8FLsn#e=0wBczy)2z0=h~}$k7P&ySb%?a~q=F%LVry zK43xl`|oviRUlz!TLA+5`5$3o3+Vv;KxKwL?Uqi5O`)=q=uRv^H{z2Q@2H(+>SZ+L zif#fsN(3sC6-9Og9lUMM-NGb9sgIw7}CITf70WHn*0wB|AAB z|B;*fXhkH>Dep4zz0)f&P&#V_d?8rd`ruxLzN_NciaB%wp6ZUB4F|egi`ef9(B$OQDHm6dDy!sW^Ky~EI7>4#Eb1z65R^H?~PsicyR%_#mNPMP@UZm=^) z(W{1nIpA`J3C16cY1Roe38)+vmTx>q5^Qv9tU~2#@MwF=%=_)|C9?Ms1wVw2H}$@E zjg6Mxu_D0%$$C_xAUP7&8R@g{?J@7jL%St%P1E9e-@bTccdUieNk!3u-Ed#M>TB-b zs0)BN=B<7L-CqL~&Z&kD+jciIeb1^gKBq6iV6BVV?F7-dFVdR?wAZ6>3JEt>i#$X0 zO9cPtOjO?)!UY2iODTVCv)=J&teD$8#0Vs?_a*nxCLNtdXViyH^F7T1A=bHOO~28hK=yeXh+Q@p56=`p3ZJL&H2Ee-3Yy z(qe!W{cUyUb={IsB=LaJHEc&l@)v_oy}hD0T$)!6)3r1pBkvM6!X%M@{P?3$<-Ubx z);tX;#i$8s z*)5btO*Avqx3J}S1EY9b_R=4+bm(4$%>_wI=}rIF_!XbJwzOe_Ls-_C#n~N02Et^r;Kk z#nw)Wu!=K^rX$k59f31N#^KJ>-A~8=z)kH9Xtgw>sVd?K7n6NVj^FfTO8Z=bs2_C| zj8*x6A}_hf$1AD^nV%JsWu#fQd39c4d$OfkXV6rCGVu7r<~Q3O&!%hOY6-`hvq(zf ztLH=OnG6S6WMg;J{QB81&u_ËLcI30=e7O(2GfhIt$HGGe>K2#{@+h8^Yg)fS>S>*m5qN7OJkRCQp)QKb`7@(l1O?457chv1jr-o6kp}AM zm8k!9041&GwxEIc;26$Hh}vZOah#)5B$L6qR>-hzDkZ4BXq&_FJ-R4;+6k1%g_m)x zZ;cJ(3-A5&kfix^8_h_;;El<4*VTvPVf8nS5jI~>C{LmN)D8Fbf!RMo@HrtDtA0$< z!L3SSBq$PIkrEm@2ubcmKDF6(2>G)`{;G}}l zfLc#tHQ!4y>zc3|%ll9=F;;hXub4;&)ykJ6ooB2Cy(b zdY;@Exei;JAKC5Q*BN}3()s)cDc z8Zli@LT_n=@I8x%|y-7|9O+Fi3t*0wJ4R|5M!sky_2h)97(j&;wIfK z7on@thDQ%3fAW+>zpatH`