From 91664a926abd093de630b56108c1b6ddbae7aa7d Mon Sep 17 00:00:00 2001 From: Paladz Date: Mon, 18 Dec 2017 10:08:38 +0800 Subject: [PATCH] Utxo storage (#196) * 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 --- blockchain/account/reserve.go | 7 +- blockchain/hsm_test.go | 12 +- blockchain/reactor.go | 13 +- blockchain/txdb/internal/storage/storage.pb.go | 162 ---------------------- blockchain/txdb/internal/storage/storage.proto | 34 ----- blockchain/txdb/mainchain.go | 7 +- blockchain/txdb/mainchain_test.go | 27 ++++ blockchain/txdb/snapshot.go | 103 -------------- blockchain/txdb/snapshot_test.go | 69 ---------- blockchain/txdb/storage/storage.pb.go | 120 ++++++++++++++++ blockchain/txdb/storage/storage.proto | 19 +++ blockchain/txdb/storage/utxo_entry.go | 20 +++ blockchain/txdb/store.go | 55 ++++---- blockchain/txdb/utxo_view.go | 69 ++++++++++ blockchain/txdb/utxo_view_test.go | 184 +++++++++++++++++++++++++ config/genesis.go | 7 - config/genesis_test.go | 4 - mining/mining.go | 17 +-- mining/mining_test.go | 66 --------- protocol/block.go | 27 +++- protocol/protocol.go | 47 +++---- protocol/recover.go | 72 ---------- protocol/state/snapshot.go | 140 ------------------- protocol/state/snapshot_test.go | 105 -------------- protocol/state/utxo_view.go | 96 +++++++++++++ protocol/state/utxo_view_test.go | 170 +++++++++++++++++++++++ 26 files changed, 800 insertions(+), 852 deletions(-) delete mode 100644 blockchain/txdb/internal/storage/storage.pb.go delete mode 100644 blockchain/txdb/internal/storage/storage.proto delete mode 100644 blockchain/txdb/snapshot.go delete mode 100644 blockchain/txdb/snapshot_test.go create mode 100644 blockchain/txdb/storage/storage.pb.go create mode 100644 blockchain/txdb/storage/storage.proto create mode 100644 blockchain/txdb/storage/utxo_entry.go create mode 100644 blockchain/txdb/utxo_view.go create mode 100644 blockchain/txdb/utxo_view_test.go delete mode 100644 mining/mining_test.go delete mode 100644 protocol/recover.go delete mode 100644 protocol/state/snapshot.go delete mode 100644 protocol/state/snapshot_test.go create mode 100644 protocol/state/utxo_view.go create mode 100644 protocol/state/utxo_view_test.go diff --git a/blockchain/account/reserve.go b/blockchain/account/reserve.go index 79ef71fa..4d300884 100755 --- a/blockchain/account/reserve.go +++ b/blockchain/account/reserve.go @@ -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 { diff --git a/blockchain/hsm_test.go b/blockchain/hsm_test.go index 27f0ef37..2d96fd07 100755 --- a/blockchain/hsm_test.go +++ b/blockchain/hsm_test.go @@ -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) diff --git a/blockchain/reactor.go b/blockchain/reactor.go index 4ff0603f..ab10fdb5 100755 --- a/blockchain/reactor.go +++ b/blockchain/reactor.go @@ -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 index 5994ce6b..00000000 --- a/blockchain/txdb/internal/storage/storage.pb.go +++ /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 index 2d8bc135..00000000 --- a/blockchain/txdb/internal/storage/storage.proto +++ /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 diff --git a/blockchain/txdb/mainchain.go b/blockchain/txdb/mainchain.go index ac8ebd55..90e5b14d 100644 --- a/blockchain/txdb/mainchain.go +++ b/blockchain/txdb/mainchain.go @@ -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 } diff --git a/blockchain/txdb/mainchain_test.go b/blockchain/txdb/mainchain_test.go index 92b16682..560e912d 100644 --- a/blockchain/txdb/mainchain_test.go +++ b/blockchain/txdb/mainchain_test.go @@ -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 index 45a22b51..00000000 --- a/blockchain/txdb/snapshot.go +++ /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 index ad03729c..00000000 --- a/blockchain/txdb/snapshot_test.go +++ /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 index 00000000..de7736e7 --- /dev/null +++ b/blockchain/txdb/storage/storage.pb.go @@ -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 index 00000000..9bff217b --- /dev/null +++ b/blockchain/txdb/storage/storage.proto @@ -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 index 00000000..af4d8d59 --- /dev/null +++ b/blockchain/txdb/storage/utxo_entry.go @@ -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 +} diff --git a/blockchain/txdb/store.go b/blockchain/txdb/store.go index 93984135..60618c60 100644 --- a/blockchain/txdb/store.go +++ b/blockchain/txdb/store.go @@ -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 index 00000000..5baa2db0 --- /dev/null +++ b/blockchain/txdb/utxo_view.go @@ -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 index 00000000..bb1531c9 --- /dev/null +++ b/blockchain/txdb/utxo_view_test.go @@ -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) + } + } +} diff --git a/config/genesis.go b/config/genesis.go index 1fbe28ca..cce2848c 100644 --- a/config/genesis.go +++ b/config/genesis.go @@ -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, }, diff --git a/config/genesis_test.go b/config/genesis_test.go index 2ffa91fc..5d73071d 100644 --- a/config/genesis_test.go +++ b/config/genesis_test.go @@ -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") - } } diff --git a/mining/mining.go b/mining/mining.go index 6b6d2feb..256d2abc 100644 --- a/mining/mining.go +++ b/mining/mining.go @@ -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 index 5c5fdc25..00000000 --- a/mining/mining_test.go +++ /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)) -} diff --git a/protocol/block.go b/protocol/block.go index 9c648ab0..3e177e9f 100755 --- a/protocol/block.go +++ b/protocol/block.go @@ -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 diff --git a/protocol/protocol.go b/protocol/protocol.go index abcd42a2..6962a4c9 100755 --- a/protocol/protocol.go +++ b/protocol/protocol.go @@ -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 index f77d97d4..00000000 --- a/protocol/recover.go +++ /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 index 53caedcf..00000000 --- a/protocol/state/snapshot.go +++ /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 index e4b5f0e7..00000000 --- a/protocol/state/snapshot_test.go +++ /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 index 00000000..82bd9d9f --- /dev/null +++ b/protocol/state/utxo_view.go @@ -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 index 00000000..296cbbb8 --- /dev/null +++ b/protocol/state/utxo_view_test.go @@ -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) + } + } +} -- 2.11.0