}
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 {
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)
package blockchain
import (
- "fmt"
- "time"
"context"
- "reflect"
+ "fmt"
"net/http"
+ "reflect"
+ "time"
log "github.com/sirupsen/logrus"
cmn "github.com/tendermint/tmlibs/common"
}
}
-
func (bcr *BlockchainReactor) info(ctx context.Context) (map[string]interface{}, error) {
return map[string]interface{}{
"is_configured": false,
})
}
-
// Used as a request object for api queries
type requestQuery struct {
Filter string `json:"filter,omitempty"`
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:
}
}
-
// BroadcastStatusRequest broadcasts `BlockStore` height.
func (bcR *BlockchainReactor) BroadcastStatusResponse() {
- block, _ := bcR.chain.State()
+ block := bcR.chain.BestBlock()
bcR.Switch.Broadcast(BlockchainChannel, struct{ BlockchainMessage }{NewStatusResponseMessage(block)})
}
+++ /dev/null
-// 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,
-}
+++ /dev/null
-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
"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"
)
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()}
return errors.Wrap(err, "marshaling Mainchain")
}
- db.Set(calcMainchainKey(hash), b)
- db.SetSync(nil, nil)
+ batch.Set(calcMainchainKey(hash), b)
return nil
}
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")
+++ /dev/null
-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)
-}
+++ /dev/null
-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")
- }
-}
--- /dev/null
+// 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,
+}
--- /dev/null
+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
--- /dev/null
+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
+}
"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"
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 {
}
}
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
}
}
+// 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)
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)
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()
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
}
--- /dev/null
+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
+}
--- /dev/null
+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)
+ }
+ }
+}
"github.com/bytom/crypto/sha3pool"
"github.com/bytom/protocol/bc"
"github.com/bytom/protocol/bc/legacy"
- "github.com/bytom/protocol/state"
)
// Generate genesis transaction
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))
TimestampMS: 1511318565142,
BlockCommitment: legacy.BlockCommitment{
TransactionsMerkleRoot: merkleRoot,
- AssetsMerkleRoot: snap.Tree.RootHash(),
},
Bits: 2161727821138738707,
},
if tx := GenerateGenesisTx(); tx == nil {
t.Errorf("Generate genesis tx failed")
}
-
- if block := GenerateGenesisBlock(); block == nil {
- t.Errorf("Generate genesis block failed")
- }
}
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
},
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...)
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
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")
+++ /dev/null
-// 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))
-}
}
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
}
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
"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"
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
block *legacy.Block
hash *bc.Hash
mainChain map[uint64]*bc.Hash
- snapshot *state.Snapshot
}
store Store
seedCaches *seed.SeedCaches
storeStatus := store.GetStoreStatus()
if storeStatus.Height == 0 {
- c.state.snapshot = state.Empty()
c.state.mainChain = make(map[uint64]*bc.Hash)
return c, nil
}
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
}
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()
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
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
+++ /dev/null
-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
-}
+++ /dev/null
-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
-}
+++ /dev/null
-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)
- }
-}
--- /dev/null
+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
+}
--- /dev/null
+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)
+ }
+ }
+}