--- /dev/null
+package utxo_view
+
+import (
+ "os"
+ "testing"
+
+ "github.com/bytom/testutil"
+
+ "github.com/golang/protobuf/proto"
+ dbm "github.com/tendermint/tmlibs/db"
+
+ "github.com/bytom/database/leveldb"
+ "github.com/bytom/database/storage"
+ "github.com/bytom/protocol/bc"
+ "github.com/bytom/protocol/bc/types"
+ "github.com/bytom/protocol/state"
+)
+
+func TestAttachOrDetachBlocks(t *testing.T) {
+ cases := []struct {
+ desc string
+ before map[bc.Hash]*storage.UtxoEntry
+ want map[bc.Hash]*storage.UtxoEntry
+ attachBlock []*bc.Block
+ detachBlock []*bc.Block
+ attachTxStatus []*bc.TransactionStatus
+ detachTxStatus []*bc.TransactionStatus
+ }{
+ {
+ desc: "coinbase tx",
+ before: make(map[bc.Hash]*storage.UtxoEntry),
+ want: map[bc.Hash]*storage.UtxoEntry{*newTx(mockBlocks[0].Transactions[0]).OutputHash(0): storage.NewUtxoEntry(true, mockBlocks[0].Block.Height, false)},
+ attachBlock: []*bc.Block{
+ types.MapBlock(&mockBlocks[0].Block),
+ },
+ attachTxStatus: []*bc.TransactionStatus{
+ &bc.TransactionStatus{VerifyStatus: []*bc.TxVerifyResult{
+ &bc.TxVerifyResult{StatusFail: false},
+ }},
+ },
+ },
+ {
+ desc: "Chain trading 3",
+ before: map[bc.Hash]*storage.UtxoEntry{
+ newTx(mockBlocks[1].Transactions[1]).getSpentOutputID(): storage.NewUtxoEntry(false, mockBlocks[1].Height-1, false),
+ },
+ want: map[bc.Hash]*storage.UtxoEntry{
+ *newTx(mockBlocks[1].Transactions[0]).OutputHash(0): storage.NewUtxoEntry(true, mockBlocks[1].Height, false),
+ *newTx(mockBlocks[1].Transactions[1]).OutputHash(0): storage.NewUtxoEntry(false, mockBlocks[1].Height, false),
+ *newTx(mockBlocks[1].Transactions[2]).OutputHash(0): storage.NewUtxoEntry(false, mockBlocks[1].Height, false),
+ *newTx(mockBlocks[1].Transactions[3]).OutputHash(0): storage.NewUtxoEntry(false, mockBlocks[1].Height, false),
+ *newTx(mockBlocks[1].Transactions[3]).OutputHash(1): storage.NewUtxoEntry(false, mockBlocks[1].Height, false),
+ },
+ attachBlock: []*bc.Block{
+ types.MapBlock(&mockBlocks[1].Block),
+ },
+ attachTxStatus: []*bc.TransactionStatus{
+ &bc.TransactionStatus{VerifyStatus: []*bc.TxVerifyResult{
+ &bc.TxVerifyResult{StatusFail: false},
+ &bc.TxVerifyResult{StatusFail: false},
+ &bc.TxVerifyResult{StatusFail: false},
+ &bc.TxVerifyResult{StatusFail: false},
+ }},
+ },
+ },
+ }
+ node := blockNode(types.MapBlock(&mockBlocks[0].Block).BlockHeader)
+ defer os.RemoveAll("temp")
+ for index, c := range cases {
+ testDB := dbm.NewDB("testdb", "leveldb", "temp")
+ store := leveldb.NewStore(testDB)
+
+ utxoViewpoint := state.NewUtxoViewpoint()
+ for k, v := range c.before {
+ utxoViewpoint.Entries[k] = v
+ }
+ if err := store.SaveChainStatus(node, utxoViewpoint); err != nil {
+ t.Error(err)
+ }
+
+ utxoViewpoint = state.NewUtxoViewpoint()
+ for index, block := range c.detachBlock {
+ if err := store.GetTransactionsUtxo(utxoViewpoint, block.Transactions); err != nil {
+ t.Error(err)
+ }
+ if err := utxoViewpoint.DetachBlock(block, c.detachTxStatus[index]); err != nil {
+ t.Error(err)
+ }
+ }
+
+ for index, block := range c.attachBlock {
+ if err := store.GetTransactionsUtxo(utxoViewpoint, block.Transactions); err != nil {
+ t.Error(err)
+ }
+ if err := utxoViewpoint.ApplyBlock(block, c.attachTxStatus[index]); err != nil {
+ t.Error(err)
+ }
+ }
+ if err := store.SaveChainStatus(node, utxoViewpoint); err != nil {
+ t.Error(err)
+ }
+
+ want := map[string]*storage.UtxoEntry{}
+ result := make(map[string]*storage.UtxoEntry)
+
+ for k, v := range c.want {
+ want[string(calcUtxoKey(&k))] = v
+ }
+
+ iter := testDB.IteratorPrefix([]byte(utxoPreFix))
+ defer iter.Release()
+
+ for iter.Next() {
+ utxoEntry := &storage.UtxoEntry{}
+ if err := proto.Unmarshal(iter.Value(), utxoEntry); err != nil {
+ t.Error(err)
+ }
+ key := string(iter.Key())
+ result[key] = utxoEntry
+ }
+
+ if !testutil.DeepEqual(want, result) {
+ t.Errorf("case [%d] fail. want: %v, result: %v", index, want, result)
+ }
+ testDB.Close()
+ os.RemoveAll("temp")
+ }
+}
--- /dev/null
+package utxo_view
+
+import (
+ "encoding/hex"
+
+ "github.com/bytom/consensus"
+ "github.com/bytom/consensus/difficulty"
+ "github.com/bytom/protocol/bc"
+ "github.com/bytom/protocol/bc/types"
+ "github.com/bytom/protocol/state"
+ "github.com/bytom/testutil"
+)
+
+const utxoPreFix = "UT:"
+
+func calcUtxoKey(hash *bc.Hash) []byte {
+ return []byte(utxoPreFix + hash.String())
+}
+
+type tx struct {
+ Tx *types.Tx
+}
+
+func newTx(t *types.Tx) *tx {
+ return &tx{
+ Tx: t,
+ }
+}
+
+func (t *tx) getSourceID(outIndex int) *bc.Hash {
+ output := t.Tx.Entries[*t.Tx.OutputID(outIndex)].(*bc.Output)
+ return output.Source.Ref
+}
+
+func (t *tx) getAmount(outIndex int) uint64 {
+ output := t.Tx.Entries[*t.Tx.OutputID(outIndex)].(*bc.Output)
+ return output.Source.Value.Amount
+}
+
+func (t *tx) getSpentOutputID() bc.Hash {
+ return t.Tx.SpentOutputIDs[0]
+}
+
+func (t *tx) OutputHash(outIndex int) *bc.Hash {
+ return t.Tx.ResultIds[outIndex]
+}
+
+func blockNode(header *bc.BlockHeader) *state.BlockNode {
+ h := types.BlockHeader{
+ Version: header.Version,
+ Height: header.Height,
+ PreviousBlockHash: *header.PreviousBlockId,
+ Timestamp: header.Timestamp,
+ Bits: header.Bits,
+ Nonce: header.Nonce,
+ }
+ return &state.BlockNode{
+ Parent: nil,
+ Hash: h.Hash(),
+ WorkSum: difficulty.CalcWork(h.Bits),
+ Version: h.Version,
+ Height: h.Height,
+ Timestamp: h.Timestamp,
+ Nonce: h.Nonce,
+ Bits: h.Bits,
+ }
+}
+
+func mustDecodeHex(str string) []byte {
+ data, err := hex.DecodeString(str)
+ if err != nil {
+ panic(err)
+ }
+ return data
+}
+
+func coinBaseTx(amount uint64, arbitrary string) *types.Tx {
+ return types.NewTx(types.TxData{
+ Inputs: []*types.TxInput{
+ types.NewCoinbaseInput([]byte(arbitrary)),
+ },
+ Outputs: []*types.TxOutput{
+ types.NewTxOutput(*consensus.BTMAssetID, amount, mustDecodeHex("00144431c4278632c6e35dd2870faa1a4b8e0a275cbc")),
+ },
+ })
+}
+
+var mockTransaction = []*tx{}
+var mockBlocks = []*block{}
+
+func toHash(hash string) bc.Hash {
+ sourceID := bc.Hash{}
+ sourceID.UnmarshalText([]byte(hash))
+ return sourceID
+}
+
+type block struct {
+ types.Block
+}
+
+func init() {
+ t := &tx{
+ Tx: types.NewTx(types.TxData{
+ Inputs: []*types.TxInput{
+ types.NewSpendInput(nil, toHash("ca9b179e549406aa583869e124e39817414d4500a8ce5476e95b6018d182b966"), *consensus.BTMAssetID, 41250000000, 0, []byte("00144431c4278632c6e35dd2870faa1a4b8e0a275cbc")),
+ },
+ Outputs: []*types.TxOutput{
+ types.NewTxOutput(*consensus.BTMAssetID, 100000000, []byte("00148c704747e94387fa0b8712b053ed2132d84820ac")),
+ types.NewTxOutput(*consensus.BTMAssetID, 41150000000, []byte("00144431c4278632c6e35dd2870faa1a4b8e0a275cbc")),
+ },
+ }),
+ }
+ mockTransaction = append(mockTransaction, t)
+
+ t = &tx{
+ Tx: types.NewTx(types.TxData{
+ Inputs: []*types.TxInput{
+ types.NewSpendInput(nil, *mockTransaction[0].getSourceID(1), *consensus.BTMAssetID, 41150000000, 1, []byte("00144431c4278632c6e35dd2870faa1a4b8e0a275cbc")),
+ },
+ Outputs: []*types.TxOutput{
+ types.NewTxOutput(*consensus.BTMAssetID, 100000000, []byte("00148c704747e94387fa0b8712b053ed2132d84820ac")),
+ types.NewTxOutput(*consensus.BTMAssetID, 41050000000, []byte("00144431c4278632c6e35dd2870faa1a4b8e0a275cbc")),
+ },
+ }),
+ }
+ mockTransaction = append(mockTransaction, t)
+
+ t = &tx{
+ Tx: types.NewTx(types.TxData{
+ Inputs: []*types.TxInput{
+ types.NewSpendInput(nil, *mockTransaction[1].getSourceID(1), *consensus.BTMAssetID, 41050000000, 1, []byte("00144431c4278632c6e35dd2870faa1a4b8e0a275cbc")),
+ },
+ Outputs: []*types.TxOutput{
+ types.NewTxOutput(*consensus.BTMAssetID, 100000000, []byte("00148c704747e94387fa0b8712b053ed2132d84820ac")),
+ types.NewTxOutput(*consensus.BTMAssetID, 40950000000, []byte("00144431c4278632c6e35dd2870faa1a4b8e0a275cbc")),
+ },
+ }),
+ }
+ mockTransaction = append(mockTransaction, t)
+
+ mockBlocks = []*block{
+ // coinbase tx
+ &block{Block: types.Block{
+ BlockHeader: types.BlockHeader{
+ Height: 100,
+ PreviousBlockHash: testutil.MustDecodeHash("0ab29c0bd7bff3b3b7eb98802f8d5f8833884c86c0fb21559a65cc58dda99667"),
+ Timestamp: 1522908275,
+ Nonce: 0,
+ },
+ Transactions: []*types.Tx{
+ coinBaseTx(41250000000, "arbitrary block0"),
+ },
+ }},
+
+ // Chain trading 3
+ &block{Block: types.Block{
+ BlockHeader: types.BlockHeader{
+ Height: 101,
+ PreviousBlockHash: testutil.MustDecodeHash("0ab29c0bd7bff3b3b7eb98802f8d5f8833884c86c0fb21559a65cc58dda99667"),
+ Timestamp: 1522908275,
+ Nonce: 0,
+ },
+ Transactions: []*types.Tx{
+ coinBaseTx(41250000000, "arbitrary block1"),
+ mockTransaction[0].Tx,
+ mockTransaction[1].Tx,
+ mockTransaction[2].Tx,
+ },
+ }},
+ }
+
+}