func TestConstructionTxDeserialize(t *testing.T) {
	type testCase struct {
		name       string
		serialized string
		wantErr    error
		wantCtx    *constructionTx
	}
	prevOut1 := wire.OutPoint{Index: 2, Tree: 1}
	copy(prevOut1.Hash[:], bytes.Repeat([]byte{0x01}, 32))
	prevOut2 := wire.OutPoint{Index: 3, Tree: 0}
	copy(prevOut2.Hash[:], bytes.Repeat([]byte{0x18}, 32))
	slice1 := bytes.Repeat([]byte{0xac}, 10)
	slice2 := bytes.Repeat([]byte{0x1e}, 10)
	slice3 := bytes.Repeat([]byte{0x65}, 10)
	testCases := []testCase{{
		name:       "no inputs, no outputs, no prevouts",
		serialized: "0100000000000000000000000000000000",
		wantCtx: &constructionTx{
			tx: &wire.MsgTx{
				TxIn:  []*wire.TxIn{},
				TxOut: []*wire.TxOut{},
			},
			prevOutPoints: map[wire.OutPoint]*types.PrevOutput{},
		},
	}, {
		name:       "one input, one output both empty",
		serialized: "01000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000010000000000000000000000000000000000010000000000000000000000",
		wantCtx: &constructionTx{
			tx: &wire.MsgTx{
				TxIn:  []*wire.TxIn{{SignatureScript: []byte{}}},
				TxOut: []*wire.TxOut{{PkScript: []byte{}}},
			},
			prevOutPoints: map[wire.OutPoint]*types.PrevOutput{
				{}: {PkScript: []byte{}},
			},
		},
	}, {
		name:       "one input, one output, filled data",
		serialized: "0176760000010101010101010101010101010101010101010101010101010101010101010101020000000191919191014a4a4a4a4a4a4a4a21210a1e1e1e1e1e1e1e1e1e1e5454545438383838013a3a3a3a3a3a3a3a78787878535353530aacacacacacacacacacac01212121212121212187870a65656565656565656565",
		wantCtx: &constructionTx{
			tx: &wire.MsgTx{
				Version:  0x7676,
				LockTime: 0x54545454,
				Expiry:   0x38383838,
				TxIn: []*wire.TxIn{{
					PreviousOutPoint: prevOut1,
					Sequence:         0x91919191,
					ValueIn:          0x3a3a3a3a3a3a3a3a,
					BlockHeight:      0x78787878,
					BlockIndex:       0x53535353,
					SignatureScript:  slice1,
				}},
				TxOut: []*wire.TxOut{{
					Value:    0x4a4a4a4a4a4a4a4a,
					Version:  0x2121,
					PkScript: slice2,
				}},
			},
			prevOutPoints: map[wire.OutPoint]*types.PrevOutput{
				prevOut1: {
					Amount:   0x2121212121212121,
					Version:  0x8787,
					PkScript: slice3,
				},
			},
		},
	}, {
		name:       "two inputs, one output",
		serialized: "01767600000201010101010101010101010101010101010101010101010101010101010101010200000001919191911818181818181818181818181818181818181818181818181818181818181818030000000000000000014a4a4a4a4a4a4a4a21210a1e1e1e1e1e1e1e1e1e1e5454545438383838023a3a3a3a3a3a3a3a78787878535353530aacacacacacacacacacac000000000000000000000000000000000002212121212121212187870a656565656565656565650000000000000000000000",
		wantCtx: &constructionTx{
			tx: &wire.MsgTx{
				Version:  0x7676,
				LockTime: 0x54545454,
				Expiry:   0x38383838,
				TxIn: []*wire.TxIn{{
					PreviousOutPoint: prevOut1,
					Sequence:         0x91919191,
					ValueIn:          0x3a3a3a3a3a3a3a3a,
					BlockHeight:      0x78787878,
					BlockIndex:       0x53535353,
					SignatureScript:  slice1,
				}, {
					PreviousOutPoint: prevOut2,
					SignatureScript:  []byte{},
				}},
				TxOut: []*wire.TxOut{{
					Value:    0x4a4a4a4a4a4a4a4a,
					Version:  0x2121,
					PkScript: slice2,
				}},
			},
			prevOutPoints: map[wire.OutPoint]*types.PrevOutput{
				prevOut2: {
					PkScript: []byte{},
				},
				prevOut1: {
					Amount:   0x2121212121212121,
					Version:  0x8787,
					PkScript: slice3,
				},
			},
		},
	}, {
		name:       "invalid hex",
		serialized: "0100000000000000000000000000000000x",
		wantErr:    types.ErrInvalidHexString,
	}, {
		name:       "unrecognized version 0",
		serialized: "0076760000010101010101010101010101010101010101010101010101010101010101010101020000000191919191014a4a4a4a4a4a4a4a21210a1e1e1e1e1e1e1e1e1e1e5454545438383838013a3a3a3a3a3a3a3a78787878535353530aacacacacacacacacacac01212121212121212187870a65656565656565656565",
		wantErr:    errInvalidCtrtxVersion,
	}, {
		name:       "broken tx serialization type",
		serialized: "017676000ff10101010101010101010101010101010101010101010101010101010101010101020000000191919191014a4a4a4a4a4a4a4a21210a1e1e1e1e1e1e1e1e1e1e5454545438383838013a3a3a3a3a3a3a3a78787878535353530aacacacacacacacacacac01212121212121212187870a65656565656565656565",
		wantErr:    types.ErrInvalidTransaction,
	}, {
		name:       "short number of tx bytes",
		serialized: "01767600000101010101",
		wantErr:    types.ErrInvalidTransaction,
	}, {
		name:       "too few prevouts",
		serialized: "0176760000010101010101010101010101010101010101010101010101010101010101010101020000000191919191014a4a4a4a4a4a4a4a21210a1e1e1e1e1e1e1e1e1e1e5454545438383838013a3a3a3a3a3a3a3a78787878535353530aacacacacacacacacacac00",
		wantErr:    errWrongNbPrevOuts,
	}, {
		name:       "too many prevouts",
		serialized: "0176760000010101010101010101010101010101010101010101010101010101010101010101020000000191919191014a4a4a4a4a4a4a4a21210a1e1e1e1e1e1e1e1e1e1e5454545438383838013a3a3a3a3a3a3a3a78787878535353530aacacacacacacacacacac02212121212121212187870a65656565656565656565212121212121212187870a65656565656565656565",
		wantErr:    errWrongNbPrevOuts,
	}, {
		name:       "prevout nb not specified",
		serialized: "0176760000010101010101010101010101010101010101010101010101010101010101010101020000000191919191014a4a4a4a4a4a4a4a21210a1e1e1e1e1e1e1e1e1e1e5454545438383838013a3a3a3a3a3a3a3a78787878535353530aacacacacacacacacacac",
		wantErr:    io.EOF,
	}, {
		name:       "short read on prevout amount",
		serialized: "0176760000010101010101010101010101010101010101010101010101010101010101010101020000000191919191014a4a4a4a4a4a4a4a21210a1e1e1e1e1e1e1e1e1e1e5454545438383838013a3a3a3a3a3a3a3a78787878535353530aacacacacacacacacacac0121212121212121",
		wantErr:    io.EOF,
	}, {
		name:       "short read on prevout version",
		serialized: "0176760000010101010101010101010101010101010101010101010101010101010101010101020000000191919191014a4a4a4a4a4a4a4a21210a1e1e1e1e1e1e1e1e1e1e5454545438383838013a3a3a3a3a3a3a3a78787878535353530aacacacacacacacacacac01212121212121212187",
		wantErr:    io.EOF,
	}, {
		name:       "short read on prevout pkscript size",
		serialized: "0176760000010101010101010101010101010101010101010101010101010101010101010101020000000191919191014a4a4a4a4a4a4a4a21210a1e1e1e1e1e1e1e1e1e1e5454545438383838013a3a3a3a3a3a3a3a78787878535353530aacacacacacacacacacac0121212121212121218787",
		wantErr:    io.EOF,
	}, {
		name:       "short read on prevout pkscript",
		serialized: "0176760000010101010101010101010101010101010101010101010101010101010101010101020000000191919191014a4a4a4a4a4a4a4a21210a1e1e1e1e1e1e1e1e1e1e5454545438383838013a3a3a3a3a3a3a3a78787878535353530aacacacacacacacacacac01212121212121212187870a6565",
		wantErr:    io.EOF,
	}}
	test := func(t *testing.T, tc *testCase) {
		var gotCtx constructionTx
		err := gotCtx.deserialize(tc.serialized)
		if !errors.Is(err, tc.wantErr) {
			t.Fatalf("unexpected error. want=%v, got=%v", tc.wantErr, err)
		}
		if tc.wantErr != nil {
			return
		}
		require.Equal(t, *tc.wantCtx, gotCtx)
	}
	for _, tc := range testCases {
		tc := tc
		t.Run(tc.name, func(t *testing.T) { test(t, &tc) })
	}
}
TestConstructionTxDeserialize ensures the constructionTx deserialize()
method works as expected.
package backend
import (
	"bytes"
	"context"
	"errors"
	"fmt"
	"io"
	"strconv"
	"testing"
	"decred.org/dcrros/types"
	rtypes "github.com/coinbase/rosetta-sdk-go/types"
	"github.com/davecgh/go-spew/spew"
	"github.com/decred/dcrd/chaincfg/chainhash"
	"github.com/decred/dcrd/chaincfg/v3"
	"github.com/decred/dcrd/dcrec"
	"github.com/decred/dcrd/dcrec/secp256k1/v3"
	"github.com/decred/dcrd/dcrec/secp256k1/v3/ecdsa"
	"github.com/decred/dcrd/dcrjson/v3"
	"github.com/decred/dcrd/dcrutil/v3"
	"github.com/decred/dcrd/txscript/v3"
	"github.com/decred/dcrd/wire"
	"github.com/stretchr/testify/require"
)
func TestConstructionDeriveEndpoint(t *testing.T) {
	params := chaincfg.RegNetParams()
	c := newMockChain(t, params)
	pubkey := mustHex("03eb7ae61d440e13823589ce2e3728a02b7f55530d4463aaf8059c7c41ef521e7d")
	v0 := float64(0)
	v1 := float64(1)
	cfg := &ServerConfig{
		ChainParams: params,
		DBType:      dbTypePreconfigured,
		c:           c,
	}
	svr := newTestServer(t, cfg)
	type testCase struct {
		name     string
		req      *rtypes.ConstructionDeriveRequest
		wantErr  error
		wantAddr string
	}
	testCases := []testCase{{
		name: "secp25k1 key for ecdsa algo",
		req: &rtypes.ConstructionDeriveRequest{
			PublicKey: &rtypes.PublicKey{
				Bytes:     pubkey,
				CurveType: rtypes.Secp256k1,
			},
			Metadata: map[string]interface{}{
				"script_version": v0,
				"algo":           "ecdsa",
			},
		},
		wantErr:  nil,
		wantAddr: "RsL5dxVrAaEdwHB37sQYe5HA8RoSKVnq8LN",
	}, {
		name: "secp25k1 key for schnorr algo",
		req: &rtypes.ConstructionDeriveRequest{
			PublicKey: &rtypes.PublicKey{
				Bytes:     pubkey,
				CurveType: rtypes.Secp256k1,
			},
			Metadata: map[string]interface{}{
				"script_version": v0,
				"algo":           "schnorr",
			},
		},
		wantErr:  nil,
		wantAddr: "RSP8EumNJZ3ucaJPkMPtt1WHMQXTqUeDcqA",
	}, {
		name: "secp25k1 key without algo specified defaults to ecdsa",
		req: &rtypes.ConstructionDeriveRequest{
			PublicKey: &rtypes.PublicKey{
				Bytes:     pubkey,
				CurveType: rtypes.Secp256k1,
			},
			Metadata: map[string]interface{}{
				"script_version": v0,
			},
		},
		wantErr:  nil,
		wantAddr: "RsL5dxVrAaEdwHB37sQYe5HA8RoSKVnq8LN",
	}, {
		name: "non v0 script fails",
		req: &rtypes.ConstructionDeriveRequest{
			PublicKey: &rtypes.PublicKey{
				Bytes:     pubkey,
				CurveType: rtypes.Secp256k1,
			},
			Metadata: map[string]interface{}{
				"script_version": v1,
			},
		},
		wantErr: types.ErrUnsupportedAddressVersion,
	}, {
		name: "script_version unspecified fails",
		req: &rtypes.ConstructionDeriveRequest{
			PublicKey: &rtypes.PublicKey{
				Bytes:     pubkey,
				CurveType: rtypes.Secp256k1,
			},
			Metadata: map[string]interface{}{},
		},
		wantErr: types.ErrUnspecifiedAddressVersion,
	}, {
		name: "short pubkey fails",
		req: &rtypes.ConstructionDeriveRequest{
			PublicKey: &rtypes.PublicKey{
				Bytes:     pubkey[:32],
				CurveType: rtypes.Secp256k1,
			},
			Metadata: map[string]interface{}{
				"script_version": v0,
			},
		},
		wantErr: types.ErrInvalidSecp256k1PubKey,
	}, {
		name: "long pubkey fails",
		req: &rtypes.ConstructionDeriveRequest{
			PublicKey: &rtypes.PublicKey{
				Bytes:     append(pubkey, 0x01),
				CurveType: rtypes.Secp256k1,
			},
			Metadata: map[string]interface{}{
				"script_version": v0,
			},
		},
		wantErr: types.ErrInvalidSecp256k1PubKey,
	}, {
		name: "invalid pubkey prefix byte fails",
		req: &rtypes.ConstructionDeriveRequest{
			PublicKey: &rtypes.PublicKey{
				Bytes:     append([]byte{0xff}, pubkey[:32]...),
				CurveType: rtypes.Secp256k1,
			},
			Metadata: map[string]interface{}{
				"script_version": v0,
			},
		},
		wantErr: types.ErrNotCompressedSecp256k1Key,
	}, {
		name: "unsupported algo fails",
		req: &rtypes.ConstructionDeriveRequest{
			PublicKey: &rtypes.PublicKey{
				Bytes:     pubkey,
				CurveType: rtypes.Secp256k1,
			},
			Metadata: map[string]interface{}{
				"script_version": v0,
				"algo":           "none",
			},
		},
		wantErr: types.ErrUnsupportedAddressAlgo,
	}, {
		name: "unsupported curve type fails",
		req: &rtypes.ConstructionDeriveRequest{
			PublicKey: &rtypes.PublicKey{
				Bytes:     pubkey,
				CurveType: "foo",
			},
			Metadata: map[string]interface{}{
				"script_version": v0,
			},
		},
		wantErr: types.ErrUnsupportedCurveType,
	}}
	test := func(t *testing.T, tc *testCase) {
		t.Parallel()
		res, rerr := svr.ConstructionDerive(context.Background(), tc.req)
		if !types.RosettaErrorIs(rerr, tc.wantErr) {
			t.Fatalf("unexpected error. want=%v got=%v",
				tc.wantErr, rerr)
		}
		if tc.wantErr != nil {
			return
		}
		gotAddr := res.AccountIdentifier.Address
		if gotAddr != tc.wantAddr {
			t.Fatalf("unexpected address. want=%v got=%v", tc.wantAddr,
				gotAddr)
		}
	}
	for _, tc := range testCases {
		tc := tc
		t.Run(tc.name, func(t *testing.T) { test(t, &tc) })
	}
}
func TestConstructionTxSerialize(t *testing.T) {
	type testCase struct {
		name    string
		ctx     *constructionTx
		wantStr string
		wantErr error
	}
	prevOut1 := wire.OutPoint{Index: 2, Tree: 1}
	copy(prevOut1.Hash[:], bytes.Repeat([]byte{0x01}, 32))
	prevOut2 := wire.OutPoint{Index: 3, Tree: 0}
	copy(prevOut2.Hash[:], bytes.Repeat([]byte{0x18}, 32))
	slice1 := bytes.Repeat([]byte{0xac}, 10)
	slice2 := bytes.Repeat([]byte{0x1e}, 10)
	slice3 := bytes.Repeat([]byte{0x65}, 10)
	testCases := []testCase{{
		name: "no inputs, no outputs, no prevouts",
		ctx: &constructionTx{
			tx: &wire.MsgTx{
				TxIn:  []*wire.TxIn{},
				TxOut: []*wire.TxOut{},
			},
			prevOutPoints: map[wire.OutPoint]*types.PrevOutput{},
		},
		wantStr: "0100000000000000000000000000000000",
	}, {
		name: "one input, one output both empty",
		ctx: &constructionTx{
			tx: &wire.MsgTx{
				TxIn:  []*wire.TxIn{{SignatureScript: []byte{}}},
				TxOut: []*wire.TxOut{{PkScript: []byte{}}},
			},
			prevOutPoints: map[wire.OutPoint]*types.PrevOutput{
				{}: {PkScript: []byte{}},
			},
		},
		wantStr: "01000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000010000000000000000000000000000000000010000000000000000000000",
	}, {
		name: "one input, one output, filled data",
		ctx: &constructionTx{
			tx: &wire.MsgTx{
				Version:  0x7676,
				LockTime: 0x54545454,
				Expiry:   0x38383838,
				TxIn: []*wire.TxIn{{
					PreviousOutPoint: prevOut1,
					Sequence:         0x91919191,
					ValueIn:          0x3a3a3a3a3a3a3a3a,
					BlockHeight:      0x78787878,
					BlockIndex:       0x53535353,
					SignatureScript:  slice1,
				}},
				TxOut: []*wire.TxOut{{
					Value:    0x4a4a4a4a4a4a4a4a,
					Version:  0x2121,
					PkScript: slice2,
				}},
			},
			prevOutPoints: map[wire.OutPoint]*types.PrevOutput{
				prevOut1: {
					Amount:   0x2121212121212121,
					Version:  0x8787,
					PkScript: slice3,
				},
			},
		},
		wantStr: "0176760000010101010101010101010101010101010101010101010101010101010101010101020000000191919191014a4a4a4a4a4a4a4a21210a1e1e1e1e1e1e1e1e1e1e5454545438383838013a3a3a3a3a3a3a3a78787878535353530aacacacacacacacacacac01212121212121212187870a65656565656565656565",
	}, {
		name: "two inputs, one output",
		ctx: &constructionTx{
			tx: &wire.MsgTx{
				Version:  0x7676,
				LockTime: 0x54545454,
				Expiry:   0x38383838,
				TxIn: []*wire.TxIn{{
					PreviousOutPoint: prevOut1,
					Sequence:         0x91919191,
					ValueIn:          0x3a3a3a3a3a3a3a3a,
					BlockHeight:      0x78787878,
					BlockIndex:       0x53535353,
					SignatureScript:  slice1,
				}, {
					PreviousOutPoint: prevOut2,
					SignatureScript:  []byte{},
				}},
				TxOut: []*wire.TxOut{{
					Value:    0x4a4a4a4a4a4a4a4a,
					Version:  0x2121,
					PkScript: slice2,
				}},
			},
			prevOutPoints: map[wire.OutPoint]*types.PrevOutput{
				prevOut2: {
					PkScript: []byte{},
				},
				prevOut1: {
					Amount:   0x2121212121212121,
					Version:  0x8787,
					PkScript: slice3,
				},
			},
		},
		wantStr: "01767600000201010101010101010101010101010101010101010101010101010101010101010200000001919191911818181818181818181818181818181818181818181818181818181818181818030000000000000000014a4a4a4a4a4a4a4a21210a1e1e1e1e1e1e1e1e1e1e5454545438383838023a3a3a3a3a3a3a3a78787878535353530aacacacacacacacacacac000000000000000000000000000000000002212121212121212187870a656565656565656565650000000000000000000000",
	}, {
		name: "trying to serialize without a corresponding prevOut",
		ctx: &constructionTx{
			tx: &wire.MsgTx{
				TxIn: []*wire.TxIn{{
					PreviousOutPoint: prevOut1,
					SignatureScript:  []byte{},
				}},
				TxOut: []*wire.TxOut{{PkScript: []byte{}}},
			},
			prevOutPoints: map[wire.OutPoint]*types.PrevOutput{
				{}: {PkScript: []byte{}},
			},
		},
		wantErr: errInPrevOutNotFound,
	}, {
		name: "trying to serialize incorrect nb of prevouts",
		ctx: &constructionTx{
			tx: &wire.MsgTx{
				TxIn: []*wire.TxIn{{
					PreviousOutPoint: prevOut1,
					SignatureScript:  []byte{},
				}},
				TxOut: []*wire.TxOut{{PkScript: []byte{}}},
			},
			prevOutPoints: map[wire.OutPoint]*types.PrevOutput{},
		},
		wantErr: errWrongNbPrevOuts,
	}}
	test := func(t *testing.T, tc *testCase) {
		gotStr, gotErr := tc.ctx.serialize()
		if !errors.Is(gotErr, tc.wantErr) {
			t.Fatalf("unexpected error. want=%v, got=%v", tc.wantErr,
				gotErr)
		}
		if tc.wantErr != nil {
			return
		}
		if gotStr != tc.wantStr {
			t.Fatalf("unexpected serialization. want=%s, got=%s",
				tc.wantStr, gotStr)
		}
		var gotCtx constructionTx
		if err := gotCtx.deserialize(gotStr); err != nil {
			t.Fatalf("unexpected deserialization error: %v", err)
		}
		require.Equal(t, *tc.ctx, gotCtx)
	}
	for _, tc := range testCases {
		tc := tc
		t.Run(tc.name, func(t *testing.T) { test(t, &tc) })
	}
}