// +build functional package test import ( "encoding/json" "fmt" "io/ioutil" "os" "testing" log "github.com/sirupsen/logrus" dbm "github.com/tendermint/tmlibs/db" "github.com/vapor/account" "github.com/vapor/asset" "github.com/vapor/blockchain/pseudohsm" "github.com/vapor/consensus" "github.com/vapor/protocol/bc" "github.com/vapor/protocol/bc/types" "github.com/vapor/protocol/validation" "github.com/vapor/protocol/vm" ) func init() { log.SetLevel(log.ErrorLevel) } type TxTestConfig struct { Keys []*keyInfo `json:"keys"` Accounts []*accountInfo `json:"accounts"` Transactions []*ttTransaction `json:"transactions"` } func (cfg *TxTestConfig) Run() error { dirPath, err := ioutil.TempDir(".", "pseudo_hsm") if err != nil { return err } defer os.RemoveAll(dirPath) hsm, err := pseudohsm.New(dirPath) if err != nil { return err } chainDB := dbm.NewDB("chain_db", "leveldb", "chain_db") defer os.RemoveAll("chain_db") chain, _, _, _ := MockChain(chainDB) txTestDB := dbm.NewDB("tx_test_db", "leveldb", "tx_test_db") defer os.RemoveAll("tx_test_db") accountManager := account.NewManager(txTestDB, chain) assets := asset.NewRegistry(txTestDB, chain) generator := NewTxGenerator(accountManager, assets, hsm) for _, key := range cfg.Keys { if err := generator.createKey(key.Name, key.Password); err != nil { return err } } for _, acc := range cfg.Accounts { if err := generator.createAccount(acc.Name, acc.Keys, acc.Quorum); err != nil { return err } } block := &bc.Block{ BlockHeader: &bc.BlockHeader{ Height: 1, Version: 1, }, } for _, t := range cfg.Transactions { tx, err := t.create(generator) if err != nil { return err } tx.TxData.Version = t.Version tx.Tx = types.MapTx(&tx.TxData) status, err := validation.ValidateTx(tx.Tx, block) result := err == nil if result != t.Valid { return fmt.Errorf("tx %s validate failed, expected: %t, have: %t", t.Describe, t.Valid, result) } if status == nil { continue } gasOnlyTx := false if err != nil && status.GasValid { gasOnlyTx = true } if gasOnlyTx != t.GasOnly { return fmt.Errorf("gas only tx %s validate failed", t.Describe) } if result && t.TxFee != status.BTMValue { return fmt.Errorf("gas used dismatch, expected: %d, have: %d", t.TxFee, status.BTMValue) } } return nil } type ttTransaction struct { wtTransaction Describe string `json:"describe"` Version uint64 `json:"version"` Valid bool `json:"valid"` GasOnly bool `json:"gas_only"` TxFee uint64 `json:"tx_fee"` } // UnmarshalJSON unmarshal transaction with default version 1 func (t *ttTransaction) UnmarshalJSON(data []byte) error { type typeAlias ttTransaction tx := &typeAlias{ Version: 1, } err := json.Unmarshal(data, tx) if err != nil { return err } *t = ttTransaction(*tx) return nil } func (t *ttTransaction) create(g *TxGenerator) (*types.Tx, error) { g.Reset() for _, input := range t.Inputs { switch input.Type { case "spend_account": utxo, err := g.mockUtxo(input.AccountAlias, input.AssetAlias, input.Amount) if err != nil { return nil, err } if err := g.AddTxInputFromUtxo(utxo, input.AccountAlias); err != nil { return nil, err } case "issue": _, err := g.createAsset(input.AccountAlias, input.AssetAlias) if err != nil { return nil, err } if err := g.AddIssuanceInput(input.AssetAlias, input.Amount); err != nil { return nil, err } } } for _, output := range t.Outputs { switch output.Type { case "output": if err := g.AddTxOutput(output.AccountAlias, output.AssetAlias, output.Amount); err != nil { return nil, err } case "retire": if err := g.AddRetirement(output.AssetAlias, output.Amount); err != nil { return nil, err } } } return g.Sign(t.Passwords) } func TestTx(t *testing.T) { walk(t, txTestDir, func(t *testing.T, name string, test *TxTestConfig) { if err := test.Run(); err != nil { t.Fatal(err) } }) } func TestCoinbaseMature(t *testing.T) { db := dbm.NewDB("test_coinbase_mature_db", "leveldb", "test_coinbase_mature_db") defer os.RemoveAll("test_coinbase_mature_db") chain, _, _, _ := MockChain(db) defaultCtrlProg := []byte{byte(vm.OP_TRUE)} if err := AppendBlocks(chain, 1); err != nil { t.Fatal(err) } height := chain.BestBlockHeight() block, err := chain.GetBlockByHeight(height) if err != nil { t.Fatal(err) } tx, err := CreateTxFromTx(block.Transactions[0], 0, 1000000000, defaultCtrlProg) if err != nil { t.Fatal(err) } txs := []*types.Tx{tx} matureHeight := chain.BestBlockHeight() + consensus.CoinbasePendingBlockNumber currentHeight := chain.BestBlockHeight() for h := currentHeight + 1; h < matureHeight; h++ { block, err := NewBlock(chain, txs, defaultCtrlProg) if err != nil { t.Fatal(err) } if err := SolveAndUpdate(chain, block); err == nil { t.Fatal("spent immature coinbase output success") } block, err = NewBlock(chain, nil, defaultCtrlProg) if err != nil { t.Fatal(err) } if err := SolveAndUpdate(chain, block); err != nil { t.Fatal(err) } } block, err = NewBlock(chain, txs, defaultCtrlProg) if err != nil { t.Fatal(err) } if err := SolveAndUpdate(chain, block); err != nil { t.Fatalf("spent mature coinbase output failed: %s", err) } } func TestCoinbaseTx(t *testing.T) { db := dbm.NewDB("test_coinbase_tx_db", "leveldb", "test_coinbase_tx_db") defer os.RemoveAll("test_coinbase_tx_db") chain, _, _, _ := MockChain(db) defaultCtrlProg := []byte{byte(vm.OP_TRUE)} if err := AppendBlocks(chain, 1); err != nil { t.Fatal(err) } block, err := chain.GetBlockByHeight(chain.BestBlockHeight()) if err != nil { t.Fatal(err) } tx, err := CreateTxFromTx(block.Transactions[0], 0, 1000000000, defaultCtrlProg) if err != nil { t.Fatal(err) } block, err = NewBlock(chain, []*types.Tx{tx}, defaultCtrlProg) if err != nil { t.Fatal(err) } txFees := []uint64{100000, 5000000000000} for _, txFee := range txFees { coinbaseTx, err := CreateCoinbaseTx(defaultCtrlProg, block.Height, txFee) if err != nil { t.Fatal(err) } if err := ReplaceCoinbase(block, coinbaseTx); err != nil { t.Fatal(err) } if err := SolveAndUpdate(chain, block); err == nil { t.Fatalf("invalid coinbase tx validate success") } } }