OSDN Git Service

Utxo storage (#196)
authorPaladz <yzhu101@uottawa.ca>
Mon, 18 Dec 2017 02:08:38 +0000 (10:08 +0800)
committerGitHub <noreply@github.com>
Mon, 18 Dec 2017 02:08:38 +0000 (10:08 +0800)
* tmp save

* init version of snapshot upgrade

* pass all the unit test

* fix the code logic

* fix a bug

* add unit test for TestSaveMainchain

* add unit test for TestSaveUtxoView

* add unit test: TestGetTransactionsUtxo

* add unit test for utxo_view

* delete the costly unit test

* fix bug on endto end test

26 files changed:
blockchain/account/reserve.go
blockchain/hsm_test.go
blockchain/reactor.go
blockchain/txdb/internal/storage/storage.pb.go [deleted file]
blockchain/txdb/internal/storage/storage.proto [deleted file]
blockchain/txdb/mainchain.go
blockchain/txdb/mainchain_test.go
blockchain/txdb/snapshot.go [deleted file]
blockchain/txdb/snapshot_test.go [deleted file]
blockchain/txdb/storage/storage.pb.go [new file with mode: 0644]
blockchain/txdb/storage/storage.proto [new file with mode: 0644]
blockchain/txdb/storage/utxo_entry.go [new file with mode: 0644]
blockchain/txdb/store.go
blockchain/txdb/utxo_view.go [new file with mode: 0644]
blockchain/txdb/utxo_view_test.go [new file with mode: 0644]
config/genesis.go
config/genesis_test.go
mining/mining.go
mining/mining_test.go [deleted file]
protocol/block.go
protocol/protocol.go
protocol/recover.go [deleted file]
protocol/state/snapshot.go [deleted file]
protocol/state/snapshot_test.go [deleted file]
protocol/state/utxo_view.go [new file with mode: 0644]
protocol/state/utxo_view_test.go [new file with mode: 0644]

index 79ef71f..4d30088 100755 (executable)
@@ -240,8 +240,11 @@ func (re *reserver) ExpireReservations(ctx context.Context) error {
 }
 
 func (re *reserver) checkUTXO(u *utxo) bool {
-       _, s := re.c.State()
-       return s.Tree.Contains(u.OutputID.Bytes())
+       utxo, err := re.c.GetUtxo(&u.OutputID)
+       if err != nil {
+               return false
+       }
+       return !utxo.Spend
 }
 
 func (re *reserver) source(src source) *sourceReserver {
index 27f0ef3..2d96fd0 100755 (executable)
@@ -35,23 +35,13 @@ func TestHSM(t *testing.T) {
 
        var accounts *account.Manager
        var assets *asset.Registry
-       genesisBlock := cfg.GenerateGenesisBlock()
        // tx pool init
        txPool := protocol.NewTxPool()
-       chain, err := protocol.NewChain(genesisBlock.Hash(), store, txPool)
+       chain, err := protocol.NewChain(bc.Hash{}, store, txPool)
        if err != nil {
                t.Fatal(err)
        }
 
-       // add gensis block info
-       if err := chain.SaveBlock(genesisBlock); err != nil {
-               t.Fatal(err)
-       }
-       // parse block and apply
-       if err := chain.ConnectBlock(genesisBlock); err != nil {
-               t.Fatal(err)
-       }
-
        walletDB := dbm.NewDB("wallet", config.DBBackend, dir)
        accounts = account.NewManager(walletDB, chain)
        assets = asset.NewRegistry(walletDB, chain)
index 4ff0603..ab10fdb 100755 (executable)
@@ -1,11 +1,11 @@
 package blockchain
 
 import (
-       "fmt"
-       "time"
        "context"
-       "reflect"
+       "fmt"
        "net/http"
+       "reflect"
+       "time"
 
        log "github.com/sirupsen/logrus"
        cmn "github.com/tendermint/tmlibs/common"
@@ -95,7 +95,6 @@ func batchRecover(ctx context.Context, v *interface{}) {
        }
 }
 
-
 func (bcr *BlockchainReactor) info(ctx context.Context) (map[string]interface{}, error) {
        return map[string]interface{}{
                "is_configured": false,
@@ -118,7 +117,6 @@ func maxBytes(h http.Handler) http.Handler {
        })
 }
 
-
 // Used as a request object for api queries
 type requestQuery struct {
        Filter       string        `json:"filter,omitempty"`
@@ -256,7 +254,7 @@ func (bcR *BlockchainReactor) Receive(chID byte, src *p2p.Peer, msgBytes []byte)
                bcR.blockKeeper.AddBlock(msg.GetBlock(), src.Key)
 
        case *StatusRequestMessage:
-               block, _ := bcR.chain.State()
+               block := bcR.chain.BestBlock()
                src.TrySend(BlockchainChannel, struct{ BlockchainMessage }{NewStatusResponseMessage(block)})
 
        case *StatusResponseMessage:
@@ -302,10 +300,9 @@ func (bcR *BlockchainReactor) syncRoutine() {
        }
 }
 
-
 // BroadcastStatusRequest broadcasts `BlockStore` height.
 func (bcR *BlockchainReactor) BroadcastStatusResponse() {
-       block, _ := bcR.chain.State()
+       block := bcR.chain.BestBlock()
        bcR.Switch.Broadcast(BlockchainChannel, struct{ BlockchainMessage }{NewStatusResponseMessage(block)})
 }
 
diff --git a/blockchain/txdb/internal/storage/storage.pb.go b/blockchain/txdb/internal/storage/storage.pb.go
deleted file mode 100644 (file)
index 5994ce6..0000000
+++ /dev/null
@@ -1,162 +0,0 @@
-// Code generated by protoc-gen-go. DO NOT EDIT.
-// source: storage.proto
-
-/*
-Package storage is a generated protocol buffer package.
-
-It is generated from these files:
-       storage.proto
-
-It has these top-level messages:
-       Snapshot
-       Mainchain
-*/
-package storage
-
-import proto "github.com/golang/protobuf/proto"
-import fmt "fmt"
-import math "math"
-
-// Reference imports to suppress errors if they are not otherwise used.
-var _ = proto.Marshal
-var _ = fmt.Errorf
-var _ = math.Inf
-
-// This is a compile-time assertion to ensure that this generated file
-// is compatible with the proto package it is being compiled against.
-// A compilation error at this line likely means your copy of the
-// proto package needs to be updated.
-const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package
-
-// Snapshot represents a snapshot of the blockchain, including the state
-// tree and issuance memory.
-type Snapshot struct {
-       // Nodes contains every node within the state tree, including interior nodes.
-       // The nodes are ordered according to a pre-order traversal.
-       Nodes []*Snapshot_StateTreeNode `protobuf:"bytes,1,rep,name=nodes" json:"nodes,omitempty"`
-       // Nonces contains the record of recent nonces for ensuring
-       // uniqueness of issuances.
-       Nonces []*Snapshot_Nonce `protobuf:"bytes,2,rep,name=nonces" json:"nonces,omitempty"`
-}
-
-func (m *Snapshot) Reset()                    { *m = Snapshot{} }
-func (m *Snapshot) String() string            { return proto.CompactTextString(m) }
-func (*Snapshot) ProtoMessage()               {}
-func (*Snapshot) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} }
-
-func (m *Snapshot) GetNodes() []*Snapshot_StateTreeNode {
-       if m != nil {
-               return m.Nodes
-       }
-       return nil
-}
-
-func (m *Snapshot) GetNonces() []*Snapshot_Nonce {
-       if m != nil {
-               return m.Nonces
-       }
-       return nil
-}
-
-type Snapshot_Nonce struct {
-       Hash     []byte `protobuf:"bytes,1,opt,name=hash,proto3" json:"hash,omitempty"`
-       ExpiryMs uint64 `protobuf:"varint,2,opt,name=expiry_ms,json=expiryMs" json:"expiry_ms,omitempty"`
-}
-
-func (m *Snapshot_Nonce) Reset()                    { *m = Snapshot_Nonce{} }
-func (m *Snapshot_Nonce) String() string            { return proto.CompactTextString(m) }
-func (*Snapshot_Nonce) ProtoMessage()               {}
-func (*Snapshot_Nonce) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0, 0} }
-
-func (m *Snapshot_Nonce) GetHash() []byte {
-       if m != nil {
-               return m.Hash
-       }
-       return nil
-}
-
-func (m *Snapshot_Nonce) GetExpiryMs() uint64 {
-       if m != nil {
-               return m.ExpiryMs
-       }
-       return 0
-}
-
-type Snapshot_StateTreeNode struct {
-       Key []byte `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"`
-}
-
-func (m *Snapshot_StateTreeNode) Reset()                    { *m = Snapshot_StateTreeNode{} }
-func (m *Snapshot_StateTreeNode) String() string            { return proto.CompactTextString(m) }
-func (*Snapshot_StateTreeNode) ProtoMessage()               {}
-func (*Snapshot_StateTreeNode) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0, 1} }
-
-func (m *Snapshot_StateTreeNode) GetKey() []byte {
-       if m != nil {
-               return m.Key
-       }
-       return nil
-}
-
-// Mainchain represents a mainchain of the blockchain
-type Mainchain struct {
-       Hashs []*Mainchain_Hash `protobuf:"bytes,1,rep,name=hashs" json:"hashs,omitempty"`
-}
-
-func (m *Mainchain) Reset()                    { *m = Mainchain{} }
-func (m *Mainchain) String() string            { return proto.CompactTextString(m) }
-func (*Mainchain) ProtoMessage()               {}
-func (*Mainchain) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{1} }
-
-func (m *Mainchain) GetHashs() []*Mainchain_Hash {
-       if m != nil {
-               return m.Hashs
-       }
-       return nil
-}
-
-type Mainchain_Hash struct {
-       Key []byte `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"`
-}
-
-func (m *Mainchain_Hash) Reset()                    { *m = Mainchain_Hash{} }
-func (m *Mainchain_Hash) String() string            { return proto.CompactTextString(m) }
-func (*Mainchain_Hash) ProtoMessage()               {}
-func (*Mainchain_Hash) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{1, 0} }
-
-func (m *Mainchain_Hash) GetKey() []byte {
-       if m != nil {
-               return m.Key
-       }
-       return nil
-}
-
-func init() {
-       proto.RegisterType((*Snapshot)(nil), "chain.core.txdb.internal.storage.Snapshot")
-       proto.RegisterType((*Snapshot_Nonce)(nil), "chain.core.txdb.internal.storage.Snapshot.Nonce")
-       proto.RegisterType((*Snapshot_StateTreeNode)(nil), "chain.core.txdb.internal.storage.Snapshot.StateTreeNode")
-       proto.RegisterType((*Mainchain)(nil), "chain.core.txdb.internal.storage.Mainchain")
-       proto.RegisterType((*Mainchain_Hash)(nil), "chain.core.txdb.internal.storage.Mainchain.Hash")
-}
-
-func init() { proto.RegisterFile("storage.proto", fileDescriptor0) }
-
-var fileDescriptor0 = []byte{
-       // 252 bytes of a gzipped FileDescriptorProto
-       0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x91, 0x31, 0x4f, 0xc3, 0x30,
-       0x10, 0x85, 0x95, 0x36, 0x29, 0xcd, 0x41, 0x25, 0xe4, 0x29, 0x0a, 0x4b, 0xe8, 0x94, 0xe9, 0x84,
-       0x60, 0xe9, 0xcc, 0x80, 0xba, 0x34, 0x83, 0xcb, 0xc4, 0x82, 0xdc, 0xe4, 0x84, 0x23, 0xa8, 0x1d,
-       0xd9, 0x1e, 0xda, 0x91, 0x7f, 0x8e, 0xec, 0xb8, 0x48, 0x88, 0x01, 0xba, 0x9d, 0x4f, 0xfe, 0xbe,
-       0xf7, 0x64, 0xc3, 0xc2, 0x3a, 0x6d, 0xc4, 0x1b, 0xe1, 0x60, 0xb4, 0xd3, 0xac, 0x6a, 0xa5, 0xe8,
-       0x15, 0xb6, 0xda, 0x10, 0xba, 0x43, 0xb7, 0xc3, 0x5e, 0x39, 0x32, 0x4a, 0x7c, 0x60, 0xbc, 0xb7,
-       0xfc, 0x9c, 0xc0, 0x7c, 0xab, 0xc4, 0x60, 0xa5, 0x76, 0xac, 0x81, 0x4c, 0xe9, 0x8e, 0x6c, 0x91,
-       0x54, 0xd3, 0xfa, 0xf2, 0x7e, 0x85, 0x7f, 0xe1, 0x78, 0x42, 0x71, 0xeb, 0x84, 0xa3, 0x67, 0x43,
-       0xd4, 0xe8, 0x8e, 0xf8, 0xa8, 0x61, 0x6b, 0x98, 0x29, 0xad, 0x5a, 0xb2, 0xc5, 0x24, 0x08, 0xef,
-       0xce, 0x10, 0x36, 0x1e, 0xe4, 0x91, 0x2f, 0x57, 0x90, 0x85, 0x05, 0x63, 0x90, 0x4a, 0x61, 0x65,
-       0x91, 0x54, 0x49, 0x7d, 0xc5, 0xc3, 0xcc, 0x6e, 0x20, 0xa7, 0xc3, 0xd0, 0x9b, 0xe3, 0xeb, 0xde,
-       0x27, 0x25, 0x75, 0xca, 0xe7, 0xe3, 0x62, 0x63, 0xcb, 0x5b, 0x58, 0xfc, 0xe8, 0xc6, 0xae, 0x61,
-       0xfa, 0x4e, 0xc7, 0x28, 0xf0, 0xe3, 0x72, 0x0f, 0xf9, 0x46, 0xf4, 0x2a, 0x74, 0x63, 0x4f, 0x90,
-       0x79, 0xe9, 0xe9, 0x0d, 0xfe, 0x51, 0xf9, 0x9b, 0xc5, 0xb5, 0xb0, 0x92, 0x8f, 0x78, 0x59, 0x40,
-       0xea, 0x8f, 0xbf, 0xe3, 0x1e, 0xf3, 0x97, 0x8b, 0x88, 0xee, 0x66, 0xe1, 0x9b, 0x1e, 0xbe, 0x02,
-       0x00, 0x00, 0xff, 0xff, 0xf6, 0x91, 0x6b, 0xd7, 0xb7, 0x01, 0x00, 0x00,
-}
diff --git a/blockchain/txdb/internal/storage/storage.proto b/blockchain/txdb/internal/storage/storage.proto
deleted file mode 100644 (file)
index 2d8bc13..0000000
+++ /dev/null
@@ -1,34 +0,0 @@
-syntax = "proto3";
-option go_package = "storage";
-package chain.core.txdb.internal.storage;
-
-// Snapshot represents a snapshot of the blockchain, including the state
-// tree and issuance memory.
-message Snapshot {
-  // Nodes contains every node within the state tree, including interior nodes.
-  // The nodes are ordered according to a pre-order traversal.
-  repeated StateTreeNode nodes = 1;
-
-  // Nonces contains the record of recent nonces for ensuring
-  // uniqueness of issuances.
-  repeated Nonce nonces = 2;
-
-  message Nonce {
-    bytes  hash      = 1;
-    uint64 expiry_ms = 2;
-  }
-
-  message StateTreeNode {
-    bytes key = 1;
-  }
-}
-
-// Mainchain represents a mainchain of the blockchain
-message Mainchain {
-
-  repeated Hash hashs = 1;
-
-  message Hash {
-    bytes key = 1;
-  }
-}
\ No newline at end of file
index ac8ebd5..90e5b14 100644 (file)
@@ -6,7 +6,7 @@ import (
        "github.com/golang/protobuf/proto"
        dbm "github.com/tendermint/tmlibs/db"
 
-       "github.com/bytom/blockchain/txdb/internal/storage"
+       "github.com/bytom/blockchain/txdb/storage"
        "github.com/bytom/errors"
        "github.com/bytom/protocol/bc"
 )
