From aebc0b7b0a29b1465d3dd57553c5d45a3c561740 Mon Sep 17 00:00:00 2001 From: Simon Ordish Date: Sat, 19 Mar 2022 10:57:07 +0100 Subject: [PATCH 01/13] Merge branch 'master' of https://github.com/libsv/go-bt --- bscript/script.go | 21 +++++++++++++++++++-- bscript/script_test.go | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+), 2 deletions(-) diff --git a/bscript/script.go b/bscript/script.go index 42bf7626..8f1f6dea 100644 --- a/bscript/script.go +++ b/bscript/script.go @@ -233,13 +233,30 @@ func (s *Script) ToASM() (string, error) { parts, err := DecodeParts(*s) // if err != nil, we will append [error] to the ASM script below (as done in the node). + data := false + if len(*s) > 1 && ((*s)[0] == 0x6a || ((*s)[0] == 0x00 && (*s)[1] == 0x6a)) { + data = true + } + var asm strings.Builder + for _, p := range parts { asm.WriteRune(' ') if len(p) == 1 { - asm.WriteString(opCodeValues[p[0]]) + if data && p[0] != 0x6a { + asm.WriteString(fmt.Sprintf("%d", p[0])) + } else { + asm.WriteString(opCodeValues[p[0]]) + } } else { - asm.WriteString(hex.EncodeToString(p)) + if data && len(p) <= 4 { + for i := 0; i < 4-len(p); i++ { + p = append(p, 0) + } + asm.WriteString(fmt.Sprintf("%d", binary.LittleEndian.Uint32(p))) + } else { + asm.WriteString(hex.EncodeToString(p)) + } } } diff --git a/bscript/script_test.go b/bscript/script_test.go index 349dfc76..deb8c69c 100644 --- a/bscript/script_test.go +++ b/bscript/script_test.go @@ -490,3 +490,37 @@ func TestScript_UnmarshalJSON(t *testing.T) { }) } } + +func TestRunScriptExample1(t *testing.T) { + script, _ := hex.DecodeString("006a2231394878696756345179427633744870515663554551797131707a5a56646f4175744d7301e4b8bbe381aa54574954544552e381a8425356e381ae547765746368e381a7e381aee98195e381840a547765746368e381a7e381afe887aae58886e381aee69bb8e38184e3819fe38384e382a8e383bce38388e381afe4b880e795aae69c80e5889de381bee381a70ae38195e3818be381aee381bce381a3e381a6e38184e381a4e381a7e38282e7a2bae8aa8de58fafe883bde381a7e8aaade381bfe8bebce381bfe381a7e995b7e69982e996930ae5819ce6ada2e381afe38182e3828ae381bee3819be38293e380825954e383aae383b3e382afe381aee58b95e794bbe38292e8a696e881b4e38197e3819fe5a0b4e590880ae99fb3e6a5bde381afe382b9e382afe383ade383bce383abe38197e381a6e38282e98094e58887e3828ce3819ae881b4e38193e38188e3819fe381bee381be0ae38384e382a4e38383e382bfe383bce381afe69c80e5889de381aee383ace382b9e381bee381a7e8a18ce38191e381aae38184e381a7e38197e38287e380820a746578742f706c61696e04746578741f7477657463685f7477746578745f313634343834393439353138332e747874017c223150755161374b36324d694b43747373534c4b79316b683536575755374d74555235035345540b7477646174615f6a736f6e046e756c6c0375726c046e756c6c07636f6d6d656e74046e756c6c076d625f757365720439373038057265706c794035366462363536376363306230663539316265363561396135313731663533396635316334333165643837356464326136373431643733353061353539363762047479706504706f73740974696d657374616d70046e756c6c036170700674776574636807696e766f6963652461366637336133312d336334342d346164612d393937352d386537386261666661623765017c22313550636948473232534e4c514a584d6f53556157566937575371633768436676610d424954434f494e5f454344534122314c6970354b335671677743415662674d7842536547434d344355364e344e6b75744c58494b4b554a35765a7753336b4c456e353749356a36485a2b43325733393834314e543532334a4c374534387655706d6f57306b4677613767392b51703246434f4d42776a556a7a76454150624252784d496a746c6b476b3d") + s := bscript.Script(script) + + asm, err := s.ToASM() + if err != nil { + t.Error(err) + t.FailNow() + } + + expected := "0 OP_RETURN 31394878696756345179427633744870515663554551797131707a5a56646f417574 e4b8bbe381aa54574954544552e381a8425356e381ae547765746368e381a7e381aee98195e381840a547765746368e381a7e381afe887aae58886e381aee69bb8e38184e3819fe38384e382a8e383bce38388e381afe4b880e795aae69c80e5889de381bee381a70ae38195e3818be381aee381bce381a3e381a6e38184e381a4e381a7e38282e7a2bae8aa8de58fafe883bde381a7e8aaade381bfe8bebce381bfe381a7e995b7e69982e996930ae5819ce6ada2e381afe38182e3828ae381bee3819be38293e380825954e383aae383b3e382afe381aee58b95e794bbe38292e8a696e881b4e38197e3819fe5a0b4e590880ae99fb3e6a5bde381afe382b9e382afe383ade383bce383abe38197e381a6e38282e98094e58887e3828ce3819ae881b4e38193e38188e3819fe381bee381be0ae38384e382a4e38383e382bfe383bce381afe69c80e5889de381aee383ace382b9e381bee381a7e8a18ce38191e381aae38184e381a7e38197e38287e38082 746578742f706c61696e 1954047348 7477657463685f7477746578745f313634343834393439353138332e747874 124 3150755161374b36324d694b43747373534c4b79316b683536575755374d74555235 5522771 7477646174615f6a736f6e 1819047278 7107189 1819047278 636f6d6d656e74 1819047278 6d625f75736572 942683961 7265706c79 35366462363536376363306230663539316265363561396135313731663533396635316334333165643837356464326136373431643733353061353539363762 1701869940 1953722224 74696d657374616d70 1819047278 7368801 747765746368 696e766f696365 61366637336133312d336334342d346164612d393937352d386537386261666661623765 124 313550636948473232534e4c514a584d6f5355615756693757537163376843667661 424954434f494e5f4543445341 314c6970354b335671677743415662674d7842536547434d344355364e344e6b7574 494b4b554a35765a7753336b4c456e353749356a36485a2b43325733393834314e543532334a4c374534387655706d6f57306b4677613767392b51703246434f4d42776a556a7a76454150624252784d496a746c6b476b3d" + + if asm != expected { + t.Errorf("\nExpected %q\ngot %q", expected, asm) + } + +} + +func TestRunScriptExample2(t *testing.T) { + script, _ := hex.DecodeString("006a0372756e01050c63727970746f6669676874734d16057b22696e223a312c22726566223a5b22343561666530303862396634393663333130356663396132636234373234316565643566646531333531303532616339353938323531636666623939376136385f6f31222c22643335343933633964313266656538363134313663333366653336346662336566373531363234373532313833316264623232303933333731303330383663325f6f31222c22306338623636326339363862316537376164626535666161653566666436633033653537353965373833376132353534653438643561356535326335346634385f6f31222c22336136376365633363313662646238343762393732626565326663316330373137633539656463616537626635663438633931666563636661363335616633335f6f31222c22313465323738633638666635323165303931366164376337313361653461303135366537363336316462643362326233353764666236303238653064636137615f6f31222c22613738663561366437326637383731316536366336323131666262643061306266643135616439316264643030343034393238613966616363363364613664395f6f31222c22373535633932326336366363656533353766356265656437383164323631336634313739346230323839333963333435316466653438393032303238343263355f6f31222c22316661333532383030333363343534663465313263323134383333343436643335313734663031666565373064346639653633366664393462363237316436325f6f31222c22636233356534656361336635616334303561636261636464383632346366303835636333626535336639323633663531616565373037393234616265316237385f6f31222c22373166626133383633343162393332333830656335626665646333613430626365343364343937346465636463393463343139613934613863653564666332335f6f31222c22363161653132323165646438626431646438336332326461326232616237643131346139313239363439366365336664306562613737333236623638613238335f6f31222c22386462643166643638373934353131636364616338333938333136393638306662616338356233613961626439636166366361326666343839633862313633385f6f31222c22386564316564633665656439386135326635373234396333353032663266333764623561336666356233643135613930363732353063383465363035366531315f6f31222c22343062396534373865333766383733636532386364383162666635323532346631383063623538353837376331656139383636343933383039363363646237385f6f31225d2c226f7574223a5b2262633038643265323932623036313031323463313337656361356566393632383464363534363139616162366630313461333932353532613066336339666162225d2c2264656c223a5b5d2c22637265223a5b5d2c2265786563223a5b7b226f70223a2243414c4c222c2264617461223a5b7b22246a6967223a307d2c227265736f6c7665222c5b2263633031363466343332613563383635306331393731376430373161656561646261393065643761303939386234396464656535663537316430323139373032222c313634373630333734373833342c302c2230336339386161663266623237393930613130356364336362616462656461383536613064363238343262666564633430353730343966636232343163333563222c5b302c302c305d5d5d7d5d7d") + s := bscript.Script(script) + + asm, err := s.ToASM() + if err != nil { + t.Error(err) + t.FailNow() + } + + expected := "0 OP_RETURN 7239026 5 63727970746f666967687473 7b22696e223a312c22726566223a5b22343561666530303862396634393663333130356663396132636234373234316565643566646531333531303532616339353938323531636666623939376136385f6f31222c22643335343933633964313266656538363134313663333366653336346662336566373531363234373532313833316264623232303933333731303330383663325f6f31222c22306338623636326339363862316537376164626535666161653566666436633033653537353965373833376132353534653438643561356535326335346634385f6f31222c22336136376365633363313662646238343762393732626565326663316330373137633539656463616537626635663438633931666563636661363335616633335f6f31222c22313465323738633638666635323165303931366164376337313361653461303135366537363336316462643362326233353764666236303238653064636137615f6f31222c22613738663561366437326637383731316536366336323131666262643061306266643135616439316264643030343034393238613966616363363364613664395f6f31222c22373535633932326336366363656533353766356265656437383164323631336634313739346230323839333963333435316466653438393032303238343263355f6f31222c22316661333532383030333363343534663465313263323134383333343436643335313734663031666565373064346639653633366664393462363237316436325f6f31222c22636233356534656361336635616334303561636261636464383632346366303835636333626535336639323633663531616565373037393234616265316237385f6f31222c22373166626133383633343162393332333830656335626665646333613430626365343364343937346465636463393463343139613934613863653564666332335f6f31222c22363161653132323165646438626431646438336332326461326232616237643131346139313239363439366365336664306562613737333236623638613238335f6f31222c22386462643166643638373934353131636364616338333938333136393638306662616338356233613961626439636166366361326666343839633862313633385f6f31222c22386564316564633665656439386135326635373234396333353032663266333764623561336666356233643135613930363732353063383465363035366531315f6f31222c22343062396534373865333766383733636532386364383162666635323532346631383063623538353837376331656139383636343933383039363363646237385f6f31225d2c226f7574223a5b2262633038643265323932623036313031323463313337656361356566393632383464363534363139616162366630313461333932353532613066336339666162225d2c2264656c223a5b5d2c22637265223a5b5d2c2265786563223a5b7b226f70223a2243414c4c222c2264617461223a5b7b22246a6967223a307d2c227265736f6c7665222c5b2263633031363466343332613563383635306331393731376430373161656561646261393065643761303939386234396464656535663537316430323139373032222c313634373630333734373833342c302c2230336339386161663266623237393930613130356364336362616462656461383536613064363238343262666564633430353730343966636232343163333563222c5b302c302c305d5d5d7d5d7d" + if asm != expected { + t.Errorf("\nExpected %q\ngot %q", expected, asm) + } +} From 292130b3449d345f8e47ec3607f0dc8173fab40a Mon Sep 17 00:00:00 2001 From: Simon Ordish Date: Sat, 19 Mar 2022 20:22:10 +0100 Subject: [PATCH 02/13] Fix panic in ToAsm() caused by last commit. (#109) --- bscript/script.go | 6 ++++-- bscript/script_test.go | 16 ++++++++++++++++ 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/bscript/script.go b/bscript/script.go index 8f1f6dea..ae6fdb54 100644 --- a/bscript/script.go +++ b/bscript/script.go @@ -250,10 +250,12 @@ func (s *Script) ToASM() (string, error) { } } else { if data && len(p) <= 4 { + b := make([]byte, 0) + b = append(b, p...) for i := 0; i < 4-len(p); i++ { - p = append(p, 0) + b = append(b, 0) } - asm.WriteString(fmt.Sprintf("%d", binary.LittleEndian.Uint32(p))) + asmScript.WriteString(fmt.Sprintf("%d", binary.LittleEndian.Uint32(b))) } else { asm.WriteString(hex.EncodeToString(p)) } diff --git a/bscript/script_test.go b/bscript/script_test.go index deb8c69c..f7165035 100644 --- a/bscript/script_test.go +++ b/bscript/script_test.go @@ -524,3 +524,19 @@ func TestRunScriptExample2(t *testing.T) { t.Errorf("\nExpected %q\ngot %q", expected, asm) } } + +func TestRunScriptExample3(t *testing.T) { + script, _ := hex.DecodeString("006a223139694733575459537362796f7333754a373333794b347a45696f69314665734e55010042666166383166326364346433663239383061623162363564616166656231656631333561626339643534386461633466366134656361623230653033656365362d300274780134") + s := bscript.Script(script) + + asm, err := s.ToASM() + if err != nil { + t.Error(err) + t.FailNow() + } + + expected := "0 OP_RETURN 3139694733575459537362796f7333754a373333794b347a45696f69314665734e55 0 666166383166326364346433663239383061623162363564616166656231656631333561626339643534386461633466366134656361623230653033656365362d30 30836 52" + if asm != expected { + t.Errorf("\nExpected %q\ngot %q", expected, asm) + } +} From b8fdba093529a98f3e0a7a626bd52f62c9de33ab Mon Sep 17 00:00:00 2001 From: Simon Ordish Date: Tue, 22 Mar 2022 12:41:43 +0100 Subject: [PATCH 03/13] Fix rename of variable --- bscript/script.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bscript/script.go b/bscript/script.go index ae6fdb54..34d85db7 100644 --- a/bscript/script.go +++ b/bscript/script.go @@ -255,7 +255,7 @@ func (s *Script) ToASM() (string, error) { for i := 0; i < 4-len(p); i++ { b = append(b, 0) } - asmScript.WriteString(fmt.Sprintf("%d", binary.LittleEndian.Uint32(b))) + asm.WriteString(fmt.Sprintf("%d", binary.LittleEndian.Uint32(b))) } else { asm.WriteString(hex.EncodeToString(p)) } From 20b08ee439b2c19aeb1f53038b20ea2655da9f06 Mon Sep 17 00:00:00 2001 From: Simon Ordish Date: Tue, 22 Mar 2022 16:30:45 +0100 Subject: [PATCH 04/13] Fixed an old test that was failing due to changes in ToAsm() method --- txjson_node_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/txjson_node_test.go b/txjson_node_test.go index 80be8daf..d6aa3ae4 100644 --- a/txjson_node_test.go +++ b/txjson_node_test.go @@ -109,7 +109,7 @@ func TestTxJSON_Node_MarshallJSON(t *testing.T) { "value": 0, "n": 0, "scriptPubKey": { - "asm": "OP_FALSE OP_RETURN 48656c6c6f", + "asm": "0 OP_RETURN 48656c6c6f", "hex": "006a0548656c6c6f", "type": "nulldata" } @@ -246,7 +246,7 @@ func TestTxJSON_Node_UnmarshalJSON(t *testing.T) { "value": 0, "n": 0, "scriptPubKey": { - "asm": "OP_FALSE OP_RETURN 48656c6c6f", + "asm": "0 OP_RETURN 48656c6c6f", "hex": "006a0548656c6c6f", "type": "nulldata" } @@ -355,7 +355,7 @@ func TestTxsJSON_Node_MarshallJSON(t *testing.T) { "value": 0, "n": 0, "scriptPubKey": { - "asm": "OP_FALSE OP_RETURN 48656c6c6f", + "asm": "0 OP_RETURN 48656c6c6f", "hex": "006a0548656c6c6f", "type": "nulldata" } From ac4c85ebae05cfe2be17d34b6569963a12de7a06 Mon Sep 17 00:00:00 2001 From: Simon Ordish Date: Thu, 24 Mar 2022 22:29:28 +0100 Subject: [PATCH 05/13] Explicity check error --- fees_test.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/fees_test.go b/fees_test.go index 502e0393..5c86708d 100644 --- a/fees_test.go +++ b/fees_test.go @@ -617,9 +617,11 @@ func TestFeeQuote_MarshalUnmarshalJSON(t *testing.T) { } for name, test := range tests { t.Run(name, func(t *testing.T) { - bb, _ := json.Marshal(test.quote) + bb, err := json.Marshal(test.quote) + assert.NoError(t, err) + var quote *FeeQuote - err := json.Unmarshal(bb, "e) + err = json.Unmarshal(bb, "e) if test.err != nil { assert.Error(t, err) assert.EqualError(t, err, test.err.Error()) From 67954e0358b752b74d966583732f70586a5332be Mon Sep 17 00:00:00 2001 From: Simon Ordish Date: Thu, 10 Nov 2022 09:08:39 +0000 Subject: [PATCH 06/13] Spelling --- examples/read_txs_from_block/read_txs_from_block.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/read_txs_from_block/read_txs_from_block.go b/examples/read_txs_from_block/read_txs_from_block.go index c03aee15..d16b40e2 100644 --- a/examples/read_txs_from_block/read_txs_from_block.go +++ b/examples/read_txs_from_block/read_txs_from_block.go @@ -12,7 +12,7 @@ import ( // In this example, all txs from a block are being read in via chunking, so at no point // does the entire block have to be held in memory, and instead can be streamed. // -// We represent the block by interatively reading a file, however it could be any data +// We represent the block by interactively reading a file, however it could be any data // stream that satisfies the io.Reader interface. func main() { @@ -27,7 +27,7 @@ func main() { r := bufio.NewReader(f) // Read file header. This step is specific to file reading and - // may need omitted or modified for other implentations. + // may need omitted or modified for other implementations. _, err = io.ReadFull(f, make([]byte, 80)) if err != nil { panic(err) From b951e0a38714cbf5e1a0049ab27365645fde0bab Mon Sep 17 00:00:00 2001 From: Simon Ordish Date: Thu, 10 Nov 2022 16:21:25 +0000 Subject: [PATCH 07/13] All transaction parsing functions now use one function. Added BIP-239 support (extended transaction format) --- bscript/interpreter/engine_test.go | 16 +-- bscript/interpreter/state.go | 6 +- input.go | 40 +++++++- input_test.go | 40 ++++---- signaturehash_test.go | 6 +- tx.go | 150 +++++++++++++++++------------ tx_test.go | 26 +++++ txinput.go | 69 +++++-------- txjson_test.go | 2 +- 9 files changed, 212 insertions(+), 143 deletions(-) diff --git a/bscript/interpreter/engine_test.go b/bscript/interpreter/engine_test.go index e0bd43fa..8cf695df 100644 --- a/bscript/interpreter/engine_test.go +++ b/bscript/interpreter/engine_test.go @@ -122,7 +122,7 @@ func TestCheckErrorCondition(t *testing.T) { tx: tx, }) if err != nil { - t.Errorf("failed to configure thread %w", err) + t.Errorf("failed to configure thread %v", err) } var done bool @@ -132,7 +132,7 @@ func TestCheckErrorCondition(t *testing.T) { t.Fatalf("failed to step %dth time: %v", i, err) } if done && i != len(*lscript)-1 { - t.Fatalf("finshed early on %dth time", i) + t.Fatalf("finished early on %dth time", i) } } err = vm.CheckErrorCondition(false) @@ -289,7 +289,7 @@ func TestValidateParams(t *testing.T) { }, expErr: errors.New("tx and previous output must be supplied for checksig"), }, - "provided locking script that differs from previoustxout's errors": { + "provided locking script that differs from previous txout's errors": { params: execOpts{ lockingScript: func() *bscript.Script { script, err := bscript.NewFromHexString("52529387") @@ -322,7 +322,7 @@ func TestValidateParams(t *testing.T) { }, expErr: errors.New("locking script does not match the previous outputs locking script"), }, - "provided unlocking scropt that differs from tx input's errors": { + "provided unlocking script that differs from tx input's errors": { params: execOpts{ lockingScript: func() *bscript.Script { script, err := bscript.NewFromHexString("76a91454807ccc44c0eec0b0e187b3ce0e137e9c6cd65d88ac") @@ -501,7 +501,7 @@ func TestCheckPubKeyEncoding(t *testing.T) { "when it should have succeeded: %v", test.name, err) } else if err == nil && !test.isValid { - t.Errorf("checkSignatureEncooding test '%s' succeeded "+ + t.Errorf("checkSignatureEncoding test '%s' succeeded "+ "when it should have failed", test.name) } } @@ -673,7 +673,7 @@ func TestCheckSignatureEncoding(t *testing.T) { "when it should have succeeded: %v", test.name, err) } else if err == nil && !test.isValid { - t.Errorf("checkSignatureEncooding test '%s' succeeded "+ + t.Errorf("checkSignatureEncoding test '%s' succeeded "+ "when it should have failed", test.name) } } @@ -845,7 +845,7 @@ func TestEngine_WithState(t *testing.T) { CondStack: []int{}, ElseStack: [][]byte{}, Flags: scriptflag.UTXOAfterGenesis | scriptflag.EnableSighashForkID, - LastCodeSeperatorIdx: 0, + LastCodeSeparatorIdx: 0, NumOps: 3, SavedFirstStack: [][]byte{}, Scripts: func() []ParsedScript { @@ -884,7 +884,7 @@ func TestEngine_WithState(t *testing.T) { CondStack: []int{}, ElseStack: [][]byte{}, Flags: scriptflag.UTXOAfterGenesis | scriptflag.EnableSighashForkID, - LastCodeSeperatorIdx: 0, + LastCodeSeparatorIdx: 0, NumOps: 8, SavedFirstStack: [][]byte{}, Scripts: func() []ParsedScript { diff --git a/bscript/interpreter/state.go b/bscript/interpreter/state.go index a538ba35..d3780f7a 100644 --- a/bscript/interpreter/state.go +++ b/bscript/interpreter/state.go @@ -12,7 +12,7 @@ type State struct { Scripts []ParsedScript ScriptIdx int OpcodeIdx int - LastCodeSeperatorIdx int + LastCodeSeparatorIdx int NumOps int Flags scriptflag.Flag IsFinished bool @@ -66,7 +66,7 @@ func (t *thread) State() *State { Scripts: make([]ParsedScript, len(t.scripts)), ScriptIdx: scriptIdx, OpcodeIdx: offsetIdx, - LastCodeSeperatorIdx: t.lastCodeSep, + LastCodeSeparatorIdx: t.lastCodeSep, NumOps: t.numOps, Flags: t.flags, IsFinished: t.scriptIdx > scriptIdx, @@ -127,7 +127,7 @@ func (t *thread) SetState(state *State) { t.scripts = state.Scripts t.scriptIdx = state.ScriptIdx t.scriptOff = state.OpcodeIdx - t.lastCodeSep = state.LastCodeSeperatorIdx + t.lastCodeSep = state.LastCodeSeparatorIdx t.numOps = state.NumOps t.flags = state.Flags t.afterGenesis = state.Genesis.AfterGenesis diff --git a/input.go b/input.go index 93452bd8..c1e5db0b 100644 --- a/input.go +++ b/input.go @@ -28,7 +28,6 @@ const DefaultSequenceNumber uint32 = 0xFFFFFFFF // Input is a representation of a transaction input // // DO NOT CHANGE ORDER - Optimised for memory via maligned -// type Input struct { previousTxID []byte PreviousTxSatoshis uint64 @@ -40,6 +39,14 @@ type Input struct { // ReadFrom reads from the `io.Reader` into the `bt.Input`. func (i *Input) ReadFrom(r io.Reader) (int64, error) { + return i.readFrom(r, false) +} + +func (i *Input) ReadFromExtended(r io.Reader) (int64, error) { + return i.readFrom(r, true) +} + +func (i *Input) readFrom(r io.Reader, extended bool) (int64, error) { *i = Input{} var bytesRead int64 @@ -83,6 +90,37 @@ func (i *Input) ReadFrom(r io.Reader) (int64, error) { i.UnlockingScript = bscript.NewFromBytes(script) i.SequenceNumber = binary.LittleEndian.Uint32(sequence) + if extended { + prevSatoshis := make([]byte, 8) + var prevTxLockingScript bscript.Script + + n, err = io.ReadFull(r, prevSatoshis) + bytesRead += int64(n) + if err != nil { + return bytesRead, errors.Wrapf(err, "prevSatoshis(8): got %d bytes", n) + } + + // Read in the prevTxLockingScript + var scriptLen VarInt + n64, err := scriptLen.ReadFrom(r) + bytesRead += n64 + if err != nil { + return bytesRead, err + } + + script := make([]byte, scriptLen) + n, err := io.ReadFull(r, script) + bytesRead += int64(n) + if err != nil { + return bytesRead, errors.Wrapf(err, "script(%d): got %d bytes", scriptLen.Length(), n) + } + + prevTxLockingScript = *bscript.NewFromBytes(script) + + i.PreviousTxSatoshis = binary.LittleEndian.Uint64(prevSatoshis) + i.PreviousTxScript = bscript.NewFromBytes(prevTxLockingScript) + } + return bytesRead, nil } diff --git a/input_test.go b/input_test.go index 1388654d..0fe9a76c 100644 --- a/input_test.go +++ b/input_test.go @@ -1,13 +1,14 @@ package bt import ( + "bytes" "encoding/hex" "testing" "github.com/stretchr/testify/assert" ) -func TestNewInputFromBytes(t *testing.T) { +func TestNewInputFromReader(t *testing.T) { t.Parallel() t.Run("valid tx", func(t *testing.T) { @@ -15,36 +16,38 @@ func TestNewInputFromBytes(t *testing.T) { b, err := hex.DecodeString(rawHex) assert.NoError(t, err) - var i *Input - var s int - i, s, err = newInputFromBytes(b) + i := &Input{} + var s int64 + s, err = i.readFrom(bytes.NewReader(b), false) + assert.NoError(t, err) assert.NotNil(t, i) - assert.Equal(t, 148, s) + assert.Equal(t, int64(148), s) assert.Equal(t, uint32(1), i.PreviousTxOutIndex) assert.Equal(t, 107, len(*i.UnlockingScript)) assert.Equal(t, DefaultSequenceNumber, i.SequenceNumber) }) t.Run("empty bytes", func(t *testing.T) { - i, s, err := newInputFromBytes([]byte("")) + i := &Input{} + + s, err := i.readFrom(bytes.NewReader([]byte("")), false) assert.Error(t, err) - assert.Nil(t, i) - assert.Equal(t, 0, s) + assert.Equal(t, int64(0), s) }) t.Run("invalid input, too short", func(t *testing.T) { - i, s, err := newInputFromBytes([]byte("invalid")) + i := &Input{} + s, err := i.readFrom(bytes.NewReader([]byte("invalid")), false) assert.Error(t, err) - assert.Nil(t, i) - assert.Equal(t, 0, s) + assert.Equal(t, int64(7), s) }) t.Run("invalid input, too short + script", func(t *testing.T) { - i, s, err := newInputFromBytes([]byte("000000000000000000000000000000000000000000000000000000000000000000000000")) + i := &Input{} + s, err := i.readFrom(bytes.NewReader([]byte("000000000000000000000000000000000000000000000000000000000000000000000000")), false) assert.Error(t, err) - assert.Nil(t, i) - assert.Equal(t, 0, s) + assert.Equal(t, int64(72), s) }) } @@ -54,12 +57,13 @@ func TestInput_String(t *testing.T) { b, err := hex.DecodeString(rawHex) assert.NoError(t, err) - var i *Input - var s int - i, s, err = newInputFromBytes(b) + i := &Input{} + var s int64 + + s, err = i.readFrom(bytes.NewReader(b), false) assert.NoError(t, err) assert.NotNil(t, i) - assert.Equal(t, 148, s) + assert.Equal(t, int64(148), s) assert.Equal(t, "prevTxHash: 6fc75f30a085f3313265b92c818082f9768c13b8a1a107b484023ecf63c86e4c\nprevOutIndex: 1\nscriptLen: 107\nscript: 483045022100f01c1a1679c9437398d691c8497f278fa2d615efc05115688bf2c3335b45c88602201b54437e54fb53bc50545de44ea8c64e9e583952771fcc663c8687dc2638f7854121037e87bbd3b680748a74372640628a8f32d3a841ceeef6f75626ab030c1a04824f\nsequence: ffffffff\n", diff --git a/signaturehash_test.go b/signaturehash_test.go index 08c18d43..c0a0da28 100644 --- a/signaturehash_test.go +++ b/signaturehash_test.go @@ -60,7 +60,7 @@ func TestTx_CalcInputPreimage(t *testing.T) { assert.NoError(t, err) assert.NotNil(t, tx) - // Add the UTXO amount and script (PreviousTxScript already in unsiged tx) + // Add the UTXO amount and script (PreviousTxScript already in unsigned tx) tx.InputIdx(test.index).PreviousTxSatoshis = test.previousTxSatoshis tx.InputIdx(test.index).PreviousTxScript, err = bscript.NewFromHexString(test.previousTxScript) assert.NoError(t, err) @@ -150,7 +150,7 @@ func TestTx_CalcInputSignatureHash(t *testing.T) { assert.NoError(t, err) assert.NotNil(t, tx) - // Add the UTXO amount and script (PreviousTxScript already in unsiged tx) + // Add the UTXO amount and script (PreviousTxScript already in unsigned tx) tx.Inputs[test.index].PreviousTxSatoshis = test.previousTxSatoshis tx.Inputs[test.index].PreviousTxScript, err = bscript.NewFromHexString(test.previousTxScript) assert.NoError(t, err) @@ -213,7 +213,7 @@ func TestTx_CalcInputPreimageLegacy(t *testing.T) { assert.NoError(t, err) assert.NotNil(t, tx) - // Add the UTXO amount and script (PreviousTxScript already in unsiged tx) + // Add the UTXO amount and script (PreviousTxScript already in unsigned tx) tx.InputIdx(test.index).PreviousTxSatoshis = test.previousTxSatoshis tx.InputIdx(test.index).PreviousTxScript, err = bscript.NewFromHexString(test.previousTxScript) assert.NoError(t, err) diff --git a/tx.go b/tx.go index 232034f4..dfa0043b 100644 --- a/tx.go +++ b/tx.go @@ -36,7 +36,6 @@ lock_time if non-zero and sequence numbers are < 0xFFFFFFFF: block height // Tx wraps a bitcoin transaction // // DO NOT CHANGE ORDER - Optimised memory via malign -// type Tx struct { Inputs []*Input Outputs []*Output @@ -82,50 +81,11 @@ func NewTxFromBytes(b []byte) (*Tx, error) { // Despite the name, this is not actually reading a stream in the true sense: it is a byte slice that contains // many transactions one after another. func NewTxFromStream(b []byte) (*Tx, int, error) { - if len(b) < 10 { - return nil, 0, ErrTxTooShort - } - - var offset int - t := Tx{ - Version: binary.LittleEndian.Uint32(b[offset:4]), - } - offset += 4 - - inputCount, size := NewVarIntFromBytes(b[offset:]) - offset += size + tx := Tx{} - // create Inputs - var i uint64 - var err error - var input *Input - for ; i < uint64(inputCount); i++ { - input, size, err = newInputFromBytes(b[offset:]) - if err != nil { - return nil, 0, err - } - offset += size - t.addInput(input) - } + bytesRead, err := tx.ReadFrom(bytes.NewReader(b)) - // create Outputs - var outputCount VarInt - var output *Output - outputCount, size = NewVarIntFromBytes(b[offset:]) - offset += size - for i = 0; i < uint64(outputCount); i++ { - output, size, err = newOutputFromBytes(b[offset:]) - if err != nil { - return nil, 0, err - } - offset += size - t.AddOutput(output) - } - - t.LockTime = binary.LittleEndian.Uint32(b[offset:]) - offset += 4 - - return &t, offset, nil + return &tx, int(bytesRead), err } // ReadFrom reads from the `io.Reader` into the `bt.Tx`. @@ -142,6 +102,8 @@ func (tx *Tx) ReadFrom(r io.Reader) (int64, error) { tx.Version = binary.LittleEndian.Uint32(version) + extended := false + var inputCount VarInt n64, err := inputCount.ReadFrom(r) bytesRead += n64 @@ -149,10 +111,45 @@ func (tx *Tx) ReadFrom(r io.Reader) (int64, error) { return bytesRead, err } + var outputCount VarInt + locktime := make([]byte, 4) + + if inputCount == 0 { + // The next bytes are either 0xEF or a varint that specifies the number of outputs + // Read 1 more byte to see if this is in extended format... + n64, err = outputCount.ReadFrom(r) + bytesRead += n64 + if err != nil { + return bytesRead, err + } + + if outputCount == 0 { + // Read in lock time + n, err = io.ReadFull(r, locktime) + bytesRead += int64(n) + if err != nil { + return bytesRead, err + } + + if binary.BigEndian.Uint32(locktime) != 0xEF { + tx.LockTime = binary.LittleEndian.Uint32(locktime) + return bytesRead, nil + } + + extended = true + + n64, err := inputCount.ReadFrom(r) + bytesRead += n64 + if err != nil { + return bytesRead, err + } + } + } + // create Inputs for i := uint64(0); i < uint64(inputCount); i++ { - input := new(Input) - n64, err = input.ReadFrom(r) + input := &Input{} + n64, err = input.readFrom(r, extended) bytesRead += n64 if err != nil { return bytesRead, err @@ -160,11 +157,13 @@ func (tx *Tx) ReadFrom(r io.Reader) (int64, error) { tx.Inputs = append(tx.Inputs, input) } - var outputCount VarInt - n64, err = outputCount.ReadFrom(r) - bytesRead += n64 - if err != nil { - return bytesRead, err + if inputCount > 0 || extended { + // Re-read the actual output count... + n64, err = outputCount.ReadFrom(r) + bytesRead += n64 + if err != nil { + return bytesRead, err + } } for i := uint64(0); i < uint64(outputCount); i++ { @@ -178,7 +177,6 @@ func (tx *Tx) ReadFrom(r io.Reader) (int64, error) { tx.Outputs = append(tx.Outputs, output) } - locktime := make([]byte, 4) n, err = io.ReadFull(r, locktime) bytesRead += int64(n) if err != nil { @@ -297,13 +295,17 @@ func IsValidTxID(txid []byte) bool { // Bytes encodes the transaction into a byte array. // See https://chainquery.com/bitcoin-cli/decoderawtransaction func (tx *Tx) Bytes() []byte { - return tx.toBytesHelper(0, nil) + return tx.toBytesHelper(0, nil, false) +} + +func (tx *Tx) ExtendedBytes() []byte { + return tx.toBytesHelper(0, nil, true) } // BytesWithClearedInputs encodes the transaction into a byte array but clears its Inputs first. // This is used when signing transactions. func (tx *Tx) BytesWithClearedInputs(index int, lockingScript []byte) []byte { - return tx.toBytesHelper(index, lockingScript) + return tx.toBytesHelper(index, lockingScript, false) } // Clone returns a clone of the tx @@ -322,11 +324,13 @@ func (tx *Tx) Clone() *Tx { // NodeJSON returns a wrapped *bt.Tx for marshalling/unmarshalling into a node tx format. // // Marshalling usage example: -// bb, err := json.Marshal(tx.NodeJSON()) +// +// bb, err := json.Marshal(tx.NodeJSON()) // // Unmarshalling usage example: -// tx := bt.NewTx() -// if err := json.Unmarshal(bb, tx.NodeJSON()); err != nil {} +// +// tx := bt.NewTx() +// if err := json.Unmarshal(bb, tx.NodeJSON()); err != nil {} func (tx *Tx) NodeJSON() interface{} { return &nodeTxWrapper{Tx: tx} } @@ -334,20 +338,26 @@ func (tx *Tx) NodeJSON() interface{} { // NodeJSON returns a wrapped bt.Txs for marshalling/unmarshalling into a node tx format. // // Marshalling usage example: -// bb, err := json.Marshal(txs.NodeJSON()) +// +// bb, err := json.Marshal(txs.NodeJSON()) // // Unmarshalling usage example: -// var txs bt.Txs -// if err := json.Unmarshal(bb, txs.NodeJSON()); err != nil {} +// +// var txs bt.Txs +// if err := json.Unmarshal(bb, txs.NodeJSON()); err != nil {} func (tt *Txs) NodeJSON() interface{} { return (*nodeTxsWrapper)(tt) } -func (tx *Tx) toBytesHelper(index int, lockingScript []byte) []byte { +func (tx *Tx) toBytesHelper(index int, lockingScript []byte, extended bool) []byte { h := make([]byte, 0) h = append(h, LittleEndianBytes(tx.Version, 4)...) + if extended { + h = append(h, []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0xEF}...) + } + h = append(h, VarInt(uint64(len(tx.Inputs))).Bytes()...) for i, in := range tx.Inputs { @@ -358,6 +368,20 @@ func (tx *Tx) toBytesHelper(index int, lockingScript []byte) []byte { } else { h = append(h, s...) } + + if extended { + b := make([]byte, 8) + binary.LittleEndian.PutUint64(b, in.PreviousTxSatoshis) + h = append(h, b...) + + if in.PreviousTxScript != nil { + l := uint64(len(*in.PreviousTxScript)) + h = append(h, VarInt(l).Bytes()...) + h = append(h, *in.PreviousTxScript...) + } else { + h = append(h, 0x00) // The length of the script is zero + } + } } h = append(h, VarInt(uint64(len(tx.Outputs))).Bytes()...) @@ -524,12 +548,12 @@ func (tx *Tx) feesPaid(size *TxSize, fees *FeeQuote) (*TxFees, error) { return nil, err } - resp := &TxFees{ + txFees := &TxFees{ StdFeePaid: size.TotalStdBytes * uint64(stdFee.MiningFee.Satoshis) / uint64(stdFee.MiningFee.Bytes), DataFeePaid: size.TotalDataBytes * uint64(dataFee.MiningFee.Satoshis) / uint64(dataFee.MiningFee.Bytes), } - resp.TotalFeePaid = resp.StdFeePaid + resp.DataFeePaid - return resp, nil + txFees.TotalFeePaid = txFees.StdFeePaid + txFees.DataFeePaid + return txFees, nil } diff --git a/tx_test.go b/tx_test.go index 240778da..4c49e657 100644 --- a/tx_test.go +++ b/tx_test.go @@ -18,6 +18,7 @@ import ( "github.com/libsv/go-bt/v2/testing/data" "github.com/libsv/go-bt/v2/unlocker" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestNewTx(t *testing.T) { @@ -1189,3 +1190,28 @@ func TestTxs_ReadFrom(t *testing.T) { assert.Equal(t, "b7c59d7fa17a74bbe0a05e5381f42b9ac7fe23b8a1ca40005a74802fe5b8bb5a", txs[len(txs)-1].TxID()) assert.Equal(t, int64(340219), bytesRead) } + +func TestExtendedFormat(t *testing.T) { + tx, err := bt.NewTxFromString("0100000001478a4ac0c8e4dae42db983bc720d95ed2099dec4c8c3f2d9eedfbeb74e18cdbb1b0100006b483045022100b05368f9855a28f21d3cb6f3e278752d3c5202f1de927862bbaaf5ef7d67adc50220728d4671cd4c34b1fa28d15d5cd2712b68166ea885522baa35c0b9e399fe9ed74121030d4ad284751daf629af387b1af30e02cf5794139c4e05836b43b1ca376624f7fffffffff01000000000000000070006a0963657274696861736822314c6d763150594d70387339594a556e374d3948565473446b64626155386b514e4a406164386337373536356335363935353261626463636634646362353537376164633936633866613933623332663630373865353664666232326265623766353600000000") + if err != nil { + t.Error(err) + return + } + + require.Equal(t, "e6adcaf6b86fb5d690a3bade36011cd02f80dd364f1ecf2bb04902aa1b6bf455", tx.TxID()) + + tx.Inputs[0].PreviousTxSatoshis = 16 + s, _ := hex.DecodeString("76a9140c77a935b45abdcf3e472606d3bc647c5cc0efee88ac") + tx.Inputs[0].PreviousTxScript = bscript.NewFromBytes(s) + + tx2, err := bt.NewTxFromBytes(tx.ExtendedBytes()) + if err != nil { + t.Error(err) + return + } + + require.Equal(t, "e6adcaf6b86fb5d690a3bade36011cd02f80dd364f1ecf2bb04902aa1b6bf455", tx2.TxID()) + + assert.Equal(t, uint64(16), tx2.Inputs[0].PreviousTxSatoshis) + assert.Equal(t, s, []byte(*tx2.Inputs[0].PreviousTxScript)) +} diff --git a/txinput.go b/txinput.go index b12caf39..0f787f0b 100644 --- a/txinput.go +++ b/txinput.go @@ -23,30 +23,6 @@ import ( // It is expected that bt.ErrNoUTXO will be returned once the utxo source is depleted. type UTXOGetterFunc func(ctx context.Context, deficit uint64) ([]*UTXO, error) -// newInputFromBytes returns a transaction input from the bytes provided. -func newInputFromBytes(bytes []byte) (*Input, int, error) { - if len(bytes) < 36 { - return nil, 0, fmt.Errorf("%w < 36", ErrInputTooShort) - } - - offset := 36 - l, size := NewVarIntFromBytes(bytes[offset:]) - offset += size - - totalLength := offset + int(l) + 4 // 4 bytes for nSeq - - if len(bytes) < totalLength { - return nil, 0, fmt.Errorf("%w < 36 + script + 4", ErrInputTooShort) - } - - return &Input{ - previousTxID: ReverseBytes(bytes[0:32]), - PreviousTxOutIndex: binary.LittleEndian.Uint32(bytes[32:36]), - SequenceNumber: binary.LittleEndian.Uint32(bytes[offset+int(l):]), - UnlockingScript: bscript.NewFromBytes(bytes[offset : offset+int(l)]), - }, totalLength, nil -} - // TotalInputSatoshis returns the total Satoshis inputted to the transaction. func (tx *Tx) TotalInputSatoshis() (total uint64) { for _, in := range tx.Inputs { @@ -137,25 +113,26 @@ func (tx *Tx) FromUTXOs(utxos ...*UTXO) error { // If insufficient utxos are provided from the UTXOGetterFunc, a bt.ErrInsufficientFunds is returned. // // Example usage: -// if err := tx.Fund(ctx, bt.NewFeeQuote(), func(ctx context.Context, deficit satoshis) ([]*bt.UTXO, error) { -// utxos := make([]*bt.UTXO, 0) -// for _, f := range funds { -// deficit -= satoshis -// utxos := append(utxos, &bt.UTXO{ -// TxID: f.TxID, -// Vout: f.Vout, -// LockingScript: f.Script, -// Satoshis: f.Satoshis, -// }) -// if deficit == 0 { -// return utxos, nil -// } -// } -// return nil, bt.ErrNoUTXO -// }); err != nil { -// if errors.Is(err, bt.ErrInsufficientFunds) { /* handle */ } -// return err -// } +// +// if err := tx.Fund(ctx, bt.NewFeeQuote(), func(ctx context.Context, deficit satoshis) ([]*bt.UTXO, error) { +// utxos := make([]*bt.UTXO, 0) +// for _, f := range funds { +// deficit -= satoshis +// utxos := append(utxos, &bt.UTXO{ +// TxID: f.TxID, +// Vout: f.Vout, +// LockingScript: f.Script, +// Satoshis: f.Satoshis, +// }) +// if deficit == 0 { +// return utxos, nil +// } +// } +// return nil, bt.ErrNoUTXO +// }); err != nil { +// if errors.Is(err, bt.ErrInsufficientFunds) { /* handle */ } +// return err +// } func (tx *Tx) Fund(ctx context.Context, fq *FeeQuote, next UTXOGetterFunc) error { deficit, err := tx.estimateDeficit(fq) if err != nil { @@ -234,7 +211,7 @@ func (tx *Tx) InsertInputUnlockingScript(index uint32, s *bscript.Script) error // It takes an Unlocker interface as a parameter so that different // unlocking implementations can be used to unlock the transaction - // for example local or external unlocking (hardware wallet), or -// signature/nonsignature based. +// signature / non-signature based. func (tx *Tx) FillInput(ctx context.Context, unlocker Unlocker, params UnlockerParams) error { if unlocker == nil { return ErrNoUnlocker @@ -244,12 +221,12 @@ func (tx *Tx) FillInput(ctx context.Context, unlocker Unlocker, params UnlockerP params.SigHashFlags = sighash.AllForkID } - uscript, err := unlocker.UnlockingScript(ctx, tx, params) + unlockingScript, err := unlocker.UnlockingScript(ctx, tx, params) if err != nil { return err } - return tx.InsertInputUnlockingScript(params.InputIdx, uscript) + return tx.InsertInputUnlockingScript(params.InputIdx, unlockingScript) } // FillAllInputs is used to sign all inputs. It takes an UnlockerGetter interface diff --git a/txjson_test.go b/txjson_test.go index 1227175b..254bc492 100644 --- a/txjson_test.go +++ b/txjson_test.go @@ -17,7 +17,7 @@ func TestTx_JSON(t *testing.T) { tx *bt.Tx err error }{ - "standard tx should marshal and unmarshall correctly": { + "standard tx should marshal and unmarshal correctly": { tx: func() *bt.Tx { tx := bt.NewTx() assert.NoError(t, tx.From( From 5a1361023b5e1d6ad25d17752e17ff832ca4126c Mon Sep 17 00:00:00 2001 From: Simon Ordish Date: Thu, 10 Nov 2022 18:43:09 +0000 Subject: [PATCH 08/13] Add comments and extra test --- input.go | 1 + tx.go | 2 ++ tx_test.go | 6 ++++++ 3 files changed, 9 insertions(+) diff --git a/input.go b/input.go index c1e5db0b..b308b1da 100644 --- a/input.go +++ b/input.go @@ -42,6 +42,7 @@ func (i *Input) ReadFrom(r io.Reader) (int64, error) { return i.readFrom(r, false) } +// ReadFromExtended reads the `io.Reader` into the `bt.Input` when the reader is consuming an extended format transaction. func (i *Input) ReadFromExtended(r io.Reader) (int64, error) { return i.readFrom(r, true) } diff --git a/tx.go b/tx.go index dfa0043b..ccaa38d2 100644 --- a/tx.go +++ b/tx.go @@ -298,6 +298,8 @@ func (tx *Tx) Bytes() []byte { return tx.toBytesHelper(0, nil, false) } +// ExtendedBytes outputs the transaction into a byte array in extended format +// (with PreviousTxSatoshis and PreviousTXScript included) func (tx *Tx) ExtendedBytes() []byte { return tx.toBytesHelper(0, nil, true) } diff --git a/tx_test.go b/tx_test.go index 4c49e657..b747dd64 100644 --- a/tx_test.go +++ b/tx_test.go @@ -1215,3 +1215,9 @@ func TestExtendedFormat(t *testing.T) { assert.Equal(t, uint64(16), tx2.Inputs[0].PreviousTxSatoshis) assert.Equal(t, s, []byte(*tx2.Inputs[0].PreviousTxScript)) } + +func TestFromNodeJS(t *testing.T) { + _, err := bt.NewTxFromString("010000000000000000ef01478a4ac0c8e4dae42db983bc720d95ed2099dec4c8c3f2d9eedfbeb74e18cdbb1b0100006b483045022100b05368f9855a28f21d3cb6f3e278752d3c5202f1de927862bbaaf5ef7d67adc50220728d4671cd4c34b1fa28d15d5cd2712b68166ea885522baa35c0b9e399fe9ed74121030d4ad284751daf629af387b1af30e02cf5794139c4e05836b43b1ca376624f7fffffffff10000000000000001976a9140c77a935b45abdcf3e472606d3bc647c5cc0efee88ac01000000000000000070006a0963657274696861736822314c6d763150594d70387339594a556e374d3948565473446b64626155386b514e4a406164386337373536356335363935353261626463636634646362353537376164633936633866613933623332663630373865353664666232326265623766353600000000") + + require.NoError(t, err) +} From 31354a4623147947b1dc35f57251cfe5530c34f3 Mon Sep 17 00:00:00 2001 From: Simon Ordish Date: Fri, 11 Nov 2022 11:01:32 +0000 Subject: [PATCH 09/13] Overcome linter --- tx.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tx.go b/tx.go index ccaa38d2..adc6cda9 100644 --- a/tx.go +++ b/tx.go @@ -117,8 +117,9 @@ func (tx *Tx) ReadFrom(r io.Reader) (int64, error) { if inputCount == 0 { // The next bytes are either 0xEF or a varint that specifies the number of outputs // Read 1 more byte to see if this is in extended format... - n64, err = outputCount.ReadFrom(r) - bytesRead += n64 + var o64 int64 + o64, err = outputCount.ReadFrom(r) + bytesRead += o64 if err != nil { return bytesRead, err } @@ -466,7 +467,7 @@ func (tx *Tx) estimatedFinalTx() (*Tx, error) { return nil, ErrUnsupportedScript } if in.UnlockingScript == nil || len(*in.UnlockingScript) == 0 { - // nolint:lll // insert dummy p2pkh unlocking script (sig + pubkey) + //nolint:lll // insert dummy p2pkh unlocking script (sig + pubkey) dummyUnlockingScript, _ := hex.DecodeString("4830450221009c13cbcbb16f2cfedc7abf3a4af1c3fe77df1180c0e7eee30d9bcc53ebda39da02207b258005f1bc3cf9dffa06edb358d6db2bcfc87f50516fac8e3f4686fc2a03df412103107feff22788a1fc8357240bf450fd7bca4bd45d5f8bac63818c5a7b67b03876") in.UnlockingScript = bscript.NewFromBytes(dummyUnlockingScript) } From 141265f9e86ebaeee45ad81979fac3537e8426d0 Mon Sep 17 00:00:00 2001 From: Simon Ordish Date: Fri, 11 Nov 2022 11:02:34 +0000 Subject: [PATCH 10/13] Linter workarounds --- bscript/address.go | 4 ++-- bscript/interpreter/operations.go | 2 +- sighash/flag.go | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/bscript/address.go b/bscript/address.go index 6b7455f7..a8bf214b 100644 --- a/bscript/address.go +++ b/bscript/address.go @@ -83,7 +83,7 @@ func NewAddressFromPublicKeyHash(hash []byte, mainnet bool) (*Address, error) { if !mainnet { bb[0] = 111 } - // nolint: makezero // we need to set up the array with 1 + //nolint: makezero // we need to set up the array with 1 bb = append(bb, hash...) return &Address{ @@ -105,7 +105,7 @@ func NewAddressFromPublicKey(pubKey *bec.PublicKey, mainnet bool) (*Address, err if !mainnet { bb[0] = 111 } - // nolint: makezero // we need to set up the array with 1 + //nolint: makezero // we need to set up the array with 1 bb = append(bb, hash...) return &Address{ diff --git a/bscript/interpreter/operations.go b/bscript/interpreter/operations.go index 487e25ce..f3688ab0 100644 --- a/bscript/interpreter/operations.go +++ b/bscript/interpreter/operations.go @@ -1836,7 +1836,7 @@ func opcodeSha1(op *ParsedOpcode, t *thread) error { return err } - hash := sha1.Sum(buf) // nolint:gosec // operation is for sha1 + hash := sha1.Sum(buf) //nolint:gosec // operation is for sha1 t.dstack.PushByteArray(hash[:]) return nil } diff --git a/sighash/flag.go b/sighash/flag.go index a4ecddaf..ca615932 100644 --- a/sighash/flag.go +++ b/sighash/flag.go @@ -40,7 +40,7 @@ func (f Flag) HasWithMask(shf Flag) bool { } func (f Flag) String() string { - switch f { // nolint:exhaustive // not needed + switch f { //nolint:exhaustive // not needed case All: return "ALL" case None: From 00c923196b8799d8f033f02a19f7ec6051aa589c Mon Sep 17 00:00:00 2001 From: Simon Ordish Date: Fri, 11 Nov 2022 11:07:43 +0000 Subject: [PATCH 11/13] Linter fix --- input.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/input.go b/input.go index b308b1da..0601809d 100644 --- a/input.go +++ b/input.go @@ -42,7 +42,8 @@ func (i *Input) ReadFrom(r io.Reader) (int64, error) { return i.readFrom(r, false) } -// ReadFromExtended reads the `io.Reader` into the `bt.Input` when the reader is consuming an extended format transaction. +// ReadFromExtended reads the `io.Reader` into the `bt.Input` when the reader is +// consuming an extended format transaction. func (i *Input) ReadFromExtended(r io.Reader) (int64, error) { return i.readFrom(r, true) } From 548edc89c3b8a23fe90934ea2c2980cbffb8494e Mon Sep 17 00:00:00 2001 From: Simon Ordish Date: Fri, 11 Nov 2022 11:20:35 +0000 Subject: [PATCH 12/13] More linting workarounds --- bscript/address.go | 1 + bscript/interpreter/debug/debugger.go | 18 ++++++++++-------- bscript/interpreter/errs/error.go | 9 +++++---- bscript/interpreter/scriptflag/scriptflag.go | 1 + sighash/flag.go | 1 + testing/data/data.go | 1 + tx.go | 13 ++++++++----- unlocker/simple.go | 1 + 8 files changed, 28 insertions(+), 17 deletions(-) diff --git a/bscript/address.go b/bscript/address.go index a8bf214b..b530be89 100644 --- a/bscript/address.go +++ b/bscript/address.go @@ -1,3 +1,4 @@ +// Package bscript comment package bscript import ( diff --git a/bscript/interpreter/debug/debugger.go b/bscript/interpreter/debug/debugger.go index 49dcc7f1..d3d880d2 100644 --- a/bscript/interpreter/debug/debugger.go +++ b/bscript/interpreter/debug/debugger.go @@ -1,3 +1,4 @@ +// Package debug comment package debug import "github.com/libsv/go-bt/v2/bscript/interpreter" @@ -66,14 +67,15 @@ type debugger struct { // functions. // // Example usage: -// debugger := debug.NewDebugger() -// debugger.AttachBeforeExecuteOpcode(func (state *interpreter.State) { -// fmt.Println(state.DataStack) -// }) -// debugger.AttachAfterStackPush(func (state *interpreter.State, data []byte) { -// fmt.Println(hex.EncodeToString(data)) -// }) -// engine.Execute(interpreter.WithDebugger(debugger)) +// +// debugger := debug.NewDebugger() +// debugger.AttachBeforeExecuteOpcode(func (state *interpreter.State) { +// fmt.Println(state.DataStack) +// }) +// debugger.AttachAfterStackPush(func (state *interpreter.State, data []byte) { +// fmt.Println(hex.EncodeToString(data)) +// }) +// engine.Execute(interpreter.WithDebugger(debugger)) func NewDebugger(oo ...DebuggerOptionFunc) DefaultDebugger { opts := &debugOpts{} for _, o := range oo { diff --git a/bscript/interpreter/errs/error.go b/bscript/interpreter/errs/error.go index 72383ffc..7f69d0f8 100644 --- a/bscript/interpreter/errs/error.go +++ b/bscript/interpreter/errs/error.go @@ -2,6 +2,7 @@ // Use of this source code is governed by an ISC // license that can be found in the LICENSE file. +// Package errs comment package errs import ( @@ -405,10 +406,10 @@ func (e ErrorCode) String() string { // Error identifies a script-related error. It is used to indicate three // classes of errors: -// 1) Script execution failures due to violating one of the many requirements -// imposed by the script engine or evaluating to false -// 2) Improper API usage by callers -// 3) Internal consistency check failures +// 1. Script execution failures due to violating one of the many requirements +// imposed by the script engine or evaluating to false +// 2. Improper API usage by callers +// 3. Internal consistency check failures // // The caller can use type assertions on the returned errors to access the // ErrorCode field to ascertain the specific reason for the error. As an diff --git a/bscript/interpreter/scriptflag/scriptflag.go b/bscript/interpreter/scriptflag/scriptflag.go index 9f969f58..4e3a4a44 100644 --- a/bscript/interpreter/scriptflag/scriptflag.go +++ b/bscript/interpreter/scriptflag/scriptflag.go @@ -1,3 +1,4 @@ +// Package scriptflag comment package scriptflag // Flag is a bitmask defining additional operations or tests that will be diff --git a/sighash/flag.go b/sighash/flag.go index ca615932..2b5d8d33 100644 --- a/sighash/flag.go +++ b/sighash/flag.go @@ -1,3 +1,4 @@ +// Package sighash comment package sighash // Flag represents hash type bits at the end of a signature. diff --git a/testing/data/data.go b/testing/data/data.go index 6b651d20..fc780c02 100644 --- a/testing/data/data.go +++ b/testing/data/data.go @@ -1,3 +1,4 @@ +// Package data comment package data import ( diff --git a/tx.go b/tx.go index adc6cda9..ce7666b1 100644 --- a/tx.go +++ b/tx.go @@ -93,6 +93,9 @@ func (tx *Tx) ReadFrom(r io.Reader) (int64, error) { *tx = Tx{} var bytesRead int64 + var n64 int64 + var err error + version := make([]byte, 4) n, err := io.ReadFull(r, version) bytesRead += int64(n) @@ -105,7 +108,8 @@ func (tx *Tx) ReadFrom(r io.Reader) (int64, error) { extended := false var inputCount VarInt - n64, err := inputCount.ReadFrom(r) + + n64, err = inputCount.ReadFrom(r) bytesRead += n64 if err != nil { return bytesRead, err @@ -117,9 +121,8 @@ func (tx *Tx) ReadFrom(r io.Reader) (int64, error) { if inputCount == 0 { // The next bytes are either 0xEF or a varint that specifies the number of outputs // Read 1 more byte to see if this is in extended format... - var o64 int64 - o64, err = outputCount.ReadFrom(r) - bytesRead += o64 + n64, err = outputCount.ReadFrom(r) + bytesRead += n64 if err != nil { return bytesRead, err } @@ -139,7 +142,7 @@ func (tx *Tx) ReadFrom(r io.Reader) (int64, error) { extended = true - n64, err := inputCount.ReadFrom(r) + n64, err = inputCount.ReadFrom(r) bytesRead += n64 if err != nil { return bytesRead, err diff --git a/unlocker/simple.go b/unlocker/simple.go index 9bcc1661..8e612bf4 100644 --- a/unlocker/simple.go +++ b/unlocker/simple.go @@ -1,3 +1,4 @@ +// Package unlocker comment package unlocker import ( From 649090faffc176a4c85661f1724907363563e2ba Mon Sep 17 00:00:00 2001 From: Simon Ordish Date: Mon, 14 Nov 2022 10:48:07 +0100 Subject: [PATCH 13/13] Minor changes to address comments in PR --- bscript/script.go | 2 +- bscript/script_test.go | 2 +- tx.go | 12 ++++++++++-- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/bscript/script.go b/bscript/script.go index 147b7dff..706fa1b4 100644 --- a/bscript/script.go +++ b/bscript/script.go @@ -234,7 +234,7 @@ func (s *Script) ToASM() (string, error) { // if err != nil, we will append [error] to the ASM script below (as done in the node). data := false - if len(*s) > 1 && ((*s)[0] == 0x6a || ((*s)[0] == 0x00 && (*s)[1] == 0x6a)) { + if len(*s) > 1 && ((*s)[0] == OpRETURN || ((*s)[0] == OpFALSE && (*s)[1] == OpRETURN)) { data = true } diff --git a/bscript/script_test.go b/bscript/script_test.go index 2ef4a795..eccd5fcc 100644 --- a/bscript/script_test.go +++ b/bscript/script_test.go @@ -515,7 +515,7 @@ func TestScript_UnmarshalJSON(t *testing.T) { } } -func TestRunScriptExample1(t *testing.T) { +func TestScriptToAsm(t *testing.T) { script, _ := hex.DecodeString("006a2231394878696756345179427633744870515663554551797131707a5a56646f4175744d7301e4b8bbe381aa54574954544552e381a8425356e381ae547765746368e381a7e381aee98195e381840a547765746368e381a7e381afe887aae58886e381aee69bb8e38184e3819fe38384e382a8e383bce38388e381afe4b880e795aae69c80e5889de381bee381a70ae38195e3818be381aee381bce381a3e381a6e38184e381a4e381a7e38282e7a2bae8aa8de58fafe883bde381a7e8aaade381bfe8bebce381bfe381a7e995b7e69982e996930ae5819ce6ada2e381afe38182e3828ae381bee3819be38293e380825954e383aae383b3e382afe381aee58b95e794bbe38292e8a696e881b4e38197e3819fe5a0b4e590880ae99fb3e6a5bde381afe382b9e382afe383ade383bce383abe38197e381a6e38282e98094e58887e3828ce3819ae881b4e38193e38188e3819fe381bee381be0ae38384e382a4e38383e382bfe383bce381afe69c80e5889de381aee383ace382b9e381bee381a7e8a18ce38191e381aae38184e381a7e38197e38287e380820a746578742f706c61696e04746578741f7477657463685f7477746578745f313634343834393439353138332e747874017c223150755161374b36324d694b43747373534c4b79316b683536575755374d74555235035345540b7477646174615f6a736f6e046e756c6c0375726c046e756c6c07636f6d6d656e74046e756c6c076d625f757365720439373038057265706c794035366462363536376363306230663539316265363561396135313731663533396635316334333165643837356464326136373431643733353061353539363762047479706504706f73740974696d657374616d70046e756c6c036170700674776574636807696e766f6963652461366637336133312d336334342d346164612d393937352d386537386261666661623765017c22313550636948473232534e4c514a584d6f53556157566937575371633768436676610d424954434f494e5f454344534122314c6970354b335671677743415662674d7842536547434d344355364e344e6b75744c58494b4b554a35765a7753336b4c456e353749356a36485a2b43325733393834314e543532334a4c374534387655706d6f57306b4677613767392b51703246434f4d42776a556a7a76454150624252784d496a746c6b476b3d") s := bscript.Script(script) diff --git a/tx.go b/tx.go index ce7666b1..5a8b38c9 100644 --- a/tx.go +++ b/tx.go @@ -93,6 +93,7 @@ func (tx *Tx) ReadFrom(r io.Reader) (int64, error) { *tx = Tx{} var bytesRead int64 + // Define n64 and err here to avoid linter complaining about shadowing variables. var n64 int64 var err error @@ -118,9 +119,11 @@ func (tx *Tx) ReadFrom(r io.Reader) (int64, error) { var outputCount VarInt locktime := make([]byte, 4) + // ---------------------------------------------------------------------------------- + // If the inputCount is 0, we may be parsing an incomplete transaction, or we may be + // both of these cases without needing to rewind (peek) the incoming stream of bytes. + // ---------------------------------------------------------------------------------- if inputCount == 0 { - // The next bytes are either 0xEF or a varint that specifies the number of outputs - // Read 1 more byte to see if this is in extended format... n64, err = outputCount.ReadFrom(r) bytesRead += n64 if err != nil { @@ -149,6 +152,11 @@ func (tx *Tx) ReadFrom(r io.Reader) (int64, error) { } } } + // ---------------------------------------------------------------------------------- + // If we have not returned from the previous block, we will have detected a sane + // transaction and we will know if it is extended format or not. + // We can now proceed with reading the rest of the transaction. + // ---------------------------------------------------------------------------------- // create Inputs for i := uint64(0); i < uint64(inputCount); i++ {