diff --git a/.golangci.yml b/.golangci.yml index 08b90dd0..f8cf1584 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -353,6 +353,7 @@ linters: - gocritic # use this for very opinionated linting - gochecknoglobals - whitespace + - gci - wsl - goerr113 - godot diff --git a/bscript/script.go b/bscript/script.go index 04c72dd8..69e3fe6a 100644 --- a/bscript/script.go +++ b/bscript/script.go @@ -18,6 +18,15 @@ var ( ErrNotP2PKH = errors.New("not a P2PKH") ) +// ScriptKey types. +const ( + ScriptTypePubKey = "pubkey" + ScriptTypePubKeyHash = "pubkeyhash" + ScriptTypeNonStandard = "nonstandard" + ScriptTypeMultiSig = "multisig" + ScriptTypeNullData = "nulldata" +) + // Script type type Script []byte @@ -87,7 +96,7 @@ func NewP2PKHFromPubKeyHash(pubKeyHash []byte) (*Script, error) { b := []byte{ OpDUP, OpHASH160, - 0x14, + OpDATA20, } b = append(b, pubKeyHash...) b = append(b, OpEQUALVERIFY) @@ -223,7 +232,7 @@ func (s *Script) IsP2PKH() bool { return len(b) == 25 && b[0] == OpDUP && b[1] == OpHASH160 && - b[2] == 0x14 && + b[2] == OpDATA20 && b[23] == OpEQUALVERIFY && b[24] == OpCHECKSIG } @@ -255,7 +264,7 @@ func (s *Script) IsP2SH() bool { return len(b) == 23 && b[0] == OpHASH160 && - b[1] == 0x14 && + b[1] == OpDATA20 && b[22] == OpEQUAL } @@ -264,8 +273,8 @@ func (s *Script) IsP2SH() bool { func (s *Script) IsData() bool { b := []byte(*s) - return b[0] == 0x6a || - b[0] == 0x00 && b[1] == 0x6a + return b[0] == OpRETURN || + b[0] == OpFALSE && b[1] == OpRETURN } // IsMultiSigOut returns true if this is a multisig output script. @@ -303,7 +312,7 @@ func (s *Script) PublicKeyHash() ([]byte, error) { return nil, ErrEmptyScript } - if (*s)[0] != 0x76 || (*s)[1] != 0xa9 { + if (*s)[0] != OpDUP || (*s)[1] != OpHASH160 { return nil, ErrNotP2PKH } @@ -315,6 +324,42 @@ func (s *Script) PublicKeyHash() ([]byte, error) { return parts[0], nil } +// ScriptType returns the type of script this is as a string. +func (s *Script) ScriptType() string { + if s.IsP2PKH() { + return ScriptTypePubKeyHash + } + if s.IsP2PK() { + return ScriptTypePubKey + } + if s.IsMultiSigOut() { + return ScriptTypeMultiSig + } + if s.IsData() { + return ScriptTypeNullData + } + return ScriptTypeNonStandard +} + +// Addresses will return all addresses found in the script, if any. +func (s *Script) Addresses() ([]string, error) { + addresses := make([]string, 0) + if s.IsP2PKH() { + pkh, err := s.PublicKeyHash() + if err != nil { + return nil, err + } + a, err := NewAddressFromPublicKeyHash(pkh, true) + if err != nil { + return nil, err + } + addresses = []string{a.AddressString} + } + // TODO: handle multisig, and other outputs + // https://github.com/libsv/go-bt/issues/6 + return addresses, nil +} + // Equals will compare the script to b and return true if they match. func (s *Script) Equals(b *Script) bool { return bytes.Equal(*s, *b) diff --git a/input.go b/input.go index 899ca6e0..27c06e3e 100644 --- a/input.go +++ b/input.go @@ -2,6 +2,7 @@ package bt import ( "encoding/hex" + "encoding/json" "fmt" "github.com/libsv/go-bt/bscript" @@ -35,6 +36,70 @@ type Input struct { SequenceNumber uint32 } +// inputJSON is used to covnert an input to and from json. +// Script is duplicated as we have our own name for unlockingScript +// but want to be compatible with node json also. +type inputJSON struct { + UnlockingScript *struct { + Asm string `json:"asm"` + Hex string `json:"hex"` + } `json:"unlockingScript,omitempty"` + ScriptSig *struct { + Asm string `json:"asm"` + Hex string `json:"hex"` + } `json:"scriptSig,omitempty"` + TxID string `json:"txid"` + Vout uint32 `json:"vout"` + Sequence uint32 `json:"sequence"` +} + +// MarshalJSON will convert an input to json, expanding upon the +// input struct to add additional fields. +func (i *Input) MarshalJSON() ([]byte, error) { + asm, err := i.UnlockingScript.ToASM() + if err != nil { + return nil, err + } + input := &inputJSON{ + TxID: hex.EncodeToString(i.PreviousTxIDBytes), + Vout: i.PreviousTxOutIndex, + UnlockingScript: &struct { + Asm string `json:"asm"` + Hex string `json:"hex"` + }{ + Asm: asm, + Hex: i.UnlockingScript.String(), + }, + Sequence: i.SequenceNumber, + } + return json.Marshal(input) +} + +// UnmarshalJSON will convert a JSON input to an input. +func (i *Input) UnmarshalJSON(b []byte) error { + var ij inputJSON + if err := json.Unmarshal(b, &ij); err != nil { + return err + } + ptxID, err := hex.DecodeString(ij.TxID) + if err != nil { + return err + } + sig := ij.UnlockingScript + if sig == nil { + sig = ij.ScriptSig + } + s, err := bscript.NewFromHexString(sig.Hex) + if err != nil { + return err + } + i.UnlockingScript = s + i.PreviousTxIDBytes = ptxID + i.PreviousTxOutIndex = ij.Vout + i.SequenceNumber = ij.Sequence + return nil +} + // PreviousTxIDStr returns the Previous TxID as a hex string. func (i *Input) PreviousTxIDStr() string { return hex.EncodeToString(i.PreviousTxIDBytes) diff --git a/output.go b/output.go index 4a2af50d..2c4d6262 100644 --- a/output.go +++ b/output.go @@ -3,6 +3,7 @@ package bt import ( "encoding/binary" "encoding/hex" + "encoding/json" "fmt" "github.com/libsv/go-bt/bscript" @@ -24,6 +25,79 @@ Txout-script / scriptPubKey Script 0 { + o.Satoshis = oj.Satoshis + } else { + o.Satoshis = uint64(oj.Value * 100000000) + } + o.index = oj.Index + o.LockingScript = s + return nil } // LockingScriptHexString returns the locking script diff --git a/tx.go b/tx.go index 7e080ab7..70e86d88 100644 --- a/tx.go +++ b/tx.go @@ -4,6 +4,8 @@ import ( "bytes" "encoding/binary" "encoding/hex" + "encoding/json" + "errors" "fmt" "github.com/libsv/go-bk/crypto" @@ -36,10 +38,64 @@ lock_time if non-zero and sequence numbers are < 0xFFFFFFFF: block height // DO NOT CHANGE ORDER - Optimised memory via malign // type Tx struct { - Inputs []*Input - Outputs []*Output - Version uint32 - LockTime uint32 + Inputs []*Input `json:"vin"` + Outputs []*Output `json:"vout"` + Version uint32 `json:"version"` + LockTime uint32 `json:"locktime"` +} + +type txJSON struct { + Version uint32 `json:"version"` + LockTime uint32 `json:"locktime"` + TxID string `json:"txid"` + Hash string `json:"hash"` + Size int `json:"size"` + Hex string `json:"hex"` + Inputs []*Input `json:"vin"` + Outputs []*Output `json:"vout"` +} + +// MarshalJSON will serialise a transaction to json. +func (tx *Tx) MarshalJSON() ([]byte, error) { + if tx == nil { + return nil, errors.New("tx is nil so cannot be marshalled") + } + for i, o := range tx.Outputs { + o.index = i + } + txj := txJSON{ + Version: tx.Version, + LockTime: tx.LockTime, + Inputs: tx.Inputs, + Outputs: tx.Outputs, + TxID: tx.TxID(), + Hash: tx.TxID(), + Size: len(tx.ToBytes()), + Hex: tx.String(), + } + return json.Marshal(txj) +} + +// UnmarshalJSON will unmarshall a transaction that has been marshalled with this library. +func (tx *Tx) UnmarshalJSON(b []byte) error { + var txj txJSON + if err := json.Unmarshal(b, &txj); err != nil { + return err + } + // quick convert + if txj.Hex != "" { + t, err := NewTxFromString(txj.Hex) + if err != nil { + return err + } + *tx = *t + return nil + } + tx.Inputs = txj.Inputs + tx.Outputs = txj.Outputs + tx.LockTime = txj.LockTime + tx.Version = txj.Version + return nil } // NewTx creates a new transaction object with default values. @@ -115,6 +171,7 @@ func NewTxFromStream(b []byte) (*Tx, int, error) { if err != nil { return nil, 0, err } + output.index = int(i) offset += size t.Outputs = append(t.Outputs, output) } diff --git a/tx_test.go b/tx_test.go index 331e09e7..b1216dc1 100644 --- a/tx_test.go +++ b/tx_test.go @@ -3,9 +3,12 @@ package bt_test import ( "context" "encoding/hex" + "encoding/json" + "fmt" "reflect" "testing" + "github.com/libsv/go-bk/wif" . "github.com/libsv/go-bk/wif" "github.com/libsv/go-bt" "github.com/libsv/go-bt/bscript" @@ -271,3 +274,376 @@ func TestTx_HasDataOutputs(t *testing.T) { assert.Equal(t, false, tx.HasDataOutputs()) }) } + +func TestTx_ToJson(t *testing.T) { + tx, _ := bt.NewTxFromString("0100000001abad53d72f342dd3f338e5e3346b492440f8ea821f8b8800e318f461cc5ea5a2010000006a4730440220042edc1302c5463e8397120a56b28ea381c8f7f6d9bdc1fee5ebca00c84a76e2022077069bbdb7ed701c4977b7db0aba80d41d4e693112256660bb5d674599e390cf41210294639d6e4249ea381c2e077e95c78fc97afe47a52eb24e1b1595cd3fdd0afdf8ffffffff02000000000000000008006a0548656c6c6f7f030000000000001976a914b85524abf8202a961b847a3bd0bc89d3d4d41cc588ac00000000") + + bb, err := json.MarshalIndent(tx, "", "\t") + assert.NoError(t, err) + fmt.Println(string(bb)) +} + +func TestTx_JSON(t *testing.T) { + tests := map[string]struct { + tx *bt.Tx + err error + }{ + "standard tx should marshal and unmarshall correctly": { + tx: func() *bt.Tx { + tx := bt.NewTx() + assert.NoError(t, tx.From( + "3c8edde27cb9a9132c22038dac4391496be9db16fd21351565cc1006966fdad5", + 0, + "76a914eb0bd5edba389198e73f8efabddfc61666969ff788ac", + 2000000)) + assert.NoError(t, tx.PayTo("n2wmGVP89x3DsLNqk3NvctfQy9m9pvt7mk", 1000)) + var wif *WIF + wif, err := DecodeWIF("KznvCNc6Yf4iztSThoMH6oHWzH9EgjfodKxmeuUGPq5DEX5maspS") + assert.NoError(t, err) + assert.NotNil(t, wif) + + _, err = tx.SignAuto(context.Background(), &bt.LocalSigner{PrivateKey: wif.PrivKey}) + assert.NoError(t, err) + return tx + }(), + }, "data tx should marshall correctly": { + tx: func() *bt.Tx { + tx := bt.NewTx() + assert.NoError(t, tx.From( + "3c8edde27cb9a9132c22038dac4391496be9db16fd21351565cc1006966fdad5", + 0, + "76a914eb0bd5edba389198e73f8efabddfc61666969ff788ac", + 2000000)) + assert.NoError(t, tx.PayTo("n2wmGVP89x3DsLNqk3NvctfQy9m9pvt7mk", 1000)) + var wif *WIF + wif, err := DecodeWIF("KznvCNc6Yf4iztSThoMH6oHWzH9EgjfodKxmeuUGPq5DEX5maspS") + assert.NoError(t, err) + assert.NotNil(t, wif) + s := &bscript.Script{} + assert.NoError(t, s.AppendPushDataString("test")) + tx.AddOutput(&bt.Output{ + LockingScript: s, + }) + _, err = tx.SignAuto(context.Background(), &bt.LocalSigner{PrivateKey: wif.PrivKey}) + assert.NoError(t, err) + return tx + }(), + }, + } + for name, test := range tests { + t.Run(name, func(t *testing.T) { + bb, err := json.Marshal(test.tx) + assert.NoError(t, err) + if err != nil { + return + } + var tx *bt.Tx + assert.NoError(t, json.Unmarshal(bb, &tx)) + assert.Equal(t, test.tx.String(), tx.String()) + }) + } +} + +func TestTx_MarshallJSON(t *testing.T) { + tests := map[string]struct { + tx *bt.Tx + expJSON string + }{ + "transaction with 1 input 1 p2pksh output 1 data output should create valid json": { + tx: func() *bt.Tx { + tx, err := bt.NewTxFromString("0100000001abad53d72f342dd3f338e5e3346b492440f8ea821f8b8800e318f461cc5ea5a2010000006a4730440220042edc1302c5463e8397120a56b28ea381c8f7f6d9bdc1fee5ebca00c84a76e2022077069bbdb7ed701c4977b7db0aba80d41d4e693112256660bb5d674599e390cf41210294639d6e4249ea381c2e077e95c78fc97afe47a52eb24e1b1595cd3fdd0afdf8ffffffff02000000000000000008006a0548656c6c6f7f030000000000001976a914b85524abf8202a961b847a3bd0bc89d3d4d41cc588ac00000000") + assert.NoError(t, err) + return tx + }(), + expJSON: `{ + "version": 1, + "locktime": 0, + "txid": "aec245f27b7640c8b1865045107731bfb848115c573f7da38166074b1c9e475d", + "hash": "aec245f27b7640c8b1865045107731bfb848115c573f7da38166074b1c9e475d", + "size": 208, + "hex": "0100000001abad53d72f342dd3f338e5e3346b492440f8ea821f8b8800e318f461cc5ea5a2010000006a4730440220042edc1302c5463e8397120a56b28ea381c8f7f6d9bdc1fee5ebca00c84a76e2022077069bbdb7ed701c4977b7db0aba80d41d4e693112256660bb5d674599e390cf41210294639d6e4249ea381c2e077e95c78fc97afe47a52eb24e1b1595cd3fdd0afdf8ffffffff02000000000000000008006a0548656c6c6f7f030000000000001976a914b85524abf8202a961b847a3bd0bc89d3d4d41cc588ac00000000", + "vin": [ + { + "unlockingScript": { + "asm": "30440220042edc1302c5463e8397120a56b28ea381c8f7f6d9bdc1fee5ebca00c84a76e2022077069bbdb7ed701c4977b7db0aba80d41d4e693112256660bb5d674599e390cf41 0294639d6e4249ea381c2e077e95c78fc97afe47a52eb24e1b1595cd3fdd0afdf8", + "hex": "4730440220042edc1302c5463e8397120a56b28ea381c8f7f6d9bdc1fee5ebca00c84a76e2022077069bbdb7ed701c4977b7db0aba80d41d4e693112256660bb5d674599e390cf41210294639d6e4249ea381c2e077e95c78fc97afe47a52eb24e1b1595cd3fdd0afdf8" + }, + "txid": "a2a55ecc61f418e300888b1f82eaf84024496b34e3e538f3d32d342fd753adab", + "vout": 1, + "sequence": 4294967295 + } + ], + "vout": [ + { + "value": 0, + "satoshis": 0, + "n": 0, + "lockingScript": { + "asm": "OP_FALSE OP_RETURN 48656c6c6f", + "hex": "006a0548656c6c6f", + "type": "nulldata" + } + }, + { + "value": 0.00000895, + "satoshis": 895, + "n": 1, + "lockingScript": { + "asm": "OP_DUP OP_HASH160 b85524abf8202a961b847a3bd0bc89d3d4d41cc5 OP_EQUALVERIFY OP_CHECKSIG", + "hex": "76a914b85524abf8202a961b847a3bd0bc89d3d4d41cc588ac", + "reqSigs": 1, + "type": "pubkeyhash" + } + } + ] +}`, + }, "transaction with multiple inputs": { + tx: func() *bt.Tx { + tx := bt.NewTx() + assert.NoError(t, tx.From( + "3c8edde27cb9a9132c22038dac4391496be9db16fd21351565cc1006966fdad5", + 0, + "76a914eb0bd5edba389198e73f8efabddfc61666969ff788ac", + 10000)) + assert.NoError(t, tx.From( + "3c8edde27cb9a9132c22038dac4391496be9db16fd21351565cc1006966fdad5", + 2, + "76a914eb0bd5edba389198e73f8efabddfc61666969ff788ac", + 10000)) + assert.NoError(t, tx.From( + "3c8edde27cb9a9132c22038dac4391496be9db16fd21351565cc1006966fdad5", + 114, + "76a914eb0bd5edba389198e73f8efabddfc61666969ff788ac", + 10000)) + assert.NoError(t, tx.PayTo("n2wmGVP89x3DsLNqk3NvctfQy9m9pvt7mk", 1000)) + var w *wif.WIF + w, err := wif.DecodeWIF("KznvCNc6Yf4iztSThoMH6oHWzH9EgjfodKxmeuUGPq5DEX5maspS") + assert.NoError(t, err) + assert.NotNil(t, w) + _, err = tx.SignAuto(context.Background(), &bt.LocalSigner{PrivateKey: w.PrivKey}) + assert.NoError(t, err) + return tx + }(), + expJSON: `{ + "version": 1, + "locktime": 0, + "txid": "41741af6fb64839c69f2385987eb3770c55c42eb6f7900fa2af9d667c42ceb20", + "hash": "41741af6fb64839c69f2385987eb3770c55c42eb6f7900fa2af9d667c42ceb20", + "size": 486, + "hex": "0100000003d5da6f960610cc65153521fd16dbe96b499143ac8d03222c13a9b97ce2dd8e3c000000006b48304502210081214df575da1e9378f1d5a29dfd6811e93466a7222fb010b7c50dd2d44d7f2e0220399bb396336d2e294049e7db009926b1b30018ac834ee0cbca20b9d99f488038412102798913bc057b344de675dac34faafe3dc2f312c758cd9068209f810877306d66ffffffffd5da6f960610cc65153521fd16dbe96b499143ac8d03222c13a9b97ce2dd8e3c0200000069463043021f7059426d6aeb7d74275e52819a309b2bf903bd18b2b4d942d0e8e037681df702203f851f8a45aabfefdca5822f457609600f5d12a173adc09c6e7e2d4fdff7620a412102798913bc057b344de675dac34faafe3dc2f312c758cd9068209f810877306d66ffffffffd5da6f960610cc65153521fd16dbe96b499143ac8d03222c13a9b97ce2dd8e3c720000006b483045022100e7b3837f2818fe00a05293e0f90e9005d59b0c5c8890f22bd31c36190a9b55e9022027de4b77b78139ea21b9fd30876a447bbf29662bd19d7914028c607bccd772e4412102798913bc057b344de675dac34faafe3dc2f312c758cd9068209f810877306d66ffffffff01e8030000000000001976a914eb0bd5edba389198e73f8efabddfc61666969ff788ac00000000", + "vin": [ + { + "unlockingScript": { + "asm": "304502210081214df575da1e9378f1d5a29dfd6811e93466a7222fb010b7c50dd2d44d7f2e0220399bb396336d2e294049e7db009926b1b30018ac834ee0cbca20b9d99f48803841 02798913bc057b344de675dac34faafe3dc2f312c758cd9068209f810877306d66", + "hex": "48304502210081214df575da1e9378f1d5a29dfd6811e93466a7222fb010b7c50dd2d44d7f2e0220399bb396336d2e294049e7db009926b1b30018ac834ee0cbca20b9d99f488038412102798913bc057b344de675dac34faafe3dc2f312c758cd9068209f810877306d66" + }, + "txid": "3c8edde27cb9a9132c22038dac4391496be9db16fd21351565cc1006966fdad5", + "vout": 0, + "sequence": 4294967295 + }, + { + "unlockingScript": { + "asm": "3043021f7059426d6aeb7d74275e52819a309b2bf903bd18b2b4d942d0e8e037681df702203f851f8a45aabfefdca5822f457609600f5d12a173adc09c6e7e2d4fdff7620a41 02798913bc057b344de675dac34faafe3dc2f312c758cd9068209f810877306d66", + "hex": "463043021f7059426d6aeb7d74275e52819a309b2bf903bd18b2b4d942d0e8e037681df702203f851f8a45aabfefdca5822f457609600f5d12a173adc09c6e7e2d4fdff7620a412102798913bc057b344de675dac34faafe3dc2f312c758cd9068209f810877306d66" + }, + "txid": "3c8edde27cb9a9132c22038dac4391496be9db16fd21351565cc1006966fdad5", + "vout": 2, + "sequence": 4294967295 + }, + { + "unlockingScript": { + "asm": "3045022100e7b3837f2818fe00a05293e0f90e9005d59b0c5c8890f22bd31c36190a9b55e9022027de4b77b78139ea21b9fd30876a447bbf29662bd19d7914028c607bccd772e441 02798913bc057b344de675dac34faafe3dc2f312c758cd9068209f810877306d66", + "hex": "483045022100e7b3837f2818fe00a05293e0f90e9005d59b0c5c8890f22bd31c36190a9b55e9022027de4b77b78139ea21b9fd30876a447bbf29662bd19d7914028c607bccd772e4412102798913bc057b344de675dac34faafe3dc2f312c758cd9068209f810877306d66" + }, + "txid": "3c8edde27cb9a9132c22038dac4391496be9db16fd21351565cc1006966fdad5", + "vout": 114, + "sequence": 4294967295 + } + ], + "vout": [ + { + "value": 0.00001, + "satoshis": 1000, + "n": 0, + "lockingScript": { + "asm": "OP_DUP OP_HASH160 eb0bd5edba389198e73f8efabddfc61666969ff7 OP_EQUALVERIFY OP_CHECKSIG", + "hex": "76a914eb0bd5edba389198e73f8efabddfc61666969ff788ac", + "reqSigs": 1, + "type": "pubkeyhash" + } + } + ] +}`, + }, + } + for name, test := range tests { + t.Run(name, func(t *testing.T) { + bb, err := json.MarshalIndent(test.tx, "", "\t") + assert.NoError(t, err) + assert.Equal(t, test.expJSON, string(bb)) + }) + } +} + +func TestTx_UnmarshalJSON(t *testing.T) { + t.Parallel() + tests := map[string]struct { + json string + expTX *bt.Tx + }{ + "our json with hex should map correctly": { + json: `{ + "version": 1, + "locktime": 0, + "txid": "aec245f27b7640c8b1865045107731bfb848115c573f7da38166074b1c9e475d", + "hash": "aec245f27b7640c8b1865045107731bfb848115c573f7da38166074b1c9e475d", + "size": 208, + "hex": "0100000001abad53d72f342dd3f338e5e3346b492440f8ea821f8b8800e318f461cc5ea5a2010000006a4730440220042edc1302c5463e8397120a56b28ea381c8f7f6d9bdc1fee5ebca00c84a76e2022077069bbdb7ed701c4977b7db0aba80d41d4e693112256660bb5d674599e390cf41210294639d6e4249ea381c2e077e95c78fc97afe47a52eb24e1b1595cd3fdd0afdf8ffffffff02000000000000000008006a0548656c6c6f7f030000000000001976a914b85524abf8202a961b847a3bd0bc89d3d4d41cc588ac00000000", + "vin": [ + { + "unlockingScript": { + "asm": "30440220042edc1302c5463e8397120a56b28ea381c8f7f6d9bdc1fee5ebca00c84a76e2022077069bbdb7ed701c4977b7db0aba80d41d4e693112256660bb5d674599e390cf41 0294639d6e4249ea381c2e077e95c78fc97afe47a52eb24e1b1595cd3fdd0afdf8", + "hex": "4730440220042edc1302c5463e8397120a56b28ea381c8f7f6d9bdc1fee5ebca00c84a76e2022077069bbdb7ed701c4977b7db0aba80d41d4e693112256660bb5d674599e390cf41210294639d6e4249ea381c2e077e95c78fc97afe47a52eb24e1b1595cd3fdd0afdf8" + }, + "txid": "a2a55ecc61f418e300888b1f82eaf84024496b34e3e538f3d32d342fd753adab", + "vout": 1, + "sequence": 4294967295 + } + ], + "vout": [ + { + "value": 0, + "satoshis": 0, + "n": 0, + "lockingScript": { + "asm": "OP_FALSE OP_RETURN 48656c6c6f", + "hex": "006a0548656c6c6f", + "type": "nulldata" + } + }, + { + "value": 0.00000895, + "satoshis": 895, + "n": 1, + "lockingScript": { + "asm": "OP_DUP OP_HASH160 b85524abf8202a961b847a3bd0bc89d3d4d41cc5 OP_EQUALVERIFY OP_CHECKSIG", + "hex": "76a914b85524abf8202a961b847a3bd0bc89d3d4d41cc588ac", + "reqSigs": 1, + "type": "pubkeyhash" + } + } + ] + }`, + expTX: func() *bt.Tx { + tx, err := bt.NewTxFromString("0100000001abad53d72f342dd3f338e5e3346b492440f8ea821f8b8800e318f461cc5ea5a2010000006a4730440220042edc1302c5463e8397120a56b28ea381c8f7f6d9bdc1fee5ebca00c84a76e2022077069bbdb7ed701c4977b7db0aba80d41d4e693112256660bb5d674599e390cf41210294639d6e4249ea381c2e077e95c78fc97afe47a52eb24e1b1595cd3fdd0afdf8ffffffff02000000000000000008006a0548656c6c6f7f030000000000001976a914b85524abf8202a961b847a3bd0bc89d3d4d41cc588ac00000000") + assert.NoError(t, err) + return tx + }(), + }, "ONLY hex should map correctly": { + json: `{ + "hex": "0100000001abad53d72f342dd3f338e5e3346b492440f8ea821f8b8800e318f461cc5ea5a2010000006a4730440220042edc1302c5463e8397120a56b28ea381c8f7f6d9bdc1fee5ebca00c84a76e2022077069bbdb7ed701c4977b7db0aba80d41d4e693112256660bb5d674599e390cf41210294639d6e4249ea381c2e077e95c78fc97afe47a52eb24e1b1595cd3fdd0afdf8ffffffff02000000000000000008006a0548656c6c6f7f030000000000001976a914b85524abf8202a961b847a3bd0bc89d3d4d41cc588ac00000000" + }`, + expTX: func() *bt.Tx { + tx, err := bt.NewTxFromString("0100000001abad53d72f342dd3f338e5e3346b492440f8ea821f8b8800e318f461cc5ea5a2010000006a4730440220042edc1302c5463e8397120a56b28ea381c8f7f6d9bdc1fee5ebca00c84a76e2022077069bbdb7ed701c4977b7db0aba80d41d4e693112256660bb5d674599e390cf41210294639d6e4249ea381c2e077e95c78fc97afe47a52eb24e1b1595cd3fdd0afdf8ffffffff02000000000000000008006a0548656c6c6f7f030000000000001976a914b85524abf8202a961b847a3bd0bc89d3d4d41cc588ac00000000") + assert.NoError(t, err) + return tx + }(), + }, "Node json with hex should map correctly": { + json: `{ + "version": 1, + "locktime": 0, + "txid": "aec245f27b7640c8b1865045107731bfb848115c573f7da38166074b1c9e475d", + "hash": "aec245f27b7640c8b1865045107731bfb848115c573f7da38166074b1c9e475d", + "size": 208, + "hex": "0100000001abad53d72f342dd3f338e5e3346b492440f8ea821f8b8800e318f461cc5ea5a2010000006a4730440220042edc1302c5463e8397120a56b28ea381c8f7f6d9bdc1fee5ebca00c84a76e2022077069bbdb7ed701c4977b7db0aba80d41d4e693112256660bb5d674599e390cf41210294639d6e4249ea381c2e077e95c78fc97afe47a52eb24e1b1595cd3fdd0afdf8ffffffff02000000000000000008006a0548656c6c6f7f030000000000001976a914b85524abf8202a961b847a3bd0bc89d3d4d41cc588ac00000000", + "vin": [ + { + "scriptSig": { + "asm": "30440220042edc1302c5463e8397120a56b28ea381c8f7f6d9bdc1fee5ebca00c84a76e2022077069bbdb7ed701c4977b7db0aba80d41d4e693112256660bb5d674599e390cf41 0294639d6e4249ea381c2e077e95c78fc97afe47a52eb24e1b1595cd3fdd0afdf8", + "hex": "4730440220042edc1302c5463e8397120a56b28ea381c8f7f6d9bdc1fee5ebca00c84a76e2022077069bbdb7ed701c4977b7db0aba80d41d4e693112256660bb5d674599e390cf41210294639d6e4249ea381c2e077e95c78fc97afe47a52eb24e1b1595cd3fdd0afdf8" + }, + "txid": "a2a55ecc61f418e300888b1f82eaf84024496b34e3e538f3d32d342fd753adab", + "vout": 1, + "sequence": 4294967295 + } + ], + "vout": [ + { + "value": 0, + "n": 0, + "scriptPubKey": { + "asm": "OP_FALSE OP_RETURN 48656c6c6f", + "hex": "006a0548656c6c6f", + "type": "nulldata" + } + }, + { + "value": 0.00000895, + "n": 1, + "scriptPubKey": { + "asm": "OP_DUP OP_HASH160 b85524abf8202a961b847a3bd0bc89d3d4d41cc5 OP_EQUALVERIFY OP_CHECKSIG", + "hex": "76a914b85524abf8202a961b847a3bd0bc89d3d4d41cc588ac", + "reqSigs": 1, + "type": "pubkeyhash" + } + } + ] + }`, + expTX: func() *bt.Tx { + tx, err := bt.NewTxFromString("0100000001abad53d72f342dd3f338e5e3346b492440f8ea821f8b8800e318f461cc5ea5a2010000006a4730440220042edc1302c5463e8397120a56b28ea381c8f7f6d9bdc1fee5ebca00c84a76e2022077069bbdb7ed701c4977b7db0aba80d41d4e693112256660bb5d674599e390cf41210294639d6e4249ea381c2e077e95c78fc97afe47a52eb24e1b1595cd3fdd0afdf8ffffffff02000000000000000008006a0548656c6c6f7f030000000000001976a914b85524abf8202a961b847a3bd0bc89d3d4d41cc588ac00000000") + assert.NoError(t, err) + return tx + }(), + }, "Node json without hex should map correctly": { + json: `{ + "version": 1, + "locktime": 0, + "txid": "aec245f27b7640c8b1865045107731bfb848115c573f7da38166074b1c9e475d", + "hash": "aec245f27b7640c8b1865045107731bfb848115c573f7da38166074b1c9e475d", + "size": 208, + "vin": [{ + "scriptSig": { + "asm": "30440220042edc1302c5463e8397120a56b28ea381c8f7f6d9bdc1fee5ebca00c84a76e2022077069bbdb7ed701c4977b7db0aba80d41d4e693112256660bb5d674599e390cf41 0294639d6e4249ea381c2e077e95c78fc97afe47a52eb24e1b1595cd3fdd0afdf8", + "hex": "4730440220042edc1302c5463e8397120a56b28ea381c8f7f6d9bdc1fee5ebca00c84a76e2022077069bbdb7ed701c4977b7db0aba80d41d4e693112256660bb5d674599e390cf41210294639d6e4249ea381c2e077e95c78fc97afe47a52eb24e1b1595cd3fdd0afdf8" + }, + "txid": "a2a55ecc61f418e300888b1f82eaf84024496b34e3e538f3d32d342fd753adab", + "vout": 1, + "sequence": 4294967295 + }], + "vout": [{ + "value": 0, + "n": 0, + "scriptPubKey": { + "asm": "OP_FALSE OP_RETURN 48656c6c6f", + "hex": "006a0548656c6c6f", + "type": "nulldata" + } + }, + { + "value": 0.00000895, + "n": 1, + "scriptPubKey": { + "asm": "OP_DUP OP_HASH160 b85524abf8202a961b847a3bd0bc89d3d4d41cc5 OP_EQUALVERIFY OP_CHECKSIG", + "hex": "76a914b85524abf8202a961b847a3bd0bc89d3d4d41cc588ac", + "reqSigs": 1, + "type": "pubkeyhash" + } + } + ] +}`, + expTX: func() *bt.Tx { + tx, err := bt.NewTxFromString("0100000001abad53d72f342dd3f338e5e3346b492440f8ea821f8b8800e318f461cc5ea5a2010000006a4730440220042edc1302c5463e8397120a56b28ea381c8f7f6d9bdc1fee5ebca00c84a76e2022077069bbdb7ed701c4977b7db0aba80d41d4e693112256660bb5d674599e390cf41210294639d6e4249ea381c2e077e95c78fc97afe47a52eb24e1b1595cd3fdd0afdf8ffffffff02000000000000000008006a0548656c6c6f7f030000000000001976a914b85524abf8202a961b847a3bd0bc89d3d4d41cc588ac00000000") + assert.NoError(t, err) + return tx + }(), + }, + } + for name, test := range tests { + t.Run(name, func(t *testing.T) { + var tx *bt.Tx + err := json.Unmarshal([]byte(test.json), &tx) + assert.NoError(t, err) + assert.Equal(t, test.expTX, tx) + }) + } +} diff --git a/txoutput_test.go b/txoutput_test.go index 587dcd58..d0e9ca76 100644 --- a/txoutput_test.go +++ b/txoutput_test.go @@ -5,9 +5,10 @@ import ( "fmt" "testing" + "github.com/stretchr/testify/assert" + "github.com/libsv/go-bt" "github.com/libsv/go-bt/bscript" - "github.com/stretchr/testify/assert" ) func TestNewP2PKHOutputFromPubKeyHashStr(t *testing.T) {