@@ -35,7 +35,7 @@ func DecodeMainchain(data []byte) (map[uint64]*bc.Hash, error) {
        return mainchain, nil
 }
 
-func saveMainchain(db dbm.DB, mainchain map[uint64]*bc.Hash, hash *bc.Hash) error {
+func saveMainchain(batch dbm.Batch, mainchain map[uint64]*bc.Hash, hash *bc.Hash) error {
        var mainchainList storage.Mainchain
        for i := 1; i <= len(mainchain); i++ {
                rawHash := &storage.Mainchain_Hash{Key: mainchain[uint64(i)].Bytes()}
@@ -47,8 +47,7 @@ func saveMainchain(db dbm.DB, mainchain map[uint64]*bc.Hash, hash *bc.Hash) erro
                return errors.Wrap(err, "marshaling Mainchain")
        }
 
-       db.Set(calcMainchainKey(hash), b)
-       db.SetSync(nil, nil)
+       batch.Set(calcMainchainKey(hash), b)
        return nil
 }
 
index 92b1668..560e912 100644 (file)
@@ -8,8 +8,35 @@ import (
        dbm "github.com/tendermint/tmlibs/db"
 
        "github.com/bytom/protocol/bc"
+       "github.com/bytom/testutil"
 )
 
+func TestSaveMainchain(t *testing.T) {
+       testDB := dbm.NewDB("testdb", "leveldb", "temp")
+       defer os.RemoveAll("temp")
+
+       inputMain := make(map[uint64]*bc.Hash)
+       for i := uint64(1); i <= uint64(10); i++ {
+               inputMain[i] = &bc.Hash{V0: i}
+       }
+
+       saveHash := &bc.Hash{V0: 0}
+       batch := testDB.NewBatch()
+       if err := saveMainchain(batch, inputMain, saveHash); err != nil {
+               t.Errorf(err.Error())
+       }
+       batch.Write()
+
+       fetchMain, err := getMainchain(testDB, saveHash)
+       if err != nil {
+               t.Errorf(err.Error())
+       }
+
+       if !testutil.DeepEqual(inputMain, fetchMain) {
+               t.Errorf("inputMain and fetchMain is not equal")
+       }
+}
+
 func TestCleanMainchainDB(t *testing.T) {
        testDB := dbm.NewDB("testdb", "leveldb", "temp")
        defer os.RemoveAll("temp")
diff --git a/blockchain/txdb/snapshot.go b/blockchain/txdb/snapshot.go
deleted file mode 100644 (file)
index 45a22b5..0000000
+++ /dev/null
@@ -1,103 +0,0 @@
-package txdb
-
-import (
-       "bytes"
-
-       "github.com/golang/protobuf/proto"
-       dbm "github.com/tendermint/tmlibs/db"
-
-       "github.com/bytom/blockchain/txdb/internal/storage"
-       "github.com/bytom/errors"
-       "github.com/bytom/protocol/bc"
-       "github.com/bytom/protocol/patricia"
-       "github.com/bytom/protocol/state"
-)
-
-const snapshotPreFix = "SS:"
-
-func calcSnapshotKey(hash *bc.Hash) []byte {
-       return []byte(snapshotPreFix + hash.String())
-}
-
-// DecodeSnapshot decodes a snapshot from bytes
-func DecodeSnapshot(data []byte) (*state.Snapshot, error) {
-       var storedSnapshot storage.Snapshot
-       if err := proto.Unmarshal(data, &storedSnapshot); err != nil {
-               return nil, errors.Wrap(err, "unmarshaling state snapshot proto")
-       }
-
-       tree := new(patricia.Tree)
-       for _, node := range storedSnapshot.Nodes {
-               if err := tree.Insert(node.Key); err != nil {
-                       return nil, errors.Wrap(err, "reconstructing state tree")
-               }
-       }
-
-       nonces := make(map[bc.Hash]uint64, len(storedSnapshot.Nonces))
-       for _, nonce := range storedSnapshot.Nonces {
-               var b32 [32]byte
-               copy(b32[:], nonce.Hash)
-               hash := bc.NewHash(b32)
-               nonces[hash] = nonce.ExpiryMs
-       }
-
-       return &state.Snapshot{
-               Tree:   tree,
-               Nonces: nonces,
-       }, nil
-}
-
-func saveSnapshot(db dbm.DB, snapshot *state.Snapshot, hash *bc.Hash) error {
-       var storedSnapshot storage.Snapshot
-       err := patricia.Walk(snapshot.Tree, func(key []byte) error {
-               n := &storage.Snapshot_StateTreeNode{Key: key}
-               storedSnapshot.Nodes = append(storedSnapshot.Nodes, n)
-               return nil
-       })
-       if err != nil {
-               return errors.Wrap(err, "walking patricia tree")
-       }
-
-       storedSnapshot.Nonces = make([]*storage.Snapshot_Nonce, 0, len(snapshot.Nonces))
-       for k, v := range snapshot.Nonces {
-               storedSnapshot.Nonces = append(storedSnapshot.Nonces, &storage.Snapshot_Nonce{
-                       Hash:     k.Bytes(),
-                       ExpiryMs: v,
-               })
-       }
-
-       b, err := proto.Marshal(&storedSnapshot)
-       if err != nil {
-               return errors.Wrap(err, "marshaling state snapshot")
-       }
-
-       db.Set(calcSnapshotKey(hash), b)
-       db.SetSync(nil, nil)
-       return nil
-}
-
-func getSnapshot(db dbm.DB, hash *bc.Hash) (*state.Snapshot, error) {
-       data := db.Get(calcSnapshotKey(hash))
-       if data == nil {
-               return nil, errors.New("no this snapshot")
-       }
-
-       snapshot, err := DecodeSnapshot(data)
-       if err != nil {
-               return nil, errors.Wrap(err, "decoding snapshot")
-       }
-       return snapshot, nil
-}
-
-func cleanSnapshotDB(db dbm.DB, hash *bc.Hash) {
-       keepKey := calcSnapshotKey(hash)
-
-       iter := db.IteratorPrefix([]byte(snapshotPreFix))
-       defer iter.Release()
-       for iter.Next() {
-               if key := iter.Key(); !bytes.Equal(key, keepKey) {
-                       db.Delete(key)
-               }
-       }
-       db.SetSync(nil, nil)
-}
diff --git a/blockchain/txdb/snapshot_test.go b/blockchain/txdb/snapshot_test.go
deleted file mode 100644 (file)
index ad03729..0000000
+++ /dev/null
@@ -1,69 +0,0 @@
-package txdb
-
-import (
-       "bytes"
-       "os"
-       "testing"
-
-       dbm "github.com/tendermint/tmlibs/db"
-
-       "github.com/bytom/protocol/bc"
-       "github.com/bytom/protocol/state"
-)
-
-func TestProtoSnapshotTree(t *testing.T) {
-       testDB := dbm.NewDB("testdb", "leveldb", "temp")
-       defer os.RemoveAll("temp")
-
-       hashes := []bc.Hash{}
-       insertSnap := state.Empty()
-
-       hash := bc.Hash{}
-       for i := uint64(0); i <= uint64(10); i++ {
-               hash.V0 = i
-               hashes = append(hashes, hash)
-               insertSnap.Tree.Insert(hash.Bytes())
-       }
-
-       if err := saveSnapshot(testDB, insertSnap, &hash); err != nil {
-               t.Errorf(err.Error())
-       }
-
-       popSnap, err := getSnapshot(testDB, &hash)
-       if err != nil {
-               t.Errorf(err.Error())
-       }
-
-       for _, h := range hashes {
-               if !popSnap.Tree.Contains(h.Bytes()) {
-                       t.Errorf("%s isn't in the snap tree", h.String())
-               }
-       }
-}
-
-func TestCleanSnapshotDB(t *testing.T) {
-       testDB := dbm.NewDB("testdb", "leveldb", "temp")
-       defer os.RemoveAll("temp")
-
-       // Insert the test data
-       hash := &bc.Hash{}
-       for i := uint64(0); i <= uint64(10); i++ {
-               hash.V0 = i
-               testDB.Set(calcSnapshotKey(hash), nil)
-       }
-       testDB.SetSync(nil, nil)
-
-       // run the test function
-       cleanSnapshotDB(testDB, hash)
-
-       // check the clean result
-       iter := testDB.IteratorPrefix([]byte(snapshotPreFix))
-       defer iter.Release()
-
-       if !iter.Next() || !bytes.Equal(iter.Key(), calcSnapshotKey(hash)) {
-               t.Errorf("latest snapshot get deleted from db")
-       }
-       if iter.Next() {
-               t.Errorf("more than one snapshot still saved in the db")
-       }
-}
diff --git a/blockchain/txdb/storage/storage.pb.go b/blockchain/txdb/storage/storage.pb.go
new file mode 100644 (file)
index 0000000..de7736e
--- /dev/null
@@ -0,0 +1,120 @@
+// Code generated by protoc-gen-go. DO NOT EDIT.
+// source: storage.proto
+
+/*
+Package storage is a generated protocol buffer package.
+
+It is generated from these files:
+       storage.proto
+
+It has these top-level messages:
+       UtxoEntry
+       Mainchain
+*/
+package storage
+
+import proto "github.com/golang/protobuf/proto"
+import fmt "fmt"
+import math "math"
+
+// Reference imports to suppress errors if they are not otherwise used.
+var _ = proto.Marshal
+var _ = fmt.Errorf
+var _ = math.Inf
+
+// This is a compile-time assertion to ensure that this generated file
+// is compatible with the proto package it is being compiled against.
+// A compilation error at this line likely means your copy of the
+// proto package needs to be updated.
+const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package
+
+type UtxoEntry struct {
+       IsCoinBase  bool   `protobuf:"varint,1,opt,name=isCoinBase" json:"isCoinBase,omitempty"`
+       BlockHeight uint64 `protobuf:"varint,2,opt,name=blockHeight" json:"blockHeight,omitempty"`
+       Spend       bool   `protobuf:"varint,3,opt,name=spend" json:"spend,omitempty"`
+}
+
+func (m *UtxoEntry) Reset()                    { *m = UtxoEntry{} }
+func (m *UtxoEntry) String() string            { return proto.CompactTextString(m) }
+func (*UtxoEntry) ProtoMessage()               {}
+func (*UtxoEntry) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} }
+
+func (m *UtxoEntry) GetIsCoinBase() bool {
+       if m != nil {
+               return m.IsCoinBase
+       }
+       return false
+}
+
+func (m *UtxoEntry) GetBlockHeight() uint64 {
+       if m != nil {
+               return m.BlockHeight
+       }
+       return 0
+}
+
+func (m *UtxoEntry) GetSpend() bool {
+       if m != nil {
+               return m.Spend
+       }
+       return false
+}
+
+// Mainchain represents a mainchain of the blockchain
+type Mainchain struct {
+       Hashs []*Mainchain_Hash `protobuf:"bytes,1,rep,name=hashs" json:"hashs,omitempty"`
+}
+
+func (m *Mainchain) Reset()                    { *m = Mainchain{} }
+func (m *Mainchain) String() string            { return proto.CompactTextString(m) }
+func (*Mainchain) ProtoMessage()               {}
+func (*Mainchain) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{1} }
+
+func (m *Mainchain) GetHashs() []*Mainchain_Hash {
+       if m != nil {
+               return m.Hashs
+       }
+       return nil
+}
+
+type Mainchain_Hash struct {
+       Key []byte `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"`
+}
+
+func (m *Mainchain_Hash) Reset()                    { *m = Mainchain_Hash{} }
+func (m *Mainchain_Hash) String() string            { return proto.CompactTextString(m) }
+func (*Mainchain_Hash) ProtoMessage()               {}
+func (*Mainchain_Hash) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{1, 0} }
+
+func (m *Mainchain_Hash) GetKey() []byte {
+       if m != nil {
+               return m.Key
+       }
+       return nil
+}
+
+func init() {
+       proto.RegisterType((*UtxoEntry)(nil), "chain.core.txdb.internal.storage.UtxoEntry")
+       proto.RegisterType((*Mainchain)(nil), "chain.core.txdb.internal.storage.Mainchain")
+       proto.RegisterType((*Mainchain_Hash)(nil), "chain.core.txdb.internal.storage.Mainchain.Hash")
+}
+
+func init() { proto.RegisterFile("storage.proto", fileDescriptor0) }
+
+var fileDescriptor0 = []byte{
+       // 212 bytes of a gzipped FileDescriptorProto
+       0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x84, 0x90, 0xb1, 0x4b, 0xc4, 0x30,
+       0x14, 0xc6, 0x89, 0xbd, 0x53, 0xfb, 0x4e, 0x41, 0x82, 0x43, 0x70, 0x90, 0x70, 0x53, 0xa7, 0x87,
+       0xe8, 0x7f, 0x70, 0xa2, 0xdc, 0xe2, 0x12, 0x70, 0x71, 0x4b, 0xd3, 0xd0, 0x84, 0xd6, 0xa4, 0x24,
+       0x19, 0xda, 0xff, 0x5e, 0x9a, 0x16, 0xe9, 0x76, 0xdb, 0xfb, 0x3e, 0xde, 0x0f, 0x7e, 0x7c, 0x70,
+       0x1f, 0x93, 0x0f, 0xb2, 0xd5, 0x38, 0x04, 0x9f, 0x3c, 0xe5, 0xca, 0x48, 0xeb, 0x50, 0xf9, 0xa0,
+       0x31, 0x8d, 0x4d, 0x8d, 0xd6, 0x25, 0x1d, 0x9c, 0xec, 0x71, 0xfd, 0x3b, 0x2a, 0x28, 0xbf, 0xd3,
+       0xe8, 0x3f, 0x5c, 0x0a, 0x13, 0x7d, 0x06, 0xb0, 0xf1, 0xdd, 0x5b, 0x77, 0x92, 0x51, 0x33, 0xc2,
+       0x49, 0x75, 0x2b, 0x36, 0x0d, 0xe5, 0x70, 0xa8, 0x7b, 0xaf, 0xba, 0xb3, 0xb6, 0xad, 0x49, 0xec,
+       0x8a, 0x93, 0x6a, 0x27, 0xb6, 0x15, 0x7d, 0x84, 0x7d, 0x1c, 0xb4, 0x6b, 0x58, 0x91, 0xe1, 0x25,
+       0x1c, 0x7f, 0xa1, 0xfc, 0x92, 0xd6, 0x65, 0x19, 0xfa, 0x09, 0x7b, 0x23, 0xa3, 0x89, 0x8c, 0xf0,
+       0xa2, 0x3a, 0xbc, 0xbe, 0xe0, 0x25, 0x47, 0xfc, 0x67, 0xf1, 0x2c, 0xa3, 0x11, 0x0b, 0xfe, 0xc4,
+       0x60, 0x37, 0x47, 0xfa, 0x00, 0x45, 0xa7, 0xa7, 0x6c, 0x7b, 0x27, 0xe6, 0xf3, 0x54, 0xfe, 0xdc,
+       0xac, 0x68, 0x7d, 0x9d, 0x77, 0x78, 0xfb, 0x0b, 0x00, 0x00, 0xff, 0xff, 0x6c, 0x3e, 0xf1, 0xc4,
+       0x18, 0x01, 0x00, 0x00,
+}
diff --git a/blockchain/txdb/storage/storage.proto b/blockchain/txdb/storage/storage.proto
new file mode 100644 (file)
index 0000000..9bff217
--- /dev/null
@@ -0,0 +1,19 @@
+syntax = "proto3";
+option go_package = "storage";
+package chain.core.txdb.internal.storage;
+
+message UtxoEntry {
+  bool   isCoinBase  = 1;
+  uint64 blockHeight = 2;
+  bool   spend       = 3;
+}
+
+// Mainchain represents a mainchain of the blockchain
+message Mainchain {
+
+  repeated Hash hashs = 1;
+
+  message Hash {
+    bytes key = 1;
+  }
+}
\ No newline at end of file
diff --git a/blockchain/txdb/storage/utxo_entry.go b/blockchain/txdb/storage/utxo_entry.go
new file mode 100644 (file)
index 0000000..af4d8d5
--- /dev/null
@@ -0,0 +1,20 @@
+package storage
+
+// NewUtxoEntry will creaye a new utxo entry
+func NewUtxoEntry(isCoinBase bool, blockHeight uint64, spend bool) *UtxoEntry {
+       return &UtxoEntry{
+               IsCoinBase:  isCoinBase,
+               BlockHeight: blockHeight,
+               Spend:       spend,
+       }
+}
+
+// SpendOutput marks the output at the provided index as spent
+func (entry *UtxoEntry) SpendOutput() {
+       entry.Spend = true
+}
+
+// UnspendOutput marks the output at the provided index as unspent
+func (entry *UtxoEntry) UnspendOutput() {
+       entry.Spend = false
+}
index 9398413..60618c6 100644 (file)
@@ -7,7 +7,7 @@ import (
        "github.com/tendermint/tmlibs/common"
        dbm "github.com/tendermint/tmlibs/db"
 
-       "github.com/bytom/errors"
+       "github.com/bytom/blockchain/txdb/storage"
        "github.com/bytom/protocol/bc"
        "github.com/bytom/protocol/bc/legacy"
        "github.com/bytom/protocol/state"
@@ -21,12 +21,12 @@ type BlockStoreStateJSON struct {
        Hash   *bc.Hash
 }
 
-func (bsj BlockStoreStateJSON) save(db dbm.DB) {
+func (bsj BlockStoreStateJSON) save(batch dbm.Batch) {
        bytes, err := json.Marshal(bsj)
        if err != nil {
                common.PanicSanity(common.Fmt("Could not marshal state bytes: %v", err))
        }
-       db.SetSync(blockStoreKey, bytes)
+       batch.Set(blockStoreKey, bytes)
 }
 
 func loadBlockStoreStateJSON(db dbm.DB) BlockStoreStateJSON {
@@ -37,8 +37,7 @@ func loadBlockStoreStateJSON(db dbm.DB) BlockStoreStateJSON {
                }
        }
        bsj := BlockStoreStateJSON{}
-       err := json.Unmarshal(bytes, &bsj)
-       if err != nil {
+       if err := json.Unmarshal(bytes, &bsj); err != nil {
                common.PanicCrisis(common.Fmt("Could not unmarshal bytes: %X", bytes))
        }
        return bsj
@@ -79,6 +78,11 @@ func NewStore(db dbm.DB) *Store {
        }
 }
 
+// GetUtxo will search the utxo in db
+func (s *Store) GetUtxo(hash *bc.Hash) (*storage.UtxoEntry, error) {
+       return getUtxo(s.db, hash)
+}
+
 // BlockExist check if the block is stored in disk
 func (s *Store) BlockExist(hash *bc.Hash) bool {
        block, err := s.cache.lookup(hash)
@@ -90,6 +94,11 @@ func (s *Store) GetBlock(hash *bc.Hash) (*legacy.Block, error) {
        return s.cache.lookup(hash)
 }
 
+// GetTransactionsUtxo will return all the utxo that related to the input txs
+func (s *Store) GetTransactionsUtxo(view *state.UtxoViewpoint, txs []*bc.Tx) error {
+       return getTransactionsUtxo(s.db, view, txs)
+}
+
 // GetStoreStatus return the BlockStoreStateJSON
 func (s *Store) GetStoreStatus() BlockStoreStateJSON {
        return loadBlockStoreStateJSON(s.db)
@@ -100,11 +109,6 @@ func (s *Store) GetMainchain(hash *bc.Hash) (map[uint64]*bc.Hash, error) {
        return getMainchain(s.db, hash)
 }
 
-// GetSnapshot read the snapshot tree from db
-func (s *Store) GetSnapshot(hash *bc.Hash) (*state.Snapshot, error) {
-       return getSnapshot(s.db, hash)
-}
-
 // SaveBlock persists a new block in the database.
 func (s *Store) SaveBlock(block *legacy.Block) error {
        binaryBlock, err := block.MarshalText()
@@ -118,21 +122,22 @@ func (s *Store) SaveBlock(block *legacy.Block) error {
        return nil
 }
 
-// SaveMainchain saves the mainchain map to the database.
-func (s *Store) SaveMainchain(mainchain map[uint64]*bc.Hash, hash *bc.Hash) error {
-       err := saveMainchain(s.db, mainchain, hash)
-       return errors.Wrap(err, "saving mainchain")
-}
+// SaveChainStatus save the core's newest status && delete old status
+func (s *Store) SaveChainStatus(block *legacy.Block, view *state.UtxoViewpoint, m map[uint64]*bc.Hash) error {
+       hash := block.Hash()
+       batch := s.db.NewBatch()
 
-// SaveSnapshot saves a state snapshot to the database.
-func (s *Store) SaveSnapshot(snapshot *state.Snapshot, hash *bc.Hash) error {
-       err := saveSnapshot(s.db, snapshot, hash)
-       return errors.Wrap(err, "saving state tree")
-}
+       if err := saveMainchain(batch, m, &hash); err != nil {
+               return err
+       }
 
-// SaveStoreStatus save the core's newest status && delete old status
-func (s *Store) SaveStoreStatus(height uint64, hash *bc.Hash) {
-       BlockStoreStateJSON{Height: height, Hash: hash}.save(s.db)
-       cleanMainchainDB(s.db, hash)
-       cleanSnapshotDB(s.db, hash)
+       if err := saveUtxoView(batch, view); err != nil {
+               return err
+       }
+
+       BlockStoreStateJSON{Height: block.Height, Hash: &hash}.save(batch)
+       batch.Write()
+
+       cleanMainchainDB(s.db, &hash)
+       return nil
 }
diff --git a/blockchain/txdb/utxo_view.go b/blockchain/txdb/utxo_view.go
new file mode 100644 (file)
index 0000000..5baa2db
--- /dev/null
@@ -0,0 +1,69 @@
+package txdb
+
+import (
+       dbm "github.com/tendermint/tmlibs/db"
+
+       "github.com/bytom/blockchain/txdb/storage"
+       "github.com/bytom/errors"
+       "github.com/bytom/protocol/bc"
+       "github.com/bytom/protocol/state"
+       "github.com/golang/protobuf/proto"
+)
+
+const utxoPreFix = "UT:"
+
+func calcUtxoKey(hash *bc.Hash) []byte {
+       return []byte(utxoPreFix + hash.String())
+}
+
+func getTransactionsUtxo(db dbm.DB, view *state.UtxoViewpoint, txs []*bc.Tx) error {
+       for _, tx := range txs {
+               for _, prevout := range tx.SpentOutputIDs {
+                       if view.HasUtxo(&prevout) {
+                               continue
+                       }
+
+                       data := db.Get(calcUtxoKey(&prevout))
+                       if data == nil {
+                               return errors.New("can't find utxo in db")
+                       }
+
+                       var utxo storage.UtxoEntry
+                       if err := proto.Unmarshal(data, &utxo); err != nil {
+                               return errors.Wrap(err, "unmarshaling utxo entry")
+                       }
+
+                       view.Entries[prevout] = &utxo
+               }
+       }
+
+       return nil
+}
+
+func getUtxo(db dbm.DB, hash *bc.Hash) (*storage.UtxoEntry, error) {
+       var utxo storage.UtxoEntry
+       data := db.Get(calcUtxoKey(hash))
+       if data == nil {
+               return nil, errors.New("can't find utxo in db")
+       }
+       if err := proto.Unmarshal(data, &utxo); err != nil {
+               return nil, errors.Wrap(err, "unmarshaling utxo entry")
+       }
+       return &utxo, nil
+}
+
+func saveUtxoView(batch dbm.Batch, view *state.UtxoViewpoint) error {
+       for key, entry := range view.Entries {
+               if entry.Spend && !entry.IsCoinBase {
+                       batch.Delete(calcUtxoKey(&key))
+                       continue
+               }
+
+               b, err := proto.Marshal(entry)
+               if err != nil {
+                       return errors.Wrap(err, "marshaling utxo entry")
+               }
+               batch.Set(calcUtxoKey(&key), b)
+       }
+       return nil
+}
diff --git a/blockchain/txdb/utxo_view_test.go b/blockchain/txdb/utxo_view_test.go
new file mode 100644 (file)
index 0000000..bb1531c
--- /dev/null
@@ -0,0 +1,184 @@
+package txdb
+
+import (
+       "blockchain/testutil"
+       "os"
+       "testing"
+
+       dbm "github.com/tendermint/tmlibs/db"
+
+       "github.com/bytom/blockchain/txdb/storage"
+       "github.com/bytom/protocol/bc"
+       "github.com/bytom/protocol/state"
+)
+
+func TestSaveUtxoView(t *testing.T) {
+       testDB := dbm.NewDB("testdb", "leveldb", "temp")
+       batch := testDB.NewBatch()
+       defer os.RemoveAll("temp")
+
+       cases := []struct {
+               hash      bc.Hash
+               utxoEntry *storage.UtxoEntry
+               exist     bool
+       }{
+               {
+                       hash:      bc.Hash{V0: 0},
+                       utxoEntry: storage.NewUtxoEntry(true, 0, true),
+                       exist:     true,
+               },
+               {
+                       hash:      bc.Hash{V0: 1},
+                       utxoEntry: storage.NewUtxoEntry(true, 0, false),
+                       exist:     true,
+               },
+               {
+                       hash:      bc.Hash{V0: 2},
+                       utxoEntry: storage.NewUtxoEntry(false, 0, false),
+                       exist:     true,
+               },
+               {
+                       hash:      bc.Hash{V0: 3},
+                       utxoEntry: storage.NewUtxoEntry(false, 0, true),
+                       exist:     false,
+               },
+       }
+
+       view := state.NewUtxoViewpoint()
+       for _, c := range cases {
+               view.Entries[c.hash] = c.utxoEntry
+       }
+
+       saveUtxoView(batch, view)
+       batch.Write()
+
+       for _, c := range cases {
+               entry, err := getUtxo(testDB, &c.hash)
+
+               if !c.exist {
+                       if err == nil {
+                               t.Errorf("%v should be unexisted, but it's in the db", c)
+                       }
+                       continue
+               }
+
+               if !testutil.DeepEqual(entry, c.utxoEntry) {
+                       t.Errorf("%v utxo in the db isn't match", c)
+               }
+       }
+}
+
+func TestGetTransactionsUtxo(t *testing.T) {
+       testDB := dbm.NewDB("testdb", "leveldb", "temp")
+       defer os.RemoveAll("temp")
+
+       batch := testDB.NewBatch()
+       inputView := state.NewUtxoViewpoint()
+       for i := 0; i <= 2; i++ {
+               inputView.Entries[bc.Hash{V0: uint64(i)}] = storage.NewUtxoEntry(false, uint64(i), false)
+       }
+       saveUtxoView(batch, inputView)
+       batch.Write()
+
+       cases := []struct {
+               txs       []*bc.Tx
+               inputView *state.UtxoViewpoint
+               fetchView *state.UtxoViewpoint
+               err       bool
+       }{
+
+               {
+                       txs: []*bc.Tx{
+                               &bc.Tx{
+                                       SpentOutputIDs: []bc.Hash{bc.Hash{V0: 10}},
+                               },
+                       },
+                       inputView: state.NewUtxoViewpoint(),
+                       fetchView: state.NewUtxoViewpoint(),
+                       err:       true,
+               },
+               {
+                       txs: []*bc.Tx{
+                               &bc.Tx{
+                                       SpentOutputIDs: []bc.Hash{bc.Hash{V0: 0}},
+                               },
+                       },
+                       inputView: state.NewUtxoViewpoint(),
+                       fetchView: &state.UtxoViewpoint{
+                               Entries: map[bc.Hash]*storage.UtxoEntry{
+                                       bc.Hash{V0: 0}: storage.NewUtxoEntry(false, 0, false),
+                               },
+                       },
+                       err: false,
+               },
+               {
+                       txs: []*bc.Tx{
+                               &bc.Tx{
+                                       SpentOutputIDs: []bc.Hash{
+                                               bc.Hash{V0: 0},
+                                               bc.Hash{V0: 1},
+                                       },
+                               },
+                       },
+                       inputView: state.NewUtxoViewpoint(),
+                       fetchView: &state.UtxoViewpoint{
+                               Entries: map[bc.Hash]*storage.UtxoEntry{
+                                       bc.Hash{V0: 0}: storage.NewUtxoEntry(false, 0, false),
+                                       bc.Hash{V0: 1}: storage.NewUtxoEntry(false, 1, false),
+                               },
+                       },
+                       err: false,
+               },
+               {
+                       txs: []*bc.Tx{
+                               &bc.Tx{
+                                       SpentOutputIDs: []bc.Hash{
+                                               bc.Hash{V0: 0},
+                                               bc.Hash{V0: 1},
+                                       },
+                               },
+                               &bc.Tx{
+                                       SpentOutputIDs: []bc.Hash{
+                                               bc.Hash{V0: 2},
+                                       },
+                               },
+                       },
+                       inputView: state.NewUtxoViewpoint(),
+                       fetchView: &state.UtxoViewpoint{
+                               Entries: map[bc.Hash]*storage.UtxoEntry{
+                                       bc.Hash{V0: 0}: storage.NewUtxoEntry(false, 0, false),
+                                       bc.Hash{V0: 1}: storage.NewUtxoEntry(false, 1, false),
+                                       bc.Hash{V0: 2}: storage.NewUtxoEntry(false, 2, false),
+                               },
+                       },
+                       err: false,
+               },
+               {
+                       txs: []*bc.Tx{
+                               &bc.Tx{
+                                       SpentOutputIDs: []bc.Hash{bc.Hash{V0: 0}},
+                               },
+                       },
+                       inputView: &state.UtxoViewpoint{
+                               Entries: map[bc.Hash]*storage.UtxoEntry{
+                                       bc.Hash{V0: 0}: storage.NewUtxoEntry(false, 1, false),
+                               },
+                       },
+                       fetchView: &state.UtxoViewpoint{
+                               Entries: map[bc.Hash]*storage.UtxoEntry{
+                                       bc.Hash{V0: 0}: storage.NewUtxoEntry(false, 1, false),
+                               },
+                       },
+                       err: false,
+               },
+       }
+
+       for i, c := range cases {
+               if err := getTransactionsUtxo(testDB, c.inputView, c.txs); c.err != (err != nil) {
+                       t.Errorf("want err = %v, get err = %v", c.err, err)
+               }
+               if !testutil.DeepEqual(c.inputView, c.fetchView) {
+                       t.Errorf("test case %d, want %v, get %v", i, c.fetchView, c.inputView)
+               }
+       }
+}
index 1fbe28c..cce2848 100644 (file)
@@ -7,7 +7,6 @@ import (
        "github.com/bytom/crypto/sha3pool"
        "github.com/bytom/protocol/bc"
        "github.com/bytom/protocol/bc/legacy"
-       "github.com/bytom/protocol/state"
 )
 
 // Generate genesis transaction
@@ -42,11 +41,6 @@ func GenerateGenesisBlock() *legacy.Block {
                log.Panicf("Fatal create merkelRoot")
        }
 
-       snap := state.Empty()
-       if err := snap.ApplyTx(genesisCoinbaseTx.Tx); err != nil {
-               log.Panicf("Fatal ApplyTx")
-       }
-
        var seed [32]byte
        sha3pool.Sum256(seed[:], make([]byte, 32))
 
@@ -58,7 +52,6 @@ func GenerateGenesisBlock() *legacy.Block {
                        TimestampMS: 1511318565142,
                        BlockCommitment: legacy.BlockCommitment{
                                TransactionsMerkleRoot: merkleRoot,
-                               AssetsMerkleRoot:       snap.Tree.RootHash(),
                        },
                        Bits: 2161727821138738707,
                },
index 2ffa91f..5d73071 100644 (file)
@@ -9,8 +9,4 @@ func TestGenesis(t *testing.T) {
        if tx := GenerateGenesisTx(); tx == nil {
                t.Errorf("Generate genesis tx failed")
        }
-
-       if block := GenerateGenesisBlock(); block == nil {
-               t.Errorf("Generate genesis block failed")
-       }
 }
index 6b6d2fe..256d2ab 100644 (file)
@@ -57,8 +57,8 @@ func createCoinbaseTx(accountManager *account.Manager, amount uint64, blockHeigh
 func NewBlockTemplate(c *protocol.Chain, txPool *protocol.TxPool, accountManager *account.Manager) (*legacy.Block, error) {
        // Extend the most recently known best block.
        var err error
-       preBlock, snap := c.State()
-       newSnap := state.Copy(snap)
+       preBlock := c.BestBlock()
+       view := state.NewUtxoViewpoint()
 
        preBcBlock := legacy.MapBlock(preBlock)
        nextBlockHeight := preBlock.BlockHeader.Height + 1
@@ -85,7 +85,6 @@ func NewBlockTemplate(c *protocol.Chain, txPool *protocol.TxPool, accountManager
                },
                Transactions: make([]*legacy.Tx, 0, len(txDescs)),
        }
-       newSnap.PruneNonces(b.BlockHeader.TimestampMS)
 
        appendTx := func(tx *legacy.Tx, weight, fee uint64) {
                b.Transactions = append([]*legacy.Tx{tx}, b.Transactions...)
@@ -94,12 +93,18 @@ func NewBlockTemplate(c *protocol.Chain, txPool *protocol.TxPool, accountManager
                txFee += fee
        }
 
+       bcBlock := legacy.MapBlock(b)
        for _, txDesc := range txDescs {
                tx := txDesc.Tx.Tx
                if blockWeight+txDesc.Weight > consensus.MaxBlockSzie-consensus.MaxTxSize {
                        break
                }
-               if err := newSnap.ApplyTx(tx); err != nil {
+               if err := c.GetTransactionsUtxo(view, []*bc.Tx{tx}); err != nil {
+                       log.WithField("error", err).Error("mining block generate skip tx due to")
+                       txPool.RemoveTransaction(&tx.ID)
+                       continue
+               }
+               if err := view.ApplyTransaction(bcBlock, tx); err != nil {
                        log.WithField("error", err).Error("mining block generate skip tx due to")
                        txPool.RemoveTransaction(&tx.ID)
                        continue
@@ -117,12 +122,8 @@ func NewBlockTemplate(c *protocol.Chain, txPool *protocol.TxPool, accountManager
        if err != nil {
                return nil, errors.Wrap(err, "fail on createCoinbaseTx")
        }
-       if err := newSnap.ApplyTx(cbTx.Tx); err != nil {
-               return nil, errors.Wrap(err, "fail on append coinbase transaction to snap")
-       }
        appendTx(cbTx, 0, 0)
 
-       b.BlockHeader.BlockCommitment.AssetsMerkleRoot = newSnap.Tree.RootHash()
        b.BlockHeader.BlockCommitment.TransactionsMerkleRoot, err = bc.MerkleRoot(txEntries)
        if err != nil {
                return nil, errors.Wrap(err, "calculating tx merkle root")
diff --git a/mining/mining_test.go b/mining/mining_test.go
deleted file mode 100644 (file)
index 5c5fdc2..0000000
+++ /dev/null
@@ -1,66 +0,0 @@
-// Copyright (c) 2014-2016 The btcsuite developers
-// Use of this source code is governed by an ISC
-// license that can be found in the LICENSE file.
-
-package mining
-
-import (
-       "fmt"
-       "testing"
-       "time"
-
-       "github.com/bytom/consensus"
-       "github.com/bytom/crypto/sha3pool"
-       "github.com/bytom/protocol/bc"
-       "github.com/bytom/protocol/bc/legacy"
-       "github.com/bytom/protocol/state"
-)
-
-func TestNewInitBlock(t *testing.T) {
-       coinbaseTx, err := createCoinbaseTx(nil, 0, 1)
-       if err != nil {
-               t.Error(err)
-       }
-       merkleRoot, err := bc.MerkleRoot([]*bc.Tx{coinbaseTx.Tx})
-       if err != nil {
-               t.Error(err)
-       }
-       snap := state.Empty()
-       if err := snap.ApplyTx(coinbaseTx.Tx); err != nil {
-               t.Error(err)
-       }
-
-       var seed [32]byte
-       sha3pool.Sum256(seed[:], make([]byte, 32))
-
-       b := &legacy.Block{
-               BlockHeader: legacy.BlockHeader{
-                       Version:           1,
-                       Height:            1,
-                       PreviousBlockHash: bc.Hash{},
-                       TimestampMS:       bc.Millis(time.Now()),
-                       BlockCommitment: legacy.BlockCommitment{
-                               TransactionsMerkleRoot: merkleRoot,
-                               AssetsMerkleRoot:       snap.Tree.RootHash(),
-                       },
-                       Bits: uint64(2161727821138738707),
-                       Seed: bc.NewHash(seed),
-               },
-               Transactions: []*legacy.Tx{coinbaseTx},
-       }
-
-       for i := uint64(0); i <= 10000000000000; i++ {
-               b.Nonce = i
-               hash := b.Hash()
-
-               if consensus.CheckProofOfWork(&hash, b.Bits) {
-                       break
-               }
-       }
-
-       rawBlock, err := b.MarshalText()
-       if err != nil {
-               t.Error(err)
-       }
-       fmt.Println(string(rawBlock))
-}
index 9c648ab..3e177e9 100755 (executable)
@@ -60,13 +60,18 @@ func (c *Chain) ConnectBlock(block *legacy.Block) error {
 }
 
 func (c *Chain) connectBlock(block *legacy.Block) error {
-       newSnapshot := state.Copy(c.state.snapshot)
-       if err := newSnapshot.ApplyBlock(legacy.MapBlock(block)); err != nil {
+       bcBlock := legacy.MapBlock(block)
+       utxoView := state.NewUtxoViewpoint()
+
+       if err := c.store.GetTransactionsUtxo(utxoView, bcBlock.Transactions); err != nil {
+               return err
+       }
+       if err := utxoView.ApplyBlock(bcBlock); err != nil {
                return err
        }
 
        blockHash := block.Hash()
-       if err := c.setState(block, newSnapshot, map[uint64]*bc.Hash{block.Height: &blockHash}); err != nil {
+       if err := c.setState(block, utxoView, map[uint64]*bc.Hash{block.Height: &blockHash}); err != nil {
                return err
        }
 
@@ -95,24 +100,32 @@ func (c *Chain) getReorganizeBlocks(block *legacy.Block) ([]*legacy.Block, []*le
 
 func (c *Chain) reorganizeChain(block *legacy.Block) error {
        attachBlocks, detachBlocks := c.getReorganizeBlocks(block)
-       newSnapshot := state.Copy(c.state.snapshot)
+       utxoView := state.NewUtxoViewpoint()
        chainChanges := map[uint64]*bc.Hash{}
 
        for _, d := range detachBlocks {
-               if err := newSnapshot.DetachBlock(legacy.MapBlock(d)); err != nil {
+               detachBlock := legacy.MapBlock(d)
+               if err := c.store.GetTransactionsUtxo(utxoView, detachBlock.Transactions); err != nil {
+                       return err
+               }
+               if err := utxoView.DetachBlock(detachBlock); err != nil {
                        return err
                }
        }
 
        for _, a := range attachBlocks {
-               if err := newSnapshot.ApplyBlock(legacy.MapBlock(a)); err != nil {
+               attachBlock := legacy.MapBlock(a)
+               if err := c.store.GetTransactionsUtxo(utxoView, attachBlock.Transactions); err != nil {
+                       return err
+               }
+               if err := utxoView.ApplyBlock(attachBlock); err != nil {
                        return err
                }
                aHash := a.Hash()
                chainChanges[a.Height] = &aHash
        }
 
-       return c.setState(block, newSnapshot, chainChanges)
+       return c.setState(block, utxoView, chainChanges)
 }
 
 // SaveBlock will validate and save block into storage
index abcd42a..6962a4c 100755 (executable)
@@ -6,6 +6,7 @@ import (
        "time"
 
        "github.com/bytom/blockchain/txdb"
+       "github.com/bytom/blockchain/txdb/storage"
        "github.com/bytom/errors"
        "github.com/bytom/protocol/bc"
        "github.com/bytom/protocol/bc/legacy"
@@ -34,13 +35,12 @@ type Store interface {
 
        GetBlock(*bc.Hash) (*legacy.Block, error)
        GetMainchain(*bc.Hash) (map[uint64]*bc.Hash, error)
-       GetSnapshot(*bc.Hash) (*state.Snapshot, error)
        GetStoreStatus() txdb.BlockStoreStateJSON
+       GetTransactionsUtxo(*state.UtxoViewpoint, []*bc.Tx) error
+       GetUtxo(*bc.Hash) (*storage.UtxoEntry, error)
 
        SaveBlock(*legacy.Block) error
-       SaveMainchain(map[uint64]*bc.Hash, *bc.Hash) error
-       SaveSnapshot(*state.Snapshot, *bc.Hash) error
-       SaveStoreStatus(uint64, *bc.Hash)
+       SaveChainStatus(*legacy.Block, *state.UtxoViewpoint, map[uint64]*bc.Hash) error
 }
 
 // OrphanManage is use to handle all the orphan block
@@ -129,7 +129,6 @@ type Chain struct {
                block     *legacy.Block
                hash      *bc.Hash
                mainChain map[uint64]*bc.Hash
-               snapshot  *state.Snapshot
        }
        store      Store
        seedCaches *seed.SeedCaches
@@ -148,7 +147,6 @@ func NewChain(initialBlockHash bc.Hash, store Store, txPool *TxPool) (*Chain, er
        storeStatus := store.GetStoreStatus()
 
        if storeStatus.Height == 0 {
-               c.state.snapshot = state.Empty()
                c.state.mainChain = make(map[uint64]*bc.Hash)
                return c, nil
        }
@@ -158,9 +156,6 @@ func NewChain(initialBlockHash bc.Hash, store Store, txPool *TxPool) (*Chain, er
        if c.state.block, err = store.GetBlock(storeStatus.Hash); err != nil {
                return nil, err
        }
-       if c.state.snapshot, err = store.GetSnapshot(storeStatus.Hash); err != nil {
-               return nil, err
-       }
        if c.state.mainChain, err = store.GetMainchain(storeStatus.Hash); err != nil {
                return nil, err
        }
@@ -177,6 +172,7 @@ func (c *Chain) Height() uint64 {
        return c.state.block.Height
 }
 
+// BestBlockHash return the hash of the chain tail block
 func (c *Chain) BestBlockHash() *bc.Hash {
        c.state.cond.L.Lock()
        defer c.state.cond.L.Unlock()
@@ -191,6 +187,7 @@ func (c *Chain) inMainchain(block *legacy.Block) bool {
        return *hash == block.Hash()
 }
 
+// InMainChain checks wheather a block is in the main chain
 func (c *Chain) InMainChain(height uint64, hash bc.Hash) bool {
        if height == 0 {
                return true
@@ -216,40 +213,40 @@ func (c *Chain) TimestampMS() uint64 {
        return c.state.block.TimestampMS
 }
 
-// State returns the most recent state available. It will not be current
-// unless the current process is the leader. Callers should examine the
-// returned block header's height if they need to verify the current state.
-func (c *Chain) State() (*legacy.Block, *state.Snapshot) {
+// BestBlock returns the chain tail block
+func (c *Chain) BestBlock() *legacy.Block {
        c.state.cond.L.Lock()
        defer c.state.cond.L.Unlock()
-       return c.state.block, c.state.snapshot
+       return c.state.block
 }
 
+// SeedCaches return the seedCached manager
 func (c *Chain) SeedCaches() *seed.SeedCaches {
        return c.seedCaches
 }
 
-// This function must be called with mu lock in above level
-func (c *Chain) setState(block *legacy.Block, s *state.Snapshot, m map[uint64]*bc.Hash) error {
-       if block.AssetsMerkleRoot != s.Tree.RootHash() {
-               return ErrBadStateRoot
-       }
+// GetUtxo try to find the utxo status in db
+func (c *Chain) GetUtxo(hash *bc.Hash) (*storage.UtxoEntry, error) {
+       return c.store.GetUtxo(hash)
+}
 
+// GetTransactionsUtxo return all the utxos that related to the txs' inputs
+func (c *Chain) GetTransactionsUtxo(view *state.UtxoViewpoint, txs []*bc.Tx) error {
+       return c.store.GetTransactionsUtxo(view, txs)
+}
+
+// This function must be called with mu lock in above level
+func (c *Chain) setState(block *legacy.Block, view *state.UtxoViewpoint, m map[uint64]*bc.Hash) error {
        blockHash := block.Hash()
        c.state.block = block
        c.state.hash = &blockHash
-       c.state.snapshot = s
        for k, v := range m {
                c.state.mainChain[k] = v
        }
 
-       if err := c.store.SaveSnapshot(c.state.snapshot, &blockHash); err != nil {
-               return err
-       }
-       if err := c.store.SaveMainchain(c.state.mainChain, &blockHash); err != nil {
+       if err := c.store.SaveChainStatus(block, view, c.state.mainChain); err != nil {
                return err
        }
-       c.store.SaveStoreStatus(block.Height, &blockHash)
 
        c.state.cond.Broadcast()
        return nil
diff --git a/protocol/recover.go b/protocol/recover.go
deleted file mode 100644 (file)
index f77d97d..0000000
+++ /dev/null
@@ -1,72 +0,0 @@
-package protocol
-
-import (
-       "context"
-       //      "fmt"
-
-       //      "github.com/blockchain/errors"
-       "github.com/bytom/protocol/bc/legacy"
-       "github.com/bytom/protocol/state"
-)
-
-// Recover performs crash recovery, restoring the blockchain
-// to a complete state. It returns the latest confirmed block
-// and the corresponding state snapshot.
-//
-// If the blockchain is empty (missing initial block), this function
-// returns a nil block and an empty snapshot.
-func (c *Chain) Recover(ctx context.Context) (*legacy.Block, *state.Snapshot, error) {
-       //snapshot, snapshotHeight, err := c.store.LatestSnapshot(ctx)
-       /*if err != nil {
-               return nil, nil, errors.Wrap(err, "getting latest snapshot")
-       }
-       var b *legacy.Block
-       if snapshotHeight > 0 {
-               b, err = c.store.GetBlock(ctx, snapshotHeight)
-               if err != nil {
-                       return nil, nil, errors.Wrap(err, "getting snapshot block")
-               }
-               c.lastQueuedSnapshot = b.Time()
-       }
-       if snapshot == nil {
-               snapshot = state.Empty()
-       }
-
-       // The true height of the blockchain might be higher than the
-       // height at which the state snapshot was taken. Replay all
-       // existing blocks higher than the snapshot height.
-       height, err := c.store.Height(ctx)
-       if err != nil {
-               return nil, nil, errors.Wrap(err, "getting blockchain height")
-       }
-
-       // Bring the snapshot up to date with the latest block
-       for h := snapshotHeight + 1; h <= height; h++ {
-               b, err = c.store.GetBlock(ctx, h)
-               if err != nil {
-                       return nil, nil, errors.Wrap(err, "getting block")
-               }
-               err = snapshot.ApplyBlock(legacy.MapBlock(b))
-               if err != nil {
-                       return nil, nil, errors.Wrap(err, "applying block")
-               }
-               if b.AssetsMerkleRoot != snapshot.Tree.RootHash() {
-                       return nil, nil, fmt.Errorf("block %d has state root %x; snapshot has root %x",
-                               b.Height, b.AssetsMerkleRoot.Bytes(), snapshot.Tree.RootHash().Bytes())
-               }
-       }
-       if b != nil {
-               // All blocks before the latest one have been fully processed
-               // (saved in the db, callbacks invoked). The last one may have
-               // been too, but make sure just in case. Also "finalize" the last
-               // block (notifying other processes of the latest block height)
-               // and maybe persist the snapshot.
-               err = c.CommitAppliedBlock(ctx, b, snapshot)
-               if err != nil {
-                       return nil, nil, errors.Wrap(err, "committing block")
-               }
-       }
-       return b, snapshot, nil
-       */
-       return nil, nil, nil
-}
diff --git a/protocol/state/snapshot.go b/protocol/state/snapshot.go
deleted file mode 100644 (file)
index 53caedc..0000000
+++ /dev/null
@@ -1,140 +0,0 @@
-package state
-
-import (
-       "fmt"
-
-       "github.com/bytom/errors"
-       "github.com/bytom/protocol/bc"
-       "github.com/bytom/protocol/patricia"
-)
-
-// Snapshot encompasses a snapshot of entire blockchain state. It
-// consists of a patricia state tree and the nonce set.
-//
-// Nonces maps a nonce entry's ID to the time (in Unix millis) at
-// which it should expire from the nonce set.
-//
-// TODO: consider making type Snapshot truly immutable.  We already
-// handle it that way in many places (with explicit calls to Copy to
-// get the right behavior).  PruneNonces and the Apply functions would
-// have to produce new Snapshots rather than updating Snapshots in
-// place.
-type Snapshot struct {
-       Tree   *patricia.Tree
-       Nonces map[bc.Hash]uint64
-}
-
-// PruneNonces modifies a Snapshot, removing all nonce IDs with
-// expiration times earlier than the provided timestamp.
-func (s *Snapshot) PruneNonces(timestampMS uint64) {
-       for hash, expiryMS := range s.Nonces {
-               if timestampMS > expiryMS {
-                       delete(s.Nonces, hash)
-               }
-       }
-}
-
-// Copy makes a copy of provided snapshot. Copying a snapshot is an
-// O(n) operation where n is the number of nonces in the snapshot's
-// nonce set.
-func Copy(original *Snapshot) *Snapshot {
-       c := &Snapshot{
-               Tree:   new(patricia.Tree),
-               Nonces: make(map[bc.Hash]uint64, len(original.Nonces)),
-       }
-       *c.Tree = *original.Tree
-       for k, v := range original.Nonces {
-               c.Nonces[k] = v
-       }
-       return c
-}
-
-// Empty returns an empty state snapshot.
-func Empty() *Snapshot {
-       return &Snapshot{
-               Tree:   new(patricia.Tree),
-               Nonces: make(map[bc.Hash]uint64),
-       }
-}
-
-// ApplyBlock updates s in place.
-func (s *Snapshot) ApplyBlock(block *bc.Block) error {
-       for i, tx := range block.Transactions {
-               if err := s.ApplyTx(tx); err != nil {
-                       return errors.Wrapf(err, "applying block transaction %d", i)
-               }
-       }
-       return nil
-}
-
-// ApplyTx updates s in place.
-func (s *Snapshot) ApplyTx(tx *bc.Tx) error {
-       for _, n := range tx.NonceIDs {
-               // Add new nonces. They must not conflict with nonces already
-               // present.
-               if _, ok := s.Nonces[n]; ok {
-                       return fmt.Errorf("conflicting nonce %x", n.Bytes())
-               }
-
-               s.Nonces[n] = 0
-       }
-
-       // Remove spent outputs. Each output must be present.
-       for _, prevout := range tx.SpentOutputIDs {
-               if !s.Tree.Contains(prevout.Bytes()) {
-                       return fmt.Errorf("invalid prevout %x", prevout.Bytes())
-               }
-               s.Tree.Delete(prevout.Bytes())
-       }
-
-       // Add new outputs. They must not yet be present.
-       for _, id := range tx.TxHeader.ResultIds {
-               // Ensure that this result is an output. It could be a retirement
-               // which should not be inserted into the state tree.
-               e := tx.Entries[*id]
-               if _, ok := e.(*bc.Output); !ok {
-                       continue
-               }
-
-               err := s.Tree.Insert(id.Bytes())
-               if err != nil {
-                       return err
-               }
-       }
-       return nil
-}
-
-func (s *Snapshot) DetachBlock(block *bc.Block) error {
-       for i, tx := range block.Transactions {
-               err := s.DetachTx(tx)
-               if err != nil {
-                       return errors.Wrapf(err, "detachTx block transaction %d", i)
-               }
-       }
-       return nil
-}
-
-func (s *Snapshot) DetachTx(tx *bc.Tx) error {
-       for _, n := range tx.NonceIDs {
-               delete(s.Nonces, n)
-       }
-
-       for _, prevout := range tx.SpentOutputIDs {
-               if s.Tree.Contains(prevout.Bytes()) {
-                       return fmt.Errorf("invalid prevout %x", prevout.Bytes())
-               }
-               if err := s.Tree.Insert(prevout.Bytes()); err != nil {
-                       return err
-               }
-       }
-
-       for _, id := range tx.TxHeader.ResultIds {
-               e := tx.Entries[*id]
-               if _, ok := e.(*bc.Output); !ok {
-                       continue
-               }
-
-               s.Tree.Delete(id.Bytes())
-       }
-       return nil
-}
diff --git a/protocol/state/snapshot_test.go b/protocol/state/snapshot_test.go
deleted file mode 100644 (file)
index e4b5f0e..0000000
+++ /dev/null
@@ -1,105 +0,0 @@
-package state
-
-import (
-       "reflect"
-       "testing"
-       "time"
-
-       "github.com/bytom/protocol/bc"
-       "github.com/bytom/protocol/bc/bctest"
-       "github.com/bytom/protocol/bc/legacy"
-)
-
-func TestApplyTxSpend(t *testing.T) {
-       assetID := bc.AssetID{}
-       sourceID := bc.NewHash([32]byte{0x01, 0x02, 0x03})
-       sc := legacy.SpendCommitment{
-               AssetAmount:    bc.AssetAmount{AssetId: &assetID, Amount: 100},
-               SourceID:       sourceID,
-               SourcePosition: 0,
-               VMVersion:      1,
-               ControlProgram: nil,
-               RefDataHash:    bc.Hash{},
-       }
-       spentOutputID, err := legacy.ComputeOutputID(&sc)
-       if err != nil {
-               t.Fatal(err)
-       }
-
-       snap := Empty()
-       snap.Tree.Insert(spentOutputID.Bytes())
-
-       tx := legacy.MapTx(&legacy.TxData{
-               Version: 1,
-               Inputs: []*legacy.TxInput{
-                       legacy.NewSpendInput(nil, sourceID, assetID, 100, 0, nil, bc.Hash{}, nil),
-               },
-               Outputs: []*legacy.TxOutput{},
-       })
-
-       // Apply the spend transaction.
-       err = snap.ApplyTx(tx)
-       if err != nil {
-               t.Fatal(err)
-       }
-       if snap.Tree.Contains(spentOutputID.Bytes()) {
-               t.Error("snapshot contains spent prevout")
-       }
-       err = snap.ApplyTx(tx)
-       if err == nil {
-               t.Error("expected error applying spend twice, got nil")
-       }
-}
-
-func TestApplyIssuanceTwice(t *testing.T) {
-       snap := Empty()
-       issuance := legacy.MapTx(&bctest.NewIssuanceTx(t, bc.EmptyStringHash).TxData)
-       err := snap.ApplyTx(issuance)
-       if err != nil {
-               t.Fatal(err)
-       }
-       err = snap.ApplyTx(issuance)
-       if err == nil {
-               t.Errorf("expected error for duplicate nonce, got %s", err)
-       }
-}
-
-func TestCopySnapshot(t *testing.T) {
-       snap := Empty()
-       err := snap.ApplyTx(legacy.MapTx(&bctest.NewIssuanceTx(t, bc.EmptyStringHash).TxData))
-       if err != nil {
-               t.Fatal(err)
-       }
-       dupe := Copy(snap)
-       if !reflect.DeepEqual(dupe, snap) {
-               t.Errorf("got %#v, want %#v", dupe, snap)
-       }
-}
-
-func TestApplyBlock(t *testing.T) {
-       // Setup a snapshot with a nonce with a known expiry.
-       maxTime := bc.Millis(time.Now().Add(5 * time.Minute))
-       issuance := bctest.NewIssuanceTx(t, bc.EmptyStringHash, func(tx *legacy.Tx) {})
-       snap := Empty()
-       err := snap.ApplyTx(legacy.MapTx(&issuance.TxData))
-       if err != nil {
-               t.Fatal(err)
-       }
-       if n := len(snap.Nonces); n != 1 {
-               t.Errorf("got %d nonces, want 1", n)
-       }
-
-       // Land a block later than the issuance's max time.
-       block := &legacy.Block{
-               BlockHeader: legacy.BlockHeader{
-                       TimestampMS: maxTime + 1,
-               },
-       }
-       err = snap.ApplyBlock(legacy.MapBlock(block))
-       if err != nil {
-               t.Fatal(err)
-       }
-       if n := len(snap.Nonces); n != 1 {
-               t.Errorf("got %d nonces, want 1", n)
-       }
-}
diff --git a/protocol/state/utxo_view.go b/protocol/state/utxo_view.go
new file mode 100644 (file)
index 0000000..82bd9d9
--- /dev/null
@@ -0,0 +1,96 @@
+package state
+
+import (
+       "errors"
+
+       "github.com/bytom/blockchain/txdb/storage"
+       "github.com/bytom/protocol/bc"
+)
+
+// UtxoViewpoint represents a view into the set of unspent transaction outputs
+type UtxoViewpoint struct {
+       Entries map[bc.Hash]*storage.UtxoEntry
+}
+
+// NewUtxoViewpoint returns a new empty unspent transaction output view.
+func NewUtxoViewpoint() *UtxoViewpoint {
+       return &UtxoViewpoint{
+               Entries: make(map[bc.Hash]*storage.UtxoEntry),
+       }
+}
+
+func (view *UtxoViewpoint) HasUtxo(hash *bc.Hash) bool {
+       _, ok := view.Entries[*hash]
+       return ok
+}
+
+func (view *UtxoViewpoint) ApplyTransaction(block *bc.Block, tx *bc.Tx) error {
+       for _, prevout := range tx.SpentOutputIDs {
+               entry, ok := view.Entries[prevout]
+               if !ok {
+                       return errors.New("fail to find utxo entry")
+               }
+               if entry.Spend {
+                       return errors.New("utxo has been spend")
+               }
+               entry.SpendOutput()
+       }
+
+       for _, id := range tx.TxHeader.ResultIds {
+               e := tx.Entries[*id]
+               if _, ok := e.(*bc.Output); !ok {
+                       continue
+               }
+
+               isCoinbase := false
+               if block != nil && len(block.Transactions) > 0 && block.Transactions[0].ID == tx.ID {
+                       isCoinbase = true
+               }
+               view.Entries[*id] = storage.NewUtxoEntry(isCoinbase, block.Height, false)
+       }
+       return nil
+}
+
+func (view *UtxoViewpoint) ApplyBlock(block *bc.Block) error {
+       for _, tx := range block.Transactions {
+               if err := view.ApplyTransaction(block, tx); err != nil {
+                       return err
+               }
+       }
+       return nil
+}
+
+func (view *UtxoViewpoint) DetachTransaction(tx *bc.Tx) error {
+       for _, prevout := range tx.SpentOutputIDs {
+               entry, ok := view.Entries[prevout]
+               if !ok || !entry.Spend {
+                       return errors.New("try to revert a unspend utxo")
+               }
+
+               if !ok {
+                       view.Entries[prevout] = storage.NewUtxoEntry(false, 0, false)
+                       continue
+               }
+
+               entry.UnspendOutput()
+       }
+
+       for _, id := range tx.TxHeader.ResultIds {
+               e := tx.Entries[*id]
+               if _, ok := e.(*bc.Output); !ok {
+                       continue
+               }
+
+               view.Entries[*id] = storage.NewUtxoEntry(false, 0, true)
+       }
+       return nil
+}
+
+func (view *UtxoViewpoint) DetachBlock(block *bc.Block) error {
+       for _, tx := range block.Transactions {
+               if err := view.DetachTransaction(tx); err != nil {
+                       return err
+               }
+       }
+       return nil
+}
diff --git a/protocol/state/utxo_view_test.go b/protocol/state/utxo_view_test.go
new file mode 100644 (file)
index 0000000..296cbbb
--- /dev/null
@@ -0,0 +1,170 @@
+package state
+
+import (
+       "testing"
+
+       "github.com/bytom/blockchain/txdb/storage"
+       "github.com/bytom/protocol/bc"
+       "github.com/bytom/testutil"
+)
+
+func TestApplyBlock(t *testing.T) {
+       cases := []struct {
+               block     *bc.Block
+               inputView *UtxoViewpoint
+               fetchView *UtxoViewpoint
+               err       bool
+       }{
+               {
+                       block: &bc.Block{
+                               Transactions: []*bc.Tx{
+                                       &bc.Tx{
+                                               SpentOutputIDs: []bc.Hash{
+                                                       bc.Hash{V0: 0},
+                                               },
+                                       },
+                               },
+                       },
+                       inputView: NewUtxoViewpoint(),
+                       fetchView: NewUtxoViewpoint(),
+                       err:       true,
+               },
+               {
+                       block: &bc.Block{
+                               Transactions: []*bc.Tx{
+                                       &bc.Tx{
+                                               SpentOutputIDs: []bc.Hash{
+                                                       bc.Hash{V0: 0},
+                                               },
+                                       },
+                               },
+                       },
+                       inputView: &UtxoViewpoint{
+                               Entries: map[bc.Hash]*storage.UtxoEntry{
+                                       bc.Hash{V0: 0}: storage.NewUtxoEntry(false, 0, true),
+                               },
+                       },
+                       err: true,
+               },
+               {
+                       block: &bc.Block{
+                               Transactions: []*bc.Tx{
+                                       &bc.Tx{
+                                               TxHeader: &bc.TxHeader{
+                                                       ResultIds: []*bc.Hash{},
+                                               },
+                                               SpentOutputIDs: []bc.Hash{
+                                                       bc.Hash{V0: 0},
+                                               },
+                                       },
+                               },
+                       },
+                       inputView: &UtxoViewpoint{
+                               Entries: map[bc.Hash]*storage.UtxoEntry{
+                                       bc.Hash{V0: 0}: storage.NewUtxoEntry(false, 0, false),
+                               },
+                       },
+                       fetchView: &UtxoViewpoint{
+                               Entries: map[bc.Hash]*storage.UtxoEntry{
+                                       bc.Hash{V0: 0}: storage.NewUtxoEntry(false, 0, true),
+                               },
+                       },
+                       err: false,
+               },
+       }
+
+       for i, c := range cases {
+               if err := c.inputView.ApplyBlock(c.block); c.err != (err != nil) {
+                       t.Errorf("want err = %v, get err = %v", c.err, err)
+               }
+               if c.err {
+                       continue
+               }
+               if !testutil.DeepEqual(c.inputView, c.fetchView) {
+                       t.Errorf("test case %d, want %v, get %v", i, c.fetchView, c.inputView)
+               }
+       }
+}
+
+func TestDetachBlock(t *testing.T) {
+       cases := []struct {
+               block     *bc.Block
+               inputView *UtxoViewpoint
+               fetchView *UtxoViewpoint
+               err       bool
+       }{
+               {
+                       block: &bc.Block{
+                               Transactions: []*bc.Tx{
+                                       &bc.Tx{
+                                               TxHeader: &bc.TxHeader{
+                                                       ResultIds: []*bc.Hash{},
+                                               },
+                                               SpentOutputIDs: []bc.Hash{
+                                                       bc.Hash{V0: 0},
+                                               },
+                                       },
+                               },
+                       },
+                       inputView: NewUtxoViewpoint(),
+                       err:       true,
+               },
+               {
+                       block: &bc.Block{
+                               Transactions: []*bc.Tx{
+                                       &bc.Tx{
+                                               TxHeader: &bc.TxHeader{
+                                                       ResultIds: []*bc.Hash{},
+                                               },
+                                               SpentOutputIDs: []bc.Hash{
+                                                       bc.Hash{V0: 0},
+                                               },
+                                       },
+                               },
+                       },
+                       inputView: &UtxoViewpoint{
+                               Entries: map[bc.Hash]*storage.UtxoEntry{
+                                       bc.Hash{V0: 0}: storage.NewUtxoEntry(false, 0, false),
+                               },
+                       },
+                       err: true,
+               },
+               {
+                       block: &bc.Block{
+                               Transactions: []*bc.Tx{
+                                       &bc.Tx{
+                                               TxHeader: &bc.TxHeader{
+                                                       ResultIds: []*bc.Hash{},
+                                               },
+                                               SpentOutputIDs: []bc.Hash{
+                                                       bc.Hash{V0: 0},
+                                               },
+                                       },
+                               },
+                       },
+                       inputView: &UtxoViewpoint{
+                               Entries: map[bc.Hash]*storage.UtxoEntry{
+                                       bc.Hash{V0: 0}: storage.NewUtxoEntry(false, 0, true),
+                               },
+                       },
+                       fetchView: &UtxoViewpoint{
+                               Entries: map[bc.Hash]*storage.UtxoEntry{
+                                       bc.Hash{V0: 0}: storage.NewUtxoEntry(false, 0, false),
+                               },
+                       },
+                       err: false,
+               },
+       }
+
+       for i, c := range cases {
+               if err := c.inputView.DetachBlock(c.block); c.err != (err != nil) {
+                       t.Errorf("want err = %v, get err = %v", c.err, err)
+               }
+               if c.err {
+                       continue
+               }
+               if !testutil.DeepEqual(c.inputView, c.fetchView) {
+                       t.Errorf("test case %d, want %v, get %v", i, c.fetchView, c.inputView)
+               }
+       }
+}