--- /dev/null
+package account
+
+import (
+ "encoding/json"
+ "testing"
+ "time"
+
+ "github.com/vapor/blockchain/signers"
+ "github.com/vapor/blockchain/txbuilder"
+ "github.com/vapor/consensus"
+ "github.com/vapor/crypto/ed25519/chainkd"
+ "github.com/vapor/protocol/bc"
+ "github.com/vapor/testutil"
+)
+
+func TestReserveBtmUtxoChain(t *testing.T) {
+ chainTxUtxoNum = 3
+ utxos := []*UTXO{}
+ m := mockAccountManager(t)
+ for i := uint64(1); i <= 20; i++ {
+ utxo := &UTXO{
+ OutputID: bc.Hash{V0: i},
+ AccountID: "TestAccountID",
+ AssetID: *consensus.BTMAssetID,
+ Amount: i * chainTxMergeGas,
+ }
+ utxos = append(utxos, utxo)
+
+ data, err := json.Marshal(utxo)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ m.db.Set(StandardUTXOKey(utxo.OutputID), data)
+ }
+
+ cases := []struct {
+ amount uint64
+ want []uint64
+ err bool
+ }{
+ {
+ amount: 1 * chainTxMergeGas,
+ want: []uint64{1},
+ },
+ {
+ amount: 888888 * chainTxMergeGas,
+ want: []uint64{},
+ err: true,
+ },
+ {
+ amount: 7 * chainTxMergeGas,
+ want: []uint64{4, 3, 1},
+ },
+ {
+ amount: 15 * chainTxMergeGas,
+ want: []uint64{5, 4, 3, 2, 1, 6},
+ },
+ {
+ amount: 163 * chainTxMergeGas,
+ want: []uint64{20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 2, 1, 3},
+ },
+ }
+
+ for i, c := range cases {
+ m.utxoKeeper.expireReservation(time.Unix(999999999, 0))
+ utxos, err := m.reserveBtmUtxoChain(&txbuilder.TemplateBuilder{}, "TestAccountID", c.amount, false)
+
+ if err != nil != c.err {
+ t.Fatalf("case %d got err %v want err = %v", i, err, c.err)
+ }
+
+ got := []uint64{}
+ for _, utxo := range utxos {
+ got = append(got, utxo.Amount/chainTxMergeGas)
+ }
+
+ if !testutil.DeepEqual(got, c.want) {
+ t.Fatalf("case %d got %d want %d", i, got, c.want)
+ }
+ }
+
+}
+
+func TestBuildBtmTxChain(t *testing.T) {
+ chainTxUtxoNum = 3
+ m := mockAccountManager(t)
+ cases := []struct {
+ inputUtxo []uint64
+ wantInput [][]uint64
+ wantOutput [][]uint64
+ wantUtxo uint64
+ }{
+ {
+ inputUtxo: []uint64{5},
+ wantInput: [][]uint64{},
+ wantOutput: [][]uint64{},
+ wantUtxo: 5 * chainTxMergeGas,
+ },
+ {
+ inputUtxo: []uint64{5, 4},
+ wantInput: [][]uint64{
+ []uint64{5, 4},
+ },
+ wantOutput: [][]uint64{
+ []uint64{8},
+ },
+ wantUtxo: 8 * chainTxMergeGas,
+ },
+ {
+ inputUtxo: []uint64{5, 4, 1, 1},
+ wantInput: [][]uint64{
+ []uint64{5, 4, 1},
+ []uint64{1, 9},
+ },
+ wantOutput: [][]uint64{
+ []uint64{9},
+ []uint64{9},
+ },
+ wantUtxo: 9 * chainTxMergeGas,
+ },
+ {
+ inputUtxo: []uint64{22, 123, 53, 234, 23, 4, 2423, 24, 23, 43, 34, 234, 234, 24},
+ wantInput: [][]uint64{
+ []uint64{22, 123, 53},
+ []uint64{234, 23, 4},
+ []uint64{2423, 24, 23},
+ []uint64{43, 34, 234},
+ []uint64{234, 24, 197},
+ []uint64{260, 2469, 310},
+ []uint64{454, 3038},
+ },
+ wantOutput: [][]uint64{
+ []uint64{197},
+ []uint64{260},
+ []uint64{2469},
+ []uint64{310},
+ []uint64{454},
+ []uint64{3038},
+ []uint64{3491},
+ },
+ wantUtxo: 3491 * chainTxMergeGas,
+ },
+ }
+
+ acct, err := m.Create([]chainkd.XPub{testutil.TestXPub}, 1, "testAccount", signers.BIP0044)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ acp, err := m.CreateAddress(acct.ID, false)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ for caseIndex, c := range cases {
+ utxos := []*UTXO{}
+ for _, amount := range c.inputUtxo {
+ utxos = append(utxos, &UTXO{
+ Amount: amount * chainTxMergeGas,
+ AssetID: *consensus.BTMAssetID,
+ Address: acp.Address,
+ ControlProgram: acp.ControlProgram,
+ })
+ }
+
+ tpls, gotUtxo, err := m.buildBtmTxChain(utxos, acct.Signer)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ for i, tpl := range tpls {
+ gotInput := []uint64{}
+ for _, input := range tpl.Transaction.Inputs {
+ gotInput = append(gotInput, input.Amount()/chainTxMergeGas)
+ }
+
+ gotOutput := []uint64{}
+ for _, output := range tpl.Transaction.Outputs {
+ gotOutput = append(gotOutput, output.Amount/chainTxMergeGas)
+ }
+
+ if !testutil.DeepEqual(c.wantInput[i], gotInput) {
+ t.Fatalf("case %d tx %d input got %d want %d", caseIndex, i, gotInput, c.wantInput[i])
+ }
+ if !testutil.DeepEqual(c.wantOutput[i], gotOutput) {
+ t.Fatalf("case %d tx %d output got %d want %d", caseIndex, i, gotOutput, c.wantOutput[i])
+ }
+ }
+
+ if c.wantUtxo != gotUtxo.Amount {
+ t.Fatalf("case %d got utxo=%d want utxo=%d", caseIndex, gotUtxo.Amount, c.wantUtxo)
+ }
+ }
+
+}
+
+func TestMergeSpendAction(t *testing.T) {
+ testBTM := &bc.AssetID{}
+ if err := testBTM.UnmarshalText([]byte("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")); err != nil {
+ t.Fatal(err)
+ }
+
+ testAssetID1 := &bc.AssetID{}
+ if err := testAssetID1.UnmarshalText([]byte("50ec80b6bc48073f6aa8fa045131a71213c33f3681203b15ddc2e4b81f1f4730")); err != nil {
+ t.Fatal(err)
+ }
+
+ testAssetID2 := &bc.AssetID{}
+ if err := testAssetID2.UnmarshalText([]byte("43c6946d092b2959c1a82e90b282c68fca63e66de289048f6acd6cea9383c79c")); err != nil {
+ t.Fatal(err)
+ }
+
+ cases := []struct {
+ testActions []txbuilder.Action
+ wantActions []txbuilder.Action
+ testActionCount int
+ wantActionCount int
+ }{
+ {
+ testActions: []txbuilder.Action{
+ txbuilder.Action(&spendAction{
+ AssetAmount: bc.AssetAmount{
+ AssetId: testBTM,
+ Amount: 100,
+ },
+ AccountID: "test_account",
+ }),
+ txbuilder.Action(&spendAction{
+ AssetAmount: bc.AssetAmount{
+ AssetId: testBTM,
+ Amount: 200,
+ },
+ AccountID: "test_account",
+ }),
+ txbuilder.Action(&spendAction{
+ AssetAmount: bc.AssetAmount{
+ AssetId: testBTM,
+ Amount: 300,
+ },
+ AccountID: "test_account",
+ }),
+ txbuilder.Action(&spendAction{
+ AssetAmount: bc.AssetAmount{
+ AssetId: testAssetID1,
+ Amount: 300,
+ },
+ AccountID: "test_account",
+ }),
+ },
+ wantActions: []txbuilder.Action{
+ txbuilder.Action(&spendAction{
+ AssetAmount: bc.AssetAmount{
+ AssetId: testBTM,
+ Amount: 600,
+ },
+ AccountID: "test_account",
+ }),
+ txbuilder.Action(&spendAction{
+ AssetAmount: bc.AssetAmount{
+ AssetId: testAssetID1,
+ Amount: 300,
+ },
+ AccountID: "test_account",
+ }),
+ },
+ testActionCount: 4,
+ wantActionCount: 2,
+ },
+ {
+ testActions: []txbuilder.Action{
+ txbuilder.Action(&spendAction{
+ AssetAmount: bc.AssetAmount{
+ AssetId: testBTM,
+ Amount: 100,
+ },
+ AccountID: "test_account",
+ }),
+ txbuilder.Action(&spendAction{
+ AssetAmount: bc.AssetAmount{
+ AssetId: testAssetID1,
+ Amount: 200,
+ },
+ AccountID: "test_account",
+ }),
+ txbuilder.Action(&spendAction{
+ AssetAmount: bc.AssetAmount{
+ AssetId: testBTM,
+ Amount: 500,
+ },
+ AccountID: "test_account",
+ }),
+ txbuilder.Action(&spendAction{
+ AssetAmount: bc.AssetAmount{
+ AssetId: testAssetID1,
+ Amount: 300,
+ },
+ AccountID: "test_account",
+ }),
+ },
+ wantActions: []txbuilder.Action{
+ txbuilder.Action(&spendAction{
+ AssetAmount: bc.AssetAmount{
+ AssetId: testBTM,
+ Amount: 600,
+ },
+ AccountID: "test_account",
+ }),
+ txbuilder.Action(&spendAction{
+ AssetAmount: bc.AssetAmount{
+ AssetId: testAssetID1,
+ Amount: 500,
+ },
+ AccountID: "test_account",
+ }),
+ },
+ testActionCount: 4,
+ wantActionCount: 2,
+ },
+ {
+ testActions: []txbuilder.Action{
+ txbuilder.Action(&spendAction{
+ AssetAmount: bc.AssetAmount{
+ AssetId: testBTM,
+ Amount: 100,
+ },
+ AccountID: "test_account",
+ }),
+ txbuilder.Action(&spendAction{
+ AssetAmount: bc.AssetAmount{
+ AssetId: testAssetID1,
+ Amount: 200,
+ },
+ AccountID: "test_account",
+ }),
+ txbuilder.Action(&spendAction{
+ AssetAmount: bc.AssetAmount{
+ AssetId: testAssetID2,
+ Amount: 300,
+ },
+ AccountID: "test_account",
+ }),
+ txbuilder.Action(&spendAction{
+ AssetAmount: bc.AssetAmount{
+ AssetId: testAssetID1,
+ Amount: 300,
+ },
+ AccountID: "test_account",
+ }),
+ txbuilder.Action(&spendAction{
+ AssetAmount: bc.AssetAmount{
+ AssetId: testAssetID2,
+ Amount: 500,
+ },
+ AccountID: "test_account",
+ }),
+ },
+ wantActions: []txbuilder.Action{
+ txbuilder.Action(&spendAction{
+ AssetAmount: bc.AssetAmount{
+ AssetId: testBTM,
+ Amount: 100,
+ },
+ AccountID: "test_account",
+ }),
+ txbuilder.Action(&spendAction{
+ AssetAmount: bc.AssetAmount{
+ AssetId: testAssetID1,
+ Amount: 500,
+ },
+ AccountID: "test_account",
+ }),
+ txbuilder.Action(&spendAction{
+ AssetAmount: bc.AssetAmount{
+ AssetId: testAssetID2,
+ Amount: 800,
+ },
+ AccountID: "test_account",
+ }),
+ },
+ testActionCount: 5,
+ wantActionCount: 3,
+ },
+ {
+ testActions: []txbuilder.Action{
+ txbuilder.Action(&spendAction{
+ AssetAmount: bc.AssetAmount{
+ AssetId: testBTM,
+ Amount: 100,
+ },
+ AccountID: "test_account",
+ }),
+ txbuilder.Action(&spendAction{
+ AssetAmount: bc.AssetAmount{
+ AssetId: testBTM,
+ Amount: 200,
+ },
+ AccountID: "test_account1",
+ }),
+ txbuilder.Action(&spendAction{
+ AssetAmount: bc.AssetAmount{
+ AssetId: testBTM,
+ Amount: 500,
+ },
+ AccountID: "test_account",
+ }),
+ txbuilder.Action(&spendAction{
+ AssetAmount: bc.AssetAmount{
+ AssetId: testAssetID1,
+ Amount: 300,
+ },
+ AccountID: "test_account1",
+ }),
+ },
+ wantActions: []txbuilder.Action{
+ txbuilder.Action(&spendAction{
+ AssetAmount: bc.AssetAmount{
+ AssetId: testBTM,
+ Amount: 600,
+ },
+ AccountID: "test_account",
+ }),
+ txbuilder.Action(&spendAction{
+ AssetAmount: bc.AssetAmount{
+ AssetId: testBTM,
+ Amount: 200,
+ },
+ AccountID: "test_account1",
+ }),
+ txbuilder.Action(&spendAction{
+ AssetAmount: bc.AssetAmount{
+ AssetId: testAssetID1,
+ Amount: 300,
+ },
+ AccountID: "test_account1",
+ }),
+ },
+ testActionCount: 4,
+ wantActionCount: 3,
+ },
+ {
+ testActions: []txbuilder.Action{
+ txbuilder.Action(&spendUTXOAction{
+ OutputID: &bc.Hash{V0: 128},
+ }),
+ txbuilder.Action(&spendAction{
+ AssetAmount: bc.AssetAmount{
+ AssetId: testBTM,
+ Amount: 100,
+ },
+ AccountID: "test_account",
+ }),
+ txbuilder.Action(&spendAction{
+ AssetAmount: bc.AssetAmount{
+ AssetId: testAssetID1,
+ Amount: 200,
+ },
+ AccountID: "test_account1",
+ }),
+ txbuilder.Action(&spendUTXOAction{
+ OutputID: &bc.Hash{V0: 256},
+ }),
+ txbuilder.Action(&spendAction{
+ AssetAmount: bc.AssetAmount{
+ AssetId: testAssetID2,
+ Amount: 300,
+ },
+ AccountID: "test_account2",
+ }),
+ },
+ wantActions: []txbuilder.Action{
+ txbuilder.Action(&spendUTXOAction{
+ OutputID: &bc.Hash{V0: 128},
+ }),
+ txbuilder.Action(&spendAction{
+ AssetAmount: bc.AssetAmount{
+ AssetId: testBTM,
+ Amount: 100,
+ },
+ AccountID: "test_account",
+ }),
+ txbuilder.Action(&spendAction{
+ AssetAmount: bc.AssetAmount{
+ AssetId: testAssetID1,
+ Amount: 200,
+ },
+ AccountID: "test_account1",
+ }),
+ txbuilder.Action(&spendUTXOAction{
+ OutputID: &bc.Hash{V0: 256},
+ }),
+ txbuilder.Action(&spendAction{
+ AssetAmount: bc.AssetAmount{
+ AssetId: testAssetID2,
+ Amount: 300,
+ },
+ AccountID: "test_account2",
+ }),
+ },
+ testActionCount: 5,
+ wantActionCount: 5,
+ },
+ }
+
+ for _, c := range cases {
+ gotActions := MergeSpendAction(c.testActions)
+
+ gotMap := make(map[string]uint64)
+ wantMap := make(map[string]uint64)
+ for _, got := range gotActions {
+ switch got := got.(type) {
+ case *spendAction:
+ gotKey := got.AssetId.String() + got.AccountID
+ gotMap[gotKey] = got.Amount
+ default:
+ continue
+ }
+ }
+
+ for _, want := range c.wantActions {
+ switch want := want.(type) {
+ case *spendAction:
+ wantKey := want.AssetId.String() + want.AccountID
+ wantMap[wantKey] = want.Amount
+ default:
+ continue
+ }
+ }
+
+ for key := range gotMap {
+ if gotMap[key] != wantMap[key] {
+ t.Fatalf("gotMap[%s]=%v, wantMap[%s]=%v", key, gotMap[key], key, wantMap[key])
+ }
+ }
+
+ if len(gotActions) != c.wantActionCount {
+ t.Fatalf("number of gotActions=%d, wantActions=%d", len(gotActions), c.wantActionCount)
+ }
+ }
+}
+
+func TestCalcMergeGas(t *testing.T) {
+ chainTxUtxoNum = 10
+ cases := []struct {
+ utxoNum int
+ gas uint64
+ }{
+ {
+ utxoNum: 0,
+ gas: 0,
+ },
+ {
+ utxoNum: 1,
+ gas: 0,
+ },
+ {
+ utxoNum: 9,
+ gas: chainTxMergeGas,
+ },
+ {
+ utxoNum: 10,
+ gas: chainTxMergeGas,
+ },
+ {
+ utxoNum: 11,
+ gas: chainTxMergeGas * 2,
+ },
+ {
+ utxoNum: 20,
+ gas: chainTxMergeGas * 3,
+ },
+ {
+ utxoNum: 21,
+ gas: chainTxMergeGas * 3,
+ },
+ {
+ utxoNum: 74,
+ gas: chainTxMergeGas * 9,
+ },
+ }
+
+ for i, c := range cases {
+ gas := calcMergeGas(c.utxoNum)
+ if gas != c.gas {
+ t.Fatalf("case %d got %d want %d", i, gas, c.gas)
+ }
+ }
+}