From 103507e5c1105ba9a7768dd251a716298e1107a4 Mon Sep 17 00:00:00 2001 From: Calvin Kim Date: Thu, 14 Nov 2024 13:07:35 +0900 Subject: [PATCH 1/2] wire: add Equal on LeafData Added for utility but mainly to check that if the LeafData has been initialized or not. Makes it easy to tell if the txIn this LeafData represents is confirmed or unconfirmed. --- wire/leaf.go | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/wire/leaf.go b/wire/leaf.go index b61044be..d61749f0 100644 --- a/wire/leaf.go +++ b/wire/leaf.go @@ -5,6 +5,7 @@ package wire import ( + "bytes" "crypto/sha512" "encoding/hex" "encoding/json" @@ -29,16 +30,22 @@ var ( // empty is useful when comparing against BlockHash to see if it hasn't been // initialized. empty chainhash.Hash + + // emptyLd is useful when comparing against LeafData to see if it hasn't been + // initialized. + emptyLd LeafData ) // LeafData is all the data that goes into a leaf in the utreexo accumulator. // The data here serve two roles: commitments and data needed for verification. // -// Commitment: BlockHash is included in the LeafData to commit to a block. -// Verification: OutPoint is the OutPoint for the utxo being referenced. +// Commitment: +// - BlockHash is included in the LeafData to commit to a block. // -// Height, IsCoinbase, Amount, and PkScript is the data needed for -// tx verification (script, signatures, etc). +// Verification: +// - OutPoint is the OutPoint for the utxo being referenced. +// - Height, IsCoinbase, Amount, and PkScript is the data needed for +// tx verification (script, signatures, etc). type LeafData struct { BlockHash chainhash.Hash OutPoint OutPoint @@ -49,6 +56,17 @@ type LeafData struct { PkScript []byte } +// Equal returns if the passed in LeafData is equal to this one. +func (l *LeafData) Equal(other LeafData) bool { + return l.BlockHash == other.BlockHash && + l.OutPoint == other.OutPoint && + l.Height == other.Height && + l.IsCoinBase == other.IsCoinBase && + l.Amount == other.Amount && + l.ReconstructablePkType == other.ReconstructablePkType && + bytes.Equal(l.PkScript, other.PkScript) +} + // Copy creates a deep copy of the leafdata so the original does not get modified // when the copy is manipulated. func (l *LeafData) Copy() *LeafData { From 5eea94c19006b4bae6ef9ef941aa260d3c81e31f Mon Sep 17 00:00:00 2001 From: Calvin Kim Date: Thu, 14 Nov 2024 13:13:19 +0900 Subject: [PATCH 2/2] wire, netsync: replace UData with BatchProof and LeafData and encode unconfirmed marker in the TxIn.OutPoint.Index Instead of having the UData wrapper, MsgUtreexoTx has the BatchProof and the LeafData structs directly. Also, confirmed marker for each TxIn is now included in its OutPoint.Index. --- netsync/manager.go | 2 +- server.go | 15 ++++--- wire/message_test.go | 2 +- wire/msgutreexotx.go | 94 ++++++++++++++++++++++++++++++++++++-------- 4 files changed, 88 insertions(+), 25 deletions(-) diff --git a/netsync/manager.go b/netsync/manager.go index abbf9e87..a3b65f2c 100644 --- a/netsync/manager.go +++ b/netsync/manager.go @@ -1611,7 +1611,7 @@ out: msg.reply <- struct{}{} case *utreexoTxMsg: - sm.handleTxMsg(&msg.utreexoTx.Tx, msg.peer, &msg.utreexoTx.MsgUtreexoTx().UData) + sm.handleTxMsg(&msg.utreexoTx.Tx, msg.peer, msg.utreexoTx.MsgUtreexoTx().UData) msg.reply <- struct{}{} case *blockMsg: diff --git a/server.go b/server.go index d4c736eb..a4511955 100644 --- a/server.go +++ b/server.go @@ -1642,8 +1642,9 @@ func (s *server) pushTxMsg(sp *serverPeer, hash *chainhash.Hash, packedPositions } utreexoTx = &wire.MsgUtreexoTx{ - MsgTx: *tx.MsgTx(), - UData: *ud, + MsgTx: *tx.MsgTx(), + LeafDatas: ud.LeafDatas, + AccProof: ud.AccProof, } } } @@ -1671,8 +1672,9 @@ func (s *server) pushTxMsg(sp *serverPeer, hash *chainhash.Hash, packedPositions } utreexoTx = &wire.MsgUtreexoTx{ - MsgTx: *tx.MsgTx(), - UData: *ud, + MsgTx: *tx.MsgTx(), + LeafDatas: ud.LeafDatas, + AccProof: ud.AccProof, } } } else if s.flatUtreexoProofIndex != nil { @@ -1696,8 +1698,9 @@ func (s *server) pushTxMsg(sp *serverPeer, hash *chainhash.Hash, packedPositions } utreexoTx = &wire.MsgUtreexoTx{ - MsgTx: *tx.MsgTx(), - UData: *ud, + MsgTx: *tx.MsgTx(), + LeafDatas: ud.LeafDatas, + AccProof: ud.AccProof, } } } diff --git a/wire/message_test.go b/wire/message_test.go index 1f04703a..ae6e39ea 100644 --- a/wire/message_test.go +++ b/wire/message_test.go @@ -95,7 +95,7 @@ func TestMessage(t *testing.T) { {msgGetData, msgGetData, pver, MainNet, 25}, {msgNotFound, msgNotFound, pver, MainNet, 25}, {msgTx, msgTx, pver, MainNet, 34}, - {msgUtreexoTx, msgUtreexoTx, pver, MainNet, 37}, + {msgUtreexoTx, msgUtreexoTx, pver, MainNet, 36}, {msgPing, msgPing, pver, MainNet, 32}, {msgPong, msgPong, pver, MainNet, 32}, {msgGetHeaders, msgGetHeaders, pver, MainNet, 61}, diff --git a/wire/msgutreexotx.go b/wire/msgutreexotx.go index 7852ba5b..c70db8df 100644 --- a/wire/msgutreexotx.go +++ b/wire/msgutreexotx.go @@ -6,6 +6,8 @@ package wire import ( "io" + + "github.com/utreexo/utreexo" ) // MsgUtreexoTx implements the Message interface and represents a bitcoin utreexo @@ -19,17 +21,36 @@ type MsgUtreexoTx struct { // MsgTx is the underlying Bitcoin transaction message. MsgTx - // UData is the underlying utreexo data. - UData + // AccProof is the utreexo accumulator proof for all the inputs. + AccProof utreexo.Proof + + // LeafDatas are the tx validation data for every input. + LeafDatas []LeafData } // Copy creates a deep copy of a transaction so that the original does not get // modified when the copy is manipulated. func (msg *MsgUtreexoTx) Copy() *MsgUtreexoTx { msgTx := msg.MsgTx.Copy() + + // Copy proof + proofCopy := utreexo.Proof{ + Targets: make([]uint64, len(msg.AccProof.Targets)), + Proof: make([]utreexo.Hash, len(msg.AccProof.Proof)), + } + copy(proofCopy.Targets, msg.AccProof.Targets) + copy(proofCopy.Proof, msg.AccProof.Proof) + + // Copy leaf datas. + LeafDatas := make([]LeafData, len(msg.LeafDatas)) + for i := range LeafDatas { + LeafDatas[i] = *msg.LeafDatas[i].Copy() + } + newTx := MsgUtreexoTx{ - MsgTx: *msgTx, - UData: *msg.UData.Copy(), + MsgTx: *msgTx, + AccProof: proofCopy, + LeafDatas: LeafDatas, } return &newTx @@ -40,22 +61,41 @@ func (msg *MsgUtreexoTx) Copy() *MsgUtreexoTx { // See Deserialize for decoding transactions stored to disk, such as in a // database, as opposed to decoding transactions from the wire. func (msg *MsgUtreexoTx) BtcDecode(r io.Reader, pver uint32, enc MessageEncoding) error { + // Decode the batchproof. + proof, err := BatchProofDeserialize(r) + if err != nil { + return err + } + msg.AccProof = *proof + // Decode the MsgTx. var msgTx MsgTx - err := msgTx.BtcDecode(r, pver, enc) + err = msgTx.BtcDecode(r, pver, enc) if err != nil { return err } msg.MsgTx = msgTx - // Decode the utreexo data. - ud := new(UData) - ud.LeafDatas = nil - err = ud.Deserialize(r) - if err != nil { - return err + // Return early if it's 0. Makes sure msg.LeafDatas is nil which is important for + // reflect.DeepEqual in the tests. + if len(msg.MsgTx.TxIn) == 0 { + return nil + } + + msg.LeafDatas = make([]LeafData, len(msg.MsgTx.TxIn)) + for i, txIn := range msgTx.TxIn { + isUnconfirmed := txIn.PreviousOutPoint.Index&1 == 1 + txIn.PreviousOutPoint.Index >>= 1 + + if isUnconfirmed { + continue + } + + err = msg.LeafDatas[i].DeserializeCompact(r) + if err != nil { + return err + } } - msg.UData = *ud return nil } @@ -63,8 +103,7 @@ func (msg *MsgUtreexoTx) BtcDecode(r io.Reader, pver uint32, enc MessageEncoding // Deserialize decodes a transaction from r into the receiver using a format // that is suitable for long-term storage such as a database while respecting // the Version field in the transaction. This function differs from BtcDecode -// in that BtcDecode decodes from the bitcoin wire protocol as it was sent -// across the network. The wire encoding can technically differ depending on +// in that BtcDecode decodes from the bitcoin wire protocol as it was sent across the network. The wire encoding can technically differ depending on // the protocol version and doesn't even really need to match the format of a // stored transaction at all. As of the time this comment was written, the // encoded transaction is the same in both instances, but there is a distinct @@ -82,14 +121,35 @@ func (msg *MsgUtreexoTx) Deserialize(r io.Reader) error { // See Serialize for encoding transactions to be stored to disk, such as in a // database, as opposed to encoding transactions for the wire. func (msg *MsgUtreexoTx) BtcEncode(w io.Writer, pver uint32, enc MessageEncoding) error { + // Write batch proof. + err := BatchProofSerialize(w, &msg.AccProof) + if err != nil { + return err + } + + // Go through the TxIns and mark the ones that are not confirmed with a 1 in the LSB. + for i := range msg.TxIn { + msg.TxIn[i].PreviousOutPoint.Index <<= 1 + if msg.UData.LeafDatas[i].Equal(emptyLd) { + msg.TxIn[i].PreviousOutPoint.Index |= 1 + } + } + // Encode the msgTx. - err := msg.MsgTx.BtcEncode(w, pver, enc) + err = msg.MsgTx.BtcEncode(w, pver, enc) if err != nil { return err } - // Encode the utreexo data. - return msg.UData.Serialize(w) + // Write the actual leaf datas. + for _, ld := range msg.LeafDatas { + err = ld.SerializeCompact(w) + if err != nil { + return err + } + } + + return nil } // Command returns the protocol command string for the message. This is part